pub trait Node<F: Field>:
Sync
+ Send
+ DynClone {
// Required method
fn calculate(
&self,
parameters: &[F],
event: &Event<F>,
) -> Result<Complex<F>, RustitudeError>;
// Provided methods
fn precalculate(
&mut self,
_dataset: &Dataset<F>,
) -> Result<(), RustitudeError> { ... }
fn parameters(&self) -> Vec<String> { ... }
fn into_amplitude(self, name: &str) -> Amplitude<F>
where Self: Sized + 'static { ... }
fn named(self, name: &str) -> Amplitude<F>
where Self: Sized + 'static { ... }
fn is_python_node(&self) -> bool { ... }
}Expand description
A trait which contains all the required methods for a functioning Amplitude.
The Node trait represents any mathematical structure which takes in some parameters and some
Event data and computes a Complex for each Event. This is the fundamental
building block of all analyses built with Rustitude. Nodes are intended to be optimized at the
user level, so they should be implemented on structs which can store some precalculated data.
§Examples:
A Node for calculating spherical harmonics:
use rustitude_core::prelude::*;
use nalgebra::{SMatrix, SVector};
use rayon::prelude::*;
use sphrs::SHEval;
use sphrs::{ComplexSH, Coordinates};
#[derive(Clone, Copy, Default)]
#[rustfmt::skip]
enum Wave {
#[default]
S,
S0,
Pn1, P0, P1, P,
Dn2, Dn1, D0, D1, D2, D,
Fn3, Fn2, Fn1, F0, F1, F2, F3, F,
}
#[rustfmt::skip]
impl Wave {
fn l(&self) -> i64 {
match self {
Self::S0 | Self::S => 0,
Self::Pn1 | Self::P0 | Self::P1 | Self::P => 1,
Self::Dn2 | Self::Dn1 | Self::D0 | Self::D1 | Self::D2 | Self::D => 2,
Self::Fn3 | Self::Fn2 | Self::Fn1 | Self::F0 | Self::F1 | Self::F2 | Self::F3 | Self::F => 3,
}
}
fn m(&self) -> i64 {
match self {
Self::S | Self::P | Self::D | Self::F => 0,
Self::S0 | Self::P0 | Self::D0 | Self::F0 => 0,
Self::Pn1 | Self::Dn1 | Self::Fn1 => -1,
Self::P1 | Self::D1 | Self::F1 => 1,
Self::Dn2 | Self::Fn2 => -2,
Self::D2 | Self::F2 => 2,
Self::Fn3 => -3,
Self::F3 => 3,
}
}
}
#[derive(Clone)]
pub struct Ylm<F: Field> {
wave: Wave,
data: Vec<Complex<F>>,
}
impl<F: Field> Ylm<F> {
pub fn new(wave: Wave) -> Self {
Self {
wave,
data: Vec::default(),
}
}
}
impl<F: Field> Node<F> for Ylm<F> {
fn precalculate(&mut self, dataset: &Dataset<F>) -> Result<(), RustitudeError> {
self.data = dataset
.events
.par_iter()
.map(|event| {
let resonance = event.daughter_p4s[0] + event.daughter_p4s[1];
let beam_res_vec = event.beam_p4.boost_along(&resonance).momentum();
let recoil_res_vec = event.recoil_p4.boost_along(&resonance).momentum();
let daughter_res_vec = event.daughter_p4s[0].boost_along(&resonance).momentum();
let z = -recoil_res_vec.unit();
let y = event
.beam_p4
.momentum()
.cross(&(-recoil_res_vec))
.unit();
let x = y.cross(&z);
let p = Coordinates::cartesian(
daughter_res_vec.dot(&x),
daughter_res_vec.dot(&y),
daughter_res_vec.dot(&z)
);
ComplexSH::Spherical.eval(self.wave.l(), self.wave.m(), &p)
})
.collect();
Ok(())
}
fn calculate(&self, _parameters: &[F], event: &Event<F>) -> Result<Complex<F>, RustitudeError> {
Ok(self.data[event.index])
}
}A Node which computes a single complex scalar entirely determined by input parameters:
use rustitude_core::prelude::*;
#[derive(Clone)]
struct ComplexScalar;
impl<F: Field> Node<F> for ComplexScalar {
fn calculate(&self, parameters: &[F], _event: &Event<F>) -> Result<Complex<F>, RustitudeError> {
Ok(Complex::new(parameters[0], parameters[1]))
}
fn parameters(&self) -> Vec<String> {
vec!["real".to_string(), "imag".to_string()]
}
}Required Methods§
Sourcefn calculate(
&self,
parameters: &[F],
event: &Event<F>,
) -> Result<Complex<F>, RustitudeError>
fn calculate( &self, parameters: &[F], event: &Event<F>, ) -> Result<Complex<F>, RustitudeError>
A method which runs every time the amplitude is evaluated and produces a Complex.
Because this method is run on every evaluation, it should be as lean as possible.
Additionally, you should avoid rayon’s parallel loops inside this method since we
already parallelize over the Dataset. This method expects a single Event as well as
a slice of Fields. This slice is guaranteed to have the same length and order as
specified in the Node::parameters method, or it will be empty if that method returns
None.
§Errors
This function should be written to return a RustitudeError if any part of the
calculation fails.
Provided Methods§
Sourcefn precalculate(&mut self, _dataset: &Dataset<F>) -> Result<(), RustitudeError>
fn precalculate(&mut self, _dataset: &Dataset<F>) -> Result<(), RustitudeError>
A method that is run once and stores some precalculated values given a Dataset input.
This method is intended to run expensive calculations which don’t actually depend on the
parameters. For instance, to calculate a spherical harmonic, we don’t actually need any
other information than what is contained in the Event, so we can calculate a spherical
harmonic for every event once and then retrieve the data in the Node::calculate method.
§Errors
This function should be written to return a RustitudeError if any part of the
calculation fails.
Sourcefn parameters(&self) -> Vec<String>
fn parameters(&self) -> Vec<String>
A method which specifies the number and order of parameters used by the Node.
This method tells the crate::manager::Manager how to assign its input Vec of parameter values to
each Node. If this method returns None, it is implied that the Node takes no
parameters as input. Otherwise, the parameter names should be listed in the same order they
are expected to be given as input to the Node::calculate method.
Sourcefn into_amplitude(self, name: &str) -> Amplitude<F>where
Self: Sized + 'static,
fn into_amplitude(self, name: &str) -> Amplitude<F>where
Self: Sized + 'static,
Sourcefn named(self, name: &str) -> Amplitude<F>where
Self: Sized + 'static,
fn named(self, name: &str) -> Amplitude<F>where
Self: Sized + 'static,
A convenience method for turning Nodes into Amplitudes. This method has a
shorter name than Node::into_amplitude, which it calls.