1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
use std::fs::File;
use std::io::{self, BufRead, BufReader};

use castep_periodic_table::{data::ELEMENT_TABLE, element::LookupElement};

use crate::cell_settings::{
    CellSettingExport, ExternalEField, ExternalPressure, FixAllCell, FixCOM, IonicConstraints,
    KPointsList, SpeciesCharacteristics,
};
use crate::{ModelFormat, StructureFile};

#[derive(Debug, Clone, Copy, Default)]
/// A unit struct to mark `cell`format.
pub struct Cell;

impl ModelFormat for Cell {}

/// Methods for `CellFormat`
impl Cell {
    pub fn write_block(block: (String, String)) -> String {
        let (block_name, content) = block;
        format!(
            "%BLOCK {}\n{}%ENDBLOCK {}\n\n",
            block_name, content, block_name
        )
    }
}

impl StructureFile<Cell> {
    pub fn write_lattice_vectors(&self) -> String {
        let formatted_vector: Vec<String> = self
            .lattice_model
            .lattice_vectors()
            .unwrap()
            .data()
            .column_iter()
            .map(|col| format!("{:24.18}{:24.18}{:24.18}\n", col.x, col.y, col.z))
            .collect();
        let formatted_vector = formatted_vector.concat();
        Cell::write_block(("LATTICE_CART".to_string(), formatted_vector))
    }
    pub fn write_atoms(&self) -> String {
        let cart_to_frac_matrix = self
            .lattice_model
            .lattice_vectors()
            .unwrap()
            .mat_cart_to_frac();
        let atom_frac_coords_text: Vec<String> = self
            .lattice_model
            .atoms()
            .iter()
            .map(|atom| {
                let frac_xyz = cart_to_frac_matrix * atom.cartesian_coord();
                let symbol = atom.symbol();
                let spin = ELEMENT_TABLE.get_by_symbol(symbol).unwrap().spin();
                let spin_str = if spin > 0 {
                    format!(" SPIN={:14.10}", spin)
                } else {
                    "".into()
                };
                format!(
                    "{:>3}{:20.16}{:20.16}{:20.16}{spin_str}\n",
                    symbol, frac_xyz.x, frac_xyz.y, frac_xyz.z
                )
            })
            .collect();
        let all_atom_fracs = atom_frac_coords_text.concat();
        Cell::write_block(("POSITIONS_FRAC".into(), all_atom_fracs))
    }
    pub fn spin_total(&self) -> u8 {
        self.lattice_model
            .atoms()
            .iter()
            .map(|atom| ELEMENT_TABLE.get_by_symbol(&atom.symbol()).unwrap().spin())
            .reduce(|total, next| total + next)
            .unwrap()
    }
    pub fn get_final_cutoff_energy(&self, potentials_loc: &str) -> Result<f64, io::Error> {
        let mut energy: f64 = 0.0;
        self.lattice_model
            .element_list()
            .iter()
            .try_for_each(|elm| -> Result<(), io::Error> {
                let potential_file = ELEMENT_TABLE.get_by_symbol(elm).unwrap().potential();
                let potential_path = format!("{potentials_loc}/{potential_file}");
                if let Ok(file) = File::open(&potential_path) {
                    let file = BufReader::new(file);
                    let fine_energy: u32 = file
                        .lines()
                        .find(|line| line.as_ref().unwrap().contains("FINE"))
                        .map(|line| {
                            let num_str = line.as_ref().unwrap().split_whitespace().next().unwrap();
                            num_str.parse::<u32>().unwrap()
                        })
                        .unwrap();
                    let round_bigger_tenth = |num: u32| -> f64 {
                        match num % 10 {
                            0 => num as f64,
                            _ => ((num / 10 + 1) * 10) as f64,
                        }
                    };
                    let ultra_fine_energy = round_bigger_tenth((fine_energy as f64 * 1.1) as u32);
                    energy = if energy > ultra_fine_energy {
                        energy
                    } else {
                        ultra_fine_energy
                    };
                    Ok(())
                } else {
                    panic!(
                        "Error while reading potential file for element: {}, {}",
                        elm, potential_path
                    )
                }
            })?;
        Ok(energy)
    }
    pub fn export_geom_cell(&self) -> String {
        let lattice_cart = self.write_lattice_vectors();
        let atoms = self.write_atoms();
        let kpts_list = KPointsList::default().write_kpoints_list();
        let fix_constraints = FixCOM::default().write_to_cell();
        let fix_all_cell = FixAllCell::default().write_to_cell();
        let ionic_cons = IonicConstraints::default().write_to_cell();
        let extern_field = ExternalEField::default().write_to_cell();
        let extern_pressure = ExternalPressure::default().write_to_cell();
        let species_characters =
            SpeciesCharacteristics::new(self.lattice_model.element_list()).write_to_cell();
        let text = vec![
            lattice_cart,
            atoms,
            kpts_list,
            fix_constraints,
            fix_all_cell,
            ionic_cons,
            extern_field,
            extern_pressure,
            species_characters,
        ];
        text.concat()
    }
    pub fn export_bs_cell(&self) -> String {
        let lattice_cart = self.write_lattice_vectors();
        let atoms = self.write_atoms();
        let bs_kpts_list = KPointsList::default().write_bs_kpoints_list();
        let kpts_list = KPointsList::default().write_kpoints_list();
        let fix_constraints = FixCOM::default().write_to_cell();
        let fix_all_cell = FixAllCell::default().write_to_cell();
        let ionic_cons = IonicConstraints::default().write_to_cell();
        let extern_field = ExternalEField::default().write_to_cell();
        let extern_pressure = ExternalPressure::default().write_to_cell();
        let species_characters =
            SpeciesCharacteristics::new(self.lattice_model.element_list()).write_to_cell();
        let text = vec![
            lattice_cart,
            atoms,
            bs_kpts_list,
            kpts_list,
            fix_constraints,
            fix_all_cell,
            ionic_cons,
            extern_field,
            extern_pressure,
            species_characters,
        ];
        text.concat()
    }
}