Когда не можешь выбрать, куда пойти на свидание, можно мучиться между кофейней, баром и прогулкой. А можно поступить взросло: отправить задачу на квантовый компьютер IBM и переложить ответственность на физику. Внутри — Qiskit, 8 кубитов, реальный job_id и самый пафосный способ заменить подбрасывание обычной монетки.

GitHub репо по ссылке

Да, это максимально избыточный способ заменить random.choice(). В этом и смысл. Зато в конце будет настоящий job_id, запуск на IBM Quantum и моральное право сказать: «Это не я выбрал. Это квантовая механика так решила».

Что понадобится

  1. Python и библиотека qiskit

    pip install qiskit qiskit-aer qiskit-ibm-runtime
  2. Бесплатный токен IBM Quantum. Регистрируетесь на quantum.ibm.com, получаете токен, копируете в переменную окружения IBM_QUANTUM_TOKEN. Всё.

Код

Схема такая: 8 кубитов, гейт Адамара на каждом, измерение, 256 повторных запусков. На выходе получаем набор битстрингов, берём один из реально измеренных результатов, превращаем его в число и маппим на индекс в списке вариантов.

В квантовые вычисления я заходил как разработчик, поэтому часть терминов пришлось отдельно разобрать по документации и примерам. Но в итоге схема оказалась вполне понятной.

