From 0900a4c6e0b7af30cfc64f0f7aa9ccb9894a3e61 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Fri, 12 Jun 2026 15:41:54 +0200 Subject: [PATCH 01/21] docs(research): record TODO(nowcast) decoding-scheme plan in lstm deep dive Per-step vs DMS vs produce-then-truncate comparison across the swiss benchmark, TSL, and this repo; action items before any nowcast experiment (per-step head for lstm, audit other models, optional per-step aux loss). Mirrors the inline TODO(nowcast) in liulian/models/torch/lstm.py. Co-Authored-By: Claude Fable 5 --- docs/research/entity-id-deep/lstm.md | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/research/entity-id-deep/lstm.md b/docs/research/entity-id-deep/lstm.md index 9f434e4..145e209 100644 --- a/docs/research/entity-id-deep/lstm.md +++ b/docs/research/entity-id-deep/lstm.md @@ -151,3 +151,32 @@ class LSTMAdapter(EntityAwareMixin, TorchModelAdapter): # changed inheritance **Uncertainties:** - Whether a learned `(h_0, c_0)` actually helps beyond the RNN's own capacity — for long sequences, the initial state gets overwritten quickly. Short-horizon tasks benefit more. - In multi-channel split, LSTM processes all `enc_in` channels jointly → per-sample entity id is ill-defined (*whose* station is this sample about?). Only unambiguous in per-entity split. Document this constraint. + +## 9. TODO(nowcast) — decoding scheme (recorded 2026-06-12) + +Three decoding schemes exist across the reference projects and this repo: + +| Scheme | Output | Supervision per forward | Used by | +|---|---|---|---| +| Per-time-step decoding | `(B, win_len, 1)` — linear head on **every** LSTM output; loss on the full NaN/pad-masked sequence (output NOT truncated) | `win_len` steps | swiss benchmark `LstmModel` (`model.py#L32`, `training.py:147`) — its **nowcast** models | +| Direct multi-step (DMS) | `(B, pred_len, c_out)` from the **last** hidden state | `pred_len` steps | **our `lstm.py`**; swiss benchmark `ExtrapoLstmModel('limo')`; TSL contract (DLinear `Linear(seq_len→pred_len)`, PatchTST flatten-head) | +| Produce-then-truncate | decoder emits `label_len+pred_len`, exp loop slices `outputs[:, -pred_len:]` | `pred_len` steps | TSL encoder-decoder family (`exp_long_term_forecasting.py:66`) | + +For `task='forecast'` (the entity-identifier matrix) DMS is the right tool and +matches both references. For `task='nowcast'` our last-hidden head is +**wasteful**: 1 supervised step per window (vs `win_len`), and covering a +series of length `T` needs `T` sliding windows ≈ `seq_len`× redundant compute +versus one per-step pass; the only upside is a homogeneous fixed-length +context per prediction. + +**Action items before any nowcast experiment (task #31):** +1. Add a per-step decoding head to `lstm.py` for `task='nowcast'` + (benchmark `LstmModel` style: head on every LSTM output, full-sequence + masked loss). +2. Audit the other adapters (dlinear / patchtst / …) for nowcast behaviour + and decide per model — DMS models may need a `pred_len=seq_len` mode + (cf. DLinear's existing branch at `models/DLinear.py:20`). +3. Optional research item: per-step auxiliary loss as multi-task for the + forecast setting (dense supervision without changing the primary head). + +See also the inline `TODO(nowcast)` block in `liulian/models/torch/lstm.py`. From b0826c0443d38c84cc3bf7d3f2497ac77d41442c Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Fri, 12 Jun 2026 15:45:07 +0200 Subject: [PATCH 02/21] =?UTF-8?q?docs:=20add=20task=20ledger=20(docs/tasks?= =?UTF-8?q?.md)=20=E2=80=94=20completed=20/=20in-progress=20/=20future?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Repo-visible record of work items (the agent session task list was not visible in the repo). Includes the nowcast decoding TODO, multi_channel coordinates injection, Case-2 outer sweep, sin/random backfill, Ray local_mode cleanup, and the completed 2026-05/06 milestones. Convention added to CLAUDE.md: update the ledger in the same commit when work finishes or a new item is decided. mkdocs nav: Task Ledger entry. Co-Authored-By: Claude Fable 5 --- CLAUDE.md | 3 ++ docs/tasks.md | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 3 files changed, 122 insertions(+) create mode 100644 docs/tasks.md diff --git a/CLAUDE.md b/CLAUDE.md index d11d413..bd79231 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,6 +51,9 @@ Layer → directory map: `tasks/`, `data/`, `models/` (ABC), `adapters/` (wrappe ## Conventions worth knowing up-front +- **Task ledger:** `docs/tasks.md` records completed / in-progress / future + work. Update it in the same commit whenever a work item finishes or a new + one is decided (agent session task IDs `#N` are mirrored there). - **Optional deps:** core is numpy + pyyaml only. Torch, ray, wandb, mkdocs are all extras. When importing an optional dep in library code, catch `ImportError` and raise with install hint (`pip install -e '.[logging]'`). - **Datasets go through manifests.** `manifests/*.yaml` defines fields/topology/integrity hash; loaders in `liulian/data/` read from these, not raw paths. - **Experiments live in `experiments//`** with their own config YAML + runner. They're separate from `tests/`. diff --git a/docs/tasks.md b/docs/tasks.md new file mode 100644 index 0000000..9c30c19 --- /dev/null +++ b/docs/tasks.md @@ -0,0 +1,118 @@ +# Task Ledger + +Living record of **completed / in-progress / future** work on the ML core +(`liulian-python`). Newest first inside each section. Every entry names its +artifacts (commits, files, run-tags) so it can be audited later. Agent +session task IDs (`#N`) are kept for traceability. + +> Federation-level (platform / UI / ops) work is tracked in the +> `liulian-docs` hub repo — see `docs/strategy/MOVED.md`. +> +> **Convention:** whenever a work item finishes or a new one is decided, +> update this file in the same commit/PR. + +## In progress + +- **swiss3dt dropout-tuned baselines** (#30) — swiss-river-{1990, 2010, + zurich} × lstm × {none, embedding}; dropout tuned `{0, 0.1}` + (reference LSTM has none); per-trial Ray seeding active. Jobs + 5874880-82, run-tags `swiss3dt-*-20260612`. Remaining identifier modes + (onehot / coordinates / sinusoidal / random) await scheme review. + +## Future + +- **Nowcast decoding scheme** (#31) — add a per-step decoding head to + `lstm.py` for `task='nowcast'` (benchmark `LstmModel` style: head on + every LSTM output, full-sequence masked loss); audit the other adapters + (DMS models may need a `pred_len=seq_len` mode); optional per-step + auxiliary loss for the forecast setting. Details: + `docs/research/entity-id-deep/lstm.md` §9 + inline `TODO(nowcast)` in + `liulian/models/torch/lstm.py`. **Do BEFORE any nowcast experiment.** +- **multi_channel coordinates injection** (#28) — pipeline never injects + `config['coordinates']` / `config['station_ids']` for + `ChannelTransparentWrapper`, so coordinates are offered only for + swiss × lstm (per_entity). Wire the injection, re-offer the + patchtst/dlinear pairs, rerun the invalidated swiss-river-1990 cells. + Also needs a NaN-masking story before any 2010/zurich multi_channel run. +- **Invalidate pre-2026-06-11 swiss coordinates results in research docs** + (#29) — those cells ran zero-vector identifiers (see Completed + 2026-06-11); mark them invalid in the 2026-05-15 slide / analysis / + heatmap figures and replace with swiss3 reruns. +- **Case 2: outer-sweep param-analysis entry point** (#26) — sweep a + transparent dim (e.g. `sinusoidal_dim`) while FIXING inner HPO to the + swept value; the only way to study these dims on per_entity swiss-lstm + (their features are frozen into the data loaders per run). Separate + entry point, not the matrix runner. +- **sin/random identifier backfill for traffic + electricity** (#17) — + deferred cells of the 2026-05 baseline matrix. +- **`hpo_local_mode` cleanup** — current Ray removed `local_mode`; the + config knob is dead and ~56 e2e tests fail on it + (`ray_optimizer.py:586`). Migrate the tests, drop the knob. +- **trainer.py predict-aggregate bug** — `TypeError` at + `trainer.py:623` (`sum(all_entity_ids, [])` with tuples); pre-existing, + one e2e test fails. Fix is a one-liner once approved. +- **results.json `model.*` shows pre-HPO defaults** — cosmetic: the model + section reflects the initial config, while tuned values are in + `hpo.best_hparams` (and `best_hparams.yaml`). Align when convenient. +- **Repo hygiene** — stray `ls` file at repo root (committed by mistake); + root-level demo screenshots; decide what stays. + +## Completed + +### 2026-06-12 + +- **results.json `hpo.best_hparams` populated** — producer alias + + builder fallback + regression tests (`tests/runtime/test_results_json.py`); + the three completed swiss3 none runs backfilled in place from + `best_hparams.yaml` (commit `b3acc80`). +- **Per-trial Ray seeding** — `_trainable` seeds + `seed + trial_index`; trials are reproducible and differ only through + sampled hyperparameters (commit `edec5d0`). Neither reference project + (TSL, swiss benchmark) seeds trials; neither does CV — both select on a + single fixed chronological hold-out, as we do. +- **Dropout for swiss lstm: tuned, not assumed** — reference `LstmModel` + has no dropout; ours applied 0.1 silently. Now HPO chooses `{0, 0.1}`; + config default 0.0. +- **LSTM decoding-scheme comment corrected** (commit `128fe03`) — ours is + direct multi-step (TSL contract; = benchmark `ExtrapoLstmModel('limo')`); + the benchmark's plain `LstmModel` decodes per time step and is NOT + truncated; produce-then-truncate is TSL's exp loop. +- **Branch reorg** — `feat/platform-upgrade-2026-05` merged to `main` + (merge `a3dc2f9`) and deleted (local + remote); new branches: + `feat/update-2026-06` (platform), `exp/entity-identifier-2026-06` + (current working branch). + +### 2026-06-11 + +- **swiss3 none baselines on UBELIX (gratis)** — verified genuine + (50 trials, 30 epochs, batch 32 fixed, no caps). Test denorm RMSE: + 1990 = 1.669 °C, 2010 = 1.646 °C, zurich = 1.562 °C. Run-tags + `swiss3-*-20260611` (kept for old-vs-new dropout comparison). +- **Coordinates identifier fixed** (commit `421f1e7`) — was silently + fake: with `graph_mode='none'` the topology never loaded and + `make_entity_features` zero-filled → every pre-fix swiss coordinates + cell measured "2 constant zero channels". Now: topology loads for + coordinates mode, coords min-max normalized per dataset, missing + coords RAISE (no silent zeros). +- **Per-station NaN drop** (commit `421f1e7`) — swiss-river-2010/zurich + CSVs carry NaNs (stations join/leave the network); a single NaN sample + drove the per_entity loss to NaN from epoch 1. Dropped per station; + segment breaks keep windows from spanning gaps; no-op for 1990. +- **swiss-river-2010 / zurich registered** in the matrix; per-experiment + launcher `experiments/entity_identifier/run_job.py`; swiss lstm + `batch_size` 8 → 32 (fixed, not HPO-tuned) (commit `e851ca6`). +- **HPO search spaces externalized** to `liulian/optim/search_spaces.yaml` + (mode-aware identifier params; front-end editable). Fixed the dead-knob + bug where lstm always tuned `embedding_size` even for `none`; per-trial + `ChannelTransparentWrapper` rebuild makes sinusoidal/random dims real + knobs in multi_channel (commit `ccba987`). + +### 2026-05 (baseline matrix — summary) + +- Entity-identifier baseline matrix on UBELIX: {swiss-river-1990, + traffic, electricity} × {lstm, patchtst, dlinear} × identifier modes; + UBELIX tier setup + cost tracking (paygo 10 CHF cap, + `jobs/ubelix_cost_tracker.py`); advisor progress slide and pred-true + aggregation analysis (`docs/research/2026-05-15-*`). + Caveat discovered 2026-06-11: the swiss *coordinates* cells of this + matrix are invalid (zero-vector identifiers, see above). diff --git a/mkdocs.yml b/mkdocs.yml index 09c486c..1f06695 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -27,6 +27,7 @@ theme: nav: - Home: index.md - Architecture: architecture.md + - Task Ledger: tasks.md - Guides: - CLI Usage: cli.md - Forecasting Pipeline: forecasting_pipeline.md From 412f5315eea2bba430f3a043baf96000237b0e9d Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Fri, 12 Jun 2026 15:47:01 +0200 Subject: [PATCH 03/21] style: wrap three over-long comment lines (CI ruff E501) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit swiss_river.py CASE-1/2 note, lstm.py benchmark URL line, trainer.py batch todo — comments only, no behaviour change. Co-Authored-By: Claude Fable 5 --- liulian/data/swiss_river.py | 8 ++++++-- liulian/models/torch/lstm.py | 5 +++-- liulian/runtime/trainer.py | 3 ++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/liulian/data/swiss_river.py b/liulian/data/swiss_river.py index dd42ff6..0b87281 100644 --- a/liulian/data/swiss_river.py +++ b/liulian/data/swiss_river.py @@ -114,8 +114,12 @@ def __init__( include_historical_predicted_y: bool = False, identifier_mode: str = 'none', id_integration: str = 'concat_to_x', - sinusoidal_dim: int = 16, # CASE 1 (inner HPO) tunable for multi_channel via search_spaces.yaml; per_entity (here) is frozen per run -> CASE 2 outer sweep for "dim vs metrics" analysis (separate entry point, TBD) - random_identifier_dim: int = 16, # same as sinusoidal_dim (see CASE 1 / CASE 2 note above) + # sinusoidal_dim / random_identifier_dim: CASE 1 (inner HPO) tunable + # for multi_channel via search_spaces.yaml; per_entity (here) is + # frozen per run -> CASE 2 outer sweep for "dim vs metrics" analysis + # (separate entry point, TBD — see docs/tasks.md). + sinusoidal_dim: int = 16, + random_identifier_dim: int = 16, random_identifier_seed: int = 2026, graph_mode: str = 'none', graphlet_num_hops: int = 1, diff --git a/liulian/models/torch/lstm.py b/liulian/models/torch/lstm.py index 7e25e59..d2a9701 100644 --- a/liulian/models/torch/lstm.py +++ b/liulian/models/torch/lstm.py @@ -89,8 +89,9 @@ def forward( # family as the swiss-river benchmark's ExtrapoLstmModel('limo'). # NOTE: the benchmark's plain LstmModel instead decodes PER TIME STEP # over the whole window ((B, win_len, 1), loss on the full masked - # sequence — its output is NOT truncated; see - # https://github.com/jajupmochi/swiss-river-network-benchmark/blob/main/swissrivernetwork/benchmark/model.py#L32 ). + # sequence — its output is NOT truncated; see model.py#L32 in + # github.com/jajupmochi/swiss-river-network-benchmark + # (swissrivernetwork/benchmark/model.py). # The produce-then-truncate pattern lives in TSL's exp loop # (outputs[:, -pred_len:]), not in the benchmark. # TODO(nowcast): for task='nowcast' this last-hidden head is wasteful diff --git a/liulian/runtime/trainer.py b/liulian/runtime/trainer.py index 801a8cd..6aa3a72 100644 --- a/liulian/runtime/trainer.py +++ b/liulian/runtime/trainer.py @@ -411,7 +411,8 @@ def evaluate( batch_y = batch[1] batch_x_mark = batch[2] batch_y_mark = batch[3] - batch_entity_ids = batch[4] if len(batch) > 4 else None # todo: comment what is batch[5] (and batch[4] in this case) + # todo: comment what batch[4] / batch[5] are in this case + batch_entity_ids = batch[4] if len(batch) > 4 else None batch_entity_idx = batch[5] if len(batch) > 5 else None batch_x = batch_x.float().to(self.device) # todo: always to float as timellm? From 0d89607e0e1f62f5bc892ec838bf96e6a494958a Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Fri, 12 Jun 2026 15:50:30 +0200 Subject: [PATCH 04/21] docs: task ledger moved to liulian-docs hub; keep pointer stub Ledger now lives at liulian-docs/docs/tasks/liulian-python/.md (per-repo sub-dirs, monthly files, topic tags). docs/tasks.md stays as a pointer so in-repo links and the mkdocs nav keep working; CLAUDE.md convention updated. Co-Authored-By: Claude Fable 5 --- CLAUDE.md | 7 +-- docs/tasks.md | 124 +++++--------------------------------------------- 2 files changed, 15 insertions(+), 116 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index bd79231..45789fc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,9 +51,10 @@ Layer → directory map: `tasks/`, `data/`, `models/` (ABC), `adapters/` (wrappe ## Conventions worth knowing up-front -- **Task ledger:** `docs/tasks.md` records completed / in-progress / future - work. Update it in the same commit whenever a work item finishes or a new - one is decided (agent session task IDs `#N` are mirrored there). +- **Task ledger:** lives in the `liulian-docs` hub repo at + `docs/tasks/liulian-python/.md` (monthly files, topic-tagged + entries; `docs/tasks.md` here is just a pointer stub). Update the ledger + whenever a work item finishes or a new one is decided. - **Optional deps:** core is numpy + pyyaml only. Torch, ray, wandb, mkdocs are all extras. When importing an optional dep in library code, catch `ImportError` and raise with install hint (`pip install -e '.[logging]'`). - **Datasets go through manifests.** `manifests/*.yaml` defines fields/topology/integrity hash; loaders in `liulian/data/` read from these, not raw paths. - **Experiments live in `experiments//`** with their own config YAML + runner. They're separate from `tests/`. diff --git a/docs/tasks.md b/docs/tasks.md index 9c30c19..7ed2e8f 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -1,118 +1,16 @@ -# Task Ledger +# Task Ledger — moved to the federation hub -Living record of **completed / in-progress / future** work on the ML core -(`liulian-python`). Newest first inside each section. Every entry names its -artifacts (commits, files, run-tags) so it can be audited later. Agent -session task IDs (`#N`) are kept for traceability. +The task ledger for this repo lives in the **`liulian-docs`** hub +(per-repo sub-directories, one file per month, topic-tagged entries): -> Federation-level (platform / UI / ops) work is tracked in the -> `liulian-docs` hub repo — see `docs/strategy/MOVED.md`. -> -> **Convention:** whenever a work item finishes or a new one is decided, -> update this file in the same commit/PR. +- **Live ledger:** `liulian-docs/docs/tasks/liulian-python/2026-06.md` +- **Frozen history:** `liulian-docs/docs/tasks/liulian-python/2026-05.md` +- **Conventions:** `liulian-docs/docs/tasks/index.md` -## In progress +GitHub: -- **swiss3dt dropout-tuned baselines** (#30) — swiss-river-{1990, 2010, - zurich} × lstm × {none, embedding}; dropout tuned `{0, 0.1}` - (reference LSTM has none); per-trial Ray seeding active. Jobs - 5874880-82, run-tags `swiss3dt-*-20260612`. Remaining identifier modes - (onehot / coordinates / sinusoidal / random) await scheme review. +> **Convention:** whenever a work item in this repo finishes or a new one +> is decided, update the ledger in `liulian-docs` (same working session; +> reference the `liulian-python` commit hashes there). -## Future - -- **Nowcast decoding scheme** (#31) — add a per-step decoding head to - `lstm.py` for `task='nowcast'` (benchmark `LstmModel` style: head on - every LSTM output, full-sequence masked loss); audit the other adapters - (DMS models may need a `pred_len=seq_len` mode); optional per-step - auxiliary loss for the forecast setting. Details: - `docs/research/entity-id-deep/lstm.md` §9 + inline `TODO(nowcast)` in - `liulian/models/torch/lstm.py`. **Do BEFORE any nowcast experiment.** -- **multi_channel coordinates injection** (#28) — pipeline never injects - `config['coordinates']` / `config['station_ids']` for - `ChannelTransparentWrapper`, so coordinates are offered only for - swiss × lstm (per_entity). Wire the injection, re-offer the - patchtst/dlinear pairs, rerun the invalidated swiss-river-1990 cells. - Also needs a NaN-masking story before any 2010/zurich multi_channel run. -- **Invalidate pre-2026-06-11 swiss coordinates results in research docs** - (#29) — those cells ran zero-vector identifiers (see Completed - 2026-06-11); mark them invalid in the 2026-05-15 slide / analysis / - heatmap figures and replace with swiss3 reruns. -- **Case 2: outer-sweep param-analysis entry point** (#26) — sweep a - transparent dim (e.g. `sinusoidal_dim`) while FIXING inner HPO to the - swept value; the only way to study these dims on per_entity swiss-lstm - (their features are frozen into the data loaders per run). Separate - entry point, not the matrix runner. -- **sin/random identifier backfill for traffic + electricity** (#17) — - deferred cells of the 2026-05 baseline matrix. -- **`hpo_local_mode` cleanup** — current Ray removed `local_mode`; the - config knob is dead and ~56 e2e tests fail on it - (`ray_optimizer.py:586`). Migrate the tests, drop the knob. -- **trainer.py predict-aggregate bug** — `TypeError` at - `trainer.py:623` (`sum(all_entity_ids, [])` with tuples); pre-existing, - one e2e test fails. Fix is a one-liner once approved. -- **results.json `model.*` shows pre-HPO defaults** — cosmetic: the model - section reflects the initial config, while tuned values are in - `hpo.best_hparams` (and `best_hparams.yaml`). Align when convenient. -- **Repo hygiene** — stray `ls` file at repo root (committed by mistake); - root-level demo screenshots; decide what stays. - -## Completed - -### 2026-06-12 - -- **results.json `hpo.best_hparams` populated** — producer alias + - builder fallback + regression tests (`tests/runtime/test_results_json.py`); - the three completed swiss3 none runs backfilled in place from - `best_hparams.yaml` (commit `b3acc80`). -- **Per-trial Ray seeding** — `_trainable` seeds - `seed + trial_index`; trials are reproducible and differ only through - sampled hyperparameters (commit `edec5d0`). Neither reference project - (TSL, swiss benchmark) seeds trials; neither does CV — both select on a - single fixed chronological hold-out, as we do. -- **Dropout for swiss lstm: tuned, not assumed** — reference `LstmModel` - has no dropout; ours applied 0.1 silently. Now HPO chooses `{0, 0.1}`; - config default 0.0. -- **LSTM decoding-scheme comment corrected** (commit `128fe03`) — ours is - direct multi-step (TSL contract; = benchmark `ExtrapoLstmModel('limo')`); - the benchmark's plain `LstmModel` decodes per time step and is NOT - truncated; produce-then-truncate is TSL's exp loop. -- **Branch reorg** — `feat/platform-upgrade-2026-05` merged to `main` - (merge `a3dc2f9`) and deleted (local + remote); new branches: - `feat/update-2026-06` (platform), `exp/entity-identifier-2026-06` - (current working branch). - -### 2026-06-11 - -- **swiss3 none baselines on UBELIX (gratis)** — verified genuine - (50 trials, 30 epochs, batch 32 fixed, no caps). Test denorm RMSE: - 1990 = 1.669 °C, 2010 = 1.646 °C, zurich = 1.562 °C. Run-tags - `swiss3-*-20260611` (kept for old-vs-new dropout comparison). -- **Coordinates identifier fixed** (commit `421f1e7`) — was silently - fake: with `graph_mode='none'` the topology never loaded and - `make_entity_features` zero-filled → every pre-fix swiss coordinates - cell measured "2 constant zero channels". Now: topology loads for - coordinates mode, coords min-max normalized per dataset, missing - coords RAISE (no silent zeros). -- **Per-station NaN drop** (commit `421f1e7`) — swiss-river-2010/zurich - CSVs carry NaNs (stations join/leave the network); a single NaN sample - drove the per_entity loss to NaN from epoch 1. Dropped per station; - segment breaks keep windows from spanning gaps; no-op for 1990. -- **swiss-river-2010 / zurich registered** in the matrix; per-experiment - launcher `experiments/entity_identifier/run_job.py`; swiss lstm - `batch_size` 8 → 32 (fixed, not HPO-tuned) (commit `e851ca6`). -- **HPO search spaces externalized** to `liulian/optim/search_spaces.yaml` - (mode-aware identifier params; front-end editable). Fixed the dead-knob - bug where lstm always tuned `embedding_size` even for `none`; per-trial - `ChannelTransparentWrapper` rebuild makes sinusoidal/random dims real - knobs in multi_channel (commit `ccba987`). - -### 2026-05 (baseline matrix — summary) - -- Entity-identifier baseline matrix on UBELIX: {swiss-river-1990, - traffic, electricity} × {lstm, patchtst, dlinear} × identifier modes; - UBELIX tier setup + cost tracking (paygo 10 CHF cap, - `jobs/ubelix_cost_tracker.py`); advisor progress slide and pred-true - aggregation analysis (`docs/research/2026-05-15-*`). - Caveat discovered 2026-06-11: the swiss *coordinates* cells of this - matrix are invalid (zero-vector identifiers, see above). +This stub stays so in-repo links and the mkdocs nav keep working. From 6900e1978b6c0e63e8b9866a89e183091171c515 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Fri, 12 Jun 2026 15:52:32 +0200 Subject: [PATCH 05/21] style: ruff-format entity_mixin.py (CI format check) Co-Authored-By: Claude Fable 5 --- liulian/models/torch/entity_mixin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/liulian/models/torch/entity_mixin.py b/liulian/models/torch/entity_mixin.py index 200dac9..01bcf20 100644 --- a/liulian/models/torch/entity_mixin.py +++ b/liulian/models/torch/entity_mixin.py @@ -137,7 +137,7 @@ def forward( x_enc = self.enc_proj(torch.cat([x_enc, emb], dim=-1)) # Also augment x_dec for encoder-decoder models # todo: this is not needed for non-enc-dec models - if x_dec is not None: # todo: test this part + if x_dec is not None: # todo: test this part T_dec = x_dec.size(1) e_id_scalar = e_ids[:, 0:1] # (B, 1) dec_emb = self.embedding(e_id_scalar.expand(-1, T_dec)) # (B, T_dec, embedding_size) From 6f35a4a456c5dadcbaaee2fbfa2e11135a505a1d Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Fri, 12 Jun 2026 16:24:40 +0200 Subject: [PATCH 06/21] feat(optim): make data-layer dims genuinely tunable on per_entity sinusoidal_dim / random_identifier_dim were gated out of per_entity HPO because the loaders are frozen per run (a sampled dim would have been a dead knob). Per user requirement they are now REAL inner-HPO knobs in both split modes: - make_trainable gains loaders_factory: when a trial samples a DATA_LAYER_DIM_KEYS param, the trial rebuilds dataset+loaders with the sampled dim inside the Ray worker and re-syncs enc_in from a real batch - experiment.py passes a zero-capture factory (build_dataset+build_loaders) and, post-HPO, rebuilds the loaders with the WINNING dim before the final checkpoint-load + evaluation (enc_in synced on best_args too) - resolve_search_space: per_entity gating removed; DATA_LAYER_DIM_KEYS promoted to the public contract Verified by a real 4-trial HPO on swiss-river-zurich+sinusoidal: trials sampled dims {3, 14, 16, 17}, best_config carries sinusoidal_dim=3, and the final evaluation ran on rebuilt dim-3 loaders with finite metrics. Co-Authored-By: Claude Fable 5 --- liulian/data/swiss_river.py | 10 +++++---- liulian/optim/ray_optimizer.py | 30 ++++++++++++++++++++++++--- liulian/optim/search_spaces.py | 19 +++++++++-------- liulian/optim/search_spaces.yaml | 15 +++++++------- liulian/runtime/experiment.py | 35 ++++++++++++++++++++++++++++++++ 5 files changed, 85 insertions(+), 24 deletions(-) diff --git a/liulian/data/swiss_river.py b/liulian/data/swiss_river.py index 0b87281..a05a6c9 100644 --- a/liulian/data/swiss_river.py +++ b/liulian/data/swiss_river.py @@ -114,10 +114,12 @@ def __init__( include_historical_predicted_y: bool = False, identifier_mode: str = 'none', id_integration: str = 'concat_to_x', - # sinusoidal_dim / random_identifier_dim: CASE 1 (inner HPO) tunable - # for multi_channel via search_spaces.yaml; per_entity (here) is - # frozen per run -> CASE 2 outer sweep for "dim vs metrics" analysis - # (separate entry point, TBD — see docs/tasks.md). + # sinusoidal_dim / random_identifier_dim: inner-HPO tunable in BOTH + # split modes (since 2026-06-12). per_entity trials rebuild this + # dataset + its loaders with the sampled dim (loaders_factory in + # optim/ray_optimizer.make_trainable); multi_channel rebuilds the + # ChannelTransparentWrapper instead. The CASE-2 outer sweep (ledger + # #26) remains for systematic dim-vs-metrics curves. sinusoidal_dim: int = 16, random_identifier_dim: int = 16, random_identifier_seed: int = 2026, diff --git a/liulian/optim/ray_optimizer.py b/liulian/optim/ray_optimizer.py index f779bae..d93193a 100644 --- a/liulian/optim/ray_optimizer.py +++ b/liulian/optim/ray_optimizer.py @@ -36,6 +36,7 @@ def make_trainable( base_config: Dict[str, Any], model_factory: Optional[Callable[[Any], Any]] = None, save_checkpoints: bool = True, + loaders_factory: Optional[Callable[[Dict[str, Any]], Dict[str, Any]]] = None, ) -> Callable[[Dict[str, Any]], None]: """Create a Ray Tune trainable from liulian components. @@ -58,6 +59,14 @@ def make_trainable( save_checkpoints: If ``True`` save a ``.pth`` model state-dict **every epoch** alongside the Ray report. Required for post-HPO checkpoint trimming and best-model loading. + loaders_factory: Optional callable ``(merged_config) -> loaders``. + Invoked INSIDE the trial (Ray worker) when the trial config + samples a data-layer dim (``DATA_LAYER_DIM_KEYS``, e.g. + ``sinusoidal_dim``): those features are baked into the windows + at dataset-build time, so the shared ``loaders`` (built with + the base dim) must be rebuilt for the trial — otherwise the + sampled value would be a dead knob. ``enc_in`` is re-synced + from the rebuilt loaders automatically. Returns: A callable ``(config: dict) -> None`` suitable for ``tune.run``. @@ -103,6 +112,21 @@ def _trainable(config: Dict[str, Any]) -> None: if hasattr(args, k): setattr(args, k, v) + # Data-layer dims (entity-feature width) are baked into the loaders + # at dataset-build time. When this trial samples one, rebuild the + # loaders for THIS trial and sync enc_in from a real batch — + # otherwise the sampled value would be a dead knob. + from liulian.optim.search_spaces import DATA_LAYER_DIM_KEYS + + trial_loaders = loaders + if loaders_factory is not None and any(k in config for k in DATA_LAYER_DIM_KEYS): + trial_loaders = loaders_factory(merged) + xb = next(iter(trial_loaders['train']))[0] + enc_in = int(xb.shape[-1]) + merged['enc_in'] = enc_in + if hasattr(args, 'enc_in'): + args.enc_in = enc_in + # Build fresh model — use factory if available if model_factory is not None: model = model_factory(args) @@ -139,9 +163,9 @@ def _trainable(config: Dict[str, Any]) -> None: checkpoint_dir=trial_checkpoint_dir, ) - train_loader = loaders['train'] - val_loader = loaders['val'] - test_loader = loaders.get('test') + train_loader = trial_loaders['train'] + val_loader = trial_loaders['val'] + test_loader = trial_loaders.get('test') # Build epoch callback for per-epoch Ray reporting metric_name = merged.get('metric', 'loss') diff --git a/liulian/optim/search_spaces.py b/liulian/optim/search_spaces.py index 1da5d79..d743119 100644 --- a/liulian/optim/search_spaces.py +++ b/liulian/optim/search_spaces.py @@ -925,9 +925,11 @@ def get_asha_preset(name: str = 'default') -> Dict[str, Any]: _YAML_PATH = Path(__file__).with_name('search_spaces.yaml') -# Identifier params whose feature is built in the DATA layer (frozen per HPO run -# for per_entity splits) — gated out of inner-HPO for per_entity models. -_DATA_LAYER_DIMS = frozenset({'sinusoidal_dim', 'random_identifier_dim'}) +# Identifier params whose feature is built in the DATA layer. Tuning one in a +# trial requires REBUILDING the data loaders for that trial (and syncing +# enc_in) — wired via make_trainable(loaders_factory=...) in ray_optimizer.py +# and the post-HPO rebuild in runtime/experiment.py. +DATA_LAYER_DIM_KEYS: tuple[str, ...] = ('sinusoidal_dim', 'random_identifier_dim') def _spec_to_tune(spec: Dict[str, Any]) -> Any: @@ -990,15 +992,14 @@ def _resolve_from_yaml(model: str, data: str, identifier_mode: str, id_integrati id_extra = cfg['identifier_spaces'].get(identifier_mode, {}) or {} has_emb = identifier_mode in ('embedding', 'embedding_idx') skip_emb = model == 'patchtst' and has_emb and id_integration == 'add_after_patch' - # Transparent-feature dims are tunable ONLY for multi_channel models; for - # per_entity (swiss-lstm) the feature is frozen in the data loaders, so a - # tuned value would be a dead knob — gate it out (use the Case-2 sweep). - is_per_entity = model == 'lstm' and data.startswith('swiss-river') + # Transparent-feature dims (DATA_LAYER_DIM_KEYS) are live knobs in BOTH + # split modes: multi_channel rebuilds the wrapper per trial; per_entity + # rebuilds the data loaders per trial (loaders_factory in + # ray_optimizer.make_trainable) so the sampled dim genuinely changes the + # inputs. for name, spec in id_extra.items(): if name == 'embedding_size' and skip_emb: continue - if name in _DATA_LAYER_DIMS and is_per_entity: - continue space[name] = _spec_to_tune(spec) return space diff --git a/liulian/optim/search_spaces.yaml b/liulian/optim/search_spaces.yaml index a9280c1..b45b16c 100644 --- a/liulian/optim/search_spaces.yaml +++ b/liulian/optim/search_spaces.yaml @@ -72,14 +72,13 @@ identifier_spaces: embedding: embedding_size: {dist: randint, low: 1, high: 31} # ref: randint(1, 31). Skipped for patchtst+add_after_patch. - # sinusoidal_dim / random_identifier_dim ARE tunable (CASE 1 — inner HPO), - # but ONLY for multi_channel models: the ChannelTransparentWrapper is rebuilt - # per HPO trial (see runtime/experiment.py), so a tuned dim takes effect. - # For per_entity (swiss-lstm) the transparent feature is baked into the FROZEN - # data loaders (built once per run, not per trial), so resolve_search_space() - # GATES these knobs out there — tuning them would be a dead knob. To study - # "dim vs metrics" for swiss-lstm, use the CASE 2 outer sweep (separate - # param-analysis entry point — recorded, not yet built; see swiss_river.py FIXME). + # sinusoidal_dim / random_identifier_dim are REAL inner-HPO knobs in BOTH + # split modes (since 2026-06-12): multi_channel rebuilds the + # ChannelTransparentWrapper per trial; per_entity REBUILDS THE DATA LOADERS + # per trial (the feature is baked into the windows) and re-syncs enc_in — + # see make_trainable(loaders_factory=...) in optim/ray_optimizer.py and the + # post-HPO rebuild in runtime/experiment.py. The CASE-2 outer sweep (ledger + # item #26) remains for systematic dim-vs-metrics curves at a fixed budget. sinusoidal: sinusoidal_dim: {dist: randint, low: 1, high: 31} random: diff --git a/liulian/runtime/experiment.py b/liulian/runtime/experiment.py index d2257b8..0028135 100644 --- a/liulian/runtime/experiment.py +++ b/liulian/runtime/experiment.py @@ -478,6 +478,14 @@ def _model_factory(args): else: _model_factory = None + def _loaders_factory(cfg: dict) -> dict: + """Rebuild dataset + loaders for a trial that tunes a + data-layer dim (runs inside the Ray worker; captures + nothing, so the closure stays small).""" + from liulian.pipeline import build_dataset, build_loaders + + return build_loaders(build_dataset(cfg), cfg) + trainable = None try: from liulian.optim.ray_optimizer import make_trainable @@ -494,6 +502,7 @@ def _model_factory(args): base_config=self.config, model_factory=_model_factory, save_checkpoints=_save_ckpts, + loaders_factory=_loaders_factory, ) except Exception as exc: logger.warning('Could not build HPO trainable: %s', exc) @@ -544,6 +553,30 @@ def _model_factory(args): # Retrain with best config best_cfg = {**self.config, **hpo_result.best_config} best_cfg.pop('search_space', None) + + # If the best trial tuned a data-layer dim, the frozen loaders + # were built with the BASE dim — rebuild with the winning dim so + # the final model/evaluation matches the best trial's input space. + from liulian.optim.search_spaces import DATA_LAYER_DIM_KEYS + + _best_dims = {k: hpo_result.best_config[k] for k in DATA_LAYER_DIM_KEYS if k in hpo_result.best_config} + _rebuilt_enc_in: int | None = None + if _best_dims: + from liulian.pipeline import build_dataset, build_loaders + + loaders = build_loaders(build_dataset(best_cfg), best_cfg) + train_loader = loaders['train'] + val_loader = loaders['val'] + test_loader = loaders.get('test') + _xb = next(iter(train_loader))[0] + _rebuilt_enc_in = int(_xb.shape[-1]) + best_cfg['enc_in'] = _rebuilt_enc_in + logger.info( + 'Rebuilt loaders for best data-layer dims %s (enc_in=%d)', + _best_dims, + _rebuilt_enc_in, + ) + trainer = ForecastTrainer( config=best_cfg, checkpoint_dir=ckpt_dir, @@ -556,6 +589,8 @@ def _model_factory(args): from types import SimpleNamespace as _NS best_args = _NS(**{**vars(model_args), **hpo_result.best_config}) + if _rebuilt_enc_in is not None and hasattr(best_args, 'enc_in'): + best_args.enc_in = _rebuilt_enc_in if _model_factory is not None: torch_model = _model_factory(best_args) else: From 8611acdeea88120400feaee7b50df8ee714a75ad Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Fri, 12 Jun 2026 21:37:48 +0200 Subject: [PATCH 07/21] feat(models): unify transparent identifiers behind EntityTransparentWrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-entity transparent identifiers (onehot/coordinates/sinusoidal/random/ numeric_id) now follow the same mechanism as embedding and multi_channel: MODEL-layer injection via a wrapper that is rebuilt per HPO trial. - EntityTransparentWrapper: per-sample fixed-feature concat (NO projection, no learnable params); table built via the data-layer make_entity_features for constructive bitwise equivalence with the legacy bake-in - id_injection config knob: 'model' (canonical) | 'data' (legacy fallback); dataset skips baking under 'model' while still emitting entity ids - pipeline: enc_in widened by the identifier dim + wrapper mounted - trainer: pass_entity_ids covers model-injected transparent modes - experiment: per-trial factory rebuilds inner model at enc_in = base + dim; loaders_factory + post-HPO loader rebuild now gated to id_injection='data' - FIX latent overwrite bug: SwissRiverDataset.super().__init__ silently reset sinusoidal_dim / random_identifier_dim / random_identifier_seed / id_injection to parent defaults — custom dims never reached the data path (this also invalidated the loaders_factory dim-tuning verification; v2 verification now checks trial checkpoint weight shapes) - tests: 15-case equivalence suite (table / layout / swiss end-to-end) Verified: 4-trial HPO on zurich+sinusoidal — every trial checkpoint's LSTM input width == 1 + sampled dim ({3,14,16,17}), best=3, finite final metrics; 358 guard tests green. Co-Authored-By: Claude Fable 5 --- experiments/swiss_river/default_config.yaml | 1 + liulian/config.py | 5 + liulian/data/swiss_river.py | 16 +++ liulian/data/ts/timeseriesdataset.py | 15 +- liulian/models/torch/entity_mixin.py | 116 +++++++++++++++ liulian/pipeline.py | 82 +++++++++++ liulian/runtime/experiment.py | 61 +++++++- liulian/runtime/trainer.py | 16 ++- .../test_transparent_injection_equivalence.py | 136 ++++++++++++++++++ 9 files changed, 435 insertions(+), 13 deletions(-) create mode 100644 tests/models/torch/test_transparent_injection_equivalence.py diff --git a/experiments/swiss_river/default_config.yaml b/experiments/swiss_river/default_config.yaml index 1c42d15..597d823 100644 --- a/experiments/swiss_river/default_config.yaml +++ b/experiments/swiss_river/default_config.yaml @@ -46,6 +46,7 @@ include_historical_predicted_y: false # ── Entity identifiers ───────────────────────────────────────────────── identifier_mode: embedding # none | embedding | embedding_idx | onehot | numeric_id | coordinates | sinusoidal | descriptors id_integration: concat_to_x # concat_to_x | add_to_x | add_after_patch (PatchTST+embedding) +id_injection: model # model (wrapper, per-trial rebuild — canonical) | data (legacy bake-in) embedding_size: 10 num_embeddings: null # Auto-detected from dataset if null diff --git a/liulian/config.py b/liulian/config.py index 1c2cd96..dbe430b 100644 --- a/liulian/config.py +++ b/liulian/config.py @@ -76,6 +76,11 @@ # ── Entity identifiers ────────────────────────────────────────── 'identifier_mode': 'embedding', 'id_integration': 'concat_to_x', + # 'model' (canonical since 2026-06-12): transparent identifiers injected + # by EntityTransparentWrapper, rebuilt per HPO trial — unified with the + # embedding / multi_channel wrappers; inputs are bitwise-identical to the + # legacy 'data' bake-in (see test_transparent_injection_equivalence.py). + 'id_injection': 'model', 'embedding_size': 10, 'num_embeddings': None, 'sinusoidal_dim': 16, diff --git a/liulian/data/swiss_river.py b/liulian/data/swiss_river.py index a05a6c9..fc15e42 100644 --- a/liulian/data/swiss_river.py +++ b/liulian/data/swiss_river.py @@ -114,6 +114,11 @@ def __init__( include_historical_predicted_y: bool = False, identifier_mode: str = 'none', id_integration: str = 'concat_to_x', + # 'model' (canonical) -> transparent identifiers injected by + # EntityTransparentWrapper (rebuilt per HPO trial); 'data' -> legacy + # bake-in at window-build time. Both produce bitwise-identical + # model inputs (see test_transparent_injection_equivalence.py). + id_injection: str = 'data', # sinusoidal_dim / random_identifier_dim: inner-HPO tunable in BOTH # split modes (since 2026-06-12). per_entity trials rebuild this # dataset + its loaders with the sampled dim (loaders_factory in @@ -137,6 +142,7 @@ def __init__( self.sinusoidal_dim = int(sinusoidal_dim) self.random_identifier_dim = int(random_identifier_dim) self.random_identifier_seed = int(random_identifier_seed) + self.id_injection = str(id_injection).strip().lower() project_root = Path(__file__).resolve().parents[2] self.root_path = Path(root_path) if root_path else project_root / 'dataset' / 'swiss_river' @@ -225,6 +231,15 @@ def __init__( station_ids=self.station_ids, identifier_mode=identifier_mode, id_integration=id_integration, + # These MUST be forwarded: TimeSeriesDataset.__init__ assigns + # self.id_injection / sinusoidal_dim / random_identifier_dim / + # random_identifier_seed and would silently overwrite our earlier + # assignments with the parent defaults (same trap as the entity + # scaler below; bit us 2026-06-12 — custom dims were reset to 16). + id_injection=id_injection, + sinusoidal_dim=sinusoidal_dim, + random_identifier_dim=random_identifier_dim, + random_identifier_seed=random_identifier_seed, topology=topology, graph_mode=graph_mode, graph_metadata={'graph_name': self.graph_name}, @@ -471,6 +486,7 @@ def _build_pe_split(self, split_name: str) -> TimeSeriesSplit: station_ids=self.station_ids, identifier_mode=self.identifier_mode, id_integration=self.id_integration, + id_injection=self.id_injection, coordinates=(self.topology.coordinates if self.topology else {}), station_name=station, sinusoidal_dim=self.sinusoidal_dim, diff --git a/liulian/data/ts/timeseriesdataset.py b/liulian/data/ts/timeseriesdataset.py index 8e515ad..0e45ab2 100644 --- a/liulian/data/ts/timeseriesdataset.py +++ b/liulian/data/ts/timeseriesdataset.py @@ -148,7 +148,7 @@ def make_entity_features( dtype=torch.float32, ) norm = torch.linalg.norm(vec) - if torch.isfinite(norm) and float(norm) > 0: + if torch.isfinite(norm) and float(norm) > 0: # todo: what happens if norm is not finite or not 0? vec = vec / norm elif mode == 'descriptors': if descriptors and station_name in descriptors: @@ -800,6 +800,14 @@ def __init__( station_ids: list[str] | None = None, identifier_mode: str = 'none', id_integration: str = 'concat_to_x', + # 'data' -> transparent identifier features are baked into the + # windows here (legacy / fallback path). + # 'model' -> the dataset emits ONLY base features; injection happens + # in EntityTransparentWrapper (models/torch/entity_mixin), + # which rebuilds per HPO trial — unified with the + # embedding / multi_channel wrapper mechanism. + # Entity ids are emitted either way (the collate adds entity_idx). + id_injection: str = 'data', coordinates: dict[str, tuple[float, float]] | None = None, station_name: str | None = None, sinusoidal_dim: int = 16, @@ -842,6 +850,7 @@ def __init__( self.station_ids = station_ids or [] self.identifier_mode = identifier_mode self.id_integration = id_integration + self.id_injection = str(id_injection).strip().lower() self.coordinates = coordinates or {} self.station_name = station_name self.sinusoidal_dim = int(sinusoidal_dim) @@ -1142,7 +1151,7 @@ def _precompute_tensors( 'descriptors', 'numeric_id', } - if self.identifier_mode in _transparent_modes and self.station_name is None: + if self.identifier_mode in _transparent_modes and self.station_name is None and self.id_injection == 'data': logger.warning( # todo: test this 'identifier_mode=%r requires station_name to generate ' 'entity features in the data layer, but station_name is ' @@ -1152,7 +1161,7 @@ def _precompute_tensors( 'the dataset sets station_name for each station.', self.identifier_mode, ) - if self.identifier_mode != 'none' and self.station_name is not None: + if self.identifier_mode != 'none' and self.station_name is not None and self.id_injection == 'data': ent = make_entity_features( self.station_name, self.station_ids, diff --git a/liulian/models/torch/entity_mixin.py b/liulian/models/torch/entity_mixin.py index 01bcf20..9825061 100644 --- a/liulian/models/torch/entity_mixin.py +++ b/liulian/models/torch/entity_mixin.py @@ -369,6 +369,122 @@ def _build_channel_features( raise ValueError(f'Unsupported transparent mode for channel wrapper: {mode!r}') +class EntityTransparentWrapper(nn.Module): + """Per-sample transparent (non-learned) feature injection — per_entity mode. + + Model-layer equivalent of the data-layer bake-in + (``liulian.data.ts.timeseriesdataset.make_entity_features``): looks up a + FIXED ``(num_stations, D)`` feature table by per-sample entity index, + tiles it over time and CONCATENATES it to ``x_enc`` — no projection, no + learnable parameters. The inner model must therefore be built with + ``enc_in = base_features + D``. + + The table rows are built by calling the data-layer + ``make_entity_features`` per station, so the injected values are + BITWISE-IDENTICAL to what the data-layer bake-in would have produced + (see tests/models/torch/test_transparent_injection_equivalence.py). + This is what keeps wrapper-injected runs comparable with the + data-injected baselines. + + Unlike :class:`EntityWrapper` (embedding mode) there is no projection + back to the original width — transparency means the raw fixed vector IS + the input. ``x_dec`` is passed through untouched (decoder injection is + not needed for the current per_entity matrix; LSTM ignores ``x_dec``). + + Parameters + ---------- + inner_model : nn.Module + TSL-style model built with ``enc_in = base + feature_dim``. + mode : str + Transparent identifier mode (``onehot`` / ``coordinates`` / + ``sinusoidal`` / ``random`` / ``numeric_id``). + station_ids : list[str] + Ordered station ids; row ``i`` of the table belongs to + ``station_ids[i]`` (must match the dataset's entity indexing). + coordinates : dict | None + Station-name to ``(x, y)`` mapping (required for ``coordinates``). + sinusoidal_dim / random_dim / random_seed + Forwarded to ``make_entity_features``. + """ + + def __init__( + self, + inner_model: nn.Module, + mode: str, + station_ids: list[str], + *, + coordinates: dict[str, tuple[float, float]] | None = None, + sinusoidal_dim: int = 16, + random_dim: int = 16, + random_seed: int = 2026, + ) -> None: + super().__init__() + # Data-layer feature builder reused on purpose: constructive + # equivalence with the bake-in path (NOT make_channel_features, + # whose sinusoidal formula differs — see ledger 2026-06-12). + from liulian.data.ts.timeseriesdataset import make_entity_features + + self.inner = inner_model + self.mode = str(mode) + self.station_ids = [str(s) for s in station_ids] + self._coordinates = coordinates + self.sinusoidal_dim = int(sinusoidal_dim) + self.random_dim = int(random_dim) + self.random_seed = int(random_seed) + + rows = [] + for name in self.station_ids: + block = make_entity_features( + name, + self.station_ids, + self.mode, + seq_len=1, + coordinates=coordinates, + sinusoidal_dim=self.sinusoidal_dim, + random_dim=self.random_dim, + random_seed=self.random_seed, + ) + if block is None: + raise ValueError( + f'mode {mode!r} produces no transparent features — EntityTransparentWrapper is not applicable.' + ) + rows.append(block[0]) + self.register_buffer('feature_table', torch.stack(rows)) # (N, D) + + @property + def feature_dim(self) -> int: + """Width ``D`` of the injected feature block.""" + return int(self.feature_table.shape[-1]) + + def forward( + self, + x_enc: torch.Tensor, + x_mark_enc: torch.Tensor | None = None, + x_dec: torch.Tensor | None = None, + x_mark_dec: torch.Tensor | None = None, + mask: torch.Tensor | None = None, + entity_ids: torch.Tensor | None = None, + ) -> torch.Tensor: + """Concat the per-sample fixed feature block to ``x_enc``. + + ``entity_ids``: integer station indices of shape ``(B,)`` — supplied + by the trainer from the loader's per-sample entity index. + """ + if entity_ids is None: + raise ValueError( + 'EntityTransparentWrapper requires entity_ids (per-sample ' + 'station indices); check the trainer wiring ' + '(pass_entity_ids).' + ) + rows = self.feature_table[entity_ids.long()] # (B, D) + ent = rows.unsqueeze(1).expand(-1, x_enc.size(1), -1) # (B, T, D) + # Same layout as the data-layer bake-in: identifier block LAST. + x_enc = torch.cat([x_enc, ent.to(x_enc.dtype)], dim=-1) + if mask is not None: + return self.inner(x_enc, x_mark_enc, x_dec, x_mark_dec, mask) + return self.inner(x_enc, x_mark_enc, x_dec, x_mark_dec) + + class ChannelTransparentWrapper(nn.Module): """Per-channel transparent (non-learned) feature injection for multi_channel mode. diff --git a/liulian/pipeline.py b/liulian/pipeline.py index a59d6fa..159c225 100644 --- a/liulian/pipeline.py +++ b/liulian/pipeline.py @@ -195,6 +195,7 @@ def build_dataset(config: Dict[str, Any]) -> Any: include_historical_predicted_y=config['include_historical_predicted_y'], identifier_mode=config['identifier_mode'], id_integration=config['id_integration'], + id_injection=config.get('id_injection', 'data'), sinusoidal_dim=config.get('sinusoidal_dim', 16), random_identifier_dim=config.get('random_identifier_dim', 16), random_identifier_seed=config.get('random_identifier_seed', 2026), @@ -321,6 +322,50 @@ def print_dataset_summary(dataset: Any) -> None: raise ValueError(f'Dataset missing expected split: "{name}".') from None +_MODEL_INJECTABLE_TRANSPARENT_MODES = frozenset({'onehot', 'coordinates', 'sinusoidal', 'random', 'numeric_id'}) + + +def _uses_model_transparent_injection(config: Dict[str, Any]) -> bool: + """True when transparent identifiers are injected by the MODEL wrapper. + + ``id_injection='model'`` + a transparent mode + non-multi_channel split: + the dataset emits only base features and ``EntityTransparentWrapper`` + concatenates the fixed per-station block per batch (rebuilt per HPO + trial). multi_channel keeps its own ChannelTransparentWrapper path. + """ + mode = str(config.get('identifier_mode', 'none')).strip().lower() + if str(config.get('id_injection', 'data')).strip().lower() != 'model': + return False + if config.get('split_mode') == 'multi_channel': + return False + if mode == 'descriptors': + raise ValueError( + "id_injection='model' does not support identifier_mode=" + "'descriptors' yet (no descriptor source is plumbed) — use " + "id_injection='data'." + ) + return mode in _MODEL_INJECTABLE_TRANSPARENT_MODES + + +def _transparent_feature_dim(config: Dict[str, Any], dataset: Any) -> int: + """Width D of the transparent identifier block for the active mode.""" + mode = str(config.get('identifier_mode', 'none')).strip().lower() + if mode == 'onehot': + n = len(getattr(dataset, 'station_ids', []) or []) + if n == 0: + raise ValueError('onehot identifier needs dataset.station_ids.') + return n + if mode == 'coordinates': + return 2 + if mode == 'numeric_id': + return 1 + if mode == 'sinusoidal': + return int(config.get('sinusoidal_dim', 16)) + if mode == 'random': + return int(config.get('random_identifier_dim', 16)) + raise ValueError(f'No transparent feature dim for mode {mode!r}.') + + def auto_detect_enc_in(dataset: Any) -> int: """Derive ``enc_in`` from the training split's feature dimension.""" split = dataset.get_split('train') @@ -376,9 +421,19 @@ def build_model(config: Dict[str, Any], dataset: Any = None) -> Any: model_name = config['model'] # Auto-detect enc_in from the training split's feature dimension. + _mi_transparent_dim = 0 if config.get('enc_in') is None and dataset is not None: config['enc_in'] = auto_detect_enc_in(dataset) + # Model-layer transparent injection: the dataset emits ONLY base + # features, but the inner model consumes the wrapper's concatenated + # input — widen enc_in by the identifier dim (keeps enc_in semantics + # identical to the data-layer bake-in path). + if _uses_model_transparent_injection(config): + _mi_transparent_dim = _transparent_feature_dim(config, dataset) + config['enc_in'] = int(config['enc_in']) + _mi_transparent_dim logger.info('Auto-detected enc_in=%d from training data', config['enc_in']) + elif _uses_model_transparent_injection(config): + _mi_transparent_dim = _transparent_feature_dim(config, dataset) # ── Auto-detect c_out / dec_in based on features mode ─────────────── # @@ -489,6 +544,33 @@ def build_model(config: Dict[str, Any], dataset: Any = None) -> Any: emb_size, ) + # Model-layer transparent injection (id_injection='model'): wrap with the + # per-sample fixed-feature injector. The wrapper holds NO learnable + # parameters; the inner model was built with enc_in = base + feature_dim. + if _mi_transparent_dim: + from liulian.models.torch.entity_mixin import EntityTransparentWrapper + + _station_ids = [str(s) for s in getattr(dataset, 'station_ids', [])] + if not _station_ids: + raise ValueError("id_injection='model' requires dataset.station_ids for the transparent feature table.") + _topo = getattr(dataset, 'topology', None) + _coords = getattr(_topo, 'coordinates', None) if _topo is not None else None + model = EntityTransparentWrapper( + inner_model=model, + mode=config['identifier_mode'], + station_ids=_station_ids, + coordinates=_coords, + sinusoidal_dim=int(config.get('sinusoidal_dim', 16)), + random_dim=int(config.get('random_identifier_dim', 16)), + random_seed=int(config.get('random_identifier_seed', 2026)), + ) + logger.info( + 'EntityTransparentWrapper: mode=%s, stations=%d, feature_dim=%d', + config['identifier_mode'], + len(_station_ids), + model.feature_dim, + ) + n_params = sum(p.numel() for p in model.parameters()) logger.info('Model: %s (%.1fK params)', model_name, n_params / 1e3) diff --git a/liulian/runtime/experiment.py b/liulian/runtime/experiment.py index 0028135..afb091a 100644 --- a/liulian/runtime/experiment.py +++ b/liulian/runtime/experiment.py @@ -375,14 +375,29 @@ def _run_torch( from liulian.models.torch.entity_mixin import ( ChannelEntityWrapper, ChannelTransparentWrapper, + EntityTransparentWrapper, EntityWrapper, ) is_entity_wrapped = isinstance(torch_model, EntityWrapper) is_channel_wrapped = isinstance(torch_model, ChannelEntityWrapper) is_transparent_wrapped = isinstance(torch_model, ChannelTransparentWrapper) - - if is_channel_wrapped: + is_pe_transparent = isinstance(torch_model, EntityTransparentWrapper) + + if is_pe_transparent: + # Per_entity transparent modes with id_injection='model'. The + # wrapper holds the (N, D) fixed feature table; capture its + # rebuild ingredients so a trial-tuned sinusoidal_dim / + # random_identifier_dim takes effect (the inner model is + # rebuilt with enc_in = base + tuned_dim). + inner_model_cls = type(torch_model.inner) + model_args = getattr(torch_model.inner, '_args', None) + _pt_mode = str(torch_model.mode) + _pt_station_ids = list(torch_model.station_ids) + _pt_coords = torch_model._coordinates + _pt_seed = int(torch_model.random_seed) + _pt_fixed_dim = int(torch_model.feature_dim) + elif is_channel_wrapped: inner_model_cls = type(torch_model.inner) model_args = getattr(torch_model.inner, '_args', None) _cw_num_stations = torch_model.station_embedding.num_embeddings @@ -415,7 +430,7 @@ def _run_torch( model_args = SimpleNamespace(**self.config) - if is_transparent_wrapped: + if is_transparent_wrapped or is_pe_transparent: # Make the transparent-dim knobs visible on the namespace so a # tuned value from the trial config propagates into the rebuild # (make_trainable only sets config keys the namespace already has). @@ -423,9 +438,37 @@ def _run_torch( if not hasattr(model_args, _attr): setattr(model_args, _attr, self.config.get(_attr, 16)) + if is_pe_transparent: + # Base (identifier-free) input width: the captured namespace + # carries the WIDENED enc_in the inner model was built with. + _pt_base_enc_in = int(getattr(model_args, 'enc_in', self.config.get('enc_in', 1))) - _pt_fixed_dim + # Build a model factory that rebuilds the full model (including # entity / transparent wrapping) from a namespace of args. - if is_channel_wrapped: + if is_pe_transparent: + + def _model_factory(args): + # Tuned dims change the input width: rebuild the inner + # model at enc_in = base + dim, then re-wrap. + if _pt_mode == 'sinusoidal': + dim = int(getattr(args, 'sinusoidal_dim', _pt_fixed_dim)) + elif _pt_mode == 'random': + dim = int(getattr(args, 'random_identifier_dim', _pt_fixed_dim)) + else: # onehot / coordinates / numeric_id: width is fixed + dim = _pt_fixed_dim + args.enc_in = _pt_base_enc_in + dim + inner = inner_model_cls(args).float() + return EntityTransparentWrapper( + inner_model=inner, + mode=_pt_mode, + station_ids=_pt_station_ids, + coordinates=_pt_coords, + sinusoidal_dim=int(getattr(args, 'sinusoidal_dim', 16)), + random_dim=int(getattr(args, 'random_identifier_dim', 16)), + random_seed=_pt_seed, + ) + + elif is_channel_wrapped: def _model_factory(args): inner = inner_model_cls(args).float() @@ -486,6 +529,12 @@ def _loaders_factory(cfg: dict) -> dict: return build_loaders(build_dataset(cfg), cfg) + # Per-trial loader rebuilds are only needed when transparent + # identifiers are injected at the DATA layer; with + # id_injection='model' the loaders are identifier-agnostic and + # the wrapper rebuild (above) handles tuned dims. + _inj_is_data = str(self.config.get('id_injection', 'data')).strip().lower() == 'data' + trainable = None try: from liulian.optim.ray_optimizer import make_trainable @@ -502,7 +551,7 @@ def _loaders_factory(cfg: dict) -> dict: base_config=self.config, model_factory=_model_factory, save_checkpoints=_save_ckpts, - loaders_factory=_loaders_factory, + loaders_factory=_loaders_factory if _inj_is_data else None, ) except Exception as exc: logger.warning('Could not build HPO trainable: %s', exc) @@ -561,7 +610,7 @@ def _loaders_factory(cfg: dict) -> dict: _best_dims = {k: hpo_result.best_config[k] for k in DATA_LAYER_DIM_KEYS if k in hpo_result.best_config} _rebuilt_enc_in: int | None = None - if _best_dims: + if _best_dims and _inj_is_data: from liulian.pipeline import build_dataset, build_loaders loaders = build_loaders(build_dataset(best_cfg), best_cfg) diff --git a/liulian/runtime/trainer.py b/liulian/runtime/trainer.py index 6aa3a72..7f7b0b5 100644 --- a/liulian/runtime/trainer.py +++ b/liulian/runtime/trainer.py @@ -104,7 +104,15 @@ def __init__( self.nan_mask_loss = bool(self.config.get('nan_mask_loss', False)) self.teacher_forcing = str(self.config.get('teacher_forcing', 'label')).strip().lower() self.eval_denorm = bool(self.config.get('eval_denorm', False)) - self.use_entity_embedding = str(self.config.get('identifier_mode', 'none')).strip().lower() == 'embedding' + _idmode = str(self.config.get('identifier_mode', 'none')).strip().lower() + self.use_entity_embedding = _idmode == 'embedding' + # Models that need per-sample entity indices in forward(): + # embedding (EntityWrapper) AND model-layer transparent injection + # (EntityTransparentWrapper, id_injection='model'). + self.pass_entity_ids = self.use_entity_embedding or ( + str(self.config.get('id_injection', 'data')).strip().lower() == 'model' + and _idmode in {'onehot', 'coordinates', 'sinusoidal', 'random', 'numeric_id'} + ) # Data augmentation during training aug_cfg = self.config.get('augmentation', None) @@ -429,7 +437,7 @@ def evaluate( # Pass entity_ids to model if in embedding mode fwd_kwargs: Dict[str, Any] = {} - if self.use_entity_embedding and batch_entity_idx is not None: + if self.pass_entity_ids and batch_entity_idx is not None: fwd_kwargs['entity_ids'] = batch_entity_idx outputs = model(batch_x, batch_x_mark, dec_inp, batch_y_mark, **fwd_kwargs) if isinstance(outputs, tuple): @@ -559,7 +567,7 @@ def predict( dec_inp = self._build_decoder_input(batch_y, label_len, pred_len) fwd_kwargs: Dict[str, Any] = {} - if self.use_entity_embedding and batch_entity_idx is not None: + if self.pass_entity_ids and batch_entity_idx is not None: fwd_kwargs['entity_ids'] = batch_entity_idx outputs = model(batch_x, batch_x_mark, dec_inp, batch_y_mark, **fwd_kwargs) if isinstance(outputs, tuple): @@ -694,7 +702,7 @@ def _train_epoch( dec_inp = self._build_decoder_input(batch_y, label_len, pred_len) fwd_kwargs: Dict[str, Any] = {} - if self.use_entity_embedding and batch_entity_idx is not None: + if self.pass_entity_ids and batch_entity_idx is not None: fwd_kwargs['entity_ids'] = batch_entity_idx outputs = model(batch_x, batch_x_mark, dec_inp, batch_y_mark, **fwd_kwargs) if isinstance(outputs, tuple): diff --git a/tests/models/torch/test_transparent_injection_equivalence.py b/tests/models/torch/test_transparent_injection_equivalence.py new file mode 100644 index 0000000..52cca2d --- /dev/null +++ b/tests/models/torch/test_transparent_injection_equivalence.py @@ -0,0 +1,136 @@ +"""Equivalence: model-layer transparent injection == data-layer bake-in. + +`EntityTransparentWrapper` (id_injection='model') must feed the inner model +BITWISE-IDENTICAL inputs to the legacy data-layer bake-in +(`make_entity_features` concatenated at window-build time). This is the +contract that keeps wrapper-injected runs comparable with the data-injected +baselines (swiss3 2026-06-11, swiss3dt 2026-06-12). +""" + +from pathlib import Path + +import pytest +import torch +import torch.nn as nn + +from liulian.data.ts.timeseriesdataset import make_entity_features +from liulian.models.torch.entity_mixin import EntityTransparentWrapper + +IDS = ['alpha', 'beta', 'gamma', 'delta'] +COORDS = { + 'alpha': (600000.0, 200000.0), + 'beta': (610000.0, 210000.0), + 'gamma': (620000.0, 190000.0), + 'delta': (605000.0, 205000.0), +} +MODES = ('onehot', 'sinusoidal', 'random', 'coordinates', 'numeric_id') + + +class _CaptureInner(nn.Module): + """Stub inner model that records the x_enc it receives.""" + + def __init__(self) -> None: + super().__init__() + self.last_x: torch.Tensor | None = None + + def forward(self, x_enc, x_mark_enc=None, x_dec=None, x_mark_dec=None, mask=None): + self.last_x = x_enc + return x_enc[..., :1] + + +def _wrapper(mode: str) -> EntityTransparentWrapper: + return EntityTransparentWrapper( + _CaptureInner(), + mode, + IDS, + coordinates=COORDS if mode == 'coordinates' else None, + sinusoidal_dim=8, + random_dim=8, + random_seed=7, + ) + + +@pytest.mark.parametrize('mode', MODES) +def test_feature_table_matches_data_layer_bakein(mode: str) -> None: + """Table row i must equal the data-layer block for station i.""" + w = _wrapper(mode) + for i, name in enumerate(IDS): + baked = make_entity_features( + name, + IDS, + mode, + seq_len=5, + coordinates=COORDS if mode == 'coordinates' else None, + sinusoidal_dim=8, + random_dim=8, + random_seed=7, + ) + assert baked is not None + assert torch.equal(w.feature_table[i].unsqueeze(0).expand(5, -1), baked) + + +@pytest.mark.parametrize('mode', MODES) +def test_forward_concat_layout_matches_bakein(mode: str) -> None: + """Wrapper concat = [base block | identifier block], same as bake-in.""" + w = _wrapper(mode) + B, T, base = 3, 6, 2 + x = torch.randn(B, T, base) + eidx = torch.tensor([2, 0, 3]) + w(x, entity_ids=eidx) + out = w.inner.last_x + assert out is not None + assert out.shape == (B, T, base + w.feature_dim) + assert torch.equal(out[..., :base], x) + for b in range(B): + assert torch.equal(out[b, :, base:], w.feature_table[eidx[b]].unsqueeze(0).expand(T, -1)) + + +def test_requires_entity_ids() -> None: + """No silent fallback: missing entity ids must raise, not zero-fill.""" + w = _wrapper('onehot') + with pytest.raises(ValueError, match='entity_ids'): + w(torch.randn(2, 4, 1)) + + +_ZURICH = Path(__file__).resolve().parents[3] / 'dataset' / 'swiss_river' / 'zurich_train.csv' + + +@pytest.mark.skipif(not _ZURICH.exists(), reason='swiss CSVs not present (gitignored)') +@pytest.mark.parametrize('mode', ('onehot', 'sinusoidal', 'random', 'coordinates')) +def test_swiss_dataset_both_injection_paths_identical(mode: str) -> None: + """End-to-end on real zurich data: data-baked sample == wrapper output.""" + from liulian.data.swiss_river import SwissRiverDataset + + kw = dict( + data_name='swiss-river-zurich', + identifier_mode=mode, + split_mode='per_entity', + graph_mode='none', + sinusoidal_dim=8, + random_identifier_dim=8, + random_identifier_seed=7, + max_samples=50, + ) + ds_data = SwissRiverDataset(id_injection='data', **kw) + ds_model = SwissRiverDataset(id_injection='model', **kw) + tr_d = ds_data.get_split('train') + tr_m = ds_model.get_split('train') + assert len(tr_d) == len(tr_m) + + w = EntityTransparentWrapper( + _CaptureInner(), + mode, + [str(s) for s in ds_model.station_ids], + coordinates=(ds_model.topology.coordinates if ds_model.topology else None), + sinusoidal_dim=8, + random_dim=8, + random_seed=7, + ) + sid_to_idx = {str(s): i for i, s in enumerate(ds_model.station_ids)} + for i in (0, len(tr_d) // 2, len(tr_d) - 1): + baked_x = tr_d[i][0] + item = tr_m[i] + base_x, eid = item[0], item[4] + w(base_x.unsqueeze(0), entity_ids=torch.tensor([sid_to_idx[str(eid)]])) + assert w.inner.last_x is not None + assert torch.equal(w.inner.last_x[0], baked_x) From 15faeddca492e436813d7d46fe7b24209dcda0bc Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Sat, 13 Jun 2026 17:13:13 +0200 Subject: [PATCH 08/21] docs(research): swiss3dt 6-mode results + coordinates-fix writeup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tools/plot_swiss3dt_results.py: reads denorm RMSE from real results.json (no hardcoded numbers), emits grouped-bar + coordinates old/new figures and a CSV - docs/research/2026-06-13-swiss3dt-results.md: 18-cell table, research- critic caveats (single seed, enc_in capacity confound, same domain — no "transparent > embedding" claim), and the coordinates flip explanation (old = zero-vector bug, retracted; new = real + min-max normalized) Co-Authored-By: Claude Fable 5 --- docs/research/2026-06-13-swiss3dt-results.md | 98 ++++++++++++++++ .../swiss3dt-coordinates-fix.png | Bin 0 -> 52226 bytes .../swiss3dt-rmse-by-mode.png | Bin 0 -> 53495 bytes .../swiss3dt-2026-06-13/swiss3dt-rmse.csv | 4 + tools/plot_swiss3dt_results.py | 111 ++++++++++++++++++ 5 files changed, 213 insertions(+) create mode 100644 docs/research/2026-06-13-swiss3dt-results.md create mode 100644 docs/research/figures/swiss3dt-2026-06-13/swiss3dt-coordinates-fix.png create mode 100644 docs/research/figures/swiss3dt-2026-06-13/swiss3dt-rmse-by-mode.png create mode 100644 docs/research/figures/swiss3dt-2026-06-13/swiss3dt-rmse.csv create mode 100644 tools/plot_swiss3dt_results.py diff --git a/docs/research/2026-06-13-swiss3dt-results.md b/docs/research/2026-06-13-swiss3dt-results.md new file mode 100644 index 0000000..002e52a --- /dev/null +++ b/docs/research/2026-06-13-swiss3dt-results.md @@ -0,0 +1,98 @@ +# swiss-river × LSTM entity-identifier results (swiss3dt, 2026-06-13) + +Per-entity LSTM on three Swiss river-temperature datasets, six identifier +modes, **single seed 2026**, 50-trial Ray HPO per cell, dropout tuned +`{0, 0.1}`, batch_size 32 (fixed), 30 epochs, per-trial deterministic +seeding, single chronological 80/20 hold-out (no CV). + +Numbers are read straight off the run `results.json` files by +`tools/plot_swiss3dt_results.py` (no hand-copied values). Raw table: +`figures/swiss3dt-2026-06-13/swiss3dt-rmse.csv`. + +## Results — test RMSE (°C, denormalized) + +![RMSE by mode](figures/swiss3dt-2026-06-13/swiss3dt-rmse-by-mode.png) + +| dataset (stations) | none | embedding | onehot | random | sinusoidal | coordinates | +|---|---|---|---|---|---|---| +| 1990 (28) | 1.723 | 1.289 | 1.119 | 1.139 | **1.116** | 1.155 | +| 2010 (63) | 1.642 | 1.368 | **1.201** | 1.222 | 1.224 | 1.255 | +| zurich (15) | 1.553 | 1.378 | **1.237** | 1.249 | 1.259 | 1.276 | + +Best HPO picks per cell (d_model / e_layers / dropout / identifier-dim) and +the artifact-level verification of the tuned dims live in the task ledger +(`liulian-docs/docs/tasks/liulian-python/2026-06.md`, 2026-06-13). + +## What we can and cannot claim (research-critic audit) + +**Defensible (single seed):** + +- Every identifier mode beats the `none` baseline by a large margin — + embedding ≈ −20 to −25 %, the four transparent modes ≈ −19 to −35 %, + consistently across all three datasets. +- Embedding and the transparent modes are the strongest; the transparent + modes sit a touch lower in RMSE than embedding on every dataset. + +**NOT claimed (insufficient evidence):** + +- *"Transparent identifiers outperform learned embeddings."* The gap + (e.g. 1.289 vs 1.116 on 1990) is from a **single seed** with no variance + estimate; it may not exceed run-to-run noise. +- *Any ranking among the four transparent modes.* They fall within + ~0.04 °C of each other — not separable at one seed. +- *Generalization beyond Swiss river temperature.* All three datasets are + the same domain. + +**Known confound:** transparent modes concatenate the identifier block to +`x_enc`, enlarging `enc_in` and hence the LSTM input weights (random +≈ 55 k params vs embedding ≈ 51 k vs none ≈ 51 k). "Identifier type" is +entangled with input capacity. A capacity-matched control is needed before +attributing the gain purely to the identifier. + +**To harden** (tracked as task #32): ≥3 seeds with mean ± std and a paired +test; a capacity-matched `none` control (widen `enc_in` with random / +padding channels). + +## Coordinates: why it flipped from "hurts" to "helps" + +![Coordinates old vs new](figures/swiss3dt-2026-06-13/swiss3dt-coordinates-fix.png) + +The 2026-05 matrix reported `coordinates` as **regressing** the LSTM. On +swiss-river-1990 × LSTM the old run gave coord 3.914 vs none 3.882 (+0.8 % +— i.e. no benefit, slight harm). The new run gives coord 1.155 vs none +1.723 (−33 % — a clear benefit). + +The old absolute numbers are higher across the board (old none 3.882 vs new +1.723) because of unrelated training-setup changes (batch_size 8 → 32, new +HPO search space). The **relative** coordinate effect is what flipped, and +the cause is a fixed bug: + +- **Old = zero-vector bug.** The identifier-matrix default is + `graph_mode='none'`, and the old code only loaded the dataset topology + (which carries the station `(x, y)` from the graph file) when a graph + mode was active. So `coordinates` received an **empty** coordinate map + and `make_entity_features` fell back to `torch.zeros(2)` — every station + got the same zero vector. Measured directly on 2026-06-11: the coordinate + channels had `abs-max = 0.0`. The 2026-05 slide's description of "raw + lat/lon as unscaled features" was wrong — no coordinate ever reached the + model. With two constant-zero channels, `coordinates` was effectively + `none` plus a useless widening, hence ≈ none / slightly worse. +- **New = real, normalized coordinates** (commit `421f1e7`). Three changes: + 1. load the topology whenever `identifier_mode='coordinates'` (not only + for graph modes), so the `(x, y)` map is actually populated; + 2. min-max normalize coordinates per dimension over the dataset's own + stations (raw CH1903 metres are ~1e5–1e6 and would dwarf the scaled + inputs); + 3. raise instead of silently zero-filling when a coordinate is missing + (no-fake-features rule). + +So the flip is not a modelling surprise — the old "coordinates hurt" +finding was an artifact of a silent zero-feature bug, and is **retracted**. +The 2026-05 slide/analysis should be annotated accordingly (task #29). + +## Reproduce + +```bash +# regenerate figures + CSV from the local result artifacts +python tools/plot_swiss3dt_results.py +``` diff --git a/docs/research/figures/swiss3dt-2026-06-13/swiss3dt-coordinates-fix.png b/docs/research/figures/swiss3dt-2026-06-13/swiss3dt-coordinates-fix.png new file mode 100644 index 0000000000000000000000000000000000000000..e9a0339539c66599b51575ad4b5de6ab4002763f GIT binary patch literal 52226 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 Vwknj&q8@NH9(U`8>+5$P|8L`Q?>Ybg literal 0 HcmV?d00001 diff --git a/docs/research/figures/swiss3dt-2026-06-13/swiss3dt-rmse-by-mode.png b/docs/research/figures/swiss3dt-2026-06-13/swiss3dt-rmse-by-mode.png new file mode 100644 index 0000000000000000000000000000000000000000..3d0c7c0911d0c5c5d3cb07fc8b5f457fab561306 GIT binary patch literal 53495 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 z z!lG_GZnEcFF;FGV5r%H)?$5~IW$vpqRtPgLB+&%dNu-5zJ7Rk8BViL5R`_JV(E4QP zT$=Po&gDjUgEs;dPT}CsM0+F5T7cuJ0ujG?_m)t)l7pI8CPq293ARu8jm*)RM=|NY}1X?VM!ve3}blhL&6H!Nq% zON&g(cS4lgV#oGk_qOG(*k9f2J@3z-2Pqvbh)&m=y%BLoH8HUDAVb!hYB%q#Mj{8>2eKmPvzGE1+D&6Unh W-S>1~y float | None: + hits = glob.glob(f'{ART}/{tag}/{ds}-lstm-{mode}-*/**/results.json', recursive=True) + if not hits: + return None + m = json.load(open(hits[0]))['metrics']['test'] + return float(m.get('denorm_rmse', m.get('rmse'))) + + +# ── gather ────────────────────────────────────────────────────────────── +new = {ds: {mode: rmse(NEW_TAG[ds], ds, mode) for mode in MODES} for ds in DATASETS} + +csv_path = OUT / 'swiss3dt-rmse.csv' +with open(csv_path, 'w', newline='') as fh: + w = csv.writer(fh) + w.writerow(['dataset', *MODES]) + for ds in DATASETS: + w.writerow([ds, *[f'{new[ds][m]:.4f}' if new[ds][m] is not None else '' for m in MODES]]) +print('wrote', csv_path) + +# ── fig 1: grouped bars ───────────────────────────────────────────────── +COLORS = { + 'none': '#9aa0a6', 'embedding': '#1a73e8', 'onehot': '#34a853', + 'random': '#a142f4', 'sinusoidal': '#ea8600', 'coordinates': '#d93025', +} +fig, ax = plt.subplots(figsize=(9, 4.8)) +n_modes = len(MODES) +group_w = 0.82 +bar_w = group_w / n_modes +short = {'swiss-river-1990': '1990 (28 st.)', 'swiss-river-2010': '2010 (63 st.)', + 'swiss-river-zurich': 'zurich (15 st.)'} +for j, mode in enumerate(MODES): + xs = [i + (j - n_modes / 2 + 0.5) * bar_w for i in range(len(DATASETS))] + ys = [new[ds][mode] for ds in DATASETS] + ax.bar(xs, ys, bar_w, label=mode, color=COLORS[mode], edgecolor='white', linewidth=0.5) +ax.set_xticks(range(len(DATASETS))) +ax.set_xticklabels([short[d] for d in DATASETS]) +ax.set_ylabel('Test RMSE (°C, denormalized)') +ax.set_title('swiss-river × LSTM: entity-identifier modes (single seed 2026, 50-trial HPO)') +ax.legend(ncol=6, fontsize=8, loc='upper center', bbox_to_anchor=(0.5, -0.12), frameon=False) +ax.grid(axis='y', alpha=0.3) +for sp in ('top', 'right'): + ax.spines[sp].set_visible(False) +fig.tight_layout() +fig.savefig(OUT / 'swiss3dt-rmse-by-mode.png', dpi=150, bbox_inches='tight') +print('wrote', OUT / 'swiss3dt-rmse-by-mode.png') + +# ── fig 2: coordinates old vs new (1990 only) ─────────────────────────── +old_coord = rmse(OLD_TAG, 'swiss-river-1990', 'coordinates') +old_none = rmse(OLD_TAG, 'swiss-river-1990', 'none') +new_coord = new['swiss-river-1990']['coordinates'] +new_none = new['swiss-river-1990']['none'] + +fig2, (axL, axR) = plt.subplots(1, 2, figsize=(8, 4.2)) +for ax, (title, none_v, coord_v, sub) in zip( + (axL, axR), + [('OLD (2026-05-11)\nzero-vector bug', old_none, old_coord, 'coord ≈ none (+%.1f%%)'), + ('NEW (2026-06-13)\nreal + normalized', new_none, new_coord, 'coord beats none (%.1f%%)')], +): + delta = (coord_v - none_v) / none_v * 100.0 + bars = ax.bar(['none', 'coordinates'], [none_v, coord_v], + color=['#9aa0a6', '#d93025'], edgecolor='white') + ax.set_title(title, fontsize=10) + ax.set_ylabel('Test RMSE (°C)') + ax.set_ylim(0, max(old_none, old_coord) * 1.15) + ax.text(0.5, 0.92, sub % delta, transform=ax.transAxes, ha='center', fontsize=9, + color=('#d93025' if delta > 0 else '#188038')) + for b, v in zip(bars, [none_v, coord_v]): + ax.text(b.get_x() + b.get_width() / 2, v + 0.03, f'{v:.3f}', ha='center', fontsize=8) + for sp in ('top', 'right'): + ax.spines[sp].set_visible(False) +fig2.suptitle('Coordinates identifier — swiss-river-1990 × LSTM', fontsize=11) +fig2.tight_layout() +fig2.savefig(OUT / 'swiss3dt-coordinates-fix.png', dpi=150, bbox_inches='tight') +print('wrote', OUT / 'swiss3dt-coordinates-fix.png') +print(f'OLD coord/none = {old_coord:.3f}/{old_none:.3f} ' + f'NEW coord/none = {new_coord:.3f}/{new_none:.3f}') From 7437a803e53b481ca37924fea3128063df6d0f29 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Sat, 13 Jun 2026 17:24:43 +0200 Subject: [PATCH 09/21] docs: retract 2026-05 coordinates results (task #29) + fix comparison baseline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 2026-05-15 advisor slide reported coordinates as regressing the LSTM (+4.1%). That was a zero-vector bug (topology never loaded under graph_mode='none' -> make_entity_features zero-filled), not "unscaled lat/lon". Annotate the slide in place (comment-don't-delete): top correction banner + inline [R] retractions on the table cell, heatmap column, §2(4), §5.1, and the next-steps line. Original snapshot text preserved as the as-presented record. Also fix the coordinate comparison figure/writeup to use the run the slide actually came from (swissriver-lstm-REAL-20260512, whose onehot 1.1717 matches the slide's 1.171) instead of the under-trained fullmatrix-0511: old coord 1.630 vs none 1.561 (+4.4%) -> new 1.155 vs 1.723 (-33%), same denorm-RMSE units, comparable none levels. Co-Authored-By: Claude Fable 5 --- .../2026-05-15-entity-id-progress-slide.md | 37 +++++++++++++++++- docs/research/2026-06-13-swiss3dt-results.md | 14 +++---- .../swiss3dt-coordinates-fix.png | Bin 52226 -> 66962 bytes tools/plot_swiss3dt_results.py | 15 ++++--- 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/docs/research/2026-05-15-entity-id-progress-slide.md b/docs/research/2026-05-15-entity-id-progress-slide.md index 76caaeb..2c14851 100644 --- a/docs/research/2026-05-15-entity-id-progress-slide.md +++ b/docs/research/2026-05-15-entity-id-progress-slide.md @@ -2,6 +2,21 @@ _Advisor update · 2026-05-15 · single-slide summary (+ speaker notes)_ +> ⚠️ **Correction (2026-06-13) — `coordinates` rows are RETRACTED.** +> This snapshot's `coordinates` results (the `🔴 +4.1 %` LSTM cell in §2, +> its heatmap column, and §5.1) are **invalid**: a bug meant the coordinate +> identifier was fed an +> all-**zero vector** (the dataset topology that holds station `(x, y)` was +> never loaded under the matrix default `graph_mode='none'`, so +> `make_entity_features` silently zero-filled — measured `abs-max = 0.0` on +> 2026-06-11). The "raw lat/lon as unscaled features" explanation below is +> therefore also wrong — no coordinate ever reached the model. After the fix +> (real, min-max-normalized coordinates, commit `421f1e7`), `coordinates` +> **helps** the LSTM by ≈ −33 % vs `none` (1.155 vs 1.723 on 1990), not +> +4 %. The non-coordinate rows stand. Full corrected results: +> [`2026-06-13-swiss3dt-results.md`](2026-06-13-swiss3dt-results.md). +> Original text below is preserved unedited as the as-presented record. + > **One-line takeaway:** On Swiss-River-1990, attaching a near-zero-cost entity > identifier to a plain LSTM cuts per-station RMSE by **up to 32 %** and lets a > 55 K-parameter LSTM **outperform a 410 K-parameter PatchTST transformer** — @@ -35,7 +50,7 @@ that row.** | dataset | model (split) | `none` baseline | embedding | onehot | sinusoidal | random | coordinates | |---|---|---|---|---|---|---|---| -| **swiss-river** | LSTM (per_entity) | 1.725 °C | −21.4 % | 🟢 **−32.1 %** | −30.9 % | −31.0 % | 🔴 +4.1 % | +| **swiss-river** | LSTM (per_entity) | 1.725 °C | −21.4 % | 🟢 **−32.1 %** | −30.9 % | −31.0 % | 🔴 +4.1 % ⚠️[R] | | **swiss-river** | PatchTST (multi-ch) | 1.382 °C | 🟢 **−4.7 %** | −1.0 % | −1.2 % | −0.6 % | −0.1 % | | **swiss-river** | DLinear (multi-ch) | 1.287 °C | **−0.7 %** | −0.7 % | −0.4 % | −0.4 % | −0.5 % | | **traffic** | LSTM (multi-ch) | 0.0280 † | 🟢 **−4.5 %** | −1.7 % | _deferred_ | _deferred_ | N/A | @@ -50,8 +65,16 @@ occupancy fraction for traffic; ‡ standardised power for electricity. Absolute RMSE units differ by dataset — the **Δ%** column is the cross-comparable signal._ `coordinates` is only meaningful for swiss-river (has lat/lon). +> ⚠️[R] **RETRACTED (2026-06-13):** the `coordinates +4.1 %` cell is a +> zero-vector artifact (see the correction banner at the top). Corrected: +> `coordinates` ≈ −33 % vs `none` on swiss-river-1990. + ![results heatmap](figures/entity-id-2026-05-15/results-heatmap-all.png) +> ⚠️[R] The **`coordinates` column** of this heatmap is invalid (zero-vector +> bug, see top banner). Read it with that column greyed out; the corrected +> coordinate bars are in `2026-06-13-swiss3dt-results.md`. + **Cross-dataset signal (read the heatmap):** 1. **`PatchTST × embedding` is the only universally-effective cell** — −4.7 / @@ -66,6 +89,9 @@ signal._ `coordinates` is only meaningful for swiss-river (has lat/lon). identity signal. 4. **`coordinates` regresses LSTM** (+4.1 %) — raw lat/lon as unscaled features acts as noise. Needs normalisation / a learned geo-encoder. + > ⚠️[R] **RETRACTED (2026-06-13):** wrong — the input was an all-zero + > vector (bug), not "unscaled lat/lon". Fixed → `coordinates` **helps** + > (≈ −33 %). See top banner + `2026-06-13-swiss3dt-results.md`. 5. **`traffic × PatchTST × onehot` essentially neutral (+0.2 %).** Now that the cell finished, the pattern holds: PatchTST gains only via `embedding` (native `add_after_patch`); transparent identifiers on PatchTST barely move @@ -144,6 +170,10 @@ largely redundant (PatchTST/DLinear ≤5 % RMSE). 1. **`coordinates` hurts LSTM (+4 % RMSE).** Raw lat/lon fed as two unscaled features — large-magnitude, low-information columns that act as noise. Needs normalisation / a learned geo-encoder before it can help. *(actionable fix)* + > ⚠️[R] **RETRACTED (2026-06-13):** the diagnosis was wrong — coordinates + > were never loaded (zero vector), not "unscaled". The actionable fix + > (load topology + min-max normalize, `421f1e7`) is done; `coordinates` + > now helps the LSTM (≈ −33 %). 2. **PatchTST & DLinear barely move (≤5 % RMSE).** In `multi_channel` split every station is already its own channel, so channel identity is *implicit* in the layout — an explicit identifier is largely redundant (best case is @@ -165,7 +195,10 @@ largely redundant (PatchTST/DLinear ≤5 % RMSE). - **Controlled comparison:** re-run PatchTST/DLinear in `per_entity` to remove the split-mode confound. - **Multi-seed** (≥3) for the headline cells → significance bands. -- **Fix `coordinates`:** normalise + try a small geo-MLP encoder. +- ~~**Fix `coordinates`:** normalise + try a small geo-MLP encoder.~~ + ✅ **Done (2026-06-13):** the regression was a zero-vector bug, not + scaling; fixed by loading topology + min-max normalization (`421f1e7`). + `coordinates` now helps (≈ −33 %). - Ablation: parameter-matched baseline (does the `EntityWrapper`'s extra linear layer explain part of the embedding gain?). diff --git a/docs/research/2026-06-13-swiss3dt-results.md b/docs/research/2026-06-13-swiss3dt-results.md index 002e52a..3a1c2c6 100644 --- a/docs/research/2026-06-13-swiss3dt-results.md +++ b/docs/research/2026-06-13-swiss3dt-results.md @@ -58,14 +58,12 @@ padding channels). ![Coordinates old vs new](figures/swiss3dt-2026-06-13/swiss3dt-coordinates-fix.png) The 2026-05 matrix reported `coordinates` as **regressing** the LSTM. On -swiss-river-1990 × LSTM the old run gave coord 3.914 vs none 3.882 (+0.8 % -— i.e. no benefit, slight harm). The new run gives coord 1.155 vs none -1.723 (−33 % — a clear benefit). - -The old absolute numbers are higher across the board (old none 3.882 vs new -1.723) because of unrelated training-setup changes (batch_size 8 → 32, new -HPO search space). The **relative** coordinate effect is what flipped, and -the cause is a fixed bug: +swiss-river-1990 × LSTM the old run (`swissriver-lstm-REAL-20260512`, the +batch the advisor slide used — its onehot 1.1717 matches the slide's 1.171) +gave coord 1.630 vs none 1.561 (**+4.4 %** — worse than no identifier). The +new run gives coord 1.155 vs none 1.723 (**−33 %** — a clear benefit). The +two `none` baselines are at a similar level (1.561 vs 1.723), so the flip is +in the coordinate effect itself, and the cause is a fixed bug: - **Old = zero-vector bug.** The identifier-matrix default is `graph_mode='none'`, and the old code only loaded the dataset topology diff --git a/docs/research/figures/swiss3dt-2026-06-13/swiss3dt-coordinates-fix.png b/docs/research/figures/swiss3dt-2026-06-13/swiss3dt-coordinates-fix.png index e9a0339539c66599b51575ad4b5de6ab4002763f..96c340abd7a658e18bd8b17371d84bfdfbac3384 100644 GIT binary patch literal 66962 zcmeFZc{G>n+dh07k~C;gG)PJa6-om_%2bL>kugFUGA9`e$vlfnR0vTdV}(-YD48;3 zCJBYi!|%BEexB$1dw>7EYrX4T>s{;JYwf*P;^V%r>%7kMIFI8vFJI-ea;urPFi{k> zT0#D#3PsV!QxvTgBLlvocsOMM{}Z*B(X>~!GO>5OXlqQJxoB@~VP$V|#c;vmq>WZ=?t4tWcqtnl zr`K9(#()2l?X{G$RqDU~fG-M2hF%~>As?%8wv{vpc@^QL_)EG)$(C3JhWbG%|>+smwYIhSnz{Q1-T(s*TQ zFbA`vqhoSvss{fZ*M(V!RcwOPrcIla)z!W8^N%vD*>b?}tlze*fTswyX`@47Vbne~0 z&G0fSi{J7-UibX>+S(U4c(62!yVWEd=omQ@av~xkcJT5tZasXJ-e{GJ1jpp$WJYeT z+>INe&vjqles=zaVb^Y)3)yv7Z+D?ZLLxl?*<+op8P@6+J_&%K2^GS<%+KlGl#U2k`iA> zOUstCXU~4Fub*%J#ND7=YH=>k~iLxh>9^{^}^=IR9Mdrnk3ub&g>snh zSKEr?+!uY{I#2O!W1q2_7|!i59IQ)3-dVl6{@Hovf`S71vu8{Ds_xp%j2V*;tbKTl zj=Fy1#{0TDm86GQG+OFGY^?F(KH8A`8ft1@cszsZXd&Cy$zR_NJl8{HJ|HJ{Ol!UW z#~)vwEU$TRlfs*!z9RdYhvH5<4<8d1-9qJsZX9jRw=+%8@#h_=m0X&yKa-qU;4sAZW@*tfATaPt zw)uJTcwfJ6z~Yxi?bS|g+qZM)t(6iB^W(MALPiYqv@~o+ns0B2a4S6w2;C)zZ&l^i z6nFTkrc+?Q!o+$xHu39%HF1)`ST{SGR6mXM3*Mf~ncsi@ytXWu z!zVZQFx80=|IwJfb#k;Lc+=hs26YJv99_R$ZY*cs{-G}M^t+xvzpM5a8FYW}{ajbq zJr*7wZqr-7?tz%&S+lK9T*vcq=J`cbQjU8vTp#OQaqU<0BSC8Sj5FV!(~O*P{P%9j zG0q6=)yZ3HTJzxMy?a|0=f{<>z9;ym=_zh*Zbl|1X?y#FqK+f;z86lFOR8SH$muZ_ zO6wI;xquT=@~5Xv$Z$wXT z{G7)76)f+@#8fQHi`uVWvnkTX{R=K;o zKag;}aP@v=Wo3rj!i`rJ-`3C0&aP$hPF8)o21kA!3yT**jbB`@^5Fqr)aQ0_g7PG zE%VM$Mx~T~t)Gtw8$W!-vx&uSb86^_@-`i;0PeK5EB}4Du)? zWd&AW^TLJqWo65e&&w+-QyY(lzF{GyYH)B6L3%4aeJ>&+e=v7!6KAl$KV5K{XFVhu;ke@lXiowoyH~+ugJ2xi0_V`1PAMaW@2^ly&;=FZ4jQxh}4k z9sE}GeMt!owJUn% z51gqz^ABdCySuw7>eD@uXMbHQ#{2H-(}qaeP9PZX1P9CEL5;UZtdlM`^&$T;Sz26J zMQvcD|ApKrlG=mKsIIS%pfQks6q28xFN8Y7;6Y=s_rfcKZ{NOgSc)kuzV}|aIj+_e zyH{OO;;E&ro%Q*%aw(fpZJeYjit{Q;_1w9O&!0cnY1h-!v-sWlZWYyrCrlz|z2CVz_BCd>=25yb< z^J8)h!U@i^qxV?Gz`!RykglC;qWk)qF%^yYBk!%Dq2af$K~mab*|G~w@C9mMuuV2wmpYot$Zg;GeUwkfDs(J0& zAv=5fOB5US4cVjm>(_9t3m1fMN)=NXI7S*n59sOX1u2X7UMs3gOK<;~aq0i{s~XRy ziEO>uXmpX`TXVC#<(;Gna|pO|r?bv+^!uj;H$LHOzcfciWn{(&YE3D!_#3abBEc|tEKGb= zip(}vy%^4R(TJ&YY zhB!y9@V}yVLmJkBuq|F&>g;Nn&Iqlci_9CK<0wUdSu4ZEDzO^;X z-Zwd!_u9mda~jIZJ0c%OMU~=*%BYM?qq53vPZLh)eY^V-$Myh~b~aX2ML%q_kpLH$ z*WiyIg48e6Pn{c=E^#5C9UQ(Tp1zxqvuDqqi*mu+jZuG?re+ZK0(j=Zp&=|)ee$`s zKmH#Lb5LTq*D~hqj~HDZZ)U3#@K($enc(P=BN=m3qjpX!R;)<7SaN$6b^oYcQnihI zVS@Uk%-{l5KR2gtpU$!@exFWW@wAHsi3jZ*i_ikc_wV0F1FN%6q{-XXETg44aOjZ9 z-TnIgW8aHv7Zp;SM~}J9N7EP}ai|#Y*Dd(4OoGZVu8LCVoBHv^lg0pb=Vi2W zYnH9*U`|#52C^$tzi^>4k$LN(02%|M(tvZ#&%Zp8!$MF0?n1nVM)IGxJ)V5Pw8nan znM0!Y)(9YK)QVExX9YCLe(#pfJ(11){oC4mAe~QSg_O|bh+|%Nhwqy=;z{Z0pMX8J z6ckqc6N|@4S1KU}SZzXSqFXR~^_EK+Wn=FFKcCDoo?l@>?jQHNinIr1IHalY?rjYUO<3}0pQC0f*H*eltDik1NJ??-r?RcPq!~QU#0blzOv>LXkWUko2jgyZyL}ieUVU+>Z{NSiv@5m_j zI-sma2=HxRMs4$JiiOI#R1s#jJ;kMxA-=$V@I94L6DwBrX|$tAvMI;!ctvmTrK@U9 ziwkqcIj7H_?Z4GRB}jyG-AoxRoI88*HF`NtI}VMeu-&KgmTyA`Z2INPmp0Q=1_lO! zo=O4gl2qi~yJbLF~*)kGaPXFD)(2Mjf*4m49B1U%i<+ zbfu1~ln@;Z?x$t$E zPvupANW#cI-GX>zC*hfe*}tCo`A&VyHGFM)N-GkuR__`bHd4L&9`LRAwVwWcQ88MO zMz_HJ_)uedS5FTe2M0%QZ|@FCNe-nriF6II=lVrKIJWP zVXN<6NlDzd*Rb>D1{4)ZqV7JFaP@0?pq!``#ubcxj)$3>;-U1UxA7_Qqe(1)iv>`Np)g zw5bh^%zytj>C;hX<0Jy}?FMX{9thq3=+Aokt#5kw7G;N@^~a0c7AQ(JN%0+ybKV<- z$Eqy{@5~9fxVJujyp!?(C-VLpR+{Gel<=*4(Gsqrn*=mH-oKZ&AN|hm=H@1U=FG>8 zizP^Rn^HvjfN7#OVZ*!-7sa1Gtrp+TynFZVI{^V72(^LzJ$SW^4J((HeTe-0?c2AH zT>rhpQP1&LGfn4c(I_Kluj;v(`qewULW0l8$t|Zs?%qBBWZ1~ikfLg8YM82yh>7ju z=9c~GP$AO({4s!g<(az?R`toDZ7$$|0QBBCjmvfw^7nLh?l}Aw6@|J~+c(toqHAI9hTnpJhDg^&ddLhnmD0Iq>5L@8P`j zkzfio^C&$`o4ghoDzH{J=3bjKmaDnQU2>n^n>(m}kCT%b0c1VVD6Cqwiu4*N>@=aF zp}!|4dTG@w-;A;IGMluZ!RjjU$$0rv`t`Np&#CJCugn^l07``i9MOSj=2&b-$IZNQ zBYIx{0VR^yY}u~os*Mu`P<9g zcNZrHRarJ|x>Y-1x6-jHJSvKT686ua>$3_zAS9G>&e^=Oqk{v~%3idrx6sxZee~Oy ztY0Ld_vZS|U%xCSete-r$jh0T@lAc>&b&5!LHx!9Ba+7D_5zW#w6ypqPduBOHSc^h z4A>&#=d7yt(XyeTfhNB0?iC01rpp=8x>$wQJqht^Sk4t-FMU*MpnNez^ov;NoygHsk8m zGS4*9t^ah(kZ2nj8R2ks_gXuqbP{xDx#`oZ?FEzv5N!Qr=L%J$3RY*Q>gwu*O1JWB z8{aOOU0$JJ-L(*ZQYLaZm(OpKev0+)rvZAZ)8`Fg*}}GXU22(LGi6+D>Li{&OVTI-;R8`oC3Lb5fC+6 z%yDG-^2ezKYiMs$va+)B3JRg8UXZ|Q5Sir(*=BnD_&Yhtn$!=arF_}7!Rg#n4==Gi z;L5NcZV|!C*$t9l%D6~}VB+2Ap>puXi0i$3_wsD}m_Sb2bQaTWlXQFFyh|%q)ZsUH zs_y$%g=x_C1zCjljzO>r76mJ@)wQrVp5LZO`(dnJe&L_f4+5`8qrm}cTj=59VZ^RS>vZ> z>dWhY3`uux-kA0d9l`JEKSuR0^f~a0k1H#$1)8Ig>Tlb!b!!jWN6kW)qbM4`=V$CL znVLR9xs5*Nd`Pd*#TQMewH0jy8hDDjbmKCF<#l)@&mTW7qkgXg z%#n3-lOVi9it1Ch&o<{R;vNqvYiMv!HB0v9EuP^j`<8reS7MOrw{^!Vn+siEtIVa< z1iu8zJSr-RLPA?nQ33MHSJ6YkRu9|H2!;yD=X4@}Np$^+1zlgqK;%NYC_?e}i=Sz#Zr!A+dwF`&fe<>UtgPJg<;B?a%uL0xM!zA)25yDoPv5?6zIN@J zNJ|d*c$L90haRNKk7u%U$z9ot&J0wB;WF$kKaKVD5j(RmN$c*Ju_B4UXC$5LmCQI`mAo2y=S5)DWp@=GPB| z19q13ShjML+P8{N)zuNRwhf7=St;_ly=TuIaT?awy7oA&FiqNe-rTcu_X1BkENxN(`Qt82^`J%G}ip`q)> z#>Osfa#~*b^e7s?ci?|kMrf4bgnz+r1ta^!s-v(u%al2Hq*+OF!E3Z=X*=*VmfkLX z->mLA<0n45xl|8JUaG$%26fdj2M9llLh=OMf#%Cg zD})amNUTJTHNYEQZp#w_VEmNGyzR(Z1e=_m-gf9zybU&i0Rg?=*$%r*Tc_|WinO2I zj4CwU?Z2NKDC|Zz=y{#*=;no-#x5~>zc*{pQiXhS3oixn&71IAR+f-9G_y=r6V2sn zXzP^1L&1&$htnfl1kB9Lra|;k)Gpb8;>L7s{i7I}Kv?>|VBP z86nOo3dbYz`J==DaN?v$fw?Q2{?n64D<*dBS20@X_{Te6zcD-$Jaj^#W(wSa#D!RO ztpfW!P&C}8yOyIaoa`4hH8#FDHPSvk*=n_vbEqNd-goD)L3`9~DFp?RVZ-)oNj??* zItqIKp9H!POI3Ofhk0mxlnI8$;0qq9lDIQQ#<#-KV2E!7?hJ zJNFI=syAv04;`PF=nZ6SfRrKZKE9HPY3-IRenLj2^*36d_2hQAg|GPC{??flMXCf? z=ks%2u>dw9X_MDbM~I50;_rJmD#~!Y|C7!PG_QAn`DS8|l<#o3P18ut-2aI~Q`(2U z4xDR$AYvZXR%9BfaQ3V$G|IKCtUlO*i(tZp9R#3{An(e7P!qlOEBSYz7e_eqBE#w} zB*l~mSl^f#+l@yod24R~e3#v_2@Unt^dkzNBOnkSV8a1U@`HR0fogMbnGqE&wS#b) z)m2sg-&$J_5JFDyk#c#|K3%@-VU)Y*l$1Sm#mGD{_wR2tN1CI#Yb+tXx253aA}8Kq+c#-zR-6 zA@o3AT`k%eY>GUha|76ku&8!6HWwijf)iF%H3FoE+CQplU&Cf3aPVM6N^&wtyYuG> z9rzN;nlNQC)>{DrQno8QZNx^Xv>69;V~&jU7l$?++Pf%k<>1Kwc?{J5)V7?|1-^pP z0HGX!#H8oXYhs;QSy>6=15F^mLyqWBmRtk#3lr`I%=uGXQ>&>I^Y?5<;K6jZQ&ABS zT)AtUrdshv`NINx_r{?9*e{WY=AG6H(7LIMK=-wOlsMTjz^=}fUNLPanJQQp>30q~tg zejGr=wrqTH5$HqHSS)q@_U$zR0Red&AaqA&&ntF-{;$rtapMN438FR%L)0Z2{>XJ~ zVwJ6}ZSTkdEM2P)bX+?}M^k)fTjmR$e3gsNRh5;RaT2b13kS{W?}*^vIdJ=PAjMvq z#re$7yEKCOYJBVLr~xo~<(pOE%xy6GnP}=YoF&8N^uT`poG)C;y8LA)$b9hkZJ8;l zsV~t43Gd&(-^9qMq@5MZK*YXhwjE(NdKZSD$&VdIWhy+y(e$(7*;1tY@)evXwTQpL zW%BXk$C`kv<}I)GkhSFF@0>v6%CbXd}87f zzebvYjEoG^6jZbbP+z6ZK*i0;=bl7EAR$47O8aYWw8(U3W`@aACVQQSVL)a`$Xc@N zz+5pwL2Jmz0LqyfX6Ds(en8dJL=ldLuFV<{+Vm~R&(9D|oe6TY>Cc}(2@#{ErIiV| zo&|841zOA2c*BMb-$3JN4%EbI0pUl3t58)}H^#3fMCmVpv&dMD&5_IE*^GV>8n>e( z4!V&Lh{VP;tphnWJ+h_I^Bz)Xg8eg@SCV(QCt@Cr1N^nVzPD2S&BK468ZAv$ML18E zY~U85y?gg2J%0Qi-IS)!8usQ?^=Fr8`IZ`~dY?Au6FzJDZY@Kd5}v_{1aNvpBVh6r566DK@HE`RnwrqBHF;be;F)sDhXCN&Q#F1)(@4!VC8 zNhRPJ$hm|BYKX^fkl4?`Pkm%FLWkDoT7Zbik&v{!+n(d;P=Bs<g5ZY^dzP41>uS`6L1~GKYzXkRT=)4tzBJR4U(J` z3<3-&i$8w;lsdP_&@@XMl0L`3Ye8rUM`$FcaE4AQ2HMwCEXTv_z7TKxKNsU&fuRmgIeVdjY}la)_5@(Nk< zLI-{TW)lVv1l~WD=_cLsmw@HM6ofZ%CxW!Xza84K1HwEWSJg~EY?fq0DJg0z-e(Br zCju4!kzliOb4_8sO8$~L51hV=62pG0&B6z>nIZ*;$Ps8y%F*u;ZJ5#or5!EL6_)ZM zBO^q_>iYv4gZ}=0i((HN0#RuUU}p-V8Fl zH|K3yT0r2oqhT}#fPZPKPtRI(7T>IHZM{!01?&%*E&tG=Z8iW|F@J%vu3Wwhkp_aR zaG(C0>+h%x?1wdCQVA`0b}PVsWo)>gWMumRgytnG8Y>T7))@NRB~*>JoDP@qz_#HV z6F=VJC~0Axvs)DqjBj3a4>Upw?TgaNvxc}2q{bg90iP0sM}A&jISI!%u5(c+(ePbu zrW(N!=g-9m8#6UU!AnxH*T~AMmV!(1`5xB*FyE#w5^=6z4O9jZQr(kH85d797P-4O zrl|5HJ$X_>WuOtrh=^dxcR2e{Jch<#%a$!^D5&K96=h_+p(SqGvV~7ouTH71vC#w( zYf1zuu=A@Z?3M-Tz*~#1eC2*V0wcjokg}QJ3j`4gjp%0*Ydue%JW1bl9!;(x`8hbq zps_=pLY1}66+^=hfN=>JE&uiFEb_w;Ed>$r=*$<)a?$81C5`qD4QUS^0%@wKs~Zd2 zArp*N7XCdTCnv|?vBFy#zA&K}%QYMl)l>$STJ3e9my04GU9V(k|EK!|Ew!KFZGQnISAxvG^L3-NBA76IGxGu~L#1VPZ+1WYf z+9p=kEOfkyH8)Q}InK(?hK&VETK)t`UNm;;8B1eenaKmAYBYn1NF0N**9>|swP8&u z+g2oEZ4e!=um9O8<~*6EvXBLJI>xS?%?MOa*LFep^iK8pel%>1KTfBSdsR0^Tzc^= z37P|-xwT4euii{;+O{n%LkA8*)$`{~#kVW<`?i#=l2Rbj1`RRmoNCyUiCAIq*sELc z>Ow2=P{MjKJ|2E*|Gs_w4<#ywisfbRv=_Q+r!2rSZ2cF`3xz%?WJ2od>L!+!pTLTy zHn_(F7UlQM5{asGpbNKhtwE9fc60Z`K0T)Qj8zjw8IBwRk&w6OA zAG>&YHLtXU-P5zN?|aIEp`JameoI4L2B*q>_5oul8(v>;X!rsoE>uu!&@^W0&aYtM zgHI45W%7kNbO1Ee8@GiEd<*E^%4=$5(N=Wz_rvE)WX*^dnF)B!)zDlk+nWFzlvPx2 zq49xh=gEcP^Cw@{geSyqRL>n=;_`cb_e1A9&rraI&!0aRWoZF6N*HyxFC7ETpK;@_ zS;Et&O)YL0vdxxJ!b96ZvaB{XHvawR&vBe@Xm&Tj?xzTcS#mk>8bN(t>;Dm6SnKST zJu?FPU>*|Xz)t5`chPzTH!UTn#cg6|Uo9yqdGJbI0v;scWuYn>83R*OADEGBhpy2` zsi>?c-d`~>F&Hu2Bd*mmpzu@hj7L^%6~4qs8Nj>gZhoIJh49)g{ahR`;mjM5h+x?) zYR^k`qFQ7Z%0GJea67h|mdd;S=dnsFwD8W#NPgntMtAWnyC^U32o<7bAO>Yi%E{-% zc7<7dNMuW}rhu%p^sP2m1yOUzpT$UT8TNxJu#Il-aGhcYGpIYbb8zgEu`wrDV5*m) z7D6AeNdTBu{EqV8rwIv!=4TVTCQ!IAsSWluJ|Q9Df@ShvEVI|8n`dQKrX%riHW**M z8U*@o*WtstvMW#VpI$G1V~OSgS02|2DRqq`QJQ3g1)Wr+zjl$*n3ezw)5Xh|%Sj@P z2Pusz4%`{>?RkOgQ@^>?v{5y9CoNlZx`tj9m4Xh*L{5RQsWBAoaqsT}bdw#M{E4~# z&dHrbKZnt+v`ODR5U_LCfdg4B!_&=RO1-0_*^V4J0+@508}2Yd!DH8?18cT&a(Grg zy-IN=TvL#^bWB`4RiG7}AYA=B#l*Jm*Lx#{$TQEj1m(cEV#PZ=cO)Ojx^*{Sz7&SP zTEkp2F)>jS<|rzj@as-tRM49>Y9H+tlvDUX)t`WTilp@!kC!4|p<0b#niy_n1+}5` z9eB8FV893VM7U}Bv-c~st_FU`$7L*lzmb=h4{@@c?ycBR9Ve;J_X*BBceUkw0~hjvb6arCNm6cR}{NEtAjHz6mf%vDxZm7pdNNASv({$|;B4XNss z2NM2?GiL%%zepWTNK7OQH{1?84xViNV>H-4i~z%J2)Fd}eQKtLeF zszZX2o;JVrx|35EKq7u}wxdNun(Hz>3uoNXijXVDqROCh{!tb2Xbonssp95lHY&o+ zjLT_#UvIBk`a<=?V_RW{x(yc+{6PC7d7R#6z#rUZx%*};sv6%^ctnKPKl3L2wQ>6! zwCKLG57eiqetC68^`xxqP9C1+U`BlK(+E64lWMla;Ag2h{ANXfd?dL;9^ixl`{>c5 zdDn)wK+7Vt3UDjzVYhy^8gE|)YZ6g+(Di6v-3)92=4F?V&>xOJ^`UD)<3GapPXB!N zA|x&@7#MHQ+jjUKF!s{Bh*6ePk?9cVt3Q1rc?l~_!|Sejt*3M~6dd6Bc zn~R|El7+`nL*gw3^uSs#=i=sm49hhyFR$`!vsv9#_$wpO)x@h0y2C#pVAY2Y9}J*d zmzS4IjedViq>{weKaY^>JkdAq;NqeW-hS*BdJD9oJa%jNi7G~Q0^7N(@~;S$B?>;z zt{7Wxr?K;qz+P16txJ_e>?N8C3^2diU+31<)cKUx*2)3h6Vm`_?+W??%KGaToEvM{ zjiB!whqZ)|?5G8pI9R)W{jKKa<_lMzc){ccl)4h#CBV5fz6*3(nePT3!As?9k?*?T z8Mc+8pa?P0F3G~jgR97Te$J?_Ph9j?pK+ofset^rYd%Ce`s z`<90Xl`lCO{FCO=(=HiVSy+oG=yf_VK*>8fId85sldse;oz*=vdsgY8_&a!NirGqt z59@D?*$LEiXoDE$DGStiAz$aX-uL7Y`yXpbaz}8$6w?D$8|m;QT3n2hsG3{46tRPi zDn?DaXkb7A82@U?UWqOPdllupD!XrfX51iv^T2@(;7)Ht7z8bzY4DELyH&Zv*TwAM zvfz-~2as*3K8efN{wXgxVbPRB3bI=2I_Kks!}ge~ukMQ?1r34mZeTnUXYQcYcb%jF zP@ojAi)~a~oPM7Kbb~KwE!qlQx8hZsUpu~Nvqf)PUR`|>w65_(9Z@l{63m{!5%d8T z;I1D(?!0<+P!Rrby7z3%Skov8HKeKCZO9@%N`URHVeIFX)j-6#%2a zCh7L3epYEc`h*Xtjy7lp2)K|Pl2p$foj`orkF;%u?n|U>H2eZURevRk2MR3^2SKBe z;$`WEre*8tJsNJG8mW80AA@=48li&uXPY+}qMV|WDV2M%v~ajfH{W&{dIpu_bQRLQ z;60AxY+H}_pP`Z)O|2Ye&w^t30h{*v_3H((n3%IsyPBUmdlqyq<|1wZC%>oaAtU}N zT3TR6TYbcJ_TaDK;l8Nw34OjHF`7}?8#Ge#@^{)2>A7fl&CShGsk#x5ms?(?C?0ik z<*6u2Pfza)s)=d}f^b%qrVhjL+!s3K_0o^lqI)O&yfs?Rd>Y=LVxdZbNuBG)V#43> z9YQ)op^l7-3}_p~^G(mUER4hT@G0=+xohiALJ6Sy*b^#G%E>*!#0VIm67(}dcmr?a z-6fT1_WJry*Y!ClQ)Env%zSNQXScy<;wj0`yRA}HUzvpV=5Z-~1N2>98eOeqV6eku ztj0581MiA{b&T(M;VdDqSu4Fs-FZd(qk#qvj`H1oy3#m%@7T!53Q^U~va=6uy0aT+ zrUOF^0U(Kp`VFh>{Exqfuhh|Fz2p@YPZ}ETe6IJF2`U>#@$8IIT`roN`=f(`ytN$Q z7O4GVwrmQ2t0UmR3H+9|?CgPHj0jL=diqWbzD}GB=q!=1`_HMnJ_XV=B2!-V^&gO5 zFEy5@CKtH!P?+wxJD3FnSZvh`g^m~8!;C9ehF_7>)Qo;_w7|wSb^CD)bG_rvxGiuF z&r~}M)-8i?qZslE85`pjVBN66lOP)S4nbF>|GfrZ^=gCxEOlzLXZD4yDVej9O5v4| z*p6sD3D+M;y?mK>8?V3$2VV%WAmy(VhGl@F3LHoGg-?-)0dwMw)|Yg!4r45#X*8Up zpDaN;==jLhRh*NBwx8INV$r(sC^9nj@dfu{Gu&xNU(Ar#N%i~Dn)^bf6}X}kU^62p z=XqnnCSb5Tgh^mvXJ>~i=+Be=`#uef=dU{pPDc91je@q&GbACwSCXImLEUtgog!W~ zpq9wVSoGsxvP_k5@-{%BOr6+kMuE@0nPb^XORd>1&Ks_;wM5D?8IFDQ`0*`3$B}Kk z-J_$-8#itweBITxXX>8LYPvp!wP)wPeQUtos62TNb%h2o$Hl`_b?mOl6*_Li|-%R-ywY{8jZN4Gqn|!!jy! zbj2(SPEL#SN4H|ihprf1Tr=q~B!V71*ao|L^xL;@+vY(v5&ewHAOjS-qaYvIs74^+ zC^(H1_iN|Cd|p^sDBLQbUkD}-e38JRLmDXy!10Zst{6PhwX$USTruUhiXzIVVW#F_ z4oFikVtP+XDkW@3Vj0Xk|2ui|35wS@;4I^x7&%(|^~;xwR5NmB@SkaV<#=P%oMMbrOLSlC}H?N{H zkiqN*5vQP{v@b3hW4~5Wh-LPwnwtK)T0}D}%h*Vw{+-J0aYY=fHjhTYEM$s~+z{=p zaM=W!DMAb2Em7JZeG!e2*$6_9Y&G=KZHXWL72^nQ31YV^Aq>)N$cyi05Xq5^hDLVOtn zz>rS^AnEPnBbOcY1jiVO-~_s25Z#GmH>O4ccgY1wA%l+4hDyzR?wx8n$jirfD=v=X zYFi$Sot+(V$zq@)&!&d~os~7_u2Ys}j<&z{fh2fShyj)KA6DS*8<_%pmCQ(^buIq* zaT&a%ZC?)|Sae{|I&<$}O5;(jOv7bKXJeUgeC&UGra>h-;yft;v+5r#k0EliO5ei# zyfw=GLCY2v2p(wv?*s+)Z>l7Gb>{Uy-DcS|9`yxhNS|nCmRf)8D?pVu+SlT;_-SdwVI0kigl^*R8Du z!DudLWxab!E|PxPvWkFxg+KyLN{}<;PoECgx-$n-SVi?6bpA-argMg5D0|JCQm~Ce zn8!|pFHi=CPad~3LTEgx_p|~zm!?+d=F>w8pjEuGV zE`OF`F3lQf3!MuCs(lH1i%Us6z{0(U;~3qO9Fp5-^gw9Qx=_4%`T0xWr4@vQ)GWIP zqZg!ZP!Ykm-O=`8-Yua~4pa{rsizFkQnLve(i2%@WMmEYl6(f%;W|i5Fr6d_F@-N> zqK{+k^Y-mAA_Spcs6UJKayb}xa2_$Ojl!J?!;;|P!-tceJ*y(}K3MiFw^4>mw?@agc|SM|d4r|~RyBP5^j_AQ+ye$ox=@Stxgg8&KIAkithcAbTiKnd~*Rr*g3_>D!2v-bbWI_z3+8QwnP#eqJ z5AEL{{OQ5baN-mq3jjt3oMS1vn}6R8DK-r#f%q&^o;}MVjuSE*7_cuj8M6+cqwa`6 zA;(`GK;zSxW2x=!`aG5QYuWSbBWSXk(I9Ank&8hXY+&+MJbl`qd^?V@f=RvezoU1} zFshl=Ye-8lY@xwMpWm}@-)%IU@c-KY7%8=H8(X0RdXF$)n7q!o{N*ve2DYk~;{{+e zF`r{X7%N4n>v)a`Ex=14p!eCit;T`3lY@K6n|TF>AYB{Ce@;a)m{0 z(ujb>>3NM|oBA_Yb?L>8ZkYOpJ3hvkvE01`^Yv?iV8D&2{`PgjF5oJF7rWN`euk9* za%VR<{8fx%4 z{y2D{P{nV4UuzAGjD#+K-T|IwIn4RE(4(s|R@`YlyQsYfm@XuSuHN2%{FE@$oZ>i* z!9(z1h11>xJ++c2G{Bh-wL^l1h+__iK%Ko z%eDR@HpAM{(FZ&Lt{>^@>+>dka$1^V(i;?KtS7QqCI%nD7Hk6(__?WR`HuFoJiCE4 zgv+xqqSc(?J}767w1Y4fdf2Wf`Kz&U^yU>HQSDOAnkTxaWU+Y zt>`KwB!oW03#WI0SATb`V~Q-7B@Aq!hu9ObzFVn1@J?b5i?jRW128m z-G=T;E0HT9#{l7cjfEjm=Fs%g>VnI5m3jp(iZH zM#L{+Z-25Zi2V=v>=^STc^H-j@E*T8NK)1jK+Q1^xIQ3>?^?^CyOL_r)19Zm{yE)bBlOvRk0<+$OGA+{1$r zj67Vd0LsJ{l36Ee3MAy7%BaZ5N1&I9tDHnF=t{v->#q09$k-rkawiA{P*{l#Blkp* z=@rlgZMq-@RJ4V z|Az)u(59RxZWm*Q4-Ansj&aDthe5#iBq@OSnva+Hh*Gu=Zq~!2pin^rgcnR=+Jx*R zBsOM<-_x_Rw}fuvXOIuNfuP_#%T_yvi!S8Ap>nSGg&}7dq#`8wM>uU02Ep@Lg3^pB z(wIjZht5usx#0W>Y@8tR=wbX8QNWY^3E3nS+MRk6jt4^Y`=9M2UpOAlFJiv30R0SQ2(um{)|Ih+a-<;2Iv&Mnd3+?GdG8+VJxOf>nrmn^f*LWhZj)A34quD4i@17q8Hh=>V(?~qLmtXv+u&I z8JS`#NB;PR+kw_Dc zV(0y4W4-2M$v8$z9VlnCvc%gzfflT4_=r3j3L%QXS)F^mbgh^~Ky|CKAh&VgZUJ2T z5>jzmLE$0{BJfdX0SzX$Y-eTVIb8mMjqn~uRCBQ8B5yXcLg%lApq)kmZ=J?(6YC5T zh3yp>df={rH5>&h<>eXmWf;|8!^Qp(lP-dpnmWc?`NHgNXIvzl$*5Cd!nm+mJ@3AB zZkl9VU;%pt6uQ)seP3Q0pZ@1gvzY`whIovu><;JHvgN{HNd}mc1C#(SuYAhFG1pn# z1>!n85qfHgXL&rC*bR4_o3zYdpas{*;1PS&PS)f;dUau{`ulf1KHc>m_m0>+Zo4Kw z%Ldw7*a+2g@xxx1&Kyx%ud5l5$&alENWdFoE$%B-`?8FrUhT$ecf4}># z8z35k2Tb9W!~9_Tg(;$S>9io8`WPD7_?atlcZCY&H*fTrMO<1wkrV_}@g7{cLS`2d z#z1c#iP=M1icQo$S@9tj5@!B8l8e)H zz}p`IrBW@xS^~vhzKO&EQR*>#DLM}?&FX%1MLBu&>J&!0?)tC$I7K!S91UYq?y`ldV}e^j!Y zk-K4@uL8Cm+XK6QNlD2GZrzwdW(sVTD);E%*QX@=LNmDyGdFOrU}@ePW(QpHl=AoB zZm!e(<@f#idU`y;o}D+BBs{kQ?);!vF66pN7)XqgUBv$!?_;Nr>+6&2wN7A0ufF~m zg`P+*yOmsb7VmOKRpCAt#ym)aiClr^ui{M#CgPk)+YXW#ZY-*l&DPG2#^u4#u601% z$2}R2%g8JPBs`8Zl`meg0RPDTeAnF-_mnq|?pIh+)GN~DWqAx!3oLYfa^{kp9JdIP z_9kpL41l=cYdwA%cTU|?E9|x8+bDUYFElqPB0D>~?In&s!|##6y`$j?MK}KXoepn$ zgcW0j8nG;1JY)m9VsP=+G|6a~ccCU?&pfbmxv{;`a87Y;uy)o*5hljxbcG#OfhqTr zD~^UJ+v`PMG%`{hT;v)~-J|fdzOwSv)kU9Iu_WC^v$%3A%&tITikyp=e{9&{BXk=} zPiA*@iU(c8q{@6yAN3Ob)%Yu)U8O29Q_g*6jC$UE>t~po=i|j?xGD%O5nT7bA%MhC zYU&HLP76wwmX_X58^`%`vJw*L!K#yMe%`yf1`N?rK+M>I<@kwzKq_mW{KyN$+eLWE znTGc8@pUiiHaKb2$8q(l#>0+!@;M~#RhxzB(IOqO?Y*P;l%l3I*2=8pKUMC$G8Weq z1-9(AvJZZZ9Tg!=0LP@f+B>?ucX3~2 zpmpk?(?WCVcqtp{*y3cr45ajqrz2%V7Owq>gNqA5ik5=VQI=FzGQp=$F5^O8+`+m8)gFe+WOibDd5SnuN4pFcB~nGdb__fD>pvf!P;E(gK4 zMBWEk+qMeXpc`!;8Ry0%g+yiHUIBsMxUvV3iNJK>nK4;i^@cry!ot!7v*L~bNXYL1 zrtm^O%Df-2K8cm59{!w}o-TiL@#MOSI}bsj@T(W{i5y+daRTGDiG$z*bnbz?rFQJ! zuQYOKv?zsvI$W=-r)NA!P@1H7LY&}@3F16BpJIukMnj?coFMe}7y)}%>6VUjEilL| z`1?6=yjD_W4;#uNvftGs(~xGUqAZ%CImwtc^P^7(nHN2~X2_ z8y0#NG}TA%m8>BT9s~d_R?yb~Xw%)4@<8kI1XOfN0ZveDw$?eRq@4=CWx3W@e0q^? zryPkNtI+K+0Cd6dmhF0Aa59yJ?Yk6&A7g=75tbrX_Z@&d|9A=X92ey|aJ(Tc8sUXX zpfsr&)h3C7S_u>jO>ONSROx9nMaE-F502af%EuLGRqR)m9B0Iy*Fww9!2Kc^USY&9 zRX-yAjC<;CLI>}QbleJt7fe0wzaoX54425SU}Aa?X6Os@46GYPbuWnv1I;8E)kX2D z>ctq)E(l2b^$XQ*EN$R9OCMKT259DoceERXXDs^QmGxv`9;2_`LyUXsAPFE_cLG8- z7dY@!GyX13>DsISGh~34fq8oXi1rv^q|ESH!Ktw^Vkm|y*$>pwPDx4qRJdow2~)99 z)az@Ys2I?e)7W@4*$+dc2@2uJn8k_hP!5e1eEP7qkr(`r?;j8c3kJ`?`xjI^yQXs`W| z+actmrlIQqT^=oXkrw7J8{9ku>V!{V?jx&yJTf9_zhE!B18^534D3n7z^Nthtv+bn z;MPcpN;C+#2sjGWYrfNHN3YQ%LNc61mxAKGJbwNRC*~7FvCvU#+>!P<)T0 zC%tH86-+LZ!<}~+O_v6NFg8AJ10;~I6xsV{Z0tSo&>f`3cde~k;ai8pz7%gA8WBOi zeEISe51*Id*+1hlN6>W}$iYT?ePi<1p4?xUrCH0t5sb(0f}Bho?cgHu+3O&(p`#&P zjmn8pTsP(o6BFrP5o`&r9LrM1_+%a^Ki1HZ!JS-hRD%I1_H~GX_ zOn5T4x#7cvK!@-F>beB#2#L>){2JD~+&ypI%5a^#mZtl96F@U*1ff}bLw;0*z=xJ= z;e(vI^FPfno)sAqx*i=!jg_TrYIpeCUz;bm`vtA7Fx*S1M0wHo|p7rZ1`lukq=uYiCT0s(^*H^~?< z0|P^Y#G!bX58=HE8hBjv44ky#X-{(AJZ_7=7Ie%mhFiHo#R*!Y>Nok=+MK z;cOFL}3Kdh+6T^;{r)_BK8gnq!wbI%6ig`rx( zXC+!VHzG!oHjGVV&yy5mc21r7U&Ot2P}Oe~|9JrsC8d!x0FjmuX%G?#%A}!(rrd@BMu4^PK0L_c`Zve8FP2 zfqQljL@)3|k==p(PnuLv#zuHxZCxES^1r@-p1dlLBQ9_aFKcOOeF9w$`nq7HbW7_h zAxk9}59~>m`yo5BOsGXXC?F&acufBW9B!y$>Vb?@bV0*L;h^GQ2&3-h;?=R@Y$Rz99oFyt zCzG}}5d70bPhWw42b79d$&LoLjQ28EX| zS>Y}twP~>1(NMIaUcpf32sLu>9;q_m3Ka2nrPTK0j6CdyV{Vo-XWI{+|;=7XpXtzo#Srl>dK+!=C59wz~Lko`2eN z<@u>)X5j^CsZ}UbmFI6Yz6H^{qNhYglhi7EfBUknIz``9WnAhJ{CmT(;|;Kq;d2g| zi1biJhMnf9Kk13Co2@Q$W~+QRm{pV@ef#NjW%AeS7Y|y~h^p9ELr1fX_@>G+MG4c*yRmt6^ran18%#Xp-`=J37LvpgaE^XFz!5FNbj_F~Ol-lA-tBfO-e zs++^zgw|SY;oaW5l*No;ZJ&o}->r_3EmL`6Ku6BqSURt(rL=e|d-OVtVa0jazO%PK z(3BBm78?##6~c{uSN7wNYkJSYRpdi4-=Ph-xifTI-kc^!{fNu<`kS89Kw90lCetT; zzfK1!H(1<^+LNqp7&c<$kA5`2{2@?{tA9V_?Jkiw@@s?qpirA0r(IJrDkJiW_Ig@i~&PWK)+%dyo)${!u0y6LHsp>A8m+Ud*oN){H{1 zd{TTM^LKZG??vO%AENfN#$GWbbNr_YnC!ItOn-2271uu`XXva}znsK$^a-7wQm4}A zrf>3Aw2)KG@A$3k^5?Qc&Hfu5Pxp-C3(`3E3b|-v@V*@TJeCgW(DyHg%nxTyTVHD= zzhQimMPS8^EN_u|(%6dTfxN2|E6j(eLUq>W(D8tjW^y1N5qx(gc*G*8G~s!41;YvR z*;%=#>cYSd_qO|>%#!(1nr`}iigAx?Lad3lv!lF!t!e#?!_L*e7UKLYZgsvxmYz{S za=Y%hrEQp|VOsD#+|Qf&oB=FajO;D3?%B6*i4dK|JH3v4&ldCc)%WkKCm;|Jks1%@ znqJvk^JQFAv^(UVNGAv3Zdh-!&_S@{kJ5q~pAS-(s5P8AeUp>tP1Xzh%+9u>%GX%| zovm&Pn$XE-d4*MWkqv*zZRhv8ggH{&{J>10U$cFqVQJPgN+{&@Xirz_ zcqSEUX5p`xNdT?SY;1a!-SzSNVtVSA(yywKYy=hOVisz8wF^7qGO9IT8S2-4UuQ&i zW}D;T&}|`E*T7@}B@H(!M^av)qYdgob@j~t>549~n*Em2zjKc+xgEUz8c2z19PTAj zsu#o{t>4o+$Tr_zs=A57IoAu=2pQL?p1zaYQ+nTsfHHo#ZM!ca^)BnC+TT&Ww)xwl zMy(64K8~307`Bmyw866~dW80@`Uz2-Hd@yXBxzf<*!=Ar&&ld>{4%9J_B}6XKC{TR zNUM{;c`NcDsG8=ExWr@6>kpx*E@S?aP~h4JPbM!)3`S<(-Zb_v9v;tlyoml%#&32e z3iuaz3aXs)kO`sDSty1)ePF~2zc*cBm2r8!SDvqIvnue-wOaXJF^>Ml2Im!Oocef5 zt)iI&yjp5*YX9H${#glF{d@(F`Xbwut>L=Ax-2I1266HxWS|UaEmRE2?o_#*#jAfKn z5Fa}>42NAjS0H~SSRr+?0wegl;#)DTFcz)jV+I3iohOc!QmW$Ts8 z>BVnSX4h1g@5znbBwSr=KO46jU^jE~xTztsy0zIpR)?3#5t(9SK<=_(Oj-p zCF2jCe^J<0`T;MS-7gg1NPeMma39k{ z7{jdmC0_bf@oVw%GSMN9_HX@b$bXQrwvK0trq3K}aF$Z&bn7%M-lwTVZ0YzgXU7F478M@)#&WiIeKfX)PV~O53Zd#^&l{(NgI=rg z)bD6aDsGgjvFEsF;w`CD?N7YoqIPaBuHaGHnOopdrE@YzoLod?NSatpzZI}26H7@~ zTNWuFcM_YbR%>tAyW=3>Sg2BS_2&HaO79u>FV{OHVQNX@^z6RT_55R_?#5rI&CK#F zY!>iEANaEI;m_Hw*k5t+c#t4Cw;E%Zmgrk4pV%`%Tsl9;__m?ScVbRRdnGPaDN^y{ z5_j-PKLLtxJAA3e_*T|YXfydRySAG-4sHE=zaS|LYJ5E$)D?Fxj35rHZ@;5n^lFTV zA3j>={CuXxD=_@!RQ`wXXKeCheTmW>5`$p{!U=P(Px3EKJEs)ge1>KPZCRE@uw?_4 zdwPi^680OHI4=9N{kC0*D4TlvXI=TNaKr120zM`(()6TMhRa`_T7E4;iSTx2C@HFl zVZ!gI*7?u+;(SXAGI!L(quDCy;uqB>dpI61q@999gcOjV2x zImdIqXWM=u4^C%T%dqWdUT(ab2^HTevAFeF;nyyeHcdV8p`obwb>fzeZ)9LM(;MW- zbM53>?i-J0vOORvxoo>E^UKDQj)>c^MYRXl@C3K*Y2^l&;|sFq#zPA<4`}SKTidCo zW*7N7;ZOZ7T7U3y`J=k45Xy#gg#^{ukot1Efol^h8PzB`evMXB!Dgj{p|-or2fid! z`=um$>fqI;sVdquwWQbg7#$}U_-!`nzsU!uXrk$DT}554`ulr?MG$$d=guFmkk(@x z{K-I}K6Ov;$0mH_pM5^(xpDEE+rX%MNMC!*lhI*_WwYg&Z?C0v3FiS7acI)dbrv?c zb1KA@^OiX>zaQ~JL3(*rt9N@7?F>!g#+Nej;msPAm?JAMhvV!)PmuGp#r6E#NP)?POF%c?33Z%3%3ls1!b0v2W<{7v9wEXM>+=y zlS@vaQ@De!7F8?%mWknd{6>;qJQFYGd&2{1#U=3^J`41-XSY)<}|?5Wu?T)#@|ve2Va7j0437H|Dz6JssndiVEc z|48A(CyyxQ8xnTK_cPA9=sXnID3m^>l>`Tx)1W?l^$s$omBm-LQhf9FXRHtU}KYAJg~*eh6Zkncrqi6?fJ4sFhA$#u{IqY z#^x0LNWzl?O#l0MBAdJig22mU#;ag&B<3H?PMM^!4SEq6?jf`EfLU z;%3=CySnVv==WlzWY>wQ0FRZky_^bO;3{c7YWL@PAe|!XLg$m^1mk$?BPM-0VQ%?d zn%1+WJWJnu{^B)vjKLqvd13Zm#Q>wXQs&M_Gpc88>%A5Nqvq-d)HA z@&Yy4GYX1%>eU#Bici}&thXL4TtB|Q9z45Iw{>5XSX_G1xI^klZGg{UIxP8T@#~sQ zZvUFYhXyieS+r-@Ulqv`J$C~}`5MX&GCt{-yX&yDl2&MevuYG$qVT@m41Tst85> zUmJ&WR^3r0KfCg(!Rgu(6$+*5m4st!z|N{!6i@+onEE?Kg!jk53GO=|JeQo82q8vh zY6cTC(Ra1yjhs)vsJs0vZBf2gh>{Vgcuv2Q z6gC<`!mGY+w;Y?Oe^CqMZeeJrq~rYF7H{X*nUdk6rOVhlC6SV9ettoCg^d*rb-4w7 zJ5B3_$@=)!R}Ib*pZ2lv0=B$ZF-cc_*S2|vF?4{$8xA;blB6&3RX8F)QMbt*y z)%E+9Mf2l%eW9@adA@7$aT3=t3ALVXuUqEc4H21n^`Voo4BIwv%%$C`rKWrLrJN=8_cTVcK!19{l-Ax1ElNzGN#HSE z-cp~Z$;K%D!1d3mMmks!+G*B>Q&)Q@iBBC3nNuR&wD9|r; z{)L@ONEPQdI>D3l!u+bXlR3`H^}*KMd%CH`sW_;Qzw90eyu8v_pw`vbk;{wA+jU|w zov8ih)11&6#TN>iL>)Ay3)UnX7xqvgp@2!}b1|RnJ*LGia_5rLX(iQxPo3XP|89IE z%GttvAY#Eyt+0ZIs^6#bEz-?65$Jr}AtzzXjkQKu7DW|3*wlwbjNj9GYTxi|wGjw& z*1KGs`%67GoTW{(jpdxZy}!q}&wF34)e;Ay6`fCzpR?n6-0LmRA8cn&5vrbo%pE9k>cVeNwxFjXWZs5Yer1ot^9%FDhuE`F3R|}mydB$PSV?t)1>Jd!SUSL*6 z(B8Pa%F7Nu-jnrC@*L5l2e?{uR)nctPZopJ>YS>dQg zaJ5yzcwJjswpUxQQ>4TCW<(K6aff$O-t0NPuejkGQqo(8J>00yYy;BCptt@Bg5)0P zFwqO^w~QK^2p#_&lZJPBf_%}~B4`WgX_6hqsRNlcgM&Qv{*1WNAzH@|h7uFrWEX`t z3-V15dH!MKiFbY2(jv!5BSgy+AB{!xje((E(3_H(ET(spgNBEj@{IKFm_R!}ySorN zu^I&~Ueq{klJb=#vE!a=N>Wl57OKNdp4UEpY&P@wp#DL|OoiO7?j(J_)jmonNaL7V z{gL-pEaKh0bu3JoT?|e8*rvMh*6{~2rKgejO3KI!l(x7?AZ>?NJ9gpA6uz0s30K-M zEfH-0dn657-ljaY@%Bx3Qcgq61!&HiuyK^*x8BJ0@Y|9I!C@X6$5dWt&>((IXY|Hq zGygjroQyDWHJnGkGo%NXomjku9a<~PO=-?)t_3|io_v`*O{0LXgu({NcpyY_C4U_;c=RU2 z?O92{L45jP!;%7*?yBvxn^WvZ*TM94k>8pALe?{WU%KFhaV<6_j>?#BwPPLyrR^V( z;?8lLrukuz(WrO4I@}Iip1mrc&hUUgH=Y~G?;J(Iup=@3Isu9^DQ}V(ve|FHgHWB) z!4tAy2{CSnLM^neb5i?0;&%az}aa3>@boRkE^a<$LJhshF)_de7 zgN812Y|})Kf|%%$az5%^97O=_r|?{NIJa|mhA)$Cl{tmy39^Y|+@Ujkc=+g5CcJ)c zM=q%zE9IQaUMq9hZP;BD6v4cv7y_M4tZdC?a=ImRJNMV#pJE&__-Xg;dM^z~p1(_* zpWtDw`GN_XzwLES>)EWEAQr=KI=dqMy-qAg9&LUiy<3Z+&ZHQUp&*~Rj zj)VUwQPQCkacKOu*)FH2e7R!(v!EFqu^AJiAkx^%CfE0(1>P_Hz-7D1%b~v^0$bVi zQFZtQJ5ucIleUBf2kgV{Qt`|M*`%0;=judJxsO{WaT;ru`C3c8qa7pJSPCpLNx5-y za|MVj@JyyNfU!TQ;M7pmiEL%19BjuIr~RpOVAovhP7~OwR28s9mIloPmQl z)xtcaxm{Pe-1)5-FnN_J>990a(L=I|xK{VK%DF$Lo+sXwP=_HWoPdZ?-ec;odclONricJ;2p28fW}WW2i+i_xAzF7D6Jz+`uI~PcsmG9iD|o zIaFHwMbAOjm2L)yN-M5*@&3YD=F)t2ziFXqwudg2c1n=}4M=W`kt5&aE39wkcD3Z? z$|q%i`jU6$h}ovpj45*C*>W(*Yc9w4X^R}GW;gP8u*7F=D+!Nw!qrFnxd>4+u5NGC zk7u!7l$`ved#C8?|0zv~vvxH9AEWv44wrsPLOmjISN?OsACKI`$%1+INofP-3vJyf%Pjem-0ZG~oqye=2T36sJM_Bw%J1$8%WLnY zT%r@-2!~7GyX!z}G+p0Fux>)fTBF1BEunfuSyeOeZ?M)cn^zqE8*zb{=W2vAebQN{ z0cH1cf+i(mIx;xEmJkNtli#O#|KTJXky3uVJS3(3IQDEfci|(609^xtA;agydVl<+ zOa`$Z-&9{~?o96$mjOW4g&=}hB!8T@6kQMvxKTSeAaB|z#B9P({I_#ET69aHe2(Y1p3-A;>(m4aoalB zU&fcekUr`{8+*)Q^2BytN5+GitD+#F>0POEzTy66`7O-KqT7F#o(*0#$k`Ind}KSp z4Qd|#u3?OCaq9bmvznCUb&M9m1?y14KMS{A7uBP_waL_7LZP&630?B?NIY)6J$*4x z_NS@iTT!Q-YwHmTMa(u@AO7CSRcFI6J;09TE{lv(r<*UNH!htzdvdv|#n3x;;3&7V z7?NTr2v-|+hMecgl{Kt2yy00bTG!Cl5;rG2+|zBSy))`ameTO?~m~J?eg!*uZ zJ@NDUpgOW&%%w3@*i0_=V`?Z-L)#0VqiwQz;X$kAJF+MeO-i4LU{)5gj)Ox*(du9f zl{=aJI)j#sq&{vVBBZ}423vnP8Q%PSgQ~%QQ!Ms!;jE6GsI|H9C%{ulQra!bVWlU9Wughdt!=;_&}>dDtwWe z2`IaHlbdT7)C{EsNI?WtruZf%(jYzhkf41A5D}z?8rnB6w;aAfhl!j}qakf(#soc) zNW%eC_1%PAHNvZdiW6uw*zXHV9K^4$xG!m)PfN*sv||_(&d6OR8D$`{roO{qurpoe zaHRB6a(Q_s*Rs9-Ozx){UQrQtP^w&%X*=CU-n-}AN+u~Dfr(46nE$#RD?8etLm6D} zS6bLPmo|$X-|{fCiWw|yn>?oTHf>W&`r&)-liEwiNdkf!$waMx0fu{cwL8^~YmFwI zxLdqktNo{Gn5-k##lyD4WZ*VIy5#NiAWbB|Qh$2m@W<7-aQ@`x8cT+YsSgv}cD_$! zhl(+i@|f3(CofVO-!-Px+Uv(evhh_hTh|qRN>5d`x6oeJF<{_G@{NwUW&E4c4L*7R0|VuXf_G4Xn`?=0MYS#O)7^NMGf>s$ZEw6SgFb(fE)B#2U! zwR&=IaYp_V?PrCJrS$mzqIn(L5sfB?vbj8HQF)WA0YH{#oA3fCgk~i|Q zV1>74%(0?uw{fFg)aLd**=MM{TXebvh!9;Dw14R+zHpmxpgP@OpvwBv}Gk}ouuMEKAiSs!Wn-}HNU6PRW}P_GCr zF2AA94k;J{Qq9cbqCdc(pLRywl^x-O+C(Au16(LF7Kd~`QWhiR@%I{34LpS8dzNCf z{>&>VDuB{jKPXH|5^{Y5xL>H?n*}}x6wWk43Lnb&Mdnl@$12BYs7wPeTTEDu~|s8qA=M=jICnx=lRjG zlGTPDmR!puF=X{@)qXOwi?J8Hz=EZPy*VA}>YiM6gRb>n*iFv+(eYl{tfT`1z3am` zF;vcPvUB}TyA~`du6!xK`K459dw}9yP*A?UwUm$u!PCw#9@UJ!eHNd4X5~APSL;cp z@~7Oe%cW8Z@knvxNJN9Ap7`b3iyfKeH@k=X1Y)a4Q|#XfbNkKsl!0MLr$&~x{CFWT zI^x@3g1kST%w&$K*k9|8#q(XeWLH%RH*ZwZcIYVQ;Vbs84~4nw zCJOOqho&>Fu*)|jvqb-tof77a<}<=)G91*8ipSM(KT;!vB#QmKQ6Fjn+K; zm@}ce-4#OhzvvH6%YR0#8V>B;GL-6y@-&1z#f~g?Z|6^m=j&Ms*kkP&1apT|E+--) zVPRQ|ihr~Aul(3N#gm-l((`bt>pW1GHr~+Du+xvO)!ei%HAB(5RGqA-4EeD6nG zcBJ`McNo)55FVj>ZlgSPYksFI`%J??S*pWXO{v9jBBle!+m`AwrJL$%Tph<=(~}9v zyCm1hBNRFJ5%0U<+?2RX$-{pc?~vljuWF4d?o4liOM(gGO@LCzH825ihSKK>M*|yB zN0^==dCu`r%6h7{2@14R^6~QZB@===3>@62{>L$4jY%Zj@o}!sH_Be znp<#{9c-YK^nIxoZhR?00(n@vk08NT#0u334QyDQohjqxccM~f7ttWbm`@V$pR01F z;j(>(##LdJGhDheG?Y4=a}`1a$-YQ4v#$+Qx!I#Xh|@+m!kuTS-|oLmRekoA#!!QF zj7C6W@0g*CU_CaE+qlc^oxOd^xI*=Bw9e^oha#X(D(l=T$B**5XkzMm+%~>|(=>&% z!u5TA_%?x|7McAKF z>Hp?pR=KXcOf|`zKH_|>@y~6?S)nM?4v}@XQ1|a4@A8AcT7KTy=_9Zl#P{15;C(YX zk!J28GfMTf@hh5f`CuY@86YM`t5h!_%crw6q)Z&KS1H73VglK>TMnX zS*zLteVg0cwC5g_P0^;o&u0DhnT$=BM%0fnIRibOt-tx|_WX*cO{lx0u(HUjRY+c0 z*tKg@R!CXoX)pPHe&>0DPDAZk6|RJ#ca{nxxRQUK`Nxiyab!OR9|;nFAmU)<<>$=( z>{yz z`~yD4!osVW-M>ruPfIuQAR7mXQz}r~$_xvzj;tuY~CQX39GK~T0FfR)m@vON{?T>xR znH2)qzJ|qXAL6rIZ>-QKW#37I)S=(&;)ywrAA&WM#Jc#>b=c_{D^yQ@+fA@Vos~;U zK*AAqgZp?HRKIuMOzlPtZ(k@}bl!D|wza+6P#qK$6rPlXZ@=4V_{s}P457gR+TvxI zKU_jVq5INQonVF4WFVmDZgFy6Lm~CuLSCoP@JP8drg0D?4a`CtC|9Kxbi#$H9A^)( z9$7>y)+n|^?wdaqi*P#s_LtMK*TqGz`!gixrS%@wN!)3wEP0z3!WR79D`aoK;np)T zKC{YISS9Vk_UKURlEZy+CwG}EOZ3uh^Z9GFNdjn~IHjt#A!)UgC2+-!bw<<-mT*1qV><&F1gIXkGpM}DZN`Dv&?fc&@k zbHT_IYZaFhMpf^)u+vqo)aSZ%Qhfv}HfPN0tAdSYds?!Uv-?ro*Opc#>TK0=KI)2n ziVdhO*?pRs*;~9dEOgdnm^SlM!G!J?taZX(n?tBlAV{0%V{)Uw>>F2qFp(qRle#?X zH`hk6&1^^2*E9w3)vK){+$3IHk(XDo+5P%s{P5gLDs1ET!bd0# zn4)^VOYRvZFfej~UGEQ4TyUi92;C_|#0$liuI<;#g)aiuZr|*EIi>piSih-zC%aRJ zbSe$NzR5L3cxRm73b78Rgt3#SFQ-MSv6x*MB1}HQCQmYGS$Kc)G}Ait_|fpkML{CD z=KJa0U!N@v=io`2-lBGOppC;E)gC)@&#nA&HYZ9`SK;Z`c*fnEZtJeS6n_hfgu;ty zQ%!}PaqYD>ZpQm?G^Q0z-+DTY3u!0Hx=(^)x*EiHlJy>X-nJA{^fV~^U6}qVIBJ5& zVTy;S-pK&AVsLsGyO^Oq)@Q+5_sGJy(5spUr*w>dKP6Uqefy?TF$?zDK0Nv8`(pVj zD84Am<4Y}||APD`G2!-fweTA9LTszN5_SfZKfk|CFHgLqC|pfH5K6Wmk0^l@_BM=A zzk?Ol*2YwP?(&g3=pNSdrpeCh_}}-Rrlbb_S$_Y3oC=?qO=8cyzf`A9mlV#;(+N}N ztkY5y&HYO$vG(pc*y^^Y+fuciir)u^Z_k?O>{C7qWL-X>NvijJT`b{S`upG}53e-} zwt2=HUlo}*xs@Z;v+0s(jp2d54lME{{g%RSC$bG88>a*L+8(yV@#9s^I@8qP-O`)X ze?R(_&&1BsGextkaxZJRO{MW$jg$=jeC$^jkqf2*HG1x$nnsphZ6;;7ievYpE_B+-$6{O zArkc&%2VxjVRDH-l@L0eXQ^bD^l!R zMJXVYJ+#MxlsY98^K=wls9#;z^nyT!xPQ1n46<%eLL{E;`GNvVq3tu-kb7Rr>;~q` zNmP9W{@|yn5PFh$U9r|?A~?h|v!-QoJ`TU$|LOZ!_kv}oSnuI^w4Cz2`>)S?#VA4& znVbnNrGoD$NSsFpG1WR_nUeo}Q~UzO+jg1ruWjkzAQNbGL75FV|2)1@cJbhC?dLm9 zlDBd`etcM{LQHYNTbRe}Z_M}*DcZ@8Rk68^{uCF|>_hlsXg;G#a^V$j*LOJ1-lt(5 zrdSvU34Hc`+ncr_%7|(m>3usCBi#P0>M~@(q@K;wT-n=C9Zx3+u8+rkM)R2Wt2V&YhhUBa=%SRJtF}Ims>XHI%4+U%k}3I!b6-$!3e5p)*77AJ!t7-+@4b`3?=J&W1+H+L*InFW~i7D^3it=g~PsO^ao+2 zhktsa1Wv91)o^Ls`@V!y`j^DbNz-ChDzvQo-JIRdB=rZ^**~AFnU3*)Y1~NdDp_j2 zOkL$nShO;CxD*?c`Io2%>0;6oze(rNl&S)f!Zo356ZfuE`1f1K zuP#r}Ry@+wc6a21S{@{I^eiP8i^})w$Ds|D+&*r50mV0=U++Jp`_wv|aI<8PuLY82 zTh=oye5ifnKX%D zy46mfS?F~(ZA23eGdxamEFNj56X_=xJTH9Hj9zZt|7gG}Aw}i2>3FUtWzW};y-jJO zFH3Do`-jV^o^j@5 zZe{^q0#O;CM&;&kHPvUd$)b1l4`y`+n2xD+MXr*azpm}%Gxvf#|`6m$mERlH!4LB%(s*^+|4(w zE-CpIri&F-RaFsr0=+XG-~}+?=8?dY&YO*nC`fp37Ps$Bu?hJ@elpv zkdAabY@i)(p5_)} zIvdNM(r?nJBSE2_HmOyHj0=N+*!p~Uu`&RU2Ulq-vN!ssGD6X<@y+1;6UGxnhr-qU`cK!yg-=*`&JceDS6 zBM_&y^Dt(NoljILF`;r~-s|`L(_1l37lZFE4MNPt3$RgWB)rSTr3zgHrW?AIP}w*< zKOa>aE*1g9UO8m)pqVA}SWm`$yfvsI5<^U3@mESJrKsi(>C#wd)98~C(PK#s4h$4Z zuXS1=YV$|&SaWN~lkiFr^`R~?UX#P+cF?Goe+q7@>*&POdTfc;9gJBntW4((C2JYe z@!Nef@>Hp=taQ#iM*VvXYC0wQbpHG~tnWOT34A>T=z!`4>^DbbhkDVyK!c%i7*pAL zoVEjfK(R}oEk|GkutToA=YM}~!BV3j1}uDLdU{D{qNWsbDG@nYtufVeHw+0(-} zqL3({0o+JRL8k&}i&8BxG%?Aj?p}DG4^-?zopM>gn`k_~qV)pqB0PRA+?7lJ(8K2%o+N zc$nGnNurmyp}hmnY(*fs>>M5f>(dt!1-Zy5x$bTSXl9CzjMP8h{_hI4qxGLZE-O2C z9x_$A)foX4b$j#`S)ei(K)*eV5ip0IUieg6&^^ct4M>1iQUf9)Q(qf2$OBF^6G){g z(CmRg@@Q#kDf#U(fTfy?Pa~kdH2Uw#P!^Pt&nO;gaG=M4g77#3u>bik4DhQV@_WSa zZ?b+88s>k`JP}97{_m&zQvc6>O7ZXP=;(u>M4V0|Ekofdv!TSWREN zh;{z~*a#^}$#$UD0oQcr;9xlFuA0N~-m>ZmF!Rj-HvzQUtZ1y7R~xpT@8^E z77DfYzJn7tJ&zm>8LLDu0~r^MS|ZrYVdwkB-pk?AwBhu~Vp{oCnhxFd`OsC03{6D7 z0ia9`L6;*}NGyC00If6w@xx*uD+C%)QengwFdXuM-*>#-MmGdT&d3wmE=egV^Zx#I zfjL!TK)p}}NMHTMxjR5DmVxdxpujh&p8JRXIXWuk*cb~eiWnZSW@unix;S)DCLk?L zp4+YD$AEB-iik*q#bE*RcRZJ=Y+PFcmuVm{KC*$4%EZ7R1vIMx`2F5|q>=?>j{<{( zS^Mt5@G4~S?CuUgTM-ErRX!fo{Usn^sw*mnLg!ODG}p6;@S;#Tp}){HLbk_+;5M)z z&1_cnN6%qs$P(nP?3|qHjZXIV8~|Pg_euuZQ&nLVbvwdESd5lsLep3#@NG!PRfd3+ z0(V&#-V(S#+NJlPpBUm==GO1uEue9b)Qb$PH!4rmNem5z8hi+y;SuNZw18IH$y;xL zPEk@avOMpL^VSHZi21p>G{}fS`V5#kj*C0cZEFU!7wE-TgC5iO_keE0AOkqhq95t7 z(gekjWtKuV*5KWcA!;~qvRbVW19n#;Vi33-LHkk*GBPp@#|~@1Q~;xMSB(yZQs(B@ z*hjXG%{rTQiC6>=7*n`lz49}_e$mUo;B2zhpw5B*e&(S+vVjKWC;5Pp1~^-cIWGJ$ z?=NJnD|Z3kNs-QD>ou&jbO3t9<|;WkRnT|95DcR_Sg!QTuyCSRz%*De{&&&0U@meW z(1lDX{0%(!HFlpUAEbPd_%1ho9bPYw;J+skfh`#Na8m*jf0;1J0v35xHH?sUWRQV= zIkHuN(fzNc9fk6Ve}4&CH*5&9deZezqO6?UfG>ea&WM3~q23uVAsA!?pmQFquAQ~@ zrtCi4dS2qIOz@YK?1F;2&XbO@$TACo0g})gSTE10R{fpHO~V@6GLbs~WO%)_9#~io z0EOrJpZ}Vbk53CYOS1CvgP>0DkG@Fm=<6$;_zms)lF)(&E!UBXpB|_fg+l9US6`nB zu&yhsC)(Pi`KDab+4ZVYKwyla;08!%1PL{@-o`$B87B|_eUbjmOiaDN3D}2;)X0he zyji?)DIM6p| z7`6jC1Ddx7q2m+wUjVi+6ke8aFt%_8=NVjL#kq3j13lO!t!-Jjy?Gq}ZNQ&C{~a1W z3gQ1h{WSUt-2%RhctPi)N5K>~Vd!FaL{rA_ruO}H(;EYHI4qz^tlbUOEYMB`9Xt;b z?PTLZCu^T6IiHGE|M~BL%VAr)kH-6Ij3=R|6aYRl{m{N_y<7d?J6JH+2xD{(1NAEf zPLB4`(I?QGt5HY&-*0P7D+N!bHlMOslKT&}GSb;0l`xLC(NoedA7c+1WOXD%WOJQc#F4F1C*Q{@+{V6uDsb zyn$06@~T>Zl2V0tYP}EV(h%InFVK+;gkdva9Js9(Rt^GmuI}t$+-))LMbUQN|9Hvpa4j#4y@&4m~3272PY2lsF6Tl z;ix}9woH6r`w-?E_d)ESmwNB?^psu(`g@1giLjKkt0@U*LW6WJgg$o8&Lbgw zQ^+ny{1^%ju%mFEssW@C`p>hVUws)+7uKe<#Hc(yQ?Ds}AdoUfcFbu3=k&)qu*z~_ zhPOuLgk6x3u<#Ikf$+I&zV(QHmqXqrz{O|(HfFb(OVUoACi-uz6;O0iA)2M=-C840m0Z$_;{{lLh z_rZl*4i((!6}>o8f{C!q;H<#-8usCh>|`%hScv;Gz66~^LmDtYF9?|~3(dr<5H!U4 zwp@5k6VuWbfin-RSt%eP=#-hf->5&a@;sb$2KHStFD(_SsJav8nr&47*#lT1Ig6n8 zc`dE`+CAhfTtud?sq_dRenS38_~LXI=3Mmyn#FOm?r;SnfdR0Z-LK%3M=l8Nm?AKB zl)*QGi9$tSOjjnIr-xu@7W3H;l+RA-_$75XTtJ%FNju#s1O5tESZq>Kk#)nFCb$)S z!iN*~trb8Q8~{f&>cfW=pa87E#<8=tRjF}WheNIadcR?$Zpj(}2CC#eF#K4Ah4le* zH_#(`Q2-uVO!X$vlJ@`p4qNTGlZ+VoqF)EV&tg(pTwTqFRj~=DEzgA={k?lC$=>JH zhzoHB4e_z`!ds0Ph{Fo_;XE)qQNNeLqs;lyGcLGMl`i?DkHfz$__{GW90$E{%CiI3 z*nSi|SqGT!whEZ8VZc*$zbKi4`}*}{qb4Zi=o_(84xmW@=|>CBLJqo9f)7C5wOm|W z;>M+=qyW{EH+YO(fv9MMym7f%-$NMW$FVzs^2r16KpN1KHF7q=;HWDrr+@${bG*Ax zL_iS6wQ2PVAc*wKu<;hl;ERO?L3Tn%2d(_V;OieZT%3r~hX~Gx3};1{WRe+2axuhg8NmKU@DrL>0 z`?@j;71ah1-Gxo4%vc@n=<1S#z;R%I#IP6!o8^?0lt`X{6ATl^;O-e8AFmeg!!WLV z*o0q~vl4#1LQt%*WfVvBo%KuWrCxcR?Q1275Tkzi!i}80w+E*+uqx2FdH{if+j6o9raOEpaSagGM>q%k1z%c+& zU1>0kZp)dmf#nx4(`*QsExUi<{IY~97sSj|vmy&7fsf;!mdYp)Szhk(pq`3?@y!BD zX;eHNy$G*%JkoHU2_dfT>CrLVi&a>}(e(Te9+)A5O=xJvaXcDIOB!*saKMUbXlv&H zLjpVv^N0)pSi-GT(Ih8a+W0OHYk$qj`MfI$6slfi7ls(4yo zdj)0lY8#stg(3s+rc4B|i}edUVE6?R#>0(|HClk%o@mu@=H&PG?Gmy~2b4q$p&|k} zd+EPELj1w7_!Y*>l=7olj#j)%K#T`OLqJCC@+kLoWqVTgZQ@YYkj`^YrPK>hFIq1f zU?7X7Ftf!ws1aHP8$Sqp_V%4@mW!fOWW#}B&HSfv;o^Sz^&K2dFcCGhE;DFISPaJs zXkRhUC@Qo?P&3#_1mJ@S_5#^ZML;hiW3lefcux0pXfuwjPCE? z;zNvCLQ;}d5^+fu1Phh`JCCN#2K@k{^iQo{VWzqn1nChOaCAI*$qi7oAS`tl(Olne zDbc+HQ{iR%H4GaP`0cZ%yiQ&rgQWrC>mAodfQL7f9ww3p9F(1DI+UIto;pct8*{fG4A&*EZUNr^IeOZQ@7FoG0JcuIsgS9k5k4u}o$t7w)h zfRh6p>72L{St^w5F?)S>4DM*4At$ln#aLZihpATU8>xKwvI#T^TRkp&Ec1%y;AOn@_= z#F8lF1J?|!svNN4$((OIX#oNvL#|$pvaaLBR9)i|e0$cuz+_(#d!y z(Wy3gd4hzb53^=c5!VlDHRU3Jro{s|*t)%bxy~Oy)So_0LEK7sia0N8pAo~^HWn=3; z<%d8DK(P@SV0;iChwKFWb|hGV6RZ}oLqKB60lh}&tAlyz1wW#|SkLxMdIEd2@yLk3<)rr?);;R?Ho%DEBQM0#yV6DURWclr6N z1GOMpvOtF;3f7v=`sEVDAR`h>P|yG{FXaXy(q9EmGOLOeR~(P+oFoVeGkJW8`bA9l zkN<~kK*WuPQVy}b452!zlMRk<1p5U9Pf)5%N)>>e1}Eq(#9@aBA5k%;+*a8T%?Nyp ziX_*?ILlfKSb-Xk#n7~1Fiau2p@#}6qTo%2m~h2?G3kil+Sdo)2;9)tVxXMGnt6jm zgGiSErUd_^507sm1$GU%{vXFPva?qqS|55#>;R;dXv$#w% z*j(J)h)P!uB@LkQkjfqag;x$fNNXo0kD2n?E=02q5PYn_jN^FDXK#SNlnOZ) zM&KUD(r0F5Aod=nLnVW5p8;N6X}$j~XK9Lg4N#Y;3T@)Pa2nZyO+T8e*kH`KX$_ z_;;{GFemwdN<0MkA_f_V(>{-lK}@#a%HrY^QdLvKA|y2N$p>zH0^FzU1nc@Iz(Udj z36Y`W(}#Ew&#z#__xq&jmcc)chZ6_J#O8p&2}6==ndvZxlvf(3m}_lP^J3BT)bOpW zcFyj@&noFs($Xvf0y?-uRX$E>Avzn3L~zy^;6#{CvjA=5>{!E|hZ12Osp8^sKD%5Kk}mz=|CJ z*~zhst#XO(;%@-ZFzJ_jrs?G(I!yZcrajWZ49FhuuB?DeyHr>?)ei)xqBD#X9hO=f z@$XVZ$~pSj`EF=VX5g>K3%gew*&7%n(dPoWf3XZaLnyX<+@Bn44f81Vw)L{oMmCF_ z{z5p^w%>?Bz;x@@L#R{!ke!_kFzGBf!4aJgj7>l6%bSqvnEg|u36~I^Y<;8wi%9uC zeTt7IfYQ8>QwrQCYj&7C5=oDVg_RJg)SrA|KLh3rTwc9hM|6~hKe8F%%#}P^Fop2b zl$HaWKFFpNA}XUh1UiAQ#>*|U0nyHM^QJfmbc2B5U4TUhOt-ra9%w@{$O4?>Q9202 zfAJD2H}VdHTMS$3qi7}g=@Eml;E7%(@R%EkWROB!^MC5s#~x{XfqRO`=%z^^DGk1) zeu%GU45X=8xa=c)5Mvo+;Fx0TI{?SIb?l#@^fU|%lqPViiy-l}1jp_oms~LqB9ekg zGXRR_4^tp|X(0z8GE1q&%#DH%q|w$UR> z?=bkL^fF2!M;Q?K6~hKYOzt3r+Jk_ym+1$+pa4|LJord?h7I+=5iEjqM)}K8)r&*# z3$2%-K+QznL>Y>w_LkDaR*;p=XPpEaBpV3iWR|)PH(Y@cOTP@X>R|%W(;G5w=SP2D z9sK#H8Hh>6;kIz*xe=DI-nb#we?f$oLkZrJCgjChzoxxagShDZr9r?w7DKeK0;e+M zFH|6#1a3+OEQ-E4SEbdF;t@c~A)|6SAwCD%m)kai|slg2XU%U~hW#zBN?;|cNf<%msjfHaxgl50*h-+EhkXa1N zMSD#PLPhwN@Z97S7axrgKA3Vv(0{#qFx@QI{=83iKwluWertRPl}1r z%yPv*M`w-6&dTZ>8L5;v{_nljwQAqoF$capcsgpZ(!rDimz^Cj)y$w-!5*=BKYjoq zB!djFY027lx3)ge|1a&GcU0A9*6%SI9a~H^*h>-*7O@x_`Z_S!y!qeO}sE?|yM}gB)`K46AOZOjLnT^-!|+F_)y8g3W9%Ue&%1GWGMx@Z zd(^gVWiL0kQGfe!m$E^lRMgJ|x_}bX`UZm5CV$OaKXh_aZ|B(`B?52)ULT0c4{Z|^+-DLX{Guy76 zoV&q$76J@at^S77ty_CNU-SG$)495XOgd2!m;BTp2)$X%tMo~s8#lBe79H1{wb`D-6{YKcuzodzEsWmmqHr_l+pQ_=l6GDAJ!g9{-pXJlRDUpN3tF%tT?dmaVJ|inD z24;qqMBq~fIt$(`@TA~Jv8e_uis5RKC34qa(+);HDN?>C`>b0xdDW^ka2#m@VHeC) zA`JjEapBCm>**^8w@K#1nm$b)P9@izUpT0}-3t;-wa3zaMaH&OYV^j1-OAzP{*qrw zm=?J$>uDU zB_=@iMiZgeRRL!!8E2}Rdt@5d)tl;!>k6XhNV@uH?+y!#k!uL+Tm!>=zIa0U}RrX3XKlQpl3* zZSNgQXUeQnb>zsAjA!713MCUr50syv=ubNS5tOEeZLZX^Wp10!^<~vskx1Yh4L3B(_0L$*aCqV#@ORd`pYlc+9%JqQ+R*Y6Nc&aoV|GA z!Y%Rf@$jI#AkdRQj#W7vDXYB9yXo~(NBftTFOPOBmCug3+&Gf+vqe!NG_0J5E@xS& zs5+AS2aQ)LoSdA15yx%TfBpkQYYXojZ`g~gAC~iAD~OZv<+tbEReO#fH&kAJ$JMqR zd{;KViX`{Q^&sq@iNChLw*ct&%kO^JX}E{k+Ub$A&IHIheE>R2a4JqMjl06`6yBN9 zq&l>z62An&6pTR1WE2oi`PiYDK*#66f(-L2j_=Ku~TJOC2)$}35 zhP8>=ym4cW2-iq~CFCIsI#`8LPz1IJ-H+>wZ_kL}%H(bXKVM&8GQ9o0w$i-afPHpH4p|)w%dh>bKrG6h#6=uGpvO@@D^{-5>DSMM z+$bHrRHPuy&0%whOTGKQWpA(`h7v%8(taln88rV!4|=-ZWTlA{ZBIATFq~&{_t>)y zw+iAna@VfR;q{jd6Pg-*dTYYqTm8`4k&tb=T2zfW7VcjTlX?ygg1CgR}O6T={Up`5Yh~R$LX5!)Q<*1 z8_!HSIy$K(XU-H~IG{Oq`m^p1Jw_Zm-fc$x+#l*2W2O!JiJO7iplK^yoFfGTJZ;*jaOGHl0JmIT2YTfpluoAQ}Eat@+Nl zkHHbVug9HOR>dS}doqF`LY$tkI`<{Ba4p&Kx#7lb><%AaP|m9>H-u%>FroJTx{K!? zlq;hIh~nT2uUv9^vZa}s-=>a3!orOMEtXjG3XgpC>+i!9OLN_Q$MUeVof%vHW$=D6KHP#?P@j5zNqKVmVxu|KjHud zwuGF%eqvL!G?rv0TKylrqivGo1Y~`@8bB2#)7NA6f*&O4oZ>la_>VJ`&_HWECFPCK z7nH^BfBJ=+h%}^50HpKP{beedy7txm7D96U=}IbLZiICfutosl+?O0}lmplEprph) z&So0lSJqAxTZURKP3SpuI9E~FujU=UG{J>?i7kmvra!2xlH%wx?bpHC=i0*|coOKg zQ0)N%=)?U@ebJ7Oita5BN+UZ;7v2809*id+UYRqcs_ejp3iu0~xlV1@l13^rJ1i&y zPS|(Wk6&K;{no}bluI>l)iZy<|C-D5tppE@q$p6;Hrvtc9TDzX!$l`X5!%NRJ(ZsG z3ELi(xt4|H<@+QFfCF6QF)uhL(wM7yb<3vA+XG_F-_LIkb-U5B z8;iadJXu1c2volM>Kna$26RqKu3H_6^-K7KKt<(~+YXm^HgbMhbZL~Jm+lrzHKlI` zThQaMXP!Aw9sluPxeZ9XRH7sr8ID2eN^T%aO%kve&Jd-J)`l9!4V-hcu9Csqv$}l3 zt!tQf5{S;M5zk$d9jz4LSb!a(QVpaRP2GX(_u^*44nv>M3eQ?mo|Ip)*{ga~H(w>o zVKSrY1x=;slgvPKRdvCuw(GKG%Qn^lbm|wa5bbs*5z-aR9?pIVL8D z@*oN-+se%|T&e=|%pkz7jKx5DYC`4avYkjy516)+ddbo>SG`Z%B|Rtz53Y~|dDg~B zoNw1d$tgNkn(-{Ns|SKR%Y(I@5Z=;6?t891H=rq_2i6p%`RPd>mHy=hr^5$f>6Ws z1p8t=&>y>S4#2uWfK47Kb--o2lefs@a}dso_Dd)UQZ?94pz~3wyBx|^Lw(lfK9JoX z8mqlcU5p5TMq-O$+ljKp;BB>ActnKt++!RW;>o?phv#xOrI3eE1Q%6W#@eY3Lax%f zQPTMZ2JWT7m_u4DEa^hA3TY2u?I0&i`&jTjWn(7zNC-Z?`-GFBkmRY4cC1uCgYC<1 zT|$BxL&bpHkc248bnX@6Yn#xP6k|%&^tLYfaoeR}62b%^J@LG+)lYy~nhY!CQdgArUzqY{dy~ntF`i#^<04^;EU4!t{h#9qkrOk7Go!Xv?V*OB z>bj2Cu{Pn6`n7~4s1LzYuY{WiRzkBzuL9MQ*xCt;*!FD_| zQ`c@$tYCeBW)^giiO<0>q*8K@D(^GN zP)PnM5#uKloR>bn5d}4LIwNBY4`~XDET&Q9!1FKGCm(oMg7;34V+cg5hs;V!X~Ool zz$`)isp$6Er(1KR^iYqANOE7!u2GF7dx}Ppx?oC}!}8^a4XU4>G|lp45mj8BvDyF0 zEzP$~XD0{5942HM(2tSzn60!KY|^|2w0N4bS@J8^4%h zW<2Zq>NYTP7XZ&}|3$SP>x@77%h)>T$cHVs89fJ}3PmvpvRZ;|vOTfB`fMh38tCaF*63E>hj(3`fZ9eKR3&t22RBwP(wgEv5y%Y!4?~eZ+Ay z&YUMAvW9KjPIun)o@_3Q*qv{`#^EPU5X%|WV))F&`R{}hR)blS?yP~jn$SPE;{f>n zaR9lN5=pu7fXFA;lVT+N()ww$KfuTHZ5E`56HJN_ujUQ7pg-lqn1&kF<_LrBS3Mik z9Ca+BJXi|t7xSLMiAunBFPN7cMeNZH8h<@u`nMT}IQS4lJmC+%EA3uKFwAHu(IFLV) zanIJ5r?bm08~4@JIZ(0e2gQUP`cnx5y(~Csxj;9*d2UlR2pwt*B@qtLN8u!Hpd9GZ zGr1!^`^=D1&wTFOSUHIWiD$v=T^ioH&bQK12z3(er5@2z8CYIUU%3o@xT(k(`E`0! z_l%chx_nm5nSo2+*4Ch}w|d{qTA8*}Z3-5S8PJ1ztD2YVL2)bbAn#^aMfm`t? zrUTj}0Mx}9XB2UJc-n=JeRuARmot!A@s{&eQUt?4`j?(HG@GPppk9bc#vUH2j}PDf zD17f;9Wr0vqpuv-WSu^JTBYD{MJv}8XwMUnRPry9JJ6d3373sGW9r;D$l6NisC{E% zVuS;id&*y)l+exe2bTEZXjs4IU}>D->GAZZMwFk`O*R8T#}J8(RP;o%_i25g5j}+S zP=yR6bok=wiHGeJnGmQrTBkE-%Sp4~%HwVVIl0_8oG4OmG3k`tK6dO7aprMz;mN$gnmW!_hTR|vPx^iv>NSL#eUOEMCv{&q|I2+2me>mTSebLJc%lt0At zkl`&fLOot{ck4azGQ827exgA-|Lsu{wO5$FIxdDn>)MU^pizh$%=r@F&?TPK&G}1i zCTud&3Z*;Si@+k%)*YdFOkH2FH`+0RyZ>FEIP;^YTFw+*plhm%N4>}O?AcT6{45XA z`jK!>BP!>#Opl^n-}gGd?6v<*`onxtJD{?wq=p6|r%2n%ij1J+7U`G6s%IJIt)?QH zFGo+4$4$qxnA*H~QyecOGz(S47o>$vn>OVy^iNpJg-SG~Esp9XwZ1w@B%PEhRLKLFXXp1U(nR#d`kQoN*4~>D@PnJu4E6#a*6A=H=WaVEnsq2rMR$C^t*Q5J^Cvb z#GeA3fcEx5PG)wmr-}KO->HSo-qwBoZ|FL=+hGdRe}-9x3fSyV{#)GexR;b0nFS_HXm%2 zX0Qx8P}XV%%P!=MbUP4~^tro!M;yF|bI{Zs^8&x6-9o3gwJa|MP?HZG?;39k7XfiS|3(kirm!-waL{uMo; zq8<+q-fQyF=_eaf3`s%hcnqxgMZFM0pI#@q3hxJImp0J}{`F!yu4yoO=QtQ{%+|S9$WOpzZN@uMPCaxT}4#a6U`5Z9s5m0?S2GtZ*L7j zY_zI4qHPLN6SmR9-ydga$*1;*KfC`$;~qJE)|jVzo0`4SOar$qLDA+5fV}$E-A3So zr3fZm&=iU=YlG+dxR%|v3KH>Q-7{SZ)+;C}6qztOr+@z01ObQMk|Gn_m+q|#mH|kM z0JYPy8UAyN*1*%L1k%2ycH?vw}Fh~UYM?1g1(@_8IC0nwYJ!HoX4 zN%zn`j{wrWYG3ReN*Mx_8;MrWs?kRsFN*x15HD^4Sup$h`(NYTN=}s5OvLkVuLEH{ z_~sJdTdQ77V1J0bSquc?%cQa>rgPA$`m}XxRKSz*_T@kzpdH)8I-|}P)dR}#*qmTs zy>nAE)o;jsnty|w!YYmiqx-NF?3ADI&i-MMVtw@8&4y=qBA|idLN%$HgN@dEvbNsqj&S*>rJ9v zHS6#ui%Et`Q`9vU`>ex7QwcI1ph!tgwRYbmg*h+~x>^fxQ*Hf;lv`@g@33K|l+OXa zSKgkVRopLeZ3^PxDqL z9R|(42!9kyQ=*N22rBXt3Jswe=5YOa+fKX4IhDV<4_hk#WB1`9QLNCoWvRof?zXsF z4^G2~RS1cJEcOZ}ZPSXO@|WEHSSUbYh9s4 zg|y$wK~3sM8CZhctBJPUxLQWk8!Gj*)EjIgYn7J14;z=LBmANWlN7~r4EGhi6Ue9B z#_T;5{Ttcl0Xv|)43VUrD_1q~*N-te_RTN2(Td=Cds=q!%|6w9QJ`|-#6wp0*Uv-8 zP4kjqU^q`o7_2B?_-4;Mmt?k1lvygY1ikWh0#&HYK$z6F#H1+`*2z}V-hHC6*hj1u zwKLxf8l~EUzs)MPudrotX5!r(mg`sd#5*EaG4Bn@qB=;95ThrJY@bU`tk_hXSwU5N z96vc0`TF(z!HNJi>`I-D#W^p6cu*KH_0fr`9I-21J7u5PAI=pb+DmkRTy##U_kSBj$}Z>`8-j5ZVN3`_W&q~- z%vEpS)IUlISoKmH$!#Ex&>U9eMo&Q0zeq16u4KoWFS>R;H?PkmTZ&j`F${=I5Zd$A zXbQe=`)8jWxbo#(5h-IV1e}-?o{1_s2Mh8ZUicI$X)yGyECfN+BAFZjoNl(x;GMFD z&lasSEkjS*Hy%gHrQP2{bm=64|4$pC?b-Ae0j>f__G?M?un*#>1zO0mrL$Q9?&JX5 zbdg>ivL9EJ{WWJ{%Fpc>UQj)RbB0yYI=EdU8WGk|@`d)qCPaCvmFaQcXRx?wIE-0{ zG5~iIN=F(?EkPDS2FH)y`S+p*?V2p$QhRz}q+wyA-EJ<2EnIKhv#l_Ez*ipkNMZE-$_uqwT+LruL3Hg_3eFIsQ~R&xpx zY1t;}Kl@IiIUR>9Aox%e*Ue6vuu?T~fBIWqdB2qN&jB5mSiM(MPL zqyvC3s#x@Q8Vk+Uc-~A7n3fvu(pH4ZQaduWO3?!Eps`Ajyu6jm zmyc87UbT!uTN3&(`sqia8`w|~r4`g7qF0k%{Dq5-W4F>j#9F81nK zQt2rLV^M=m_^bS_7w8801qWLPMM>4SHa%-RPgkYr^#0fn@8B5I#*tELzo{vVia+lp z{h(C{zb*FuJ!~i&?)nKF);a^)VKkwEMrmv3M1lTAroaJ6rKBf?lu6gz@3uNqAfcJK zNTy!GF|xwj*#=Th!*2bHg`9zu`vfhZP|C>M=;PxrdRhlxC%)f2=0xKjhm3iww%dni zj2DCVrcHT(PnAGcL1!pH;z-S-MMcXQ>7*4Qr7$6iCk-yD2Tod^j0yq)F}7vc zg})I7nKmIs5BJthWkKu5|qCKaD7M>llLE;Hm zn)YvFKSU)X<~^y2SX2Tf2~H*DfcZ{jU}g^8cK2vGGO3jihRi29l>KVMh}U1ee*NK~ zb{!%&iEH-G)`V#LwdU2gx|?KXG)1O$Cs;QnHTy|cC;r)N)T!~_?w<^(_i|rq=6EsC z$j{&T&ZqXSr?>EqVfypaI+pW(!F%F-=lDPKf`4P&y|4eAZC!uf)r67sAV*z}pg}>x zcFFp8>fWS--3!a7eeBbEAT%jV=Q~{#!p)la+FogpBYSJsnbt7b)z-iB^oA3rL^;G@ zmWsIg2$+qJ|H-jH$Wah*M#1E_nxy|PjnvTPPeWhtoY2h|V%&fG_IZi(aOr}&HWl4x z-}DO8SrNbS{Go?p%s#++b+P&a^ASXKJ%0T7@HYJaCcbs5hx0DoZMbGD7>1<0goJH4SwZL{oS@$Jrfz|fC_-s z1VAW$TD4MXkThdp^wjRI@BIlkquN7=*4Ecg%c+9EXjy#(Ifg0@}0Vfry7w4|#NIO~eu9w7g3DH)9l=z~;i7Fquq$rf_X6|tmMS=J!q$d{9 znL1O7I|jJO8W1p_9yf02M3hTL6Z)jTI6q9jwCZKKjf<|@P32v$zIIhxQgT2_p1cu5 z_i0I%PmvGjP>|>}G*vxd?c*6YIwuu8{wd{Uh@x2&4V0$vFk-g^o3j`?#|CMSq8M^D zKIcnb@0hq*>WjDI;$<|DUE1ikyQ^$Iqi^*;JsVI%R0$(MbK?z4x7P^Q){p!itmSB4aqj&d$9S2?t5p9rUOP zb<-<HVa3GlK_4MT zkc(UU&(KaP`mM*U3LBs)W!0`{z#9T}2#yNf-MV1O--{_O-r&X0jykRGN>c^<#haAz zQ6?ZN_LNdF1)@F~&;Hz@C>H-I;TP!l^Wo2b?kejDGen`~{83|$>=bVl*OWMctL=<& znHZDJv@aS`A&@a6h!7N?RVf>HR&d`TL$2KmWDn);AV!<{fJMnW_6xH8v|x*S%a8i^ZLrBEm|CNd_zxcUiF$l@0oDDorwyJH9Gm0hbS9_i~Ojw zzSNgguf>N7=F5kZI5MRI82KVB!*`3n9pb4 zg%5XF1!908GSIQ|9Yk?wu}=sNQGXpvqv+*o44sVauYjl8_tgwZ=i%Cp5q}Xiw0rgx zo0SE|E}el{yrqLwNTgt$UcF+~e|UWtg+8lz4m40<$&9vZS+ppg7^3HKZ(Vn_Pz?O& zunci|F<-pYuysmSl565L+0GQf%xiuZGLL}m>2#pnD?K*2sUK{_t;o;`XH|@`y_*c_=zTYkLCeKWnG9_hrTMciQI36#~0{?LpwmN8< ztI$OY!(J>pKg6mm@~+T+t`M~04Q;}elP2X;?8N}m6hVrmI2M42ILjduG#A7%krgq| zOwzWXtD{Gs()4PI=+Gq)2;?`8tyUij%Q%GGqc{Nv}i2=o(~4un=e= zjD=bi&6&(l5p5#{SuFBw%Q>qlD;?%8Zh9-}9>Z%WLH&!zulcRRPg~(eK^EwI3%cQB z5J+!p8GU4BwH4aB_aPj{JzidWXDmjaCOmvkmI_!tgXLxgJskf#mT2O6wXnBnHV^L) zvw8A9dboJ=c8GkCYGw4*@?|_#aSJkbBtyCchb|m54C@45` zV}=)Cxaev@baoT3QobtHNqt&q^vI3wF8EvZXtn15;iitfhIq=1=>_llS#$HVIsg4e z)p&D4EDhf6Ma(PMk)oI^ICv+Z3`F7->_qd^D=plg+`4Jo`S%f)Ig9S&o9?keWVh_@{k8h4bG`63ZNe%uh_aB|pY)_BY=$cgM z;*+MoWY&;ot5#EDJ#o4GcSC}zzvYp-Z0;n|N}t;^h1;=qaZ+}Qe6uE za$Tw|u6Fc>k$2To{kS}hkS|0|^1uJ|?iuVaZIJWt`vBYf2f@6$`9|gZ&pyuiuKIxI z{22P&QO}}6dgDT4FNt5QvMpPNp;VM%9@YnD`!E_c2N-Cv&kz~zRtRd^cd~_*6{&t~ zPe53_Fbsm+f>=4+MLdJluiPTu?+Vy7jMsH@_PmO#+BGk zi8CrC({}$%Wl%xp-@&^!6DKZx^EE96KhrdqzHY*l$Q@Wxw3G|dD+2^BC31s+!7&~_@KuPxg7lC}XL4|Hp;5>=k-+g$eam#~C9QZdd;1W#u{feis8ZMc z<^-Lm`&N?i%3tQgK2m`f1z&jgXOqgF*CddT$EnpqQbNNa+1`iCiV@8)JhIect6nW+ zC{MspFVeO&5Hat*)uh$6!jD99|Ho|Q?|MTde{^YLq>92*VFtotfk))rqZYrG-f@)P z8r7P~4W$A<-e;NNUx1Sl&tx=1!Tci=FcOJlT!zGm;9k& zb0=>aAc!4PdQV@6|2*fo6FX`J?@IuGX!?sXqCqP(wm0Rj;|kiAa}T8)=u&qHC;B-2 zq|;=_`#Hhr+>7^Zr17t-`6|tfeLqpE4^2GNKy0}RB}8)~eA(mja=WM%9Xob}W!6#R zBt7=<1$SxoY28hsI6MhzPpsc`Yk=SimlcKh$pd1t&zT~$k(MUr8xHUGqchwAV7 z>G$sezrgU~iLAMV#?l-e!}{E;%uFK{J}w=-Nq=oJm4=*PCgY{%>XO0?$6R}!cjt__ zE36MM+?Bbx%YqlLjfZs^pr@Cc=Guu%oy3CNap1s#9n0@ER$c~nnQ!36*sa)q5*KeW z>`%s=r0A1g#e-|$>5-U7(X&?ujGXF-bxQ2h%}xqW+RMr5kz*xG88gNSis$KlHZ$o2 z3?~O|OGsy1gh&gu4jtC}62~gpK_>OWB7WMgT@*ChaFnfD=NI3TH@a}hksI~2l;X&d zv3QtGm;^WpG~W^p4rA1`(ak`8e(JiDak_qfevVEY>1NN`%3 zQr}30f2WeQEs`>s!beplMnm*e;*_I*?0Mx&B9N5*?#Mx$*}Ot-vt7mADjGM*jf@2} z`pw-c1cJ+BX>TIA+-1(0qaSZmB7 zpY`ia&CJZ!J!d|JL?_MQ+sN5?=nwOsu}-5mxO%!iMVjHqFa_?^;mT{OC(nceb^7dK z=YB-*Y5*(LyJT}+jZ%?s^X=Ob>znOY)p3@2vt|9hsT+*jQ2jbgwqLDO8$o+?s$13H z)aBxLYy3|xT$`W%8y7B!PnnIoPtgBn(*L}M_@A@u*%{jmJgt#rMlm0)H-paU$z(pA zK=eWPe|vSwQ@}o0Qt7uYC@32XU)-;{|3VO{+n8gngvf@FHY~4GeCXmrY(k-GVoYm*8 z%qf#egb<1snbT4MFnz`GtTD-9F}^>eX=kU;2LHbJF;LyyId?2u)2m(*Re%`%|X% zF8rMtXz*vB9d}%Tj{~*lnZwrH#M_kXKYl1I^Xs3#i79W(M2pmn=V4u&ilbJ%GvVDr z`PQ=M&4iy*YP*LddAKVRUt#_qBA{v?*sUzme34Bpgvf>N0Vmy6a#W(09!0m&e(WEJzoTc4>Le!Wny z<5WH@y48{KhMMy}zmT1sXeG)4^|*)A!(U2@p+Zbc(-+PdC}DX@Rig$C=AitMAT1+* zo<4oLIG(i287b*@)}onwQsl(4#f{7^O|1{&l1!O!3&n@-<{)A+b2UdnrVATD?_qVf zQhYg84%6s8Os?3Lc5SNI1gCs_hZI>P5Sf)mQI;#yq{0k-op~Mcj4K$(q(7+*WELdE zi%!}hQKMjxF)eoL!xOoBJ!$CvX;>OG4fcOl=WUvh@`&$wnboJ&ukedPDZ6P1^uZs2 z7o9bb>d=f1_uAk6YrXgDU(kE=r&bh~8K(#6>MoJVs9=jtnl&?{aM5`a$t7r>P%m7n zCd;Ra;{OyuG5zA_6@MkwP6mU2F@h9s+d}`Tw|HwfKad(#hC_;m5A>ot7Ec*`Z#hBU z!qp~qd-fC&=5LUZTd$s5j`?X4I5X2Iub39t?eG={SH$?Bu8X9g&NK3i{?nNd%;zd~hK*X1ee_iA^)qVw?`@-d#*u8Vj zsJD|@n{M~xp9Jo@{*QOFy9;JkXY5?HtGfDwkd4jFzGgCk$HMVe=6z3UuYS_1$NhV2 zqU{?loDgJt^T99oH}TKWpF4Ito(k3X#=>q!w4NuNQWfegHot;&GV*k(Qn=B;j_Va*hGIso)V_M?vcpAQCKg5h5}tA@0-^7PwW5 zXBp!D!0R$tOLWzSkJ(p<_(9jK+ z)=C_}G;l^0K&gU?OHqzyN2ZwpixDTy@ynqNpK~sY1>R%uJuP+L#V~dR0@8<*Lu5oa z(RnVMx7+6bo`eV@SeE)9^eI}Zxwp`p(yuc$QSMfv8v;9Z{=nOy(|4P(pp!w zIHVaIBJGQue<3qbUJaqURG<5y+4;WF|JA6OrMnNEGKQ0%HV;{YXG~mwUsB)ov=s+< zd|i?9wEW3Wfm2n@zN`|PjgnMp{f&Rz`pq*O79OZdHm2M301oOSMX_}h6M z-o_wliiBMb&#T|#SjvIfW|b{9%yO=|shYo?$dTIU3S;IDs!g)Ea!+gB*2aeeh%>`x zpWGUy`%Gnb^qW9%T_wF$velEjcRyE(A9F(H{)g3;j=Dj;YBN86*_){^&=yETG&E2B z(ywgHymf&J$B5wdJCnU@?!9RfaFv7^oN3W$c9_Th%8^kTb2mJh9$NC$)AGpE7p)T< zR=qvZlCDw>jaZdkpxe$ik zyQ$BK7(K=|ZTVzs?TfUUxq$ZX1HaNbK%i}v!Qalv+>oMdlWF&JW0@U=*TRMuDg?B| z7AC?FsIuwvg{mh8C5nPnOkk*xWJm+96q(J&P>NqMV5VZ0T35>iFssq(*V0sAgY1q6 z(Ix*YINYXbL|nw8`6yZZJjd=avB{lv}C7fQscB2bLUb zw7T}S(>DkwObXvcAP!{UtNEGMrEIBMCdWUQ)&K7@WUZqdtsFF7l#UjiH8mMLwTF?o zX4$EHXmrziT7GThgUd!k$01Cto2r-F`k85EA&@8qqrLvKQ>|o-4X@!vv^{QE!=%w^ z-(8jpA#|e%yFfWCE7cQmdh>B>(VsPiMeV^C3F!^7EW9M+DS9K9RI9|%jU(&0^aQ%- z3Zyp!KiudcOR>3J6t^6}OUrqEy_~AB7>EU*b=QYKb?2}gqD^?*6RJUB_yiea2CmTJ zUT+F+3q2T9J4B;Ujz$nwsu0dtv%rz|plX}LI*|dxDR$H=OwiefuoCIY1Tk;Qu>^oZ z)$3=BfQWwhB!jbBWH@=147om-UcK@a+8qQ)ekh^QIPb=av9*9WC(#_a-!8V*UNT{6 zO#D%8_vYc-o%>BYu7S#y@l7IJF@eVtp%L~HXQqjB`pvj?i5N>O8Ov9cXR{!eVYr{c zqO&N@-W#AQL0M}xShd+9gCN(dH3$!PeQ`JLf$v8 z%BjBJL+oOMY5%5;)0L@c8{6xhepPV0cJ{$}1$kR$GGC?d+UadGk;?g?vF)I023Xg1 z;J}>2rEy1^u|7tr`mcEYpyiD2T3YQ;t_*s+%!}5cJ$=}L&$AaUUaWic)x8al3rb9Q#mJdE|X`j5DPGI=8> zf9(cHU8~qlQp?1g)5WcTujqg@YXD;u&X#Idd9d)#l~YP>1h780W#&MaoSzTh-!yC^ z4Z?1;_Ix#6=2)7Flp3B#b3hoLEoIpZ7UbADb0QaUkXJhnA?T08J7a%sX(JP3B#(uo z(nhD5g)sfCSC6Xcy|NF}0sBzkY%N_~Rjhs&3zsb$OozaboaxN%?LY_3YTzAZWJ>V= zJp1YM=eiV})B5yn5}{}()ZHS=LpM7@>F3#VNX3bX6dp4MF1U4L;gTf-1Nzum$I7W1 zx&+!eIs0t#zl7P$t?lK#|4Y25cZ-Xi$LVrrM6RQkbBD~GZ(BWO1U}#0R_Mjgvk#Xa zD%eQf8C+0yE4`q)&0F@_xk94%SgNaL%1q*e4^j{}J5<7Vc{J)Tz+jtF{rt&YHcMOd z4Hu2?*05MLUvyisKQ`m6PYromUKj&a zw9Y^lzM>rdsbf-5h zU$;E!(0!T7&C?VhyJew->550gvQ;u>PDeew}y+Chk0ZXcx*8HZ9t zlD*DfVJeC8-td{WZiFa~8X;y|&P`^Zs|>;<0Qw#~=C!VhXyTwc2#bY}%Bbr0FB4$- zxzs5xU5sx#h}wthz{synQgepg@7_IQT@@lqhi5~K+}NuDJq}c)+tL|Xibl}w!tlM= zYuQum$+Ul3X4`i%~MiXRE^h%NudyCcoyAbo9Tkcr@~#DwzA* zI&s4W40y4=(mRP+=T<5b;L41pmT&BxFNXe8h7hxtd(iU10eFTVJqDENp5eZH5a;i#ZiHP&ySbXoElf0)4JkMi_=VsonpErpbyTkm$)I-fLi$75Ei<}XG zl>o_DgaAy)4lR*Hoz@@Wecgv*d+5FJ5ufzUwO53C>(EG??=})kmH&|St51H?zP&cb ztC$u{t(wqyTHm;e8q-@Z!Akt?CZ_mRzPZ+~8T+^xQJP0tVwTJti6Yp@P%_QHFE4v zjX$0=hKM&)Jp}q98fc}i!|T_UV}f3Co4ZV3EMKugOg%EMX@yP?lTIvRQ_fnN45TSm zBbOsFp7Ef+W10FKAcX{{V?`KfzOJR#eXW_*Yu^oP8BQSW33^*Og~}-B31bd!%xGOR zC=u}|yx8=VDi}I@nHR`AHCws`b`qP33F>8 zhQke&+;s1}xG)*^pfp{zYE^h<#?8DymQ6dB8+Rr}Qn+%%O8qL%tDa>?v!2CPXtATx=JxD%ac zZAQ|B<57J(hdRSQxKWRIajBBR^F-hy{sgTQ}n)%lHjOUs>{+SDgPsPQF;p ze6_{Hy;ewdiV|K<@!9ZUfbbMEHV=>B* zG*^yA?C8T`0%s0$I~ytQ+9O9GvUBU2Uduzj5oOD6I2tOFtgNhmG;KNns@DgtZF|*5 z7E4CIW+%U-HhvvkPUYJcJTu+=(@z32#*DGJ8Zo(9*vJMUsbk-M z$bXHz=y>VqlaU+Zj~8|5H?^6yesGy~>orfBx9Qcn@7F`7Jg!+)QoVdfkW<@N&zt*N z?+ftp@o{*3Wq$IMPsfy0dLVKcF%-KH+$78{jb0NdIej5=gQTq&)>Z}eeBm? zU%7TI=+>=U<0nmegnj2q(vv4o4kRV{lb6i2{c~KBdiCzjNSS*l%bqldey}t2P(s2M z6de5OOe$dV^3K&ey>v~|)6>Im&Ud0jv$L~{$}gR2V{?WxLifH&J9_ltojaeMn?1na zNQ;}jG-s97FAp9lsDq*`Vxr8n+jZ=Cmf>k1wP?YK0WSPoAL1L?bL`lN?c29I z4{Sfx%IatOl4CnM8GbR~r)57IkMMe9{~-Krz%fdibl44th@A1)%r}k^? zF+7tx`q{^XwU&G3#Q5iT=ZsXtPiwLsR6WEx8h-4KHG_%Gj)eQ~G;9t;<~fbNNE8mw z^EtG>I%;`!IDfd-`0t_Vl^^4Oj(t(3A0A&&WYX_z8XYY?w{qw7*l13WRE;YArr>pI z|NcjdZQT(c>c4+SziD7ZvtH;we?gW#$Z+cinfPlf(=Ed zKCeEn8dOnHaWZT~=avyh3i@-ehQKXlZM^%+=MkDM^a1IZfs9*t#Xx;NW1(wj5LW)87tyU78BC z`j~H*Be7+v-xoM0LLy5 z0WR5-%h=fBJp^+fA1e)2_WxVa)Wj$%Dk>=}tJzcL@BVY>wLy`XxOmlY>w|YsP8|L= zJgj!?7_ZkzTlJF@AFkfKd3?AqQp&ljc>RIvJI+T%tv!AE^xKU6EaWGVQkqOJj=Jq{ zkCrIBHrQ7k@vWy^CFkM8+jsA-*tTt3M@L6RZSBhP#lH_Uq;GWY^9?Lkwts!SIx*|P zgDu;(J#6WC{^G^#pR%&DK?U+;LJnq+QS_TKLn_IwZHC;Np!N zys@#dZoh|(%u76$}?let(xToL${lh)UojYqUu4KCP;DOj7 z*ApjJ?c2A{(b3V+yCZ|g*~Nw2jGf)!@Y>9-Yw_{D-@aWw`t+ock8qkZ-iAq;pKnOv zu@Vo#nk(EZ^Gq1GY}r!$?Ahk6TUX5e`SI%I#g%z>jqHp(@&`|r;yumG&i3LlQQ?7! zva+(16TRy5leK&ur9KkdEy@^WWMr~js;#Bw7Z&mYmjbAuxHuMyUr~{#x3^c`XN<>t zynCC^k%7c-_tF9?(={U~YIu0~{;^UyypnZ%e9=0I+k4+<>X47SsdR1yH8C*}R8mr6 zsABxcoQ9HDP|#U8wv4|(eCN&$y%5mxw$7J3s3(3_Kr^J?63P{l4Zh6 z`d>#U`)j2e@dRjs(hg1L{pc(*K4GpQ_4Un-2V?6*bB;Vb>d_Nu!G*tYdbnlP#*G_s zffeKto%nE9x`=^+p`S%RaDH;-R{fidFzV-`R9gdRIIaB)^>Z}i4B2M!!4Q&v{qcxf$f zWwMNeKw(UXPTfP3Jhj`L+FrvgFZ*kw^ISURO6rBi#zoo_)~S^pIo-*N4K3r^t(dyo zYjt*ZHhEdN3GtXSw3LmFO(d@ZdrVBsx3{;|Wi_?yzKRG5tyG<4q*2rQ9 ztBk{&;IuTpU%!5_emXVTSG_Kx+Oho+Z@}!}hRYh8sp?fxc{a7_#*dE+3kz3_ek>3y z)Y-qkH_l8aLA;kmU)6oHb8Qsw+xPD)Gj$T({{A`<=EAUg^+YXSAVslnJ6a`lld5QJ zWMJ5^eHk^sI5#dVE-u~^M&)8{&i`yyu`4X~{JIuf(`al_%o=-(3UA+=s_2`q(YK1y z)zy{s7|`O8cf0j|`q<;+N;{l8)?r1(_@=1XFe-SD28D;O#v3sHJ|`4>wRsgO`WEq# zk&FQ+7f?Y^E8+|~M@B|$2I^uA2FJT!ys8RgIdo5t_x^(i>ihPsyu!V+W@@l8CM`{( zs65=c_TkTUyu4Q;Hb|r&zNaTCCl`89_?iiy2wusSy^%r5$($`MEtU=rmn;Himw){D z(W5`6=!{%ZhlCH?G#-rOhr4U=6mMl^t&)(C;MwJ&VV{(k zQcXvPVf*&&Nm4eUO;5Kiqor9|ezLRBh1tor?%7!$cJ>v4fq_XWDe6W>YzSOP_4ZS!HZ&OkQyGMy1$uQLIW1sVO^M)3FubwzpqO zNMJ|(4aO~sw)pr)N0{vtkd$1veEIUEovs^)+a5BY3bfomM0at;8Z~F=u% z!SpVh8bfmlT~f-eYmHlZI}ED&F|2RjZ^O4#W>tM{MAbvR?N0*Y}He_C+_iY*@eke4$IHHAN(|N5yPMLo1vm27^YY6Al7y{C60V|(m+wL0TVV(W6H*U20pKQ+7tL3Q@az?_O=! z=g-NRnSu;!*V;!){h6Qj1e&O!Bn1TphlYn6rhk0@UXOiMOXY4eE7bb+0-JS3(DG%= zw62x=&BpeOhpypHQsuYA3e7omXRoe>MH-DI?fD>-U47ARUIp)oT1P9Zm*h)cyihuP z=#Ukabmz_%9c}H{j=NaGO_h}wXe?z5I*W_I3wy@f{sW z5-GAy35L3hi;I3QN=kNl`uI2$78dFrIBF(>YP8^h+Wd#+cJT*0y^z7O0 zo@P8L!Qn)4)6GA->d%(5v9n79ew9gZb8`>%_P#vX{^)hNLvguSq*OveLJbcO4+piC zb#?N^i*!FHCM<`BhI9p(ad&TSC=BivqM@N-q_|~IvKHj$TdUo^iW`u@#kD;!mZ<5t zRKDlPkt58VXMKI8DCdR0zdh!P^75>x4m3Z(hG?E$l{;i*IjOsk9=#cEf80#I8AXY% z0!zFWS1V9h;?_&cS3BJk*i7XDeMm7eG1*&MRu~u&E=J0!H9Nk`#jMa7FQX@L=|#(oo$1q) zY;+ZdMn-lU+p*J5zTf*&>9~3T(vo|uyabb3qQod z<~l#)1{1DctpGGie)?3F1;ynDEgc;v<^ApLrb6rL)hmK#XJ-67Qi1PwrT;^|Q?Hm* zB&nP|7gw--QWg@r^zqZDsf#~nX3mt20YuFwegm*BG$^zgePww>fH}`1a3Oy1>sOs$ zjUAUHY*CoR<>V3`;|Vxm?S3gYJR&fgfd_Zw@$uK;;o%%M)fUIm3mb*xiaqo+G?pp& z%}6%Bd6OWlud8d@<3G)d8;j)FpqpWxwg9FlIPu=oTkwqKR)8Q&OAd0&wJ)IB276Dw`3XAzVzQC z_Cx1=ZT-FIX|eIAv59`W0~ZxpR)^>wJ{;%r>B;`S7c9)omDq2y!4CVoc5MjLXsWBb zeC*lju*Y*V{(p9fjGr3^#$lv@9pfC&P4;Ojf2RrBcKo?XdKoAOBh~rL>w!i|&cFvO zwONf0lqtba4h{|lpG$oTE?=fEi_~$Jb$lE2;_PqjbpMklMLTn?=pw?x4$WueGSv&k zXjLgHDVd%={X~N?vR+@=y&ik)Xx->sL%f(DZISI}5ozhz6ON9O_>1h~Cj5tdrpJyQ zL2q9XbRb3UK)TBISpnuSvfC8gzEBp4iHSnLr}fu|Z%r!}XsRt|O7)-nGg*EIPi?3X z4YWasK}GMb`}{nJk*Kinm&TQAdFrTK)Rx98P93>c*kL;y4`xj^7C3*DR;B?Q6Dga= zjrmpj<4+C3I5gvT&KgQjs(>%Cb8(4IKabDcTrX6kJij(^ULhtqx&FzMCuDIqHaFk< zw-)z-O4b=??u*{YNR4#AI2Tg*HFg;)wLyUohgezun>BxR0(u{O^N#fAX!0Iz*>A6l zCN-aU*`fL7{#MU|7oykwV!eLv+PO0^t=g#Dl!;UYygJkp?Q4ngZ|jeD{NK6clPj>u#B%1D)- z{hi(7ZJv&{u9^L4cdY)+n>P(CuIc>!nihyU=jtkP=*!oa_pn4%fS+EPnws;@Rnc+k`LX(dcQ#bV%w#`X_$3_<39~}oUK?SCL_Y%~ zqj;@^L;;D}Ye`9UWIu!G?mk|_L8=;EMcLe#Lf91eqa@{A6Uiw(?ep~w@78tnNodq* zEe@mVQ>!SmVmGT@(S~xGar)>#wv-7#v~An;*yoh!hqh|c%E~v>UO)92Kc4jiJ7+dn zjtXlQ*e)V#*T7ohe&D8}>9D`aaTNFgiBqXc=Mpsc?wv{#L1|~xQN9fKx(-wa5FUq3B&3C_ zk2gMqHZhxRYimnUSOua_7Q$GBC<@3cwq#Ka90kmdKF>rv@2Zn#k=XujE_L%B+(huh zhYt-GcJ}zu9X|ZwJyQ-VO2`evU zsA%W=4012u?oM4pgTXfr*4DcRxRawFNL+z7vD4?qsWzyviwqU-p@B6K)Kxe?dA1bZ2yc54~L^!ZBbIcI`*Ah6VNq{C>XPNd31ofS5u8b~v z{rdHC>i+%vbysu%!YeMVWHPJ*k&yHmGancjxCQ7j@%y)?ni}njwLF(mYWC-BGHyi^ zbw>rvIr?-p;0r_w6>u!1FgiS(mT>1rm;TYwNbIYyeVdI>m&O9Uixs3gjSmb6 z6dV^|ehK-(wWln;bo_lsXJ@1MW7C4I)OXL>q1W6X{hKy#PHzcdo9#1uPujo_=pJ^3 zR*^Kqiw4)OT?+#}?(G$;rtYH!fbh*i?1gbaAGiiAN>ic~@tAs2St4 z9c&;1fB#wotQG@Ep#7ci>M}Qfah44v2sC9MR%G6(&%3+I{JGIz{bIFaYM;?nhv_OP zC@7wppxD{j9X~&ly}r{m3grLQhYyE`=ht#dF9T4n`B>m!Wp5w)=SSzO&Q9*vDLXGc zcrYR)xN2bGfLLS2c{+DA%+XIz835X4ECR3+1h#Ii{Pc;7V9v%gW&iNhR37>ewSbsY zrpzoXG^!_Gdfl24aj&VVVGA!xM<c%I6-uZ zN6@r_gO@E9Tq(dyR`i=$2cqrxE`2!&?yZLpuL$YiR265p0rj3|nbbb@0OWZ& zA;DB}PvwUPy0Dg(Rt4doad8(R^;wOyJzTeXY0jw!8W{&FAKBMcRT}2y0lY+pIr~fa zR8JZC+*??7#r|`0qobqQwo5?BCNIv)UbuMi-E&`0^I|s|tTy#Kd#{N42mUYgF?euf z8<=>agxC`$pQlN41`_DCVH%zKA0K5z^DQ;?GBfZA322MDl-tD=S&T8zCUW#HTd`vE z=FPNN(Md|@`kK^lP@SJ z5L=LMoEoTS3Hf9CQFN-i-@e>^edE)v?b`~# zd?~QN(as_!^wYsK#Ut3_^S?gm&p_jvosNQNQ~BzZ6i6VCvL6T8dk$SKkB)Hz^~WY9 zzbo}|O%5Bob&G$|mFHgiV~51;$DqhXtYq5AkK4yerGk!?deaC8Y&NUU;F#_P)Qtbz zO@s@yqb){bLqnl#Vn>8ei`$vzTG9aThZ;RN(hEd+=GV9N(DBF@6QaI*_il>c>(rIV zNVgxKWW`PMm+jfJ=gi{#uJ`ZXzkTNAq?@M9iw#wC_dv>w(4zaV-@j?7`uchgo4(=V zLbl(1)eJm}XRaD{6mcn!|Bh-V?>WTS(%R|{mFM*L_sdbFn2X%5`P8snQqWUTQOWI- zU8S=iGJDyZmzTFGO@$9tNz>L=pzW~*5Y}0KF);=ro(8XCQT|@6QzJC)B`hi$>pPm} zVQ#@ZCVwL}m36nCro)u5HFm=49|ejk;iBW`nqR)8!8cuwj;;ky%Z%0O@+F+4%32Dg zRN&AfdKMOzoMWZzSk1+FBa44WB%V06uO(^^%69IVN$apb-rnBOD|Ujk5EWP4B$qmV z{5T=imX^1wepu{0)m4FKnh{XkSt5Gz`gN{AvyT8TG^|2DnD~?#Ri_dZzPvi8kH&Yq z{Ydw=H+(ueI&r=^)8jplGq!Alc)v4M*`Eu$pzihS>Q|STq^v5JRaI3@6gm2yIiuxr z57hvAULWXZ-J3Uij~?aHyQvrk!r2RWNd;k>?-v*b$)l*|n@VxqOg`Ka?S-4u@7#Hp zZ>LjLQ$rePlCuBq{QUecU%wIsboI0u8~W9|0tbDny6Ei@Hhq2lmaM}ovDK=v{P>x} z2D;OAA%<-s_C=X!KtRCT)>hK;f%p!O{lNRd%{?CI9Yz)US>Gvg!HJKPQ>38m{(XA= zn~EaFX>8RGoemeBI>y1txxe*ZMn>J0%$!c2Vl(yE|Kyv|Gwgf;mawz*Mw=6lSdpg}-CyL#isv0l^1 z`7?k2HYm2mj(8afZvQwmI?Bk+owC}c6E88$|N|D~ws&!0E& z%20Tnx9l1dE{BEfLF>r1sa@NYEVB~giaMa-?$h6bfU=D4k7rjtyeB-8YIv78G}d_> z*v)%WH~sX(c!JE ztuKJ@Z4wr~Z_o!aClb^r!OC_crn>j|C0VC7^d+6v#?m$SM5A=p+uay%uy=A|*tv7( zz|fF&Yqrr+y|Ri!=8NfKQ@z?~zI|uT$aBd#3%ffRqFIBzOIoTTT4mAXkqcmX1Q_7A z@89>oo;nFH37r5REb{c0!a+YL8$8j-D_3R(PWVD?;erCCar*R*v%iN!P=qH(A{U?{ z&&+;JL%Th{mUp+|ciJAocsZw}SDzoviWIzHm2!N`|K!wXu7_qt3BEc)n}Ypb6LWBO zSog+mHav_y$B%FP^yI`v+{R`>LCy379v;$Ag+lNRy#w`ee!lRgtel)8nwoTf{pd8d zetUNAkIQ1c8^PIJ$Ef z5ZR>Edt1+1;j(QG*sumgF}4m4{m@`mQV^`JJ+n{P47#@J*s)`Zfr~s__C;OO8g}k1 zgmCB_6dU`?Ae>Fi48f^v};tlFwDEN%sg6nr??$5TPMW?QSR_nOrYf)b^?B!lyso|==@<~cMcjSs*sqX4Z zwcw7@Nn%#3TUxUhwe&WStDJL(B$y}UIA{xyG=Kj3WsBzWPma?yHBClQ)3WX?^%>6x zT9Tx6H8n$EgXQh^8kVFOsh?9*)UR^XbF`1a# z=Bo&5GuY_VmQzmz%!mkv4=pXWR1)~6Lcn}+SO0v{bI%$Mo0l(N>h9kkMq>#yL;f`i zMg)yzf6Wye2#p>VjZ+&p*hIcg-F5hsIFIk+e!%3J-S9rzAd-{I&`Xs+G*oE!!?@e2 ziNl6)<&!5*&X@SOyIVnEX!`W&`jY>z$;p_GP=&92iGgFvgD+}dxKdirw?e1}S}a>j zMO?WO|M61JbZ4=9|ICl3*RPZB-4pWk_O^%r#Ylbl@WBRFdS9o{NE@VL$kL`}W(BzW z0^r7}z%Af+P?b)XykVFwvKeYh_ULpAyqJ@dGf?O|@#TD#?>7|TtT`qBzdP_r{Y!e# zQKMx~btUbRm!GKpIk~tUTgaZr4iwrhG&FQR8Vb|W-$e1A0*C09!e`H(>GH4pZ=v)^ zskh7G8MCJ+t3k-^#$@E>lX@;;)i|}>U;m-Cbuii=KCVaqHc?S)h%y7y{R0D4s2nqa zM7My>5I?7AYiE}S-76PZCl{^Apc4cux~Zln6zla_*IaM+G+re0*XjPW*`Hr!YB)T- zyauBWVA-`kG)we7dsbRZOl;AA(>nT&$K{JhhssI<{-d69e}Z}%675tS&So*tvqz2o z5bM9@^b~bj&U@6P?baSTViaIiCV_`zRdA;4^1Rz6VKg~8>0BASs;RsCW>$8#G^`FH zS%V)nczAjajy>MoDH7{v9hryPCkHXi9-%qc3eS(Y*MN{^NKbjJHBJ=jeC zB?1x-WVP$*=@GpR-CGI>w5<@bHtJKBZA0R9zrWX}yMt)xW;FKf-5YA?4~Y|snUj-P z5tHe75g1AyXizS8xHO#SyV=6z;4Mr!cwbnUi9rL_amvwM27)f%@; zz6^3qE*mTXzQplx&u#uf72ZxxQ zjZN@fnRKkaIiKA^-Cr{zkDE|VC! zbZ&{fpZQs5%xqSHzN1QAOEHYKRtMsB!brSAQqYIAo9BgAv7cyD z&?(Ui)^TtM`=kJMsq(M0e0gE{U{nltT1?2?JX)Um$&=YlawL<2#{-foct{!5-$3PX zN!qt>j#LGjF8?}uNv~nU9|Npvel@k4PG1NQRX#3{;iyy8S%rmZ;>l)?i>SSDO)ZA<)=!z*1Yeb?geI9>R;El>0Y+mA8Oa1( z!W6`TgIW=UXh9U3`hFMgb?YkrIqSktBp#Im(XrzPxSy(L-ai^Z$Y1?)U?J|AoH=tQ zB+3pT88(jhXp+V(nhFO+{O9XCPE`K=^P>@DFZrObE!6>^;MHmdhRnZ>_4QWB%fWKb z$`Tp*aF2;#R4OSiPwL#_e2K;!Oo6FdN7Q`sZD)UddwH@oyPA?jJ@$sCmz=kOjV*7J zdMLmv8qQPKo}0gabSC=3KE}2G>2xiRpYszx_UxL<7}jbo6ecM=A#zhIf@EF0&4&VNqWPL@rLj(u zUGTIxC~t^6A<>gC({zPd^V0(U?oanahHQQjih1MN>9Ntq^>B?a3JID__K(6_ zp|M1oEeC#V4z>ybw_zHa*?D-xTW@j;0k7xDxqQkc_7ljKE}jZTAmt85$pN{#5AFh#U#9jY~;CgqnD+l>qbCo)HIxfF0DH9H0jBA4x9ISb}Aa zq9Pb{5NkF>kP#|6{QXy}s+*K@FwOlOT z4aA8@9Zh2i zZ7%N9DG=!Y@EP=aKOlt&yS21Pz|@di0kss8lCOXL+71pHBUSL^$+g|hurV{dzrWWM z^^HwUZMv!GoBr1Ki(>VBJ06IIsiv$aMWA*LUoNCx2E*^P> zOE%o4`+4%`^1!q;e9F(8rgfncKxQ>uSX8d61oHPAjq#3U>2;~_Qz?4fevp{dnFtbcuIpqLdZrT&>3r6 zpzs9Ti_S8C1?1%;Gx2>}6(^zDdDWZl0n3CT183f!Za!CB}xv!+gb_`b)1ILxIrpbqk9|o&K-9lHf?z}`w&p!m!-Ruva1yWYRL2k@Pc5qO(R_;1(ltWM^d$DN!}z=*%dXlj;c$2 zSaqXzDzX*aL&zs*7#s|L85I6_NnhgE@$#i1hX?+n*w>5{IA%h2f7EP$@^pMiY4IPC zei?M|6+xOB8d|LjD2^!8ZeJ>dH1=!#z74oK+jK+TofcjE0vcV{@8#4j)JqPfvpYC! zO!95_$JaiEI*m|ZA6CNP(XWo66{Pn}PfN5fqUuo;R#}X2xyRsY$QG9W(de>%l+6xt zQ`JtQA|xygcTRfxDN>;%xD0}H(AZeQcz()lWVgF}f?;<@xd1S^I)bXOhaM=f0x4RA zr3Ef>qTre2SuaPfIonwU-q}hj%PR_6N_cwuW(4d=q+3f*Z?COwh6eX3?ng|T&fU*B zA!y>$#EetBIz%KS^!xl^h<=%${YiQIMp`KR?V!{ub(4)isgBaEj{pN2|Yo|8DS`I0^tQQr1=U#9E?Xy;=GoOm+Pko2aO&uIkYvb#$(L)(WQus^bo%Hmp ziHR$rum z!PlbZg1mfuBnn*ca&M=nUwHCl2k|+Pwlm^NK{EZqg$onk-fao_bSWg{HZ)HdwS%AZ z_w1p8t+a;v*7h((gE#ScjO-av(>H?G8uA^QHwp*{5F88o8h^k4(Q!Ntk|5U7(di#S zScgKPZo>{V!HJau(Jg@xW(bG~ry()dR{J$<0TyVILFqT0>VQ{B+;n#RJf?;qTM zeS7EeIy}3sBRWDI&=z~F771Q~!eluYE`|ZS$LF+t%$g|2ATj8jBgR#MYvci{pMVx1>9MDz1#eZJG`b|_;HR9Jji9E z=|f@{^K)k7oO5?4118aYDmBQ|#-yf_tl`TKMc8MHR4q2i0zRJ zhRlWt_`YGD1VcbessbAR(jDPdS&C%V$dJP)h0~|{u{d(TRE1C-YaY4_aUsF;;7j^s z(PiqB1%S7{SxRa>{%xYR2hA5(yO#F$T4aU33=ORSJ}0KeB__U5Ren``DQ8d*L8CV}l^AL`h-dr#y&x%J@R>Zn zzKJhgbLB=|V&tjPEdw$Rj*j%Ic}&Tbm1>I(Yj=ueiMkg&(0;V#AlI1O)BL-xo^T_g zD=JiBrQ!9g1TTs?c_%Y-Ipy&<$5v+eVG_wF6d z!RLS2$aI=1{4A-nzZ}UsC(IgZ>CquMyyw~+(*oA+eXzwz1;+!l#;5}Gh>|-A5x{Tk zMLYy};PKnD`h&a*)z#GiV{5hc?|%ssUGh|ye3o3i>|&CF&vw{E7x8cU-~vn6UhFD^ zjGU3M48=+wZy@ej&X#%a-X#FMesGwHsi{iTxp#0=k{b=*c7*xW z6Z+xs24eu2(Rhi1hb2;B(5Z|Z4`#-|YeJqr^@^|cMtYB`rdzFvObG44QFdav-U7HL zktrgsU_&JT5Z|_qhJt&VVA>pSW@=iWvmv6Io}Rvf>;@#h&|>TSV{gRAv*AS@gv_4& z8^yH-FKPC{HGr*-LYEXn1+*DpnV)&^uMs}b8DgE!?_1(1QXPctFq@y>ht3^z^qp)z z8IIFjKu;$*8qz*7b76dTiBDy64A1}Ip+hlR9BvU&uwXc$(^uhJXl|LOy%^+F5EK%+ zgT;)l#kPWJV;GD!PG`*Yze}u-J6a?wpv_MX4_p@BU!%&SoQ%p zfvZ+SesvqDTc2Dn#HA1;!TH&HAG*Ui9%s}CBoeh<8eeyuz%Wm6W%j~vJ#=4(cHw7V ztiYuxR;DOz7EaDE?77V{GAx+pdHLn##lgbCyRpzL%!CEh6n~c%F>rFUxJ`V z$N#l@qZFgp&86Ap?rz);HFyoudHSfdL4D`xc2Wb(zJk>?HKd5a44RLBl<$@ouWL)T zL$CG2@M_Cdn}X*9Wu&Dm5s$o@k`m$jbI9oI67HA?EGWe5SXeHin^%1P%!}IS#|>CO z8qkg%tgv*{pss<*n?3sYJv{1$w2aI<6c-?^bZ9i#iWj@=*7NZ4LRSliKGSI4e?KSh z1GE6L@$l;l#B~EF>v+TNJQhl0-#(oyrV#%c+Kp{Tjbr37xRKFEziL&?xt#d(=UnSb z`_I?t4e0Fs$B+=l)IhyohsjHX$qt_?MMuanFJZ=13(HpB`%}eR^-?eAc)AgpUb2Mc-msK3k>R+Yqhh8+D`O(S(=2f3u#ZP2yJwlhlApz>H@MkT1#>GMxG5 zBPK*D55F$&z8U&PUcka%Z40fV!mLv=DX=VqGBN~`Wv-K&LZG<*MX}~>KGZ=BR003Q z@AhtJY*fedbzAuB^#qxnmV22rL9n$D_g_vC^kwLOqCXlXzaR2X2v8Kb#H=TOG+I7z zrW=gf{`5L@CLo6*HaiYd0$^A0H~o(c&>WAEDmO@}NXWH!)#i z%RoUj{id`R;LkE3%rI3ukQp;2G<5-4oTQMD4WZ ziQMw^b*!Kcd>Ke#|Kx}O;Aqp&&%8J(LYc#+0L5gerjH+!!>X-AYy#(Jq%duxi>NYj znKD#V(#qdEY#L^lec{8Wb5ZrdEAWzV~FFDM-H!@y*M!;lq33e4C4%S|W!(-qgk z#A=1$Da-5vaDQlQ0c#GfFLscD7vjJ~BEpzO_w!wi2-Dtg#%hwJn%=%WXw5@LGl&s~ zsR0b|#dK`lvEv%1VP)XeL+i?MoA2T{0h%RFQg%Jhyu|1#AU2r5jjCM041^2&~NayuZqggO>aj&hQ5XaXccvm-_s(WeV`0K_4D(?5c67S z-5no4P8xrNChU&fA|&Z@^RKqw-)AOCTG3($y@hv(!0{J!Q_z%`m^ukTVo>V|$vR>) zy@M4C*n!Z_7HBgTtUt*N?uF$H4QzB&*z(WlN@2*MhE3~&ZD-!3l|e8)pvKt1fEjgr z1qf`In0Nc5V{xC_@%fVoWY}PTd_xB;u@M9olt&Rm8VhU3z^m)%73?D_2w^pCadF80 z;!QV|xRHO%e6y7knedjJJ{0GP{$&_bM=&)@vk023g!`N^rZg%?gSEqht!KbLVzL0nik2vrNnG6Kag9l>WUUEM0Y$%C*-N<6<>1H=u6 z_ZRg9yn@t3u{K`_oSE2!iYnNqzGm&(P%JQt2%sE_XC%q8gnmm39NHZ&t`^S}6WgH( zTHe-AQ#P183w{V)8nd@6Q&EY`-I=FM@3*yV*py+8!12dOPMLr1^1F9dpj1uo-^Z@u z+Hn~#qW*JK%B8Nmw%cJZs(KPM+e zj8g@%jS5hUh$wY8J-rf=vI$xv1qAXg_|N?)YO1OV22PQIi9%8> z?7Y0kZhKa8*nnZ!1G_l@66)&cL_ldzQ;tRmkX`<2($qYagc}g_t1LOyp|vS9y1z zwj{&AACJT=)TJz>V#emK-i-!4apL;RNJ?7bitTUTzRk8<2-y(~u#%BDqA!!e22Z{N z^*_U&E&f_sTJunIS~EIuEfH8TR`PF9P0HAmmD46saa!W|b4%jJqwLy%)smnO={&Hp z^Tm%FT}1p)hNJ$$ekubniTN+>`czXHEUtP%skRp4hc-Xt{% zZGgDWSmE-!clY59?S-#eS?hr;A?|a>sV=0WUxlLGrS=pnc6+@XV~ zhO->3(dA%5U;BvAD?3&w69yqcrd1CgW+9vmY6NL+RM6wc5?~*U6jJ(yRlW;n{DY=a zFUC4ClF0$=fl$~;c(h~PFCI+nI){)Q9`YfWbm*ve5CMa%SZYYTQCt@Z$By26BFlsY zdkL8%RV*uvWfSWdt4Dpr49_;pTTrD3jg_RF0nXJyMSu;zc7hA_Ct3uY$I%hUv)o32 z9;wP+T%`1zlJ_V_C?n?ulEAWAhK5Mao~UbVoQsYc`vA9Hjei|KL?#%fYKRJAdSVR< z42h4Th)DlCA|#wmj8t346DRgSOoJgt9u@%1vMh+Eg6N%+> z8%8-9na4R2Wb7JlEuHYjMEFCTy?l~v%WN(~1|4`cqGG+R7k-1o$q%`Sfn91enbX>YR;j<{IAapNPm z(O|z-jzcV)EMibMS3un~7({wX-fNgOSgqdgC)UcB@$qPwCwSMS83Bw`qfrq?hlW9m zFyYl<2~pA25L_j9?9d?bTZ~RYx1cJX`~D;rJkc*9i$Gt+0LE%8k?1d9EX|*tUWMf- z{?mkPW@HbEBS^*!P?j@4k&zfm5|ye<9IX30CK8Z+Wx$*?x(F>I&mqWGBe_mm9Pniy;-GHs?&qNy_guoMDN9Ri zN-ObFD>11>7$k;VdeK@89w#e8OmoCQ9RxZOX1I2ZmCUW+*BMMvW#%06LY*XTJbDed zFb&iU0{TyXdrO14`Ne@n@W`tTSL5R7`1tq;Cd08Ajvcw=`e-Nw*!MsXT*u8F0on8p zXfm0Hz}qG&9|ExJhW$F%vuO^KSr$5!SFOv2*ta;a#KTtgVhkB1cu>3o5Devl{y40V=?{f z`(bhz0Nmcvz@@S(Z$$X5v7DLh0I_qxoG>7bvZ28{{n4B=EetrJsi9#wZf+Gly(L91 z2RU0mU$j^}lnrzhclFb@=e-7m#^V5QXXoQff%V2M`H+1Vfav>)Fxbanqe&w7iSFVsuXp3A= zz#PnR5>n54hSfk~B?t(TIHX!$`I$*I`wL-GHDDO8adbey+yQGo^Y^3udp6rdzVM$T z4s$LNG!u5HUX0WOATAw!eF>Sn7&>O#wX)0d9uyIzFA_P`_jggo2#26W>$G~(C87pT z*|F_c#?lR?v4ow>e9=n}M)<$u#4?V?>neXPc300foFpWk_6N!lvw05l>L%uERhgr9yFgi0ZT}_|4R)A76WTbwOkH-qth>3e{r;$w*0Afi(^_j(3&d zUE&0SWSdOX3mGa0JIL=NHm}{*Rt_<}y5y9U!&6~?vi~rQ_0O-(wWv1lirsAxYTuJS z1`0?8ffqJ(6+(KO8F^ezL?2w2dioq63@7qge7>{`@*_AvIa0G-AeL8@5jx1i>=rXjM0fl_zqL-2`JVOB%) zSWXeWAFUN<+9c@jS_ycPP_jL7Ge(**1ubmU{n$bUqho2&57Hl*m(Wt*P!gKH|3(oX z>?F9ZwH^j5j6j3I@JN4(G&Tb-kgaMc1CuyU3UTQ@;_MftYTEs?p_K2>NvrQoIDFM> zE*|1O+~p&W7BEtymyH+=jq0TIvecv`4%lb#Douvu2vmYPg2xj|M&LSsQ@8MR2curW zHdUSEx@)e>gR`OF^mIM*Vj$gIKcf^hUoTDh3K_OhP~eV~B17KyQ`;}Gu(2%%lt6#( zMRUAw^nl%>_C+}s_IA&ls)sa2Qw!Bsnr*K{#K*@^zTVkm38WX#uCl2$jBJ|BsEtbD z7)z+^>f*uEya0Nrp)}ocEtc^hWGCc>^mX-Q8N>^3DNF@FmL>lb6y%k+;|7| zoT}cRimFcdp}z9x=e{hYBciiep&Yr^@_xW`$cS~}(}BPL@aWSSS$f-tB1Y@6Pk4O4 z8WG_(b1XXIL3%L_88C19s~>iQ6g%B1<+x(^N-PZ9;{tuxc;+N)B;h8IESSEWf259w zUpN3*!ZZG-&!0_gO+#QLv!OV`Rd9p;*P1m`HRNt2!vEkeDU2{&TWl5Ow=n6pAe0E7< zM6r=upLt42;ULpAsM>#bXyB#qz~ zD9^UQ7>wjo*$Dlx8W3m`>uS31BQng1!8Ma-r$qqaEfGFM3q&RZ2RRv?GRRgWLCT-K z{N_{Vb)5Y~HTi7QFY9xDxW>NE*V-I87~J+ntYsU7G3WcH$WT4xMTpsAgTF;YMb)6l z6Qr*T)Jmq1PQH3kj^LlzkMymZELbq&Hx<7h-5*HnDrTZE1e%VTE!~M)(2tI1^8Abx zq$e@E9*j|*nVk{@>syX?kagCy;9&#TIw%}AC+-J4&V)5KcJ#IaW5dvT0XWzCEyapFF|I6FdD>I%2 z+ck~hR4V3aB+0=`w_S)hM_xN|xO?Ys+_=%i`DZD9sqgp4Cns14{=^F#?S5f4)|3;c6J@6+u$~EwDbA5l>Q~4Wy zJwhcTOb&FB6mm$3aVLMG@P;vY0Fn_=6&RD8aRr}U;1iBB?1$+*UtS`19;is#=3%zy zL3Tu;A)D|cA|_GlHNt-6k@+g{dL*rGN*EtHbUrb01DO|u24D6(H+LQ2WEJpS=3m#= zY&P_h^=xb*0G?VcE}x(Mj@z(<#BQ>W!k_*!Fc1tct{3WEoQVRx@Xag!gAHt9D;?b! zEYLiU_w_YmPCO5z<4ap7PDdZ z24h(SdMrUlA~X4njErtLR{$)$&IR*FHblcDZYO9i{Fv>nTLG4#2n;O2+;t_|ATn^) zBCBgS06@b)7xa}^s3~tC$M#u-q3J-_Kx<*y1Ntsr@EP6~4Mk>^an>QgeKa=eOXQj` z8W(~zHG&6jP!G`JdePIRA8)PiLplUlXbo!nMLab>e}5}m+h9nv{77~XEK|Q$6FNI6 z(?*gVLYRsHzQpN~wgdK^cobW}nQ_cb1)N0;0x>C7(FHqXqCM{0Ii+1OSM((r*W-S%lRf&l_F7w8*`6>(*i{4AAWL>(=oTHuA!BY1^YA^{vAl`xMv^Q5kNn%WufOmvF`)87+hwncc>L_ZY|jbw@wSMUXn5((1d z7{h0$zh59A(7fpW4(OU8nDs*U80TqSF6Yw@G@mAb$?h>KS-~qY$`IT_AGB!Gx;j{_zZXj8Zf&=Y{4$uiwb0nAS zYT&G)&g`tLUR1lJ-Cm*?XN93(wf4iCqGw=u1)nJ#pW2JRgoADgK#YMC zi-Q@AKK&&tMau}e{#sm1E{?81j(QzT5_Rw`sOHGqgko+n9l+A>9A-JXfT8e$_reFl z*D|82k<=L?QA^oz`sfvq%xkcY@-WCij^U(8CBhJEKLV`}T9!A7k&}7o*Q_C@+PWe9 zpy)fb3=F@P#A7hhrXS#6Joy{@$sJEJ*#*0JcHl6UEHN}7vs$9Yl7kHpkUj{}4usqi z!EA&6x5$!0)Mo(2-AbMyHu=j}ujud+H4F_g%vWcuULk5T8;G{E)Q)lCFq9E1d{*Wo zSxmG3ANJldtm?jL8(oTGP$~#Wh$2Xbg3_&0($Z3rf`EW@Dgpx1B}gOE-K}(YNK1Ej zzw_7Yx{v31k9{0_KOgqqU)~Sbt+%k&`mbNioO901Ij{hi56=F|8DA9zBnS(9_DjDV zDztnM(Lz;KVz8O+-@ohaToC!^*Xg^At>JZeh&YFht!*3xbs+nD4#jP^eHpAfs1Yj_ znUGRO|HJ&sBGlK%Yi>{rs0jxEx$;1QK&vG^Fs*UW#)kCc0$57rdIHag++F=o|6Pjt z#oE-Bf8GrFv7*m^|MCA!shL6;HnzH zS(xRnFMmX!b>qVS|I&v3yCeV^{=XEY{(tF1zrBnu=D6cjAH0Y?l@`2rOcTwT>iXFt zDU~a@Lr_bWJo<5pU$C0$U*i9a>bbtX)=1nqp5anX`X%nro#e?+BJzv*W`yT=k6(;x zIaqC8-I^#Z#`qrdQe`yGKkJjMavQ+T>fzUXhcs(b(AXM8fzUD|3eHgR$}Ix^&5s$x$a3LRC|e zA1%{L25E+I8A2?_-F6h8S+OXFDkQxd@~6D7-^M*h`iNn_%&NM0_WiXhPj9TW^Hg}b z@bFF4u1h4FE>g=RrGEPmpnkGGcVJ1_RZE6%_kG4IP9{vg)NMPzbB2HZ zeXQ(}W|i)D>FC@LT}&tTA+4FZ93jDsToP>BIo>a?~QvA`!%B@x8`Eh+{WwU*f-ZB zt8?0U%82BA8Tg%K&bixwT$<&*W5+%AQj=3;KOD-)&8}CD@?6pCD%y~6^?l1)SYPe$ zqvULy^~AZv&Gw7cs|V+K?8M+%T}R48vx z%H&2ci?qCK0JZZt=dFwZ?CK8~OV{VSa1F&$Q79uc3+`z1t73vpStC-o30)>?sM%;$ zSH>Ncu#)kHD;+tUZwMCqv+KIFh~whYLGio5=!2PuQ%$$HDs$OMv9llD4Zj=F+Lj%I z^DE#HBfOFw^?k;P8T z`hK&d^FP|Z-WwTtp!2N1Pnse_-5m_THNz$TGGk#vpKzres_&Q=P1UD#lN34^y3r2L z)vP;ha8Hj^V7y+gRUA4`PKjL^+=@=kB($5LTjLPtJ)+Wx{D z#v1zta1c&TaK@MpaKrEF|1`IVTkXE~==<|9LmU*xb!-1}TT*j!6W19Xp~32_r94Oc zSuwBjz9`9De<(@v{@Fd8Pw#eUoy>I_xh+WOsfZ72D(7&eP_YwZghW1H$+sqL@e(P7 zzL`pkG4cQW%8qi;%fr7hv$&&nr?*c?#_+0!!u~1)iod`Y7sY=EPp~fzdozZ2Vt9I_ z#B*Ew?8mDq$;-iFXVl#$eb}CK`1j_VF7z&yQNQg4dzL!<-aaY&M1lEsg}u9=*>(Eu z=UqeX7(osm0zyxtOs&^XY2I8N_Y=O-aregMi8qTbWD$G-oO9R#Wd$ck6AF<ebOV+ChC!wf~HqMjA8A4C_DTSzJgchO7Y(81FtWHzV-ct zlY_(+YMf&Es(88Ay_rm^-@C}$1mJmF4(#5!KZDh3DY5RlcW2vTiM-EROp+*6W5_*7 zCT#sN&$&{=Ap_$zH=2w}SIUdGiFG+mM3}n(j7hwK2ks+hHtxNslT<)0)#yxL?Ms zSc5-`A>R}A`_1ByqBPUVPfHK>_ML^Ps_IkexU!R5Ud$(lRDOG}p{7CN$?ex86E{CQ z^1Zk4W3U?(VSghkKXT-pOp*|UlV@;b>82iYDqWZ@xH!%Qro6RCT&3#jh8)$Yec1~Q z1M5}aV}g!rY*rV#u`h~lhwrgoeSlUaP=CSI_m7+Urg=qvC+z=nkq=U4BSHtAUHsAMwJTX*^29ERCx`ITbGl4p1d-v{0io4m}ld624@=OB7KNi%0^3rfUPLh#+NOSvhPPJh9 zS2kjJf{nG3?&L>r8O8>DKnDwIw`5P8I5(-BE=Ri!q?mT4IoV$*-TPSZo}+N&vY@OL z?YK!(T^pg}Tl`6uF>TEnllhhI>r!LF+MTPOzbxiHJ&Exf<8d8#k7M7RKdVt~wvWe0y@Q+U_au^)F?F*+%EL`z)D%B@hs);ybrT+Y`CJFS|dfJW1!!6MN6D z-_#>dDob;}GG&Ih|3kds{u-eN)tbgD{u&d5wRxWwCX@K1*|lp?$13qFd^qj#N1(B857IfBSbjrBPc~WO?8omsZ z!^-pFui`tTQ7$E!SMDET&N#p8&Hq8@de&~HWN(RMNc=@RS)Xh>b^La{iu*4smg1KU zoDU5+C7-DYCY4sgfkodNfA-UP&Zb;K%HN=9ZS}$S_`yBIsHtOdYDy=p+Qfu>$nluf zoN&W>c%<%Z%;dyZcGagf>Or!Mwa%=*cJ&0`TiV6kCuJX7Dc%jSV=iT_xJ3orbhq1e zPfEnteHzI>CvngJO9*-1nV;L!@%Jf(51v~Z1}~8`21W$=eq!^BP5X`|7nK{N0S0M< zoCbgQS;5Uxky`-PQ0s-Ew^?je%kEK9VZj!mp<*1oKReeK*{-kB7%ucrpYg2J-e6_5 zewKx<+fit$vxM3f>dSS4|~O5{r*%6<&P&$fqZ8=f=-h!@S8uzs{jBtH|Ke z90e~adCSWA7r%PY?RUZTCLRj)TE^+zu^%u(s`P)v=U!5EmJQ#l>d>D5}N2Ba~#$!?h5S`u4>dC>Ftj83t zUSrWL5qcP)82kn0^XxWrc-iIiG8>bhMTr&ez214K6U~PfB{_=1mE~yDYf(`1o4{{!$P`bA;ye)Ju=43T5 zH)u9mH(tg1Ih=wUV0L&Rn1NpA%Qe4Ni=?=&e2&izQnSN`k4>)xMDvb4C&Y@vj`Ba6 zzq_eX=`vwm*VNKm!6m)Vc>9@A5zRYHLrd?0SJD)s5OsgEBelo-5LI+0pe?Sre7pF? ztMAXi+B+|`=hZ4IIhf%ZE)7(BPJE3^%0xpEj=s4zl!@^pA6L9{EaxI7t5*Xb;MOO0 z>h}u|nE1IGW>H>zN5Z6$o*M4`&k%xAM6=k1(9j7!groBO+)tuu7ckJ!E z`Dg5e1L&yZX)cne$5?cG8#vFe28Z^QYtrOl1c+{~oIrWX_Z(#XzqtF%eUwk=mE9wcB;OD?YzTVCd~!TI18 zO#|@YL6bEhzQ#!HnX_oXTJ*%}F!g7H$&T@eI?;AXx`CYUb9$4@jikp$LnHNQ+8^|_ z*G9GXbLuov{3<5s-=mez^H>^366J)%Tx11saQqm%F1JOst3cg|y+1o({^;4VW8{Ge z@wgM1E1RrkZ;6Sc&eQP$V$}C2rO%2qrr8!hUghT=z13QI(W`r5HR|(4f_`m*x$c7edVb$fAoyzLyu(Y*NEzDJc+OZ!7 ztrz&IpB&~me|sfkVR8q?zIf>i-K?*AZ`Htvy#C`N5i=oY?#qTlK&?9JSKt54mSI-S zz52*0{K}&_bPX?q zg$wy>VE^gGgf2co_??dztEQBt8O{Ce|egpA;gmXMNH8Ak$XU&DSBFtu*o;(~Jjtc;&Cer~8_|K%SGW$DJ|Ip~m_&HGO#jO~ z3*No2yFyk1jCzqOzSkv=Ck)>BB!(6Ku#F?5cQ*1BD5E03%OX0<3#`UBy`t-~*IBnb z{m+z4rcWk?d%1#7ZTbKnp-?;S-EwbAR3nRrBQ~RthZX7>o;MfJ$LhSQFJcfa@ZIj= z*{>uj7F`>V&%qJ({Vrt$^Cs?pBqHNw-Z{kN*D}A6%jgmN+Y<9hsa@xruB)`63NwqU zePaTKskx2CHjDY}Q#EpYo6*k%bPVY8oWa{PmY8ALO5GN!TdXO?6eLx1n#B`d7_anx z$V*{pPKGzm7vp$B%V+PYsB{_i5LcB9H;F*QmOfmEe!)#c9Iw(RNO`ZeO<;0d?0bQx zMfvi3McbJQ8~5S|_hNw<@FqQqGhC966dB6CVd{K({i5llCi8wxe2AfCq-Kt7(F;E0 zdgg{adRMg}y!pLAglnJeGu7GEfI{rCwkXd%@42|y?eWAya3ZnPj|#f|yn*DN@v_+> zbSFA|oBCSOu&mL#uIS>ULi3PRqgV3W=$@aQX$Sjd4|m_e+34-g@bp-N*#doR0Zv{7 z17T%P(NZ{ZZt3dE;M4xBOV4~!QdYteegE0i!jVC7zj<|3@uat69Fxi9?vlq1V+=2^ z2F#8i3Vm|2a_6gDrYEitdiY0wy@FmpP|+gtf|~cz)X-bz!g`IcqKU3DeS@XnKHm$7 ztLr=CUp8Dp?L1$mJY~4yrJ4TPWqSliJE2=$k!jU3WJASqgbmf(`?$JKkxY+AxZD== z0WJcQyagYN6?D)vJ_Y|7m@B8197nasUYu03kZL5}7iHsc9p}t(-0ZN_Dp2I}EyMYV zmDV(BJOXmY6;i8F-p>zy=50)$h#z;W913QnDXW!7+1@;OdhwH_Z%t`h*#pgi3#~g} zT~K?`vMYQcAx;d-%T-FL4yOk@nxC~kbX>ats?}};k2MXK)?||0vB33wc<|F=eJ^%K zc6cGhXbQmbr*I|mCNU9Pm~G~N_Jy5A?$tAy*DEM~9Zd@PBps#lx2$YwZbw;-(xnUo zmFsOr+41C_gWS1#L+g%BN-iodUi~zDYEbmr?*m&_K(00m(3o!0YNUqkjGcyq=%u4} zj=%KPZ{HZ8_fQ`fkd}SB>z5wRJyPBGoIaHOw!9omxZ~ToP_6;5ZJ!%PVsAwgf>*vy z*QYdcDxB||O#4L7B&Anz>Vm=x5lKGBe>gE|N%XFC*Poi;^^(Z(S1x=rh3~zuW{>Tk zi`%X5Vj?EMuTROtZG!c)ZBV3jymj?85!VVlOpv195YQ!k%gfDb5m@ z*EkkL#^ejwc%!{uh%N2qI&i>cuv>AkwKLrPbetqvVapbS@ zB39Z#Opub~vXUqKQp1KY!P`SoYSfSR#;ZLleu5sH49)WsXv%q}PXw=fiG41^vs2sp zrq9u`s47bR;fv!u*Q2C;5_LN>v@rD%e`0+rC&`p1DwAi*>ROdAMgb>eGffshQSKSS zq70NGo0R(iC+E5G36E@U&${CL<9F#j=Q9g*m=a>iW!+uPjOkZ7Z<==FGH*?r=Uc2Z zT8z>#$V-IJVeY@JEND?;KW;iQNG~hBWK?;8MMS&!%fXS);SJ9Q&;+QutfpjFmJRxi zo6Damn10RTO$*42hTo8y4*GlCq{T7Z)>@)Vo5yd2L|zUk)G?1`PO(v;-@6!fWGWN5 zoTlj}DtawBMVulUctw1U=_A^lb&e!z<=uBLZDse?1gbxb-|g-GC}vEIcc0?Ui3szJ z*^c)Sc^JU&y3EYpSu&(n5;(Vg37bao%TlbX-j~GslNbd` z+(qlo*LpnBkSSd*AlSZiK`dluq#PT?OvF*xTPaUZAFwxnj;|&C+lPU(%uBj5hv|9o zt!eVb zRCLhab~Q*dI3Aw!D9kPI5OXihjewzZF0FNuC6!`~4(&4Uaux$&>vY;Liyjs+nK(zB z-ELVSJELJ4l%{5^1r9S=vFEn1duCX%?kd>8UxbxeFCP%SCeoA%i^+-I(&7H(>}4TU zlRYt{jycYElgh(1fMY2+K3~i>V}C%tok2%;Pt5^4DgPLIO7(&QJ->qpra4Qjn97(V zXQ*|I`YP|&;bQH%73XWWaqq%#nZTj~E+Da{Bwy-;uU!IucEs2I6VTl$7hb;;BN_kB z+9OH-p~#1evk#PJ%0;~Dez=eB+a)=Vqd^tq>ylf{)Rf`EpW>+A58?Io&}RBF>awx5ZD zoZVK!nUzciF}`ZgY2AE>iT=LkZhI@(5wymuIIapyR>`FAp3^od+=Am0OTEBTwQeOk z67(C7hmB0mf>ttrddx4`#s&4_R3a6x_KCABU+&u?V?7Uzkkl4z9JD9o$z>wruV~_8 z_nS3kQkMld6d9!zLWiF=@3vh_O1zmRpm3h9G3?;%Mwy@O#ZPt8IYE_^CfBq{Ah);PFf9M&e} zTjrO?Lm#-;hKRewlb>EO)|Iig@Cfw01q8Ifm^TiZu$koJK(;K1;E2vxvFs|29E)|a zTv9W5KG#2gvVzv2MRtlROcIjW3R6Cg5DW29{mA`#=0^4n*WC29xW-DSn}+RJ6#e$J zisNc_{ITENYiBTI)^~kgI3kCwoW@?WCL=L~_#jr$EcjVk2HAy0B9Xy7G&f4UOx^Kv z-{o~?gM<9Q;yT&LAlKut4obZ_&uy&OH(GAhMP?#=b|Ud-<*;^EVy3( z9A*<4Dn@@cMYXl0;+bD?_aUpG!Y>bDYJCs4^J2}?h5il?HCAgT-xsDoYJ&ucgF5=- zMeNx96djI>fm4yak?*GQ@%=1H2TU>I_&@WXoMwPO{$9CC*q{>^i=o2-TJ1Q^tCFO2im@I~LIu9RQOYVqQ6 z+tCL2B06##-{Fiky4=|l5Zv+SB;HIXX*t=tHGPFi%ZP0>)>m@lw;JeZtK_Gk^*&Rs^%yK%{w*|EXxgSbKxqzpA`qA z*5+tQ=gHpk<+j;M=}cYU`1}`mPpbGOYe~Pfs$I~{>rN7+yG1CNQHhaj`caV`|G<&> zht07LMeA3$FHWOzeTDaGl_4=|i$|`;LnyVc9PhYVV{gObaP3{`wo6mwfkVlLU(kkL zH({+*%;)VO;W$IZ^4~VIC32bv2h7org~^+gWUH&k;}iz2aH>%ZBy}&bT^6MaBVTP; zyW8FU40wXr;e?{v^E)Vi#}lFp&CQ+Lh6F9Pn_Fl zMXE}4zH-JM;a)O#hJHLxLa&ZDow6DcVoe*pj7Cgj6(9j&u2WLXwQT)|oTfzXtL?^d ziSh5J{FtiyJCUqm*piKr;jIa^XXKo32+xxdOF=|EwO+H;Dc4iKU6^oSb$_T{seeWB z^Oa~H{ItV3A4fT*#qhh7RHPk&>Jxg)j_HdW7?9J3)c5>a|IM$a7vgH%?;0qS-JN8? zHRGhkV|c^;c5>rs3VTGq$tMWxHTO?bl2vOU+kH)l$9_uS7#DM#4a0tR z3U=M1l7257h3((9!7m);c0vDcc?a+E^{>15iV15!GD2~8F<&a^@vV?ln4J#Fk9;ag zT9=z^=dJi~k>GKxX9ykr&hxm;ba&vj-tIQtoW41u9oWI_c}s-L^vo4)Px5C^_eFfW z``4~Kv5DTkN)YYl`d-T{OoJkNn=oeMHiN>BYgh;|HhSi4%TjN4-JbeM4(%JNp$rUu z`-WMYuN${L$Rb@KVc_)Ufzs(gY4*9r=G5aM$;db#d+qI?%JX^J^vjqT*>mmJ^RqXC z#dzH+%j;s81bJ^lsaXp8eU@3l(SqyUA9nAY#JIXhU)GglnLoh!AaYg{!RbDJg3`qh z|F$~ln~&yQkH-??=C61C~yZYkZod+n-Lp#!OGp$Y$?=RJ|p zl8fghBn%D;-6!_6Z>k(uumT$-Fqr!o`GwsT;>Pl~_~bl1)L&J97%Wp5JT^&{<|`ew zpBgo-BBz>^94z`75(G)BYYW78Y=_amk^aJX??ua6;i$!GbtBk_W;dTWBh*-{QtOgz zp|Rj<1y^IG)kQ%GhU79f_tV(Boo~pw9t7KRTu=@Z(k$RoGF-uOb(x?R6BRfN(9EtD zL{efKwHg#d*&iH$oyKYlHKqv6fXphdhIiTNdB|e^f{G6NwcD=JcRoVc0ttoLf#Gv5 zS9pcA5>0(22KKZXGK7F-di&Vy2jQB4fics;fpXDzQeUj$Pi}XKuWfRZ+$srY9$@+T zBqnbEr@enKtl(?oaXhX|^!3MEOWBb6n@=@@JSyFDW0tFh&iV8MIkeuv+18!rt=t79 zT&ARg>N4jd0BkCh5_hTd?iO+s?5q#*C0P80SzGng~I~ojGet3d3t*J z#AwEk7{IOhv<neJCEOXiXs<~#W9NPHf#rIufu9_73*Y~8l8VYbBkC{XMjtfbpK%I>5qc_ZnGncom zWZt+(aM!D#vp;ZP=~o~1(e@L}UfSi8#;2)yk%Vr?Ui4IBK=SHNPfZD_UX1#CAj7F6 z9SluYeDyuE06M=Xez51S=8qdNB-jk%@k922DT5`G!A_EeXLy#iTA!29;lPQmzM=1>zDbx z%c>^?o8IXbkO%Yo)m!unuac}tgO6Xl?7q^k8T}OAQ(GH+lY&TS$C0JE@HxcHV*M<| zZpZ745C(?5-owKc{6t>MF=zu1ldn?nv`xb~ZS^fL@vS7ksg>Z3>|fF5ReO~WALV0iDDK@7 zX%0PM9Y52zbvrQ7T>O+XcJA6$gUYKZB=3f!NLbw>k)peAZ&>xn;YA|K)INWzQvOz) zrM4;tIDnFhAe21<*RH9*_gX3w`Mt{rD*u$?^-ZLS@Jhhg9B91hBSnWVaMlSGn8@rd zD2e2UnS{_mh4d-IboThpT~JPW4=q2))aa}%KcxK@I=y9AkT!Ni%MFU7|EfI8C!ayT zCo~X)LRkrk@Kce#_yeS9g$tQM9Ipe?1j~LUX}q&(L8eM>fd) zBNMw&oglss>MxbO#?UwE1)U*xpz$A(`9frYUTy*46b=s9S6yMR!p zU=A{L1Q|OIvfOl5Z$G{`N5pa+S*22kE)3r}D8z@hHPG-vRKK8q76tXw+%U}~=;(VL z%ayy+Er_@`beY~|Vd}S z7J%7GpvMO51%gL3kFcRnsoeSOHpuBAY8apZMmw{AIfeS4XW?}0Vu?YNJqVc8HK7>{ zYKSutfr4c@(-M&G#)mb+gl1Cc%ti`Ak(M$L)1c^Lgogs%8dvBBhfR=%dSH-QQ#JH= zTpi(nLE;xcxD>k0T%cAQid@~H$5>(L0j{7J)eTQgEv@&^^iyICT>#fXp$h7bk(vc) zEEWKD9z;6Na~E1|u3f!(R$TsW;Rc8)X1^KC-E@}{35ok>^MarxP+A81p=v;y_<_y3 zCJ2bUoaH2)UQ*Z$C5DN$$Uq%XQbGp#LDjv-^?T16M|a_Gh@K`i89V@GNklUW+HDEh zOh}^noPD8Se#`h0CZP+gt~OL%BaLrJJu6fcgM=goqD}&W9Vksu)=u!C&k6Rhq_`A zPpTBG%7eW@MwTN>1#LoM_V$9Q=Y7Kely!|(Hclhsg`ko+7S!3GH3F=_w+Luf{@KwX z6~7D;2>ei|o~_dqC>taN;w5O(+(JKL1SM)6lrWL*aVUeI36g>)GGv%2bb)__hVV_K zzl;Rh!>XZ>c>^>A&!eD@{5%wBd)~SM${Q|zemKy$;fc)OGRKa*My>^|I>&4I($r6& zdmsl&s!LX&#pVh$;=e30e;UtCLxVK)qr9B==mP&pqQO~pgZAD~d)5DZmrag+@*pWX zh4dewNRbVKPtZW&B&#vPd65jX%aQhVz7*5V@hOv%63{(PSpg9O5L`ir&+s${r7+OA z*f?u%Y(z;P>+VchxlFfuLLtA%%AE7csPz~5nF`)x`}xbC$Z0=azUt-Wqj?VN(u1cBbyXagugOEF>le8EI#BoCvd!s|XkC4UN~h8P?g8kxTI`Kl z>%(aQp_KMlb8OHz2TIb77YSba%Sm!j!WP*;t3+s!Bxj0Y6)5O1SdfBx`RjJ}sn3y4 zhYQM%JAFpScCo?R&r$r2oxfKPw*8!s?Y-)f?Y(K+7V zC|S;J*_==C(?fn~k{7zhnxXpq;j7wDAQQ_7JvFV523`got(Gw*MVgECmBS^|St%H3 zrwn@;=yBVQ8oF_uqU~2pNGyA0l&wcCS)lixGLr@5;9#KDJrG2rZ!)@wUohcLtTz{B z-Tw0uStNW5b{OK`sBXU!LdhdF6=~7lgyXzl^U`L*ZBWw{D|aST6`B5iFoEOzh2r&a z;mX4Sv!T~XiO}S5u;J(Ya%$jw;y3;Cs6}sd>)D>1hTAZ4W_CKlD3hjX1Ak8^w9c_i z_#`hqY=olex-dxqDD=z@8D+JtIiDR-Xv93SoC#5!O-}GljTiuRgc19-3Xb(3XnA{M z_KH4xE5mFs>}s1_U-G1^g{g|$VzyOz@pb)15KDzD@hO@+B_Nu zZI#2mtj&H^Q{A>l1Fh)9Z~W14xS2sgQWOq&*y}cEGvXRhvsPoQI^J~HpYV{Mg45g% z8fGk7)gRN#H=E_d4yrd}|9&3=UY_Ii8fD!OdHk!F1G9*ardZ0V0J${KaUV^WSxgQavVMU(l#T}%C9T0)%o2ki79#P5`R-C?nF)+f2(TWr z)#p%r>X^$KHuBQ4Pkzv>fs}xwOlz%TuicX>`U^Rwp^1Zp943vh_ftAophDy$@8>LEw>epxgBp`&&(D*9nBddF(}_R;pt* zQ$8=vn<(Yi))319Qy;bj&x$K|`c*C91NERE5H6PSZP>dJ$Gy>mE%V?WP&qS5E^t^VJ z`I9R8)00hZ6w2W9M=4gNf|BW3Sn76xOQgZZg@gBnZ`44XiXCtZFY^Li09GcpYOVzYKQL_bSbOzi;2XdNCMv7C7L4d__ z5DNu$gtD$>6Z@N!v*A1AQPyMO#3sde;SGlb!$ywx8~D`c1HTI4@VpCHrx88f46B+3 zyx9SLRxHp~1r20$4Y%#zfVs=*dp^=Vrh;J<##AT)x$Qu#@E+1&6W7 zP#y3Si`itYq0tJg^!?{wq`L=jP&@CVub}qtS0~kVikzEy?9|{Ces(sVfT>h|KSMRC zK|^@J@)o`OpWk6DLrcE5rQas@Kz0AZxQ>$=0Sd*R!J4vk9=-@l$&6zw1^Z+o^GFti z4+eDkD+Zt)^7yyQPfIm)`1zwd4yi)WAD@yQsW_mEydv1AWfl38ns@>i`BV3v1M*I> zZ$3&P|2NoY{R;fyl6#%^9Q-N$xEiLcqfpJy_AkI6{DYTckpKICUi^O+?Em7BeE;b# z6{c?Y8ai!Wf220wC7%C7XO)uzIDVg%+>ISfBI^z6O;^EmS zXhruxkFIPO21=R=aW87G*C%>3z&ai8IiFegbwE_tf|zsge*IeV@NC}5v(Zsv#enxn z`+S>+Gpl@4?ECZZKro`@Y3aFQ3L1_(Z*uDJ5%{B0_(r*zatn0MsYEOHC%wyehm1Jp zA}w+OLCt|ZYe`9pKJ+8Uwn)Bf}?bQV;AE)WXi~s6S2XS3V0@Tq^yn z(!+8N{8RJc{}zY+L7-K6Zzp zH+gNyRw3R1mPrzud;Ldx*=p%{@i_FWejPR`V*p@jj}18OrPZLWEud;cm1hOWL8l0R zs4FJwhk?pY|FedQQx?Tv9~p%L)MkTrTRTafo!&&l_&}YCo&J`Eq?l-!>O3=MzsAt= zdKv+Bfi|00QPOk&yp|#RXlyUkwkj#wjuwP6bzA+ z{;HdY93sBcDZb5GTqzw0eGb6CcSAofbbs@}l(QyKN{fM3>V88kl#6T^vYE<0HA!F* zzJrEQ_#-2QXvf__(>LFEVQwcZ{9|QU&#l?7rr)6(_mLVW0wSGu(lq*PoKY?_PreVseIiicdmX;F1PGn?XgM!}5d+m3J0Fg6sn4vRkb^ZsK!xs@ zDZo_d=9Q2LyNEK7{%3R7Q=p06_Vi#b%(e>p;(4LHIcp&og~IZJyKS&+NW!}UcQc&Z z)*LZYiDPqc*b%Uq3oFw9-=D-6;1kFOkT;N6JdcK*HH|!j&6xh8?JEjmfuRepnc!{1 z|9x9aXc|ZCI0D}x?0EoeO$W?RiNxVMW`>*F1_9a@D3Q&=f9>xt-To3~HAI6%t%+gk z5d{P4Wx-kJP_bj($WE%|4J|utcL^uKP8B20{08Hyy8W`IanObgz49Qx?Z4s z0~P!8pWW|pP|<{cvt7z4+Vs{qCMPy6H-m^TDV{oz0{Ocn=fbygI93tVsMxMQpCkV? zCw7V^0Pr7jy`=)vOBYjL1H7iesTu*&j7t=`uDKaz|1;+?{bwg+(ppwidjJe0QBoo= zJ?h8RKU<7nP`Qkaz#;r31%2MOXdbRGH7jK#;)hMAK~Uo3L7gDkK6F&JVCUas-Qf@c zq9UM65#!BRN{tw82w?)@q&}45J2R=vOB_T2!?Z;$~+*CqaepAvBMAzsa=xQGOClpNSV)*yVwpNAhmS+(w|az5kU|LqzM z+}gtx7;6v?a?E@XdP{)M%8CdD%(}Ml&;9Cw`EGv>s{QzpI_Td7gQe$04j+hQXsjH= z->9rd%!Co@0!B{g0?!oA`dSwH{FQrdfS2a^vm)}~<9grC!JVg7RI`*vqA|F2T%eXF z5(v^16o@Ycjl?hz4W9F6s%!*F+8_#u?Od@QH$G?}L4we4Fohz~mXfj`Bq}JHAd+Y) ztpDrdbdS*Sh!XzqQH;W$L!pqizM#Pa=F=9qk}bZoExubUde8OR4y&A{3t$Wgh&0=x zV-otWIqj7pK`%`xJ>3LuFIhE}!HB{~cnC~;gwV|S?+MOHGuwaHBf%Dui;Z%*8TiM7j=Uv-Wpc?) z>EnqBt%rCO*p>qdwbFN@5W%-pkbo;ejU82?Nj|xG~0K!@V`4B-P_Z^vPGYISs3lKmr z!O<4b5}{(_{+KT{xSN1R5<$`fPRxnm6yghj(DR2y`a@8{LIhDDNDI{1B105ZX#BW0 zMOISLQ3iMZb!o3b_(IvVVh8`0^Fm_S=1iEzcfb|^0Uhy&3Kvt;^uXk|1NN8y@4gu@ zY}P~I8=ux|0N$Z!!dHD0fOy~B3eaBZTfKzJCjZ9{)_~Fp=L~ouRUHS=(?PB&tYPY? zDd^+Kv-Lm=ao2y$1L&|l1fuaJ@N@Fo@K6f30Xb4MSyygF8SW9o)dJQ?_P;*}=SD4V z5BOLT$L%iUidCMStmz}qtf=7_;=?w^4%@v&euMz%aCWm-g=L`LZW030)h9PzmsZGfjr`!BveqEBzG2Ahi>v8%U%-$6Ji z1l|HLj0#ayFBv*eeUtN)v1~aPIBUiNWqdHwe|>32brOZLQAWXL0@;Z{%Is};?gBMz z`0-!2>BCfph8$K?*(sKoQ(%yL4JR!7m6H^X&`_)w{@gD(;k}^I_2R{gUW0Mi>K6(M z{qJ4_&Jp@^91AMvoYT?}8G1_cu2WzTkcyZx zG2DQpzeauh?J+;8fCzr}Xec9;k85Hz9ZQ3lz6PVI|8u$)+g%y$OV?Trx(I$}NUi0u98 zQq?IP-$9d)b`Ha%lf8Di!XiXBx%grEw ztzvKND?q4HbFSV?ygTiR}@n%a)z7!+ZzL2m4LCueW!G%l-NB9Hn zXP#hglq%s-e|UZGmnyRd;&dyl>X;I=huV=VhHUW3(O%XNFKiMLoRv&^(QSeON~HDz zW}ryHXbeW6aZG-?2T;>EUuG9h$Mdg`86flVuAO_Ubp)ts+euHV+vkf-Kw7&zWc0*_iV>y}-;|mdSn!;0nGTaEy zDak(Z@lAf$Gx;fmcCS1dU7H01*?xs8(jV?mg~Af>b03fmZlwk8#4vXXv9pL-5X}V_ zl7vJ{ypDDVaa|5fIR{uG^W8yxNaJ%XfG$y+c@zJCNVC-cW8hh0OiCBNBi|l!CLOSLYLC2k`M@-29BWv zNsvLVuD^09G5cT^h!cdPKq&5ok&-QQWdc+z=ifUEh(p7ErQl6Lp901s8PRUwHY4G7 z!U7`D8E#AANJKJ)EpXI94vrDFAoLYF45zB^kLg43X$07wei#N~Sj0d$ZkU!K3mQv3 zl!nt@beVvXU$20!@^SqEhUCpNYk;p(PBL^R6rZ3Wg4NOUI_oShjv41j+^zwrqi*m*kUqxCG+v#sjJ%S68P2@ zt&o1ZgzEH1JkU&r!bA^n$3b8S3DyLzQP{}l{>&C*dsAyB+%tx zW}IYmwV}`Pk4S_Dv;S7_!89Yow3MBh{$ zJbN##hK-AmK&07)f;oAH?~=TXUu z$kA}@yc1((jbs<$Tu22%#ssjj#q)wKM&O%}{E2NSItnCjU||kp1>B_&X1#BXfb~Tm z$5MJ8t1OB?0dWC~uIRRdqsYyPr>=y8nbpyy(ibQf&V;`|yY+{(?qvSo0IFQ>`s)T# z>sB`c^?jc|pSTQWG*RWRm&_?g780TsJu9%gkfXf|vy_lrh3pC!2&cnYesPqB4B6V^ zh52SL-*r7jj6Xu3rRO%&BJ|m6=~CuXUkkxMf~5*a;Sd&PfUV8}QSc#yD(>LN+53k|Lag4qI(7gC;p~Y!5JLo1fEmg4>@Q zOM)*aEzmkfeZN>bxP#=Rcplj==Lkyz*aI<6dbsqa?%1ReFbazYj)PU-2#9>@fn+}z z9--WR6{8tQ+qNPO2ma$i+?g*#St?a}sKu~AHz{!=kWhWy|E7fLFW{vP0d>7Oh?&Fy zQ*9!_Vp>L_%A%t+_^GG$2nMA<5-n-NT z%20^zLRx7VEI0<~7A(Cu1RNHC|E|WM6!=?qqNNZ31WBhT_&}zPe^{BsUYJh=k#veq zLm9;S@GD;t^Chnh^v0^-Pq{$Y7#-NLHsp+gOSblGcmq)k;>CgEjMuR|FYVmBxReih zJ=3_DwUW8$y6Ac&U<4{65hh*2iFj%J&+?~JBh)B_?DUsN-tzZ_KNsHwR*Fg#7`x)e zLIfg1f(-#O2twpOoKN8R?Sdwz&VsVcf?Es&Rv*G+ zB9T0-(g@oa=2-*ZsoClA9N%21s!0=&Z|W*MlXO7RA31yAY8b4JsA3!fp=d{z0c_x%g>Zvpld6+lii3Lb)pj7qki;+Kxt)T@ zj(=YgcRYr1+|vhhF&#)Wkj8l~*;x#qw_?2-10kjn)5XN<1W`;91OUpwZ{`gdrpAnb{6S3&sAi`JIelWV`6b4s|jw)>cqOl6rvg1|kf^fCc_3u0hIOko-0b?Sx6?W(ds)PeoiZ9Vp%nHGQ&`?@){H z!E(Sfh-L`MaysFb^Mt~MpGGcuAd+bTKB^HyOO@1rZnfpaq&HJLlu9!x*nMT}7r3%HoJU~_UIwelUrB;}_dDpCOQO6@<7 z2C?9kad!QGQq|;BP^gl^DQ;+n&9mK!6bOmSw}22iQpS@Qf$_sDPG`?SML)`N6TXqc z$uWcwG>~c?1df3qf(?)YrBfcnL*#$RGKr?@B;Ftl-rHp8r*C{b#ko*H^x=sT{*#tudx}jnaafBFc2Y||u z3Kfb=r0=BvbxAFmU?f9AwiZhH?Aa(D`_Z zY%6NbfeHaZMp+ewjgb)SAf+R=z(I=_{L4NOeT72{V?$b@Fe)6mrbZBayqYtH!;M7k z|E>U}xBxmV0LpHK;+`Nl*YsxUppptRvx8+B{=*7=*O`!mGEEjJD>aSWL#oQ4#FMV? zKTQ1_Si2qIppo(gpwugWaUj9SAL6(0SNwn7Zm>YEWXE@q(~P7apcYL(U>F*zT!q{aZ-(umYUA)F?{At7KvbQ749St`B*(Z|0uU6Fz= zB*=W|u)jW}h>hw*{U7MVObBIu5|K&ZDX>edTM%wYJbyW)j&;Zuz!hJ$Yq57emZ8^IJ8{r|uO8(`)h=Ky$ zr7+R~`y*9X&1ME*Q<0j7|JB~r$Hlnj{jA0DbaHm7h7ikysOF)fNG)qHDk9aCUYt%; zBRZkz&GfMLteF#S4=w+Vo@Am9@_Vaw6{p|j; zpZ#b5rJ1?!>%Okv^?m!@m(Q15f*u4AH5@*Yy}G{>s0ocjVD^* z)6vAG@dyL6K+ZTTEo9|bcCm;Z%||f#*vh8EHQ>R<))$K)aLDX9 zGpA9IBnOr-3fymEz@$g`>jDv2rfVY&p5=b@>!|zQJc=@bRGW}qNHho#&;L|E%tz^o zX{&v%o?g5Aegs0!URU=eeLaJ4{r&%tK{KNaU*zEa`s+ftY5$tI;I|7{R z3j`WxI>R=j<3&HkSgvGKBoU>UhZikZ!$h%iuP?EBiSRKa<5Wqq%**5JYtTL~oU@rdN>m3QOrQH!Oc@BD?OFsNcS`5JgL zW1iSbIG9cOuoyuP40%#d51!}F{S8Br^oBsu7V!u&rp7(TvP})yqN{O+$^Pi%ow5A1 z#2>ZLQ@S;mP0$=GEW47wU6UjQ37bSnnbFvvVY^Ygi#p`RCr!s@iQlX!IQG$784pQo#k3f3%&&Sn#@m6M@gfbJ1 zG|D!Ace_Ty=G+_DLF^I-G^L`HlOx=NSDagXsI{k&?9cQMzt(k>fI9`n_UAUO+?OHW z$UF`xyUxC;=lbHCN7LR4wQDt)&{M{JVx0(2%DdVrzKeC_Az|n`UV6?d_!%ZuVP(at zmq)(=1^weTOsY~y#Rq{l+mYhl3bd0|wQ=?vY#xsx1msnY<4gPr9JxZAj$OImg~s(u zT?;cJl_7&~oxAI|jIn7NL})dj&=v-i!Tz*TYc$Z$!&AXbLh|_e?1-X0`8g4kEfPv!><7W#k|PQz@HuCAY02?wn`N`>BPG zNArz#Ph~#zcavMTC&C+>AQ@aB4n$<1Y)ldwd6MKMU3XT1mai*9XlDx2E~|%L9O?%j zkY6vQ-y5(ddcXTYI^Nsv4ST*6>49@7152SfRbb{IkuyQ9cJ?PdwM0vwo3CnUv&uJu zL+oC|nSdzV?9-$`|B^IID|QcZS_;AWdQccb*qv3EA050tSuODx>B^>Wo_;oa(yF8% zO+dH>k61(Rbg@yBqat|nkJV(8hV-T}itK?C?)uQr#8r@c1$PC(?*~yhl2;EkVqV92 zQX>#*!l{st!PN>%hKo+yBES(G^J^Ua{L6nM?CnDs5jcx{Z|1H-|DB)vr)`1%4&VJh z;k&<#wn@i(U*LZ8mhX+TiYLz|=Yeq7L0HD^sMJ@wt6^2RFcXOI;Rcb-RW6+Xc*BkVZL;!AdRW7*B1IF6)sSMw*w?B}fS&p?F9Q|_FB zTzrYuF@BF@?a&q!N#mmH;)uzKyzaU{xAiA(Cl6RZ7L5$4{m>}-w^yKR1jW#6o-_=?6O5-@YFFR201!j!?zhto`Pk9T+Ym9M6UktA3)AY zMX%K?t7BpA@QArN&5#m!5>UH+=1zDzR-c~^bX)>ScXIIO z{D98ne>f1WL*ynkIe7u5W5@ayAZ15F+St!mUscC0S7p3?3sBgVDZn)eph?WW;9g^F zYQwC@i)=gG-1Tnwd|ifXXPEvj>{k)gNn!Ko^xST6?uZcEiVe1R-kzT94ITv} zy9DQgtN^FKhyYh7qI^hg#*(iG!DI7wP4U|y((}<(2N&fKqNq`nX(VM1C@Zhzh45bC zN75S+=OdXl9atLaB-?`s{~P5LQpU&}=3+Hg_H7&+M5r7PAQtDO=cfp#-Ml;2FI7Dy zaH6rYVLk%ATR}Xi8;R+UflefA3rCWR%E+z9i{eS!qErd>bHR2xbn21ra?cIQ(BMeZ zx)S32pbeZALmpNWAqm7Fl&~B`^8jpw1SE-)1TO)nZ=v+;RXRA?0=lmN7Z@ecz)wS5 zAS}i>5a&TxNGn7U^7lzuAmtvNnxzoGib~{`kPeto0^GcsRO|DUeuH-`8^lZf?-I$E zq$DRNcaep_iX&B+WF#1a6_gUCrUir(JF6|5ymm&d#@gtFQv%J(`y{tP)rXdeT0VFEMhC5k=pyA=|cfw*@2t}1qg~A z9rm($hbzd3xONcHE*AARMWx`-KPNP8{^WT-VgeV3F^Vuk+N7*GtNapODFf(<9F4w2 z6DW;C_Pi%hp8~ zguV8174FcGJjDgPD)@A-K``8X#*bG-AZeM;+>(QnTXqA4dNJA|S+r;nAMV9OV1d6G zN;KpBvUtn!5R-8}Dcoajz|-ZAGGAQamtSBnQ2XbI99N|Jvc<-@T#H) zqq7sCods~(x7uq z(j<+aCQO-Q2jb0gXu&-%BdcivF93Ju6xj@YUAdCa-A<>)8(|B`R$QNyF>}D?W>{zJ zfQ>=I`%{y>i@);$r4td~i+YE}>>ZUd3?sl%Q=tmKfYT6*8e0s9AHMR4mrjbTMR{z{?;|{0|WV?j7=x)AW$;m|fSeILP4$t@7sCQPw#a~I-l#e0Ds#W#!2uimQn?PYhxGYo{1kng#o;M3x z)Z3=bTEWKIs%~B*Xwi-tUajp-ZKGZp`4z_}QFve!!sJs#_u>|wu~_jJIz}NZo`kHi ztM$63f}J>jIG*4Db}x6YPaW4wLtwgNPh5HOtU4{1$u!|F##DRvEOB}kOM zAwZ0L%lSFShF_7T6B81i_;rnOabJ@6r?`YuUDO2_XQE$rDiq3~p7S@wxzka;c+j`C z*WAeWUzI55SC8y>ZqF4cWjSvQqEp8#mWUS6BSIV7SrV5w%?;cM25^rWMe!XU&!~pa zP#3Mz@@qkWc32^`mynfx-Yger!=b2XpxW9P?bFQDg2<6TiANF}jG(?zvVdAji)u~P zG|neX)zQUP>dRdi=6Ny~R;nIPxzJqIbo}sL+;6svH$@f!Y+{kE%@y3reoZbuK*_?A&^9$)il+$wsv=sXZ zAqJiQctO2z9a%J&n$gWzdfT5m2avjclM=o42@cmhL_r2t(J=S4Y*W@EnkQlB;gr*Tfpf0-2ch4X{g@gAe+&i&wuS4{ zPbO06{_*2OjnD#}K14PgA8JyK2vr>D|GVB=miCRi{wHe9&zYYFyFHpv8mz-J;)bz( zM(-3pfln$-fOM15I7Y~inoM22o|ArwZJ){M0~79Z#gTHEeE@P6gsLInQJ1gIJav9= zM{HuyfKBPfirPD0NcpecL0%(MwV!F_E&5^{VPj*?-S_J$5ZP@KiWTkrO{D3;S0`C* z&_Z{$PP_}R2)d3(Uy*90DBER9OfEH5kt|AyLstL_@x@O#j z)@ATHuC?8TPybn8B+pbi43L-AnHIkn*q6Ah_Ub|Ga#E7(u#1)9!NY*9?n)2M) zZNH*K0|A9SA!DU7D1{WR9Y@^HsMf9=>3o{5)Px}z+VwsX7B*0E4q;?!5``+a0>?We zH0T>eSFyDROHL-82d@duQz22oWZuAg{{iH6FMyMC-T5gQ_Xlk3)`X<(x$G_Q>grh+ zYGVJ`YRiKE_=dBRL}%u z>!b6^k$g7wGF4hd1n?m2X%-$GZXo_ZzVC7jmnSM0n9ouX+T+qTp3xYufAmM8z2T-g z0UH3cu;zLprsd8=@<0wlU7J#hq>~Bi$w3C9g@Wo#h<6yTGvJS+)i$GXoX{6?tk(Df zT)P~AQVXbl8RhU~s%dz9+mxfy(bmNP*Qf*vpl|0ncuES)7X}UTL=YmpYIpmDPncRTg0FxUpoBL#fIC&)8j zMI*!}Is8pcc!vgL<$Zr+X4iLsw7F(oS1yb2r!3lEsqcxhgl0%8h=39u%4qWsfKt!G zZv<2!*OH9%E<+4n1ABYCmE&|w{+xgxf6K+zB8DiyqOqcP6}(1?ck}A0HrauF{zML@ zxE8*d188nf(Bvp&9oERW(Vm44XaJ_Jb_H|_S9-z>fJ6?2HDi$t|Ltl(LPH7)B}1#= zqNZS0g8FNl_rTtCB?*WNMkk`5CSSY?Z5FQ~1{u9X{g&mydpy{COFEX(fTa(z zLFY#mc2Z9?PA_HLi1B9={)Yn9gj&0a6R!4NSt8nVClIaOYH9`(hZq~=U)g6oZq+5N Vwknj&q8@NH9(U`8>+5$P|8L`Q?>Ybg diff --git a/tools/plot_swiss3dt_results.py b/tools/plot_swiss3dt_results.py index a55be87..3690df8 100644 --- a/tools/plot_swiss3dt_results.py +++ b/tools/plot_swiss3dt_results.py @@ -30,7 +30,11 @@ NEW_TAG = {'swiss-river-1990': 'swiss3dt-1990-20260612', 'swiss-river-2010': 'swiss3dt-2010-20260612', 'swiss-river-zurich': 'swiss3dt-zurich-20260612'} -OLD_TAG = 'swissriver-fullmatrix-20260511' # only swiss-river-1990 exists here +# The 2026-05-15 advisor slide's numbers come from this run (its onehot +# 1.1717 matches the slide's 1.171), NOT the earlier under-trained +# fullmatrix-0511 (none 3.88). Use it so the coordinate comparison is on the +# same footing as the slide. +OLD_TAG = 'swissriver-lstm-REAL-20260512-084128' # swiss-river-1990 only def rmse(tag: str, ds: str, mode: str) -> float | None: @@ -88,7 +92,7 @@ def rmse(tag: str, ds: str, mode: str) -> float | None: fig2, (axL, axR) = plt.subplots(1, 2, figsize=(8, 4.2)) for ax, (title, none_v, coord_v, sub) in zip( (axL, axR), - [('OLD (2026-05-11)\nzero-vector bug', old_none, old_coord, 'coord ≈ none (+%.1f%%)'), + [('OLD (2026-05-12)\nzero-vector bug', old_none, old_coord, 'coord worse than none (+%.1f%%)'), ('NEW (2026-06-13)\nreal + normalized', new_none, new_coord, 'coord beats none (%.1f%%)')], ): delta = (coord_v - none_v) / none_v * 100.0 @@ -96,11 +100,12 @@ def rmse(tag: str, ds: str, mode: str) -> float | None: color=['#9aa0a6', '#d93025'], edgecolor='white') ax.set_title(title, fontsize=10) ax.set_ylabel('Test RMSE (°C)') - ax.set_ylim(0, max(old_none, old_coord) * 1.15) - ax.text(0.5, 0.92, sub % delta, transform=ax.transAxes, ha='center', fontsize=9, + ax.set_ylim(0, max(none_v, coord_v) * 1.30) # headroom for the annotation + ax.text(0.5, 0.95, sub % delta, transform=ax.transAxes, ha='center', fontsize=9, color=('#d93025' if delta > 0 else '#188038')) for b, v in zip(bars, [none_v, coord_v]): - ax.text(b.get_x() + b.get_width() / 2, v + 0.03, f'{v:.3f}', ha='center', fontsize=8) + ax.text(b.get_x() + b.get_width() / 2, v + max(none_v, coord_v) * 0.02, + f'{v:.3f}', ha='center', fontsize=8) for sp in ('top', 'right'): ax.spines[sp].set_visible(False) fig2.suptitle('Coordinates identifier — swiss-river-1990 × LSTM', fontsize=11) From fdecec1f490e8fd82f7862234ded7d9e53e94346 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Sun, 14 Jun 2026 07:39:19 +0200 Subject: [PATCH 10/21] fix(mc): wire transparent identifiers + coords + NaN for swiss dlinear/patchtst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four multi_channel bugs surfaced by a smoke matrix (swiss × {dlinear, patchtst} × all modes), all fixed: - P1 transparent identifiers were a DEAD KNOB in mc: ChannelTransparentWrapper is only mounted by the adapter's _init_entity_support, but pipeline.build_model instantiates the model class directly (not via adapter) and only mounted the embedding wrapper. So onehot/sinusoidal/random/coordinates RMSE == none (this also invalidates the 2026-05 dlinear/patchtst transparent results). Fix: pipeline.build_model now mounts ChannelTransparentWrapper for mc + transparent (via _transparent_injection_kind), unifying with the per_entity EntityTransparentWrapper path; this also activates the previously-dead per-trial rebuild in experiment.py. - P2 coordinates injection was never wired (config['coordinates'] unset); now pulled from dataset.topology at mount time. - P3 mc swiss-river-2010/zurich were all-NaN (target _wt carries NaNs; feature _at is clean). Fix: nan_mask_loss:true in the swiss mc configs + _compute_metrics now masks NaN targets unconditionally (no-op on clean data). - P4 was a consequence of P1 (rebuild branch unreachable); now live. Hyperparameters (user decision 2026-06-14): new dlinear_swiss / patchtst_swiss spaces with batch_size FIXED at 32 (not tuned, matches swiss lstm) and patch_len/stride fixed 16/8; traffic/electricity keep the batch-tuned global spaces. matrix: coordinates restored for all swiss pairs. Verified: 36-cell smoke (2 models × 3 datasets × 6 modes) all PASS, 0 NaN, transparent ≠ none; mc sinusoidal_dim genuinely tuned per trial (4-trial HPO, channel_features width == sampled dim {8,20,23,25}); 493 guard tests green. Co-Authored-By: Claude Fable 5 --- experiments/entity_identifier/matrix.py | 90 ++++--------- experiments/swiss_river/dlinear_config.yaml | 1 + experiments/swiss_river/patchtst_config.yaml | 1 + liulian/optim/search_spaces.yaml | 21 +++ liulian/pipeline.py | 123 +++++++++++------- liulian/runtime/trainer.py | 11 ++ tests/e2e/test_dlinear_pipeline.py | 10 +- .../test_entity_identifier_pipeline.py | 12 +- 8 files changed, 150 insertions(+), 119 deletions(-) diff --git a/experiments/entity_identifier/matrix.py b/experiments/entity_identifier/matrix.py index 72e5b4f..48f158b 100644 --- a/experiments/entity_identifier/matrix.py +++ b/experiments/entity_identifier/matrix.py @@ -30,22 +30,23 @@ ) DEFAULT_SEEDS: tuple[int, ...] = (2026,) -_ALWAYS_SUPPORTED_MODES = frozenset( - {'none', 'embedding', 'onehot', 'sinusoidal', 'random'} -) +_ALWAYS_SUPPORTED_MODES = frozenset({'none', 'embedding', 'onehot', 'sinusoidal', 'random'}) # Modes needing extra information (e.g. geographic coordinates) are # restricted to pairs where that data is available. _EXTRA_MODES_BY_PAIR: dict[tuple[str, str], frozenset[str]] = { - # Coordinates live in the Swiss graph files. Offered ONLY for the - # per_entity path (LSTM) for now: the multi_channel wrapper needs - # config['coordinates'] injected, which the pipeline does not do yet. - # Before 2026-06-11 the multi_channel cells silently ran ZERO vectors - # (make_channel_features fallback) — those results are invalid, so the - # patchtst/dlinear coordinate cells are withdrawn until the injection - # fix lands. + # Coordinates live in the Swiss graph files. Available for ALL swiss + # model pairs since 2026-06-14: the multi_channel ChannelTransparentWrapper + # coordinate injection is now wired in pipeline.build_model (the earlier + # zero-vector cells are retracted). lstm uses the per_entity path. ('swiss-river-1990', 'lstm'): frozenset({'coordinates'}), ('swiss-river-2010', 'lstm'): frozenset({'coordinates'}), ('swiss-river-zurich', 'lstm'): frozenset({'coordinates'}), + ('swiss-river-1990', 'dlinear'): frozenset({'coordinates'}), + ('swiss-river-2010', 'dlinear'): frozenset({'coordinates'}), + ('swiss-river-zurich', 'dlinear'): frozenset({'coordinates'}), + ('swiss-river-1990', 'patchtst'): frozenset({'coordinates'}), + ('swiss-river-2010', 'patchtst'): frozenset({'coordinates'}), + ('swiss-river-zurich', 'patchtst'): frozenset({'coordinates'}), } @@ -53,28 +54,14 @@ # All Swiss variants share the same base configs — the `data` key is # overridden per job by build_cli_overrides(). ('swiss-river-1990', 'lstm'): Path('experiments/swiss_river/default_config.yaml'), - ('swiss-river-1990', 'patchtst'): Path( - 'experiments/swiss_river/patchtst_config.yaml' - ), - ('swiss-river-1990', 'dlinear'): Path( - 'experiments/swiss_river/dlinear_config.yaml' - ), + ('swiss-river-1990', 'patchtst'): Path('experiments/swiss_river/patchtst_config.yaml'), + ('swiss-river-1990', 'dlinear'): Path('experiments/swiss_river/dlinear_config.yaml'), ('swiss-river-2010', 'lstm'): Path('experiments/swiss_river/default_config.yaml'), - ('swiss-river-2010', 'patchtst'): Path( - 'experiments/swiss_river/patchtst_config.yaml' - ), - ('swiss-river-2010', 'dlinear'): Path( - 'experiments/swiss_river/dlinear_config.yaml' - ), - ('swiss-river-zurich', 'lstm'): Path( - 'experiments/swiss_river/default_config.yaml' - ), - ('swiss-river-zurich', 'patchtst'): Path( - 'experiments/swiss_river/patchtst_config.yaml' - ), - ('swiss-river-zurich', 'dlinear'): Path( - 'experiments/swiss_river/dlinear_config.yaml' - ), + ('swiss-river-2010', 'patchtst'): Path('experiments/swiss_river/patchtst_config.yaml'), + ('swiss-river-2010', 'dlinear'): Path('experiments/swiss_river/dlinear_config.yaml'), + ('swiss-river-zurich', 'lstm'): Path('experiments/swiss_river/default_config.yaml'), + ('swiss-river-zurich', 'patchtst'): Path('experiments/swiss_river/patchtst_config.yaml'), + ('swiss-river-zurich', 'dlinear'): Path('experiments/swiss_river/dlinear_config.yaml'), ('traffic', 'lstm'): Path('experiments/traffic/lstm_config.yaml'), ('traffic', 'patchtst'): Path('experiments/traffic/patchtst_config.yaml'), ('traffic', 'dlinear'): Path('experiments/traffic/dlinear_config.yaml'), @@ -98,18 +85,10 @@ TSL_REQUIRED_HPARAMS: dict[tuple[str, str], frozenset[str]] = { - ('traffic', 'patchtst'): frozenset( - {'batch_size', 'learning_rate', 'd_model', 'd_ff', 'n_heads', 'e_layers'} - ), - ('electricity', 'patchtst'): frozenset( - {'batch_size', 'learning_rate', 'd_model', 'd_ff', 'n_heads', 'e_layers'} - ), - ('traffic', 'dlinear'): frozenset( - {'batch_size', 'learning_rate', 'moving_avg'} - ), - ('electricity', 'dlinear'): frozenset( - {'batch_size', 'learning_rate', 'moving_avg'} - ), + ('traffic', 'patchtst'): frozenset({'batch_size', 'learning_rate', 'd_model', 'd_ff', 'n_heads', 'e_layers'}), + ('electricity', 'patchtst'): frozenset({'batch_size', 'learning_rate', 'd_model', 'd_ff', 'n_heads', 'e_layers'}), + ('traffic', 'dlinear'): frozenset({'batch_size', 'learning_rate', 'moving_avg'}), + ('electricity', 'dlinear'): frozenset({'batch_size', 'learning_rate', 'moving_avg'}), } @@ -187,9 +166,7 @@ def folder_name(self) -> str: @property def tsl_alignment(self) -> str: - return TSL_ALIGNMENT_STATUS.get( - (self.dataset, self.model), 'no canonical TSL comparison' - ) + return TSL_ALIGNMENT_STATUS.get((self.dataset, self.model), 'no canonical TSL comparison') def supported_modes_for_pair(dataset: str, model: str) -> frozenset[str]: @@ -261,10 +238,7 @@ def iter_jobs( ) ) if not jobs: - raise ValueError( - 'No applicable matrix jobs were generated for the given ' - 'datasets/models/modes filters.' - ) + raise ValueError('No applicable matrix jobs were generated for the given datasets/models/modes filters.') return jobs @@ -337,9 +311,7 @@ def build_cli_overrides( if job.model == 'patchtst': # Required by the approved design: embedding uses add_after_patch. - overrides['id_integration'] = ( - 'add_after_patch' if job.mode == 'embedding' else 'concat_to_x' - ) + overrides['id_integration'] = 'add_after_patch' if job.mode == 'embedding' else 'concat_to_x' elif job.mode in {'onehot', 'coordinates', 'sinusoidal', 'random'}: overrides['id_integration'] = 'concat_to_x' elif job.mode == 'embedding': @@ -361,14 +333,10 @@ def build_cli_overrides( if hpo: default_max_concurrent, default_gpu_per_trial = recommend_hpo_parallelism(job) overrides['hpo_max_concurrent'] = ( - int(hpo_max_concurrent) - if hpo_max_concurrent is not None - else default_max_concurrent + int(hpo_max_concurrent) if hpo_max_concurrent is not None else default_max_concurrent ) overrides['hpo_resources_gpu'] = ( - float(hpo_resources_gpu) - if hpo_resources_gpu is not None - else default_gpu_per_trial + float(hpo_resources_gpu) if hpo_resources_gpu is not None else default_gpu_per_trial ) return overrides @@ -438,7 +406,5 @@ def validate_tsl_hparam_coverage(config: dict[str, Any], job: MatrixJob) -> list keys = set(search_space.keys()) missing = sorted(required.difference(keys)) if missing: - raise ValueError( - f'Missing TSL-relevant hparams for {job.dataset}/{job.model}: {missing}' - ) + raise ValueError(f'Missing TSL-relevant hparams for {job.dataset}/{job.model}: {missing}') return sorted(required) diff --git a/experiments/swiss_river/dlinear_config.yaml b/experiments/swiss_river/dlinear_config.yaml index 67d4ca7..36f86fd 100644 --- a/experiments/swiss_river/dlinear_config.yaml +++ b/experiments/swiss_river/dlinear_config.yaml @@ -75,6 +75,7 @@ batch_size: 32 learning_rate: 0.0001 loss: mse metrics: rmse,mae,nse +nan_mask_loss: true # multi_channel: target NaNs (2010/zurich) excluded from loss; per_entity drops them in the data layer eval_denorm: true show_progress: true patience: 10 diff --git a/experiments/swiss_river/patchtst_config.yaml b/experiments/swiss_river/patchtst_config.yaml index 91583ba..f99ac35 100644 --- a/experiments/swiss_river/patchtst_config.yaml +++ b/experiments/swiss_river/patchtst_config.yaml @@ -74,6 +74,7 @@ batch_size: 32 learning_rate: 0.0001 loss: mse metrics: rmse,mae,nse +nan_mask_loss: true # multi_channel: target NaNs (2010/zurich) excluded from loss; per_entity drops them in the data layer eval_denorm: true show_progress: true patience: 10 diff --git a/liulian/optim/search_spaces.yaml b/liulian/optim/search_spaces.yaml index b45b16c..67048cf 100644 --- a/liulian/optim/search_spaces.yaml +++ b/liulian/optim/search_spaces.yaml @@ -43,6 +43,25 @@ model_spaces: e_layers: {dist: randint, low: 1, high: 4} dropout: {dist: choice, values: [0.0, 0.1, 0.2, 0.3]} + # DLinear on swiss-river (multi_channel). batch_size FIXED at 32 (user + # decision 2026-06-14: match the swiss lstm so the cross-model comparison + # is clean) -> NOT in the space, taken from the config. DLinear is a pure + # linear decomposition model: moving_avg (decomp kernel) is its only + # architecture knob (no dropout / d_model / attention). + dlinear_swiss: + learning_rate: {dist: loguniform, low: 0.0001, high: 0.005} + moving_avg: {dist: choice, values: [13, 25, 51]} + + # PatchTST on swiss-river (multi_channel). batch_size FIXED at 32 (same + # reason); patch_len/stride FIXED at 16/8 (TSL standard, not tuned). + patchtst_swiss: + learning_rate: {dist: loguniform, low: 0.0001, high: 0.001} + d_model: {dist: choice, values: [128, 256, 512]} + d_ff: {dist: choice, values: [256, 512, 2048]} + n_heads: {dist: choice, values: [2, 4, 8, 16]} + e_layers: {dist: choice, values: [1, 2, 3]} + dropout: {dist: choice, values: [0.1, 0.2]} + # DLinear — must EXACTLY mirror dlinear_space() to preserve anchored baselines. # batch_size stays TUNED here: the 2026-05 traffic/electricity baselines were # produced with it, and TSL_REQUIRED_HPARAMS (matrix.py) enforces coverage. @@ -90,6 +109,8 @@ identifier_spaces: # --------------------------------------------------------------------------- # resolution: - {data: swiss-river, model: lstm, space: lstm_swiss} + - {data: swiss-river, model: dlinear, space: dlinear_swiss} + - {data: swiss-river, model: patchtst, space: patchtst_swiss} - {model: lstm, space: lstm_general} - {model: dlinear, space: dlinear} - {model: patchtst, space: patchtst} diff --git a/liulian/pipeline.py b/liulian/pipeline.py index 159c225..73b01c7 100644 --- a/liulian/pipeline.py +++ b/liulian/pipeline.py @@ -325,26 +325,33 @@ def print_dataset_summary(dataset: Any) -> None: _MODEL_INJECTABLE_TRANSPARENT_MODES = frozenset({'onehot', 'coordinates', 'sinusoidal', 'random', 'numeric_id'}) -def _uses_model_transparent_injection(config: Dict[str, Any]) -> bool: - """True when transparent identifiers are injected by the MODEL wrapper. +def _transparent_injection_kind(config: Dict[str, Any]) -> str | None: + """Where the MODEL wrapper injects transparent identifiers, or ``None``. - ``id_injection='model'`` + a transparent mode + non-multi_channel split: - the dataset emits only base features and ``EntityTransparentWrapper`` - concatenates the fixed per-station block per batch (rebuilt per HPO - trial). multi_channel keeps its own ChannelTransparentWrapper path. + With ``id_injection='model'`` + a transparent mode the dataset emits only + base features and a model-layer wrapper adds the fixed per-station block, + rebuilt per HPO trial. The wrapper differs by split: + + * ``'per_entity'`` — ``EntityTransparentWrapper`` CONCATENATES the + block, so ``enc_in`` widens by the feature dim. + * ``'multi_channel'`` — ``ChannelTransparentWrapper`` fuses a per-channel + block and projects back to one value per channel, so ``enc_in`` (the + station-channel count) is UNCHANGED. + + Returns ``'per_entity'`` / ``'multi_channel'`` / ``None``. """ mode = str(config.get('identifier_mode', 'none')).strip().lower() if str(config.get('id_injection', 'data')).strip().lower() != 'model': - return False - if config.get('split_mode') == 'multi_channel': - return False - if mode == 'descriptors': - raise ValueError( - "id_injection='model' does not support identifier_mode=" - "'descriptors' yet (no descriptor source is plumbed) — use " - "id_injection='data'." - ) - return mode in _MODEL_INJECTABLE_TRANSPARENT_MODES + return None + if mode not in _MODEL_INJECTABLE_TRANSPARENT_MODES: + if mode == 'descriptors': + raise ValueError( + "id_injection='model' does not support identifier_mode=" + "'descriptors' yet (no descriptor source is plumbed) — use " + "id_injection='data'." + ) + return None + return 'multi_channel' if config.get('split_mode') == 'multi_channel' else 'per_entity' def _transparent_feature_dim(config: Dict[str, Any], dataset: Any) -> int: @@ -421,19 +428,19 @@ def build_model(config: Dict[str, Any], dataset: Any = None) -> Any: model_name = config['model'] # Auto-detect enc_in from the training split's feature dimension. - _mi_transparent_dim = 0 + _inj_kind = _transparent_injection_kind(config) + _pe_transparent_dim = 0 # per_entity only — widens enc_in if config.get('enc_in') is None and dataset is not None: config['enc_in'] = auto_detect_enc_in(dataset) - # Model-layer transparent injection: the dataset emits ONLY base - # features, but the inner model consumes the wrapper's concatenated - # input — widen enc_in by the identifier dim (keeps enc_in semantics - # identical to the data-layer bake-in path). - if _uses_model_transparent_injection(config): - _mi_transparent_dim = _transparent_feature_dim(config, dataset) - config['enc_in'] = int(config['enc_in']) + _mi_transparent_dim + # per_entity model-layer injection CONCATENATES the identifier block, + # so the inner model needs a wider enc_in. multi_channel keeps enc_in + # (ChannelTransparentWrapper projects each channel back to one value). + if _inj_kind == 'per_entity': + _pe_transparent_dim = _transparent_feature_dim(config, dataset) + config['enc_in'] = int(config['enc_in']) + _pe_transparent_dim logger.info('Auto-detected enc_in=%d from training data', config['enc_in']) - elif _uses_model_transparent_injection(config): - _mi_transparent_dim = _transparent_feature_dim(config, dataset) + elif _inj_kind == 'per_entity': + _pe_transparent_dim = _transparent_feature_dim(config, dataset) # ── Auto-detect c_out / dec_in based on features mode ─────────────── # @@ -544,32 +551,54 @@ def build_model(config: Dict[str, Any], dataset: Any = None) -> Any: emb_size, ) - # Model-layer transparent injection (id_injection='model'): wrap with the - # per-sample fixed-feature injector. The wrapper holds NO learnable - # parameters; the inner model was built with enc_in = base + feature_dim. - if _mi_transparent_dim: - from liulian.models.torch.entity_mixin import EntityTransparentWrapper - + # Model-layer transparent injection (id_injection='model'). Two wrappers, + # split-dependent (see _transparent_injection_kind): per_entity CONCATS a + # zero-parameter block (enc_in already widened above); multi_channel fuses + # a per-channel block via a small Linear(1+D,1) projection (enc_in kept). + # Both pull station coords from dataset.topology — this also wires the + # previously-missing coordinates injection for the channel path. + if _inj_kind is not None: _station_ids = [str(s) for s in getattr(dataset, 'station_ids', [])] if not _station_ids: raise ValueError("id_injection='model' requires dataset.station_ids for the transparent feature table.") _topo = getattr(dataset, 'topology', None) _coords = getattr(_topo, 'coordinates', None) if _topo is not None else None - model = EntityTransparentWrapper( - inner_model=model, - mode=config['identifier_mode'], - station_ids=_station_ids, - coordinates=_coords, - sinusoidal_dim=int(config.get('sinusoidal_dim', 16)), - random_dim=int(config.get('random_identifier_dim', 16)), - random_seed=int(config.get('random_identifier_seed', 2026)), - ) - logger.info( - 'EntityTransparentWrapper: mode=%s, stations=%d, feature_dim=%d', - config['identifier_mode'], - len(_station_ids), - model.feature_dim, - ) + if _inj_kind == 'per_entity': + from liulian.models.torch.entity_mixin import EntityTransparentWrapper + + model = EntityTransparentWrapper( + inner_model=model, + mode=config['identifier_mode'], + station_ids=_station_ids, + coordinates=_coords, + sinusoidal_dim=int(config.get('sinusoidal_dim', 16)), + random_dim=int(config.get('random_identifier_dim', 16)), + random_seed=int(config.get('random_identifier_seed', 2026)), + ) + logger.info( + 'EntityTransparentWrapper: mode=%s, stations=%d, feature_dim=%d', + config['identifier_mode'], + len(_station_ids), + model.feature_dim, + ) + else: # multi_channel + from liulian.models.torch.entity_mixin import ChannelTransparentWrapper + + model = ChannelTransparentWrapper( + inner_model=model, + mode=config['identifier_mode'], + num_stations=len(_station_ids), + sinusoidal_dim=int(config.get('sinusoidal_dim', 16)), + random_dim=int(config.get('random_identifier_dim', 16)), + random_seed=int(config.get('random_identifier_seed', 2026)), + coordinates=_coords, + station_ids=_station_ids, + ) + logger.info( + 'ChannelTransparentWrapper: mode=%s, channels=%d', + config['identifier_mode'], + len(_station_ids), + ) n_params = sum(p.numel() for p in model.parameters()) logger.info('Model: %s (%.1fK params)', model_name, n_params / 1e3) diff --git a/liulian/runtime/trainer.py b/liulian/runtime/trainer.py index 7f7b0b5..e15f141 100644 --- a/liulian/runtime/trainer.py +++ b/liulian/runtime/trainer.py @@ -817,6 +817,17 @@ def _compute_metrics( true: torch.Tensor, metric_names: List[str], ) -> Dict[str, float]: + # Exclude NaN targets unconditionally — a no-op for clean data (mask + # all-True), but required for multi_channel swiss-river-2010/zurich + # whose target CSVs carry NaNs (stations join/leave the network). + # Without this every metric is NaN even when nan_mask_loss trains fine. + mask = ~torch.isnan(true) + if not bool(mask.all()): + pred = pred[mask] + true = true[mask] + if true.numel() == 0: + nan = float('nan') + return {name: nan for name in metric_names} mse = torch.mean((pred - true) ** 2) mae = torch.mean(torch.abs(pred - true)) rmse = torch.sqrt(mse + 1e-12) diff --git a/tests/e2e/test_dlinear_pipeline.py b/tests/e2e/test_dlinear_pipeline.py index 930548b..81382c1 100644 --- a/tests/e2e/test_dlinear_pipeline.py +++ b/tests/e2e/test_dlinear_pipeline.py @@ -253,8 +253,9 @@ class TestModelAwareSearchSpace: """Default HPO search space adapts to model type.""" def test_dlinear_search_space(self): - """DLinear search space should have learning_rate, batch_size, moving_avg - but NOT embedding_size or d_model.""" + """Swiss DLinear search space: learning_rate + moving_avg, with + batch_size FIXED at 32 (user decision 2026-06-14, not tuned) and no + LSTM/PatchTST-specific params.""" from liulian.pipeline import build_optimizer cfg = { @@ -286,9 +287,10 @@ def test_dlinear_search_space(self): space = cfg['search_space'] assert 'learning_rate' in space - assert 'batch_size' in space assert 'moving_avg' in space - # Should NOT have LSTM-specific params + # batch_size FIXED at 32 for swiss (dlinear_swiss space) -> not tuned + assert 'batch_size' not in space + # Should NOT have LSTM/PatchTST-specific params assert 'embedding_size' not in space assert 'd_model' not in space diff --git a/tests/runtime/test_entity_identifier_pipeline.py b/tests/runtime/test_entity_identifier_pipeline.py index a40f7cc..c564845 100644 --- a/tests/runtime/test_entity_identifier_pipeline.py +++ b/tests/runtime/test_entity_identifier_pipeline.py @@ -38,14 +38,14 @@ def test_iter_jobs_default_matrix_size() -> None: jobs = iter_jobs() # 15 pairs (5 datasets x 3 models) x (none + embedding) = 30 baseline jobs # + 3 universally-supported transparent modes (onehot, sinusoidal, random) x 15 pairs = 45 - # + coordinates for the 3 Swiss LSTM pairs (per_entity path only) = 3 - # Total = 30 + 45 + 3 = 78 - assert len(jobs) == 78 + # + coordinates for all 9 Swiss pairs (3 datasets x {lstm, dlinear, patchtst}) = 9 + # Total = 30 + 45 + 9 = 84 + assert len(jobs) == 84 coord_jobs = [job for job in jobs if job.mode == 'coordinates'] - # Coordinates only available for Swiss River + LSTM (per_entity) pairs - assert len(coord_jobs) == 3 + # Coordinates available for every Swiss River pair (wired in mc since 2026-06-14) + assert len(coord_jobs) == 9 assert all(job.dataset.startswith('swiss-river') for job in coord_jobs) - assert all(job.model == 'lstm' for job in coord_jobs) + assert {job.model for job in coord_jobs} == {'lstm', 'dlinear', 'patchtst'} # All pairs get onehot, sinusoidal, random onehot_datasets = {job.dataset for job in jobs if job.mode == 'onehot'} assert onehot_datasets == { From 82f090302c557244c9546a98666f9ea7eed45e53 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Sun, 14 Jun 2026 14:17:41 +0200 Subject: [PATCH 11/21] fix(mc): rebuild ChannelTransparentWrapper from the model, not config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mc HPO rebuild read coordinates/station_ids from config, but those are never written there (pipeline pulls them from dataset.topology). So every trial rebuilt with integer station ids: harmless for onehot/sinusoidal (index-based) but the coordinates mode raised "missing coordinate for ['0','1',...]" and ALL 6 swiss mc coordinate cells failed in the full run (smoke missed it — smoke runs hpo=False, skipping the rebuild path). Fix mirrors the per_entity EntityTransparentWrapper: ChannelTransparentWrapper now stores mode/station_ids/coordinates/dims/seed, and experiment.py captures them from the model instance for the per-trial factory. Verified: coordinates survives a real 3-trial HPO rebuild (rmse 1.3327, no raise); 74 guard tests green. Co-Authored-By: Claude Fable 5 --- liulian/models/torch/entity_mixin.py | 11 +++++++++++ liulian/runtime/experiment.py | 14 ++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/liulian/models/torch/entity_mixin.py b/liulian/models/torch/entity_mixin.py index 9825061..aa9d2fb 100644 --- a/liulian/models/torch/entity_mixin.py +++ b/liulian/models/torch/entity_mixin.py @@ -538,6 +538,17 @@ def __init__( ) -> None: super().__init__() self.inner = inner_model + # Stored so an HPO trial can rebuild this wrapper faithfully from the + # model instance (NOT from config — coordinates/station_ids are never + # written to config). Mirrors EntityTransparentWrapper; without this + # the rebuild fell back to integer station ids and the coordinates + # mode raised "missing coordinate for ['0','1',...]" (fixed 2026-06-14). + self.mode = str(mode) + self.station_ids = [str(s) for s in station_ids] if station_ids else None + self._coordinates = coordinates + self.sinusoidal_dim = int(sinusoidal_dim) + self.random_dim = int(random_dim) + self.random_seed = int(random_seed) features = _build_channel_features( mode, num_stations, diff --git a/liulian/runtime/experiment.py b/liulian/runtime/experiment.py index afb091a..505eb67 100644 --- a/liulian/runtime/experiment.py +++ b/liulian/runtime/experiment.py @@ -410,7 +410,13 @@ def _run_torch( inner_model_cls = type(torch_model.inner) model_args = getattr(torch_model.inner, '_args', None) _tw_num_stations = int(torch_model.channel_features.shape[0]) - _tw_mode = str(self.config.get('identifier_mode', 'none')) + _tw_mode = str(torch_model.mode) + # From the model instance, NOT config (coordinates/station_ids + # are never in config) — otherwise the rebuild falls back to + # integer ids and coordinates raises. + _tw_station_ids = torch_model.station_ids + _tw_coords = torch_model._coordinates + _tw_seed = int(torch_model.random_seed) elif is_entity_wrapped: inner_model_cls = type(torch_model.inner) model_args = getattr(torch_model.inner, '_args', None) @@ -514,9 +520,9 @@ def _model_factory(args): self.config.get('random_identifier_dim', 16), ) ), - random_seed=int(self.config.get('random_identifier_seed', 2026)), - coordinates=self.config.get('coordinates'), - station_ids=self.config.get('station_ids'), + random_seed=_tw_seed, + coordinates=_tw_coords, + station_ids=_tw_station_ids, ) else: _model_factory = None From 59f44bbda8c5a4b58b89cab2ded5bfcb44e56c48 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Sun, 14 Jun 2026 15:34:27 +0200 Subject: [PATCH 12/21] docs(research): swiss mc dlinear/patchtst results + research-critic audit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 36-cell multi_channel results (read from real results.json). Audit-corrected framing: DLinear shows no measurable identifier effect (flat, val mirrors test); on PatchTST the INJECTION POINT drives it — add_after_patch embedding helps (~-5%), pre-patch concat_to_x transparent fusion hurts (+30-60%). NOT claimed: "transparent harmful" (embedding is also an identifier and helps) or "DLinear immune" (can't separate model from projection). Key caveat: mc transparent results reflect ChannelTransparentWrapper's fusion projection, not intrinsic identifier value. Contrast table vs per_entity LSTM notes the split-mode confound. Follow-up ablations listed. Co-Authored-By: Claude Fable 5 --- docs/research/2026-06-14-swiss-mc-results.md | 92 ++++++++++++++++++ .../swiss-mc-rmse-by-mode.png | Bin 0 -> 71184 bytes .../swiss-mc-2026-06-14/swiss-mc-rmse.csv | 7 ++ tools/plot_swiss_mc_results.py | 80 +++++++++++++++ 4 files changed, 179 insertions(+) create mode 100644 docs/research/2026-06-14-swiss-mc-results.md create mode 100644 docs/research/figures/swiss-mc-2026-06-14/swiss-mc-rmse-by-mode.png create mode 100644 docs/research/figures/swiss-mc-2026-06-14/swiss-mc-rmse.csv create mode 100644 tools/plot_swiss_mc_results.py diff --git a/docs/research/2026-06-14-swiss-mc-results.md b/docs/research/2026-06-14-swiss-mc-results.md new file mode 100644 index 0000000..22d9014 --- /dev/null +++ b/docs/research/2026-06-14-swiss-mc-results.md @@ -0,0 +1,92 @@ +# swiss-river × {DLinear, PatchTST} entity-identifier results (multi_channel, 2026-06-14) + +DLinear and PatchTST on the three Swiss datasets in **multi_channel** split +(each station is a channel — identity is already implicit in the channel +layout, unlike the per_entity LSTM where the identifier is the *only* +identity signal). Single seed 2026, 50-trial HPO per cell, batch_size fixed +32, 30 epochs. Numbers read from real `results.json` by +`tools/plot_swiss_mc_results.py` (no hand-copied values); +CSV: `figures/swiss-mc-2026-06-14/swiss-mc-rmse.csv`. + +> These results only became measurable after fixing four multi_channel bugs +> on 2026-06-14 (commits `fdecec1`, `82f0903`): before that, the transparent +> identifier modes were a silent no-op (== none) and the 2010/zurich cells +> were all-NaN. The 2026-05 dlinear/patchtst transparent results are +> **retracted** (they measured the no-op). + +## Results — test RMSE (°C, denormalized) + +![RMSE by mode](figures/swiss-mc-2026-06-14/swiss-mc-rmse-by-mode.png) + +| model | dataset | none | embedding | onehot | random | sinusoidal | coordinates | +|---|---|---|---|---|---|---|---| +| DLinear | 1990 | 1.281 | 1.286 | 1.286 | 1.283 | 1.282 | 1.279 | +| DLinear | 2010 | 1.355 | 1.356 | 1.356 | 1.361 | 1.358 | 1.357 | +| DLinear | zurich | 1.391 | 1.401 | 1.396 | 1.393 | 1.393 | 1.385 | +| PatchTST | 1990 | 1.374 | **1.304** | 2.189 | 2.059 | 2.108 | 1.815 | +| PatchTST | 2010 | 1.488 | **1.387** | 2.505 | 2.345 | 2.340 | 2.036 | +| PatchTST | zurich | 1.480 | **1.388** | 2.738 | 2.631 | 2.690 | 2.377 | + +## What we can and cannot claim (research-critic audit) + +**Defensible (single seed; val mirrors test, so not overfitting):** + +- **DLinear shows no measurable identifier effect** on swiss multi_channel: + all six modes land within ~1% of `none` on every dataset (HPO val is + equally flat, e.g. 1990 val 0.1103–0.1105 across modes). A linear + decomposition model over channels has no head-room to exploit per-channel + identity, and the wrapper's fusion projection adds none. +- **On PatchTST, the injection POINT — not the identifier type — drives the + result.** `embedding` via `add_after_patch` (injected *after* patch + embedding) helps (~−5% vs none). The transparent modes, injected via + `concat_to_x` + `ChannelTransparentWrapper`'s per-channel + `Linear(1+D,1)` fusion *before* patching, hurt substantially (+30–60%), + and they hurt in HPO val too (1990 onehot val 0.231 vs none 0.124). + +**NOT claimed (would over-reach):** + +- *"Transparent identifiers are harmful."* Too broad — `embedding` is also + an identifier and it helps. What hurts on PatchTST is the **pre-patch + `concat_to_x` fusion**, which perturbs the per-channel series that + PatchTST then slices into patch tokens. `add_after_patch` avoids this. +- *"DLinear is immune to identity."* We observe no effect; we cannot tell + apart "the linear model can't use identity" from "the fusion projection + discards it" without inspecting the projection weights. +- Anything cross-domain — all three datasets are Swiss water temperature. + +**Key caveat (read before citing):** the multi_channel transparent results +reflect the behaviour of **`ChannelTransparentWrapper`** (concat + learnable +per-channel fusion projection), *not* the intrinsic value of the identifier +information. This wrapper is structurally different from the per_entity +`EntityTransparentWrapper` (zero-parameter concat). Cross-split comparisons +must account for this. + +## Contrast with the per_entity LSTM (2026-06-13) + +| split | model | identifier effect | +|---|---|---| +| per_entity | LSTM | every identifier helps (−25 to −35% vs none) — identity is the ONLY identity signal | +| multi_channel | DLinear | none (≈ flat) — identity already in the channel layout | +| multi_channel | PatchTST | `add_after_patch` embedding helps; pre-patch transparent injection hurts | + +The dominant variable across these is the **split mode**, not the model: +per_entity makes each sample identity-blind (so the identifier is decisive), +while multi_channel already encodes identity in the channel index (so an +extra identifier is at best redundant, at worst disruptive to the +architecture). This matches the split-mode confound flagged in the 2026-05 +report. + +## To harden / explain (follow-ups) + +- **Ablation isolating injection point from identifier type**: run PatchTST + + transparent + `add_after_patch` — if it stops hurting, the cause is the + pre-patch fusion, confirming the audit's reading. +- **Inspect the DLinear fusion-projection weights** to tell "can't use" from + "discards". +- **Multi-seed** (≥3) for variance bands (shared with the lstm follow-up #32). + +## Reproduce + +```bash +python tools/plot_swiss_mc_results.py +``` diff --git a/docs/research/figures/swiss-mc-2026-06-14/swiss-mc-rmse-by-mode.png b/docs/research/figures/swiss-mc-2026-06-14/swiss-mc-rmse-by-mode.png new file mode 100644 index 0000000000000000000000000000000000000000..36a85f16109ca2763bd89b32f7b9641c6c0814e6 GIT binary patch literal 71184 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 R=5wTr;Uil0@E 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 literal 0 HcmV?d00001 diff --git a/docs/research/figures/swiss-mc-2026-06-14/swiss-mc-rmse.csv b/docs/research/figures/swiss-mc-2026-06-14/swiss-mc-rmse.csv new file mode 100644 index 0000000..342cfa5 --- /dev/null +++ b/docs/research/figures/swiss-mc-2026-06-14/swiss-mc-rmse.csv @@ -0,0 +1,7 @@ +model,dataset,none,embedding,onehot,random,sinusoidal,coordinates +dlinear,swiss-river-1990,1.2814,1.2860,1.2860,1.2827,1.2817,1.2790 +dlinear,swiss-river-2010,1.3545,1.3560,1.3558,1.3606,1.3576,1.3572 +dlinear,swiss-river-zurich,1.3905,1.4008,1.3964,1.3927,1.3933,1.3851 +patchtst,swiss-river-1990,1.3737,1.3035,2.1889,2.0590,2.1075,1.8148 +patchtst,swiss-river-2010,1.4884,1.3865,2.5051,2.3452,2.3402,2.0356 +patchtst,swiss-river-zurich,1.4803,1.3884,2.7380,2.6311,2.6900,2.3773 diff --git a/tools/plot_swiss_mc_results.py b/tools/plot_swiss_mc_results.py new file mode 100644 index 0000000..0ef0977 --- /dev/null +++ b/tools/plot_swiss_mc_results.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +"""Plot swiss multi_channel (dlinear/patchtst) entity-identifier results. + +Reads denorm test RMSE from real results.json (no hardcoded numbers) and +emits one grouped-bar figure per model (3 datasets × 6 modes) with the +`none` baseline drawn as a reference line. Also writes a CSV. +""" +from __future__ import annotations + +import csv +import glob +import json +from pathlib import Path + +import matplotlib + +matplotlib.use('Agg') +import matplotlib.pyplot as plt + +ROOT = Path(__file__).resolve().parents[1] +ART = ROOT / 'artifacts' / 'entity_identifier' +OUT = ROOT / 'docs' / 'research' / 'figures' / 'swiss-mc-2026-06-14' +OUT.mkdir(parents=True, exist_ok=True) + +DATASETS = ['swiss-river-1990', 'swiss-river-2010', 'swiss-river-zurich'] +MODELS = ['dlinear', 'patchtst'] +MODES = ['none', 'embedding', 'onehot', 'random', 'sinusoidal', 'coordinates'] +TAG = {d: f'swiss-mc-{d.split("-")[-1]}-20260614' for d in DATASETS} +SHORT = {'swiss-river-1990': '1990 (28)', 'swiss-river-2010': '2010 (63)', + 'swiss-river-zurich': 'zurich (15)'} +COLORS = {'none': '#9aa0a6', 'embedding': '#1a73e8', 'onehot': '#34a853', + 'random': '#a142f4', 'sinusoidal': '#ea8600', 'coordinates': '#d93025'} + + +def rmse(tag, ds, model, mode): + hits = glob.glob(f'{ART}/{tag}/{ds}-{model}-{mode}-*/**/results.json', recursive=True) + if not hits: + return None + m = json.load(open(hits[0]))['metrics']['test'] + return float(m.get('denorm_rmse', m.get('rmse'))) + + +data = {model: {ds: {mode: rmse(TAG[ds], ds, model, mode) for mode in MODES} + for ds in DATASETS} for model in MODELS} + +with open(OUT / 'swiss-mc-rmse.csv', 'w', newline='') as fh: + w = csv.writer(fh) + w.writerow(['model', 'dataset', *MODES]) + for model in MODELS: + for ds in DATASETS: + cells = data[model][ds] + row = [f'{cells[m]:.4f}' if cells[m] is not None else '' for m in MODES] + w.writerow([model, ds, *row]) +print('wrote', OUT / 'swiss-mc-rmse.csv') + +fig, axes = plt.subplots(1, 2, figsize=(13, 4.8), sharey=False) +for ax, model in zip(axes, MODELS): + n = len(MODES) + bw = 0.82 / n + for j, mode in enumerate(MODES): + xs = [i + (j - n / 2 + 0.5) * bw for i in range(len(DATASETS))] + ys = [data[model][ds][mode] for ds in DATASETS] + ax.bar(xs, ys, bw, label=mode, color=COLORS[mode], edgecolor='white', linewidth=0.5) + # none reference line per dataset (thin) — visual anchor + for i, ds in enumerate(DATASETS): + nb = data[model][ds]['none'] + ax.plot([i - 0.45, i + 0.45], [nb, nb], color='#444', lw=0.8, ls='--', alpha=0.6) + ax.set_xticks(range(len(DATASETS))) + ax.set_xticklabels([SHORT[d] for d in DATASETS]) + ax.set_title(f'{model} (multi_channel)') + ax.set_ylabel('Test RMSE (°C)') + ax.grid(axis='y', alpha=0.3) + for sp in ('top', 'right'): + ax.spines[sp].set_visible(False) +axes[0].legend(ncol=6, fontsize=8, loc='upper center', bbox_to_anchor=(1.05, -0.12), frameon=False) +fig.suptitle('swiss-river × {dlinear, patchtst}: entity-identifier modes ' + '(single seed 2026, 50-trial HPO; dashed = none baseline)', fontsize=11) +fig.tight_layout() +fig.savefig(OUT / 'swiss-mc-rmse-by-mode.png', dpi=150, bbox_inches='tight') +print('wrote', OUT / 'swiss-mc-rmse-by-mode.png') From 20986f87df86f26eab2bf9069b146d47337b1977 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Sun, 14 Jun 2026 20:47:49 +0200 Subject: [PATCH 13/21] perf(mc): algebraic ChannelTransparentWrapper._augment (fixes onehot OOM) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fusion projection was applied to a materialized (B,T,N,1+D) tensor, which is O(B·T·N²) for onehot (D=N) and OOMed on big multi_channel datasets (traffic N=862 tried to allocate ~9 GiB; smoke FAILed). Since the projection is linear, proj(cat([x, feats])) == w_val*x + (feats@w_feat + bias) per channel — O(B·T·N + N·D), no 4D materialization. Numerically identical (verified, max diff ~1e-7). Adds ChannelTransparentWrapper tests: algebraic == materializing reference for all transparent modes, and onehot at N=800 runs without the N² blowup. Verified: traffic+electricity × {lstm,dlinear,patchtst} × 5 modes smoke all PASS (onehot no longer OOMs). Co-Authored-By: Claude Fable 5 --- liulian/models/torch/entity_mixin.py | 20 +++++-- .../test_transparent_injection_equivalence.py | 57 +++++++++++++++++++ 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/liulian/models/torch/entity_mixin.py b/liulian/models/torch/entity_mixin.py index aa9d2fb..9d16c37 100644 --- a/liulian/models/torch/entity_mixin.py +++ b/liulian/models/torch/entity_mixin.py @@ -564,12 +564,20 @@ def __init__( self.dec_proj = nn.Linear(1 + feat_dim, 1) def _augment(self, x: torch.Tensor, proj: nn.Linear) -> torch.Tensor: - B, T, N = x.shape - feats = self.channel_features # (N, D) - feats = feats.unsqueeze(0).unsqueeze(0).expand(B, T, -1, -1) # (B,T,N,D) - x_4d = x.unsqueeze(-1) # (B, T, N, 1) - augmented = torch.cat([x_4d, feats], dim=-1) # (B, T, N, 1+D) - return proj(augmented).squeeze(-1) # (B, T, N) + # proj(cat([x, feats])) is LINEAR, so it equals + # w_val * x + (feats @ w_feat + bias) [per channel], + # which avoids ever materializing the (B, T, N, 1+D) tensor. That + # materialization is O(B·T·N²) for onehot (D=N) and OOMs on big mc + # datasets (traffic N=862 → ~9 GiB). This form is O(B·T·N + N·D) and + # is numerically identical (verified in tests). + w = proj.weight # (1, 1+D) + w_val = w[0, 0] + w_feat = w[0, 1:] # (D,) + feats = self.channel_features.to(x.dtype) # (N, D) + chan_offset = feats @ w_feat.to(x.dtype) # (N,) + if proj.bias is not None: + chan_offset = chan_offset + proj.bias # (N,) + return w_val * x + chan_offset # (B, T, N), chan_offset broadcasts over N def forward( self, diff --git a/tests/models/torch/test_transparent_injection_equivalence.py b/tests/models/torch/test_transparent_injection_equivalence.py index 52cca2d..66dcd7a 100644 --- a/tests/models/torch/test_transparent_injection_equivalence.py +++ b/tests/models/torch/test_transparent_injection_equivalence.py @@ -134,3 +134,60 @@ def test_swiss_dataset_both_injection_paths_identical(mode: str) -> None: w(base_x.unsqueeze(0), entity_ids=torch.tensor([sid_to_idx[str(eid)]])) assert w.inner.last_x is not None assert torch.equal(w.inner.last_x[0], baked_x) + + +# --------------------------------------------------------------------------- # +# ChannelTransparentWrapper (multi_channel) — algebraic _augment correctness # +# --------------------------------------------------------------------------- # +from liulian.models.torch.entity_mixin import ChannelTransparentWrapper # noqa: E402 + + +class _IdentInner(nn.Module): + """Returns x_enc unchanged so tests can read the augmented tensor.""" + + def forward(self, x_enc, x_mark_enc=None, x_dec=None, x_mark_dec=None, mask=None): + return x_enc + + +def _materializing_augment(x, proj, feats): + """The pre-2026-06-14 O(B·T·N²) reference implementation.""" + B, T, N = x.shape + f = feats.unsqueeze(0).unsqueeze(0).expand(B, T, -1, -1) + aug = torch.cat([x.unsqueeze(-1), f], dim=-1) + return proj(aug).squeeze(-1) + + +@pytest.mark.parametrize('mode', ['onehot', 'sinusoidal', 'random', 'coordinates']) +def test_channel_augment_matches_materializing_reference(mode: str) -> None: + """The algebraic _augment must equal the old materializing form.""" + torch.manual_seed(0) + w = ChannelTransparentWrapper( + _IdentInner(), + mode, + num_stations=len(IDS), + coordinates=COORDS if mode == 'coordinates' else None, + sinusoidal_dim=8, + random_dim=8, + random_seed=7, + station_ids=IDS, + ) + x = torch.randn(3, 6, len(IDS)) + got = w._augment(x, w.enc_proj) + ref = _materializing_augment(x, w.enc_proj, w.channel_features) + assert torch.allclose(got, ref, atol=1e-5) + + +def test_channel_onehot_does_not_materialize_n_squared() -> None: + """onehot on a big channel count must not allocate an (N,N)-per-element + tensor — the algebraic path keeps peak work O(B·T·N).""" + N = 800 # ~ traffic scale; the old path would build (B,T,N,1+N) + w = ChannelTransparentWrapper( + _IdentInner(), + 'onehot', + num_stations=N, + station_ids=[str(i) for i in range(N)], + ) + x = torch.randn(8, 16, N) + out = w(x) # would OOM-scale under the materializing path + assert out.shape == (8, 16, N) + assert torch.isfinite(out).all() From 97c89c7fb3f5a31202e48605d89653082b651694 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Mon, 15 Jun 2026 15:33:15 +0200 Subject: [PATCH 14/21] docs(research): entity-id summary figures (heatmap + LaTeX table) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tools/build_entity_id_figures.py scans all run-tag results.json (no hardcoded numbers, resume-safe — latest per cell) and emits: - heatmap-vs-none.png: % test-RMSE change vs each (dataset,model)'s own none baseline (green=better, red=worse) - results-table.{tex,pdf}: actual RMSE, best cell per dataset bolded, compiled with pdflatex Current snapshot (55 cells): swiss lstm per_entity all green (-11..-35%); swiss patchtst mc transparent all red (+32..+85%, pre-patch fusion); swiss dlinear flat; traffic/electricity lstm in progress. Re-run with --pull after new cells finish. Co-Authored-By: Claude Fable 5 --- .../entity-id-summary/heatmap-vs-none.png | Bin 0 -> 133639 bytes .../entity-id-summary/results-table.pdf | Bin 0 -> 29661 bytes .../entity-id-summary/results-table.tex | 23 +++ tools/build_entity_id_figures.py | 188 ++++++++++++++++++ 4 files changed, 211 insertions(+) create mode 100644 docs/research/figures/entity-id-summary/heatmap-vs-none.png create mode 100644 docs/research/figures/entity-id-summary/results-table.pdf create mode 100644 docs/research/figures/entity-id-summary/results-table.tex create mode 100644 tools/build_entity_id_figures.py diff --git a/docs/research/figures/entity-id-summary/heatmap-vs-none.png b/docs/research/figures/entity-id-summary/heatmap-vs-none.png new file mode 100644 index 0000000000000000000000000000000000000000..4bee90ed85ee7d3854cdc686c0fcfaf98fe20860 GIT binary patch literal 133639 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

C_i^yF#@PJ+O#s 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

wYFmYbWwPLj4k&}_3EYCTJE(@`M_ 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 literal 0 HcmV?d00001 diff --git a/docs/research/figures/entity-id-summary/results-table.tex b/docs/research/figures/entity-id-summary/results-table.tex new file mode 100644 index 0000000..06e235d --- /dev/null +++ b/docs/research/figures/entity-id-summary/results-table.tex @@ -0,0 +1,23 @@ +\documentclass[border=6pt]{standalone} +\usepackage{booktabs}\usepackage[table]{xcolor} +\begin{document} +\begin{tabular}{llrrrrrr} +\toprule +dataset (unit) & model & none & embedding & onehot & sinusoidal & random & coordinates \\ +\midrule +swiss-1990 (°C) & lstm & 1.723 & 1.289 & 1.119 & \textbf{1.116} & 1.139 & 1.155 \\ + & patchtst & 1.374 & 1.303 & 2.189 & 2.108 & 2.059 & 1.815 \\ + & dlinear & 1.281 & 1.286 & 1.286 & 1.282 & 1.283 & 1.279 \\ +\midrule +swiss-2010 (°C) & lstm & 1.642 & 1.368 & \textbf{1.201} & 1.224 & 1.222 & 1.255 \\ + & patchtst & 1.488 & 1.386 & 2.505 & 2.340 & 2.345 & 2.036 \\ + & dlinear & 1.354 & 1.356 & 1.356 & 1.358 & 1.361 & 1.357 \\ +\midrule +swiss-zurich (°C) & lstm & 1.553 & 1.378 & \textbf{1.237} & 1.259 & 1.249 & 1.276 \\ + & patchtst & 1.480 & 1.388 & 2.738 & 2.690 & 2.631 & 2.377 \\ + & dlinear & 1.390 & 1.401 & 1.396 & 1.393 & 1.393 & 1.385 \\ +\midrule +electricity (std) & lstm & \textbf{2473} & -- & -- & -- & -- & -- \\ +\bottomrule +\end{tabular} +\end{document} \ No newline at end of file diff --git a/tools/build_entity_id_figures.py b/tools/build_entity_id_figures.py new file mode 100644 index 0000000..c7acf46 --- /dev/null +++ b/tools/build_entity_id_figures.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +"""Build the two entity-identifier summary artifacts from real results.json: + + 1. a %-vs-none heatmap (figures/entity-id-summary/heatmap-vs-none.png) + 2. an actual-RMSE LaTeX table, compiled to PDF, with the best result per + dataset (over all models × identifier modes) bolded + (figures/entity-id-summary/results-table.{tex,pdf}) + +Re-run after every new cell finishes — it scans whatever results.json exist +and skips missing cells. No hardcoded numbers (code-verifier). + +Usage: python tools/build_entity_id_figures.py [--pull] + --pull rsync the cluster run-tag dirs first. +""" +from __future__ import annotations + +import argparse +import glob +import json +import os +import subprocess +from pathlib import Path + +import matplotlib + +matplotlib.use('Agg') +import matplotlib.pyplot as plt +import numpy as np + +ROOT = Path(__file__).resolve().parents[1] +ART = ROOT / 'artifacts' / 'entity_identifier' +OUT = ROOT / 'docs' / 'research' / 'figures' / 'entity-id-summary' +OUT.mkdir(parents=True, exist_ok=True) + +# Where each (dataset, model) family's cells live. Order here = display order. +RUN_TAGS = [ + 'swiss3dt-1990-20260612', 'swiss3dt-2010-20260612', 'swiss3dt-zurich-20260612', + 'swiss-mc-1990-20260614', 'swiss-mc-2010-20260614', 'swiss-mc-zurich-20260614', + 'traffic-mc-20260614', 'elec-mc-20260614', +] +DATASET_ORDER = ['swiss-river-1990', 'swiss-river-2010', 'swiss-river-zurich', + 'traffic', 'electricity'] +MODEL_ORDER = ['lstm', 'patchtst', 'dlinear'] +MODES = ['none', 'embedding', 'onehot', 'sinusoidal', 'random', 'coordinates'] +UNITS = {'swiss-river-1990': '°C', 'swiss-river-2010': '°C', + 'swiss-river-zurich': '°C', 'traffic': 'norm', 'electricity': 'std'} + + +def collect() -> dict: + """(dataset, model) -> {mode: rmse}, taking the latest results.json per cell.""" + data: dict = {} + for tag in RUN_TAGS: + for cell in glob.glob(f'{ART}/{tag}/*-seed2026'): + rjs = glob.glob(f'{cell}/**/results.json', recursive=True) + if not rjs: + continue + rj = max(rjs, key=os.path.getmtime) # newest (resume-safe) + try: + r = json.load(open(rj)) + ds = r['data']['dataset'] + model = r['model']['type'] + mode = r['data']['identifier_mode'] + m = r['metrics']['test'] + rmse = m.get('denorm_rmse', m.get('rmse')) + if rmse is None or rmse != rmse: # skip NaN + continue + data.setdefault((ds, model), {})[mode] = float(rmse) + except (KeyError, json.JSONDecodeError): + continue + return data + + +def ordered_rows(data: dict) -> list[tuple[str, str]]: + rows = [] + for ds in DATASET_ORDER: + for model in MODEL_ORDER: + if (ds, model) in data: + rows.append((ds, model)) + return rows + + +def build_heatmap(data: dict, rows: list[tuple[str, str]]) -> None: + """%-change vs the (dataset, model)'s own none baseline. Green=better.""" + grid = np.full((len(rows), len(MODES)), np.nan) + annot = [['' for _ in MODES] for _ in rows] + for i, (ds, model) in enumerate(rows): + cells = data[(ds, model)] + base = cells.get('none') + for j, mode in enumerate(MODES): + v = cells.get(mode) + if v is None: + continue + if mode == 'none': + annot[i][j] = '0' + grid[i, j] = 0.0 + elif base: + pct = (v - base) / base * 100.0 + grid[i, j] = pct + annot[i][j] = f'{pct:+.1f}' + fig, ax = plt.subplots(figsize=(8.5, 0.5 * len(rows) + 1.8)) + vmax = np.nanmax(np.abs(grid)) if np.isfinite(grid).any() else 1 + vmax = min(vmax, 60) # clip so the swiss ±35% stays readable next to +60% + im = ax.imshow(grid, cmap='RdYlGn_r', vmin=-vmax, vmax=vmax, aspect='auto') + ax.set_xticks(range(len(MODES))) + ax.set_xticklabels(MODES, rotation=30, ha='right') + ax.set_yticks(range(len(rows))) + ax.set_yticklabels([f'{ds.replace("swiss-river-", "swiss-")} · {model}' for ds, model in rows]) + for i in range(len(rows)): + for j in range(len(MODES)): + if annot[i][j]: + ax.text(j, i, annot[i][j], ha='center', va='center', fontsize=7.5, + color='black') + cb = fig.colorbar(im, ax=ax, shrink=0.7) + cb.set_label('% RMSE change vs none (green = better, red = worse)') + ax.set_title('Entity identifiers: % test-RMSE change vs none\n' + '(single seed; clipped at ±60%)', fontsize=10) + fig.tight_layout() + fig.savefig(OUT / 'heatmap-vs-none.png', dpi=150, bbox_inches='tight') + print('wrote', OUT / 'heatmap-vs-none.png') + + +def build_latex(data: dict, rows: list[tuple[str, str]]) -> None: + """Actual RMSE table; best cell per DATASET (over all models×modes) bolded.""" + best_per_ds = {} + for (ds, model), cells in data.items(): + for mode, v in cells.items(): + if ds not in best_per_ds or v < best_per_ds[ds]: + best_per_ds[ds] = v + + def fmt(ds, v): + if v is None: + return '--' + s = f'{v:.3f}' if v < 100 else f'{v:.0f}' + return f'\\textbf{{{s}}}' if abs(v - best_per_ds.get(ds, -1)) < 1e-9 else s + + lines = [ + r'\documentclass[border=6pt]{standalone}', + r'\usepackage{booktabs}\usepackage[table]{xcolor}', + r'\begin{document}', + r'\begin{tabular}{ll' + 'r' * len(MODES) + '}', + r'\toprule', + 'dataset (unit) & model & ' + ' & '.join(MODES) + r' \\', + r'\midrule', + ] + last_ds = None + for ds, model in rows: + cells = data[(ds, model)] + dlabel = f'{ds.replace("swiss-river-", "swiss-")} ({UNITS.get(ds, "?")})' if ds != last_ds else '' + if ds != last_ds and last_ds is not None: + lines.append(r'\midrule') + last_ds = ds + vals = ' & '.join(fmt(ds, cells.get(m)) for m in MODES) + lines.append(f'{dlabel} & {model} & {vals} ' + r'\\') + lines += [r'\bottomrule', r'\end{tabular}', r'\end{document}'] + tex = OUT / 'results-table.tex' + tex.write_text('\n'.join(lines)) + print('wrote', tex) + try: + subprocess.run(['pdflatex', '-interaction=nonstopmode', '-halt-on-error', + 'results-table.tex'], cwd=OUT, check=True, + capture_output=True, timeout=60) + for ext in ('aux', 'log'): + (OUT / f'results-table.{ext}').unlink(missing_ok=True) + print('compiled', OUT / 'results-table.pdf') + except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired) as e: + print(f'pdflatex failed ({e}); .tex written, compile manually.') + + +def main() -> None: + ap = argparse.ArgumentParser() + ap.add_argument('--pull', action='store_true', help='rsync cluster run-tags first') + args = ap.parse_args() + if args.pull: + host = 'lj22u267@submit03.unibe.ch' + for tag in RUN_TAGS: + subprocess.run( + ['rsync', '-azq', '--exclude=ray_results/', '--exclude=checkpoints/', + f'{host}:~/codes/liulian-python/artifacts/entity_identifier/{tag}', + str(ART) + '/'], check=False) + data = collect() + rows = ordered_rows(data) + print(f'collected {sum(len(v) for v in data.values())} cells across {len(rows)} (dataset,model) rows') + build_heatmap(data, rows) + build_latex(data, rows) + + +if __name__ == '__main__': + main() From fb9472466ff0a91e61457a28d6991bd4dd779d87 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Mon, 15 Jun 2026 17:42:07 +0200 Subject: [PATCH 15/21] docs(research): mark per-(model,dataset) best id-mode in summary table Table now has two emphases: bold = best over all models & modes for a dataset; underline = best id-mode within each (model, dataset) row. Legend row added. Co-Authored-By: Claude Fable 5 --- .../entity-id-summary/results-table.pdf | Bin 29661 -> 50607 bytes .../entity-id-summary/results-table.tex | 21 +++++++++-------- tools/build_entity_id_figures.py | 22 ++++++++++++++---- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/docs/research/figures/entity-id-summary/results-table.pdf b/docs/research/figures/entity-id-summary/results-table.pdf index 9801c403171a25ea2421309547a71b7f371ec51d..a469c62c6dfae1351947c338371806a4f0999db8 100644 GIT binary patch delta 22516 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 JB|4CR{{bqlsy6@t diff --git a/docs/research/figures/entity-id-summary/results-table.tex b/docs/research/figures/entity-id-summary/results-table.tex index 06e235d..15722b6 100644 --- a/docs/research/figures/entity-id-summary/results-table.tex +++ b/docs/research/figures/entity-id-summary/results-table.tex @@ -5,19 +5,20 @@ \toprule dataset (unit) & model & none & embedding & onehot & sinusoidal & random & coordinates \\ \midrule -swiss-1990 (°C) & lstm & 1.723 & 1.289 & 1.119 & \textbf{1.116} & 1.139 & 1.155 \\ - & patchtst & 1.374 & 1.303 & 2.189 & 2.108 & 2.059 & 1.815 \\ - & dlinear & 1.281 & 1.286 & 1.286 & 1.282 & 1.283 & 1.279 \\ +swiss-1990 (°C) & lstm & 1.723 & 1.289 & 1.119 & \underline{\textbf{1.116}} & 1.139 & 1.155 \\ + & patchtst & 1.374 & \underline{1.303} & 2.189 & 2.108 & 2.059 & 1.815 \\ + & dlinear & 1.281 & 1.286 & 1.286 & 1.282 & 1.283 & \underline{1.279} \\ \midrule -swiss-2010 (°C) & lstm & 1.642 & 1.368 & \textbf{1.201} & 1.224 & 1.222 & 1.255 \\ - & patchtst & 1.488 & 1.386 & 2.505 & 2.340 & 2.345 & 2.036 \\ - & dlinear & 1.354 & 1.356 & 1.356 & 1.358 & 1.361 & 1.357 \\ +swiss-2010 (°C) & lstm & 1.642 & 1.368 & \underline{\textbf{1.201}} & 1.224 & 1.222 & 1.255 \\ + & patchtst & 1.488 & \underline{1.386} & 2.505 & 2.340 & 2.345 & 2.036 \\ + & dlinear & \underline{1.354} & 1.356 & 1.356 & 1.358 & 1.361 & 1.357 \\ \midrule -swiss-zurich (°C) & lstm & 1.553 & 1.378 & \textbf{1.237} & 1.259 & 1.249 & 1.276 \\ - & patchtst & 1.480 & 1.388 & 2.738 & 2.690 & 2.631 & 2.377 \\ - & dlinear & 1.390 & 1.401 & 1.396 & 1.393 & 1.393 & 1.385 \\ +swiss-zurich (°C) & lstm & 1.553 & 1.378 & \underline{\textbf{1.237}} & 1.259 & 1.249 & 1.276 \\ + & patchtst & 1.480 & \underline{1.388} & 2.738 & 2.690 & 2.631 & 2.377 \\ + & dlinear & 1.390 & 1.401 & 1.396 & 1.393 & 1.393 & \underline{1.385} \\ \midrule -electricity (std) & lstm & \textbf{2473} & -- & -- & -- & -- & -- \\ +electricity (std) & lstm & \underline{\textbf{2473}} & -- & -- & -- & -- & -- \\ \bottomrule +\multicolumn{8}{l}{\footnotesize \textbf{bold} = best over all models \& modes for that dataset; \underline{underline} = best id-mode for that (model, dataset) row.} \\ \end{tabular} \end{document} \ No newline at end of file diff --git a/tools/build_entity_id_figures.py b/tools/build_entity_id_figures.py index c7acf46..710c216 100644 --- a/tools/build_entity_id_figures.py +++ b/tools/build_entity_id_figures.py @@ -127,11 +127,17 @@ def build_latex(data: dict, rows: list[tuple[str, str]]) -> None: if ds not in best_per_ds or v < best_per_ds[ds]: best_per_ds[ds] = v - def fmt(ds, v): + def fmt(ds, v, row_best): + """bold = best for the whole dataset; underline = best id-mode for + this (model, dataset) row. Both can stack.""" if v is None: return '--' s = f'{v:.3f}' if v < 100 else f'{v:.0f}' - return f'\\textbf{{{s}}}' if abs(v - best_per_ds.get(ds, -1)) < 1e-9 else s + if abs(v - best_per_ds.get(ds, -1)) < 1e-9: + s = f'\\textbf{{{s}}}' + if row_best is not None and abs(v - row_best) < 1e-9: + s = f'\\underline{{{s}}}' + return s lines = [ r'\documentclass[border=6pt]{standalone}', @@ -149,9 +155,17 @@ def fmt(ds, v): if ds != last_ds and last_ds is not None: lines.append(r'\midrule') last_ds = ds - vals = ' & '.join(fmt(ds, cells.get(m)) for m in MODES) + row_best = min(cells.values()) if cells else None + vals = ' & '.join(fmt(ds, cells.get(m), row_best) for m in MODES) lines.append(f'{dlabel} & {model} & {vals} ' + r'\\') - lines += [r'\bottomrule', r'\end{tabular}', r'\end{document}'] + lines += [ + r'\bottomrule', + r'\multicolumn{' + str(2 + len(MODES)) + r'}{l}{\footnotesize ' + r'\textbf{bold} = best over all models \& modes for that dataset; ' + r'\underline{underline} = best id-mode for that (model, dataset) row.} \\', + r'\end{tabular}', + r'\end{document}', + ] tex = OUT / 'results-table.tex' tex.write_text('\n'.join(lines)) print('wrote', tex) From 6eabbda98ec6c4df9f6e0886c4233f681b65e0f3 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Mon, 15 Jun 2026 17:44:59 +0200 Subject: [PATCH 16/21] docs(research): PatchTST transparent failure = instance-norm cancels identifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root-cause mechanism (verified numerically, max diff 8e-6): PatchTST's per-channel instance normalization subtracts each channel's time-mean, and all transparent identifiers are per-channel CONSTANTS injected pre-norm via concat_to_x, so the norm erases them exactly — leaving only the fusion projection's distortion (the +30-85% regression). embedding survives via add_after_patch (post-norm, d_model token space). Transparent can't use add_after_patch today: gated to embedding + dimension mismatch (needs a Linear(D,d_model) projection) — the clean ablation to run next. Co-Authored-By: Claude Fable 5 --- docs/research/2026-06-14-swiss-mc-results.md | 52 +++++++++++++++++--- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/docs/research/2026-06-14-swiss-mc-results.md b/docs/research/2026-06-14-swiss-mc-results.md index 22d9014..2a57eab 100644 --- a/docs/research/2026-06-14-swiss-mc-results.md +++ b/docs/research/2026-06-14-swiss-mc-results.md @@ -37,11 +37,12 @@ CSV: `figures/swiss-mc-2026-06-14/swiss-mc-rmse.csv`. decomposition model over channels has no head-room to exploit per-channel identity, and the wrapper's fusion projection adds none. - **On PatchTST, the injection POINT — not the identifier type — drives the - result.** `embedding` via `add_after_patch` (injected *after* patch - embedding) helps (~−5% vs none). The transparent modes, injected via - `concat_to_x` + `ChannelTransparentWrapper`'s per-channel - `Linear(1+D,1)` fusion *before* patching, hurt substantially (+30–60%), - and they hurt in HPO val too (1990 onehot val 0.231 vs none 0.124). + result**, and the root cause is **instance normalization** (see the + mechanism section below). `embedding` via `add_after_patch` (injected + *after* patching, in d_model token space, **post-norm**) helps (~−5% vs + none). The transparent modes, injected via `concat_to_x` *before* patching + (**pre-norm**), hurt substantially (+30–85%), and hurt in HPO val too + (1990 onehot val 0.231 vs none 0.124). **NOT claimed (would over-reach):** @@ -61,6 +62,37 @@ information. This wrapper is structurally different from the per_entity `EntityTransparentWrapper` (zero-parameter concat). Cross-split comparisons must account for this. +## Why PatchTST transparent fails — instance-norm cancels constant identifiers (2026-06-15) + +PatchTST.forecast starts with per-channel **instance normalization** (from +the Non-stationary Transformer): it subtracts each channel's mean over time +and divides by its std (`patchtst.py:142-145`). All transparent identifiers +are **per-channel CONSTANTS** (onehot / coordinates / sinusoidal / random +produce one fixed vector per station, the same at every time step). The +`concat_to_x` path adds that constant to the channel's series *before* +PatchTST runs — so instance-norm, which subtracts the per-channel time-mean, +**erases it exactly**. + +Verified numerically: feeding `w·x + const_offset` through PatchTST's +instance-norm yields a tensor identical (max diff 8e-6) to normalizing the +plain `x` — the identifier contributes **nothing** after norm, leaving only +the fusion projection's distortion of the signal. Hence transparent on +PatchTST is all cost, no signal → the +30–85% regression. + +`embedding` survives because `add_after_patch` injects in **d_model patch- +token space, after** patching/normalization (`_inject_entity_after_patch`, +`patchtst.py:127-138`) — the only injection point downstream of the norm. + +**Why the transparent modes can't currently use `add_after_patch`:** that +path is hard-gated to `identifier_mode='embedding'` (`patchtst.py:72`) and is +literally `nn.Embedding(N, d_model)` added to the token — it produces a +d_model vector by construction. A transparent fixed vector has dimension +≠ d_model (onehot=N, coords=2, sin/random=D), so adding it post-patch would +require a learnable `Linear(D, d_model)` projection (no longer "zero-param +transparent", and not implemented). So "can't" = a current implementation +limit + a dimension mismatch, **not** a fundamental impossibility — it's the +clean ablation to run next (below). + ## Contrast with the per_entity LSTM (2026-06-13) | split | model | identifier effect | @@ -78,9 +110,13 @@ report. ## To harden / explain (follow-ups) -- **Ablation isolating injection point from identifier type**: run PatchTST - + transparent + `add_after_patch` — if it stops hurting, the cause is the - pre-patch fusion, confirming the audit's reading. +- **Ablation isolating injection point from identifier type**: implement a + transparent `add_after_patch` for PatchTST (project the fixed (N,D) feature + table to d_model with one `Linear(D, d_model)`, add to the patch token like + the embedding path), then rerun PatchTST × {onehot, sinusoidal, random, + coordinates}. If they stop hurting (or help), it confirms the cause is the + pre-norm `concat_to_x` injection being cancelled by instance-norm, not the + identifier type. (Tracked as a task.) - **Inspect the DLinear fusion-projection weights** to tell "can't use" from "discards". - **Multi-seed** (≥3) for variance bands (shared with the lstm follow-up #32). From bead95c475c197d992b1e20220ccda81dfbd4107 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Mon, 15 Jun 2026 19:48:09 +0200 Subject: [PATCH 17/21] feat(figures): pre-build ablation table scaffold for patchtst injection (task #40) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactors collect() -> collect_tags(tags) and adds build_ablation_table(): a separate concat_to_x-vs-add_after_patch table for patchtst transparent modes (per-dataset, bolds the better injection, shows none ref). Skips cleanly (no file) until ABLATION_TAGS is populated, so it's safe on every watcher refresh. Adds MAIN_PATCHTST_SOURCE knob (concat|add_after_patch| better) for which injection the MAIN table uses per cell — defaults to concat; final pick is the user's once the ablation runs. Co-Authored-By: Claude Fable 5 --- tools/build_entity_id_figures.py | 151 ++++++++++++++++++++++++++----- 1 file changed, 130 insertions(+), 21 deletions(-) diff --git a/tools/build_entity_id_figures.py b/tools/build_entity_id_figures.py index 710c216..91790db 100644 --- a/tools/build_entity_id_figures.py +++ b/tools/build_entity_id_figures.py @@ -12,6 +12,7 @@ Usage: python tools/build_entity_id_figures.py [--pull] --pull rsync the cluster run-tag dirs first. """ + from __future__ import annotations import argparse @@ -34,22 +35,43 @@ # Where each (dataset, model) family's cells live. Order here = display order. RUN_TAGS = [ - 'swiss3dt-1990-20260612', 'swiss3dt-2010-20260612', 'swiss3dt-zurich-20260612', - 'swiss-mc-1990-20260614', 'swiss-mc-2010-20260614', 'swiss-mc-zurich-20260614', - 'traffic-mc-20260614', 'elec-mc-20260614', + 'swiss3dt-1990-20260612', + 'swiss3dt-2010-20260612', + 'swiss3dt-zurich-20260612', + 'swiss-mc-1990-20260614', + 'swiss-mc-2010-20260614', + 'swiss-mc-zurich-20260614', + 'traffic-mc-20260614', + 'elec-mc-20260614', ] -DATASET_ORDER = ['swiss-river-1990', 'swiss-river-2010', 'swiss-river-zurich', - 'traffic', 'electricity'] +DATASET_ORDER = ['swiss-river-1990', 'swiss-river-2010', 'swiss-river-zurich', 'traffic', 'electricity'] MODEL_ORDER = ['lstm', 'patchtst', 'dlinear'] MODES = ['none', 'embedding', 'onehot', 'sinusoidal', 'random', 'coordinates'] -UNITS = {'swiss-river-1990': '°C', 'swiss-river-2010': '°C', - 'swiss-river-zurich': '°C', 'traffic': 'norm', 'electricity': 'std'} +UNITS = { + 'swiss-river-1990': '°C', + 'swiss-river-2010': '°C', + 'swiss-river-zurich': '°C', + 'traffic': 'norm', + 'electricity': 'std', +} +# --- PatchTST transparent injection ablation (task #40) -------------------- # +# The MAIN table/heatmap above use the concat_to_x (pre-norm) patchtst +# transparent results from RUN_TAGS. The ablation reruns the SAME cells with +# add_after_patch (post-norm) injection; fill ABLATION_TAGS once those run. +# The main table will then show, per patchtst transparent cell, the BETTER of +# the two — but the final pick is the user's (see MAIN_PATCHTST_SOURCE). +ABLATION_TAGS: list[str] = [] # add_after_patch run-tags — TBD when #40 runs +TRANSPARENT_MODES = ['onehot', 'sinusoidal', 'random', 'coordinates'] +# 'concat' | 'add_after_patch' | 'better' — which injection the MAIN summary +# uses for patchtst transparent cells. Stays 'concat' until the user decides. +MAIN_PATCHTST_SOURCE = 'concat' -def collect() -> dict: - """(dataset, model) -> {mode: rmse}, taking the latest results.json per cell.""" + +def collect_tags(tags: list[str]) -> dict: + """(dataset, model) -> {mode: rmse} over the given run-tags, latest per cell.""" data: dict = {} - for tag in RUN_TAGS: + for tag in tags: for cell in glob.glob(f'{ART}/{tag}/*-seed2026'): rjs = glob.glob(f'{cell}/**/results.json', recursive=True) if not rjs: @@ -70,6 +92,11 @@ def collect() -> dict: return data +def collect() -> dict: + """Main results over all RUN_TAGS (concat_to_x / embedding / none).""" + return collect_tags(RUN_TAGS) + + def ordered_rows(data: dict) -> list[tuple[str, str]]: rows = [] for ds in DATASET_ORDER: @@ -108,12 +135,10 @@ def build_heatmap(data: dict, rows: list[tuple[str, str]]) -> None: for i in range(len(rows)): for j in range(len(MODES)): if annot[i][j]: - ax.text(j, i, annot[i][j], ha='center', va='center', fontsize=7.5, - color='black') + ax.text(j, i, annot[i][j], ha='center', va='center', fontsize=7.5, color='black') cb = fig.colorbar(im, ax=ax, shrink=0.7) cb.set_label('% RMSE change vs none (green = better, red = worse)') - ax.set_title('Entity identifiers: % test-RMSE change vs none\n' - '(single seed; clipped at ±60%)', fontsize=10) + ax.set_title('Entity identifiers: % test-RMSE change vs none\n(single seed; clipped at ±60%)', fontsize=10) fig.tight_layout() fig.savefig(OUT / 'heatmap-vs-none.png', dpi=150, bbox_inches='tight') print('wrote', OUT / 'heatmap-vs-none.png') @@ -170,9 +195,13 @@ def fmt(ds, v, row_best): tex.write_text('\n'.join(lines)) print('wrote', tex) try: - subprocess.run(['pdflatex', '-interaction=nonstopmode', '-halt-on-error', - 'results-table.tex'], cwd=OUT, check=True, - capture_output=True, timeout=60) + subprocess.run( + ['pdflatex', '-interaction=nonstopmode', '-halt-on-error', 'results-table.tex'], + cwd=OUT, + check=True, + capture_output=True, + timeout=60, + ) for ext in ('aux', 'log'): (OUT / f'results-table.{ext}').unlink(missing_ok=True) print('compiled', OUT / 'results-table.pdf') @@ -180,22 +209,102 @@ def fmt(ds, v, row_best): print(f'pdflatex failed ({e}); .tex written, compile manually.') +def build_ablation_table(main_data: dict) -> None: + """Separate table: patchtst transparent — concat_to_x vs add_after_patch. + + Skips (no file written) until ABLATION_TAGS is populated and has data, so + it's safe to call on every watcher refresh. Bolds the better injection per + (dataset, mode); shows the none baseline for reference. + """ + if not ABLATION_TAGS: + print('ablation: ABLATION_TAGS empty (add_after_patch not run yet) — skipping') + return + abl = collect_tags(ABLATION_TAGS) # (ds, 'patchtst') -> {mode: rmse} + have = {ds for (ds, model) in abl if model == 'patchtst'} + if not have: + print('ablation: no patchtst add_after_patch cells yet — skipping') + return + datasets = [d for d in DATASET_ORDER if (d, 'patchtst') in main_data and d in have] + lines = [ + r'\documentclass[border=6pt]{standalone}', + r'\usepackage{booktabs}', + r'\begin{document}', + r'\begin{tabular}{llrrrr}', + r'\toprule', + r'dataset (unit) & id-mode & none & concat\_to\_x & add\_after\_patch & better \\', + r'\midrule', + ] + last = None + for ds in datasets: + cc = main_data[(ds, 'patchtst')] + ap_ = abl.get((ds, 'patchtst'), {}) + none_v = cc.get('none') + for mode in TRANSPARENT_MODES: + c = cc.get(mode) + a = ap_.get(mode) + if c is None and a is None: + continue + dlabel = f'{ds.replace("swiss-river-", "swiss-")} ({UNITS.get(ds, "?")})' if ds != last else '' + if ds != last and last is not None: + lines.append(r'\midrule') + last = ds + + def f(v, bold=False): + if v is None: + return '--' + s = f'{v:.3f}' if v < 100 else f'{v:.0f}' + return f'\\textbf{{{s}}}' if bold else s + + better = '' + if c is not None and a is not None: + better = 'add\\_after\\_patch' if a < c else 'concat' + lines.append( + f'{dlabel} & {mode} & {f(none_v)} & ' + f'{f(c, c is not None and a is not None and c <= a)} & ' + f'{f(a, a is not None and c is not None and a < c)} & {better} ' + r'\\' + ) + lines += [r'\bottomrule', r'\end{tabular}', r'\end{document}'] + tex = OUT / 'ablation-patchtst-injection.tex' + tex.write_text('\n'.join(lines)) + try: + subprocess.run( + ['pdflatex', '-interaction=nonstopmode', '-halt-on-error', 'ablation-patchtst-injection.tex'], + cwd=OUT, + check=True, + capture_output=True, + timeout=60, + ) + for ext in ('aux', 'log'): + (OUT / f'ablation-patchtst-injection.{ext}').unlink(missing_ok=True) + print('compiled', OUT / 'ablation-patchtst-injection.pdf') + except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired) as e: + print(f'ablation pdflatex failed ({e}); .tex written.') + + def main() -> None: ap = argparse.ArgumentParser() ap.add_argument('--pull', action='store_true', help='rsync cluster run-tags first') args = ap.parse_args() if args.pull: host = 'lj22u267@submit03.unibe.ch' - for tag in RUN_TAGS: + for tag in RUN_TAGS + ABLATION_TAGS: subprocess.run( - ['rsync', '-azq', '--exclude=ray_results/', '--exclude=checkpoints/', - f'{host}:~/codes/liulian-python/artifacts/entity_identifier/{tag}', - str(ART) + '/'], check=False) + [ + 'rsync', + '-azq', + '--exclude=ray_results/', + '--exclude=checkpoints/', + f'{host}:~/codes/liulian-python/artifacts/entity_identifier/{tag}', + str(ART) + '/', + ], + check=False, + ) data = collect() rows = ordered_rows(data) print(f'collected {sum(len(v) for v in data.values())} cells across {len(rows)} (dataset,model) rows') build_heatmap(data, rows) build_latex(data, rows) + build_ablation_table(data) if __name__ == '__main__': From e428fc17224bd2527965f6458124f8e0ea4300d6 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Mon, 15 Jun 2026 20:16:14 +0200 Subject: [PATCH 18/21] feat(patchtst): transparent add_after_patch (task #40, integrated from subagent) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Transparent identifiers (onehot/sin/random/coordinates) can now inject via add_after_patch: PatchTST builds the fixed (N,D) table internally, projects it to d_model with a learned Linear, and adds it to patch tokens downstream of instance-norm — bypassing the norm that erases the concat_to_x (pre-norm) constant injection. The clean ablation for "injection point vs identifier type". Opt-in via env LIULIAN_PATCHTST_TRANSPARENT_ADD_AFTER_PATCH; default concat_to_x unchanged. patchtst.py + test_patchtst.py taken from the subagent (its base was clean there). pipeline.py + matrix.py were RE-DONE by hand on the current structure: the subagent's worktree branched from an old commit (a3dc2f9, pre the id_injection / unified-wrapper / mc-mount work), so its pipeline/ matrix edits didn't apply — its "concat_to_x doesn't inject" flag was a misread of that old tree (verified: concat_to_x DOES mount ChannelTransparentWrapper on current main). _transparent_injection_kind now returns None for patchtst add_after_patch (handled internally, no wrapper); build_model injects coords/station_ids/dims onto config for that path. Verified on current tree: concat_to_x onehot still mounts the wrapper; add_after_patch onehot/coordinates build the internal id_proj+id_table (no wrapper, no raise); HPO sanity (2-trial Ray) PASS; 88 model/matrix tests green. Smoke hint: add_after_patch onehot 1.42 vs concat_to_x 2.19 — the instance-norm hypothesis looks right, pending the full HPO ablation. Co-Authored-By: Claude Fable 5 --- experiments/entity_identifier/matrix.py | 19 +++- liulian/models/torch/patchtst.py | 68 +++++++++++- liulian/pipeline.py | 40 ++++++- tests/models/torch/test_patchtst.py | 137 ++++++++++++++++++++++++ 4 files changed, 254 insertions(+), 10 deletions(-) diff --git a/experiments/entity_identifier/matrix.py b/experiments/entity_identifier/matrix.py index 48f158b..0214b35 100644 --- a/experiments/entity_identifier/matrix.py +++ b/experiments/entity_identifier/matrix.py @@ -4,6 +4,7 @@ import hashlib import json +import os from dataclasses import dataclass from pathlib import Path from typing import Any, Iterable @@ -277,6 +278,7 @@ def build_cli_overrides( hpo_max_concurrent: int | None = None, hpo_resources_gpu: float | None = None, hpo_resume: bool = False, + patchtst_transparent_add_after_patch: bool | None = None, ) -> dict[str, Any]: """Build deterministic CLI overrides for one job. @@ -309,9 +311,22 @@ def build_cli_overrides( else: overrides['split_mode'] = 'multi_channel' + # Ablation opt-in (task #40): route patchtst transparent through + # add_after_patch (post-instance-norm) instead of the default concat_to_x. + # Explicit arg wins; else env var; else default (concat_to_x) — unchanged. + if patchtst_transparent_add_after_patch is None: + _env = os.environ.get('LIULIAN_PATCHTST_TRANSPARENT_ADD_AFTER_PATCH', '') + patchtst_transparent_add_after_patch = _env.strip().lower() in {'1', 'true', 'yes', 'on'} + if job.model == 'patchtst': - # Required by the approved design: embedding uses add_after_patch. - overrides['id_integration'] = 'add_after_patch' if job.mode == 'embedding' else 'concat_to_x' + # embedding always uses add_after_patch (approved design); transparent + # uses add_after_patch only under the ablation opt-in, else concat_to_x. + if job.mode == 'embedding': + overrides['id_integration'] = 'add_after_patch' + elif patchtst_transparent_add_after_patch and job.mode in {'onehot', 'coordinates', 'sinusoidal', 'random'}: + overrides['id_integration'] = 'add_after_patch' + else: + overrides['id_integration'] = 'concat_to_x' elif job.mode in {'onehot', 'coordinates', 'sinusoidal', 'random'}: overrides['id_integration'] = 'concat_to_x' elif job.mode == 'embedding': diff --git a/liulian/models/torch/patchtst.py b/liulian/models/torch/patchtst.py index a3bb7ba..ecb8cff 100644 --- a/liulian/models/torch/patchtst.py +++ b/liulian/models/torch/patchtst.py @@ -12,6 +12,14 @@ * ``identifier_mode: embedding`` + ``id_integration: add_after_patch`` — add a learned per-channel embedding in ``d_model`` space after patching and patch projection. +* Transparent modes (``onehot`` / ``sinusoidal`` / ``random`` / + ``coordinates`` / ``numeric_id``) + ``id_integration: add_after_patch`` + — project the FIXED per-channel identifier table ``(N, D)`` to + ``d_model`` via a learned ``nn.Linear`` and add it in patch-token + space, downstream of the per-channel instance-norm. This lets the + transparent identifiers survive instance normalization (which would + otherwise subtract away any pre-norm per-channel constant), exactly + like the learned ``embedding`` path. """ import torch @@ -21,7 +29,13 @@ from liulian.models.torch.layers.attention import FullAttention, AttentionLayer from liulian.models.torch.layers.embed import PatchEmbedding from liulian.models.torch.base_adapter import TorchModelAdapter -from liulian.models.torch.entity_mixin import EntityAwareMixin +from liulian.models.torch.entity_mixin import EntityAwareMixin, _build_channel_features + + +# Transparent identifier modes that can also use ``add_after_patch``. +# These build a FIXED ``(N, D)`` table (no learned lookup) projected to +# ``d_model`` and added in patch-token space. +_TRANSPARENT_ADD_AFTER_PATCH_MODES = frozenset({'onehot', 'sinusoidal', 'random', 'coordinates', 'numeric_id'}) class Transpose(nn.Module): @@ -69,15 +83,47 @@ def __init__(self, configs, patch_len=16, stride=8): self.id_integration = getattr(configs, 'id_integration', 'concat_to_x') padding = stride - self._use_add_after_patch = self.identifier_mode == 'embedding' and self.id_integration == 'add_after_patch' + # ``add_after_patch`` has two flavours, both injecting a per-channel + # vector in ``d_model`` patch-token space (downstream of the + # instance-norm in ``forecast``): + # * embedding → learned ``nn.Embedding`` lookup. + # * transparent → FIXED ``(N, D)`` identifier table projected to + # ``d_model`` via a learned ``nn.Linear`` (the table itself is a + # non-learned buffer; only the projection is trained). + self._use_embedding_after_patch = ( + self.identifier_mode == 'embedding' and self.id_integration == 'add_after_patch' + ) + self._use_transparent_after_patch = ( + self.identifier_mode in _TRANSPARENT_ADD_AFTER_PATCH_MODES and self.id_integration == 'add_after_patch' + ) + # Union flag — gates the early return in ``_inject_entity_after_patch`` + # and the multi_channel requirement shared by both flavours. + self._use_add_after_patch = self._use_embedding_after_patch or self._use_transparent_after_patch if self._use_add_after_patch and getattr(configs, 'split_mode', None) != 'multi_channel': raise ValueError("PatchTST only supports id_integration='add_after_patch' in split_mode='multi_channel'.") # patching and embedding self.patch_embedding = PatchEmbedding(configs.d_model, patch_len, stride, padding, configs.dropout) - if self._use_add_after_patch: + if self._use_embedding_after_patch: num_stations = getattr(configs, 'enc_in', 1) self.entity_embedding = nn.Embedding(num_stations, configs.d_model) + elif self._use_transparent_after_patch: + num_stations = getattr(configs, 'enc_in', 1) + # Build the FIXED per-channel identifier table once, reusing the + # canonical feature formulas from entity_mixin (onehot/sinusoidal/ + # random/coordinates/numeric_id). Registered as a buffer so it + # moves with the model (.to(device)) and lands in state_dict. + id_table = _build_channel_features( + self.identifier_mode, + num_stations, + sinusoidal_dim=int(getattr(configs, 'sinusoidal_dim', 16)), + random_dim=int(getattr(configs, 'random_identifier_dim', 16)), + random_seed=int(getattr(configs, 'random_identifier_seed', 2026)), + coordinates=getattr(configs, 'coordinates', None), + station_ids=getattr(configs, 'station_ids', None), + ) + self.register_buffer('id_table', id_table) # (N, D) + self.id_proj = nn.Linear(id_table.shape[1], configs.d_model) # Encoder self.encoder = Encoder( @@ -129,12 +175,24 @@ def _inject_entity_after_patch( enc_out: torch.Tensor, n_vars: int, ) -> torch.Tensor: - """Add per-channel entity embeddings in patch-token space.""" + """Add per-channel entity vectors in patch-token space. + + ``enc_out`` is ``(batch * n_vars, patch_num, d_model)`` with the + channels interleaved as ``arange(n_vars).repeat(batch)``. Both the + learned-embedding and the transparent-projection flavours produce a + per-channel ``(n_vars, d_model)`` matrix that is indexed by channel + and broadcast over the ``patch_num`` axis. + """ if not self._use_add_after_patch: return enc_out batch_size = enc_out.shape[0] // n_vars ids = torch.arange(n_vars, device=enc_out.device).repeat(batch_size) - emb = self.entity_embedding(ids) + if self._use_transparent_after_patch: + # (N, d_model) projection of the fixed identifier table. + per_channel = self.id_proj(self.id_table) # (N, d_model) + emb = per_channel[ids] # (batch * n_vars, d_model) + else: + emb = self.entity_embedding(ids) # (batch * n_vars, d_model) return enc_out + emb.unsqueeze(1) def forecast(self, x_enc, x_mark_enc, x_dec, x_mark_dec): diff --git a/liulian/pipeline.py b/liulian/pipeline.py index 73b01c7..6904a1d 100644 --- a/liulian/pipeline.py +++ b/liulian/pipeline.py @@ -323,6 +323,9 @@ def print_dataset_summary(dataset: Any) -> None: _MODEL_INJECTABLE_TRANSPARENT_MODES = frozenset({'onehot', 'coordinates', 'sinusoidal', 'random', 'numeric_id'}) +# Transparent modes PatchTST can inject via add_after_patch (projected fixed +# table, post-instance-norm). Kept in sync with patchtst._TRANSPARENT_ADD_AFTER_PATCH_MODES. +_PATCHTST_TRANSPARENT_ADD_AFTER_PATCH_MODES = frozenset({'onehot', 'sinusoidal', 'random', 'coordinates', 'numeric_id'}) def _transparent_injection_kind(config: Dict[str, Any]) -> str | None: @@ -351,6 +354,11 @@ def _transparent_injection_kind(config: Dict[str, Any]) -> str | None: "id_injection='data'." ) return None + # PatchTST transparent add_after_patch injects INTERNALLY (project the + # fixed table to d_model post-norm); no external wrapper — handled in + # build_model's config-injection block, not here. + if config.get('model') == 'patchtst' and config.get('id_integration') == 'add_after_patch': + return None return 'multi_channel' if config.get('split_mode') == 'multi_channel' else 'per_entity' @@ -483,6 +491,28 @@ def build_model(config: Dict[str, Any], dataset: Any = None) -> Any: features, ) + # PatchTST transparent add_after_patch: PatchTST builds the fixed (N,D) + # identifier table INTERNALLY and projects it to d_model post-norm (no + # external wrapper — _transparent_injection_kind returns None for this + # case). Surface the data-derived bits it needs onto config BEFORE ns is + # built. enc_in stays = #channels (post-patch injection, not concat). + if ( + model_name == 'patchtst' + and config.get('id_integration') == 'add_after_patch' + and config.get('identifier_mode') in _PATCHTST_TRANSPARENT_ADD_AFTER_PATCH_MODES + ): + if dataset is not None: + _topo = getattr(dataset, 'topology', None) + if _topo is not None and config.get('coordinates') is None: + config['coordinates'] = getattr(_topo, 'coordinates', None) + if config.get('station_ids') is None: + _sids = getattr(dataset, 'station_ids', None) + if _sids is not None: + config['station_ids'] = [str(s) for s in _sids] + config.setdefault('sinusoidal_dim', config.get('sinusoidal_dim', 16)) + config.setdefault('random_identifier_dim', config.get('random_identifier_dim', 16)) + config.setdefault('random_identifier_seed', config.get('random_identifier_seed', 2026)) + ns = SimpleNamespace(**config) # Pre-processing for LLM-based models @@ -492,10 +522,14 @@ def build_model(config: Dict[str, Any], dataset: Any = None) -> Any: if config.get('id_integration') == 'add_after_patch': if not model_name == 'patchtst': raise ValueError( - "id_integration='add_after_patch' is only supported for model='patchtst' with identifier_mode='embedding'." + "id_integration='add_after_patch' is only supported for model='patchtst' " + "with identifier_mode='embedding' or a transparent mode." ) - if not config.get('identifier_mode') == 'embedding': - logger.warning('id_integration=add_after_patch has no effect when identifier_mode is not embedding.') + if ( + config.get('identifier_mode') != 'embedding' + and config.get('identifier_mode') not in _PATCHTST_TRANSPARENT_ADD_AFTER_PATCH_MODES + ): + logger.warning('id_integration=add_after_patch has no effect for this identifier_mode.') # Dynamic import for all models _module_name = model_name diff --git a/tests/models/torch/test_patchtst.py b/tests/models/torch/test_patchtst.py index e5f5499..8a782d3 100644 --- a/tests/models/torch/test_patchtst.py +++ b/tests/models/torch/test_patchtst.py @@ -241,3 +241,140 @@ def test_classification(self, classification_config, sample_classification_input model = PatchTSTAdapter(classification_config) outputs = model.run(sample_classification_inputs) validate_classification_output(outputs, 4, 10) + + +class TestPatchTSTTransparentAddAfterPatch: + """PatchTST transparent identifiers via ``add_after_patch``. + + Transparent modes (onehot/sinusoidal/random/coordinates/numeric_id) can be + injected in d_model patch-token space (downstream of the per-channel + instance-norm) via a learned projection of a FIXED identifier table, instead + of the pre-norm ``concat_to_x`` path. This bypasses the instance-norm that + would otherwise erase a per-channel time-invariant identifier. + """ + + N = 6 # channels / stations + + def _make_ns(self, mode, integration='add_after_patch', split_mode='multi_channel'): + from types import SimpleNamespace + + return SimpleNamespace( + task_name='long_term_forecast', + seq_len=64, + pred_len=8, + enc_in=self.N, + dec_in=self.N, + c_out=self.N, + d_model=16, + n_heads=2, + e_layers=1, + d_ff=32, + dropout=0.0, + activation='gelu', + factor=1, + split_mode=split_mode, + identifier_mode=mode, + id_integration=integration, + sinusoidal_dim=8, + random_identifier_dim=8, + random_identifier_seed=2026, + coordinates={str(i): (float(i), float(2 * i)) for i in range(self.N)}, + station_ids=[str(i) for i in range(self.N)], + ) + + @pytest.mark.parametrize( + 'mode,expected_dim', + [ + ('onehot', N), + ('numeric_id', 1), + ('sinusoidal', 8), + ('random', 8), + ('coordinates', 2), + ], + ) + def test_builds_projection_with_correct_dims(self, mode, expected_dim): + """The fixed table + learned projection are built with matching dims.""" + from liulian.models.torch.patchtst import Model + + model = Model(self._make_ns(mode), patch_len=16, stride=8) + assert model._use_transparent_after_patch is True + assert model._use_embedding_after_patch is False + assert model._use_add_after_patch is True + assert hasattr(model, 'id_table') + assert hasattr(model, 'id_proj') + assert tuple(model.id_table.shape) == (self.N, expected_dim) + assert model.id_proj.in_features == expected_dim + assert model.id_proj.out_features == 16 + # The fixed table must NOT be a trainable parameter (it is a buffer); + # only the projection is learned. + param_names = {n for n, _ in model.named_parameters()} + assert not any('id_table' in n for n in param_names) + assert any('id_proj' in n for n in param_names) + + def test_forward_runs_and_is_finite(self): + """Forward pass produces a finite, correctly shaped forecast.""" + import torch + + from liulian.models.torch.patchtst import Model + + model = Model(self._make_ns('onehot'), patch_len=16, stride=8).eval() + B = 4 + x = torch.randn(B, 64, self.N) + mark = torch.zeros(B, 64, 1) + dec = torch.zeros(B, 8, self.N) + with torch.no_grad(): + out = model(x, mark, dec, mark) + assert out.shape == (B, 8, self.N) + assert torch.isfinite(out).all() + + def test_identifier_survives_instance_norm(self): + """The injected identifier changes the output (survives instance-norm). + + Contrast with a per-channel constant added pre-norm, which instance-norm + erases. Zeroing the projection turns the injection into a no-op; the + output must differ from the trained (non-zero) projection. + """ + import torch + + from liulian.models.torch.patchtst import Model + + torch.manual_seed(0) + model = Model(self._make_ns('onehot'), patch_len=16, stride=8).eval() + B = 4 + x = torch.randn(B, 64, self.N) + mark = torch.zeros(B, 64, 1) + dec = torch.zeros(B, 8, self.N) + with torch.no_grad(): + out_with_id = model(x, mark, dec, mark) + model.id_proj.weight.zero_() + model.id_proj.bias.zero_() + out_no_id = model(x, mark, dec, mark) + # Non-trivial difference => the post-patch identifier is NOT erased. + assert (out_with_id - out_no_id).abs().max().item() > 1e-4 + + def test_embedding_path_unchanged(self): + """The learned ``embedding`` add_after_patch path is untouched.""" + from liulian.models.torch.patchtst import Model + + model = Model(self._make_ns('embedding'), patch_len=16, stride=8) + assert model._use_embedding_after_patch is True + assert model._use_transparent_after_patch is False + assert hasattr(model, 'entity_embedding') + assert not hasattr(model, 'id_proj') + assert not hasattr(model, 'id_table') + + def test_requires_multi_channel(self): + """add_after_patch is rejected outside multi_channel split.""" + from liulian.models.torch.patchtst import Model + + with pytest.raises(ValueError, match='multi_channel'): + Model(self._make_ns('onehot', split_mode='per_entity'), patch_len=16, stride=8) + + def test_concat_to_x_does_not_build_projection(self): + """Transparent + concat_to_x keeps the legacy (no internal) path.""" + from liulian.models.torch.patchtst import Model + + model = Model(self._make_ns('onehot', integration='concat_to_x'), patch_len=16, stride=8) + assert model._use_transparent_after_patch is False + assert model._use_add_after_patch is False + assert not hasattr(model, 'id_proj') From 66a5a800d01073c26d71e4bf370f63b1ae9165d0 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Tue, 16 Jun 2026 13:13:35 +0200 Subject: [PATCH 19/21] fix(hpo): stable Ray experiment name when resuming (cross-walltime resume) build_hpo_experiment_name appended a timestamp unconditionally, so every resubmit after a walltime TIMEOUT started a FRESH Ray experiment and re-ran all trials from scratch. Harmless when a cell finishes within one walltime (swiss/electricity), but the big traffic cell (>12h, 50 trials) never finished: 3 fresh experiments, ~129 trials, 0 cells done, ~36 GPU-h wasted. Now the timestamp is omitted when hpo_resume is set, so a resumed run targets the SAME experiment dir and Ray continues the remaining trials. Each matrix cell has its own hpo_storage_path, so the stable name stays unique per cell. Non-resume runs keep the timestamp. Co-Authored-By: Claude Fable 5 --- liulian/pipeline.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/liulian/pipeline.py b/liulian/pipeline.py index 6904a1d..b7dc419 100644 --- a/liulian/pipeline.py +++ b/liulian/pipeline.py @@ -93,11 +93,18 @@ def parse_metric_names(value: str | list[str]) -> list[str]: def build_hpo_experiment_name(config: Dict[str, Any]) -> str: """Build a Ray Tune experiment name following the naming convention. - Pattern: ``{data}_{model}_{task}_{split_mode}[_{extra}]_{timestamp}`` + Pattern: ``{data}_{model}_{task}_{split_mode}[_{extra}][_{timestamp}]``. + + The timestamp is OMITTED when ``hpo_resume`` is set, so a resumed run + targets the SAME Ray experiment directory across walltime restarts. With a + timestamp every restart is a fresh experiment that re-runs all trials from + scratch — which is exactly what broke cross-12h resume on the big traffic + cell (single cell > walltime, 3 fresh experiments, ~129 trials, none + finished). Each matrix cell already has its own ``hpo_storage_path``, so a + stable name is unique per cell. """ import datetime - ts = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') parts = [ config.get('data', 'data'), config.get('model', 'model'), @@ -107,7 +114,8 @@ def build_hpo_experiment_name(config: Dict[str, Any]) -> str: extra = config.get('hpo_experiment_name') if extra: parts.append(extra) - parts.append(ts) + if not config.get('hpo_resume', False): + parts.append(datetime.datetime.now().strftime('%Y%m%d_%H%M%S')) return '_'.join(parts) From 302e93db61ee5916154c7f5ef018f14d773b6404 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Tue, 16 Jun 2026 16:01:52 +0200 Subject: [PATCH 20/21] feat(run_job): --patchtst-transparent-add-after-patch flag (ablation env passthrough) sbatch is a fresh shell, so the matrix opt-in env var must be exported in the job script. ExperimentParams + CLI flag now export LIULIAN_PATCHTST_TRANSPARENT_ADD_AFTER_PATCH=1 when set, enabling the task #40 ablation to run on the cluster (paygo). Co-Authored-By: Claude Fable 5 --- experiments/entity_identifier/run_job.py | 136 +++++++++++++++-------- 1 file changed, 88 insertions(+), 48 deletions(-) diff --git a/experiments/entity_identifier/run_job.py b/experiments/entity_identifier/run_job.py index 07d0aee..b6824dc 100644 --- a/experiments/entity_identifier/run_job.py +++ b/experiments/entity_identifier/run_job.py @@ -87,20 +87,24 @@ class ExperimentParams: (e.g. CPU-only) are applied through ``ResourceProfile.param_overrides``. """ - phase: str = 'full' # dry | smoke | dev | full - run_tag: str = '' # '' -> run.py auto-timestamps + phase: str = 'full' # dry | smoke | dev | full + run_tag: str = '' # '' -> run.py auto-timestamps datasets: tuple[str, ...] = ('swiss-river-1990',) models: tuple[str, ...] = ('lstm', 'patchtst', 'dlinear') modes: tuple[str, ...] = ('none', 'embedding', 'onehot') seeds: tuple[int, ...] = (2026,) - hpo_num_samples: int | None = 50 # paper-grade trials, matches the 2026-05 baseline matrix - train_epochs: int | None = 30 # matches per-experiment config default (30) - max_train_samples: int | None = None # cap for fast pipeline checks - hpo_max_concurrent: int | None = None # None -> auto policy in matrix.py - hpo_resources_gpu: float | None = None # None -> auto policy in matrix.py - resume: bool = True # skip ok cells + Ray hpo_resume - skip_compare: bool = True # skip post-run comparison report - output_root: str = '' # '' -> run.py default + hpo_num_samples: int | None = 50 # paper-grade trials, matches the 2026-05 baseline matrix + train_epochs: int | None = 30 # matches per-experiment config default (30) + max_train_samples: int | None = None # cap for fast pipeline checks + hpo_max_concurrent: int | None = None # None -> auto policy in matrix.py + hpo_resources_gpu: float | None = None # None -> auto policy in matrix.py + resume: bool = True # skip ok cells + Ray hpo_resume + skip_compare: bool = True # skip post-run comparison report + output_root: str = '' # '' -> run.py default + # Ablation opt-in (task #40): route patchtst transparent modes through + # add_after_patch instead of concat_to_x. Passed to the cluster job as the + # env var matrix.build_cli_overrides reads (sbatch is a fresh shell). + patchtst_transparent_add_after_patch: bool = False # Default experiment. Edit here, or override per-field on the command line. @@ -147,7 +151,7 @@ class ResourceProfile: """SBATCH resources for a mode, plus any per-mode run.py overrides.""" name: str - kind: str # 'sbatch' | 'local' + kind: str # 'sbatch' | 'local' account: str = 'gratis' partition: str = 'gpu' qos: str = 'job_gratis' @@ -163,37 +167,58 @@ class ResourceProfile: RESOURCE_PROFILES: dict[str, ResourceProfile] = { # In-process, debuggable. Small concurrency so a debug run stays light. 'local': ResourceProfile( - name='local', kind='local', + name='local', + kind='local', param_overrides={'hpo_max_concurrent': 1}, ), # UBELIX free full tier. 'gratis-gpu': ResourceProfile( - name='gratis-gpu', kind='sbatch', - account='gratis', partition='gpu', qos='job_gratis', - gres='gpu:rtx4090:1', time_limit='12:00:00', - cpus_per_task=4, mem_per_cpu='10G', + name='gratis-gpu', + kind='sbatch', + account='gratis', + partition='gpu', + qos='job_gratis', + gres='gpu:rtx4090:1', + time_limit='12:00:00', + cpus_per_task=4, + mem_per_cpu='10G', ), # UBELIX free preemptable tier (investor idle GPUs; max wall 6h). 'preempt-gpu': ResourceProfile( - name='preempt-gpu', kind='sbatch', - account='gratis', partition='gpu-invest', qos='job_gpu_preemptable', - gres='gpu:rtx4090:1', time_limit='06:00:00', - cpus_per_task=4, mem_per_cpu='10G', + name='preempt-gpu', + kind='sbatch', + account='gratis', + partition='gpu-invest', + qos='job_gpu_preemptable', + gres='gpu:rtx4090:1', + time_limit='06:00:00', + cpus_per_task=4, + mem_per_cpu='10G', ), # UBELIX paid tier — COSTS MONEY. Log via ubelix_cost_tracker.py. 'paygo-gpu': ResourceProfile( - name='paygo-gpu', kind='sbatch', - account='paygo', partition='gpu', qos='job_gpu', + name='paygo-gpu', + kind='sbatch', + account='paygo', + partition='gpu', + qos='job_gpu', wckey='inf_prg-research', - gres='gpu:rtx4090:1', time_limit='12:00:00', - cpus_per_task=4, mem_per_cpu='10G', + gres='gpu:rtx4090:1', + time_limit='12:00:00', + cpus_per_task=4, + mem_per_cpu='10G', ), # UBELIX CPU partition (no GPU). Ray trials run CPU-only. 'cpu': ResourceProfile( - name='cpu', kind='sbatch', - account='gratis', partition='epyc2,bdw', qos='job_gratis', - gres=None, time_limit='12:00:00', - cpus_per_task=8, mem_per_cpu='10G', + name='cpu', + kind='sbatch', + account='gratis', + partition='epyc2,bdw', + qos='job_gratis', + gres=None, + time_limit='12:00:00', + cpus_per_task=8, + mem_per_cpu='10G', param_overrides={'hpo_resources_gpu': 0.0}, ), } @@ -202,15 +227,16 @@ class ResourceProfile: # --------------------------------------------------------------------------- # # Builders / runners # # --------------------------------------------------------------------------- # -def _resolve_params( - profile: ResourceProfile, params: ExperimentParams -) -> ExperimentParams: +def _resolve_params(profile: ResourceProfile, params: ExperimentParams) -> ExperimentParams: """Apply a profile's per-mode overrides on top of the shared params.""" return replace(params, **profile.param_overrides) if profile.param_overrides else params def build_sbatch_script( - profile: ResourceProfile, run_argv: list[str], job_suffix: str = '' + profile: ResourceProfile, + run_argv: list[str], + job_suffix: str = '', + patchtst_transparent_add_after_patch: bool = False, ) -> str: """Render the sbatch script for a cluster mode.""" job_name = f'eid.{profile.name}' + (f'.{job_suffix}' if job_suffix else '') @@ -239,6 +265,10 @@ def build_sbatch_script( f'module load {PYTHON_MODULE}', f'source "{VENV_ACTIVATE}"', f'export FUNCTION_SIZE_ERROR_THRESHOLD={RAY_FN_SIZE_THRESHOLD}', + ] + if patchtst_transparent_add_after_patch: + lines.append('export LIULIAN_PATCHTST_TRANSPARENT_ADD_AFTER_PATCH=1') + lines += [ f'cd "{PROJECT_ROOT}"', 'python3 --version', payload, @@ -247,15 +277,18 @@ def build_sbatch_script( return '\n'.join(lines) -def submit_sbatch( - profile: ResourceProfile, params: ExperimentParams, dry_run: bool -) -> None: +def submit_sbatch(profile: ResourceProfile, params: ExperimentParams, dry_run: bool) -> None: """Build and submit (or print) an sbatch job.""" resolved = _resolve_params(profile, params) run_argv = build_run_argv(resolved) # run_tag in the job name keeps concurrent submissions distinguishable # in squeue and in the outputs/errors filenames. - script = build_sbatch_script(profile, run_argv, job_suffix=resolved.run_tag) + script = build_sbatch_script( + profile, + run_argv, + job_suffix=resolved.run_tag, + patchtst_transparent_add_after_patch=resolved.patchtst_transparent_add_after_patch, + ) (PROJECT_ROOT / 'outputs').mkdir(parents=True, exist_ok=True) (PROJECT_ROOT / 'errors').mkdir(parents=True, exist_ok=True) if dry_run: @@ -265,11 +298,11 @@ def submit_sbatch( print(script) return if profile.name == 'paygo-gpu': - print('[run_job] ⚠️ PAYGO is a PAID tier — read the printed cost block, ' - 'then log the job with jobs/ubelix_cost_tracker.py.') - result = subprocess.run( - ['sbatch'], input=script, text=True, capture_output=True, check=False - ) + print( + '[run_job] ⚠️ PAYGO is a PAID tier — read the printed cost block, ' + 'then log the job with jobs/ubelix_cost_tracker.py.' + ) + result = subprocess.run(['sbatch'], input=script, text=True, capture_output=True, check=False) print(result.stdout.strip()) if result.returncode != 0: raise RuntimeError(f'sbatch failed: {result.stderr.strip()}') @@ -292,7 +325,7 @@ def run_local(params: ExperimentParams) -> dict[str, Any]: argv = build_run_argv(resolved) print(f'[run_job] LOCAL in-process call → run.main({argv})') - return run_mod.main(argv) # ← put a breakpoint inside main() to debug + return run_mod.main(argv) # ← put a breakpoint inside main() to debug # --------------------------------------------------------------------------- # @@ -301,15 +334,14 @@ def run_local(params: ExperimentParams) -> dict[str, Any]: def _build_parser() -> argparse.ArgumentParser: p = argparse.ArgumentParser( description='Job launcher for the entity-identifier experiment ' - '(local in-process debug, or sbatch on UBELIX tiers).', + '(local in-process debug, or sbatch on UBELIX tiers).', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=__doc__, ) - p.add_argument('--mode', required=True, - choices=sorted(RESOURCE_PROFILES), - help='Execution backend / resource profile.') - p.add_argument('--dry-run', action='store_true', - help='Cluster modes: print the sbatch script without submitting.') + p.add_argument( + '--mode', required=True, choices=sorted(RESOURCE_PROFILES), help='Execution backend / resource profile.' + ) + p.add_argument('--dry-run', action='store_true', help='Cluster modes: print the sbatch script without submitting.') # Optional per-field overrides of EXPERIMENT_PARAMS (default = the dataclass): # default=None -> EXPERIMENT_PARAMS.phase ('full') wins unless overridden. p.add_argument('--phase', default=None, choices=('dry', 'smoke', 'dev', 'full')) @@ -321,6 +353,12 @@ def _build_parser() -> argparse.ArgumentParser: p.add_argument('--hpo-num-samples', type=int, default=None) p.add_argument('--train-epochs', type=int, default=None) p.add_argument('--max-train-samples', type=int, default=None) + p.add_argument( + '--patchtst-transparent-add-after-patch', + action='store_true', + help='Ablation: route patchtst transparent modes through ' + 'add_after_patch (post-instance-norm) instead of concat_to_x.', + ) return p @@ -345,6 +383,8 @@ def _params_from_cli(args: argparse.Namespace) -> ExperimentParams: overrides['train_epochs'] = args.train_epochs if args.max_train_samples is not None: overrides['max_train_samples'] = args.max_train_samples + if args.patchtst_transparent_add_after_patch: + overrides['patchtst_transparent_add_after_patch'] = True return replace(EXPERIMENT_PARAMS, **overrides) if overrides else EXPERIMENT_PARAMS From 579b2b96421e420ea05821d8058fe9c8cd6a5b22 Mon Sep 17 00:00:00 2001 From: jajupmochi Date: Tue, 16 Jun 2026 16:03:19 +0200 Subject: [PATCH 21/21] chore(cost): log June ablation paygo jobs (projected ~3.6 CHF); wire ABLATION_TAGS Monthly cap reset (user, 2026-06-16): the 10 CHF paygo cap is per-month, so June starts fresh (May's 4.42 doesn't count). Ablation = swiss patchtst transparent add_after_patch on paygo, 3 jobs ~1.2 CHF each projected. Filled ABLATION_TAGS so build_entity_id_figures emits the concat-vs-add_after_patch table once results land. Co-Authored-By: Claude Fable 5 --- jobs/ubelix_cost_ledger.json | 43 ++++++++++++++++++++++++++++++-- tools/build_entity_id_figures.py | 6 ++++- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/jobs/ubelix_cost_ledger.json b/jobs/ubelix_cost_ledger.json index 712e97e..fcde6de 100644 --- a/jobs/ubelix_cost_ledger.json +++ b/jobs/ubelix_cost_ledger.json @@ -53,7 +53,46 @@ "actual_chf": 0.9002, "cost_method": "elapsed", "status": "done", - "notes": "Paygo PatchTST resume #2 — onehot only" + "notes": "Paygo PatchTST resume #2 \u2014 onehot only" + }, + { + "jobid": "TBD", + "run_tag": "swiss-ptap-1990-20260616", + "account": "paygo", + "wckey": "inf_prg-research", + "session": "entity-id-2026-06", + "submit_date": "2026-06-16", + "projected_chf": 1.2, + "actual_chf": null, + "status": "running", + "cost_method": "elapsed", + "notes": "ablation: swiss patchtst transparent add_after_patch, 4 modes, 1x4090" + }, + { + "jobid": "TBD", + "run_tag": "swiss-ptap-2010-20260616", + "account": "paygo", + "wckey": "inf_prg-research", + "session": "entity-id-2026-06", + "submit_date": "2026-06-16", + "projected_chf": 1.2, + "actual_chf": null, + "status": "running", + "cost_method": "elapsed", + "notes": "ablation: swiss patchtst transparent add_after_patch, 4 modes, 1x4090" + }, + { + "jobid": "TBD", + "run_tag": "swiss-ptap-zurich-20260616", + "account": "paygo", + "wckey": "inf_prg-research", + "session": "entity-id-2026-06", + "submit_date": "2026-06-16", + "projected_chf": 1.2, + "actual_chf": null, + "status": "running", + "cost_method": "elapsed", + "notes": "ablation: swiss patchtst transparent add_after_patch, 4 modes, 1x4090" } ] -} +} \ No newline at end of file diff --git a/tools/build_entity_id_figures.py b/tools/build_entity_id_figures.py index 91790db..b65dd8d 100644 --- a/tools/build_entity_id_figures.py +++ b/tools/build_entity_id_figures.py @@ -61,7 +61,11 @@ # add_after_patch (post-norm) injection; fill ABLATION_TAGS once those run. # The main table will then show, per patchtst transparent cell, the BETTER of # the two — but the final pick is the user's (see MAIN_PATCHTST_SOURCE). -ABLATION_TAGS: list[str] = [] # add_after_patch run-tags — TBD when #40 runs +ABLATION_TAGS: list[str] = [ # patchtst transparent add_after_patch (task #40, paygo) + 'swiss-ptap-1990-20260616', + 'swiss-ptap-2010-20260616', + 'swiss-ptap-zurich-20260616', +] TRANSPARENT_MODES = ['onehot', 'sinusoidal', 'random', 'coordinates'] # 'concat' | 'add_after_patch' | 'better' — which injection the MAIN summary # uses for patchtst transparent cells. Stays 'concat' until the user decides.