co9olguy / qdata

1 stars 3 forks source link

Problems with defining operators #23

Open therooler opened 3 years ago

therooler commented 3 years ago

I tried to write a parser that takes the QasmProgram output, grabs the operators defined in the tfi_chain.qasm and xxz_chain.qasmfile and then translates these to cirq.Paulisum objects.

It took me while to figure out a way to do this, but the following code does the job:

import qdata.parser as parser
import qdata.ir as ir
import cirq
import math

# Parse the Tree
fname = 'examples/example_data/tfi_chain.qasm'
with open(fname, 'r') as file:
    qasm_str = file.readlines()
tree = parser.qasm_parser.parse("".join(qasm_str))
tree = parser.QASMToIRTransformer().transform(tree)

# Define mappings for operators from a .qasm name to a cirq operator.
parsed_operators = {'x': lambda qubits: cirq.X(*qubits),
                    'y': lambda qubits: cirq.Y(*qubits),
                    'z': lambda qubits: cirq.Z(*qubits),
                    }

def operator_parser_paulisum(operator):
    """
    Return a function that implements the operator defined in the .qasm file.

    Args:
        operator:
            A ir.OperatorDeclaration object

    Returns:
        Tuple with name of the defined operator and a function that accepts the
        qubits on which to act
    """
    # get the name of the operator
    name = operator.kwargs['op'].name
    # parameter names in the .qasm
    lambda_wires = operator.kwargs['op'].wires

    def operator_fn(qubits):
        # first, we need to map the .qasm parameters to the qubits
        qubit_map = dict(zip(lambda_wires, qubits))
        # if we have a linear combination we need to sum the terms
        cirq_ops_sum = []
        for term in operator.goplist:
            # If the term is a TensorOp, we need to collect all the terms and
            # use math.prod to get the correct Paulisum tensor product
            if isinstance(term, ir.TensorOp):
                cirq_ops_tensor_product = []
                for op in term.ops:
                    cirq_ops_tensor_product.append(parsed_operators[op.name](
                        [qubit_map[q] for q in op.wires]))
                cirq_ops_sum.append(math.prod(
                    [op_i for op_i in cirq_ops_tensor_product]))
            # Otherwise, we assume it is a `Term` and add it to the linear comb
            else:
                cirq_ops_sum.append(parsed_operators[term.op.name](
                    [qubit_map[q] for q in term.op.wires]) * term.coeff)
        return sum(cirq_ops_sum)

    return name, operator_fn

# Define cirq qubits
qubits = cirq.GridQubit.rect(8, 1)

# go trough the statements and fine the operator definitions.
for statement in tree.statements:
    if isinstance(statement, ir.OperatorDeclaration):
        name, operator_function = operator_parser_paulisum(statement)
        # add the newly defined operators to the operator mapping
        if name not in parsed_operators.keys():
            parsed_operators[name] = operator_function

        else:
            raise KeyError(
                f'Operator {name} already defined in parsed_operators')
# Does it give the correct Pauli Strings? 
print(parsed_operators['zz']([qubits[i] for i in (0,1)]))
print(parsed_operators['tfi_energy_operator_open']([qubits[i] for i in list(range(8))]))

The things I struggled with were the differences between Terms, Gates, Ops and TensorOps objects. For instance, this is a TensorOp:

operator energy a, b, c {
    zz a, b;
}

, which is fine. But this is a Term

operator energy a, b, c {
    1.0 zz a, b;
}

, even though it is not a linear combinations of terms. This is a 'Gate':

operator energy a {
    z a;
}

, but it should be an Op right?

In the next example, the first line is again a Gate, not a Term, even though the second line defines a Term.

operator energy a, b, c {
    zz a, b;
    -1.0 zz b, c;
}

Same here:

operator energy a, b, c {
    zz a, b;
    zz b, c;
}

, neither are Terms, but Gates instead.

All of this can make it confusing how to add things, because Terms have a coeff attribute, and Gates or Ops do not. My suggestion is that if we have multiple lines in the operator definition, then each line is a Term so that it has a coeff attribute. Or you could always define operators as Terms, even if they contain a single operator definition. I hope this makes sense.

co9olguy commented 3 years ago

Thanks @therooler, very helpful feedback!

Flagging @josh146 so he sees as well

josh146 commented 3 years ago

Thanks @therooler! This is really good to know.

I think this requires fixes in two places:

This feels like something that should be modified more at the lexing level; the parser should simply be using the generated tokens to map directly from the AST to the IR.