Код под спойлером:
```python
import os
import sys
import secrets
from dataclasses import dataclass
from collections import Counter

# pip install qiskit qiskit-aer qiskit-ibm-runtime
#
# Для реального IBM Quantum:
#   export IBM_QUANTUM_TOKEN="ваш_api_key"
#
# Опционально, но желательно:
#   export IBM_QUANTUM_INSTANCE="ваш_CRN_или_service_name"

SHOTS = 256
QUBITS = 8
REGISTER_NAME = "coin"

# ========================
# ВАШ СПИСОК ВАРИАНТОВ
# ========================
OPTIONS = [
    "Кофейня",
    "Бар",
    "Кино",
    "Прогулка",
    "Не идти никуда и наконец-то выспаться",
]
# ========================


@dataclass(frozen=True)
class CoinRun:
    bitstrings: list[str]
    counts: dict[str, int]
    backend: str
    job_id: str | None = None


def validate_options() -> None:
    if not OPTIONS:
        raise ValueError("Список OPTIONS пуст. Даже квантовая механика тут бессильна.")

    max_values = 2 ** QUBITS
    if len(OPTIONS) > max_values:
        raise ValueError(
            f"Слишком много вариантов: {len(OPTIONS)}. "
            f"При {QUBITS} кубитах доступно максимум {max_values} базовых значений."
        )


def make_circuit():
    from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister

    q = QuantumRegister(QUBITS, "q")
    c = ClassicalRegister(QUBITS, REGISTER_NAME)

    qc = QuantumCircuit(q, c, name="quantum_coin")

    # Каждый кубит отправляем в состояние, где при измерении
    # 0 и 1 выпадают примерно с равной вероятностью.
    for qubit in q:
        qc.h(qubit)

    qc.measure(q, c)
    return qc


def get_job_id(job) -> str | None:
    job_id = getattr(job, "job_id", None)

    if callable(job_id):
        return job_id()

    if job_id:
        return str(job_id)

    return None


def get_bits_local() -> CoinRun:
    """
    Локальный режим:
    1. Сначала пробуем AerSimulator.
    2. Если qiskit-aer не установлен — используем secrets как честный fallback.
    """
    try:
        from qiskit_aer import AerSimulator

        qc = make_circuit()
        simulator = AerSimulator()

        job = simulator.run(qc, shots=SHOTS, memory=True)
        result = job.result()

        bitstrings = result.get_memory(qc)
        counts = result.get_counts(qc)

        return CoinRun(
            bitstrings=bitstrings,
            counts=dict(counts),
            backend="AerSimulator локально",
            job_id=get_job_id(job),
        )

    except ImportError:
        bitstrings = [
            "".join(secrets.choice("01") for _ in range(QUBITS))
            for _ in range(SHOTS)
        ]

        return CoinRun(
            bitstrings=bitstrings,
            counts=dict(Counter(bitstrings)),
            backend="Python secrets fallback",
            job_id=None,
        )


def get_bits_ibm() -> CoinRun:
    from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
    from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

    token = os.getenv("IBM_QUANTUM_TOKEN")
    instance = os.getenv("IBM_QUANTUM_INSTANCE")

    if not token:
        raise RuntimeError(
            "Токен не найден. Укажите IBM_QUANTUM_TOKEN в переменной окружения."
        )

    service_kwargs = {
        "channel": "ibm_quantum_platform",
        "token": token,
    }

    if instance:
        service_kwargs["instance"] = instance

    service = QiskitRuntimeService(**service_kwargs)

    backend = service.least_busy(
        operational=True,
        simulator=False,
        min_num_qubits=QUBITS,
    )

    qc = make_circuit()

    pass_manager = generate_preset_pass_manager(
        backend=backend,
        optimization_level=1,
    )

    isa_circuit = pass_manager.run(qc)

    sampler = Sampler(mode=backend)
    job = sampler.run([isa_circuit], shots=SHOTS)

    job_id = get_job_id(job)

    print(f"\nОтправили задачу на IBM Quantum backend: {backend.name}")
    if job_id:
        print(f"Job ID: {job_id}")

    print("Ждём ответ от Вселенной...")

    result = job.result()
    pub_result = result[0]

    register_data = getattr(pub_result.data, REGISTER_NAME)

    bitstrings = register_data.get_bitstrings()
    counts = register_data.get_counts()

    return CoinRun(
        bitstrings=bitstrings,
        counts=dict(counts),
        backend=backend.name,
        job_id=job_id,
    )


def choose_option(bitstrings: list[str]) -> tuple[str, int, str, int]:
    """
    Выбираем вариант по одному измеренному битстрингу.

    Важно:
    простой value % len(OPTIONS) может давать небольшой перекос,
    если число вариантов не делит 2**QUBITS без остатка.

    Поэтому используем rejection sampling:
    берём только значения из диапазона, который ровно делится
    на количество вариантов.
    """
    variants_count = len(OPTIONS)
    quantum_space = 2 ** QUBITS

    usable_space = (quantum_space // variants_count) * variants_count

    for bitstring in bitstrings:
        value = int(bitstring, 2)

        if value < usable_space:
            index = value % variants_count
            return OPTIONS[index], index, bitstring, value

    # Практически сюда попасть почти невозможно при SHOTS=256,
    # но пусть fallback будет.
    index = secrets.randbelow(variants_count)
    return OPTIONS[index], index, "fallback", index


def print_top_counts(counts: dict[str, int], selected_bitstring: str) -> None:
    if not counts:
        return

    print("\nСтатистика измерений:")
    print("-" * 40)

    selected_count = counts.get(selected_bitstring, 0)
    total = sum(counts.values())

    if total > 0 and selected_count > 0:
        selected_percent = selected_count / total * 100
        print(
            f"Выбранный битстринг встретился {selected_count} раз "
            f"из {total} ({selected_percent:.2f}%)."
        )

    print("\nТоп-5 самых частых битстрингов:")
    for bitstring, count in sorted(
        counts.items(),
        key=lambda item: item[1],
        reverse=True,
    )[:5]:
        print(f"  {bitstring}: {count}")


def main() -> None:
    validate_options()

    print("\n⚛️  КВАНТОВАЯ МОНЕТКА ⚛️")
    print("=" * 40)
    print(f"Кубитов: {QUBITS}")
    print(f"Измерений: {SHOTS}")
    print(f"Вариантов: {len(OPTIONS)}")
    print("=" * 40)

    use_ibm = input("Дёрнуть реальный IBM Quantum? (y/n): ").strip().lower() == "y"

    if use_ibm:
        try:
            run = get_bits_ibm()
            print(f"\n✅ Ответ получен с backend: {run.backend}")
        except Exception as error:
            print(f"\n❌ IBM Quantum не ответил: {error}")
            print("Переключаемся на локальный режим...")
            run = get_bits_local()
    else:
        run = get_bits_local()
        print(f"\n? Считаем локально: {run.backend}")

    choice, index, selected_bitstring, raw_value = choose_option(run.bitstrings)

    print("\n? РЕЗУЛЬТАТ ?")
    print("=" * 40)
    print(f"Источник: {run.backend}")

    if run.job_id:
        print(f"Job ID: {run.job_id}")

    print(f"Битстринг: {selected_bitstring}")
    print(f"Число: {raw_value}")
    print(f"Вариант: {index + 1} из {len(OPTIONS)}")
    print(f"ВЫБОР: {choice}")
    print("=" * 40)

    print_top_counts(run.counts, selected_bitstring)

    print(
        "\nЭто всё ещё максимально избыточная замена random.choice(). "
        "Но теперь с кубитами, job_id и чувством научной важности."
    )


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\nВыбор отменён. Судьба подождёт.")
        sys.exit(130)
```

