Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,15 @@ The convention is all outgoing with physical charges `t = tau / sqrt(2)`;
endpoint-pairing phases belong in the supplied tensor coefficients. Overlap
matrices are Gram matrices and must not be multiplied as charge-action
matrices. This is color algebra, not a model or numerical amplitude generator.
`ColorConnection::apply_connected` extends an existing exact tensor sum while
preserving its complex coefficients, Laurent powers and root-two normalization.
`GluonSplittingColorPair` supplies daughter-spectator insertions after `g -> q qbar`
and the symmetric-adjoint pair `D_parent . T_spectator`, with
`(D^c)_AB = d^{AcB}` (no factor of `i`). For the physical splitting tensor
`S_ij,B = t^B_ij`, its raw daughter overlaps include `T_R=1/2`; the symmetric
pair is exactly twice their difference, with no extra `T_R`. The parent must be
adjoint and the spectator a distinct colored leg; callers supply fresh daughter
and emission labels. Mixed spin densities and kinematic kernels remain separate.
The `color::network` module also lowers exact Spenso SU(3) vertex expressions
(`f`, `t`, chains, traces and color metrics) to typed matrix products and
contracts their internal indices with `reduce_network`. It retains complete
Expand Down
38 changes: 31 additions & 7 deletions rust/crates/rusticol-core/src/color/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ use symbolica::domains::{float::Complex, rational::Rational};
use crate::{RusticolError, RusticolResult};

pub mod network;
mod splitting;
pub use splitting::{GluonSplittingColorPair, SplitDaughterColorTerms};
#[cfg(test)]
mod tests;
mod trace;
Expand Down Expand Up @@ -259,12 +261,34 @@ impl ColorConnection {
tensor: &ColorTensor,
coefficient: ExactColorCoefficient,
) -> RusticolResult<ConnectedColorTensor> {
tensor.validate_legs(&self.initial_legs)?;
let mut terms = vec![TensorTerm {
coefficient,
tensor: tensor.clone(),
nc_power: 0,
}];
self.apply_connected(&ConnectedColorTensor::new(
self.initial_legs.clone(),
vec![TensorTerm {
coefficient,
tensor: tensor.clone(),
nc_power: 0,
}],
0,
)?)
}

/// Apply this ordered connection to an existing exact linear combination.
/// Complex coefficients, Laurent powers and the input's common inverse
/// square root of two normalization are preserved.
pub fn apply_connected(
&self,
tensor: &ConnectedColorTensor,
) -> RusticolResult<ConnectedColorTensor> {
if tensor.legs != self.initial_legs {
return Err(invalid(
"connection input must have identical typed color legs",
));
}
let normalization_power = tensor
.normalization_power
.checked_add(self.order)
.ok_or_else(|| invalid("color normalization power overflows"))?;
let mut terms = tensor.terms.clone();
for &emission in &self.emissions {
let mut transformed = Vec::new();
for term in terms {
Expand All @@ -278,7 +302,7 @@ impl ColorConnection {
}
terms = combine_terms(transformed)?;
}
ConnectedColorTensor::new(self.output_legs.clone(), terms, self.order)
ConnectedColorTensor::new(self.output_legs.clone(), terms, normalization_power)
}
}

Expand Down
126 changes: 126 additions & 0 deletions rust/crates/rusticol-core/src/color/splitting.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// SPDX-License-Identifier: 0BSD

//! Exact daughter-charge insertions through an all-outgoing gluon splitting.

use super::*;

/// Raw daughter-spectator insertions, including the splitting norm `T_R=1/2`.
///
/// Each entry is `<S bra | T_daughter . T_spectator | S ket>`, with
/// `S_ij,B=t^B_ij`. The entries retain directed complex phases and all input
/// normalizations. Dividing either entry by `T_R` multiplies it by two.
#[derive(Clone, Debug, PartialEq)]
pub struct SplitDaughterColorTerms {
pub quark: ColorNcTerms,
pub antiquark: ColorNcTerms,
}

impl SplitDaughterColorTerms {
/// `<bra | D_parent . T_spectator | ket>`, where
/// `(D^c)_AB=d^{AcB}` has no factor of `i`.
///
/// The splitting identities are `S† T_q^c S / T_R=(T_adj^c+D^c)/2`
/// and `S† T_qbar^c S / T_R=(T_adj^c-D^c)/2`. Consequently this is
/// exactly `2*(quark-antiquark)`, with no additional `T_R` factor.
pub fn symmetric_adjoint_nc_terms(&self) -> ColorNcTerms {
let mut terms = ColorNcTerms::new();
for (source, factor) in [(&self.quark, 2), (&self.antiquark, -2)] {
for (&power, coefficient) in source {
add_coefficient(
&mut terms,
power,
scale(coefficient, &Rational::from(factor)),
);
}
}
terms.retain(|_, coefficient| !is_zero(coefficient));
terms
}
}

/// A typed symmetric-adjoint pair obtained from exact splitting connections.
///
/// The parent is adjoint; the spectator is a distinct colored external leg.
/// Daughter and emitted-adjoint labels must be distinct and fresh relative to
/// every input leg. All legs and charges use the all-outgoing convention.
/// This object supplies only color matrix elements: mixed spin densities and
/// their kinematic splitting kernels remain the caller's responsibility.
#[derive(Clone, Debug)]
pub struct GluonSplittingColorPair {
quark: ColorConnection,
antiquark: ColorConnection,
spectator: ColorConnection,
}

impl GluonSplittingColorPair {
pub fn new(
initial_legs: Vec<ColorLeg>,
parent: i64,
spectator: i64,
quark: i64,
antiquark: i64,
emitted: i64,
) -> RusticolResult<Self> {
if parent == spectator {
return Err(invalid("splitting parent and spectator must be distinct"));
}
if !initial_legs
.iter()
.any(|leg| leg.label == spectator && leg.representation != ColorRepresentation::Singlet)
{
return Err(invalid("splitting spectator must be an input colored leg"));
}
let connection = |emitter| {
ColorConnection::new(
initial_legs.clone(),
vec![
ColorEmission::SplitGluon {
parent,
quark,
antiquark,
},
ColorEmission::EmitGluon { emitter, emitted },
],
)
};
Ok(Self {
quark: connection(quark)?,
antiquark: connection(antiquark)?,
spectator: connection(spectator)?,
})
}

/// Raw overlaps before division by `T_R`, for arbitrary complex input
/// tensors with matching typed legs. An odd combined inverse-sqrt(2)
/// input normalization cannot be represented by rational Laurent terms and
/// is rejected by the common overlap implementation.
pub fn raw_daughter_nc_terms(
&self,
bra: &ConnectedColorTensor,
ket: &ConnectedColorTensor,
) -> RusticolResult<SplitDaughterColorTerms> {
let spectator = self.spectator.apply_connected(ket)?;
Ok(SplitDaughterColorTerms {
quark: self
.quark
.apply_connected(bra)?
.overlap_nc_terms(&spectator)?,
antiquark: self
.antiquark
.apply_connected(bra)?
.overlap_nc_terms(&spectator)?,
})
}

/// Full complex Laurent matrix element of `D_parent . T_spectator`.
/// No color averaging, real projection, or extra `T_R` factor is applied.
pub fn symmetric_adjoint_nc_terms(
&self,
bra: &ConnectedColorTensor,
ket: &ConnectedColorTensor,
) -> RusticolResult<ColorNcTerms> {
Ok(self
.raw_daughter_nc_terms(bra, ket)?
.symmetric_adjoint_nc_terms())
}
}
Loading
Loading