Statistiques
| Révision :

root / ase / gui / scaling.py @ 19

Historique | Voir | Annoter | Télécharger (14,54 ko)

1
# encoding: utf-8
2

    
3
"Module for homogeneous deformation and calculations of elastic constants."
4

    
5
import gtk
6
from ase.gui.simulation import Simulation
7
from ase.gui.minimize import MinimizeMixin
8
from ase.gui.energyforces import OutputFieldMixin
9
from ase.gui.widgets import oops, pack, AseGuiCancelException
10
import ase
11
import numpy as np
12

    
13
scaling_txt = """\
14
This module is intended for calculating elastic constants by homogeneously
15
deforming a system."""
16

    
17
class HomogeneousDeformation(Simulation, MinimizeMixin, OutputFieldMixin):
18
    "Window for homogeneous deformation and elastic constants."
19
    
20
    def __init__(self, gui):
21
        Simulation.__init__(self, gui)
22
        self.set_title("Homogeneous scaling")
23
        vbox = gtk.VBox()
24
        self.packtext(vbox, scaling_txt)
25
        self.packimageselection(vbox, txt1="", txt2="")
26
        self.start_radio_nth.set_active(True)
27
        pack(vbox, gtk.Label(""))
28

    
29
        # Radio buttons for choosing deformation mode.
30
        tbl = gtk.Table(4,3)
31
        for i, l in enumerate(('3D', '2D', '1D')):
32
            l = l + " deformation   "
33
            lbl = gtk.Label(l)
34
            tbl.attach(lbl, i, i+1, 0, 1)
35
        self.radio_bulk = gtk.RadioButton(None, "Bulk")
36
        tbl.attach(self.radio_bulk, 0, 1, 1, 2)
37
        self.radio_xy = gtk.RadioButton(self.radio_bulk, "xy-plane")
38
        tbl.attach(self.radio_xy, 1, 2, 1, 2)
39
        self.radio_xz = gtk.RadioButton(self.radio_bulk, "xz-plane")
40
        tbl.attach(self.radio_xz, 1, 2, 2, 3)
41
        self.radio_yz = gtk.RadioButton(self.radio_bulk, "yz-plane")
42
        tbl.attach(self.radio_yz, 1, 2, 3, 4)
43
        self.radio_x = gtk.RadioButton(self.radio_bulk, "x-axis")
44
        tbl.attach(self.radio_x, 2, 3, 1, 2)
45
        self.radio_y = gtk.RadioButton(self.radio_bulk, "y-axis")
46
        tbl.attach(self.radio_y, 2, 3, 2, 3)
47
        self.radio_z = gtk.RadioButton(self.radio_bulk, "z-axis")
48
        tbl.attach(self.radio_z, 2, 3, 3, 4)
49
        tbl.show_all()
50
        pack(vbox, [tbl])
51
        self.deformtable = [
52
            (self.radio_bulk, (1,1,1)),
53
            (self.radio_xy, (1,1,0)),
54
            (self.radio_xz, (1,0,1)),
55
            (self.radio_yz, (0,1,1)),
56
            (self.radio_x, (1,0,0)),
57
            (self.radio_y, (0,1,0)),
58
            (self.radio_z, (0,0,1))]
59
        self.deform_label = gtk.Label("")
60
        pack(vbox, [self.deform_label])
61
        self.choose_possible_deformations(first=True)
62

    
63
        # Parameters for the deformation
64
        framedef = gtk.Frame("Deformation:")
65
        vbox2 = gtk.VBox()
66
        vbox2.show()
67
        framedef.add(vbox2)
68
        self.max_scale = gtk.Adjustment(0.010, 0.001, 10.0, 0.001)
69
        max_scale_spin = gtk.SpinButton(self.max_scale, 10.0, 3)
70
        pack(vbox2, [gtk.Label("Maximal scale factor: "), max_scale_spin])
71
        self.scale_offset = gtk.Adjustment(0.0, -10.0, 10.0, 0.001)
72
        scale_offset_spin = gtk.SpinButton(self.scale_offset, 10.0, 3)
73
        pack(vbox2, [gtk.Label("Scale offset: "), scale_offset_spin])
