Spaces:
Sleeping
Sleeping
| """ | |
| AI Model for Rabi Oscillation Parameter Estimation. | |
| Transformer-based model that predicts fit parameters (A, T, phi, C) from raw signal data. | |
| """ | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| import numpy as np | |
| import math | |
| DEVICE = torch.device("mps") if torch.backends.mps.is_available() else torch.device("cpu") | |
| FIXED_LEN = 256 | |
| A_MIN, A_MAX = 1e-5, 0.5 | |
| T_MIN, T_MAX = 0.02, 0.3 | |
| C_MIN, C_MAX = -0.1, 1.0 | |
| LOG_A_MIN, LOG_A_MAX = math.log(A_MIN), math.log(A_MAX) | |
| LOG_T_MIN, LOG_T_MAX = math.log(T_MIN), math.log(T_MAX) | |
| AUX_SCALES = np.array([0.1, 0.1, 0.1, 0.05, 0.1, 2.0], dtype=np.float32) | |
| def preprocess_sample(x_raw, y_raw): | |
| xu = np.linspace(x_raw[0], x_raw[-1], FIXED_LEN) | |
| yr = np.interp(xu, x_raw, y_raw) | |
| ymin, ymax = float(yr.min()), float(yr.max()) | |
| ymean, ystd = float(yr.mean()), float(yr.std()) | |
| xrange = float(x_raw[-1] - x_raw[0]) | |
| ydm = yr - ymean | |
| zc = np.where(np.diff(np.sign(ydm)))[0] | |
| nper = len(zc) / 2.0 if len(zc) >= 1 else 0.5 | |
| span = ymax - ymin | |
| if span < 1e-15: | |
| span = 1.0 | |
| yn = ((yr - ymin) / span).astype(np.float32) | |
| fft = np.fft.rfft(yn) | |
| fft_mag = np.abs(fft[:64]) | |
| fft_ang = np.angle(fft[:64]) | |
| if len(fft_mag) < 64: | |
| fft_mag = np.pad(fft_mag, (0, 64 - len(fft_mag))) | |
| fft_ang = np.pad(fft_ang, (0, 64 - len(fft_ang))) | |
| fft_feats = np.concatenate([fft_mag, fft_ang]).astype(np.float32) | |
| aux = np.array([ymin, ymax, ymean, ystd, xrange, nper], dtype=np.float32) / AUX_SCALES | |
| return yn, fft_feats, aux | |
| def target_to_params(t): | |
| nA, nT, sp, cp, nC = t[..., 0], t[..., 1], t[..., 2], t[..., 3], t[..., 4] | |
| A = np.exp(nA * (LOG_A_MAX - LOG_A_MIN) + LOG_A_MIN) | |
| T = np.exp(nT * (LOG_T_MAX - LOG_T_MIN) + LOG_T_MIN) | |
| phi = np.arctan2(sp, cp) | |
| C = nC * (C_MAX - C_MIN) + C_MIN | |
| if t.ndim == 1: | |
| return np.array([A, T, phi, C], dtype=np.float64) | |
| return np.stack([A, T, phi, C], -1).astype(np.float64) | |
| class PositionalEncoding(nn.Module): | |
| def __init__(self, d_model, max_len=512): | |
| super().__init__() | |
| pe = torch.zeros(max_len, d_model) | |
| position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1) | |
| div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)) | |
| pe[:, 0::2] = torch.sin(position * div_term) | |
| pe[:, 1::2] = torch.cos(position * div_term) | |
| self.register_buffer('pe', pe) | |
| def forward(self, x): | |
| return x + self.pe[:x.size(1), :] | |
| class RabiEstimator(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| self.conv1 = nn.Sequential(nn.Conv1d(1, 64, kernel_size=7, stride=2, padding=3), nn.BatchNorm1d(64), nn.GELU()) | |
| self.conv2 = nn.Sequential(nn.Conv1d(64, 128, kernel_size=5, stride=2, padding=2), nn.BatchNorm1d(128), nn.GELU()) | |
| self.conv3 = nn.Sequential(nn.Conv1d(128, 256, kernel_size=5, stride=2, padding=2), nn.BatchNorm1d(256), nn.GELU()) | |
| self.d_model = 256 | |
| self.pos_enc = PositionalEncoding(self.d_model) | |
| encoder_layer = nn.TransformerEncoderLayer(d_model=self.d_model, nhead=8, dim_feedforward=512, dropout=0.1, batch_first=True) | |
| self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=6) | |
| self.fft_net = nn.Sequential(nn.Linear(128, 256), nn.GELU(), nn.Dropout(0.1), nn.Linear(256, 128), nn.GELU()) | |
| self.aux_net = nn.Sequential(nn.Linear(6, 64), nn.GELU(), nn.Linear(64, 128), nn.GELU()) | |
| self.fusion = nn.Sequential(nn.Linear(256 + 128 + 128, 512), nn.BatchNorm1d(512), nn.GELU(), nn.Dropout(0.1)) | |
| self.head_atc = nn.Sequential(nn.Linear(512, 256), nn.GELU(), nn.Linear(256, 3)) | |
| self.head_ph = nn.Sequential(nn.Linear(512, 256), nn.GELU(), nn.Linear(256, 2)) | |
| def forward(self, wave, fft_f, aux): | |
| x = wave.unsqueeze(1) | |
| x = self.conv3(self.conv2(self.conv1(x))) | |
| x = x.permute(0, 2, 1) | |
| x = self.pos_enc(x) | |
| x = self.transformer(x) | |
| x_pool = x.mean(dim=1) | |
| f = self.fft_net(fft_f) | |
| a = self.aux_net(aux) | |
| feat = self.fusion(torch.cat([x_pool, f, a], dim=1)) | |
| atc = torch.sigmoid(self.head_atc(feat)) | |
| ph = torch.tanh(self.head_ph(feat)) | |
| return torch.cat([atc[:, :2], ph, atc[:, 2:3]], 1) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Helper functions | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def rabi_formula(x, A, T, phi, C): | |
| """Compute Rabi oscillation: A * cos(2*pi/T * x + phi) + C""" | |
| return A * np.cos((2 * np.pi / T) * x + phi) + C | |
| def load_model(model_path): | |
| """Load trained RabiEstimator weights from disk.""" | |
| model = RabiEstimator().to(DEVICE) | |
| checkpoint = torch.load(model_path, map_location=DEVICE, weights_only=False) | |
| if isinstance(checkpoint, dict) and 'model_state_dict' in checkpoint: | |
| model.load_state_dict(checkpoint['model_state_dict']) | |
| else: | |
| model.load_state_dict(checkpoint) | |
| model.eval() | |
| return model | |
| def predict_ai_fit(x_raw, y_raw, model): | |
| """ | |
| Predict Rabi parameters using the AI model and return the absolute fit curve. | |
| Args: | |
| x_raw: np.array of x values (time/amplitude axis) | |
| y_raw: np.array of y values (signal) | |
| model: loaded RabiEstimator model | |
| Returns: | |
| y_fit: np.array of predicted fit values evaluated on x_raw | |
| params: dict with keys 'amplitude', 'T', 'phase', 'offset' | |
| """ | |
| x_raw = np.asarray(x_raw, dtype=np.float64) | |
| y_raw = np.asarray(y_raw, dtype=np.float64) | |
| # Ensure eval mode (BatchNorm1d requires it for batch_size=1) | |
| model.eval() | |
| # Sort by x for interpolation | |
| sort_idx = np.argsort(x_raw) | |
| x_sorted = x_raw[sort_idx] | |
| y_sorted = y_raw[sort_idx] | |
| yn, fft_feats, aux = preprocess_sample(x_sorted, y_sorted) | |
| wave_t = torch.tensor(yn, dtype=torch.float32).unsqueeze(0).to(DEVICE) | |
| fft_t = torch.tensor(fft_feats, dtype=torch.float32).unsqueeze(0).to(DEVICE) | |
| aux_t = torch.tensor(aux, dtype=torch.float32).unsqueeze(0).to(DEVICE) | |
| with torch.no_grad(): | |
| pred = model(wave_t, fft_t, aux_t) | |
| pred_np = pred.cpu().numpy()[0] | |
| A, T, phi, C = target_to_params(pred_np) | |
| # Evaluate fit on the original x values (unsorted order preserved) | |
| y_fit = rabi_formula(x_raw, float(A), float(T), float(phi), float(C)) | |
| return y_fit, { | |
| 'amplitude': float(A), | |
| 'T': float(T), | |
| 'phase': float(phi), | |
| 'offset': float(C), | |
| } | |