Metadata-Version: 2.4
Name: qubecore-client
Version: 1.1.5
Summary: QubeCore Client SDK
License: MIT License
        
        Copyright (c) 2026 QubeCore Project
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: grpcio>=1.80.0
Requires-Dist: protobuf>=6.0.0
Requires-Dist: python-jose[cryptography]>=3.3.0
Provides-Extra: dev
Requires-Dist: grpcio-tools>=1.80.0; extra == 'dev'
Requires-Dist: mypy>=1.8.0; extra == 'dev'
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
Requires-Dist: pytest-mock>=3.12.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: python-jose[cryptography]>=3.3.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Description-Content-Type: text/markdown

# qubecore-client

QubeCore — Quantum Computing Operating System을 위한 Python 클라이언트 SDK입니다.  
게이트 회로 제출, 잡 상태 조회, QPU 캘리브레이션 관리 등 QubeCore의 모든 기능을 Python에서 사용할 수 있습니다.

---

## 사전 조건

- Python 3.11 이상
- 실행 중인 QubeCore gRPC 서버

---

## 설치

```bash
pip install qubecore-client
```

---

## 빠른 시작

```python
from qubecore_client import login, QubeClient

access_token, refresh_tok = login("localhost:50051", "admin", "admin")

with QubeClient(address="localhost:50051", token=access_token) as client:
    job_id = client.submit_gate_job(
        gate_circuits=["OPENQASM 2.0; include \"qelib1.inc\"; qreg q[2]; creg c[2]; h q[0]; cx q[0],q[1]; measure q->c;"],
        shots=1000,
    )
    print(client.get_job_result(job_id))
```

---

## 핵심 개념

### 잡 상태 흐름

```
PENDING → RUNNING → COMPLETED
                  ↘ FAILED
       ↘ CANCELLED  (취소는 PENDING 상태에서만 가능)
```

### 우선순위

| 값 | 레이블 | 설명 |
|----|--------|------|
| `1` | CRITICAL | 최우선 실행 (admin 전용) |
| `2` | HIGH | 높은 우선순위 |
| `3` | NORMAL | 기본값 |
| `4` | LOW | 낮은 우선순위 |

---

## API 레퍼런스

### 인증

```python
from qubecore_client import login, logout, refresh_token

# 로그인 — access token, refresh token 반환
access_token, refresh_tok = login(address, username, password)

# 갱신 — refresh token은 rotation되므로 새 refresh token도 저장해야 함
access_token, refresh_tok = refresh_token(address, refresh_tok)

# 로그아웃
logout(address, access_token)
```

---

### QubeClient

```python
client = QubeClient(address="localhost:50051", token=access_token)
client.set_token(new_token)  # 토큰 교체
client.close()               # 채널 종료
```

컨텍스트 매니저로도 사용 가능합니다:

```python
with QubeClient(address="localhost:50051", token=access_token) as client:
    ...
```

---

### 유저

| 메서드 | 설명 |
|--------|------|
| `get_me()` | 현재 유저 정보 반환 |
| `get_my_jobs(page, page_size)` | 내 잡 목록 반환 (기본: page=1, page_size=20) |

```python
info = client.get_me()
# {"user_id": "...", "username": "admin", "role": "admin", "created_at": "..."}

result = client.get_my_jobs(page=1, page_size=20)
# {"total": 42, "jobs": [{"job_id": "...", "type": "gate", "status": "COMPLETED", ...}]}
```

---

### 잡 제출

모든 submit 메서드는 `job_id: str`을 반환합니다.  
공통 선택 파라미터: `priority: int` (1=CRITICAL / 2=HIGH / 3=NORMAL / 4=LOW)

#### `submit_gate_job()`

| 파라미터 | 타입 | 필수 | 설명 |
|----------|------|:----:|------|
| `gate_circuits` | `list[str]` | O | OpenQASM 2.0 / 3.0 / QIR / JSON 회로 문자열 리스트 (서버가 prefix로 자동 감지) |
| `shots` | `int` | O | 측정 횟수 |
| `priority` | `int` | — | 잡 우선순위 (기본값: 3=NORMAL) |
| `optimization_level` | `int` | — | 트랜스파일 최적화 레벨 (0~3). **QIR 입력에는 적용되지 않음.** |

지원 포맷별 감지 규칙:

| 포맷 | 감지 기준 |
|------|----------|
| OpenQASM 2.0 | `OPENQASM 2` 로 시작 |
| OpenQASM 3.0 | `OPENQASM 3` 로 시작 |
| QIR (LLVM IR) | `__quantum__`, `target triple`, `; ModuleID`, `target datalayout`, `source_filename` 중 하나가 첫 2 KB 안에 존재. 텍스트 IR(`.ll`)만 지원 — 바이너리 비트코드(`.bc`)는 미지원. |
| JSON (pulse) | `{` 또는 `[` 로 시작 |

