@@ -74,47 +74,19 @@ def __init__(
7474 circuit = transpile (enc_circ , target = qiskit_pennylane_target , optimization_level = 0 )
7575 self ._pennylane_circuit = PennyLaneCircuit (circuit , "state" )
7676
77- @lru_cache (maxsize = self ._cache_size )
78- def pennylane_circuit_executor (* args , ** kwargs ):
79- args_numpy = [np .array (arg ) for arg in args ]
80- return self ._executor .pennylane_execute (
81- self ._pennylane_circuit , * args_numpy , ** kwargs
82- )
83-
84- self ._cached_execution = pennylane_circuit_executor
85-
8677 elif self ._executor .quantum_framework == "qulacs" :
8778
8879 enc_circ = self ._encoding_circuit .get_circuit (x , self ._parameter_vector )
8980 self ._qulacs_circuit = QulacsCircuit (enc_circ , None )
9081
91- @lru_cache (maxsize = self ._cache_size )
92- def qulacs_circuit_executor (* args ):
93- args_numpy = [np .array (arg ) for arg in args ]
94- if len (args_numpy ) == 0 :
95- return self ._executor .qulacs_execute (
96- qulacs_evaluate_statevector , self ._qulacs_circuit
97- )
98- elif len (args_numpy ) == 1 :
99- return self ._executor .qulacs_execute (
100- qulacs_evaluate_statevector , self ._qulacs_circuit , x = args_numpy [0 ]
101- )
102- elif len (args_numpy ) == 2 :
103- return self ._executor .qulacs_execute (
104- qulacs_evaluate_statevector ,
105- self ._qulacs_circuit ,
106- p = args_numpy [0 ],
107- x = args_numpy [1 ],
108- )
109-
110- self ._cached_execution = qulacs_circuit_executor
111-
11282 else :
11383 raise RuntimeError (
11484 "Quantum framework not supported for FidelityKernelStatevector: "
11585 f"{ self ._executor .quantum_framework } "
11686 )
11787
88+ self ._build_cached_execution ()
89+
11890 else :
11991
12092 # Mode 2 for shot based: calculate the |0> probabilities
@@ -144,6 +116,74 @@ def qulacs_circuit_executor(*args):
144116 f"{ self ._executor .quantum_framework } "
145117 )
146118
119+ def _build_cached_execution (self ) -> None :
120+ """(Re)create the ``lru_cache``-wrapped circuit executor.
121+
122+ Only statevector kernels use a cached executor (shot-based kernels do
123+ not). The closure is rebuilt from the already-constructed circuit
124+ object rather than from the executor's framework, so it survives being
125+ deserialized onto a different backend executor (see
126+ :meth:`__getstate__`/:meth:`__setstate__`).
127+ """
128+ if not self ._executor .is_statevector :
129+ return
130+
131+ if getattr (self , "_qulacs_circuit" , None ) is not None :
132+
133+ @lru_cache (maxsize = self ._cache_size )
134+ def qulacs_circuit_executor (* args ):
135+ args_numpy = [np .array (arg ) for arg in args ]
136+ if len (args_numpy ) == 0 :
137+ return self ._executor .qulacs_execute (
138+ qulacs_evaluate_statevector , self ._qulacs_circuit
139+ )
140+ elif len (args_numpy ) == 1 :
141+ return self ._executor .qulacs_execute (
142+ qulacs_evaluate_statevector , self ._qulacs_circuit , x = args_numpy [0 ]
143+ )
144+ elif len (args_numpy ) == 2 :
145+ return self ._executor .qulacs_execute (
146+ qulacs_evaluate_statevector ,
147+ self ._qulacs_circuit ,
148+ p = args_numpy [0 ],
149+ x = args_numpy [1 ],
150+ )
151+
152+ self ._cached_execution = qulacs_circuit_executor
153+
154+ elif getattr (self , "_pennylane_circuit" , None ) is not None :
155+
156+ @lru_cache (maxsize = self ._cache_size )
157+ def pennylane_circuit_executor (* args , ** kwargs ):
158+ args_numpy = [np .array (arg ) for arg in args ]
159+ return self ._executor .pennylane_execute (
160+ self ._pennylane_circuit , * args_numpy , ** kwargs
161+ )
162+
163+ self ._cached_execution = pennylane_circuit_executor
164+
165+ def __getstate__ (self ) -> dict :
166+ """Return a picklable copy of the kernel's state.
167+
168+ ``self._cached_execution`` is a local closure wrapped in
169+ :func:`functools.lru_cache`. Such objects are not picklable by
170+ reference: pickle/cloudpickle serialize them via the wrapped function's
171+ ``<locals>`` qualname
172+ (``FidelityKernelStatevector.__init__.<locals>.qulacs_circuit_executor``),
173+ which cannot be resolved on load -> ``AttributeError: Can't get local
174+ object ...``. It is a pure memoization cache, so we drop it here and
175+ rebuild it in :meth:`__setstate__`; every other attribute (including the
176+ circuit objects) is preserved unchanged.
177+ """
178+ state = self .__dict__ .copy ()
179+ state .pop ("_cached_execution" , None )
180+ return state
181+
182+ def __setstate__ (self , state : dict ) -> None :
183+ """Restore the kernel and rebuild the dropped executor closure."""
184+ self .__dict__ .update (state )
185+ self ._build_cached_execution ()
186+
147187 @property
148188 def num_parameters (self ) -> int :
149189 """Returns the number of trainable parameters."""
0 commit comments