Skip to main content

sc_neurocore_engine/ir/
printer.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Commercial license available
3// © Concepts 1996–2026 Miroslav Šotek. All rights reserved.
4// © Code 2020–2026 Miroslav Šotek. All rights reserved.
5// ORCID: 0009-0009-3560-0851
6// Contact: www.anulum.li | protoscience@anulum.li
7// SC-NeuroCore — Text-format printer for SC IR graphs
8
9//! Text-format printer for SC IR graphs.
10//!
11//! # Format
12//!
13//! ```text
14//! sc.graph @module_name {
15//!   %0 = sc.input "x_in" : rate
16//!   %1 = sc.constant 0.5 : rate
17//!   %2 = sc.encode %0, length=1024, seed=0xACE1 : bitstream<1024>
18//!   %3 = sc.encode %1, length=1024, seed=0xBEEF : bitstream<1024>
19//!   %4 = sc.and %2, %3 : bitstream<1024>
20//!   %5 = sc.popcount %4 : u64
21//!   sc.output "result" %5
22//! }
23//! ```
24
25use crate::ir::graph::*;
26
27/// Format an `f64` constant so the parser reads it back as a float, not an integer.
28///
29/// The scalar/vector constant parser decides the `ScConst` variant from a `.` in the text
30/// (or a `rate` type). A whole-number `f64` such as `5.0` formats as `"5"` via `{}`, which
31/// would re-parse to an integer variant (`U64`/`I64`), and an all-digit vector like
32/// `[5.0, 6.0]` prints `"[5, 6]"` and re-parses to `I64Vec` — either breaks the round trip.
33/// Appending `.0` when the shortest form carries no fractional/exponent marker keeps the
34/// value a float across `parse . print`. (`{}` never emits an exponent for `f64`, so a plain
35/// digit run — optionally signed — is exactly the whole-number case.)
36fn fmt_f64(v: f64) -> String {
37    let s = format!("{v}");
38    if !s.is_empty() && s.bytes().all(|b| b.is_ascii_digit() || b == b'-') {
39        format!("{s}.0")
40    } else {
41        s
42    }
43}
44
45/// Print a graph to its text representation.
46pub fn print(graph: &ScGraph) -> String {
47    let mut out = String::new();
48    out.push_str(&format!("sc.graph @{} {{\n", graph.name));
49
50    for op in &graph.ops {
51        out.push_str("  ");
52        match op {
53            ScOp::Input { id, name, ty } => {
54                out.push_str(&format!("{} = sc.input \"{}\" : {}\n", id, name, ty));
55            }
56            ScOp::Output { name, source, .. } => {
57                out.push_str(&format!("sc.output \"{}\" {}\n", name, source));
58            }
59            ScOp::Constant { id, value, ty } => {
60                let val_str = match value {
61                    ScConst::F64(v) => fmt_f64(*v),
62                    ScConst::I64(v) => format!("{v}"),
63                    ScConst::U64(v) => format!("{v}"),
64                    ScConst::F64Vec(v) => format!(
65                        "[{}]",
66                        v.iter().map(|x| fmt_f64(*x)).collect::<Vec<_>>().join(", ")
67                    ),
68                    ScConst::I64Vec(v) => format!(
69                        "[{}]",
70                        v.iter()
71                            .map(|x| format!("{x}"))
72                            .collect::<Vec<_>>()
73                            .join(", ")
74                    ),
75                };
76                out.push_str(&format!("{} = sc.constant {} : {}\n", id, val_str, ty));
77            }
78            ScOp::Encode {
79                id,
80                prob,
81                length,
82                seed,
83            } => {
84                out.push_str(&format!(
85                    "{} = sc.encode {}, length={}, seed=0x{:04X} : bitstream<{}>\n",
86                    id, prob, length, seed, length
87                ));
88            }
89            ScOp::BitwiseAnd { id, lhs, rhs } => {
90                out.push_str(&format!("{} = sc.and {}, {} : bitstream\n", id, lhs, rhs));
91            }
92            ScOp::BitwiseXor { id, lhs, rhs } => {
93                out.push_str(&format!("{} = sc.xor {}, {} : bitstream\n", id, lhs, rhs));
94            }
95            ScOp::Popcount { id, input } => {
96                out.push_str(&format!("{} = sc.popcount {} : u64\n", id, input));
97            }
98            ScOp::Reduce { id, input, mode } => {
99                out.push_str(&format!(
100                    "{} = sc.reduce {}, mode={} : rate\n",
101                    id, input, mode
102                ));
103            }
104            ScOp::LifStep {
105                id,
106                current,
107                leak,
108                gain,
109                noise,
110                params,
111            } => {
112                out.push_str(&format!(
113                    "{} = sc.lif_step {}, leak={}, gain={}, noise={}, \
114                     dw={}, frac={}, vt={}, rp={} : (bool, fixed<{},{}>)\n",
115                    id,
116                    current,
117                    leak,
118                    gain,
119                    noise,
120                    params.data_width,
121                    params.fraction,
122                    params.v_threshold,
123                    params.refractory_period,
124                    params.data_width,
125                    params.fraction
126                ));
127            }
128            ScOp::DenseForward {
129                id,
130                inputs,
131                weights,
132                leak,
133                gain,
134                params,
135            } => {
136                out.push_str(&format!(
137                    "{} = sc.dense_forward {}, weights={}, leak={}, gain={}, \
138                     ni={}, nn={}, len={} : vec<bool,{}>\n",
139                    id,
140                    inputs,
141                    weights,
142                    leak,
143                    gain,
144                    params.n_inputs,
145                    params.n_neurons,
146                    params.stream_length,
147                    params.n_neurons
148                ));
149            }
150            ScOp::DclsLayer {
151                id,
152                spike,
153                weights,
154                centre,
155                sigma,
156                params,
157            } => {
158                out.push_str(&format!(
159                    "{} = sc.dcls_layer {}, weights={}, centre={}, sigma={}, \
160                     taps={}, depth={}, dw={}, frac={} : fixed<{},{}>\n",
161                    id,
162                    spike,
163                    weights,
164                    centre,
165                    sigma,
166                    params.n_taps,
167                    params.delay_depth,
168                    params.data_width,
169                    params.fraction,
170                    params.data_width,
171                    params.fraction
172                ));
173            }
174            ScOp::GraphForward {
175                id,
176                features,
177                adjacency,
178                n_nodes,
179                n_features,
180            } => {
181                out.push_str(&format!(
182                    "{} = sc.graph_forward {}, adj={}, nodes={}, features={} : rate\n",
183                    id, features, adjacency, n_nodes, n_features
184                ));
185            }
186            ScOp::SoftmaxAttention { id, q, k, v, dim_k } => {
187                out.push_str(&format!(
188                    "{} = sc.softmax_attention {}, {}, {}, dim_k={} : rate\n",
189                    id, q, k, v, dim_k
190                ));
191            }
192            ScOp::KuramotoStep {
193                id,
194                phases,
195                omega,
196                coupling,
197                dt,
198            } => {
199                out.push_str(&format!(
200                    "{} = sc.kuramoto_step {}, omega={}, K={}, dt={} : rate\n",
201                    id, phases, omega, coupling, dt
202                ));
203            }
204            ScOp::Scale { id, input, factor } => {
205                out.push_str(&format!(
206                    "{} = sc.scale {}, factor={} : rate\n",
207                    id, input, factor
208                ));
209            }
210            ScOp::Offset { id, input, offset } => {
211                out.push_str(&format!(
212                    "{} = sc.offset {}, offset={} : rate\n",
213                    id, input, offset
214                ));
215            }
216            ScOp::DivConst { id, input, divisor } => {
217                out.push_str(&format!(
218                    "{} = sc.div_const {}, divisor={} : u64\n",
219                    id, input, divisor
220                ));
221            }
222        }
223    }
224
225    out.push_str("}\n");
226    out
227}