ds2-toolkit 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, David Tom Foss
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,127 @@
1
+ Metadata-Version: 2.4
2
+ Name: ds2-toolkit
3
+ Version: 0.1.0
4
+ Summary: Algebraic toolkit for 2x2 doubly stochastic matrices: cascade, entropy, correction, phase recovery, and compilation
5
+ Author-email: David Tom Foss <david@foss.com.de>
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/davidtomfoss/ds2
8
+ Project-URL: Documentation, https://ds2.readthedocs.io
9
+ Project-URL: Bug Tracker, https://github.com/davidtomfoss/ds2/issues
10
+ Project-URL: Paper (Foundations), https://doi.org/TBD
11
+ Project-URL: Paper (Toolkit), https://doi.org/TBD
12
+ Keywords: doubly stochastic,scattering matrix,cascade analysis,Birkhoff polytope,insertion loss,Landauer principle,error correction,two-port network
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Physics
22
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: numpy>=1.24
28
+ Provides-Extra: rf
29
+ Requires-Dist: scikit-rf>=0.30; extra == "rf"
30
+ Provides-Extra: test
31
+ Requires-Dist: pytest>=7.0; extra == "test"
32
+ Requires-Dist: pytest-cov>=4.0; extra == "test"
33
+ Provides-Extra: docs
34
+ Requires-Dist: sphinx>=7.0; extra == "docs"
35
+ Requires-Dist: sphinx-book-theme; extra == "docs"
36
+ Requires-Dist: myst-nb; extra == "docs"
37
+ Provides-Extra: dev
38
+ Requires-Dist: ds2[docs,rf,test]; extra == "dev"
39
+ Requires-Dist: ruff; extra == "dev"
40
+ Requires-Dist: pre-commit; extra == "dev"
41
+ Dynamic: license-file
42
+
43
+ # DS2 — Algebraic Toolkit for 2x2 Doubly Stochastic Matrices
44
+
45
+ [![PyPI version](https://img.shields.io/pypi/v/ds2)](https://pypi.org/project/ds2/)
46
+ [![Python](https://img.shields.io/pypi/pyversions/ds2)](https://pypi.org/project/ds2/)
47
+ [![License](https://img.shields.io/pypi/l/ds2)](https://github.com/davidtomfoss/ds2/blob/main/LICENSE)
48
+ [![Tests](https://img.shields.io/badge/tests-passing-brightgreen)]()
49
+
50
+ **One scalar replaces four complex scattering parameters.**
51
+
52
+ Every 2x2 doubly stochastic matrix is uniquely determined by a single eigenvalue
53
+ `lambda in [-1, 1]`, and `P(a) * P(b) = P(a*b)`. This monoid structure reduces
54
+ cascade analysis to scalar multiplication — no matrix algebra needed.
55
+
56
+ ## Installation
57
+
58
+ ```bash
59
+ pip install ds2
60
+ ```
61
+
62
+ ## Quick Start
63
+
64
+ ```python
65
+ import ds2
66
+
67
+ # Build a P(lambda) matrix
68
+ P = ds2.matrix(0.7)
69
+ # array([[0.85, 0.15],
70
+ # [0.15, 0.85]])
71
+
72
+ # Cascade 4 devices: just multiply eigenvalues
73
+ result = ds2.cascade([0.9, 0.8, -0.5, 0.95])
74
+ print(result.eigenvalue) # -0.342
75
+ print(result.insertion_loss_db) # 3.33 dB
76
+
77
+ # Analog error correction (k=3 repetition code)
78
+ ds2.correct(0.3, k=3) # 0.4365 — repels from 0
79
+
80
+ # Compile a target IL from standard components
81
+ r = ds2.compile(target_il=7.0)
82
+ print(r.chain) # ('6dB', '1dB')
83
+ print(r.error_db) # 0.0
84
+
85
+ # Landauer efficiency (quasistatic erasure)
86
+ ds2.landauer_efficiency(delta_0=0.9, delta_f=0.1, k=100)
87
+ # {'eta': 0.982, 'delta_h': 0.469, 'dissipation': 0.009, ...}
88
+
89
+ # 13-domain universality
90
+ ds2.domains.bsc(epsilon=0.1) # 0.8
91
+ ds2.domains.beam_splitter(reflectance=0.3) # -0.4
92
+ ds2.domains.scattering(s11_sq=0.3) # -0.4
93
+ ```
94
+
95
+ ## Five Tools
96
+
97
+ | Tool | What it does | Key result |
98
+ |------|-------------|------------|
99
+ | **Cascade** | `lambda_total = prod(lambda_i)` | Error < 10^-16 |
100
+ | **Thermodynamics** | Entropy budget + Landauer cost | eta -> 1 as k -> inf |
101
+ | **Correction** | `f_3(lambda) = (3*lambda - lambda^3)/2` | 26.5x capacity gain |
102
+ | **Phase recovery** | Interferometric S-matrix reconstruction | 100% on 1000 unitaries |
103
+ | **Compiler** | Target IL -> device chain | 0.00 dB error |
104
+
105
+ ## Thirteen Domains
106
+
107
+ The DS2 monoid appears in: microwave scattering, quantum gates, Markov chains,
108
+ softmax/neural networks, optical beam splitters, population genetics, resistor
109
+ dividers, polarization (Malus's law), IIR filters, binary symmetric channels,
110
+ synaptic transmission, gene regulation, and epidemic transmission.
111
+
112
+ ```python
113
+ from ds2.domains import DOMAIN_TABLE
114
+ for d in DOMAIN_TABLE:
115
+ print(f"{d['domain']:30s} lambda = {d['lambda']}")
116
+ ```
117
+
118
+ ## References
119
+
120
+ - D. T. Foss, "Algebraic Structure of Doubly Stochastic Power Matrices in
121
+ Electromagnetic Scattering," Proc. ICEAA/APWC, Toyama, Japan, Sep. 2026.
122
+ - D. T. Foss, "The DS2 Toolkit: Algebraic Methods for Two-Port Network Analysis
123
+ Across Thirteen Domains," Proc. ICEAA/APWC, Toyama, Japan, Sep. 2026.
124
+
125
+ ## License
126
+
127
+ BSD 3-Clause. See [LICENSE](LICENSE).
@@ -0,0 +1,85 @@
1
+ # DS2 — Algebraic Toolkit for 2x2 Doubly Stochastic Matrices
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/ds2)](https://pypi.org/project/ds2/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/ds2)](https://pypi.org/project/ds2/)
5
+ [![License](https://img.shields.io/pypi/l/ds2)](https://github.com/davidtomfoss/ds2/blob/main/LICENSE)
6
+ [![Tests](https://img.shields.io/badge/tests-passing-brightgreen)]()
7
+
8
+ **One scalar replaces four complex scattering parameters.**
9
+
10
+ Every 2x2 doubly stochastic matrix is uniquely determined by a single eigenvalue
11
+ `lambda in [-1, 1]`, and `P(a) * P(b) = P(a*b)`. This monoid structure reduces
12
+ cascade analysis to scalar multiplication — no matrix algebra needed.
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pip install ds2
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```python
23
+ import ds2
24
+
25
+ # Build a P(lambda) matrix
26
+ P = ds2.matrix(0.7)
27
+ # array([[0.85, 0.15],
28
+ # [0.15, 0.85]])
29
+
30
+ # Cascade 4 devices: just multiply eigenvalues
31
+ result = ds2.cascade([0.9, 0.8, -0.5, 0.95])
32
+ print(result.eigenvalue) # -0.342
33
+ print(result.insertion_loss_db) # 3.33 dB
34
+
35
+ # Analog error correction (k=3 repetition code)
36
+ ds2.correct(0.3, k=3) # 0.4365 — repels from 0
37
+
38
+ # Compile a target IL from standard components
39
+ r = ds2.compile(target_il=7.0)
40
+ print(r.chain) # ('6dB', '1dB')
41
+ print(r.error_db) # 0.0
42
+
43
+ # Landauer efficiency (quasistatic erasure)
44
+ ds2.landauer_efficiency(delta_0=0.9, delta_f=0.1, k=100)
45
+ # {'eta': 0.982, 'delta_h': 0.469, 'dissipation': 0.009, ...}
46
+
47
+ # 13-domain universality
48
+ ds2.domains.bsc(epsilon=0.1) # 0.8
49
+ ds2.domains.beam_splitter(reflectance=0.3) # -0.4
50
+ ds2.domains.scattering(s11_sq=0.3) # -0.4
51
+ ```
52
+
53
+ ## Five Tools
54
+
55
+ | Tool | What it does | Key result |
56
+ |------|-------------|------------|
57
+ | **Cascade** | `lambda_total = prod(lambda_i)` | Error < 10^-16 |
58
+ | **Thermodynamics** | Entropy budget + Landauer cost | eta -> 1 as k -> inf |
59
+ | **Correction** | `f_3(lambda) = (3*lambda - lambda^3)/2` | 26.5x capacity gain |
60
+ | **Phase recovery** | Interferometric S-matrix reconstruction | 100% on 1000 unitaries |
61
+ | **Compiler** | Target IL -> device chain | 0.00 dB error |
62
+
63
+ ## Thirteen Domains
64
+
65
+ The DS2 monoid appears in: microwave scattering, quantum gates, Markov chains,
66
+ softmax/neural networks, optical beam splitters, population genetics, resistor
67
+ dividers, polarization (Malus's law), IIR filters, binary symmetric channels,
68
+ synaptic transmission, gene regulation, and epidemic transmission.
69
+
70
+ ```python
71
+ from ds2.domains import DOMAIN_TABLE
72
+ for d in DOMAIN_TABLE:
73
+ print(f"{d['domain']:30s} lambda = {d['lambda']}")
74
+ ```
75
+
76
+ ## References
77
+
78
+ - D. T. Foss, "Algebraic Structure of Doubly Stochastic Power Matrices in
79
+ Electromagnetic Scattering," Proc. ICEAA/APWC, Toyama, Japan, Sep. 2026.
80
+ - D. T. Foss, "The DS2 Toolkit: Algebraic Methods for Two-Port Network Analysis
81
+ Across Thirteen Domains," Proc. ICEAA/APWC, Toyama, Japan, Sep. 2026.
82
+
83
+ ## License
84
+
85
+ BSD 3-Clause. See [LICENSE](LICENSE).
@@ -0,0 +1,67 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ds2-toolkit"
7
+ version = "0.1.0"
8
+ description = "Algebraic toolkit for 2x2 doubly stochastic matrices: cascade, entropy, correction, phase recovery, and compilation"
9
+ readme = "README.md"
10
+ license = "BSD-3-Clause"
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ {name = "David Tom Foss", email = "david@foss.com.de"},
14
+ ]
15
+ keywords = [
16
+ "doubly stochastic",
17
+ "scattering matrix",
18
+ "cascade analysis",
19
+ "Birkhoff polytope",
20
+ "insertion loss",
21
+ "Landauer principle",
22
+ "error correction",
23
+ "two-port network",
24
+ ]
25
+ classifiers = [
26
+ "Development Status :: 3 - Alpha",
27
+ "Intended Audience :: Science/Research",
28
+ "Operating System :: OS Independent",
29
+ "Programming Language :: Python :: 3",
30
+ "Programming Language :: Python :: 3.10",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ "Programming Language :: Python :: 3.13",
34
+ "Topic :: Scientific/Engineering :: Physics",
35
+ "Topic :: Scientific/Engineering :: Mathematics",
36
+ "Typing :: Typed",
37
+ ]
38
+ dependencies = [
39
+ "numpy>=1.24",
40
+ ]
41
+
42
+ [project.optional-dependencies]
43
+ rf = ["scikit-rf>=0.30"]
44
+ test = ["pytest>=7.0", "pytest-cov>=4.0"]
45
+ docs = ["sphinx>=7.0", "sphinx-book-theme", "myst-nb"]
46
+ dev = ["ds2[rf,test,docs]", "ruff", "pre-commit"]
47
+
48
+ [project.urls]
49
+ Homepage = "https://github.com/davidtomfoss/ds2"
50
+ Documentation = "https://ds2.readthedocs.io"
51
+ "Bug Tracker" = "https://github.com/davidtomfoss/ds2/issues"
52
+ "Paper (Foundations)" = "https://doi.org/TBD"
53
+ "Paper (Toolkit)" = "https://doi.org/TBD"
54
+
55
+ [tool.setuptools.packages.find]
56
+ where = ["src"]
57
+
58
+ [tool.pytest.ini_options]
59
+ testpaths = ["tests"]
60
+ addopts = "--cov=ds2 --cov-report=term-missing -v"
61
+
62
+ [tool.ruff]
63
+ target-version = "py310"
64
+ line-length = 88
65
+
66
+ [tool.ruff.lint]
67
+ select = ["E", "F", "W", "I", "UP", "NPY"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,70 @@
1
+ """DS2: Algebraic toolkit for 2x2 doubly stochastic matrices.
2
+
3
+ The DS2 monoid: every 2x2 doubly stochastic matrix is P(lambda) for a unique
4
+ lambda in [-1, 1], and P(a) * P(b) = P(ab). This single-scalar parameterization
5
+ replaces four complex scattering parameters for power-level two-port analysis.
6
+
7
+ Five core tools:
8
+ 1. Cascade-by-multiplication (cascade)
9
+ 2. Thermodynamic analysis (thermo)
10
+ 3. Analog error correction (correction)
11
+ 4. Phase recovery (phase)
12
+ 5. Cascade compiler (compiler)
13
+
14
+ Universal across 13 domains (domains).
15
+
16
+ Example::
17
+
18
+ >>> import ds2
19
+ >>> P = ds2.matrix(0.7)
20
+ >>> ds2.cascade([0.9, 0.8, -0.5, 0.95])
21
+ CascadeResult(eigenvalue=-0.342, insertion_loss_db=...)
22
+ >>> ds2.correct(0.3, k=3)
23
+ 0.4365...
24
+
25
+ References:
26
+ D. T. Foss, "Algebraic Structure of Doubly Stochastic Power Matrices
27
+ in Electromagnetic Scattering," Proc. ICEAA/APWC, Toyama, 2026.
28
+
29
+ D. T. Foss, "The DS2 Toolkit: Algebraic Methods for Two-Port Network
30
+ Analysis Across Thirteen Domains," Proc. ICEAA/APWC, Toyama, 2026.
31
+ """
32
+
33
+ __version__ = "0.1.0"
34
+
35
+ from ds2.core import matrix, eigenvalue, insertion_loss, from_insertion_loss
36
+ from ds2.cascade import cascade, CascadeResult
37
+ from ds2.thermo import entropy, entropy_change, landauer_cost, landauer_efficiency
38
+ from ds2.correction import correct, correction_polynomial, duality_fixed_points
39
+ from ds2.phase import recover_phase_2port
40
+ from ds2.compiler import compile, CompilerResult, STANDARD_LIBRARY
41
+ from ds2 import domains
42
+
43
+ __all__ = [
44
+ "__version__",
45
+ # core
46
+ "matrix",
47
+ "eigenvalue",
48
+ "insertion_loss",
49
+ "from_insertion_loss",
50
+ # cascade
51
+ "cascade",
52
+ "CascadeResult",
53
+ # thermo
54
+ "entropy",
55
+ "entropy_change",
56
+ "landauer_cost",
57
+ "landauer_efficiency",
58
+ # correction
59
+ "correct",
60
+ "correction_polynomial",
61
+ "duality_fixed_points",
62
+ # phase
63
+ "recover_phase_2port",
64
+ # compiler
65
+ "compile",
66
+ "CompilerResult",
67
+ "STANDARD_LIBRARY",
68
+ # domains
69
+ "domains",
70
+ ]
@@ -0,0 +1,138 @@
1
+ """Cascade-by-multiplication: reduce k-device chain to scalar arithmetic.
2
+
3
+ The DS2 monoid product P(a) * P(b) = P(ab) means cascading k lossless
4
+ two-port devices with eigenvalues lambda_1, ..., lambda_k gives:
5
+
6
+ lambda_total = prod(lambda_i)
7
+ IL_total = sum(IL_i) [in dB]
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass, field
13
+ from typing import Sequence
14
+
15
+ import numpy as np
16
+
17
+ from ds2.core import insertion_loss, matrix
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class CascadeResult:
22
+ """Result of cascading multiple DS2 devices.
23
+
24
+ Attributes
25
+ ----------
26
+ eigenvalue : float
27
+ Product eigenvalue of the cascade.
28
+ insertion_loss_db : float
29
+ Total insertion loss in dB.
30
+ matrix : np.ndarray
31
+ The 2x2 doubly stochastic matrix P(eigenvalue).
32
+ individual : tuple[float, ...]
33
+ Individual eigenvalues in cascade order.
34
+ cumulative : tuple[float, ...]
35
+ Cumulative eigenvalue after each stage.
36
+ """
37
+
38
+ eigenvalue: float
39
+ insertion_loss_db: float
40
+ matrix: np.ndarray = field(repr=False)
41
+ individual: tuple[float, ...] = field(repr=False)
42
+ cumulative: tuple[float, ...] = field(repr=False)
43
+
44
+ def __repr__(self) -> str:
45
+ return (
46
+ f"CascadeResult(eigenvalue={self.eigenvalue:.6f}, "
47
+ f"insertion_loss_db={self.insertion_loss_db:.4f}, "
48
+ f"stages={len(self.individual)})"
49
+ )
50
+
51
+
52
+ def cascade(eigenvalues: Sequence[float]) -> CascadeResult:
53
+ """Cascade k devices by multiplying their eigenvalues.
54
+
55
+ Parameters
56
+ ----------
57
+ eigenvalues : sequence of float
58
+ Eigenvalues lambda_i in [-1, 1] for each device.
59
+
60
+ Returns
61
+ -------
62
+ CascadeResult
63
+ Contains product eigenvalue, total IL, cumulative trace.
64
+
65
+ Raises
66
+ ------
67
+ ValueError
68
+ If any eigenvalue is outside [-1, 1] or sequence is empty.
69
+
70
+ Examples
71
+ --------
72
+ >>> r = cascade([0.9, 0.8, 0.7])
73
+ >>> abs(r.eigenvalue - 0.9 * 0.8 * 0.7) < 1e-15
74
+ True
75
+ """
76
+ evs = list(eigenvalues)
77
+ if not evs:
78
+ raise ValueError("Need at least one eigenvalue")
79
+ for i, lam in enumerate(evs):
80
+ if not -1.0 <= lam <= 1.0:
81
+ raise ValueError(f"eigenvalue[{i}] = {lam} outside [-1, 1]")
82
+
83
+ cum = []
84
+ prod = 1.0
85
+ for lam in evs:
86
+ prod *= lam
87
+ cum.append(prod)
88
+
89
+ lam_total = cum[-1]
90
+ il_db = insertion_loss(lam_total)
91
+ P = matrix(lam_total)
92
+
93
+ return CascadeResult(
94
+ eigenvalue=lam_total,
95
+ insertion_loss_db=il_db,
96
+ matrix=P,
97
+ individual=tuple(evs),
98
+ cumulative=tuple(cum),
99
+ )
100
+
101
+
102
+ def link_budget(
103
+ devices: Sequence[tuple[str, float]],
104
+ ) -> list[dict[str, float | str]]:
105
+ """Compute a link budget table for named devices.
106
+
107
+ Parameters
108
+ ----------
109
+ devices : sequence of (name, eigenvalue) tuples
110
+ Each device with its name and eigenvalue.
111
+
112
+ Returns
113
+ -------
114
+ list of dict
115
+ Each dict has keys: 'device', 'eigenvalue', 'cumulative_eigenvalue',
116
+ 'cumulative_il_db'.
117
+
118
+ Examples
119
+ --------
120
+ >>> budget = link_budget([("coupler", -0.002), ("mismatch", -0.589)])
121
+ >>> budget[1]['cumulative_il_db'] # doctest: +SKIP
122
+ 3.01...
123
+ """
124
+ rows = []
125
+ prod = 1.0
126
+ for name, lam in devices:
127
+ if not -1.0 <= lam <= 1.0:
128
+ raise ValueError(f"eigenvalue for '{name}' = {lam} outside [-1, 1]")
129
+ prod *= lam
130
+ rows.append(
131
+ {
132
+ "device": name,
133
+ "eigenvalue": lam,
134
+ "cumulative_eigenvalue": prod,
135
+ "cumulative_il_db": insertion_loss(prod),
136
+ }
137
+ )
138
+ return rows
@@ -0,0 +1,137 @@
1
+ """Cascade compiler: find exact device chains for target insertion loss.
2
+
3
+ Since IL values add in dB under cascade (log|lambda| is additive),
4
+ cascade design reduces to subset-sum on IL values.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+ from typing import Sequence
11
+
12
+ import numpy as np
13
+
14
+
15
+ # Standard component library (name, IL in dB)
16
+ STANDARD_LIBRARY: list[tuple[str, float]] = [
17
+ ("20dB", 20.0),
18
+ ("10dB", 10.0),
19
+ ("6dB", 6.0),
20
+ ("3dB", 3.0),
21
+ ("1dB", 1.0),
22
+ ("0.5dB", 0.5),
23
+ ("0.1dB", 0.1),
24
+ ]
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class CompilerResult:
29
+ """Result of cascade compilation.
30
+
31
+ Attributes
32
+ ----------
33
+ chain : tuple[str, ...]
34
+ Device names in the compiled chain.
35
+ target_il_db : float
36
+ Requested insertion loss.
37
+ achieved_il_db : float
38
+ Achieved insertion loss.
39
+ error_db : float
40
+ |target - achieved| in dB.
41
+ eigenvalue : float
42
+ Equivalent eigenvalue of the chain.
43
+ """
44
+
45
+ chain: tuple[str, ...]
46
+ target_il_db: float
47
+ achieved_il_db: float
48
+ error_db: float
49
+ eigenvalue: float
50
+
51
+ def __repr__(self) -> str:
52
+ chain_str = " + ".join(self.chain)
53
+ return (
54
+ f"CompilerResult(chain=[{chain_str}], "
55
+ f"target={self.target_il_db:.2f}dB, "
56
+ f"achieved={self.achieved_il_db:.2f}dB, "
57
+ f"error={self.error_db:.2f}dB)"
58
+ )
59
+
60
+
61
+ def compile(
62
+ target_il: float,
63
+ library: Sequence[tuple[str, float]] | None = None,
64
+ tolerance: float = 0.05,
65
+ ) -> CompilerResult:
66
+ """Find a device chain achieving the target insertion loss.
67
+
68
+ Uses a greedy algorithm on a sorted library. For practical libraries
69
+ (< 20 component types), this achieves exact solutions.
70
+
71
+ Parameters
72
+ ----------
73
+ target_il : float
74
+ Target insertion loss in dB (> 0).
75
+ library : sequence of (name, IL_dB) tuples, optional
76
+ Component library. Defaults to STANDARD_LIBRARY.
77
+ tolerance : float
78
+ Acceptable error in dB.
79
+
80
+ Returns
81
+ -------
82
+ CompilerResult
83
+ Compiled chain with achieved IL and error.
84
+
85
+ Examples
86
+ --------
87
+ >>> r = compile(7.0)
88
+ >>> r.chain
89
+ ('6dB', '1dB')
90
+ >>> r.error_db
91
+ 0.0
92
+ """
93
+ if target_il <= 0.0:
94
+ raise ValueError(f"target_il must be > 0, got {target_il}")
95
+ if library is None:
96
+ library = STANDARD_LIBRARY
97
+
98
+ # Sort by IL descending
99
+ lib = sorted(library, key=lambda x: x[1], reverse=True)
100
+
101
+ chain: list[str] = []
102
+ current_il = 0.0
103
+
104
+ while abs(current_il - target_il) > tolerance:
105
+ remaining = target_il - current_il
106
+ if remaining <= 0:
107
+ break
108
+
109
+ # Find best component
110
+ best_name = None
111
+ best_il = 0.0
112
+ best_gap = float("inf")
113
+
114
+ for name, il in lib:
115
+ if il <= remaining + tolerance:
116
+ gap = abs(remaining - il)
117
+ if gap < best_gap:
118
+ best_gap = gap
119
+ best_name = name
120
+ best_il = il
121
+
122
+ if best_name is None:
123
+ break
124
+
125
+ chain.append(best_name)
126
+ current_il += best_il
127
+
128
+ error = abs(current_il - target_il)
129
+ lam = 1.0 - 2.0 * 10.0 ** (-current_il / 10.0) if current_il > 0 else 1.0
130
+
131
+ return CompilerResult(
132
+ chain=tuple(chain),
133
+ target_il_db=target_il,
134
+ achieved_il_db=round(current_il, 10),
135
+ error_db=round(error, 10),
136
+ eigenvalue=lam,
137
+ )