Skip to content

Commit f8ba4fe

Browse files
Make Executor session handling safer (#377)
* move destructor up in file * replace destructor with weakref finalize * add conditional context management * always add finalizer if session is present * add some tests * change mock_session scope to function * only register finalizer if not present * use static method call * use _cleanup_session directly on session ref * remove unnecessary test, add finally * clean up tests * only open session if needed in context manager * add docstrings and lint statements * add SessionContextMisuseWarning * adapt docstrings to encourage context managed use * encorage use of context manager in user guide * adapt example_executor_qiskit * use new ibm platform details * adapt backend mitigation example to use context manager * new black
1 parent 0700441 commit f8ba4fe

5 files changed

Lines changed: 751 additions & 250 deletions

File tree

docs/user_guide/executor.rst

Lines changed: 149 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ The :class:`Executor <squlearn.Executor>` class provides the following key comfo
6060
sub-programs. If the version of the Qiskit installation is larger equal than 1.2, the V2 Primitives are created, otherwise the V1 Primitives are returned.
6161
- **Qiskit Session handling:** Automatically manages the creation and handling of Qiskit IBM Runtime sessions.
6262
If Sessions are time out, the :class:`Executor <squlearn.Executor>` automatically creates a new session and re-executes the
63-
job. Note that a manual closing of the session is reuqired when running in jupyter notebook, since otherwise unncessary runtime on the IBM Quantum Machines might be a consequence.
63+
job. **Important:** When using IBM Quantum backends or Sessions, it is **strongly recommended**
64+
to use the :class:`Executor <squlearn.Executor>` within a context manager (``with`` statement) to ensure proper cleanup
65+
and avoid unnecessary charges for open sessions. See :ref:`Session Management <session_management>` for details.
6466
- **Automatic backend selection (IBM Quantum only):** The :class:`Executor <squlearn.Executor>` class can automatically select the most suitable
6567
backend for the quantum job. The selection process is facilitated by the `mapomatic <https://github.com/qiskit-community/mapomatic>`_ tool `[1]`_.
6668
The :class:`Executor <squlearn.Executor>` can be initialized with a list of backends, a :class:`QiskitRuntimeService <qiskit_ibm_runtime.QiskitRuntimeService>`, or a Qiskit
@@ -148,18 +150,39 @@ execution environment:
148150
149151
150152
- A :class:`Backend <qiskit.providers.Backend>` from :class:`QiskitRuntimeService <qiskit_ibm_runtime.QiskitRuntimeService>`'s :meth:`backend <qiskit_ibm_runtime.QiskitRuntimeservice.backend>` method, which utilizes the execution of quantum jobs on IBM Quantum.
151-
Sessions and Primitives are automatically created and managed by the :class:`Executor <squlearn.Executor>` class (remember to close the session at the end of your code when running on IBM Quantum).
153+
Sessions and Primitives are automatically created and managed by the :class:`Executor <squlearn.Executor>` class.
154+
155+
**Important:** Use the context manager to ensure the session is properly closed:
152156

153157
.. code-block:: python
154158
155159
from squlearn import Executor
156160
from qiskit_ibm_runtime import QiskitRuntimeService
157161
158-
service = QiskitRuntimeService(channel="ibm_quantum", token="INSERT_YOUR_TOKEN_HERE")
159-
executor = Executor(service.backend('ibm_brisbane'))
160-
...
161-
# Close the session at the end of your code
162-
executor.close_session()
162+
service = QiskitRuntimeService(channel="ibm_quantum_platform", token="INSERT_YOUR_TOKEN_HERE")
163+
164+
# Recommended: Use context manager
165+
with Executor(service.backend('ibm_kingston'), caching=True) as executor:
166+
# Your quantum computations here
167+
qnn.fit(X_train, y_train)
168+
# Session is automatically closed
169+
170+
If you cannot use a context manager, remember to close the session at the end of your code:
171+
172+
.. code-block:: python
173+
174+
from squlearn import Executor
175+
from qiskit_ibm_runtime import QiskitRuntimeService
176+
177+
service = QiskitRuntimeService(channel="ibm_quantum_platform", token="INSERT_YOUR_TOKEN_HERE")
178+
executor = Executor(service.backend('ibm_kingston'))
179+
try:
180+
# Your quantum computations here
181+
qnn.fit(X_train, y_train)
182+
finally:
183+
executor.close_session()
184+
185+
See :ref:`Session Management <session_management>` for details.
163186

164187
It is also possible to pass a list of IBM Quantum backends from which the most suited backend is chosen
165188
automatically (see :ref:`Automatic backend selection <autoselect>`)
@@ -172,8 +195,12 @@ execution environment:
172195
from squlearn import Executor
173196
from qiskit_ibm_runtime import QiskitRuntimeService
174197
175-
service = QiskitRuntimeService(channel="ibm_quantum", token="INSERT_YOUR_TOKEN_HERE")
176-
executor = Executor(service)
198+
service = QiskitRuntimeService(channel="ibm_quantum_platform", token="INSERT_YOUR_TOKEN_HERE")
199+
200+
# Use context manager for proper session management
201+
with Executor(service, auto_backend_mode="quality") as executor:
202+
# Your quantum computations here
203+
qkrr.fit(X_train, y_train)
177204
178205
- A pre-initialized :class:`Session <qiskit_ibm_runtime.Session>` object, which can be used to execute quantum jobs on the :class:`QiskitRuntimeService <qiskit_ibm_runtime.QiskitRuntimeService>`.
179206

@@ -182,11 +209,13 @@ execution environment:
182209
from squlearn import Executor
183210
from qiskit_ibm_runtime import QiskitRuntimeService
184211
185-
service = QiskitRuntimeService(channel="ibm_quantum", token="INSERT_YOUR_TOKEN_HERE")
186-
session = service.create_session(backend = service.backend('ibm_brisbane'))
187-
executor = Executor(session)
188-
...
189-
executor.close_session()
212+
service = QiskitRuntimeService(channel="ibm_quantum_platform", token="INSERT_YOUR_TOKEN_HERE")
213+
session = service.create_session(backend = service.backend('ibm_kingston'))
214+
215+
# Use context manager with a pre-initialized session
216+
with Executor(session) as executor:
217+
# Your quantum computations here
218+
qkrr.fit(X_train, y_train)
190219
191220
- Pre-configured Primitive (:class:`Estimator <qiskit_ibm_runtime.Estimator>`, :class:`Sampler <qiskit_ibm_runtime.Sampler>`) with options for error mitigation.
192221
Note that the options from an :class:`Estimator <qiskit_ibm_runtime.Estimator>` are not automatically copied to
@@ -197,17 +226,17 @@ execution environment:
197226
from squlearn import Executor
198227
from qiskit_ibm_runtime import QiskitRuntimeService, Estimator
199228
200-
service = QiskitRuntimeService(channel="ibm_quantum", token="INSERT_YOUR_TOKEN_HERE")
229+
service = QiskitRuntimeService(channel="ibm_quantum_platform", token="INSERT_YOUR_TOKEN_HERE")
201230
202-
session = service.create_session(backend = service.backend('ibm_brisbane'))
231+
session = service.create_session(backend = service.backend('ibm_kingston'))
203232
estimator = Estimator(session=session)
204233
estimator.options.resilience.zne_mitigation = True
205234
estimator.options.resilience.zne.noise_factors = (1, 3, 5)
206235
estimator.options.resilience.zne.extrapolator = "linear"
207236
208-
executor = Executor(estimator)
209-
...
210-
executor.close_session()
237+
with Executor(estimator) as executor:
238+
# Your quantum computations here
239+
qkrr.fit(X_train, y_train)
211240
212241
213242
- If only the ``backend.run`` execution is wanted, this can be achieved by utilizing the
@@ -231,22 +260,24 @@ backend from the :class:`QiskitRuntimeService <qiskit_ibm_runtime.QiskitRuntimeS
231260
from squlearn import Executor
232261
from qiskit_ibm_runtime import QiskitRuntimeService
233262
234-
service = QiskitRuntimeService(channel="ibm_quantum", token="INSERT_YOUR_TOKEN_HERE")
263+
service = QiskitRuntimeService(channel="ibm_quantum_platform", token="INSERT_YOUR_TOKEN_HERE")
235264
236265
options = {"resilience":{"zne_mitigation" : True,
237266
"zne":{"noise_factors" : (1, 3, 5),"extrapolator":"linear" }}
238267
}
239268
240-
executor = Executor(service.backend('ibm_brisbane'), # Specify the backend
241-
cache_dir='cache', # Set cache folder to "cache"
242-
caching=True, # Enable caching default for remote executions
243-
log_file="executor.log", # Set-up logging file
244-
wait_restart=600, # Set 10 min pause between restarts of Jobs
245-
max_jobs_retries=10, # Set maximum number of restarts to 10 before aborting
246-
options_estimator=options # Set options for the Estimator primitive
247-
)
248-
249-
executor.set_shots(1234) # Shots can be adjusted after initialization
269+
# Recommended: Use context manager
270+
with Executor(service.backend('ibm_kingston'), # Specify the backend
271+
cache_dir='cache', # Set cache folder to "cache"
272+
caching=True, # Enable caching default for remote executions
273+
log_file="executor.log", # Set-up logging file
274+
wait_restart=600, # Set 10 min pause between restarts of Jobs
275+
max_jobs_retries=10, # Set maximum number of restarts to 10 before aborting
276+
options_estimator=options # Set options for the Estimator primitive
277+
) as executor:
278+
executor.set_shots(1234) # Shots can be adjusted after initialization
279+
# Your quantum computations here
280+
qnn.fit(X_train, y_train)
250281
251282
252283
Utilizing Executor Primitives in Qiskit Routines
@@ -294,13 +325,15 @@ They folow the same dictionary datastructure than the original V2 Primtives.
294325
from squlearn import Executor
295326
from qiskit_ibm_runtime import QiskitRuntimeService
296327
297-
service = QiskitRuntimeService(channel="ibm_quantum", token="INSERT_YOUR_TOKEN_HERE")
328+
service = QiskitRuntimeService(channel="ibm_quantum_platform", token="INSERT_YOUR_TOKEN_HERE")
298329
299330
options = {"resilience":{"zne_mitigation" : True,
300331
"zne":{"noise_factors" : (1, 3, 5),"extrapolator":"linear" }}
301332
}
302333
303-
executor = Executor(service.backend("ibm_brisbane"),options_estimator=options)
334+
with Executor(service.backend("ibm_kingston"), options_estimator=options) as executor:
335+
# Your quantum computations here
336+
pass
304337
305338
Alternatively, the options can be adjusted by the attributes :meth:`estimator_options` and :meth:`sampler_options` similar
306339
to the option interface of the V2 Primtives:
@@ -310,10 +343,13 @@ to the option interface of the V2 Primtives:
310343
from squlearn import Executor
311344
from qiskit_ibm_runtime import QiskitRuntimeService
312345
313-
service = QiskitRuntimeService(channel="ibm_quantum", token="INSERT_YOUR_TOKEN_HERE")
314-
executor = Executor(service.backend("ibm_brisbane"))
315-
executor.sampler_options.dynamical_decoupling.enable = True
316-
executor.sampler_options.dynamical_decoupling.sequence_type = "XpXm"
346+
service = QiskitRuntimeService(channel="ibm_quantum_platform", token="INSERT_YOUR_TOKEN_HERE")
347+
348+
with Executor(service.backend("ibm_kingston")) as executor:
349+
executor.sampler_options.dynamical_decoupling.enable = True
350+
executor.sampler_options.dynamical_decoupling.sequence_type = "XpXm"
351+
# Your quantum computations here
352+
pass
317353
318354
Options can be adjusted by the :meth:`set_options` method of the Primitives that are created by the
319355
:class:`Executor <squlearn.Executor>` class.
@@ -323,11 +359,73 @@ Options can be adjusted by the :meth:`set_options` method of the Primitives that
323359
from squlearn import Executor
324360
from qiskit_ibm_runtime import QiskitRuntimeService, Options
325361
326-
service = QiskitRuntimeService(channel="ibm_quantum", token="INSERT_YOUR_TOKEN_HERE")
362+
service = QiskitRuntimeService(channel="ibm_quantum_platform", token="INSERT_YOUR_TOKEN_HERE")
327363
328-
executor = Executor(service.backend("ibm_brisbane"))
329-
estimator = executor.get_estimator()
330-
estimator.set_options(resilience_level=2)
364+
with Executor(service.backend("ibm_kingston")) as executor:
365+
estimator = executor.get_estimator()
366+
estimator.set_options(resilience_level=2)
367+
# Your quantum computations here
368+
pass
369+
370+
.. _session_management:
371+
372+
Session Management (IBM Quantum)
373+
--------------------------------
374+
375+
When using the :class:`Executor <squlearn.Executor>` with IBM Quantum backends or Sessions, **proper session management is critical** to avoid:
376+
377+
- Unnecessary open sessions consuming resources
378+
- Unexpected charges from IBM Quantum for unused sessions
379+
- Resource leaks in long-running applications
380+
381+
**Recommended: Using Context Manager**
382+
383+
The recommended way to use the :class:`Executor <squlearn.Executor>` with IBM Quantum backends is within a context manager (``with`` statement).
384+
This ensures that sessions are automatically closed when you exit the block, regardless of whether an error occurs:
385+
386+
.. code-block:: python
387+
388+
from squlearn import Executor
389+
from qiskit_ibm_runtime import QiskitRuntimeService
390+
391+
service = QiskitRuntimeService(channel="ibm_quantum_platform", token="INSERT_YOUR_TOKEN_HERE")
392+
393+
# Recommended: Use context manager
394+
with Executor(service.backend('ibm_kingston'), caching=True,
395+
cache_dir='cache', log_file="log.log") as executor:
396+
# Your quantum computations here
397+
qnn.fit(X_train, y_train)
398+
# Session is automatically closed here
399+
400+
**Alternative: Manual Session Closing**
401+
402+
If you cannot use a context manager, you **must** manually close the session by calling :meth:`close_session` when done.
403+
A ``UserWarning`` will be issued if a session is created outside of a context manager context:
404+
405+
.. code-block:: python
406+
407+
from squlearn import Executor
408+
from qiskit_ibm_runtime import QiskitRuntimeService
409+
410+
service = QiskitRuntimeService(channel="ibm_quantum_platform", token="INSERT_YOUR_TOKEN_HERE")
411+
executor = Executor(service.backend('ibm_kingston'))
412+
413+
try:
414+
# Your code here
415+
qnn.fit(X_train, y_train)
416+
finally:
417+
# Make sure to close the session
418+
executor.close_session()
419+
420+
**Warning on Session Misuse**
421+
422+
Creating a session outside of a context manager will issue a :class:`SessionContextMisuseWarning`.
423+
This warning indicates that you should either:
424+
425+
1. Use a context manager (recommended)
426+
2. Ensure you explicitly call :meth:`close_session` at the end
427+
428+
Failing to do so may result in open sessions consuming resources and incurring charges on IBM Quantum.
331429

332430
.. _autoselect:
333431

@@ -400,16 +498,17 @@ the mode is switched to ``"speed"``. The :class:`Executor <squlearn.Executor>` i
400498
401499
# Executor is initialized with a service, and considers all available backends
402500
# (except simulators)
403-
service = QiskitRuntimeService(channel="ibm_quantum", token="INSERT_YOUR_TOKEN_HERE")
404-
executor = Executor(service, auto_backend_mode="speed")
405-
406-
# Create a QKRR model with a FidelityKernel and the ChebyshevRx encoding circuit
407-
qkrr = QKRR(FidelityKernel(ChebyshevRx(4),executor))
408-
409-
# Backend is automatically selected based on the smallest queue
410-
# All the following functions will be executed on the selected backend
411-
X_train, y_train = np.array([[0.1],[0.2]]), np.array([0.1,0.2])
412-
qkrr.fit(X_train, y_train)
501+
service = QiskitRuntimeService(channel="ibm_quantum_platform", token="INSERT_YOUR_TOKEN_HERE")
502+
503+
# Use context manager for proper session management
504+
with Executor(service, auto_backend_mode="speed") as executor:
505+
# Create a QKRR model with a FidelityKernel and the ChebyshevRx encoding circuit
506+
qkrr = QKRR(FidelityKernel(ChebyshevRx(4),executor))
507+
508+
# Backend is automatically selected based on the smallest queue
509+
# All the following functions will be executed on the selected backend
510+
X_train, y_train = np.array([[0.1],[0.2]]), np.array([0.1,0.2])
511+
qkrr.fit(X_train, y_train)
413512
414513
415514
In-QPU parallelization (Qiskit only)

0 commit comments

Comments
 (0)