**OpenQASM 2.0 예제**

```python
job_id = client.submit_gate_job(
    gate_circuits=["OPENQASM 2.0; include \"qelib1.inc\"; qreg q[2]; creg c[2]; h q[0]; cx q[0],q[1]; measure q->c;"],
    shots=1000,
    priority=2,
    optimization_level=1,
)
```

**OpenQASM 3.0 예제**

```python
qasm3 = """
OPENQASM 3;
include "stdgates.inc";
qubit[2] q;
bit[2] c;
h q[0];
cx q[0], q[1];
c[0] = measure q[0];
c[1] = measure q[1];
"""
job_id = client.submit_gate_job(gate_circuits=[qasm3], shots=1000)
```

**QIR 예제** (LLVM IR 문자열)

```python
# pyqir 로 생성한 IR 문자열 또는 파일에서 읽어온 IR 사용
qir = open("bell.ll").read()
job_id = client.submit_gate_job(gate_circuits=[qir], shots=1000)
# optimization_level 은 QIR 에는 적용되지 않음 (지정해도 무시됨)
```

#### `submit_pulse_job()`

| 파라미터 | 타입 | 필수 | 설명 |
|----------|------|:----:|------|
| `pulse_circuits` | `list[str]` | O | 펄스 프로그램 JSON 문자열 리스트 |
| `shots` | `int` | O | 측정 횟수 |
| `priority` | `int` | — | 잡 우선순위 |

```python
job_id = client.submit_pulse_job(
    pulse_circuits=['[{"name":"X90","qubit":"qubit_0"},{"name":"read","qubit":"qubit_0"}]'],
    shots=1000,
)
```

#### `submit_reset_job()`

| 파라미터 | 타입 | 필수 | 설명 |
|----------|------|:----:|------|
| `qubits` | `list[str]` | O | 리셋할 큐비트 이름 리스트 |
| `shots` | `int` | O | 리셋 후 검증 측정 횟수 |
| `priority` | `int` | — | 잡 우선순위 |

```python
job_id = client.submit_reset_job(qubits=["qubit_0", "qubit_1"], shots=100)
```

---

### 캘리브레이션 잡 제출

캘리브레이션 타입별 전용 메서드를 사용합니다. 모든 메서드는 `job_id: str`을 반환합니다.

#### `submit_widescan_job()`

| 파라미터 | 타입 | 필수 | 설명 |
|----------|------|:----:|------|
| `qubit` | `str` | O | 큐비트 이름 |
| `freq_span` | `float` | O | 주파수 스캔 범위 (Hz) |
| `n_freqs` | `int` | O | 스캔 포인트 수 |
| `shots` | `int` | O | 포인트당 측정 횟수 |
| `priority` | `int` | — | 잡 우선순위 |

```python
job_id = client.submit_widescan_job(
    params={"qubit": "qubit_0", "freq_span": 100_000_000, "n_freqs": 200, "shots": 100},
)
```

#### `submit_punchout_job()`

| 파라미터 | 타입 | 필수 | 설명 |
|----------|------|:----:|------|
| `qubit` | `str` | O | 큐비트 이름 |
| `freq_span` | `float` | O | 주파수 스캔 범위 (Hz) |
| `n_freqs` | `int` | O | 주파수 포인트 수 |
| `amps` | `list[float]` | O | 스캔할 진폭 리스트 |
| `shots` | `int` | O | 포인트당 측정 횟수 |
| `priority` | `int` | — | 잡 우선순위 |

```python
job_id = client.submit_punchout_job(
    params={"qubit": "qubit_0", "freq_span": 20_000_000, "n_freqs": 20, "amps": [0.01, 0.05, 0.1, 0.2, 0.3, 0.5], "shots": 100},
)
```

#### `submit_chevron_job()`

| 파라미터 | 타입 | 필수 | 설명 |
|----------|------|:----:|------|
| `qubit` | `str` | O | 큐비트 이름 |
| `freq_span` | `float` | O | 주파수 스캔 범위 (Hz) |
| `n_freqs` | `int` | O | 주파수 포인트 수 |
| `x_twidth` | `list[float]` | O | 펄스 폭 리스트 (s) |
| `shots` | `int` | O | 포인트당 측정 횟수 |
| `center_freq` | `float` | — | 중심 주파수 (Hz, 선택) |
| `priority` | `int` | — | 잡 우선순위 |

```python
job_id = client.submit_chevron_job(
    params={"qubit": "qubit_0", "freq_span": 1_000_000, "n_freqs": 20, "x_twidth": [1e-8, 2e-8, 3e-8], "shots": 100},
)
```

#### `submit_amp_rabi_job()`

