import shutil
import subprocess
from ast import literal_eval
from functools import reduce
from pathlib import Path
import numpy as np
from ssip.basic_functions import (
BinMatrix,
CSScode,
compose_to_zero,
find_X_basis,
find_Z_basis,
num_data_qubits,
systolic_distance,
)
[docs]
def Z_distance(C: CSScode) -> int:
"""Finds the Z distance of a CSS code in a naive manner, by enumerating over
all Z logicals.
Args:
C: The CSScode.
Return:
The Z distance of C.
"""
return systolic_distance(C.PZ.T, C.PX)
[docs]
def X_distance(C: CSScode) -> int:
"""Finds the X distance of a CSS code in a naive manner, by enumerating over
all X logicals.
Args:
C: The CSScode.
Return:
The X distance of C.
"""
return systolic_distance(C.PX.T, C.PZ)
[docs]
def code_distance(C: CSScode, verbose: bool = False) -> int:
"""Finds the distance of a CSS code in a naive manner, by enumerating over
all logicals.
Args:
C: The CSScode.
Return:
The distance of C.
"""
dZ = Z_distance(C)
if verbose:
print(f"{dZ=}")
dX = X_distance(C)
if verbose:
print(f"{dX=}")
return min(dZ, dX)
# taken almost verbatim from Simon Burton's Qumba, see https://github.com/punkdit/qumba
[docs]
def distance_lower_bound_z3(Hx: BinMatrix, Lx: BinMatrix, d: int) -> list | None:
"""Checks to see whether there are any Z logicals of weight at most d. If there are,
returns the first one found.
Args:
Hx: The X parity check matrix of the code.
Lx: A spanning set of X logicals of the code.
d: The weight to check below.
Return:
The first nontrivial Z logical of weight at most d, if there is one.
Otherwise returns None.
"""
from z3 import Bool, If, Not, Or, Solver, Sum, Xor, sat
m, n = Hx.shape
k, n1 = Lx.shape
if n != n1:
raise ValueError("Stabiliser and logical matrices must have compatible sizes.")
solver = Solver()
add = solver.add
v = [Bool("v%d" % i) for i in range(n)]
term = Sum([If(v[i], 1, 0) for i in range(n)]) <= d
add(term)
def check(hx):
terms = [v[j] for j in range(n) if hx[j]]
if len(terms) > 1:
return reduce(Xor, terms)
elif len(terms) == 1:
return terms[0]
raise RuntimeError("dead check")
# parity checks
for i in range(m):
add(Not(check(Hx[i])))
# non-trivial logical
term = reduce(Or, [check(Lx[i]) for i in range(k)])
add(term)
result = solver.check()
if result != sat:
return
model = solver.model()
v = [model.evaluate(v[i]) for i in range(n)]
v = [int(literal_eval(str(vi))) for vi in v]
assert compose_to_zero(Hx, np.array([v]).T), "bug bug... try updating z3?"
assert not compose_to_zero(Lx, np.array([v]).T), "bug bug... try updating z3?"
assert sum(v) <= d, "sum(v)=%d: bug bug... try updating z3?" % sum(v)
return v
[docs]
def distance_z3(
C: CSScode, Lz: BinMatrix | None = None, Lx: BinMatrix | None = None
) -> int:
"""Finds the distance of a CSS code. Searches for logicals with weight 1, then
weight 2 and so on. Works best for codes with low distances, but is implemented
using Z3 so is quite fast.
Args:
C: The CSScode.
Lz: A spanning set of Z logicals of the code.
Lx: A spanning set of X logicals of the code.
Return:
The distance of C.
"""
n = num_data_qubits(C)
if Lz is None:
Lz = np.array(find_Z_basis(C))
if Lx is None:
Lx = np.array(find_X_basis(C))
d_X = 1
while d_X < n:
v = distance_lower_bound_z3(C.PZ, Lz, d_X)
if v is not None:
break
d_X += 1
d_Z = 1
while d_Z < d_X:
v = distance_lower_bound_z3(C.PX, Lx, d_Z)
if v is not None:
break
d_Z += 1
return min(d_Z, d_X)
# GAP QDistRnd I/O hack
[docs]
def distance_GAP(
C: CSScode,
gap_path: str | None = None,
num_information_sets: int | None = None,
input_filename: str | None = None,
output_filename: str | None = None,
) -> int:
"""Gives an upper bound on the distance of a CSS code using GAP. If the code is small
enough and the number of information sets is high then this upper bound is tight.
Empirically, we find that setting num_information_sets = 10,000 gives
complete accuracy at ~200 qubits.
Args:
C: The CSScode.
gap_path: The absolute path to the GAP directory.
num_information_sets: The number of information sets to make.
input_filename: Which file to use as input to GAP.
output_filename: Which file to receive results from GAP.
Return:
An upper bound on the distance of C.
"""
if input_filename is None:
input_filename = "input.g"
if output_filename is None:
output_filename = "output.txt"
if gap_path is None:
gap_path = "gap"
if num_information_sets is None:
# empirically, 10,000 gives complete accuracy at ~200 qubits
num_information_sets = 10000
# Do the first way round...
with Path.open(Path(input_filename), "w") as f:
f.write('LoadPackage("QDistRnd");; \nF:=GF(2);;\n')
str_PX = "Hx:=One(F)*["
for i in range(C.PX.shape[0]):
str_row = "["
int_row = list(C.PX[i])
for entry in int_row:
str_row += str(int(entry)) + ","
str_row = str_row[:-1] + "],"
str_PX += str_row
str_PX = str_PX[:-1] + "];;\n"
f.write(str_PX)
str_PZ = "Hz:=One(F)*["
for i in range(C.PZ.shape[0]):
str_row = "["
int_row = list(C.PZ[i])
for entry in int_row:
str_row += str(int(entry)) + ","
str_row = str_row[:-1] + "],"
str_PZ += str_row
str_PZ = str_PZ[:-1] + "];;\n"
f.write(str_PZ)
f.write(
"d:= DistRandCSS(Hz,Hx,"
+ str(num_information_sets)
+ ',0,2 : field:=F);; \nPrintTo("'
+ output_filename
+ '", d);; \nquit;'
)
f.close()
subprocess.run(
[
str(shutil.which(gap_path)),
"--nointeract",
"--norepl",
input_filename,
],
shell=False, # noqa: S603
stdout=subprocess.DEVNULL,
)
d1 = 0
with Path.open(Path(output_filename)) as f:
d1 = int(f.read())
# Do the other way round...
with Path.open(Path(input_filename), "w") as f:
f.write('LoadPackage("QDistRnd");; \nF:=GF(2);;\n')
str_PX = "Hx:=One(F)*["
for i in range(C.PX.shape[0]):
str_row = "["
int_row = list(C.PX[i])
for entry in int_row:
str_row += str(int(entry)) + ","
str_row = str_row[:-1] + "],"
str_PX += str_row
str_PX = str_PX[:-1] + "];;\n"
f.write(str_PX)
str_PZ = "Hz:=One(F)*["
for i in range(C.PZ.shape[0]):
str_row = "["
int_row = list(C.PZ[i])
for entry in int_row:
str_row += str(int(entry)) + ","
str_row = str_row[:-1] + "],"
str_PZ += str_row
str_PZ = str_PZ[:-1] + "];;\n"
f.write(str_PZ)
f.write(
"d:= DistRandCSS(Hx,Hz,"
+ str(num_information_sets)
+ ',0,2 : field:=F);; \nPrintTo("'
+ output_filename
+ '", d);; \nquit;'
)
f.close()
subprocess.run(
[
str(shutil.which(gap_path)),
"--nointeract",
"--norepl",
input_filename,
],
shell=False, # noqa: S603
stdout=subprocess.DEVNULL,
)
d2 = 0
with Path.open(Path(output_filename)) as f:
d2 = int(f.read())
return min(d1, d2)
# Hack to use CSS min distance to find subsystem CSS dressed distance
[docs]
def subsystem_distance_GAP(
C: CSScode,
gauge_Zs: list[list[int]],
gauge_Xs: list[list[int]],
gap_path: str | None = None,
num_information_sets: int | None = None,
input_filename: str | None = None,
output_filename: str | None = None,
) -> int:
"""Gives an upper bound on the dressed distance of a subsystem CSS code using GAP.
If the code is small enough and the number of information sets is high then this
upper bound is tight.
Args:
C: The CSScode.
gauge_Zs: A spanning set of the gauged Z logicals.
gauge_Xs: A spanning set of the gauged X logicals.
gap_path: The absolute path to the GAP directory.
num_information_sets: The number of information sets to make.
input_filename: Which file to use as input to GAP.
output_filename: Which file to receive results from GAP.
Return:
An upper bound on the dressed distance of C as a subsystem code.
"""
if input_filename is None:
input_filename = "input.g"
if output_filename is None:
output_filename = "output.txt"
if gap_path is None:
gap_path = "gap"
if num_information_sets is None:
# empirically, 10,000 typically gives complete accuracy at ~200 qubits
num_information_sets = 10000
# Do the first way round...
with Path.open(Path(input_filename), "w") as f:
f.write('LoadPackage("QDistRnd");; \nF:=GF(2);;\n')
str_PX = "Hx:=One(F)*["
for i in range(C.PX.shape[0]):
str_row = "["
int_row = list(C.PX[i])
for entry in int_row:
str_row += str(int(entry)) + ","
str_row = str_row[:-1] + "],"
str_PX += str_row
str_PX = str_PX[:-1] + "];;\n"
f.write(str_PX)
str_PZ = "Hz:=One(F)*["
for i in range(C.PZ.shape[0]):
str_row = "["
int_row = list(C.PZ[i])
for entry in int_row:
str_row += str(int(entry)) + ","
str_row = str_row[:-1] + "],"
str_PZ += str_row
for gauge_logical in gauge_Zs:
str_row = "["
for entry in gauge_logical:
str_row += str(int(entry)) + ","
str_row = str_row[:-1] + "],"
str_PZ += str_row
str_PZ = str_PZ[:-1] + "];;\n"
f.write(str_PZ)
f.write(
"d:= DistRandCSS(Hz,Hx,"
+ str(num_information_sets)
+ ',0,2 : field:=F);; \nPrintTo("'
+ output_filename
+ '", d);; \nquit;'
)
f.close()
subprocess.run(
[
str(shutil.which(gap_path)),
"--nointeract",
"--norepl",
input_filename,
],
shell=False, # noqa: S603
stdout=subprocess.DEVNULL,
)
d1 = 0
with Path.open(Path(output_filename)) as f:
d1 = int(f.read())
# Do the other way round...
with Path.open(Path(input_filename), "w") as f:
f.write('LoadPackage("QDistRnd");; \nF:=GF(2);;\n')
str_PX = "Hx:=One(F)*["
for i in range(C.PX.shape[0]):
str_row = "["
int_row = list(C.PX[i])
for entry in int_row:
str_row += str(int(entry)) + ","
str_row = str_row[:-1] + "],"
str_PX += str_row
for gauge_logical in gauge_Xs:
str_row = "["
for entry in gauge_logical:
str_row += str(int(entry)) + ","
str_row = str_row[:-1] + "],"
str_PX += str_row
str_PX = str_PX[:-1] + "];;\n"
f.write(str_PX)
str_PZ = "Hz:=One(F)*["
for i in range(C.PZ.shape[0]):
str_row = "["
int_row = list(C.PZ[i])
for entry in int_row:
str_row += str(int(entry)) + ","
str_row = str_row[:-1] + "],"
str_PZ += str_row
str_PZ = str_PZ[:-1] + "];;\n"
f.write(str_PZ)
f.write(
"d:= DistRandCSS(Hx,Hz,"
+ str(num_information_sets)
+ ',0,2 : field:=F);; \nPrintTo("'
+ output_filename
+ '", d);; \nquit;'
)
f.close()
subprocess.run(
[
str(shutil.which(gap_path)),
"--nointeract",
"--norepl",
input_filename,
],
shell=False, # noqa: S603
stdout=subprocess.DEVNULL,
)
d2 = 0
with Path.open(Path(output_filename)) as f:
d2 = int(f.read())
return min(d1, d2)