use std::f64::consts::TAU;
#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Copy, Clone, Default, Hash)]
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(rename_all = "lowercase")
)]
pub enum Stat {
#[default]
#[cfg_attr(feature = "serde", serde(alias = "C1B1"))]
C1B1 = 1,
#[cfg_attr(feature = "serde", serde(alias = "C1B2"))]
C1B2 = 2,
#[cfg_attr(feature = "serde", serde(alias = "C2B1"))]
C2B1 = 3,
#[cfg_attr(feature = "serde", serde(alias = "C2B2"))]
C2B2 = 4,
}
impl std::fmt::Display for Stat {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::C1B1 => write!(f, "Circuit 1-1"),
Self::C1B2 => write!(f, "Circuit 1-2"),
Self::C2B1 => write!(f, "Circuit 2-1"),
Self::C2B2 => write!(f, "Circuit 2-2"),
}
}
}
#[derive(Debug)]
pub struct StatError;
impl std::fmt::Display for StatError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "invalid state")
}
}
impl std::error::Error for StatError {}
impl TryFrom<u8> for Stat {
type Error = StatError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
1 => Ok(Self::C1B1),
2 => Ok(Self::C1B2),
3 => Ok(Self::C2B1),
4 => Ok(Self::C2B2),
_ => Err(StatError),
}
}
}
impl Stat {
pub const fn name_lowercase(&self) -> &'static str {
match self {
Self::C1B1 => "c1b1",
Self::C1B2 => "c1b2",
Self::C2B1 => "c2b1",
Self::C2B2 => "c2b2",
}
}
pub const fn name_uppercase(&self) -> &'static str {
match self {
Self::C1B1 => "C1B1",
Self::C1B2 => "C1B2",
Self::C2B1 => "C2B1",
Self::C2B2 => "C2B2",
}
}
pub fn is_c1(&self) -> bool {
matches!(self, Self::C1B1 | Self::C1B2)
}
pub fn is_b1(&self) -> bool {
matches!(self, Self::C1B1 | Self::C2B1)
}
pub fn switch_circuit(&mut self) {
*self = match self {
Self::C1B1 => Self::C2B1,
Self::C1B2 => Self::C2B2,
Self::C2B1 => Self::C1B1,
Self::C2B2 => Self::C1B2,
};
}
pub fn switch_branch(&mut self) {
*self = match self {
Self::C1B1 => Self::C1B2,
Self::C1B2 => Self::C1B1,
Self::C2B1 => Self::C2B2,
Self::C2B2 => Self::C2B1,
};
}
}
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Copy, Clone, PartialEq, Default, Debug)]
pub enum AngleBound {
Closed,
OpenC1B2([f64; 2]),
OpenC2B2([f64; 2]),
#[default]
Invalid,
}
impl AngleBound {
pub const MIN_ANGLE: f64 = std::f64::consts::FRAC_PI_2;
pub const fn description(&self) -> &'static str {
match self {
Self::Closed => "Closed curve",
Self::OpenC1B2(_) => "Open curve with 1 circuits 2 branches",
Self::OpenC2B2(_) => "Open curve with 2 circuits 2 branches",
Self::Invalid => "Invalid",
}
}
pub fn from_planar_loop(mut planar_loop: [f64; 4], stat: Stat) -> Self {
let [l1, l2, l3, l4] = planar_loop;
planar_loop.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());
if planar_loop[3] > planar_loop[..3].iter().sum() {
return Self::Invalid;
}
match (l1 + l2 <= l3 + l4, (l1 - l2).abs() >= (l3 - l4).abs()) {
(true, true) => Self::Closed,
(true, false) => {
let l33 = l3 - l4;
let d = (l1 * l1 + l2 * l2 - l33 * l33) / (2. * l1 * l2);
Self::OpenC1B2([d.acos(), TAU - d.acos()])
}
(false, true) => {
let l33 = l3 + l4;
let d = (l1 * l1 + l2 * l2 - l33 * l33) / (2. * l1 * l2);
Self::OpenC1B2([-d.acos(), d.acos()])
}
(false, false) => {
let tmp1 = l1 * l1 + l2 * l2;
let tmp2 = 2. * l1 * l2;
let l33 = l3 - l4;
let d1 = (tmp1 - l33 * l33) / tmp2;
let l33 = l3 + l4;
let d2 = (tmp1 - l33 * l33) / tmp2;
if stat.is_b1() {
Self::OpenC2B2([TAU - d2.acos(), TAU - d1.acos()])
} else {
Self::OpenC2B2([d1.acos(), d2.acos()])
}
}
}
}
pub fn open_and_rev_at(a: f64, b: f64) -> [Self; 2] {
[Self::OpenC1B2([a, b]), Self::OpenC1B2([b, a])]
}
pub fn check_mode(self, is_open: bool) -> Self {
if self.is_valid() && self.is_open() == is_open {
self
} else {
Self::Invalid
}
}
pub fn check_min(self) -> Self {
match self {
Self::OpenC1B2([a, b]) | Self::OpenC2B2([a, b]) => {
let b = if b > a { b } else { b + TAU };
if b - a > Self::MIN_ANGLE {
self
} else {
Self::Invalid
}
}
_ => self,
}
}
pub fn to_value(self) -> Option<[f64; 2]> {
match self {
Self::Closed => Some([0., TAU]),
Self::OpenC1B2(a) | Self::OpenC2B2(a) => Some(a),
Self::Invalid => None,
}
}
pub fn is_open(&self) -> bool {
matches!(self, Self::OpenC1B2(_) | Self::OpenC2B2(_))
}
pub fn is_valid(&self) -> bool {
!matches!(self, Self::Invalid)
}
pub fn get_states(&self) -> Vec<Stat> {
match self {
Self::Closed => vec![Stat::C1B1, Stat::C2B1],
Self::OpenC1B2(_) => vec![Stat::C1B1, Stat::C1B2],
Self::OpenC2B2(_) => vec![Stat::C1B1, Stat::C1B2, Stat::C2B1, Stat::C2B2],
Self::Invalid => vec![Stat::C1B1],
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[allow(clippy::upper_case_acronyms)]
pub enum FourBarTy {
GCCC,
GCRR,
GRCR,
GRRC,
RRR1,
RRR2,
RRR3,
RRR4,
Invalid,
}
impl FourBarTy {
pub fn from_loop(fb_loop: [f64; 4]) -> Self {
let mut i = 0;
let mut fb_loop = fb_loop.map(|l| {
let ret = (i, l);
i += 1;
ret
});
fb_loop.sort_unstable_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap());
let [(s_ind, s), (_, p), (_, q), (l_ind, l)] = fb_loop;
if l > s + p + q {
Self::Invalid
} else if s + l < p + q {
[Self::GCCC, Self::GCRR, Self::GRCR, Self::GRRC][s_ind]
} else {
[Self::RRR1, Self::RRR2, Self::RRR3, Self::RRR4][l_ind]
}
}
pub const fn name(&self) -> &'static str {
match self {
Self::GCCC => "Grashof double crank (Drag-link, GCCC)",
Self::GCRR => "Grashof crank rocker (GCRR)",
Self::GRCR => "Grashof double rocker (GRCR)",
Self::GRRC => "Grashof rocker crank (GRRC)",
Self::RRR1 => "Non-Grashof triple rocker (RRR1)",
Self::RRR2 => "Non-Grashof triple rocker (RRR2)",
Self::RRR3 => "Non-Grashof triple rocker (RRR3)",
Self::RRR4 => "Non-Grashof triple rocker (RRR4)",
Self::Invalid => "Invalid",
}
}
pub const fn is_valid(&self) -> bool {
!matches!(self, Self::Invalid)
}
pub const fn is_grashof(&self) -> bool {
matches!(self, Self::GCCC | Self::GCRR | Self::GRCR | Self::GRRC)
}
pub const fn is_closed_curve(&self) -> bool {
matches!(self, Self::GCCC | Self::GCRR)
}
pub const fn is_open_curve(&self) -> bool {
matches!(
self,
Self::GRCR | Self::GRRC | Self::RRR1 | Self::RRR2 | Self::RRR3 | Self::RRR4
)
}
}
pub trait Statable: PlanarLoop + Clone {
fn stat_mut(&mut self) -> &mut Stat;
fn stat(&self) -> Stat;
fn set_stat(&mut self, stat: Stat) {
*self.stat_mut() = stat;
}
fn with_stat(mut self, stat: Stat) -> Self {
self.set_stat(stat);
self
}
fn ty(&self) -> FourBarTy {
FourBarTy::from_loop(self.planar_loop())
}
fn angle_bound(&self) -> AngleBound {
let stat = self.stat();
AngleBound::from_planar_loop(self.planar_loop(), stat)
}
fn is_valid(&self) -> bool {
self.angle_bound().is_valid()
}
fn is_open(&self) -> bool {
self.angle_bound().is_open()
}
fn inv(&self) -> bool {
!self.stat().is_c1()
}
fn to_states(self) -> Vec<Self> {
let bound = self.angle_bound();
self.states_from_bound(bound)
}
fn other_states(&self) -> Vec<Self> {
self.other_states_from_bound(self.angle_bound())
}
fn states_from_bound(self, bound: AngleBound) -> Vec<Self> {
let mut states = self.other_states_from_bound(bound);
states.push(self);
states
}
fn other_states_from_bound(&self, bound: AngleBound) -> Vec<Self> {
let stat = self.stat();
bound
.get_states()
.into_iter()
.filter(|s| *s != stat)
.map(|s| self.clone().with_stat(s))
.collect()
}
}
impl<S> Statable for S
where
S: std::ops::DerefMut + PlanarLoop + Clone,
S::Target: Statable,
{
fn stat_mut(&mut self) -> &mut Stat {
self.deref_mut().stat_mut()
}
fn stat(&self) -> Stat {
self.deref().stat()
}
}
pub trait PlanarLoop {
fn planar_loop(&self) -> [f64; 4];
fn set_to_planar_loop(&mut self) {}
}