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 #[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 #[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 #[arg(long = "amplitude-graphs", default_value_t = false)]
45 pub amplitude_graphs: bool,
46
47 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[arg(
238 long = "output_process",
239 value_name = "NAME",
240 completion_disable_special_value()
241 )]
242 pub output_process: Option<String>,
243
244 #[arg(
246 long = "output_integrand",
247 value_name = "NAME",
248 completion_disable_special_value()
249 )]
250 pub output_integrand: Option<String>,
251
252 #[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}