Skip to main content

gammaloop_api/commands/
inspect.rs

1use clap::Args;
2use colored::Colorize;
3use ndarray::Array2;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6use spenso::algebra::complex::Complex;
7use tracing::info;
8
9use crate::{
10    commands::evaluate_samples::{evaluate_sample, EvaluateSamples},
11    commands::CliArgumentMetadataExt,
12    completion::CompletionArgExt,
13    state::{ProcessRef, State},
14};
15use color_eyre::Result;
16use eyre::eyre;
17use gammalooprs::utils::F;
18
19#[derive(Debug, Args, Serialize, Deserialize, Clone, JsonSchema, PartialEq, Default)]
20pub struct Inspect {
21    /// Process reference: `#<id>`, `name:<name>`, or `<id>/<name>`
22    #[arg(
23        short = 'p',
24        long = "process",
25        value_name = "PROCESS",
26        completion_process_selector(crate::completion::SelectorKind::Any)
27    )]
28    pub process: Option<ProcessRef>,
29    /// The integrand name to inspect
30    #[arg(
31        short = 'i',
32        long = "integrand-name",
33        value_name = "NAME",
34        completion_integrand_selector(crate::completion::SelectorKind::Any)
35    )]
36    pub integrand_name: Option<String>,
37    /// Integration coordinates, or flattened loop momenta `(px, py, pz) ...` with `--momentum-space`
38    #[arg(
39        short = 'x',
40        long = "point",
41        num_args = 2..,
42        value_name = "POINT",
43        value_delimiter = ',',
44        allow_negative_numbers = true,
45    )]
46    pub point: Vec<f64>,
47
48    /// Force arbitrary-precision (Arb) internal evaluation; output remains f64
49    #[arg(short = 'f', long = "use_arb_prec")]
50    pub use_arb_prec: bool,
51
52    /// Interpret `--point` as spatial loop-momentum triplets; energy components are not accepted
53    #[arg(short = 'm', long)]
54    pub momentum_space: bool,
55
56    /// The discrete dimensions of the sample
57    #[arg(
58        short = 'd',
59        long = "discrete-dim",
60        value_name = "DIMS",
61        num_args = 1..,
62        value_delimiter = ',',
63        conflicts_with_all = ["graph_id", "orientation_id"],
64    )]
65    pub discrete_dim: Vec<usize>,
66
67    /// Select a specific graph in momentum-space inspect
68    #[arg(long = "graph-id", value_name = "GRAPH_ID")]
69    pub graph_id: Option<usize>,
70
71    /// Select a specific orientation of the selected graph in momentum-space inspect
72    #[arg(
73        long = "orientation-id",
74        value_name = "ORIENTATION_ID",
75        cli_requires("graph_id"),
76        conflicts_with = "discrete_dim"
77    )]
78    pub orientation_id: Option<usize>,
79}
80
81impl Inspect {
82    fn validate_selector_mode(&self) -> Result<()> {
83        if !self.momentum_space && (self.graph_id.is_some() || self.orientation_id.is_some()) {
84            return Err(eyre!(
85                "Graph and orientation selectors are only supported in momentum-space inspect."
86            ));
87        }
88        Ok(())
89    }
90
91    fn resolve_graph_name(
92        &self,
93        state: &mut State,
94        process_id: usize,
95        integrand_name: &str,
96    ) -> Result<Option<String>> {
97        let Some(graph_id) = self.graph_id else {
98            return Ok(None);
99        };
100        let integrand = state
101            .process_list
102            .get_integrand_mut(process_id, integrand_name)?;
103        let graph_name = integrand.graph_name_by_id(graph_id).ok_or_else(|| {
104            eyre!(
105                "Graph id {} is out of range for integrand '{}'; it has {} graphs.",
106                graph_id,
107                integrand_name,
108                integrand.graph_count()
109            )
110        })?;
111        Ok(Some(graph_name.to_string()))
112    }
113
114    fn validate_x_space_point(
115        &self,
116        state: &mut State,
117        process_id: usize,
118        integrand_name: &str,
119    ) -> Result<()> {
120        let integrand = state
121            .process_list
122            .get_integrand_mut(process_id, integrand_name)?;
123        let expected_dimension = integrand.expected_x_space_dimension(&self.discrete_dim)?;
124        if self.point.len() != expected_dimension {
125            return Err(eyre!(
126                "Expected {} x-space coordinates for this integrand selection, got {}.",
127                expected_dimension,
128                self.point.len()
129            ));
130        }
131        Ok(())
132    }
133
134    pub fn run(&self, state: &mut State) -> Result<(Option<f64>, Complex<f64>)> {
135        let (process_id, integrand_name) =
136            state.find_integrand_ref(self.process.as_ref(), self.integrand_name.as_ref())?;
137        self.validate_selector_mode()?;
138        if !self.momentum_space {
139            self.validate_x_space_point(state, process_id, &integrand_name)?;
140        }
141        let graph_name = self.resolve_graph_name(state, process_id, &integrand_name)?;
142        let show_detailed_output = state
143            .process_list
144            .get_integrand_mut(process_id, &integrand_name)?
145            .get_settings()
146            .general
147            .generate_events;
148        let points = Array2::from_shape_vec((1, self.point.len()), self.point.clone())?;
149        let discrete_dims =
150            Array2::from_shape_vec((1, self.discrete_dim.len()), self.discrete_dim.clone())?;
151        let result = evaluate_sample(
152            state,
153            &EvaluateSamples {
154                process_id: Some(process_id),
155                integrand_name: Some(integrand_name.clone()),
156                use_arb_prec: self.use_arb_prec,
157                minimal_output: !show_detailed_output,
158                return_generated_events: None,
159                momentum_space: self.momentum_space,
160                points: points.view(),
161                integrator_weights: None,
162                discrete_dims: Some(discrete_dims.view()),
163                graph_names: graph_name.map(|name| vec![Some(name)]),
164                orientations: self
165                    .orientation_id
166                    .map(|orientation| vec![Some(orientation)]),
167            },
168        )?;
169        let evaluation = &result.sample.evaluation;
170
171        let raw_result = evaluation.integrand_result;
172        let jacobian = evaluation.parameterization_jacobian.map(|jac| jac.0);
173        let displayed_result = if self.momentum_space {
174            raw_result
175        } else {
176            let jacobian = jacobian.ok_or_else(|| {
177                eyre!("x-space inspect requires a parameterization jacobian, but none was returned")
178            })?;
179            raw_result.map(|entry| entry * F(jacobian))
180        };
181
182        let point_label = if self.momentum_space {
183            "Input point in momentum space"
184        } else {
185            "Input point in unit hypercube xs"
186        };
187        info!(
188            "\n{}:\n\n{}\n\nThe evaluation of integrand '{}' is:\n\n{}\n",
189            point_label,
190            format!(
191                "( {} )",
192                self.point
193                    .iter()
194                    .map(|x| format!("{x:.16}"))
195                    .collect::<Vec<_>>()
196                    .join(", ")
197            )
198            .blue(),
199            integrand_name.green(),
200            format!(
201                "( {:+.16e}, {:+.16e} i)",
202                displayed_result.re, displayed_result.im
203            )
204            .blue(),
205        );
206
207        if let Some(jacobian) = jacobian {
208            info!(
209                "Parameterization jacobian for this point: {:+.16e}",
210                jacobian
211            );
212        }
213        if show_detailed_output {
214            info!("\n{}", result);
215        }
216
217        Ok((jacobian, displayed_result.map(|entry| entry.0)))
218    }
219}