Metadata-Version: 2.4
Name: cu3gppchan
Version: 0.1.4
Summary: GPU-accelerated 3GPP channel models (TR 38.901)
License: Apache-2.0
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: numpy
Requires-Dist: h5py
Requires-Dist: pyyaml
Requires-Dist: nvidia-cuda-runtime>=13.0.88
Requires-Dist: nvidia-curand>=10.4.0
Requires-Dist: cuda-bindings
Requires-Dist: cupy-cuda13x; sys_platform != "win32"
Requires-Dist: cupy-cuda13x[ctk]; sys_platform == "win32"
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Dynamic: requires-python

<!-- SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -->
<!-- SPDX-License-Identifier: Apache-2.0 -->

# cu3GPPChan Python Bindings

Python interface to the GPU-accelerated cu3gppchan C++/CUDA library via nanobind.

## Prerequisites

| Component | Version |
|-----------|---------|
| Python | 3.10+ |
| CUDA Toolkit | 12.x+ |
| GPU | SM 8.0+ (Ampere / Hopper) |
| numpy | any |
| h5py | any |
| pyyaml | any |
| cupy (optional) | `cupy-cuda13x` — required for `StatisticalChannel` and `FadingChannel` |

The wheel build invokes the repo CMake project and packages both
`_cu3gppchan*.so` and `libchanModels.so` into the wheel. The target machine must
still provide the CUDA driver/runtime and system libraries such as HDF5.

