Skip to main content

gammaloop_api/commands/
select.rs

1use clap::Args;
2use color_eyre::Result;
3use colored::Colorize;
4use eyre::{eyre, Context};
5use gammalooprs::processes::{
6    CycleSignature, GraphGroupSelectionMode, GraphGroupSelectionSpec, ParticleSignature,
7    RaisedPropagatorScope, RaisedPropagatorSignature, SelectionPolarity, VertexSignature,
8};
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11use std::str::FromStr;
12use tracing::info;
13
14use crate::{
15    completion::CompletionArgExt,
16    state::{
17        GraphGroupSelectionContext, GraphGroupSelectionTarget, ProcessRef, SelectedGraphGroups,
18        State,
19    },
20    CLISettings,
21};
22
23#[derive(Args, Debug, Serialize, Deserialize, Clone, JsonSchema, PartialEq)]
24pub struct Select {
25    /// Process reference: `#<id>`, `name:<name>`, or `<id>/<name>`
26    #[arg(
27        long = "process",
28        short = 'p',
29        value_name = "PROCESS",
30        completion_process_selector(crate::completion::SelectorKind::Any)
31    )]
32    pub process: Option<ProcessRef>,
33
34    /// Integrand name inside the selected process
35    #[arg(
36        long = "integrand-name",
37        short = 'i',
38        value_name = "NAME",
39        completion_integrand_selector(crate::completion::SelectorKind::Any)
40    )]
41    pub integrand_name: Option<String>,
42
43    /// Apply structural filters to left/right amplitude graphs of cross-section cuts
44    #[arg(long = "amplitude-graphs", default_value_t = false)]
45    pub amplitude_graphs: bool,
46
47    /// Retain graph groups with these master graph names
48    #[arg(
49        long = "with-graph-names",
50        value_name = "MASTER_GRAPH",
51        num_args = 1..,
52        completion_selected_master_graph()
53    )]
54    pub with_graph_names: Vec<String>,
55
56    /// Retain exactly these graph groups and ignore every other selection filter
57    #[arg(
58        long = "with-only-graph-names",
59        value_name = "MASTER_GRAPH",
60        num_args = 1..,
61        completion_selected_master_graph()
62    )]
63    pub with_only_graph_names: Vec<String>,
64
65    /// Remove graph groups with these master graph names
66    #[arg(
67        long = "without-graph-names",
68        value_name = "MASTER_GRAPH",
69        num_args = 1..,
70        completion_selected_master_graph()
71    )]
72    pub without_graph_names: Vec<String>,
73
74    /// Retain graph groups with one of these raised-propagator signatures
75    #[arg(
76        long = "with-raised-propagator-signatures",
77        value_name = "SIGNATURE",
78        num_args = 1..,
79        completion_select_raised_signature(crate::completion::SelectRaisedSignatureScope::All)
80    )]
81    pub with_raised_propagator_signatures: Vec<String>,
82
83    /// Remove graph groups with one of these raised-propagator signatures
84    #[arg(
85        long = "without-raised-propagator-signatures",
86        value_name = "SIGNATURE",
87        num_args = 1..,
88        completion_select_raised_signature(crate::completion::SelectRaisedSignatureScope::All)
89    )]
90    pub without_raised_propagator_signatures: Vec<String>,
91
92    /// Retain graph groups with one of these massive raised-propagator signatures
93    #[arg(
94        long = "with-massive-raised-propagator-signatures",
95        value_name = "SIGNATURE",
96        num_args = 1..,
97        completion_select_raised_signature(crate::completion::SelectRaisedSignatureScope::Massive)
98    )]
99    pub with_massive_raised_propagator_signatures: Vec<String>,
100
101    /// Remove graph groups with one of these massive raised-propagator signatures
102    #[arg(
103        long = "without-massive-raised-propagator-signatures",
104        value_name = "SIGNATURE",
105        num_args = 1..,
106        completion_select_raised_signature(crate::completion::SelectRaisedSignatureScope::Massive)
107    )]
108    pub without_massive_raised_propagator_signatures: Vec<String>,
109
110    /// Retain graph groups with one of these massless raised-propagator signatures
111    #[arg(
112        long = "with-massless-raised-propagator-signatures",
113        value_name = "SIGNATURE",
114        num_args = 1..,
115        completion_select_raised_signature(crate::completion::SelectRaisedSignatureScope::Massless)
116    )]
117    pub with_massless_raised_propagator_signatures: Vec<String>,
118
119    /// Remove graph groups with one of these massless raised-propagator signatures
120    #[arg(
121        long = "without-massless-raised-propagator-signatures",
122        value_name = "SIGNATURE",
123        num_args = 1..,
124        completion_select_raised_signature(crate::completion::SelectRaisedSignatureScope::Massless)
125    )]
126    pub without_massless_raised_propagator_signatures: Vec<String>,
127
128    /// Retain graph groups where a process-valid cut touches one of these raised-propagator group signatures
129    #[arg(
130        long = "with-raised-cuts-signatures",
131        value_name = "SIGNATURE",
132        num_args = 1..,
133        completion_select_raised_cut_signature(crate::completion::SelectRaisedSignatureScope::All)
134    )]
135    pub with_raised_cuts_signatures: Vec<String>,
136
137    /// Remove graph groups where a process-valid cut touches one of these raised-propagator group signatures
138    #[arg(
139        long = "without-raised-cuts-signatures",
140        value_name = "SIGNATURE",
141        num_args = 1..,
142        completion_select_raised_cut_signature(crate::completion::SelectRaisedSignatureScope::All)
143    )]
144    pub without_raised_cuts_signatures: Vec<String>,
145
146    /// Retain graph groups where a process-valid cut touches one of these massive raised-propagator group signatures
147    #[arg(
148        long = "with-massive-raised-cuts-signatures",
149        value_name = "SIGNATURE",
150        num_args = 1..,
151        completion_select_raised_cut_signature(crate::completion::SelectRaisedSignatureScope::Massive)
152    )]
153    pub with_massive_raised_cuts_signatures: Vec<String>,
154
155    /// Remove graph groups where a process-valid cut touches one of these massive raised-propagator group signatures
156    #[arg(
157        long = "without-massive-raised-cuts-signatures",
158        value_name = "SIGNATURE",
159        num_args = 1..,
160        completion_select_raised_cut_signature(crate::completion::SelectRaisedSignatureScope::Massive)
161    )]
162    pub without_massive_raised_cuts_signatures: Vec<String>,
163
164    /// Retain graph groups where a process-valid cut touches one of these massless raised-propagator group signatures
165    #[arg(
166        long = "with-massless-raised-cuts-signatures",
167        value_name = "SIGNATURE",
168        num_args = 1..,
169        completion_select_raised_cut_signature(crate::completion::SelectRaisedSignatureScope::Massless)
170    )]
171    pub with_massless_raised_cuts_signatures: Vec<String>,
172
173    /// Remove graph groups where a process-valid cut touches one of these massless raised-propagator group signatures
174    #[arg(
175        long = "without-massless-raised-cuts-signatures",
176        value_name = "SIGNATURE",
177        num_args = 1..,
178        completion_select_raised_cut_signature(crate::completion::SelectRaisedSignatureScope::Massless)
179    )]
180    pub without_massless_raised_cuts_signatures: Vec<String>,
181
182    /// Retain graph groups matching one of these cycle signatures
183    #[arg(
184        long = "with-cycle-signatures",
185        value_name = "SIGNATURE",
186        num_args = 1..,
187        completion_select_cycle_signature()
188    )]
189    pub with_cycle_signatures: Vec<String>,
190
191    /// Remove graph groups matching one of these cycle signatures
192    #[arg(
193        long = "without-cycle-signatures",
194        value_name = "SIGNATURE",
195        num_args = 1..,
196        completion_select_cycle_signature()
197    )]
198    pub without_cycle_signatures: Vec<String>,
199
200    /// Retain graph groups containing at least one of these vertex-rule multisets
201    #[arg(
202        long = "with-vertices",
203        value_name = "VERTEX_LIST",
204        num_args = 1..,
205        completion_select_vertex_signature()
206    )]
207    pub with_vertices: Vec<String>,
208
209    /// Remove graph groups containing at least one of these vertex-rule multisets
210    #[arg(
211        long = "without-vertices",
212        value_name = "VERTEX_LIST",
213        num_args = 1..,
214        completion_select_vertex_signature()
215    )]
216    pub without_vertices: Vec<String>,
217
218    /// Retain graph groups containing all particles from at least one of these particle sets
219    #[arg(
220        long = "with-particles",
221        value_name = "PARTICLE_SET",
222        num_args = 1..,
223        completion_select_particle_signature()
224    )]
225    pub with_particles: Vec<String>,
226
227    /// Remove graph groups containing all particles from at least one of these particle sets
228    #[arg(
229        long = "without-particles",
230        value_name = "PARTICLE_SET",
231        num_args = 1..,
232        completion_select_particle_signature()
233    )]
234    pub without_particles: Vec<String>,
235
236    /// Copy the selected graph groups to this output process instead of modifying in place
237    #[arg(
238        long = "output_process",
239        value_name = "NAME",
240        completion_disable_special_value()
241    )]
242    pub output_process: Option<String>,
243
244    /// Copy the selected graph groups to this output integrand instead of modifying in place
245    #[arg(
246        long = "output_integrand",
247        value_name = "NAME",
248        completion_disable_special_value()
249    )]
250    pub output_integrand: Option<String>,
251
252    /// Allow copy-mode selection to replace an existing output process or integrand
253    #[arg(
254        long = "clear-existing-processes",
255        short = 'c',
256        alias = "clear",
257        default_value_t = false
258    )]
259    pub clear_existing_processes: bool,
260}
261
262impl Select {
263    pub fn run(&self, state: &mut State, global_cli_settings: &CLISettings) -> Result<()> {
264        let selection = self.selection_spec(state)?;
265        let target = self.selection_target();
266        let selected = state.select_integrand_graph_groups(
267            self.process.as_ref(),
268            self.integrand_name.as_ref(),
269            &selection,
270            &target,
271            GraphGroupSelectionContext::new(
272                &global_cli_settings.global.generation,
273                &global_cli_settings.state.folder,
274                global_cli_settings.session.read_only_state,
275            ),
276        )?;
277
278        report_selection(&selected);
279        if selected.copied_to_output {
280            if selected.replaced_existing_target {
281                info!("{}", "Replaced existing output target.".yellow());
282            }
283            if selected.removed_target_artifacts {
284                info!("{}", "Removed stale saved output artifacts.".yellow());
285            }
286            info!(
287                "{} {}",
288                "Created selected pregeneration integrand; generate with".yellow(),
289                format!(
290                    "generate existing -p #{} -i {}",
291                    selected.process_id, selected.integrand_name
292                )
293                .green()
294            );
295        } else if selected.discarded_generated_integrand {
296            info!(
297                "{} {}",
298                "Discarded generated integrand state; regenerate with".yellow(),
299                format!(
300                    "generate existing -p #{} -i {}",
301                    selected.process_id, selected.integrand_name
302                )
303                .green()
304            );
305        }
306
307        Ok(())
308    }
309
310    fn selection_target(&self) -> GraphGroupSelectionTarget {
311        GraphGroupSelectionTarget::copy(
312            self.output_process.clone(),
313            self.output_integrand.clone(),
314            self.clear_existing_processes,
315        )
316    }
317
318    fn selection_spec(&self, state: &State) -> Result<GraphGroupSelectionSpec> {
319        let mode = if self.amplitude_graphs {
320            GraphGroupSelectionMode::CrossSectionAmplitudeGraphs
321        } else {
322            GraphGroupSelectionMode::MasterGraphs
323        };
324        if !self.with_only_graph_names.is_empty() {
325            return Ok(GraphGroupSelectionSpec::from_master_graph_names(
326                self.with_only_graph_names.clone(),
327            )
328            .with_mode(mode));
329        }
330
331        let mut spec = GraphGroupSelectionSpec::new()
332            .with_mode(mode)
333            .with_master_graph_names_polarity(
334                SelectionPolarity::With,
335                self.with_graph_names.clone(),
336            )
337            .with_master_graph_names_polarity(
338                SelectionPolarity::Without,
339                self.without_graph_names.clone(),
340            );
341
342        spec = spec
343            .with_raised_propagator_signatures(
344                SelectionPolarity::With,
345                RaisedPropagatorScope::All,
346                parse_raised_signatures(&self.with_raised_propagator_signatures)?,
347            )
348            .with_raised_propagator_signatures(
349                SelectionPolarity::Without,
350                RaisedPropagatorScope::All,
351                parse_raised_signatures(&self.without_raised_propagator_signatures)?,
352            )
353            .with_raised_propagator_signatures(
354                SelectionPolarity::With,
355                RaisedPropagatorScope::Massive,
356                parse_raised_signatures(&self.with_massive_raised_propagator_signatures)?,
357            )
358            .with_raised_propagator_signatures(
359                SelectionPolarity::Without,
360                RaisedPropagatorScope::Massive,
361                parse_raised_signatures(&self.without_massive_raised_propagator_signatures)?,
362            )
363            .with_raised_propagator_signatures(
364                SelectionPolarity::With,
365                RaisedPropagatorScope::Massless,
366                parse_raised_signatures(&self.with_massless_raised_propagator_signatures)?,
367            )
368            .with_raised_propagator_signatures(
369                SelectionPolarity::Without,
370                RaisedPropagatorScope::Massless,
371                parse_raised_signatures(&self.without_massless_raised_propagator_signatures)?,
372            )
373            .with_raised_cut_signatures(
374                SelectionPolarity::With,
375                RaisedPropagatorScope::All,
376                parse_raised_cut_signatures(&self.with_raised_cuts_signatures)?,
377            )
378            .with_raised_cut_signatures(
379                SelectionPolarity::Without,
380                RaisedPropagatorScope::All,
381                parse_raised_cut_signatures(&self.without_raised_cuts_signatures)?,
382            )
383            .with_raised_cut_signatures(
384                SelectionPolarity::With,
385                RaisedPropagatorScope::Massive,
386                parse_raised_cut_signatures(&self.with_massive_raised_cuts_signatures)?,
387            )
388            .with_raised_cut_signatures(
389                SelectionPolarity::Without,
390                RaisedPropagatorScope::Massive,
391                parse_raised_cut_signatures(&self.without_massive_raised_cuts_signatures)?,
392            )
393            .with_raised_cut_signatures(
394                SelectionPolarity::With,
395                RaisedPropagatorScope::Massless,
396                parse_raised_cut_signatures(&self.with_massless_raised_cuts_signatures)?,
397            )
398            .with_raised_cut_signatures(
399                SelectionPolarity::Without,
400                RaisedPropagatorScope::Massless,
401                parse_raised_cut_signatures(&self.without_massless_raised_cuts_signatures)?,
402            )
403            .with_cycle_signatures(
404                SelectionPolarity::With,
405                parse_cycle_signatures(&self.with_cycle_signatures, state)?,
406            )
407            .with_cycle_signatures(
408                SelectionPolarity::Without,
409                parse_cycle_signatures(&self.without_cycle_signatures, state)?,
410            )
411            .with_vertex_signatures(
412                SelectionPolarity::With,
413                parse_vertex_signatures(&self.with_vertices, state)?,
414            )
415            .with_vertex_signatures(
416                SelectionPolarity::Without,
417                parse_vertex_signatures(&self.without_vertices, state)?,
418            )
419            .with_particle_signatures(
420                SelectionPolarity::With,
421                parse_particle_signatures(&self.with_particles, state)?,
422            )
423            .with_particle_signatures(
424                SelectionPolarity::Without,
425                parse_particle_signatures(&self.without_particles, state)?,
426            );
427
428        if spec.is_empty() {
429            return Err(eyre!(
430                "No graph-group selection filters were provided. Use --with-only-graph-names, --with-graph-names, or another --with/--without selection option."
431            ));
432        }
433
434        Ok(spec)
435    }
436}
437
438fn parse_raised_signatures(values: &[String]) -> Result<Vec<RaisedPropagatorSignature>> {
439    values
440        .iter()
441        .map(|value| {
442            RaisedPropagatorSignature::from_str(value)
443                .with_context(|| format!("While parsing raised-propagator signature '{value}'"))
444        })
445        .collect()
446}
447
448fn parse_raised_cut_signatures(values: &[String]) -> Result<Vec<RaisedPropagatorSignature>> {
449    values
450        .iter()
451        .map(|value| {
452            RaisedPropagatorSignature::from_str(value)
453                .with_context(|| format!("While parsing raised-cut signature '{value}'"))
454        })
455        .collect()
456}
457
458fn parse_cycle_signatures(values: &[String], state: &State) -> Result<Vec<CycleSignature>> {
459    values
460        .iter()
461        .map(|value| CycleSignature::parse(value, &state.model))
462        .collect()
463}
464
465fn parse_vertex_signatures(values: &[String], state: &State) -> Result<Vec<VertexSignature>> {
466    values
467        .iter()
468        .map(|value| {
469            let signature = VertexSignature::parse(value)?;
470            for vertex_rule_name in signature.vertex_rule_names() {
471                if !state
472                    .model
473                    .vertex_rule_name_to_position
474                    .contains_key(vertex_rule_name)
475                {
476                    return Err(eyre!(
477                        "Unknown vertex rule '{}' in vertex selection signature '{}'.",
478                        vertex_rule_name,
479                        value
480                    ));
481                }
482            }
483            Ok(signature)
484        })
485        .collect()
486}
487
488fn parse_particle_signatures(values: &[String], state: &State) -> Result<Vec<ParticleSignature>> {
489    values
490        .iter()
491        .map(|value| ParticleSignature::parse(value, &state.model))
492        .collect()
493}
494
495fn report_selection(selected: &SelectedGraphGroups) {
496    let report = &selected.report;
497    if selected.copied_to_output {
498        info!(
499            "{} {} {} {} {} {} {} {} {} {}",
500            "Selected".blue(),
501            report.kept_master_graphs.len().to_string().green(),
502            "graph groups from".blue(),
503            selected.source_integrand_name.green(),
504            "in process".blue(),
505            selected.source_process_name.green(),
506            "to".blue(),
507            selected.integrand_name.green(),
508            "in process".blue(),
509            selected.process_name.green()
510        );
511    } else {
512        info!(
513            "{} {} {} {} {} {}",
514            "Selected".blue(),
515            report.kept_master_graphs.len().to_string().green(),
516            "graph groups for integrand".blue(),
517            selected.integrand_name.green(),
518            "in process".blue(),
519            selected.process_name.green()
520        );
521    }
522    info!(
523        "{} {}",
524        "Kept master graphs:".blue(),
525        format_graph_name_list(&report.kept_master_graphs).green()
526    );
527    info!(
528        "{} {}",
529        "Removed master graphs:".blue(),
530        format_graph_name_list(&report.removed_master_graphs).red()
531    );
532    if !report.removed_graphs.is_empty()
533        && report.removed_graphs.len() != report.removed_master_graphs.len()
534    {
535        info!(
536            "{} {}",
537            "Removed concrete graphs:".blue(),
538            format_graph_name_list(&report.removed_graphs).red()
539        );
540    }
541}
542
543fn format_graph_name_list(graph_names: &[String]) -> String {
544    match graph_names.len() {
545        0 => "0".to_string(),
546        1..=10 => format!("{} ({})", graph_names.len(), graph_names.join(", ")),
547        len => format!("{len} (list elided; more than 10 graph names)"),
548    }
549}
550
551#[cfg(test)]
552mod tests {
553    use super::*;
554
555    fn empty_select() -> Select {
556        Select {
557            process: None,
558            integrand_name: None,
559            amplitude_graphs: false,
560            with_graph_names: Vec::new(),
561            with_only_graph_names: Vec::new(),
562            without_graph_names: Vec::new(),
563            with_raised_propagator_signatures: Vec::new(),
564            without_raised_propagator_signatures: Vec::new(),
565            with_massive_raised_propagator_signatures: Vec::new(),
566            without_massive_raised_propagator_signatures: Vec::new(),
567            with_massless_raised_propagator_signatures: Vec::new(),
568            without_massless_raised_propagator_signatures: Vec::new(),
569            with_raised_cuts_signatures: Vec::new(),
570            without_raised_cuts_signatures: Vec::new(),
571            with_massive_raised_cuts_signatures: Vec::new(),
572            without_massive_raised_cuts_signatures: Vec::new(),
573            with_massless_raised_cuts_signatures: Vec::new(),
574            without_massless_raised_cuts_signatures: Vec::new(),
575            with_cycle_signatures: Vec::new(),
576            without_cycle_signatures: Vec::new(),
577            with_vertices: Vec::new(),
578            without_vertices: Vec::new(),
579            with_particles: Vec::new(),
580            without_particles: Vec::new(),
581            output_process: None,
582            output_integrand: None,
583            clear_existing_processes: false,
584        }
585    }
586
587    #[test]
588    fn selection_spec_rejects_empty_filters() {
589        let state = State::new_test();
590        let err = empty_select().selection_spec(&state).unwrap_err();
591        assert!(
592            err.to_string()
593                .contains("No graph-group selection filters were provided"),
594            "{err:?}"
595        );
596    }
597
598    #[test]
599    fn selection_spec_accepts_raised_cut_filters() {
600        let state = State::new_test();
601        let mut select = empty_select();
602        select.with_raised_cuts_signatures = vec!["[2,]".to_string()];
603        select.with_massive_raised_cuts_signatures = vec!["[]".to_string()];
604        select.without_massless_raised_cuts_signatures = vec!["[2]".to_string()];
605
606        let spec = select.selection_spec(&state).unwrap();
607        assert!(spec.has_raised_cut_rules());
608    }
609
610    #[test]
611    fn selection_spec_accepts_any_raising_keyword() {
612        let state = State::new_test();
613        let mut select = empty_select();
614        select.without_raised_propagator_signatures = vec!["ANY_RAISING".to_string()];
615        select.without_raised_cuts_signatures = vec!["ANY_RAISING".to_string()];
616
617        let spec = select.selection_spec(&state).unwrap();
618        assert!(spec.has_raised_cut_rules());
619    }
620
621    #[test]
622    fn selection_spec_with_only_graph_names_ignores_other_filters() {
623        let state = State::new_test();
624        let mut select = empty_select();
625        select.amplitude_graphs = true;
626        select.with_only_graph_names = vec!["GL04".to_string(), "GL05".to_string()];
627        select.with_graph_names = vec!["ignored".to_string()];
628        select.without_graph_names = vec!["GL04".to_string()];
629        select.with_raised_propagator_signatures = vec!["not a signature".to_string()];
630
631        let spec = select.selection_spec(&state).unwrap();
632        let expected = GraphGroupSelectionSpec::from_master_graph_names(vec![
633            "GL04".to_string(),
634            "GL05".to_string(),
635        ])
636        .with_mode(GraphGroupSelectionMode::CrossSectionAmplitudeGraphs);
637        assert_eq!(spec, expected);
638    }
639}