Convert an OPTIMADE structure, in the format of
StructureResource
to a CIF file (Crystallographic Information File).
For more information on the CIF file format, see
the official documentation.
Note
This conversion function is inspired heavily by the similar conversion
function in the ASE library.
See here for the original ASE code.
For more information on the ASE library, see their documentation.
This conversion function relies on the NumPy library.
get_cif(optimade_structure)
Get CIF file as string from OPTIMADE structure.
Parameters:
Returns:
Type |
Description |
str
|
The CIF file as a single Python str object.
|
Source code in optimade/adapters/structures/cif.py
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 | def get_cif(
optimade_structure: OptimadeStructure,
) -> str:
"""Get CIF file as string from OPTIMADE structure.
Parameters:
optimade_structure: OPTIMADE structure.
Returns:
The CIF file as a single Python `str` object.
"""
# NumPy is needed for calculations
if globals().get("np", None) is None:
warn(NUMPY_NOT_FOUND, AdapterPackageNotFound)
return None # type: ignore[return-value]
cif = """#
# Created from an OPTIMADE structure.
#
# See https://www.optimade.org and/or
# https://github.com/Materials-Consortia/OPTIMADE for more information.
#
"""
cif += f"data_{optimade_structure.id}\n\n"
attributes = optimade_structure.attributes
# Do this only if there's three non-zero lattice vectors
# NOTE: This also negates handling of lattice_vectors with null/None values
if valid_lattice_vector(attributes.lattice_vectors): # type:ignore[arg-type]
a_vector, b_vector, c_vector, alpha, beta, gamma = cell_to_cellpar(
attributes.lattice_vectors # type: ignore[arg-type]
)
cif += (
f"_cell_length_a {a_vector:g}\n"
f"_cell_length_b {b_vector:g}\n"
f"_cell_length_c {c_vector:g}\n"
f"_cell_angle_alpha {alpha:g}\n"
f"_cell_angle_beta {beta:g}\n"
f"_cell_angle_gamma {gamma:g}\n\n"
)
cif += (
"_symmetry_space_group_name_H-M 'P 1'\n"
"_symmetry_int_tables_number 1\n\n"
"loop_\n"
" _symmetry_equiv_pos_as_xyz\n"
" 'x, y, z'\n\n"
)
# Since some structure viewers are having issues with cartesian coordinates,
# we calculate the fractional coordinates if this is a 3D structure and we have all the necessary information.
if not hasattr(attributes, "fractional_site_positions"):
attributes.fractional_site_positions = fractional_coordinates(
cell=attributes.lattice_vectors, # type:ignore[arg-type]
cartesian_positions=attributes.cartesian_site_positions, # type:ignore[arg-type]
)
# NOTE: This is otherwise a bit ahead of its time, since this OPTIMADE property is part of an open PR.
# See https://github.com/Materials-Consortia/OPTIMADE/pull/206
coord_type = (
"fract" if hasattr(attributes, "fractional_site_positions") else "Cartn"
)
cif += (
"loop_\n"
" _atom_site_type_symbol\n" # species.chemical_symbols
" _atom_site_label\n" # species.name + unique int
" _atom_site_occupancy\n" # species.concentration
f" _atom_site_{coord_type}_x\n" # cartesian_site_positions
f" _atom_site_{coord_type}_y\n" # cartesian_site_positions
f" _atom_site_{coord_type}_z\n" # cartesian_site_positions
" _atom_site_thermal_displace_type\n" # Set to 'Biso'
" _atom_site_B_iso_or_equiv\n" # Set to 1.0:f
)
if coord_type == "fract":
sites = attributes.fractional_site_positions
else:
sites = attributes.cartesian_site_positions
species: dict[str, OptimadeStructureSpecies] = {
species.name: species
for species in attributes.species # type: ignore[union-attr]
}
symbol_occurences: dict[str, int] = {}
for site_number in range(attributes.nsites): # type: ignore[arg-type]
species_name = attributes.species_at_sites[site_number] # type: ignore[index]
site = sites[site_number]
current_species = species[species_name]
for index, symbol in enumerate(current_species.chemical_symbols):
if symbol == "vacancy":
continue
if symbol in symbol_occurences:
symbol_occurences[symbol] += 1
else:
symbol_occurences[symbol] = 1
label = f"{symbol}{symbol_occurences[symbol]}"
cif += (
f" {symbol} {label} {current_species.concentration[index]:6.4f} {site[0]:8.5f} "
f"{site[1]:8.5f} {site[2]:8.5f} {'Biso':4} {'1.000':6}\n"
)
return cif
|