74
        self.nsteps = gtk.Adjustment(5, 3, 100, 1)
75
        nsteps_spin = gtk.SpinButton(self.nsteps, 1, 0)
76
        pack(vbox2, [gtk.Label("Number of steps: "), nsteps_spin])
77
        
78
        # Atomic relaxations
79
        framerel = gtk.Frame("Atomic relaxations:")
80
        vbox2 = gtk.VBox()
81
        vbox2.show()
82
        framerel.add(vbox2)
83
        self.radio_relax_on = gtk.RadioButton(None, "On   ")
84
        self.radio_relax_off = gtk.RadioButton(self.radio_relax_on, "Off")
85
        self.radio_relax_off.set_active(True)
86
        pack(vbox2, [self.radio_relax_on, self.radio_relax_off])
87
        self.make_minimize_gui(vbox2)
88
        for r in (self.radio_relax_on, self.radio_relax_off):
89
            r.connect("toggled", self.relax_toggled)
90
        self.relax_toggled()
91
        pack(vbox, [framedef, gtk.Label(" "), framerel])
92
        pack(vbox, gtk.Label(""))
93
        
94
        # Results
95
        pack(vbox, [gtk.Label("Results:")])
96
        self.radio_results_optimal = gtk.RadioButton(
97
            None, "Load optimal configuration")
98
        self.radio_results_all =  gtk.RadioButton(
99
            self.radio_results_optimal, "Load all configurations")
100
        self.radio_results_optimal.set_active(True)
101
        pack(vbox, [self.radio_results_optimal])
102
        pack(vbox, [self.radio_results_all])
103

    
104
        # Output field
105
        outframe = self.makeoutputfield(None)
106
        fitframe = gtk.Frame("Fit:")
107
        vbox2 = gtk.VBox()
108
        vbox2.show()
109
        fitframe.add(vbox2)
110
        self.radio_fit_2 = gtk.RadioButton(None, "2nd")
111
        self.radio_fit_3 = gtk.RadioButton(self.radio_fit_2, "3rd")
112
        self.radio_fit_2.connect("toggled", self.change_fit)
113
        self.radio_fit_3.connect("toggled", self.change_fit)
114
        self.radio_fit_3.set_active(True)
115
        pack(vbox2, [gtk.Label("Order of fit: "), self.radio_fit_2,
116
                     self.radio_fit_3])
117
        pack(vbox2, [gtk.Label("")])
118
        scrwin = gtk.ScrolledWindow()
119
        scrwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
120
        self.fit_output = gtk.TextBuffer()
121
        txtview = gtk.TextView(self.fit_output)
122
        txtview.set_editable(False)
123
        scrwin.add(txtview)
124
        scrwin.show_all()
125
        self.fit_win = scrwin
126
        print "SIZE RQ:", scrwin.size_request()
127
        vbox2.pack_start(scrwin, True, True, 0)
128
        hbox = gtk.HBox(homogeneous=True)
129
        for w in [outframe, fitframe]:
130
            hbox.pack_start(w)
131
            w.show()
132
        pack(vbox, hbox)    
133
        pack(vbox, gtk.Label(""))
134

    
135
        # Status field
136
        self.status_label = gtk.Label("")
137
        pack(vbox, [self.status_label])
138
        
139
        # Run buttons etc.
140
        self.makebutbox(vbox)
141
        vbox.show()
142
        self.add(vbox)
143
        self.show()
144
        self.gui.register_vulnerable(self)
145

    
146
    def choose_possible_deformations(self, first=False):
147
        """Turn on sensible radio buttons.
148

149
        Only radio buttons corresponding to deformations in directions
150
        with periodic boundary conditions should be turned on.
151
        """
152
        if self.setup_atoms():
153
            pbc = self.atoms.get_pbc()
154
        else:
155
            pbc = [False, False, False]
156
        for radio, requirement in self.deformtable:
157
            ok = True
158
            for i in range(3):
159
                if requirement[i] and not pbc[i]:
160
                    ok = False
161
            radio.set_sensitive(ok)
162
            if first and ok:
163
                # The first acceptable choice, choose it to prevent
164
                # inconsistent state.
