use crate::error::ReaclibError as RError;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
use arrayvec::{ArrayString, ArrayVec};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
hash::Hash,
io::{BufRead, Lines},
ops::Range,
str::FromStr,
};
pub use crate::error::ReaclibError;
mod error;
#[cfg(test)]
mod tests;
pub type Nuclide = ArrayString<5>;
pub type Reaction = (ArrayVec<Nuclide, 4>, ArrayVec<Nuclide, 4>);
#[derive(Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Set {
pub reactants: ArrayVec<Nuclide, 4>,
pub products: ArrayVec<Nuclide, 4>,
pub label: ArrayString<4>,
pub resonance: Resonance,
pub reverse: bool,
pub q_value: f64,
pub params: [f64; 7],
}
impl Set {
fn from_lines(chapter: Chapter, lines: &[String; 3]) -> Result<Self, RError> {
fn range_err(line: &str, range: Range<usize>) -> Result<&str, RError> {
if line.len() < range.end {
Err(RError::TooShortLine)
} else {
Ok(line.get(range).ok_or(RError::StrIndex)?.trim())
}
}
let reactants = (0..chapter.num_reactants())
.map(|i| {
let r = (5 + 5 * i)..(5 + 5 * (i + 1));
Ok(Nuclide::from(range_err(&lines[0], r)?)
.expect("the range is 5 and the capacity is 5"))
})
.collect::<Result<_, RError>>()?;
let products = (chapter.num_reactants()
..(chapter.num_reactants() + chapter.num_products()))
.map(|i| {
let r = (5 + 5 * i)..(5 + 5 * (i + 1));
Ok(Nuclide::from(range_err(&lines[0], r)?)
.expect("the range is 5 and the capacity is 5"))
})
.collect::<Result<_, RError>>()?;
let label = ArrayString::from(range_err(&lines[0], 43..47)?)
.expect("the range is 4 and the capacity is 4");
let resonance = range_err(&lines[0], 47..48)?.parse()?;
let reverse = range_err(&lines[0], 48..49)? == "v";
let q_value = range_err(&lines[0], 52..64)?.parse()?;
let params = [
range_err(&lines[1], 0..13)?.parse()?,
range_err(&lines[1], 13..26)?.parse()?,
range_err(&lines[1], 26..39)?.parse()?,
range_err(&lines[1], 39..52)?.parse()?,
range_err(&lines[2], 0..13)?.parse()?,
range_err(&lines[2], 13..26)?.parse()?,
range_err(&lines[2], 26..39)?.parse()?,
];
Ok(Self {
reactants,
products,
label,
resonance,
reverse,
q_value,
params,
})
}
#[must_use]
pub fn rate(&self, temperature: f64) -> f64 {
#[allow(clippy::cast_precision_loss)]
let sum = (1..=5)
.map(|i| self.params[i] * f64::powf(temperature, 2.0 * (i as f64) * 5.0 / 3.0))
.sum::<f64>();
f64::exp(self.params[6].mul_add(f64::ln(temperature), self.params[0] + sum))
}
}
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for Set {
fn arbitrary(u: &mut Unstructured) -> arbitrary::Result<Self> {
fn array_string<const CAP: usize>(
u: &mut Unstructured,
) -> arbitrary::Result<ArrayString<CAP>> {
let size = usize::min(u.arbitrary_len::<u8>()?, CAP);
match std::str::from_utf8(u.peek_bytes(size).unwrap()) {
Ok(s) => {
u.bytes(size).unwrap();
Ok(ArrayString::from(s).expect("size is limited to CAP"))
}
Err(e) => {
let i = e.valid_up_to();
let valid = u.bytes(i).unwrap();
let s = ArrayString::from(
std::str::from_utf8(valid).expect("we already checked for validity"),
)
.expect("size is limited to CAP");
Ok(s)
}
}
}
let chapter: Chapter = u.arbitrary()?;
let mut reactants = ArrayVec::new();
for _ in 0..(chapter.num_reactants()) {
reactants.push(array_string(u)?);
}
let mut products = ArrayVec::new();
for _ in 0..(chapter.num_products()) {
products.push(array_string(u)?);
}
let label = array_string(u)?;
let resonance = u.arbitrary()?;
let reverse = u.arbitrary()?;
let q_value = u.arbitrary()?;
let params = u.arbitrary()?;
Ok(Self {
reactants,
products,
label,
resonance,
reverse,
q_value,
params,
})
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[non_exhaustive]
pub enum Resonance {
NonResonant,
Resonant,
Weak,
S,
}
impl FromStr for Resonance {
type Err = RError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"" | " " | "n" => Ok(Self::NonResonant),
"r" => Ok(Self::Resonant),
"w" => Ok(Self::Weak),
"s" => Ok(Self::S),
_ => Err(RError::UnknownResonance(s.to_string())),
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[non_exhaustive]
pub enum Format {
Reaclib1,
Reaclib2,
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[non_exhaustive]
pub enum Chapter {
Chapter1,
Chapter2,
Chapter3,
Chapter4,
Chapter5,
Chapter6,
Chapter7,
Chapter8,
Chapter9,
Chapter10,
Chapter11,
}
impl Chapter {
#[must_use]
pub const fn num_reactants(&self) -> usize {
#[allow(clippy::match_same_arms)]
match self {
Self::Chapter1 => 1,
Self::Chapter2 => 1,
Self::Chapter3 => 1,
Self::Chapter4 => 2,
Self::Chapter5 => 2,
Self::Chapter6 => 2,
Self::Chapter7 => 2,
Self::Chapter8 => 3,
Self::Chapter9 => 3,
Self::Chapter10 => 4,
Self::Chapter11 => 1,
}
}
#[must_use]
pub const fn num_products(&self) -> usize {
#[allow(clippy::match_same_arms)]
match self {
Self::Chapter1 => 1,
Self::Chapter2 => 2,
Self::Chapter3 => 3,
Self::Chapter4 => 1,
Self::Chapter5 => 2,
Self::Chapter6 => 3,
Self::Chapter7 => 4,
Self::Chapter8 => 1,
Self::Chapter9 => 2,
Self::Chapter10 => 2,
Self::Chapter11 => 4,
}
}
fn from_lines_v1(lines: &[String; 3]) -> Option<Result<Self, RError>> {
let [l1, l2, l3] = lines;
if l2.trim().is_empty() && l3.trim().is_empty() {
match l1.trim().parse::<u8>() {
Ok(c) => Some(c.try_into()),
Err(e) => Some(Err(e.into())),
}
} else {
None
}
}
fn from_lines_v2(line: &str) -> Result<Self, RError> {
line.trim().parse::<u8>()?.try_into()
}
}
impl TryFrom<u8> for Chapter {
type Error = RError;
fn try_from(x: u8) -> Result<Self, Self::Error> {
match x {
1 => Ok(Self::Chapter1),
2 => Ok(Self::Chapter2),
3 => Ok(Self::Chapter3),
4 => Ok(Self::Chapter4),
5 => Ok(Self::Chapter5),
6 => Ok(Self::Chapter6),
7 => Ok(Self::Chapter7),
8 => Ok(Self::Chapter8),
9 => Ok(Self::Chapter9),
10 => Ok(Self::Chapter10),
11 => Ok(Self::Chapter11),
_ => Err(RError::UnknownChapter(x)),
}
}
}
pub struct Iter<R: BufRead> {
lines: Lines<R>,
format: Format,
chapter: Option<Chapter>,
}
impl<R: BufRead> Iter<R> {
pub fn new(reader: R, format: Format) -> Self {
let lines = reader.lines();
Self {
lines,
format,
chapter: None,
}
}
fn next_v1(&mut self) -> Option<<Self as Iterator>::Item> {
loop {
let lines = match (self.lines.next(), self.lines.next(), self.lines.next()) {
(None, _, _) => return None,
(_, None, _) | (_, _, None) => {
return Some(Err(RError::TooFewLines));
}
(Some(Err(e)), _, _) | (_, Some(Err(e)), _) | (_, _, Some(Err(e))) => {
return Some(Err(e.into()));
}
(Some(Ok(l1)), Some(Ok(l2)), Some(Ok(l3))) => [l1, l2, l3],
};
match Chapter::from_lines_v1(&lines) {
Some(Ok(chapter)) => {
self.chapter = Some(chapter);
continue;
}
Some(Err(e)) => {
break Some(Err(e));
}
None => {
if let Some(chapter) = self.chapter {
break Some(Set::from_lines(chapter, &lines));
}
break Some(Err(RError::ChapterUnset));
}
}
}
}
fn next_v2(&mut self) -> Option<<Self as Iterator>::Item> {
let (ch_line, set_lines) = match (
self.lines.next(),
self.lines.next(),
self.lines.next(),
self.lines.next(),
) {
(None, _, _, _) => return None,
(_, None, _, _) | (_, _, None, _) | (_, _, _, None) => {
return Some(Err(RError::TooFewLines));
}
(Some(Err(e)), _, _, _)
| (_, Some(Err(e)), _, _)
| (_, _, Some(Err(e)), _)
| (_, _, _, Some(Err(e))) => {
return Some(Err(e.into()));
}
(Some(Ok(l1)), Some(Ok(l2)), Some(Ok(l3)), Some(Ok(l4))) => (l1, [l2, l3, l4]),
};
match Chapter::from_lines_v2(&ch_line) {
Ok(chapter) => Some(Set::from_lines(chapter, &set_lines)),
Err(e) => Some(Err(e)),
}
}
}
impl<R: BufRead> Iterator for Iter<R> {
type Item = Result<Set, RError>;
fn next(&mut self) -> Option<Self::Item> {
match self.format {
Format::Reaclib1 => self.next_v1(),
Format::Reaclib2 => self.next_v2(),
}
}
}
pub fn to_hash_map<R: BufRead>(
reader: R,
format: Format,
) -> Result<HashMap<Reaction, Vec<Set>>, RError> {
let mut m = HashMap::new();
for set in Iter::new(reader, format) {
let set = set?;
let key = (set.reactants.clone(), set.products.clone());
m.entry(key).or_insert_with(Vec::new).push(set);
}
Ok(m)
}