Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Jordan Wigner, Visualize #271

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1cc054a
Modified the custom_jw_transform.py so that it works without specifyi…
Rick0317 Dec 21, 2022
55d6fa3
test cases using pytest to test custom_jw_transform are created in te…
Rick0317 Dec 21, 2022
efece14
Modified some of the failed test cases
Rick0317 Dec 21, 2022
47ac768
Merge branch 'master' into visualize
Rick0317 Feb 28, 2023
d6de495
Visualization for circuit.
Rick0317 Feb 28, 2023
bb61629
Fixed the reimporing openfermion probelm
Rick0317 Mar 1, 2023
48474b7
Resolved issues commented in the pull requests
Rick0317 Mar 10, 2023
10092f7
Delete edge_vertex_ops.py
Rick0317 Mar 10, 2023
76a97f0
Update test_array.py
Rick0317 Mar 10, 2023
3982199
Merge pull request #1 from Rick0317/visualize_mar10
Rick0317 Mar 10, 2023
60badf3
Merge branch 'devel' into visualize
kottmanj Mar 16, 2023
c418407
Merge branch 'devel' into visualize
kottmanj Mar 17, 2023
e9f302a
Resolved some issues
Rick0317 Mar 27, 2023
4a4545d
Resolved some issues
Rick0317 Mar 28, 2023
0b58e3e
Merge pull request #2 from Rick0317/test_files
Rick0317 Mar 28, 2023
d619c0f
Merge branch 'devel' into visualize
Rick0317 Mar 28, 2023
60f811e
Test for visualization
Rick0317 Mar 28, 2023
9d707cc
Merge pull request #3 from Rick0317/revise_mar28
Rick0317 Mar 28, 2023
1b1b401
Revised code accordingly to comments on pull request
Rick0317 Mar 28, 2023
fbcd681
parameter name changed
Rick0317 Mar 28, 2023
1ac6a5e
Merge pull request #4 from Rick0317/small_change
Rick0317 Mar 28, 2023
054cf21
Merge branch 'devel' into visualize
kottmanj Sep 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions src/tequila/circuit/visualize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import importlib.util
import tequila
from typing import Optional, List

if importlib.util.find_spec("networkx") is not None:
import networkx

if importlib.util.find_spec("matplotlib") is not None:
import matplotlib.pyplot as plt
import matplotlib.figure as figure

def visualize(qubit_hamiltonian: tequila.QubitHamiltonian,
circuit: Optional[tequila.QCircuit] = None,
connectivity: Optional[List[tuple]] = None,
filename: Optional[str] = None) -> figure.Figure:
"""
Precondition:
The maximum number of qubits is 10 at the moment (Feb24, 2023)

Post condition:
The graph of the qubit_hamiltonian is displayed
or is stored in filename

One thing to note is that if you are using command-line interface,
the plot might not be successfully shown

so it's better idea to save it as an image.
=== parameters ===
qubit_hamiltonian: A QubitHamiltonian representation of pauli operators
circuit: A QCircuit that corresponds to the
connectivity: networkx.Graph.edges that show the connectivity of qubits
A nested list should be provided for this argument.
filename: str format of file name
to which the plotted graph will be exported. The file will be a png format

=== return ===
matplotlib.figure

=== sample usages ===
visualize(tequila.QubitHamiltonian("X(0)X(5)Y(3)")))
*** A graph with nodes 0 and 5 having colour red
and node 3 having colour green ***

visualize(tequila.QubitHamiltonian("X(0)X(5)Y(3)"),
circuit=tequila.gates.X(0) + tequila.gates.CNOT(0, 5) + tequila.gates.Y(3))
*** A graph with nodes 0 and 5 having color red and
node 3 having colour green with edge 0 and 5 exists ***

visualize(tequila.QubitHamiltonian("X(0)X(5)Y(3)"),
connectivity=[(0, 0), (0, 5), (3, 3)])
*** A graph with nodes 0 and 5 having color red and
node 3 having colour green with edge 0 and 5 exists ***

visualize(tequila.QubitHamiltonian("X(0)X(5)Y(3)"),
connectivity=[(0, 0), (0, 5), (3, 3)],
filename="test_system")
*** Exported an image of a graph with nodes 0 and 5 having color red and
node 3 having colour green with edge 0 and 5 exists to test_system.png ***
"""

if(len(qubit_hamiltonian.qubit_operator.terms)) != 1:
raise tequila.TequilaException("The input qubit_operator"
" should have length 1")

qh = next(iter(qubit_hamiltonian.qubit_operator.terms))
graph = networkx.Graph()
graph, pos = _draw_basics(graph)
if circuit is None and connectivity is None:
graph, pos = _visualize_helper(qh, graph, pos)

