Trait Node

Source
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§

Source

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§

Source

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.

Source

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.

Source

fn into_amplitude(self, name: &str) -> Amplitude<F>
where Self: Sized + 'static,

A convenience method for turning Nodes into Amplitudes.

Source

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.

Source

fn is_python_node(&self) -> bool

A flag which says if the Node was written in Python. This matters because the GIL cannot currently play nice with rayon multithreading. You will probably never need to set this, as the only object which returns True is in the py_rustitude crate which binds this crate to Python.

Implementors§

Source§

impl<F: Field> Node<F> for Amplitude<F>

Source§

impl<F: Field> Node<F> for ComplexScalar

Source§

impl<F: Field> Node<F> for PolarComplexScalar

Source§

impl<F: Field> Node<F> for Scalar

Source§

impl<V, F> Node<F> for Piecewise<V, F>
where V: Fn(&Event<F>) -> F + Send + Sync + Copy, F: Field,