Metadata-Version: 2.4
Name: qkan-sim-lib
Version: 0.4.0
Summary: From-scratch statevector qubit simulator with a Quantum Kolmogorov-Arnold Network (QKAN) layer on top, organized as importable qkan_sim.<layer> subpackages so you can use just the layers you need.
License: MIT License
        
        Copyright (c) 2026 ByteTheBait
        
        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.
        
Classifier: License :: OSI Approved :: MIT License
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.24
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Dynamic: license-file

# Qkan-Sim-Lib

A from-scratch qubit simulation stack, split into layers, topped by a
small **Quantum Kolmogorov-Arnold Network (QKAN)**. No Qiskit/PennyLane/Cirq
dependency — just NumPy for the linear algebra.

```
5.   qkan_sim.analysis          regression metrics, reports, and entanglement
                                 diagnostics of a trained network
                |
4.   qkan_sim.qkan               QKANEdge -> QKANLayer -> QKANNetwork, trained
                                 via parameter-shift gradients; Trainer built
                                 to run longer than 24 hours with
                                 checkpoint/resume
                |
3.5. qkan_sim.noise                optional realism: depolarizing /
                                 amplitude- / phase-damping channels, wraps a
                                 circuit and replays it with noise injected
                                 (kept out of qkan training -- see
                                 qkan_sim/noise/README.md for why)
                |
3.   qkan_sim.gates               gate library (H, X, RX/RY/RZ, CNOT, ...)
                                 and the QuantumCircuit DSL (Param/Input-
                                 driven gate sequences)
                |
2.   qkan_sim.entanglement         the interaction mechanism: apply_operator
                                 lets qubits affect each other; diagnostics
                                 measure how entangled the result is (density
                                 matrix, purity, entropy)
                |
1.   qkan_sim.qubit_simulator       raw n-qubit statevector: init, storage,
                                 readout

   qkan_sim.observability   (cross-layer, not numbered) a pub/sub event bus
                             every layer above can emit to -- see
                             qkan_sim/observability/README.md
```

Every layer is a subpackage of one importable namespace, `qkan_sim`, each
with its own README and its own `tests/` directory. Importing `qkan_sim`
itself does nothing beyond defining that namespace — no subpackage is loaded
until you import it specifically, so using one layer never pulls the rest in:

- [`qkan_sim/qubit_simulator/`](qkan_sim/qubit_simulator/README.md) — layer 1
- [`qkan_sim/entanglement/`](qkan_sim/entanglement/README.md) — layer 2
- [`qkan_sim/gates/`](qkan_sim/gates/README.md) — layer 3
- [`qkan_sim/noise/`](qkan_sim/noise/README.md) — layer 3.5 (bonus: realistic decoherence)
- [`qkan_sim/qkan/`](qkan_sim/qkan/README.md) — layer 4
- [`qkan_sim/analysis/`](qkan_sim/analysis/README.md) — layer 5
- [`qkan_sim/observability/`](qkan_sim/observability/README.md) — cross-layer event bus (bonus)

## Install

Requires Python >= 3.10.

```bash
pip install -e ".[dev]"   # numpy + pytest
```

## Using just one (or a few) layers in another project

`pip install qkan-sim-lib` gets you all seven subpackages on disk, but
Python only ever *executes* the ones you actually import — so picking just
the layers you want works exactly like you'd hope:

```python
# Only want the noise-channel machinery? This is all that loads:
# qkan_sim.noise -> qkan_sim.gates -> qkan_sim.entanglement -> qkan_sim.qubit_simulator
from qkan_sim.noise import NoisyQuantumCircuit, depolarizing_channel

# Just the simulator core, nothing else:
from qkan_sim.qubit_simulator.state import QuantumState

# A couple of layers together:
from qkan_sim.entanglement import apply_operator, entanglement_entropy
from qkan_sim import analysis
```

None of these touch `qkan_sim.qkan` (the training/QKAN layer) unless you
import it — each subpackage only pulls in the layers it genuinely depends on
(e.g. `noise` needs `gates`, `entanglement`, `qubit_simulator`; it does not
need `qkan` or `analysis`).

## Run it

```bash
python main.py
```

This builds a small dataset (`y = sin(pi*x)`), trains a `[1, 4, 1]` QKAN
(2 qubits per edge) with `qkan_sim.qkan.training.Trainer`, then hands the
trained network to the `analysis` layer for a text report (loss curve,
MSE/MAE/R², predictions vs. targets) and a quantum diagnostic (how entangled
each edge's circuit actually gets across the dataset).

For a longer, checkpointed/resumable run:

```bash
python examples/train_small_qkan.py --max-epochs 0 --checkpoint runs/sin.pkl
# trains indefinitely; Ctrl+C (or kill) any time, rerun the same command to resume.
```

## Playground

```bash
python playground.py
```

A small terminal REPL over the whole stack — no new dependencies. Build a
register, apply gates, inspect entanglement/noise live, build and train a
QKAN, all interactively:

```
(qkan) qubits 2
(qkan) h 0
(qkan) cnot 0 1
(qkan) probs
|00>  ███████████████                 0.5000
|01>                                  0.0000
|10>                                  0.0000
|11>  ███████████████                 0.5000
(qkan) entropy 0
1.0000 bits
(qkan) noise depolarizing 0.1
(qkan) events on
(qkan) qkan build 1,4,1 2 2
(qkan) qkan train 100
```

Type `help` inside for the full command list.

## Testing

Each layer owns its own `tests/` directory (plus a root `tests/` for
`playground.py`):

```bash
pytest -q
```

## Why split it this way

The boundary between layers 2 and 3 is the one worth explaining: layer 2
(`entanglement`) owns the *only* function that mutates a `QuantumState`'s
amplitudes (`apply_operator`) plus the diagnostics for measuring entanglement
(reduced density matrix, purity, von Neumann entropy). Layer 3 (`gates`) owns
gate *names* and a circuit-building DSL, but always calls back down into
`apply_operator` to actually run anything — it never touches state directly.
That split means "how do qubits interact" and "what gates exist" are
independently testable and independently replaceable, and it's what lets
layer 5 reach directly into layer 2 to ask "is this trained QKAN edge
actually using entanglement, or just behaving like a classical function in a
quantum costume?" (see `qkan_sim/analysis/quantum_diagnostics.py`).

Two more boundaries worth naming: `noise` wraps a `gates.circuit.QuantumCircuit`
from the outside rather than gates gaining a `noisy=True` flag, so the
noise-free path (which `qkan` training depends on for exact parameter-shift
gradients) never has to think about noise at all. And `observability` isn't
numbered — it's not "above" or "below" anything, every layer can optionally
emit to it, and it costs nothing when nobody's listening.

Everything lives under one `qkan_sim` namespace package rather than seven
separate top-level names (`gates`, `analysis`, etc.) for two reasons: it
avoids collisions with unrelated packages of those same common names in
whatever project you import this into, and it makes the layering visible in
the import path itself (`qkan_sim.entanglement.diagnostics` reads as what it
is). `qkan_sim/__init__.py` stays intentionally empty so that importing the
namespace doesn't cost you anything — only the specific subpackage(s) you
import actually execute.
