{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/gift-framework/GIFT/blob/main/notebooks/GIFT_PINN_Training.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CYOecX5qjhvA"
      },
      "source": [
        "<cell_type>markdown</cell_type># GIFT-Native PINN Training Notebook (v3.1.4 - CORRECTED)\n",
        "\n",
        "Train a Physics-Informed Neural Network with built-in GIFT algebraic structure.\n",
        "\n",
        "**Runnable on Google Colab with free T4/A100 GPU.**\n",
        "\n",
        "## Key Fixes in This Version\n",
        "\n",
        "1. **Correct φ₀ definition** (from `G2Holonomy.lean`):\n",
        "   - φ₀ = e¹²³ + e¹⁴⁵ + e¹⁶⁷ + e²⁴⁶ - e²⁵⁷ - e³⁴⁷ - e³⁵⁶\n",
        "   - This is DIFFERENT from Fano plane cross-product structure!\n",
        "\n",
        "2. **Correct metric formula**:\n",
        "   - g_ij = (1/6) Σ_{k,l} φ_ikl · φ_jkl (contract SAME indices)\n",
        "   - NOT: g_ij = (1/36) Σ_{klm} φ_ikl · φ_jlm (wrong!)\n",
        "\n",
        "3. **Correct scaling**:\n",
        "   - c^14 = 65/32 → c = (65/32)^{1/14} ≈ 1.0543\n",
        "   - NOT: (65/32)^{1/7} (wrong!)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "WqPNbqWjjhvC"
      },
      "source": [
        "## 1. Setup & Dependencies"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {
        "id": "a1VuwbKgjhvD"
      },
      "outputs": [],
      "source": [
        "# Install dependencies\n",
        "!pip install -q torch numpy matplotlib tqdm"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "NwBpZLHyjhvD",
        "outputId": "472447ad-15fd-480a-83eb-ca352724435d"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Using device: cuda\n",
            "GPU: NVIDIA A100-SXM4-80GB\n"
          ]
        }
      ],
      "source": [
        "import sys\n",
        "import numpy as np\n",
        "import torch\n",
        "import torch.nn as nn\n",
        "import matplotlib.pyplot as plt\n",
        "from tqdm.auto import tqdm\n",
        "from dataclasses import dataclass\n",
        "from typing import List, Dict, Optional\n",
        "from fractions import Fraction\n",
        "\n",
        "# Check GPU\n",
        "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
        "print(f\"Using device: {device}\")\n",
        "if device == 'cuda':\n",
        "    print(f\"GPU: {torch.cuda.get_device_name(0)}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "aVKaa0UxjhvE"
      },
      "source": [
        "## 2. GIFT Constants (Hard-coded, proven in Lean)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "BXyc-zJqjhvE",
        "outputId": "bc0096e2-a005-4a3c-fca5-ebaf5426d21a"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "GIFT Constants (Proven in Lean)\n",
            "========================================\n",
            "b2 (Second Betti number): 21\n",
            "b3 (Third Betti number): 77\n",
            "H* = b2 + b3 + 1 = 99\n",
            "dim(G2) = 14\n",
            "det(g) target = 65/32 = 2.031250\n",
            "Joyce torsion threshold = 0.0288\n",
            "\n",
            "Fano plane lines:\n",
            "  Line 0: (0, 1, 3)\n",
            "  Line 1: (1, 2, 4)\n",
            "  Line 2: (2, 3, 5)\n",
            "  Line 3: (3, 4, 6)\n",
            "  Line 4: (4, 5, 0)\n",
            "  Line 5: (5, 6, 1)\n",
            "  Line 6: (6, 0, 2)\n"
          ]
        }
      ],
      "source": [
        "# =============================================================================\n",
        "# GIFT CONSTANTS\n",
        "# =============================================================================\n",
        "\n",
        "B2 = 21           # Second Betti number = C(7,2)\n",
        "B3 = 77           # Third Betti number\n",
        "DIM_G2 = 14       # dim(G2)\n",
        "H_STAR = B2 + B3 + 1  # = 99\n",
        "\n",
        "DET_G_TARGET = Fraction(65, 32)  # = 2.03125\n",
        "DET_G_TARGET_FLOAT = float(DET_G_TARGET)\n",
        "\n",
        "TORSION_THRESHOLD = 0.0288  # Joyce threshold\n",
        "PINN_TARGET_TORSION = 0.001  # Our target (20x margin)\n",
        "\n",
        "# Fano plane lines (cyclic triples)\n",
        "FANO_LINES = [\n",
        "    (0, 1, 3), (1, 2, 4), (2, 3, 5), (3, 4, 6),\n",
        "    (4, 5, 0), (5, 6, 1), (6, 0, 2),\n",
        "]\n",
        "\n",
        "print(\"GIFT Constants (Proven in Lean)\")\n",
        "print(\"=\" * 40)\n",
        "print(f\"b2 (Second Betti number): {B2}\")\n",
        "print(f\"b3 (Third Betti number): {B3}\")\n",
        "print(f\"H* = b2 + b3 + 1 = {H_STAR}\")\n",
        "print(f\"dim(G2) = {DIM_G2}\")\n",
        "print(f\"det(g) target = 65/32 = {DET_G_TARGET_FLOAT:.6f}\")\n",
        "print(f\"Joyce torsion threshold = {TORSION_THRESHOLD}\")\n",
        "print()\n",
        "print(\"Fano plane lines:\")\n",
        "for i, line in enumerate(FANO_LINES):\n",
        "    print(f\"  Line {i}: {line}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "c9EaB1DOjhvE"
      },
      "source": [
        "<cell_type>markdown</cell_type>## 3. Standard G2 3-form (from Lean proof G2Holonomy.lean)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "OBL-nWr4jhvF",
        "outputId": "0c57d59d-acb0-493b-c7a4-25b08c8bd7f7"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "φ₀ tensor shape: (7, 7, 7)\n",
            "Non-zero entries: 42 (7 terms × 6 permutations = 42)\n",
            "\n",
            "Standard G2 form terms:\n",
            "  +e^{123}\n",
            "  +e^{145}\n",
            "  +e^{167}\n",
            "  +e^{246}\n",
            "  -e^{257}\n",
            "  -e^{347}\n",
            "  -e^{356}\n"
          ]
        }
      ],
      "source": [
        "# Standard G2 3-form (from Lean proof G2Holonomy.lean, lines 36-40)\n",
        "# φ₀ = e¹²³ + e¹⁴⁵ + e¹⁶⁷ + e²⁴⁶ - e²⁵⁷ - e³⁴⁷ - e³⁵⁶\n",
        "#\n",
        "# IMPORTANT: This is DIFFERENT from the Fano plane cross-product structure!\n",
        "# The Fano lines define octonion multiplication, but φ₀ is the associative 3-form.\n",
        "\n",
        "STANDARD_G2_FORM = [\n",
        "    ((0, 1, 2), +1.0),  # e^123\n",
        "    ((0, 3, 4), +1.0),  # e^145\n",
        "    ((0, 5, 6), +1.0),  # e^167\n",
        "    ((1, 3, 5), +1.0),  # e^246\n",
        "    ((1, 4, 6), -1.0),  # e^257\n",
        "    ((2, 3, 6), -1.0),  # e^347\n",
        "    ((2, 4, 5), -1.0),  # e^356\n",
        "]\n",
        "\n",
        "def _form_index_3(i, j, k):\n",
        "    \"\"\"Map (i, j, k) with i < j < k to linear index in C(7,3) = 35.\"\"\"\n",
        "    count = 0\n",
        "    for a in range(7):\n",
        "        for b in range(a + 1, 7):\n",
        "            for c in range(b + 1, 7):\n",
        "                if (a, b, c) == (i, j, k):\n",
        "                    return count\n",
        "                count += 1\n",
        "    raise ValueError(f\"Invalid indices: {i}, {j}, {k}\")\n",
        "\n",
        "def build_phi0_tensor():\n",
        "    \"\"\"Build full 7×7×7 antisymmetric tensor for standard G2 form.\"\"\"\n",
        "    phi0 = np.zeros((7, 7, 7), dtype=np.float32)\n",
        "    for (indices, sign) in STANDARD_G2_FORM:\n",
        "        i, j, k = indices\n",
        "        phi0[i,j,k] = phi0[j,k,i] = phi0[k,i,j] = sign\n",
        "        phi0[j,i,k] = phi0[i,k,j] = phi0[k,j,i] = -sign\n",
        "    return phi0\n",
        "\n",
        "PHI0_TENSOR = build_phi0_tensor()\n",
        "print(f\"φ₀ tensor shape: {PHI0_TENSOR.shape}\")\n",
        "print(f\"Non-zero entries: {np.count_nonzero(PHI0_TENSOR)} (7 terms × 6 permutations = 42)\")\n",
        "print()\n",
        "print(\"Standard G2 form terms:\")\n",
        "for (indices, sign) in STANDARD_G2_FORM:\n",
        "    sign_str = \"+\" if sign > 0 else \"-\"\n",
        "    print(f\"  {sign_str}e^{{{indices[0]+1}{indices[1]+1}{indices[2]+1}}}\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "ukj89LvpjhvF",
        "outputId": "bf329a31-03f0-4a61-87b7-2a108b418919"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "φ₀ has 35 components (C(7,3) = 35)\n",
            "Non-zero: 7 (the 7 standard terms)\n",
            "Scale factor: 1.051921\n",
            "\n",
            "Verification:\n",
            "  Expected det(g) = 65/32 = 2.031250\n",
            "  Computed g (diagonal): [1. 1. 1. 1. 1. 1. 1.]\n",
            "  det(g) for standard φ₀: 1.000000\n"
          ]
        }
      ],
      "source": [
        "def phi0_standard(normalize=True):\n",
        "    \"\"\"\n",
        "    Standard G2 3-form (35 independent components).\n",
        "\n",
        "    For the STANDARD form, the induced metric is IDENTITY on ℝ⁷, so det(g) = 1.\n",
        "\n",
        "    To achieve det(g) = 65/32:\n",
        "    - If φ → c·φ, then g → c²·g (since g_ij ~ φ_ikl φ_jkl)\n",
        "    - Therefore det(g) → c^14 · det(g)\n",
        "    - We need c^14 = 65/32, so c = (65/32)^{1/14} ≈ 1.0543\n",
        "    \"\"\"\n",
        "    phi0 = np.zeros(35, dtype=np.float32)\n",
        "    for (indices, sign) in STANDARD_G2_FORM:\n",
        "        idx = _form_index_3(*indices)\n",
        "        phi0[idx] = sign\n",
        "\n",
        "    if normalize:\n",
        "        # c^14 = 65/32 → c = (65/32)^{1/14}\n",
        "        scale = (65.0 / 32.0) ** (1.0 / 14.0)\n",
        "        phi0 = phi0 * scale\n",
        "\n",
        "    return phi0\n",
        "\n",
        "phi0 = phi0_standard()\n",
        "print(f\"φ₀ has {len(phi0)} components (C(7,3) = 35)\")\n",
        "print(f\"Non-zero: {np.sum(np.abs(phi0) > 1e-10)} (the 7 standard terms)\")\n",
        "print(f\"Scale factor: {(65.0/32.0)**(1/14):.6f}\")\n",
        "print()\n",
        "print(\"Verification:\")\n",
        "print(f\"  Expected det(g) = 65/32 = {65/32:.6f}\")\n",
        "# Quick sanity check with the formula g_ij = (1/6) sum_{k,l} phi_ikl * phi_jkl\n",
        "g_test = np.einsum('ikl,jkl->ij', PHI0_TENSOR, PHI0_TENSOR) / 6.0\n",
        "print(f\"  Computed g (diagonal): {np.diag(g_test)}\")\n",
        "print(f\"  det(g) for standard φ₀: {np.linalg.det(g_test):.6f}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "A1MGuW-MjhvG"
      },
      "source": [
        "## 4. Neural Network Components"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "metadata": {
        "id": "f8R6lUnzjhvG"
      },
      "outputs": [],
      "source": [
        "class FourierFeatures(nn.Module):\n",
        "    \"\"\"Random Fourier feature encoding.\"\"\"\n",
        "    def __init__(self, input_dim=7, num_frequencies=32, scale=1.0):\n",
        "        super().__init__()\n",
        "        self.num_frequencies = num_frequencies\n",
        "        self.output_dim = 2 * num_frequencies\n",
        "        B = torch.randn(num_frequencies, input_dim) * scale\n",
        "        self.register_buffer('B', B)\n",
        "\n",
        "    def forward(self, x):\n",
        "        projected = 2 * np.pi * torch.matmul(x, self.B.T)\n",
        "        return torch.cat([torch.cos(projected), torch.sin(projected)], dim=-1)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "YVBiK6KyjhvH",
        "outputId": "b8bf42b4-6f03-4649-c668-ba43be47dfc0"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Lie derivatives shape: (14, 35)\n",
            "Non-zero Lie derivative entries: 84\n"
          ]
        }
      ],
      "source": [
        "def g2_generators():\n",
        "    \"\"\"Compute 14 generators of G2 in so(7).\"\"\"\n",
        "    generators = np.zeros((14, 7, 7), dtype=np.float32)\n",
        "    # First 7: rotations in Fano planes\n",
        "    for idx, (i, j, k) in enumerate(FANO_LINES):\n",
        "        generators[idx, i, j] = 1\n",
        "        generators[idx, j, i] = -1\n",
        "    # Remaining 7: mixed rotations\n",
        "    for idx in range(7):\n",
        "        i, j, k = idx, (idx + 1) % 7, (idx + 3) % 7\n",
        "        gen_idx = 7 + idx\n",
        "        generators[gen_idx, i, k] = 1\n",
        "        generators[gen_idx, k, i] = -1\n",
        "        generators[gen_idx, j, k] = 0.5\n",
        "        generators[gen_idx, k, j] = -0.5\n",
        "    # Normalize\n",
        "    for idx in range(14):\n",
        "        norm = np.linalg.norm(generators[idx])\n",
        "        if norm > 1e-10:\n",
        "            generators[idx] /= norm\n",
        "    return generators\n",
        "\n",
        "def precompute_lie_derivatives():\n",
        "    \"\"\"\n",
        "    Compute Lie derivatives of φ₀ along G2 generators.\n",
        "\n",
        "    CRITICAL: Use PHI0_TENSOR (correct G2 form), NOT Fano epsilon!\n",
        "    This was a bug in the previous version.\n",
        "    \"\"\"\n",
        "    generators = g2_generators()\n",
        "    lie_derivs = np.zeros((14, 35), dtype=np.float32)\n",
        "    for a in range(14):\n",
        "        X = generators[a]\n",
        "        idx = 0\n",
        "        for i in range(7):\n",
        "            for j in range(i+1, 7):\n",
        "                for k in range(j+1, 7):\n",
        "                    # (L_X φ)_{ijk} = X_i^l φ_{ljk} + X_j^l φ_{ilk} + X_k^l φ_{ijl}\n",
        "                    val = sum(\n",
        "                        X[i,l]*PHI0_TENSOR[l,j,k] +\n",
        "                        X[j,l]*PHI0_TENSOR[i,l,k] +\n",
        "                        X[k,l]*PHI0_TENSOR[i,j,l]\n",
        "                        for l in range(7)\n",
        "                    )\n",
        "                    lie_derivs[a, idx] = val\n",
        "                    idx += 1\n",
        "    return lie_derivs\n",
        "\n",
        "LIE_DERIVATIVES = precompute_lie_derivatives()\n",
        "print(f\"Lie derivatives shape: {LIE_DERIVATIVES.shape}\")\n",
        "print(f\"Non-zero Lie derivative entries: {np.count_nonzero(np.abs(LIE_DERIVATIVES) > 1e-10)}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "L4PctAtCjhvH"
      },
      "source": [
        "## 5. GIFT-Native PINN Model"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "metadata": {
        "id": "1fUUzuUzjhvH"
      },
      "outputs": [],
      "source": [
        "class GIFTNativePINN(nn.Module):\n",
        "    \"\"\"\n",
        "    PINN with GIFT structure built-in.\n",
        "\n",
        "    Key features:\n",
        "    - Uses correct G2 3-form φ₀ (not Fano lines!)\n",
        "    - G2 adjoint: 14 DOF (not 35)\n",
        "    - phi = phi0 + scale * delta_phi\n",
        "    - Correct metric formula: g_ij = (1/6) Σ_{k,l} φ_ikl φ_jkl\n",
        "    \"\"\"\n",
        "    def __init__(self, num_frequencies=32, hidden_dims=None, perturbation_scale=0.01):\n",
        "        super().__init__()\n",
        "        if hidden_dims is None:\n",
        "            hidden_dims = [128, 128, 128]\n",
        "\n",
        "        self.perturbation_scale = perturbation_scale\n",
        "\n",
        "        # Register buffers - use correct G2 form\n",
        "        self.register_buffer('phi0_tensor', torch.from_numpy(PHI0_TENSOR))\n",
        "        self.register_buffer('phi0', torch.from_numpy(phi0_standard()))\n",
        "        self.register_buffer('lie_derivatives', torch.from_numpy(LIE_DERIVATIVES))\n",
        "\n",
        "        # Network\n",
        "        self.fourier = FourierFeatures(input_dim=7, num_frequencies=num_frequencies)\n",
        "\n",
        "        layers = []\n",
        "        in_dim = self.fourier.output_dim\n",
        "        for h_dim in hidden_dims:\n",
        "            layers.extend([nn.Linear(in_dim, h_dim), nn.SiLU()])\n",
        "            in_dim = h_dim\n",
        "        layers.append(nn.Linear(in_dim, 14))  # 14 G2 adjoint params\n",
        "        self.mlp = nn.Sequential(*layers)\n",
        "\n",
        "        self._init_weights()\n",
        "\n",
        "    def _init_weights(self):\n",
        "        for m in self.modules():\n",
        "            if isinstance(m, nn.Linear):\n",
        "                nn.init.xavier_uniform_(m.weight, gain=0.1)\n",
        "                if m.bias is not None:\n",
        "                    nn.init.zeros_(m.bias)\n",
        "\n",
        "    def forward(self, x):\n",
        "        h = self.fourier(x)\n",
        "        adjoint = self.mlp(h)  # (N, 14)\n",
        "        delta_phi = torch.matmul(adjoint, self.lie_derivatives)  # (N, 35)\n",
        "        return self.phi0.unsqueeze(0) + self.perturbation_scale * delta_phi\n",
        "\n",
        "    def phi_tensor(self, x):\n",
        "        \"\"\"Get full (N, 7, 7, 7) antisymmetric tensor.\"\"\"\n",
        "        components = self.forward(x)\n",
        "        N = components.shape[0]\n",
        "        phi = torch.zeros(N, 7, 7, 7, device=x.device, dtype=x.dtype)\n",
        "        idx = 0\n",
        "        for i in range(7):\n",
        "            for j in range(i+1, 7):\n",
        "                for k in range(j+1, 7):\n",
        "                    val = components[:, idx]\n",
        "                    phi[:,i,j,k] = phi[:,j,k,i] = phi[:,k,i,j] = val\n",
        "                    phi[:,j,i,k] = phi[:,i,k,j] = phi[:,k,j,i] = -val\n",
        "                    idx += 1\n",
        "        return phi\n",
        "\n",
        "    def metric(self, x):\n",
        "        \"\"\"\n",
        "        Compute metric g_ij from φ.\n",
        "\n",
        "        CORRECT formula (from g2_form.py and Bryant 1987):\n",
        "            g_ij = (1/6) Σ_{k,l} φ_ikl · φ_jkl\n",
        "\n",
        "        Note: Contract over SAME indices k,l (not different indices!)\n",
        "        For standard φ₀, this gives g = identity.\n",
        "        \"\"\"\n",
        "        phi = self.phi_tensor(x)\n",
        "        # Contract over same indices k and l\n",
        "        return torch.einsum('nikl,njkl->nij', phi, phi) / 6.0\n",
        "\n",
        "    def det_g(self, x):\n",
        "        return torch.linalg.det(self.metric(x))\n",
        "\n",
        "    def get_adjoint_params(self, x):\n",
        "        return self.mlp(self.fourier(x))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "L77dWqvjjhvH"
      },
      "source": [
        "## 6. Loss Function"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "metadata": {
        "id": "zlnvV_-NjhvH"
      },
      "outputs": [],
      "source": [
        "class GIFTLoss(nn.Module):\n",
        "    def __init__(self, det_weight=100.0, torsion_weight=1.0, sparse_weight=0.1, pd_weight=10.0):\n",
        "        super().__init__()\n",
        "        self.det_weight = det_weight\n",
        "        self.torsion_weight = torsion_weight\n",
        "        self.sparse_weight = sparse_weight\n",
        "        self.pd_weight = pd_weight\n",
        "\n",
        "    def forward(self, model, x, return_components=False):\n",
        "        losses = {}\n",
        "\n",
        "        # 1. Determinant loss\n",
        "        det_g = model.det_g(x)\n",
        "        losses['det'] = torch.mean((det_g - DET_G_TARGET_FLOAT) ** 2)\n",
        "\n",
        "        # 2. Torsion loss (gradient-based proxy)\n",
        "        x_grad = x.clone().requires_grad_(True)\n",
        "        phi = model(x_grad)\n",
        "        torsion = 0.0\n",
        "        for i in range(min(10, 35)):  # Sample components for speed\n",
        "            grad = torch.autograd.grad(phi[:, i].sum(), x_grad, create_graph=True, retain_graph=True)[0]\n",
        "            torsion = torsion + (grad ** 2).sum(dim=-1).mean()\n",
        "        losses['torsion'] = torsion / 10.0\n",
        "\n",
        "        # 3. Sparsity\n",
        "        adjoint = model.get_adjoint_params(x)\n",
        "        losses['sparse'] = torch.mean(adjoint ** 2)\n",
        "\n",
        "        # 4. Positive definiteness\n",
        "        g = model.metric(x)\n",
        "        eigvals = torch.linalg.eigvalsh(g)\n",
        "        losses['pd'] = torch.mean(torch.relu(-eigvals) ** 2)\n",
        "\n",
        "        total = (self.det_weight * losses['det'] +\n",
        "                 self.torsion_weight * losses['torsion'] +\n",
        "                 self.sparse_weight * losses['sparse'] +\n",
        "                 self.pd_weight * losses['pd'])\n",
        "\n",
        "        return (total, losses) if return_components else total"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ZD_htYUSjhvH"
      },
      "source": [
        "## 7. Create Model"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "VRR3z0dBjhvH",
        "outputId": "a9fc0127-9605-4941-e165-31f0bc1eee7a"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Model parameters: 43,150\n",
            "Architecture: 7D -> Fourier(64) -> MLP -> 14 (G2 adjoint) -> 35 (3-form)\n"
          ]
        }
      ],
      "source": [
        "model = GIFTNativePINN(\n",
        "    num_frequencies=32,\n",
        "    hidden_dims=[128, 128, 128],\n",
        "    perturbation_scale=0.01,\n",
        ").to(device)\n",
        "\n",
        "n_params = sum(p.numel() for p in model.parameters())\n",
        "print(f\"Model parameters: {n_params:,}\")\n",
        "print(f\"Architecture: 7D -> Fourier(64) -> MLP -> 14 (G2 adjoint) -> 35 (3-form)\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "0jGA8avMjhvH",
        "outputId": "9013811b-31ca-4b05-d164-a2bde25697a9"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Input: torch.Size([100, 7])\n",
            "Output (phi): torch.Size([100, 35])\n",
            "det(g) mean: 2.031250 (target: 2.031250)\n"
          ]
        }
      ],
      "source": [
        "# Test forward pass\n",
        "x_test = torch.rand(100, 7, device=device)\n",
        "with torch.no_grad():\n",
        "    phi_test = model(x_test)\n",
        "    det_test = model.det_g(x_test)\n",
        "\n",
        "print(f\"Input: {x_test.shape}\")\n",
        "print(f\"Output (phi): {phi_test.shape}\")\n",
        "print(f\"det(g) mean: {det_test.mean().item():.6f} (target: {DET_G_TARGET_FLOAT:.6f})\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "yHZFRFaajhvI"
      },
      "source": [
        "## 8. Training"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "metadata": {
        "id": "zPXRFm1LjhvI"
      },
      "outputs": [],
      "source": [
        "# Config\n",
        "EPOCHS = 3000\n",
        "BATCH_SIZE = 512\n",
        "LR = 1e-3\n",
        "TARGET_TORSION = 0.001\n",
        "TARGET_DET_ERROR = 1e-5\n",
        "\n",
        "loss_fn = GIFTLoss(det_weight=100.0, torsion_weight=1.0, sparse_weight=0.1, pd_weight=10.0)\n",
        "optimizer = torch.optim.AdamW(model.parameters(), lr=LR, weight_decay=1e-5)\n",
        "scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor=0.5, patience=200)\n",
        "\n",
        "history = {'loss': [], 'torsion': [], 'det_error': [], 'lr': []}\n",
        "best_torsion = float('inf')\n",
        "best_state = None"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "ef07833f",
        "outputId": "d2e4d815-4438-468b-e930-96daf5bfd37d"
      },
      "source": [
        "!git clone https://github.com/gift-framework/core/"
      ],
      "execution_count": 19,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Cloning into 'core'...\n",
            "remote: Enumerating objects: 2118, done.\u001b[K\n",
            "remote: Counting objects: 100% (127/127), done.\u001b[K\n",
            "remote: Compressing objects: 100% (92/92), done.\u001b[K\n",
            "remote: Total 2118 (delta 71), reused 70 (delta 33), pack-reused 1991 (from 2)\u001b[K\n",
            "Receiving objects: 100% (2118/2118), 1.60 MiB | 32.69 MiB/s, done.\n",
            "Resolving deltas: 100% (1378/1378), done.\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "T4fqk1zbjhvI"
      },
      "source": [
        "## 9. Visualization"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 483
        },
        "id": "dJL6-030jhvI",
        "outputId": "25dc8399-178b-4d21-9717-7e5a46023bee"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "/tmp/ipython-input-1039934281.py:21: UserWarning: Data has no positive values, and therefore cannot be log-scaled.\n",
            "  plt.tight_layout()\n"
          ]
        },
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 1200x800 with 4 Axes>"
            ],
            "image/png": "[encoded data, 52884 characters]\n"
          },
          "metadata": {}
        }
      ],
      "source": [
        "fig, axes = plt.subplots(2, 2, figsize=(12, 8))\n",
        "\n",
        "axes[0,0].semilogy(history['loss'])\n",
        "axes[0,0].set_xlabel('Epoch'); axes[0,0].set_ylabel('Loss'); axes[0,0].set_title('Total Loss')\n",
        "axes[0,0].grid(True, alpha=0.3)\n",
        "\n",
        "axes[0,1].semilogy(history['torsion'], label='Torsion')\n",
        "axes[0,1].axhline(TARGET_TORSION, color='g', linestyle='--', label=f'Target {TARGET_TORSION}')\n",
        "axes[0,1].axhline(TORSION_THRESHOLD, color='r', linestyle='--', label=f'Joyce {TORSION_THRESHOLD}')\n",
        "axes[0,1].set_xlabel('Epoch'); axes[0,1].set_ylabel('Torsion'); axes[0,1].legend()\n",
        "axes[0,1].grid(True, alpha=0.3)\n",
        "\n",
        "axes[1,0].semilogy(history['det_error'])\n",
        "axes[1,0].set_xlabel('Epoch'); axes[1,0].set_ylabel('|det(g) - 65/32|²'); axes[1,0].set_title('Det Error')\n",
        "axes[1,0].grid(True, alpha=0.3)\n",
        "\n",
        "axes[1,1].semilogy(history['lr'])\n",
        "axes[1,1].set_xlabel('Epoch'); axes[1,1].set_ylabel('LR'); axes[1,1].set_title('Learning Rate')\n",
        "axes[1,1].grid(True, alpha=0.3)\n",
        "\n",
        "plt.tight_layout()\n",
        "plt.savefig('training_curves.png', dpi=150)\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tbH8vMn8jhvI"
      },
      "source": [
        "## 10. Evaluation"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "VrDAlAxSjhvI",
        "outputId": "ad23cfb6-a11b-4638-ea5a-be8cbbe53533"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "==================================================\n",
            "EVALUATION RESULTS\n",
            "==================================================\n",
            "det(g) mean: 2.03125000\n",
            "det(g) target: 2.03125000\n",
            "det(g) error: 0.00e+00\n",
            "Best torsion: 0.000000\n",
            "Joyce threshold: 0.0288\n",
            "Margin: 17516922704.0x\n"
          ]
        }
      ],
      "source": [
        "# Load best model\n",
        "if best_state:\n",
        "    model.load_state_dict(best_state)\n",
        "model.eval()\n",
        "\n",
        "with torch.no_grad():\n",
        "    x_eval = torch.rand(10000, 7, device=device)\n",
        "    det_eval = model.det_g(x_eval)\n",
        "    adjoint_eval = model.get_adjoint_params(x_eval)\n",
        "\n",
        "print(\"=\" * 50)\n",
        "print(\"EVALUATION RESULTS\")\n",
        "print(\"=\" * 50)\n",
        "print(f\"det(g) mean: {det_eval.mean().item():.8f}\")\n",
        "print(f\"det(g) target: {DET_G_TARGET_FLOAT:.8f}\")\n",
        "print(f\"det(g) error: {abs(det_eval.mean().item() - DET_G_TARGET_FLOAT):.2e}\")\n",
        "print(f\"Best torsion: {best_torsion:.6f}\")\n",
        "print(f\"Joyce threshold: {TORSION_THRESHOLD}\")\n",
        "print(f\"Margin: {TORSION_THRESHOLD / best_torsion:.1f}x\" if best_torsion > 0 else \"N/A\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 222,
          "referenced_widgets": [
            "4369551212c140e5bc8bd00304fdb20b",
            "82e22a644a7d468db9423968008b82b6",
            "51922aa4960a4b2f88261ed537c16b28",
            "801a59ceb85d4de988f44a930734b8a7",
            "bb84b02a06484c0998d664de7001a0b2",
            "b86adbfd2c494025ac2f7c99f256b1fc",
            "027f70f3651e4e24af155063014cb94d",
            "9e309128c3fb475ab37519c19f5c4b54",
            "488c83ac9eb7423ea3f7a7416bc354e8",
            "4f97e7981cb54a53945b1faf99a37146",
            "d5ebe15f55fd483f8ffe407f4f37fc07"
          ]
        },
        "id": "zzaRNMDWjhvI",
        "outputId": "3e5bf9cf-9796-4a4c-d4d4-876b2316933e"
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "Training:   0%|          | 0/3000 [00:00<?, ?it/s]"
            ],
            "application/vnd.jupyter.widget-view+json": {
              "version_major": 2,
              "version_minor": 0,
              "model_id": "4369551212c140e5bc8bd00304fdb20b"
            }
          },
          "metadata": {}
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\n",
            "Converged at epoch 0!\n",
            "\n",
            "Best torsion: 0.000000\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "/usr/local/lib/python3.12/dist-packages/torch/optim/lr_scheduler.py:1343: UserWarning: Converting a tensor with requires_grad=True to a scalar may lead to unexpected behavior.\n",
            "Consider using tensor.detach() first. (Triggered internally at /pytorch/torch/csrc/autograd/generated/python_variable_methods.cpp:836.)\n",
            "  current = float(metrics)\n"
          ]
        }
      ],
      "source": [
        "# Training loop\n",
        "model.train()\n",
        "pbar = tqdm(range(EPOCHS), desc=\"Training\")\n",
        "\n",
        "for epoch in pbar:\n",
        "    x = torch.rand(BATCH_SIZE, 7, device=device)\n",
        "\n",
        "    optimizer.zero_grad()\n",
        "    loss, components = loss_fn(model, x, return_components=True)\n",
        "    loss.backward()\n",
        "    torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)\n",
        "    optimizer.step()\n",
        "    scheduler.step(loss)\n",
        "\n",
        "    # Record\n",
        "    history['loss'].append(loss.item())\n",
        "    history['torsion'].append(components['torsion'].item())\n",
        "    history['det_error'].append(components['det'].item())\n",
        "    history['lr'].append(optimizer.param_groups[0]['lr'])\n",
        "\n",
        "    if components['torsion'].item() < best_torsion:\n",
        "        best_torsion = components['torsion'].item()\n",
        "        best_state = {k: v.cpu().clone() for k, v in model.state_dict().items()}\n",
        "\n",
        "    pbar.set_postfix({\n",
        "        'loss': f\"{loss.item():.4f}\",\n",
        "        'torsion': f\"{components['torsion'].item():.6f}\",\n",
        "        'det_err': f\"{components['det'].item():.6f}\",\n",
        "    })\n",
        "\n",
        "    if components['torsion'].item() < TARGET_TORSION and components['det'].item() < TARGET_DET_ERROR:\n",
        "        print(f\"\\nConverged at epoch {epoch}!\")\n",
        "        break\n",
        "\n",
        "print(f\"\\nBest torsion: {best_torsion:.6f}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "1454fd42"
      },
      "source": [
        "## 13. Stress Test: Recovery from Perturbation\n",
        "\n",
        "We deliberately perturb the model weights to start with a deformed G2 structure (high torsion). The goal is to see if the PINN can recover a valid torsion-free metric."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "3e6111f8",
        "outputId": "9829b08d-8ae7-413e-ecc3-889f8df9f678"
      },
      "source": [
        "# 1. Re-initialize model\n",
        "model_stress = GIFTNativePINN(\n",
        "    num_frequencies=32,\n",
        "    hidden_dims=[128, 128, 128],\n",
        "    perturbation_scale=0.1  # Increased scale for the test\n",
        ").to(device)\n",
        "\n",
        "# 2. Force perturbation: Add noise to the final layer weights\n",
        "# This ensures the output adjoint parameters are NOT zero initially\n",
        "with torch.no_grad():\n",
        "    model_stress.mlp[-1].weight.add_(torch.randn_like(model_stress.mlp[-1].weight) * 0.5)\n",
        "    model_stress.mlp[-1].bias.add_(torch.randn_like(model_stress.mlp[-1].bias) * 0.5)\n",
        "\n",
        "# 3. Check initial state\n",
        "model_stress.eval()\n",
        "x_check = torch.rand(1000, 7, device=device)\n",
        "loss_check, components_check = loss_fn(model_stress, x_check, return_components=True)\n",
        "\n",
        "initial_torsion = components_check['torsion'].item()\n",
        "print(f\"STRESS TEST INITIALIZATION\")\n",
        "print(f\"Initial Torsion: {initial_torsion:.6f}\")\n",
        "print(f\"Joyce Threshold: {TORSION_THRESHOLD}\")\n",
        "\n",
        "if initial_torsion > TORSION_THRESHOLD:\n",
        "    print(\"✅ Model is successfully perturbed (High Torsion).\")\n",
        "else:\n",
        "    print(\"⚠️ Perturbation too weak, retrying...\")"
      ],
      "execution_count": 22,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "STRESS TEST INITIALIZATION\n",
            "Initial Torsion: 0.000000\n",
            "Joyce Threshold: 0.0288\n",
            "⚠️ Perturbation too weak, retrying...\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 95,
          "referenced_widgets": [
            "c2a08f58ecbc415faa45a956cbcc9f3f",
            "751a689cb74d42e7bf26a83b7187c7f1",
            "327baf613c5c49069a7e233e6b26e71f",
            "7310ccfe88084e04ad60c4577c7181f4",
            "ea0590995c554f1d992a7b5044dc6f56",
            "7917dcb2f2bb46b1a1901bcede573595",
            "fd447ebdecc74619839df2059dbce6e1",
            "1b6085777692426eb959401d390f0559",
            "0b2604933585488eb92291bbe84c5736",
            "7992e38b73b24b7aa5ec3a7417717381",
            "fff493f707034a07bbbe969b2b66d79d"
          ]
        },
        "id": "0d523e39",
        "outputId": "de13a76a-0401-4273-abd8-4786b80c766b"
      },
      "source": [
        "# 4. Train to recover\n",
        "optimizer_stress = torch.optim.AdamW(model_stress.parameters(), lr=5e-4)\n",
        "history_stress = {'torsion': [], 'loss': []}\n",
        "\n",
        "model_stress.train()\n",
        "pbar = tqdm(range(500), desc=\"Stress Test Recovery\")\n",
        "\n",
        "for epoch in pbar:\n",
        "    x = torch.rand(BATCH_SIZE, 7, device=device)\n",
        "\n",
        "    optimizer_stress.zero_grad()\n",
        "    loss, components = loss_fn(model_stress, x, return_components=True)\n",
        "    loss.backward()\n",
        "    optimizer_stress.step()\n",
        "\n",
        "    curr_torsion = components['torsion'].item()\n",
        "    history_stress['torsion'].append(curr_torsion)\n",
        "    history_stress['loss'].append(loss.item())\n",
        "\n",
        "    pbar.set_postfix({'torsion': f\"{curr_torsion:.6f}\"})\n",
        "\n",
        "print(f\"Final Torsion: {curr_torsion:.6f}\")"
      ],
      "execution_count": 23,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "Stress Test Recovery:   0%|          | 0/500 [00:00<?, ?it/s]"
            ],
            "application/vnd.jupyter.widget-view+json": {
              "version_major": 2,
              "version_minor": 0,
              "model_id": "c2a08f58ecbc415faa45a956cbcc9f3f"
            }
          },
          "metadata": {}
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Final Torsion: 0.000010\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 355
        },
        "id": "edd3bb98",
        "outputId": "d2076fce-b089-4770-81c5-f220e29b3d8d"
      },
      "source": [
        "plt.figure(figsize=(10, 5))\n",
        "plt.semilogy(history_stress['torsion'], label='Stress Test Torsion')\n",
        "plt.axhline(TORSION_THRESHOLD, color='r', linestyle='--', label='Joyce Threshold')\n",
        "plt.axhline(initial_torsion, color='orange', linestyle=':', label='Initial Perturbation')\n",
        "plt.title('Recovery from Strong Perturbation')\n",
        "plt.xlabel('Epoch')\n",
        "plt.ylabel('Torsion (Log Scale)')\n",
        "plt.legend()\n",
        "plt.grid(True, alpha=0.3)\n",
        "plt.show()"
      ],
      "execution_count": 24,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 1000x500 with 1 Axes>"
            ],
            "image/png": "[encoded data, 55236 characters]\n"
          },
          "metadata": {}
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "084f71a5",
        "outputId": "835c1b76-5bc5-4138-f66d-9e0dec8afad0"
      },
      "source": [
        "def read_file(path):\n",
        "    try:\n",
        "        with open(path, 'r') as f:\n",
        "            return f.read()\n",
        "    except Exception as e:\n",
        "        return str(e)\n",
        "\n",
        "print(\"=== Lean/GIFT/Foundations/G2Holonomy.lean ===\")\n",
        "print(read_file('core/Lean/GIFT/Foundations/G2Holonomy.lean')[:1000]) # Read first 1000 chars\n",
        "\n",
        "print(\"\\n=== gift_core/g2/g2_form.py ===\")\n",
        "print(read_file('core/gift_core/g2/g2_form.py'))"
      ],
      "execution_count": 21,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "=== Lean/GIFT/Foundations/G2Holonomy.lean ===\n",
            "-- GIFT Foundations: G₂ Holonomy\n",
            "-- Level 2 formalization: G₂ structure groups and holonomy\n",
            "--\n",
            "-- This module provides genuine differential-geometric content:\n",
            "-- - G₂ as stabilizer of the associative 3-form φ₀\n",
            "-- - dim(G₂) = 14 derived from orbit-stabilizer\n",
            "-- - G₂ decomposition of differential forms\n",
            "-- - Connection to K7 Betti numbers\n",
            "--\n",
            "-- References:\n",
            "--   - Joyce, \"Compact Manifolds with Special Holonomy\"\n",
            "--   - Bryant, \"Some remarks on G₂-structures\"\n",
            "\n",
            "import Mathlib.Analysis.InnerProductSpace.PiL2\n",
            "import Mathlib.LinearAlgebra.Dimension.Finrank\n",
            "import Mathlib.Data.Fin.VecNotation\n",
            "import Mathlib.Data.Real.Basic\n",
            "import Mathlib.Algebra.BigOperators.Group.Finset.Basic\n",
            "\n",
            "namespace GIFT.Foundations.G2Holonomy\n",
            "\n",
            "open Finset BigOperators\n",
            "\n",
            "/-!\n",
            "## The Associative 3-form φ₀\n",
            "\n",
            "The standard G₂ structure on ℝ⁷ is defined by the associative 3-form:\n",
            "  φ₀ = e¹²³ + e¹⁴⁵ + e¹⁶⁷ + e²⁴⁶ - e²⁵⁷ - e³⁴⁷ - e³⁵⁶\n",
            "\n",
            "where eⁱʲᵏ = eⁱ ∧ eʲ ∧ eᵏ.\n",
            "\n",
            "G₂ = { g ∈ GL(7,ℝ) | g*φ₀ = φ₀ } ⊂ SO(7)\n",
            "-/\n",
            "\n",
            "/-- The 7 terms in the ass\n",
            "\n",
            "=== gift_core/g2/g2_form.py ===\n",
            "\"\"\"\n",
            "G2 3-form and 4-form structures.\n",
            "\n",
            "The G2 structure on a 7-manifold is defined by a stable 3-form phi\n",
            "satisfying certain positivity conditions. From phi, we can recover:\n",
            "- The Riemannian metric g\n",
            "- The volume form vol_g\n",
            "- The 4-form psi = *phi\n",
            "\n",
            "Reference: Bryant (1987), Joyce (2000)\n",
            "\"\"\"\n",
            "\n",
            "from dataclasses import dataclass, field\n",
            "from typing import List, Tuple, Optional, Dict\n",
            "import numpy as np\n",
            "\n",
            "try:\n",
            "    import torch\n",
            "    from torch import Tensor\n",
            "    HAS_TORCH = True\n",
            "except ImportError:\n",
            "    HAS_TORCH = False\n",
            "    Tensor = np.ndarray\n",
            "\n",
            "\n",
            "# Standard G2 3-form structure (7 terms)\n",
            "# phi = e^{123} + e^{145} + e^{167} + e^{246} - e^{257} - e^{347} - e^{356}\n",
            "STANDARD_G2_INDICES = [\n",
            "    ((0, 1, 2), +1.0),  # e^123\n",
            "    ((0, 3, 4), +1.0),  # e^145\n",
            "    ((0, 5, 6), +1.0),  # e^167\n",
            "    ((1, 3, 5), +1.0),  # e^246\n",
            "    ((1, 4, 6), -1.0),  # e^257\n",
            "    ((2, 3, 6), -1.0),  # e^347\n",
            "    ((2, 4, 5), -1.0),  # e^356\n",
            "]\n",
            "\n",
            "# Standard *phi (G2 4-form) structure\n",
            "# *phi = e^{4567} + e^{2367} + e^{2345} + e^{1357} - e^{1346} - e^{1256} - e^{1247}\n",
            "STANDARD_G2_STAR_INDICES = [\n",
            "    ((3, 4, 5, 6), +1.0),  # e^{4567}\n",
            "    ((1, 2, 5, 6), +1.0),  # e^{2367}\n",
            "    ((1, 2, 3, 4), +1.0),  # e^{2345}\n",
            "    ((0, 2, 4, 6), +1.0),  # e^{1357}\n",
            "    ((0, 2, 3, 5), -1.0),  # e^{1346}\n",
            "    ((0, 1, 4, 5), -1.0),  # e^{1256}\n",
            "    ((0, 1, 3, 6), -1.0),  # e^{1247}\n",
            "]\n",
            "\n",
            "\n",
            "def _form_index_3(i: int, j: int, k: int) -> int:\n",
            "    \"\"\"Map (i, j, k) with i < j < k to linear index in C(7,3) = 35.\"\"\"\n",
            "    count = 0\n",
            "    for a in range(7):\n",
            "        for b in range(a + 1, 7):\n",
            "            for c in range(b + 1, 7):\n",
            "                if (a, b, c) == (i, j, k):\n",
            "                    return count\n",
            "                count += 1\n",
            "    raise ValueError(f\"Invalid indices: {i}, {j}, {k}\")\n",
            "\n",
            "\n",
            "def _form_index_4(i: int, j: int, k: int, l: int) -> int:\n",
            "    \"\"\"Map (i, j, k, l) with i < j < k < l to linear index in C(7,4) = 35.\"\"\"\n",
            "    count = 0\n",
            "    for a in range(7):\n",
            "        for b in range(a + 1, 7):\n",
            "            for c in range(b + 1, 7):\n",
            "                for d in range(c + 1, 7):\n",
            "                    if (a, b, c, d) == (i, j, k, l):\n",
            "                        return count\n",
            "                    count += 1\n",
            "    raise ValueError(f\"Invalid indices: {i}, {j}, {k}, {l}\")\n",
            "\n",
            "\n",
            "@dataclass\n",
            "class G2Form:\n",
            "    \"\"\"\n",
            "    G2 3-form phi on a 7-manifold.\n",
            "\n",
            "    A G2 structure is a reduction of the frame bundle to G2 c SO(7).\n",
            "    It is specified by a 3-form phi with stabilizer exactly G2.\n",
            "\n",
            "    The 3-form determines a metric via:\n",
            "    g_ij = (1/6) (phi, phi)_ij\n",
            "\n",
            "    where (phi, phi)_ij involves contractions with the volume form.\n",
            "\n",
            "    Attributes:\n",
            "        components: 35 independent components of the 3-form\n",
            "        is_normalized: Whether the form has unit norm\n",
            "    \"\"\"\n",
            "\n",
            "    components: np.ndarray  # Shape: (35,) or (N, 35) for batched\n",
            "    is_normalized: bool = False\n",
            "\n",
            "    def __post_init__(self):\n",
            "        \"\"\"Validate and process components.\"\"\"\n",
            "        if self.components.ndim == 1:\n",
            "            self.components = self.components.reshape(1, -1)\n",
            "        assert self.components.shape[-1] == 35, \"G2 3-form has 35 components\"\n",
            "\n",
            "    @property\n",
            "    def batch_size(self) -> int:\n",
            "        \"\"\"Number of forms in batch.\"\"\"\n",
            "        return self.components.shape[0]\n",
            "\n",
            "    def full_tensor(self) -> np.ndarray:\n",
            "        \"\"\"\n",
            "        Construct full antisymmetric 3-tensor.\n",
            "\n",
            "        Returns:\n",
            "            Shape (N, 7, 7, 7) antisymmetric tensor\n",
            "        \"\"\"\n",
            "        N = self.batch_size\n",
            "        phi = np.zeros((N, 7, 7, 7))\n",
            "\n",
            "        # Fill from components using lexicographic ordering\n",
            "        idx = 0\n",
            "        for i in range(7):\n",
            "            for j in range(i + 1, 7):\n",
            "                for k in range(j + 1, 7):\n",
            "                    val = self.components[:, idx]\n",
            "                    # All antisymmetric permutations\n",
            "                    phi[:, i, j, k] = val\n",
            "                    phi[:, j, k, i] = val\n",
            "                    phi[:, k, i, j] = val\n",
            "                    phi[:, j, i, k] = -val\n",
            "                    phi[:, i, k, j] = -val\n",
            "                    phi[:, k, j, i] = -val\n",
            "                    idx += 1\n",
            "\n",
            "        return phi\n",
            "\n",
            "    def metric(self) -> np.ndarray:\n",
            "        \"\"\"\n",
            "        Extract metric g_ij from phi.\n",
            "\n",
            "        The metric is given by:\n",
            "        g_ij * vol = (1/6) * (e_i _| phi) ^ (e_j _| phi) ^ phi\n",
            "\n",
            "        For the standard phi, this gives the flat metric.\n",
            "\n",
            "        Returns:\n",
            "            Metric tensors, shape (N, 7, 7)\n",
            "        \"\"\"\n",
            "        N = self.batch_size\n",
            "        phi = self.full_tensor()\n",
            "\n",
            "        # Compute metric via contraction formula\n",
            "        # g_ij = (1/144) * phi_ikl * phi_jmn * eps^{klmnpqr} * phi_pqr / sqrt(det)\n",
            "\n",
            "        # Simplified: for near-standard form, use approximation\n",
            "        g = np.zeros((N, 7, 7))\n",
            "\n",
            "        # Contract phi with itself\n",
            "        for i in range(7):\n",
            "            for j in range(7):\n",
            "                for k in range(7):\n",
            "                    for l in range(7):\n",
            "                        for m in range(7):\n",
            "                            for n in range(7):\n",
            "                                g[:, i, j] += phi[:, i, k, l] * phi[:, j, m, n]\n",
            "\n",
            "        # Normalize\n",
            "        g /= 36.0\n",
            "\n",
            "        return g\n",
            "\n",
            "    def volume_form(self) -> np.ndarray:\n",
            "        \"\"\"\n",
            "        Compute volume form from metric.\n",
            "\n",
            "        vol = sqrt(det(g)) * e^{1234567}\n",
            "\n",
            "        Returns:\n",
            "            Volume factor, shape (N,)\n",
            "        \"\"\"\n",
            "        g = self.metric()\n",
            "        return np.sqrt(np.abs(np.linalg.det(g)))\n",
            "\n",
            "    @property\n",
            "    def det_g(self) -> np.ndarray:\n",
            "        \"\"\"Determinant of induced metric.\"\"\"\n",
            "        g = self.metric()\n",
            "        return np.linalg.det(g)\n",
            "\n",
            "    def normalize(self) -> 'G2Form':\n",
            "        \"\"\"\n",
            "        Return normalized G2 form with det(g) = 1.\n",
            "\n",
            "        Returns:\n",
            "            Normalized G2Form\n",
            "        \"\"\"\n",
            "        det = self.det_g\n",
            "        scale = det ** (-1/6)\n",
            "        new_components = self.components * scale[:, np.newaxis]\n",
            "        return G2Form(components=new_components, is_normalized=True)\n",
            "\n",
            "    def scale_to_det(self, target_det: float) -> 'G2Form':\n",
            "        \"\"\"\n",
            "        Scale phi to achieve target metric determinant.\n",
            "\n",
            "        Args:\n",
            "            target_det: Target value for det(g)\n",
            "\n",
            "        Returns:\n",
            "            Scaled G2Form\n",
            "        \"\"\"\n",
            "        current_det = self.det_g\n",
            "        # det(g) ~ phi^{14/3}, so phi ~ det^{3/14}\n",
            "        scale = (target_det / current_det) ** (3/14)\n",
            "        new_components = self.components * scale[:, np.newaxis]\n",
            "        return G2Form(components=new_components)\n",
            "\n",
            "    def hodge_star(self) -> 'G2Form4':\n",
            "        \"\"\"\n",
            "        Compute *phi (the G2 4-form psi).\n",
            "\n",
            "        Returns:\n",
            "            G2Form4 representing *phi\n",
            "        \"\"\"\n",
            "        # For standard phi, *phi has known form\n",
            "        N = self.batch_size\n",
            "        psi_components = np.zeros((N, 35))\n",
            "\n",
            "        # Use standard *phi structure if phi is near-standard\n",
            "        phi_std = standard_g2_form()\n",
            "        phi_diff = np.linalg.norm(self.components - phi_std.components, axis=-1)\n",
            "\n",
            "        if np.all(phi_diff < 0.1):\n",
            "            # Near standard: use standard *phi\n",
            "            psi = G2Form4.standard()\n",
            "            return psi\n",
            "\n",
            "        # General case: compute via metric and Hodge star\n",
            "        # This is computationally expensive\n",
            "        return self._compute_hodge_star()\n",
            "\n",
            "    def _compute_hodge_star(self) -> 'G2Form4':\n",
            "        \"\"\"Compute Hodge star via full contraction.\"\"\"\n",
            "        N = self.batch_size\n",
            "        phi = self.full_tensor()\n",
            "        g = self.metric()\n",
            "        vol = self.volume_form()\n",
            "\n",
            "        psi = np.zeros((N, 7, 7, 7, 7))\n",
            "\n",
            "        # *phi_{ijkl} = (1/6) eps_{ijklmnp} phi^{mnp}\n",
            "        # where phi^{mnp} = g^{mm'} g^{nn'} g^{pp'} phi_{m'n'p'}\n",
            "\n",
            "        # Raise indices\n",
            "        g_inv = np.linalg.inv(g)\n",
            "        phi_up = np.einsum('nab,ncd,nef,nbdf->nace', g_inv, g_inv, g_inv, phi)\n",
            "\n",
            "        # Contract with epsilon (simplified)\n",
            "        # For now, return standard form\n",
            "        return G2Form4.standard()\n",
            "\n",
            "    @classmethod\n",
            "    def from_metric(cls, g: np.ndarray) -> 'G2Form':\n",
            "        \"\"\"\n",
            "        Construct G2 form from a metric (if G2-compatible).\n",
            "\n",
            "        This is the inverse problem: given g, find phi such that\n",
            "        the G2 metric equals g.\n",
            "\n",
            "        Args:\n",
            "            g: Metric tensors, shape (N, 7, 7)\n",
            "\n",
            "        Returns:\n",
            "            G2Form compatible with the metric\n",
            "        \"\"\"\n",
            "        # This requires solving a nonlinear system\n",
            "        # Start with standard phi and scale\n",
            "        phi = standard_g2_form()\n",
            "\n",
            "        det_g = np.linalg.det(g)\n",
            "        det_std = phi.det_g\n",
            "\n",
            "        # Scale to match determinant\n",
            "        return phi.scale_to_det(float(np.mean(det_g)))\n",
            "\n",
            "    @classmethod\n",
            "    def standard(cls) -> 'G2Form':\n",
            "        \"\"\"\n",
            "        Create standard G2 3-form.\n",
            "\n",
            "        phi_0 = e^{123} + e^{145} + e^{167} + e^{246} - e^{257} - e^{347} - e^{356}\n",
            "\n",
            "        Returns:\n",
            "            Standard G2Form\n",
            "        \"\"\"\n",
            "        components = np.zeros(35)\n",
            "\n",
            "        for (indices, sign) in STANDARD_G2_INDICES:\n",
            "            idx = _form_index_3(*indices)\n",
            "            components[idx] = sign\n",
            "\n",
            "        return cls(components=components)\n",
            "\n",
            "\n",
            "@dataclass\n",
            "class G2Form4:\n",
            "    \"\"\"\n",
            "    G2 4-form psi = *phi on a 7-manifold.\n",
            "\n",
            "    The 4-form is the Hodge dual of the G2 3-form with respect\n",
            "    to the G2 metric.\n",
            "\n",
            "    Attributes:\n",
            "        components: 35 independent components of the 4-form\n",
            "    \"\"\"\n",
            "\n",
            "    components: np.ndarray  # Shape: (35,) or (N, 35)\n",
            "\n",
            "    def __post_init__(self):\n",
            "        \"\"\"Validate components.\"\"\"\n",
            "        if self.components.ndim == 1:\n",
            "            self.components = self.components.reshape(1, -1)\n",
            "        assert self.components.shape[-1] == 35, \"G2 4-form has 35 components\"\n",
            "\n",
            "    @property\n",
            "    def batch_size(self) -> int:\n",
            "        \"\"\"Number of forms in batch.\"\"\"\n",
            "        return self.components.shape[0]\n",
            "\n",
            "    def full_tensor(self) -> np.ndarray:\n",
            "        \"\"\"\n",
            "        Construct full antisymmetric 4-tensor.\n",
            "\n",
            "        Returns:\n",
            "            Shape (N, 7, 7, 7, 7) antisymmetric tensor\n",
            "        \"\"\"\n",
            "        N = self.batch_size\n",
            "        psi = np.zeros((N, 7, 7, 7, 7))\n",
            "\n",
            "        # Fill from components\n",
            "        idx = 0\n",
            "        for i in range(7):\n",
            "            for j in range(i + 1, 7):\n",
            "                for k in range(j + 1, 7):\n",
            "                    for l in range(k + 1, 7):\n",
            "                        val = self.components[:, idx]\n",
            "                        # Antisymmetrize (24 permutations)\n",
            "                        for perm, sign in _antisymm_4():\n",
            "                            ii, jj, kk, ll = [\n",
            "                                [i, j, k, l][p] for p in perm\n",
            "                            ]\n",
            "                            psi[:, ii, jj, kk, ll] = sign * val\n",
            "                        idx += 1\n",
            "\n",
            "        return psi\n",
            "\n",
            "    @classmethod\n",
            "    def standard(cls) -> 'G2Form4':\n",
            "        \"\"\"\n",
            "        Create standard G2 4-form *phi_0.\n",
            "\n",
            "        *phi_0 = e^{4567} + e^{2367} + e^{2345} + e^{1357}\n",
            "               - e^{1346} - e^{1256} - e^{1247}\n",
            "\n",
            "        Returns:\n",
            "            Standard G2Form4\n",
            "        \"\"\"\n",
            "        components = np.zeros(35)\n",
            "\n",
            "        for (indices, sign) in STANDARD_G2_STAR_INDICES:\n",
            "            idx = _form_index_4(*indices)\n",
            "            components[idx] = sign\n",
            "\n",
            "        return cls(components=components)\n",
            "\n",
            "\n",
            "def _antisymm_4():\n",
            "    \"\"\"Generate antisymmetric permutations for 4-form.\"\"\"\n",
            "    from itertools import permutations\n",
            "\n",
            "    result = []\n",
            "    for perm in permutations([0, 1, 2, 3]):\n",
            "        # Count inversions for sign\n",
            "        inversions = 0\n",
            "        for i in range(4):\n",
            "            for j in range(i + 1, 4):\n",
            "                if perm[i] > perm[j]:\n",
            "                    inversions += 1\n",
            "        sign = (-1) ** inversions\n",
            "        result.append((perm, sign))\n",
            "\n",
            "    return result\n",
            "\n",
            "\n",
            "def standard_g2_form() -> G2Form:\n",
            "    \"\"\"Create the standard flat G2 3-form.\"\"\"\n",
            "    return G2Form.standard()\n",
            "\n",
            "\n",
            "def random_g2_form(n_forms: int = 1, scale: float = 0.1,\n",
            "                   seed: Optional[int] = None) -> G2Form:\n",
            "    \"\"\"\n",
            "    Create random G2 forms near the standard form.\n",
            "\n",
            "    Args:\n",
            "        n_forms: Number of forms to generate\n",
            "        scale: Perturbation scale\n",
            "        seed: Random seed\n",
            "\n",
            "    Returns:\n",
            "        G2Form with perturbed components\n",
            "    \"\"\"\n",
            "    if seed is not None:\n",
            "        np.random.seed(seed)\n",
            "\n",
            "    std = standard_g2_form()\n",
            "    perturbation = scale * np.random.randn(n_forms, 35)\n",
            "    components = std.components + perturbation\n",
            "\n",
            "    return G2Form(components=components)\n",
            "\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "889b3b5d",
        "outputId": "fb295667-b44d-4c04-acb9-5feba5ee5c8e"
      },
      "source": [
        "import os\n",
        "\n",
        "def list_files(startpath):\n",
        "    for root, dirs, files in os.walk(startpath):\n",
        "        level = root.replace(startpath, '').count(os.sep)\n",
        "        indent = ' ' * 4 * (level)\n",
        "        print('{}{}/'.format(indent, os.path.basename(root)))\n",
        "        subindent = ' ' * 4 * (level + 1)\n",
        "        for f in files:\n",
        "            print('{}{}'.format(subindent, f))\n",
        "\n",
        "list_files('core')"
      ],
      "execution_count": 20,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "core/\n",
            "    README.md\n",
            "    .gitignore\n",
            "    pyproject.toml\n",
            "    gift_core_plan.md\n",
            "    MANIFEST.in\n",
            "    LICENSE\n",
            "    lakefile.toml\n",
            "    CHANGELOG.md\n",
            "    giftpy-docs.tar.gz\n",
            "    run_dashboard.py\n",
            "    lean-toolchain\n",
            "    CLAUDE.md\n",
            "    blueprint/\n",
            "        lean_decls\n",
            "        src/\n",
            "            blueprint.sty\n",
            "            latexmkrc\n",
            "            extra_styles.css\n",
            "            references.bib\n",
            "            content.tex\n",
            "            print.tex\n",
            "            plastex.cfg\n",
            "            web.tex\n",
            "            macros/\n",
            "                common.tex\n",
            "                print.tex\n",
            "                web.tex\n",
            "    COQ/\n",
            "        _CoqProject\n",
            "        Makefile\n",
            "        Certificate/\n",
            "            AllProven.v\n",
            "        Topology/\n",
            "            Betti.v\n",
            "        Algebra/\n",
            "            E8.v\n",
            "            G2.v\n",
            "        Geometry/\n",
            "            K7.v\n",
            "            Jordan.v\n",
            "        Relations/\n",
            "            Physical.v\n",
            "            BaseDecomposition.v\n",
            "            Cosmology.v\n",
            "            ExceptionalGroups.v\n",
            "            GaugeSector.v\n",
            "            ExceptionalChain.v\n",
            "            Weinberg.v\n",
            "            LeptonSector.v\n",
            "            YukawaDuality.v\n",
            "            MassFactorization.v\n",
            "            GoldenRatio.v\n",
            "            IrrationalSector.v\n",
            "            NeutrinoSector.v\n",
            "    .devcontainer/\n",
            "        Dockerfile\n",
            "        devcontainer.json\n",
            "    tests/\n",
            "        test_torch_optim.py\n",
            "        test_constants.py\n",
            "        test_v2_primes.py\n",
            "        test_yukawa_duality.py\n",
            "        test_v2_monster_mckay.py\n",
            "        test_monte_carlo.py\n",
            "        test_v2_sequences.py\n",
            "        test_topological_extension.py\n",
            "        __init__.py\n",
            "        test_relations.py\n",
            "        test_k7_metric.py\n",
            "    Lean/\n",
            "        GIFT.lean\n",
            "        lakefile.toml\n",
            "        lean-toolchain\n",
            "        GIFT/\n",
            "            Primes.lean\n",
            "            Core.lean\n",
            "            Algebra.lean\n",
            "            Foundations.lean\n",
            "            Joyce.lean\n",
            "            ImplicitFunction.lean\n",
            "            Topology.lean\n",
            "            Monster.lean\n",
            "            McKay.lean\n",
            "            Sequences.lean\n",
            "            Algebraic.lean\n",
            "            IntervalArithmetic.lean\n",
            "            Sobolev.lean\n",
            "            DifferentialForms.lean\n",
            "            Relations.lean\n",
            "            Certificate.lean\n",
            "            Geometry.lean\n",
            "            Primes/\n",
            "                Generators.lean\n",
            "                Tier2.lean\n",
            "                Heegner.lean\n",
            "                Special.lean\n",
            "                Tier1.lean\n",
            "            Monster/\n",
            "                Dimension.lean\n",
            "                JInvariant.lean\n",
            "            Foundations/\n",
            "                RootSystems.lean\n",
            "                G2Holonomy.lean\n",
            "                E8Lattice.lean\n",
            "                GoldenRatio.lean\n",
            "                E8Mathlib.lean\n",
            "                TCSConstruction.lean\n",
            "                GraphTheory.lean\n",
            "                RationalConstants.lean\n",
            "                G2CrossProduct.lean\n",
            "                V5/\n",
            "                    HodgeTheory.lean\n",
            "                    JoyceAnalytic.lean\n",
            "                    E8Lattice.lean\n",
            "                    WedgeProduct.lean\n",
            "                    InnerProductSpace.lean\n",
            "                    G2TensorForm.lean\n",
            "                    ExteriorAlgebra.lean\n",
            "                    HarmonicForms.lean\n",
            "            Sequences/\n",
            "                Lucas.lean\n",
            "                Fibonacci.lean\n",
            "                Recurrence.lean\n",
            "            McKay/\n",
            "                Correspondence.lean\n",
            "                GoldenEmergence.lean\n",
            "            Algebraic/\n",
            "                GIFTConstants.lean\n",
            "                G2.lean\n",
            "                BettiNumbers.lean\n",
            "                SO16Decomposition.lean\n",
            "                GeometricSaturation.lean\n",
            "                Octonions.lean\n",
            "                CayleyDickson.lean\n",
            "                Quaternions.lean\n",
            "            Relations/\n",
            "                LeptonSector.lean\n",
            "                ExceptionalGroups.lean\n",
            "                IrrationalSector.lean\n",
            "                GaugeSector.lean\n",
            "                GoldenRatio.lean\n",
            "                QuarkSector.lean\n",
            "                SO16Relations.lean\n",
            "                Structural.lean\n",
            "                Cosmology.lean\n",
            "                MassFactorization.lean\n",
            "                YukawaDuality.lean\n",
            "                NeutrinoSector.lean\n",
            "                ExceptionalChain.lean\n",
            "                LandauerDarkEnergy.lean\n",
            "                BaseDecomposition.lean\n",
            "    gift_core/\n",
            "        dashboard.py\n",
            "        _version.py\n",
            "        constants.py\n",
            "        torch_optim.py\n",
            "        topology.py\n",
            "        scales.py\n",
            "        monte_carlo.py\n",
            "        relations.py\n",
            "        experimental.py\n",
            "        __init__.py\n",
            "        g2/\n",
            "            constraints.py\n",
            "            g2_form.py\n",
            "            torsion.py\n",
            "            __init__.py\n",
            "            holonomy.py\n",
            "        verification/\n",
            "            numerical_bounds.py\n",
            "            lean_export.py\n",
            "            __init__.py\n",
            "            certificate.py\n",
            "        physics/\n",
            "            coupling_constants.py\n",
            "            mass_spectrum.py\n",
            "            __init__.py\n",
            "            yukawa_tensor.py\n",
            "        nn/\n",
            "            training.py\n",
            "            loss_functions.py\n",
            "            fourier_features.py\n",
            "            g2_pinn.py\n",
            "            __init__.py\n",
            "        sequences/\n",
            "            __init__.py\n",
            "        analysis/\n",
            "            joyce_certificate.py\n",
            "            intervals.py\n",
            "            __init__.py\n",
            "        harmonic/\n",
            "            betti_validation.py\n",
            "            harmonic_forms.py\n",
            "            hodge_laplacian.py\n",
            "            __init__.py\n",
            "        mckay/\n",
            "            __init__.py\n",
            "        pipeline/\n",
            "            config.py\n",
            "            full_pipeline.py\n",
            "            __init__.py\n",
            "        monster/\n",
            "            __init__.py\n",
            "        primes/\n",
            "            __init__.py\n",
            "        geometry/\n",
            "            tcs_construction.py\n",
            "            k7_metric.py\n",
            "            acyl_cy3.py\n",
            "            __init__.py\n",
            "            k3_surface.py\n",
            "    docs/\n",
            "        USAGE.md\n",
            "        tmp/\n",
            "            GIFT_PINN_LEAN_STRATEGY.md\n",
            "            B4_direct_verification.py\n",
            "            .gitkeep\n",
            "            GIFT_BLUEPRINT_IMPLEMENTATION_PLAN.md\n",
            "            GIFT_ACTION_PLAN.md\n",
            "            GIFT_v3.1_LITERATURE_ANALYSIS.md\n",
            "            GIFT_BLUEPRINT_STRATEGIC_PLAN.md\n",
            "            B4_Lagrange_Proof.lean\n",
            "    .git/\n",
            "        HEAD\n",
            "        index\n",
            "        packed-refs\n",
            "        config\n",
            "        description\n",
            "        branches/\n",
            "        objects/\n",
            "            pack/\n",
            "                pack-087ee249ca8e133ab044bceffb18ea9740b72f92.idx\n",
            "                pack-087ee249ca8e133ab044bceffb18ea9740b72f92.pack\n",
            "            info/\n",
            "        logs/\n",
            "            HEAD\n",
            "            refs/\n",
            "                heads/\n",
            "                    main\n",
            "                remotes/\n",
            "                    origin/\n",
            "                        HEAD\n",
            "        hooks/\n",
            "            prepare-commit-msg.sample\n",
            "            pre-receive.sample\n",
            "            post-update.sample\n",
            "            applypatch-msg.sample\n",
            "            commit-msg.sample\n",
            "            pre-merge-commit.sample\n",
            "            push-to-checkout.sample\n",
            "            pre-push.sample\n",
            "            update.sample\n",
            "            pre-applypatch.sample\n",
            "            pre-commit.sample\n",
            "            pre-rebase.sample\n",
            "            fsmonitor-watchman.sample\n",
            "        refs/\n",
            "            heads/\n",
            "                main\n",
            "            tags/\n",
            "            remotes/\n",
            "                origin/\n",
            "                    HEAD\n",
            "        info/\n",
            "            exclude\n",
            "    home_page/\n",
            "        Gemfile\n",
            "        404.html\n",
            "        index.md\n",
            "        _config.yml\n",
            "        _layouts/\n",
            "            default.html\n",
            "    .github/\n",
            "        workflows/\n",
            "            test.yml\n",
            "            blueprint.yml\n",
            "            verify.yml\n",
            "            publish.yml\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 219
        },
        "id": "XI0bKlrpjhvJ",
        "outputId": "3201ca4f-c136-4426-820b-339fec11cf99"
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 1200x400 with 2 Axes>"
            ],
            "image/png": "[encoded data, 38732 characters]\n"
          },
          "metadata": {}
        }
      ],
      "source": [
        "# Histograms\n",
        "fig, axes = plt.subplots(1, 2, figsize=(12, 4))\n",
        "\n",
        "axes[0].hist(det_eval.cpu().numpy(), bins=50, density=True, alpha=0.7)\n",
        "axes[0].axvline(DET_G_TARGET_FLOAT, color='r', linestyle='--', label='Target 65/32')\n",
        "axes[0].set_xlabel('det(g)'); axes[0].set_title('det(g) Distribution'); axes[0].legend()\n",
        "\n",
        "axes[1].hist(adjoint_eval.cpu().numpy().flatten(), bins=50, density=True, alpha=0.7)\n",
        "axes[1].set_xlabel('Adjoint params'); axes[1].set_title('G2 Adjoint Distribution')\n",
        "\n",
        "plt.tight_layout()\n",
        "plt.savefig('evaluation.png', dpi=150)\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "oRoFYudWjhvJ"
      },
      "source": [
        "## 11. Save Model"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "ZMvpDRrojhvJ",
        "outputId": "0b0f6fcc-bd5f-4be9-f647-f0cbc9445553"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Model saved to: gift_pinn_trained.pt\n"
          ]
        }
      ],
      "source": [
        "torch.save({\n",
        "    'model_state_dict': model.state_dict(),\n",
        "    'best_state_dict': best_state,\n",
        "    'history': history,\n",
        "    'best_torsion': best_torsion,\n",
        "    'config': {\n",
        "        'num_frequencies': 32,\n",
        "        'hidden_dims': [128, 128, 128],\n",
        "        'perturbation_scale': 0.01,\n",
        "    }\n",
        "}, 'gift_pinn_trained.pt')\n",
        "\n",
        "print(\"Model saved to: gift_pinn_trained.pt\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xrfyU39DjhvJ"
      },
      "source": [
        "## 12. Summary"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "MO9cQsxfjhvJ",
        "outputId": "8ae20901-63ed-4ce5-a304-804f5d88abf9"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "============================================================\n",
            "  GIFT-Native PINN Training Summary\n",
            "============================================================\n",
            "  [X] Torsion < 0.001: 0.000000\n",
            "  [X] |det(g) - 65/32| < 1e-05: 0.00e+00\n",
            "\n",
            "SUCCESS: All criteria met!\n"
          ]
        }
      ],
      "source": [
        "torsion_ok = best_torsion < TARGET_TORSION\n",
        "det_ok = abs(det_eval.mean().item() - DET_G_TARGET_FLOAT) < TARGET_DET_ERROR\n",
        "\n",
        "print(\"=\" * 60)\n",
        "print(\"  GIFT-Native PINN Training Summary\")\n",
        "print(\"=\" * 60)\n",
        "print(f\"  [{'X' if torsion_ok else ' '}] Torsion < {TARGET_TORSION}: {best_torsion:.6f}\")\n",
        "print(f\"  [{'X' if det_ok else ' '}] |det(g) - 65/32| < {TARGET_DET_ERROR}: {abs(det_eval.mean().item() - DET_G_TARGET_FLOAT):.2e}\")\n",
        "print()\n",
        "if torsion_ok and det_ok:\n",
        "    print(\"SUCCESS: All criteria met!\")\n",
        "else:\n",
        "    print(\"Training may need more epochs.\")"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    },
    "language_info": {
      "name": "python",
      "version": "3.10.0"
    },
    "colab": {
      "provenance": [],
      "gpuType": "A100",
      "machine_shape": "hm",
      "include_colab_link": true
    },
    "accelerator": "GPU",
    "widgets": {
      "application/vnd.jupyter.widget-state+json": {
        "4369551212c140e5bc8bd00304fdb20b": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "HBoxModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "HBoxModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "HBoxView",
            "box_style": "",
            "children": [
              "IPY_MODEL_82e22a644a7d468db9423968008b82b6",
              "IPY_MODEL_51922aa4960a4b2f88261ed537c16b28",
              "IPY_MODEL_801a59ceb85d4de988f44a930734b8a7"
            ],
            "layout": "IPY_MODEL_bb84b02a06484c0998d664de7001a0b2"
          }
        },
        "82e22a644a7d468db9423968008b82b6": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "HTMLModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "HTMLModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "HTMLView",
            "description": "",
            "description_tooltip": null,
            "layout": "IPY_MODEL_b86adbfd2c494025ac2f7c99f256b1fc",
            "placeholder": "​",
            "style": "IPY_MODEL_027f70f3651e4e24af155063014cb94d",
            "value": "Training:   0%"
          }
        },
        "51922aa4960a4b2f88261ed537c16b28": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "FloatProgressModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "FloatProgressModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "ProgressView",
            "bar_style": "danger",
            "description": "",
            "description_tooltip": null,
            "layout": "IPY_MODEL_9e309128c3fb475ab37519c19f5c4b54",
            "max": 3000,
            "min": 0,
            "orientation": "horizontal",
            "style": "IPY_MODEL_488c83ac9eb7423ea3f7a7416bc354e8",
            "value": 0
          }
        },
        "801a59ceb85d4de988f44a930734b8a7": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "HTMLModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "HTMLModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "HTMLView",
            "description": "",
            "description_tooltip": null,
            "layout": "IPY_MODEL_4f97e7981cb54a53945b1faf99a37146",
            "placeholder": "​",
            "style": "IPY_MODEL_d5ebe15f55fd483f8ffe407f4f37fc07",
            "value": " 0/3000 [00:00&lt;?, ?it/s, loss=0.0000, torsion=0.000000, det_err=0.000000]"
          }
        },
        "bb84b02a06484c0998d664de7001a0b2": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "b86adbfd2c494025ac2f7c99f256b1fc": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "027f70f3651e4e24af155063014cb94d": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "DescriptionStyleModel",
          "model_module_version": "1.5.0",
          "state": {
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "DescriptionStyleModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "StyleView",
            "description_width": ""
          }
        },
        "9e309128c3fb475ab37519c19f5c4b54": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "488c83ac9eb7423ea3f7a7416bc354e8": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "ProgressStyleModel",
          "model_module_version": "1.5.0",
          "state": {
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "ProgressStyleModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "StyleView",
            "bar_color": null,
            "description_width": ""
          }
        },
        "4f97e7981cb54a53945b1faf99a37146": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "d5ebe15f55fd483f8ffe407f4f37fc07": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "DescriptionStyleModel",
          "model_module_version": "1.5.0",
          "state": {
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "DescriptionStyleModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "StyleView",
            "description_width": ""
          }
        },
        "c2a08f58ecbc415faa45a956cbcc9f3f": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "HBoxModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "HBoxModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "HBoxView",
            "box_style": "",
            "children": [
              "IPY_MODEL_751a689cb74d42e7bf26a83b7187c7f1",
              "IPY_MODEL_327baf613c5c49069a7e233e6b26e71f",
              "IPY_MODEL_7310ccfe88084e04ad60c4577c7181f4"
            ],
            "layout": "IPY_MODEL_ea0590995c554f1d992a7b5044dc6f56"
          }
        },
        "751a689cb74d42e7bf26a83b7187c7f1": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "HTMLModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "HTMLModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "HTMLView",
            "description": "",
            "description_tooltip": null,
            "layout": "IPY_MODEL_7917dcb2f2bb46b1a1901bcede573595",
            "placeholder": "​",
            "style": "IPY_MODEL_fd447ebdecc74619839df2059dbce6e1",
            "value": "Stress Test Recovery: 100%"
          }
        },
        "327baf613c5c49069a7e233e6b26e71f": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "FloatProgressModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "FloatProgressModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "ProgressView",
            "bar_style": "success",
            "description": "",
            "description_tooltip": null,
            "layout": "IPY_MODEL_1b6085777692426eb959401d390f0559",
            "max": 500,
            "min": 0,
            "orientation": "horizontal",
            "style": "IPY_MODEL_0b2604933585488eb92291bbe84c5736",
            "value": 500
          }
        },
        "7310ccfe88084e04ad60c4577c7181f4": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "HTMLModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "HTMLModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "HTMLView",
            "description": "",
            "description_tooltip": null,
            "layout": "IPY_MODEL_7992e38b73b24b7aa5ec3a7417717381",
            "placeholder": "​",
            "style": "IPY_MODEL_fff493f707034a07bbbe969b2b66d79d",
            "value": " 500/500 [00:35&lt;00:00, 14.23it/s, torsion=0.000010]"
          }
        },
        "ea0590995c554f1d992a7b5044dc6f56": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "7917dcb2f2bb46b1a1901bcede573595": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "fd447ebdecc74619839df2059dbce6e1": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "DescriptionStyleModel",
          "model_module_version": "1.5.0",
          "state": {
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "DescriptionStyleModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "StyleView",
            "description_width": ""
          }
        },
        "1b6085777692426eb959401d390f0559": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "0b2604933585488eb92291bbe84c5736": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "ProgressStyleModel",
          "model_module_version": "1.5.0",
          "state": {
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "ProgressStyleModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "StyleView",
            "bar_color": null,
            "description_width": ""
          }
        },
        "7992e38b73b24b7aa5ec3a7417717381": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "fff493f707034a07bbbe969b2b66d79d": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "DescriptionStyleModel",
          "model_module_version": "1.5.0",
          "state": {
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "DescriptionStyleModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "StyleView",
            "description_width": ""
          }
        }
      }
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}