crystal_cif_io/grammar/
chemrust_impl.rs

1use std::str::FromStr;
2
3use castep_periodic_table::element::ElementSymbol;
4use chemrust_core::data::{
5    atom::CoreAtomData, geom::coordinates::CoordData, lattice::{CellConstants, CrystalModel, UnitCellParameters}, symmetry::SymmetryInfo,
6};
7use nalgebra::Point3;
8
9use crate::{
10    data_dict::
11        core_cif::{
12            atom_site::chemrust_impl::basic_atom_site_data, audit::default_audit_data,
13            cell::chemrust_impl::basic_cell_data,
14            space_group::chemrust_impl::basic_space_group_data,
15        },
16    LoopColumn,
17};
18
19use super::{
20    structures::{DataBlock, DataBlockHeading, DataBlockMember}, CifDocument, SyntacticUnit
21};
22
23
24pub fn from_data_block_members(members: &[DataBlockMember], data_name:&str) ->  CifDocument {
25    let heading = DataBlockHeading::new(data_name.to_string());
26    let data_block = DataBlock::from_heading_members((heading, members.to_vec()));
27    CifDocument::new(None, Some(vec![data_block]))
28}
29
30pub fn to_data_block<T: CrystalModel+SymmetryInfo>(model: &T, data_name: &str) -> DataBlock {
31    let datablock_members = [
32        default_audit_data(),
33        basic_space_group_data(model),
34        basic_cell_data(model.get_cell_parameters()),
35        [basic_atom_site_data(model.get_atom_data())].to_vec(),
36    ]
37    .into_iter()
38    .flat_map(|items| {
39        items
40            .into_iter()
41            .map(DataBlockMember::DataItems)
42            .collect::<Vec<DataBlockMember>>()
43    })
44    .collect();
45    let heading = DataBlockHeading::new(data_name.to_string());
46    DataBlock::from_heading_members((heading, datablock_members))
47}
48
49pub fn to_cif_document<T: CrystalModel + SymmetryInfo>(model: &T, data_name: &str) -> CifDocument {
50    let datablock_members = [
51        default_audit_data(),
52        basic_space_group_data(model),
53        basic_cell_data(model.get_cell_parameters()),
54        [basic_atom_site_data(model.get_atom_data())].to_vec(),
55    ]
56    .into_iter()
57    .flat_map(|items| {
58        items
59            .into_iter()
60            .map(DataBlockMember::DataItems)
61            .collect::<Vec<DataBlockMember>>()
62    })
63    .collect();
64    let heading = DataBlockHeading::new(data_name.to_string());
65    let data_block = DataBlock::from_heading_members((heading, datablock_members));
66    CifDocument::new(None, Some(vec![data_block]))
67}
68
69impl UnitCellParameters for DataBlock {
70    fn lattice_bases(&self) -> nalgebra::Matrix3<f64> {
71        let length_a = self["cell_length_a"].as_single_value().and_then(|v| v.value().as_numeric())
72            .and_then(|n| n.number().as_float().map(|&float| f64::from(float)))
73            .expect("float to f64");
74        let length_b = self["cell_length_b"].as_single_value().and_then(|v| v.value().as_numeric())
75            .and_then(|n| n.number().as_float().map(|&float| f64::from(float)))
76            .expect("float to f64");
77        let length_c = self["cell_length_c"].as_single_value().and_then(|v| v.value().as_numeric())
78            .and_then(|n| n.number().as_float().map(|&float| f64::from(float)))
79            .expect("float to f64");
80        let alpha = self["cell_angle_alpha"].as_single_value().and_then(|v| v.value().as_numeric())
81            .and_then(|n| n.number().as_float().map(|&float| f64::from(float)))
82            .expect("float to f64");
83        let beta = self["cell_angle_beta"].as_single_value().and_then(|v| v.value().as_numeric())
84            .and_then(|n| n.number().as_float().map(|&float| f64::from(float)))
85            .expect("float to f64");
86        let gamma = self["cell_angle_gamma"].as_single_value().and_then(|v| v.value().as_numeric())
87            .and_then(|n| n.number().as_float().map(|&float| f64::from(float)))
88            .expect("float to f64");
89        let cell_constants = CellConstants::new(length_a, length_b, length_c, alpha, beta, gamma);
90        cell_constants.lattice_bases()
91    }
92}
93
94impl CoreAtomData for DataBlock {
95    fn indices_repr(&self) -> Vec<usize> {
96        let atom_sites = &self["atom_site_label"].as_multi_values().expect("this data block does not have atom sites data");
97        let labels: &LoopColumn = &atom_sites["atom_site_label"];
98        let len = labels.values().len();
99        (0..len).collect()
100    }
101
102    fn symbols_repr(&self) -> Vec<ElementSymbol> {
103        let atom_sites = &self["atom_site_label"].as_multi_values().expect("this data block does not have atom sites data");
104            let symbols = &atom_sites["atom_site_type_symbol"];
105            symbols
106                .values()
107                .iter()
108                .map(|value| {
109                    value
110                        .as_char_string()
111                        .map(|char_string| {
112                            
113                            ElementSymbol::from_str(char_string.as_ref()).expect("Single element symbol from periodic table. Multi-element symbol representation is not supported.")
114                        })
115                        .expect("the element symbol value should be <CharString>")
116                })
117                .collect()
118    }
119
120    fn coords_repr(&self) -> Vec<CoordData> {
121        let atom_sites = &self["atom_site_label"].as_multi_values().expect("this data block does not have atom sites data");
122        if atom_sites.find_loop_column_by_tag("atom_site_fract_x").is_some() &&
123        atom_sites.find_loop_column_by_tag("atom_site_fract_y").is_some() &&
124        atom_sites.find_loop_column_by_tag("atom_site_fract_z").is_some() {
125            let fract_x = &atom_sites["atom_site_fract_x"];
126            let fract_y =  &atom_sites["atom_site_fract_y"];
127            let fract_z =  &atom_sites["atom_site_fract_z"];
128            fract_x.values().iter().zip(fract_y.values().iter()).zip(fract_z.values().iter())
129            .map(|((x,y), z)| {
130                    let (x, y, z) = (*x.as_numeric().expect("Numeric").number().as_float().expect("fractional coord should be float"),
131                        *y.as_numeric().expect("Numeric").number().as_float().expect("fractional coord should be float"),
132                        *z.as_numeric().expect("Numeric").number().as_float().expect("fractional coord should be float"));
133                    let (x,y,z) = ((*x).into(), ( *y ).into(), ( *z ).into());
134                    CoordData::Fractional(Point3::new(x, y, z))
135
136                    
137                }).collect()
138        } else if  
139
140        atom_sites.find_loop_column_by_tag("atom_site_cartn_x").is_some() &&
141        atom_sites.find_loop_column_by_tag("atom_site_cartn_y").is_some() &&
142        atom_sites.find_loop_column_by_tag("atom_site_cartn_z").is_some() {
143            let cartn_x = &atom_sites["atom_site_cartn_x"];
144            let cartn_y =  &atom_sites["atom_site_cartn_y"];
145            let cartn_z =  &atom_sites["atom_site_cartn_z"];
146            cartn_x.values().iter().zip(cartn_y.values().iter()).zip(cartn_z.values().iter())
147            .map(|((x,y), z)| {
148                    let (x, y, z) = (*x.as_numeric().expect("Numeric").number().as_float().expect("coord should be float"),
149                        *y.as_numeric().expect("Numeric").number().as_float().expect("coord should be float"),
150                        *z.as_numeric().expect("Numeric").number().as_float().expect("coord should be float"));
151                    let (x,y,z) = ((*x).into(), ( *y ).into(), ( *z ).into());
152                    CoordData::Cartesian(Point3::new(x, y, z))
153                }).collect()
154        } else {
155            panic!("the data block does not have sufficient coordinate data (x, y, and z)")
156        }
157
158    }
159
160    fn labels_repr(&self) -> Vec<Option<String>> {
161        let atom_sites = &self["atom_site_label"].as_multi_values().expect("this data block does not have atom sites data");
162        atom_sites["atom_site_label"].values().iter()
163        .map(|val| val.as_char_string().map(|v| v.formatted_output()))
164        .collect()
165    }
166
167}
168
169impl CrystalModel for DataBlock {
170    fn get_cell_parameters(&self) -> &impl UnitCellParameters {
171        self
172    }
173
174    fn get_atom_data(&self) -> &impl CoreAtomData {
175        self
176    }
177
178    fn get_cell_parameters_mut(&mut self) -> &mut impl UnitCellParameters {
179        self
180    }
181
182    fn get_atom_data_mut(&mut self) -> &mut impl CoreAtomData {
183        self
184    }
185}
186
187impl SymmetryInfo for DataBlock {
188    fn make_symmetry(&self) -> bool {
189        self.get_space_group_it_num() > 1_u8
190    }
191
192    fn get_space_group_it_num(&self) -> u8 {
193        self.find_single_value_by_tag("IT_number")
194            .and_then(|value| value.value().as_numeric()).map(|value| value.number())
195            .and_then(|value| value.as_integer().map(|i| i.0 as u8))
196            .unwrap_or(1)
197    }
198}
199