use serde::{Deserialize, Serialize};
use super::cluster::Cluster;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum Edge {
#[default]
K,
L1,
L2,
L3,
M1,
M2,
M3,
M4,
M5,
}
impl Edge {
pub fn hole_index(self) -> u32 {
match self {
Edge::K => 1,
Edge::L1 => 2,
Edge::L2 => 3,
Edge::L3 => 4,
Edge::M1 => 5,
Edge::M2 => 6,
Edge::M3 => 7,
Edge::M4 => 8,
Edge::M5 => 9,
}
}
pub fn label(self) -> &'static str {
match self {
Edge::K => "K",
Edge::L1 => "L1",
Edge::L2 => "L2",
Edge::L3 => "L3",
Edge::M1 => "M1",
Edge::M2 => "M2",
Edge::M3 => "M3",
Edge::M4 => "M4",
Edge::M5 => "M5",
}
}
pub fn parse(text: &str) -> Option<Edge> {
match text.trim().to_ascii_uppercase().as_str() {
"K" => Some(Edge::K),
"L1" => Some(Edge::L1),
"L2" => Some(Edge::L2),
"L3" | "L" => Some(Edge::L3),
"M1" => Some(Edge::M1),
"M2" => Some(Edge::M2),
"M3" => Some(Edge::M3),
"M4" => Some(Edge::M4),
"M5" | "M" => Some(Edge::M5),
_ => None,
}
}
pub fn default_for_z(z: u8) -> Edge {
if z < 58 {
Edge::K
} else {
Edge::L3
}
}
pub const ALL: [Edge; 9] = [
Edge::K,
Edge::L1,
Edge::L2,
Edge::L3,
Edge::M1,
Edge::M2,
Edge::M3,
Edge::M4,
Edge::M5,
];
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum FeffInputStyle {
#[default]
Classic,
Feff8,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FeffInputOptions {
pub edge: Edge,
pub rmax: Option<f64>,
pub rpath: Option<f64>,
pub s02: f64,
pub scf: bool,
pub exafs: bool,
pub nleg: u8,
pub style: FeffInputStyle,
pub titles: Vec<String>,
}
impl Default for FeffInputOptions {
fn default() -> Self {
Self {
edge: Edge::K,
rmax: None,
rpath: None,
s02: 1.0,
scf: false,
exafs: true,
nleg: 4,
style: FeffInputStyle::Classic,
titles: Vec::new(),
}
}
}
pub fn write_feff_inp(cluster: &Cluster, opts: &FeffInputOptions) -> String {
let absorber = cluster.absorber();
let rmax = opts.rmax.unwrap_or(cluster.radius).clamp(2.0, 50.0);
let rpath = opts.rpath.unwrap_or(rmax);
let mut s = String::new();
s.push_str("* feff.inp generated by rexafs\n");
s.push_str(&format!(
"TITLE {} {} edge, {} (cluster {:.2} Å)\n",
absorber.symbol,
opts.edge.label(),
cluster.structure_title,
cluster.radius
));
s.push_str(&format!("TITLE Formula: {}\n", cluster.formula));
if let Some(sg) = &cluster.space_group {
s.push_str(&format!("TITLE SpaceGroup: {sg}\n"));
}
for t in &opts.titles {
let t = t.trim();
if !t.is_empty() {
s.push_str(&format!("TITLE {t}\n"));
}
}
for w in &cluster.warnings {
s.push_str(&format!("* {w}\n"));
}
s.push('\n');
match opts.style {
FeffInputStyle::Classic => {
s.push_str(&format!(
"HOLE {} {:.3}\n\n",
opts.edge.hole_index(),
opts.s02
));
s.push_str("CONTROL 1 1 1 1 1 1\n");
s.push_str("PRINT 1 0 0 0 0 3\n");
s.push_str(&format!("RMAX {rmax:.2}\n"));
s.push_str(&format!("NLEG {}\n", opts.nleg.max(2)));
if opts.exafs {
s.push_str("EXAFS 20\n");
} else {
s.push_str("XANES 4.0\n");
}
if opts.scf {
s.push_str("SCF 5.0\n");
}
}
FeffInputStyle::Feff8 => {
s.push_str(&format!("EDGE {}\n", opts.edge.label()));
s.push_str(&format!("S02 {:.3}\n", opts.s02));
s.push_str("CONTROL 1 1 1 1 1 1\n");
s.push_str("PRINT 1 0 0 0 0 3\n");
if opts.exafs {
s.push_str("EXAFS 20.0\n");
} else {
s.push_str("XANES 4.0\n");
}
s.push_str(&format!("RPATH {rpath:.2}\n"));
s.push_str(&format!("NLEG {}\n", opts.nleg.max(2)));
if opts.scf {
s.push_str("SCF 5.0\n");
} else {
s.push_str("*SCF 5.0\n");
}
s.push_str("EXCHANGE 0\n");
}
}
s.push_str("\nPOTENTIALS\n* ipot Z tag\n");
for p in &cluster.potentials {
let tag = if p.ipot == 0 {
format!("{}0", p.symbol)
} else {
p.symbol.clone()
};
s.push_str(&format!(" {:4} {:3} {}\n", p.ipot, p.z, tag));
}
s.push_str("\nATOMS\n* x y z ipot tag distance site\n");
for atom in &cluster.atoms {
let tag = if atom.ipot == 0 {
format!("{}0", atom.symbol)
} else {
atom.symbol.clone()
};
s.push_str(&format!(
" {:9.5} {:9.5} {:9.5} {:3} {:<6} {:9.5} * {}\n",
atom.cart[0], atom.cart[1], atom.cart[2], atom.ipot, tag, atom.distance, atom.label
));
}
s.push_str("END\n");
s
}