Skip to main content

qudit_core/
function.rs

1use std::ops::Range;
2
3use bit_set::BitSet;
4use std::hash::{Hash, Hasher};
5
6use crate::RealScalar;
7
8/// A data structure representing indices of parameters (e.g. for a function).
9/// There are optimized methods for consecutive and disjoint parameters.
10#[derive(Clone, Debug)]
11pub enum ParamIndices {
12    /// The index of the first parameter (consecutive parameters)
13    Joint(usize, usize), // start, length
14
15    /// The index of each parameter (disjoint parameters)
16    Disjoint(Vec<usize>),
17}
18
19impl PartialEq for ParamIndices {
20    fn eq(&self, other: &Self) -> bool {
21        if self.len() != other.len() {
22            return false;
23        }
24
25        match (self, other) {
26            // Same variants - delegate to inner equality
27            (ParamIndices::Joint(start1, len1), ParamIndices::Joint(start2, len2)) => {
28                start1 == start2 && len1 == len2
29            }
30            (ParamIndices::Disjoint(v1), ParamIndices::Disjoint(v2)) => v1 == v2,
31            // Cross variants - compare iterators element by element
32            _ => self.iter().eq(other.iter()),
33        }
34    }
35}
36
37impl Eq for ParamIndices {}
38
39impl Hash for ParamIndices {
40    fn hash<H: Hasher>(&self, state: &mut H) {
41        self.len().hash(state);
42        for index in self.iter() {
43            index.hash(state);
44        }
45    }
46}
47
48impl ParamIndices {
49    /// Converts the `ParamIndices` to a `BitSet`.
50    ///
51    /// This function creates a `BitSet` where each bit corresponds to a parameter index.
52    /// If a parameter index is present in the `ParamIndices`, the corresponding bit in the
53    /// `BitSet` is set to 1.
54    ///
55    /// # Returns
56    ///
57    /// A `BitSet` representing the parameter indices.
58    pub fn to_bitset(&self) -> BitSet {
59        let mut bitset = BitSet::new();
60        match self {
61            ParamIndices::Joint(start, length) => {
62                for i in 0..*length {
63                    bitset.insert(*start + i);
64                }
65            }
66            ParamIndices::Disjoint(indices) => {
67                for index in indices {
68                    bitset.insert(*index);
69                }
70            }
71        }
72        bitset
73    }
74
75    /// Creates a `ParamIndices` representing a constant value (no parameters).
76    ///
77    /// # Returns
78    ///
79    /// A `ParamIndices` representing an empty set of parameter indices.
80    pub fn empty() -> ParamIndices {
81        ParamIndices::Joint(0, 0)
82    }
83
84    /// Checks if the `ParamIndices` is empty (contains no parameters).
85    ///
86    /// # Returns
87    ///
88    /// `true` if the `ParamIndices` is empty, `false` otherwise.
89    pub fn is_empty(&self) -> bool {
90        match self {
91            ParamIndices::Joint(_, length) => *length == 0,
92            ParamIndices::Disjoint(v) => v.is_empty(),
93        }
94    }
95
96    /// Checks if the `ParamIndices` represents a consecutive range of parameters.
97    ///
98    /// # Returns
99    ///
100    /// `true` if the `ParamIndices` is consecutive, `false` otherwise.
101    pub fn is_consecutive(&self) -> bool {
102        match self {
103            ParamIndices::Joint(_, _) => true,
104            ParamIndices::Disjoint(v) => {
105                if v.len() <= 1 {
106                    return true;
107                }
108                let mut clone_v = v.clone();
109                clone_v.sort();
110                for i in 1..clone_v.len() {
111                    if clone_v[i] != clone_v[i - 1] + 1 {
112                        return false;
113                    }
114                }
115                true
116            }
117        }
118    }
119
120    /// Returns the number of parameters represented by the `ParamIndices`.
121    ///
122    /// # Returns
123    ///
124    /// The number of parameters.
125    ///
126    pub fn num_params(&self) -> usize {
127        match self {
128            ParamIndices::Joint(_, length) => *length,
129            ParamIndices::Disjoint(v) => v.len(),
130        }
131    }
132
133    /// Returns the starting index of the parameters.
134    ///
135    /// For `ParamIndices::Joint`, this is the index of the first parameter in the consecutive range.
136    /// For `ParamIndices::Disjoint`, this is the index of the first parameter in the vector, or 0 if the vector is empty.
137    ///
138    /// # Returns
139    ///
140    /// The starting index of the parameters.
141    pub fn start(&self) -> usize {
142        match self {
143            ParamIndices::Joint(start, _) => *start,
144            ParamIndices::Disjoint(v) => *v.first().unwrap_or(&0),
145        }
146    }
147
148    /// Concatenates two `ParamIndices` into a single `ParamIndices`.
149    ///
150    /// This function combines the parameter indices from two `ParamIndices` instances.
151    /// If the two `ParamIndices` represent overlapping ranges, they are merged into a single `ParamIndices::Joint` if possible.
152    /// Otherwise, the parameter indices are combined into a `ParamIndices::Disjoint`.
153    ///
154    /// # Arguments
155    ///
156    /// * `other` - The other `ParamIndices` to concatenate with.
157    ///
158    /// # Returns
159    ///
160    /// A new `ParamIndices` representing the concatenation of the two input `ParamIndices`.
161    pub fn union(&self, other: &ParamIndices) -> ParamIndices {
162        match (self, other) {
163            (ParamIndices::Joint(start1, length1), ParamIndices::Joint(start2, length2)) => {
164                if *start2 > *start1 && *start2 < *start1 + *length1 {
165                    ParamIndices::Joint(*start1, *start2 + *length2 - *start1)
166                } else if *start1 > *start2 && *start1 < *start2 + *length2 {
167                    ParamIndices::Joint(*start2, *start1 + *length1 - *start2)
168                } else {
169                    let mut indices = Vec::new();
170                    for i in *start1..*start1 + *length1 {
171                        indices.push(i);
172                    }
173                    for i in *start2..*start2 + *length2 {
174                        indices.push(i);
175                    }
176                    ParamIndices::Disjoint(indices)
177                }
178            }
179            (ParamIndices::Joint(start, length), ParamIndices::Disjoint(v)) => {
180                let mut indices = Vec::new();
181                for i in *start..*start + *length {
182                    if v.contains(&i) {
183                        continue;
184                    }
185                    indices.push(i);
186                }
187                indices.extend(v.iter());
188                ParamIndices::Disjoint(indices)
189            }
190            (ParamIndices::Disjoint(v), ParamIndices::Joint(start, length)) => {
191                let mut indices = Vec::new();
192                for i in *start..*start + *length {
193                    if v.contains(&i) {
194                        continue;
195                    }
196                    indices.push(i);
197                }
198                indices.extend(v.iter());
199                ParamIndices::Disjoint(indices)
200            }
201            (ParamIndices::Disjoint(v1), ParamIndices::Disjoint(v2)) => {
202                let mut indices = Vec::new();
203                for i in v1 {
204                    if v2.contains(i) {
205                        continue;
206                    }
207                    indices.push(*i);
208                }
209                indices.extend(v2.iter());
210                ParamIndices::Disjoint(indices)
211            }
212        }
213    }
214
215    /// Computes the intersection of two `ParamIndices`.
216    ///
217    /// This function returns a new `ParamIndices` containing only the parameter indices that are present in both input `ParamIndices`.
218    /// The result is always a `ParamIndices::Disjoint`.
219    ///
220    /// # Arguments
221    ///
222    /// * `other` - The other `ParamIndices` to intersect with.
223    ///
224    /// # Returns
225    ///
226    /// A new `ParamIndices` representing the intersection of the two input `ParamIndices`.
227    pub fn intersect(&self, other: &ParamIndices) -> ParamIndices {
228        match (self, other) {
229            (ParamIndices::Joint(start1, length1), ParamIndices::Joint(start2, length2)) => {
230                let mut indices = Vec::new();
231                for i in *start1..*start1 + *length1 {
232                    if i >= *start2 && i < *start2 + *length2 {
233                        indices.push(i);
234                    }
235                }
236                ParamIndices::Disjoint(indices)
237            }
238            (ParamIndices::Joint(start, length), ParamIndices::Disjoint(v)) => {
239                let mut indices = Vec::new();
240                for i in *start..*start + *length {
241                    if v.contains(&i) {
242                        indices.push(i);
243                    }
244                }
245                ParamIndices::Disjoint(indices)
246            }
247            (ParamIndices::Disjoint(v), ParamIndices::Joint(start, length)) => {
248                let mut indices = Vec::new();
249                for i in *start..*start + *length {
250                    if v.contains(&i) {
251                        indices.push(i);
252                    }
253                }
254                ParamIndices::Disjoint(indices)
255            }
256            (ParamIndices::Disjoint(v1), ParamIndices::Disjoint(v2)) => {
257                let mut indices = Vec::new();
258                for i in v1 {
259                    if v2.contains(i) {
260                        indices.push(*i);
261                    }
262                }
263                ParamIndices::Disjoint(indices)
264            }
265        }
266    }
267
268    /// Sorts the indices.
269    ///
270    /// If the `ParamIndices` is `Joint`, this method does nothing.
271    ///
272    /// # Returns
273    ///
274    /// A mutable reference to `self` for chaining.
275    pub fn sort(&mut self) -> &mut Self {
276        match self {
277            ParamIndices::Joint(_, _) => {}
278            ParamIndices::Disjoint(indices) => indices.sort(),
279        }
280        self
281    }
282
283    /// Returns a new `ParamIndices` with the indices sorted.
284    pub fn sorted(&self) -> Self {
285        match self {
286            ParamIndices::Joint(s, l) => ParamIndices::Joint(*s, *l),
287            ParamIndices::Disjoint(indices) => {
288                let mut indices_out = indices.clone();
289                indices_out.sort();
290                ParamIndices::Disjoint(indices_out)
291            }
292        }
293    }
294
295    /// Creates an iterator over the parameter indices.
296    ///
297    /// # Returns
298    ///
299    /// A `ParamIndicesIter` that yields the parameter indices.
300    pub fn iter<'a>(&'a self) -> ParamIndicesIter<'a> {
301        match self {
302            ParamIndices::Joint(start, length) => ParamIndicesIter::Joint {
303                start: *start,
304                length: *length,
305                current: 0,
306            },
307            ParamIndices::Disjoint(indices) => ParamIndicesIter::Disjoint {
308                indices,
309                current: 0,
310            },
311        }
312    }
313
314    /// Checks if the `ParamIndices` contains the given index.
315    ///
316    /// # Arguments
317    ///
318    /// * `index` - The index to check.
319    ///
320    /// # Returns
321    ///
322    /// `true` if the `ParamIndices` contains the index, `false` otherwise.
323    pub fn contains(&self, index: usize) -> bool {
324        match self {
325            ParamIndices::Joint(start, length) => index >= *start && index < *start + *length,
326            ParamIndices::Disjoint(indices) => indices.contains(&index),
327        }
328    }
329
330    /// Convert the indices to a vector
331    pub fn to_vec(self) -> Vec<usize> {
332        match self {
333            ParamIndices::Joint(_, _) => self.iter().collect(),
334            ParamIndices::Disjoint(vec) => vec,
335        }
336    }
337
338    /// Convert the indices to a vector without consuming the indices object.
339    pub fn as_vec(&self) -> Vec<usize> {
340        self.iter().collect()
341    }
342
343    /// Returns the number of indices tracked by this object; alias for num_params()
344    pub fn len(&self) -> usize {
345        match self {
346            ParamIndices::Joint(_, length) => *length,
347            ParamIndices::Disjoint(indices) => indices.len(),
348        }
349    }
350
351    /// Returns the ParameterId at the given relative index.
352    ///
353    /// This is O(1) for both Joint and Disjoint variants.
354    #[inline]
355    pub fn get(&self, index: usize) -> usize {
356        match self {
357            ParamIndices::Joint(start, length) => {
358                if index >= *length {
359                    panic!(
360                        "Index {} out of bounds for Joint range of length {}",
361                        index, length
362                    );
363                }
364                start + index
365            }
366            ParamIndices::Disjoint(v) => v[index],
367        }
368    }
369
370    /// Maps internal relative parameter indices through a parent's `ParamIndices`.
371    pub fn map_through(&self, parent: &ParamIndices) -> ParamIndices {
372        match (self, parent) {
373            (ParamIndices::Joint(sub_start, sub_len), ParamIndices::Joint(p_start, p_len)) => {
374                if sub_start + sub_len > *p_len {
375                    panic!("Sub-parameter index out of parent bounds");
376                }
377                ParamIndices::Joint(p_start + sub_start, *sub_len)
378            }
379
380            _ => {
381                let mapped: Vec<usize> =
382                    self.iter().map(|local_idx| parent.get(local_idx)).collect();
383                ParamIndices::Disjoint(mapped)
384            }
385        }
386    }
387}
388
389/// An iterator over the parameter indices in a `ParamIndices`.
390pub enum ParamIndicesIter<'a> {
391    /// Iterator for `ParamIndices::Joint`.
392    Joint {
393        /// The starting index of the consecutive range.
394        start: usize,
395        /// The length of the consecutive range.
396        length: usize,
397        /// The current index in the range.
398        current: usize,
399    },
400    /// Iterator for `ParamIndices::Disjoint`.
401    Disjoint {
402        /// A slice of the indices.
403        indices: &'a [usize],
404        /// The current index in the slice.
405        current: usize,
406    },
407}
408
409impl<'a> Iterator for ParamIndicesIter<'a> {
410    type Item = usize;
411
412    fn next(&mut self) -> Option<Self::Item> {
413        match self {
414            ParamIndicesIter::Joint {
415                start,
416                length,
417                current,
418            } => {
419                if *current < *length {
420                    let result = *start + *current;
421                    *current += 1;
422                    Some(result)
423                } else {
424                    None
425                }
426            }
427            ParamIndicesIter::Disjoint { indices, current } => {
428                if *current < indices.len() {
429                    let result = indices[*current];
430                    *current += 1;
431                    Some(result)
432                } else {
433                    None
434                }
435            }
436        }
437    }
438}
439
440impl<T: AsRef<[usize]>> From<T> for ParamIndices {
441    fn from(indices: T) -> Self {
442        let indices = indices.as_ref();
443
444        if indices.is_empty() {
445            return ParamIndices::Joint(0, 0);
446        }
447
448        let first_idx = indices[0];
449        let mut is_consecutive = true;
450        for i in 1..indices.len() {
451            if indices[i] != first_idx + i {
452                is_consecutive = false;
453                break;
454            }
455        }
456
457        if is_consecutive {
458            ParamIndices::Joint(first_idx, indices.len())
459        } else {
460            ParamIndices::Disjoint(indices.to_vec())
461        }
462    }
463}
464
465impl<'a> IntoIterator for &'a ParamIndices {
466    type Item = usize;
467    type IntoIter = ParamIndicesIter<'a>;
468
469    fn into_iter(self) -> Self::IntoIter {
470        self.iter()
471    }
472}
473
474/// Information about function parameters, including their indices and whether they are constant.
475///
476/// # Fields
477///
478/// * `indices` - The parameter indices, stored as either consecutive or disjoint ranges
479/// * `constant` - A vector indicating whether each parameter is constant (true) or variable (false)
480#[derive(Clone, Debug, PartialEq, Eq, Hash)]
481pub struct ParamInfo {
482    indices: ParamIndices,
483    constant: Vec<bool>, // TODO: change to bitmap
484}
485
486impl ParamInfo {
487    /// Creates a new `ParamInfo` with the given parameter indices and constant flags.
488    ///
489    /// # Arguments
490    ///
491    /// * `indices` - The parameter indices (can be any type that converts to `ParamIndices`)
492    /// * `constant` - A vector indicating whether each parameter is constant
493    ///
494    /// # Returns
495    ///
496    /// A new `ParamInfo` instance.
497    ///
498    /// # Panics
499    ///
500    /// The length of the `constant` vector should match the number of parameters in `indices`.
501    pub fn new(indices: impl Into<ParamIndices>, constant: Vec<bool>) -> ParamInfo {
502        ParamInfo {
503            indices: indices.into(),
504            constant,
505        }
506    }
507
508    /// Creates a new `ParamInfo` where all parameters are variable (not constant).
509    ///
510    /// This is a convenience method for creating parameter information where all
511    /// parameters can vary during optimization or analysis.
512    ///
513    /// # Arguments
514    ///
515    /// * `indices` - The parameter indices (can be any type that converts to `ParamIndices`)
516    ///
517    /// # Returns
518    ///
519    /// A new `ParamInfo` instance with all parameters marked as variable.
520    pub fn parameterized(indices: impl Into<ParamIndices>) -> ParamInfo {
521        let indices = indices.into();
522        let num_params = indices.num_params();
523        ParamInfo {
524            indices,
525            constant: vec![false; num_params],
526        }
527    }
528
529    /// Returns a new `ParamInfo` with its parameter indices sorted.
530    ///
531    /// If the `ParamIndices` are `Disjoint`, the internal vector is sorted.
532    /// If the `ParamIndices` are `Joint`, they remain `Joint` as their order is inherently sorted.
533    /// The `constant` vector is reordered to match the new order of indices.
534    pub fn to_sorted(&self) -> ParamInfo {
535        // Create a map from each parameter index to its 'constant' status.
536        let index_to_constant_map: std::collections::HashMap<usize, bool> = self
537            .indices
538            .iter()
539            .zip(self.constant.iter().cloned())
540            .collect();
541
542        // Get the sorted parameter indices. This will preserve the `Joint` variant if applicable.
543        let new_indices = self.indices.sorted();
544
545        // Build the new `constant` vector by iterating through the `new_indices`
546        // and looking up their constant status in the map.
547        let new_constant: Vec<bool> = new_indices
548            .iter()
549            .map(|index| *index_to_constant_map.get(&index).unwrap_or(&false))
550            .collect();
551
552        ParamInfo {
553            indices: new_indices,
554            constant: new_constant,
555        }
556    }
557
558    /// Unions this `ParamInfo` with another, combining their parameter indices and constant flags.
559    ///
560    /// This operation combines parameter indices from both `ParamInfo` instances. If a parameter
561    /// index appears in both instances, their constant flags must match or an assertion will fail.
562    ///
563    /// # Arguments
564    ///
565    /// * `other` - The other `ParamInfo` to concatenate with
566    ///
567    /// # Returns
568    ///
569    /// A new `ParamInfo` containing the combined parameters and their constant status.
570    pub fn union(&self, other: &ParamInfo) -> ParamInfo {
571        let combined_indices = self.indices.union(&other.indices);
572
573        let self_index_to_constant: std::collections::HashMap<usize, bool> = self
574            .indices
575            .iter()
576            .zip(self.constant.iter().cloned())
577            .collect();
578
579        let other_index_to_constant: std::collections::HashMap<usize, bool> = other
580            .indices
581            .iter()
582            .zip(other.constant.iter().cloned())
583            .collect();
584
585        let mut new_constant = Vec::new();
586        for index in combined_indices.iter() {
587            if let Some(&c) = self_index_to_constant.get(&index) {
588                if let Some(&c_other) = other_index_to_constant.get(&index) {
589                    assert_eq!(c, c_other);
590                }
591                new_constant.push(c);
592            } else if let Some(&c) = other_index_to_constant.get(&index) {
593                new_constant.push(c);
594            } else {
595                unreachable!();
596            };
597        }
598
599        ParamInfo {
600            indices: combined_indices,
601            constant: new_constant,
602        }
603    }
604
605    /// Computes the intersection of this `ParamInfo` with another.
606    ///
607    /// Returns a new `ParamInfo` containing only the parameters that are present in both
608    /// instances. The constant flags must match for common parameters.
609    ///
610    /// # Arguments
611    ///
612    /// * `other` - The other `ParamInfo` to intersect with
613    ///
614    /// # Returns
615    ///
616    /// A new `ParamInfo` containing only the common parameters.
617    pub fn intersect(&self, other: &ParamInfo) -> ParamInfo {
618        let combined_indices = self.indices.intersect(&other.indices);
619
620        let self_index_to_constant: std::collections::HashMap<usize, bool> = self
621            .indices
622            .iter()
623            .zip(self.constant.iter().cloned())
624            .collect();
625
626        let other_index_to_constant: std::collections::HashMap<usize, bool> = other
627            .indices
628            .iter()
629            .zip(other.constant.iter().cloned())
630            .collect();
631
632        let mut new_constant = Vec::new();
633        for index in combined_indices.iter() {
634            let c_self = self_index_to_constant.get(&index);
635            let c_other = other_index_to_constant.get(&index);
636
637            // An index in combined_indices must exist in both self and other ParamInfo
638            match (c_self, c_other) {
639                (Some(&s_const), Some(&o_const)) => {
640                    // Their constant status must be the same for the common index
641                    assert_eq!(
642                        s_const, o_const,
643                        "Constant status mismatch for common index {}",
644                        index
645                    );
646                    new_constant.push(s_const);
647                }
648                _ => unreachable!(
649                    "Intersected index {} not found in both original ParamInfos",
650                    index
651                ),
652            }
653        }
654
655        ParamInfo {
656            indices: combined_indices,
657            constant: new_constant,
658        }
659    }
660
661    /// Returns the total number of parameters.
662    ///
663    /// # Returns
664    ///
665    /// The number of parameters tracked by this `ParamInfo`.
666    pub fn num_params(&self) -> usize {
667        self.indices.num_params()
668    }
669
670    /// Returns the total number of variable parameters.
671    ///
672    /// # Returns
673    ///
674    /// The number of non-constant parameters tracked by this `ParamInfo`.
675    pub fn num_var_params(&self) -> usize {
676        self.indices.num_params() - self.constant.iter().filter(|&x| *x).count()
677    }
678
679    /// Returns the parameter indices as a vector of u64 values.
680    ///
681    /// This is useful for interfacing with external libraries that expect
682    /// parameter indices as 64-bit unsigned integers.
683    ///
684    /// # Returns
685    ///
686    /// A vector containing all parameter indices converted to u64.
687    pub fn get_param_map(&self) -> Vec<u64> {
688        self.indices.iter().map(|x| x as u64).collect()
689    }
690
691    /// Returns a copy of the constant flags for all parameters.
692    ///
693    /// The returned vector has the same length as the number of parameters,
694    /// where each boolean indicates whether the corresponding parameter is constant.
695    ///
696    /// # Returns
697    ///
698    /// A vector of boolean values indicating parameter constancy.
699    pub fn get_const_map(&self) -> Vec<bool> {
700        self.constant.clone()
701    }
702
703    /// Creates an empty `ParamInfo` with no parameters.
704    ///
705    /// This is useful as a default or starting point for parameter information.
706    ///
707    /// # Returns
708    ///
709    /// An empty `ParamInfo` instance.
710    pub fn empty() -> ParamInfo {
711        ParamInfo {
712            indices: vec![].into(),
713            constant: vec![],
714        }
715    }
716
717    /// Checks if this `ParamInfo` contains no parameters.
718    ///
719    /// # Returns
720    ///
721    /// `true` if there are no parameters, `false` otherwise.
722    pub fn is_empty(&self) -> bool {
723        self.len() == 0
724    }
725
726    /// Returns the number of parameters (alias for `num_params`).
727    ///
728    /// # Returns
729    ///
730    /// The total number of parameters.
731    pub fn len(&self) -> usize {
732        self.constant.len()
733    }
734
735    /// Returns a sorted vector of indices for parameters that are not constant.
736    ///
737    /// This is useful for optimization routines that only need to vary non-constant
738    /// parameters.
739    ///
740    /// # Returns
741    ///
742    /// A sorted vector containing the indices of all non-constant parameters.
743    pub fn sorted_non_constant(&self) -> Vec<usize> {
744        let mut non_constant_indices = Vec::new();
745        for (index, &is_constant) in self.indices.iter().zip(self.constant.iter()) {
746            if !is_constant {
747                non_constant_indices.push(index);
748            }
749        }
750        non_constant_indices.sort();
751        non_constant_indices
752    }
753}
754
755/// A parameterized object.
756pub trait HasParams {
757    /// The number of parameters this object requires.
758    fn num_params(&self) -> usize;
759}
760
761/// A bounded, parameterized object.
762pub trait HasBounds<R: RealScalar>: HasParams {
763    /// The bounds for each variable of the function
764    fn bounds(&self) -> Vec<Range<R>>;
765}
766
767/// A periodic, parameterized object
768pub trait HasPeriods<R: RealScalar>: HasParams {
769    /// The core period for each variable of the function
770    fn periods(&self) -> Vec<Range<R>>;
771}
772
773// #[cfg(test)]
774// pub mod strategies {
775//     use std::ops::Range;
776
777//     use proptest::prelude::*;
778
779//     use super::BoundedFn;
780
781//     pub fn params(num_params: usize) -> impl Strategy<Value = Vec<f64>> {
782//         prop::collection::vec(
783//             prop::num::f64::POSITIVE
784//                 | prop::num::f64::NEGATIVE
785//                 | prop::num::f64::NORMAL
786//                 | prop::num::f64::SUBNORMAL
787//                 | prop::num::f64::ZERO,
788//             num_params,
789//         )
790//     }
791
792//     pub fn params_with_bounds(
793//         bounds: Vec<Range<f64>>,
794//     ) -> impl Strategy<Value = Vec<f64>> {
795//         bounds
796//     }
797
798//     pub fn arbitrary_with_params_strategy<F: Clone + BoundedFn + Arbitrary>(
799//     ) -> impl Strategy<Value = (F, Vec<f64>)> {
800//         any::<F>().prop_flat_map(|f| (Just(f.clone()), f.get_bounds()))
801//     }
802// }
803
804#[cfg(feature = "python")]
805mod python {
806    use super::*;
807    use pyo3::prelude::*;
808    use pyo3::types::PyIterator;
809    use pyo3_stub_gen::derive::*;
810    use pyo3_stub_gen::impl_stub_type;
811
812    impl_stub_type!(ParamIndices = PyParamIndices);
813
814    /// Python wrapper for parameter indices.
815    ///
816    /// This provides parameter index functionality to Python, supporting both
817    /// consecutive (Joint) and disjoint parameter representations.
818    #[gen_stub_pyclass]
819    #[pyclass(
820        name = "ParamIndices",
821        module = "openqudit",
822        frozen,
823        eq,
824        hash,
825        from_py_object
826    )]
827    #[derive(Clone, Debug, PartialEq, Eq, Hash)]
828    pub struct PyParamIndices {
829        inner: ParamIndices,
830    }
831
832    #[gen_stub_pymethods]
833    #[pymethods]
834    impl PyParamIndices {
835        /// Creates new parameter indices.
836        ///
837        /// # Arguments
838        ///
839        /// * `indices_or_start` - Either an iterable of parameter indices, or the starting index for a consecutive range
840        /// * `length` - Optional length for consecutive range (only used if `indices_or_start` is an integer)
841        ///
842        /// # Examples
843        ///
844        /// ```python
845        /// # Disjoint parameters
846        /// params1 = ParamIndices([0, 2, 5, 7])
847        ///
848        /// # Consecutive parameters starting at index 10 with length 5
849        /// params2 = ParamIndices(10, 5)  # represents [10, 11, 12, 13, 14]
850        /// ```
851        #[new]
852        #[pyo3(signature = (indices_or_start, length = None))]
853        fn new<'py>(indices_or_start: &Bound<'py, PyAny>, length: Option<usize>) -> PyResult<Self> {
854            if let Some(len) = length {
855                // Joint case: start + length
856                let start: usize = indices_or_start.extract()?;
857                Ok(PyParamIndices {
858                    inner: ParamIndices::Joint(start, len),
859                })
860            } else {
861                // Try to extract as an integer first (single parameter)
862                if let Ok(single_index) = indices_or_start.extract::<usize>() {
863                    Ok(PyParamIndices {
864                        inner: ParamIndices::Joint(single_index, 1),
865                    })
866                } else {
867                    // Try to extract as an iterable of indices
868                    let iter = PyIterator::from_object(indices_or_start)?;
869                    let mut indices = Vec::new();
870                    for item in iter {
871                        let index: usize = item?.extract()?;
872                        indices.push(index);
873                    }
874                    Ok(PyParamIndices {
875                        inner: ParamIndices::Disjoint(indices),
876                    })
877                }
878            }
879        }
880
881        /// Returns the number of parameters.
882        #[getter]
883        fn num_params(&self) -> usize {
884            self.inner.num_params()
885        }
886
887        /// Returns the starting index (for Joint) or first index (for Disjoint).
888        #[getter]
889        fn start(&self) -> usize {
890            self.inner.start()
891        }
892
893        /// Returns whether the parameters are consecutive.
894        #[getter]
895        fn is_consecutive(&self) -> bool {
896            self.inner.is_consecutive()
897        }
898
899        /// Returns whether the parameter indices are empty.
900        #[getter]
901        fn is_empty(&self) -> bool {
902            self.inner.is_empty()
903        }
904
905        /// Checks if the parameter indices contain the given index.
906        fn contains(&self, index: usize) -> bool {
907            self.inner.contains(index)
908        }
909
910        /// Returns all parameter indices as a list.
911        fn to_list(&self) -> Vec<usize> {
912            self.inner.as_vec()
913        }
914
915        /// Returns a sorted copy of these parameter indices.
916        fn sorted(&self) -> PyParamIndices {
917            PyParamIndices {
918                inner: self.inner.sorted(),
919            }
920        }
921
922        /// Unions with another ParamIndices.
923        fn union(&self, other: &PyParamIndices) -> PyParamIndices {
924            PyParamIndices {
925                inner: self.inner.union(&other.inner),
926            }
927        }
928
929        /// Intersects with another ParamIndices.
930        fn intersect(&self, other: &PyParamIndices) -> PyParamIndices {
931            PyParamIndices {
932                inner: self.inner.intersect(&other.inner),
933            }
934        }
935
936        /// Creates parameter indices for a constant (no parameters).
937        #[staticmethod]
938        fn constant() -> PyParamIndices {
939            PyParamIndices {
940                inner: ParamIndices::empty(),
941            }
942        }
943
944        fn __repr__(&self) -> String {
945            match &self.inner {
946                ParamIndices::Joint(start, length) => {
947                    if *length == 1 {
948                        format!("ParamIndices({})", start)
949                    } else {
950                        format!("ParamIndices({}, {})", start, length)
951                    }
952                }
953                ParamIndices::Disjoint(indices) => {
954                    format!("ParamIndices({:?})", indices)
955                }
956            }
957        }
958
959        fn __str__(&self) -> String {
960            let indices = self.inner.as_vec();
961            if indices.len() <= 3 {
962                format!("{:?}", indices)
963            } else {
964                format!(
965                    "[{}, ..., {}]",
966                    indices.first().unwrap(),
967                    indices.last().unwrap()
968                )
969            }
970        }
971
972        fn __len__(&self) -> usize {
973            self.inner.len()
974        }
975
976        fn __iter__(slf: PyRef<'_, Self>) -> PyParamIndicesIterator {
977            PyParamIndicesIterator {
978                indices: slf.inner.as_vec(),
979                index: 0,
980            }
981        }
982
983        fn __contains__(&self, index: usize) -> bool {
984            self.inner.contains(index)
985        }
986    }
987
988    #[gen_stub_pyclass]
989    #[pyclass(name = "ParamIndicesIterator", module = "openqudit")]
990    struct PyParamIndicesIterator {
991        indices: Vec<usize>,
992        index: usize,
993    }
994
995    #[gen_stub_pymethods]
996    #[pymethods]
997    impl PyParamIndicesIterator {
998        fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
999            slf
1000        }
1001
1002        fn __next__(mut slf: PyRefMut<'_, Self>) -> Option<usize> {
1003            if slf.index < slf.indices.len() {
1004                let result = slf.indices[slf.index];
1005                slf.index += 1;
1006                Some(result)
1007            } else {
1008                None
1009            }
1010        }
1011    }
1012
1013    impl From<ParamIndices> for PyParamIndices {
1014        fn from(indices: ParamIndices) -> Self {
1015            PyParamIndices { inner: indices }
1016        }
1017    }
1018
1019    impl From<PyParamIndices> for ParamIndices {
1020        fn from(py_indices: PyParamIndices) -> Self {
1021            py_indices.inner
1022        }
1023    }
1024
1025    impl<'py> IntoPyObject<'py> for ParamIndices {
1026        type Target = PyParamIndices;
1027        type Output = Bound<'py, Self::Target>;
1028        type Error = PyErr;
1029
1030        fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
1031            let py_indices = PyParamIndices::from(self);
1032            Bound::new(py, py_indices)
1033        }
1034    }
1035
1036    impl<'a, 'py> FromPyObject<'a, 'py> for ParamIndices {
1037        type Error = PyErr;
1038
1039        fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1040            let py_indices: PyParamIndices = ob.extract()?;
1041            Ok(py_indices.into())
1042        }
1043    }
1044
1045    fn register(parent_module: &Bound<'_, PyModule>) -> PyResult<()> {
1046        parent_module.add_class::<PyParamIndices>()?;
1047        parent_module.add_class::<PyParamIndicesIterator>()?;
1048        Ok(())
1049    }
1050    inventory::submit! { crate::PyRegistrar { func: register } }
1051}