On Windows the layout differs: `chanModels`, HDF5, yaml-cpp and the CUDA
runtime are all linked *statically* into `_cu3gppchan.pyd`, so the wheel
contains a single self-contained extension module and no `libchanModels`
counterpart. The only DLL resolved at runtime is `curand64_10.dll` from the
`nvidia-curand` dependency. A CUDA Toolkit install is not required to build or
run; the toolchain comes from PyPI. See the [Windows section of the top-level
README](../README.md#windows).

## Build a Wheel

```bash
# From the repository root:
bash scripts/build_wheel.sh --clean

# Install the generated wheel
python3 -m pip install dist/cu3gppchan-*.whl

# Verify import and basic config construction
python3 scripts/use_python_wheel.py
```

Set CUDA architectures with:

```bash
CU3GPPCHAN_CUDA_ARCHS="80;90" bash scripts/build_wheel.sh --clean
```

For editable development:

```bash
bash scripts/build.sh --python
python3 -m pip install -e python/
```

On Windows, use the PowerShell counterpart instead. The system prerequisites are
Visual Studio Build Tools 2022 with the "Desktop development with C++" workload,
`uv`, `git`, and a PowerShell host — no CUDA Toolkit install:

```powershell
pwsh -NoProfile -File scripts/build_wheel.ps1 -CudaArchs 89 -Clean
python -m pip install (Get-Item dist\cu3gppchan-*-win_amd64.whl)
```

It builds one version-specific wheel per invocation (default `-PythonVersion
3.12`). `build_wheel.sh` instead loops Python 3.10–3.14 and partitions them into
three wheels by default: one `cp312-abi3` wheel covering the non-free-threaded
≥ 3.12 interpreters, plus a version-specific wheel each for 3.10 and 3.11, which
cannot use the stable ABI. HDF5 1.14.6 and yaml-cpp 0.8.0 are compiled from
source via CMake `FetchContent` on every Windows build, which adds several
minutes. Only **cp312 on SM 8.9** has been built and tested on Windows; other
interpreter versions and CUDA architectures are unexercised there. See the
[Windows section of the top-level README](../README.md#windows) for the full
prerequisite list.

Verify:

```python
import cu3gppchan
print(cu3gppchan.__version__)  # 0.1.4
```

### Wheel smoke tests

`python/tests/test_wheel_smoke.py` is tiered:

- Tier 1 (import + config construction) runs everywhere.
- Tier 2 (`FadingChannel` / `StatisticalChannel` GPU runs) auto-skips when no
  cupy/CUDA device is present.
- `test_windows_cuda_dlls_resolve` is Windows-only and skips elsewhere. It
  asserts that a directory containing `curand64_10.dll` was registered via
  `os.add_dll_directory`, and skips entirely if CUDA is not supplied by the
  `nvidia` pip packages in that environment.

How the build scripts use it differs by platform:

- `scripts/build_wheel.sh` smoke-tests **repaired** wheels only. Repair is on by
  default (`REPAIR=1`), and the pre-repair smoke run is gated on `REPAIR -eq 0`,
  so it is skipped unless you pass `--no-repair`. After `auditwheel repair`,
  each wheel is installed into a fresh venv and verified with **either**
  `pytest python/tests/test_wheel_smoke.py -v` — when `pytest` can be installed
  into that venv — **or** `scripts/use_python_wheel.py` as a fallback when it
  cannot. Never both.
- With `--no-repair`, that pre-repair path runs instead and uses
  `scripts/use_python_wheel.py` only; pytest is never involved on that path.
- `scripts/build_wheel.ps1` runs `scripts/use_python_wheel.py` only. **pytest
  is never invoked by the Windows build script** — run it yourself.

Run manually against an installed wheel:

    pip install "cu3gppchan[test]"
    pytest python/tests/test_wheel_smoke.py -v

## Package Structure

```
python/
  pyproject.toml
  src/cu3gppchan/
    __init__.py              Public API re-exports
    _cu3gppchan*.so          nanobind C++ extension (built by CMake)
    libchanModels.so         bundled runtime library used by the extension
    statistical_channel.py   StatisticalChannel — system-level SLS wrapper
    fading_channel.py        FadingChannel — link-level TDL/CDL wrapper
    channel_config.py        TdlChannelConfig / CdlChannelConfig
    channel_api_ref.py       3GPP TR 38.901 reference data
    cuda_utils.py            CUDA stream and array helpers
```

## API Overview

### Low-Level Bindings (no CuPy needed)

These are direct nanobind wrappers of C++ classes. Use when you manage GPU
memory yourself or only need configuration objects.

| Class | Description |
|-------|-------------|
| `SimConfig` | Simulation parameters (frequency, bandwidth, run mode) |
| `SystemLevelConfig` | Scenario, topology (sites, sectors, UTs) |
| `LinkLevelConfig` | Fading type, delay profile, mobility |
| `ExternalConfig` | External cell/UT/antenna configuration |
| `TdlConfig` / `TdlChan` | TDL channel config and engine |
| `CdlConfig` / `CdlChan` | CDL channel config and engine |
| `StatisChanModel` | System-level stochastic channel engine |
| `GauNoiseAdder` | AWGN noise on GPU |
| `OfdmModulate` / `OfdmDeModulate` | OFDM mod/demod |
| `Scenario` | Enum: `UMa`, `UMi`, `RMa`, etc. |

### High-Level Wrappers (require CuPy)

These provide a Pythonic interface with CuPy array I/O and automatic GPU
memory management.

#### `StatisticalChannel` — System-Level Channel

```python
from cu3gppchan import (
    StatisticalChannel, SimConfig, SystemLevelConfig,
    LinkLevelConfig, ExternalConfig, Scenario,
)

sim_cfg = SimConfig(center_freq_hz=3.5e9, bandwidth_hz=100e6, run_mode=1)
sys_cfg = SystemLevelConfig(scenario=Scenario.UMa, n_site=1, n_ut=10)
link_cfg = LinkLevelConfig(fast_fading_type=2)  # CDL
ext_cfg = ExternalConfig()

chan = StatisticalChannel(sim_cfg, sys_cfg, link_cfg, ext_cfg)

# Run one TTI
chan.run(ref_time=0.0)
```

#### `FadingChannel` — Link-Level TDL / CDL

```python
from cu3gppchan import FadingChannel, TdlChannelConfig

config = TdlChannelConfig(
    n_cell=1, n_ue=1,
    n_bs_ant=4, n_ue_ant=4,
    delay_profile='A',
    delay_spread_ns=30,
    max_doppler_hz=5,
    sc_spacing_hz=30e3,
)

chan = FadingChannel(config)
rx_signal = chan.run(tx_signal, ref_time=0.0, snr_db=20.0)
```

### Configuration Classes

| Class | Description |
|-------|-------------|
| `TdlChannelConfig` | TDL channel parameters (profiles A–E, delay spread, Doppler) |
| `CdlChannelConfig` | CDL channel parameters (antenna arrays, spatial correlation) |
| `CellParam` | Per-cell parameters (position, antenna panel) |
| `UtParamCfg` | Per-UT parameters (position, velocity, type) |
| `AntPanelConfig` | Antenna panel geometry `[M_g, N_g, M, N, P]` per TR 38.901 |

### Enums

| Enum | Values |
|------|--------|
| `Scenario` | `UMa`, `UMi`, `RMa`, `InH`, `InF` |
| `SensingTargetType` | ISAC target types |
| `UeType` | UE mobility types |

## Running Tests

```bash
# Static analysis (flake8 / pylint / mypy)
bash tests/run_static_tests.sh

# Python unit tests
bash tests/run_unit_tests.sh --python_only
```

## Troubleshooting

### Windows: `ImportError: DLL load failed while importing _cu3gppchan`

`_cu3gppchan.pyd` links `chanModels`, HDF5, yaml-cpp and the CUDA runtime
statically; the one CUDA DLL it still imports is `curand64_10.dll`, which ships
inside the `nvidia-curand` pip package under
`site-packages/nvidia/cu13/bin/x86_64/`. Since Python 3.8 Windows does not
search `PATH` when loading an extension module's dependencies, so
`cu3gppchan/__init__.py` registers that directory via `os.add_dll_directory` at
import time.

If the import fails, confirm `nvidia-curand` is installed in the *same*
environment as `cu3gppchan` and check which directories were registered:

    python -c "import cu3gppchan; print(cu3gppchan._dll_directories_added)"

An empty list means no `nvidia/cu13` tree was found — the package is missing or
installed into a different environment. A non-empty list that contains no
directory holding `curand64_10.dll` means the DLL itself is absent; reinstall
`nvidia-curand`. The list is always empty on Linux, where the mechanism does
not apply.

Also verify the Microsoft Visual C++ Redistributable is present. The extension
links the MSVC runtime dynamically, as every CPython extension does, and needs
both `VCRUNTIME140.dll` (C runtime) and `MSVCP140.dll` (C++ runtime — required
because `chanModels`, yaml-cpp and nanobind are C++; HDF5 is built as its C
library only, so it does not contribute). A working Python install does
*not* imply `MSVCP140.dll`: CPython is written in C and pulls in only the C
runtime. If that is the missing DLL, install the Visual C++ Redistributable.

## Dependencies

Required (installed automatically by `pip install`):

- `numpy`
- `h5py`
- `pyyaml`

Optional:

- `cupy-cuda13x` — for `StatisticalChannel`, `FadingChannel`, and GPU array wrappers

## License

Apache-2.0. See file headers for details.
