OSCR

From lipid dynamics to precision predictions: A new approach methodology for precision modeling of phosphoinositide signaling.

Code ↔ Paper

11 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 11 matches · 3 of them tie a paragraph to a whole file, not to given lines: weak matches, whose lines are not tinted
  1. [1] § Methods › Phosphoinositide signaling model structure ↔ Codes_For_Figures/Figure_4/Global_Sensitivity_Analysis.m, lines 1–42 · score 0.81 · plasma membrane surface, cytoplasmic volume, converts molecules, conversion factor, biosensor, Phosphoinositide
  2. [2] § Methods › Phosphoinositide signaling model structure ↔ Codes_For_Figures/Figure_2/Figure_2_B.m, lines 1–55 · score 0.81 · plasma membrane surface, cytoplasmic volume, converts molecules, conversion factor, biosensor, signaling
  3. [3] § Methods › Neural network parameter estimation ↔ Codes_For_Figures/Figure_6/Figure_6_AB.m, the whole file · a weak match · score 0.78 · neural network, network predicted, kPLC, kPIP5K, kPI4K, kdeg
  4. [4] § Methods › Neural network parameter estimation ↔ Codes_For_Figures/Figure_6/Colab/PI_Cycle_v6.ipynb, lines 279–289 · score 0.78 · learning rate scheduling, weight decay, PyTorch, Adam, patience, loss
  5. [5] § Results ↔ Codes_For_Figures/Figure_6/Figure_6_AB.m, the whole file · a weak match · score 0.73 · neural network predicted, parameter fingerprints, kPIP5K, kPI4K, k4P, neuroblastoma
  6. [6] § Methods › Neural network parameter estimation ↔ Codes_For_Figures/Figure_6/Colab/PI_Cycle_v6.ipynb, lines 140–204 · score 0.69 · Latin Hypercube, stimulus durations, kinetic parameters, uniformly, channel, 40 %
  7. [7] § Methods › Global sensitivity analysis ↔ Codes_For_Figures/Figure_4/Global_Sensitivity_Analysis.m, lines 44–101 · score 0.63 · Latin Hypercube Sampling, global sensitivity, LHS
  8. [8] § Methods › Neural network parameter estimation ↔ Codes_For_Figures/Figure_6/Colab/PI_Cycle_v6.ipynb, lines 140–204 · score 0.59 · stimulus duration, steady state, enforce, seeds, prediction, baseline
  9. [9] § Methods › Phosphoinositide signaling model structure ↔ Codes_For_Figures/Figure_2/Isolated_SCG_Neurons.m, lines 160–200 · score 0.53 · state variables, LIBRAvIII, cytoplasmic, membrane, bound, molecules
  10. [10] § Methods › Nelder-Mead parameter optimization of the phosphoinositide signaling model ↔ Codes_For_Figures/Figure_2/Figure_2_A_Optimization/main_optimization.m, the whole file · a weak match · score 0.52 · squared errors, sum, SSE, global, optimization, model
  11. [11] § Methods › Neural network parameter estimation ↔ Codes_For_Figures/Figure_6/Colab/PI_Cycle_v6.ipynb, lines 224–268 · score 0.52 · hidden, kernel, layers, LSTM, batch, max

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Jupyter notebook · 767 lines · 32 KB · GPL-3.0 · 4 matches

  1. # %% [markdown]
  2. # # PI Cycle Parameter Estimation - v4
  3. #
  4. # **Key change from v3: ratio fed as input, not predicted**
  5. #
  6. # The PI/PIP ratio is known from biology for each cell type:
  7. # - SCG: 50.0
  8. # - tsA: 35.0
  9. # - Neuroblastoma: 22.42
  10. # - Hippocampal: 5.38
  11. #
  12. # Instead of asking the network to guess the ratio from normalized data
  13. # (impossible after normalization removes absolute pool size), we feed it
  14. # as a 5th input channel. The network then predicts only 5 parameters:
  15. # k_PI4K, k_PIP5K, k_5P, k_plc, k_deg.
  16. #
  17. # k_4P is always derived as: k_4P = known_ratio * predicted_k_PI4K
  18. #
  19. # **Run cells in order. Do not skip.**
  20. # %%
  21. # -------------------------------------------------------
  22. # SET RUN SEED HERE BEFORE HITTING RUN ALL
  23. # Run 1: RUN_SEED = 42
  24. # Run 2: RUN_SEED = 123
  25. # Run 3: RUN_SEED = 456
  26. # -------------------------------------------------------
  27. RUN_SEED = 456
  28. # -------------------------------------------------------
  29. # Uncomment below ONLY to delete checkpoint for this run
  30. # import os
  31. # for f in ['best_model.pth', 'training_checkpoint.pt']:
  32. # path = f'/content/drive/MyDrive/PI_Cycle_checkpoints/run_{RUN_SEED}/{f}'
  33. # if os.path.exists(path):
  34. # os.remove(path)
  35. # print(f'Deleted {f}')
  36. print(f'RUN_SEED = {RUN_SEED}')
  37. # %%
  38. !pip install -q torch numpy scipy matplotlib scikit-learn tqdm
  39. print('Packages ready!')
  40. # %%
  41. import numpy as np
  42. import matplotlib.pyplot as plt
  43. from scipy.integrate import odeint
  44. from scipy.stats import qmc
  45. from sklearn.model_selection import train_test_split
  46. import torch
  47. import torch.nn as nn
  48. from torch.utils.data import Dataset, DataLoader
  49. from tqdm import tqdm
  50. import time
  51. import os
  52. from google.colab import drive
  53. drive.mount('/content/drive')
  54. OUTPUT_DIR = f'/content/drive/MyDrive/PI_Cycle_outputs/run_{RUN_SEED}'
  55. CHECKPOINT_DIR = f'/content/drive/MyDrive/PI_Cycle_checkpoints/run_{RUN_SEED}'
  56. os.makedirs(OUTPUT_DIR, exist_ok=True)
  57. os.makedirs(CHECKPOINT_DIR, exist_ok=True)
  58. print('Imports done, Drive mounted.')
  59. # %%
  60. # -------------------------------------------------------
  61. # TOGGLE HERE
  62. # True = quick test: 10k samples, 30 epochs (~10 min)
  63. # False = full run: 50k samples, 50 epochs (~60 min)
  64. # -------------------------------------------------------
  65. TEST_MODE = False
  66. # -------------------------------------------------------
  67. # -------------------------------------------------------
  68. N_SAMPLES = 10000 if TEST_MODE else 50000
  69. N_EPOCHS = 30 if TEST_MODE else 50
  70. PATIENCE = 10
  71. CELL_RATIOS = {
  72. 'SCG': 50.00,
  73. 'tsA': 35.00,
  74. 'Neuroblastoma': 22.42,
  75. 'Hippocampal': 5.38,
  76. }
  77. RATIO_MIN = 3.0
  78. RATIO_MAX = 50.0
  79. def normalize_ratio(ratio):
  80. return (ratio - RATIO_MIN) / (RATIO_MAX - RATIO_MIN)
  81. print(f'RUN_SEED: {RUN_SEED}')
  82. print(f'Mode: {"TEST" if TEST_MODE else "FULL"} | Samples: {N_SAMPLES:,} | Epochs: {N_EPOCHS}')
  83. # %%
  84. def pi_cycle_odes(y, t, params, PI, cf,
  85. stim_start=80, stim_end=100, PLC_amp=0.7):
  86. PIP, PIP2, IP3_free, LIBRA_free, IP3_LIBRA = y
  87. k_PI4K = params['k_PI4K']
  88. k_4P = params['k_4P']
  89. k_PIP5K = params['k_PIP5K']
  90. k_5P = params['k_5P']
  91. k_plc = params['k_plc']
  92. k_deg = params['k_deg']
  93. PLC = PLC_amp if (stim_start <= t <= stim_end) else 0.0
  94. spd = 1.0
  95. KD = 0.5
  96. fp = 1.0
  97. dPIP = fp*k_PI4K*PI - k_4P*PIP - fp*k_PIP5K*PIP + fp*k_5P*PIP2
  98. dPIP2 = fp*k_PIP5K*PIP - fp*k_5P*PIP2 - k_plc*fp*PIP2*PLC
  99. dIP3 = cf*fp*k_plc*PIP2*PLC - k_deg*IP3_free - spd*IP3_free*LIBRA_free + spd*KD*IP3_LIBRA
  100. dLIBRA = -spd*IP3_free*LIBRA_free + spd*KD*IP3_LIBRA
  101. dIP3L = spd*LIBRA_free*IP3_free - spd*KD*IP3_LIBRA
  102. return [dPIP, dPIP2, dIP3, dLIBRA, dIP3L]
  103. def steady_state_y0(k_PI4K, PI_PIP_ratio, k_PIP5K, k_5P, PI):
  104. """Exact pre-stimulus steady state. IP3=0 before stimulus."""
  105. PIP_ss = PI / PI_PIP_ratio
  106. PIP2_ss = (k_PIP5K / k_5P) * PIP_ss
  107. return [PIP_ss, PIP2_ss, 0.0, 6.0, 0.0]
  108. print('ODE system defined.')
  109. # %%
  110. baseline = {
  111. 'k_PI4K': 0.00079,
  112. 'PI_PIP_ratio': 50.0,
  113. 'k_PIP5K': 0.01624,
  114. 'k_5P': 0.02172,
  115. 'k_plc': 0.242,
  116. 'k_deg': 0.07
  117. }
  118. print('Baseline parameters set.')
  119. # %%
  120. def generate_data(n_samples=50000, seed=42):
  121. np.random.seed(seed)
  122. print(f'Generating {n_samples:,} samples...')
  123. PI = 226975
  124. SA = 4100
  125. Vol = 6644
  126. cf = (SA / Vol) * (1 / 602)
  127. # 5 kinetic parameters to predict (ratio removed from labels)
  128. ranges = {
  129. 'k_PI4K': (0.6 * baseline['k_PI4K'], 1.4 * baseline['k_PI4K']),
  130. 'k_PIP5K': (0.4 * baseline['k_PIP5K'], 1.6 * baseline['k_PIP5K']),
  131. 'k_5P': (0.4 * baseline['k_5P'], 1.6 * baseline['k_5P']),
  132. 'k_plc': (0.4 * baseline['k_plc'], 1.6 * baseline['k_plc']),
  133. 'k_deg': (0.6 * baseline['k_deg'], 1.4 * baseline['k_deg']),
  134. }
  135. stim_durations = np.random.choice([20, 50, 60], size=n_samples)
  136. # Sample ratio separately -- uniform over full range
  137. ratios = np.random.uniform(RATIO_MIN, RATIO_MAX, size=n_samples)
  138. # LHS for the 5 kinetic parameters
  139. sampler = qmc.LatinHypercube(d=5, seed=seed)
  140. raw = sampler.random(n=n_samples)
  141. p = np.zeros_like(raw)
  142. for i, (lo, hi) in enumerate(ranges.values()):
  143. p[:, i] = raw[:, i] * (hi - lo) + lo
  144. X, y = [], []
  145. for i in tqdm(range(n_samples)):
  146. k_PI4K, k_PIP5K, k_5P, k_plc, k_deg = p[i]
  147. ratio = ratios[i]
  148. k_4P = ratio * k_PI4K # constraint enforced
  149. y0 = steady_state_y0(k_PI4K, ratio, k_PIP5K, k_5P, PI)
  150. pdict = {'k_PI4K': k_PI4K, 'k_4P': k_4P, 'k_PIP5K': k_PIP5K,
  151. 'k_5P': k_5P, 'k_plc': k_plc, 'k_deg': k_deg}
  152. sd = stim_durations[i]
  153. t = np.linspace(0, 300, 100)
  154. try:
  155. sol = odeint(pi_cycle_odes, y0, t,
  156. args=(pdict, PI, cf, 80, 80+sd, 0.7),
  157. rtol=1e-8, atol=1e-10)
  158. if not np.isfinite(sol).all():
  159. continue
  160. PIP = sol[:, 0] / sol[0, 0]
  161. PIP2 = sol[:, 1] / sol[0, 1]
  162. mx = sol[:, 4].max()
  163. IP3 = sol[:, 4] / mx if mx > 0 else sol[:, 4]
  164. stim = np.full(100, sd / 100.0)
  165. # 5th channel: normalized ratio (same value across all 100 time points)
  166. ratio_ch = np.full(100, normalize_ratio(ratio))
  167. X.append(np.stack([PIP, PIP2, IP3, stim, ratio_ch], axis=1))
  168. # Labels: 5 kinetic params only (ratio is an input, not a target)
  169. y.append([k_PI4K, k_PIP5K, k_5P, k_plc, k_deg])
  170. except Exception:
  171. continue
  172. print(f'Valid samples: {len(X):,} / {n_samples:,}')
  173. return np.array(X, dtype=np.float32), np.array(y, dtype=np.float32)
  174. print('Data generation function ready.')
  175. # %%
  176. X_train, y_train = generate_data(n_samples=N_SAMPLES, seed=RUN_SEED)
  177. print(f'X: {X_train.shape} # (n, 100, 5)')
  178. print(f'y: {y_train.shape} # (n, 5)')
  179. # %%
  180. fig, axes = plt.subplots(1, 3, figsize=(15, 4))
  181. t = np.linspace(0, 300, 100)
  182. for j, name in enumerate(['PIP', 'PIP2', 'IP3']):
  183. for i in np.random.choice(len(X_train), 100, replace=False):
  184. axes[j].plot(t, X_train[i, :, j], alpha=0.05, color='blue')
  185. axes[j].set_title(name)
  186. axes[j].set_xlabel('Time (s)')
  187. axes[j].grid(alpha=0.3)
  188. plt.tight_layout()
  189. plt.savefig(f'{OUTPUT_DIR}/training_preview.png', dpi=150)
  190. plt.show()
  191. # %%
  192. class PIDataset(Dataset):
  193. def __init__(self, X, y):
  194. self.X = torch.FloatTensor(X)
  195. self.y_log = torch.log10(torch.FloatTensor(y) + 1e-10)
  196. def __len__(self):
  197. return len(self.X)
  198. def __getitem__(self, idx):
  199. return self.X[idx], self.y_log[idx]
  200. class PINet(nn.Module):
  201. def __init__(self, input_dim=5, hidden_dim=128, num_layers=2, output_dim=5):
  202. super().__init__()
  203. self.conv1 = nn.Conv1d(input_dim, 64, kernel_size=5, padding=2)
  204. self.conv2 = nn.Conv1d(64, 128, kernel_size=5, padding=2)
  205. self.pool = nn.MaxPool1d(2)
  206. self.lstm = nn.LSTM(128, hidden_dim, num_layers,
  207. batch_first=True, dropout=0.2)
  208. self.fc1 = nn.Linear(hidden_dim, 128)
  209. self.fc2 = nn.Linear(128, 64)
  210. self.fc3 = nn.Linear(64, output_dim)
  211. self.relu = nn.ReLU()
  212. self.dropout = nn.Dropout(0.3)
  213. self.bn1 = nn.BatchNorm1d(64)
  214. self.bn2 = nn.BatchNorm1d(128)
  215. def forward(self, x):
  216. x = x.permute(0, 2, 1)
  217. x = self.relu(self.bn1(self.conv1(x)))
  218. x = self.pool(x)
  219. x = self.relu(self.bn2(self.conv2(x)))
  220. x = self.pool(x)
  221. x = x.permute(0, 2, 1)
  222. x, _ = self.lstm(x)
  223. x = x[:, -1, :]
  224. x = self.relu(self.fc1(x))
  225. x = self.dropout(x)
  226. x = self.relu(self.fc2(x))
  227. x = self.dropout(x)
  228. return self.fc3(x)
  229. print('Dataset and model defined.')
  230. print('Input channels: PIP, PIP2, IP3, stim_duration, ratio (5 total)')
  231. print('Output params: k_PI4K, k_PIP5K, k_5P, k_plc, k_deg (5 total)')
  232. # %%
  233. X_tr, X_val, y_tr, y_val = train_test_split(
  234. X_train, y_train, test_size=0.1, random_state=RUN_SEED)
  235. print(f'Train: {len(X_tr):,} Val: {len(X_val):,}')
  236. train_loader = DataLoader(PIDataset(X_tr, y_tr), batch_size=64, shuffle=True)
  237. val_loader = DataLoader(PIDataset(X_val, y_val), batch_size=64, shuffle=False)
  238. print('Loaders ready.')
  239. # %%
  240. device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  241. print(f'Device: {device}')
  242. model = PINet(input_dim=5, output_dim=5).to(device)
  243. criterion = nn.MSELoss()
  244. optimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-5)
  245. scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
  246. optimizer, mode='min', factor=0.5, patience=5)
  247. print(f'Parameters: {sum(p.numel() for p in model.parameters()):,}')
  248. # %%
  249. def train_epoch(model, loader, criterion, optimizer, device):
  250. model.train()
  251. total_loss, valid_batches = 0.0, 0
  252. for X_batch, y_batch in loader:
  253. X_batch, y_batch = X_batch.to(device), y_batch.to(device)
  254. if not torch.isfinite(X_batch).all(): continue
  255. if not torch.isfinite(y_batch).all(): continue
  256. optimizer.zero_grad()
  257. loss = criterion(model(X_batch), y_batch)
  258. if not torch.isfinite(loss): continue
  259. loss.backward()
  260. torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
  261. optimizer.step()
  262. total_loss += loss.item()
  263. valid_batches += 1
  264. return total_loss / valid_batches if valid_batches > 0 else float('nan')
  265. def validate(model, loader, criterion, device):
  266. model.eval()
  267. total_loss, valid_batches = 0.0, 0
  268. with torch.no_grad():
  269. for X_batch, y_batch in loader:
  270. X_batch, y_batch = X_batch.to(device), y_batch.to(device)
  271. if not torch.isfinite(X_batch).all(): continue
  272. if not torch.isfinite(y_batch).all(): continue
  273. loss = criterion(model(X_batch), y_batch)
  274. if not torch.isfinite(loss): continue
  275. total_loss += loss.item()
  276. valid_batches += 1
  277. return total_loss / valid_batches if valid_batches > 0 else float('nan')
  278. print('Training functions ready.')
  279. # %%
  280. checkpoint_path = f'{CHECKPOINT_DIR}/training_checkpoint.pt'
  281. start_epoch = 0
  282. best_val_loss = float('inf')
  283. train_losses = []
  284. val_losses = []
  285. patience_counter = 0
  286. if os.path.exists(checkpoint_path):
  287. print('Found checkpoint, resuming...')
  288. ckpt = torch.load(checkpoint_path)
  289. model.load_state_dict(ckpt['model'])
  290. optimizer.load_state_dict(ckpt['optimizer'])
  291. scheduler.load_state_dict(ckpt['scheduler'])
  292. start_epoch = ckpt['epoch'] + 1
  293. best_val_loss = ckpt['best_val_loss']
  294. train_losses = ckpt['train_losses']
  295. val_losses = ckpt['val_losses']
  296. patience_counter = ckpt['patience_counter']
  297. print(f'Resumed from epoch {start_epoch}')
  298. else:
  299. print('Starting fresh training')
  300. print(f'Training for up to {N_EPOCHS} epochs...\n')
  301. start_time = time.time()
  302. for epoch in range(start_epoch, N_EPOCHS):
  303. train_loss = train_epoch(model, train_loader, criterion, optimizer, device)
  304. val_loss = validate(model, val_loader, criterion, device)
  305. train_losses.append(train_loss)
  306. val_losses.append(val_loss)
  307. if np.isfinite(val_loss) and val_loss < best_val_loss:
  308. best_val_loss = val_loss
  309. torch.save(model.state_dict(), f'{CHECKPOINT_DIR}/best_model.pth')
  310. patience_counter = 0
  311. else:
  312. patience_counter += 1
  313. torch.save({
  314. 'epoch': epoch, 'model': model.state_dict(),
  315. 'optimizer': optimizer.state_dict(), 'scheduler': scheduler.state_dict(),
  316. 'best_val_loss': best_val_loss, 'train_losses': train_losses,
  317. 'val_losses': val_losses, 'patience_counter': patience_counter
  318. }, checkpoint_path)
  319. if (epoch + 1) % 10 == 0:
  320. lr = optimizer.param_groups[0]['lr']
  321. print(f'Epoch {epoch+1}/{N_EPOCHS} | '
  322. f'Train: {train_loss:.6f} | Val: {val_loss:.6f} | '
  323. f'Best: {best_val_loss:.6f} | LR: {lr:.6f}')
  324. if patience_counter >= PATIENCE:
  325. print(f'Early stopping at epoch {epoch+1}')
  326. break
  327. elapsed = time.time() - start_time
  328. print(f'\nDone in {elapsed/60:.1f} min | Best val loss: {best_val_loss:.6f}')
  329. # %%
  330. plt.figure(figsize=(8, 4))
  331. plt.plot(train_losses, label='Train')
  332. plt.plot(val_losses, label='Val')
  333. plt.xlabel('Epoch')
  334. plt.ylabel('MSE Loss')
  335. plt.title('Training curve')
  336. plt.legend()
  337. plt.grid(alpha=0.3)
  338. plt.tight_layout()
  339. plt.savefig(f'{OUTPUT_DIR}/loss_curve.png', dpi=150)
  340. plt.show()
  341. # %%
  342. experimental_data = {
  343. 'SCG': {
  344. 'time': np.arange(0, 264, 4),
  345. 'PIP': np.array([1.000228945, 1.019145079, 1.01847486, 1.013001365, 1.011373365, 1.00949151, 1.007427164, 1.013568162, 1.007596261, 1.007161807, 1.006393468, 1.005397447, 1.005972331, 1.004322455, 1.00322294, 1.001841359, 0.999476243, 0.994473405, 0.985835205, 0.978447035, 0.95768421, 0.92601231, 0.883451217, 0.840965043, 0.794204451, 0.768181235, 0.746076956, 0.740754728, 0.735236155, 0.738837119, 0.738899465, 0.738881756, 0.735339572, 0.739057224, 0.748468351, 0.755294914, 0.762498274, 0.769645878, 0.788341545, 0.796131065, 0.806043529, 0.816959776, 0.831344526, 0.837966439, 0.856660122, 0.866097364, 0.873055981, 0.888776333, 0.897794697, 0.898283764, 0.908599388, 0.912732096, 0.918565414, 0.927323747, 0.931540278, 0.930044926, 0.935394321, 0.941467019, 0.947825275, 0.950261638, 0.95221063, 0.953447485, 0.955094919, 0.962056348, 0.961344415, 0.964188999]),
  346. 'PIP2': np.array([1.00000084, 0.994329381, 1.003684332, 1.01238027, 1.001370793, 1.014275208, 1.019068623, 1.025286393, 1.021376017, 1.033808349, 1.033765832, 1.044470898, 1.057564312, 1.066161852, 1.054367241, 1.054415043, 1.03169049, 1.03743873, 1.050858443, 1.04603695, 1.029628345, 0.923978784, 0.48679318, 0.276771394, 0.137688215, 0.087431413, 0.050632959, 0.049037994, 0.076933626, 0.1173743, 0.167048938, 0.223215209, 0.273250572, 0.329744052, 0.370604954, 0.432028621, 0.469869382, 0.488020552, 0.556228878, 0.593654941, 0.640071437, 0.663253074, 0.69453996, 0.716814579, 0.729178755, 0.760902464, 0.772180962, 0.80298907, 0.816698667, 0.835885027, 0.855669997, 0.881415341, 0.867530616, 0.881964245, 0.89338665, 0.867959341, 0.873557793, 0.882656851, 0.888458335, 0.910417653, 0.885229072, 0.902716717, 0.889080619, 0.884813434, 0.899757591, 0.8871869]),
  347. 'IP3': np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.187654148, 0.488781155, 0.797607295, 0.889403036, 0.965747014, 1, 0.95862008, 0.965747014, 0.907691096, 0.918967978, 0.890175961, 0.757785213, 0.79063917, 0.724851376, 0.647930747, 0.692170804, 0.592637375, 0.513760867, 0.544461898, 0.635768453, 0.4959248, 0.454274312, 0.4162112, 0.424496157, 0.436885106, 0.371886584, 0.354061644, 0.360037911, 0.343798328, 0.314046331, 0.310208367, 0.311755623, 0.208950662, 0.224153337, 0.267904163, 0.254036531, 0.223629547, 0.199424576, 0.229936645, 0.156234824, 0.152500174, 0.12721983, 0.050564457, 0.120429132, 0.120429132]),
  348. 'stimulus_duration': 20,
  349. 'PI_PIP_ratio': 50.00
  350. },
  351. 'Hippocampal': {
  352. 'time': np.arange(0, 276, 4),
  353. 'PIP': np.array([1.02, 1.02, 1.02, 1.02, 1.03, 1.03, 1.02, 1.02, 1.02, 1.02, 1.02, 1.01, 1.01, 1.01, 1.00, 1.00, 1.01, 1.00, 1.00, 1.00, 1.00, 1.00, 0.99, 0.99, 0.99, 0.98, 0.98, 0.99, 0.99, 0.99, 0.97, 0.98, 0.97, 0.97, 0.96, 0.96, 0.95, 0.95, 0.95, 0.93, 0.94, 0.92, 0.93, 0.93, 0.93, 0.93, 0.92, 0.91, 0.92, 0.92, 0.91, 0.90, 0.92, 0.91, 0.91, 0.90, 0.90, 0.90, 0.91, 0.89, 0.90, 0.88, 0.89, 0.90, 0.89, 0.90, 0.88, 0.88, 0.88]),
  354. 'PIP2': np.array([0.914, 0.914, 0.914, 0.914, 0.965, 0.963, 0.967, 0.958, 1.002, 0.965, 0.963, 0.967, 0.958, 1.002, 0.974, 1.032, 1.011, 1.002, 1.034, 1.053, 1.030, 0.958, 0.737, 0.410, 0.201, 0.141, 0.008, 0.055, 0.039, 0.032, -0.008, 0.041, 0.133, 0.184, 0.147, 0.147, 0.168, 0.187, 0.235, 0.307, 0.377, 0.451, 0.507, 0.574, 0.627, 0.664, 0.711, 0.710, 0.729, 0.752, 0.764, 0.768, 0.773, 0.780, 0.803, 0.768, 0.770, 0.801, 0.768, 0.768, 0.773, 0.888, 0.854, 0.791, 0.798, 0.733, 0.721, 0.751, 0.742]),
  355. 'IP3': np.array([0.147059, 0.147059, 0.147059, 0.147059, 0.117647, 0.029412, 0, 0.029412, 0.073529, 0.044118, 0.073529, 0.029412, 0.029412, 0.058824, 0.073529, 0.102941, 0.132353, 0.058824, 0.014706, 0.161765, 0.323529, 0.426471, 0.661765, 0.720588, 0.808824, 0.897059, 0.926471, 0.897059, 0.941176, 1, 0.970588, 0.985294, 0.955882, 0.985294, 1, 0.970588, 0.955882, 0.882353, 0.838235, 0.867647, 0.779412, 0.632353, 0.632353, 0.544118, 0.455882, 0.205882, 0.235294, 0.235294, 0.132353, 0.088235, 0.073529, 0.235294, 0.25, 0.220588, 0.161765, 0.058824, 0.102941, 0.132353, 0.058824, 0.132353, 0.161765, 0.102941, 0.088235, 0.102941, 0.132353, 0.102941, 0.147059, 0.205882, 0.25]),
  356. 'stimulus_duration': 50,
  357. 'PI_PIP_ratio': 5.38
  358. },
  359. 'tsA': {
  360. 'time': np.arange(0, 300, 4),
  361. 'PIP': np.array([0.946, 0.962, 0.983, 0.962, 0.965, 0.988, 0.958, 0.974, 0.957, 0.972, 0.971, 1, 0.979, 0.997, 0.989, 0.994, 0.993, 0.966, 0.956, 0.903, 0.873, 0.814, 0.718, 0.683, 0.662, 0.654, 0.643, 0.657, 0.606, 0.612, 0.6, 0.602, 0.604, 0.6, 0.606, 0.605, 0.614, 0.62, 0.635, 0.641, 0.631, 0.635, 0.64, 0.656, 0.628, 0.64, 0.661, 0.661, 0.676, 0.679, 0.653, 0.685, 0.675, 0.682, 0.682, 0.702, 0.697, 0.708, 0.697, 0.722, 0.717, 0.711, 0.705, 0.717, 0.742, 0.719, 0.728, 0.715, 0.733, 0.72, 0.721, 0.715, 0.746, 0.741, 0.734]),
  362. 'PIP2': np.array([0.991, 0.987260044, 0.985, 0.982739956, 0.979, 0.973655131, 0.972, 0.977889519, 0.984, 0.983786795, 0.985, 0.994463303, 1.001, 0.994359992, 0.986, 0.988471727, 0.997, 1.001878098, 0.991, 0.95064088, 0.863, 0.721808383, 0.571, 0.455625586, 0.375, 0.317064271, 0.27, 0.225742331, 0.191, 0.172091407, 0.159, 0.142142043, 0.13, 0.131215422, 0.137, 0.137496269, 0.136, 0.137674501, 0.142, 0.147055726, 0.151, 0.152852594, 0.155, 0.159908897, 0.167, 0.174886819, 0.182, 0.187293828, 0.192, 0.19681287, 0.198, 0.193579691, 0.193, 0.204868367, 0.223, 0.238571841, 0.247, 0.24784427, 0.253, 0.27130108, 0.287, 0.285451412, 0.281, 0.289393273, 0.303, 0.311725495, 0.319, 0.329204745, 0.337, 0.338330524, 0.344, 0.363473159, 0.386, 0.395776841, 0.377]),
  363. 'IP3': np.array([0, 0, 0, 0, 0.020173079, 0.045400514, 0.012211553, 0.000322887, 0.010325055, 0.030511855, 0.044581874, 0.054724416, 0.068358943, 0.064446759, 0.055239999, 0.033072461, 0.013131853, 0.000866517, 0, 0, 0, 0.014970792, 0.2898243, 0.752767783, 0.930383958, 0.961510528, 0.999909621, 0.96142814, 0.906313748, 0.927474172, 0.966169559, 0.974910744, 0.977399169, 0.940000652, 0.951186253, 0.921090191, 0.833836074, 0.787703727, 0.812838593, 0.812291835, 0.80938294, 0.734586375, 0.676045647, 0.642574059, 0.616124078, 0.593201146, 0.572566747, 0.550100919, 0.53245906, 0.512246625, 0.489295962, 0.471909516, 0.458368649, 0.487777547, 0.45786329, 0.385951806, 0.375624462, 0.38903272, 0.370521362, 0.334801694, 0.386746725, 0.428079727, 0.28953423, 0.278087256, 0.291503843, 0.329827114, 0.229275314, 0.251684177, 0.354957011, 0.31487703, 0.198784886, 0.175409618, 0.212764316, 0.242300052, 0.241267038]),
  364. 'stimulus_duration': 20,
  365. 'PI_PIP_ratio': 35.00
  366. },
  367. 'Neuroblastoma': {
  368. 'time': np.array([0, 80, 88, 140, 170, 200, 256, 440, 737, 1038, 1945]),
  369. 'PIP': np.array([0.9352, 0.9352, 0.411, 0.392, 0.415, 0.545, 0.6548, 0.7976, 0.975, 1.0, 0.9945]),
  370. 'PIP2': np.array([0.9439, 0.9439, 0.2783, 0.2414, 0.671, 0.777, 0.8466, 0.9351, 1.0, 0.986, 0.9985]),
  371. 'IP3': np.array([0, 0, 0.7293, 1, 0.7289, 0.5331, 0.3708, 0.1885, 0.1034, 0.091, 0.0341]),
  372. 'stimulus_duration': 60,
  373. 'PI_PIP_ratio': 22.42
  374. }
  375. }
  376. time_model = np.linspace(0, 300, 100)
  377. for ct, data in experimental_data.items():
  378. for key in ['PIP', 'PIP2', 'IP3']:
  379. experimental_data[ct][key] = np.interp(time_model, data['time'], data[key])
  380. print('Experimental data loaded with known ratios:')
  381. for ct, data in experimental_data.items():
  382. print(f' {ct:15s}: PI/PIP ratio = {data["PI_PIP_ratio"]}')
  383. # %%
  384. model.load_state_dict(torch.load(f'{CHECKPOINT_DIR}/best_model.pth'))
  385. model.eval()
  386. param_names = ['k_PI4K', 'k_PIP5K', 'k_5P', 'k_plc', 'k_deg']
  387. predictions = {}
  388. print('=' * 80)
  389. print('PREDICTIONS vs SCG BASELINE')
  390. print('=' * 80)
  391. for cell_type, data in experimental_data.items():
  392. ratio = data['PI_PIP_ratio']
  393. stim_duration = data['stimulus_duration']
  394. stim_ch = np.full(100, stim_duration / 100.0)
  395. ratio_ch = np.full(100, normalize_ratio(ratio))
  396. X_exp = np.stack([data['PIP'], data['PIP2'], data['IP3'],
  397. stim_ch, ratio_ch], axis=1)
  398. X_exp = torch.FloatTensor(X_exp).unsqueeze(0).to(device)
  399. with torch.no_grad():
  400. y_pred = 10 ** model(X_exp).cpu().numpy()[0]
  401. # Derive k_4P from known ratio and predicted k_PI4K
  402. k_4P_pred = y_pred[0] * ratio
  403. pred_dict = {name: y_pred[i] for i, name in enumerate(param_names)}
  404. pred_dict['PI_PIP_ratio'] = ratio # known, not predicted
  405. pred_dict['k_4P_derived'] = k_4P_pred
  406. predictions[cell_type] = pred_dict
  407. print(f'\n{cell_type} (ratio = {ratio}):')
  408. print('-' * 80)
  409. for name in param_names:
  410. base = baseline[name]
  411. pred = pred_dict[name]
  412. print(f' {name:15s}: Pred = {pred:.6f}, Base = {base:.6f}, Fold = {pred/base:.2f}x')
  413. base_k4P = baseline['k_PI4K'] * baseline['PI_PIP_ratio']
  414. print(f' {"k_4P (derived)":15s}: Pred = {k_4P_pred:.6f}, '
  415. f'Base = {base_k4P:.6f}, Fold = {k_4P_pred/base_k4P:.2f}x')
  416. print('\n' + '=' * 80)
  417. print('Predictions complete!')
  418. # %% [markdown]
  419. # ## Step 1: Network Predictions (above)
  420. #
  421. # ---
  422. # %%
  423. from scipy.optimize import minimize
  424. param_names_5 = ['k_PI4K', 'k_PIP5K', 'k_5P', 'k_plc', 'k_deg']
  425. cell_geometry = {
  426. 'SCG': {'PI': 226975, 'SA': 4100, 'Vol': 6644},
  427. 'Hippocampal': {'PI': 140000, 'SA': 4100, 'Vol': 6644},
  428. 'tsA': {'PI': 140000, 'SA': 1500, 'Vol': 2500},
  429. 'Neuroblastoma': {'PI': 17222, 'SA': 700, 'Vol': 700},
  430. }
  431. cell_PIP0 = {
  432. 'SCG': 4540,
  433. 'Hippocampal': 26000,
  434. 'tsA': 4000,
  435. 'Neuroblastoma': 768,
  436. }
  437. cell_PIP2_0 = {
  438. 'SCG': 3232,
  439. 'Hippocampal': 2400,
  440. 'tsA': 5000,
  441. 'Neuroblastoma': 573,
  442. }
  443. t_model = np.linspace(0, 300, 100)
  444. STIM_IDX = int(80 / 300 * 100)
  445. PARAM_FLOORS = np.array([0.0003, 1e-4, 0.008, 0.05, 1e-4])
  446. PARAM_CEILINGS = np.array([0.002, 0.08, 0.08, 0.6, 0.15])
  447. def simulate(params_vec, ratio, PI, cf, stim_duration, PIP0, PIP2_0):
  448. k_PI4K, k_PIP5K, k_5P, k_plc, k_deg = params_vec
  449. k_4P = ratio * k_PI4K
  450. pdict = {'k_PI4K': k_PI4K, 'k_4P': k_4P, 'k_PIP5K': k_PIP5K,
  451. 'k_5P': k_5P, 'k_plc': k_plc, 'k_deg': k_deg}
  452. y0 = [PIP0, PIP2_0, 0.0, 6.0, 0.0]
  453. try:
  454. sol = odeint(pi_cycle_odes, y0, t_model,
  455. args=(pdict, PI, cf, 80, 80 + stim_duration, 0.7),
  456. rtol=1e-8, atol=1e-10)
  457. if not np.isfinite(sol).all():
  458. return None
  459. PIP = sol[:, 0] / sol[0, 0]
  460. PIP2 = sol[:, 1] / sol[0, 1]
  461. mx = sol[:, 4].max()
  462. IP3 = sol[:, 4] / mx if mx > 0 else sol[:, 4]
  463. return PIP, PIP2, IP3
  464. except Exception:
  465. return None
  466. def objective(params_vec, ratio, PI, cf, stim_duration, PIP0, PIP2_0,
  467. exp_PIP, exp_PIP2, exp_IP3,
  468. w_PIP=1.0, w_PIP2=1.0, w_IP3=0.5):
  469. p = np.array(params_vec)
  470. if np.any(p < PARAM_FLOORS) or np.any(p > PARAM_CEILINGS):
  471. return 1e6
  472. result = simulate(p, ratio, PI, cf, stim_duration, PIP0, PIP2_0)
  473. if result is None:
  474. return 1e6
  475. sim_PIP, sim_PIP2, sim_IP3 = result
  476. loss = (w_PIP * np.mean((sim_PIP[STIM_IDX:] - exp_PIP[STIM_IDX:]) ** 2) +
  477. w_PIP2 * np.mean((sim_PIP2[STIM_IDX:] - exp_PIP2[STIM_IDX:]) ** 2) +
  478. w_IP3 * np.mean((sim_IP3[STIM_IDX:] - exp_IP3[STIM_IDX:]) ** 2))
  479. return loss
  480. refined_predictions = {}
  481. print('=' * 80)
  482. print('STEP 1: REFINED PREDICTIONS (Nelder-Mead, biological ICs)')
  483. print('=' * 80)
  484. for cell_type, data in experimental_data.items():
  485. print(f'\nOptimizing {cell_type}...')
  486. pred = predictions[cell_type]
  487. ratio = pred['PI_PIP_ratio']
  488. geo = cell_geometry[cell_type]
  489. PI = geo['PI']
  490. cf = (geo['SA'] / geo['Vol']) * (1 / 602)
  491. sd = data['stimulus_duration']
  492. PIP0 = cell_PIP0[cell_type]
  493. PIP2_0 = cell_PIP2_0[cell_type]
  494. exp_PIP = data['PIP']
  495. exp_PIP2 = data['PIP2']
  496. exp_IP3 = data['IP3']
  497. x0 = np.array([pred[name] for name in param_names_5])
  498. loss_before = objective(x0, ratio, PI, cf, sd, PIP0, PIP2_0,
  499. exp_PIP, exp_PIP2, exp_IP3)
  500. result = minimize(
  501. objective, x0,
  502. args=(ratio, PI, cf, sd, PIP0, PIP2_0, exp_PIP, exp_PIP2, exp_IP3),
  503. method='Nelder-Mead',
  504. options={'maxiter': 5000, 'xatol': 1e-8, 'fatol': 1e-8, 'adaptive': True}
  505. )
  506. x_ref = result.x
  507. k_4P_refined = x_ref[0] * ratio
  508. refined_dict = {name: x_ref[i] for i, name in enumerate(param_names_5)}
  509. refined_dict['PI_PIP_ratio'] = ratio
  510. refined_dict['k_4P_derived'] = k_4P_refined
  511. refined_dict['PIP0'] = PIP0
  512. refined_dict['PIP2_0'] = PIP2_0
  513. refined_dict['PI'] = PI
  514. refined_dict['cf'] = cf
  515. refined_predictions[cell_type] = refined_dict
  516. print(f'{cell_type} (ratio={ratio}, PI={PI}, PIP0={PIP0}, PIP2_0={PIP2_0})')
  517. print('-' * 80)
  518. print(f' {"Parameter":15s} {"Network":>12s} {"Refined":>12s} {"Change":>10s}')
  519. print('-' * 80)
  520. for i, name in enumerate(param_names_5):
  521. print(f' {name:15s} {x0[i]:12.6f} {x_ref[i]:12.6f} {x_ref[i]/x0[i]:10.2f}x')
  522. print(f' {"k_4P (derived)":15s} {pred["k_4P_derived"]:12.6f} {k_4P_refined:12.6f} {k_4P_refined/pred["k_4P_derived"]:10.2f}x')
  523. print(f' Loss before: {loss_before:.6f} -> Loss after: {result.fun:.6f} '
  524. f'(improvement: {(1 - result.fun/loss_before)*100:.1f}%)')
  525. print('\n' + '=' * 80)
  526. print('Refinement complete!')
  527. # %% [markdown]
  528. # ## Step 2: Local Refinement
  529. #
  530. # Takes the network predictions as starting point and runs L-BFGS-B to minimize trajectory MSE directly against experimental data. Bounds are +-60% around each network prediction so the optimizer stays in a physically reasonable region.
  531. #
  532. # The refined plot shows network prediction (gray dashed) vs refined (solid color) vs experimental (dots).
  533. # %%
  534. ss_corrected = {}
  535. print('=' * 80)
  536. print('STEP 2: SS CORRECTION (analytical, k_PIP5K only)')
  537. print('=' * 80)
  538. for cell_type in experimental_data:
  539. ref = refined_predictions[cell_type]
  540. PIP0 = ref['PIP0']
  541. PIP2_0 = ref['PIP2_0']
  542. ratio = ref['PI_PIP_ratio']
  543. PI = ref['PI']
  544. cf = ref['cf']
  545. k_PI4K = ref['k_PI4K']
  546. k_5P = ref['k_5P']
  547. k_plc = ref['k_plc']
  548. k_deg = ref['k_deg']
  549. k_PIP5K_ss = k_5P * (PIP2_0 / PIP0)
  550. k_4P_ss = ratio * k_PI4K
  551. dPIP2_dt = k_PIP5K_ss * PIP0 - k_5P * PIP2_0
  552. dPIP_dt = k_PI4K*PI - k_4P_ss*PIP0 - k_PIP5K_ss*PIP0 + k_5P*PIP2_0
  553. ss_corrected[cell_type] = {
  554. 'k_PI4K': k_PI4K,
  555. 'k_4P': k_4P_ss,
  556. 'k_PIP5K': k_PIP5K_ss,
  557. 'k_5P': k_5P,
  558. 'k_plc': k_plc,
  559. 'k_deg': k_deg,
  560. 'PI_PIP_ratio': ratio,
  561. 'PIP0': PIP0,
  562. 'PIP2_0': PIP2_0,
  563. 'PI': PI,
  564. 'cf': cf,
  565. }
  566. print(f'\n{cell_type}:')
  567. print(f' {"Parameter":15s} {"Refined":>12s} {"SS-Corrected":>14s}')
  568. print('-' * 50)
  569. print(f' {"k_PI4K":15s} {ref["k_PI4K"]:12.6f} (unchanged)')
  570. print(f' {"k_PIP5K":15s} {ref["k_PIP5K"]:12.6f} {k_PIP5K_ss:14.6f}')
  571. print(f' {"k_5P":15s} {ref["k_5P"]:12.6f} (unchanged)')
  572. print(f' {"k_plc":15s} {ref["k_plc"]:12.6f} (unchanged)')
  573. print(f' {"k_deg":15s} {ref["k_deg"]:12.6f} (unchanged)')
  574. print(f' {"k_4P":15s} {ref["k_4P_derived"]:12.6f} {k_4P_ss:14.6f}')
  575. print(f' dPIP2/dt at t=0 = {dPIP2_dt:.8f} (should be 0)')
  576. print(f' dPIP/dt at t=0 = {dPIP_dt:.8f} (should be ~0)')
  577. print('\n' + '=' * 80)
  578. print('SS correction complete!')
  579. # %%
  580. def plot_final(cell_type):
  581. ref = ss_corrected[cell_type]
  582. pred = predictions[cell_type]
  583. PI = ref['PI']
  584. cf = ref['cf']
  585. PIP0 = ref['PIP0']
  586. PIP2_0 = ref['PIP2_0']
  587. ratio = ref['PI_PIP_ratio']
  588. sd = experimental_data[cell_type]['stimulus_duration']
  589. params_final = {
  590. 'k_PI4K': ref['k_PI4K'], 'k_4P': ref['k_4P'],
  591. 'k_PIP5K': ref['k_PIP5K'], 'k_5P': ref['k_5P'],
  592. 'k_plc': ref['k_plc'], 'k_deg': ref['k_deg'],
  593. }
  594. params_net = {
  595. 'k_PI4K': pred['k_PI4K'], 'k_4P': pred['k_4P_derived'],
  596. 'k_PIP5K': pred['k_PIP5K'], 'k_5P': pred['k_5P'],
  597. 'k_plc': pred['k_plc'], 'k_deg': pred['k_deg'],
  598. }
  599. y0_final = [PIP0, PIP2_0, 0.0, 6.0, 0.0]
  600. y0_net = [PIP0, (pred['k_PIP5K'] / pred['k_5P']) * PIP0, 0.0, 6.0, 0.0]
  601. t = np.linspace(0, 300, 100)
  602. sol_final = odeint(pi_cycle_odes, y0_final, t,
  603. args=(params_final, PI, cf, 80, 80+sd, 0.7),
  604. rtol=1e-8, atol=1e-10)
  605. sol_net = odeint(pi_cycle_odes, y0_net, t,
  606. args=(params_net, PI, cf, 80, 80+sd, 0.7),
  607. rtol=1e-8, atol=1e-10)
  608. def norm(sol, col):
  609. v = sol[:, col]
  610. return v / v[0] if v[0] != 0 else v
  611. def norm_ip3(sol):
  612. v = sol[:, 4]
  613. mx = v.max()
  614. return v / mx if mx > 0 else v
  615. fig, axes = plt.subplots(1, 3, figsize=(18, 5))
  616. for ax, (name, col, color), ekey in zip(
  617. axes,
  618. [('PIP', 0, 'black'), ('PIP2', 1, 'red'), ('IP3', None, 'magenta')],
  619. ['PIP', 'PIP2', 'IP3']
  620. ):
  621. exp = experimental_data[cell_type][ekey]
  622. n_trace = norm(sol_net, col) if col is not None else norm_ip3(sol_net)
  623. f_trace = norm(sol_final, col) if col is not None else norm_ip3(sol_final)
  624. ax.scatter(t, exp, color=color, alpha=0.5, s=30, label='Experimental', zorder=3)
  625. ax.plot(t, n_trace, color='gray', linewidth=2, linestyle='--', label='Network', zorder=1)
  626. ax.plot(t, f_trace, color=color, linewidth=3, label='Final', zorder=2)
  627. ax.axvline(80, color='blue', linewidth=1, linestyle=':', alpha=0.6, label='Stim on')
  628. ax.axvline(80+sd, color='blue', linewidth=1, linestyle='-.', alpha=0.6, label='Stim off')
  629. ax.set_xlabel('Time (s)', fontsize=13, fontweight='bold')
  630. ax.set_ylabel(f'{name} (normalized)', fontsize=13, fontweight='bold')
  631. ax.set_title(f'{cell_type} - {name} (ratio={ratio})', fontsize=14, fontweight='bold')
  632. ax.legend(fontsize=10)
  633. ax.grid(alpha=0.3)
  634. ax.set_xlim([0, 300])
  635. plt.tight_layout()
  636. plt.savefig(f'{OUTPUT_DIR}/{cell_type}_final.png', dpi=150, bbox_inches='tight')
  637. plt.show()
  638. print(f'{cell_type} final plot saved.')
  639. for ct in experimental_data:
  640. plot_final(ct)
  641. # %% [markdown]
  642. # ## Done
  643. #
  644. # **v4 architecture change:**
  645. # - Input channels: PIP, PIP2, IP3, stim_duration, **ratio** (5 total, was 4)
  646. # - Output parameters: k_PI4K, k_PIP5K, k_5P, k_plc, k_deg (5 total, was 6)
  647. # - PI_PIP_ratio is now a **known input** from biology, not a predicted output
  648. # - k_4P derived as `known_ratio * predicted_k_PI4K` at prediction time
  649. # - Plots use cell-specific PI pools and geometry from literature
  650. #
  651. # **Known ratios used:**
  652. # - SCG: 50.00
  653. # - tsA: 35.00
  654. # - Neuroblastoma: 22.42
  655. # - Hippocampal: 5.38
  656. #
  657. # **To retrain from scratch:** uncomment Cell 1, run it, comment it back, Run All.

