"""QDK/Chemistry Q# Utilities Module."""
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
import threading
from collections.abc import Iterator
from contextlib import contextmanager
from pathlib import Path
import qdk
from qdk import TargetProfile
__all__ = [
"QSHARP_UTILS",
"create_qsharp_context",
"get_qsharp_context",
"set_qsharp_context",
"use_qsharp_context",
]
_PROJECT_ROOT = str(Path(__file__).parent)
class _SharedContext:
"""Lock-guarded holder for the process-wide shared Q# context."""
def __init__(self) -> None:
self.lock = threading.RLock()
self.context: qdk.Context | None = None
_shared = _SharedContext()
_thread_local = threading.local()
[docs]
def create_qsharp_context(
target_profile: TargetProfile = TargetProfile.Base,
target_name: str | None = None,
language_features: list[str] | None = None,
qdk_config: dict[str, int | float | str | bool] | None = None,
) -> qdk.Context:
"""Create a new, isolated ``qdk.Context`` preloaded with the Q# chemistry utilities.
Every call returns a *fresh* context with its own Q# interpreter. Most users never
need this — the library maintains one shared context (see :func:`get_qsharp_context`).
Reach for this only when you need a context configured differently from the default
(for example a non-default ``target_profile``); then register it with
:func:`set_qsharp_context` if the chemistry builders should use it too.
:param target_profile: Target profile the Q# interpreter compiles for. Defaults to
``TargetProfile.Base``.
:param target_name: Optional target machine name used to infer a compatible profile.
:param language_features: Optional list of experimental Q# language feature flags.
:param qdk_config: Optional configuration values exposed to Q# code via
``Std.Core.ConfigValue`` (values must be ``int``, ``float``, ``str``, or ``bool``).
``project_root`` is intentionally not exposed: it is fixed to the vendored Q# utility
project so the chemistry utilities are always available on the returned context.
"""
kwargs: dict = {}
if target_name is not None:
kwargs["target_name"] = target_name
if language_features is not None:
kwargs["language_features"] = language_features
if qdk_config is not None:
kwargs["qdk_config"] = qdk_config
return qdk.Context(project_root=_PROJECT_ROOT, target_profile=target_profile, **kwargs)
[docs]
def get_qsharp_context() -> qdk.Context:
"""Return the shared ``qdk.Context`` that QDK/Chemistry uses for all Q# composition.
Call it when you want to define your *own* Q# operation (e.g. a custom state
preparation) and compose it with a chemistry builder. The context is created lazily
on first use and access is thread-safe.
"""
override = getattr(_thread_local, "context", None)
if override is not None:
return override
with _shared.lock:
if _shared.context is None:
_shared.context = create_qsharp_context()
return _shared.context
[docs]
def set_qsharp_context(context: qdk.Context | None) -> None:
"""Replace the process-wide shared Q# context (pass ``None`` to reset to the default)."""
with _shared.lock:
_shared.context = context
[docs]
@contextmanager
def use_qsharp_context(context: qdk.Context) -> Iterator[qdk.Context]:
"""Temporarily use *context* as the shared Q# context on the current thread."""
previous = getattr(_thread_local, "context", None)
_thread_local.context = context
try:
yield context
finally:
_thread_local.context = previous
class _QSharpUtilsProxy:
"""Resolve the chemistry Q# utilities against the active shared context."""
def __getattr__(self, name: str):
"""Resolve *name* on the utilities namespace of the active context."""
return getattr(get_qsharp_context().code.QDKChemistry.Utils, name)
QSHARP_UTILS = _QSharpUtilsProxy()