elif connectivity is None:
graph, pos = _visualize_helper(qh, graph, pos, list(circuit.to_networkx().edges))

elif circuit is None:
graph, pos = _visualize_helper(qh, graph, pos, connectivity)

if filename is None:
return plt.figure()
if filename is not None:
plt.savefig(filename+".png", format="PNG")
return plt.figure()


def _draw_basics(graph):
"""
A helper function for visualize() function.
This function sets up the basic graph to be used.
"""
length = 10
for site in range(length):
graph.add_node(site)
pos = networkx.spring_layout(graph, seed=3113794652)
networkx.draw_networkx_nodes(
graph, pos, node_color="#FFFFFF", edgecolors="#000000")
return graph, pos


def _visualize_helper(qh_list: List[tequila.QubitHamiltonian],
graph, pos, connection: Optional[list] = None):
"""
A helper function for visualize() function.
This function visualize the graph based on the QubitHamiltonian and connection
"""
for pair in qh_list:
if pair[1] == 'X':
networkx.draw_networkx_nodes(
graph, pos,
nodelist=[pair[0]], node_color="tab:red")
elif pair[1] == 'Y':
networkx.draw_networkx_nodes(
graph, pos,
nodelist=[pair[0]], node_color="tab:green")
elif pair[1] == 'Z':
networkx.draw_networkx_nodes(
graph, pos,
nodelist=[pair[0]], node_color="tab:blue")
if connection is not None:
for edge in connection:
if edge[0] != edge[1]:
networkx.draw_networkx_edges(
graph, pos, edgelist=[edge], width=5)
networkx.draw_networkx_labels(
graph, pos,
labels={0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9})
return graph, pos
232 changes: 232 additions & 0 deletions src/tequila/hamiltonian/custom_jw_transform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import typing
from tequila.hamiltonian.qubit_hamiltonian import QubitHamiltonian


def _custom_transform(fermion: str, qubits: list) -> QubitHamiltonian:
"""
This method maps fermion to qubits.

Precondition:
The last index of qubits should be greater than
the site value of fermion.
For instance fermion: "3", qubits: [1, 2, 3, 4] is allowed, but
fermion: "3", qubits: [1, 2, 3] is not allowed

Post condition:
QubitHamiltonian of the qubits representation is returned

=== Parameter ===
fermion: string representation of fermion operator to be mapped
qubits: list of qubits on which Paulis are applied
e.g. cjw.custom_transform("3^", [0, 1, 3, 5])

=== Return ===
QubitHamiltonian
"""
if not _is_input_compatible(fermion, qubits):
raise Exception("Incorrect Input Format")

qubit_hamiltonian = QubitHamiltonian()
operator = fermion[0]
site = qubits[int(operator[0])]
x_op = 'X(' + str(site) + ')'
pauli_x = QubitHamiltonian(x_op)
y_op = 'Y(' + str(site) + ')'
pauli_y = QubitHamiltonian(y_op)
if fermion[-1] == '^':
pauli_z = QubitHamiltonian("1")
for qubit in range(0, int(operator)):
z_op = 'Z(' + str(qubits[qubit]) + ')'
pauli_z *= QubitHamiltonian(z_op)

qubit_hamiltonian += pauli_z * pauli_x * 0.5

qubit_hamiltonian += pauli_z * pauli_y * -0.5j

else:
pauli_z = QubitHamiltonian("1")
for qubit in range(0, int(operator)):
z_op = 'Z(' + str(qubits[qubit]) + ')'
pauli_z *= QubitHamiltonian(z_op)

qubit_hamiltonian += pauli_z * pauli_x * 0.5
qubit_hamiltonian += pauli_z * pauli_y * 0.5j

return qubit_hamiltonian


def _custom_transform_one_fermion(
fermion: str, qubits: list,
qubit_map: typing.Optional[dict] = None) -> QubitHamiltonian:
"""
This method maps one fermion operator to qubit with specification of
the mapping allowed

Precondition:
The last index of qubits should be greater than
the site value of fermion. (Example in custom_transform method)
The qubit specified by qubit_map should be contained in qubits and
should not be duplicated in qubits.

Post condition:
Return QubitHamiltonian based on the custom transformation the user
specified with qubits and qubit_map

=== Parameter ===
fermion: str e.g. '3' '4^'
qubits: list[int]
qubit_map: dict[str, int], default=None

=== Return ===
QubitHamiltonian

"""

