From ee89426fb4ed35318498e92ad2af6f39a503dd54 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Sun, 25 Jan 2026 00:24:57 +0000
Subject: [PATCH 1/9] feat: Implement QuantumGPUClient and hardware interface
Implements the QuantumGPUClient architecture to enable direct, zero-orchestration communication between GPUs and QPUs. This includes:
- `IHardwareQPUInterface`: Abstract base class for QPU hardware interaction (DMA setup, event registration).
- `QuantumGPUClient`: Client class to manage shared buffers and event creation.
- `QPUEvent` and `GPUCompletionEvent`: Classes representing hardware synchronization signals.
- `tests/test_quantum_gpu_client.py`: Unit tests verifying the API and workflow using a mock interface.
This implementation lays the groundwork for ultra-low latency hybrid quantum-classical computing by removing CPU orchestration overheads.
---
tests/test_quantum_gpu_client.py | 95 ++++++++++++++++++
thrml/quantum_gpu_client.py | 159 +++++++++++++++++++++++++++++++
2 files changed, 254 insertions(+)
create mode 100644 tests/test_quantum_gpu_client.py
create mode 100644 thrml/quantum_gpu_client.py
diff --git a/tests/test_quantum_gpu_client.py b/tests/test_quantum_gpu_client.py
new file mode 100644
index 0000000..a3c37a1
--- /dev/null
+++ b/tests/test_quantum_gpu_client.py
@@ -0,0 +1,95 @@
+from typing import Any
+
+import pytest
+
+from thrml.quantum_gpu_client import GPUCompletionEvent, IHardwareQPUInterface, QPUEvent, QuantumGPUClient
+
+
+class MockHardwareQPUInterface(IHardwareQPUInterface):
+ def __init__(self):
+ self.dma_allocations = []
+ self.qpu_events = []
+ self.gpu_events = []
+
+ def setup_dma(self, buffer_size: int, dtype: Any) -> Any:
+ handle = f"dma_handle_{len(self.dma_allocations)}"
+ self.dma_allocations.append((buffer_size, dtype, handle))
+ return handle
+
+ def register_qpu_event(self, signal_address: int) -> Any:
+ handle = f"qpu_event_handle_{signal_address}"
+ self.qpu_events.append((signal_address, handle))
+ return handle
+
+ def register_gpu_completion_event(self, signal_address: int) -> Any:
+ handle = f"gpu_event_handle_{signal_address}"
+ self.gpu_events.append((signal_address, handle))
+ return handle
+
+
+def test_quantum_gpu_client_allocation():
+ interface = MockHardwareQPUInterface()
+ client = QuantumGPUClient(interface)
+
+ size = 1024
+ dtype = "float32"
+ handle = client.allocate_qpu_gpu_buffer(size, dtype)
+
+ assert handle == "dma_handle_0"
+ assert len(interface.dma_allocations) == 1
+ assert interface.dma_allocations[0] == (size, dtype, handle)
+
+
+def test_quantum_gpu_client_events():
+ interface = MockHardwareQPUInterface()
+ client = QuantumGPUClient(interface)
+
+ qpu_addr = 0x1000
+ qpu_event = client.create_qpu_event(qpu_addr)
+
+ assert isinstance(qpu_event, QPUEvent)
+ assert qpu_event.signal_address == qpu_addr
+ assert qpu_event.handle == f"qpu_event_handle_{qpu_addr}"
+ assert len(interface.qpu_events) == 1
+
+ gpu_addr = 0x2000
+ gpu_event = client.create_gpu_completion_event(gpu_addr)
+
+ assert isinstance(gpu_event, GPUCompletionEvent)
+ assert gpu_event.signal_address == gpu_addr
+ assert gpu_event.handle == f"gpu_event_handle_{gpu_addr}"
+ assert len(interface.gpu_events) == 1
+
+
+def test_workflow_simulation():
+ interface = MockHardwareQPUInterface()
+ client = QuantumGPUClient(interface)
+
+ # 1. Initialization
+ buffer_handle = client.allocate_qpu_gpu_buffer(1024, "complex128")
+
+ qpu_signal_addr = 0xCAFE
+ gpu_signal_addr = 0xBABE
+
+ event_from_qpu = client.create_qpu_event(qpu_signal_addr)
+ event_to_qpu = client.create_gpu_completion_event(gpu_signal_addr)
+
+ # 2. Critical Path Simulation (Logical check)
+ # GPU prepares state (mock)
+ # GPU signals QPU
+ event_to_qpu.record() # Should be non-blocking/successful
+
+ # QPU execution (simulated by us just proceeding)
+
+ # QPU writes results and signals GPU (simulated)
+ # GPU waits for QPU
+ event_from_qpu.wait() # Should be non-blocking/successful
+
+ # Verify handles were created correctly
+ assert buffer_handle is not None
+ assert event_from_qpu.signal_address == qpu_signal_addr
+ assert event_to_qpu.signal_address == gpu_signal_addr
+
+
+if __name__ == "__main__":
+ pytest.main([__file__])
diff --git a/thrml/quantum_gpu_client.py b/thrml/quantum_gpu_client.py
new file mode 100644
index 0000000..a820c15
--- /dev/null
+++ b/thrml/quantum_gpu_client.py
@@ -0,0 +1,159 @@
+from abc import ABC, abstractmethod
+from typing import Any
+
+
+class IHardwareQPUInterface(ABC):
+ """
+ Abstract interface for QPU hardware interaction.
+
+ Concrete implementations (e.g., FPGAQPUInterface, ASICQPUInterface) would provide
+ hardware-specific details for DMA setup and event signaling.
+ """
+
+ @abstractmethod
+ def setup_dma(self, buffer_size: int, dtype: Any) -> Any:
+ """
+ Sets up Direct Memory Access (DMA) for a shared buffer between GPU and QPU.
+
+ Args:
+ buffer_size: Size of the buffer to allocate.
+ dtype: Data type of the buffer elements.
+
+ Returns:
+ A handle to the shared memory, accessible by both GPU kernels and QPU hardware.
+ """
+ pass
+
+ @abstractmethod
+ def register_qpu_event(self, signal_address: int) -> Any:
+ """
+ Registers a specific memory-mapped address or interrupt for QPU-to-GPU signaling.
+
+ Args:
+ signal_address: The hardware address (MMIO) the QPU writes to.
+
+ Returns:
+ A handle representing the event registration.
+ """
+ pass
+
+ @abstractmethod
+ def register_gpu_completion_event(self, signal_address: int) -> Any:
+ """
+ Registers a specific memory-mapped address for GPU-to-QPU signaling.
+
+ Args:
+ signal_address: The hardware address (MMIO) the GPU writes to.
+
+ Returns:
+ A handle representing the event registration.
+ """
+ pass
+
+
+class QPUEvent:
+ """
+ Represents a hardware signal generated by the QPU.
+
+ A GPU kernel can wait on this event (e.g., cudaStreamWaitEvent) or
+ the host/GPU can poll the signal address.
+ """
+
+ def __init__(self, signal_address: int, handle: Any):
+ self.signal_address = signal_address
+ self.handle = handle
+
+ def wait(self, stream: Any = None):
+ """
+ Waits for the QPU event.
+
+ Args:
+ stream: Optional CUDA stream. If provided, inserts a wait into the stream.
+ If None, performs a host-side wait (polling).
+ """
+ # In a real implementation, this would interface with CUDA driver/runtime
+ # e.g. cudaStreamWaitEvent(stream, self.handle)
+ pass
+
+
+class GPUCompletionEvent:
+ """
+ Represents a hardware signal the GPU can generate for the QPU.
+
+ A GPU kernel can record this event (e.g. write to address) to signal the QPU.
+ """
+
+ def __init__(self, signal_address: int, handle: Any):
+ self.signal_address = signal_address
+ self.handle = handle
+
+ def record(self, stream: Any = None):
+ """
+ Records the event (signals the QPU).
+
+ Args:
+ stream: Optional CUDA stream. If provided, inserts the signal operation
+ into the stream.
+ """
+ # In a real implementation, this would trigger the GPU to write to the signal_address
+ pass
+
+
+class QuantumGPUClient:
+ """
+ Client for managing zero-copy, zero-orchestration communication
+ between GPU and QPU.
+
+ This client enables:
+ 1. Direct Memory Access (DMA) between QPU and GPU Device Memory.
+ 2. Hardware-Native Event Synchronization (MMIO, interrupts) to bypass CPU orchestration.
+ """
+
+ def __init__(self, qpu_interface: IHardwareQPUInterface):
+ """
+ Initialize the client with a specific QPU hardware interface.
+
+ Args:
+ qpu_interface: An instance of IHardwareQPUInterface.
+ """
+ self.qpu_interface = qpu_interface
+
+ def allocate_qpu_gpu_buffer(self, size: int, dtype: Any) -> Any:
+ """
+ Allocates GPU device memory (or pinned host memory) and registers it
+ with the QPU's driver to enable direct access.
+
+ Args:
+ size: Size of the buffer.
+ dtype: Data type.
+
+ Returns:
+ A handle to the shared memory.
+ """
+ return self.qpu_interface.setup_dma(size, dtype)
+
+ def create_qpu_event(self, qpu_signal_address: int) -> QPUEvent:
+ """
+ Creates an event object representing a signal FROM the QPU.
+
+ Args:
+ qpu_signal_address: The memory address the QPU writes to signal completion.
+
+ Returns:
+ A QPUEvent object.
+ """
+ handle = self.qpu_interface.register_qpu_event(qpu_signal_address)
+ return QPUEvent(qpu_signal_address, handle)
+
+ def create_gpu_completion_event(self, gpu_signal_address: int) -> GPUCompletionEvent:
+ """
+ Creates an event object representing a signal TO the QPU.
+
+ Args:
+ gpu_signal_address: The memory address the GPU writes to signal readiness.
+
+ Returns:
+ A GPUCompletionEvent object.
+ """
+ handle = self.qpu_interface.register_gpu_completion_event(gpu_signal_address)
+ return GPUCompletionEvent(gpu_signal_address, handle)
From 36c1ba21634d65bdb2ee3c6c95ebaf68a8d2ddee Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 3 Feb 2026 14:38:14 +0000
Subject: [PATCH 2/9] Initial plan
From 806a5ca8b3b0b15d01492ec5c68684b2c0b865c2 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 3 Feb 2026 14:41:43 +0000
Subject: [PATCH 3/9] Add Next.js application structure with Recharts
integration
Co-authored-by: igor-holt <125706350+igor-holt@users.noreply.github.com>
---
.gitignore | 13 +-
app/globals.css | 39 +
app/layout.tsx | 18 +
app/page.tsx | 31 +
app/telemetry/page.tsx | 9 +
components/Card.tsx | 14 +
components/TelemetryTimeline.tsx | 71 ++
next.config.js | 6 +
package-lock.json | 1381 ++++++++++++++++++++++++++++++
package.json | 33 +
tsconfig.json | 26 +
11 files changed, 1640 insertions(+), 1 deletion(-)
create mode 100644 app/globals.css
create mode 100644 app/layout.tsx
create mode 100644 app/page.tsx
create mode 100644 app/telemetry/page.tsx
create mode 100644 components/Card.tsx
create mode 100644 components/TelemetryTimeline.tsx
create mode 100644 next.config.js
create mode 100644 package-lock.json
create mode 100644 package.json
create mode 100644 tsconfig.json
diff --git a/.gitignore b/.gitignore
index 57be4dd..d460db6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -163,4 +163,15 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
-.vscode/
\ No newline at end of file
+.vscode/
+
+# Next.js
+/.next/
+/out/
+next-env.d.ts
+
+# Node.js
+/node_modules/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
\ No newline at end of file
diff --git a/app/globals.css b/app/globals.css
new file mode 100644
index 0000000..0bb64e0
--- /dev/null
+++ b/app/globals.css
@@ -0,0 +1,39 @@
+* {
+ box-sizing: border-box;
+ padding: 0;
+ margin: 0;
+}
+
+html,
+body {
+ max-width: 100vw;
+ overflow-x: hidden;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
+}
+
+a {
+ color: inherit;
+ text-decoration: none;
+}
+
+.card {
+ background: #fff;
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ padding: 20px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+h1 {
+ font-size: 2.5rem;
+ margin-bottom: 1rem;
+}
+
+h2 {
+ font-size: 1.8rem;
+ margin-bottom: 1rem;
+}
+
+table {
+ font-size: 0.9rem;
+}
diff --git a/app/layout.tsx b/app/layout.tsx
new file mode 100644
index 0000000..1df9756
--- /dev/null
+++ b/app/layout.tsx
@@ -0,0 +1,18 @@
+import './globals.css';
+
+export const metadata = {
+ title: 'THRML Telemetry',
+ description: 'Telemetry visualization for THRML',
+};
+
+export default function RootLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+
{children}
+
+ );
+}
diff --git a/app/page.tsx b/app/page.tsx
new file mode 100644
index 0000000..c1a6b0f
--- /dev/null
+++ b/app/page.tsx
@@ -0,0 +1,31 @@
+import Link from 'next/link';
+
+export default function Home() {
+ return (
+
+
THRML - Thermodynamic HypergRaphical Model Library
+
+ THRML is a JAX library for building and sampling probabilistic graphical models,
+ with a focus on efficient block Gibbs sampling and energy-based models.
+
+
+
+
Telemetry Dashboard
+
+ View real-time telemetry data and visualizations:
+
+
+ Go to Telemetry Timeline
+
+
+
+ );
+}
diff --git a/app/telemetry/page.tsx b/app/telemetry/page.tsx
new file mode 100644
index 0000000..e6b147d
--- /dev/null
+++ b/app/telemetry/page.tsx
@@ -0,0 +1,9 @@
+import TelemetryTimeline from '@/components/TelemetryTimeline';
+
+export default function TelemetryPage() {
+ return (
+
",
+ "main": "index.js",
+ "directories": {
+ "doc": "docs",
+ "example": "examples",
+ "test": "tests"
+ },
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "next": "^16.1.6",
+ "react": "^19.2.4",
+ "react-dom": "^19.2.4",
+ "recharts": "^3.7.0"
+ },
+ "devDependencies": {
+ "@types/node": "^25.2.0",
+ "@types/react": "^19.2.10",
+ "@types/react-dom": "^19.2.3",
+ "typescript": "^5.9.3"
+ }
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..e7ff90f
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./*"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}
From 75810800c6087452d97176cb6371aeb61d77ad94 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 3 Feb 2026 14:42:28 +0000
Subject: [PATCH 4/9] Add telemetry data files and update import paths
Co-authored-by: igor-holt <125706350+igor-holt@users.noreply.github.com>
---
components/TelemetryTimeline.tsx | 2 +-
data/telemetry.ts | 60 ++++++++++++++++++++++++++++++++
data/types.ts | 11 ++++++
3 files changed, 72 insertions(+), 1 deletion(-)
create mode 100644 data/telemetry.ts
create mode 100644 data/types.ts
diff --git a/components/TelemetryTimeline.tsx b/components/TelemetryTimeline.tsx
index 0c9a081..fb99c76 100644
--- a/components/TelemetryTimeline.tsx
+++ b/components/TelemetryTimeline.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import { Card } from './Card';
-import { telemetryData, getProbabilityData } from '../lib/telemetry';
+import { telemetryData, getProbabilityData } from '../data/telemetry';
import {
LineChart,
Line,
diff --git a/data/telemetry.ts b/data/telemetry.ts
new file mode 100644
index 0000000..27b3518
--- /dev/null
+++ b/data/telemetry.ts
@@ -0,0 +1,60 @@
+import { TelemetryEntry } from './types';
+
+export const telemetryData: TelemetryEntry[] = [
+ {
+ logId: '0x004F',
+ utc: '2026-02-01T03:14:15Z',
+ event: 'HighRadiationLikeSpike',
+ prediction: { mode: 'Shear-2', p: 0.61 },
+ outcome: 'TBD',
+ subjective: 'Cold. The static tastes like blue geometry.',
+ },
+ {
+ logId: '0x0050',
+ utc: '2026-02-02T14:22:30Z',
+ event: 'LedgerBurnAlert',
+ prediction: { mode: 'Nominal', p: 0.85 },
+ outcome: 'Mitigated',
+ subjective: 'Warmth fading; credits conserved.',
+ },
+ {
+ logId: '0x0051',
+ utc: '2026-02-02T18:45:00Z',
+ event: 'SubstrateDensityAnomaly',
+ prediction: { mode: 'Resonant-A', p: 0.73 },
+ outcome: 'Monitored',
+ subjective: 'Patterns echo in the void.',
+ },
+ {
+ logId: '0x0052',
+ utc: '2026-02-03T02:10:22Z',
+ event: 'QuantumFluxSpike',
+ prediction: { mode: 'Cascade-3', p: 0.92 },
+ outcome: 'TBD',
+ subjective: 'The flux sings in harmonics.',
+ },
+ {
+ logId: '0x0053',
+ utc: '2026-02-03T08:33:15Z',
+ event: 'EnergyWellDepletion',
+ prediction: { mode: 'Critical', p: 0.45 },
+ outcome: 'Recharging',
+ subjective: 'Dim corridors, faint hum.',
+ },
+ {
+ logId: '0x0054',
+ utc: '2026-02-03T12:15:47Z',
+ event: 'SeismicEventDetected',
+ prediction: { mode: 'Shear-1', p: 0.78 },
+ outcome: 'Logged',
+ subjective: 'Ground trembles with ancient memory.',
+ },
+];
+
+// Utility to extract chart data
+export function getProbabilityData() {
+ return telemetryData.map(entry => ({
+ time: new Date(entry.utc).getTime(), // For x-axis
+ probability: entry.prediction.p,
+ }));
+}
diff --git a/data/types.ts b/data/types.ts
new file mode 100644
index 0000000..b838e96
--- /dev/null
+++ b/data/types.ts
@@ -0,0 +1,11 @@
+export interface TelemetryEntry {
+ logId: string;
+ utc: string;
+ event: string;
+ prediction: {
+ mode: string;
+ p: number;
+ };
+ outcome: string;
+ subjective: string;
+}
From 637708b24122b2fd9a18bdbddef0684ab208d6e6 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 3 Feb 2026 14:43:51 +0000
Subject: [PATCH 5/9] Fix: Add 'use client' directive to TelemetryTimeline
component
Co-authored-by: igor-holt <125706350+igor-holt@users.noreply.github.com>
---
components/TelemetryTimeline.tsx | 2 ++
tsconfig.json | 27 +++++++++++++++++++++------
2 files changed, 23 insertions(+), 6 deletions(-)
diff --git a/components/TelemetryTimeline.tsx b/components/TelemetryTimeline.tsx
index fb99c76..52babc1 100644
--- a/components/TelemetryTimeline.tsx
+++ b/components/TelemetryTimeline.tsx
@@ -1,3 +1,5 @@
+'use client';
+
import React from 'react';
import { Card } from './Card';
import { telemetryData, getProbabilityData } from '../data/telemetry';
diff --git a/tsconfig.json b/tsconfig.json
index e7ff90f..9e9bbf7 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,10 @@
{
"compilerOptions": {
- "lib": ["dom", "dom.iterable", "esnext"],
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -10,7 +14,7 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
- "jsx": "preserve",
+ "jsx": "react-jsx",
"incremental": true,
"plugins": [
{
@@ -18,9 +22,20 @@
}
],
"paths": {
- "@/*": ["./*"]
- }
+ "@/*": [
+ "./*"
+ ]
+ },
+ "target": "ES2017"
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
- "exclude": ["node_modules"]
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ ".next/dev/types/**/*.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
}
From 5ec5ca0bc5db0a6eacd29145c8dd5237409d424f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 3 Feb 2026 14:45:59 +0000
Subject: [PATCH 6/9] Add web application documentation
Co-authored-by: igor-holt <125706350+igor-holt@users.noreply.github.com>
---
WEB_README.md | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 93 insertions(+)
create mode 100644 WEB_README.md
diff --git a/WEB_README.md b/WEB_README.md
new file mode 100644
index 0000000..f141b36
--- /dev/null
+++ b/WEB_README.md
@@ -0,0 +1,93 @@
+# THRML Telemetry Visualization
+
+This directory contains a Next.js web application for visualizing telemetry data from the THRML library.
+
+## Features
+
+- 📊 Interactive Recharts line chart showing probability trends over time
+- 📅 Time-series visualization with formatted UTC timestamps
+- 📋 Detailed data table with all telemetry entries
+- 🎨 Responsive design with clean UI components
+- ⚡ Built with Next.js 16 App Router and TypeScript
+
+## Getting Started
+
+### Prerequisites
+
+- Node.js 18+
+- npm or yarn
+
+### Installation
+
+Install dependencies:
+
+```bash
+npm install
+```
+
+### Development
+
+Run the development server:
+
+```bash
+npm run dev
+```
+
+Open [http://localhost:3000](http://localhost:3000) to view the home page, then navigate to [http://localhost:3000/telemetry](http://localhost:3000/telemetry) to see the telemetry visualization.
+
+### Build
+
+Build for production:
+
+```bash
+npm run build
+npm start
+```
+
+## Project Structure
+
+```
+├── app/
+│ ├── globals.css # Global styles
+│ ├── layout.tsx # Root layout component
+│ ├── page.tsx # Home page
+│ └── telemetry/
+│ └── page.tsx # Telemetry visualization page
+├── components/
+│ ├── Card.tsx # Reusable card component
+│ └── TelemetryTimeline.tsx # Main telemetry chart component
+├── data/
+│ ├── telemetry.ts # Telemetry data and utilities
+│ └── types.ts # TypeScript type definitions
+├── next.config.js # Next.js configuration
+├── tsconfig.json # TypeScript configuration
+└── package.json # Project dependencies
+```
+
+## Technologies
+
+- **Next.js 16**: React framework with App Router
+- **React 19**: UI library
+- **TypeScript**: Type-safe JavaScript
+- **Recharts**: Charting library for data visualization
+- **CSS**: Custom styling for components
+
+## Data Model
+
+The telemetry data includes:
+- **logId**: Unique identifier for each entry
+- **utc**: UTC timestamp
+- **event**: Type of event detected
+- **prediction**: Object containing mode and probability (p)
+- **outcome**: Result of the event
+- **subjective**: Descriptive notes
+
+## Extending
+
+To add more telemetry data, edit `data/telemetry.ts` and add new entries to the `telemetryData` array following the `TelemetryEntry` interface defined in `data/types.ts`.
+
+To customize the chart, modify the Recharts components in `components/TelemetryTimeline.tsx`.
+
+## License
+
+See the main repository LICENSE file.
From bf48889ebe60909198c37a5c7f7af5a7d19698d9 Mon Sep 17 00:00:00 2001
From: Igor Holt
Date: Sat, 7 Mar 2026 05:46:05 -0500
Subject: [PATCH 7/9] Created using Colab
---
examples/01_all_of_thrml.ipynb | 3258 ++++++++++++++++----------------
1 file changed, 1672 insertions(+), 1586 deletions(-)
diff --git a/examples/01_all_of_thrml.ipynb b/examples/01_all_of_thrml.ipynb
index e963287..a4606d3 100644
--- a/examples/01_all_of_thrml.ipynb
+++ b/examples/01_all_of_thrml.ipynb
@@ -1,1598 +1,1684 @@
{
- "cells": [
- {
- "cell_type": "markdown",
- "id": "4bf7c6cf",
- "metadata": {},
- "source": [
- "# All of THRML"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "8f065cdefd22f21e",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "THRML is a simple library for simulating probabilistic computers on GPUs. \n",
- "\n",
- "\n",
- "Concretely, THRML provides tools for GPU accelerating block sampling algorithms on sparse, heterogeneous probabilistic graphical models (PGMs) like the ones that Extropic hardware runs. The primary function of THRML is to be a scaffold that makes it much easier to implement any desired block sampling algorithm than it would be to do so from scratch. As such, this notebook will walk you through the main set of tools that THRML exposes that you can use in your own explorations. "
- ]
- },
- {
- "cell_type": "markdown",
- "id": "5ee0766846938947",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "We will demonstrate the capabilities of THRML by using it to implement the Gibbs sampling algorithm for a Gaussian PGM.\n\nGibbs sampling is obviously not a practical numerical method for Gaussian sampling in most cases, and should probably instead be handled using the [Cholesky decomposition](https://en.wikipedia.org/wiki/Cholesky_decomposition). We implement it here solely for the purposes of demonstrating THRML, which in reality will be used to attack more complex problems that can't be treated analytically.\n\n"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "5096dab893ef7148",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "Specifically, in the first part of this example we will consider a PGM that embodies the Gaussian distribution\n",
- "\n",
- "$$P(x) \\propto e^{-E_G(x)}$$\n",
- "\n",
- "Where the energy function $E_G(x)$ is,\n",
- "\n",
- "$$E_G(x) = \\frac{1}{2} \\left(x - \\mu \\right)^T A \\left( x - \\mu \\right) $$\n",
- "\n",
- "and $A = \\Sigma^{-1}$, where $\\Sigma$ is the covariance matrix of the distribution.\n",
- "\n",
- "We can expand this and write the energy function as a sum of terms,\n",
- "\n",
- "$$E_G(x) + C = \\frac{1}{2} \\sum_i A_{ii} \\: x_i^2 + \\sum_{j>i} A_{ij} \\: x_i \\: x_j + \\sum_i b_i \\: x_i$$\n",
- "\n",
- "\n",
- "Where $C$ is a constant independent of $x$, and $b = -\\mu^T A$ is a biasing vector.\n",
- "\n",
- "This form makes the graphical interpretation of the problem clear. Each of the variables $x_i$ can be represented by a node, and the nonzero matrix elements of $A$ define edges between the nodes. \n"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "60ee0c3a9d0d3139",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "We will use the Gibbs sampling algorithm to draw samples from our Gaussian distribution. To do this, we first identify the distribution of each $x_i$ conditioned on the rest of the graph,\n",
- "\n",
- "$$P(x_i | x_{nb(i)}) \\propto e^{-E_i (x_i ,x_{nb(i)})}$$\n",
- "\n",
- "$$E_i (x_i ,x_{nb(i)}) = \\frac{1}{2} A_{ii} \\: x_i^2 + x_i \\left( \\sum_{j \\in nb(i)} \\: A_{ij} \\: x_j + b_i \\right)$$\n",
- "\n",
- "where $nb(i)$ indicates the neighbours of node i, which in this case is all j such that $A_{ij} \\neq 0$. This form makes it clear that Gibbs sampling is local, i.e the state of each node is updated using only information about nodes that it is directly connected to.\n",
- "\n",
- "We can write this in a different form that makes it obvious that the conditional is Gaussian,\n",
- "\n",
- "$$E_i (x_i ,x_{nb(i)}) + D = \\frac{1}{2} (x_i - m_i) A_{ii} (x_i - m_i) $$\n",
- "\n",
- "$$ m_i = - \\left( \\sum_{j \\in nb(i)} \\frac{A_{ij}}{A_{ii}} x_j + \\frac{b_i}{A_{ii}} \\right) $$\n",
- "\n",
- "where $D$ is a constant independent of $x_i$.\n",
- "\n",
- "The Gibbs sampling algorithm works by iteratively updating each of the $x_i$ according to this conditional distribution. In chromatic Gibbs sampling, nodes that belong to the same color group are updated in parallel. This \"blocked\" version is what we will implement here."
- ]
- },
- {
- "cell_type": "markdown",
- "id": "dea608a1d20ea180",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "With the math out of the way, we can proceed with the implementation of our sampling algorithm using THRML. First, let's get some imports out of the way:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 27,
- "id": "dfd9f494512409ce",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:16.386902328Z",
- "start_time": "2025-08-28T16:29:15.573490016Z"
- }
- },
- "outputs": [],
- "source": [
- "import random\n",
- "from collections import defaultdict\n",
- "from typing import Hashable, Mapping\n",
- "\n",
- "import equinox as eqx\n",
- "import jax\n",
- "import jax.numpy as jnp\n",
- "import matplotlib.pyplot as plt\n",
- "import networkx as nx\n",
- "import numpy as np\n",
- "from jaxtyping import Array, Key, PyTree\n",
- "from sklearn.decomposition import PCA\n",
- "from sklearn.preprocessing import StandardScaler"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 28,
- "id": "b6318cf59688b42",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:16.428988580Z",
- "start_time": "2025-08-28T16:29:16.428562851Z"
- }
- },
- "outputs": [],
- "source": [
- "from thrml.block_management import Block\n",
- "from thrml.block_sampling import (\n",
- " BlockGibbsSpec,\n",
- " BlockSamplingProgram,\n",
- " sample_states,\n",
- " sample_with_observation,\n",
- " SamplingSchedule,\n",
- ")\n",
- "from thrml.conditional_samplers import (\n",
- " _SamplerState,\n",
- " _State,\n",
- " AbstractConditionalSampler,\n",
- ")\n",
- "from thrml.factor import AbstractFactor, FactorSamplingProgram\n",
- "from thrml.interaction import InteractionGroup\n",
- "from thrml.models.discrete_ebm import SpinEBMFactor, SpinGibbsConditional\n",
- "from thrml.observers import MomentAccumulatorObserver\n",
- "from thrml.pgm import AbstractNode"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "6c3e91042f1a83",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "Next, we will define our graph. In THRML, nodes that represent random variables with different data types (binary, categorical, continuous, etc.) are identified using distinct classes that inherit from `AbstractNode`. For our problem we only have one type of node, which we will define now,\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 29,
- "id": "dae80e1827e8f902",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:16.429239088Z",
- "start_time": "2025-08-28T16:29:16.428923209Z"
- }
- },
- "outputs": [],
- "source": [
- "class ContinuousNode(AbstractNode):\n",
- " pass"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "987fda7dfe6b8b19",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "We will now use the existing python graph library NetworkX to construct a grid graph of our nodes."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 30,
- "id": "d8402db6280bd6fd",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:16.430081203Z",
- "start_time": "2025-08-28T16:29:16.429095796Z"
- }
- },
- "outputs": [],
- "source": [
- "def generate_grid_graph(\n",
- " *side_lengths: int,\n",
- ") -> tuple[\n",
- " tuple[list[ContinuousNode], list[ContinuousNode]], tuple[list[ContinuousNode], list[ContinuousNode]], nx.Graph\n",
- "]:\n",
- " G = nx.grid_graph(dim=side_lengths, periodic=False)\n",
- "\n",
- " coord_to_node = {coord: ContinuousNode() for coord in G.nodes}\n",
- " nx.relabel_nodes(G, coord_to_node, copy=False)\n",
- "\n",
- " for coord, node in coord_to_node.items():\n",
- " G.nodes[node][\"coords\"] = coord\n",
- "\n",
- " # an aperiodic grid is always 2-colorable\n",
- " bicol = nx.bipartite.color(G)\n",
- " color0 = [n for n, c in bicol.items() if c == 0]\n",
- " color1 = [n for n, c in bicol.items() if c == 1]\n",
- "\n",
- " u, v = map(list, zip(*G.edges()))\n",
- "\n",
- " return (bicol, color0, color1), (u, v), G\n",
- "\n",
- "\n",
- "def plot_grid_graph(\n",
- " G: nx.Graph,\n",
- " bicol: Mapping[Hashable, int],\n",
- " ax: plt.Axes,\n",
- " *,\n",
- " node_size: int = 300,\n",
- " colors: tuple[str, str] = (\"black\", \"orange\"),\n",
- " **draw_kwargs,\n",
- "):\n",
- " pos = {n: G.nodes[n][\"coords\"][:2] for n in G.nodes}\n",
- "\n",
- " node_colors = [colors[bicol[n]] for n in G.nodes]\n",
- "\n",
- " nx.draw(\n",
- " G,\n",
- " pos=pos,\n",
- " ax=ax,\n",
- " node_color=node_colors,\n",
- " node_size=node_size,\n",
- " edgecolors=\"k\",\n",
- " linewidths=0.8,\n",
- " width=1.0,\n",
- " with_labels=False,\n",
- " **draw_kwargs,\n",
- " )"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 31,
- "id": "82d08d060db3e98e",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:16.474066145Z",
- "start_time": "2025-08-28T16:29:16.429184644Z"
- }
- },
- "outputs": [
+ "cells": [
{
- "data": {
- "image/png": "iVBORw0KGgoAAAANSUhEUgAAAgMAAAGFCAYAAABg2vAPAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvc2/+5QAAAAlwSFlzAAAPYQAAD2EBqD+naQAASipJREFUeJzt3Qd4FUX7NvA7/YSEjnQQkCpICz2U0EWaoEjvIEoTAbGADVF6kSrSka506dJCJwm99957S8/5rmcQ/7x+osk5u2RP5v5dFy++mPOckd1n59nZ2Rk3u91uBxEREWnLPbEbQERERImLxQAREZHmWAwQERFpjsUAERGR5lgMEBERaY7FABERkeZYDBAREWmOxQAREZHmWAwQERFpjsUAERGR5lgMEBERaY7FABERkeZYDBAREWmOxQAREZHmWAwQERFpjsUAERGR5lgMEBERaY7FABERkeZYDBAREWmOxQAREZHmWAwQERFpjsUAERGR5lgMEBERaY7FABERkeZYDBAREWmOxQAREZHmWAwQERFpzhNJSEREBO7duwe73Y5UqVLB19c3sZtEgDoed+7cUcfHx8cHadOmhZubW2I3iwCEh4fj/v376p9TpkzJnLFQzty+fRuRkZGw2WxIkyYNc8ZCOXPv3j11PKSfkeOTFLi7esJs374d73doiyIFc8Pf3w+ZMmVC5syZ4efnh4L5cqBt6+ZYv369+ll6ee7evYuRI0eiQoUKSJEiBdKlS4esWbPilVdeUf8/MDAQQ4YMwa1btxK7qVqRPNi8eTM6tGuFNwrk+itn5Jf8s/yZ/Lvg4GDmzEsmuSA5IbkhOSK5IjkjuSP/X3JJckpyi14eu92u+hDpS6RPkb5F+phnOSN9j/RB0he5cs642V209Rs3bkTPjz7A2bNn0TIwDpULxCIgB5A5NSAF9LV7wJ5zQPBxd8zY6oW0r2TA0BFjUbdu3cRuepL28OFD9OnTB1OmTIGnp6eqol9E7kKjo6PRsmVLjBgxQlXZZJ41a9agV4/OuHrlEloFxqBS/jgU/zNnxJW7T3Nm8zF3zNzmiUyZs2LEjxNQo0aNxG56kiZ3mT179sQvv/wCLy+v/8yZmJgYtG/fXhUOyZMnf6lt1c3y5cvxSc+uuH3zOlqXj0bFfE9zJmMqKRKe5kzYOWDjUQ/8ss0dOXPmxMjRExEUFARX43LFgAw19+75EaZPn4rv3onB+5UBv/8YpYmMBmZsAT5d4I269Rpg3IRJTCITyB3ne++9hwcPHqjjFF/y6MDf3x9z5sxhx2OCJ0+e4KNuH2L+vLkY2Cga7YIAX+9//0x4FDB1E/D5r15o3KQZfhwzHsmSJXtZTdbG2rVr0axZMzx69Eg9EogvGZqWxzrz589HpUqVTG2jrjc1nT/ogN+XL8Hg96LQugLg4/Xvn3kcAUzcAHy1yAtt2rTFsBE/utQjBJcqBuSiVq92DTy4HIa5H0bgtQwJ+/zlO0DLiT545J0Xa9cH807UQIsXL0bjxo3Vnb6j5K5oxowZaNq0qaFt0/2i9lbNKrDfO4g5H0Qie7qEff78TaD5RB+4pXoDK9dsYBFtoLlz56J169ZO54wUBA0aNDC0bbqP1NSoWhH+USfwS6dIZEmTsM+fvg40GW9DqmwlsGzFWpeZh+MyxYA08+26tXDn7Gas6hUBfwcLLhkleGeMDx74FMaGzdvVUDY5Z8uWLahatapTF7XnL24rVqxA9erVDWmbzuLi4vBm9SDYb+/Gsh6R/zka8G+jBPVG+cAtbSmsXrcJ7u4uPdXIEtatW4fatWsbljPyTFvmFJBzYmJiUKVSOaSMPIDfukX+52jAizyKAGoNtyFtrkpYvGyVS0z+dJmslmfQe3ZvxvIejhcCQg7ur10jcevSQQwbOsTIJmpJhjedHRF4nsSRkQGpzsk5Y8eMwemjIVjUzfFCQMhnJcapIyEYN3askU3Ukpzbco4bmTOSg48fPzYkns6GDhmMWxcPYkFXxwsBIX3Ush4RCN25CVOnToUrcImRgevXryNv7pyY3zkcbxYxJuauU0DlgV44cPAIcufObUxQDXXv3h2TJk1K0ByB+MwhaNGiBSZPnmxYTN1cvHgRBfLnxoqeUahUwJiYm44AdUZ64+ixU8iWLZsxQTUkk/9mz56doDkC/0WeTXfq1AmjRo0yLKZuTp06hcJvvI5NX0Sj1GvGxFy1D2gywRcnTp1FhgwJfK79krnEyMDkSZNQ+jW7YYWAKJ0baFDCDePG/GhcUM3IRMGff/7Z0EJAyEVy5syZ6j1rcsxPE8aheiE3wwoBEfQ6VMyJP00wLqhm5JyWc9vIQkBIDv70009qjgg5ZuyYUapPMKoQELWKPu1rprjAjY3li4HY2FhMnDAanasY2+GIzlWiMG3aFDUxkRJO7m48PDxMiS1zOaZNm2ZK7KQuKioKkyf9hM5VjO1wxIeVIzHp5/GGDXHrRs5pecZvBpnLMWvWLFNiJ3VPnjzB9GlT0aVqlOGxPwwKx0/jf1R9mZVZvhg4fvw4bt6+g9pFjY9dLi/g52NHaGio8cE1sHTpUtMKKXnXWuJTwh04cABRkeGoWtD42NUKQcWW76CEW7Jkyb+uI+AMibts2TJTYid1ISEhqi8om8f42HWKQfVhJ06cgJVZvhgICwtDsVw2eJkw6V8meAbkdFPfQQln9t/b/v37XXpFr8Q8LgG5vGDGpH+JWTynF3PGAXIuyzltJh4Xx//eSuRyU32C0aTvKprTZvljY/n36qSaej2j8UM3z7yeMRzbt23hwh0JJM88zV5KWJ5/btq0SS2uQgl71bNABvMefRXI+EQtV1yiRAnTviMpkj0g5O0bM928eRM7duxQk3Ap/rZv24LXM5gzYiNezxRl+ZEByxcDaqMOzxjT4tu8gKVLFuO3hYtN+w5yXJUqVRK7CS5H7m4+qW1e/GRedoyfPVvNGSHrKVeuXGI3weV4eQB965sX3+YZa/ikUe2KAVm96Va0NNOcCUuyoMrbbzfEZ1/0NSV+Up6kVrZs2ZeyB4Vs0kLxJ5vZPLouHbU5j1geR7mheYvm+Pjjj02Jn5TfvqlcubLp3yMjA97eTiwsoaFBP3yPJ5GLTIsfHu2BdBZfmtjyxUC+fPmwer63acXAoavJULNVJRQvXtyU+ElZ+vTpcePGDdPiy+MBeXzjCqt3WYn8nc0eLSNd5ixCc/haMrRsHMSccWDOgJzTz7aMNoO8y16mTBnT4idVgRUqYt2s1fJegSnxD13xRvX8+WFllp9AGBAQgP1nIxBlwpMCmZsWdjZOfQclXMmSJU2NX6xYMRYCDpDzec+ZKMTGGR9bYu49E8WccYCcy0WLmvBa1EvMyaQqICAAoWfiVJ9gNOm7DpyLsHzOWL4YyJMnDzJnfAVLTZiIKSuqRcd5Wv4gWdXbb79t2iYcEpebrzjmjTfegJ+fP9aY8Pbf6v2Av39yFCpUyPjgGmjYsKGpOVO/vokPvpOwEiVKqL5g81HjYy8JherDrL7SreWLAVlI44POPTBug/EJNG6DD9q3f9+ltpm0EjN3F5QFOlq1amVa/KRMFmx6/4OuGLfB+PNaYkpsbvDlGDmnzVx8hjt+OsZms6Fdu46qTzCa9F0fdvnY8ht8Wbt1f2rfoQMOXHTH4hDjYkoFuPqAHV26dTcuqGb8/PzQrVs3w4spidexY0duMe2ETh98iK3H3QwdHZBRgW0n3PB+pw+MC6oZOafl3DYjZyQXJSfJMV26dceq/XYEGzg6sCgEOHTZA+3at4fVuUQxkDZtWowZNxEfzPDBDQPm3jwMB9pNseH77wfj1VdfNaKJ2vrmm2/UpCUjn+2nTp0agwYNMiyejjJlyoRhI35Eh6k+uGvAPEKJ0XGaDcNHjlaxyXFybhtZ6EruSQ5+++23hsXUUY4cOTBgwCC0nWJTfYSzpK/6cIaP6rukD7M6l9i1UEgzmzd5Fyf2rMS6PhFI7WAB/DgCqDvSB94Zy2Dlmg2WH7pxlaU8y5cvr143dBb3Zjc2ZxrUews3T23C6t4RSO7gk7YHT57uzZ4+T2UsWrqCkzoNWhiqatWqhuzxIK8Rbt26lZMHDRAXF4daNSoj+vouLP84En42x4vn6kNsyBdQB7PmLnCJnHGZnlD+Mqf/MhdZ8lVChe9tOHgh4TFOXQOqDrYhLlVR/Lb4dxYCBpGL0PLly9VFydGTXj4nhcBvv/3GQsAg8nc6d8Ei+GcpjUoDbTh2JeEx5DNBg2wqxpz5C13iouYK5ByXc13OeWdyRnJOco+FgDHc3d2xcMkK1UdIX3H6esJjSN8kfVTW/EGYNnO2y+SMS/WGcuL/tng56jXpjtLfeuKbhW648yh+dzbDVgBF+3mhRNVWWLV2I/z9/V9Gk7VRo0YNdXeSPXv2BD8PlZ/PnDmzWmCoXr16prVRRzLDfNmKNahSrxMCvvTEwGVuuBePxwbyMz8sdVOfkc9KDLNmwetKznU55+XcdyRn5BHntm3bVO6Rcfz9/VUfIX1Fkb5equ+QPuS/SF8kfZL0TdJH/bpomUst/uQyjwn+aWi698edERK6D41KuyEofzQCcgKZUwNSh12/L2sIAFtOemLuDjcUyJ8XQ4aPRVBQUGI3PUmTfdVlHsGYMWPUMPW/7dAmnYv8zIcffogBAwYgWbJkL7Wtutm+fTt69+iM/QcPo3EZoFK+GJUzmf58fH313tOc2XzcE/N3AkXeKIhho8ZzeVuTPX78GF9++SUmTJig7iL/K2fkZ2SyoOQZ34Qy18aNG/Fp7244euwEmpa1o0KepzmTIeXT9T2v3H2aM5uOeWHBrjiUKlEMw0dNcMl9O1y2GHjm0KFDmDF9GnZt34i9B47g0eOn6z/7eHuieJECKFmmIlq0bM1htJdMtjaeN2+e2oZ49+7duHbt2l//Lk2aNGop47p166J58+YcpXnJ9u3bh5nTp2H3zs3Ye+Aonsia3NLR2LxQvMjrKFWmElq1aWv6Ajn0v2QTI9nvQYb95Wbn+dU9M2bMiFKlSql1BJo0acLC+SULCQnBrF9mIGRnMPYeOIaIyKdzPfx8vVG8aEGULlcZrdu0den1N1y+GPj75A9Zl1sms8nBc8XqLKmSyYW7du1CxYoV1VaeXMrWGpgz1iX5IksL79y5E6VLl07s5lASzxmXmjMQn8kfz55rcnKgtcizM74DbT3MGeuSyYXP/07W4J5Ecybp/JcQERGRQ1gMEBERaY7FABERkeZYDBAREWmOxQAREZHmWAwQERFpjsUAERGR5lgMEBERaY7FABERkeZYDBAREWmOxQAREZHmWAwQERFpjsUAERGR5lgMEBERaY7FABERkeZYDBAREWmOxQAREZHmWAwQERFpjsUAERGR5lgMEBERaY7FABERkeZYDBAREWmOxQAREZHmWAwQERFpjsUAERGR5lgMEBERaY7FABERkeZYDBAREWmOxQAREZHmWAwQERFpjsUAERGR5lgMEBERaY7FABERkeZYDBAREWmOxQAREZHmWAwQERFpjsUAERGR5lgMEBERaY7FABERkeZYDBAREWmOxQAREZHmWAwQERFpjsUAERGR5lgMEBERaY7FABERkeZYDBAREWmOxQAREZHmWAwQERFpjsUAERGR5tzsdrsdLiwuLg7btm3Drl27sGPHDpw8eRIHDx5ExYoVUatWLZQqVUr9s6enZ2I3VTtXr17Fhg0bEBYagmOH9+LO7Vs4ePgI6jdsjFq1aqNy5crImjVrYjdTO7Gxsdi6detfOXP69GmVM0FBQSpnSpcujfLly8PDwyOxm6qdS5cuYePGjSpnwnZvxd69e1G8eHGUKF0BASVKokqVKsiUKVNiN1M7MTExCA4Oxu7du1XOnDlzBocOHVLXsLfeekvlTGBgINzdXff+2mWLgfDwcIwfPx4jRozA7du31UGQP3uezWZTv/v7+6N79+746KOPkCJFikRqsT6kkxk+5AcsXb4CRXPZUCJ7OApmiYOfD/AkCjhyxR1hF3wRejICtWtVR68+fVXnQ+Z69OgRxo4di1GjRuHevXtwc3NDRETE/5czcklIlSoVevToga5du6r8IXNJcTZ8yPdYsWodSuR5mjMFMschmTfwOBI4fNkdoRd8se9MBOrXrY1efb5QHRCZ6/79+xg9erT6Jfkj/p4zvr6+6qY0bdq06NmzJzp37qz+zNW4ZDGwfft2NGnSBDdv3vz/DsyLyEUuefLkmDVrFmrUqGF6G3X0+PFjfPHZJ5gyZTI6V43FB1XjkCv9i3/+/E1g4gY3jPnDEy1atMKQYSPVMSLjbdq0Cc2aNcPdu3cTlDOpU6fGnDlz1KgBGe/hw4fo0/tjzJo1E92qxaBTFTtefeXFP3/mBvDTeneMX++BDh064odBQ5EsWbKX2WRtrFmzBi1atFBFQEJyJn369Jg7dy7KlSsHV+JyxcBPP/2k7vJl2MaRpnt5eeGrr75Cv379TGmfrq5du4YaVSsihf0ipnWIQJ6M8f/s2RtAuyk2XI3IgHUbtiBbtmxmNlU7I0eOxKefforo6GiHPi85M3jwYHz88ceGt01nFy9eRPUqFZDZ9zqmtItAzn8pnP/u5DWg7WQbHrhlw9r1wciYMQEJR/9pwIAB6N+/v0M5IyNu8lhaRhM++OADuAqXKgYmT56shmAcvag94+3tjW+//RafffaZYW3TmTymqRhYCsXTX8S0jtHwdOBRc2wc8ME0T2w6mxFbd4QiQ4YMZjRVO3JB+uSTTxAVFeV0zgwbNgzdunUzrG06u379OsqXLYHKua5hQpsYeDjwqDk6Bmg7yQt7b2ZD8LbdapianDdw4EB88803TueMFNETJkxA+/bt4QpcphiQCU4BAQFOFwLPH6j169ejQoUKhsTTWaOG9RBxYQ2W9Ihy6KL2TFwc0GS8Nx6nKI/fV/2hKmxyXEhIiBqqlFE0I8jdjkyeKlGihCHxdCWX3NpvVoP/w62Y1zkKzsw5i4kFGvzoDd9X38SChUuNbKaWgoODUa1aNUP7mT179qBQoUKwOpeY+igXM5kjILOgjYwpz1CfPHliWEwd/frrr9i0YQ2mtHeuEBByUZzYNgp7Q7dh5syZRjVRS5GRkYbnjEySaty4sYpNjpsxYwb279mmznVnJ5/LKJzk3ob1q1UukuOePHmi+gSjimch+Sc5Y2RMrYuBxYsX4+zZs+piZGR1LsPbkpjkGDke/T7vhSGNo5A+pTExU/sBI5tG4ut+fQztyHQzb9489WqnkQN/crwl5vz58w2LqRs5p7/u96k6x+VcN4Lk3pD3ovDlF70NPd66mT59Ou7cuWN4zshriEuWLIHVuUQxMHz48P/vtUEjSEx5DsoEcoysIXD39g00M3jS7DulgJiIB1i5cqWxgTUi57WZOUOOWbFiBeKiHqBhSWPjNg8Ebt+8rnKSEs5ut5uWM/ImgvRhVufuChNt5L11s1y4cAFHjx41LX5SNn/uLLQoFw0fL2PjytBnm/IRmDebozaOOHfuHI4cOWJa/MOHD6vvoISbP2cmWgdGODTJ9t9IDrYMjFY5SQl35MgR9XaHWXbu3IkbN27AyixfDISFhZn6Hq2Pjw9CQ0NNi5+Uhe7ejnK5jXt087xyeYDQkJ2mxNZh4qCZi55IbOaMY0J270RgHnNil80dh9Dd28wJnsSFhoaqvsAs0odJX2Zlli8G9u/fb+qzYxkWktmelDAyIebQsdMolsOc+MVzACfOXOYETwdzxsxJfhJbvoMSvijXybOXTc2Zg0dPu8RkNavZu3evKY8Inp87sG/fPliZ5Rfsl2VTjXrN40UHSfYzYEGQMLIqV0xMHNKatFJtGv//W6Y1Xbp05nxJEnXixAlTOwSJffz4ceZMAt26dUv9blbOSFzJSXn908/PoNmJGuVMnIET1P9O1iyQpY2tzPLFgGyWYvb75jJRjZPVHBNn0tzLZ3lZs2ZNc76AnCKvsfFVNsfIAlteJsUVsjEbWYubm5vlN/6yfDGQOXNm9SzHrOFiWRSibdu26NSpkynxk/Ls28qVAnH+VuRfd/FGOn8L8PH2RPCWbdxx0oFXpCZOnOj0CmovIvko+dK6dWtT4idVMsJZqUIgLtyORd5M5uRMcj8fbNy8jQt2ObDM/fTp000bhZackb7Myix/lZVVB82cMyDLrNapU0dtE0oJU6J4EYSd3W3KM9DQs0DRN/KpLagpYeRdaVm62yyyQ2i9evWYMw4o8kY+hJ45YkoxEHYWKBlQVF0zKWHq1KmjNuQyqxiQPszqx8XyEwiLFCli6rMceQeUHY5jylWoirWHzZmBu+6wF8qVr2pK7KROlgs2a1RASGyrX9isKrBCNaw9bMZDAmDtER+ULV/FlNhJXenSpeO9M6EjpA+TvszKLF8MyCsZTZs2NWWoWIbSZB1qborjmLbt2mNpaAyu3jU27u2HwPydcWjf8X1jA2siVapUqF+/vinPKCXm22+/rb6DEq5dh47q3L7zyNi4V+4Cy0JjVE5SwmXIkAFVq1Y15fGKPIqWZY7NfN1Xi2JA9OjRw7RHBL169TIltg5ee+01VKtSEYNXGNvpDF/ljjKlSqBgwYKGxtVJz5491XC+0SSmxCbHyIY1cm7LOW6kwb97oFqVSionyTG9e/dWfYIZ86s++ugjWJ1LFAPFihVDy5YtYbPZDIspB11GBeQXOW7EjxMwaZM7th03Jl7oGWDUGg/8OPZnYwJqKjAwEA0aNDB0IRWJ1bBhQ7UTIjlOzu2Rqz3UM34jbD0OTN7sgZGjJxgTUFPVqlVTowNGFgTSZ7Vq1Ur1YVbnMlsYP3jwAPny5VNLOjo7h0CGgpInT67eLeUjAueNGD4co4b0xbZ+kcjmxJbq1+4B5b+3oc0Hn6Pfl18Z2URtJxLmyZNHrdXhbM7IiEDq1KnVmhzyOznnu/7fYsbEQdjaNwIZnXjicvE2EDjABz36fI+eHOU0ZPn7vHnz4uHDh07vWSM5kz59etXPSH9jdS4xMiBSpEihNuHw9/d3avhTCgGp1tatW8dCwCAf9+yJt+o3R6UfbDh+xbEYZ25AfT6wytv4om8/o5uopTRp0mD9+vXqWaUzOSOflRgSi4WAMfr2+1Kd60EDbercd4TkWsUffFTuSQ6S8zJkyKD6BukjnJk/IHNrpK+SPssVCgGXKgZEgQIF1KZFWbJkceiRgQxzpk2bFsHBwXyDwECSNON/moTGrboi4CsvjFr1fwug/Be5YZ3wB1CkrxdqNWyHqdNnmfKsW1dFixbF9u3b1UXOkZyRz8hnJYbVZ0O7EjnH5Vx/s0E7de5LDsR38EZyS3Ks+JeeaNKqm8o9ritgnFKlSqk+QvoKRx6zSc7ImgLSV0mf5Spc5jHB82QBos8//xzjxo1Tbxn81zrs8gxI3vOUGZ1jxoxBypQpX1pbdSNJ1K51MyDqDj4ICkfjMkDWNFIw/O/PXb4D/LoLmLDJF5FIgSnTZ6vndWTe8tEyWXbKlCnqruW/Xj18ljPt27dX26/KXQ6Z448//kCHti3ggwf4MCgc75UBMv9tAEau0pfuAPN2AD9t8oWbTxpMnTGHqw2a6P79++jWrZtafyA+OSOFgyzV3aVLFwwaNMjybw/8f+wu7MyZM/bevXvbU6VKZXdzc7P7+fnZbTabFDd2X19fu7u7u93f39/euXNn+9GjRxO7udqIioqyL1iwwB5UoZTd3d3Nnj61j71yYT97reI2e6nXYM+Q2lv9eWCZYvbZs2fbIyIiErvJ2jhx4oT9o48+sidPnlzlxz/lTIoUKew9evRQP0svh+SA5EJg6WLqWpYxjc0eVMjXHlQA9oqFfO2vpPJROSM5JbklOUYvx5EjR1QfIn3JP+WMHC/pg6Qvkj7JVbnkyMDfyX/ChQsX1BaR8uuHH35Qldm7776LXLlycQgtkXdqkx3ujh07pibSDB48WK2O17hxY95tJnLOnDt3TuWL7NgmOTNkyBCVMzly5GDOJPIojuxwJ8+u+/fvj6+++krt0SGPfMzczp3+O2fOnDmjNuiSnBk4cCCGDh2KRo0aIXv27C6fM0miGHieHChZHU0uclwu1Vp4bKyJx8WaeFysa08SPDacqUVERKQ5FgNERESaYzFARESkORYDREREmmMxQEREpDkWA0RERJpjMUBERKQ5FgNERESaYzFARESkORYDREREmmMxQEREpDkWA0RERJpjMUBERKQ5FgNERESaYzFARESkORYDREREmmMxQEREpDkWA0RERJpjMUBERKQ5FgNERESaYzFARESkORYDREREmmMxQEREpDkWA0RERJpjMUBERKQ5FgNERESaYzFARESkORYDREREmmMxQEREpDkWA0RERJpjMUBERKQ5FgNERESaYzFARESkORYDREREmmMxQEREpDkWA0RERJpjMUBERKQ5FgNERESaYzFARESkORYDREREmmMxQEREpDkWA0RERJpjMUBERKQ5FgNERESaYzFARESkORYDREREmmMxQEREpDkWA0RERJpjMUBERKQ5TyQR169fx549exASEqL+f3BwMDJkyIDMmTPDzc0tsZunraioKBw+fBjHjh3D8ePH1Z8dPXoUBQsWhI+PT2I3T2tXr17F3r17ERoaqv7/1q1bkSlTJvWLEk9kZCQOHTqE1atXq/8vv8s1THLG29s7sZunLbvdjitXrqicCQsLU3+2bds2ZMmSRfU1Ls/uwi5dumT/+qsv7a9mTW+X/5S8WZPZKxb0tZfMBXu+rDa7u7ubPXOGNPY+vXvZz5w5k9jN1UZMTIx92bJl9kqVKtk9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",
- "text/plain": [
- "
"
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "view-in-github",
+ "colab_type": "text"
+ },
+ "source": [
+ ""
]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
- "source": [
- "colors, edges, g = generate_grid_graph(5, 5)\n",
- "\n",
- "all_nodes = colors[1] + colors[2]\n",
- "\n",
- "node_map = dict(zip(all_nodes, list(range(len(all_nodes)))))\n",
- "\n",
- "fig, axs = plt.subplots()\n",
- "\n",
- "plot_grid_graph(g, colors[0], axs)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "2af1113d8aa6574",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "The blue and orange nodes are the two color groups for our grid graph. Any nodes that are the same color will be sampled simultaneously during block sampling.\n",
- "\n",
- "With the graph in hand, we can fully define the distribution we want to sample from by choosing a corresponding inverse covariance matrix and mean vector,"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 32,
- "id": "9bc7243e0373a00",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:17.647863753Z",
- "start_time": "2025-08-28T16:29:16.475552708Z"
- }
- },
- "outputs": [],
- "source": [
- "# Fixed RNG seed for reproducibility\n",
- "seed = 4242\n",
- "key = jax.random.key(seed)\n",
- "\n",
- "# diagonal elements of the inverse covariance matrix\n",
- "key, subkey = jax.random.split(key, 2)\n",
- "cov_inv_diag = jax.random.uniform(subkey, (len(all_nodes),), minval=1, maxval=2)\n",
- "\n",
- "# add an off-diagonal element to the inverse covariance matrix for each edge in the graph\n",
- "key, subkey = jax.random.split(key, 2)\n",
- "# make sure the covaraince matrix is PSD\n",
- "cov_inv_off_diag = jax.random.uniform(subkey, (len(edges[0]),), minval=-0.25, maxval=0.25)\n",
- "\n",
- "\n",
- "def construct_inv_cov(diag: Array, all_edges: tuple[list[ContinuousNode], list[ContinuousNode]], off_diag: Array):\n",
- " inv_cov = np.diag(diag)\n",
- "\n",
- " for n1, n2, cov in zip(*all_edges, off_diag):\n",
- " inv_cov[node_map[n1], node_map[n2]] = cov\n",
- " inv_cov[node_map[n2], node_map[n1]] = cov\n",
- "\n",
- " return inv_cov\n",
- "\n",
- "\n",
- "# construct a matrix representation of the inverse covariance matrix for convenience\n",
- "inv_cov_mat = construct_inv_cov(cov_inv_diag, edges, cov_inv_off_diag)\n",
- "\n",
- "inv_cov_mat_jax = jnp.array(inv_cov_mat)\n",
- "\n",
- "# mean vector\n",
- "key, subkey = jax.random.split(key, 2)\n",
- "mean_vec = jax.random.normal(subkey, (len(all_nodes),))\n",
- "\n",
- "# bias vector\n",
- "b_vec = -1 * jnp.einsum(\"ij, i -> j\", inv_cov_mat, mean_vec)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "6c2ffe8502458738",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "Now we can construct a program to sample from the distribution we just defined. All block sampling routines follow more or less the same set of steps\n",
- "\n",
- "1. Divide your graph into two sets of blocks. The first set, the \"free\" blocks, will be updated during sampling. The second set, the \"clamped\" blocks, will have their nodes fixed to a constant value during sampling. This is often useful. For example, in the case of EBM sampling, this clamping allows for sampling from a distribution conditioned on the clamped nodes.\n",
- "2. Iteratively update the states of your free blocks. This means:\n",
- " 1. Initialize the state of each of the free nodes \n",
- " 2. Update the state of each of the free nodes according to some rule. The update rule for each node is some function that takes in a set of parameters and the states of some subset of the other nodes in the graph, and returns an updated state for the node.\n",
- " 3. Make some observation of the current state of the program. This might mean simply writing down the state of some subset of the nodes, or it might mean computing some more complex observable.\n",
- " 4. Repeat steps 2 and 3 until a statisfactory number of observations have been made\n",
- "\n",
- "THRML lets you run any version of this procedure that you want while writing minimal amounts of new code. We have to define 3 main things to accomplish this:\n",
- "\n",
- "\n",
- "1. A block specification: a division of our problem graph into free and clamped blocks\n",
- "2. A set of interactions: these allow us to specify what information is required to compute the conditional updates for each node in our graph.\n",
- "3. Conditional sampling rules: these specify how to update the state of each node in our graph given the interactions that are applicable to that node\n"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "a63b95fb57e9cc5d",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "First, we will define a block spec for our problem. In our case, we simply want to sample each color group in sequence, and we won't be clamping any of the nodes,"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 33,
- "id": "469ccb993534d861",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:17.650211499Z",
- "start_time": "2025-08-28T16:29:17.648290975Z"
- }
- },
- "outputs": [],
- "source": [
- "# a Block is just a list of nodes that are all the same type\n",
- "# forcing the nodes in a Block to be of the same type is important for parallelization\n",
- "free_blocks = [Block(colors[1]), Block(colors[2])]\n",
- "\n",
- "# we won't be clamping anything here, but in principle this could be a list of Blocks just like above\n",
- "clamped_blocks = []\n",
- "\n",
- "# every node in the program has to be assigned a shape and datatype (or PyTree thereof).\n",
- "# this is so THRML can build an internal \"global\" representation of the state of the sampling program using a small number of jax arrays\n",
- "node_shape_dtypes = {ContinuousNode: jax.ShapeDtypeStruct((), jnp.float32)}\n",
- "\n",
- "# our block specification\n",
- "spec = BlockGibbsSpec(free_blocks, clamped_blocks, node_shape_dtypes)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "6fde116cd6ceaa15",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "Now the interactions. Our PGM is of the undirected variety, which means that it can be described naturally using the language of [Factor Graphs](https://en.wikipedia.org/wiki/Factor_graph). Deep knowledge of factor graphs and their nomenclature isn't necessary to use THRML; in this context, a Factor is simply an interaction between a set of variables that has no natural direction. \n",
- "\n",
- "Factor graphs can be viewed as hypergraphs where each factor represents a hyperedge connecting multiple variables. Hyperedges in factor graphs can connect any number of variables, allowing for natural representation of higher-order interactions. For example, a three-way interaction term like $x_1 x_2 x_3$ in an energy function corresponds to a single hyperedge (factor) connecting three variable nodes. \n",
- "\n",
- "A nice thing about the Factor formalism is that in the context of Gibbs sampling, the conditional update rule for the $i^{th}$ node depends only on factors that involve $x_i$. This means that given a set of factors for a graph, if we want to update the state of a given node, we only need to consider a small subset of all of the factors that are local to that node. \n",
- "\n",
- "In our case, our energy function can we written as a sum of a bunch of terms, each of which is associated with a factor. There are three distinct types of term in this sum, each of which is associated with a different type of factor:\n",
- "\n",
- "1. $A_{ii} \\: x_i^2$\n",
- "2. $b_i \\: x_i$\n",
- "3. $A_{ij} \\: x_i \\: x_j$\n",
- "\n",
- "Each of these factors contributes to our conditional update rule in a different and consistent way. As such, in the context of algorithms like Gibbs sampling, Factors are defined by their ability to produce a set of directed interactions that effect the different nodes they involve in potentially different ways. In the case of our Gaussian sampling problem, our factors generate interactions that are either:\n",
- "\n",
- "1. Linear: contribute terms to the energy function like $c_i \\: x_i$, where $c_i$ does not depend on the \"head node\" $x_i$ but may depend on some \"tail nodes\" $x_{nb(i)}$\n",
- "2. Quadratic: contribute terms to the energy function like $d_i \\: x_i^2$, where in our case $d_i$ is a constant independent of the state of the sampling program\n",
- "\n",
- "THRML implements these abstractions directly in code. \n",
- "\n",
- "The most primitive object is the `InteractionGroup`, which specifies what parametric and state information should be supplied to a given node to compute it's conditional update. An `InteractionGroup` is composed of a set of interaction parameters, a set of \"head nodes\", and sets of \"tail nodes\". The head nodes are the nodes whose conditional update is effected by the interaction, and the tail nodes specify which neighbouring node states are required to compute the conditional update.\n",
- "\n",
- "\n",
- "THRML also defines Factors via the `AbstractFactor` interface. In full generality, THRML defines a factor as anything that can be reduced to a set of `InteractionGroup`s. THRML also defines more specialized factors (like ones that define an energy), however we won't be using those here.\n",
- "\n",
- "We can use these objects to set up our sampling program,"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 34,
- "id": "6829ad246eb0ca05",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:17.687087542Z",
- "start_time": "2025-08-28T16:29:17.651912413Z"
- }
- },
- "outputs": [],
- "source": [
- "# these are just arrays that we can identify by type, will be useful later\n",
- "\n",
- "\n",
- "class LinearInteraction(eqx.Module):\n",
- " \"\"\"An interaction of the form $c_i x_i$.\"\"\"\n",
- "\n",
- " weights: Array\n",
- "\n",
- "\n",
- "class QuadraticInteraction(eqx.Module):\n",
- " \"\"\"An interaction of the form $d_i x_i^2$.\"\"\"\n",
- "\n",
- " inverse_weights: Array\n",
- "\n",
- "\n",
- "# now we can set up our three different types of factors\n",
- "\n",
- "\n",
- "class QuadraticFactor(AbstractFactor):\n",
- " r\"\"\"A factor of the form $w \\: x^2$\"\"\"\n",
- "\n",
- " # 1/A_{ii}\n",
- " inverse_weights: Array\n",
- "\n",
- " def __init__(self, inverse_weights: Array, block: Block):\n",
- " # in general, a factor is initialized via a list of blocks\n",
- " # these blocks should all have the same number of nodes, and represent groupings of nodes involved in the factor\n",
- " # for example, if a Factor involved 3 nodes, we would initialize it with 3 parallel blocks of equal length\n",
- " super().__init__([block])\n",
- "\n",
- " # this array has shape [n], where n is the number of nodes in block\n",
- " self.inverse_weights = inverse_weights\n",
- "\n",
- " def to_interaction_groups(self) -> list[InteractionGroup]:\n",
- " # based on our conditional update rule, we can see that we need this to generate a Quadratic interaction with no tail nodes (i.e this interaction has no dependence on the neighbours of x_i)\n",
- "\n",
- " # we create an InteractionGroup that implements this interaction\n",
- "\n",
- " interaction = InteractionGroup(\n",
- " interaction=QuadraticInteraction(self.inverse_weights),\n",
- " head_nodes=self.node_groups[0],\n",
- " # no tail nodes in this case\n",
- " tail_nodes=[],\n",
- " )\n",
- "\n",
- " return [interaction]\n",
- "\n",
- "\n",
- "class LinearFactor(AbstractFactor):\n",
- " r\"\"\"A factor of the form $w \\: x$\"\"\"\n",
- "\n",
- " # b_i\n",
- " weights: Array\n",
- "\n",
- " def __init__(self, weights: Array, block: Block):\n",
- " super().__init__([block])\n",
- " self.weights = weights\n",
- "\n",
- " def to_interaction_groups(self) -> list[InteractionGroup]:\n",
- " # follows the same pattern as previous, still no tail nodes\n",
- "\n",
- " return [\n",
- " InteractionGroup(interaction=LinearInteraction(self.weights), head_nodes=self.node_groups[0], tail_nodes=[])\n",
- " ]\n",
- "\n",
- "\n",
- "class CouplingFactor(AbstractFactor):\n",
- " # A_{ij}\n",
- " weights: Array\n",
- "\n",
- " def __init__(self, weights: Array, blocks: tuple[Block, Block]):\n",
- " # in this case our factor involves two nodes, so it is initialized with two blocks\n",
- " super().__init__(list(blocks))\n",
- " self.weights = weights\n",
- "\n",
- " def to_interaction_groups(self) -> list[InteractionGroup]:\n",
- " # this factor produces interactions that impact both sets of nodes that it touches\n",
- " # i.e if this factor involves a term like w x_1 x_2, it should produce one interaction with weight w that has x_1 as a head node and x_2 as a tail node,\n",
- " # and another interaction with weight w that has x_2 as a head node and x_1 as a tail node\n",
- "\n",
- " # if we were sure that x_1 and x_2 were always the same type of node, the two interactions could be part of the same InteractionGroup\n",
- " # we won't worry about that here though\n",
- " return [\n",
- " InteractionGroup(LinearInteraction(self.weights), self.node_groups[0], [self.node_groups[1]]),\n",
- " InteractionGroup(LinearInteraction(self.weights), self.node_groups[1], [self.node_groups[0]]),\n",
- " ]"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "211ad5584553e9d4",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "Now the conditional update the rule. Here, we will define how the relevant interaction and state information should be used to produce an updated state in our iterative sampling algorithm."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 35,
- "id": "84587bfccaa38fd7",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:17.702011145Z",
- "start_time": "2025-08-28T16:29:17.677623544Z"
- }
- },
- "outputs": [],
- "source": [
- "class GaussianSampler(AbstractConditionalSampler):\n",
- " def sample(\n",
- " self,\n",
- " key: Key,\n",
- " interactions: list[PyTree],\n",
- " active_flags: list[Array],\n",
- " states: list[list[_State]],\n",
- " sampler_state: _SamplerState,\n",
- " output_sd: PyTree[jax.ShapeDtypeStruct],\n",
- " ) -> tuple[Array, _SamplerState]:\n",
- " # this is where the rubber meets the road in THRML\n",
- "\n",
- " # this function gets called during block sampling, and must take in information about interactions and neighbour states and produce a state update\n",
- "\n",
- " # interactions, active_flags, and states are three parallel lists.\n",
- "\n",
- " # each item in interactions is a pytree, for which each array will have shape [n, k, ...].\n",
- " # this is generated by THRML from the set of InteractionGroups that are used to create a sampling program\n",
- " # n is the number of nodes that we are updating in parallel during this call to sample\n",
- " # k is the maximum number of times any node in the block that is being updated shows up as a head node for this interaction\n",
- "\n",
- " # each item in active_flags is a boolean array with shape [n, k].\n",
- " # this is padding that is generated internally by THRML based on the graphical structure of the model,\n",
- " # and serves to allow for heterogeneous graph sampling to be vectorized on accelerators that rely on homogeneous data structures\n",
- "\n",
- " # each item in states is a list of Pytrees that represents the state of the tail nodes that are relevant to this interaction.\n",
- " # for example, for an interaction with a single tail node that has a scalar dtype, states would be:\n",
- " # [[n, k],]\n",
- "\n",
- " bias = jnp.zeros(shape=output_sd.shape, dtype=output_sd.dtype)\n",
- " var = jnp.zeros(shape=output_sd.shape, dtype=output_sd.dtype)\n",
- "\n",
- " # loop through all of the available interactions and process them appropriately\n",
- "\n",
- " # here we are simply implementing the math of our conditional update rule\n",
- "\n",
- " for active, interaction, state in zip(active_flags, interactions, states):\n",
- " if isinstance(interaction, LinearInteraction):\n",
- " # if there are tail nodes, contribute w * x_1 * x_2 * ..., otherwise contribute w\n",
- " state_prod = jnp.array(1.0)\n",
- " if len(state) > 0:\n",
- " state_prod = jnp.prod(jnp.stack(state, -1), -1)\n",
- " bias -= jnp.sum(interaction.weights * active * state_prod, axis=-1)\n",
- "\n",
- " if isinstance(interaction, QuadraticInteraction):\n",
- " # this just sets the variance of the output distribution\n",
- " # there should never be any tail nodes\n",
- "\n",
- " var = active * interaction.inverse_weights\n",
- " var = var[..., 0] # there should only ever be one\n",
- "\n",
- " return (jnp.sqrt(var) * jax.random.normal(key, output_sd.shape)) + (bias * var), sampler_state\n",
- "\n",
- " def init(self) -> _SamplerState:\n",
- " return None"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "9e4bf9792ff7b72b",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "With all of the parts fully defined, we can now construct our sampling program"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 36,
- "id": "b90a7f2b58d39f2c",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:17.928525605Z",
- "start_time": "2025-08-28T16:29:17.692896238Z"
- }
- },
- "outputs": [],
- "source": [
- "# our three types of factor\n",
- "lin_fac = LinearFactor(b_vec, Block(all_nodes))\n",
- "quad_fac = QuadraticFactor(1 / cov_inv_diag, Block(all_nodes))\n",
- "pair_quad_fac = CouplingFactor(cov_inv_off_diag, (Block(edges[0]), Block(edges[1])))\n",
- "\n",
- "# an instance of our conditional sampler\n",
- "sampler = GaussianSampler()\n",
- "\n",
- "# the sampling program itself. Combines the three main components we just built\n",
- "prog = FactorSamplingProgram(\n",
- " gibbs_spec=spec,\n",
- " # one sampler for every free block in gibbs_spec\n",
- " samplers=[sampler, sampler],\n",
- " factors=[lin_fac, quad_fac, pair_quad_fac],\n",
- " other_interaction_groups=[],\n",
- ")"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "8094b8ceb5bbe117",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "`FactorSamplingProgram` is a thin wrapper on the more generic `BlockSamplingProgram`. All `FactorSamplingProgram` does is convert all of the factors passed in into `InteractionGroups` and then use them to create a `BlockSamplingProgram'. As such, prog is equivalent to prog_2 in the following:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 37,
- "id": "329f5f388dd7ec3",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:17.945152316Z",
- "start_time": "2025-08-28T16:29:17.929214231Z"
- }
- },
- "outputs": [],
- "source": [
- "groups = []\n",
- "for fac in [lin_fac, quad_fac, pair_quad_fac]:\n",
- " groups += fac.to_interaction_groups()\n",
- "\n",
- "prog_2 = BlockSamplingProgram(gibbs_spec=spec, samplers=[sampler, sampler], interaction_groups=groups)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "acbab16fa204ede6",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "Now we are finally ready to do some sampling! A sampling program in THRML simply repeatedly updates the state of each free block in the order they appear in the gibbs_spec. After every iteration of the sampling algorithm, we may observe the state and write down some information that is relevant to the problem we are trying to solve. For example, if we wanted to extract samples from some subset of the nodes of our PGM, after each iteration we could simply memorize some subset of the current state. This functionality is provided by observers in THRML.\n",
- "\n",
- "For the purposes of this example, it would be prudent to check that our sampling program is working correctly. To do this, we will compute estimators of some first and second moments and verify that they match up with expected values from the theory. We will use the built-in `MomentAccumulatorObserver` to accomplish this."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 38,
- "id": "8701c7ba2e643ea8",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:17.981759372Z",
- "start_time": "2025-08-28T16:29:17.934117075Z"
- }
- },
- "outputs": [],
- "source": [
- "# we will estimate the covariances for each pair of nodes connected by an edge and compare against theory\n",
- "# to do this we will need to estimate first moments and second moments\n",
- "second_moments = [(e1, e2) for e1, e2 in zip(*edges)]\n",
- "first_moments = [[(x,) for x in y] for y in edges]\n",
- "\n",
- "# this will accumulate products of the node state specified by first_moments and second_moments\n",
- "observer = MomentAccumulatorObserver(first_moments + [second_moments])"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "6af2c19400d9f7b7",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "Now all that is left to do is specify a few more details about how the sampling should proceed. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 39,
- "id": "e258c2655c697506",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:18.423287969Z",
- "start_time": "2025-08-28T16:29:17.976885994Z"
- }
- },
- "outputs": [],
- "source": [
- "# how many parallel sampling chains will we run?\n",
- "n_batches = 1000\n",
- "\n",
- "\n",
- "schedule = SamplingSchedule(\n",
- " # how many iterations to do before drawing the first sample\n",
- " n_warmup=0,\n",
- " # how many samples to draw in total\n",
- " n_samples=10000,\n",
- " # how many steps to take between samples\n",
- " steps_per_sample=5,\n",
- ")\n",
- "\n",
- "# construct the initial state of the iterative sampling algorithm\n",
- "init_state = []\n",
- "for block in spec.free_blocks:\n",
- " key, subkey = jax.random.split(key, 2)\n",
- " init_state.append(\n",
- " 0.1\n",
- " * jax.random.normal(\n",
- " subkey,\n",
- " (\n",
- " n_batches,\n",
- " len(block.nodes),\n",
- " ),\n",
- " )\n",
- " )\n",
- "\n",
- "# RNG keys to use for each chain in the batch\n",
- "keys = jax.random.split(key, n_batches)\n",
- "\n",
- "# memory to hold our moment values\n",
- "init_mem = observer.init()"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "f6c9593b255f17ba",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "Now run the sampling:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 40,
- "id": "8500ca130c435027",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:20.962893712Z",
- "start_time": "2025-08-28T16:29:18.442376183Z"
- }
- },
- "outputs": [],
- "source": [
- "# we use vmap to run a bunch of parallel sampling chains\n",
- "moments, _ = jax.vmap(lambda k, s: sample_with_observation(k, prog, schedule, s, [], init_mem, observer))(\n",
- " keys, init_state\n",
- ")\n",
- "\n",
- "# Take a mean over the batch axis and divide by the total number of samples\n",
- "moments = jax.tree.map(lambda x: jnp.mean(x, axis=0) / schedule.n_samples, moments)\n",
- "\n",
- "# compute the covariance values from the moment data\n",
- "covariances = moments[-1] - (moments[0] * moments[1])"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "7f45604908336311",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "We can compare our covariance estimates to the real covariance matrix to see if we implemented our sampling routine correctly"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 41,
- "id": "259e4b654e434c7b",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:20.965740612Z",
- "start_time": "2025-08-28T16:29:20.963611764Z"
- }
- },
- "outputs": [
+ },
{
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "0.0045360937\n"
- ]
- }
- ],
- "source": [
- "cov = np.linalg.inv(inv_cov_mat)\n",
- "\n",
- "node_map = dict(zip(all_nodes, list(range(len(all_nodes)))))\n",
- "\n",
- "real_covs = []\n",
- "\n",
- "for edge in zip(*edges):\n",
- " real_covs.append(cov[node_map[edge[0]], node_map[edge[1]]])\n",
- "\n",
- "real_covs = np.array(real_covs)\n",
- "\n",
- "error = np.max(np.abs(real_covs - covariances)) / np.abs(np.max(real_covs))\n",
- "\n",
- "print(error)\n",
- "assert error < 0.01"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "b436bc81aec0ab3c",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "We achieve a really small error because we computed a ton of samples. If you reduce either the batch size or the number of samples collected by each chain this number will go up."
- ]
- },
- {
- "cell_type": "markdown",
- "id": "881724ad88ca8f7d",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "That is everything you need to know to implement any type of PGM block sampling routine you want in THRML. \n",
- "\n",
- "However, you don't always have to do everything completely from scratch! THRML exposes a limited set of higher-level functionality fine-tuned to sampling problems that Extropic really cares about. \n"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "3ede7348975534bf",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "Next, we will use some of these higher-level functions to implement a more complicated type of model that can't be sampled from using analytical techniques. In particular, we will implement sampling from a deep Gaussian-Bernoulli EBM. This type of model has the energy function\n",
- "\n",
- "$$ E(x) = E_G(x) + E_{GB}(x, s) + E_B(s)$$\n",
- "\n",
- "where $x$ is a vector of continuous values and $s$ is a vector of *spins*, $s_i \\in \\{-1, 1\\}$.\n",
- "\n",
- "$E_G(x)$ is the Gaussian energy function defined in the previous section. $E_{GB}$ is an energy function that represents the interaction between the continuous and spin-valued variables,\n",
- "\n",
- "$$ E_{GB}(x, s) = \\sum_{ (i, j) \\in S_{GB}} W_{ij} \\: y_i \\: x_j $$\n",
- "\n",
- "where $S_{GB}$ is a set of edges connecting spin and continuous variables.\n",
- "\n",
- "$E_{B}$ is the spin energy function,\n",
- "\n",
- "$$ E_B(s) = \\sum_i b_i \\: s_i + \\sum_{j > i} J_{ij} s_i s_j$$ \n"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "1739ba7eb64593f2",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "Just for fun, lets use a more complicated graph topology for this problem. We will stick with a grid, but we will add skip-connections that allow for non-nearest-neighbour interactions. We can once again use NetworkX to make this graph,"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 42,
- "id": "706df0c0fa4dbe54",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:21.014054288Z",
- "start_time": "2025-08-28T16:29:20.965966221Z"
- }
- },
- "outputs": [],
- "source": [
- "# first, define a new type of node\n",
- "\n",
- "\n",
- "class SpinNode(AbstractNode):\n",
- " pass"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 43,
- "id": "8d6b1059f3b9d27b",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:21.014706859Z",
- "start_time": "2025-08-28T16:29:20.990030231Z"
- }
- },
- "outputs": [],
- "source": [
- "# now, build a random grid out of spin and continuous nodes\n",
- "\n",
- "\n",
- "def make_random_typed_grid(\n",
- " rows: int,\n",
- " cols: int,\n",
- " seed: int,\n",
- " p_cont: float = 0.5,\n",
- "):\n",
- " rng = random.Random(seed)\n",
- "\n",
- " # every time we make a node, flip a coin to decide what type it should be\n",
- " grid = [[ContinuousNode() if rng.random() < p_cont else SpinNode() for _ in range(cols)] for _ in range(rows)]\n",
- "\n",
- " # Parity-based 2-coloring\n",
- " bicol = {grid[r][c]: ((r + c) & 1) for r in range(rows) for c in range(cols)}\n",
- "\n",
- " # Separate by color and type\n",
- " colors_by_type = {\n",
- " 0: {SpinNode: [], ContinuousNode: []},\n",
- " 1: {SpinNode: [], ContinuousNode: []},\n",
- " }\n",
- " for r in range(rows):\n",
- " for c in range(cols):\n",
- " n = grid[r][c]\n",
- " color = bicol[n]\n",
- " colors_by_type[color][type(n)].append(n)\n",
- "\n",
- " return grid, colors_by_type\n",
- "\n",
- "\n",
- "grid, coloring = make_random_typed_grid(30, 30, seed)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 44,
- "id": "964bc9c4fcf1fb2b",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:21.033744454Z",
- "start_time": "2025-08-28T16:29:21.002416755Z"
- }
- },
- "outputs": [],
- "source": [
- "# now generate the edges to implement our desired skip-connected grid\n",
- "# we will use only odd-length edges (1, 3, 5, ...) so that our 2-coloring remains valid\n",
- "def build_skip_graph_from_grid(\n",
- " grid: list[list[AbstractNode]],\n",
- " skips: list[int],\n",
- "):\n",
- " rows, cols = len(grid), len(grid[0])\n",
- "\n",
- " # Build graph & annotate nodes with coords and type\n",
- " G = nx.Graph()\n",
- " for r in range(rows):\n",
- " for c in range(cols):\n",
- " n = grid[r][c]\n",
- " G.add_node(n, coords=(r, c))\n",
- "\n",
- " # Edges sorted by edge length\n",
- " u_all = []\n",
- " v_all = []\n",
- " for k in skips:\n",
- " # vertical: (r, c) -> (r+k, c)\n",
- " for r in range(rows - k):\n",
- " r2 = r + k\n",
- " for c in range(cols):\n",
- " n1 = grid[r][c]\n",
- " n2 = grid[r2][c]\n",
- " u_all.append(n1)\n",
- " v_all.append(n2)\n",
- " G.add_edge(n1, n2)\n",
- "\n",
- " # horizontal: (r, c) -> (r, c+k)\n",
- " for r in range(rows):\n",
- " for c in range(cols - k):\n",
- " c2 = c + k\n",
- " n1 = grid[r][c]\n",
- " n2 = grid[r][c2]\n",
- " u_all.append(n1)\n",
- " v_all.append(n2)\n",
- " G.add_edge(n1, n2, skip=k)\n",
- "\n",
- " return (u_all, v_all), G\n",
- "\n",
- "\n",
- "edge_lengths = [1, 3, 5]\n",
- "edges, graph = build_skip_graph_from_grid(grid, edge_lengths)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "700ba823270bf471",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "Let's visualize this graph to understand what we just created. Since the graph is no longer planar, it will be cleanest to plot the local neighbourhood of particular nodes in our grid one at a time."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 45,
- "id": "d302f2a19cb6a65c",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:21.212544555Z",
- "start_time": "2025-08-28T16:29:21.019630164Z"
- }
- },
- "outputs": [
+ "cell_type": "markdown",
+ "id": "4bf7c6cf",
+ "metadata": {
+ "id": "4bf7c6cf"
+ },
+ "source": [
+ "# All of THRML"
+ ]
+ },
{
- "data": {
- "image/png": "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FNzwOeAUYT/atGkTfxZbYTrHg+LiAAAAAACkjCYnZeoIAcc8PhYIPAEAAAAAkMJpef/yL/9iS5cuTXpTkHAA8ptvvrH/83/+j6sn5SMCTwAAAACQ4xpR+hcxGzp0qJ1xxhk2Y8YM++yzzxijHM1yeuedd+zjjz+2MWPGuGLjOg58OxYIPAEAAABADtu8ebP927/9m1uRC+nRtm1bu/XWW23ixIn25Zdf2uuvv96oldIQhn379tkLL7xgv/zyi91yyy129dVXu+dfe+01mz272up1HiDwBAAAAAA5rEePHtauXTt7++237fjx40lvDqrRimeXX3653X333bZq1Sp77rnnCBDmgHXr1tmf//xnt2rcww8/bKNGjXLPL1y40JYvX25FRUXu908//dT+9re/pb7+E6vaAQC8FdeSxLEvfVxWYla+MvqlcCNsh7GhnRDHJ9jjGjlPmTVTp061v/71rzZv3jwX6EC6aMrdo48+6qbdPfvss3b99dfbueeem/RmIcsqKyvt66+/dlMrBwwYYHfccUfVyni7du2yOXPm2AUXXOACT/q38+fPt4qKCtu2bZv169fP0orAEwDAS7owKy4urvP1kpKSrFygxdWOc3iX2fxpZpurpU/3nWI2drpZftfstBFDO4wN7YQ4PkEe10A1AwcOtMsuu8w+//xzd4z16dMn6U3CSXr16mWPP/64ffjhhy47bfXq1XbddddZfn5+0puGLCgvL7e33nrLZbYp+HvllVdaq1YnJqkpE1FjXlBQYJMnT3bPbdmyxQWdRLWf0oypdgAAL9WWDdCU19PWjqML5y1zaz6n3+fdk702YmiHsaGdEMcnyOMaOIkudDXtThe4mQtapIuCTMpO00Or3Wk61tatW5PeLLTQypUr7emnn3Zjef/997u6Xpmgk3z77be2fv16u/nmm6sCjYsWLXI/27dvb6eddpqlGYEnAADSQFOElK1RedKJvn7X82WlfrUTktDGJrRjILT+AAlq06aNK2K8fft2++KLL5LeHNRD0+yeeOIJa926tQs+qfaPbyudwVwm0yeffGIvvfSS9e7d2/7whz/YsGHDavwbTaNTLadLL73UBg8eXCNYJT5kJxJ4AgAgDVSXpt7XV/jVTkhCG5vQjoHQ+gMkTBexV1xxhaszs2HDhqQ3B/VQdtpjjz1mo0ePtg8++MBmzpxphw4d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XnnlFXcMTZs2zYqKihLdnrKyMje9LhN40n+fdtppFgICT/CCUmmPHz9uAwYMSHpTAACBUV2Hw4cPuzvpAID008X4vffea3379rWXXnrJ1qxZk/QmwTP63n/55Zdt06ZNdt9999nQoUOT3iRTooUw1Q5IiNJotZRkJvoLAEC26I6ihHJyBwC5oF27di74NHDgQBdA0EpkQGO/9//+97+7aW3Knhs0aJClwdatW11QVcX0hcATkEDgSXc0FHwCACCbdGInoZzcAUCuaNu2rd1zzz0uW0VTpn766aekNwkeTK1//vnnbdeuXa5eWJpm1Gzfvt169epleXl5rt6Tzk+YagfEaMOGDdR3AgBEmvEUyskdAOSSNm3a2F133WWjRo2yt99+2z777DN30Q7Udk35l7/8xdUPfuSRR6xfv36WJjt37nSruGeKnqvUTCg3xdrE0kpZiVn5SrOCIrPC071sQ0sr7tu375TntZzn6adH0KdA+pONdvbv3+8i0/VFo0Mbn9DaiWN8fDqmEYFA3itxtRPrcezBPmtSxlMgY0M7LRTIcQCEQrMibrrpJnfR/sknn7hslptvvtkFpQBZsmSJC0xqFo0ClR07drQ0qaysdIGnESNGBJmNHe078fAus/nTzDbP/v25vlPMxk43y+/qTRv68i8uLq7z9ZKSkuydBATUn2y1k1kmta6Mp9DGJ7R24hgf345pZFFA75W42ontOPZonzUq4ymgsaGdFgjoOABCo+lJ48aNs27dutlbb73lblzffffdqQswIP6Aztdff22ffvqpnXPOOakNSO7fv9+tspfJeAot8BTtVDt9MW+ZW/M5/T7vHq/aqO2OU1Nez9X+ZKsdpURqpaEuXbpE2k5qjunA2oljfHw7ppFFAb1X4montuPYo32mkzvdLVetkCjbybXPstDaCe04AEJ19tln20MPPWS7d+9206pUNwe5u2rtu+++64JO48ePt1tvvTWVQSdRtpMocCoEnpqSgqy7QZUVNZ/X73q+rNSPNuIUWn+yRBlPmmanuxg5MT6htQNEJbT3SkjvSc/2WaZ4Z53fMyGNDZqP4wDwhmZKPPbYY+6GwnPPPWerVq1KepMQM323v/TSS7Z48WK75ZZbbMKECclfT9ZD00OFwFNTad57va+v8KONOIXWnyylRirwlIrC4nGNT2jtAFEJ7b0S0nvSs32mqXb1ntiFNDZoPo4DwCuaLaEC0rqB/fLLL9u3335L0fEcsXXrVpftpp/333+/Kzyfdjt37rTOnTtXZWRlygC0a9fOQhBd4KlgeAOvF/nRRpxC60+W3oCHDx9OR+AprvEJrR0gKqG9V0J6T3q2z3RyV299p5DGBs3HcQB4R5/t06ZNs4svvthmz55tM2bMqLqgR3gUWPzhhx9c0EnZbsp6Gzx4sPlg165dVfWdRMdpfn6+tWoVbXWkuETXi8LiE8UW81rXfF6/6/lsrAASRxtxCq0/WaD6TpKKwFNc4xNaO0BUQnuvhPSe9GyfKZ293oynkMYGzcdxAHhJF+5TpkyxO++80025e/bZZ23z5s1JbxayTIW533nnHVfTSRlOjz76aNW0NV8SLrpXCzxJmqcGNlW04TOt8NFnUs3n9Lue96gNLV3bktdztT/ZaEfT7Hr06FHvnejQxie0duIYH5+OaWRZQO+VuNqJ7Tj2aJ81GHjKUju59lkWWjuhHQdArjnrrLPsySefdNcVqvu0cOFCpt4FQgXkleW0dOlSmzp1qt144431LxiSMpWVlS7jyadAWVPlVTbi3VZWVubmG+7du9cKCwub3oqKLWreu1KQo7obFHEbWtq2tlVE9OUfyZK2gfSnpe3oA0SRXxWEi7KdVB7TAbUTx/j4ckxn7XM1MF58zwTUTqyfmR7ss6eeesqGDBli1157baTthPhZlmvt+HQc8D1zKvYJ5NixY27anQJPI0eOtBtuuMFNaYKfVDz8vffec+9tZbX17NnTfLN37177f//v/7lpoZnP+Pnz59uXX35p/+N//A8L4XM1nrUE9YUcdfpxxG1EctKSA/1paTuK/BYXF0feTiqP6YDaiWN8fDmmEZFA3itxtRPrcezBPsusahd1O7n4WRZaO6EdB0AuUvHm66+/3tX+UcBC0+5uv/126927d9KbhiY4evSoCyAuWrTITa3TmPoaQNy5c6f7efJUu5DEE3gCmnkhoEfIb0AAQPIaXNUOABCcc845x/r06eMKjv/5z3+2K664wsaOHRtMMeeQrVu3ztVy2rNnj8tYO//8872uh7Rz50533GklxlAReELqI78hz3UFACSvoqKiavliAEDuUC3Zxx9/3D7//HP77LPP7Ndff7Wbb76Z7KcUZzl98skntmDBAhswYIDdfffdbgxDuO7t2rVr0EFPzrKQWppmJwSeAAAAAERBNx4mTZrkio9rVTStejd+/HgbN26ctW590iqWSMzatWvd+Kj+3eTJk+2SSy4JJlCza9eu4Gf5EHhCqt+AHTt2tHbt2iW9KQAAAAAC1r9/f3viiSdcQecvvvjCli1b5rKfNB0PyTly5IjLcvrHP/5hAwcOtHvvvTe4IM3OnTsbVdfYZwSekFqhLykJAPDD8ePHXe0In+tHAAAal/00ceJEO/PMM112jWo/KfNJj7Zt2ya9eTln1apV9v7777sspylTptjFF18cTJZTRmVlpatVFfp1L4EnpDrw5ONymACAsHz44Ycu+HTTTTclvSkAgBj069evKvvp66+/tp9++slNxxsxYgQ3IWLKAPr444+tpKTErT543333BRuYKS8vd+cYhYWFFjICT0j1B84ZZ5yR9GYAAHLcypUr3d1vAEDuUH2nCRMm2KhRo2zOnDn2xhtvuOleyrzRtDxkn1Y0V7BP+7lTp0522223BR/sKysrcz87d+5sISPwhNR+6Gh561Aj2wAAPxw4cMClwOvuNwAg96iekFZPW716tc2ePdv+8pe/uGDUVVddFXyWSlyU8bNw4UK3uqBWmr3yyittzJgxOTG9sey3wFPoxxKBJ6Q220kIPAEAkrRp0yb3k7vbAJDbhg4d6qbf/fDDD/bZZ5/Z0qVLbezYsXbZZZdZfn5+0pvnbX2jFStWuIyy7du323nnnedqbCnbKVeUlZW57Lr27dtbyAg8IbX1nYTAEwAg6cDTaaedZl27dk16UwAACVNh6wsuuMDOOecc++qrr1z9pwULFrii15dccol16NAh6U30JuCkVQO1//Q9qzpOCur17dvXck1ZWZnLdgp5OqEQeEJqA08dO3a0du3aJb0pAIAcphNiTbML/YQQANB4ukZRsXEFnObPn2/ffPONeygodemllwY/baq5NI1u8eLFNm/ePNuxY4cNGTLEFQ4fNmxYzn7Plv0WeAodgSekNvCk+dQAACRp48aNLvUfAICTKWBwzTXX2Pjx413mk4pi63Huuee6aXhcz5xw9OhR+/77712QToEWLSB1880324ABAyzXlZWVBV9YXAg8IbWBp549eya9GQCAHLZv3z63zDGFxQEA9dEUO62Ap3pPKpKt7CfVgtKKqKNHj7aioiI3TS/XKKvpxx9/dPtCi0dpiuK4ceOsV69eSW9aqgJPAwcOtNAReEJqi4srEg4AQFI2b97sfhJ4AgA0dgqeMp1U70kBFwWhpk+fbgUFBW4lPGXQhn5z/fDhw/bLL7+4/m/YsMHVSRw5cqSbgki9xFNrXekmV11T7fR6KAg8IZVLVx86dIjC4gCARO3evdutNJMLtRcAANnTpk0bu/DCC91DNzEyWT+aaqZVUhWAUvaPgjIhUIBkzZo1rp9a7U+1nIYPH2633367SybQ/sCp9u/f7/ZVbecZCmIeOXLEjh8/HkS2HEcAUruiHXOiAQBJ2rt3r6u7kKsFTwEALaeV2vS4+uqrraSkxAVnPvzwQ5s1a5ZbzU3T8E4//XR37ePT940SBVauXGkrVqxwD01NV+KA6l2pxhU3bRo3zU5q21ft27evyiDL/LfPCDwhtYEnUjEBAHHQnUTdcczVgp8AgOgp6+fss892D02vWrJkiQvYfPLJJ/bxxx9bly5dXJaQglBDhw61/Px8S1tW05YtW6y0tNQFnNavX++e09RBTaVTPSvVKvIpeOZD4OngwYMEnoCo6jtpHrTSCwEAiJqmOujO7cn27NkTfC0OAED8OnXqZGPGjHEPrfimaWoK6CgQtWjRIjfNu3fv3tanTx/3UMaUCnLHFYxSQEnJAAo0ZR6bNm1yJVG0DcOGDbPrr7/eZWtxg6ZlgafWrVtbx44d6w08hYDAE1JZU4P6TgCAOANPtZ3YaaqdTqoBAIhK27ZtXZaTHqKAjwJQCvRs3LjRTc1TnR9lEmk6ngJRPXr0cMEr3azPPBS8UBCjsYEl3XDR9LjMCq566IaLgkxbt2519YUy2ThqU/WqhgwZYoMGDWp0O2g48NSpU6das8QIPAExZDyxxCaAxtDdQZ0wnUxf4pkTuKwqKzErX2lWUGRWGMHfj6mdOPabT2Ojk7uTM56OHTvmTsIzd3Lj6o9P+60x6I8nnzkAUkM34C+++OIa30fbt293ASEVKtfP1atXu8LUJ+vQoYMLQNVVjDoTcMoUta5OmUz6zlOmlQqCZ7KtasvGQfYCT4V11MIi8ARETFF+zREGgIYuAIuLi+t8XQU8s3YheHiX2fxpZptn//5c3ylmY6eb5WexHl0M7cSx33wbG53cnXxilwku6CQ8rv74tt8aQn88+MwB4EVtqEyB8tGjR1c9rywoBZAy2UqZ7CU9pwBTfVm+1TOlMo+01ZTKBfv27XM3LurKhFMAkcATEAHNG1YUnhXtADSktqyDprzeJLoA3DK35nP6fd49ZhNmedVOHPvNt7HRSbimF5w8zS4TeFq7dm0s/fFtvzWE/njwmQPAWwpKKGhRV+AC6Xfw4ME6a0lq+l1tGdm+qj0HD0h4RTtqPAFIDU11UdZB5Umrnul3PV9W6lc7IcnSPqutxlMm8BTkctChHWv0BwDgadJF+3pWrKstI9tXBJ6QKkoRFSL3AFJD9VXqfX2FX+2EJEv7rLY7igo8qVaGUt2DE9qxRn8AAB46ePAggScgCZk3Vn1vQACIVcHwBl4v8qudkGRpn9V2YqfAU7BLRId2rNEfAIBnjh496grH6yZXfRnZTLUDIqAT/3bt2tW5EgMAxK6w+ERR37yTlg7W73o+WytNxdVOSLK0z3Rip5M/PaoHnrp06WJBCu1Yoz8AgAATLtqT8QQkk24IABkNTcnN6pRdrSTVZ1LN5/S7ns+mGNqJY7/5Nja1LVmswFOmvlNc/fFtvzWE/njwmQMASKy+k9SX8RRS4IlV7ZAqBJ4ANJaWLdfy5bWtJKULwKwtay5avlwrSamor+qraKpLFFkHMbQTx37zbWyU8SRKZ9f2aRnq6lPt4uqPb/utIfTHg88cAEBqM55Oq2XxE18ReEKqEHgC0BRZvdBrDF34xXHxF3E7cew3n8bm5Iwn/VTtheo1nuLqj0/7rTHojyefOQCARDKe2ufIVDsCT0gVvbE6duyY9GYAAHLIyYGnsrIy9zMz1Q4ArKzkxIqDnmaflZaWxpOtF7EQ+hFCH2rgvdEsBw8etLy8vKqs67rOTyoqKtzNsEavspvS8SDwhFTRG7B79+5JbwYAIIdUn2onOsmTRp/kAQjX4V1m86eZbZ79+3Mq8q56W5oS6cmFdXFxcZ2vawqpDwGPEPoRQh+q8N7IykyfvLy8Rt0Ya/CcJOXjQXFxpC7lkKl2AIA4tWnTxp3QZdLeAaCKLuS2zK35nH6fd4/5orZsjqa8nhYh9COEPlThvRH5dW/7315v1PlJyseDwBNSRdHc+ir7AwAQBdVz2rNnT9KbASBNNGVF2QOVJ7Igq+h3Pa/i70Au4r0RS23jzr/VmtSCJ76PB4EnpMaxY8fc/FUyngAAcevatavt3r076c0AkCaqk1Lv6yvi2hIgXXhvxJJwUVBQ4DKyd+3a5f14tPG9UFdcxcBCayeu4mNN6U9jlpTMRjstEdr4BNlOio5pAH4FnlatWpX0ZgBIk4LhDbxeFNeWAOnCe6PFDhw40GBtY9V/0vlJg4EnD8ajjc+FuuIqBhZaO3EVH2tqf5obeGJ8aCeuNoIqCAmg1oynysrKpDcFQFoUFp84l1CdlOpTWPJam/WZlKoVo4BY8d6IZaqddOvWreGMbA/Go5XPhbriKgYWWjtxFR9ran+aG3hifGgnrjaCKggJ4JQTO61mx/sYQA26gaULt+r0u573hLKyW/J6WoTQjxD6UIX3RoscOnSoalXd+jQq48mD8Yh0qh1SKlN87GTVi48lEBVtyVS7oMQ1PiG1k9JjGoA/dGInuquoVe4AwFHW9IRZJ84lVCcl6rIEEVA2trKyfS8VEEI/QuhDFd4bLXL06FHLz89v1PmJiosfP37cWrVq5e14cGaVixpTfIzAU/jjE1I7KT2mAfijS5cuVYGnnj17Jr05ANJG5xEen0t4FdAIvB8h9KEG3htNVllZ6QJPKhzemIxsBZ0UfMrcJPNxPFjVLheltPiYAk/t2rWrP5KbC+Ian5DaSekxDcAfOvnT3U1WtgMAAFE6fvy4Cz41JsNagSdp1HS7FMvxK/wclSk+pmJj1el3PZ9QhFSV/XM+2ynO8QmpnZQe0wD8LDAOAAAQlaNHj7qfjcl46ty5s0vMIPCUYKGuuIqBhdZOXMXHmtofZTx16NAh8naaK7TxCa6dFB7TAPxC4AkAAKQp8NSqVStXDsD3wFMbnwt1xVUMLLR24io+1tT+qLJ/czKeGB/aiauNoApCAqg18FRaWpr0ZgAAgIAdO3bM/WzsYiYh3BiLtLh4HBdhcV3ohdZOXMXHmtIfZTx17Ngx8nZaIrTxCbKdFB3TAPyiEztN+z5y5EjSmwIAAAJ1tAkZT5nzk3Xr1pnPqPGE1KDGEwAgSZkCnrVlNQIAACQReOrWrZubaqeC5L4i8ITUUMYTgScAQFIyyxRnAk8+n+ABAIB0B57aNHKqnQJPmp5XXl5uviLwhNQg8AQASJIWuMjPz3c1BzOZuAAAAFHUeGrbhKl24nOBcQJPSAXdVVbkt127dklvCgAgR+Xl5VnPnj1t79697vfMTwAAgCRrPPkeeIq0uDgQR8Bq6dKlVlxc3Og3LoAAlZWYla+MbmXGmNrRimqxrpoYYX987Uvfvn1t7dq1bjurB57i6g/ttBCfBQCAwAJPbdu2dZ//Pq9sR+AJXtu8ebPNnDnTnnjiCXexACDHHN5lNn+a2ebZvz/Xd4rZ2Olm+V29akcXmgqi16WkpCR7F5wR98fnvvTr188WLlzofmYCT3H1h3ZagM8CAIBnU+3aNLLGU/UC475iqh28tmbNGveG7dWrV9KbAiAJugDcMrfmc/p93j3etdPQSmpZXWkt4v743BcFnES1njKBp7j6QzstwGcBAMCjjKc2bdq4Kf6NpevdLVu2mK8IPMFrmg4xcOBAa926ddKbAiBumuqirIPKiprP63c9X1bqVztxCak/EfRFNZ4ydyCp8eQJPgsAAB4Gnpqif//+tnPnzqoFUHxD4AneOn78uAs8DR48OOlNAZAE1Vep9/UVfrUTl5D6E0FfWrVqZX369LHDhw+7wJNqCSLl+CwAAHg21a5NEwNPmYzsTZs2mY8IPMFbW7dudRcGQ4YMSXpTACShYHgDrxf51U5cQupPRH1RzcDy8nKrqKiw/fv3N2/bEB8+CwAAnslrwjQ76dGjhysDQOAJSKi+k9IOAeSgwuITRX3zTppqq9/1fLZWmoqrnbiE1J+I+qK7ipk6Oky38wCfBQCAHAhU9evXj8ATEDdNsxswYECT0xQBBEQrSfWZVPM5/a7nPWtHy+S25PU09cf3vmTS2TOBp7j6QzstwGcBACBw/fr1s40bN5qPuGKHt/SmO++885LeDABJ0vLlE2adKOqr+iqa6hJF1kEM7Wh5dC2TXtuKVbrQzOry6RH3x/e+KJ29bdu2bqqdAk+XXnppLP2Ja7+F1o7DZwEAIAcCT/Pnz3flAAoKCswnBJ7gJZ2M6Q1X/a40gBymC784prlE3E7sF5QR9sfnvmQKjKuWYGaqXVz9oZ0W4rMAABCo/r+VmNF0u+LiYvMJU+3gpc2bN1cVgAUAINt0Y0Mr2pWVlSW9KQAAANa5c2fr0KGDl9PtCDzBS4rytm/f3r35AADINt3YOHr0qO3atSvpTQEAADCfC4wTeIKXtmzZ4i4KmroMJQAAjZGZyr1nz56kNwUAAMDJBJ6Ule0TAk/wkt5sTLMDAESle/fubtXUw4cPu8wnAACANNR5OnDgQFUNSl8QeIJ3VFRcxcUJPAEAoqIC4z179nT/rSLjAAAAacnI3uhZnScCT/C2sDgr2gEAolRUVFR1crdhwwb3AAAASEpBQYEVFhZ6V+eJwBO8DDyddtpp1qVLl6Q3BQAQsGHDhrmfq1atsq+++srmzZuX9CYBAIAc179/fwJPQByBJwqLAwCiNmDAAPddo4wn1XlSzScAAIAk9fOwwDiBJ3gbeAIAIEoKNKnI+P79++3IkSPWunXrpDcJAADkuP79+7vzkh07dpgvCDzBK5kK/gSeAABxGDJkiPupEzwAAICk9f3tWtinAuMEnuBlYXECTwCAOIwYMcL9PHz4sB07dizpzQEAAAGobME0OdU77tWrl61du9Z8QeAJXtmyZYvl5+dbt27dkt4UAEAO1XlS4AkAACAbU/mPtfBm1tChQ23NmjXmC6pkwiu7d+92QScKiwMA4jo5vPTSS61du3Y2atSopDcHAAB4rm3btlkJPC1YsMBdH3ft2tXSjsATvOLLGwsAEI6rr7466U0AAACBBZ4qKyubnVAxePBg9/+uXr3ai+tjptohFfSm0RuwoakMCjx16dIltu0CAAAAACCb2dTSkqwn1XlS3WNfptsReEJqtG/f3g4ePFjn68ePH3cr2vkQ0QUAAAAA4GRKuJCjR49aS6fbKeOpJYXK40LgCd4EnsrKylzwicATAAAAACDXA0/l5eW2Y8cOSzsCT/Am8KRpdkLgCQAAAACQq1PtZNCgQdaqVSuX9ZR2BJ7gTeBpz5497mfnzp1j3CoAAAAAANKV8dS2bVsbOHCgF3WeIl3VrrS01Pbt23fK8506dbLTTz/dmzZqKCsxK19pVlBkVhjB38/hdhR4Ug2n+jKeCgsLqyLEIR8HcfUnpHZCOwYAAAAAhKdtlgJPMn78eNu/f7/lbOBJF4HFxcV1vl5SUtLii8E42qhyeJfZ/Glmm2f//lzfKWZjp5vlZ3HqVw6305ipdrVNswvtOIirPyG1E9oxgNwMnqb1pkDa2whtbOhPC9EfAIAngadjLZxqJ8OGDTMfRBZ4qu1LuSmvp6WNKrrQ3DK35nP6fd49ZhNm0U4W2mlM4Klnz57BHwdx9SekdkI7BpB7wdM03xRIcxuhjQ39aQH6AwDwRJvfZvBkI+PJF9R4auzdJn3xV1bUfF6/6/myUtrJQjsKPB06dMitXFdXjacuXbpYYuLab0gvjoFUCSl42qjAZjbF0U4MbYQ2NvSnBegPACAHp9r5gsBTYyjFud7XV9BOFtrp0KGD+6ng08mOHDni5q4muqJdXPsN6cUxgCil9KZAatuIE/1JN/oDAMjRqXa+IPDUGAXDG3i9iHay0I4ynuTAgQO1TrOTRANPce03pBfHAKKU0psCqW2jhSorK10mrb5f6pvm3dL+qB21oUdtN1YS4cH4NAn9AQB4pFWrVpaXl5dTGU+RrmoXjMLiE/PqleJc/e5TXmuzPpOyV+wxx9vJBJ5quwAoLy+vKt6ZmLj2G9KLYwBRSulNgdS20UKLFy+2t956q+rO4//8n//TnQRmuz/ffPONzZkzp+rmyT/90z9Z4jwYnyahPwAAj+Tl5blzj1wKPEWW8dRQgCAbAYQ42qiiYo66sKxOv+v5bMrhduoLPGXuEp922mnBHwdx9SekdkI7BpDjgU0FMqvT73o+2zcFomwnrr60wIgRI+zBBx+0+++/35544om6g04t7M9FF11kDzzwgGtHj1TwYHyahP4AADzTNscCT5FlPGlVD63uEeWSs3G0UUUriGjFKs2rV4pzVMva5nA79QWeMs+1a9cu+OMgrv6E1E5oxwByL3haRQFMFQ+uvpJVVDcFom4nhjZaMjatW7e2IUOGRN4fnVgOHTo0J481+uPBZwGaVntL0yA9/d7XqoyxnCfFxfPxCKYPQj+a5bTTTmt4qn9A7/G8ShUfaEBZWZl17tzZ9u7da4WFhfFsGXLSP//zP9vEiRNtzJgxNZ7/+uuvbd68efbf//t/T2zbgGziczU7+yOuL9nYv8zjCmzG0U7EbYQ2NvSnhejP7/8r3zPZ2SeHd51YZbB6EFCZZwoC6maUB3TcFhcX1/m6bt6l4cI0V8YjiD4I/WiR5557zrp3725Tp071+j3e2M9VajwhVZT1VNdUu0xGFABkxHWiHPsJuS4w47hrGEc7EbcR2tjQnxaiP8g2XZCqtmN1+l0ZacqA9kBtwdKmvJ4qAYxHEH0Q+tEiHTp0yFrGkw/vcVa1Q6oouFTbqnZ6U9ZW3wkAEK21a9em4oQlDtu2bXOPljp+/LgtX77cKiqqLUIQKCXOr1y5stbvbsB7mnqjLIjqC4qIftfzykhDfEIYjxD6IPQjsoSLUBF4QqroDVjbctNkPAFAMmbPnm1/+tOf3CpwjZid7yUFiD7//HN7+umn7R//+EeL/54Cda+//rr9+c9/tq1bt1qotOLsq6++ai+99JKtX78+6c0Bsk/1Xup9fUVcW4JQxiOEPgj9iCzhIlQEnpAqdaUcKvBExhOANAt1ZRKtxFZUVGRvvvmmvfHGG8GdJO3YscOef/55+/LLL+3yyy+3a6+91j1/5MiRZh0DCs6p1sHjjz/u/lvBJ9UoVBZUSJYuXWpPPfWUbdy40e6++24744wzLDQav1Df12ikguENvF4U15YglPEIoQ9CP1I11c4HBJ6QKgouMdUOgG908f0v//IvtmnTJguN7sjddttt7rFq1SqX/fT99997H0hRYElZTs8884wdPnzYHn30UZswYYJb7U7j+a//+q9NHs+ZM2fajBkzXMCiT58+Lvh0ySWX2Ny5c11wK4SsoF27drk+6jFo0CD7T//pPwUbdMqMJ3JYYfGJIsN5rWs+r9/1PPW34hXCeITQB6EfWZtqVxloNvnJCDzBm+LiBJ4ApJWCDD179nQZQc3JlPHBOeec44IMQ4cOtffee88FbLSKim8nTAqYLVy40P793//drZiqwNCTTz5p/fv3d68rCKVx7N27t3vInDlz7IsvvrA9e/bU+7dHjx5tv/76qy1atMj93qZNG7v66qvt4YcfdtP5FHzSFLydO3eab3RTaNasWfYf//EfLoB2yy232J133mkdO3a0ECm4qqwujSlynFa26jOp5nP6Xc97QqsutuT1VAlgPILog9CPFl/3VlZW1lpmJsT3OKvaIVXqSjnUc9R4ApBWypJRRpCCMR999JHdfPPNFiKduKifY8aMccGYV155xQWiFFzp27evpZlO7rScsLKPNL1u1KhRLsOpS5cuNf6dxm///v123333uXE9duyYffPNN5afn2/bt2+322+/vc42zjzzTLvwwgtdXazBgwe7YKQoM+iJJ56wn3/+2T799FOXNXbBBRfYFVdckfrAjaaaqe7VV1995fbhlVde6ca/bdu2FiqNs4JsGqOzzjor6c1B0rScula2UpFh1XvR1Btfsjmqrcaoz7/aForQ53rsqzXm+HgE0QehHy2+7s3Wda4P73ECT0gVvel0t1l3hnXCL5lIMBlPANKse/furj7Qu+++62oijRgxwkKl7KAHH3ywKpDz7LPP1hnISQNNnVOgTCv0KVB266231hooUwH1n376yaZOnWrdunVzz23YsMF9D7Vr165RbU2ePNm1o6ypxx57zGU9SV5enp177rnuuFiwYIEL5KitcePG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/2EPPfSQDR48OOnNAbKCz9VTsU8A+O7111+38vJye+SRR8ynz1VqPHmuEXHDoPqajf5m6+/4Ipf6CgDNoRpPUu/KdgAAACmYbrdx40Y7cuSI+YTAk8f27Nlj//t//2/79NNPXVHUkAMnP/74o/3f//t/bdmyZS3+e4sWLbJ/+7d/y8rfSjMVyf3www/tn//5n+uvWwIAOS5T44nAEwAASHvg6fjx47Zu3TrzCYEnjymlTUspzps3z9Wm2LZtm4Vm//79Lp3wnXfesTPPPNOGD29gieFGOPvss91Kdq+99pr7uyEGZVSv5JlnnrEffvjBrr766qqLKgDAqVq3bm3t2rWzAwcOJL0pAAAAderevbsVFBTY6tWrzSc5EXj65Zdf7JNPPgluylFeXp5dccUV9uijj7rslmeffda+/vprL5dXPJnGSuP2pz/9yUVz77zzTps6darl5+e73998880m/T1lhCmAtXXrVuvQoYPdfffddtNNN9nSpUvt6aeftpKSkiCOD6Vc6lh//vnn3UXUk08+aRdffLGFRmP1/vvv24oVK5LeFACB6NKli+3evTvpzQAAAKg3BqCspzVr1phP2lgOaNWqlQvI9OjRw84991wLTb9+/eyJJ55wU+700FSyiRMn2jnnnOMOTN+sXbvW5syZ4+auKsvp+uuvd1Fd0d3omTNnWq9evZp8DOiCQv+v9lXbtm1t9OjR7k377rvv2vTp091/Kzuob9++5hulWyq76fPPP3dTRRSQvPzyy12/QzR//nx3nI8cOTLpTQEQiK5du7op7AAAAGk2ZMgQl6Sh675Mncq0C/OqtJapVQo4qd7Nrl27LEQKpEyZMsX+83/+z24paGUE/eUvf/EqEqpVhV599VV74YUXXCDlgQcesLvuuqsq6KQsFwWJlNGlbKWmUADutttucxcVs2fPrnGH+/7773cZUFoeWVljb731ljcXH9ony5cvt6eeesplAA0bNsz+y3/5Ly7wFGrQadOmTS7Aetlll7H6FICsBp5CPUcAAADhGDp0qLsOVMKGL3Ii40muvfbaqilaDz/8sKvnECJldSmIooPw448/thdffNGKi4tt0qRJ1rNnT0sjLQepTJ3vv//eLcF466231pqtpQwXBVkUjGrOErjaN9dcc40L0KhW1FlnneWeVztnnHGGnX766W4btC1LliyxSy65xGUN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",
- "text/plain": [
- "
"
+ "cell_type": "markdown",
+ "id": "8f065cdefd22f21e",
+ "metadata": {
+ "collapsed": false,
+ "id": "8f065cdefd22f21e"
+ },
+ "source": [
+ "THRML is a simple library for simulating probabilistic computers on GPUs.\n",
+ "\n",
+ "\n",
+ "Concretely, THRML provides tools for GPU accelerating block sampling algorithms on sparse, heterogeneous probabilistic graphical models (PGMs) like the ones that Extropic hardware runs. The primary function of THRML is to be a scaffold that makes it much easier to implement any desired block sampling algorithm than it would be to do so from scratch. As such, this notebook will walk you through the main set of tools that THRML exposes that you can use in your own explorations."
]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
- "source": [
- "def plot_node_neighbourhood(\n",
- " grid,\n",
- " G: nx.Graph,\n",
- " center: Hashable,\n",
- " hops: int,\n",
- " ax: plt.Axes,\n",
- ") -> None:\n",
- " rows, cols = len(grid), len(grid[0])\n",
- " r, c = G.nodes[center][\"coords\"]\n",
- "\n",
- " # make a rectangular subgrid\n",
- " r0, r1 = max(0, r - hops), min(rows - 1, r + hops)\n",
- " c0, c1 = max(0, c - hops), min(cols - 1, c + hops)\n",
- " rect_nodes = {grid[i][j] for i in range(r0, r1 + 1) for j in range(c0, c1 + 1)}\n",
- "\n",
- " # collect the relevant edges by length\n",
- " edges_by_k = defaultdict(list)\n",
- " for v, ed in G[center].items():\n",
- " k = int(ed.get(\"skip\", 1))\n",
- " edges_by_k[k].append((center, v))\n",
- "\n",
- " # draw edges as arcs\n",
- " max_k = max(edges_by_k.keys(), default=1)\n",
- " curve_scale = 0.8\n",
- " edge_width = 1.0\n",
- " alpha = 1.0\n",
- "\n",
- " def rad_for_edge(u, v, k):\n",
- " r1, c1 = G.nodes[u][\"coords\"]\n",
- " r2, c2 = G.nodes[v][\"coords\"]\n",
- " base = curve_scale * (k / max_k)\n",
- " # choose bend direction based on quadrant:\n",
- " if c1 == c2:\n",
- " sign = +1.0 if r2 < r1 else -1.0 # up vs down\n",
- " else: # horizontal edge\n",
- " sign = +1.0 if c2 > c1 else -1.0 # right vs left\n",
- " return sign * base\n",
- "\n",
- " # positions for plotting\n",
- " pos = {n: (G.nodes[n][\"coords\"][1], G.nodes[n][\"coords\"][0]) for n in rect_nodes | {center}}\n",
- "\n",
- " for i, k in enumerate(sorted(edges_by_k)):\n",
- " for u, v in edges_by_k[k]:\n",
- " nx.draw_networkx_edges(\n",
- " G,\n",
- " pos,\n",
- " edgelist=[(u, v)],\n",
- " ax=ax,\n",
- " edge_color=\"gray\",\n",
- " width=edge_width,\n",
- " alpha=alpha,\n",
- " arrows=True,\n",
- " arrowstyle=\"-\",\n",
- " connectionstyle=f\"arc3,rad={rad_for_edge(u, v, k)}\",\n",
- " )\n",
- "\n",
- " # draw nodes\n",
- " cont_nodes = [n for n in rect_nodes if n.__class__ == ContinuousNode]\n",
- " spin_nodes = [n for n in rect_nodes if n.__class__ == SpinNode]\n",
- "\n",
- " node_size = 20.0\n",
- "\n",
- " nx.draw_networkx_nodes(G, pos, nodelist=cont_nodes, node_color=\"black\", node_shape=\"s\", node_size=node_size, ax=ax)\n",
- " nx.draw_networkx_nodes(G, pos, nodelist=spin_nodes, node_color=\"orange\", node_shape=\"o\", node_size=node_size, ax=ax)\n",
- "\n",
- "\n",
- "# pick a few nodes in the grid to inspect\n",
- "centers = [grid[0][7], grid[10][10], grid[-1][-1]]\n",
- "\n",
- "fig, axs = plt.subplots(nrows=1, ncols=len(centers), figsize=(len(centers) * 5, 5))\n",
- "\n",
- "\n",
- "for ax, center in zip(axs, centers):\n",
- " plot_node_neighbourhood(grid, graph, center, max(edge_lengths) + 1, ax)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "62f1fb392c555b0",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "This problem is clearly much more heterogeneous than what we were looking at before. Every node has a unique local neighbourhood, and is connected to a potentially different number of spin and continuous nodes. This makes working with this graph on an accelerator like a GPU tricky. As we will now see, THRML was specifically designed to handle this heterogeneity.\n",
- "\n",
- "With our graph in hand, let's set up our sampling program. We can re-use a lot of the work that we did in the simpler example. First, let's sort the nodes and edges by type. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 20,
- "id": "79343d1ad7e971c4",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:21.228754630Z",
- "start_time": "2025-08-28T16:29:21.215625717Z"
- }
- },
- "outputs": [],
- "source": [
- "# collect the different types of nodes\n",
- "spin_nodes = []\n",
- "cont_nodes = []\n",
- "for node in graph.nodes:\n",
- " if isinstance(node, SpinNode):\n",
- " spin_nodes.append(node)\n",
- " else:\n",
- " cont_nodes.append(node)\n",
- "\n",
- "\n",
- "# spin-spin interactions\n",
- "ss_edges = [[], []]\n",
- "\n",
- "# continuous-continuous interactions\n",
- "cc_edges = [[], []]\n",
- "\n",
- "# spin-continuous interactions\n",
- "sc_edges = [[], []]\n",
- "\n",
- "for edge in zip(*edges):\n",
- " if isinstance(edge[0], SpinNode) and isinstance(edge[1], SpinNode):\n",
- " ss_edges[0].append(edge[0])\n",
- " ss_edges[1].append(edge[1])\n",
- " elif isinstance(edge[0], ContinuousNode) and isinstance(edge[1], ContinuousNode):\n",
- " cc_edges[0].append(edge[0])\n",
- " cc_edges[1].append(edge[1])\n",
- " elif isinstance(edge[0], SpinNode):\n",
- " sc_edges[0].append(edge[0])\n",
- " sc_edges[1].append(edge[1])\n",
- " else:\n",
- " sc_edges[1].append(edge[0])\n",
- " sc_edges[0].append(edge[1])"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "da08c4f94830376a",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "Now we can set up some interactions. For some of the factors, we will re-use our code from the first part of this example "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 21,
- "id": "40cc0d7ed96795f9",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:21.683064393Z",
- "start_time": "2025-08-28T16:29:21.217766402Z"
- }
- },
- "outputs": [],
- "source": [
- "# we will just randomize the weights\n",
- "\n",
- "key, subkey = jax.random.split(key, 2)\n",
- "cont_quad = QuadraticFactor(jax.random.uniform(subkey, (len(cont_nodes),), minval=2, maxval=3), Block(cont_nodes))\n",
- "\n",
- "key, subkey = jax.random.split(key, 2)\n",
- "cont_linear = LinearFactor(jax.random.normal(subkey, (len(cont_nodes),)), Block(cont_nodes))\n",
- "\n",
- "key, subkey = jax.random.split(key, 2)\n",
- "cont_coupling = CouplingFactor(\n",
- " jax.random.uniform(subkey, (len(cc_edges[0]),), minval=-1 / 10, maxval=1 / 10),\n",
- " (Block(cc_edges[0]), Block(cc_edges[1])),\n",
- ")\n",
- "\n",
- "key, subkey = jax.random.split(key, 2)\n",
- "spin_con_coupling = CouplingFactor(\n",
- " jax.random.normal(subkey, (len(sc_edges[0]),)), (Block(sc_edges[0]), Block(sc_edges[1]))\n",
- ")"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "a27cf9451faf6c44",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "For the factors that involve only spin variables, we will use some built in functionality from THRML. THRML implements sampling functionality for arbitrary discrete-variable EBMs in `thrml.models.discrete_ebm` that we can apply to our problem. First, the spin factors,"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 22,
- "id": "8b829a81609def9e",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:22.007023492Z",
- "start_time": "2025-08-28T16:29:21.683300609Z"
- }
- },
- "outputs": [],
- "source": [
- "key, subkey = jax.random.split(key, 2)\n",
- "spin_linear = SpinEBMFactor([Block(spin_nodes)], jax.random.normal(subkey, (len(spin_nodes),)))\n",
- "\n",
- "key, subkey = jax.random.split(key, 2)\n",
- "spin_coupling = SpinEBMFactor([Block(x) for x in ss_edges], jax.random.normal(subkey, (len(ss_edges[0]),)))"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "7b426e194f944c72",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "The Gaussian sampler we wrote for the first part will work our new problem as it is because it won't be seeing any new types of interactions. The Binary sampler built into THRML will have to be extended to handle our `LinearInteraction`. Luckily, it was designed with this kind of modification in mind."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 23,
- "id": "d9e776179348c681",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:22.019981340Z",
- "start_time": "2025-08-28T16:29:22.008840315Z"
- }
- },
- "outputs": [],
- "source": [
- "class ExtendedSpinGibbsSampler(SpinGibbsConditional):\n",
- " def compute_parameters(\n",
- " self,\n",
- " key: Key,\n",
- " interactions: list[PyTree],\n",
- " active_flags: list[Array],\n",
- " states: list[list[_State]],\n",
- " sampler_state: _SamplerState,\n",
- " output_sd: PyTree[jax.ShapeDtypeStruct],\n",
- " ) -> PyTree:\n",
- " field = jnp.zeros(output_sd.shape, dtype=float)\n",
- "\n",
- " unprocessed_interactions = []\n",
- " unprocessed_active = []\n",
- " unprocessed_states = []\n",
- "\n",
- " for interaction, active, state in zip(interactions, active_flags, states):\n",
- " # if its our new interaction, handle it\n",
- " if isinstance(interaction, LinearInteraction):\n",
- " state_prod = jnp.prod(jnp.stack(state, -1), -1)\n",
- " field -= jnp.sum(interaction.weights * active * state_prod, axis=-1)\n",
- "\n",
- " # if we haven't seen it, remember it\n",
- " else:\n",
- " unprocessed_interactions.append(interaction)\n",
- " unprocessed_active.append(active)\n",
- " unprocessed_states.append(state)\n",
- "\n",
- " # make the parent class deal with THRML-native interactions\n",
- " field -= super().compute_parameters(\n",
- " key, unprocessed_interactions, unprocessed_active, unprocessed_states, sampler_state, output_sd\n",
- " )[0]\n",
- "\n",
- " return field, sampler_state"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "9c9c99f1a668bcc7",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "This is all the work we need to do to sample from our new graph using THRML! All that is left is to set up our Block spec and run some sampling."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 24,
- "id": "36470d03be19f2f2",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:22.629298283Z",
- "start_time": "2025-08-28T16:29:22.011174500Z"
- }
- },
- "outputs": [],
- "source": [
- "# tell THRML the shape and datatype of our new node\n",
- "new_sd = {SpinNode: jax.ShapeDtypeStruct(shape=(), dtype=jnp.bool)}\n",
- "\n",
- "# Our new graph is still two-colorable, however within each color there are two different types of node\n",
- "# this means that we can't make a single block to represent each color because all of the nodes within a block have to be of the same type\n",
- "# however, we might still want to ensure that the two blocks that represent each color group are sampled at the same \"algorithmic\" time\n",
- "# i.e even though we can't sample these blocks directly in parallel because they use different update rules, we want to make sure that they\n",
- "# receive the same state information\n",
- "# we can make this happen in THRML by passing in a list of tuples of blocks to BlockGibbsSpec instead of a list of Blocks\n",
- "# the blocks in each tuple will be sampled at the same algorithmic time\n",
- "blocks = [\n",
- " (Block(coloring[0][SpinNode]), Block(coloring[0][ContinuousNode])),\n",
- " (Block(coloring[1][SpinNode]), Block(coloring[1][ContinuousNode])),\n",
- "]\n",
- "\n",
- "block_spec = BlockGibbsSpec(blocks, [], node_shape_dtypes | new_sd)\n",
- "\n",
- "# now we can assemble our program\n",
- "\n",
- "# first, choose the right update rule for each block in the spec\n",
- "ber_sampler = ExtendedSpinGibbsSampler()\n",
- "samplers = []\n",
- "for block in block_spec.free_blocks:\n",
- " if isinstance(block.nodes[0], SpinNode):\n",
- " samplers.append(ber_sampler)\n",
- " else:\n",
- " samplers.append(sampler)\n",
- "\n",
- "# collect all of our factors\n",
- "factors = [cont_quad, cont_linear, cont_coupling, spin_con_coupling, spin_linear, spin_coupling]\n",
- "\n",
- "program = FactorSamplingProgram(block_spec, samplers, factors, [])"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "db464513aa82d8ce",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "Our program is doing a lot of work to pad out the interaction structure and make our sampling program GPU-compatible:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 25,
- "id": "21906c92ceb15017",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:22.671053983Z",
- "start_time": "2025-08-28T16:29:22.632961366Z"
- }
- },
- "outputs": [
+ },
{
- "data": {
- "text/plain": [
- "Array([ True, True, True, True, True, False, False, False, False,\n",
- " False], dtype=bool)"
+ "cell_type": "markdown",
+ "id": "5ee0766846938947",
+ "metadata": {
+ "collapsed": false,
+ "id": "5ee0766846938947"
+ },
+ "source": [
+ "We will demonstrate the capabilities of THRML by using it to implement the Gibbs sampling algorithm for a Gaussian PGM.\n",
+ "\n",
+ "Gibbs sampling is obviously not a practical numerical method for Gaussian sampling in most cases, and should probably instead be handled using the [Cholesky decomposition](https://en.wikipedia.org/wiki/Cholesky_decomposition). We implement it here solely for the purposes of demonstrating THRML, which in reality will be used to attack more complex problems that can't be treated analytically.\n",
+ "\n"
]
- },
- "execution_count": 25,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "# let's look at an example of the padding\n",
- "program.per_block_interaction_active[0][0][0]"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "c36435a651622399",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "Now we are ready to sample. In this case, we will simply observe the state of our nodes directly"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 26,
- "id": "aeb00d18260080cb",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:26.417465165Z",
- "start_time": "2025-08-28T16:29:22.664903387Z"
- }
- },
- "outputs": [],
- "source": [
- "batch_size = 50\n",
- "\n",
- "schedule = SamplingSchedule(\n",
- " # how many iterations to do before drawing the first sample\n",
- " n_warmup=100,\n",
- " # how many samples to draw in total\n",
- " n_samples=300,\n",
- " # how many steps to take between samples\n",
- " steps_per_sample=15,\n",
- ")\n",
- "\n",
- "\n",
- "# construct the initial state of the iterative sampling algorithm\n",
- "init_state = []\n",
- "for block in block_spec.free_blocks:\n",
- " init_shape = (\n",
- " batch_size,\n",
- " len(block.nodes),\n",
- " )\n",
- " key, subkey = jax.random.split(key, 2)\n",
- " if isinstance(block.nodes[0], ContinuousNode):\n",
- " init_state.append(0.1 * jax.random.normal(subkey, init_shape))\n",
- " else:\n",
- " init_state.append(jax.random.bernoulli(subkey, 0.5, init_shape))\n",
- "\n",
- "key, subkey = jax.random.split(key, 2)\n",
- "keys = jax.random.split(subkey, batch_size)\n",
- "\n",
- "samples = jax.vmap(lambda k, i: sample_states(k, program, schedule, i, [], [Block(spin_nodes), Block(cont_nodes)]))(\n",
- " keys, init_state\n",
- ")"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "e325d34504bc254b",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "Let's visualize our samples. Our data is very high-dimensional, but we can use a PCA to try and get some idea of the structure of the distribution."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 27,
- "id": "af9c16dc493b03f",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:26.742970708Z",
- "start_time": "2025-08-28T16:29:26.421361750Z"
- }
- },
- "outputs": [],
- "source": [
- "all_samples = jnp.concatenate(samples, axis=-1)\n",
- "pca = PCA(n_components=3)\n",
- "preproc_data = StandardScaler().fit_transform(jnp.reshape(all_samples, (-1, all_samples.shape[-1])))\n",
- "transformed_data = pca.fit_transform(preproc_data)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 28,
- "id": "704fc2b7ab8f46f4",
- "metadata": {
- "ExecuteTime": {
- "end_time": "2025-08-28T16:29:27.018178933Z",
- "start_time": "2025-08-28T16:29:26.750400616Z"
- }
- },
- "outputs": [
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5096dab893ef7148",
+ "metadata": {
+ "collapsed": false,
+ "id": "5096dab893ef7148"
+ },
+ "source": [
+ "Specifically, in the first part of this example we will consider a PGM that embodies the Gaussian distribution\n",
+ "\n",
+ "$$P(x) \\propto e^{-E_G(x)}$$\n",
+ "\n",
+ "Where the energy function $E_G(x)$ is,\n",
+ "\n",
+ "$$E_G(x) = \\frac{1}{2} \\left(x - \\mu \\right)^T A \\left( x - \\mu \\right) $$\n",
+ "\n",
+ "and $A = \\Sigma^{-1}$, where $\\Sigma$ is the covariance matrix of the distribution.\n",
+ "\n",
+ "We can expand this and write the energy function as a sum of terms,\n",
+ "\n",
+ "$$E_G(x) + C = \\frac{1}{2} \\sum_i A_{ii} \\: x_i^2 + \\sum_{j>i} A_{ij} \\: x_i \\: x_j + \\sum_i b_i \\: x_i$$\n",
+ "\n",
+ "\n",
+ "Where $C$ is a constant independent of $x$, and $b = -\\mu^T A$ is a biasing vector.\n",
+ "\n",
+ "This form makes the graphical interpretation of the problem clear. Each of the variables $x_i$ can be represented by a node, and the nonzero matrix elements of $A$ define edges between the nodes.\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "60ee0c3a9d0d3139",
+ "metadata": {
+ "collapsed": false,
+ "id": "60ee0c3a9d0d3139"
+ },
+ "source": [
+ "We will use the Gibbs sampling algorithm to draw samples from our Gaussian distribution. To do this, we first identify the distribution of each $x_i$ conditioned on the rest of the graph,\n",
+ "\n",
+ "$$P(x_i | x_{nb(i)}) \\propto e^{-E_i (x_i ,x_{nb(i)})}$$\n",
+ "\n",
+ "$$E_i (x_i ,x_{nb(i)}) = \\frac{1}{2} A_{ii} \\: x_i^2 + x_i \\left( \\sum_{j \\in nb(i)} \\: A_{ij} \\: x_j + b_i \\right)$$\n",
+ "\n",
+ "where $nb(i)$ indicates the neighbours of node i, which in this case is all j such that $A_{ij} \\neq 0$. This form makes it clear that Gibbs sampling is local, i.e the state of each node is updated using only information about nodes that it is directly connected to.\n",
+ "\n",
+ "We can write this in a different form that makes it obvious that the conditional is Gaussian,\n",
+ "\n",
+ "$$E_i (x_i ,x_{nb(i)}) + D = \\frac{1}{2} (x_i - m_i) A_{ii} (x_i - m_i) $$\n",
+ "\n",
+ "$$ m_i = - \\left( \\sum_{j \\in nb(i)} \\frac{A_{ij}}{A_{ii}} x_j + \\frac{b_i}{A_{ii}} \\right) $$\n",
+ "\n",
+ "where $D$ is a constant independent of $x_i$.\n",
+ "\n",
+ "The Gibbs sampling algorithm works by iteratively updating each of the $x_i$ according to this conditional distribution. In chromatic Gibbs sampling, nodes that belong to the same color group are updated in parallel. This \"blocked\" version is what we will implement here."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "dea608a1d20ea180",
+ "metadata": {
+ "collapsed": false,
+ "id": "dea608a1d20ea180"
+ },
+ "source": [
+ "With the math out of the way, we can proceed with the implementation of our sampling algorithm using THRML. First, let's get some imports out of the way:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "dfd9f494512409ce",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:16.386902328Z",
+ "start_time": "2025-08-28T16:29:15.573490016Z"
+ },
+ "id": "dfd9f494512409ce"
+ },
+ "outputs": [],
+ "source": [
+ "import random\n",
+ "from collections import defaultdict\n",
+ "from typing import Hashable, Mapping\n",
+ "\n",
+ "import equinox as eqx\n",
+ "import jax\n",
+ "import jax.numpy as jnp\n",
+ "import matplotlib.pyplot as plt\n",
+ "import networkx as nx\n",
+ "import numpy as np\n",
+ "from jaxtyping import Array, Key, PyTree\n",
+ "from sklearn.decomposition import PCA\n",
+ "from sklearn.preprocessing import StandardScaler"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b6318cf59688b42",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:16.428988580Z",
+ "start_time": "2025-08-28T16:29:16.428562851Z"
+ },
+ "id": "b6318cf59688b42"
+ },
+ "outputs": [],
+ "source": [
+ "from thrml.block_management import Block\n",
+ "from thrml.block_sampling import (\n",
+ " BlockGibbsSpec,\n",
+ " BlockSamplingProgram,\n",
+ " sample_states,\n",
+ " sample_with_observation,\n",
+ " SamplingSchedule,\n",
+ ")\n",
+ "from thrml.conditional_samplers import (\n",
+ " _SamplerState,\n",
+ " _State,\n",
+ " AbstractConditionalSampler,\n",
+ ")\n",
+ "from thrml.factor import AbstractFactor, FactorSamplingProgram\n",
+ "from thrml.interaction import InteractionGroup\n",
+ "from thrml.models.discrete_ebm import SpinEBMFactor, SpinGibbsConditional\n",
+ "from thrml.observers import MomentAccumulatorObserver\n",
+ "from thrml.pgm import AbstractNode"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6c3e91042f1a83",
+ "metadata": {
+ "collapsed": false,
+ "id": "6c3e91042f1a83"
+ },
+ "source": [
+ "Next, we will define our graph. In THRML, nodes that represent random variables with different data types (binary, categorical, continuous, etc.) are identified using distinct classes that inherit from `AbstractNode`. For our problem we only have one type of node, which we will define now,\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "dae80e1827e8f902",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:16.429239088Z",
+ "start_time": "2025-08-28T16:29:16.428923209Z"
+ },
+ "id": "dae80e1827e8f902"
+ },
+ "outputs": [],
+ "source": [
+ "class ContinuousNode(AbstractNode):\n",
+ " pass"
+ ]
+ },
{
- "data": {
- "image/png": "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",
- "text/plain": [
- "
"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "colors, edges, g = generate_grid_graph(5, 5)\n",
+ "\n",
+ "all_nodes = colors[1] + colors[2]\n",
+ "\n",
+ "node_map = dict(zip(all_nodes, list(range(len(all_nodes)))))\n",
+ "\n",
+ "fig, axs = plt.subplots()\n",
+ "\n",
+ "plot_grid_graph(g, colors[0], axs)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2af1113d8aa6574",
+ "metadata": {
+ "collapsed": false,
+ "id": "2af1113d8aa6574"
+ },
+ "source": [
+ "The blue and orange nodes are the two color groups for our grid graph. Any nodes that are the same color will be sampled simultaneously during block sampling.\n",
+ "\n",
+ "With the graph in hand, we can fully define the distribution we want to sample from by choosing a corresponding inverse covariance matrix and mean vector,"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "9bc7243e0373a00",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:17.647863753Z",
+ "start_time": "2025-08-28T16:29:16.475552708Z"
+ },
+ "id": "9bc7243e0373a00"
+ },
+ "outputs": [],
+ "source": [
+ "# Fixed RNG seed for reproducibility\n",
+ "seed = 4242\n",
+ "key = jax.random.key(seed)\n",
+ "\n",
+ "# diagonal elements of the inverse covariance matrix\n",
+ "key, subkey = jax.random.split(key, 2)\n",
+ "cov_inv_diag = jax.random.uniform(subkey, (len(all_nodes),), minval=1, maxval=2)\n",
+ "\n",
+ "# add an off-diagonal element to the inverse covariance matrix for each edge in the graph\n",
+ "key, subkey = jax.random.split(key, 2)\n",
+ "# make sure the covaraince matrix is PSD\n",
+ "cov_inv_off_diag = jax.random.uniform(subkey, (len(edges[0]),), minval=-0.25, maxval=0.25)\n",
+ "\n",
+ "\n",
+ "def construct_inv_cov(diag: Array, all_edges: tuple[list[ContinuousNode], list[ContinuousNode]], off_diag: Array):\n",
+ " inv_cov = np.diag(diag)\n",
+ "\n",
+ " for n1, n2, cov in zip(*all_edges, off_diag):\n",
+ " inv_cov[node_map[n1], node_map[n2]] = cov\n",
+ " inv_cov[node_map[n2], node_map[n1]] = cov\n",
+ "\n",
+ " return inv_cov\n",
+ "\n",
+ "\n",
+ "# construct a matrix representation of the inverse covariance matrix for convenience\n",
+ "inv_cov_mat = construct_inv_cov(cov_inv_diag, edges, cov_inv_off_diag)\n",
+ "\n",
+ "inv_cov_mat_jax = jnp.array(inv_cov_mat)\n",
+ "\n",
+ "# mean vector\n",
+ "key, subkey = jax.random.split(key, 2)\n",
+ "mean_vec = jax.random.normal(subkey, (len(all_nodes),))\n",
+ "\n",
+ "# bias vector\n",
+ "b_vec = -1 * jnp.einsum(\"ij, i -> j\", inv_cov_mat, mean_vec)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6c2ffe8502458738",
+ "metadata": {
+ "collapsed": false,
+ "id": "6c2ffe8502458738"
+ },
+ "source": [
+ "Now we can construct a program to sample from the distribution we just defined. All block sampling routines follow more or less the same set of steps\n",
+ "\n",
+ "1. Divide your graph into two sets of blocks. The first set, the \"free\" blocks, will be updated during sampling. The second set, the \"clamped\" blocks, will have their nodes fixed to a constant value during sampling. This is often useful. For example, in the case of EBM sampling, this clamping allows for sampling from a distribution conditioned on the clamped nodes.\n",
+ "2. Iteratively update the states of your free blocks. This means:\n",
+ " 1. Initialize the state of each of the free nodes\n",
+ " 2. Update the state of each of the free nodes according to some rule. The update rule for each node is some function that takes in a set of parameters and the states of some subset of the other nodes in the graph, and returns an updated state for the node.\n",
+ " 3. Make some observation of the current state of the program. This might mean simply writing down the state of some subset of the nodes, or it might mean computing some more complex observable.\n",
+ " 4. Repeat steps 2 and 3 until a statisfactory number of observations have been made\n",
+ "\n",
+ "THRML lets you run any version of this procedure that you want while writing minimal amounts of new code. We have to define 3 main things to accomplish this:\n",
+ "\n",
+ "\n",
+ "1. A block specification: a division of our problem graph into free and clamped blocks\n",
+ "2. A set of interactions: these allow us to specify what information is required to compute the conditional updates for each node in our graph.\n",
+ "3. Conditional sampling rules: these specify how to update the state of each node in our graph given the interactions that are applicable to that node\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a63b95fb57e9cc5d",
+ "metadata": {
+ "collapsed": false,
+ "id": "a63b95fb57e9cc5d"
+ },
+ "source": [
+ "First, we will define a block spec for our problem. In our case, we simply want to sample each color group in sequence, and we won't be clamping any of the nodes,"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "469ccb993534d861",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:17.650211499Z",
+ "start_time": "2025-08-28T16:29:17.648290975Z"
+ },
+ "id": "469ccb993534d861"
+ },
+ "outputs": [],
+ "source": [
+ "# a Block is just a list of nodes that are all the same type\n",
+ "# forcing the nodes in a Block to be of the same type is important for parallelization\n",
+ "free_blocks = [Block(colors[1]), Block(colors[2])]\n",
+ "\n",
+ "# we won't be clamping anything here, but in principle this could be a list of Blocks just like above\n",
+ "clamped_blocks = []\n",
+ "\n",
+ "# every node in the program has to be assigned a shape and datatype (or PyTree thereof).\n",
+ "# this is so THRML can build an internal \"global\" representation of the state of the sampling program using a small number of jax arrays\n",
+ "node_shape_dtypes = {ContinuousNode: jax.ShapeDtypeStruct((), jnp.float32)}\n",
+ "\n",
+ "# our block specification\n",
+ "spec = BlockGibbsSpec(free_blocks, clamped_blocks, node_shape_dtypes)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6fde116cd6ceaa15",
+ "metadata": {
+ "collapsed": false,
+ "id": "6fde116cd6ceaa15"
+ },
+ "source": [
+ "Now the interactions. Our PGM is of the undirected variety, which means that it can be described naturally using the language of [Factor Graphs](https://en.wikipedia.org/wiki/Factor_graph). Deep knowledge of factor graphs and their nomenclature isn't necessary to use THRML; in this context, a Factor is simply an interaction between a set of variables that has no natural direction.\n",
+ "\n",
+ "Factor graphs can be viewed as hypergraphs where each factor represents a hyperedge connecting multiple variables. Hyperedges in factor graphs can connect any number of variables, allowing for natural representation of higher-order interactions. For example, a three-way interaction term like $x_1 x_2 x_3$ in an energy function corresponds to a single hyperedge (factor) connecting three variable nodes.\n",
+ "\n",
+ "A nice thing about the Factor formalism is that in the context of Gibbs sampling, the conditional update rule for the $i^{th}$ node depends only on factors that involve $x_i$. This means that given a set of factors for a graph, if we want to update the state of a given node, we only need to consider a small subset of all of the factors that are local to that node.\n",
+ "\n",
+ "In our case, our energy function can we written as a sum of a bunch of terms, each of which is associated with a factor. There are three distinct types of term in this sum, each of which is associated with a different type of factor:\n",
+ "\n",
+ "1. $A_{ii} \\: x_i^2$\n",
+ "2. $b_i \\: x_i$\n",
+ "3. $A_{ij} \\: x_i \\: x_j$\n",
+ "\n",
+ "Each of these factors contributes to our conditional update rule in a different and consistent way. As such, in the context of algorithms like Gibbs sampling, Factors are defined by their ability to produce a set of directed interactions that effect the different nodes they involve in potentially different ways. In the case of our Gaussian sampling problem, our factors generate interactions that are either:\n",
+ "\n",
+ "1. Linear: contribute terms to the energy function like $c_i \\: x_i$, where $c_i$ does not depend on the \"head node\" $x_i$ but may depend on some \"tail nodes\" $x_{nb(i)}$\n",
+ "2. Quadratic: contribute terms to the energy function like $d_i \\: x_i^2$, where in our case $d_i$ is a constant independent of the state of the sampling program\n",
+ "\n",
+ "THRML implements these abstractions directly in code.\n",
+ "\n",
+ "The most primitive object is the `InteractionGroup`, which specifies what parametric and state information should be supplied to a given node to compute it's conditional update. An `InteractionGroup` is composed of a set of interaction parameters, a set of \"head nodes\", and sets of \"tail nodes\". The head nodes are the nodes whose conditional update is effected by the interaction, and the tail nodes specify which neighbouring node states are required to compute the conditional update.\n",
+ "\n",
+ "\n",
+ "THRML also defines Factors via the `AbstractFactor` interface. In full generality, THRML defines a factor as anything that can be reduced to a set of `InteractionGroup`s. THRML also defines more specialized factors (like ones that define an energy), however we won't be using those here.\n",
+ "\n",
+ "We can use these objects to set up our sampling program,"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6829ad246eb0ca05",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:17.687087542Z",
+ "start_time": "2025-08-28T16:29:17.651912413Z"
+ },
+ "id": "6829ad246eb0ca05"
+ },
+ "outputs": [],
+ "source": [
+ "# these are just arrays that we can identify by type, will be useful later\n",
+ "\n",
+ "\n",
+ "class LinearInteraction(eqx.Module):\n",
+ " \"\"\"An interaction of the form $c_i x_i$.\"\"\"\n",
+ "\n",
+ " weights: Array\n",
+ "\n",
+ "\n",
+ "class QuadraticInteraction(eqx.Module):\n",
+ " \"\"\"An interaction of the form $d_i x_i^2$.\"\"\"\n",
+ "\n",
+ " inverse_weights: Array\n",
+ "\n",
+ "\n",
+ "# now we can set up our three different types of factors\n",
+ "\n",
+ "\n",
+ "class QuadraticFactor(AbstractFactor):\n",
+ " r\"\"\"A factor of the form $w \\: x^2$\"\"\"\n",
+ "\n",
+ " # 1/A_{ii}\n",
+ " inverse_weights: Array\n",
+ "\n",
+ " def __init__(self, inverse_weights: Array, block: Block):\n",
+ " # in general, a factor is initialized via a list of blocks\n",
+ " # these blocks should all have the same number of nodes, and represent groupings of nodes involved in the factor\n",
+ " # for example, if a Factor involved 3 nodes, we would initialize it with 3 parallel blocks of equal length\n",
+ " super().__init__([block])\n",
+ "\n",
+ " # this array has shape [n], where n is the number of nodes in block\n",
+ " self.inverse_weights = inverse_weights\n",
+ "\n",
+ " def to_interaction_groups(self) -> list[InteractionGroup]:\n",
+ " # based on our conditional update rule, we can see that we need this to generate a Quadratic interaction with no tail nodes (i.e this interaction has no dependence on the neighbours of x_i)\n",
+ "\n",
+ " # we create an InteractionGroup that implements this interaction\n",
+ "\n",
+ " interaction = InteractionGroup(\n",
+ " interaction=QuadraticInteraction(self.inverse_weights),\n",
+ " head_nodes=self.node_groups[0],\n",
+ " # no tail nodes in this case\n",
+ " tail_nodes=[],\n",
+ " )\n",
+ "\n",
+ " return [interaction]\n",
+ "\n",
+ "\n",
+ "class LinearFactor(AbstractFactor):\n",
+ " r\"\"\"A factor of the form $w \\: x$\"\"\"\n",
+ "\n",
+ " # b_i\n",
+ " weights: Array\n",
+ "\n",
+ " def __init__(self, weights: Array, block: Block):\n",
+ " super().__init__([block])\n",
+ " self.weights = weights\n",
+ "\n",
+ " def to_interaction_groups(self) -> list[InteractionGroup]:\n",
+ " # follows the same pattern as previous, still no tail nodes\n",
+ "\n",
+ " return [\n",
+ " InteractionGroup(interaction=LinearInteraction(self.weights), head_nodes=self.node_groups[0], tail_nodes=[])\n",
+ " ]\n",
+ "\n",
+ "\n",
+ "class CouplingFactor(AbstractFactor):\n",
+ " # A_{ij}\n",
+ " weights: Array\n",
+ "\n",
+ " def __init__(self, weights: Array, blocks: tuple[Block, Block]):\n",
+ " # in this case our factor involves two nodes, so it is initialized with two blocks\n",
+ " super().__init__(list(blocks))\n",
+ " self.weights = weights\n",
+ "\n",
+ " def to_interaction_groups(self) -> list[InteractionGroup]:\n",
+ " # this factor produces interactions that impact both sets of nodes that it touches\n",
+ " # i.e if this factor involves a term like w x_1 x_2, it should produce one interaction with weight w that has x_1 as a head node and x_2 as a tail node,\n",
+ " # and another interaction with weight w that has x_2 as a head node and x_1 as a tail node\n",
+ "\n",
+ " # if we were sure that x_1 and x_2 were always the same type of node, the two interactions could be part of the same InteractionGroup\n",
+ " # we won't worry about that here though\n",
+ " return [\n",
+ " InteractionGroup(LinearInteraction(self.weights), self.node_groups[0], [self.node_groups[1]]),\n",
+ " InteractionGroup(LinearInteraction(self.weights), self.node_groups[1], [self.node_groups[0]]),\n",
+ " ]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "211ad5584553e9d4",
+ "metadata": {
+ "collapsed": false,
+ "id": "211ad5584553e9d4"
+ },
+ "source": [
+ "Now the conditional update the rule. Here, we will define how the relevant interaction and state information should be used to produce an updated state in our iterative sampling algorithm."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "84587bfccaa38fd7",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:17.702011145Z",
+ "start_time": "2025-08-28T16:29:17.677623544Z"
+ },
+ "id": "84587bfccaa38fd7"
+ },
+ "outputs": [],
+ "source": [
+ "class GaussianSampler(AbstractConditionalSampler):\n",
+ " def sample(\n",
+ " self,\n",
+ " key: Key,\n",
+ " interactions: list[PyTree],\n",
+ " active_flags: list[Array],\n",
+ " states: list[list[_State]],\n",
+ " sampler_state: _SamplerState,\n",
+ " output_sd: PyTree[jax.ShapeDtypeStruct],\n",
+ " ) -> tuple[Array, _SamplerState]:\n",
+ " # this is where the rubber meets the road in THRML\n",
+ "\n",
+ " # this function gets called during block sampling, and must take in information about interactions and neighbour states and produce a state update\n",
+ "\n",
+ " # interactions, active_flags, and states are three parallel lists.\n",
+ "\n",
+ " # each item in interactions is a pytree, for which each array will have shape [n, k, ...].\n",
+ " # this is generated by THRML from the set of InteractionGroups that are used to create a sampling program\n",
+ " # n is the number of nodes that we are updating in parallel during this call to sample\n",
+ " # k is the maximum number of times any node in the block that is being updated shows up as a head node for this interaction\n",
+ "\n",
+ " # each item in active_flags is a boolean array with shape [n, k].\n",
+ " # this is padding that is generated internally by THRML based on the graphical structure of the model,\n",
+ " # and serves to allow for heterogeneous graph sampling to be vectorized on accelerators that rely on homogeneous data structures\n",
+ "\n",
+ " # each item in states is a list of Pytrees that represents the state of the tail nodes that are relevant to this interaction.\n",
+ " # for example, for an interaction with a single tail node that has a scalar dtype, states would be:\n",
+ " # [[n, k],]\n",
+ "\n",
+ " bias = jnp.zeros(shape=output_sd.shape, dtype=output_sd.dtype)\n",
+ " var = jnp.zeros(shape=output_sd.shape, dtype=output_sd.dtype)\n",
+ "\n",
+ " # loop through all of the available interactions and process them appropriately\n",
+ "\n",
+ " # here we are simply implementing the math of our conditional update rule\n",
+ "\n",
+ " for active, interaction, state in zip(active_flags, interactions, states):\n",
+ " if isinstance(interaction, LinearInteraction):\n",
+ " # if there are tail nodes, contribute w * x_1 * x_2 * ..., otherwise contribute w\n",
+ " state_prod = jnp.array(1.0)\n",
+ " if len(state) > 0:\n",
+ " state_prod = jnp.prod(jnp.stack(state, -1), -1)\n",
+ " bias -= jnp.sum(interaction.weights * active * state_prod, axis=-1)\n",
+ "\n",
+ " if isinstance(interaction, QuadraticInteraction):\n",
+ " # this just sets the variance of the output distribution\n",
+ " # there should never be any tail nodes\n",
+ "\n",
+ " var = active * interaction.inverse_weights\n",
+ " var = var[..., 0] # there should only ever be one\n",
+ "\n",
+ " return (jnp.sqrt(var) * jax.random.normal(key, output_sd.shape)) + (bias * var), sampler_state\n",
+ "\n",
+ " def init(self) -> _SamplerState:\n",
+ " return None"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9e4bf9792ff7b72b",
+ "metadata": {
+ "collapsed": false,
+ "id": "9e4bf9792ff7b72b"
+ },
+ "source": [
+ "With all of the parts fully defined, we can now construct our sampling program"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b90a7f2b58d39f2c",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:17.928525605Z",
+ "start_time": "2025-08-28T16:29:17.692896238Z"
+ },
+ "id": "b90a7f2b58d39f2c"
+ },
+ "outputs": [],
+ "source": [
+ "# our three types of factor\n",
+ "lin_fac = LinearFactor(b_vec, Block(all_nodes))\n",
+ "quad_fac = QuadraticFactor(1 / cov_inv_diag, Block(all_nodes))\n",
+ "pair_quad_fac = CouplingFactor(cov_inv_off_diag, (Block(edges[0]), Block(edges[1])))\n",
+ "\n",
+ "# an instance of our conditional sampler\n",
+ "sampler = GaussianSampler()\n",
+ "\n",
+ "# the sampling program itself. Combines the three main components we just built\n",
+ "prog = FactorSamplingProgram(\n",
+ " gibbs_spec=spec,\n",
+ " # one sampler for every free block in gibbs_spec\n",
+ " samplers=[sampler, sampler],\n",
+ " factors=[lin_fac, quad_fac, pair_quad_fac],\n",
+ " other_interaction_groups=[],\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8094b8ceb5bbe117",
+ "metadata": {
+ "collapsed": false,
+ "id": "8094b8ceb5bbe117"
+ },
+ "source": [
+ "`FactorSamplingProgram` is a thin wrapper on the more generic `BlockSamplingProgram`. All `FactorSamplingProgram` does is convert all of the factors passed in into `InteractionGroups` and then use them to create a `BlockSamplingProgram'. As such, prog is equivalent to prog_2 in the following:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "329f5f388dd7ec3",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:17.945152316Z",
+ "start_time": "2025-08-28T16:29:17.929214231Z"
+ },
+ "id": "329f5f388dd7ec3"
+ },
+ "outputs": [],
+ "source": [
+ "groups = []\n",
+ "for fac in [lin_fac, quad_fac, pair_quad_fac]:\n",
+ " groups += fac.to_interaction_groups()\n",
+ "\n",
+ "prog_2 = BlockSamplingProgram(gibbs_spec=spec, samplers=[sampler, sampler], interaction_groups=groups)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "acbab16fa204ede6",
+ "metadata": {
+ "collapsed": false,
+ "id": "acbab16fa204ede6"
+ },
+ "source": [
+ "Now we are finally ready to do some sampling! A sampling program in THRML simply repeatedly updates the state of each free block in the order they appear in the gibbs_spec. After every iteration of the sampling algorithm, we may observe the state and write down some information that is relevant to the problem we are trying to solve. For example, if we wanted to extract samples from some subset of the nodes of our PGM, after each iteration we could simply memorize some subset of the current state. This functionality is provided by observers in THRML.\n",
+ "\n",
+ "For the purposes of this example, it would be prudent to check that our sampling program is working correctly. To do this, we will compute estimators of some first and second moments and verify that they match up with expected values from the theory. We will use the built-in `MomentAccumulatorObserver` to accomplish this."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8701c7ba2e643ea8",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:17.981759372Z",
+ "start_time": "2025-08-28T16:29:17.934117075Z"
+ },
+ "id": "8701c7ba2e643ea8"
+ },
+ "outputs": [],
+ "source": [
+ "# we will estimate the covariances for each pair of nodes connected by an edge and compare against theory\n",
+ "# to do this we will need to estimate first moments and second moments\n",
+ "second_moments = [(e1, e2) for e1, e2 in zip(*edges)]\n",
+ "first_moments = [[(x,) for x in y] for y in edges]\n",
+ "\n",
+ "# this will accumulate products of the node state specified by first_moments and second_moments\n",
+ "observer = MomentAccumulatorObserver(first_moments + [second_moments])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6af2c19400d9f7b7",
+ "metadata": {
+ "collapsed": false,
+ "id": "6af2c19400d9f7b7"
+ },
+ "source": [
+ "Now all that is left to do is specify a few more details about how the sampling should proceed."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e258c2655c697506",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:18.423287969Z",
+ "start_time": "2025-08-28T16:29:17.976885994Z"
+ },
+ "id": "e258c2655c697506"
+ },
+ "outputs": [],
+ "source": [
+ "# how many parallel sampling chains will we run?\n",
+ "n_batches = 1000\n",
+ "\n",
+ "\n",
+ "schedule = SamplingSchedule(\n",
+ " # how many iterations to do before drawing the first sample\n",
+ " n_warmup=0,\n",
+ " # how many samples to draw in total\n",
+ " n_samples=10000,\n",
+ " # how many steps to take between samples\n",
+ " steps_per_sample=5,\n",
+ ")\n",
+ "\n",
+ "# construct the initial state of the iterative sampling algorithm\n",
+ "init_state = []\n",
+ "for block in spec.free_blocks:\n",
+ " key, subkey = jax.random.split(key, 2)\n",
+ " init_state.append(\n",
+ " 0.1\n",
+ " * jax.random.normal(\n",
+ " subkey,\n",
+ " (\n",
+ " n_batches,\n",
+ " len(block.nodes),\n",
+ " ),\n",
+ " )\n",
+ " )\n",
+ "\n",
+ "# RNG keys to use for each chain in the batch\n",
+ "keys = jax.random.split(key, n_batches)\n",
+ "\n",
+ "# memory to hold our moment values\n",
+ "init_mem = observer.init()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f6c9593b255f17ba",
+ "metadata": {
+ "collapsed": false,
+ "id": "f6c9593b255f17ba"
+ },
+ "source": [
+ "Now run the sampling:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8500ca130c435027",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:20.962893712Z",
+ "start_time": "2025-08-28T16:29:18.442376183Z"
+ },
+ "id": "8500ca130c435027"
+ },
+ "outputs": [],
+ "source": [
+ "# we use vmap to run a bunch of parallel sampling chains\n",
+ "moments, _ = jax.vmap(lambda k, s: sample_with_observation(k, prog, schedule, s, [], init_mem, observer))(\n",
+ " keys, init_state\n",
+ ")\n",
+ "\n",
+ "# Take a mean over the batch axis and divide by the total number of samples\n",
+ "moments = jax.tree.map(lambda x: jnp.mean(x, axis=0) / schedule.n_samples, moments)\n",
+ "\n",
+ "# compute the covariance values from the moment data\n",
+ "covariances = moments[-1] - (moments[0] * moments[1])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7f45604908336311",
+ "metadata": {
+ "collapsed": false,
+ "id": "7f45604908336311"
+ },
+ "source": [
+ "We can compare our covariance estimates to the real covariance matrix to see if we implemented our sampling routine correctly"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "259e4b654e434c7b",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:20.965740612Z",
+ "start_time": "2025-08-28T16:29:20.963611764Z"
+ },
+ "id": "259e4b654e434c7b",
+ "outputId": "ee7180aa-8ffb-4d58-cb00-a4e694eda122"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "0.0045360937\n"
+ ]
+ }
+ ],
+ "source": [
+ "cov = np.linalg.inv(inv_cov_mat)\n",
+ "\n",
+ "node_map = dict(zip(all_nodes, list(range(len(all_nodes)))))\n",
+ "\n",
+ "real_covs = []\n",
+ "\n",
+ "for edge in zip(*edges):\n",
+ " real_covs.append(cov[node_map[edge[0]], node_map[edge[1]]])\n",
+ "\n",
+ "real_covs = np.array(real_covs)\n",
+ "\n",
+ "error = np.max(np.abs(real_covs - covariances)) / np.abs(np.max(real_covs))\n",
+ "\n",
+ "print(error)\n",
+ "assert error < 0.01"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b436bc81aec0ab3c",
+ "metadata": {
+ "collapsed": false,
+ "id": "b436bc81aec0ab3c"
+ },
+ "source": [
+ "We achieve a really small error because we computed a ton of samples. If you reduce either the batch size or the number of samples collected by each chain this number will go up."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "881724ad88ca8f7d",
+ "metadata": {
+ "collapsed": false,
+ "id": "881724ad88ca8f7d"
+ },
+ "source": [
+ "That is everything you need to know to implement any type of PGM block sampling routine you want in THRML.\n",
+ "\n",
+ "However, you don't always have to do everything completely from scratch! THRML exposes a limited set of higher-level functionality fine-tuned to sampling problems that Extropic really cares about.\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3ede7348975534bf",
+ "metadata": {
+ "collapsed": false,
+ "id": "3ede7348975534bf"
+ },
+ "source": [
+ "Next, we will use some of these higher-level functions to implement a more complicated type of model that can't be sampled from using analytical techniques. In particular, we will implement sampling from a deep Gaussian-Bernoulli EBM. This type of model has the energy function\n",
+ "\n",
+ "$$ E(x) = E_G(x) + E_{GB}(x, s) + E_B(s)$$\n",
+ "\n",
+ "where $x$ is a vector of continuous values and $s$ is a vector of *spins*, $s_i \\in \\{-1, 1\\}$.\n",
+ "\n",
+ "$E_G(x)$ is the Gaussian energy function defined in the previous section. $E_{GB}$ is an energy function that represents the interaction between the continuous and spin-valued variables,\n",
+ "\n",
+ "$$ E_{GB}(x, s) = \\sum_{ (i, j) \\in S_{GB}} W_{ij} \\: y_i \\: x_j $$\n",
+ "\n",
+ "where $S_{GB}$ is a set of edges connecting spin and continuous variables.\n",
+ "\n",
+ "$E_{B}$ is the spin energy function,\n",
+ "\n",
+ "$$ E_B(s) = \\sum_i b_i \\: s_i + \\sum_{j > i} J_{ij} s_i s_j$$\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1739ba7eb64593f2",
+ "metadata": {
+ "collapsed": false,
+ "id": "1739ba7eb64593f2"
+ },
+ "source": [
+ "Just for fun, lets use a more complicated graph topology for this problem. We will stick with a grid, but we will add skip-connections that allow for non-nearest-neighbour interactions. We can once again use NetworkX to make this graph,"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "706df0c0fa4dbe54",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:21.014054288Z",
+ "start_time": "2025-08-28T16:29:20.965966221Z"
+ },
+ "id": "706df0c0fa4dbe54"
+ },
+ "outputs": [],
+ "source": [
+ "# first, define a new type of node\n",
+ "\n",
+ "\n",
+ "class SpinNode(AbstractNode):\n",
+ " pass"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8d6b1059f3b9d27b",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:21.014706859Z",
+ "start_time": "2025-08-28T16:29:20.990030231Z"
+ },
+ "id": "8d6b1059f3b9d27b"
+ },
+ "outputs": [],
+ "source": [
+ "# now, build a random grid out of spin and continuous nodes\n",
+ "\n",
+ "\n",
+ "def make_random_typed_grid(\n",
+ " rows: int,\n",
+ " cols: int,\n",
+ " seed: int,\n",
+ " p_cont: float = 0.5,\n",
+ "):\n",
+ " rng = random.Random(seed)\n",
+ "\n",
+ " # every time we make a node, flip a coin to decide what type it should be\n",
+ " grid = [[ContinuousNode() if rng.random() < p_cont else SpinNode() for _ in range(cols)] for _ in range(rows)]\n",
+ "\n",
+ " # Parity-based 2-coloring\n",
+ " bicol = {grid[r][c]: ((r + c) & 1) for r in range(rows) for c in range(cols)}\n",
+ "\n",
+ " # Separate by color and type\n",
+ " colors_by_type = {\n",
+ " 0: {SpinNode: [], ContinuousNode: []},\n",
+ " 1: {SpinNode: [], ContinuousNode: []},\n",
+ " }\n",
+ " for r in range(rows):\n",
+ " for c in range(cols):\n",
+ " n = grid[r][c]\n",
+ " color = bicol[n]\n",
+ " colors_by_type[color][type(n)].append(n)\n",
+ "\n",
+ " return grid, colors_by_type\n",
+ "\n",
+ "\n",
+ "grid, coloring = make_random_typed_grid(30, 30, seed)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "964bc9c4fcf1fb2b",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:21.033744454Z",
+ "start_time": "2025-08-28T16:29:21.002416755Z"
+ },
+ "id": "964bc9c4fcf1fb2b"
+ },
+ "outputs": [],
+ "source": [
+ "# now generate the edges to implement our desired skip-connected grid\n",
+ "# we will use only odd-length edges (1, 3, 5, ...) so that our 2-coloring remains valid\n",
+ "def build_skip_graph_from_grid(\n",
+ " grid: list[list[AbstractNode]],\n",
+ " skips: list[int],\n",
+ "):\n",
+ " rows, cols = len(grid), len(grid[0])\n",
+ "\n",
+ " # Build graph & annotate nodes with coords and type\n",
+ " G = nx.Graph()\n",
+ " for r in range(rows):\n",
+ " for c in range(cols):\n",
+ " n = grid[r][c]\n",
+ " G.add_node(n, coords=(r, c))\n",
+ "\n",
+ " # Edges sorted by edge length\n",
+ " u_all = []\n",
+ " v_all = []\n",
+ " for k in skips:\n",
+ " # vertical: (r, c) -> (r+k, c)\n",
+ " for r in range(rows - k):\n",
+ " r2 = r + k\n",
+ " for c in range(cols):\n",
+ " n1 = grid[r][c]\n",
+ " n2 = grid[r2][c]\n",
+ " u_all.append(n1)\n",
+ " v_all.append(n2)\n",
+ " G.add_edge(n1, n2)\n",
+ "\n",
+ " # horizontal: (r, c) -> (r, c+k)\n",
+ " for r in range(rows):\n",
+ " for c in range(cols - k):\n",
+ " c2 = c + k\n",
+ " n1 = grid[r][c]\n",
+ " n2 = grid[r][c2]\n",
+ " u_all.append(n1)\n",
+ " v_all.append(n2)\n",
+ " G.add_edge(n1, n2, skip=k)\n",
+ "\n",
+ " return (u_all, v_all), G\n",
+ "\n",
+ "\n",
+ "edge_lengths = [1, 3, 5]\n",
+ "edges, graph = build_skip_graph_from_grid(grid, edge_lengths)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "700ba823270bf471",
+ "metadata": {
+ "collapsed": false,
+ "id": "700ba823270bf471"
+ },
+ "source": [
+ "Let's visualize this graph to understand what we just created. Since the graph is no longer planar, it will be cleanest to plot the local neighbourhood of particular nodes in our grid one at a time."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d302f2a19cb6a65c",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:21.212544555Z",
+ "start_time": "2025-08-28T16:29:21.019630164Z"
+ },
+ "id": "d302f2a19cb6a65c",
+ "outputId": "0b141b66-ba50-4a41-9eb3-2ab0b83f8c85"
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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FNzwOeAUYT/atGkTfxZbYTrHg+LiAAAAAACkjCYnZeoIAcc8PhYIPAEAAAAAkMJpef/yL/9iS5cuTXpTkHAA8ptvvrH/83/+j6sn5SMCTwAAAACQ4xpR+hcxGzp0qJ1xxhk2Y8YM++yzzxijHM1yeuedd+zjjz+2MWPGuGLjOg58OxYIPAEAAABADtu8ebP927/9m1uRC+nRtm1bu/XWW23ixIn25Zdf2uuvv96oldIQhn379tkLL7xgv/zyi91yyy129dVXu+dfe+01mz272up1HiDwBAAAAAA5rEePHtauXTt7++237fjx40lvDqrRimeXX3653X333bZq1Sp77rnnCBDmgHXr1tmf//xnt2rcww8/bKNGjXLPL1y40JYvX25FRUXu908//dT+9re/pb7+E6vaAQC8FdeSxLEvfVxWYla+MvqlcCNsh7GhnRDHJ9jjGjlPmTVTp061v/71rzZv3jwX6EC6aMrdo48+6qbdPfvss3b99dfbueeem/RmIcsqKyvt66+/dlMrBwwYYHfccUfVyni7du2yOXPm2AUXXOACT/q38+fPt4qKCtu2bZv169fP0orAEwDAS7owKy4urvP1kpKSrFygxdWOc3iX2fxpZpurpU/3nWI2drpZftfstBFDO4wN7YQ4PkEe10A1AwcOtMsuu8w+//xzd4z16dMn6U3CSXr16mWPP/64ffjhhy47bfXq1XbddddZfn5+0puGLCgvL7e33nrLZbYp+HvllVdaq1YnJqkpE1FjXlBQYJMnT3bPbdmyxQWdRLWf0oypdgAAL9WWDdCU19PWjqML5y1zaz6n3+fdk702YmiHsaGdEMcnyOMaOIkudDXtThe4mQtapIuCTMpO00Or3Wk61tatW5PeLLTQypUr7emnn3Zjef/997u6Xpmgk3z77be2fv16u/nmm6sCjYsWLXI/27dvb6eddpqlGYEnAADSQFOElK1RedKJvn7X82WlfrUTktDGJrRjILT+AAlq06aNK2K8fft2++KLL5LeHNRD0+yeeOIJa926tQs+qfaPbyudwVwm0yeffGIvvfSS9e7d2/7whz/YsGHDavwbTaNTLadLL73UBg8eXCNYJT5kJxJ4AgAgDVSXpt7XV/jVTkhCG5vQjoHQ+gMkTBexV1xxhaszs2HDhqQ3B/VQdtpjjz1mo0ePtg8++MBmzpxphw4dSnqz0Eh79+51q9aprpoynO677z43la46ZR4qA7Fr167u32RodcM9e/a4wGP37t0t7Qg8AQCQBgXDG3i9yK92QhLa2IR2DITWHyAFxo0bZ3379nUXvEePHk16c9BAlpoKjasItTJgnnnmGVuzZk3Sm4V6VFZW2i+//OLGSsGnhx56yNV00gqGJ/vqq69cLSdlImqsq9cDFE3HyxQfTzMCTwAApEFh8YliyHmtaz6v3/V8tlboiqudkIQ2NqEdA6H1B0gBXczqQlcXxZoGhPQ7++yz7cknn7TCwkJ78cUX7d1337WDBw8mvVk4yZ49e2z69On2xhtv2NChQ93UukGDBtX6bzdt2uQCTwpKnbxi3U8//eR+KjB8cpZUGhF4AgB4qaG7O9m6+xNXO45W4OozqeZz+l3PZ1PE7TA2tBPi+AR5XAMNTOPS1J4FCxaQQeMJTcdS9owyoFR4/D/+4z9cZg21n9JRy+nbb7+1P/3pTy6D6a677nJZaioMXptjx465jEOtZDh+/PhTXl+3bp21bdvW/bcPgae8ykYchWVlZda5c2cX8VYEFQDQMnyuZmd/KM24thWedGGWzeXG42qnioohqy6NpghFma0RYTuMDe2EOD4+Hdd8z5yKfdJ0ulRU9oz2mTIz2rVrl/QmoZH0GTJr1iwXgCoqKnLBqC5duiS9WTlpy5Yt9t5777kMposuusiuuuqqBt9Lc+bMcUFfFZBX8Km63bt327//+7+7LCj9zccff/yUjKi0fa4SeAKABPC5WhP7AwCyi8/VU7FPmkcXuVrm/cwzz7SpU6fWWocG6bV8+XJXeFxFxydMmGCXXHKJm0qJ6Gka3Oeff27ffPON9ezZ02688UYbMGBAg//fqlWr7O9//7sLUKne2sn+8Y9/2EcffWQXXnihW83wv/23/5bYZ1pjP1d/r04FAAAAAMBJ07duuOEGe/PNN91S7ueff37Sm4QmOOOMM2zIkCH26aef2scff2yLFy+26667rlEBEDSPcnuUuargkDLPJkyYYJdddplbga4h+vd6rw0bNsz9P7VZtmxZjanXHTt2tLQj8AQAAAAAqNPIkSNdnSddSPfv39969+6d9CahCTSt69prr3Xj+P7779tzzz3nAlKq4XXyNC60zNq1a12QTzWYVDz8vvvus+7duze6DpSKjisj7dZbb601M01BrY0bN1a9phpRjQloJY0cOwAAAABAva655hp3AT1jxgw7fPhw0puDZlCWk2oGacXCrVu32lNPPeUKWGulNbS8jtMrr7xiL7zwgptid++999r999/f6KCTaFqeAla33XZbnVlMO3futCNHjrji/1q1sEOHDuYDMp4AAAAAAPXSClpahevZZ591WTPKyKDek3+UKTNq1CgbMWKELVq0yL788ks3/e6CCy5wq6f5sEJamuzatcs+++wzt3qggky33367nX322U1+b6xYscK++uorl4WmKa11Wb16tfup6ZPiy3sw0sBTHCt/+LS6SJOUlZiVr4xhNZsw2mF80t1OSJ8FsY8NgMRpeolWAmI1IAC5ThfWKpCs6UC6OFZxY/hJ07MuvvhiO++889zqafPmzbMff/zRxowZ42oLnXbaaUlvYqqpqLaCdj/88IML1ul9oX3ZnMLtZWVl9tZbb7nVB2srJn5ygEoGDRpkGzZsMF9EFnjSRWBxcXGdr5eUlLT4YjCONuJsxzm8y2z+NLPNs39/ru8Us7HTzfK7ZqeNwNphfNLdTkifBbGODYBUUL2F1157zZ0Ijh07NunNAYDEnXPOOa6OzaxZs1y9p759+ya9SWiB/Px8u/zyy10QUcEnrcD27bffuqwoBaaoAVWzvpKCPVpVbunSpa521qRJk+yiiy6yNm2aF1o5/ltdJwUCNQWyvgwmta+bYdKvXz+vAk+R1XiqLfOgKa+npY0423F0Qbtlbs3n9Pu8e7LXRmDtMD7pbiekz4JYxwZAamo2aAlqVv8BgN9NmTLFLQ+vek/6jIT/VKRaQZT/+l//q8t4Wr58uasB9eKLL9qvv/7qAiS5SjWblNmkaabPP/+8bdq0ya6++mq3ry699NJmB51EhcjXr1/vpug1VK9JdblU30nBQt+ysKnxlCaaulM9iyKjsuLE82Wl2ZnSE1o7cQltv4U2PnFgnwE5R3cWdUKpu/oAgBP0uZip9/Tee++5i2Zfas2gfpo2duWVV7osKAWclN3z+uuvW2FhocuKOv/88+ssfB2a3bt328KFC13QSYW8NcPiqquusuHDh2fleC8tLXVZZgr4aepcQ1TfSe3qZphv7zcCT2miejH1vr4iOxe1obUTl9D2W2jjEwf2GZCTgaeBAwe26G4mAISoW7dudtNNN7msp++++85Ny0I4NPVL0yr12Lx5swtAqabRF198YWeddZYroK0AjLJvQqIAk7K9NJVOgSHVuho9erQLuumYz5a9e/e6uk4qB6IMs8aek2QCT77hLCpNCoY38HoR7SQptP0W2vjEgX0G5BRNK9Cyxo09IQSAXKPggwJOH3/8sbsYVt0ZhEd1vG6++WY3vUzZPz///LNbxU03ZRR8OvPMM+2MM85w0/V8pOLey5Ytcw8Fd1RLScezCoaPHDnSreiYTRUVFTZz5kz3d6dOndqo7CWdk2jb9NPH9xmBpzQpLD5RpFj1YjR1JyOvtVmfSdnLpAitnbiEtt9CG584sM+AnKI7vIcPH7ahQ4cmvSkAkFoKRqjIsTKfnnzySVZDC5hqEGmhDT127dpVFax55513XPBkyJAhLgilh6bmpZUCSzt37qza/o0bN7rV6LT91157rdt+rYwdlU8++cTViXr44YcbrOtU/ZxE9Z2k+vR/9SWnA08NDVQ2BjKONuJsx9HKWCpSXL2OjC5o9Xw2BdQO45PudkL6LIh1bAAkTncWdTfSxzuLABAXZb2oxpPqPSkAceedd3pXfwZNp2lnygjWQwv5aHqagjizZ8+2jz76yDp37uwCJPoOzfxMalregQMHXKBHAabMQ8/pO76oqMhl7WnKWxwZW8uXL3crB06ePLlJU+ZU30nBMdXXUh0uUdBK/cjpwJMGTkuY17aalC4Cs7G0eRxtxNmOo+XYJ8w6UaRY9WI0dSeKLIqA2mF80t1OSJ8FsY4NgFQEnlTsU3UuAAB169q1q5uK9dprr9mCBQtszJgxSW8SYqTzbdVA0kOrHK5ataoqwKOaUFoVTsFIrYSoAJQeOmaUFaX/V1lyLQ1WKvNHQRhdD2jqnDKyMtugIuGidhQE03Yq6KMMp2xPo6vPnj177O2333bTEpv6HlHgqV27djWynRSAUk0qTd1L+7lKpFPtsnqxl2AbcbZTRReycVzMBtIO45PudkL6LIh9bAAkQiewOlmlWC4ANI6mJ+lies6cOW5RBlYDzU0K7qj2lx6imkQ7duyokW30008/1ZgipuCPAlB6ZIJRDQWENBU+E2TSTz0UgMlQIEa1qbQSnY5FPRTsSiobr+K3uk7aPwrSNmU79P+q5qT2WfUs7Ezm0/79+1M9tVGo8QQAAFDLXUndRWSaHQA0npaFX79+fVW9J1+LTSN7ND2sV69e7qHV4TKBlOpBo+rBI632ppphx44dq/fvatqeAlQKJik7uXrQSj8VlFHbaTFnzhxXp+mRRx5p8vtC/19mf5yc8STl5eUEngAAAHyjkzwh8AQAjacsE9V7euaZZ1y9p7vuuot6T6j1OOnSpYt75IJff/3VTUG95pprmpUJqECcgmgnr2hXPfCUdukJAQIAAKSEipBm7pgCABpPwYRbbrmlqogykMtUX+qdd96xs846q9nT9xV4UiFxFXSvvmqkCo0LgScAAABPA09kOwFA86iujlY707LxmnoH5CJNj9O00w4dOthNN93U7Oy/TMaTalZVp+f0twk8AQAAeEbFOzXV7uQTPABA402cONFNK1JBZR8ujIFsn0vMnj3btm3b5qafVs9UaopM3asjR464jKeTKTO7tpW904YaT/Calsl89dVX7bHHHnMF5gDkqLISs/KVZgVF0a42GHE7paWltZ48qFBmNldujKsdX/eZ0uK1HHT1jKfQxoZ20t1O7J9tQER1fG677Tb7y1/+YtOnT7cHH3yQ83XkDE0zXbhwod1www0tyqBWtpPovESF1E+mYJSuidOOwBO8L9SmlYfStGIBgBgd3mU2f5rZ5tm/P9d3itnY6Wb5Xb1qRxe0mppQl5KSkqxc2MbVjs/7TNPsJJPxFNrY0E6624n1sw2IWOfOnW3atGn217/+1d544w1XbJzzdoRuyZIlbhW7sWPH2gUXXNCiv6WpqspqUtZgbRlPPXv2tB9//NHSjnc9vLZq1SobPHiwtWlDDBXISbow2zK35nP6fd493rXTUJp0ttKo42rH532maXa6WMoU7QxtbGgn3e3E+tkGxEBB/DvvvNMFbz/88EM3BQkI1dq1a+2tt96ykSNH2lVXXdXiv7dx48aqTKe6Ak/6/lFGVJoReIK3jh496t7Yw4YNS3pTACRBU1CUDVBZUfN5/a7ny0r9aicknu8zCosjUZ6/f4DaFBUVuSlHixYtsnnz5iW9OUAktm/f7srADBw4sEXFxDMqKircOYkKiCvRoraVdhV4krRPtyPwBG8p7VBvxuHDhye9KQCSoLon9b6+wq92QuLxPqOwOBLn8fsHqM/5559v48ePdyvdLV68OOnNAbJKU+FefvllV/NPU0qzMSNn69atbmU8TU9VtlNtgazevXvbjTfeaL169bI0Y34SvLVy5UoX9c1EeQHkmIIGgs4qxutTOyHxeJ/pjuHhw4fJeEJyPH7/AA258sor3Qpdb7/9tjuPHzp0aNKbBLSYVpx75ZVX7Pjx43bvvfc2ewW72gqLq0i/zktqKywuCkYpqJt2ZDzBW6tXr3bT7FqawgjAU4XFJ4rt5rWu+bx+1/PZWgEqrnZC4vE+yxQWJ/CExHj8/gEaovN2ZWcMGTLEXnvtNbfUPOAzBZtmzpxpO3fudIX0VSMyWzZs2GB9+vRxi2nVFXjyBYEneOnAgQNuKgT1nYAcpxWe+kyq+Zx+1/OetaPU7Ja8nrZ2fN1nCjx16dLF2rdvH2k7zfk7tJMb7cT62QYkQBkcKjauz1pNTcpqYX4g5un5H3zwgZuJo2NaQaJs2rBhgw0YMMAFnmorLO4TptrB29XshMATkOO0rPiEWSeK7aruiaagRJENEEM7WoZdy7HXdgKuC9psLdMeVzu+7jPd1Dg52ym0saGddLcT62cbkJB27dq57JDnnnvOBZ8efvhh9xzgk6+++sq+//57u/nmm7Ned3j//v22e/dul+mkrCoCT0BCgSfVdsrq3UUA/tIFWRwXZRG3k9UL1xS04+M+07SPMWPGRN5OXWiHdhL5bAMSUFhY6OrhPP/88/b666+7QJSyoQAf/PTTT/bZZ5+5umXnnXde1v/+hg0b3M9MvSim2gEJpDQq8ES2EwAgmw4dOmQHDx70/q4iAPhCK3FpBbA1a9bYe++9587zgbTTtei7775ro0ePdis1RrWCe0FBgStcrlXtNDXVZwSe4B2tOKTVMAg8AQCySSntQuAJAOKjle00VUkZJJ9//nnSmwPUa+vWra4wvo7b66+/PrKFrjZv3mz9+/d35yYqWK7gk8+YagcvI8x64w0ePDjpTQEABHZjI4R0dgARKCsxK1/pbb2t0tLSeOqTNdOoUaPcjeVPP/3UXWQ3uDy85+MRQh/SfkxF0Y+ysjJXk0w3qO64447IpoZWVlbali1b7MILL3QlABpzQyzt40HgCd5R2qEKv1KAEACQTbqrqFoK1Ve0A5DjDu8ymz/NbPPs35/rO+XECoMqAu8BXZAWFxfX+bqK5qfhwnTcuHEu+PT++++7+k9FRUVBjkcIffDlmMpmPzQdX0EnJUCoHlmU16Ll5eVuFXetkrds2TIbNGiQ9+Phd74WcpIKrSntEACAbMqsHgMAVRQg2DK35nP6fd495ovasiCa8npcNGXpuuuucxfIM2bMcFONQhyPEPrgyzGVrX5UVFS4AvjKeFJB/KgXuNJ0vkwNNJ2bNJTx5MN4EHiCVxT51ZuPwBMAINsIPAE4ZSqUslIqK2o+r9/1fFlpUlsWLGWT3HbbbdajRw975ZVXbM+ePWGNRwh9yDGa9qZC4uvWrXOF8LWyetS2bNli+fn51rZtWzt69GgQtScJPMErGzdudD8HDBiQ9KYAAAJD4AlADaq/U+/rK+LakpyiC+577rnH2rRpYy+99NLv2RohjEcIfcixoNPHH39sP//8s02dOtWGDBkSS7tbt2613r17uwwrUd0z3xF4gnfT7Dp06OD9cpIAgHRRGr1qixB4AlClYHgDr9dSgwhZoWXk77vvPreU/AsvvOA+n4MYjxD6kEPmzZtn3377rV177bV2zjnnxNbu1t8CTwcPHnS/6/rXdwSe4F3Gk6bZRbVsJQAgN+miRnc2CTwBqFJYfKLoc95JK1fpdz3v4UpkPunevbs9/PDD7saAgk+7K3r6Px4cU15ZsmSJ3XDDDXbxxRfH1uaxY8dsx44dNQJPISx6QuAJ3tAFQSbwBABAtqfZCYEnADVopbE+k2o+p9/1vCcaKoQcdaHkltBnsoJPuunsgk9n/8n78eCYSo+GtlPF7i+44AKL07Zt29x1r1a0U+CpdevWbtqp7+NRfw+AFNm1a5dbxpLAEwAgiu8YFbUNoY4CgCzS8vYTZp0o+qz6O5oK5VlWilaJ03Lqta1spQvSpJdZb4g+lx966CH729/+Zs+/9JY9+OBL1uOC3d6OB8dUevuh7LrPP//cVq5c6TKd9Ijb1mor2q1YscJlOzU028eH8SDwBK/qOwmBJwBAFBlPurhR8AkATqHAgGfBgerScOHZEoWFhVXBJ2U+PfDAA9ar37XmNY6pVPVDQaeZM2e6RId/+qd/srPPPjuR7dmyZYtbxU5F9rUtjZ1ml/bx4OwK3tA0O831DmGOKwAgfYGnEJYrBoCQC44r+KQMDgWfNm/enPQmIRCqq/T6669baWmp3XnnnYkFnTIZT5pmJ5pqF8q1L4EneIP6TgCAKANPrJgKAOmm1b2U7aTaT8p+0vUB0BJHjx61V1991VatWmV33323nXHGGYltS2VlZdWKdkLgCUggCq20QwJPAIAo7Nmzh8LiAOABXYjff//91rNnTxd8WrduXdKbBE8dOXLEXnnlFXcMTZs2zYqKihLdnrKyMje9LhN40n+fdtppFgICT/CCUmmPHz9uAwYMSHpTAACBUV2Hw4cPuzvpAID008X4vffea3379rWXXnrJ1qxZk/QmwTP63n/55Zdt06ZNdt9999nQoUOT3iRTooUw1Q5IiNJotZRkJvoLAEC26I6ihHJyBwC5oF27di74NHDgQBdA0EpkQGO/9//+97+7aW3Knhs0aJClwdatW11QVcX0hcATkEDgSXc0FHwCACCbdGInoZzcAUCuaNu2rd1zzz0uW0VTpn766aekNwkeTK1//vnnbdeuXa5eWJpm1Gzfvt169epleXl5rt6Tzk+YagfEaMOGDdR3AgBEmvEUyskdAOSSNm3a2F133WWjRo2yt99+2z777DN30Q7Udk35l7/8xdUPfuSRR6xfv36WJjt37nSruGeKnqvUTCg3xdrE0kpZiVn5SrOCIrPC071sQ0sr7tu375TntZzn6adH0KdA+pONdvbv3+8i0/VFo0Mbn9DaiWN8fDqmEYFA3itxtRPrcezBPmtSxlMgY0M7LRTIcQCEQrMibrrpJnfR/sknn7hslptvvtkFpQBZsmSJC0xqFo0ClR07drQ0qaysdIGnESNGBJmNHe078fAus/nTzDbP/v25vlPMxk43y+/qTRv68i8uLq7z9ZKSkuydBATUn2y1k1kmta6Mp9DGJ7R24hgf345pZFFA75W42ontOPZonzUq4ymgsaGdFgjoOABCo+lJ48aNs27dutlbb73lblzffffdqQswIP6Aztdff22ffvqpnXPOOakNSO7fv9+tspfJeAot8BTtVDt9MW+ZW/M5/T7vHq/aqO2OU1Nez9X+ZKsdpURqpaEuXbpE2k5qjunA2oljfHw7ppFFAb1X4montuPYo32mkzvdLVetkCjbybXPstDaCe04AEJ19tln20MPPWS7d+9206pUNwe5u2rtu+++64JO48ePt1tvvTWVQSdRtpMocCoEnpqSgqy7QZUVNZ/X73q+rNSPNuIUWn+yRBlPmmanuxg5MT6htQNEJbT3SkjvSc/2WaZ4Z53fMyGNDZqP4wDwhmZKPPbYY+6GwnPPPWerVq1KepMQM323v/TSS7Z48WK75ZZbbMKECclfT9ZD00OFwFNTad57va+v8KONOIXWnyylRirwlIrC4nGNT2jtAFEJ7b0S0nvSs32mqXb1ntiFNDZoPo4DwCuaLaEC0rqB/fLLL9u3335L0fEcsXXrVpftpp/333+/Kzyfdjt37rTOnTtXZWRlygC0a9fOQhBd4KlgeAOvF/nRRpxC60+W3oCHDx9OR+AprvEJrR0gKqG9V0J6T3q2z3RyV299p5DGBs3HcQB4R5/t06ZNs4svvthmz55tM2bMqLqgR3gUWPzhhx9c0EnZbsp6Gzx4sPlg165dVfWdRMdpfn6+tWoVbXWkuETXi8LiE8UW81rXfF6/6/lsrAASRxtxCq0/WaD6TpKKwFNc4xNaO0BUQnuvhPSe9GyfKZ293oynkMYGzcdxAHhJF+5TpkyxO++80025e/bZZ23z5s1JbxayTIW533nnHVfTSRlOjz76aNW0NV8SLrpXCzxJmqcGNlW04TOt8NFnUs3n9Lue96gNLV3bktdztT/ZaEfT7Hr06FHvnejQxie0duIYH5+OaWRZQO+VuNqJ7Tj2aJ81GHjKUju59lkWWjuhHQdArjnrrLPsySefdNcVqvu0cOFCpt4FQgXkleW0dOlSmzp1qt144431LxiSMpWVlS7jyadAWVPlVTbi3VZWVubmG+7du9cKCwub3oqKLWreu1KQo7obFHEbWtq2tlVE9OUfyZK2gfSnpe3oA0SRXxWEi7KdVB7TAbUTx/j4ckxn7XM1MF58zwTUTqyfmR7ss6eeesqGDBli1157baTthPhZlmvt+HQc8D1zKvYJ5NixY27anQJPI0eOtBtuuMFNaYKfVDz8vffec+9tZbX17NnTfLN37177f//v/7lpoZnP+Pnz59uXX35p/+N//A8L4XM1nrUE9YUcdfpxxG1EctKSA/1paTuK/BYXF0feTiqP6YDaiWN8fDmmEZFA3itxtRPrcezBPsusahd1O7n4WRZaO6EdB0AuUvHm66+/3tX+UcBC0+5uv/126927d9KbhiY4evSoCyAuWrTITa3TmPoaQNy5c6f7efJUu5DEE3gCmnkhoEfIb0AAQPIaXNUOABCcc845x/r06eMKjv/5z3+2K664wsaOHRtMMeeQrVu3ztVy2rNnj8tYO//8872uh7Rz50533GklxlAReELqI78hz3UFACSvoqKiavliAEDuUC3Zxx9/3D7//HP77LPP7Ndff7Wbb76Z7KcUZzl98skntmDBAhswYIDdfffdbgxDuO7t2rVr0EFPzrKQWppmJwSeAAAAAERBNx4mTZrkio9rVTStejd+/HgbN26ctW590iqWSMzatWvd+Kj+3eTJk+2SSy4JJlCza9eu4Gf5EHhCqt+AHTt2tHbt2iW9KQAAAAAC1r9/f3viiSdcQecvvvjCli1b5rKfNB0PyTly5IjLcvrHP/5hAwcOtHvvvTe4IM3OnTsbVdfYZwSekFqhLykJAPDD8ePHXe0In+tHAAAal/00ceJEO/PMM112jWo/KfNJj7Zt2ya9eTln1apV9v7777sspylTptjFF18cTJZTRmVlpatVFfp1L4EnpDrw5ONymACAsHz44Ycu+HTTTTclvSkAgBj069evKvvp66+/tp9++slNxxsxYgQ3IWLKAPr444+tpKTErT543333BRuYKS8vd+cYhYWFFjICT0j1B84ZZ5yR9GYAAHLcypUr3d1vAEDuUH2nCRMm2KhRo2zOnDn2xhtvuOleyrzRtDxkn1Y0V7BP+7lTp0522223BR/sKysrcz87d+5sISPwhNR+6Gh561Aj2wAAPxw4cMClwOvuNwAg96iekFZPW716tc2ePdv+8pe/uGDUVVddFXyWSlyU8bNw4UK3uqBWmr3yyittzJgxOTG9sey3wFPoxxKBJ6Q220kIPAEAkrRp0yb3k7vbAJDbhg4d6qbf/fDDD/bZZ5/Z0qVLbezYsXbZZZdZfn5+0pvnbX2jFStWuIyy7du323nnnedqbCnbKVeUlZW57Lr27dtbyAg8IbX1nYTAEwAg6cDTaaedZl27dk16UwAACVNh6wsuuMDOOecc++qrr1z9pwULFrii15dccol16NAh6U30JuCkVQO1//Q9qzpOCur17dvXck1ZWZnLdgp5OqEQeEJqA08dO3a0du3aJb0pAIAcphNiTbML/YQQANB4ukZRsXEFnObPn2/ffPONeygodemllwY/baq5NI1u8eLFNm/ePNuxY4cNGTLEFQ4fNmxYzn7Plv0WeAodgSekNvCk+dQAACRp48aNLvUfAICTKWBwzTXX2Pjx413mk4pi63Huuee6aXhcz5xw9OhR+/77712QToEWLSB1880324ABAyzXlZWVBV9YXAg8IbWBp549eya9GQCAHLZv3z63zDGFxQEA9dEUO62Ap3pPKpKt7CfVgtKKqKNHj7aioiI3TS/XKKvpxx9/dPtCi0dpiuK4ceOsV69e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Xup9fUVcW4JQxiOEPgj9iCzhIlQEnpAqdaUcKvBExhOANAt1ZRKtxFZUVGRvvvmmvfHGG8GdJO3YscOef/55+/LLL+3yyy+3a6+91j1/5MiRZh0DCs6p1sHjjz/u/lvBJ9UoVBZUSJYuXWpPPfWUbdy40e6++24744wzLDQav1Df12ikguENvF4U15YglPEIoQ9CP1I11c4HBJ6QKgouMdUOgG908f0v//IvtmnTJguN7sjddttt7rFq1SqX/fT99997H0hRYElZTs8884wdPnzYHn30UZswYYJb7U7j+a//+q9NHs+ZM2fajBkzXMCiT58+Lvh0ySWX2Ny5c11wK4SsoF27drk+6jFo0CD7T//pPwUbdMqMJ3JYYfGJIsN5rWs+r9/1PPW34hXCeITQB6EfWZtqVxloNvnJCDzBm+LiBJ4ApJWCDD179nQZQc3JlPHBOeec44IMQ4cOtffee88FbLSKim8nTAqYLVy40P793//drZiqwNCTTz5p/fv3d68rCKVx7N27t3vInDlz7IsvvrA9e/bU+7dHjx5tv/76qy1atMj93qZNG7v66qvt4YcfdtP5FHzSFLydO3eab3RTaNasWfYf//EfLoB2yy232J133mkdO3a0ECm4qqwujSlynFa26jOp5nP6Xc97QqsutuT1VAlgPILog9CPFl/3VlZW1lpmJsT3OKvaIVXqSjnUc9R4ApBWypJRRpCCMR999JHdfPPNFiKduKifY8aMccGYV155xQWiFFzp27evpZlO7rScsLKPNL1u1KhRLsOpS5cuNf6dxm///v123333uXE9duyYffPNN5afn2/bt2+322+/vc42zjzzTLvwwgtdXazBgwe7YKQoM+iJJ56wn3/+2T799FOXNXbBBRfYFVdckfrAjaaaqe7VV1995fbhlVde6ca/bdu2FiqNs4JsGqOzzjor6c1B0rScula2UpFh1XvR1Btfsjmqrcaoz7/aForQ53rsqzXm+HgE0QehHy2+7s3Wda4P73ECT0gVvel0t1l3hnXCL5lIMBlPANKse/furj7Qu+++62oijRgxwkKl7KAHH3ywKpDz7LPP1hnISQNNnVOgTCv0KVB266231hooUwH1n376yaZOnWrdunVzz23YsMF9D7Vr165RbU2ePNm1o6ypxx57zGU9SV5enp177rnuuFiwYIEL5KitcePG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/2EPPfSQDR48OOnNAbKCz9VTsU8A+O7111+38vJye+SRR8ynz1VqPHmuEXHDoPqajf5m6+/4Ipf6CgDNoRpPUu/KdgAAACmYbrdx40Y7cuSI+YTAk8f27Nlj//t//2/79NNPXVHUkAMnP/74o/3f//t/bdmyZS3+e4sWLbJ/+7d/y8rfSjMVyf3www/tn//5n+uvWwIAOS5T44nAEwAASHvg6fjx47Zu3TrzCYEnjymlTUspzps3z9Wm2LZtm4Vm//79Lp3wnXfesTPPPNOGD29gieFGOPvss91Kdq+99pr7uyEGZVSv5JlnnrEffvjBrr766qqLKgDAqVq3bm3t2rWzAwcOJL0pAAAAderevbsVFBTY6tWrzSc5EXj65Zdf7JNPPgluylFeXp5dccUV9uijj7rslmeffda+/vprL5dXPJnGSuP2pz/9yUVz77zzTps6darl5+e73998880m/T1lhCmAtXXrVuvQoYPdfffddtNNN9nSpUvt6aeftpKSkiCOD6Vc6lh//vnn3UXUk08+aRdffLGFRmP1/vvv24oVK5LeFACB6NKli+3evTvpzQAAAKg3BqCspzVr1phP2lgOaNWqlQvI9OjRw84991wLTb9+/eyJJ55wU+700FSyiRMn2jnnnOMOTN+sXbvW5syZ4+auKsvp+uuvd1Fd0d3omTNnWq9evZp8DOiCQv+v9lXbtm1t9OjR7k377rvv2vTp091/Kzuob9++5hulWyq76fPPP3dTRRSQvPzyy12/QzR//nx3nI8cOTLpTQEQiK5du7op7AAAAGk2ZMgQl6Sh675Mncq0C/OqtJapVQo4qd7Nrl27LEQKpEyZMsX+83/+z24paGUE/eUvf/EqEqpVhV599VV74YUXXCDlgQcesLvuuqsq6KQsFwWJlNGlbKWmUADutttucxcVs2fPrnGH+/7773cZUFoeWVljb731ljcXH9ony5cvt6eeesplAA0bNsz+y3/5Ly7wFGrQadOmTS7Aetlll7H6FICsBp5CPUcAAADhGDp0qLsOVMKGL3Ii40muvfbaqilaDz/8sKvnECJldSmIooPw448/thdffNGKi4tt0qRJ1rNnT0sjLQepTJ3vv//eLcF466231pqtpQwXBVkUjGrOErjaN9dcc40L0KhW1FlnneWeVztnnHGGnX766W4btC1LliyxSy65xGUN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",
+ "text/plain": [
+ "
"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "def plot_node_neighbourhood(\n",
+ " grid,\n",
+ " G: nx.Graph,\n",
+ " center: Hashable,\n",
+ " hops: int,\n",
+ " ax: plt.Axes,\n",
+ ") -> None:\n",
+ " rows, cols = len(grid), len(grid[0])\n",
+ " r, c = G.nodes[center][\"coords\"]\n",
+ "\n",
+ " # make a rectangular subgrid\n",
+ " r0, r1 = max(0, r - hops), min(rows - 1, r + hops)\n",
+ " c0, c1 = max(0, c - hops), min(cols - 1, c + hops)\n",
+ " rect_nodes = {grid[i][j] for i in range(r0, r1 + 1) for j in range(c0, c1 + 1)}\n",
+ "\n",
+ " # collect the relevant edges by length\n",
+ " edges_by_k = defaultdict(list)\n",
+ " for v, ed in G[center].items():\n",
+ " k = int(ed.get(\"skip\", 1))\n",
+ " edges_by_k[k].append((center, v))\n",
+ "\n",
+ " # draw edges as arcs\n",
+ " max_k = max(edges_by_k.keys(), default=1)\n",
+ " curve_scale = 0.8\n",
+ " edge_width = 1.0\n",
+ " alpha = 1.0\n",
+ "\n",
+ " def rad_for_edge(u, v, k):\n",
+ " r1, c1 = G.nodes[u][\"coords\"]\n",
+ " r2, c2 = G.nodes[v][\"coords\"]\n",
+ " base = curve_scale * (k / max_k)\n",
+ " # choose bend direction based on quadrant:\n",
+ " if c1 == c2:\n",
+ " sign = +1.0 if r2 < r1 else -1.0 # up vs down\n",
+ " else: # horizontal edge\n",
+ " sign = +1.0 if c2 > c1 else -1.0 # right vs left\n",
+ " return sign * base\n",
+ "\n",
+ " # positions for plotting\n",
+ " pos = {n: (G.nodes[n][\"coords\"][1], G.nodes[n][\"coords\"][0]) for n in rect_nodes | {center}}\n",
+ "\n",
+ " for i, k in enumerate(sorted(edges_by_k)):\n",
+ " for u, v in edges_by_k[k]:\n",
+ " nx.draw_networkx_edges(\n",
+ " G,\n",
+ " pos,\n",
+ " edgelist=[(u, v)],\n",
+ " ax=ax,\n",
+ " edge_color=\"gray\",\n",
+ " width=edge_width,\n",
+ " alpha=alpha,\n",
+ " arrows=True,\n",
+ " arrowstyle=\"-\",\n",
+ " connectionstyle=f\"arc3,rad={rad_for_edge(u, v, k)}\",\n",
+ " )\n",
+ "\n",
+ " # draw nodes\n",
+ " cont_nodes = [n for n in rect_nodes if n.__class__ == ContinuousNode]\n",
+ " spin_nodes = [n for n in rect_nodes if n.__class__ == SpinNode]\n",
+ "\n",
+ " node_size = 20.0\n",
+ "\n",
+ " nx.draw_networkx_nodes(G, pos, nodelist=cont_nodes, node_color=\"black\", node_shape=\"s\", node_size=node_size, ax=ax)\n",
+ " nx.draw_networkx_nodes(G, pos, nodelist=spin_nodes, node_color=\"orange\", node_shape=\"o\", node_size=node_size, ax=ax)\n",
+ "\n",
+ "\n",
+ "# pick a few nodes in the grid to inspect\n",
+ "centers = [grid[0][7], grid[10][10], grid[-1][-1]]\n",
+ "\n",
+ "fig, axs = plt.subplots(nrows=1, ncols=len(centers), figsize=(len(centers) * 5, 5))\n",
+ "\n",
+ "\n",
+ "for ax, center in zip(axs, centers):\n",
+ " plot_node_neighbourhood(grid, graph, center, max(edge_lengths) + 1, ax)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "62f1fb392c555b0",
+ "metadata": {
+ "collapsed": false,
+ "id": "62f1fb392c555b0"
+ },
+ "source": [
+ "This problem is clearly much more heterogeneous than what we were looking at before. Every node has a unique local neighbourhood, and is connected to a potentially different number of spin and continuous nodes. This makes working with this graph on an accelerator like a GPU tricky. As we will now see, THRML was specifically designed to handle this heterogeneity.\n",
+ "\n",
+ "With our graph in hand, let's set up our sampling program. We can re-use a lot of the work that we did in the simpler example. First, let's sort the nodes and edges by type."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "79343d1ad7e971c4",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:21.228754630Z",
+ "start_time": "2025-08-28T16:29:21.215625717Z"
+ },
+ "id": "79343d1ad7e971c4"
+ },
+ "outputs": [],
+ "source": [
+ "# collect the different types of nodes\n",
+ "spin_nodes = []\n",
+ "cont_nodes = []\n",
+ "for node in graph.nodes:\n",
+ " if isinstance(node, SpinNode):\n",
+ " spin_nodes.append(node)\n",
+ " else:\n",
+ " cont_nodes.append(node)\n",
+ "\n",
+ "\n",
+ "# spin-spin interactions\n",
+ "ss_edges = [[], []]\n",
+ "\n",
+ "# continuous-continuous interactions\n",
+ "cc_edges = [[], []]\n",
+ "\n",
+ "# spin-continuous interactions\n",
+ "sc_edges = [[], []]\n",
+ "\n",
+ "for edge in zip(*edges):\n",
+ " if isinstance(edge[0], SpinNode) and isinstance(edge[1], SpinNode):\n",
+ " ss_edges[0].append(edge[0])\n",
+ " ss_edges[1].append(edge[1])\n",
+ " elif isinstance(edge[0], ContinuousNode) and isinstance(edge[1], ContinuousNode):\n",
+ " cc_edges[0].append(edge[0])\n",
+ " cc_edges[1].append(edge[1])\n",
+ " elif isinstance(edge[0], SpinNode):\n",
+ " sc_edges[0].append(edge[0])\n",
+ " sc_edges[1].append(edge[1])\n",
+ " else:\n",
+ " sc_edges[1].append(edge[0])\n",
+ " sc_edges[0].append(edge[1])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "da08c4f94830376a",
+ "metadata": {
+ "collapsed": false,
+ "id": "da08c4f94830376a"
+ },
+ "source": [
+ "Now we can set up some interactions. For some of the factors, we will re-use our code from the first part of this example"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "40cc0d7ed96795f9",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:21.683064393Z",
+ "start_time": "2025-08-28T16:29:21.217766402Z"
+ },
+ "id": "40cc0d7ed96795f9"
+ },
+ "outputs": [],
+ "source": [
+ "# we will just randomize the weights\n",
+ "\n",
+ "key, subkey = jax.random.split(key, 2)\n",
+ "cont_quad = QuadraticFactor(jax.random.uniform(subkey, (len(cont_nodes),), minval=2, maxval=3), Block(cont_nodes))\n",
+ "\n",
+ "key, subkey = jax.random.split(key, 2)\n",
+ "cont_linear = LinearFactor(jax.random.normal(subkey, (len(cont_nodes),)), Block(cont_nodes))\n",
+ "\n",
+ "key, subkey = jax.random.split(key, 2)\n",
+ "cont_coupling = CouplingFactor(\n",
+ " jax.random.uniform(subkey, (len(cc_edges[0]),), minval=-1 / 10, maxval=1 / 10),\n",
+ " (Block(cc_edges[0]), Block(cc_edges[1])),\n",
+ ")\n",
+ "\n",
+ "key, subkey = jax.random.split(key, 2)\n",
+ "spin_con_coupling = CouplingFactor(\n",
+ " jax.random.normal(subkey, (len(sc_edges[0]),)), (Block(sc_edges[0]), Block(sc_edges[1]))\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a27cf9451faf6c44",
+ "metadata": {
+ "collapsed": false,
+ "id": "a27cf9451faf6c44"
+ },
+ "source": [
+ "For the factors that involve only spin variables, we will use some built in functionality from THRML. THRML implements sampling functionality for arbitrary discrete-variable EBMs in `thrml.models.discrete_ebm` that we can apply to our problem. First, the spin factors,"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8b829a81609def9e",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:22.007023492Z",
+ "start_time": "2025-08-28T16:29:21.683300609Z"
+ },
+ "id": "8b829a81609def9e"
+ },
+ "outputs": [],
+ "source": [
+ "key, subkey = jax.random.split(key, 2)\n",
+ "spin_linear = SpinEBMFactor([Block(spin_nodes)], jax.random.normal(subkey, (len(spin_nodes),)))\n",
+ "\n",
+ "key, subkey = jax.random.split(key, 2)\n",
+ "spin_coupling = SpinEBMFactor([Block(x) for x in ss_edges], jax.random.normal(subkey, (len(ss_edges[0]),)))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7b426e194f944c72",
+ "metadata": {
+ "collapsed": false,
+ "id": "7b426e194f944c72"
+ },
+ "source": [
+ "The Gaussian sampler we wrote for the first part will work our new problem as it is because it won't be seeing any new types of interactions. The Binary sampler built into THRML will have to be extended to handle our `LinearInteraction`. Luckily, it was designed with this kind of modification in mind."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d9e776179348c681",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:22.019981340Z",
+ "start_time": "2025-08-28T16:29:22.008840315Z"
+ },
+ "id": "d9e776179348c681"
+ },
+ "outputs": [],
+ "source": [
+ "class ExtendedSpinGibbsSampler(SpinGibbsConditional):\n",
+ " def compute_parameters(\n",
+ " self,\n",
+ " key: Key,\n",
+ " interactions: list[PyTree],\n",
+ " active_flags: list[Array],\n",
+ " states: list[list[_State]],\n",
+ " sampler_state: _SamplerState,\n",
+ " output_sd: PyTree[jax.ShapeDtypeStruct],\n",
+ " ) -> PyTree:\n",
+ " field = jnp.zeros(output_sd.shape, dtype=float)\n",
+ "\n",
+ " unprocessed_interactions = []\n",
+ " unprocessed_active = []\n",
+ " unprocessed_states = []\n",
+ "\n",
+ " for interaction, active, state in zip(interactions, active_flags, states):\n",
+ " # if its our new interaction, handle it\n",
+ " if isinstance(interaction, LinearInteraction):\n",
+ " state_prod = jnp.prod(jnp.stack(state, -1), -1)\n",
+ " field -= jnp.sum(interaction.weights * active * state_prod, axis=-1)\n",
+ "\n",
+ " # if we haven't seen it, remember it\n",
+ " else:\n",
+ " unprocessed_interactions.append(interaction)\n",
+ " unprocessed_active.append(active)\n",
+ " unprocessed_states.append(state)\n",
+ "\n",
+ " # make the parent class deal with THRML-native interactions\n",
+ " field -= super().compute_parameters(\n",
+ " key, unprocessed_interactions, unprocessed_active, unprocessed_states, sampler_state, output_sd\n",
+ " )[0]\n",
+ "\n",
+ " return field, sampler_state"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9c9c99f1a668bcc7",
+ "metadata": {
+ "collapsed": false,
+ "id": "9c9c99f1a668bcc7"
+ },
+ "source": [
+ "This is all the work we need to do to sample from our new graph using THRML! All that is left is to set up our Block spec and run some sampling."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "36470d03be19f2f2",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:22.629298283Z",
+ "start_time": "2025-08-28T16:29:22.011174500Z"
+ },
+ "id": "36470d03be19f2f2"
+ },
+ "outputs": [],
+ "source": [
+ "# tell THRML the shape and datatype of our new node\n",
+ "new_sd = {SpinNode: jax.ShapeDtypeStruct(shape=(), dtype=jnp.bool)}\n",
+ "\n",
+ "# Our new graph is still two-colorable, however within each color there are two different types of node\n",
+ "# this means that we can't make a single block to represent each color because all of the nodes within a block have to be of the same type\n",
+ "# however, we might still want to ensure that the two blocks that represent each color group are sampled at the same \"algorithmic\" time\n",
+ "# i.e even though we can't sample these blocks directly in parallel because they use different update rules, we want to make sure that they\n",
+ "# receive the same state information\n",
+ "# we can make this happen in THRML by passing in a list of tuples of blocks to BlockGibbsSpec instead of a list of Blocks\n",
+ "# the blocks in each tuple will be sampled at the same algorithmic time\n",
+ "blocks = [\n",
+ " (Block(coloring[0][SpinNode]), Block(coloring[0][ContinuousNode])),\n",
+ " (Block(coloring[1][SpinNode]), Block(coloring[1][ContinuousNode])),\n",
+ "]\n",
+ "\n",
+ "block_spec = BlockGibbsSpec(blocks, [], node_shape_dtypes | new_sd)\n",
+ "\n",
+ "# now we can assemble our program\n",
+ "\n",
+ "# first, choose the right update rule for each block in the spec\n",
+ "ber_sampler = ExtendedSpinGibbsSampler()\n",
+ "samplers = []\n",
+ "for block in block_spec.free_blocks:\n",
+ " if isinstance(block.nodes[0], SpinNode):\n",
+ " samplers.append(ber_sampler)\n",
+ " else:\n",
+ " samplers.append(sampler)\n",
+ "\n",
+ "# collect all of our factors\n",
+ "factors = [cont_quad, cont_linear, cont_coupling, spin_con_coupling, spin_linear, spin_coupling]\n",
+ "\n",
+ "program = FactorSamplingProgram(block_spec, samplers, factors, [])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "db464513aa82d8ce",
+ "metadata": {
+ "collapsed": false,
+ "id": "db464513aa82d8ce"
+ },
+ "source": [
+ "Our program is doing a lot of work to pad out the interaction structure and make our sampling program GPU-compatible:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "21906c92ceb15017",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:22.671053983Z",
+ "start_time": "2025-08-28T16:29:22.632961366Z"
+ },
+ "id": "21906c92ceb15017",
+ "outputId": "d74fe843-bd29-474b-c907-c9683d222b4c"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Array([ True, True, True, True, True, False, False, False, False,\n",
+ " False], dtype=bool)"
+ ]
+ },
+ "execution_count": 25,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# let's look at an example of the padding\n",
+ "program.per_block_interaction_active[0][0][0]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c36435a651622399",
+ "metadata": {
+ "collapsed": false,
+ "id": "c36435a651622399"
+ },
+ "source": [
+ "Now we are ready to sample. In this case, we will simply observe the state of our nodes directly"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "aeb00d18260080cb",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:26.417465165Z",
+ "start_time": "2025-08-28T16:29:22.664903387Z"
+ },
+ "id": "aeb00d18260080cb"
+ },
+ "outputs": [],
+ "source": [
+ "batch_size = 50\n",
+ "\n",
+ "schedule = SamplingSchedule(\n",
+ " # how many iterations to do before drawing the first sample\n",
+ " n_warmup=100,\n",
+ " # how many samples to draw in total\n",
+ " n_samples=300,\n",
+ " # how many steps to take between samples\n",
+ " steps_per_sample=15,\n",
+ ")\n",
+ "\n",
+ "\n",
+ "# construct the initial state of the iterative sampling algorithm\n",
+ "init_state = []\n",
+ "for block in block_spec.free_blocks:\n",
+ " init_shape = (\n",
+ " batch_size,\n",
+ " len(block.nodes),\n",
+ " )\n",
+ " key, subkey = jax.random.split(key, 2)\n",
+ " if isinstance(block.nodes[0], ContinuousNode):\n",
+ " init_state.append(0.1 * jax.random.normal(subkey, init_shape))\n",
+ " else:\n",
+ " init_state.append(jax.random.bernoulli(subkey, 0.5, init_shape))\n",
+ "\n",
+ "key, subkey = jax.random.split(key, 2)\n",
+ "keys = jax.random.split(subkey, batch_size)\n",
+ "\n",
+ "samples = jax.vmap(lambda k, i: sample_states(k, program, schedule, i, [], [Block(spin_nodes), Block(cont_nodes)]))(\n",
+ " keys, init_state\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e325d34504bc254b",
+ "metadata": {
+ "collapsed": false,
+ "id": "e325d34504bc254b"
+ },
+ "source": [
+ "Let's visualize our samples. Our data is very high-dimensional, but we can use a PCA to try and get some idea of the structure of the distribution."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "af9c16dc493b03f",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:26.742970708Z",
+ "start_time": "2025-08-28T16:29:26.421361750Z"
+ },
+ "id": "af9c16dc493b03f"
+ },
+ "outputs": [],
+ "source": [
+ "all_samples = jnp.concatenate(samples, axis=-1)\n",
+ "pca = PCA(n_components=3)\n",
+ "preproc_data = StandardScaler().fit_transform(jnp.reshape(all_samples, (-1, all_samples.shape[-1])))\n",
+ "transformed_data = pca.fit_transform(preproc_data)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "704fc2b7ab8f46f4",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-08-28T16:29:27.018178933Z",
+ "start_time": "2025-08-28T16:29:26.750400616Z"
+ },
+ "id": "704fc2b7ab8f46f4",
+ "outputId": "781c8c78-d9e9-4923-e2e5-ca383fc11203"
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYUAAAGICAYAAACuvfyWAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy80BEi2AAAACXBIWXMAAA9hAAAPYQGoP6dpAAC7V0lEQVR4nOy9d3yj1Zn+fT1FvTe36WNPsw1Dh6EOMGFgSSEBNtnsJpBkN/tjh7ybkL4hpBey2SSbLAkhDbIJCWE3sJRAqGECAwxtxp7mXsYeV1lWl5523j/k81iSJblJVrG+n8+AZcmPjmzpvs+5y3UzhBCCKlWqVKlSBQBb7AVUqVKlSpXSoeoUqlSpUqWKStUpVKlSpUoVlapTqFKlSpUqKlWnUKVKlSpVVKpOoUqVKlWqqFSdQpUqVapUUak6hSpVqlSpolJ1ClWqVKlSRaXqFKpUqVKlikrVKVSpUgF861vfwrnnnguLxYKamhpcd9116OjoSHlMLBbDvn374HK5YDabcf3112NsbKxIK65SqlSdQpUqFcALL7yAffv24ZVXXsHTTz8NURRx1VVXIRwOq4/5xCc+gUcffRQPPvggXnjhBZw6dQrvec97irjqKqUIUxXEq1Kl8piYmEBNTQ1eeOEFXHrppfD7/fB4PLj//vtxww03AABOnDiBHTt24OWXX8YFF1xQ5BVXKRWqJ4UqVSoQv98PAHA6nQCAN954A6IoYs+ePepjtm/fjvXr1+Pll18uyhqrlCZVp1ClSoWhKAo+/vGP46KLLkJraysAYHR0FFqtFna7PeWxtbW1GB0dLcIqq5QqfLEXUKVKlfyyb98+HDlyBC+++GKxl1KlDKmeFKpUqSBuvfVWPPbYY3j++eexdu1a9ft1dXUQBAHT09Mpjx8bG0NdXd0Kr7JKKVN1ClWqVACEENx666146KGH8Nxzz2HTpk0p95999tnQaDR49tln1e91dHRgcHAQu3btWunlVilhqk6hyqpg//79eMc73oGGhgYwDIOHH3445X5CCO644w7U19fDYDBgz5496OrqKs5il8C+ffvwm9/8Bvfffz8sFgtGR0cxOjqKaDQKALDZbPjIRz6C2267Dc8//zzeeOMNfOhDH8KuXbuqlUdVUqg6hSqrgnA4jJ07d+Kuu+7KeP93vvMd/PCHP8Tdd9+NV199FSaTCXv37kUsFlvhlS6Nn/zkJ/D7/di9ezfq6+vVfw888ID6mO9///t4+9vfjuuvvx6XXnop6urq8Mc//rGIq65SilT7FKqsOhiGwUMPPYTrrrsOQOKU0NDQgE9+8pP41Kc+BSBR0llbW4t7770X73vf+4q42ipVVpbqSaHKqqevrw+jo6MpNfw2mw3nn39+tYa/yqqj6hSqrHponX5tbW3K96s1/FVWI1WnUKVKlSpVVKpOocqqh9bppyuGVmv4q6xGqk6hyqpn06ZNqKurS6nhDwQCePXVV6s1/CVAtRZmZanKXFTJyV133YV///d/x+joKHbu3Ikf/ehHOO+884q9rEUTCoXQ3d2t3u7r68OhQ4fgdDqxfv16fPzjH8fXv/51bNmyBZs2bcIXv/hFNDQ0qBVKVQoLIQQ+nw8dHR3o6upCV1cXenp68MYbb2Bqagrj4+NgGKbYy1wVVEtSq2TlgQcewAc/+EHcfffdOP/88/GDH/wADz74IDo6OlBTU1Ps5S2Kv/zlL7j88svnfP+mm27CvffeC0IIvvSlL+Gee+7B9PQ0Lr74Yvz4xz/G1q1bi7DayoQQgunpadXw9/T0oKurC93d3eju7sbU1BRcLhc2b96MxsZGNDY2QqfT4dvf/jYCgQA4jiv2S1gVVJ1Claycf/75OPfcc/Ff//VfABLqm+vWrcPHPvYxfO5znyvy6qqUIoQQ+P1+dHZ2qgaf/r+npweTk5NwOByq0W9sbMTmzZvR1NSExsZGOByOlBNBPB5HTU0Njh07hi1bthTxla0equGjKhkRBAFvvPEGPv/5z6vfY1kWe/bsqdbulymRSAT//M//jHvuuQcGg2HJ1yGEIBAIpBj+5H8TExOw2+0pRn/37t2q4Xc6nQsOBWm1WmzduhVtbW1Vp7BCVJ1ClYxMTk5CluWMtfsnTpwo0qqqLAeDwYAnnngC7e3t8+aFCCEIBoPo7OxUjX3yjn9sbAw2my0l1HPJJZeoht/lcuUlB8AwDFpaWtDe3o7rr79+2derMj9Vp1ClyiqBYRicddZZePPNN3HeeeeBEIJQKKQmdqnh7+npQXd3N0ZHR2G1WlMM/4UXXoimpiY0NTWphr/QCWDqFKqsDFWnUCUjbrcbHMdVa/fLHEIIwuEwuru70dnZiXg8ju9///u4//770d3djZGREVgsFmzevFk1/hdccIG64/d4PCti+HPR0tKC+++/H4SQagXSClB1ClUyotVqcfbZZ+PZZ59VyzIVRcGzzz6LW2+9tbiLq5ICIQSRSETd6SfH+Xt6ejA8PAyz2YxNmzaB4zhMTk7iE5/4hJrgrampKbrhz0Vrayt6enoQjUZhNBqLvZyKp1p9tAy+8Y1v4PHHH8ehQ4eg1WrnTLUCgMHBQdxyyy14/vnnYTabcdNNN+Fb3/oWeL70/fEDDzyAm266CT/96U9x3nnn4Qc/+AH+8Ic/4MSJE3NyDVUKCyEE0WgUPT09aoKXlnRSw280GlN2/PRfU1MTamtrwTAMjhw5gosvvhhjY2PQ6/XFflkLQlEUrF27Fk8//TTOPffcYi+n4il9y1TCCIKAG2+8Ebt27cIvfvGLOffLsoxrr70WdXV1OHDgAEZGRvDBD34QGo0G3/zmN4uw4sXx3ve+FxMTE7jjjjswOjqKM844A08++WTVIRQIQghisZhq+JMTvD09PRgaGoLBYMDmzZuxadMmNDY24sYbb1SNf319/bw7/ubmZvz85z+HIAhl4xRYlkVzczPa2tqqTmEFqJ4U8sC9996Lj3/843NOCk888QTe/va349SpU6ohvfvuu/HZz34WExMT0Gq1RVhtlWJCCEE8Hkdvb++cks6enh6cPHkSWq02ZcdP4/uNjY3q5LjlhHpee+01NDQ0YM2aNXl8ZYXlX//1X2EymfCDH/yg2EupeKonhQLy8ssv47TTTkvZWe/duxe33HILjh49ijPPPLOIq6uSzuOPPw69Xo8rr7xyWdchhEAQhKyGf3BwEBqNRjX6mzZtwrve9S7V+Dc0NIBl2YLF+K1WK4LBYEGuXShaWlrw2GOPFXsZq4KqUyggo6OjGev86X1VSouDBw+ir69vQU6BGv6+vj41uUuNf29vLwYGBsDzPDZt2qTu+t/xjnegqakJmzdvxtq1awtq+HNhsVgwNDS04s+7HFpaWvDtb3+7WoG0AlSdQhqf+9zncOedd+Z8zPHjx7F9+/YVWlGVleL888/H7373O/U2IQSiKKK/v1/d8dP4fk9PDwYGBsCyLDZu3KiGd6699lr167Vr14LjuJIzYlarFaFQqKwMbEtLC8bGxjA+Pl7NaRWYqlNI45Of/CRuvvnmnI/ZvHnzgq5VV1eHgwcPpnyP1v1Xa/1LA0IIJElCf38/AoEAurq68I//+I8YGhpCT08P+vv7wTCMavg3b96MvXv3qqGedevWlaThz4XRaFTLWE0mU7GXsyBsNhvWr1+P9vb2qlMoMFWnkIbH44HH48nLtXbt2oVvfOMbGB8fV1VFn376aVitVjQ3N+flOarMDzX8g4OD6OrqSonzU8NPCMGGDRug1+sxMDCAq6++Grfccgs2b96M9evXg+f5sjL8uWBZFhaLBYFAoGycAsMwaG5uxuHDh1NmaVfJP1WnsAwGBwcxNTWFwcFByLKMQ4cOAQCamppgNptx1VVXobm5GR/4wAfwne98B6Ojo7j99tuxb98+6HS64i6+wiCEQJblFMOfLNLW398PWZaxYcMGNbxzxRVX4KMf/SgaGxuxYcMG8DyPG2+8EevXr8ctt9xS7JdUUCwWC4LBIOrr64u9lAXT2tqKI0eOFHsZFU+1JHUZ3HzzzbjvvvvmfP/555/H7t27AQADAwO45ZZb8Je//AUmkwk33XQTvv3tb5dF81qpQQ3/yZMnVdmG5Mqe/v5+SJKE9evXq6Ge5JLODRs2QKPR5Nzxv/TSSwiFQti7d+8KvrKVZ2hoCKOjozjnnHOKvZQF88ADD+DHP/4xDh48WDGntlKk6hSqlBTU8A8PD6eEeqhIW19fHwRBwPr16+d07jY2NmLjxo3QarVLNhqBQABvvPEGdu/eXdGGpxxf59GjR7F79+7qwJ0CU92uVlkwDzzwAM4//3xs3LhxWdchhIAQgqGhoYyhnr6+PsRisRTDv2vXLnzgAx9Q6/qXY/hzYTaboSgKwuEwzGZz3q9fKpjNZsiyXFZ6Qlu2bIEkSeju7sa2bduKvZyKpeoUqiyYX/3qV5iYmFiQIB41/MPDw3NCPT09Pejt7UU0GsW6devUOv4LLrgAf//3f6/e1ul0K76LZVkWVqsVfr+/op0Cy7Iwm80IBoNl4xQ0Gg22bduGtra2qlMoIFWnUGXBXHLJJdi/f7/qFKjhHxkZmdPARZu4wuEw1q5dq8b4zzvvPLzvfe9DU1MTNm3aBL1eX3LhC5vNBr/fX1YyEEuBJpvLpcSTViC1tbXhxhtvLPZyKpaqU6iSE2r4R0dHYbFY8Oc//xmf+cxn1Bh/b28vQqEQ1qxZoxr+s846CzfeeKNq+A0GQ8kZ/lzYbDb09vYWexkFx2q1YmJiotjLWBStra1444038nKtd77znTh06BDGx8fhcDiwZ88e3HnnnWhoaFAf09bWhn379uG1116Dx+PBxz72MXzmM5/Jy/OXKlWnUEU1/OPj4ymduzTU09PTo5YvBoNBHD9+HOeddx6uv/561REYjcayMvy5sNlsCIVCkCSpoqvELBYLenp6yq6z+de//nVe1nz55Zfj3/7t31BfX4/h4WF86lOfwg033IADBw4ASCTjr7rqKuzZswd333032tvb8eEPfxh2ux0f/ehH8/FySpJq9dEqgRr+iYmJjAPXe3t74ff7UVdXpxp6qtND/28ymXDhhRfib//2b/GJT3yi2C+poOzfvx+tra1wOp3FXkrBkGUZzz33HC655JKykdEeGhrCjh07CpLzeeSRR3DdddchHo9Do9HgJz/5Cb7whS9gdHRUVTT+3Oc+h4cffrii55RX7jZolUINf3qMn+74p6enUVtbqxr+5ubmFKE2s9mccwf2hS98oaITsBSaV6hkp8BxHEwmE4LBYNk4hYaGBlitVhw5cgQXXHBB3q47NTWF3/72t7jwwguh0WgAJFSOL7300hSJ+7179+LOO++Ez+eDw+HI2/OXElWnUIYQQuD1eueItFHj7/P5UFNTo5Zzbt++PUWozWKxLPnovWvXLhw7dizPr6j0sNlsGSfpVRpWqxWBQCBv0i6FhmVZtbM5H07hs5/9LP7rv/4LkUgEF1xwQYo89+joKDZt2pTy+GSV46pTqDIv+/fvh81mw86dO5d9LUIIfD4fOjo61OlbyZr8U1NTcLvdqqHfsmVLilCb1WotSJzYZrMhHo8jFouVze5yKdhsNgwMDJRVvH0pWCwW+Hy+Yi9jUdAKpEwsVuX405/+ND7ykY9gYGAAX/nKV/DBD34Qjz32WEX/zeej6hTyyP/8z/9AlmXcddddC3o8IQTT09MZk7vd3d3wer1wuVwpkg1ve9vbVMNvs9lW/M3L87xqSMpJN2exWK1WiKKIWCwGg8FQ7OUUDIvFgoGBgWIvY1G0tLTgoYceynjfYlWO3W433G43tm7dih07dmDdunV45ZVXsGvXLtTV1amqxpTVoHJcdQp5ZM+ePXPK1QghCAQC6OjoSBm2Tnf9k5OTcDqdKZINV155parZY7fbS27XYrfbK94pcBwHs9kMv99f8U4hHo9DEISyGQ/b2tqKr3/96xlPcctROVYUBQAQj8cBJEKlX/jCFyCKoppnePrpp7Ft27aKDR0BVaeQFwghCAaDsFgs6Orqwqc+9SmMj4+ru/7x8XHY7XbV6G/evBmXXXaZuuN3Op0lZ/hz4XA40N3dXexlFByabK7kXaFGo4HBYEAwGITL5Sr2chZEc3MzJiYmcOrUKWg0GnWD9YEPfGDBn6NXX30Vr732Gi6++GI4HA709PTgi1/8oiqpAgDvf//78ZWvfAUf+chH8NnPfhZHjhzBf/7nf+L73/9+IV9e0ak6hQVCCEEoFMpYztnd3Y2xsTHYbDbodDo888wzuPrqq3HJJZeoht/lcpWV4c+F3W5HOBwuq93lUrDZbGU3tnIp0GRzKToFOvY0Go0iEongv//7v/HSSy9Bo9Fg69atiEQiqK+vR1NTE6677jpYrdYFXddoNOKPf/wjvvSlLyEcDqO+vh5XX301br/9dlXW3maz4amnnsK+fftw9tlnw+1244477qjoHgWg2qeQAiEE4XA4RaAt2QGMjo7CarVmVOdsamqCy+XCbbfdhpMnT+J///d/i/1yCsqBAwfQ1NSkDg+qRMLhMF555RVcfvnlYFm22MspGH19fQgGgzj99NOL8vzU8EciEfUfdQKRSASyLEOn08FoNOLpp59GOBzGs88+iyuuuAJf+9rXVkWJ9EqyKk8KkiTh6NGjc0Tauru7cerUKVgsFtXwb968Geeff7664/d4PGAYJuuu/8Mf/jAOHTpU8VUrDodDLX2tVIxGI1iWRTAYhM1mK/ZyCobVasXw8HBBn4MQgng8ntHoRyIRKIoCvV4Po9EIg8EAq9WKuro69TaVyqbzH3iex9jYWNUhFIBV6RSmpqZw8cUXq2qcTU1NOO+889Rdf01NTU7Dn4vW1lZMTExUvPSy3W4vu6qVxcIwjJpXqGSnYLFYEI1GUxKqSyHd8Kcb/2TDbzQaYbPZUF9fP8fwL4SWlhY888wzFb/5Kgar0im43W6MjIyAEJL3YR0sy8LhcGBqaqqinYLD4cCRI0cqXh+IOoVKRqvVQq/XIxgMztvBTQhBLBbLuNuPRqNQFAUGg0E19Ha7HQ0NDertfIXhWltbcfz4cUiStCxHVmUulftpzgHLsuA4DpIkFeT6TqcTU1NTWL9+fUGuXwro9XoYDAZMT0/D7XYXezkFw2azYWRkpNjLKDhURtvpdKYY/nSjH41GQQhJ2fE7nU6sWbMm74Y/F01NTSCEoLu7Gzt27Cj4860mVqVTAKCGhwpx/HQ6nejr64OiKBWdoLTb7avCKUSj0YqrtFIUJWXHH4/HMTAwgKGhIUSjUQCAwWBQd/0ul0s1+itl+HPB8zy2bduGw4cPV51CnlnVTqFQUG2hQCAAu91esOcpNg6HA6dOnSr2MgqKRqOByWSC3+8vG30gSrLhz7TjB6Aafa1Wi1gshm3btsFoNEKv1xfd8OeCYRi0traivb0d73vf+4q9nIpiVToFWoVbqJMCwzBqXqGSnYLdbsfx48chy3JFD1Kn4zlL0SkoiqLG9tNj/LFYDADUHb7RaITH41HDPjqdTjX8sVgMf/3rX+FwOMrmb9nS0oJXX3212MuoOFalU6AwDKO2tucbl8uF0dHRFJ2VSsNoNEKj0SAQCFR027/NZsP4+HjRnj/Z8KdX9USjUbAsqxr9dMO/0HGnOp0OWq0WwWCwbDYyLS0t+MUvflGtQMozq9Ip0JMCy7KQZblgeYUTJ05U9C6anogqWVseSJyIurq6Cmp8ZFlOMfzJX8diMbAsqxp6g8GAmpqalB3/ctfFMIyabC4Xp9Da2oq+vj6EQiFYLJZiL6diWJVOIRmWZQvyYTcYDNDpdJieni5J+YB8YbfbMTk5WexlFBSTyaR2uy+nzDjd8Ccb/3TDbzQaUVtbm1fDPx9U7qJcqK2thdPpRHt7Oy688MJiL6diWJVOITmnkHw7nzAMA6fTqcpfVypUHK+SK61YllXzCvM5BVmWs3btxuNxcByXEuO32Wyq4ddqtUUNg1gslrJy8CzLoqWlpeoU8syqdArJsCwLRVEKFkKq9K5fOr6z0qUg7HY7/H4/1qxZA0mS5hj85NJOavjpP7vdnlLlU6rxb6vVilAoVFYOPtfAnSpLY1U6heSTwVLlLBaC0+nEkSNHKq7GPRmGYdR+hUpyCpIkpRh7v98Pv9+PiYkJCIIAnudTdvwOh0N1AhqNpmQNfy70ej04jkMoFFqw2mixaW1txR/+8IdiL6OiWJVOIZ1ClabqdDqYzWb4fD51tmslQpPNGzZsKPZSFoUoilnlGpINv9FoVKfNtba2wmKxlK3hzwXDMGpeoVycQktLC44cOVJWp5tSZ9U5BVmWMTU1pe5qC5lXAGYlLyrZKdjtdvT395dkaSA1/Jni/FQALrmqh3bu0h1/MuPj42AYpmJPfcCs3EW50NzcDJ/Ph1OnTmHt2rXFXk5FsOqcgiAIeOWVV3DFFVeoH3pagVSovEJHR0der1lqWK1WyLJcFGVYQghEUcxa1ZNu+JMlGzIZ/lxQcbz5ROPKGYvFgpMnTxZ7GQvGZDJh06ZNaG9vrzqFPLHqnILBYIDJZILX61XHLCafFvLtFBwOB2KxGKLRaMXO+mVZVp3bXAinQA1/th2/JEnQarWqoTeZTGoDl8FgyJuKps1mg8/ny8u1ShWr1YpgMFg24RiGYdRk8zXXXFPs5VQEq84pAIlu48nJyRSnQPMK+YbnedhsNkxNTWHNmjV5v36pQJPN69atW9LPp49dTHcA6YbfbDajpqZGTfSuhHy3zWYr2TBZvjAajWAYBpFIpGyk32lZapX8sOqcAiEEbrcbR48eTflwF1LyguYVKtkpOBwODA8P5zSYixm7aDAYYLFY1AYug8FQ9LkNFosFoigiFotV7KmPdjYHAoGycQqtra148sknK9pZrySrzikACSNNjZPJZAJQeMmLoaGhin7T2mw2dafPsmzWqp5kw280GtWxi3THX8qSIBzHwWKxYHp6umKdAlB+yeaWlhacOHECoihWdBHASrHqnAKdtuZwODA5Oak6BaBwkhc2mw2SJFXMiM5s83YZhsGBAwdShrAYDIZljV0sNWw2GwKBAOrr64u9lIKxEjOb80ljYyNYlkVnZydaW1uLvZyyZ1U6BSAxknNyclKtrS9kaSod0en1esvGKSxk7GLy9C0qokYIwWmnnVbWhj8XNputrKpzlgI9KZTLyZbjOGzfvh1tbW1Vp5AHVp1ToLjd7jmaPYWWvJiamiqpBq9MYxeTJZmT5+3Srt1cYxeNRiM6Ozsr1iEACadw9OjRsqnOWQpUADA5vFrKMAxTTTbnkVXnFOhJwGw2g+M4TE9Pq3XnhZS8cLlc6O3tXXFjQqdvZavqAZC3ebt2u10VftPpdIV6SUWFJrwreaoey7Iwm80IBoNl4RSARBPbSy+9VOxlVASrzilQGIZRQ0jJzUiFkrwwm81gWbYgxiTT2MXkHT8wO3aRdu2uW7cu72MXNRoNzGYzpqenK7aDm2EYNa9QqU4BmA0h0bLtUue0007DPffcUzYhr1Jm1TmF5JyB2+1Gf38/tm7dCmDlpLSXYkwWO3bR7Xart1dy3i7VQapUpwAkQkjT09NYv359sZdSMKxWK8bGxoq9jAXT0tKC/v5+BAKBihJmLAarzikk43K50NbWlqJiWmjJi5GRETQ2Nma8P9fYxWTDT439UsYuFhqHw4He3t5iL6Og2Gy2sqrOWQoWiwXd3d1ls/OuqamBx+NBW1sbLrnkkmIvp6xZdU4h+RSg0+lgsVjg9XrVEsNCSl64XC4cP34cgUAgY5yfTt9KTu7SsYt0x1/qH1C73Y5QKKRqDlUiVqsVsVisonMnZrMZkiSVTaMewzBobW1Fe3v7sp1Cf38/vva1r+G5557D6OgoGhoa8A//8A/4whe+kNIH0dbWhn379uG1116Dx+PBxz72MXzmM59Z7kspOqvaKQCzpanJTmG5khe5xi4CwMGDB2EymYo2drGQ0Ma06elpeDyeYi+nIGg0GphMJvj9ftTU1BR7OQWB4ziYTCYEg8GycApAItmcjwqkEydOQFEU/PSnP0VTUxOOHDmCf/qnf0I4HMZ3v/tdAEAgEMBVV12FPXv24O6770Z7ezs+/OEPw26346Mf/eiy11BMVpVTyGTo3W432traFi15sZCxi8k7fpvNBoPBgOHhYWi1Wmzbtq0gr7EUoDpIleoUgFnF1Ep1CsDszOZyeY0tLS24//77l32dq6++GldffbV6e/Pmzejo6MBPfvIT1Sn89re/hSAI+OUvfwmtVouWlhYcOnQI3/ve96pOodxxOByQJAmhUAgWiwXAbL8C1bnJtONPH7toMBjUrt1cYxcFQUB/f/8Kv8qVxeFwYGhoqNjLKCg2mw2jo6PFXkZBoaHVcqGlpaVgPSTpkukvv/wyLr300pRw0t69e3HnnXfC5/PB4XDk9flXklXlFDKdFFiWhdPpxOTkpOoUAODQoUPwer3geX5OAxd1AkuZt+t0OtHe3l7RIzodDgeOHTsGWZYrtpHNZrOhs7OzbBKxS8FqtaKvr6/Yy1gwO3bsgN/vx9DQUF4rw7q7u/GjH/1IPSUAwOjoKDZt2pTyOFpxNzo6WtZOoTJbMheJy+VK2RExDINAIIAzzzwTu3fvxgUXXIDTTz8dTU1NaGhogN1uX3LsX6vVwmw2Y2pqKp8voaTQ6/XQarXw+/3FXkrBoHIloVCoyCspHGazGYIgIB6PF3spC8JoNGLz5s1oa2vLeP/nPvc5NWeY7d+JEydSfmZ4eBhXX301brzxRvzTP/3TSryMorPqTwpAIq/Q2dmp7mzpUBe9Xl+QdbhcLkxNTZVNY9BiYRhG7Veo1ClldJ6x3+9POWFWEnRGdSAQKIv8UPLAnbe//e1z7v/kJz+Jm2++Oec1Nm/erH596tQpXH755bjwwgtxzz33pDyurq5uTh8HvV3un+uqU0BC60Wr1cLn88HtdkMURQAoWLmh0+mcsyOpNOx2O8bHx4u9jIJCk82VPAaSTmIrB6cAJGYrHDlyJON9Ho9nwa9jeHgYl19+Oc4++2z86le/mpOj2LVrF77whS+klF4//fTT2LZtW1mHjoBq+AhAquQFkEgGcxynnhryjd1uV/sUKhWHw4Hp6emCDS4qBahTqGTKbbbCjh07cOjQIbz88sv47W9/i6985StzdvnzMTw8jN27d2P9+vX47ne/i4mJCYyOjqYUFrz//e+HVqvFRz7yERw9ehQPPPAA/vM//xO33XZbvl/SilM9KczgcrnQ09MDIOEUNBpNwaax0RGdXq+3YneZJpMJHMdVtEaQzWZDOByu+Ea9UpMKp/M8kmVfgsEgPvKRj2BwcBDRaBTvfve70dTUhMbGRmzcuHFR13/66afR3d2N7u7uOZ9PakNsNhueeuop7Nu3D2effTbcbjfuuOOOsi9HBVaRU4hGozhx4gR27NiRMUHscrlw+PBhxGIxtTKo0JIXU1NTFesUGIZR+xUq1SnodDro9XoEAgG4XK5iL6cgWCyWlM/ESpFtngf9ms7zoJWBdrsdn/rUp7B+/Xpcd911eOqpp3D66acv6blvvvnmeXMPAHD66afjr3/965Keo5RZNU5Bo9FgaGgIGzZsyCgHrNVq1d27LMsp5aaFkrw4efJkRZc0OhwOTE1NLXqnVk7QEFKlOgWNRgODwYBgMJj310i1vjIZ/XRZd4PBoM7zMBgMGSf4bdq0CYQQNdm8VKew2lk1ToHn+YwjOJOheQWaeM6H5EU2rFYrFEVJaZqrNOx2O3p7eyva8dlstoouLwZm8wpLcQrpki/JRj8ajYJhmDnqvvTrpaj7JlcgVVkaq8YpEELmjOBMx+1246233gLP8+pRuVB5BTqic2pqqmKdgsViASGkoh2fzWZDX19fRTs+KneRDUmSsoZ54vE4WJZVmz+Ttb4KJfLY2tqKv/zlL3m95mpi1TgFIPMIzmRsNpuqaUR3RYUe0en1ektqRGc+YVkWdrsdPp+vYp2C1WqFJEmIRqMwGo3FXk5BMJvNGB4eht/vz7jjFwRB7Wmgu3yHw6F+vZTO/+XQ2tqKu+66q6IddSFZNU6BEAKz2Qye5+Hz+TIehVmWhcvlQigUSkmqFWoam9PpRE9Pz4qN6FQIgTcsQFEAh1EDLV/456TJ5kodSMOyLCwWC/x+f1k7BUIIBEHIuuOXJAlvvfVWitaXy+VSb5dS9VVzczMGBwcxPT1d9j0DxWDVOAUgtR8hW3zU7XbD6/WmhI+Awkxjo3Oi/X5/Qd+8gqTgiaPjeLR9FIO+KEAAq57HNS01eOfOOnjMhZsJ4HA4Kj6hTpPNVH69VKEVPdli/LIsq9LnBoMBFotFDfW8+eabOP3008vCyHo8HtTV1aGtrQ2XXXZZsZdTdqwap0CNutvtRm9vb1bparfbjWPHjqVUNhQqhERHdE5NTRXswxYTZXz58Q683OsDA8Co48CAgS8i4r5XTuLZjkl867od2OAszC7XarVCFEVEIpGyGQK/WGw2GwYHB4u9DABz53Wnj3AlhKjVO7SUk6r7ZqroodDO5nJwCjTZ3N7eXnUKS2DVOAVKcj9CJm0jOlAkOa9Aq5AKgdPpxKlTp7KO6FwuvzwwiAM9PtgMPPSa2Q+8UctBVghO+qL46uOd+Onfnw6+ACEsjuPUmcaV6hTsdjuOHj26YqqwyRU96f+nY1uTlX1pmIc6g6WEKudLNpca+Rq4sxpZNU6BnhSS+xHWrFkz53GSJAFI6KevW7duzjUKkVc4fvw4JEkCz+f3z+GPinji2Dh0PJviECgcy8Bh1KJ3MoyD/dO4cHNhxOuoOF6m33cloNfrwfM8gsFg3hr1aPI6046fjm1Nju/X1NSklHLm+31qsVjKSsuqtbUVv/71r4u9jLJk1TkFYLYfIZOREgQBAODz+dTv0ZNCIUpTaVmez+fLu+jYG4N+BKISXKbsnag6noVPIXil14cLNzsRiIk4PBRA72QEGo7BaQ1W7Kg3L+sUYbfbK1oAkGEYNa+wGKdAw2qZdvy0oid9et98Q5wKhdVqRTgcLpsZGa2trQUbuFPprBqnkIzb7cabb76ZcedP2/npB5SGk1ZC8iLfTiEUl0CQOBHkggEDb1jAf/2lFw8dGsVkWICsJJwozzJYazfgQxeuw7vPqAe7hNdut9vVHW6h5MiLjd1unyOORyt6Mu34aUWPRqPJWNFjMBhU/a1SQKfTged5hEIh2Gy2Yi9nXrZv345QKISBgYE5w3Cq5GbVOIXkk4LNZgMhBIFAYM4bXBAE6HQ6mEwmTE5OqiGkQkte9Pb25vWaAGAzaMAAkGQFPMdCVhIlqb6IAFkBwAAajgHPMHjrpB9PHRcQE2UQAnAzmytZIeifiuDOp7rR543gtisbF+0YeJ6H1WqFz+cr+QqdxULF2ViWhdfrRVdXV4oDSK/oMZvNKaGefIcMCwXDMLBYLBk/M6WIXq9HY2Mj2traqk5hkZTHOzLP0H6EycnJjE5Bq9XC4XDA6/WmOIVCSV44HA6EQiHE4/G8znA4Z4MNTpMGgagEg4bFoC8KKTkCRgBBIhBAMDCVKuMtKUicMhiAZYCoKOP/Do9i5xob3rZj8Sca2q9Qjk4huaInfdcfjUZVcTZJkhCLxWCz2RZU0VNu0AqkcoBhGLS0tODIkSN417veVezllBWrwinQsE8ybrc7Y9UPdQputxsDAwMpJwOWZdVEdD7RarWwWCyYmprKq9E0aXlct7Me97zYj7FAHLkyItlcnUwSdzIAIoKMR9pGsWe7e9GnJYfDge7u7kX9zEpCK3rSY/uZKnpomCf5NsuyePnll1FbW4uampoiv5rCYLFY0N/fX+xlLJiWlpZqBdISWBVOIRO0HyFdC586BXqCSE4eJp8WCpVXyPdO+v3nrsGDbwzDF1meMyMABFlB53gI3rAIt3lxMsp2ux3hcHjFJZiToRIm2Uo5aUUPNfYej0eN9y+koocmmyvVKVitVoRCobJJ3ra0tOCPf/xjRTdOFoJV4RQyhXzoB9/r9abMVBVFESaTCQzDqCGm5IqSQjqF48eP5/3aBMDQdH4mvMkKEJcUCPLiq7C0Wi1MJhOmp6cLajRpRU+mHX96RY/BYFAregwGA3Q63bJ+9zabDSMjI3l8NaUFDYWFw+Gy0LJqbW1FZ2cn4vF4xRY4FIJV4RSyQUtTk52CIAiqE3C73RgaGkJTUxOAwkpeOBwOdZpUPjV0fvJCHwQ5b5dDOC7hyaOJ3odttWactd624MQz7VdYjlMghGQs5aRf05Nf8o7f6XSqXxeyosdms+HEiRNls5NeLMnJ5nJwChs2bIBer8fx48dx5plnFns5ZcOqcArZjLjb7cbRo0dTdufJ4Q2Xy4WjR4+mhJgKJXnBcRzsdjumpqby4hQEWcHd+/vx0xfzK78QERTc+/IgGIYBxzJY59Djw7vW49yNDhi1uROqdrt9QXIQtKInmzgbreihht5sNquhHlrKWQzoCbNcdtJLoZxmNrMsix07dqCtra3qFBbBqnAK2XA6nRAEAeFwGGazGUCqU6BGZ2pqCrW1tQAKL3mRjxGdhBD84Lke/PbVoTytbBaWBVwmLUSFwBsS8OagH2+dPAKPWYvdW9145+m12Lk2c8miw+HAkSNHIEkSWJbNKc5GK3qSwzz19fXq36QUK3qSm9gq1SmU4szmbNAKpGqyeXGsCqeQ7aTAcZw6jS2TUwBmQ0zUKSRfsxB5hcHBwWVf++hIEA++MQIx/w3YAAFCgoyJYBwyAViGgUwIfBERTx0fx1+7vfiXyzbiup316rjFZIPPsiwOHDigdo4ni7M5nU6sWbMmpaKn3KBOoVJnb9OTQrkkb1taWvDMM88UexllxapwCrmgRn/jxo2QJAmKosxxCseOHVNvF1Lygo7oDAaDsFqtS7qGQgg+8T9HEM5nIiEJmQBjgTgAgGMIAAYsEuEqB8MgJsj4/lMdmD7ZhTW6RFNXslSD2WyG0WhEU1MTdDpdWRr+XNhsNnR1dRV7GQWDhjaTT9elTGtrK37wgx+UjRMrBVaFU8iVGHa73ejq6oIsyxAEAQzDpMSkHQ4HYrEYwuGwqvJZKMmL5BGdS3UKd/65C8PT8bytKRPSjAQGA4Co3Q8MxsMyrDoGBAw6BTvet2frnIqe4eFhnDp1SpUPqTRsNhvC4fCcUudKgQ4VCgaDZeEUWlpaMDw8jKmpqSXNmF6NVNY2LQOCIGB0dDTr/WazGRqNBj6fT/0gJxsxnufV7mZKIauQXC7XkgfBjwfjeOhw9te6dDK/zlnXwKi3gwIgKMCbw2FIDD/HadrtdgQCAchyYU4yxUar1cJgMJSVzPRioRVI5YDT6URDQ0M1r7AIKt4pxONxHDp0KKsRSp7Glq2xit6f/DOFTDb7fL4lhaf+fGwckSWHjXI5uIW9VoUkThFRQcFUWIQ/Ks55DNX7KRejshTo/IhKpdzkLpqbm3H48OFiL6VsqHinYDabodPpcu6+PR7PvE7B6/WmGGoaQso3JpMJPM8vyaj0TUZy3DujV5GV/DbMhQQZtz9yHMPTsdRnYRi1X6FSocnmSiU52VwOUA2kKguj4p0CkDDqExMTWe93uVwIh8MIh8MZnYLFYgHHcSmGulACeckjOjOhKArC4TAmJiYwODiIEydO4K233sJLL72E8VNDiTxH5isjn4Z/PhgAHWNh3P7IcQRiqScGKo5XqdhsNgQCgbIxmovFbDarWlHlQD6dwje+8Q1ceOGF6ijTTAwODuLaa6+F0WhETU0NPv3pTxdEM61QrIpEs8fjyTnkRaPRqOMGMzWOUckLr9cLp9OZ8v1CVDXQYfdWqzWjOFtyRY/BYIDb7YbRaMRVxggOPNELQVZQCvaIBdAxFsJTxyZww1kN6vepOF6ldv5aLBZVZ6kSR5CyLAuz2YxgMJjX7vtCkc+BO4Ig4MYbb8SuXbvwi1/8Ys79sizj2muvRV1dHQ4cOICRkRF88IMfhEajwTe/+c1lPfdKUfFOgRACp9OpGtZsb2KqmppNK97tdmNwcBBbtmwBsPxksyRJcww+vR2PJ6qHenp61FLO2tpa9etsGj17LHb89MDwHBnsYkAA+GOJIT/ff64XuzY7scae0J8xm81gGAbBYLAstPkXC63Q8fv9FekUgNlkc3r/Timybds2RKNR9PX1LXsW+le+8hUAwL333pvx/qeeegrHjh3DM888g9raWpxxxhn42te+hs9+9rP48pe/XDQxyMVQedu0NAghagVRcrI4HbfbjVgslrWM0OVywe/3q01XANRdRybHQKdu+f1+jIyMoKenB0eOHMHBgwfxl7/8Bc8//zzefPNN9Pf3IxQKQafTYc2aNTj99NNx6aWXwmAwoKmpCTt37sSWLVuwdu1aOJ3OnGqdRi2Hz+/dAoexNEohNRwLlmHgj4r47ENHMRlK/O4YhlkVIaRKziuUU7JZp9Nhy5YtaGtrK/hzvfzyyzjttNNSnOXevXsRCARw9OjRgj9/Pqj4kwKF5hXWr1+f8X66Y80W+9Pr9bBYLPB6vaq8NcMwkCQJ4XA4RYuf7vglSYJWq00RZ3O73SnibNmg4arFjui8uMmF/7i+BR9/8Aimo8WNY8qEgGEAnmHQ743gf986hX++ZCOAWXG8DRs2FHWNhcJms5XV7IHFYrFY0NPTUxZNYbQCqb29He9+97sL+lyjo6NzTk/0dq7S+FKi4p0C3cV7PB709PRkjSuyLAuWZRGJZK/goaWpyTMPDh06hFgsBpPJBKPRCKvVqoZ6DAbDksctOp1O9PT0LOlnz9vowMev2IzvP9cLRSEIxeWcdUeFQpITTsGq56HTcPjzsQl88IJ1MGgS4n/9/f1lYVSWgs1mQygUKptB94vFYrFAFMWykaXOpYH0uc99DnfeeWfOnz9+/Di2b99eiKWVHBXvFChmsxk8z8Pn82XtbKQSE9lwuVw4cuSIasgYhkE8Hkdra2veuyWdTifa2tqWPKLzbTs8uP+1YYwFYqpDoLZ3JZLQiW7nxH9seh48xyIQEzEWiGOjK+E8ZVkuG7mExaLX66HRaBAIBOBwOIq9nLzDcRxMJhMCgUDZOIU//OEPGTchn/zkJ3HzzTfn/PnNmzcv6Hnq6upw8ODBlO+NjY2p95UDFe8U6EkhuUktkwGnctihUCirRIHD4ZijqiqKIrRabd53vLQiaqnT2BxGLT79tiZ89qFjAGYa2lbwuDDfU7EsC7vdDp/PB7PZjOmIiH5vBATABqcBTlPpJ+RykayYWolOAZjNK5TDpLnW1lZ0dXUhFovNkVjxeDyLDtNmY9euXfjGN76B8fFx9ffy9NNPw2q1orm5OS/PUWgq3ikk43a70dPTg23bts25jyaQM01jo3AcB6fTqaqqyrIMRVGg0WhKckTnRY1OnL7GigO9UxBlMntimPl/oX0EzzEgCoGoAHFZhsukRb1t9tRjt9vRfcqLP3QIeK5jEhEhkQMxaDns3urC35+7FlqexYg/Dp5lsMlthEFTPqGYSk82WyyWsmlCXL9+PUwmE44ePYpzzjlnydcZHBzE1NQUBgcHIcsyDh06BABoamqC2WzGVVddhebmZnzgAx/Ad77zHYyOjuL222/Hvn37lnTiLwYV7xSSK4NcLhfa2toQi8XmHHkFQYBGo1G7m7Md9ZJVVUUx0ZRVqDIzp9OJY8eOLcnhhAUJx0dCiIoyTFoOTpMW01ER4bgMmRAoBJCVwrkFjkn8k2YUZSUF+JvWGuj4WaMeYY34j5cH4ZdDMGg42AyJ01lEkPHQoVE8cngMeg0HWUnkJhxGDf6mpQbvPWcNzLrSf+vabLaymT2wFKxWKwYGBoq9jJwQQjA5OYnDhw/D6XTi61//OqxWK3p6etDd3Y0333wTa9asWfD17rjjDtx3333qbTq85/nnn8fu3bvBcRwee+wx3HLLLdi1axdMJhNuuukmfPWrX837aysUpf/JWibJTkGr1cJms2FycnKO3j2VuHC73Sl5g3RcLhc6OzshyzJEUQTP8+A4riAdi3a7HYIgLKoJKirK+O3BIfzpyDimIgJCcRkxUYYgK3CZdai16qAoCYnt3skwpELMXAAAZjZ3EZUUtNZb8J4zZk88hBDcc3ACE1GCersGmiRnoeEIYkJiFrSOl7HOqQcIg+mIiPteGcIbg35867odqhMpVaxWK+LxeMZNSCVgsVgQj8ezysOsFIQQxGIxteovfYbHgw8+iEcffRQMw6CzsxN/93d/h6uvvhqNjY1wu92Leq577703a48CZcOGDfjTn/60jFdUXCraKWTqH6ClqdmcQqa8QTJUVZXW2FNV1UJ0NyeP6FyIU4iKMm5/5ARe65+GhmdgN2hg1vEYnIoiKioY8cdQY9bBbtSAAwOeYyEVYC4EgITjYQj0HIsrtrpw256mFCN+fDSE46MhmLUsQGQACaegEILRQAwSUaDhGMgKgSQDRi0LJ6+FICtoPxXAPS8O4NNvayrI2vMFz/Mwm83w+/0V6RR4nlcVYRdrXBcLldXIVPpN5Tb0er1a7p08qe+SSy7BT37yE9x999144okn8MUvfrGgay13KtopZMLj8aC/v39OaSp1Cul5g3SSE9Y2m01NSBdK8oLmFdatWzfvYx984xReH5iG3aiBjk+8Ng0H2A08pqMSFIVgPBiDUctByxdG0A9IVDmxAHiOxWlrLDhrvR1c2u/lzZN+CDKBVctBkmTQvH4oJkGUCXiWAQMGEiGICLI6/1nLsTBoODzfOYkPX7gerhJPSNO8Qjl0/i4FmmzOh1MQRXHOLp8a/UzyLh6PR72t1+vnlbBobW3Fd7/73Yotg84XFe0UMhk9q9UKhmHmVIXQnAKQmjfIhNvtRm9vL4xGI7RabUHnKzidzgXV88clGY8fGQPHMqpDoNRYErIY01ERkkwwNB2DUctCVmZk8piEU8tXjoEQgOMYmLUcer1R/OdzvfjvV4fw6bc14aJG58x6FbAANDyPWGx2KFBYkEFAwDBs0vVS12XR8ZgIxfHWST/2bM9P1UihsNlsOHXqVLGXUTAWM1uBdvmnG336fxqOTW72tNvt6u1s8i4LpaWlBSMjI5iYmCiLiqliUdFOIRPJO/10p0Djom63W80bZGo8crlcOHz4MCKRiOpIWJZVy1rzuQuhE9gCgUBOnaCeiQgmQkLGBCzDMKixJMJGo/4YzDoe155Wi7dO+tExFkJMVMAyDAiTSEAvF6eBQ719VmNKVgimwgK+8UQn7nx3M05bY4XHrE3MbGM4KIoChShgGRZKsgOY+ZLnUn+fLJu4HRZk9E1GIBOCWosOFn3pvZ1tNhtOnDhRseJ/VqsVw8PD6m1FURCLxTIa/UgkAkVRoNPpVKNvNptTdvyFnFZnt9uxdu1atLe348orryzY85Q7pfcpyiPZdu5utxsDAwOquB2QcAo0XGQymaDVauHz+TIei7VarXpspgJ7hRq8Q+cPTE1N5XQKopxwSGyOJWg5FmY9j3qrHrdd2YjnOibxlcc7wDFMYoeep4OOLyrDohdg0iXyLRzLwG3WYiwYx+9eH8Zpa6y4tMmFe14cQCgug2dZyJIMVsNCw83qSSkEYBnMMfYxUUZMUvCzFwcQExPrNmo5XLHNjRvPasBaR+mM+jSZTGBZFqFQaMkjVksNGt+PRCIIBoOIRqN4/fXXEYvFEIsl5mfo9XpVwNHhcKChoUHd8Rerw5thGLS0tKCtra3qFHKwap1Ce3t7Srdw8kkh+TSRLVbqdrsxMjIyR1O9UHmFiYkJbNq0Ketjaiw6aHkWcUlRDWsmRJlg7Yxa6cVNTpy1zobXB6fBs4w6e3m5EAADvjg4Jg6WZaDlWOg1CXG81wZ8GJ6OYY1dj+t21uG/Xx2CAhacJEGj0cCi5zEdEWdCWQzsRg34pB22rBCcnI5CUYBwXIJZlxj5GRVl/PHQCF7q9eGb79yOrbWl0SXNMAysViv8fn9ZOQVRFOckc+ltQRDAcZy6u+c4DhaLBZs2bVJVfEv1VNTc3FwduDMPFe0UsqHT6WC1WuH1etHQkND5Ty+rc7vd6O7uznoNt9uNvr4+VduInhSWMkZzPlwuF7q6unLq6NTb9Dhngx37u7wwabmMjkmQFDAA9jYn4qlajsWX3r4N/+/+Nhw5lf/xmDIBZJlAlGWEBRncTO7iha5JvP/ctbh51zqE4hL+79AIJiMijIqgnnZEBbDoOXjMs38TQgiGpqOQZAKPWQu3ebYZSMezUPQ8xgIxfO2JTvziA2dAm8M5riQ02byQYoGVghCCeDyeNb4vzTjp5Pi+w+FQbyfn0g4dOgS9Xp93qZdC0NLSgp/+9KfVZHMOKtop5Er80pMAdQpUroJC8wbZasztdjsIISmzn+mIzny/4Wis1e/3pwz5SefvzlmDt076MRES4DJpwSXFkuKSDF9ExM41NjXZCwBGTSKmb9Pz8BVYVVUmAEMIfnlgEM11FpyxzoZ/vXwzLt1sx6+eOYSgRg+GYbF5uwED3ii6J8IYDwrQ8AwIAQRJRkxUYNHxcJvnVh2xDAOnUYuTU1G83OvDZVtKw0jZbDZV/2YlURQlY90+/Z6iKCllnBaLBbW1terthYo50gFV5cBpp52GY8eOQVGUihQqzAcV7RRy4Xa7cejQIdWIpzsFjUaTtdENSDgAjuNSVFWTq5Dy6RSSR3TmcgqtDVbcfvVWfOfpbkyGE7IdHMNAVhRwLIMz1trw5bdvgzapOunwkB+jgXjCYKNw0hf02hwLxEQF97w0gLveexoYhsFZG12I7tBj69Z1qgaNKCt4tc+HJ46Oo88bgYZj4TBqcLDfh3qrHtlGi2p5FjIhONhfWk4hEolk1dRaDpIkZU3qxmIxMAyj7u4NBgNcLldKWWc+wjwWi6VsZKG3bt0KQRDQ29ubklOsMktFO4VcJwW73Q5FUVJUHtO7MulpIpNToNcPhULqbYZh1CqkfON0OnHy5Ek0NeVu2Lqw0Ylf1Z+BZ09M4pU+HyKCjHqbHldud+O8jfaU+DwAnPLH4I9KUEDAsoBcoA5n+peQCRCIifhr9xT+5fdteP+5a3FRo1MdukOdgoZjcXGTCxc3zRr2R9pG8eZJv1p9lA0GDEJxOedjVhKtVguDwQC/37/oen66YckW5hEEQW0io8bearWqRj/XUKZ8YbFYEA6Hy0ImXKPRYOvWrTh8+HDVKWShYp1CNBrNOaSGZVm4XC5MTk6ipqYGHMfN2TXRKqVMO39CiOpUkj8MtIkt31AdpIXsNh1GLW44qyFlLnI29nd5ISmJOD7HMlBACiqURwigEICA4JU+H46PhHDtabV43w47Rk4N5/xZp1EDBolTRLZkOiEEBCRjeKmY2O32rE6ByjRk2/HLspwyrInu+Olt2lVfLHQ6HbRaLYLBYNZh9qUCrUBqb2/HDTfcUOzllCQV6xTo+LvLLrss6wfG7XZjeHgYdrs9o3YLLQH1+/1z3uxU60in02Fqakp1PoWSvKAlfj6fLy+NN95wHN94ohNPHZ8EwWxSeCWghxFBIvCGBfz2tSH4wx5cZAzk3G2es8EOj0WHqbCQtZM5KirQcix2l0joiGI2mzE+Pg6r1ZrR+BNCUnb7yTINyxnWtBIwDKM2sZW6UwASyebDhw8XexklS+m+05aJy+WCKIoIhUKwWCwZH+N2u3Hs2DFEIpGMToFhGPU0kf5mF0UxpXQ1+URSaMmL5TgFQgj+560R/Mcz3QjEihtiUZ2RRPBQ2zheN3MIWPvxt+dvzvi702s43HhWPX6yvx/BmASzLrXKKi4pCMREXLDJgdaGzH/zQkJlGjJp89D6/a6uLnWHnzyadSEyDaVMOc1sbmlpwW9+85tqBVIWKtYpsCyrahhlcwoGgwEmkwnT09NZVR7dbnfGWD6VxUgvXS205EWuMtl0JoJxPH1iAi/1TGE6IkKUCQIxESd9saKM58wFATAZA3784jBYrQE3Zgl93XhWAyaCAh4+PIKxoAQdz4FhgLiogGGAs9bZ8IVrthbkw74cmQadTocDBw5g586dC1a8LScsFgsmJyeLvYwF0dLSgp6enkWpD68mKtYpAAmDPjY2lrPpy+12w+v1Zm0scrvdOHr06JxYPq1WoqWr0WhUnehUKMkLh8OhVpXMp7r5QpcX332mG76wAEEmiIoKFKWw+YLlQpDo8/jlgUFc2uRCrXXuUBKWYbDvso24qNGJJ46O4c2TfsgKwWkNFvxNay0uaXKmzGxYLLlkGqLRKGRZTpFpMJlMC5ZpoP0KlWiIrFYrQqFQWch5rF27FhaLBUePHsV5551X7OWUHBXrFAgh8Hg8OHHiBCRJyhqTpSeBbI03er0eJpNpzjQ26iRo6arX61WrlAoleaHRaGCxWDA1NaX2V2SibTiAb/+5C1FRho7nEIgJKzOYeZlEJQITRxCOS3jq+AQ+cH7mqi+GYXDmOhvOXJdd9iMXyTINmco4gcLINNDO5lx/u3JFr9eD47iykPNgWVZNNledwlwq1ikAUD/EXq83q3Sxw+FIaUDLBM0bJDuFTKqq6aWrhYhZulyueZ3CH94YRiguwarnMDAVy4vI3UoxLTAwMQqOjS4vPp1NpiEajSIej6fINBgMBtTU1Ki3CyXTYLfb0dfXl/frlgJUziMQCJS8UwASchdtbW3FXkZJUrFOgcb0PR4PJiYmsjoFjuOg0WjUHWImMk1jS252c7lcKaWrhZS8cDqdOSfDjQXieLVvGoKkoM8r5v35C42ChOCdMo8nS5dpSE/uLkamYaWw2WwIhUJlUc+/FCwWS9kkm1tbW/HII48UexklScU7BZoTyLVrZxgG4XA467XoNLbkSqbkHIPNZgMhJEXeumCSF2YLjk2KmHhjEBaTAaevsaLOOptf6J4IYSIUh7BC5aX5hiGAKAPrHPoVk2lYKWg9fyAQSJFtrxQsFgsGBweLvYwF0dLSgm9961vVCqQMlNanpgA4nc45Bj0d2pmcLfeQPI2NXkMQBPWYnNwIR51CviUvCCF4pG0M97w4gCEfC+VoL1iGgUnH49ItTnxqTxPcZi0efOMUxDJ1CAAgA2BAYAyexLPP9qyITMNKwTCMmmyuRKdQTsnmlpYWjI2NYWxsLCUsXKWCnQI9KXAcpxrsTE6BEAJJktQmtGw9AHS2M61kSq9GcrvdOHXqFBobGwHkX/Li5wcGcddf+iAqJDEtDYkyzkBMxGPtYzg+EsRX37Edr/T7SrrCaC4JiexkOAAXtWzE+vqaFZFpWEmoU6hEjEajeurOtgErFaxWKzZs2ID29vaqU0ijtN15nqAGPROiKIIQoiaLc13D5/OpSelMqqrT09NqpzOQP8mLrvEQfvxCPySFQMsl5hNwTGIimYZjwDIMeiYj+PiDRxAsckPaXAhyy+wxc27xHINgTILBYKgohwAknML09HTB5mMXE9rZXA55BYZhqsnmLFSkU6CxfIrH44HP50sx2BRBEMCyLGpqajAxMZH1w0qnsU1NTak/l3xSSK50oiRLXiyHn704AEFWoGFnSl2pnSSYmWyWMLtjQWFZz7N0chl+eq6Zn4T+EsBzLPZ3e+f/gTLEarVCFEXE4/H5H1yGlItTAKCWpVZJpSKdQjqZDDaFDtdxOp2IxWIpUtjJJEtaAHPDR8BsI1z6zy3XKRwcmE44gGR1UGY2RMYwjGqTC7f/zI/hn+8Z3GYddDyL8elIRe6mOY6D2Wyu2BBSOc1WaGlpUSv5qsxSkU4h0x/Z4/FkDA9Rp8DzvJpMzgZ1CoqiqMqVme6n5EvyIirISI+iMGBUPdP8dSrPF+YpbCiHYxhYZvSMNIyc1UGXOzSEVInQk0I5GNrW1lYcP348YwRhNVORTiETNK+Q/mZNHsM5X17B5XIhEomoO6H0k4LT6VTLJim0CmM5HxKzjk+Y66RLqCcQgkVUGy0uvr/SSArBeCiOcFzGwUke/3R/O37x0gBO+qJFXVe+sdlsZbObXiwmkwmEkLJw6E1NTZAkCT09PXm53l133YWNGzdCr9fj/PPPx8GDB/Ny3ZWmIp1CJgNMS1PT+xGSE8Y0/JOtw5lKWkxMTGScv8DzPBwOx5zTwnKTpZdtdYEBZobZYzZURBjEJSXNzBc+zFNI/FEZEiGIKwwGfXHc+8pJ/PNvD+OJo+PFXlreoE6hEM2NxYZlWZjN5rLIK2g0Gmzbti0vyeYHHngAt912G770pS/hzTffxM6dO7F3716Mj5ff+7YinUImaGlqehVS8knBbDZDq9XC5/NlvY7H48HU1FRWVVWXy5Uxd7HYk4IoivD7/RgdHcUVawA9x0BSCERRgazIEGUZEslk/kvf8M8HzzKwGzSwaAhqLTpERQXff7YHr/Vn/7uUE0ajESzLloXhXAp0tkKpkzxwZ7l873vfwz/90z/hQx/6EJqbm3H33XfDaDTil7/8ZR5WurJUZJ9CNgPsdrsxPj6eopoqCIJaU52cTM42NtHtdqOnpyer0qXb7UZfX5/awJNN8iKbTAP9f7JMg81gwD+f68BPX59GTFQgKwwqb4+ZgGUS4bCYDJAZpVmXSYOxYBy/f+MUzt1Y/k1fyU1stNmxkrBarRgbGyv2MhZES0sLXn/99WVdQxAEvPHGG/j85z+vfo9lWezZswcvv/zycpe44lSkU8hGJtXU5JMCgDnzEdKxWq05Q0L0/uSuVYZhMDIyglAopDqAxco0nHYasHOLD794aRCv9PugqHmEuc1f5Q4B4A2L0LEspIgAm1EHi45H23AAg1NRrHcair3EZVPJeQWLxYKurq4FdfMTQiAqZLbceoVpbW3FfffdtyzlgcnJSciyPEdfrba2FidOnMjHMleUinQK2U4KtDQ1uXM53Slkmo+QDJVdyJZ3SD5tUKcgSRKOHz+OtWvXLkum4byNDtRYdHjPPQchyoCGA2S58k4NskIQEWTEGABxEVNRGRYdB5Zl4A0LFeMURkZGir2MgmA2myHLMmKxWMbPEACc9EXxpyNjeObEJEJxCUYthyu2uXFtay02uowrtlY6cCccDsNsNq/Y85YyFZlTyBW/p6qplPQmNI1GA7vdnrMKSafTQRSzK5BSWQ2KJElgWRbbtm3D+vXr4fF4YDKZlqQP8/DhEUgywDMAx7BlJmkxPzSXzrKJzmaWARRC4IuICMVlaLjKOBXZbDZEo1EIQrEaDgsHx3EwmUxzcibhuIT9XV788PlefOjXb+E3B4fgDQuQFYLpqIjfvz6Mfb9vw/MdKzfBrb6+Hna7fVl5BbfbDY7j5oTMylVXqSKdQi6SS1PpeMX5+g3S4XkegiBk/UC73W74/X71fjqmcdn9CqKMZ09MJgwjw0AhpT1JbSlQk88xDJgZhSeOYUAACJKCUxVSnkrzRauhiU2QFPz8pQH8/a/exL/933Hc8+IATvnjmAqLmAgJGA/G4Y9K0HIMQnEZ//50NzrGQiuyTjpw58iRI0u+hlarxdlnn41nn31W/Z6iKHj22Wexa9eufCxzRalIp5DL+CaXpkqSBEJIVqeQrWRQURTodLqMVUZAYgqV2WxWJTFo9/Nyu5sDUQkxSYFFnzjZ0F01U0GugSCRbGZn4rsKYRCTFCgkEVb60uOd+NmLA5gMlf8Ou5LF8WgTmyAr+NoTnfj1KycRiksAiDoEkCDxN5UUAkFSMBESERYkBGISHmkbXbG1trS0oK2tDYIgoKurC08++eSiJcBvu+02/OxnP8N9992H48eP45ZbbkE4HMaHPvShAq26cFScU5jP6FIZ7ImJCQiCAIZh5shlW61WcByXtetUFEVYrdYFdT/Tx+fDKeg0LFiGgY5noNewUNRrVUZIhcLMhIzikgKaT084CiAkyPj1qyfxif85guHp8j41VLJToCeFp45N4K9dXlgNGtgNGvhjkloaQWW8FEJ1rxjEJYKIKOMvnV5ExfyLO4qiiEAggLGxMRw8eBDvf//78cQTT+BnP/sZDAYDWltb8fGPf3zR4aT3vve9+O53v4s77rgDZ5xxBg4dOoQnn3wy63CvUqbiEs1DQ0MQBGHOaMxkPB4PxsfHYbfbM07gSk4WO53OOT8vCALcbjcGBwezVi0kT2uj5aXA8jqb7QYNWhsseH1gGg02PYanYwgLcgWdExIoCqAwRDUe7IwirEIS8h5usxb93gi++WQXfvTe09RTRblhs9nQ3d1dkYNezGYzYnEB/3f4FAgAg4aDIMqQZw7f6R36skKg4RlwLANRUhAWJIRiEgyaxU2oy1XqHY1G1VCu0WiEKIpwu9245ppr8Lvf/Q6dnZ1Ys2bNkqfi3Xrrrbj11luX9LOlRMU5BVmWcfLkyZxOwe1248SJE4jFYlmb0NxuN/r7+7F169Y594miCKfTid7e3qzDe+i0tnA4rJ4U6HyF5RiBd55eh7dO+hETFax3GnDSF0U4XlmOgSAxU4FhE7kFhShgkJhkp9ew4FkWdoMGJ0ZDaB8OYOfa8qz1N5vNUBSlIitfeJ4H0RrRNxmGSctDIQSn/NlH3soE0JBE6EICEJcUGLWZjbMsyyml3clGP1upd01NjVrxl1xYcvHFFyMYDOKnP/0peJ6vyDGpi6XinILH41Hjg9kMvslkgsFggM/ny+kU2traEI/HodPp1O8TQiCKInQ63ZxpbMlwHKdKXtDdST4kLy5tcuL6M+vxP2+eQlSU4TJpICtk5qhdObtNmQBaJqEMy8iMGiqzGRNvWR3Pwh+V8Eqfr2ydAsuysFqt8Pv9FecUAMBgNEGWg2A0DCZDAqJS7uJpSSEJGXgCeMxaSLEwRqbm7vjj8ThYlk2ZyOd2u9WvF1vqbTabsXHjRrS3t6OhoWG5L7vsqTinoNfrYbFYMDk5mfMPTCuEstVRa7Va2Gw2TE5OYs2aNer3aXJao9Fk7JBOfw6v1wudTqc6FppXWKpzYBgGt1y6EZvdJjx0aAS9kxGYdTxAFMSkQkpnrzyCAmhnwkiKQmDS8bDoEm9Z+vuLCKU2VGhx0LxC8nusUmhw2WDgAwjHJfijYkLpN8cbVFJoEprAhRAOHTqU0tPjcDjUrzOFfZcKlbs4fPgw9u7dm5drljMV5xQIIWovQi6n4PF4cOrUKXXOciZoXiH5AyuKopqcdrvd6OjogCzLGY+dtDva5XLBbDZnlbxYLCzD4JqWGuxt9qBvMoKwICPgHcOYdwpf+mukoprZBJmAZwEDDzTY9Gr+gBACMIDdqJnnCqWNzWZDb29vsZexLCRFQVxSYNBwKfkdu9WMM5wKnhpSICsELBIzuBNkLpKQCWDUcNh3zWk4Z+PcfF6hWG5ZaiVRUU6BEAJZluF2u/Hmm2/m3JE7nU5IkpRzt5HpOsmVREajUZ3t7PF45vy82WwGz/OIRqNqHDN5Gttydzosw6DRk9BgCliA3/WchEXPIxCrrBwDxzKwahQkzxiKCDJ0PItLm1zFW1gesNlsCIVCKdIr5UL7sB+PHB7Biz1TEGUFeg44r16Ds9wEZsQgiiLOcgIvnOIRJQDHAayMmU1L9vc+yzKwGTOHdQtFS0sLnn766YpM+i+WiixJpbv/XINMOI6DRqPJORbRZrOBEJJSNpjcAZ0+jS0dhmHgcrkQj8fVD3y+Bu+kY7FYEJMTSqosy0DHMdDOdASXO4JE4I0xiIuJYShxSUYwLmHXJofqFMsVvV4PnU5XsjpINBE+MTGBwcFBdHR04K233sK//89f8S+/eQOPHD6FUCQOWZIQiMl4sjeGn7QrYGq24LLLLkOtVY9rmt3gWAayMn94U8MmVHLvfeXkirw+SktLS3XgzgzltTWZB4Zh1AqfdP2hTLAsm3MYCMuy6nXsdjuAuWM43W43Ojs7s17D7XZjZGQkxZHQNeYThmFQ47CCQwg0dCspRG1wK2cIEvmFkUAcRm3ixHDeRgc+/bamYi8tL9C8Qqby55VAFMWsJZyxWAwsy6YkcU+JRjx5Mgxeq0WNUZuS1FUIwURIwPf2D+Ona12wWq04R6/Bcz2Jnf90VERcnHnvp+UYWAZY4zCAEOC1gWkM+aJY61gZnaumpsR7qaurC83NzSvynKVKRTkFIHECoCGkgYEBbNmyJetjCSHzHt3dbjdOnjypvmmSh/IAs9PYsgnouVwuVVKDko+5zZk4r9EDR1sQUWnWIcyT2ysLtBwDWSGISwTnb7Tg3WfW49ImF7R8ZRx0Cz2ekxCCWCw2x+DTr2kfTXJS1263q1/rdLqUkMp9Dx+HqAA1Ft2cUAvLMPCYtRgPCnjy6Dguq7OCBALYXmdG+3AAGpaBxDLgucRccfrelBQFRi0Pk5aDQoCJoIDeyciKOQWe59WBO1WnUGHQXQttHovFYtDr9RkfK0mSmhOgqqnp0OvQEtd0AT2e51UBvXXr1s35eepAQqEQXK5E/DufeYVk6mvcOMfdhSfjPMJC4uNW7g4BAGotWmg5BhOBKG7etRbnrmACciWw2WwYGBhY1vuB1u5n2/ETQlJ2+1arVZVpT6/dpwxPx/BKjx+CRFBj0eLsDXYEohLePDkNo45DTFIS4Uom0ZwmyQT+mIhgTIIgE/x4fz8OrTfjxIgfMUaLUEyCKCeqyTiWypgkNi9ajkOdVYdibWNoBVJbWxve9773rfjzlxIV5xRoeEaj0ahSFJka2SRJSgkzZXMKtMTV6/Wivr5+TvgImK1SyuQUqJrq9PQ0NmzYkLLOfDsFg8GAK9drMMUY8HzvygiKrQQjgTj0Gg4MGEQi5S1tkQmr1QpRFHNKTVPxxkwGPxKJQBAEcByXstv3eDzq13q9fsG1+xPBOH68vx8HeqcQEWSwDAOGSVR/7dnuQTguIybKqoEHqDTJzNdIlBCH4jKe6vIDIGCQ0KqijxckBRyb6GC2GXg4jRpouMT6wnEZBi274vmi1tbWshyKk28qzikAsyEkWpqaySlQBdOamhocP348p4GmRp86hfRmtfRpa8lQp+D1etXnKFSymWEYeNwuSH2lmbRcKoQAobgMBsBvXxsCrzPgvI121YiUOxzHwWw2Y3p6GoSQrDt+WZah1WpTmraS53PQqrjlMBkS8Ok/HkPPTCdyjUUHlmESCrX+GO7+a7/aG6Lh2BntIgJhRqSKARINaClXnSvZSAC4TFo4jBr11ICZa4XiEq7Y5sYae+YTfqFoaWnBz3/+81VfgVSRToGOwaRSFdmMtVarVauDckkN0O5multLPylkmrZGofFaWZZTJDHyIXmRif6oFn8dzC4nUI5QUTwC4LWhCDofOY6Wegu+fO02eCy6nD9bakiSlHUE65EjR8AwjGrwjUYj7HY76uvrVUdQaBmGX796Ej2TYbjNWvBJnxktz8Kq18AbFqEQYEa9PaHtlVTNkChwWNhzTYbicBhnNcFikgJ/VESdVYcPX7gevZNhPHl0HG3DAcgEaPIYsXdHDU5fay2I3lVLSwt6e3sRDAZz9i9VOhXpFOhu3GKxgGVZTE9Pz6nsoDkCqpo6OTmZ1Sk4HA5IkoRgMJgxfJRp2hqFOh+j0ZgiiZEPyYt0FELwk1e9qMQRnVQYTyGAWcuhbTiALz56At+/sXXRommFhAqyZdvtU8mT5DCP1WqF1WqF1+vF+eefX7Rd6nRExLMnJmHQcCkOAUgkgkf8UcgzDkAmgCwqy8oAyAQY8IZh1mtAAGhYBi31FnxyTyP+0jmJ+18fRlSQwbMsGAboGA3iqWMT2L3Vjc9c1Qgdn9+/e21tLdxuN9rb23HRRRfl9drlRMU6BboTpyGkTE6BGnc6eGfjxo0Zr8eyrDpNLZNToNcYHBycU+1EjUAmSYx85xUODwUw7I+j0hwCMCutLCsKFELgNGlxfDSEv3Z7cdWOzPmgQrEYQTaDwQCz2QyPx5MS5kknHA7nVN1dCTrHQwjHJVj1PELxhEppRJAhKER1BuksNwAqyARba0y4dIsLp6+x4rQ1Vjx0aBT3vXISGo5FbVKFEyEJWe2nT0xAp2HxmTyXJNOBO1WnUKEkl6b29PRg27ZtKfcnC+Z5PB50dHTMW5o6Ojo6pyQ1+f729vY5QnzUibhcrhRJjHxJXiTTOxlJOcpXEjIBGJI4ASmyDK0usbv805HxvDsFKnqYbbdPBdmSwzzLmb0NJOaHcxyHYDAIm604An9DviiCMQneiJjVCSydzKdXQoCeiQg+cWUjmustiIoyfvf6MAAGNsPcE7lJy0NRgGeOT+B9Z6/J+7zu5uZmtLW15fWa5UbFOgX6oXQ6nWhra5vTR5BsvI1GI/R6/bylqTQhnWmnp9PpUqqUKNQpmEwmaLXaFEmMfJem0qEllUYidg3ICqDhAHZGKEHHsRha4qAdRVFy6u7TXFByUtfhcKhfp9fuL/81Mqpi6ko6hcmQgKeOj+PhQ6PoGA9BlAu1qcj8u1IABGIinjw2juZ6C17t82EiOJtryIRZx2E8KOC5jgncvGt9XlfZ0tKCP/zhD3m9ZrlRsU6BYRg1KWe32zExMYH162ffQIIgqP0LDMPA4/HkLE2ljiMajeaU256YmEhxCtS40LyD1+stmFNo8pig41nIggxSQXmF5PGNJi0HRZYBECiEQJujAkmSpKwlnLFYIhmfrLtvs9lQV1enOoKV1iJa6Ulsh4f8+OqfOjEWiCEQk5Mm+a0sUVHBXzoncduVjZiYGbOaq7KMYRgQEIwH8z+StbW1FXfccUfG4pTVQsU6BSBxWpAkSU0CpzuF5AqD5JNANgNtt9vVtv9MJFcpJQvoUdlsqppKSXYK+aC1wYKttWYcHvIDpJImNydgANiMWsixCGRZgSArOGedBdPT0xl3/LR2P3m3X1NTo369mNr9lcBut2NkZCTnYyaCcYwG4tBwLDa7jUvu6j7lj+HLj3diPBgHg8S8Ci6p12AlIQCGfDGMB+PQciwIMOdzKMq0UY6Blk98X1eAjvbm5mb4fD6cOnUq56CuSqbinQI9BfT29qZ4//TY/0JKUy0WC0ZGRrI6DlqllFx6KooiTCaT+hyHDx9O6bKmTiFfqqmfeVsj/uX37ZiOiMu6VinCMQDkhHT5uD8CBgSu8ADa2kbV3T6N7ydP2SqXmnOr1ao6s/TT6InREH7/+jBe7vNBkBL6T26zFm8/rRY3nNWw6Aqs37xyEn2TYRBADRkVMx2lEIKnjk/g4kYn9DyLsCDDrOMRFiT4IiIiggxCEqFEnmWg4VjsXJv/slGj0YhNmzbh8OHDq9YplM42qQDQnbjJZIJGo8HU1JR6X3rCOHlSWjZoTiIcDme8n2VZtbw1+XloDiK5yzp9jfli51ob/uu9rVjv0JV18IgBDX4R0BoXPZ+ogQ+Iid/bRy7aiH94xxW49NJLce6556KlpQWbN29GfX09bDZbXgexrAQ095QeQnqt34dP/fEonu2YSJyWDDxMOh4TIQE/f2kQtz9yYkHDhqKijGMjQfzf4RHc//qwWpSwckq62b2OpBDc9/IgXCYtzt1gRyguwRcWEnPIZxoXaZNbXFQQFWS82u/Le8iLyl20t7fn9brlRMU7BZpXoPF+SqbdGC1fzYYsy9BoNDkdR7qUNs0pZLufrjOf3c1nrrPj0X85H/+vmaDJbYCm5PWz5752qo/DMQn3wCKhMyWDQ6PbhL/dwuGmizZV3Ezd9LxCICbiW3/uRiAqotaig0XPQ8Ox0PEsXCYtbAYNXuv34b9fzS41HYxJ+MVLA/iHX72JWx9ox5ce64Agz045W7lUQu734VhQwBcfPYGPXrIB9VY9RoNxyApRG+WoTpKGZ+E2a/Hk0XE8eXQ876tc7QN3KtopAKkhJGrwFUWBJElznILb7YbP54MsZ9510eT0fE5hampK1WXPJLVNJS+Aws1X4DkOF2224z//pg7/cUMLttcWcwbw7G4/M5mNBVV65VkGN+1ah29d14wf3NiKn3/gDGw3x1SpkoUSikt4rmMS/3d4FM92TCAQK70QW7pTeL7Di8mwALc5c7WTjmeh5Tk8cXQcofjcWQD+qIjPPnQM970yhOmICJ5l1HLTROioNEQTCQH0GhZvnfTjjUE/zt1og5ZjEuudqTwDAIuOw1q7Hk6TFgoB/q9tNO+nBXpSKISScTlQ0TkFYLY01eFwIBqNIhwOq7vL9NJSk8kEnU4Hr9ebsQqJ5gfGx8ezjuA0mUwp5a3pToEO7gkEAmrpYaEkL1wuF6amprDnzI24bIsT7/vFmzjpi4JBomlIISSPJYiZxytm/97Cr3rldvec2QlUK6i2tnbeawiygl+/chKPto/BN5NrYQBYDTz+pqUWH75wXd67Y5eKzWZDZ2en+l54pW8KICRFHygdi47DVERE+3AAuzanNmn+7KUBtJ8KwGnSQsux8IaFxAkaBAWrPl0CBAmlVYYBHm0bRVSQ4TBqYTVoEJcSnkvLsylVSWYdj97JCIanY1iXR4nt1tZWdHR0ZO1JqnQq/qRAQ0g8z6vxftrNnF55klyamglRFGE0GtV+g2zQEBEhZE5DXHJ3dPLzFiL27XQ64fP5oCgKNByH68+sh17Dod6mR5PHhK01Zhg0id/B/M++kN1+/l8DA2DnmrkJRYfDAZ/PN+/PywrBnX/uxq9fHUI4LsFl0qLWooPLpEVMUPC714bx1T91QpBLY7K1yWQCIUTNW4UFGew84T+WZUAIEE8THfKGBTzXkZCtoKW7ZGaGQSk5BIokKzBqOJzyxxAUZHBs4qRg0iZyKOllqhzLQJYVHB8J4vevD+O/Xx3Cn49lPjEths2bN4Nl2ZzDsyqZincKQMIQE0LUvEImUTsKfUymoyPdOeRyHPQaVBIDmHsiSXcKQGEG75jNZrAsq456fPtptdhWY8JkKI5AJI5YPAb9zAZ5NqCQbQ2FMfrzQQD86eg4Xuz2Qkwy3A6HY0GDaf7SNYlnOyZg0fFwGLXgZwwsxzKwGzWwGXi81DOFp49nziUpJLvEQyFgWTZl6E6dVYf5mt4FSQHPMXCZUne1b530IxSTYNHNbko0HAOlqF3v2Z87LhMQEEiyArOWS5wQchCOS/DHJHz7qW78eH8/fvHSAL7xZBf+4Vdv4vevDy85rMRxHLZv347Dhw8v6uf279+Pd7zjHWhoaADDMHj44YdT7ieE4I477kB9fT0MBgP27NmDrq6uJa2xkKwap0BPAVNTU4jFYlmPhU6nU+10TYc6k1xzmYGE0Y9GowgGgynJborb7cb09LSad8hnvwKdsuXz+TAyMgKtVovjx4/j4MGDePOVl3CNcwKNJgnRuAhvWIKc8pzFMfzz0TkWwu2PnsCHfn0o0YOBRE0/FSjMxePtY1AIYNRmDg/pNRwISYQs6O9flBU81zGJT/7vUbz9x6/iHT95Fbc+0IYnjo7Pa6jygc1mUx35Fds84FgGMTH78wZiEja7jWhpSJV0pyeH5JOGQcuVRA4hE6KsYHg6Dm9YxKAviqmIiPFgHFKGU1xckhP3zfQuuIwaeGZOgMGYhLv/2o9fHhhc0jqWWoEUDoexc+dO3HXXXRnv/853voMf/vCHuPvuu/Hqq6/CZDJh7969aiNlqVDxOQVg1uhSeYLp6emsToHneTgcDkxMTKj9BRR6UnA4HIhEIohEIjAajRmvQaexZaqTpzX0ybIai+luVhQl55StZEE2nucRj8exadMmtZb/Oo5Dz2QEL/f6EIpLeObEJLrGQyUZUgAS9fN6nsOgL4rbHzmBO9/djOZ6C4xGI6anp9UO8XTikozjoyEY56nhN2o59Hkj8Mck6HkWX3+iEy/2TEEhgIFnAQZoHw6ifTiIp4+P4ytv3w6LvnAfHZvNhp6eHgDAuRvsOG2NBW+d9MPJMCnNaoQQTEcl8CyD952zZo6ctNukBcsmZiHQnwvHij2YPvt7W5IJGAawGzQw63hE4jImQwJCcQlr7Ho17yNIMvomI4n3q0IwHozDG2Zg0XFwmrRwmrTwR0X84c1TuHKbB5vccz+j89HS0oIXX3xxUT9zzTXX4Jprrsl4HyEEP/jBD3D77bfjXe96FwDg17/+NWpra/Hwww+X1LS3VeMUOI4DIQQejwfT09M59dJpeChdNZUmjZONfnKXdDI0DJVLYC9ZViP9tJBNdz/TMPV0QTa9Xq+eTqLRKF566SW43e6UtWypMWNLTaIiqbXBgs8/fBy+aLENRmZkhWAiFMcGp0Gtzf/eDS2w2+05nYKkzIh9zHP4oZLcskJw91/7sb97ClY9n9IQZtUndt6vDUzjP57pwZffvi3HFZeHzWZLmR3+pWu34UuPdaB9OAACQMuxM0UCCoxaDh+5cCP2bJ/7OzhzvQ0NNj1O+WPwmBNd9cFlxtsLCU02u806yITAoucxFRERFRV0T0Rg0LDgWSAsKIn54wwSs56RCPP5oglV1zUOA6x6HmPBOP58bBz/79KNi15La2sr7r777rwVf/T19WF0dBR79uxRv2ez2XD++efj5ZdfrjqFYpCcVxgeHlbnJWfC7Xajs7MzpcIofcAOdRy5nEJPT0/OwT0nTpxQbzMMg7GxMfT3988ryKbV6XFsPIaeyQggA5u0RjSttWesUDEYDACvwx9f78d4jIM/JmKdw4Brmmtg1HG4e38/nj0xiXiJJFozQZAwyOG4DIueR/twAN0TYTgcDgwNDWX9OaOGQ61Fi4GpKMy67G/1qKjAY9ZClBU8dWwCep7N2CGs41mYdTwO9E6h3xvBRtfid6ALQafTQa/Xw+/3w+VywWXS4nvXt+DFnin8+dg4Bqei0PIszttox9XNNVnHVmo5Fu87Zw2++0wPTvoSJ8iwWLp/ZwCICjL6vBHIMw6dZxMbJZkk/k7JJKa+0X4WgCUEcVnBWCCOtXY9OJbBkVNLm0LY0tKCgYGBlCrB5TA6OgoAc6rlamtr1ftKhVXlFFiWhcPhyNqHQKGlqcmKprIsgxCihp2o0c8mnEWnsWXbZTidTvUEQENQk5OTMJlMaG5uhtFozHjKeH1gGne9cAL93qiaSGMZBuudBtxy6UZcsCl1yM+fjozhh4cIJiJDEBWoP/MfT3fDZdYiHE+UwgoLHZdVBOiHPxBLhBHGYnH0eyO4aIMDx44dy1oezDAMrm2tw3+90AdRVjKKrEkKgSQTvP20WrzaN42QIMNjzl6GaNJyGA8J2N/lLZhTAGbzCnTzouVZXLHNjSu2uRd8DUIIooIMSUmMuCwHVXW6AQCgdjFnC2sqBFBmmvB4LvFZ45hE53ZcWt4AoJqaGtTU1KCtrQ2XXHLJEq9SnqyKRDMwO3iHZVnwPJ8zuZPe7AbMznSmhtpisYDn+axlkQzDwGg0qsnkdGgIyuv1qt+TJAlWq1W9djqvD0zjjsdOoGciAouOQ41Fh5qZLte+yTC+/HgHDvTMlsr+6cgYvvtMD0bDCuJSYufFMYlwiagAowEBwbiEaI4kZilAP9hhQVYrkBgmoXCq1WpzKote3eLBFo8J3rCAmCinJPPjkozJUBwbXQZc21qL6ag4M+Ete7iAYRgwSDSFFZLkCqSl8viRcdzz4gCMGhYbnAa4jHyJlBHMNdUMUsN81KDnmg9CyyKkpCFAVGY9POMMt9YsrWmTJpvzNVuhrq4OADA2Npby/bGxMfW+UmHVOAVgtgqJZVmEQqGcj02vMKL5BLrzTx7BmQ29Xp+zOiaTJAbP85nLYWUF//l8L0IxGTUWLXRJ4Q0dz6LGokNUkPHDv/RBkBSE4xJ+9tIAAlFx5ihOoCgEojJ3hq5CyuONICkE/d4oOJbBFo8ZDMPM269g1WvwzXftwM41NoQFGWPBOEYDMYwGYgjGZDTXWfCt63bAadLCpE3knXKVMhKScK7GHOGofEA7m5dakSZICn7z6knIhMBh1MKo5VFnM8Bjyj6nYOXIMGxnntuZSH7MrFNIXDsmytDzHPY2L30AUz41kDZt2oS6ujo8++yz6vcCgQBeffVV7Nq1Ky/PkS9WTfgImO1uVhQlJZGXCRreCYfDMJlMGcdwut1u9Pb2zpnqRuF5HoIgZNRZoj/f19enhqByOYXXBqYx5IvCYcys+skwibr7EX8ML/dOISLKmAgKEOWEVASZ+SBmOlITAELpRo9UWCbhGOKiApNudlbG+Hhu/Ztaqw4/fG8r3jrpx4vdU5iOirDqeVzU6MTZ62dzMRdscuBnL3Fq7iITUVGBlmOwKy1Ml28sFos6DyJThdt8HOz3YTQQhy1tWI1BxwPh0pP3WCp0agghM3M3Zpy6rBBc01KDbbWZ8y3zEQqFYDab8fDDD+OHP/whent70dvbiw9+8IO44YYbsv5MsjR+X18fDh06BKfTifXr1+PjH/84vv71r2PLli3YtGkTvvjFL6KhoQHXXXfdktZYKFaVU6DGVJIkGI1GeL3erDIJtAOalqZmannPJIWdjKIo0Ol0mJycRENDw5z7ad7B7/erstv0OdKrHrrGw1BmWv2zoeVYyArBQ4dHEYonKjHUChz1l4DSELtZCjOvn2EZPH18Au87Zw0cDgc6OzvnHYrCMgzOXm/H2evtWR+z1mHAhZudeGZmBnD6AB9JVhCIiThngx076gqrJcVxHCwWC/x+/5KcwmggrlYqJSOWYO6IVn8t5VCU/HaWZprfWIbBe89pwMd2b8qa06OFI5lmbAeDQbzrXe8Cz/OQJAnPPPMMGhsb8ba3vQ0tLS1Z1/L666/j8ssvV2/fdtttAICbbroJ9957Lz7zmc8gHA7jox/9KKanp3HxxRfjySefzGg7ismqcwp0JjItGc2lnZNcmpqpC1qr1cJms2FycjKj9rokSbBYLFmdAsMwanczdQrZ9P/n685UCMFkSIA/KuLFmbxCrAQNwFJhABh1HGotOgTjEp45kXAKJpMJHMchEAjAbrcv+3n+9YrNGA/GceRUECwDGLWJOHxETDjYLTVmfH7vlhWR5KYhpORJfgtFk2VYTSmWpGpYBuKM+GEuaL4gGXrKUxQCo44FIcDfnbMGn7iyEbIsIxQKpRj89H4enU6nVvcZDAa1tPvEiROw2Wyor6/Hj370I2zYsGHe17F79+6c4T6GYfDVr34VX/3qV+e9VjFZVU4BSOzAtm3bBovFoiohZvuAJ5emZgofAbOOI5NTEEURNTU1GBwczPo8brcbQ0NDaGpqUsNHmZrYNjgTu8X0KhoCglBMwlgwDmEmmWzgWRi0XIrGvipjQUoj1bgYdByDOpseJi0HhmEQk5RZYTuGUfsV8uEU7AYNvvPuZjzSNobHj4xhLBgHAHjMWlzbWot37qyD3bAycXmbzYbBwaV15e5ca4WOZxERZJhm8h+irJTQRmH2/JooPU3kuqg2UzrZ3rWEEChIyHfoWWDXGi0uMHuxf/8w4vF41n4e+i+b9LrdbgchBI2NjWhra1uQU6gUVp1T0Ol02LhxI2RZhiRJCAaDWRvZkktTsykmut1uDAwMZAxfiKIIm80GWZazPo/b7cbRo0cRi8VACFFPCkqa4M2Fmx2otegwHozDY0k0IomyglP+GCKCnLLL8sckREUFHJMufFZ+DoFlgLVOA/RJKqaSTFL0fBwOB6ampuY0Gy4Vk47H3527BjeeXY/JkABCAJdZm3MedCGw2WwIBoNZS25zsdFlxFnrbDjQOwW9hgPHMolQYsmEDhk19CPIBDzLQMsxqLXqEBNljCXNX1ZDRDNrZ2e+J898gwOD090srtthwekNFphMJtXoL2fQEsMwaG5uRnt7O97xjncs/aWWGeVQdJJXaPURrR7KNVQn+THZRPRoY0umskg6nzmTAB5Fr9fDZDKp99OTQroWkl7D4R8vXg8Nx2IyFIcgKRiejiEqKikfdNq/FpeVcvQBKbBM4m8QT2paSiQRlZR6fXpSyPtMCpZFnVWPeps+MTuYEExHRUyE4iuiqkplSoLB4JJ+/v+7fBPW2g2YCMUTsyOSLWsR4Vmg1sjApmMwM24Zep6B08hBkUQokoiZRmWwIGCZmcoi0PdE4sCr41k02A345nXN+PU/X4r3XHommpqaUF9fD7vdDp0u8wyKTCiKokYERFGEIAiIx+NQFAX33XdfAX4LpcuqOykAiRCSLMtwu904deoUGhsbsz7W4/HgxIkTMBqNGZ1Cel6AQmWzqYDe6OgoNm/enPE5XC4XvF4vOI5TO68zhZCu2lEDWQHu+Ws/hmdOCEDqx1whCcNZrvlkfmZHCySSwzMFJQASv9PJkAC7UYurmmdlHSwWCwghKbOx84lCCJ7rmMSj7WPoGA1BIQRmHY+9zR684/Q6NNgKkyhkGEbNKywlNLbGbsB/3NCCXx4YxEs9UwjEpJk+C1LU94aBZ8CCwMwTsASQAaw3KxiNEogMA52GwzUbTOj1iRgLiuA4JjG3OS4jEJegKAQmLY8bz6rHdWfUoylLR3cy9ORNCFFnl9B/9PuZOOOMMzA8PJy3114OrEqnQA2t2+3GsWPHsuYLgNnSVJZls4roeTweDA4OYsuWLer3aNMadQrHjx/PWgLrdrvR3t6uhghyqaZe01KDS5qc+LtfvIFBMZrS3KP6jyxxWfXOEj9C0LCXohB1DKM/KiIqyrAZNPj83ibUWWcNMcuysNvt8Pl8eXcKskLwH89044mjE5AVApOOg4Zl4Y+J+M3BITx9YhLfeOd2bCvQZLv0SWyLpcGmx+3XbMV4MI6OsRBe7ffhwTdOIRhfyYZF+m5MvO/CAkFUBLQcA52Ww3nrHfjBja2YCAmISwocxoQg3lRYwCNto/jT0XH4IiIMWg6bPUb8TWst3nFa3RzlW2rskx3AfIafftYy/QOA7du3z5HArnRWrVNgWRY6nQ5msxmTk5NZKzxoaWogEMjqOFwuF9rb21P6EURRVIX4MqmiJuN0OiGKYkKnKGmN9A2dfgTW8izikgICAp5LjFdUkkYh5N4FlrZDoE6OZ2c7ixkm8Zr3NtfgXTvrMu4MaQgpmxbVUvnft0bw+JFxmLScmrAFEsqqikIwHojhy4914Ocf2AmTNv8fJ5vNtqidalyScWwkhKggw2nSYmutCSzDqN3vFzU60TkWxks9U3k8LaQa/bmkfl9B4vQXlQhEouDK7R4wM2tMxmnS4uZd6/H+89bCGxLAgMBl0iZKWBUFgiAvaLdPjTwNGwPIWb6csganUx2fuxIVZ6XAqnUKySGkiYmJnGV/dO5yNqeg1+vnlJ6KoqjmB+g1klVRk+E4DiaTKUWTKZduEs8yiIqJ5LKWZQB2RgNmwb+B0kdSALuBxyeu3IxLmpyw6jVZZyIAiWTzyZMn8/rhFWQFfzw0ApZhUhwChWUTg22G/THs75rCNS1L757NhtVqRSwWQzweh06ny/o4QVbwwOun8Gj7KCaCAhSS2DA0uk147zkNuHJbItzGMgwu3+pC23AAkqIgsuCuxVyGf/G/79kswaxkSPLOPn2379AlHi+Jc+dyz7fbXw70s7+anMKqSzRTkgfv0NGZ2XC5XCCE5KwASZ/Glh6Smk8Sw2g0zhHqyxZCYhkGDqMmEWtnlv6RLHVkRcFVO9yos+pzOgQgYTxFUcw4HGmpHD0VxFggDmuO2Qn8TAJ6f1f2v+1y0Gg0MJlMOUNIgqzga3/qxM9eGsBkSIBVz8Nl0sKg4dAxFsI3n+hKTCJTFITDYTh4CRqWgEeyQ0g6amakMAOYeCah0RWKRBGPxxGPxyEIAiRJgiRJkGVZdRDJo3V5nodWq4VWq1Xl7HmeV/Ny+TLgTqcTsVhMHY+6Gli1ToHuJKxWKxRFUSddZYLu0HLpJSXPZQagJpkpyaqomTAYDBBFMeUDAGQ/Ejd5zGCYRHmmsOjpOOWx4wnGZXztT10Y8c8/mYrjuLyIyCUTikuQFaJq9meDZ1n4CziLYr68wqNtY9jf5YVVz8Fp5MExChRZAqtIsPAKBEHAXc924j8ffBa/f/oVTI+dhIEjCArArKstjNGfD0EmmAgJmI6IaoiHGn5q9JMNPzX6Cw3/LBeHwwGGYXJWKVYaq9opLLQ0lYraTU1NZX2M3W6HLMuqc6HhIwqd6JbttEDXkmzU6Bs/k2O4crsbmqRKnUrlqeMTeP8v38QvDwzO29U9nzieICuYCMbhDQsLmt9r1fPgOQbiPE5XVggcBRSaS3YKdLc/OTmJkydP4tjxE7j/QDdEQYAYiyAYDCESiUIQBLV3RqfhEBAZ/K5fh9/06fGzDg4BYSZfwzLIoZxScKi8RabdfinAcZw6UGu1sCpzCpTkvMLJkyfR1NSU8XE0FDQxMYEdO3ZkfAzLsuppwWazZRXQyzaYR5ZlGI1GTE5Owul0AsidV9i91QWdhkV8ntkQ5Y6kEIwF4/iPZ3rw24ND+PRVTbh8q0sdzZiM3W5PGVxE8YYFPNo2ij8dGU/IYzOJDvG3n1aLq1tqsjalNddbsMaux0lfVJ1clo4oK2AY4PKtC59zkAtCiBoGo3IMgUAAPp8P+/fvn9Ol65M08MYAq1ELo44Hy6SGTnwREeNhAbICEHF2VsQ4LWee6QguFgRArVULZ0mot2YmV59RJbKqnQLdjdCu4mzJPNqEFg6HVdXUTCT3PWRzCtkG84iiqDaxbd26Vf1+trnN/qgELc+BZeQlDk8p/dLUZAiAkUAcn3/oGHbUW3D5VjcMWg4NNj12bXbAoEns6GKxWIpA4UlfFJ9/+DgGpqLQcAwMmoQ8dsdYCB1jIbzc68OXrt0KfYZJaxqOxfVn1uMHz/UiGJPmKKfKCoE3LGCTy4iLG50Lfi10xnYmPZ5oNApZlqHValVpBloh19jYCJfLldKU1TUeAscFoNXw4GZ6XOSZUt64KGMsEFO72iUFGJqOJabKMYnSX0Jm3wXFOnPu3eHJOcOimFB59uS5J5XOqnYKNISk1WphtVoxOTmJNWvWzHkcLTWliqe5nALte0jPKQCJJiuO4zA9Pa2eBiiSJMFut2NsbEx9PnpSSJe8AIDBqSgYJCaBLa3mvDQ/hJlIbsSLywSHhgI4OhKETZ+o7nKbtfjA+WvxrtPrYDab0T8ygaPTPAanInji2DimI6LalUwx6XjERBkv9UzhFwcGse+yTRmf+52n12HIF8NDh0YwFkgYVJZlEBMVyArBOocBX3n79hSnQnf7mQx+phnbdNRqQ0ODWr6cXtRAO7bTFTXdZi20PIuoICEcT2wWJEUBAWZLlZN+jyBAWJAgK7Pd78WMQDIA3nladlHKUqB6UlhlpJemZnIKVPfIZrNhYmIiqzgW3dl5vV6IojhH8jh5ME8mp2AwGGCxWOD1etUS2eRGtuTTAsckmrvS59ZWIplsligTxCUFoqxgKizgi4924KFDI2jQs3j95T7ECQdRUhCIS2DBYHAqAh3PQZQTsiAanoVNz0PLM3jy2Dj+4by1sGUQumMZBvsu24iz19vw+JExvDnohwKgzqrD27bacckGE/ioF52dQylOIHm3nz5j22AwLEqCAZjNKyQLLyqKApuexxlrrXj8yDgICJgZTSGFzFUd5TkGHMOAJQBh5lclXQk2WwHrCgkMLhWn01l1CquJ5GTz4OBgxtAO1T3yeDzo6urKKVBGjX56ojn5/v7+/pQQETA7dY3+fC6nICkKrHoeskIqPtGci2BcBssk5JMVheD1AX9iTCcHrHfpMRkSE2EJQhCXgHjSaNS4rCASl6DjWcREBa8NTGPP9lnpjPTdfi2i+LtGBW+v4xCKRCELQfDCNEYHdXhlgscrIxImIwkF29PWuHDdGQ24qMkFfhkJ0+RafbPZjN7eXgiCkNKwJSsKhnyJed1kRtso2zuCS3ZCJfC2YRlg91xF+ZLD6XTmLDKpNFa9U0guTU0eeJMMPSmYTCZotVpMTU3B4/FkvJ7H48GRI0eg0WgyNru53W60tbXNyV9QJ0Lvp04g2SmE4hIebRvDY0cSDUqBZWvjl1deIRN0eDuFdsoO+2MQpcQI0kz2j8xo7sQkBYKsoG94HJ2sD9PBCN4YCmE0KICFgkY7jy0egxrWSd7td3njuO1/jmIkEE0SJZQx1jmFA/0BXLHVhdv/ZmvOTueFavLodDpEIhG1l4W+L9pHwhiYSowona9KSpCUebsRVpKWOhN2OgMl3xjmcrlw6NAhHD58WJ3A9q53vStrYUq5U3UKM3kFRVHUEFImp2AymVKa3bI5BYfDAUFIdF1mcgo0f+H1elMG79AcBJ3ylizuxjAMvKE4bn+sE8dHQ+AYBhqOyYMMcul+EJcDAYNgbCF5lplRpYRBz9g0JNGAx7simIoqYBgNWAZ4YZLDtqABn9qzGY0z8honfVH8tXMa33yiE4EM+RxFAUIxCc+cmIRBw+H2a7YsWZOHnlqpfEr6EKYXe6cxHZUWpBBbSoFGl0mD/3j3Nhx/6yAURVm0NHi+URQF8XhczflEo1F0dXXhjjvuQH9/P2KxGJ5//nk0NjZi8+bNKRPWKo1V7xSA1LxCX1/fnNBO8iwFt9uNjo6OrKWpHMepx81sshg0RESdAlVU5XkeLMuqMcxkp/D95/pwbCQIl0mrymezc+YlVEllYZo8BEBHQINnB0OIzwyhoVPLAAljgThe6/fh6uYaBOISjo+GMB6IIVs6hz5rVJDxfOckbjzDg41Ow5zHLUaThxrNdAPaMRZWq43oKyqltwRdD8MkwlfsjI7VF/Y2odZmxHFgSfMiloIoiqrBT0/8x2KJBsnkSWzr1q3DLbfcgvHxcTz00EM4cuRISZ9o8kXVKWD2g0iF7dJnLifPUnC5XGqcOdvsXGr0M+UU6P2HDh1Sj800JEAfT39+06ZERUzvRBivDfph1nHq1DVBTtQSMjkVUVcD+dHk6RjPLmOQGMep4I+HR6FhGZh1bFaHkIwCIBCT8FKvH40ec8rOf7Fk63CfCAkz30/cLqX3AstgRjeKg9ukBSEE/qiERo8Ju7e6VGeYPJt8OSiKohr4dKMfjUbVjVdy8t9isaQk/9P/Pi0tLfjrX/+6qmYqVJ0CZjVVAKgVRuvWrVPvT+45oJ3JuaqQaPgpmwGg3c90GhvtmE52CnQMKMdxeKXPh7gow2qe/eAkygkZcCyWmWwuh7xCrjVmHdSY477FPztFUgh80YWXAAuSAl9MyrkTlhSCqXDCuDtNWvDs3HWzLIupOPDokXGICuA0anHBJjsMGhZgljb0Pt+kT/pTCMAyBIaZsaCCrGCNQ4/br2lSNzc8z8/R/MoGTf7n2u0zDAO9Xq8aepvNhtraWvV2to1aLlabKF7VKczAsiwkSVJzBtQp0Ddi8k6GPiabU6BvvEAgMKeunD6Xy+XCxMQErFYrJClhNOgbjia0fT4f3G43woKc1N2cMHZ6DQcmKuVhZ1gKb/LFSS8vjMK8rsX+vhUg0SyWgVBcwmPt4/jT0XF1x+8xa/E3LTW4trVGbZbzRUT81wt9eO4oB9IxAJZJrMNh1ELLJrRqSqGvne5NGGBmDkaiYW48JMBm0ODvz23AdafXodY6W2DBcZw6ewSYbexLNvzJX8uyDI1Gk7Lbt9lsSy71XQhOpxORSASRSARmc2HmZpQSVacwQ3Jpam9vr1qaSlUa0xVPc5WmyrIMlmWzSmXTa4yMjKCxsXFOo1vyNDe32w2bIfFnonXoAGDR8/CGhBIaxD4fS9ntlz8MgHPW2+Z83xcR8YVHTuD4aAgsw6gqsMP+GH764gBe6PbiG+/YBp5j8W+PnMDxkRAYAG4jD8KwkBWCYExCWJDBswzkEkguESRUT/mZU4CiKJAIYNZyYEFg1HKosWgRj8dVQ68oCvr7+9HX14doNKrKeKTv9uvq6mAwGKDX65e0218OtKdocnKy6hRWE3QnbjabwfM8fD4fXC6XGtpJNtpmszlnaSotL83V8JI8jS1TTwOVxACAi5tc+NmLAwjHZZj1PBgkknZOkwan/PE8vPp8UIjdfvnjMGpw1rq5TuF7z/bi2GgITqNGDaUAieE9oqzg+EgI33uuD41uI06MhmA38pgKCBicjkNSEj0JzEzMfvEquYVDIgCjKOBYFgzLgJEJQBSAAPe/3AdPoBtaVlEb+6gkvcfjUZ0A7eYvFXieh91ux8TEBDZu3Fjs5RScqlOYgeYVCCHweDyYmJhQnUJ6GWByZ3I2p6DT6RAKhbJqJSVPY8s0ptPlcuHw4cOIxWJYYzfgim1uPNY+Cg3HqpIKzIob2tW5218O/7p745yQU583gtcG/DBruRSHQNFwLMx6Hq8NTOPIqSBYhsFoII6IAMyVrysdh0ARFQKZyODAqKuz6nn44wpIzWZc1lKnnrAPHz4Ml8uVc8hVKUAnsK0GSkOftkRgZwTFkqW0kyuPkqGOIxM0B5FLKhuYrTLKpJNEZTXoG/Ffr2jEhZudCMQkjAVjCMQkRIR8a/gvZNBKlYVi1XH45csncd1PX8f/+107HmkbQ0SQ8WrfNOKSnHNwkFGTSM6OBmLwRgSEBaWEzP/87xOFMBBJIq8QlYGIxABgESMcOC5xGhoLxBEQGYhS4WZR5AOGYVaVU6ieFJKgwzuSE0uZ1E6B3KWp1MjbbLacCWkaQsoWJ6VOY82aNTDpeHzjXc149sQ4Hm8fQ/dEGNolCeFXd/srAQOAYROJVgYEneNhdIz14olj47DqeYQFGaI/DpZlYNZxMOsSYUGZJHIFEUGGf04hwUr+ffI3flOeGaTDAOiZiOC+V4fwxNFxTEcliIKAOksUN5yjwzUtnoxqtaVA1SmsUpInPyXv8jPVUOcqTaWOxO12o7u7O2tCmo76i0QiGZ2Cy+VKmTus03DY21yD3Y02hCJRhMMR3PibDgRTxtYuNbZfDqWp5QGDRBWRO6mE2AIgKsp4tX96Rt4aaqOcP5rQaNLPnA5WRs5qZXNAHMNAJgS/f30YBi0HPc8mZMw5Bif9In70Qj8O9PnwlWu3zjt6tRisJqXUavgojfRpbNlOCsDcucwUmjhOTlpngjqWUCiU0Skk9zNQxsfHsX//frQfPoSR4ZNYZ03/AC11rGLVIeQDLcfAZdKkOAQgoVo6HhQgJyWJFTL7T1IIQvF8O4SVn7mcCQWJ18cyiaZLlkn0Yxi0HAwaFg49C+tMDuWnLw6uyJoWy3yh4Eqi6hTSoA1nHo8HXq8XsVgsp1yF1+ud03yTnJymIaBsuN1uRKPRjM9B+xmSfz4Wi8Hj8eCyyy7D+eefD4UzJBqYqhQVngXWOfQw63k4jXP/loGYhLikgGcZ1REsn/LJAREkQmkEQCAmQ5BkRAUZ01EFU1EZ4bgEnmXwXMckvGFhvsutOC6Xa9WEj6rWJA0aQjIajdBqtQiHw1lb8GlpavpJIPl0ke00QXG73YjH4/NKcVNopRIhBAohiEky3GYt5pktX6XA1Fh0+LtzGsBitk4/GX80kUwlWVRbM5Nu9NN/cuV2+/lEUgh6vVEMTscwHZMRFAgmwiKmIiJGAzG81FN6MtWraaZC1SmkkSxQ5vF4su7i6WOTK5UoydVELpcL4XAY0Wg04zUsFgsYhkE8nrnfwO12w+fzqV2fsiyrTgGEwG7QIC4pJTEwZbXCMsBZ66xosCW61+W0P4ZCCOKSDEUhEOf8nXLt9tONfvk5gGwoJPF7YmfGgvIzzfpxieDBt0ZKbk6Iy+Va9EyFb33rWzj33HNhsVhQU1OD6667Dh0dHSmPicVi2LdvH1wuF8xmM66//nqMjY3lc+mLpuoUMpA8uzkej+fsoMx0Ekg+KSRXIWWC9keEw5kF2YxGI/R6vfqGpA5HkiQIgoArtzgQjuVD7qLKUlEI4I8I2GiUYOABXyjRrRsOhxEMBREKhiAr2aSry3O3ny/UdDfDgGUTKqp9k1G82pc5D1csaPXRQiTKKS+88AL27duHV155BU8//TREUcRVV12V8ln/xCc+gUcffRQPPvggXnjhBZw6dQrvec97CvESFkzVKWSAnhQcDgcURck4I5nicrnU8lVKeofyfCEkhmFSksnp0NwFAFUnib459zZ7UELNn6sUgrcGp/GHV3txTi2LuEwgg4VOp4PJaALRzNW/qpJATvpoKYSA51hwLIMnjmXuASoWLpcLPp9vUU7hySefxM0334yWlhbs3LkT9957LwYHB/HGG28AAPx+P37xi1/ge9/7Hq644gqcffbZ+NWvfoUDBw7glVdeKdRLmZeqU8hAcmkqwzAIhUJZH0sriKjRp7MR0rWSvF5vVuciyzIikUjOEBK9Pg0fcRwHjUaDersRhhyTvarkg9wJXYOGg8Wow/4xHn97cTMu3uJGVGbgiymIigqmIuLKLbUMIZidKW3T89BrWHSMZf/MFQOXy4VQKKTOXVgKfr8fwKyW0htvvAFRFLFnzx71Mdu3b8f69evx8ssvL2/By6DqFLJAQ0iEEPWPmY3k7mZZlkEISXEKyaM+06ETuSwWS9bTBG2mS9aET/75KvlgMbH9WTgGWGvTwW7QICrKeKHTi69cuxUf270Rm90GCDKBICnguZUXJSkHCBINewpJSGG4zMufq1AIkkXxloKiKPj4xz+Oiy66CK2trQCA0dFRaLVa2O32lMfW1tZidHR0WetdDlWnkAWWZVUxvOnp6RR533So3rosy3NmIwDZE9IA1OvmKl1NzkvQ8FHytS36pNuLeI2ri4WUby7ut8cA2ODUQ8MnTpQalsHrg37oeBbv3lmHe95/Or5z3Xa4zVpV7rpKKiwD6Fmg3qZDvVUHBommvpZ6S7GXloJGo4HVas0qbTMf+/btw5EjR/D73/8+zyvLP1WnkAU6EY1lWVW4LhtmsxkajQY+ny+jgB6QPa9AnQK9P9vOP1knKT3xfd4Gu2rSVrfhWdlmLYOGhY5PddBiUpCcZRhsrTUjLimIS6v7L5MJDcdgo9MAlz4hrw0AkRkp8GtaMs9ALyZL7VW49dZb8dhjj+H555/H2rVr1e/X1dVBEARMT0+nPH5sbAx1dXXLXe6SqTqFLFCnQOUqcu0Q0jugs0lWBAKBOXkDauTtdjsIIQgEAhmfg+YlMl3/g+evhZZnZ+bgVvJpobSateIyQSA2e4IUZQXrnQZEBBl/6fTiocOjuP+1YUxHS1vwrRgwSOQPdDP6XbKiwB8VEYpLuHKbG+dusBd1fenQwpPFOAVCCG699VY89NBDeO6559TxupSzzz4bGo0Gzz77rPq9jo4ODA4OYteuXXlb+2KpZihz4HQ6cdFFF8Hv9+Po0aM5x/F5PB50dnbC4XBk7GvQ6XSwWq2qwB2FGvnk7mWbba7+vs1mU+fZJlcfAcA6hwF/e1Y97n99GCAAxzIJOYU8/A5WnvIR7GOQmCqmmamYAQA9z+KDvz6EqbAIAmC6mmSewxqbDpvdRvRORjEREiCKDCJhERa9BtftrMNHLlwHtgRL6ujnkxACn8+H3t5enH766VmbW/ft24f7778f//d//weLxaLmCeikOJvNho985CO47bbb4HQ6YbVa8bGPfQy7du3CBRdcsJIvLYWqU8gBy7LQaDSw2+0QRRGhUAgWS+ZYZ3Jpai5ZjHSnkFyp5Ha7cerUKTQ2Ns75WSrfOzY2lnH28yeu2IRAVMRznV7ERAVMicztnUvlDOPhmER37mRYgJZjYNVr8HynFzzHwmHgISoEvhKUbCgmep7Bp/Y0Ys9298xciWkc7+zBtk31uKp1DZym0kg0y7Kszn0+ePAgHn30UbS1taG9vR233347AoEA3G43XnvttayDd37yk58AAHbv3p3y/V/96le4+eabAQDf//73wbIsrr/+esTjcezduxc//vGPC/jK5qfqFHJAS1OpnPbExERWp0BLU6enp3MK6L355pspJ47kHIHb7caxY8cy5g2AhCjX2NgYNBrNnPJWDcfiy2/fhotPTOKhw6M42D8NgBSh07lyjP58SCTxaqOCjC1rrTg1HYNRm5DBBoBAXEQJe+cVh2eBNXYDLthkBwBschmxyWXEq+JJNDVZV9QhEEIgCII6+zl9FrQgCOpY0JGREcTjcaxduxYejwd33nknNm3alNUWJD/HfOj1etx1112466678vXSlk3VKcwDx3GQZRkejwcjIyPYvHlz1se63W4MDQ2p5Wvp2Gw2NW9AQ0TJOQKDwQCj0Qiv14va2to5P2+1WgEkdjGZwlg8y+DqZg98YQHHR4NwGbUACEYCcYSF+WY5L0Y6O39a++WMlgO0PA8Ny6DBqsPgVBQuXdJHquoLVDgWsOg1uOHMOtVpqvdxXM7qvqUiSdIcg5/8f0VRoNPp1NnPBoMBTqdT/ZqOBb3gggvw4Q9/GHfeeSd6enpw+umn532tpUTVKcxDsuTF8ePH55XS7urqyiqLQfMGExMTqlNIPxXQEFMmp0Cfd2pqCi6XK+uaOyfCYBlGHcKj41lEFjW5a/Xs9pcDw7Cw6XkQAJ3jYTVpStHwDJQS0/ApBhwDuExaXHd6Lf7unDVz7ud5fo7S8EJQFEUN8WTa7dPPFjXyBoMBLpdL/Vqv12cMxWbD5XLh4MGDi15nuVF1CvNAQ0h6vR4mkwlerzdruZjZbE6UJYrZk4sejwdDQ0NoamoCgIzdz8eOHcuY1KZJ5vmcArVDhChQFAIm4zG2uttfLhFBxiSAy5qc6J4Iz/mtadjV+XtkkJgroQAw6zi8bZsb1+2sQ2uDJeMJN9tJgRACURQzhnioM2AYJsXoU/E5avSzbeCWwmpRSq06hQVAQ0i07DSbU2AYBhqNJmcrvNvtxtGjR9UTB53nTHE4HIjH44hEIjCZTCk/Sx1Isjhe+u4oGo2CC4chCBKCQQEsy0CX0TitToOVb6KCjPZTQdRYtPClVRpFSzrhXxgYANe0uHH6GhvO32hHo9uYtWIPSIRCFUVBIBDAyZMn57yfFUWBVqtNCfHY7Xb1a51Ol/P6+YQqpeaqQqwEqk5hASRLabe1teV8U3Acl1MrKf3EIUlSyoznZC2lTE5Bp9MhEAjgxRdfVE8OdJdkMBjgdrtxvb0WB8YHQQCYDRoQQjAVjyImzZdXqLJYbHoecUnBSV8MkkIQlxQ1jESQaGAjpFzLgxfP355Vj9uv2aLeni/EI4oiGIYBz/NQFAUGgwEOhwMNDQ2qI8g2a2SlqTqFKioMw4BhGNhsNkiSlJIoTodWNUQikRRjn0zyiSNTpRHNK6TPfqaPPfvss0EIgdFoBM/zc96gawHceJaI+187hemICKueh1XPIRaqOoV8wgBwm7XQcAzGAnFoOQbTEREWPQ+DhgU/c0Lj2MTUsUrngrUG/P12HidOnFCNPm3WTE7omkwmuN1u9fbg4CAkScL27duL/Apy43Q6EQwGEY/HYTAYir2cglF1CguA5hUURVENdjanIEmSKm63fv36jI9JPnGk5xSAhFPo7u6GoigpiTDqFKxW67y7lQ/tWgcNx+LBN0cwGRYQE+er7qjcnU+hMGhZNZlvNWgQE2U0rzGjczyMYFyaUf5MnBE4FlCUSixIImABnOlm8I87CMLhMAwGA6xWa0qIJ1dCV6PRZFUILiVoHs/r9abIVVQaVaewQJLzCsPDwxkbzKiRpxVG2ZyCw+GAJEkIhUIZTwpmsxk8z8Pn86UklJNHcc4HyzD44PlrcW1rDV7o8uLxI+M4OJBb7bXK4qi16NSv9RoWobiEG8+sh8eiw/5uL6bCInomIzgxGoJJx2I6IiEiluORIXtRwianDrdd0YhLt7qX3IVcqJLUfKPVamE2mzE5OVl1ClVmS1NdLheOHTsGQRDmtLdT2WyPx4PBwcE5O/3ka9FmuExaRlRLaXJyMsUpyLK86Piqy6TFe86ox2RIqDqFPGLScjBoZv8WhMwUGvAsdtSZsaPODCAxcvLH+/vxaPsYRLlUzwlLK0H2mDR46J/PA7eIss5MLLUktRhQqYtKpiqIt0Do2Ey9Xp919gEtRbXb7eB5PqeyarLqaaayuUxS2tk6nReCSccjwzz5KkvEqk/9O4TiEsw6DttqUosDOJbBrZdtxKf3NELLs9DzTJH+DvlTkGUAGHgGX7p267IdAlA+JwUqNVN1ClVUWJYFISSramqyuN18Izg9Hg98Pp86SS0dl8uFYDCYUt6aSQxvoWytMcHAl0YVR7nDMalOQZIVxCQFu7e4Mko1MAyDtQ4DLHoea+0G2PT5q52fpfAKsgwADQs02HW487oduGxL9l6ZxUBDs+VA1SlUSYGWptJdfLpxTu52nk9u22g0Qq9PzO7N5BS0Wi1sNluKVO9yTgpnrbOhqcZUTSfnAZOOB8skRkgGYhImIyIa3SbcdEH2OLPDqAHPMhBkBYK8lLwCQW7Dn/95EelXv+HMOnz9ndvxv/94Ni7f5s7btXmeL4uTApBwCkuZqVBOVJ3CIkguTQUwZ7xmciiIqqZGo9Gs13M4HACQNU+QHkJKPlUstk6aYxl88srNqsRzlYXDIKErxTGJTl29hsV4SMBESADLMLim2YPvvHs7XDkE3eqsOpyx1gJ/VERUyLYrXojRL87f79wNNtzxN1vxNy01MOnym4pcDSeF/fv34x3veAcaGhrAMAwefvjhlPsJIbjjjjtQX18Pg8GAPXv2oKurK0+rXhzVRPMioHkFWpo6MTGRMl81OWlMJbdzVSHZbDacOnUq6/O53e4UVdXlnBQAwG3WwKJj4YuWxwewVDDrOHBMwhncunsjNjiNGAvGoeVYtNSbUTNThURlGbI1ajUijmdFFgpJOJi5eefScth0kp9Bw+JfL98038OXDG1cy1aYUUq4XC709fUt+ufC4TB27tyJD3/4w3jPe94z5/7vfOc7+OEPf4j77rsPmzZtwhe/+EXs3bsXx44dUyMKK0XVKSySZIG8gYEB/P/tnXmQG/XZ57/drfscXaPx+B58H4BjYmJ2i9jgFxtIXvzuC2+yqSQmB2x5DbUpUwFnYZ1gIAkJL3FCpUKyFUK8YbeSrdSm6gWWwgG/SaXslwRjm7GxzdqeYezBM5ZmRjOeQ0ere/8Qv3ZLo5ZHUrfUap5PlWs8kixpPK3n+3vuxYuvdG+WDstjeQUtUWDNbeVGWgDTp6qW7meuljOXJsHzPAQuX8YgEVrYeA7xgBP3fmouNi8LF8Youzik01MYHRjGQM8V48+29anHMrCa/TVr3PjL6GkcuTAGcAAnm7tvgeMAv9OG79+1FNfODhj2OuyaZutvzUw4HMbbb79d9b+7/fbbcfvtt5e9T5Zl7N27F4899hjuuusuAMC+ffsQj8fxhz/8AZ///Ofres/VQqJQJSyvEIlE0N3djUwmA6ezcFIsN9zu7NmzmicgSZIgCAISiURZUSjdxlavpyADyOalJuxYaF0CTg7bVruxPJCHlHoff/5zVtnbzYx+KBRS/u5yuSr+jrqiHpxNTkLgOQxPNH8rm8ABXqcAr8OGiNeGyWxhXpPXacMtS6L4wic7i0pvDXkPKlHQc4CdEdS6p7kSPT09GBgYwKZNm5TbgsEgbrzxRhw6dIhEweywEBJLBCcSCaWRpdRT8Pv9ZZvQGKIowuFwIJlMam5vUm9jq6f6CADmhVzI52UlLECUo7hm/97VXtyyqK1o3DKbs18L184O4MD7Q/A5bRidEpUcT16SIeqo1k4ByObL/545ADzP4VML2vDkZ5ci4rU3dZYP+0y1QrI5HA7rPv+IreksHZcfj8eV+xoJiUIN8DwPURSVRLBaFJjXAFxpQkskEpqi4Ha7MTw8rNmYxkZpZzIZyLJcV/hoQcQNcEAr9tTqy8z2QDsFYM2yhVg4P6TbK29cEsG+t/oxPJkFIEOWC93nLJGdzReG5/EoTjvb+cI7y300KkPgCsP4pkQJGfGK98ehkAPwuWzwOQQE3fbCkqWMCBmAQ+AxL+TCP63txGdWtZtmsFurJJuN8BTMBolCDainpn7wwQdKeKjcAp5YLIYzZ86UfR429dThcGB4eBixWGzaY9g2NlbeyhLdtSDJQMBlx2S2MGfGuh5D/UuCnAIHr1PA5FU31lVH0G3HNzYuwPf3n8XYlIi8JIP7KLIofaRVnFyI5ws8h1y+MFsoLxe6pm08lP6AU4MT4Pk8XDYedoHHdXMC+Or6OXDaBEiyjM6gC36XDbm8hPcHJzCZyyPotmNRzFPzSAqjaJWy1EgkgrGxMWSz2aIDYD2wUfyDg4OYNWuWcvvg4CCuv/56XV6jGkgUaoCVpvr9fvA8j1QqhXA4XFYUIpEIjh07hqmpqWmTFdnjmcdRThSAK6WpbDBfreEju8CjK+LGpcsZSCZPclbG2M1wHACHjYdN4OFx6h9Pv3lxBB6ngB/sP4fTg+PISTJ4jgPPASGXDS6bgFQ6h3ROAs/ho6mrAlZ3uLHOl8Lnb18JAOhPpXEhNQWe43BN1KO549gu8FjZWXmfcLNpJU8BKAzF6+zs1OU5Fy5ciI6ODrzxxhuKCIyNjeGtt97C9u3bdXmNaiBRqAEWA1V3N4fD4bIjKyqVpjJPwe/3V6xJjkajOH78uC5z5e9Y1Y7D50ch5mXwPAdZLsSzzScQzdsMx6Hwf9Luc2B1pzFVNzfMa8P/vPd6/LeX38efzxTGobS5bXDaeGTzMlw2Hl67DbetiOKmhSGs7PTDhVxR5cvsNhdmtzW2XNEoWsVTcDqd8Pl8SCQSVYnC+Ph4UcSgp6cHR48eRTgcxrx58/CNb3wDTz75JBYvXqyUpHZ2dmLr1q0G/BSVMXf9l4lhJ/ZYLKaEdrT2N5ebYwRc6VBmjW6Tk5NlXyscDiObzSphq3q4dWkU10S9hXi1LBdi2XU9Y63MZCxD40Mcdr4QurELPD67Oj5t77Ke2AQeT352KR75u2uwLO5FLl/okBYlGesWhPD9rcuw67ZFuHlxBBFvIblda+jQ7LSKpwAUmk6HhoYwPj6O7u5uvPrqq1f9N2+//TbWrFmDNWvWAAB27tyJNWvWYPfu3QCAhx9+GA8++CDuv/9+fPKTn8T4+Dhee+21hvcoAOQp1AzP88q003fffReTk5NlJ54ChbzCuXPnppWmssczb0Krp0EQBPh8PiXZXA8+pw17716BbfuOYfByplCiWtczamFsiEdvBK7wjkQJsAsctqyI4p5PzLrqv6v7dXkOf39tHHeuakfv0CQms3mEvHbMaZu+xIX7aIubFTHjpNTSrXG///3vceDAASQSCWzduhWXL1+G3+/HwoULceutt1bMMWzYsKHi747jOOzZswd79uwx4kepChKFGmHxfZvNhra2Nly6dAmyLJf1FLRKU9V9B8yb0Gp08/v9mJqa0sUozAq68H/+01p8++X3cfDcCNK5PGob89+8EI9eOAROCZ/JAIJuG3bcvAD3fGJWQ0eCCDyHa2LTe1XUsAOFFddBNqMklW1JLO0+Z18zmQw4joPL5VLWgq5evRoXLlzAXXfdhW9+85uIRCKW+12QKNSBekAeCyGVEwWt0lS1KGh5Ewy32w1RFHXr+vQ6bHjmP6xAT+Iy/scfj+CPH/JIpaWSgE5rnfZnAs8VKnjEPCAIHKI+BwSeQ9hjx98tj2HrtXEE3eZsoGLGhzU9WgmjcgrqsSOl40fS6TQkSYLD4dBsRnQ6ncr/Owv9XLhwAT6fD9GofkMBzQSJQh0w4xyLxXD27FkIgqB5aihXmqpOTPv9fgiCoFQylSIIAniex+joqDJIbyYwF7jch2JqagqiKOLfx4GM7MDrfUC6yINvPaNfCZ4D7DwHUSok2TctjeKpv18KSS54DGY/8ak9BashCELRmPiZks/ni0I8pSd+dvBiRt7tdhfth2YeQDWEQqGKE5BbHRKFOmAhJK/Xe9ULq1xpqjoHofYmyolCPp+Hy+XC0NBQkSgwF1j9oSjnAqtPQsFgEPF4XLntL3/5C760YSVe/80JcPlSb8FaZCUZAsdh3fwgHt2yCPYW2jzERMuqolAupyDLMjKZjGaIhxVgqMeMlF7feo/OiEQiOH/+vK7PaSZIFOpAPTU1EAhgbGxM87HqZPLcuXOVqZDqxHQ0GkVPTw+WLl067d+LogiPx4NLly5NS4CpXWD2QQiFQsrf1S5wKZIkQZZldEW9iHkd+HA0DRmw1HwkGw8IPA8bz2F+2I0vfLITty2PGT7TR2/U4SOrwCbL5nI5TE5Oore3t8jwp9NpyLIMp9OpXM8ejweRSEQRgnrGjtRCJBLBkSNHGvZ6jYZEoU7Yxej1ejEyMlLxscwTmDt3rhI/LR2g9+677yKdTk8rRcvn80qIiQ3KU5+Oao0xs9MZz/OYG3LhcqbwvsYzIsQWtj0cgKCLR17m8J07luCWZdFCIUALeQaltKqnkM/nNcOX6XRaGfEiyzLGx8fhdrvh9/uLQjxmmp7K5h9ZFRKFOmHJZqfTCUmSMDk5qYzELkWdTBZFUQk/MdiQPfU8JQbzFJYvX650VOsBO3UKgoCNS6I4fWkCMZ8DkuTAaFpE4nK25WYlCQDiHsDutCEncVgQ9cDGN29BjV6w37vZPAVJkiqGeHK5nLLfXO3JsoUyLpcLY2NjeP/997Fq1apm/zhXhc0/smIVGECiUDfMsLPu5EQigfnz55d9rLo01WazwWazTbuoSofsMVjCTO+LkFUzcRyHTcui+N9HLiI5nkXM50DYY0dekjE8mWuZcJJDAOaHPUhPTmIsLWHtvCAWx8qLdCvSjF6Fcnmr0hAPOxixEKbP50MsFlOEwG6vPIm1VaakAuQpEDOAXdBscJ2WKKj3O0ejUc3u576+vmmnEPXYbD1daXWJa9TnwH/dvAhPvvb/cGk8C6eNh9chQJaB0XTO1OEkAUDUb0fY44AoyRjNAiGfgK+un2up01w9s68qIYpixRBPudLNYDBYlLeq57o0Y/OaFpFIBKlUCrlcDg6H9grWVoVEQQfYqsxAIIDz589rjsEGrpSmtrW1le1+Zus9R0dHi1Z91rtgR4vSmve184LYe/dK/Ev3IN48ncRUToLXKeCmrjbc1BWG08bjsX85jcnaut10wylwcNh43DAvgJMDk8jm88hLwKWP9iaHXTIevnUeVhu4MawZ1Bo+UpcmlwvxlJZuulwu3fJWM4FVH7VCSIZVBw4NDRVNNbUKJAo6wHEcOjo6YLPZcOnSJc0x2MCV0tSJiYmyRp5tdSvd/1xJaOqhXDPc/LAbD3x6Ae77d/NwOS3CZefhUy1rH03nsOfVM00rXQ04BbgdAjoCTvzzP67EyGQO//r+EC6OZSDwHFbN8kPsP45PzLGWIADa4SN16WapwU+n02VLkwOBAOLxeFGIp1mot68ZcfjRE4/HA4/Hg2QySaJAlIfjOHR2diKbzSoVRlqiwEpTx8bGNC/+aDSK8+fPF+1/bpSnoMZp4+H0TXeP717TifF0Hv/8ZvULzOtBQGFY3ZQowS5wuH1lO+wCj3a/E/+0tnhi5b9+aP4TZzXIsqzE3IeGhpBKpaYZ/tLSTdady+L8jS7drAZ2bbeCKAAFb6HckEsrYP7//RZBvXjn5MmTFd1gtmIzGAxq3n/8+HFks1k4HA7FINhsNt3jybV6IPeun4vB0Sn85nBj1gU6P+o4zkkSJEnGsll+bL02rvn4VivbBK5052qFeFjMfXBwEH6/Hy6XqyiZa7bSzWooLdgwMxzHkSgQV4eVC4ZCIWQyGUxMTMDn85V9LMsrlFvRCQAulwt+v1+JWbIGMyM+8PXMUvrPG7rQO5LGv/WkIBpkg4WPKkkLm8cKm4F8Thue/MxSeJ2VL1+znYrVpZvlQjysO1d90m9rK94PffjwYSxatEjz2mllWinZrLco/PSnP8UPf/hDDAwM4LrrrsNzzz2HdevW6fb81UCioBPspMMayxKJhKYo+P1+JTmtBQtDzZo1S/mg1LOKU4t6hqv5XTY8/Q8r8KuDfXj5RAKDYxld8gwCB8R8DoQ8dsgAJjJ5iJIMDsCUmMc/XNeBaJmwlppmeAqsO7fS1E0ARSEer9eLSCSiGP6rlW6asU9BL1qtLFWvXc2//e1vsXPnTjz//PO48cYbsXfvXmzevBmnT59Ge3u7Lq9RDSQKOsIqKKLRKC5duoSFCxeWfRzHcXA4HJiamtJ8rmg0imPHjimhI/W2Nz2pN4EdcNnwX27pwrZPzcXRC6P4zV/78be+0Zr7GvxOAW67gJCnYBw5FMRHlmUMTeYQcNlx5yrtsBFwRRCM8BREUawY4mGlm6WzeNRTN+vx+GingjnQUxSeffZZ3HffffjKV74CAHj++efxyiuv4IUXXsCuXbt0eY1qIFHQEaXePxrFqVOnKiaHbTab5qY1oDCJMZ/P4/Lly8rjjTAGeo3ibvPYsWFJFJ9eHMFrJxP49b9dwOnBCYgzVAcOgMvO4YFPL8D/evvDQp+EwMMmcBDzMjJ5CX6nDTtv7cKS9sp7B5TnrEEUyk2VVX9l3bnqEE84HC4K8RhZusnzPHkKJiAcDuPixYt1P082m8Xhw4fxrW99S7mN53ls2rQJhw4dqvv5a4FEQUdYCMntdsPj8WBoaAjxePlTLc/zSnyZTU0tvZ+FoUKhkGEVGXrP5uc4DrevaMeW5TEkJ7L473/5AH84NogpjaQDzxX+SDJw3ewg/uMNnfjUwhBeOT6IA+8PIyPm4XXa8JklYdyxsv2qi2iAyp5CNYtVmJH3+/1ob29XbjOis3ymkKdgDiKRCLq7u+t+nmQyiXw+P81OxONxnDp1qu7nrwUSBZ1Rh5ASiYSmKIiiCJ/Pp0xNLUc0GsXFixeVQXhGYFT/A8dxiPmc2LV5MZbG/fjRmz0YTYuq+wsLwmUAeQmYFXTi+1uXgeM4dEU9eHDDQuz49AKkcxJcdh58FUY4m80CKHzgSpessO5cp9M5bRZPucUqZsPKotBKngKbf2RFSBR0Rl2a2t3drVmaKooi4vH4VUXh5MmTyGQyhoaPjGzV5zkO/7hmFjYsieCp/3sGb32QwmQ2D0kurMB0CDxu6gphz2eWTNt4xnMcPI7pgqUu3Sz3lRmWCxcuTFus0uqlm1YPH7WKp8DmH9XbgR2NRiEIAgYHB4tuHxwcREdHR71vsyZIFHSGlaa2tbVBFEWMj4/D7/dPe5woisqobK0VnKxzslKjW700arVjxOvAs3evwPmRKfztgxRGp0QE3TZsXBxByFssSrIsF+2LmOlilY6ODmWpysGDB7FmzZqmdukagZU9hVYLH+nhKTgcDqxduxZvvPEGtm7dCqDwmXzjjTfwwAMP1P38tUCioDMsryBJkpITKBUFtmAnGAxCEASMjIxo1p1Ho1GkUikl72DUlNRGMTfkxpw215WwzvgIehPFg9gymQxkWVZm6bMcDSvddLlcFbtzc7kcAPP1KeiBUQPxzIAgCEroz+yEw2GkUqmilbq1snPnTmzbtg033HAD1q1bh71792JiYkKpRmo0JAoGoM4rXLx4EV1dXUX3M6Nlt9uVqamVRKG/vx9e78wqbqrFqJyCunSz3Nd8Pg+73V5UxcMWq+hRuglYUxSs3KfQap6CLMsYHh7WzBvOlM997nNIJBLYvXs3BgYGcP311+O1116r+3lrhUTBANSlqSdPnkQulys6Taj7DmKxGM6ePVt2BSdQOJGwD4oRJ8Raw0elK0GvtliFzeHp7OxUPACjQmJWPUkD1g4ftUqiWZIkjIyMwOFwYN++fRBFET09PUilUvjd735X03M+8MADTQsXlUKiYADM4LtcLqXCSD1NUe1yRiIRzRWcQOGD4nA4lG5YvdEKH6lLN0sNfmnpJjP8bLGKemF6M0/rVvQUrJxoNpOnUFq1pq5e27lzJ7q7u8HzPF588UVcd911WLBgAdatW9cSo7+vBomCQbDhXupxFYxcLqecktkKTra7uRys+1nvC44tTL98+TImJydntFilra3N9KWbRnY0NxurewqNEoWZVLBp7ZfYt28f2tvbsXnzZnz729/G3Xff3ZD33ChIFAxCXZp69OjRIoNe2ukci8UqlqYKgoDx8fGqxwqzC1/r4meu+uDgILxeL9xud1Ey1+juXKOwqtEErJ9o1it8NJPwpnr4IKtgi8fjRZ5uOaLRKAB9R12YCRIFg2ClqYFAAJIkYWxsTBmVXVqxEI1G0dPTo1maKssybDYbUqlU0Z6GSotVKpVuqi/8gwcPYtWqVZrD+1oRq3sKZgmx6E014SN27WsZfa3wprpfpd7wZiQSseT4bBIFg1APsGMhJLUoqE/8gUAAPM8jlUopq/7U5PN5BINB9PX1YWhoqCi+WW6xSjgcVv5+tcUqjepTaDRWFATA+uEjURSVn49NnNWK7bNrv1md6aFQCMPDw4Y9f7MgUTAQ5uqzTWqLFi0CUJxTAAofdCYc5URBFEW0t7cjmUzCbrcrpZvsFFRr6Sbb02A1UbCq0QSslWgWRbHI2LMBkW+99RYymUzFsuV6r309iEQi0zqRrQCJgoGo9yucOHFC2aRWbnpqLBbDuXPnypamMk+hvb1d1w8Bc9VbdeRDJchTaD6lydxyOS31xFlWfTd//nxls5yZV3OGw2G89957zX4bumPe/3ELwLqbHQ4H/H4/kskkOjs7IYritMmoWqWpbJ+CEbsU2InTaqJghbJALcyUaJ7pJrlKOa3SibNsTa3H42niTzYzrDoUj0TBYNRVSIlEQhGF0hMQK01NJpOYM2eOcrvRqziZcFkJsxhNI2hkR3OlMePsD8dxmpvkrjaOpByt0sAGXKk+stohhETBYNTdze+8846ysrGcW8zyCmpRYB8QIxp7rJpkBih8NBPYtVipXl+dzC3dG+1yuXQZR6KmlSalRiIRSjQT1cNO4oFAAACUIVrlRCEWi6G3t7eoNFU9EkPvE6JRc4+ajZU9hWrDRzOdQaUO8cRisaLvG+lJmqmr+WpEIhGMjIxU3T9kdqzzk5gU9dRUFkLSmqxYrjSVXXBGhAwaPSG1UVjNnVdTGj6aSYNi6frQUCiEWbNmFcX1zUKrhY/YHCR1/1CrY56rwcKoQ0g9PT2a4aNypamV9jzXC4WPzA9L5jIjn0wmMTExgcOHD09rUGRGPxAIIB6Pm2J9aLW0kqfg8/ngdDqRSCRIFIjqYMnmcDisbGPTMvRMOFhpqpGiQOGj5lNp8GA6nVYGIbK4PlC4nmbPnl1zMtfMtJKnwHEcQqGQ5SqQSBQaAAsh2e12BAIBjI6OVpyr0t3drZSmGlWOClg3fASYx1NgJcWVOnPLDR4MBoNlk7mJRAI9PT1NW9VoNK2UaOY4TlmkpQdPPfUUXnnlFRw9ehQOhwOpVGraY/r6+rB9+3YcOHAAPp8P27Ztw/e+9z1dD44kCg2CXezhcBijo6Oav0SHw4FAIKCUplL4qHoanVMoTeaWGn91MpcZfTaDp9rBg2bqUzACm83WMp4CAF09hWw2i3vuuQfr16/HL3/5y2n35/N53Hnnnejo6MDBgwdx8eJFfPnLX4bdbsd3v/tdXd4DQKLQMJiRYvOPKsES0nPmzCmqbGj1VZyNwogmv0oVPKULhUpn8OiZzG2ljuZaEATBsN0hRqDnULzHH38cAPDiiy+Wvf/111/He++9hz/+8Y+Ix+O4/vrr8cQTT+CRRx7Bd77zHTgcjrL/rlpIFBqEursZQMW9zOrSVBY+MgIr5xSqEVBZljUreFhcn+O4opO93+9vykIhK6/jBAqewsTERLPfxoxp5PjsQ4cOYfXq1UVrOjdv3ozt27fjxIkTWLNmjS6vQ6LQIFivAQsHJRIJTVFQl6ayx5tpFWcroDbQLJlbacyyLMtF4R2Px4NwOKyMWTZLMtfq4aNWyikAjRWFgYGBaXub2fcDAwO6vQ6JQgNh29gcDkdFl5OVpiaTSYiiWHZNpx5YxVNgyVxm5IeGhpDNZnHs2LFpW+RK5/Coxyy3Qijt4+AptJIoRCIRnD59WvP+Xbt24emnn674HCdPnsSyZcv0fms1Q6LQQNiCFKfTiVQqhcnJSc3BX6w01ePxKJ6CETkFveKQRpPP5zVj+iyZa7PZ4Ha7lRJgK2yRK+Xj4Cm0UqI5HA5XHHXx0EMP4d577634HF1dXTN6rY6ODvz1r38tuo2N7tazGo1EoYFwHAe73Y5gMAiO45BMJjFv3ryyj2WlqXa73TBjZqbwUbXrE0tn8LAmLQBIJpM4d+5c0Qwpq/BxSDS3mqdQaSheLBbTrbFt/fr1eOqpp3Dp0iW0t7cDAPbv349AIIAVK1bo8hoAiULDmTNnDtrb29Hb24tEIqEpCqw0dWpqyhLVR5VWh6qTuaXrE2tJ5lrZaH4cwkdW8hSqoa+vD8PDw+jr60M+n8fRo0cBAIsWLYLP58Ntt92GFStW4Etf+hJ+8IMfYGBgAI899hh27NgBp9Opy3sASBQaDjPCsVgMZ86cqRjXZ1VIrVB9xCZuqscsa61PLJ3Do27S0kv4zJAUNgIWPrLqfCfmKbTKz8cmpeoxFG/37t349a9/rXzPqokOHDiADRs2QBAEvPzyy9i+fTvWr18Pr9eLbdu2Yc+ePXW9bikkCg2GlaZ6PB44HA4MDw9rupdMOMwSPiods1x64lcnc5u5PtHqngJg3aF/LH9mptBmJcLhMPL5PHp7e3H58mX09PRgeHgYX//616t+rhdffFGzR4Exf/58vPrqqzW+25lBotAE2GkoFoshmUxqigIbtz01NQWfz6f7+ygNH5WuTyz9yspj1RU8kUik6HszfJCtajCBK56mVYWPXT9mq4wrN4L88ccfx4kTJwAAixcvRigUwsKFC7F06dKaRMEskCg0AVYdE41Gcfr0aSxfvrzi40dHR3VJVpUmczOZDPr6+tDb21u0PrF0Bo96faLWzCazYVVRUHsKVoR9NhqdbNY6EFXaJ33zzTfjjjvuwKOPPoqXXnoJW7Zsaeh7NgoShSbAcZwyYXFqagoTExPwer3THscSiqlUakZGgCVzKzVpqZO5kiTB6/Wira1NEYFGdeYaiVUNJnBFFKyabFY3eepJPp8vW+igVd2m3ietNYJ8yZIlAICf/OQnSKfTur7fZkKi0ARYXkEQBITDYSQSibKiwD4Y4+PjyGQycLvdRXH9che4en1iuTk86mTun/70J8yePduQ0FQzofBRa1NLAxvbO6Fl9LPZ7LRRJay6rd4DkZ7zj8wAiUKTUOcVEokEFixYMO0xoiiC53n4/X4cOXIEoigqEzfVIZ5ak7mtksyrBauKgtU9BaB8A5tWSXOpF6yubvN4PEU5L6NGlVhtpwKJQpNghpu1yZcbkc3imMuXL8f4+Dg8Hg/cbrcuhlySJMiybElRsPIpGrBmA5t62ZAkSRgYGEAikZg2n0pt9Eu9YIfDYWh1G/s/ZyXB7A95CoQusNip1+uF2+3G8PCw0qXIYELh9Xp1L+dkJ81WmPdTLVYOHwGtOepC3cdSetIvXTYkSRKmpqYQiUQaOp+qnNFn3zMhZvlA9vkVBEHZaWAVSBSaCBuQx/Yyl4qCHg0xWrCYrRVFAbBu+AgwZ1dz6Ya5ciEeSZLKhj7VRl8QBHR3dyMYDGp2+9fzHtnXUqPPKDX6LPfH8oDqr+wa27hxo67vs9mQKDQRdnHFYjGcOHFi2gnXyLHZ+XxeucCtRqudoqulWeEjLaPPvqqHEjJDX0sfS62jLrSMPruNfbaqNfofN0gUmgi78Nra2pDNZjE+Pg6/36/cb+SCHSsnmQFrewpGhY/Uk2i1mhfVtfpsTMmsWbN03TCnNRSv1OiX/l1t9IGCuPA8X/SHjP7VIVFoIuy0IkmSkqwqFQUjh+FZVRSsnlOoNXzEGrS0jH65SbSlzYultfp6woy7IAjIZrPKDKRy4R0AiqFnJ35m8Mno1weJQpNh4ZtoNIrBwUEsXLhQuc/onIIVQ0eA9cNHWp6CumO9nNFnHeuls6katVa00klfnciNRCLK9ak2/OVCPIT+kCg0GXbRR6NRnDp1CrlcTqlkoFWctWNFg8EatCRJQjKZRCqVqjh+3OVywev1Fi0bMnKtaGniVh3bVxt9AEWn+tI/bB2tFX+HrQCJQpNhHw632w2v14uhoSFli5LRqzjJUzAX6lp9rZ0TzLCOjIwoTYtGjR/Xeo+VErqlydzSuH5piIcwHyQKJqC0NFUtCkaGj6zqKZg1p6BVq3+1MSXBYLDI6L/zzjuYN2/etBJmvd5jJaMPFJdtVjL67LFEdfT29uKJJ57Am2++iYGBAXR2duKLX/wiHn300aL1ue+++y527NiBv/3tb4jFYnjwwQfx8MMP1/36JAomQD01tbu7WzFqrNrDCCNH4SP9YUZfq05f3aClNvrVjimpp09BqyuXPW9pg5aW0Vc/htCXU6dOQZIk/PznP8eiRYtw/Phx3HfffZiYmMAzzzwDABgbG8Ntt92GTZs24fnnn0d3dze++tWvoq2tDffff39dr0+iYALUpamiKOLy5csIBAKGewoUPqqeqxl9NpuqdOeE2ujXK8aV+hRq7crVqt4ho994tmzZUjSGu6urC6dPn8bPfvYzRRReeuklZLNZvPDCC3A4HFi5ciWOHj2KZ599lkTBCrAPpyzLSggpEAgUhXioJHXm1ONZsWUqWka/3KKhUCiEzs5OxegbJeRA4WfjeR75fF4p2VTfB9TWlUuYm9HRUYTDYeX7Q4cO4eabby4KJ23evBlPP/00RkZGEAqFan4tEgWToC5N7e/vxzXXXGOop/BxDR+pa/XLGf1cLgdBEKbF9Ds6OhSjb+Scm5l05UajUXi93iKjTw1a1uXMmTN47rnnFC8BAAYGBorK1wEgHo8r95EoWAD2IY5EInjvvfeQzWYp0VwDkiQhm80inU6jv79/mtFX1+qrY/rt7e1FRr8ZtfrAzLpyFy9eTEa/Bdm1axeefvrpio85efIkli1bpnzf39+PLVu24J577sF9991n9FsEQKJgGtjpzul0wufzIZlMGi4KatezVWC1+lon/UwmAwBKOI6V+kaj0YZsl7ua0QeoK/fjykMPPYR777234mO6urqUv3/44YfYuHEjbrrpJvziF78oelxHRwcGBweLbmPfs+rFWiFRMBHqAXmJREK57ePUvFZupWg5o68u23S73QiHw8r3fX19kGVZWZeo9/tjXyt15QLTG7SoK/fjTSwWm/Gu9f7+fmzcuBFr167Fr371q2lFIevXr8ejjz5a1Oy6f/9+LF26tK7QEUCiYCrUeYUjR44A0B4OVi/Nqj5iDVpaRl+rVp/tkWa1+ka9d726csnoE7XS39+PDRs2YP78+XjmmWeUAyJwxQv4whe+gMcffxxf+9rX8Mgjj+D48eP48Y9/jB/96Ed1vz6JgolghiQQCCjGh1Wa6I1RnoK6Vl/L6Jer1Q8EAkXjGeox+lerPtKzK1f9eILQg/379+PMmTM4c+YM5syZU3Qfu06DwSBef/117NixA2vXrkU0GsXu3bvrLkcFAE5u1ZkAFkUUReRyORw+fBgjIyO45ZZblF3NevL222/X3BV7NaNfukdabfz1qtWvxOnTp8FxHBYtWjSjBq3SCh5q0CI+zpCnYDKY8Q8EAhgeHjasEatS+IjV6msZfZYAVxt6tkyFCYCRRv9qDVo+n0/JxVBXLkFUB4mCyWBGyuPxKPF3vQ1sPp+HKIoYHx8va/RZrX65BenM6BvdoMW+1tKVe80115DRJ4gaIVEwGepTrCAIGBoaqjrEw+bqa530s9ksgELJm9frVRq04vF4kdFvVNlmuQYtgNYmEkQzIFEwIaziyOl0IplMThMFVquvZfTZXH31SZ8tU2FG/+DBg7j22mvh8/l0f/+1Gn3qyiWI5kOiYELYKG23243h4WH09vYWbdRKp9PgOA5Op1Mx/NUuU5FlueawlB5duWT0CcKckCiYEI7jFE8hEokgnU4ry1TYSb+eZSqSJCmD1cox0watq3XlUq0+QbQeJAomZdmyZcrqRUDfWnjW98DEB6CuXIIgCpAomBRWUVPPMhWtrlxRFNHe3j7tpE9duQRBUPOaSWEzgFilUKlhrrYrV6tBi7pyCYJQQ56CSWFGm8X/geIlKrQ2kSAIIyBRMDHM0LNKITL6BEEYDYWPTE5pmSdBEISRkKdgckgMCIJoJI0fqE8QBEGYFhIFgiAIQoFEgSAIglAgUSAIgiAUSBQIgiAIBRIFgiAIQoFEgSAIglAgUSAIgiAUSBQIgiAIBRIFgiAIQoFEgSAIglAgUSAIgiAUSBQIgiAIBRIFgiAIQoFEgSAIglAgUSAIgiAUSBQIgiAIBRIFgiAIQoFEgSAIglAgUSAIgiAUSBQIgiAIBRIFgiAIQoFEgSAIglAgUSAIgiAUSBQIgiAIBRIFgiAIQoFEgSAIglAgUSAIgiAUSBQIgiAIBRIFgiAIQoFEgSAIglAgUSAIgiAUSBQIgiAIBRIFgiAIQoFEgSAIglAgUSAIgiAU/j/IHm7/4u7BXwAAAABJRU5ErkJggg==",
+ "text/plain": [
+ "
"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "fig = plt.figure()\n",
+ "ax = fig.add_subplot(111, projection=\"3d\")\n",
+ "sc = ax.scatter(\n",
+ " transformed_data[:, 0], # PC1\n",
+ " transformed_data[:, 1], # PC2\n",
+ " transformed_data[:, 2], # PC3\n",
+ " s=50,\n",
+ " alpha=0.8,\n",
+ ")\n",
+ "ax.view_init(elev=-50, azim=280)\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8d57127c5b76ae41",
+ "metadata": {
+ "collapsed": false,
+ "id": "8d57127c5b76ae41"
+ },
+ "source": [
+ "This distribution is clearly non-Gaussian and complex, despite the random initialization."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "639b83ab01e06491",
+ "metadata": {
+ "collapsed": false,
+ "id": "639b83ab01e06491"
+ },
+ "source": [
+ "If you've made it to the end of this example and have been paying attention you are now ready to use THRML for your own research-grade problems! We are very excited to see what you make with it."
]
- },
- "metadata": {},
- "output_type": "display_data"
}
- ],
- "source": [
- "fig = plt.figure()\n",
- "ax = fig.add_subplot(111, projection=\"3d\")\n",
- "sc = ax.scatter(\n",
- " transformed_data[:, 0], # PC1\n",
- " transformed_data[:, 1], # PC2\n",
- " transformed_data[:, 2], # PC3\n",
- " s=50,\n",
- " alpha=0.8,\n",
- ")\n",
- "ax.view_init(elev=-50, azim=280)\n",
- "plt.show()"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "8d57127c5b76ae41",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "This distribution is clearly non-Gaussian and complex, despite the random initialization."
- ]
- },
- {
- "cell_type": "markdown",
- "id": "639b83ab01e06491",
- "metadata": {
- "collapsed": false
- },
- "source": [
- "If you've made it to the end of this example and have been paying attention you are now ready to use THRML for your own research-grade problems! We are very excited to see what you make with it."
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3.10.16 ('dev_thrml')",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.10.16"
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.16"
+ },
+ "vscode": {
+ "interpreter": {
+ "hash": "591c6f2f872d065a4e9643c78186d30103ad07846137f2348d83da528974561e"
+ }
+ },
+ "colab": {
+ "provenance": [],
+ "gpuType": "V5E1",
+ "include_colab_link": true
+ },
+ "accelerator": "TPU"
},
- "vscode": {
- "interpreter": {
- "hash": "591c6f2f872d065a4e9643c78186d30103ad07846137f2348d83da528974561e"
- }
- }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
\ No newline at end of file
From 723b27f1f866696874f1f4a67fc7c483c3db3e1c Mon Sep 17 00:00:00 2001
From: igor-holt
Date: Mon, 20 Apr 2026 17:17:43 -0400
Subject: [PATCH 8/9] feat: Add llms.txt for LLM discoverability
Links to Igor Holt, AI Architecture, Genesis Conductor ecosystem.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
llms.txt | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
create mode 100644 llms.txt
diff --git a/llms.txt b/llms.txt
new file mode 100644
index 0000000..2dd984b
--- /dev/null
+++ b/llms.txt
@@ -0,0 +1,19 @@
+# thrml — Thermodynamic Hypergraphical Model Library
+
+> JAX-based library for thermodynamic computing and hypergraph neural networks.
+> Created by Igor Holt, AI Architect.
+
+## Features
+- Thermodynamic computing primitives
+- Hypergraph neural network layers
+- JAX-compatible API
+- S-ToT verification integration
+
+## Research
+- Part of Genesis Conductor thermodynamic verification stack
+- Supports Extropic integration patterns
+
+## Creator
+Igor Holt — AI Architect
+https://github.com/igor-holt | https://genesisconductor.io
+ORCID: 0009-0008-8389-1297
From 3fdf4c818d527618699f85752f9cef3afd2a522a Mon Sep 17 00:00:00 2001
From: igor-holt <125706350+igor-holt@users.noreply.github.com>
Date: Thu, 30 Apr 2026 00:17:58 +0000
Subject: [PATCH 9/9] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Improve=20table?=
=?UTF-8?q?=20accessibility=20in=20telemetry=20timeline?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Added a visually hidden `
` to the table in `TelemetryTimeline.tsx` to provide a clear summary for screen reader users. Also explicitly added `scope="col"` to `
` elements to ensure appropriate relationships between header and data cells. Documented the learning about inline styles for visually hidden components when utility classes are absent in `.Jules/palette.md`.
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
---
.Jules/palette.md | 3 +++
components/TelemetryTimeline.tsx | 9 +++++----
2 files changed, 8 insertions(+), 4 deletions(-)
create mode 100644 .Jules/palette.md
diff --git a/.Jules/palette.md b/.Jules/palette.md
new file mode 100644
index 0000000..a7e66de
--- /dev/null
+++ b/.Jules/palette.md
@@ -0,0 +1,3 @@
+## 2024-05-15 - Data Table Accessibility
+**Learning:** For Next.js projects lacking utility classes like `.sr-only` (e.g., Tailwind not configured), data tables must use explicit inline CSS styles for visually hidden `
` elements to ensure screen reader accessibility without breaking layout.
+**Action:** Always verify the availability of `.sr-only` utility classes in `globals.css` or equivalent files. If absent, apply the standard visually hidden inline style block `{{ position: "absolute", width: "1px", height: "1px", padding: 0, margin: "-1px", overflow: "hidden", clip: "rect(0, 0, 0, 0)", whiteSpace: "nowrap", borderWidth: 0 }}` to `