File: acemolecule.py

package info (click to toggle)
python-ase 3.21.1-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 13,936 kB
  • sloc: python: 122,428; xml: 946; makefile: 111; javascript: 47
file content (279 lines) | stat: -rw-r--r-- 11,352 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# type: ignore
import os
from copy import deepcopy
from ase.io.acemolecule import read_acemolecule_out
from ase.calculators.calculator import ReadError
from ase.calculators.calculator import FileIOCalculator


class ACE(FileIOCalculator):
    '''
    ACE-Molecule logfile reader
    It has default parameters of each input section
    And parameters' type = list of dictionaries
    '''


    name = 'ace'
    implemented_properties = ['energy', 'forces', 'excitation-energy']
    #    results = {}
    # 'geometry', 'excitation-energy']
    # defaults is default section_name of ACE-input
    basic_list = [{
        'Type': 'Scaling', 'Scaling': '0.35', 'Basis': 'Sinc',
                  'Grid': 'Sphere',
                  'KineticMatrix': 'Finite_Difference', 'DerivativesOrder': '7',
                  'GeometryFilename': None, 'NumElectrons': None}
                  ]
    scf_list = [{
        'ExchangeCorrelation': {'XFunctional': 'GGA_X_PBE', 'CFunctional': 'GGA_C_PBE'},
        'NumberOfEigenvalues': None,
    }]

    force_list = [{'ForceDerivative': 'Potential'}]
    tddft_list = [{
        'SortOrbital': 'Order', 'MaximumOrder': '10',
        'ExchangeCorrelation': {'XFunctional': 'GGA_X_PBE', 'CFunctional': 'GGA_C_PBE'},
    }]

    order_list = ['BasicInformation', 'Guess', 'Scf']
    guess_list = [{}]
    default_parameters = {'BasicInformation': basic_list, 'Guess' : guess_list,
                          'Scf': scf_list, 'Force': force_list, 'TDDFT': tddft_list, 'order': order_list}

    def __init__(
            self, restart=None,
            ignore_bad_restart_file=FileIOCalculator._deprecated,
            label='ace', atoms=None, command=None,
            basisfile=None, **kwargs):
        FileIOCalculator.__init__(self, restart, ignore_bad_restart_file,
                                  label, atoms, command=command, **kwargs)

    def set(self, **kwargs):
        '''Update parameters self.parameter member variable.
        1. Add default values for repeated parameter sections with self.default_parameters using order.
        2. Also add empty dictionary as an indicator for section existence if no relevant default_parameters exist.
        3. Update parameters from arguments.

        Returns
        =======
        Updated parameter
        '''
        new_parameters = deepcopy(self.parameters)

        changed_parameters = FileIOCalculator.set(self, **kwargs)

        # Add default values for repeated parameter sections with self.default_parameters using order.
        # Also add empty dictionary as an indicator for section existence if no relevant default_parameters exist.
        if 'order' in kwargs:
            new_parameters['order'] = kwargs['order']
            section_sets = set(kwargs['order'])
            for section_name in section_sets:
                repeat = kwargs['order'].count(section_name)
                if section_name in self.default_parameters.keys():
                    for i in range(repeat-1):
                        new_parameters[section_name] += deepcopy(self.default_parameters[section_name])
                else:
                    new_parameters[section_name] = []
                    for i in range(repeat):
                        new_parameters[section_name].append({})

        # Update parameters
        for section in new_parameters['order']:
            if section in kwargs.keys():
                if isinstance(kwargs[section], dict):
                    kwargs[section] = [kwargs[section]]

                i = 0
                for section_param in kwargs[section]:
                    new_parameters[section][i] = update_parameter(new_parameters[section][i], section_param)
                    i += 1
        self.parameters = new_parameters
        return changed_parameters

    def read(self, label):
        FileIOCalculator.read(self, label)
        filename = self.label + ".log"

        with open(filename, 'r') as f:
            lines = f.readlines()
        if 'WARNING' in lines:
            raise ReadError("Not convergy energy in log file {}.".format(filename))
        if '! total energy' not in lines:
            raise ReadError("Wrong ACE-Molecule log file {}.".format(filename))

        if not os.path.isfile(filename):
            raise ReadError("Wrong ACE-Molecule input file {}.".format(filename))

        self.read_results()

    def write_input(self, atoms, properties=None, system_changes=None):
        '''Initializes input parameters and xyz files. If force calculation is requested, add Force section to parameters if not exists.

        Parameters
        ==========
        atoms: ASE atoms object.
        properties: List of properties to be calculated. Should be element of self.implemented_properties.
        system_chages: Ignored.

        '''
        FileIOCalculator.write_input(self, atoms, properties, system_changes)
        with open(self.label + '.inp', 'w') as inputfile:
            xyz_name = "{}.xyz".format(self.label)
            atoms.write(xyz_name)

            run_parameters = self.prepare_input(xyz_name, properties)
            self.write_acemolecule_input(inputfile, run_parameters)

    def prepare_input(self, geometry_filename, properties):
        '''Initialize parameters dictionary based on geometry filename and calculated properties.

        Parameters
        ==========
        geometry_filename: Geometry (XYZ format) file path.
        properties: Properties to be calculated.

        Returns
        =======
        Updated version of self.parameters; geometry file and optionally Force section are updated.
        '''
        copied_parameters = deepcopy(self.parameters)
        if properties is not None and "forces" in properties and 'Force' not in copied_parameters['order']:
            copied_parameters['order'].append('Force')
        copied_parameters["BasicInformation"][0]["GeometryFilename"] = "{}.xyz".format(self.label)
        copied_parameters["BasicInformation"][0]["GeometryFormat"] = "xyz"
        return copied_parameters

    def read_results(self):
        '''Read calculation results, speficied by 'quantities' variable, from the log file.
        quantities
        =======
        energy : obtaing single point energy(eV) from log file
        forces : obtaing force of each atom form log file
        excitation-energy : it able to calculate TDDFT. Return value is None. Result is not used.
        atoms : ASE atoms object
        '''
        filename = self.label + '.log'
