use super::RhsProgram;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum RhsError {
Fault { code: &'static str, message: String },
Shape {
what: &'static str,
expected: usize,
got: usize,
},
ForeignProgram { what: &'static str },
ForcingSignature { detail: String },
SlotIndex { slot: usize, slots: usize },
NotGathered,
}
impl std::fmt::Display for RhsError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RhsError::Fault { code, message } => write!(f, "{code}: {message}"),
RhsError::Shape {
what,
expected,
got,
} => write!(
f,
"`{what}` does not fit the program: expected length {expected}, got {got}"
),
RhsError::ForeignProgram { what } => write!(
f,
"`{what}` was made by another program; it serves only the program that made it \
and that program's clones"
),
RhsError::ForcingSignature { detail } => write!(
f,
"the forcing set was built for other forcing slots than the program's: {detail}"
),
RhsError::SlotIndex { slot, slots } => write!(
f,
"forcing slot {slot} is out of range: the set has {slots} slot(s), indexed from 0"
),
RhsError::NotGathered => write!(
f,
"the lane inputs hold no successful gather; call `gather_lane_inputs` first"
),
}
}
}
impl std::error::Error for RhsError {}
const FAULT_CODES: [&str; 9] = [
"E_TREEWALK_CONSTARRAY_OOB",
"E_TREEWALK_GEOMETRY_CLIP",
"E_TREEWALK_INDEX_ON_SCALAR",
"E_TREEWALK_MISSING_DATA",
"E_TREEWALK_MISSING_INITIAL_VALUE",
"E_TREEWALK_RECUR_UNAVAILABLE",
"E_TREEWALK_UNBOUND_NAME",
"E_TREEWALK_UNDECLARED_INDEX_SET",
"E_TREEWALK_UNRESOLVED_ORDER",
];
pub(super) fn fault(message: String) -> RhsError {
let code = FAULT_CODES
.iter()
.copied()
.find(|c| {
message
.strip_prefix(c)
.is_some_and(|rest| rest.starts_with(':'))
})
.unwrap_or("rhs_fault");
RhsError::Fault { code, message }
}
pub(super) fn shape(what: &'static str, expected: usize, got: usize) -> RhsError {
RhsError::Shape {
what,
expected,
got,
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ParamSlot {
pub name: String,
pub default: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StateBlock {
pub name: String,
pub flat_offset: usize,
pub shape: Vec<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SlotKind {
Forcing,
ArrayParameter,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ForcingSlot {
pub name: String,
pub shape: Vec<usize>,
pub kind: SlotKind,
}
impl ForcingSlot {
pub fn len(&self) -> usize {
self.shape.iter().product::<usize>()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
static NEXT_EPOCH: AtomicU64 = AtomicU64::new(1);
fn next_epoch() -> u64 {
NEXT_EPOCH.fetch_add(1, Ordering::Relaxed)
}
#[derive(Debug, Clone)]
pub struct ForcingSet {
epoch: u64,
slots: Arc<[ForcingSlot]>,
bufs: Arc<[Arc<[f64]>]>,
}
impl ForcingSet {
pub fn new(prog: &RhsProgram, bufs: Vec<Arc<[f64]>>) -> Result<ForcingSet, RhsError> {
ForcingSet::for_slots(prog.slots_arc(), bufs)
}
pub(super) fn for_slots(
slots: Arc<[ForcingSlot]>,
bufs: Vec<Arc<[f64]>>,
) -> Result<ForcingSet, RhsError> {
if bufs.len() != slots.len() {
return Err(shape("forcing", slots.len(), bufs.len()));
}
for (slot, buf) in slots.iter().zip(&bufs) {
check_len(slot, buf)?;
}
Ok(ForcingSet {
epoch: next_epoch(),
slots,
bufs: bufs.into(),
})
}
pub fn replace(&self, slot: usize, buf: Arc<[f64]>) -> Result<ForcingSet, RhsError> {
let Some(s) = self.slots.get(slot) else {
return Err(RhsError::SlotIndex {
slot,
slots: self.slots.len(),
});
};
check_len(s, &buf)?;
let mut bufs: Vec<Arc<[f64]>> = self.bufs.to_vec();
bufs[slot] = buf;
Ok(ForcingSet {
epoch: next_epoch(),
slots: Arc::clone(&self.slots),
bufs: bufs.into(),
})
}
pub fn epoch(&self) -> u64 {
self.epoch
}
pub fn slots(&self) -> &[ForcingSlot] {
&self.slots
}
pub fn buffer(&self, k: usize) -> &[f64] {
&self.bufs[k]
}
pub(super) fn slots_arc(&self) -> Arc<[ForcingSlot]> {
Arc::clone(&self.slots)
}
pub(super) fn bufs(&self) -> &[Arc<[f64]>] {
&self.bufs
}
pub(super) fn fits(&self, slots: &Arc<[ForcingSlot]>) -> bool {
Arc::ptr_eq(&self.slots, slots) || *self.slots == **slots
}
pub(super) fn mismatch(&self, slots: &[ForcingSlot]) -> RhsError {
let show = |s: &ForcingSlot| format!("`{}` {:?} ({:?})", s.name, s.shape, s.kind);
let detail = match self.slots.iter().zip(slots).position(|(a, b)| a != b) {
Some(k) => format!(
"slot {k} is {} in the set, {} in the program",
show(&self.slots[k]),
show(&slots[k])
),
None => format!(
"the set has {} slot(s), the program {}",
self.slots.len(),
slots.len()
),
};
RhsError::ForcingSignature { detail }
}
}
fn check_len(slot: &ForcingSlot, buf: &[f64]) -> Result<(), RhsError> {
if buf.len() != slot.len() {
return Err(shape("forcing", slot.len(), buf.len()));
}
Ok(())
}