crystal_cif_io/grammar/
chemrust_impl.rs1use 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