Не забудьте выставить ключ и запустить

$env:IBM_QUANTUM_TOKEN="ваштокен"
python quantum.py

В итоге Вселенная ответила и запрос был отработан на реальном квантовом сервере:

ну значит идем в кино)
ну значит идем в кино)

IBM в панели отчитался о выполненной задаче.

Как это работает

Честно - сначала сам не знал, но разобрался.

Схема такая:

  1. Создаём 8 кубитов.

  2. На каждый кубит применяем гейт Адамара. Если совсем грубо: после этого при измерении каждый кубит может дать 0 или 1 с примерно равной вероятностью.

  3. Измеряем схему 256 раз и получаем набор битстрингов вроде 01010110, 11100001, 00011010.

  4. Берём один реально измеренный битстринг.

  5. Превращаем его в число.

  6. Маппим число на индекс в списке вариантов.

Какие списки можно подставить

```python
# Мягкий режим: написать или не написать
OPTIONS = [
    "Написать Ане",
    "Написать Кате",
    "Написать Маше",
    "Написать Лене",
    "Не писать никому и героически лечь спать в 23:00",
]

# Свидание без выбора людей как пунктов меню
OPTIONS = [
    "Позвать в кофейню",
    "Позвать в бар",
    "Позвать в кино",
    "Позвать просто погулять",
    "Не устраивать социальный эксперимент и спокойно пережить вечер",
]

# Режим «я голодный, но решения принимать не способен»
OPTIONS = [
    "Пицца",
    "Суши",
    "Бургер",
    "Шаурма",
    "Гречка: скучно, зато без архитектурных рисков",
]

# Для тех, кто в субботу за ноутом
OPTIONS = [
    "Закрыть баг",
    "Написать тесты",
    "Дописать README",
    "Рефакторить то, что никто не просил трогать",
    "Пойти гулять, пока проект не превратился в свой фреймворк",
]

# Для разработчика перед релизом
OPTIONS = [
    "Задеплоить и сделать вид, что всё под контролем",
    "Сначала всё-таки прогнать тесты",
    "Посмотреть логи и пожалеть об этом",
    "Откатиться, пока никто не заметил",
    "Сказать: «у меня локально работало»",
]

# Для выбора пет-проекта
OPTIONS = [
    "Сделать маленькую полезную утилиту",
    "Начать новый SaaS и страдать",
    "Написать плагин для IDE",
    "Сделать локальный AI-инструмент",
    "Закрыть ноутбук и не плодить ещё один репозиторий",
]
```

Подставьте что угодно: вакансии, фильмы, страны для отпуска. Скрипт одинаково беспристрастен к выбору между шаурмой и рефакторингом.

В комментариях жду

  • Ваши самые безумные списки OPTIONS

  • Скриншоты job'ов из IBM Quantum

  • Священные холивары, куда без них :-)

Любые претензии к выбору направлять не мне, а в Институт Нильса Бора. Я только скрипт написал :-)

GitHub репо по ссылке

Удачи в экспериментах!

Комментарии (2)


  1. BibopAiSlop
    17.06.2026 22:04

    одинаково беспристрастен к выбору между шаурмой и рефакторингом. )))


  1. blackyblack
    17.06.2026 22:04

    Можно же одним кубитом обойтись.