1use numpy::IntoPyArray;
12use pyo3::prelude::*;
13use pyo3::types::PyDict;
14use pyo3::IntoPyObject;
15
16use crate::dna;
17
18#[pyfunction]
21fn py_dna_design_sequence(_py: Python<'_>, length: usize, seed: u64) -> String {
22 String::from_utf8(dna::design_sequence(length, seed)).unwrap_or_default()
23}
24
25#[pyfunction]
26fn py_dna_design_orthogonal_set(
27 _py: Python<'_>,
28 count: usize,
29 length: usize,
30 seed: u64,
31) -> Vec<String> {
32 dna::design_orthogonal_set(count, length, seed)
33 .into_iter()
34 .map(|s| String::from_utf8(s).unwrap_or_default())
35 .collect()
36}
37
38#[pyfunction]
39fn py_dna_check_cross_hybridization<'py>(
40 py: Python<'py>,
41 sequences: Vec<String>,
42 threshold: usize,
43) -> PyResult<Py<PyAny>> {
44 let seqs: Vec<Vec<u8>> = sequences.into_iter().map(|s| s.into_bytes()).collect();
45 let flags = dna::check_cross_hybridization(&seqs, threshold);
46 let result: Vec<Py<PyAny>> = flags
47 .into_iter()
48 .map(|(i, j, score)| {
49 let d = PyDict::new(py);
50 d.set_item("strand_a", i).unwrap();
51 d.set_item("strand_b", j).unwrap();
52 d.set_item("score", score).unwrap();
53 d.into_any().unbind()
54 })
55 .collect();
56 Ok(result.into_pyobject(py)?.into())
57}
58
59#[pyfunction]
60#[pyo3(signature = (
61 gate_types, gate_inputs, gate_outputs, gate_thresholds, gate_leaks,
62 input_names, input_concs, duration_s=1800.0, dt=1.0,
63 k_hyb=3e5, k_disp=1.0, temperature_c=37.0, use_rk4=true
64))]
65#[allow(clippy::too_many_arguments)]
66fn py_dna_simulate_kinetics<'py>(
67 py: Python<'py>,
68 gate_types: Vec<String>,
69 gate_inputs: Vec<Vec<String>>,
70 gate_outputs: Vec<String>,
71 gate_thresholds: Vec<f64>,
72 gate_leaks: Vec<f64>,
73 input_names: Vec<String>,
74 input_concs: Vec<f64>,
75 duration_s: f64,
76 dt: f64,
77 k_hyb: f64,
78 k_disp: f64,
79 temperature_c: f64,
80 use_rk4: bool,
81) -> PyResult<Py<PyAny>> {
82 let gates: Vec<dna::DnaGateSpec> = gate_types
83 .iter()
84 .zip(gate_inputs.iter())
85 .zip(gate_outputs.iter())
86 .zip(gate_thresholds.iter())
87 .zip(gate_leaks.iter())
88 .map(|((((gt, gi), go), th), lk)| {
89 let gate_type = match gt.to_uppercase().as_str() {
90 "AND" => dna::DnaGateType::And,
91 "OR" => dna::DnaGateType::Or,
92 "NOT" => dna::DnaGateType::Not,
93 "THRESHOLD" => dna::DnaGateType::Threshold,
94 "MUX" => dna::DnaGateType::Mux,
95 "AMPLIFIER" => dna::DnaGateType::Amplifier,
96 "BUFFER" => dna::DnaGateType::Buffer,
97 "NAND" => dna::DnaGateType::Nand,
98 "XOR" => dna::DnaGateType::Xor,
99 _ => dna::DnaGateType::And,
100 };
101 dna::DnaGateSpec {
102 gate_type,
103 input_names: gi.clone(),
104 output_name: go.clone(),
105 threshold: *th,
106 leak_rate: *lk,
107 }
108 })
109 .collect();
110
111 let mut inputs = std::collections::HashMap::new();
112 for (name, conc) in input_names.into_iter().zip(input_concs) {
113 inputs.insert(name, conc);
114 }
115
116 let config = dna::KineticConfig {
117 k_hyb,
118 k_disp,
119 temperature_c,
120 max_conc: 200.0,
121 use_rk4,
122 };
123
124 let result = dna::simulate_kinetics(&gates, &inputs, duration_s, dt, &config);
125
126 let dict = PyDict::new(py);
127 for (key, trace) in result {
128 dict.set_item(key, trace.into_pyarray(py))?;
129 }
130 Ok(dict.into_any().unbind())
131}
132
133#[pyfunction]
134#[pyo3(signature = (sequence, min_stem=4, min_loop=3))]
135fn py_dna_detect_hairpins(
136 _py: Python<'_>,
137 sequence: &str,
138 min_stem: usize,
139 min_loop: usize,
140) -> Vec<(usize, usize, usize)> {
141 dna::detect_hairpins(sequence.as_bytes(), min_stem, min_loop)
142}
143
144pub(crate) fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
145 m.add_function(wrap_pyfunction!(py_dna_design_sequence, m)?)?;
146 m.add_function(wrap_pyfunction!(py_dna_design_orthogonal_set, m)?)?;
147 m.add_function(wrap_pyfunction!(py_dna_check_cross_hybridization, m)?)?;
148 m.add_function(wrap_pyfunction!(py_dna_simulate_kinetics, m)?)?;
149 m.add_function(wrap_pyfunction!(py_dna_detect_hairpins, m)?)?;
150 Ok(())
151}