"""Conversions between Bell inequality notations."""
import numpy as np
[docs]
def cg_to_fc(cg_mat: np.ndarray, behavior: bool = False) -> np.ndarray:
r"""Convert a Bell functional or behavior from Collins-Gisin (CG) to Full Correlator (FC) notation.
The Collins-Gisin (CG) notation for a Bell functional or behavior is represented by a matrix:
\[
\text{CG} =
\begin{pmatrix}
K & p_B(0|1) & p_B(0|2) & \dots \\
p_A(0|1) & p(00|11) & p(00|12) & \dots \\
p_A(0|2) & p(00|21) & p(00|22) & \dots \\
\vdots & \vdots & \vdots & \ddots
\end{pmatrix}
\]
The Full Correlator (FC) notation is represented by:
\[
\text{FC} =
\begin{pmatrix}
K & \langle B_1 \rangle & \langle B_2 \rangle & \dots \\
\langle A_1 \rangle & \langle A_1 B_1 \rangle & \langle A_1 B_2 \rangle & \dots \\
\langle A_2 \rangle & \langle A_2 B_1 \rangle & \langle A_2 B_2 \rangle & \dots \\
\vdots & \vdots & \vdots & \ddots
\end{pmatrix}
\]
This function converts between these two notations.
Examples:
Consider the CHSH inequality in CG notation for a functional:
\[
\text{CHSH}_{CG} =
\begin{pmatrix}
0 & 0 & 0 \\
0 & 1 & -1 \\
0 & -1 & 1
\end{pmatrix}
\]
Converting to FC notation:
```python exec="1" source="above"
import numpy as np
from toqito.state_opt.bell_notation_conversions import cg_to_fc
chsh_cg = np.array([[0, 0, 0], [0, 1, -1], [0, -1, 1]])
print(cg_to_fc(chsh_cg))
```
Consider a behavior (probability distribution) in CG notation:
\[
P_{CG} =
\begin{pmatrix}
1 & 0.5 & 0.5 \\
0.5 & 0.25 & 0.25 \\
0.5 & 0.25 & 0.25
\end{pmatrix}
\]
Converting to FC notation:
```python exec="1" source="above"
import numpy as np
from toqito.state_opt.bell_notation_conversions import cg_to_fc
p_cg = np.array([[1, 0.5, 0.5], [0.5, 0.25, 0.25], [0.5, 0.25, 0.25]])
print(cg_to_fc(p_cg, behavior=True))
```
Args:
cg_mat: The matrix in Collins-Gisin notation.
behavior: If True, assume input is a behavior (default: False, assume functional).
Returns:
The matrix in Full Correlator notation.
!!! Note
This function is adapted from the QETLAB MATLAB package function ``CG2FC``.
"""
ia = cg_mat.shape[0] - 1
ib = cg_mat.shape[1] - 1
fc_mat = np.zeros((ia + 1, ib + 1))
a_vec = cg_mat[1:, 0]
b_vec = cg_mat[0, 1:]
c_mat = cg_mat[1:, 1:]
if not behavior:
fc_mat[0, 0] = cg_mat[0, 0] + np.sum(a_vec) / 2 + np.sum(b_vec) / 2 + np.sum(c_mat) / 4
fc_mat[1:, 0] = a_vec / 2 + np.sum(c_mat, axis=1) / 4
fc_mat[0, 1:] = b_vec / 2 + np.sum(c_mat, axis=0) / 4
fc_mat[1:, 1:] = c_mat / 4
else:
fc_mat[0, 0] = 1
fc_mat[1:, 0] = 2 * a_vec - 1
fc_mat[0, 1:] = 2 * b_vec - 1
fc_mat[1:, 1:] = np.ones((ia, ib)) - 2 * a_vec[:, np.newaxis] - 2 * b_vec[np.newaxis, :] + 4 * c_mat
return fc_mat
[docs]
def fc_to_cg(fc_mat: np.ndarray, behavior: bool = False) -> np.ndarray:
r"""Convert a Bell functional or behavior from Full Correlator (FC) to Collins-Gisin (CG) notation.
The Full Correlator (FC) notation is represented by:
\[
\text{FC} =
\begin{pmatrix}
K & \langle B_1 \rangle & \langle B_2 \rangle & \dots \\
\langle A_1 \rangle & \langle A_1 B_1 \rangle & \langle A_1 B_2 \rangle & \dots \\
\langle A_2 \rangle & \langle A_2 B_1 \rangle & \langle A_2 B_2 \rangle & \dots \\
\vdots & \vdots & \vdots & \ddots
\end{pmatrix}
\]
The Collins-Gisin (CG) notation for a Bell functional or behavior is represented by a matrix:
\[
\text{CG} =
\begin{pmatrix}
K & p_B(0|1) & p_B(0|2) & \dots \\
p_A(0|1) & p(00|11) & p(00|12) & \dots \\
p_A(0|2) & p(00|21) & p(00|22) & \dots \\
\vdots & \vdots & \vdots & \ddots
\end{pmatrix}
\]
This function converts between these two notations.
Examples:
Consider the CHSH inequality in FC notation for a functional:
\[
\text{CHSH}_{FC} =
\begin{pmatrix}
0 & 0 & 0 \\
0 & 1/4 & -1/4 \\
0 & -1/4 & 1/4
\end{pmatrix}
\]
Converting to CG notation:
```python exec="1" source="above"
import numpy as np
from toqito.state_opt.bell_notation_conversions import fc_to_cg
chsh_fc = np.array([[0, 0, 0], [0, 0.25, -0.25], [0, -0.25, 0.25]])
print(fc_to_cg(chsh_fc))
```
Consider a behavior (correlation matrix) in FC notation:
\[
P_{FC} =
\begin{pmatrix}
1 & 0 & 0 \\
0 & 0 & 0 \\
0 & 0 & 0
\end{pmatrix}
\]
Converting to CG notation:
```python exec="1" source="above"
import numpy as np
from toqito.state_opt.bell_notation_conversions import fc_to_cg
p_fc = np.array([[1, 0, 0], [0, 0, 0], [0, 0, 0]])
print(fc_to_cg(p_fc, behavior=True))
```
Args:
fc_mat: The matrix in Full Correlator notation.
behavior: If True, assume input is a behavior (default: False, assume functional).
Returns:
The matrix in Collins-Gisin notation.
!!! Note
This function is adapted from the QETLAB MATLAB package function ``FC2CG``.
"""
ia = fc_mat.shape[0] - 1
ib = fc_mat.shape[1] - 1
cg_mat = np.zeros((ia + 1, ib + 1))
a_vec = fc_mat[1:, 0]
b_vec = fc_mat[0, 1:]
c_mat = fc_mat[1:, 1:]
if not behavior:
cg_mat[0, 0] = fc_mat[0, 0] + np.sum(c_mat) - np.sum(a_vec) - np.sum(b_vec)
cg_mat[1:, 0] = 2 * a_vec - 2 * np.sum(c_mat, axis=1)
cg_mat[0, 1:] = 2 * b_vec - 2 * np.sum(c_mat, axis=0)
cg_mat[1:, 1:] = 4 * c_mat
else:
cg_mat[0, 0] = 1
cg_mat[1:, 0] = (1 + a_vec) / 2
cg_mat[0, 1:] = (1 + b_vec) / 2
cg_mat[1:, 1:] = (np.ones((ia, ib)) + a_vec[:, np.newaxis] + b_vec[np.newaxis, :] + c_mat) / 4
return cg_mat
[docs]
def cg_to_fp(cg_mat: np.ndarray, desc: list[int], behavior: bool = False) -> np.ndarray:
r"""Convert a Bell functional or behavior from Collins-Gisin (CG) to Full Probability (FP) notation.
The Collins-Gisin (CG) notation for a Bell functional or behavior is represented by a matrix
(see :func:`cg_to_fc`). The Full Probability (FP) notation represents the full probability
distribution \(V(a, b, x, y) = P(a, b | x, y)\), the probability of Alice getting outcome
\(a\) (0 to oa-1) and Bob getting outcome \(b\) (0 to ob-1) given inputs \(x\)
(0 to ia-1) and \(y\) (0 to ib-1). This is stored as a 4D numpy array with indices
`V[a, b, x, y]`.
This function converts from CG to FP notation.
Examples:
Consider the CHSH inequality functional in CG notation:
\[
\text{CHSH}_{CG} =
\begin{pmatrix}
0 & 0 & 0 \\
0 & 1 & -1 \\
0 & -1 & 1
\end{pmatrix}
\]
Converting to FP notation (desc = [2, 2, 2, 2]):
```python exec="1" source="above"
import numpy as np
from toqito.state_opt.bell_notation_conversions import cg_to_fp
chsh_cg = np.array([[0, 0, 0], [0, 1, -1], [0, -1, 1]])
desc = [2, 2, 2, 2] # oa, ob, ia, ib
print(cg_to_fp(chsh_cg, desc))
```
Consider a behavior (probability distribution) in CG notation (desc = [2, 2, 2, 2]):
\[
P_{CG} =
\begin{pmatrix}
1 & 0.5 & 0.5 \\
0.5 & 0.25 & 0.25 \\
0.5 & 0.25 & 0.25
\end{pmatrix}
\]
Converting to FP notation:
```python exec="1" source="above"
import numpy as np
from toqito.state_opt.bell_notation_conversions import cg_to_fp
p_cg = np.array([[1, 0.5, 0.5], [0.5, 0.25, 0.25], [0.5, 0.25, 0.25]])
desc = [2, 2, 2, 2]
print(cg_to_fp(p_cg, desc, behavior=True))
```
Args:
cg_mat: The matrix in Collins-Gisin notation.
desc: A list [\(oa\), \(ob\), \(ia\), \(ib\)] describing the number of outputs
(\(oa\), \(ob\)) and inputs (\(ia\), \(ib\)).
behavior: If True, assume input is a behavior (default: False, assume functional).
Returns:
The probability tensor \(V[a, b, x, y]\) in Full Probability notation.
!!! Note
This function is adapted from the QETLAB MATLAB package function ``CG2FP``.
"""
oa, ob, ia, ib = desc
v_mat = np.zeros((oa, ob, ia, ib))
def aindex(a: int, x: int) -> int:
r"""CG matrix row index for Alice's outcome \(a\) (0..\(oa-2\)) and input \(x\) (0..\(ia-1\)).
Returns 1-based index.
"""
return 1 + a + x * (oa - 1)
def bindex(b: int, y: int) -> int:
r"""CG matrix col index for Bob's outcome \(b\) (0..\(ob-2\)) and input \(y\) (0..\(ib-1\)).
Returns 1-based index.
"""
return 1 + b + y * (ob - 1)
if not behavior:
# Functional case logic
k_term = cg_mat[0, 0] / (ia * ib) if ia > 0 and ib > 0 else 0
for x in range(ia):
for y in range(ib):
# Fill V[a, b, x, y] for a < oa-1, b < ob-1
for a in range(oa - 1):
a_term = cg_mat[aindex(a, x), 0] / ib if ib > 0 else 0
for b in range(ob - 1):
b_term = cg_mat[0, bindex(b, y)] / ia if ia > 0 else 0
v_mat[a, b, x, y] = k_term + a_term + b_term + cg_mat[aindex(a, x), bindex(b, y)]
# Fill V[a, ob-1, x, y] for a < oa-1 (last column for Bob)
for a in range(oa - 1):
a_term = cg_mat[aindex(a, x), 0] / ib if ib > 0 else 0
v_mat[a, ob - 1, x, y] = k_term + a_term
# Fill V[oa-1, b, x, y] for b < ob-1 (last row for Alice)
for b in range(ob - 1):
b_term = cg_mat[0, bindex(b, y)] / ia if ia > 0 else 0
v_mat[oa - 1, b, x, y] = k_term + b_term
# Fill V[oa-1, ob-1, x, y] (bottom-right corner)
v_mat[oa - 1, ob - 1, x, y] = k_term
else:
for x in range(ia):
for y in range(ib):
# Calculate slices for CG matrix corresponding to non-last outcomes
# Need 1-based indices for slicing cg_mat
start_row_a = aindex(0, x)
end_row_a = aindex(oa - 2, x) + 1 if oa > 1 else start_row_a
slice_a = slice(start_row_a, end_row_a)
start_col_b = bindex(0, y)
end_col_b = bindex(ob - 2, y) + 1 if ob > 1 else start_col_b
slice_b = slice(start_col_b, end_col_b)
# Get corresponding submatrix or default to zeros/scalars if outputs=1
cg_sub_mat = cg_mat[slice_a, slice_b] if oa > 1 and ob > 1 else np.array([[]])
cg_a_marg = cg_mat[slice_a, 0] if oa > 1 else np.array([])
cg_b_marg = cg_mat[0, slice_b] if ob > 1 else np.array([])
# V(0..oa-2, 0..ob-2, x, y) = p(a,b|xy)
if oa > 1 and ob > 1:
v_mat[0 : oa - 1, 0 : ob - 1, x, y] = cg_sub_mat
# V(0..oa-2, ob-1, x, y) = pA(a|x) - sum_{b'=0..ob-2} p(a,b'|xy)
if oa > 1:
sum_b = np.sum(cg_sub_mat, axis=1) if ob > 1 else np.zeros(oa - 1)
v_mat[0 : oa - 1, ob - 1, x, y] = cg_a_marg - sum_b
# V(oa-1, 0..ob-2, x, y) = pB(b|y) - sum_{a'=0..oa-2} p(a',b|xy)
if ob > 1:
sum_a = np.sum(cg_sub_mat, axis=0) if oa > 1 else np.zeros(ob - 1)
v_mat[oa - 1, 0 : ob - 1, x, y] = cg_b_marg - sum_a
# V(oa-1, ob-1, x, y) = 1 - sum pA(a|x) - sum pB(b|y) + sum p(ab|xy)
sum_a_marg = np.sum(cg_a_marg)
sum_b_marg = np.sum(cg_b_marg)
sum_ab_joint = np.sum(cg_sub_mat)
v_mat[oa - 1, ob - 1, x, y] = (
cg_mat[0, 0] # Should be 1 for behavior
- sum_a_marg
- sum_b_marg
+ sum_ab_joint
)
return v_mat
[docs]
def fc_to_fp(fc_mat: np.ndarray, behavior: bool = False) -> np.ndarray:
r"""Convert a Bell functional or behavior from Full Correlator (FC) to Full Probability (FP) notation.
Assumes binary outcomes (\(oa=2\), \(ob=2\)) corresponding to physical values +1 and -1.
The FP tensor indices \(a, b = 0, 1\) correspond to outcomes \(+1, -1\) respectively.
The Full Correlator (FC) notation is represented by a matrix (see :func:`.fc_to_cg`).
The Full Probability (FP) notation represents the full probability distribution
\(V(a, b, x, y) = P(\text{out}_A=a', \text{out}_B=b' | x, y)\),
where \(a=0 \rightarrow a'=+1\), \(a=1 \rightarrow a'=-1\) (similarly for \(b\)),
stored as a 4D numpy array \(V[a, b, x, y]\).
This function converts from FC to FP notation.
Examples:
Consider the CHSH inequality functional in FC notation:
\[
\text{CHSH}_{FC} =
\begin{pmatrix}
0 & 0 & 0 \\
0 & 1/4 & -1/4 \\
0 & -1/4 & 1/4
\end{pmatrix}
\]
Converting to FP notation:
```python exec="1" source="above"
import numpy as np
from toqito.state_opt.bell_notation_conversions import fc_to_fp
chsh_fc = np.array([[0, 0, 0], [0, 0.25, -0.25], [0, -0.25, 0.25]])
print(fc_to_fp(chsh_fc))
```
Consider a behavior (correlation matrix) in FC notation (e.g., from PR box):
Note: This FC matrix corresponds to the PR box *after* applying ``fp_to_fc(pr_box, behavior=True)``,
which uses the QETLAB convention of averaging marginal correlators.
\[
P_{FC} =
\begin{pmatrix}
1 & 0 & 0 \\
0 & 1/\sqrt{2} & 1/\sqrt{2} \\
0 & 1/\sqrt{2} & -1/\sqrt{2}
\end{pmatrix}
\]
Converting to FP notation:
```python exec="1" source="above"
import numpy as np
from toqito.state_opt.bell_notation_conversions import fc_to_fp
p_fc = np.array([[1, 0, 0], [0, 1/np.sqrt(2), 1/np.sqrt(2)], [0, 1/np.sqrt(2), -1/np.sqrt(2)]])
print(fc_to_fp(p_fc, behavior=True))
```
Args:
fc_mat: The matrix in Full Correlator notation.
behavior: If True, assume input is a behavior (default: False, assume functional).
Returns:
The probability tensor \(V[a, b, x, y]\) in Full Probability notation (oa=2, ob=2).
!!! Note
This function is adapted from the QETLAB MATLAB package function ``FC2FP`` [@QETLAB].
For `behavior=True`, it applies the standard formula relating probabilities to correlators:
\(P(a', b' | x, y) = (1 + a'\langle A_x \rangle + b'\langle B_y \rangle +\)
\(a'b'\langle A_x B_y \rangle) / 4\),
where \(a', b' \in \{+1, -1\}\).
Crucially, it uses the values \(\langle A_x \rangle\) and \(\langle B_y \rangle\) directly
from the input ``fc_mat``. If this input matrix was generated using a convention where these
entries represent *averaged* marginal correlators (like the output of ``fp_to_fc(..., behavior=True)``),
the resulting FP tensor might not represent a valid probability distribution (e.g., entries could be negative).
"""
ia = fc_mat.shape[0] - 1
ib = fc_mat.shape[1] - 1
# Assumes oa=2, ob=2 based on FC notation structure
oa, ob = 2, 2
v_mat = np.zeros((oa, ob, ia, ib))
if not behavior:
# Functional case logic
k_term = fc_mat[0, 0] / (ia * ib) if ia > 0 and ib > 0 else 0
for x in range(ia):
ax_term = fc_mat[1 + x, 0] / ib if ib > 0 else 0
for y in range(ib):
by_term = fc_mat[0, 1 + y] / ia if ia > 0 else 0
axby_term = fc_mat[1 + x, 1 + y]
# V[0,0,x,y] = P(++,xy) coefficient
v_mat[0, 0, x, y] = k_term + ax_term + by_term + axby_term
# V[0,1,x,y] = P(+-,xy) coefficient
v_mat[0, 1, x, y] = k_term + ax_term - by_term - axby_term
# V[1,0,x,y] = P(-+,xy) coefficient
v_mat[1, 0, x, y] = k_term - ax_term + by_term - axby_term
# V[1,1,x,y] = P(--,xy) coefficient
v_mat[1, 1, x, y] = k_term - ax_term - by_term + axby_term
else:
for x in range(ia):
ax_val = fc_mat[1 + x, 0]
for y in range(ib):
by_val = fc_mat[0, 1 + y]
axby_val = fc_mat[1 + x, 1 + y]
# V[0,0,x,y] = P(++,xy) = (1 + <Ax> + <By> + <AxBy>)/4
v_mat[0, 0, x, y] = 1 + ax_val + by_val + axby_val
# V[0,1,x,y] = P(+-,xy) = (1 + <Ax> - <By> - <AxBy>)/4
v_mat[0, 1, x, y] = 1 + ax_val - by_val - axby_val
# V[1,0,x,y] = P(-+,xy) = (1 - <Ax> + <By> - <AxBy>)/4
v_mat[1, 0, x, y] = 1 - ax_val + by_val - axby_val
# V[1,1,x,y] = P(--,xy) = (1 - <Ax> - <By> + <AxBy>)/4
v_mat[1, 1, x, y] = 1 - ax_val - by_val + axby_val
v_mat = v_mat / 4
return v_mat
[docs]
def fp_to_cg(v_mat: np.ndarray, behavior: bool = False) -> np.ndarray:
r"""Convert a Bell functional or behavior from Full Probability (FP) to Collins-Gisin (CG) notation.
The Full Probability (FP) notation represents the full probability distribution
\(V(a, b, x, y) = P(a, b | x, y)\), where \(a\) (0 to \(oa-1\)), \(b\) (0 to \(ob-1\)) are
outcomes and \(x\) (0 to \(ia-1\)), \(y\) (0 to \(ib-1\)) are inputs. It's stored as a 4D
numpy array \(V[a, b, x, y]\). The Collins-Gisin (CG) notation for a Bell functional or
behavior is represented by a matrix (see :[cg_to_fc][toqito.state_opt.bell_notation_conversions.cg_to_fc]).
This function converts from FP to CG notation.
Examples:
Consider the CHSH inequality functional in FP notation:
(Here V represents coefficients, not probabilities)
```python exec="1" source="above"
import numpy as np
from toqito.state_opt.bell_notation_conversions import fp_to_cg
chsh_fp = np.zeros((2, 2, 2, 2))
chsh_fp[0, 0, 0, 0] = 1
chsh_fp[0, 0, 0, 1] = -1
chsh_fp[0, 0, 1, 0] = -1
chsh_fp[0, 0, 1, 1] = 1
print(fp_to_cg(chsh_fp))
```
Consider a behavior (probability distribution) in FP notation (standard PR box):
```python exec="1" source="above"
import numpy as np
from toqito.state_opt.bell_notation_conversions import fp_to_cg
pr_box = np.zeros((2, 2, 2, 2))
pr_box[0, 0, 0, 0] = 0.5 # p(0,0|0,0)
pr_box[1, 1, 0, 0] = 0.5 # p(1,1|0,0)
pr_box[0, 0, 0, 1] = 0.5 # p(0,0|0,1)
pr_box[1, 1, 0, 1] = 0.5 # p(1,1|0,1)
pr_box[0, 0, 1, 0] = 0.5 # p(0,0|1,0)
pr_box[1, 1, 1, 0] = 0.5 # p(1,1|1,0)
pr_box[0, 1, 1, 1] = 0.5 # p(0,1|1,1)
pr_box[1, 0, 1, 1] = 0.5 # p(1,0|1,1)
print(fp_to_cg(pr_box, behavior=True))
```
Args:
v_mat: The probability tensor \(V[a, b, x, y]\) in Full Probability notation.
behavior: If True, assume input is a behavior (default: False, assume functional).
Returns:
The matrix in Collins-Gisin notation.
!!! Note
This function is adapted from the QETLAB MATLAB package function ``FP2CG``.
For ``behavior=True``, it uses the QETLAB convention for calculating marginal probabilities,
summing over the other party's outcomes for a *fixed* input setting of the other party
(\(y=0\) for Alice's marginal \(p_A(a|x)\), \(x=0\) for Bob's marginal \(p_B(b|y)\)).
"""
oa, ob, ia, ib = v_mat.shape
alice_pars = max(0, ia * (oa - 1)) + 1 if oa > 0 else 0
bob_pars = max(0, ib * (ob - 1)) + 1 if ob > 0 else 0
if alice_pars == 0 or bob_pars == 0:
if behavior:
raise ValueError("behavior case requires non-zero outputs (oa>0, ob>0).")
cg_mat = np.zeros((alice_pars, bob_pars))
return cg_mat
cg_mat = np.zeros((alice_pars, bob_pars))
def _cg_row_index(a: int, x: int) -> int:
r"""Calculate 0-based CG matrix row index for Alice.
Outcome \(a\) (0..\(oa-2\)) and input \(x\) (0..\(ia-1\)).
"""
return 1 + a + x * (oa - 1)
def _cg_col_index(b: int, y: int) -> int:
r"""Calculate 0-based CG matrix col index for Bob.
Outcome \(b\) (0..\(ob-2\)) and input \(y\) (0..\(ib-1\)).
"""
return 1 + b + y * (ob - 1)
if not behavior:
# Functional case logic
cg_mat[0, 0] = np.sum(v_mat[oa - 1, ob - 1, :, :])
if oa > 1:
for a in range(oa - 1):
for x in range(ia):
cg_mat[_cg_row_index(a, x), 0] = np.sum(v_mat[a, ob - 1, x, :] - v_mat[oa - 1, ob - 1, x, :])
if ob > 1:
for b in range(ob - 1):
for y in range(ib):
cg_mat[0, _cg_col_index(b, y)] = np.sum(v_mat[oa - 1, b, :, y] - v_mat[oa - 1, ob - 1, :, y])
if oa > 1 and ob > 1:
for a in range(oa - 1):
for b in range(ob - 1):
for x in range(ia):
for y in range(ib):
row_idx_0based = _cg_row_index(a, x)
col_idx_0based = _cg_col_index(b, y)
cg_mat[row_idx_0based, col_idx_0based] = (
v_mat[a, b, x, y]
- v_mat[a, ob - 1, x, y]
- v_mat[oa - 1, b, x, y]
+ v_mat[oa - 1, ob - 1, x, y]
)
else:
cg_mat[0, 0] = 1.0 # Set K=1 for behavior
if oa > 1 and ib > 0:
for x in range(ia):
for a in range(oa - 1):
target_row_0based = _cg_row_index(a, x)
cg_mat[target_row_0based, 0] = np.sum(v_mat[a, :, x, 0])
elif oa > 1 and ib == 0:
pass # Already initialized to 0
if ob > 1 and ia > 0:
for y in range(ib):
for b in range(ob - 1):
target_col_0based = _cg_col_index(b, y)
cg_mat[0, target_col_0based] = np.sum(v_mat[:, b, 0, y])
elif ob > 1 and ia == 0:
pass
if oa > 1 and ob > 1:
for x in range(ia):
for y in range(ib):
for a in range(oa - 1):
target_row_0based = _cg_row_index(a, x)
for b in range(ob - 1):
target_col_0based = _cg_col_index(b, y)
cg_mat[target_row_0based, target_col_0based] = v_mat[a, b, x, y]
return cg_mat
[docs]
def fp_to_fc(v_mat: np.ndarray, behavior: bool = False) -> np.ndarray:
r"""Convert a Bell functional or behavior from Full Probability (FP) to Full Correlator (FC) notation.
Assumes binary outcomes (\(oa=2\), \(ob=2\)). The FP tensor indices \(a, b = 0, 1\)
correspond to physical outcomes \(+1, -1\) respectively.
The Full Probability (FP) notation represents the full probability distribution
\(V(a, b, x, y) = P(\text{out}_A=a', \text{out}_B=b' | x, y)\), where
\(a=0 \rightarrow a'=+1\), \(a=1 \rightarrow a'=-1\) (similarly for \(b\)),
stored as a 4D numpy array \(V[a, b, x, y]\).
The Full Correlator (FC) notation is represented by a matrix
(see [fc_to_cg][toqito.state_opt.bell_notation_conversions.fc_to_cg]).
This function converts from FP to FC notation.
Examples:
Consider the CHSH inequality functional in FP notation:
(Here V represents coefficients, not probabilities)
```python exec="1" source="above"
import numpy as np
from toqito.state_opt.bell_notation_conversions import fp_to_fc, fc_to_fp
chsh_fc = np.array([[0, 0, 0], [0, 0.25, -0.25], [0, -0.25, 0.25]])
chsh_fp = fc_to_fp(chsh_fc)
print(fp_to_fc(chsh_fp))
```
Consider a behavior (probability distribution) in FP notation (standard PR box):
```python exec="1" source="above"
import numpy as np
from toqito.state_opt.bell_notation_conversions import fp_to_fc
pr_box = np.zeros((2, 2, 2, 2))
pr_box[0, 0, 0, 0] = 0.5 # p(0,0|0,0)
pr_box[1, 1, 0, 0] = 0.5 # p(1,1|0,0)
pr_box[0, 0, 0, 1] = 0.5 # p(0,0|0,1)
pr_box[1, 1, 0, 1] = 0.5 # p(1,1|0,1)
pr_box[0, 0, 1, 0] = 0.5 # p(0,0|1,0)
pr_box[1, 1, 1, 0] = 0.5 # p(1,1|1,0)
pr_box[0, 1, 1, 1] = 0.5 # p(0,1|1,1)
pr_box[1, 0, 1, 1] = 0.5 # p(1,0|1,1)
print(fp_to_fc(pr_box, behavior=True))
```
Args:
v_mat: The probability tensor \(V[a, b, x, y]\)
in Full Probability notation (:math:`oa=2`, :math:`ob=2`).
behavior: If True, assume input is a behavior (default: False, assume functional).
Returns:
The matrix in Full Correlator notation.
!!! Note
This function is adapted from the QETLAB MATLAB package function ``FP2FC``.
For ``behavior=True``, it calculates the *average* marginal correlators \(\langle A_x \rangle\)
and \(\langle B_y \rangle\) by summing over the other party's inputs
and dividing by the number of inputs (\(ib\) or \(ia\)).
The joint correlators \(\langle A_x B_y \rangle\) are calculated directly for each (\(x\), \(y\)).
"""
oa, ob, ia, ib = v_mat.shape
if oa != 2 or ob != 2:
raise ValueError("FP to FC conversion currently only supports binary outcomes (oa=2, ob=2).")
fc_mat = np.zeros((1 + ia, 1 + ib))
fc_mat[0, 0] = np.sum(v_mat) # K' = sum(V), used for functional case
for x in range(ia):
fc_mat[x + 1, 0] = np.sum(v_mat[0, :, x, :]) - np.sum(v_mat[1, :, x, :])
for y in range(ib):
fc_mat[0, 1 + y] = np.sum(v_mat[:, 0, :, y]) - np.sum(v_mat[:, 1, :, y])
# Calculate E[AxBy] for each (x,y) -> FC[x+1, y+1] component
for x in range(ia):
for y in range(ib):
fc_mat[x + 1, y + 1] = v_mat[0, 0, x, y] - v_mat[0, 1, x, y] - v_mat[1, 0, x, y] + v_mat[1, 1, x, y]
if not behavior:
fc_mat = fc_mat / 4
else:
fc_mat[0, 0] = 1
if ib > 0:
fc_mat[1:, 0] = fc_mat[1:, 0] / ib
else:
# If no Bob inputs, average marginal <Ax> is 0.
fc_mat[1:, 0] = 0
if ia > 0:
fc_mat[0, 1:] = fc_mat[0, 1:] / ia
else:
# If no Alice inputs, average marginal <By> is 0.
fc_mat[0, 1:] = 0
return fc_mat