Repository: quantumlib/OpenFermion
Pull request #63 (2017-10-19 14:00:02)

First commit of _trotter_qasm_conversion
Methods for taking in arbitrary quantum operator (composed of Pauli strings), and giving Trotter-Suzuki decomposition in QASM format. -- ES Fried and NPD Sawaya

babbush: Thanks for this contribution Nico and Eric! I am traveling until this evening so I am assigning the code review to @jarrodmcc. A few quick comments though. First, it looks like tests are failing in python3. This should be easy to fix. Likely there are some functions you are using that are not forward compatible; e.g. "print my_string" instead of "print(my_string)". Coverage is slightly too low but its pretty close so I am sure Eric's extra tests will fix things. Next, please download and run the PEP 8 linter and look for violations. This helps ensure compliance with style and will point out some things that jump out right away such as having multiple blank lines at the end of a file. I will also take a look at the actual code when I get a chance.
babbush: Another thing that jumps out is that all functions must come with doc strings that follow the format of the rest of our library.
jarrodmcc: Hey Nico, thanks a bunch for submitting this! I will go through it momentarily and offer some inline comments.  The style stuff is annoying at first, but becomes more natural! It looks like a great contribution!
jarrodmcc: Another point we need to consider is that the routine converts from a QubitOperator that contains its terms as a dictionary, which are then converted to a list.  The list has well-defined order, which then determines the Trotter sequence, but the dictionary does not.  This means that this routine called on QubitOperators that test equal can produce different results, which could be problematic for testing and consistency across different users.  I think we need an intermediate routine that makes the order well-defined in the conversion from dict to list so 2 users running this on identical Hamiltonians expect the same results. 

I see the term_order list that you have now that could perform this function, but I think it should be a general function with a default that is well-ordered.  The list system you used could always be passed as a special case.
googlebot: So there's good news and bad news.

:thumbsup: The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so.  Everything is all good there.

:confused: The bad news is that it appears that one or more commits were authored by someone other than the pull request submitter.  We need to confirm that they're okay with their commits being contributed to this project.  Please have them confirm that here in the pull request.

*Note to project maintainer: This is a terminal state, meaning the `cla/google` commit status will not change from this State. It's up to you to confirm consent of the commit author(s) and merge this pull request when appropriate.*

<!-- need_author_consent -->
googlebot: So there's good news and bad news.

:thumbsup: The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so.  Everything is all good there.

:confused: The bad news is that it appears that one or more commits were authored by someone other than the pull request submitter.  We need to confirm that they're okay with their commits being contributed to this project.  Please have them confirm that here in the pull request.

*Note to project maintainer: This is a terminal state, meaning the `cla/google` commit status will not change from this State. It's up to you to confirm consent of the commit author(s) and merge this pull request when appropriate.*

<!-- need_author_consent -->
nicolassawaya: Hi everyone, Thanks a bunch for all the feedback! Eric made a lot of changes yesterday, and I'm taking everything from here.

I need new units tests and I'll probably add some easier functionality for outputting the result as operator objects. Also some renaming and modularization. This will be finished at some point this week--thanks for allowing for that.

Btw next time we'll make smaller commits...

cheers, nicolas
nicolassawaya: Hi Jarrod, Ryan, Eric,
I just pushed again. Let me know if PR'ing incorrectly (:

Some highlights:
* PEP8 compliance, though there were some W503 warnings that I simply couldn't figure out how to get rid of.
* So, the code does output “trotterized” order of operators as lists, then makes a product of exponents after that. Just wanted to make that clear. And the QASM-related functions are entirely separated from the Troterrization/exponentiation portion
* I renamed the module and its functions substantially.
* There’s now a standard default ordering, which is simply the sorted dictionary keys.
* Everything is written to *streams*, not files, which makes it more modular and easier for testing.

Let us know your thoughts.

Btw congrats on the arxiv release!
Nicolas
nicolassawaya: Apologies for my delay. Eric's updates have been pushed.
babbush: Note that the test coverage will come up once tests pass. So focus on fixing the breaking tests first and then see where things stand.
schuylerfried: Yeah so we made a mistake in the commit and forgot to delete the old files - the only three files which you should comment on are _trotter_exp_to_qgates_test.py, _trotter_exp_to_qgates.py, and __init__.py. Changes requested for those will be made shortly. Hopefully test coverage will be better and the Travis CI will build, once we remove the old files
nicolassawaya: I uploaded to see what Travis CI says; not necessarily done yet. I guess Travis doesn't run right away though.

