{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":101849,"databundleVersionId":13093295,"sourceType":"competition"},{"sourceId":248968822,"sourceType":"kernelVersion"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np \nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport torch\nimport torch.nn as nn\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torchvision\nimport torchvision.transforms as transforms\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nfrom tqdm import tqdm\nfrom sklearn.model_selection import train_test_split\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-08-17T06:18:08.113849Z","iopub.execute_input":"2025-08-17T06:18:08.114468Z","iopub.status.idle":"2025-08-17T06:18:13.493071Z","shell.execute_reply.started":"2025-08-17T06:18:08.11444Z","shell.execute_reply":"2025-08-17T06:18:13.492197Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data_train = np.load('/kaggle/input/complete-lightweight-data/data_train.npy', mmap_mode='r')\ndata_train_FGS = np.load('/kaggle/input/complete-lightweight-data/data_train_FGS.npy', mmap_mode='r')\ntrain_df = pd.read_csv(\"/kaggle/input/ariel-data-challenge-2025/train.csv\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T06:18:13.494973Z","iopub.execute_input":"2025-08-17T06:18:13.495483Z","iopub.status.idle":"2025-08-17T06:18:13.706027Z","shell.execute_reply.started":"2025-08-17T06:18:13.495455Z","shell.execute_reply":"2025-08-17T06:18:13.705173Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data_train.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T06:18:13.706976Z","iopub.execute_input":"2025-08-17T06:18:13.707272Z","iopub.status.idle":"2025-08-17T06:18:13.713987Z","shell.execute_reply.started":"2025-08-17T06:18:13.70725Z","shell.execute_reply":"2025-08-17T06:18:13.713121Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data_train_FGS.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T06:18:13.716089Z","iopub.execute_input":"2025-08-17T06:18:13.716883Z","iopub.status.idle":"2025-08-17T06:18:13.737054Z","shell.execute_reply.started":"2025-08-17T06:18:13.716856Z","shell.execute_reply":"2025-08-17T06:18:13.736157Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T06:23:02.463163Z","iopub.status.idle":"2025-08-17T06:23:02.463557Z","shell.execute_reply.started":"2025-08-17T06:23:02.463365Z","shell.execute_reply":"2025-08-17T06:23:02.463382Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nfrom tqdm import tqdm\n\ndef get_stats_in_batches(data_array, indices, batch_size=16):\n    \"\"\"\n    Calculates mean and std deviation of a large, memory-mapped numpy array \n    iteratively in batches without loading the whole array into RAM.\n    \"\"\"\n    num_samples = len(indices)\n    # Get the shape of a single data point to initialize our accumulators\n    sample_shape = data_array[0].shape \n    \n    # --- PASS 1: Calculate Mean ---\n    print(\"Pass 1/2: Calculating mean...\")\n    # Use float64 for accumulators to maintain precision\n    mean_sum = np.zeros(sample_shape, dtype=np.float64)\n    \n    for i in tqdm(range(0, num_samples, batch_size)):\n        # Correctly slice the indices for the current batch\n        batch_indices = indices[i:i+batch_size]\n        # Load only the data for the current batch using the correct indices\n        batch_data = data_array[batch_indices]\n        # Sum along the batch dimension (axis=0)\n        mean_sum += batch_data.sum(axis=0)\n        \n    global_mean = mean_sum / num_samples\n\n    # --- PASS 2: Calculate Standard Deviation ---\n    print(\"\\nPass 2/2: Calculating standard deviation...\")\n    std_sum_sq = np.zeros(sample_shape, dtype=np.float64)\n    \n    for i in tqdm(range(0, num_samples, batch_size)):\n        batch_indices = indices[i:i+batch_size]\n        batch_data = data_array[batch_indices]\n        # Sum the squared differences along the batch dimension (axis=0)\n        std_sum_sq += ((batch_data - global_mean)**2).sum(axis=0)\n        \n    global_std = np.sqrt(std_sum_sq / num_samples)\n\n    # Add a small epsilon to std to avoid division by zero\n    global_std[global_std == 0] = 1e-9\n\n    print(\"\\nCalculation complete.\")\n    # Return as float32, which is what PyTorch expects\n    return global_mean.astype(np.float32), global_std.astype(np.float32)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T06:18:13.738077Z","iopub.execute_input":"2025-08-17T06:18:13.739228Z","iopub.status.idle":"2025-08-17T06:18:13.756429Z","shell.execute_reply.started":"2025-08-17T06:18:13.739204Z","shell.execute_reply":"2025-08-17T06:18:13.755279Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_examples = data_train.shape[0]\ntrain_target = train_df.head(num_examples)\ntrain_target = train_target.drop(columns=['planet_id'])\n\n# Combine targets for the unified model\ntargets_combined = train_target.values.astype(np.float32)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T06:18:13.757463Z","iopub.execute_input":"2025-08-17T06:18:13.757837Z","iopub.status.idle":"2025-08-17T06:18:13.796133Z","shell.execute_reply.started":"2025-08-17T06:18:13.757775Z","shell.execute_reply":"2025-08-17T06:18:13.795078Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# CNN Models","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nclass FGS_1D_CNN(nn.Module):\n    \"\"\" A 1D CNN for the FGS data. \"\"\"\n    def __init__(self):\n        super(FGS_1D_CNN, self).__init__()\n        # Input will be reshaped to (batch, 187, 32*32=1024)\n        self.conv_block1 = nn.Sequential(\n            nn.Conv1d(187, 64, kernel_size=7, padding=3), nn.BatchNorm1d(64), nn.ReLU(), nn.MaxPool1d(2)\n        )\n        self.conv_block2 = nn.Sequential(\n            nn.Conv1d(64, 128, kernel_size=5, padding=2), nn.BatchNorm1d(128), nn.ReLU(), nn.MaxPool1d(2)\n        )\n        self.flatten = nn.Flatten()\n        self.fc = nn.Sequential(\n            nn.Linear(128 * 256, 512), # 1024 -> 512 -> 256\n            nn.ReLU(),\n            nn.Dropout(0.5),\n            nn.Linear(512,256),\n            nn.ReLU(),\n            nn.Dropout(0.5),\n            nn.Linear(256, 2) # Output mean and sigma for 1 wavelength\n            ,nn.ReLU()\n        )\n\n    def forward(self, x):\n        # Reshape for 1D convolution: (batch, channels, height, width) -> (batch, channels, sequence_length)\n        batch_size = x.shape[0]\n        x = x.view(batch_size, 187, -1)\n        \n        x = self.conv_block1(x)\n        x = self.conv_block2(x)\n        x = self.flatten(x)\n        output = self.fc(x)\n        \n        y_pred = output[:, :1]\n        sigma_pred = F.softplus(output[:, 1:]) + 1e-6\n        return y_pred, sigma_pred\n\nclass AIRS_1D_CNN(nn.Module):\n    \"\"\" A 1D CNN for the AIRS data. \"\"\"\n    def __init__(self):\n        super(AIRS_1D_CNN, self).__init__()\n        # Input will be reshaped to (batch, 187, 356*32=11392)\n        self.conv_block1 = nn.Sequential(\n            nn.Conv1d(187, 64, kernel_size=7, padding=3), nn.BatchNorm1d(64), nn.ReLU(), nn.MaxPool1d(4)\n        )\n        self.conv_block2 = nn.Sequential(\n            nn.Conv1d(64, 128, kernel_size=5, padding=2), nn.BatchNorm1d(128), nn.ReLU(), nn.MaxPool1d(4)\n        )\n        self.flatten = nn.Flatten()\n        self.fc = nn.Sequential(\n            nn.Linear(128 * 712, 512), # 11392 -> 2848 -> 712\n            nn.ReLU(),\n            nn.Dropout(0.5),\n            nn.Linear(512,512),nn.ReLU(),nn.Dropout(0.5),\n            nn.Linear(512, 282 * 2) # Output mean and sigma for 282 wavelengths\n            ,nn.ReLU()\n        )\n\n    def forward(self, x):\n        # Reshape for 1D convolution\n        batch_size = x.shape[0]\n        x = x.view(batch_size, 187, -1)\n        \n        x = self.conv_block1(x)\n        x = self.conv_block2(x)\n        x = self.flatten(x)\n        output = self.fc(x)\n        \n        y_pred = output[:, :282]\n        sigma_pred = F.softplus(output[:, 282:]) + 1e-6\n        return y_pred, sigma_pred\n\nclass CombinedArielModel(nn.Module):\n    \"\"\" A wrapper model to combine the 1D CNNs for joint training. \"\"\"\n    def __init__(self):\n        super(CombinedArielModel, self).__init__()\n        self.model_fgs = FGS_1D_CNN()\n        self.model_airs = AIRS_1D_CNN()\n\n    def forward(self, x_fgs, x_airs):\n        y_pred_fgs, sigma_pred_fgs = self.model_fgs(x_fgs)\n        y_pred_airs, sigma_pred_airs = self.model_airs(x_airs)\n\n        y_pred_combined = torch.cat([y_pred_fgs, y_pred_airs], dim=1)\n        sigma_pred_combined = torch.cat([sigma_pred_fgs, sigma_pred_airs], dim=1)\n\n        return y_pred_combined, sigma_pred_combined","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T06:18:13.79707Z","iopub.execute_input":"2025-08-17T06:18:13.7974Z","iopub.status.idle":"2025-08-17T06:18:13.814325Z","shell.execute_reply.started":"2025-08-17T06:18:13.797366Z","shell.execute_reply":"2025-08-17T06:18:13.813041Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Loss function","metadata":{}},{"cell_type":"code","source":"class GLLLoss(nn.Module):\n    \"\"\"\n    This class converts the scoring metric into a PyTorch loss function.\n    It re-implements the Gaussian Log-Likelihood calculation using PyTorch tensors,\n    making it differentiable for model training.\n    \"\"\"\n    def __init__(self, naive_mean, naive_sigma, fsg_sigma_true=1e-6, airs_sigma_true=1e-5, fgs_weight=1.0, n_wavelengths=283, device='cpu'):\n        super(GLLLoss, self).__init__()\n        self.naive_mean = torch.tensor(naive_mean, dtype=torch.float32).to(device)\n        self.naive_sigma = torch.tensor(naive_sigma, dtype=torch.float32).to(device)\n\n        # Pre-calculate constants and move them to the correct device as tensors.\n        sigma_true_np = np.append(np.array([fsg_sigma_true]), np.ones(n_wavelengths - 1) * airs_sigma_true)\n        self.sigma_true = torch.tensor(sigma_true_np, dtype=torch.float32).to(device)\n\n        weights_np = np.append(np.array([fgs_weight]), np.ones(n_wavelengths - 1))\n        self.weights = torch.tensor(weights_np, dtype=torch.float32).to(device)\n\n    def log_pdf(self, x, loc, scale):\n        \"\"\"\n        PyTorch implementation of the Gaussian log probability density function.\n        \"\"\"\n        eps = 1e-9\n        return -torch.log(scale + eps) - 0.5 * np.log(2 * np.pi) - 0.5 * torch.pow((x - loc) / (scale + eps), 2)\n\n    def forward(self, y_pred, sigma_pred, y_true):\n        \"\"\"\n        Calculates the competition metric as a loss. Since the goal is to MAXIMIZE\n        the score, the loss is the NEGATIVE of the score, which we MINIMIZE.\n        \"\"\"\n        GLL_pred = self.log_pdf(y_true, loc=y_pred, scale=sigma_pred)\n        GLL_true = self.log_pdf(y_true, loc=y_true, scale=self.sigma_true)\n        GLL_mean = self.log_pdf(y_true, loc=self.naive_mean, scale=self.naive_sigma)\n\n        ind_scores = (GLL_pred - GLL_mean) / (GLL_true - GLL_mean + 1e-9)\n        weighted_scores = ind_scores * self.weights.unsqueeze(0)\n        submit_score = torch.sum(weighted_scores) / (torch.sum(self.weights) * y_true.shape[0])\n\n        # The loss is the negative of the score.\n        loss = -submit_score\n        return loss","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T06:18:13.81525Z","iopub.execute_input":"2025-08-17T06:18:13.815554Z","iopub.status.idle":"2025-08-17T06:18:13.835383Z","shell.execute_reply.started":"2025-08-17T06:18:13.815529Z","shell.execute_reply":"2025-08-17T06:18:13.834444Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Preparing Datasets and Dataloaders","metadata":{}},{"cell_type":"code","source":"from torch.utils.data import Dataset\nimport torch\nimport numpy as np\n\nclass ArielDataset(Dataset):\n    \"\"\"\n    Custom PyTorch Dataset for the Ariel data.\n    This class handles loading data from memory-mapped files and applies\n    on-the-fly normalization using pre-computed statistics.\n    \"\"\"\n    def __init__(self, data_fgs, data_airs, targets, indices, mean_fgs, std_fgs, mean_airs, std_airs):\n        \"\"\"\n        Args:\n            data_fgs (np.memmap): Memory-mapped array for FGS data.\n            data_airs (np.memmap): Memory-mapped array for AIRS data.\n            targets (np.array): Numpy array of target values.\n            indices (np.array): The indices of the data to use (e.g., train_idx or val_idx).\n            mean_fgs (np.array): Pre-computed mean for the FGS training data.\n            std_fgs (np.array): Pre-computed std deviation for the FGS training data.\n            mean_airs (np.array): Pre-computed mean for the AIRS training data.\n            std_airs (np.array): Pre-computed std deviation for the AIRS training data.\n        \"\"\"\n        self.data_fgs = data_fgs\n        self.data_airs = data_airs\n        self.targets = targets\n        self.indices = indices\n        \n        # Store the normalization statistics as attributes of the dataset\n        self.mean_fgs = mean_fgs\n        self.std_fgs = std_fgs\n        self.mean_airs = mean_airs\n        self.std_airs = std_airs\n\n    def __len__(self):\n        \"\"\" Returns the total number of samples in the dataset. \"\"\"\n        return len(self.indices)\n\n    def __getitem__(self, idx):\n        \"\"\"\n        Fetches one sample from the dataset at the given index, applies\n        normalization, and returns it as a tuple of PyTorch tensors.\n        \"\"\"\n        # Get the actual index from the provided list of indices (train or val)\n        i = self.indices[idx]\n        \n        # Load the raw data for one sample\n        x_fgs_raw = self.data_fgs[i]\n        x_airs_raw = self.data_airs[i]\n        y = self.targets[i]\n\n        # Apply normalization on-the-fly using the stored statistics.\n        # A small epsilon is added to the standard deviation to prevent division by zero.\n        x_fgs = (x_fgs_raw - self.mean_fgs) / (self.std_fgs + 1e-9)\n        x_airs = (x_airs_raw - self.mean_airs) / (self.std_airs + 1e-9)\n\n        # Convert the numpy arrays to PyTorch tensors and return\n        return (torch.from_numpy(x_fgs).float(),\n                torch.from_numpy(x_airs).float(),\n                torch.from_numpy(y).float())\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T06:18:13.836418Z","iopub.execute_input":"2025-08-17T06:18:13.836808Z","iopub.status.idle":"2025-08-17T06:18:13.856463Z","shell.execute_reply.started":"2025-08-17T06:18:13.836782Z","shell.execute_reply":"2025-08-17T06:18:13.855381Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"indices = np.arange(num_examples)\ntrain_idx, val_idx = train_test_split(indices, test_size=0.1, random_state=69)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T06:18:13.860551Z","iopub.execute_input":"2025-08-17T06:18:13.861076Z","iopub.status.idle":"2025-08-17T06:18:13.880998Z","shell.execute_reply.started":"2025-08-17T06:18:13.861053Z","shell.execute_reply":"2025-08-17T06:18:13.880093Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"mean_FGS, std_FGS = get_stats_in_batches(data_train_FGS,train_idx,batch_size=16)\nmean_AIRS, std_AIRS = get_stats_in_batches(data_train,train_idx,batch_size=16)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T06:18:13.881932Z","iopub.execute_input":"2025-08-17T06:18:13.882188Z","iopub.status.idle":"2025-08-17T06:23:02.372578Z","shell.execute_reply.started":"2025-08-17T06:18:13.88217Z","shell.execute_reply":"2025-08-17T06:23:02.371003Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data_train_FGS.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T06:23:07.729466Z","iopub.execute_input":"2025-08-17T06:23:07.729842Z","iopub.status.idle":"2025-08-17T06:23:07.736778Z","shell.execute_reply.started":"2025-08-17T06:23:07.729812Z","shell.execute_reply":"2025-08-17T06:23:07.735627Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = ArielDataset(data_train_FGS, data_train, targets_combined, train_idx,mean_FGS,std_FGS,mean_AIRS,std_AIRS)\nval_dataset = ArielDataset(data_train_FGS, data_train, targets_combined, val_idx,mean_FGS,std_FGS,mean_AIRS,std_AIRS)\n\ntrain_dataloader = DataLoader(train_dataset, shuffle=True, batch_size=16, pin_memory=True, num_workers=2)\nval_dataloader = DataLoader(val_dataset, shuffle=False, batch_size=16, pin_memory=True, num_workers=2)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T06:23:46.357482Z","iopub.execute_input":"2025-08-17T06:23:46.357861Z","iopub.status.idle":"2025-08-17T06:23:46.364259Z","shell.execute_reply.started":"2025-08-17T06:23:46.357825Z","shell.execute_reply":"2025-08-17T06:23:46.363452Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Training the AIRS-MODEL\n","metadata":{}},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {device}\")\n\nmodel = CombinedArielModel().to(device)\noptimizer = torch.optim.AdamW(model.parameters(), lr=2e-5,weight_decay = 1e-4)\nfrom torch.optim.lr_scheduler import ReduceLROnPlateau\nscheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.2, patience=2, verbose=True)\n\n# Calculate naive mean and sigma from the training set for the loss function\n# Note: Using the whole dataset here for simplicity. In a strict setting,\n# you'd only use the training split.\nNAIVE_MEAN = np.mean(targets_combined)\nNAIVE_SIGMA = np.std(targets_combined)\n\nfn_loss = GLLLoss(\n    naive_mean=NAIVE_MEAN,\n    naive_sigma=NAIVE_SIGMA,\n    device=device\n)\n\nbest_val_loss = float('inf')\npatience = 3\nwait = 0\nnum_epochs = 50\n\nfor epoch in range(num_epochs):\n    print(f\"\\nEpoch {epoch + 1}/{num_epochs}\")\n    model.train()\n    train_losses = []\n\n    for data_fgs, data_airs, targets in tqdm(train_dataloader):\n        data_fgs = data_fgs.to(device, non_blocking=True)\n        data_airs = data_airs.to(device, non_blocking=True)\n        targets = targets.to(device, non_blocking=True)\n\n        optimizer.zero_grad()\n        y_pred, sigma_pred = model(data_fgs, data_airs)\n        loss = fn_loss(y_pred, sigma_pred, targets)\n        loss.backward()\n        optimizer.step()\n\n        train_losses.append(loss.item())\n\n    avg_train_loss = sum(train_losses) / len(train_losses)\n\n    # --- Validation ---\n    model.eval()\n    val_losses = []\n    with torch.no_grad():\n        for data_fgs, data_airs, targets in val_dataloader:\n            data_fgs = data_fgs.to(device, non_blocking=True)\n            data_airs = data_airs.to(device, non_blocking=True)\n            targets = targets.to(device, non_blocking=True)\n\n            y_pred, sigma_pred = model(data_fgs, data_airs)\n            loss = fn_loss(y_pred, sigma_pred, targets)\n            val_losses.append(loss.item())\n\n    avg_val_loss = sum(val_losses) / len(val_losses)\n    scheduler.step(avg_val_loss)\n    # Since loss is -score, a lower loss value is better.\n    print(f\"Avg Train Loss: {avg_train_loss:.6f} | Avg Val Loss: {avg_val_loss:.6f}\")\n\n    # --- Early stopping ---\n    if avg_val_loss < best_val_loss:\n        best_val_loss = avg_val_loss\n        wait = 0\n        torch.save(model.state_dict(), \"Combined_Ariel_Model.pt\")\n        print(f\"Validation loss improved. Best val loss: {best_val_loss:.6f}. Model saved.\")\n    else:\n        wait += 1\n        if wait >= patience:\n            print(\"EARLY STOPPING TRIGGERED\")\n            break\n        print(f\"No improvement. Early stop patience: {wait}/{patience}\")\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T06:23:49.934785Z","iopub.execute_input":"2025-08-17T06:23:49.93528Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"We will now use the saved model to make prediction","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}