Metadata-Version: 2.4
Name: pyPhyNR
Version: 0.2.0
Summary: Python toolkit for 5G NR physical layer simulations
Home-page: https://github.com/kir812/pyPhyNR
Author: kir812
License: MIT
Keywords: 5G NR OFDM waveform multicarrier signal processing
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy
Requires-Dist: matplotlib
Requires-Dist: scipy
Provides-Extra: notebooks
Requires-Dist: jupyterlab; extra == "notebooks"
Requires-Dist: ipykernel; extra == "notebooks"
Provides-Extra: dev
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Requires-Dist: black; extra == "dev"
Requires-Dist: flake8; extra == "dev"
Dynamic: author
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: license
Dynamic: license-file
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# pyPhyNR

Python tools for NR resource grids, PDSCH/DMRS and complex OFDM waveform generation.

Version 0.2.0 adds composite waveform generation, relative carrier gains and optional
equal average PSD. Carrier RB counts are selected from bandwidth and numerology;
FFT size is derived from sampling rate divided by subcarrier spacing.

## Installation

```sh
python -m pip install --upgrade pyPhyNR
```

Python 3.9 or later is required. Runtime dependencies are NumPy, SciPy and Matplotlib.
For the example notebooks, install `pyPhyNR[notebooks]` and select a Python kernel
with that package installed. For a source checkout, use
`python -m pip install -e ".[notebooks]"` from the repository root.

## Single carrier

```python
import pyPhyNR as pynr

signal = pynr.NRSignalBuilder(bandwidth_mhz=100, numerology=1, cell_id=1)
signal.configure_carrier(sample_rate=245.76e6).initialize_grid()
signal.add_pdsch(
    start_rb=0,
    num_rb=signal.carrier_config.n_resource_blocks,
    start_symbol=0,
    num_symbols=14,
    slot_pattern=list(range(signal.carrier_config.numerology.slots_per_frame)),
    modulation="QAM256",
).add_dmrs(dmrs_positions=[2, 11], clear_full_symbol=False)

iq = signal.generate_signal(target_rms=1.0)
pynr.utils.plot_frequency_domain(iq, signal.carrier_config)
```

Numerology 0 selects 15 kHz SCS and numerology 1 selects 30 kHz. For the example
above, the carrier has 273 RBs and an 8192-point FFT. PDSCH can fill the carrier
using its derived RB count, or occupy a smaller allocation using an explicit count.

## Composite waveform

Populate a second carrier independently, then combine the two resource grids:

```python
second = pynr.NRSignalBuilder(bandwidth_mhz=100, numerology=1, cell_id=2)
second.configure_carrier(sample_rate=245.76e6).initialize_grid()
second.add_pdsch(
    start_rb=0,
    num_rb=second.carrier_config.n_resource_blocks,
    start_symbol=0,
    num_symbols=14,
    slot_pattern=list(range(second.carrier_config.numerology.slots_per_frame)),
    modulation="QAM256",
).add_dmrs(dmrs_positions=[2, 11], clear_full_symbol=False)

composite = pynr.NRCompositeSignalBuilder(sample_rate=245.76e6, equal_psd=True)
composite.add_carrier(signal, frequency_offset_hz=-50e6)
composite.add_carrier(second, frequency_offset_hz=50e6, gain_db=0.0)
iq = composite.generate_signal(target_rms=1.0)
pynr.utils.plot_frequency_domain(iq, composite)
```

Frequency offsets are relative to output DC. `gain_db=-3` lowers a carrier by 3 dB.
`equal_psd=True` sets each carrier's average power in proportion to its allocated
bandwidth; use it for mixed bandwidths such as NR100 + NR10 + NR5. Leave per-carrier
`target_rms` unset in this mode. The final `target_rms` sets the composite RMS while
preserving relative carrier powers. IQ amplitudes are not clipped.

Composite generation supports a common 15 or 30 kHz SCS, normal CP and one 10 ms
frame. Carriers are synthesized directly at the output sampling rate without
changing their input configurations. Overlapping allocations and carriers outside
the output Nyquist interval are rejected. Equal PSD refers to average power per
allocated Hz; differently loaded or bursty grids can have different active-symbol PSD.

## Examples

- Two adjacent NR100 carriers: `notebooks/nr_2x100mhz_composite.ipynb`
- Mixed NR100 + NR10 + NR5: `notebooks/nr100_nr10_nr5_composite.ipynb`
- Single-carrier 64-QAM: `notebooks/nr20m_64qam_test.ipynb`
- Single-carrier 256-QAM: `notebooks/nr20m_256qam_test.ipynb`
- TDD waveform: `notebooks/nr20m_tdd_udc38_test.ipynb`

Notebooks use the installed library and its plotting helpers. Optional TXT export
uses NumPy. Download the notebooks in the source distribution from
[PyPI](https://pypi.org/project/pyPhyNR/#files), or use this repository;
generated IQ files and saved notebook outputs are excluded from the release.

## Compatibility notes for 0.2.0

The existing `pynr.NRSignalBuilder` and `pyPhyNR.core` imports remain available.
Omit `fft_size` for automatic selection, including non-power-of-two FFTs such as
1920 at 57.6 Msps / 30 kHz. An explicit FFT size must equal sampling rate / SCS;
inconsistent values now raise an error instead of changing the waveform's SCS.
Notebook tools are an optional dependency via the `notebooks` extra. The historical
`matlab_to_python_nr_gen` helpers remain available for compatibility; new examples
use `NRSignalBuilder`.

This is an experimental signal-generation library, not a complete encoded NR link
or an RF conformance certification tool. RF band/raster compatibility and other
physical channels remain outside the composite generator's scope.

## Build and release

```sh
python -m pip install ".[dev]"
python -m build
python -m twine check dist/*
python -m twine upload --repository-url https://upload.pypi.org/legacy/ dist/*
```

Use a PyPI API token through Twine's supported credential configuration.

## License

MIT. Copyright kir812.
