@@ -18,7 +18,7 @@ use crate::{
1818 units:: { Cycles , Radians } ,
1919} ;
2020
21- use super :: sampling:: IqSamples ;
21+ use super :: sampling:: { IqSamples , SamplingError } ;
2222
2323////////////////////////////////////////////////////////////////////////////////
2424// General built-in waveform types
@@ -60,16 +60,18 @@ pub struct CommonBuiltinParameters {
6060 pub detuning : Option < f64 > ,
6161}
6262
63- /// Like [`CommonBuiltinParameters`], but with the defaults resolved.
63+ /// Like [`CommonBuiltinParameters`], but with the defaults resolved and the duration discretized .
6464#[ derive( Clone , Copy , PartialEq , Debug , Serialize , Deserialize ) ]
6565#[ cfg_attr( feature = "stubs" , gen_stub_pyclass) ]
6666#[ cfg_attr(
6767 feature = "python" ,
6868 pyo3:: pyclass( module = "quil.waveforms" , subclass, get_all, set_all, eq)
6969) ]
7070pub struct ExplicitCommonBuiltinParameters {
71- /// Full duration of the pulse (s)
72- pub duration : f64 ,
71+ /// Integral number of samples that should be taken of the pulse
72+ ///
73+ /// Corresponds to [`CommonBuiltinParameters::duration`].
74+ pub sample_count : f64 ,
7375 /// Scale to apply to waveform envelope
7476 pub scale : f64 ,
7577 /// Phase shift for the entire waveform
@@ -78,20 +80,54 @@ pub struct ExplicitCommonBuiltinParameters {
7880 pub detuning : f64 ,
7981}
8082
81- impl From < CommonBuiltinParameters > for ExplicitCommonBuiltinParameters {
82- fn from ( parameters : CommonBuiltinParameters ) -> Self {
83+ impl CommonBuiltinParameters {
84+ /// Given a sample rate, return the corresponding [explicit
85+ /// parameters][ExplicitCommonBuiltinParameters].
86+ ///
87+ /// For the three optional fields ([`scale`][Self::scale], [`phase`][Self::phase], and
88+ /// [`detuning`][Self::detuning]), the explicit version is either their original value (if
89+ /// present) or their default value (if missing).
90+ ///
91+ /// For the [`duration`][Self::duration], the explicit version is [the integer number of
92+ /// samples][ExplicitCommonBuiltinParameters::sample_count] required to fill out the
93+ /// [`Self::duration`]. If the requested duration is misaligned with respect to the sample
94+ /// rate, returns an error.
95+ ///
96+ /// *Misalignment* means that to fill the duration would require a nonintegral number of
97+ /// samples. This function will ignore any error less than 1% – that is, if the function would
98+ /// an integer number of samples plus one fractional sample of size between 99% and 101%, then
99+ /// that last sample is considered another integral sample.
100+ #[ inline]
101+ pub fn resolve_with_sample_rate (
102+ self ,
103+ sample_rate : f64 ,
104+ ) -> Result < ExplicitCommonBuiltinParameters , SamplingError > {
83105 let CommonBuiltinParameters {
84106 duration,
85107 scale,
86108 phase,
87109 detuning,
88- } = parameters;
89- Self {
90- duration,
110+ } = self ;
111+
112+ let sample_count_fract = duration * sample_rate;
113+ let sample_count = sample_count_fract. round ( ) ;
114+ let misalignment = sample_count_fract - sample_count;
115+ let max_misalignment = 1.0 / ( sample_rate * 100.0 ) ;
116+ if misalignment. abs ( ) >= max_misalignment {
117+ return Err ( SamplingError :: MisalignedDuration {
118+ duration,
119+ sample_rate,
120+ misalignment,
121+ max_misalignment,
122+ } ) ;
123+ }
124+
125+ Ok ( ExplicitCommonBuiltinParameters {
126+ sample_count,
91127 scale : scale. unwrap_or ( 1.0 ) ,
92128 phase : phase. unwrap_or ( Cycles ( 0.0 ) ) ,
93129 detuning : detuning. unwrap_or ( 0.0 ) ,
94- }
130+ } )
95131 }
96132}
97133
@@ -107,7 +143,7 @@ pub trait BuiltinWaveformParameters:
107143 self ,
108144 common : CommonBuiltinParameters ,
109145 sample_rate : f64 ,
110- ) -> IqSamples ;
146+ ) -> Result < IqSamples , SamplingError > ;
111147}
112148
113149mod private {
@@ -266,7 +302,7 @@ impl BuiltinWaveformParameters for BuiltinWaveform {
266302 self ,
267303 common : CommonBuiltinParameters ,
268304 sample_rate : f64 ,
269- ) -> IqSamples {
305+ ) -> Result < IqSamples , SamplingError > {
270306 match self {
271307 BuiltinWaveform :: Flat ( flat) => flat. iq_values_at_sample_rate ( common, sample_rate) ,
272308
@@ -298,20 +334,19 @@ impl BuiltinWaveformParameters for Flat {
298334 self ,
299335 common : CommonBuiltinParameters ,
300336 sample_rate : f64 ,
301- ) -> IqSamples {
337+ ) -> Result < IqSamples , SamplingError > {
302338 let ExplicitCommonBuiltinParameters {
303- duration ,
339+ sample_count ,
304340 scale,
305341 phase,
306342 detuning,
307- } = common. into ( ) ;
343+ } = common. resolve_with_sample_rate ( sample_rate ) ? ;
308344 let Self { iq } = self ;
309345
310- // TODO: no verification of integer sample count
311- let sample_count = ceiling_with_epsilon ( duration * sample_rate) as usize ;
346+ let sample_count = sample_count as usize ;
312347 let scaled_iq = scale * iq;
313348
314- if detuning == 0.0 {
349+ Ok ( if detuning == 0.0 {
315350 IqSamples :: Flat {
316351 iq : apply_phase ( scaled_iq, phase) ,
317352 sample_count,
@@ -320,7 +355,7 @@ impl BuiltinWaveformParameters for Flat {
320355 let mut samples = vec ! [ scaled_iq; sample_count] ;
321356 apply_phase_and_detuning ( & mut samples, phase, detuning, sample_rate) ;
322357 IqSamples :: Samples ( samples)
323- }
358+ } )
324359 }
325360}
326361
@@ -329,7 +364,7 @@ impl BuiltinWaveformParameters for Gaussian {
329364 self ,
330365 common : CommonBuiltinParameters ,
331366 sample_rate : f64 ,
332- ) -> IqSamples {
367+ ) -> Result < IqSamples , SamplingError > {
333368 let Self { fwhm, t0 } = self ;
334369
335370 build_samples_and_adjust_for_common_parameters (
@@ -349,7 +384,7 @@ impl BuiltinWaveformParameters for DragGaussian {
349384 self ,
350385 common : CommonBuiltinParameters ,
351386 sample_rate : f64 ,
352- ) -> IqSamples {
387+ ) -> Result < IqSamples , SamplingError > {
353388 let Self {
354389 fwhm,
355390 t0,
@@ -379,7 +414,7 @@ impl BuiltinWaveformParameters for ErfSquare {
379414 self ,
380415 common : CommonBuiltinParameters ,
381416 sample_rate : f64 ,
382- ) -> IqSamples {
417+ ) -> Result < IqSamples , SamplingError > {
383418 let CommonBuiltinParameters { duration, .. } = common;
384419 let Self {
385420 risetime,
@@ -413,7 +448,7 @@ impl BuiltinWaveformParameters for HermiteGaussian {
413448 self ,
414449 common : CommonBuiltinParameters ,
415450 sample_rate : f64 ,
416- ) -> IqSamples {
451+ ) -> Result < IqSamples , SamplingError > {
417452 let Self {
418453 fwhm,
419454 t0,
@@ -447,19 +482,18 @@ impl BuiltinWaveformParameters for BoxcarKernel {
447482 self ,
448483 common : CommonBuiltinParameters ,
449484 sample_rate : f64 ,
450- ) -> IqSamples {
485+ ) -> Result < IqSamples , SamplingError > {
451486 let ExplicitCommonBuiltinParameters {
452- duration ,
487+ sample_count ,
453488 scale,
454489 phase,
455490 detuning,
456- } = common. into ( ) ;
491+ } = common. resolve_with_sample_rate ( sample_rate ) ? ;
457492 let Self = self ; // Get errors if the definition changes
458493
459- // TODO: no verification of integer sample count
460- let sample_count = ceiling_with_epsilon ( duration * sample_rate) as usize ;
494+ let sample_count = sample_count as usize ;
461495
462- if detuning == 0.0 {
496+ Ok ( if detuning == 0.0 {
463497 let iq = polar_to_rectangular ( scale / sample_count as f64 , phase) ;
464498 IqSamples :: Flat { iq, sample_count }
465499 } else {
@@ -470,7 +504,7 @@ impl BuiltinWaveformParameters for BoxcarKernel {
470504 )
471505 } ) ;
472506 IqSamples :: Samples ( samples. collect ( ) )
473- }
507+ } )
474508 }
475509}
476510
@@ -501,20 +535,19 @@ fn build_samples_and_adjust_for_common_parameters<I: IntoIterator<Item = Complex
501535 parameters : SamplingParameters ,
502536 common : CommonBuiltinParameters ,
503537 build : impl FnOnce ( SamplingInfo ) -> I ,
504- ) -> IqSamples {
538+ ) -> Result < IqSamples , SamplingError > {
505539 let SamplingParameters { sample_rate, fwhm } = parameters;
506540 let ExplicitCommonBuiltinParameters {
507- duration ,
541+ sample_count ,
508542 scale,
509543 phase,
510544 detuning,
511- } = common. into ( ) ;
545+ } = common. resolve_with_sample_rate ( sample_rate ) ? ;
512546
513547 // It would be nice to be able to optimize `scale: 0.0` to `IqSamples::Flat`, but we'd have to
514548 // figure out how to take care of the `erf_squared` padding durations.
515549
516- let length = ceiling_with_epsilon ( duration * sample_rate) ;
517- let time_steps = Array :: range ( 0.0 , length, 1.0 ) / sample_rate;
550+ let time_steps = Array :: range ( 0.0 , sample_count, 1.0 ) / sample_rate;
518551 let sigma = 0.5 * fwhm / ( 2.0 * LN_2 ) . sqrt ( ) ;
519552
520553 let mut samples: Vec < _ > = build ( SamplingInfo { time_steps, sigma } )
@@ -526,7 +559,7 @@ fn build_samples_and_adjust_for_common_parameters<I: IntoIterator<Item = Complex
526559 apply_phase_and_detuning_at_index ( scale * * sample, phase, detuning, sample_rate, index) ;
527560 }
528561
529- IqSamples :: Samples ( samples)
562+ Ok ( IqSamples :: Samples ( samples) )
530563}
531564
532565/// Modulate and phase shift waveform IQ data in place.
@@ -562,20 +595,6 @@ fn apply_phase(iq_value: Complex64, phase: Cycles<f64>) -> Complex64 {
562595 iq_value * Complex64 :: cis ( Radians :: from ( phase) . 0 )
563596}
564597
565- /// A custom `ceil()` method that includes a machine-epsilon sized region above each integer in the
566- /// set of values that are mapped to that integer. In other words:
567- /// ceil_eps(x) = n, for all x and integers n s.t. n <= x < (n + n*epsilon)
568- ///
569- /// To handle accumulated floating point errors in sweeps above typical floating point imprecision
570- /// we make epsilon 10x larger than floating point epsilon.
571- fn ceiling_with_epsilon ( value : f64 ) -> f64 {
572- // This isn't really what EPSILON is for:
573- // <https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/>.
574- // However, changing this now might break behavior, so we leave it be.
575- let truncated = value - ( value * 10.0 * f64:: EPSILON ) ;
576- truncated. ceil ( )
577- }
578-
579598/// Convert polar coordinates to rectangular coordinates.
580599fn polar_to_rectangular ( magnitude : f64 , angle : Cycles < f64 > ) -> Complex64 {
581600 Complex64 :: from_polar ( magnitude, Radians :: from ( angle) . 0 )
@@ -611,15 +630,18 @@ mod tests {
611630 #[ case( -1.0 , Cycles ( 0.0 ) , Complex64 :: new( -0.1 , 0.0 ) ) ]
612631 #[ case( 0.0 , Cycles ( 0.0 ) , Complex64 :: new( 0.0 , 0.0 ) ) ]
613632 fn boxcar_kernel ( #[ case] scale : f64 , #[ case] phase : Cycles < f64 > , #[ case] expected : Complex64 ) {
614- match BoxcarKernel . iq_values_at_sample_rate (
615- CommonBuiltinParameters {
616- duration : 0.1 ,
617- scale : Some ( scale) ,
618- phase : Some ( phase) ,
619- detuning : None ,
620- } ,
621- 100.0 ,
622- ) {
633+ match BoxcarKernel
634+ . iq_values_at_sample_rate (
635+ CommonBuiltinParameters {
636+ duration : 0.1 ,
637+ scale : Some ( scale) ,
638+ phase : Some ( phase) ,
639+ detuning : None ,
640+ } ,
641+ 100.0 ,
642+ )
643+ . unwrap ( )
644+ {
623645 IqSamples :: Flat { iq, sample_count } => {
624646 assert_eq ! ( sample_count, 10 ) ;
625647 assert_almost_eq ( iq, expected, 1e-10 ) ;
@@ -634,14 +656,74 @@ mod tests {
634656 }
635657
636658 #[ rstest:: rstest]
637- #[ case( 0.0 , 0.0 ) ]
638- #[ case( -f64 :: EPSILON , 0.0 ) ]
639- #[ case( f64 :: EPSILON , 1.0 ) ]
640- // Based on a past edge case
641- #[ case( 8.800_000_000_000_001e-8 * 1.0e9 , 88.0 ) ]
642- fn ceiling_with_epsilon ( #[ case] value : f64 , #[ case] expected : f64 ) {
643- let result = super :: ceiling_with_epsilon ( value) ;
644- assert_eq ! ( result, expected) ;
659+ #[ case( 0.0 , 0.0 , Some ( 0.0 ) ) ]
660+ #[ case( 0.0 , 1e9 , Some ( 0.0 ) ) ]
661+ #[ case( 1e9 , 0.0 , Some ( 0.0 ) ) ]
662+ #[ case( f64 :: EPSILON , 1.0 , Some ( 0.0 ) ) ]
663+ #[ case( -f64 :: EPSILON , 1.0 , Some ( 0.0 ) ) ]
664+ #[ case( 0.9999999 , 101.0 , Some ( 101.0 ) ) ]
665+ #[ case( 1.0000001 , 101.0 , Some ( 101.0 ) ) ]
666+ #[ case( 0.99 , 101.0 , None ) ]
667+ #[ case( 1.01 , 101.0 , None ) ]
668+ #[ case( 8.800_000_000_000_001e-8 , 1.0e9 , Some ( 88.0 ) ) ] // Based on a past edge case
669+ #[ case( 0.5 , 3.0 , None ) ]
670+ fn sample_count (
671+ #[ case] duration : f64 ,
672+ #[ case] sample_rate : f64 ,
673+ #[ case] expected : Option < f64 > ,
674+ ) {
675+ let actual = CommonBuiltinParameters {
676+ duration,
677+ scale : None ,
678+ phase : None ,
679+ detuning : None ,
680+ }
681+ . resolve_with_sample_rate ( sample_rate) ;
682+
683+ match ( actual, expected) {
684+ (
685+ Ok ( ExplicitCommonBuiltinParameters {
686+ sample_count : actual,
687+ ..
688+ } ) ,
689+ Some ( expected) ,
690+ ) => {
691+ assert_eq ! (
692+ expected, actual,
693+ "duration = {duration} s,\n \
694+ sample_rate = {sample_rate} Hz,\n \
695+ expected = {expected} samples,\n \
696+ actual = {actual} samples"
697+ )
698+ }
699+ ( Err ( _) , None ) => { }
700+ (
701+ Ok ( ExplicitCommonBuiltinParameters {
702+ sample_count : actual,
703+ ..
704+ } ) ,
705+ None ,
706+ ) => {
707+ panic ! (
708+ "duration = {duration} s, sample_rate = {sample_rate} Hz: \
709+ expected to be unable to generate a sample count, but generated {actual}",
710+ duration = duration,
711+ sample_rate = sample_rate,
712+ actual = actual,
713+ )
714+ }
715+ ( Err ( actual) , Some ( expected) ) => {
716+ panic ! (
717+ "duration = {duration} s, sample_rate = {sample_rate} Hz: \
718+ expected a sample count of {expected} samples, but got the following error:\n \
719+ {actual}",
720+ duration = duration,
721+ sample_rate = sample_rate,
722+ expected = expected,
723+ actual = actual,
724+ )
725+ }
726+ }
645727 }
646728
647729 /// Assert that for some exemplar waveform templates, the right IQ values are generated.
@@ -717,6 +799,7 @@ mod tests {
717799 ) {
718800 let iq_values = parameters
719801 . iq_values_at_sample_rate ( common, 1e6 )
802+ . unwrap ( )
720803 . into_iq_values ( ) ;
721804 let count = iq_values. len ( ) ;
722805
0 commit comments