Skip to content

Commit

Permalink
refactor!: merge lossless source coding encoders/decoders into code c…
Browse files Browse the repository at this point in the history
…lasses

- Removed `FixedToVariableEncoder`, `FixedToVariableDecoder`, `VariableToFixedEncoder`, `VariableToFixedDecoder`. Instead of `encoder = FixedToVariableEncoder(code); output = encoder(input)`, use `output = code.encode(input)`.
  • Loading branch information
rwnobrega committed Dec 12, 2024
1 parent c3d642e commit ae26c89
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 199 deletions.
4 changes: 0 additions & 4 deletions site/toc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,8 @@ Source coding:
Lossless coding:
- FixedToVariableCode
- HuffmanCode
- FixedToVariableEncoder
- FixedToVariableDecoder
- VariableToFixedCode
- TunstallCode
- VariableToFixedEncoder
- VariableToFixedDecoder
Quantization:
- ScalarQuantizer
- LloydMaxQuantizer
Expand Down
47 changes: 46 additions & 1 deletion src/komm/_lossless_coding/FixedToVariableCode.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing_extensions import Self

from .._util.information_theory import PMF
from .util import Word, is_prefix_free, is_uniquely_decodable
from .util import Word, is_prefix_free, is_uniquely_decodable, parse_prefix_free


@frozen
Expand Down Expand Up @@ -202,3 +202,48 @@ def rate(self, pmf: npt.ArrayLike) -> float:
probabilities = [np.prod(ps) for ps in it.product(pmf, repeat=k)]
lengths = [len(word) for word in self.codewords]
return np.dot(lengths, probabilities) / k

def encode(self, source_symbols: npt.ArrayLike) -> npt.NDArray[np.integer]:
r"""
Encodes a sequence of source symbols using the code.
Parameters:
source_symbols: The sequence of symbols to be encoded. Must be a 1D-array with elements in $[0:S)$, where $S$ is the source cardinality of the code.
Returns:
The sequence of encoded symbols. It is a 1D-array with elements in $[0:T)$, where $T$ is the target cardinality of the code.
Examples:
>>> code = komm.FixedToVariableCode.from_codewords(3, [(0,), (1,0), (1,1)])
>>> code.encode([1, 0, 1, 0, 2, 0])
array([1, 0, 0, 1, 0, 0, 1, 1, 0])
"""
source_symbols = np.asarray(source_symbols)
k, enc = self.source_block_size, self.enc_mapping
return np.concatenate([enc[tuple(s)] for s in source_symbols.reshape(-1, k)])

def decode(self, target_symbols: npt.ArrayLike) -> npt.NDArray[np.integer]:
r"""
Decodes a sequence of target symbols using the code. Only works if the code is prefix-free.
Parameters:
target_symbols: The sequence of symbols to be decoded. Must be a 1D-array with elements in $[0:T)$, where $T$ is the target cardinality of the code.
Returns:
output: The sequence of decoded symbols. It is a 1D-array with elements in $[0:S)$, where $S$ is the source cardinality of the code.
Examples:
>>> code = komm.FixedToVariableCode.from_codewords(3, [(0,), (1,0), (1,1)])
>>> code.decode([1, 0, 0, 1, 0, 0, 1, 1, 0])
array([1, 0, 1, 0, 2, 0])
>>> code = komm.FixedToVariableCode.from_codewords(2, [(0,), (1,0), (1,1), (1,1,0)])
>>> code.decode([1, 0, 0, 1, 0, 0, 1, 1, 0])
Traceback (most recent call last):
...
ValueError: code is not prefix-free
"""
if not self.is_prefix_free():
raise ValueError("code is not prefix-free")
target_symbols = np.asarray(target_symbols)
return parse_prefix_free(target_symbols, self.inv_enc_mapping)
48 changes: 0 additions & 48 deletions src/komm/_lossless_coding/FixedToVariableDecoder.py

This file was deleted.

38 changes: 0 additions & 38 deletions src/komm/_lossless_coding/FixedToVariableEncoder.py

This file was deleted.

47 changes: 46 additions & 1 deletion src/komm/_lossless_coding/VariableToFixedCode.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing_extensions import Self

from .._util.information_theory import PMF
from .util import Word, is_prefix_free
from .util import Word, is_prefix_free, parse_prefix_free


@frozen
Expand Down Expand Up @@ -178,3 +178,48 @@ def rate(self, pmf: npt.ArrayLike) -> float:
probabilities = [np.prod([pmf[x] for x in word]) for word in self.sourcewords]
lengths = [len(word) for word in self.sourcewords]
return self.target_block_size / np.dot(lengths, probabilities)