165
                radio.set_active(True)
166
                first = False
167

    
168
    def relax_toggled(self, *args):
169
        "Turn minimization widgets on or off."
170
        state = self.radio_relax_on.get_active()
171
        for widget in (self.algo, self.fmax_spin, self.steps_spin):
172
            widget.set_sensitive(state)
173
        
174
    def notify_atoms_changed(self):
175
        "When atoms have changed, check for the number of images."
176
        self.setupimageselection()
177
        self.choose_possible_deformations()
178

    
179
    def get_deformation_axes(self):
180
        """Return which axes the user wants to deform along."""
181
        for but, deform in self.deformtable:
182
            if but.get_active():
183
                return np.array(deform)
184
        # No deformation chosen!
185
        oops("No deformation chosen: Please choose a deformation mode.")
186
        return False
187
            
188
    def run(self, *args):
189
        """Make the deformation."""
190
        self.output.set_text("")
191
        if not self.setup_atoms():
192
            return
193
        deform_axes = self.get_deformation_axes()
194
        if deform_axes is False:
195
            return  #Nothing to do!
196

    
197
        # Prepare progress bar
198
        if self.radio_relax_on.get_active():
199
            fmax = self.fmax.value
200
            mininame = self.minimizers[self.algo.get_active()]
201
            self.begin(mode="scale/min", algo=mininame, fmax=fmax,
202
                       steps=self.steps.value, scalesteps=self.nsteps.value)
203
        else:
204
            self.begin(mode="scale", scalesteps=self.nsteps.value)
205
        try:
206
            logger_func = self.gui.simulation['progress'].get_logger_stream
207
        except (KeyError, AttributeError):
208
            logger = None
209
        else:
210
            logger = logger_func()  # Don't catch errors in the function.
211

    
212
        # Display status message
213
        self.status_label.set_text("Running ...")
214
        self.status_label.modify_fg(gtk.STATE_NORMAL,
215
                                    gtk.gdk.color_parse('#AA0000'))
216
        while gtk.events_pending():
217
            gtk.main_iteration()
218

    
219
        # Do the scaling
220
        scale = self.max_scale.value
221
        steps = np.linspace(-scale, scale, self.nsteps.value)
222
        steps += self.scale_offset.value
223
        undef_cell = self.atoms.get_cell()
224
        results = []
225
        txt = "Strain\t\tEnergy [eV]\n"
226
        # If we load all configurations, prepare it.
227
        if self.radio_results_all.get_active():
228
            self.prepare_store_atoms()
229

    
230
        try:
231
            # Now, do the deformation
232
            for i, d in enumerate(steps):
233
                deformation = np.diag(1.0 + d * deform_axes)
234
                self.atoms.set_cell(np.dot(undef_cell, deformation),
235
                                    scale_atoms=True)
236
                if self.gui.simulation.has_key('progress'):
237
                    self.gui.simulation['progress'].set_scale_progress(i)
238
                if self.radio_relax_on.get_active():
239
                    algo = getattr(ase.optimize, mininame)
240
                    minimizer = algo(self.atoms, logfile=logger)
241
                    minimizer.run(fmax=fmax, steps=self.steps.value)
242
                e = self.atoms.get_potential_energy()
243
                results.append((d, e))
244
                txt = txt + ("%.3f\t\t%.3f\n" % (d, e))
245
                self.output.set_text(txt)
246
                if self.radio_results_all.get_active():
247
                    self.store_atoms()
248
        except AseGuiCancelException:
249
            # Update display to reflect cancellation of simulation.
250
            self.status_label.set_text("Calculation CANCELLED.")
251
            self.status_label.modify_fg(gtk.STATE_NORMAL,
252
                                        gtk.gdk.color_parse('#AA4000'))
253
        except MemoryError:
254
            self.status_label.set_text("Out of memory, consider using LBFGS instead")
255
            self.status_label.modify_fg(gtk.STATE_NORMAL,
256
                                        gtk.gdk.color_parse('#AA4000'))
257
            
258
        else:
259
            # Update display to reflect succesful end of simulation.
260
            self.status_label.set_text("Calculation completed.")
