1use rayon::prelude::*;
4
5use crate::error::{InfotheoryError, InfotheoryResult};
6use crate::spec::CompiledCompressionBackend;
7
8use crate::runtime::CompressionRuntime;
9use crate::with_default_ctx;
10
11#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
13pub enum OperationParallelism {
14 Serial,
16 #[default]
18 Auto,
19 Threads(usize),
21}
22
23#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
25pub struct NcdComputeOptions {
26 pub parallelism: OperationParallelism,
28}
29
30pub fn try_compress_size_chain_backend(
32 parts: &[&[u8]],
33 backend: &CompiledCompressionBackend,
34) -> InfotheoryResult<u64> {
35 let mut runtime = crate::runtime::build_compression_runtime(backend)
36 .map_err(InfotheoryError::invalid_backend_config)?;
37 runtime.compress_size_chain(parts)
38}
39
40pub fn try_compress_size_backend(
42 data: &[u8],
43 backend: &CompiledCompressionBackend,
44) -> InfotheoryResult<u64> {
45 let mut runtime = crate::runtime::build_compression_runtime(backend)
46 .map_err(InfotheoryError::invalid_backend_config)?;
47 runtime.compress_size(data)
48}
49
50pub fn try_compress_bytes_backend(
52 data: &[u8],
53 backend: &CompiledCompressionBackend,
54) -> InfotheoryResult<Vec<u8>> {
55 let mut runtime = crate::runtime::build_compression_runtime(backend)
56 .map_err(InfotheoryError::invalid_backend_config)?;
57 runtime.compress_bytes(data)
58}
59
60pub fn try_decompress_bytes_backend(
62 input: &[u8],
63 backend: &CompiledCompressionBackend,
64) -> InfotheoryResult<Vec<u8>> {
65 let mut runtime = crate::runtime::build_compression_runtime(backend)
66 .map_err(InfotheoryError::invalid_backend_config)?;
67 runtime.decompress_bytes(input)
68}
69
70#[derive(Clone, Copy, Debug, Eq, PartialEq)]
72pub enum NcdVariant {
73 Vitanyi,
75 SymVitanyi,
77 Cons,
79 SymCons,
81}
82
83#[inline(always)]
84fn ncd_from_sizes(cx: u64, cy: u64, cxy: u64, cyx: Option<u64>, variant: NcdVariant) -> f64 {
85 let min_c = cx.min(cy) as f64;
86 let max_c = cx.max(cy) as f64;
87
88 match variant {
89 NcdVariant::Vitanyi => {
90 if max_c == 0.0 {
91 0.0
92 } else {
93 (cxy as f64 - min_c) / max_c
94 }
95 }
96 NcdVariant::SymVitanyi => {
97 let m = cxy.min(cyx.expect("cyx required for SymVitanyi")) as f64;
98 if max_c == 0.0 {
99 0.0
100 } else {
101 (m - min_c) / max_c
102 }
103 }
104 NcdVariant::Cons => {
105 let denom = cxy as f64;
106 if denom == 0.0 {
107 0.0
108 } else {
109 (cxy as f64 - min_c) / denom
110 }
111 }
112 NcdVariant::SymCons => {
113 let m = cxy.min(cyx.expect("cyx required for SymCons")) as f64;
114 if m == 0.0 { 0.0 } else { (m - min_c) / m }
115 }
116 }
117}
118
119#[inline(always)]
120pub fn try_ncd_bytes_default(x: &[u8], y: &[u8], variant: NcdVariant) -> InfotheoryResult<f64> {
122 with_default_ctx(|ctx| ctx.try_ncd_bytes(x, y, variant))
123}
124
125pub fn try_ncd_bytes_backend(
127 x: &[u8],
128 y: &[u8],
129 backend: &CompiledCompressionBackend,
130 variant: NcdVariant,
131) -> InfotheoryResult<f64> {
132 try_ncd_bytes_backend_with_options(x, y, backend, variant, NcdComputeOptions::default())
133}
134
135pub fn try_ncd_bytes_backend_with_options(
138 x: &[u8],
139 y: &[u8],
140 backend: &CompiledCompressionBackend,
141 variant: NcdVariant,
142 options: NcdComputeOptions,
143) -> InfotheoryResult<f64> {
144 let compute = || -> InfotheoryResult<f64> {
145 let (cx, cy) = rayon::join(
146 || try_compress_size_backend(x, backend),
147 || try_compress_size_backend(y, backend),
148 );
149 let cx = cx?;
150 let cy = cy?;
151
152 let cxy = try_compress_size_chain_backend(&[x, y], backend)?;
153
154 let cyx = match variant {
155 NcdVariant::SymVitanyi | NcdVariant::SymCons => {
156 Some(try_compress_size_chain_backend(&[y, x], backend)?)
157 }
158 _ => None,
159 };
160
161 Ok(ncd_from_sizes(cx, cy, cxy, cyx, variant))
162 };
163
164 match options.parallelism {
165 OperationParallelism::Serial => {
166 let cx = try_compress_size_backend(x, backend)?;
167 let cy = try_compress_size_backend(y, backend)?;
168 let cxy = try_compress_size_chain_backend(&[x, y], backend)?;
169 let cyx = match variant {
170 NcdVariant::SymVitanyi | NcdVariant::SymCons => {
171 Some(try_compress_size_chain_backend(&[y, x], backend)?)
172 }
173 _ => None,
174 };
175 Ok(ncd_from_sizes(cx, cy, cxy, cyx, variant))
176 }
177 OperationParallelism::Auto => compute(),
178 OperationParallelism::Threads(threads) => {
179 if threads <= 1 {
180 return try_ncd_bytes_backend_with_options(
181 x,
182 y,
183 backend,
184 variant,
185 NcdComputeOptions {
186 parallelism: OperationParallelism::Serial,
187 },
188 );
189 }
190 let pool = rayon::ThreadPoolBuilder::new()
191 .num_threads(threads)
192 .build()
193 .map_err(|err| {
194 InfotheoryError::runtime(format!("failed to build rayon pool: {err}"))
195 })?;
196 pool.install(compute)
197 }
198 }
199}
200
201pub fn try_ncd_matrix_bytes_default(
205 datas: &[Vec<u8>],
206 variant: NcdVariant,
207) -> InfotheoryResult<Vec<f64>> {
208 with_default_ctx(|ctx| try_ncd_matrix_bytes_backend(datas, &ctx.compression_backend, variant))
209}
210
211pub fn try_ncd_matrix_bytes_backend(
215 datas: &[Vec<u8>],
216 backend: &CompiledCompressionBackend,
217 variant: NcdVariant,
218) -> InfotheoryResult<Vec<f64>> {
219 try_ncd_matrix_bytes_backend_with_options(datas, backend, variant, NcdComputeOptions::default())
220}
221
222pub fn try_ncd_matrix_bytes_backend_with_options(
225 datas: &[Vec<u8>],
226 backend: &CompiledCompressionBackend,
227 variant: NcdVariant,
228 options: NcdComputeOptions,
229) -> InfotheoryResult<Vec<f64>> {
230 let compute = || try_ncd_matrix_bytes_backend_impl(datas, backend, variant);
231 match options.parallelism {
232 OperationParallelism::Serial => {
233 try_ncd_matrix_bytes_backend_serial(datas, backend, variant)
234 }
235 OperationParallelism::Auto => compute(),
236 OperationParallelism::Threads(threads) => {
237 if threads <= 1 {
238 return try_ncd_matrix_bytes_backend_serial(datas, backend, variant);
239 }
240 let pool = rayon::ThreadPoolBuilder::new()
241 .num_threads(threads)
242 .build()
243 .map_err(|err| {
244 InfotheoryError::runtime(format!("failed to build rayon pool: {err}"))
245 })?;
246 pool.install(compute)
247 }
248 }
249}
250
251fn try_ncd_matrix_bytes_backend_impl(
252 datas: &[Vec<u8>],
253 backend: &CompiledCompressionBackend,
254 variant: NcdVariant,
255) -> InfotheoryResult<Vec<f64>> {
256 let n = datas.len();
257 let cx = datas
258 .par_iter()
259 .map(|d| try_compress_size_backend(d, backend))
260 .collect::<Vec<_>>()
261 .into_iter()
262 .collect::<InfotheoryResult<Vec<_>>>()?;
263
264 let mut out = vec![0.0f64; n * n];
265
266 match variant {
267 NcdVariant::SymVitanyi | NcdVariant::SymCons => {
268 let pairs = (0..n)
269 .flat_map(|i| (i + 1..n).map(move |j| (i, j)))
270 .collect::<Vec<_>>();
271 let pair_results = pairs
272 .into_par_iter()
273 .map(|(i, j)| -> InfotheoryResult<(usize, usize, f64)> {
274 let x = &datas[i];
275 let y = &datas[j];
276 let cxy = try_compress_size_chain_backend(&[x, y], backend)?;
277 let cyx = try_compress_size_chain_backend(&[y, x], backend)?;
278
279 let d = ncd_from_sizes(cx[i], cx[j], cxy, Some(cyx), variant);
280 Ok((i, j, d))
281 })
282 .collect::<Vec<_>>();
283 for entry in pair_results {
284 let (i, j, d) = entry?;
285 out[i * n + j] = d;
286 out[j * n + i] = d;
287 }
288 }
289 NcdVariant::Vitanyi | NcdVariant::Cons => {
290 let rows = (0..n)
291 .into_par_iter()
292 .map(|i| -> InfotheoryResult<Vec<(usize, usize, f64)>> {
293 let x = &datas[i];
294 let mut row = Vec::with_capacity(n);
295 for j in 0..n {
296 let d = if i == j {
297 0.0
298 } else {
299 let y = &datas[j];
300 let cxy = try_compress_size_chain_backend(&[x, y], backend)?;
301 ncd_from_sizes(cx[i], cx[j], cxy, None, variant)
302 };
303 row.push((i, j, d));
304 }
305 Ok(row)
306 })
307 .collect::<Vec<_>>();
308 for row in rows {
309 for (i, j, d) in row? {
310 out[i * n + j] = d;
311 }
312 }
313 }
314 }
315
316 Ok(out)
317}
318
319fn try_ncd_matrix_bytes_backend_serial(
320 datas: &[Vec<u8>],
321 backend: &CompiledCompressionBackend,
322 variant: NcdVariant,
323) -> InfotheoryResult<Vec<f64>> {
324 let n = datas.len();
325 let mut cx = Vec::with_capacity(n);
326 for d in datas {
327 cx.push(try_compress_size_backend(d, backend)?);
328 }
329 let mut out = vec![0.0f64; n * n];
330 for i in 0..n {
331 for j in 0..n {
332 if i == j {
333 out[i * n + j] = 0.0;
334 continue;
335 }
336 let cxy = try_compress_size_chain_backend(
337 &[datas[i].as_slice(), datas[j].as_slice()],
338 backend,
339 )?;
340 let cyx = match variant {
341 NcdVariant::SymVitanyi | NcdVariant::SymCons => {
342 Some(try_compress_size_chain_backend(
343 &[datas[j].as_slice(), datas[i].as_slice()],
344 backend,
345 )?)
346 }
347 _ => None,
348 };
349 out[i * n + j] = ncd_from_sizes(cx[i], cx[j], cxy, cyx, variant);
350 }
351 }
352 Ok(out)
353}