diff --git a/picometer/atom.py b/picometer/atom.py
index 4eaee9a..da7ece1 100644
--- a/picometer/atom.py
+++ b/picometer/atom.py
@@ -119,6 +119,20 @@ def fract_xyz(self) -> np.ndarray:
def cart_xyz(self) -> np.ndarray:
return self.orthogonalise(self.fract_xyz)
+ @property
+ def fract_uij(self) -> np.ndarray:
+ """Return a 3D array i.e. stack of 3x3 fract. displacement tensors."""
+ t = self.table
+ default = pd.Series([np.nan] * len(t), index=t.index)
+ uij = np.zeros((len(t), 3, 3), dtype=np.float64)
+ uij[:, 0, 0] = t.get('U11', default).to_numpy(dtype=np.float64)
+ uij[:, 1, 1] = t.get('U22', default).to_numpy(dtype=np.float64)
+ uij[:, 2, 2] = t.get('U33', default).to_numpy(dtype=np.float64)
+ uij[:, 0, 1] = uij[:, 1, 0] = t.get('U12', default).to_numpy(dtype=np.float64)
+ uij[:, 0, 2] = uij[:, 2, 0] = t.get('U13', default).to_numpy(dtype=np.float64)
+ uij[:, 1, 2] = uij[:, 2, 1] = t.get('U23', default).to_numpy(dtype=np.float64)
+ return uij
+
def fractionalise(self, cart_xyz: np.ndarray) -> np.ndarray:
"""Multiply 3xN vector by crystallographic matrix to get fract coord"""
return np.linalg.inv(self.base.A_d.T) @ cart_xyz
@@ -158,6 +172,17 @@ def transform(self, symm_op_code: str) -> 'AtomSet':
data['fract_x'] = fract_xyz[:, 0]
data['fract_y'] = fract_xyz[:, 1]
data['fract_z'] = fract_xyz[:, 2]
+ if {'U11', 'U22', 'U33', 'U12', 'U13', 'U23'}.issubset(data.columns):
+ uij = self.fract_uij # shape: (n_atoms, 3, 3)
+ mask = ~np.isnan(uij).all(axis=(1, 2)) # atoms with defined Uij
+ if np.any(mask):
+ uij_rot = (s := symm_op.tf) @ uij[mask] @ s.T
+ data.loc[mask, 'U11'] = uij_rot[:, 0, 0]
+ data.loc[mask, 'U22'] = uij_rot[:, 1, 1]
+ data.loc[mask, 'U33'] = uij_rot[:, 2, 2]
+ data.loc[mask, 'U12'] = uij_rot[:, 0, 1]
+ data.loc[mask, 'U13'] = uij_rot[:, 0, 2]
+ data.loc[mask, 'U23'] = uij_rot[:, 1, 2]
return self.__class__(self.base, data)
@property
diff --git a/picometer/instructions.py b/picometer/instructions.py
index 7fda268..a337974 100644
--- a/picometer/instructions.py
+++ b/picometer/instructions.py
@@ -15,6 +15,7 @@
from typing import Any, Union, Protocol
from numpy import rad2deg
+import numpy as np
import pandas as pd
import yaml
@@ -218,6 +219,31 @@ def _load_model_state(self, cif_path, block_name):
label = cif_path + (':' + block_name if block_name else '')
self.processor.model_states[label] = ModelState(atoms=atoms)
logger.info(f'Loaded model state {label}')
+
+ if self.processor.settings['complete_uiso_from_umatrix']:
+ if 'U11' in atoms.table.columns:
+ if 'Uiso' not in atoms.table.columns:
+ atoms.table['Uiso'] = pd.NA
+ u_equiv = atoms.table[['U11', 'U22', 'U33']].mean(axis=1)
+ atoms.table['Uiso'].fillna(u_equiv, inplace=True)
+
+ if self.processor.settings['complete_umatrix_from_uiso']:
+ u_columns = ['U11', 'U12', 'U13', 'U22', 'U23', 'U33']
+ if 'Uiso' in atoms.table.columns:
+ for col in u_columns:
+ if col not in atoms.table.columns:
+ atoms.table[col] = pd.NA
+ mask1 = atoms.table['Uiso'].notna()
+ mask2 = atoms.table[['U11', 'U22', 'U33']].isna().all(axis=1)
+ # based on http://dx.doi.org/10.1107/S0021889802008580
+ n_mat = np.diag([atoms.base.a_r, atoms.base.b_r, atoms.base.c_r])
+ n_inv = np.linalg.inv(n_mat)
+ u_star = (m := np.linalg.inv(atoms.base.A_d.T)) @ m.T
+ u_cif = n_inv @ u_star @ n_inv.T
+ for atom_label in atoms.table.index[mask1 & mask2]:
+ u_atom = atoms.table.at[atom_label, 'Uiso'] * u_cif
+ atoms.table.loc[atom_label, u_columns] = u_atom[np.triu_indices(3)]
+
if not self.processor.settings['auto_write_unit_cell']:
return
et = self.processor.evaluation_table
@@ -326,6 +352,7 @@ class DisplacementInstructionHandler(SerialInstructionHandler):
def handle_one(self, instruction: Instruction, ms_key: str, ms: ModelState) -> None:
focus = ms.nodes.locate(self.processor.selection)
+ assert len(focus) > 0
for label, displacements in focus.table.iterrows():
for suffix in 'Uiso U11 U22 U33 U23 U13 U12'.split():
label_ = label + '_' + suffix
diff --git a/picometer/settings.py b/picometer/settings.py
index 4068407..bfb2d83 100644
--- a/picometer/settings.py
+++ b/picometer/settings.py
@@ -19,6 +19,8 @@ class DefaultSettings:
"""Store default values of all settings. Use `AnyValue` if no default."""
auto_write_unit_cell: bool = True
clear_selection_after_use: bool = True
+ complete_uiso_from_umatrix: bool = False
+ complete_umatrix_from_uiso: bool = False
@classmethod
def get_field(cls, key: str) -> Field:
diff --git a/picometer/settings.yaml b/picometer/settings.yaml
index f06ec18..a2aa79b 100644
--- a/picometer/settings.yaml
+++ b/picometer/settings.yaml
@@ -1,3 +1,5 @@
settings:
auto_write_unit_cell: True
clear_selection_after_use: True
+ complete_uiso_from_umatrix: False
+ complete_umatrix_from_uiso: False
diff --git a/tests/cobalt.cif b/tests/cobalt.cif
new file mode 100644
index 0000000..1836257
--- /dev/null
+++ b/tests/cobalt.cif
@@ -0,0 +1,311 @@
+#------------------------------------------------------------------------------
+#$Date: 2016-02-21 02:03:34 +0200 (Sun, 21 Feb 2016) $
+#$Revision: 176798 $
+#$URL: svn://www.crystallography.net/cod/cif/2/23/23/2232341.cif $
+#------------------------------------------------------------------------------
+#
+# This file is available in the Crystallography Open Database (COD),
+# http://www.crystallography.net/. The original data for this entry
+# were provided by IUCr Journals, http://journals.iucr.org/.
+#
+# The file may be used within the scientific community so long as
+# proper attribution is given to the journal article from which the
+# data were obtained.
+#
+data_2232341
+loop_
+_publ_author_name
+'Golenia, Irina A.'
+'Boyko, Alexander N.'
+'Kotova, Natalia V.'
+'Haukka, Matti'
+'Kalibabchuk, Valentina A.'
+_publ_section_title
+;
+ fac-Tris(pyridine-2-carboxylato-\k^2^N,O)cobalt(III)
+;
+_journal_coeditor_code HY2479
+_journal_issue 11
+_journal_name_full 'Acta Crystallographica Section E'
+_journal_page_first m1596
+_journal_page_last m1597
+_journal_paper_doi 10.1107/S1600536811043303
+_journal_volume 67
+_journal_year 2011
+_chemical_formula_iupac '[Co (C6 H4 N O2)3]'
+_chemical_formula_moiety 'C18 H12 Co N3 O6'
+_chemical_formula_sum 'C18 H12 Co N3 O6'
+_chemical_formula_weight 425.24
+_chemical_name_systematic
+;
+fac-Tris(pyridine-2-carboxylato-\k^2^N,O)cobalt(III)
+;
+_space_group_IT_number 168
+_symmetry_cell_setting hexagonal
+_symmetry_space_group_name_Hall 'P 6'
+_symmetry_space_group_name_H-M 'P 6'
+_atom_sites_solution_hydrogens geom
+_atom_sites_solution_primary direct
+_atom_sites_solution_secondary difmap
+_audit_creation_method SHELXL-97
+_cell_angle_alpha 90.00
+_cell_angle_beta 90.00
+_cell_angle_gamma 120.00
+_cell_formula_units_Z 2
+_cell_length_a 12.8617(12)
+_cell_length_b 12.8617(12)
+_cell_length_c 6.2122(9)
+_cell_measurement_reflns_used 713
+_cell_measurement_temperature 120(2)
+_cell_measurement_theta_max 24.48
+_cell_measurement_theta_min 3.20
+_cell_volume 889.96(17)
+_computing_cell_refinement 'DENZO/SCALEPACK (Otwinowski & Minor, 1997)'
+_computing_data_collection 'COLLECT (Nonius, 1998)'
+_computing_data_reduction 'DENZO/SCALEPACK (Otwinowski & Minor, 1997)'
+_computing_molecular_graphics 'DIAMOND (Brandenburg, 1999)'
+_computing_publication_material 'SHELXL97 (Sheldrick, 2008)'
+_computing_structure_refinement 'SHELXL97 (Sheldrick, 2008)'
+_computing_structure_solution 'SIR2004 (Burla et al., 2005)'
+_diffrn_ambient_temperature 120(2)
+_diffrn_detector_area_resol_mean 9
+_diffrn_measured_fraction_theta_full 0.993
+_diffrn_measured_fraction_theta_max 0.993
+_diffrn_measurement_device '95mm CCD camera on \k-goniostat'
+_diffrn_measurement_device_type 'Nonius KappaCCD'
+_diffrn_measurement_method '\f and \w scans with \k offset'
+_diffrn_radiation_monochromator 'horizontally mounted graphite crystal'
+_diffrn_radiation_source 'fine-focus sealed tube'
+_diffrn_radiation_type MoK\a
+_diffrn_radiation_wavelength 0.71073
+_diffrn_reflns_av_R_equivalents 0.0428
+_diffrn_reflns_av_sigmaI/netI 0.0346
+_diffrn_reflns_limit_h_max 15
+_diffrn_reflns_limit_h_min -15
+_diffrn_reflns_limit_k_max 15
+_diffrn_reflns_limit_k_min -15
+_diffrn_reflns_limit_l_max 7
+_diffrn_reflns_limit_l_min -7
+_diffrn_reflns_number 5635
+_diffrn_reflns_theta_full 25.00
+_diffrn_reflns_theta_max 25.00
+_diffrn_reflns_theta_min 3.17
+_exptl_absorpt_coefficient_mu 1.006
+_exptl_absorpt_correction_T_max 0.9695
+_exptl_absorpt_correction_T_min 0.8001
+_exptl_absorpt_correction_type multi-scan
+_exptl_absorpt_process_details '(DENZO/SCALEPACK; Otwinowski & Minor, 1997)'
+_exptl_crystal_colour pink
+_exptl_crystal_density_diffrn 1.587
+_exptl_crystal_density_method 'not measured'
+_exptl_crystal_description block
+_exptl_crystal_F_000 432
+_exptl_crystal_size_max 0.23
+_exptl_crystal_size_mid 0.08
+_exptl_crystal_size_min 0.03
+_refine_diff_density_max 1.048
+_refine_diff_density_min -0.594
+_refine_ls_abs_structure_details 'Flack (1983), 400 Friedel pairs'
+_refine_ls_abs_structure_Flack -0.02(7)
+_refine_ls_extinction_method none
+_refine_ls_goodness_of_fit_ref 1.158
+_refine_ls_hydrogen_treatment constr
+_refine_ls_matrix_type full
+_refine_ls_number_parameters 86
+_refine_ls_number_reflns 978
+_refine_ls_number_restraints 1
+_refine_ls_restrained_S_all 1.157
+_refine_ls_R_factor_all 0.0753
+_refine_ls_R_factor_gt 0.0682
+_refine_ls_shift/su_max 0.000
+_refine_ls_shift/su_mean 0.000
+_refine_ls_structure_factor_coef Fsqd
+_refine_ls_weighting_details
+'calc w=1/[\s^2^(Fo^2^)+(0.1316P)^2^+0.9442P] where P=(Fo^2^+2Fc^2^)/3'
+_refine_ls_weighting_scheme calc
+_refine_ls_wR_factor_gt 0.1873
+_refine_ls_wR_factor_ref 0.1972
+_reflns_number_gt 893
+_reflns_number_total 978
+_reflns_threshold_expression I>2\s(I)
+_cod_data_source_file hy2479.cif
+_cod_data_source_block I
+_cod_original_cell_volume 890.0(2)
+_cod_database_code 2232341
+_cod_database_fobs_code 2232341
+loop_
+_symmetry_equiv_pos_as_xyz
+'x, y, z'
+'x-y, x, z'
+'-y, x-y, z'
+'-x, -y, z'
+'-x+y, -x, z'
+'y, -x+y, z'
+loop_
+_atom_site_type_symbol
+_atom_site_label
+_atom_site_fract_x
+_atom_site_fract_y
+_atom_site_fract_z
+_atom_site_U_iso_or_equiv
+_atom_site_adp_type
+_atom_site_calc_flag
+_atom_site_refinement_flags
+_atom_site_occupancy
+_atom_site_symmetry_multiplicity
+Co Co1 0.3333 0.6667 0.3408(2) 0.0379(5) Uani d S 1 3
+O O1 0.4550(7) 0.7860(7) 0.5147(11) 0.0514(18) Uani d . 1 1
+O O2 0.6490(9) 0.8907(8) 0.5627(16) 0.083(3) Uani d . 1 1
+N N1 0.4619(6) 0.6785(6) 0.1705(12) 0.0332(15) Uani d . 1 1
+C C1 0.4622(10) 0.6303(9) -0.0009(17) 0.051(2) Uani d . 1 1
+H H1 0.3875 0.5756 -0.0656 0.061 Uiso calc R 1 1
+C C2 0.5687(9) 0.6533(9) -0.1037(17) 0.050(2) Uani d . 1 1
+H H2 0.5659 0.6145 -0.2355 0.061 Uiso calc R 1 1
+C C3 0.6747(10) 0.7308(9) -0.0138(18) 0.052(2) Uani d . 1 1
+H H3 0.7483 0.7480 -0.0795 0.062 Uiso calc R 1 1
+C C4 0.6729(9) 0.7796(9) 0.159(2) 0.052(3) Uani d . 1 1
+H H4 0.7469 0.8358 0.2241 0.062 Uiso calc R 1 1
+C C5 0.5690(8) 0.7548(9) 0.2577(18) 0.048(2) Uani d . 1 1
+C C6 0.5660(11) 0.8159(9) 0.4512(18) 0.057(3) Uani d . 1 1
+loop_
+_atom_site_aniso_label
+_atom_site_aniso_U_11
+_atom_site_aniso_U_22
+_atom_site_aniso_U_33
+_atom_site_aniso_U_12
+_atom_site_aniso_U_13
+_atom_site_aniso_U_23
+Co1 0.0513(7) 0.0513(7) 0.0111(8) 0.0256(3) 0.000 0.000
+O1 0.067(4) 0.066(4) 0.018(4) 0.031(4) -0.007(3) -0.008(3)
+O2 0.105(7) 0.067(5) 0.054(6) 0.027(4) -0.036(5) 0.004(4)
+N1 0.045(4) 0.047(4) 0.014(3) 0.028(3) -0.001(3) 0.007(3)
+C1 0.065(5) 0.056(5) 0.034(5) 0.031(4) 0.006(4) 0.008(4)
+C2 0.061(5) 0.058(5) 0.041(6) 0.036(5) 0.010(4) 0.006(4)
+C3 0.060(6) 0.059(6) 0.046(6) 0.038(5) 0.014(5) 0.016(5)
+C4 0.042(5) 0.067(6) 0.055(7) 0.033(4) 0.012(5) 0.023(6)
+C5 0.048(5) 0.057(5) 0.042(6) 0.029(4) -0.005(4) 0.021(5)
+C6 0.071(7) 0.052(6) 0.036(6) 0.021(5) -0.027(6) 0.010(5)
+loop_
+_atom_type_symbol
+_atom_type_description
+_atom_type_scat_dispersion_real
+_atom_type_scat_dispersion_imag
+_atom_type_scat_source
+C C 0.0033 0.0016 'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
+H H 0.0000 0.0000 'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
+N N 0.0061 0.0033 'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
+O O 0.0106 0.0060 'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
+Co Co 0.3494 0.9721 'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
+loop_
+_geom_angle_atom_site_label_1
+_geom_angle_atom_site_label_2
+_geom_angle_atom_site_label_3
+_geom_angle_site_symmetry_1
+_geom_angle_site_symmetry_3
+_geom_angle
+O1 Co1 O1 3_665 . 90.6(3)
+O1 Co1 O1 3_665 5_565 90.6(3)
+O1 Co1 O1 . 5_565 90.6(3)
+O1 Co1 N1 3_665 3_665 85.4(3)
+O1 Co1 N1 . 3_665 92.1(2)
+O1 Co1 N1 5_565 3_665 175.1(3)
+O1 Co1 N1 3_665 5_565 92.1(2)
+O1 Co1 N1 . 5_565 175.1(3)
+O1 Co1 N1 5_565 5_565 85.4(3)
+N1 Co1 N1 3_665 5_565 92.1(3)
+O1 Co1 N1 3_665 . 175.1(3)
+O1 Co1 N1 . . 85.4(3)
+O1 Co1 N1 5_565 . 92.1(2)
+N1 Co1 N1 3_665 . 92.1(3)
+N1 Co1 N1 5_565 . 92.1(3)
+C6 O1 Co1 . . 113.3(7)
+C1 N1 C5 . . 117.1(9)
+C1 N1 Co1 . . 131.3(7)
+C5 N1 Co1 . . 111.5(7)
+N1 C1 C2 . . 122.4(10)
+N1 C1 H1 . . 118.8
+C2 C1 H1 . . 118.8
+C3 C2 C1 . . 119.3(10)
+C3 C2 H2 . . 120.4
+C1 C2 H2 . . 120.4
+C4 C3 C2 . . 117.6(10)
+C4 C3 H3 . . 121.2
+C2 C3 H3 . . 121.2
+C3 C4 C5 . . 122.2(11)
+C3 C4 H4 . . 118.9
+C5 C4 H4 . . 118.9
+N1 C5 C4 . . 121.4(11)
+N1 C5 C6 . . 115.8(10)
+C4 C5 C6 . . 122.5(11)
+O2 C6 O1 . . 116.2(13)
+O2 C6 C5 . . 130.0(13)
+O1 C6 C5 . . 113.8(10)
+loop_
+_geom_bond_atom_site_label_1
+_geom_bond_atom_site_label_2
+_geom_bond_site_symmetry_2
+_geom_bond_distance
+Co1 O1 3_665 1.889(7)
+Co1 O1 . 1.889(7)
+Co1 O1 5_565 1.889(7)
+Co1 N1 3_665 1.904(7)
+Co1 N1 5_565 1.904(7)
+Co1 N1 . 1.904(7)
+O1 C6 . 1.339(15)
+O2 C6 . 1.232(14)
+N1 C1 . 1.233(14)
+N1 C5 . 1.343(12)
+C1 C2 . 1.402(14)
+C1 H1 . 0.9500
+C2 C3 . 1.344(16)
+C2 H2 . 0.9500
+C3 C4 . 1.251(15)
+C3 H3 . 0.9500
+C4 C5 . 1.354(14)
+C4 H4 . 0.9500
+C5 C6 . 1.447(15)
+loop_
+_geom_hbond_atom_site_label_D
+_geom_hbond_atom_site_label_H
+_geom_hbond_atom_site_label_A
+_geom_hbond_site_symmetry_A
+_geom_hbond_distance_DH
+_geom_hbond_distance_HA
+_geom_hbond_distance_DA
+_geom_hbond_angle_DHA
+C3 H3 O2 2_654 0.95 2.60 3.212(14) 123
+loop_
+_geom_torsion_atom_site_label_1
+_geom_torsion_atom_site_label_2
+_geom_torsion_atom_site_label_3
+_geom_torsion_atom_site_label_4
+_geom_torsion_site_symmetry_1
+_geom_torsion
+O1 Co1 O1 C6 3_665 179.0(6)
+O1 Co1 O1 C6 5_565 88.4(8)
+N1 Co1 O1 C6 . -3.7(7)
+O1 Co1 N1 C1 . -175.4(8)
+O1 Co1 N1 C1 5_565 94.2(9)
+N1 Co1 N1 C1 3_665 -83.4(7)
+N1 Co1 N1 C1 5_565 8.8(8)
+O1 Co1 N1 C5 . 3.7(6)
+O1 Co1 N1 C5 5_565 -86.7(6)
+N1 Co1 N1 C5 3_665 95.7(7)
+N1 Co1 N1 C5 5_565 -172.1(6)
+C5 N1 C1 C2 . -0.3(13)
+Co1 N1 C1 C2 . 178.7(6)
+N1 C1 C2 C3 . -0.2(14)
+C1 C2 C3 C4 . -0.2(14)
+C2 C3 C4 C5 . 1.2(15)
+C1 N1 C5 C4 . 1.3(13)
+Co1 N1 C5 C4 . -177.9(7)
+C1 N1 C5 C6 . 176.1(7)
+Co1 N1 C5 C6 . -3.1(8)
+C3 C4 C5 N1 . -1.8(14)
+C3 C4 C5 C6 . -176.3(8)
+Co1 O1 C6 O2 . -177.8(7)
+Co1 O1 C6 C5 . 2.9(9)
+N1 C5 C6 O2 . -179.0(10)
+C4 C5 C6 O2 . -4.2(13)
+N1 C5 C6 O1 . 0.2(9)
+C4 C5 C6 O1 . 174.9(9)
diff --git a/tests/ferrocene2.cif b/tests/ferrocene2.cif
index 6700503..5cd3f0b 100644
--- a/tests/ferrocene2.cif
+++ b/tests/ferrocene2.cif
@@ -71,9 +71,9 @@ _atom_site_aniso_U_33
_atom_site_aniso_U_23
_atom_site_aniso_U_13
_atom_site_aniso_U_12
-Fe .01 .01 .01 .0 .0 .0
-C(11) .02 .02 .02 .0 .0 .0
-C(12) .02 .02 .02 .0 .0 .0
-C(13) .02 .02 .02 .0 .0 .0
-C(14) .02 .02 .02 .0 .0 .0
-C(15) .02 .02 .02 .0 .0 .0
+Fe .01 .01 .01 .0 .005143 .0
+C(11) .02 .02 .02 .0 .010286 .0
+C(12) .02 .02 .02 .0 .010286 .0
+C(13) .02 .02 .02 .0 .010286 .0
+C(14) .02 .02 .02 .0 .010286 .0
+C(15) .02 .02 .02 .0 .010286 .0
diff --git a/tests/test_instructions.py b/tests/test_instructions.py
index ad6e59a..73687b1 100644
--- a/tests/test_instructions.py
+++ b/tests/test_instructions.py
@@ -7,7 +7,6 @@
import unittest
import numpy as np
-import pandas
import pandas as pd
from pandas.testing import assert_frame_equal
@@ -299,6 +298,30 @@ def test_displacement(self):
self.assertEqual(results[1], 0.02)
np.testing.assert_equal(results[0], np.nan)
+ def test_displacement_complete_uiso_from_umatrix(self):
+ r = 'settings: \n complete_uiso_from_umatrix: True\n' + self.routine_text
+ r += ' - select: C(11)\n - displacement\n'
+ p = process(Routine.from_string(r))
+ results = p.evaluation_table['C(11)_Uiso'].to_numpy()
+ self.assertEqual(results[0], 0.02)
+ self.assertEqual(results[1], 0.02)
+ np.testing.assert_equal(results[2], np.nan)
+ np.testing.assert_equal(results[3], np.nan)
+ np.testing.assert_equal(results[4], np.nan)
+ np.testing.assert_equal(results[5], np.nan)
+
+ def test_displacement_complete_umatrix_from_Uiso(self):
+ r = 'settings: \n complete_umatrix_from_uiso: True\n' + self.routine_text
+ r += ' - select: C(11)\n - displacement\n'
+ p = process(Routine.from_string(r))
+ results = p.evaluation_table['C(11)_U13'].to_numpy()
+ self.assertAlmostEqual(results[0], 0.010286, places=6)
+ self.assertAlmostEqual(results[0], 0.010286, places=6)
+ np.testing.assert_equal(results[2], np.nan)
+ np.testing.assert_equal(results[3], np.nan)
+ np.testing.assert_equal(results[4], np.nan)
+ np.testing.assert_equal(results[5], np.nan)
+
def test_distance_plane_plane(self):
self.routine_text += ' - select: cp_A_plane\n'
self.routine_text += ' - select: cp_B_plane\n'
diff --git a/tests/test_transformations.py b/tests/test_transformations.py
new file mode 100644
index 0000000..0cc34d0
--- /dev/null
+++ b/tests/test_transformations.py
@@ -0,0 +1,42 @@
+import importlib.resources
+import unittest
+
+import numpy as np
+
+from picometer.atom import AtomSet
+
+
+class TestTransformations(unittest.TestCase):
+ rot3_at_cobalt_code = '1-y,1+x-y,z'
+ rot6_at_origin_code = 'x-y,x,z'
+
+ @classmethod
+ def setUpClass(cls) -> None:
+ with importlib.resources.path('tests', 'cobalt.cif') as cif_path:
+ cls.atoms = AtomSet.from_cif(str(cif_path))
+
+ def test_transform_coordinates(self) -> None:
+ t = self.atoms.table
+ t3 = self.atoms.transform(self.rot3_at_cobalt_code).table
+ t6 = self.atoms.transform(self.rot6_at_origin_code).table
+ self.assertAlmostEqual(t.at['Co1', 'fract_x'], t3.at['Co1', 'fract_x'], places=3)
+ self.assertAlmostEqual(t.at['Co1', 'fract_y'], t3.at['Co1', 'fract_y'], places=3)
+ self.assertAlmostEqual(t.at['Co1', 'fract_z'], t3.at['Co1', 'fract_z'], places=3)
+ self.assertNotAlmostEqual(t.at['Co1', 'fract_x'], t6.at['Co1', 'fract_x'], places=3)
+ self.assertNotAlmostEqual(t.at['Co1', 'fract_y'], t6.at['Co1', 'fract_y'], places=3)
+ self.assertAlmostEqual(t.at['Co1', 'fract_z'], t6.at['Co1', 'fract_z'], places=3)
+
+ def test_transform_u_matrix(self) -> None:
+ t = self.atoms.table
+ t3 = self.atoms.transform(self.rot3_at_cobalt_code).table
+ t6 = self.atoms.transform(self.rot6_at_origin_code).table
+ us = ['U11', 'U22', 'U33', 'U12', 'U13', 'U23']
+ np.testing.assert_allclose(t.loc['Co1', us], t3.loc['Co1', us], rtol=0.01)
+ np.testing.assert_allclose(t.loc['Co1', us], t6.loc['Co1', us], rtol=0.01)
+ with np.testing.assert_raises(AssertionError):
+ np.testing.assert_allclose(t.loc['O1', us], t3.loc['O1', us], rtol=0.1)
+ np.testing.assert_equal(t.loc['O1', us], np.array([.067,.066,.018,.031,-.007,-.008]))
+
+
+if __name__ == '__main__':
+ unittest.main()