use crate::calibration::pads::baseline::try_pad_baseline;
use crate::calibration::pads::delay::try_pad_delay;
use crate::calibration::pads::gain::try_pad_gain;
use crate::calibration::wires::baseline::try_wire_baseline;
use crate::calibration::wires::delay::try_wire_delay;
use crate::calibration::wires::gain::try_wire_gain;
use crate::deconvolution::pads::pad_deconvolution;
use crate::deconvolution::wires::{contiguous_ranges, wire_range_deconvolution};
use crate::drift::DRIFT_TABLES;
use crate::matching::{match_column_inputs, pad_column_to_wires, wire_to_pad_column};
use crate::reconstruction::{cluster_spacepoints, find_vertices, Coordinate};
use alpha_g_detector::alpha16::aw_map::{
self, MapTpcWirePositionError, TpcWirePosition, TPC_ANODE_WIRES,
};
use alpha_g_detector::alpha16::{self, AdcPacket, TryAdcPacketFromSliceError};
use alpha_g_detector::midas::{
Adc32BankName, Alpha16BankName, MainEventBankName, ParseMainEventBankNameError,
};
use alpha_g_detector::padwing::map::{
MapTpcPadPositionError, TpcPadPosition, TPC_PAD_COLUMNS, TPC_PAD_ROWS,
};
use alpha_g_detector::padwing::{
self, Chunk, PwbPacket, TryChunkFromSliceError, TryPwbPacketFromChunksError,
};
use alpha_g_detector::trigger::TryTrgPacketFromSliceError;
use alpha_g_detector::trigger::{self, TrgPacket};
use std::collections::{BTreeSet, HashMap};
use thiserror::Error;
use uom::si::f64::*;
use uom::typenum::P2;
pub use crate::calibration::pads::baseline::MapPadBaselineError;
pub use crate::calibration::pads::delay::MapPadDelayError;
pub use crate::calibration::pads::gain::MapPadGainError;
pub use crate::calibration::wires::baseline::MapWireBaselineError;
pub use crate::calibration::wires::delay::MapWireDelayError;
pub use crate::calibration::wires::gain::MapWireGainError;
pub use crate::drift::TryDriftLookupError;
mod calibration;
mod drift;
mod deconvolution;
pub mod chronobox;
mod matching;
pub mod reconstruction;
#[derive(Clone, Copy, Debug)]
pub struct Avalanche {
pub t: Time,
pub phi: Angle,
pub z: Length,
pub wire_amplitude: f64,
pub pad_amplitude: f64,
}
pub const ANODE_WIRES_RADIUS: Length = Length {
dimension: uom::lib::marker::PhantomData,
units: uom::lib::marker::PhantomData,
value: aw_map::ANODE_WIRES_RADIUS,
};
pub const TRG_CLOCK_FREQ: Frequency = Frequency {
dimension: uom::lib::marker::PhantomData,
units: uom::lib::marker::PhantomData,
value: trigger::TRG_CLOCK_FREQ,
};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SpacePoint {
pub r: Length,
pub phi: Angle,
pub z: Length,
}
impl TryFrom<Avalanche> for SpacePoint {
type Error = TryDriftLookupError;
fn try_from(avalanche: Avalanche) -> Result<Self, Self::Error> {
let (r, lorentz_correction) = DRIFT_TABLES.at(avalanche.z, avalanche.t)?;
Ok(SpacePoint {
r,
phi: avalanche.phi - lorentz_correction,
z: avalanche.z,
})
}
}
impl SpacePoint {
pub fn x(self) -> Length {
self.r * self.phi.cos()
}
pub fn y(self) -> Length {
self.r * self.phi.sin()
}
pub fn distance(self, other: Self) -> Length {
((self.x() - other.x()).powi(P2::new())
+ (self.y() - other.y()).powi(P2::new())
+ (self.z - other.z).powi(P2::new()))
.sqrt()
}
}
#[derive(Error, Debug)]
pub enum TryMainEventFromDataBanksError {
#[error("unknown bank name")]
UnknownBank(#[from] ParseMainEventBankNameError),
#[error("bad alpha16 data")]
BadAlpha16(#[from] TryAdcPacketFromSliceError),
#[error("alpha16 board_id/channel_id mismatch (expected {expected:?}, found {found:?})")]
Alpha16IdMismatch {
expected: (alpha16::BoardId, alpha16::Adc32ChannelId),
found: (alpha16::BoardId, alpha16::Adc32ChannelId),
},
#[error("anode wire data bank `{bank_name:?}` has a BV channel_id")]
WireBankWithBvChannel { bank_name: Adc32BankName },
#[error("duplicate anode wire data banks with name `{bank_name:?}`")]
DuplicateWireBank { bank_name: Adc32BankName },
#[error("bad padwing chunk data")]
BadPadwingChunk(#[from] TryChunkFromSliceError),
#[error("padwing board_id mismatch (expected `{expected:?}`, found `{found:?}`)")]
PadwingBoardIdMismatch {
expected: padwing::BoardId,
found: padwing::BoardId,
},
#[error("bad padwing data")]
BadPadwing(#[from] TryPwbPacketFromChunksError),
#[error("duplicate cathode pad signal in position `{position:?}`")]
DuplicatePadSignal { position: TpcPadPosition },
#[error("bad trg data")]
BadTrg(#[from] TryTrgPacketFromSliceError),
#[error("duplicate trigger data bank")]
DuplicateTrgBank,
#[error("missing trigger data bank")]
MissingTrgBank,
#[error("wire position mapping failed")]
WirePositionError(#[from] MapTpcWirePositionError),
#[error("pad position mapping failed")]
PadPositionError(#[from] MapTpcPadPositionError),
#[error("wire baseline calibration failed")]
WireBaselineError(#[from] MapWireBaselineError),
#[error("wire delay calibration failed")]
WireDelayError(#[from] MapWireDelayError),
#[error("wire gain calibration failed")]
WireGainError(#[from] MapWireGainError),
#[error("pad baseline calibration failed")]
PadBaselineError(#[from] MapPadBaselineError),
#[error("pad delay calibration failed")]
PadDelayError(#[from] MapPadDelayError),
#[error("pad gain calibration failed")]
PadGainError(#[from] MapPadGainError),
}
#[derive(Debug, Clone)]
pub struct MainEvent {
wire_signals: [Option<Vec<f64>>; TPC_ANODE_WIRES],
pad_signals: [[Option<Vec<f64>>; TPC_PAD_ROWS]; TPC_PAD_COLUMNS],
trigger_timestamp: u32,
}
impl MainEvent {
pub fn try_from_banks<'a, I>(
run_number: u32,
banks: I,
) -> Result<Self, TryMainEventFromDataBanksError>
where
I: IntoIterator<Item = (&'a str, &'a [u8])>,
{
let mut wire_signals = [(); TPC_ANODE_WIRES].map(|_| None);
let mut pad_signals = [(); TPC_PAD_COLUMNS].map(|_| [(); TPC_PAD_ROWS].map(|_| None));
let mut trigger_timestamp = None;
let mut pwb_chunks_map: HashMap<_, Vec<_>> = HashMap::new();
for (bank_name, data_slice) in banks {
match MainEventBankName::try_from(bank_name)? {
MainEventBankName::Alpha16(Alpha16BankName::A32(bank_name)) => {
let packet = AdcPacket::try_from(data_slice)?;
let waveform = packet.waveform();
if waveform.is_empty() {
continue;
}
let board_id = packet.board_id().unwrap();
let alpha16::ChannelId::A32(channel_id) = packet.channel_id() else {
return Err(TryMainEventFromDataBanksError::WireBankWithBvChannel {
bank_name,
});
};
if (bank_name.board_id(), bank_name.channel_id()) != (board_id, channel_id) {
return Err(TryMainEventFromDataBanksError::Alpha16IdMismatch {
expected: (bank_name.board_id(), bank_name.channel_id()),
found: (board_id, channel_id),
});
}
let wire_position = TpcWirePosition::try_new(run_number, board_id, channel_id)?;
let wire_index = usize::from(wire_position);
if wire_signals[wire_index].is_some() {
return Err(TryMainEventFromDataBanksError::DuplicateWireBank {
bank_name,
});
} else {
let baseline = try_wire_baseline(run_number, wire_position)?;
let gain = try_wire_gain(run_number, wire_position)?;
let delay = try_wire_delay(run_number)?;
let signal: Vec<_> = waveform
.iter()
.skip(delay)
.map(|&v| f64::from(i32::from(v) - i32::from(baseline)) * gain)
.collect();
if !signal.is_empty() {
wire_signals[wire_index] = Some(signal);
}
}
}
MainEventBankName::Padwing(bank_name) => {
let chunk = Chunk::try_from(data_slice)?;
let key = (chunk.board_id(), chunk.after_id());
if key.0 != bank_name.board_id() {
return Err(TryMainEventFromDataBanksError::PadwingBoardIdMismatch {
expected: bank_name.board_id(),
found: key.0,
});
}
pwb_chunks_map.entry(key).or_default().push(chunk);
}
MainEventBankName::Trg(_) => {
let packet = TrgPacket::try_from(data_slice)?;
if trigger_timestamp.is_some() {
return Err(TryMainEventFromDataBanksError::DuplicateTrgBank);
} else {
trigger_timestamp = Some(packet.timestamp());
}
}
_ => {}
}
}
for chunks in pwb_chunks_map.into_values() {
let packet = PwbPacket::try_from(chunks)?;
let board_id = packet.board_id();
let after_id = packet.after_id();
for &channel_id in packet.channels_sent() {
if let padwing::ChannelId::Pad(pad_channel_id) = channel_id {
let waveform = packet.waveform_at(channel_id).unwrap();
let pad_position =
TpcPadPosition::try_new(run_number, board_id, after_id, pad_channel_id)?;
let pad_index = (
usize::from(pad_position.column),
usize::from(pad_position.row),
);
if pad_signals[pad_index.0][pad_index.1].is_some() {
return Err(TryMainEventFromDataBanksError::DuplicatePadSignal {
position: pad_position,
});
} else {
let baseline = try_pad_baseline(run_number, pad_position)?;
let gain = try_pad_gain(run_number, pad_position)?;
let delay = try_pad_delay(run_number)?;
let signal: Vec<_> = waveform
.iter()
.skip(delay)
.map(|&v| f64::from(v.checked_sub(baseline).unwrap()) * gain)
.collect();
if !signal.is_empty() {
pad_signals[pad_index.0][pad_index.1] = Some(signal);
}
}
}
}
}
Ok(Self {
wire_signals,
pad_signals,
trigger_timestamp: trigger_timestamp
.ok_or(TryMainEventFromDataBanksError::MissingTrgBank)?,
})
}
pub fn vertex(&self) -> Option<Coordinate> {
let points = self
.avalanches()
.into_iter()
.filter_map(|avalanche| avalanche.try_into().ok())
.collect();
let tracks = cluster_spacepoints(points)
.clusters
.into_iter()
.filter_map(|cluster| cluster.try_into().ok())
.collect();
find_vertices(tracks).primary.map(|info| info.position)
}
pub fn timestamp(&self) -> u32 {
self.trigger_timestamp
}
pub fn avalanches(&self) -> Vec<Avalanche> {
let mut pad_columns = BTreeSet::new();
let mut wire_inputs = [(); TPC_ANODE_WIRES].map(|_| Vec::new());
for range in contiguous_ranges(&self.wire_signals) {
for (i, input) in wire_range_deconvolution(&self.wire_signals, range) {
wire_inputs[i] = input;
pad_columns.insert(wire_to_pad_column(i));
}
}
let mut avalanches = Vec::new();
for column in pad_columns {
let mut pad_inputs_column = [(); TPC_PAD_ROWS].map(|_| Vec::new());
for (row, input) in pad_inputs_column.iter_mut().enumerate() {
if let Some(signal) = self.pad_signals[column][row].as_ref() {
*input = pad_deconvolution(signal);
}
}
let wire_indices = pad_column_to_wires(column);
avalanches.extend(match_column_inputs(
wire_indices.clone().collect::<Vec<_>>().try_into().unwrap(),
wire_inputs[wire_indices].try_into().unwrap(),
&pad_inputs_column,
));
}
avalanches
}
}
#[cfg(test)]
mod tests;