Skip to content

Commit

Permalink
Port diamond norm from quantumflow to own package
Browse files Browse the repository at this point in the history
  • Loading branch information
gecrooks authored Dec 12, 2020
2 parents c67118f + 163cfce commit 997a2da
Show file tree
Hide file tree
Showing 11 changed files with 253 additions and 733 deletions.
13 changes: 10 additions & 3 deletions .github/workflows/python-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.7', '3.8']
python-version: ['3.7', '3.8', '3.9']

steps:
- uses: actions/checkout@v2
Expand All @@ -26,9 +26,16 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
conda install -c conda-forge cvxpy
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
python -m pip install .[dev] # install package + test dependencies
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
python -m pip install importlib_metadata
python -m pip install quantumflow
# python -m pip install cvxpy
python -m pip install pytest pytest-cov flake8 mypy black isort sphinx sphinxcontrib-bibtex setuptools_scm
python -m pip install .
- name: About
run: |
python -m $(python -Wi setup.py --name).about
Expand Down
12 changes: 4 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

.DEFAULT_GOAL := help

PROJECT = gecrooks_python_template
PROJECT = qf_diamond_norm
FILES = $(PROJECT) docs/conf.py setup.py

help:
Expand All @@ -21,13 +21,9 @@ coverage: ## Report test coverage
@echo

lint: ## Lint check python source
@echo
isort --check -m 3 --tc $(PROJECT) || echo "FAILED isort!"
@echo
black --diff --color $(PROJECT) || echo "FAILED black"
@echo
flake8 $(FILES) || echo "FAILED flake8"
@echo
@isort --check -m 3 --tc $(PROJECT) || echo "isort: FAILED!"
@black --check --quiet $(PROJECT) || echo "black: FAILED!"
@flake8 --quiet --quiet --output-file=/dev/null $(FILES) || echo "flake8: FAILED!"

delint: ## Run isort and black to delint project
@echo
Expand Down
704 changes: 27 additions & 677 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

# -- Project information -----------------------------------------------------

project = "python_mvp"
project = "qf_diamond_norm"
copyright = "2020, Gavin Crooks"
author = "Gavin Crooks"

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Copyright 2019-2021, Gavin E. Crooks and contributors
#
# This source code is licensed under the Apache License, Version 2.0 found in
# This source code is licensed under the Apache License 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.

from .config import __version__ # noqa: F401
from .config import about # noqa: F401
from .info import diamond_norm # noqa: F401
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# Copyright 2019-2021, Gavin E. Crooks and contributors
#
# This source code is licensed under the Apache License, Version 2.0 found in
# This source code is licensed under the Apache License 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.

# Command line interface for the about() function
# > python -m gecrooks_python_template.about
# > python -m qf_diamond_norm.about
#
# NB: This module should not be imported by any other code in the package
# (else we will get multiple import warnings)

if __name__ == "__main__":
import gecrooks_python_template
import qf_diamond_norm

gecrooks_python_template.about()
qf_diamond_norm.about()
21 changes: 12 additions & 9 deletions gecrooks_python_template/config.py → qf_diamond_norm/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Copyright 2019-2021, Gavin E. Crooks and contributors
#
# This source code is licensed under the Apache License, Version 2.0 found in
# This source code is licensed under the Apache License 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.

"""
Expand All @@ -23,38 +23,41 @@
__all__ = ["__version__", "about"]


package_name = "gecrooks_python_template"

try:
__version__ = importlib_metadata.version(package_name) # type: ignore
__version__ = importlib_metadata.version(__package__) # type: ignore
except Exception: # pragma: no cover
# package is not installed
__version__ = "?.?.?"


def about(file: typing.TextIO = None) -> None:
f"""Print information about the configuration
f"""Print information about the package
``> python -m {package_name}.about``
``> python -m {__package__}.about``
Args:
file: Output stream (Defaults to stdout)
"""
metadata = importlib_metadata.metadata(__package__) # type: ignore
print(f"# {metadata['Name']}", file=file)
print(f"{metadata['Summary']}", file=file)
print(f"{metadata['Home-page']}", file=file)

name_width = 24
versions = {}
versions["platform"] = platform.platform(aliased=True)
versions[package_name] = __version__
versions[__package__] = __version__
versions["python"] = sys.version[0:5]

for req in importlib_metadata.requires(package_name): # type: ignore
for req in importlib_metadata.requires(__package__): # type: ignore
name = re.split("[; =><]", req)[0]
try:
versions[name] = importlib_metadata.version(name) # type: ignore
except Exception: # pragma: no cover
pass

print(file=file)
print(f"# Configuration (> python -m {package_name}.about)", file=file)
print("# Configuration", file=file)
for name, vers in versions.items():
print(name.ljust(name_width), vers, file=file)
print(file=file)
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
# Copyright 2019-2021, Gavin E. Crooks and contributors
#
# This source code is licensed under the Apache License, Version 2.0 found in
# This source code is licensed under the Apache License 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.

import glob
import io
import subprocess

import gecrooks_python_template
import qf_diamond_norm as pkg


def test_version() -> None:
assert gecrooks_python_template.__version__
assert pkg.__version__


def test_about() -> None:
out = io.StringIO()
gecrooks_python_template.about(out)
pkg.about(out)
print(out)


def test_about_main() -> None:
rval = subprocess.call(["python", "-m", "gecrooks_python_template.about"])
rval = subprocess.call(["python", "-m", f"{pkg.__name__}.about"])
assert rval == 0


