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 #[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 #[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 #[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 #[arg(short = 'f', long = "use_arb_prec")]
50 pub use_arb_prec: bool,
51
52 #[arg(short = 'm', long)]
54 pub momentum_space: bool,
55
56 #[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 #[arg(long = "graph-id", value_name = "GRAPH_ID")]
69 pub graph_id: Option<usize>,
70
71 #[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}