| 파라미터 | 타입 | 필수 | 설명 |
|----------|------|:----:|------|
| `qubit` | `str` | O | 큐비트 이름 |
| `n_amps` | `int` | O | 진폭 분할 수 |
| `shots` | `int` | O | 포인트당 측정 횟수 |
| `target_twidth` | `float` | — | 목표 펄스 폭 (s, 선택) |
| `prior_fit_params` | `list[float]` | — | 이전 피팅 파라미터 (선택) |
| `amp_range_min` | `float` | — | 진폭 범위 최솟값 (기본값: 0.0) |
| `amp_range_max` | `float` | — | 진폭 범위 최댓값 (기본값: 1.0) |
| `priority` | `int` | — | 잡 우선순위 |

```python
job_id = client.submit_amp_rabi_job(
    params={"qubit": "qubit_0", "n_amps": 20, "shots": 100},
)
```

#### `submit_time_rabi_job()`

| 파라미터 | 타입 | 필수 | 설명 |
|----------|------|:----:|------|
| `qubit` | `str` | O | 큐비트 이름 |
| `shots` | `int` | O | 포인트당 측정 횟수 |
| `x_twidth` | `list[float]` | — | 펄스 폭 리스트 (s, 선택) |
| `target_amplitude` | `float` | — | 목표 진폭 (선택) |
| `priority` | `int` | — | 잡 우선순위 |

```python
job_id = client.submit_time_rabi_job(
    params={"qubit": "qubit_0", "shots": 100, "x_twidth": [1e-8, 2e-8, 3e-8]},
)
```

#### `submit_ramsey_job()`

| 파라미터 | 타입 | 필수 | 설명 |
|----------|------|:----:|------|
| `qubit` | `str` | O | 큐비트 이름 |
| `delay_interval` | `list[float]` | O | 딜레이 간격 리스트 (s) |
| `shots` | `int` | O | 포인트당 측정 횟수 |
| `framsey_offsets` | `list[float]` | — | Framsey 오프셋 리스트 (선택) |
| `priority` | `int` | — | 잡 우선순위 |

```python
job_id = client.submit_ramsey_job(
    params={"qubit": "qubit_0", "delay_interval": [1e-6, 2e-6, 3e-6], "shots": 100},
)
```

#### `submit_t1_job()`

| 파라미터 | 타입 | 필수 | 설명 |
|----------|------|:----:|------|
| `qubit` | `str` | O | 큐비트 이름 |
| `delay_interval` | `list[float]` | O | 딜레이 간격 리스트 (s) |
| `shots` | `int` | O | 포인트당 측정 횟수 |
| `priority` | `int` | — | 잡 우선순위 |

```python
job_id = client.submit_t1_job(
    params={"qubit": "qubit_0", "delay_interval": [1e-6, 2e-6, 5e-6, 1e-5], "shots": 100},
)
```

#### `submit_stack_x90_job()`

| 파라미터 | 타입 | 필수 | 설명 |
|----------|------|:----:|------|
| `qubit` | `str` | O | 큐비트 이름 |
| `shots` | `int` | O | 포인트당 측정 횟수 |
| `stages` | `list[dict]` | — | 스테이지 정의 리스트 (선택) |
| `priority` | `int` | — | 잡 우선순위 |

```python
job_id = client.submit_stack_x90_job(
    params={"qubit": "qubit_0", "shots": 100},
)
```

#### `submit_drag_alpha_job()`

| 파라미터 | 타입 | 필수 | 설명 |
|----------|------|:----:|------|
| `qubit` | `str` | O | 큐비트 이름 |
| `shots` | `int` | O | 포인트당 측정 횟수 |
| `alphas` | `list[float]` | — | Alpha 값 리스트 (선택) |
| `priority` | `int` | — | 잡 우선순위 |

```python
job_id = client.submit_drag_alpha_job(
    params={"qubit": "qubit_0", "shots": 100, "alphas": [-0.5, -0.3, 0.0, 0.3, 0.5]},
)
```

#### `submit_blob_readout_freq_job()`

| 파라미터 | 타입 | 필수 | 설명 |
|----------|------|:----:|------|
| `qubit` | `str` | O | 큐비트 이름 |
| `shots` | `int` | O | 포인트당 측정 횟수 |
| `dfreads` | `list[float]` | — | Readout 주파수 오프셋 리스트 (선택) |
| `priority` | `int` | — | 잡 우선순위 |

```python
job_id = client.submit_blob_readout_freq_job(
    params={"qubit": "qubit_0", "shots": 100, "dfreads": [-1e6, 0, 1e6]},
)
```

#### `submit_readout_fidelity_job()`

Readout 신뢰도 측정 잡을 제출한다.