#        quantities = ['energy', 'forces', 'atoms', 'excitation-energy']
        #for section_name in quantities:
        self.results = read_acemolecule_out(filename)

    def write_acemolecule_section(self, fpt, section, depth=0):
        '''Write parameters in each section of input

        Parameters
        ==========
        fpt: ACE-Moleucle input file object. Should be write mode.
        section: Dictionary of a parameter section.
        depth: Nested input depth.
        '''
        for section, section_param in section.items():
            if isinstance(section_param, str) or isinstance(section_param, int) or isinstance(section_param, float):
                fpt.write('    ' * depth + str(section) + " " + str(section_param) + "\n")
            elif isinstance(section_param, dict):
                fpt.write('    ' * depth + "%% " + str(section) + "\n")
                self.write_acemolecule_section(fpt, section_param, depth + 1)
                fpt.write('    ' * depth + "%% End\n")

    def write_acemolecule_input(self, fpt, param, depth=0):
        '''Write ACE-Molecule input

        ACE-Molecule input examples (not minimal)
        %% BasicInformation
            Type    Scaling
            Scaling 0.4
            Basis   Sinc
            Cell    10.0
            Grid    Sphere
            GeometryFormat      xyz
            SpinMultiplicity    3.0
            Polarize    1
            Centered    0
            %% Pseudopotential
                Pseudopotential 1
                UsingDoubleGrid 0
                FilterType      Sinc
                Format          upf
                PSFilePath      /PATH/TO/UPF
                PSFileSuffix    .pbe-theos.UPF
            %% End
            GeometryFilename    xyz/C.xyz
        %% End
        %% Guess
            InitialGuess        3
            InitialFilenames    001.cube
            InitialFilenames    002.cube
        %% End
        %% Scf
            IterateMaxCycle     150
            ConvergenceType     Energy
            ConvergenceTolerance    0.00001
            EnergyDecomposition     1
            ComputeInitialEnergy    1
            %% Diagonalize
                Tolerance           0.000001
            %% End
            %% ExchangeCorrelation
                XFunctional     GGA_X_PBE
                CFunctional     GGA_C_PBE
            %% End
            %% Mixing
                MixingMethod         1
                MixingType           Density
                MixingParameter      0.5
                PulayMixingParameter 0.1
            %% End
        %% End

        Parameters
        ==========
        fpt: File object, should be write mode.
        param: Dictionary of parameters. Also should contain special 'order' section_name for parameter section ordering.
        depth: Nested input depth.

        Notes
        =====
         - Order of parameter section (denoted using %% -- %% BasicInformation, %% Guess, etc.) is important, because it determines calculation order.
           For example, if Guess section comes after Scf section, calculation will not run because Scf will tries to run without initial Hamiltonian.
         - Order of each parameter section-section_name pair is not important unless their keys are the same.
         - Indentation unimportant and capital letters are important.
        '''
        prefix = "    " * depth

        for i in range(len(param['order'])):
            fpt.write(prefix + "%% " + param['order'][i] + "\n")
            section_list = param[param['order'][i]]
            if len(section_list) > 0:
                section = section_list.pop(0)
                self.write_acemolecule_section(fpt, section, 1)
            fpt.write("%% End\n")
        return


def update_parameter(oldpar, newpar):
    '''Update each section of parameter (oldpar) using newpar keys and values.
    If section of newpar exist in oldpar,
        - Replace the section_name with newpar's section_name if oldvar section_name type is not dict.
        - Append the section_name with newpar's section_name if oldvar section_name type is list.
        - If oldpar section_name type is dict, it is subsection. So call update_parameter again.
    otherwise, add the parameter section and section_name from newpar.

    Parameters
    ==========
    oldpar: dictionary of original parameters to be updated.
    newpar: dictionary containing parameter section and values to update.

    Return
    ======
    Updated parameter dictionary.
    '''
    for section, section_param in newpar.items():
        if section in oldpar:
            if isinstance(section_param, dict):
                oldpar[section] = update_parameter(oldpar[section], section_param)
            else:
                oldpar[section] = section_param
        else:
            oldpar[section] = section_param
    return oldpar