use ndarray::{s, Array1, Array2};
use regex::Regex;
use std::collections::{BTreeSet, HashMap};
use crate::channel::*;
use crate::instruction::*;
use crate::utils::*;
pub trait BaseDevice {
fn channels(&self) -> &HashMap<String, Channel>;
fn name(&self) -> &str;
fn task_type(&self) -> TaskType;
fn samp_rate(&self) -> f64;
fn samp_clk_src(&self) -> Option<&str>;
fn trig_line(&self) -> Option<&str>;
fn export_trig(&self) -> Option<bool>;
fn ref_clk_line(&self) -> Option<&str>;
fn export_ref_clk(&self) -> Option<bool>;
fn ref_clk_rate(&self) -> Option<f64>;
fn channels_(&mut self) -> &mut HashMap<String, Channel>;
fn samp_clk_src_(&mut self) -> &mut Option<String>;
fn trig_line_(&mut self) -> &mut Option<String>;
fn export_trig_(&mut self) -> &mut Option<bool>;
fn ref_clk_line_(&mut self) -> &mut Option<String>;
fn export_ref_clk_(&mut self) -> &mut Option<bool>;
fn ref_clk_rate_(&mut self) -> &mut Option<f64>;
fn cfg_samp_clk_src(&mut self, src: &str) {
*(self.samp_clk_src_()) = Some(src.to_string());
}
fn cfg_trig(&mut self, trig_line: &str, export_trig: bool) {
*(self.trig_line_()) = Some(trig_line.to_string());
*(self.export_trig_()) = Some(export_trig);
}
fn cfg_ref_clk(&mut self, ref_clk_line: &str, ref_clk_rate: f64, export_ref_clk: bool) {
if export_ref_clk {
assert_eq!(ref_clk_rate, 1e7,
"Device {} needs to explicitly acknowledge exporting 10Mhz clk by setting ref_clk_rate=1e7",
self.name());
}
*(self.ref_clk_line_()) = Some(ref_clk_line.to_string());
*(self.ref_clk_rate_()) = Some(ref_clk_rate);
*(self.export_ref_clk_()) = Some(export_ref_clk);
}
fn editable_channels(&self) -> Vec<&Channel> {
self.channels()
.values()
.filter(|&chan| chan.editable())
.collect()
}
fn editable_channels_(&mut self) -> Vec<&mut Channel> {
self.channels_()
.values_mut()
.filter(|chan| (*chan).editable())
.collect()
}
fn add_channel(&mut self, name: &str) {
let (name_match_string, name_format_description) = match self.task_type() {
TaskType::AO => (String::from(r"^ao\d+$"), String::from("ao(number)")),
TaskType::DO => (
String::from(r"^port\d+/line\d+$"),
String::from("port(number)/line(number)"),
),
};
let re = Regex::new(&name_match_string).unwrap();
if !re.is_match(name) {
panic!(
"Expecting channels to be of format '{}' yet received channel name {}",
name_format_description, name
);
}
for channel in self.channels().values() {
if channel.name() == name {
panic!(
"Physical name of channel {} already registered. Registered channels are {:?}",
name,
self.channels()
.values()
.map(|c| c.name())
.collect::<Vec<_>>()
);
}
}
let new_channel = Channel::new(self.task_type(), name, self.samp_rate());
self.channels_().insert(name.to_string(), new_channel);
}
fn is_compiled(&self) -> bool {
self.editable_channels()
.iter()
.any(|channel| channel.is_compiled())
}
fn is_edited(&self) -> bool {
self.editable_channels()
.iter()
.any(|channel| channel.is_edited())
}
fn is_fresh_compiled(&self) -> bool {
self.editable_channels()
.iter()
.all(|channel| channel.is_fresh_compiled())
}
fn clear_edit_cache(&mut self) {
self.editable_channels_()
.iter_mut()
.for_each(|chan| chan.clear_edit_cache());
}
fn clear_compile_cache(&mut self) {
self.editable_channels_()
.iter_mut()
.for_each(|chan| chan.clear_compile_cache());
}
fn compile(&mut self, stop_pos: usize) {
self.editable_channels_()
.iter_mut()
.for_each(|chan| chan.compile(stop_pos));
if self.task_type() != TaskType::DO {
return;
}
for match_port in self.unique_port_numbers() {
let mut instr_end_set = BTreeSet::new();
instr_end_set.extend(
self.editable_channels()
.iter()
.filter(|chan| extract_port_line_numbers(chan.name()).0 == match_port)
.flat_map(|chan| chan.instr_end().iter()),
);
let instr_end: Vec<usize> = instr_end_set.into_iter().collect();
let mut instr_val = vec![0.; instr_end.len()];
for chan in self.editable_channels() {
let (port, line) = extract_port_line_numbers(chan.name());
if port == match_port {
let mut chan_instr_idx = 0;
for i in 0..instr_val.len() {
assert!(chan_instr_idx < chan.instr_end().len());
let chan_value =
chan.instr_val()[chan_instr_idx].args.get("value").unwrap();
instr_val[i] += *chan_value as f64 * 2.0f64.powf(line as f64);
if instr_end[i] == chan.instr_end()[chan_instr_idx] {
chan_instr_idx += 1;
}
}
}
}
let port_instr_val: Vec<Instruction> = instr_val
.iter()
.map(|&val| Instruction::new_const(val))
.collect();
let mut port_channel = Channel::new(
TaskType::DO,
&format!("port{}", match_port),
self.samp_rate(),
);
*port_channel.instr_val_() = port_instr_val;
*port_channel.instr_end_() = instr_end;
self.channels_()
.insert(port_channel.name().to_string(), port_channel);
}
}
fn compiled_channels(&self, require_streamable: bool, require_editable: bool) -> Vec<&Channel> {
self.channels()
.values()
.filter(|chan| {
chan.is_compiled()
&& (!require_streamable || chan.streamable())
&& (!require_editable || chan.editable())
})
.collect()
}
fn compiled_stop_time(&self) -> f64 {
self.compiled_channels(false, false)
.iter()
.map(|chan| chan.compiled_stop_time())
.fold(0.0, f64::max)
}
fn edit_stop_time(&self) -> f64 {
self.editable_channels()
.iter()
.map(|chan| chan.edit_stop_time())
.fold(0.0, f64::max)
}
fn fill_signal_nsamps(
&self,
start_pos: usize,
end_pos: usize,
nsamps: usize,
buffer: &mut ndarray::Array2<f64>,
require_streamable: bool,
require_editable: bool,
) {
assert!(
buffer.dim().0
== self
.compiled_channels(require_streamable, require_editable)
.len(),
"Device {} has {} channels but passed buffer has shape {:?}",
self.name(),
self.compiled_channels(require_streamable, require_editable)
.len(),
buffer.dim()
);
assert!(
buffer.dim().1 == nsamps,
"Simulating position {}-{} with {} elements, but buffer has shape {:?}",
start_pos,
end_pos,
nsamps,
buffer.dim()
);
for (i, chan) in self
.compiled_channels(require_streamable, require_editable)
.iter()
.enumerate()
{
let mut channel_slice = buffer.slice_mut(s![i, ..]);
chan.fill_signal_nsamps(start_pos, end_pos, nsamps, &mut channel_slice);
}
}
fn calc_signal_nsamps(
&self,
start_pos: usize,
end_pos: usize,
nsamps: usize,
require_streamable: bool,
require_editable: bool,
) -> Array2<f64> {
let num_chans = self
.compiled_channels(require_streamable, require_editable)
.len();
assert!(
num_chans > 0,
"There is no channel with streamable={}, editable={}",
require_streamable,
require_editable
);
let mut buffer = Array2::from_elem((num_chans, nsamps), 0.);
if self.task_type() == TaskType::AO {
let t_values = Array1::linspace(
start_pos as f64 / self.samp_rate(),
end_pos as f64 / self.samp_rate(),
nsamps,
);
buffer
.outer_iter_mut()
.for_each(|mut row| row.assign(&t_values));
}
self.fill_signal_nsamps(
start_pos,
end_pos,
nsamps,
&mut buffer,
require_streamable,
require_editable,
);
buffer
}
fn unique_port_numbers(&self) -> Vec<usize> {
assert!(
self.task_type() == TaskType::DO,
"unique ports should only be invoked for DOs, but {} is not",
self.name()
);
let mut port_numbers = BTreeSet::new();
self.compiled_channels(false, true).iter().for_each(|chan| {
let name = &chan.name();
port_numbers.insert(extract_port_line_numbers(name).0);
});
port_numbers.into_iter().collect()
}
}
pub struct Device {
channels: HashMap<String, Channel>,
name: String,
task_type: TaskType,
samp_rate: f64,
samp_clk_src: Option<String>,
trig_line: Option<String>,
export_trig: Option<bool>,
ref_clk_line: Option<String>,
export_ref_clk: Option<bool>,
ref_clk_rate: Option<f64>,
}
impl Device {
pub fn new(name: &str, task_type: TaskType, samp_rate: f64) -> Self {
Self {
channels: HashMap::new(),
name: name.to_string(),
task_type,
samp_rate,
samp_clk_src: None,
trig_line: None,
export_trig: None,
ref_clk_line: None,
export_ref_clk: None,
ref_clk_rate: None,
}
}
}
impl BaseDevice for Device {
fn channels(&self) -> &HashMap<String, Channel> {
&self.channels
}
fn name(&self) -> &str {
&self.name
}
fn task_type(&self) -> TaskType {
self.task_type
}
fn samp_rate(&self) -> f64 {
self.samp_rate
}
fn samp_clk_src(&self) -> Option<&str> {
self.samp_clk_src.as_deref()
}
fn trig_line(&self) -> Option<&str> {
self.trig_line.as_deref()
}
fn export_trig(&self) -> Option<bool> {
self.export_trig
}
fn ref_clk_line(&self) -> Option<&str> {
self.ref_clk_line.as_deref()
}
fn export_ref_clk(&self) -> Option<bool> {
self.export_ref_clk
}
fn ref_clk_rate(&self) -> Option<f64> {
self.ref_clk_rate
}
fn channels_(&mut self) -> &mut HashMap<String, Channel> {
&mut self.channels
}
fn samp_clk_src_(&mut self) -> &mut Option<String> {
&mut self.samp_clk_src
}
fn trig_line_(&mut self) -> &mut Option<String> {
&mut self.trig_line
}
fn export_trig_(&mut self) -> &mut Option<bool> {
&mut self.export_trig
}
fn ref_clk_line_(&mut self) -> &mut Option<String> {
&mut self.ref_clk_line
}
fn export_ref_clk_(&mut self) -> &mut Option<bool> {
&mut self.export_ref_clk
}
fn ref_clk_rate_(&mut self) -> &mut Option<f64> {
&mut self.ref_clk_rate
}
}