Когда не можешь выбрать, куда пойти на свидание, можно мучиться между кофейней, баром и прогулкой. А можно поступить взросло: отправить задачу на квантовый компьютер IBM и переложить ответственность на физику. Внутри — Qiskit, 8 кубитов, реальный job_id и самый пафосный способ заменить подбрасывание обычной монетки.
GitHub репо по ссылке
Да, это максимально избыточный способ заменить random.choice(). В этом и смысл. Зато в конце будет настоящий job_id, запуск на IBM Quantum и моральное право сказать: «Это не я выбрал. Это квантовая механика так решила».
Что понадобится
-
Python и библиотека
qiskitpip install qiskit qiskit-aer qiskit-ibm-runtime -
Бесплатный токен IBM Quantum. Регистрируетесь на quantum.ibm.com, получаете токен, копируете в переменную окружения
IBM_QUANTUM_TOKEN. Всё.

Код
Схема такая: 8 кубитов, гейт Адамара на каждом, измерение, 256 повторных запусков. На выходе получаем набор битстрингов, берём один из реально измеренных результатов, превращаем его в число и маппим на индекс в списке вариантов.
В квантовые вычисления я заходил как разработчик, поэтому часть терминов пришлось отдельно разобрать по документации и примерам. Но в итоге схема оказалась вполне понятной.
Код под спойлером:
```pythonimport osimport sysimport secretsfrom dataclasses import dataclassfrom 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 = 256QUBITS = 8REGISTER_NAME = "coin"# ========================# ВАШ СПИСОК ВАРИАНТОВ# ========================OPTIONS = [ "Кофейня", "Бар", "Кино", "Прогулка", "Не идти никуда и наконец-то выспаться",]# ========================@dataclass(frozen=True)class CoinRun: bitstrings: list[str] counts: dict[str, int] backend: str job_id: str | None = Nonedef 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 qcdef 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 Nonedef 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", indexdef 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 в панели отчитался о выполненной задаче.

Как это работает
Честно — сначала сам не знал, но разобрался.
Схема такая:
-
Создаём 8 кубитов.
-
На каждый кубит применяем гейт Адамара. Если совсем грубо: после этого при измерении каждый кубит может дать
0или1с примерно равной вероятностью. -
Измеряем схему 256 раз и получаем набор битстрингов вроде
01010110,11100001,00011010. -
Берём один реально измеренный битстринг.
-
Превращаем его в число.
-
Маппим число на индекс в списке вариантов.
Какие списки можно подставить
```python# Мягкий режим: написать или не написатьOPTIONS = [ "Написать Ане", "Написать Кате", "Написать Маше", "Написать Лене", "Не писать никому и героически лечь спать в 23:00",]# Свидание без выбора людей как пунктов менюOPTIONS = [ "Позвать в кофейню", "Позвать в бар", "Позвать в кино", "Позвать просто погулять", "Не устраивать социальный эксперимент и спокойно пережить вечер",]# Режим «я голодный, но решения принимать не способен»OPTIONS = [ "Пицца", "Суши", "Бургер", "Шаурма", "Гречка: скучно, зато без архитектурных рисков",]# Для тех, кто в субботу за ноутомOPTIONS = [ "Закрыть баг", "Написать тесты", "Дописать README", "Рефакторить то, что никто не просил трогать", "Пойти гулять, пока проект не превратился в свой фреймворк",]# Для разработчика перед релизомOPTIONS = [ "Задеплоить и сделать вид, что всё под контролем", "Сначала всё-таки прогнать тесты", "Посмотреть логи и пожалеть об этом", "Откатиться, пока никто не заметил", "Сказать: «у меня локально работало»",]# Для выбора пет-проектаOPTIONS = [ "Сделать маленькую полезную утилиту", "Начать новый SaaS и страдать", "Написать плагин для IDE", "Сделать локальный AI-инструмент", "Закрыть ноутбук и не плодить ещё один репозиторий",]```
Подставьте что угодно: вакансии, фильмы, страны для отпуска. Скрипт одинаково беспристрастен к выбору между шаурмой и рефакторингом.
В комментариях жду
-
Ваши самые безумные списки
OPTIONS -
Скриншоты job’ов из IBM Quantum
-
Священные холивары, куда без них 🙂
Любые претензии к выбору направлять не мне, а в Институт Нильса Бора. Я только скрипт написал 🙂
GitHub репо по ссылке
Удачи в экспериментах!
ссылка на оригинал статьи https://habr.com/ru/articles/1048848/