diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..12829c0 Binary files /dev/null and b/.gitignore differ diff --git a/BOplayground.py b/BOplayground.py index de917a4..9cbd842 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -1,45 +1,61 @@ -# Todo: -# add more BO experiments; -device = 'cuda' -DEVICE = 'cuda' +device = "cuda" +DEVICE = "cuda" +import os import random import numpy as np import torch +from torch.nn.utils import clip_grad_norm_ +from torch.distributions.normal import Normal +import sympy +import scipy + from code_construction.code_construction import CodeConstructor from bayesian_optimization.objective_function import ObjectiveFunction from bayesian_optimization.encoder import * from bayesian_optimization.chaincomplexembedding import * from bayesian_optimization.gp import * +from bayesian_optimization.bo import BO_on_QEC +from Normalizer import LogStdNormalizer, StdNormalizer + import gpytorch from gpytorch.mlls import ExactMarginalLogLikelihood -from gpytorch.distributions import MultivariateNormal from gpytorch.kernels import RBFKernel, MaternKernel, SpectralMixtureKernel, ScaleKernel import copy from typing import Dict, Optional, Tuple, Any, List - -import torch -import gpytorch -from gpytorch.mlls import ExactMarginalLogLikelihood -from torch.nn.utils import clip_grad_norm_ -import math -from dataclasses import dataclass, asdict -from typing import Tuple, Union, Dict, Any - -from torch.distributions.normal import Normal -from bayesian_optimization.bo import BO_on_QEC +from argparse import ArgumentParser class HillClimbing: - def __init__(self, next_points_num, gnp, acquisition, device="cuda", validator=None): + def __init__( + self, + next_points_num, + gnp, + acquisition, + device="cuda", + validator=None, + method="bb", + l=None, + target_row_weight=None, + ): self.next_points_num = int(next_points_num) self.gnp = gnp self.acquisition = acquisition self.device = device - self.validator = validator # 例如 lambda z: code_constructor.construct(z).k != 0 - - def hill_climbing_neighbors(self, x: torch.Tensor) -> torch.Tensor: - + self.validator = ( + validator # 例如 lambda z: code_constructor.construct(z).k != 0 + ) + self.l = l + self.target_row_weight = target_row_weight + match method: + case "gb": + self.mutate = self.mutate_gb + case "bb": + self.mutate = self.flip_neighbours + case _: + raise ValueError("The method is not supported.") + + def flip_neighbours(self, x: torch.Tensor) -> torch.Tensor: x = x.detach() d = x.numel() neigh_list = [] @@ -50,11 +66,87 @@ def hill_climbing_neighbors(self, x: torch.Tensor) -> torch.Tensor: neigh_list.append(n) return torch.stack(neigh_list, dim=0) # [d, d] + @staticmethod + def bin_list_to_int(bin_list): + return sum([int(bit) << i for i, bit in enumerate(bin_list)]) + + def mutate_gb(self, x: torch.Tensor) -> torch.Tensor: + # existing p(x) is divided by g(x) + # flipping one 0->1 and one 1->0 to keep density, same as: + # p(x) + x^i + x^j + # If this must be divisible by g(x), then so must be the delta (x^i + x^j) = x^i(1 + x^|j-i|) + # let d = |j-i| + x = x.detach() + + a = x[self.l : 2 * self.l] + a_len = len(a) + + b = x[2 * self.l : 3 * self.l] + b_len = len(b) + + int_fs = list( + map(HillClimbing.bin_list_to_int, x[3 * self.l :].view(-1, self.l).tolist()) + ) + int_gx_mask = HillClimbing.bin_list_to_int(x[: self.l].tolist()) + gx_bin = CodeConstructor.gx_mask_to_bin(int_gx_mask, int_fs, self.l) + + valid_dists = [] + + for d in range(1, self.l): + delta = 1 + (1 << d) + if CodeConstructor.poly_mod_f2(delta, gx_bin) == 0: + valid_dists.append(d) + + neigh_list = [] + + a_ones = (a == 1).nonzero().squeeze().tolist() + for one_pos in a_ones: + for d in valid_dists: + if a[(one_pos + d) % a_len].item() == 0: + new_a = a.clone() + new_a[one_pos] = 0 + new_a[(one_pos + d) % a_len] = 1 + new = x.clone() + new[self.l : 2 * self.l] = new_a + neigh_list.append(new) + if a[(one_pos - d) % a_len].item() == 0 and ((one_pos - d) % a_len) != ( + (one_pos + d) % a_len + ): + new_a = a.clone() + new_a[one_pos] = 0 + new_a[(one_pos - d) % a_len] = 1 + new = x.clone() + new[self.l : 2 * self.l] = new_a + neigh_list.append(new) + + b_ones = (b == 1).nonzero().squeeze().tolist() + for one_pos in b_ones: + for d in valid_dists: + if b[(one_pos + d) % b_len].item() == 0: + new_b = b.clone() + new_b[one_pos] = 0 + new_b[(one_pos + d) % b_len] = 1 + new = x.clone() + new[2 * self.l : 3 * self.l] = new_b + neigh_list.append(new) + if b[(one_pos - d) % b_len].item() == 0 and ((one_pos - d) % b_len) != ( + (one_pos + d) % b_len + ): + new_b = b.clone() + new_b[one_pos] = 0 + new_b[(one_pos - d) % b_len] = 1 + new = x.clone() + new[2 * self.l : 3 * self.l] = new_b + neigh_list.append(new) + + if not neigh_list: + return torch.empty((0, len(x)), dtype=x.dtype, device=x.device) + + return torch.stack(neigh_list, dim=0) + @torch.no_grad() def __call__(self, gp) -> torch.Tensor: - - # 起点:np -> torch.float32 on device - X0_np = self.gnp(self.next_points_num) # [n, d], 0/1 + X0_np = self.gnp(self.next_points_num) # [n, d], 0/1 X0 = torch.tensor(X0_np, dtype=torch.float32, device=self.device) best_list = [] @@ -63,8 +155,15 @@ def __call__(self, gp) -> torch.Tensor: best_val = self.acquisition(best_neighbor.unsqueeze(0), gp).reshape(-1)[0] while True: - nbrs = self.hill_climbing_neighbors(best_neighbor).to(self.device) + nbrs = self.mutate(best_neighbor).to(self.device) acq_vals = self.acquisition(nbrs, gp).reshape(-1) # [d] + if acq_vals.numel() == 0: + print( + "WARNING: no neigbours could be found for the following code:" + ) + print(best_neighbor.tolist()) + break + top_val, top_idx = torch.topk(acq_vals, k=1) top_val = top_val[0] top_neighbor = nbrs[top_idx[0]] @@ -79,22 +178,21 @@ def __call__(self, gp) -> torch.Tensor: cand = torch.stack(best_list, dim=0) # [n, d], float32, 0/1 - if self.validator is not None: cand_np = cand.detach().cpu().numpy().astype(np.int64) for i in range(cand_np.shape[0]): if not self.validator(cand_np[i]): - tries = 0 while not self.validator(cand_np[i]): tries += 1 - cand_np[i] = np.random.randint(0, 2, cand_np[i].shape, dtype=np.int64) + cand_np[i] = self.gnp(1) if tries > 1000: - break + break cand = torch.tensor(cand_np, dtype=torch.float32, device=self.device) return cand # [n, d], float32(0/1), on device + class EIAcquisitionFunction: """ Expected Improvement (EI) acquisition function for Bayesian Optimization. @@ -126,21 +224,37 @@ class EIAcquisitionFunction: Epsilon used in log(pl + eps) transform. """ - def __init__(self, pl_to_obj_fn, best_value=None, jitter=1e-2, eps=1e-9, - device="cpu", normalizer=None, prob_lower=1e-12, prob_upper=1.0 - 1e-12, - log_eps=1e-8): + def __init__( + self, + pl_to_obj_fn, + best_value=None, + jitter=1e-2, + eps=1e-9, + device="cpu", + normalizer=None, + prob_lower=1e-12, + prob_upper=1.0 - 1e-12, + log_eps=1e-8, + ): self.pl_to_obj_fn = pl_to_obj_fn - self.best_value = best_value - self.jitter = float(jitter) - self.eps = float(eps) - self.device = device - self.prob_lower = float(prob_lower) - self.prob_upper = float(prob_upper) - self.log_eps = float(log_eps) + self.best_value = best_value + self.jitter = float(jitter) + self.eps = float(eps) + self.device = device + self.prob_lower = float(prob_lower) + self.prob_upper = float(prob_upper) + self.log_eps = float(log_eps) # EI maintains its own normalizer; create one if not provided. - self.normalizer = normalizer if normalizer is not None else LogStdNormalizer( - eps=self.log_eps, device=self.device, min_prob=self.prob_lower, max_prob=self.prob_upper + self.normalizer = ( + normalizer + if normalizer is not None + else LogStdNormalizer( + eps=self.log_eps, + device=self.device, + min_prob=self.prob_lower, + max_prob=self.prob_upper, + ) ) # ---------- Interface exposed to BO loop ---------- @@ -169,9 +283,11 @@ def _take_mean_std(self, ret): return ret[0], ret[1] if isinstance(ret, dict): mu = ret.get("mean", ret.get("mu")) - sd = ret.get("std", ret.get("sigma")) + sd = ret.get("std", ret.get("sigma")) if mu is None or sd is None: - raise RuntimeError("pl_to_obj_fn dict must contain mean/std (or mu/sigma).") + raise RuntimeError( + "pl_to_obj_fn dict must contain mean/std (or mu/sigma)." + ) return mu, sd raise RuntimeError(f"Unsupported return type from pl_to_obj_fn: {type(ret)}") @@ -188,7 +304,9 @@ def __call__(self, X, gp: torch.nn.Module): EI(x) = (μ_f - f* - ξ) Φ(Z) + σ_f φ(Z), where Z = (μ_f - f* - ξ) / σ_f, Φ = CDF, φ = PDF of standard normal. """ - assert self.best_value is not None, "best_value not set; call set_best_value() first." + assert self.best_value is not None, ( + "best_value not set; call set_best_value() first." + ) # Ensure X is a proper tensor on the correct device if not torch.is_tensor(X): @@ -204,11 +322,11 @@ def __call__(self, X, gp: torch.nn.Module): # Posterior in latent z-domain with gpytorch.settings.fast_pred_var(): - if hasattr(gp, "posterior"): # BoTorch-style API + if hasattr(gp, "posterior"): # BoTorch-style API post = gp.posterior(X) mu_lat = post.mean.reshape(-1) var_lat = post.variance.reshape(-1) - else: # Pure GPyTorch model + else: # Pure GPyTorch model mvn = gp(X) mu_lat = mvn.mean.reshape(-1) var_lat = mvn.variance.reshape(-1) @@ -217,7 +335,9 @@ def __call__(self, X, gp: torch.nn.Module): # Convert (μ_z, σ_z) → probability domain if not self.normalizer.is_fitted(): - raise RuntimeError("EI normalizer is not fitted. Call update_normalizer(pl_train) first.") + raise RuntimeError( + "EI normalizer is not fitted. Call update_normalizer(pl_train) first." + ) mu_pl, std_pl = self.normalizer.inverse_mean_std(mu_lat, std_lat) # Clamp probability and std for stability @@ -230,8 +350,16 @@ def __call__(self, X, gp: torch.nn.Module): for i in range(mu_pl.numel()): out = self.pl_to_obj_fn(X[i], mu_pl[i], std_pl[i]) mu_i, sd_i = self._take_mean_std(out) - mu_obj[i] = mu_i if torch.is_tensor(mu_i) else torch.tensor(mu_i, device=self.device, dtype=torch.float32) - std_obj[i] = sd_i if torch.is_tensor(sd_i) else torch.tensor(sd_i, device=self.device, dtype=torch.float32) + mu_obj[i] = ( + mu_i + if torch.is_tensor(mu_i) + else torch.tensor(mu_i, device=self.device, dtype=torch.float32) + ) + std_obj[i] = ( + sd_i + if torch.is_tensor(sd_i) + else torch.tensor(sd_i, device=self.device, dtype=torch.float32) + ) std_obj = std_obj.clamp_min(self.eps) @@ -242,155 +370,6 @@ def __call__(self, X, gp: torch.nn.Module): ei = imp * normal.cdf(Z) + std_obj * torch.exp(normal.log_prob(Z)) return ei -TensorLike = Union[torch.Tensor, np.ndarray, float] - - -@dataclass -class LogStdStats: - """Stores the fitted statistics (mean, std, eps) for log-domain normalization.""" - mean: float # mean of log(pl + eps) - scale: float # std of log(pl + eps) - eps: float # epsilon used in log(pl + eps) - - -class LogStdNormalizer: - """ - A normalizer that transforms probabilities `pl ∈ (0,1)` into a standardized latent space via: - - z = (log(pl + eps) - mean) / scale - - """ - - def __init__(self, eps: float = 1e-8, min_prob: float = 1e-12, max_prob: float = 1.0 - 1e-12, device: str = "cpu"): - self.stats: LogStdStats = LogStdStats(mean=0.0, scale=1.0, eps=float(eps)) - self._fitted: bool = False - self.min_prob = float(min_prob) - self.max_prob = float(max_prob) - self.device = device - - # -------------------- Basic utilities -------------------- - def _to_tensor(self, x: TensorLike, dtype=torch.float32) -> torch.Tensor: - """Convert input (np.ndarray, float, or Tensor) to a torch.Tensor on the correct device.""" - if isinstance(x, torch.Tensor): - return x.to(self.device, dtype=dtype) - arr = np.asarray(x) - return torch.tensor(arr, device=self.device, dtype=dtype) - - def _clamp_prob(self, pl: torch.Tensor) -> torch.Tensor: - """Clamp probabilities to [min_prob, max_prob] for numerical stability.""" - return pl.clamp(self.min_prob, self.max_prob) - - # -------------------- Fitting and transforms -------------------- - @torch.no_grad() - def fit(self, pl: TensorLike) -> "LogStdNormalizer": - """ - Fit mean and scale parameters from all observed probabilities (in log-domain). - """ - pl_t = self._to_tensor(pl, dtype=torch.float32).view(-1) - pl_t = self._clamp_prob(pl_t) - ylog = torch.log(pl_t + self.stats.eps) - mean = ylog.mean().item() - std = ylog.std(unbiased=False).item() # population standard deviation - - # Avoid degenerate scaling (very small std can cause instability) - if std < 1e-12: - std = 1.0 - - self.stats = LogStdStats(mean=float(mean), scale=float(std), eps=self.stats.eps) - self._fitted = True - return self - - @torch.no_grad() - def transform(self, pl: TensorLike) -> torch.Tensor: - """ - Transform from probability domain → standardized latent domain: - z = (log(pl + eps) - mean) / scale - """ - assert self._fitted, "Call fit(pl_train) before transform." - pl_t = self._to_tensor(pl, dtype=torch.float32) - pl_t = self._clamp_prob(pl_t) - ylog = torch.log(pl_t + self.stats.eps) - z = (ylog - self.stats.mean) / self.stats.scale - return z - - @torch.no_grad() - def inverse_transform(self, z: TensorLike) -> torch.Tensor: - """ - Deterministic inverse transform: - Given z, return point estimate of pl (not posterior expectation). - pl = exp(z * scale + mean) - eps - """ - assert self._fitted, "Call fit(pl_train) before inverse_transform." - z_t = self._to_tensor(z, dtype=torch.float32) - logpl = z_t * self.stats.scale + self.stats.mean - pl = torch.exp(logpl) - self.stats.eps - return self._clamp_prob(pl) - - # -------------------- Posterior inverse transform (used in EI acquisition) -------------------- - @torch.no_grad() - def inverse_mean_std(self, mu_z: TensorLike, std_z: TensorLike) -> Tuple[torch.Tensor, torch.Tensor]: - """ - If z ~ N(mu_z, std_z^2), then log(pl + eps) ~ N(mu, std^2), where: - mu = mu_z * scale + mean - std = |scale| * std_z - - Using log-normal moment formulas: - E[pl + eps] = exp(mu + 0.5 * std^2) - Var(pl + eps) = (exp(std^2) - 1) * exp(2mu + std^2) - - Returns: - (mean_pl, std_pl): expected mean and std of pl in probability domain. - """ - assert self._fitted, "Call fit(pl_train) before inverse_mean_std." - mu_z_t = self._to_tensor(mu_z, dtype=torch.float32) - std_z_t = self._to_tensor(std_z, dtype=torch.float32).abs() - - mu = mu_z_t * self.stats.scale + self.stats.mean - std = std_z_t * abs(self.stats.scale) - - # E[pl + eps] and Var(pl + eps) - exp_half_var = torch.exp(0.5 * std**2) - mean_pl_plus = torch.exp(mu) * exp_half_var - var_pl_plus = (torch.exp(std**2) - 1.0) * torch.exp(2.0 * mu + std**2) - - mean_pl = mean_pl_plus - self.stats.eps - std_pl = var_pl_plus.clamp_min(1e-30).sqrt() - - # Clamp mean within valid probability range - mean_pl = self._clamp_prob(mean_pl) - return mean_pl, std_pl - - # -------------------- State handling and serialization -------------------- - def is_fitted(self) -> bool: - """Return whether the normalizer has been fitted.""" - return self._fitted - - def get_stats(self) -> LogStdStats: - """Return the current (mean, scale, eps) statistics.""" - return self.stats - - def set_eps(self, eps: float): - """Update epsilon used in log(pl + eps).""" - self.stats.eps = float(eps) - - def state_dict(self) -> Dict[str, Any]: - """Serialize current state to a Python dict.""" - return { - "stats": asdict(self.stats), - "fitted": self._fitted, - "min_prob": self.min_prob, - "max_prob": self.max_prob, - "device": self.device - } - - def load_state_dict(self, state: Dict[str, Any]): - """Restore normalizer state from a dict.""" - s = state["stats"] - self.stats = LogStdStats(mean=float(s["mean"]), scale=float(s["scale"]), eps=float(s["eps"])) - self._fitted = bool(state.get("fitted", True)) - self.min_prob = float(state.get("min_prob", self.min_prob)) - self.max_prob = float(state.get("max_prob", self.max_prob)) - self.device = state.get("device", self.device) class GPTrainer: """ @@ -408,28 +387,26 @@ def __init__( self, model: gpytorch.models.ExactGP, device: str = "cpu", - training_iter: int = 80, - lr: Optional[Dict[str, float]] = None, # {'embed':4e-4,'mean':1e-3,'kernel':1e-3,'like':2e-2} + lr: Optional[ + Dict[str, float] + ] = None, # {'embed':4e-4,'mean':1e-3,'kernel':1e-3,'like':2e-2} weight_decay: float = 1e-4, max_grad_norm: float = 2.0, optimizer_type: str = "adamw", recreate_optimizer_each_round: bool = True, - - scheduler_cfg: Optional[Dict[str, Any]] = None, # {'factor':0.5,'patience':5,'min_lr':1e-6} - early_stopping: Optional[Dict[str, Any]] = None,# {'patience':10,'tol':1e-4} - + scheduler_cfg: Optional[ + Dict[str, Any] + ] = None, # {'factor':0.5,'patience':5,'min_lr':1e-6} + early_stopping: Optional[Dict[str, Any]] = None, # {'patience':10,'tol':1e-4} use_priors: bool = True, priors_cfg: Optional[Dict[str, Dict[str, float]]] = None, - noise_floor: float = 1e-3, # lower bound (in z-domain) for likelihood noise + noise_floor: float = 1e-3, # lower bound (in z-domain) for likelihood noise lengthscale_bounds: Optional[Tuple[float, float]] = None, - warm_start: bool = True, - rescale_on_scaler_change: bool = True, # alpha = old_std / new_std + rescale_on_scaler_change: bool = True, # alpha = old_std / new_std carry_optimizer_state: bool = False, - - freeze_cfg: Optional[Dict[str, Any]] = None, # {'lengthscale_until_round': 2} - + freeze_cfg: Optional[Dict[str, Any]] = None, # {'lengthscale_until_round': 2} verbose: bool = True, log_every: Optional[int] = None, save_best_state: bool = True, @@ -443,14 +420,18 @@ def __init__( self.optimizer_type = optimizer_type.lower() self.recreate_optimizer_each_round = bool(recreate_optimizer_each_round) - self.scheduler_cfg = scheduler_cfg or {'factor': 0.5, 'patience': 5, 'min_lr': 1e-6} - self.early_stopping = early_stopping or {'patience': 10, 'tol': 1e-4} + self.scheduler_cfg = scheduler_cfg or { + "factor": 0.5, + "patience": 5, + "min_lr": 1e-6, + } + self.early_stopping = early_stopping or {"patience": 10, "tol": 1e-4} self.use_priors = bool(use_priors) self.priors_cfg = priors_cfg or { - 'lengthscale': {'type': 'lognormal', 'loc': 0.0, 'scale': 0.5}, - 'outputscale': {'type': 'lognormal', 'loc': 0.0, 'scale': 0.5}, - 'noise': {'type': 'lognormal', 'loc': -4.0, 'scale': 0.5}, + "lengthscale": {"type": "lognormal", "loc": 0.0, "scale": 0.5}, + "outputscale": {"type": "lognormal", "loc": 0.0, "scale": 0.5}, + "noise": {"type": "lognormal", "loc": -4.0, "scale": 0.5}, } self.noise_floor = float(noise_floor) self.lengthscale_bounds = lengthscale_bounds @@ -459,12 +440,12 @@ def __init__( self.rescale_on_scaler_change = bool(rescale_on_scaler_change) self.carry_optimizer_state = bool(carry_optimizer_state) - self.freeze_cfg = freeze_cfg or {'lengthscale_until_round': 0} + self.freeze_cfg = freeze_cfg or {"lengthscale_until_round": 0} self.verbose = bool(verbose) self.log_every = log_every self.save_best_state = bool(save_best_state) - self.lr = lr or {'embed': 4e-4, 'mean': 1e-3, 'kernel': 1e-3, 'like': 2e-2} + self.lr = lr or {"embed": 4e-4, "mean": 1e-3, "kernel": 1e-3, "like": 2e-2} # ---- State ---- self._optimizer: Optional[torch.optim.Optimizer] = None @@ -502,15 +483,17 @@ def on_normalizer_change(self, old_std: Optional[float], new_std: Optional[float return alpha = float(old_std / new_std) with torch.no_grad(): - if hasattr(self.model, "covar_module") and hasattr(self.model.covar_module, "outputscale"): - self.model.covar_module.outputscale.mul_(alpha ** 2) + if hasattr(self.model, "covar_module") and hasattr( + self.model.covar_module, "outputscale" + ): + self.model.covar_module.outputscale.mul_(alpha**2) try: - self.model.likelihood.noise.mul_(alpha ** 2) + self.model.likelihood.noise.mul_(alpha**2) except Exception: # Fallback: attempt to read/set noise through likelihood noise_covar try: noise = self._get_noise_value() - self._set_noise_value(noise * (alpha ** 2)) + self._set_noise_value(noise * (alpha**2)) except Exception: pass self._last_scaler_std = new_std @@ -520,9 +503,11 @@ def maybe_freeze_unfreeze(self, round_idx: int): Optionally freeze base kernel lengthscale parameters for early rounds. """ self._round_idx = int(round_idx) - until = int(self.freeze_cfg.get('lengthscale_until_round', 0)) - freeze = (round_idx <= until) - base_kernel = getattr(getattr(self.model, "covar_module", None), "base_kernel", None) + until = int(self.freeze_cfg.get("lengthscale_until_round", 0)) + freeze = round_idx <= until + base_kernel = getattr( + getattr(self.model, "covar_module", None), "base_kernel", None + ) if base_kernel is not None: for p in base_kernel.parameters(): p.requires_grad_(not freeze) @@ -537,9 +522,14 @@ def train_one_round(self) -> Dict[str, Any]: - best-state checkpointing (optional). """ model = self.model - model.train(); model.likelihood.train() - - if self._optimizer is None or self.recreate_optimizer_each_round or not self.carry_optimizer_state: + model.train() + model.likelihood.train() + + if ( + self._optimizer is None + or self.recreate_optimizer_each_round + or not self.carry_optimizer_state + ): self._optimizer = self._build_optimizer() self._scheduler = self._build_scheduler(self._optimizer) elif self._scheduler is None: @@ -550,8 +540,8 @@ def train_one_round(self) -> Dict[str, Any]: best_loss = float("inf") best_state = None no_improve = 0 - patience = int(self.early_stopping.get('patience', 10)) - tol = float(self.early_stopping.get('tol', 1e-4)) + patience = int(self.early_stopping.get("patience", 10)) + tol = float(self.early_stopping.get("tol", 1e-4)) self._history = [] train_x, train_y = model.train_inputs[0], model.train_targets @@ -579,21 +569,29 @@ def train_one_round(self) -> Dict[str, Any]: if self.save_best_state: best_state = { "model": copy.deepcopy(model.state_dict()), - "likelihood": copy.deepcopy(model.likelihood.state_dict()) + "likelihood": copy.deepcopy(model.likelihood.state_dict()), } else: no_improve += 1 - if self.verbose and (self.log_every is None or it % max(1, self.log_every) == 0 or it == self.training_iter): + if self.verbose and ( + self.log_every is None + or it % max(1, self.log_every) == 0 + or it == self.training_iter + ): ls_val = self._safe_get_lengthscale() - out_v = self._safe_get_outputscale() - nz_v = self._safe_get_noise() - print(f"[round {self._round_idx:>3d} | {it:>4d}/{self.training_iter}] " - f"nll={cur:.4f} len={ls_val} out={out_v:.3e} noise={nz_v:.3e}") + out_v = self._safe_get_outputscale() + nz_v = self._safe_get_noise() + print( + f"[round {self._round_idx:>3d} | {it:>4d}/{self.training_iter}] " + f"nll={cur:.4f} len={ls_val} out={out_v:.3e} noise={nz_v:.3e}" + ) if no_improve >= patience and rel_impr < tol: if self.verbose: - print(f"[round {self._round_idx:>3d}] early stop @ {it}, best nll={best_loss:.4f}") + print( + f"[round {self._round_idx:>3d}] early stop @ {it}, best nll={best_loss:.4f}" + ) break if self.save_best_state and best_state is not None: @@ -615,18 +613,40 @@ def train_one_round(self) -> Dict[str, Any]: # ================= Internal Utilities ================= def _build_optimizer(self) -> torch.optim.Optimizer: # Group parameters by module for separate learning rates. - params_embed = list(getattr(self.model, "embed", torch.nn.Module()).parameters()) - params_kernel = list(getattr(self.model, "covar_module", torch.nn.Module()).parameters()) - params_like = list(self.model.likelihood.parameters()) - params_mean = list(getattr(self.model, "mean_module", torch.nn.Module()).parameters()) + params_embed = list( + getattr(self.model, "embed", torch.nn.Module()).parameters() + ) + params_kernel = list( + getattr(self.model, "covar_module", torch.nn.Module()).parameters() + ) + params_like = list(self.model.likelihood.parameters()) + params_mean = list( + getattr(self.model, "mean_module", torch.nn.Module()).parameters() + ) lr = self.lr # learning rate groups = [ - {'params': params_embed, 'lr': lr.get('embed', 4e-4), 'weight_decay': self.weight_decay}, - {'params': params_mean, 'lr': lr.get('mean', 1e-3), 'weight_decay': self.weight_decay}, - {'params': params_kernel, 'lr': lr.get('kernel', 1e-3), 'weight_decay': self.weight_decay}, - {'params': params_like, 'lr': lr.get('like', 2e-2), 'weight_decay': self.weight_decay/5}, + { + "params": params_embed, + "lr": lr.get("embed", 4e-4), + "weight_decay": self.weight_decay, + }, + { + "params": params_mean, + "lr": lr.get("mean", 1e-3), + "weight_decay": self.weight_decay, + }, + { + "params": params_kernel, + "lr": lr.get("kernel", 1e-3), + "weight_decay": self.weight_decay, + }, + { + "params": params_like, + "lr": lr.get("like", 2e-2), + "weight_decay": self.weight_decay / 5, + }, ] if self.optimizer_type == "adam": return torch.optim.Adam(groups) @@ -635,35 +655,43 @@ def _build_optimizer(self) -> torch.optim.Optimizer: def _build_scheduler(self, optimizer) -> torch.optim.lr_scheduler.ReduceLROnPlateau: cfg = self.scheduler_cfg return torch.optim.lr_scheduler.ReduceLROnPlateau( - optimizer, mode='min', - factor=float(cfg.get('factor', 0.5)), - patience=int(cfg.get('patience', 5)), - min_lr=float(cfg.get('min_lr', 1e-6)) + optimizer, + mode="min", + factor=float(cfg.get("factor", 0.5)), + patience=int(cfg.get("patience", 5)), + min_lr=float(cfg.get("min_lr", 1e-6)), ) def _register_priors_safely(self): """Attach priors to the **constrained** parameter names (lengthscale/outputscale/noise).""" from gpytorch.priors import LogNormalPrior + # lengthscale prior try: bk = self.model.covar_module.base_kernel - loc = float(self.priors_cfg['lengthscale']['loc']) - scale = float(self.priors_cfg['lengthscale']['scale']) - bk.register_prior("lengthscale_prior", LogNormalPrior(loc, scale), "lengthscale") + loc = float(self.priors_cfg["lengthscale"]["loc"]) + scale = float(self.priors_cfg["lengthscale"]["scale"]) + bk.register_prior( + "lengthscale_prior", LogNormalPrior(loc, scale), "lengthscale" + ) except Exception: pass # outputscale prior try: - loc = float(self.priors_cfg['outputscale']['loc']) - scale = float(self.priors_cfg['outputscale']['scale']) - self.model.covar_module.register_prior("outputscale_prior", LogNormalPrior(loc, scale), "outputscale") + loc = float(self.priors_cfg["outputscale"]["loc"]) + scale = float(self.priors_cfg["outputscale"]["scale"]) + self.model.covar_module.register_prior( + "outputscale_prior", LogNormalPrior(loc, scale), "outputscale" + ) except Exception: pass # noise prior try: - loc = float(self.priors_cfg['noise']['loc']) - scale = float(self.priors_cfg['noise']['scale']) - self.model.likelihood.register_prior("noise_prior", LogNormalPrior(loc, scale), "noise") + loc = float(self.priors_cfg["noise"]["loc"]) + scale = float(self.priors_cfg["noise"]["scale"]) + self.model.likelihood.register_prior( + "noise_prior", LogNormalPrior(loc, scale), "noise" + ) except Exception: pass @@ -671,7 +699,10 @@ def _register_constraints_safely(self): # Noise lower bound (constraint on raw_noise) try: self.model.likelihood.noise_covar.register_constraint( - "raw_noise", gpytorch.constraints.GreaterThan(torch.tensor(self.noise_floor, device=self.device)) + "raw_noise", + gpytorch.constraints.GreaterThan( + torch.tensor(self.noise_floor, device=self.device) + ), ) except Exception: pass @@ -704,7 +735,9 @@ def _get_noise_value(self) -> float: return float(self.model.likelihood.noise.detach().cpu().item()) except Exception: try: - return float(self.model.likelihood.noise_covar.noise.detach().cpu().item()) + return float( + self.model.likelihood.noise_covar.noise.detach().cpu().item() + ) except Exception: return float("nan") @@ -714,46 +747,66 @@ def _set_noise_value(self, v: float): self.model.likelihood.noise.copy_(torch.tensor(v, device=self.device)) except Exception: try: - self.model.likelihood.noise_covar.noise.copy_(torch.tensor(v, device=self.device)) + self.model.likelihood.noise_covar.noise.copy_( + torch.tensor(v, device=self.device) + ) except Exception: pass def _safe_get_noise(self) -> float: return self._get_noise_value() -class E(): - def __init__(self,code_constructor,views_info ): + +class E: + def __init__(self, code_constructor, views_info): self.code_constructor = code_constructor - self.encoder = CSSEncoder(views_info,mode ='relations') + self.encoder = CSSEncoder(views_info, mode="relations") - def encode_single(self,x): - return self.encoder.encode(self.code_constructor.construct(np.array(x).astype(int))) - def encode(self,x): + def encode_single(self, x): + return self.encoder.encode( + self.code_constructor.construct(np.array(x).astype(int)) + ) + + def encode(self, x): # x: B views return [self.encode_single(i) for i in x] + + views_info = [ - {"name":"decode", - "partite_classes":["SZ","DQ","SX"], - "relations":["SZ_DQ","DQ_SZ","DQ_SX","SX_DQ"], - "weight_mode":"count", "log1p":True}, - {"name":"xlogic", - "partite_classes":["DQ","SX","LX"], - "relations":["DQ_SX","SX_DQ","LX_DQ","DQ_LX"], - "weight_mode":"count", "log1p":True}, - {"name":"zlogic", - "partite_classes":["SZ","DQ","LZ"], - "relations":["SZ_DQ","DQ_SZ","LZ_DQ","DQ_LZ"], - "weight_mode":"count", "log1p":True}, + { + "name": "decode", + "partite_classes": ["SZ", "DQ", "SX"], + "relations": ["SZ_DQ", "DQ_SZ", "DQ_SX", "SX_DQ"], + "weight_mode": "count", + "log1p": True, + }, + { + "name": "xlogic", + "partite_classes": ["DQ", "SX", "LX"], + "relations": ["DQ_SX", "SX_DQ", "LX_DQ", "DQ_LX"], + "weight_mode": "count", + "log1p": True, + }, + { + "name": "zlogic", + "partite_classes": ["SZ", "DQ", "LZ"], + "relations": ["SZ_DQ", "DQ_SZ", "LZ_DQ", "DQ_LZ"], + "weight_mode": "count", + "log1p": True, + }, ] -def get_model_(X, y, kernel_type='ard_rbf', mean_type='linear', mean_input=64,embed_dim=128): - encoder = E(code_constructor,views_info) +def get_model_( + X, y, kernel_type="ard_rbf", mean_type="linear", mean_input=64, embed_dim=128 +): + + encoder = E(code_constructor, views_info) # NN embedder embedding = ChainComplexEmbedder( views_info=views_info, - d_model=embed_dim , + d_model=embed_dim, num_layers=4, view_aggr="sum", num_bases=4, @@ -763,19 +816,18 @@ def get_model_(X, y, kernel_type='ard_rbf', mean_type='linear', mean_input=64,em dropout=0.1, ).to(device) - - # kernel - if kernel_type == 'ard_rbf': + if kernel_type == "ard_rbf": base = RBFKernel(ard_num_dims=embed_dim) kernel = ScaleKernel(base) - elif kernel_type == 'matern': + elif kernel_type == "matern": base = MaternKernel(nu=1.5, ard_num_dims=embed_dim) kernel = ScaleKernel(base) - elif kernel_type == 'spectral_mixture': + elif kernel_type == "spectral_mixture": kernel = SpectralMixtureKernel(num_mixtures=4, ard_num_dims=embed_dim) - elif kernel_type == 'rbf_plus_periodic': + elif kernel_type == "rbf_plus_periodic": from gpytorch.kernels import PeriodicKernel + rbf = ScaleKernel(RBFKernel(ard_num_dims=embed_dim)) periodic = ScaleKernel(PeriodicKernel(ard_num_dims=embed_dim)) kernel = rbf + periodic @@ -790,7 +842,8 @@ def get_model_(X, y, kernel_type='ard_rbf', mean_type='linear', mean_input=64,em train_y = y.to(device).float().view(-1) gp = GaussianProcess_QEC( - train_x, train_y, + train_x, + train_y, likelihood=likelihood, kernel=kernel, encoder=encoder.encode, @@ -800,211 +853,495 @@ def get_model_(X, y, kernel_type='ard_rbf', mean_type='linear', mean_input=64,em ).to(device) return gp + + def set_all_seeds(seed: int = 42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed(seed) - torch.cuda.manual_seed_all(seed) + torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False -class Get_new_points_function(): - def __init__(self,method='qc-ldpc-hgp',code_constructor = None,encode='None'): + + +class Get_new_points_function: + def __init__( + self, + method="qc-ldpc-hgp", + code_constructor=None, + encode="None", + density=None, + desired_k=None, + gx_mask=None, + ): self.method = method self.code_constructor = code_constructor self.encode = encode + self.density = density self.init = False + self.desired_k = desired_k + self.gx_mask = gx_mask + self.factors = None - def get_new_points_function(self,number): - if self.method == 'qc-ldpc-hgp': + def get_new_points_function(self, number): + if self.method == "qc-ldpc-hgp": new_points = self.get_new_points_HGP(number) - elif self.method == 'bb': + elif self.method == "gb": + new_points = self.get_new_gb_vector(number) + elif self.method == "bb": new_points = self.get_new_bb_vector(number) return new_points - - def get_new_points_HGP(self,number): - return np.random.randint(0, self.hyperparameters['m'] + 1, (number, self.hyperparameters['p'] * self.hyperparameters['q'])) - def get_new_bb_vector(self,number): + def get_new_points_HGP(self, number): + return np.random.randint( + 0, + self.hyperparameters["m"] + 1, + (number, self.hyperparameters["p"] * self.hyperparameters["q"]), + ) + + def _get_irreducible_factors(self, l) -> list[int]: + x = sympy.Symbol("x") + poly = x**l - 1 + _, factors_counts = sympy.factor_list(poly, domain=sympy.GF(2)) + + int_factors = [] + for factor, count in factors_counts: + coeffs = sympy.Poly(factor, x).all_coeffs() + binary_coeffs = [int(c) % 2 for c in coeffs][::-1] + + # convert binary array to int + factor_int = sum([bit * (1 << i) for i, bit in enumerate(binary_coeffs)]) + + for _ in range(count): + int_factors.append(factor_int) + + return int_factors + + def _get_possible_gx_bitmasks(self, factors): + if self.desired_k is None: + raise ValueError( + "A desired value of k is required for GB codes if g(x) bitmask not provided." + ) + + target_degree = self.desired_k // 2 + no_factors = len(factors) + + degrees = [f.bit_length() - 1 for f in factors] + valid_bitmasks = [] + + for i in range(1, 1 << no_factors): # iterate through all possible bitmasks + degree = 0 + for j in range(no_factors): + if (i >> j) & 1: + degree += degrees[j] + if degree == target_degree: + valid_bitmasks.append(i) + + return valid_bitmasks + + def set_gx_mask( + self, + ): # gx_mask is input as an int and later converted to a binary array + l = self.code_constructor.para_dict["l"] + self.factors = self._get_irreducible_factors(l) + if self.gx_mask is not None: + if not (self.gx_mask > 0 and self.gx_mask < (1 << len(self.factors))): + raise ValueError( + f"Provided g(x) bitmask {self.gx_mask} is not valid: must be int in range [1, {(1 << len(self.factors)) - 1}] for l={l}" + ) + else: + possible_gx_masks = self._get_possible_gx_bitmasks(self.factors) + self.gx_mask = random.choice(possible_gx_masks) + print(f"Randomly selected g(x) bitmask = {self.gx_mask}") + + def _generate_candidate_poly(self, max_index) -> int: + indices = np.random.choice(max_index, self.density, replace=False).tolist() + + res = 0 + for idx in indices: + res |= 1 << idx + + return res + + @staticmethod + def int_to_bin_list(bin_int): + return [int(bit) for bit in bin(bin_int)[2:]][ + ::-1 + ] # least -> most significant bit + + def get_new_gb_vector(self, number): + results = [] + l = self.code_constructor.para_dict["l"] + + gx_bin = CodeConstructor.gx_mask_to_bin(self.gx_mask, self.factors, l) + max_bound = l - (gx_bin.bit_length() - 1) + + while number > 0: + cand_a = self._generate_candidate_poly(max_bound) + if ( + CodeConstructor.poly_mod_f2(cand_a, gx_bin) == 0 + ): # gx_bin divides cand_a + found_b = False + while not found_b: + cand_b = self._generate_candidate_poly(max_bound) + if CodeConstructor.poly_mod_f2(cand_b, gx_bin) == 0: + found_b = True + + padded_arr = np.zeros((3 + len(self.factors), l), dtype=np.uint8) + + parameters = [ + Get_new_points_function.int_to_bin_list(self.gx_mask), + Get_new_points_function.int_to_bin_list(cand_a), + Get_new_points_function.int_to_bin_list(cand_b), + ] + list(map(Get_new_points_function.int_to_bin_list, self.factors)) + + for i, param in enumerate(parameters): + padded_arr[i, : len(param)] = param # padded so each param len l + + results.append(padded_arr.ravel()) + number -= 1 + + return np.array(results) + + def get_new_bb_vector(self, number): results = [] - l = self.code_constructor.para_dict['l'] - g = self.code_constructor.para_dict['g'] - if self.init == False and l==12 and g==999: - print('best known bb code added to initial points') - self.init=True - - a = np.zeros((l+g-1)*2) - a[3]=1 - a[11+1]=1 - a[11+2]=1 - a[17+1]=1 - a[17+2]=1 - a[17+11+3]=1 + l = self.code_constructor.para_dict["l"] + g = self.code_constructor.para_dict["g"] + if self.init == False and l == 12 and g == 999: + print("best known bb code added to initial points") + self.init = True + + a = np.zeros((l + g - 1) * 2) + a[3] = 1 + a[11 + 1] = 1 + a[11 + 2] = 1 + a[17 + 1] = 1 + a[17 + 2] = 1 + a[17 + 11 + 3] = 1 results.append(a) - while number>0: - new_point = np.random.randint(0,2, size=(l+g-1)*2) + while number > 0: + new_point = np.random.randint(0, 2, size=(l + g - 1) * 2) c = self.code_constructor.construct(new_point) - if c.k==0: + if c.k == 0: continue else: results.append(new_point) number -= 1 return np.array(results) -if __name__ == '__main__': + + +def get_args(): + parser = ArgumentParser(description="Search for QECCs in a certain family using BO") + + parser.add_argument( + "-c", "--code-class", default="bb", help="code class to search with BO" + ) + parser.add_argument( + "--distance-exact", + action="store_true", + help="use exact code distance rather than LER to evaluate candidate codes", + ) + parser.add_argument( + "--distance-timeout", + type=int, + help=f"Distance-exact only: number of seconds before exact distance calculation times out. Default {60 * 20}s (20 minutes)", + ) + parser.add_argument( + "--distance-heuristic", + nargs="+", + help="use a specified distance heuristic and parameters used to evaluate candidate codes", + ) + parser.add_argument("-s", "--seed", default=42, type=int, help="seed") + parser.add_argument( + "-d", + "--dataset-index", + default=0, + type=int, + help="index of the dataset of starting codes", + ) + parser.add_argument( + "--lam", + default=1.0, + type=float, + help="lambda parameter of the objective function", + ) + parser.add_argument( + "--polynomial-size", + type=int, + nargs="+", + help="BB and GB codes only: sizes of polynomials a(x) and b(x)", + ) + parser.add_argument( + "--density", + type=int, + help="GB codes only: density of polynomials used to construct parity check matrices for some codes", + ) + parser.add_argument( + "--desired-k", type=int, help="GB codes only: desired number of logical qubits" + ) + parser.add_argument( + "--gx-mask", + type=int, + help="GB codes only: bitmask of irreducible factors of (x^l - 1) that make up g(x)", + ) + + args = parser.parse_args() + + if args.distance_exact and args.distance_heuristic: + raise ValueError("Cannot use both --distance-exact and --distance-heuristic") + + if args.distance_timeout and not args.distance_exact: + raise ValueError(f"--distance-timeout must be used alongside --distance-exact") + + if args.polynomial_size and args.code_class not in ["bb", "gb"]: + raise ValueError( + f"--polynomial-size can only be used with bb or gb codes, not {args.code_class}" + ) + + if args.density and args.code_class != "gb": + raise ValueError( + f"--density can only be used with gb codes, not {args.code_class}" + ) + + if args.desired_k and args.code_class != "gb": + raise ValueError( + f"--desired-k can only be used with gb codes, not {args.code_class}" + ) + + if args.gx_mask and args.code_class != "gb": + raise ValueError( + f"--gx-mask can only be used with gb codes, not {args.code_class}" + ) + + return args + + +if __name__ == "__main__": import pickle - import sys - - if len(sys.argv) == 3: - seed= int(sys.argv[1]) - dataset_index = int(sys.argv[2]) - lambda_ = 1 - elif len(sys.argv) >3: - seed= int(sys.argv[1]) - dataset_index = int(sys.argv[2]) - lambda_ = float(sys.argv[3]) + + args = get_args() + + if args.distance_exact or args.distance_heuristic: + code_eval_metric = "distance" else: - seed = 42 - dataset_index = 0 - lambda_ = 1 - - set_all_seeds(seed) - l = 12 - g = 6 # g here is m in Bravyi et al's paper - print(f'(l,g)=({l},{g}), dataset_index = {dataset_index}, seed={seed}, lambda = {lambda_}') - - - para_dict = {'l':l,'g':g} - code_class = 'bb' - - - code_constructor = CodeConstructor(method=code_class,para_dict = para_dict) + code_eval_metric = "LER" + + dist_params = {} + dist_method = None + + if args.distance_heuristic: + dist_method = args.distance_heuristic[0] + + if args.distance_heuristic[1:]: + for arg in args.distance_heuristic[1:]: + k, v = arg.split("=") + try: + v = float(v) + except ValueError: + pass + dist_params[k] = v + + if not args.polynomial_size: + l, g = 12, 6 # default value + else: + l = args.polynomial_size[0] + if len(args.polynomial_size) > 1: + g = args.polynomial_size[1] + else: + g = None + + set_all_seeds(args.seed) + + print( + f"(l,g)=({l},{g}), dataset_index = {args.dataset_index}, seed={args.seed}, lambda = {args.lam}" + ) + + para_dict = {"l": l, "g": g} + + code_constructor = CodeConstructor(method=args.code_class, para_dict=para_dict) # define objective function - pp=0.05 - Obj_Func = ObjectiveFunction(code_constructor,lambda_ = lambda_, pp=pp,decoder_param={'trail':10_000}) + pp = 0.05 + + Obj_Func = ObjectiveFunction( + code_constructor, + lambda_=args.lam, + pp=pp, + decoder_param={"trail": 10_000}, + code_eval_metric=code_eval_metric, + dist_method=dist_method, # reccommend QDistEvol for BB codes + dist_params=dist_params, + dist_seed=args.seed, + dist_timeout=args.distance_timeout, + ) obj_func = Obj_Func.forward pl_to_obj = Obj_Func.pl_to_obj_with_std # method of sampling new points - gnp = Get_new_points_function(method=code_class,code_constructor=code_constructor).get_new_points_function - # initial points: - # init_num = 20 - # X_init = gnp(init_num) - # y_init = [] - # pl_init = [] - # for x in X_init: - # y,pl = obj_func(x) - # y_init.append(y) - # pl_init.append(pl) - if l ==6 and g==3: - init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}_63.pkl" + gnp_obj = Get_new_points_function( + method=args.code_class, + code_constructor=code_constructor, + density=args.density, + desired_k=args.desired_k, + gx_mask=args.gx_mask, + ) + gnp = gnp_obj.get_new_points_function + + if code_eval_metric == "distance": + distance_path_flag = "_d" else: - init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}.pkl" - # file with 63 suffix has (l,m)=(6,3). Otherwise (l,m)=(12,6) + distance_path_flag = "" + + if args.code_class == "gb": + gnp_obj.set_gx_mask() + init_data_file = f"./data/BO_initial_points/GB_BO_initial_points{distance_path_flag}_{args.dataset_index}_{args.lam}_{args.density}_{l}_{gnp_obj.gx_mask}.pkl" + else: # bb + if l == 6 and g == 3: + if args.lam == 1: + init_data_file = f"./data/BO_initial_points/BO_initial_points{distance_path_flag}_{args.dataset_index}_1.0_63.pkl" + else: + init_data_file = f"./data/BO_initial_points/BO_initial_points{distance_path_flag}_{args.dataset_index}_{args.lam}_63.pkl" + + else: + if args.lam == 1: + init_data_file = f"./data/BO_initial_points/BO_initial_points{distance_path_flag}_{args.dataset_index}_1.0.pkl" + else: + init_data_file = f"./data/BO_initial_points/BO_initial_points{distance_path_flag}_{args.dataset_index}_{args.lam}.pkl" + # file with 63 suffix has (l,m)=(6,3). Otherwise (l,m)=(12,6) + + if not os.path.exists(init_data_file): + # no starting codes exist, so generate some: + init_num = 20 + print( + f"Generating and evaluating {init_num} initial {args.code_class} codes..." + ) + X_init = gnp(init_num) + y_init = [] + pl_init = [] + for i, x in enumerate(X_init): + y, pl = obj_func(x) + y_init.append(y) + pl_init.append(pl) + print(f"Evaluated initial code {i + 1}/{init_num} (score={y:.4f})") + + with open(init_data_file, "wb") as f: + pickle.dump({"X": X_init, "y": y_init, "pl": pl_init}, f) + print(f"Saved initial points to {init_data_file}") + + import time + + start_time = time.perf_counter() + + print(f"Loading initial codes from {init_data_file}") with open(init_data_file, "rb") as f: data = pickle.load(f) - X_init = data['X'] - y_init = data['y'] - pl_init = data['pl'] - - X_init = torch.tensor(X_init,dtype=torch.float32) - X_init.to(DEVICE) - y_init = torch.tensor(y_init,dtype=torch.float32) - y_init.to(DEVICE) - pl_init = torch.tensor(pl_init,dtype=torch.float32) - pl_init.to(DEVICE) + X_init = data["X"] + y_init = data["y"] + pl_init = data["pl"] + + X_init = torch.tensor(X_init, dtype=torch.float32).to(DEVICE) + y_init = torch.tensor(y_init, dtype=torch.float32).to(DEVICE) + pl_init = torch.tensor(pl_init, dtype=torch.float32).to(DEVICE) # get gp model model = get_model_( X_init, pl_init, - kernel_type='matern', - mean_type='linear', + kernel_type="matern", + mean_type="linear", mean_input=128, ) # gp trainer trainer = GPTrainer( model=model, device=DEVICE, - # --- Training and regularization --- training_iter=80, - lr={'embed': 4e-4, 'mean': 1e-3, 'kernel': 1e-3, 'like': 2e-2}, + lr={"embed": 4e-4, "mean": 1e-3, "kernel": 1e-3, "like": 2e-2}, weight_decay=1e-4, max_grad_norm=2.0, - optimizer_type='adamw', + optimizer_type="adamw", recreate_optimizer_each_round=True, - # --- Scheduler and early stopping --- - scheduler_cfg={'factor': 0.5, 'patience': 5, 'min_lr': 1e-6}, - early_stopping={'patience': 10, 'tol': 1e-4}, - + scheduler_cfg={"factor": 0.5, "patience": 5, "min_lr": 1e-6}, + early_stopping={"patience": 10, "tol": 1e-4}, # --- Priors and constraints (helpful for small datasets) --- use_priors=True, - noise_floor=1e-3, # Lower bound for z-domain noise to avoid overfitting - lengthscale_bounds=None, # If X is normalized to [0,1], one may use (1e-2, 10.) - + noise_floor=1e-3, # Lower bound for z-domain noise to avoid overfitting + lengthscale_bounds=None, # If X is normalized to [0,1], one may use (1e-2, 10.) # --- Warm start and scaler rescaling --- warm_start=True, - rescale_on_scaler_change=True, # Recommended: True + rescale_on_scaler_change=True, # Recommended: True carry_optimizer_state=False, - # --- Freeze base kernel lengthscale in early rounds (stabilizes small-data regime) --- - freeze_cfg={'lengthscale_until_round': 2}, - + freeze_cfg={"lengthscale_until_round": 2}, verbose=True, log_every=8, save_best_state=True, ) + + if code_eval_metric == "distance": + normalizer = StdNormalizer(device=DEVICE) + else: + normalizer = None + # acquisition function: acq = EIAcquisitionFunction( - pl_to_obj_fn = pl_to_obj, - best_value = None, - jitter = 1e-2, - eps = 1e-9, - device = DEVICE, - normalizer = None, - prob_lower = 1e-12, - prob_upper = 1.0 - 1e-12, - log_eps = 1e-8 + pl_to_obj_fn=pl_to_obj, + best_value=None, + jitter=1e-2, + eps=1e-9, + device=DEVICE, + normalizer=normalizer, + prob_lower=1e-12, + prob_upper=1.0 - 1e-12, + log_eps=1e-8, ) + # hill climbing: - def bb_validator(candidate_np): + def code_validator(candidate_np): return code_constructor.construct(candidate_np).k != 0 + next_points_num = 4 hc = HillClimbing( - next_points_num = next_points_num, # the candidate number - gnp = gnp, # get new points function - acquisition = acq, - device = DEVICE, - validator = bb_validator + next_points_num=next_points_num, # the candidate number + gnp=gnp, # get new points function + acquisition=acq, + device=DEVICE, + validator=code_validator, + method=args.code_class, + l=l, + target_row_weight=args.density, ) + # assemble BO bo_iterations = 50 bo = BO_on_QEC( - gp = model, - gp_trainer = trainer, - acquisition_function = acq, - suggest_next = hc, - objective_function = obj_func, - initial_X = X_init, - initial_pl = pl_init, - initial_y = y_init, - BO_iterations = bo_iterations, - description = 'BB-BO (GP+EI+HC)', - device = DEVICE, - pretrain = True + gp=model, + gp_trainer=trainer, + acquisition_function=acq, + suggest_next=hc, + objective_function=obj_func, + initial_X=X_init, + initial_pl=pl_init, + initial_y=y_init, + BO_iterations=bo_iterations, + description=f"{args.code_class.upper()}-BO (GP+EI+HC)", + device=DEVICE, + pretrain=True, + code_eval_metric=code_eval_metric, ) - best_x,best_y,evaluation_history = bo.run() + best_x, best_y, evaluation_history = bo.run() + end_time = time.perf_counter() + print(f"Time taken: {end_time - start_time:.2f} seconds") # The best-so-far results of bo (including initial points) flat = [v for row in evaluation_history for v in row] y_init_list = [i.item() for i in y_init] flat = y_init_list + flat - with open(f'./data/BO_results/BO_{l}_{g}_{dataset_index}_{seed}_{lambda_}.pkl','wb') as f: - results = { - 'best_x':best_x, - 'best_y':best_y, - 'evaluation_history':flat - } + results_file = init_data_file.replace("BO_initial_points", "BO_results") + + with open(results_file, "wb") as f: + results = {"best_x": best_x, "best_y": best_y, "evaluation_history": flat} pickle.dump(results, f) diff --git a/EAplayground.py b/EAplayground.py index c67b1f6..2e68a26 100644 --- a/EAplayground.py +++ b/EAplayground.py @@ -1,11 +1,8 @@ # Evolutionary algorim from code_construction.code_construction import CodeConstructor -import numpy as np from evolutionary_algorithm.ea import BivariateBicycleCodeEvolutionaryOptimization -from pymoo.operators.crossover.sbx import SBX from pymoo.operators.mutation.pm import PM from pymoo.operators.repair.rounding import RoundingRepair -from pymoo.operators.sampling.rnd import IntegerRandomSampling from pymoo.algorithms.soo.nonconvex.ga import GA from pymoo.operators.crossover.ux import UniformCrossover from pymoo.optimize import minimize @@ -18,12 +15,14 @@ # print(undetectable_error_rate.evaluate(p=0.01)) from pymoo.core.sampling import Sampling import sys + + if len(sys.argv) == 3: - seed= int(sys.argv[1]) - dataset_index = int(sys.argv[2]) - lambda_ = 1 -elif len(sys.argv) >3: - seed= int(sys.argv[1]) + seed = int(sys.argv[1]) + dataset_index = int(sys.argv[2]) + lambda_ = 1 +elif len(sys.argv) > 3: + seed = int(sys.argv[1]) dataset_index = int(sys.argv[2]) lambda_ = float(sys.argv[3]) else: @@ -31,9 +30,12 @@ dataset_index = 0 lambda_ = 1 -l=12 -g=6 -print(f'(l,g)=({l},{g}), dataset_index = {dataset_index}, seed={seed}, lambda = {lambda_}') +l = 12 +g = 6 +print( + f"(l,g)=({l},{g}), dataset_index = {dataset_index}, seed={seed}, lambda = {lambda_}" +) + class MySampling(Sampling): def __init__(self, init_samples): @@ -42,38 +44,47 @@ def __init__(self, init_samples): def _do(self, problem, n_samples, **kwargs): return self.init_samples -para_dict = {'l':l,'g':g} -code_class = 'bb' -code_constructor = CodeConstructor(method=code_class,para_dict = para_dict) +para_dict = {"l": l, "g": g} +code_class = "bb" + + +code_constructor = CodeConstructor(method=code_class, para_dict=para_dict) # define objective function -pp=0.05 -Obj_Func = ObjectiveFunction(code_constructor,lambda_=lambda_, pp=pp,decoder_param={'trail':10_000}) +pp = 0.05 +Obj_Func = ObjectiveFunction( + code_constructor, lambda_=lambda_, pp=pp, decoder_param={"trail": 10_000} +) obj_func = Obj_Func.forward -if l ==6 and g==3: - init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}_63.pkl" +if l == 6 and g == 3: + init_data_file = ( + f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}_63.pkl" + ) else: - init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}.pkl" + init_data_file = ( + f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}.pkl" + ) # file with 63 suffix has (l,m)=(6,3). Otherwise (l,m)=(12,6) with open(init_data_file, "rb") as f: data = pickle.load(f) - X_init = data['X'] - y_init = data['y'] - pl_init = data['pl'] -problem = BivariateBicycleCodeEvolutionaryOptimization(l=l,m=g,obj_func=Obj_Func) + X_init = data["X"] + y_init = data["y"] + pl_init = data["pl"] +problem = BivariateBicycleCodeEvolutionaryOptimization(l=l, m=g, obj_func=Obj_Func) algorithm = GA( pop_size=20, sampling=MySampling(X_init), crossover=UniformCrossover(prob=1.0), mutation=PM(prob=1.0, eta=3.0, vtype=float, repair=RoundingRepair()), - eliminate_duplicates=True + eliminate_duplicates=True, ) -res = minimize(problem, - algorithm, - termination=('n_gen', 11), - seed=seed, +res = minimize( + problem, + algorithm, + termination=("n_gen", 11), + seed=seed, ) flat2 = [-v for row in problem.evaluation_history for v in row] @@ -82,10 +93,12 @@ def _do(self, problem, n_samples, **kwargs): print(problem.best_parameters) print("Best solution found: \nX = %s\nF = %s" % (res.X, -res.F)) -with open(f'./data/BO_results/EA_{l}_{g}_{dataset_index}_{seed}_{lambda_}.pkl','wb') as f: +with open( + f"./data/BO_results/EA_{l}_{g}_{dataset_index}_{seed}_{lambda_}.pkl", "wb" +) as f: results = { - 'best_x':problem.best_parameters, - 'best_y':problem.best_result, - 'evaluation_history':flat2, + "best_x": problem.best_parameters, + "best_y": problem.best_result, + "evaluation_history": flat2, } pickle.dump(results, f) diff --git a/Normalizer.py b/Normalizer.py new file mode 100644 index 0000000..530ba01 --- /dev/null +++ b/Normalizer.py @@ -0,0 +1,276 @@ +from __future__ import annotations +from abc import ABC, abstractmethod +from dataclasses import dataclass, asdict + +import numpy as np +import torch + +from typing import Dict, Tuple, Any, Union + + +@dataclass +class StdStats: + mean: float + scale: float + + +@dataclass +class LogStdStats(StdStats): + eps: float + + +TensorLike = Union[torch.Tensor, np.ndarray, float] + + +class Normalizer(ABC): + def __init__( + self, + device: str = "cpu", + ): + self._fitted: bool = False + self.device = device + + def _to_tensor(self, x: TensorLike, dtype=torch.float32) -> torch.Tensor: + """Convert input (np.ndarray, float, or Tensor) to a torch.Tensor on the correct device.""" + if isinstance(x, torch.Tensor): + return x.to(self.device, dtype=dtype) + arr = np.asarray(x) + return torch.tensor(arr, device=self.device, dtype=dtype) + + @abstractmethod + def fit(self, y: TensorLike) -> Normalizer: + pass + + @abstractmethod + def transform(self, y: TensorLike) -> torch.Tensor: + pass + + @abstractmethod + def inverse_transform(self, z: TensorLike) -> torch.Tensor: + pass + + @abstractmethod + def inverse_mean_std( + self, mu_z: TensorLike, std_z: TensorLike + ) -> Tuple[torch.Tensor, torch.Tensor]: + pass + + def is_fitted(self) -> bool: + """Return whether the normalizer has been fitted.""" + return self._fitted + + def get_stats(self) -> LogStdStats: + """Return the current (mean, scale, eps) statistics.""" + return self.stats + + def state_dict(self) -> Dict[str, Any]: + """Serialize current state to a Python dict.""" + pass + + def load_state_dict(self, state: Dict[str, Any]): + """Restore normalizer state from a dict.""" + pass + + +class LogStdNormalizer(Normalizer): + """ + A normalizer that transforms probabilities `pl ∈ (0,1)` into a standardized latent space via: + + z = (log(pl + eps) - mean) / scale + + """ + + def __init__( + self, + eps: float = 1e-8, + min_prob: float = 1e-12, + max_prob: float = 1.0 - 1e-12, + device: str = "cpu", + ): + super().__init__(device) + self.stats: LogStdStats = LogStdStats(mean=0.0, scale=1.0, eps=float(eps)) + self.min_prob = float(min_prob) + self.max_prob = float(max_prob) + + # -------------------- Basic utilities -------------------- + + def _clamp_prob(self, pl: torch.Tensor) -> torch.Tensor: + """Clamp probabilities to [min_prob, max_prob] for numerical stability.""" + return pl.clamp(self.min_prob, self.max_prob) + + # -------------------- Fitting and transforms -------------------- + @torch.no_grad() + def fit(self, y: TensorLike) -> LogStdNormalizer: + """ + Fit mean and scale parameters from all observed probabilities (in log-domain). + """ + pl_t = self._to_tensor(y, dtype=torch.float32).view(-1) + pl_t = self._clamp_prob(pl_t) + ylog = torch.log(pl_t + self.stats.eps) + mean = ylog.mean().item() + std = ylog.std(unbiased=False).item() # population standard deviation + + # Avoid degenerate scaling (very small std can cause instability) + if std < 1e-12: + std = 1.0 + + self.stats = LogStdStats(mean=float(mean), scale=float(std), eps=self.stats.eps) + self._fitted = True + return self + + @torch.no_grad() + def transform(self, y: TensorLike) -> torch.Tensor: + """ + Transform from probability domain → standardized latent domain: + z = (log(pl + eps) - mean) / scale + """ + assert self._fitted, "Call fit(pl_train) before transform." + pl_t = self._to_tensor(y, dtype=torch.float32) + pl_t = self._clamp_prob(pl_t) + ylog = torch.log(pl_t + self.stats.eps) + z = (ylog - self.stats.mean) / self.stats.scale + return z + + @torch.no_grad() + def inverse_transform(self, z: TensorLike) -> torch.Tensor: + """ + Deterministic inverse transform: + Given z, return point estimate of pl (not posterior expectation). + pl = exp(z * scale + mean) - eps + """ + assert self._fitted, "Call fit(pl_train) before inverse_transform." + z_t = self._to_tensor(z, dtype=torch.float32) + logpl = z_t * self.stats.scale + self.stats.mean + pl = torch.exp(logpl) - self.stats.eps + return self._clamp_prob(pl) + + # -------------------- Posterior inverse transform (used in EI acquisition) -------------------- + @torch.no_grad() + def inverse_mean_std( + self, mu_z: TensorLike, std_z: TensorLike + ) -> Tuple[torch.Tensor, torch.Tensor]: + """ + If z ~ N(mu_z, std_z^2), then log(pl + eps) ~ N(mu, std^2), where: + mu = mu_z * scale + mean + std = |scale| * std_z + + Using log-normal moment formulas: + E[pl + eps] = exp(mu + 0.5 * std^2) + Var(pl + eps) = (exp(std^2) - 1) * exp(2mu + std^2) + + Returns: + (mean_pl, std_pl): expected mean and std of pl in probability domain. + """ + assert self._fitted, "Call fit(pl_train) before inverse_mean_std." + mu_z_t = self._to_tensor(mu_z, dtype=torch.float32) + std_z_t = self._to_tensor(std_z, dtype=torch.float32).abs() + + mu = mu_z_t * self.stats.scale + self.stats.mean + std = std_z_t * abs(self.stats.scale) + + # E[pl + eps] and Var(pl + eps) + exp_half_var = torch.exp(0.5 * std**2) + mean_pl_plus = torch.exp(mu) * exp_half_var + var_pl_plus = (torch.exp(std**2) - 1.0) * torch.exp(2.0 * mu + std**2) + + mean_pl = mean_pl_plus - self.stats.eps + std_pl = var_pl_plus.clamp_min(1e-30).sqrt() + + # Clamp mean within valid probability range + mean_pl = self._clamp_prob(mean_pl) + return mean_pl, std_pl + + # -------------------- State handling and serialization -------------------- + + def set_eps(self, eps: float): + """Update epsilon used in log(pl + eps).""" + self.stats.eps = float(eps) + + def state_dict(self) -> Dict[str, Any]: + return { + "stats": asdict(self.stats), + "fitted": self._fitted, + "min_prob": self.min_prob, + "max_prob": self.max_prob, + "device": self.device, + } + + def load_state_dict(self, state: Dict[str, Any]): + s = state["stats"] + self.stats = LogStdStats( + mean=float(s["mean"]), scale=float(s["scale"]), eps=float(s["eps"]) + ) + self._fitted = bool(state.get("fitted", True)) + self.min_prob = float(state.get("min_prob", self.min_prob)) + self.max_prob = float(state.get("max_prob", self.max_prob)) + self.device = state.get("device", self.device) + + +class StdNormalizer(Normalizer): + """ + A normalizer for distance (d) which standardizes values to + z = (d - mean) / std_dev + + As error rate (pl) scales exponentially with d, LogStdNormalizer is used for pl + and linear standardization is used for d. + """ + + def __init__(self, device): + super().__init__(device) + self.stats: StdStats = StdStats(mean=0.0, scale=1.0) + + @torch.no_grad() + def fit(self, y: TensorLike) -> StdNormalizer: + d_t = self._to_tensor(y, dtype=torch.float32).view(-1) + mean = d_t.mean().item() + std = d_t.std(unbiased=False).item() + + if std < 1e-12: + std = 1.0 + + self.stats = StdStats(mean=float(mean), scale=float(std)) + self._fitted = True + return self + + @torch.no_grad() + def transform(self, y: TensorLike) -> torch.Tensor: + assert self._fitted, "Call fit(d_train) before transform." + + d_t = self._to_tensor(y, dtype=torch.float32) + z = (d_t - self.stats.mean) / self.stats.scale + return z + + @torch.no_grad() + def inverse_transform(self, z: TensorLike) -> torch.Tensor: + assert self._fitted, "Call fit(d_train) before inverse_transform." + + z_t = self._to_tensor(z, dtype=torch.float32) + d = z_t * self.stats.scale + self.stats.mean + return d + + @torch.no_grad() + def inverse_mean_std( + self, mu_z: TensorLike, std_z: TensorLike + ) -> Tuple[torch.Tensor, torch.Tensor]: + assert self._fitted, "Call fit(d_train) before inverse_mean_std." + + mu_z_t = self._to_tensor(mu_z, dtype=torch.float32) + std_z_t = self._to_tensor(std_z, dtype=torch.float32).abs() + + mu = mu_z_t * self.stats.scale + self.stats.mean + std = std_z_t * abs(self.stats.scale) + + return mu, std + + def state_dict(self) -> Dict[str, Any]: + return { + "stats": asdict(self.stats), + "fitted": self._fitted, + "device": self.device, + } + + def load_state_dict(self, state: Dict[str, Any]): + s = state["stats"] + self.stats = StdStats(mean=float(s["mean"]), scale=float(s["scale"])) + self._fitted = bool(state.get("fitted", True)) + self.device = state.get("device", self.device) diff --git a/RSplayground.py b/RSplayground.py index ea8f44c..6041211 100644 --- a/RSplayground.py +++ b/RSplayground.py @@ -4,68 +4,76 @@ from code_construction.code_construction import CodeConstructor from bayesian_optimization.objective_function import ObjectiveFunction import sys + + def set_all_seeds(seed: int = 42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed(seed) - torch.cuda.manual_seed_all(seed) + torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False -class Get_new_points_function(): - def __init__(self,method='qc-ldpc-hgp',code_constructor = None,encode='None'): + + +class Get_new_points_function: + def __init__(self, method="qc-ldpc-hgp", code_constructor=None, encode="None"): self.method = method self.code_constructor = code_constructor self.encode = encode self.init = False - def get_new_points_function(self,number): - if self.method == 'qc-ldpc-hgp': + def get_new_points_function(self, number): + if self.method == "qc-ldpc-hgp": new_points = self.get_new_points_HGP(number) - elif self.method == 'bb': + elif self.method == "bb": new_points = self.get_new_bb_vector(number) return new_points - - def get_new_points_HGP(self,number): - return np.random.randint(0, code_constructor.para_dict['m'] + 1, (number, code_constructor.para_dict['p'] * code_constructor.para_dict['q'])) - def get_new_bb_vector(self,number): + def get_new_points_HGP(self, number): + return np.random.randint( + 0, + code_constructor.para_dict["m"] + 1, + (number, code_constructor.para_dict["p"] * code_constructor.para_dict["q"]), + ) + + def get_new_bb_vector(self, number): results = [] - l = self.code_constructor.para_dict['l'] - g = self.code_constructor.para_dict['g'] - if self.init == False and l==12 and g==999: - print('best known bb code added to initial points') - self.init=True - - a = np.zeros((l+g-1)*2) - a[3]=1 - a[11+1]=1 - a[11+2]=1 - a[17+1]=1 - a[17+2]=1 - a[17+11+3]=1 + l = self.code_constructor.para_dict["l"] + g = self.code_constructor.para_dict["g"] + if self.init == False and l == 12 and g == 999: + print("best known bb code added to initial points") + self.init = True + + a = np.zeros((l + g - 1) * 2) + a[3] = 1 + a[11 + 1] = 1 + a[11 + 2] = 1 + a[17 + 1] = 1 + a[17 + 2] = 1 + a[17 + 11 + 3] = 1 results.append(a) - while number>0: - new_point = np.random.randint(0,2, size=(l+g-1)*2) + while number > 0: + new_point = np.random.randint(0, 2, size=(l + g - 1) * 2) c = self.code_constructor.construct(new_point) - if c.k==0: + if c.k == 0: continue else: results.append(new_point) number -= 1 return np.array(results) - -if __name__ == '__main__': + + +if __name__ == "__main__": import pickle - - + if len(sys.argv) == 3: - seed= int(sys.argv[1]) + seed = int(sys.argv[1]) dataset_index = int(sys.argv[2]) lambda_ = 1 - elif len(sys.argv) >3: - seed= int(sys.argv[1]) + elif len(sys.argv) > 3: + seed = int(sys.argv[1]) dataset_index = int(sys.argv[2]) lambda_ = float(sys.argv[3]) else: @@ -74,54 +82,58 @@ def get_new_bb_vector(self,number): lambda_ = 1 set_all_seeds(seed) l = 12 - g = 6 # g here is m in Bravyi et al's paper - print(f'(l,g)=({l},{g}), dataset_index = {dataset_index}, seed={seed}, lambda = {lambda_}') - - para_dict = {'l':l,'g':g} - code_class = 'bb' - - if l ==6 and g==3: + g = 6 # g here is m in Bravyi et al's paper + print( + f"(l,g)=({l},{g}), dataset_index = {dataset_index}, seed={seed}, lambda = {lambda_}" + ) + + para_dict = {"l": l, "g": g} + code_class = "bb" + + if l == 6 and g == 3: init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}_63.pkl" else: - init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}.pkl" + init_data_file = ( + f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}.pkl" + ) # file with 63 suffix has (l,m)=(6,3). Otherwise (l,m)=(12,6) with open(init_data_file, "rb") as f: data = pickle.load(f) - X_init = data['X'] - y_init = data['y'] - pl_init = data['pl'] + X_init = data["X"] + y_init = data["y"] + pl_init = data["pl"] - - code_constructor = CodeConstructor(method=code_class,para_dict = para_dict) + code_constructor = CodeConstructor(method=code_class, para_dict=para_dict) # define objective function - pp=0.05 - Obj_Func = ObjectiveFunction(code_constructor,lambda_ = lambda_ ,pp=pp,decoder_param={'trail':10_000}) + pp = 0.05 + Obj_Func = ObjectiveFunction( + code_constructor, lambda_=lambda_, pp=pp, decoder_param={"trail": 10_000} + ) obj_func = Obj_Func.forward pl_to_obj = Obj_Func.pl_to_obj_with_std # method of sampling new points - gnp = Get_new_points_function(method=code_class,code_constructor=code_constructor).get_new_points_function - X_random = gnp(4*50) + gnp = Get_new_points_function( + method=code_class, code_constructor=code_constructor + ).get_new_points_function + X_random = gnp(4 * 50) flat3 = [] best_x = None best_y = -999 x_history = [] pl_history = [] - for i in range(4*50): - F,pL = obj_func(X_random[i]) - print(f'The {i}th point, obj_func:{F}') + for i in range(4 * 50): + F, pL = obj_func(X_random[i]) + print(f"The {i}th point, obj_func:{F}") flat3.append(F) x_history.append(X_random[i]) pl_history.append(pL) - if F>=best_y: + if F >= best_y: best_x = X_random[i] best_y = F - flat3 = y_init + flat3 - with open(f'./data/BO_results/RS_{l}_{g}_{dataset_index}_{seed}_{lambda_}.pkl','wb') as f: - results = { - 'best_x':best_x, - 'best_y':best_y, - 'evaluation_history':flat3 - } + with open( + f"./data/BO_results/RS_{l}_{g}_{dataset_index}_{seed}_{lambda_}.pkl", "wb" + ) as f: + results = {"best_x": best_x, "best_y": best_y, "evaluation_history": flat3} pickle.dump(results, f) diff --git a/__pycache__/dataset.cpython-310.pyc b/__pycache__/dataset.cpython-310.pyc deleted file mode 100644 index bc12cb7..0000000 Binary files a/__pycache__/dataset.cpython-310.pyc and /dev/null differ diff --git a/bayesian_optimization/__pycache__/chaincomplexembedding.cpython-310.pyc b/bayesian_optimization/__pycache__/chaincomplexembedding.cpython-310.pyc deleted file mode 100644 index 6c604d1..0000000 Binary files a/bayesian_optimization/__pycache__/chaincomplexembedding.cpython-310.pyc and /dev/null differ diff --git a/bayesian_optimization/__pycache__/encoder.cpython-310.pyc b/bayesian_optimization/__pycache__/encoder.cpython-310.pyc deleted file mode 100644 index 520d61c..0000000 Binary files a/bayesian_optimization/__pycache__/encoder.cpython-310.pyc and /dev/null differ diff --git a/bayesian_optimization/__pycache__/gadget.cpython-310.pyc b/bayesian_optimization/__pycache__/gadget.cpython-310.pyc deleted file mode 100644 index df0aefb..0000000 Binary files a/bayesian_optimization/__pycache__/gadget.cpython-310.pyc and /dev/null differ diff --git a/bayesian_optimization/bo.py b/bayesian_optimization/bo.py index 9112037..30bca2c 100644 --- a/bayesian_optimization/bo.py +++ b/bayesian_optimization/bo.py @@ -10,7 +10,6 @@ """ import time -import numpy as np import torch import tqdm import gpytorch @@ -79,19 +78,23 @@ class BO_on_QEC: If True, fit normalizer with initial pl, sync trainer, and perform one warmup training round before BO starts. """ - def __init__(self, - gp, - gp_trainer, # GPTrainer instance - acquisition_function, # EI with internal normalizer - suggest_next, - objective_function, # (x_single_np) -> (F, pL_total) - initial_X: torch.Tensor, - initial_pl: torch.Tensor, # probability space (pl) - initial_y: torch.Tensor, # objective space (F) - BO_iterations=10, - description='', - device="cpu", - pretrain=True): + + def __init__( + self, + gp, + gp_trainer, # GPTrainer instance + acquisition_function, # EI with internal normalizer + suggest_next, + objective_function, # (x_single_np) -> (F, pL_total) + initial_X: torch.Tensor, + initial_pl: torch.Tensor, # probability space (pl) + initial_y: torch.Tensor, # objective space (F) + BO_iterations=10, + description="", + device="cpu", + pretrain=True, + code_eval_metric="LER", + ): self.gp = gp self.trainer = gp_trainer @@ -99,17 +102,22 @@ def __init__(self, self.acquisition_function = acquisition_function self.suggest_next = suggest_next self.device = device + self.code_eval_metric = code_eval_metric - self.X = initial_X.to(self.device, dtype=torch.float32) - self.pl = initial_pl.to(self.device, dtype=torch.float32) # probability space labels - self.y = initial_y.to(self.device, dtype=torch.float32) # objective values F + self.X = initial_X.to(self.device, dtype=torch.float32) + # if code_eval_metric is distance, pl is true distance rather than true pl (logical error rate) + # all similarly named pl variables will also refer to distance rather than LER (TODO fix varibale names) + self.pl = initial_pl.to( + self.device, dtype=torch.float32 + ) # probability space labels + self.y = initial_y.to(self.device, dtype=torch.float32) # objective values F self.BO_iterations = int(BO_iterations) self.description = description self.best_value = self.y.max() self.best_parameters = self.X[self.y.argmax()].clone() - print(f'Initial best value: {self.best_value.item():.4f}') + print(f"Initial best value: {self.best_value.item():.4f}") # Online diagnostic log: per-iteration list of records (dicts) self.pred_vs_true_history = [] @@ -178,9 +186,20 @@ def run(self): pbar = tqdm.tqdm(range(self.BO_iterations), desc=self.description) # TSV for online diagnostics (pred vs. true); easy to inspect externally. - metrics_tsv = open('pred_vs_true.tsv', 'a', encoding='utf-8') + if self.code_eval_metric == "LER": + eval_name = "pl" + elif self.code_eval_metric == "distance": + eval_name = "d" + else: + raise ValueError( + f"Code evaluation metric '{self.code_eval_metric}' is not supported." + ) + + metrics_tsv = open("pred_vs_true.tsv", "a", encoding="utf-8") if metrics_tsv.tell() == 0: - metrics_tsv.write("round\tidx\tmu_pl\tstd_pl\tpl_true\tr_pl\tz_true\tmu_z\tstd_z\tr_z\tcover68_pl\tcover95_pl\n") + metrics_tsv.write( + f"round\tidx\tmu_{eval_name}\tstd_{eval_name}\t{eval_name}_true\tr_{eval_name}\tz_true\tmu_z\tstd_z\tr_z\tcover68_{eval_name}\tcover95_{eval_name}\n" + ) for rnd in pbar: # --- Step 0: refresh EI's incumbent best objective --- @@ -198,20 +217,32 @@ def run(self): if not self.acquisition_function.normalizer.is_fitted(): # Should not happen in practice (pretraining/previous rounds fit it). self.acquisition_function.update_normalizer(self.pl) - mu_pl_pred, std_pl_pred = self.acquisition_function.normalizer.inverse_mean_std(mu_z_pred, std_z_pred) - mu_pl_pred = mu_pl_pred.clamp(1e-12, 1.0 - 1e-12) + mu_pl_pred, std_pl_pred = ( + self.acquisition_function.normalizer.inverse_mean_std( + mu_z_pred, std_z_pred + ) + ) + if self.code_eval_metric == "LER": + mu_pl_pred = mu_pl_pred.clamp(1e-12, 1.0 - 1e-12) + else: # code_eval_metric == "distance" + mu_pl_pred = mu_pl_pred.clamp_min(1e-12) + std_pl_pred = std_pl_pred.clamp_min(1e-12) # --- Step 2: single-sample evaluations (objective & true pl) --- y_list, pl_list = [], [] - next_points_np = next_points.detach().round().clamp(0, 1).cpu().numpy().astype("int64") + next_points_np = next_points.detach().round().cpu().numpy().astype("int64") for i in range(next_points_np.shape[0]): y_i, pl_i = self.objective_function(next_points_np[i]) y_list.append(float(y_i) if torch.is_tensor(y_i) else y_i) pl_list.append(float(pl_i) if torch.is_tensor(pl_i) else pl_i) - next_values = torch.tensor(y_list, dtype=torch.float32, device=self.device) # objective F - next_pl = torch.tensor(pl_list, dtype=torch.float32, device=self.device) # true pl + next_values = torch.tensor( + y_list, dtype=torch.float32, device=self.device + ) # objective F + next_pl = torch.tensor( + pl_list, dtype=torch.float32, device=self.device + ) # true pl t_evaluated = time.time() evaluation_history.append(next_values.detach().cpu().tolist()) @@ -219,47 +250,55 @@ def run(self): # --- Step 2b: record online diagnostics (pred vs. true) --- iter_metrics = [] # Map true pl to z-space using the same normalizer. - z_true_all = self.acquisition_function.normalizer.transform(next_pl).detach() + z_true_all = self.acquisition_function.normalizer.transform( + next_pl + ).detach() for i in range(next_pl.numel()): - mu_z = mu_z_pred[i]; sz = std_z_pred[i].clamp_min(1e-12) - mu_pl = mu_pl_pred[i]; sp = std_pl_pred[i].clamp_min(1e-12) + mu_z = mu_z_pred[i] + sz = std_z_pred[i].clamp_min(1e-12) + mu_pl = mu_pl_pred[i] + sp = std_pl_pred[i].clamp_min(1e-12) pl_t = next_pl[i] z_t = z_true_all[i] - r_pl = float(((pl_t - mu_pl) / sp).item()) # standardized residual in pl-space - r_z = float(((z_t - mu_z) / sz).item()) # standardized residual in z-space + r_pl = float( + ((pl_t - mu_pl) / sp).item() + ) # standardized residual in pl-space + r_z = float( + ((z_t - mu_z) / sz).item() + ) # standardized residual in z-space cover68 = abs(r_pl) <= 1.0 cover95 = abs(r_pl) <= 1.96 m_rec = { "round": int(rnd), "idx": int(i), - "mu_pl": float(mu_pl.item()), - "std_pl": float(sp.item()), - "pl_true": float(pl_t.item()), - "r_pl": r_pl, + f"mu_{eval_name}": float(mu_pl.item()), + f"std_{eval_name}": float(sp.item()), + f"{eval_name}_true": float(pl_t.item()), + f"r_{eval_name}": r_pl, "z_true": float(z_t.item()), "mu_z": float(mu_z.item()), "std_z": float(sz.item()), "r_z": r_z, - "cover68_pl": bool(cover68), - "cover95_pl": bool(cover95), + f"cover68_{eval_name}": bool(cover68), + f"cover95_{eval_name}": bool(cover95), } iter_metrics.append(m_rec) # Append a TSV line metrics_tsv.write( - f"{m_rec['round']}\t{m_rec['idx']}\t{m_rec['mu_pl']:.6e}\t{m_rec['std_pl']:.6e}\t" - f"{m_rec['pl_true']:.6e}\t{m_rec['r_pl']:.3f}\t{m_rec['z_true']:.6e}\t" + f"{m_rec['round']}\t{m_rec['idx']}\t{m_rec[f'mu_{eval_name}']:.6e}\t{m_rec[f'std_{eval_name}']:.6e}\t" + f"{m_rec[f'{eval_name}_true']:.6e}\t{m_rec[f'r_{eval_name}']:.3f}\t{m_rec['z_true']:.6e}\t" f"{m_rec['mu_z']:.6e}\t{m_rec['std_z']:.6e}\t{m_rec['r_z']:.3f}\t" - f"{int(m_rec['cover68_pl'])}\t{int(m_rec['cover95_pl'])}\n" + f"{int(m_rec[f'cover68_{eval_name}'])}\t{int(m_rec[f'cover95_{eval_name}'])}\n" ) metrics_tsv.flush() self.pred_vs_true_history.append(iter_metrics) # --- Step 3: expand dataset with new points --- - self.X = torch.cat((self.X, next_points), dim=0) - self.y = torch.cat((self.y, next_values), dim=0) - self.pl = torch.cat((self.pl, next_pl), dim=0) + self.X = torch.cat((self.X, next_points), dim=0) + self.y = torch.cat((self.y, next_values), dim=0) + self.pl = torch.cat((self.pl, next_pl), dim=0) # --- Step 3b: refit normalizer on latest pl and warm-train GP --- t1 = time.time() @@ -289,18 +328,20 @@ def run(self): if nb.item() > self.best_value.item(): self.best_value = nb self.best_parameters = np_best.clone() - best_y_paras.append([ - self.best_value.item(), - self.best_parameters.detach().cpu().numpy().tolist() - ]) + best_y_paras.append( + [ + self.best_value.item(), + self.best_parameters.detach().cpu().numpy().tolist(), + ] + ) print(f"new code updated: {[best_y_paras[-1][0], best_y_paras[-1][1]]}") # --- Step 5: progress bar & logging --- pbar.set_description( - f'{self.description} (Best value: {self.best_value.item():.4f}), ' - f'time: suggest {t_next - t0:.3f}, eval {t_evaluated - t_next:.3f}, train {t_train - t1:.3f}' + f"{self.description} (Best value: {self.best_value.item():.4f}), " + f"time: suggest {t_next - t0:.3f}, eval {t_evaluated - t_next:.3f}, train {t_train - t1:.3f}" ) - with open('result.txt', 'a', encoding='utf-8') as f: + with open("result.txt", "a", encoding="utf-8") as f: f.write( f"{self.description} (Best value: {self.best_value.item():.6f}), " f"time: suggesting {t_next - t0:.6f}, evaluate {t_evaluated - t_next:.6f}, training {t_train - t1:.6f}\n" diff --git a/bayesian_optimization/chaincomplexembedding.py b/bayesian_optimization/chaincomplexembedding.py index df476d8..f518485 100644 --- a/bayesian_optimization/chaincomplexembedding.py +++ b/bayesian_optimization/chaincomplexembedding.py @@ -5,8 +5,6 @@ import torch.nn as nn - - def _cum_offsets(sizes: List[int]) -> List[int]: """Compute cumulative offsets for a list of segment sizes.""" offs = [0] @@ -53,20 +51,22 @@ class ChainComplexEmbedder(nn.Module): Activation used in the 2-layer MLP inside each message passing layer. """ - def __init__(self, - views_info: List[Dict[str, Any]], - d_model: int = 128, - num_layers: int = 4, - *, - view_aggr: str = "sum", - readout_types: Optional[List[str]] = None, - # per-layer options - num_bases: Optional[int] = 4, - norm: str = "sym", - residual: bool = True, - self_loop: bool = False, - dropout: float = 0.1, - act: Optional[nn.Module] = None): + def __init__( + self, + views_info: List[Dict[str, Any]], + d_model: int = 128, + num_layers: int = 4, + *, + view_aggr: str = "sum", + readout_types: Optional[List[str]] = None, + # per-layer options + num_bases: Optional[int] = 4, + norm: str = "sym", + residual: bool = True, + self_loop: bool = False, + dropout: float = 0.1, + act: Optional[nn.Module] = None, + ): super().__init__() assert view_aggr in ("sum", "mean") self.view_aggr = view_aggr @@ -84,27 +84,35 @@ def __init__(self, name = v.get("name", "view") nts = list(v.get("partite_classes", [])) rels = list(v.get("relations", [])) - self.views_info.append({"name": name, "partite_classes": nts, "relations": rels}) + self.views_info.append( + {"name": name, "partite_classes": nts, "relations": rels} + ) self.view_names.append(name) self.view_node_types[name] = nts self.view_relations[name] = rels all_types.update(nts) self.all_node_types: List[str] = sorted(all_types) - self.readout_types: List[str] = list(readout_types) if readout_types is not None else self.all_node_types + self.readout_types: List[str] = ( + list(readout_types) if readout_types is not None else self.all_node_types + ) # ---- Learnable initial token per node type ---- - self.type_tokens = nn.ParameterDict({ - t: nn.Parameter(torch.empty(1, self.d_model)) for t in self.all_node_types - }) + self.type_tokens = nn.ParameterDict( + {t: nn.Parameter(torch.empty(1, self.d_model)) for t in self.all_node_types} + ) for p in self.type_tokens.values(): nn.init.xavier_uniform_(p) # ---- Cross-view gates: one scalar per (layer, view) ---- - self.view_gates = nn.ParameterList([ - nn.ParameterDict({vn: nn.Parameter(torch.tensor(1.0)) for vn in self.view_names}) - for _ in range(self.num_layers) - ]) + self.view_gates = nn.ParameterList( + [ + nn.ParameterDict( + {vn: nn.Parameter(torch.tensor(1.0)) for vn in self.view_names} + ) + for _ in range(self.num_layers) + ] + ) # ---- Build message passing stacks (per layer, per view) ---- Layer = ChainComplexMessagePassingLayer @@ -113,12 +121,12 @@ def __init__(self, per_view = nn.ModuleDict() for vn in self.view_names: per_view[vn] = Layer( - node_types=self.all_node_types, # pass full set; layer skips empty types by 'sizes' + node_types=self.all_node_types, # pass full set; layer skips empty types by 'sizes' relations=self.view_relations[vn], in_dim=self.d_model, out_dim=self.d_model, num_bases=num_bases, - aggr="sum", # sum over relations; cross-view aggregation happens here + aggr="sum", # sum over relations; cross-view aggregation happens here norm=norm, residual=residual, self_loop=self_loop, @@ -135,7 +143,9 @@ def __init__(self, nn.Linear(read_dim, hidden), nn.GELU(), nn.Dropout(dropout), - nn.Linear(hidden, self.d_model), # change if you want a different final embedding size + nn.Linear( + hidden, self.d_model + ), # change if you want a different final embedding size ) # ===================== Packing utilities ===================== @@ -149,7 +159,9 @@ def _parse_relation_tag(tag: str) -> Tuple[str, str]: elif t.startswith("b"): t = t[1:] if "_" not in t: - raise ValueError(f"Bad relation tag '{tag}'; expected 'A_B' (optionally prefixed by 'b'/'co').") + raise ValueError( + f"Bad relation tag '{tag}'; expected 'A_B' (optionally prefixed by 'b'/'co')." + ) a, b = t.split("_", 1) return a.upper(), b.upper() @@ -193,8 +205,13 @@ def _concat_sparse_blockdiag( # empty sparse tensor with the correct global shape empty_idx = torch.empty((2, 0), dtype=torch.long, device=device) empty_val = torch.empty((0,), dtype=dtype, device=device) - return torch.sparse_coo_tensor(empty_idx, empty_val, size=(total_dst, total_src), - device=device, dtype=dtype).coalesce() + return torch.sparse_coo_tensor( + empty_idx, + empty_val, + size=(total_dst, total_src), + device=device, + dtype=dtype, + ).coalesce() rows = torch.cat(idx_rows, dim=0) cols = torch.cat(idx_cols, dim=0) @@ -273,9 +290,13 @@ def _pack_batch( src_sizes.append(batch_splits[src_t][b]) # zero degrees if the view is absent if batch_splits[src_t][b] > 0: - deg_src_list.append(torch.zeros(batch_splits[src_t][b], dtype=torch.float32)) + deg_src_list.append( + torch.zeros(batch_splits[src_t][b], dtype=torch.float32) + ) if batch_splits[dst_t][b] > 0: - deg_dst_list.append(torch.zeros(batch_splits[dst_t][b], dtype=torch.float32)) + deg_dst_list.append( + torch.zeros(batch_splits[dst_t][b], dtype=torch.float32) + ) continue A = vdict.get("adj", {}).get(r, None) @@ -298,11 +319,21 @@ def _pack_batch( deg_dst_list.append(ddst.detach().cpu()) # block-diagonal adjacency - A_batch = self._concat_sparse_blockdiag(parts, dst_sizes, src_sizes, device=device, dtype=dtype) + A_batch = self._concat_sparse_blockdiag( + parts, dst_sizes, src_sizes, device=device, dtype=dtype + ) # concatenated degrees - deg_src_cat = torch.cat(deg_src_list, dim=0) if deg_src_list else torch.zeros(0, dtype=torch.float32) - deg_dst_cat = torch.cat(deg_dst_list, dim=0) if deg_dst_list else torch.zeros(0, dtype=torch.float32) + deg_src_cat = ( + torch.cat(deg_src_list, dim=0) + if deg_src_list + else torch.zeros(0, dtype=torch.float32) + ) + deg_dst_cat = ( + torch.cat(deg_dst_list, dim=0) + if deg_dst_list + else torch.zeros(0, dtype=torch.float32) + ) deg_src_cat = deg_src_cat.to(device) deg_dst_cat = deg_dst_cat.to(device) @@ -320,8 +351,9 @@ def _pack_batch( # ===================== Pooling & init utilities ===================== @staticmethod - def _gather_global_sizes(views: Dict[str, Dict[str, Any]], - all_types: List[str]) -> Dict[str, int]: + def _gather_global_sizes( + views: Dict[str, Dict[str, Any]], all_types: List[str] + ) -> Dict[str, int]: """Collect total node counts per type across views (they should match; take max for robustness).""" totals: Dict[str, int] = {t: 0 for t in all_types} for v in views.values(): @@ -331,9 +363,9 @@ def _gather_global_sizes(views: Dict[str, Dict[str, Any]], return totals @staticmethod - def _build_initial_H(totals: Dict[str, int], - type_tokens: nn.ParameterDict, - device: torch.device) -> Dict[str, torch.Tensor]: + def _build_initial_H( + totals: Dict[str, int], type_tokens: nn.ParameterDict, device: torch.device + ) -> Dict[str, torch.Tensor]: """Create initial features by repeating per-type tokens according to total node counts.""" H: Dict[str, torch.Tensor] = {} for t, n in totals.items(): @@ -344,7 +376,9 @@ def _build_initial_H(totals: Dict[str, int], return H @staticmethod - def _pool_type_by_splits(H_t: torch.Tensor, splits: Optional[List[int]], mode: str = "mean") -> torch.Tensor: + def _pool_type_by_splits( + H_t: torch.Tensor, splits: Optional[List[int]], mode: str = "mean" + ) -> torch.Tensor: """ Per-type, per-sample pooling for a batched graph. @@ -376,9 +410,9 @@ def _pool_type_by_splits(H_t: torch.Tensor, splits: Optional[List[int]], mode: s # ===================== Forward ===================== - def forward(self, - views_or_list: Any, - batch_splits: Optional[Dict[str, List[int]]] = None) -> torch.Tensor: + def forward( + self, views_or_list: Any, batch_splits: Optional[Dict[str, List[int]]] = None + ) -> torch.Tensor: """ Accept either: • a list of per-sample views (output of RelationEncoder.encode for each sample), or @@ -391,14 +425,18 @@ def forward(self, # If a list is provided, pack it first if isinstance(views_or_list, list): - packed_views, batch_splits = self._pack_batch(views_or_list, device=device, dtype=dtype) + packed_views, batch_splits = self._pack_batch( + views_or_list, device=device, dtype=dtype + ) views = packed_views else: views = views_or_list # If no batch_splits are provided, assume a single-sample batch (B=1) if batch_splits is None: totals_single = self._gather_global_sizes(views, self.all_node_types) - batch_splits = {t: [int(totals_single.get(t, 0))] for t in self.all_node_types} + batch_splits = { + t: [int(totals_single.get(t, 0))] for t in self.all_node_types + } # (1) Collect total node counts per type and create initial features totals = self._gather_global_sizes(views, self.all_node_types) @@ -437,26 +475,26 @@ def forward(self, B = len(next(iter(batch_splits.values()))) if len(batch_splits) > 0 else 1 for t in self.readout_types: - splits_t = batch_splits.get(t, [0] * B) # fill zeros if a type never appears, to keep alignment + splits_t = batch_splits.get( + t, [0] * B + ) # fill zeros if a type never appears, to keep alignment pooled_t = self._pool_type_by_splits(H[t], splits_t, mode="mean") pooled_per_type.append(pooled_t) # (B, d) - G = torch.cat(pooled_per_type, dim=-1) if len(pooled_per_type) > 1 else pooled_per_type[0] # (B, d*) + G = ( + torch.cat(pooled_per_type, dim=-1) + if len(pooled_per_type) > 1 + else pooled_per_type[0] + ) # (B, d*) z = self.readout_mlp(G) # (B, d_model) return z - - - - - - - # --------------------------- # Message Passing Layer (fixed) # --------------------------- + def _parse_relation_tag_global(tag: str) -> Tuple[str, str]: """Return (SRC, DST) from 'A_B' (accepts 'bA_B'/'coA_B' and strips prefix).""" t = tag.strip() @@ -465,7 +503,9 @@ def _parse_relation_tag_global(tag: str) -> Tuple[str, str]: elif t.startswith("b"): t = t[1:] if "_" not in t: - raise ValueError(f"Bad relation tag '{tag}'; expected 'A_B' (optionally prefixed by 'b'/'co').") + raise ValueError( + f"Bad relation tag '{tag}'; expected 'A_B' (optionally prefixed by 'b'/'co')." + ) a, b = t.split("_", 1) return a.upper(), b.upper() @@ -479,19 +519,21 @@ class ChainComplexMessagePassingLayer(nn.Module): not view['sizes'], so that different views produce per-type tensors with **identical row counts**. """ - def __init__(self, - node_types: List[str], - relations: List[str], - in_dim: int, - out_dim: int, - *, - num_bases: Optional[int] = 4, - aggr: str = "sum", - norm: str = "sym", - residual: bool = True, - self_loop: bool = False, - dropout: float = 0.1, - act: Optional[nn.Module] = None): + def __init__( + self, + node_types: List[str], + relations: List[str], + in_dim: int, + out_dim: int, + *, + num_bases: Optional[int] = 4, + aggr: str = "sum", + norm: str = "sym", + residual: bool = True, + self_loop: bool = False, + dropout: float = 0.1, + act: Optional[nn.Module] = None, + ): super().__init__() assert aggr in ("sum", "mean") assert norm in ("sym", "dst", "none") @@ -508,41 +550,59 @@ def __init__(self, self.act = act if act is not None else nn.GELU() # Per-destination-type: LayerNorm (PreNorm), residual projection, update MLP, self-loop gate. - self.ln_in = nn.ModuleDict({t: nn.LayerNorm(self.in_dim) for t in self.node_types}) - self.res_proj = nn.ModuleDict({t: nn.Linear(self.in_dim, self.out_dim, bias=False) - for t in self.node_types}) + self.ln_in = nn.ModuleDict( + {t: nn.LayerNorm(self.in_dim) for t in self.node_types} + ) + self.res_proj = nn.ModuleDict( + { + t: nn.Linear(self.in_dim, self.out_dim, bias=False) + for t in self.node_types + } + ) hidden = max(self.out_dim * 2, 64) - self.update_mlp = nn.ModuleDict({ - t: nn.Sequential( - nn.Linear(self.out_dim * 2, hidden, bias=True), - self.act, - nn.Dropout(self.dropout), - nn.Linear(hidden, self.out_dim, bias=True), - ) for t in self.node_types - }) + self.update_mlp = nn.ModuleDict( + { + t: nn.Sequential( + nn.Linear(self.out_dim * 2, hidden, bias=True), + self.act, + nn.Dropout(self.dropout), + nn.Linear(hidden, self.out_dim, bias=True), + ) + for t in self.node_types + } + ) if self.self_loop: - self.self_gate = nn.ParameterDict({t: nn.Parameter(torch.tensor(1.0)) for t in self.node_types}) + self.self_gate = nn.ParameterDict( + {t: nn.Parameter(torch.tensor(1.0)) for t in self.node_types} + ) # Relation weights: basis or per-relation if self.num_bases is None or self.num_bases >= len(self.relations): self.weight_mode = "per_relation" - self.rel_W = nn.ParameterDict({ - r: nn.Parameter(torch.empty(self.in_dim, self.out_dim)) for r in self.relations - }) + self.rel_W = nn.ParameterDict( + { + r: nn.Parameter(torch.empty(self.in_dim, self.out_dim)) + for r in self.relations + } + ) for p in self.rel_W.values(): nn.init.xavier_uniform_(p) else: self.weight_mode = "basis" B = self.num_bases - self.bases = nn.ParameterList([nn.Parameter(torch.empty(self.in_dim, self.out_dim)) for _ in range(B)]) + self.bases = nn.ParameterList( + [nn.Parameter(torch.empty(self.in_dim, self.out_dim)) for _ in range(B)] + ) for V in self.bases: nn.init.xavier_uniform_(V) - self.rel_coeff = nn.ParameterDict({ - r: nn.Parameter(torch.randn(B) / (B ** 0.5)) for r in self.relations - }) + self.rel_coeff = nn.ParameterDict( + {r: nn.Parameter(torch.randn(B) / (B**0.5)) for r in self.relations} + ) # Relation gates (scalar) - self.rel_gate = nn.ParameterDict({r: nn.Parameter(torch.tensor(1.0)) for r in self.relations}) + self.rel_gate = nn.ParameterDict( + {r: nn.Parameter(torch.tensor(1.0)) for r in self.relations} + ) self.dropout_out = nn.Dropout(self.dropout) # ---- helpers ---- @@ -551,7 +611,9 @@ def _get_W(self, r: str) -> torch.Tensor: return self.rel_W[r] # basis coeff = self.rel_coeff[r] # (B,) - W = torch.zeros((self.in_dim, self.out_dim), device=coeff.device, dtype=self.bases[0].dtype) + W = torch.zeros( + (self.in_dim, self.out_dim), device=coeff.device, dtype=self.bases[0].dtype + ) for a, V in zip(coeff, self.bases): W = W + a * V return W @@ -570,11 +632,13 @@ def _safe_inv(x: torch.Tensor) -> torch.Tensor: out[mask] = x[mask].reciprocal() return out - def _spmm_norm(self, - A: torch.Tensor, # sparse COO (|dst| x |src|) - X_src: torch.Tensor, # (|src| x out_dim) - deg_src: torch.Tensor, # (|src|,) - deg_dst: torch.Tensor) -> torch.Tensor: # (|dst|,) + def _spmm_norm( + self, + A: torch.Tensor, # sparse COO (|dst| x |src|) + X_src: torch.Tensor, # (|src| x out_dim) + deg_src: torch.Tensor, # (|src|,) + deg_dst: torch.Tensor, + ) -> torch.Tensor: # (|dst|,) """Apply normalization + SpMM for one relation.""" if self.norm == "none": msg = torch.sparse.mm(A, X_src) @@ -584,16 +648,16 @@ def _spmm_norm(self, dinv = self._safe_inv(deg_dst).unsqueeze(-1) # (|dst|,1) return msg * dinv else: # 'sym' - dsrc = self._safe_inv_sqrt(deg_src).unsqueeze(-1) # (|src|,1) + dsrc = self._safe_inv_sqrt(deg_src).unsqueeze(-1) # (|src|,1) Xn = X_src * dsrc msg = torch.sparse.mm(A, Xn) - ddst = self._safe_inv_sqrt(deg_dst).unsqueeze(-1) # (|dst|,1) + ddst = self._safe_inv_sqrt(deg_dst).unsqueeze(-1) # (|dst|,1) return msg * ddst # ---- forward ---- - def forward(self, - H: Dict[str, torch.Tensor], - view: Dict[str, dict]) -> Dict[str, torch.Tensor]: + def forward( + self, H: Dict[str, torch.Tensor], view: Dict[str, dict] + ) -> Dict[str, torch.Tensor]: """ H: {type: (N_type_global, in_dim)} view: {'adj': {relation_tag: sparse_coo}, 'deg': {relation_tag: (deg_src, deg_dst)}, ...} @@ -604,11 +668,14 @@ def forward(self, device = next(self.parameters()).device # GLOBAL counts from H, not from view['sizes'] - N: Dict[str, int] = {t: (H[t].shape[0] if t in H else 0) for t in self.node_types} + N: Dict[str, int] = { + t: (H[t].shape[0] if t in H else 0) for t in self.node_types + } # Aggregation buffers per destination type (GLOBAL shapes) agg_msgs: Dict[str, torch.Tensor] = { - t: torch.zeros((N.get(t, 0), self.out_dim), device=device) for t in self.node_types + t: torch.zeros((N.get(t, 0), self.out_dim), device=device) + for t in self.node_types } rel_counts: Dict[str, int] = {t: 0 for t in self.node_types} @@ -627,8 +694,8 @@ def forward(self, for r in self.relations: if "adj" not in view or r not in view["adj"]: continue # relation absent - A = view["adj"][r] # (|dst_global| x |src_global|) sparse COO - deg_src, deg_dst = view["deg"][r] # 1D tensors + A = view["adj"][r] # (|dst_global| x |src_global|) sparse COO + deg_src, deg_dst = view["deg"][r] # 1D tensors src, dst = _parse_relation_tag_global(r) n_src = N.get(src, 0) @@ -637,15 +704,18 @@ def forward(self, continue # no-op # Sanity check: adjacency must match global shapes - assert A.size(0) == n_dst and A.size(1) == n_src, \ + assert A.size(0) == n_dst and A.size(1) == n_src, ( f"Adjacency shape mismatch for relation {r}: got {tuple(A.size())}, expected ({n_dst},{n_src})" + ) # 1) Linear map on src - W_r = self._get_W(r) # (in_dim x out_dim) - X_src = H[src] @ W_r # (|src| x out_dim) + W_r = self._get_W(r) # (in_dim x out_dim) + X_src = H[src] @ W_r # (|src| x out_dim) # 2) Normalization + SpMM - msg = self._spmm_norm(A, X_src, deg_src.to(device), deg_dst.to(device)) # (|dst| x out_dim) + msg = self._spmm_norm( + A, X_src, deg_src.to(device), deg_dst.to(device) + ) # (|dst| x out_dim) # 3) Relation gate gamma = self.rel_gate[r] @@ -677,7 +747,7 @@ def forward(self, out[t] = torch.zeros((0, self.out_dim), device=device) continue upd_in = torch.cat([proj_dst[t], agg_msgs[t]], dim=-1) # (|t|, 2*out_dim) - upd = self.update_mlp[t](upd_in) # (|t|, out_dim) + upd = self.update_mlp[t](upd_in) # (|t|, out_dim) upd = self.dropout_out(upd) out[t] = proj_dst[t] + upd if self.residual else upd return out @@ -687,6 +757,7 @@ def forward(self, # Embedder (fixed) # --------------------------- + def _cum_offsets(sizes: List[int]) -> List[int]: """Compute cumulative offsets for a list of segment sizes.""" offs = [0] @@ -707,20 +778,22 @@ class ChainComplexEmbedder(nn.Module): • per-type global counts are computed from **batch_splits** (sum) to initialize features. """ - def __init__(self, - views_info: List[Dict[str, Any]], - d_model: int = 128, - num_layers: int = 4, - *, - view_aggr: str = "sum", - readout_types: Optional[List[str]] = None, - # per-layer options - num_bases: Optional[int] = 4, - norm: str = "sym", - residual: bool = True, - self_loop: bool = False, - dropout: float = 0.1, - act: Optional[nn.Module] = None): + def __init__( + self, + views_info: List[Dict[str, Any]], + d_model: int = 128, + num_layers: int = 4, + *, + view_aggr: str = "sum", + readout_types: Optional[List[str]] = None, + # per-layer options + num_bases: Optional[int] = 4, + norm: str = "sym", + residual: bool = True, + self_loop: bool = False, + dropout: float = 0.1, + act: Optional[nn.Module] = None, + ): super().__init__() assert view_aggr in ("sum", "mean") self.view_aggr = view_aggr @@ -738,27 +811,35 @@ def __init__(self, name = v.get("name", "view") nts = list(v.get("partite_classes", [])) rels = list(v.get("relations", [])) - self.views_info.append({"name": name, "partite_classes": nts, "relations": rels}) + self.views_info.append( + {"name": name, "partite_classes": nts, "relations": rels} + ) self.view_names.append(name) self.view_node_types[name] = nts self.view_relations[name] = rels all_types.update(nts) self.all_node_types: List[str] = sorted(all_types) - self.readout_types: List[str] = list(readout_types) if readout_types is not None else self.all_node_types + self.readout_types: List[str] = ( + list(readout_types) if readout_types is not None else self.all_node_types + ) # Type tokens - self.type_tokens = nn.ParameterDict({ - t: nn.Parameter(torch.empty(1, self.d_model)) for t in self.all_node_types - }) + self.type_tokens = nn.ParameterDict( + {t: nn.Parameter(torch.empty(1, self.d_model)) for t in self.all_node_types} + ) for p in self.type_tokens.values(): nn.init.xavier_uniform_(p) # View gates: one scalar per (layer, view) - self.view_gates = nn.ParameterList([ - nn.ParameterDict({vn: nn.Parameter(torch.tensor(1.0)) for vn in self.view_names}) - for _ in range(self.num_layers) - ]) + self.view_gates = nn.ParameterList( + [ + nn.ParameterDict( + {vn: nn.Parameter(torch.tensor(1.0)) for vn in self.view_names} + ) + for _ in range(self.num_layers) + ] + ) # Per-layer, per-view message passing modules Layer = ChainComplexMessagePassingLayer @@ -803,7 +884,9 @@ def _parse_relation_tag(tag: str) -> Tuple[str, str]: elif t.startswith("b"): t = t[1:] if "_" not in t: - raise ValueError(f"Bad relation tag '{tag}'; expected 'A_B' (optionally prefixed by 'b'/'co').") + raise ValueError( + f"Bad relation tag '{tag}'; expected 'A_B' (optionally prefixed by 'b'/'co')." + ) a, b = t.split("_", 1) return a.upper(), b.upper() @@ -846,8 +929,13 @@ def _concat_sparse_blockdiag( if len(vals) == 0: empty_idx = torch.empty((2, 0), dtype=torch.long, device=device) empty_val = torch.empty((0,), dtype=dtype, device=device) - return torch.sparse_coo_tensor(empty_idx, empty_val, size=(total_dst, total_src), - device=device, dtype=dtype).coalesce() + return torch.sparse_coo_tensor( + empty_idx, + empty_val, + size=(total_dst, total_src), + device=device, + dtype=dtype, + ).coalesce() rows = torch.cat(idx_rows, dim=0) cols = torch.cat(idx_cols, dim=0) @@ -919,7 +1007,9 @@ def _pack_batch( for b in range(B): vdict = sample_views_list[b].get(vn, None) - blk_src = batch_splits[src_t][b] # ALWAYS use global per-sample block size + blk_src = batch_splits[src_t][ + b + ] # ALWAYS use global per-sample block size blk_dst = batch_splits[dst_t][b] # adjacency block (can be None) @@ -937,11 +1027,27 @@ def _pack_batch( dsrc = dsrc.detach().cpu() ddst = ddst.detach().cpu() if dsrc.numel() < blk_src: - dsrc = torch.cat([dsrc, torch.zeros(blk_src - dsrc.numel(), dtype=torch.float32)], dim=0) + dsrc = torch.cat( + [ + dsrc, + torch.zeros( + blk_src - dsrc.numel(), dtype=torch.float32 + ), + ], + dim=0, + ) elif dsrc.numel() > blk_src: dsrc = dsrc[:blk_src] if ddst.numel() < blk_dst: - ddst = torch.cat([ddst, torch.zeros(blk_dst - ddst.numel(), dtype=torch.float32)], dim=0) + ddst = torch.cat( + [ + ddst, + torch.zeros( + blk_dst - ddst.numel(), dtype=torch.float32 + ), + ], + dim=0, + ) elif ddst.numel() > blk_dst: ddst = ddst[:blk_dst] @@ -949,7 +1055,9 @@ def _pack_batch( deg_dst_list.append(ddst) # block-diagonal adjacency (GLOBAL shapes) - A_batch = self._concat_sparse_blockdiag(parts, dst_sizes, src_sizes, device=device, dtype=dtype) + A_batch = self._concat_sparse_blockdiag( + parts, dst_sizes, src_sizes, device=device, dtype=dtype + ) # concatenated degrees deg_src_cat = torch.cat(deg_src_list, dim=0).to(device) @@ -959,7 +1067,7 @@ def _pack_batch( deg_packed[r] = (deg_src_cat, deg_dst_cat) packed_views[vn] = { - "sizes": totals_v, # kept for reference; layer does not rely on it for allocation + "sizes": totals_v, # kept for reference; layer does not rely on it for allocation "adj": adj_packed, "deg": deg_packed, } @@ -967,9 +1075,11 @@ def _pack_batch( return packed_views, batch_splits @staticmethod - def _build_initial_H_from_splits(batch_splits: Dict[str, List[int]], - type_tokens: nn.ParameterDict, - device: torch.device) -> Dict[str, torch.Tensor]: + def _build_initial_H_from_splits( + batch_splits: Dict[str, List[int]], + type_tokens: nn.ParameterDict, + device: torch.device, + ) -> Dict[str, torch.Tensor]: """Create initial features by repeating per-type tokens; counts from sum of batch_splits.""" H: Dict[str, torch.Tensor] = {} for t, splits in batch_splits.items(): @@ -981,12 +1091,18 @@ def _build_initial_H_from_splits(batch_splits: Dict[str, List[int]], return H @staticmethod - def _pool_type_by_splits(H_t: torch.Tensor, splits: Optional[List[int]], mode: str = "mean") -> torch.Tensor: + def _pool_type_by_splits( + H_t: torch.Tensor, splits: Optional[List[int]], mode: str = "mean" + ) -> torch.Tensor: """Per-type, per-sample pooling for a batched graph.""" if splits is None: if H_t.numel() == 0: return H_t.new_zeros((1, H_t.shape[1])) - return H_t.mean(dim=0, keepdim=True) if mode == "mean" else H_t.sum(dim=0, keepdim=True) + return ( + H_t.mean(dim=0, keepdim=True) + if mode == "mean" + else H_t.sum(dim=0, keepdim=True) + ) B = len(splits) offs = _cum_offsets(splits) @@ -1001,9 +1117,9 @@ def _pool_type_by_splits(H_t: torch.Tensor, splits: Optional[List[int]], mode: s # ---------- Forward ---------- - def forward(self, - views_or_list: Any, - batch_splits: Optional[Dict[str, List[int]]] = None) -> torch.Tensor: + def forward( + self, views_or_list: Any, batch_splits: Optional[Dict[str, List[int]]] = None + ) -> torch.Tensor: """ Accept either: • a list of per-sample views (output of RelationEncoder.encode for each sample), or @@ -1016,10 +1132,14 @@ def forward(self, # If a list is provided, pack it first if isinstance(views_or_list, list): - packed_views, batch_splits = self._pack_batch(views_or_list, device=device, dtype=dtype) + packed_views, batch_splits = self._pack_batch( + views_or_list, device=device, dtype=dtype + ) views = packed_views # Build initial features using global counts from batch_splits - H = self._build_initial_H_from_splits(batch_splits, self.type_tokens, device=device) + H = self._build_initial_H_from_splits( + batch_splits, self.type_tokens, device=device + ) else: views = views_or_list # If no batch_splits are provided, assume B=1 and infer counts from views (best effort) @@ -1028,9 +1148,15 @@ def forward(self, for t in self.all_node_types: totals_single[t] = 0 for v in views.values(): - totals_single[t] = max(totals_single[t], int(v.get("sizes", {}).get(t, 0))) - batch_splits = {t: [totals_single.get(t, 0)] for t in self.all_node_types} - H = self._build_initial_H_from_splits(batch_splits, self.type_tokens, device=device) + totals_single[t] = max( + totals_single[t], int(v.get("sizes", {}).get(t, 0)) + ) + batch_splits = { + t: [totals_single.get(t, 0)] for t in self.all_node_types + } + H = self._build_initial_H_from_splits( + batch_splits, self.type_tokens, device=device + ) # L stacked layers; per layer: run every view, then aggregate across views for li, per_view in enumerate(self.layers): @@ -1064,10 +1190,16 @@ def forward(self, B = len(next(iter(batch_splits.values()))) if len(batch_splits) > 0 else 1 for t in self.readout_types: - splits_t = batch_splits.get(t, [0] * B) # fill zeros if a type never appears + splits_t = batch_splits.get( + t, [0] * B + ) # fill zeros if a type never appears pooled_t = self._pool_type_by_splits(H[t], splits_t, mode="mean") pooled_per_type.append(pooled_t) # (B, d) - G = torch.cat(pooled_per_type, dim=-1) if len(pooled_per_type) > 1 else pooled_per_type[0] # (B, d*) + G = ( + torch.cat(pooled_per_type, dim=-1) + if len(pooled_per_type) > 1 + else pooled_per_type[0] + ) # (B, d*) z = self.readout_mlp(G) # (B, d_model) return z diff --git a/bayesian_optimization/encoder.py b/bayesian_optimization/encoder.py index 0fbdcbe..838af51 100644 --- a/bayesian_optimization/encoder.py +++ b/bayesian_optimization/encoder.py @@ -1,187 +1,204 @@ """ - This file defines encoder that converts the input code into different representations +This file defines encoder that converts the input code into different representations """ + from __future__ import annotations from typing import List, Dict, Any, Tuple, Optional import numpy as np import torch from toponetx import CombinatorialComplex -from torch_geometric.utils import dense_to_sparse -from typing import List from scipy.sparse import csr_matrix -import numpy as np -from torch_geometric.data import Data, DataLoader +from torch_geometric.data import Data from bayesian_optimization.gadget import * -class CSSEncoder(): - def __init__(self,info,mode='graph',grakel_use = False,logical=False,cc_patern=[]): +class CSSEncoder: + def __init__( + self, info, mode="graph", grakel_use=False, logical=False, cc_patern=[] + ): """ - n: number of qubits, n = hx.shape[1] = hz.shape[1] - nx: number of x stabilizers, nx = hx.shape[0] - nz: number of z stabilizers, nz = hz.shape[0] - mode: 'graph' or 'combinatorial_complex' + n: number of qubits, n = hx.shape[1] = hz.shape[1] + nx: number of x stabilizers, nx = hx.shape[0] + nz: number of z stabilizers, nz = hz.shape[0] + mode: 'graph' or 'combinatorial_complex' """ self.logical = logical - self.mode=mode - if mode == 'graph': - n = info['n'] - nx = info['nx'] - nz = info['nz'] - self.encoder = GraphEncoder(n,nx,nz,grakel_use=grakel_use,logical=logical) - elif mode == 'combinatorial_complex': - n = info['n'] - nx = info['nx'] - nz = info['nz'] - self.encoder = CombinatorialComplexEncoder_(n,nx,nz) - elif mode == 'chain_complex': + self.mode = mode + if mode == "graph": + n = info["n"] + nx = info["nx"] + nz = info["nz"] + self.encoder = GraphEncoder( + n, nx, nz, grakel_use=grakel_use, logical=logical + ) + elif mode == "combinatorial_complex": + n = info["n"] + nx = info["nx"] + nz = info["nz"] + self.encoder = CombinatorialComplexEncoder_(n, nx, nz) + elif mode == "chain_complex": self.encoder = ChainComplexGlobalFeaturesEncoder(info) - elif mode == 'relations': + elif mode == "relations": self.encoder = RelationEncoder(info) - def encode(self,c): - if self.mode in ['chain_complex','relations']: - return self.encoder.encode(c) + + def encode(self, c): + if self.mode in ["chain_complex", "relations"]: + return self.encoder.encode(c) if self.logical: hx = c.hx hz = c.hz lx = c.lx lz = c.lz - return self.encoder.encode(hx,hz,lx,lz) + return self.encoder.encode(hx, hz, lx, lz) else: hx = c.hx hz = c.hz - return self.encoder.encode(hx,hz) - + return self.encoder.encode(hx, hz) - - -class GraphEncoder(): - def __init__(self,n,nx,nz,grakel_use=False,logical=False): +class GraphEncoder: + def __init__(self, n, nx, nz, grakel_use=False, logical=False): """ - n: number of qubits, n = hx.shape[1] = hz.shape[1] - nx: number of x stabilizers, nx = hx.shape[0] - nz: number of z stabilizers, nz = hz.shape[0] + n: number of qubits, n = hx.shape[1] = hz.shape[1] + nx: number of x stabilizers, nx = hx.shape[0] + nz: number of z stabilizers, nz = hz.shape[0] """ self.n = n self.nx = nx self.nz = nz self.grakel_use = grakel_use if grakel_use: - from grakel import Graph + pass self.logical = logical - def preprocess_graph(self,adj_matrix: np.ndarray, node_onehots: np.ndarray): + + def preprocess_graph(self, adj_matrix: np.ndarray, node_onehots: np.ndarray): """ adj_matrix: np.ndarray of shape (N, N) node_onehots: np.ndarray of shape (N, 5) """ - + src, dst = np.nonzero(adj_matrix) edge_index = torch.tensor([src, dst], dtype=torch.long) # shape [2, E] + x = torch.tensor(node_onehots, dtype=torch.float) # shape [N, 5] - x = torch.tensor(node_onehots, dtype=torch.float) # shape [N, 5] - - data = Data(x=x, edge_index=edge_index) return data - - def encode(self,hx,hz,lx=None,lz=None): - - # adjacency_matrix[self.n+self.nx+j][i] = 1 + + def encode(self, hx, hz, lx=None, lz=None): + + # adjacency_matrix[self.n+self.nx+j][i] = 1 # print(f'adjacency_matrix:{adjacency_matrix}') # edge_index = dense_to_sparse(adjacency_matrix)[0] # return edge_index - if self.logical==False: - adjacency_matrix = np.zeros((self.n+self.nx+self.nz,self.n+self.nx+self.nz)) - + if self.logical == False: + adjacency_matrix = np.zeros( + (self.n + self.nx + self.nz, self.n + self.nx + self.nz) + ) + for i in range(self.n): for j in range(self.nx): if hx[j][i] == 1: - adjacency_matrix[i][self.n+j] = 1 - adjacency_matrix[self.n+j][i] = 1 + adjacency_matrix[i][self.n + j] = 1 + adjacency_matrix[self.n + j][i] = 1 for j in range(self.nz): if hz[j][i] == 1: - adjacency_matrix[i][self.n+self.nx+j] = 1 - adjacency_matrix[self.n+self.nx+j][i] = 1 + adjacency_matrix[i][self.n + self.nx + j] = 1 + adjacency_matrix[self.n + self.nx + j][i] = 1 if not self.grakel_use: - return torch.tensor(adjacency_matrix) else: - - node_labels = {} for i in range(self.n): - node_labels[i]='0' - for i in range(self.n,self.n+self.nx): - node_labels[i]='+' - for i in range(self.n+self.nx,self.n+self.nx+self.nz): - node_labels[i]='-' - + node_labels[i] = "0" + for i in range(self.n, self.n + self.nx): + node_labels[i] = "+" + for i in range(self.n + self.nx, self.n + self.nx + self.nz): + node_labels[i] = "-" + # print('adjacency_matrix') # print(adjacency_matrix) - g = Graph(initialization_object=adjacency_matrix,node_labels=node_labels,graph_format='adjacency',construct_labels=False) + g = Graph( + initialization_object=adjacency_matrix, + node_labels=node_labels, + graph_format="adjacency", + construct_labels=False, + ) # print(g) return g else: - total_nodes = self.n+self.nx+self.nz+lx.shape[0]+lz.shape[0] - adjacency_matrix = np.zeros((total_nodes,total_nodes)) + total_nodes = self.n + self.nx + self.nz + lx.shape[0] + lz.shape[0] + adjacency_matrix = np.zeros((total_nodes, total_nodes)) for i in range(self.n): for j in range(self.nx): if hx[j][i] == 1: - adjacency_matrix[i][self.n+j] = 1 - adjacency_matrix[self.n+j][i] = 1 + adjacency_matrix[i][self.n + j] = 1 + adjacency_matrix[self.n + j][i] = 1 for j in range(self.nz): if hz[j][i] == 1: - adjacency_matrix[i][self.n+self.nx+j] = 1 - adjacency_matrix[self.n+self.nx+j][i] = 1 + adjacency_matrix[i][self.n + self.nx + j] = 1 + adjacency_matrix[self.n + self.nx + j][i] = 1 for j in range(lx.shape[0]): - if lx[j,i]==1: - adjacency_matrix[i][self.n+self.nx+self.nz+j]=1 - adjacency_matrix[self.n+self.nx+self.nz+j][i]=1 + if lx[j, i] == 1: + adjacency_matrix[i][self.n + self.nx + self.nz + j] = 1 + adjacency_matrix[self.n + self.nx + self.nz + j][i] = 1 for j in range(lz.shape[0]): - if lz[j,i]==1: - adjacency_matrix[i][self.n+self.nx+self.nz+lx.shape[0]+j]=1 - adjacency_matrix[self.n+self.nx+self.nz+lx.shape[0]+j][i]=1 + if lz[j, i] == 1: + adjacency_matrix[i][ + self.n + self.nx + self.nz + lx.shape[0] + j + ] = 1 + adjacency_matrix[self.n + self.nx + self.nz + lx.shape[0] + j][ + i + ] = 1 if not self.grakel_use: node_onehots = torch.zeros((total_nodes, 5), dtype=torch.float) - node_onehots[0 : self.n, 0] = 1.0 - node_onehots[self.n : self.n + self.nx, 1] = 1.0 + node_onehots[0 : self.n, 0] = 1.0 + node_onehots[self.n : self.n + self.nx, 1] = 1.0 node_onehots[self.n + self.nx : self.n + self.nx + self.nz, 2] = 1.0 - node_onehots[self.n + self.nx + self.nz : self.n + self.nx + self.nz + lx.shape[0], 3] = 1.0 - node_onehots[self.n + self.nx + self.nz + lx.shape[0] : total_nodes, 4] = 1.0 - return self.preprocess_graph(adjacency_matrix,node_onehots) + node_onehots[ + self.n + self.nx + self.nz : self.n + + self.nx + + self.nz + + lx.shape[0], + 3, + ] = 1.0 + node_onehots[ + self.n + self.nx + self.nz + lx.shape[0] : total_nodes, 4 + ] = 1.0 + return self.preprocess_graph(adjacency_matrix, node_onehots) else: - - node_labels = {} for i in range(self.n): - node_labels[i]='0' - for i in range(self.n,self.n+self.nx): - node_labels[i]='+' - for i in range(self.n+self.nx,self.n+self.nx+self.nz): - node_labels[i]='-' - for i in range(self.n+self.nx+self.nz,self.n+self.nx+self.nz+lx.shape[0]): - node_labels[i]='x' - for i in range(self.n+self.nx+self.nz+lx.shape[0],self.n+self.nx+self.nz+lx.shape[0]+lz.shape[0]): - node_labels[i]='z' - + node_labels[i] = "0" + for i in range(self.n, self.n + self.nx): + node_labels[i] = "+" + for i in range(self.n + self.nx, self.n + self.nx + self.nz): + node_labels[i] = "-" + for i in range( + self.n + self.nx + self.nz, self.n + self.nx + self.nz + lx.shape[0] + ): + node_labels[i] = "x" + for i in range( + self.n + self.nx + self.nz + lx.shape[0], + self.n + self.nx + self.nz + lx.shape[0] + lz.shape[0], + ): + node_labels[i] = "z" + # print('adjacency_matrix') # print(adjacency_matrix) - g = Graph(initialization_object=adjacency_matrix,node_labels=node_labels,graph_format='adjacency',construct_labels=False) + g = Graph( + initialization_object=adjacency_matrix, + node_labels=node_labels, + graph_format="adjacency", + construct_labels=False, + ) # print(g) return g - - - - - # optional SciPy sparse backend try: - import scipy.sparse as sp _HAS_SCIPY = True except Exception: _HAS_SCIPY = False @@ -200,13 +217,12 @@ def _vstack_bool_int(*rows: np.ndarray) -> np.ndarray: """vstack rows after binarization to {0,1} int64.""" rows_b = [_to_bool_int(r) for r in rows if r is not None and r.size > 0] if not rows_b: - return np.zeros((0, rows[0].shape[1]), dtype=np.int64) # assume widths match if any + return np.zeros( + (0, rows[0].shape[1]), dtype=np.int64 + ) # assume widths match if any return np.vstack(rows_b).astype(np.int64, copy=False) - - - def _to_bool_int(a: np.ndarray) -> np.ndarray: """Map any ndarray to {0,1} int64 (GF(2) semantics).""" a = np.asarray(a) @@ -223,13 +239,15 @@ def _vstack_bool_int(*rows: np.ndarray) -> np.ndarray: return np.zeros((0, 0), dtype=np.int64) width = rows_b[0].shape[1] for rb in rows_b: - assert rb.shape[1] == width, "All stacked matrices must have the same number of columns" + assert rb.shape[1] == width, ( + "All stacked matrices must have the same number of columns" + ) return np.vstack(rows_b).astype(np.int64, copy=False) -def _np_to_sparse_coo(M: np.ndarray, - device: Optional[torch.device], - dtype: torch.dtype) -> torch.Tensor: +def _np_to_sparse_coo( + M: np.ndarray, device: Optional[torch.device], dtype: torch.dtype +) -> torch.Tensor: """ Convert 0/1 numpy array to torch.sparse_coo_tensor of shape M.shape on device. Values are 1.0 (dtype provided). Always coalesced. @@ -241,10 +259,14 @@ def _np_to_sparse_coo(M: np.ndarray, if nnz == 0: idx = torch.empty((2, 0), dtype=torch.long, device=device) val = torch.empty((0,), dtype=dtype, device=device) - return torch.sparse_coo_tensor(idx, val, size=(m, n), device=device, dtype=dtype).coalesce() + return torch.sparse_coo_tensor( + idx, val, size=(m, n), device=device, dtype=dtype + ).coalesce() idx = torch.from_numpy(np.vstack([rows, cols])).long().to(device) val = torch.ones((nnz,), dtype=dtype, device=device) - return torch.sparse_coo_tensor(idx, val, size=(m, n), device=device, dtype=dtype).coalesce() + return torch.sparse_coo_tensor( + idx, val, size=(m, n), device=device, dtype=dtype + ).coalesce() class RelationEncoder: @@ -285,10 +307,12 @@ class RelationEncoder: NODE_SET = {"SX", "SZ", "DQ", "LX", "LZ", "AX", "AZ"} - def __init__(self, - views_info: Optional[List[Dict[str, Any]]] = None, - device: Optional[torch.device] = None, - dtype: torch.dtype = torch.float32): + def __init__( + self, + views_info: Optional[List[Dict[str, Any]]] = None, + device: Optional[torch.device] = None, + dtype: torch.dtype = torch.float32, + ): self.views_info = views_info or [] self.device = device self.dtype = dtype @@ -318,25 +342,39 @@ def encode(self, c) -> Dict[str, Dict[str, Any]]: AZ = _vstack_bool_int(Hz, Lz) # rows = mz + kz # Sanity on widths - assert Hx.shape[1] == n and Hz.shape[1] == n and Lx.shape[1] == n and Lz.shape[1] == n, \ - "All Hx/Hz/Lx/Lz must have width n" + assert ( + Hx.shape[1] == n + and Hz.shape[1] == n + and Lx.shape[1] == n + and Lz.shape[1] == n + ), "All Hx/Hz/Lx/Lz must have width n" # Sizes per node type sizes_all = { - "SX": mx, "SZ": mz, "DQ": n, - "LX": kx, "LZ": kz, - "AX": AX.shape[0], "AZ": AZ.shape[0], + "SX": mx, + "SZ": mz, + "DQ": n, + "LX": kx, + "LZ": kz, + "AX": AX.shape[0], + "AZ": AZ.shape[0], } # Primitive matrices by (SRC, DST) with our left-multiply convention # Shape is (|DST|, |SRC|) prim_np: Dict[Tuple[str, str], np.ndarray] = { - ("SX", "DQ"): Hx.T, ("DQ", "SX"): Hx, - ("SZ", "DQ"): Hz.T, ("DQ", "SZ"): Hz, - ("LX", "DQ"): Lx.T, ("DQ", "LX"): Lx, - ("LZ", "DQ"): Lz.T, ("DQ", "LZ"): Lz, - ("AX", "DQ"): AX.T, ("DQ", "AX"): AX, - ("AZ", "DQ"): AZ.T, ("DQ", "AZ"): AZ, + ("SX", "DQ"): Hx.T, + ("DQ", "SX"): Hx, + ("SZ", "DQ"): Hz.T, + ("DQ", "SZ"): Hz, + ("LX", "DQ"): Lx.T, + ("DQ", "LX"): Lx, + ("LZ", "DQ"): Lz.T, + ("DQ", "LZ"): Lz, + ("AX", "DQ"): AX.T, + ("DQ", "AX"): AX, + ("AZ", "DQ"): AZ.T, + ("DQ", "AZ"): AZ, } out: Dict[str, Dict[str, Any]] = {} @@ -387,8 +425,9 @@ def _validate_partite(self, partite: List[str]) -> None: # if ("LX" in partite and "AX" in partite) or ("LZ" in partite and "AZ" in partite): # pass - def _build_relation_matrix(self, tag: str, - prim_np: Dict[Tuple[str, str], np.ndarray]) -> Tuple[np.ndarray, Tuple[str, str]]: + def _build_relation_matrix( + self, tag: str, prim_np: Dict[Tuple[str, str], np.ndarray] + ) -> Tuple[np.ndarray, Tuple[str, str]]: """ Return (numpy matrix, (src,dst)) for relation tag. @@ -402,7 +441,9 @@ def _build_relation_matrix(self, tag: str, raw = tag.strip() if raw.startswith("adj"): - raise NotImplementedError(f"Relation '{tag}': 'adj*' builders are reserved for later.") + raise NotImplementedError( + f"Relation '{tag}': 'adj*' builders are reserved for later." + ) if raw.startswith("co"): core = raw[2:] @@ -429,79 +470,87 @@ def _parse_A_B(core: str) -> Tuple[str, str]: return a.strip().upper(), b.strip().upper() @staticmethod - def _lookup_primitive(src: str, dst: str, - prim_np: Dict[Tuple[str, str], np.ndarray], - tag: str) -> np.ndarray: + def _lookup_primitive( + src: str, dst: str, prim_np: Dict[Tuple[str, str], np.ndarray], tag: str + ) -> np.ndarray: key = (src, dst) if key not in prim_np: - raise ValueError(f"Relation '{tag}': unsupported primitive mapping '{src}->{dst}'.") + raise ValueError( + f"Relation '{tag}': unsupported primitive mapping '{src}->{dst}'." + ) return prim_np[key] - - -class CombinatorialComplexEncoder_(): - def __init__(self,n,nx,nz): +class CombinatorialComplexEncoder_: + def __init__(self, n, nx, nz): """ - n: number of qubits, n = hx.shape[1] = hz.shape[1] - nx: number of x stabilizers, nx = hx.shape[0] - nz: number of z stabilizers, nz = hz.shape[0] + n: number of qubits, n = hx.shape[1] = hz.shape[1] + nx: number of x stabilizers, nx = hx.shape[0] + nz: number of z stabilizers, nz = hz.shape[0] """ self.n_c0 = nx self.n_c1 = n self.n_c2 = nz - - def encode(self,hx,hz) -> List[csr_matrix]: + + def encode(self, hx, hz) -> List[csr_matrix]: """ - return Tensor: - tensor([coA01,coA02,A10,coA12,A20,A21,B01,B02,B12]) + return Tensor: + tensor([coA01,coA02,A10,coA12,A20,A21,B01,B02,B12]) """ CSS_cc = CombinatorialComplex() incidence_01 = [[] for _ in range(self.n_c1)] incidence_02 = [[] for _ in range(self.n_c2)] - for i in range(self.n_c0): - for j in range(self.n_c1): + for i in range(self.n_c0): + for j in range(self.n_c1): if hx[i][j] == 1: incidence_01[j].append(i) - - for i in range(self.n_c2): - for j in range(self.n_c1): + for i in range(self.n_c2): + for j in range(self.n_c1): if hz[i][j] == 1: - incidence_02[i].append(j) + incidence_02[i].append(j) - for i in range(self.n_c1): CSS_cc.add_cell(incidence_01[i], rank=1) for i in range(self.n_c2): CSS_cc.add_cell(incidence_02[i], rank=2) - + return [ - torch.tensor(CSS_cc.coadjacency_matrix(1,0).todense()), - torch.tensor(CSS_cc.coadjacency_matrix(2,0).todense()), - torch.tensor(CSS_cc.adjacency_matrix(1,0).todense()), - torch.tensor(CSS_cc.coadjacency_matrix(2,1).todense()), - torch.tensor(CSS_cc.adjacency_matrix(2,0).todense()), - torch.tensor(CSS_cc.adjacency_matrix(2,1).todense()), - torch.tensor(CSS_cc.boundary_matrix(0,1).todense()), - torch.tensor(CSS_cc.boundary_matrix(0,2).todense()), - torch.tensor(CSS_cc.boundary_matrix(1,2).todense()) + torch.tensor(CSS_cc.coadjacency_matrix(1, 0).todense()), + torch.tensor(CSS_cc.coadjacency_matrix(2, 0).todense()), + torch.tensor(CSS_cc.adjacency_matrix(1, 0).todense()), + torch.tensor(CSS_cc.coadjacency_matrix(2, 1).todense()), + torch.tensor(CSS_cc.adjacency_matrix(2, 0).todense()), + torch.tensor(CSS_cc.adjacency_matrix(2, 1).todense()), + torch.tensor(CSS_cc.boundary_matrix(0, 1).todense()), + torch.tensor(CSS_cc.boundary_matrix(0, 2).todense()), + torch.tensor(CSS_cc.boundary_matrix(1, 2).todense()), ] -if __name__ == '__main__': - - Hx = [[1,1,1,0,0,0,1,0],[0,0,1,0,1,1,1,0],[1,1,0,1,0,0,0,1],[0,0,0,1,1,1,0,1]] - Hz = [[0,1,0,0,1,0,1,1],[0,1,1,1,1,0,0,0],[1,0,0,0,0,1,1,1],[1,0,1,1,0,1,0,0]] - encoder = CSSEncoder(8,4,4,mode='graph',grakel_use=True) - class c(): - def __init__(self,hx,hz): +if __name__ == "__main__": + Hx = [ + [1, 1, 1, 0, 0, 0, 1, 0], + [0, 0, 1, 0, 1, 1, 1, 0], + [1, 1, 0, 1, 0, 0, 0, 1], + [0, 0, 0, 1, 1, 1, 0, 1], + ] + Hz = [ + [0, 1, 0, 0, 1, 0, 1, 1], + [0, 1, 1, 1, 1, 0, 0, 0], + [1, 0, 0, 0, 0, 1, 1, 1], + [1, 0, 1, 1, 0, 1, 0, 0], + ] + encoder = CSSEncoder(8, 4, 4, mode="graph", grakel_use=True) + + class c: + def __init__(self, hx, hz): self.hx = hx - self.hz=hz - relations = encoder.encode(c(hx= Hx,hz= Hz)) + self.hz = hz + + relations = encoder.encode(c(hx=Hx, hz=Hz)) print(type(relations)) # print(relations.dictionary) # print(relations) # print(type(relations[0])) - diff --git a/bayesian_optimization/gadget.py b/bayesian_optimization/gadget.py index d816605..6dac515 100644 --- a/bayesian_optimization/gadget.py +++ b/bayesian_optimization/gadget.py @@ -1,6 +1,7 @@ from typing import Optional, Sequence, Tuple, List import numpy as np + try: import torch except Exception: @@ -10,36 +11,41 @@ try: import scipy.sparse as sp import scipy.sparse.linalg as spla + _HAS_SCIPY = True except Exception: _HAS_SCIPY = False class ChainComplexGlobalFeaturesEncoder: - - - def __init__(self, - return_torch: bool = True, - dtype: Optional["torch.dtype"] = None, - device: Optional["torch.device"] = None, - max_degree_bin: int = 10, - quantiles: Sequence[float] = (0.10, 0.25, 0.50, 0.75, 0.90), - # SVD options - spectral_backend: str = "auto", # "auto" | "scipy" | "power" - power_iters: int = 200, - power_tol: float = 1e-6, - power_seed: int = 0, - # two-step & NB options - eig_iters: int = 200, - eig_tol: float = 1e-6, - nb_iters: int = 200, - nb_tol: float = 1e-6, - nb_seed: int = 1234, - # Hodge options - hodge_k: int = 8, - hodge_dense_threshold: int = 2048): + def __init__( + self, + return_torch: bool = True, + dtype: Optional["torch.dtype"] = None, + device: Optional["torch.device"] = None, + max_degree_bin: int = 10, + quantiles: Sequence[float] = (0.10, 0.25, 0.50, 0.75, 0.90), + # SVD options + spectral_backend: str = "auto", # "auto" | "scipy" | "power" + power_iters: int = 200, + power_tol: float = 1e-6, + power_seed: int = 0, + # two-step & NB options + eig_iters: int = 200, + eig_tol: float = 1e-6, + nb_iters: int = 200, + nb_tol: float = 1e-6, + nb_seed: int = 1234, + # Hodge options + hodge_k: int = 8, + hodge_dense_threshold: int = 2048, + ): self.return_torch = return_torch and (torch is not None) - self.dtype = dtype if dtype is not None else (torch.float32 if self.return_torch else np.float32) + self.dtype = ( + dtype + if dtype is not None + else (torch.float32 if self.return_torch else np.float32) + ) self.device = device self.max_degree_bin = int(max_degree_bin) self.quantiles = tuple(float(q) for q in quantiles) @@ -64,7 +70,7 @@ def __init__(self, # ---------- degrees ---------- @staticmethod def _np_deg_arrays(h: np.ndarray): - Hnz = (h != 0) + Hnz = h != 0 var_deg = Hnz.sum(axis=0).astype(np.int64, copy=False).ravel() chk_deg = Hnz.sum(axis=1).astype(np.int64, copy=False).ravel() return var_deg, chk_deg @@ -98,25 +104,38 @@ def _sigma2_power(H: np.ndarray, iters: int, tol: float, seed: int) -> float: if m == 0 or n == 0: return 0.0 rng = np.random.default_rng(seed) - def AtA(v): return H.T @ (H @ v) - v = rng.standard_normal(n); v /= (np.linalg.norm(v) + 1e-12) + def AtA(v): + return H.T @ (H @ v) + + v = rng.standard_normal(n) + v /= np.linalg.norm(v) + 1e-12 lam_old = 0.0 for _ in range(iters): - w = AtA(v); nrm = np.linalg.norm(w) - if nrm < 1e-14: return 0.0 - v = w / nrm; lam = float(v @ AtA(v)) - if abs(lam - lam_old) <= tol * max(1.0, abs(lam)): break + w = AtA(v) + nrm = np.linalg.norm(w) + if nrm < 1e-14: + return 0.0 + v = w / nrm + lam = float(v @ AtA(v)) + if abs(lam - lam_old) <= tol * max(1.0, abs(lam)): + break lam_old = lam v1 = v.copy() - v = rng.standard_normal(n); v -= (v @ v1) * v1 - v /= (np.linalg.norm(v) + 1e-12) + v = rng.standard_normal(n) + v -= (v @ v1) * v1 + v /= np.linalg.norm(v) + 1e-12 lam_old = 0.0 for _ in range(iters): - w = AtA(v); w -= (w @ v1) * v1; nrm = np.linalg.norm(w) - if nrm < 1e-14: return 0.0 - v = w / nrm; lam = float(v @ AtA(v)) - if abs(lam - lam_old) <= tol * max(1.0, abs(lam)): break + w = AtA(v) + w -= (w @ v1) * v1 + nrm = np.linalg.norm(w) + if nrm < 1e-14: + return 0.0 + v = w / nrm + lam = float(v @ AtA(v)) + if abs(lam - lam_old) <= tol * max(1.0, abs(lam)): + break lam_old = lam return float(np.sqrt(max(lam, 0.0))) @@ -131,30 +150,40 @@ def _sigma2_scipy(H: np.ndarray) -> float: s = np.sort(np.array(s, dtype=np.float64)) return float(max(s[-2] if s.size >= 2 else 0.0, 0.0)) except Exception: - return ChainComplexGlobalFeaturesEncoder._sigma2_power(H, iters=200, tol=1e-6, seed=0) + return ChainComplexGlobalFeaturesEncoder._sigma2_power( + H, iters=200, tol=1e-6, seed=0 + ) def _sigma2_pair(self, Hx: np.ndarray, Hz: np.ndarray) -> Tuple[float, float]: Hx_bin = self._binarize_float(Hx) Hz_bin = self._binarize_float(Hz) - use_scipy = (_HAS_SCIPY and self.spectral_backend in ("auto", "scipy")) + use_scipy = _HAS_SCIPY and self.spectral_backend in ("auto", "scipy") if use_scipy: return self._sigma2_scipy(Hx_bin), self._sigma2_scipy(Hz_bin) - return (self._sigma2_power(Hx_bin, self.power_iters, self.power_tol, self.power_seed), - self._sigma2_power(Hz_bin, self.power_iters, self.power_tol, self.power_seed + 1)) + return ( + self._sigma2_power( + Hx_bin, self.power_iters, self.power_tol, self.power_seed + ), + self._sigma2_power( + Hz_bin, self.power_iters, self.power_tol, self.power_seed + 1 + ), + ) # ---------- lambda2 on variable–variable 2-step ---------- @staticmethod def _build_varvar_norm_adj_sparse(H: np.ndarray): Hs = sp.csr_matrix((H != 0).astype(np.int8)) A = Hs.T @ Hs - A.setdiag(0); A.eliminate_zeros() + A.setdiag(0) + A.eliminate_zeros() if A.shape[0] == 0: return sp.csr_matrix((0, 0)), np.array([], dtype=bool) deg = np.array(A.sum(axis=1)).ravel() mask = deg > 0 if mask.sum() == 0: return sp.csr_matrix((0, 0)), mask - A = A[mask][:, mask]; deg = deg[mask] + A = A[mask][:, mask] + deg = deg[mask] dinv = 1.0 / np.sqrt(deg) A = A.tocoo() data = A.data * dinv[A.row] * dinv[A.col] @@ -162,27 +191,41 @@ def _build_varvar_norm_adj_sparse(H: np.ndarray): return An, mask @staticmethod - def _eig2_symmetric_power_dense(An: np.ndarray, iters: int, tol: float, seed: int) -> float: + def _eig2_symmetric_power_dense( + An: np.ndarray, iters: int, tol: float, seed: int + ) -> float: n = An.shape[0] - if n == 0: return 0.0 + if n == 0: + return 0.0 rng = np.random.default_rng(seed) - v = rng.standard_normal(n); v /= (np.linalg.norm(v) + 1e-12) + v = rng.standard_normal(n) + v /= np.linalg.norm(v) + 1e-12 lam_old = 0.0 for _ in range(iters): - w = An @ v; nrm = np.linalg.norm(w) - if nrm < 1e-14: return 0.0 - v = w / nrm; lam = float(v @ (An @ v)) - if abs(lam - lam_old) <= tol * max(1.0, abs(lam)): break + w = An @ v + nrm = np.linalg.norm(w) + if nrm < 1e-14: + return 0.0 + v = w / nrm + lam = float(v @ (An @ v)) + if abs(lam - lam_old) <= tol * max(1.0, abs(lam)): + break lam_old = lam v1 = v.copy() - v = rng.standard_normal(n); v -= (v @ v1) * v1 - v /= (np.linalg.norm(v) + 1e-12) + v = rng.standard_normal(n) + v -= (v @ v1) * v1 + v /= np.linalg.norm(v) + 1e-12 lam_old = 0.0 for _ in range(iters): - w = An @ v; w -= (w @ v1) * v1; nrm = np.linalg.norm(w) - if nrm < 1e-14: return 0.0 - v = w / nrm; lam = float(v @ (An @ v)) - if abs(lam - lam_old) <= tol * max(1.0, abs(lam)): break + w = An @ v + w -= (w @ v1) * v1 + nrm = np.linalg.norm(w) + if nrm < 1e-14: + return 0.0 + v = w / nrm + lam = float(v @ (An @ v)) + if abs(lam - lam_old) <= tol * max(1.0, abs(lam)): + break lam_old = lam return float(min(1.0, max(-1.0, lam))) @@ -195,28 +238,42 @@ def _lambda2_varvar(self, H: np.ndarray) -> float: try: vals = spla.eigsh(An, k=2, which="LM", return_eigenvectors=False) vals = np.sort(np.abs(vals)) - return float(min(1.0, max(0.0, vals[-2]))) if vals.size >= 2 else float(vals[-1]) + return ( + float(min(1.0, max(0.0, vals[-2]))) + if vals.size >= 2 + else float(vals[-1]) + ) except Exception: Ad = An.toarray() - return self._eig2_symmetric_power_dense(Ad, self.eig_iters, self.eig_tol, seed=42) + return self._eig2_symmetric_power_dense( + Ad, self.eig_iters, self.eig_tol, seed=42 + ) else: A = Hbin.T @ Hbin np.fill_diagonal(A, 0.0) deg = A.sum(axis=1) mask = deg > 0 - if not mask.any(): return 0.0 - A = A[np.ix_(mask, mask)]; deg = deg[mask] + if not mask.any(): + return 0.0 + A = A[np.ix_(mask, mask)] + deg = deg[mask] dinv = 1.0 / np.sqrt(deg) An = (A * dinv).T * dinv - return self._eig2_symmetric_power_dense(An, self.eig_iters, self.eig_tol, seed=42) + return self._eig2_symmetric_power_dense( + An, self.eig_iters, self.eig_tol, seed=42 + ) - def _lambda2_varvar_pair(self, Hx: np.ndarray, Hz: np.ndarray) -> Tuple[float, float]: + def _lambda2_varvar_pair( + self, Hx: np.ndarray, Hz: np.ndarray + ) -> Tuple[float, float]: return self._lambda2_varvar(Hx), self._lambda2_varvar(Hz) # ---------- non-backtracking spectral radius ---------- @staticmethod - def _nb_build_edge_lists(H: np.ndarray) -> Tuple[List[List[int]], List[List[int]], np.ndarray, np.ndarray]: - Hnz = (H != 0) + def _nb_build_edge_lists( + H: np.ndarray, + ) -> Tuple[List[List[int]], List[List[int]], np.ndarray, np.ndarray]: + Hnz = H != 0 m, n = Hnz.shape N_v = [list(np.where(Hnz[:, j])[0]) for j in range(n)] N_c = [list(np.where(Hnz[i, :])[0]) for i in range(m)] @@ -224,50 +281,69 @@ def _nb_build_edge_lists(H: np.ndarray) -> Tuple[List[List[int]], List[List[int] chk_idx = [] for j in range(n): for i in N_v[j]: - var_idx.append(j); chk_idx.append(i) - return N_v, N_c, np.asarray(var_idx, dtype=np.int64), np.asarray(chk_idx, dtype=np.int64) + var_idx.append(j) + chk_idx.append(i) + return ( + N_v, + N_c, + np.asarray(var_idx, dtype=np.int64), + np.asarray(chk_idx, dtype=np.int64), + ) def _nonbacktracking_rho(self, H: np.ndarray) -> float: N_v, N_c, var_idx, chk_idx = self._nb_build_edge_lists(H) E = var_idx.size - if E == 0: return 0.0 + if E == 0: + return 0.0 rng = np.random.default_rng(self.nb_seed) - a = rng.standard_normal(E); b = rng.standard_normal(E) + a = rng.standard_normal(E) + b = rng.standard_normal(E) edges_of_var = [[] for _ in range(len(N_v))] edges_of_chk = [[] for _ in range(len(N_c))] for e in range(E): - v = var_idx[e]; c = chk_idx[e] - edges_of_var[v].append(e); edges_of_chk[c].append(e) + v = var_idx[e] + c = chk_idx[e] + edges_of_var[v].append(e) + edges_of_chk[c].append(e) def step(a, b): sum_a_c = np.zeros(len(N_c)) for c, edges in enumerate(edges_of_chk): - if edges: sum_a_c[c] = a[edges].sum() + if edges: + sum_a_c[c] = a[edges].sum() new_b = sum_a_c[chk_idx] - a sum_b_v = np.zeros(len(N_v)) for v, edges in enumerate(edges_of_var): - if edges: sum_b_v[v] = new_b[edges].sum() + if edges: + sum_b_v[v] = new_b[edges].sum() new_a = sum_b_v[var_idx] - new_b return new_a, new_b prev_norm = np.linalg.norm(np.concatenate([a, b])) if prev_norm < 1e-12: - a[:] = 1.0; b[:] = 1.0; prev_norm = np.linalg.norm(np.concatenate([a, b])) + a[:] = 1.0 + b[:] = 1.0 + prev_norm = np.linalg.norm(np.concatenate([a, b])) rho = 0.0 for _ in range(self.nb_iters): new_a, new_b = step(a, b) y = np.concatenate([new_a, new_b]) y_norm = np.linalg.norm(y) - if y_norm < 1e-18: return 0.0 + if y_norm < 1e-18: + return 0.0 rho_new = y_norm / prev_norm a, b = new_a / y_norm, new_b / y_norm if abs(rho_new - rho) <= self.nb_tol * max(1.0, abs(rho_new)): - rho = rho_new; break - rho = rho_new; prev_norm = 1.0 + rho = rho_new + break + rho = rho_new + prev_norm = 1.0 return float(rho) - def _nonbacktracking_rho_pair(self, Hx: np.ndarray, Hz: np.ndarray) -> Tuple[float, float]: + def _nonbacktracking_rho_pair( + self, Hx: np.ndarray, Hz: np.ndarray + ) -> Tuple[float, float]: return self._nonbacktracking_rho(Hx), self._nonbacktracking_rho(Hz) # ---------- 4-cycle counts ---------- @@ -281,7 +357,8 @@ def _count_c4(H: np.ndarray) -> int: if _HAS_SCIPY: Hs = sp.csr_matrix(Hbin) B = (Hs.T @ Hs).tocsr() - B.setdiag(0); B.eliminate_zeros() + B.setdiag(0) + B.eliminate_zeros() # use only upper triangle Bu = sp.triu(B, k=1).tocoo() v = Bu.data.astype(np.int64, copy=False) @@ -302,18 +379,23 @@ def _count_c4_mixed(Hx: np.ndarray, Hz: np.ndarray) -> int: X = (Hx != 0).astype(np.int64, copy=False) Z = (Hz != 0).astype(np.int64, copy=False) if _HAS_SCIPY: - Xs = sp.csr_matrix(X); Zs = sp.csr_matrix(Z) - BX = (Xs.T @ Xs).tocsr(); BZ = (Zs.T @ Zs).tocsr() + Xs = sp.csr_matrix(X) + Zs = sp.csr_matrix(Z) + BX = (Xs.T @ Xs).tocsr() + BZ = (Zs.T @ Zs).tocsr() for M in (BX, BZ): - M.setdiag(0); M.eliminate_zeros() + M.setdiag(0) + M.eliminate_zeros() # upper triangle product BXu = sp.triu(BX, k=1).tocoo() # Pull corresponding entries from BZ (sparse gather) vals = BZ.tocsr()[BXu.row, BXu.col].A.ravel().astype(np.int64, copy=False) return int(np.sum(BXu.data.astype(np.int64) * vals)) else: - BX = X.T @ X; BZ = Z.T @ Z - np.fill_diagonal(BX, 0); np.fill_diagonal(BZ, 0) + BX = X.T @ X + BZ = Z.T @ Z + np.fill_diagonal(BX, 0) + np.fill_diagonal(BZ, 0) iu = np.triu_indices(BX.shape[0], k=1) return int(np.sum(BX[iu] * BZ[iu])) @@ -334,8 +416,9 @@ def _hodge_L1_small_eigs(self, Hx: np.ndarray, Hz: np.ndarray) -> np.ndarray: k = min(self.hodge_k, max(1, n - 1)) # eigsh requires k < n if _HAS_SCIPY: - Xs = sp.csr_matrix(HxF); Zs = sp.csr_matrix(HzF) - L1 = (Xs.T @ Xs) + (Zs.T @ Zs) # n x n sparse, PSD + Xs = sp.csr_matrix(HxF) + Zs = sp.csr_matrix(HzF) + L1 = (Xs.T @ Xs) + (Zs.T @ Zs) # n x n sparse, PSD try: vals = spla.eigsh(L1, k=k, which="SA", return_eigenvectors=False) vals = np.sort(np.maximum(vals, 0.0)) @@ -353,7 +436,9 @@ def _hodge_L1_small_eigs(self, Hx: np.ndarray, Hz: np.ndarray) -> np.ndarray: else: # crude randomized subspace iteration for smallest (shift by epsilon) eps = 1e-6 - M = np.linalg.pinv(L1 + eps * np.eye(n)) # inverse approximates smallest of L1 + M = np.linalg.pinv( + L1 + eps * np.eye(n) + ) # inverse approximates smallest of L1 # now largest eigenvalues of M ≈ 1/(smallest of L1) r = min(k, 8) V = np.random.default_rng(0).standard_normal((n, r)) @@ -365,7 +450,7 @@ def _hodge_L1_small_eigs(self, Hx: np.ndarray, Hz: np.ndarray) -> np.ndarray: # pad / cast out = np.zeros(self.hodge_k, dtype=np.float32) - out[:len(vals)] = vals.astype(np.float32) + out[: len(vals)] = vals.astype(np.float32) return out # ---------- main encode ---------- @@ -374,7 +459,8 @@ def encode(self, c) -> "np.ndarray | torch.Tensor": slices = {} # Base - n = int(c.n); k = int(c.k) + n = int(c.n) + k = int(c.k) rank_hx = int(c.gf2_rank(c.hx)) rank_hz = int(c.gf2_rank(c.hz)) base = np.array([n, k, rank_hx, rank_hz], dtype=np.float32) @@ -393,38 +479,47 @@ def encode(self, c) -> "np.ndarray | torch.Tensor": ("cZ_hist", self._hist_node_perspective(cz)), ("vM_hist", self._hist_node_perspective(vmerge)), ]: - slices[key] = (len(feats), len(feats) + arr.size); feats.append(arr) + slices[key] = (len(feats), len(feats) + arr.size) + feats.append(arr) for key, arr in [ - ("vX_q", self._quantiles(vx)), ("cX_q", self._quantiles(cx)), - ("vZ_q", self._quantiles(vz)), ("cZ_q", self._quantiles(cz)), + ("vX_q", self._quantiles(vx)), + ("cX_q", self._quantiles(cx)), + ("vZ_q", self._quantiles(vz)), + ("cZ_q", self._quantiles(cz)), ("vM_q", self._quantiles(vmerge)), ]: - slices[key] = (len(feats), len(feats) + arr.size); feats.append(arr) + slices[key] = (len(feats), len(feats) + arr.size) + feats.append(arr) # Spectral: sigma2 s2x, s2z = self._sigma2_pair(c.hx, c.hz) spec_sigma2 = np.array([s2x, s2z], dtype=np.float32) - slices["spec_sigma2"] = (len(feats), len(feats) + spec_sigma2.size); feats.append(spec_sigma2) + slices["spec_sigma2"] = (len(feats), len(feats) + spec_sigma2.size) + feats.append(spec_sigma2) # Spectral: lambda2 (var–var) l2x, l2z = self._lambda2_varvar_pair(c.hx, c.hz) spec_l2vv = np.array([l2x, l2z], dtype=np.float32) - slices["spec_lambda2_vv"] = (len(feats), len(feats) + spec_l2vv.size); feats.append(spec_l2vv) + slices["spec_lambda2_vv"] = (len(feats), len(feats) + spec_l2vv.size) + feats.append(spec_l2vv) # Spectral: non-backtracking rho rho_x, rho_z = self._nonbacktracking_rho_pair(c.hx, c.hz) spec_nb = np.array([rho_x, rho_z], dtype=np.float32) - slices["spec_nb_rho"] = (len(feats), len(feats) + spec_nb.size); feats.append(spec_nb) + slices["spec_nb_rho"] = (len(feats), len(feats) + spec_nb.size) + feats.append(spec_nb) # 4-cycles: [X, Z, mixed] c4x, c4z, c4m = self._count_c4_all(c.hx, c.hz) c4 = np.array([c4x, c4z, c4m], dtype=np.float32) - slices["cycles_4"] = (len(feats), len(feats) + c4.size); feats.append(c4) + slices["cycles_4"] = (len(feats), len(feats) + c4.size) + feats.append(c4) # Hodge / L1 smallest eigenvalues l1_small = self._hodge_L1_small_eigs(c.hx, c.hz) - slices["hodge_L1_small"] = (len(feats), len(feats) + l1_small.size); feats.append(l1_small) + slices["hodge_L1_small"] = (len(feats), len(feats) + l1_small.size) + feats.append(l1_small) vec = np.concatenate(feats, dtype=np.float32) self.last_slices = slices diff --git a/bayesian_optimization/gp.py b/bayesian_optimization/gp.py index 0e1b855..0401d65 100644 --- a/bayesian_optimization/gp.py +++ b/bayesian_optimization/gp.py @@ -1,6 +1,7 @@ """ - This file defines the Gaussian Process model used in Bayesian Optimization +This file defines the Gaussian Process model used in Bayesian Optimization """ + from typing import Optional import torch import torch.nn as nn @@ -10,89 +11,106 @@ from gpytorch.distributions import MultivariateNormal from gpytorch.kernels import RBFKernel, ScaleKernel from gpytorch.likelihoods import GaussianLikelihood -from gpytorch.means import ConstantMean,LinearMean from gpytorch.models import ExactGP from torch import Tensor from gpytorch.priors import LogNormalPrior from gpytorch.constraints import Interval -from gpytorch.kernels import RBFKernel, ScaleKernel -from gpytorch.likelihoods import GaussianLikelihood import math -device='cuda' + +device = "cuda" + class NNMean(Mean): def __init__(self, input_size, hidden=256): super().__init__() self.net = nn.Sequential( - nn.Linear(input_size, hidden), - nn.LeakyReLU(0.1), - nn.Linear(hidden, 1) + nn.Linear(input_size, hidden), nn.LeakyReLU(0.1), nn.Linear(hidden, 1) ) + def forward(self, x): return self.net(x).squeeze(-1) - -class GaussianProcess_QEC_(ExactGP, GPyTorchModel): +class GaussianProcess_QEC_(ExactGP, GPyTorchModel): _num_outputs = 1 # to inform GPyTorchModel API - def __init__(self, train_X, train_Y, kernel=None,encoder = None,embedding = None,train_Yvar: Optional[Tensor] = None,mean = 'linear',grakel=False,mean_input = 16,encoder2 = None,mean_module=None): + def __init__( + self, + train_X, + train_Y, + kernel=None, + encoder=None, + embedding=None, + train_Yvar: Optional[Tensor] = None, + mean="linear", + grakel=False, + mean_input=16, + encoder2=None, + mean_module=None, + ): # NOTE: This ignores train_Yvar and uses inferred noise instead. # squeeze output dim before passing train_Y to ExactGP - likelihood = likelihood = GaussianLikelihood( - noise_prior=LogNormalPrior(math.log(1e-2), 0.5), - noise_constraint=Interval(1e-6, 1.0) + likelihood = likelihood = GaussianLikelihood( + noise_prior=LogNormalPrior(math.log(1e-2), 0.5), + noise_constraint=Interval(1e-6, 1.0), ) - super().__init__(train_X, train_Y.squeeze(-1),likelihood) + super().__init__(train_X, train_Y.squeeze(-1), likelihood) self.grakel = grakel - if encoder==None: + if encoder == None: + def encode_(x): return x + self.encode = encode_ else: self.encode = encoder - if encoder2==None: + if encoder2 == None: + def encode_(x): return x + self.encode2 = encode_ else: self.encode2 = encoder2 - if embedding==None: + if embedding == None: + def embed_(x): return x + self.embed = embed_ else: self.embed = embedding - if mean in ['linear']: + if mean in ["linear"]: self.mean_module = LinearMean(mean_input) - elif mean in ['nn','NN']: + elif mean in ["nn", "NN"]: self.mean_module = NNMean(input_size=mean_input, hidden=64) - elif mean in ['given']: + elif mean in ["given"]: self.mean_module = mean_module - else : + else: self.mean_module = ConstantMean() self.covar_module = kernel - self.to(train_X) + self.to(train_X) def forward(self, x): - + # print(type(feats),len(feats)) # print(feats) # print('end printing') # print("feats mean/std:", feats.mean().item(), feats.std().item()) - if self.grakel==True: - mean_x = self.mean_module(self.encode2(x)).squeeze(-1) + if self.grakel == True: + mean_x = self.mean_module(self.encode2(x)).squeeze(-1) feats = self.embed(self.encode(x)) - else: + else: feats = self.embed(self.encode(x)) - mean_x = self.mean_module(feats) + mean_x = self.mean_module(feats) covar_x = self.covar_module(feats) - + # print(f'covar_x:{type(covar_x)},{covar_x}') - + return MultivariateNormal(mean_x, covar_x) + class GaussianProcess_QEC(gpytorch.models.ExactGP): """ ExactGP that: @@ -101,31 +119,41 @@ class GaussianProcess_QEC(gpytorch.models.ExactGP): --> GP mean(z), cov(z) """ - def __init__(self, train_x, train_y, - *, - likelihood: gpytorch.likelihoods.GaussianLikelihood, - kernel: gpytorch.kernels.Kernel, - encoder, # callable: encode(X_batch) -> list of views - embed: nn.Module, # ChainComplexEmbedder - mean: str = 'constant', - mean_input: int = 64): + def __init__( + self, + train_x, + train_y, + *, + likelihood: gpytorch.likelihoods.GaussianLikelihood, + kernel: gpytorch.kernels.Kernel, + encoder, # callable: encode(X_batch) -> list of views + embed: nn.Module, # ChainComplexEmbedder + mean: str = "constant", + mean_input: int = 64, + ): # train_y must be 1D for ExactGP; we'll squeeze in caller - super().__init__(train_x, train_y.squeeze(-1) if train_y.dim() == 2 else train_y, likelihood) - self.encoder = encoder # Python callable (non-differentiable wrt X) - self.embed = embed # nn.Module on device + super().__init__( + train_x, train_y.squeeze(-1) if train_y.dim() == 2 else train_y, likelihood + ) + self.encoder = encoder # Python callable (non-differentiable wrt X) + self.embed = embed # nn.Module on device self.embed.to(device) - d_model = next(self.embed.parameters()).shape[-1] if hasattr(self.embed, 'parameters') else 128 - d_model = getattr(self.embed, 'd_model', d_model) + d_model = ( + next(self.embed.parameters()).shape[-1] + if hasattr(self.embed, "parameters") + else 128 + ) + d_model = getattr(self.embed, "d_model", d_model) # Mean module - if mean == 'zero': + if mean == "zero": self.mean_module = gpytorch.means.ZeroMean() - elif mean == 'constant': + elif mean == "constant": self.mean_module = gpytorch.means.ConstantMean() - elif mean == 'linear': + elif mean == "linear": self.mean_module = gpytorch.means.LinearMean(input_size=d_model) - elif mean == 'nn': + elif mean == "nn": # Small MLP mean head on top of z self.nn_mean = nn.Sequential( nn.LayerNorm(d_model), @@ -133,12 +161,16 @@ def __init__(self, train_x, train_y, nn.GELU(), nn.Linear(mean_input, 1), ).to(device) + # wrap to a Mean that calls nn_mean (simple lambda-style) class _NNMean(gpytorch.means.Mean): - def __init__(self, head): - super().__init__(); self.head = head - def forward(self, x): # x: (N, d_model) + def __init__(self, head): + super().__init__() + self.head = head + + def forward(self, x): # x: (N, d_model) return self.head(x).squeeze(-1) + self.mean_module = _NNMean(self.nn_mean) else: self.mean_module = gpytorch.means.ConstantMean() @@ -155,10 +187,12 @@ def _x_to_z(self, X: torch.Tensor) -> torch.Tensor: Convert a batch of design vectors X (B, D) to embedding z (B, d_model). """ # Move to CPU numpy for code constructor if needed - X_np = X.detach().cpu().numpy() if isinstance(X, torch.Tensor) else np.asarray(X) - sample_views_list = self.encoder(X_np) # list of views (len=B) + X_np = ( + X.detach().cpu().numpy() if isinstance(X, torch.Tensor) else np.asarray(X) + ) + sample_views_list = self.encoder(X_np) # list of views (len=B) # embedder.forward(list) packs internally and returns (B, d_model) - z = self.embed(sample_views_list) # (B, d_model) on device + z = self.embed(sample_views_list) # (B, d_model) on device return z def forward(self, X: torch.Tensor) -> MultivariateNormal: @@ -166,26 +200,27 @@ def forward(self, X: torch.Tensor) -> MultivariateNormal: Return latent f(X) distribution (MVN). GPyTorch's likelihood wraps this to p(y|X). """ X = X.to(device) - z = self._x_to_z(X) # (N, d_model) + z = self._x_to_z(X) # (N, d_model) # cache self._cache_last_inputs = X self._cache_last_z = z - mean_x = self.mean_module(z) # (N,) - covar_x = self.covar_module(z) # kernel on z + mean_x = self.mean_module(z) # (N,) + covar_x = self.covar_module(z) # kernel on z return MultivariateNormal(mean_x, covar_x) # 若你更喜欢 model.posterior(X) 的接口,可以保留: def posterior(self, X: torch.Tensor): - self.eval(); self.likelihood.eval() + self.eval() + self.likelihood.eval() with torch.no_grad(), gpytorch.settings.fast_pred_var(): # predictive distribution of y return self.likelihood(self(X.to(device))) - -if __name__ == '__main__': + + +if __name__ == "__main__": import torch import numpy as np - import matplotlib.pyplot as plt import gpytorch from gpytorch.kernels import ScaleKernel, RBFKernel from gpytorch.distributions import MultivariateNormal @@ -193,6 +228,7 @@ def posterior(self, X: torch.Tensor): from gpytorch.likelihoods import GaussianLikelihood from gpytorch.mlls import ExactMarginalLogLikelihood from torch.optim import Adam + def train_gpytorch_model(model, train_x, train_y, training_iter=100, lr=0.1): model.train() model.likelihood.train() @@ -206,6 +242,7 @@ def train_gpytorch_model(model, train_x, train_y, training_iter=100, lr=0.1): loss.backward() optimizer.step() return model + n_train = 20 train_x = torch.linspace(0, 1, n_train).unsqueeze(-1) train_y = torch.sin(train_x.squeeze() * 2 * np.pi) + 0.2 * torch.randn(n_train) @@ -235,4 +272,4 @@ def train_gpytorch_model(model, train_x, train_y, training_iter=100, lr=0.1): # 4. Print first few predictions print("First 10 mean predictions:\n", mean[:10].numpy()) - print("First 10 stddev predictions:\n", stddev[:10].numpy()) \ No newline at end of file + print("First 10 stddev predictions:\n", stddev[:10].numpy()) diff --git a/bayesian_optimization/graphembedding.py b/bayesian_optimization/graphembedding.py index 0af6876..2720ff7 100644 --- a/bayesian_optimization/graphembedding.py +++ b/bayesian_optimization/graphembedding.py @@ -1,69 +1,73 @@ -from gpytorch.kernels import Kernel,RBFKernel,ScaleKernel import torch from torch import nn -from torch.nn import ReLU,Sequential import torch.nn.functional as F -from collections import defaultdict, Counter -import networkx as nx -import torch.nn.functional as F -from torch_geometric.nn import GCNConv,global_mean_pool -from topomodelx.nn.combinatorial.hmc import HMC +from torch_geometric.nn import GCNConv, global_mean_pool from torch_geometric.utils import dense_to_sparse from torch_geometric.data import Data, Batch -from topomodelx.nn.combinatorial.hmc import HMCLayer -from torch_geometric.nn import TransformerConv, global_mean_pool -from torch_geometric.nn import GINConv, global_mean_pool, BatchNorm +from torch_geometric.nn import TransformerConv +from torch_geometric.nn import GINConv + + class GraphRegressor(nn.Module): - def __init__(self,n,nx,nz,hidden_channel = 128, embedding_mode='GIN'): + def __init__(self, n, nx, nz, hidden_channel=128, embedding_mode="GIN"): super().__init__() self.embedding_mode = embedding_mode - if self.embedding_mode in ['GIN']: - self.embedder = GINEmbedder(n,nx,nz,hidden_channel) - elif self.embedding_mode in ['GT']: + if self.embedding_mode in ["GIN"]: + self.embedder = GINEmbedder(n, nx, nz, hidden_channel) + elif self.embedding_mode in ["GT"]: self.embedder = GraphTransformerEmbedder(n, nx, nz, hidden_dim=128) - elif self.embedding_mode in ['GCN']: - self.embedder = GCNEmbedder(n,nx,nz,hidden_channel) + elif self.embedding_mode in ["GCN"]: + self.embedder = GCNEmbedder(n, nx, nz, hidden_channel) self.fc = nn.Sequential( nn.Linear(hidden_channel, 256), - nn.BatchNorm1d(256), + nn.BatchNorm1d(256), nn.LeakyReLU(0.1), nn.Linear(256, 512), - nn.BatchNorm1d(512), + nn.BatchNorm1d(512), nn.LeakyReLU(0.1), - nn.Linear(512,1) - ) - def forward(self,x): + nn.Linear(512, 1), + ) + + def forward(self, x): embedding = self.embedder(x) return self.fc(embedding) + + class GraphEmbedderforGP(nn.Module): - def __init__(self,n,nx,nz,hidden_channel = 128, embedding_mode='GIN'): + def __init__(self, n, nx, nz, hidden_channel=128, embedding_mode="GIN"): super().__init__() self.embedding_mode = embedding_mode - if self.embedding_mode in ['GIN']: - self.embedder = GINEmbedder(n,nx,nz,hidden_channel) - elif self.embedding_mode in ['GT']: - self.embedder = GraphTransformerEmbedder(n, nx, nz, hidden_dim=hidden_channel) - elif self.embedding_mode in ['GCN']: - self.embedder = GCNEmbedder(n,nx,nz,hidden_channel) + if self.embedding_mode in ["GIN"]: + self.embedder = GINEmbedder(n, nx, nz, hidden_channel) + elif self.embedding_mode in ["GT"]: + self.embedder = GraphTransformerEmbedder( + n, nx, nz, hidden_dim=hidden_channel + ) + elif self.embedding_mode in ["GCN"]: + self.embedder = GCNEmbedder(n, nx, nz, hidden_channel) self.fc = nn.Sequential( nn.Linear(hidden_channel, 64), nn.LeakyReLU(0.1), nn.Linear(64, 128), nn.LeakyReLU(0.1), nn.Linear(128, 64), - ) - def forward(self,x): + ) + + def forward(self, x): embedding = self.embedder(x) return self.fc(embedding) - + class GraphEmbedder(nn.Module): def __init__(self, n, nx, nz): super().__init__() - q = torch.zeros(n, 3); q[:, 0] = 1.0 - x = torch.zeros(nx, 3); x[:, 1] = 1.0 - z = torch.zeros(nz, 3); z[:, 2] = 1.0 - self.register_buffer('single_nodes', torch.cat((q, x, z), dim=0)) + q = torch.zeros(n, 3) + q[:, 0] = 1.0 + x = torch.zeros(nx, 3) + x[:, 1] = 1.0 + z = torch.zeros(nz, 3) + z[:, 2] = 1.0 + self.register_buffer("single_nodes", torch.cat((q, x, z), dim=0)) def batch_transform(self, adjacency_matrices): batch_size = adjacency_matrices.size(0) @@ -75,23 +79,19 @@ def batch_transform(self, adjacency_matrices): return Batch.from_data_list(data_list).to(adjacency_matrices.device) - -from torch_geometric.nn import GINConv, global_mean_pool, GraphNorm -import torch.nn.functional as F from torch import nn -import torch import torch.nn as nn -import torch.nn.functional as F -from torch_geometric.nn import GCNConv, global_mean_pool + + class GCNEmbedder_WithLogicalOperators(nn.Module): - def __init__(self,hidden_dim,num_layers): + def __init__(self, hidden_dim, num_layers): super().__init__() # 1) normalize the raw 3-dim node features self.input_norm = nn.BatchNorm1d(5) # GCN layers and batch norms self.layers = nn.ModuleList() - self.bns = nn.ModuleList() + self.bns = nn.ModuleList() for i in range(num_layers): in_dim = 5 if i == 0 else hidden_dim self.layers.append(GCNConv(in_dim, hidden_dim)) @@ -101,8 +101,9 @@ def __init__(self,hidden_dim,num_layers): self.post_pool_norm = nn.LayerNorm(hidden_dim) self.num_layers = num_layers - def forward(self,batch): - + + def forward(self, batch): + x, edge_index = batch.x, batch.edge_index # --- input normalization --- @@ -122,7 +123,8 @@ def forward(self,batch): # --- embedding normalization --- out = self.post_pool_norm(out) return out # [batch_size, hidden_dim] - + + class GCNEmbedder(GraphEmbedder): def __init__(self, n, nx, nz, hidden_dim=128, num_layers=3): super().__init__(n, nx, nz) @@ -131,7 +133,7 @@ def __init__(self, n, nx, nz, hidden_dim=128, num_layers=3): # GCN layers and batch norms self.layers = nn.ModuleList() - self.bns = nn.ModuleList() + self.bns = nn.ModuleList() for i in range(num_layers): in_dim = 3 if i == 0 else hidden_dim self.layers.append(GCNConv(in_dim, hidden_dim)) @@ -165,6 +167,7 @@ def forward(self, adjacency_matrices): out = self.post_pool_norm(out) return out # [batch_size, hidden_dim] + class GINEmbedder(GraphEmbedder): def __init__(self, n, nx, nz, hidden_dim=128, num_layers=3): super().__init__(n, nx, nz) @@ -172,14 +175,14 @@ def __init__(self, n, nx, nz, hidden_dim=128, num_layers=3): self.input_norm = nn.BatchNorm1d(3) self.layers = nn.ModuleList() - self.bns = nn.ModuleList() + self.bns = nn.ModuleList() for i in range(num_layers): in_dim = 3 if i == 0 else hidden_dim mlp = nn.Sequential( nn.Linear(in_dim, hidden_dim), # ReLU for first layer, LeakyReLU thereafter nn.ReLU() if i == 0 else nn.LeakyReLU(0.1), - nn.Linear(hidden_dim, hidden_dim) + nn.Linear(hidden_dim, hidden_dim), ) self.layers.append(GINConv(mlp)) # you could swap to GraphNorm if you want normalization per-graph: @@ -213,19 +216,15 @@ def forward(self, adjacency_matrices): out = self.post_pool_norm(out) return out # [batch_size, hidden_dim] - - class GraphTransformerEmbedder(GraphEmbedder): - def __init__(self, n, nx, nz, - hidden_dim=128, num_layers=3, - heads=4, dropout=0.1): + def __init__(self, n, nx, nz, hidden_dim=128, num_layers=3, heads=4, dropout=0.1): super().__init__(n, nx, nz) - self.hidden_dim = hidden_dim - self.num_layers = num_layers - self.dropout = dropout - self.heads = heads + self.hidden_dim = hidden_dim + self.num_layers = num_layers + self.dropout = dropout + self.heads = heads # ——— Project input features (3 dims) up to hidden_dim ——— in_dim = self.single_nodes.size(1) @@ -235,35 +234,34 @@ def __init__(self, n, nx, nz, self.convs = nn.ModuleList() # first layer: hidden_dim → hidden_dim self.convs.append( - TransformerConv(hidden_dim, - hidden_dim // heads, - heads=heads, - dropout=dropout) + TransformerConv( + hidden_dim, hidden_dim // heads, heads=heads, dropout=dropout + ) ) # remaining layers for _ in range(num_layers - 1): self.convs.append( - TransformerConv(hidden_dim, - hidden_dim // heads, - heads=heads, - dropout=dropout) + TransformerConv( + hidden_dim, hidden_dim // heads, heads=heads, dropout=dropout + ) ) # ——— per-layer feed-forward + LayerNorm ——— - self.ffn = nn.ModuleList([ - nn.Sequential( - nn.Linear(hidden_dim, hidden_dim), - nn.ReLU(), - nn.Dropout(dropout) - ) for _ in range(num_layers) - ]) - self.layer_norms = nn.ModuleList([ - nn.LayerNorm(hidden_dim) for _ in range(num_layers) - ]) + self.ffn = nn.ModuleList( + [ + nn.Sequential( + nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Dropout(dropout) + ) + for _ in range(num_layers) + ] + ) + self.layer_norms = nn.ModuleList( + [nn.LayerNorm(hidden_dim) for _ in range(num_layers)] + ) def forward(self, adjacency_matrices): # 1) batchify - batch = self.batch_transform(adjacency_matrices) + batch = self.batch_transform(adjacency_matrices) x, edge_ix = batch.x, batch.edge_index # 2) initial projection @@ -272,68 +270,64 @@ def forward(self, adjacency_matrices): # 3) stacked Transformer layers with residuals for i, conv in enumerate(self.convs): h_res = h - h = conv(h, edge_ix) - h = F.dropout(h, p=self.dropout, training=self.training) - h = h + self.ffn[i](h) # FFN residual - h = h + h_res # skip residual - h = self.layer_norms[i](h) - h = F.relu(h) + h = conv(h, edge_ix) + h = F.dropout(h, p=self.dropout, training=self.training) + h = h + self.ffn[i](h) # FFN residual + h = h + h_res # skip residual + h = self.layer_norms[i](h) + h = F.relu(h) # 4) global readout out = global_mean_pool(h, batch.batch) return out # [batch_size, hidden_dim] -import torch + + import torch.nn as nn -import torch.nn.functional as F -from torch_geometric.nn import GATv2Conv, global_mean_pool, global_max_pool, global_add_pool +from torch_geometric.nn import GATv2Conv, global_max_pool, global_add_pool + class eGraphTransformerEmbedder(GraphEmbedder): - def __init__(self, n, nx, nz, - hidden_dim=512, num_layers=6, - heads=8, dropout=0.1): + def __init__(self, n, nx, nz, hidden_dim=512, num_layers=6, heads=8, dropout=0.1): super().__init__(n, nx, nz) - self.hidden_dim = hidden_dim - self.num_layers = num_layers - self.dropout = dropout - self.heads = heads - + self.hidden_dim = hidden_dim + self.num_layers = num_layers + self.dropout = dropout + self.heads = heads - self.single_nodes = torch.randn(n, nx) + self.single_nodes = torch.randn(n, nx) in_dim = self.single_nodes.size(1) - self.input_proj = nn.Linear(in_dim, hidden_dim) + self.convs = nn.ModuleList( + [ + GATv2Conv(hidden_dim, hidden_dim // heads, heads=heads, dropout=dropout) + for _ in range(num_layers) + ] + ) - self.convs = nn.ModuleList([ - GATv2Conv(hidden_dim, hidden_dim // heads, heads=heads, dropout=dropout) - for _ in range(num_layers) - ]) - - - self.ffn = nn.ModuleList([ - nn.Sequential( - nn.Linear(hidden_dim, hidden_dim * 2), - nn.ReLU(), - nn.Linear(hidden_dim * 2, hidden_dim), - nn.ReLU(), - nn.Dropout(dropout) - ) - for _ in range(num_layers) - ]) - self.layer_norms = nn.ModuleList([ - nn.LayerNorm(hidden_dim) for _ in range(num_layers) - ]) + self.ffn = nn.ModuleList( + [ + nn.Sequential( + nn.Linear(hidden_dim, hidden_dim * 2), + nn.ReLU(), + nn.Linear(hidden_dim * 2, hidden_dim), + nn.ReLU(), + nn.Dropout(dropout), + ) + for _ in range(num_layers) + ] + ) + self.layer_norms = nn.ModuleList( + [nn.LayerNorm(hidden_dim) for _ in range(num_layers)] + ) # 全局池化后拼接 -> 最终投影 self.global_proj = nn.Sequential( - nn.Linear(hidden_dim * 3, hidden_dim), - nn.ReLU(), - nn.Dropout(dropout) + nn.Linear(hidden_dim * 3, hidden_dim), nn.ReLU(), nn.Dropout(dropout) ) - def forward(self, adjacency_matrices): batch = self.batch_transform(adjacency_matrices) x, edge_ix = batch.x, batch.edge_index @@ -341,17 +335,16 @@ def forward(self, adjacency_matrices): for i in range(self.num_layers): h_res = h - h = self.convs[i](h, edge_ix) - h = F.dropout(h, p=self.dropout, training=self.training) - h = h + self.ffn[i](h) # FFN residual - h = h + h_res # skip residual - h = self.layer_norms[i](h) - h = F.relu(h) - + h = self.convs[i](h, edge_ix) + h = F.dropout(h, p=self.dropout, training=self.training) + h = h + self.ffn[i](h) # FFN residual + h = h + h_res # skip residual + h = self.layer_norms[i](h) + h = F.relu(h) mean_pool = global_mean_pool(h, batch.batch) - max_pool = global_max_pool(h, batch.batch) - sum_pool = global_add_pool(h, batch.batch) + max_pool = global_max_pool(h, batch.batch) + sum_pool = global_add_pool(h, batch.batch) pooled = torch.cat([mean_pool, max_pool, sum_pool], dim=-1) out = self.global_proj(pooled) # shape: [batch_size, hidden_dim] diff --git a/bayesian_optimization/kernels.py b/bayesian_optimization/kernels.py index a9f2ced..e50ef43 100644 --- a/bayesian_optimization/kernels.py +++ b/bayesian_optimization/kernels.py @@ -1,28 +1,24 @@ """ - This file defines the kernels used in the Bayesian Optimization algorithm +This file defines the kernels used in the Bayesian Optimization algorithm """ -from gpytorch.kernels import Kernel,RBFKernel,ScaleKernel +from gpytorch.kernels import Kernel, RBFKernel, ScaleKernel import torch from torch import nn -from torch.nn import ReLU,Sequential import torch.nn.functional as F from collections import defaultdict, Counter -import networkx as nx -from torch_geometric.nn import GCNConv,global_mean_pool +from torch_geometric.nn import GCNConv, global_mean_pool from topomodelx.nn.combinatorial.hmc import HMC from torch_geometric.utils import dense_to_sparse from torch_geometric.data import Data, Batch -from topomodelx.nn.combinatorial.hmc import HMCLayer - - class GNNEmbedding(nn.Module): """ - Graph Neural Network based message passing layer (returns a cochain) for the CSS kernel + Graph Neural Network based message passing layer (returns a cochain) for the CSS kernel """ + def __init__(self, n, nx, nz, out_channels=128): super().__init__() @@ -34,7 +30,6 @@ def __init__(self, n, nx, nz, out_channels=128): z = torch.zeros(nz, 3) z[:, 2] = 1.0 self.single_nodes = torch.cat((q, x, z), dim=0) - # Graph convolutional layers self.conv1 = GCNConv(3, 128) @@ -86,71 +81,77 @@ def forward(self, adjacency_matrices): x = F.normalize(x, p=2, dim=-1) return x + + class GNNRegressor(nn.Module): def __init__(self, n, nx, nz, out_channels=128): super().__init__() self.embedding = GNNEmbedding(n, nx, nz, out_channels) self.fc = torch.nn.Linear(out_channels // 8, 1) - def forward(self,adjacency_matrix): + + def forward(self, adjacency_matrix): embedding = self.embedding(adjacency_matrix) out = self.fc(F.relu(embedding)) return out + + class GNNKernel(Kernel): - def __init__(self,n,nx,nz,**kwargs): + def __init__(self, n, nx, nz, **kwargs): super().__init__(**kwargs) - self.gnn = GNNEmbedding(n,nx,nz) + self.gnn = GNNEmbedding(n, nx, nz) self.rbf = RBFKernel() self.scale = ScaleKernel(self.rbf) - def forward(self, x1, x2,diag=False,**params): + + def forward(self, x1, x2, diag=False, **params): # x,y = self.gnn(adjacency_matrix1, adjacency_matrix2) x = self.gnn(x1) y = self.gnn(x2) # print(x,y) - return self.scale(x,y,diag=diag,**params) + return self.scale(x, y, diag=diag, **params) - -class WLSubtreeEmbedding(): - def __init__(self, n,nx,nz,num_iterations=3, **kwargs): + +class WLSubtreeEmbedding: + def __init__(self, n, nx, nz, num_iterations=3, **kwargs): super().__init__(**kwargs) self.num_iterations = num_iterations - q = torch.zeros(n,1) - x = torch.ones(nx,1) - z = -torch.ones(nz,1) - self.nodes = torch.cat((q,x,z),dim=0) + q = torch.zeros(n, 1) + x = torch.ones(nx, 1) + z = -torch.ones(nz, 1) + self.nodes = torch.cat((q, x, z), dim=0) + def forward(self, edge_index1, edge_index2): labels1 = self.apply_wl(edge_index1, self.num_iterations) labels2 = self.apply_wl(edge_index2, self.num_iterations) # print( labels1, labels2) # Compute kernel between these two sets of labels - v1,v2 = self.compute_hist(labels1, labels2) + v1, v2 = self.compute_hist(labels1, labels2) - - return v1,v2 + return v1, v2 def apply_wl(self, edge_index, num_iterations): neighbors = defaultdict(list) for i, j in edge_index.t().tolist(): # undirected graph neighbors[i].append(j) - neighbors[j].append(i) - + neighbors[j].append(i) - labels = self.nodes.clone() + labels = self.nodes.clone() for _ in range(num_iterations): - new_labels = labels.clone() + new_labels = labels.clone() for node, node_neighbors in neighbors.items(): + neighborhood_labels = [labels[node]] + [ + labels[n] for n in node_neighbors + ] - neighborhood_labels = [labels[node]] + [labels[n] for n in node_neighbors] - sorted_labels = sorted(neighborhood_labels) - new_label_str = '_'.join(str(lbl) for lbl in sorted_labels) + new_label_str = "_".join(str(lbl) for lbl in sorted_labels) new_labels[node] = hash(new_label_str) - - labels = new_labels - + + labels = new_labels + return labels def compute_hist(self, labels1, labels2): @@ -159,32 +160,40 @@ def compute_hist(self, labels1, labels2): hist1 = Counter(labels1.flatten().tolist()) hist2 = Counter(labels2.flatten().tolist()) all_labels = list(set(list(hist1.keys()) + list(hist2.keys()))) - vec1 = torch.tensor([hist1.get(label, 0) for label in all_labels], dtype=torch.float32) - vec2 = torch.tensor([hist2.get(label, 0) for label in all_labels], dtype=torch.float32) - + vec1 = torch.tensor( + [hist1.get(label, 0) for label in all_labels], dtype=torch.float32 + ) + vec2 = torch.tensor( + [hist2.get(label, 0) for label in all_labels], dtype=torch.float32 + ) + return vec1, vec2 + + class WLSubtreeKernel(Kernel): - def __init__(self, n,nx,nz,num_iterations=3,encoder = None,eps = 1e-8,**kwargs): + def __init__(self, n, nx, nz, num_iterations=3, encoder=None, eps=1e-8, **kwargs): super().__init__(**kwargs) if encoder == None: + def encode_(x): return x + self.encode = encode_ else: self.encode = encoder self.eps = eps self.num_iterations = num_iterations - self.wl = WLSubtreeEmbedding(n,nx,nz,num_iterations) + self.wl = WLSubtreeEmbedding(n, nx, nz, num_iterations) initial_sigma_f = 1 initial_l = 1 self.log_sigma_f = nn.Parameter(torch.log(torch.tensor(initial_sigma_f))) self.log_l = nn.Parameter(torch.log(torch.tensor(initial_l))) + def forward(self, x1, x2, **kwargs): # edge_indexs1 = dense_to_sparse(edge_indexs1)[0] # print(f'in kernel!!!') edge_indices1 = self.encode(x1) edge_indices2 = self.encode(x2) - N1 = x1.shape[0] N2 = x2.shape[0] @@ -202,15 +211,14 @@ def forward(self, x1, x2, **kwargs): v1_norm = v1 / (v1.norm() + self.eps) v2_norm = v2 / (v2.norm() + self.eps) cosine_sim = torch.dot(v1_norm, v2_norm) - K[i, j] = sigma_f**2 * torch.exp(- (1 - cosine_sim) / (l**2)) + K[i, j] = sigma_f**2 * torch.exp(-(1 - cosine_sim) / (l**2)) return K - class TwoDimensionalCCNNEmbedding(nn.Module): - def __init__(self,n,nx,nz,layers=3, output_channel=64,negative_slope=0.2): + def __init__(self, n, nx, nz, layers=3, output_channel=64, negative_slope=0.2): """ - Combinatorial Complex Neural Network based massagepassing layer (returns a cochain) for the CSS kernel + Combinatorial Complex Neural Network based massagepassing layer (returns a cochain) for the CSS kernel """ super().__init__() c_1 = torch.zeros(n, 3) @@ -232,9 +240,7 @@ def __init__(self,n,nx,nz,layers=3, output_channel=64,negative_slope=0.2): negative_slope, ) - - - def forward(self,relations): + def forward(self, relations): x_0, x_1, x_2 = self.ccnn_0( x_0, x_1, @@ -289,20 +295,20 @@ def forward(self,relations): x_0 = torch.nanmean(x_0, dim=0) x_1 = torch.nanmean(x_1, dim=0) x_2 = torch.nanmean(x_2, dim=0) - out = x_0+x_1+x_2 - + out = x_0 + x_1 + x_2 + return out + + class TwoDimensionalCCNNKernel(Kernel): - def __init__(self,n,nx,nz,**kwargs): + def __init__(self, n, nx, nz, **kwargs): super().__init__(**kwargs) - self.ccnn = TwoDimensionalCCNNEmbedding(n,nx,nz) + self.ccnn = TwoDimensionalCCNNEmbedding(n, nx, nz) self.rbf = RBFKernel() self.scale = ScaleKernel(self.rbf) - def forward(self, x1, x2,diag=False,**params): + + def forward(self, x1, x2, diag=False, **params): x = self.ccnn(x1) y = self.ccnn(x2) - return self.scale(x,y,diag=diag,**params) - - - + return self.scale(x, y, diag=diag, **params) diff --git a/bayesian_optimization/objective_function.py b/bayesian_optimization/objective_function.py index bd99410..d576fcb 100644 --- a/bayesian_optimization/objective_function.py +++ b/bayesian_optimization/objective_function.py @@ -2,16 +2,24 @@ import numpy as np from evaluation.decoder_based_evaluation import * from code_construction.code_construction import * -from evaluation.circuit_level_noise import MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise +from evaluation.circuit_level_noise import ( + MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise, +) +import qldpc +import codedistance +from multiprocessing import Process, Queue +import queue # ========================= # Utilities # ========================= + def _log_binom(n: int, j: int) -> float: """Stable log binomial: log C(n, j) via lgamma.""" return math.lgamma(n + 1) - math.lgamma(j + 1) - math.lgamma(n - j + 1) + def _build_pchip(x, y): """ Try to build a PCHIP interpolator; fall back to linear if SciPy is missing. @@ -19,10 +27,14 @@ def _build_pchip(x, y): """ try: from scipy.interpolate import PchipInterpolator - return PchipInterpolator(np.asarray(x, float), np.asarray(y, float), extrapolate=True), False + + return PchipInterpolator( + np.asarray(x, float), np.asarray(y, float), extrapolate=True + ), False except Exception: return (np.asarray(x, float), np.asarray(y, float)), True + def _eval_interp(interp_obj, xq): """Evaluate either a PCHIP object or a linear interpolator (x, y tuple).""" if isinstance(interp_obj, tuple): @@ -31,6 +43,7 @@ def _eval_interp(interp_obj, xq): else: return interp_obj(np.asarray(xq, float)) + def _eval_piecewise_slope(x, y, tq): """ Compute piecewise-constant slope for linear interpolation fallback. @@ -52,10 +65,12 @@ def _eval_piecewise_slope(x, y, tq): slope = np.where(tq >= x[-1], (y[-1] - y[-2]) / (x[-1] - x[-2]), slope) return slope + # ========================= # f2_converter: t -> f2(t) # ========================= + class f2_converter: r""" Compute and interpolate: @@ -67,7 +82,9 @@ class f2_converter: def __init__(self, n: int, use_pchip: bool = True): self.n = int(n) ln3 = math.log(3.0) - log_terms = np.array([_log_binom(self.n, j) + j * ln3 for j in range(self.n + 1)], dtype=float) + log_terms = np.array( + [_log_binom(self.n, j) + j * ln3 for j in range(self.n + 1)], dtype=float + ) # prefix log-sum-exp for sum_{j=0..t} log_prefix = np.full(self.n + 1, -np.inf, dtype=float) @@ -108,10 +125,12 @@ def df2_dt(self, t): x, y = self._t2f2 return _eval_piecewise_slope(x, y, t) + # ========================================= # pl_t_converter: t <-> pL (with derivative) # ========================================= + class pl_t_converter: r""" Convert between the correctable-weight proxy t and the logical error rate p_L, @@ -128,24 +147,35 @@ def __init__(self, n: int, p_phys: float = 0.01, use_pchip: bool = True): # ----- Precompute log PMF terms ----- log_p = math.log(self.p) log_q = math.log(1.0 - self.p) - log_pmf = np.array([_log_binom(self.n, j) + j * log_p + (self.n - j) * log_q - for j in range(self.n + 1)], dtype=float) + log_pmf = np.array( + [ + _log_binom(self.n, j) + j * log_p + (self.n - j) * log_q + for j in range(self.n + 1) + ], + dtype=float, + ) # Right-tail sums: log_tail[j] = log sum_{i=j..n} pmf(i) log_tail = np.full(self.n + 2, -np.inf, dtype=float) log_tail[self.n] = log_pmf[self.n] for j in range(self.n - 1, -1, -1): m = max(log_pmf[j], log_tail[j + 1]) - log_tail[j] = m + math.log(math.exp(log_pmf[j] - m) + math.exp(log_tail[j + 1] - m)) + log_tail[j] = m + math.log( + math.exp(log_pmf[j] - m) + math.exp(log_tail[j + 1] - m) + ) # p_L(t) ≈ tail at j = floor(t)+1; build table over integer t self.t_grid = np.arange(self.n + 1, dtype=float) - log_pl_table = np.array([log_tail[int(t) + 1] for t in self.t_grid], dtype=float) + log_pl_table = np.array( + [log_tail[int(t) + 1] for t in self.t_grid], dtype=float + ) self.log10_pl_table = log_pl_table / math.log(10.0) # ----- Build interpolators ----- if use_pchip: - self._t2log10pl, self._t_linear = _build_pchip(self.t_grid, self.log10_pl_table) + self._t2log10pl, self._t_linear = _build_pchip( + self.t_grid, self.log10_pl_table + ) if not self._t_linear: self._t2log10pl_deriv = self._t2log10pl.derivative() else: @@ -187,14 +217,14 @@ def pl_to_t(self, k, pl): t_hat = _eval_interp(self._log10pl2t, log10_pl) return t_hat if np.ndim(pl) else float(t_hat) + # ========================================= # ObjectiveFunction (with uncertainty) # ========================================= -import math -import numpy as np import torch + class ObjectiveFunction: """ Objective: F(x) = R + f2(t_hat) - 1, where @@ -207,15 +237,36 @@ class ObjectiveFunction: returns (F_mean, F_std), or with aux diagnostics if return_aux=True. """ - def __init__(self, code_constructor, lambda_ = 1,pp=0.01, decoder_param={'trail': 10000,'max_error':100}, - circuit_level_noise=False, circuit_param=None): + def __init__( + self, + code_constructor, + lambda_=1, + pp=0.01, + decoder_param={"trail": 10000, "max_error": 100}, + circuit_level_noise=False, + circuit_param=None, + code_eval_metric="LER", + dist_method=None, # allows method from https://github.com/m-webster/codeDistancePYPI + dist_params={}, + dist_seed=None, + dist_timeout=None, + ): self.code_constructor = code_constructor self.n = int(code_constructor.n) self.pp = float(pp) self.decoder_param = dict(decoder_param) self.lambda_ = lambda_ + self.code_eval_metric = code_eval_metric + self.dist_method = dist_method + self.dist_params = dist_params + self.dist_seed = dist_seed + if not dist_timeout: + self.dist_timeout = 60 * 20 # default: 20 minutes + else: + self.dist_timeout = dist_timeout # Converters + # TODO rename references to pl, as could be evaluating distance instead self.pl_t_converter = pl_t_converter(self.n, p_phys=self.pp, use_pchip=True) self.f2_converter = f2_converter(self.n, use_pchip=True) @@ -224,11 +275,11 @@ def __init__(self, code_constructor, lambda_ = 1,pp=0.01, decoder_param={'trail' if circuit_level_noise: if circuit_param is None: circuit_param = { - 'noise_model':'SD6', - 'num_workers' : 24, - 'rounds':12, - 'custom_error_model':{}, - 'decoder':'bplsd' + "noise_model": "SD6", + "num_workers": 24, + "rounds": 12, + "custom_error_model": {}, + "decoder": "bplsd", } self.circuit_param = circuit_param @@ -249,28 +300,33 @@ def _to_np_bits(x): arr = np.asarray(x, dtype=np.int64) return arr - def ler(self, css): + def ler(self, css: CSSCode): """Compute the logical error rate, either via circuit-level or decoder-based simulation.""" if self.circuit_level_noise: mc = MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise( - css, noise_model=self.circuit_param['noise_model'], - p=self.pp, rounds=self.circuit_param['rounds'], - custom_error_model=self.circuit_param['custom_error_model'], - decoder=self.circuit_param['decoder'] + css, + noise_model=self.circuit_param["noise_model"], + p=self.pp, + rounds=self.circuit_param["rounds"], + custom_error_model=self.circuit_param["custom_error_model"], + decoder=self.circuit_param["decoder"], + ) + pL = mc.run( + shots=self.decoder_param["trail"], + max_error=self.decoder_param["max_error"], + num_workers=self.circuit_param["num_workers"], ) - pL = mc.run(shots=self.decoder_param['trail'], - max_error=self.decoder_param['max_error'], - num_workers=self.circuit_param['num_worker']) else: evaluator = CSS_Evaluator(css.hx, css.hz) pL = evaluator.Get_logical_error_rate_Monte_Carlo( physical_error_rate=self.pp, xyz_bias=[1, 1, 1], - trail=self.decoder_param.get('trail', 10000) + trail=self.decoder_param.get("trail", 10000), ) - pL = min(pL,1-(1e-8)) - pL = max(pL,1e-20) - return float(pL) + pL = min(pL, 1 - (1e-8)) + pL = max(pL, 1e-20) + + return float(pL) def nller(self, x): """Compute negative log-likelihood (-log pL).""" @@ -307,8 +363,30 @@ def psuedo_t(self, css): t_hat = self.pl_t_converter.pl_to_t(k=None, pl=pL) return float(t_hat), float(pL) + def _distance_worker(self, css: CSSCode, q: Queue): + d = self.distance(css) + q.put(d) + + def distance(self, css: CSSCode) -> int: + code = qldpc.codes.CSSCode(css.hx, css.hz) + distance = code.get_distance() + if distance <= 0: + distance = 1 + + return distance + + def approximate_distance(self, css: CSSCode) -> int: + res = codedistance.CSScodeDistance( + css.hx, + css.hz, + method=self.dist_method, + params=self.dist_params, + seed=self.dist_seed, + ) + return res["d"] + def forward(self, x): - """Compute the scalar objective F(x) and the corresponding total pL.""" + """Compute the scalar objective F(x) and the corresponding total pL or distance.""" css = self.code_constructor.construct(self._to_np_bits(x)) k = int(css.k) self._last_k = k @@ -316,26 +394,67 @@ def forward(self, x): return float(-1.0), float(1.0) R = k / float(self.n) - t_hat, pL_total = self.psuedo_t(css) - f2_val = float(self.f2_converter.t_to_f2(t_hat)) - F = self.lambda_ * R + f2_val - 1.0 - return float(F), float(pL_total) + if self.code_eval_metric == "LER": + t_hat, pL_total = self.psuedo_t(css) + f2_val = float(self.f2_converter.t_to_f2(t_hat)) + F = self.lambda_ * R + f2_val - 1.0 + return float(F), float(pL_total) + elif self.code_eval_metric == "distance": + if self.dist_method is None: # exact distance calculation + # spawn separate process that can be timed out + q = Queue() + p = Process(target=self._distance_worker, args=(css, q)) + + p.start() + p.join(self.dist_timeout) + + if p.is_alive(): + p.terminate() + p.join() + raise TimeoutError( + f"Distance evaluation has timed out for code size n={css.n} " + f"after running for {self.dist_timeout // 60} minutes. Consider " + "using a distance approximation method instead with the option" + "'--distance-heuristic', or increase the timeout time with '--distance-timeout'." + ) + + try: + d = q.get(block=False) + except queue.Empty: + raise RuntimeError("Process died without returning a distance") + + else: # distance heuristic + d = self.approximate_distance(css) + t = (d - 1) / 2 # correctable errors + f2_val = float(self.f2_converter.t_to_f2(t)) + F = self.lambda_ * R + f2_val - 1.0 + return float(F), float(d) + else: + raise ValueError( + f"Code evaluation metric '{self.code_eval_metric}' is not supported." + ) # --------------------------- # Uncertainty propagation: from (pL_mean, pL_std) → (F_mean, F_std) # --------------------------- def nlpl_with_std(self, x, pl_total_mean, pl_total_std): """Compute (-log pL, propagated std).""" - return float(-np.log(pl_total_mean)),float( pl_total_std / pl_total_mean) - + return float(-np.log(pl_total_mean)), float(pl_total_std / pl_total_mean) + def nllerpq_with_std(self, x, pl_total_mean, pl_total_std): """Compute mean/std of -log(1 - (1 - pL)^{1/k}).""" css = self.code_constructor.construct(self._to_np_bits(x)) - mean = -np.log(1-(1-pl_total_mean)**(1/css.k)) - std = pl_total_std*(1-pl_total_mean)**(1/css.k-1)/(css.k * (1-(1-pl_total_mean)**(1/css.k))) + mean = -np.log(1 - (1 - pl_total_mean) ** (1 / css.k)) + std = ( + pl_total_std + * (1 - pl_total_mean) ** (1 / css.k - 1) + / (css.k * (1 - (1 - pl_total_mean) ** (1 / css.k))) + ) return float(mean), float(std) - def pl_to_obj_with_std(self, x, pl_total_mean, pl_total_std, return_aux: bool = False): + def pl_to_obj_with_std( + self, x, pl_total_mean, pl_total_std, return_aux: bool = False + ): """ Given the mean/std of the total logical error rate (pL_total), compute the mean/std of the objective F(x). @@ -375,7 +494,6 @@ def pl_to_obj_with_std(self, x, pl_total_mean, pl_total_std, return_aux: bool = # gprime = (1.0 / k) * (1.0 - m_total) ** (1.0 / k - 1.0) # s_per = abs(gprime) * s_total - # # per → t_hat → f2 → F_mean # t_hat = float(self.pl_t_converter.pl_to_t(k=None, pl=m_per)) # f2_val = float(self.f2_converter.t_to_f2(t_hat)) @@ -397,7 +515,7 @@ def pl_to_obj_with_std(self, x, pl_total_mean, pl_total_std, return_aux: bool = F_mean = self.lambda_ * R + f2_val - 1.0 dlog10pl_dt = float(self.pl_t_converter.dlog10pl_dt(t_hat)) - dpl_dt = math.log(10.0) * m * dlog10pl_dt # dp/dt + dpl_dt = math.log(10.0) * m * dlog10pl_dt # dp/dt dt_dpl = 0.0 if abs(dpl_dt) < 1e-300 else 1.0 / dpl_dt df2_dt = float(self.f2_converter.df2_dt(t_hat)) diff --git a/code_construction/__pycache__/code_construction.cpython-310.pyc b/code_construction/__pycache__/code_construction.cpython-310.pyc deleted file mode 100644 index e079a8c..0000000 Binary files a/code_construction/__pycache__/code_construction.cpython-310.pyc and /dev/null differ diff --git a/code_construction/code_construction.py b/code_construction/code_construction.py index f523c47..070782f 100644 --- a/code_construction/code_construction.py +++ b/code_construction/code_construction.py @@ -1,85 +1,77 @@ """ - This file contains the code constructions. +This file contains the code constructions. - The code constructions are the following: - 1.Stabilizer codes constructed by the canonical construction. - 2.CSS code constructed by the QC-LDPC-HGP construction. - 3.CSS code constructed by the Bivariate-bycicle codes. +The code constructions are the following: + 1.Stabilizer codes constructed by the canonical construction. + 2.CSS code constructed by the QC-LDPC-HGP construction. + 3.CSS code constructed by the Bivariate-bycicle codes. """ - import numpy as np from bposd.css import css_code -class StabilizerCode(): - def __init__(self,h) -> None: + + +class StabilizerCode: + def __init__(self, h) -> None: self.h = h if not self.check_validity(): - raise ValueError('This stabilizer code is not valid.') - self.n = self.h.shape[1]//2 + raise ValueError("This stabilizer code is not valid.") + self.n = self.h.shape[1] // 2 def check_validity(self): if not isinstance(self.h, np.ndarray): return False - if self.h.shape[1]%2 != 0: + if self.h.shape[1] % 2 != 0: return False - n = self.h.shape[1]//2 - omega = np.block([ - [np.zeros((n,n)),np.eye(n)], - [np.eye(n),np.zeros((n,n))] - ]) - if not ((self.h @ omega @ self.h.T) %2 == 0).all(): + n = self.h.shape[1] // 2 + omega = np.block([[np.zeros((n, n)), np.eye(n)], [np.eye(n), np.zeros((n, n))]]) + if not ((self.h @ omega @ self.h.T) % 2 == 0).all(): return False return True - + + class CSSCode(StabilizerCode): - def __init__(self,hx=None,hz=None) -> None: + def __init__(self, hx=None, hz=None) -> None: self.hx = np.array(hx) self.hz = np.array(hz) - self.h = np.block([ - [hz, np.zeros(hz.shape)], - [np.zeros(hx.shape), hx] - ]) + self.h = np.block([[hz, np.zeros(hz.shape)], [np.zeros(hx.shape), hx]]) if not self.check_validity(): - raise ValueError('This CSS code is not valid.') + raise ValueError("This CSS code is not valid.") qcode = css_code(self.hx, self.hz) self.lx = np.array(qcode.lx.toarray()) - self.lz = np.array(qcode.lz.toarray()) # logical operators + self.lz = np.array(qcode.lz.toarray()) # logical operators self.k = qcode.K self.n = qcode.N - - - + def check_validity(self): - if not isinstance(self.hx, np.ndarray)or not isinstance(self.hz, np.ndarray): - print('not np.array') + if not isinstance(self.hx, np.ndarray) or not isinstance(self.hz, np.ndarray): + print("not np.array") return False - if self.hx.shape[1]!=self.hz.shape[1]: - print('n is not equal') + if self.hx.shape[1] != self.hz.shape[1]: + print("n is not equal") return False - if (((self.hz @ self.hx.T)%2) != 0).any(): - print('some x and z stabilizers do not commute') + if (((self.hz @ self.hx.T) % 2) != 0).any(): + print("some x and z stabilizers do not commute") return False return True - -class CodeConstructor(): +class CodeConstructor: """ - This is the class for code construction. - now we support the following methods: - 1. Canonical construction.(for Evolutionary algorithm, general stabilizer codes) - 2. QC-LDPC-HGP construction. - 3. Bivariate-bycicle construction. - 4. Rotated Surface code construction - 5. PG-LDPC-HGP construction + This is the class for code construction. + now we support the following methods: + 1. Canonical construction.(for Evolutionary algorithm, general stabilizer codes) + 2. QC-LDPC-HGP construction. + 3. Bivariate-bycicle construction. + 4. Rotated Surface code construction + 5. PG-LDPC-HGP construction """ - def __init__(self,method='qc-ldpc-hgp',para_dict=None) -> None: - """ - method(str): The method of the code construction, 'canonical', 'qc-ldpc-hgp', 'bivariate-bycicle','rotated-surface','qc-gb'. - + def __init__(self, method="qc-ldpc-hgp", para_dict=None) -> None: + """ + method(str): The method of the code construction, 'canonical', 'qc-ldpc-hgp', 'bivariate-bycicle','rotated-surface','qc-gb' """ self.method = method self.para_dict = para_dict @@ -87,271 +79,329 @@ def __init__(self,method='qc-ldpc-hgp',para_dict=None) -> None: self.nx = 0 self.nz = 0 self.k = None - + if not self.check_parameters_validity(): - raise ValueError('The parameters are not valid.') - + raise ValueError("The parameters are not valid.") + def check_parameters_validity(self): - if self.method == 'canonical' or self.method == 'canonical_css': + if self.method == "canonical" or self.method == "canonical_css": return self.check_canonical_parameters_validity() - elif self.method == 'qc-ldpc-hgp' or self.method == 'pg-ldpc-hgp': + elif self.method == "qc-ldpc-hgp" or self.method == "pg-ldpc-hgp": return self.check_qc_ldpc_hgp_parameters_validity() # elif self.method == 'bivariate-bycicle': - # return self.check_bivariate_bycicle_parameters_validity() - elif self.method == 'rotated-surface': + # return self.check_bivariate_bycicle_parameters_validity() + elif self.method == "rotated-surface": return True - elif self.method in ['qc-gb','bivariate-bycicle','bb','symmetric-qc-gb']: - if self.method in ['bb','bivariate-bycicle']: + elif self.method == "gb": + try: + self.n = self.para_dict["l"] * 2 + self.nx = self.n + self.nz = self.n + except: + raise ValueError("failing setting n,nx,nz") + return True + elif self.method in [ + "qc-gb", + "bivariate-bycicle", + "bb", + "symmetric-qc-gb", + ]: + if self.method in ["bb", "bivariate-bycicle"]: try: - self.n = self.para_dict['l'] * self.para_dict['g'] + self.n = self.para_dict["l"] * self.para_dict["g"] self.nx = self.n self.nz = self.n except: - raise ValueError('failing setting n,nx,nz') - if 'g' in self.para_dict.keys(): - if type(self.para_dict['g']) == type(int(1)): + raise ValueError("failing setting n,nx,nz") + if "g" in self.para_dict.keys(): + if type(self.para_dict["g"]) == type(int(1)): return True - elif 'ga' in self.para_dict.keys() and 'gb' in self.para_dict.keys(): - if type(self.para_dict['ga']) == type(int(1)) and type(self.para_dict['gb']) == type(int(1)): + elif "ga" in self.para_dict.keys() and "gb" in self.para_dict.keys(): + if type(self.para_dict["ga"]) == type(int(1)) and type( + self.para_dict["gb"] + ) == type(int(1)): return True return False else: - raise ValueError('The method is not supported.') - def construct(self,parameters): - if self.method == 'canonical': + raise ValueError("The method is not supported.") + + def construct(self, parameters): + if self.method == "canonical": return self.canonical_construction(parameters) - elif self.method == 'canonical_css': - return self.canonical_construction(parameters,CSS=True) - elif self.method == 'qc-ldpc-hgp': + elif self.method == "canonical_css": + return self.canonical_construction(parameters, CSS=True) + elif self.method == "qc-ldpc-hgp": return self.qc_ldpc_hgp_construction(parameters) - elif self.method == 'pg-ldpc-hgp': + elif self.method == "pg-ldpc-hgp": return self.pg_ldpc_hgp_construction(parameters) # elif self.method == 'bivariate-bycicle': # return self.bivariate_bycicle_construction(parameters) - elif self.method == 'rotated-surface': + elif self.method == "rotated-surface": return self.rotated_surface_construction(parameters) - elif self.method == 'qc-gb': + elif self.method == "qc-gb": return self.quasi_cyclic_generalized_bicycle_code(parameters) - elif self.method == 'bivariate-bycicle' or self.method == 'bb': + elif self.method == "bivariate-bycicle" or self.method == "bb": return self.arbitrary_bivariate_bicycle_code(parameters) - elif self.method == 'symmetric-qc-gb': + elif self.method == "gb": + return self.generalised_bicycle_code(parameters) + elif self.method == "symmetric-qc-gb": return self.symmetric_quasi_cyclic_generalized_bicycle_code(parameters) else: - raise ValueError('The method is not supported.') + raise ValueError("The method is not supported.") def check_canonical_parameters_validity(self): """ - For each element in canonical construction requires the following parameters: - 1. n(int): The number of qubits. - 2. k(int): The number of logical qubits. - 3. r(int): The number of stabilizer generators at least have one x-operator. - and s+r = n-k + For each element in canonical construction requires the following parameters: + 1. n(int): The number of qubits. + 2. k(int): The number of logical qubits. + 3. r(int): The number of stabilizer generators at least have one x-operator. + and s+r = n-k """ for index in self.para_dict.keys(): - if type(self.para_dict[index])!=int or self.para_dict[index]<=0: + if type(self.para_dict[index]) != int or self.para_dict[index] <= 0: return False - if not index in {'n','k','r'}: + if not index in {"n", "k", "r"}: return False - if self.para_dict['n']-self.para_dict['k'] < self.para_dict['r']: + if self.para_dict["n"] - self.para_dict["k"] < self.para_dict["r"]: return False - self.n = self.para_dict['n'] - self.k = self.para_dict['k'] - self.nx = self.para_dict['r'] - self.nz = self.n-self.k-self.nx + self.n = self.para_dict["n"] + self.k = self.para_dict["k"] + self.nx = self.para_dict["r"] + self.nz = self.n - self.k - self.nx return True - + def check_qc_ldpc_hgp_parameters_validity(self): """ - For each element in QC-LDPC-HGP construction requires the following parameters: - 1. p(int): The number of rows of the matrix M. - 2. q(int): The number of columns of the matrix M. - 3. m(int): The size of the quasi-cyclic matrix. - 4*. p_2(int): The number of rows of the matrix M_2. (* means optional) - 5*. q_2(int): The number of columns of the matrix M_2. - 6*. m_2(int): The size of the quasi-cyclic matrix M_2. + For each element in QC-LDPC-HGP construction requires the following parameters: + 1. p(int): The number of rows of the matrix M. + 2. q(int): The number of columns of the matrix M. + 3. m(int): The size of the quasi-cyclic matrix. + 4*. p_2(int): The number of rows of the matrix M_2. (* means optional) + 5*. q_2(int): The number of columns of the matrix M_2. + 6*. m_2(int): The size of the quasi-cyclic matrix M_2. """ for index in self.para_dict.keys(): - if type(self.para_dict[index])!=int or self.para_dict[index]<=0: + if type(self.para_dict[index]) != int or self.para_dict[index] <= 0: return False - for i in {'m','p','q'}: + for i in {"m", "p", "q"}: if not i in self.para_dict.keys(): return False - if 'p_2' in self.para_dict.keys(): - if not 'q_2' in self.para_dict.keys(): + if "p_2" in self.para_dict.keys(): + if not "q_2" in self.para_dict.keys(): return False - if not 'm_2' in self.para_dict.keys(): + if not "m_2" in self.para_dict.keys(): return False - self.n = self.para_dict['m']*self.para_dict['m_2']*(self.para_dict['p']*self.para_dict['p_2']+self.para_dict['q']*self.para_dict['q_2']) - self.nx = self.para_dict['m']*self.para_dict['m_2']*self.para_dict['p']*self.para_dict['q_2'] - self.nz = self.para_dict['m']*self.para_dict['m_2']*self.para_dict['p_2']*self.para_dict['q'] + self.n = ( + self.para_dict["m"] + * self.para_dict["m_2"] + * ( + self.para_dict["p"] * self.para_dict["p_2"] + + self.para_dict["q"] * self.para_dict["q_2"] + ) + ) + self.nx = ( + self.para_dict["m"] + * self.para_dict["m_2"] + * self.para_dict["p"] + * self.para_dict["q_2"] + ) + self.nz = ( + self.para_dict["m"] + * self.para_dict["m_2"] + * self.para_dict["p_2"] + * self.para_dict["q"] + ) else: - self.n = self.para_dict['m']**2*(self.para_dict['p']**2 + self.para_dict['q']**2) - self.nx = self.para_dict['m']**2*self.para_dict['p']*self.para_dict['q'] + self.n = self.para_dict["m"] ** 2 * ( + self.para_dict["p"] ** 2 + self.para_dict["q"] ** 2 + ) + self.nx = ( + self.para_dict["m"] ** 2 * self.para_dict["p"] * self.para_dict["q"] + ) self.nz = self.nx - - - + return True - + def check_bivariate_bycicle_parameters_validity(self): # print('check bb') for index in self.para_dict.keys(): - if type(self.para_dict[index])!=int or self.para_dict[index]<0: + if type(self.para_dict[index]) != int or self.para_dict[index] < 0: return False - for i in {'l','m'}: + for i in {"l", "m"}: if not i in self.para_dict.keys(): return False - self.n = 2*self.para_dict['l'] * self.para_dict['m'] - self.nx = self.n//2 + self.n = 2 * self.para_dict["l"] * self.para_dict["m"] + self.nx = self.n // 2 self.nx = self.nx return True - - - def rotated_surface_construction(self,p): + def rotated_surface_construction(self, p): """ p*p rotated surface code """ - def index(row, col,reversed=False): - if row<0 or row>=p or col<0 or col>=p: + + def index(row, col, reversed=False): + if row < 0 or row >= p or col < 0 or col >= p: return -1 if reversed: return row + col * p return row * p + col + if p % 2 == 0: raise ValueError("p must be an odd number.") N = int(p * p) - stabilizers_num = int((p-1)*(p-1)/2+p-1) + stabilizers_num = int((p - 1) * (p - 1) / 2 + p - 1) Hx = np.zeros((stabilizers_num, N), dtype=int) Hz = np.zeros((stabilizers_num, N), dtype=int) - x_stabilizers = [[] for i in range(stabilizers_num)] z_stabilizers = [[] for i in range(stabilizers_num)] - for i in range(p+1): - for j in range((p-1)//2): - if index(i-1,j+(i+1)%2)!= -1: - x_stabilizers[i*(p-1)//2+j].append(index(i-1,2*j+(i+1)%2)) - if index(i-1,j+(i+1)%2+1)!= -1: - x_stabilizers[i*(p-1)//2+j].append(index(i-1,2*j+(i+1)%2+1)) - if index(i,j+(i+1)%2)!= -1: - x_stabilizers[i*(p-1)//2+j].append(index(i,2*j+(i+1)%2)) - if index(i,j+(i+1)%2+1)!= -1: - x_stabilizers[i*(p-1)//2+j].append(index(i,2*j+(i+1)%2+1)) - for i in range(p+1): - for j in range((p-1)//2): - if index(i-1,j+(i)%2,reversed=True)!= -1: - z_stabilizers[i*(p-1)//2+j].append(index(i-1,2*j+(i)%2,reversed=True)) - if index(i-1,j+(i)%2+1,reversed=True)!= -1: - z_stabilizers[i*(p-1)//2+j].append(index(i-1,2*j+(i)%2+1,reversed=True)) - if index(i,j+(i)%2,reversed=True)!= -1: - z_stabilizers[i*(p-1)//2+j].append(index(i,2*j+(i)%2,reversed=True)) - if index(i,j+(i)%2+1,reversed=True)!= -1: - z_stabilizers[i*(p-1)//2+j].append(index(i,2*j+(i)%2+1,reversed=True)) + for i in range(p + 1): + for j in range((p - 1) // 2): + if index(i - 1, j + (i + 1) % 2) != -1: + x_stabilizers[i * (p - 1) // 2 + j].append( + index(i - 1, 2 * j + (i + 1) % 2) + ) + if index(i - 1, j + (i + 1) % 2 + 1) != -1: + x_stabilizers[i * (p - 1) // 2 + j].append( + index(i - 1, 2 * j + (i + 1) % 2 + 1) + ) + if index(i, j + (i + 1) % 2) != -1: + x_stabilizers[i * (p - 1) // 2 + j].append( + index(i, 2 * j + (i + 1) % 2) + ) + if index(i, j + (i + 1) % 2 + 1) != -1: + x_stabilizers[i * (p - 1) // 2 + j].append( + index(i, 2 * j + (i + 1) % 2 + 1) + ) + for i in range(p + 1): + for j in range((p - 1) // 2): + if index(i - 1, j + (i) % 2, reversed=True) != -1: + z_stabilizers[i * (p - 1) // 2 + j].append( + index(i - 1, 2 * j + (i) % 2, reversed=True) + ) + if index(i - 1, j + (i) % 2 + 1, reversed=True) != -1: + z_stabilizers[i * (p - 1) // 2 + j].append( + index(i - 1, 2 * j + (i) % 2 + 1, reversed=True) + ) + if index(i, j + (i) % 2, reversed=True) != -1: + z_stabilizers[i * (p - 1) // 2 + j].append( + index(i, 2 * j + (i) % 2, reversed=True) + ) + if index(i, j + (i) % 2 + 1, reversed=True) != -1: + z_stabilizers[i * (p - 1) // 2 + j].append( + index(i, 2 * j + (i) % 2 + 1, reversed=True) + ) for i in range(stabilizers_num): for j in x_stabilizers[i]: - Hx[i,j] = 1 + Hx[i, j] = 1 for j in z_stabilizers[i]: - Hz[i,j] = 1 - + Hz[i, j] = 1 + return CSSCode(Hx, Hz) - - def canonical_construction(self,bitstring,CSS=False) -> StabilizerCode: + + def canonical_construction(self, bitstring, CSS=False) -> StabilizerCode: """ - Construct the stabilizer code by the canonical construction. + Construct the stabilizer code by the canonical construction. - Args: - parameters(np.array): The parameters of the code. + Args: + parameters(np.array): The parameters of the code. - Returns: - stabilizer_code(StabilizerCode): The stabilizer code. + Returns: + stabilizer_code(StabilizerCode): The stabilizer code. """ - + n = self.n k = self.k - r = self.para_dict['r'] - s = n-k-r - + r = self.para_dict["r"] + s = n - k - r + if CSS == False: - if len(bitstring)!=((n-r)*r+k*(n-k)+(1+r)*r//2): - raise ValueError('The length of the bitstring is invalid.') + if len(bitstring) != ((n - r) * r + k * (n - k) + (1 + r) * r // 2): + raise ValueError("The length of the bitstring is invalid.") for i in bitstring: - if i!=0 and i!=1: + if i != 0 and i != 1: raise ValueError("The bitstring's value is invalid.") - A = bitstring[:r*(n-r)].reshape((r,n-r)) - A_1 = A[:,:s] - A_2 = A[:,s:] - C = bitstring[r*(n-r):r*(n-r)+k*(n-k)].reshape((n-k,k)) + A = bitstring[: r * (n - r)].reshape((r, n - r)) + A_1 = A[:, :s] + A_2 = A[:, s:] + C = bitstring[r * (n - r) : r * (n - r) + k * (n - k)].reshape((n - k, k)) C_1 = C[:r] C_2 = C[r:] - M = np.zeros((r,r)) + M = np.zeros((r, r)) # Reconstruct the symmetric matrix M - p=0 + p = 0 for i in range(r): - for j in range(i,r): - - M[i,j] = bitstring[r*(n-r)+k*(n-k)+p] - M[j,i] = M[i,j] + for j in range(i, r): + M[i, j] = bitstring[r * (n - r) + k * (n - k) + p] + M[j, i] = M[i, j] p += 1 - if s!= 0: - D = (A_1.T + C_2@A_2.T)%2 - B = (C_1@A_2.T + M)%2 - - H = np.block([ - [np.eye(r), A_1, A_2, B, np.zeros((r,s)),C_1], - [np.zeros((s,r)), np.zeros((s,s)),np.zeros((s,k)),D,np.eye(s),C_2] - ]) + if s != 0: + D = (A_1.T + C_2 @ A_2.T) % 2 + B = (C_1 @ A_2.T + M) % 2 + + H = np.block( + [ + [np.eye(r), A_1, A_2, B, np.zeros((r, s)), C_1], + [ + np.zeros((s, r)), + np.zeros((s, s)), + np.zeros((s, k)), + D, + np.eye(s), + C_2, + ], + ] + ) else: - + B = (C_1 @ A_2.T + M) % 2 - B = (C_1@A_2.T + M)%2 - - H = np.block([ - np.eye(r), A_2, B,C_1 - ]) + H = np.block([np.eye(r), A_2, B, C_1]) return StabilizerCode(H) else: - if len(bitstring)!=((n-r)*r+s*k): - raise ValueError('The length of the bitstring is invalid.') + if len(bitstring) != ((n - r) * r + s * k): + raise ValueError("The length of the bitstring is invalid.") for i in bitstring: - if i!=0 and i!=1: + if i != 0 and i != 1: raise ValueError("The bitstring's value is invalid.") - A = bitstring[:r*(n-r)].reshape((r,n-r)) - A_1 = A[:,:s] - A_2 = A[:,s:] - C = bitstring[r*(n-r):r*(n-r)+k*(n-k)].reshape((s,k)) - D = (A_1.T + C@A_2.T)%2 - hx = np.block([ - D, np.eye(s), C - ]) - hz = np.block([ - np.eye(r), A_1, A_2 - ]) - return CSSCode(hx,hz) - - - - def qc_ldpc_hgp_construction(self,M,M_2 = None ,form = None) -> CSSCode: + A = bitstring[: r * (n - r)].reshape((r, n - r)) + A_1 = A[:, :s] + A_2 = A[:, s:] + C = bitstring[r * (n - r) : r * (n - r) + k * (n - k)].reshape((s, k)) + D = (A_1.T + C @ A_2.T) % 2 + hx = np.block([D, np.eye(s), C]) + hz = np.block([np.eye(r), A_1, A_2]) + return CSSCode(hx, hz) + + def qc_ldpc_hgp_construction(self, M, M_2=None, form=None) -> CSSCode: """ - Construct the CSS code by the QC-LDPC-HGP construction. + Construct the CSS code by the QC-LDPC-HGP construction. - Args: - M(np.ndarray): The parameters of the code.M is a np.ndarray - M_2*(np.ndarray): The parameters of the code.M_2 is a np.ndarray + Args: + M(np.ndarray): The parameters of the code.M is a np.ndarray + M_2*(np.ndarray): The parameters of the code.M_2 is a np.ndarray - Returns: - CSS_code(CSSCode): The CSS code. + Returns: + CSS_code(CSSCode): The CSS code. """ if M_2 is None: - H1 = self.ldpc_construction(self.para_dict['p'],self.para_dict['q'],self.para_dict['m'],M) - H2 = self.ldpc_construction(self.para_dict['p'],self.para_dict['q'],self.para_dict['m'],M) + H1 = self.ldpc_construction( + self.para_dict["p"], self.para_dict["q"], self.para_dict["m"], M + ) + H2 = self.ldpc_construction( + self.para_dict["p"], self.para_dict["q"], self.para_dict["m"], M + ) else: - H1 = self.ldpc_construction(self.para_dict['p'],self.para_dict['q'],self.para_dict['m'],M) - H2 = self.ldpc_construction(self.para_dict['p_2'],self.para_dict['q_2'],self.para_dict['m_2'],M_2) - r1,n1 = H1.shape - r2,n2 = H2.shape + H1 = self.ldpc_construction( + self.para_dict["p"], self.para_dict["q"], self.para_dict["m"], M + ) + H2 = self.ldpc_construction( + self.para_dict["p_2"], self.para_dict["q_2"], self.para_dict["m_2"], M_2 + ) + r1, n1 = H1.shape + r2, n2 = H2.shape # HX = [H1 ⊗ In2 | Ir1 ⊗ H2.T] # HZ = [In1 ⊗ H2 | H1.T ⊗ Ir2] HX_left = np.kron(H1, np.eye(n2)) @@ -362,15 +412,14 @@ def qc_ldpc_hgp_construction(self,M,M_2 = None ,form = None) -> CSSCode: HZ_right = np.kron(H1.T, np.eye(r2)) HZ = np.hstack((HZ_left, HZ_right)) - - return CSSCode(HX,HZ) - - def ldpc_construction(self,p,q,m,M): + return CSSCode(HX, HZ) + + def ldpc_construction(self, p, q, m, M): # print(M) - if len(M)!=p*q: - raise ValueError('The shape of M is invalid!') - M = M.reshape(p,q) - H = np.zeros((p*m,q*m)) + if len(M) != p * q: + raise ValueError("The shape of M is invalid!") + M = M.reshape(p, q) + H = np.zeros((p * m, q * m)) # Define the base cyclic shift matrix S (m x m) S = np.zeros((m, m)) @@ -381,72 +430,139 @@ def ldpc_construction(self,p,q,m,M): for i in range(p): for j in range(q): # Get the value from matrix M - shift = M[i, j] % (m+1) - + shift = M[i, j] % (m + 1) + # Create the shifted version of S based on the value in M if shift == 0: - H_ij = np.zeros((m,m)) # Zero matrix for shift 0 + H_ij = np.zeros((m, m)) # Zero matrix for shift 0 else: H_ij = np.linalg.matrix_power(S, shift) - + # Place H_ij in the corresponding block of H - H[i * m: (i + 1) * m, j * m: (j + 1) * m] = H_ij + H[i * m : (i + 1) * m, j * m : (j + 1) * m] = H_ij return H - def bivariate_bycicle_construction(self,parameters) -> CSSCode: - ''' + @staticmethod + def multiply_polynomials_mod_l(p1, p2, l) -> int: + res = 0 + for i in range(p2.bit_length()): + if (p2 >> i) & 1: # checks i-th bit is 1 + shifted = p1 << i + overflow = ( + shifted >> l + ) # mod x^l, higher degree polynomials wrap around + not_overflow = shifted & ((1 << l) - 1) + res ^= not_overflow ^ overflow + + return res + + @staticmethod + def gx_mask_to_bin(gx_mask, fs, l) -> int: + gx_bin = 1 + + for i in range(gx_mask.bit_length()): + if (gx_mask >> i) & 1: + gx_bin = CodeConstructor.multiply_polynomials_mod_l(gx_bin, fs[i], l) + + return gx_bin + + @staticmethod + def poly_mod_f2(p, g): + deg_g = g.bit_length() - 1 + deg_p = p.bit_length() - 1 + while deg_p >= deg_g: # continuously subtract multiples of g(x) from p(x) + shift = deg_p - deg_g + p ^= g << shift + deg_p = p.bit_length() - 1 + + return p # p is remainder + + def _build_circulant_matrix(self, row, length=None) -> np.ndarray: + if length is None: + length = len(row) + col_idx = np.arange(length)[None, :] + row_idx = np.arange(length)[:, None] + + shift_matrix = (col_idx - row_idx) % length + return row[shift_matrix] + + def _build_bicycle_css_code(self, A, B) -> CSSCode: + HX = np.hstack((A, B)) + HZ = np.hstack((B.T, A.T)) + return CSSCode(HX, HZ) + + def generalised_bicycle_code(self, parameters) -> CSSCode: + """ + parameters = [gx_mask, a, b, f1, ..., fn] (but flattened) + gx_mask (list[binary]): binary mask of which irreducable factors of (x^l - 1) make up g(x) + a (list[binary]): binary representation of polynomial a(x) + b (list[binary]): binary representation of polynomial b(x) + fk (list[binary]): binary representation of the irreducable factors of (x^l - 1) + """ + l = self.para_dict["l"] + + a = parameters[l : 2 * l] + b = parameters[2 * l : 3 * l] + + a_array = np.array(a) + b_array = np.array(b) + + A = self._build_circulant_matrix(a_array) + B = self._build_circulant_matrix(b_array) + + return self._build_bicycle_css_code(A, B) + + def bivariate_bycicle_construction(self, parameters) -> CSSCode: + """ para_dict={ 'm'=int, # n = 2lm - 'l'=int, + 'l'=int, parameters = [boolean,boolean,boolean,boolean,boolean,boolean,int,int,int,int,int,int] } - ''' - m = self.para_dict['m'] - l = self.para_dict['l'] - Sm = np.zeros((m,m)) - Sl = np.zeros((l,l)) + """ + m = self.para_dict["m"] + l = self.para_dict["l"] + Sm = np.zeros((m, m)) + Sl = np.zeros((l, l)) # A_p = para_dict['A'] # B_p = para_dict['B'] - A_p = np.array([(parameters[i]%2,parameters[i+6]) for i in range(3)]) - B_p = np.array([(parameters[i]%2,parameters[i+6]) for i in range(3,6)]) + A_p = np.array([(parameters[i] % 2, parameters[i + 6]) for i in range(3)]) + B_p = np.array([(parameters[i] % 2, parameters[i + 6]) for i in range(3, 6)]) for i in range(m): - Sm[i][(i+1) % m]=1 + Sm[i][(i + 1) % m] = 1 for j in range(l): - Sl[j][(j+1) % l]=1 + Sl[j][(j + 1) % l] = 1 # print(f'SL:\n{Sl}\n,Sm:\n{Sm}') - x = np.kron(Sl,np.eye(m)) - y = np.kron(np.eye(l),Sm) + x = np.kron(Sl, np.eye(m)) + y = np.kron(np.eye(l), Sm) # print(f'x:\n{x}\n,y:\n{y}') - A = np.zeros((l*m,l*m)) - B= np.zeros((l*m,l*m)) + A = np.zeros((l * m, l * m)) + B = np.zeros((l * m, l * m)) for i in range(len(A_p)): - if A_p[i][0]==0: - A += np.linalg.matrix_power(x , (A_p[i][1] % l)) + if A_p[i][0] == 0: + A += np.linalg.matrix_power(x, (A_p[i][1] % l)) else: - A += np.linalg.matrix_power(y , (A_p[i][1] % l)) - if B_p[i][0]==0: - B += np.linalg.matrix_power(x , (B_p[i][1] % m)) + A += np.linalg.matrix_power(y, (A_p[i][1] % l)) + if B_p[i][0] == 0: + B += np.linalg.matrix_power(x, (B_p[i][1] % m)) else: - B += np.linalg.matrix_power(y , (B_p[i][1] % m)) + B += np.linalg.matrix_power(y, (B_p[i][1] % m)) A = A % 2 B = B % 2 - # for i in A: - # for j in i: - # print(f'{int(j)} ',end='') - # print() - # print(f'A:\n{A}\n,B:\n{B}') - HX = np.hstack((A, B)) - HZ = np.hstack((B.T,A.T)) - # print(f'n:{2*l*m}\n,rank(A):\n{np.linalg.matrix_rank(A)},\n\nrank(B):\n{np.linalg.matrix_rank(B)}') - css = CSSCode(HX,HZ) - return css - def pg_ldpc_hgp_construction(self,M): - H1 = self.pg_ldpc_construction(self.para_dict['p'],self.para_dict['q'],self.para_dict['m'],M) - H2 = self.pg_ldpc_construction(self.para_dict['p'],self.para_dict['q'],self.para_dict['m'],M) - r1,n1 = H1.shape - r2,n2 = H2.shape + + return self._build_bicycle_css_code(A, B) + + def pg_ldpc_hgp_construction(self, M): + H1 = self.pg_ldpc_construction( + self.para_dict["p"], self.para_dict["q"], self.para_dict["m"], M + ) + H2 = self.pg_ldpc_construction( + self.para_dict["p"], self.para_dict["q"], self.para_dict["m"], M + ) + r1, n1 = H1.shape + r2, n2 = H2.shape # HX = [H1 ⊗ In2 | Ir1 ⊗ H2.T] # HZ = [In1 ⊗ H2 | H1.T ⊗ Ir2] HX_left = np.kron(H1, np.eye(n2)) @@ -457,104 +573,95 @@ def pg_ldpc_hgp_construction(self,M): HZ_right = np.kron(H1.T, np.eye(r2)) HZ = np.hstack((HZ_left, HZ_right)) - - return CSSCode(HX,HZ) + return CSSCode(HX, HZ) - def pg_ldpc_construction(self,p,q,m,M): - ''' + def pg_ldpc_construction(self, p, q, m, M): + """ M has p*q elements in the permutation group; each element is represented by a permutation from 0 to m-1 M is a p*q * m long np.array - ''' - H = np.zeros((m*p,m*q)) - for i in range(p*q): + """ + H = np.zeros((m * p, m * q)) + for i in range(p * q): for j in range(m): - x = int(i//q + j) - y = int(i%q + M[m*i + j]) - H[x,y] = 1 + x = int(i // q + j) + y = int(i % q + M[m * i + j]) + H[x, y] = 1 return H - def quasi_cyclic_generalized_bicycle_code(self,parameters): - if 'g' in self.para_dict.keys(): - g_size_a = self.para_dict['g'] - g_size_b = self.para_dict['g'] + + def quasi_cyclic_generalized_bicycle_code(self, parameters): + if "g" in self.para_dict.keys(): + g_size_a = self.para_dict["g"] + g_size_b = self.para_dict["g"] else: - g_size_a = int(self.para_dict['ga']) - g_size_b = int(self.para_dict['gb']) - A = parameters['A'] - B = parameters['B'] + g_size_a = int(self.para_dict["ga"]) + g_size_b = int(self.para_dict["gb"]) + A = parameters["A"] + B = parameters["B"] # print(g) if A.shape[0] != A.shape[1]: - raise ValueError('A should have a square shape') + raise ValueError("A should have a square shape") if B.shape[0] != B.shape[1]: - raise ValueError('B should have a square shape') + raise ValueError("B should have a square shape") if g_size_a * A.shape[0] != g_size_b * B.shape[0]: - raise ValueError('The shape of B(A) and B(B) is not equal.') - A = parameters['A'] - B = parameters['B'] - B_A = self.corresponding_matrix_BA(g_size_a,A) - B_B = self.corresponding_matrix_BA(g_size_b,B) + raise ValueError("The shape of B(A) and B(B) is not equal.") + A = parameters["A"] + B = parameters["B"] + B_A = self.corresponding_matrix_BA(g_size_a, A) + B_B = self.corresponding_matrix_BA(g_size_b, B) # check = (B_B.T@B_A.T+B_A.T@B_B.T)%2 # print(check.any()) - H_x = np.hstack((B_A,B_B)) - H_z = np.hstack((B_B.T,B_A.T)) + H_x = np.hstack((B_A, B_B)) + H_z = np.hstack((B_B.T, B_A.T)) # check = (H_z @ H_x.T + B_B.T@B_A.T+B_A.T@B_B.T)%2 # print(check.any()) # return H_x,H_z - return CSSCode(H_x,H_z) - + return CSSCode(H_x, H_z) - def corresponding_matrix_BA(self,g_size,A): - r,c = A.shape[0],A.shape[1] + def corresponding_matrix_BA(self, g_size, A): + r, c = A.shape[0], A.shape[1] for i in range(r): for j in range(c): - if j==0: - r_matrix = self.corresponding_matrix_Ba(g_size,A[i][j]) + if j == 0: + r_matrix = self._build_circulant_matrix(A[i][j], g_size) else: - r_matrix = np.hstack((r_matrix,self.corresponding_matrix_Ba(g_size,A[i][j]))) - if i==0: + r_matrix = np.hstack( + (r_matrix, self._build_circulant_matrix(A[i][j], g_size)) + ) + if i == 0: B_A = r_matrix else: - B_A = np.vstack((B_A,r_matrix)) + B_A = np.vstack((B_A, r_matrix)) return B_A - def corresponding_matrix_Ba(self,g_size,a): - B_a = np.zeros((g_size,g_size),dtype=int) - for i in range(g_size): - B_a = (B_a + a[i] * self.g_power(g_size,i))%2 - return B_a - def g_power(self,g_size,p): - - g_p = np.zeros((g_size,g_size),dtype=int) - for i in range(g_size): - g_p[i][int((i+p)%g_size)]=1 - return g_p - def arbitrary_bivariate_bicycle_code(self,parameters): - + def arbitrary_bivariate_bicycle_code(self, parameters): # a=[a_0,a_1,...,a_{l+g-2}] is a rep of coefficient of A=a_0*I + a_1*x + a_2*x^2 +...+ a_{l-1}*x^{l-1} + a_l*y + a_{l+1}*y^2 +...+ a_{l+g-2}*y^{g-1} - l = self.para_dict['l'] - g = self.para_dict['g'] - a = parameters[:(l+g-1)] - b = parameters[(l+g-1):] - A = np.zeros((l,l,g),dtype=int) - B = np.zeros((l,l,g),dtype=int) + l = self.para_dict["l"] + g = self.para_dict["g"] + a = parameters[: (l + g - 1)] + b = parameters[(l + g - 1) :] + A = np.zeros((l, l, g), dtype=int) + B = np.zeros((l, l, g), dtype=int) # x: g_l \otimes I_g for i in range(l): for j in range(l): # a_j = 1 if a[j] == 1: - A[i][(i+j)%l][0]=(A[i][(i+j)%l][0]+1)%2 + A[i][(i + j) % l][0] = (A[i][(i + j) % l][0] + 1) % 2 if b[j] == 1: - B[i][(i+j)%l][0]=(B[i][(i+j)%l][0]+1)%2 + B[i][(i + j) % l][0] = (B[i][(i + j) % l][0] + 1) % 2 # y: I_l \otimes g_g - for i in range(l): - for j in range(g-1): - if a[j+l] == 1: - A[i][i][j+1]=(A[i][i][j+1]+1)%2 - if b[j+l] == 1: - B[i][i][j+1]=(B[i][i][j+1]+1)%2 - return self.quasi_cyclic_generalized_bicycle_code({'A':A,'B':B}) - def symmetric_quasi_cyclic_generalized_bicycle_code(self,A): + for i in range(l): + for j in range(g - 1): + if a[j + l] == 1: + A[i][i][j + 1] = (A[i][i][j + 1] + 1) % 2 + if b[j + l] == 1: + B[i][i][j + 1] = (B[i][i][j + 1] + 1) % 2 + + return self.quasi_cyclic_generalized_bicycle_code({"A": A, "B": B}) + + def symmetric_quasi_cyclic_generalized_bicycle_code(self, A): B = A r = A.shape[0] c = A.shape[1] @@ -562,14 +669,5 @@ def symmetric_quasi_cyclic_generalized_bicycle_code(self,A): for i in range(r): for j in range(c): for k in range(g): - B[i][j][k] = A[j][i][(g-k)%g] - return self.quasi_cyclic_generalized_bicycle_code({'A':A,'B':B}) - - - - - - - - - + B[i][j][k] = A[j][i][(g - k) % g] + return self.quasi_cyclic_generalized_bicycle_code({"A": A, "B": B}) diff --git a/data/BO_initial_points/GBB_BO_initial_points_0_1_3_12_6.pkl b/data/BO_initial_points/GBB_BO_initial_points_0_1_3_12_6.pkl new file mode 100644 index 0000000..30d12cc Binary files /dev/null and b/data/BO_initial_points/GBB_BO_initial_points_0_1_3_12_6.pkl differ diff --git a/data/BO_results/gbb_BO_12_6_0_42_1.pkl b/data/BO_results/gbb_BO_12_6_0_42_1.pkl new file mode 100644 index 0000000..7734384 Binary files /dev/null and b/data/BO_results/gbb_BO_12_6_0_42_1.pkl differ diff --git a/data/BO_results/lorenzo_results/bb_dist_144_10_18.pkl b/data/BO_results/lorenzo_results/bb_dist_144_10_18.pkl new file mode 100644 index 0000000..5851282 Binary files /dev/null and b/data/BO_results/lorenzo_results/bb_dist_144_10_18.pkl differ diff --git a/data/BO_results/lorenzo_results/bb_dist_144_4_18.pkl b/data/BO_results/lorenzo_results/bb_dist_144_4_18.pkl new file mode 100644 index 0000000..ad86cc1 Binary files /dev/null and b/data/BO_results/lorenzo_results/bb_dist_144_4_18.pkl differ diff --git a/data/BO_results/lorenzo_results/bb_dist_144_4_20.pkl b/data/BO_results/lorenzo_results/bb_dist_144_4_20.pkl new file mode 100644 index 0000000..0bc0185 Binary files /dev/null and b/data/BO_results/lorenzo_results/bb_dist_144_4_20.pkl differ diff --git a/data/BO_results/lorenzo_results/gb_ler_144_14_9.pkl b/data/BO_results/lorenzo_results/gb_ler_144_14_9.pkl new file mode 100644 index 0000000..ed953a6 Binary files /dev/null and b/data/BO_results/lorenzo_results/gb_ler_144_14_9.pkl differ diff --git a/data/BO_results/lorenzo_results/gb_ler_144_16_6.pkl b/data/BO_results/lorenzo_results/gb_ler_144_16_6.pkl new file mode 100644 index 0000000..c014165 Binary files /dev/null and b/data/BO_results/lorenzo_results/gb_ler_144_16_6.pkl differ diff --git a/data/BO_results/lorenzo_results/gb_ler_144_30_8.pkl b/data/BO_results/lorenzo_results/gb_ler_144_30_8.pkl new file mode 100644 index 0000000..5df3a57 Binary files /dev/null and b/data/BO_results/lorenzo_results/gb_ler_144_30_8.pkl differ diff --git a/data/BO_results/lorenzo_results/gb_ler_144_36_6.pkl b/data/BO_results/lorenzo_results/gb_ler_144_36_6.pkl new file mode 100644 index 0000000..befcdfe Binary files /dev/null and b/data/BO_results/lorenzo_results/gb_ler_144_36_6.pkl differ diff --git a/dataset.py b/dataset.py index 7059859..9b5aad5 100644 --- a/dataset.py +++ b/dataset.py @@ -1,152 +1,181 @@ -from typing import Callable, Tuple, List, Dict, Optional import pickle import numpy as np - from evaluation.decoder_based_evaluation import CSS_Evaluator from code_construction.code_construction import CodeConstructor - -import matplotlib.pyplot as plt from torch.utils.data import Dataset -import numpy as np -import random -from evaluation.circuit_level_noise import MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise - - -import numpy as np -import torch -class Get_new_points_function(): - def __init__(self,method='qc-ldpc-hgp',hyperparameters = {'p': 2, 'q': 6, 'm': 2},encode='None',param={'p':0.5}): +from evaluation.circuit_level_noise import ( + MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise, +) + + +class Get_new_points_function: + def __init__( + self, + method="qc-ldpc-hgp", + hyperparameters={"p": 2, "q": 6, "m": 2}, + encode="None", + param={"p": 0.5}, + ): self.method = method self.hyperparameters = hyperparameters - self.code_constructor = CodeConstructor(method,hyperparameters) + self.code_constructor = CodeConstructor(method, hyperparameters) self.encode = encode self.init = False self.param = param - def get_new_points_function(self,number): - if self.method == 'qc-ldpc-hgp': + def get_new_points_function(self, number): + if self.method == "qc-ldpc-hgp": new_points = self.get_new_points_HGP(number) - elif self.method == 'bb': - new_points = self.get_new_bb_vector(number,self.param) + elif self.method == "bb": + new_points = self.get_new_bb_vector(number, self.param) return new_points - - def get_new_points_HGP(self,number): - return np.random.randint(0, self.hyperparameters['m'] + 1, (number, self.hyperparameters['p'] * self.hyperparameters['q'])) - def get_new_bb_vector(self,number,param={'p':0.5}): - density_expectation = param['p'] - results = [] - l = self.hyperparameters['l'] - g = self.hyperparameters['g'] + def get_new_points_HGP(self, number): + return np.random.randint( + 0, + self.hyperparameters["m"] + 1, + (number, self.hyperparameters["p"] * self.hyperparameters["q"]), + ) - while number>0: - new_point = np.random.choice([0,1], size=(l+g-1)*2,p=[1-density_expectation,density_expectation]) + def get_new_bb_vector(self, number, param={"p": 0.5}): + density_expectation = param["p"] + results = [] + l = self.hyperparameters["l"] + g = self.hyperparameters["g"] + + while number > 0: + new_point = np.random.choice( + [0, 1], + size=(l + g - 1) * 2, + p=[1 - density_expectation, density_expectation], + ) c = self.code_constructor.construct(new_point) - if c.k==0: + if c.k == 0: continue else: results.append(new_point) number -= 1 return np.array(results) - - class QEC_Dataset(Dataset): - def __init__(self,l,g,load = False,number = 100,save = True,p = 0.05,gnp_param = {'p':0.5},path = './data/codes/',noise_model='depolarizing',noise_and_decoder_param={}): + def __init__( + self, + l, + g, + load=False, + number=100, + save=True, + p=0.05, + gnp_param={"p": 0.5}, + path="./data/codes/", + noise_model="depolarizing", + noise_and_decoder_param={}, + ): # l = 6 # g = 3 - para_dict = {'l':l,'g':g} + para_dict = {"l": l, "g": g} self.noise_model = noise_model self.p = p - - self.codeconstructor = CodeConstructor(method='bb',para_dict = para_dict) - self.gnp = Get_new_points_function(method='bb',hyperparameters = para_dict,param=gnp_param).get_new_points_function + + self.codeconstructor = CodeConstructor(method="bb", para_dict=para_dict) + self.gnp = Get_new_points_function( + method="bb", hyperparameters=para_dict, param=gnp_param + ).get_new_points_function + def objectivefunction(code): - if noise_model == 'depolarizing': + if noise_model == "depolarizing": evaluator = CSS_Evaluator(code.hx, code.hz) pL = evaluator.Get_logical_error_rate_Monte_Carlo( physical_error_rate=p, xyz_bias=[1, 1, 1], - trail=noise_and_decoder_param.get('trail',10_000) + trail=noise_and_decoder_param.get("trail", 10_000), ) else: - rounds = noise_and_decoder_param.get('rounds',12) - decoder = noise_and_decoder_param.get('decoder','bplsd') - custom_error_model = noise_and_decoder_param.get('custom_error_model',{}) + rounds = noise_and_decoder_param.get("rounds", 12) + decoder = noise_and_decoder_param.get("decoder", "bplsd") + custom_error_model = noise_and_decoder_param.get( + "custom_error_model", {} + ) css = self.codeconstructor.construct(code) mc = MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise( - css, - noise_model=noise_model, - p=p, - rounds=rounds, - custom_error_model=custom_error_model, - decoder=decoder - ) - pL = mc.run(shots= noise_and_decoder_param.get('trail',100_000), max_error = noise_and_decoder_param.get('max_error',100), num_workers = noise_and_decoder_param.get('num_worker',24)) + css, + noise_model=noise_model, + p=p, + rounds=rounds, + custom_error_model=custom_error_model, + decoder=decoder, + ) + pL = mc.run( + shots=noise_and_decoder_param.get("trail", 100_000), + max_error=noise_and_decoder_param.get("max_error", 100), + num_workers=noise_and_decoder_param.get("num_workers", 24), + ) return pL + self.objectfunction = objectivefunction # self.normalizer = Normalizer(mode='log_pos_trans',possitive=True) self.number = number self.load = load if load == False: - - self.get_a_dataset(l,g,number,save,path=path) + self.get_a_dataset(l, g, number, save, path=path) else: - self.load_dataset(l,g,number,path=path) + self.load_dataset(l, g, number, path=path) + def __len__(self): return self.number - def get_a_dataset(self,l,g,number,save,path = './data/codes/'): + def get_a_dataset(self, l, g, number, save, path="./data/codes/"): def get_k(x): x = self.codeconstructor.construct(x) return x.k + X = [] y = [] - print('Generating dataset...') + print("Generating dataset...") - - - - # target = 200 # sample = 0 number1 = number - while number1>0: + while number1 > 0: c = self.gnp(1) - if get_k(c[0])!=0: + if get_k(c[0]) != 0: # target-=1 - number1-=1 + number1 -= 1 X.append(c[0]) - # sample+=1 + # sample+=1 pl = self.objectfunction(c[0]) y.append(pl) - print(f'code[{number-number1}],logical error rate:{pl}') + print(f"code[{number - number1}],logical error rate:{pl}") self.X = np.array(X) self.y = np.array(y) if save: - with open(path+f'{l}_{g}_{number}_{self.noise_model}.pkl','wb') as f: - pickle.dump((X,y),f) - return X,y - - def load_dataset(self,l,g,number,path = './data/codes/'): - print('loading') - file_name = path+f'{l}_{g}_{number}_{self.noise_model}.pkl' - with open(file_name, 'rb') as f: - X,y = pickle.load(f) + with open(path + f"{l}_{g}_{number}_{self.noise_model}.pkl", "wb") as f: + pickle.dump((X, y), f) + return X, y + + def load_dataset(self, l, g, number, path="./data/codes/"): + print("loading") + file_name = path + f"{l}_{g}_{number}_{self.noise_model}.pkl" + with open(file_name, "rb") as f: + X, y = pickle.load(f) self.X = X self.y = y - print('successfully loaded') - - def __getitem__(self,idx): + print("successfully loaded") + + def __getitem__(self, idx): return self.X[idx], self.y[idx] + + from torch.utils.data import Subset + + class QECSubset(Subset): def __init__(self, base_dataset, indices): - super().__init__(base_dataset,indices) + super().__init__(base_dataset, indices) self.X = base_dataset.X[indices] self.y = base_dataset.y[indices] self.indices = indices @@ -155,4 +184,4 @@ def __len__(self): return len(self.X) def __getitem__(self, idx): - return self.X[idx], self.y[idx] \ No newline at end of file + return self.X[idx], self.y[idx] diff --git a/evaluation/__pycache__/circuit_level_noise.cpython-310.pyc b/evaluation/__pycache__/circuit_level_noise.cpython-310.pyc deleted file mode 100644 index b3506a3..0000000 Binary files a/evaluation/__pycache__/circuit_level_noise.cpython-310.pyc and /dev/null differ diff --git a/evaluation/__pycache__/cln.cpython-310.pyc b/evaluation/__pycache__/cln.cpython-310.pyc deleted file mode 100644 index 330edc7..0000000 Binary files a/evaluation/__pycache__/cln.cpython-310.pyc and /dev/null differ diff --git a/evaluation/__pycache__/css_decode_sim.cpython-310.pyc b/evaluation/__pycache__/css_decode_sim.cpython-310.pyc deleted file mode 100644 index 5c5765a..0000000 Binary files a/evaluation/__pycache__/css_decode_sim.cpython-310.pyc and /dev/null differ diff --git a/evaluation/__pycache__/decoder_based_evaluation.cpython-310.pyc b/evaluation/__pycache__/decoder_based_evaluation.cpython-310.pyc deleted file mode 100644 index 8ca845e..0000000 Binary files a/evaluation/__pycache__/decoder_based_evaluation.cpython-310.pyc and /dev/null differ diff --git a/evaluation/__pycache__/distance.cpython-310.pyc b/evaluation/__pycache__/distance.cpython-310.pyc deleted file mode 100644 index 0695124..0000000 Binary files a/evaluation/__pycache__/distance.cpython-310.pyc and /dev/null differ diff --git a/evaluation/__pycache__/non_decoder_based_evaluation.cpython-310.pyc b/evaluation/__pycache__/non_decoder_based_evaluation.cpython-310.pyc deleted file mode 100644 index ca5da58..0000000 Binary files a/evaluation/__pycache__/non_decoder_based_evaluation.cpython-310.pyc and /dev/null differ diff --git a/evaluation/__pycache__/qldpc.cpython-310.pyc b/evaluation/__pycache__/qldpc.cpython-310.pyc deleted file mode 100644 index fe4ca33..0000000 Binary files a/evaluation/__pycache__/qldpc.cpython-310.pyc and /dev/null differ diff --git a/evaluation/circuit_level_noise.py b/evaluation/circuit_level_noise.py index 73daf5c..65601d2 100644 --- a/evaluation/circuit_level_noise.py +++ b/evaluation/circuit_level_noise.py @@ -1,8 +1,8 @@ -import stim,sinter +import stim, sinter import matplotlib.pyplot as plt -from code_construction.code_construction import CodeConstructor,CSSCode +from code_construction.code_construction import CodeConstructor import numpy as np -from ldpc.sinter_decoders.sinter_lsd_decoder import SinterLsdDecoder + class CircuitGenerator: """ @@ -25,80 +25,106 @@ class CircuitGenerator: def __init__(self, css): self.hx = np.array(css.hx, dtype=np.uint8) % 2 self.hz = np.array(css.hz, dtype=np.uint8) % 2 - self.n = int(self.hx.shape[1]) + self.n = int(self.hx.shape[1]) assert self.hz.shape[1] == self.n self.mX = int(self.hx.shape[0]) self.mZ = int(self.hz.shape[0]) - self.Lx = None if getattr(css, 'lx', None) is None else (np.array(css.lx, dtype=np.uint8) % 2) - self.Lz = None if getattr(css, 'lz', None) is None else (np.array(css.lz, dtype=np.uint8) % 2) - if self.Lx is not None: assert self.Lx.shape[1] == self.n - if self.Lz is not None: assert self.Lz.shape[1] == self.n + self.Lx = ( + None + if getattr(css, "lx", None) is None + else (np.array(css.lx, dtype=np.uint8) % 2) + ) + self.Lz = ( + None + if getattr(css, "lz", None) is None + else (np.array(css.lz, dtype=np.uint8) % 2) + ) + if self.Lx is not None: + assert self.Lx.shape[1] == self.n + if self.Lz is not None: + assert self.Lz.shape[1] == self.n self.data_start = 0 - self.data_end = self.n + self.data_end = self.n self.ancZ_start = self.data_end - self.ancZ_end = self.ancZ_start + self.mZ + self.ancZ_end = self.ancZ_start + self.mZ self.ancX_start = self.ancZ_end - self.ancX_end = self.ancX_start + self.mX + self.ancX_end = self.ancX_start + self.mX + + def _supp_hz(self, j): + return np.flatnonzero(self.hz[j]).tolist() + def _supp_hx(self, j): + return np.flatnonzero(self.hx[j]).tolist() - def _supp_hz(self, j): return np.flatnonzero(self.hz[j]).tolist() - def _supp_hx(self, j): return np.flatnonzero(self.hx[j]).tolist() - def _supp_Lz(self, j): return np.flatnonzero(self.Lz[j]).tolist() - def _supp_Lx(self, j): return np.flatnonzero(self.Lx[j]).tolist() + def _supp_Lz(self, j): + return np.flatnonzero(self.Lz[j]).tolist() - def dataqubit_preparation(self,pp,pd,data_prep,measure_data): + def _supp_Lx(self, j): + return np.flatnonzero(self.Lx[j]).tolist() + + def dataqubit_preparation(self, pp, pd, data_prep, measure_data): c = stim.Circuit() data_idxs = list(range(self.data_start, self.data_end)) if self.n > 0: - prep_mode = data_prep if data_prep != 'auto' else ('X' if measure_data == 'X' else 'Z') - if prep_mode == 'Z': - c.append_operation('R', data_idxs) - c.append_operation('X_ERROR', data_idxs, pp) - elif prep_mode == 'X': - c.append_operation('RX', data_idxs) - c.append_operation('Z_ERROR', data_idxs, pp) - elif prep_mode != 'none': + prep_mode = ( + data_prep + if data_prep != "auto" + else ("X" if measure_data == "X" else "Z") + ) + if prep_mode == "Z": + c.append_operation("R", data_idxs) + c.append_operation("X_ERROR", data_idxs, pp) + elif prep_mode == "X": + c.append_operation("RX", data_idxs) + c.append_operation("Z_ERROR", data_idxs, pp) + elif prep_mode != "none": raise ValueError(f"invalid data_prep={data_prep!r}") if pd > 0: - c.append_operation('DEPOLARIZE1', data_idxs, pd) + c.append_operation("DEPOLARIZE1", data_idxs, pd) return c - def measureancilla_preparation(self,pp): + + def measureancilla_preparation(self, pp): c = stim.Circuit() - ancX = list(range(self.ancX_start,self.ancX_end)) - ancZ = list(range(self.ancZ_start,self.ancZ_end)) - c.append_operation('R',ancX) - c.append_operation('R',ancZ) - c.append_operation('X_ERROR',ancX,pp) - c.append_operation('X_ERROR',ancZ,pp) + ancX = list(range(self.ancX_start, self.ancX_end)) + ancZ = list(range(self.ancZ_start, self.ancZ_end)) + c.append_operation("R", ancX) + c.append_operation("R", ancZ) + c.append_operation("X_ERROR", ancX, pp) + c.append_operation("X_ERROR", ancZ, pp) return c - def cnotcircuit(self,pd,pg): - ancX = list(range(self.ancX_start,self.ancX_end)) - ancZ = list(range(self.ancZ_start,self.ancZ_end)) + + def cnotcircuit(self, pd, pg): + ancX = list(range(self.ancX_start, self.ancX_end)) + ancZ = list(range(self.ancZ_start, self.ancZ_end)) data_idxs = list(range(self.data_start, self.data_end)) c = stim.Circuit() - c.append_operation('H',ancX) - c.append_operation('DEPOLARIZE1',ancX,pd) - c.append_operation('TICK') + c.append_operation("H", ancX) + c.append_operation("DEPOLARIZE1", ancX, pd) + c.append_operation("TICK") cnot_gate_set = [] ordered_cnot_gate_set = [] # get all the cnot gates for tar in range(self.mZ): ctrls = self._supp_hz(tar) for ctrl in ctrls: - cnot_gate_set.append((data_idxs[ctrl],ancZ[tar])) - + cnot_gate_set.append((data_idxs[ctrl], ancZ[tar])) # arranging cnot gates for cnot in cnot_gate_set: placed_flag = False - + for order in range(len(ordered_cnot_gate_set)): placable_flag = True for arranged_cnot in ordered_cnot_gate_set[order]: - if arranged_cnot[0] == cnot[0] or arranged_cnot[0] == cnot[1] or arranged_cnot[1] == cnot[0] or arranged_cnot[1] == cnot[1]: + if ( + arranged_cnot[0] == cnot[0] + or arranged_cnot[0] == cnot[1] + or arranged_cnot[1] == cnot[0] + or arranged_cnot[1] == cnot[1] + ): placable_flag = False if placable_flag: ordered_cnot_gate_set[order].append(cnot) @@ -110,23 +136,28 @@ def cnotcircuit(self,pd,pg): # adding cnots to the circuit for cnots in ordered_cnot_gate_set: for cnot in cnots: - c.append_operation('CX',cnot) - c.append_operation('DEPOLARIZE2',cnot) - c.append_operation('TICK') - - cnot_gate_set= [] + c.append_operation("CX", cnot) + c.append_operation("DEPOLARIZE2", cnot) + c.append_operation("TICK") + + cnot_gate_set = [] ordered_cnot_gate_set = [] for ctrl in range(self.mX): tars = self._supp_hx(ctrl) for tar in tars: - cnot_gate_set.append((ancX[ctrl],data_idxs[tar])) + cnot_gate_set.append((ancX[ctrl], data_idxs[tar])) for cnot in cnot_gate_set: placed_flag = False - + for order in range(len(ordered_cnot_gate_set)): placable_flag = True for arranged_cnot in ordered_cnot_gate_set[order]: - if arranged_cnot[0] == cnot[0] or arranged_cnot[0] == cnot[1] or arranged_cnot[1] == cnot[0] or arranged_cnot[1] == cnot[1]: + if ( + arranged_cnot[0] == cnot[0] + or arranged_cnot[0] == cnot[1] + or arranged_cnot[1] == cnot[0] + or arranged_cnot[1] == cnot[1] + ): placable_flag = False if placable_flag: ordered_cnot_gate_set[order].append(cnot) @@ -138,118 +169,134 @@ def cnotcircuit(self,pd,pg): # adding cnots to the circuit for cnots in ordered_cnot_gate_set: for cnot in cnots: - c.append_operation('CX',cnot) - c.append_operation('DEPOLARIZE2',cnot) - c.append_operation('TICK') + c.append_operation("CX", cnot) + c.append_operation("DEPOLARIZE2", cnot) + c.append_operation("TICK") - c.append_operation('H',ancX) - c.append_operation('DEPOLARIZE1',ancX,pd) - c.append_operation('TICK') + c.append_operation("H", ancX) + c.append_operation("DEPOLARIZE1", ancX, pd) + c.append_operation("TICK") return c - def measurements(self,ps): - ancX = list(range(self.ancX_start,self.ancX_end)) - ancZ = list(range(self.ancZ_start,self.ancZ_end)) + + def measurements(self, ps): + ancX = list(range(self.ancX_start, self.ancX_end)) + ancZ = list(range(self.ancZ_start, self.ancZ_end)) c = stim.Circuit() - c.append_operation('X_ERROR',ancZ,ps) - c.append_operation('X_ERROR',ancX,ps) - c.append_operation('MR',ancZ) - c.append_operation('MR',ancX) - c.append_operation('X_ERROR',ancZ,ps) - c.append_operation('X_ERROR',ancX,ps) + c.append_operation("X_ERROR", ancZ, ps) + c.append_operation("X_ERROR", ancX, ps) + c.append_operation("MR", ancZ) + c.append_operation("MR", ancX) + c.append_operation("X_ERROR", ancZ, ps) + c.append_operation("X_ERROR", ancX, ps) return c - def generate( self, - rounds=1, + rounds=1, noise_model=None, burn_in_rounds=1, - measure_data='Z', # 'Z' or 'X' + measure_data="Z", # 'Z' or 'X' add_observable=True, - data_prep='auto', # 'auto'|'Z'|'X'|'none' - add_endcaps=False # start False; turn True + data_prep="auto", # 'auto'|'Z'|'X'|'none' + add_endcaps=False, # start False; turn True ) -> stim.Circuit: if noise_model is None: - noise_model = {'pd':0.0, 'pg':0.0, 'ps':0.0, 'pp':0.0} - pd = float(noise_model.get('pd', 0.0)) - pg = float(noise_model.get('pg', 0.0)) - ps = float(noise_model.get('ps', 0.0)) - pp = float(noise_model.get('pp', 0.0)) + noise_model = {"pd": 0.0, "pg": 0.0, "ps": 0.0, "pp": 0.0} + pd = float(noise_model.get("pd", 0.0)) + pg = float(noise_model.get("pg", 0.0)) + ps = float(noise_model.get("ps", 0.0)) + pp = float(noise_model.get("pp", 0.0)) - assert measure_data in ('Z', 'X') + assert measure_data in ("Z", "X") assert rounds >= 1 and burn_in_rounds >= 0 c = stim.Circuit() data_idxs = list(range(self.data_start, self.data_end)) # ---- data preparation (once) ---- - c += self.dataqubit_preparation(pp,pd,data_prep,measure_data) + c += self.dataqubit_preparation(pp, pd, data_prep, measure_data) c += self.measureancilla_preparation(pp) - c.append_operation('DEPOLARIZE1',data_idxs,pd) - c += self.cnotcircuit(pd,pg) + c.append_operation("DEPOLARIZE1", data_idxs, pd) + c += self.cnotcircuit(pd, pg) c += self.measurements(ps) - if measure_data == 'Z': + if measure_data == "Z": for i in range(self.mZ): - c.append_operation('DETECTOR',stim.target_rec(i-self.mX-self.mZ)) + c.append_operation("DETECTOR", stim.target_rec(i - self.mX - self.mZ)) else: for i in range(self.mX): - c.append_operation('DETECTOR',stim.target_rec(i-self.mX)) - + c.append_operation("DETECTOR", stim.target_rec(i - self.mX)) # repeat: rep = stim.Circuit() - rep.append_operation('TICK') - rep.append_operation('DEPOLARIZE1',data_idxs) - rep += self.cnotcircuit(pd,pg) + rep.append_operation("TICK") + rep.append_operation("DEPOLARIZE1", data_idxs) + rep += self.cnotcircuit(pd, pg) rep += self.measurements(ps) - for i in range(self.mZ+self.mX): - rep.append_operation('DETECTOR',[stim.target_rec(i-self.mX-self.mZ),stim.target_rec(i-2*(self.mX+self.mZ))]) + for i in range(self.mZ + self.mX): + rep.append_operation( + "DETECTOR", + [ + stim.target_rec(i - self.mX - self.mZ), + stim.target_rec(i - 2 * (self.mX + self.mZ)), + ], + ) if rounds > 1: - c += (rounds-1) * rep - + c += (rounds - 1) * rep - # final detectors # final observables - if measure_data == 'Z': - c.append('X_ERROR',data_idxs,ps) - c.append('MZ',data_idxs) - for det in range(self.mZ): - tars = self._supp_hz(det) - rec = [] - for i in tars: - rec.append(i-self.n) - rec.append(det-self.mZ-self.mX-self.n) - c.append_operation('DETECTOR',[stim.target_rec(i) for i in rec]) - for logical in range(len(self.Lz)): - tars = self._supp_Lz(logical) - c.append_operation( "OBSERVABLE_INCLUDE", [stim.target_rec(k-self.n) for k in tars], [logical] ) - else: - c.append('Z_ERROR',data_idxs,ps) - c.append('MX',data_idxs) - for det in range(self.mX): - tars = self._supp_hx(det) - rec = [] - for i in tars: - rec.append(i-self.n) - rec.append(det-self.mX-self.n) - c.append_operation('DETECTOR',[stim.target_rec(i) for i in rec]) - for logical in range(len(self.Lx)): - tars = self._supp_Lx(logical) - c.append_operation( "OBSERVABLE_INCLUDE", [stim.target_rec(k-self.n) for k in tars], [logical] ) - + if measure_data == "Z": + c.append("X_ERROR", data_idxs, ps) + c.append("MZ", data_idxs) + for det in range(self.mZ): + tars = self._supp_hz(det) + rec = [] + for i in tars: + rec.append(i - self.n) + rec.append(det - self.mZ - self.mX - self.n) + c.append_operation("DETECTOR", [stim.target_rec(i) for i in rec]) + for logical in range(len(self.Lz)): + tars = self._supp_Lz(logical) + c.append_operation( + "OBSERVABLE_INCLUDE", + [stim.target_rec(k - self.n) for k in tars], + [logical], + ) + else: + c.append("Z_ERROR", data_idxs, ps) + c.append("MX", data_idxs) + for det in range(self.mX): + tars = self._supp_hx(det) + rec = [] + for i in tars: + rec.append(i - self.n) + rec.append(det - self.mX - self.n) + c.append_operation("DETECTOR", [stim.target_rec(i) for i in rec]) + for logical in range(len(self.Lx)): + tars = self._supp_Lx(logical) + c.append_operation( + "OBSERVABLE_INCLUDE", + [stim.target_rec(k - self.n) for k in tars], + [logical], + ) return c - - class MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise: - def __init__(self, css, noise_model='SD6', p=0.01, rounds=1, - custom_error_model={}, decoder='bplsd', decoder_kwargs=None): + def __init__( + self, + css, + noise_model="SD6", + p=0.01, + rounds=1, + custom_error_model={}, + decoder="bplsd", + decoder_kwargs=None, + ): """ decoder: 'pymatching' | 'bplsd' decoder_kwargs: only used when decoder='bplsd', passed into SinterLsdDecoder(...) @@ -259,16 +306,20 @@ def __init__(self, css, noise_model='SD6', p=0.01, rounds=1, self.p = p self.rounds = rounds self.custom_error_model = custom_error_model - self.decoder = (decoder or 'pymatching').lower() + self.decoder = (decoder or "pymatching").lower() default_bplsd = dict( - max_iter=5, - bp_method='ms', - ms_scaling_factor=0.8, - schedule='parallel', - lsd_order=7, + max_iter=5, + bp_method="ms", + ms_scaling_factor=0.8, + schedule="parallel", + lsd_order=7, + ) + self.decoder_kwargs = ( + default_bplsd + if (decoder_kwargs is None) + else {**default_bplsd, **decoder_kwargs} ) - self.decoder_kwargs = default_bplsd if (decoder_kwargs is None) else {**default_bplsd, **decoder_kwargs} return @staticmethod @@ -278,69 +329,87 @@ def _json_safe(x): if isinstance(x, (np.generic,)): return x.item() if isinstance(x, dict): - return {str(k): MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise._json_safe(v) - for k, v in x.items()} + return { + str( + k + ): MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise._json_safe( + v + ) + for k, v in x.items() + } if isinstance(x, (list, tuple, set)): - return [MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise._json_safe(v) for v in x] + return [ + MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise._json_safe( + v + ) + for v in x + ] if isinstance(x, (str, int, float, bool)) or x is None: return x return str(x) - def get_error_param(self, noise_model: str, p: float, custom_error_model={}) -> dict: + def get_error_param( + self, noise_model: str, p: float, custom_error_model={} + ) -> dict: noise_model = noise_model.upper() - if noise_model not in ['SD6', 'SI1000', 'EM3', 'CUSTOM']: + if noise_model not in ["SD6", "SI1000", "EM3", "CUSTOM"]: raise ValueError(f"Unknown noise model: {noise_model}") - if noise_model == 'SD6': - err = {'pd': p, 'pg': p, 'ps': p, 'pp': p} - elif noise_model == 'SI1000': - err = {'pd': 0.1*p, 'pg': 1.0*p, 'ps': 0.5*p, 'pp': 0.5*p} - elif noise_model == 'EM3': - err = {'pd': 0.25*p, 'pg': 1.0*p, 'ps': 0.5*p, 'pp': 0.5*p} - elif noise_model == 'CUSTOM': + if noise_model == "SD6": + err = {"pd": p, "pg": p, "ps": p, "pp": p} + elif noise_model == "SI1000": + err = {"pd": 0.1 * p, "pg": 1.0 * p, "ps": 0.5 * p, "pp": 0.5 * p} + elif noise_model == "EM3": + err = {"pd": 0.25 * p, "pg": 1.0 * p, "ps": 0.5 * p, "pp": 0.5 * p} + elif noise_model == "CUSTOM": err = custom_error_model return err - def get_sinter_task(self, - css, - mode='both', # 'X'|'Z' - order='ZX', - noise_model='SD6', - p=0.01, - rounds=1, - burn_in_rounds=1, - custom_error_model={}): - + def get_sinter_task( + self, + css, + mode="both", # 'X'|'Z' + order="ZX", + noise_model="SD6", + p=0.01, + rounds=1, + burn_in_rounds=1, + custom_error_model={}, + ): css_code_circuit_generator = CircuitGenerator(css) - error_param = self.get_error_param(noise_model=noise_model, p=p, custom_error_model=custom_error_model) + error_param = self.get_error_param( + noise_model=noise_model, p=p, custom_error_model=custom_error_model + ) mode_up = mode.upper() - if mode_up not in ('X', 'Z'): + if mode_up not in ("X", "Z"): raise ValueError(f"mode must be 'X' or 'Z', got {mode!r}") - measure_data = 'X' if mode_up == 'X' else 'Z' + measure_data = "X" if mode_up == "X" else "Z" circuit = css_code_circuit_generator.generate( rounds=rounds, noise_model=error_param, burn_in_rounds=burn_in_rounds, - measure_data=measure_data, - add_observable=True + measure_data=measure_data, + add_observable=True, ) meta = { - 'p': self.p, - 'n': getattr(css, 'n', None), - 'k': getattr(css, 'k', None), - 'hx': getattr(css, 'hx', None), - 'hz': getattr(css, 'hz', None), - 'lx': getattr(css, 'lx', None), - 'lz': getattr(css, 'lz', None), - 'rounds': rounds, - 'burn_in_rounds': burn_in_rounds, - 'mode': mode, - 'error_model': noise_model, - 'custom_error_model': custom_error_model if noise_model.lower() == 'custom' else None, + "p": self.p, + "n": getattr(css, "n", None), + "k": getattr(css, "k", None), + "hx": getattr(css, "hx", None), + "hz": getattr(css, "hz", None), + "lx": getattr(css, "lx", None), + "lz": getattr(css, "lz", None), + "rounds": rounds, + "burn_in_rounds": burn_in_rounds, + "mode": mode, + "error_model": noise_model, + "custom_error_model": custom_error_model + if noise_model.lower() == "custom" + else None, } meta = self._json_safe(meta) @@ -348,54 +417,62 @@ def get_sinter_task(self, def _choose_decoder(self): - if self.decoder == 'bplsd': + if self.decoder == "bplsd": try: from ldpc.sinter_decoders.sinter_lsd_decoder import SinterLsdDecoder except Exception as ex: - raise RuntimeError( - "decoder='bplsd' need ldpc package" - ) from ex + raise RuntimeError("decoder='bplsd' need ldpc package") from ex - decoders = ['bplsd'] - custom_decoders = { - 'bplsd': SinterLsdDecoder(**self.decoder_kwargs) - } + decoders = ["bplsd"] + custom_decoders = {"bplsd": SinterLsdDecoder(**self.decoder_kwargs)} else: - - decoders = ['pymatching'] + decoders = ["pymatching"] custom_decoders = None return decoders, custom_decoders def run(self, shots: int = 1_000, max_error: int = 100, num_workers: int = 4): decoders_ZX, custom_ZX = self._choose_decoder() - samples_Z = sinter.collect( - tasks=self.get_sinter_task(self.css, mode='Z', - noise_model=self.noise_model, p=self.p, - rounds=self.rounds, burn_in_rounds=1, - custom_error_model=self.custom_error_model), + tasks=self.get_sinter_task( + self.css, + mode="Z", + noise_model=self.noise_model, + p=self.p, + rounds=self.rounds, + burn_in_rounds=1, + custom_error_model=self.custom_error_model, + ), decoders=decoders_ZX, custom_decoders=custom_ZX, - max_shots=shots//2, max_errors=max_error//2, num_workers=num_workers, + max_shots=shots // 2, + max_errors=max_error // 2, + num_workers=num_workers, ) - sZ = sum(getattr(r, 'shots', getattr(r, 'shot_count', 0)) for r in samples_Z) - eZ = sum(getattr(r, 'errors', getattr(r, 'error_count', 0)) for r in samples_Z) + sZ = sum(getattr(r, "shots", getattr(r, "shot_count", 0)) for r in samples_Z) + eZ = sum(getattr(r, "errors", getattr(r, "error_count", 0)) for r in samples_Z) if sZ == 0: raise RuntimeError("No shots collected for Z task.") pZ = eZ / sZ samples_X = sinter.collect( - tasks=self.get_sinter_task(self.css, mode='X', - noise_model=self.noise_model, p=self.p, - rounds=self.rounds, burn_in_rounds=1, - custom_error_model=self.custom_error_model), + tasks=self.get_sinter_task( + self.css, + mode="X", + noise_model=self.noise_model, + p=self.p, + rounds=self.rounds, + burn_in_rounds=1, + custom_error_model=self.custom_error_model, + ), decoders=decoders_ZX, custom_decoders=custom_ZX, - max_shots=shots//2, max_errors=max_error//2, num_workers=num_workers, + max_shots=shots // 2, + max_errors=max_error // 2, + num_workers=num_workers, ) - sX = sum(getattr(r, 'shots', getattr(r, 'shot_count', 0)) for r in samples_X) - eX = sum(getattr(r, 'errors', getattr(r, 'error_count', 0)) for r in samples_X) + sX = sum(getattr(r, "shots", getattr(r, "shot_count", 0)) for r in samples_X) + eX = sum(getattr(r, "errors", getattr(r, "error_count", 0)) for r in samples_X) if sX == 0: raise RuntimeError("No shots collected for X task.") pX = eX / sX @@ -403,37 +480,145 @@ def run(self, shots: int = 1_000, max_error: int = 100, num_workers: int = 4): pL = 1 - (1 - pX) * (1 - pZ) return pL -if __name__ == '__main__': + +if __name__ == "__main__": l = 12 g = 6 - code_constructor = CodeConstructor(method='bb', para_dict={'l': l, 'g': g}) + code_constructor = CodeConstructor(method="bb", para_dict={"l": l, "g": g}) gross_code = [ - 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., - 0., 0., 0., 0., 0., 0., 0., 1., 0., 0. + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + ] + code_144_8 = [ + 0.0, + 1.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 1.0, + ] + code_144_26 = [ + 0.0, + 1.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 1.0, ] - code_144_8 = [0., 1., 1., 0., 1., 1., 0., 1., 0., 0., 0., 1., 0., 0., 0., 1., 1., 1., - 1., 1., 0., 0., 0., 0., 0., 0., 1., 1., 1., 0., 1., 0., 0., 1.] - code_144_26 = [0., 1., 1., 0., 1., 1., 0., 1., 0., 0., 0., 1., 0., 0., 0., 1., 1., 1., - 1., 1., 0., 0., 0., 0., 0., 0., 1., 1., 1., 0., 1., 0., 0., 1.] css = code_constructor.construct(gross_code) # css = CodeConstructor('rotated-surface').construct(3) - code_name = ['gross_code','bb code [144,26]','bb code [144,8]'] - i=0 - for code_ in [gross_code,code_144_26,code_144_8]: + code_name = ["gross_code", "bb code [144,26]", "bb code [144,8]"] + i = 0 + for code_ in [gross_code, code_144_26, code_144_8]: p_list = [] pl_list = [] - for ppp in np.linspace(4.8,6.2,4): + for ppp in np.linspace(4.8, 6.2, 4): p = np.exp(-ppp) p_list.append(p) css = code_constructor.construct(code_) - mc = MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise(css,noise_model='SD6',p=p,rounds=12,custom_error_model={},decoder='bplsd') - pl = mc.run(shots= 100_000, max_error = 1000, num_workers = 24) + mc = MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise( + css, + noise_model="SD6", + p=p, + rounds=12, + custom_error_model={}, + decoder="bplsd", + ) + pl = mc.run(shots=100_000, max_error=1000, num_workers=24) pl_list.append(pl) - print(f'physical error rate:{p},logical error rate:{pl}') - plt.plot(p_list,pl_list,label=f'{code_name[i]}') - i+=1 - plt.yscale('log') - plt.xscale('log') + print(f"physical error rate:{p},logical error rate:{pl}") + plt.plot(p_list, pl_list, label=f"{code_name[i]}") + i += 1 + plt.yscale("log") + plt.xscale("log") plt.legend() plt.show() diff --git a/evaluation/css_decode_sim.py b/evaluation/css_decode_sim.py index 32248d7..45c9754 100644 --- a/evaluation/css_decode_sim.py +++ b/evaluation/css_decode_sim.py @@ -167,7 +167,6 @@ def _single_run(self): # randomly generate the error self.error_x, self.error_z = self._generate_error() - if self.channel_update is None: # decode z @@ -257,7 +256,7 @@ def _encoded_error_rates(self): # calculate the residual error residual_x = (self.error_x + self.bpd_x.osdw_decoding) % 2 residual_z = (self.error_z + self.bpd_z.osdw_decoding) % 2 - print(f'new syndrome:{self.hz@residual_x %2}') + print(f"new syndrome:{self.hz @ residual_x % 2}") # check for logical X-error if (self.lz @ residual_x % 2).any(): @@ -524,7 +523,7 @@ def run_decode_sim(self): self._single_run() pbar.set_description( - f"d_max: {self.min_logical_weight}; OSDW_WER: {self.osdw_word_error_rate*100:.3g}±{self.osdw_word_error_rate_eb*100:.2g}%; OSDW: {self.osdw_logical_error_rate*100:.3g}±{self.osdw_logical_error_rate_eb*100:.2g}%; OSD0: {self.osd0_logical_error_rate*100:.3g}±{self.osd0_logical_error_rate_eb*100:.2g}%;" + f"d_max: {self.min_logical_weight}; OSDW_WER: {self.osdw_word_error_rate * 100:.3g}±{self.osdw_word_error_rate_eb * 100:.2g}%; OSDW: {self.osdw_logical_error_rate * 100:.3g}±{self.osdw_logical_error_rate_eb * 100:.2g}%; OSD0: {self.osd0_logical_error_rate * 100:.3g}±{self.osd0_logical_error_rate_eb * 100:.2g}%;" ) current_time = time.time() @@ -568,4 +567,4 @@ def output_dict(self): if key in self.output_keys: output_dict[key] = value # return output_dict - return json.dumps(output_dict, sort_keys=True, indent=4) \ No newline at end of file + return json.dumps(output_dict, sort_keys=True, indent=4) diff --git a/evaluation/decoder_based_evaluation.py b/evaluation/decoder_based_evaluation.py index 9fd8798..d155264 100644 --- a/evaluation/decoder_based_evaluation.py +++ b/evaluation/decoder_based_evaluation.py @@ -6,20 +6,19 @@ from evaluation.css_decode_sim import css_decode_sim from bposd.hgp import hgp -from ldpc.codes import rep_code -from scipy.linalg import null_space from scipy.special import comb -from itertools import product -from math import floor import matplotlib.pyplot as plt from bposd import bposd_decoder from ldpc.bplsd_decoder import BpLsdDecoder -import itertools from bposd.css import css_code -def binomial_probability(n, d_e,p_p): - return comb(n, d_e) * (p_p ** d_e) * ((1 - p_p) ** (n - d_e)) + + +def binomial_probability(n, d_e, p_p): + return comb(n, d_e) * (p_p**d_e) * ((1 - p_p) ** (n - d_e)) + + def gf2_rref(H): - """ Transform the matrix H to row echelon form """ + """Transform the matrix H to row echelon form""" H = H.copy() rows, cols = H.shape r = 0 # The row we're working on @@ -30,29 +29,32 @@ def gf2_rref(H): continue # No pivot in this column, skip it pivot_row = pivot[0] + r H[[r, pivot_row]] = H[[pivot_row, r]] - + # Zero out all other entries in this column for ri in range(rows): if ri != r and H[ri, c] == 1: H[ri] ^= H[r] - + r += 1 if r == rows: break - + return H + def find_null_space(H): - """ calculate the null space of the matrix H """ + """calculate the null space of the matrix H""" # Transform H to row echelon form rref = gf2_rref(H) rows, cols = rref.shape - pivot_cols = np.array([np.where(rref[r])[0][0] for r in range(rows) if np.any(rref[r])]) + pivot_cols = np.array( + [np.where(rref[r])[0][0] for r in range(rows) if np.any(rref[r])] + ) free_cols = np.array([c for c in range(cols) if c not in pivot_cols]) - + # Generate the matrix G G = np.zeros((len(free_cols), cols), dtype=int) - + # Set the identity matrix in the free columns for i, f_col in enumerate(free_cols): G[i, f_col] = 1 # set the free variable @@ -61,23 +63,24 @@ def find_null_space(H): break if rref[r, f_col] == 1: G[i, p_col] = 1 # set the pivot variable - + return G + def Get_G_of_check_matrix(H): G = find_null_space(H) return G.T -def Get_distribution_dij(G,trails=1000): - d_ij={} + + +def Get_distribution_dij(G, trails=1000): + d_ij = {} for i in range(trails): - x_i = np.random.randint(0,2,(G.shape[1],1)) - x_j = np.random.randint(0,2,(G.shape[1],1)) + x_i = np.random.randint(0, 2, (G.shape[1], 1)) + x_j = np.random.randint(0, 2, (G.shape[1], 1)) if (x_i == x_j).all(): continue else: - - - d = (G@x_i%2 + G@x_j%2)%2 + d = (G @ x_i % 2 + G @ x_j % 2) % 2 d = np.sum(d) if d in d_ij: d_ij[d] += 1 @@ -86,91 +89,100 @@ def Get_distribution_dij(G,trails=1000): return d_ij -class CSS_Evaluator(): - def __init__(self,hx,hz): +class CSS_Evaluator: + def __init__(self, hx, hz): # print(f'initializing') - self.hx = hx - self.hz = hz - qcode = css_code(self.hx, self.hz) + qcode = css_code(hx, hz) + self.hx = qcode.hx + self.hz = qcode.hz self.lx = qcode.lx - self.lz = qcode.lz # logical operators + self.lz = qcode.lz # logical operators self.k = qcode.K self.n = qcode.N # print(f'initialized,n:{self.n},k:{self.k}') - - def Get_error_rate(self,physical_error_rate=0.01,trail=2000): + def Get_error_rate(self, physical_error_rate=0.01, trail=2000): if self.k == 0: return 1 decoder_sim = css_decode_sim( hx=self.hx, hz=self.hz, - error_rate= physical_error_rate, - xyz_error_bias= [1,1, 1], - target_runs= trail, - seed= 0, - bp_method= "minimum_sum", - ms_scaling_factor= 0, - max_iter= self.n*2, - osd_method= "osd_cs", - osd_order= 2, - save_interval= 2, - output_file= None, - check_code= 0, - tqdm_disable= 0, - run_sim= 0, - channel_update= None, - hadamard_rotate= 0, - hadamard_rotate_sector1_length= 0, - error_bar_precision_cutoff= 1e-4 + error_rate=physical_error_rate, + xyz_error_bias=[1, 1, 1], + target_runs=trail, + seed=0, + bp_method="minimum_sum", + ms_scaling_factor=0, + max_iter=self.n * 2, + osd_method="osd_cs", + osd_order=2, + save_interval=2, + output_file=None, + check_code=0, + tqdm_disable=0, + run_sim=0, + channel_update=None, + hadamard_rotate=0, + hadamard_rotate_sector1_length=0, + error_bar_precision_cutoff=1e-4, ) logical_error_rate = decoder_sim.run_decode_sim() return logical_error_rate - def Get_logical_error_rate_Monte_Carlo(self,physical_error_rate= 0.01,xyz_bias = [1,1,1],trail=10000): + + def Get_logical_error_rate_Monte_Carlo( + self, physical_error_rate=0.01, xyz_bias=[1, 1, 1], trail=10000 + ): if self.k == 0: return 1 # print('start to get errors') - errors = self.Get_errors(trail,physical_error_rate=physical_error_rate,xyz_bias=xyz_bias) + errors = self.Get_errors( + trail, physical_error_rate=physical_error_rate, xyz_bias=xyz_bias + ) fail = 0 self.init_decoder(physical_error_rate) # print('start!') # i = 0 - for error_x,error_z in errors: + for error_x, error_z in errors: # i+= 1 # if i % 1000 == 0: - # print(f'finish {i} errors') - fail += self.Single_run(error_x,error_z) - return fail/trail - - def Get_errors(self,number,physical_error_rate=0.01,xyz_bias=[1,1,1]): - - error_random_numbers = np.random.uniform(0,1,(number,self.n))/physical_error_rate + # print(f'finish {i} errors') + fail += self.Single_run(error_x, error_z) + return fail / trail + + def Get_errors(self, number, physical_error_rate=0.01, xyz_bias=[1, 1, 1]): + + error_random_numbers = ( + np.random.uniform(0, 1, (number, self.n)) / physical_error_rate + ) xyz_sum = sum(xyz_bias) errors = [] for i in range(number): error_x = np.zeros(self.n) error_z = np.zeros(self.n) for j in range(len(error_random_numbers[i])): - if error_random_numbers[i][j]<= xyz_bias[0]/xyz_sum: + if error_random_numbers[i][j] <= xyz_bias[0] / xyz_sum: error_x[j] = 1 - elif error_random_numbers[i][j]<= (xyz_bias[0]+xyz_bias[1])/xyz_sum: + elif ( + error_random_numbers[i][j] <= (xyz_bias[0] + xyz_bias[1]) / xyz_sum + ): error_x[j] = 1 error_z[j] = 1 - elif error_random_numbers[i][j]<= 1: + elif error_random_numbers[i][j] <= 1: error_z[j] = 1 - errors.append((error_x,error_z)) + errors.append((error_x, error_z)) return errors - def init_decoder(self,physical_error_rate,bp = 'lsd'): - if bp == 'lsd': + + def init_decoder(self, physical_error_rate, bp="lsd"): + if bp == "lsd": self.bpd_z = BpLsdDecoder( self.hx, - error_rate = 2*physical_error_rate/3, + error_rate=2 * physical_error_rate / 3, channel_probs=[None], - max_iter=self.n//2, + max_iter=self.n // 2, bp_method="product_sum", - schedule = 'parallel', + schedule="parallel", lsd_method="lsd_cs", lsd_order=2, ) @@ -178,86 +190,96 @@ def init_decoder(self,physical_error_rate,bp = 'lsd'): # decoder for X-errors self.bpd_x = BpLsdDecoder( self.hz, - error_rate = 2*physical_error_rate/3, + error_rate=2 * physical_error_rate / 3, channel_probs=[None], - max_iter=self.n//2, + max_iter=self.n // 2, bp_method="product_sum", - schedule = 'parallel', + schedule="parallel", lsd_method="lsd_cs", lsd_order=2, ) else: self.bpd_z = bposd_decoder( self.hx, - error_rate=physical_error_rate/2, - channel_probs=[None], #assign error_rate to each qubit. This will override "error_rate" input variable - max_iter=self.n, #the maximum number of iterations for BP) + error_rate=physical_error_rate / 2, + channel_probs=[ + None + ], # assign error_rate to each qubit. This will override "error_rate" input variable + max_iter=self.n, # the maximum number of iterations for BP) bp_method="ms", - ms_scaling_factor=7, #min sum scaling factor. If set to zero the variable scaling factor method is used - osd_method="osd_cs", #the OSD method. Choose from: 1) "osd_e", "osd_cs", "osd0" - osd_order=7 #the osd search depth - ) + ms_scaling_factor=7, # min sum scaling factor. If set to zero the variable scaling factor method is used + osd_method="osd_cs", # the OSD method. Choose from: 1) "osd_e", "osd_cs", "osd0" + osd_order=7, # the osd search depth + ) # decoder for X-errors self.bpd_x = bposd_decoder( self.hz, - error_rate=physical_error_rate/2, - channel_probs=[None], #assign error_rate to each qubit. This will override "error_rate" input variable - max_iter=self.n, #the maximum number of iterations for BP) + error_rate=physical_error_rate / 2, + channel_probs=[ + None + ], # assign error_rate to each qubit. This will override "error_rate" input variable + max_iter=self.n, # the maximum number of iterations for BP) bp_method="ms", - ms_scaling_factor=7, #min sum scaling factor. If set to zero the variable scaling factor method is used - osd_method="osd_cs", #the OSD method. Choose from: 1) "osd_e", "osd_cs", "osd0" - osd_order=7 #the osd search depth - ) - - - - def Get_precise_logical_error_rate(self,physical_error_rate=0.01,trail=1000,block = 14): - '''using the depolorizing channel to calculate the logical error rate''' + ms_scaling_factor=7, # min sum scaling factor. If set to zero the variable scaling factor method is used + osd_method="osd_cs", # the OSD method. Choose from: 1) "osd_e", "osd_cs", "osd0" + osd_order=7, # the osd search depth + ) + + def Get_precise_logical_error_rate( + self, physical_error_rate=0.01, trail=1000, block=14 + ): + """using the depolorizing channel to calculate the logical error rate""" self.init_decoder(physical_error_rate) - - + # TODO p_l = 0 # PL = [0] - for n_e in range(1,self.n): - p_ne = binomial_probability(self.n,n_e,physical_error_rate) + for n_e in range(1, self.n): + p_ne = binomial_probability(self.n, n_e, physical_error_rate) - if n_e <= block+1 : - p_l_ne = self.P_L_given_n_e(n_e,trail) + if n_e <= block + 1: + p_l_ne = self.P_L_given_n_e(n_e, trail) # print('n_e:',n_e) else: p_l_ne = 1 - p_l += p_ne*p_l_ne + p_l += p_ne * p_l_ne # PL.append(p_l_ne) - - + return p_l - def Get_precise_logical_error_rate_iterative(self, physical_error_rate=0.01, total_trail=10000, block=999, init_samples=200, batch_size=100): + + def Get_precise_logical_error_rate_iterative( + self, + physical_error_rate=0.01, + total_trail=10000, + block=999, + init_samples=200, + batch_size=100, + ): # print('start to estimate the logical error rate') - mode = 'lsd' - self.init_decoder(physical_error_rate,bp = 'lsd') - + mode = "lsd" + self.init_decoder(physical_error_rate, bp="lsd") - strata = list(range(2, min(block + 2,self.n))) + strata = list(range(2, min(block + 2, self.n))) sample_counts = {n_e: 0 for n_e in strata} success_sums = {n_e: 0.0 for n_e in strata} - bino = {n_e: binomial_probability(self.n,n_e,physical_error_rate) for n_e in strata} + bino = { + n_e: binomial_probability(self.n, n_e, physical_error_rate) + for n_e in strata + } # print('initiallized') - for n_e in strata: # print(f'n_e: {n_e}') - if mode == 'lsd' and n_e>=50: - mode = 'osd' - self.init_decoder(physical_error_rate,bp = 'osd') + if mode == "lsd" and n_e >= 50: + mode = "osd" + self.init_decoder(physical_error_rate, bp="osd") p_l_est = self.P_L_given_n_e(n_e, init_samples) sample_counts[n_e] = init_samples success_sums[n_e] = p_l_est * init_samples - + used_samples = init_samples * len(strata) - while used_samples < total_trail: remaining = total_trail - used_samples @@ -265,35 +287,48 @@ def Get_precise_logical_error_rate_iterative(self, physical_error_rate=0.01, tot weight = {n_e: 0.0 for n_e in strata} for n_e in strata: - if success_sums[n_e]==0: - weight[n_e] = np.log(bino[n_e])+np.log(sample_counts[n_e]+current_batch-1)-3*np.log(sample_counts[n_e]+current_batch) + if success_sums[n_e] == 0: + weight[n_e] = ( + np.log(bino[n_e]) + + np.log(sample_counts[n_e] + current_batch - 1) + - 3 * np.log(sample_counts[n_e] + current_batch) + ) else: - weight[n_e] = np.log(bino[n_e])+np.log((sample_counts[n_e]-success_sums[n_e])*success_sums[n_e]*current_batch)-np.log(sample_counts[n_e]+current_batch)-3*np.log(sample_counts[n_e]) + weight[n_e] = ( + np.log(bino[n_e]) + + np.log( + (sample_counts[n_e] - success_sums[n_e]) + * success_sums[n_e] + * current_batch + ) + - np.log(sample_counts[n_e] + current_batch) + - 3 * np.log(sample_counts[n_e]) + ) # print(weight) - + max_n_e = max(weight, key=weight.get) - if mode == 'lsd' and max_n_e>=50: - mode = 'osd' - self.init_decoder(physical_error_rate,bp = 'osd') - elif mode == 'osd' and max_n_e<50: - mode = 'lsd' - self.init_decoder(physical_error_rate,bp = 'lsd') + if mode == "lsd" and max_n_e >= 50: + mode = "osd" + self.init_decoder(physical_error_rate, bp="osd") + elif mode == "osd" and max_n_e < 50: + mode = "lsd" + self.init_decoder(physical_error_rate, bp="lsd") p_l_est = self.P_L_given_n_e(max_n_e, current_batch) sample_counts[max_n_e] += current_batch success_sums[max_n_e] = current_batch * p_l_est used_samples += current_batch - p_l_total = 0 p_1 = self.get_error_rate_1() - p_l_total += binomial_probability(self.n,1,physical_error_rate)*p_1 + p_l_total += binomial_probability(self.n, 1, physical_error_rate) * p_1 # print(p_1) for n_e in strata: - p_l_total += bino[n_e] * success_sums[n_e]/sample_counts[n_e] + p_l_total += bino[n_e] * success_sums[n_e] / sample_counts[n_e] # print(sample_counts) # print({n_e: success_sums[n_e]/sample_counts[n_e] for n_e in strata}) - + return p_l_total + def get_error_rate_1(self): errors = [] for i in range(self.n): @@ -301,177 +336,195 @@ def get_error_rate_1(self): z = np.zeros(self.n) x[i] = 1 z[i] = 0 - errors.append((x,z)) + errors.append((x, z)) x = np.zeros(self.n) z = np.zeros(self.n) x[i] = 1 z[i] = 1 - errors.append((x,z)) + errors.append((x, z)) x = np.zeros(self.n) z = np.zeros(self.n) x[i] = 0 z[i] = 1 - errors.append((x,z)) + errors.append((x, z)) p = 0 for i in errors: - p+= self.Single_run(i[0],i[1]) - return p/len(errors) + p += self.Single_run(i[0], i[1]) + return p / len(errors) # def Get_logical_error_rate_through_interpolation(self,physical_error_rate=0.01,trail = 1000,block=20): # self.init_decoder(physical_error_rate) - - + # # TODO # p_l = 0 # PL = [0] # for n_e in range(1,self.n): # p_ne = binomial_probability(self.n,n_e,physical_error_rate) - # if n_e <= block+1 : + # if n_e <= block+1 : # p_l_ne = self.P_L_given_n_e(n_e,physical_error_rate,int(trail*(0.98**n_e))) # # print('n_e:',n_e) # else: # p_l_ne = 1 # p_l += p_ne*p_l_ne # PL.append(p_l_ne) - + # print(PL) # return p_l,PL - def P_L_given_n_e(self,n_e,trail=100): - '''calculate the probability of logical error given n_e error''' + def P_L_given_n_e(self, n_e, trail=100): + """calculate the probability of logical error given n_e error""" # TODO p_l_ne = 0 - print(n_e) - for t in range(int(trail*(0.97**n_e))): - error_x,error_z = self.Get_error(n_e) - - p_l_ne += self.Single_run(error_x,error_z) + # print(n_e) + for t in range(int(trail * (0.97**n_e))): + error_x, error_z = self.Get_error(n_e) + + p_l_ne += self.Single_run(error_x, error_z) # print(p_l_ne/trail) - - return p_l_ne/trail + + return p_l_ne / trail + def Single_run(self, error_x, error_z): if error_z.any(): - synd_z = (self.hx @ error_z) % 2 - rec_z = self.bpd_z.decode(synd_z) + synd_z = (self.hx.dot(error_z)) % 2 + rec_z = self.bpd_z.decode(synd_z) residual_z = (error_z + rec_z) % 2 else: - residual_z = error_z.copy() if error_x.any(): - synd_x = (self.hz @ error_x) % 2 - rec_x = self.bpd_x.decode(synd_x) + synd_x = (self.hz.dot(error_x)) % 2 + rec_x = self.bpd_x.decode(synd_x) residual_x = (error_x + rec_x) % 2 else: residual_x = error_x.copy() - - sz_cleared = ((self.hx @ residual_z) % 2 == 0).all() - sx_cleared = ((self.hz @ residual_x) % 2 == 0).all() + sz_cleared = ((self.hx.dot(residual_z)) % 2 == 0).all() + sx_cleared = ((self.hz.dot(residual_x)) % 2 == 0).all() if not (sz_cleared and sx_cleared): - return 1 - - logical_x_flip = ((self.lx @ residual_x) % 2).any() - logical_z_flip = ((self.lz @ residual_z) % 2).any() + logical_x_flip = ((self.lx.dot(residual_x)) % 2).any() + logical_z_flip = ((self.lz.dot(residual_z)) % 2).any() if logical_x_flip or logical_z_flip: return 1 - return 0 - - - - def Get_error(self,d_e): - '''sample an error of weight d_e under the depolorizing model''' + def Get_error(self, d_e): + """sample an error of weight d_e under the depolorizing model""" error_x = np.zeros(self.n).astype(int) error_z = np.zeros(self.n).astype(int) - + indices = np.random.choice(self.n, d_e, replace=False) for index in indices: - error_type = np.random.choice(['x', 'z','y']) - if error_type == 'x': - error_x[ index] = 1 - elif error_type == 'z': - error_z[ index] = 1 + error_type = np.random.choice(["x", "z", "y"]) + if error_type == "x": + error_x[index] = 1 + elif error_type == "z": + error_z[index] = 1 else: - error_x[ index] = 1 - error_z[ index] = 1 + error_x[index] = 1 + error_z[index] = 1 return error_x, error_z - def _channel_update(self,update_direction): - ''' + def _channel_update(self, update_direction): + """ Function updates the channel probability vector for the second decoding component based on the first. The channel probability updates can be derived from Bayes' rule. - ''' + """ - #x component first, then z component - if update_direction=="x->z": - decoder_probs=np.zeros(self.N) + # x component first, then z component + if update_direction == "x->z": + decoder_probs = np.zeros(self.N) for i in range(self.N): - if self.bpd_x.osdw_decoding[i]==1: - if (self.channel_probs_x[i]+self.channel_probs_y[i])==0: - decoder_probs[i]=0 + if self.bpd_x.osdw_decoding[i] == 1: + if (self.channel_probs_x[i] + self.channel_probs_y[i]) == 0: + decoder_probs[i] = 0 else: - decoder_probs[i]=self.channel_probs_y[i]/(self.channel_probs_x[i]+self.channel_probs_y[i]) - elif self.bpd_x.osdw_decoding[i]==0: - decoder_probs[i]=self.channel_probs_z[i]/(1-self.channel_probs_x[i]-self.channel_probs_y[i]) - + decoder_probs[i] = self.channel_probs_y[i] / ( + self.channel_probs_x[i] + self.channel_probs_y[i] + ) + elif self.bpd_x.osdw_decoding[i] == 0: + decoder_probs[i] = self.channel_probs_z[i] / ( + 1 - self.channel_probs_x[i] - self.channel_probs_y[i] + ) + self.bpd_z.update_channel_probs(decoder_probs) - #z component first, then x component - elif update_direction=="z->x": + # z component first, then x component + elif update_direction == "z->x": self.bpd_z.osdw_decoding - decoder_probs=np.zeros(self.N) + decoder_probs = np.zeros(self.N) for i in range(self.N): - if self.bpd_z.osdw_decoding[i]==1: - - if (self.channel_probs_z[i]+self.channel_probs_y[i])==0: - decoder_probs[i]=0 + if self.bpd_z.osdw_decoding[i] == 1: + if (self.channel_probs_z[i] + self.channel_probs_y[i]) == 0: + decoder_probs[i] = 0 else: - decoder_probs[i]=self.channel_probs_y[i]/(self.channel_probs_z[i]+self.channel_probs_y[i]) - elif self.bpd_z.osdw_decoding[i]==0: - decoder_probs[i]=self.channel_probs_x[i]/(1-self.channel_probs_z[i]-self.channel_probs_y[i]) + decoder_probs[i] = self.channel_probs_y[i] / ( + self.channel_probs_z[i] + self.channel_probs_y[i] + ) + elif self.bpd_z.osdw_decoding[i] == 0: + decoder_probs[i] = self.channel_probs_x[i] / ( + 1 - self.channel_probs_z[i] - self.channel_probs_y[i] + ) - self.bpd_x.update_channel_probs(decoder_probs) - - -if __name__ == '__main__': +if __name__ == "__main__": # h = np.array([[1,1,0,0,0,0,1],[1,0,0,1,1,0,1],[0,1,1,0,1,1,0],[0,0,0,1,1,1,1]]) from parameter_converter import Parameters_converter - converter = Parameters_converter(method = 'HGP',hyperparameters={'p': 4, 'q': 4, 'm': 3}) + + converter = Parameters_converter( + method="HGP", hyperparameters={"p": 4, "q": 4, "m": 3} + ) from Construct_Code import CSS from Evaluate import CSS_Evaluator + code1 = np.array([1, 3, 0, 3, 2, 0, 1, 2, 2, 1, 3, 0, 0, 1, 0, 3]) code1 = converter.generate_parameters(code1) css_instance_1 = CSS() - css_instance_1.construct(method='HGP',para_dict=code1) + css_instance_1.construct(method="HGP", para_dict=code1) import pyldpc - h, _ = pyldpc.make_ldpc(6, 2,3) + h, _ = pyldpc.make_ldpc(6, 2, 3) # print(h) - h =np.array ([[ 1,1,1,1,1,1,0,0,0,0,0,0],[ 0,0,0,0,0,0,1,1,1,1,1,1]]) - surface_code = hgp(h,h) + h = np.array( + [[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1]] + ) + surface_code = hgp(h, h) Hx = surface_code.hx Hz = surface_code.hz - Hx = np.array([[1,1,1,0,0,0,1,0],[0,0,1,0,1,1,1,0],[1,1,0,1,0,0,0,1],[0,0,0,1,1,1,0,1]]) - Hz = np.array([[0,1,0,0,1,0,1,1],[0,1,1,1,1,0,0,0],[1,0,0,0,0,1,1,1],[1,0,1,1,0,1,0,0]]) + Hx = np.array( + [ + [1, 1, 1, 0, 0, 0, 1, 0], + [0, 0, 1, 0, 1, 1, 1, 0], + [1, 1, 0, 1, 0, 0, 0, 1], + [0, 0, 0, 1, 1, 1, 0, 1], + ] + ) + Hz = np.array( + [ + [0, 1, 0, 0, 1, 0, 1, 1], + [0, 1, 1, 1, 1, 0, 0, 0], + [1, 0, 0, 0, 0, 1, 1, 1], + [1, 0, 1, 1, 0, 1, 0, 0], + ] + ) Hx = css_instance_1.HX Hz = css_instance_1.HZ - CSS_evaluator = CSS_Evaluator(Hx,Hz) + CSS_evaluator = CSS_Evaluator(Hx, Hz) returns = CSS_evaluator.Get_error_rate(physical_error_rate=0.02) - return2 = CSS_evaluator.Get_precise_logical_error_rate(physical_error_rate=0.02,trail=300,block=10) + return2 = CSS_evaluator.Get_precise_logical_error_rate( + physical_error_rate=0.02, trail=300, block=10 + ) print(returns) print(return2) # print(type(returns)) @@ -484,23 +537,29 @@ def _channel_update(self,update_direction): # # plt.legend() # plt.grid(True) - PP = np.linspace(0.5, 7, 15) - PP = 10**(-PP) - print(f'PP:{PP}') - p_l_acc = np.array([CSS_evaluator.Get_precise_logical_error_rate(physical_error_rate=p,trail=1000,block=10) for p in PP]) + PP = 10 ** (-PP) + print(f"PP:{PP}") + p_l_acc = np.array( + [ + CSS_evaluator.Get_precise_logical_error_rate( + physical_error_rate=p, trail=1000, block=10 + ) + for p in PP + ] + ) p_l = np.array([CSS_evaluator.Get_error_rate(physical_error_rate=p) for p in PP]) - print(f'PP:{PP}') - print(f'p_l_acc:{p_l_acc}') - print(f'p_l:{p_l}') - plt.xscale('log') - - plt.yscale('log') - plt.plot(PP, p_l, label='simple Monte Carlo', color='g') - plt.plot(PP, p_l_acc, label='Precise', color='r') - plt.xlabel('PP') - plt.ylabel('P_l') - plt.title('Two estimates of P_l') + print(f"PP:{PP}") + print(f"p_l_acc:{p_l_acc}") + print(f"p_l:{p_l}") + plt.xscale("log") + + plt.yscale("log") + plt.plot(PP, p_l, label="simple Monte Carlo", color="g") + plt.plot(PP, p_l_acc, label="Precise", color="r") + plt.xlabel("PP") + plt.ylabel("P_l") + plt.title("Two estimates of P_l") plt.legend() plt.grid(True) - plt.show() \ No newline at end of file + plt.show() diff --git a/evolutionary_algorithm/__pycache__/ea.cpython-310.pyc b/evolutionary_algorithm/__pycache__/ea.cpython-310.pyc deleted file mode 100644 index ce4fbe4..0000000 Binary files a/evolutionary_algorithm/__pycache__/ea.cpython-310.pyc and /dev/null differ diff --git a/evolutionary_algorithm/ea.py b/evolutionary_algorithm/ea.py index 6dfaf2a..1d920c8 100644 --- a/evolutionary_algorithm/ea.py +++ b/evolutionary_algorithm/ea.py @@ -1,23 +1,21 @@ """ - This file contains the implementation of the Evolutionary Algorithm on the QEC codes. +This file contains the implementation of the Evolutionary Algorithm on the QEC codes. """ + from pymoo.core.problem import Problem import numpy as np -from pymoo.algorithms.soo.nonconvex.ga import GA -from pymoo.optimize import minimize -# from bayesian_optimization.bo import ObjectiveFunction -from bayesian_optimization.objective_function import ObjectiveFunction -from code_construction.code_construction import CodeConstructor + class CanonicalCSSEvolutionaryOptimization(Problem): - def __init__(self,nbits): - super().__init__(n_var=nbits, - n_obj=1, - n_constr=0, - xl=np.zeros(nbits), - xu=np.ones(nbits), - type_var=int) - + def __init__(self, nbits): + super().__init__( + n_var=nbits, + n_obj=1, + n_constr=0, + xl=np.zeros(nbits), + xu=np.ones(nbits), + type_var=int, + ) def _evaluate(self, x, out, *args, **kwargs): # calculate the ebaluation function @@ -25,29 +23,29 @@ def _evaluate(self, x, out, *args, **kwargs): out["F"] = f - class BivariateBicycleCodeEvolutionaryOptimization(Problem): - def __init__(self,l,m,obj_func): + def __init__(self, l, m, obj_func): self.objective_function = obj_func self.evaluation_history = [] self.best_result = 1 self.best_parameters = [] - super().__init__(n_var=2*(l+m-1), - n_obj=1, - n_constr=0, - xl=np.zeros(2*(l+m-1)), - xu=np.ones(2*(l+m-1)), - type_var=int) - + super().__init__( + n_var=2 * (l + m - 1), + n_obj=1, + n_constr=0, + xl=np.zeros(2 * (l + m - 1)), + xu=np.ones(2 * (l + m - 1)), + type_var=int, + ) def _evaluate(self, x, out, *args, **kwargs): # calculate the ebaluation function out_list = [] for i in x: # stabilizer_code = self.constructor.construct(i) - temp,_ = self.objective_function.forward(i) + temp, _ = self.objective_function.forward(i) temp = -temp - if self.best_result>= temp: + if self.best_result >= temp: self.best_parameters = i self.best_result = temp out_list.append(temp) diff --git a/generate_graphs.ipynb b/generate_graphs.ipynb new file mode 100644 index 0000000..ce84fb1 --- /dev/null +++ b/generate_graphs.ipynb @@ -0,0 +1,277 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "0", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from scipy.special import comb" + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "### Scatter graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "# n=144 Hamming Bound\n", + "def get_hamming_bound(n: int):\n", + " n = 144\n", + " t_values = np.arange(0, 31)\n", + " R_bound = []\n", + " t_norm = []\n", + "\n", + " for t in t_values:\n", + " sum_term = sum([comb(n, j, exact=True) * (3**j) for j in range(t + 1)])\n", + " f2_t = (1 / n) * np.log2(float(sum_term))\n", + " \n", + " R = 1.0 - f2_t\n", + " if R >= 0:\n", + " R_bound.append(R)\n", + " t_norm.append(t / n)\n", + " \n", + " return R_bound, t_norm\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3", + "metadata": {}, + "outputs": [], + "source": [ + "# GB\n", + "\n", + "# green markers: lambda = 0.5 \n", + "lambda_1_rate = [\n", + " 0.3333333333,\n", + " 0.2222222222,\n", + " 0.2152777778,\n", + " 0.2916666667,\n", + "]\n", + "\n", + "lambda_1_ptnorm = [\n", + " 2.459,\n", + " 4.852,\n", + " 5.035,\n", + " 3.655,\n", + "]\n", + "\n", + "# red markers: lambda = 1.0\n", + "lambda_05_rate = [\n", + " 0.09722222222,\n", + " 0.1111111111,\n", + " 0.1666666667,\n", + " 0.25,\n", + " 0.25,\n", + " 0.1666666667,\n", + " 0.2083333333,\n", + " 0.2222222222,\n", + " 0.2222222222,\n", + " 0.25,\n", + " 0.2777777778,\n", + " 0.2083333333,\n", + "]\n", + "\n", + "lambda_05_ptnorm = [\n", + " 6.798,\n", + " 6.537,\n", + " 5.193,\n", + " 3.99,\n", + " 5.031,\n", + " 5.186,\n", + " 4.729,\n", + " 5.01,\n", + " 3.614,\n", + " 3.827,\n", + " 3.788,\n", + " 4.302,\n", + "]\n", + "\n", + "lambda_1_ptnorm = np.array(lambda_1_ptnorm) / 144\n", + "lambda_05_ptnorm = np.array(lambda_05_ptnorm) / 144\n", + "\n", + "# black marker: Gross code benchmark\n", + "gross_code = (12/144, 0.042) # [[144, 12, 12]]\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "n = 144\n", + "R_bound, t_norm = get_hamming_bound(n)\n", + "\n", + "plt.figure(figsize=(8, 6))\n", + "plt.plot(R_bound, t_norm, '-', color='tab:blue', label=f'n={n} Hamming bound')\n", + "# plt.scatter(lambda_05_k_16[0], lambda_05_k_16[1], color='tab:green', marker='x', s=60, label='[[144, 16]]')\n", + "# plt.scatter(lambda_1_k_36[0], lambda_1_k_36[1], color='tab:red', marker='x', s=60, label='[[144, 36]]')\n", + "\n", + "plt.scatter(lambda_05_rate, lambda_05_ptnorm, color='tab:green', marker='+', s=60, label='lambda = 0.5')\n", + "plt.scatter(lambda_1_rate, lambda_1_ptnorm, color='tab:red', marker='+', s=60, label='lambda = 1')\n", + "\n", + "plt.scatter(gross_code[0], gross_code[1], color='black', marker='x', s=60, label='Gross code [[144, 12]]')\n", + "\n", + "plt.xlabel('Code Rate $R = k/n$', fontsize=12)\n", + "plt.ylabel('Normalized pseudo-distance $\\hat{t}/n$', fontsize=12)\n", + "plt.title('Operating Points of Generalised Bicycle Codes', fontsize=14)\n", + "# plt.title('Operating Points of Bivariate Bicycle Codes (found in distance mode)', fontsize=14)\n", + "plt.legend(fontsize=11)\n", + "plt.grid(True, linestyle='--', alpha=0.5)\n", + "\n", + "plt.xlim(-0.05, 0.85)\n", + "plt.ylim(0.00, 0.21)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "### Step graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "import re\n", + "\n", + "def extract_best_values(file_path):\n", + " pattern = re.compile(r\"\\(Best value:\\s*(-?\\d+\\.\\d+)\\)\")\n", + " best_values = []\n", + " \n", + " with open(file_path, 'r') as file:\n", + " for line in file:\n", + " match = pattern.search(line)\n", + " if match:\n", + " val = float(match.group(1))\n", + " best_values.append(val)\n", + " \n", + " return best_values\n", + "\n", + "\n", + "path = \"./bayopLTEg12.e2728217.5.txt\" \n", + "y_data = extract_best_values(path)\n", + "\n", + "x_data = [i for i in range(0, (len(y_data) * 2), 2)]\n", + "\n", + "print(f\"Extracted {len(y_data)} data points.\")\n", + "print(\"First 5 X-coordinates:\", x_data[:7])\n", + "print(\"First 5 Y-coordinates:\", y_data[:7])\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "def process_all_runs(folder_path):\n", + " all_runs = []\n", + " \n", + " for file_name in os.listdir(folder_path):\n", + " file_path = os.path.join(folder_path, file_name)\n", + " run_data = extract_best_values(file_path)\n", + " \n", + " if len(run_data) == 101:\n", + " all_runs.append(run_data)\n", + " elif len(run_data) == 200:\n", + " all_runs.append(run_data[::2] + [run_data[-1]])\n", + " else:\n", + " print(f\"{file_name} has {len(run_data)} points instead of 100, skipping\")\n", + "\n", + " if not all_runs:\n", + " print(\"no files found\")\n", + " return\n", + "\n", + " all_runs_matrix = np.array(all_runs)\n", + " mean_line = np.mean(all_runs_matrix, axis=0)\n", + " std_line = np.std(all_runs_matrix, axis=0)\n", + "\n", + " return mean_line, std_line\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "fig, ax = plt.subplots(figsize=(8, 5))\n", + "ax.set_ylim(-0.62, -0.18)\n", + "ax.set_xticks(np.arange(0, 201, 50))\n", + "\n", + "folders = {\n", + " \"./logs/g12\": \"GB-BO mean: k=12, density=6\",\n", + " \"./logs/g2\": \"GB-BO mean: varied params\",\n", + " \"./logs/l1_12_6\": \"BB-BO mean\",\n", + " \"./logs/RS_gb\": \"GB-RS mean\",\n", + "}\n", + "\n", + "for folder, descr in folders.items():\n", + " mean, std = process_all_runs(folder)\n", + "\n", + " ax.step(x_data, mean, label=descr, where='post')\n", + "\n", + " ax.fill_between(x_data, mean - std, mean + std, \n", + " alpha=0.2, step='post')\n", + "\n", + "ax.set_xlabel('Evaluation index', fontsize=12)\n", + "ax.set_ylabel('Best-so-far objective', fontsize=12)\n", + "ax.legend(loc='lower right', fontsize=10, framealpha=0.4)\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "qec", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.19" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/initial_points.py b/initial_points.py index b8483ac..823b385 100644 --- a/initial_points.py +++ b/initial_points.py @@ -1,5 +1,3 @@ -device = 'cuda' -DEVICE = 'cuda' import random import numpy as np import torch @@ -8,105 +6,105 @@ from bayesian_optimization.encoder import * from bayesian_optimization.chaincomplexembedding import * from bayesian_optimization.gp import * -import gpytorch -from gpytorch.mlls import ExactMarginalLogLikelihood -from gpytorch.distributions import MultivariateNormal -from gpytorch.kernels import RBFKernel, MaternKernel, SpectralMixtureKernel, ScaleKernel -import copy -from typing import Dict, Optional, Tuple, Any, List -import torch -import gpytorch -from gpytorch.mlls import ExactMarginalLogLikelihood -from torch.nn.utils import clip_grad_norm_ -import math -from dataclasses import dataclass, asdict -from typing import Tuple, Union, Dict, Any - -from torch.distributions.normal import Normal -from bayesian_optimization.bo import BO_on_QEC def set_all_seeds(seed: int = 42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed(seed) - torch.cuda.manual_seed_all(seed) + torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False -class Get_new_points_function(): - def __init__(self,method='qc-ldpc-hgp',hyperparameters = {'p': 2, 'q': 6, 'm': 2},encode='None'): + + +class Get_new_points_function: + def __init__( + self, + method="qc-ldpc-hgp", + hyperparameters={"p": 2, "q": 6, "m": 2}, + encode="None", + ): self.method = method self.hyperparameters = hyperparameters self.encode = encode self.init = False - def get_new_points_function(self,number): - if self.method == 'qc-ldpc-hgp': + def get_new_points_function(self, number): + if self.method == "qc-ldpc-hgp": new_points = self.get_new_points_HGP(number) - elif self.method == 'bb': + elif self.method == "bb": new_points = self.get_new_bb_vector(number) return new_points - - def get_new_points_HGP(self,number): - return np.random.randint(0, self.hyperparameters['m'] + 1, (number, self.hyperparameters['p'] * self.hyperparameters['q'])) - def get_new_bb_vector(self,number): + def get_new_points_HGP(self, number): + return np.random.randint( + 0, + self.hyperparameters["m"] + 1, + (number, self.hyperparameters["p"] * self.hyperparameters["q"]), + ) + + def get_new_bb_vector(self, number): results = [] - l = self.hyperparameters['l'] - g = self.hyperparameters['g'] - if self.init == False and l==12 and g==999: - print('best known bb code added to initial points') - self.init=True + l = self.hyperparameters["l"] + g = self.hyperparameters["g"] + if self.init == False and l == 12 and g == 999: + print("best known bb code added to initial points") + self.init = True - a = np.zeros((l+g-1)*2) - a[3]=1 - a[11+1]=1 - a[11+2]=1 - a[17+1]=1 - a[17+2]=1 - a[17+11+3]=1 + a = np.zeros((l + g - 1) * 2) + a[3] = 1 + a[11 + 1] = 1 + a[11 + 2] = 1 + a[17 + 1] = 1 + a[17 + 2] = 1 + a[17 + 11 + 3] = 1 results.append(a) - while number>0: - new_point = np.random.randint(0,2, size=(l+g-1)*2) + while number > 0: + new_point = np.random.randint(0, 2, size=(l + g - 1) * 2) c = code_constructor.construct(new_point) - if c.k==0: + if c.k == 0: continue else: results.append(new_point) number -= 1 return np.array(results) -if __name__ == '__main__': + + +if __name__ == "__main__": import pickle + seed = 42 set_all_seeds(seed) l = 12 - g = 6 # g here is m in Bravyi et al's paper - para_dict = {'l':l,'g':g} - code_class = 'bb' + g = 6 # g here is m in Bravyi et al's paper + para_dict = {"l": l, "g": g} + code_class = "bb" - - code_constructor = CodeConstructor(method=code_class,para_dict = para_dict) + code_constructor = CodeConstructor(method=code_class, para_dict=para_dict) # define objective function - pp=0.05 - Obj_Func = ObjectiveFunction(code_constructor, pp=pp,decoder_param={'trail':10_000}) + pp = 0.05 + Obj_Func = ObjectiveFunction( + code_constructor, pp=pp, decoder_param={"trail": 10_000} + ) obj_func = Obj_Func.forward pl_to_obj = Obj_Func.pl_to_obj_with_std # method of sampling new points - gnp = Get_new_points_function(method=code_class,hyperparameters = para_dict).get_new_points_function + gnp = Get_new_points_function( + method=code_class, hyperparameters=para_dict + ).get_new_points_function init_num = 20 for i in range(5): - print(f'generating new points...round {i}') + print(f"generating new points...round {i}") X_init = gnp(init_num) y_init = [] pl_init = [] for x in X_init: - - y,pl = obj_func(x) + y, pl = obj_func(x) y_init.append(y) pl_init.append(pl) - print(f'x:{x},y:{y},pl:{pl}') - data = {'X':X_init,'y':y_init,'pl':pl_init} - with open(f"./data/BO_initial_points/BO_initial_points_{i+5}.pkl", "wb") as f: - pickle.dump(data, f) \ No newline at end of file + print(f"x:{x},y:{y},pl:{pl}") + data = {"X": X_init, "y": y_init, "pl": pl_init} + with open(f"./data/BO_initial_points/BO_initial_points_{i + 5}.pkl", "wb") as f: + pickle.dump(data, f) diff --git a/read_results.ipynb b/read_results.ipynb new file mode 100644 index 0000000..d8f7e37 --- /dev/null +++ b/read_results.ipynb @@ -0,0 +1,396 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "0", + "metadata": {}, + "outputs": [], + "source": [ + "import pickle\n", + "\n", + "file_path = \"data/BO_results/lorenzo_results/GB_BO_results_0_0.5_4_72_10246.pkl\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1", + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"Loading results from: {file_path}\\n\")\n", + "\n", + "with open(file_path, \"rb\") as f:\n", + " results = pickle.load(f)\n", + "\n", + "best_x = results[\"best_x\"]\n", + "best_y = results[\"best_y\"]\n", + "history = results[\"evaluation_history\"]\n", + "\n", + "print(f\"Best Score (y): {best_y}\")\n", + "print(f\"Binary Vector:\\n{best_x}\\n\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "if len(history) > 10:\n", + " print(f\"First 5 scores: {[round(num, 4) for num in history[:5]]}\")\n", + " print(f\"Last 5 scores: {[round(num, 4) for num in history[-5:]]}\")\n", + "else:\n", + " print(history)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3", + "metadata": {}, + "outputs": [], + "source": [ + "from code_construction.code_construction import CodeConstructor, CSSCode\n", + "import codedistance\n", + "\n", + "cc = CodeConstructor(method=\"bb\", para_dict={\"l\": 12, \"g\": 6})\n", + "cc2 = CodeConstructor(method=\"gb\", para_dict={\"l\":72})\n", + "\n", + "\n", + "def get_nkd(code: CSSCode):\n", + " res = codedistance.CSScodeDistance(\n", + " code.hx,\n", + " code.hz,\n", + " method=\"QDistEvol\",\n", + " params={},\n", + " seed=100,\n", + " )\n", + " dist = res[\"d\"]\n", + " return code.n, code.k, dist" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "from bayesian_optimization.objective_function import ObjectiveFunction\n", + "\n", + "of1 = ObjectiveFunction(\n", + " cc,\n", + " lambda_=1,\n", + " pp=0.05,\n", + " decoder_param={\"trail\": 100_000, \"max_error\": 1000},\n", + ")\n", + "of2 = ObjectiveFunction(\n", + " cc2,\n", + " lambda_=1,\n", + " pp=0.05,\n", + " decoder_param={\"trail\": 100_000, \"max_error\": 1000},\n", + ")\n", + "\n", + "# of_cl = ObjectiveFunction(\n", + "# cc,\n", + "# lambda_=1,\n", + "# pp=0.005,\n", + "# circuit_level_noise=True,\n", + "# decoder_param={\"trail\": 100_000, \"max_error\": 1000},\n", + "# )\n", + "\n", + "pp_list = [\n", + " 0.05,\n", + " 0.040036870145840404,\n", + " 0.032059019421497734,\n", + " 0.0256708559516296,\n", + " 0.020555614525359374,\n", + " 0.01645964939039528,\n", + " 0.013179856905786339,\n", + " 0.010553604389554513,\n", + " 0.008450665770303305,\n", + " 0.0067667641618306355,\n", + "]\n", + "\n", + "gross_code = [\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + "]\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5", + "metadata": {}, + "outputs": [], + "source": [ + "code = cc.construct(gross_code)\n", + "\n", + "n, k, d = get_nkd(code)\n", + "print(f\"[[{n}, {k}, {d}]]\")\n", + "\n", + "ler_plq = of1.lerpq(gross_code)\n", + "print(f\"LER per logical qubit: {ler_plq}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "def get_row_column_weights(H):\n", + " row_weights = np.sum(H != 0, axis=1)\n", + " max_row_w = np.max(row_weights)\n", + " avg_row_w = np.mean(row_weights)\n", + "\n", + " col_weights = np.sum(H != 0, axis=0)\n", + " max_col_w = np.max(col_weights)\n", + " avg_col_w = np.mean(col_weights)\n", + "\n", + " return max_row_w, avg_row_w, max_col_w, avg_col_w\n", + "\n", + "\n", + "def percent_diff(v1, v2):\n", + " return ((v2 - v1) / v1) * 100" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "files = [\n", + " None,\n", + " \"data/BO_results/lorenzo_results/gb_ler_144_14_9.pkl.pkl\",\n", + " \"data/BO_results/lorenzo_results/gb_ler_144_16_6.pkl\",\n", + " \"data/BO_results/lorenzo_results/gb_ler_144_30_8.pkl\",\n", + " \"data/BO_results/lorenzo_results/gb_ler_144_36_6.pkl\",\n", + "]\n", + "\n", + "ler_results = []\n", + "\n", + "for file in files:\n", + " if file is None:\n", + " code = cc.construct(gross_code)\n", + " best_x = gross_code\n", + " best_y = 0\n", + " of = of1\n", + " else:\n", + " with open(file, \"rb\") as f:\n", + " results = pickle.load(f)\n", + " best_x = results[\"best_x\"]\n", + " best_y = results[\"best_y\"]\n", + "\n", + " best_x = best_x.cpu()\n", + " \n", + " if \"GB\" in file:\n", + " code = cc2.construct(best_x)\n", + " of = of2\n", + " else:\n", + " code = cc.construct(best_x)\n", + " of = of1\n", + "\n", + " n, k, d = get_nkd(code)\n", + "\n", + " of.pp = pp_list[0]\n", + " ler = of.ler(code)\n", + " ler_pq = of.lerpq(best_x)\n", + " \n", + " # th, pl = of.psuedo_t(code)\n", + "\n", + " print(f\"from {file}, score: {best_y:.5f}\")\n", + " print(f\"[[{n}, {k}, {d}]]\")\n", + " print(f\"LER per logical qubit (pp=0.05): {ler_pq}\")\n", + " mrx, arx, mcx, acx = get_row_column_weights(code.hx)\n", + " mrz, arz, mcz, acz = get_row_column_weights(code.hz)\n", + " print(f\"Hx: max row weight={mrx}, avg row weight={arx}\")\n", + " print(f\"Hx: max col weight={mcx}, avg col weight={acx}\")\n", + " print(f\"Hz: max row weight={mrz}, avg row weight={arz}\")\n", + " print(f\"Hz: max col weight={mcz}, avg col weight={acz}\")\n", + " # print(f\"pseduo-t: h-hat={th}, pL={pl}\")\n", + " print(\"--------------------------------------------\")\n", + "\n", + " res = []\n", + " res.append(ler_pq)\n", + " with open(\"line_res_gb.txt\", 'a') as f:\n", + " f.write(f\"\\n[[{n}, {k}, {d}]], {best_y:.5f}\\n\")\n", + " f.write(f\"{ler_pq}\\n\")\n", + "\n", + " for i, p in enumerate(pp_list[1:]):\n", + " of.pp = p\n", + " ler_pq = of.lerpq(best_x)\n", + " res.append(ler_pq)\n", + " with open(\"line_res_gb.txt\", 'a') as f:\n", + " f.write(f\"{ler_pq}\\n\")\n", + "\n", + " ler_results.append(res)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "import re\n", + "\n", + "# above code often got interrupted, so this function loads ler_results from a file\n", + "def parse_line_res(file_path):\n", + " blocks = []\n", + " current_block = []\n", + " current_header = None\n", + " block_start_pattern = re.compile(r\"^\\s*(\\[\\[.*?\\]\\])\")\n", + " \n", + " with open(file_path, 'r') as file:\n", + " for line in file:\n", + " line = line.strip()\n", + " if not line:\n", + " continue\n", + " \n", + " match = block_start_pattern.match(line)\n", + " if match:\n", + " if current_header and current_block:\n", + " blocks.append({'label': current_header, 'data': current_block})\n", + " current_block = []\n", + "\n", + " current_header = match.group(1)\n", + " continue\n", + " \n", + " try:\n", + " val = float(line)\n", + " current_block.append(val)\n", + " except ValueError:\n", + " print(f\"Skipping unparseable line: {line}\")\n", + " \n", + " if current_header and current_block:\n", + " blocks.append({'label': current_header, 'data': current_block})\n", + " \n", + " return blocks" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "ler_results = parse_line_res(\"line_res_gb.txt\")\n", + "\n", + "for i, r in enumerate(ler_results):\n", + " if i == 0:\n", + " label = r['label'] + \" Gross code\"\n", + " else:\n", + " label = \"GB \" + r['label']\n", + " plt.plot(pp_list, r['data'], label=label)\n", + "\n", + "plt.xlabel('Physical Error Rate', fontsize=12)\n", + "plt.ylabel('Logical Error Rate per Logical Qubit', fontsize=12)\n", + "\n", + "plt.yscale(\"log\")\n", + "plt.xscale(\"log\")\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "\n", + "of_default = ObjectiveFunction(\n", + " cc,\n", + " lambda_=1,\n", + " pp=0.05,\n", + ")\n", + "\n", + "codes = [\n", + " gross_code,\n", + "]\n", + "\n", + "for best_x in codes:\n", + " code = cc.construct(best_x) \n", + "\n", + " t0 = time.time()\n", + " n, k, d = get_nkd(code)\n", + " t1 = time.time()\n", + " print(f\"QDistEvol time: {t1-t0}\")\n", + "\n", + " t2 = time.time()\n", + " ler = of_default.ler(code)\n", + " t3 = time.time()\n", + " print(f\"LER simulation time: {t3-t2}\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "qec", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.19" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/readme.ipynb b/readme.ipynb index debee45..86b70ec 100644 --- a/readme.ipynb +++ b/readme.ipynb @@ -2,19 +2,19 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, - "id": "2bf4c0b3", + "execution_count": null, + "id": "0", "metadata": {}, "outputs": [], "source": [ "# Some tools for writing this file:\n", - "line = '**********************************************************\\n'\n", + "line = \"**********************************************************\\n\"\n", "import numpy as np" ] }, { "cell_type": "markdown", - "id": "02b23fe3", + "id": "1", "metadata": {}, "source": [ "# Code Generation:\n", @@ -29,41 +29,20 @@ }, { "cell_type": "code", - "execution_count": 2, - "id": "1b326e65", + "execution_count": null, + "id": "2", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "**********************************************************\n", - "3x3 rotated surface code,n:9,k:1\n", - "**********************************************************\n", - "\n", - "**********************************************************\n", - "A random hypergraph product code,n:288,k:8;\n", - "The weight of each row of Hx,Hz is upper bounded by 2*p = 6, the weight of each column of Hx,Hz is upper bounded by 2*q=6;\n", - " n=m(p+q)\n", - "**********************************************************\n", - "\n", - "**********************************************************\n", - "The [144,12,12] gross code,n:144,k:12\n", - "**********************************************************\n", - "\n" - ] - } - ], + "outputs": [], "source": [ "# examples of code construction\n", "from code_construction.code_construction import CodeConstructor\n", "\n", "# An example of rotated surface code\n", - "surface_code_constructor = CodeConstructor(method = 'rotated-surface')\n", + "surface_code_constructor = CodeConstructor(method=\"rotated-surface\")\n", "distance3 = surface_code_constructor.construct(3)\n", - "print(f'{line}3x3 rotated surface code,n:{distance3.n},k:{distance3.k}\\n{line}')\n", - "# this function take odd integers (p>=3) as input \n", - "'''\n", + "print(f\"{line}3x3 rotated surface code,n:{distance3.n},k:{distance3.k}\\n{line}\")\n", + "# this function take odd integers (p>=3) as input\n", + "\"\"\"\n", " returns a CSSCode instance, which has:\n", " distance3.hx (np.ndarray), # X stabilizers\n", " distance3.hz (np.ndarray), # Z stabilizers\n", @@ -71,33 +50,65 @@ " distance3.k (int), # number of logical qubits\n", " distance3.lx (np.ndarray), # X logical operators\n", " distance3.lz (np.ndarray) # Z logical operators\n", - "'''\n", + "\"\"\"\n", "\n", "# An example of hypergraph product code\n", - "hgp_code_constructor = CodeConstructor(method = 'qc-ldpc-hgp',para_dict = {\n", - " 'p':3,\n", - " 'q':3,\n", - " 'm':4\n", - "})\n", - "M = np.array([0,2,1,2,1,3,3,3,1]) # just a random (flatten) matrix in Z_5(m+1)^{3(p) \\times 3(q)}, see the definition CodeConstructor.qc_ldpc_hgp_construction()\n", + "hgp_code_constructor = CodeConstructor(\n", + " method=\"qc-ldpc-hgp\", para_dict={\"p\": 3, \"q\": 3, \"m\": 4}\n", + ")\n", + "M = np.array(\n", + " [0, 2, 1, 2, 1, 3, 3, 3, 1]\n", + ") # just a random (flatten) matrix in Z_5(m+1)^{3(p) \\times 3(q)}, see the definition CodeConstructor.qc_ldpc_hgp_construction()\n", "random_hgp_code = hgp_code_constructor.construct(M)\n", - "print(f'{line}A random hypergraph product code,n:{random_hgp_code.n},k:{random_hgp_code.k};\\nThe weight of each row of Hx,Hz is upper bounded by 2*p = 6, the weight of each column of Hx,Hz is upper bounded by 2*q=6;\\n n=m(p+q)\\n{line}')\n", + "print(\n", + " f\"{line}A random hypergraph product code,n:{random_hgp_code.n},k:{random_hgp_code.k};\\nThe weight of each row of Hx,Hz is upper bounded by 2*p = 6, the weight of each column of Hx,Hz is upper bounded by 2*q=6;\\n n=m(p+q)\\n{line}\"\n", + ")\n", "\n", "# An example of bivariate bicycle code\n", - "bb_code_constructor = CodeConstructor(method = 'bb',para_dict = {\n", - " 'l':12,\n", - " 'g':6\n", - "})\n", + "bb_code_constructor = CodeConstructor(method=\"bb\", para_dict={\"l\": 12, \"g\": 6})\n", "gross_parameter = [\n", - " 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0.\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", "]\n", "gross_code = bb_code_constructor.construct(gross_parameter)\n", - "print(f'{line}The [144,12,12] gross code,n:{gross_code.n},k:{gross_code.k}\\n{line}')" + "print(f\"{line}The [144,12,12] gross code,n:{gross_code.n},k:{gross_code.k}\\n{line}\")" ] }, { "cell_type": "markdown", - "id": "108b1bb5", + "id": "3", "metadata": {}, "source": [ "# Estimation of logical error rate\n", @@ -139,34 +150,25 @@ }, { "cell_type": "code", - "execution_count": 3, - "id": "eec4eaf6", + "execution_count": null, + "id": "4", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "gross code's logical error rate:0.0614, where physical error rate = 0.05\n" - ] - } - ], + "outputs": [], "source": [ "# An example of calculating logical error rate:\n", "\n", "from evaluation.decoder_based_evaluation import CSS_Evaluator\n", + "\n", "evaluator = CSS_Evaluator(gross_code.hx, gross_code.hz)\n", "pL = evaluator.Get_logical_error_rate_Monte_Carlo(\n", - " physical_error_rate=0.05,\n", - " xyz_bias=[1, 1, 1],\n", - " trail=10_000\n", + " physical_error_rate=0.05, xyz_bias=[1, 1, 1], trail=10_000\n", ")\n", "print(f\"gross code's logical error rate:{pL}, where physical error rate = 0.05\")" ] }, { "cell_type": "markdown", - "id": "93381902", + "id": "5", "metadata": {}, "source": [ "## Circuit-Level Noise\n", @@ -253,30 +255,33 @@ }, { "cell_type": "code", - "execution_count": 4, - "id": "2897f063", + "execution_count": null, + "id": "6", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "the logical error rate under SD6 noise model with physical error rate = 0.007 is :0.00025998559999995674\n" - ] - } - ], + "outputs": [], "source": [ - "from evaluation.circuit_level_noise import MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise\n", + "from evaluation.circuit_level_noise import (\n", + " MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise,\n", + ")\n", "\n", "\n", - "mc = MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise(gross_code,noise_model='SD6',p=0.007,rounds=12,custom_error_model={},decoder='bplsd')\n", - "pl = mc.run(shots= 100_000, max_error = 1000, num_workers = 24)\n", - "print(f'the logical error rate under SD6 noise model with physical error rate = 0.007 is :{pl}')" + "mc = MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise(\n", + " gross_code,\n", + " noise_model=\"SD6\",\n", + " p=0.007,\n", + " rounds=12,\n", + " custom_error_model={},\n", + " decoder=\"bplsd\",\n", + ")\n", + "pl = mc.run(shots=100_000, max_error=1000, num_workers=24)\n", + "print(\n", + " f\"the logical error rate under SD6 noise model with physical error rate = 0.007 is :{pl}\"\n", + ")" ] }, { "cell_type": "markdown", - "id": "335d9451", + "id": "7", "metadata": {}, "source": [ "# Generating Datasets\n", @@ -287,194 +292,77 @@ { "cell_type": "code", "execution_count": null, - "id": "97bee7a0", + "id": "8", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Generating dataset...\n", - "code[1],logical error rate:0.1410254440052202\n", - "code[2],logical error rate:0.12649947299894604\n", - "code[3],logical error rate:0.967941400304414\n", - "code[4],logical error rate:0.15046149476972592\n", - "code[5],logical error rate:0.03673792385825858\n", - "code[6],logical error rate:0.12101078011411104\n", - "code[7],logical error rate:0.07121678973297008\n", - "code[8],logical error rate:0.06582500000000002\n", - "code[9],logical error rate:0.003034730748359138\n", - "code[10],logical error rate:0.005826727039220714\n", - "code[11],logical error rate:0.00536174430128844\n", - "code[12],logical error rate:0.9945774338569657\n", - "code[13],logical error rate:0.010879033027873297\n", - "code[14],logical error rate:0.8184577338129496\n", - "code[15],logical error rate:0.13408220850304398\n", - "code[16],logical error rate:0.279169366419165\n", - "code[17],logical error rate:0.9560808298631767\n", - "code[18],logical error rate:0.013755443560430236\n", - "code[19],logical error rate:0.7931416416857697\n", - "code[20],logical error rate:0.027447062080088624\n", - "code[21],logical error rate:0.004351928134076699\n", - "code[22],logical error rate:0.0037791751803828433\n", - "code[23],logical error rate:0.012479502297822687\n", - "code[24],logical error rate:0.9481843317524491\n", - "code[25],logical error rate:0.0025817594311702274\n", - "code[26],logical error rate:0.0011796640000000247\n", - "code[27],logical error rate:0.0016393291999999837\n", - "code[28],logical error rate:0.048956469611415754\n", - "code[29],logical error rate:0.0053214731906285895\n", - "code[30],logical error rate:0.0014194967999999752\n", - "code[31],logical error rate:0.00756628777944468\n", - "code[32],logical error rate:0.04638074943803561\n", - "code[33],logical error rate:0.05000522626858106\n", - "code[34],logical error rate:0.0032476172384378055\n", - "code[35],logical error rate:0.2672155830960815\n", - "code[36],logical error rate:0.003205229455622849\n", - "code[37],logical error rate:0.000619906400000092\n", - "code[38],logical error rate:0.05580730179071092\n", - "code[39],logical error rate:0.00021998879999995946\n", - "code[40],logical error rate:0.8391500424448217\n", - "code[41],logical error rate:0.003308221006165568\n", - "code[42],logical error rate:0.7987309141384097\n", - "code[43],logical error rate:0.7783464415143805\n", - "code[44],logical error rate:0.05047586809004512\n", - "code[45],logical error rate:0.9548888488737559\n", - "code[46],logical error rate:0.0003599680000000438\n", - "code[47],logical error rate:0.9595989096573209\n", - "code[48],logical error rate:0.00023998599999996983\n", - "code[49],logical error rate:0.030304472768204027\n", - "code[50],logical error rate:0.00023998599999996983\n", - "code[51],logical error rate:0.9588865165631469\n", - "code[52],logical error rate:0.0018391571999999412\n", - "code[53],logical error rate:0.004996505071060975\n", - "code[54],logical error rate:0.07561990133507546\n", - "code[55],logical error rate:0.0011796544000000075\n", - "code[56],logical error rate:0.037816806941630365\n", - "code[57],logical error rate:0.138485810584838\n", - "code[58],logical error rate:0.05129434324065196\n", - "code[59],logical error rate:0.06393744506953547\n", - "code[60],logical error rate:0.041977270907829434\n", - "code[61],logical error rate:0.018202314002821907\n", - "code[62],logical error rate:0.8932903291344982\n", - "code[63],logical error rate:0.33225912752557196\n", - "code[64],logical error rate:0.003685083792320354\n", - "code[65],logical error rate:0.09107510864806567\n", - "code[66],logical error rate:0.04744532910132615\n", - "code[67],logical error rate:0.01847170462043246\n", - "code[68],logical error rate:0.8016926651178228\n", - "code[69],logical error rate:0.0016992775999998821\n", - "code[70],logical error rate:0.008615085367169595\n", - "code[71],logical error rate:0.003698369274622082\n", - "code[72],logical error rate:0.00673297830763786\n", - "code[73],logical error rate:0.002565726818282932\n", - "code[74],logical error rate:0.010005861449053022\n", - "code[75],logical error rate:0.802656436122692\n", - "code[76],logical error rate:0.0058518380185600405\n", - "code[77],logical error rate:0.0026283928485606456\n", - "code[78],logical error rate:0.016344182704940535\n", - "code[79],logical error rate:0.9702569565850816\n", - "code[80],logical error rate:0.033485450229133296\n", - "code[81],logical error rate:0.0028130444485817696\n", - "code[82],logical error rate:0.1009154241294663\n", - "code[83],logical error rate:0.008440071856718245\n", - "code[84],logical error rate:0.00661528086095009\n", - "code[85],logical error rate:0.06502997186618154\n", - "code[86],logical error rate:0.0017792439999999576\n", - "code[87],logical error rate:0.23620180206471375\n", - "code[88],logical error rate:0.9551425579896907\n", - "code[89],logical error rate:0.754602033660589\n", - "code[90],logical error rate:0.02057774966300463\n", - "code[91],logical error rate:0.0022577610735975417\n", - "code[92],logical error rate:0.004822783438690137\n", - "code[93],logical error rate:0.06554410970030611\n", - "code[94],logical error rate:0.01991886324226455\n", - "code[95],logical error rate:0.03763442751844592\n", - "code[96],logical error rate:0.9536384976525821\n", - "code[97],logical error rate:0.9629716301843319\n", - "code[98],logical error rate:0.02161412920829664\n", - "code[99],logical error rate:0.0009197884000000656\n", - "code[100],logical error rate:0.9543850806451613\n" - ] - } - ], + "outputs": [], "source": [ "from dataset import QEC_Dataset\n", + "\n", "l = 12\n", "g = 6\n", "dataset = QEC_Dataset(\n", - " l=l,\n", - " g=g,\n", - " oad = False,\n", - " number = 100,\n", - " save = True,\n", - " path='./data/codes',\n", - " p=0.007\n", - " noise_model='SD6',\n", - " noise_and_decoder_param={\n", - " 'rounds':12,\n", - " 'decoder':'bplsd',\n", - " 'trail':100_000,\n", - " 'max_error':100,\n", - " 'num_worker':24\n", - " },\n", - " )\n", - "# this is used to generate the dataset; \n", + " l=l,\n", + " g=g,\n", + " load=False,\n", + " number=100,\n", + " save=True,\n", + " path=\"./data/codes\",\n", + " p=0.007,\n", + " noise_model=\"SD6\",\n", + " noise_and_decoder_param={\n", + " \"rounds\": 12,\n", + " \"decoder\": \"bplsd\",\n", + " \"trail\": 100_000,\n", + " \"max_error\": 100,\n", + " \"num_workers\": 24,\n", + " },\n", + ")\n", + "# this is used to generate the dataset;\n", "# you can selet l,g as you want(By this way you can only generate bb codes; if you want to generate other codes, please change the definition of QEC_Dataset in dataset.py)" ] }, { "cell_type": "code", - "execution_count": 6, - "id": "dc36dd53", + "execution_count": null, + "id": "9", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "loading\n", - "successfully loaded\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxYAAAHqCAYAAACZcdjsAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAABAf0lEQVR4nO3deXhU9d3+8XsmCUlYQsgOmrBvKgpERJSgCGVRkU2tCLJIxQ1FkWr1Z4uoFYWCu6CtBrVIW3eLFQVkFaQmgjxoiLIm7JlgCIFsZL6/P3iYh5AQMjlnMhl4v64r18V855zPfM45czK5Ocs4jDFGAAAAAGCB098NAAAAAAh8BAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAHXOE088IYfDUW6sRYsWGjt2rM9fe8eOHXI4HJo3b55nbOzYsWrYsKHPX/sEh8OhJ554otZerya+++47XXHFFWrQoIEcDoc2bNjg75Y8Knv/nGlal8vl467Kq+42DoRlAYATCBYAzlr/+c9/6uwf6HW5tzMpLS3VTTfdpIMHD+r555/Xu+++q+bNm/u7rSo988wz+uSTT/zdhi38vSyfffaZunbtqrCwMCUlJWnq1Kk6duxYteb985//rBtuuEHx8fEBEaABeIdgASAgZGZm6q9//atX8/znP//RtGnTvJqnefPmKiws1G233ebVfN6qqrfCwkI9/vjjPn19K7Zu3aqdO3dqypQpmjBhgkaNGqUmTZr4uy2Pxx9/XIWFheXG/P3H+KmsbGN/LssXX3yhIUOGKDIyUi+//LKGDBmip59+Wvfdd1+15n/88cf13XffqUuXLj7uFIA/BPu7AQCojtDQUJ/WP3bsmNxut+rVq6ewsDCfvtaZ+Pv1z+TAgQOSpMjISP82chrBwcEKDq7bH291fRufzpQpU3TxxRfrq6++8qzjiIgIPfPMM5o0aZI6dOhQ5fzbt29XixYt5HK5FBsbWxstA6hFHLEA4FerV69Wt27dFBYWptatW+v111+vdLpTr7EoLS3VtGnT1LZtW4WFhSk6Olo9e/bU4sWLJR2/LuLVV1+VdPx89hM/0v9dR/GXv/xFL7zwglq3bq3Q0FD99NNPlV5jccK2bdvUv39/NWjQQM2aNdOTTz4pY4zn+eXLl8vhcGj58uXl5ju1ZlW9nRg79RSR9evXa+DAgYqIiFDDhg3Vp08fffvtt+WmmTdvnhwOh7755htNnjxZsbGxatCggYYOHaqcnJzKN8Apvv76a6WkpKhBgwaKjIzU4MGDlZGR4Xl+7NixuuqqqyRJN910kxwOh66++upKa23btk0Oh0PPP/98hefWrFkjh8OhBQsWVDqvMUYxMTGaPHmyZ8ztdisyMlJBQUHKy8vzjD/33HMKDg5WQUGBpIrXJTgcDh05ckRvv/22Z12fer1OXl6exo4dq8jISDVu3Fjjxo3T0aNHq1xXL730UoVeZs2aJYfDUa7vsrIyNWrUSI888ki5nk7dxtXZF3y1LNXx008/6aefftKECRPKBbd77rlHxhh98MEHZ6zRokULy30AqLvq9n/pADir/c///I/69eun2NhYPfHEEzp27JimTp2q+Pj4M877xBNPaPr06frd736nyy67TPn5+UpLS9P333+v3/zmN7rzzju1Z88eLV68WO+++26lNVJTU1VUVKQJEyYoNDRUUVFRcrvdlU5bVlamAQMG6PLLL9eMGTO0aNEiz7nlTz75pFfLXZ3eTvbjjz8qJSVFERERevjhhxUSEqLXX39dV199tVasWKHu3buXm/6+++5TkyZNNHXqVO3YsUMvvPCCJk6cqH/+859Vvs6SJUs0cOBAtWrVSk888YQKCwv18ssv68orr9T333+vFi1a6M4779R5552nZ555Rvfff7+6det22u3VqlUrXXnllZo/f74efPDBcs/Nnz9fjRo10uDBgyud1+Fw6Morr9TKlSs9Yxs3btShQ4fkdDr1zTff6LrrrpMkrVq1Sl26dDntBfbvvvuu530yYcIESVLr1q3LTXPzzTerZcuWmj59ur7//nv97W9/U1xcnJ577rnTrq+UlBS53W6tXr1a119/vacXp9OpVatWeaZbv369CgoK1KtXr9PWqu6+YNeyHDp0SKWlpaft54SwsDDPel2/fr0k6dJLLy03TbNmzXT++ed7ngdwDjMA4CdDhgwxYWFhZufOnZ6xn376yQQFBZlTfz01b97cjBkzxvP4kksuMdddd12V9e+9994KdYwxZvv27UaSiYiIMAcOHKj0udTUVM/YmDFjjCRz3333ecbcbre57rrrTL169UxOTo4xxphly5YZSWbZsmVnrHm63owxRpKZOnWq5/GQIUNMvXr1zNatWz1je/bsMY0aNTK9evXyjKWmphpJpm/fvsbtdnvGH3zwQRMUFGTy8vIqfb0TOnfubOLi4kxubq5n7IcffjBOp9OMHj3aM3ZiOd9///0q6xljzOuvv24kmYyMDM9YSUmJiYmJKbc9KzNz5kwTFBRk8vPzjTHGvPTSS6Z58+bmsssuM4888ogxxpiysjITGRlpHnzwQc98U6dOrbBuGzRoUOnrnZj29ttvLzc+dOhQEx0dXWV/ZWVlJiIiwjz88MPGmOPviejoaHPTTTeZoKAgc/jwYWOMMbNnzzZOp9P8+uuvnnkr28bV3RfsWJarrrrKSDrjz8mvM3PmTCPJZGVlVXjtbt26mcsvv7zK9XWynJycCusAQODjVCgAflFWVqYvv/xSQ4YMUVJSkme8Y8eO6t+//xnnj4yM1I8//qhffvmlxj0MHz7cq/O8J06c6Pm3w+HQxIkTVVJSoiVLltS4hzMpKyvTV199pSFDhqhVq1ae8aZNm+rWW2/V6tWrlZ+fX26eCRMmlDsVKCUlRWVlZdq5c+dpX2fv3r3asGGDxo4dq6ioKM/4xRdfrN/85jf6z3/+U6P+b775ZoWFhWn+/PmesS+//FIul0ujRo2qct4Tfa9Zs0bS8aMBKSkpSklJ8RwR2LRpk/Ly8pSSklKj/k646667Krx2bm5uhXV7MqfTqSuuuMJzVCUjI0O5ubn6wx/+IGOM1q5d6+n7oosuOu01KVb3hZosy6xZs7R48eIz/jz88MOeeU5cEF/Z9U5hYWEVLpgHcO4hWADwi5ycHBUWFqpt27YVnmvfvv0Z53/yySeVl5endu3aqVOnTvr973+vjRs3etVDy5Ytqz2t0+ks94e9JLVr107S8WsofCUnJ0dHjx6tdJ107NhRbrdb2dnZ5cZP/uNUkueOTb/++utpX+dE6Djd67hcLh05csTr/iMjIzVo0CC99957nrH58+frvPPO0zXXXFPlvF27dlX9+vU9IeJEsOjVq5fS0tJUVFTkea5nz55e93aymqwz6fgf7enp6SosLNSqVavUtGlTde3aVZdccomnt9WrV1cZfKzuC6eqzrIkJyerb9++Z/y54IILPPOEh4dLkoqLiyu8ZlFRked5AOcuggWAgNSrVy9t3bpVb731li666CL97W9/U9euXfW3v/2t2jXs/kPodF9kVlZWZuvrnElQUFCl4+akC81r0+jRo7Vt2zatWbNGhw8f1meffaYRI0bI6az6IygkJETdu3fXypUrtWXLFu3bt08pKSnq2bOnSktLtW7dOq1atUodOnSwfIehmq6zE72sXbvWE3wkeY6qbN68WTk5OZaPqHijOsty8OBB7du374w/hw4d8szTtGlTScePbp1q7969atasmc1LAiDQECwA+EVsbKzCw8MrPZUpMzOzWjWioqI0btw4LViwQNnZ2br44ovL3Wmnut9YXB1ut1vbtm0rN/bzzz9L+r873Zz4n+GT7xIkqdJTkKrbW2xsrOrXr1/pOtm8ebOcTqcSExOrVasqJ77g7nSvExMTowYNGtSo9oABAxQbG6v58+fr448/1tGjR6v9PSEpKSn673//qyVLligmJkYdOnRQVFSULrzwQq1atUqrVq2q8qLoE+x8L5zssssuU7169Ty9nAgQvXr10rp167R06VLP49Pxdl+wY1mGDRumpk2bnvFn0qRJnnk6d+4sSUpLSytXa8+ePdq1a5fneQDnLu4KBcAvgoKC1L9/f33yySfKysrynL6RkZGhL7/88ozz5+bmKjo62vO4YcOGatOmTbnTgk78IZyXl2fLdy688soreumllyQd/9/fV155RSEhIerTp4+k43+cBwUFaeXKlRoyZIhnvtdee61Crer2FhQUpH79+unTTz/Vjh07PCFm//79eu+999SzZ09FRERYXramTZuqc+fOevvtt/Xoo496etq0aZO++uqrM14PUZXg4GCNGDFC7733njIyMtSpUyddfPHF1Zo3JSVFTz75pF544QX17NnT80d1SkqK3n33Xe3Zs6daRwMaNGhQIfDZISwsTN26ddOCBQuUlZVV7ohFYWGhXnrpJbVu3drzv/2V8XZfsGNZZs2adcbTvCSVOwpx4YUXqkOHDnrjjTd05513eo6MzJkzRw6HQzfeeKNn2kOHDmnv3r1q2rSpGjdubKlXAIGDYAHAb6ZNm6ZFixYpJSVF99xzj44dO6aXX35ZF1544Rmvl7jgggt09dVXKzk5WVFRUUpLS9MHH3xQ7gLr5ORkSdL999+v/v37KygoSLfcckuNeg0LC9OiRYs0ZswYde/eXV988YU+//xzPfbYY57TcBo3bqybbrpJL7/8shwOh1q3bq2FCxd6vlDuZN709vTTT2vx4sXq2bOn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rl279OGHH2r8+PEaNmyYL3oEAAAAUMd5fY3F3LlzNWXKFN16660qLS09XiQ4WOPHj9fMmTNtbxAAAABA3ed1sKhfv75ee+01zZw5U1u3bpUktW7dWg0aNLC9OQAAAACBwetgccLevXu1d+9e9erVS+Hh4TLGcItZnFWysrLkcrlsrZmRkWFrPaAqdr/feP8CAKridbDIzc3VzTffrGXLlsnhcOiXX35Rq1atNH78eDVp0oQ7Q+GskJWVpfYdOqqo8Ki/WwG8Vlbwq+RwaNSoUf5uBQBwDvE6WDz44IMKCQlRVlaWOnbs6Bn/7W9/q8mTJxMscFZwuVwqKjyq6OsfUkh0om11C7el6dCqv9tWD6iMu7hAMob3LwCgVnkdLL766it9+eWXOv/888uNt23bVjt37rStMaAuCIlOVGhCG9vqleZm21YLOBPevwCA2uT17WaPHDmi+vXrVxg/ePCgQkNDbWkKAAAAQGDxOlikpKTonXfe8Tx2OBxyu92aMWOGevfubWtzAAAAAAKD16dCzZgxQ3369FFaWppKSkr08MMP68cff9TBgwf1zTff+KJHAAAAAHWc10csLrroIv3888/q2bOnBg8erCNHjmjYsGFav369Wrdu7YseAQAAANRxXh2xKC0t1YABAzR37lz9v//3/3zVEwAAAIAA49URi5CQEG3cuNFXvQAAAAAIUF6fCjVq1Ci9+eabvugFAAAAQIDy+uLtY8eO6a233tKSJUuUnJysBg0alHt+9uzZtjUHAAAAIDBUK1hs3LhRF110kZxOpzZt2qSuXbtKkn7++edy0zkcDvs7BAAAAFDnVStYdOnSRXv37lVcXJx27typ7777TtHR0ZZffOXKlZo5c6bS09O1d+9effzxxxoyZIjneWOMpk6dqr/+9a/Ky8vTlVdeqTlz5qht27aWXxsAAACAfap1jUVkZKS2b98uSdqxY4fcbrctL37kyBFdcsklevXVVyt9fsaMGXrppZc0d+5crVu3Tg0aNFD//v1VVFRky+sDAAAAsEe1jlgMHz5cV111lZo2bSqHw6FLL71UQUFBlU67bdu2ar/4wIEDNXDgwEqfM8bohRde0OOPP67BgwdLkt555x3Fx8frk08+0S233FLt1wEAAADgW9UKFm+88YaGDRumLVu26P7779cdd9yhRo0a+bSx7du3a9++ferbt69n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uuiii/Too4/q6NGjtdAdTudM2419LXAtX75ccXFxat++ve6++27l5ub6uyVUYfv27dq3b1+5fa1x48bq3r07+1otWLt2rSIjI3XppZd6xvr27Sun06l169ZVOS+fa74VEF+Qh7rnkUce0SuvvKKjR4/q8ssv18KFC0877b59+1SvXr0K563Gx8dr3759Pu4Up9qyZYtefvll/eUvf6lyultvvVXNmzdXs2bNtHHjRj3yyCPKzMzURx99VEud4mTV2W7sa4FpwIABGjZsmFq2bKmtW7fqscce08CBA7V27VoFBQX5uz1U4sT+FB8fX26cfa127Nu3T3FxceXGgoODFRUVVeX653PN9zhiAUnSH/7whwoXNJ36s3nzZs/0v//977V+/Xp99dVXCgoK0ujRo2X4rsVa5e02k6Tdu3drwIABuummm3THHXdUWX/ChAnq37+/OnXqpJEjR+qdd97Rxx9/rK1bt/pysc56vt5usF9Ntpk3brnlFt1www3q1KmThgwZooULF+q7777T8uXL7VuIc5Cvtxvs5+ttxuea73HEApKkhx56SGPHjq1ymlatWnn+HRMTo5iYGLVr104dO3ZUYmKivv32W/Xo0aPCfAkJCSopKVFeXl65/0ndv3+/EhIS7FqEc46322zPnj3q3bu3rrjiCr3xxhtev1737t0lHf+f89atW3s9P47z5XZjX/MNb7eZVa1atVJMTIy2bNmiPn362Fb3XOPL7XZif9q/f7+aNm3qGd+/f786d+5co5qo/jZLSEjQgQMHyo0fO3ZMBw8e9Op3HZ9r9iNYQJIUGxur2NjYGs3rdrslScXFxZU+n5ycrJCQEC1dulTDhw+XJGVmZiorK6vSIILq8Wab7d69W71791ZycrJSU1PldHp/sHLDhg2SVO5DFN7z5XZjX/MNK78fa2LXrl3Kzc1lX7PIl9utZcuWSkhI0NKlSz1BIj8/X+vWrfP6jmD4P9XdZj169FBeXp7S09OVnJwsSfr666/ldrs9YaE6+FzzAX9fPY7A8u2335qXX37ZrF+/3uzYscMsXbrUXHHFFaZ169amqKjIGGPMrl27TPv27c26des88911110mKSnJfP311yYtLc306NHD9OjRw1+LcU7ZtWuXadOmjenTp4/ZtWuX2bt3r+fn5GlO3mZbtmwxTz75pElLSzPbt283n376qWnVqpXp1auXvxbjnFOT7WYM+5q/7dy506xfv95MmzbNNGzY0Kxfv96sX7/eHD582DNN+/btzUcffWSMMebw4cNmypQpZu3atWb79u1myZIlpmvXrqZt27ae36nwPW+3mzHGPPvssyYyMtJ8+umnZuPGjWbw4MGmZcuWprCw0B+LcM4ZMGCA6dKli1m3bp1ZvXq1adu2rRkxYoTneT7X/INgAa9s3LjR9O7d20RFRZnQ0FDTokULc9ddd5ldu3Z5ptm+fbuRZJYtW+YZKywsNPfcc49p0qSJqV+/vhk6dGi5P5DgO6mpqUZSpT8nnLrNsrKyTK9evTzbuU2bNub3v/+9OXTokJ+W4txTk+1mDPuav40ZM6bSbXbyNpJkUlNTjTHGHD161PTr18/ExsaakJAQ07x5c3PHHXeYffv2+WcBzlHebjdjjt9y9o9//KOJj483oaGhpk+fPiYzM7P2mz9H5ebmmhEjRpiGDRuaiIgIM27cuHJBkM81/3AYwxW3AAAAAKzhrlAAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAC/adGihV544QV/twEAsAHBAgAAAIBlBAsAAAAAlhEsAAA18sYbb6hZs2Zyu93lxgcPHqzbb79dW7du1eDBgxUfH6+GDRuqW7duWrJkyWnr7dixQw6HQxs2bPCM5eXlyeFwaPny5Z6xTZs2aeDAgWrYsKHi4+N12223yeVy2b14AAAvESwAADVy0003KTc3V8uWLfOMHTx4UIsWLdLIkSNVUFCga6+9VkuXLtX69es1YMAADRo0SFlZWTV+zby8PF1zzTXq0qWL0tLStGjRIu3fv18333yzHYsEALAg2N8NAAACU5MmTTRw4EC999576tOnjyTpgw8+UExMjHr37i2n06lLLrnEM/1TTz2ljz/+WJ999pkmTpxYo9d85ZVX1KVLFz3zzDOesbfeekuJiYn6+eef1a5dO2sLBQCoMY5YAABqbOTIkfrwww9VXFwsSZo/f75uueUWOZ1OFRQUaMqUKerYsaMiIyPVsGFDZWRkWDpi8cMPP2jZsmVq2LCh56dDhw6SpK1bt9qyTACAmuGIBQCgxgYNGiRjjD7//HN169ZNq1at0vPPPy9JmjJlihYvXqy//OUvatOmjcLDw3XjjTeqpKSk0lpO5/H/6zLGeMZKS0vLTVNQUKBBgwbpueeeqzB/06ZN7VosAEANECwAADUWFhamYcOGaf78+dqyZYvat2+vrl27SpK++eYbjR07VkOHDpV0PBTs2LHjtLViY2MlSXv37lWXLl0kqdyF3JLUtWtXffjhh2rRooWCg/kIA4C6hFOhAACWjBw5Up9//rneeustjRw50jPetm1bffTRR9qwYYN++OEH3XrrrRXuIHWy8PBwXX755Xr22WeVkZGhFStW6PHHHy83zb333quDBw9qxIgR+u6777R161Z9+eWXGjdunMrKyny2jACAMyNYAAAsueaaaxQVFaXMzEzdeuutnvHZs2erSZMmuuKKKzRo0CD179/fczTjdN566y0dO3ZMycnJeuCBB/T000+Xe75Zs2b65ptvVFZWpn79+qlTp0564IEHFBkZ6TmVCgDgHw5z8smsAAAAAFAD/PcOAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAsv8PJ/c64COMmPMAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "from sklearn.preprocessing import StandardScaler\n", "import torch\n", "from dataset import QEC_Dataset\n", "import matplotlib.pyplot as plt\n", + "\n", "# delolarizing noise, physial error rate = 0.05\n", "l = 6\n", "g = 3\n", - "para_dict = {'l':l,'g':g}\n", - "total_dataset = QEC_Dataset(l,g,load = True,number = 300,save = True,path='./')\n", + "para_dict = {\"l\": l, \"g\": g}\n", + "total_dataset = QEC_Dataset(\n", + " l, g, load=True, number=300, save=True, path=\"./data/codes/\"\n", + ")\n", "total_dataset.X = np.array(total_dataset.X)\n", "\n", + "\n", "# My current dataset uses word error rate: WER = 1-(1-LER)^{1/k}\n", - "def convert_to_logical_error_rate(x,y):\n", - " codeconstructor = CodeConstructor(method='bb',para_dict = para_dict)\n", + "def convert_to_logical_error_rate(x, y):\n", + " codeconstructor = CodeConstructor(method=\"bb\", para_dict=para_dict)\n", " x1 = codeconstructor.construct(x)\n", " # print(x.k)\n", - " y = 1 - (1 - y)**x1.k\n", + " y = 1 - (1 - y) ** x1.k\n", " return y\n", + "\n", + "\n", "# set word error rate(logical error rate per qubit) to logical error rate\n", "# print(total_dataset.y)\n", - "total_dataset.y = torch.tensor([convert_to_logical_error_rate(total_dataset.X[i],total_dataset.y[i]) for i in range(len(total_dataset.y))])\n", + "total_dataset.y = torch.tensor(\n", + " [\n", + " convert_to_logical_error_rate(total_dataset.X[i], total_dataset.y[i])\n", + " for i in range(len(total_dataset.y))\n", + " ]\n", + ")\n", "# print(total_dataset.y)\n", "\n", "\n", - "\n", - "eps = 1e-8 \n", + "eps = 1e-8\n", "total_dataset.y = np.log(total_dataset.y + eps)\n", "\n", "\"\"\"\n", @@ -484,76 +372,64 @@ "# total_dataset.y = torch.from_numpy(sy.transform(total_dataset.y.numpy().reshape(-1,1))).float()\n", "\n", "\n", - "import random\n", - "from torch.utils.data import Subset\n", - "import pandas as pd\n", "def plot_distribution(y, bin_width):\n", " bins = np.arange(np.min(y), np.max(y) + bin_width, bin_width)\n", - " \n", + "\n", " plt.figure(figsize=(8, 5))\n", - " plt.hist(y, bins=bins, edgecolor='black')\n", - " plt.xlabel('value')\n", - " plt.ylabel('frequency')\n", - " plt.title(f'distribution of y with width={bin_width}')\n", - " plt.grid(axis='y', alpha=0.3)\n", + " plt.hist(y, bins=bins, edgecolor=\"black\")\n", + " plt.xlabel(\"value\")\n", + " plt.ylabel(\"frequency\")\n", + " plt.title(f\"distribution of y with width={bin_width}\")\n", + " plt.grid(axis=\"y\", alpha=0.3)\n", " plt.tight_layout()\n", " plt.show()\n", + "\n", + "\n", "plot_distribution(total_dataset.y.detach().cpu().numpy(), bin_width=0.1)" ] }, { "cell_type": "code", - "execution_count": 11, - "id": "dda166d7", + "execution_count": null, + "id": "10", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "loading\n", - "successfully loaded\n", - "[tensor(0.0556), tensor(0.0578), tensor(0.0834), tensor(0.0991), tensor(0.1054), tensor(0.1148), tensor(0.1176), tensor(0.1217), tensor(0.1243), tensor(0.1262), tensor(0.1310), tensor(0.1346), tensor(0.1421), tensor(0.1456), tensor(0.1616), tensor(0.1640), tensor(0.1651), tensor(0.2039), tensor(0.2059), tensor(0.2192), tensor(0.2226), tensor(0.2229), tensor(0.2388), tensor(0.2408), tensor(0.2449), tensor(0.2535), tensor(0.2548), tensor(0.2606), tensor(0.2663), tensor(0.2769), tensor(0.2811), tensor(0.2828), tensor(0.2837), tensor(0.2918), tensor(0.2930), tensor(0.2951), tensor(0.2951), tensor(0.2953), tensor(0.2977), tensor(0.3002), tensor(0.3029), tensor(0.3087), tensor(0.3106), tensor(0.3148), tensor(0.3171), tensor(0.3178), tensor(0.3180), tensor(0.3202), tensor(0.3242), tensor(0.3358), tensor(0.3368), tensor(0.3631), tensor(0.3655), tensor(0.3673), tensor(0.3676), tensor(0.3866), tensor(0.3928), tensor(0.3947), tensor(0.3979), tensor(0.3984), tensor(0.3988), tensor(0.4011), tensor(0.4041), tensor(0.4058), tensor(0.4144), tensor(0.4173), tensor(0.4185), tensor(0.4193), tensor(0.4316), tensor(0.4318), tensor(0.4330), tensor(0.4410), tensor(0.4418), tensor(0.4460), tensor(0.4580), tensor(0.4743), tensor(0.4815), tensor(0.4929), tensor(0.4943), tensor(0.4971), tensor(0.4988), tensor(0.4992), tensor(0.4998), tensor(0.5065), tensor(0.5079), tensor(0.5091), tensor(0.5093), tensor(0.5174), tensor(0.5207), tensor(0.5233), tensor(0.5349), tensor(0.5359), tensor(0.5359), tensor(0.5477), tensor(0.5527), tensor(0.5538), tensor(0.5548), tensor(0.5654), tensor(0.5681), tensor(0.5700), tensor(0.5809), tensor(0.5820), tensor(0.5926), tensor(0.5930), tensor(0.5958), tensor(0.5990), tensor(0.5994), tensor(0.5997), tensor(0.6072), tensor(0.6237), tensor(0.6273), tensor(0.6328), tensor(0.6331), tensor(0.6387), tensor(0.6404), tensor(0.6443), tensor(0.6531), tensor(0.6634), tensor(0.6637), tensor(0.6741), tensor(0.6809), tensor(0.6847), tensor(0.6920), tensor(0.6930), tensor(0.6984), tensor(0.6996), tensor(0.7019), tensor(0.7033), tensor(0.7096), tensor(0.7097), tensor(0.7179), tensor(0.7182), tensor(0.7188), tensor(0.7222), tensor(0.7241), tensor(0.7243), tensor(0.7247), tensor(0.7259), tensor(0.7261), tensor(0.7283), tensor(0.7288), tensor(0.7356), tensor(0.7470), tensor(0.7471), tensor(0.7528), tensor(0.7552), tensor(0.7559), tensor(0.7596), tensor(0.7617), tensor(0.7661), tensor(0.7729), tensor(0.7730), tensor(0.7732), tensor(0.7757), tensor(0.7800), tensor(0.7809), tensor(0.7833), tensor(0.7860), tensor(0.7884), tensor(0.7918), tensor(0.7989), tensor(0.8050), tensor(0.8059), tensor(0.8062), tensor(0.8112), tensor(0.8171), tensor(0.8208), tensor(0.8256), tensor(0.8275), tensor(0.8314), tensor(0.8323), tensor(0.8333), tensor(0.8337), tensor(0.8374), tensor(0.8429), tensor(0.8432), tensor(0.8503), tensor(0.8520), tensor(0.8530), tensor(0.8530), tensor(0.8545), tensor(0.8563), tensor(0.8565), tensor(0.8588), tensor(0.8607), tensor(0.8627), tensor(0.8630), tensor(0.8638), tensor(0.8646), tensor(0.8648), tensor(0.8654), tensor(0.8696), tensor(0.8733), tensor(0.8751), tensor(0.8760), tensor(0.8803), tensor(0.8855), tensor(0.8911), tensor(0.8923), tensor(0.8928), tensor(0.8932), tensor(0.8996), tensor(0.9007), tensor(0.9013), tensor(0.9030), tensor(0.9044), tensor(0.9059), tensor(0.9069), tensor(0.9078), tensor(0.9084), tensor(0.9108), tensor(0.9131), tensor(0.9134), tensor(0.9166), tensor(0.9190), tensor(0.9222), tensor(0.9234), tensor(0.9300), tensor(0.9312), tensor(0.9345), tensor(0.9355), tensor(0.9368), tensor(0.9377), tensor(0.9389), tensor(0.9413), tensor(0.9422), tensor(0.9482), tensor(0.9496), tensor(0.9499), tensor(0.9513), tensor(0.9514), tensor(0.9514), tensor(0.9516), tensor(0.9531), tensor(0.9542), tensor(0.9559), tensor(0.9607), tensor(0.9665), tensor(0.9689), tensor(0.9695), tensor(0.9709), tensor(0.9712), tensor(0.9718), tensor(0.9722), tensor(0.9729), tensor(0.9731), tensor(0.9738), tensor(0.9747), tensor(0.9758), tensor(0.9772), tensor(0.9787), tensor(0.9823), tensor(0.9824), tensor(0.9826), tensor(0.9829), tensor(0.9835), tensor(0.9847), tensor(0.9848), tensor(0.9851), tensor(0.9862), tensor(0.9865), tensor(0.9870), tensor(0.9872), tensor(0.9881), tensor(0.9889), tensor(0.9900), tensor(0.9917), tensor(0.9918), tensor(0.9929), tensor(0.9933), tensor(0.9948), tensor(0.9954), tensor(0.9955), tensor(0.9973), tensor(0.9976), tensor(0.9982), tensor(0.9983), tensor(0.9985), tensor(0.9986), tensor(0.9988), tensor(0.9989), tensor(0.9991), tensor(0.9995), tensor(0.9997), tensor(0.9998), tensor(0.9998), tensor(0.9998), tensor(0.9998), tensor(0.9999), tensor(1.0000), tensor(1.0000), tensor(1.0000), tensor(1.0000), tensor(1.0000), tensor(1.0000), tensor(1.0000), tensor(1.0000), tensor(1.0000), tensor(1.), tensor(1.)]\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxYAAAHqCAYAAACZcdjsAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAABADklEQVR4nO3deXgUVd728bs7ZGMJgexogMguoEAEZFWEIaAiEQYfFWWRER1BWcQFHxVBFAFBBlDRGY3LICru44JCRBZBhIgyCAZZA4SEBAwJQkJIn/cPXvohJEA61Z1Oh+/nunJd9umqU7/q0233TdWpshljjAAAAADAAru3CwAAAADg+wgWAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAqnaeeeko2m61YW8OGDTVs2DCPb3v37t2y2Wx64403nG3Dhg1TzZo1Pb7t02w2m5566qkK2155rF+/Xp07d1aNGjVks9n0888/e7skp9LePxdaNjs728NVFVfWMfaFfQGA0wgWAKqsL7/8stL+QK/MtV1IYWGhBg0apMOHD+uFF17Q22+/rQYNGni7rPN69tln9cknn3i7DLfw9r589tlnateunYKCglS/fn1NmjRJJ0+eLNO6zzzzjG666SZFRUX5RIAG4BqCBQCfkJqaqn/+858urfPll19q8uTJLq3ToEEDHT9+XHfeeadL67nqfLUdP35cjz/+uEe3b8WOHTu0Z88eTZgwQSNHjtQdd9yhOnXqeLssp8cff1zHjx8v1ubtH+NnszLG3tyXr776SomJiQoNDdW8efOUmJioqVOn6v777y/T+o8//rjWr1+vtm3berhSAN5QzdsFAEBZBAYGerT/kydPyuFwKCAgQEFBQR7d1oV4e/sXcvDgQUlSaGiodws5h2rVqqlatcr99VbZx/hcJkyYoCuuuELffPON8zUOCQnRs88+qzFjxqh58+bnXX/Xrl1q2LChsrOzFRERURElA6hAHLEA4FWrV69W+/btFRQUpEaNGumVV14pdbmz51gUFhZq8uTJatKkiYKCghQWFqauXbtq6dKlkk7Ni3jxxRclnTqf/fSf9H/zKJ5//nnNmTNHjRo1UmBgoLZs2VLqHIvTdu7cqYSEBNWoUUP16tXTlClTZIxxPv/dd9/JZrPpu+++K7be2X2er7bTbWefIrJx40b17dtXISEhqlmzpnr27Kkffvih2DJvvPGGbDabvv/+e40fP14RERGqUaOGbr75ZmVlZZU+AGf59ttv1a1bN9WoUUOhoaHq37+/tm7d6nx+2LBhuuaaayRJgwYNks1m07XXXltqXzt37pTNZtMLL7xQ4rk1a9bIZrNp0aJFpa5rjFF4eLjGjx/vbHM4HAoNDZWfn59ycnKc7dOnT1e1atV09OhRSSXnJdhsNv3555968803na/12fN1cnJyNGzYMIWGhqp27doaPny4jh07dt7Xau7cuSVqmTVrlmw2W7G6i4qKVKtWLT3yyCPFajp7jMvyWfDUvpTFli1btGXLFo0cObJYcLvvvvtkjNEHH3xwwT4aNmxouQ4AlVfl/icdAFXaf//7X/Xu3VsRERF66qmndPLkSU2aNElRUVEXXPepp57StGnT9Le//U0dOnRQbm6uNmzYoJ9++kl/+ctfdM899yg9PV1Lly7V22+/XWofSUlJys/P18iRIxUYGKi6devK4XCUumxRUZH69Omjq6++WjNmzNCSJUuc55ZPmTLFpf0uS21n+vXXX9WtWzeFhITo4Ycflr+/v1555RVde+21WrFihTp27Fhs+fvvv1916tTRpEmTtHv3bs2ZM0ejR4/We++9d97tLFu2TH379tVll12mp556SsePH9e8efPUpUsX/fTTT2rYsKHuueceXXLJJXr22Wf1wAMPqH379uccr8suu0xdunTRwoULNW7cuGLPLVy4ULVq1VL//v1LXddms6lLly5auXKls23Tpk06cuSI7Ha7vv/+e91www2SpFWrVqlt27bnnGD/9ttvO98nI0eOlCQ1atSo2DK33HKL4uLiNG3aNP3000/617/+pcjISE2fPv2cr1e3bt3kcDi0evVq3Xjjjc5a7Ha7Vq1a5Vxu48aNOnr0qLp3737Ovsr6WXDXvhw5ckSFhYXnrOe0oKAg5+u6ceNGSdJVV11VbJl69erp0ksvdT4P4CJmAMBLEhMTTVBQkNmzZ4+zbcuWLcbPz8+c/b+nBg0amKFDhzofX3nlleaGG244b/+jRo0q0Y8xxuzatctIMiEhIebgwYOlPpeUlORsGzp0qJFk7r//fmebw+EwN9xwgwkICDBZWVnGGGOWL19uJJnly5dfsM9z1WaMMZLMpEmTnI8TExNNQECA2bFjh7MtPT3d1KpVy3Tv3t3ZlpSUZCSZXr16GYfD4WwfN26c8fPzMzk5OaVu77Q2bdqYyMhIc+jQIWfbL7/8Yux2uxkyZIiz7fR+Ll68+Lz9GWPMK6+8YiSZrVu3OttOnDhhwsPDi41naWbOnGn8/PxMbm6uMcaYuXPnmgYNGpgOHTqYRx55xBhjTFFRkQkNDTXjxo1zrjdp0qQSr22NGjVK3d7pZe+6665i7TfffLMJCws7b31FRUUmJCTEPPzww8aYU++JsLAwM2jQIOPn52fy8vKMMcbMnj3b2O1288cffzjXLW2My/pZcMe+XHPNNUbSBf/O3M7MmTONJJOWllZi2+3btzdXX331eV+vM2VlZZV4DQD4Pk6FAuAVRUVF+vrrr5WYmKj69es721u0aKGEhIQLrh8aGqpff/1Vv//+e7lrGDhwoEvneY8ePdr53zabTaNHj9aJEye0bNmyctdwIUVFRfrmm2+UmJioyy67zNkeExOj22+/XatXr1Zubm6xdUaOHFnsVKBu3bqpqKhIe/bsOed2Dhw4oJ9//lnDhg1T3bp1ne1XXHGF/vKXv+jLL78sV/233HKLgoKCtHDhQmfb119/rezsbN1xxx3nXfd03WvWrJF06mhAt27d1K1bN+cRgc2bNysnJ0fdunUrV32n3XvvvSW2fejQoRKv7Znsdrs6d+7sPKqydetWHTp0SI8++qiMMVq7dq2z7latWp1zTorVz0J59mXWrFlaunTpBf8efvhh5zqnJ8SXNt8pKCioxIR5ABcfggUAr8jKytLx48fVpEmTEs81a9bsgutPmTJFOTk5atq0qVq3bq2HHnpImzZtcqmGuLi4Mi9rt9uL/bCXpKZNm0o6NYfCU7KysnTs2LFSX5MWLVrI4XBo7969xdrP/HEqyXnFpj/++OOc2zkdOs61nezsbP35558u1x8aGqp+/frpnXfecbYtXLhQl1xyia677rrzrtuuXTtVr17dGSJOB4vu3btrw4YNys/Pdz7XtWtXl2s7U3leM+nUj/aUlBQdP35cq1atUkxMjNq1a6crr7zSWdvq1avPG3ysfhbOVpZ9iY+PV69evS74d/nllzvXCQ4OliQVFBSU2GZ+fr7zeQAXL4IFAJ/UvXt37dixQ6+//rpatWqlf/3rX2rXrp3+9a9/lbkPd/8QOteNzIqKity6nQvx8/Mrtd2cMdG8Ig0ZMkQ7d+7UmjVrlJeXp88++0y33Xab7PbzfwX5+/urY8eOWrlypbZv366MjAx169ZNXbt2VWFhodatW6dVq1apefPmlq8wVN7X7HQta9eudQYfSc6jKr/99puysrIsH1FxRVn25fDhw8rIyLjg35EjR5zrxMTESDp1dOtsBw4cUL169dy8JwB8DcECgFdEREQoODi41FOZUlNTy9RH3bp1NXz4cC1atEh79+7VFVdcUexKO2W9Y3FZOBwO7dy5s1jbtm3bJP3flW5O/8vwmVcJklTqKUhlrS0iIkLVq1cv9TX57bffZLfbFRsbW6a+zuf0De7OtZ3w8HDVqFGjXH336dNHERERWrhwoT7++GMdO3aszPcJ6datm3788UctW7ZM4eHhat68uerWrauWLVtq1apVWrVq1XknRZ/mzvfCmTp06KCAgABnLacDRPfu3bVu3TolJyc7H5+Lq58Fd+zLgAEDFBMTc8G/MWPGONdp06aNJGnDhg3F+kpPT9e+ffuczwO4eHFVKABe4efnp4SEBH3yySdKS0tznr6xdetWff311xdc/9ChQwoLC3M+rlmzpho3blzstKDTP4RzcnLccs+F+fPna+7cuZJO/evv/Pnz5e/vr549e0o69ePcz89PK1euVGJionO9l156qURfZa3Nz89PvXv31qeffqrdu3c7Q0xmZqbeeecdde3aVSEhIZb3LSYmRm3atNGbb76piRMnOmvavHmzvvnmmwvOhzifatWq6bbbbtM777yjrVu3qnXr1rriiivKtG63bt00ZcoUzZkzR127dnX+qO7WrZvefvttpaenl+loQI0aNUoEPncICgpS+/bttWjRIqWlpRU7YnH8+HHNnTtXjRo1cv5rf2lc/Sy4Y19mzZp1wdO8JBU7CtGyZUs1b95cr776qu655x7nkZGXX35ZNptNf/3rX53LHjlyRAcOHFBMTIxq165tqVYAvoNgAcBrJk+erCVLlqhbt2667777dPLkSc2bN08tW7a84HyJyy+/XNdee63i4+NVt25dbdiwQR988EGxCdbx8fGSpAceeEAJCQny8/PTrbfeWq5ag4KCtGTJEg0dOlQdO3bUV199pS+++EKPPfaY8zSc2rVra9CgQZo3b55sNpsaNWqkzz//3HlDuTO5UtvUqVO1dOlSde3aVffdd5+q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W7euCQwMNA0bNjT33nuv2bdvn3OZXbt2GUlm+fLlzrbjx4+b++67z9SpU8dUr17d3HzzzcV+AKFiJSUlGUml/p129jimpaWZ7t27O8e+cePG5qGHHjJHjhzx0l5c3MozhsbwWaxshg4dWuoYnjlmkkxSUpIxxphjx46Z3r17m4iICOPv728aNGhg7r77bucXKbzD1XE05tQlZ5944gkTFRVlAgMDTc+ePU1qamrFFw9jjDGHDh0yt912m6lZs6YJCQkxw4cPLxYM+U6snObNm2fq169vAgICTIcOHcwPP/zgfO6aa64xQ4cOLbb8+++/b5o2bWoCAgJMy5YtzRdffFHBFV+YzRhmLQMAAACwpkpcFQoAAACAdxEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAIDXNGzYUHPmzPF2GQAANyBYAAAAALCMYAEAAADAMoIFAKBcXn31VdWrV08Oh6NYe//+/XXXXXdpx44d6t+/v6KiolSzZk21b99ey5YtO2d/u3fvls1m088//+xsy8nJkc1m03fffeds27x5s/r27auaNWsqKipKd955p7Kzs929ewAAFxEsAADlMmjQIB06dEjLly93th0+fFhLlizR4MGDdfToUV1//fVKTk7Wxo0b1adPH/Xr109paWnl3mZOTo6uu+46tW3bVhs2bNCSJUuUmZmpW265xR27BACwoJq3CwAA+KY6deqob9++euedd9SzZ09J0gcffKDw8HD16NFDdrtdV155pXP5p59+Wh9//LE+++wzjR49ulzbnD9/vtq2batnn33W2fb6668rNjZW27ZtU9OmTa3tFACg3DhiAQAot8GDB+vDDz9UQUGBJGnhwoW69dZbZbfbdfToUU2YMEEtWrRQaGioatasqa1bt1o6YvHLL79o+fLlqlmzpvOvefPmkqQdO3a4ZZ8AAOXDEQsAQLn169dPxhh98cUXat++vVatWqUXXnhBkjRhwgQtXbpUzz//vBo3bqzg4GD99a9/1YkTJ0rty24/9W9dxhhnW2FhYbFljh49qn79+mn69Okl1o+JiXHXbgEAyoFgAQAot6CgIA0YMEALFy7U9u3b1axZM7Vr106S9P3332vYsGG6+eabJZ0KBbt37z5nXxEREZKkAwcOqG3btpJUbCK3JLVr104ffvihGjZsqGrV+AoDgMqEU6EAAJYMHjxYX3zxhV5//XUNHjzY2d6kSRN99NFH+vnnn/XLL7/o9ttvL3EFqTMFBwfr6quv1nPPPaetW7dqxYoVevzxx4stM2rUKB0+fFi33Xab1q9frx07dujrr7/W8OHDVVRU5LF9BABcGMECAGDJddddp7p16yo1NVW33367s3327NmqU6eOOnfurH79+ikhIcF5NONcXn/9dZ08eVLx8fEaO3aspk6dWuz5evXq6fvvv1dRUZF69+6t1q1ba+zYsQoNDXWeSgUA8A6bOfNkVgAAAAAoB/55BwAAAIBlBAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAAAAAJYRLAAAAABYRrAAAAAAYNn/A/Oy6AjgvzK/AAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "from sklearn.preprocessing import StandardScaler\n", "import torch\n", "from dataset import QEC_Dataset\n", - "import matplotlib.pyplot as plt\n", + "\n", "# SD6 noise, physical error rate = 0.007\n", "l = 12\n", "g = 6\n", - "para_dict = {'l':l,'g':g}\n", - "total_dataset = QEC_Dataset(l,g,load = True,number = 300,save = True,path='./')\n", + "para_dict = {\"l\": l, \"g\": g}\n", + "total_dataset = QEC_Dataset(\n", + " l, g, load=True, number=300, save=True, path=\"./data/codes/\"\n", + ")\n", "total_dataset.X = np.array(total_dataset.X)\n", "\n", + "\n", "# My current dataset uses word error rate: WER = 1-(1-LER)^{1/k}\n", - "def convert_to_logical_error_rate(x,y):\n", - " codeconstructor = CodeConstructor(method='bb',para_dict = para_dict)\n", + "def convert_to_logical_error_rate(x, y):\n", + " codeconstructor = CodeConstructor(method=\"bb\", para_dict=para_dict)\n", " x1 = codeconstructor.construct(x)\n", " # print(x.k)\n", - " y = 1 - (1 - y)**x1.k\n", + " y = 1 - (1 - y) ** x1.k\n", " return y\n", + "\n", + "\n", "# set word error rate(logical error rate per qubit) to logical error rate\n", "# print(total_dataset.y)\n", - "total_dataset.y = torch.tensor([convert_to_logical_error_rate(total_dataset.X[i],total_dataset.y[i]) for i in range(len(total_dataset.y))])\n", + "total_dataset.y = torch.tensor(\n", + " [\n", + " convert_to_logical_error_rate(total_dataset.X[i], total_dataset.y[i])\n", + " for i in range(len(total_dataset.y))\n", + " ]\n", + ")\n", "# print(total_dataset.y)\n", "\n", "\n", "print(sorted(total_dataset.y))\n", - "eps = 1e-8 \n", + "eps = 1e-8\n", "total_dataset.y = np.log(total_dataset.y + eps)\n", "\n", "\"\"\"\n", @@ -563,28 +439,27 @@ "# total_dataset.y = torch.from_numpy(sy.transform(total_dataset.y.numpy().reshape(-1,1))).float()\n", "\n", "\n", - "import random\n", - "from torch.utils.data import Subset\n", - "import pandas as pd\n", "def plot_distribution(y, bin_width):\n", " bins = np.arange(np.min(y), np.max(y) + bin_width, bin_width)\n", - " \n", + "\n", " plt.figure(figsize=(8, 5))\n", - " plt.hist(y, bins=bins, edgecolor='black')\n", - " plt.xlabel('value')\n", - " plt.ylabel('frequency')\n", - " plt.title(f'distribution of y with width={bin_width}')\n", - " plt.grid(axis='y', alpha=0.3)\n", + " plt.hist(y, bins=bins, edgecolor=\"black\")\n", + " plt.xlabel(\"value\")\n", + " plt.ylabel(\"frequency\")\n", + " plt.title(f\"distribution of y with width={bin_width}\")\n", + " plt.grid(axis=\"y\", alpha=0.3)\n", " plt.tight_layout()\n", " # plt.xscale('log')\n", " plt.show()\n", - "plot_distribution(total_dataset.y.detach().cpu().numpy(), bin_width=0.1)\n" + "\n", + "\n", + "plot_distribution(total_dataset.y.detach().cpu().numpy(), bin_width=0.1)" ] } ], "metadata": { "kernelspec": { - "display_name": "QEC2", + "display_name": "qec", "language": "python", "name": "python3" }, @@ -598,7 +473,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.13" + "version": "3.10.19" } }, "nbformat": 4, diff --git a/readme2.ipynb b/readme2.ipynb index 6d0e914..ff80b84 100644 --- a/readme2.ipynb +++ b/readme2.ipynb @@ -2,49 +2,28 @@ "cells": [ { "cell_type": "markdown", - "id": "a5da2dc7", + "id": "0", "metadata": {}, "source": [ "# An instruction of how to set up the environmnet\n", "\n", - "Python=3.10 is required for our experiment, please create a new environment with Python=3.10" + "Please create a new environment with Python=3.11" ] }, { "cell_type": "code", "execution_count": null, - "id": "5691cce4", + "id": "1", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Channels:\n", - " - \n", - "Platform: linux-64\n", - "Collecting package metadata (repodata.json): done\n", - "Solving environment: failed\n", - "\n", - "PackagesNotFoundError: The following packages are missing from the target environment:\n", - "\n", - " - llvmlite\n", - "\n", - "\n", - "\n", - "CondaError: Run 'conda init' before 'conda activate'\n", - "\n" - ] - } - ], + "outputs": [], "source": [ - "!conda create -n qec python=3.10 numpy scipy matplotlib pandas\n", + "!conda create -n qec python=3.11\n", "!conda activate qec" ] }, { "cell_type": "markdown", - "id": "427e396e", + "id": "2", "metadata": {}, "source": [ "## Important !" @@ -52,7 +31,7 @@ }, { "cell_type": "markdown", - "id": "47d2badf", + "id": "3", "metadata": {}, "source": [ "Before installing anything else, we need to install cuda and pytorch=2.6.0; This depends on the gpu and the operation system. See [[https://pytorch.org/get-started/locally/]] for torch and [[https://docs.nvidia.com/cuda/]] for cuda" @@ -60,438 +39,38 @@ }, { "cell_type": "markdown", - "id": "2d0586a4", + "id": "4", "metadata": {}, "source": [ "## pip libraries:\n", "\n", - "please run these commands in the terminal; or enter the `qec` environment and run the following block in the notebook.\n", + "Please run these commands in the terminal; or enter the `qec` environment and run the following block in the notebook. If you have a different version of cuda on your machine, change the pytorch URL.\n", "\n", "```\n", - "pip install torch==2.6.0 \n", - "\n", - "pip install torch-geometric\n", - "\n", - "pip install gpytorch botorch ax pymatching pymoo qecsim qldpc\n", - "\n", - "pip install toponetx topomodelx topoembedx\n", - "\n", - "pip install stim sinter\n", + "pip install -r requirements.txt\n", + "pip install torch --extra-index-url https://download.pytorch.org/whl/cu126\n", + "pip install topomodelx==0.0.1 --no-deps\n", + "pip install ortools==9.14.6206\n", "\n", - "pip install ldpc bposd\n", - "\n", - "pip install pymoo\n", "```" ] }, { "cell_type": "code", "execution_count": null, - "id": "916978bc", + "id": "5", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: torch==2.6.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (2.6.0)\n", - "Requirement already satisfied: filelock in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (3.17.0)\n", - "Requirement already satisfied: typing-extensions>=4.10.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (4.12.2)\n", - "Requirement already satisfied: networkx in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (2.6.3)\n", - "Requirement already satisfied: jinja2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (3.1.5)\n", - "Requirement already satisfied: fsspec in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (2025.2.0)\n", - "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.4.127 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (12.4.127)\n", - "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.4.127 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (12.4.127)\n", - "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.4.127 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (12.4.127)\n", - "Requirement already satisfied: nvidia-cudnn-cu12==9.1.0.70 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (9.1.0.70)\n", - "Requirement already satisfied: nvidia-cublas-cu12==12.4.5.8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (12.4.5.8)\n", - "Requirement already satisfied: nvidia-cufft-cu12==11.2.1.3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (11.2.1.3)\n", - "Requirement already satisfied: nvidia-curand-cu12==10.3.5.147 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (10.3.5.147)\n", - "Requirement already satisfied: nvidia-cusolver-cu12==11.6.1.9 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (11.6.1.9)\n", - "Requirement already satisfied: nvidia-cusparse-cu12==12.3.1.170 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (12.3.1.170)\n", - "Requirement already satisfied: nvidia-cusparselt-cu12==0.6.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (0.6.2)\n", - "Requirement already satisfied: nvidia-nccl-cu12==2.21.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (2.21.5)\n", - "Requirement already satisfied: nvidia-nvtx-cu12==12.4.127 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (12.4.127)\n", - "Requirement already satisfied: nvidia-nvjitlink-cu12==12.4.127 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (12.4.127)\n", - "Requirement already satisfied: triton==3.2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (3.2.0)\n", - "Requirement already satisfied: sympy==1.13.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (1.13.1)\n", - "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from sympy==1.13.1->torch==2.6.0) (1.3.0)\n", - "Requirement already satisfied: MarkupSafe>=2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from jinja2->torch==2.6.0) (3.0.2)\n", - "Requirement already satisfied: torch-geometric in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (2.6.1)\n", - "Requirement already satisfied: aiohttp in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch-geometric) (3.11.12)\n", - "Requirement already satisfied: fsspec in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch-geometric) (2025.2.0)\n", - "Requirement already satisfied: jinja2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch-geometric) (3.1.5)\n", - "Requirement already satisfied: numpy in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch-geometric) (1.26.4)\n", - "Requirement already satisfied: psutil>=5.8.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch-geometric) (6.1.1)\n", - "Requirement already satisfied: pyparsing in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch-geometric) (3.2.1)\n", - "Requirement already satisfied: requests in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch-geometric) (2.32.3)\n", - "Requirement already satisfied: tqdm in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch-geometric) (4.67.1)\n", - "Requirement already satisfied: aiohappyeyeballs>=2.3.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->torch-geometric) (2.4.6)\n", - "Requirement already satisfied: aiosignal>=1.1.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->torch-geometric) (1.3.2)\n", - "Requirement already satisfied: async-timeout<6.0,>=4.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->torch-geometric) (5.0.1)\n", - "Requirement already satisfied: attrs>=17.3.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->torch-geometric) (25.1.0)\n", - "Requirement already satisfied: frozenlist>=1.1.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->torch-geometric) (1.5.0)\n", - "Requirement already satisfied: multidict<7.0,>=4.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->torch-geometric) (6.1.0)\n", - "Requirement already satisfied: propcache>=0.2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->torch-geometric) (0.2.1)\n", - "Requirement already satisfied: yarl<2.0,>=1.17.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->torch-geometric) (1.18.3)\n", - "Requirement already satisfied: MarkupSafe>=2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from jinja2->torch-geometric) (3.0.2)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests->torch-geometric) (3.4.1)\n", - "Requirement already satisfied: idna<4,>=2.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests->torch-geometric) (3.10)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests->torch-geometric) (2.3.0)\n", - "Requirement already satisfied: certifi>=2017.4.17 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests->torch-geometric) (2025.1.31)\n", - "Requirement already satisfied: typing-extensions>=4.1.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from multidict<7.0,>=4.5->aiohttp->torch-geometric) (4.12.2)\n", - "Requirement already satisfied: gpytorch in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (1.14)\n", - "Requirement already satisfied: botorch in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (0.13.0)\n", - "Requirement already satisfied: ax in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (0.36.0)\n", - "Requirement already satisfied: pymatching in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (2.2.1)\n", - "Requirement already satisfied: pymoo in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (0.6.1.3)\n", - "Requirement already satisfied: qecsim in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (1.0b9)\n", - "Requirement already satisfied: qldpc in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (0.2.5)\n", - "Requirement already satisfied: jaxtyping in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from gpytorch) (0.2.37)\n", - "Requirement already satisfied: mpmath<=1.3,>=0.19 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from gpytorch) (1.3.0)\n", - "Requirement already satisfied: scikit-learn in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from gpytorch) (1.6.1)\n", - "Requirement already satisfied: scipy>=1.6.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from gpytorch) (1.13.1)\n", - "Requirement already satisfied: linear-operator>=0.6 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from gpytorch) (0.6)\n", - "Requirement already satisfied: typing_extensions in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from botorch) (4.12.2)\n", - "Requirement already satisfied: pyre_extensions in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from botorch) (0.0.32)\n", - "Requirement already satisfied: torch>=2.0.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from botorch) (2.6.0)\n", - "Requirement already satisfied: pyro-ppl>=1.8.4 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from botorch) (1.9.1)\n", - "Requirement already satisfied: multipledispatch in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from botorch) (1.0.0)\n", - "Requirement already satisfied: threadpoolctl in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from botorch) (3.5.0)\n", - "Requirement already satisfied: sanic in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (24.12.0)\n", - "Requirement already satisfied: Sanic-GraphQl in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (1.1.0)\n", - "Requirement already satisfied: Sanic-Cors in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (2.2.0)\n", - "Requirement already satisfied: sanic-compress in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (0.1.1)\n", - "Requirement already satisfied: SQLAlchemy in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (1.4.54)\n", - "Requirement already satisfied: sqlalchemy-utils in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (0.41.2)\n", - "Requirement already satisfied: alembic in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (1.14.1)\n", - "Requirement already satisfied: psycopg2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (2.9.10)\n", - "Requirement already satisfied: graphene in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (2.1.9)\n", - "Requirement already satisfied: graphene-sqlalchemy in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (2.3.0)\n", - "Requirement already satisfied: graphql-core in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (2.3.2)\n", - "Requirement already satisfied: graphql-relay in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (2.0.1)\n", - "Requirement already satisfied: graphql-ws in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (0.4.4)\n", - "Requirement already satisfied: aiocache[redis] in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (0.12.3)\n", - "Requirement already satisfied: aiopubsub in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (3.0.0)\n", - "Requirement already satisfied: APScheduler in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (3.11.0)\n", - "Requirement already satisfied: pyyaml in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (6.0.2)\n", - "Requirement already satisfied: ruamel.yaml in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (0.18.10)\n", - "Requirement already satisfied: typing in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (3.7.4.3)\n", - "Requirement already satisfied: ujson in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (5.10.0)\n", - "Requirement already satisfied: loguru in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (0.7.3)\n", - "Requirement already satisfied: docopt in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (0.6.2)\n", - "Requirement already satisfied: dotmap in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (1.3.30)\n", - "Requirement already satisfied: aiosmtplib in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (4.0.0)\n", - "Requirement already satisfied: sanic-jwt in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (1.8.0)\n", - "Requirement already satisfied: passlib in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (1.7.4)\n", - "Requirement already satisfied: markdown2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (2.5.3)\n", - "Requirement already satisfied: python-dateutil in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (2.9.0.post0)\n", - "Requirement already satisfied: gunicorn in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (23.0.0)\n", - "Requirement already satisfied: rsa in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (4.9)\n", - "Requirement already satisfied: stripe in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (11.6.0)\n", - "Requirement already satisfied: pyquery in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (2.0.1)\n", - "Requirement already satisfied: lxml in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (5.3.1)\n", - "Requirement already satisfied: openpyxl in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (3.1.5)\n", - "Requirement already satisfied: aiohttp in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (3.11.12)\n", - "Requirement already satisfied: aiofiles in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (24.1.0)\n", - "Requirement already satisfied: numpy==1.* in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymatching) (1.26.4)\n", - "Requirement already satisfied: networkx in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymatching) (2.6.3)\n", - "Requirement already satisfied: matplotlib in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymatching) (3.8.4)\n", - "Requirement already satisfied: autograd>=1.4 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (1.7.0)\n", - "Requirement already satisfied: cma==3.2.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (3.2.2)\n", - "Requirement already satisfied: alive-progress in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (3.2.0)\n", - "Requirement already satisfied: dill in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (0.3.9)\n", - "Requirement already satisfied: Deprecated in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (1.2.18)\n", - "Requirement already satisfied: click>=7 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qecsim) (8.1.8)\n", - "Requirement already satisfied: cvxpy>=1.3.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qldpc) (1.7.3)\n", - "Requirement already satisfied: diskcache>=5.0.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qldpc) (5.6.3)\n", - "Requirement already satisfied: galois>=0.4.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qldpc) (0.4.6)\n", - "Requirement already satisfied: ldpc>=2.1.8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qldpc) (2.2.8)\n", - "Requirement already satisfied: platformdirs>=4.0.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qldpc) (4.3.6)\n", - "Collecting scipy>=1.6.0 (from gpytorch)\n", - " Using cached scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (61 kB)\n", - "Requirement already satisfied: stim>=1.15.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qldpc) (1.15.0)\n", - "Requirement already satisfied: sympy>=1.12 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qldpc) (1.13.1)\n", - "Requirement already satisfied: osqp>=1.0.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from cvxpy>=1.3.2->qldpc) (1.0.4)\n", - "Requirement already satisfied: clarabel>=0.5.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from cvxpy>=1.3.2->qldpc) (0.11.1)\n", - "Requirement already satisfied: scs>=3.2.4.post1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from cvxpy>=1.3.2->qldpc) (3.2.9)\n", - "Requirement already satisfied: numba<0.62,>=0.55 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from galois>=0.4.2->qldpc) (0.61.2)\n", - "Requirement already satisfied: tqdm in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ldpc>=2.1.8->qldpc) (4.67.1)\n", - "Requirement already satisfied: pytest in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ldpc>=2.1.8->qldpc) (8.3.5)\n", - "Requirement already satisfied: sinter in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ldpc>=2.1.8->qldpc) (1.15.0)\n", - "Requirement already satisfied: contourpy>=1.0.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching) (1.2.1)\n", - "Requirement already satisfied: cycler>=0.10 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching) (0.12.1)\n", - "Requirement already satisfied: fonttools>=4.22.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching) (4.56.0)\n", - "Requirement already satisfied: kiwisolver>=1.3.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching) (1.4.8)\n", - "Requirement already satisfied: packaging>=20.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching) (24.2)\n", - "Requirement already satisfied: pillow>=8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching) (11.1.0)\n", - "Requirement already satisfied: pyparsing>=2.3.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching) (3.2.1)\n", - "Requirement already satisfied: opt-einsum>=2.3.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pyro-ppl>=1.8.4->botorch) (3.4.0)\n", - "Requirement already satisfied: pyro-api>=0.1.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pyro-ppl>=1.8.4->botorch) (0.1.2)\n", - "Requirement already satisfied: six>=1.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from python-dateutil->ax) (1.17.0)\n", - "Requirement already satisfied: filelock in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (3.17.0)\n", - "Requirement already satisfied: jinja2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (3.1.5)\n", - "Requirement already satisfied: fsspec in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (2025.2.0)\n", - "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.4.127 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (12.4.127)\n", - "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.4.127 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (12.4.127)\n", - "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.4.127 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (12.4.127)\n", - "Requirement already satisfied: nvidia-cudnn-cu12==9.1.0.70 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (9.1.0.70)\n", - "Requirement already satisfied: nvidia-cublas-cu12==12.4.5.8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (12.4.5.8)\n", - "Requirement already satisfied: nvidia-cufft-cu12==11.2.1.3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (11.2.1.3)\n", - "Requirement already satisfied: nvidia-curand-cu12==10.3.5.147 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (10.3.5.147)\n", - "Requirement already satisfied: nvidia-cusolver-cu12==11.6.1.9 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (11.6.1.9)\n", - "Requirement already satisfied: nvidia-cusparse-cu12==12.3.1.170 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (12.3.1.170)\n", - "Requirement already satisfied: nvidia-cusparselt-cu12==0.6.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (0.6.2)\n", - "Requirement already satisfied: nvidia-nccl-cu12==2.21.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (2.21.5)\n", - "Requirement already satisfied: nvidia-nvtx-cu12==12.4.127 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (12.4.127)\n", - "Requirement already satisfied: nvidia-nvjitlink-cu12==12.4.127 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (12.4.127)\n", - "Requirement already satisfied: triton==3.2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (3.2.0)\n", - "Requirement already satisfied: redis>=4.2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiocache[redis]->ax) (5.2.1)\n", - "Requirement already satisfied: aiohappyeyeballs>=2.3.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->ax) (2.4.6)\n", - "Requirement already satisfied: aiosignal>=1.1.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->ax) (1.3.2)\n", - "Requirement already satisfied: async-timeout<6.0,>=4.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->ax) (5.0.1)\n", - "Requirement already satisfied: attrs>=17.3.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->ax) (25.1.0)\n", - "Requirement already satisfied: frozenlist>=1.1.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->ax) (1.5.0)\n", - "Requirement already satisfied: multidict<7.0,>=4.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->ax) (6.1.0)\n", - "Requirement already satisfied: propcache>=0.2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->ax) (0.2.1)\n", - "Requirement already satisfied: yarl<2.0,>=1.17.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->ax) (1.18.3)\n", - "Requirement already satisfied: Mako in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from alembic->ax) (1.3.9)\n", - "Requirement already satisfied: greenlet!=0.4.17 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from SQLAlchemy->ax) (3.1.1)\n", - "Requirement already satisfied: about-time==4.2.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from alive-progress->pymoo) (4.2.1)\n", - "Requirement already satisfied: grapheme==0.6.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from alive-progress->pymoo) (0.6.0)\n", - "Requirement already satisfied: tzlocal>=3.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from APScheduler->ax) (5.3)\n", - "Requirement already satisfied: wrapt<2,>=1.10 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from Deprecated->pymoo) (1.17.2)\n", - "Requirement already satisfied: aniso8601<=7,>=3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from graphene->ax) (7.0.0)\n", - "Requirement already satisfied: promise<3,>=2.3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from graphql-core->ax) (2.3)\n", - "Requirement already satisfied: rx<2,>=1.6 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from graphql-core->ax) (1.6.3)\n", - "Requirement already satisfied: singledispatch<4,>=3.4.0.3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from graphene-sqlalchemy->ax) (3.7.0)\n", - "Requirement already satisfied: wadler-lindig>=0.1.3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from jaxtyping->gpytorch) (0.1.3)\n", - "Requirement already satisfied: et-xmlfile in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from openpyxl->ax) (2.0.0)\n", - "Requirement already satisfied: cssselect>=1.2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pyquery->ax) (1.2.0)\n", - "Requirement already satisfied: typing-inspect in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pyre_extensions->botorch) (0.9.0)\n", - "Requirement already satisfied: pyasn1>=0.1.3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from rsa->ax) (0.6.1)\n", - "Requirement already satisfied: ruamel.yaml.clib>=0.2.7 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ruamel.yaml->ax) (0.2.12)\n", - "Requirement already satisfied: sanic-routing>=23.12.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from sanic->ax) (23.12.0)\n", - "Requirement already satisfied: httptools>=0.0.10 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from sanic->ax) (0.6.4)\n", - "Requirement already satisfied: uvloop>=0.15.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from sanic->ax) (0.21.0)\n", - "Requirement already satisfied: websockets>=10.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from sanic->ax) (15.0)\n", - "Requirement already satisfied: html5tagger>=1.2.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from sanic->ax) (1.3.0)\n", - "Requirement already satisfied: tracerite>=1.0.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from sanic->ax) (1.1.1)\n", - "Requirement already satisfied: setuptools>=70.1.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from sanic->ax) (75.8.0)\n", - "Requirement already satisfied: graphql-server-core>=1.0.dev in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from Sanic-GraphQl->ax) (2.0.0)\n", - "Requirement already satisfied: pytest-runner in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from Sanic-GraphQl->ax) (6.0.1)\n", - "Requirement already satisfied: pyjwt<3.0.0,>=2.1.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from sanic-jwt->ax) (2.10.1)\n", - "Requirement already satisfied: joblib>=1.2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from scikit-learn->gpytorch) (1.4.2)\n", - "Requirement already satisfied: requests>=2.20 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from stripe->ax) (2.32.3)\n", - "Requirement already satisfied: cffi in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from clarabel>=0.5.0->cvxpy>=1.3.2->qldpc) (1.15.1)\n", - "Requirement already satisfied: llvmlite<0.45,>=0.44.0dev0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from numba<0.62,>=0.55->galois>=0.4.2->qldpc) (0.44.0)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests>=2.20->stripe->ax) (3.4.1)\n", - "Requirement already satisfied: idna<4,>=2.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests>=2.20->stripe->ax) (3.10)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests>=2.20->stripe->ax) (2.3.0)\n", - "Requirement already satisfied: certifi>=2017.4.17 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests>=2.20->stripe->ax) (2025.1.31)\n", - "Requirement already satisfied: MarkupSafe>=2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from jinja2->torch>=2.0.1->botorch) (3.0.2)\n", - "Requirement already satisfied: exceptiongroup>=1.0.0rc8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pytest->ldpc>=2.1.8->qldpc) (1.2.2)\n", - "Requirement already satisfied: iniconfig in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pytest->ldpc>=2.1.8->qldpc) (2.0.0)\n", - "Requirement already satisfied: pluggy<2,>=1.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pytest->ldpc>=2.1.8->qldpc) (1.5.0)\n", - "Requirement already satisfied: tomli>=1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pytest->ldpc>=2.1.8->qldpc) (2.2.1)\n", - "Requirement already satisfied: mypy-extensions>=0.3.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from typing-inspect->pyre_extensions->botorch) (1.0.0)\n", - "Requirement already satisfied: pycparser in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from cffi->clarabel>=0.5.0->cvxpy>=1.3.2->qldpc) (2.23)\n", - "Using cached scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (37.7 MB)\n", - "Installing collected packages: scipy\n", - " Attempting uninstall: scipy\n", - " Found existing installation: scipy 1.13.1\n", - " Uninstalling scipy-1.13.1:\n", - " Successfully uninstalled scipy-1.13.1\n", - "\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", - "gensim 4.3.3 requires scipy<1.14.0,>=1.7.0, but you have scipy 1.15.3 which is incompatible.\n", - "karateclub 1.3.3 requires numpy<1.23.0, but you have numpy 1.26.4 which is incompatible.\n", - "quits 0.0.1 requires networkx>=2.8.8, but you have networkx 2.6.3 which is incompatible.\u001b[0m\u001b[31m\n", - "\u001b[0mSuccessfully installed scipy-1.15.3\n", - "Requirement already satisfied: toponetx in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (0.2.0)\n", - "Requirement already satisfied: topomodelx in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (0.0.1)\n", - "Requirement already satisfied: topoembedx in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (0.0.2.dev131)\n", - "Requirement already satisfied: networkx in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from toponetx) (2.6.3)\n", - "Requirement already satisfied: numpy in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from toponetx) (1.26.4)\n", - "Requirement already satisfied: pandas in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from toponetx) (1.3.5)\n", - "Requirement already satisfied: pyarrow in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from toponetx) (19.0.0)\n", - "Requirement already satisfied: requests in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from toponetx) (2.32.3)\n", - "Requirement already satisfied: scipy in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from toponetx) (1.15.3)\n", - "Requirement already satisfied: trimesh in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from toponetx) (4.6.2)\n", - "Requirement already satisfied: typing-extensions in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from toponetx) (4.12.2)\n", - "Requirement already satisfied: tqdm in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from topomodelx) (4.67.1)\n", - "Requirement already satisfied: scikit-learn in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from topomodelx) (1.6.1)\n", - "Requirement already satisfied: matplotlib in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from topomodelx) (3.8.4)\n", - "Requirement already satisfied: gudhi in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from topomodelx) (3.10.1)\n", - "Requirement already satisfied: pyg-nightly in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from topomodelx) (2.7.0.dev20250213)\n", - "Requirement already satisfied: karateclub in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from topoembedx) (1.3.3)\n", - "Requirement already satisfied: pyrandwalk in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from topoembedx) (1.1)\n", - "Requirement already satisfied: pygsp in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from topoembedx) (0.5.1)\n", - "Collecting numpy (from toponetx)\n", - " Using cached numpy-1.22.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (2.0 kB)\n", - "Requirement already satisfied: decorator==4.4.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from karateclub->topoembedx) (4.4.2)\n", - "Requirement already satisfied: python-louvain in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from karateclub->topoembedx) (0.16)\n", - "Requirement already satisfied: gensim>=4.0.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from karateclub->topoembedx) (4.3.3)\n", - "Requirement already satisfied: six in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from karateclub->topoembedx) (1.17.0)\n", - "Requirement already satisfied: python-Levenshtein in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from karateclub->topoembedx) (0.26.1)\n", - "Requirement already satisfied: python-dateutil>=2.7.3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pandas->toponetx) (2.9.0.post0)\n", - "Requirement already satisfied: pytz>=2017.3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pandas->toponetx) (2025.1)\n", - "Requirement already satisfied: contourpy>=1.0.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->topomodelx) (1.2.1)\n", - "Requirement already satisfied: cycler>=0.10 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->topomodelx) (0.12.1)\n", - "Requirement already satisfied: fonttools>=4.22.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->topomodelx) (4.56.0)\n", - "Requirement already satisfied: kiwisolver>=1.3.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->topomodelx) (1.4.8)\n", - "Requirement already satisfied: packaging>=20.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->topomodelx) (24.2)\n", - "Requirement already satisfied: pillow>=8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->topomodelx) (11.1.0)\n", - "Requirement already satisfied: pyparsing>=2.3.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->topomodelx) (3.2.1)\n", - "Requirement already satisfied: aiohttp in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pyg-nightly->topomodelx) (3.11.12)\n", - "Requirement already satisfied: fsspec in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pyg-nightly->topomodelx) (2025.2.0)\n", - "Requirement already satisfied: jinja2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pyg-nightly->topomodelx) (3.1.5)\n", - "Requirement already satisfied: psutil>=5.8.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pyg-nightly->topomodelx) (6.1.1)\n", - "Requirement already satisfied: art>=1.8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pyrandwalk->topoembedx) (6.4)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests->toponetx) (3.4.1)\n", - "Requirement already satisfied: idna<4,>=2.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests->toponetx) (3.10)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests->toponetx) (2.3.0)\n", - "Requirement already satisfied: certifi>=2017.4.17 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests->toponetx) (2025.1.31)\n", - "Requirement already satisfied: joblib>=1.2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from scikit-learn->topomodelx) (1.4.2)\n", - "Requirement already satisfied: threadpoolctl>=3.1.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from scikit-learn->topomodelx) (3.5.0)\n", - "INFO: pip is looking at multiple versions of scipy to determine which version is compatible with other requirements. This could take a while.\n", - "Collecting scipy (from toponetx)\n", - " Using cached scipy-1.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (61 kB)\n", - " Using cached scipy-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (61 kB)\n", - " Using cached scipy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (61 kB)\n", - " Using cached scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (60 kB)\n", - " Using cached scipy-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (60 kB)\n", - " Using cached scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (60 kB)\n", - "Requirement already satisfied: smart-open>=1.8.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from gensim>=4.0.0->karateclub->topoembedx) (7.1.0)\n", - "Requirement already satisfied: aiohappyeyeballs>=2.3.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->pyg-nightly->topomodelx) (2.4.6)\n", - "Requirement already satisfied: aiosignal>=1.1.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->pyg-nightly->topomodelx) (1.3.2)\n", - "Requirement already satisfied: async-timeout<6.0,>=4.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->pyg-nightly->topomodelx) (5.0.1)\n", - "Requirement already satisfied: attrs>=17.3.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->pyg-nightly->topomodelx) (25.1.0)\n", - "Requirement already satisfied: frozenlist>=1.1.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->pyg-nightly->topomodelx) (1.5.0)\n", - "Requirement already satisfied: multidict<7.0,>=4.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->pyg-nightly->topomodelx) (6.1.0)\n", - "Requirement already satisfied: propcache>=0.2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->pyg-nightly->topomodelx) (0.2.1)\n", - "Requirement already satisfied: yarl<2.0,>=1.17.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->pyg-nightly->topomodelx) (1.18.3)\n", - "Requirement already satisfied: MarkupSafe>=2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from jinja2->pyg-nightly->topomodelx) (3.0.2)\n", - "Requirement already satisfied: Levenshtein==0.26.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from python-Levenshtein->karateclub->topoembedx) (0.26.1)\n", - "Requirement already satisfied: rapidfuzz<4.0.0,>=3.9.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from Levenshtein==0.26.1->python-Levenshtein->karateclub->topoembedx) (3.12.1)\n", - "Requirement already satisfied: wrapt in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from smart-open>=1.8.1->gensim>=4.0.0->karateclub->topoembedx) (1.17.2)\n", - "Using cached numpy-1.22.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (16.8 MB)\n", - "Using cached scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (38.6 MB)\n", - "Installing collected packages: numpy, scipy\n", - " Attempting uninstall: numpy\n", - " Found existing installation: numpy 1.26.4\n", - " Uninstalling numpy-1.26.4:\n", - " Successfully uninstalled numpy-1.26.4\n", - " Attempting uninstall: scipy\n", - " Found existing installation: scipy 1.15.3\n", - " Uninstalling scipy-1.15.3:\n", - " Successfully uninstalled scipy-1.15.3\n", - "\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", - "ldpc 2.2.8 requires numpy>=1.24.0, but you have numpy 1.22.4 which is incompatible.\n", - "numba 0.61.2 requires numpy<2.3,>=1.24, but you have numpy 1.22.4 which is incompatible.\n", - "qldpc 0.2.5 requires numpy>=1.24.0, but you have numpy 1.22.4 which is incompatible.\n", - "qldpc 0.2.5 requires scipy>=1.14.1, but you have scipy 1.13.1 which is incompatible.\n", - "quits 0.0.1 requires networkx>=2.8.8, but you have networkx 2.6.3 which is incompatible.\n", - "quits 0.0.1 requires numpy>=1.24.0, but you have numpy 1.22.4 which is incompatible.\u001b[0m\u001b[31m\n", - "\u001b[0mSuccessfully installed numpy-1.22.4 scipy-1.13.1\n", - "Requirement already satisfied: qiskit in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (2.1.1)\n", - "Requirement already satisfied: stim in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (1.15.0)\n", - "Requirement already satisfied: sinter in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (1.15.0)\n", - "Requirement already satisfied: rustworkx>=0.15.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qiskit) (0.16.0)\n", - "Requirement already satisfied: numpy<3,>=1.17 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qiskit) (1.22.4)\n", - "Requirement already satisfied: scipy>=1.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qiskit) (1.13.1)\n", - "Requirement already satisfied: dill>=0.3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qiskit) (0.3.9)\n", - "Requirement already satisfied: stevedore>=3.0.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qiskit) (5.4.1)\n", - "Requirement already satisfied: typing-extensions in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qiskit) (4.12.2)\n", - "Requirement already satisfied: matplotlib in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from sinter) (3.8.4)\n", - "Requirement already satisfied: pbr>=2.0.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from stevedore>=3.0.0->qiskit) (6.1.1)\n", - "Requirement already satisfied: contourpy>=1.0.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->sinter) (1.2.1)\n", - "Requirement already satisfied: cycler>=0.10 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->sinter) (0.12.1)\n", - "Requirement already satisfied: fonttools>=4.22.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->sinter) (4.56.0)\n", - "Requirement already satisfied: kiwisolver>=1.3.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->sinter) (1.4.8)\n", - "Requirement already satisfied: packaging>=20.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->sinter) (24.2)\n", - "Requirement already satisfied: pillow>=8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->sinter) (11.1.0)\n", - "Requirement already satisfied: pyparsing>=2.3.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->sinter) (3.2.1)\n", - "Requirement already satisfied: python-dateutil>=2.7 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->sinter) (2.9.0.post0)\n", - "Requirement already satisfied: setuptools in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pbr>=2.0.0->stevedore>=3.0.0->qiskit) (75.8.0)\n", - "Requirement already satisfied: six>=1.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from python-dateutil>=2.7->matplotlib->sinter) (1.17.0)\n", - "Requirement already satisfied: ldpc in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (2.2.8)\n", - "Requirement already satisfied: bposd in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (2.1)\n", - "Collecting numpy>=1.24.0 (from ldpc)\n", - " Using cached numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (62 kB)\n", - "Requirement already satisfied: scipy>=1.9.3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ldpc) (1.13.1)\n", - "Requirement already satisfied: tqdm in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ldpc) (4.67.1)\n", - "Requirement already satisfied: pytest in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ldpc) (8.3.5)\n", - "Requirement already satisfied: stim in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ldpc) (1.15.0)\n", - "Requirement already satisfied: sinter in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ldpc) (1.15.0)\n", - "Requirement already satisfied: pymatching in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ldpc) (2.2.1)\n", - " Using cached numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (61 kB)\n", - "Requirement already satisfied: networkx in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymatching->ldpc) (2.6.3)\n", - "Requirement already satisfied: matplotlib in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymatching->ldpc) (3.8.4)\n", - "Requirement already satisfied: exceptiongroup>=1.0.0rc8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pytest->ldpc) (1.2.2)\n", - "Requirement already satisfied: iniconfig in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pytest->ldpc) (2.0.0)\n", - "Requirement already satisfied: packaging in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pytest->ldpc) (24.2)\n", - "Requirement already satisfied: pluggy<2,>=1.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pytest->ldpc) (1.5.0)\n", - "Requirement already satisfied: tomli>=1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pytest->ldpc) (2.2.1)\n", - "Requirement already satisfied: contourpy>=1.0.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching->ldpc) (1.2.1)\n", - "Requirement already satisfied: cycler>=0.10 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching->ldpc) (0.12.1)\n", - "Requirement already satisfied: fonttools>=4.22.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching->ldpc) (4.56.0)\n", - "Requirement already satisfied: kiwisolver>=1.3.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching->ldpc) (1.4.8)\n", - "Requirement already satisfied: pillow>=8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching->ldpc) (11.1.0)\n", - "Requirement already satisfied: pyparsing>=2.3.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching->ldpc) (3.2.1)\n", - "Requirement already satisfied: python-dateutil>=2.7 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching->ldpc) (2.9.0.post0)\n", - "Requirement already satisfied: six>=1.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from python-dateutil>=2.7->matplotlib->pymatching->ldpc) (1.17.0)\n", - "Using cached numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (18.2 MB)\n", - "Installing collected packages: numpy\n", - " Attempting uninstall: numpy\n", - " Found existing installation: numpy 1.22.4\n", - " Uninstalling numpy-1.22.4:\n", - " Successfully uninstalled numpy-1.22.4\n", - "\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", - "karateclub 1.3.3 requires numpy<1.23.0, but you have numpy 1.26.4 which is incompatible.\n", - "qldpc 0.2.5 requires scipy>=1.14.1, but you have scipy 1.13.1 which is incompatible.\n", - "quits 0.0.1 requires networkx>=2.8.8, but you have networkx 2.6.3 which is incompatible.\u001b[0m\u001b[31m\n", - "\u001b[0mSuccessfully installed numpy-1.26.4\n", - "Requirement already satisfied: pymoo in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (0.6.1.3)\n", - "Requirement already satisfied: numpy>=1.15 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (1.26.4)\n", - "Requirement already satisfied: scipy>=1.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (1.13.1)\n", - "Requirement already satisfied: matplotlib>=3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (3.8.4)\n", - "Requirement already satisfied: autograd>=1.4 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (1.7.0)\n", - "Requirement already satisfied: cma==3.2.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (3.2.2)\n", - "Requirement already satisfied: alive-progress in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (3.2.0)\n", - "Requirement already satisfied: dill in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (0.3.9)\n", - "Requirement already satisfied: Deprecated in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (1.2.18)\n", - "Requirement already satisfied: contourpy>=1.0.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib>=3->pymoo) (1.2.1)\n", - "Requirement already satisfied: cycler>=0.10 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib>=3->pymoo) (0.12.1)\n", - "Requirement already satisfied: fonttools>=4.22.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib>=3->pymoo) (4.56.0)\n", - "Requirement already satisfied: kiwisolver>=1.3.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib>=3->pymoo) (1.4.8)\n", - "Requirement already satisfied: packaging>=20.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib>=3->pymoo) (24.2)\n", - "Requirement already satisfied: pillow>=8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib>=3->pymoo) (11.1.0)\n", - "Requirement already satisfied: pyparsing>=2.3.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib>=3->pymoo) (3.2.1)\n", - "Requirement already satisfied: python-dateutil>=2.7 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib>=3->pymoo) (2.9.0.post0)\n", - "Requirement already satisfied: about-time==4.2.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from alive-progress->pymoo) (4.2.1)\n", - "Requirement already satisfied: grapheme==0.6.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from alive-progress->pymoo) (0.6.0)\n", - "Requirement already satisfied: wrapt<2,>=1.10 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from Deprecated->pymoo) (1.17.2)\n", - "Requirement already satisfied: six>=1.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from python-dateutil>=2.7->matplotlib>=3->pymoo) (1.17.0)\n" - ] - } - ], + "outputs": [], "source": [ - "\n", - "!pip install torch==2.6.0 \n", - "!pip install torch-geometric\n", - "!pip install gpytorch botorch ax pymatching pymoo qecsim qldpc\n", - "!pip install toponetx topomodelx topoembedx\n", - "!pip install stim sinter\n", - "!pip install ldpc bposd\n", - "!pip install pymoo\n", - "\n", - "\n", - "\n" + "!pip install -r requirements.txt\n", + "!pip install torch --extra-index-url https://download.pytorch.org/whl/cu126\n", + "!pip install topomodelx==0.0.1 --no-deps\n", + "!pip install ortools==9.14.6206" ] }, { "cell_type": "markdown", - "id": "d2663042", + "id": "6", "metadata": {}, "source": [ "If you have installed all the packages, please try to run this in the new environment:" @@ -499,51 +78,22 @@ }, { "cell_type": "code", - "execution_count": 3, - "id": "fca2c653", + "execution_count": null, + "id": "7", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", - " from .autonotebook import tqdm as notebook_tqdm\n" - ] - } - ], + "outputs": [], "source": [ - "import random\n", "import numpy as np\n", - "import torch\n", "from code_construction.code_construction import CodeConstructor\n", "from bayesian_optimization.objective_function import ObjectiveFunction\n", "from bayesian_optimization.encoder import *\n", "from bayesian_optimization.chaincomplexembedding import *\n", - "from bayesian_optimization.gp import *\n", - "import gpytorch\n", - "from gpytorch.mlls import ExactMarginalLogLikelihood\n", - "from gpytorch.distributions import MultivariateNormal\n", - "from gpytorch.kernels import RBFKernel, MaternKernel, SpectralMixtureKernel, ScaleKernel\n", - "import copy\n", - "from typing import Dict, Optional, Tuple, Any, List\n", - "\n", - "import torch\n", - "import gpytorch\n", - "from gpytorch.mlls import ExactMarginalLogLikelihood\n", - "from torch.nn.utils import clip_grad_norm_\n", - "import math\n", - "from dataclasses import dataclass, asdict\n", - "from typing import Tuple, Union, Dict, Any\n", - "\n", - "from torch.distributions.normal import Normal\n", - "from bayesian_optimization.bo import BO_on_QEC\n", - "import pymoo" + "from bayesian_optimization.gp import *" ] }, { "cell_type": "markdown", - "id": "ef3afdda", + "id": "8", "metadata": {}, "source": [ "## The instructions of how to run the file" @@ -551,7 +101,7 @@ }, { "cell_type": "markdown", - "id": "603f22d4", + "id": "9", "metadata": {}, "source": [ "Type following command in the terminal:\n", @@ -563,7 +113,7 @@ }, { "cell_type": "markdown", - "id": "d7abff86", + "id": "10", "metadata": {}, "source": [ "## Read & plot the results" @@ -571,7 +121,7 @@ }, { "cell_type": "markdown", - "id": "8a36536d", + "id": "11", "metadata": {}, "source": [ "#### Reading the results of BO/EA/RS:\n" @@ -580,35 +130,37 @@ { "cell_type": "code", "execution_count": null, - "id": "47c00e9f", + "id": "12", "metadata": {}, "outputs": [], "source": [ "import pickle\n", - "from code_construction.code_construction import CodeConstructor\n", - "l=12\n", - "g=6\n", + "\n", + "l = 12\n", + "g = 6\n", "dataset_index = 0\n", - "seed = 1\n", - "filename = f'./data/BO_results/BO_{l}_{g}_{dataset_index}_{seed}.pkl' # if you want to read the EA/RS results, place replace the prefix 'BO' to 'EA' or 'RS'\n", + "seed = 42\n", + "lamda_ = 1\n", + "filename = f\"./data/BO_results/BO_{l}_{g}_{dataset_index}_{seed}_{lamda_}.pkl\" # if you want to read the EA/RS results, place replace the prefix 'BO' to 'EA' or 'RS'\n", "\n", - "best_known_y = -0.00693 # The objective function of [[144,26]] code. \n", - "with open(filename,'rs') as f:\n", + "best_known_y = -0.00693 # The objective function of [[144,26]] code.\n", + "with open(filename, \"rb\") as f:\n", " data = pickle.load(f)\n", - " flat = data['evaluation_history'] # this is used to plot the best-so-far curve\n", - " best_y = data['best_y'] # if this is better than the best know found code, we could replace the results in figure 7 and 9. \n", - " best_x = data['best_x']\n", + " flat = data[\"evaluation_history\"] # this is used to plot the best-so-far curve\n", + " best_y = data[\n", + " \"best_y\"\n", + " ] # if this is better than the best know found code, we could replace the results in figure 7 and 9.\n", + " best_x = data[\"best_x\"]\n", " if best_y >= best_known_y:\n", - " codeconstructor = CodeConstructor(method='bb',para_dict={'l':l,'g':g})\n", + " codeconstructor = CodeConstructor(method=\"bb\", para_dict={\"l\": l, \"g\": g})\n", " c = codeconstructor.construct(best_x)\n", " k = c.k\n", - " print(f'This [[144,{k}]] code is better')\n", - " " + " print(f\"This [[144,{k}]] code is better\")" ] }, { "cell_type": "markdown", - "id": "f6ae4cf9", + "id": "13", "metadata": {}, "source": [ "#### The data of figure 7; figure 8; figure 9" @@ -616,7 +168,7 @@ }, { "cell_type": "markdown", - "id": "d1245e2a", + "id": "14", "metadata": {}, "source": [ "Figure 7\n", @@ -631,20 +183,73 @@ { "cell_type": "code", "execution_count": null, - "id": "77d96691", + "id": "15", "metadata": {}, "outputs": [], "source": [ - "pp_list = [0.05, 0.040036870145840404, 0.032059019421497734, 0.0256708559516296, 0.020555614525359374, 0.01645964939039528, 0.013179856905786339, 0.010553604389554513, 0.008450665770303305, 0.0067667641618306355]\n", + "pp_list = [\n", + " 0.05,\n", + " 0.040036870145840404,\n", + " 0.032059019421497734,\n", + " 0.0256708559516296,\n", + " 0.020555614525359374,\n", + " 0.01645964939039528,\n", + " 0.013179856905786339,\n", + " 0.010553604389554513,\n", + " 0.008450665770303305,\n", + " 0.0067667641618306355,\n", + "]\n", "# [144,26]\n", - "code_144_26= [0.011445891946481712, 0.005810775130175694, 0.0028562211382342495, 0.0015016991653288292, 0.0007955535034166461, 0.00042144348451766955, 0.00023259809361952932, 0.00016032087308970322, 0.00012056614268329824, 4.5025331880887975e-05]\n", - "gross = [0.004941267916952596, 0.002594195977199232, 0.001366896217540714, 0.0007455495580136473, 0.0003992086896752456, 0.00020857243058891584, 9.588388262971037e-05, 5.585048616230104e-05, 2.5837004546613862e-05, 2.083572086741814e-05]\n", - "code_72_12_6=[0.010151361651358104, 0.0050821776826791565, 0.0025933384934497816, 0.0013330638587575327, 0.0006506567387046802, 0.00036238808305733006, 0.00018184843515745008, 0.00011340403922088793, 5.5016644518901536e-05, 1.8335182204287648e-05]" + "code_144_26 = [\n", + " 0.011445891946481712,\n", + " 0.005810775130175694,\n", + " 0.0028562211382342495,\n", + " 0.0015016991653288292,\n", + " 0.0007955535034166461,\n", + " 0.00042144348451766955,\n", + " 0.00023259809361952932,\n", + " 0.00016032087308970322,\n", + " 0.00012056614268329824,\n", + " 4.5025331880887975e-05,\n", + "]\n", + "gross = [\n", + " 0.004941267916952596,\n", + " 0.002594195977199232,\n", + " 0.001366896217540714,\n", + " 0.0007455495580136473,\n", + " 0.0003992086896752456,\n", + " 0.00020857243058891584,\n", + " 9.588388262971037e-05,\n", + " 5.585048616230104e-05,\n", + " 2.5837004546613862e-05,\n", + " 2.083572086741814e-05,\n", + "]\n", + "code_72_12_6 = [\n", + " 0.010151361651358104,\n", + " 0.0050821776826791565,\n", + " 0.0025933384934497816,\n", + " 0.0013330638587575327,\n", + " 0.0006506567387046802,\n", + " 0.00036238808305733006,\n", + " 0.00018184843515745008,\n", + " 0.00011340403922088793,\n", + " 5.5016644518901536e-05,\n", + " 1.8335182204287648e-05,\n", + "]\n", + "\n", + "import matplotlib.pyplot as plt\n", + "\n", + "plt.plot(pp_list, gross, label=\"gross\")\n", + "plt.plot(pp_list, code_144_26, label=\"144,8\")\n", + "plt.yscale(\"log\")\n", + "plt.xscale(\"log\")\n", + "plt.legend()\n", + "plt.show()" ] }, { "cell_type": "markdown", - "id": "1354068b", + "id": "16", "metadata": {}, "source": [ "Figure 8\n", @@ -659,37 +264,59 @@ { "cell_type": "code", "execution_count": null, - "id": "3d4c37a4", + "id": "17", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAi8AAAGhCAYAAACphlRxAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAABZFklEQVR4nO3dd3wUdf7H8demk5BCCAkEEhJ6T5AuRZpyFhSRoiJNBQt6KpaT33nY7s67UxBL7AUBURABu6ChFymh9xY6SWgppGd3fn+M5A5pCSSZ3c37+Xjk8ch3dnbns0OSfTPzLTbDMAxEREREXISH1QWIiIiIlIbCi4iIiLgUhRcRERFxKQovIiIi4lIUXkRERMSlKLyIiIiIS1F4EREREZfiZXUBZc3hcHD06FECAwOx2WxWlyMiIiIlYBgGWVlZREZG4uFx6Wsrbhdejh49SlRUlNVliIiIyBU4dOgQderUueQ+bhNeEhISSEhIoKioCDDffFBQkMVViYiISElkZmYSFRVFYGDgZfe1udvyAJmZmQQHB5ORkaHwIiIi4iJK8/mtDrsiIiLiUhReRERExKUovIiIiIhLcZsOu6Vlt9spLCy0ugy34+3tjaenp9VliIiIG6t04cUwDFJSUkhPT7e6FLcVEhJCzZo1Nc+OiIiUi0oXXs4Gl/DwcPz9/fUBW4YMwyAnJ4e0tDQAatWqZXFFIiLijipVeLHb7cXBpXr16laX45aqVKkCQFpaGuHh4bqFJCIiZa5Sddg928fF39/f4krc29nzqz5FIiJSHipVeDlLt4rKl86viIiUp0oZXkRERMR1KbyIiIiIS1F4EREREZei8OKmCgoKrC5BRETc0c6fITfd0hIUXlxEVlYWQ4YMISAggFq1avH666/TvXt3Hn/8cQBiYmJ4+eWXGTZsGEFBQYwePRqAr7/+mubNm+Pr60tMTAwTJkw453XfeecdGjZsiJ+fHxEREQwYMKD4sVmzZtGyZUuqVKlC9erV6d27N9nZ2RX2nkVExInYi+CX8fDFYJjzIDgclpXiNvO8JCQkkJCQgN1uL9XzDMMgt7B0zykLVbw9SzUqZ+zYsSxfvpxvv/2WiIgIxo8fz7p164iPjy/e57XXXmP8+PE8//zzACQlJTFo0CBeeOEFBg8ezIoVK3j44YepXr06I0aMYO3atfz5z39m6tSpXHvttZw6dYqlS5cCcOzYMe666y7+85//cPvtt5OVlcXSpUsxDKNMz4OIiLiAM8dh1kjYb35GUL0+GA6sugZiM9zs0ygzM5Pg4GAyMjIICgo657G8vDySk5OJjY3Fz88PgJyCIpqNn1fhdW57qQ/+PiXLjllZWVSvXp3p06cXXxnJyMggMjKSUaNGMWnSJGJiYmjdujVz5swpft6QIUM4fvw48+fPL972zDPP8MMPP7B161Zmz57NyJEjOXz4MIGBgeccc926dbRp04b9+/dTt27dUr23C51nERFxUYfWwMxhkHUUvAPgtrehRf8yP8ylPr//SLeNXMC+ffsoLCykffv2xduCg4Np3LjxOfu1bdv2nPb27dvp3LnzOds6d+7M7t27sdvtXH/99dStW5d69eoxdOhQPv/8c3JycgCIi4ujV69etGzZkoEDB/Lhhx9y+vTpcnqHIiLidAwDVn8In95oBpewRjB6YbkEl9Jym9tGV6qKtyfbXupjyXHLWkBAQKn2DwwMZN26dSxatIj58+czfvx4XnjhBdasWUNISAi//PILK1asYP78+bz11lv89a9/ZdWqVcTGxpZ57SIi4kQKcuD7J2DTl2a72W1wWwL4Bl76eRWk0l95sdls+Pt4VfhXafq71KtXD29vb9asWVO8LSMjg127dl3yeU2bNmX58uXnbFu+fDmNGjUqXnPIy8uL3r1785///IdNmzaxf/9+FixYUHxuOnfuzIsvvsj69evx8fE557aUiIi4oZN74ePrzeBi84Qb/g4DP3Oa4AK68uISAgMDGT58OE8//TShoaGEh4fz/PPP4+HhcckQ9OSTT9KuXTtefvllBg8ezMqVK3n77bd55513APj+++/Zt28f3bp1o1q1avz44484HA4aN27MqlWrSExM5IYbbiA8PJxVq1Zx/PhxmjZtWlFvW0REKtrOn2D2A5CfAQE1YMCnENvV6qrOo/DiIiZOnMiDDz7ILbfcQlBQEM888wyHDh26ZIfYa665hpkzZzJ+/HhefvllatWqxUsvvcSIESMACAkJYfbs2bzwwgvk5eXRsGFDvvjiC5o3b8727dtZsmQJkyZNIjMzk7p16zJhwgRuvPHGCnrHIiJSYRx2WPhPWPqa2a7THgZ9BkGR1tZ1EZV+tJGrys7Opnbt2kyYMIH77rvP6nLO4U7nWUTE7WWfhK/vg30LzXb7B8xbRV4+FVpGaUYb6cqLi1i/fj07duygffv2ZGRk8NJLLwFw2223WVyZiIi4rCPrzGHQGYfA2x/6vgmtBlpd1WUpvLiQ1157jZ07d+Lj40ObNm1YunQpYWFhVpclIiKuKOkz+PEpsBdAaD0YPA0iml/2aflFdgwD/Mph1GxJKby4iNatW5OUlGR1GSIi4uoKc83Qsn6a2W58M9z+LvgFX/JpRXYHc9YfYdKvu7mzXRSP9mpYAcVemMKLiIhIZXH6AMwcCsc2gs0Dej4HnZ8Aj4vPnOJwGPy0JYUJv+xk33FzfbtvNh5lTI8GeHiUfNqPsqTwIiIiUhns/hVm3w+5p8G/OtzxMdTvcdHdDcNg0c7jvDpvJ9uOZQIQ4u/Nw93rM7RjjGXBBRReRERE3JvDAUtehUWvAAZEXgODpkBI1EWf8tu+k7w6bydJB8xlYar6enFfl1ju7xpLoJ93BRV+cQovIiIi7ir3NMweDbt/X6C37b3wp3+Bl+8Fd994KJ3X5u9k6e4TAPh6eTDi2hgeuK4+oQEVO3T6UhReRERE3NGxjTBjKKQfAC8/uHkitB5ywV13pWYxYf5O5m1NBcDLw8ad7aN4tGdDIoKcb74uhRcRERF3s2G6ubBiUR6E1DWHQddqdd5uB05mM+nX3czdcATDAA8b9Gtdm8d7NSK6ur8FhZeMwouIiIi7KMqHn5+FtZ+Y7YY3QP8PoEq1c3ZLycjjzQW7mbnmEEUOc6L9G1vUZOz1jWgY4TwLMF5MpV9V2lUsWbKEvn37EhkZic1mY+7cuRfd98EHH8RmszFp0qQLPp6fn098fDw2m40NGzZc8rgpKSkMHTqUmjVrEhAQwDXXXMPXX3995W9ERETKR8Zh+PTG34OLDbr/H9w145zgcvJMPn//fhvdXl3I9FUHKXIYXNeoBt890oV372njEsEFdOXFZWRnZxMXF8e9995L//79L7rfnDlz+O2334iMvPhiWs888wyRkZFs3LjxsscdNmwY6enpfPvtt4SFhTF9+nQGDRrE2rVrad269RW9FxERKWP7FsGseyHnJPiFmMOgG/Yufjgzr5CPluzj42XJZBfYAWgXU42n+zShfWyoNTVfBacLL4cOHWLo0KGkpaXh5eXF3/72NwYOdP51FsrbjTfeeNkVnY8cOcKjjz7KvHnzuPnmmy+4z08//cT8+fP5+uuv+emnny573BUrVvDuu+/Svn17AJ577jlef/11kpKSFF5ERKzmcMDy12HB38FwQK04cxh0tRgAcgvsfLZyP+8u2ktGbiEALWoH8dQNjbmuUQ1sNuvmarkaThdevLy8mDRpEvHx8aSkpNCmTRtuuukmAgICyueAhgGFOeXz2pfi7Q9l+EPjcDgYOnQoTz/9NM2bX3htitTUVEaNGsXcuXPx9y9ZR6xrr72WGTNmcPPNNxMSEsLMmTPJy8uje/fuZVa7iIhcgbwMmPMQ7PzBbLe+B26aAN5+FBQ5+HLNQd5asIfjWfkANAivypPXN+JPLWq6bGg5y+nCS61atahVqxYANWvWJCwsjFOnTpVfeCnMgX9e/BZLufm/o+BTdu/p3//+N15eXvz5z3++4OOGYTBixAgefPBB2rZty/79+0v0ujNnzmTw4MFUr14dLy8v/P39mTNnDg0aNCiz2kVEpJRSt8KMe+DUPvD0gZtegzbDzfWH1h5i0q+7OZKeC0CdalV4oncj+rWujaeFs+KWpVJ32C1Jx9GEhARiYmLw8/OjQ4cOrF69+oqKS0pKwm63ExV18VkAxTxPb7zxBpMnT75omn7rrbfIyspi3LhxpXrtv/3tb6Snp/Prr7+ydu1axo4dy6BBg9i8eXNZlC4iIqW16Sv4qLcZXIKj4N6fcbQexg+bjtFn0hKenrWJI+m5hAf68nK/Fix4sjt3tKnjNsEFruDKy+U6js6YMYOxY8fy3nvv0aFDByZNmkSfPn3YuXMn4eHhAMTHx1NUVHTec+fPn1/c0fTUqVMMGzaMDz/8sLQllo63v3kVpKJ5l934+aVLl5KWlkZ0dHTxNrvdzpNPPsmkSZPYv38/CxYsYOXKlfj6njurYtu2bRkyZAifffbZea+7d+9e3n77bbZs2VJ8KyouLo6lS5eSkJDAe++9V2bvQURELqOoAOY/B6vfN9v1emDc8RGLDjl47etlbD16/vpDVXw8LSy4/JQ6vFyu4+jEiRMZNWoUI0eOBOC9997jhx9+4JNPPuHZZ58FuOzw3Pz8fPr168ezzz7Ltddee9l98/Pzi9uZmZklfCe/s9nK9PaNFYYOHUrv3r3P2danTx+GDh1a/O/w5ptv8ve//7348aNHj9KnTx9mzJhBhw4dLvi6OTlmXyCPP6w26unpicPhKMu3ICIil5J5DL4aDodWme1uT7MqejSvTtnFWiddf6g8lWmfl4KCApKSks65NeHh4UHv3r1ZuXJliV7jbN+Mnj17MnTo0Mvu/8orr/Diiy9ecc2u4syZM+zZs6e4nZyczIYNGwgNDSU6Oprq1aufs7+3tzc1a9akcePGAOdclQGoWrUqAPXr16dOnToXPGaTJk1o0KABDzzwAK+99hrVq1dn7ty5/PLLL3z//fdl+fZERORi9i+Dr0ZCdhr4BpPcbSLjd0SxdP4awHnXHypPZTpJ3YkTJ7Db7URERJyzPSIigpSUlBK9xvLly5kxYwZz584lPj6e+Pj4S/avGDduHBkZGcVfhw4duqr34KzOzqtydnjy2LFjad26NePHjy/T49hsNiZPngyYAejHH3+kRo0a9O3bl1atWjFlyhQ+++wzbrrppjI9roiI/IFhwIq34LNbITuN/OpNeS7iLXp8V4Wlu0/g5WHjno7RLHmmB+Nualppggs44WijLl26lOqWhK+v73n9ONxR9+7dMQyjxPtfbjRRTEzMea+XnJyMl5cXnTt3Lt7WsGFDzagrIlLR8rPgmzGw7RsA1gZdz9Cjd5NreLnM+kPlqUzDS1hYGJ6enqSmpp6zPTU1lZo1a5bloaQc/Pjjj4wePZqGDRtaXYqISOV1fKc5DPrELopsXrxcOJTP0noDNpdaf6g8lWl48fHxoU2bNiQmJtKvXz/AnDwtMTGRRx55pCwPdZ6EhAQSEhKw2+3lehx3NmbMGKtLEBGp3LbOwZg7BlthNilGKA/lP8Z6oyHXNarBUzc0pmWdYKsrdAqlDi+X6zg6duxYhg8fTtu2bWnfvj2TJk0iOzu7eNRLeRkzZgxjxowhMzOT4GD944qIiAuxF5E/72/4rn4HG7DC3oxHCx+lXkwMM110/aHyVOrwsnbtWnr06FHcHjt2LADDhw9n8uTJDB48mOPHjzN+/HhSUlKIj4/n559/Pq8Tr4iIiEDuqaOcnnIPkelJALxb1Jefwu9nQp9mLr3+UHmyGaXpBeoCzl55ycjIICgo6JzH8vLySE5OJiYmhipVqlhUofvLzc1l//79xMbG4ufnZ3U5IiJOqaDIQeL8b2i7+glqcJosowoT/B+jw00j3GL9odK61Of3HzndaKMrVZI+L97e5qQ9OTk5Ci/l6OzkdmfPt4iI/FeR3cGcdYc5PP8NHin4FG+bnWRbFLt6vMvfunZxq2n8y0uluvICcOzYMdLT0wkPD8ff37/SJdvyZBgGOTk5pKWlERISUrzApoiIgMNh8PPWFBLmbWR0xhvc5rkCgP01/0TksA/x8b/01QZ3VymvvJTU2SHbaWlpFlfivkJCQjQ0XkTkd4ZhsGjXcSbM30n20Z286z2JJp6HcNi8KOr1EjGdHzaXqpESq3ThxWazUatWLcLDwyksLLS6HLfj7e2Np6d7LgQmIlJaq5NP8eq8HazZf5obPNYwwec9Am25OALC8Rg0BZ+6nawu0SVVuvBylqenpz5kRUSkXGw+nMFr83eyeNdxbDh42mc2Yzxmmw9GX4vHwE8hUFeor5TbhBdNUiciIlbbk5bFxF928eNmcz2/II98vqjxKc0zlpg7dHwYrn8JPDWg4WpUug67IiIiZe3QqRwm/bqbOesP4zDMLiz3NvPgL+kv4nNyO3j6QN83IP5uq0t1WuqwKyIiUgHSMvN4e+Eevlh9kEK7eS2gT/MInmtxmqhfHoCckxAQDndOh6h2FlfrPhReRERESik9p4D3Fu9j8opk8godAHRtGMZTNzQmLm0ufPckOIqgVhzc+QUE17a2YDej8CIiIlJCZ/KL+GRZMh8u2UdWfhEA10SH8HSfJnSKCYZ542D1B+bOzfvDbQng429hxe5J4UVEROQy8grtTPvtAO8s2sup7AIAmtYK4uk+jejROBxb7mmY1h+SF5tP6PkcdH1K87eUE7cJLxptJCIiZa3Q7mBW0mHeTNzNsYw8AGLDAhh7fSNublkLDw8bHN8JX9wJp/aBdwDc8SE0udniyt2bRhuJiIj8gcNh8N2mo7z+yy72nzTXa4sM9uOx3g2545o6eHl6mDvumgez7oOCLAiJhru+hIjmFlbuujTaSERE5AoYhkHi9jRem7+THSlZAFQP8OGRng24q300ft6eZ3eE5W/Ary8ABtTtDIOmQECYZbVXJgovIiIiwIo9J/jPvJ1sOJQOQKCfFw9eV58R18YQ4Ps/H5eFefDdY7DpS7PdZiTc+B/w8qn4oisphRcREanU1h88zWvzd7J8z0kAqnh7MrJzDA90q0+w/x9mws1KgS/vhiNJYPOEG/8N7e5Xx9wKpvAiIiKV0o6UTCbM38Uv21IB8Pa0MaRDXR7uUZ/wQL/zn3BknRlcso6BXwgM+gzqda/QmsXkNuFFo41ERKQk9p/I5vVfd/HtxqMYBnjY4I5r6vBY74bUqXaROVk2z4JvxkBRHoQ1hru/hNB6FVu4FNNoIxERqRSOZeTyZuIeZq49hN1hfvTd3KoWT/RuRIPwqhd+ksMBC/8OSyeY7YZ94I6PwE+fL2VNo41ERER+d/JMPu8u2suU3w5QUGRO5d+jcQ2evKExLWoHX/yJ+VkwezTs/NFsd34Mej0PHp4VULVcisKLiIi4pcy8Qj5amszHS/eRXWB2KWgfE8rTf2pMu5jQSz/5VDJ8cRcc3w6evnDrWxA3uAKqlpJQeBEREbeSW2Dns5X7eW/xXtJzCgFoUTuIp/s0oVvDMGyXGxmUvBRmDoPcU1C1prkidJ02FVC5lJTCi4iIuIWCIgcz1hzkzQV7OJ6VD0CD8Ko8eX0j/tSi5uVDC8Caj+Cnv5grQke2NoNLUGQ5Vy6lpfAiIiIuze4wmLv+CK//uovDp3MBqFOtCk/0bkS/1rXx9ChBaLEXmqFl7cdmu+VA81aRd5VyrFyulMKLiIi4JMMwmLc1hdfm72JP2hkAagT68ueeDRjcLhofL4+SvVDOKfM20f6lgA16jYcuT2jiOSem8CIiIi7FMAyW7D7Ba/N2svlIBgDBVbx5qHt9hneKoYpPKUYDpW2H6YMh/QD4VDWHQTe+sZwql7LiNuFFk9SJiLi/tftP8Z95O1mdfAoAfx9P7u8Sy/3d6hHk532ZZ//Bzp/g6/uh4AxUizFXhA5vWvZFS5nTJHUiIuL0thzJYML8nSzceRwAHy8Phnasy0Pd6xNW1bd0L2YYsGwiJL4MGBDT1VwR2v8yw6elXGmSOhERcQu7UrN4I3E3P2w6BoCnh41BbaP4c68G1Aq+gs60hbnwzSOwZZbZbnufubiiZymv2oilFF5ERMTpbDqcTsLCPczbai6aaLPBrXGRPN67EbFhAVf2oplHzYUVj64HD6//rggtLkfhRUREnIJhGKxKPkXCwj0s3X0CMENLn2Y1eax3Q5rWuoquAIeTzOByJgWqhJq3iWK7llHlUtEUXkRExFKGYbBo13ESFuxh7YHTgHl76Lb4SB66rj4NIwKv7gAbZ8C3j4I9H8KbmRPPhcaWQeViFYUXERGxhN1hztOSsHAPW49mAuDj6cHAtnV48Lr6RIX6X90BHHZIfBGWv2G2G98E/T8A36sMQ2I5hRcREalQhXYH32w4yruL9rD3eDZgDnke0iGa+7vWIyLI7+oPkpdpDoPePc9sd30SejwHHiWcuE6cmsKLiIhUiLxCO18lHeb9xXuLp/EP8vNiROdYRl4bQ7UAn7I50Mm95orQJ3aClx/clgAtB5TNa4tTUHgREZFylZ1fxOerDvDh0uTiBRPDqvpwX5d63NMxmsDSTi53KfsWwczhkJcOgbXgzs+htlaEdjcKLyIiUi7ScwqYvGI/k1fsJz2nEIDIYD8euK4+g9tF4eddimn8L8cwYPWH8POzYNjNwHLndAisWXbHEKfhNuFFywOIiDiHtKw8Pl6WzLSVB8guMP8mx4YF8FD3+vSLr13yBRNLqqgAfnoakiab7VaDoe+b4F0GfWfEKWl5ABERKROHT+fwwZJ9zFhziPwiBwBNagYypkcDbmpZC0+PclilOfuEuSL0geWADa5/Ea79s1aEdkFaHkBERCrM3uNneHfRXuauP0KRw/z/cOvoEB7p0YCeTcKxlVeQSNkCX94F6QfBNwju+Bga3VA+xxKnovAiIiJXZOvRDN5ZuJcftxzj7DX8zg2qM6ZHAzrVq15+oQVg+/cwezQUZkO1WLh7BtRoXH7HE6ei8CIiIqWSdOA0CQv3sGBHWvG23k0jGNOjPq2jq5XvwQ0DlrwGC/9utmOvg4GTtSJ0JaPwIiIil2UYBsv3nOTthbv5bd8pADxscEurSB7qXv/q1h0qqYIc+OZh2DrHbLd/APr8QytCV0IKLyIiclEOh8Gv21NJWLSXjYfSAfD2tNG/dR0e7F7/yld4Lq2Mw+bCisc2mitC3zwB2oyomGOL01F4ERGR8xTZHfyw+RjvLNzLztQsAPy8PbizXTSju9UjMqRKxRVzaDV8OQSy08C/OgyaCjGdK+744nQUXkREpFhBkYPZ6w7z7uK9HDiZA0CgrxdDO9Xl3i6xhFX1rdiCNn75+4rQBRDeHO76AqrVrdgaxOkovIiICLkFdr5YfZAPl+7jWEYeANX8vbm3cyzDro0huEoF9ytxOGDhP2Dpa2a7yS1w+/vgW7Vi6xCnpPAiIlKJZeYVMnXlAT5elsyp7AIAIoJ8GdW1Hnd3iMbfx4KPicJcmPvQfzvmdnkCeo7XitBSTOFFRKQSOpVdwCfLkvls5X6y8ooAiA7158Hr6nNHm9r4epXhukOlcSbNXBH6yFrw8Ia+k6D1PdbUIk5L4UVEpBJJycjjgyX7+GL1QXILzXWHGoZXZUyPBtzSqhZenhZe3UjdBtMHQ8ZB8AuBwdMgtqt19YjTUngREakEDpzM5r3Fe5mVdJhCuzkdbsvawYzp0YAbmkXgUR7rDpXG7l/hqxFQkAWh9eDuryCsgbU1idNSeBERcWO7UrN4Z+Eevt14lN+XHaJ9bCiP9GhA14Zh5TuFf0mt/hB+egYMB9TtbF5x0Yy5cgluE14SEhJISEjAbrdbXYqIiOXWHzzNu4v2Mn9bavG27o1rMKZHA9rFOEkwcNhh3l9h1btmO+5u6PsGePlYW5c4PZthnF1Oyz2UZkltERF3YncY/LItlY+W7mPtgdMA2GxwY4uaPNy9AS1qB1tc4f/Iz4JZ98HueWa713joMtYsWCql0nx+u82VFxGRyiq3wM6spEN8vCyZ/b9PLOftaeO2+No8eF09GoQHWlzhH6Qfgi/uhNQt4OUHt78HzW+3uipxIQovIiIu6nhWPlNW7mfabwc4nVMIQHAVb+7pGM3wTjGEB/lZXOEFHEkyh0KfSYWAcLjrS6jTxuqqxMUovIiIuJjdqVl8tDSZOeuPUGB3AOYcLfd1iWVg2zrWTCxXEtu+gdkPQFGuOdX/3V9CSLTVVYkLctKfcBER+V+GYbBy70k+WLqPRTuPF2+/JjqEUV3rcUPzmnhaPdz5YgwDlr0OiS+a7QbXw4BPwE/9EuXKKLyIiDixQruDHzYd44Ml+9h2LBMw+7T2aVaTUd1iaVPXSUYOXUxRAXz/BGyYZrbbPwB9/gme+viRK6efHhERJ5SZV8gXqw4yecX+4oUSq3h7MrBtHe7tHEtMWIDFFZZAzimYOQz2LwWbB/zp39BhtNVViRtQeBERcSKHT+fw6fL9zFhziDP55ppDYVV9Gdk5hiEdognxd5E5UE7uhc8Hwqm94FMVBnwKjW6wuipxEwovIiJOYNPhdD5cmsyPm49h/30q3EYRVbm/az1ui4+0bqHEK7F/Gcy4B3JPQ3AU3D0DIppbXZW4EYUXERGLOBwGC3ak8eHSfaxKPlW8vUuDMO7vGst1jWo4x/T9pbFhOnz7Z3AUQu02cOcXEBhhdVXiZhReREQqWF6hndnrjvDRsn3sO54NgJeHjVvjIrmvayzNI51oJtyScjhg4d9h6QSz3ayfOfmcdxVLyxL3pPAiIlJBTp7JZ+pvB5i68gAnswsACPTz4u4O0Yy4NoZawS76QV+YC3MeMOdxAej6JPR4Djw8rK1L3JbCi4hIOdt7/AwfLU1m9rrD5BeZk8rVDqnCvV1iGdwuiqq+LvynOCsVvrzLnDnXwxtufRPi77a6KnFzLvwbIyLivAzDYFXyKT5auo9ft6cVb4+rE8z9XetxY4uaeHm6+JWJ1K0wfTBkHIIq1WDwNIjpYnVVUgkovIiIlKEiu4Mft6Tw0dJ9bDqcAZiTyvVqEsGorrG0jw11vU64F7L7F/hqJBRkQWh9GPIVVK9vdVVSSSi8iIiUgTP5RXy5+iCfLt/PkfRcAHy9PLijTR3u6xJL/RpVLa6wDK36AH7+CxgOiOkKg6aAv5PP9CtuReFFROQqHMvIZfLy/UxfdZCs3yeVqx7gw7BOMdzTMZrqVX0trrAM2Ytg3jhY/YHZjr8HbnkdvFxk4jxxGwovIiJXYMuRDD5auo/vNx2j6PdJ5erVCGBU13rc3ro2ft4uNKlcSeRlwqx7Yc8vZrvX89DlCfOemEgFU3gRESkhwzBYtOs4Hy7Zx4q9J4u3d4gNZXS3evRoHI6Hs67sfDXSD5kdc9O2glcV6P8+NLvN6qqkEnO68JKenk7v3r0pKiqiqKiIxx57jFGjRlldlohUYnmFdr7ZcISPliazO+0MAJ4eNm5qWYtRXWNpVSfE2gLL0+Ek+OJOyE6DqhFw1xfmzLkiFnK68BIYGMiSJUvw9/cnOzubFi1a0L9/f6pXr251aSJSyZzOLmDabwf4bOUBTpzJB6Cqrxd3totiROcY6lTzt7jCcrZ1Dsx5EIryILy5uUZRSJTVVYk4X3jx9PTE39/8g5Cfn49hGBiGYXFVIlKZ7D+RzcfLkvkq6RB5heakcrWC/RjZOYY720cT5OdtcYXlzDBg2URIfMlsN+wDAz4G30Br6xL5XalnSFqyZAl9+/YlMjISm83G3Llzz9snISGBmJgY/Pz86NChA6tXry7VMdLT04mLi6NOnTo8/fTThIWFlbZMEZFS23Ikg4c/T6LHhEVM/e0AeYUOmkcGMWlwPEue6cHobvXdP7gUFcDch/8bXDo8ZN4qUnARJ1LqKy/Z2dnExcVx77330r9///MenzFjBmPHjuW9996jQ4cOTJo0iT59+rBz507Cw8MBiI+Pp6io6Lznzp8/n8jISEJCQti4cSOpqan079+fAQMGEBGhVUlFpHys2X+KtxfsYfGu48XbejSuwaiu9ehUv7p7TCpXEjmnYMY9cGA52Dzgxv9Ae/U5FOdjM67inozNZmPOnDn069eveFuHDh1o164db7/9NgAOh4OoqCgeffRRnn322VIf4+GHH6Znz54MGDDggo/n5+eTn59f3M7MzCQqKoqMjAyCgoJKfTwRqRwMw2Dp7hO8vXAPq5NPAeBhg75xkTzcvQGNa1ayKw0n9sD0gXBqH/gEwsDJ0LC31VVJJZKZmUlwcHCJPr/LtM9LQUEBSUlJjBs3rnibh4cHvXv3ZuXKlSV6jdTUVPz9/QkMDCQjI4MlS5bw0EMPXXT/V155hRdffPGqaxeRysHhMJi/LZV3Fu0pnr7f29PGgDZ1eKBbfWLCAiyu0ALJS80rLnnpEBxtdsyNaGZ1VSIXVabh5cSJE9jt9vNu8URERLBjx44SvcaBAwcYPXp0cUfdRx99lJYtW150/3HjxjF27Nji9tkrLyIi/6vI7uD7Tcd4Z9EedqWaw539vD24q300o7vVo1ZwFYsrtMj6afDd4+AohNptzf4tVcOtrkrkkpxutFH79u3ZsGFDiff39fXF19eNpt8WkTKVX2Tn66QjvLd4LwdP5QAQ6OvFsGvrcm/nWPeavr80HA5Y8BIse91sN78d+r0L3pU0xIlLKdPwEhYWhqenJ6mpqedsT01NpWbNmmV5KBGRS8opKGL6qoN8uHQfqZlmv7jQAB/u7RzD0E4xBFdx81FDl1KQA3MegO3fmu1uT0P3/wOPUg9AFbFEmYYXHx8f2rRpQ2JiYnEnXofDQWJiIo888khZHuo8CQkJJCQkYLfby/U4IuLcMnILmbpyP58s38+p7AIAIoJ8Gd2tPne1j8Lfx+kuOFesrBT44i44ug48vOHWtyD+LqurEimVUv8Wnzlzhj179hS3k5OT2bBhA6GhoURHRzN27FiGDx9O27Ztad++PZMmTSI7O5uRI0eWaeF/NGbMGMaMGVPcW1lEKpeTZ/L5ZHkyU1YcKF7dOTrUn4e616f/NbXx9XKzhRKvRMpmmH4nZB6GKtVg8OcQ09nqqkRKrdThZe3atfTo0aO4fbaz7PDhw5k8eTKDBw/m+PHjjB8/npSUFOLj4/n55581T4uIlItjGbl8sGQfX6w+WDwbbsPwqozp0YBbWtXCy1O3QgDYNc9cFbrgDFRvAHfPhOr1ra5K5Ipc1Twvzqg048RFxHUdOJnNe4v3MivpMIV2889YqzrBjOnRgOubRrjn6s5XwjBg1fswbxwYDojpCoOnmldeRJyIZfO8WEl9XkQqh50pWbyzaA/fbTyK4/f/erWPDeWRHg3o2jCs8syGWxL2Ivj5L7DmI7PdeijcPBG8fKytS+Qq6cqLiLiEjYfSSVi4h/nb/juasXvjGozp0YB2MaEWVuak8jJh1kjY8ytgg94vQOfHQOFOnFSlvPIiIu7HMAxWJZ8iYeEelu4+AZifvTe2qMnD3RvQorY6519Q6laYOQxO7gGvKnDHh9C0r9VViZQZhRcRcTqGYbBo53ESFu5h7YHTAHh62LgtPpKHu9enQXglW3eoNDZMh+/HQlEuBNWGwdOg9jVWVyVSphReRMRp2B0G87amkLBwD1uPZgLg4+XBoLbmukNRof4WV+jECnPhx6dh/VSzXb8n9P8IAqpbW5dIOXCb8KIOuyKuq9Du4JsNR3l30R72Hs8GwN/HkyEdohnVtR7hQX4WV+jkTu6FmcMhdTNgg+7joNtT4KG5bcQ9qcOuiFgmr9DOV0mHeX/xXg6fzgUgyM+LEZ1jGXltDNUCNCrmsrZ9C9+MgfxM8A8z+7fU72l1VSKlpg67IuLUsvOL+HzVAT5cmszxLHPdobCqPtzftR5DOkQT6FeJ1x0qKXsh/PI8/JZgtqM6wsBPISjS2rpEKoDCi4hUmPScAj5bcYBPVySTnlMIQGSwHw9cV5/B7aLw89ZtjhLJOGIOgz60ymx3esQcCu2p0CeVg8KLiJS741n5fLRsH9NWHiC7wOyXFhsWwEPd69MvvjY+XprCv8T2JMLsUZBzEnyDoN87GgYtlY7Ci4iUmyPpuby/eC8z1hwiv8hcd6hJzUDG9GjATS1r4akp/EvOYYfF/4HF/wYMqNkKBn0GofWsrkykwrlNeNFoIxHnse/4Gd5dtJc5649Q9Psc/q2jQ3ikRwN6NgnXFP6llX0Cvr4f9i00221GwJ/+Dd4ahSWVk0YbiUiZ2XY0k4RFe/hx8zHO/mXp3KA6Y7o3oFP96gotV+Lgb/DVSMg6as6We8vrEH+X1VWJlDmNNhKRCmMYBiv3neTDJftYuPN48fbeTcN5uEcDronW6sVXxDBgZQL8+jw4iqB6Qxg0BSKaWV2ZiOUUXkTkihTaHfy4+RgfLt3HliPmbLgeNri5lTmFf9NauvJ5xfIyYO7DsON7s928P9z6JvhqWQQRUHgRkVLKyitkxppDfLp8P0fSzYnl/Lw9GNgmivu6xBITFmBxhS7u2CZzUcXTyeDhDX96Bdrdr9WgRf6HwouIlMixjFwmL9/P9FUHycovAsyJ5YZ3imFIx7qEajbcq2MYsG6KuT6RPR+Co2DgZ1CnjdWViTgdhRcRuaStRzP4aGky3208WjxyqH6NAEZ1rUe/1rU1sVxZKMiBH56EjdPNdsMb4Pb3wT/U2rpEnJTbhBcNlRYpO4ZhsGT3CT5cso9le04Ub+9YL5RRXevRo3E4HpqjpWyc2G3eJkrbBjYP6PkcdH4CPDRxn8jFaKi0iBTLL7Lz7YajfLQ0mZ2pWQB4eti4qWUtRnWNpVWdEGsLdDdbZsO3j0LBGQgIhwGfQGxXq6sSsYSGSotIqWTkFPL56gNMXr6ftN8XSvT38eTOdtGM7BxDVKi/xRW6maICmP8crH7fbNftAgM+hsCa1tYl4iIUXkQqsUOncvhkeTIz1hwi5/c1hyKCfBlxbSx3t48m2F8L/ZW59IPw1Qg4kmS2uzwBPZ4DT/05Fikp/baIVEIbD6XzwdJ9/LT5GL/3waVJzUBGda1H37hILZRYXnb/Yi6qmHsa/ELMTrmN/2R1VSIuR+FFpJJwOAwW7Ejjg6X7WJ18qnh714ZhjOpaj64NwzR9f3lx2GHhP2Hpa2Y7srU5DLpaXWvrEnFRCi8ibi6v0M7sdUf4aNk+9h3PBsDLw8at8ZHc36UezSLVsb1cnUmDr++D5CVmu9390Oef4OVrbV0iLkzhRcRNncouYOrKA0xZuZ+T2QUABPp5cXeHaEZcG0Ot4CoWV1gJ7F8Os+6FMyngHWBO8d9ygNVVibg8twkvmudFxLTv+Bk+XpbMrKTD5Bc5AKgdUoV7u8QyuF0UVX3d5tfeeTkcsOJNSHwJDDvUaGIuqlijsdWVibgFzfMi4gYMwyDpwGk+WLKPX7ancva3umXtYEZ1q8dNLWri5alOuBUi9zTMeQh2/WS2Ww2GW14HH635JHIpmudFpJKwOwzmbU3hgyX72HAovXh7rybhjOpWjw6xoeqEW5GOrjdny00/CJ4+cON/oM0ILaooUsYUXkRcUE5BEV+tPczHy5I5eCoHAB9PD/pfU5v7u8bSIDzQ4gorGcOAtR/Dz+PAXgAhdc3bRJHxVlcm4pYUXkRcSFpmHp+t3M+03w6SkVsIQIi/N0M71mVop7qEB/pZXGEllH8Gvn8cNn9lthvfDP0SoEo1S8sScWcKLyIuYFdqFh8t3cfc9UcpsJudcOtW9+f+LrHc0aYO/j76VbZE2g7zNtGJnWDzhN4vwLWP6jaRSDnTXzwRJ2UYBiv3nuSDpftYtPN48fZrokMY3a0e1zeriadWdrbOpq/guz9DYQ5UrQkDP4W611pdlUiloPAi4mQK7Q5+3HyMD5bsY+vRTMD8j3yfZjUZ1S2WNnVDLa6wkivMg3njYO0nZju2G9zxMVQNt7YukUpE4UXESWTlFfLl6kN8ujyZoxl5APh5ezCwTRT3dYklJkxDbS13er95m+jYRrPd7Rno/ix4eFpalkhlo/AiYrGj6blMXrGfL1YdJCu/CICwqj4M7xTDkI51CQ3wsbhCAWDHjzD3QcjLgCqh0P9DaNjb6qpEKiWFFxELnO3PMmXlAX7Znor996Wd69cIYFTXevRrXRs/b/1v3inYi2DBS7D8DbNdpx0MnAzBdSwtS6Qyc5vwouUBxBVk5RUye90Rpv52gD1pZ4q3d6wXyqiu9ejROBwPdcJ1Hlkp5tpEB5ab7Q4PwfUvgZeuholYScsDiFSA3alZTFl5gNnrDpNdYAZsfx9P+l9Tm6EdY2hcU5PKOZ19i83VoLOPg08g3PYWNL/d6qpE3JaWBxBxAkV2B79sS+Wzlfv5bd+p4u31agQwrGNd+repQ5Cft4UVygU5HLBsAiz8JxgOCG9uzpYb1sDqykTkdwovImUsLSuPL1cfYvqqg6RkmqOGPGzQu2kEw6+N4dr61bXekLOyF8Hs+2HrHLMdfw/c9Cr4+Ftbl4icQ+FFpAycXdV5ysoD/LTlGIV2825s9QAf7mwfxd0d6lI7pIrFVcolORzw7aNmcPH0gZsnwjVDra5KRC5A4UXkKuQUFPHNhqNMWXmA7ccyi7e3jg5hWKe63NSyFr5eGjXk9AwDfv4LbJxuTvM/4FNoeovVVYnIRSi8iFyB5BPZTPvtAF+tPURmnjk3i6+XB7fFRzKsUwwtagdbXKGUyoKXYfUHgA36vavgIuLkFF5ESsjuMFi4I40pvx1gya7/rjUUHerPPR2jGdgmimqaUM71LJ0ISyeY3988AeIGW1uPiFyWwovIZZzOLmDG2kNM++0Ah0/nAuZaQ9c1qsHwTjFc16iG5mZxVas/hMQXze+vfwna3WdtPSJSIgovIhex6XA6n604wHebjlJQ5AAguIo3g9rW4Z6OdalbXWsNubQN0+HHp8zvuz0NnR+zth4RKTGFF5H/kVdo54dNx5jy2wE2Hkov3t48MojhnWLoGxdJFR91wHV5276Bb8aY33d4EHr81dp6RKRUFF5EgMOnc/h81UFmrDnEqewCALw9bdzcshZDO8VwTXSI5mZxF3t+hVn3mRPQtb4H+rxi3gcUEZeh8CKVlsNhsGzPCaasPMCCHan8vjYitYL9GNIhmsHtoqkR6GttkVK29i+HL+8BRyE06wd93wQPD6urEpFSUniRSicjt5Cvkw4z7bcD7DuRXby9c4PqDO0YQ++m4Xh56gPN7RxZB9MHQ1EuNLwB+n8IHroFKOKKFF6k0th+LJMpKw8wd/0RcgvNxRGr+npxxzW1GdqpLg3CtTii20rdBtP6Q0EW1O1irlWklaFFXJbbhJeEhAQSEhKw2+1WlyJOpNDu4OctKUxdeYDV+/+7OGLD8KoMuzaG21vXpqqv2/wayIWc3AtT+0HuaajdBu7+Ery1VIOIK7MZhmFYXURZKs2S2uK+UjPzmL7qINNXH+R4Vj4Anh42+jSPYGjHGDrWC1UH3Mog4wh88ifIOGiuDj3ie/APtboqEbmA0nx+67+c4jYMw2B18immrDzAvK0pFP3eAzesqi93d4jm7vbR1Az2s7hKqTBnjsOU28zgElofhs5RcBFxEwov4vKy84uYs/4IU1ceYGdqVvH2djHVGNophj81r4mPlzrgViq5p2Hq7XByNwTVgWHfQGCE1VWJSBlReBGXlXwim89W7OfrpMNk5ZuLI1bx9qRf60iGdoyhWaRuG1ZK+Wfg84GQuhkCwmH4txASZXVVIlKGFF7E5RTZHby/ZB+Tft1Fod28NRQbFsA9HesyoE0dgqt4W1yhWKYwD768Cw6vAb8Q81ZR9fpWVyUiZUzhRVxK8olsxs7cwPqD6QB0bRjG/V3r0bVBmBZHrOzshfDVCEheAj5V4Z7ZULOF1VWJSDlQeBGX4HAYTP3tAK/8tJ28QgeBvl68cGtz+l9TW6OGBBx2mPMA7PoJvPzgri+hThurqxKRcqLwIk7vaHouz8zaxLI9JwBzJtz/DIijdojm6hDAMOD7x2HL1+DhBYOmQmxXq6sSkXKk8CJOyzAM5qw/wvPfbiUrrwg/bw+e/VMThnWK0S0iMRkGzPsrrJsCNg+44yNodIPVVYlIOVN4Ead08kw+/zdnM/O2pgIQHxXCxEFx1KtR1eLKxKks/jf8lmB+3/dNaH67tfWISIVQeBGnM29rCv83ezMnswvw9rTxeO9GPNCtnhZLlHOteBsWvWJ+/6d/wzVDra1HRCqMwos4jcy8Ql78dhtfrzsMQOOIQCYOjqN5ZLDFlYnTSZoM8/9qft/zOej4oKXliEjFUngRp7B8zwme/mojRzPy8LDB6G71eeL6hvh6eVpdmjibzbPgu8fN7zs/Bl2fsrQcEal4Ci9iqdwCO//6aTufrTwAQN3q/kwYGEfbGK1BIxew40eYPRowoO290PtF0FB5kUpH4UUss+7gaZ6auZF9J7IBuKdjNONubEqAr34s5QL2LTInoTPs0Gow3DRBwUWkktKnhFS4giIHbyTu4t1Fe3EYUDPIj/8MaEW3RjWsLk2c1aHV8MXdYM+HJrfAbe+Ahzpwi1RWCi9SobYfy2TszI1sP5YJQL/4SF68tQXB/lqPSC7i2CaYNgAKs6FeDxjwCXjqT5dIZaa/AFIh7A6DD5bsY+IvOym0G1Tz9+Yft7fkppa1rC5NnNnxXTD1dsjPgKiOcOfn4OVrdVUiYjGFFyl3+09k8+RXG0k6cBqA3k3D+Wf/loQH+llcmTi10wdgym2QcwJqxcGQmeATYHVVIuIEFF6k3BiGwbRVB/nnD9vJLbRT1deL8X2bMbBNHS2mKJeWeQym3ApZRyGssblCtJ/m+xERk9P2eMvJyaFu3bo89ZTmcHBFxzJyGfbJav42dwu5hXY61gvl58e7MqhtlIKLXFr2SZjaD07vh5C6MGwuBIRZXJSIOBOnvfLyj3/8g44dO1pdhpSSYRh8s+Eo47/ZQmZeEb5eHvzlT00Yca0WU5QSyMuAaf3h+A4IrAXDv4WgSKurEhEn45ThZffu3ezYsYO+ffuyZcsWq8uREjp5Jp/n5m7hpy0pAMTVCWbCoHgahGsxRSmBghyYPhiObQD/6jDsG6gWY3VVIuKESn3baMmSJfTt25fIyEhsNhtz5849b5+EhARiYmLw8/OjQ4cOrF69ulTHeOqpp3jllVdKW5pY6JdtqfSZtISftqTg5WHjyesb8fVD1yq4SMkU5cOMIXBwJfgGw9A5UKOx1VWJiJMq9ZWX7Oxs4uLiuPfee+nfv/95j8+YMYOxY8fy3nvv0aFDByZNmkSfPn3YuXMn4eHhAMTHx1NUVHTec+fPn8+aNWto1KgRjRo1YsWKFZetJz8/n/z8/OJ2ZmZmad+SXIWsvEJe+m4bXyWZiyk2iqjKxEHxtKitzpVSQvYi+Po+2LsAvP3NUUW14qyuSkScmM0wDOOKn2yzMWfOHPr161e8rUOHDrRr1463334bAIfDQVRUFI8++ijPPvvsZV9z3LhxTJs2DU9PT86cOUNhYSFPPvkk48ePv+D+L7zwAi+++OJ52zMyMggKCrqyNyYlsmLvCZ7+ahNH0nOx2WB013o8cX0j/Ly1mKKUkMMB3zwMG78ATx+4ewbU72l1VSJigczMTIKDg0v0+V2m4aWgoAB/f39mzZp1TqAZPnw46enpfPPNN6V6/cmTJ7NlyxZee+21i+5zoSsvUVFRCi/lKLfAzr9/3sHkFfsBiAqtwoSB8bSP1WKKUgqGAT8+BWs+ApsnDJoCTW+xuioRsUhpwkuZdtg9ceIEdrudiIiIc7ZHRESwY8eOsjxUMV9fX3x9NeNmRdlwKJ2xMzew77i5mOJd7aP5681NqarFFKW0El80gws2uP09BRcRKTGn/sQZMWKE1SXI7wqKHLy1YDfvLNqL3WEQHujLvwe0okfjcKtLE1e0dAIse938/paJ0GqQtfWIiEsp0/ASFhaGp6cnqamp52xPTU2lZs2aZXmo8yQkJJCQkIDdbi/X41RGO1OyGDtzA1uPmp2hb42L5KXbmhPi72NxZeKSVn0AiS+Z31//MrS919p6RMTllOkMuz4+PrRp04bExMTibQ6Hg8TERDp16lSWhzrPmDFj2LZtG2vWrCnX41QmdofB+4v30vetZWw9mkmIvzdv392aN+9qreAiV2bDdPjpafP7bs9A5z9bW4+IuKRSX3k5c+YMe/bsKW4nJyezYcMGQkNDiY6OZuzYsQwfPpy2bdvSvn17Jk2aRHZ2NiNHjizTwqV8HTiZzVNfbWTNfnMxxZ5NwvlX/5aEB2kxRblCW+fCN2PM7zs8BD3+z9JyRMR1lTq8rF27lh49ehS3x44dC5gjiiZPnszgwYM5fvw448ePJyUlhfj4eH7++efzOvGKczIMg+mrD/KPH7aTU2AnwMeT8X2baU0iuTq7f4Gv7wfDAa3vgT7/BP08icgVuqqh0s7kf/u87Nq1S0Olr0BKRh5/+XoTi3cdB6BDbCivDYwjKtTf4srEpe1fBtPugKI8aH473PExeGguIBE5V4XN8+KMSvPmxWQYBt9uPMr4b7aSkVuIj5cHz/RpzL2dY7WYolydI0nw2W1QkAUN+8DgaeCl/lIicj7L5nkR13Mqu4C/zd3CD5uPAdCydjATB8XRMCLQ4srE5aVuM6+4FGRBTFcY9JmCi4iUCYWXSixxeyp/+XozJ87k4+lh49GeDRjTowHenmU6CE0qo5N7YWo/yD0NtdvAXV+AdxWrqxIRN6HwUgll5RXy9++3M2PtIQAahFdl4qA4WtUJsbYwcQ8Zh2HKbXAmFcKbw5BZ4KsreSJSdtwmvGiSukvLK7SzYu8JErenMW9rKifO5GOzwX2dY3mqT2Mtpihl40yaGVwyDkFofRg2F/y15pWIlC112HVjqZl5JG5PY8GOVJbtOUFeoaP4sTrVqvDawDg61qtuYYXiVnJPw+RbIHULBEfByJ8gJMrqqkTERajDbiXlcBhsOZrBr78Hli1HMs95vFawHz2bhNO7aQSd6lfX1RYpGw4HbJ0NC/8Bp/ZBQDgM+0bBRUTKjcKLi8spKGLZbvN20IKdaRzPyi9+zGaDuDoh9GoSTq+mETStFaiJ5qTsGAbs/BEW/APStprbAsLNW0XV61tamoi4N4UXF3QkPZcF21NJ3JHGir0nKSj67+2gAB9PujasQc+m4fRoHE6NQF8LKxW3ZBiwdwEs+DscXWdu8w2Cax+Fjg+pc66IlDuFFxdgdxhsPJxO4vZUErensSMl65zH61SrQu+mEfRsEk6HeqH4eul2kJSTAysg8WU4uMJse/tDhwfN4KKOuSJSQdwmvLjbaKOsvEKW7T7Br9vTWLQzjZPZBcWPedjgmuhq9GoaQa+m4TQMr6rbQVK+jiSZt4f2/r5ivKcvtLsPuoyFqjWsrU1EKh2NNnIiB0/mkLjDvLqyKvkkhfb//tME+nrRrXENejcN57pG4YQGaKZSqQCpW2HhP2HH92bbwwtaD4VuT0NwbWtrExG3otFGLqLI7mDdwXQSd6SyYHsau9POnPN4THX/4qsr7WJCNfOtVJwTe2DRK7Dla8AAmwe0GgzX/QVCY62uTkQqOYWXCpaRW8jiXcdZsD2VRbuOk55TWPyYp4eNdjHV6NXEDCz1alS1sFKplNIPwuJ/w4YvwPj9FmyzftDj/6BGY0tLExE5S+GlAuw7foYFO9L4dXsqa/afxu747+2g4Cre9Ghcg55NI7iuYQ2C/b0trFQqrcxjsHQCJE0Gx++ButGfzNBSK87S0kRE/kjhpRwU2h2s2X+KBdvTSNyRRvKJ7HMebxBelV5Nw+nVJIJrokPw0u0gsUr2SVj+Oqz+EIryzG2x10HP5yCqvbW1iYhchMJLGTmdXcCiXWkkbk9j8a7jZOUVFT/m7WmjQ2x1ejUNp2eTcOpWD7CwUhEgNx1WJsBv70DB732t6rSHXn+D2G6WliYicjluE14qeqi0YRjsSTtTPBV/0oHT/M/dIEIDfOjROJxeTcPp2jCMQD/dDhInUJANq96D5W9CXrq5rWYr6Pk3aHi9OS2ziIiT01DpUsgvsrM6+RSJ29NI3JHKoVO55zzepGbg71dXIoiPCsHTQx8E4iQK82DtJ7BsImQfN7fVaGL2aWnSFzx061JErKWh0uXgh03HeGbWRrIL/ntlx8fLg2vrV6dXk3B6NAmnTjV/CysUuQB7IayfCotfhayj5rZqMdD9/6DlAPDQbMwi4noUXkooJsyf7AI7NQJ96dXE7LvSuUEYAb46heKEHHbYNBMW/wtO7ze3BdWG656B+CHgqduYIuK69MlbQs1qBfHdI11oHhmEh24HibNyOGD7t+asuCd2mtsCakDXp6DNCPD2s7Q8EZGyoPBSQjabjZZ1gq0uQ+TCDAN2z4cFL0PKZnObXwh0eRzajwYfjXATEfeh8CLi6vYthgV/h8OrzbZPIHQaA50eBj8FbhFxPwovIq7q0GpIfAn2LzXbXlWgw2i49jEIqG5tbSIi5chtwktFz/MiYpljG80rLbvnm21PH2gzErqOhcCa1tYmIlIBNM+LiKtI2wEL/2F2yAWweULrIdDtaQiJtrY2EZGrpHleRNzJqX2w6F/m0GcMwGbO0dJ9HFSvb3V1IiIVTuFFxFllHIYlr8L6aeD4fa2sJrdAj79CRDNraxMRsZDCi4izOZMGSyfC2o/BXmBua9DbDC21r7G2NhERJ6DwIuIsck7Bijdh1ftQmGNuq9sFej4HdTtZW5uIiBNReBGxWl4m/PYurHwb8jPNbbXbmCs91+uulZ5FRP5A4UXESoeTYOZQyDxitiNamLeHGt+o0CIichEKLyJWSfoMfnzK7NdSLRZ6/Q2a3Q4eHlZXJiLi1BReRCpaUT789AwkTTbbTW6Bfu+Cn+YlEhEpCYUXkYqUcQRmDoMjawGb2Rm3y1hdbRERKQW3CS9aHkCc3v7l8NVwyD5urvh8x8fQsLfVVYmIuBwtDyBS3gzDHP48/6/mZHMRLWDwNAiNtboyERGnoeUBRJxFQQ58/zhsmmG2WwyAW98EnwBLyxIRcWUKLyLl5fR+mHEPpGw2F1G84e/Q8SENgRYRuUoKLyLlYU8ifH0f5J4G/zAYOBliu1pdlYiIW1B4ESlLhgHLXocFL4PhMGfKHTQVgmtbXZmIiNtQeBEpK/lZMPch2P6d2b5mGNz4Knj7WVuXiIibUXgRKQsndsOXQ+DETvDwhptehbYjra5KRMQtKbyIXK0dP8KcB8xFFQNrmbeJotpZXZWIiNtSeBG5Ug4HLHoFlvzHbEdfa3bMDYywtCwREXen8CJyJXJPw+zRsHu+2W7/APT5B3h6W1uXiEgloPAiUlqpW83+LaeTwcsP+r4BcXdaXZWISKWh8CJSGptnwbePQmEOBEfD4KkQGW91VSIilYrCi0hJ2Ivg1+dh5dtmu14PGPAJ+IdaW5eISCWk8CJyOdkn4KsRsH+p2e7yBPT8G3h4WlqWiEhl5TbhJSEhgYSEBOx2u9WliDs5sg5mDIXMw+AdALe/C81us7oqEZFKzWYYhmF1EWWpNEtqi1zS+mnw/Viw50NofbhzOoQ3sboqERG3VJrPb7e58iJSZooK4OdnYe3HZrvRjdD/ffALtrYuEREBFF5EzpV5DL4aDodWATboPg66PQ0eHlZXJiIiv1N4ETnr4G8wcxicSQXfYLjjQ2jUx+qqRETkDxReRAwD1nxk3ipyFEGNpnDn51C9vtWViYjIBSi8SOVWmGt2yt043Ww3vx1ufRt8q1pbl4iIXJTCi1Re6Qdhxj1wbCPYPKD3i3Dto2CzWV2ZiIhcgsKLVE77FsFXIyH3FFQJhYGfQr3uVlclIiIloPAilYthwIo34dcXwHBArXhzfaKQaKsrExGRElJ4kcoj/wx8+whsnWO244fAzRPAu4q1dYmISKkovEjlcHKv2b8lbRt4eMGf/gXt7lf/FhERF6TwIu5v1zz4ehTkZ0DVCBg0BaI7Wl2ViIhcIYUXcV8OByz5Dyx6xWxHdYCBn0FQLWvrEhGRq6LwIu4pLwNmPwC7fjLb7e6HPq+Al4+1dYmIyFVTeBH3k7YdvhwCp/aCpy/c8jq0HmJ1VSIiUkYUXsS9bJ0Lcx+GwmwIqmMOg659jdVViYhIGVJ4EffgsEPiS7B8ktmO7QYDPoWAMEvLEhGRsqfwIq4v5xTMGmnOmgvmFP+9XgBP/XiLiLgj/XUX13Z0A8wYChkHwdsfbnsbWtxhdVUiIlKOFF7E9RQVwPEdkLwEFrwMRXlQLRbu/BwimltdnYiIlDOnDC8xMTEEBQXh4eFBtWrVWLhwodUliVVy0yF1C6RsNr+ObTKDi6Pwv/s0vAH6fwBVqllWpoiIVBynDC8AK1asoGrVqlaXIRXFMCDj0H9DSspmSNkE6QcvvL9fMNRsBY1vhA4PgYdHxdYrIiKWcdrwIm6sqABO7Dw/qORlXHj/kGgzqNRs+d+v4CitSyQiUkmVOrwsWbKEV199laSkJI4dO8acOXPo16/fOfskJCTw6quvkpKSQlxcHG+99Rbt27cv8TFsNhvXXXcdHh4ePP744wwZognGXNYfb/ukbIK0P9z2OcvDG8KbnBtUIprrdpCIiJyj1OElOzubuLg47r33Xvr373/e4zNmzGDs2LG89957dOjQgUmTJtGnTx927txJeHg4APHx8RQVFZ333Pnz5xMZGcmyZcuoXbs2x44do3fv3rRs2ZJWrVpdsJ78/Hzy8/OL25mZmaV9S1IWDAMyDp8bUi5128c3+NwrKTVbQo0mmr5fREQuy2YYhnHFT7bZzrvy0qFDB9q1a8fbb78NgMPhICoqikcffZRnn3221Md4+umnad68OSNGjLjg4y+88AIvvvjiedszMjIICgoq9fGkBOyFcHznH4LKZshLv/D+wdHnB5WQaN32ERGRYpmZmQQHB5fo87tM+7wUFBSQlJTEuHHjird5eHjQu3dvVq5cWaLXyM7OxuFwEBgYyJkzZ1iwYAGDBg266P7jxo1j7Nixxe3MzEyioqKu/E3IufIyIOUPt32O7wB7wfn7enhBjaZ/CCotdNtHRETKVJmGlxMnTmC324mIiDhne0REBDt27CjRa6SmpnL77bcDYLfbGTVqFO3atbvo/r6+vvj6+l550WK64G2fzZB+4ML7+wZd5LaP/i1ERKR8Od1oo3r16rFx40ary3Bv9kI4set/5k7ZeJnbPlEXuO1TV7d9RETEEmUaXsLCwvD09CQ1NfWc7ampqdSsWbMsD3WehIQEEhISsNvt5Xocl3dkHXx5N2QdO/8xDy/z6sn/hpSIFuAfWvF1ioiIXESZhhcfHx/atGlDYmJicSdeh8NBYmIijzzySFke6jxjxoxhzJgxxR1+5AL2L4Ppd0JBFvgEmuGkVivd9hEREZdS6vBy5swZ9uzZU9xOTk5mw4YNhIaGEh0dzdixYxk+fDht27alffv2TJo0iezsbEaOHFmmhUsp7fwZvhpurgMU0xXu+gJ8A62uSkREpNRKHV7Wrl1Ljx49ittnR/oMHz6cyZMnM3jwYI4fP8748eNJSUkhPj6en3/++bxOvFKBNn0Fcx8ERxE0vgkGfAreflZXJSIickWuap4XZ/K/fV527dqleV7OWvMR/PAUYECrwXBbAnh6W12ViIjIOUozz4vbhJezSvPm3d7SiZD4+wR+7UbBjf/RAoYiIuKULJukTpyEYcCvz8PyN8x216eg53Ma2iwiIm5B4cXdOOzww1hImmy2b/g7XPuopSWJiIiUJYUXd1JUAHMegK2zARv0fQPaDLe6KhERkTLlNh0gEhISaNas2SWXEnBrBTkwY4gZXDy8YeCnCi4iIuKW1GHXHeRlmJPPHVwBXlVg8DRo2NvqqkREREpMHXYrk+wTMK2/uT6RbxDcPRPqdrK6KhERkXKj8OLKMo7A1H7mIov+YTB0NtSKs7oqERGRcqXw4qpO7oUp/SDjIATVgWFzIayh1VWJiIiUO4UXV5SyGab2h+w0qN4Ahs6FkCirqxIREakQGm3kag6ugsk3m8GlZksY+bOCi4iIVCoabeRK9i6AL4dAYQ5EdYS7Z0CVEKurEhERuWoabeSOtn0Ds+4DRyHU7wWDp4JPgNVViYiIVDi3uW3k1tZPg69GmMGlWT+460sFFxERqbQUXpzdynfgmzFgOKD1UBjwCXj5WF2ViIiIZXTbyFkZBiz6Fyz+l9nu9Ii5yKJWhhYRkUpO4cUZORwwbxyses9s93wOuj6l4CIiIoIb3TZym6HS9iLzNtHZ4HLjq9DtaQUXERGR32motDMpyodZ98KO78HmCf3egbg7ra5KRESk3GmotCvKPwMzhsC+ReDpAwMnQ5Obra5KRETE6Si8OIOcUzB9EBxeA94BcNcXUO86q6sSERFxSgovVstKham3Q9pWqFINhnwNddpYXZWIiIjTUnix0ukDMOU2OJ0MVWvC0DkQ0czqqkRERJyawotV0nbA1H6QdQxC6sKwuRBaz+qqREREnJ7CixWOrINpd0DuKajRBIbOhaBaVlclIiLiEjTPS0Xbvww+u9UMLpHXwMifFFxERERKQfO8VKRd82DmMCjKg5iu5qgi30CrqxIREbGc5nlxRptnwZwHwFEEjW4053Hx9rO6KhEREZfjNreNnNqaj+Hr+83g0nIQDJ6q4CIiInKFFF7K29KJ8MNYwIB2o+D298HT2+qqREREXJZuG5UXw4BfX4Dlk8x216fM1aG1wKKIiMhVUXgpDw47/PAkJH1qtq9/GTr/2dqaRERE3ITCS1mzF5odc7d8Ddig7yRoM8LiokRERNyHwktZKsiBr4bD7vng4Q39P4AW/a2uSkRExK0ovJSVvEz44k44sBy8qsDgadCwt9VViYiIuB2Fl7KQfQKm9YdjG8E3CO6eCXU7WV2ViIiIW3KbodKWLQ+QcQQ+vdEMLv5hMPw7BRcREZFypOUBrsbJvTClH2QchKA65srQYQ3L95giIiJuSMsDVISULTD1dshOg9D6MOwbCImyuioRERG3p/ByJQ6ths8HQF4GRLSEobOharjVVYmIiFQKCi+ltXcBfDkECnMgqoPZObdKiNVViYiIVBoKL6Wx7Vv4+j6wF0D9nuZwaJ8Aq6sSERGpVBReSmrzLJg9CgwHNLsN+n8IXr5WVyUiIlLpKLyUVM2W4BcCTW6Cvm+Ch6fVFYmIiFRKCi8lVaMxPLAYgqO0MrSIiIiFFF5KIyTa6gpEREQqPbeZYVdEREQqB4UXERERcSkKLyIiIuJSFF5ERETEpSi8iIiIiEtxm/CSkJBAs2bNaNeundWliIiISDmyGYZhWF1EWSrNktoiIiLiHErz+e02V15ERESkclB4EREREZei8CIiIiIuReFFREREXIrCi4iIiLgUhRcRERFxKW63qvTZkd+ZmZkWVyIiIiIldfZzuyQzuLhdeMnKygIgKirK4kpERESktLKysggODr7kPm43SZ3D4eDo0aMEBgZis9kuu3+7du1Ys2ZNudSSmZlJVFQUhw4d0oR5UiHK8+dZzqfz7T7nwBXehzPVWB61GIZBVlYWkZGReHhculeL21158fDwoE6dOiXe39PTs9yDRVBQkMKLVIiK+HmW/9L5dp9z4Arvw5lqLK9aLnfF5axK32F3zJgxVpcgUmb081yxdL7d5xy4wvtwphqtrsXtbhs5E62zJCIiUvYq/ZWX8uTr68vzzz+Pr6+v1aWIiIi4DV15EREREZeiKy8iIiLiUhReRERExKUovIiIiIhLUXgRkRI5dOgQ3bt3p1mzZrRq1YqvvvrK6pLcls61zkFFcsVzrQ67IlIix44dIzU1lfj4eFJSUmjTpg27du0iICDA6tLcjs61zkFFcsVzrSsvTsAVU69UPrVq1SI+Ph6AmjVrEhYWxqlTp6wtyk3pXOscVCRXPNcKL07Ay8uLSZMmsW3bNubPn8/jjz9Odna21WVJGXnllVdo164dgYGBhIeH069fP3bu3Fmmx1iyZAl9+/YlMjISm83G3LlzL7hfQkICMTEx+Pn50aFDB1avXn1Fx0tKSsJutzvdAqjvvvsurVq1Kl6So1OnTvz0009legxXOtf/+te/sNlsPP7441d07ItxpXNQ3o4cOcI999xD9erVqVKlCi1btmTt2rVl9vo61xem8OIEXDH1SsktXryYMWPG8Ntvv/HLL79QWFjIDTfccNGAunz5cgoLC8/bvm3bNlJTUy/4nOzsbOLi4khISLhoHTNmzGDs2LE8//zzrFu3jri4OPr06UNaWlrxPvHx8bRo0eK8r6NHjxbvc+rUKYYNG8YHH3xQ0lNQYerUqcO//vUvkpKSWLt2LT179uS2225j69atF9zfnc/1mjVreP/992nVqtUl93Pnc1DeTp8+TefOnfH29uann35i27ZtTJgwgWrVql1wf53rMmTIZS1evNi45ZZbjFq1ahmAMWfOnPP2efvtt426desavr6+Rvv27Y1Vq1Zd0bHWrl1rNG/e/CorFmeWlpZmAMbixYvPe8xutxtxcXHGgAEDjKKiouLtO3bsMCIiIox///vfl339i/2Mtm/f3hgzZsw5x4qMjDReeeWVEteel5dndO3a1ZgyZUqJn2O1atWqGR999NF52935XGdlZRkNGzY0fvnlF+O6664zHnvssQvu587noCL85S9/Mbp06VKifXWuy5auvJTA5ZJvpUy9csUyMjIACA0NPe8xDw8PfvzxR9avX8+wYcNwOBzs3buXnj170q9fP5555pkrOmZBQQFJSUn07t37nGP17t2blStXlug1DMNgxIgR9OzZk6FDh15RHRXJbrfz5Zdfkp2dTadOnc573J3P9ZgxY7j55pvPqeFC3PkcVIRvv/2Wtm3bMnDgQMLDw2ndujUffvjhBffVuS5j1mYn18MFkm9lTL1yZex2u3HzzTcbnTt3vuR+Bw4cMKKjo43Bgwcb0dHRxrBhwwyHw1GiY1zoZ/TIkSMGYKxYseKc7U8//bTRvn37Er3u0qVLDZvNZsTFxRV/bdq0qUTPrUibNm0yAgICDE9PTyM4ONj44YcfLrm/u53rL774wmjRooWRm5trGIZxySsvZ7nbOagovr6+hq+vrzFu3Dhj3bp1xvvvv2/4+fkZkydPvuhzdK7LhpdlqclNnE2948aNK95WKVKvXJExY8awZcsWli1bdsn9oqOjmTp1Ktdddx316tXj448/xmazVVCVF9alSxccDoelNZRE48aN2bBhAxkZGcyaNYvhw4ezePFimjVrdsH93elcHzp0iMcee4xffvkFPz+/Ej/Pnc5BRXI4HLRt25Z//vOfALRu3ZotW7bw3nvvMXz48As+R+e6bOi20VU6ceIEdrudiIiIc7ZHRESQkpJSotdYvnw5M2bMYO7cucTHxxMfH8/mzZvLo1yx0COPPML333/PwoULqVOnziX3TU1NZfTo0fTt25ecnByeeOKJqzp2WFgYnp6e53UKTE1NpWbNmlf12s7Gx8eHBg0a0KZNG1555RXi4uJ44403Lrq/O53rpKQk0tLSuOaaa/Dy8sLLy4vFixfz5ptv4uXlhd1uv+Dz3OkcVKRatWqdF4qbNm3KwYMHL/ocneuyofDiBM6m3g0bNhR/tWzZ0uqypIwYhsEjjzzCnDlzWLBgAbGxsZfc/8SJE/Tq1YumTZsye/ZsEhMTmTFjBk899dQV1+Dj40ObNm1ITEws3uZwOEhMTLxgfxB34nA4yM/Pv+Bj7naue/XqxebNm8/5W9K2bVuGDBnChg0b8PT0PO857nYOKlLnzp3Pm/Zg165d1K1b94L761yXIavvW7ka/nDPMT8/3/D09DzvPuSwYcOMW2+9tWKLE6f00EMPGcHBwcaiRYuMY8eOFX/l5OSct6/dbjfatm1r3HTTTUZ+fn7x9g0bNhihoaHGxIkTL3iMrKwsY/369cb69esNwJg4caKxfv1648CBA8X7fPnll4avr68xefJkY9u2bcbo0aONkJAQIyUlpezftEWeffZZY/HixUZycrKxadMm49lnnzVsNpsxf/788/atLOf6cqONKsM5KC+rV682vLy8jH/84x/G7t27jc8//9zw9/c3pk2bdt6+OtdlS+GllP4YXgzD7LD7yCOPFLftdrtRu3btUnXYFfcFXPDr008/veD+8+fPL+5s+b/WrVtnHDp06ILPWbhw4QWPMXz48HP2e+utt4zo6GjDx8fHaN++vfHbb79d7dtzKvfee69Rt25dw8fHx6hRo4bRq1evCwaXsyrDub5ch93KcA7K03fffWe0aNHC8PX1NZo0aWJ88MEHF91X57rsaG2jEjhz5gx79uwBzA5ZEydOpEePHoSGhhIdHc2MGTMYPnw477//Pu3bt2fSpEnMnDmTHTt2nNcXRkRERK6OwksJLFq0iB49epy3ffjw4UyePBmAt99+m1dffZWUlBTi4+N588036dChQwVXKiIi4v4UXkRERMSlaLSRiIiIuBSFFxEREXEpCi8iIiLiUhReRERExKUovIiIiIhLUXgRERERl6LwIiIiIi5F4UVERERcisKLiIiIuBSFFxEREXEpCi8iIiLiUhReRERExKX8P/huhP4Z0ROSAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "pp_list = [0.07, 0.05605161820417657, 0.04488262719009683, 0.035939198332281444, 0.028777860335503124, 0.02304350914655339, 0.018451799668100875, 0.014775046145376319, 0.011830932078424629, 0.00947346982656289]\n", - "gross = [0.01468388594008141, 0.00730116936108316, 0.003624729974722629, 0.001908233950801752, 0.0010434677566117934, 0.0005491556129465502, 0.00029966005248871497, 0.00017099405602716278, 6.835902874036126e-05, 4.584489121428614e-05]\n", - "code_144_8 = [0.01986802811443611, 0.00698988488192942, 0.0023225422652949357, 0.0007444367661358919, 0.0002677507823637404, 6.50147923076938e-05, 3.2503697475871896e-05, 1.125044299365996e-05, 8.75026798052847e-06, 2.5000218752957437e-06]\n", + "pp_list = [\n", + " 0.07,\n", + " 0.05605161820417657,\n", + " 0.04488262719009683,\n", + " 0.035939198332281444,\n", + " 0.028777860335503124,\n", + " 0.02304350914655339,\n", + " 0.018451799668100875,\n", + " 0.014775046145376319,\n", + " 0.011830932078424629,\n", + " 0.00947346982656289,\n", + "]\n", + "gross = [\n", + " 0.01468388594008141,\n", + " 0.00730116936108316,\n", + " 0.003624729974722629,\n", + " 0.001908233950801752,\n", + " 0.0010434677566117934,\n", + " 0.0005491556129465502,\n", + " 0.00029966005248871497,\n", + " 0.00017099405602716278,\n", + " 6.835902874036126e-05,\n", + " 4.584489121428614e-05,\n", + "]\n", + "code_144_8 = [\n", + " 0.01986802811443611,\n", + " 0.00698988488192942,\n", + " 0.0023225422652949357,\n", + " 0.0007444367661358919,\n", + " 0.0002677507823637404,\n", + " 6.50147923076938e-05,\n", + " 3.2503697475871896e-05,\n", + " 1.125044299365996e-05,\n", + " 8.75026798052847e-06,\n", + " 2.5000218752957437e-06,\n", + "]\n", "# These data was calculated from 100_000 Monte-Carlo decodings, the standard error will be sqrt(wer(1-wer)/100_000)\n", - "import matplotlib.pyplot as plt\n", - "plt.plot(pp_list,gross,label='gross')\n", - "plt.plot(pp_list,code_144_8,label='144,8')\n", - "plt.yscale('log')\n", - "plt.xscale('log')\n", + "\n", + "plt.plot(pp_list, gross, label=\"gross\")\n", + "plt.plot(pp_list, code_144_8, label=\"144,8\")\n", + "plt.yscale(\"log\")\n", + "plt.xscale(\"log\")\n", "plt.legend()\n", "plt.show()" ] }, { "cell_type": "markdown", - "id": "f1293b7b", + "id": "18", "metadata": {}, "source": [ "Figure 9\n", @@ -702,32 +329,69 @@ { "cell_type": "code", "execution_count": null, - "id": "15bf1845", + "id": "19", "metadata": {}, "outputs": [], "source": [ "l = 12\n", "g = 6\n", - "para_dict = {'l':l,'g':g}\n", - "code_class = 'bb'\n", - "import numpy as np\n", + "para_dict = {\"l\": l, \"g\": g}\n", + "code_class = \"bb\"\n", + "pp = 0.05\n", "from code_construction.code_construction import CodeConstructor\n", - "code_constructor = CodeConstructor(method=code_class,para_dict = para_dict)\n", - "x = [0., 1., 1., 0., 1., 1., 0., 1., 0., 0., 0., 1., 0., 0., 0., 1., 1., 1.,\n", - " 1., 1., 0., 0., 0., 0., 0., 0., 1., 1., 1., 0., 1., 0., 0., 1.] # Let x be the best_x we have\n", + "\n", + "code_constructor = CodeConstructor(method=code_class, para_dict=para_dict)\n", + "x = [\n", + " 0.0,\n", + " 1.0,\n", + " 1.0,\n", + " 0.0,\n", + " 1.0,\n", + " 1.0,\n", + " 0.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 1.0,\n", + " 1.0,\n", + " 1.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 1.0,\n", + " 1.0,\n", + " 0.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + "] # Let x be the best_x we have\n", "c = code_constructor.construct(x)\n", - "psuedo_t = ObjectiveFunction(code_constructor, pp=pp,decoder_param={'trail': 100000}).psuedo_t\n", + "psuedo_t = ObjectiveFunction(\n", + " code_constructor, pp=pp, decoder_param={\"trail\": 100000}\n", + ").psuedo_t\n", "\n", "# this function is used to calculate psuedo_t\n", "print(psuedo_t(c))\n", "# code rate k/n\n", - "print(c.k/144)" + "print(c.k / 144)" ] }, { "cell_type": "code", "execution_count": null, - "id": "21f12a88", + "id": "20", "metadata": {}, "outputs": [], "source": [ @@ -739,9 +403,17 @@ "# 11*11surface code, psuedo t:12.741790221308744\n", "# 13*13surface code, psuedo t:17.228540816991853\n", "# 15*15surface code, psuedo t:22.092362338524666\n", - "dis = [3,5,7,9,11,13,15]\n", - "code_rates = [1/i**2 for i in dis]\n", - "surfacecodes_psuedot = [1.240409037092388,3.168288067483846,5.806713473577745,9.007246610022946,12.741790221308744,17.228540816991853,22.092362338524666]\n", + "dis = [3, 5, 7, 9, 11, 13, 15]\n", + "code_rates = [1 / i**2 for i in dis]\n", + "surfacecodes_psuedot = [\n", + " 1.240409037092388,\n", + " 3.168288067483846,\n", + " 5.806713473577745,\n", + " 9.007246610022946,\n", + " 12.741790221308744,\n", + " 17.228540816991853,\n", + " 22.092362338524666,\n", + "]\n", "\n", "# plt.scatter([26/144],[8.280255381569452/144],color='#f77a66',label='[[144,26]] bb code we discoverd')\n", "# plt.scatter([8/144],[11.450956649650138/144],color=\"#f7f766\",label='[[144,8]] bb code we discoverd')\n", @@ -753,77 +425,53 @@ { "cell_type": "code", "execution_count": null, - "id": "cca8ae5d", + "id": "21", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "n=144 non-degenerate CSS Hamming bound: code rates:[0.7847222222222222, 0.7430555555555556, 0.7013888888888888, 0.6597222222222222, 0.6180555555555556, 0.5833333333333334, 0.5416666666666666, 0.5069444444444444, 0.4722222222222222, 0.4375, 0.4027777777777778, 0.3680555555555556, 0.3402777777777778, 0.3055555555555556, 0.2777777777777778, 0.25, 0.2152777777777778, 0.1875, 0.1597222222222222, 0.13194444444444445, 0.10416666666666667, 0.0763888888888889, 0.05555555555555555, 0.027777777777777776, 0.0], psuedo t//n:[0.034722222222222224, 0.041666666666666664, 0.04861111111111111, 0.05555555555555555, 0.0625, 0.06944444444444445, 0.0763888888888889, 0.08333333333333333, 0.09027777777777778, 0.09722222222222222, 0.10416666666666667, 0.1111111111111111, 0.11805555555555555, 0.125, 0.13194444444444445, 0.1388888888888889, 0.14583333333333334, 0.1527777777777778, 0.1597222222222222, 0.16666666666666666, 0.1736111111111111, 0.18055555555555555, 0.1875, 0.19444444444444445, 0.2013888888888889]\n", - "n=72 non-degenerate CSS Hamming bound: code rates:[0.7916666666666666, 0.7083333333333334, 0.625, 0.5555555555555556, 0.4861111111111111, 0.4166666666666667, 0.3472222222222222, 0.2916666666666667, 0.2361111111111111, 0.18055555555555555, 0.125, 0.06944444444444445, 0.013888888888888888], psuedo t//n:[0.041666666666666664, 0.05555555555555555, 0.06944444444444445, 0.08333333333333333, 0.09722222222222222, 0.1111111111111111, 0.125, 0.1388888888888889, 0.1527777777777778, 0.16666666666666666, 0.18055555555555555, 0.19444444444444445, 0.20833333333333334]\n", - "n=36 non-degenerate CSS Hamming bound: code rates:[0.6388888888888888, 0.5, 0.3611111111111111, 0.25, 0.1388888888888889, 0.027777777777777776], psuedo t//n:[0.08333333333333333, 0.1111111111111111, 0.1388888888888889, 0.16666666666666666, 0.19444444444444445, 0.2222222222222222]\n", - "n=288 non-degenerate CSS Hamming bound: code rates:[0.78125, 0.7569444444444444, 0.7361111111111112, 0.7152777777777778, 0.6944444444444444, 0.6736111111111112, 0.6527777777777778, 0.6319444444444444, 0.6111111111111112, 0.59375, 0.5729166666666666, 0.5555555555555556, 0.5347222222222222, 0.5173611111111112, 0.5, 0.4826388888888889, 0.4652777777777778, 0.4479166666666667, 0.4305555555555556, 0.4131944444444444, 0.3958333333333333, 0.3784722222222222, 0.3645833333333333, 0.3472222222222222, 0.3333333333333333, 0.3159722222222222, 0.2986111111111111, 0.2847222222222222, 0.2708333333333333, 0.2534722222222222, 0.23958333333333334, 0.22569444444444445, 0.20833333333333334, 0.19444444444444445, 0.18055555555555555, 0.16666666666666666, 0.1527777777777778, 0.1388888888888889, 0.125, 0.1111111111111111, 0.09722222222222222, 0.08333333333333333, 0.06944444444444445, 0.059027777777777776, 0.04513888888888889, 0.03125, 0.017361111111111112, 0.006944444444444444], psuedo t//n:[0.03125, 0.034722222222222224, 0.03819444444444445, 0.041666666666666664, 0.04513888888888889, 0.04861111111111111, 0.052083333333333336, 0.05555555555555555, 0.059027777777777776, 0.0625, 0.06597222222222222, 0.06944444444444445, 0.07291666666666667, 0.0763888888888889, 0.0798611111111111, 0.08333333333333333, 0.08680555555555555, 0.09027777777777778, 0.09375, 0.09722222222222222, 0.10069444444444445, 0.10416666666666667, 0.1076388888888889, 0.1111111111111111, 0.11458333333333333, 0.11805555555555555, 0.12152777777777778, 0.125, 0.1284722222222222, 0.13194444444444445, 0.13541666666666666, 0.1388888888888889, 0.1423611111111111, 0.14583333333333334, 0.14930555555555555, 0.1527777777777778, 0.15625, 0.1597222222222222, 0.16319444444444445, 0.16666666666666666, 0.1701388888888889, 0.1736111111111111, 0.17708333333333334, 0.18055555555555555, 0.1840277777777778, 0.1875, 0.1909722222222222, 0.19444444444444445]\n", - "n=576 non-degenerate CSS Hamming bound: code rates:[0.7899305555555556, 0.7777777777777778, 0.765625, 0.7552083333333334, 0.7430555555555556, 0.7326388888888888, 0.7222222222222222, 0.7100694444444444, 0.6996527777777778, 0.6892361111111112, 0.6788194444444444, 0.6684027777777778, 0.6579861111111112, 0.6475694444444444, 0.6371527777777778, 0.6284722222222222, 0.6180555555555556, 0.6076388888888888, 0.5989583333333334, 0.5885416666666666, 0.578125, 0.5694444444444444, 0.5607638888888888, 0.5503472222222222, 0.5416666666666666, 0.53125, 0.5225694444444444, 0.5138888888888888, 0.5052083333333334, 0.4947916666666667, 0.4861111111111111, 0.4774305555555556, 0.46875, 0.4600694444444444, 0.4513888888888889, 0.4427083333333333, 0.4340277777777778, 0.4253472222222222, 0.4166666666666667, 0.4079861111111111, 0.4010416666666667, 0.3923611111111111, 0.3836805555555556, 0.375, 0.3680555555555556, 0.359375, 0.3506944444444444, 0.34375, 0.3350694444444444, 0.3263888888888889, 0.3194444444444444, 0.3107638888888889, 0.3038194444444444, 0.2951388888888889, 0.2881944444444444, 0.2795138888888889, 0.2725694444444444, 0.265625, 0.2569444444444444, 0.25, 0.24131944444444445, 0.234375, 0.22743055555555555, 0.2204861111111111, 0.21180555555555555, 0.2048611111111111, 0.19791666666666666, 0.1909722222222222, 0.1840277777777778, 0.1753472222222222, 0.1684027777777778, 0.16145833333333334, 0.1545138888888889, 0.14756944444444445, 0.140625, 0.13368055555555555, 0.1267361111111111, 0.11979166666666667, 0.11284722222222222, 0.10590277777777778, 0.09895833333333333, 0.0920138888888889, 0.08506944444444445, 0.0798611111111111, 0.07291666666666667, 0.06597222222222222, 0.059027777777777776, 0.052083333333333336, 0.04513888888888889, 0.03993055555555555, 0.03298611111111111, 0.026041666666666668, 0.019097222222222224, 0.013888888888888888, 0.006944444444444444, 0.0], psuedo t//n:[0.027777777777777776, 0.029513888888888888, 0.03125, 0.03298611111111111, 0.034722222222222224, 0.036458333333333336, 0.03819444444444445, 0.03993055555555555, 0.041666666666666664, 0.043402777777777776, 0.04513888888888889, 0.046875, 0.04861111111111111, 0.050347222222222224, 0.052083333333333336, 0.05381944444444445, 0.05555555555555555, 0.057291666666666664, 0.059027777777777776, 0.06076388888888889, 0.0625, 0.0642361111111111, 0.06597222222222222, 0.06770833333333333, 0.06944444444444445, 0.07118055555555555, 0.07291666666666667, 0.07465277777777778, 0.0763888888888889, 0.078125, 0.0798611111111111, 0.08159722222222222, 0.08333333333333333, 0.08506944444444445, 0.08680555555555555, 0.08854166666666667, 0.09027777777777778, 0.0920138888888889, 0.09375, 0.0954861111111111, 0.09722222222222222, 0.09895833333333333, 0.10069444444444445, 0.10243055555555555, 0.10416666666666667, 0.10590277777777778, 0.1076388888888889, 0.109375, 0.1111111111111111, 0.11284722222222222, 0.11458333333333333, 0.11631944444444445, 0.11805555555555555, 0.11979166666666667, 0.12152777777777778, 0.1232638888888889, 0.125, 0.1267361111111111, 0.1284722222222222, 0.13020833333333334, 0.13194444444444445, 0.13368055555555555, 0.13541666666666666, 0.1371527777777778, 0.1388888888888889, 0.140625, 0.1423611111111111, 0.1440972222222222, 0.14583333333333334, 0.14756944444444445, 0.14930555555555555, 0.15104166666666666, 0.1527777777777778, 0.1545138888888889, 0.15625, 0.1579861111111111, 0.1597222222222222, 0.16145833333333334, 0.16319444444444445, 0.16493055555555555, 0.16666666666666666, 0.1684027777777778, 0.1701388888888889, 0.171875, 0.1736111111111111, 0.1753472222222222, 0.17708333333333334, 0.17881944444444445, 0.18055555555555555, 0.18229166666666666, 0.1840277777777778, 0.1857638888888889, 0.1875, 0.1892361111111111, 0.1909722222222222, 0.19270833333333334]\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_340170/1970098735.py:13: RuntimeWarning: overflow encountered in multiply\n", - " s += comb(n, i)* (3**i)\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjUAAAGdCAYAAADqsoKGAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAAC8MElEQVR4nOzdZ3RUZff38e/MZCa9hyQkBELovRN6kUhTFKUogiAidpQOoYpU6SJgQRFUQEAUpYggUqUTegm9pRfSy7TzvJj75n74O0ECM07A/Vlr1vJMJtfZyVqan+cqW6UoioIQQgghxCNO7egChBBCCCFsQUKNEEIIIR4LEmqEEEII8ViQUCOEEEKIx4KEGiGEEEI8FiTUCCGEEOKxIKFGCCGEEI8FCTVCCCGEeCw4ObqAf4rZbCY+Ph5PT09UKpWjyxFCCCHEfVAUhezsbEJCQlCr7/0s5l8TauLj4wkLC3N0GUIIIYR4ADdv3qRMmTL3/My/JtR4enoCll+Kl5eXg6sRQgghxP3IysoiLCzszt/xe/nXhJr/Tjl5eXlJqBFCCCEeMfezdEQWCgshhBDisSChRgghhBCPBQk1QgghhHgsSKgRQgghxGPhgULNokWLCA8Px8XFhcjISA4dOlTkZ5csWULLli3x9fXF19eXqKiouz5vMBgYNWoUtWrVwt3dnZCQEPr27Ut8fPxd44SHh6NSqe56zZgx40HKF0IIIcRjqNihZvXq1QwdOpSJEycSExNDnTp16NChA8nJyVY/v3PnTnr16sWOHTvYv38/YWFhtG/fnri4OADy8vKIiYlh/PjxxMTE8OOPPxIbG8szzzzzl7E+/PBDEhIS7rwGDRpU3PKFEEII8ZhSKYqiFOcbIiMjadSoEQsXLgQsJ/WGhYUxaNAgRo8e/bffbzKZ8PX1ZeHChfTt29fqZw4fPkzjxo25fv06ZcuWBSxPagYPHszgwYOLU+4dWVlZeHt7k5mZKVu6hRBCiEdEcf5+F+tJjV6v5+jRo0RFRf1vALWaqKgo9u/ff19j5OXlYTAY8PPzK/IzmZmZqFQqfHx87np/xowZ+Pv7U69ePWbNmoXRaCxO+UIIIYR4jBXr8L3U1FRMJhNBQUF3vR8UFMT58+fva4xRo0YREhJyVzD6/xUUFDBq1Ch69ep1VyJ77733qF+/Pn5+fuzbt4/o6GgSEhKYO3eu1XEKCwspLCy8c52VlXVf9QkhhBDi0fSPnig8Y8YMvv/+e3bu3ImLi8tfvm4wGOjZsyeKovDpp5/e9bWhQ4fe+efatWuj0+l44403mD59Os7Ozn8Za/r06UyaNMn2P4QQQgghSqRiTT8FBASg0WhISkq66/2kpCSCg4Pv+b2zZ89mxowZbN26ldq1a//l6/8NNNevX2fbtm1/O28WGRmJ0Wjk2rVrVr8eHR1NZmbmndfNmzfv/cMJIYQQ4pFWrFCj0+lo0KAB27dvv/Oe2Wxm+/btNG3atMjvmzlzJpMnT2bLli00bNjwL1//b6C5ePEiv//+O/7+/n9by/Hjx1Gr1QQGBlr9urOz850+T9LvSQghhHj8FXv6aejQofTr14+GDRvSuHFj5s+fT25uLv379wegb9++hIaGMn36dAA++ugjJkyYwMqVKwkPDycxMREADw8PPDw8MBgMdO/enZiYGDZu3IjJZLrzGT8/P3Q6Hfv37+fgwYO0bdsWT09P9u/fz5AhQ+jTpw++vr62+l04RFp+GiN3j2RU41FU9q3s6HKEEEKIR5fyAD755BOlbNmyik6nUxo3bqwcOHDgztdat26t9OvX7851uXLlFOAvr4kTJyqKoihXr161+nVA2bFjh6IoinL06FElMjJS8fb2VlxcXJRq1aop06ZNUwoKCu675szMTAVQMjMzH+RHtpvo3dFKzWU1lYbfNlR+vPCjo8sRQgghSpTi/P0u9jk1j6qSek7N7YLbRO+N5s+4PwF4tsKzjG0yFlcnVwdXJoQQQjie3c6pEbbn6+LL4naLea/ee6hVan6+/DMvbXqJK5lXHF2aEEII8UiRUFMCqFVqBtYeyJInl+Dv4s+ljEv02tiLzVc2O7o0IYQQ4pEhoaYEaVy6MT888wONghuRZ8xj1J5RTDkwhUJT4d9/sxBCCPEvJ6GmhAlwDWDJk0t4vfbrAKyOXc3Lm1/mZracsyOEEELci4SaEkij1jCo3iA+jfoUH2cfzqWf44UNL7D9+va//2YhhBDiX0pCTQnWIrQFa7uspW6pumQbshm8czCzDs/CYDY4ujQhhBCixJFQU8IFuweztONS+lXvB8A3Z7+h/5b+JOYmOrgyIYQQomSRUPMI0Kq1DG80nI/bfoyn1pMTKSfosaEHe27tcXRpQgghRIkhoeYR8kTZJ1jdZTXV/auTUZjB29vfZkHMAoxmo6NLE0IIIRxOQs0jJswzjG87fcuLVV4EYMmpJby+7XVS8lIcXJkQQgjhWBJqHkE6jY6xTcYyq9Us3JzcOJx4mB4benAo4ZCjSxNCCCEcRkLNI6xj+Y58//T3VPKtRFpBGgO3DeTzE59jVsyOLk0IIYT4x0moecSV9y7Pis4reK7ic5gVMwuPL+Tt398mvSDd0aUJIYQQ/ygJNY8BVydXPmz+IZObT8ZF48Kf8X/SY0MPjiUfc3RpQgghxD9GQs1jpGvFrqx8aiXhXuEk5yXTf0t/lp1ehqIoji5NCCGEsDsJNY+ZSr6V+P7p7+lUvhMmxcSco3N4b8d7ZBZmOro0IYQQwq4k1DyG3LXufNTyI8Y3GY9WrWXnzZ28sPEFzqSecXRpQgghhN1IqHlMqVQqelbpyXedv6OMRxnicuJ4+deXWXV+lUxHCSGEeCxJqHnMVfevzuouq2lXth0Gs4FpB6cxYvcIcvQ5ji5NCCGEsCkJNf8CXjov5rWZx8hGI3FSOfHbtd94cdOLxKbHOro0IYQQwmYk1PxLqFQqXq7+Ml93/JogtyCuZ12n9+be/HTxJ5mOEkII8ViQUGNvhdnwzbNw46CjKwGgbmBd1nZZS4vQFhSaCpmwbwLj/hxHniHP0aUJIYQQD0VCjb3t+giu7IRlnWHfQigBT0V8XXxZ1G4R79d/H7VKzS+Xf6H35t5cybji6NKEEEKIByahxt5aj4Ka3cBshK1j4fvekJ/h6KpQq9S8Vus1vmz/JQGuAVzKuMSLm15k05VNji5NCCGEeCASauzN2RO6fQVPzQGNDmI3weetIL5ktDBoFNyItV3WEhkcSb4xn9F7RjN5/2QKTYWOLk0IIYQoFgk1/wSVChq9BgO2gk85yLgOX7WHQ0tKxHRUgGsAnz/5OW/UfgMVKtZcWMPLm1/mZtZNR5cmhBBC3DcJNf+kkHrwxm6o+jSY9LB5OKwbYFlM7GAatYZ3673Lp1Gf4uvsy7n0c/Tc2JPfr//u6NKEEEKI+yKh5p/m6gMvfAftp4LaCU6vgy/aQFLJaGHQPLQ5a7qsoV5gPXIMOQzZOYSPDn2EwWRwdGlCCCHEPUmocQSVCpq9C69sBq9QSLsES56AY985ujIAgt2D+arDV/Sv0R+A7859xytbXiEhJ8HBlQkhhBBFk1DjSGUj4Y09UDEKjAXw8zuw/m3QO/7MGK1ay9CGQ1nQdgGeOk9Opp6kx8Ye7L6129GlCSGEEFZJqHE0d394aS08MR5Uaji+Ar5sBykXHF0ZAG3LtmXN02uo4V+DzMJM3tn+Dh/HfIzRbHR0aUIIIcRdJNSUBGo1tBoOfX8G90BIPgtL2sKpHxxdGQBlPMvwTadv6FW1FwBfnvqS17a+RnJesoMrE0IIIf5HQk1JUr4VvLkXwluCPseyM2rjUDAUOLoydBodYyLHMKv1LNy17hxNOkqPDT04kHDA0aUJIYQQwAOGmkWLFhEeHo6LiwuRkZEcOnSoyM8uWbKEli1b4uvri6+vL1FRUX/5vKIoTJgwgdKlS+Pq6kpUVBQXL1686zPp6en07t0bLy8vfHx8GDBgADk5OQ9Svk3pjWYGLDvMzlgbPbXwDLI8sWk1wnJ95CtY2h7Sr9pm/IfUMbwj3z/1PZV9K5NekM7rW1/n0xOfYjKbHF2aEEKIf7lih5rVq1czdOhQJk6cSExMDHXq1KFDhw4kJ1v/o75z50569erFjh072L9/P2FhYbRv3564uLg7n5k5cyYLFizgs88+4+DBg7i7u9OhQwcKCv73hKJ3796cOXOGbdu2sXHjRnbv3s3rr7/+AD+ybS3bd5Xt55Ppv+wws3+LxWgyP/ygag08MQ56rwNXP0g4AZ+3hnMbHn5sGwj3DmdF5xU8X+l5FBQWH1/M29vfJr0g3dGlCSGE+BdTKUrxjrSNjIykUaNGLFy4EACz2UxYWBiDBg1i9OjRf/v9JpMJX19fFi5cSN++fVEUhZCQEIYNG8bw4cMByMzMJCgoiGXLlvHiiy9y7tw5qlevzuHDh2nYsCEAW7ZsoXPnzty6dYuQkJC/vW9WVhbe3t5kZmbi5eVVnB/5ngoMJqZuOse3B64D0CTCjwUv1iPQy8U2N8i8BT+8Cjf/0+W76bsQ9QFotLYZ/yH9fOlnphyYQoGpgEC3QGa1mkX9oPqOLksIIcRjojh/v4v1pEav13P06FGioqL+N4BaTVRUFPv377+vMfLy8jAYDPj5+QFw9epVEhMT7xrT29ubyMjIO2Pu378fHx+fO4EGICoqCrVazcGDB63ep7CwkKysrLte9uCi1TC5a00W9KqHu07DgSvpdF6wl32XU21zA+8y8MomS5gB2L8Qvu5sCTslwLMVn2XlUysp712e5LxkXv3tVb4+/TXFzMpCCCHEQytWqElNTcVkMhEUFHTX+0FBQSQmJt7XGKNGjSIkJOROiPnv991rzMTERAIDA+/6upOTE35+fkXed/r06Xh7e995hYWF3Vd9D+qZOiH8MqgFVYI8Sc0ppM+XB1n4x0XMZhv8cddoocNUeHEluHjDrUPwWUu4uO3hx7aBSr6V+P6p7+lcvjMmxcTco3N574/3yCzMdHRpQggh/kX+0d1PM2bM4Pvvv+enn37CxcVG0zNFiI6OJjMz887r5k37N2esUMqD9e80p0eDMpgVmL31Aq8sO0x6rt42N6j6lKV3VOm6kJ8OK7rD9g/B5PgzY9y0bsxoOYMJTSegU+vYeWsnPTf05HTqaUeXJoQQ4l+iWKEmICAAjUZDUlLSXe8nJSURHBx8z++dPXs2M2bMYOvWrdSuXfvO+//9vnuNGRwc/JeFyEajkfT09CLv6+zsjJeX112vf4KrTsOsHnWY2b02Llo1uy+k8NSCPRy9bqNFtL7hlm7fjQZarvfMgW+7Qvb9PSmzJ5VKRY/KPfiu83eEeYYRnxvPy7++zMpzK2U6SgghhN0VK9TodDoaNGjA9u3b77xnNpvZvn07TZs2LfL7Zs6cyeTJk9myZctd62IAypcvT3Bw8F1jZmVlcfDgwTtjNm3alIyMDI4ePXrnM3/88Qdms5nIyMji/Aj/mJ4Nw1j/TnMiSrmTkFnAC58fYMnuK7b54+7kDE/Nhu5LQecB1/ZYpqOu7Hr4sW2gmn81Vj+9mqiyURjNRqYfms7wXcPJ0Tt+C74QQojHV7F3P61evZp+/frx+eef07hxY+bPn8+aNWs4f/48QUFB9O3bl9DQUKZPnw7ARx99xIQJE1i5ciXNmze/M46HhwceHh53PjNjxgyWL19O+fLlGT9+PCdPnuTs2bN3pqk6depEUlISn332GQaDgf79+9OwYUNWrlx5X3Xba/fT38kpNBL94yk2nIgH4MnqQczuXgdvNxvtXkq9CGv6QfIZS5uFNtHQcrjllGIHUxSFFedWMOfIHIyKkXJe5ZjTeg5V/Ko4ujQhhBCPiGL9/VYewCeffKKULVtW0el0SuPGjZUDBw7c+Vrr1q2Vfv363bkuV66cAvzlNXHixDufMZvNyvjx45WgoCDF2dlZadeunRIbG3vXPdPS0pRevXopHh4eipeXl9K/f38lOzv7vmvOzMxUACUzM/NBfuSHYjablW/2X1MqjdmslBu1UWnx0Xbl5M0M292gMFdR1r+tKBO9LK9vnlOUnBTbjf+QjicfV6LWRik1l9VUGnzbQPkh9gfFbDY7uiwhhBCPgOL8/S72k5pHlaOe1Pz/Tt3K5O2VR7mZno9Oo2b809Xo06QcKpXKNjc4tgI2DQNjPniGQI+voWwT24z9kDIKMhizdwx74vYA0CWiC+OajMNN6+bgyoQQQpRkxfn7LaHmH5aZb2DE2hNsPWtZGP107dLM6FYbD2cn29wg6Sys6QtpF0GlsRzU12wQ2Co4PQSzYmbp6aV8cuwTzIqZCt4VmNtmLhE+EY4uTQghRAllt8P3xN8z5+dz8823yC3iMEJvVy2fv9yAcU9Vw0mtYuPJBJ75ZC/nEmx0OGBQdXh9B9TsDooJto2H71+C/Nu2Gf8hqFVqXqv1Gl+2/5JSrqW4nHmZFze9yMYrGx1dmhBCiMeAhBobS1vyJTk7d3LzrbfJPWD9tGOVSsVrLSNY/UZTSnu7cCU1l66L/mTNERudpePsCd2+hKfmgkYHsZvh81YQF2Ob8R9So+BGrOmyhsjSkeQb84neE82k/ZMoNBU6ujQhhBCPMAk1Nub/5hu4t26FUlDAzbfeIvceHcwblPNl03staVOlFIVGMyN/OMnwtSfI19ug47VKBY0GwIBtlrNtMm7A0g5waAmUgBnHANcAPo/6nDfrvIkKFT9c+IE+m/twI+uGo0sTQgjxiJJQ85BMRjObFp3g+pk0ANQ6HWUWLMC9ZUuU/0xF5R05UuT3+7nrWNqvESM6VEGtgh+O3qLroj+5lGyjM11C6sLru6Dq02DSw+bh8EN/KLBPL6zi0Kg1vFP3HT6L+gxfZ1/Op5/nhY0vsO16yWj/IIQQ4tEiC4Uf0rGtN9j34yVQQYOO5Wj8dHnUGjXmwkJuvfU2ufv2oXZzI+zLJbjVv3f36v2X03jv+2OkZBfiptMw/flaPFs31DaFKgoc+NSyxsZsBL8K0PMbCK5pm/EfUlJuEiN3jyQm2TJF1qdaH4Y2GIq2hHQjF0II4Riy+8kKe4Uao8HEnz9c4vSuOABCK/vw5IAauHs7Y/7PFFTe/gOo3d0p+9WXuNate8/xkrMLeH/VcfZfsTz56R1ZlvFPV8dFq7FNwTcPw9pXIOsWOLlA59lQr0+J2B1lMBv45NgnfH36awBqBdRiduvZhHiEOLgyIYQQjiKhxgp7b+m+eDiJHd+dx1BowtVLR/sBNShTxffObqi8gwdRe3gQ9sXnf/vExmRW+Pj3C3yy4xKKAjVDvVj8UgPK+tvoTJe8dPjxdbj0n2meOi9Z2i7o3G0z/kPaeXMnY/eOJUufhZfOi+ktp9OqTCtHlyWEEMIBJNRY8U+cU3M7MZctX5wmPT4XlQoad4mgQcdyKAX53HzjTfIOHwatlqARw/F9+eW/PXRv14UUhqw+TnquHk8XJ2Z1r0PHmvduHHrfzGb4cx78MQUUM5SqBj2XQ6mS0cIgLieO4TuHczrN0uV7QM0BvFvvXZzUNjrPRwghxCNBQo0V/9Thewa9id3fX+D8vgQAylb3I+rV6jirDMSPGUv2b78B4Nm+PaWnTkHj6XnP8RIy83l35TGOXrecMzOgRXlGdayKzslGa7yv7YUfXoWcJNC6Q5ePoXYP24z9kPQmPXOOzGHleUt/r/qB9ZnVehaBboEOrkwIIcQ/RUKNFf/0icLn9sWze9UFjAYz7j7OdHqjFoHhntz+bgVJM2eCwYC2bFnKzJ+HS/Xq9xzLYDIz67dYvth9BYB6ZX1Y+FJ9Qn1cbVNsTjKsGwBXd1uuG/SHjjNA62Kb8R/Sb9d+Y+K+ieQacvFz8WNGyxk0DSm6K7wQQojHh4QaKxzRJiEtLoctX5wmIykPrYuGZ96rS3CEN/knTxI3eAiG+HhUOh1BY8fi07PH305HbT2TyPC1J8gqMOLjpmXeC3VpW8VGTy3MJtg5A3bPAhQIrm2ZjvIrGS0MrmddZ9jOYcTejkWFirfqvMXrtV9Ho7bRAmohhBAlkoQaKxzV+0lfYGTzpyeJi81A56LhmffrEVTeC1NGBvGjo8nZuRMAr2e6UHriRNTu916sezM9j3dWxnDyViYA77StwJCoyjhpbDQddel3yyLivDRw9oJnF0H1Z2wz9kMqMBYw49AM1l1cB0DT0k2Z3nI6/q7+Dq5MCCGEvUioscKRDS0NhSY2LjxB/MUMdK5OdBlUh+AIbxSzmfSlS0meNx9MJnQVKlBm/jycK1W653iFRhPTNp1j+f7rAESW92Nx7/r4ezjbpuDMOMs6m5sHLNdN3oaoSeCks834D2nD5Q1MPjCZfGM+ga6BzGw9kwZBDRxdlhBCCDuQUGOFo7t06wuMbFx4goRLmag1Kpp3r0itNmVQqVTkHTlC3NBhGJOTUbm6UvqDiXg/++zfjrnhRDyj150kV2+iarAnKwc2wc/dRsHDZIDtH8K+BZbr0IbQYxn4hNlm/Id06fYlhu0axpXMK2hUGt6r/x6v1HgFtUoOyRZCiMeJhBorHB1qwBJsti8/x5VjKQBUqB/IEy9XRefqhDEtjfgRI8jdZ+nu7dOjO0Fjx6J2ufdi3UvJ2by05CDJ2YVUDfZk1cAm+Noq2ACc3wzr34SCTHD1hee+gMrtbTf+Q8gz5PHhgQ/ZdGUTAK3LtGZqi6l4O3s7uDIhhBC2IqHGipIQagAUReHkH7fYt+4SZrOCdylXOr5Rk4AynigmE6mffkbqokWgKDhXrUqZ+fPQhYffc8xLyTm8+MUBUnMKqV7ai5UDI/Fxs2GwuX3Ncgpx/DHLdYuh0HYsaBx/ZoyiKKy7uI7pB6ejN+sJcQ9hduvZ1CpVy9GlCSGEsAEJNVaUlFDzX4lXMvltyWlybhei0app9WJlqjUrjUqlInffPuKGj8CUno7a3Z3SU6fg1bHjPce7lJz9n2Cjp2aoFysGNMHbzYZ9k4yFsHUcHPrCcl2uOXT7CrxK2+4eD+F8+nmG7hzKzeybOKmdGN5wOC9Vfelvd5QJIYQo2STUWFHSQg1AQY6B35ed5fppS5+nKk2Cad2rClpnDYakZOKGDSX/yFEAfPv0IXDkCNS6op/AXEjKptcXB0jL1VO7jDffDojE29XGDSFP/wi/vAf6bHAvBd2+hIg2tr3HA8rWZzNx38Q7Xb6fLPckk5pNwlN37wMOhRBClFwSaqwoiaEGQDErxGy9zsGfr6Ao4FvanY6v18SvtDuK0UjKxwtIW7IEAJdatQidNw9dmaI7d8cmZtNryQHSc/XUKePNt69F4uVi42CTegnW9oOk04AK2kRDq+FQAs6MURSFledXMvvIbIxmI2U9yzKnzRyq+lV1dGlCCCEegIQaK0pqqPmvuAu32frVGfIy9Tg5a2jzUhWqRFr6PGXv3En8qNGYMzNRe3sTMn06nk+0LXKscwlZvLTkALfzDNQr68M3rzbG09bBxpAPv46EmG8s1xWegOeXgHuAbe/zgE6mnGT4ruEk5CagU+uIjoymW6VuMh0lhBCPGAk1VpT0UAOQl6Vn29Iz3Dpv6fNUvWUILXtWwkmrwRAXx62hQyk4cRIAvwGvEjh4MCqt9bByJj6T3l8eJCPPQINyvix/tTEeznZY2Ht8FWwaCoY88AyB7kuhXMloYZBZmMmYvWPYfcvS/uHpiKcZ32Q8blobdTsXQghhdxJqrHgUQg2A2axwZNNVDm++BgoEhHnQ6Y1aeAW4ouj1JM+ZQ/pyy9MR1wYNCJ07B21QkNWxTsdl8tKSA2QVGGkU7suy/o1xt0ewST4Ha/pC6gVQaSBqIjQdBGrHnxljVswsO7OMBTELMCkmIrwjmNtmLhV8Kji6NCGEEPdBQo0Vj0qo+a+bZ9PZ9vUZ8rMNePq50HVoPbwCLA0ss37bSsLYsZhzctD4+hIyaxYeLZpbHefUrUxe+vIA2QVGGpf3Y1n/Rrjp7BBsCnNg42A4tdZyXbkTdF0Mbn62v9cDOJJ4hJG7R5KSn4Krkyvjm4ynS4Uuji5LCCHE35BQY8WjFmoAcm4X8PP842Qk5eHp78Jzw+rj6Wc5jE9//Tq3Bg+h8Nw5UKkIeOstAt55G5Xmr4t1T9zMoM+XB8kuNNIkwo+vX2mMq84Oi3oVBY4ug19HgakQvMtaTiEuUzJaGKTlpzF6z2gOJFjaP3Sr1I3RjUfj4lQyupELIYT4q+L8/Xb8/IAokoevC88Orod3KVey0wpYPzeGrNR8AHTlyhH+/Sp8XngBFIXUxYu5MeA1jLdv/2WcOmE+fDPAsqbmwJV0Biw/THaBwfYFq1TQsD+8tg18y0PmDVjaAQ5+bgk8Dubv6s9nUZ/xdp23UaFi3cV19Nnch+tZ1x1dmhBCCBuQJzWPgJzbBfw09xhZKfk4uznR7pXqlK/9v11GmRs2kDDxA5S8PJyrVaPc10vR+Pj8ZZyj19Pp+9UhcvUmyge4s+il+lQPsdPvoiATfn4Hzm2wXFfvCs98Ai4l43e/L34f0XuiSS9Ix13rzofNPqR9eMlo/yCEEOJ/ZPrJikc51IAl2Gz54jRJV7MAqPdkWSK7RqDRWB62FVy4wI3+r2JKS8OlenXKfr0UjfdfeyCduJnBW98dJT6zAGcnNR8+W4OeDcPss9VZUeDgZ5aTiM1G8IuAnt9AcMloYZCUm8TI3SOJSY4BoHe13gxrMAytxsbb34UQQjwwCTVWPOqhBsBkNLP/x8uc+OMmAKUreNP+tZp4+DoDUHjxItf7vYIpPR2XmjUpu/QrNFZ+1tu5eoasOc7OWEtjzefrhzKla037LCAGuHnY0jsq6xY4uUCnmVC/r2W6ysGMZiOfHPuEpaeXAlAroBazW88mxCPEwZUJIYQACTVWPQ6h5r8uxyTzxzfn0BeYcPHQ8uSr1Slb3R/4zxObfq9gun0bl9q1KfvVl2g8/9omwGxW+HTXZeZsjcWsQOUgDxb3rk/FQDu1FMhLh5/egItbLde1X4Sn54LO3T73K6ZdN3cxZu8YsvRZeOm8mNZiGq3DWju6LCGE+NeTUGPF4xRqADKS8/htyWlSb+aAChp2DqfRU+VRq1UUxMZagk1GBq516hD21ZdoPDysjnPgShqDVh0jJbsQN52G6c/X4tm6RbdheChmM/w5H/6YDIoZSlW1TEeVqmKf+xVTfE48w3cN51TqKQD61+zPe/Xew0nt+G7kQgjxbyWhxorHLdQAGA0m9q65yJk98QCEVvGl/YAauHnpKDh3jhuv9MeUmYlrvXqEffap1TU2ACnZhbz//TH2XbY01nwpsiwTnq6Oi9ZOvZyu7YUfBkBOImjdoMvHULunfe5VTAaTgTlH57Di3AoA6gfWZ2armQS5Wz/gUAghhH3ZfUv3okWLCA8Px8XFhcjISA4dOlTkZ8+cOUO3bt0IDw9HpVIxf/78v3zmv1/7v6933nnnzmfatGnzl6+/+eabD1L+Y8NJq6FN76pE9a+Ok05NXOxtVk89RPzF27hUq0bY0q9Qe3mRf+wYV5/vRv6pU1bHKeXpzLcDInnviYqoVLDy4A26fbqP62m59ik8vAW8uQfKt7a0V/hxIGx4HwwF9rlfMWg1WkY3Hs2c1nNw17oTkxxDz4092Re/z9GlCSGE+BvFDjWrV69m6NChTJw4kZiYGOrUqUOHDh1ITk62+vm8vDwiIiKYMWMGwcHBVj9z+PBhEhIS7ry2bdsGQI8ePe763MCBA+/63MyZM4tb/mOpSmQwPUY3wre0O3mZetbPPcbRLddwqVadct8sRxsWhiEujusv9SZ9xQqsPZzTqFUMbV+FZf0b4+eu40x8Fk8v2MuW0wn2KdojEF7+CVqPBlSWQ/u+ioK0y/a5XzG1D2/PmqfXUMW3CukF6by57U0WH1+MyWxydGlCCCGKUOzpp8jISBo1asTChQsBMJvNhIWFMWjQIEaPHn3P7w0PD2fw4MEMHjz4np8bPHgwGzdu5OLFi3e2Grdp04a6detafdJzPx7H6af/y1BoYtfKWGIPJgJQrqY/Ua9UR2vOJ2HsWLK3/Q6AZ6eOlJ48uch1NgmZ+QxaeYwj1y0H+b3avDyjO1VF52Snsxovbbc8rclLA2cveHYhVH/WPvcqpgJjAR8d/ogfLvwAQJPSTZjRcgb+rv4OrkwIIf4d7Db9pNfrOXr0KFFRUf8bQK0mKiqK/fv3P1i1Vu7x3Xff8eqrr/7l7JQVK1YQEBBAzZo1iY6OJi8vr8hxCgsLycrKuuv1uNM6a2j3SjXavlwVjVbN9dNprJ56iJRUhdAFCwiKHg1OTmT/uoVr3bpTEBtrdZzS3q6ser0Jb7SKAGDpn1fp+fl+4jLy7VN4xXbw5l4o2xQKsyzNMX8dDUa9fe5XDC5OLkxsOpFpLabh6uTKgYQD9NjQgyOJRxxdmhBCiP+jWKEmNTUVk8lE0P/pCh0UFERiYqJNClq/fj0ZGRm88sord73/0ksv8d1337Fjxw6io6P59ttv6dOnT5HjTJ8+HW9v7zuvsLAwm9RX0qlUKqo3D6H7qAZ4B7qSc7uQn2bHcPKPW/j27Uu5b7/BKTgY/fXrXOv5Ahnr1lmdjtJq1ER3rsaSvg3xcnHi+M0Mnlqwhx3nrU8zPjSvEOi3AZq/b7k++Cl83REybtjnfsXUpUIXVj21igreFUjJT+G1ra/x1amvMCtmR5cmhBDiP0pc76evvvqKTp06ERJy9+Fnr7/+Oh06dKBWrVr07t2bb775hp9++onLl62vwYiOjiYzM/PO6+bNm/9E+SVGQBlPekY3okL9QMxmhb1rL7Lli9NoqtSk/E8/4t6qJUphIQljx5EQPQZzEU+9nqwexKb3WlK7jDcZeQb6LzvMzC3nMZrs8Mdco4UnP4Re34OLD8Qdhc9awoXfbH+vB1DBpwIrn1pJl4gumBQT82PmM+iPQWQUZDi6NCGEEBQz1AQEBKDRaEhKSrrr/aSkpCIXARfH9evX+f3333nttdf+9rORkZEAXLp0yerXnZ2d8fLyuuv1b6NzdaLDwBq0fKEyao2KK8dSWDPtMLeznQj77DNKDRkCajWZ69dz7YUXKCwiIIb5ubH2zab0a1oOgMU7L/PSlwdJyrLTbqUqneCN3RBSHwoyYGVP+P0DMBntc79icNO6MbXFVD5o+gE6tY7dt3bTc2NPTqacdHRpQgjxr1esUKPT6WjQoAHbt2+/857ZbGb79u00bdr0oYv5+uuvCQwM5Kmnnvrbzx4/fhyA0qVLP/R9H2cqlYrabcvw/PAGePq5kJVawLqZRzmzNwH/1wdS9uuv0ZQKoPDiJa726Enmho1Wx3F20jDp2ZosfKkeHs5OHLqazlML9vDnpVT7FO5bDl7dAo3fsFzvnQfLu0CWnXZjFYNKpaJb5W6seGoFZT3LkpCbQL8t/VhxzvrOMiGEEP+MYk8/DR06lCVLlrB8+XLOnTvHW2+9RW5uLv379wegb9++REdH3/m8Xq/n+PHjHD9+HL1eT1xcHMePH//LExaz2czXX39Nv379cHK6+wTXy5cvM3nyZI4ePcq1a9f45Zdf6Nu3L61ataJ27doP8nP/6wSV96Ln2EaE1w7AZDSza2Us25aexbl+AyJ++gm3Jk1Q8vKIHzGChIkfYC4stDrO07VD+OXd5lQN9iQ1R0+frw7y8e8XMZnt8MfcyRk6z4Qey0DnCTf2wWct4PIO29/rAVT1q8rqp1fzZLknMZqNzDg0g2G7hpGtz3Z0aUII8a/0QCcKL1y4kFmzZpGYmEjdunVZsGDBnemgNm3aEB4ezrJlywC4du0a5cuX/8sYrVu3ZufOnXeut27dSocOHYiNjaVy5cp3ffbmzZv06dOH06dPk5ubS1hYGM899xzjxo2772mlf8OW7vuhKArHt91k//rLKGaFsjX86PRmLTRqSF20iNRPPwNFwbl6NcrMn4+ubFmr4xQYTEz8+Qyrj1jWKrWsFMD8F+ri7+Fsn8LTLsOafpB0ClBBm9HQagSo7XTqcTEoisLK8yuZfWQ2RrORMM8w5rSeQzX/ao4uTQghHnnSJsEKCTV3uxV7m02LTmDUmylX059Ob9RCo1WTs2cv8SNHYrp9G7WHB6WnTcWrffsix/nh6C3GrT9FgcFMsJcLn7xUj0bhfvYp2pAPv46EmG8s1xFt4fkl4FHKPvcrplMppxi+azjxufHo1DpGR46me6XufzmaQAghxP2TUGOFhJq/uhV7m00LT2A0mAmv5U/7gTXR6jQYEhOJGzqM/JgYAHz7vkzQ8OGodDqr48QmZvP2iqNcTslFo1YxskMVXm8VYb8/5sdXwaahlhYLnqWh+1Io18w+9yqmzMJMxu4dy65buwB4KuIpJjSZgJvWzcGVCSHEo0lCjRUSaqy7eT6dTYtOYjKY8Qtxp+PrNfENdkcxGEieN5/0pUsBcKlTmzLz5qH9P1vt/yu30MiYn07x83FLc82oaoHM7lEHHzfrQeihJZ+zTEelxoJKA+0mQLP3QO34UwrMipnlZ5bzcczHmBQTEd4RzGk9h4q+FR1dmhBCPHIk1FghoaZo8Rdvs2XJGfKz9GidNbTtU5VKjSwHLGb/8Qfxo6MxZ2Wh8fam9Ecz8GzTxuo4iqKw8tANJm04i95oJtTHlcW961MnzMc+hRfmwMYhcGqN5bpyR+j6KbjZafqrmGKSYhixawTJ+cm4Orkyvsl4ulTo4uiyhBDikSKhxgoJNfeWm1nItq/OEHchA4CarUJp0aMSGq0a/a1bxA0eQsHp0wD4DxxIqfffQ/V/dqn91+m4TN5eEcON9Dy0GhXjnqpO36bl7DMdpSiWZpi/jgJTIXiXteyWKtPA9vd6AGn5aYzeM5oDCQcA6FapG6Mbj8bFycXBlQkhxKNBQo0VEmr+ntlk5tDGqxz99ToApcp60mFgTbxLuWLW60n+aCa3V6wAwK1hQ0LmzEEbFGh1rKwCAyPXnmTLGUv7jKdqlWZGt1p4umjtU3zCCct01O2roNZCh6nQ+HUoAYt0TWYTX5z6gk+Pf4qCQmXfysxtM5dyXuUcXZoQQpR4EmqskFBz/66fSeP3pWcpyDWgc3WiXb9qRNS17DDK2ryZhHHjMeflofH3J3T2LNyLOHhRURS+/vMa0zafw2hWKB/gzqKX6lM9xE6//4JM+PldOPeL5br6s/DMJ+DibZ/7FdOBhAOM2j2K9IJ03LXuTGo2iQ7hHRxdlhBClGgSaqyQUFM82ekFbP3yNIlXLN3N60SF0fS5Cmg0agqvXiXu/cEUXrgAKhUB775DwJtvotJYPzMm5sZt3l0RQ3xmAc5OaiY9U4MXGoXZbzrq4OewdRyYDeAXAT2WQ+mScUhjcl4yI3eP5GjSUQB6Ve3F8IbD0WnstKBaCCEecRJqrJBQU3wmk5n9P13mxO+WA/aCI7xo/1pNPP1cMBcUkDhlCpk/rAPAvVkzQmbNxMnf3+pYt3P1DF1znB2xKQA8Xz+UKV1r4qazvi7nod06AmtfgcyboPnPycT1+5WI6Sij2cjCYwv56vRXANT0r8nsNrMJ9Qh1cGVCCFHySKixQkLNg7tyPIXty8+hzzfi4q4l6tXqlKthCS8ZP60ncdIklIICnAIDCZ03F7cG1hfpms0Kn+2+zOzfYjErUCnQg0/71KdioKd9Cs9Lh5/ehIv/6fJd+0V4ei7o3O1zv2LafWs30XuiydJn4anzZFqLabQJa+PosoQQokSRUGOFhJqHk5mSz29LTpNyw9LXqEGncjR+ujxqjZqCCxeIGzwE/ZUroNEQOHQIfv37oyrizJgDV9J4b9UxkrMLcdNpmPZcLbrWs9NTCrMZ9n0M2yeDYoJSVS3TUYFV7XO/YorPiWfErhGcTLV0+e5foz+D6g9Cq7bTgmohhHjESKixQkLNwzMaTPz5wyVO74oDILSyD08OqIG7tzPm3FwSJn5A1kZLl2+Ptm0JmT4NjY+P1bFSsgsZvPoYf15KA6BX47JM7FIdF62dejld+xN+eBVyEkHrBk/Phzov2OdexWQwGZh7dC7fnfsOgPqB9ZnZaiZB7kEOrkwIIRxPQo0VEmps5+LhJHZ8dx5DoQlXLx3tB9SgTBVfFEUhY/UakqZNQ9Hr0YaEEDp/Hq5FdFI3mRU+3n6RT/64iKJA9dJeLO5dn/AAO00P5aTAj6/BlZ2W6/r9oNNHoHW1z/2Kadv1bUz4cwI5hhx8nX2Z0XIGzUJLRvsHIYRwFAk1Vkiosa3bibn8tuQ0aXG5qFTQuEt5GnQMR6VWUXD2LLcGD8Fw4wZotQSNHIlvn95F7nbafSGFwauPk56rx9PZiZnda9OpVmn7FG42we5ZsHMGoEBwLct0lH8F+9yvmG5k3WDYrmGcTz+PChVv1HmDN2u/iaYEdCMXQghHkFBjhYQa2zPoTez+/gLn9yUAULa6H1GvVsfVQ4cpO5uEMWPJ3rYNAM+OHSk9ZTIaDw+rYyVmFvDuyhiOXL8NQP/m4UR3qobOyU69nC7/AesGQl4q6Dzh2YVQo6t97lVMhaZCPjr0EWsvrAUgsnQkM1rOIMA1wMGVCSHEP09CjRUSauzn3L4Edq+KxWgw4+HrTPvXalK6gjeKonD7229JmjkLjEa05cpS5uOPcalqfZGuwWRm9tZYPt91BYA6YT4seqkeZXzt1OE6K96yzubGfst15Jvw5GRwKhlnxmy8spEP939IvjGfUq6lmNlqJg2DGzq6LCGE+EdJqLFCQo19pcXlsOWL02Qk5aFWq2jyXAXqRlkO2Ms/fpxbQ4ZiTEhA5exM0Lix+HTvXuR01LazSQxbc5ysAiPerlrmvVCHJ6raadGsyQh/TIY/51uuQxtYekf5lLXP/YrpSsYVhu0axqWMS6hVagbVG8SrNV9FrXJ8N3IhhPgnSKixQkKN/ekLjOz87jwXjyQDUL5OAE/0rYaLuxbj7dvEjx5N7q7dAHg/+wzBEyeidrP+FOZmeh7vrozhxK1MAN5qU4FhT1bGSWOnP+axW+CnN6AgA1x84LnPoUpH+9yrmPIMeUw9OJVfLlvaP7QMbcm0FtPwcfFxbGFCCPEPkFBjhYSaf4aiKJzZHceetRcxGxW8AlzoMLAmgeW8UMxm0r78ipT588FsRlexAmU+/hjnCtYX6RYaTUzffJ5l+64B0Djcj09eqkeQl506XGfcsJxCHGdpYUDz9+GJCaCx06nHxaAoCj9d+olpB6dRaCok2D2Y2a1nU6dUHUeXJoQQdiWhxgoJNf+s5OtZ/LbkNFmpBaidVLToXomarUNRqVTkHjpE/LDhGFNSULm6UvrDSXh36VLkWJtOJjBq3UlyCo34u+v4+MV6tKhkp0WzRj1sGw8HP7Ncl20G3b8CrxD73K+YYtNjGbZrGNezruOkcmJow6H0qdbHPn20hBCiBJBQY4WEmn9eYZ6B7cvPcfVEKgAVGwbStk9VdC5OGFNTiRsxgrz9BwDw6dmToLFjUDs7Wx3rSkoOb6+I4XxiNioVvN+uEoOeqIRGbac/5mfWWzp+67PBLQC6LYEKT9jnXsWUo89h4r6JbL2+FYCoslF82PxDPHV2ajchhBAOJKHGCgk1jqEoCie232T/j5cxmxV8gtzoMLAmAWU8UEwmUhctJvXTT0FRcK5WjTLz56ErV87qWAUGEx/8cobvD1sabLaoGMD8F+sS4GE9CD20tMuwph8knQJU0HoUtB4JJeDMGEVRWHV+FbOOzMJoNlLGowxz2syhun91R5cmhBA2JaHGCgk1jpV4JZPflpwm53YhGq2aVi9Wpnpzy5ROzt4/iR8xAtPt26g9PCg9dSpeHdoXOda6o7cYt/40+QYTQV7OfNKrPo3L+9mncEM+bBkNR5dZriPawPNfgkcp+9yvmE6nnmbYzmHE58ajU+sY1XgUPSr3kOkoIcRjQ0KNFRJqHC8/R8/vX5/lxpl0AKo2DaZVrypodRoMSUnEDR1G/lHLIl3fvi8TNHw4Kp31M2MuJGXz9ooYLiXnoFGrGNGhCq+3jEBtr+moE6th42Aw5IFHMHRfCuHN7XOvYsoszGTc3nHsvLUTgM7lOzOx6UTctHY630cIIf5BEmqskFBTMihmhaNbrnNowxUUBfxC3On4ek18g91RDAZSPv6YtC+/AsCldm3KzJuLNtR6B+/cQiNjfzrF+uPxALSrGsicnnXwcbPT4XnJ52FNX0iNBZUG2o2HZu9DEd3I/0mKorD8zHLmx8zHpJgo712eOa3nUMm3kqNLE0KIhyKhxgoJNSVLXOxttn51hrwsPU7OGtr2qULlRsEAZP+xg/joaMyZmai9vQn5aAaebdpYHUdRFFYduskHG86gN5oJ9XFlUe/61A3zsU/h+lzYOBROfm+5rtQBnvsM3Ow0/VVMx5KPMXzXcJLzknHRuDCuyTierfiso8sSQogHJqHGCgk1JU9uZiHblp4hLjYDgBqtQmnRoyJOWg36W3HEDRlCwalTAPgPHEip999D5WT9zJjTcZm8szKG62l5aDUqxnSuxivNwu2ztkRRIOYb2DwCTIXgHWY5hbhMyWhhkF6QTvSeaPbF7wPg+UrPE904GhcnO53vI4QQdiShxgoJNSWT2axweONVjvx6DRQICPOg4+s18S7lhlmvJ3nmLG5/9x0Arg0bEDpnLtqgQKtjZRUYGPXDSX49nQhA51rBzOhWGy8XrX2KTzgJa/tB+hVQa6H9FIh8A0rAIl2T2cSSU0tYfHwxCgqVfSszp/Ucwr3DHV2aEEIUi4QaKyTUlGzXz6Tx+9KzFOQa0LloaNevOhH1LDuMsrZsIWHsOMy5uWj8/AidPQv3Zs2sjqMoCsv2XWPa5nMYTArh/m4s6l2fGiHe9im8IAt+eRfO/my5rvaMpeO3i53uV0wHEg4wavco0gvScXNyY1LzSXQMLxntH4QQ4n5IqLFCQk3Jl51ewNYvz5B4xdLvqU67MJo+VwGNkxr9tWvcGjyEwvPnQaUi4J13CHjrTVQa62fGHLtxm3dXHiMuIx+dk5pJz9TgxUZh9puOOvQF/DYWzAbwLQ89l0PpktHCICUvhRG7R3A0ybKzrFfVXgxvOBydpmR0IxdCiHuRUGOFhJpHg8lk5sBPlzn+u+WAvaDyXnQYWBNPPxfMBQUkTZ1KxtofAHBv1oyQWTNx8ve3OlZGnp6ha07wx3lLg83n6oUypWtN3J3t1Mvp1lFL76jMG6Bxhk4fQYNXSsR0lNFsZNHxRXx56ksAavjXYHbr2ZTxLOPgyoQQ4t4k1FghoebRcuV4Cn98c47CPCPO7k482b8G5Wpawkvmzz+T8MEklPx8nAIDCZ07B7eG1hfpms0KX+y5wqzfYjGZFSoGevBp7/pUCrJTS4G8dFj/FlzYYrmu1ROengfOHva5XzHtvrWbMXvHkFmYiafOk6nNp9K2bFtHlyWEEEWSUGOFhJpHT1ZqPlu+OE3KjWwAGnQsR+Mu5VFr1BRevMitwUPQX74MGg2BQwbj9+qrqIo4M+bglTQGrTpGcnYhrloN056vyXP17PSUwmyG/Z/A75NAMUFAFct0VGA1+9yvmBJyEhi+ezgnU04C8EqNV3iv/nto1XZaUC2EEA+hOH+/H+jUsEWLFhEeHo6LiwuRkZEcOnSoyM+eOXOGbt26ER5u2V47f/78v3zmgw8+QKVS3fWqWrXqXZ8pKCjgnXfewd/fHw8PD7p160ZSUtKDlC8eEV4BrnQb0YBarS2H7x3dcp2f5x8nN7MQ50qVKL9mNV5duoDJRPLsOdx6+x1MGRlWx4qM8Gfz+y1pUTGAfIOJIatPEP3jSQoMJtsXrlZD8/fhlU3gWdpyWN+SJ+DE97a/1wMo7VGaZR2W0adaHwCWnVnGq1teJTE30cGVCSHEwyl2qFm9ejVDhw5l4sSJxMTEUKdOHTp06EBycrLVz+fl5REREcGMGTMIDg4uctwaNWqQkJBw57V37967vj5kyBA2bNjA2rVr2bVrF/Hx8Tz//PPFLV88YjRaNa16VaH9azXQOmuIv5jB6imHuHU+HbW7OyEzPyJ40iRUOh05O3dy9flu5J88aXWsAA9nlr/amMFRlVCpYNWhmzy3eB9XU3PtU3y5pvDGHku/KEMe/PQG/DLI0k/KwbQaLaMaj2Jem3l4aD04nnKcnht68mfcn44uTQghHlixp58iIyNp1KgRCxcuBMBsNhMWFsagQYMYPXr0Pb83PDycwYMHM3jw4Lve/+CDD1i/fj3Hjx+3+n2ZmZmUKlWKlStX0r17dwDOnz9PtWrV2L9/P02aNPnbumX66dGXkZTHli9OkRaXi0oFjZ4uT8NO4ajUKgrOnuXW4CEYbtwArZagkSPx7dO7yN1Oey6mMPj746Tl6vFwdmJm99p0rlXaPoWbTbB7NuycDigQVBN6fgP+Fexzv2K6mXWTYbuGcS79HCpUDKw9kLfrvI2mBHQjF0IIu00/6fV6jh49SlRU1P8GUKuJiopi//79D1btf1y8eJGQkBAiIiLo3bs3N27cuPO1o0ePYjAY7rpv1apVKVu2bJH3LSwsJCsr666XeLT5BLnRbVRDqjUrbdlFveEqGxeeID9bj0v16pRf9wOe7duDwUDS1KnEDR6CKTvb6lgtK5Vi03staRTuS06hkbdXxPDBL5ZWCzan1kCbUdB3PbiXgqTT8HlrOPOT7e/1AMK8wvi287f0rNwTBYUvTn7B69teJzU/1dGlCSFEsRQr1KSmpmIymQgKCrrr/aCgIBITH3w+PjIykmXLlrFlyxY+/fRTrl69SsuWLcn+zx+kxMREdDodPj4+933f6dOn4+3tfecVFhb2wPWJkkOr0/BE32q061cNJ62aG2fTWT31MAmXMtB4ehL68XyCxowBrZbs337javfuFJw7Z3WsYG8XVg1swputLU9Mlu27Ro/P93MzPc8+xUe0sUxHlWsO+mzL9u/NI8BYaJ/7FYOzxpnxTcczo+UMXJ1cOZR4iB4benA48bCjSxNCiPvm+PbCQKdOnejRowe1a9emQ4cObN68mYyMDNasWfPAY0ZHR5OZmXnndfPmTRtWLBytatPSdB/dEJ8gN3IzCvlp7jGObbU83fPr+zLh332LU0hpDNdvcO2FF7m9Zg3WZlqdNGpGd6rKV/0a4u2q5cTNDJ7+ZC/bz9lpEbpXaej7C7QYYrk+9AUs7Qi3r9vnfsX0VMRTfP/091T0qUhqfiqvbX2NJSeXYFbs8ARLCCFsrFihJiAgAI1G85ddR0lJSfdcBFxcPj4+VK5cmUuXLgEQHByMXq8n4//sbLnXfZ2dnfHy8rrrJR4v/qEe9IhuSKVGQShmhX0/XmLzp6coyDXgWqcO5detw6N1axS9nsQJE4kfNQpzrvVFwe2qBbHpvRbUCfMhM9/AgOVHmP7rOQwmO/wx1zhB1Afw0hpw8YH4GPi8JZzfbPt7PYAI7whWdF7BMxWewayYWXBsAe9sf4eMggxHlyaEEPdUrFCj0+lo0KAB27dvv/Oe2Wxm+/btNG3a1GZF5eTkcPnyZUqXtizcbNCgAVqt9q77xsbGcuPGDZveVzx6dC5OPPlqdVq/VAW1k4prJ1NZM80yHeXk60uZTxdTathQ0GjI+mUDV3u+QOF/wvL/VcbXjbVvNOWVZuEAfL7rCi8tOUBiZoF9iq/cAd7cA6ENoSATvu8FW8eDyWCf+xWDm9aNKc2n8GGzD3HWOLM3bi89NvbgePJxR5cmhBBFKvb009ChQ1myZAnLly/n3LlzvPXWW+Tm5tK/f38A+vbtS3R09J3P6/V6jh8/zvHjx9Hr9cTFxXH8+PE7T2EAhg8fzq5du7h27Rr79u3jueeeQ6PR0KtXLwC8vb0ZMGAAQ4cOZceOHRw9epT+/fvTtGnT+9r5JB5vKpWKmq1C6T6yIV4BLmSnFfDj7Bi2f3OOglwjAQMHUm7Z1ziVKoX+8mWu9uhJ5i+/WB1L56Tmg2dqsLh3fTycnTh87TZPLdjDnosp9inepyz0/xWavG253rcAlj0NmXH2uV8xqFQqnqv0HCs6r6CcVzkScxPpv6U/35z5xupUnhBCONoDnSi8cOFCZs2aRWJiInXr1mXBggVERkYC0KZNG8LDw1m2bBkA165do3z58n8Zo3Xr1uzcuROAF198kd27d5OWlkapUqVo0aIFU6dOpUKF/215LSgoYNiwYaxatYrCwkI6dOjA4sWL73vaS7Z0/zsU5hn484dLnNuXAICzmxNNulagRosQTLfTiRs+nLz9BwDw6dGDoLFjULu4WB3rWmoub62I4VxCFioVvPdEJd5rVwmN2k69nM7+DD+/C4VZ4OYPzy+Biu3sc69iytHn8MH+D/jt2m8AtCvbjg+bf4iXTv5dEkLYl7RJsEJCzb9LwuVMdq2KJe1WDgCB5Tx5om81/IJdSV38KamLF4Oi4Fy1KmXmz0MXHm51nAKDiUkbzrLqkGURcvOK/szqXocQH1f7FJ52Gdb2g8RTgApaj4TWoyzbwh1MURRWx65m5uGZGMwGQj1CmdNmDjX8azi6NCHEY0xCjRUSav59zCYzp3bFcfCXKxgKTDhp1bTqVZlqzULI+fNP4keMxJRuOZm49NQpeHXsWORYPx27xZgfT5NvMOGm0/Beu0q82rw8Oic7bCA0FMCW0XD0a8t1+VbQ7SvwCLT9vR7AmdQzDNs1jLicOLRqLaMajaJnlZ5FHnQohBAPQ0KNFRJq/r1yMwv5Y/k5bpxNB6Bq02Ba9aoCt1OJGzqM/KNHAfDt04egkSNQ6XRWx7mUnM3odac4cv02AJUCPZjctSZNIvztU/jJNbBhMBhywSMIui+F8Bb2uVcxZRZmMu7Pcey8uROATuU7MbHpRNy17g6tSwjx+JFQY4WEmn83xaxwdMt1Dm24gqKAX4g7HV+viU+AMykff0zaki8BcKldm9C5c9GVCbU6jtmssC7mFjN+PU9arh6A5+qFEt25KoGe1tfmPJSUWFjTF1LOg0oNT4yD5kMsTTMdTFEUvjn7DfOOzsOkmAj3CmdOmzlU9q3s6NKEEI8RCTVWSKgRALdib7P1qzPkZ+nROmto26cqlRoFkb1jB/GjozFnZqL29iZk+nQ8n2hb5DiZeQZmbT3PioM3UBTwdHZiWPvK9GlSDieNjQOHPhc2DoWT/+nyXak9PPc5uPnZ9j4P6FjyMYbvGk5yXjIuGhfGNhlL14pdHV2WEOIxIaHGCgk14r9yMwvZtvQMcbEZANRsFUrzHhUxJyUSN2QIBadOAeD/2gBKvf8+Kq22yLFO3Mxg/M+nOXkrE4Dqpb2Y8lxN6pf1tW3RigLHvv1PW4UC8CoDPZZBWCPb3ucBpRekM2bPGP6Mt3T57lqxK2Mix+DqZKcF1UKIfw0JNVZIqBH/P7NZ4fDGqxzZfA2AUmU96TCwJl7eGpJmzeb2t98C4Fq/PiEfzUB3j95hJrPCqkM3mLnlPFkFRgBebBTGqI5V8XW3vj7ngSWegjX9IP0yqJ3gycnQ5C0oAYt0zYqZJSeXsPjEYsyKmUq+lZjTeg7lvf96pIMQQtwvCTVWSKgR1lw/k8bvS89SkGtA5+pEu37ViKhbiqwtv5Ewdizm3FxUzs74v/E6/q+9hrqIRcQAqTmFzPj1PD8cvQWAj5uW0R2r0rNhGGpbnm1TkAW/DIKz6y3X1brAs4vAxdt293gIBxMOMmr3KNIK0nBzcmNSs0l0LF/0zjIhhLgXCTVWSKgRRclOL2Drl2dIvGKZQqoTFUbT5ypgunWThIkfkHfAclifrlw5gsaPx6NF83uOd/haOuPXn+Z8oqXLfL2yPkx+tiY1Q20YOhQFDi2B38aA2QC+4dBjOYTUtd09HkJKXgojd4/kSNIRAF6o8gIjG41Ep7HxkyshxGNPQo0VEmrEvZhMZg78dJnjv1u6uQdHeNH+tZp4+DqTtWkzSR/NwJSSCoBnhw4ERY9Ge4/TrI0mM8v2XWPetgvk6k2oVdC3aThD21fGy6XoNTrFFncU1rwCmTdA4wwdp0PDV0vEdJTRbGTx8cUsObUEgOr+1ZndejZhnkVP5QkhxP8locYKCTXiflw5nsL25efQ5xtxcdcS9Wp1ytXwx5SdTconn3D7uxVgNqNyc6PUO+/g1/fley4kTsoqYMqmc2w4EQ9AgIcz456qxrN1Q2x3WF1eOqx/Gy78armu1QOeng/OHrYZ/yHtubWH6L3RZBZm4qn1ZEqLKTxR9glHlyWEeERIqLFCQo24X5kp+fy25DQpNyzTRw06laPx0+VRa9QUnD9P4qQPyT92DADnShUJnjABt0b33oW092IqE345zZWUXACaRPgx+dmaVArytE3RimJphvn7JFBMEFDZMh0VVN024z+kxNxEhu0axsmUkwD0rd6XwQ0Go1Xb8KmVEOKxJKHGCgk1ojiMBhN//nCJ07ss3bJDK/vwRL9qePm7opjNZP70E8mz52C6bTld2PvZZwgaMwaNd9HrZgqNJr7cc5VP/rhIgcGMk1rFay0jGPJkJZydbNTb6fp++KE/ZCeAkys8PRfqvmSbsR+SwWRgfsx8vjn7DQB1StVheovphHnJdJQQomgSaqyQUCMexMXDSez47jyGQkvvqIZPhVM3qiwaJzWmjAyS580nY80aUBS0ZcoQOm8errVq3nPMm+l5fLjxLNvOJgFQp4w3C1+qT5ifm22Kzk2Fda/BlR2W63p9oNMs0Nlo/Ie0/fp2xv85nmxDNjq1jgG1BjCg1gCcNc6OLk0IUQJJqLFCQo14UBlJeez47jzxFzMA8Alyo1WvyoRVtZzom3/iBHHDhmO4dQuVVktg9Gh8e/X62zUzW88kMnLdSTLyDHi5ODG3Z12iqgfZpmizCXbPhp3TAQUCa0DPbyCgom3Gf0g3s2/y4f4POZBg2VkW5hlGdONoWpZp6eDKhBAljYQaKyTUiIehKAoXDiXx57pL5GdZej5VahhIyxcr4+qhw5SVRfyYMeT8vh0Ar86dCP5wMhqPezd4vHU7j3dXHuP4zQwA3mgVwfAOVdDaqtXClZ2Wpza5KaDzgGcWQM1uthn7ISmKwm/Xf2PWoVkk5ycDEFU2ilGNRxHsXvTOMiHEv4uEGisk1AhbKMwzcPCXq5zedQtFAQ9fZzoMrElwhDeKopC+fDnJs+eA0YguPJzQj+fjUqXKPcfUG83M+PU8S/+8CkCjcF8+6VWfYG8bNcjMSoB1A+C6pYUBjQZCh6ngVDKme3INuSw+vpgV51ZgUky4OrnyRu036Fu9L1qNLCQW4t9OQo0VEmqELSVfz2LrV2fITM5HrVbR9PkK1GkXhkqlIu/YMeKGDMWYmIjK2ZngCRPw6fb8347566kERv5wkuxCI37uOua/UJdWlUvZpmCTEXZMhb1zLdch9Sy9o3zDbTO+DVy4fYGpB6YSkxwDQIR3BOOajKNRcMnobyWEcAwJNVZIqBG2ps83suO781w6apk6iahbiif6VsXZTYvx9m3iR40id/ceALy7diV4wnjUbvderHs9LZe3V8RwJj4LlQoGPVGJ99tVQmOrNgsXtsJPr0P+bUtbha6fQdXOthnbBhRFYcOVDcw5Mof0gnQAOpfvzPCGwynlZqOAJ4R4pEiosUJCjbAHRVE4vSuOvT9cxGxU8ApwocPAmgSW80Ixm0lb8iUpH38MZjPOlSoS+vHHOEdE3HPMAoOJDzeeZeXBGwA0r+jP/BfqUcrTRtNFGTct275vHbZcNxsE7SZCCZrqySzM5JNjn7Amdg0KCh5aD96t9y4vVHkBJ7WTo8sTQvyDJNRYIaFG2FPy9Sy2fHGa7LQC1E4qWvasTI2WllODcw8eIm74MEwpqajc3Cg9aRLeXZ7+2zHXH4tjzE+nyNObCPR05pNe9YiM8LdNwUY9/D4RDiy2XIdFQvevwTvUNuPbyJnUM0w5MIXTaacBqOpXlbGRY6kbWNexhQkh/jESaqyQUCPsrSDXwB/fnOPqCUuPqEqNgmjTuwo6FyeMKSnEDR9B3sGDAPi8+AJB0dGone/99OVScjZvr4jhQlIOahUM71CFN1tVsF3X77O/wM/vQGEWuPnD819AxSjbjG0jJrOJdRfX8XHMx2TpswB4vtLzDK4/GF8XXwdXJ4SwNwk1VkioEf8ERVE4/vtN9v90GcWs4BPkRsfXa+If6oFiMpG6aBGpn34GioJz9WqUmT8fXdmy9xwzT29k3PrT/BhjOd24bZVSzO1ZF193G3W8Tr8Ca/pB4klABa2GQ5toUNvolGMbSS9IZ97Reay/tB4Ab2dv3q//Pt0qdUOtstEWeCFEiSOhxgoJNeKflHApg9++PENuRiFOWjWtelWhWrPSAOTs2UP8iJGYMjJQe3pSetpUvJ588p7jKYrCmiM3mfDzGQqNZkK8XVjYuz71y9roSYWhAH6LhiNLLdfhLaHbV+Bpo8MAbehY8jGmHJjChdsXAKgdUJuxTcZS3b9k9LkSQtiWhBorJNSIf1p+tp7fvz7LjbOWXTxVmwbTokclnN20GBISiBs67E5jTN++LxM4ePDf7o46G5/FOytjuJqai5NaRXTnarzaPNx2Hb9ProUN74MhFzyCLMGmfMk75ddoNrLq/CoWHV9EriEXtUpNz8o9GVR/EF46+fdbiMeJhBorJNQIR1DMCke3XOPQhqsoCrh66WjerSKVGweB0Ujy3Hmkf/01AE4hpQkeMwaPdu3uGVKyCwyMXneKTacSAOhYI5iZPWrj5WKj3UspF2BNX0g5Byo1tB0LLYaCuuRN8STnJTP78Gx+vfYrAH4ufgxvOJynI562XdATQjiUhBorJNQIR4q7cJudK2LJSMoDLF2/W71YBb8Qd7J37iTxww8xxltCikfr1gSNG4surOju1Yqi8M3+60zZdBaDSaGsnxuLe9enZmjRXcKLRZ8Lm4bBiVWW64pR8NwX4G6j3Vc2diDhAFMPTOVa1jUAGgY1ZGzkWCr6loxeV0KIByehxgoJNcLRTAYzx36/wdHN1zAazKjVKuo+GUbjpyNQGQpI/exz0r7+GgwGVM7O+L/xOv4DBtxzh9SJmxm8vSKGuIx8dE5qJnapzkuNy9rmKYWiwLHvYPNwMBaAV6jlFOKwxg8/th0YTAaWn13O5yc+p8BUgJPKiT7V+/BWnbdw05aMDuVCiOKTUGOFhBpRUmSl5rN37cU7W78Dy3nSYWBNvAJcKbxyhcQPJ5N3wNK9WluuLMHjxuPRskWR42Xk6Rm+9gS/n7OcbPxs3RCmPVcLd2cbHVKXeNoyHZV+GdRO8OSH0ORtKKHTO/E58Xx06CP+uPkHAEFuQYxsNJInyz0pU1JCPIIk1FghoUaUNFeOp/DHt+cozDXi7OZEu37VKF+nFIqikLV5M8kzPsKYkgKAZ4cOBEWPRhtsvXu1oih8sfsKM3+LxWRWqFDKncW9G1Al2NM2xRZkwYb34MxPluuqT8Ozi8DVxzbj28HuW7uZdnAacTmWrfDNQpoxJnIM5bzKObgyIURxSKixQkKNKImy0vLZ+uUZkq5aDpWrGxVG42ci0Oo0mHJySP1kIenffQcmEyo3N0oNGoRfv76oili0e/haOu+ujCEpqxAXrZopXWvRvUEZ2xSrKHD4S/htDJj04FMOei63NMcsoQqMBXx1+iu+OvUVBrMBrVrLqzVf5bVar+HiZKMu6EIIu5JQY4WEGlFSmYxm9v14iZN/3ALA09+FVi9UJrx2AAAFsbEkTvqQ/BhL92r31q0ImTEDJ1/rZ9Sk5RQyePVx9ly0TG/1bFiGSc/UxFVno8P04mJgbT/IuAEaHXScDg0HlNjpKIDrWdeZfnA6f8b/CUCoRyjRjaNpHdbawZUJIf6OhBorJNSIku7K8RT2rL5Azu1CAMJrB9CyZyW8AlxRzGYy1v5A0rRpKIWFOJUuTZl5c3GtW9fqWCazwqIdl5j3+wUUBaoGe7K4d30iSnnYptj827D+bYjdbLmu2R26zAdnG0132YGiKPx+43c+OvQRSXlJALQNa8voxqMJ8QhxcHVCiKIU5+/3Ax08sWjRIsLDw3FxcSEyMpJDhw4V+dkzZ87QrVs3wsMtB4TNnz//L5+ZPn06jRo1wtPTk8DAQLp27UpsbOxdn2nTpg0qlequ15tvvvkg5QtRIkXULUWviZHUe7IsarWKaydTWTXpIEe3XENBhe8LPQlf/T26cuUwJiRwrc/LpC9fjrX/L9GoVbzXrhLfDYgkwEPH+cRsunyylw0n4m1TrKsvvLgS2k8BlQZO/wBftIWkM7YZ3w5UKhVPlnuSX7r+Qv8a/XFSObHj5g6eXf8sS04uQW/SO7pEIcRDKnaoWb16NUOHDmXixInExMRQp04dOnToQHJystXP5+XlERERwYwZMwguYpHjrl27eOeddzhw4ADbtm3DYDDQvn17cnNz7/rcwIEDSUhIuPOaOXNmccsXokTTuTjRrFtFeo5rREglH4wGMwfWX2HjJ8fJy9LjUrUq4et+wLNjRzAaSZo+g7j33seUnW11vOYVA9j0Xksal/cjV29i0KpjTPj5NIVG08MXq1JBs0HQfzN4hkDaRVjSDo6tePix7chN68bQhkNZ22UtDYMaUmAqYMGxBXT7pRsHEg44ujwhxEMo9vRTZGQkjRo1YuHChQCYzWbCwsIYNGgQo0ePvuf3hoeHM3jwYAYPHnzPz6WkpBAYGMiuXbto1aoVYHlSU7duXatPeu6HTD+JR42iKJzfn8Du7y9g1Jtx99bR/rWahFTyQVEUbq9cSdKMj8BgQBsWRuj8ebjWqGF1LKPJzNxtF1i88zIAtUK9Wdy7PmF+Njq/JTcVfnwdLm+3XNftA51nga5knw+jKAobr2xkzpE5pBWkAdAxvCMjGo0g0C3QwdUJIcCO0096vZ6jR48SFRX1vwHUaqKioti/f/+DVWtFZmYmAH5+fne9v2LFCgICAqhZsybR0dHk5eUVOUZhYSFZWVl3vYR4lKhUKqo1C6H76Ib4BruRm6ln/bxjxPx2HRTw692b8JUr0IaGYrh5k+u9XuL296utTkc5adSM7FiVr19phI+bllNxmTy1YA8bT8Zb/XyxuQdA7x+g7ThLa4Xj38GX7Ur0dBRYfsddKnThl+d+oVfVXqhVarZc28Iz65/hmzPfYDQbHV2iEKIYihVqUlNTMZlMBAXd3bk3KCiIxMREmxRkNpsZPHgwzZs3p2bNmnfef+mll/juu+/YsWMH0dHRfPvtt/Tp06fIcaZPn463t/edV9g9jpwXoiTzD/Gg++iGVG4chGJW2P/TZTZ/epKCXAOutWpR/sd1eLRti6LXk/jBB8SPGIn5/0zd/lfbqoFseq8ldcN8yCow8u7KY7zy9WGupVr/fLGo1dB6BLy8HtwDIfksfNYStoyBQuvTYyWFl86LMZFjWPXUKmoH1CbXkMusI7N4YeMLHEs+5ujyhBD3qVjTT/Hx8YSGhrJv3z6aNm165/2RI0eya9cuDh48eM/vv5/pp7feeotff/2VvXv3UqZM0edr/PHHH7Rr145Lly5RoUKFv3y9sLCQwsLCO9dZWVmEhYXJ9JN4ZCmKwtm98exZfRGT0YyHnzMdBtYkuLw3iqKQvnQpyXPngcmELiKC0PnzcKlc2epYeqOZT/64yOe7rqA3mdE5qXmrdQXealMBF60Ntn5nJ8LmEXDuF8u1Z2noMA1qPFeit34DmBUzP178kfkx88kstDw1frbCswxtOBQ/F7+/+W4hhK3ZbfopICAAjUZDUlLSXe8nJSUVuQi4ON599102btzIjh077hlowLK2B+DSpUtWv+7s7IyXl9ddLyEeZSqVihotQ+k2qgFepVzJSS/kp9kxHNt2A8Ws4D9gAOW+/QanoCD0V65wrecLZPy03upYOic1w9pXYcvglrSsFIDeaObj7RdpP283O85bX/RfLJ7B8MK30Hsd+JaH7AT4oT982xVSLz78+HakVqnpXrk7G7puoFulbgD8fPlnuvzUhTWxazCZbbDIWghhF8UKNTqdjgYNGrB9+/Y775nNZrZv337Xk5viUhSFd999l59++ok//viD8uXL/+33HD9+HIDSpUs/8H2FeBSVCvOk55hGVKhXCrNJYd+6S6ydcYTEK5m41a9P+Z9+xL15c5SCAhKio4kfOxZzfr7VsSJKefDNq41Z9FJ9grycuZGeR/9lh3nj2yPEZVj/nmKpFAVvH4A20aBxhis7YXFT2D4Z9EWviSsJfF18+aDZB3zb6Vuq+lUlS5/F5AOT6b25N2dSS/ZaISH+rYq9+2n16tX069ePzz//nMaNGzN//nzWrFnD+fPnCQoKom/fvoSGhjJ9+nTAsrj47NmzAHTu3JnevXvTu3dvPDw8qFixIgBvv/02K1eu5Oeff6ZKlSp37uXt7Y2rqyuXL19m5cqVdO7cGX9/f06ePMmQIUMoU6YMu3btuq+6ZfeTeNwoisKZPfEcWH+ZwjzLgtbqLUJo2rUCzq5qUj//nNRPFoKi4Fy5MqEfz8f5Hv/DkFNo5OPfL7D0z2uYzAquWg2D2lXktRYR6Jwe6Eiru6VfgV9HwcWtlmvvstDpI6ja+eHHtjOj2cjq2NUsPLaQHEMOKlT0rNKTQfUG4e3s7ejyhHis2f1E4YULFzJr1iwSExOpW7cuCxYsuDMd1KZNG8LDw1m2bBkA165ds/rkpXXr1uzcudNSRBFz7F9//TWvvPIKN2/epE+fPpw+fZrc3FzCwsJ47rnnGDdu3H0HFAk14nGVl6Vn/4+XOH/AsljfxV1L8x4VqRIZTN6BA8QNH4EpLQ21mxvBEyfg9cwz9+xWfT4xiwnrz3DoWjoAFUq5M7lrTZpVCHj4YhUFzm+EX0dDlqUtBJU7WsKNb/jDj29nqfmpzD4ym01XNgHg5+LH0AZDeabCvX+nQogHJ20SrJBQIx538Rcz2LUqlvR4y06mqk2CadWrCmSmET90GHlHjgDgFhlJ8ITxOFtZYP9fiqLwY0wc0389R2qO5aTdZ+qEMO6pagR62aARpD4Xds+CfQvBbAAnF2g5HJq/B07ODz++nR1OPMyUA1O4knkFgPqB9RnbZCyVfa0vzBZCPDgJNVZIqBH/BiaTmWO/3eDQhisoCviFuNNhYE18SzmT9uWXpH76GUphIWi1+L/Sj4C33kLtVvQBeZn5BuZsjeXbA9dRFPBwdmLok5Xp27QcThobTEmlxMKmYXBtj+Xar4Ll0L6K7R5+bDszmAx8e+5bPjvxGfnGfDQqDS9Ve4l36r6Du9bd0eUJ8diQUGOFhBrxbxJ34TZbvzxDXpYeJ2cNrV6oRNWmpTHExZE0ZSo5/5n6dQopTfCYMXi0a3fP6ZNTtzIZ9/NpTtzMAKBaaS+mdK1Bg3I22OKsKHB6Hfw2BnL+s7OyelfLFnDv0Icf384SchKYeXgmv9/4HYBA10BGNBpBh/AOMiUlhA1IqLFCQo34t8nL0rP1qzPExd4GIKSSD616VcY/xIPsP/4gacpUDPGWBpfurVsRPG4cunscUmk2K3x/+CYfbTlPZr4BgJ4NyzC6UzX83HUPX3BBJuyYDoc+B8UMWndoMxqavAUa7cOPb2d74/Yy7eA0bmbfBKBJ6SaMiRxDee+/380phCiahBorJNSIfyOzWeH4thsc3ngVo8GMWq2idrswGj0VjpNiIPWzz0lbuhQMBlQ6Hf5vvI7/a6+hdi56XUtaTiEzfj3P2qOWhb4+blpGdqjKi43CUKtt8GQi4aRlSurWIct1qWrw1BwIb/7wY9tZoamQpaeW8uWpL9Gb9Tipnehfoz8Daw/E1cnV0eUJ8UiSUGOFhBrxb5aVms/etRe5eiIVAHcfZ1r3qkz5OqUovHKVpCmTyd1n6d+mLVuW4PHj8GjZ8p5jHrmWzrj1pzmfaGmBUCfMh5ndalMl2PPhCzab4fgK2DYB8i27sKjTC578EDxKfqPJm1k3mX5oOnviLGuFQtxDGN14NG3LtnVwZUI8eiTUWCGhRgi4diqVPasvkJVaAECdqDCaPlcBtVpF9q+/kjTjI4zJlhOFPZ98kqAx0WjvccCl0WTmm/3XmbvtAjmFRly0aqZ0rUX3Bvc+Efy+5aXD9klwdDmggLM3tBsPDV8FtQ3aOdiRoij8ceMPZhyeQWKuZbt96zKtGd14NGU8bfT7EeJfQEKNFRJqhLAw6k0c+OUKJ363rP0IjvCi/Ws18fRzwZSTS+onn5D+3XdgMqFyc6PU22/h17cvKl3R62aSsgoY8cNJdl9IASxrbSY9UxNXnY2Cx62jsGkIJJywXJeuC0/NhTINbDO+HeUZ8vji5BcsP7sco9mIs8aZgbUG0r9mf3QaG6xFEuIxJ6HGCgk1QtztyvEUti8/hz7fiIu7lrYvVyWibikACmJjSZz0IfkxMQDoKlQgeMIE3CMbFzme2aywaMcl5v1+AbMCVYM9WdS7PhVKedimYLMJjiy1tFgozARU0KAftJsIbiW/0eSVjCtMPTiVQ4mWtULlvMoxpvEYmoU2c3BlQpRsEmqskFAjxF9lpuTz25LTpNywrIsJrx1Ay56V8ApwRTGbyVz/M8mzZ2NKt6xr8erShaCRI3AqVarIMfddSuW974+TmlOIu07DjG616VInxHZF5yRb1tqcWGW5dvOHqElQtzeobXB2jh0pisKvV39l1pFZpOZb1je1L9eeEY1GEOz+8E2BhXgcSaixQkKNENaZDGYObbzC8W03MZsVnLRqGnQKp96TZdFo1ZgyM0meP5+M71eDoqD28KDU++/j2+tFVE5OVsdMzipg0KpjHLxqCUMvNynHuKer4exkw3Uw1/607JJKOWe5Dou07JIKrmW7e9hJtj6bxccXs/L8SsyKGVcnV96u8za9q/dGqy7529eF+CdJqLFCQo0Q95Yen8vu72OJu5ABgE+QG237VCGkki8A+adOkTjpQwpOnwbAuVo1Sk+ZjGuNGlbHM5rMzPv9Aot2XAagVqg3i3vXJ8yv6BOMi81kgIOfWc63MeSCSg2N34C2Y8Cl5P97fj79PFMOTOFEimWtUEWfioyNHEvD4IYOrkyIkkNCjRUSaoT4e4qicPFwEnt/uER+lh6VCho/E0GDDuVQqVUoJhMZa9eSPHce5qwsVFotQWPH4PPCC0WenrsjNpkhq4+TkWfA08WJOT3q0L6GjadaMuMsJxKfXW+59giynEhcsxuU8FN9zYqZny/9zNyjc8kozACgS0QXhjYcSoCrDZqICvGIk1BjhYQaIe5fYb6Rvasv3On8XbaGP0/2r46Lh2VqxJieTsL4CeRs3w6A11NPETxpEhoP6z2P4jPyeWdlDMduZADwWovyjOpUFa0t+kf9/y5th80jIN3ydIjwlpYpqVJVbHsfO8goyODjYx+z7sI6FBQ8tZ4Mqj+InpV7oinh29eFsCcJNVZIqBGi+M7+Gc/u7y9gMpjx8HUm6pXqhFaxTEcpikL618tInjMHTCZ05csTOn8+LlWsd6rWG83M3HKeL/deBaB+WR8WvlSfEB8bn7RrLIQ/F8Ce2WAsALUWmr0LrUaAruQ3mjyVcorJByZzLt2yVqiaXzXGNxlPrVIlf62QEPYgocYKCTVCPJjUWzls+eIUmcn5AFSJDKZZt4q4eVnOWMmLiSFuyFCMSUmoXFwInjABn+efK3K8LacTGfHDCbILjPi6aZn3Ql3aVLHDKcG3r8Gvo+DCFsu1dxh0nAFVnyrxU1Ims4k1F9bwScwnZBuyUaGiW+VuDK4/GG9nb0eXJ8Q/SkKNFRJqhHhw+nwj+368xJm98aCAztWJJs9GUKNVKGq1CmN6OvEjR5G7dy8A3s8/T/D4cahdrT+FuZ6Wy9srYjgTnwXAu20rMjiqEk62no4COL/ZEm4yb1iuK7WHTjPBr+Q3mkzNT2Xe0Xn8cvkXAHydfRnSYAjPVnwWtapkb18XwlYk1FghoUaIh5d0LYtdK2PvnGtTqqwnrXtVIai8F4rZTNrnn5PyyUIwm3GuVImQ2bOLnI4qMJiYsuks3x2whI0mEX7M7lGHMr423B31X/o8y3TUnwvAbACNM7QcCs0Hg9bF9vezsSOJR5h6cCqXMi4BULdUXcY1GUcVv5K/VkiIhyWhxgoJNULYhtmscHZPHAd+vkJhnhFUUL1FCE2frYCLh5bcAweIGz4CU2oqaDT49elDwKBBRS4i/vl4HNE/niJPb8JFq2bQE5UY2DICnZMdnkSkXoTNw+HKTsu1b3noPBsqRdn+XjZmMBtYeW4li48vJs+Yh1ql5qWqL/FO3Xfw0Nno1GYhSiAJNVZIqBHCtvKy9Oz78RKx/9kh5eKupenzFajWtDTG1BSSpkwle+tWAJwCAwkaPQrPTp2sbv2+nJJD9I+nOPSfw/oqlHJn8rM1aVbRDluaFQXO/AhbxkCOpXaqdbGst/Eu+Y0mE3MTmXl4JtuubwMgwDWAEQ1H0Km89d+tEI86CTVWSKgRwj7iL2awa1Us6fG5gKVBZtuXq+FX2p2cPXtInDwFww3LFJN7s6YEjRuPc8Rf17MoisJPx+KYtvkcqTl6AJ6pE8K4p6oR6GWHKaKCLNg5w3J4n2ICrTu0HglN3gankt9ocl/cPqYdmsb1rOsANA5uzNjIsUT4RDi4MiFsS0KNFRJqhLAfk8nMyT9ucXjjVQyFJpx0atr0rkqVyGDMhYWkLfmStC++QNHrQavFf8CrBLzxhtWFxJn5BuZsjeW7A9cxK+Dh7MSQJyvTr2k5+ywkTjxtabdw84DlOqCK5Wyb8i1tfy8b05v0fH36a5acWkKhqRAntRN9q/fljdpv4Ka1w9okIRxAQo0VEmqEsL+c2wVsX36OW+dvA1C9ZQgte1bCSatBf+MGiVOnkrtrNwDakBCCxo3F84knrI51Oi6TsetPc+JmBmDp+j2la00ahtuhI7fZbGmQuW0C5FkaTVKrJ7SfAp5Btr+fjd3KvsWMQzPYdWsXAKXdSzOq0SieKPuETEmJR56EGisk1AjxzzCbFY5susrhzddAgYAwD6L6V8c/xANFUcj+/XeSpk3HmJAAgEebNgRPmoQ26K9n1ZjNCt8fvslHW86TmW8AoEeDMozuVBV/D2fbF59/G7ZPhiNLAQWcveCJcdBwAGisN+8sSXbc2MGMQzOIz40HoGVoS6IjownzDHNwZUI8OAk1VkioEeKfdeNsGtuWnqUgx4BaraJOVBgNO4ejc3HCnJdH6qefkvb1MjAa0fj5ETp7Fu7NmlkdKz1Xz0e/nmf1kZsAeLtqGdmxCi82KotGbYcnEXFHLVNS8ccs18G14Kl5ENbI9veysXxjPktOLuHrM19jNBvRqXW8Vvs1Xq35Ks4aOwRBIexMQo0VEmqE+Ofl3C5k9/exXD1hmdLx8HWmRY9KRNQrhUqlovDSJeKGj6Dw/HlQqQh45x0C3noTlcZ6r6Oj19MZt/4M5xIsh/bVKePNlK61qFXGDqfsmk1wdBlsnwQFmZb36r0MUZPA3d/297Oxq5lXmXZwGgcSLGuFwjzDGBM5hhahLRxcmRDFI6HGCgk1QjjOtVOp7Fl9gazUAgDK1vCj5QuV8Ql0w1xQQNLUaWSsXQtYdkiFzJqFk7/14GA0mfn2wHXmbL1ATqERlQr6RJZjePsqeLtpbV98Tgr8PhGOr7Bcu/pagk29l0Fdsk/1VRSF3679xqzDs0jOTwYgqmwUoxqPItjdxp3ShbATCTVWSKgRwrGMehNHt1wnZut1zEYFjZOaeh3K0qBDOZx0GjJ//pmEDyah5OfjVKoUoXPn4Nao6Ome5KwCpm4+x8/HLetH/N11jOlcjefrh9pncez1/ZYpqeQzlusyjSy7pErXsf29bCzXkMvi44tZcW4FJsWEq5Mrb9Z5k5ervYxWY4cgKIQNSaixQkKNECVDRlIeu1df4OZZy0F7XgEutHyhMuG1Aii8eJFbg4egv3wZNBpKDX4f/wEDUN3jici+y6lM+PkMl5JzAGgc7sfkrjWpEuxp++JNRjj0OeyYBvocUKmh0UB4Yiy4lPxGk7HpsUw9OJVjyZa1QhHeEYxrMo5GwSV/rZD495JQY4WEGiFKDkVRuByTwt61F8nNKASgfJ0AWr9UBVcnIwkfTCJrwwYAPFq3Jnjyh2gDi+7krTea+WrvVRZsv0i+wYRGraJ/s3AGP1kZD2c77FrKSoCtY+H0Osu1e6Bl+3ftniW+A7hZMfPL5V+Yd3Qe6QWWYPlUxFMMbzicAFc7nOAsxEOSUGOFhBohSh59gZHDm65xYvtNFLOCq5eO9gNqEFrZh4w1a0maOhVFr0ft4UGp997D96VeqJyKDilxGflM3nCWLWcs7Q+CvJwZ/3R1nqpV2j5TUld2wqbhkHbRcl2uBTw1GwKr2f5eNpZZmMknxz5hTewaFBQ8tB68W+9dXqjyAk7qkr99Xfx7SKixQkKNECVXWlwOW786Q3p8LioVNO4SQYOO5Si8EEvC+AkUnDoFgHO1agRPGI9bvXr3HG9HbDITfz7DjfQ8AFpWCmDSMzWIKGWHxo/GQti/EHbNAmM+qJ0srRZajwLnkt9o8nTqaaYcmMKZNMtaoap+VRnXZBx1SpX8tULi30FCjRUSaoQo2Qx6E7tXxXJ+v+UpS9kafrTtUw13Lycy1v5A8rx5mDMtW6u9u3cjcNgwnHx9ixyvwGDi052X+XTXZfRGMzqNmtdbRfBO24q46qxvGX8ot6/DlmiI3WS59gqFjtOh2jMlfkrKZDax7uI65sfMJ1ufDUC3St0YXH8wPi4+ji1O/OsV5+/3A+1HXLRoEeHh4bi4uBAZGcmhQ4eK/OyZM2fo1q0b4eHhqFQq5s+f/0BjFhQU8M477+Dv74+HhwfdunUjKSnpQcoXQpRAWp2Gdv2q80Tfqmi0am6cSWflBwc4sSMO7x49qPDrZryffx6AzB/WcaVjJ26vXoNiNlsdz0WrYciTldk6uBWtK5dCbzKzcMclnpy3i9/P2uG/Hb7loNdK6LUafMpCVhys6QvfdYO0y7a/nw1p1Bp6VunJhq4beLbCswCsu7iOp9c/zboL6zAr1n/HQpQ0xQ41q1evZujQoUycOJGYmBjq1KlDhw4dSE5Otvr5vLw8IiIimDFjBsHB1s9FuJ8xhwwZwoYNG1i7di27du0iPj6e5//zHzghxOOjWrMQeoxuSFB5LwyFJv784RJrph0mOV1NyLSplFu5AucqVTBlZpI4cSLXevUi/8yZIscLD3BnWf9GfNanPiHeLty6nc9r3xzhteWHufmf6SmbqtIR3jkErUaCRgeXt8PiJpYdU4Z829/Phvxd/ZnSYgrLOy6nkm8lMgsz+WD/B7z868ucSzvn6PKE+FvFnn6KjIykUaNGLFy4EACz2UxYWBiDBg1i9OjR9/ze8PBwBg8ezODBg4s1ZmZmJqVKlWLlypV0794dgPPnz1OtWjX2799PkyZN/rZumX4S4tGimBXO7U9g/4+XKci19H2q2iSYps9XxNVNze2VK0n5eAHm3FxQq/Ht1YtS77+H5h7/fufpjSzYfokv91zBaFZwdlIz6ImKDGwVgbOTHaak0i7D5uFw+Q/LtU856DwLKnew/b1szGg2svLcShYdX0SeMQ+1Ss2LVV7k3Xrv4qmzw3Z5IYpgt+knvV7P0aNHiYqK+t8AajVRUVHs37//gYq9nzGPHj2KwWC46zNVq1albNmyD3xfIUTJplKrqN48hN6TmlC9RQgA5w8ksvKDA5z5MxGfPi8TsXkzXk89BWYzt1es4HLnp8j85ReK+n81N50ToztV5df3W9Ikwo9Co5nZWy/Qaf4e9lxMsf0P4V8B+vwIPZaDZwhkXIeVPWHVS5Bxw/b3syEntRN9a/Tll66/0DG8I2bFzMrzK+nyUxc2XN5Q5O9YCEcqVqhJTU3FZDIRFBR01/tBQUEkJiY+UAH3M2ZiYiI6nQ4fH5/7vm9hYSFZWVl3vYQQjx4XDy1t+1Sl28gGBIR5UJhnZNeqC6z76Ai3C1wInTObssu+Rle+PKbUVOJHjuJG334UXrxY5JiVgjxZNbAJH79YlwAPZ66k5vLyV4d4Z2UMiZkFtv0BVCqo0RXePQTNBll2R8VugoWNYc8cMOptez8bC3IPYlbrWXzx5BeEe4WTVpDGmL1jePW3V7l0+5KjyxPiLiW7cclDmD59Ot7e3ndeYWFhji5JCPEQgiO86RHdiJYvVEbnoiH5ejZrZxxh58pYNLUaEPHzekoNGYLKxYW8w4e58tzzJM2cZZmeskKlUvFs3VD+GN6aV5qFo1bBppMJtJuzky/3XMFgsvHiWGdPywF9b+6Fcs0t27+3fwifNYcru2x7LztoGtKUdc+s47167+GiceFI0hF6bOjB3CNzyTPYYW2SEA+gWKEmICAAjUbzl11HSUlJRS4CtsWYwcHB6PV6MjIy7vu+0dHRZGZm3nndvHnzgeoTQpQcarWK2m3L8NKkJlSODAIFzuyOY+UHBzh/JA3/gQOpsGkjHlHtwGgkfelSLj/1NFlbfityusTLRcsHz9Rgw6AW1C/rQ67exJRN53h6wV4OXU23/Q8RWA1e2QTPfQ7upSD1AnzzDPwwALIf7In3P0Wn0TGw9kDWd11P27C2GBUjX5/5mmfWP8O269tkSko4XLFCjU6no0GDBmzfvv3Oe2azme3bt9O0adMHKuB+xmzQoAFarfauz8TGxnLjxo0i7+vs7IyXl9ddLyHE48Hd25kn+9eg65B6+JZ2Jz/bwB/fnOOnOTFkKt6ELVxImc8+RVumDMbEROIGD+bmawPRX7tW5Jg1Qrz54c1mfNStFr5uWmKTsun5+X6GrTlBak6hbX8AlQrqvAjvHoHGr1t6SJ3+AT5pCPsXW3pMlWChHqEseGIBC59YSKhHKEl5SQzdOZS3tr/FjaySvVZIPN6Kvftp9erV9OvXj88//5zGjRszf/581qxZw/nz5wkKCqJv376EhoYyffp0wLIQ+OzZswB07tyZ3r1707t3bzw8PKhYseJ9jQnw1ltvsXnzZpYtW4aXlxeDBg0CYN++ffdVt+x+EuLxZDKaObH9Joc3XcWoN6NSq6j9RBmaPBuB2mQg7YslpC1ZgmIwoNJq8R/4Gv6vv47axaXIMW/n6pn5WyzfH76BooCXixMjOlThpchyaNR2OEgv/jhsGgpxRy3XQTUtHcDL/v3OTkcrMBbw5akvWXp6KQazAZ1ax6u1XmVAzQG4OBX9Oxbiftn9ROGFCxcya9YsEhMTqVu3LgsWLCAyMhKANm3aEB4ezrJlywC4du0a5cuX/8sYrVu3ZufOnfc1JlgO3xs2bBirVq2isLCQDh06sHjx4vue9pJQI8TjLTu9gD/XXuTyMcsupoAwDzq+XhPvUm7or10jccpUcvfuBUBbpgxBY8fg2bbtPcc8duM249af5ky8ZaNBrVBvpnStSZ0wH9v/AGYzHPsGfv8A8m9b3qvbB56cBO4lv9Hk9azrTDs4jX3xlv/RLONRhujIaFqVaeXgysSjTtokWCGhRoh/h2unUtm+/BwFOQZ0Lhqe6FeNCvUCURSF7K3bSJo+HeN/dk16tGtH8JhotKGhRY5nMit8d+A6s7fGkl1gRKWCXo3LMrJDFXzcdLb/AXLT4PcJcOw7y7WLD0RNhPr9QG2Hs3RsSFEUtl3fxkeHPyI5z3J46hNhTzCq8ShCPEIcXJ14VEmosUJCjRD/Hjm3C9j65RkSLlt6RdVqW4amXSugddZgzs0lZfFi0pd/A0YjKhcXAt58E79X+6PWFR1SUrILmb75HD8eiwPAz13H6E5V6V6/DGp7TEndOAibhkGSpZknIfXh6bkQcu9mniVBriGXz058xrdnv8WkmHDRuPBGnTfoV70fWo3W0eWJR4yEGisk1Ajx72IymTm4/grHtlkWrnr4OdOyZ2XK1wlApVJRePEiiZM+JO/IEQB05csTPGE87n+z6eHAlTTGrz/NxeQcABqU82VK15pUK22H/66YjHD4S/hjCuizARU0GgBPjAPXopt5lhQXb19kyoEpxCTHAFDeuzxjI8cSWTryb75TiP+RUGOFhBoh/p2unUpl96oLZKdbDtUrV9Ofli9UxruUK4qikLVhA0kzZ2FKTQXAq3NnAkeNQhsUWOSYBpOZr/+8yvzfL5KnN6FRq+jXNJwhT1bC08UOTyKyE2HrODi11nLtFgDtJ0OdXiW+A7iiKGy8spHZR2aTXmDZIt+pfCdGNBxBKbdSDq5OPAok1FghoUaIfy+D3sTRzdc4tu0GZpOCxklN/Y7lqN+hLE5aDaasLFI+XsDtVavAbEbt7k7AoHfx69MHlZNTkeMmZOYzeeNZNp+yrNEJ9HRm3NPV6VK7NCp7hI2ru2HTcEiNtVyXbWbZJRVU3fb3srEsfRafxHzCmgtrMCtm3LXuvFv3XV6s+iJO6qJ/x0JIqLFCQo0Q4nZiLru/v8Ct85bdRV6lXGn1YmXK1fAHIP/MGRI//JCCEycBcK5SheCJE3CrX/+e4+66kMLEn09zLc1ysm6zCv58+GxNKgZ62P6HMOrhwCLYNRMMeaDSQJO3oM1oy6nFJdzZtLNMOTCFU6mWtUJVfKswrsk46gbWdWxhosSSUGOFhBohBFimQy4dTebPtRfJzbT0XYqoV4oWPSrh6eeCYjaT8cMPpMyZiynTstDY+7nnCBwxHCc/vyLHLTCY+GL3FRbtuESh0YxWo2JgywjefaIibjo7PInIuAm/RcO5DZZrz9LQYRrUeK7ET0mZFTPrLq5j/tH5ZOkt2+Wfq/gcgxsMxs+l6N+x+HeSUGOFhBohxP9PX2Dk0MarnPzjFopZwclZQ6PO4dRpF4bGSY3x9m1S5s4lY+0PAKi9vQkcMhifHj1QaYreWn0jLY8PNpzhj/OWLc2hPq5M6FKd9tWD7DMldXEbbB4Bt69ariPaQufZEFDR9veysdsFt5kfM58fL/4IgJfOi/frv0/3yt1Rqx7b1oSimCTUWCGhRghhTVpcDrtWxZJwyfJUxjfYjda9qhBaxbK7KO/YMRI/nEzhuXMAuNSqRfDEibjWrFHkmIqisO1sEpM2nCUuIx+AJ6oG8kGXGpT1d7P9D2EogD/nw565YCoEjQ6avQcth4HODvezsePJx5lyYAqxty1rhWoF1GJsk7HU8C/6dyz+PSTUWCGhRghRFEVRiD2QyL4fL5GfbQCgcuMgmnWriLu3M4rRyO1V35Py8ceYc3JApcK314uUev99NN7eRY6brzexcMdFvth9BYNJwdlJzTttK/J6qwhctHY4SC/9CmweCZe2Wa59ykKnmVClk+3vZWNGs5Hvz3/PwuMLyTXkokJFzyo9GVRvEN7ORf+OxeNPQo0VEmqEEH+nINfAwZ+vcHpPHCigc9EQ+WwENVuFotaoMaakkDRzFlkbLOtYNH5+BI4YgXfXZ+85tXQpOYeJv5zmz0tpAIT7uzHp2Zq0rmyHLc2KAuc3wq+jIeuW5b3KnaDTDPANt/39bCwlL4XZR2az+epmAPxc/BjWcBhdIrrYZ/pOlHgSaqyQUCOEuF/J17PYtTKW5OvZgKWPVOteVQiOsDwxyD14iMTJH6K/dBkA1wYNCJ4wAZcqlYscU1EUNp5MYPLGsyRnW7p+d64VzPinq1Pa29X2P4Q+17JDav9CMBvByQVaDbdMSzk52/5+NnYw4SBTD07laqZlrVD9wPqMazKOSr6VHFyZ+KdJqLFCQo0QojjMZoWze+M5sP4yhXlGAKo3L03T5yri4qFF0etJ/+YbUhYtRsnPB40Gv5dfJuDdd9F4uBc5bnaBgXnbLrJ8/zVMZgU3nYb321Xi1Rbl0WrssDg2+TxsHg7X9liu/StC51lQ4Qnb38vGDCYD35z9hs9Pfk6+MR+NSkOfan14q+5buGuL/h2Lx4uEGisk1AghHkRelp79P13i/H7LAXvO7k40e64i1ZqVRqVWYYiPJ2n6DLK3WdaxOAUGEhQ9Gs+OHe85XXIuIYvx609z5LrlzJzKQR58+GxNmkT42/6HUBQ49QP8NgZyLbuyqPGcZQu4V8lvNJmQk8BHhz9i+43tAAS6BTKy0Ujal2svU1L/AhJqrJBQI4R4GPGXMti9Kpa0uFwAgsp70bpXFUqVtRx4l7NnD4mTp2C4Yek15d6sKUHjxuMcUb7IMc1mhXUxt5j+63nScy1n5jxXL5TozlUJ9HSx/Q9R8P/au/OwKOv9/+PPmQGGfd8RQRT3HQUxFzTM1EpPm2mpdTRt0Y56+patlpZaWplLLqVZncqyYx63TMUlc0PFfUdAQAFZZN+Gmfv3x22cw68RBAcXej+ua66rufnMZ4l0Xt33Z8mD7TMgdikoJrBxhKjXIGIc3AUHTf6W+hsz988ktVCdKxTpF8nrEa8T7BJ8ezsm6pWEGjMk1AghbpbJaOLY9lRi1yViKDOi0UDbqEZEPBSC3s4KU1kZ2Z9/QfbSpSjl5WBtjcfov+M5bhxau+vPm8ktLmf2r2f5LjYZRQEnvRUv92/BU92C0NXHCeBpx9QTwFNj1fferdXjFoK6W74tCyutKGX5ieUsO76MclM51lprnmn7DGPajcHOqh7mJonbTkKNGRJqhBCWUpRbxu6fznP+oPoox87ZhnseaUbzcHWDvfLkZNLff5+inb8BYO3vj8+bb+DUt/p5LEdTcnlzzQmOX1L3zGnj78z0IW3p3LgeTuQ2meDIv2DLVChRD5qkw3DoNw0c7/yDJpPzk5kRO4Pdl3YDEOAYwJTwKUQFRt3ejgmLk1BjhoQaIYSlpZzJ4bfvz5GboZ755B/qSq9hzfHwd0RRFAq2biVjxkwq0tIAcIyKwufNN7Bp1Oi6dRpNCt/FJjN70xnyS9UJysPCA3mlf0vcHGwsP4jiHIh5Fw59BShg6wJ934IufwdtPeylY0GKohCTHMOs2FlkFGcAEBUYxZTwKQQ4Btzm3glLkVBjhoQaIUR9MBpMHIlJ5uCGJCoMJrRaDR2iA+kyMBgbWytMxcVkLVpM9ooVYDCg0evxfG4c7qNHo7W5fkjJKixj1i9n+OmQOn/Ezd6aV+9vyeNdAtHWxyOp1IOwfhKkq4d54tcRHvgYAsIs35aFFRuKWXxsMd+c/IYKpQJbnS3Ptn+Wp9s8jY2uHoKguKUk1JghoUYIUZ/ys0r4fdV5Eo9mAeDopqfHY6GEdPJCo9FQduEC6dOmU7x/PwA2QUH4vPUWjj3uqbbeA0k5vLXmBGfS1T1zOjV25b0hbWnjXw+77JqMcHA5xEyHsjxAA2FPw71vg/2df9DkhdwLvLfvPQ5mHAQg2DmY1yNeJ9I/8jb3TNwMCTVmSKgRQtwKScez2PXDOfKzSgFo3MadnkOb4+ptj6Io5G/YSMYHszBmquHH6f778XltCtY+Ptet02A08dWeJD7Zco6iciNaDYyMDGbyfc1xtq2HVUuFV2DzW3Bspfre3kOda9NhOGjv7IMmFUVhQ+IG5hyYQ3apuoPz/cH383KXl/FxuP6/Y3HnklBjhoQaIcStUlFu5NCmi8RtvoipQkFnpaVT/8aE9Q/CykaHsaCAzPnzufqvb8FkQmtvj+f48biPeAqN9fVDSnpeKe9tOMX6Y+ocHS8nPW8MbMXgjv71s19L0m51lVSmepgngd3UVVK+bS3floUVlBew8MhCvj/zPSbFhL2VPS90fIHhrYZjrb3zl6+L/5JQY4aEGiHErZabUcyuH86RfEpdXeTsaUvPoc0JbucJQOmZM6S/O42Sw4cB0IeG4jv1bey7dKm23t/PZ/H22hMkZKp75nQLcWf64LaE+jhZfhBGA+xbBDtmgaEINDp1X5uo18D2zv+79HT2ad7b/x7HMtW5QqFuobwZ8SadfTrf5p6JGyWhxgwJNUKI20FRFC7EZfL7qvMU5apnPjXp4EmPx0Nx9rBDMZnI+/lnrsz5CONVdXdhl8GD8f6/l7Hy9LxuvWUVRr7Ylcj8becpNZiw0moY3bMJL/UNxUFvZfmB5F1SdyQ+tUZ97+gL/d+Hto/AHb6rr0kxsSZ+DZ8c+oTcslxCXEJY/dBqdHf46i6hklBjhoQaIcTtVF5awYENSRyLScFkUrCy1tJlUDAdoxujs9JizM3lysefkLtqFSgKWicnvCZNxG3oUDS663/5puQUM239KbacUpc0+7vY8vaDrenfxrd+HknFx6hnSeUkqO+b9IKBH4HX9Q/zvFPkluYyN24uA5oMIMIv4nZ3R9wgCTVmSKgRQtwJsi8X8tv357h8PhcAN197ej3RnEYt1dVFJceOkf7Ou5SeOgWAbZs2+E59G7v27autd+upDN5Zd5LUqyUA9G7uxbsPtSHYsx4OfjSUwp55sOsjqCgFrTV0n6CeAm4jB00Ky5JQY4aEGiHEnUJRFM7tT2f3v+MpKTAAENrVh3sebYaDix7FaOTqypVkzv0UU0EBaDS4PvYY3pMnoXN1vW69JeVGPtsRz5KdCZQbTdhYaXm+d1Oej2qKrXU9PGrJSYRfXoXzv6rvXQLh/lnQctAd/0hK3D0k1JghoUYIcacpKzaw/z8JnPjtEooC1rY6Ih4MoV1UAFqdloqsLK7MnkPef/4DgM7NDe+X/4nL3/6Gppql1QmZhUxde5Jd59Vl443d7Xl3cBv6tPC2/CAUBc5uhF+mQJ56mCeh/WHAB+B+/cM8hbhREmrMkFAjhLhTZSYXsOO7s1xJygfAI8CR3sOa49fMFYDiAwdInzaNsvPxANh16oTv1LexbdnyunUqisLG4+lMX3+K9Hx1z5z+bXx4+8E2BLjWw8GP5cWwaw7sngcmA1jZQs9/QveXwLoeThwXfxkSasyQUCOEuJMpJoVTuy+zd80FyorUM59advej+9+aYudkg2IwkPPNv8hasABTcTFotbg99SReL72EztHxuvUWllXw6dZzLN+dhNGkYGet46V7Qxndowk2VvWwkV7mOXUiceJO9b17CAycDc2iLd+W+EuQUGOGhBohxN2gpLCcvasvcHqPusGe3t6KbkOa0rqHP1qtBkN6OhkzZ1HwqzqPReflic+rU3AeNLDa1U5n0vN5e81JYpPUPXOaeTsybXAbuje9/rLxOlMUOLkaNr0OhenqtdaDof9McJGDJkXtSKgxQ0KNEOJuknYhj53fnyU7tRAA7yAnejwWWvlIqvD33WRMn075xYsA2EdE4Pv2W+ibNr1unYqisDruEjN/OU1WYTkAgzv688bAVng718MjotJ8ddO+/YtBMYK1A0S9Ct1eAJ3s6itujIQaMyTUCCHuNiajieM7L7F/bQKGUiMAga3dCX+gCb4hLpjKy8lZtoysxUtQysrA2hqPp5/G8/nn0NrbX7fevGIDczaf5V/7L6Io4KS3YlK/5oyMDMJKVw+PpNJPqMctpOxT33u1gkFzILiH5dsSDY6EGjMk1Agh7lZFeWXErk3gzN50TCb1r+zGbTwIf7AJPsHOlKemkvHe+xTu2AGAlb8fvq+/juO991b7SOpYai5vrTnB0dQ8AFr5OfPekLaEBblZfhAmExz9Hra8BcXqQZO0Hwr9poOTHDQprq823991iuQLFy4kODgYW1tbIiIiiI2Nrbb8qlWraNmyJba2trRr146NGzdW+blGozH7mj17dmWZ4ODgP/181qxZdem+EELcVRxc9PQZ0Yrh73ajZXc/NFoNySez+WnWQdYvPEqB1pXAxYto9NlCrP39qbicRur4CaQ89xzlKSnXrbd9I1dWv3AP7/+tLS521pxOy+eRRXt45aejpOWVWHYQWi10ehLGH4Qufwc0cOwHWNAF9i8Fk9Gy7Ym/pFrfqfnhhx8YOXIkixcvJiIigrlz57Jq1SrOnj2Lt/ef90DYs2cPvXr1YubMmTzwwAN89913fPDBB8TFxdG2rXrSa3p6epXP/PLLL4wePZr4+HhCQkIANdSMHj2aZ599trKck5MTDg43tnul3KkRQjQUuVeKObQxibP701EU0Go1tL83kK6DgrFSDGQtXkL28uVgMKCxscFj3Fg8xoxBq9dft87swjJm/XKGVYdSAbCx0jI8vDHPRzXFpz7m21w6BOsnQ9oR9b1vexj0MQR2tXxb4q5Wr4+fIiIi6Nq1KwsWLADAZDIRGBjIhAkTmDJlyp/KDx06lKKiItavX195rVu3bnTs2JHFixebbWPIkCEUFBQQExNTeS04OJiJEycyceLE2nS3koQaIURDk5tRzJ7V8SQeVTfZc3TTc8+joTTt7EV5YhLp06dRvFedx2LduDG+b72JY8+e1dZ56GIOH/xytnKVlN5Ky5MRQTwXFYK3k4XDjckIh76EmGlQqj4Co/MoiH4H7N0t25a4a9Xb46fy8nIOHTpEdPR/9xvQarVER0ezd+9es5/Zu3dvlfIA/fv3v275jIwMNmzYwOjRo//0s1mzZuHh4UGnTp2YPXs2FRUV1+1rWVkZ+fn5VV5CCNGQuPrYM/D59gx6sT3OnrYUXi3j189PsG7+UUocfGi8fDkBH3+ElZcXhuRkUp4dS+qElzCkpV23zrAgd34Y141vx0QQFuRGWYWJ5bsT6fXhdt7fcIqswjLLDUCrg65jYPwh6DBcvRb3FcwPg7iv1Xk4QtRCrUJNVlYWRqMRH5+qk7p8fHz+9AjpD+np6bUq/9VXX+Hk5MTDDz9c5fpLL73EypUr2b59O+PGjWPGjBm88sor1+3rzJkzcXFxqXwFBgbeyBCFEOKuE9zOk2FvR9BlYDBaKw0pp3L4fvp+YtclYh/dn5BfNuI+ahTodBRs2cKFQQ+Q/cUXKOXlZuvTaDTc08yTn56L5Ou/h9OpsSulBhOf70qk5wfbmfnLaXKKzH+2Thy94G+L4JlfwLs1lOTA2gmwvD+kHbNcO6LBq4e1ezdn+fLlPPnkk9jaVr3NOXnyZKKiomjfvj3PPfccH330EfPnz6eszPz/Nbz22mvk5eVVvlKqmSwnhBB3OysbHREPhTDsrQgat3bHVKFwcGMS30/bT0pCKT6vTaHJ6n9j17kzSnExV+Z8RMLfHqZo//UXemg0Gno192L189358pmudGjkQonByJKdCfT4YBsfbDrDVUuGm6DuMO43uO99sHGE1FhY2ls9NPOPx1NCVKNWocbT0xOdTkdGRkaV6xkZGfj6+pr9jK+v7w2X37VrF2fPnmXMmDE19iUiIoKKigqSkpLM/lyv1+Ps7FzlJYQQDZ2rjz0PTOjA/WPb4uimJz+rlA2fHWPjomOUezQm6F/f4DdjBjp3d8ovXCB51Cgu/d8rlKdeum6dGo2GPi28WfPiPSwb1YW2Ac4UlxtZtOMCPT7Yxpxfz5JbbKFwo7OG7uNh/AFo8zAoJnXzvgVd4diP6m7FQlxHrUKNjY0NYWFhVSbwmkwmYmJiiIyMNPuZyMjIKuUBtmzZYrb8smXLCAsLo0OHDjX25ciRI2i1WrMrroQQ4q9Mo9HQtLM3w6ZG0KlfY7RaDYlHs/j+3f3EbU7G6aHBNP1lI67DngCNhvx167gwYABpU9/BcPlytfXe28qHdeN7sHREGK39nCkqN7Jgezw9P9jOx1vOkVdisMwgnP3hsS9hxBrwaAaFGbD6WfjqQbhyxjJtiAanTku6R40axZIlSwgPD2fu3Ln8+OOPnDlzBh8fH0aOHElAQAAzZ84E1CXdvXv3ZtasWQwaNIiVK1cyY8aMKku6QZ3d7Ofnx0cffcRzzz1Xpc29e/eyf/9++vTpg5OTE3v37mXSpEkMGDCAr7766ob6LaufhBB/VdmXC/nt+3NcPp8LqHdzeg1rTmBLd0qOnyDzk48p2qMu3tBYW+P62KN4jB2L9XXuwP/BZFLYfCqDuVvPcSa9AAAnWyvG9AjhmR7BONta6CiEijLYMx9+mwMVJaC1gsgXodcroL/+YZ6iYaj3HYUXLFjA7NmzSU9Pp2PHjsybN4+IiAgAoqKiCA4OZsWKFZXlV61axZtvvklSUhKhoaF8+OGHDBw4sEqdS5cuZeLEiaSlpeHi4lLlZ3FxcbzwwgucOXOGsrIymjRpwogRI5g8eTL6avZd+F8SaoQQf2WKonAuNoPd/46nJF99VBTaxZuIwU1x8bKj+OBBMucvoHj/fgA0Nja4Pv44Hs8+i7VP9XfETSaFTSfTmbv1HOcy1LOqXOysebZnE56+pwmOeivLDOLqRdg0Bc5e28DVuRHcPxNaPQjV7Jws7m5yTIIZEmqEEALKig3sX5vIiZ2plRv3tYz0JWxgMM4edhTt20/mgvmUHDwEgEavx+2JoXiMGYOVl1e1dZtMChuOp/FpzHnir6jhxtXemrG9QhgVGYyDpcLN2V/gl1cgN1l93ywaBnwIHtc/zFPcvSTUmCGhRggh/iszuYC9ay6QckrdZE+r09Cqux9hA4JxdNNTvG8fmfPmU3L4MAAaW1vchg3DY8xorDw8qq3baFJYf+wyn249T0JWEQDuDjaM7RXCyMgg7G0sEG7Ki+H3j2H3p2AsB50eekyCHhPB2u7m6xd3DAk1ZkioEUKIP0uLzyV2fSKpZ64Carhp3cOf8AebYOtgTdHuPWTNn0/J0aMAaOzscH/qSTzGjUPnWP18lgqjibVHLzMv5jxJ2cUAeDra8HxUM0ZGBmFtiRPBs+Jh48uQsF197xYM970HLR+QR1INhIQaMyTUCCHE9V0+f5XYdYlcOpcLgN7BisghTWl9jz9ooGjXLjLnL6D0+HEArLy88J7yKs4DB1Z7Ejio4ebnw5eYvy2e5Bw13LTwcWL6kLaEN7HAcQiKAqfWwKbXoODabsl+HSDqNWh+v4Sbu5yEGjMk1AghRM1Sz17l9x/Pk31JnRPj08SZ3sNa4NXYCUVRKNy+g4wPZmG4qM5nsY/shu9bb6MPaVJj3QajiVUHU5n96xmuFqtLvx/uHMBrA1rh5XRjiz6qVVYAv38C+xaDQX3shX8niHodQvtJuLlLSagxQ0KNEELcGJPRxPEdl9i/LgFDqRGNBtr0CqDroCbYO9tgKisje9kyspcsRSkrA2trPJ55Bs/nn0NrV/N8lqtF5Xz461lWHkhGUdRl4K/0b8HwiCB0WgsEj6Js2DMPYpeCQb0zREAX6PMaNL1Xws1dRkKNGRJqhBCidopyy9j973jOH1B3hbey1tI2qhGd72uMnZMN5SkpZLz3PoU7dwJg7e+Pzxuv49i3b42PpAAOJ1/lzTUnOHlZPXC4XYAL04e0pWOgq2UGUJgJez6F2C/U/W0AGoVDn9chJErCzV1CQo0ZEmqEEKJuUs9eZd+aC2QkquHDSq+jfVQAHfs1xtbBmsJt20h//30qLqvzWRx798bnzTewuYGDhI0mhW/3X2T2r2cpKK1Ao4Enujbm1ftb4GpvY5kBFGSoq6QOLoOKUvVa40g13DTpZZk2RL2RUGOGhBohhKg7RVG4eCKb2HWJZCaruwdb63W079uIzv2DsDKVk7V4CdlffgkGAxq9Ho8xY3B/5hl0jg411p9ZUMbMjadZfVg9g8rdwYYp97fk0bBGaC3xSAqgIF2dc3PwSzBeOww5qIf6WCq4h2XaEBYnocYMCTVCCHHzFEUh6Xg2sesSyEpRJxM7uunp8VgoIZ28KE9MJH3adIr37QNA5+KC++jRuD85HK1DzeFmf0I2b/3nROXOxGFBbkwf3JbW/hb8ezv/Muz6GOK+Uve4AfWOTdTrEGT+HENx+0ioMUNCjRBCWI6iKCQcyWT3T/EUZKuPdBq3cafn0Oa4eNlRsGkTmZ/OozwpCQCdmxseo/+O2/DhaO3tq63bYDTx5e5E5m49T3G5Ea0GRnUPZnK/5jhZ6jwpgLxU2PURxH0DpmsHcYb0UR9LBYZbrh1xUyTUmCGhRgghLK+i3MihTReJ23wRU4WCzkpLp/6NCesfhE6rkL9hA5mffVa5BFzn4YHH6NG4DXuixpVSaXklvLf+NBuOq3N1vJz0vDmoFQ918L+hicg3LDdZPSzzyLdgqlCvNYtW79w0CrNcO6JOJNSYIaFGCCHqT25GMb/9cK7y2AVnT1t6Dm1OcDtPlIoK8tauI2vRIgwpKQDoPD3xfHYMrkOHorW1rbbu385lMnXtSRKvHbkQGeLB9CFtaObtZNlBXE26Fm6+A8WoXgvtr8658e9k2bbEDZNQY4aEGiGEqF+KonAhLpPfV52nKFediNukgyc9Hg/F2cMOxWAgb+1asj5bhOGSOiHYyssLj7FjcX38MbT662/AV1ZhZOnOBBZsj6eswoS1TsPoHiG8dG8zy5wl9b9yEtRwc/R7UEzqteYD1HDj18GybYkaSagxQ0KNEELcGuWlFRzYkMSxmBRMJgUray1hA4Jp37cRNrZWKOXl5K5ZQ9bixZXLwK18fPB84QVcH30EjU533bpTcop5Z+1JYs5cASDA1Y4pA1oyqJ2f5VZJ/SH7Auz8EI7/+N9w0/IBiJoCvu0s25a4Lgk1ZkioEUKIWyv7UiE7vz9LWnweAHZO1nTqF0TbqACsbXSYysvJW72arMVLqEhPB8C2bVt8p07Frl3bauveciqDd9ae5FKuuqleCx8nJkaH0r+Nr+XDTdZ52PkBHP8JuPaV2eoh9Wwpn9aWbUv8iYQaMyTUCCHEracoCudiM4hdn0h+phpA7Jys6dw/iLa9ArC6Fm5yV64kc958TIWFoNHgOvRxvCdNQufict26S8qNLPntAst2JVJQpk7wbeXnzMToUO5r7WPZycQAV86o4ebkz6jhRgNthkDvKeDd0rJtiUoSasyQUCOEELePyWji7P50Dm5MIj9LXQJu72xD5/uDaNPTHytrHRWZmWTMnk3+2nUA6Nzd8X75ZVyGDEaj1V637rxiA8t+T2D57iQKr4WbNv7OTIpuzr2tvC0fbjJOwc5ZcOo/1y5ooO0j0PtV8Gpu2baEhBpzJNQIIcTtZzSaOLsvnYMbkijIUcONg4sNYQOCaX2PPzprLUX7Y0mfPo3y+AsA2HXujO/Ut7Ft0aLauq8WlfPF7wms2J1EUbm6eql9IxcmRTcnqoWX5cNN+gnYMRPOrFffa7TQ7jE13Hg0tWxbf2ESasyQUCOEEHcOY4WJM3vTOLgxicKr6kopRzc9YQOCadXdD62pgpyvvyZz4WcoJSWg0+H+1FN4ThiPztGx2rpzispZ+lsCX+1JosSghpuOga5MjA6ld/N6CDdpx2DHLDi7QX2v0UL7J6D3/4F7iGXb+guSUGOGhBohhLjzGA0mTu+5zMFfLlYuA3d01xP+QBNadvOjIiOdjJmzKNi8GVCXgHu//E+cH3ig2lVSAFmFZSz9LYGv9yZRalBXL3Vu7Mqkfs3p0czT8uHm8mE13JzbpL7X6KDDMOj1Mrg3sWxbfyESasyQUCOEEHeuCoORU79f5tCmixTnqecx+YY402tYC7wCnSjctYv06e9hSFZ3JrYJCcHzhRdwHnB/jeHmSkEpS3Ym8K99FymrUMNN12A3JvVrTvemnpYfTOoh9bFU/Bb1vdYKOg6HXv8Hro0t314DJ6HGDAk1Qghx56soN3J8xyUObEjEUGZEo4F2UY0IfygEa62RnBVfkb18OaY8dZm4TbOmeL34Ik79+1c7mRjgSn4pi3Ze4Nv9yZRfCzcRTdyZ1K853UI8LD+YlAOwYwZc2Ka+11pDp6eg5z/BNdDy7TVQEmrMkFAjhBB3j8KrZez+93niD6qb7Nk729BtSFNaRPiglBST8/XX5Kz4ClN+PgD60FA8x4/HqV90jeEmPa+URTvi+T42hXKjGm66N/VgUr/mdA12t/xgkvfB9hmQuFN9r7OBziOhx2RwCbB8ew2MhBozJNQIIcTdJ+V0Dr+tPEduRjEALt52dB3UhNCuPiiFBeR8/Q05K1ao+9sA+hYt8JowHsd7761xzszl3BI+2xHPDwdSMBjVr8KeoZ5MjG5OWJCb5QeTtFt9LJW0S32v00PY09BjEjj7Wb69BkJCjRkSaoQQ4u5kNJg4uj2Fw78mU1pkAMDN154ug4JpFuaDUpBPzldfkfPV15iK1EMv9a1b4TV+PI59+tQYblKvFrNw+wVWHUyhwqR+JfZq7sWk6FA6Na6HcJP4G2yfCcl71PdWttDl73DPRHDysXx7dzkJNWZIqBFCiLtbeWkFx3ekcnhzMmXF6iZ7bn4OdB0UTLPO3pjy88j+cgVXv/kGU7F6Z8e2bVs8x7+IY+/eNYablJxiFmyL56e4VIzXwk2fFl5M6tec9o1cLTsYRVEfR22fASn71WtWdtB1tBpuHL0s295dTEKNGRJqhBCiYSgvqeDY9hSObE2pDDfu/g6EP9CEkI5eGPNyyVm+nJxvv0P5I9y0b4/XhPE49OhRY7i5mF3E/G3x/Hz4UmW4iW7lzcTo5rQNuP6xDXWiKOpE4h0zIfWAes3aHsKfhe7/AId6mMB8l5FQY4aEGiGEaFjKig0c3ZbK0a3JlJeqm+x5BDgS/mATmnTwxHj1KtlfLOPqd9+hlKq7F9t17IjnhPE4dO9eY7hJyipiXsx51hy5xLVsw32tfZgY3ZzW/hb+HlEUiN+q3rm5HKdes3aAiHHQfQLY18ME5ruEhBozJNQIIUTDVFpk4GhMCke3pWC4Fm68GjsROaQpga3dqcjKUsPN99+jlKkb/NmFheE1YQIO3SJqrP9CZiHzYs6z9uhl/vjGHNDWl39Eh9LStx7Czblf1aXgaUfVazZOariJfPEvGW4k1JghoUYIIRq20kIDR7Ymc3R7KhVlarhp2tmbHo81w9HNFsOVK2R/8QW5K39AKVc3+LPv2lW9cxMeXmP95zMK+DTmPBuOp1WGm0Ht/Zh4byihPk6WHYyiwNmN6mOp9OPqNb0zdHseur0Adq6Wbe8OJqHGDAk1Qgjx11BSWM7BDUkc35GKooCVXkf4oCa0v7cROp0WQ0YG2Us/J/fHH1EM6moq+27d8JowHvuwsBrrP5dRwKdb1XADoNHAg+39eeneUJp5V38uVa2ZTOqZUttnwpWT6jW9i3rXpttzYGvhOT53IAk1ZkioEUKIv5as1AJ2fneO9AR192F3fwfuebQZga3c0Wg0GNLSyFq6lNyf/g3Xwo1D90g8J0zAvlOnGus/nZbPp1vPs+lkOgBaDQzuGMBL94bSxNPBsoMxmeD0WvVsqczT6jVbV+g+HiKeA72F7xTdQWrz/V39tovXsXDhQoKDg7G1tSUiIoLY2Nhqy69atYqWLVtia2tLu3bt2LhxY5WfP/3002g0miqv+++/v0qZnJwcnnzySZydnXF1dWX06NEUXttsSQghhPj/eTZy4uGXO9NnREtsHa3JuVzEunlH+XlOHClncrDy9cVv6lSabfoF18cfBysrivbs5eKw4SQ/O5aSY8eqrb+VnzOLR4Sx4aUe9Gvtg0mBnw9f4t6PdvDPH49yMbvIcoPRaqHNEHh+Dzy6HDybQ2kubHsP5raHXR9DmXwn1vpOzQ8//MDIkSNZvHgxERERzJ07l1WrVnH27Fm8vb3/VH7Pnj306tWLmTNn8sADD/Ddd9/xwQcfEBcXR9u2bQE11GRkZPDll19Wfk6v1+Pm9t9NjwYMGEBaWhpLlizBYDDwzDPP0LVrV7777rsb6rfcqRFCiL+u0iIDBzckcWLXJYzXTuz2D3Ul/IEmBLRQv2vKUy+RtXgReT+vAaM6J8ehdy+8xk/Arl3bGts4nprH3K3niDmjHu2g02p4uFMAE/qG0tjD3rIDMhnhxGrYOQuy49Vr9h5wzz+g6xiwsfCdotuoXh8/RURE0LVrVxYsWACAyWQiMDCQCRMmMGXKlD+VHzp0KEVFRaxfv77yWrdu3ejYsSOLFy8G1FCTm5vLmjVrzLZ5+vRpWrduzYEDB+jSpQsAmzZtYuDAgaSmpuLv719jvyXUCCGEKMot49CvFzm16zLGa4daBjR3JfzBEPxDXQEoT04ma9Fi8taurQw3jn364DVhPLatW9fYxpGUXOZuPceOs5kAWGk1PBrWiPF9m9HIzcLhxlgBJ36CnR9AToJ6zcFL3cCvy9/BxsLt3Qb19vipvLycQ4cOER0d/d8KtFqio6PZu3ev2c/s3bu3SnmA/v37/6n8jh078Pb2pkWLFjz//PNkZ2dXqcPV1bUy0ABER0ej1WrZv3+/2XbLysrIz8+v8hJCCPHX5uCqp9fQ5jw1PZJ2vQPQWmm4dC6Xnz+K4z9zD5MWn4tN48b4z5xB0w3rcRn8EGi1FG7fTuLDj5AyfjylZ85U20bHQFdWPBPO6he60zPUkwqTwsoDKfSZs4PXfz7O5dwSyw1IZwUdnoAXD8Dgz8AtGIoyYfMbMK8j7FsEBgu2d4erVajJysrCaDTi41P1bAofHx/S09PNfiY9Pb3G8vfffz9ff/01MTExfPDBB+zcuZMBAwZgvJaQ09PT//Roy8rKCnd39+u2O3PmTFxcXCpfgYFyzLsQQgiVo5ueXsNa8NS0SNr0CkCr05B65iqr58Sxdt4R0hPysAkOxv+DDwhZvx7nBx8EjYbCrTEkDvkbqS/9g9Kz56pto3NjN74ZHcG/n4+kRzNPDEaF7/YnEzV7B2+tOUFanoXDTacnYfxBeGg+uDaGwgzYNAU+7Qj7l4Kh1HLt3aHqNFHY0p544gkeeugh2rVrx5AhQ1i/fj0HDhxgx44dda7ztddeIy8vr/KVkpJiuQ4LIYRoEJzcbYka3oInp3WjdQ9/tFoNKady+PeHh1g3/ygZSfnoQ5oQMPtDQtavw3ngQNBoKNi8mcTBg0mdNImy+Phq2wgLcudfYyL4cVwkkSEelBtNfLPvIr0/3ME7a0+SkW/BsKGzhs4jYfwheGAuODeCwnT45f9gXic48AVUlFmuvTtMrUKNp6cnOp2OjIyMKtczMjLw9fU1+xlfX99alQcICQnB09OT+Gv/ofj6+nLlypUqZSoqKsjJybluPXq9Hmdn5yovIYQQwhxnDzv6PNWSJ6d1o1V3PzRaDckns/lp1kE2LDzKlYv56Js2JeDjjwhZ+x+crq3QLfhlEwkPPsSlf75MWUJCtW2EN3Hn+7Hd+P7ZboQ3cafcaGLFniR6fbidaetOcaXAguHGyga6PAMvxcGgj8A5AAouw4Z/wrzOcPBLqCi3XHt3iFqFGhsbG8LCwoiJiam8ZjKZiImJITIy0uxnIiMjq5QH2LJly3XLA6SmppKdnY2fn19lHbm5uRw6dKiyzLZt2zCZTERE1LzFtRBCCHEjnD3t6DuyFcPfiaBlN180Gkg6ns2qmQfZuOgYmSkF6ENDaTT3E5r8Zw1O/fqBopC/YQMJDzzIpVdeoSwxsdo2Ipt68MPYbnw7JoIuQW6UVZhYvjuRXh9u5/0Np8gqtOCdFCu9uhpqQhwMmA1OfpCfCusnwvwwiPsajAbLtXeb1WlJ96hRo1iyZAnh4eHMnTuXH3/8kTNnzuDj48PIkSMJCAhg5syZgLqku3fv3syaNYtBgwaxcuVKZsyYUbmku7CwkHfffZdHHnkEX19fLly4wCuvvEJBQQHHjx9Hr9cD6pLujIwMFi9eXLmku0uXLrKkWwghRL3JzSjmwMZEzsdmVB6NENLRi64PNMGzkbp7cOnp02QuWEjhH/8Dr9Xi8uCDeL7wPDZBQdXWrygKu85n8cnWcxxOzgXAzlrHyO5BjOvVFHcHG8sOyFACh1bA75+oc25AnVzc6xVoP1Sdm3OHqfcdhRcsWMDs2bNJT0+nY8eOzJs3r/KOSVRUFMHBwaxYsaKy/KpVq3jzzTdJSkoiNDSUDz/8kIEDBwJQUlLCkCFDOHz4MLm5ufj7+3Pfffcxffr0KhOMc3JyGD9+POvWrUOr1fLII48wb948HB1vbEtqCTVCCCHq6mp6EQc2JHH+YAZc+9Zs2tmLroOa4BGgfg+VnDhJ1oIFFP4xH1Snw2XwYDyffw6bGharKIrCjnOZzN1yjqOp6g7I9jY6nu4ezLM9Q3CzdLgpL4aDy2H3XHW1FIB7iBpu2j12R4UbOSbBDAk1QgghblbO5SIObEgk/tC1eZ4aaBbmTddBTXD3Uze8Kzl2jMwFCyj6bZdaxsoK178NwWPcc9g0Cqi2fkVR2H72Ch9vOceJS+pWJI56K57uHsyYnk1wtbd0uClSJw/v/hSKr22l4tEMer8KbR8Brc6y7dWBhBozJNQIIYSwlOxLhRxYn8iFw9fucmggtIsPXQcF4+Z7LdwcOULm/AUU7d6tlrG2xvXhh/EcNxbrGjaNVRSFraev8MmWc5xKU8ONk96KZ3o0YXSPJrjYWVt2QGWFcOBzNdyUXFWveTZXw02bh9VjGm4TCTVmSKgRQghhaVmpBcSuSyTxaBagntjdPNyXLoOCcfVWd/Mtjosjc/58ivfuU8tYW+P62KN4jB2LdTUrgQFMJoXNpzKYu/UcZ9ILAHCytWJMjxCe6RGMs62lw00B7F8Ce+arZ0sBeLWCqFeh1eDbEm4k1JghoUYIIUR9yUwuIHZ9IknHroUbrYYWET50GdgEFy87AIoPHCBzwUKKr+2Er7GxwfXxx/F49lmsff58duL/MpkUNp1MZ+7Wc5zLUA+udLGz5tmeTXj6niY46i08B6Y071q4WQBl6hwfvNtA1BRo+cAtDTcSasyQUCOEEKK+XbmYT+y6RC6eUOenaLQaWkb60mVAMM6eargp2refzAXzKTmoblOi0etxe2IoHmPGYOXlVW39JpPChuNpfBpznvgrarhxtbdmbK8QRkUG42DpcFOSqx61sO8zKLt23JBPO+jzGrRQNyKsbxJqzJBQI4QQ4lZJT8zjwPpEkk/mAKDVamh5jx9dBgTj5G6LoigU79tH5vwFlMTFAaCxtcVt2DA8xozGysOj2vqNJoX1xy7z6dbzJGQVAeDuYMO4XiGMiAzC3sbS4eYq7F0I+xZDufoYDL8OEPU6NO9fr+FGQo0ZEmqEEELcaukJecSuSyDltDr5VqvT0Ooef8LuD6oMN0W795A1fz4lR48CoLGzw/3J4biPHo2Vm1u19VcYTaw9epl5MedJyi4GwNPRhud6N+XJiCDsbCy8eqk4R51vs38JGNQwhX9n6PM6NIuul3AjocYMCTVCCCFul8vxucSuS+TS2WvhxkpDm3v86Xx/MI5uejXc7NpF5vwFlB4/DoDG3h73p57C/Zmnbyjc/Hz4EvO3xZOc80e40fN8VFOejGiMrbWFw01RFuyZB7Gfg0Ftj4Au6mOppvdaNNxIqDFDQo0QQojb7dK5q8SuS+Ty+VwAdFZa2vTyp3P/IBxc1HBTuGMHWfMXUHrqFABaBwfcRo7A4+mn0bm4VFu/wWji57hLzNt2ntSr6ing3k56XohqyhPh9RBuCjPVDfwOLIOKEnWl1PN7LDqRWEKNGRJqhBBC3AkUReHS2avErk8kLV5dWaSz1tK2dwCd7wvC3tlGDTfbtpG5YCFlp08DoHV0xH3kSNyfHoWuhu+x8goTPx1KZeH2eC7lquHG19mWF/s24/EujdBbWTjcFGSoe9wEdYdWD1i0agk1ZkioEUIIcSdRFIXU01eJXZ9AeoK6ssjqWrhp3zdQnXNjMlEQE0PW/AWUnTsHgNbJCfenR+E+ciQ6J6dq2yirMLLqoBpu0vLUU8D9XWwZ3zeUR8MaYWN1+zbVu1ESasyQUCOEEOJOpCgKyadyiF2XyJUkNdxotBpCOnrRoW8jfJu6gKJQsHkLWQsXUHY+HgCtiwsezzyN21Mj0Dk6VNtGqcHIDwdS+GxHPBn56ingAa52vHRvMx7u3Ahr3Z0bbiTUmCGhRgghxJ1MURQunsjmyNZkLp3Nrbzu1diJDn0b0SzMB60OCjZtInPhZ5RfuACAztUV97//Hfcnh6N1qDncfLc/mUU7L5BZoIabxu72jO/bjIc7BWB1B4YbCTVmSKgRQghxt8hKLeTY9hTO7c/AWGECwM7Zhna9A2jfpxE2ei35G38ha+FCypOSANC5ueExZjRuw4ahtbevtv6SciPf7r/I4p0XyCosByDYw54JfUMZ3NH/jgo3EmrMkFAjhBDiblNSWM7JXZc5sSOVojw1fOjtrehwbyAd+gZibQ35GzaQ+dlnGC4mA6Dz8MBjzBjcnhiK1s6u2vqLyyv4176LLN6ZQE6RWn+IpwMv3RvKgx380Wnrf8fgmkioMUNCjRBCiLuV0WjiQtwVDv1ykZzL6qZ3ensrOkY3pn3fRlhbQd7adWQtWoQhJQUAnZcnns8+i+vjj6O1ta22/qKyCr7ee5Elv10gt9gAQFMvB/4R3ZwH2vmhvY3hRkKNGRJqhBBC3O0Uk0J83BUOrE/karq66Z2tgzUd+wXSLqoR1jqFvP/8h6xFizFcugSAlZcXHuPG4frYo2j1+mrrLyyr4Ks9SSz9LYG8EjXcNPdx5B/3NmdAW9/bEm4k1JghoUYIIURDYTIpxB/M4MCGJHIzroUbR2s63deYdr0bYaUxkvvzGrIWL6YiLQ0AK19fPMeNxeWRR9Da2FRbf36pgRW7k/h8VwIFpRUAtPR1YmJ0KPe1vrXhRkKNGRJqhBBCNDQmo4nzB9Rwk5epbrJn52RN5/5BtOkVgA4jef/+N1lLllKRng6AlZ8fns89h+vfhqCpIdzklRhY/nsiy39PpKBMDTet/ZyZGB1Kv9Y+aOSU7ttDQo0QQoiGymQ0cXZ/Bgc3JpKfpW6yZ+9sQ6f7GtPqHn+stUZyV/1E9tKlVFy5AoC1vz8eY5/FZfDgGicU5xaXs+xauCkqNwLQLsCFidGh9G3pXa/hRkKNGRJqhBBCNHRGo4mz+9I5uDGJgmw13FjrdbTs7kf7qEY4u2jJ/fFHsj7/HGNmFqBu4uf22KO4DR+Otb9/tfVfLSrn810JrNiTRHG5kSAPe7ZO7l2vm/dJqDFDQo0QQoi/CmOFGm6OxKRwNa2o8npQWw/a921EQLAdeT/9RM7X32BITVV/qNPhFB2N+8gR2HXuXO3dl+zCMpbuSqCNvwsPdag+CN0sCTVmSKgRQgjxV/PH+VJHt6dw8Xh25XU3X3s6RjemebgXJbt+I+frbyjev7/y57Zt2uA24imcBw6scVJxfZNQY4aEGiGEEH9luRnFHNuRypk9aRjK1Hkxzp62hA0IpkU3Xwzx57n6r3+Rt3YdSpl6hILO0xO3oUNxe2IoVl5et6XfEmrMkFAjhBBCQFlJBad3XyZuczIl+eouws5ednQdGEzzcB9M+Xnk/riKq999R0VGBgAaa2ucBw7AbcRI7Nq2uaX9lVBjhoQaIYQQ4r8M5UZO7LzE4c0XKSlQN9pz8bYj7P4gQrv6oMNEwZYt5Hz9DSVHjlR+zq5zZ9xHjsCpXz80Ol2991NCjRkSaoQQQog/M5QZOb4jlcObkyktUsONnZM1bXoG0LZ3AA4uekqOHyfn62/I37QJDAasAwNpuukXCTW3i4QaIYQQ4vrKSys4sfMSx3ekUnhVnVOj1WloFuZN+76B+AQ7Y7hyhdyVK7EOCMD1kUduSb8k1JghoUYIIYSomcloIuFIFse2pZB2Ia/yum+IM+37BhLSyQtdPe5L8/+TUGOGhBohhBCidq5czOfYtlTOH8zAZFTjgoOrnnZRAbTu4Y+dY/0v95ZQY4aEGiGEEKJuivLKOPnbJU78dqlyUrHOWkvzcB869A3EI8Cx3tqWUGOGhBohhBDi5hgNJs4fyuDYtlQykwsqrwe0cKV9n0CadPC0+DlQtfn+trJoy0IIIYRosHTWWlp286NFhC/pF/I4ui2VhCOZXDqbS2lhBU06eN7W/tVpps/ChQsJDg7G1taWiIgIYmNjqy2/atUqWrZsia2tLe3atWPjxo2VPzMYDLz66qu0a9cOBwcH/P39GTlyJJcvX65SR3BwMBqNpspr1qxZdem+EEIIIW6CRqPBr5kr949ty4j3IuncvzFhA4Lq9bTuG1HrUPPDDz8wefJkpk6dSlxcHB06dKB///5cuXaU+f9vz549DBs2jNGjR3P48GGGDBnCkCFDOHHiBADFxcXExcXx1ltvERcXx+rVqzl79iwPPfTQn+qaNm0aaWlpla8JEybUtvtCCCGEsCAnd1si/9aM0C4+t7srtZ9TExERQdeuXVmwYAEAJpOJwMBAJkyYwJQpU/5UfujQoRQVFbF+/frKa926daNjx44sXrzYbBsHDhwgPDycixcv0rhxY0C9UzNx4kQmTpxYm+5Wkjk1QgghxN2nNt/ftbpTU15ezqFDh4iOjv5vBVot0dHR7N271+xn9u7dW6U8QP/+/a9bHiAvLw+NRoOrq2uV67NmzcLDw4NOnToxe/ZsKioqrltHWVkZ+fn5VV5CCCGEaLhqNVE4KysLo9GIj0/VW0w+Pj6cOXPG7GfS09PNlk9PTzdbvrS0lFdffZVhw4ZVSWQvvfQSnTt3xt3dnT179vDaa6+RlpbGxx9/bLaemTNn8u6779ZmeEIIIYS4i91Rq58MBgOPP/44iqKwaNGiKj+bPHly5T+3b98eGxsbxo0bx8yZM9Hr9X+q67XXXqvymfz8fAIDA+uv80IIIYS4rWoVajw9PdHpdGRcO4r8DxkZGfj6+pr9jK+v7w2V/yPQXLx4kW3bttX43CwiIoKKigqSkpJo0aLFn36u1+vNhh0hhBBCNEy1mlNjY2NDWFgYMTExlddMJhMxMTFERkaa/UxkZGSV8gBbtmypUv6PQHP+/Hm2bt2Kh4dHjX05cuQIWq0Wb2/v2gxBCCGEEA1UrR8/TZ48mVGjRtGlSxfCw8OZO3cuRUVFPPPMMwCMHDmSgIAAZs6cCcA//vEPevfuzUcffcSgQYNYuXIlBw8eZOnSpYAaaB599FHi4uJYv349RqOxcr6Nu7s7NjY27N27l/3799OnTx+cnJzYu3cvkyZN4qmnnsLNzc1S/y6EEEIIcRerdagZOnQomZmZvP3226Snp9OxY0c2bdpUORk4OTkZrfa/N4C6d+/Od999x5tvvsnrr79OaGgoa9asoW3btgBcunSJtWvXAtCxY8cqbW3fvp2oqCj0ej0rV67knXfeoaysjCZNmjBp0qQqc2aEEEII8dcmZz8JIYQQ4o5Vb/vUCCGEEELcqSTUCCGEEKJBkFAjhBBCiAZBQo0QQgghGoQ7akfh+vTHfGg5A0oIIYS4e/zxvX0j65r+MqGmoKAAQI5KEEIIIe5CBQUFuLi4VFvmL7Ok22QycfnyZZycnNBoNBat+49zpVJSUhrscnEZY8MgY2wYZIx3v4Y+PrDcGBVFoaCgAH9//yr74Jnzl7lTo9VqadSoUb224ezs3GD/4/yDjLFhkDE2DDLGu19DHx9YZow13aH5g0wUFkIIIUSDIKFGCCGEEA2ChBoL0Ov1TJ06Fb1ef7u7Um9kjA2DjLFhkDHe/Rr6+OD2jPEvM1FYCCGEEA2b3KkRQgghRIMgoUYIIYQQDYKEGiGEEEI0CBJqhBBCCNEgSKi5QQsXLiQ4OBhbW1siIiKIjY2ttvyqVato2bIltra2tGvXjo0bN96intZdbcZ48uRJHnnkEYKDg9FoNMydO/fWdfQm1GaMn3/+OT179sTNzQ03Nzeio6Nr/L3fCWozxtWrV9OlSxdcXV1xcHCgY8eOfPPNN7ewt3VT2z+Pf1i5ciUajYYhQ4bUbwdvUm3Gt2LFCjQaTZWXra3tLext3dT2d5ibm8uLL76In58fer2e5s2b3/F/r9ZmjFFRUX/6PWo0GgYNGnQLe1x7tf09zp07lxYtWmBnZ0dgYCCTJk2itLTUch1SRI1Wrlyp2NjYKMuXL1dOnjypPPvss4qrq6uSkZFhtvzu3bsVnU6nfPjhh8qpU6eUN998U7G2tlaOHz9+i3t+42o7xtjYWOXll19Wvv/+e8XX11f55JNPbm2H66C2Yxw+fLiycOFC5fDhw8rp06eVp59+WnFxcVFSU1Nvcc9vXG3HuH37dmX16tXKqVOnlPj4eGXu3LmKTqdTNm3adIt7fuNqO8Y/JCYmKgEBAUrPnj2VwYMH35rO1kFtx/fll18qzs7OSlpaWuUrPT39Fve6dmo7xrKyMqVLly7KwIEDld9//11JTExUduzYoRw5cuQW9/zG1XaM2dnZVX6HJ06cUHQ6nfLll1/e2o7XQm3H+O233yp6vV759ttvlcTEROXXX39V/Pz8lEmTJlmsTxJqbkB4eLjy4osvVr43Go2Kv7+/MnPmTLPlH3/8cWXQoEFVrkVERCjjxo2r137ejNqO8X8FBQXdFaHmZsaoKIpSUVGhODk5KV999VV9dfGm3ewYFUVROnXqpLz55pv10T2LqMsYKyoqlO7duytffPGFMmrUqDs61NR2fF9++aXi4uJyi3pnGbUd46JFi5SQkBClvLz8VnXxpt3sn8VPPvlEcXJyUgoLC+urizettmN88cUXlb59+1a5NnnyZOWee+6xWJ/k8VMNysvLOXToENHR0ZXXtFot0dHR7N271+xn9u7dW6U8QP/+/a9b/naryxjvNpYYY3FxMQaDAXd39/rq5k252TEqikJMTAxnz56lV69e9dnVOqvrGKdNm4a3tzejR4++Fd2ss7qOr7CwkKCgIAIDAxk8eDAnT568Fd2tk7qMce3atURGRvLiiy/i4+ND27ZtmTFjBkaj8VZ1u1Ys8ffNsmXLeOKJJ3BwcKivbt6Uuoyxe/fuHDp0qPIRVUJCAhs3bmTgwIEW69df5kDLusrKysJoNOLj41Pluo+PD2fOnDH7mfT0dLPl09PT662fN6MuY7zbWGKMr776Kv7+/n8KrHeKuo4xLy+PgIAAysrK0Ol0fPbZZ/Tr16++u1sndRnj77//zrJlyzhy5Mgt6OHNqcv4WrRowfLly2nfvj15eXnMmTOH7t27c/LkyXo/xLcu6jLGhIQEtm3bxpNPPsnGjRuJj4/nhRdewGAwMHXq1FvR7Vq52b9vYmNjOXHiBMuWLauvLt60uoxx+PDhZGVl0aNHDxRFoaKigueee47XX3/dYv2SUCPEDZg1axYrV65kx44dd8UkzNpwcnLiyJEjFBYWEhMTw+TJkwkJCSEqKup2d+2mFRQUMGLECD7//HM8PT1vd3fqRWRkJJGRkZXvu3fvTqtWrViyZAnTp0+/jT2zHJPJhLe3N0uXLkWn0xEWFsalS5eYPXv2HRlqbtayZcto164d4eHht7srFrVjxw5mzJjBZ599RkREBPHx8fzjH/9g+vTpvPXWWxZpQ0JNDTw9PdHpdGRkZFS5npGRga+vr9nP+Pr61qr87VaXMd5tbmaMc+bMYdasWWzdupX27dvXZzdvSl3HqNVqadasGQAdO3bk9OnTzJw5844MNbUd44ULF0hKSuLBBx+svGYymQCwsrLi7NmzNG3atH47XQuW+LNobW1Np06diI+Pr48u3rS6jNHPzw9ra2t0Ol3ltVatWpGenk55eTk2Njb12ufaupnfY1FREStXrmTatGn12cWbVpcxvvXWW4wYMYIxY8YA0K5dO4qKihg7dixvvPEGWu3Nz4iROTU1sLGxISwsjJiYmMprJpOJmJiYKv939L8iIyOrlAfYsmXLdcvfbnUZ492mrmP88MMPmT59Ops2baJLly63oqt1Zqnfo8lkoqysrD66eNNqO8aWLVty/Phxjhw5Uvl66KGH6NOnD0eOHCEwMPBWdr9GlvgdGo1Gjh8/jp+fX31186bUZYz33HMP8fHxlYEU4Ny5c/j5+d1xgQZu7ve4atUqysrKeOqpp+q7mzelLmMsLi7+U3D5I6gqljqG0mJTjhuwlStXKnq9XlmxYoVy6tQpZezYsYqrq2vlsskRI0YoU6ZMqSy/e/duxcrKSpkzZ45y+vRpZerUqXfFku7ajLGsrEw5fPiwcvjwYcXPz095+eWXlcOHDyvnz5+/XUOoUW3HOGvWLMXGxkb56aefqiy1LCgouF1DqFFtxzhjxgxl8+bNyoULF5RTp04pc+bMUaysrJTPP//8dg2hRrUd4//vTl/9VNvxvfvuu8qvv/6qXLhwQTl06JDyxBNPKLa2tsrJkydv1xBqVNsxJicnK05OTsr48eOVs2fPKuvXr1e8vb2V995773YNoUZ1/e+0R48eytChQ291d+uktmOcOnWq4uTkpHz//fdKQkKCsnnzZqVp06bK448/brE+Sai5QfPnz1caN26s2NjYKOHh4cq+ffsqf9a7d29l1KhRVcr/+OOPSvPmzRUbGxulTZs2yoYNG25xj2uvNmNMTExUgD+9evfufes7Xgu1GWNQUJDZMU6dOvXWd7wWajPGN954Q2nWrJlia2uruLm5KZGRkcrKlStvQ69rp7Z/Hv/XnR5qFKV245s4cWJlWR8fH2XgwIFKXFzcbeh17dT2d7hnzx4lIiJC0ev1SkhIiPL+++8rFRUVt7jXtVPbMZ45c0YBlM2bN9/intZdbcZoMBiUd955R2natKlia2urBAYGKi+88IJy9epVi/VHoyiWuucjhBBCCHH7yJwaIYQQQjQIEmqEEEII0SBIqBFCCCFEgyChRgghhBANgoQaIYQQQjQIEmqEEEII0SBIqBFCCCFEgyChRgghhBANgoQaIYQQQjQIEmqEEEII0SBIqBFCCCFEgyChRgghhBANwv8DT7L2Dm+LLKUAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "# This is used to plot the non-degenerate Hamming bound in the psuedo_t vs code rate view.\n", "from scipy.special import comb\n", - "import numpy as np\n", "import matplotlib.pyplot as plt\n", - "def binomial_probability(n,d_e,p_p):\n", - " return comb(n, d_e) * (p_p ** d_e) * ((1 - p_p) ** (n - d_e))\n", - "def code_rate_delta(t,n = 144):\n", + "\n", + "\n", + "def binomial_probability(n, d_e, p_p):\n", + " return comb(n, d_e) * (p_p**d_e) * ((1 - p_p) ** (n - d_e))\n", + "\n", + "\n", + "def code_rate_delta(t, n=144):\n", " # t = int(delta*n / 2)\n", " t = int(t)\n", - " delta = (2*t+1)\n", + " delta = 2 * t + 1\n", " s = 0\n", " for i in range(t):\n", - " s += comb(n, i)* (3**i)\n", + " s += comb(n, i) * (3**i)\n", " k = np.floor(n - np.log2(s))\n", - " \n", - " CR = k/n\n", + "\n", + " CR = k / n\n", "\n", " return CR\n", "\n", + "\n", "# select n here; I chose 144,72 in my thesis\n", - "for n in [144,72,36,288,576]:\n", + "for n in [144, 72, 36, 288, 576]:\n", " CR = []\n", " Delta = []\n", - " for t in range(1,n//2):\n", - " cr = code_rate_delta(t,n)\n", - " if cr>=0 and cr<=0.8:\n", + " for t in range(1, n // 2):\n", + " cr = code_rate_delta(t, n)\n", + " if cr >= 0 and cr <= 0.8:\n", " CR.append(cr)\n", - " Delta.append(t/n)\n", - " plt.plot(CR,Delta,label=f'n={n} Non-degenerate CSS Code Hamming Bound')\n", - " print(f'n={n} non-degenerate CSS Hamming bound: code rates:{CR}, psuedo t//n:{Delta}')\n", + " Delta.append(t / n)\n", + " plt.plot(CR, Delta, label=f\"n={n} Non-degenerate CSS Code Hamming Bound\")\n", + " print(\n", + " f\"n={n} non-degenerate CSS Hamming bound: code rates:{CR}, psuedo t//n:{Delta}\"\n", + " )\n", "plt.show()" ] } ], "metadata": { "kernelspec": { - "display_name": "QEC2", + "display_name": "qec", "language": "python", "name": "python3" }, @@ -837,7 +485,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.16" + "version": "3.10.19" } }, "nbformat": 4, diff --git a/requirements.txt b/requirements.txt index 52c077c..1e94c2e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,192 +1,17 @@ -about-time==4.2.1 -aiocache==0.12.3 -aiofiles==24.1.0 -aiohappyeyeballs==2.4.6 -aiohttp==3.11.12 -aiopubsub==3.0.0 -aiosignal==1.3.2 -aiosmtplib==4.0.0 -alembic==1.14.1 -alive-progress==3.2.0 -aniso8601==7.0.0 -APScheduler==3.11.0 -art==6.4 -asttokens @ file:///home/conda/feedstock_root/build_artifacts/asttokens_1733250440834/work -async-timeout==5.0.1 -attrs==25.1.0 -autograd==1.7.0 -ax==0.36.0 -botorch==0.13.0 +botorch==0.16.1 bposd==2.1 -certifi @ file:///croot/certifi_1738623731865/work/certifi -charset-normalizer==3.4.1 -cma==3.2.2 -comm @ file:///home/conda/feedstock_root/build_artifacts/comm_1733502965406/work -contourpy==1.2.1 -cssselect==1.2.0 -cycler==0.12.1 -Cython==3.0.12 -debugpy @ file:///home/conda/feedstock_root/build_artifacts/debugpy_1737269734548/work -decorator==4.4.2 -Deprecated==1.2.18 -dill==0.3.9 -docopt==0.6.2 -dotmap==1.3.30 -et_xmlfile==2.0.0 -exceptiongroup @ file:///home/conda/feedstock_root/build_artifacts/exceptiongroup_1733208806608/work -executing @ file:///home/conda/feedstock_root/build_artifacts/executing_1733569351617/work -filelock==3.17.0 -fonttools==4.56.0 -frozenlist==1.5.0 -fsspec==2025.2.0 -future==1.0.0 -gensim==4.3.3 -gpytorch==1.14 -GraKeL==0.1.10 -grapheme==0.6.0 -graphene==2.1.9 -graphene-sqlalchemy==2.3.0 -graphql-core==2.3.2 -graphql-relay==2.0.1 -graphql-server-core==2.0.0 -graphql-ws==0.4.4 -greenlet==3.1.1 -gudhi==3.10.1 -gunicorn==23.0.0 -html5tagger==1.3.0 -httptools==0.6.4 -idna==3.10 -importlib_metadata @ file:///home/conda/feedstock_root/build_artifacts/importlib-metadata_1737420181517/work -iniconfig==2.0.0 -ipykernel @ file:///home/conda/feedstock_root/build_artifacts/ipykernel_1719845459717/work -ipython @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_ipython_1738421264/work -jaxtyping==0.2.37 -jedi @ file:///home/conda/feedstock_root/build_artifacts/jedi_1733300866624/work -Jinja2==3.1.5 -joblib==1.4.2 -jupyter_client @ file:///home/conda/feedstock_root/build_artifacts/jupyter_client_1733440914442/work -jupyter_core @ file:///home/conda/feedstock_root/build_artifacts/jupyter_core_1727163409502/work -karateclub==1.3.3 -kiwisolver==1.4.8 -ldpc==2.2.8 -Levenshtein==0.26.1 -linear-operator==0.6 -loguru==0.7.3 -lxml==5.3.1 -Mako==1.3.9 -markdown2==2.5.3 -MarkupSafe==3.0.2 +codedistance @ git+https://github.com/SpyroL7/codeDistancePYPI.git@v0.1.1 +Evaluate==0.4.6 +gpytorch==1.15.1 +ldpc==2.4.1 matplotlib==3.8.4 -matplotlib-inline @ file:///home/conda/feedstock_root/build_artifacts/matplotlib-inline_1733416936468/work -mpmath==1.3.0 -multidict==6.1.0 -multipledispatch==1.0.0 -mypy-extensions==1.0.0 -nest_asyncio @ file:///home/conda/feedstock_root/build_artifacts/nest-asyncio_1733325553580/work -networkx==2.6.3 -numpy==1.26.4 -nvidia-cublas-cu12==12.4.5.8 -nvidia-cuda-cupti-cu12==12.4.127 -nvidia-cuda-nvrtc-cu12==12.4.127 -nvidia-cuda-runtime-cu12==12.4.127 -nvidia-cudnn-cu12==9.1.0.70 -nvidia-cufft-cu12==11.2.1.3 -nvidia-curand-cu12==10.3.5.147 -nvidia-cusolver-cu12==11.6.1.9 -nvidia-cusparse-cu12==12.3.1.170 -nvidia-cusparselt-cu12==0.6.2 -nvidia-nccl-cu12==2.21.5 -nvidia-nvjitlink-cu12==12.4.127 -nvidia-nvtx-cu12==12.4.127 -openpyxl==3.1.5 -opt_einsum==3.4.0 -packaging @ file:///home/conda/feedstock_root/build_artifacts/packaging_1733203243479/work -pandas==1.3.5 -parso @ file:///home/conda/feedstock_root/build_artifacts/parso_1733271261340/work -passlib==1.7.4 -pexpect @ file:///home/conda/feedstock_root/build_artifacts/pexpect_1733301927746/work -pickleshare @ file:///home/conda/feedstock_root/build_artifacts/pickleshare_1733327343728/work -pillow==11.1.0 -platformdirs @ file:///home/conda/feedstock_root/build_artifacts/platformdirs_1733232627818/work -pluggy==1.5.0 -promise==2.3 -prompt_toolkit @ file:///home/conda/feedstock_root/build_artifacts/prompt-toolkit_1737453357274/work -propcache==0.2.1 -psutil @ file:///home/conda/feedstock_root/build_artifacts/psutil_1735327341346/work -psycopg2==2.9.10 -ptyprocess @ file:///home/conda/feedstock_root/build_artifacts/ptyprocess_1733302279685/work/dist/ptyprocess-0.7.0-py2.py3-none-any.whl#sha256=92c32ff62b5fd8cf325bec5ab90d7be3d2a8ca8c8a3813ff487a8d2002630d1f -pure_eval @ file:///home/conda/feedstock_root/build_artifacts/pure_eval_1733569405015/work -pyarrow==19.0.0 -pyasn1==0.6.1 -pyg-nightly==2.7.0.dev20250213 -Pygments @ file:///home/conda/feedstock_root/build_artifacts/pygments_1736243443484/work -PyGSP==0.5.1 -PyJWT==2.10.1 -PyMatching==2.2.1 -pymoo==0.6.1.3 -pyparsing==3.2.1 -pyquery==2.0.1 -pyrandwalk==1.1 -pyre-extensions==0.0.32 -pyro-api==0.1.2 -pyro-ppl==1.9.1 -pytest==8.3.5 -pytest-runner==6.0.1 -python-dateutil @ file:///home/conda/feedstock_root/build_artifacts/python-dateutil_1733215673016/work -python-Levenshtein==0.26.1 -python-louvain==0.16 -pytz==2025.1 -PyYAML==6.0.2 -pyzmq @ file:///home/conda/feedstock_root/build_artifacts/pyzmq_1738270958168/work -RapidFuzz==3.12.1 -redis==5.2.1 -requests==2.32.3 -rsa==4.9 -ruamel.yaml==0.18.10 -ruamel.yaml.clib==0.2.12 -Rx==1.6.3 -sanic==24.12.0 -Sanic-Cors==2.2.0 -Sanic-GraphQL==1.1.0 -sanic-jwt==1.8.0 -sanic-routing==23.12.0 -sanic_compress==0.1.1 -scikit-learn==1.6.1 -scipy==1.13.1 -singledispatch==3.7.0 -sinter==1.14.0 -six @ file:///home/conda/feedstock_root/build_artifacts/six_1733380938961/work -smart-open==7.1.0 -SQLAlchemy==1.4.54 -SQLAlchemy-Utils==0.41.2 -stack_data @ file:///home/conda/feedstock_root/build_artifacts/stack_data_1733569443808/work -stim==1.14.0 -stripe==11.6.0 +numpy==2.2.6 +pymoo==0.6.1.6 +qLDPC @ git+https://github.com/qLDPCOrg/qLDPC.git@main +scipy==1.17.1 +sinter==1.15.0 +stim==1.16.dev1770241571 sympy==1.13.1 -threadpoolctl==3.5.0 -tomli==2.2.1 -TopoEmbedX==0.0.2.dev131 -TopoModelX==0.0.1 TopoNetX==0.2.0 -torch==2.6.0 -torch-geometric==2.6.1 -tornado @ file:///home/conda/feedstock_root/build_artifacts/tornado_1732615898999/work -tqdm==4.67.1 -tracerite==1.1.1 -traitlets @ file:///home/conda/feedstock_root/build_artifacts/traitlets_1733367359838/work -trimesh==4.6.2 -triton==3.2.0 -typing==3.7.4.3 -typing-inspect==0.9.0 -typing_extensions @ file:///home/conda/feedstock_root/build_artifacts/typing_extensions_1733188668063/work -tzdata==2025.1 -tzlocal==5.3 -ujson==5.10.0 -urllib3==2.3.0 -uvloop==0.21.0 -wadler_lindig==0.1.3 -wcwidth @ file:///home/conda/feedstock_root/build_artifacts/wcwidth_1733231326287/work -websockets==15.0 -wrapt==1.17.2 -yarl==1.18.3 -zipp @ file:///home/conda/feedstock_root/build_artifacts/zipp_1732827521216/work +torch_geometric==2.7.0 +tqdm==4.67.3 \ No newline at end of file diff --git a/test_bo.ipynb b/test_bo.ipynb index 08f410e..c23b761 100644 --- a/test_bo.ipynb +++ b/test_bo.ipynb @@ -2,18 +2,18 @@ "cells": [ { "cell_type": "code", - "execution_count": 26, - "id": "4a6bebbf", + "execution_count": null, + "id": "0", "metadata": {}, "outputs": [], "source": [ - "device = 'cuda'\n", - "DEVICE = 'cuda'" + "device = \"cuda\"\n", + "DEVICE = \"cuda\"" ] }, { "cell_type": "markdown", - "id": "cbc45bb1", + "id": "1", "metadata": {}, "source": [ "# Define the search space" @@ -21,23 +21,24 @@ }, { "cell_type": "code", - "execution_count": 1, - "id": "4fd1756b", + "execution_count": null, + "id": "2", "metadata": {}, "outputs": [], "source": [ "l = 12\n", "g = 6\n", - "para_dict = {'l':l,'g':g}\n", - "code_class = 'bb'\n", + "para_dict = {\"l\": l, \"g\": g}\n", + "code_class = \"bb\"\n", "\n", "from code_construction.code_construction import CodeConstructor\n", - "code_constructor = CodeConstructor(method=code_class,para_dict = para_dict)" + "\n", + "code_constructor = CodeConstructor(method=code_class, para_dict=para_dict)" ] }, { "cell_type": "markdown", - "id": "9923d5a0", + "id": "3", "metadata": {}, "source": [ "# Define the objective function\n" @@ -46,28 +47,30 @@ { "cell_type": "code", "execution_count": null, - "id": "5ade0e42", + "id": "4", "metadata": {}, "outputs": [], "source": [ "from bayesian_optimization.objective_function import ObjectiveFunction\n", "\n", - "pp=0.006\n", + "pp = 0.006\n", "\n", "# ------depolarizing noise------\n", "# Obj_Func = ObjectiveFunction(code_constructor, pp=pp)\n", "# ------circuit level noise------\n", - "Obj_Func = ObjectiveFunction(code_constructor,\n", - " pp=pp,\n", - " decoder_param={'trail': 10000,'max_error':100},\n", - " circuit_level_noise = True,\n", - " circuit_param={\n", - " 'noise_model':'SD6',\n", - " 'num_workers' : 24,\n", - " 'rounds':12,\n", - " 'custom_error_model':{},\n", - " 'decoder':'bplsd'\n", - " })\n", + "Obj_Func = ObjectiveFunction(\n", + " code_constructor,\n", + " pp=pp,\n", + " decoder_param={\"trail\": 10000, \"max_error\": 100},\n", + " circuit_level_noise=True,\n", + " circuit_param={\n", + " \"noise_model\": \"SD6\",\n", + " \"num_workers\": 24,\n", + " \"rounds\": 12,\n", + " \"custom_error_model\": {},\n", + " \"decoder\": \"bplsd\",\n", + " },\n", + ")\n", "\n", "\n", "# ------if we use ler function\n", @@ -78,13 +81,12 @@ "# pl_to_obj = Obj_Func.nllerpq_with_std\n", "# if we use forward function\n", "# obj_func = Obj_Func.forward\n", - "# pl_to_obj = Obj_Func.pl_to_obj_with_std\n", - "\n" + "# pl_to_obj = Obj_Func.pl_to_obj_with_std" ] }, { "cell_type": "markdown", - "id": "3279d950", + "id": "5", "metadata": {}, "source": [ "# Initialize the Get_new_points_function " @@ -92,51 +94,62 @@ }, { "cell_type": "code", - "execution_count": 29, - "id": "8382b866", + "execution_count": null, + "id": "6", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import torch\n", - "class Get_new_points_function():\n", - " def __init__(self,method='qc-ldpc-hgp',hyperparameters = {'p': 2, 'q': 6, 'm': 2},encode='None'):\n", + "\n", + "\n", + "class Get_new_points_function:\n", + " def __init__(\n", + " self,\n", + " method=\"qc-ldpc-hgp\",\n", + " hyperparameters={\"p\": 2, \"q\": 6, \"m\": 2},\n", + " encode=\"None\",\n", + " ):\n", " self.method = method\n", " self.hyperparameters = hyperparameters\n", " self.encode = encode\n", " self.init = False\n", "\n", - " def get_new_points_function(self,number):\n", - " if self.method == 'qc-ldpc-hgp':\n", + " def get_new_points_function(self, number):\n", + " if self.method == \"qc-ldpc-hgp\":\n", " new_points = self.get_new_points_HGP(number)\n", - " elif self.method == 'bb':\n", + " elif self.method == \"bb\":\n", " new_points = self.get_new_bb_vector(number)\n", " return new_points\n", - " \n", - " def get_new_points_HGP(self,number):\n", - " return np.random.randint(0, self.hyperparameters['m'] + 1, (number, self.hyperparameters['p'] * self.hyperparameters['q']))\n", "\n", - " def get_new_bb_vector(self,number):\n", + " def get_new_points_HGP(self, number):\n", + " return np.random.randint(\n", + " 0,\n", + " self.hyperparameters[\"m\"] + 1,\n", + " (number, self.hyperparameters[\"p\"] * self.hyperparameters[\"q\"]),\n", + " )\n", + "\n", + " def get_new_bb_vector(self, number):\n", " results = []\n", - " l = self.hyperparameters['l']\n", - " g = self.hyperparameters['g']\n", - " if self.init == False and l==12 and g==999:\n", - " print('best known bb code added to initial points')\n", - " self.init=True\n", - "\n", - " a = np.zeros((l+g-1)*2)\n", - " a[3]=1\n", - " a[11+1]=1\n", - " a[11+2]=1\n", - " a[17+1]=1\n", - " a[17+2]=1\n", - " a[17+11+3]=1\n", + " l = self.hyperparameters[\"l\"]\n", + " g = self.hyperparameters[\"g\"]\n", + " if self.init == False and l == 12 and g == 999:\n", + " print(\"best known bb code added to initial points\")\n", + " self.init = True\n", + "\n", + " a = np.zeros((l + g - 1) * 2)\n", + " a[3] = 1\n", + " a[11 + 1] = 1\n", + " a[11 + 2] = 1\n", + " a[17 + 1] = 1\n", + " a[17 + 2] = 1\n", + " a[17 + 11 + 3] = 1\n", " results.append(a)\n", "\n", - " while number>0:\n", - " new_point = np.random.randint(0,2, size=(l+g-1)*2)\n", + " while number > 0:\n", + " new_point = np.random.randint(0, 2, size=(l + g - 1) * 2)\n", " c = code_constructor.construct(new_point)\n", - " if c.k==0:\n", + " if c.k == 0:\n", " continue\n", " else:\n", " results.append(new_point)\n", @@ -144,49 +157,38 @@ " return np.array(results)\n", "\n", "\n", - "gnp = Get_new_points_function(method=code_class,hyperparameters = para_dict).get_new_points_function" + "gnp = Get_new_points_function(\n", + " method=code_class, hyperparameters=para_dict\n", + ").get_new_points_function" ] }, { "cell_type": "code", - "execution_count": 30, - "id": "af2fe6ca", + "execution_count": null, + "id": "7", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "tensor([0.5301, 0.1433, 0.4758, 0.1553, 0.1842, 0.2261, 0.1476, 0.7077, 0.2567,\n", - " 0.4345, 0.3065, 0.2032, 0.6829, 0.2447, 0.2757, 0.2973, 0.4243, 0.2770,\n", - " 0.2961, 0.2400], device='cuda:0')" - ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "init_num = 20\n", "X_init = gnp(init_num)\n", "y_init = []\n", "pl_init = []\n", "for x in X_init:\n", - " y,pl = obj_func(x)\n", + " y, pl = obj_func(x)\n", " y_init.append(y)\n", " pl_init.append(pl)\n", "\n", - "X_init = torch.tensor(X_init,dtype=torch.float32)\n", + "X_init = torch.tensor(X_init, dtype=torch.float32)\n", "X_init.to(DEVICE)\n", - "y_init = torch.tensor(y_init,dtype=torch.float32)\n", + "y_init = torch.tensor(y_init, dtype=torch.float32)\n", "y_init.to(DEVICE)\n", - "pl_init = torch.tensor(pl_init,dtype=torch.float32)\n", + "pl_init = torch.tensor(pl_init, dtype=torch.float32)\n", "pl_init.to(DEVICE)" ] }, { "cell_type": "markdown", - "id": "6b85278e", + "id": "8", "metadata": {}, "source": [ "# Define the Gaussian Process and it's training process" @@ -194,8 +196,8 @@ }, { "cell_type": "code", - "execution_count": 31, - "id": "c57f1075", + "execution_count": null, + "id": "9", "metadata": {}, "outputs": [], "source": [ @@ -203,43 +205,59 @@ "from bayesian_optimization.chaincomplexembedding import *\n", "from bayesian_optimization.gp import *\n", "import gpytorch\n", - "from gpytorch.mlls import ExactMarginalLogLikelihood\n", - "from gpytorch.distributions import MultivariateNormal\n", "from gpytorch.kernels import RBFKernel, MaternKernel, SpectralMixtureKernel, ScaleKernel\n", "\n", - "class E():\n", - " def __init__(self,code_constructor,views_info ):\n", + "\n", + "class E:\n", + " def __init__(self, code_constructor, views_info):\n", " self.code_constructor = code_constructor\n", - " self.encoder = CSSEncoder(views_info,mode ='relations')\n", + " self.encoder = CSSEncoder(views_info, mode=\"relations\")\n", "\n", - " def encode_single(self,x):\n", - " return self.encoder.encode(self.code_constructor.construct(np.array(x).astype(int)))\n", - " def encode(self,x):\n", + " def encode_single(self, x):\n", + " return self.encoder.encode(\n", + " self.code_constructor.construct(np.array(x).astype(int))\n", + " )\n", + "\n", + " def encode(self, x):\n", " # x: B views\n", " return [self.encode_single(i) for i in x]\n", + "\n", + "\n", "views_info = [\n", - " {\"name\":\"decode\",\n", - " \"partite_classes\":[\"SZ\",\"DQ\",\"SX\"],\n", - " \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"DQ_SX\",\"SX_DQ\"], \n", - " \"weight_mode\":\"count\", \"log1p\":True},\n", - " {\"name\":\"xlogic\",\n", - " \"partite_classes\":[\"DQ\",\"SX\",\"LX\"],\n", - " \"relations\":[\"DQ_SX\",\"SX_DQ\",\"LX_DQ\",\"DQ_LX\"],\n", - " \"weight_mode\":\"count\", \"log1p\":True},\n", - " {\"name\":\"zlogic\",\n", - " \"partite_classes\":[\"SZ\",\"DQ\",\"LZ\"],\n", - " \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"LZ_DQ\",\"DQ_LZ\"],\n", - " \"weight_mode\":\"count\", \"log1p\":True},\n", + " {\n", + " \"name\": \"decode\",\n", + " \"partite_classes\": [\"SZ\", \"DQ\", \"SX\"],\n", + " \"relations\": [\"SZ_DQ\", \"DQ_SZ\", \"DQ_SX\", \"SX_DQ\"],\n", + " \"weight_mode\": \"count\",\n", + " \"log1p\": True,\n", + " },\n", + " {\n", + " \"name\": \"xlogic\",\n", + " \"partite_classes\": [\"DQ\", \"SX\", \"LX\"],\n", + " \"relations\": [\"DQ_SX\", \"SX_DQ\", \"LX_DQ\", \"DQ_LX\"],\n", + " \"weight_mode\": \"count\",\n", + " \"log1p\": True,\n", + " },\n", + " {\n", + " \"name\": \"zlogic\",\n", + " \"partite_classes\": [\"SZ\", \"DQ\", \"LZ\"],\n", + " \"relations\": [\"SZ_DQ\", \"DQ_SZ\", \"LZ_DQ\", \"DQ_LZ\"],\n", + " \"weight_mode\": \"count\",\n", + " \"log1p\": True,\n", + " },\n", "]\n", "\n", - "def get_model_(X, y, kernel_type='ard_rbf', mean_type='linear', mean_input=64,embed_dim=128):\n", "\n", - " encoder = E(code_constructor,views_info)\n", + "def get_model_(\n", + " X, y, kernel_type=\"ard_rbf\", mean_type=\"linear\", mean_input=64, embed_dim=128\n", + "):\n", + "\n", + " encoder = E(code_constructor, views_info)\n", "\n", " # NN embedder\n", " embedding = ChainComplexEmbedder(\n", " views_info=views_info,\n", - " d_model=embed_dim ,\n", + " d_model=embed_dim,\n", " num_layers=4,\n", " view_aggr=\"sum\",\n", " num_bases=4,\n", @@ -249,19 +267,18 @@ " dropout=0.1,\n", " ).to(device)\n", "\n", - "\n", - "\n", " # kernel\n", - " if kernel_type == 'ard_rbf':\n", + " if kernel_type == \"ard_rbf\":\n", " base = RBFKernel(ard_num_dims=embed_dim)\n", " kernel = ScaleKernel(base)\n", - " elif kernel_type == 'matern':\n", + " elif kernel_type == \"matern\":\n", " base = MaternKernel(nu=1.5, ard_num_dims=embed_dim)\n", " kernel = ScaleKernel(base)\n", - " elif kernel_type == 'spectral_mixture':\n", + " elif kernel_type == \"spectral_mixture\":\n", " kernel = SpectralMixtureKernel(num_mixtures=4, ard_num_dims=embed_dim)\n", - " elif kernel_type == 'rbf_plus_periodic':\n", + " elif kernel_type == \"rbf_plus_periodic\":\n", " from gpytorch.kernels import PeriodicKernel\n", + "\n", " rbf = ScaleKernel(RBFKernel(ard_num_dims=embed_dim))\n", " periodic = ScaleKernel(PeriodicKernel(ard_num_dims=embed_dim))\n", " kernel = rbf + periodic\n", @@ -276,7 +293,8 @@ " train_y = y.to(device).float().view(-1)\n", "\n", " gp = GaussianProcess_QEC(\n", - " train_x, train_y,\n", + " train_x,\n", + " train_y,\n", " likelihood=likelihood,\n", " kernel=kernel,\n", " encoder=encoder.encode,\n", @@ -286,11 +304,13 @@ " ).to(device)\n", "\n", " return gp\n", + "\n", + "\n", "model = get_model_(\n", " X_init,\n", " pl_init,\n", - " kernel_type='matern',\n", - " mean_type='linear',\n", + " kernel_type=\"matern\",\n", + " mean_type=\"linear\",\n", " mean_input=128,\n", ")" ] @@ -298,7 +318,7 @@ { "cell_type": "code", "execution_count": null, - "id": "b87b3643", + "id": "10", "metadata": {}, "outputs": [], "source": [ @@ -307,7 +327,6 @@ "from typing import Dict, Optional, Tuple, Any, List\n", "\n", "import torch\n", - "import gpytorch\n", "from gpytorch.mlls import ExactMarginalLogLikelihood\n", "from torch.nn.utils import clip_grad_norm_\n", "\n", @@ -328,28 +347,26 @@ " self,\n", " model: gpytorch.models.ExactGP,\n", " device: str = \"cpu\",\n", - "\n", " training_iter: int = 80,\n", - " lr: Optional[Dict[str, float]] = None, # {'embed':4e-4,'mean':1e-3,'kernel':1e-3,'like':2e-2}\n", + " lr: Optional[\n", + " Dict[str, float]\n", + " ] = None, # {'embed':4e-4,'mean':1e-3,'kernel':1e-3,'like':2e-2}\n", " weight_decay: float = 1e-4,\n", " max_grad_norm: float = 2.0,\n", " optimizer_type: str = \"adamw\",\n", " recreate_optimizer_each_round: bool = True,\n", - "\n", - " scheduler_cfg: Optional[Dict[str, Any]] = None, # {'factor':0.5,'patience':5,'min_lr':1e-6}\n", - " early_stopping: Optional[Dict[str, Any]] = None,# {'patience':10,'tol':1e-4}\n", - "\n", + " scheduler_cfg: Optional[\n", + " Dict[str, Any]\n", + " ] = None, # {'factor':0.5,'patience':5,'min_lr':1e-6}\n", + " early_stopping: Optional[Dict[str, Any]] = None, # {'patience':10,'tol':1e-4}\n", " use_priors: bool = True,\n", " priors_cfg: Optional[Dict[str, Dict[str, float]]] = None,\n", - " noise_floor: float = 1e-3, # lower bound (in z-domain) for likelihood noise\n", + " noise_floor: float = 1e-3, # lower bound (in z-domain) for likelihood noise\n", " lengthscale_bounds: Optional[Tuple[float, float]] = None,\n", - "\n", " warm_start: bool = True,\n", - " rescale_on_scaler_change: bool = True, # alpha = old_std / new_std\n", + " rescale_on_scaler_change: bool = True, # alpha = old_std / new_std\n", " carry_optimizer_state: bool = False,\n", - "\n", - " freeze_cfg: Optional[Dict[str, Any]] = None, # {'lengthscale_until_round': 2}\n", - "\n", + " freeze_cfg: Optional[Dict[str, Any]] = None, # {'lengthscale_until_round': 2}\n", " verbose: bool = True,\n", " log_every: Optional[int] = None,\n", " save_best_state: bool = True,\n", @@ -363,14 +380,18 @@ " self.optimizer_type = optimizer_type.lower()\n", " self.recreate_optimizer_each_round = bool(recreate_optimizer_each_round)\n", "\n", - " self.scheduler_cfg = scheduler_cfg or {'factor': 0.5, 'patience': 5, 'min_lr': 1e-6}\n", - " self.early_stopping = early_stopping or {'patience': 10, 'tol': 1e-4}\n", + " self.scheduler_cfg = scheduler_cfg or {\n", + " \"factor\": 0.5,\n", + " \"patience\": 5,\n", + " \"min_lr\": 1e-6,\n", + " }\n", + " self.early_stopping = early_stopping or {\"patience\": 10, \"tol\": 1e-4}\n", "\n", " self.use_priors = bool(use_priors)\n", " self.priors_cfg = priors_cfg or {\n", - " 'lengthscale': {'type': 'lognormal', 'loc': 0.0, 'scale': 0.5},\n", - " 'outputscale': {'type': 'lognormal', 'loc': 0.0, 'scale': 0.5},\n", - " 'noise': {'type': 'lognormal', 'loc': -4.0, 'scale': 0.5},\n", + " \"lengthscale\": {\"type\": \"lognormal\", \"loc\": 0.0, \"scale\": 0.5},\n", + " \"outputscale\": {\"type\": \"lognormal\", \"loc\": 0.0, \"scale\": 0.5},\n", + " \"noise\": {\"type\": \"lognormal\", \"loc\": -4.0, \"scale\": 0.5},\n", " }\n", " self.noise_floor = float(noise_floor)\n", " self.lengthscale_bounds = lengthscale_bounds\n", @@ -379,12 +400,12 @@ " self.rescale_on_scaler_change = bool(rescale_on_scaler_change)\n", " self.carry_optimizer_state = bool(carry_optimizer_state)\n", "\n", - " self.freeze_cfg = freeze_cfg or {'lengthscale_until_round': 0}\n", + " self.freeze_cfg = freeze_cfg or {\"lengthscale_until_round\": 0}\n", "\n", " self.verbose = bool(verbose)\n", " self.log_every = log_every\n", " self.save_best_state = bool(save_best_state)\n", - " self.lr = lr or {'embed': 4e-4, 'mean': 1e-3, 'kernel': 1e-3, 'like': 2e-2}\n", + " self.lr = lr or {\"embed\": 4e-4, \"mean\": 1e-3, \"kernel\": 1e-3, \"like\": 2e-2}\n", "\n", " # ---- State ----\n", " self._optimizer: Optional[torch.optim.Optimizer] = None\n", @@ -422,15 +443,17 @@ " return\n", " alpha = float(old_std / new_std)\n", " with torch.no_grad():\n", - " if hasattr(self.model, \"covar_module\") and hasattr(self.model.covar_module, \"outputscale\"):\n", - " self.model.covar_module.outputscale.mul_(alpha ** 2)\n", + " if hasattr(self.model, \"covar_module\") and hasattr(\n", + " self.model.covar_module, \"outputscale\"\n", + " ):\n", + " self.model.covar_module.outputscale.mul_(alpha**2)\n", " try:\n", - " self.model.likelihood.noise.mul_(alpha ** 2)\n", + " self.model.likelihood.noise.mul_(alpha**2)\n", " except Exception:\n", " # Fallback: attempt to read/set noise through likelihood noise_covar\n", " try:\n", " noise = self._get_noise_value()\n", - " self._set_noise_value(noise * (alpha ** 2))\n", + " self._set_noise_value(noise * (alpha**2))\n", " except Exception:\n", " pass\n", " self._last_scaler_std = new_std\n", @@ -440,9 +463,11 @@ " Optionally freeze base kernel lengthscale parameters for early rounds.\n", " \"\"\"\n", " self._round_idx = int(round_idx)\n", - " until = int(self.freeze_cfg.get('lengthscale_until_round', 0))\n", - " freeze = (round_idx <= until)\n", - " base_kernel = getattr(getattr(self.model, \"covar_module\", None), \"base_kernel\", None)\n", + " until = int(self.freeze_cfg.get(\"lengthscale_until_round\", 0))\n", + " freeze = round_idx <= until\n", + " base_kernel = getattr(\n", + " getattr(self.model, \"covar_module\", None), \"base_kernel\", None\n", + " )\n", " if base_kernel is not None:\n", " for p in base_kernel.parameters():\n", " p.requires_grad_(not freeze)\n", @@ -457,9 +482,14 @@ " - best-state checkpointing (optional).\n", " \"\"\"\n", " model = self.model\n", - " model.train(); model.likelihood.train()\n", - "\n", - " if self._optimizer is None or self.recreate_optimizer_each_round or not self.carry_optimizer_state:\n", + " model.train()\n", + " model.likelihood.train()\n", + "\n", + " if (\n", + " self._optimizer is None\n", + " or self.recreate_optimizer_each_round\n", + " or not self.carry_optimizer_state\n", + " ):\n", " self._optimizer = self._build_optimizer()\n", " self._scheduler = self._build_scheduler(self._optimizer)\n", " elif self._scheduler is None:\n", @@ -470,8 +500,8 @@ " best_loss = float(\"inf\")\n", " best_state = None\n", " no_improve = 0\n", - " patience = int(self.early_stopping.get('patience', 10))\n", - " tol = float(self.early_stopping.get('tol', 1e-4))\n", + " patience = int(self.early_stopping.get(\"patience\", 10))\n", + " tol = float(self.early_stopping.get(\"tol\", 1e-4))\n", " self._history = []\n", "\n", " train_x, train_y = model.train_inputs[0], model.train_targets\n", @@ -499,21 +529,29 @@ " if self.save_best_state:\n", " best_state = {\n", " \"model\": copy.deepcopy(model.state_dict()),\n", - " \"likelihood\": copy.deepcopy(model.likelihood.state_dict())\n", + " \"likelihood\": copy.deepcopy(model.likelihood.state_dict()),\n", " }\n", " else:\n", " no_improve += 1\n", "\n", - " if self.verbose and (self.log_every is None or it % max(1, self.log_every) == 0 or it == self.training_iter):\n", + " if self.verbose and (\n", + " self.log_every is None\n", + " or it % max(1, self.log_every) == 0\n", + " or it == self.training_iter\n", + " ):\n", " ls_val = self._safe_get_lengthscale()\n", - " out_v = self._safe_get_outputscale()\n", - " nz_v = self._safe_get_noise()\n", - " print(f\"[round {self._round_idx:>3d} | {it:>4d}/{self.training_iter}] \"\n", - " f\"nll={cur:.4f} len={ls_val} out={out_v:.3e} noise={nz_v:.3e}\")\n", + " out_v = self._safe_get_outputscale()\n", + " nz_v = self._safe_get_noise()\n", + " print(\n", + " f\"[round {self._round_idx:>3d} | {it:>4d}/{self.training_iter}] \"\n", + " f\"nll={cur:.4f} len={ls_val} out={out_v:.3e} noise={nz_v:.3e}\"\n", + " )\n", "\n", " if no_improve >= patience and rel_impr < tol:\n", " if self.verbose:\n", - " print(f\"[round {self._round_idx:>3d}] early stop @ {it}, best nll={best_loss:.4f}\")\n", + " print(\n", + " f\"[round {self._round_idx:>3d}] early stop @ {it}, best nll={best_loss:.4f}\"\n", + " )\n", " break\n", "\n", " if self.save_best_state and best_state is not None:\n", @@ -535,17 +573,39 @@ " # ================= Internal Utilities =================\n", " def _build_optimizer(self) -> torch.optim.Optimizer:\n", " # Group parameters by module for separate learning rates.\n", - " params_embed = list(getattr(self.model, \"embed\", torch.nn.Module()).parameters())\n", - " params_kernel = list(getattr(self.model, \"covar_module\", torch.nn.Module()).parameters())\n", - " params_like = list(self.model.likelihood.parameters())\n", - " params_mean = list(getattr(self.model, \"mean_module\", torch.nn.Module()).parameters())\n", + " params_embed = list(\n", + " getattr(self.model, \"embed\", torch.nn.Module()).parameters()\n", + " )\n", + " params_kernel = list(\n", + " getattr(self.model, \"covar_module\", torch.nn.Module()).parameters()\n", + " )\n", + " params_like = list(self.model.likelihood.parameters())\n", + " params_mean = list(\n", + " getattr(self.model, \"mean_module\", torch.nn.Module()).parameters()\n", + " )\n", "\n", " lr = self.lr\n", " groups = [\n", - " {'params': params_embed, 'lr': lr.get('embed', 4e-4), 'weight_decay': self.weight_decay},\n", - " {'params': params_mean, 'lr': lr.get('mean', 1e-3), 'weight_decay': self.weight_decay},\n", - " {'params': params_kernel, 'lr': lr.get('kernel', 1e-3), 'weight_decay': self.weight_decay},\n", - " {'params': params_like, 'lr': lr.get('like', 2e-2), 'weight_decay': self.weight_decay/5},\n", + " {\n", + " \"params\": params_embed,\n", + " \"lr\": lr.get(\"embed\", 4e-4),\n", + " \"weight_decay\": self.weight_decay,\n", + " },\n", + " {\n", + " \"params\": params_mean,\n", + " \"lr\": lr.get(\"mean\", 1e-3),\n", + " \"weight_decay\": self.weight_decay,\n", + " },\n", + " {\n", + " \"params\": params_kernel,\n", + " \"lr\": lr.get(\"kernel\", 1e-3),\n", + " \"weight_decay\": self.weight_decay,\n", + " },\n", + " {\n", + " \"params\": params_like,\n", + " \"lr\": lr.get(\"like\", 2e-2),\n", + " \"weight_decay\": self.weight_decay / 5,\n", + " },\n", " ]\n", " if self.optimizer_type == \"adam\":\n", " return torch.optim.Adam(groups)\n", @@ -554,36 +614,44 @@ " def _build_scheduler(self, optimizer) -> torch.optim.lr_scheduler.ReduceLROnPlateau:\n", " cfg = self.scheduler_cfg\n", " return torch.optim.lr_scheduler.ReduceLROnPlateau(\n", - " optimizer, mode='min',\n", - " factor=float(cfg.get('factor', 0.5)),\n", - " patience=int(cfg.get('patience', 5)),\n", - " min_lr=float(cfg.get('min_lr', 1e-6)),\n", - " verbose=False\n", + " optimizer,\n", + " mode=\"min\",\n", + " factor=float(cfg.get(\"factor\", 0.5)),\n", + " patience=int(cfg.get(\"patience\", 5)),\n", + " min_lr=float(cfg.get(\"min_lr\", 1e-6)),\n", + " verbose=False,\n", " )\n", "\n", " def _register_priors_safely(self):\n", " \"\"\"Attach priors to the **constrained** parameter names (lengthscale/outputscale/noise).\"\"\"\n", " from gpytorch.priors import LogNormalPrior\n", + "\n", " # lengthscale prior\n", " try:\n", " bk = self.model.covar_module.base_kernel\n", - " loc = float(self.priors_cfg['lengthscale']['loc'])\n", - " scale = float(self.priors_cfg['lengthscale']['scale'])\n", - " bk.register_prior(\"lengthscale_prior\", LogNormalPrior(loc, scale), \"lengthscale\")\n", + " loc = float(self.priors_cfg[\"lengthscale\"][\"loc\"])\n", + " scale = float(self.priors_cfg[\"lengthscale\"][\"scale\"])\n", + " bk.register_prior(\n", + " \"lengthscale_prior\", LogNormalPrior(loc, scale), \"lengthscale\"\n", + " )\n", " except Exception:\n", " pass\n", " # outputscale prior\n", " try:\n", - " loc = float(self.priors_cfg['outputscale']['loc'])\n", - " scale = float(self.priors_cfg['outputscale']['scale'])\n", - " self.model.covar_module.register_prior(\"outputscale_prior\", LogNormalPrior(loc, scale), \"outputscale\")\n", + " loc = float(self.priors_cfg[\"outputscale\"][\"loc\"])\n", + " scale = float(self.priors_cfg[\"outputscale\"][\"scale\"])\n", + " self.model.covar_module.register_prior(\n", + " \"outputscale_prior\", LogNormalPrior(loc, scale), \"outputscale\"\n", + " )\n", " except Exception:\n", " pass\n", " # noise prior\n", " try:\n", - " loc = float(self.priors_cfg['noise']['loc'])\n", - " scale = float(self.priors_cfg['noise']['scale'])\n", - " self.model.likelihood.register_prior(\"noise_prior\", LogNormalPrior(loc, scale), \"noise\")\n", + " loc = float(self.priors_cfg[\"noise\"][\"loc\"])\n", + " scale = float(self.priors_cfg[\"noise\"][\"scale\"])\n", + " self.model.likelihood.register_prior(\n", + " \"noise_prior\", LogNormalPrior(loc, scale), \"noise\"\n", + " )\n", " except Exception:\n", " pass\n", "\n", @@ -591,7 +659,10 @@ " # Noise lower bound (constraint on raw_noise)\n", " try:\n", " self.model.likelihood.noise_covar.register_constraint(\n", - " \"raw_noise\", gpytorch.constraints.GreaterThan(torch.tensor(self.noise_floor, device=self.device))\n", + " \"raw_noise\",\n", + " gpytorch.constraints.GreaterThan(\n", + " torch.tensor(self.noise_floor, device=self.device)\n", + " ),\n", " )\n", " except Exception:\n", " pass\n", @@ -624,7 +695,9 @@ " return float(self.model.likelihood.noise.detach().cpu().item())\n", " except Exception:\n", " try:\n", - " return float(self.model.likelihood.noise_covar.noise.detach().cpu().item())\n", + " return float(\n", + " self.model.likelihood.noise_covar.noise.detach().cpu().item()\n", + " )\n", " except Exception:\n", " return float(\"nan\")\n", "\n", @@ -634,59 +707,55 @@ " self.model.likelihood.noise.copy_(torch.tensor(v, device=self.device))\n", " except Exception:\n", " try:\n", - " self.model.likelihood.noise_covar.noise.copy_(torch.tensor(v, device=self.device))\n", + " self.model.likelihood.noise_covar.noise.copy_(\n", + " torch.tensor(v, device=self.device)\n", + " )\n", " except Exception:\n", " pass\n", "\n", " def _safe_get_noise(self) -> float:\n", - " return self._get_noise_value()\n" + " return self._get_noise_value()" ] }, { "cell_type": "code", "execution_count": null, - "id": "05472b9e", + "id": "11", "metadata": {}, "outputs": [], "source": [ "trainer = GPTrainer(\n", " model=model,\n", " device=DEVICE,\n", - "\n", " # --- Training and regularization ---\n", " training_iter=80,\n", - " lr={'embed': 4e-4, 'mean': 1e-3, 'kernel': 1e-3, 'like': 2e-2},\n", + " lr={\"embed\": 4e-4, \"mean\": 1e-3, \"kernel\": 1e-3, \"like\": 2e-2},\n", " weight_decay=1e-4,\n", " max_grad_norm=2.0,\n", - " optimizer_type='adamw',\n", + " optimizer_type=\"adamw\",\n", " recreate_optimizer_each_round=True,\n", - "\n", " # --- Scheduler and early stopping ---\n", - " scheduler_cfg={'factor': 0.5, 'patience': 5, 'min_lr': 1e-6},\n", - " early_stopping={'patience': 10, 'tol': 1e-4},\n", - "\n", + " scheduler_cfg={\"factor\": 0.5, \"patience\": 5, \"min_lr\": 1e-6},\n", + " early_stopping={\"patience\": 10, \"tol\": 1e-4},\n", " # --- Priors and constraints (helpful for small datasets) ---\n", " use_priors=True,\n", - " noise_floor=1e-3, # Lower bound for z-domain noise to avoid overfitting\n", - " lengthscale_bounds=None, # If X is normalized to [0,1], one may use (1e-2, 10.)\n", - "\n", + " noise_floor=1e-3, # Lower bound for z-domain noise to avoid overfitting\n", + " lengthscale_bounds=None, # If X is normalized to [0,1], one may use (1e-2, 10.)\n", " # --- Warm start and scaler rescaling ---\n", " warm_start=True,\n", - " rescale_on_scaler_change=True, # Recommended: True\n", + " rescale_on_scaler_change=True, # Recommended: True\n", " carry_optimizer_state=False,\n", - "\n", " # --- Freeze base kernel lengthscale in early rounds (stabilizes small-data regime) ---\n", - " freeze_cfg={'lengthscale_until_round': 2},\n", - "\n", + " freeze_cfg={\"lengthscale_until_round\": 2},\n", " verbose=True,\n", " log_every=8,\n", " save_best_state=True,\n", - ")\n" + ")" ] }, { "cell_type": "markdown", - "id": "4474c609", + "id": "12", "metadata": {}, "source": [ "# Acquisition function and it's optimization" @@ -695,16 +764,14 @@ { "cell_type": "code", "execution_count": null, - "id": "06762aea", + "id": "13", "metadata": {}, "outputs": [], "source": [ "# log_std_normalizer.py\n", - "import math\n", "from dataclasses import dataclass, asdict\n", "from typing import Tuple, Union, Dict, Any\n", "\n", - "import numpy as np\n", "import torch\n", "\n", "TensorLike = Union[torch.Tensor, np.ndarray, float]\n", @@ -713,9 +780,10 @@ "@dataclass\n", "class LogStdStats:\n", " \"\"\"Stores the fitted statistics (mean, std, eps) for log-domain normalization.\"\"\"\n", - " mean: float # mean of log(pl + eps)\n", + "\n", + " mean: float # mean of log(pl + eps)\n", " scale: float # std of log(pl + eps)\n", - " eps: float # epsilon used in log(pl + eps)\n", + " eps: float # epsilon used in log(pl + eps)\n", "\n", "\n", "class LogStdNormalizer:\n", @@ -728,7 +796,13 @@ " (μ_z, σ_z) in z-space back to the probability domain, using log-normal moments.\n", " \"\"\"\n", "\n", - " def __init__(self, eps: float = 1e-8, min_prob: float = 1e-12, max_prob: float = 1.0 - 1e-12, device: str = \"cpu\"):\n", + " def __init__(\n", + " self,\n", + " eps: float = 1e-8,\n", + " min_prob: float = 1e-12,\n", + " max_prob: float = 1.0 - 1e-12,\n", + " device: str = \"cpu\",\n", + " ):\n", " self.stats: LogStdStats = LogStdStats(mean=0.0, scale=1.0, eps=float(eps))\n", " self._fitted: bool = False\n", " self.min_prob = float(min_prob)\n", @@ -757,7 +831,7 @@ " pl_t = self._clamp_prob(pl_t)\n", " ylog = torch.log(pl_t + self.stats.eps)\n", " mean = ylog.mean().item()\n", - " std = ylog.std(unbiased=False).item() # population standard deviation\n", + " std = ylog.std(unbiased=False).item() # population standard deviation\n", "\n", " # Avoid degenerate scaling (very small std can cause instability)\n", " if std < 1e-12:\n", @@ -795,7 +869,9 @@ "\n", " # -------------------- Posterior inverse transform (used in EI acquisition) --------------------\n", " @torch.no_grad()\n", - " def inverse_mean_std(self, mu_z: TensorLike, std_z: TensorLike) -> Tuple[torch.Tensor, torch.Tensor]:\n", + " def inverse_mean_std(\n", + " self, mu_z: TensorLike, std_z: TensorLike\n", + " ) -> Tuple[torch.Tensor, torch.Tensor]:\n", " \"\"\"\n", " If z ~ N(mu_z, std_z^2), then log(pl + eps) ~ N(mu, std^2), where:\n", " mu = mu_z * scale + mean\n", @@ -809,19 +885,19 @@ " (mean_pl, std_pl): expected mean and std of pl in probability domain.\n", " \"\"\"\n", " assert self._fitted, \"Call fit(pl_train) before inverse_mean_std.\"\n", - " mu_z_t = self._to_tensor(mu_z, dtype=torch.float32)\n", + " mu_z_t = self._to_tensor(mu_z, dtype=torch.float32)\n", " std_z_t = self._to_tensor(std_z, dtype=torch.float32).abs()\n", "\n", - " mu = mu_z_t * self.stats.scale + self.stats.mean\n", + " mu = mu_z_t * self.stats.scale + self.stats.mean\n", " std = std_z_t * abs(self.stats.scale)\n", "\n", " # E[pl + eps] and Var(pl + eps)\n", " exp_half_var = torch.exp(0.5 * std**2)\n", " mean_pl_plus = torch.exp(mu) * exp_half_var\n", - " var_pl_plus = (torch.exp(std**2) - 1.0) * torch.exp(2.0 * mu + std**2)\n", + " var_pl_plus = (torch.exp(std**2) - 1.0) * torch.exp(2.0 * mu + std**2)\n", "\n", " mean_pl = mean_pl_plus - self.stats.eps\n", - " std_pl = var_pl_plus.clamp_min(1e-30).sqrt()\n", + " std_pl = var_pl_plus.clamp_min(1e-30).sqrt()\n", "\n", " # Clamp mean within valid probability range\n", " mean_pl = self._clamp_prob(mean_pl)\n", @@ -847,23 +923,25 @@ " \"fitted\": self._fitted,\n", " \"min_prob\": self.min_prob,\n", " \"max_prob\": self.max_prob,\n", - " \"device\": self.device\n", + " \"device\": self.device,\n", " }\n", "\n", " def load_state_dict(self, state: Dict[str, Any]):\n", " \"\"\"Restore normalizer state from a dict.\"\"\"\n", " s = state[\"stats\"]\n", - " self.stats = LogStdStats(mean=float(s[\"mean\"]), scale=float(s[\"scale\"]), eps=float(s[\"eps\"]))\n", + " self.stats = LogStdStats(\n", + " mean=float(s[\"mean\"]), scale=float(s[\"scale\"]), eps=float(s[\"eps\"])\n", + " )\n", " self._fitted = bool(state.get(\"fitted\", True))\n", " self.min_prob = float(state.get(\"min_prob\", self.min_prob))\n", " self.max_prob = float(state.get(\"max_prob\", self.max_prob))\n", - " self.device = state.get(\"device\", self.device)\n" + " self.device = state.get(\"device\", self.device)" ] }, { "cell_type": "code", "execution_count": null, - "id": "6478f2d7", + "id": "14", "metadata": {}, "outputs": [], "source": [ @@ -903,21 +981,37 @@ " Epsilon used in log(pl + eps) transform.\n", " \"\"\"\n", "\n", - " def __init__(self, pl_to_obj_fn, best_value=None, jitter=1e-2, eps=1e-9,\n", - " device=\"cpu\", normalizer=None, prob_lower=1e-12, prob_upper=1.0 - 1e-12,\n", - " log_eps=1e-8):\n", + " def __init__(\n", + " self,\n", + " pl_to_obj_fn,\n", + " best_value=None,\n", + " jitter=1e-2,\n", + " eps=1e-9,\n", + " device=\"cpu\",\n", + " normalizer=None,\n", + " prob_lower=1e-12,\n", + " prob_upper=1.0 - 1e-12,\n", + " log_eps=1e-8,\n", + " ):\n", " self.pl_to_obj_fn = pl_to_obj_fn\n", - " self.best_value = best_value\n", - " self.jitter = float(jitter)\n", - " self.eps = float(eps)\n", - " self.device = device\n", - " self.prob_lower = float(prob_lower)\n", - " self.prob_upper = float(prob_upper)\n", - " self.log_eps = float(log_eps)\n", + " self.best_value = best_value\n", + " self.jitter = float(jitter)\n", + " self.eps = float(eps)\n", + " self.device = device\n", + " self.prob_lower = float(prob_lower)\n", + " self.prob_upper = float(prob_upper)\n", + " self.log_eps = float(log_eps)\n", "\n", " # EI maintains its own normalizer; create one if not provided.\n", - " self.normalizer = normalizer if normalizer is not None else LogStdNormalizer(\n", - " eps=self.log_eps, device=self.device, min_prob=self.prob_lower, max_prob=self.prob_upper\n", + " self.normalizer = (\n", + " normalizer\n", + " if normalizer is not None\n", + " else LogStdNormalizer(\n", + " eps=self.log_eps,\n", + " device=self.device,\n", + " min_prob=self.prob_lower,\n", + " max_prob=self.prob_upper,\n", + " )\n", " )\n", "\n", " # ---------- Interface exposed to BO loop ----------\n", @@ -946,9 +1040,11 @@ " return ret[0], ret[1]\n", " if isinstance(ret, dict):\n", " mu = ret.get(\"mean\", ret.get(\"mu\"))\n", - " sd = ret.get(\"std\", ret.get(\"sigma\"))\n", + " sd = ret.get(\"std\", ret.get(\"sigma\"))\n", " if mu is None or sd is None:\n", - " raise RuntimeError(\"pl_to_obj_fn dict must contain mean/std (or mu/sigma).\")\n", + " raise RuntimeError(\n", + " \"pl_to_obj_fn dict must contain mean/std (or mu/sigma).\"\n", + " )\n", " return mu, sd\n", " raise RuntimeError(f\"Unsupported return type from pl_to_obj_fn: {type(ret)}\")\n", "\n", @@ -965,7 +1061,9 @@ " EI(x) = (μ_f - f* - ξ) Φ(Z) + σ_f φ(Z),\n", " where Z = (μ_f - f* - ξ) / σ_f, Φ = CDF, φ = PDF of standard normal.\n", " \"\"\"\n", - " assert self.best_value is not None, \"best_value not set; call set_best_value() first.\"\n", + " assert self.best_value is not None, (\n", + " \"best_value not set; call set_best_value() first.\"\n", + " )\n", "\n", " # Ensure X is a proper tensor on the correct device\n", " if not torch.is_tensor(X):\n", @@ -981,11 +1079,11 @@ "\n", " # Posterior in latent z-domain\n", " with gpytorch.settings.fast_pred_var():\n", - " if hasattr(gp, \"posterior\"): # BoTorch-style API\n", + " if hasattr(gp, \"posterior\"): # BoTorch-style API\n", " post = gp.posterior(X)\n", " mu_lat = post.mean.reshape(-1)\n", " var_lat = post.variance.reshape(-1)\n", - " else: # Pure GPyTorch model\n", + " else: # Pure GPyTorch model\n", " mvn = gp(X)\n", " mu_lat = mvn.mean.reshape(-1)\n", " var_lat = mvn.variance.reshape(-1)\n", @@ -994,7 +1092,9 @@ "\n", " # Convert (μ_z, σ_z) → probability domain\n", " if not self.normalizer.is_fitted():\n", - " raise RuntimeError(\"EI normalizer is not fitted. Call update_normalizer(pl_train) first.\")\n", + " raise RuntimeError(\n", + " \"EI normalizer is not fitted. Call update_normalizer(pl_train) first.\"\n", + " )\n", " mu_pl, std_pl = self.normalizer.inverse_mean_std(mu_lat, std_lat)\n", "\n", " # Clamp probability and std for stability\n", @@ -1007,8 +1107,16 @@ " for i in range(mu_pl.numel()):\n", " out = self.pl_to_obj_fn(X[i], mu_pl[i], std_pl[i])\n", " mu_i, sd_i = self._take_mean_std(out)\n", - " mu_obj[i] = mu_i if torch.is_tensor(mu_i) else torch.tensor(mu_i, device=self.device, dtype=torch.float32)\n", - " std_obj[i] = sd_i if torch.is_tensor(sd_i) else torch.tensor(sd_i, device=self.device, dtype=torch.float32)\n", + " mu_obj[i] = (\n", + " mu_i\n", + " if torch.is_tensor(mu_i)\n", + " else torch.tensor(mu_i, device=self.device, dtype=torch.float32)\n", + " )\n", + " std_obj[i] = (\n", + " sd_i\n", + " if torch.is_tensor(sd_i)\n", + " else torch.tensor(sd_i, device=self.device, dtype=torch.float32)\n", + " )\n", "\n", " std_obj = std_obj.clamp_min(self.eps)\n", "\n", @@ -1017,32 +1125,32 @@ " Z = imp / std_obj\n", " normal = Normal(torch.zeros_like(mu_obj), torch.ones_like(std_obj))\n", " ei = imp * normal.cdf(Z) + std_obj * torch.exp(normal.log_prob(Z))\n", - " return ei\n" + " return ei" ] }, { "cell_type": "code", "execution_count": null, - "id": "391de9c1", + "id": "15", "metadata": {}, "outputs": [], "source": [ "acq = EIAcquisitionFunction(\n", - " pl_to_obj_fn = pl_to_obj,\n", - " best_value = None, \n", - " jitter = 1e-2,\n", - " eps = 1e-9,\n", - " device = DEVICE,\n", - " normalizer = None, \n", - " prob_lower = 1e-12,\n", - " prob_upper = 1.0 - 1e-12,\n", - " log_eps = 1e-8\n", + " pl_to_obj_fn=pl_to_obj,\n", + " best_value=None,\n", + " jitter=1e-2,\n", + " eps=1e-9,\n", + " device=DEVICE,\n", + " normalizer=None,\n", + " prob_lower=1e-12,\n", + " prob_upper=1.0 - 1e-12,\n", + " log_eps=1e-8,\n", ")" ] }, { "cell_type": "markdown", - "id": "1761403f", + "id": "16", "metadata": {}, "source": [ "params for hill climbing:" @@ -1051,21 +1159,26 @@ { "cell_type": "code", "execution_count": null, - "id": "c4d61c9c", + "id": "17", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import torch\n", "\n", + "\n", "class HillClimbing:\n", - " def __init__(self, next_points_num, gnp, acquisition, device=\"cuda\", validator=None):\n", + " def __init__(\n", + " self, next_points_num, gnp, acquisition, device=\"cuda\", validator=None\n", + " ):\n", "\n", " self.next_points_num = int(next_points_num)\n", " self.gnp = gnp\n", " self.acquisition = acquisition\n", " self.device = device\n", - " self.validator = validator # 例如 lambda z: code_constructor.construct(z).k != 0\n", + " self.validator = (\n", + " validator # 例如 lambda z: code_constructor.construct(z).k != 0\n", + " )\n", "\n", " def hill_climbing_neighbors(self, x: torch.Tensor) -> torch.Tensor:\n", "\n", @@ -1083,7 +1196,7 @@ " def __call__(self, gp) -> torch.Tensor:\n", "\n", " # 起点:np -> torch.float32 on device\n", - " X0_np = self.gnp(self.next_points_num) # [n, d], 0/1\n", + " X0_np = self.gnp(self.next_points_num) # [n, d], 0/1\n", " X0 = torch.tensor(X0_np, dtype=torch.float32, device=self.device)\n", "\n", " best_list = []\n", @@ -1108,46 +1221,48 @@ "\n", " cand = torch.stack(best_list, dim=0) # [n, d], float32, 0/1\n", "\n", - "\n", " if self.validator is not None:\n", " cand_np = cand.detach().cpu().numpy().astype(np.int64)\n", " for i in range(cand_np.shape[0]):\n", " if not self.validator(cand_np[i]):\n", - "\n", " tries = 0\n", " while not self.validator(cand_np[i]):\n", " tries += 1\n", - " cand_np[i] = np.random.randint(0, 2, cand_np[i].shape, dtype=np.int64)\n", + " cand_np[i] = np.random.randint(\n", + " 0, 2, cand_np[i].shape, dtype=np.int64\n", + " )\n", " if tries > 1000:\n", - " break \n", + " break\n", " cand = torch.tensor(cand_np, dtype=torch.float32, device=self.device)\n", "\n", - " return cand # [n, d], float32(0/1), on device\n" + " return cand # [n, d], float32(0/1), on device" ] }, { "cell_type": "code", "execution_count": null, - "id": "b6e29b61", + "id": "18", "metadata": {}, "outputs": [], "source": [ "def bb_validator(candidate_np):\n", " return code_constructor.construct(candidate_np).k != 0\n", + "\n", + "\n", "next_points_num = 4\n", "\n", "hc = HillClimbing(\n", - " next_points_num = next_points_num, # the candidate number\n", - " gnp = gnp, # get new points function\n", - " acquisition = acq, \n", - " device = DEVICE,\n", - " validator = bb_validator\n", + " next_points_num=next_points_num, # the candidate number\n", + " gnp=gnp, # get new points function\n", + " acquisition=acq,\n", + " device=DEVICE,\n", + " validator=bb_validator,\n", ")" ] }, { "cell_type": "markdown", - "id": "ba52d0d9", + "id": "19", "metadata": {}, "source": [ "# Assemble the BO\n" @@ -1156,932 +1271,33 @@ { "cell_type": "code", "execution_count": null, - "id": "b06101f5", + "id": "20", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Initial best value: -0.1862\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages/torch/optim/lr_scheduler.py:62: UserWarning: The verbose parameter is deprecated. Please use get_last_lr() to access the learning rate.\n", - " warnings.warn(\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[round 0 | 8/80] nll=3.6365 len=[[0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996]] out=6.965e-01 noise=6.849e-01\n", - "[round 0 | 16/80] nll=3.5021 len=[[0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996]] out=6.988e-01 noise=6.278e-01\n", - "[round 0 | 24/80] nll=3.5551 len=[[0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996]] out=6.997e-01 noise=5.885e-01\n", - "[round 0] early stop @ 29, best nll=3.4734\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "BB-BO (GP+EI+HC): 0%| | 0/50 [00:00" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "# Flatten the provided evaluation history into a 1D vector and plot it.\n", "import matplotlib.pyplot as plt\n", - "import pandas as pd\n", - " \n", + "\n", "\n", "# Flatten to 1D list\n", "flat = [v for row in evaluation_history for v in row]\n", "\n", "\n", - "\n", "# Plot\n", "plt.figure()\n", - "plt.plot(range(1, len(flat)+1), flat, marker='o')\n", + "plt.plot(range(1, len(flat) + 1), flat, marker=\"o\")\n", "plt.xlabel(\"Evaluation index (flattened)\")\n", "plt.ylabel(\"F value\")\n", "plt.title(\"Evaluation History (Flattened)\")\n", "plt.grid(True)\n", "plt.tight_layout()\n", - "plt.show()\n", - "\n" + "plt.show()" ] } ], "metadata": { "kernelspec": { - "display_name": "QEC2", + "display_name": "qec", "language": "python", "name": "python3" }, @@ -2169,7 +1361,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.13" + "version": "3.10.19" } }, "nbformat": 4, diff --git a/test_gp.ipynb b/test_gp.ipynb index c66631c..314594b 100644 --- a/test_gp.ipynb +++ b/test_gp.ipynb @@ -2,50 +2,37 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, - "id": "471440b1", + "execution_count": null, + "id": "0", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", - " from .autonotebook import tqdm as notebook_tqdm\n" - ] - } - ], + "outputs": [], "source": [ "import numpy as np\n", - "from bayesian_optimization.encoder import *\n", + "from bayesian_optimization.encoder import CSSEncoder\n", + "from gpytorch.mlls import ExactMarginalLogLikelihood\n", "from code_construction.code_construction import *\n", "from dataset import *\n", - "from botorch.models import SingleTaskGP\n", - "from torch import nn\n", - "from botorch.optim import optimize_acqf\n", - "from botorch.acquisition import AcquisitionFunction\n", - "from gpytorch.mlls import ExactMarginalLogLikelihood\n", "import random\n", - "import numpy as np\n", "import torch\n", "\n", + "\n", "def set_all_seeds(seed: int = 42):\n", " random.seed(seed)\n", " np.random.seed(seed)\n", " torch.manual_seed(seed)\n", " torch.cuda.manual_seed(seed)\n", - " torch.cuda.manual_seed_all(seed) \n", + " torch.cuda.manual_seed_all(seed)\n", " torch.backends.cudnn.deterministic = True\n", " torch.backends.cudnn.benchmark = False\n", "\n", + "\n", "seed = 42\n", - "set_all_seeds(seed)\n", - "\n" + "set_all_seeds(seed)" ] }, { "cell_type": "markdown", - "id": "cca0acae", + "id": "1", "metadata": {}, "source": [ "# Load the dataset" @@ -53,36 +40,30 @@ }, { "cell_type": "code", - "execution_count": 2, - "id": "c513a24b", + "execution_count": null, + "id": "2", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxYAAAHqCAYAAACZcdjsAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAABFbklEQVR4nO3deXgUZb728bs6JOkEEkL2RMIiuyAuERFZBgTZlAFh3JFFRp0ZQCVydJiXOYgbKoPigqhnFNQBdXREHR1BYWRRQSWCDBqibIaddBCykA4xXe8fHPoQEiDdlU51J9/PdeW66KfrqfpVdfVyU8tjmKZpCgAAAAAscNhdAAAAAIDQR7AAAAAAYBnBAgAAAIBlBAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawABB07r//fhmGUamtVatWGjduXMCXvXPnThmGoYULF3rbxo0bpyZNmgR82ScYhqH777+/zpbnj6+//lqXX365GjduLMMwtHHjRrtL8qpu/znbtC6XK8BVVVbT1zgU1gUATiBYAKi3/vWvfwXtD/Rgru1sysvLde211+rQoUN68skn9dprr6lly5Z2l3VGjzzyiN599127y6gVdq/L+++/r4svvlhOp1MtWrTQjBkz9Msvv9So78MPP6xf//rXSklJCYkADcA3BAsAISE3N1f/8z//41Off/3rX5o5c6ZPfVq2bKnS0lLdcsstPvXz1ZlqKy0t1fTp0wO6fCu2bdumn376SVOnTtXtt9+u0aNHq1mzZnaX5TV9+nSVlpZWarP7x/iprLzGdq7LRx99pBEjRiguLk7PPPOMRowYoYceekiTJ0+uUf/p06fr66+/1kUXXRTgSgHYoZHdBQBATURGRgZ0/r/88os8Ho8iIiLkdDoDuqyzsXv5Z3Pw4EFJUlxcnL2FnEajRo3UqFFwf70F+2t8OlOnTlXXrl318ccfe7dxbGysHnnkEd11113q2LHjGfvv2LFDrVq1ksvlUlJSUl2UDKAOccQCgK0+++wzdevWTU6nU23atNELL7xQ7XSnXmNRXl6umTNnql27dnI6nUpISFCvXr30ySefSDp+XcS8efMkHT+f/cSf9H/XUfzlL3/R3Llz1aZNG0VGRur777+v9hqLE7Zv365BgwapcePGSk9P1wMPPCDTNL3Pr1y5UoZhaOXKlZX6nTrPM9V2ou3UU0Q2bNigIUOGKDY2Vk2aNFH//v21bt26StMsXLhQhmHo888/V1ZWlpKSktS4cWNdc801ys/Pr/4FOMW///1v9e7dW40bN1ZcXJyGDx+unJwc7/Pjxo3Tr371K0nStddeK8Mw1Ldv32rntX37dhmGoSeffLLKc1988YUMw9Drr79ebV/TNJWYmKisrCxvm8fjUVxcnMLCwnT48GFv+2OPPaZGjRqpuLhYUtXrEgzDUElJiV555RXvtj71ep3Dhw9r3LhxiouLU9OmTTV+/HgdPXr0jNvq6aefrlLLnDlzZBhGpborKioUExOj++67r1JNp77GNXkvBGpdauL777/X999/r9tvv71ScPvDH/4g0zT19ttvn3UerVq1slwHgOAV3P+lA6Be+89//qOBAwcqKSlJ999/v3755RfNmDFDKSkpZ+17//33a9asWfrtb3+rSy+9VIWFhVq/fr2++eYbXXnllbrjjju0d+9effLJJ3rttdeqnceCBQvkdrt1++23KzIyUvHx8fJ4PNVOW1FRocGDB+uyyy7T448/rqVLl3rPLX/ggQd8Wu+a1Hay7777Tr1791ZsbKzuvfdehYeH64UXXlDfvn21atUqde/evdL0kydPVrNmzTRjxgzt3LlTc+fO1aRJk/Tmm2+ecTnLly/XkCFDdO655+r+++9XaWmpnnnmGfXs2VPffPONWrVqpTvuuEPnnHOOHnnkEd15553q1q3baV+vc889Vz179tSiRYs0ZcqUSs8tWrRIMTExGj58eLV9DcNQz549tXr1am/bpk2bdOTIETkcDn3++ee66qqrJElr1qzRRRdddNoL7F977TXvfnL77bdLktq0aVNpmuuuu06tW7fWrFmz9M033+ivf/2rkpOT9dhjj512e/Xu3Vsej0efffaZrr76am8tDodDa9as8U63YcMGFRcXq0+fPqedV03fC7W1LkeOHFF5eflp6znB6XR6t+uGDRskSZdcckmladLT09W8eXPv8wAaMBMAbDJixAjT6XSaP/30k7ft+++/N8PCwsxTP55atmxpjh071vv4ggsuMK+66qozzn/ixIlV5mOaprljxw5TkhkbG2sePHiw2ucWLFjgbRs7dqwpyZw8ebK3zePxmFdddZUZERFh5ufnm6Zpmp9++qkpyfz000/POs/T1WaapinJ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wzIMQytXrvS2bd68WUOGDFGTJk2UkpKiW265RS6Xq7ZXDwDgI4IFAMAv1157rQoKCvTpp5962w4dOqSlS5fq5ptvVnFxsYYOHaoVK1Zow4YNGjx4sIYNG6a8vDy/l3n48GFdccUVuuiii7R+/XotXbpUBw4c0HXXXVcbqwQAsKCR3QUAAEJTs2bNNGTIEC1evFj9+/eXJL399ttKTExUv3795HA4dMEFF3inf/DBB7VkyRK9//77mjRpkl/LfPbZZ3XRRRfpkUce8ba9/PLLysjI0A8//KD27dtbWykAgN84YgEA8NvNN9+sf/zjHyorK5MkLVq0SDfccIMcDoeKi4s1depUderUSXFxcWrSpIlycnIsHbH49ttv9emnn6pJkybev44dO0qStm3bVivrBADwD0csAAB+GzZsmEzT1Icffqhu3bppzZo1evLJJyVJU6dO1SeffKK//OUvatu2raKiovSb3/xGx44dq3ZeDsfx/+syTdPbVl5eXmma4uJiDRs2TI899liV/mlpabW1WgAAPxAsAAB+czqdGjlypBYtWqStW7eqQ4cOuvjiiyVJn3/+ucaNG6drrrlG0vFQsHPnztPOKykpSZK0b98+XXTRRZJU6UJuSbr44ov1j3/8Q61atVKjRnyFAUAw4VQoAIAlN998sz788EO9/PLLuvnmm73t7dq10zvvvKONGzfq22+/1U033VTlDlIni4qK0mWXXaZHH31UOTk5WrVqlaZPn15pmokTJ+rQoUO68cYb9fXXX2vbtm1atmyZxo8fr4qKioCtIwDg7AgWAABLrrjiCsXHxys3N1c33XSTt/2JJ55Qs2bNdPnll2vYsGEaNGiQ92jG6bz88sv65ZdflJmZqbvvvlsPPfRQpefT09P1+eefq6KiQgMHDtT555+vu+++W3Fxcd5TqQAA9jDMk09mBQAAAAA/8N87AAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAy/4/HojC5rFtZy8AAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "from sklearn.preprocessing import StandardScaler\n", + "from matplotlib import pyplot as plt\n", + "\n", "# l = 6\n", "# g = 3\n", "# para_dict = {'l':l,'g':g}\n", "# total_dataset = QEC_Dataset(l,g,load = True,number = 300,save = True)\n", "total_dataset = MergeDatasets()\n", - "total_dataset.load(['./data/codes/bb_36_6_3_100_depolarizing.pkl',\n", - " './data/codes/bb_36_9_2_100_depolarizing.pkl',\n", - " './data/codes/hgp_36_2_2_3_100_depolarizing.pkl',\n", - " './data/codes/hgp_36_3_3_2_100_depolarizing.pkl'])\n", + "total_dataset.load(\n", + " [\n", + " \"./data/codes/bb_36_6_3_100_depolarizing.pkl\",\n", + " \"./data/codes/bb_36_9_2_100_depolarizing.pkl\",\n", + " \"./data/codes/hgp_36_2_2_3_100_depolarizing.pkl\",\n", + " \"./data/codes/hgp_36_3_3_2_100_depolarizing.pkl\",\n", + " ]\n", + ")\n", "total_dataset.y = torch.tensor(total_dataset.y)\n", "\n", "\n", - "\n", "eps = 1e-20\n", "total_dataset.y = torch.log(total_dataset.y + eps)\n", "\n", @@ -94,21 +75,25 @@ "# total_dataset.X = torch.tensor(total_dataset.X)\n", "\n", "\n", - "import random\n", - "from torch.utils.data import Subset\n", "import pandas as pd\n", + "\n", + "\n", "def plot_distribution(y, bin_width):\n", " bins = np.arange(np.min(y), np.max(y) + bin_width, bin_width)\n", - " \n", + "\n", " plt.figure(figsize=(8, 5))\n", - " plt.hist(y, bins=bins, edgecolor='black')\n", - " plt.xlabel('value')\n", - " plt.ylabel('frequency')\n", - " plt.title(f'distribution of y with width={bin_width}')\n", - " plt.grid(axis='y', alpha=0.3)\n", + " plt.hist(y, bins=bins, edgecolor=\"black\")\n", + " plt.xlabel(\"value\")\n", + " plt.ylabel(\"frequency\")\n", + " plt.title(f\"distribution of y with width={bin_width}\")\n", + " plt.grid(axis=\"y\", alpha=0.3)\n", " plt.tight_layout()\n", " plt.show()\n", + "\n", + "\n", "plot_distribution(total_dataset.y.detach().cpu().numpy(), bin_width=0.1)\n", + "\n", + "\n", "# plot_distribution(total_dataset.y, bin_width=0.1)\n", "def train_test_split(dataset, test_size=0.2, shuffle=True, random_state=None):\n", " \"\"\"\n", @@ -136,75 +121,72 @@ "\n", " # Split\n", " train_idx = indices[:n_train]\n", - " test_idx = indices[n_train:]\n", + " test_idx = indices[n_train:]\n", "\n", " # Wrap in Subset\n", " train_dataset = QECSubset(dataset, train_idx)\n", - " test_dataset = QECSubset(dataset, test_idx)\n", + " test_dataset = QECSubset(dataset, test_idx)\n", " return train_dataset, test_dataset\n", + "\n", + "\n", "def evaluate_gp_on_test(\n", " test_data,\n", " model,\n", " *,\n", " plot: bool = True,\n", " return_df: bool = False,\n", - " use_std: bool = False\n", + " use_std: bool = False,\n", "):\n", "\n", " X_test = torch.tensor(test_data.X, dtype=torch.float32)\n", " y_test = torch.tensor(test_data.y, dtype=torch.float32)\n", "\n", - "\n", " model.eval()\n", " model.likelihood.eval()\n", " with torch.no_grad(), gpytorch.settings.fast_pred_var():\n", - "\n", " posterior = model.posterior(X_test)\n", " mean = posterior.mean\n", " var = posterior.variance\n", "\n", - "\n", - "\n", " err = torch.sqrt(var) if use_std else var\n", "\n", - "\n", " mse = torch.mean((mean - y_test) ** 2).item()\n", " ss_res = torch.sum((y_test - mean) ** 2).item()\n", " ss_tot = torch.sum((y_test - torch.mean(y_test)) ** 2).item()\n", - " r2 = 1 - ss_res / ss_tot if ss_tot > 0 else float('nan')\n", + " r2 = 1 - ss_res / ss_tot if ss_tot > 0 else float(\"nan\")\n", "\n", - " y_test = y_test.squeeze(-1) \n", - " mean = mean.squeeze(-1) \n", - " err = err.squeeze(-1) \n", + " y_test = y_test.squeeze(-1)\n", + " mean = mean.squeeze(-1)\n", + " err = err.squeeze(-1)\n", " idx = torch.argsort(y_test)\n", " y_sorted = y_test[idx].numpy()\n", " m_sorted = mean[idx].numpy()\n", " e_sorted = err[idx].numpy()\n", "\n", - " \n", " if plot:\n", " plt.figure()\n", - " plt.plot(y_sorted, label='True values')\n", - " plt.plot(m_sorted, label='Predicted mean')\n", - " plt.plot(m_sorted + e_sorted, label=f'Mean + {\"STD\" if use_std else \"Var\"}')\n", - " plt.plot(m_sorted - e_sorted, label=f'Mean - {\"STD\" if use_std else \"Var\"}')\n", - " plt.xlabel('Samples (sorted by true value)')\n", - " plt.ylabel('Value')\n", - " plt.title('True vs Predicted mean ± ' + (\"STD\" if use_std else \"VAR\"))\n", + " plt.plot(y_sorted, label=\"True values\")\n", + " plt.plot(m_sorted, label=\"Predicted mean\")\n", + " plt.plot(m_sorted + e_sorted, label=f\"Mean + {'STD' if use_std else 'Var'}\")\n", + " plt.plot(m_sorted - e_sorted, label=f\"Mean - {'STD' if use_std else 'Var'}\")\n", + " plt.xlabel(\"Samples (sorted by true value)\")\n", + " plt.ylabel(\"Value\")\n", + " plt.title(\"True vs Predicted mean ± \" + (\"STD\" if use_std else \"VAR\"))\n", " plt.legend()\n", " plt.tight_layout()\n", " plt.show()\n", "\n", - "\n", - " df = pd.DataFrame({\n", - " 'True value': y_sorted,\n", - " 'Predicted mean': m_sorted,\n", - " 'Predicted ' + ('std' if use_std else 'var'): e_sorted\n", - " })\n", + " df = pd.DataFrame(\n", + " {\n", + " \"True value\": y_sorted,\n", + " \"Predicted mean\": m_sorted,\n", + " \"Predicted \" + (\"std\" if use_std else \"var\"): e_sorted,\n", + " }\n", + " )\n", "\n", " if return_df:\n", - " return mse,r2, df\n", - " return mse,r2\n", + " return mse, r2, df\n", + " return mse, r2\n", "\n", "\n", "# model = get_model(l,g,torch.tensor(train_dataset.X,dtype=torch.float32),torch.tensor(train_dataset.y,dtype=torch.float32))" @@ -212,7 +194,7 @@ }, { "cell_type": "markdown", - "id": "8b557acd", + "id": "3", "metadata": {}, "source": [ "# Initialize the Encoding\n", @@ -252,312 +234,38 @@ }, { "cell_type": "code", - "execution_count": 5, - "id": "2d13fe48", + "execution_count": null, + "id": "4", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'decode': {'sizes': {'SZ': 18, 'DQ': 36, 'SX': 18},\n", - " 'adj': {'SZ_DQ': tensor(indices=tensor([[ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3,\n", - " 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6,\n", - " 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 10, 10,\n", - " 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13,\n", - " 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17,\n", - " 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23,\n", - " 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30, 30,\n", - " 31, 31, 32, 32, 33, 33, 34, 34, 35, 35],\n", - " [ 0, 3, 9, 15, 1, 4, 10, 16, 2, 5, 11, 17, 0, 3,\n", - " 6, 12, 1, 4, 7, 13, 2, 5, 8, 14, 3, 6, 9, 15,\n", - " 4, 7, 10, 16, 5, 8, 11, 17, 0, 6, 9, 12, 1, 7,\n", - " 10, 13, 2, 8, 11, 14, 3, 9, 12, 15, 4, 10, 13, 16,\n", - " 5, 11, 14, 17, 0, 6, 12, 15, 1, 7, 13, 16, 2, 8,\n", - " 14, 17, 2, 3, 0, 4, 1, 5, 5, 6, 3, 7, 4, 8,\n", - " 8, 9, 6, 10, 7, 11, 11, 12, 9, 13, 10, 14, 14, 15,\n", - " 12, 16, 13, 17, 0, 17, 1, 15, 2, 16]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]),\n", - " size=(36, 18), nnz=108, layout=torch.sparse_coo),\n", - " 'DQ_SZ': tensor(indices=tensor([[ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2,\n", - " 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4,\n", - " 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6,\n", - " 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9,\n", - " 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11,\n", - " 11, 11, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13,\n", - " 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 16, 16,\n", - " 16, 16, 16, 16, 17, 17, 17, 17, 17, 17],\n", - " [ 0, 3, 9, 15, 19, 33, 1, 4, 10, 16, 20, 34, 2, 5,\n", - " 11, 17, 18, 35, 0, 3, 6, 12, 18, 22, 1, 4, 7, 13,\n", - " 19, 23, 2, 5, 8, 14, 20, 21, 3, 6, 9, 15, 21, 25,\n", - " 4, 7, 10, 16, 22, 26, 5, 8, 11, 17, 23, 24, 0, 6,\n", - " 9, 12, 24, 28, 1, 7, 10, 13, 25, 29, 2, 8, 11, 14,\n", - " 26, 27, 3, 9, 12, 15, 27, 31, 4, 10, 13, 16, 28, 32,\n", - " 5, 11, 14, 17, 29, 30, 0, 6, 12, 15, 30, 34, 1, 7,\n", - " 13, 16, 31, 35, 2, 8, 14, 17, 32, 33]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]),\n", - " size=(18, 36), nnz=108, layout=torch.sparse_coo),\n", - " 'DQ_SX': tensor(indices=tensor([[ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2,\n", - " 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4,\n", - " 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6,\n", - " 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9,\n", - " 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11,\n", - " 11, 11, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13,\n", - " 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 16, 16,\n", - " 16, 16, 16, 16, 17, 17, 17, 17, 17, 17],\n", - " [ 2, 3, 18, 21, 27, 33, 0, 4, 19, 22, 28, 34, 1, 5,\n", - " 20, 23, 29, 35, 5, 6, 18, 21, 24, 30, 3, 7, 19, 22,\n", - " 25, 31, 4, 8, 20, 23, 26, 32, 8, 9, 21, 24, 27, 33,\n", - " 6, 10, 22, 25, 28, 34, 7, 11, 23, 26, 29, 35, 11, 12,\n", - " 18, 24, 27, 30, 9, 13, 19, 25, 28, 31, 10, 14, 20, 26,\n", - " 29, 32, 14, 15, 21, 27, 30, 33, 12, 16, 22, 28, 31, 34,\n", - " 13, 17, 23, 29, 32, 35, 0, 17, 18, 24, 30, 33, 1, 15,\n", - " 19, 25, 31, 34, 2, 16, 20, 26, 32, 35]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]),\n", - " size=(18, 36), nnz=108, layout=torch.sparse_coo),\n", - " 'SX_DQ': tensor(indices=tensor([[ 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,\n", - " 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13,\n", - " 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 18, 18, 19, 19,\n", - " 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22,\n", - " 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26,\n", - " 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29,\n", - " 30, 30, 30, 30, 31, 31, 31, 31, 32, 32, 32, 32, 33, 33,\n", - " 33, 33, 34, 34, 34, 34, 35, 35, 35, 35],\n", - " [ 1, 15, 2, 16, 0, 17, 0, 4, 1, 5, 2, 3, 3, 7,\n", - " 4, 8, 5, 6, 6, 10, 7, 11, 8, 9, 9, 13, 10, 14,\n", - " 11, 12, 12, 16, 13, 17, 14, 15, 0, 3, 9, 15, 1, 4,\n", - " 10, 16, 2, 5, 11, 17, 0, 3, 6, 12, 1, 4, 7, 13,\n", - " 2, 5, 8, 14, 3, 6, 9, 15, 4, 7, 10, 16, 5, 8,\n", - " 11, 17, 0, 6, 9, 12, 1, 7, 10, 13, 2, 8, 11, 14,\n", - " 3, 9, 12, 15, 4, 10, 13, 16, 5, 11, 14, 17, 0, 6,\n", - " 12, 15, 1, 7, 13, 16, 2, 8, 14, 17]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]),\n", - " size=(36, 18), nnz=108, layout=torch.sparse_coo)},\n", - " 'deg': {'SZ_DQ': (tensor([6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6.]),\n", - " tensor([4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.,\n", - " 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.])),\n", - " 'DQ_SZ': (tensor([4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.,\n", - " 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.]),\n", - " tensor([6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6.])),\n", - " 'DQ_SX': (tensor([2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.,\n", - " 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.]),\n", - " tensor([6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6.])),\n", - " 'SX_DQ': (tensor([6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6.]),\n", - " tensor([2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.,\n", - " 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.]))}},\n", - " 'xlogic': {'sizes': {'DQ': 36, 'SX': 18, 'LX': 2},\n", - " 'adj': {'DQ_SX': tensor(indices=tensor([[ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2,\n", - " 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4,\n", - " 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6,\n", - " 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9,\n", - " 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11,\n", - " 11, 11, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13,\n", - " 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 16, 16,\n", - " 16, 16, 16, 16, 17, 17, 17, 17, 17, 17],\n", - " [ 2, 3, 18, 21, 27, 33, 0, 4, 19, 22, 28, 34, 1, 5,\n", - " 20, 23, 29, 35, 5, 6, 18, 21, 24, 30, 3, 7, 19, 22,\n", - " 25, 31, 4, 8, 20, 23, 26, 32, 8, 9, 21, 24, 27, 33,\n", - " 6, 10, 22, 25, 28, 34, 7, 11, 23, 26, 29, 35, 11, 12,\n", - " 18, 24, 27, 30, 9, 13, 19, 25, 28, 31, 10, 14, 20, 26,\n", - " 29, 32, 14, 15, 21, 27, 30, 33, 12, 16, 22, 28, 31, 34,\n", - " 13, 17, 23, 29, 32, 35, 0, 17, 18, 24, 30, 33, 1, 15,\n", - " 19, 25, 31, 34, 2, 16, 20, 26, 32, 35]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]),\n", - " size=(18, 36), nnz=108, layout=torch.sparse_coo),\n", - " 'SX_DQ': tensor(indices=tensor([[ 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,\n", - " 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13,\n", - " 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 18, 18, 19, 19,\n", - " 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22,\n", - " 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26,\n", - " 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29,\n", - " 30, 30, 30, 30, 31, 31, 31, 31, 32, 32, 32, 32, 33, 33,\n", - " 33, 33, 34, 34, 34, 34, 35, 35, 35, 35],\n", - " [ 1, 15, 2, 16, 0, 17, 0, 4, 1, 5, 2, 3, 3, 7,\n", - " 4, 8, 5, 6, 6, 10, 7, 11, 8, 9, 9, 13, 10, 14,\n", - " 11, 12, 12, 16, 13, 17, 14, 15, 0, 3, 9, 15, 1, 4,\n", - " 10, 16, 2, 5, 11, 17, 0, 3, 6, 12, 1, 4, 7, 13,\n", - " 2, 5, 8, 14, 3, 6, 9, 15, 4, 7, 10, 16, 5, 8,\n", - " 11, 17, 0, 6, 9, 12, 1, 7, 10, 13, 2, 8, 11, 14,\n", - " 3, 9, 12, 15, 4, 10, 13, 16, 5, 11, 14, 17, 0, 6,\n", - " 12, 15, 1, 7, 13, 16, 2, 8, 14, 17]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]),\n", - " size=(36, 18), nnz=108, layout=torch.sparse_coo),\n", - " 'LX_DQ': tensor(indices=tensor([[ 2, 3, 4, 5, 5, 6, 7, 8, 8, 11, 12, 13, 14, 14,\n", - " 17, 18, 19, 20],\n", - " [ 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1,\n", - " 0, 1, 1, 1]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1.]),\n", - " size=(36, 2), nnz=18, layout=torch.sparse_coo),\n", - " 'DQ_LX': tensor(indices=tensor([[ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,\n", - " 1, 1, 1, 1],\n", - " [ 2, 5, 8, 11, 14, 17, 3, 4, 5, 6, 7, 8, 12, 13,\n", - " 14, 18, 19, 20]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1.]),\n", - " size=(2, 36), nnz=18, layout=torch.sparse_coo)},\n", - " 'deg': {'DQ_SX': (tensor([2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.,\n", - " 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.]),\n", - " tensor([6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6.])),\n", - " 'SX_DQ': (tensor([6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6.]),\n", - " tensor([2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.,\n", - " 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.])),\n", - " 'LX_DQ': (tensor([ 6., 12.]),\n", - " tensor([0., 0., 1., 1., 1., 2., 1., 1., 2., 0., 0., 1., 1., 1., 2., 0., 0., 1.,\n", - " 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])),\n", - " 'DQ_LX': (tensor([0., 0., 1., 1., 1., 2., 1., 1., 2., 0., 0., 1., 1., 1., 2., 0., 0., 1.,\n", - " 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]),\n", - " tensor([ 6., 12.]))}},\n", - " 'zlogic': {'sizes': {'SZ': 18, 'DQ': 36, 'LZ': 2},\n", - " 'adj': {'SZ_DQ': tensor(indices=tensor([[ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3,\n", - " 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6,\n", - " 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 10, 10,\n", - " 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13,\n", - " 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17,\n", - " 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23,\n", - " 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30, 30,\n", - " 31, 31, 32, 32, 33, 33, 34, 34, 35, 35],\n", - " [ 0, 3, 9, 15, 1, 4, 10, 16, 2, 5, 11, 17, 0, 3,\n", - " 6, 12, 1, 4, 7, 13, 2, 5, 8, 14, 3, 6, 9, 15,\n", - " 4, 7, 10, 16, 5, 8, 11, 17, 0, 6, 9, 12, 1, 7,\n", - " 10, 13, 2, 8, 11, 14, 3, 9, 12, 15, 4, 10, 13, 16,\n", - " 5, 11, 14, 17, 0, 6, 12, 15, 1, 7, 13, 16, 2, 8,\n", - " 14, 17, 2, 3, 0, 4, 1, 5, 5, 6, 3, 7, 4, 8,\n", - " 8, 9, 6, 10, 7, 11, 11, 12, 9, 13, 10, 14, 14, 15,\n", - " 12, 16, 13, 17, 0, 17, 1, 15, 2, 16]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]),\n", - " size=(36, 18), nnz=108, layout=torch.sparse_coo),\n", - " 'DQ_SZ': tensor(indices=tensor([[ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2,\n", - " 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4,\n", - " 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6,\n", - " 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9,\n", - " 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11,\n", - " 11, 11, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13,\n", - " 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 16, 16,\n", - " 16, 16, 16, 16, 17, 17, 17, 17, 17, 17],\n", - " [ 0, 3, 9, 15, 19, 33, 1, 4, 10, 16, 20, 34, 2, 5,\n", - " 11, 17, 18, 35, 0, 3, 6, 12, 18, 22, 1, 4, 7, 13,\n", - " 19, 23, 2, 5, 8, 14, 20, 21, 3, 6, 9, 15, 21, 25,\n", - " 4, 7, 10, 16, 22, 26, 5, 8, 11, 17, 23, 24, 0, 6,\n", - " 9, 12, 24, 28, 1, 7, 10, 13, 25, 29, 2, 8, 11, 14,\n", - " 26, 27, 3, 9, 12, 15, 27, 31, 4, 10, 13, 16, 28, 32,\n", - " 5, 11, 14, 17, 29, 30, 0, 6, 12, 15, 30, 34, 1, 7,\n", - " 13, 16, 31, 35, 2, 8, 14, 17, 32, 33]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]),\n", - " size=(18, 36), nnz=108, layout=torch.sparse_coo),\n", - " 'LZ_DQ': tensor(indices=tensor([[ 0, 0, 1, 2, 4, 6, 7, 8, 8, 9, 9, 10, 11, 13,\n", - " 17, 18, 19, 20],\n", - " [ 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0,\n", - " 0, 1, 1, 1]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1.]),\n", - " size=(36, 2), nnz=18, layout=torch.sparse_coo),\n", - " 'DQ_LZ': tensor(indices=tensor([[ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,\n", - " 1, 1, 1, 1],\n", - " [ 0, 4, 8, 9, 13, 17, 0, 1, 2, 6, 7, 8, 9, 10,\n", - " 11, 18, 19, 20]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1.]),\n", - " size=(2, 36), nnz=18, layout=torch.sparse_coo)},\n", - " 'deg': {'SZ_DQ': (tensor([6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6.]),\n", - " tensor([4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.,\n", - " 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.])),\n", - " 'DQ_SZ': (tensor([4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.,\n", - " 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.]),\n", - " tensor([6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6.])),\n", - " 'LZ_DQ': (tensor([ 6., 12.]),\n", - " tensor([2., 1., 1., 0., 1., 0., 1., 1., 2., 2., 1., 1., 0., 1., 0., 0., 0., 1.,\n", - " 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])),\n", - " 'DQ_LZ': (tensor([2., 1., 1., 0., 1., 0., 1., 1., 2., 2., 1., 1., 0., 1., 0., 0., 0., 1.,\n", - " 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]),\n", - " tensor([ 6., 12.]))}}}" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "views_info = [\n", - " {\"name\":\"decode\",\n", - " \"partite_classes\":[\"SZ\",\"DQ\",\"SX\"],\n", - " \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"DQ_SX\",\"SX_DQ\"], \n", - " },\n", - " {\"name\":\"xlogic\",\n", - " \"partite_classes\":[\"DQ\",\"SX\",\"LX\"],\n", - " \"relations\":[\"DQ_SX\",\"SX_DQ\",\"LX_DQ\",\"DQ_LX\"],\n", - " },\n", - " {\"name\":\"zlogic\",\n", - " \"partite_classes\":[\"SZ\",\"DQ\",\"LZ\"],\n", - " \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"LZ_DQ\",\"DQ_LZ\"],\n", - " },\n", + " {\n", + " \"name\": \"decode\",\n", + " \"partite_classes\": [\"SZ\", \"DQ\", \"SX\"],\n", + " \"relations\": [\"SZ_DQ\", \"DQ_SZ\", \"DQ_SX\", \"SX_DQ\"],\n", + " },\n", + " {\n", + " \"name\": \"xlogic\",\n", + " \"partite_classes\": [\"DQ\", \"SX\", \"LX\"],\n", + " \"relations\": [\"DQ_SX\", \"SX_DQ\", \"LX_DQ\", \"DQ_LX\"],\n", + " },\n", + " {\n", + " \"name\": \"zlogic\",\n", + " \"partite_classes\": [\"SZ\", \"DQ\", \"LZ\"],\n", + " \"relations\": [\"SZ_DQ\", \"DQ_SZ\", \"LZ_DQ\", \"DQ_LZ\"],\n", + " },\n", "]\n", "# views_info = [\n", "# {\"name\":\"decode\",\n", "# \"partite_classes\":[\"SZ\",\"DQ\",\"SX\"],\n", - "# \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"DQ_SX\",\"SX_DQ\"], \n", + "# \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"DQ_SX\",\"SX_DQ\"],\n", "# },\n", "# ]\n", "# views_info = [\n", "# {\"name\":\"decode\",\n", "# \"partite_classes\":[\"SZ\",\"DQ\",\"SX\"],\n", - "# \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"DQ_SX\",\"SX_DQ\"], \n", + "# \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"DQ_SX\",\"SX_DQ\"],\n", "# },\n", "# {\"name\":\"xlogic\",\n", "# \"partite_classes\":[\"DQ\",\"SX\",\"LX\"],\n", @@ -575,13 +283,13 @@ "# views_info = [\n", "# {\"name\":\"5-partite\",\n", "# \"partite_classes\":[\"SZ\",\"DQ\",\"SX\",\"LX\",\"LZ\"],\n", - "# \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"DQ_SX\",\"SX_DQ\",\"LX_DQ\",\"DQ_LX\",\"LZ_DQ\",\"DQ_LZ\"], \n", + "# \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"DQ_SX\",\"SX_DQ\",\"LX_DQ\",\"DQ_LX\",\"LZ_DQ\",\"DQ_LZ\"],\n", "# },\n", "# ]\n", "# views_info = [\n", "# {\"name\":\"decode\",\n", "# \"partite_classes\":[\"SZ\",\"DQ\",\"SX\"],\n", - "# \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"DQ_SX\",\"SX_DQ\"], \n", + "# \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"DQ_SX\",\"SX_DQ\"],\n", "# },\n", "# {\"name\":\"xlogic\",\n", "# \"partite_classes\":[\"DQ\",\"SX\",\"LZ\"],\n", @@ -593,11 +301,11 @@ "# },\n", "# ]\n", "# views_info = [\n", - "# # Z-error chain (SZ -> DQ) \n", + "# # Z-error chain (SZ -> DQ)\n", "# {\"name\":\"decode_z\", \"partite_classes\":[\"SZ\",\"DQ\"],\n", "# \"relations\":[\"SZ_DQ\"], },\n", "\n", - "# # X-error chain (DQ -> SX) \n", + "# # X-error chain (DQ -> SX)\n", "# {\"name\":\"decode_x\", \"partite_classes\":[\"DQ\",\"SX\"],\n", "# \"relations\":[\"DQ_SX\"], },\n", "\n", @@ -609,34 +317,35 @@ "# ]\n", "\n", "\n", - "\n", "# An Example of the Encoding\n", - "class E():\n", - " def __init__(self,views_info ):\n", - " self.encoder = CSSEncoder(views_info,mode ='relations')\n", + "class E:\n", + " def __init__(self, views_info):\n", + " self.encoder = CSSEncoder(views_info, mode=\"relations\")\n", "\n", - " def encode_single(self,x):\n", + " def encode_single(self, x):\n", " return self.encoder.encode(x)\n", - " def encode(self,x):\n", + "\n", + " def encode(self, x):\n", " # x: B views\n", " return [self.encode_single(i) for i in x]\n", "\n", + "\n", "e = E(views_info)\n", "e.encode_single(total_dataset.X[0])" ] }, { "cell_type": "code", - "execution_count": 6, - "id": "6a6351c4", + "execution_count": null, + "id": "5", "metadata": {}, "outputs": [], "source": [ - "train_dataset, test_dataset = train_test_split(total_dataset,\n", - " test_size=0.2,\n", - " shuffle=True,\n", - " random_state=seed)\n", - "from torch import nn, optim\n", + "train_dataset, test_dataset = train_test_split(\n", + " total_dataset, test_size=0.2, shuffle=True, random_state=seed\n", + ")\n", + "from torch import optim\n", + "\n", "\n", "def train_gpytorch_model(model, train_x, train_y, training_iter=50, lr=0.002):\n", " model.train()\n", @@ -647,42 +356,49 @@ " history = []\n", " for i in range(training_iter):\n", " optimizer.zero_grad()\n", - " output = model(train_x) # MultivariateNormal over latent f\n", + " output = model(train_x) # MultivariateNormal over latent f\n", " loss = -mll(output, train_y.squeeze(-1))\n", " loss.backward()\n", " optimizer.step()\n", " history.append(loss.item())\n", " plt.plot(history)\n", " return loss.item()\n", + "\n", + "\n", "def train_gpytorch_model_seperate(model, train_x, train_y, training_iter=50, lr=0.002):\n", " model.train()\n", " model.likelihood.train()\n", " try:\n", - " nn_parameters = list(model.mean_module.parameters()) + list(model.embed.parameters())\n", + " nn_parameters = list(model.mean_module.parameters()) + list(\n", + " model.embed.parameters()\n", + " )\n", " kernel_parameters = list(model.covar_module.parameters())\n", " likelihood_parameters = list(model.likelihood.parameters())\n", "\n", - "\n", - " optimizer = torch.optim.AdamW([\n", - " {'params': nn_parameters, 'lr': 0.001}, \n", - " {'params': kernel_parameters, 'lr': 0.0001}, \n", - " {'params': likelihood_parameters, 'lr': 0.02} \n", - " ])\n", + " optimizer = torch.optim.AdamW(\n", + " [\n", + " {\"params\": nn_parameters, \"lr\": 0.001},\n", + " {\"params\": kernel_parameters, \"lr\": 0.0001},\n", + " {\"params\": likelihood_parameters, \"lr\": 0.02},\n", + " ]\n", + " )\n", " except:\n", - " nn_parameters = list(model.embed.parameters())\n", + " nn_parameters = list(model.embed.parameters())\n", " kernel_parameters = list(model.covar_module.parameters())\n", " likelihood_parameters = list(model.likelihood.parameters())\n", "\n", - "\n", - " optimizer = torch.optim.AdamW([\n", - " {'params': nn_parameters, 'lr': 0.005}, \n", - " {'params': kernel_parameters, 'lr': 0.001}, \n", - " {'params': likelihood_parameters, 'lr': 0.02} ])\n", + " optimizer = torch.optim.AdamW(\n", + " [\n", + " {\"params\": nn_parameters, \"lr\": 0.005},\n", + " {\"params\": kernel_parameters, \"lr\": 0.001},\n", + " {\"params\": likelihood_parameters, \"lr\": 0.02},\n", + " ]\n", + " )\n", " mll = ExactMarginalLogLikelihood(model.likelihood, model)\n", " history = []\n", " for i in range(training_iter):\n", " optimizer.zero_grad()\n", - " output = model(train_x) # MultivariateNormal over latent f\n", + " output = model(train_x) # MultivariateNormal over latent f\n", " loss = -mll(output, train_y.squeeze(-1))\n", " loss.backward()\n", " optimizer.step()\n", @@ -693,7 +409,7 @@ }, { "cell_type": "markdown", - "id": "9ac6c7c0", + "id": "6", "metadata": {}, "source": [ "# Results\n", @@ -806,7 +522,7 @@ }, { "cell_type": "markdown", - "id": "cd46b583", + "id": "7", "metadata": {}, "source": [ "# Test our embedding on nn-regressor" @@ -814,274 +530,18 @@ }, { "cell_type": "code", - "execution_count": 10, - "id": "afe8a4e6", + "execution_count": null, + "id": "8", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Epoch 1 | Loss: 0.318859\n", - "Epoch 2 | Loss: 0.236694\n", - "Epoch 3 | Loss: 0.217672\n", - "Epoch 4 | Loss: 0.210474\n", - "Epoch 5 | Loss: 0.199423\n", - "Epoch 6 | Loss: 0.214220\n", - "Epoch 7 | Loss: 0.199688\n", - "Epoch 8 | Loss: 0.203249\n", - "Epoch 9 | Loss: 0.187467\n", - "Epoch 10 | Loss: 0.190376\n", - "Epoch 11 | Loss: 0.194188\n", - "Epoch 12 | Loss: 0.183740\n", - "Epoch 13 | Loss: 0.186241\n", - "Epoch 14 | Loss: 0.184350\n", - "Epoch 15 | Loss: 0.174657\n", - "Epoch 16 | Loss: 0.183481\n", - "Epoch 17 | Loss: 0.176370\n", - "Epoch 18 | Loss: 0.172502\n", - "Epoch 19 | Loss: 0.160232\n", - "Epoch 20 | Loss: 0.163823\n", - "Epoch 21 | Loss: 0.154833\n", - "Epoch 22 | Loss: 0.162528\n", - "Epoch 23 | Loss: 0.149254\n", - "Epoch 24 | Loss: 0.143769\n", - "Epoch 25 | Loss: 0.172772\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAAHqCAYAAAAZLi26AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAADmFklEQVR4nOzdd3hUVfrA8e+09IQkpFGSEHrvgohSlC4q9rIuYN+1obhr2137T3bXsrg2dF0Fe127Ik1EBUF6kU4gQEgjvU9m7u+PkzuTnplkJjOB9/M8PHdy55YzuZNh3nvOeV+DpmkaQgghhBBCCCGE8DijrxsghBBCCCGEEEKcqiToFkIIIYQQQgghvESCbiGEEEIIIYQQwksk6BZCCCGEEEIIIbxEgm4hhBBCCCGEEMJLJOgWQgghhBBCCCG8RIJuIYQQQgghhBDCSyToFkIIIYQQQgghvESCbiGEEEIIIYQQwksk6BZCCOFT3bp1Y+7cuY6fV69ejcFgYPXq1R47h8Fg4JFHHvHY8YR7jh49SlBQED///LOvm+Ixrr5PH3nkEQwGAzk5OW3TMD+waNEikpKSqKio8HVThBDCL0jQLYQQp7HFixdjMBgc/4KCgujduze33347mZmZvm6eW7755pvTJrDWAz5X/vmDxx57jNGjRzN27Ng2P/eTTz7JZ5991ubnbSt1/4Yb+9etW7c2a9PcuXOprKzklVdeabNzCiGEPzP7ugFCCCF877HHHiMlJYXy8nJ++uknXn75Zb755ht27txJSEhIm7Zl3LhxlJWVERAQ4NZ+33zzDS+++GKDgXdZWRlm86nzX16/fv146623aq174IEHCAsL4y9/+YuPWtWw7OxslixZwpIlS3xy/ieffJLLLruMWbNm+eT83jZu3Lh674Ubb7yRUaNGcfPNNzvWhYWFtVmbgoKCmDNnDs8++yx33HGH39z8EUIIXzl1voEIIYRosenTpzNy5EhAfWHv2LEjzz77LJ9//jlXX311g/uUlJQQGhrq8bYYjUaCgoI8ekxPH8/X4uPjufbaa2ut+/vf/05MTEy99TXZ7XYqKyvb9Pfx9ttvYzabueCCC9rsnJqmUV5eTnBwcJud01e6d+9O9+7da637wx/+QPfu3Zt8L1RVVWG3292+ueWqK664gn/+8598//33nHvuuV45hxBCtBcyvFwIIUQ9+pfk1NRUQA0XDQsL4+DBg8yYMYPw8HB+97vfASqQW7hwIQMGDCAoKIj4+HhuueUW8vLyah1T0zSeeOIJunbtSkhICBMnTmTXrl31zt3YXNn169czY8YMoqKiCA0NZfDgwTz33HOO9r344osADQ6tbmhO95YtW5g+fToRERGEhYVx3nnn8csvv9TaRh+6+/PPPzN//nxiY2MJDQ3l4osvJjs7u8nf4dNPP43BYODIkSP1nnvggQcICAhw/I7279/PpZdeSkJCAkFBQXTt2pWrrrqKgoKCJs/RHIPBwO23384777zDgAEDCAwMZOnSpY3+jg8fPozBYGDx4sW11u/Zs4fLLruM6OhogoKCGDlyJF988YVLbfjss88YPXp0vZ5WV15zVVUVjz/+OD169CAwMJBu3brx4IMP1psr3K1bN2bOnMl3333HyJEjCQ4O5pVXXsFgMFBSUsKSJUsc74ma+QOOHz/O9ddfT3x8PIGBgQwYMIDXX3+93ms4duwYs2bNIjQ0lLi4OO6++2635yvn5ORwxRVXEBERQceOHZk3bx7l5eWO58ePH8+QIUMa3LdPnz5MnTrVrfPVpF/Xp59+moULFzp+n7/99pvjPX748OFa+zT1dzht2jQ6dOhASEgI48ePb3Cu/ogRI4iOjubzzz9vcbuFEOJUIT3dQggh6jl48CAAHTt2dKyrqqpi6tSpnH322Tz99NOOYee33HILixcv5rrrruPOO+8kNTWVF154gS1btvDzzz9jsVgAeOihh3jiiSeYMWMGM2bMYPPmzUyZMoXKyspm27N8+XJmzpxJp06dmDdvHgkJCezevZuvvvqKefPmccstt5Cens7y5cvrDbVtyK5duzjnnHOIiIjg3nvvxWKx8MorrzBhwgR++OEHRo8eXWv7O+64g6ioKB5++GEOHz7MwoULuf322/nggw8aPccVV1zBvffey4cffsif//znWs99+OGHTJkyhaioKCorK5k6dSoVFRXccccdJCQkcPz4cb766ivy8/Pp0KFDs6+nKatWreLDDz/k9ttvJyYmhm7dupGfn+/y/rt27WLs2LF06dKF+++/n9DQUD788ENmzZrFJ598wsUXX9zovlarlV9//ZU//vGPtda7+ppvvPFGlixZwmWXXcY999zD+vXrWbBgAbt37+bTTz+tdcy9e/dy9dVXc8stt3DTTTfRp08f3nrrrXpDrXv06AFAZmYmZ555puPGRGxsLN9++y033HADhYWF3HXXXYCamnDeeeeRlpbGnXfeSefOnXnrrbdYtWqVy79DUO+Hbt26sWDBAn755Rf+/e9/k5eXx5tvvgnA73//e2666SZ27tzJwIEDHfv9+uuv7Nu3j7/+9a9una8hb7zxBuXl5dx8880EBgYSHR3t1v6rVq1i+vTpjBgxgocffhij0cgbb7zBueeey48//sioUaNqbT98+PBTKnmeEEK0mCaEEOK09cYbb2iAtmLFCi07O1s7evSo9v7772sdO3bUgoODtWPHjmmapmlz5szRAO3++++vtf+PP/6oAdo777xTa/3SpUtrrc/KytICAgK0888/X7Pb7Y7tHnzwQQ3Q5syZ41j3/fffa4D2/fffa5qmaVVVVVpKSoqWnJys5eXl1TpPzWPddtttWmP/rQHaww8/7Ph51qxZWkBAgHbw4EHHuvT0dC08PFwbN25cvd/PpEmTap3r7rvv1kwmk5afn9/g+XRjxozRRowYUWvdhg0bNEB78803NU3TtC1btmiA9tFHHzV5rOYMGDBAGz9+fK11gGY0GrVdu3bVWl/3d6xLTU3VAO2NN95wrDvvvPO0QYMGaeXl5Y51drtdO+uss7RevXo12aYDBw5ogPb888/XWu/Ka966dasGaDfeeGOt9X/60580QFu1apVjXXJysgZoS5curXec0NDQWu8v3Q033KB16tRJy8nJqbX+qquu0jp06KCVlpZqmqZpCxcu1ADtww8/dGxTUlKi9ezZs8HfYV0PP/ywBmgXXnhhrfW33nqrBmjbtm3TNE3T8vPztaCgIO2+++6rtd2dd96phYaGasXFxU2ep6a6r1m/rhEREVpWVlatbfX3eGpqaq31dd8jdrtd69WrlzZ16tRafwulpaVaSkqKNnny5HrtuPnmm7Xg4GCX2y2EEKcqGV4uhBCCSZMmERsbS2JiIldddRVhYWF8+umndOnSpdZ2dXssP/roIzp06MDkyZPJyclx/BsxYgRhYWF8//33AKxYsYLKysp6SZX03sSmbNmyhdTUVO666y4iIyNrPdeSBE02m41ly5Yxa9asWnNhO3XqxDXXXMNPP/1EYWFhrX1uvvnmWuc655xzsNlsDQ4dr+nKK69k06ZNjpEDAB988AGBgYFcdNFFAI5e3e+++47S0lK3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lqNknqtSU1O58MILmTZtGuvWrePtt9/mmmuuqVebe9iwYQwcOJCPPvqIfv36MXz4cLfO464BAwZw5pln8sADD5Cbm0t0dDTvv/9+vZsiRqOR1157jenTpzNgwACuu+46unTpwvHjx/n++++JiIjgyy+/dGy/adMmcnNzHQkDhRDitOajrOlCCCH8gF4u6Ndff21yuzlz5mihoaGNPv/qq69qI0aM0IKDg7Xw8HBt0KBB2r333qulp6c7trHZbNqjjz6qderUSQsODtYmTJig7dy5U0tOTm6yZJjup59+0iZPnqyFh4droaGh2uDBg2uVoqqqqtLuuOMOLTY2VjMYDLXKh1GnZJimadrmzZu1qVOnamFhYVpISIg2ceJEbe3atS79fhprY2P+85//aIAWHh6ulZWV1Xru0KFD2vXXX6/16NFDCwoK0qKjo7WJEydqK1ascOnYusZKht12220Nbp+dna1deumlWkhIiBYVFaXdcsst2s6dO+uVDNM0TTt48KA2e/ZsLSEhQbNYLFqXLl20mTNnah9//HGz7crMzNTMZrP21ltvuf2arVar9uijj2opKSmaxWLREhMTtQceeKBW+TJNUyXDzj///AbPv2fPHm3cuHFacHBwvfJ0mZmZ2m233aYlJiZqFotFS0hI0M477zzt1VdfrXWMI0eOaBdeeKEWEhKixcTEaPPmzXOUxXO1ZNhvv/2mXXbZZVp4eLgWFRWl3X777fXeC7p//vOfGqA9+eSTTR67MY2VDHvqqaca3P7gwYPapEmTtMDAQC0+Pl578MEHteXLlzf4+rZs2aJdcsklWseOHbXAwEAtOTlZu+KKK7SVK1fW2u6+++7TkpKSapUXE0KI05VB05rJAiOEEEII0Qo33HAD+/btc2TlFk177rnnuPvuuzl8+HC9zPntQUVFBd26deP+++9n3rx5vm6OEEL4nATdQgghhPCqtLQ0evfuzcqVKxk7dqyvm+PXNE1jyJAhdOzY0VHnvr1ZtGgRTz75JPv37ycwMNDXzRFCCJ+ToFsIIYQQwsdKSkr44osv+P777/nPf/7D559/zoUXXujrZgkhhPAACbqFEEIIIXzs8OHDpKSkEBkZya233sr//d//+bpJQgghPESCbiGEEEIIIYQQwkukTrcQQgghhBBCCOElEnQLIYQQQgghhBBeYvZ1A/yd3W4nPT2d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YjZ7723xv//9D4vF4tK2EigLIYQQQgjhn/ZlFvH7/64ns7CCLpHB/PvqofSMCyciyFx/2LirDulJ1E6doeUgQfdpbdq0abzxxhtUVFTwzTffcNttt2GxWHjggQdqbVdZWUlAQIBHzhkdHe2R4wghhBBCCCF8Y9ORPK5f/CsFZVZ6x4fx5vWjSegQ1LqDVhTB8U3q8SkWdEv28tNYYGAgCQkJJCcn88c//pFJkybxxRdfMHfuXGbNmsX//d//0blzZ/r06QPA0aNHueKKK4iMjCQ6OpqLLrqIw4cPO45ns9mYP38+kZGRdOzYkXvvvRdN02qdc8KECdx1112OnysqKrjvvvtITEwkMDCQnj178t///pfDhw8zceJEAKKiojAYDMydOxcAu93OggULSElJITg4mCFDhvDxxx/XOs8333xD7969CQ4OZuLEibXaKYQQQgghhGiZH/Zlc+1r6ykoszIsKZIPbxnT+oAb4Mg60GwQmQxRya0/nh+Rnm5P0jSwlrb9ee12de5WCg4O5uTJkwCsXLmSiIgIli9fDoDVamXq1KmMGTOGH3/8EbPZzBNPPMG0adPYvn07AQEBPPPMMyxevJjXX3+dfv368cwzz/Dpp59y7rnnNnrO2bNns27dOv79738zZMgQUlNTycnJITExkU8++YRLL72UvXv3EhERQXBwMAALFizg7bffZtGiRfTq1Ys1a9Zw7bXXEhsby/jx4zl69CiXXHIJt912GzfffDMbN27knnvuafXvRwghhBBCiNPZF9vSuefDrVhtGuN7x/LytcMJCfBQSJl6ag4tBwm6PctaCk92bvPTGgFu2w10aGbLhmmaxsqVK/nuu++44447yM7OJjQ0lNdee80xrPztt9/Gbrfz2muvOeZovPHGG0RGRrJ69WqmTJnCwoULeeCBB7jkkksAWLRoEd99912j5923bx8ffvghy5cvZ9KkSQB0797d8bw+FD0uLs4xp7uiooInn3ySFStWMGbMGMc+P/30E6+88grjx4/n5ZdfpkePHjzzzDMA9OnThx07dvCPf/yjRb8fIYQQQgghTndvrTvMQ1/sQtPggiGdeebyIQSYPThw+hSsz62ToPs09tVXXxEWFobVasVut3PNNdfwyCOPcNtttzFo0KBa87i3bdvGgQMHCA8Pr3WM8vJyDh48SEFBASdOnGD06NGO58xm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r9G4cWPjpptuMjIyMpyvq208w/fff2+0a9fO8PPzM1JSUoz333+/wuv6rOBeM2bMMIBKf+d6D7lXbm6u8fDDDxsJCQmGv7+/0axZM+P//u//jOLiYuc+nvz+sRiGYbili11ERERERETkAqc53SIiIiIiIiIuoqRbRERERERExEWUdIuIiIiIiIi4iJJuERERERERERdR0i0iIiIiIiLiIkq6RURERERERFxESbeIiIiIiIiIiyjpFhEREREREXERJd0iIiIezGKx8M0337g7DJ599lk6derk8uscPnyYqKgoduzY4fJr1ZTExETeeOMNAEpKSkhMTGT58uXuDUpERDyGkm4REanXDh48yH333UdCQgJ+fn7ExMSQmprKokWL3B1ajdixYwcWi4XVq1e7O5Qqeemllxg6dCiJiYkuvc6JiXJN8vX15c9//jNPPPFEjZ9bRETqJm93ByAiIuJOw4YNo6SkhClTptCsWTP279/P7NmzOXz4sLtDq3cKCgr48MMPmTFjhsuuUVJSgq+vr8vODzB8+HAef/xxNmzYQNu2bV16LRER8Xzq6RYRkXorOzubBQsWMGHCBC699FKaNm1Kjx49ePLJJ7nmmmuc+73++uu0b9+eoKAg4uPjuf/++zl27Jjz9cmTJxMWFsYPP/xAq1atCAwM5IYbbqCgoIApU6aQmJhIw4YNeeihh7Db7c7jEhMTeeGFF7jlllsICgqicePGvP3222eMeffu3fzhD38gLCyM8PBwhg4dek5DsefNm4fFYmH27Nl069aNwMBAevfuTXp6eoX9/va3vxEdHU1ISAijR4+mqKjolHN98MEHtG7dGn9/f1JSUvjXv/7lfG3UqFF06NCB4uJiwEx2O3fuzB133HHa2KZPn46fnx8XXXSRc9vRo0cZPnw4kZGRBAQE0KJFCyZNmuR8fd26dVx22WUEBATQqFEj7rnnngptM3LkSK699lpeeukl4uLiaNWqFf3792fnzp08+uijWCwWLBaLc/+FCxdy8cUXExAQQHx8PA899BD5+fnO1w8cOMCQIUMICAggKSmJzz777JSfo2HDhvTp04dp06ad9mcVEZH6Q0m3iIjUW8HBwQQHB/PNN984k8PKWK1WJk6cyIYNG5gyZQpz5sxhzJgxFfYpKChg4sSJTJs2jZ9//pl58+Zx3XXXMX36dKZPn84nn3zCe++9x5dfflnhuL///e907NiRVatWMXbsWB5++GFmzpxZaRw2m43U1FRCQkJYsGABixYtIjg4mEGDBlFSUnJOP/v//d//8dprr7F8+XK8vb0ZNWqU87XPP/+cZ599lpdffpnly5cTGxtbIaEG+Oyzz3j66ad56aWXSEtL4+WXX2bcuHFMmTIFgIkTJ5Kfn8/YsWOd18vOzuaf//znaWNasGABXbt2rbBt3LhxbNy4kZ9++om0tDTeeecdIiIiAMjPzyc1NZWGDRuybNkyvvjiC2bNmsWDDz5Y4RyzZ88mPT2dmTNn8sMPP/DVV1/RpEkTnn/+efbt28e+ffsA2Lp1K4MGDWLYsGGsXbuW//73vyxcuLDC+UaOHMnu3buZO3cuX375Jf/61784cODAKT9Ljx49WLBgwVnbQURE6gFDRESkHvvyyy+Nhg0bGv7+/kbv3r2NJ5980lizZs0Zj/niiy+MRo0aOZ9PmjTJAIyMjAzntnvvvdcIDAw08vLynNtSU1ONe++91/m8adOmxqBBgyqc+6abbjIGDx7sfA4YX3/9tWEYhvHJJ58YrVq1MhwOh/P14uJiIyAgwJgxY0alsW7fvt0AjFWrVhmGYRhz5841AGPWrFnOfX788UcDMAoLCw3DMIxevXoZ999/f4Xz9OzZ0+jYsaPzeXJysjF16tQK+7zwwgtGr169nM9/++03w8fHxxg3bpzh7e1tLFiwoNIYyw0dOtQYNWpUhW1Dhgwx7rzzzkr3f//9942GDRsax44dq/CzWK1WIysryzAMwxgxYoQRHR1tFBcXVzi2adOmxj/+8Y8K20aPHm3cc889FbYtWLDAsFqtRmFhoZGenm4AxtKlS52vp6WlGcAp53rzzTeNxMTEM/68IiJSP6inW0RE6rVhw4axd+9evvvuOwYNGsS8efPo0qULkydPdu4za9YsBgwYQOPGjQkJCeH222/n8OHDFBQUOPcJDAwkOTnZ+Tw6OprExESCg4MrbDu5V7RXr16nPE9LS6s01jVr1pCRkUFISIizlz48PJyioiK2bt16Tj93hw4dnI9jY2MBnLGlpaXRs2fP08aZn5/P1q1bGT16tDOO4OBgXnzxxQpx9OrViz//+c+88MILPP744/Tt2/eMMRUWFuLv719h23333ce0adPo1KkTY8aM4bfffnO+lpaWRseOHQkKCnJu69OnDw6Ho8Jw+fbt21dpHveaNWuYPHlyhZ8pNTUVh8PB9u3bSUtLw9vbu0JvfEpKCmFhYaecKyAgoMK/DxERqb9USE1EROo9f39/Lr/8ci6//HLGjRvHXXfdxTPPPMPIkSPZsWMHV199Nffddx8vvfQS4eHhLFy4kNGjR1NSUkJgYCAAPj4+Fc5psVgq3eZwOKod57Fjx+jatWul84gjIyPP6VwnxlY+p7mqsZXPmf73v/99SnLu5eXlfOxwOFi0aBFeXl5kZGSc9bwREREcPXq0wrbBgwezc+dOpk+fzsyZMxkwYAAPPPAAr776apViBSok5Wdy7Ngx7r33Xh566KFTXktISGDz5s1VvuaRI0fOuU1EROTCpJ5uERGRk7Rp08ZZPGvFihU4HA5ee+01LrroIlq2bMnevXtr7Fq///77Kc9bt25d6b5dunRhy5YtREVF0bx58wq3Bg0a1FhMrVu3ZsmSJaeNMzo6mri4OLZt23ZKHElJSc79/v73v7Np0ybmz5/Pzz//XKEAWmU6d+7Mxo0bT9keGRnJiBEj+PTTT3njjTd4//33nXGuWbOmQqGzRYsWYbVaadWq1Rmv5evrW6GoHZi/340bN57yMzVv3hxfX19SUlIoLS1lxYoVzmPS09PJzs4+5fzr16+nc+fOZ4xBRETqByXdIiJSbx0+fJjLLruMTz/9lLVr17J9+3a++OILXnnlFYYOHQpA8+bNsdlsvPXWW2zbto1PPvmEd999t8ZiWLRoEa+88gqbN2/m7bff5osvvuDhhx+udN/hw4cTERHB0KFDWbBgAdu3b2fevHk89NBD7Nmzp8Zievjhh/noo4+YNGkSmzdv5plnnmHDhg0V9nnuuecYP348EydOZPPmzaxbt45Jkybx+uuvA7Bq1SqefvppPvjgA/r06cPrr7/Oww8/zLZt20573dTUVDZs2FCht/vpp5/m22+/JSMjgw0bNvDDDz84v5QYPnw4/v7+jBgxgvXr1zN37lz+9Kc/cfvttxMdHX3GnzExMZFff/2VzMxMDh06BMATTzzBb7/9xoMPPsjq1avZsmUL3377rbOQWqtWrRg0aBD33nsvS5YsYcWKFdx1110EBASccv4FCxZwxRVXVOG3LSIiFzol3SIiUm8FBwfTs2dP/vGPf3DJJZfQrl07xo0bx9133+2sst2xY0def/11JkyYQLt27fjss88YP358jcXw+OOPs3z5cjp37syLL77I66+/TmpqaqX7BgYG8uuvv5KQkMD1119P69atnct5hYaG1lhMN910E+PGjWPMmDF07dqVnTt3ct9991XY56677uKDDz5g0qRJtG/fnn79+jF58mSSkpIoKiritttuY+TIkQwZMgSAe+65h0svvZTbb7/9lB7mcu3bt6dLly58/vnnzm2+vr48+eSTdOjQgUsuuQQvLy/nUlyBgYHMmDGDI0eO0L17d2644QYGDBhwxgrp5Z5//nl27NhBcnKycxh4hw4dmD9/Pps3b+biiy+mc+fOPP3008TFxTmPmzRpEnFxcfTr14/rr7+ee+65h6ioqArnXrx4MTk5Odxwww1V+G2LiMiFzmIYhuHuIEREROqjxMREHnnkER555BF3h+IxfvzxR/7yl7+wfv16rNa62Tdw00030bFjR5566il3hyIiIh5AhdRERETEY1x11VVs2bKFzMxM4uPj3R3OOSspKaF9+/Y8+uij7g5FREQ8hJJuERER8Sh1ueff19eXv/71r+4OQ0REPIiGl4uIiIiIiIi4SN2cLCUiIiIiIiJSByjpFhEREREREXERJd0iIiIiIiIiLqKkW0RERERERMRFlHSLiIiIiIiIuIiSbhEREREREREXUdItIiIiIiIi4iJKukVERERERERcREm3iIiIiIiIiIv8P18Fu8GZpET0AAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Test MSE: 0.411219\n", - "R² Score: 0.6324\n", - "Epoch 26 | Loss: 0.154059\n", - "Epoch 27 | Loss: 0.143227\n", - "Epoch 28 | Loss: 0.149588\n", - "Epoch 29 | Loss: 0.142508\n", - "Epoch 30 | Loss: 0.129562\n", - "Epoch 31 | Loss: 0.133575\n", - "Epoch 32 | Loss: 0.134543\n", - "Epoch 33 | Loss: 0.130371\n", - "Epoch 34 | Loss: 0.120946\n", - "Epoch 35 | Loss: 0.122995\n", - "Epoch 36 | Loss: 0.123962\n", - "Epoch 37 | Loss: 0.126102\n", - "Epoch 38 | Loss: 0.127146\n", - "Epoch 39 | Loss: 0.121188\n", - "Epoch 40 | Loss: 0.123526\n", - "Epoch 41 | Loss: 0.116458\n", - "Epoch 42 | Loss: 0.119750\n", - "Epoch 43 | Loss: 0.122837\n", - "Epoch 44 | Loss: 0.119757\n", - "Epoch 45 | Loss: 0.113774\n", - "Epoch 46 | Loss: 0.114809\n", - "Epoch 47 | Loss: 0.109918\n", - "Epoch 48 | Loss: 0.105860\n", - "Epoch 49 | Loss: 0.106406\n", - "Epoch 50 | Loss: 0.105855\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAAHqCAYAAAAZLi26AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdd3hb1fnA8a+W5T3jle0MsncYSYAQCAmEFcoeDVCgtGwopUBLgcKPlBZoKFACpew9A4UAGRACJUD23suZHvHesnR/fxxdybJlW9OS4/fzPH4kXd17daQryXrve857DJqmaQghhBBCCCGEECLkjJFugBBCCCGEEEIIcbSSoFsIIYQQQgghhAgTCbqFEEIIIYQQQogwkaBbCCGEEEIIIYQIEwm6hRBCCCGEEEKIMJGgWwghhBBCCCGECBMJuoUQQgghhBBCiDCRoFsIIYQQQgghhAgTCbqFEEIIIYQQQogwkaBbCCFERPXt25err77adXvJkiUYDAaWLFkSsscwGAw8+OCDIduf8M++ffuIjY3lf//7X6SbEjK+vk8ffPBBDAYDxcXFHdOwKDB37lx69+5NfX19pJsihBBRQYJuIYTowl555RUMBoPrLzY2lmOOOYabb76ZgoKCSDfPL/Pnz+8ygbUe8PnyFw3+8pe/cPzxxzNp0qQOf+xHH32UefPmdfjjdpTmn+HW/vr27dthbbr66qtpaGjg+eef77DHFEKIaGaOdAOEEEJE3l/+8hfy8vKoq6vj+++/57nnnmP+/Pls2LCB+Pj4Dm3LySefTG1tLTExMX5tN3/+fJ599lmvgXdtbS1m89HzL2/IkCG8/vrrHsvuvfdeEhMT+eMf/xihVnlXVFTEq6++yquvvhqRx3/00Ue58MILmTlzZkQeP9xOPvnkFu+F6667juOOO45f//rXrmWJiYkd1qbY2FiuuuoqnnzySW655ZaoOfkjhBCRcvT8AhFCCBGwM888k/HjxwPqB3tGRgZPPvkkn3zyCZdddpnXbaqrq0lISAh5W4xGI7GxsSHdZ6j3F2nZ2dlceeWVHsv++te/0q1btxbLm3I4HDQ0NHTo6/HGG29gNps555xzOuwxNU2jrq6OuLi4DnvMSOnXrx/9+vXzWPab3/yGfv36tfleaGxsxOFw+H1yy1cXX3wxf/vb3/jmm2849dRTw/IYQgjRWUj3ciGEEC3oP5J3794NqO6iiYmJ7Ny5kxkzZpCUlMQVV1wBqEBuzpw5DBs2jNjYWLKzs7nhhhsoLS312KemaTzyyCP07NmT+Ph4pkyZwsaNG1s8dmtjZX/66SdmzJhBWloaCQkJjBw5kqeeesrVvmeffRbAa9dqb2O6V69ezZlnnklycjKJiYmcdtpp/Pjjjx7r6F13//e//3HnnXeSmZlJQkIC559/PkVFRW2+ho8//jgGg4G9e/e2uO/ee+8lJibG9Rpt376dCy64gJycHGJjY+nZsyeXXnop5eXlbT5GewwGAzfffDNvvvkmw4YNw2q18uWXX7b6Gu/ZsweDwcArr7zisXzLli1ceOGFpKenExsby/jx4/n00099asO8efM4/vjjW2RafXnOjY2NPPzww/Tv3x+r1Urfvn257777WowV7tu3L2effTZfffUV48ePJy4ujueffx6DwUB1dTWvvvqq6z3RtH7AgQMH+NWvfkV2djZWq5Vhw4bx0ksvtXgO+/fvZ+bMmSQkJJCVlcUdd9zh93jl4uJiLr74YpKTk8nIyOC2226jrq7Odf/kyZMZNWqU120HDRrE9OnT/Xq8pvTj+vjjjzNnzhzX67lp0ybXe3zPnj0e27T1OTzjjDNISUkhPj6eyZMnex2rP27cONLT0/nkk08CbrcQQhwtJNMthBCihZ07dwKQkZHhWtbY2Mj06dM58cQTefzxx13dzm+44QZeeeUVrrnmGm699VZ2797NM888w+rVq/nf//6HxWIB4M9//jOPPPIIM2bMYMaMGaxatYpp06bR0NDQbnsWLlzI2WefTW5uLrfddhs5OTls3ryZzz77jNtuu40bbriBgwcPsnDhwhZdbb3ZuHEjJ510EsnJydx9991YLBaef/55TjnlFL799luOP/54j/VvueUW0tLSeOCBB9izZw9z5szh5ptv5t133231MS6++GLuvvtu3nvvPX7/+9973Pfee+8xbdo00tLSaGhoYPr06dTX13PLLbeQk5PDgQMH+OyzzygrKyMlJaXd59OWr7/+mvfee4+bb76Zbt260bdvX8rKynzefuPGjUyaNIkePXpwzz33kJCQwHvvvcfMmTP58MMPOf/881vd1mazsXz5cn772996LPf1OV933XW8+uqrXHjhhfzud7/jp59+Yvbs2WzevJmPP/7YY59bt27lsssu44YbbuD6669n0KBBvP766y26Wvfv3x+AgoICTjjhBNeJiczMTL744guuvfZaKioquP322wE1NOG0004jPz+fW2+9le7du/P666/z9ddf+/wagno/9O3bl9mzZ/Pjjz/yz3/+k9LSUl577TUAfvnLX3L99dezYcMGhg8f7tpu+fLlbNu2jT/96U9+PZ43L7/8MnV1dfz617/GarWSnp7u1/Zff/01Z555JuPGjeOBBx7AaDTy8ssvc+qpp/Ldd99x3HHHeaw/duzYo6p4nhBCBEwTQgjRZb388ssaoC1atEgrKirS9u3bp73zzjtaRkaGFhcXp+3fv1/TNE276qqrNEC75557PLb/7rvvNEB78803PZZ/+eWXHssLCwu1mJgY7ayzztIcDodrvfvuu08DtKuuusq17JtvvtEA7ZtvvtE0TdMaGxu1vLw8rU+fPlppaanH4zTd10033aS19m8N0B544AHX7ZkzZ2oxMTHazp07XcsOHjyoJSUlaSeffHKL12fq1Kkej3XHHXdoJpNJKysr8/p4ugkTJmjjxo3zWPbzzz9rgPbaa69pmqZpq1ev1gDt/fffb3Nf7Rk2bJg2efJkj2WAZjQatY0bN3osb/4a63bv3q0B2ssvv+xadtppp2kjRozQ6urqXMscDoc2ceJEbeDAgW22aceOHRqgPf300x7LfXnOa9as0QDtuuuu81h+1113aYD29ddfu5b16dNHA7Qvv/yyxX4SEhI83l+6a6+9VsvNzdWKi4s9ll966aVaSkqKVlNTo2maps2ZM0cDtPfee8+1TnV1tTZgwACvr2FzDzzwgAZo5557rsfyG2+8UQO0tWvXapqmaWVlZVpsbKz2hz/8wWO9W2+9VUtISNCqqqrafJymmj9n/bgmJydrhYWFHuvq7/Hdu3d7LG/+HnE4HNrAgQO16dOne3wWampqtLy8PO30009v0Y5f//rXWlxcnM/tFkKIo5V0LxdCCMHUqVPJzMykV69eXHrppSQmJvLxxx/To0cPj/WaZyzff/99UlJSOP300ykuLnb9jRs3jsTERL755hsAFi1aRENDQ4uiSno2sS2rV69m9+7d3H777aSmpnrcF0iBJrvdzoIFC5g5c6bHWNjc3Fwuv/xyvv/+eyoqKjy2+fWvf+3xWCeddBJ2u91r1/GmLrnkElauXOnqOQDw7rvvYrVaOe+88wBc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cuVKcnNzef31111T5vlq9+7dnHvuuZxxxhksW7aMN954g8svv7zF3Nxjxoxh+PDhvP/++wwZMoSxY8f69Tj+GjZsGCeccAL33nsvJSUlpKen884777Q4KWI0GnnxxRc588wzGTZsGNdccw09evTgwIEDfPPNNyQnJ/Pf//7Xtf7KlSspKSlxFQwUQoguLUJV04UQQkQBfbqg5cuXt7neVVddpSUkJLR6/wsvvKCNGzdOi4uL05KSkrQRI0Zod999t3bw4EHXOna7XXvooYe03NxcLS4uTjvllFO0DRs2aH369GlzyjDd999/r51++ulaUlKSlpCQoI0cOdJjKqrGxkbtlltu0TIzMzWDweAxfRjNpgzTNE1btWqVNn36dC0xMVGLj4/XpkyZov3www8+vT6ttbE1//73vzVAS0pK0mpraz3u27Vrl/arX/1K69+/vxYbG6ulp6drU6ZM0RYtWuTTvnWtTRl20003eV2/qKhIu+CCC7T4+HgtLS1Nu+GGG7QNGza0mDJM0zRt586d2qxZs7ScnBzNYrFoPXr00M4++2ztgw8+aLddBQUFmtls1l5//XW/n7PNZtMeeughLS8vT7NYLFqvXr20e++912P6Mk1TU4adddZZXh9/y5Yt2sknn6zFxcW1mJ6uoKBAu+mmm7RevXppFotFy8nJ0U477TTthRde8NjH3r17tXPPPVeLj4/XunXrpt12222uafF8nTJs06ZN2oUXXqglJSVpaWlp2s0339zivaD729/+pgHao48+2ua+W9PalGF///vfva6/c+dOberUqZrVatWys7O1++67T1u4cKHX57d69WrtF7/4hZaRkaFZrVatT58+2sUXX6wtXrzYY70//OEPWu/evT2mFxNCiK7KoGntVIERQgghhAjCtddey7Zt21xVuUXbnnrqKe644w727NnTonJ+Z1BfX0/fvn255557uO222yLdHCGEiDgJuoUQQggRVvn5+RxzzDEsXryYSZMmRbo5UU3TNEaNGkVGRoZrnvvOZu7cuTz66KNs374dq9Ua6eYIIUTESdAthBBCCBFh1dXVfPrpp3zzzTf8+9//5pNPPuHcc8+NdLOEEEKEgATdQgghhBARtmfPHvLy8khNTeXGG2/k//7v/yLdJCGEECEiQbcQQgghhBBCCBEmMk+3EEIIIYQQQggRJhJ0CyGEEEIIIYQQYWKOdAOincPh4ODBgyQlJWEwGCLdHCGEEEIIIYQQUUDTNCorK+nevTtGY+v5bAm623Hw4EF69eoV6WYIIYQQQgghhIhC+/bto2fPnq3eL0F3O5KSkgD1QiYnJ0e4Nd7ZbDYWLFjAtGnTsFgskW6O8EKOUXST4xPd5PhEPzlG0U2OT3ST4xP95BhFt0gen4qKCnr16uWKGVsjQXc79C7lycnJUR10x8fHk5ycLF8EUUqOUXST4xPd5PhEPzlG0U2OT3ST4xP95BhFt2g4Pu0NQ5ZCakIIIYQQQgghRJhI0C2EEEIIIYQQQoSJBN1CCCGEEEIIIUSYyJjuELHb7dhstog8ts1mw2w2U1dXh91uj0gbOjOLxYLJZIp0M4QQQgghhBBHIQm6g6RpGocPH6asrCyibcjJyWHfvn0yl3iAUlNTycnJkddPCCGEEEIIEVISdAdJD7izsrKIj4+PSNDmcDioqqoiMTGxzUnZRUuaplFTU0NhYSEAubm5EW6REEIIIYQQ4mgiQXcQ7Ha7K+DOyMiIWDscDgcNDQ3ExsZK0B2AuLg4AAoLC8nKypKu5kIIIYQQQoiQkQgtCPoY7vj4+Ai3RARLP4aRGpcvhBBCCCGEODpJ0B0CMg6485NjKIQQQgghhAgHCbqFEEIIIYQQQogwkaBbCCGEEEIIIYQIEwm6uxiDwdDm34MPPhjpJgohhBBCCCHEUaPTBN2zZ8/m2GOPJSkpiaysLGbOnMnWrVvb3e79999n8ODBxMbGMmLECObPn98BrY1ehw4dcv3NmTOH5ORkj2V33XWXa11N02hsbIxga4UQQgghhBCic+s0Qfe3337LTTfdxI8//sjChQux2WxMmzaN6urqVrf54YcfuOyyy7j22mtZvXo1M2fOZObMmWzYsKEDWx5dcnJyXH8pKSkYDAbX7S1btpCUlMQXX3zBuHHjsFqtfP/991x99dXMnDnTYz+33347p5xyiuu2w+Fg9uzZ5OXlERcXx6hRo/jggw869skJIYQQQgghRJTpNPN0f/nllx63X3nlFbKysli5ciUnn3yy122eeuopzjjjDH7/+98D8PDDD7Nw4UKeeeYZ5s6dG/I2appGrc0e8v22x+FwoGlayPZ3zz338Pjjj9OvXz/S0tJ82mb27Nm88cYbzJ07l4EDB7J06VKuvPJKMjMzmTx5csjaJoQQQgghhDjKaRp8/ySMvARSeka6NUHrNEF3c+Xl5QCkp6e3us6yZcu48847PZZNnz6defPmtbpNfX099fX1rtsVFRWAmr+5+RzONpsNTdNwOBw4HA5qGhoZ/uBCf59KSCy78wSSnW3xlb5u88sHH3yQ0047zbWepmmu59l0mb5NfX09jz76KAsWLGDChAkA9O3bl++++465c+dy0kknBffkOoB+4sJms2EymUK+f/29I/OARyc5PtFNjk/0k2MU3eT4RDc5PtFPjlHHM2z/CvPiv6D98DSNt6wFS3yr60by+Pj6mJ0y6HY4HNx+++1MmjSJ4cOHt7re4cOHyc7O9liWnZ3N4cOHW91m9uzZPPTQQy2WL1iwgPh4z4NtNpvJycmhqqqKhoYGahs6PsvdVGVlpV/r19XVoWma68RCTU0NAIMGDXItA/Vmamxs9FjW0NDgWrZ582ZqamqYPn26x/4bGhoYOXKkx3bRqqGhgdraWpYuXRrWcewLF0bmpIzwjRyf6CbHJ/rJMYpucnyimxyf6CfHqINoDk7Z+mdSgM2JE9m+cIlPm0Xi+OjxU3s6ZdB90003sWHDBr7//vuQ7/vee+/1yI5XVFTQq1cvpk2bRnJysse6dXV17Nu3j8TERGJjY0nSNDY8eHrI29QeTdNorKshKSkJg8Hg83axsbEYDAbX89JPKuTk5Hg8V6vVislk8lhmMBgwm80ey/773//So0cPj8ewWq0tXrdoVFdXR1xcHCeffDKxsbEh37/NZmPhwoWcfvrpWCyWkO9fBEeOT3ST4xP95BhFNzk+0U2OT/STY9SxDJs+xrwmnwotjrsqL+GNU6eRFNt62BrJ4+NrcrHTBd0333wzn332GUuXLqVnz7b79+fk5FBQUOCxrKCggJycnFa3sVqtWK3WFsstFkuLg2i32zEYDBiNRoxGVZMuMQxdk9vjcDioqDe42uIrfV1vl033k5WVxcaNGz2WrV27FovFgtFoZPjw4VitVvbv38+UKVNC8ZQ6nNFoxGAweD3OoRTu/YvgyPGJbnJ8op8co+gmxye6yfGJfnKMOoC9kcYlfwXg341nceyQ/qQnxfm0aSSOj6+P12mql2uaxs0338zHH3/M119/TV5eXrvbTJgwgcWLF3ssW7hwoWvcsfDNqaeeyooVK3jttdfYvn07DzzwgEcF+KSkJO666y7uuOMOXn31VXbu3MmqVat4+umnefXVVyPYciGEEEIIIURn4VjzFubSnRzRkvgh82LunTEk0k0KiU6T6b7pppt46623+OSTT0hKSnKNy05JSSEuTp39mDVrFj169GD27NkA3HbbbUyePJknnniCs846i3feeYcVK1bwwgsvROx5dEbTp0/n/vvv5+6776auro5f/epXzJo1i/Xr17vWefjhh8nMzGT27Nns2rWL1NRUxo4dy3333RfBlgshhBBCCCE6hcZ6qhb8H8nAi9pM/nbFJGItHd+LOBw6TdD93HPPAXjMDQ3w8ssvc/XVVwOQn5/v0QV64sSJvPXWW/zpT3/ivvvuY+DAgcybN6/N4mtdydVXX+167UC9tq1NPfbQQw95LTCnMxgM3Hbbbdx2222hbqYQQgghhBDiKLd3wbP0qT/MIS2dY86+nf6ZiZFuUsh0mqDbl3molyxZ0mLZRRddxEUXXRSGFgkhhBBCCCGECFZpaSlJP88B4Pvca7jouAGRbVCIdZox3UIIIYQQQgghji6apvH1a4+QTjkHDDnMmPX7SDcp5CToFkIIIYQQQggROHsjOOwBbfr2t+uYWvIWAI7J95AQ71u18s5Egm4hhBBCCCGEEP6rLYWvH4HH+sILk6HikF+bbzhQTunif5BiqKE0oT+9Tp4VnnZGWKcZ0y2EEEIIIYQQIgrUlsGP/4Ifn4P6CrXs8Hp4aTrM+gTS25/euaq+kfvf/IY3jPMBSD3rQTAeHdXKm5NMtxBCCCGEEEKI9tWVw5K/wpyR8O1jKuDOGgbnPgNpeVC2F146Awo3t7urP8/bwFkV75BgqKcxZzSGIed0wBOIDMl0CyGEEEIIIYRoXV0F/DQXlj2jAm+AzCFwyj0w5FwwGmHg6fD6+VC4CV4+E678EHqM87q7D1fuZ9nqdcy2LgLAPPXPYDB01LPpcBJ0CyGEEEIIIYRoqb5SBds/PAN1ZWpZ5mCY/AcYOlMF27qkHLj6c3jzIjiwAl49Fy57G/JO9tjlzqIq7v9kA38yf4TVYIM+J0L/UzvsKUWCBN1CCCGEEEII0RU0VMOX98D+Fb6tX3HAndnudowKtoed3/rY6/h0Nab7ncth97fwxoVw8asw6EwA6mx2bn5rNZm2A1xi/VZtc9r9R3WWGyToFmF29dVXU1ZWxrx58wA45ZRTGD16NHPmzOnQdixZsoQpU6ZQWlpKampqhz62EEIIIYQQEVdVBG9dDAdX+bddxkAVbA//RbuFzmx2BwU1Rg5Nep6etTeSe/hr7G9fwUuZd/NR4yQOlNZQUdfIc3EfYdIcMHAa9D4hiCfVOUjQ3UVdffXVvPrqqwBYLBZ69+7NrFmzuO+++zCbw/e2+Oijj7BYLD6tK4GyEEIIIYQQIXBkJ7xxAZTuhrh0OOsJiM9ofztLnBqX3UawXVLdwJ/mrWfV3jIKK+twaGq5iWv4m8XGBabvuLbwr+xtvJrN9tMZYz3IGdr/1Eqn/ikETy76SdDdhZ1xxhm8/PLL1NfXM3/+fG666SYsFgv33nuvx3oNDQ3ExMSE5DHT09NDsh8hhBBCCCGED/avVBnummJI7QNXfgTdBoRk1wfKapn1n5/YWVTtWmYxGchNiSM3JZYfUv5C37LnGHf4PR6xvMytk7JIL12HYbumxoTnjgpJO6KdTBnWhVmtVnJycujTpw+//e1vmTp1Kp9++ilXX301M2fO5P/+7//o3r07gwYNAmDfvn1cfPHFpKamkp6eznnnnceePXtc+7Pb7dx5552kpqaSkZHB3XffjaZpHo95yimncPvtt7tu19fX84c//IFevXphtVoZMGAA//nPf9izZw9TpkwBIC0tDYPBwNVXXw2Aw+Fg9uzZ5OXlERcXx6hRo/jggw88Hmf+/Pkcc8wxxMXFMWXKFI92CiGEEEII0SVs+wpePVsF3Lmj4LpFIQu4dxRWcuFzP7CzqJrclFjeuu54fv7jaWx9+EyW3j2Fd2+YwBOXjmXcDS/AyXcDkPXzY5i3fwEGI0z5Y0ja0RlIpjuUNA1sNR3/uA6HeuwgxcXFceTIEQAWL15McnIyCxcuBMBmszF9+nQmTJjAd999h9ls5pFHHuGMM85g3bp1xMTE8MQTT/DKK6/w0ksvMWTIEJ544gk+/vhjTj219WqEs2bNYtmyZfzzn/9k1KhR7N69m+LiYnr16sWHH37IBRdcwNatW0lOTiYuLg6A2bNn88YbbzB37lwGDhzI0qVLufLKK8nMzGTy5Mns27ePX/ziF9x00038+te/ZsWKFfzud78L+vURQgghhBCi01j1Gvz3dtDs0P80VdDMmhSSXa/OL+WaV5ZTVmOjf2YCr117PD1S47yvbDDAqX+E2BRY4Ay0R10GmceEpC2dgQTdoWSrgUe7d/jDGgFu2gykBLS9pmksXryYr776iltuuYWioiISEhJ48cUXXd3K33jjDRwOBy+++CIGZ3XBl19+mdTUVJYsWcK0adOYM2cO9957L7/4xS8AmDt3Ll999VWrj7tt2zbee+89Fi5cyNSpUwHo16+f6369K3pWVpZrTHd9fT2PPvooixYtYsKECa5tvv/+e55//nkmT57Mc889R//+/XniiScAGDRoEOvXr+exxx4L6PURQgghhBCi09A0+PYxWDJb3R51OZz7TzD5VlepPUu3FfGbN1ZS02BnVK9UXr76WNITfBiKOvFmSM6F7Qth6oMhaUtnIUF3F/bZZ5+RmJiIzWbD4XBw+eWX8+CDD3LTTTcxYsQIj3Hca9euZceOHSQleZ4dq6urY+fOnZSXl3Po0CGOP/54131ms5nx48e36GKuW7NmDSaTicmTJ/vc5h07dlBTU8Ppp5/usbyhoYExY8YAsHnzZo92AK4AXQghhBBCiKOWvRE+v0NluQFOuksVKwvRlFyfrj3I795bg82ucdLAbsy9chwJVj9CyuEXqL8uRoLuULLEw30HO/xhHQ4H1Db6vd2UKVN47rnniImJoXv37h5VyxMSEjzWraqqYty4cbz55pst9pOZmel/o8HVXdwfVVVVAHz++ef06NHD4z6r1RpQO4QQQgghhOj0Gqrh/Wtg+1dqzPSMx+HYa0O2+9eW7eGBTzeiaXD2yFyevHg0MWYpEeYLCbpDyWCAmIT21ws1hwPqKvzeLCEhgQEDfCukMHbsWN59912ysrJITk72uk5ubi4//fQTJ598MgCNjY2sXLmSsWPHel1/xIgROBwOvv32W1f38qb0TLvdbnctGzp0KFarlfz8/FYz5EOGDOHTTz/1WPbjjz+2/ySFEEIIIYQIt8Z6WPEydB8DvY9vf31flO+H92bBgZVgjoULX4LBZ4Vk15qm8Y9F2/nn4u0AzJrQhwfPGYbRGJrseVcgpyaET6644gq6devGeeedx3fffcfu3btZsmQJt956K/v37wfgtttu469//Svz5s1jy5Yt3HjjjZSVlbW6z759+3LVVVfxq1/9innz5rn2+d577wHQp08fDAYDn332GUVFRVRVVZGUlMRdd93FHXfcwauvvsrOnTtZtWoVTz/9tGve8d/85jds376d3//+92zdupW33nqLV155JdwvkRBCCCGEEO3b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trYWH374Ia677jqsWLEi3MsiAAcOHMBdd92FxYsXw2w2h3s5dBw52wMAw4cPx7hx45Cbm4v3338fMTExYVwZyZxOJ8aMGYM///nPAICRI0di27ZteP3113HdddeFeXXk7a233sL555+P7OzscC+FXN5//328++67WLRoEYYMGYJNmzbh7rvvRnZ2dkS8flheHsHS0tJgMBhO6Jx49OhRZGVlhWlV1B75ePBYhd8dd9yBzz//HMuWLUOfPn3cl2dlZcFms6GmpqbN7XmMQstkMqF///4YPXo05s2bhxEjRuCll17i8dGADRs2oLy8HKNGjUJUVBSioqKwYsUKvPzyy4iKikJmZiaPkYYkJSVh4MCBKCkp4etHI3r16oWTTjqpzWWDBw92bwPgewVt2L9/P5YsWYKbbrrJfRlfQ+F33333Yc6cObjqqqswbNgwXHvttZg9ezbmzZsHQPuvHwbdEcxkMmH06NFYunSp+zKn04mlS5di/PjxYVwZHS8/Px9ZWVltjlVdXR3WrFnDYxUikiThjjvuwP/+9z989913yM/Pb3P96NGjYTQa2xyj4uJilJaW8hiFkdPpREtLC4+PBkyaNAlbt27Fpk2b3P/GjBmDmTNnus/zGGlHQ0MDdu/ejV69evH1oxETJ048YVTlzp07kZubC4DvFbRi/vz5yMjIwAUXXOC+jK+h8LNardDr24auBoMBTqcTQAS8fsLdyY2C895770nR0dHSggULpF9++UW65ZZbpKSkJKmsrCzcS+tx6uvrpY0bN0obN26UAEgvvPCCtHHjRmn//v2SJEnSU089JSUlJUmffPKJtGXLFmn69OlSfn6+1NTUFOaV9wy33XablJiYKC1fvlw6cuSI+5/VanXf5re//a3Ut29f6bvvvpPWr18vjR8/Xho/fnwYV92zzJkzR1qxYoW0d+9eacuWLdKcOXMknU4nffvtt5Ik8fhokXf3ckniMQqne++9V1q+fLm0d+9eafXq1dLkyZOltLQ0qby8XJIkHhstWLt2rRQVFSU9+eST0q5du6R3331Xio2Nld555x33bfheIbwcDofUt29f6YEHHjjhOr6Gwuu6666TevfuLX3++efS3r17pY8++khKS0uT7r//fvdttPz6YdDdDbzyyitS3759JZPJJI0dO1b66aefwr2kHmnZsmUSgBP+XXfddZIkiVEGc+fOlTIzM6Xo6Ghp0qRJUnFxcXgX3YO0d2wASPPnz3ffpqmpSfrd734nJScnS7GxsdIll1wiHTlyJHyL7mFuuOEGKTc3VzKZTFJ6ero0adIkd8AtSTw+WnR80M1jFD5XXnml1KtXL8lkMkm9e/eWrrzySqmkpMR9PY+NNnz22WfS0KFDpejoaKmwsFD6+9//3uZ6vlcIr2+++UYC0O7PnK+h8Kqrq5PuuusuqW/fvpLZbJYKCgqkP/7xj1JLS4v7Nlp+/egkSZLCkmInIiIiIiIi6ua4p5uIiIiIiIhIJQy6iYiIiIiIiFTCoJuIiIiIiIhIJQy6iYiIiIiIiFTCoJuIiIiIiIhIJQy6iYiIiIiIiFTCoJuIiIiIiIhIJQy6iYiIiIiIiFTCoJuIiEjDdDodPv7443AvA4888ghOPvlk1Z+nsrISGRkZ2Ldvn+rPpZS8vDy8+OKLAACbzYa8vDysX78+vIsiIiLNYNBNREQ92rFjx3Dbbbehb9++iI6ORlZWFqZMmYLVq1eHe2mK2LdvH3Q6HTZt2hTupfjkySefxPTp05GXl6fq83gHykoymUz4v//7PzzwwAOKPzYREUWmqHAvgIiIKJxmzJgBm82GhQsXoqCgAEePHsXSpUtRWVkZ7qX1OFarFW+99Ra++eYb1Z7DZrPBZDKp9vgAMHPmTNx7773Yvn07hgwZoupzERGR9jHTTUREPVZNTQ1WrlyJp59+GmeffTZyc3MxduxYPPjgg/jVr37lvt0LL7yAYcOGIS4uDjk5Ofjd736HhoYG9/ULFixAUlISPv/8cwwaNAixsbG47LLLYLVasXDhQuTl5SE5ORl33nknHA6H+355eXl4/PHHcfXVVyMuLg69e/fGq6++2umaDxw4gCuuuAJJSUlISUnB9OnT/SrFXr58OXQ6HZYuXYoxY8YgNjYWEyZMQHFxcZvbPfXUU8jMzER8fDxuvPFGNDc3n/BYb775JgYPHgyz2YzCwkL87W9/c193ww03YPjw4WhpaQEggt2RI0fiN7/5TYdr+/LLLxEdHY1TTz3VfVl1dTVmzpyJ9PR0xMTEYMCAAZg/f777+q1bt+Kcc85BTEwMUlNTccstt7Q5NrNmzcLFF1+MJ598EtnZ2Rg0aBDOOuss7N+/H7NnJgrDrgAACepJREFUz4ZOp4NOp3PfftWqVTj99NMRExODnJwc3HnnnWhsbHRfX15ejosuuggxMTHIz8/Hu+++e8L3kZycjIkTJ+K9997r8HslIqKeg0E3ERH1WBaLBRaLBR9//LE7OGyPXq/Hyy+/jO3bt2PhwoX47rvvcP/997e5jdVqxcsvv4z33nsPX3/9NZYvX45LLrkEX375Jb788ku8/fbbeOONN/Dhhx+2ud+zzz6LESNGYOPGjZgzZw7uuusuLF68uN112O12TJkyBfHx8Vi5ciVWr14Ni8WCqVOnwmaz+fW9//GPf8Tzzz+P9evXIyoqCjfccIP7uvfffx+PPPII/vznP2P9+vXo1atXm4AaAN5991089NBDePLJJ1FUVIQ///nPmDt3LhYuXAgAePnll9HY2Ig5c+a4n6+mpgZ//etfO1zTypUrMXr06DaXzZ07F7/88gu++uorFBUV4bXXXkNaWhoAoLGxEVOmTEFycjLWrVuHDz74AEuWLMEdd9zR5jGWLl2K4uJiLF68GJ9//jk++ugj9OnTB4899hiOHDmCI0eOAAB2796NqVOnYsaMGdiyZQv+85//YNWqVW0eb9asWThw4ACWLVuGDz/8EH/7299QXl5+wvcyduxYrFy5ssvjQEREPYBERETUg3344YdScnKyZDabpQkTJkgPPvigtHnz5k7v88EHH0ipqanur+fPny8BkEpKStyX3XrrrVJsbKxUX1/vvmzKlCnSrbfe6v46NzdXmjp1apvHvvLKK6Xzzz/f/TUA6X//+58kSZL09ttvS4MGDZKcTqf7+paWFikmJkb65ptv2l3r3r17JQDSxo0bJUmSpGXLlkkApCVLlrhv88UXX0gApKamJkmSJGn8+PHS7373uzaPM27cOGnEiBHur/v16yctWrSozW0ef/xxafz48e6vf/jhB8loNEpz586VoqKipJUrV7a7Rtn06dOlG264oc1lF110kXT99de3e/u///3vUnJystTQ0NDme9Hr9VJZWZkkSZJ03XXXSZmZmVJLS0ub++bm5kp/+ctf2lx24403Srfcckuby1auXCnp9XqpqalJKi4ulgBIa9eudV9fVFQkATjhsV566SUpLy+v0++XiIh6Bma6iYioR5sxYwYOHz6MTz/9FFOnTsXy5csxatQoLFiwwH2bJUuWYNKkSejduzfi4+Nx7bXXorKyElar1X2b2NhY9OvXz/11ZmYm8vLyYLFY2lx2fFZ0/PjxJ3xdVFTU7lo3b96MkpISxMfHu7P0KSkpaG5uxu7du/36vocPH+4+36tXLwBwr62oqAjjxo3rcJ2NjY3YvXs3brzxRvc6LBYLnnjiiTbrGD9+PP7v//4Pjz/+OO69916cdtppna6pqakJZrO5zWW33XYb3nvvPZx88sm4//778cMPP7ivKyoqwogRIxAXF+e+bOLEiXA6nW3K5YcNG+bTPu7NmzdjwYIFbb6nKVOmwOl0Yu/evSgqKkJUVFSbbHxhYSGSkpJOeKyYmJg2vx9ERNRzsZEaERH1eGazGeeeey7OPfdczJ07FzfddBMefvhhzJo1C/v27cOFF16I2267DU8++SRSUlKwatUq3HjjjbDZbIiNjQUAGI3GNo+p0+navczpdAa8zoaGBowePbrdfcTp6el+PZb32uQ9zb6uTd4z/Y9//OOE4NxgMLjPO51OrF69GgaDASUlJV0+blpaGqqrq9tcdv7552P//v348ssvsXjxYkyaNAm33347nnvuOZ/WCqBNUN6ZhoYG3HrrrbjzzjtPuK5v377YuXOnz89ZVVXl9zEhIqLuiZluIiKi45x00knu5lkbNmyA0+nE888/j1NPPRUDBw7E4cOHFXuun3766YSvBw8e3O5tR40ahV27diEjIwP9+/dv8y8xMVGxNQ0ePBhr1qzpcJ2ZmZnIzs7Gnj17TlhHfn6++3bPPvssduzYgRUrVuDrr79u0wCtPSNHjsQvv/xywuXp6em47rrr8M477+DFF1/E3//+d/c6N2/e3KbR2erVq6HX6zFo0KBOn8tkMrVpageIn+8vv/xywvfUv39/mEwmFBYWorW1FRs2bHDfp7i4GDU1NSc8/rZt2zBy5MhO10BERD0Dg24iIuqxKisrcc455+Cdd97Bli1bsHfvXnzwwQd45plnMH36dABA//79Ybfb8corr2DPnj14++238frrryu2htWrV+OZZ57Bzp078eqrr+KDDz7AXXfd1e5tZ86cibS0NEyfPh0rV67E3r17sXz5ctx55504ePCgYmu666678M9//hPz58/Hzp078fDDD2P79u1tbvPoo49i3rx5ePnll7Fz505s3boV8+fPxwsvvAAA2LhxIx566CG8+eabmDhxIl544QXcdddd2LNnT4fPO2XKFGzfvr1Ntvuhhx7CJ598gpKSEmzfvh2ff/65+0OJmTNnwmw247rrrsO2bduwbNky/P73v8e1116LzMzMTr/HvLw8fP/99zh06BAqKioAAA888AB++OEH3HHHHdi0aRN27dqFTz75xN1IbdCgQZg6dSpuvfVWrFmzBhs2bMBNN92EmJiYEx5/5cqVOO+883z4aRMRUXfHoJuIiHosi8WCcePG4S9/+QvOOOMMDB06FHPnzsXNN9/s7rI9YsQIvPDCC3j66acxdOhQvPvuu5g3b55ia7j33nuxfv16jBw5Ek888QReeOEFTJkypd3bxsbG4vvvv0ffvn1x6aWXYvDgwe5xXgkJCYqt6corr8TcuXNx//33Y/To0di/fz9uu+22Nre56aab8Oabb2L+/PkYNmwYzjzzTCxYsAD5+flobm7Gr3/9a8yaNQsXXXQRAOCWW27B2WefjWuvvfaEDLNs2LBhGDVqFN5//333ZSaTCQ8++CCGDx+OM844AwaDwT2KKzY2Ft988w2qqqpwyimn4LLLLsOkSZM67ZAue+yxx7Bv3z7069fPXQY+fPhwrFixAjt37sTpp5+OkSNH4qGHHkJ2drb7fvPnz0d2djbOPPNMXHrppbjllluQkZHR5rF//PFH1NbW4rLLLvPhp01ERN2dTpIkKdyLICIi6ony8vJw99134+677w73UjTjiy++wH333Ydt27ZBr4/M3MCVV16JESNG4A9/+EO4l0JERBrARmpERESkGRdccAF27dqFQ4cOIScnJ9zL8ZvNZsOwYcMwe/bscC+FiIg0gkE3ERERaUokZ/5NJhP+9Kc/hXsZRESkISwvJyIiIiIiIlJJZG6WIiIiIiIiIooADLqJiIiIiIiIVMKgm4iIiIiIiEglDLqJiIiIiIiIVMKgm4iIiIiIiEglDLqJiIiIiIiIVMKgm4iIiIiIiEglDLqJiIiIiIiIVMKgm4iIiIiIiEgl/w/HqNPC2kTZwgAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Test MSE: 0.411573\n", - "R² Score: 0.6321\n", - "Epoch 51 | Loss: 0.106542\n", - "Epoch 52 | Loss: 0.103152\n", - "Epoch 53 | Loss: 0.103517\n", - "Epoch 54 | Loss: 0.092629\n", - "Epoch 55 | Loss: 0.124167\n", - "Epoch 56 | Loss: 0.108883\n", - "Epoch 57 | Loss: 0.107365\n", - "Epoch 58 | Loss: 0.104630\n", - "Epoch 59 | Loss: 0.090972\n", - "Epoch 60 | Loss: 0.092814\n", - "Epoch 61 | Loss: 0.085010\n", - "Epoch 62 | Loss: 0.077792\n", - "Epoch 63 | Loss: 0.078269\n", - "Epoch 64 | Loss: 0.083287\n", - "Epoch 65 | Loss: 0.071687\n", - "Epoch 66 | Loss: 0.082848\n", - "Epoch 67 | Loss: 0.074119\n", - "Epoch 68 | Loss: 0.098934\n", - "Epoch 69 | Loss: 0.062886\n", - "Epoch 70 | Loss: 0.061841\n", - "Epoch 71 | Loss: 0.065269\n", - "Epoch 72 | Loss: 0.060058\n", - "Epoch 73 | Loss: 0.058145\n", - "Epoch 74 | Loss: 0.058967\n", - "Epoch 75 | Loss: 0.075962\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAAHqCAYAAAAZLi26AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAAD/aElEQVR4nOzdd3hb1fnA8a8ky/Le21nOJHsSSBghQBIIK2WX0oRRoGUFwiihpUBpSWmBhgI/AqWUvSl7mgzChiwgk+ztvbfG/f1xdCXLU5IlS7bfz/P4uRp3HOnKtt57znlfg6ZpGkIIIYQQQgghhAg4Y6gbIIQQQgghhBBC9FYSdAshhBBCCCGEEEEiQbcQQgghhBBCCBEkEnQLIYQQQgghhBBBIkG3EEIIIYQQQggRJBJ0CyGEEEIIIYQQQSJBtxBCCCGEEEIIESQSdAshhBBCCCGEEEEiQbcQQgghhBBCCBEkEnQLIYQIqUGDBnHJJZe47q9atQqDwcCqVasCdgyDwcBdd90VsP0J3+zfv5+oqCi+/PLLUDclYLz9nN51110YDAZKSkq6p2FhYNmyZQwYMIDGxsZQN0UIIcKCBN1CCNGHPf300xgMBtdPVFQUw4cP59prr6WwsDDUzfPJBx980GcCaz3g8+YnHPz5z3/mqKOO4phjjun2Y99777289dZb3X7c7tLyd7i9n0GDBnVbmy655BKampp4/PHHu+2YQggRziJC3QAhhBCh9+c//5m8vDwaGhr44osveOyxx/jggw/YuHEjMTEx3dqW448/nvr6eiIjI33a7oMPPuDRRx9tM/Cur68nIqL3/MsbOXIkzz33nMdjixcvJi4ujj/84Q8halXbiouLeeaZZ3jmmWdCcvx7772Xc889l3nz5oXk+MF2/PHHt/os/OY3v2Hq1KlceeWVrsfi4uK6rU1RUVEsWLCABx98kOuuuy5sLv4IIUSo9J5vIEIIIfx26qmnMmXKFEB9YU9NTeXBBx/k7bff5pe//GWb29TW1hIbGxvwthiNRqKiogK6z0DvL9QyMzO5+OKLPR7729/+RlpaWqvHm3M4HDQ1NXXr+/H8888TERHBGWec0W3H1DSNhoYGoqOju+2YoTJ48GAGDx7s8dhvf/tbBg8e3OFnwWaz4XA4fL645a3zzz+fv//976xcuZITTzwxKMcQQoieQoaXCyGEaEX/krx7925ADReNi4tj586dzJ07l/j4eH71q18BKpBbunQpo0ePJioqiszMTK666irKy8s99qlpGn/5y1/o168fMTExzJw5k02bNrU6dntzZb/99lvmzp1LcnIysbGxjBs3joceesjVvkcffRSgzaHVbc3pXr9+PaeeeioJCQnExcVx0kkn8c0333isow/d/fLLL1m0aBHp6enExsbyi1/8guLi4g7fw/vvvx+DwcDevXtbPbd48WIiIyNd79H27ds555xzyMrKIioqin79+nHhhRdSWVnZ4TE6YzAYuPbaa3nhhRcYPXo0FouFjz76qN33eM+ePRgMBp5++mmPx7du3cq5555LSkoKUVFRTJkyhXfeecerNrz11lscddRRrXpavXnNNpuNe+65hyFDhmCxWBg0aBC33357q7nCgwYN4vTTT+fjjz9mypQpREdH8/jjj2MwGKitreWZZ55xfSaa5w84ePAgl112GZmZmVgsFkaPHs1TTz3V6jUcOHCAefPmERsbS0ZGBjfeeKPP85VLSko4//zzSUhIIDU1lYULF9LQ0OB6fsaMGYwfP77NbUeMGMGcOXN8Ol5z+nm9//77Wbp0qev93Lx5s+szvmfPHo9tOvo9POWUU0hMTCQmJoYZM2a0OVd/8uTJpKSk8Pbbb/vdbiGE6C2kp1sIIUQrO3fuBCA1NdX1mM1mY86cORx77LHcf//9rmHnV111FU8//TSXXnop119/Pbt37+aRRx5h/fr1fPnll5jNZgD+9Kc/8Ze//IW5c+cyd+5c1q1bx+zZs2lqauq0Pfn5+Zx++ulkZ2ezcOFCsrKy2LJlC++99x4LFy7kqquu4tChQ+Tn57caatuWTZs2cdxxx5GQkMCtt96K2Wzm8ccf54QTTuCzzz7jqKOO8lj/uuuuIzk5mTvvvJM9e/awdOlSrr32Wl555ZV2j3H++edz66238uqrr3LLLbd4PPfqq68ye/ZskpOTaWpqYs6cOTQ2NnLdddeRlZXFwYMHee+996ioqCAxMbHT19ORFStW8Oqrr3LttdeSlpbGoEGDqKio8Hr7TZs2ccwxx5Cbm8ttt91GbGwsr776KvPmzeONN97gF7/4RbvbWq1Wvv/+e373u995PO7ta/7Nb37DM888w7nnnstNN93Et99+y5IlS9iyZQtvvvmmxz63bdvGL3/5S6666iquuOIK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ezerVq3i+uuv58CBAwAsXLiQv/3tb7z11lts3bqVq6++moqKinb3OWjQIBYsWMBll13GW2+95drnq6++CsDAgQMxGAy89957FBcXU1NTQ3x8PDfffDM33ngjzzzzDDt37mTdunU8/PDDrrrjv/3tb9m+fTu33HIL27Zt48UXX+Tpp58O9lskeju9lxukp1sIIYQQ/qsvU8uOgu4woWkaj67ayRZrJgCXDG/i0V9NCmzADVB9SOXHMkZA8qDA7jsMSNAtvBITE8Pq1asZMGAAZ599NiNHjuTyyy+noaHB1fN900038etf/5oFCxYwbdo04uPj+cUvftHhfh977DHOPfdcrr76ao444giuuOIKamtVQJObm8vdd9/NbbfdRmZmJtdeey0A99xzD3fccQdLlixh5MiRnHLKKbz//vvk5eUBMGDAAN544w3eeustxo8fz7Jly7j33nuD+O6IPqF50B2KKgVCCCGE6B286ekOA002B39+bzP/+HgbuzRVn/vioVZMxiAMcdfncyfngSmAQ9bDhAwv76M66vlt77msrCxXb3JbIiIiWLp0KUuXLm13nVWrVnncj4qK4sEHH+TBBx9sc/077riDO+64w+Mxg8HAwoULWbhwYbvHOf300zn99NM9Hrv00kvbXV+ITklPtxBCCCECwRV0p4S2HU41jTZ2FtWwo6iGHcVqubOohr1lddgdKonbgGHjYddXGPQh4IHmylze++ZzgwTdQgjhnerD7tsyp1sIIYQQ/gphT7fdoZG/uYBvdpWx0xlgH65saHf95Bgzd505mkmmetiFe951oLlqdHuXb6qnkaBbCCG8IT3dQgghhAiEEATdVruDdzYc4tFVO9hV3Pp7TFqchaEZsQzNiGNoehxDM+IZmhFHZoIFg8EAh53JzfTgONB6ceZykKBbCCG8U9M86JY53UIIIYTwg6Z1a9DdaLPzxtqDPPbZDvaX1QOQGG3mFxNzOSJLBdZDM+JIionseEepzh7oulKoK4OYAA+NL3H2oPfCGt0gQbcQQnjHo6dbhpcLIYQQwg9NNaruNgQ16G6w2nn5u308vnqXa/h4amwkvzluMBcfPcD3+tqRsZCQC1UH1RDzmKmBa2xTLVSpaki9sVwYSNAthBDe8ZjTLcPLhRBCCOEHvZfbZAFzdMB3X9to4/lv9vLvz3dTUtMIQGaChauOH8Iv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uffee/n6668ZN24cM2fO5ODBg8EeWr9TUVHBuHHjePrpp1u8/bHHHuPJJ5/k2WefZfny5URHRzNz5kyqq6t7eKT90+LFi7nhhhv44osvWLBgAW63m3POOYeKigrfMbfeeiv//e9/ef3111m8eDH79u3j4osvDuKo+5fBgwfz61//mpUrV7JixQrOPvtsZs+ezYYNGwDNTyj56quv+POf/8zYsWObXK85Cq7jjjuO/fv3+/599tlnvts0N8F35MgRTj31VFwuF++99x4bN27kd7/7HYmJib5j9FkheL766qsmr58FCxYAcOmllwJ6DQXbo48+yjPPPMMf//hHNm3axKOPPspjjz3GU0895TsmpF8/hvRqkyZNMm644QbfZY/HY2RkZBiPPPJIEEclgPHWW2/5Lnu9XiMtLc34zW9+47uuuLjYCA8PN/7xj38EYYRy8OBBAzAWL15sGIY5Hy6Xy3j99dd9x2zatMkAjGXLlgVrmP1eYmKi8dxzz2l+QkhZWZkxYsQIY8GCBcaZZ55p3HzzzYZh6DUUbPfee68xbty4Fm/T3ISGO+64wzjttNNavV2fFULLzTffbAwbNszwer16DYWA888/37j66qubXHfxxRcbV1xxhWEYof/6Uaa7F6utrWXlypVMnz7dd53dbmf69OksW7YsiCOTo+3YsYOCgoImcxUfH8/kyZM1V0FSUlICQFJSEgArV67E7XY3maNRo0aRlZWlOQoCj8fDq6++SkVFBVOmTNH8hJAbbriB888/v8lcgF5DoWDr1q1kZGQwdOhQrrjiCnbv3g1obkLFf/7zH0488UQuvfRSBg4cyIQJE/jrX//qu12fFUJHbW0tL730EldffTU2m02voRBwyimnsHDhQrZs2QLAmjVr+Oyzzzj33HOB0H/9OIM9AOm8wsJCPB4PqampTa5PTU1l8+bNQRqVtKSgoACgxbmybpOe4/V6ueWWWzj11FM5/vjjAXOOwsLCSEhIaHKs5qhnrVu3jilTplBdXU1MTAxvvfUWxx57LKtXr9b8hIBXX32Vr7/+mq+++qrZbXoNBdfkyZOZP38+I0eOZP/+/dx///2cfvrprF+/XnMTIrZv384zzzzDT3/6U37xi1/w1VdfcdNNNxEWFsZVV12lzwoh5O2336a4uJi5c+cCen8LBXfeeSelpaWMGjUKh8OBx+Ph4Ycf5oorrgBC/7O2gm4R6XduuOEG1q9f32S/o4SGkSNHsnr1akpKSnjjjTe46qqrWLx4cbCHJcCePXu4+eabWbBgAREREcEejhzFyvYAjB07lsmTJzNkyBBee+01IiMjgzgysXi9Xk488UR+9atfATBhwgTWr1/Ps88+y1VXXRXk0Uljzz//POeeey4ZGRnBHorUe+2113j55Zd55ZVXOO6441i9ejW33HILGRkZveL1o+XlvVhycjIOh6NZ5cQDBw6QlpYWpFFJS6z50FwF34033sg777zDokWLGDx4sO/6tLQ0amtrKS4ubnK85qhnhYWFMXz4cCZOnMgjjzzCuHHj+MMf/qD5CQErV67k4MGDnHDCCTidTpxOJ4sXL+bJJ5/E6XSSmpqqOQohCQkJHHPMMWzbtk2vnxCRnp7Oscce2+S60aNH+7YB6LNCaNi1axcfffQR1157re86vYaC7+c//zl33nknl19+OWPGjOHKK6/k1ltv5ZFHHgFC//WjoLsXCwsLY+LEiSxcuNB3ndfrZeHChUyZMiWII5Oj5eTkkJaW1mSuSktLWb58ueaqhxiGwY033shbb73Fxx9/TE5OTpPbJ06ciMvlajJHubm57N69W3MURF6vl5qaGs1PCJg2bRrr1q1j9erVvn8nnngiV1xxhe+85ih0lJeXk5eXR3p6ul4/IeLUU09t1qpyy5YtDBkyBNBnhVAxb948Bg4cyPnnn++7Tq+h4KusrMRubxq6OhwOvF4v0AteP8Gu5CZd8+qrrxrh4eHG/PnzjY0bNxrXXXedkZCQYBQUFAR7aP1OWVmZsWrVKmPVqlUGYDz++OPGqlWrjF27dhmGYRi//vWvjYSEBOPf//63sXbtWmP27NlGTk6OUVVVFeSR9w/XX3+9ER8fb3zyySfG/v37ff8qKyt9x/zoRz8ysrKyjI8//thYsWKFMWXKFGPKlClBHHX/cueddxqLFy82duzYYaxdu9a48847DZvNZnz44YeGYWh+QlHj6uWGoTkKpttuu8345JNPjB07dhhLly41pk+fbiQnJxsHDx40DENzEwq+/PJLw+l0Gg8//LCxdetW4+WXXzaioqKMl156yXeMPisEl8fjMbKysow77rij2W16DQXXVVddZQwaNMh45513jB07dhhvvvmmkZycbNx+++2+Y0L59aOguw946qmnjKysLCMsLMyYNGmS8cUXXwR7SP3SokWLDKDZv6uuusowDLOVwd13322kpqYa4eHhxrRp04zc3NzgDrofaWluAGPevHm+Y6qqqowf//jHRmJiohEVFWVcdNFFxv79+4M36H7m6quvNoYMGWKEhYUZKSkpxrRp03wBt2FofkLR0UG35ih4LrvsMiM9Pd0ICwszBg0aZFx22WXGtm3bfLdrbkLDf//7X+P44483wsPDjVGjRhl/+ctfmtyuzwrB9cEHHxhAi79zvYaCq7S01Lj55puNrKwsIyIiwhg6dKjxy1/+0qipqfEdE8qvH5thGEZQUuwiIiIiIiIifZz2dIuIiIiIiIgEiIJuERERERERkQBR0C0iIiIiIiISIAq6RURERERERAJEQbeIiIiIiIhIgCjoFhEREREREQkQBd0iIiIiIiIiAaKgW0RERERERCRAFHSLiIiEMJvNxttvvx3sYXDfffcxfvz4gD9PUVERAwcOZOfOnQF/ru6SnZ3NE088AUBtbS3Z2dmsWLEiuIMSEZGQoaBbRET6tUOHDnH99deTlZVFeHg4aWlpzJw5k6VLlwZ7aN1i586d2Gw2Vq9eHeyh+OXhhx9m9uzZZGdnB/R5GgfK3SksLIyf/exn3HHHHd3+2CIi0js5gz0AERGRYJozZw61tbW88MILDB06lAMHDrBw4UKKioqCPbR+p7Kykueff54PPvggYM9RW1tLWFhYwB4f4IorruC2225jw4YNHHfccQF9LhERCX3KdIuISL9VXFzMkiVLePTRRznrrLMYMmQIkyZN4q677uKb3/ym77jHH3+cMWPGEB0dTWZmJj/+8Y8pLy/33T5//nwSEhJ45513GDlyJFFRUVxyySVUVlbywgsvkJ2dTWJiIjfddBMej8d3v+zsbB588EG+/e1vEx0dzaBBg3j66afbHPOePXv41re+RUJCAklJScyePbtDS7E/+eQTbDYbCxcu5MQTTyQqKopTTjmF3NzcJsf9+te/JjU1ldjYWK655hqqq6ubPdZzzz3H6NGjiYiIYNSoUfzpT3/y3Xb11VczduxYampqADPYnTBhAt/73vdaHdu7775LeHg4J598su+6I0eOcMUVV5CSkkJkZCQjRoxg3rx5vtvXrVvH2WefTWRkJAMGDOC6665rMjdz587lwgsv5OGHHyYjI4ORI0cydepUdu3axa233orNZsNms/mO/+yzzzj99NOJjIwkMzOTm266iYqKCt/tBw8e5IILLiAyMpKcnBxefvnlZj9HYmIip556Kq+++mqrP6uIiPQfCrpFRKTfiomJISYmhrffftsXHLbEbrfz5JNPsmHDBl544QU+/vhjbr/99ibHVFZW8uSTT/Lqq6/y/vvv88knn3DRRRfx7rvv8u677/Liiy/y5z//mTfeeKPJ/X7zm98wbtw4Vq1axZ133snNN9/MggULWhyH2+1m5syZxMbGsmTJEpYuXUpMTAyzZs2itra2Qz/7L3/5S373u9+xYsUKnE4nV199te+21157jfvuu49f/epXrFixgvT09CYBNcDLL7/MPffcw8MPP8ymTZv41a9+xd13380LL7wAwJNPPklFRQV33nmn7/mKi4v54x//2OqYlixZwsSJE5tcd/fdd7Nx40bee+89Nm3axDPPPENycjIAFRUVzJw5k8TERL766itef/11PvroI2688cYmj7Fw4UJyc3NZsGAB77zzDm+++SaDBw/mgQceYP/+/ezfvx+AvLw8Zs2axZw5c1i7di3//Oc/+eyzz5o83ty5c9mzZw+LFi3ijTfe4E9/+hMHDx5s9rNMmjSJJUuWtDsPIiLSDxgiIiL92BtvvGEkJiYaERERximnnGLcddddxpo1a9q8z+uvv24MGDDAd3nevHkGYGzbts133Q9/+EMjKirKKCsr8103c+ZM44c//KHv8pAhQ4xZs2Y1eezLLrvMOPfcc32XAeOtt94yDMMwXnzxRWPkyJGG1+v13V5TU2NERkYaH3zwQYtj3bFjhwEYq1atMgzDMBYtWmQAxkcffeQ75n//+58BGFVVVYZhGMaUKVOMH//4x00eZ/Lkyca4ceN8l4cNG2a88sorTY558MEHjSlTpvguf/7554bL5TLuvvtuw+l0GkuWLGlxjJbZs2cbV199dZPrLrjgAuP73/9+i8f/5S9/MRITE43y8vImP4vdbjcKCgoMwzCMq666ykhNTTVqamqa3HfIkCHG73//+ybXXXPNNcZ1113X5LolS5YYdrvdqKqqMnJzcw3A+PLLL323b9q0yQCaPdYf/vAHIzs7u82fV0RE+gdlukVEpF+bM2cO+/bt4z//+Q+zZs3ik08+4YQTTmD+/Pm+Yz766COmTZvGoEGDiI2N5corr6SoqIjKykrfMVFRUQwbNsx3OTU1lezsbGJiYppcd3RWdMqUKc0ub9q0qcWxrlmzhm3bthEbG+vL0iclJVFdXU1eXl6Hfu6xY8f6zqenpwP4xrZp0yYmT57c6jgrKirIy8vjmmuu8Y0jJiaGhx56qMk4pkyZws9+9jMefPBBbrvtNk477bQ2x1RVVUVEREST666//npeffVVxo8fz+23387nn3/uu23Tpk2MGzeO6Oho33WnnnoqXq+3yXL5MWPG+LWPe82aNcyfP7/JzzRz5ky8Xi87duxg06ZNOJ3OJtn4UaNGkZCQ0OyxIiMjm/x9iIhI/6VCaiIi0u9FREQwY8YMZsyYwd133821117Lvffey9y5c9m5cyff+MY3uP7663n44YdJSkris88+45prrqG2tpaoqCgAXC5Xk8e02WwtXuf1ejs9zvLyciZOnNjiPuKUlJQOPVbjsVl7mv0dm7Vn+q9//Wuz4NzhcPjOe71eli5disPhYNu2be0+bnJyMkeOHGly3bnnnsuuXbt49913WbBgAdOmTeOGG27gt7/9rV9jBZoE5W0pLy/nhz/8ITfddFOz27KystiyZYvfz3n48OEOz4mIiPRNynSLiIgc5dhjj/UVz1q5ciVer5ff/e53nHzyyRxzzDHs27ev257riy++aHZ59OjRLR57wgknsHXrVgYOHMjw4cOb/IuPj++2MY0ePZrly5e3Os7U1FQyMjLYvn17s3Hk5OT4jvvNb37D5s2bWbx4Me+//36TAmgtmTBhAhs3bmx2fUpKCldddRUvvfQSTzzxBH/5y19841yzZk2TQmdLly7FbrczcuTINp8rLCysSVE7MH+/GzdubPYzDR8+nLCwMEaNGkVdXR0rV6703Sc3N5fi4uJmj79+/XomTJjQ5hhERKR/UNAtIiL9VlFREWeffTYvvfQSa9euZceOHbz++us89thjzJ49G4Dhw4fjdrt56qmn2L59Oy+++CLPPvtst41h6dKlPPbYY2zZsoWnn36a119/nZtvvrnFY6+44gqSk5OZPXs2S5YsYceOHXzyySfcdNNN7N27t9vGdPPNN/O3v/2NefPmsWXLFu699142bNjQ5Jj777+fRx55hCeffJItW7awbt065s2bx+OPPw7AqlWruOeee3juuec49dRTefzxx7n55pvZvn17q887c+ZMNmzY0CTbfc899/Dvf/+bbdu2sWHDBt555x3flxJXXHEFERERXHXVVaxfv55Fixbxk5/8hCuvvJLU1NQ2f8bs7Gw+/fRT8vPzKSwsBOCOO+7g888/58Ybb2T16tVs3bqVf//7375CaiNHjmTWrFn88Ic/ZPny5axcuZJrr72WyMjIZo+/ZMkSzjnnHD9+2yIi0tcp6BYRkX4rJiaGyZMn8/vf/54zzjiD448/nrvvvpsf/OAHvirb48aN4/HHH+fRRx/l+OOP5+WXX+aRRx7ptjHcdtttrFixggkTJvDQQw/x+OOPM3PmzBaPjYqK4tNPPyUrK4uLL76Y0aNH+9p5xcXFdduYLrvsMu6++25uv/12Jk6cyK5du7j++uubHHPttdfy3HPPMW/ePMaMGcOZZ57J/PnzycnJobq6mu9+97vMnTuXCy64AIDrrruOs846iyuvvLJZhtkyZswYTjjhBF577TXfdWFhYdx1112MHTuWM844A4fD4WvFFRUVxQcffMDhw4c56aSTuOSSS5g2bVqbFdItDzzwADt37mTYsGG+ZeBjx45l8eLFbNmyhdNPP50JEyZwzz33kJGR4bvfvHnzyMjI4Mwzz+Tiiy/muuuuY+DAgU0ee9myZZSUlHDJJZf48dsWEZG+zmYYhhHsQYiIiPRH2dnZ3HLLLdxyyy3BHkrI+N///sfPf/5z1q9fj93eO3MDl112GePGjeMXv/hFsIciIiIhQIXUREREJGScf/75bN26lfz8fDIzM4M9nA6rra1lzJgx3HrrrcEeioiIhAgF3SIiIhJSenPmPywsjP/7v/8L9jBERCSEaHm5iIiIiIiISID0zs1SIiIiIiIiIr2Agm4RERERERGRAFHQLSIiIiIiIhIgCrpFREREREREAkRBt4iIiIiIiEiAKOgWERERERERCRAF3SIiIiIiIiIBoqBbREREREREJEAUdIuIiIiIiIgEyP8DYA8aY5gL6noAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Test MSE: 0.378216\n", - "R² Score: 0.6619\n", - "Epoch 76 | Loss: 0.064937\n", - "Epoch 77 | Loss: 0.050079\n", - "Epoch 78 | Loss: 0.055219\n", - "Epoch 79 | Loss: 0.053107\n", - "Epoch 80 | Loss: 0.065674\n", - "Epoch 81 | Loss: 0.074585\n", - "Epoch 82 | Loss: 0.071789\n", - "Epoch 83 | Loss: 0.063313\n", - "Epoch 84 | Loss: 0.050698\n", - "Epoch 85 | Loss: 0.058837\n", - "Epoch 86 | Loss: 0.049046\n", - "Epoch 87 | Loss: 0.044147\n", - "Epoch 88 | Loss: 0.055033\n", - "Epoch 89 | Loss: 0.046234\n", - "Epoch 90 | Loss: 0.036136\n", - "Epoch 91 | Loss: 0.035275\n", - "Epoch 92 | Loss: 0.047785\n", - "Epoch 93 | Loss: 0.040791\n", - "Epoch 94 | Loss: 0.047713\n", - "Epoch 95 | Loss: 0.042139\n", - "Epoch 96 | Loss: 0.036102\n", - "Epoch 97 | Loss: 0.034951\n", - "Epoch 98 | Loss: 0.038887\n", - "Epoch 99 | Loss: 0.035606\n", - "Epoch 100 | Loss: 0.034245\n" - ] - }, - { - "data": { - "image/png": "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ruDGG2/khhtuYNCgQbz66qsNUq379+8PQEFBAaeeeqrnwkR6ejqfffYZ1113HWVlZdx2222AGppw1llnkZuby+9+9zu6d+/Oq6++ysqVK33+G4J6PfTp04dFixbx3Xff8dRTT1FcXMwrr7wCwFVXXcUNN9zAtm3bGD58uGe/H374gd27d/OnP/3Jr8drzEsvvURNTQ2/+tWviI6OJi0tza/9V65cydlnn83YsWO5//77MRqNvPTSS5x55pl8/fXXjB8/vs72Y8aM6VDF84QQImCaEEKITuull17SAG3FihVaYWGhdujQIe3NN9/UunTposXGxmqHDx/WNE3TFixYoAHa3XffXWf/r7/+WgO0//znP3Vu//zzz+vcfuzYMS0qKko755xzNJfL5dnu3nvv1QBtwYIFnttWrVqlAdqqVas0TdM0h8Oh9e3bV+vdu7dWXFxc53FqH+umm27Smvq3Bmj333+/5/d58+ZpUVFR2r59+zy3HT16VEtMTNTOOOOMBn+f6dOn13ms22+/XTOZTFpJSUmjj6ebOHGiNnbs2Dq3ff/99xqgvfLKK5qmadrmzZs1QHvnnXeaPVZLhg0bpk2ZMqXObYBmNBq17du317m9/t9Yd+DAAQ3QXnrpJc9tZ511ljZixAitpqbGc5vL5dImTZqkDRw4sNk27d27VwO0p59+us7tvjznLVu2aIB2/fXX17n997//vQZoK1eu9NzWu3dvDdA+//zzBseJj4+v8/rSXXfddVq3bt2048eP17n98ssv15KTk7WqqipN0zRt8eLFGqC9/fbbnm0qKyu1AQMGNPo3rO/+++/XAO28886rc/tvf/tbDdC2bt2qaZqmlZSUaDExMdpdd91VZ7vf/e53Wnx8vFZRUdHs49RW/znr5zUpKUk7duxYnW311/iBAwfq3F7/NeJyubSBAwdqs2bNqvNeqKqq0vr27avNmDGjQTt+9atfabGxsT63WwghOipJLxdCCMH06dNJT08nOzubyy+/nISEBN5//3169OhRZ7v6PZbvvPMOycnJzJgxg+PHj3t+xo4dS0JCAqtWrQJgxYoV2Gy2BkWV9N7E5mzevJkDBw5w2223kZKSUue+QAo0OZ1Oli1bxrx58+qMhe3WrRtXXnkl33zzDWVlZXX2+dWvflXnsU4//XScTmejqeO1XXbZZWzcuNGTOQDw1ltvER0dzfnnnw/g6dX94osvqKqq8vv5tGTKlCkMHTo0oH2LiopYuXIll156KeXl5Z7ze+LECWbNmsWePXs4cuRIk/ufOHECgNTU1Dq3+/KcP/30UwAWLlxY5/Y77rgDgE8++aTO7X379vU5BVvTNN577z3OPfdcNE2r89qdNWsWpaWlbNq0ydOObt26cfHFF3v2j4uLq1OkzBc33XRTnd9vueUWz/FB/U3OP/983njjDTRNA9Rr9a233vKktrfWRRddRHp6ekD7btmyhT179nDllVdy4sQJz9+rsrKSs846izVr1uByuersk5qaSnV1dUhe10II0Z5I0C2EEIJnnnmG5cuXs2rVKn7++Wf279/fIIAxm8307Nmzzm179uyhtLSUjIwM0tPT6/xUVFRw7NgxAE9wOnDgwDr7p6enNwjI6tMD1topt61RWFhIVVUVgwYNanDfkCFDcLlcHDp0qM7tvXr1qvO73ub649bru+SSSzAajZ40dE3TeOeddzxjyUEFiwsXLuSFF16ga9euzJo1i2eeeabV47l1ffv2DXjfvXv3omkaf/7znxuc3/vvvx/Ac46boweRtdvU0nPOycnBaDQyYMCAOvtmZWWRkpLS4IKHP8+zsLCQkpISnn/++QbP65prrqnzvHJychgwYECDCzyNvX6aU/+1379/f4xGY52x1PPnzyc3N5evv/4aUBerCgoKuOqqq/x6rKa05rWwZ88eABYsWNDgb/bCCy9gtVobvGb18y7Vy4UQnZ2M6RZCCMH48eM91cubEh0djdFY91qty+UiIyOD//znP43uE2ivWqQxmUyN3l4/mKyve/funH766bz99tvce++9fPfdd+Tm5vK3v/2tznaPP/44V199NR9++CHLli3jd7/7nWfsb/0LHf5qrIJ3U0GQ0+ms87vec/n73/++yV7k+kFxbXpNgMYuTvj6nH0N2PypVK4/r1/+8pcsWLCg0W1Gjhzp8/EC0djzmjVrFpmZmbz22mucccYZvPbaa2RlZTF9+vSgPGYwXguPPvooJ598cqP71C+WV1xcTFxcXKeoIi+EEM2RoFsIIUTA+vfvz4oVK5g8eXKzX6x79+4NqN6y2indhYWFLfYW64Wvtm3b1mzw4Wtwlp6eTlxcHLt27Wpw386dOzEajWRnZ/t0LF9cdtll/Pa3v2XXrl289dZbxMXFNTp91ogRIxgxYgR/+tOfWLt2LZMnT2bJkiX85S9/CVpbdHpPff2CavV7j/VzZbFYAgr8evXqRWxsrKcKfn3NPefevXvjcrnYs2cPQ4YM8exTUFBASUmJ5zXVksZeF+np6SQmJuJ0Olt8Xr1792bbtm1omlbnWI29fpqzZ8+eOj3Ne/fuxeVy0adPH89tJpOJK6+8kqVLl/K3v/2NDz74gBtuuKHJiz7B4OtrQX8fJiUl+fxaOHDgQJ1zJ4QQnZWklwshhAjYpZdeitPp5OGHH25wn8Ph8HyRnz59OhaLhaeffrpO7/DixYtbfIwxY8bQt29fFi9e3CAwqH0sfcxrS5W5TSYTM2fO5MMPP6yT2ltQUMDrr7/Oaaed5kn9DoaLLroIk8nEG2+8wTvvvMPcuXPrjM8tKyvD4XDU2WfEiBEYjUa/p6XyVe/evTGZTKxZs6bO7f/617/q/J6RkcHUqVN57rnnyMvLa3CclqZNs1gsjBs3jg0bNtS53ZfnPGfOHKDha+SJJ54A4Jxzzmn2sXXx8fENXhMmk4mLLrqI9957j23btjXYp/bzmjNnDkePHuXdd9/13FZVVcXzzz/v0+Pr9CntdE8//TQAZ599dp3br7rqKoqLi7nxxhupqKhodq7tYNCD6dqvBafT2eD5jR07lv79+/PYY49RUVHR4DiNvRY2bdrEpEmTgtxiIYRof6SnWwghRMCmTJnCjTfeyKJFi9iyZQszZ87EYrGwZ88e3nnnHZ588kkuvvhi0tPT+f3vf8+iRYuYO3cuc+bMYfPmzXz22Wd07dq12ccwGo08++yznHvuuZx88slcc801dOvWjZ07d7J9+3a++OILQAUFAL/73e+YNWsWJpOJyy+/vNFj/uUvf2H58uWcdtpp/Pa3v8VsNvPcc89htVr5+9//HtS/UUZGBtOmTeOJJ56gvLycyy67rM79K1eu5Oabb+aSSy7hpJNOwuFw8Oqrr3oCw1BITk7mkksu4emnn8ZgMNC/f38+/vjjRsdnP/PMM5x22mmMGDGCG264gX79+lFQUMC6des4fPgwW7dubfaxzj//fP74xz9SVlbmuZjhy3MeNWoUCxYs4Pnnn6ekpIQpU6bw/fff8/LLLzNv3jymTZvm03MdO3YsK1as4IknnqB79+707duXCRMm8Ne//pVVq1YxYcIEbrjhBoYOHUpRURGbNm1ixYoVFBUVAXDDDTfwz3/+k/nz57Nx40a6devGq6++6pkyz1cHDhzgvPPOY/bs2axbt47XXnuNK6+8ssHc3KNHj2b48OG88847DBkyhDFjxvj1OP4aNmwYp556Kvfccw9FRUWkpaXx5ptvNrgoYjQaeeGFFzj77LMZNmwY11xzDT169ODIkSOsWrWKpKQk/ve//3m237hxI0VFRZ6CgUII0amFqWq6EEKICKBPF/TDDz80u92CBQu0+Pj4Ju9//vnntbFjx2qxsbFaYmKiNmLECO3OO+/Ujh496tnG6XRqDz74oNatWzctNjZWmzp1qrZt2zatd+/ezU4Zpvvmm2+0GTNmaImJiVp8fLw2cuTIOlNRORwO7ZZbbtHS09M1g8FQZ/ow6k0ZpmmatmnTJm3WrFlaQkKCFhcXp02bNk1bu3atT3+fptrYlH//+98aoCUmJmrV1dV17tu/f7927bXXav3799diYmK0tLQ0bdq0adqKFSt8OrauqSnDbrrppka3Lyws1C666CItLi5OS01N1W688UZt27ZtDaYM0zRN27dvnzZ//nwtKytLs1gsWo8ePbS5c+dq7777bovtKigo0Mxms/bqq6/6/Zztdrv24IMPan379tUsFouWnZ2t3XPPPXWmL9M0NWXYOeec0+jj79y5UzvjjDO02NjYBtPTFRQUaDfddJOWnZ2tWSwWLSsrSzvrrLO0559/vs4xcnJytPPOO0+Li4vTunbtqt16662eafF8nTLs559/1i6++GItMTFRS01N1W6++eYGrwXd3//+dw3QHnnkkWaP3ZSmpgx79NFHG91+37592vTp07Xo6GgtMzNTu/fee7Xly5c3+vw2b96sXXjhhVqXLl206OhorXfv3tqll16qffnll3W2u+uuu7RevXrVmV5MCCE6K4OmtVAFRgghhBCiFa677jp2797tqcotmvfkk09y++23c/DgwQaV89sDq9VKnz59uPvuu7n11lvD3RwhhAg7CbqFEEIIEVK5ubmcdNJJfPnll0yePDnczYlomqYxatQounTp4pnnvr1ZsmQJjzzyCHv27CE6OjrczRFCiLCToFsIIYQQIswqKyv56KOPWLVqFf/+97/58MMPOe+888LdLCGEEEEgQbcQQgghRJgdPHiQvn37kpKSwm9/+1v+3//7f+FukhBCiCCRoFsIIYQQQgghhAgRmadb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1OKu56WXXmLo0KGedTbHjh3L559/7nle/RNcHn/8cSwWC7fddpvnMfVR4Dz44INYLJYG/3JycjzPq2+Cw969e/nFL35BUlISERERDBkyhGXLlnme12eFwMnKyjrqGrJYLNx0002ArqFAczqd3HfffWRnZxMREUGfPn14+OGHqV+eLJivHwXdndy//vUv7rjjDh544AF++OEHhg0bxqRJkzhw4ECgm9bllJWVMWzYMF544YVGn3/yySd57rnnePnll1myZAlRUVFMmjSJysrKDm5p1zR37lxuuukmFi9ezOzZs3E4HJx11lmUlZV59rn99tv573//y3vvvcfcuXPZt28fF110UQBb3bX06NGDxx9/nOXLl7Ns2TLOOOMMpkyZwo8//giof4LJ0qVL+dvf/sbQoUMbPK4+CqxBgwaxf/9+z7/58+d7nlPfBF5BQQHjxo3Dbrfz+eefs27dOp5++mkSEhI8++izQuAsXbq0wfUze/ZsAC655BJA11CgPfHEE7z00kv89a9/Zf369TzxxBM8+eSTPP/88559gvr6MaRTGz16tHHTTTd57judTiMjI8N47LHHAtgqAYwPPvjAc9/lchlpaWnGn/70J89jhYWFRlhYmPH2228HoIVy4MABAzDmzp1rGIbZH3a73Xjvvfc8+6xfv94AjEWLFgWqmV1eQkKC8eqrr6p/gkhJSYnRr18/Y/bs2cZpp51m3HrrrYZh6BoKtAceeMAYNmxYo8+pb4LD3XffbZx88slNPq/PCsHl1ltvNfr06WO4XC5dQ0Hg3HPPNa6++uoGj1100UXGtGnTDMMI/utHI92dWHV1NcuXL2fixImex6xWKxMnTmTRokUBbJkcafv27eTm5jboq7i4OMaMGaO+CpCioiIAEhMTAVi+fDkOh6NBH+Xk5JCZmak+CgCn08k777xDWVkZY8eOVf8EkZtuuolzzz23QV+ArqFgsHnzZjIyMujduzfTpk1j165dgPomWHz88cccf/zxXHLJJaSkpDBixAj+/ve/e57XZ4XgUV1dzZtvvsnVV1+NxWLRNRQETjrpJL755hs2bdoEwKpVq5g/fz5nn302EPzXT0igGyBtd+jQIZxOJ6mpqQ0eT01NZcOGDQFqlTQmNzcXoNG+cj8nHcflcnHbbbcxbtw4Bg8eDJh9FBoaSnx8fIN91Ucda82aNYwdO5bKykqio6P54IMPGDhwICtXrlT/BIF33nmHH374gaVLlx71nK6hwBozZgwzZ86kf//+7N+/n4ceeohTTjmFtWvXqm+CxLZt23jppZe44447+N3vfsfSpUu55ZZbCA0N5corr9RnhSDy4YcfUlhYyPTp0wH9fQsG99xzD8XFxeTk5GCz2XA6nTz66KNMmzYNCP7P2gq6RaTLuemmm1i7dm2D+Y4SHPr378/KlSspKiri/fff58orr2Tu3LmBbpYAu3fv5tZbb2X27NmEh4cHujlyBPdoD8DQoUMZM2YMvXr14t133yUiIiKALRM3l8vF8ccfzx//+EcARowYwdq1a3n55Ze58sorA9w6qe+1117j7LPPJiMjI9BNkVrvvvsus2bN4q233mLQoEGsXLmS2267jYyMjE5x/Si9vBPr1q0bNpvtqMqJeXl5pKWlBahV0hh3f6ivAu/mm2/mk08+4bvvvqNHjx6ex9PS0qiurqawsLDB/uqjjhUaGkrfvn0ZNWoUjz32GMOGDeMvf/mL+icILF++nAMHDjBy5EhCQkIICQlh7ty5PPfcc4SEhJCamqo+CiLx8fEcd9xxbNmyRddPkEhPT2fgwIENHhswYIBnGoA+KwSHnTt38vXXX3Pttdd6HtM1FHi//e1vueeee/jZz37GkCFDuPzyy7n99tt57LHHgOC/fhR0d2KhoaGMGjWKb775xvOYy+Xim2++YezYsQFsmRwpOzubtLS0Bn1VXFzMkiVL1FcdxDAMbr75Zj744AO+/fZbsrOzGzw/atQo7HZ7gz7auHEju3btUh8FkMvloqqqSv0TBCZMmMCaNWtYuXKl59/xxx/PtGnTPLfVR8GjtLSUrVu3kp6erusnSIwbN+6opSo3bdpEr169AH1WCBYzZswgJSWFc8891/OYrqHAKy8vx2ptGLrabDZcLhfQCa6fQFdyk/Z55513jLCwMGPmzJnGunXrjOuvv96Ij483cnNzA920LqekpMRYsWKFsWLFCgMwnnnmGWPFihXGzp07DcMwjMcff9yIj483PvroI2P16tXGlClTjOzsbKOioiLALe8abrzxRiMuLs6YM2eOsX//fs+/8vJyzz6//OUvjczMTOPbb781li1bZowdO9YYO3ZsAFvdtdxzzz3G3Llzje3btxurV6827rnnHsNisRhfffWVYRjqn2BUv3q5YaiPAunOO+805syZY2zfvt1YsGCBMXHiRKNbt27GgQMHDMNQ3wSD77//3ggJCTEeffRRY/PmzcasWbOMyMhI48033/Tso88KgeV0Oo3MzEzj7rvvPuo5XUOBdeWVVxrdu3c3PvnkE2P79u3Gf/7zH6Nbt27GXXfd5dknmK8fBd3HgOeff97IzMw0QkNDjdGjRxuLFy8OdJO6pO+++84Ajvp35ZVXGoZhLmVw3333GampqUZYWJgxYcIEY+PGjYFtdBfSWN8AxowZMzz7VFRUGL/61a+MhIQEIzIy0rjwwguN/fv3B67RXczVV19t9OrVywgNDTWSk5ONCRMmeAJuw1D/BKMjg271UeBceumlRnp6uhEaGmp0797duPTSS40tW7Z4nlffBIf//ve/xuDBg42wsDAjJyfHeOWVVxo8r88KgfXll18aQKPvua6hwCouLjZuvfVWIzMz0wgPDzd69+5t/N///Z9RVVXl2SeYrx+LYRhGQIbYRURERERERI5xmtMtIiIiIiIi4icKukVERERERET8REG3iIiIiIiIiJ8o6BYRERERERHxEwXdIiIiIiIiIn6ioFtERERERETETxR0i4iIiIiIiPiJgm4RERERERERP1HQLSIiEsQsFgsffvhhoJvBgw8+yPDhw/1+nvz8fFJSUtixY4ffz+UrWVlZPPvsswBUV1eTlZXFsmXLAtsoEREJGgq6RUSkSzt48CA33ngjmZmZhIWFkZaWxqRJk1iwYEGgm+YTO3bswGKxsHLlykA3xSuPPvooU6ZMISsry6/nqR8o+1JoaCi/+c1vuPvuu31+bBER6ZxCAt0AERGRQJo6dSrV1dW8/vrr9O7dm7y8PL755hvy8/MD3bQup7y8nNdee40vv/zSb+eorq4mNDTUb8cHmDZtGnfeeSc//vgjgwYN8uu5REQk+GmkW0REuqzCwkLmzZvHE088wemnn06vXr0YPXo09957Lz/5yU88+z3zzDMMGTKEqKgoevbsya9+9StKS0s9z8+cOZP4+Hg++eQT+vfvT2RkJBdffDHl5eW8/vrrZGVlkZCQwC233ILT6fS8Lisri4cffpif//znREVF0b17d1544YVm27x7925++tOfEh8fT2JiIlOmTGlVKvacOXOwWCx88803HH/88URGRnLSSSexcePGBvs9/vjjpKamEhMTwzXXXENlZeVRx3r11VcZMGAA4eHh5OTk8OKLL3qeu/rqqxk6dChVVVWAGeyOGDGCK664osm2ffbZZ4SFhXHiiSd6HisoKGDatGkkJycTERFBv379mDFjhuf5NWvWcMYZZxAREUFSUhLXX399g76ZPn06F1xwAY8++igZGRn079+f8ePHs3PnTm6//XYsFgsWi8Wz//z58znllFOIiIigZ8+e3HLLLZSVlXmeP3DgAOeffz4RERFkZ2cza9aso36OhIQExo0bxzvvvNPkzyoiIl2Hgm4REemyoqOjiY6O5sMPP/QEh42xWq0899xz/Pjjj7z++ut8++233HXXXQ32KS8v57nnnuOdd97hiy++YM6cOVx44YV89tlnfPbZZ7zxxhv87W9/4/3332/wuj/96U8MGzaMFStWcM8993Drrbcye/bsRtvhcDiYNGkSMTExzJs3jwULFhAdHc3kyZOprq5u1c/+f//3fzz99NMsW7aMkJAQrr76as9z7777Lg8++CB//OMfWbZsGenp6Q0CaoBZs2Zx//338+ijj7J+/Xr++Mc/ct999/H6668D8Nxzz1FWVsY999zjOV9hYSF//etfm2zTvHnzGDVqVIPH7rvvPtatW8fnn3/O+vXreemll+jWrRsAZWVlTJo0iYSEBJYuXcp7773H119/zc0339zgGN988w0bN25k9uzZfPLJJ/znP/+hR48e/OEPf2D//v3s378fgK1btzJ58mSmTp3K6tWr+de//sX8+fMbHG/69Ons3r2b7777jvfff58XX3yRAwcOHPWzjB49mnnz5rXYDyIi0gUYIiIiXdj7779vJCQkGOHh4cZJJ51k3HvvvcaqVauafc17771nJCUlee7PmDHDAIwtW7Z4HrvhhhuMyMhIo6SkxPPYpEmTjBtuuMFzv1evXsbkyZMbHPvSSy81zj77bM99wPjggw8MwzCMN954w+jfv7/hcrk8z1dVVRkRERHGl19+2Whbt2/fbgDGihUrDMMwjO+++84AjK+//tqzz6effmoARkVFhWEYhjF27FjjV7/6VYPjjBkzxhg2bJjnfp8+fYy33nqrwT4PP/ywMXbsWM/9hQsXGna73bjvvvuMkJAQY968eY220W3KlCnG1Vdf3eCx888/37jqqqsa3f+VV14xEhISjNLS0gY/i9VqNXJzcw3DMIwrr7zSSE1NNaqqqhq8tlevXsaf//znBo9dc801xvXXX9/gsXnz5hlWq9WoqKgwNm7caADG999/73l+/fr1BnDUsf7yl78YWVlZzf68IiLSNWikW0REurSpU6eyb98+Pv74YyZPnsycOXMYOXIkM2fO9Ozz9ddfM2HCBLp3705MTAyXX345+fn5lJeXe/aJjIykT58+nvupqalkZWURHR3d4LEjR0XHjh171P3169c32tZVq1axZcsWYmJiPKP0iYmJVFZWsnXr1lb93EOHDvXcTk9PB/C0bf369YwZM6bJdpaVlbF161auueYaTzuio6N55JFHGrRj7Nix/OY3v+Hhhx/mzjvv5OSTT262TRUVFYSHhzd47MYbb+Sdd95h+PDh3HXXXSxcuNDz3Pr16xk2bBhRUVGex8aNG4fL5WqQLj9kyBCv5nGvWrWKmTNnNviZJk2ahMvlYvv27axfv56QkJAGo/E5OTnEx8cfdayIiIgGvx8iItJ1qZCaiIh0eeHh4Zx55pmceeaZ3HfffVx77bU88MADTJ8+nR07dnDeeedx44038uijj5KYmMj8+fO55pprqK6uJjIyEgC73d7gmBaLpdHHXC5Xm9tZWlrKqFGjGp1HnJyc3Kpj1W+be06zt21zz5n++9//flRwbrPZPLddLhcLFizAZrOxZcuWFo/brVs3CgoKGjx29tlns3PnTj777DNmz57NhAkTuOmmm3jqqae8aivQIChvTmlpKTfccAO33HLLUc9lZmayadMmr895+PDhVveJiIgcmzTSLSIicoSBAwd6imctX74cl8vF008/zYknnshxxx3Hvn37fHauxYsXH3V/wIABje47cuRINm/eTEpKCn379m3wLy4uzmdtGjBgAEuWLGmynampqWRkZLBt27aj2pGdne3Z709/+hMbNmxg7ty5fPHFFw0KoDVmxIgRrFu37qjHk5OTufLKK3nzzTd59tlneeWVVzztXLVqVYNCZwsWLMBqtdK/f/9mzxUaGtqgqB2Y7++6deuO+pn69u1LaGgoOTk51NTUsHz5cs9rNm7cSGFh4VHHX7t2LSNGjGi2DSIi0jUo6BYRkS4rPz+fM844gzfffJPVq1ezfft23nvvPZ588kmmTJkCQN++fXE4HDz//PNs27aNN954g5dfftlnbViwYAFP4sYLEAAAA2pJREFUPvkkmzZt4oUXXuC9997j1ltvbXTfadOm0a1bN6ZMmcK8efPYvn07c+bM4ZZbbmHPnj0+a9Ott97KP/7xD2bMmMGmTZt44IEH+PHHHxvs89BDD/HYY4/x3HPPsWnTJtasWcOMGTN45plnAFixYgX3338/r776KuPGjeOZZ57h1ltvZdu2bU2ed9KkSfz4448NRrvvv/9+PvroI7Zs2cKPP/7IJ5984vlSYtq0aYSHh3PllVeydu1avvvuO379619z+eWXk5qa2uzPmJWVxf/+9z/27t3LoUOHALj77rtZuHAhN998MytXrmTz5s189NFHnkJq/fv3Z/Lkydxwww0sWbKE5cuXc+211xIREXHU8efNm8dZZ53lxbstIiLHOgXdIiLSZUVHRzNmzBj+/Oc/c+qppzJ48GDuu+8+rrvuOk+V7WHDhvHMM8/wxBNPMHjwYGbNmsVjjz3mszbceeedLFu2jBEjRvDII4/wzDPPMGnSpEb3jYyM5H//+x+ZmZlcdNFFDBgwwLOcV2xsrM/adOmll3Lfffdx1113MWrUKHbu3MmNN97YYJ9rr72WV199lRkzZjBkyBBOO+00Zs6cSXZ2NpWVlfziF79g+vTpnH/++QBcf/31nH766Vx++eVHjTC7DRkyhJEjR/Luu+96HgsNDeXee+9l6NChnHrqqdhsNs9SXJGRkXz55ZccPnyYE044gYsvvpgJEyY0WyHd7Q9/+AM7duygT58+njTwoUOHMnfuXDZt2sQpp5zCiBEjuP/++8nIyPC8bsaMGWRkZHDaaadx0UUXcf3115OSktLg2IsWLaKoqIiLL77Yi3dbRESOdRbDMIxAN0JERKQrysrK4rbbbuO2224LdFOCxqeffspvf/tb1q5di9XaOccGLr30UoYNG8bvfve7QDdFRESCgAqpiYiISNA499xz2bx5M3v37qVnz56Bbk6rVVdXM2TIEG6//fZAN0VERIKEgm4REREJKp155D80NJTf//73gW6GiIgEEaWXi4iIiIiIiPhJ55wsJSIiIiIiItIJKOgWERERERER8RMF3SIiIiIiIiJ+oqBbRERERERExE8UdIuIiIiIiIj4iYJuERERERERET9R0C0iIiIiIiLiJwq6RURERERERPxEQbeIiIiIiIiIn/w/1aTAa4UczGsAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Test MSE: 0.374343\n", - "R² Score: 0.6654\n", - "Epoch 101 | Loss: 0.032393\n", - "Epoch 102 | Loss: 0.029607\n", - "Epoch 103 | Loss: 0.028110\n", - "Epoch 104 | Loss: 0.030553\n", - "Epoch 105 | Loss: 0.029351\n", - "Epoch 106 | Loss: 0.024944\n", - "Epoch 107 | Loss: 0.024700\n", - "Epoch 108 | Loss: 0.030539\n", - "Epoch 109 | Loss: 0.031320\n", - "Epoch 110 | Loss: 0.031327\n", - "Epoch 111 | Loss: 0.030809\n", - "Epoch 112 | Loss: 0.029617\n", - "Epoch 113 | Loss: 0.032479\n", - "Epoch 114 | Loss: 0.030788\n", - "Epoch 115 | Loss: 0.066005\n", - "Epoch 116 | Loss: 0.061317\n", - "Epoch 117 | Loss: 0.033944\n", - "Epoch 118 | Loss: 0.027432\n", - "Epoch 119 | Loss: 0.036940\n", - "Epoch 120 | Loss: 0.055142\n", - "Epoch 121 | Loss: 0.035384\n", - "Epoch 122 | Loss: 0.033386\n", - "Epoch 123 | Loss: 0.032751\n", - "Epoch 124 | Loss: 0.028801\n", - "Epoch 125 | Loss: 0.022487\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAAHqCAYAAAAZLi26AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdd3hb1fnA8a+W5T3jlcRxnD3IDoEkQAJkEQKEPRt2admElvVrGaUlHUDTUkqgFMIss4wyM0hYYWWRQfZylne8bc37++PoytuWbMmS7ffzPH7utXTHka9s673vOe8xaJqmIYQQQgghhBBCiIAzhroBQgghhBBCCCFEdyVBtxBCCCGEEEIIESQSdAshhBBCCCGEEEEiQbcQQgghhBBCCBEkEnQLIYQQQgghhBBBIkG3EEIIIYQQQggRJBJ0CyGEEEIIIYQQQSJBtxBCCCGEEEIIESQSdAshhBBCCCGEEEEiQbcQQoiQ6t+/P1dddZX3+9WrV2MwGFi9enXAzmEwGHjwwQcDdjzhn4MHDxIZGcnXX38d6qYEjK/v0wcffBCDwUBRUVHnNCwMLFmyhH79+mGz2ULdFCGECAsSdAshRA+2dOlSDAaD9ysyMpIhQ4Zw8803k5+fH+rm+eWjjz7qMYG1HvD58hUOfve733HCCScwderUTj/3I488wrvvvtvp5+0sjX+HW/rq379/p7Xpqquuwm638/TTT3faOYUQIpyZQ90AIYQQofe73/2OnJwcamtr+eqrr3jqqaf46KOP2LJlC9HR0Z3allNOOYWamhoiIiL82u+jjz7iySefbDbwrqmpwWzuPv/yhg8fzksvvdTgsXvvvZfY2Fj+7//+L0Stal5hYSEvvPACL7zwQkjO/8gjj3DBBRcwf/78kJw/2E455ZQm74XrrruOSZMm8fOf/9z7WGxsbKe1KTIykiuvvJLHH3+cW265JWxu/gghRKh0n08gQggh2u2MM85g4sSJgPrAnpKSwuOPP857773HpZde2uw+VVVVxMTEBLwtRqORyMjIgB4z0McLtfT0dK644ooGj/3xj3+kV69eTR6vz+12Y7fbO/Xn8fLLL2M2mznrrLM67ZyaplFbW0tUVFSnnTNUBgwYwIABAxo89otf/IIBAwa0+l5wOp243W6/b2756qKLLuLPf/4zq1at4rTTTgvKOYQQoquQ7uVCCCGa0D8k79u3D1DdRWNjY9mzZw9z584lLi6Oyy+/HFCB3OLFixk5ciSRkZGkp6dzww03cOzYsQbH1DSN3//+9/Tt25fo6GhOPfVUtm7d2uTcLY2V/e6775g7dy5JSUnExMQwevRo/va3v3nb9+STTwI027W6uTHdGzZs4IwzziA+Pp7Y2FhOP/10vv322wbb6F13v/76axYuXEhqaioxMTGce+65FBYWtvozfPTRRzEYDBw4cKDJc/feey8RERHen9GuXbs4//zzycjIIDIykr59+3LJJZdQVlbW6jnaYjAYuPnmm3nllVcYOXIkVquVTz75pMWf8f79+zEYDCxdurTB49u3b+eCCy4gOTmZyMhIJk6cyPvvv+9TG959911OOOGEJplWX16z0+nk4YcfZuDAgVitVvr37899993XZKxw//79mTdvHp9++ikTJ04kKiqKp59+GoPBQFVVFS+88IL3PVG/fsDhw4e55pprSE9Px2q1MnLkSJ577rkmr+HQoUPMnz+fmJgY0tLSuOOOO/wer1xUVMRFF11EfHw8KSkp3HbbbdTW1nqfnzZtGmPGjGl236FDhzJ79my/zleffl0fffRRFi9e7P15/vTTT973+P79+xvs09rv4Zw5c0hISCA6Oppp06Y1O1Z/woQJJCcn895777W73UII0V1IplsIIUQTe/bsASAlJcX7mNPpZPbs2Zx00kk8+uij3m7nN9xwA0uXLuXqq6/m1ltvZd++ffzjH/9gw4YNfP3111gsFgDuv/9+fv/73zN37lzmzp3L+vXrmTVrFna7vc32LF++nHnz5pGZmcltt91GRkYG27Zt44MPPuC2227jhhtu4MiRIyxfvrxJV9vmbN26lZNPPpn4+HjuuusuLBYLTz/9NNOnT+fzzz/nhBNOaLD9LbfcQlJSEg888AD79+9n8eLF3Hzzzbz++ustnuOiiy7irrvu4o033uDXv/51g+feeOMNZs2aRVJSEna7ndmzZ2Oz2bjlllvIyMjg8OHDfPDBB5SWlpKQkNDm62nNZ599xhtvvMHNN99Mr1696N+/P6WlpT7vv3XrVqZOnUqfPn245557iImJ4Y033mD+/Pm8/fbbnHvuuS3u63A4+OGHH/jlL3/Z4HFfX/N1113HCy+8wAUXXMCdd97Jd999x6JFi9i2bRvvvPNOg2Pu2LGDSy+9lBtuuIHrr7+eoUOH8tJLLzXpaj1w4EAA8vPzOfHEE703JlJTU/n444+59tprKS8v5/bbbwfU0ITTTz+d3Nxcbr31Vnr37s1LL73EZ5995vPPENT7oX///ixatIhvv/2Wv//97xw7dowXX3wRgJ/97Gdcf/31bNmyheOOO8673w8//MDOnTv5zW9+49f5mvP8889TW1vLz3/+c6xWK8nJyX7t/9lnn3HGGWcwYcIEHnjgAYxGI88//zynnXYaX375JZMmTWqw/fjx47tV8TwhhGg3TQghRI/1/PPPa4C2YsUKrbCwUDt48KD22muvaSkpKVpUVJR26NAhTdM07corr9QA7Z577mmw/5dffqkB2iuvvNLg8U8++aTB4wUFBVpERIR25plnam6327vdfffdpwHalVde6X1s1apVGqCtWrVK0zRNczqdWk5Ojpadna0dO3aswXnqH+umm27SWvq3BmgPPPCA9/v58+drERER2p49e7yPHTlyRIuLi9NOOeWUJj+fGTNmNDjXHXfcoZlMJq20tLTZ8+kmT56sTZgwocFj33//vQZoL774oqZpmrZhwwYN0N58881Wj9WWkSNHatOmTWvwGKAZjUZt69atDR5v/DPW7du3TwO0559/3vvY6aefro0aNUqrra31PuZ2u7UpU6ZogwcPbrVNu3fv1gDtiSeeaPC4L69548aNGqBdd911DR7/1a9+pQHaZ5995n0sOztbA7RPPvmkyXFiYmIavL901157rZaZmakVFRU1ePySSy7REhIStOrqak3TNG3x4sUaoL3xxhvebaqqqrRBgwY1+zNs7IEHHtAA7eyzz27w+I033qgB2o8//qhpmqaVlpZqkZGR2t13391gu1tvvVWLiYnRKisrWz1PfY1fs35d4+PjtYKCggbb6u/xffv2NXi88XvE7XZrgwcP1mbPnt3gd6G6ulrLycnRZs6c2aQdP//5z7WoqCif2y2EEN2VdC8XQgjBjBkzSE1NJSsri0suuYTY2Fjeeecd+vTp02C7xhnLN998k4SEBGbOnElRUZH3a8KECcTGxrJq1SoAVqxYgd1ub1JUSc8mtmbDhg3s27eP22+/ncTExAbPtadAk8vlYtmyZcyfP7/BWNjMzEwuu+wyvvrqK8rLyxvs8/Of/7zBuU4++WRcLlezXcfru/jii1m3bp235wDA66+/jtVq5ZxzzgHwZnU//fRTqqur/X49bZk2bRojRoxo174lJSV89tlnXHTRRVRUVHivb3FxMbNnz2bXrl0cPny4xf2Li4sBSEpKavC4L6/5o48+AmDhwoUNHr/zzjsB+PDDDxs8npOT43MXbE3TePvttznrrLPQNK3Be3f27NmUlZWxfv16bzsyMzO54IILvPtHR0c3KFLmi5tuuqnB97fccov3+KB+Jueccw7/+c9/0DQNUO/V119/3du1vaPOP/98UlNT27Xvxo0b2bVrF5dddhnFxcXen1dVVRWnn346X3zxBW63u8E+SUlJ1NTUBOV9LYQQXYkE3UIIIXjyySdZvnw5q1at4qeffmLv3r1NAhiz2Uzfvn0bPLZr1y7KyspIS0sjNTW1wVdlZSUFBQUA3uB08ODBDfZPTU1tEpA1pges9bvcdkRhYSHV1dUMHTq0yXPDhw/H7XZz8ODBBo/369evwfd6mxuPW2/swgsvxGg0eruha5rGm2++6R1LDipYXLhwIc8++yy9evVi9uzZPPnkkx0ez63Lyclp9767d+9G0zR++9vfNrm+DzzwAID3GrdGDyLrt6mt13zgwAGMRiODBg1qsG9GRgaJiYlNbnj48zoLCwspLS3lmWeeafK6rr766gav68CBAwwaNKjJDZ7m3j+tafzeHzhwIEajscFY6gULFpCbm8uXX34JqJtV+fn5/OxnP/PrXC3pyHth165dAFx55ZVNfmbPPvssNputyXtWv+5SvVwI0dPJmG4hhBBMmjTJW728JVarFaOx4b1at9tNWloar7zySrP7tDerFm5MJlOzjzcOJhvr3bs3J598Mm+88Qb33Xcf3377Lbm5ufzpT39qsN1jjz3GVVddxXvvvceyZcu49dZbvWN/G9/o8FdzFbxbCoJcLleD7/XM5a9+9asWs8iNg+L69JoAzd2c8PU1+xqw+VOpXH9dV1xxBVdeeWWz24wePdrn47VHc69r9uzZpKen8/LLL3PKKafw8ssvk5GRwYwZMwJyzkC8F/7yl78wduzYZvdpXCzv2LFjREdH94gq8kII0RoJuoUQQrTbwIEDWbFiBVOnTm31g3V2djagsmX1u3QXFha2mS3WC19t2bKl1eDD1+AsNTWV6OhoduzY0eS57du3YzQaycrK8ulYvrj44ou58cYb2bFjB6+//jrR0dHNTp81atQoRo0axW9+8xvWrFnD1KlTWbJkCb///e8D1hadnqlvXFCtcfZYv1YWi6VdgV+/fv2IioryVsFvrLXXnJ2djdvtZteuXQwfPty7T35+PqWlpd73VFuae1+kpqYSFxeHy+Vq83VlZ2ezZcsWNE1rcKzm3j+t2bVrV4NM8+7du3G73fTv39/7mMlk4rLLLmPp0qX86U9/4t133+X6669v8aZPIPj6XtB/D+Pj431+L+zbt6/BtRNCiJ5KupcLIYRot4suugiXy8XDDz/c5Dmn0+n9ID9jxgwsFgtPPPFEg+zw4sWL2zzH+PHjycnJYfHixU0Cg/rH0se8tlWZ22QyMWvWLN57770GXXvz8/N59dVXOemkk7xdvwPh/PPPx2Qy8Z///Ic333yTefPmNRifW15ejtPpbLDPqFGjMBqNfk9L5avs7GxMJhNffPFFg8f/+c9/Nvg+LS2N6dOn8/TTT3P06NEmx2lr2jSLxcLEiRNZu3Ztg8d9ec1z584Fmr5HHn/8cQDOPPPMVs+ti4mJafKeMJlMnH/++bz99tts2bKlyT71X9fcuXM5cuQIb731lvex6upqnnnmGZ/Or9OntNM98cQTAJxxxhkNHv/Zz37GsWPHuOGGG6isrGx1ru1A0IPp+u8Fl8vV5PVNmDCBgQMH8uijj1JZWdnkOM29F9avX8+UKVMC3GIhhOh6JNMthBCi3aZNm8YNN9zAokWL2LhxI7NmzcJisbBr1y7efPNN/va3v3HBBReQmprKr371KxYtWsS8efOYO3cuGzZs4OOPP6ZXr16tnsNoNPLUU09x1llnMXbsWK6++moyMzPZvn07W7du5dNPPwVUUABw6623Mnv2bEwmE5dcckmzx/z973/P8uXLOemkk7jxxhsxm808/fTT2Gw2/vznPwf0Z5SWlsapp57K448/TkVFBRdffHGD5z/77DNuvvlmLrzwQoYMGYLT6eSll17yBobBkJCQwIUXXsgTTzyBwWBg4MCBfPDBB82Oz37yySc56aSTGDVqFNdffz0DBgwgPz+fb775hkOHDvHjjz+2eq5zzjmH//u//6O8vNx7M8OX1zxmzBiuvPJKnnnmGUpLS5k2bRrff/89L7zwAvPnz+fUU0/16bVOmDCBFStW8Pjjj9O7d29ycnI44YQT+OMf/8iqVas44YQTuP766xkxYgQlJSWsX7+eFStWUFJSAsD111/PP/7xDxYsWMC6devIzMzkpZde8k6Z56t9+/Zx9tlnM2fOHL755htefvllLrvssiZzc48bN47jjjuON998k+HDhzN+/Hi/zuOvkSNHcuKJJ3LvvfdSUlJCcnIyr732WpObIkajkWeffZYzzjiDkSNHcvXVV9OnTx8OHz7MqlWriI+P53//+593+3Xr1lFSUuItGCiEED1aiKqmCyGECAP6dEE//PBDq9tdeeWVWkxMTIvPP/PMM9qECRO0qKgoLS4uThs1apR21113aUeOHPFu43K5tIceekjLzMzUoqKitOnTp2tbtmzRsrOzW50yTPfVV19pM2fO1OLi4rSYmBht9OjRDaaicjqd2i233KKlpqZqBoOhwfRhNJoyTNM0bf369drs2bO12NhYLTo6Wjv11FO1NWvW+PTzaamNLfnXv/6lAVpcXJxWU1PT4Lm9e/dq11xzjTZw4EAtMjJSS05O1k499VRtxYoVPh1b19KUYTfddFOz2xcWFmrnn3++Fh0drSUlJWk33HCDtmXLliZThmmapu3Zs0dbsGCBlpGRoVksFq1Pnz7avHnztLfeeqvNduXn52tms1l76aWX/H7NDodDe+ihh7ScnBzNYrFoWVlZ2r333ttg+jJNU1OGnXnmmc2ef/v27dopp5yiRUVFNZmeLj8/X7vpppu0rKwszWKxaBkZGdrpp5+uPfPMMw2OceDAAe3ss8/WoqOjtV69emm33Xabd1o8X6cM++mnn7QLLrhAi4uL05KSkrSbb765yXtB9+c//1kDtEceeaTVY7ekpSnD/vKXvzS7/Z49e7QZM2ZoVqtVS09P1+677z5t+fLlzb6+DRs2aOedd56WkpKiWa1WLTs7W7vooou0lStXNtju7rvv1vr169dgejEhhOipDJrWRhUYIYQQQogOuPbaa9m5c6e3Krdo3d/+9jfuuOMO9u/f36Ryfldgs9no378/99xzD7fddluomyOEECEnQbcQQgghgio3N5chQ4awcuVKpk6dGurmhDVN0xgzZgwpKSneee67miVLlvDII4+wa9curFZrqJsjhBAhJ0G3EEIIIUSIVVVV8f7777Nq1Sr+9a9/8d5773H22WeHullCCCECQIJuIYQQQogQ279/Pzk5OSQmJnLjjTfyhz/8IdRNEkIIESASdAshhBBCCCGEEEEi83QLIYQQQgghhBBBIkG3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2ev1GpmZmcbvf/97/2WFhYVGdHS08e9//zsMI5T9+/cbgLFw4ULDMMz5cLlcxmuvveY/Zv369QZgLFmyJFzD7PZ69OhhPPPMM5qfCFJSUmIMHTrUmD9/vnHSSScZN954o2EYeg+F2z333GOMHTu20es0N5Hh9ttvN44//vgmr9dnhchy4403GoMHDza8Xq/eQxHgzDPPNC6//PJ6l51zzjnGxRdfbBhG5L9/lOnuxKqrq1m+fDnTp0/3X2a325k+fTpLliwJ48ikoW3btpGfn19vrpKTk5k8ebLmKkyKiooASE1NBWD58uW43e56c5STk0O/fv00R2Hg8Xh4+eWXKSsrY8qUKZqfCHLddddx5pln1psL0HsoEmzatImsrCwGDRrExRdfzM6dOwHNTaR4++23mTRpEueddx49e/Zk/PjxPP300/7r9VkhclRXV/PCCy9w+eWXY7PZ9B6KAMcddxwLFixg48aNAKxatYrFixdzxhlnAJH//nGGewDSdgcPHsTj8dCrV696l/fq1YsNGzaEaVTSmPz8fIBG58q6TjqO1+vlpptuYurUqYwaNQow5ygqKoqUlJR6x2qOOtZ3333HlClTqKysJCEhgTfeeIMRI0awcuVKzU8EePnll/n2229ZunTpEdfpPRRekydPZt68eQwbNoy9e/dy3333ccIJJ7BmzRrNTYTYunUrTz75JLfccgu//OUvWbp0KTfccANRUVFcdtll+qwQQd58800KCwuZO3cuoL9vkeCOO+6guLiYnJwcHA4HHo+HBx54gIsvvhiI/M/aCrpFpNu57rrrWLNmTb31jhIZhg0bxsqVKykqKuL111/nsssuY+HCheEelgC7du3ixhtvZP78+cTExIR7ONKAle0BGDNmDJMnT6Z///68+uqrxMbGhnFkYvF6vUyaNInf/va3AIwfP541a9bw1FNPcdlll4V5dFLXP/7xD8444wyysrLCPRTxefXVV3nxxRd56aWXGDlyJCtXruSmm24iKyurU7x/VF7eiaWnp+NwOI7onLhv3z4yMzPDNCppjDUfmqvwu/7663n33Xf57LPP6Nu3r//yzMxMqqurKSwsrHe85qhjRUVFMWTIECZOnMiDDz7I2LFj+fOf/6z5iQDLly9n//79TJgwAafTidPpZOHChTz22GM4nU569eqlOYogKSkpHHXUUWzevFnvnwjRu3dvRowYUe+y4cOH+5cB6LNCZNixYweffPIJV155pf8yvYfC7xe/+AV33HEHP/zhDxk9ejSXXHIJN998Mw8++CAQ+e8fBd2dWFRUFBMnTmTBggX+y7xeLwsWLGDKlClhHJk0NHDgQDIzM+vNVXFxMV9//bXmqoMYhsH111/PG2+8waeffsrAgQPrXT9x4kRcLle9OcrNzWXnzp2aozDyer1UVVVpfiLAtGnT+O6771i5cqX/36RJk7j44ov95zVHkaO0tJQtW7bQu3dvvX8ixNSpU4/YqnLjxo30798f0GeFSPHss8/Ss2dPzjzzTP9leg+FX3l5OXZ7/dDV4XDg9XqBTvD+CXcnN2mfl19+2YiOjjbmzZtnrFu3zrj66quNlJQUIz8/P9xD63ZKSkqMFStWGCtWrDAA45FHHjFWrFhh7NixwzAMw/jd735npKSkGG+99ZaxevVqY/bs2cbAgQONioqKMI+8e7j22muN5ORk4/PPPzf27t3r/1deXu4/5ic/+YnRr18/49NPPzWWLVtmTJkyxZgyZUoYR9293HHHHcbChQuNbdu2GatXrzbuuOMOw2azGR9//LFhGJqfSFS3e7lhaI7C6dZbbzU+//xzY9u2bcYXX3xhTJ8+3UhPTzf2799vGIbmJhJ88803htPpNB544AFj06ZNxosvvmjExcUZL7zwgv8YfVYIL4/HY/Tr18+4/fbbj7hO76Hwuuyyy4w+ffoY7777rrFt2zbjv//9r5Genm7cdttt/mMi+f2joLsLePzxx41+/foZUVFRxjHHHGN89dVX4R5St/TZZ58ZwBH/LrvsMsMwzK0M7rrrLqNXr15GdHS0MW3aNCM3Nze8g+5GGpsbwHj22Wf9x1RUVBg//elPjR49ehhxcXHG2Wefbezduzd8g+5mLr/8cqN///5GVFSUkZGRYUybNs0fcBuG5icSNQy6NUfhc8EFFxi9e/c2oqKijD59+hgXXHCBsXnzZv/1mpvI8M477xijRo0yoqOjjZycHOPvf/97vev1WSG8PvroIwNo9DnXeyi8iouLjRtvvNHo16+fERMTYwwaNMj4v//7P6Oqqsp/TCS/f2yGYRhhSbGLiIiIiIiIdHFa0y0iIiIiIiISIgq6RUREREREREJEQbeIiIiIiIhIiCjoFhEREREREQkRBd0iIiIiIiIiIaKgW0RERERERCREFHSLiIiIiIiIhIiCbhEREREREZEQUdAtIiISwWw2G2+++Wa4h8G9997LuHHjQv44BQUF9OzZk+3bt4f8sYJlwIABPProowBUV1czYMAAli1bFt5BiYhIxFDQLSIi3dqBAwe49tpr6devH9HR0WRmZjJjxgy++OKLcA8tKLZv347NZmPlypXhHkqrPPDAA8yePZsBAwaE9HHqBsrBFBUVxc9//nNuv/32oN+3iIh0Ts5wD0BERCSc5syZQ3V1Nc899xyDBg1i3759LFiwgIKCgnAPrdspLy/nH//4Bx999FHIHqO6upqoqKiQ3T/AxRdfzK233sratWsZOXJkSB9LREQinzLdIiLSbRUWFrJo0SIeeughTjnlFPr3788xxxzDnXfeyfe//33/cY888gijR48mPj6e7OxsfvrTn1JaWuq/ft68eaSkpPDuu+8ybNgw4uLiOPfccykvL+e5555jwIAB9OjRgxtuuAGPx+O/3YABA7j//vu58MILiY+Pp0+fPjzxxBPNjnnXrl2cf/75pKSkkJqayuzZswMqxf7888+x2WwsWLCASZMmERcXx3HHHUdubm694373u9/Rq1cvEhMTueKKK6isrDzivp555hmGDx9OTEwMOTk5/PWvf/Vfd/nllzNmzBiqqqoAM9gdP348l156aZNje//994mOjubYY4/1X3b48GEuvvhiMjIyiI2NZejQoTz77LP+67/77jtOPfVUYmNjSUtL4+qrr643N3PnzuUHP/gBDzzwAFlZWQwbNoyTTz6ZHTt2cPPNN2Oz2bDZbP7jFy9ezAknnEBsbCzZ2dnccMMNlJWV+a/fv38/Z511FrGxsQwcOJAXX3zxiN+jR48eTJ06lZdffrnJ31VERLoPBd0iItJtJSQkkJCQwJtvvukPDhtjt9t57LHHWLt2Lc899xyffvopt912W71jysvLeeyxx3j55Zf58MMP+fzzzzn77LN5//33ef/99/nXv/7F3/72N15//fV6t/v973/P2LFjWbFiBXfccQc33ngj8+fPb3QcbrebGTNmkJiYyKJFi/jiiy9ISEhg5syZVFdXB/S7/9///R9//OMfWbZsGU6nk8svv9x/3auvvsq9997Lb3/7W5YtW0bv3r3rBdQAL774InfffTcPPPAA69ev57e//S133XUXzz33HACPPfYYZWVl3HHHHf7HKyws5C9/+UuTY1q0aBETJ06sd9ldd93FunXr+OCDD1i/fj1PPvkk6enpAJSVlTFjxgx69OjB0qVLee211/jkk0+4/vrr693HggULyM3NZf78+bz77rv897//pW/fvvz6179m79697N27F4AtW7Ywc+ZM5syZw+rVq3nllVdYvHhxvfubO3cuu3bt4rPPPuP111/nr3/9K/v37z/idznmmGNYtGhRi/MgIiLdgCEiItKNvf7660aPHj2MmJgY47jjjjPuvPNOY9WqVc3e5rXXXjPS0tL8Pz/77LMGYGzevNl/2TXXXGPExcUZJSUl/stmzJhhXHPNNf6f+/fvb8ycObPefV9wwQXGGWec4f8ZMN544w3DMAzjX//6lzFs2DDD6/X6r6+qqjJiY2ONjz76qNGxbtu2zQCMFStWGIZhGJ999pkBGJ988on/mPfee88AjIqKCsMwDGPKlCnGT3/603r3M3nyZGPs2LH+nwcPHmy89NJL9Y65//77jSlTpvh//vLLLw2Xy2XcddddhtPpNBYtWtToGC2zZ882Lr/88nqXnXXWWcaPf/zjRo//+9//bvTo0cMoLS2t97vY7XYjPz/fMAzDuOyyy4xevXoZVVVV9W7bv39/409/+lO9y6644grj6quvrnfZokWLDLvdblRUVBi5ubkGYHzzzTf+69evX28AR9zXn//8Z2PAgAHN/r4iItI9KNMtIiLd2pw5c9izZw9vv/02M2fO5PPPP2fChAnMmzfPf8wnn3zCtGnT6NOnD4mJiVxyySUUFBRQXl7uPyYuLo7Bgwf7f+7VqxcDBgwgISGh3mUNs6JTpkw54uf169c3OtZVq1axefNmEhMT/Vn61NRUKisr2bJlS0C/95gxY/zne/fuDeAf2/r165k8eXKT4ywrK2PLli1cccUV/nEkJCTwm9/8pt44pkyZws9//nPuv/9+br31Vo4//vhmx1RRUUFMTEy9y6699lpefvllxo0bx2233caXX37pv279+vWMHTuW+Ph4/2VTp07F6/XWK5cfPXp0q9Zxr1q1innz5tX7nWbMmIHX62Xbtm2sX78ep9NZLxufk5NDSkrKEfcVGxtb7/UhIiLdlxqpiYhItxcTE8Npp53Gaaedxl133cWVV17JPffcw9y5rRrDmAAABU1JREFUc9m+fTvf+973uPbaa3nggQdITU1l8eLFXHHFFVRXVxMXFweAy+Wqd582m63Ry7xeb5vHWVpaysSJExtdR5yRkRHQfdUdm7WmubVjs9ZMP/3000cE5w6Hw3/e6/XyxRdf4HA42Lx5c4v3m56ezuHDh+tddsYZZ7Bjxw7ef/995s+fz7Rp07juuuv4wx/+0KqxAvWC8uaUlpZyzTXXcMMNNxxxXb9+/di4cWOrH/PQoUMBz4mIiHRNynSLiIg0MGLECH/zrOXLl+P1evnjH//Isccey1FHHcWePXuC9lhfffXVET8PHz680WMnTJjApk2b6NmzJ0OGDKn3Lzk5OWhjGj58OF9//XWT4+zVqxdZWVls3br1iHEMHDjQf9zvf/97NmzYwMKFC/nwww/rNUBrzPjx41m3bt0Rl2dkZHDZZZfxwgsv8Oijj/L3v//dP85Vq1bVa3T2xRdfYLfbGTZsWLOPFRUVVa+pHZjP77p16474nYYMGUJUVBQ5OTnU1NSwfPly/21yc3MpLCw84v7XrFnD+PHjmx2DiIh0Dwq6RUSk2yooKODUU0/lhRdeYPXq1Wzbto3XXnuNhx9+mNmzZwMwZMgQ3G43jz/+OFu3buVf//oXTz31VNDG8MUXX/Dwww+zceNGnnjiCV577TVuvPHGRo+9+OKLSU9PZ/bs2SxatIht27bx+eefc8MNN7B79+6gjenGG2/kn//8J88++ywbN27knnvuYe3atfWOue+++3jwwQd57LHH2LhxI9999x3PPvssjzzyCAArVqzg7rvv5plnnmHq1Kk88sgj3HjjjWzdurXJx50xYwZr166tl+2+++67eeutt9i8eTNr167l3Xff9X8pcfHFFxMTE8Nll13GmjVr+Oyzz/jZz37GJZdcQq9evZr9HQcMGMD//vc/8vLyOHjwIAC33347X375Jddffz0rV65k06ZNvPXWW/5GasOGDWPmzJlcc801fP311yxfvpwrr7yS2NjYI+5/0aJFnH766a14tkVEpKtT0C0iIt1WQkICkydP5k9/+hMnnngio0aN4q677uKqq67yd9keO3YsjzzyCA899BCjRo3ixRdf5MEHHwzaGG699VaWLVvG+PHj+c1vfsMjjzzCjBkzGj02Li6O//3vf/Tr149zzjmH4cOH+7fzSkpKCtqYLrjgAu666y5uu+02Jk6cyI4dO7j22mvrHXPllVfyzDPP8OyzzzJ69GhOOukk5s2bx8CBA6msrORHP/oRc+fO5ayzzgLg6quv5pRTTuGSSy45IsNsGT16NBMmTODVV1/1XxYVFcWdd97JmDFjOPHEE3E4HP6tuOLi4vjoo484dOgQRx99NOeeey7Tpk1rtkO65de//jXbt29n8ODB/jLwMWPGsHDhQjZu3MgJJ5zA+PHjufvuu8nKyvLf7tlnnyUrK4uTTjqJc845h6uvvpqePXvWu+8lS5ZQVFTEueee24pnW0REujqbYRhGuAchIiLSHQ0YMICbbrqJm266KdxDiRjvvfcev/jFL1izZg12e+fMDVxwwQWMHTuWX/7yl+EeioiIRAA1UhMREZGIceaZZ7Jp0yby8vLIzs4O93ACVl1dzejRo7n55pvDPRQREYkQCrpFREQkonTmzH9UVBS/+tWvwj0MERGJICovFxEREREREQmRzrlYSkRERERERKQTUNAtIiIiIiIiEiIKukVERERERERCREG3iIiIiIiISIgo6BYREREREREJEQXdIiIiIiIiIiGioFtEREREREQkRBR0i4iIiIiIiISIgm4RERERERGREPl/Dgf8lRZIIk0AAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Test MSE: 0.406312\n", - "R² Score: 0.6368\n", - "Epoch 126 | Loss: 0.025907\n", - "Epoch 127 | Loss: 0.027972\n", - "Epoch 128 | Loss: 0.025494\n", - "Epoch 129 | Loss: 0.019651\n", - "Epoch 130 | Loss: 0.027020\n", - "Epoch 131 | Loss: 0.037656\n", - "Epoch 132 | Loss: 0.028049\n", - "Epoch 133 | Loss: 0.030038\n", - "Epoch 134 | Loss: 0.029521\n", - "Epoch 135 | Loss: 0.030721\n", - "Epoch 136 | Loss: 0.024325\n" - ] - }, - { - "ename": "KeyboardInterrupt", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[10], line 127\u001b[0m\n\u001b[1;32m 124\u001b[0m y_batch \u001b[38;5;241m=\u001b[39m y_batch\u001b[38;5;241m.\u001b[39mto(device)\u001b[38;5;241m.\u001b[39mfloat()\u001b[38;5;241m.\u001b[39mview(\u001b[38;5;241m-\u001b[39m\u001b[38;5;241m1\u001b[39m, \u001b[38;5;241m1\u001b[39m) \u001b[38;5;66;03m# (B,1)\u001b[39;00m\n\u001b[1;32m 126\u001b[0m optimizer\u001b[38;5;241m.\u001b[39mzero_grad()\n\u001b[0;32m--> 127\u001b[0m pred \u001b[38;5;241m=\u001b[39m \u001b[43mmodel\u001b[49m\u001b[43m(\u001b[49m\u001b[43msample_views_list\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# (B,1)\u001b[39;00m\n\u001b[1;32m 128\u001b[0m loss \u001b[38;5;241m=\u001b[39m criterion(pred, y_batch)\n\u001b[1;32m 129\u001b[0m loss\u001b[38;5;241m.\u001b[39mbackward()\n", - "File \u001b[0;32m~/miniconda3/envs/QEC2/lib/python3.10/site-packages/torch/nn/modules/module.py:1739\u001b[0m, in \u001b[0;36mModule._wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1737\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_compiled_call_impl(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs) \u001b[38;5;66;03m# type: ignore[misc]\u001b[39;00m\n\u001b[1;32m 1738\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1739\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_call_impl\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/miniconda3/envs/QEC2/lib/python3.10/site-packages/torch/nn/modules/module.py:1750\u001b[0m, in \u001b[0;36mModule._call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1745\u001b[0m \u001b[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001b[39;00m\n\u001b[1;32m 1746\u001b[0m \u001b[38;5;66;03m# this function, and just call forward.\u001b[39;00m\n\u001b[1;32m 1747\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_pre_hooks\n\u001b[1;32m 1748\u001b[0m \u001b[38;5;129;01mor\u001b[39;00m _global_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_backward_hooks\n\u001b[1;32m 1749\u001b[0m \u001b[38;5;129;01mor\u001b[39;00m _global_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_forward_pre_hooks):\n\u001b[0;32m-> 1750\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mforward_call\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1752\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m 1753\u001b[0m called_always_called_hooks \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mset\u001b[39m()\n", - "Cell \u001b[0;32mIn[10], line 85\u001b[0m, in \u001b[0;36mQECRegressor.forward\u001b[0;34m(self, sample_views_list)\u001b[0m\n\u001b[1;32m 84\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21mforward\u001b[39m(\u001b[38;5;28mself\u001b[39m, sample_views_list):\n\u001b[0;32m---> 85\u001b[0m z \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43membedder\u001b[49m\u001b[43m(\u001b[49m\u001b[43msample_views_list\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# (B, d_model)\u001b[39;00m\n\u001b[1;32m 86\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mhead(z)\n", - "File \u001b[0;32m~/miniconda3/envs/QEC2/lib/python3.10/site-packages/torch/nn/modules/module.py:1739\u001b[0m, in \u001b[0;36mModule._wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1737\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_compiled_call_impl(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs) \u001b[38;5;66;03m# type: ignore[misc]\u001b[39;00m\n\u001b[1;32m 1738\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1739\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_call_impl\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/miniconda3/envs/QEC2/lib/python3.10/site-packages/torch/nn/modules/module.py:1750\u001b[0m, in \u001b[0;36mModule._call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1745\u001b[0m \u001b[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001b[39;00m\n\u001b[1;32m 1746\u001b[0m \u001b[38;5;66;03m# this function, and just call forward.\u001b[39;00m\n\u001b[1;32m 1747\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_pre_hooks\n\u001b[1;32m 1748\u001b[0m \u001b[38;5;129;01mor\u001b[39;00m _global_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_backward_hooks\n\u001b[1;32m 1749\u001b[0m \u001b[38;5;129;01mor\u001b[39;00m _global_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_forward_pre_hooks):\n\u001b[0;32m-> 1750\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mforward_call\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1752\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m 1753\u001b[0m called_always_called_hooks \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mset\u001b[39m()\n", - "File \u001b[0;32m/mnt/c/Imeperial_MRes_Files/QEC/BO_for_QECcodes/bayesian_optimization/chaincomplexembedding.py:1041\u001b[0m, in \u001b[0;36mChainComplexEmbedder.forward\u001b[0;34m(self, views_or_list, batch_splits)\u001b[0m\n\u001b[1;32m 1039\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m vn \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m views:\n\u001b[1;32m 1040\u001b[0m \u001b[38;5;28;01mcontinue\u001b[39;00m\n\u001b[0;32m-> 1041\u001b[0m outs_per_view[vn] \u001b[38;5;241m=\u001b[39m \u001b[43mper_view\u001b[49m\u001b[43m[\u001b[49m\u001b[43mvn\u001b[49m\u001b[43m]\u001b[49m\u001b[43m(\u001b[49m\u001b[43mH\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mviews\u001b[49m\u001b[43m[\u001b[49m\u001b[43mvn\u001b[49m\u001b[43m]\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1043\u001b[0m \u001b[38;5;66;03m# Cross-view aggregation -> new H (shapes are identical across views per type)\u001b[39;00m\n\u001b[1;32m 1044\u001b[0m H_new: Dict[\u001b[38;5;28mstr\u001b[39m, torch\u001b[38;5;241m.\u001b[39mTensor] \u001b[38;5;241m=\u001b[39m {}\n", - "File \u001b[0;32m~/miniconda3/envs/QEC2/lib/python3.10/site-packages/torch/nn/modules/module.py:1739\u001b[0m, in \u001b[0;36mModule._wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1737\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_compiled_call_impl(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs) \u001b[38;5;66;03m# type: ignore[misc]\u001b[39;00m\n\u001b[1;32m 1738\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1739\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_call_impl\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/miniconda3/envs/QEC2/lib/python3.10/site-packages/torch/nn/modules/module.py:1750\u001b[0m, in \u001b[0;36mModule._call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1745\u001b[0m \u001b[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001b[39;00m\n\u001b[1;32m 1746\u001b[0m \u001b[38;5;66;03m# this function, and just call forward.\u001b[39;00m\n\u001b[1;32m 1747\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_pre_hooks\n\u001b[1;32m 1748\u001b[0m \u001b[38;5;129;01mor\u001b[39;00m _global_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_backward_hooks\n\u001b[1;32m 1749\u001b[0m \u001b[38;5;129;01mor\u001b[39;00m _global_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_forward_pre_hooks):\n\u001b[0;32m-> 1750\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mforward_call\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1752\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m 1753\u001b[0m called_always_called_hooks \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mset\u001b[39m()\n", - "File \u001b[0;32m/mnt/c/Imeperial_MRes_Files/QEC/BO_for_QECcodes/bayesian_optimization/chaincomplexembedding.py:624\u001b[0m, in \u001b[0;36mChainComplexMessagePassingLayer.forward\u001b[0;34m(self, H, view)\u001b[0m\n\u001b[1;32m 622\u001b[0m x \u001b[38;5;241m=\u001b[39m H[t] \u001b[38;5;66;03m# (N_t_global, in_dim)\u001b[39;00m\n\u001b[1;32m 623\u001b[0m x_norm \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mln_in[t](x)\n\u001b[0;32m--> 624\u001b[0m proj_dst[t] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mres_proj\u001b[49m\u001b[43m[\u001b[49m\u001b[43mt\u001b[49m\u001b[43m]\u001b[49m\u001b[43m(\u001b[49m\u001b[43mx_norm\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# (N_t_global, out_dim)\u001b[39;00m\n\u001b[1;32m 626\u001b[0m \u001b[38;5;66;03m# Iterate relations\u001b[39;00m\n\u001b[1;32m 627\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m r \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mrelations:\n", - "File \u001b[0;32m~/miniconda3/envs/QEC2/lib/python3.10/site-packages/torch/nn/modules/module.py:1739\u001b[0m, in \u001b[0;36mModule._wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1737\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_compiled_call_impl(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs) \u001b[38;5;66;03m# type: ignore[misc]\u001b[39;00m\n\u001b[1;32m 1738\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1739\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_call_impl\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/miniconda3/envs/QEC2/lib/python3.10/site-packages/torch/nn/modules/module.py:1750\u001b[0m, in \u001b[0;36mModule._call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1745\u001b[0m \u001b[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001b[39;00m\n\u001b[1;32m 1746\u001b[0m \u001b[38;5;66;03m# this function, and just call forward.\u001b[39;00m\n\u001b[1;32m 1747\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_pre_hooks\n\u001b[1;32m 1748\u001b[0m \u001b[38;5;129;01mor\u001b[39;00m _global_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_backward_hooks\n\u001b[1;32m 1749\u001b[0m \u001b[38;5;129;01mor\u001b[39;00m _global_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_forward_pre_hooks):\n\u001b[0;32m-> 1750\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mforward_call\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1752\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m 1753\u001b[0m called_always_called_hooks \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mset\u001b[39m()\n", - "File \u001b[0;32m~/miniconda3/envs/QEC2/lib/python3.10/site-packages/torch/nn/modules/linear.py:125\u001b[0m, in \u001b[0;36mLinear.forward\u001b[0;34m(self, input)\u001b[0m\n\u001b[1;32m 124\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21mforward\u001b[39m(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;28minput\u001b[39m: Tensor) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Tensor:\n\u001b[0;32m--> 125\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mF\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mlinear\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mweight\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbias\u001b[49m\u001b[43m)\u001b[49m\n", - "\u001b[0;31mKeyboardInterrupt\u001b[0m: " - ] - } - ], + "outputs": [], "source": [ "import math\n", - "import numpy as np\n", "import torch\n", "import torch.nn as nn\n", "from torch.utils.data import DataLoader\n", - "import matplotlib.pyplot as plt\n", - "from bayesian_optimization.chaincomplexembedding import *\n", + "from bayesian_optimization.chaincomplexembedding import ChainComplexEmbedder\n", + "\n", + "\n", "@torch.no_grad()\n", "def show_results(model, data_loader, encoder: E, device: torch.device):\n", " model.eval()\n", @@ -1113,8 +573,8 @@ " p_sorted = [t[1] for t in paired]\n", "\n", " plt.figure(figsize=(10, 5))\n", - " plt.plot(y_sorted, label='True')\n", - " plt.plot(p_sorted, label='Predicted')\n", + " plt.plot(y_sorted, label=\"True\")\n", + " plt.plot(p_sorted, label=\"Predicted\")\n", " plt.title(\"Prediction vs True (sorted by True)\")\n", " plt.xlabel(\"Sample Index (sorted)\")\n", " plt.ylabel(\"Value\")\n", @@ -1131,21 +591,24 @@ "\n", " print(f\"Test MSE: {mse:.6f}\")\n", " print(f\"R² Score: {R2:.4f}\")\n", + "\n", + "\n", "# 1) Build the embedder (uses the same views_info you defined)\n", "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n", "\n", "embedder = ChainComplexEmbedder(\n", " views_info=views_info,\n", - " d_model=128, # you can tune\n", - " num_layers=4, # you can tune\n", + " d_model=128, # you can tune\n", + " num_layers=4, # you can tune\n", " view_aggr=\"sum\",\n", - " num_bases=4, # basis decomposition for relation weights\n", + " num_bases=4, # basis decomposition for relation weights\n", " norm=\"sym\",\n", " residual=True,\n", " self_loop=False,\n", " dropout=0.1,\n", ").to(device)\n", "\n", + "\n", "# 2) Add a small regression head on top of the embedding (B, d_model) -> (B, 1)\n", "class QECRegressor(nn.Module):\n", " def __init__(self, embedder: nn.Module, d_model: int = 128):\n", @@ -1158,30 +621,40 @@ " nn.Dropout(0.1),\n", " nn.Linear(128, 1),\n", " )\n", + "\n", " def forward(self, sample_views_list):\n", " z = self.embedder(sample_views_list) # (B, d_model)\n", - " return self.head(z) # (B, 1)\n", + " return self.head(z) # (B, 1)\n", + "\n", "\n", "model = QECRegressor(embedder, d_model=128).to(device)\n", "\n", + "\n", "# 3) Loss and optimizer\n", "class LogCoshLoss(nn.Module):\n", " def forward(self, y_pred, y_true):\n", " # y_pred, y_true: (B,) or (B,1)\n", " diff = y_pred - y_true\n", " # log(cosh(x)) = x + softplus(-2x) - ln(2)\n", - " return torch.mean(diff + torch.nn.functional.softplus(-2.0 * diff) - math.log(2.0))\n", + " return torch.mean(\n", + " diff + torch.nn.functional.softplus(-2.0 * diff) - math.log(2.0)\n", + " )\n", + "\n", "\n", "criterion = LogCoshLoss()\n", "optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)\n", "\n", + "\n", "# 4) Data\n", "def custom_collate(batch):\n", " xs, ys = zip(*batch)\n", " return list(xs), torch.tensor(ys, dtype=torch.float32)\n", "\n", - "train_loader = DataLoader(train_dataset, batch_size=8, shuffle=True,collate_fn=custom_collate)\n", - "test_loader = DataLoader(test_dataset, batch_size=32,collate_fn=custom_collate)\n", + "\n", + "train_loader = DataLoader(\n", + " train_dataset, batch_size=8, shuffle=True, collate_fn=custom_collate\n", + ")\n", + "test_loader = DataLoader(test_dataset, batch_size=32, collate_fn=custom_collate)\n", "\n", "# Your encoder wrapper (already defined in your message)\n", "# para_dict, codeconstructor are already defined as you said.\n", @@ -1197,11 +670,13 @@ " for x_batch, y_batch in train_loader:\n", " # x_batch: whatever your dataset yields (e.g., adjacency tensors or parameters)\n", " # encoder.encode(x_batch) -> List[views dict] of length B\n", - " sample_views_list = encoder.encode(x_batch) # DO NOT .to(device); it's a list of dicts\n", - " y_batch = y_batch.to(device).float().view(-1, 1) # (B,1)\n", + " sample_views_list = encoder.encode(\n", + " x_batch\n", + " ) # DO NOT .to(device); it's a list of dicts\n", + " y_batch = y_batch.to(device).float().view(-1, 1) # (B,1)\n", "\n", " optimizer.zero_grad()\n", - " pred = model(sample_views_list) # (B,1)\n", + " pred = model(sample_views_list) # (B,1)\n", " loss = criterion(pred, y_batch)\n", " loss.backward()\n", " optimizer.step()\n", @@ -1210,9 +685,9 @@ "\n", " avg = total_loss / len(train_loader)\n", " history.append(avg)\n", - " if epoch%25 == 0 and epoch>=1:\n", - " show_results(model, test_loader, encoder, device)\n", - " print(f\"Epoch {epoch+1:>3d} | Loss: {avg:.6f}\")\n", + " if epoch % 25 == 0 and epoch >= 1:\n", + " show_results(model, test_loader, encoder, device)\n", + " print(f\"Epoch {epoch + 1:>3d} | Loss: {avg:.6f}\")\n", "\n", "plt.figure()\n", "plt.plot(history)\n", @@ -1222,6 +697,7 @@ "plt.tight_layout()\n", "plt.show()\n", "\n", + "\n", "# 6) Evaluation helper (MSE / R^2 + a visual check)\n", "@torch.no_grad()\n", "def show_results(model, data_loader, encoder: E, device: torch.device):\n", @@ -1254,8 +730,8 @@ " p_sorted = [t[1] for t in paired]\n", "\n", " plt.figure(figsize=(10, 5))\n", - " plt.plot(y_sorted, label='True')\n", - " plt.plot(p_sorted, label='Predicted')\n", + " plt.plot(y_sorted, label=\"True\")\n", + " plt.plot(p_sorted, label=\"Predicted\")\n", " plt.title(\"Prediction vs True (sorted by True)\")\n", " plt.xlabel(\"Sample Index (sorted)\")\n", " plt.ylabel(\"Value\")\n", @@ -1273,14 +749,15 @@ " print(f\"Test MSE: {mse:.6f}\")\n", " print(f\"R² Score: {R2:.4f}\")\n", "\n", + "\n", "# 7) Run evaluation on train/test\n", "show_results(model, train_loader, encoder, device)\n", - "show_results(model, test_loader, encoder, device)\n" + "show_results(model, test_loader, encoder, device)" ] }, { "cell_type": "markdown", - "id": "71e8b05b", + "id": "9", "metadata": {}, "source": [ "# Test our embedding in Gaussian Process" @@ -1289,33 +766,17 @@ { "cell_type": "code", "execution_count": null, - "id": "617d8645", + "id": "10", "metadata": {}, - "outputs": [ - { - "ename": "TypeError", - "evalue": "must be real number, not CSSCode", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[14], line 345\u001b[0m\n\u001b[1;32m 339\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m mse, r2, df, extras\n\u001b[1;32m 340\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m mse, r2, extras\n\u001b[1;32m 344\u001b[0m model \u001b[38;5;241m=\u001b[39m get_model_(\n\u001b[0;32m--> 345\u001b[0m \u001b[43mtorch\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtensor\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtrain_dataset\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mX\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdtype\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtorch\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfloat32\u001b[49m\u001b[43m)\u001b[49m,\n\u001b[1;32m 346\u001b[0m torch\u001b[38;5;241m.\u001b[39mtensor(train_dataset\u001b[38;5;241m.\u001b[39my, dtype\u001b[38;5;241m=\u001b[39mtorch\u001b[38;5;241m.\u001b[39mfloat32),\n\u001b[1;32m 347\u001b[0m kernel_type\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mmatern\u001b[39m\u001b[38;5;124m'\u001b[39m,\n\u001b[1;32m 348\u001b[0m mean_type\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mlinear\u001b[39m\u001b[38;5;124m'\u001b[39m,\n\u001b[1;32m 349\u001b[0m mean_input\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m128\u001b[39m,\n\u001b[1;32m 350\u001b[0m )\n\u001b[1;32m 352\u001b[0m _ \u001b[38;5;241m=\u001b[39m train_gpytorch_model_separate(\n\u001b[1;32m 353\u001b[0m model,\n\u001b[1;32m 354\u001b[0m torch\u001b[38;5;241m.\u001b[39mtensor(train_dataset\u001b[38;5;241m.\u001b[39mX, dtype\u001b[38;5;241m=\u001b[39mtorch\u001b[38;5;241m.\u001b[39mfloat32),\n\u001b[1;32m 355\u001b[0m torch\u001b[38;5;241m.\u001b[39mtensor(train_dataset\u001b[38;5;241m.\u001b[39my, dtype\u001b[38;5;241m=\u001b[39mtorch\u001b[38;5;241m.\u001b[39mfloat32),\n\u001b[1;32m 356\u001b[0m training_iter\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m200\u001b[39m\n\u001b[1;32m 357\u001b[0m )\n\u001b[1;32m 359\u001b[0m mse1, r21, extra1 \u001b[38;5;241m=\u001b[39m evaluate_gp_on_test(train_dataset, model, plot\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m, return_df\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m, use_std\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n", - "\u001b[0;31mTypeError\u001b[0m: must be real number, not CSSCode" - ] - } - ], + "outputs": [], "source": [ "# ======= GP + NN Embedder for QEC =======\n", - "import math\n", - "import numpy as np\n", "import torch\n", "import torch.nn as nn\n", "import gpytorch\n", - "from gpytorch.mlls import ExactMarginalLogLikelihood\n", "from gpytorch.distributions import MultivariateNormal\n", "from gpytorch.kernels import RBFKernel, MaternKernel, SpectralMixtureKernel, ScaleKernel\n", "import matplotlib.pyplot as plt\n", - "import pandas as pd\n", "\n", "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n", "\n", @@ -1328,31 +789,41 @@ " --> GP mean(z), cov(z)\n", " \"\"\"\n", "\n", - " def __init__(self, train_x, train_y,\n", - " *,\n", - " likelihood: gpytorch.likelihoods.GaussianLikelihood,\n", - " kernel: gpytorch.kernels.Kernel,\n", - " encoder, # callable: encode(X_batch) -> list of views\n", - " embedding: nn.Module, # ChainComplexEmbedder\n", - " mean: str = 'constant',\n", - " mean_input: int = 64):\n", + " def __init__(\n", + " self,\n", + " train_x,\n", + " train_y,\n", + " *,\n", + " likelihood: gpytorch.likelihoods.GaussianLikelihood,\n", + " kernel: gpytorch.kernels.Kernel,\n", + " encoder, # callable: encode(X_batch) -> list of views\n", + " embedding: nn.Module, # ChainComplexEmbedder\n", + " mean: str = \"constant\",\n", + " mean_input: int = 64,\n", + " ):\n", " # train_y must be 1D for ExactGP; we'll squeeze in caller\n", - " super().__init__(train_x, train_y.squeeze(-1) if train_y.dim() == 2 else train_y, likelihood)\n", - " self.encoder = encoder # Python callable (non-differentiable wrt X)\n", - " self.embed = embedding # nn.Module on device\n", + " super().__init__(\n", + " train_x, train_y.squeeze(-1) if train_y.dim() == 2 else train_y, likelihood\n", + " )\n", + " self.encoder = encoder # Python callable (non-differentiable wrt X)\n", + " self.embed = embedding # nn.Module on device\n", " self.embed.to(device)\n", "\n", - " d_model = next(self.embed.parameters()).shape[-1] if hasattr(self.embed, 'parameters') else 128\n", - " d_model = getattr(self.embed, 'd_model', d_model)\n", + " d_model = (\n", + " next(self.embed.parameters()).shape[-1]\n", + " if hasattr(self.embed, \"parameters\")\n", + " else 128\n", + " )\n", + " d_model = getattr(self.embed, \"d_model\", d_model)\n", "\n", " # Mean module\n", - " if mean == 'zero':\n", + " if mean == \"zero\":\n", " self.mean_module = gpytorch.means.ZeroMean()\n", - " elif mean == 'constant':\n", + " elif mean == \"constant\":\n", " self.mean_module = gpytorch.means.ConstantMean()\n", - " elif mean == 'linear':\n", + " elif mean == \"linear\":\n", " self.mean_module = gpytorch.means.LinearMean(input_size=d_model)\n", - " elif mean == 'nn':\n", + " elif mean == \"nn\":\n", " # Small MLP mean head on top of z\n", " self.nn_mean = nn.Sequential(\n", " nn.LayerNorm(d_model),\n", @@ -1360,20 +831,22 @@ " nn.GELU(),\n", " nn.Linear(mean_input, 1),\n", " ).to(device)\n", - " # wrap to a Mean that calls nn_mean \n", + "\n", + " # wrap to a Mean that calls nn_mean\n", " class _NNMean(gpytorch.means.Mean):\n", - " def __init__(self, head): \n", - " super().__init__(); self.head = head\n", - " def forward(self, x): \n", + " def __init__(self, head):\n", + " super().__init__()\n", + " self.head = head\n", + "\n", + " def forward(self, x):\n", " return self.head(x).squeeze(-1)\n", + "\n", " self.mean_module = _NNMean(self.nn_mean)\n", " else:\n", " self.mean_module = gpytorch.means.ConstantMean()\n", "\n", - "\n", " self.covar_module = kernel\n", "\n", - "\n", " self._cache_last_inputs = None\n", " self._cache_last_z = None\n", "\n", @@ -1382,10 +855,12 @@ " Convert a batch of design vectors X (B, D) to embedding z (B, d_model).\n", " \"\"\"\n", " # Move to CPU numpy for code constructor if needed\n", - " X_np = X.detach().cpu().numpy() if isinstance(X, torch.Tensor) else np.asarray(X)\n", - " sample_views_list = self.encoder(X_np) # list of views (len=B)\n", + " X_np = (\n", + " X.detach().cpu().numpy() if isinstance(X, torch.Tensor) else np.asarray(X)\n", + " )\n", + " sample_views_list = self.encoder(X_np) # list of views (len=B)\n", " # embedder.forward(list) packs internally and returns (B, d_model)\n", - " z = self.embed(sample_views_list) # (B, d_model) on device\n", + " z = self.embed(sample_views_list) # (B, d_model) on device\n", " return z\n", "\n", " def forward(self, X: torch.Tensor) -> MultivariateNormal:\n", @@ -1393,34 +868,32 @@ " Return latent f(X) distribution (MVN). GPyTorch's likelihood wraps this to p(y|X).\n", " \"\"\"\n", " X = X.to(device)\n", - " z = self._x_to_z(X) \n", + " z = self._x_to_z(X)\n", " # cache\n", " self._cache_last_inputs = X\n", " self._cache_last_z = z\n", "\n", - " mean_x = self.mean_module(z) \n", - " covar_x = self.covar_module(z) \n", + " mean_x = self.mean_module(z)\n", + " covar_x = self.covar_module(z)\n", " return MultivariateNormal(mean_x, covar_x)\n", "\n", - "\n", " def posterior(self, X: torch.Tensor):\n", - " self.eval(); self.likelihood.eval()\n", + " self.eval()\n", + " self.likelihood.eval()\n", " with torch.no_grad(), gpytorch.settings.fast_pred_var():\n", " # predictive distribution of y\n", " return self.likelihood(self(X.to(device)))\n", "\n", "\n", "# --------- Modeling ---------\n", - "def get_model_( X, y, kernel_type='ard_rbf', mean_type='linear', mean_input=64):\n", - "\n", - "\n", + "def get_model_(X, y, kernel_type=\"ard_rbf\", mean_type=\"linear\", mean_input=64):\n", "\n", " encoder = E(views_info)\n", " embed_dim = 128\n", " # NN embedder\n", " embedding = ChainComplexEmbedder(\n", " views_info=views_info,\n", - " d_model=embed_dim ,\n", + " d_model=embed_dim,\n", " num_layers=4,\n", " view_aggr=\"sum\",\n", " num_bases=4,\n", @@ -1430,19 +903,18 @@ " dropout=0.1,\n", " ).to(device)\n", "\n", - "\n", - "\n", " # kernel\n", - " if kernel_type == 'ard_rbf':\n", + " if kernel_type == \"ard_rbf\":\n", " base = RBFKernel(ard_num_dims=embed_dim)\n", " kernel = ScaleKernel(base)\n", - " elif kernel_type == 'matern':\n", + " elif kernel_type == \"matern\":\n", " base = MaternKernel(nu=1.5, ard_num_dims=embed_dim)\n", " kernel = ScaleKernel(base)\n", - " elif kernel_type == 'spectral_mixture':\n", + " elif kernel_type == \"spectral_mixture\":\n", " kernel = SpectralMixtureKernel(num_mixtures=4, ard_num_dims=embed_dim)\n", - " elif kernel_type == 'rbf_plus_periodic':\n", + " elif kernel_type == \"rbf_plus_periodic\":\n", " from gpytorch.kernels import PeriodicKernel\n", + "\n", " rbf = ScaleKernel(RBFKernel(ard_num_dims=embed_dim))\n", " periodic = ScaleKernel(PeriodicKernel(ard_num_dims=embed_dim))\n", " kernel = rbf + periodic\n", @@ -1457,7 +929,8 @@ " train_y = y.to(device).float().view(-1)\n", "\n", " gp = GaussianProcess_QEC(\n", - " train_x, train_y,\n", + " train_x,\n", + " train_y,\n", " likelihood=likelihood,\n", " kernel=kernel,\n", " encoder=encoder.encode,\n", @@ -1471,45 +944,54 @@ "\n", "# --------- Train ---------\n", "def train_gpytorch_model_separate(model, train_x, train_y, training_iter=100):\n", - " model.train(); model.likelihood.train()\n", + " model.train()\n", + " model.likelihood.train()\n", " # parameter groups\n", " params_embed = list(model.embed.parameters())\n", - " params_kernel = list(model.covar_module.parameters()) if hasattr(model, \"covar_module\") else []\n", + " params_kernel = (\n", + " list(model.covar_module.parameters()) if hasattr(model, \"covar_module\") else []\n", + " )\n", " params_like = list(model.likelihood.parameters())\n", - " params_mean = list(model.mean_module.parameters()) if hasattr(model, \"mean_module\") else []\n", - "\n", - "\n", - " optimizer = torch.optim.AdamW([\n", - " {'params': params_embed, 'lr': 4e-4},\n", - " {'params': params_mean, 'lr': 1e-3},\n", - " {'params': params_kernel, 'lr': 1e-3},\n", - " {'params': params_like, 'lr': 2e-2},\n", - " ])\n", + " params_mean = (\n", + " list(model.mean_module.parameters()) if hasattr(model, \"mean_module\") else []\n", + " )\n", + "\n", + " optimizer = torch.optim.AdamW(\n", + " [\n", + " {\"params\": params_embed, \"lr\": 4e-4},\n", + " {\"params\": params_mean, \"lr\": 1e-3},\n", + " {\"params\": params_kernel, \"lr\": 1e-3},\n", + " {\"params\": params_like, \"lr\": 2e-2},\n", + " ]\n", + " )\n", "\n", " mll = ExactMarginalLogLikelihood(model.likelihood, model)\n", "\n", - "\n", " train_x = train_x.to(device).float()\n", " train_y = train_y.to(device).float().view(-1) # 1D\n", "\n", " history = []\n", " for i in range(training_iter):\n", " optimizer.zero_grad()\n", - " output = model(train_x) # MVN over latent f\n", + " output = model(train_x) # MVN over latent f\n", " loss = -mll(output, train_y)\n", " loss.backward()\n", " optimizer.step()\n", " history.append(loss.item())\n", "\n", - " if (i+1) % max(1, training_iter//10) == 0:\n", - " print(f\"[{i+1:>4d}/{training_iter}] nll={loss.item():.4f}, \"\n", - " f\"noise={model.likelihood.noise.item():.3e}\")\n", + " if (i + 1) % max(1, training_iter // 10) == 0:\n", + " print(\n", + " f\"[{i + 1:>4d}/{training_iter}] nll={loss.item():.4f}, \"\n", + " f\"noise={model.likelihood.noise.item():.3e}\"\n", + " )\n", "\n", " plt.figure()\n", " plt.plot(history)\n", - " plt.xlabel(\"iter\"); plt.ylabel(\"Neg. MLL\")\n", + " plt.xlabel(\"iter\")\n", + " plt.ylabel(\"Neg. MLL\")\n", " plt.title(\"GP training curve\")\n", - " plt.grid(True); plt.tight_layout()\n", + " plt.grid(True)\n", + " plt.tight_layout()\n", " plt.show()\n", " return float(history[-1])\n", "\n", @@ -1524,25 +1006,25 @@ " return_df=False,\n", " use_std=True,\n", " plot_mll_hist=True,\n", - " bins=30\n", + " bins=30,\n", "):\n", " import torch\n", " import gpytorch\n", - " import numpy as np\n", " import pandas as pd\n", " import matplotlib.pyplot as plt\n", "\n", " device = next(model.parameters()).device\n", - " model.eval(); model.likelihood.eval()\n", + " model.eval()\n", + " model.likelihood.eval()\n", "\n", " X_test = torch.tensor(test_data.X, dtype=torch.float32, device=device)\n", " y_test = torch.tensor(test_data.y, dtype=torch.float32, device=device).view(-1)\n", "\n", " with gpytorch.settings.fast_pred_var():\n", " # predictive distribution of y (test predictive)\n", - " pred = model.likelihood(model(X_test)) # MultivariateNormal over y|X\n", - " mean = pred.mean \n", - " var = pred.variance \n", + " pred = model.likelihood(model(X_test)) # MultivariateNormal over y|X\n", + " mean = pred.mean\n", + " var = pred.variance\n", "\n", " # error band for plotting\n", " err = torch.sqrt(var) if use_std else var\n", @@ -1551,8 +1033,7 @@ " mse = torch.mean((mean - y_test) ** 2).item()\n", " ss_res = torch.sum((y_test - mean) ** 2).item()\n", " ss_tot = torch.sum((y_test - torch.mean(y_test)) ** 2).item()\n", - " r2 = 1 - ss_res / ss_tot if ss_tot > 0 else float('nan')\n", - "\n", + " r2 = 1 - ss_res / ss_tot if ss_tot > 0 else float(\"nan\")\n", "\n", " # Use univariate Normal with mean_i, var_i\n", " eps = 1e-12\n", @@ -1560,30 +1041,26 @@ " normal = torch.distributions.Normal(loc=mean, scale=std)\n", "\n", " # per-point log-likelihood and density\n", - " per_point_logpdf = normal.log_prob(y_test) \n", - " per_point_pdf = torch.exp(per_point_logpdf) \n", - "\n", + " per_point_logpdf = normal.log_prob(y_test)\n", + " per_point_pdf = torch.exp(per_point_logpdf)\n", "\n", - " z = (y_test - mean) / std \n", + " z = (y_test - mean) / std\n", " z_mean = z.mean().item()\n", - " z_std = z.std(unbiased=True).item()\n", - " z_mae = z.abs().mean().item()\n", + " z_std = z.std(unbiased=True).item()\n", + " z_mae = z.abs().mean().item()\n", " z_within_1 = (z.abs() <= 1.0).float().mean().item()\n", " z_within_2 = (z.abs() <= 2.0).float().mean().item()\n", " z_within_3 = (z.abs() <= 3.0).float().mean().item()\n", "\n", - "\n", " # summary stats of per-point densities\n", " avg_pdf = per_point_pdf.mean().item()\n", " avg_logpdf = per_point_logpdf.mean().item()\n", "\n", - "\n", " total_loglik = pred.log_prob(y_test).item()\n", - " avg_loglik = total_loglik / y_test.numel()\n", - "\n", + " avg_loglik = total_loglik / y_test.numel()\n", "\n", " total_nll = -total_loglik\n", - " avg_nll = -avg_loglik\n", + " avg_nll = -avg_loglik\n", " per_point_nll = (-per_point_logpdf).mean().item()\n", "\n", " # ----- Sorting for line-plot -----\n", @@ -1593,52 +1070,62 @@ " e_sorted = err[idx].detach().cpu().numpy()\n", "\n", " if plot:\n", - " plt.figure(figsize=(10,5))\n", - " plt.plot(y_sorted, label='True')\n", - " plt.plot(m_sorted, label='Predicted mean')\n", - " plt.plot(m_sorted + e_sorted, label=('Mean + STD' if use_std else 'Mean + Var'))\n", - " plt.plot(m_sorted - e_sorted, label=('Mean - STD' if use_std else 'Mean - Var'))\n", + " plt.figure(figsize=(10, 5))\n", + " plt.plot(y_sorted, label=\"True\")\n", + " plt.plot(m_sorted, label=\"Predicted mean\")\n", + " plt.plot(m_sorted + e_sorted, label=(\"Mean + STD\" if use_std else \"Mean + Var\"))\n", + " plt.plot(m_sorted - e_sorted, label=(\"Mean - STD\" if use_std else \"Mean - Var\"))\n", " plt.xlabel(\"Samples (sorted by true)\")\n", " plt.ylabel(\"Value\")\n", - " plt.legend(); plt.grid(True); plt.tight_layout(); plt.show()\n", + " plt.legend()\n", + " plt.grid(True)\n", + " plt.tight_layout()\n", + " plt.show()\n", "\n", " if plot_mll_hist:\n", " pdf_np = per_point_pdf.detach().cpu().numpy()\n", " logpdf_np = per_point_logpdf.detach().cpu().numpy()\n", "\n", - " plt.figure(figsize=(10,4))\n", - " plt.subplot(1,2,1)\n", + " plt.figure(figsize=(10, 4))\n", + " plt.subplot(1, 2, 1)\n", " plt.hist(pdf_np, bins=bins, alpha=0.85)\n", " plt.title(\"Per-point predictive density p(y_i|X)\")\n", - " plt.xlabel(\"density value\"); plt.ylabel(\"count\"); plt.grid(True)\n", + " plt.xlabel(\"density value\")\n", + " plt.ylabel(\"count\")\n", + " plt.grid(True)\n", "\n", - " plt.subplot(1,2,2)\n", + " plt.subplot(1, 2, 2)\n", " plt.hist(logpdf_np, bins=bins, alpha=0.85)\n", " plt.title(\"Per-point predictive log-likelihood\")\n", - " plt.xlabel(\"log density\"); plt.ylabel(\"count\"); plt.grid(True)\n", - " plt.tight_layout(); plt.show()\n", + " plt.xlabel(\"log density\")\n", + " plt.ylabel(\"count\")\n", + " plt.grid(True)\n", + " plt.tight_layout()\n", + " plt.show()\n", "\n", " # dataframe for inspection\n", - " df = pd.DataFrame({\n", - " 'True value': y_sorted,\n", - " 'Predicted mean': m_sorted,\n", - " ('std' if use_std else 'var'): e_sorted\n", - " })\n", + " df = pd.DataFrame(\n", + " {\n", + " \"True value\": y_sorted,\n", + " \"Predicted mean\": m_sorted,\n", + " (\"std\" if use_std else \"var\"): e_sorted,\n", + " }\n", + " )\n", "\n", " extras = {\n", - " 'avg_per_point_pdf': avg_pdf,\n", - " 'avg_per_point_logpdf': avg_logpdf,\n", - " 'total_multivariate_loglik': total_loglik,\n", - " 'avg_multivariate_loglik_per_point': avg_loglik,\n", - " 'total_multivariate_nll': total_nll,\n", - " 'avg_multivariate_nll_per_point': avg_nll,\n", - " 'avg_per_point_nll_independent': per_point_nll,\n", - " 'z_mean': z_mean,\n", - " 'z_std': z_std,\n", - " 'z_mae': z_mae,\n", - " 'coverage_within_1sigma': z_within_1, \n", - " 'coverage_within_2sigma': z_within_2, \n", - " 'coverage_within_3sigma': z_within_3, \n", + " \"avg_per_point_pdf\": avg_pdf,\n", + " \"avg_per_point_logpdf\": avg_logpdf,\n", + " \"total_multivariate_loglik\": total_loglik,\n", + " \"avg_multivariate_loglik_per_point\": avg_loglik,\n", + " \"total_multivariate_nll\": total_nll,\n", + " \"avg_multivariate_nll_per_point\": avg_nll,\n", + " \"avg_per_point_nll_independent\": per_point_nll,\n", + " \"z_mean\": z_mean,\n", + " \"z_std\": z_std,\n", + " \"z_mae\": z_mae,\n", + " \"coverage_within_1sigma\": z_within_1,\n", + " \"coverage_within_2sigma\": z_within_2,\n", + " \"coverage_within_3sigma\": z_within_3,\n", " }\n", " print(extras)\n", "\n", @@ -1647,12 +1134,11 @@ " return mse, r2, extras\n", "\n", "\n", - "\n", "model = get_model_(\n", " train_dataset.X,\n", " torch.tensor(train_dataset.y, dtype=torch.float32),\n", - " kernel_type='matern',\n", - " mean_type='linear',\n", + " kernel_type=\"matern\",\n", + " mean_type=\"linear\",\n", " mean_input=128,\n", ")\n", "\n", @@ -1660,33 +1146,28 @@ " model,\n", " torch.tensor(train_dataset.X, dtype=torch.float32),\n", " torch.tensor(train_dataset.y, dtype=torch.float32),\n", - " training_iter=200\n", + " training_iter=200,\n", ")\n", "\n", - "mse1, r21, extra1 = evaluate_gp_on_test(train_dataset, model, plot=True, return_df=False, use_std=True)\n", + "mse1, r21, extra1 = evaluate_gp_on_test(\n", + " train_dataset, model, plot=True, return_df=False, use_std=True\n", + ")\n", "print(f\"Train MSE: {mse1:.6f} | Train R2: {r21:.6f}\")\n", "# print(df_tr.head())\n", "\n", - "mse2, r22, extra2 = evaluate_gp_on_test(test_dataset, model, plot=True, return_df=False, use_std=True)\n", + "mse2, r22, extra2 = evaluate_gp_on_test(\n", + " test_dataset, model, plot=True, return_df=False, use_std=True\n", + ")\n", "print(f\"Test MSE: {mse2:.6f} | Test R2: {r22:.6f}\")\n", - "# print(df_te.head())\n" + "# print(df_te.head())" ] }, { "cell_type": "code", - "execution_count": 11, - "id": "ad36439e", + "execution_count": null, + "id": "11", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Train MSE: 0.249584 | Train R2: 0.735525\n", - "Test MSE: 0.705363 | Test R2: 0.423769\n" - ] - } - ], + "outputs": [], "source": [ "print(f\"Train MSE: {mse1:.6f} | Train R2: {r21:.6f}\")\n", "print(f\"Test MSE: {mse2:.6f} | Test R2: {r22:.6f}\")" @@ -1694,18 +1175,10 @@ }, { "cell_type": "code", - "execution_count": 12, - "id": "2254720f", + "execution_count": null, + "id": "12", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'avg_per_point_pdf': 0.4129357933998108, 'avg_per_point_logpdf': -1.3659827709197998, 'total_multivariate_loglik': -79.96087646484375, 'avg_multivariate_loglik_per_point': -1.3326812744140626, 'total_multivariate_nll': 79.96087646484375, 'avg_multivariate_nll_per_point': 1.3326812744140626, 'avg_per_point_nll_independent': 1.3659827709197998, 'z_mean': -0.2602979242801666, 'z_std': 1.4013820886611938, 'z_mae': 1.1067477464675903, 'coverage_within_1sigma': 0.5333333611488342, 'coverage_within_2sigma': 0.8333333730697632, 'coverage_within_3sigma': 0.98333340883255}\n" - ] - } - ], + "outputs": [], "source": [ "print(extra2)" ] @@ -1713,7 +1186,7 @@ ], "metadata": { "kernelspec": { - "display_name": "QEC2", + "display_name": "qec", "language": "python", "name": "python3" }, @@ -1727,7 +1200,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.16" + "version": "3.10.19" } }, "nbformat": 4, diff --git a/tests/test_dist_eval.ipynb b/tests/test_dist_eval.ipynb new file mode 100644 index 0000000..6bbe72a --- /dev/null +++ b/tests/test_dist_eval.ipynb @@ -0,0 +1,184 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "0", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "from pathlib import Path\n", + "\n", + "path_root = Path.cwd().parent\n", + "\n", + "if str(path_root) not in sys.path:\n", + " sys.path.append(str(path_root))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1", + "metadata": {}, + "outputs": [], + "source": [ + "# test normalizer\n", + "import numpy as np\n", + "from Normalizer import StdNormalizer\n", + "\n", + "DEVICE = \"cuda\"\n", + "normalizer = StdNormalizer(DEVICE)\n", + "\n", + "mean = 7\n", + "scale = 2\n", + "e = 0.01 # arbitrary error threshold for random test\n", + "\n", + "data = np.random.normal(mean, scale, 1000000)\n", + "\n", + "normalizer = normalizer.fit(data)\n", + "assert normalizer.is_fitted()\n", + "print(normalizer.stats.mean - mean)\n", + "print(normalizer.stats.scale - scale)\n", + "assert abs(normalizer.stats.mean - mean) < e\n", + "assert abs(normalizer.stats.scale - scale) < e\n", + "\n", + "data_point = np.random.normal(mean, scale, 1)[0]\n", + "transformed_dp = normalizer.transform(data_point).item()\n", + "print(f\"{data_point} -> {transformed_dp}\")\n", + "assert (\n", + " transformed_dp > -3 and transformed_dp < 3\n", + ") # standard normal stddev is 1, 99% of the time should be within mean (0) += 3*sd\n", + "\n", + "inverse_transformed_dp = normalizer.inverse_transform(transformed_dp).item()\n", + "print(f\"{transformed_dp} -> {inverse_transformed_dp}\")\n", + "print(inverse_transformed_dp - data_point)\n", + "assert abs(inverse_transformed_dp - data_point) < e\n", + "\n", + "print(\"Normalizer tests passed!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "# test changes in bo/bo\n", + "from bayesian_optimization.bo import BO_on_QEC\n", + "\n", + "# bo = BO_on_QEC(code_eval_metric=\"distance\")\n", + "\n", + "# nothing really to test here" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3", + "metadata": {}, + "outputs": [], + "source": [ + "# test changes in bo/objective function\n", + "from bayesian_optimization.objective_function import ObjectiveFunction\n", + "from code_construction.code_construction import CSSCode\n", + "import qldpc\n", + "\n", + "code_class = \"bb\"\n", + "\n", + "\n", + "class DummyCodeConstructor:\n", + " def __init__(self):\n", + " self.n = 1\n", + "\n", + " # def construct(self, code: qldpc.codes.CSSCode):\n", + " # return CSSCode(code.matrix_x, code.matrix_z)\n", + "\n", + " def construct(self, matrices):\n", + " return CSSCode(matrices[0], matrices[1])\n", + "\n", + "\n", + "cc = DummyCodeConstructor()\n", + "\n", + "# NO LONGER WORKS DUE TO TIMEOUT: see test_exact_dist_eval.py\n", + "\"\"\"\n", + "# exact distance\n", + "obj = ObjectiveFunction(code_constructor=cc, code_eval_metric=\"distance\", dist_timeout=100)\n", + "\n", + "small_code = qldpc.codes.SteaneCode()\n", + "small_code_matrices = np.array([small_code.matrix_x, small_code.matrix_z])\n", + "small_known_distance = 3\n", + "_, d = obj.forward(small_code_matrices)\n", + "assert d == small_known_distance\n", + "\n", + "print(\"Exact distance test passed!\")\n", + "print()\n", + "\"\"\"\n", + "\n", + "# distance estimation with various methods\n", + "L = 10\n", + "large_code = qldpc.codes.ToricCode(L)\n", + "large_code_matrices = np.array([large_code.matrix_x, large_code.matrix_z])\n", + "\n", + "large_known_distance = L\n", + "# commented out methods have issues (require license, local binaries missing etc.)\n", + "methods = [\n", + " \"QDistEvol\",\n", + " # 'magmaMinWeight',\n", + " # 'magmaMinWord',\n", + " # 'magmaWEDist',\n", + " # 'qubitSerfBZ',\n", + " # 'qubitSerfMM',\n", + " # 'dist_m4ri_RW',\n", + " # 'dist_m4ri_CC',\n", + " \"QDistRndMW\",\n", + " \"UndetectableErrorStim\",\n", + " \"GraphLikeErrorStim\",\n", + " \"ColourCodeDistStim\",\n", + " \"UndetectableErrorMW\",\n", + " \"GraphLikeErrorMW\",\n", + " \"ColourCodeDistMW\",\n", + " \"decoderDist\",\n", + " # 'GurobiDist',\n", + " \"MIPDist\",\n", + " # 'pySATDist'\n", + "]\n", + "\n", + "for m in methods:\n", + " obj = ObjectiveFunction(\n", + " code_constructor=cc, code_eval_metric=\"distance\", dist_method=m\n", + " )\n", + "\n", + " _, est_d = obj.forward(large_code_matrices)\n", + " print(\n", + " f\"method: {m}, actual distance: {large_known_distance}, estimated distance: {est_d}\"\n", + " )\n", + "\n", + "print()\n", + "print(\"All methods tested!\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "qec", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.19" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tests/test_exact_dist_eval.py b/tests/test_exact_dist_eval.py new file mode 100644 index 0000000..5af0812 --- /dev/null +++ b/tests/test_exact_dist_eval.py @@ -0,0 +1,49 @@ +# multiprocessing is used for the timeout of exact distance evaluation, and it doesn't work well with notebooks + +import numpy as np +import qldpc + + +import sys +from pathlib import Path + +# hack to access classes outside of test directory +current_file_path = Path(__file__).resolve() +path_root = current_file_path.parent.parent + +if str(path_root) not in sys.path: + sys.path.insert(0, str(path_root)) + +from bayesian_optimization.objective_function import ObjectiveFunction +from code_construction.code_construction import CSSCode + + +# ----------------------------------------------------------------------- + + +class DummyCodeConstructor: + def __init__(self): + self.n = 1 + self.k = 1 + + def construct(self, matrices): + return CSSCode(matrices[0], matrices[1]) + + +if __name__ == "__main__": + print("Initializing...") + cc = DummyCodeConstructor() + obj = ObjectiveFunction( + code_constructor=cc, code_eval_metric="distance", dist_timeout=100 + ) + + small_code = qldpc.codes.SteaneCode() + small_code_matrices = np.array([small_code.matrix_x, small_code.matrix_z]) + small_known_distance = 3 + + print("Running objective function...") + _, d = obj.forward(small_code_matrices) + + print(f"Calculated distance: {d}") + assert d == small_known_distance + print("Exact distance test passed!") diff --git a/tests/test_gb_codes.ipynb b/tests/test_gb_codes.ipynb new file mode 100644 index 0000000..5367dd9 --- /dev/null +++ b/tests/test_gb_codes.ipynb @@ -0,0 +1,448 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "0", + "metadata": {}, + "outputs": [], + "source": [ + "def int_to_poly(p: int):\n", + " poly = \"\"\n", + " for i in range(p.bit_length()):\n", + " bit = (p >> i) & 1\n", + " if bit:\n", + " if i == 0:\n", + " add = \"1\"\n", + " elif i == 1:\n", + " add = \"x\"\n", + " else:\n", + " add = f\"x^{i}\"\n", + "\n", + " if poly == \"\":\n", + " poly = add\n", + " else:\n", + " poly += f\" + {add}\"\n", + "\n", + " return poly\n", + "\n", + "\n", + "print(int_to_poly(19))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "from pathlib import Path\n", + "\n", + "path_root = Path.cwd().parent\n", + "\n", + "if str(path_root) not in sys.path:\n", + " sys.path.append(str(path_root))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "# TEST: does multiply_polynomials_mod_l correctly multiply polynomials with coefficients mod 2 and exponents mod l\n", + "from code_construction.code_construction import CodeConstructor, CSSCode\n", + "\n", + "p1 = 417 # 110100001 -> 1 + x^5 + x^7 + x^8\n", + "p2 = 9 # 1001 -> 1 + x^3\n", + "\n", + "l = 9\n", + "\n", + "# p1 * p2 = 1 + x^5 + x^7 + x^8 + x^3 + x^8 + x^10 + x^11 = 1 + x^5 + x^7 + x^8 + x^3 + x^8 + x + x^2 = 1 + x + x^2 + x^3 + x^5 + x^7\n", + "\n", + "p1p2 = CodeConstructor.multiply_polynomials_mod_l(p1, p2, l)\n", + "print(int_to_poly(p1))\n", + "print(int_to_poly(p2))\n", + "print(int_to_poly(p1p2))\n", + "\n", + "assert int_to_poly(p1p2) == \"1 + x + x^2 + x^3 + x^5 + x^7\"\n", + "\n", + "print(\"Test Passed!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3", + "metadata": {}, + "outputs": [], + "source": [ + "# TEST: does gx_mask_to_bin correctly generate the g(x) polynomial in int form given a list of factors and a bitmask\n", + "fs = [\n", + " 5, # 101 -> 1+x^2\n", + " 49, # 110001 -> 1+x^4+x^5\n", + " 17, # 10001 -> 1+x^4\n", + "]\n", + "\n", + "\n", + "gx_mask = 6 # 110 -> (1+x^4+x^5)(1+x^4) = 1 + x^4 + x^5 + x^4 + x^8 + x^9 = 1 + x^5 + x^8 + 1 = x^5 + x^8\n", + "\n", + "gx_bin = CodeConstructor.gx_mask_to_bin(gx_mask, fs, l)\n", + "\n", + "for f in fs:\n", + " print(int_to_poly(f))\n", + "\n", + "print(int_to_poly(gx_bin))\n", + "\n", + "assert int_to_poly(gx_bin) == \"x^5 + x^8\"\n", + "\n", + "print(\"Test Passed!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "# TEST: test int to binary list and vice versa\n", + "from BOplayground import HillClimbing\n", + "from BOplayground import Get_new_points_function\n", + "\n", + "\n", + "assert HillClimbing.bin_list_to_int([1, 1, 1, 0, 0, 0, 1]) == 71\n", + "\n", + "assert Get_new_points_function.int_to_bin_list(71) == [1, 1, 1, 0, 0, 0, 1]\n", + "\n", + "print(\"Test Passed!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5", + "metadata": {}, + "outputs": [], + "source": [ + "# TEST: candidate polynomials have the correct weight and length\n", + "\n", + "para_dict = {\"l\": l}\n", + "code_class = \"gb\"\n", + "target_weight = 3\n", + "desired_k = 4\n", + "\n", + "cc = CodeConstructor(method=code_class, para_dict=para_dict)\n", + "\n", + "gnp = Get_new_points_function(\n", + " method=code_class, code_constructor=cc, density=target_weight, desired_k=desired_k\n", + ")\n", + "gnp.set_gx_mask()\n", + "\n", + "assert gnp._generate_candidate_poly(l).bit_count() == target_weight\n", + "print(\"Test Passed!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "# TEST: can correctly find one polynomial mod another\n", + "p1 = 49 # 100011 -> 1 + x^4 + x^5\n", + "p2 = CodeConstructor.multiply_polynomials_mod_l(p1, 5, l)\n", + "assert CodeConstructor.poly_mod_f2(p1, p1) == 0\n", + "\n", + "p3 = 12 # 0011 -> x^2 + x^3\n", + "# p1 = (p3 * x^2) + 1\n", + "assert CodeConstructor.poly_mod_f2(p1, p3) == 1\n", + "print(\"Test Passed!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "# TEST: are randomly generated GB codes valid\n", + "\n", + "# this is the same as multiply_polynomials_mod_l but without the mod l\n", + "def binary_poly_mul(p1, p2):\n", + " res = 0\n", + " for i in range(p2.bit_length()):\n", + " if (p2 >> i) & 1:\n", + " res ^= p1 << i\n", + " return res\n", + "\n", + "\n", + "x_exp_l_minus_1 = 1 + (1 << l) # x^l - 1 = x^l + 1 in binary ring\n", + "\n", + "new_points = gnp.get_new_points_function(5)\n", + "\n", + "for point in new_points:\n", + " gx_mask = point[:l]\n", + " a = point[l : 2 * l]\n", + " b = point[2 * l : 3 * l]\n", + "\n", + " int_gx_mask = HillClimbing.bin_list_to_int(gx_mask)\n", + "\n", + " fs = point[3 * l :].reshape(-1, l)\n", + " int_fs = list(map(HillClimbing.bin_list_to_int, fs))\n", + "\n", + " prod_fs = 1\n", + " for f in int_fs:\n", + " prod_fs = binary_poly_mul(prod_fs, f)\n", + "\n", + " assert prod_fs == x_exp_l_minus_1\n", + "\n", + " gx_bin = CodeConstructor.gx_mask_to_bin(int_gx_mask, int_fs, l)\n", + "\n", + " assert sum(a) == target_weight\n", + " assert sum(b) == target_weight\n", + "\n", + "print(\"Test Passed!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "small_l = 3\n", + "print(\"l=3\")\n", + "\n", + "# factors of x^3 - 1 in the ring should be (x + 1)(x^2 + x + 1)\n", + "factors = gnp._get_irreducible_factors(small_l)\n", + "for f in factors:\n", + " print(f\"{int_to_poly(f)} ({f})\")\n", + "\n", + "print(\"\\n\")\n", + "print(\"l=9\")\n", + "\n", + "# factors of x^3 - 1 in the ring should be (x + 1)(x^2 + x + 1)\n", + "factors = gnp._get_irreducible_factors(l)\n", + "for f in factors:\n", + " print(f\"{int_to_poly(f)} ({f})\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "from sympy import symbols, factor, expand, GF\n", + "\n", + "\n", + "def factor_poly(poly):\n", + " factors = factor(poly, domain=GF(2))\n", + " return factors\n", + "\n", + "\n", + "x = symbols(\"x\")\n", + "print(factor_poly(1 + x + x**5))\n", + "\n", + "print(expand((1 + x**2 + x**3) * (1 + x), modulus=2))\n", + "print(expand((1 + x + x**2) * (1 + x), modulus=2))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "# TEST: is the CSS code generated by CodeConstructor correct for a given GB internal representation\n", + "import numpy as np\n", + "\n", + "a = 23 # 000010111\n", + "b = 9 # 000001001\n", + "\n", + "a = [1, 1, 1, 0, 1] # -> 1 + x + x^2 + x^4\n", + "b = [1, 0, 0, 1] # -> 1 + x^3\n", + "\n", + "gx_mask = [1] # 001, gx_bin=[1, 1]\n", + "fs = [[1, 1], [1, 1, 1], [1, 0, 0, 1, 0, 0, 1]] # from l=9 code above\n", + "\n", + "int_gx_mask = HillClimbing.bin_list_to_int(gx_mask)\n", + "int_fs = list(map(HillClimbing.bin_list_to_int, fs))\n", + "\n", + "gx_bin = CodeConstructor.gx_mask_to_bin(int_gx_mask, int_fs, l)\n", + "\n", + "params = [gx_mask, a, b] + fs\n", + "\n", + "# from Get_new_point_function.get_new_gb_vector -----\n", + "x = np.zeros((3 + len(fs), l), dtype=np.uint8)\n", + "for i, param in enumerate(params):\n", + " x[i, : len(param)] = param # padded so each param len l\n", + "x = x.ravel()\n", + "# ---------------------------------------------------\n", + "\n", + "a_padded = a + ([0] * (l - len(a)))\n", + "print(a_padded)\n", + "b_padded = b + ([0] * (l - len(b)))\n", + "print(b_padded)\n", + "\n", + "expected_A = np.array(\n", + " [\n", + " a_padded,\n", + " np.roll(a_padded, 1),\n", + " np.roll(a_padded, 2),\n", + " np.roll(a_padded, 3),\n", + " np.roll(a_padded, 4),\n", + " np.roll(a_padded, 5),\n", + " np.roll(a_padded, 6),\n", + " np.roll(a_padded, 7),\n", + " np.roll(a_padded, 8),\n", + " ]\n", + ")\n", + "\n", + "expected_B = np.array(\n", + " [\n", + " b_padded,\n", + " np.roll(b_padded, 1),\n", + " np.roll(b_padded, 2),\n", + " np.roll(b_padded, 3),\n", + " np.roll(b_padded, 4),\n", + " np.roll(b_padded, 5),\n", + " np.roll(b_padded, 6),\n", + " np.roll(b_padded, 7),\n", + " np.roll(b_padded, 8),\n", + " ]\n", + ")\n", + "\n", + "expected_hx = np.hstack((expected_A, expected_B))\n", + "\n", + "expected_hz = np.hstack((expected_B.T, expected_A.T))\n", + "\n", + "css = cc.construct(x)\n", + "\n", + "assert isinstance(css, CSSCode)\n", + "assert (((css.hx @ css.hz.T) % 2) == 0).all() # CSS parity check constraint\n", + "\n", + "assert np.array_equal(css.hx, expected_hx)\n", + "assert np.array_equal(css.hz, expected_hz)\n", + "\n", + "print(\"Test Passed!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "11", + "metadata": {}, + "outputs": [], + "source": [ + "# TEST: are generated neighbours of a GB code valid, do they have the same g(x) and have one q the same, and one different\n", + "import torch\n", + "\n", + "a = [1, 0, 1] # 1 + x^2\n", + "b = [1, 0, 0, 1]\n", + "\n", + "gx_mask = [1, 0, 0]\n", + "\n", + "fs = [[1, 1], [1, 1, 1], [1, 0, 0, 1, 0, 0, 1]] # from l=9 code above\n", + "\n", + "int_gx_mask = HillClimbing.bin_list_to_int(gx_mask)\n", + "int_fs = list(map(HillClimbing.bin_list_to_int, fs))\n", + "gx_bin = CodeConstructor.gx_mask_to_bin(int_gx_mask, int_fs, l)\n", + "a_int = HillClimbing.bin_list_to_int(a)\n", + "b_int = HillClimbing.bin_list_to_int(b)\n", + "\n", + "assert CodeConstructor.poly_mod_f2(a_int, gx_bin) == 0\n", + "assert CodeConstructor.poly_mod_f2(b_int, gx_bin) == 0\n", + "\n", + "params = [gx_mask, a, b] + fs\n", + "\n", + "# from Get_new_point_function.get_new_gb_vector -----\n", + "x = np.zeros((3 + len(fs), l), dtype=np.uint8)\n", + "for i, param in enumerate(params):\n", + " x[i, : len(param)] = param\n", + "x = x.ravel()\n", + "# ---------------------------------------------------\n", + "\n", + "target_weight2 = 2\n", + "\n", + "gnp2 = Get_new_points_function(\n", + " method=code_class, code_constructor=cc, density=target_weight2, desired_k=desired_k\n", + ")\n", + "\n", + "gnp2.set_gx_mask()\n", + "\n", + "hc = HillClimbing(\n", + " next_points_num=1,\n", + " gnp=gnp2.get_new_points_function,\n", + " acquisition=None,\n", + " l=l,\n", + " target_row_weight=target_weight2,\n", + ")\n", + "\n", + "x_tensor = torch.tensor(x, dtype=torch.float32)\n", + "\n", + "x_neighbours = hc.mutate_gb(x_tensor)\n", + "assert len(x_neighbours) > 0\n", + "print(f\"neighbours found: {len(x_neighbours)}\")\n", + "\n", + "for n in x_neighbours:\n", + " n = n.to(torch.long).tolist()\n", + " padded_gx_mask = gx_mask + ([0] * (l - len(gx_mask)))\n", + " padded_a = a + ([0] * (l - len(a)))\n", + " padded_b = b + ([0] * (l - len(b)))\n", + " padded_fs = list(map(lambda x: x + ([0] * (l - len(x))), fs))\n", + "\n", + " n_g = n[:l]\n", + " n_a = n[l : 2 * l]\n", + " n_b = n[2 * l : 3 * l]\n", + " n_fs = x[3 * l :].reshape(-1, l)\n", + "\n", + " assert n_g == padded_gx_mask\n", + "\n", + " # if (n_a == padded_a) & (n_b == padded_b):\n", + " # print(\n", + " # \"neighbours contain original\"\n", + " # )\n", + "\n", + " assert (n_a == padded_a) ^ (n_b == padded_b)\n", + " assert np.array_equal(n_fs, padded_fs)\n", + " assert sum(n_a) == target_weight2\n", + " assert sum(n_b) == target_weight2\n", + "\n", + " cc.construct(n) # CSSCode has built in checks to make sure the code is valid\n", + "\n", + "print(\"Test Passed!\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "qec", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.19" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/true_dist.py b/true_dist.py new file mode 100644 index 0000000..f11ace5 --- /dev/null +++ b/true_dist.py @@ -0,0 +1,87 @@ +import os +import pickle +from code_construction.code_construction import CodeConstructor, CSSCode +import codedistance + +def get_nkd(code: CSSCode): + res = codedistance.CSScodeDistance( + code.hx, + code.hz, + method="MIPDist", # exact distance finding algorithm + params={'solverType': 'CP_SAT'}, + seed=101, + ) + dist = res["d"] + return code.n, code.k, dist + + +cc = CodeConstructor(method="bb", para_dict={"l": 12, "g": 6}) +cc2 = CodeConstructor(method="gb", para_dict={"l":72}) + +gross_code = [ + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, +] + +folder_path = "data/BO_results/lorenzo_results" + +for file_name in [None] + os.listdir(folder_path): + if file_name is None: + file = "gross code" + code = cc.construct(gross_code) + best_x = gross_code + best_y = 0 + else: + file = os.path.join(folder_path, file_name) + with open(file, "rb") as f: + results = pickle.load(f) + best_x = results["best_x"] + best_y = results["best_y"] + + best_x = best_x.cpu() + + if "GB" in file: + code = cc2.construct(best_x) + else: + code = cc.construct(best_x) + + n, k, d = get_nkd(code) + + print(f"from {file}, score: {best_y:.5f}") + print(f"[[{n}, {k}, {d}]]") + print("-------------------------------") + + with open("distance_res.txt", 'a') as f: + f.write(f"\n[[{n}, {k}, {d}]], {best_y:.5f}\n")