aims_project 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+
5
+ require "wx"
6
+ require "erb"
7
+ require "gl"
8
+ require "glu"
9
+
10
+ require 'aims'
11
+ require 'aims_project'
12
+ require 'aims_project/material.rb'
13
+ require 'aims_project/atom.rb'
14
+ require 'aims_project/inspector.rb'
15
+ require 'aims_project/crystal_viewer.rb'
16
+ require 'aims_project/geometry_editor.rb'
17
+ require 'aims_project/project_tree.rb'
18
+ require 'aims_project/calculation_tree.rb'
19
+ require 'aims_project/app_controller.rb'
20
+ require 'aims_project/geometry_window.rb'
21
+ require 'aims_project/calculation_window.rb'
22
+ require 'aims_project/thread_callback_event.rb'
23
+ require 'aims_project/crystal_viewer_options.rb'
24
+
25
+ controller = AimsProject::AppController.new
26
+
27
+ unless __FILE__.nil?
28
+ cwd = File.dirname(File.expand_path("."))
29
+ controller.working_dir = cwd
30
+ end
31
+
32
+ controller.open_file(ARGV[0]) if ARGV[0]
33
+
34
+ project_obj_files = Dir["*.yaml"]
35
+ controller.project = AimsProject::Project.load(project_obj_files.first) unless project_obj_files.empty?
36
+ AimsProject::ThreadCallbackEvent.set_event_type(Wx::Event.new_event_type)
37
+ Wx::EvtHandler.register_class(AimsProject::ThreadCallbackEvent, AimsProject::ThreadCallbackEvent.event_type, "evt_thread_callback", 0)
38
+
39
+ controller.main_loop
@@ -0,0 +1,158 @@
1
+
2
+ # AimsProject is an application for visualizing and managing projects for
3
+ # the FHI-AIMS package
4
+
5
+ require "rubygems"
6
+ require 'aims'
7
+ require 'yaml'
8
+
9
+ require 'aims_project/project.rb'
10
+ require 'aims_project/calculation.rb'
11
+ require 'aims_project/geometry_file.rb'
12
+ require 'aims_project/aims_project_exception.rb'
13
+
14
+ # AimsProject is a set of tools for managing and organizing
15
+ # calculations for executaion on high-performance computing clusters.
16
+ # It is designed specifically for the FHI-AIMS package published
17
+ # by the Fritz-Haber Institute (https://aimsclub.fhi-berlin.mpg.de),
18
+ # but can probably be generatlized to other codes.
19
+ #
20
+ # Author: Joshua Shapiro (email:joshua.shapiro@gmail.com)
21
+ # Copyright: 2012 Joshua Shapiro
22
+ # License: TBD
23
+ #
24
+ # = Why should I use AimsProject?
25
+ # Good organization is crucial to obtaining meaningful results with
26
+ # the FHI-AIMS package. Careful testing of convergence
27
+ # across parameters in the control and geometry files easily requires
28
+ # dozens of calculations. The novice and expert user alike can quickly
29
+ # lose track of which calculations are complete, which are still pending,
30
+ # which calculations were errors, and which calculations failed or were aborted.
31
+ # In this framework, even the most experienced user can and will make mistakes.
32
+ #
33
+ # <em>The aim of this tool is to simplify and streamline the calculation
34
+ # pipeline, so the user can focus on the results.</em>
35
+ #
36
+ # = Features
37
+ # * Automated generation of calculations from control & geometry files.
38
+ # * Automated synchronization between a workstation and the compute cluster.
39
+ # * Automated job submission of pending calculations to the queue
40
+ # * Status tracking of calculations from creation to completion.
41
+ # * Simple organizational structure with human readable metadata.
42
+ #
43
+ # = Planned Features
44
+ # * Input file validation (To catch mistakes before submitting to a queue)
45
+ # * Geometry input and output visualization
46
+ #
47
+ # = Quick Start
48
+ # == Installation
49
+ #
50
+ # gem install aims_project
51
+ #
52
+ # == Creating a Project
53
+ #
54
+ # Type:
55
+ # AimsProject myProject
56
+ #
57
+ # This will create the directory structure
58
+ # myProject
59
+ # -> calculations/
60
+ # -> config/
61
+ # -> control/
62
+ # -> geometry/
63
+ # -> Capfile
64
+ # -> myProject.yaml
65
+ #
66
+ #
67
+ # +calculations+:: Contains one subdirectory for each calculation.
68
+ # +config+:: Contains special configuration files for automation.
69
+ # +geometry+:: This is where you will place all your geometry files.
70
+ # +control+:: This is where you place all your control files.
71
+ # +Capfile+:: Location where you customize the interaction with the compute cluster
72
+ # +myProject.yaml+:: Human readable metadata related to this project.
73
+ #
74
+ # == Creating a calculation
75
+ #
76
+ # Assume you are investigating two atomic configurations _alpha_ and _beta_, and
77
+ # you want to calculate them with the _light_ and _tight_ settings.
78
+ #
79
+ # Create the FHI-AIMS formatted input files and name them
80
+ # * +geometry/alpha+
81
+ # * +geometry/beta+
82
+ # * +control/light+
83
+ # * +control/tight+
84
+ #
85
+ # Now run
86
+ # > AimsCalc create alpha light
87
+ # > AimsCalc create beta light
88
+ # > AimsCalc create alpha tight
89
+ # > AimsCalc create beta tight
90
+ #
91
+ # This will create four subdirectories inside +calculations/+.
92
+ # > ls calculations/*
93
+ # calculations/alpha.light:
94
+ # calc_status.yaml control.in geometry.in
95
+ #
96
+ # calculations/alpha.tight:
97
+ # calc_status.yaml control.in geometry.in
98
+ #
99
+ # calculations/beta.light:
100
+ # calc_status.yaml control.in geometry.in
101
+ #
102
+ # calculations/beta.tight:
103
+ # calc_status.yaml control.in geometry.in
104
+ #
105
+ # Notice that each calculation directory has the required +control.in+ and
106
+ # +geometry.in+ file. These were directly copied from the geometry and control files
107
+ # passed to AimsCalc. *Note* It is possible to embed variables inside the control
108
+ # and geometry files, see AimsCalc for more details. Each directory also contains
109
+ # a file named +calc_status.yaml+. This is a metadata file used for tracking
110
+ # the history and status of the calculation. Currently this file looks something like
111
+ # --- !ruby/object:AimsProject::Calculation
112
+ # control: light
113
+ # geometry: alpha
114
+ # status: STAGED
115
+ #
116
+ #
117
+ # Feel free to get more creative with the geometry and control file names. You should encode
118
+ # as much information in the file name as possible, as this will make it easy
119
+ # for you to identify the calculation later. For example, a geometry file
120
+ # named +alpha2x2_5layers_relaxed+ and a control file named +light_tier1_norelax_6x6x1_lomem+
121
+ # will result in a calulation directory named +alpha2x2_5layers_relaxed.light_tier1_norelax_6x6x1_lomem+
122
+ #
123
+ # == Running a Calculation
124
+ # At this point the calculation status is +STAGED+. To run the calculation, first customize
125
+ # the +Capfile+ with details necessary for running FHI-AIMS on the computing cluster.
126
+ # Check this file carefully, this is where you define the name and location of the aims executable,
127
+ # the name of the server, and the method for submitting jobs to the queue. Once this file
128
+ # is properly configured, the calculations are submitted with one line:
129
+ #
130
+ # cap aims:enqueue
131
+ #
132
+ # This command invokes custom tasks in +Capistrano+, a 3rd party tool for automated deployment,
133
+ # that will upload the calculations to the server and submit them to the queue.
134
+ #
135
+
136
+
137
+
138
+ module AimsProject
139
+ # Constants
140
+ STAGED = "STAGED"
141
+ HOLD = "HOLD"
142
+ QUEUED = "QUEUED"
143
+ RUNNING = "RUNNING"
144
+ COMPLETE = "COMPLETE"
145
+ ABORTED = "ABORTED"
146
+ CANCELED = "CANCELED"
147
+
148
+ CONFIG_DIR = "config"
149
+ CALCULATION_DIR = "calculations"
150
+ GEOMETRY_DIR = "geometry"
151
+ CONTROL_DIR = "control"
152
+ CALC_STATUS_FILENAME = "calc_status.yaml"
153
+ PROJECT_VARIABLES_FILENAME = "project_vars.rb"
154
+
155
+ GLOBAL_CONFIG_DIR = File.join(ENV["HOME"], ".aims_project")
156
+ GLOBAL_VARIABLES_FILENAME = "aims_project_vars.rb"
157
+
158
+ end
@@ -0,0 +1,42 @@
1
+
2
+ module AimsProject
3
+ class AimsProjectException < StandardError
4
+ end
5
+
6
+ class InvalidFilenameException < AimsProjectException
7
+ def initialize(filename)
8
+ if filename.nil?
9
+ super "No filename specified"
10
+ else
11
+ super "The filename #{filename} is invalid."
12
+ end
13
+ end
14
+ end
15
+
16
+ class ObjectFileNotFoundException < AimsProjectException
17
+ def initialize(filename)
18
+ super "The serialized yaml file was not found: #{filename}"
19
+ end
20
+ end
21
+
22
+ class CorruptObjectFileException < AimsProjectException
23
+ def initialize(filename)
24
+ super "The serialized yaml file is corrupt: #{filename}"
25
+ end
26
+ end
27
+
28
+ class GeometryValidationException < AimsProjectException
29
+ attr_reader :violations
30
+ def initialize(violations)
31
+ @violations = violations
32
+ super "The Geometry failed validation."
33
+ end
34
+ end
35
+
36
+ class GeometryEvaluationException < AimsProjectException
37
+ def initialize
38
+ super "Error evaluating geometry"
39
+ end
40
+ end
41
+
42
+ end
@@ -0,0 +1,52 @@
1
+ module AimsProject
2
+
3
+ # A class that encapsulates the Aims::Geometry and other
4
+ # state information important for AimsProject
5
+ class AimsProjectGeometry
6
+
7
+ # Boolean flag indicating whether the atoms should be displaced to all lie
8
+ # within the unit cell
9
+ attr_accessor :correct_geometry
10
+
11
+ # The filename defining this geometry
12
+ attr_reader :filename
13
+
14
+ def initialize(file)
15
+ @filename = file
16
+ @geometry_original = Aims::GeometryParser.parse(@filename)
17
+ end
18
+
19
+ def geometry
20
+ if self.correct_geometry
21
+ geometry_corrected
22
+ else
23
+ geometry_original
24
+ end
25
+ end
26
+
27
+ def geometry_corrected
28
+ if @geometry_corrected.nil?
29
+ @geometry_corrected = @geometry_original.correct
30
+ end
31
+ @geometry_corrected
32
+ end
33
+
34
+ def geometry_original
35
+ @geometry_original
36
+ end
37
+
38
+ def to_s
39
+ @filename
40
+ end
41
+
42
+
43
+ def save
44
+ unless @filename
45
+ raise InvalidFilenameException
46
+ end
47
+ raise "AimsProjectGeometry.save is not yet implemented"
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,298 @@
1
+
2
+ module AimsProject
3
+ class AppController < Wx::App
4
+
5
+ include Wx
6
+
7
+ ID_NEW = 102
8
+ ID_SAVE_IMAGE = 103
9
+ ID_MOVE_CLIP_PLANE = 104
10
+ ID_SAVE_AS = 105
11
+
12
+ ID_INSPECTOR = 201
13
+
14
+ ID_DELETE_ATOM = 301
15
+
16
+ @frame = nil
17
+ @menubar = nil
18
+ @inspector = nil
19
+ @statusbar = nil
20
+
21
+ # The project
22
+ attr_accessor :project
23
+
24
+ # Used to synchronize directory in open/save dialogs
25
+ attr_accessor :working_dir
26
+
27
+ # The root frame
28
+ attr_accessor :frame
29
+
30
+ # Build the application
31
+ def on_init
32
+
33
+ self.app_name = "AimsViewer"
34
+ # Create the frame, toolbar and menubar and define event handlers
35
+ size = [1000,700]
36
+ @frame = Frame.new(nil, -1, "AimsViewer", DEFAULT_POSITION, size)
37
+ @statusbar = @frame.create_status_bar
38
+
39
+ # This timer will cause the main thread to pass every 2 ms so that other threads
40
+ # can get work done.
41
+ timer = Wx::Timer.new(self, Wx::ID_ANY)
42
+ evt_timer(timer.id) {Thread.pass}
43
+ timer.start(2)
44
+
45
+ # Initialize the selection
46
+ @selection = {}
47
+
48
+ # Initialize the inspector
49
+ @inspector = Inspector.new(self, @frame)
50
+
51
+ # Create the notebook
52
+ @notebook = Notebook.new(@frame)
53
+
54
+ # Create the geometry notebook page
55
+ @geomWindow = GeometryWindow.new(self, @notebook)
56
+
57
+ # Create the control window
58
+ # The base window is a horizontal splitter
59
+ # Left side is a list of control files
60
+ # right side is a Rich Text Control
61
+ controlWindow = SplitterWindow.new(@notebook)
62
+ @controlList = ListCtrl.new(controlWindow);
63
+ @controlEditor = RichTextCtrl.new(controlWindow)
64
+ controlWindow.split_horizontally(@controlList, @controlEditor)
65
+
66
+ # Create the calculations window
67
+ # Similar to the geometryWindow
68
+ # Left side is a list control
69
+ # Right side is a crystal viewer
70
+ calcWindow = CalculationWindow.new(self, @notebook)
71
+
72
+ # Add windows to the notebook
73
+ @notebook.add_page(@geomWindow, 'Geometry')
74
+ @notebook.add_page(controlWindow, "Control")
75
+ @notebook.add_page(calcWindow, "Calculations")
76
+
77
+ evt_notebook_page_changed(@notebook) {|evt|
78
+ cp = @notebook.get_current_page
79
+ if cp.respond_to? :show_inspector
80
+ @notebook.get_current_page.show_inspector
81
+ end
82
+ }
83
+
84
+ # Set the selected notebook page
85
+ @notebook.set_selection(2)
86
+
87
+ # @tree = ProjectTree.new(self, hsplitter)
88
+ @frame.set_menu_bar(menubar)
89
+ # @toolbar = @frame.create_tool_bar
90
+ # populate_toolbar(@toolbar) if @toolbar
91
+
92
+
93
+ # Check off the current tool
94
+ # set_tool
95
+
96
+ # Display
97
+ @frame.show
98
+ end
99
+
100
+ # Process a menu event
101
+ def process_menu_event(event)
102
+ case event.id
103
+ when ID_INSPECTOR
104
+ show_inspector
105
+ when ID_DELETE_ATOM
106
+ delete_atom
107
+ when Wx::ID_OPEN
108
+ open_file
109
+ when Wx::ID_SAVE
110
+ save_geometry
111
+ when ID_NEW
112
+ new_geometry_file
113
+ when ID_SAVE_AS
114
+ save_geometry_as
115
+ when ID_SAVE_IMAGE
116
+ save_image
117
+ when Wx::ID_EXIT
118
+ exit(0)
119
+ else
120
+ event.skip
121
+ end
122
+
123
+ end
124
+
125
+
126
+ # Return the menubar. If it is undefined, then define it and attach the event handler
127
+ def menubar
128
+ unless @menubar
129
+ fileMenu = Menu.new
130
+ fileMenu.append(ID_NEW, "New Geometry ...")
131
+ fileMenu.append(Wx::ID_OPEN, "Open ...\tCTRL+o")
132
+ fileMenu.append(Wx::ID_SAVE, "Save Geometry\tCTRL+s")
133
+ fileMenu.append(ID_SAVE_AS, "Save Geometry As...")
134
+ fileMenu.append(ID_SAVE_IMAGE, "Export Image ...")
135
+ fileMenu.append(Wx::ID_EXIT, "Exit")
136
+
137
+ editMenu = Menu.new
138
+ editMenu.append(ID_DELETE_ATOM, "Delete Atom\tCTRL+d")
139
+
140
+ toolsMenu = Menu.new
141
+ # toolsMenu.append(ID_ROTATE, "rotate", "Rotate", Wx::ITEM_CHECK)
142
+ # toolsMenu.append(ID_ZOOM, "zoom", "Zoom", Wx::ITEM_CHECK)
143
+ # toolsMenu.append(ID_PAN, "pan", "Pan", Wx::ITEM_CHECK)
144
+ # toolsMenu.append(ID_MOVE_CLIP_PLANE, "move cilp plane", "Move", Wx::ITEM_CHECK)
145
+
146
+ viewMenu = Menu.new
147
+ viewMenu.append(ID_INSPECTOR, "inspector\tCTRL+i")
148
+
149
+
150
+ @menubar = MenuBar.new
151
+ @menubar.append(fileMenu, "File")
152
+ @menubar.append(editMenu, "Edit")
153
+ @menubar.append(viewMenu, "Views")
154
+ @menubar.append(toolsMenu, "Tools")
155
+
156
+ evt_menu @menubar, :process_menu_event
157
+ end
158
+ @menubar
159
+ end
160
+
161
+ def set_status(string)
162
+ @frame.set_status_text(string)
163
+ end
164
+
165
+ # Get the inspector
166
+ def inspector
167
+ if @inspector.nil?
168
+ @inspector = Inspector.new(self, @frame)
169
+ end
170
+ @inspector
171
+ end
172
+
173
+ # Show the inspector
174
+ def show_inspector
175
+ @inspector.show(true)
176
+ end
177
+
178
+ # Hide the inspector
179
+ def hide_inspector
180
+ @inspector.hide
181
+ end
182
+
183
+ # Create a new geometry file
184
+ def new_geometry_file(file = nil)
185
+ fd = TextEntryDialog.new(@frame, :message => "New Geometry", :caption => "Specify name of geometry:")
186
+ if Wx::ID_OK == fd.show_modal
187
+ begin
188
+ geom_name = fd.get_value
189
+ geometry = GeometryFile.new("")
190
+ geometry = geometry.save_as(File.new(File.join(GEOMETRY_DIR, geom_name), "w"))
191
+ @geomWindow.add_geometry(geometry)
192
+ rescue Exception => e
193
+ error_dialog(e)
194
+ end
195
+ end
196
+ end
197
+
198
+ # Display a file dialog and attempt to open and display the file
199
+ def open_geometry_file(file = nil)
200
+ begin
201
+ unless file
202
+ fd = FileDialog.new(@frame, :message => "Open", :style => FD_OPEN, :default_dir => @working_dir)
203
+ if ID_OK == fd.show_modal
204
+ file = fd.get_path
205
+ @working_dir = fd.get_directory
206
+ else
207
+ return
208
+ end
209
+ end
210
+ puts "Opening #{file}"
211
+
212
+ @frame.set_title(file)
213
+ @geomEditor.show
214
+ @calcTree.hide
215
+
216
+ if (project)
217
+ erb = ERB.new(File.read(file))
218
+ show_geometry Aims::GeometryParser.parse_string(erb.result(project.get_binding))
219
+ else
220
+ show_geometry Aims::GeometryParser.parse(file)
221
+ end
222
+
223
+
224
+ rescue Exception => dang
225
+ error_dialog(dang)
226
+ end
227
+ end
228
+ alias_method :open_file, :open_geometry_file
229
+
230
+ def save_geometry
231
+ page = @notebook.get_current_page
232
+ if (page.respond_to? :geometry) && not(page.geometry.nil?)
233
+ geometry = @notebook.get_current_page.geometry
234
+ begin
235
+ geometry.save
236
+ rescue Exception => e
237
+ error_dialog(e)
238
+ end
239
+ end
240
+ end
241
+
242
+ # Save the geometry
243
+ def save_geometry_as
244
+
245
+ page = @notebook.get_current_page
246
+ if (page.respond_to? :geometry) && not(page.geometry.nil?)
247
+ geometry = @notebook.get_current_page.geometry
248
+
249
+ fd = TextEntryDialog.new(@frame, :message => "Save Geometry", :caption => "Specify name of geometry:")
250
+ if Wx::ID_OK == fd.show_modal
251
+ begin
252
+ geom_name = fd.get_value
253
+ new_geom = geometry.save_as(File.new(File.join(GEOMETRY_DIR, geom_name), "w"))
254
+ @geomWindow.add_geometry(new_geom)
255
+ rescue Exception => e
256
+ error_dialog(e)
257
+ end
258
+ end
259
+ else
260
+ error_dialog("No geometry selected.")
261
+ end
262
+ end
263
+
264
+ # Display an error dialog for the exception
265
+ def error_dialog(exception)
266
+ message = if exception.is_a? String
267
+ exception
268
+ elsif exception.is_a? Exception
269
+ exception.message + "\n\n" + exception.backtrace[0..2].join("\n")
270
+ end
271
+ puts message
272
+ MessageDialog.new(@frame, message, "Error", Wx::ICON_ERROR).show_modal
273
+ end
274
+
275
+ def save_image
276
+
277
+ begin
278
+ page = @notebook.get_current_page
279
+ if (page.respond_to? :image)
280
+ image = page.image
281
+ fd = FileDialog.new(@frame, :message => "Save Image", :style => FD_SAVE, :default_dir => @working_dir)
282
+ if Wx::ID_OK == fd.show_modal
283
+ @working_dir = fd.get_directory
284
+ puts "Writing #{fd.get_path}"
285
+ image.mirror(false).save_file(fd.get_path)
286
+ end
287
+ else
288
+ error_dialog("Sorry, could not generate an image.")
289
+ end
290
+
291
+ rescue Exception => e
292
+ error_dialog(e)
293
+ end
294
+ end
295
+
296
+ end
297
+ end
298
+