{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":87793,"databundleVersionId":11553390,"sourceType":"competition"}],"dockerImageVersionId":30919,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nimport RNA\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nimport torch.optim as optim\nfrom tqdm.auto import tqdm\nimport subprocess\n\nMAX_SEQ_LEN = None\nvocab = {'A': 1, 'C': 2, 'G': 3, 'U': 4, '<pad>': 0}\n\ndef encode(seq: str) -> torch.LongTensor:\n    return torch.LongTensor([vocab.get(nt, 0) for nt in seq])\n\ndef compute_ss_matrix(seq: str) -> np.ndarray:\n    dotbracket, _ = RNA.fold(seq)\n    N = len(seq)\n    ss_mat = np.zeros((N, N), dtype=np.float32)\n    stack = []\n    for i, ch in enumerate(dotbracket):\n        if ch == '(':\n            stack.append(i)\n        elif ch == ')':\n            if stack:\n                j = stack.pop()\n                ss_mat[i, j] = 1.0\n                ss_mat[j, i] = 1.0\n    return ss_mat\n\ndef compute_tmscore(ref_coords, pred_coords):\n    np.savetxt('ref.xyz', ref_coords, header='', comments='')\n    np.savetxt('pred.xyz', pred_coords, header='', comments='')\n    result = subprocess.run(['./TMscore', 'ref.xyz', 'pred.xyz'], capture_output=True, text=True)\n    for line in result.stdout.splitlines():\n        if line.startswith('TM-score'):\n            return float(line.split('=')[1].strip().split()[0])\n    return None\n\ndef pairwise_dist(coords: torch.Tensor):\n    return torch.cdist(coords, coords)\n\ndef extract_base_id(full_id: str) -> str:\n    return full_id.rsplit('_', 1)[0]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-27T13:55:00.147811Z","iopub.execute_input":"2025-03-27T13:55:00.148142Z","iopub.status.idle":"2025-03-27T13:55:04.867906Z","shell.execute_reply.started":"2025-03-27T13:55:00.148115Z","shell.execute_reply":"2025-03-27T13:55:04.866960Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class RNADataset(Dataset):\n    def __init__(self, seq_df: pd.DataFrame, coord_df: pd.DataFrame):\n        self.seq_df = seq_df.copy()\n        self.coord_df = coord_df.copy()\n        self.groups = self.coord_df.groupby('target_id')\n        self.ids = sorted(self.groups.groups.keys())\n    def __len__(self):\n        return len(self.ids)\n    def __getitem__(self, idx: int):\n        tid = self.ids[idx]\n        seq_str = self.seq_df.loc[tid, 'sequence']\n        seq_encoded = encode(seq_str)\n        grp = self.groups.get_group(tid).sort_values('resid')\n        coords = grp[['x_1', 'y_1', 'z_1']].values\n        ss_mat = compute_ss_matrix(seq_str)\n        return seq_encoded, coords, ss_mat, tid\n\ndef custom_collate(batch):\n    if MAX_SEQ_LEN is None:\n        max_len = max(x[0].shape[0] for x in batch)\n    else:\n        max_len = min(max(x[0].shape[0] for x in batch), MAX_SEQ_LEN)\n\n    seq_list, coords_list, ss_list, mask_list, tids = [], [], [], [], []\n\n    for (seq_enc, coords, ss_mat, tid) in batch:\n        L = seq_enc.shape[0]\n        if MAX_SEQ_LEN is not None and L > MAX_SEQ_LEN:\n            seq_enc = seq_enc[:MAX_SEQ_LEN]\n            coords = coords[:MAX_SEQ_LEN]\n            ss_mat = ss_mat[:MAX_SEQ_LEN, :MAX_SEQ_LEN]\n            L = MAX_SEQ_LEN\n\n        # Sequence is used for an embedding layer => keep it long/int\n        seq_pad = F.pad(seq_enc, (0, max_len - L), value=0).long()\n\n        # Convert coords to float\n        coords_pad = F.pad(\n            torch.from_numpy(coords).float(),  # <-- .float() here\n            (0, 0, 0, max_len - L),\n            value=0.0\n        )\n\n        # Convert ss_mat to float\n        ss_pad = F.pad(\n            torch.from_numpy(ss_mat).float(),  # <-- .float() here\n            (0, max_len - L, 0, max_len - L),\n            value=0.0\n        )\n\n        mask = torch.zeros(max_len, dtype=torch.bool)\n        mask[:L] = True\n\n        seq_list.append(seq_pad)\n        coords_list.append(coords_pad)\n        ss_list.append(ss_pad)\n        mask_list.append(mask)\n        tids.append(tid)\n\n    seq_tensor = torch.stack(seq_list, dim=0)\n    coords_tensor = torch.stack(coords_list, dim=0)\n    ss_tensor = torch.stack(ss_list, dim=0)\n    mask_tensor = torch.stack(mask_list, dim=0)\n\n    return seq_tensor, coords_tensor, ss_tensor, mask_tensor, tids\n\n\nclass RNATestDataset(Dataset):\n    def __init__(self, test_seq_df: pd.DataFrame):\n        self.test_seq_df = test_seq_df.copy()\n        self.ids = sorted(self.test_seq_df.index.unique())\n    def __len__(self):\n        return len(self.ids)\n    def __getitem__(self, idx):\n        tid = self.ids[idx]\n        seq_str = self.test_seq_df.loc[tid, 'sequence']\n        seq_enc = encode(seq_str)\n        ss_mat = compute_ss_matrix(seq_str)\n        return seq_enc, ss_mat, tid\n\ndef test_collate(batch):\n    if MAX_SEQ_LEN is None:\n        max_len = max(x[0].shape[0] for x in batch)\n    else:\n        max_len = min(max(x[0].shape[0] for x in batch), MAX_SEQ_LEN)\n    seq_list, ss_list, mask_list, tids = [], [], [], []\n    for (seq_enc, ss_mat, tid) in batch:\n        L = seq_enc.shape[0]\n        if MAX_SEQ_LEN is not None and L > MAX_SEQ_LEN:\n            seq_enc = seq_enc[:MAX_SEQ_LEN]\n            ss_mat = ss_mat[:MAX_SEQ_LEN, :MAX_SEQ_LEN]\n            L = MAX_SEQ_LEN\n        seq_pad = F.pad(seq_enc, (0, max_len - L), value=0)\n        ss_pad = F.pad(torch.from_numpy(ss_mat), (0, max_len - L, 0, max_len - L), value=0.0)\n        mask = torch.zeros(max_len, dtype=torch.bool)\n        mask[:L] = True\n        seq_list.append(seq_pad)\n        ss_list.append(ss_pad)\n        mask_list.append(mask)\n        tids.append(tid)\n    seq_tensor = torch.stack(seq_list, dim=0)\n    ss_tensor = torch.stack(ss_list, dim=0)\n    mask_tensor = torch.stack(mask_list, dim=0)\n    return seq_tensor, ss_tensor, mask_tensor, tids\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-27T13:55:05.547436Z","iopub.execute_input":"2025-03-27T13:55:05.547774Z","iopub.status.idle":"2025-03-27T13:55:05.563063Z","shell.execute_reply.started":"2025-03-27T13:55:05.547731Z","shell.execute_reply":"2025-03-27T13:55:05.561943Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class RelativeSelfAttention(nn.Module):\n    def __init__(self, embed_dim, num_heads, max_relative_position=512):\n        super().__init__()\n        self.embed_dim = embed_dim\n        self.num_heads = num_heads\n        self.head_dim = embed_dim // num_heads\n        self.q_proj = nn.Linear(embed_dim, embed_dim)\n        self.k_proj = nn.Linear(embed_dim, embed_dim)\n        self.v_proj = nn.Linear(embed_dim, embed_dim)\n        self.out_proj = nn.Linear(embed_dim, embed_dim)\n        self.max_rel = max_relative_position\n        self.rel_pos_emb = nn.Parameter(torch.randn(2 * max_relative_position - 1, self.head_dim))\n        self.ss_weight = nn.Parameter(torch.tensor(1.0))\n\n    def forward(self, x: torch.Tensor, ss_mat: torch.Tensor = None, mask: torch.Tensor = None):\n        B, L, _ = x.size()\n        Q = self.q_proj(x).view(B, self.num_heads, L, self.head_dim)\n        K = self.k_proj(x).view(B, self.num_heads, L, self.head_dim)\n        V = self.v_proj(x).view(B, self.num_heads, L, self.head_dim)\n\n        scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.head_dim ** 0.5)\n        idx = torch.arange(L, device=x.device)\n        rel_idx = (idx[None, :] - idx[:, None]).clamp(-self.max_rel+1, self.max_rel-1) + (self.max_rel-1)\n        rel_emb = self.rel_pos_emb[rel_idx.long()]\n        scores = scores + torch.einsum('bnhd,ljd->bnhl', Q, rel_emb)\n\n        if ss_mat is not None:\n            scores = scores + self.ss_weight * ss_mat[:, None, :, :]\n\n        if mask is not None:\n            attn_mask = mask.unsqueeze(1) & mask.unsqueeze(2)\n            scores = scores.masked_fill(~attn_mask.unsqueeze(1), float('-inf'))\n\n        weights = torch.softmax(scores, dim=-1)\n\n        if mask is not None:\n            query_mask = mask.unsqueeze(1).unsqueeze(-1)\n            weights = weights.masked_fill(~query_mask, 0.0)\n\n        out = torch.matmul(weights, V).transpose(1, 2).reshape(B, L, self.embed_dim)\n        out = self.out_proj(out)\n\n        if mask is not None:\n            out = out.masked_fill(~mask.unsqueeze(-1), 0.0)\n\n        return out\n\nclass RNA3DPredictor(nn.Module):\n    def __init__(self, vocab_size):\n        super().__init__()\n        self.embed_dim = 64\n        self.embed = nn.Embedding(vocab_size, self.embed_dim)\n        self.attn = RelativeSelfAttention(self.embed_dim, num_heads=8)\n        self.mlp = nn.Sequential(nn.Linear(self.embed_dim, 128), nn.ReLU(), nn.Linear(128, 3))\n    def forward(self, seq: torch.Tensor, ss_mat: torch.Tensor = None, mask: torch.Tensor = None):\n        x = self.embed(seq)\n        x = self.attn(x, ss_mat, mask)\n        x = self.mlp(x)\n        return x\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-27T14:11:44.353513Z","iopub.execute_input":"2025-03-27T14:11:44.353870Z","iopub.status.idle":"2025-03-27T14:11:44.368493Z","shell.execute_reply.started":"2025-03-27T14:11:44.353840Z","shell.execute_reply":"2025-03-27T14:11:44.367400Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def extract_base_id(full_id: str) -> str:\n    return \"_\".join(full_id.split(\"_\")[:2])  # e.g. \"1SCL_A_1\" -> \"1SCL_A\"\n\ndef drop_incomplete_sequences(lbl_df, seq_df):\n    drop_ids = set()\n    seq_map = dict(zip(seq_df[\"target_id\"], seq_df[\"sequence\"]))\n    for tid, group in lbl_df.groupby(\"target_id\"):\n        if group[[\"x_1\", \"y_1\", \"z_1\"]].isna().any().any():\n            drop_ids.add(tid)\n            continue\n        if tid not in seq_map:\n            drop_ids.add(tid)\n            continue\n        expected_len = len(seq_map[tid])\n        sorted_resid = group[\"resid\"].astype(int).sort_values().values\n        if not np.array_equal(sorted_resid, np.arange(1, expected_len + 1)):\n            drop_ids.add(tid)\n    return drop_ids\n\nbase = '/kaggle/input/stanford-rna-3d-folding'\ntrain_seq = pd.read_csv(f'{base}/train_sequences.csv')\ntrain_lbl = pd.read_csv(f'{base}/train_labels.csv')\n\ntrain_lbl[\"target_id\"] = train_lbl[\"ID\"].apply(extract_base_id)\n# If needed:\n# train_seq[\"target_id\"] = train_seq[\"target_id\"].apply(extract_base_id)\n\ndrop_ids = drop_incomplete_sequences(train_lbl, train_seq)\ntrain_lbl_clean = train_lbl[~train_lbl[\"target_id\"].isin(drop_ids)].copy()\ntrain_seq_clean = train_seq[~train_seq[\"target_id\"].isin(drop_ids)].copy()\n\nvalid_ids = set(train_lbl_clean[\"target_id\"]) & set(train_seq_clean[\"target_id\"])\ntrain_lbl_clean = train_lbl_clean[train_lbl_clean[\"target_id\"].isin(valid_ids)].copy()\ntrain_seq_clean = train_seq_clean[train_seq_clean[\"target_id\"].isin(valid_ids)].copy()\n\nprint(\"Remaining targets in train_lbl_clean:\", train_lbl_clean[\"target_id\"].nunique())\nprint(\"Remaining targets in train_seq_clean:\", train_seq_clean[\"target_id\"].nunique())\n\n# Set the index once, on the cleaned DataFrames\ntrain_seq_clean = train_seq_clean.set_index(\"target_id\")\ntrain_lbl_clean = train_lbl_clean.set_index(\"target_id\")\n\ntrain_dset = RNADataset(train_seq_clean, train_lbl_clean)\ntrain_loader = DataLoader(train_dset, batch_size=4, shuffle=True, collate_fn=custom_collate)\nprint(f\"Train dataset size: {len(train_dset)} sequences.\")\n\n\n# Load raw validation data\nval_seq = pd.read_csv(f'{base}/validation_sequences.csv')\nval_lbl = pd.read_csv(f'{base}/validation_labels.csv')\n\n# Normalize IDs\nval_lbl[\"target_id\"] = val_lbl[\"ID\"].apply(extract_base_id)\nval_seq[\"target_id\"] = val_seq[\"target_id\"].apply(extract_base_id)\n\n# Drop incomplete/mismatched entries\ndrop_ids = drop_incomplete_sequences(val_lbl, val_seq)\nval_lbl_clean = val_lbl[~val_lbl[\"target_id\"].isin(drop_ids)].copy()\nval_seq_clean = val_seq[~val_seq[\"target_id\"].isin(drop_ids)].copy()\n\n# Keep only IDs present in both\ncommon = set(val_lbl_clean[\"target_id\"]) & set(val_seq_clean[\"target_id\"])\nval_lbl_clean = val_lbl_clean[val_lbl_clean[\"target_id\"].isin(common)].set_index(\"target_id\")\nval_seq_clean = val_seq_clean[val_seq_clean[\"target_id\"].isin(common)].set_index(\"target_id\")\n\nprint(\"Cleaned validation targets:\", len(common))\n\n# Create DataLoader\nval_dset = RNADataset(val_seq_clean, val_lbl_clean)\nval_loader = DataLoader(val_dset, batch_size=4, shuffle=False, collate_fn=custom_collate)\nprint(\"Validation loader batches:\", len(val_loader))\n\ndevice = 'cuda' if torch.cuda.is_available() else 'cpu'\nmodel = RNA3DPredictor(len(vocab)).to(device)\nopt = optim.Adam(model.parameters(), lr=1e-5)\nrelative_loss_fn = nn.MSELoss()\n\n# ——— Insert debug here ———\nloader = DataLoader(train_dset, batch_size=4, shuffle=False, collate_fn=custom_collate)\nseq_batch, coords_batch, ss_batch, mask_batch, _ = next(iter(loader))\nseq_batch, coords_batch, ss_batch, mask_batch = [t.to(device) for t in (seq_batch, coords_batch, ss_batch, mask_batch)]\n\nmodel.eval()\nwith torch.no_grad():\n    pred = model(seq_batch, ss_batch, mask_batch)\n\nprint(\"Any NaNs in coords_batch? \", torch.isnan(coords_batch).any().item())\nprint(\"Any NaNs in ss_batch?    \", torch.isnan(ss_batch).any().item())\nprint(\"Any NaNs in model output?\", torch.isnan(pred).any().item())\n\nmask_flat = mask_batch.view(-1)\npred_masked = pred[mask_batch]\ncoords_masked = coords_batch[mask_batch]\ndist_pred = pairwise_dist(pred_masked).unsqueeze(0)\ndist_ref = pairwise_dist(coords_masked).unsqueeze(0)\nloss = relative_loss_fn(dist_pred, dist_ref)\nprint(\"Loss is NaN? \", torch.isnan(loss).item())\n\nn_epochs = 5\nfor epoch in range(n_epochs):\n    model.train()\n    total_loss = 0.0\n    for seq_batch, coords_batch, ss_batch, mask_batch, tids in tqdm(train_loader, desc=f\"Epoch {epoch+1}\"):\n        seq_batch = seq_batch.to(device)\n        coords_batch = coords_batch.to(device)\n        ss_batch = ss_batch.to(device)\n        mask_batch = mask_batch.to(device)\n        pred_coords = model(seq_batch, ss_batch, mask_batch)\n        batch_loss = 0.0\n        for b_idx in range(seq_batch.size(0)):\n            L_b = mask_batch[b_idx].sum().item()\n            pred_b = pred_coords[b_idx, :L_b, :]\n            coords_b = coords_batch[b_idx, :L_b, :]\n            dist_pred = pairwise_dist(pred_b).unsqueeze(0)\n            dist_ref = pairwise_dist(coords_b).unsqueeze(0)\n            sample_loss = relative_loss_fn(dist_pred, dist_ref)\n            batch_loss += sample_loss\n        batch_loss = batch_loss / seq_batch.size(0)\n        opt.zero_grad()\n        batch_loss.backward()\n        opt.step()\n        total_loss += batch_loss.item()\n    avg_loss = total_loss / len(train_loader)\n    print(f\"[Epoch {epoch+1}/{n_epochs}] Mean pairwise-dist MSE: {avg_loss:.4f}\")\n\nmodel.eval()\n\n# Re-create the test DataLoader (in case it was deleted)\ntest_seq = pd.read_csv(f'{base}/test_sequences.csv')\ntest_seq[\"target_id\"] = test_seq[\"target_id\"].apply(extract_base_id)\ntest_seq = test_seq.set_index(\"target_id\")\n\ntest_dset = RNATestDataset(test_seq)\ntest_loader = DataLoader(test_dset, batch_size=4, shuffle=False, collate_fn=test_collate)\n\n# Build submission with required columns\ncolumns = [\n    \"ID\",\"resname\",\"resid\",\n    \"x_1\",\"y_1\",\"z_1\",\n    \"x_2\",\"y_2\",\"z_2\",\n    \"x_3\",\"y_3\",\"z_3\",\n    \"x_4\",\"y_4\",\"z_4\",\n    \"x_5\",\"y_5\",\"z_5\",\n]\n\nresults = []\nmodel.eval()\n\nfor seq_batch, ss_batch, mask_batch, tids in test_loader:\n    seq_batch = seq_batch.to(device)\n    ss_batch = ss_batch.to(device)\n    mask_batch = mask_batch.to(device)\n\n    with torch.no_grad():\n        preds = model(seq_batch, ss_batch, mask_batch)\n\n    for b, tid in enumerate(tids):\n        seq_str = test_seq.loc[tid, \"sequence\"]\n        L = mask_batch[b].sum().item()\n        for i in range(L):\n            x, y, z = preds[b, i].cpu().numpy()\n            results.append({\n                \"ID\": f\"{tid}_{i+1}\",\n                \"resname\": seq_str[i],\n                \"resid\": i+1,\n                \"x_1\": x, \"y_1\": y, \"z_1\": z,\n                **{f\"x_{j}\": 0.0 for j in range(2,6)},\n                **{f\"y_{j}\": 0.0 for j in range(2,6)},\n                **{f\"z_{j}\": 0.0 for j in range(2,6)},\n            })\n\nsubmission = pd.DataFrame(results, columns=columns)\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(f\"Wrote submission.csv with {len(submission)} rows\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-27T14:11:49.733004Z","iopub.execute_input":"2025-03-27T14:11:49.733324Z","iopub.status.idle":"2025-03-27T14:52:08.921912Z","shell.execute_reply.started":"2025-03-27T14:11:49.733300Z","shell.execute_reply":"2025-03-27T14:52:08.920681Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(submission)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-27T14:55:18.152981Z","iopub.execute_input":"2025-03-27T14:55:18.153360Z","iopub.status.idle":"2025-03-27T14:55:18.174696Z","shell.execute_reply.started":"2025-03-27T14:55:18.153327Z","shell.execute_reply":"2025-03-27T14:55:18.173661Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\n\ndef kabsch_rmsd(A: np.ndarray, B: np.ndarray) -> float:\n    A_cent = A - A.mean(axis=0)\n    B_cent = B - B.mean(axis=0)\n    H = B_cent.T @ A_cent\n    U, _, Vt = np.linalg.svd(H)\n    R = Vt.T @ U.T\n    if np.linalg.det(R) < 0:\n        Vt[-1, :] *= -1\n        R = Vt.T @ U.T\n    B_rot = B_cent @ R\n    diff = A_cent - B_rot\n    return np.sqrt((diff**2).sum() / A.shape[0])\n\n# === Clean validation DataFrames ===\nval_seq = pd.read_csv(f'{base}/validation_sequences.csv')\nval_lbl = pd.read_csv(f'{base}/validation_labels.csv')\n\nval_lbl[\"target_id\"] = val_lbl[\"ID\"].apply(lambda x: x.rsplit(\"_\",1)[0])\nval_seq[\"target_id\"] = val_seq[\"target_id\"]\n\ncommon = set(val_lbl[\"target_id\"]) & set(val_seq[\"target_id\"])\nval_lbl_clean = val_lbl[val_lbl[\"target_id\"].isin(common)].set_index(\"target_id\")\nval_seq_clean = val_seq[val_seq[\"target_id\"].isin(common)].set_index(\"target_id\")\n\nsentinel = -1e18\nbad_ids = val_lbl_clean[val_lbl_clean[['x_1','y_1','z_1']].eq(sentinel).any(axis=1)].index.unique()\nval_lbl_clean = val_lbl_clean.drop(index=bad_ids)\nval_seq_clean = val_seq_clean.drop(index=bad_ids)\n\nprint(\"Final validation targets:\", len(val_lbl_clean.index.unique()))\n\nval_dset = RNADataset(val_seq_clean, val_lbl_clean)\nval_loader = DataLoader(val_dset, batch_size=4, shuffle=False, collate_fn=custom_collate)\nprint(\"Validation loader batches:\", len(val_loader))\n\n# === RMSD Validation ===\nmodel.eval()\nrmsds = []\nfor seq_batch, coords_batch, ss_batch, mask_batch, tids in val_loader:\n    seq_batch, ss_batch, mask_batch = seq_batch.to(device), ss_batch.to(device), mask_batch.to(device)\n    with torch.no_grad():\n        preds = model(seq_batch, ss_batch, mask_batch)\n    for i, tid in enumerate(tids):\n        L = mask_batch[i].sum().item()\n        ref = val_lbl_clean.loc[tid].sort_values('resid')[['x_1','y_1','z_1']].values\n        pred = preds[i, :L].cpu().numpy()\n        rmsds.append(kabsch_rmsd(ref, pred))\n\nif rmsds:\n    print(f\"Validation mean RMSD: {np.mean(rmsds):.4f} Å\")\nelse:\n    print(\"No validation examples left after cleaning.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-27T14:56:41.555029Z","iopub.execute_input":"2025-03-27T14:56:41.555432Z","iopub.status.idle":"2025-03-27T14:56:43.200562Z","shell.execute_reply.started":"2025-03-27T14:56:41.555398Z","shell.execute_reply":"2025-03-27T14:56:43.199735Z"}},"outputs":[],"execution_count":null}]}