118 lines
3.8 KiB
Python
118 lines
3.8 KiB
Python
import numpy as np
|
|
import sympy as sp
|
|
from typing import List, Callable
|
|
|
|
|
|
class HomotopyGenerator:
|
|
"""Generates homotopy functions from a binary parity check matrix."""
|
|
|
|
def __init__(self, parity_check_matrix: np.ndarray):
|
|
"""
|
|
Initialize with a parity check matrix.
|
|
|
|
Args:
|
|
parity_check_matrix: Binary matrix where rows represent parity checks
|
|
and columns represent variables.
|
|
"""
|
|
self.H_matrix = parity_check_matrix
|
|
self.num_checks, self.num_vars = parity_check_matrix.shape
|
|
|
|
# Create symbolic variables
|
|
self.x_vars = [sp.symbols(f'x{i+1}') for i in range(self.num_vars)]
|
|
self.t = sp.symbols('t')
|
|
|
|
# Generate G, F, and H
|
|
self.G = self._create_G()
|
|
self.F = self._create_F()
|
|
self.H = self._create_H()
|
|
|
|
# Convert to callable functions
|
|
self._H_lambda = self._create_H_lambda()
|
|
self._DH_lambda = self._create_DH_lambda()
|
|
|
|
def _create_G(self) -> List[sp.Expr]:
|
|
"""Create G polynomial system (the starting system)."""
|
|
# For each variable xi, add the polynomial [xi]
|
|
G = []
|
|
for var in self.x_vars:
|
|
G.append(var)
|
|
|
|
return G
|
|
|
|
def _create_F(self) -> List[sp.Expr]:
|
|
"""Create F polynomial system (the target system)."""
|
|
F = []
|
|
|
|
# Add 1 - xi^2 for each variable
|
|
for var in self.x_vars:
|
|
F.append(1 - var**2)
|
|
|
|
# Add parity check polynomials: 1 - x1*x2*...*xk for each parity check
|
|
for row in self.H_matrix:
|
|
# Create product of variables that participate in this check
|
|
term = 1
|
|
for i, bit in enumerate(row):
|
|
if bit == 1:
|
|
term *= self.x_vars[i]
|
|
|
|
if term != 1: # Only add if there are variables in this check
|
|
F.append(1 - term)
|
|
|
|
return F
|
|
|
|
def _create_H(self) -> List[sp.Expr]:
|
|
"""Create the homotopy H = (1-t)*G + t*F."""
|
|
H = []
|
|
|
|
# Make sure G and F have the same length
|
|
# Repeat variables from G if needed to match F's length
|
|
G_extended = self.G.copy()
|
|
while len(G_extended) < len(self.F):
|
|
# Cycle through variables to repeat
|
|
for i in range(min(self.num_vars, len(self.F) - len(G_extended))):
|
|
G_extended.append(self.x_vars[i % self.num_vars])
|
|
|
|
# Create the homotopy
|
|
for g, f in zip(G_extended, self.F):
|
|
H.append((1 - self.t) * g + self.t * f)
|
|
|
|
return H
|
|
|
|
def _create_H_lambda(self) -> Callable:
|
|
"""Create a lambda function to evaluate H."""
|
|
all_vars = self.x_vars + [self.t]
|
|
return sp.lambdify(all_vars, self.H, 'numpy')
|
|
|
|
def _create_DH_lambda(self) -> Callable:
|
|
"""Create a lambda function to evaluate the Jacobian of H."""
|
|
all_vars = self.x_vars + [self.t]
|
|
jacobian = sp.Matrix([[sp.diff(expr, var)
|
|
for var in all_vars] for expr in self.H])
|
|
return sp.lambdify(all_vars, jacobian, 'numpy')
|
|
|
|
def evaluate_H(self, y: np.ndarray) -> np.ndarray:
|
|
"""
|
|
Evaluate H at point y.
|
|
|
|
Args:
|
|
y: Array of form [x1, x2, ..., xn, t] where xi are the variables
|
|
and t is the homotopy parameter.
|
|
|
|
Returns:
|
|
Array containing H evaluated at y.
|
|
"""
|
|
return np.array(self._H_lambda(*y))
|
|
|
|
def evaluate_DH(self, y: np.ndarray) -> np.ndarray:
|
|
"""
|
|
Evaluate the Jacobian of H at point y.
|
|
|
|
Args:
|
|
y: Array of form [x1, x2, ..., xn, t] where xi are the variables
|
|
and t is the homotopy parameter.
|
|
|
|
Returns:
|
|
Matrix containing the Jacobian of H evaluated at y.
|
|
"""
|
|
return np.array(self._DH_lambda(*y), dtype=float)
|