1# type: ignore
2import numpy as np
3
4from ase import Atoms
5from ase.io import read, write
6import ase.io.rmc6f as rmc6f
7from ase.lattice.compounds import TRI_Fe2O3
8
9
10rmc6f_input_text = """
11(Version 6f format configuration file)
12Metadata owner:     Joe Smith
13Metadata date:      31-01-1900
14Metadata material:  SF6
15Metadata comment:  Some comments go here...
16Metadata source:    GSAS
17Number of moves generated:           89692
18Number of moves tried:               85650
19Number of moves accepted:            10074
20Number of prior configuration saves: 0
21Number of atoms:                     7
22Number density (Ang^-3):                 0.068606
23Supercell dimensions:                1 1 1
24Cell (Ang/deg): 4.672816 4.672816 4.672816 90.0 90.0 90.0
25Lattice vectors (Ang):
26    4.672816    0.000000    0.000000
27    0.000000    4.672816    0.000000
28    0.000000    0.000000    4.672816
29Atoms:
30     1   S     0.600452    0.525100    0.442050     1   0   0   0
31     2   F     0.911952    0.450722    0.382733     2   0   0   0
32     3   F     0.283794    0.616712    0.500094     3   0   0   0
33     4   F     0.679823    0.854839    0.343915     4   0   0   0
34     5   F     0.531660    0.229024    0.535688     5   0   0   0
35     6   F     0.692514    0.584931    0.746683     6   0   0   0
36     7   F     0.509687    0.449350    0.111960     7   0   0   0
37"""
38# Information for system
39positions = [
40    [0.600452, 0.525100, 0.442050],
41    [0.911952, 0.450722, 0.382733],
42    [0.283794, 0.616712, 0.500094],
43    [0.679823, 0.854839, 0.343915],
44    [0.531660, 0.229024, 0.535688],
45    [0.692514, 0.584931, 0.746683],
46    [0.509687, 0.449350, 0.111960]]
47symbols = ['S', 'F', 'F', 'F', 'F', 'F', 'F']
48lat = 4.672816
49
50# Intermediate data structures to test against
51symbol_xyz_list = [[s] + xyz for s, xyz in zip(symbols, positions)]
52symbol_xyz_dict = {i + 1: row for i, row in enumerate(symbol_xyz_list)}
53symbol_xyz_list_ext = [tuple([i + 1] + row + [0, 0, 0, 0])
54                       for i, row in enumerate(symbol_xyz_list)]
55
56# Target Atoms system
57lat_positions = lat * np.array(positions)
58cell = [lat, lat, lat]
59rmc6f_atoms = Atoms(symbols, positions=lat_positions, cell=cell, pbc=[1, 1, 1])
60
61
62def test_rmc6f_read():
63    """Test for reading rmc6f input file."""
64    with open('input.rmc6f', 'w') as rmc6f_input_f:
65        rmc6f_input_f.write(rmc6f_input_text)
66
67    rmc6f_input_atoms = read('input.rmc6f')
68    assert len(rmc6f_input_atoms) == 7
69    assert rmc6f_input_atoms == rmc6f_atoms
70
71
72def test_rmc6f_write():
73    """Test for writing rmc6f input file."""
74    tol = 1e-5
75    write('output.rmc6f', rmc6f_atoms)
76    readback = read('output.rmc6f')
77    assert np.allclose(rmc6f_atoms.positions, readback.positions, rtol=tol)
78    assert readback == rmc6f_atoms
79
80
81def test_rmc6f_write_with_order():
82    """Test for writing rmc6f input file with order passed in."""
83    tol = 1e-5
84    write('output.rmc6f', rmc6f_atoms, order=['F', 'S'])
85    readback = read('output.rmc6f')
86    reordered_positions = np.vstack(
87        (rmc6f_atoms.positions[1:7], rmc6f_atoms.positions[0]))
88    assert np.allclose(reordered_positions, readback.positions, rtol=tol)
89
90
91def test_rmc6f_write_with_triclinic_system():
92    """Test for writing rmc6f input file for triclinic system
93    """
94    tol = 1e-5
95    fe4o6 = TRI_Fe2O3(
96        symbol=('Fe', 'O'),
97        latticeconstant={
98            'a': 5.143,
99            'b': 5.383,
100            'c': 14.902,
101            'alpha': 90.391,
102            'beta': 90.014,
103            'gamma': 89.834},
104        size=(1, 1, 1))
105
106    # Make sure these are the returned cells (verified correct for rmc6f file)
107    va = [5.143, 0.0, 0.0]
108    vb = [0.015596, 5.382977, 0.0]
109    vc = [-0.00364124, -0.101684, 14.901653]
110
111    write('output.rmc6f', fe4o6)
112    readback = read('output.rmc6f')
113    assert np.allclose(fe4o6.positions, readback.positions, rtol=tol)
114    assert np.allclose(va, readback.cell[0], rtol=tol)
115    assert np.allclose(vb, readback.cell[1], rtol=tol)
116    assert np.allclose(vc, readback.cell[2], rtol=tol)
117
118
119def test_rmc6f_read_construct_regex():
120    """Test for utility function that constructs rmc6f header regex."""
121    header_lines = [
122        "Number of atoms:",
123        "  Supercell dimensions:  ",
124        "    Cell (Ang/deg):  ",
125        "      Lattice vectors (Ang):  "]
126    result = rmc6f._read_construct_regex(header_lines)
127    target = "(Number\\s+of\\s+atoms:|Supercell\\s+dimensions:|Cell\\s+\\(Ang/deg\\):|Lattice\\s+vectors\\s+\\(Ang\\):)"  # noqa: E501
128    assert result == target
129
130
131def test_rmc6f_read_line_of_atoms_section_style_no_labels():
132    """Test for reading a line of atoms section
133    w/ 'no labels' style for rmc6f
134    """
135    atom_line = "1 S 0.600452 0.525100 0.442050 1 0 0 0"
136    atom_id, props = rmc6f._read_line_of_atoms_section(atom_line.split())
137    target_id = 1
138    target_props = ['S', 0.600452, 0.5251, 0.44205]
139    assert atom_id == target_id
140    assert props == target_props
141
142
143def test_rmc6f_read_line_of_atoms_section_style_labels():
144    """Test for reading a line of atoms section
145    w/ 'labels'-included style for rmc6f
146    """
147    atom_line = "1 S [1] 0.600452 0.525100 0.442050 1 0 0 0"
148    atom_id, props = rmc6f._read_line_of_atoms_section(atom_line.split())
149    target_id = 1
150    target_props = ['S', 0.600452, 0.5251, 0.44205]
151    assert atom_id == target_id
152    assert props == target_props
153
154
155def test_rmc6f_read_line_of_atoms_section_style_magnetic():
156    """Test for reading a line of atoms section
157    w/ 'magnetic' style for rmc6f
158    """
159    atom_line = "1 S 0.600452 0.525100 0.442050 1 0 0 0 M: 0.1"
160    atom_id, props = rmc6f._read_line_of_atoms_section(atom_line.split())
161    target_id = 1
162    target_props = ['S', 0.600452, 0.5251, 0.44205, 0.1]
163    assert atom_id == target_id
164    assert props == target_props
165
166
167def test_rmc6f_read_process_rmc6f_lines_to_pos_and_cell():
168    """Test for utility function that processes lines of rmc6f using
169    regular expressions to capture atom properties and cell information
170    """
171    tol = 1e-5
172    lines = rmc6f_input_text.split('\n')
173    props, cell = rmc6f._read_process_rmc6f_lines_to_pos_and_cell(lines)
174
175    target_cell = np.zeros((3, 3), float)
176    np.fill_diagonal(target_cell, 4.672816)
177
178    assert props == symbol_xyz_dict
179    assert np.allclose(cell, target_cell, rtol=tol)
180
181
182def test_rmc6f_read_process_rmc6f_lines_to_pos_and_cell_padded_whitespace():
183    """Test for utility function that processes lines of rmc6f using
184    regular expressions to capture atom properties and cell information
185    with puposeful whitespace padded on one line
186    """
187    tol = 1e-5
188    lines = rmc6f_input_text.split('\n')
189    lines[14] = "    {}    ".format(lines[14])  # intentional whitespace
190    props, cell = rmc6f._read_process_rmc6f_lines_to_pos_and_cell(lines)
191
192    target_cell = np.zeros((3, 3), float)
193    np.fill_diagonal(target_cell, 4.672816)
194
195    assert props == symbol_xyz_dict
196    assert np.allclose(cell, target_cell, rtol=tol)
197
198
199def test_rmc6f_write_output_column_format():
200    """Test for utility function that processes the columns in array
201    and gets back out formatting information.
202    """
203    cols = ['id', 'symbols', 'scaled_positions', 'ref_num', 'ref_cell']
204    arrays = {}
205    arrays['id'] = np.array([1, 2, 3, 4, 5, 6, 7])
206    arrays['symbols'] = np.array(symbols)
207    arrays['ref_num'] = np.zeros(7, int)
208    arrays['ref_cell'] = np.zeros((7, 3), int)
209    arrays['scaled_positions'] = lat_positions / lat
210
211    ncols, dtype_obj, fmt = rmc6f._write_output_column_format(cols, arrays)
212
213    target_ncols = [1, 1, 3, 1, 3]
214    target_fmt = "%8d %s%14.6f %14.6f %14.6f %8d %8d %8d %8d \n"
215
216    assert ncols == target_ncols
217    assert fmt == target_fmt
218
219
220def test_rmc6f_write_output():
221    """Test for utility function for writing rmc6f output
222    """
223    fileobj = "output.rmc6f"
224    header_lines = [
225        '(Version 6f format configuration file)',
226        '(Generated by ASE - Atomic Simulation Environment https://wiki.fysik.dtu.dk/ase/ )',  # noqa: E501
227        "Metadata date:18-007-'2019",
228        'Number of types of atoms:   2 ',
229        'Atom types present:          S F',
230        'Number of each atom type:   1 6',
231        'Number of moves generated:           0',
232        'Number of moves tried:               0',
233        'Number of moves accepted:            0',
234        'Number of prior configuration saves: 0',
235        'Number of atoms:                     7',
236        'Supercell dimensions:                1 1 1',
237        'Number density (Ang^-3):              0.06860598423060468',
238        'Cell (Ang/deg): 4.672816 4.672816 4.672816 90.0 90.0 90.0',
239        'Lattice vectors (Ang):',
240        '    4.672816     0.000000     0.000000',
241        '    0.000000     4.672816     0.000000',
242        '    0.000000     0.000000     4.672816',
243        'Atoms:']
244
245    data_array = symbol_xyz_list_ext
246    data_dtype = [
247        ('id', '<i8'),
248        ('symbols', '<U1'),
249        ('scaled_positions0', '<f8'),
250        ('scaled_positions1', '<f8'),
251        ('scaled_positions2', '<f8'),
252        ('ref_num', '<i8'),
253        ('ref_cell0', '<i8'),
254        ('ref_cell1', '<i8'),
255        ('ref_cell2', '<i8')]
256    data = np.array(data_array, dtype=data_dtype)
257    fmt = "%8d %s%14.6f %14.6f %14.6f %8d %8d %8d %8d \n"
258    with open('output.rmc6f', 'w') as fileobj:
259        rmc6f._write_output(fileobj, header_lines, data, fmt)
260