PI_Cycle_v6.ipynb at commit 3094923, under GPL-3.0 · at the source

Overview

Authors: Gonzalo Hernandez-Hernandez1,2, Mindy Tieu2, Pei-Chi Yang1,2, Oscar Vivas3, Timothy J Lewis4, L Fernando Santana2, Colleen E Clancy1,2,5
ORCID iDs: Colleen E Clancy
  1. Center for Precision Medicine and Data Science, University of California, Davis, CA, USA
  2. University of California Davis, Department of Physiology and Membrane Biology, Davis, CA, USA
  3. Department of Physiology and Biophysics, University of Washington, Seattle, WA, USA
  4. Department of Mathematics, University of California, Davis, CA, USA
  5. Department of Pharmacology, University of California, Davis, CA, USA
Institutions: University of California, Davis (United States); University of Washington (United States)
Dates: published online 25 July 2026; in print September 2026
Type: Research article · Language: English
License: CC BY-NC-ND
Identifiers: DOI 10.1016/j.premed.2026.100050 · PMID 42741463 · PMCID PMC13574257 · OpenAlex W7168787831
Open access: hybrid, a free copy (OpenAlex)
Status: code verified
Methods: Machine learning
Keywords: Phosphoinositide signaling, New approach methodologies (NAMs), Precision medicine, Biological sciences, Biophysics and computational biology
Topic: Protein Kinase Regulation and GTPase Signaling (Molecular Biology, Biochemistry, Genetics and Molecular Biology), according to OpenAlex
Funding: NHLBI NIH HHS (R01 HL174001, R01 HL168874, R01 HL128537); NIGMS NIH HHS (R35 GM142690)
Citations: not cited yet (Europe PMC); 31 references in the paper

