use crate::geometry::Geometry;
use crate::lattice::PaddedTileLattice;
#[derive(Clone)]
pub struct TensorAutomaton {
num_states: usize,
num_symbols: usize,
start: usize,
accept: Vec<bool>,
geom: Geometry,
matrices: Vec<PaddedTileLattice<f32>>,
}
impl TensorAutomaton {
pub fn new(start: usize, accept: Vec<bool>, trans: Vec<Vec<usize>>) -> Self {
let num_states = trans.len();
assert!(num_states > 0, "need at least one state");
assert_eq!(accept.len(), num_states, "accept flag per state");
let num_symbols = trans[0].len();
let geom = Geometry::TPU_V;
let matrices = (0..num_symbols)
.map(|a| {
let mut dense = vec![0.0f32; num_states * num_states];
for (s, row) in trans.iter().enumerate() {
let next = row[a];
assert!(next < num_states, "transition out of range");
dense[s * num_states + next] = 1.0;
}
PaddedTileLattice::from_dense(num_states, num_states, &dense, geom).unwrap()
})
.collect();
TensorAutomaton {
num_states,
num_symbols,
start,
accept,
geom,
matrices,
}
}
pub fn mod_k(k: usize) -> Self {
assert!(k > 0, "modulus must be positive");
let trans: Vec<Vec<usize>> = (0..k).map(|s| vec![(2 * s) % k, (2 * s + 1) % k]).collect();
let accept = (0..k).map(|s| s == 0).collect();
TensorAutomaton::new(0, accept, trans)
}
#[inline]
pub fn num_states(&self) -> usize {
self.num_states
}
#[inline]
pub fn num_symbols(&self) -> usize {
self.num_symbols
}
#[inline]
pub fn is_accepting(&self, state: usize) -> bool {
self.accept[state]
}
#[inline]
pub fn transition(&self, symbol: usize) -> &PaddedTileLattice<f32> {
&self.matrices[symbol]
}
fn argmax_row(row: &[f32]) -> usize {
let mut best = (0usize, f32::NEG_INFINITY);
for (i, &v) in row.iter().enumerate() {
if v > best.1 {
best = (i, v);
}
}
best.0
}
pub fn run(&self, input: &[usize]) -> usize {
let q = self.num_states;
let mut state = vec![0.0f32; q];
state[self.start] = 1.0;
for &sym in input {
let s = PaddedTileLattice::from_dense(1, q, &state, self.geom).unwrap();
let next = s.matmul(&self.matrices[sym]).unwrap();
state = next.to_dense();
}
TensorAutomaton::argmax_row(&state)
}
pub fn accepts(&self, input: &[usize]) -> bool {
self.accept[self.run(input)]
}
pub fn run_batch(&self, inputs: &[&[usize]]) -> Vec<usize> {
let b = inputs.len();
let q = self.num_states;
if b == 0 {
return Vec::new();
}
let max_len = inputs.iter().map(|s| s.len()).max().unwrap_or(0);
let mut states = vec![0.0f32; b * q];
for row in states.chunks_exact_mut(q) {
row[self.start] = 1.0;
}
for t in 0..max_len {
let mut next = vec![0.0f32; b * q];
for (i, input) in inputs.iter().enumerate() {
if t >= input.len() {
next[i * q..(i + 1) * q].copy_from_slice(&states[i * q..(i + 1) * q]);
}
}
for a in 0..self.num_symbols {
let mut masked = vec![0.0f32; b * q];
let mut any = false;
for (i, input) in inputs.iter().enumerate() {
if t < input.len() && input[t] == a {
masked[i * q..(i + 1) * q].copy_from_slice(&states[i * q..(i + 1) * q]);
any = true;
}
}
if !any {
continue;
}
let m = PaddedTileLattice::from_dense(b, q, &masked, self.geom).unwrap();
let advanced = m.matmul(&self.matrices[a]).unwrap().to_dense();
for (n, adv) in next.iter_mut().zip(&advanced) {
*n += adv;
}
}
states = next;
}
states
.chunks_exact(q)
.map(TensorAutomaton::argmax_row)
.collect()
}
pub fn batch_accepts(&self, inputs: &[&[usize]]) -> Vec<bool> {
self.run_batch(inputs)
.into_iter()
.map(|s| self.accept[s])
.collect()
}
}
impl core::fmt::Debug for TensorAutomaton {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"TensorAutomaton {{ states: {}, symbols: {} }}",
self.num_states, self.num_symbols
)
}
}