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}