gammaloop_api/commands/
evaluate.rs1use std::{fs, path::PathBuf};
2
3use clap::Args;
4use gammalooprs::processes::{AmplitudeGraph, AnalyticalEvaluationConfig};
5
6use gammalooprs::processes::{Amplitude, ProcessCollection};
7
8use gammalooprs::utils::vakint;
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12use color_eyre::Result;
13use colored::Colorize;
14use gammalooprs::settings::RuntimeSettings;
15use symbolica::atom::{Atom, AtomCore};
16use tracing::{info, warn};
17
18use crate::{
19 completion::CompletionArgExt,
20 state::{ProcessRef, State},
21 CLISettings,
22};
23
24#[cfg_attr(
25 feature = "python_api",
26 pyo3::pyclass(from_py_object, unsendable, name = "EvaluationSettings")
27)]
28#[derive(Debug, Args, Serialize, Deserialize, Clone, JsonSchema, PartialEq)]
29pub struct Evaluate {
30 #[arg(
32 short = 'p',
33 long = "process",
34 value_name = "PROCESS",
35 completion_process_selector(crate::completion::SelectorKind::Amplitude)
36 )]
37 pub process: Option<ProcessRef>,
38
39 #[arg(
41 short = 'i',
42 long = "integrand-name",
43 value_name = "NAME",
44 completion_integrand_selector(crate::completion::SelectorKind::Amplitude)
45 )]
46 pub graphs_group_name: Option<String>,
47
48 #[arg(short = 'o', long, value_hint = clap::ValueHint::FilePath)]
50 pub result_path: Option<PathBuf>,
51
52 #[arg(short = 'm', long = "numerical")]
54 pub numerical: bool,
55
56 #[arg(short = 'e', long = "n-epsilon-terms")]
59 pub number_of_terms_in_epsilon_expansion: Option<usize>,
60}
61
62impl Evaluate {
63 pub fn run(
64 &self,
65 state: &mut State,
66 global_cli_settings: &CLISettings,
67 default_runtime_settings: &RuntimeSettings,
68 ) -> Result<Atom> {
69 let (process_id, integrand_name) =
70 state.find_integrand_ref(self.process.as_ref(), self.graphs_group_name.as_ref())?;
71
72 let amplitude: &Amplitude = match &state.process_list.processes[process_id].collection {
73 ProcessCollection::Amplitudes(amplitudes) => amplitudes.get(&integrand_name).unwrap(),
74 ProcessCollection::CrossSections(_) => {
75 return Err(color_eyre::eyre::eyre!(
76 "Evaluate command does not support cross-section graphs"
77 ));
78 }
79 };
80 let model = state.resolve_model_for_integrand(process_id, &integrand_name)?;
83
84 let mut true_settings = global_cli_settings
85 .global
86 .generation
87 .uv
88 .vakint
89 .true_settings();
90 let refresh_model_values = amplitude.integrand.is_some();
91
92 let vakint = vakint()?;
93
94 if let Some(n_terms) = self.number_of_terms_in_epsilon_expansion {
95 true_settings.number_of_terms_in_epsilon_expansion = n_terms as i64;
96 }
97
98 let mut full_evaluation = Atom::Zero;
99
100 for graph_term in amplitude.graphs.iter() {
101 let g = &graph_term.graph;
102 let mut complete_evaluation_for_this_graph = Atom::num(1);
103 if g.n_externals() != 0 {
104 return Err(color_eyre::eyre::eyre!(
105 "Graph named: {} has external legs. Analytical evaluation in gammaloop only supports vacuum graphs.",
106 graph_term.graph.name
107 ));
108 }
109 let connected_components = g.connected_components(&g.full_filter());
110 if connected_components.len() > 1 && g.global_prefactor.num != Atom::num(1) {
111 warn!(
112 "Graph named: {} has more than one ({}) connected components and its expression contains a global numerator ({}), which will only be applied to the first connected component. Make sure this is intended.",
113 graph_term.graph.name,
114 connected_components.len(),
115 g.global_prefactor.num
116 );
117 }
118 for (i_gc, gc) in connected_components.iter().enumerate() {
119 info!(
120 "Evaluating connected component #{} of graph named: {}",
121 i_gc + 1,
122 graph_term.graph.name.blue()
123 );
124 complete_evaluation_for_this_graph *= graph_term.analytical_evaluation(
125 gc,
126 AnalyticalEvaluationConfig {
127 model: &model,
128 refresh_model_values,
129 evaluate_numerically: self.numerical,
130 vakint,
131 true_settings: &true_settings,
132 settings: &global_cli_settings.global.generation.uv.vakint,
133 run_time_settings: default_runtime_settings,
134 include_global_numerator: i_gc == 0,
136 },
137 )?;
138 }
139 complete_evaluation_for_this_graph *= &g.global_prefactor.projector * &g.overall_factor;
140
141 full_evaluation += complete_evaluation_for_this_graph;
142 }
143
144 if let Some(p) = self.result_path.clone() {
145 info!(
146 "Saving evaluation result for process {} to path: {}",
147 state.process_list.processes[process_id]
148 .definition
149 .folder_name
150 .green(),
151 p.display()
152 );
153 fs::write(
154 p,
155 toml::to_string_pretty(&full_evaluation.to_canonical_string())?,
156 )?;
157 } else if self.numerical {
158 let numerical_evaluation_result =
159 AmplitudeGraph::to_numerical(full_evaluation.as_view(), &true_settings)?;
160 info!(
161 "Numerical evaluation of the analytical result for process {}:\n{}",
162 state.process_list.processes[process_id]
163 .definition
164 .folder_name
165 .green(),
166 numerical_evaluation_result
167 );
168 } else {
169 info!(
170 "Analytical result for process {}:\n{}",
171 state.process_list.processes[process_id]
172 .definition
173 .folder_name
174 .green(),
175 full_evaluation
176 );
177 }
178
179 Ok(full_evaluation)
180 }
181}