def test_copyright() -> None:
"""Check that source code files contain a copyright line"""
exclude = set(["gecrooks_python_template/version.py"])
for fname in glob.glob("gecrooks_python_template/**/*.py", recursive=True):
exclude = set([f"{pkg.__name__}/version.py"])
for fname in glob.glob(f"{pkg.__name__}/**/*.py", recursive=True):
if fname in exclude:
continue
print("Checking " + fname + " for copyright header")
Expand Down
83 changes: 83 additions & 0 deletions qf_diamond_norm/info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Copyright 2020-, Gavin E. Crooks and contributors
#
# This source code is licensed under the Apache License 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.


"""
====================
Information Measures
====================
Channel Measures
#################
.. autofunction:: diamond_norm
"""

import numpy as np
from quantumflow import Channel

__all__ = ("diamond_norm",)


def diamond_norm(chan0: Channel, chan1: Channel) -> float:
"""Return the diamond norm between two completely positive
trace-preserving (CPTP) superoperators.
Note: Requires "cvxpy" package (and dependencies) to be fully installed.
The calculation uses the simplified semidefinite program of Watrous
[arXiv:0901.4709](http://arxiv.org/abs/0901.4709)
[J. Watrous, [Theory of Computing 5, 11, pp. 217-238
(2009)](http://theoryofcomputing.org/articles/v005a011/)]
"""
# Kudos: Based on MatLab code written by Marcus P. da Silva
# (https://github.com/BBN-Q/matlab-diamond-norm/)
import cvxpy as cvx

if set(chan0.qubits) != set(chan1.qubits):
raise ValueError("Channels must operate on same qubits")

if chan0.qubits != chan1.qubits:
chan1 = chan1.permute(chan0.qubits)

N = chan0.qubit_nb
dim = 2 ** N

choi0 = chan0.choi()
choi1 = chan1.choi()

delta_choi = choi0 - choi1

# Density matrix must be Hermitian, positive semidefinite, trace 1
rho = cvx.Variable([dim, dim], complex=True)
constraints = [rho == rho.H]
constraints += [rho >> 0]
constraints += [cvx.trace(rho) == 1]

# W must be Hermitian, positive semidefinite
W = cvx.Variable([dim ** 2, dim ** 2], complex=True)
constraints += [W == W.H]
constraints += [W >> 0]

constraints += [(W - cvx.kron(np.eye(dim), rho)) << 0]

J = cvx.Parameter([dim ** 2, dim ** 2], complex=True)
objective = cvx.Maximize(cvx.real(cvx.trace(J.H * W)))

prob = cvx.Problem(objective, constraints)

J.value = delta_choi
prob.solve()

dnorm = prob.value * 2

# Diamond norm is between 0 and 2. Correct for floating point errors
dnorm = min(2, dnorm)
dnorm = max(0, dnorm)

return dnorm


# fin
76 changes: 76 additions & 0 deletions qf_diamond_norm/info_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Copyright 2020-, Gavin E. Crooks and contributors
#
# This source code is licensed under the Apache License 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.

import numpy as np
import pytest
import quantumflow as qf

from qf_diamond_norm import diamond_norm


def test_diamond_norm() -> None:
# Test cases borrowed from qutip,
# https://github.com/qutip/qutip/blob/master/qutip/tests/test_metrics.py
# which were in turn generated using QuantumUtils for MATLAB
# (https://goo.gl/oWXhO9)

RTOL = 0.01
chan0 = qf.I(0).aschannel()
chan1 = qf.X(0).aschannel()
dn = diamond_norm(chan0, chan1)
assert np.isclose(2.0, dn, rtol=RTOL)

turns_dnorm = [
[1.000000e-03, 3.141591e-03],
[3.100000e-03, 9.738899e-03],
[1.000000e-02, 3.141463e-02],
[3.100000e-02, 9.735089e-02],
[1.000000e-01, 3.128689e-01],
[3.100000e-01, 9.358596e-01],
]

for turns, target in turns_dnorm:
chan0 = qf.XPow(0.0, 0).aschannel()
chan1 = qf.XPow(turns, 0).aschannel()

dn = diamond_norm(chan0, chan1)
assert np.isclose(target, dn, rtol=RTOL)

hadamard_mixtures = [
[1.000000e-03, 2.000000e-03],
[3.100000e-03, 6.200000e-03],
[1.000000e-02, 2.000000e-02],
[3.100000e-02, 6.200000e-02],
[1.000000e-01, 2.000000e-01],
[3.100000e-01, 6.200000e-01],
]

for p, target in hadamard_mixtures:
tensor = qf.I(0).aschannel().tensor * (1 - p) + qf.H(0).aschannel().tensor * p
chan0 = qf.Channel(tensor, [0])

chan1 = qf.I(0).aschannel()

dn = diamond_norm(chan0, chan1)
assert np.isclose(dn, target, rtol=RTOL)

chan0 = qf.YPow(0.5, 0).aschannel()
chan1 = qf.I(0).aschannel()
dn = diamond_norm(chan0, chan1)
assert np.isclose(dn, np.sqrt(2), rtol=RTOL)

chan0 = qf.CNot(0, 1).aschannel()
chan1 = qf.CNot(1, 0).aschannel()
diamond_norm(chan0, chan1)


def test_diamond_norm_err() -> None:
with pytest.raises(ValueError):
chan0 = qf.I(0).aschannel()
chan1 = qf.I(1).aschannel()
diamond_norm(chan0, chan1)


# fin
Loading

0 comments on commit 997a2da

Please sign in to comment.