Open astralcai opened 2 months ago
Hi @astralcai, thank you for raising this. I shall start looking into a fix.
This means that the same treatment for
Hamiltonian
,LinearCombination
andSum
should extend toSProd
andProd
as well, including_translate_observable
, which should registerSum
,SProd
andProd
all under the same dispatch function asHamiltonian
, which usesH.terms()
.
The current _translate_observable
implementations for Sum
, SProd
and Prod
recursively call _translate_observable
on their operands:
@_translate_observable.register
def _(t: qml.operation.Tensor):
return reduce(lambda x, y: x @ y, [_translate_observable(factor) for factor in t.obs])
@_translate_observable.register
def _(t: qml.ops.Prod):
return reduce(lambda x, y: x @ y, [_translate_observable(factor) for factor in t.operands])
@_translate_observable.register
def _(t: qml.ops.SProd):
return t.scalar * _translate_observable(t.base)
Shouldn't that take care of the nesting problem?
measurements = []
for mp in circuit.measurements:
obs = mp.obs
if isinstance(obs, (Hamiltonian, LinearCombination, Sum, SProd, Prod)):
obs = obs.simplify()
mp = type(mp)(obs)
measurements.append(mp)
I'm noticing that simplify
alters the order of operands (at least in Prod
); is this intentional?
This means that the same treatment for
Hamiltonian
,LinearCombination
andSum
should extend toSProd
andProd
as well, including_translate_observable
, which should registerSum
,SProd
andProd
all under the same dispatch function asHamiltonian
, which usesH.terms()
.The current
_translate_observable
implementations forSum
,SProd
andProd
recursively call_translate_observable
on their operands:@_translate_observable.register def _(t: qml.operation.Tensor): return reduce(lambda x, y: x @ y, [_translate_observable(factor) for factor in t.obs]) @_translate_observable.register def _(t: qml.ops.Prod): return reduce(lambda x, y: x @ y, [_translate_observable(factor) for factor in t.operands]) @_translate_observable.register def _(t: qml.ops.SProd): return t.scalar * _translate_observable(t.base)
Shouldn't that take care of the nesting problem?
It should, but as I recall it didn't. I was looking into it some time ago and couldn't make it work, that's why I suggested using the same approach for all potential multi-term observables. You can give it a try. I don't remember what the issue was exactly, but I believe it has something to do with the braket backend unable to parse scalar products.
/opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/site-packages/braket/default_simulator/openqasm/interpreter.py:545: in _
parsed = self.context.parse_pragma(node.command)
/opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/site-packages/braket/default_simulator/openqasm/program_context.py:455: in parse_pragma
return parse_braket_pragma(pragma_body, self.qubit_mapping)
/opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/site-packages/braket/default_simulator/openqasm/parser/braket_pragmas.py:216: in parse_braket_pragma
visited = BraketPragmaNodeVisitor(qubit_table).visit(tree)
/opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/site-packages/antlr4/tree/Tree.py:34: in visit
return tree.accept(self)
/opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/site-packages/braket/default_simulator/openqasm/parser/generated/BraketPragmasParser.py:861: in accept
return visitor.visitBraketPragma(self)
/opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/site-packages/braket/default_simulator/openqasm/parser/generated/BraketPragmasParserVisitor.py:14: in visitBraketPragma
return self.visitChildren(ctx)
/opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/site-packages/antlr4/tree/Tree.py:44: in visitChildren
childResult = c.accept(self)
/opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/site-packages/braket/default_simulator/openqasm/parser/generated/BraketPragmasParser.py:1226: in accept
return visitor.visitBraketResultPragma(self)
/opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/site-packages/braket/default_simulator/openqasm/parser/generated/BraketPragmasParserVisitor.py:39: in visitBraketResultPragma
return self.visitChildren(ctx)
/opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/site-packages/antlr4/tree/Tree.py:44: in visitChildren
childResult = c.accept(self)
/opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/site-packages/braket/default_simulator/openqasm/parser/generated/BraketPragmasParser.py:1290: in accept
return visitor.visitResultType(self)
/opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/site-packages/braket/default_simulator/openqasm/parser/generated/BraketPragmasParserVisitor.py:44: in visitResultType
return self.visitChildren(ctx)
/opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/site-packages/antlr4/tree/Tree.py:44: in visitChildren
childResult = c.accept(self)
/opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/site-packages/braket/default_simulator/openqasm/parser/generated/BraketPragmasParser.py:1867: in accept
return visitor.visitObservableResultType(self)
/opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/site-packages/braket/default_simulator/openqasm/parser/braket_pragmas.py:98: in visitObservableResultType
observables, targets = self.visit(ctx.observable())
E TypeError: cannot unpack non-iterable NoneType object
----------------------------- Captured stderr call -----------------------------
line 1:26 mismatched input '0.1' expecting {'x', 'y', 'z', 'i', 'h', 'hermitian'}
This occured when trying to parse the scalar product of an observable. See this run: https://github.com/PennyLaneAI/plugin-test-matrix/actions/runs/9018042395/job/24777766316
measurements = [] for mp in circuit.measurements: obs = mp.obs if isinstance(obs, (Hamiltonian, LinearCombination, Sum, SProd, Prod)): obs = obs.simplify() mp = type(mp)(obs) measurements.append(mp)
I'm noticing that
simplify
alters the order of operands (at least inProd
); is this intentional?
Simplify does not preserve the original order of operands.
Sum
Sum
should be handled the same way asHamiltonian
andLinearCombination
, which was partially addressed in https://github.com/amazon-braket/amazon-braket-pennylane-plugin-python/pull/252, but the same treatment should be applied totranslate_result_type
andtranslate_result
intranslation.py
as well.Note:
Sum.ops
is deprecated, so instead ofmeasurement.obs.ops
, do_, ops = measurement.obs.terms()
, and then useops
.SProd and Prod
Since
SProd
andProd
could be nested, they are not guaranteed to be single-term observables. For example, anSProd
could be0.1 * (qml.Z(0) + qml.X(1))
, in which case it's actually aSum
. Similarly, aProd
could beqml.Z(0) @ (qml.X(0) + qml.Y(1))
.This means that the same treatment for
Hamiltonian
,LinearCombination
andSum
should extend toSProd
andProd
as well, including_translate_observable
, which should registerSum
,SProd
andProd
all under the same dispatch function asHamiltonian
, which usesH.terms()
.Caveat:
Prod.terms()
will resolve to itself if theProd
only contains one term. For example:This may result in infinite recursion in
_translate_observable
, so a base case should be added to returnreduce(lambda x, y: x @ y, [_translate_observable(factor) for factor in H.operands])
ifH
is aProd
with a single term.Note: The
terms()
function will unwrap any nested structures but also simplify the observable. For example:This will create a mismatch between the number of targets in the translated observable and the original observable. We do plan on addressing this issue in PennyLane and have
terms()
recursively unwraps the observable without doing any simplification, but for now, in_pl_to_braket_circuit
, do not usecircuit.measurements
directly, instead do something likeThen use
measurements
instead ofcircuit.measurements
from this point on. The list of simplified measurements should also be passed into_apply_gradient_result_type
and used there.Device
Now since
SProd
,Prod
, andSum
all could be nested, multi-term observables, they should be removed from the list of supported observables and added back if no shots are present: