Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions QPIXL/qiskit/README(new).md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
### `qpixl_module.py`
- Wraps an image or data source into a reusable quantum circuit using the QPIXL (cFRQI) encoding scheme.
- Supports compression, optional gate injection, and validation.
### `quantum_composer.py`

A flexible system to combine multiple quantum circuits using integration rules.

#### Key Features:

* Combines modules using rules like:
* `sequential`: stack circuits in time
* `merge`: align circuits side-by-side with optional entanglement
* `hardware_aware_sequential`: transpile for specific coupling maps
* Built-in circuit safety (no duplicate bits)
* Modular `CircuitModule` interface for Qiskit circuits or QPIXL modules

#### Example:

```python
composer = QuantumComposer([qpixl_mod, algo_mod])
combined = composer.combine(rule="merge", connection_map={0: 5}, entangle_type="cx")
``````
### `QPIXL_demo_composer_extension.ipynb`

A complete test notebook demonstrating how to use the new modules.```

#### Covers:

* Basic image encoding + algorithm circuit
* Audio signal → QPIXL circuit integration
* Injecting gates during encoding
* Hardware-aware transpilation with `transpile()`
* Entanglement across subsystems
* Compression vs uncompressed QPIXL comparison
* `process_audio` helper
* You can define this in the notebook to convert `.mp3` audio signals into QPIXL-compatible angle arrays and circuits.
* **Steps:**
* Downsample audio → normalize → map to `[0, π]` rotation angles
* Use `cFRQI()` to create circuit
* Supports direct integration into `QPIXLModule`
126 changes: 126 additions & 0 deletions QPIXL/qiskit/qpixl_module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import numpy as np
from qiskit import QuantumCircuit, transpile
from enum import Enum
from QPIXL.qiskit.qpixl import cFRQI


class InjectionPoint(Enum):
BEFORE_ENCODING = "before"
DURING_ENCODING = "during"
AFTER_ENCODING = "after"


class QPIXLModule:
def __init__(self, image_array, compression=0, name="QPIXLModule", algorithm_qubits=0):
if not isinstance(image_array, np.ndarray):
raise TypeError("Expected NumPy array")
if image_array.ndim != 1:
raise ValueError("Expected a 1D flattened array")
if not (0 <= compression <= 100):
raise ValueError("Compression must be in [0, 100]")

self.image_array = image_array
self.compression = compression
self._name = name
self._algo_qubits = algorithm_qubits
self._injections = []

@property
def name(self):
return self._name

def add_injection(self, when, gate_type, qubits, params=None, condition=None):
"""Injects a standard gate like ry, cx, crx, unitary, etc."""
self._injections.append({
"when": InjectionPoint(when),
"gate": gate_type.lower(),
"qubits": qubits,
"params": params or {},
"cond": condition
})
return self

def add_custom_injection(self, func, when):
"""Injects a custom callable. Signature: func(circuit, idx, angle)"""
self._injections.append({
"when": InjectionPoint(when),
"custom": func
})
return self

def get_circuit(self, optimize=False, verbose=False):
# Total qubits = log2(image) + encoding qubit + optional algorithm qubits
total_qubits = int(np.log2(len(self.image_array))) + 2 + self._algo_qubits
circuit = QuantumCircuit(total_qubits, name=self._name)

# Inject before encoding
self._apply_injections(circuit, InjectionPoint.BEFORE_ENCODING, total_qubits)

# Encode angles with QPIXL (gets a new circuit)
base = cFRQI(self.image_array, self.compression)

# Strip ghost registers and names
base.name = None
base.qregs = []
base.cregs = []

# Inject during encoding (per angle)
for i, angle in enumerate(self.image_array):
for inj in self._injections:
if inj.get("when") != InjectionPoint.DURING_ENCODING:
continue
if "custom" in inj:
inj["custom"](circuit, i, angle)
else:
q = self._resolve_qubits(inj["qubits"], total_qubits)
if not inj.get("cond") or inj["cond"](i, angle):
self._apply_gate(circuit, inj["gate"], q, inj["params"])

# Compose base into the correct qubit range (avoid mismatch)
base_qubit_count = base.num_qubits
circuit.compose(base, qubits=range(base_qubit_count), inplace=True)

# Inject after encoding
self._apply_injections(circuit, InjectionPoint.AFTER_ENCODING, total_qubits)

if optimize:
circuit = transpile(circuit, optimization_level=3)

if verbose:
print(f"[QPIXL] qubits={circuit.num_qubits}, depth={circuit.depth()}, compression={self.compression}")

return circuit.copy()

def _apply_injections(self, circuit, when, total_qubits):
for inj in self._injections:
if inj.get("when") != when:
continue
if "custom" in inj:
inj["custom"](circuit, None, None)
else:
q = self._resolve_qubits(inj["qubits"], total_qubits)
if not inj.get("cond") or inj["cond"](None, None):
self._apply_gate(circuit, inj["gate"], q, inj["params"])

def _resolve_qubits(self, qubit_ids, total):
return [q if q >= 0 else total + q for q in qubit_ids]

def _apply_gate(self, circuit, gate, qubits, params):
theta = params.get("angle", 0)
if gate == "ry":
circuit.ry(theta, qubits[0])
elif gate == "cry":
circuit.cry(theta, *qubits)
elif gate == "crx":
circuit.crx(theta, *qubits)
elif gate == "cz":
circuit.cz(*qubits)
elif gate == "cx":
circuit.cx(*qubits)
elif gate == "swap":
circuit.swap(*qubits)
elif gate == "unitary":
circuit.unitary(params["matrix"], qubits, label="U")
else:
raise ValueError(f"Unsupported gate: {gate}")

Loading