use super::error::RealizationError;
use super::markov::MarkovSequence;
use faer::{Mat, MatRef};
#[derive(Clone, Debug, PartialEq)]
pub struct BlockHankel<T> {
start_index: usize,
row_blocks: usize,
col_blocks: usize,
noutputs: usize,
ninputs: usize,
matrix: Mat<T>,
}
impl<T> BlockHankel<T> {
pub fn from_markov(
sequence: &MarkovSequence<T>,
start_index: usize,
row_blocks: usize,
col_blocks: usize,
) -> Result<Self, RealizationError>
where
T: Copy,
{
validate_hankel_request(sequence.len(), start_index, row_blocks, col_blocks)?;
let (noutputs, ninputs) = sequence.block_shape();
let (nrows, ncols) = hankel_matrix_shape(noutputs, ninputs, row_blocks, col_blocks);
let matrix = Mat::from_fn(nrows, ncols, |row, col| {
let block_row = row / noutputs;
let row_in_block = row % noutputs;
let block_col = col / ninputs;
let col_in_block = col % ninputs;
sequence.block(start_index + block_row + block_col)[(row_in_block, col_in_block)]
});
Ok(Self {
start_index,
row_blocks,
col_blocks,
noutputs,
ninputs,
matrix,
})
}
#[must_use]
pub fn start_index(&self) -> usize {
self.start_index
}
#[must_use]
pub fn row_blocks(&self) -> usize {
self.row_blocks
}
#[must_use]
pub fn col_blocks(&self) -> usize {
self.col_blocks
}
#[must_use]
pub fn noutputs(&self) -> usize {
self.noutputs
}
#[must_use]
pub fn ninputs(&self) -> usize {
self.ninputs
}
#[must_use]
pub fn matrix(&self) -> MatRef<'_, T> {
self.matrix.as_ref()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ShiftedBlockHankelPair<T> {
h0: BlockHankel<T>,
h1: BlockHankel<T>,
}
impl<T> ShiftedBlockHankelPair<T> {
pub fn from_markov(
sequence: &MarkovSequence<T>,
row_blocks: usize,
col_blocks: usize,
) -> Result<Self, RealizationError>
where
T: Copy,
{
validate_hankel_request(sequence.len(), 1, row_blocks, col_blocks)?;
validate_hankel_request(sequence.len(), 2, row_blocks, col_blocks)?;
Ok(Self {
h0: BlockHankel::from_markov(sequence, 1, row_blocks, col_blocks)?,
h1: BlockHankel::from_markov(sequence, 2, row_blocks, col_blocks)?,
})
}
#[must_use]
pub fn h0(&self) -> &BlockHankel<T> {
&self.h0
}
#[must_use]
pub fn h1(&self) -> &BlockHankel<T> {
&self.h1
}
}
#[must_use]
pub fn hankel_matrix_shape(
noutputs: usize,
ninputs: usize,
row_blocks: usize,
col_blocks: usize,
) -> (usize, usize) {
(noutputs * row_blocks, ninputs * col_blocks)
}
#[must_use]
pub fn required_markov_len(start_index: usize, row_blocks: usize, col_blocks: usize) -> usize {
start_index + row_blocks + col_blocks - 1
}
#[must_use]
pub fn max_square_era_block_dim(markov_len: usize) -> usize {
markov_len.saturating_sub(1) / 2
}
fn validate_hankel_request(
sequence_len: usize,
start_index: usize,
row_blocks: usize,
col_blocks: usize,
) -> Result<(), RealizationError> {
if row_blocks == 0 {
return Err(RealizationError::ZeroBlockCount {
which: "row_blocks",
});
}
if col_blocks == 0 {
return Err(RealizationError::ZeroBlockCount {
which: "col_blocks",
});
}
let required = required_markov_len(start_index, row_blocks, col_blocks);
if sequence_len < required {
return Err(RealizationError::SequenceTooShort {
available: sequence_len,
required,
start_index,
row_blocks,
col_blocks,
});
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::{
BlockHankel, ShiftedBlockHankelPair, max_square_era_block_dim, required_markov_len,
};
use crate::control::realization::{MarkovSequence, RealizationError};
use faer::Mat;
fn scalar_markov(values: &[f64]) -> MarkovSequence<f64> {
MarkovSequence::from_blocks(
values
.iter()
.map(|&value| Mat::from_fn(1, 1, |_, _| value))
.collect(),
)
.unwrap()
}
#[test]
fn block_hankel_places_scalar_blocks_correctly() {
let seq = scalar_markov(&[10.0, 11.0, 12.0, 13.0, 14.0]);
let hankel = BlockHankel::from_markov(&seq, 1, 2, 3).unwrap();
assert_eq!(hankel.start_index(), 1);
assert_eq!(hankel.matrix()[(0, 0)], 11.0);
assert_eq!(hankel.matrix()[(0, 1)], 12.0);
assert_eq!(hankel.matrix()[(0, 2)], 13.0);
assert_eq!(hankel.matrix()[(1, 0)], 12.0);
assert_eq!(hankel.matrix()[(1, 1)], 13.0);
assert_eq!(hankel.matrix()[(1, 2)], 14.0);
}
#[test]
fn shifted_pair_is_one_step_shifted() {
let seq = scalar_markov(&[10.0, 11.0, 12.0, 13.0, 14.0]);
let pair = ShiftedBlockHankelPair::from_markov(&seq, 1, 2).unwrap();
assert_eq!(pair.h0().start_index(), 1);
assert_eq!(pair.h1().start_index(), 2);
assert_eq!(pair.h0().matrix()[(0, 0)], 11.0);
assert_eq!(pair.h0().matrix()[(0, 1)], 12.0);
assert_eq!(pair.h1().matrix()[(0, 0)], 12.0);
assert_eq!(pair.h1().matrix()[(0, 1)], 13.0);
}
#[test]
fn shifted_pair_requires_enough_markov_blocks() {
let seq = scalar_markov(&[1.0, 2.0, 3.0]);
let err = ShiftedBlockHankelPair::from_markov(&seq, 2, 2).unwrap_err();
assert_eq!(
err,
RealizationError::SequenceTooShort {
available: 3,
required: 4,
start_index: 1,
row_blocks: 2,
col_blocks: 2,
}
);
}
#[test]
fn era_block_helpers_match_length_constraint() {
assert_eq!(required_markov_len(1, 2, 3), 5);
assert_eq!(max_square_era_block_dim(0), 0);
assert_eq!(max_square_era_block_dim(1), 0);
assert_eq!(max_square_era_block_dim(5), 2);
assert_eq!(max_square_era_block_dim(7), 3);
}
}