Btw, is there a standalone utility that tests py2/py3 compliance? (Ian said he didn't know of any.) Ditto for test coverage?
babbush: You need to install both python 2 and 3 in order to test for compliance in
both languages. But pytest-cov can be used to easily check coverage. Travis
tries to run immediately but depending on server load might get queued. It
usually runs within ten minutes.

On Nov 23, 2017 14:27, "nicolassawaya" <notifications@github.com> wrote:

> I uploaded to see what Travis CI says; not necessarily done yet. I guess
> Travis doesn't run right away though.
>
> Btw, is there a standalone utility that tests py2/py3 compliance? (Ian
> said he didn't know of any.) Ditto for test coverage?
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/quantumlib/OpenFermion/pull/63#issuecomment-346704658>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/ANlTf5_WVhheIvdXVsvKsr2k5hVGPD8gks5s5fFsgaJpZM4P_Rez>
> .
>

nicolassawaya: @babbush  Great--thanks a bunch
nicolassawaya: Okay, just finished writing all the unit tests. We went from 32% to 100% coverage, according to py.test on my machine. pep8 passes. We'll see if Travis reflects all this. Let us know about any changes or revamps we should do.
babbush: @jarrodmcc go ahead and use admin override to squash and merge when you decide the PR is ready.

Patch:
diff --git a/src/openfermion/utils/__init__.py b/src/openfermion/utils/__init__.py
index fd34e9f..db5a88e 100644
--- a/src/openfermion/utils/__init__.py
+++ b/src/openfermion/utils/__init__.py
@@ -40,6 +40,10 @@ from ._sparse_tools import (expectation,
 
 from ._trotter_error import error_bound, error_operator
 
+from ._trotter_exp_to_qgates import (pauli_exp_to_qasm,
+									 trotterize_exp_qubop_to_qasm,
+									 trotter_operator_grouping)
+
 from ._unitary_cc import (uccsd_convert_amplitude_format,
                           uccsd_operator,
                           uccsd_singlet_operator,
diff --git a/src/openfermion/utils/_trotter_exp_to_qgates.py b/src/openfermion/utils/_trotter_exp_to_qgates.py
new file mode 100644
index 0000000..3539008
--- /dev/null
+++ b/src/openfermion/utils/_trotter_exp_to_qgates.py
@@ -0,0 +1,277 @@
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+"""Module to perform Trotter-Suzuki decompositions to output as circuits."""
+
+from openfermion.ops import QubitOperator
+import numpy
+import copy
+import collections
+
+"""
+Description:
+    Functions for estimating the exponential of an operator
+    composed of Pauli matrices, by Trotterization. Outputs QASM
+    format to a python stream.
+
+    Change-of-basis gates:
+    H           : Z to X basis (and back)
+    Rx(pi/2)    : Z to Y basis .....
+    Rx(-pi/2)   : Y to Z basis .....
+"""
+
+
+def _third_order_trotter_helper(op_list):
+    """Iteratively find 3rd-order Trotter ordering of a QubitOperator.
+
+    This Trotter ordering is done according to the scheme:
+    e^(A+B) = e^(7/24 * A) e^(2/3 * B) e^(3/4 * A) * e^(-2/3 * B)
+        * e^(-1/24 * A) e^(B)
+
+    Note:
+        See N. Hatano and M. Suzuki Lect. Notes Phys 679, 37-68 (2005)
+
+    Args:
+        op_list (list of QubitOperator's): the terms in the
+                hamiltonian as a list of QubitOperators
+    Returns:
+        list of QubitOperators giving the trotterized hamiltonian
+    """
+    # Create list to hold return value
+    ret_val = collections.deque([7.0 / 24.0 * op_list[-2],
+                                2.0 / 3.0 * op_list[-1],
+                                3.0 / 4.0 * op_list[-2],
+                                -2.0 / 3.0 * op_list[-1],
+                                -1.0 / 24.0 * op_list[-2],
+                                1.0 * op_list[-1]])
+
+    for i in reversed(range(len(op_list)-2)):
+        temp_ret_val = copy.deepcopy(ret_val)
+        ret_val = collections.deque()
+        ret_val.appendleft(7.0 / 24.0 * op_list[i])
+        ret_val.extend([2.0 / 3.0 * qubop for qubop in temp_ret_val])
+        ret_val.append(3.0 / 4.0 * op_list[i])
+        ret_val.extend([-2.0 / 3.0 * qubop for qubop in temp_ret_val])
+        ret_val.append(-1.0 / 24.0 * op_list[i])
+        ret_val.extend([1.0 * qubop for qubop in temp_ret_val])
+    return ret_val
+
+
+def trotter_operator_grouping(hamiltonian,
+                              trotter_number=1,
+                              trotter_order=1,
+                              term_ordering=None,
+                              k_exp=1.0):
+    """Trotter-decomposes operators into groups without exponentiating.
+
+    Operators are still Hermitian at the end of this method but have been
+        multiplied by k_exp.
+
+    Note:
+        The default term_ordering is simply the ordered keys of
+        the QubitOperators.terms dict.
+
+    Args:
+        hamiltonian (QubitOperator): full hamiltonian
+        trotter_number (int): optional number of trotter steps -
+            default is 1
+        trotter_order (int): optional order of trotterization as
+            an integer from 1-3 - default is 1
+        term_ordering (list of (tuples of tuples)): optional list
+            of QubitOperator terms dictionary keys that specifies
+            order of terms when trotterizing
+        k_exp (float): optional exponential factor
+            to all terms when trotterizing
+
+    Yields:
+        QubitOperator generator
+
+    Raises:
+        ValueError if order > 3 or order <= 0,
+        TypeError for incorrect types
+    """
+    # Check for default arguments and type errors
+    if (trotter_order > 3) or (trotter_order <= 0):
+        raise ValueError("Invalid trotter order: " + str(trotter_order))
+    if not isinstance(hamiltonian, QubitOperator):
+        raise TypeError("Hamiltonian must be a QubitOperator.")
+    if len(hamiltonian.terms) == 0:
+        raise TypeError("Hamiltonian must be a non-empty QubitOperator.")
+    if term_ordering is None:
+        # To have consistent default behavior, ordering = sorted keys.
+        term_ordering = sorted(list(hamiltonian.terms.keys()))
+    if len(term_ordering) == 0:
+        raise TypeError("term_ordering must None or non-empty list.")
+
+    # Enforce float
+    k_exp = float(k_exp)
+
+    ret_val = []
+    # First order trotter
+    if trotter_order == 1:
+        for step in range(trotter_number):
+            for op in term_ordering:
+                yield QubitOperator(op,
+                                    hamiltonian.terms[op]
+                                    * float(k_exp)
+                                    / float(trotter_number)
+                                    )
+    # Second order trotter
+    elif trotter_order == 2:
+        if len(term_ordering) < 2:
+            raise ValueError("Not enough terms in the Hamiltonian to do " +
+                             "second order trotterization")
+        for step in range(trotter_number):
+            for op in term_ordering[:-1]:
+                yield QubitOperator(op,
+                                    (hamiltonian.terms[op]
+                                     * k_exp
+                                     / (2.0 * float(trotter_number))))
+
+            yield QubitOperator(term_ordering[-1],
+                                hamiltonian.terms[term_ordering[-1]]
+                                * float(k_exp)
+                                / float(trotter_number))
+
+            for op in reversed(term_ordering[:-1]):
+                yield QubitOperator(op, hamiltonian.terms[op]
+                                    * float(k_exp)
+                                    / (2.0 * float(trotter_number)))
+
+    # Third order trotter
+    elif trotter_order == 3:
+        if len(term_ordering) < 2:
+            raise ValueError("Not enough terms in the Hamiltonian to do " +
+                             "third order trotterization")
+        # Make sure original hamiltonian is not modified
+        ham = hamiltonian / float(trotter_number)
+        ham *= float(k_exp)
+        ham_temp = []
+        for term in term_ordering:
+            ham_temp.append(QubitOperator(term, ham.terms[term]))
+        for step in range(trotter_number):
+            for returned_op in _third_order_trotter_helper(ham_temp):
+                yield returned_op
+
+
+def pauli_exp_to_qasm(qubit_operator_list,
+                      evolution_time=1.0):
+    """Exponentiate a list of QubitOperators to a QASM string generator.
+
+    Exponentiates a list of QubitOperators, and yields string generators in
+        QASM format using the formula:  exp(-1.0j * evolution_time * op).
+
+    Args:
+        qubit_operator_list (list of QubitOperators): list of single Pauli-term
+            QubitOperators to be exponentiated
+        evolution_time (float): evolution time of the operators in
+            the list
+
+    Yields:
+        string
+    """
+    for qubit_operator in qubit_operator_list:
+        # ret_val = ""
+        ret_list = []
+
+        for term in qubit_operator.terms:
+            term_coeff = qubit_operator.terms[term]
+
+            # List of operators and list of qubit ids
+            ops = []
+            qids = []
+            string_basis_1 = []  # Basis rotations 1
+            string_basis_2 = []  # Basis rotations 2
+
+            for p in term:  # p = single pauli term
+                qid = p[0]  # Qubit index
+                pop = p[1]  # Pauli op
+
+                qids.append(qid)  # Qubit index
+                ops.append(pop)  # Pauli operator
+
+                if pop == 'X':
+                    string_basis_1.append("H {}".format(qid))    # Hadamard
+                    string_basis_2.append("H {}".format(qid))    # Hadamard
+                elif pop == 'Y':
+                    string_basis_1.append(
+                        "Rx 1.5707963267948966 {}".format(qid))
+                    string_basis_2.append(
+                        "Rx -1.5707963267948966 {}".format(qid))
+
+            # Prep for CNOTs
+            cnot_pairs = numpy.vstack((qids[:-1], qids[1:]))
+            cnots1 = []
+            cnots2 = []
+            for i in range(cnot_pairs.shape[1]):
+                pair = cnot_pairs[:, i]
+                cnots1.append("CNOT {} {}".format(pair[0], pair[1]))
+            for i in numpy.arange(cnot_pairs.shape[1])[::-1]:
+                pair = cnot_pairs[:, i]
+                cnots2.append("CNOT {} {}".format(pair[0], pair[1]))
+
+            # Exponentiating each Pauli string requires five parts
+
+            # 1. Perform basis rotations
+            ret_list = ret_list + string_basis_1
+
+            # 2. First set CNOTs
+            ret_list = ret_list + cnots1
+
+            # 3. Rotation (Note kexp & Ntrot)
+            ret_list = ret_list + ["Rz {} {}".format(
+                        term_coeff * evolution_time, qids[-1])]
+
+            # 4. Second set of CNOTs
+            ret_list = ret_list + cnots2
+
+            # 5. Rotate back to Z basis
+            ret_list = ret_list + string_basis_2
+
+            for gate in ret_list:
+                yield gate
+
+
+def trotterize_exp_qubop_to_qasm(hamiltonian,
+                                 trotter_number=1,
+                                 trotter_order=1,
+                                 term_ordering=None,
+                                 k_exp=1.0):
+    """Trotterize a Qubit hamiltonian and write it to QASM format.
+
+    Assumes input hamiltonian is still hermitian and -1.0j has not yet been
+    applied. Therefore, signs of coefficients should reflect this. Returns
+    a generator which generates a QASM file.
+
+    Args:
+        hamiltonian (QubitOperator): hamiltonian
+        trotter_number (int): optional number of trotter steps (slices) for
+            trotterization as an integer - default = 1
+        trotter_order: optional order of trotterization as an integer -
+            default = 1
+        term_ordering (list of (tuples of tuples)): list of tuples
+            (QubitOperator terms dictionary keys) that specifies
+            order of terms when trotterizing
+        k_exp (float): optional exponential factor to all
+            terms when trotterizing
+
+        Yields:
+            string generator
+
+    """
+
+    for trotterized_op in trotter_operator_grouping(hamiltonian,
+                                                    trotter_number,
+                                                    trotter_order,
+                                                    term_ordering,
+                                                    k_exp):
+        for exponentiated_qasm_string in pauli_exp_to_qasm([trotterized_op]):
+            yield exponentiated_qasm_string


Test patch:
diff --git a/src/openfermion/utils/_trotter_exp_to_qgates_test.py b/src/openfermion/utils/_trotter_exp_to_qgates_test.py
new file mode 100644
index 0000000..6f566ff
--- /dev/null
+++ b/src/openfermion/utils/_trotter_exp_to_qgates_test.py
@@ -0,0 +1,236 @@
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+"""Tests for _trotter_exp_to_qgates.py"""
+
+import unittest
+from openfermion.utils._trotter_exp_to_qgates import *
+from openfermion.utils._trotter_exp_to_qgates import _third_order_trotter_helper
+from openfermion.utils import count_qubits
+
+
+class TrottQasmTest(unittest.TestCase):
+
+    def setUp(self):
+        # First qubit operator example
+        self.opA = QubitOperator('X0 Z1 Y3', 0.5)
+        self.opB = 0.6 * QubitOperator('Z3 Z4')
+        self.qo1 = self.opA + self.opB
+
+    def test_exceptions(self):
+        # Test exceptions in trotter_operator_grouping()
+
+        with self.assertRaises(ValueError):
+            [i for i in trotter_operator_grouping(self.qo1, trotter_order=0)]
+
+        with self.assertRaises(ValueError):
+            [i for i in trotter_operator_grouping(self.qo1, trotter_order=4)]
+
+        with self.assertRaises(TypeError):
+            [i for i in trotter_operator_grouping(42)]
+
+        emptyQO = QubitOperator()
+        with self.assertRaises(TypeError):
+            [i for i in trotter_operator_grouping(emptyQO)]
+
+        emptyTO = []
+        with self.assertRaises(TypeError):
+            [i for i in trotter_operator_grouping(self.qo1,
+                                                  term_ordering=emptyTO)]
+
+        # Too few ops for 2nd-order
+        with self.assertRaises(ValueError):
+            [i for i in trotter_operator_grouping(self.opA, trotter_order=2)]
+
+        # Too few ops for 3rd-order
+        with self.assertRaises(ValueError):
+            [i for i in trotter_operator_grouping(self.opA,
+                                                  trotter_order=3)]
+
+    def compare_qubop_lists(self, gold, res):
+        # Compare lists of operators. Used in most test functions.
+        for a, b in zip(gold, res):
+            self.assertEqual(len(a.terms), 1)
+            self.assertEqual(len(b.terms), 1)
+            for key in set(a.terms.keys()).union(b.terms.keys()):
+                self.assertEqual(key in a.terms, key in b.terms)
+                self.assertAlmostEqual(a.terms[key], b.terms[key])
+
+    def test_3rd_order_helper_2ops(self):
+        # Test 3rd-order helper, H=A+B
+        op_a = QubitOperator('X0 Z1 Y2', 0.1)
+        op_b = QubitOperator('Z2', 0.1)
+        a = 'X0 Z1 Y2'
+        b = 'Z2'
+
+        gold = []
+        gold.append(op_a * (7./24))
+        gold.append(op_b * (2./3))
+        gold.append(op_a * (3./4))
+        gold.append(op_b * (-2./3))
+        gold.append(op_a * (-1./24))
+        gold.append(op_b * (1.))
+
+        # Second arg must be in list form
+        res = _third_order_trotter_helper([op_a, op_b])
+
+        # Assert each term in list of QubitOperators is correct
+        self.compare_qubop_lists(gold, res)
+
+    def test_3rd_order_helper_3ops(self):
+        # Test 3rd-order helper, H=A+B+C
+        op_a = QubitOperator('X0', 1.)
+        op_b = QubitOperator('Z2', 1.)
+        op_c = QubitOperator('Z3', 1.)
+
+        gold = []
+        gold.append(op_a * 7./24)
+        gold.append(op_b * 7./36)
+        gold.append(op_c * 4./9)
+        gold.append(op_b * 1./2)
+        gold.append(op_c * -4./9)
+        gold.append(op_b * -1./36)
+        gold.append(op_c * 2./3)
+        gold.append(op_a * 3./4)
+        gold.append(op_b * -7./36)
+        gold.append(op_c * -4./9)
+        gold.append(op_b * -1./2)
+        gold.append(op_c * 4./9)
+        gold.append(op_b * 1./36)
+        gold.append(op_c * -2./3)
+        gold.append(op_a * -1./24)
+        gold.append(op_b * 7./24)
+        gold.append(op_c * 2./3)
+        gold.append(op_b * 3./4)
+        gold.append(op_c * -2./3)
+        gold.append(op_b * -1./24)
+        gold.append(op_c * 1.0)
+
+        # Second arg must be in list form
+        res = _third_order_trotter_helper([op_a, op_b, op_c])
+
+        # Assert each term in list of QubitOperators is correct
+        self.compare_qubop_lists(gold, res)
+
+    def test_trott_ordering_3rd_ord(self):
+        # Test 3rd-order Trotterization, H=A+B+C
+        op_a = QubitOperator('X0', 1.)
+        op_b = QubitOperator('Z2', 1.)
+        op_c = QubitOperator('Z3', 1.)
+        ham = op_a + op_b + op_c
+
+        # Result from code
+        res = [op for op in trotter_operator_grouping(
+            ham, trotter_order=3
+            )]
+
+        gold = []
+        gold.append(op_a * 7./24)
+        gold.append(op_b * 7./36)
+        gold.append(op_c * 4./9)
+        gold.append(op_b * 1./2)
+        gold.append(op_c * -4./9)
+        gold.append(op_b * -1./36)
+        gold.append(op_c * 2./3)
+        gold.append(op_a * 3./4)
+        gold.append(op_b * -7./36)
+        gold.append(op_c * -4./9)
+        gold.append(op_b * -1./2)
+        gold.append(op_c * 4./9)
+        gold.append(op_b * 1./36)
+        gold.append(op_c * -2./3)
+        gold.append(op_a * -1./24)
+        gold.append(op_b * 7./24)
+        gold.append(op_c * 2./3)
+        gold.append(op_b * 3./4)
+        gold.append(op_c * -2./3)
+        gold.append(op_b * -1./24)
+        gold.append(op_c * 1.0)
+
+        # Assert each term in list of QubitOperators is correct
+        self.compare_qubop_lists(gold, res)
+
+    def test_trott_ordering_2nd_ord(self):
+        # Test 2nd-order Trotter ordering
+        op_a = QubitOperator('X0', 1.)
+        op_b = QubitOperator('Z2', 1.)
+        op_c = QubitOperator('Z3', 1.)
+        ham = op_a + op_b + op_c
+
+        res = [op for op in trotter_operator_grouping(
+            ham, trotter_order=2
+            )]
+
+        gold = []
+        gold.append(op_a * 0.5)
+        gold.append(op_b * 0.5)
+        gold.append(op_c * 1.0)
+        gold.append(op_b * 0.5)
+        gold.append(op_a * 0.5)
+
+    def test_get_trott_qubops(self):
+        # Testing with trotter number of 2 (first-order)
+        res = [op for op in trotter_operator_grouping(
+                self.qo1,
+                trotter_number=2,
+                trotter_order=1,
+                term_ordering=None,
+                k_exp=1.0)]
+
+        gold = []
+        gold.append(0.5*self.opA)
+        gold.append(0.5*self.opB)
+        gold.append(0.5*self.opA)
+        gold.append(0.5*self.opB)
+
+        # Assert each term in list of QubitOperators is correct
+        self.compare_qubop_lists(gold, res)
+
+    def test_qasm_string_Z(self):
+        # Testing for correct QASM string output w/ Pauli-X
+
+        # Number of qubits
+        qasmstr = str(count_qubits(self.opB)) + "\n"
+
+        # Write each QASM operation
+        qasmstr += "\n".join(trotterize_exp_qubop_to_qasm(self.opB))
+
+        # Correct string
+        strcorrect = '''5
+CNOT 3 4
+Rz 0.6 4
+CNOT 3 4'''
+
+        self.assertEqual(qasmstr, strcorrect)
+
+    def test_qasm_string_XYZ(self):
+        # Testing for correct QASM string output w/ Pauli-{X,Y,Z}
+        # QubitOperator('X0 Z1 Y3', 0.5)
+
+        # Number of qubits
+        qasmstr = str(count_qubits(self.opA)) + "\n"
+
+        # Write each QASM operation
+        qasmstr += "\n".join(trotterize_exp_qubop_to_qasm(self.opA))
+
+        # Correct string
+        strcorrect = '''4
+H 0
+Rx 1.5707963267948966 3
+CNOT 0 1
+CNOT 1 3
+Rz 0.5 3
+CNOT 1 3
+CNOT 0 1
+H 0
+Rx -1.5707963267948966 3'''
+
+        self.assertEqual(qasmstr, strcorrect)