def encode(self, source_symbols: npt.ArrayLike) -> npt.NDArray[np.integer]:
r"""
Encodes a sequence of source symbols using the code.
Parameters:
source_symbols: The sequence of symbols to be encoded. Must be a 1D-array with elements in $[0:S)$, where $S$ is the source cardinality of the code.
Returns:
The sequence of encoded symbols. It is a 1D-array with elements in $[0:T)$, where $T$ is the target cardinality of the code.
Examples:
>>> code = komm.VariableToFixedCode.from_sourcewords(2, [(0,0,0), (0,0,1), (0,1), (1,)])
>>> code.encode([0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0])
array([0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0])
>>> code = komm.VariableToFixedCode.from_sourcewords(2, [(0,0,0), (0,0,1), (0,1), (0,)])
>>> code.encode([0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0])
Traceback (most recent call last):
...
ValueError: code is not prefix-free
"""
if not self.is_prefix_free():
raise ValueError("code is not prefix-free")
source_symbols = np.asarray(source_symbols)
return parse_prefix_free(source_symbols, self.inv_dec_mapping)

def decode(self, target_symbols: npt.ArrayLike) -> npt.NDArray[np.integer]:
r"""
Decodes a sequence of target symbols using the code.
Parameters:
target_symbols: The sequence of symbols to be decoded. Must be a 1D-array with elements in $[0:T)$, where $T$ is the target cardinality of the code.
Returns:
The sequence of decoded symbols. It is a 1D-array with elements in $[0:S)$, where $S$ is the source cardinality of the code.
Examples:
>>> code = komm.VariableToFixedCode.from_sourcewords(2, [(0,0,0), (0,0,1), (0,1), (1,)])
>>> code.decode([0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0])
array([0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0])
"""
target_symbols = np.asarray(target_symbols)
n, dec = self.target_block_size, self.dec_mapping
return np.concatenate([dec[tuple(s)] for s in target_symbols.reshape(-1, n)])
38 changes: 0 additions & 38 deletions src/komm/_lossless_coding/VariableToFixedDecoder.py

This file was deleted.

49 changes: 0 additions & 49 deletions src/komm/_lossless_coding/VariableToFixedEncoder.py

This file was deleted.

8 changes: 0 additions & 8 deletions src/komm/_lossless_coding/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
from .FixedToVariableCode import FixedToVariableCode
from .FixedToVariableDecoder import FixedToVariableDecoder
from .FixedToVariableEncoder import FixedToVariableEncoder
from .HuffmanCode import HuffmanCode
from .TunstallCode import TunstallCode
from .VariableToFixedCode import VariableToFixedCode
from .VariableToFixedDecoder import VariableToFixedDecoder
from .VariableToFixedEncoder import VariableToFixedEncoder

__all__ = [
"FixedToVariableCode",
"FixedToVariableDecoder",
"FixedToVariableEncoder",
"HuffmanCode",
"TunstallCode",
"VariableToFixedCode",
"VariableToFixedDecoder",
"VariableToFixedEncoder",
]
12 changes: 4 additions & 8 deletions tests/lossless_coding/test_fixed_to_variable_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def test_non_injective_enc_mapping():
def test_decoding_not_prefix_free(source_cardinality, codewords):
code = komm.FixedToVariableCode.from_codewords(source_cardinality, codewords)
with pytest.raises(ValueError):
komm.FixedToVariableDecoder(code)
code.decode([0, 0, 0, 0])


@pytest.mark.parametrize(
Expand Down Expand Up @@ -228,11 +228,7 @@ def test_rate_invalid_pmf(pmf):
],
)
def test_encoding_decoding(code_parameters, x, y):
source_cardinality, target_cardinality, source_block_size, codewords = (
code_parameters.values()
)
source_cardinality, _, _, codewords = code_parameters.values()
code = komm.FixedToVariableCode.from_codewords(source_cardinality, codewords)
encoder = komm.FixedToVariableEncoder(code)
decoder = komm.FixedToVariableDecoder(code)
assert np.array_equal(encoder(x), y)
assert np.array_equal(decoder(y), x)
assert np.array_equal(code.encode(x), y)
assert np.array_equal(code.decode(y), x)
6 changes: 2 additions & 4 deletions tests/lossless_coding/test_variable_to_fixed_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,7 @@ def test_encoding_decoding():
code = komm.VariableToFixedCode.from_sourcewords(
2, [(0, 0, 0), (0, 0, 1), (0, 1), (1,)]
)
encoder = komm.VariableToFixedEncoder(code)
decoder = komm.VariableToFixedDecoder(code)
x = [0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0]
y = [0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0]
assert np.array_equal(encoder(x), y)
assert np.array_equal(decoder(y), x)
assert np.array_equal(code.encode(x), y)
assert np.array_equal(code.decode(y), x)

0 comments on commit ae26c89

Please sign in to comment.