1use std::fmt;
2
3use bincode_trait_derive::{Decode, Encode};
4use eyre::{Result as EyreResult, eyre};
5use schemars::JsonSchema;
6use serde::{Deserialize, Deserializer, Serialize};
7use symbolica::prelude::*;
8
9use crate::{
10 GammaLoopContext,
11 cff::orientations::GraphOrientation,
12 processes::EvaluatorSettings,
13 utils::{
14 DEFAULT_ESURFACE_EXISTENCE_THRESHOLD, GS, W_,
15 serde_utils::{
16 IsDefault, deserialize_nonnegative_finite_f64, is_default_esurface_existence_threshold,
17 is_false, is_float, is_true, is_usize, show_defaults_helper,
18 },
19 symbolica_ext::StringSerializedAtom,
20 },
21 uv::UVgenerationSettings,
22};
23
24#[cfg_attr(
25 feature = "python_api",
26 pyo3::pyclass(from_py_object, get_all, set_all)
27)]
28#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, JsonSchema)]
29#[trait_decode(trait = GammaLoopContext)]
30#[serde(default, deny_unknown_fields)]
31#[derive(Default)]
32pub struct GenerationSettings {
33 #[serde(skip_serializing_if = "IsDefault::is_default")]
36 pub evaluator: EvaluatorSettings,
37 #[serde(skip_serializing_if = "IsDefault::is_default")]
39 pub feyngen: FeyGenSettings,
40
41 #[serde(skip_serializing_if = "IsDefault::is_default")]
43 pub orientation_pattern: OrientationPattern,
44 #[serde(skip_serializing_if = "IsDefault::is_default")]
46 pub compile: GammaloopCompileOptions,
47 #[serde(skip_serializing_if = "IsDefault::is_default")]
49 pub tropical_subgraph_table: TropicalSubgraphTableSettings,
50 #[serde(skip_serializing_if = "IsDefault::is_default")]
52 pub threshold_subtraction: ThresholdSubtractionSettings,
53 #[serde(skip_serializing_if = "IsDefault::is_default")]
55 pub vector_polarization_sum_gauge: VectorPolarizationSumGauge,
56 #[serde(skip_serializing_if = "IsDefault::is_default")]
58 pub uv: UVgenerationSettings,
59 #[serde(skip_serializing_if = "IsDefault::is_default")]
61 pub force_cuts: Vec<Vec<String>>,
62 #[serde(skip_serializing_if = "is_false")]
64 pub override_lmb_heuristics: bool,
65}
66
67#[cfg_attr(
68 feature = "python_api",
69 pyo3::pyclass(from_py_object, get_all, set_all)
70)]
71#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, JsonSchema)]
72#[trait_decode(trait = GammaLoopContext)]
73#[serde(default, deny_unknown_fields)]
74pub struct ThresholdSubtractionSettings {
75 #[serde(skip_serializing_if = "is_true")]
77 pub enable_thresholds: bool,
78 #[serde(skip_serializing_if = "is_false")]
80 pub check_esurface_at_generation: bool,
81 #[serde(
84 deserialize_with = "deserialize_nonnegative_finite_f64",
85 skip_serializing_if = "is_default_esurface_existence_threshold"
86 )]
87 #[schemars(range(min = 0.0))]
88 pub esurface_existence_threshold: f64,
89 #[serde(skip_serializing_if = "is_true")]
91 pub skip_thresholds_that_are_cuts: bool,
92 #[serde(skip_serializing_if = "is_false")]
94 pub disable_integrated_ct: bool,
95 #[serde(skip_serializing_if = "is_true")]
97 pub assume_positive_external_energies: bool,
98}
99
100impl Default for ThresholdSubtractionSettings {
101 fn default() -> Self {
102 Self {
103 enable_thresholds: true,
104 check_esurface_at_generation: false,
105 esurface_existence_threshold: DEFAULT_ESURFACE_EXISTENCE_THRESHOLD,
106 skip_thresholds_that_are_cuts: true,
107 disable_integrated_ct: false,
108 assume_positive_external_energies: true,
109 }
110 }
111}
112
113#[derive(
114 Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode, PartialEq, Eq, JsonSchema, Default,
115)]
116#[cfg_attr(feature = "python_api", pyo3::pyclass(from_py_object))]
117pub enum VectorPolarizationSumGauge {
118 #[serde(rename = "Feynman", alias = "feynman")]
119 Feynman,
120 #[default]
121 #[serde(rename = "LightLikeAxial", alias = "light_like_axial")]
122 LightLikeAxial,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, Eq, Copy, JsonSchema)]
126#[cfg_attr(feature = "python_api", pyo3::pyclass(from_py_object))]
127#[serde(deny_unknown_fields)]
128#[derive(Default)]
129pub enum CompilationOptimizationLevel {
130 O0,
131 O1,
132 #[default]
133 O2,
134 O3,
135}
136
137impl From<CompilationOptimizationLevel> for usize {
138 fn from(value: CompilationOptimizationLevel) -> Self {
139 match value {
140 CompilationOptimizationLevel::O0 => 0,
141 CompilationOptimizationLevel::O1 => 1,
142 CompilationOptimizationLevel::O2 => 2,
143 CompilationOptimizationLevel::O3 => 3,
144 }
145 }
146}
147
148impl fmt::Display for CompilationOptimizationLevel {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 match self {
151 CompilationOptimizationLevel::O0 => f.write_str("O0"),
152 CompilationOptimizationLevel::O1 => f.write_str("O1"),
153 CompilationOptimizationLevel::O2 => f.write_str("O2"),
154 CompilationOptimizationLevel::O3 => f.write_str("O3"),
155 }
156 }
157}
158
159pub const fn yes() -> bool {
160 true
161}
162
163#[cfg(target_vendor = "apple")]
164pub const fn default_external_compiler() -> &'static str {
165 "clang++"
166}
167
168#[cfg(not(target_vendor = "apple"))]
169pub const fn default_external_compiler() -> &'static str {
170 "g++"
171}
172
173pub fn default_external_compiler_owned() -> String {
174 default_external_compiler().to_owned()
175}
176
177pub fn is_default_external_compiler(compiler: &str) -> bool {
178 show_defaults_helper(default_external_compiler() == compiler)
179}
180
181#[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode, PartialEq, Eq, JsonSchema)]
182#[cfg_attr(
183 feature = "python_api",
184 pyo3::pyclass(from_py_object, get_all, set_all)
185)]
186#[derive(Default)]
187pub enum CompilationMode {
188 #[serde(rename = "c++", alias = "cpp")]
189 Cpp,
190 #[serde(rename = "assembly")]
191 Assembly,
192 #[default]
193 #[serde(rename = "symjit")]
194 Symjit,
195}
196
197impl fmt::Display for CompilationMode {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 match self {
200 CompilationMode::Cpp => f.write_str("c++"),
201 CompilationMode::Assembly => f.write_str("assembly"),
202 CompilationMode::Symjit => f.write_str("symjit"),
203 }
204 }
205}
206
207#[derive(
208 Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, Eq, JsonSchema, Default,
209)]
210#[cfg_attr(
211 feature = "python_api",
212 pyo3::pyclass(from_py_object, get_all, set_all)
213)]
214#[serde(default, deny_unknown_fields)]
215pub struct ExternalCompilationOptionsSnapshot {
216 #[serde(skip_serializing_if = "IsDefault::is_default")]
217 pub optimization_level: CompilationOptimizationLevel,
218 #[serde(skip_serializing_if = "is_true")]
219 pub fast_math: bool,
220 #[serde(skip_serializing_if = "is_true")]
221 pub unsafe_math: bool,
222 #[serde(skip_serializing_if = "is_default_external_compiler")]
223 pub compiler: String,
224 #[serde(skip_serializing_if = "IsDefault::is_default")]
225 pub custom: Vec<String>,
226}
227
228impl fmt::Display for ExternalCompilationOptionsSnapshot {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 write!(
231 f,
232 "{} fast_math={} unsafe_math={} compiler={} custom={}",
233 self.optimization_level,
234 self.fast_math,
235 self.unsafe_math,
236 self.compiler,
237 if self.custom.is_empty() {
238 "[]".to_string()
239 } else {
240 format!("[{}]", self.custom.join(", "))
241 }
242 )
243 }
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, Eq, JsonSchema)]
247pub enum FrozenCompilationMode {
248 Eager,
249 Symjit(CompilationOptimizationLevel),
250 Cpp(ExternalCompilationOptionsSnapshot),
251 Assembly(ExternalCompilationOptionsSnapshot),
252}
253
254impl FrozenCompilationMode {
255 pub fn compile_enabled(&self) -> bool {
256 !matches!(self, FrozenCompilationMode::Eager)
257 }
258
259 pub fn active_backend_name(&self) -> &'static str {
260 match self {
261 FrozenCompilationMode::Eager => "eager",
262 FrozenCompilationMode::Symjit(_) => "symjit",
263 FrozenCompilationMode::Cpp(_) => "c++",
264 FrozenCompilationMode::Assembly(_) => "assembly",
265 }
266 }
267
268 pub fn external_options(&self) -> Option<&ExternalCompilationOptionsSnapshot> {
269 match self {
270 FrozenCompilationMode::Cpp(options) | FrozenCompilationMode::Assembly(options) => {
271 Some(options)
272 }
273 FrozenCompilationMode::Eager | FrozenCompilationMode::Symjit(_) => None,
274 }
275 }
276
277 pub fn requires_external_compilation(&self) -> bool {
278 matches!(
279 self,
280 FrozenCompilationMode::Cpp(_) | FrozenCompilationMode::Assembly(_)
281 )
282 }
283
284 pub(crate) fn export_settings(&self) -> ExportSettings {
285 ExportSettings::new().inline_asm(match self {
286 FrozenCompilationMode::Assembly(_) => InlineASM::default(),
287 FrozenCompilationMode::Cpp(_)
288 | FrozenCompilationMode::Symjit(_)
289 | FrozenCompilationMode::Eager => InlineASM::None,
290 })
291 }
292
293 pub fn to_symbolica_compile_options(&self) -> Option<CompileOptions> {
294 let options = self.external_options()?;
295 Some(
296 CompileOptions::new()
297 .optimization_level(options.optimization_level.into())
298 .fast_math(options.fast_math)
299 .unsafe_math(options.unsafe_math)
300 .compiler(options.compiler.clone())
301 .args(options.custom.clone()),
302 )
303 }
304}
305
306impl fmt::Display for FrozenCompilationMode {
307 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
308 match self {
309 FrozenCompilationMode::Eager => f.write_str("eager"),
310 FrozenCompilationMode::Symjit(level) => write!(f, "symjit ({level})"),
311 FrozenCompilationMode::Cpp(options) => write!(f, "c++ ({options})"),
312 FrozenCompilationMode::Assembly(options) => write!(f, "assembly ({options})"),
313 }
314 }
315}
316
317#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, JsonSchema)]
318#[cfg_attr(
319 feature = "python_api",
320 pyo3::pyclass(from_py_object, get_all, set_all)
321)]
322#[serde(default, deny_unknown_fields)]
323pub struct GammaloopCompileOptions {
324 #[serde(skip_serializing_if = "IsDefault::is_default")]
326 pub compilation_mode: CompilationMode,
327
328 #[serde(skip_serializing_if = "IsDefault::is_default")]
330 pub optimization_level: CompilationOptimizationLevel,
331
332 #[serde(skip_serializing_if = "is_true")]
334 pub fast_math: bool,
335
336 #[serde(skip_serializing_if = "is_true")] pub unsafe_math: bool,
339
340 #[serde(skip_serializing_if = "is_default_external_compiler")] pub compiler: String,
343 #[serde(skip_serializing_if = "IsDefault::is_default")]
345 pub custom: Vec<String>,
346}
347
348impl Default for GammaloopCompileOptions {
349 fn default() -> Self {
350 Self {
351 compilation_mode: CompilationMode::Symjit,
352 optimization_level: CompilationOptimizationLevel::O2,
353 fast_math: true,
354 unsafe_math: true,
355 compiler: default_external_compiler_owned(),
356 custom: vec![],
357 }
358 }
359}
360
361impl GammaloopCompileOptions {
362 pub fn external_options_snapshot(&self) -> ExternalCompilationOptionsSnapshot {
363 ExternalCompilationOptionsSnapshot {
364 optimization_level: self.optimization_level,
365 fast_math: self.fast_math,
366 unsafe_math: self.unsafe_math,
367 compiler: self.compiler.clone(),
368 custom: self.custom.clone(),
369 }
370 }
371
372 pub fn requires_external_compilation(&self) -> bool {
373 matches!(
374 self.compilation_mode,
375 CompilationMode::Cpp | CompilationMode::Assembly
376 )
377 }
378
379 pub fn frozen_mode(&self, evaluator_settings: &EvaluatorSettings) -> FrozenCompilationMode {
380 if !evaluator_settings.compile {
381 return FrozenCompilationMode::Eager;
382 }
383
384 match self.compilation_mode {
385 CompilationMode::Cpp => FrozenCompilationMode::Cpp(self.external_options_snapshot()),
386 CompilationMode::Assembly => {
387 FrozenCompilationMode::Assembly(self.external_options_snapshot())
388 }
389 CompilationMode::Symjit => FrozenCompilationMode::Symjit(self.optimization_level),
390 }
391 }
392
393 pub fn to_symbolica_compile_options(&self) -> CompileOptions {
394 CompileOptions::new()
395 .optimization_level(self.optimization_level.into())
396 .fast_math(self.fast_math)
397 .unsafe_math(self.unsafe_math)
398 .compiler(self.compiler.clone())
399 .args(self.custom.clone())
400 }
401}
402
403#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, JsonSchema)]
404#[cfg_attr(
405 feature = "python_api",
406 pyo3::pyclass(from_py_object, get_all, set_all)
407)]
408#[serde(default, deny_unknown_fields)]
409#[derive(Default)]
410pub struct FeyGenSettings {
411 #[serde(skip_serializing_if = "is_false")]
413 pub gamma_simplification_closure_check: bool,
414}
415
416#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, JsonSchema)]
417#[cfg_attr(
418 feature = "python_api",
419 pyo3::pyclass(from_py_object, get_all, set_all)
420)]
421#[serde(default, deny_unknown_fields)]
422pub struct TropicalSubgraphTableSettings {
423 #[serde(skip_serializing_if = "is_false")]
425 pub panic_on_fail: bool,
426 #[serde(skip_serializing_if = "is_float::<1>")] pub target_omega: f64,
429 #[serde(skip_serializing_if = "is_false")]
431 pub disable_tropical_generation: bool,
432}
433
434impl Default for TropicalSubgraphTableSettings {
435 fn default() -> Self {
436 Self {
437 panic_on_fail: false,
438 target_omega: 1.0,
439 disable_tropical_generation: false,
440 }
441 }
442}
443
444#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, Default, PartialEq, JsonSchema)]
445#[trait_decode(trait = GammaLoopContext)]
446#[cfg_attr(feature = "python_api", pyo3::pyclass(from_py_object))]
447#[serde(default, deny_unknown_fields)]
448pub struct OrientationPattern {
449 #[serde(
451 default,
452 skip_serializing_if = "IsDefault::is_default",
453 deserialize_with = "deserialize_orientation_pattern_atom"
454 )]
455 pub pat: Option<StringSerializedAtom>,
456}
457
458fn deserialize_orientation_pattern_atom<'de, D>(
459 deserializer: D,
460) -> std::result::Result<Option<StringSerializedAtom>, D::Error>
461where
462 D: Deserializer<'de>,
463{
464 let raw = Option::<String>::deserialize(deserializer)?;
465 raw.map(|value| OrientationPattern::parse_user_pattern(&value))
466 .transpose()
467 .map(|parsed| parsed.map(StringSerializedAtom))
468 .map_err(serde::de::Error::custom)
469}
470
471impl From<Atom> for OrientationPattern {
472 fn from(value: Atom) -> Self {
473 OrientationPattern {
474 pat: Some(StringSerializedAtom(value)),
475 }
476 }
477}
478
479impl OrientationPattern {
480 fn is_orientation_delta_atom(atom: &Atom) -> bool {
481 matches!(
482 atom.as_view(),
483 AtomView::Fun(function)
484 if function.get_symbol().get_stripped_name() == "orientation_delta"
485 )
486 }
487
488 fn split_top_level_args(input: &str) -> EyreResult<Vec<String>> {
489 let mut args = Vec::new();
490 let mut start = 0usize;
491 let mut depth = 0usize;
492
493 for (index, ch) in input.char_indices() {
494 match ch {
495 '(' | '[' | '{' => depth += 1,
496 ')' | ']' | '}' => {
497 if depth == 0 {
498 return Err(eyre!(
499 "Unbalanced delimiter in orientation pattern: {input}"
500 ));
501 }
502 depth -= 1;
503 }
504 ',' if depth == 0 => {
505 let arg = input[start..index].trim();
506 if arg.is_empty() {
507 return Err(eyre!("Empty orientation-pattern entry in pattern: {input}"));
508 }
509 args.push(arg.to_string());
510 start = index + ch.len_utf8();
511 }
512 _ => {}
513 }
514 }
515
516 if depth != 0 {
517 return Err(eyre!(
518 "Unbalanced delimiter in orientation pattern: {input}"
519 ));
520 }
521
522 let tail = input[start..].trim();
523 if tail.is_empty() {
524 if args.is_empty() {
525 return Err(eyre!("Orientation pattern cannot be empty"));
526 }
527 return Err(eyre!("Empty orientation-pattern entry in pattern: {input}"));
528 }
529 args.push(tail.to_string());
530
531 Ok(args)
532 }
533
534 fn normalize_user_pattern(pattern: &str) -> EyreResult<String> {
535 let trimmed = pattern.trim();
536 if trimmed.is_empty() {
537 return Err(eyre!("Orientation pattern cannot be empty"));
538 }
539
540 let args = if let Some(rest) = trimmed.strip_prefix("orientation_delta") {
541 let rest = rest.trim();
542 if !(rest.starts_with('(') && rest.ends_with(')')) {
543 return Err(eyre!(
544 "orientation_delta patterns must use parentheses, got: {pattern}"
545 ));
546 }
547 Self::split_top_level_args(&rest[1..rest.len() - 1])?
548 } else if trimmed.starts_with('(') && trimmed.ends_with(')') {
549 Self::split_top_level_args(&trimmed[1..trimmed.len() - 1])?
550 } else {
551 Self::split_top_level_args(trimmed)?
552 };
553
554 let normalized_args = args
555 .into_iter()
556 .map(|arg| match arg.as_str() {
557 "+" | "+1" => "1".to_string(),
558 "-" | "-1" => "-1".to_string(),
559 _ => arg,
560 })
561 .collect::<Vec<_>>()
562 .join(",");
563
564 Ok(format!("orientation_delta({normalized_args})"))
565 }
566
567 pub fn parse_user_pattern(pattern: &str) -> EyreResult<Atom> {
568 let trimmed = pattern.trim();
569 if trimmed.is_empty() {
570 return Err(eyre!("Orientation pattern cannot be empty"));
571 }
572
573 if let Ok(parsed) = try_parse!(trimmed)
574 && Self::is_orientation_delta_atom(&parsed)
575 {
576 return Ok(parsed);
577 }
578
579 let normalized = Self::normalize_user_pattern(pattern)?;
580 try_parse!(normalized.as_str())
581 .map_err(|error| eyre!("Symbolica parsing error for orientation pattern: {error}"))
582 }
583
584 pub fn from_user_pattern(pattern: &str) -> EyreResult<Self> {
585 Ok(Self {
586 pat: Some(StringSerializedAtom(Self::parse_user_pattern(pattern)?)),
587 })
588 }
589
590 pub fn from_orientation<O: GraphOrientation>(orientation: &O) -> Self {
591 orientation.orientation_delta().into()
592 }
593
594 pub fn select_pattern(&self, atom: impl AtomCore) -> Option<Atom> {
595 Some(
596 atom.replace(self.pat.as_ref()?.as_view().to_pattern())
597 .with(function!(GS.selected, &self.pat.as_ref()?.0))
598 .replace(function!(GS.orientation_delta, W_.a___))
599 .level_range((0, Some(0)))
600 .with(Atom::Zero),
601 )
602 }
603
604 pub fn filter<O: GraphOrientation>(&self, orientation: &O) -> bool {
605 if let Some(pat) = &self.pat {
606 let a = orientation.orientation_delta();
607
608 a.pattern_match(&pat.to_pattern(), None, None)
612 .next()
613 .is_some()
614 } else {
615 true
616 }
617 }
618
619 pub fn alt_filter<O: GraphOrientation>(&self, orientation: &O) -> bool {
620 self.filter(orientation)
621 }
622}
623
624#[cfg(test)]
625mod orientation_pattern_tests {
626 use super::OrientationPattern;
627 use linnet::half_edge::involution::{EdgeVec, Orientation};
628 use symbolica::atom::AtomCore;
629
630 fn orientation(value: i8) -> Orientation {
631 match value {
632 1 => Orientation::Default,
633 -1 => Orientation::Reversed,
634 0 => Orientation::Undirected,
635 _ => panic!("invalid orientation encoding"),
636 }
637 }
638
639 fn edgevec(values: impl IntoIterator<Item = i8>) -> EdgeVec<Orientation> {
640 EdgeVec::from_iter(values.into_iter().map(orientation))
641 }
642
643 #[test]
644 fn orientation_pattern_deserialization_supports_shorthand_and_missing_wrapper() {
645 let wrapped: OrientationPattern =
646 toml::from_str(r#"pat = "orientation_delta(+,-,0)""#).unwrap();
647 let tuple_only: OrientationPattern = toml::from_str(r#"pat = "(+,-,0)""#).unwrap();
648 let bare_args: OrientationPattern = toml::from_str(r#"pat = "+,-,0""#).unwrap();
649
650 assert_eq!(wrapped, tuple_only);
651 assert_eq!(wrapped, bare_args);
652 assert_eq!(
653 wrapped.pat.as_ref().unwrap().to_string(),
654 "orientation_delta(1,-1,0)"
655 );
656 assert!(wrapped.filter(&edgevec([1, -1, 0])));
657 assert!(!wrapped.filter(&edgevec([1, 1, 0])));
658 }
659
660 #[test]
661 fn orientation_pattern_repeated_wildcards_enforce_identical_bindings() {
662 let pattern: OrientationPattern =
663 toml::from_str(r#"pat = "(+,+,-,x_,-,x_,+,0,+,y_,-,+)""#).unwrap();
664
665 assert!(pattern.filter(&edgevec([1, 1, -1, 1, -1, 1, 1, 0, 1, 0, -1, 1])));
666 assert!(pattern.filter(&edgevec([1, 1, -1, 0, -1, 0, 1, 0, 1, -1, -1, 1])));
667 assert!(!pattern.filter(&edgevec([1, 1, -1, 1, -1, 0, 1, 0, 1, 0, -1, 1])));
668 assert!(pattern.alt_filter(&edgevec([1, 1, -1, 1, -1, 1, 1, 0, 1, 0, -1, 1])));
669 }
670
671 #[test]
672 fn orientation_pattern_accepts_canonical_namespaced_roundtrip() {
673 let original = OrientationPattern::from_user_pattern("(+,-,0)").unwrap();
674 let canonical = original.pat.as_ref().unwrap().0.to_canonical_string();
675 let reparsed = OrientationPattern::from_user_pattern(&canonical).unwrap();
676
677 assert_eq!(original, reparsed);
678 assert!(reparsed.filter(&edgevec([1, -1, 0])));
679 }
680}
681
682#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, JsonSchema)]
683#[cfg_attr(
684 feature = "python_api",
685 pyo3::pyclass(from_py_object, get_all, set_all)
686)]
687#[serde(default, deny_unknown_fields)]
688pub struct Parallelisation {
689 #[serde(skip_serializing_if = "is_usize::<1>")]
691 pub feyngen: usize,
692 #[serde(skip_serializing_if = "is_usize::<1>")]
694 pub generate: usize,
695 #[serde(skip_serializing_if = "is_usize::<1>")]
697 pub compile: usize,
698 #[serde(skip_serializing_if = "is_usize::<1>")]
700 pub integrate: usize,
701}
702
703impl Default for Parallelisation {
704 fn default() -> Self {
705 Self {
706 feyngen: 1,
707 generate: 1,
708 compile: 1,
709 integrate: 1,
710 }
711 }
712}