use crate::ChiralTag;
struct Table {
rotations: &'static [&'static [u8]],
slots: &'static [&'static [u8]],
}
const SP_ROTATIONS: &[&[u8]] = &[
&[0, 1, 2, 3],
&[0, 3, 2, 1],
&[1, 0, 3, 2],
&[1, 2, 3, 0],
&[2, 1, 0, 3],
&[2, 3, 0, 1],
&[3, 0, 1, 2],
&[3, 2, 1, 0],
];
const SP_SLOTS: &[&[u8]] = &[&[0, 1, 2, 3], &[0, 2, 1, 3], &[0, 1, 3, 2]];
const TB_ROTATIONS: &[&[u8]] = &[
&[0, 1, 2, 3, 4],
&[0, 2, 3, 1, 4],
&[0, 3, 1, 2, 4],
&[4, 1, 3, 2, 0],
&[4, 2, 1, 3, 0],
&[4, 3, 2, 1, 0],
];
const TB_SLOTS: &[&[u8]] = &[
&[0, 1, 2, 3, 4],
&[0, 1, 3, 2, 4],
&[0, 1, 2, 4, 3],
&[0, 1, 4, 2, 3],
&[0, 1, 3, 4, 2],
&[0, 1, 4, 3, 2],
&[0, 2, 3, 4, 1],
&[0, 2, 4, 3, 1],
&[1, 0, 2, 3, 4],
&[1, 0, 2, 4, 3],
&[1, 0, 3, 2, 4],
&[1, 0, 4, 2, 3],
&[1, 0, 3, 4, 2],
&[1, 0, 4, 3, 2],
&[2, 0, 1, 3, 4],
&[2, 0, 1, 4, 3],
&[3, 0, 1, 2, 4],
&[3, 0, 2, 1, 4],
&[2, 0, 4, 1, 3],
&[2, 0, 3, 1, 4],
];
const OH_ROTATIONS: &[&[u8]] = &[
&[0, 1, 2, 3, 4, 5],
&[0, 2, 3, 4, 1, 5],
&[0, 3, 4, 1, 2, 5],
&[0, 4, 1, 2, 3, 5],
&[1, 0, 4, 5, 2, 3],
&[1, 2, 0, 4, 5, 3],
&[1, 4, 5, 2, 0, 3],
&[1, 5, 2, 0, 4, 3],
&[2, 0, 1, 5, 3, 4],
&[2, 1, 5, 3, 0, 4],
&[2, 3, 0, 1, 5, 4],
&[2, 5, 3, 0, 1, 4],
&[3, 0, 2, 5, 4, 1],
&[3, 2, 5, 4, 0, 1],
&[3, 4, 0, 2, 5, 1],
&[3, 5, 4, 0, 2, 1],
&[4, 0, 3, 5, 1, 2],
&[4, 1, 0, 3, 5, 2],
&[4, 3, 5, 1, 0, 2],
&[4, 5, 1, 0, 3, 2],
&[5, 1, 4, 3, 2, 0],
&[5, 2, 1, 4, 3, 0],
&[5, 3, 2, 1, 4, 0],
&[5, 4, 3, 2, 1, 0],
];
const OH_SLOTS: &[&[u8]] = &[
&[0, 1, 2, 3, 4, 5],
&[0, 1, 4, 3, 2, 5],
&[0, 1, 2, 3, 5, 4],
&[0, 1, 2, 4, 3, 5],
&[0, 1, 2, 5, 3, 4],
&[0, 1, 2, 4, 5, 3],
&[0, 1, 2, 5, 4, 3],
&[0, 1, 3, 2, 4, 5],
&[0, 1, 3, 2, 5, 4],
&[0, 1, 4, 2, 3, 5],
&[0, 1, 5, 2, 3, 4],
&[0, 1, 4, 2, 5, 3],
&[0, 1, 5, 2, 4, 3],
&[0, 1, 3, 4, 2, 5],
&[0, 1, 3, 5, 2, 4],
&[0, 1, 5, 3, 2, 4],
&[0, 1, 4, 5, 2, 3],
&[0, 1, 5, 4, 2, 3],
&[0, 1, 3, 4, 5, 2],
&[0, 1, 3, 5, 4, 2],
&[0, 1, 4, 3, 5, 2],
&[0, 1, 5, 3, 4, 2],
&[0, 1, 4, 5, 3, 2],
&[0, 1, 5, 4, 3, 2],
&[0, 2, 3, 4, 5, 1],
&[0, 2, 3, 5, 4, 1],
&[0, 2, 4, 3, 5, 1],
&[0, 2, 5, 3, 4, 1],
&[0, 2, 4, 5, 3, 1],
&[0, 2, 5, 4, 3, 1],
];
fn table_for(tag: ChiralTag) -> Option<Table> {
match tag {
ChiralTag::SquarePlanar => Some(Table {
rotations: SP_ROTATIONS,
slots: SP_SLOTS,
}),
ChiralTag::TrigonalBipyramidal => Some(Table {
rotations: TB_ROTATIONS,
slots: TB_SLOTS,
}),
ChiralTag::Octahedral => Some(Table {
rotations: OH_ROTATIONS,
slots: OH_SLOTS,
}),
_ => None,
}
}
#[must_use]
pub fn ligand_count(tag: ChiralTag) -> Option<usize> {
table_for(tag).map(|t| t.slots[0].len())
}
#[must_use]
pub fn renumber(tag: ChiralTag, perm: u8, from: &[u32], to: &[u32]) -> Option<u8> {
let t = table_for(tag)?;
let n = t.slots[0].len();
if from.len() != n || to.len() != n {
return None;
}
let p_i = *t.slots.get(usize::from(perm).checked_sub(1)?)?;
let mut q = [0u8; 6];
for k in 0..n {
let ligand = *from.get(usize::from(p_i[k]))?;
let pos = to.iter().position(|&x| x == ligand)?;
q[k] = u8::try_from(pos).ok()?;
}
for (j, p_j) in t.slots.iter().enumerate() {
if t.rotations
.iter()
.any(|r| (0..n).all(|k| usize::from(p_j[k]) == usize::from(q[usize::from(r[k])])))
{
return u8::try_from(j + 1).ok();
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
fn permutations(n: usize) -> Vec<Vec<u32>> {
let mut out = Vec::new();
let mut cur: Vec<u32> = (0..n as u32).collect();
fn go(k: usize, cur: &mut Vec<u32>, out: &mut Vec<Vec<u32>>) {
if k == cur.len() {
out.push(cur.clone());
return;
}
for i in k..cur.len() {
cur.swap(k, i);
go(k + 1, cur, out);
cur.swap(k, i);
}
}
go(0, &mut cur, &mut out);
out
}
fn factorial(n: usize) -> usize {
(1..=n).product()
}
const ALL: [ChiralTag; 3] = [
ChiralTag::SquarePlanar,
ChiralTag::TrigonalBipyramidal,
ChiralTag::Octahedral,
];
#[test]
fn 排列表是转动群的一组陪集代表元() {
for tag in ALL {
let t = table_for(tag).expect("有表");
let n = t.slots[0].len();
assert_eq!(
t.slots.len() * t.rotations.len(),
factorial(n),
"{tag:?}:序号数 {} × 转动群阶 {} ≠ {n}! = {}",
t.slots.len(),
t.rotations.len(),
factorial(n)
);
for (a, pa) in t.slots.iter().enumerate() {
for (b, pb) in t.slots.iter().enumerate().skip(a + 1) {
let same = t
.rotations
.iter()
.any(|r| (0..n).all(|k| pa[k] == pb[usize::from(r[k])]));
assert!(
!same,
"{tag:?}:序号 {} 与 {} 落在同一个陪集里",
a + 1,
b + 1
);
}
}
}
}
#[test]
fn 转动群是个群() {
for tag in ALL {
let t = table_for(tag).expect("有表");
let n = t.slots[0].len();
let ident: Vec<u8> = (0..n as u8).collect();
assert!(
t.rotations.iter().any(|r| r == &&ident[..]),
"{tag:?}:转动群里没有恒等"
);
for a in t.rotations {
for b in t.rotations {
let ab: Vec<u8> = (0..n).map(|k| a[usize::from(b[k])]).collect();
assert!(
t.rotations.iter().any(|r| r == &&ab[..]),
"{tag:?}:转动群对复合不封闭"
);
}
}
}
}
#[test]
fn 换算可逆且是双射() {
for tag in ALL {
let t = table_for(tag).expect("有表");
let n = t.slots[0].len();
let ident: Vec<u32> = (0..n as u32).collect();
for to in permutations(n) {
let mut seen = vec![false; t.slots.len()];
for i in 1..=t.slots.len() as u8 {
let j = renumber(tag, i, &ident, &to)
.unwrap_or_else(|| panic!("{tag:?}:序号 {i} 换到 {to:?} 换不出来"));
assert!(
!seen[usize::from(j) - 1],
"{tag:?}:两个序号都换成了 {j},换算不是双射"
);
seen[usize::from(j) - 1] = true;
assert_eq!(
renumber(tag, j, &to, &ident),
Some(i),
"{tag:?}:序号 {i} 换过去再换回来不是自己"
);
}
}
}
}
#[test]
fn 平面四方的表与反位配对规则对得上() {
const TRANS: [[(usize, usize); 2]; 3] =
[[(0, 2), (1, 3)], [(0, 1), (2, 3)], [(0, 3), (1, 2)]];
fn pairing(perm: u8, ligands: &[u32]) -> Vec<[u32; 2]> {
let mut v: Vec<[u32; 2]> = TRANS[usize::from(perm) - 1]
.iter()
.map(|&(i, j)| {
let (a, b) = (ligands[i], ligands[j]);
if a <= b {
[a, b]
} else {
[b, a]
}
})
.collect();
v.sort_unstable();
v
}
let ident: Vec<u32> = (0..4).collect();
for to in permutations(4) {
for i in 1..=3u8 {
let j = renumber(ChiralTag::SquarePlanar, i, &ident, &to).expect("换得出来");
assert_eq!(
pairing(i, &ident),
pairing(j, &to),
"序号 {i} 换到顺序 {to:?} 得到 {j},反位配对却变了"
);
}
}
}
#[test]
fn 换算不了时不猜() {
let l: Vec<u32> = (0..4).collect();
assert_eq!(renumber(ChiralTag::SquarePlanar, 0, &l, &l), None, "序号 0");
assert_eq!(
renumber(ChiralTag::SquarePlanar, 4, &l, &l),
None,
"序号越界"
);
assert_eq!(renumber(ChiralTag::Cw, 1, &l, &l), None, "四面体没有排列表");
assert_eq!(
renumber(ChiralTag::Allene, 1, &l, &l),
None,
"@AL 没有排列表"
);
assert_eq!(
renumber(ChiralTag::SquarePlanar, 1, &l[..3], &l[..3]),
None,
"配体数与几何对不上"
);
assert_eq!(
renumber(ChiralTag::SquarePlanar, 1, &l, &[0, 1, 2, 9]),
None,
"两侧不是同一组配体"
);
assert_eq!(ligand_count(ChiralTag::Cw), None);
assert_eq!(ligand_count(ChiralTag::Octahedral), Some(6));
}
}