261
            self.status_label.modify_fg(gtk.STATE_NORMAL,
262
                                        gtk.gdk.color_parse('#007700'))
263
                     
264
        if results:
265
            self.do_fit(np.array(results))
266
            if self.radio_results_optimal.get_active():
267
                if self.minimum_ok:
268
                    deformation = np.diag(1.0 + self.x0 * deform_axes)
269
                    self.atoms.set_cell(np.dot(undef_cell, deformation),
270
                                        scale_atoms=True)
271
                    if self.radio_relax_on.get_active():
272
                        if self.gui.simulation.has_key('progress'):
273
                            self.gui.simulation['progress'].set_scale_progress(
274
                                len(steps))
275
                        algo = getattr(ase.optimize, mininame)
276
                        minimizer = algo(self.atoms, logfile=logger)
277
                        minimizer.run(fmax=fmax, steps=self.steps.value)
278
                    # Store the optimal configuration.
279
                    self.prepare_store_atoms()
280
                    self.store_atoms()  
281
                else:
282
                    oops("No trustworthy minimum: Old configuration kept.")
283
            self.activate_output()
284
            self.gui.notify_vulnerable()
285
        self.end()    
286
            
287
        # If we store all configurations: Open movie window and energy graph
288
        if self.gui.images.nimages > 1:
289
            self.gui.movie()
290
            assert not np.isnan(self.gui.images.E[0])
291
            if not self.gui.plot_graphs_newatoms():
292
                expr = 'i, e - E[-1]'            
293
                self.gui.plot_graphs(expr=expr)
294
            # Continuations should use the best image
295
            nbest = np.argmin(np.array(results)[:,1])
296
            self.start_nth_adj.value = nbest
297
            
298

    
299
    def change_fit(self, widget):
300
        "Repeat the fitting if the order is changed."
301
        # It may be called both for the button being turned on and the
302
        # one being turned off.  But we only want to call do_fit once.
303
        # And only if there are already cached results (ie. if the
304
        # order is changed AFTER the calculation is done).
305
        if widget.get_active() and getattr(self, "results", None) is not None:
306
            self.do_fit(None)
307
                
308
    def do_fit(self, results):
309
        "Fit the results to a polynomial"
310
        if results is None:
311
            results = self.results  # Use cached results
312
        else:
313
            self.results = results  # Keep for next time
314
        self.minimum_ok = False
315
        if self.radio_fit_3.get_active():
316
            order = 3
317
        else:
318
            order = 2
319
            
320
        if len(results) < 3:
321
            txt = ("Insufficent data for a fit\n(only %i data points)\n"
322
                   % (len(results),) )
323
            order = 0
324
        elif len(results) == 3 and order == 3:
325
            txt = "REVERTING TO 2ND ORDER FIT\n(only 3 data points)\n\n"
326
            order = 2
327
        else:
328
            txt = ""
329

    
330
        if order > 0:
331
            fit0 = np.poly1d(np.polyfit(results[:,0], results[:,1], order))
332
            fit1 = np.polyder(fit0, 1)
333
            fit2 = np.polyder(fit1, 1)
334

    
335
            x0 = None
336
            for t in np.roots(fit1):
337
                if fit2(t) > 0:
338
                    x0 = t
339
                    break
340
            if x0 is None:
341
                txt = txt + "No minimum found!"
342
            else:
343
                e0 = fit0(x0)
344
                e2 = fit2(x0)
345
                txt += "E = "
346
                if order == 3:
347
                    txt += "A(x - x0)³ + "
348
                txt += "B(x - x0)² + C\n\n"
349
                txt += "B = %.5g eV\n" % (e2,)
350
                txt += "C = %.5g eV\n" % (e0,)
351
                txt += "x0 = %.5g\n" % (x0,)
352
                lowest = self.scale_offset.value - self.max_scale.value
353
                highest = self.scale_offset.value + self.max_scale.value
354
                if x0 < lowest or x0 > highest:
355
                    txt += "\nWARNING: Minimum is outside interval\n"
356
                    txt += "It is UNRELIABLE!\n"
357
                else:
358
                    self.minimum_ok = True
359
                    self.x0 = x0
360
        self.fit_output.set_text(txt)
361