sc_neurocore_engine/photonic/
bindings.rs1use pyo3::prelude::*;
12use pyo3::types::PyDict;
13
14use crate::photonic;
15
16#[pyfunction]
20#[pyo3(signature = (adjacency_flat, n, pitch_um=250.0, loss_db_per_cm=2.0))]
21fn py_ph_route_waveguides<'py>(
22 py: Python<'py>,
23 adjacency_flat: Vec<f64>,
24 n: usize,
25 pitch_um: f64,
26 loss_db_per_cm: f64,
27) -> PyResult<Py<PyAny>> {
28 let result = photonic::route_waveguides(&adjacency_flat, n, pitch_um, loss_db_per_cm);
29
30 let dict = PyDict::new(py);
31 let sources: Vec<usize> = result.iter().map(|r| r.source).collect();
32 let targets: Vec<usize> = result.iter().map(|r| r.target).collect();
33 let lengths: Vec<f64> = result.iter().map(|r| r.length_um).collect();
34 let losses: Vec<f64> = result.iter().map(|r| r.loss_db).collect();
35 let crossings: Vec<usize> = result.iter().map(|r| r.n_crossings).collect();
36
37 dict.set_item("sources", sources)?;
38 dict.set_item("targets", targets)?;
39 dict.set_item("lengths_um", lengths)?;
40 dict.set_item("losses_db", losses)?;
41 dict.set_item("crossings", crossings)?;
42 dict.set_item("n_segments", result.len())?;
43 dict.set_item("backend", "rust")?;
44 Ok(dict.into_any().unbind())
45}
46
47#[pyfunction]
49fn py_ph_mzi_transfer_matrix(_py: Python<'_>, phase_rad: f64) -> Vec<f64> {
50 photonic::mzi_transfer_matrix(phase_rad).to_vec()
51}
52
53#[pyfunction]
55fn py_ph_cascade_mzi(_py: Python<'_>, phases: Vec<f64>) -> Vec<f64> {
56 photonic::cascade_mzi(&phases).to_vec()
57}
58
59#[pyfunction]
61#[pyo3(signature = (channel_ids, wavelengths, bandwidths, powers, adjacent_xt_db=-25.0))]
62fn py_ph_analyze_crosstalk<'py>(
63 py: Python<'py>,
64 channel_ids: Vec<usize>,
65 wavelengths: Vec<f64>,
66 bandwidths: Vec<f64>,
67 powers: Vec<f64>,
68 adjacent_xt_db: f64,
69) -> PyResult<Py<PyAny>> {
70 let channels: Vec<(usize, f64, f64, f64)> = channel_ids
71 .into_iter()
72 .zip(wavelengths)
73 .zip(bandwidths)
74 .zip(powers)
75 .map(|(((id, wl), bw), p)| (id, wl, bw, p))
76 .collect();
77
78 let result = photonic::analyze_crosstalk(&channels, adjacent_xt_db);
79
80 let dict = PyDict::new(py);
81 let ids: Vec<usize> = result.iter().map(|r| r.channel_id).collect();
82 let xts: Vec<f64> = result.iter().map(|r| r.crosstalk_db).collect();
83 let osnrs: Vec<f64> = result.iter().map(|r| r.osnr_db).collect();
84 let adjs: Vec<usize> = result.iter().map(|r| r.n_adjacent).collect();
85
86 dict.set_item("channel_ids", ids)?;
87 dict.set_item("crosstalk_db", xts)?;
88 dict.set_item("osnr_db", osnrs)?;
89 dict.set_item("n_adjacent", adjs)?;
90 dict.set_item("backend", "rust")?;
91 Ok(dict.into_any().unbind())
92}
93
94#[pyfunction]
96#[pyo3(signature = (wg_sources, wg_targets, wg_losses, laser_power_dbm=0.0, detector_sensitivity_dbm=-20.0))]
97fn py_ph_analyze_power_budget<'py>(
98 py: Python<'py>,
99 wg_sources: Vec<usize>,
100 wg_targets: Vec<usize>,
101 wg_losses: Vec<f64>,
102 laser_power_dbm: f64,
103 detector_sensitivity_dbm: f64,
104) -> PyResult<Py<PyAny>> {
105 let wgs: Vec<(usize, usize, f64)> = wg_sources
106 .into_iter()
107 .zip(wg_targets)
108 .zip(wg_losses)
109 .map(|((s, t), l)| (s, t, l))
110 .collect();
111
112 let result =
113 photonic::analyze_power_budget(&wgs, &[], laser_power_dbm, detector_sensitivity_dbm);
114
115 let dict = PyDict::new(py);
116 let margins: Vec<f64> = result.iter().map(|r| r.margin_db).collect();
117 let passed: Vec<bool> = result.iter().map(|r| r.passed).collect();
118 let total_losses: Vec<f64> = result.iter().map(|r| r.total_loss_db).collect();
119
120 dict.set_item("margins_db", margins)?;
121 dict.set_item("passed", passed)?;
122 dict.set_item("total_losses_db", total_losses)?;
123 dict.set_item("n_paths", result.len())?;
124 dict.set_item("backend", "rust")?;
125 Ok(dict.into_any().unbind())
126}
127
128#[pyfunction]
130#[pyo3(signature = (num_waveguides, gap_nm, coupling_length_um, wavelength_nm=1550.0, core_index=3.48, cladding_index=1.45))]
131fn py_ph_analyze_crosstalk_bank<'py>(
132 py: Python<'py>,
133 num_waveguides: usize,
134 gap_nm: f64,
135 coupling_length_um: f64,
136 wavelength_nm: f64,
137 core_index: f64,
138 cladding_index: f64,
139) -> PyResult<Py<PyAny>> {
140 let r = photonic::analyze_crosstalk_bank(
141 num_waveguides,
142 gap_nm,
143 coupling_length_um,
144 wavelength_nm,
145 core_index,
146 cladding_index,
147 );
148 let dict = PyDict::new(py);
149 dict.set_item("num_waveguides", r.num_waveguides)?;
150 dict.set_item("num_pairs", r.num_near_pairs + r.num_far_pairs)?;
151 dict.set_item("num_near_pairs", r.num_near_pairs)?;
152 dict.set_item("num_far_pairs", r.num_far_pairs)?;
153 dict.set_item("gap_nm", r.gap_nm)?;
154 dict.set_item("coupling_length_um", r.coupling_length_um)?;
155 dict.set_item("adjacent_coupling_ratio", r.adjacent_coupling_ratio)?;
156 dict.set_item("adjacent_isolation_db", r.adjacent_isolation_db)?;
157 dict.set_item("next_nearest_coupling_ratio", r.next_nearest_coupling_ratio)?;
158 dict.set_item("next_nearest_isolation_db", r.next_nearest_isolation_db)?;
159 dict.set_item("worst_isolation_db", r.worst_isolation_db)?;
160 dict.set_item("mean_coupling_ratio", r.mean_coupling_ratio)?;
161 dict.set_item("max_coupling_ratio", r.max_coupling_ratio)?;
162 dict.set_item("crosstalk_safe", r.crosstalk_safe)?;
163 dict.set_item("backend", "rust")?;
164 Ok(dict.into_any().unbind())
165}
166
167#[pyfunction]
171#[pyo3(signature = (pairs_a, pairs_b, gaps_nm, lengths_um, wavelength_nm=1550.0, core_index=3.48, cladding_index=1.45))]
172fn py_ph_analyze_crosstalk_pairs<'py>(
173 py: Python<'py>,
174 pairs_a: Vec<usize>,
175 pairs_b: Vec<usize>,
176 gaps_nm: Vec<f64>,
177 lengths_um: Vec<f64>,
178 wavelength_nm: f64,
179 core_index: f64,
180 cladding_index: f64,
181) -> PyResult<Py<PyAny>> {
182 let n = pairs_a.len();
183 if pairs_b.len() != n || gaps_nm.len() != n || lengths_um.len() != n {
184 return Err(pyo3::exceptions::PyValueError::new_err(
185 "pairs_a, pairs_b, gaps_nm, lengths_um must be equal length",
186 ));
187 }
188 let pairs: Vec<(usize, usize, f64, f64)> = pairs_a
189 .into_iter()
190 .zip(pairs_b)
191 .zip(gaps_nm)
192 .zip(lengths_um)
193 .map(|(((a, b), g), l)| (a, b, g, l))
194 .collect();
195 let results =
196 photonic::analyze_crosstalk_pairs(&pairs, wavelength_nm, core_index, cladding_index);
197
198 let dict = PyDict::new(py);
199 let idx_a: Vec<usize> = results.iter().map(|r| r.index_a).collect();
200 let idx_b: Vec<usize> = results.iter().map(|r| r.index_b).collect();
201 let gaps: Vec<f64> = results.iter().map(|r| r.gap_nm).collect();
202 let lens: Vec<f64> = results.iter().map(|r| r.coupling_length_um).collect();
203 let kappas: Vec<f64> = results
204 .iter()
205 .map(|r| r.coupling_coefficient_per_um)
206 .collect();
207 let ratios: Vec<f64> = results.iter().map(|r| r.coupling_ratio).collect();
208 let isos: Vec<f64> = results.iter().map(|r| r.isolation_db).collect();
209
210 dict.set_item("pair_a", idx_a)?;
211 dict.set_item("pair_b", idx_b)?;
212 dict.set_item("gap_nm", gaps)?;
213 dict.set_item("coupling_length_um", lens)?;
214 dict.set_item("coupling_coefficient_per_um", kappas)?;
215 dict.set_item("coupling_ratio", ratios)?;
216 dict.set_item("isolation_db", isos)?;
217 dict.set_item("num_pairs", n)?;
218 dict.set_item("backend", "rust")?;
219 Ok(dict.into_any().unbind())
220}
221
222pub(crate) fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
223 m.add_function(wrap_pyfunction!(py_ph_route_waveguides, m)?)?;
224 m.add_function(wrap_pyfunction!(py_ph_mzi_transfer_matrix, m)?)?;
225 m.add_function(wrap_pyfunction!(py_ph_cascade_mzi, m)?)?;
226 m.add_function(wrap_pyfunction!(py_ph_analyze_crosstalk, m)?)?;
227 m.add_function(wrap_pyfunction!(py_ph_analyze_power_budget, m)?)?;
228 m.add_function(wrap_pyfunction!(py_ph_analyze_crosstalk_bank, m)?)?;
229 m.add_function(wrap_pyfunction!(py_ph_analyze_crosstalk_pairs, m)?)?;
230 Ok(())
231}