{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"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 random\n\n# Nearest-Neighbor stacking energies (kcal/mol at 37°C)\nNN_DG = {\n    'AA': -1.00, 'TT': -1.00, 'AT': -0.88, 'TA': -0.58,\n    'CA': -1.45, 'TG': -1.45, 'GT': -1.44, 'AC': -1.44,\n    'CT': -1.28, 'AG': -1.28, 'GA': -1.30, 'TC': -1.30,\n    'CG': -2.17, 'GC': -2.24, 'GG': -1.84, 'CC': -1.84\n}\n\ndef create_dataset(num_sequences=250, split_ratio=0.8):\n    data = []\n    categories = ['duplex_standard', 'gc_clamp_hairpin', 'alternating_at']\n\n    for s_idx in range(num_sequences):\n        seq_id = f\"SEQ_{s_idx:04d}\"\n        cat = random.choice(categories)\n        length = random.randint(70, 110)\n\n        if cat == 'duplex_standard':\n            seq = ''.join(random.choices('ATGC', weights=[0.25, 0.25, 0.25, 0.25], k=length))\n        elif cat == 'gc_clamp_hairpin':\n            stem = ''.join(random.choices('GC', k=12))\n            loop = ''.join(random.choices('AT', k=6))\n            inv_stem = ''.join({'G':'C', 'C':'G'}[b] for b in reversed(stem))\n            seq = stem + loop + inv_stem + ''.join(random.choices('ATGC', k=max(0, length - 30)))\n        else:\n            seq = ('AT' * (length // 2 + 1))[:length]\n\n        length = len(seq)\n        accumulated_torque = 0.0\n\n        for i in range(length - 1):\n            dinuc = seq[i:i+2]\n            base_dg = NN_DG.get(dinuc, -1.20)\n            baseline_force = abs(base_dg) * 7.2  # ~pN equivalent\n\n            # State-dependent physics: GC builds local twist, AT relaxes it\n            if dinuc in ['GC', 'CG', 'GG', 'CC']:\n                accumulated_torque += 0.22\n            else:\n                accumulated_torque = max(0.0, accumulated_torque - 0.08)\n\n            # Target mechanical unzipping rupture force\n            unzip_force = baseline_force + (accumulated_torque * 1.65)\n            # Add thermal Brownian micro-noise\n            noise = np.random.normal(0, 0.15)\n\n            data.append({\n                'seq_id': seq_id,\n                'category': cat,\n                'step_index': i,\n                'dinucleotide': dinuc,\n                'prev_dinuc': seq[i-2:i] if i >= 2 else 'START',\n                'target_force_pN': round(unzip_force + noise, 3)\n            })\n\n    df = pd.DataFrame(data)\n    unique_ids = df['seq_id'].unique()\n    split_idx = int(len(unique_ids) * split_ratio)\n    \n    train_ids = set(unique_ids[:split_idx])\n    train_df = df[df['seq_id'].isin(train_ids)].copy()\n    test_df = df[~df['seq_id'].isin(train_ids)].copy()\n\n    train_df.to_csv(\"train.csv\", index=False)\n    test_df.to_csv(\"test.csv\", index=False)\n    print(f\"Generated train.csv ({len(train_df)} rows) and test.csv ({len(test_df)} rows).\")\n\nif __name__ == \"__main__\":\n    create_dataset()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nfrom sklearn.ensemble import RandomForestRegressor\nfrom sklearn.metrics import mean_squared_error\n\n# Load the data\ntrain = pd.read_csv(\"train.csv\")\ntest = pd.read_csv(\"test.csv\")\n\n# -------------------------------------------------------------\n# 1. COMPETITOR APPROACH: Static Feature Machine Learning\n# (Treats each step as an isolated row with flat one-hot features)\n# -------------------------------------------------------------\nX_train = pd.get_dummies(train[['step_index', 'dinucleotide']])\nX_test = pd.get_dummies(test[['step_index', 'dinucleotide']]).reindex(columns=X_train.columns, fill_value=0)\n\nml_model = RandomForestRegressor(n_estimators=50, random_state=42)\nml_model.fit(X_train, train['target_force_pN'])\npreds_static = ml_model.predict(X_test)\nrmse_static = np.sqrt(mean_squared_error(test['target_force_pN'], preds_static))\n\n# -------------------------------------------------------------\n# 2. YOUR APPROACH: Recursive State-Dependent Engine\n# (Carries torque memory forward along the trajectory)\n# -------------------------------------------------------------\nNN_DG = {\n    'AA': -1.00, 'TT': -1.00, 'AT': -0.88, 'TA': -0.58,\n    'CA': -1.45, 'TG': -1.45, 'GT': -1.44, 'AC': -1.44,\n    'CT': -1.28, 'AG': -1.28, 'GA': -1.30, 'TC': -1.30,\n    'CG': -2.17, 'GC': -2.24, 'GG': -1.84, 'CC': -1.84\n}\n\npreds_recursive = []\nfor seq_id, group in test.groupby('seq_id', sort=False):\n    torque_state = 0.0\n    for _, row in group.iterrows():\n        d = row['dinucleotide']\n        base_dg = NN_DG.get(d, -1.20)\n        base_force = abs(base_dg) * 7.2\n        \n        # State machine transition logic\n        if d in ['GC', 'CG', 'GG', 'CC']:\n            torque_state += 0.22\n        else:\n            torque_state = max(0.0, torque_state - 0.08)\n            \n        pred_step = base_force + (torque_state * 1.65)\n        preds_recursive.append(pred_step)\n\nrmse_recursive = np.sqrt(mean_squared_error(test['target_force_pN'], preds_recursive))\n\nprint(\"=\" * 45)\nprint(f\"Static ML Baseline RMSE:    {rmse_static:.4f} pN\")\nprint(f\"Recursive State Engine RMSE: {rmse_recursive:.4f} pN\")\nprint(\"=\" * 45)","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}