1use std::path::PathBuf;
2
3use crate::{
4 commands::CliArgumentMetadataExt,
5 completion::CompletionArgExt,
6 state::{ProcessRef, State},
7 CLISettings,
8};
9use color_eyre::Result;
10use eyre::eyre;
11use gammalooprs::{
12 integrands::process::{
13 ir::{IRProfileSetting, IrLimitTestReport},
14 OrientationProfileMode, ProcessIntegrand,
15 },
16 processes::ProcessCollection,
17 uv::{
18 profile::{ProfileSettings, UVProfileFixedRay, UVProfileable},
19 UVProfileAnalysis,
20 },
21};
22use schemars::JsonSchema;
23use serde::{Deserialize, Serialize};
24use tracing::{info, instrument};
25
26use clap::{Args, Subcommand};
27
28#[derive(Subcommand, Debug, Serialize, Deserialize, Clone, JsonSchema, PartialEq)]
29pub enum Profile {
30 UltraViolet(#[command(flatten)] UltraVioletProfile),
32 #[command(name = "bulk")]
34 InfraRed(#[command(flatten)] InfraRedProfile),
35}
36
37#[derive(Args, Debug, Serialize, Deserialize, Clone, JsonSchema, PartialEq)]
38pub struct UltraVioletProfile {
39 #[arg(
41 short = 'p',
42 long = "process",
43 value_name = "PROCESS",
44 completion_process_selector(crate::completion::SelectorKind::Any)
45 )]
46 pub process: Option<ProcessRef>,
47
48 #[arg(
50 short = 'i',
51 long = "integrand-name",
52 value_name = "NAME",
53 completion_integrand_selector(crate::completion::SelectorKind::Any)
54 )]
55 pub integrand_name: Option<String>,
56
57 #[arg(long = "n-points", default_value_t = 20)]
59 pub n_points: usize,
60
61 #[arg(long = "min-scaling", default_value_t = 3.0)]
63 pub min_scale_exponent: f64,
64
65 #[arg(long = "max-scaling", default_value_t = 6.0)]
67 pub max_scale_exponent: f64,
68
69 #[arg(long = "use_f128")]
71 pub use_f128: bool,
72
73 #[arg(long = "analyse_analytically")]
75 pub analyse_analytically: bool,
76
77 #[arg(long = "per-orientation")]
79 pub per_orientation: bool,
80
81 #[arg(long = "seed")]
83 pub seed: Option<u64>,
84
85 #[arg(
87 long = "uv-ray-directions",
88 num_args = 1..,
89 value_delimiter = ',',
90 allow_negative_numbers = true
91 )]
92 pub uv_ray_directions: Vec<f64>,
93
94 #[arg(
96 long = "uv-ray-norms",
97 num_args = 1..,
98 value_delimiter = ',',
99 allow_negative_numbers = true,
100 cli_requires("uv_ray_directions")
101 )]
102 pub uv_ray_norms: Vec<f64>,
103
104 #[arg(short = 'o', long = "output", value_hint = clap::ValueHint::FilePath)]
106 pub output_file: Option<PathBuf>,
107}
108
109#[derive(Args, Debug, Serialize, Deserialize, Clone, JsonSchema, PartialEq)]
110pub struct InfraRedProfile {
111 #[arg(
113 short = 'p',
114 long = "process",
115 value_name = "PROCESS",
116 completion_process_selector(crate::completion::SelectorKind::CrossSection)
117 )]
118 pub process: Option<ProcessRef>,
119
120 #[arg(
122 short = 'i',
123 long = "integrand-name",
124 value_name = "NAME",
125 completion_integrand_selector(crate::completion::SelectorKind::CrossSection)
126 )]
127 pub integrand_name: Option<String>,
128
129 #[arg(long = "n-points", default_value_t = 20)]
131 pub n_points: usize,
132
133 #[arg(long = "min-scaling", default_value_t = -2.0)]
135 pub min_scale_exponent: f64,
136
137 #[arg(long = "max-scaling", default_value_t = -3.0)]
139 pub max_scale_exponent: f64,
140
141 #[arg(long = "seed")]
143 pub seed: Option<u64>,
144
145 #[arg(short = 'o', long = "output", value_hint = clap::ValueHint::FilePath)]
147 pub output_file: Option<PathBuf>,
148
149 #[arg(short = 's', long = "select")]
151 pub select: Option<String>,
152
153 #[arg(long = "per-orientation")]
155 pub per_orientation: bool,
156
157 #[arg(long = "show-per-cut-info")]
159 pub show_per_cut_info: bool,
160}
161
162impl Default for InfraRedProfile {
163 fn default() -> Self {
164 Self {
165 process: None,
166 integrand_name: None,
167 n_points: 20,
168 min_scale_exponent: -2.0,
169 max_scale_exponent: -3.0,
170 seed: None,
171 output_file: None,
172 select: None,
173 per_orientation: false,
174 show_per_cut_info: false,
175 }
176 }
177}
178impl Default for UltraVioletProfile {
179 fn default() -> Self {
180 Self {
181 process: None,
182 integrand_name: None,
183 n_points: 20,
184 min_scale_exponent: 3.0,
185 max_scale_exponent: 6.0,
186 use_f128: false,
187 analyse_analytically: false,
188 per_orientation: false,
189 seed: None,
190 uv_ray_directions: Vec::new(),
191 uv_ray_norms: Vec::new(),
192 output_file: None,
193 }
194 }
195}
196
197pub enum ProfileResult {
198 UltraViolet(UVProfileAnalysis),
199 InfraRed(IrLimitTestReport),
200}
201
202impl ProfileResult {
203 pub fn unwrap_uv(self) -> UVProfileAnalysis {
204 match self {
205 ProfileResult::UltraViolet(uv_analyis) => uv_analyis,
206 _ => panic!("result does not contain uv profilel analysis data"),
207 }
208 }
209
210 pub fn unwrap_ir(self) -> IrLimitTestReport {
211 match self {
212 ProfileResult::InfraRed(ir_analyis) => ir_analyis,
213 _ => panic!("result does not contain ir profile analysis data"),
214 }
215 }
216}
217
218impl Profile {
219 #[instrument(skip_all)]
220 #[allow(clippy::needless_update)]
221 pub fn run(
222 &self,
223 state: &mut State,
224 _global_cli_settings: &CLISettings,
225 ) -> Result<ProfileResult> {
226 match self {
227 Profile::UltraViolet(UltraVioletProfile {
228 process,
229 integrand_name,
230 n_points,
231 min_scale_exponent,
232 max_scale_exponent,
233 use_f128,
234 seed,
235 uv_ray_directions,
236 uv_ray_norms,
237 analyse_analytically,
238 per_orientation,
239 output_file,
240 }) => {
241 let (process_id, integrand_name) =
242 state.find_integrand_ref(process.as_ref(), integrand_name.as_ref())?;
243 let model = state.resolve_model_for_integrand(process_id, &integrand_name)?;
244 let default_uv_ray_norm = {
245 let process = &mut state.process_list.processes[process_id];
246 match &mut process.collection {
247 ProcessCollection::Amplitudes(amplitudes) => {
248 let amplitude =
249 amplitudes.get_mut(&integrand_name).ok_or_else(|| {
250 eyre!(
251 "No amplitude named '{}' in process '{}'",
252 integrand_name,
253 process.definition.folder_name
254 )
255 })?;
256 let integrand = amplitude.integrand.as_mut().ok_or(eyre!(
257 "Integrand {} has not yet been generated, but exists",
258 amplitude.name
259 ))?;
260 integrand.warm_up(&model)?;
261 integrand.get_settings().kinematics.e_cm
262 }
263 ProcessCollection::CrossSections(cross_sections) => {
264 let cross_section =
265 cross_sections.get_mut(&integrand_name).ok_or_else(|| {
266 eyre!(
267 "No cross section named '{}' in process '{}'",
268 integrand_name,
269 process.definition.folder_name
270 )
271 })?;
272 let integrand = cross_section.integrand.as_mut().ok_or(eyre!(
273 "Integrand {} has not yet been generated, but exists",
274 cross_section.name
275 ))?;
276 integrand.warm_up(&model)?;
277 integrand.get_settings().kinematics.e_cm
278 }
279 }
280 };
281
282 let fixed_uv_ray = if uv_ray_directions.is_empty() {
283 None
284 } else {
285 let uv_ray_norms = if uv_ray_norms.is_empty() {
286 vec![default_uv_ray_norm]
287 } else {
288 uv_ray_norms.clone()
289 };
290 Some(UVProfileFixedRay::from_flat_components(
291 uv_ray_directions,
292 &uv_ray_norms,
293 )?)
294 };
295
296 let profile_settings = ProfileSettings {
297 n_points: *n_points,
298 min_scale_exponent: *min_scale_exponent,
299 max_scale_exponent: *max_scale_exponent,
300 seed: (*seed).unwrap_or(42),
301 use_f128: *use_f128,
302 analyse_analytically: *analyse_analytically,
303 orientation_mode: if *per_orientation {
304 OrientationProfileMode::PerOrientation
305 } else {
306 OrientationProfileMode::Summed
307 },
308 fixed_uv_ray,
309 ..Default::default()
310 };
311 let profile_res = {
312 let process = &mut state.process_list.processes[process_id];
313 match &mut process.collection {
314 ProcessCollection::Amplitudes(amplitudes) => amplitudes
315 .get_mut(&integrand_name)
316 .ok_or_else(|| {
317 eyre!(
318 "No amplitude named '{}' in process '{}'",
319 integrand_name,
320 process.definition.folder_name
321 )
322 })?
323 .profile(&model, &profile_settings)?,
324 ProcessCollection::CrossSections(cross_sections) => cross_sections
325 .get_mut(&integrand_name)
326 .ok_or_else(|| {
327 eyre!(
328 "No cross section named '{}' in process '{}'",
329 integrand_name,
330 process.definition.folder_name
331 )
332 })?
333 .profile(&model, &profile_settings)?,
334 }
335 }
336 .analyse();
337
338 for t in profile_res.tables_per_graph(-0.9) {
339 info!("\n{}", t);
340 }
341
342 for t in profile_res.analytic_tables_per_graph() {
343 let Some(t) = t else {
344 continue;
345 };
346 info!("\n{}", t);
347 }
348
349 for t in profile_res.per_orientation_tables_per_graph(-0.9) {
350 let Some(t) = t else {
351 continue;
352 };
353 info!("\n{}", t);
354 }
355
356 if let Some(file) = output_file {
357 profile_res.write_profile_data(file)?
358 }
359
360 Ok(ProfileResult::UltraViolet(profile_res))
361 }
362 Profile::InfraRed(InfraRedProfile {
363 process,
364 integrand_name,
365 n_points,
366 min_scale_exponent,
367 max_scale_exponent,
368 seed,
369 output_file: _,
370 select,
371 per_orientation,
372 show_per_cut_info,
373 }) => {
374 let ir_profile_settings = IRProfileSetting {
375 lambda_exp_start: *min_scale_exponent,
376 lambda_exp_end: *max_scale_exponent,
377 steps: *n_points,
378 seed: seed.unwrap_or(420),
379 select_limits_and_graphs: select.clone(),
380 orientation_mode: if *per_orientation {
381 OrientationProfileMode::PerOrientation
382 } else {
383 OrientationProfileMode::Summed
384 },
385 show_per_cut_info: *show_per_cut_info,
386 };
387
388 let (process_id, integrand_name) =
389 state.find_integrand_ref(process.as_ref(), integrand_name.as_ref())?;
390
391 let model = state.resolve_model_for_integrand(process_id, &integrand_name)?;
392
393 let process = &mut state.process_list.processes[process_id];
394 let integrand = match &mut process.collection {
395 ProcessCollection::Amplitudes(amplitudes) => amplitudes
396 .get_mut(&integrand_name)
397 .ok_or_else(|| {
398 eyre!(
399 "No amplitude named '{}' in process '{}'",
400 integrand_name,
401 process.definition.folder_name
402 )
403 })?
404 .integrand
405 .as_mut(),
406 ProcessCollection::CrossSections(xs) => xs
407 .get_mut(&integrand_name)
408 .ok_or_else(|| {
409 eyre!(
410 "No xs named '{}' in process '{}'",
411 integrand_name,
412 process.definition.folder_name
413 )
414 })?
415 .integrand
416 .as_mut(),
417 };
418
419 let profile_result = match integrand.ok_or(eyre!(
420 "Integrand {} has not yet been generated",
421 integrand_name
422 ))? {
423 ProcessIntegrand::CrossSection(cross_section_integrand) => {
424 cross_section_integrand.test_ir(&ir_profile_settings, &model)?
425 }
426 ProcessIntegrand::Amplitude(amplitude_integrand) => {
427 amplitude_integrand.test_ir(&ir_profile_settings, &model)?
428 }
429 };
430
431 info!("\n{}", profile_result);
432 Ok(ProfileResult::InfraRed(profile_result))
433 }
434 }
435 }
436}