Abstract

The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.

Repository

Its files are read in the Code ↔ Paper reader above, with 11 matches between paragraphs and lines of code.

ClancyLabUCD/from-lipid-dynamics-to-precision-predictions

License: GPL-3.0
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 30949231ef04ff484224489768dfd0ef6ca059fa, 29 June 2026
Languages: MATLAB (20), Jupyter (1)
Size: 23 files, 21 scripts
Software Heritage: not archived
Found in: the text, “Simulation protocols”
Holds: README, license file, 1 notebook
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: Parallel Computing Toolbox (2 files), Matplotlib (1 file), NumPy (1 file), PyTorch (1 file), scikit-learn (1 file), SciPy (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
23 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 21 scripts, each with its path and the digest of its content;
  • 11 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Code and data availability statement

The paper has a code and data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

  • it says that the data are available on request

Read it in the paper: doi.org/10.1016/j.premed.2026.100050.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 2, 28 September 2026

  • Publisher: n/a → Elsevier BV

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, pages, dates, 7 authors, 5 keywords, 2 funders, 31 references.

Cite

This paper

Hernandez-Hernandez, G., Tieu, M., Yang, P.-C., Vivas, O., Lewis, T. J., Santana, L. F., & Clancy, C. E. (2026). From lipid dynamics to precision predictions: A new approach methodology for precision modeling of phosphoinositide signaling. Journal of precision medicine (Amsterdam, Netherlands), 7, 100050. https://doi.org/10.1016/j.premed.2026.100050

BibTeX

@article{hernandezhernandez2026lipid,
author = {Hernandez-Hernandez, Gonzalo and Tieu, Mindy and Yang, Pei-Chi and Vivas, Oscar and Lewis, Timothy J and Santana, L Fernando and Clancy, Colleen E},
title = {{From lipid dynamics to precision predictions: A new approach methodology for precision modeling of phosphoinositide signaling}},
journal = {Journal of precision medicine (Amsterdam, Netherlands)},
year = {2026},
month = jul,
volume = {7},
pages = {100050},
publisher = {Elsevier BV},
issn = {3050-6328},
doi = {10.1016/j.premed.2026.100050},
url = {https://doi.org/10.1016/j.premed.2026.100050},
pmid = {42741463},
pmcid = {PMC13574257}
}

RIS

TY - JOUR
AU - Hernandez-Hernandez, Gonzalo
AU - Tieu, Mindy
AU - Yang, Pei-Chi
AU - Vivas, Oscar
AU - Lewis, Timothy J
AU - Santana, L Fernando
AU - Clancy, Colleen E
TI - From lipid dynamics to precision predictions: A new approach methodology for precision modeling of phosphoinositide signaling
T2 - Journal of precision medicine (Amsterdam, Netherlands)
J2 - J Precis Med (Amst)
PY - 2026
DA - 2026/07/25
VL - 7
SP - 100050
SN - 3050-6328
PB - Elsevier BV
DO - 10.1016/j.premed.2026.100050
UR - https://doi.org/10.1016/j.premed.2026.100050
LA - en
ER -

CSL-JSON

{
"id": "10.1016/j.premed.2026.100050",
"type": "article-journal",
"title": "From lipid dynamics to precision predictions: A new approach methodology for precision modeling of phosphoinositide signaling",
"container-title": "Journal of precision medicine (Amsterdam, Netherlands)",
"author": [
{
"family": "Hernandez-Hernandez",
"given": "Gonzalo"
},
{
"family": "Tieu",
"given": "Mindy"
},
{
"family": "Yang",
"given": "Pei-Chi"
},
{
"family": "Vivas",
"given": "Oscar"
},
{
"family": "Lewis",
"given": "Timothy J"
},
{
"family": "Santana",
"given": "L Fernando"
},
{
"family": "Clancy",
"given": "Colleen E"
}
],
"container-title-short": "J Precis Med (Amst)",
"volume": "7",
"page": "100050",
"DOI": "10.1016/j.premed.2026.100050",
"PMID": "42741463",
"PMCID": "PMC13574257",
"ISSN": "3050-6328",
"publisher": "Elsevier BV",
"URL": "https://doi.org/10.1016/j.premed.2026.100050",
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
25
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.7554/elife.110013 [code]
Large-scale synthetic data enable digital twins of human excitable cells.
Journal: eLife
In common: Matplotlib, NumPy, 2 references, author Colleen E Clancy
[2] doi:10.1038/s41467-026-75347-4 [code]
Sleep reveals dynamics integrating and segregating movement and stimulus representations in V1.
Journal: Nature communications
In common: Parallel Computing Toolbox, PyTorch, scikit-learn, 3 other tools
[3] doi:10.1371/journal.pone.0352191 [code]
Bayesian Uncertainty-aware Deep Learning with noisy labels: Tackling annotation ambiguity in EEG seizure detection.
Journal: PloS one
In common: Parallel Computing Toolbox, PyTorch, scikit-learn, 3 other tools
[4] doi:10.1016/j.patter.2026.101590 [code]
Density-based longitudinal neuron tracking in high-density electrophysiological recordings.
Journal: Patterns (New York, N.Y.)
In common: Parallel Computing Toolbox, PyTorch, scikit-learn, 3 other tools
[5] doi:10.3389/frai.2026.1785867 [code]
A generalized logistic-logit function and its application to multi-layer perceptron and neuron segmentation.
Journal: Frontiers in artificial intelligence
In common: Parallel Computing Toolbox, PyTorch, scikit-learn, 3 other tools
[6] doi:10.1016/j.neuron.2026.04.011 [code]
Precision fMRI reveals densely interdigitated network patches with conserved motifs in the lateral prefrontal cortex.
Journal: Neuron
In common: Parallel Computing Toolbox, PyTorch, scikit-learn, 3 other tools
[7] doi:10.1038/s41467-026-72214-0 [code]
Auricular vagus nerve stimulation drives analgesia via an auricle-brain axis in a mouse model of neuropathic pain.
Journal: Nature communications
In common: Parallel Computing Toolbox, PyTorch, scikit-learn, 3 other tools
[8] doi:10.1016/j.neuron.2026.03.034 [code]
Dentate gyrus interneurons modulate winner-take-all network dynamics in freely behaving mice.
Journal: Neuron
In common: Parallel Computing Toolbox, PyTorch, scikit-learn, 3 other tools
[9] doi:10.1038/s41467-026-71458-0 [code]
Early differential impact of MeCP2 mutations on functional networks in Rett syndrome patient-derived human cortical organoids.
Journal: Nature communications
In common: Parallel Computing Toolbox, PyTorch, scikit-learn, 3 other tools
[10] doi:10.1038/s41467-026-71719-y [code]
Brain functional-structural gradient coupling reflects development, behavior and genetic influences.
Journal: Nature communications
In common: Parallel Computing Toolbox, PyTorch, scikit-learn, 3 other tools

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.