QuYAML: a readable, compact quantum circuit format with Qiskit converter and CLI
Project description
QuYAML v1.0: A Human-Readable Standard for Quantum Circuits
QuYAML is a token-efficient, human-readable data format for defining quantum circuits, designed for the age of AI-driven quantum development.
This repository contains the official Python tooling for the QuYAML format. The parser targets the v0.4 specification. It converts .quyaml files or strings into executable Qiskit QuantumCircuit objects and provides a CLI for validation, conversion, diffs, and benchmarking.
Highlights in v1.0:
- Native Parameterization: High-performance sweeps using Qiskit parameters.
- Opt-Out Provenance: Auto-saving of results for reproducibility.
- Zero-Trust Security: Secure backend resolution.
- Structured control flow: if/elif/else, while, and for blocks
- Composite conditions with
&&and|| - Safer YAML surface: rejects anchors, aliases, custom tags, and merge keys
- Strict schema and canonical formatter
- CI-friendly parse-time gates and token-count gates
Why QuYAML?
As we increasingly use Large Language Models (LLMs) to assist in quantum development, the verbosity of standard formats like JSON becomes a bottleneck. QuYAML solves this by being:
- Token-Efficient: Achieves 73% fewer tokens compared to standard Qiskit JSON (measured with exact GPT-4 tokenization)
- Human-Readable: Clean, minimal syntax makes it easy for researchers to write, read, and share circuit designs
- Structured & Extensible: Built on YAML, it's easy to extend with new features like metadata and parameters
- Production-Ready: Comprehensive test suite with metamorphic testing ensures mathematical correctness
Performance Comparison
Using exact GPT-4 tokenization across 8 diverse quantum circuits:
| Format | Avg Tokens | vs QuYAML | Cost per 100K calls |
|---|---|---|---|
| OpenQASM 2.0 | 84.9 | -3.5% better ✅ | $254.64 (-$9) |
| OpenQASM 3.0 | 83.9 | -4.8% better ✅ | $251.64 (-$12) |
| QuYAML (Optimized) | 87.9 | baseline | $263.64 |
| JSON (Qiskit) | 325.0 | +72.9% worse ❌ | $975.00 (+$711) |
Key Findings:
- ✅ 73% more efficient than JSON - Save $711 per 100K API calls vs JSON
- ⚠️ 3.5% behind OpenQASM 2.0 - Costs $9 more per 100K calls
- ⚠️ 4.8% behind OpenQASM 3.0 - Costs $12 more per 100K calls
- ✨ Wins on simple circuits - 15.8% better than OpenQASM for non-parameterized circuits (Bell, GHZ, QFT, Teleportation)
- 📊 Loses on parameterized circuits - 17.2% worse than OpenQASM for circuits with symbolic parameters (QAOA, VQE, Max-Cut, Grover)
Trade-off: QuYAML prioritizes human readability with symbolic parameters ($gamma, 2*$beta) over maximum token efficiency. OpenQASM's pre-evaluated numeric values (1.0, 2.4) are more token-efficient but less readable for humans and LLMs working with parameterized circuits.
See benchmarks/README.md for detailed per-circuit analysis.
QuYAML v1.0 at a glance
QuYAML supports both original and optimized syntax (fully backward compatible):
Optimized Syntax (Recommended for LLMs)
# Bell state – minimal and token-efficient
version: 0.4
circuit: bell
qubits: q[2]
bits: c[2]
ops:
- h 0
- cx 0 1
- measure
Original Syntax (Human-Readable)
# Bell state – explicit and descriptive
version: 0.4
circuit: BellState
metadata:
description: Creates an entangled Bell pair
qreg: q[2]
creg: c[2]
instructions:
- h q[0]
- cx q[0], q[1]
- measure
Parameterized circuits
version: 0.4
circuit: qaoa_p1
qubits: q[2]
params: {gamma: 0.5, beta: 1.2}
ops:
- h 0
- h 1
- cphase(2*$gamma) 0 1
- rx(2*$beta) 0
- rx(2*$beta) 1
Control flow
Conditional blocks with composite conditions:
version: 0.4
qubits: q[2]
bits: c[2]
ops:
- h 0
- {measure: {q: 0, c: 0}}
- if:
cond: "c[0] == 1 && c[1] == 0" # && and || supported
then:
- x 1
else:
- h 1
While and for-loops:
version: 0.4
qubits: q[1]
bits: c[1]
ops:
- reset 0 # string form
- while:
cond: "c[0] == 0"
body:
- h 0
- {measure: {q: 0, c: 0}}
- for:
range: [0, 3] # start, stop (exclusive)
body:
- rx(pi/4) 0
Reset and measure forms:
reset 0or{reset: {q: 0}}measure(all qubits) or{measure: {q: 0, c: 0}}for mid-circuit bit-targeted measurement
Installation
# Clone the repository
git clone https://github.com/Ahmed-Samir11/QuYAML.git
cd QuYAML
# Install dependencies
pip install -r requirements.txt
# Optional: Install with visualizer support
pip install .[viz]
Dependencies: pyyaml, qiskit, numpy (plus optional: tiktoken, jsonschema, matplotlib)
Usage
Basic Example
from quyaml_parser import parse_quyaml_to_qiskit
# Optimized syntax for token efficiency
quyaml_string = """
circuit: bell
qubits: q[2]
ops:
- h 0
- cx 0 1
"""
quantum_circuit = parse_quyaml_to_qiskit(quyaml_string)
print(quantum_circuit)
quantum_circuit.draw('mpl')
Advanced example: Parameterized QAOA circuit
from quyaml_parser import parse_quyaml_to_qiskit
quyaml_string = """
version: 0.4
circuit: QAOA_Ansatz
qubits: q[2]
params: {gamma: 0.5, beta: 1.2}
ops:
- h 0
- h 1
- barrier
- cx 0 1
- ry(2*$gamma) 1
- cx 0 1
- barrier
- rx(2*$beta) 0
- rx(2*$beta) 1
"""
qc = parse_quyaml_to_qiskit(quyaml_string)
print(qc)
PennyLane compatibility
QuYAML circuits can be used directly with PennyLane via an optional helper:
import pennylane as qml
from quyaml_pennylane import parse_quyaml_to_pennylane
quyaml_string = """
circuit: bell
qubits: q[2]
ops:
- h 0
- cx 0 1
"""
my_template = parse_quyaml_to_pennylane(quyaml_string) # returns a PennyLane quantum function
dev = qml.device("default.qubit", wires=2)
@qml.qnode(dev)
def pl_circuit():
my_template(wires=[0, 1])
return qml.expval(qml.Z(0))
print(pl_circuit())
Notes:
- Requires installing optional dependencies:
pip install pennylane pennylane-qiskit - Under the hood, QuYAML -> Qiskit QuantumCircuit -> PennyLane via
qml.from_qiskit()
## Testing & verification
To verify the parser's correctness, install the test dependencies and run pytest:
```bash
pip install -r requirements.txt
pytest
The project includes comprehensive tests for v0.4 features and QASM3 round-trips.
Dev setup notes
- QASM3 round-trip tests require the optional OpenQASM 3 loader:
pip install qiskit_qasm3_import. - We pin Qiskit to the 2.x line for compatibility with dynamic circuits and optional integrations:
qiskit>=2.0,<2.2(seerequirements.txt). - If you use PennyLane’s qiskit plugin, ensure versions are compatible with the Qiskit pin.
- Unit Tests (
tests/test_valid_circuits.py): Basic circuit parsing with metamorphic testing - Advanced Circuit Tests (
tests/test_advanced_circuits.py): QML feature maps and QAOA ansatz circuits - Advanced Algorithm Tests (
tests/test_advanced_algorithms.py): QFT, QAOA Max-Cut, Toffoli decomposition, VQE, Quantum Teleportation - Failure Tests (
tests/test_invalid_syntax.py): Error handling and edge cases - Property-Based Tests (
tests/test_property_based.py): Hypothesis-based generative testing
Metamorphic Testing
The test suite includes metamorphic tests that prove the parser produces circuits mathematically identical to those created by Qiskit. These tests compare unitary matrices using Operator.equiv(), ensuring correctness for complex algorithms like:
- Quantum Fourier Transform (QFT)
- QAOA optimization ansätze
- Variational Quantum Eigensolver (VQE) circuits
- Quantum Teleportation protocol
Run tests with verbose output:
pytest -v
Benchmarks
Running Benchmarks
# Recommended: Optimized syntax with exact GPT-4 tokenization
python benchmarks/benchmark_optimized.py
# Original syntax baseline
python benchmarks/benchmark_with_tiktoken.py
# Test syntax compatibility
python benchmarks/test_optimized_syntax.py
Results Summary
Using exact GPT-4 tokenization (tiktoken) across 8 diverse circuits:
| Circuit Type | OpenQASM | QuYAML | Improvement |
|---|---|---|---|
| Simple Circuits | 62.8 | 53.5 | +15.8% ✓ |
| Bell State | 46 | 37 | +19.6% |
| GHZ (3 qubits) | 55 | 46 | +16.4% |
| Teleportation | 60 | 51 | +15.0% |
| QFT (3 qubits) | 90 | 79 | +12.2% |
| Parameterized | 107.0 | 125.3 | -17.2% ✗ |
| QAOA (p=1) | 65 | 82 | -26.2% |
| VQE Ansatz | 78 | 95 | -21.8% |
| Overall Average | 84.9 | 87.9 | -3.5% |
Compared to JSON:
- QuYAML: 87.9 tokens (average)
- JSON: 325.0 tokens (average)
- Reduction: 73.0% ✓
Cost Impact (GPT-4 API @ $0.03/1K tokens):
- QuYAML vs OpenQASM: -$9 per 100K calls (negligible)
- QuYAML vs JSON: +$711 per 100K calls (significant savings)
See benchmarks/README.md and OPTIMIZATION_RESULTS.md for detailed analysis.
Additional Docs
- Expert evaluation and roadmap:
docs/EVALUATION_v0.2.md
PennyLane-style Benchmarks (QuYAML vs JSON vs OpenQASM 3)
We also evaluated a set of harder, PennyLane-style circuits (QAOA Max-Cut, QFT, Grover, Phase Estimation, random entangling layers, and a shallow quantum-volume-like circuit), using exact GPT-4 tokenization and measuring parse times:
- Tokens are counted for the QuYAML source, a compact Qiskit JSON representation, and OpenQASM 3 generated from the same circuit.
- Parse times measure: QuYAML parse (QuYAML → Qiskit), JSON decode only, and QASM3 parse via Qiskit’s
qasm3.loads.
Update (includes v0.3 control-flow case and JSON→Qiskit rebuild timing):
- New case: Conditional full-register equality (2q)
- Tokens: QuYAML 131, JSON 442, QASM3 90
- Parse times (ms): QuYAML 3.545, JSON decode 0.013, JSON rebuild 0.168, QASM3 parse 8.464
Refreshed averages across all tests:
- Tokens: QuYAML 424.6, JSON 633.8, QASM3 372.9
- QuYAML vs JSON: +33.0% fewer tokens than JSON
- QuYAML vs QASM3: −13.9% more tokens than QASM3 (QASM3 wins on average)
- Parse times (ms): QuYAML 6.269, JSON decode 0.051, JSON rebuild 0.672, QASM3 parse 21.475
Per-circuit token counts and deltas:
| Circuit | QuYAML | JSON | QASM3 | QuYAML vs JSON | QuYAML vs QASM3 |
|---|---|---|---|---|---|
| QAOA Max-Cut p=3 (ring-6) | 678 | 715 | 484 | +5.2% | -40.1% |
| QFT (5 qubits) | 191 | 346 | 195 | +44.8% | +2.1% |
| Grover (4q, oracle=1111) | 268 | 527 | 236 | +49.1% | -13.6% |
| Phase Estimation (3+1) | 122 | 225 | 126 | +45.8% | +3.2% |
| QAOA Max-Cut p=5 (ring-6) | 948 | 1123 | 772 | +15.6% | -22.8% |
| Random entangling (n=6, d=4) | 738 | 1152 | 718 | +35.9% | -2.8% |
| Quantum-volume-like (n=6, layers=2) | 321 | 540 | 362 | +40.6% | +11.3% |
Per-circuit parse times (milliseconds):
| Circuit | QuYAML parse | JSON decode | QASM3 parse |
|---|---|---|---|
| QAOA Max-Cut p=3 (ring-6) | 7.409 | 0.068 | 30.593 |
| QFT (5 qubits) | 3.061 | 0.040 | 13.344 |
| Grover (4q, oracle=1111) | 3.443 | 0.035 | 20.074 |
| Phase Estimation (3+1) | 1.737 | 0.018 | 8.313 |
| QAOA Max-Cut p=5 (ring-6) | 11.093 | 0.076 | 36.120 |
| Random entangling (n=6, d=4) | 7.730 | 0.083 | 45.472 |
| Quantum-volume-like (n=6, layers=2) | 4.352 | 0.037 | 21.815 |
Reproduce locally:
# Activate venv (Windows PowerShell)
& F:/repos/QuYAML/quyaml/Scripts/Activate.ps1
# Run the PennyLane-style benchmark
F:/repos/QuYAML/quyaml/Scripts/python.exe benchmarks/benchmark_pennylane.py
The latest run output is saved to benchmarks/_last_pennylane_results.txt.
CLI utilities
The CLI supports validate, lint, format, convert (QuYAML↔QASM3), diff, compile (compact JSON), count-tokens, and time-parse. Use a file path or - for stdin.
# Validate and print a summary
F:/repos/QuYAML/quyaml/Scripts/python.exe scripts/quyaml_cli.py validate examples/bell.quyaml --summary
# Lint with JSON Schema (if jsonschema installed) and parse check
F:/repos/QuYAML/quyaml/Scripts/python.exe scripts/quyaml_cli.py lint examples/bell.quyaml
# Format to canonical key order and style (in-place)
F:/repos/QuYAML/quyaml/Scripts/python.exe scripts/quyaml_cli.py format examples/bell.quyaml -w
# Convert QuYAML -> QASM3
F:/repos/QuYAML/quyaml/Scripts/python.exe scripts/quyaml_cli.py convert examples/bell.quyaml --to qasm3 -o -
# Convert QASM3 -> QuYAML
Get-Content examples/bell.qasm | F:/repos/QuYAML/quyaml/Scripts/python.exe scripts/quyaml_cli.py convert --from qasm3 - > bell.quyaml
# Structural diff (human)
F:/repos/QuYAML/quyaml/Scripts/python.exe scripts/quyaml_cli.py diff a.quyaml b.quyaml
# Structural diff (JSON)
F:/repos/QuYAML/quyaml/Scripts/python.exe scripts/quyaml_cli.py diff a.quyaml b.quyaml --json
# Compile to compact JSON (for benchmarking/token counting)
F:/repos/QuYAML/quyaml/Scripts/python.exe scripts/quyaml_cli.py compile examples/bell.quyaml -o -
# Count tokens across QuYAML/JSON/QASM3 (requires tiktoken)
F:/repos/QuYAML/quyaml/Scripts/python.exe scripts/quyaml_cli.py count-tokens examples/bell.quyaml --json --qasm3
# Measure average parse time (ms) over N iterations
F:/repos/QuYAML/quyaml/Scripts/python.exe scripts/quyaml_cli.py time-parse examples/bell.quyaml -n 200
CI and performance gates
We ship a CI-friendly benchmark harness scripts/bench_ci.py to keep parse-time regressions and token budgets under control.
Examples:
# Parse-time gates for small, static samples
F:/repos/QuYAML/quyaml/Scripts/python.exe scripts/bench_ci.py --iters 300 --max-ms 100 benchmarks/samples/ghz.quyaml benchmarks/samples/qft3.quyaml benchmarks/samples/control_flow.quyaml
# Parse-time gates for dynamic circuits (looser threshold)
F:/repos/QuYAML/quyaml/Scripts/python.exe scripts/bench_ci.py --iters 300 --max-ms 200 benchmarks/samples/teleportation.quyaml benchmarks/samples/ipea.quyaml benchmarks/samples/rus.quyaml
# Token-count gates for static samples (500 tokens)
F:/repos/QuYAML/quyaml/Scripts/python.exe scripts/bench_ci.py --max-tokens 500 benchmarks/samples/ghz.quyaml benchmarks/samples/qft3.quyaml benchmarks/samples/control_flow.quyaml
# Token-count gates for dynamic circuits (higher threshold e.g. 1000)
F:/repos/QuYAML/quyaml/Scripts/python.exe scripts/bench_ci.py --max-tokens 1000 benchmarks/samples/teleportation.quyaml benchmarks/samples/ipea.quyaml benchmarks/samples/rus.quyaml
Our GitHub Actions workflow .github/workflows/ci.yml runs a matrix across Windows and Ubuntu with Python 3.11/3.12, executes unit tests, and enforces the gates above. Adjust thresholds per your project needs.
YAML safety and restrictions (enforced)
To keep parsing safe and predictable for both humans and LLMs, the parser enforces a restricted YAML subset and resource guardrails:
- Not allowed: YAML anchors (&name), aliases (*name), custom tags (!tag), and merge keys (<<:)
- Size limits: sane ceilings on input length and nesting depth
- Safe loader: prefers CSafeLoader when available, otherwise safe_load
Violations fail parsing with a clear error. See the JSON Schema in docs/schema/quyaml.schema.json for structural validation.
YAML safety and restrictions
To keep parsing safe and predictable for both humans and LLMs, the parser enforces a restricted YAML subset:
- Allowed: plain mappings/sequences/scalars used by the QuYAML fields
- Not allowed: YAML anchors (&name) and aliases (*name)
- Not allowed: YAML custom tags (!tag)
If any of these are present, parsing will fail with a clear error. We also use yaml.safe_load under the hood.
Editor tip: VS Code users get schema validation and completion by opening this repo; it includes .vscode/settings.json mapping the QuYAML schema (docs/schema/quyaml.schema.json) to *.quyaml files.
QuYAML language reference (selected)
Field Names (Aliases Supported)
Both original and optimized syntax work identically:
| Feature | Original | Optimized |
|---|---|---|
| Quantum register | qreg: q[n] |
qubits: q[n] |
| Classical register | creg: c[n] |
bits: c[n] |
| Parameters | parameters: |
params: |
| Instructions | instructions: |
ops: |
| Qubit index | q[0] |
0 (implicit) |
Required Fields
circuit: CircuitName # Circuit identifier
qubits: q[n] # Quantum register (n qubits) [or qreg]
ops: # List of operations [or instructions]
- gate_name args
Optional Fields
bits: c[n] # Classical register (n bits) [or creg]
params: # Symbolic parameters [or parameters]
theta: 0.5
gamma: 1.2
metadata: # Circuit metadata (optional, ignored by parser)
description: "Circuit description"
author: "Your Name"
Supported Gates
| Gate | Original Syntax | Optimized Syntax | Description |
|---|---|---|---|
| Hadamard | h q[0] |
h 0 |
Single-qubit Hadamard |
| Pauli-X | x q[0] |
x 0 |
Bit flip gate |
| CNOT | cx q[0], q[1] |
cx 0 1 |
Controlled-NOT |
| SWAP | swap q[0], q[1] |
swap 0 1 |
Swap two qubits |
| RX | rx(theta) q[0] |
rx(theta) 0 |
X-axis rotation |
| RY | ry(theta) q[0] |
ry(theta) 0 |
Y-axis rotation |
| Controlled-Phase | cphase(theta) q[0], q[1] |
cphase(theta) 0 1 |
Controlled phase gate |
| Barrier | barrier |
barrier |
Prevent optimization across barrier |
| Measure | measure |
measure |
Measure all qubits |
Parameter substitution
Use $variable to reference parameters, and expressions are evaluated with NumPy:
params: {theta: 0.5, gamma: 1.2}
ops:
- rx(2*$theta) 0
- ry($gamma+pi/4) 1
- cphase(2*(pi-$theta)) 0 1
Supported constants: pi, e
Operators: +, -, *, /, **, %
Functions: a minimal safe subset sufficient for common arithmetic; expression evaluation is sandboxed.
For full details, browse the parser source in quyaml_parser.py.
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/AmazingFeature) - Add tests for your changes
- Ensure all tests pass (
pytest) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Migration
Coming from v0.2 or v0.3? See the migration guide: docs/MIGRATION_0.3_to_0.4.md. For v0.4 to v1.0, no breaking changes were introduced to the circuit syntax, but the execution model now supports native parameterization and auto-saving.
Citation
If you use QuYAML in your research, please cite:
@software{quyaml2025,
author = {Ahmed Samir},
title = {QuYAML: A Token-Efficient Standard for Quantum Circuits},
year = {2025},
url = {https://github.com/Ahmed-Samir11/QuYAML},
version = {1.0.0}
}
Contact
Ahmed Samir
GitHub: @Ahmed-Samir11
Project Link: https://github.com/Ahmed-Samir11/QuYAML
Built for the age of AI-driven quantum development. 🚀
⚡ Parameter Sweeps (Batch Execution)
QuYAML v1.0 introduces Native Parameterization, allowing you to define symbolic parameters in your circuit and sweep over them efficiently at runtime. Unlike previous versions that re-parsed the circuit for every value, v1.0 uses Qiskit's assign_parameters to bind values to a single circuit object, enabling high-performance batch execution.
1. Define a Parameterized Circuit
Use the $variable syntax in your QuYAML file. You can define a default value in params, but it will be overridden during a sweep.
# sweep_example.quyaml
version: '0.4'
circuit: native_sweep
qubits: q[1]
bits: c[1]
params:
theta: 0 # Default value (will be symbolic in sweep)
ops:
- h 0
- rx($theta) 0
- measure q[0], c[0]
2. Execute with a Sweep
Define a parameter_sweep dictionary in the execution config (or pass it programmatically).
from quyaml_parser import parse_quyaml_job
import numpy as np
# Load the circuit
with open("sweep_example.quyaml", "r") as f:
job = parse_quyaml_job(f.read())
# Define the sweep at runtime
# This overrides any 'parameter_sweep' defined in the YAML file
job.execution['parameter_sweep'] = {
'theta': np.linspace(0, np.pi, 10).tolist()
}
# Execute
# This generates 10 bound circuits from 1 symbolic circuit (High Performance)
result = job.execute(log_dir="./runs")
print(f"Job ID: {result.job_id}")
print(f"Results saved to: ./runs/quyaml_job_{result.job_id}.json")
🛡️ Provenance & Reproducibility
QuYAML v1.0 enforces Opt-Out Provenance. By default, every job execution automatically saves a comprehensive JSON manifest to disk, ensuring you never lose the context of an experiment.
The saved file (./runs/quyaml_job_<id>.json) contains:
- Job ID & Timestamp: When and where it ran.
- Backend Info: Which quantum computer (or simulator) was used.
- Full Results: The counts or probabilities returned.
- Metadata: The exact shots, parameter sweep values, and QuYAML version used.
To customize the save location, pass log_dir to the execute method:
result = job.execute(log_dir="./experiments/2025_qaoa_runs")
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file quyaml-1.0.0.tar.gz.
File metadata
- Download URL: quyaml-1.0.0.tar.gz
- Upload date:
- Size: 42.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb76c55a31e1e9940160a215a3064a06e184cf66ddcabf3295c9c99bcaf07550
|
|
| MD5 |
22c82ec89d379f5c5d8c16eacab35abb
|
|
| BLAKE2b-256 |
fce7e9001326c2e0b086e6c6be38143ace0f95378fb9a922866744c20bd8714a
|
File details
Details for the file quyaml-1.0.0-py3-none-any.whl.
File metadata
- Download URL: quyaml-1.0.0-py3-none-any.whl
- Upload date:
- Size: 27.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e6fb8636850e0430630c89cdcdfd9f8937c8990eb0f3a3d37f515c394bd1a687
|
|
| MD5 |
51fc42e8a547ddf3c3839f9aafc8900e
|
|
| BLAKE2b-256 |
644807d3b56ecde8b8776312cc5d70770253fd3bf2e4262f6c94c49c9b5544c3
|