Skip to content

Commit 9386445

Browse files
add circuit inverse method
1 parent f210361 commit 9386445

File tree

5 files changed

+84
-5
lines changed

5 files changed

+84
-5
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
- Add `enable_dlpack` option on interfaces and torchnn
1616

17+
- Add `inverse` method for Circuit (#26)
18+
1719
### Changed
1820

1921
- Refactor `interfaces` code as a submodule and add pytree support for args

examples/hybrid_gpu_pipeline.py

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ def qpreds(x, weights):
7676
use_jit=True,
7777
enable_dlpack=True,
7878
)
79+
# enable_dlpack = False for old version of ML libs
7980

8081

8182
model = torch.nn.Sequential(quantumnet, torch.nn.Linear(9, 1), torch.nn.Sigmoid())

tensorcircuit/circuit.py

+34
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,40 @@ def from_qir(
713713
c = cls._apply_qir(c, qir)
714714
return c
715715

716+
def inverse(self, circuit_params: Optional[Dict[str, Any]] = None) -> "Circuit":
717+
"""
718+
inverse the circuit, return a new inversed circuit
719+
720+
:EXAMPLE:
721+
722+
>>> c = tc.Circuit(2)
723+
>>> c.H(0)
724+
>>> c.rzz(1, 2, theta=0.8)
725+
>>> c1 = c.inverse()
726+
727+
:param circuit_params: keywords dict for initialization the new circuit, defaults to None
728+
:type circuit_params: Optional[Dict[str, Any]], optional
729+
:return: the inversed circuit
730+
:rtype: Circuit
731+
"""
732+
if circuit_params is None:
733+
circuit_params = {}
734+
if "nqubits" not in circuit_params:
735+
circuit_params["nqubits"] = self._nqubits
736+
737+
c = type(self)(**circuit_params)
738+
for d in reversed(self._qir):
739+
if "parameters" not in d:
740+
c.apply_general_gate_delayed(d["gatef"].adjoint(), d["name"], mpo=d["mpo"])( # type: ignore
741+
c, *d["index"], split=d["split"] # type: ignore
742+
)
743+
else:
744+
c.apply_general_variable_gate_delayed(d["gatef"].adjoint(), d["name"], mpo=d["mpo"])( # type: ignore
745+
c, *d["index"], **d["parameters"], split=d["split"] # type: ignore
746+
)
747+
748+
return c
749+
716750
def append_from_qir(self, qir: List[Dict[str, Any]]) -> None:
717751
"""
718752
Apply the ciurict in form of quantum intermediate representation after the current cirucit.

tensorcircuit/gates.py

+33-5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from scipy.stats import unitary_group
1414

1515
from .cons import backend, dtypestr, npdtype
16+
from .backends import get_backend # type: ignore
1617

1718
thismodule = sys.modules[__name__]
1819

@@ -234,14 +235,25 @@ def __init__(
234235
self.ctrl = ctrl
235236

236237
def __call__(self, *args: Any, **kws: Any) -> Gate:
238+
# m = array_to_tensor(self.m)
239+
# m = backend.cast(m, dtypestr)
237240
m = self.m.astype(npdtype)
238241
return Gate(deepcopy(m), name=self.n)
239242

240-
def adjoint(self, *args: Any, **kws: Any) -> "GateF":
241-
m = self.__call__(*args, **kws)
242-
ma = backend.adjoint(m.tensor)
243-
return GateF(ma, self.n + "d", self.ctrl)
244-
# TODO(@refraction-ray): adjoint gate convention finally determined
243+
def adjoint(self) -> "GateF":
244+
m = self.__call__()
245+
npb = get_backend("numpy")
246+
shape0 = npb.shape_tuple(m.tensor)
247+
m0 = npb.reshapem(m.tensor)
248+
ma = npb.adjoint(m0)
249+
if np.allclose(m0, ma, atol=1e-5):
250+
name = self.n
251+
else:
252+
name = self.n + "d"
253+
ma = npb.reshape(ma, shape0)
254+
return GateF(ma, name, self.ctrl)
255+
256+
# TODO(@refraction-ray): adjoint gate convention finally determined
245257

246258
def controlled(self, *args: Any, **kws: Any) -> "GateF":
247259
def f(*args: Any, **kws: Any) -> Any:
@@ -304,6 +316,22 @@ def __init__(
304316
def __call__(self, *args: Any, **kws: Any) -> Gate:
305317
return self.f(*args, **kws)
306318

319+
def adjoint(self) -> "GateVF":
320+
def f(*args: Any, **kws: Any) -> Gate:
321+
m = self.__call__(*args, **kws)
322+
npb = get_backend("numpy")
323+
shape0 = npb.shape_tuple(m.tensor)
324+
m0 = npb.reshapem(m.tensor)
325+
ma = npb.adjoint(m0)
326+
if np.allclose(m0, ma, atol=1e-5):
327+
name = self.n
328+
else:
329+
name = self.n + "d"
330+
ma = npb.reshape(ma, shape0)
331+
return Gate(ma, name)
332+
333+
return GateVF(f, self.n + "d", self.ctrl)
334+
307335

308336
def meta_gate() -> None:
309337
"""

tests/test_circuit.py

+14
Original file line numberDiff line numberDiff line change
@@ -1017,3 +1017,17 @@ def construct_c(pbc=True):
10171017
m1 = c.expectation_ps(z=[0], enable_lightcone=True)
10181018
m2 = c.expectation_ps(z=[0])
10191019
np.testing.assert_allclose(m1, m2, atol=1e-5)
1020+
1021+
1022+
@pytest.mark.parametrize("backend", [lf("npb"), lf("tfb"), lf("jaxb")])
1023+
def test_circuit_inverse(backend):
1024+
inputs = np.random.uniform(size=[8])
1025+
inputs /= np.linalg.norm(inputs)
1026+
c = tc.Circuit(3, inputs=inputs)
1027+
c.H(1)
1028+
c.rx(0, theta=0.5)
1029+
c.cnot(1, 2)
1030+
c.rzz(0, 2, theta=-0.8)
1031+
c1 = c.inverse()
1032+
c.append(c1)
1033+
np.testing.assert_allclose(c.state(), inputs, atol=1e-5)

0 commit comments

Comments
 (0)