use crate::Model;
use crate::RMatrixData;
use crate::error::{Result, TbError};
use crate::kplane::gen_kplane;
use crate::kpoints::gen_kmesh;
use crate::solve_ham::Solve;
use ndarray::prelude::*;
use std::fs;
use std::io::{BufWriter, Write};
use std::process::Command;
const MS_EDGES: [[i8; 5]; 16] = [
[-1, -1, -1, -1, -1], [0, 3, -1, -1, -1], [0, 1, -1, -1, -1], [1, 3, -1, -1, -1], [1, 2, -1, -1, -1], [0, 1, 2, 3, -1], [0, 2, -1, -1, -1], [2, 3, -1, -1, -1], [2, 3, -1, -1, -1], [0, 2, -1, -1, -1], [0, 3, 1, 2, -1], [1, 2, -1, -1, -1], [1, 3, -1, -1, -1], [0, 1, -1, -1, -1], [0, 3, -1, -1, -1], [-1, -1, -1, -1, -1], ];
fn marching_squares_2d(
energy: &Array2<f64>, kvec: &Array2<f64>, n1: usize,
n2: usize,
e_fermi: f64,
) -> Vec<(Array1<f64>, Array1<f64>)> {
let mut segments = Vec::new();
for j in 0..n2.saturating_sub(1) {
for i in 0..n1.saturating_sub(1) {
let idx00 = i + j * n1;
let idx10 = (i + 1) + j * n1;
let idx11 = (i + 1) + (j + 1) * n1;
let idx01 = i + (j + 1) * n1;
let e00 = energy[[j, i]];
let e10 = energy[[j, i + 1]];
let e11 = energy[[j + 1, i + 1]];
let e01 = energy[[j + 1, i]];
let case = ((e00 >= e_fermi) as usize)
| (((e10 >= e_fermi) as usize) << 1)
| (((e11 >= e_fermi) as usize) << 2)
| (((e01 >= e_fermi) as usize) << 3);
let edges = &MS_EDGES[case];
let corners = [&idx00, &idx10, &idx11, &idx01];
let edge_pairs: [(usize, usize); 4] = [(0, 1), (1, 2), (3, 2), (0, 3)];
let edge_vals: [(f64, f64); 4] = [(e00, e10), (e10, e11), (e01, e11), (e00, e01)];
let mut ei = 0;
while ei < 5 && edges[ei] != -1 {
let e_a = edges[ei] as usize;
let e_b = edges[ei + 1] as usize;
let (ca, cb) = edge_pairs[e_a];
let (va, vb) = edge_vals[e_a];
let p_a = interpolate_edge(
kvec.row(*corners[ca]),
kvec.row(*corners[cb]),
va,
vb,
e_fermi,
);
let (ca, cb) = edge_pairs[e_b];
let (va, vb) = edge_vals[e_b];
let p_b = interpolate_edge(
kvec.row(*corners[ca]),
kvec.row(*corners[cb]),
va,
vb,
e_fermi,
);
segments.push((p_a, p_b));
ei += 2;
}
}
}
segments
}
fn interpolate_edge(
ka: ArrayView1<f64>,
kb: ArrayView1<f64>,
va: f64,
vb: f64,
e_fermi: f64,
) -> Array1<f64> {
let denom = vb - va;
let t = if denom.abs() < 1e-14 {
0.5
} else {
(e_fermi - va) / denom
};
&ka + &((&kb - &ka) * t)
}
const TET_EDGE_PAIRS: [(usize, usize); 6] = [(0, 1), (1, 2), (2, 0), (0, 3), (1, 3), (2, 3)];
const TET_CASES: [[i8; 7]; 16] = [
[-1, -1, -1, -1, -1, -1, -1], [0, 2, 3, -1, -1, -1, -1], [0, 1, 4, -1, -1, -1, -1], [2, 1, 4, 2, 4, 3, -1], [1, 2, 5, -1, -1, -1, -1], [0, 1, 5, 0, 5, 3, -1], [0, 2, 5, 0, 5, 4, -1], [3, 4, 5, -1, -1, -1, -1], [3, 5, 4, -1, -1, -1, -1], [0, 2, 5, 0, 5, 4, -1], [0, 1, 5, 0, 5, 3, -1], [2, 1, 5, -1, -1, -1, -1], [2, 1, 4, 2, 4, 3, -1], [0, 1, 4, -1, -1, -1, -1], [0, 2, 3, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], ];
const CUBE_TETS: [[usize; 4]; 5] = [
[0, 1, 2, 4], [3, 1, 2, 7], [5, 1, 4, 7], [6, 2, 4, 7], [1, 2, 4, 7], ];
const CUBE_CORNERS: [(usize, usize, usize); 8] = [
(0, 0, 0), (1, 0, 0), (0, 1, 0), (1, 1, 0), (0, 0, 1), (1, 0, 1), (0, 1, 1), (1, 1, 1), ];
fn marching_tetrahedra_3d(
energy: &Array3<f64>, kvec: &Array2<f64>, n1: usize,
n2: usize,
n3: usize,
e_fermi: f64,
) -> Vec<[Array1<f64>; 3]> {
let mut triangles = Vec::new();
for k in 0..n3.saturating_sub(1) {
for j in 0..n2.saturating_sub(1) {
for i in 0..n1.saturating_sub(1) {
let mut corner_val = [0.0f64; 8];
let mut corner_idx = [0usize; 8];
for (c, &(di, dj, dk)) in CUBE_CORNERS.iter().enumerate() {
let ci = i + di;
let cj = j + dj;
let ck = k + dk;
corner_val[c] = energy[[ck, cj, ci]];
corner_idx[c] = ci + cj * n1 + ck * (n1 * n2);
}
for &[v0, v1, v2, v3] in &CUBE_TETS {
let ev = [
corner_val[v0],
corner_val[v1],
corner_val[v2],
corner_val[v3],
];
let case = ((ev[0] >= e_fermi) as usize)
| (((ev[1] >= e_fermi) as usize) << 1)
| (((ev[2] >= e_fermi) as usize) << 2)
| (((ev[3] >= e_fermi) as usize) << 3);
let tet_verts = [v0, v1, v2, v3];
let edges = &TET_CASES[case];
let mut ei = 0;
while ei < 6 && edges[ei] != -1 {
let e_a = edges[ei] as usize;
let e_b = edges[ei + 1] as usize;
let e_c = edges[ei + 2] as usize;
let tri = [
tet_interp(kvec, &corner_idx, &corner_val, &tet_verts, e_a, e_fermi),
tet_interp(kvec, &corner_idx, &corner_val, &tet_verts, e_b, e_fermi),
tet_interp(kvec, &corner_idx, &corner_val, &tet_verts, e_c, e_fermi),
];
triangles.push(tri);
ei += 3;
}
}
}
}
}
triangles
}
fn tet_interp(
kvec: &Array2<f64>,
corner_idx: &[usize; 8],
corner_val: &[f64; 8],
tet_verts: &[usize; 4],
edge: usize,
e_fermi: f64,
) -> Array1<f64> {
let (a, b) = TET_EDGE_PAIRS[edge];
let ca = corner_idx[tet_verts[a]];
let cb = corner_idx[tet_verts[b]];
let va = corner_val[tet_verts[a]];
let vb = corner_val[tet_verts[b]];
interpolate_edge(kvec.row(ca), kvec.row(cb), va, vb, e_fermi)
}
fn render_fermi_2d(
all_segments: &[Vec<(Array1<f64>, Array1<f64>)>],
name: &str,
x_label: &str,
y_label: &str,
) -> Result<()> {
fs::create_dir_all(name)?;
let has_segments = all_segments.iter().any(|s| !s.is_empty());
if !has_segments {
return Err(TbError::NoBandsInEnergyRange);
}
use gnuplot::{AxesCommon, Color, Figure, Font, LineStyle, Solid};
let mut fg = Figure::new();
let axes = fg.axes2d();
axes.set_x_label(x_label, &[Font("Times New Roman", 18.0)]);
axes.set_y_label(y_label, &[Font("Times New Roman", 18.0)]);
axes.set_x_range(gnuplot::AutoOption::Auto, gnuplot::AutoOption::Auto);
axes.set_y_range(gnuplot::AutoOption::Auto, gnuplot::AutoOption::Auto);
let colors = [
"red", "blue", "green", "orange", "purple", "brown", "cyan", "magenta",
];
for (band_idx, segments) in all_segments.iter().enumerate() {
if segments.is_empty() {
continue;
}
let color = colors[band_idx % colors.len()];
let mut xs = Vec::new();
let mut ys = Vec::new();
for (p1, p2) in segments {
xs.push(p1[0]);
ys.push(p1[1]);
xs.push(p2[0]);
ys.push(p2[1]);
xs.push(f64::NAN);
ys.push(f64::NAN);
}
axes.lines(&xs, &ys, &[Color(color), LineStyle(Solid)]);
}
let pdf_name = format!("{}/fermi_surface.pdf", name);
fg.set_terminal("pdfcairo", &pdf_name);
fg.show()
.map_err(|e| TbError::Other(format!("gnuplot error: {}", e)))?;
Ok(())
}
fn render_fermi_3d(triangles: &[[Array1<f64>; 3]], name: &str) -> Result<()> {
fs::create_dir_all(name)?;
let data_path = format!("{}/fermi_triangles.dat", name);
let pdf_path = format!("{}/fermi_surface.pdf", name);
{
let mut f = BufWriter::with_capacity(1 << 20, fs::File::create(&data_path)?);
for tri in triangles {
for v in tri {
writeln!(f, "{:.8} {:.8} {:.8}", v[0], v[1], v[2])?;
}
writeln!(f)?;
}
f.flush()?;
}
let mut gnuplot = Command::new("gnuplot")
.stdin(std::process::Stdio::piped())
.spawn()
.map_err(|e| TbError::Other(format!("Failed to launch gnuplot: {}", e)))?;
if let Some(stdin) = gnuplot.stdin.as_mut() {
writeln!(stdin, "set terminal pdfcairo").ok();
writeln!(stdin, "set output '{}'", pdf_path).ok();
writeln!(stdin, "set pm3d depthorder").ok();
writeln!(stdin, "set style fill transparent solid 0.5").ok();
writeln!(stdin, "set view 60, 30").ok();
writeln!(stdin, "set xlabel 'k_x' font 'Times New Roman,18'").ok();
writeln!(stdin, "set ylabel 'k_y' font 'Times New Roman,18'").ok();
writeln!(stdin, "set zlabel 'k_z' font 'Times New Roman,18'").ok();
writeln!(stdin, "splot '{}' with pm3d notitle", data_path).ok();
}
let status = gnuplot
.wait()
.map_err(|e| TbError::Other(format!("gnuplot failed: {}", e)))?;
if !status.success() {
return Err(TbError::Other("gnuplot exited with error".into()));
}
Ok(())
}
pub trait BxsfExport {
fn write_bxsf(&self, k_mesh: &[usize; 3], e_fermi: f64, filename: &str) -> Result<()>;
}
impl<const SPIN: bool, R: RMatrixData> BxsfExport for Model<SPIN, 3, R> {
fn write_bxsf(&self, k_mesh: &[usize; 3], e_fermi: f64, filename: &str) -> Result<()> {
let [nx, ny, nz] = *k_mesh;
let nk = nx * ny * nz;
let b = self.rec_lat()?;
let kvec: Array2<f64> = gen_kmesh(&arr1(&[nx, ny, nz]))?;
let eval = self.solve_band_all_parallel(&kvec);
let nsta = self.nsta();
let path = if filename.ends_with(".bxsf") {
filename.to_owned()
} else {
format!("{filename}.bxsf")
};
use std::fmt::Write as _;
let mut out = String::with_capacity(1024 + nsta * nk * 20);
writeln!(out, "BEGIN_INFO").ok();
writeln!(out, " # BXSF exported by Rustb").ok();
writeln!(out, " # Number of k-points: {nx}×{ny}×{nz} = {nk}").ok();
writeln!(out, " # Number of bands: {nsta}").ok();
writeln!(out, " Fermi Energy: {e_fermi:.12}").ok();
writeln!(out, "END_INFO").ok();
writeln!(out).ok();
writeln!(out, "BEGIN_BLOCK_BANDGRID_3D").ok();
writeln!(out, " band_energies").ok();
writeln!(out, " BEGIN_BANDGRID_3D_band_energies").ok();
writeln!(out, " {nsta}").ok();
writeln!(out, " {nx} {ny} {nz}").ok();
writeln!(out, " 0.0 0.0 0.0").ok(); writeln!(
out,
" {:.10} {:.10} {:.10}",
b[[0, 0]],
b[[0, 1]],
b[[0, 2]]
)
.ok();
writeln!(
out,
" {:.10} {:.10} {:.10}",
b[[1, 0]],
b[[1, 1]],
b[[1, 2]]
)
.ok();
writeln!(
out,
" {:.10} {:.10} {:.10}",
b[[2, 0]],
b[[2, 1]],
b[[2, 2]]
)
.ok();
for ib in 0..nsta {
writeln!(out, " BAND: {}", ib + 1).ok();
let mut row = 0;
for _ix in 0..nx {
for _iy in 0..ny {
for _iz in 0..nz {
let e = eval[[row, ib]];
row += 1;
if _iz % 10 == 0 && _iz > 0 {
out.push('\n');
}
write!(out, " {e:.10E}").ok();
}
out.push('\n');
}
}
out.push('\n');
}
writeln!(out, " END_BANDGRID_3D").ok();
writeln!(out, "END_BLOCK_BANDGRID_3D").ok();
fs::write(&path, out.as_bytes())?;
Ok(())
}
}
fn frmsf_order(eval: &Array2<f64>, nk: &[usize; 3]) -> Vec<f64> {
let nk_total = nk[0] * nk[1] * nk[2];
let nbnd = eval.ncols();
let mut out = Vec::with_capacity(nk_total * nbnd);
for ib in 0..nbnd {
for row in 0..nk_total {
out.push(eval[[row, ib]]);
}
}
out
}
fn format_frmsf_block(values: &[f64]) -> String {
let mut buf = String::with_capacity(values.len() * 15);
const COLS: usize = 10;
for (i, &v) in values.iter().enumerate() {
use std::fmt::Write;
write!(buf, " {v:.10E}").ok();
if (i + 1) % COLS == 0 {
buf.push('\n');
}
}
if values.len() % COLS != 0 {
buf.push('\n');
}
buf
}
pub fn write_spin_frmsf<const SPIN: bool, R: RMatrixData>(
up_model: &Model<SPIN, 3, R>,
dn_model: &Model<SPIN, 3, R>,
k_mesh: &[usize; 3],
e_fermi: f64,
filename: &str,
) -> Result<()> {
let [nx, ny, nz] = *k_mesh;
let nsta = up_model.nsta();
if dn_model.nsta() != nsta {
return Err(TbError::Other(format!(
"Spin models must have same band count: up={nsta}, dn={}",
dn_model.nsta()
)));
}
let lat_diff = (&up_model.lat - &dn_model.lat).mapv(|x| x.abs()).sum();
if lat_diff > 1e-10 {
return Err(TbError::Other(format!(
"up_model and dn_model must have the same lattice, diff = {lat_diff}"
)));
}
let b = up_model.rec_lat()?;
let kvec: Array2<f64> = gen_kmesh(&arr1(&[nx, ny, nz]))?;
let eval_up = up_model.solve_band_all_parallel(&kvec) - e_fermi;
let eval_dn = dn_model.solve_band_all_parallel(&kvec) - e_fermi;
let nk = nx * ny * nz;
let nbnd_total = nsta * 2;
let mut eval_merged = Array2::<f64>::zeros((nk, nbnd_total));
for ib in 0..nsta {
eval_merged.column_mut(ib).assign(&eval_up.column(ib));
eval_merged
.column_mut(ib + nsta)
.assign(&eval_dn.column(ib));
}
let path = if filename.ends_with(".frmsf") {
filename.to_owned()
} else {
format!("{filename}.frmsf")
};
let mut f = BufWriter::with_capacity(1 << 20, fs::File::create(&path)?);
writeln!(f, "{nx} {ny} {nz}")?;
writeln!(f, "1")?; writeln!(f, "{nbnd_total}")?;
writeln!(f, "{:.10} {:.10} {:.10}", b[[0, 0]], b[[0, 1]], b[[0, 2]])?;
writeln!(f, "{:.10} {:.10} {:.10}", b[[1, 0]], b[[1, 1]], b[[1, 2]])?;
writeln!(f, "{:.10} {:.10} {:.10}", b[[2, 0]], b[[2, 1]], b[[2, 2]])?;
let energy_flat = frmsf_order(&eval_merged, k_mesh);
f.write_all(format_frmsf_block(&energy_flat).as_bytes())?;
let n_vals = nk * nbnd_total;
let color_flat: Vec<f64> = (0..n_vals)
.map(|i| {
let ib = i / nk;
if ib < nsta { 1.0 } else { -1.0 }
})
.collect();
f.write_all(format_frmsf_block(&color_flat).as_bytes())?;
f.flush()?;
Ok(())
}
pub trait FermiSurface: Solve {
fn show_fermi_surface(&self, k_mesh: &Array1<usize>, e_fermi: f64, name: &str) -> Result<()>;
}
pub trait FermiSurfacePlane: Solve {
fn show_fermi_surface_plane(
&self,
origin: &Array1<f64>,
vec1: &Array1<f64>,
vec2: &Array1<f64>,
n1: usize,
n2: usize,
e_fermi: f64,
name: &str,
) -> Result<()>;
}
impl<const SPIN: bool, const DIM: usize, R: RMatrixData> FermiSurface for Model<SPIN, DIM, R> {
fn show_fermi_surface(&self, k_mesh: &Array1<usize>, e_fermi: f64, name: &str) -> Result<()> {
match self.dim_r() {
1 => Err(TbError::NotImplemented(
"Fermi surface not meaningful for 1D systems".into(),
)),
2 => {
let kvec: Array2<f64> = gen_kmesh(k_mesh)?;
let n1 = k_mesh[0];
let n2 = k_mesh[1];
let eval = self.solve_band_all_parallel(&kvec);
let nsta = self.nsta();
let mut all_segments: Vec<Vec<(Array1<f64>, Array1<f64>)>> =
Vec::with_capacity(nsta);
for b in 0..nsta {
let mut energy = Array2::<f64>::zeros((n2, n1));
for j in 0..n2 {
for i in 0..n1 {
energy[[j, i]] = eval[[i + j * n1, b]];
}
}
let segs = marching_squares_2d(&energy, &kvec, n1, n2, e_fermi);
all_segments.push(segs);
}
render_fermi_2d(&all_segments, name, "k_x", "k_y")
}
3 => {
let kvec: Array2<f64> = gen_kmesh(k_mesh)?;
let n1 = k_mesh[0];
let n2 = k_mesh[1];
let n3 = k_mesh[2];
let nk = n1 * n2 * n3;
let eval = self.solve_band_all_parallel(&kvec);
let nsta = self.nsta();
let mut all_triangles: Vec<[Array1<f64>; 3]> = Vec::new();
for b in 0..nsta {
let mut emin = f64::INFINITY;
let mut emax = f64::NEG_INFINITY;
for ik in 0..nk {
let e = eval[[ik, b]];
if e < emin {
emin = e;
}
if e > emax {
emax = e;
}
}
if emin > e_fermi || emax < e_fermi {
continue; }
let mut energy = Array3::<f64>::zeros((n3, n2, n1));
for k in 0..n3 {
for j in 0..n2 {
for i in 0..n1 {
energy[[k, j, i]] = eval[[i + j * n1 + k * (n1 * n2), b]];
}
}
}
let tris = marching_tetrahedra_3d(&energy, &kvec, n1, n2, n3, e_fermi);
all_triangles.extend(tris);
}
if all_triangles.is_empty() {
return Err(TbError::NoBandsInEnergyRange);
}
render_fermi_3d(&all_triangles, name)
}
_ => unreachable!(),
}
}
}
impl<const SPIN: bool, const DIM: usize, R: RMatrixData> FermiSurfacePlane for Model<SPIN, DIM, R> {
fn show_fermi_surface_plane(
&self,
origin: &Array1<f64>,
vec1: &Array1<f64>,
vec2: &Array1<f64>,
n1: usize,
n2: usize,
e_fermi: f64,
name: &str,
) -> Result<()> {
if self.dim_r() != 3 {
return Err(TbError::InvalidDimension {
dim: self.dim_r(),
supported: vec![3],
});
}
let kvec: Array2<f64> = gen_kplane(origin, vec1, vec2, n1, n2)?;
let eval = self.solve_band_all_parallel(&kvec);
let nsta = self.nsta();
let mut all_segments: Vec<Vec<(Array1<f64>, Array1<f64>)>> = Vec::with_capacity(nsta);
for b in 0..nsta {
let mut energy = Array2::<f64>::zeros((n2, n1));
for j in 0..n2 {
for i in 0..n1 {
energy[[j, i]] = eval[[i + j * n1, b]];
}
}
let segs = marching_squares_2d(&energy, &kvec, n1, n2, e_fermi);
all_segments.push(segs);
}
let x_label = format!("k · ({:.1},{:.1},{:.1})", vec1[0], vec1[1], vec1[2]);
let y_label = format!("k · ({:.1},{:.1},{:.1})", vec2[0], vec2[1], vec2[2]);
render_fermi_2d(&all_segments, name, &x_label, &y_label)
}
}