qubit_hamiltonian = QubitHamiltonian()
revised_qubits = qubits
mapped_qubits = False
if qubit_map is None:
qubit_hamiltonian += _custom_transform(fermion, qubits)
return qubit_hamiltonian
for key in qubit_map:
if key == fermion and qubits.count(qubit_map[key]) > 1:
raise Exception("Duplicated qubits. Either change "
"the mapped qubit or qubits list")
if key == fermion and qubit_map[key] not in qubits:
raise Exception("The mapped qubit should be contained in "
"qubits list")
if key == fermion:
revised_qubits.remove(qubit_map[key])
revised_qubits.insert(int(key[0]), qubit_map[key])
qubit_hamiltonian += _custom_transform(
fermion, revised_qubits)
mapped_qubits = True
if not mapped_qubits:
qubit_hamiltonian += _custom_transform(fermion, qubits)
return qubit_hamiltonian


def custom_jw_transform(
fermions: typing.Union[str, list], qubits: typing.Optional[list] = None,
qubit_map: typing.Optional[dict] = None) -> QubitHamiltonian:
"""
This method maps multiple fermion operators to specified qubits
Precondition:
Same as custom_transform_one_fermion

Post condition:
Return QubitHamiltonian based on the custom transformation the user
specified with qubits and qubit_map

=== Parameter ===
fermion: str e.g. '3' '3 4^ 6'
qubits: list[list[int]] e.g. [[1, 2, 4, 5], [2, 3, 4, 6], [3, 5, 2, 3]]
qubit_map: dict[str, int], default=None e.g. {'3': 4, '5^': 2}

=== Return ===
QubitHamiltonian
"""

if qubits is None:
if qubit_map is None:
largest_index = _find_largest_index(fermions)
new_qubit_list = [i for i in range(largest_index + 1)]
if _num_of_fermions(fermions) == 1:
return custom_jw_transform(fermions, new_qubit_list, qubit_map)
else:
qubits_list = \
[new_qubit_list for _ in range(_num_of_fermions(fermions))]
return custom_jw_transform(fermions, qubits_list, qubit_map)

if not _is_input_compatible(fermions, qubits):
raise Exception("Incorrect input given (fermions or qubits)."
" Follow the convention for fermions and qubits input")

if isinstance(fermions, str):
if len(fermions) <= 2:
return _custom_transform_one_fermion(fermions, qubits, qubit_map)
fermion_ops = _split_fermions(fermions)
qubit_hamiltonian = _custom_transform_one_fermion(
fermion_ops[0], qubits[0], qubit_map)
for i in range(1, len(fermion_ops)):
qubit_hamiltonian *= _custom_transform_one_fermion(
fermion_ops[i], qubits[i], qubit_map)

return qubit_hamiltonian
if isinstance(fermions, list):
qubit_hamiltonian = _custom_transform_one_fermion(
fermions[0], qubits[0], qubit_map)
for i in range(1, len(fermions)):
qubit_hamiltonian *= _custom_transform_one_fermion(
fermions[i], qubits[i], qubit_map)

return qubit_hamiltonian


def _num_of_fermions(fermions: typing.Union[str, list]) -> int:
"""

"""
if isinstance(fermions, str):
if len(fermions) <= 2:
return 1
return len(_split_fermions(fermions))
elif isinstance(fermions, list):
return len(fermions)


def _find_largest_index(fermions: typing.Union[str, list]) -> int:
if isinstance(fermions, str):
if len(fermions) <= 2:
return int(fermions[0])
fermion_ops = _split_fermions(fermions)
max_ind = 0
for index in fermion_ops:
max_ind = max(max_ind, int(index[0]))
return max_ind
elif isinstance(fermions, list):
max_ind = 0
for index in fermions:
max_ind = max(max_ind, int(index[0]))
return max_ind


def _split_fermions(fermions: str) -> list:
if ", " in fermions:
fermion_ops = fermions.split(", ")
elif " " in fermions:
fermion_ops = fermions.split(" ")
elif "," in fermions:
fermion_ops = fermions.split(",")
elif ":" in fermions:
fermion_ops = fermions.split(":")
else:
raise Exception("Incorrect input format")
return fermion_ops


def _is_input_compatible(
fermion: typing.Union[str, list], qubits: list) -> bool:
"""
Helper function for CustomJordanWigner class
Checks if the inputs, namely the fermion operators are compatible with
qubits in the following ways; the size, ...
"""
if isinstance(fermion, str) and len(fermion) < 3:
if int(fermion[0]) >= len(qubits):
return False
if len(qubits) != len(set(qubits)):
return False
return True
elif isinstance(fermion, str) and len(fermion) >= 3:
for maps in qubits:
if not isinstance(maps, list):
return False
fermion_ops = _split_fermions(fermion)
if len(fermion_ops) > len(qubits):
return False
for i in range(len(fermion_ops)):
if not _is_input_compatible(fermion_ops, qubits[i]):
return False
return True
return True
Loading
Loading