Trait rustitude_core::amplitude::Node

source ·
pub trait Node: Sync + Send {
    // Required method
    fn calculate(
        &self,
        parameters: &[f64],
        event: &Event
    ) -> Result<Complex64, RustitudeError>;

    // Provided methods
    fn precalculate(&mut self, _dataset: &Dataset) -> Result<(), RustitudeError> { ... }
    fn parameters(&self) -> Vec<String> { ... }
}
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 Complex64 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 num_complex::Complex64;
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,
        }
    }
}

struct Ylm(Wave, Vec<Complex64>);
impl Ylm {
    fn new(wave: Wave) -> Self {
        Self(wave, Vec::default())
    }
}
impl Node for Ylm {
    fn parameters(&self) -> Vec<String> { vec![] }
    fn precalculate(&mut self, dataset: &Dataset) -> Result<(), RustitudeError> {
        self.1 = dataset.events.read()
            .par_iter()
            .map(|event| {
                let resonance = event.daughter_p4s[0] + event.daughter_p4s[1];
                let p1 = event.daughter_p4s[0];
                let recoil_res = event.recoil_p4.boost_along(&resonance); // Boost to helicity frame
                let p1_res = p1.boost_along(&resonance);
                let z = -1.0 * recoil_res.momentum().normalize();
                let y = event
                    .beam_p4
                    .momentum()
                    .cross(&(-1.0 * event.recoil_p4.momentum()));
                let x = y.cross(&z);
                let p1_vec = p1_res.momentum();
                let p = Coordinates::cartesian(p1_vec.dot(&x), p1_vec.dot(&y), p1_vec.dot(&z));
                ComplexSH::Spherical.eval(self.0.l(), self.0.m(), &p)
            })
            .collect();
        Ok(())
    }

    fn calculate(&self, _parameters: &[f64], event: &Event) -> Result<Complex64, RustitudeError> {
        Ok(self.1[event.index])
    }
}

A Node which computes a single complex scalar entirely determined by input parameters:

use rustitude_core::prelude::*;
struct ComplexScalar;
impl Node for ComplexScalar {
    fn calculate(&self, parameters: &[f64], _event: &Event) -> Result<Complex64, RustitudeError> {
        Ok(Complex64::new(parameters[0], parameters[1]))
    }

    fn parameters(&self) -> Vec<String> {
        vec!["real".to_string(), "imag".to_string()]
    }
}

Required Methods§

source

fn calculate( &self, parameters: &[f64], event: &Event ) -> Result<Complex64, RustitudeError>

A method which runs every time the amplitude is evaluated and produces a Complex64.

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 f64s. 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.

Provided Methods§

source

fn precalculate(&mut self, _dataset: &Dataset) -> 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.

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.

Implementors§