1+ use super :: pdf_backend:: { self , Backend , ForcePositive , PdfBackend , PdfSetBackend } ;
12use super :: GlobalConfiguration ;
23use anyhow:: { anyhow, bail, Context , Error , Result } ;
34use itertools:: Itertools ;
@@ -64,6 +65,7 @@ impl FromStr for ConvFuns {
6465 }
6566}
6667
68+ /// Creates convolution functions using LHAPDF backend (legacy).
6769pub fn create_conv_funs ( funs : & ConvFuns ) -> Result < Vec < Pdf > > {
6870 Ok ( funs
6971 . lhapdf_names
@@ -82,6 +84,22 @@ pub fn create_conv_funs(funs: &ConvFuns) -> Result<Vec<Pdf>> {
8284 . collect :: < Result < _ , _ > > ( ) ?)
8385}
8486
87+ /// Creates convolution functions using the specified backend.
88+ pub fn create_conv_funs_with_backend (
89+ funs : & ConvFuns ,
90+ backend : Backend ,
91+ ) -> Result < Vec < Box < dyn PdfBackend > > > {
92+ funs. lhapdf_names
93+ . iter ( )
94+ . zip ( & funs. members )
95+ . map ( |( name, member) | {
96+ let member = member. unwrap_or ( 0 ) ;
97+ pdf_backend:: create_pdf ( name, member, backend)
98+ } )
99+ . collect ( )
100+ }
101+
102+ /// Creates convolution functions for a PDF set using LHAPDF backend (legacy).
85103pub fn create_conv_funs_for_set (
86104 funs : & ConvFuns ,
87105 index_of_set : usize ,
@@ -115,6 +133,30 @@ pub fn create_conv_funs_for_set(
115133 Ok ( ( set, conv_funs) )
116134}
117135
136+ /// Creates convolution functions for a PDF set using the specified backend.
137+ ///
138+ /// Returns a tuple of (PdfSetBackend, Vec<Vec<Box<dyn PdfBackend>>>).
139+ pub fn create_conv_funs_for_set_with_backend (
140+ funs : & ConvFuns ,
141+ index_of_set : usize ,
142+ backend : Backend ,
143+ ) -> Result < ( Box < dyn PdfSetBackend > , Vec < Vec < Box < dyn PdfBackend > > > ) > {
144+ let setname = & funs. lhapdf_names [ index_of_set] ;
145+ let set = pdf_backend:: create_pdf_set ( setname, backend) ?;
146+
147+ let set_members = set. mk_pdfs ( ) ?;
148+ let conv_funs = set_members
149+ . into_iter ( )
150+ . map ( |member_pdf| {
151+ let mut conv_funs = create_conv_funs_with_backend ( funs, backend) ?;
152+ conv_funs[ index_of_set] = member_pdf;
153+ Ok :: < _ , Error > ( conv_funs)
154+ } )
155+ . collect :: < Result < _ , _ > > ( ) ?;
156+
157+ Ok ( ( set, conv_funs) )
158+ }
159+
118160pub fn read_grid ( input : & Path ) -> Result < Grid > {
119161 Grid :: read ( File :: open ( input) . context ( format ! ( "unable to open '{}'" , input. display( ) ) ) ?)
120162 . context ( format ! ( "unable to read '{}'" , input. display( ) ) )
@@ -238,6 +280,7 @@ pub enum ConvoluteMode {
238280 Normal ,
239281}
240282
283+ /// Performs convolution with scale variations using LHAPDF backend (legacy).
241284pub fn convolve_scales (
242285 grid : & Grid ,
243286 conv_funs : & mut [ Pdf ] ,
@@ -357,6 +400,131 @@ pub fn convolve_scales(
357400 }
358401}
359402
403+ /// Performs convolution with scale variations using the backend abstraction.
404+ #[ allow( clippy:: too_many_arguments) ]
405+ pub fn convolve_scales_with_backend (
406+ grid : & Grid ,
407+ conv_funs : & mut [ Box < dyn PdfBackend > ] ,
408+ conv_types : & [ ConvType ] ,
409+ orders : & [ ( u8 , u8 ) ] ,
410+ bins : & [ usize ] ,
411+ channels : & [ bool ] ,
412+ scales : & [ ( f64 , f64 , f64 ) ] ,
413+ mode : ConvoluteMode ,
414+ cfg : & GlobalConfiguration ,
415+ ) -> Vec < f64 > {
416+ let orders: Vec < _ > = grid
417+ . orders ( )
418+ . iter ( )
419+ . map ( |order| {
420+ orders. is_empty ( )
421+ || orders
422+ . iter ( )
423+ . any ( |other| ( order. alphas == other. 0 ) && ( order. alpha == other. 1 ) )
424+ } )
425+ . collect ( ) ;
426+
427+ if cfg. force_positive {
428+ for fun in conv_funs. iter_mut ( ) {
429+ fun. set_force_positive ( ForcePositive :: ClipNegative ) ;
430+ }
431+ }
432+
433+ // TODO: promote this to an error
434+ assert ! (
435+ cfg. use_alphas_from < conv_funs. len( ) ,
436+ "expected `use_alphas_from` to be an integer within `[0, {})`, but got `{}`" ,
437+ conv_funs. len( ) ,
438+ cfg. use_alphas_from
439+ ) ;
440+
441+ // Get x_min/x_max before creating closures (requires mut)
442+ let x_min_max: Vec < _ > = conv_funs
443+ . iter_mut ( )
444+ . map ( |fun| ( fun. x_min ( ) , fun. x_max ( ) ) )
445+ . collect ( ) ;
446+
447+ // Create closures for xfx evaluation
448+ let mut funs: Vec < _ > = conv_funs
449+ . iter ( )
450+ . zip ( & x_min_max)
451+ . map ( |( fun, & ( x_min, x_max) ) | {
452+ move |id : i32 , x : f64 , q2 : f64 | {
453+ if !cfg. allow_extrapolation && ( x < x_min || x > x_max) {
454+ 0.0
455+ } else {
456+ fun. xfx_q2 ( id, x, q2)
457+ }
458+ }
459+ } )
460+ . collect ( ) ;
461+
462+ let xfx: Vec < _ > = funs
463+ . iter_mut ( )
464+ . map ( |fun| fun as & mut dyn FnMut ( i32 , f64 , f64 ) -> f64 )
465+ . collect ( ) ;
466+
467+ // Create alphas closures
468+ let mut alphas_funs: Vec < _ > = conv_funs
469+ . iter ( )
470+ . map ( |fun| {
471+ let fun_ref = fun. as_ref ( ) ;
472+ move |q2 : f64 | fun_ref. alphas_q2 ( q2)
473+ } )
474+ . collect ( ) ;
475+
476+ // Get particle IDs from the PDFs using the backend interface
477+ let convolutions: Vec < _ > = conv_funs
478+ . iter ( )
479+ . zip ( conv_types)
480+ . map ( |( fun, & conv_type) | {
481+ let pid = fun. particle_id ( ) ;
482+ Conv :: new ( conv_type, pid)
483+ } )
484+ . collect ( ) ;
485+
486+ let mut cache = ConvolutionCache :: new ( convolutions, xfx, & mut alphas_funs[ cfg. use_alphas_from ] ) ;
487+ let mut results = grid. convolve ( & mut cache, & orders, bins, channels, scales) ;
488+
489+ match mode {
490+ ConvoluteMode :: Asymmetry => {
491+ let bin_count = grid. bwfl ( ) . len ( ) ;
492+
493+ // calculating the asymmetry for a subset of bins doesn't work
494+ assert ! ( ( bins. is_empty( ) || ( bins. len( ) == bin_count) ) && ( bin_count % 2 == 0 ) ) ;
495+
496+ results
497+ . iter ( )
498+ . skip ( ( bin_count / 2 ) * scales. len ( ) )
499+ . zip (
500+ results
501+ . chunks_exact ( scales. len ( ) )
502+ . take ( bin_count / 2 )
503+ . rev ( )
504+ . flatten ( ) ,
505+ )
506+ . map ( |( pos, neg) | ( pos - neg) / ( pos + neg) )
507+ . collect ( )
508+ }
509+ ConvoluteMode :: Integrated => {
510+ results
511+ . iter_mut ( )
512+ . zip (
513+ grid. bwfl ( )
514+ . normalizations ( )
515+ . into_iter ( )
516+ . enumerate ( )
517+ . filter ( |( index, _) | bins. is_empty ( ) || bins. contains ( index) )
518+ . flat_map ( |( _, norm) | iter:: repeat ( norm) . take ( scales. len ( ) ) ) ,
519+ )
520+ . for_each ( |( value, norm) | * value *= norm) ;
521+
522+ results
523+ }
524+ ConvoluteMode :: Normal => results,
525+ }
526+ }
527+
360528pub fn scales_vector ( grid : & Grid , scales : usize ) -> & [ ( f64 , f64 , f64 ) ] {
361529 let Scales { fac, frg, .. } = grid. scales ( ) ;
362530
@@ -373,6 +541,7 @@ pub fn scales_vector(grid: &Grid, scales: usize) -> &[(f64, f64, f64)] {
373541 }
374542}
375543
544+ /// Performs convolution using LHAPDF backend (legacy).
376545pub fn convolve (
377546 grid : & Grid ,
378547 conv_funs : & mut [ Pdf ] ,
@@ -397,6 +566,32 @@ pub fn convolve(
397566 )
398567}
399568
569+ /// Performs convolution using the backend abstraction.
570+ #[ allow( clippy:: too_many_arguments) ]
571+ pub fn convolve_with_backend (
572+ grid : & Grid ,
573+ conv_funs : & mut [ Box < dyn PdfBackend > ] ,
574+ conv_types : & [ ConvType ] ,
575+ orders : & [ ( u8 , u8 ) ] ,
576+ bins : & [ usize ] ,
577+ lumis : & [ bool ] ,
578+ scales : usize ,
579+ mode : ConvoluteMode ,
580+ cfg : & GlobalConfiguration ,
581+ ) -> Vec < f64 > {
582+ convolve_scales_with_backend (
583+ grid,
584+ conv_funs,
585+ conv_types,
586+ orders,
587+ bins,
588+ lumis,
589+ scales_vector ( grid, scales) ,
590+ mode,
591+ cfg,
592+ )
593+ }
594+
400595pub fn convolve_limits ( grid : & Grid , bins : & [ usize ] , mode : ConvoluteMode ) -> Vec < Vec < ( f64 , f64 ) > > {
401596 let limits: Vec < _ > = grid
402597 . bwfl ( )
0 commit comments