| 파라미터 | 타입 | 필수 | 설명 |
|----------|------|:----:|------|
| `qubit` | `str` | O | 큐비트 이름 |
| `shots` | `int` | O | 측정 횟수 |
| `priority` | `int` | — | 잡 우선순위 |

```python
job_id = client.submit_readout_fidelity_job(
    params={"qubit": "qubit_0", "shots": 1000},
)
```

#### `submit_gate_fidelity_job()`

Randomized Benchmarking(RB)으로 게이트 신뢰도 측정 잡을 제출한다.

| 파라미터 | 타입 | 필수 | 설명 |
|----------|------|:----:|------|
| `qubit` | `str` | O | 큐비트 이름 |
| `shots` | `int` | O | 시퀀스당 측정 횟수 |
| `lengths` | `list[int]` | — | RB 게이트 길이 리스트 (선택, 예: `[1, 2, 4, 8, 16, 32]`) |
| `n_seeds` | `int` | — | RB 랜덤 시드 수 (선택) |
| `seed_base` | `int` | — | 랜덤 시드 기준값 (선택) |
| `priority` | `int` | — | 잡 우선순위 |

```python
job_id = client.submit_gate_fidelity_job(
    params={"qubit": "qubit_0", "shots": 100},
)
job_id = client.submit_gate_fidelity_job(
    params={"qubit": "qubit_0", "shots": 100, "lengths": [1, 2, 4, 8, 16], "n_seeds": 5},
)
```

---

### 잡 관리

| 메서드 | 설명 |
|--------|------|
| `get_job_status(job_id)` | 잡 상태 조회 |
| `get_job_result(job_id)` | 잡 결과 조회 |
| `cancel_job(job_id)` | 잡 취소 (PENDING 상태만 가능) |

```python
status = client.get_job_status(job_id)
# {"job_id": "...", "status": "COMPLETED", "priority": 3, "submitted_at": "...", ...}

result = client.get_job_result(job_id)
# gate/pulse/reset: {"job_id": "...", "status": "COMPLETED", "results": [{"counts": {...}, "s11": {...}, "qubit_mapping": {...}}]}
# calibration:      {"job_id": "...", "status": "COMPLETED", "calibration_result": {...}}

message = client.cancel_job(job_id)  # PENDING 상태가 아니면 RuntimeError 발생
```

---

### 스트리밍

파라미터 스윕을 양방향 스트리밍으로 실행합니다. 각 iteration 결과를 즉시 yield합니다.

| 파라미터 | 타입 | 필수 | 설명 |
|----------|------|:----:|------|
| `circuit_template` | `str` | O | `{param}` 플레이스홀더가 있는 QASM 문자열 |
| `params_list` | `list[dict[str, float]]` | O | iteration당 파라미터 딕셔너리 리스트 |
| `shots` | `int` | O | iteration당 측정 횟수 |
| `priority` | `int` | — | 잡 우선순위 |
| `optimization_level` | `int` | — | 트랜스파일 최적화 레벨 (0~3) |

```python
for i, result in enumerate(client.stream_gate_job(
    circuit_template="OPENQASM 2.0; include \"qelib1.inc\"; qreg q[1]; creg c[1]; rx({theta}) q[0]; measure q[0]->c[0];",
    params_list=[{"theta": 0.0}, {"theta": 1.5707}, {"theta": 3.1415}],
    shots=200,
)):
    print(f"[{i+1}] counts={result['counts']}")
```

---

### QPU 정보

| 메서드 | 설명 |
|--------|------|
| `get_backend_info()` | 백엔드 정보 조회 (이름, 큐비트 수, 커플링 맵, 게이트 목록) |
| `get_characterization()` | 큐비트 캐릭터라이제이션 조회 (T1, T2, 주파수) |
| `update_characterization(qubits=None, priority=None)` | QPU 실측으로 캐릭터라이제이션 업데이트 (admin 전용) |

```python
info = client.get_backend_info()
# {"name": "kreo.sc-20", "num_qubits": 20, "coupling_map": [...], "native_gates": [...], "qasm_supported_gates": [...]}

data = client.get_characterization()
# {"last_calibrated": "2026-05-13T00:00:00Z", "qubits": [{"index": 0, "t1": 1.1e-4, "t2": 9.8e-5, "frequency": 4.85e9}, ...]}

# 전체 큐비트 측정 — job_id 반환 (스케줄러에 enqueue)
job_id = client.update_characterization()

# 특정 큐비트만 측정
job_id = client.update_characterization(qubits=["qubit_0", "qubit_1"])

# 우선순위 지정 (기본값: 1=CRITICAL)
job_id = client.update_characterization(qubits=["qubit_0"], priority=2)
```

---

## 호환성

| qubecore-client | qubecore |
|----------------|----------|
| 1.0.24+ | 1.x |

---

## 라이선스

MIT
