-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
21 changed files
with
452 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
[project] | ||
name = "komm" | ||
version = "0.15.1" | ||
version = "0.16.0" | ||
description = "An open-source library for Python 3 providing tools for analysis and simulation of analog and digital communication systems." | ||
readme = "README.md" | ||
authors = [{ name = "Roberto W. Nobrega", email = "[email protected]" }] | ||
|
@@ -42,6 +42,7 @@ lint = [ | |
] | ||
test = [ | ||
"pytest==8.3.3", | ||
"pytest-repeat==0.9.3", | ||
"pytest-benchmark==5.1.0", | ||
"pytest-cov==6.0.0", | ||
] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import numpy as np | ||
import numpy.typing as npt | ||
from tqdm import tqdm | ||
|
||
from .._util.information_theory import PMF | ||
from .FixedToVariableCode import FixedToVariableCode | ||
from .util import Word, empty_mapping, extended_probabilities | ||
|
||
|
||
def FanoCode( | ||
pmf: npt.ArrayLike, | ||
source_block_size: int = 1, | ||
) -> FixedToVariableCode: | ||
r""" | ||
Binary Fano code. It is a [fixed-to-variable length code](/ref/FixedToVariableCode) in which the source words are first sorted in descending order of probability and then are recursively partitioned into two groups of approximately equal total probability, assigning bit $\mathtt{0}$ to one group and bit $\mathtt{1}$ to the other, until each source word is assigned a unique codeword. For more details, see [Wikipedia: Shannon–Fano coding](https://en.wikipedia.org/wiki/Shannon%E2%80%93Fano_coding). | ||
|
||
Notes: | ||
Fano codes are always [prefix-free](/ref/FixedToVariableCode/#is_prefix_free) (hence [uniquely decodable](/ref/FixedToVariableCode/#is_uniquely_decodable)). | ||
|
||
Parameters: | ||
pmf: The probability mass function of the source. | ||
source_block_size: The source block size $k$. The default value is $k = 1$. | ||
|
||
Examples: | ||
>>> pmf = [0.7, 0.15, 0.15] | ||
|
||
>>> code = komm.FanoCode(pmf, 1) | ||
>>> code.enc_mapping # doctest: +NORMALIZE_WHITESPACE | ||
{(0,): (0,), | ||
(1,): (1, 0), | ||
(2,): (1, 1)} | ||
>>> code.rate(pmf) # doctest: +NUMBER | ||
np.float64(1.3) | ||
|
||
>>> code = komm.FanoCode(pmf, 2) | ||
>>> code.enc_mapping # doctest: +NORMALIZE_WHITESPACE | ||
{(0, 0): (0,), | ||
(0, 1): (1, 0, 0), | ||
(0, 2): (1, 0, 1), | ||
(1, 0): (1, 1, 0), | ||
(1, 1): (1, 1, 1, 1, 0, 0), | ||
(1, 2): (1, 1, 1, 1, 0, 1), | ||
(2, 0): (1, 1, 1, 0), | ||
(2, 1): (1, 1, 1, 1, 1, 0), | ||
(2, 2): (1, 1, 1, 1, 1, 1)} | ||
>>> code.rate(pmf) # doctest: +NUMBER | ||
np.float64(1.1975) | ||
""" | ||
pmf = PMF(pmf) | ||
return FixedToVariableCode( | ||
source_cardinality=pmf.size, | ||
target_cardinality=2, | ||
source_block_size=source_block_size, | ||
enc_mapping=fano_algorithm(pmf, source_block_size), | ||
) | ||
|
||
|
||
def fano_algorithm(pmf: PMF, source_block_size: int) -> dict[Word, Word]: | ||
pbar = tqdm( | ||
desc="Generating Fano code", | ||
total=2 * pmf.size**source_block_size, | ||
delay=2.5, | ||
) | ||
|
||
enc_mapping = empty_mapping(pmf.size, source_block_size) | ||
xpmf = extended_probabilities(pmf, source_block_size, pbar) | ||
stack: list[tuple[list[tuple[Word, float]], Word]] = [(xpmf, ())] | ||
while stack: | ||
current_pmf, prefix = stack.pop() | ||
if len(current_pmf) == 1: | ||
u, _ = current_pmf[0] | ||
enc_mapping[u] = prefix | ||
pbar.update() | ||
continue | ||
probs = [p for _, p in current_pmf] | ||
total = np.sum(probs) | ||
index = np.argmin(np.abs(np.cumsum(probs) - total / 2)) | ||
stack.append((current_pmf[index + 1 :], prefix + (1,))) | ||
stack.append((current_pmf[: index + 1], prefix + (0,))) | ||
|
||
pbar.close() | ||
|
||
return enc_mapping |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
from math import ceil, log2 | ||
|
||
import numpy.typing as npt | ||
from tqdm import tqdm | ||
|
||
from .._util.information_theory import PMF | ||
from .FixedToVariableCode import FixedToVariableCode | ||
from .util import Word, empty_mapping, extended_probabilities | ||
|
||
|
||
def ShannonCode( | ||
pmf: npt.ArrayLike, | ||
source_block_size: int = 1, | ||
) -> FixedToVariableCode: | ||
r""" | ||
Binary Shannon code. It is a [fixed-to-variable length code](/ref/FixedToVariableCode) in which the length of the codeword $\Enc(u)$ for a source symbol $u \in \mathcal{S}^k$ is given by | ||
$$ | ||
\ell_u = \left\lceil \log_2 \frac{1}{p_u} \right\rceil, | ||
$$ | ||
where $p_u$ is the probability of the source symbol $u$. This function implements the lexicographic order assignment as described in [Wikipedia: Shannon–Fano coding](https://en.wikipedia.org/wiki/Shannon%E2%80%93Fano_coding). | ||
|
||
Notes: | ||
Shannon codes are always [prefix-free](/ref/FixedToVariableCode/#is_prefix_free) (hence [uniquely decodable](/ref/FixedToVariableCode/#is_uniquely_decodable)). | ||
|
||
Parameters: | ||
pmf: The probability mass function of the source. | ||
source_block_size: The source block size $k$. The default value is $k = 1$. | ||
|
||
Examples: | ||
>>> pmf = [0.7, 0.15, 0.15] | ||
|
||
>>> code = komm.ShannonCode(pmf, 1) | ||
>>> code.enc_mapping # doctest: +NORMALIZE_WHITESPACE | ||
{(0,): (0,), | ||
(1,): (1, 0, 0), | ||
(2,): (1, 0, 1)} | ||
>>> code.rate(pmf) # doctest: +NUMBER | ||
np.float64(1.6) | ||
|
||
>>> code = komm.ShannonCode(pmf, 2) | ||
>>> code.enc_mapping # doctest: +NORMALIZE_WHITESPACE | ||
{(0, 0): (0, 0), | ||
(0, 1): (0, 1, 0, 0), | ||
(0, 2): (0, 1, 0, 1), | ||
(1, 0): (0, 1, 1, 0), | ||
(1, 1): (1, 0, 0, 0, 0, 0), | ||
(1, 2): (1, 0, 0, 0, 0, 1), | ||
(2, 0): (0, 1, 1, 1), | ||
(2, 1): (1, 0, 0, 0, 1, 0), | ||
(2, 2): (1, 0, 0, 0, 1, 1)} | ||
>>> code.rate(pmf) # doctest: +NUMBER | ||
np.float64(1.6) | ||
""" | ||
pmf = PMF(pmf) | ||
return FixedToVariableCode( | ||
source_cardinality=pmf.size, | ||
target_cardinality=2, | ||
source_block_size=source_block_size, | ||
enc_mapping=shannon_code(pmf, source_block_size), | ||
) | ||
|
||
|
||
def next_in_lexicographic_order(word: Word) -> Word: | ||
word_list = list(word) | ||
for i in range(len(word_list) - 1, -1, -1): | ||
if word_list[i] == 0: | ||
word_list[i] = 1 | ||
break | ||
word_list[i] = 0 | ||
return tuple(word_list) | ||
|
||
|
||
def shannon_code(pmf: PMF, source_block_size: int) -> dict[Word, Word]: | ||
pbar = tqdm( | ||
desc="Generating Shannon code", | ||
total=2 * pmf.size**source_block_size, | ||
delay=2.5, | ||
) | ||
|
||
enc_mapping = empty_mapping(pmf.size, source_block_size) | ||
v = () | ||
for u, pu in extended_probabilities(pmf, source_block_size, pbar): | ||
length = ceil(log2(1 / pu)) | ||
v = next_in_lexicographic_order(v) + (0,) * (length - len(v)) | ||
enc_mapping[u] = v | ||
pbar.update() | ||
|
||
pbar.close() | ||
|
||
return enc_mapping |
Oops, something went wrong.