-
Notifications
You must be signed in to change notification settings - Fork 1
/
GUI_GO.py
11650 lines (9655 loc) · 501 KB
/
GUI_GO.py
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
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
Library: GUI_GO
Version: 1.01
Author: Lucas Korol
Institution: University of Saskatchewan
Last Updated: October 10, 2023
Python: version 3.7
Purpose: This python file contains the graphical user interface for the GO-RXR software package.
---------------------------------------------------------------------------------------------------------------------------------------
Imported Libraries
material_model (version 0.1) - Part of the 'name' software package and is used to retrieve the form factors and
calculate the optical constants
material_structure (version 0.1) - Part of the 'name' software package and is used to retrieve to calculate the reflectivity spectra
global_optimization (version 0.1) - Part of the 'name' software package and is used to perform the global optimization
data_structure (version 0.2) - Part of the 'name' software package that is used to saving and loading the workspace
numpy (version 1.21.4) - used for array manipulation
scipy (version 1.7.1) - used for data smoothing
PyQt5 (version 5.15.7) - This library is used to contruct the application
pyqtgraph (version 0.12.4) - This library is used for the plotting widget
pyinstaller (version 5.9.0) - This library is used to create a standalone executable file
--------------------------------------------------------------------------------------------------------------------------------------
Note: There are a few immediate changes that could be implemented for future versions which are listed below:
1. Magnetization direction. Currently, we can only calculate the reflectivity spectra for a magnetization direction
in the x, y, and z-directions. In the case where we can have an arbitarty direction that are defined by phi and theta
this should be altered. Instead of using a QComboBox in magneticWidget for the magnetization direction I would create
two QLineEdits for phi and theta respectively. I would make sure to include checks that the angles are physically
appropriate. Note that if this is done changes would need to be made in the magneticWidget, sampleWidget, and the
data_structure python file (save the magnetization direction as phi and theta instead of a string x,y,z). A good plan
to make sure you have changed everything accordingly would be to search magDirection in the python file.
2. Layer magnetization. Currently, the user has the ability to give different magnetization directions per layer.
The current reflectivity calculation implementation is unable to handle multiple magnetization directions, so
it would make sense to remove this capability. If I have time I will get to this.
3. Inclusion of other global optimization algorithms. I would like to include the direct algorithm into the the list of
algorithms to use. The issue is that we required python 3.8 and above to use it, but some of the underlining
code that is used for the reflectivity calculations does not allow for the use of python 3.8 and above. I've already
included a lot of the code to include the direct algorithm, or any other algorithm. I would suggest searching 'direct'
in all of the python files (or another algorithm) to see where to make the appropriate changes.
4. Data smoothing. It may be worth including other data smoothing methods. For example, there has been discussion of
using neural networks to perform some of the smoothing. The neural networks have been found to remove the noise
while maintaining the shape, even for very noisy signals.
Warning: Any changes to the data type would need to be carefully considered.
Instructions to create executable using pytinstaller:
1. Install pyinstaller in the python environement.
2. Run 'pyinstaller GUI.GO.py' in the terminal.
3. A new file named GUI_GO.spec will appear in the project file. Open this file.
4. In this file there is a variable called datas = []. Replace the empty array with:
datas = [('.\\global_optimization.py', '.'), ('.\\data_structure.py','.'),('.\\material_structure.py','.'),
('.\\material_model.py','.'), ('.\\Ti34_XAS_Python.py','.'), ('.\\Ti34OpsPython.pkl','.'),('.\\default_script.txt','.'), ('.\\form_factor.pkl','.'),
('.\\form_factor_magnetic.pkl','.'), ('.\\Perovskite_Density.txt','.'), ('.\\Atomic_Mass.txt','.'),
('.\\demo.h5','.'), ('.\\GO-RXR_UserGuide_v0.3.1.pdf','.'), ('.\\license.txt','.'),('.\\tips.txt','.'),('.\\demo.h5','.'), ('.\\logo.png','.')]
5. This includes all the python files, text files, and all other data used to execute GUI_GO.py. If newer versions
of GO-RXR depend on more files than they must be included into this array. Follow the same naming convention as shown.
6. Once satisfied run 'pyinstaller GUI_GO.spec' in the terminal. This will ensure that all desired data is included into
the data file.
7. The executable file will be found in the 'dist' directory in the project workspace. There should now be a directory
in this workspace with the name GUI_GO.
8. You can copy this file an input it into another directory. From here I would suggest creating a zip file that
can be distributed.
9. A one file can be used to compile the executable into a single file instead of a directory. However, this can take much longer
to initialize because the all the libraries and files need to be unpacked before the software can be run.
"""
import os
import ast
from scipy import interpolate
from PyQt5.QtWidgets import *
from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import traceback
import numpy as np
import time
import sys
import UTILS.material_structure as ms
from UTILS.material_model import change_ff, retrieve_ff
import os
import pyqtgraph as pg
import UTILS.data_structure as ds
import copy
import UTILS.global_optimization as go
import UTILS.material_model as mm
from scipy import signal
import h5py
import multiprocessing as mp
import pickle
from scipy.interpolate import interp1d, UnivariateSpline
from scipy.fft import fft, fftfreq, fftshift, ifft, ifftshift
from UTILS.Ti34_XAS_Python import GetTiFormFactor
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)
# This global variable is used to stop the data fitting
global stop
stop = False
# this global variable is used to store the parameter values at each optimization iteration
global x_vars
x_vars = []
def change_internal_ff(element, ff_dict, my_data):
"""
Purpose: Change the atomic form factor data for the global atomic form factor dictionary
:param element: Element symbol
:param ff_dict: atomic form factor dictionary
:param my_data:
:return:
"""
ff_dict[element] = my_data
return ff_dict
def stringcheck(string):
"""
Purpose: Checks to make sure sample properties are in the correct format
:param string: Parameters value
:return: Boolean determining if value is in the correct format
"""
num = 0 # used to check how many '.' are found in the string
correctFormat = True # is value in the correct format?
if len(string) == 0: # string is empty
correctFormat = False
else:
first = True # checks the first character in the string
for char in string:
if first: # makes sure first character is a digit
if char == '.':
correctFormat = False
elif not char.isdigit():
correctFormat = False
first = False
elif not char.isdigit(): # checks if all other characters are digits
if char == '.':
num = num + 1
if num > 1:
correctFormat = False
else:
correctFormat = False
return correctFormat
class compoundInput(QDialog):
"""
Purpose: Creates a widget when user wants to add another layer. This widget allows the user to select the chemical
formula, thickness, density (g/cm^3), roughness, and linked roughness for the layer.
"""
def __init__(self):
super().__init__()
self.val = [] # class value used to store layer properties
pagelayout = QVBoxLayout() # page layout
infolayout = QGridLayout() # compound information layout
# Chemical formula of added layer
formula = QLabel('Formula: ')
self.formula = QLineEdit()
self.formula.editingFinished.connect(self.formulaDone)
# thickness of layer
thickness = QLabel('Thickness (A): ')
self.thickness = QLineEdit()
self.thickness.setText('10')
# Density (g/cm^3)
density = QLabel('Density (g/cm^3): ')
self.density = QLineEdit()
# roughness of layer
roughness = QLabel('Roughness (A): ')
self.roughness = QLineEdit()
self.roughness.setText('2')
# include linked roughness if applicable to layer
linkedroughnesslayout = QHBoxLayout() # layout for linked roughness
linkedroughness = QLabel('Linked Roughness (A): ')
self.linkedroughness = QLineEdit()
self.linkedroughness.setHidden(True)
# checkbox to determine if user wants to add a linked roughness to layer
self.checkbox = QCheckBox()
self.checkbox.stateChanged.connect(self.linkedroughnessbox)
self.checkboxstate = 0
linkedroughnesslayout.addWidget(self.checkbox)
linkedroughnesslayout.addWidget(linkedroughness)
# add labels to the info layout
infolayout.addWidget(formula, 0, 0)
infolayout.addWidget(thickness, 1, 0)
infolayout.addWidget(density, 2, 0)
infolayout.addWidget(roughness, 3, 0)
infolayout.addLayout(linkedroughnesslayout, 4, 0)
# add the text edit widgets to the info layout
infolayout.addWidget(self.formula, 0, 1)
infolayout.addWidget(self.thickness, 1, 1)
infolayout.addWidget(self.density, 2, 1)
infolayout.addWidget(self.roughness, 3, 1)
infolayout.addWidget(self.linkedroughness, 4, 1)
# add layer properties to the current sample model
enterButton = QPushButton('Enter')
enterButton.clicked.connect(self.inputComplete)
self.errorMessage = QLabel('') # let user know if they have made a mistake in their input
# add all layouts to the page layout
pagelayout.addLayout(infolayout)
pagelayout.addWidget(enterButton)
pagelayout.addWidget(self.errorMessage)
self.setLayout(pagelayout)
def formulaDone(self):
"""
Purpose: searches database for material density
:return: density (g/cm^3)
"""
cwd = os.getcwd()
filename = 'DATA/Perovskite_Density.txt'
found = False # boolean user that determines if material found in database
with open(filename) as file:
for line in file:
myformula = line.split()[0] # retrieves the chemical formula
mydensity = line.split()[1] # retrieves the density
if not found:
if self.formula.text() == myformula: # material in database
self.density.setText(mydensity) # set the density in the widget
found = True
else: # material not found in the database
self.density.clear() # reset the density in the widget
def linkedroughnessbox(self):
"""
Purpose: Hide or make visible the linked roughness lineEdit widget
"""
self.checkboxstate = self.checkbox.checkState() # retrieve the checkbox state
if self.checkboxstate > 0:
self.linkedroughness.setHidden(False)
else:
self.linkedroughness.setHidden(True)
def inputComplete(self):
"""
Purpose: Checks to make sure parameters are in correct format before sending back
to main widget to add to current sample
:return: List of the layer parameters
"""
finished = True
# gets the elements and their stoichiometry
myElements = ms.find_stoichiometry(self.formula.text()) # gets the elements and their stoichiometry
# gets the density
myThickness = self.thickness.text()
thicknessCorrect = stringcheck(myThickness) # checks thickness format
myDensity = self.density.text()
densityCorrect = stringcheck(myDensity) # checks density format
# gets the density
myRoughness = self.roughness.text()
roughnessCorrect = stringcheck(myRoughness) # checks roughness format
# gets the linked roughness
myLinkedroughness = self.linkedroughness.text()
# sends error message to user if formate is incorrect
linkedroughnessCorrect = True
if myLinkedroughness != '':
linkedroughnessCorrect = stringcheck(myLinkedroughness)
# checks to make sure that the inputs are in the correct format
if not (thicknessCorrect) or not (densityCorrect) or not (roughnessCorrect) or not (linkedroughnessCorrect):
if not (thicknessCorrect):
self.errorMessage.setText('Please check thickness!')
elif not (densityCorrect):
self.errorMessage.setText('Please check density!')
elif not (roughnessCorrect):
self.errorMessage.setText('Please check roughness!')
elif not (linkedroughnessCorrect):
self.errorMessage.setText('Please check linked roughness!')
else: # transform sample parameters into correct format
molar_mass = 0 # molar mass
elements = list(myElements[0].keys()) # list of elements in layer
# gets the molar mass of the compound
for ele in elements:
stoich = myElements[0][ele].stoichiometry
if ele[-1].isdigit():
ele = ele.rstrip(ele[-1])
molar_mass = molar_mass + ms.atomic_mass(ele) * stoich
tempArray = []
# put layer info in correct format depending for both linked roughness cases
if myLinkedroughness == '': # no linked roughness case
# pre-set form factor to element name
for ele in elements:
stoich = myElements[0][ele].stoichiometry
density = float(myDensity)
if ele[-1].isdigit():
ff_ele = ele.rstrip(ele[-1])
else:
ff_ele = ele
tempArray.append(
[ele, myThickness, str(density * float(stoich) / molar_mass), myRoughness, False, ff_ele,
stoich])
else: # linked roughness case
for ele in elements:
stoich = myElements[0][ele].stoichiometry
density = float(myDensity)
# pre-set form factor to element symbol
if ele[-1].isdigit():
ff_ele = ele.rstrip(ele[-1])
else:
ff_ele = ele
tempArray.append(
[ele, myThickness, str(density * float(stoich) / molar_mass), myRoughness, myLinkedroughness,
ff_ele, stoich])
self.val = np.array(tempArray) # reset layer values
self.accept() # close the widget
class variationWidget(QDialog):
"""
Purpose: This widget provides the user a workspace to handle elemental variations.It allows for the user to include
the different oxidation states of a material. The only properites that the user may change is the number
of element variations, their ratio, and their form factors.
"""
def __init__(self, mainWidget, sample):
"""
Purpose: Initialize the widget
:param mainWidget: This is the sampleWidget. This is used as an input value so the information stored in sample
widget can be used in the variationWidget.
:param sample: This is the most recent material model (slab class)
"""
super().__init__()
pagelayout = QHBoxLayout() # page layout
self.elelayout = QVBoxLayout() # This is the element layout
self.mainWidget = mainWidget # referring to the structuralWidget
self.mainWidget.layerBox.currentIndexChanged.connect(self.changeElements) # change elements when layer changed
# add element variation button
addButton = QPushButton('Add')
addButton.clicked.connect(self.addVarEle)
# remove element variation button
deleteButton = QPushButton('Delete')
deleteButton.clicked.connect(self.deleteVarEle)
self.sample = sample # reset sample information
#self.radiobutton = QRadioButton()
idx = self.mainWidget.layerBox.currentIndex() # retrieves the current layer index
# adding elements in the current layer to the combobox
for j in range(len(list(self.sample.structure[idx].keys()))):
ele = list(self.sample.structure[idx].keys())[j]
self.mainWidget.elementBox.addItem(ele)
self.mainWidget.elementBox.currentIndexChanged.connect(self.mainWidget.setTableVar) # element selection changed
self.elelayout.addWidget(self.mainWidget.elementBox) # add combobox to layout
self.mainWidget.setTableVar() # set the element variation table
# add the buttons to the element layout
self.elelayout.addWidget(addButton)
self.elelayout.addWidget(deleteButton)
# setting the headers for the element variation table
self.mainWidget.varTable.setRowCount(2)
self.mainWidget.varTable.setColumnCount(3)
self.mainWidget.varTable.setHorizontalHeaderLabels(
['Name', 'Ratio', 'Form Factor'])
# Bold variation table header
aFont = QtGui.QFont()
aFont.setBold(True)
self.mainWidget.varTable.horizontalHeader().setFont(aFont)
pagelayout.addLayout(self.elelayout) # add element layout to the page layout
pagelayout.addWidget(self.mainWidget.varTable) # add element variation table to page layout
# set the colors for the variation table
self.mainWidget.varTable.setStyleSheet('background-color: white;')
self.setStyleSheet('background-color: lightgrey;')
self.setLayout(pagelayout)
def changeElements(self):
"""
Purpose: changes the elements based on current layer
"""
# prevents adding elements to combobox from triggering other signals
self.mainWidget.change_elements = True # are we currently changing the elements
idx = self.mainWidget.layerBox.currentIndex() # retrieves the current layer
self.mainWidget.elementBox.clear() # clears the element comboBox
# adds the elements found in the current layer
for j in range(len(self.mainWidget.structTableInfo[idx])):
ele = self.mainWidget.structTableInfo[idx][j][0]
self.mainWidget.elementBox.addItem(ele)
self.mainWidget.change_elements = False # no longer changing the elements
self.mainWidget.elementBox.setCurrentIndex(self.mainWidget.element_index) # sets current index to previous index
def addVarEle(self):
"""
Purpose: Make appropriate changes to adding new element variation
:return:
"""
self.mainWidget.parameterFit = [] # resets fitting parameters
self.mainWidget.currentVal = [] # resets current fitting parameter values
current_layer = self.mainWidget.layerBox.currentIndex() # retrieves the current layer
current_element = self.mainWidget.elementBox.currentIndex() # retrieves the current element
element = self.mainWidget.structTableInfo[current_layer][current_element][0] # retrieves the element symbol
# adds an empty spot in the variation data and magnetic data
row = len(self.mainWidget.varData[element][current_layer][0])
for lay in range(len(self.mainWidget.varData[element])):
if type(self.mainWidget.varData[element][lay][0]) == np.ndarray: # element already initialized for element variation
self.mainWidget.varData[element][lay][0] = np.append(self.mainWidget.varData[element][lay][0], '') # variation id
self.mainWidget.varData[element][lay][1] = np.append(self.mainWidget.varData[element][lay][1], '') # ratio
self.mainWidget.varData[element][lay][2] = np.append(self.mainWidget.varData[element][lay][2], '') # form factor
self.mainWidget.magData[element][lay][0] = np.append(self.mainWidget.magData[element][lay][0], '') # variation id
self.mainWidget.magData[element][lay][1] = np.append(self.mainWidget.magData[element][lay][1], '') # ratio
self.mainWidget.magData[element][lay][2] = np.append(self.mainWidget.magData[element][lay][2], '') # form factor
else: # newly added element variation
self.mainWidget.varData[element][lay][0].append('') # add another element to name list
self.mainWidget.varData[element][lay][1].append('') # add another element to name list
self.mainWidget.varData[element][lay][2].append('') # add another element to name list
self.mainWidget.magData[element][lay][0].append('') # make appropriate changes to magnetic data
self.mainWidget.magData[element][lay][1].append('')
self.mainWidget.magData[element][lay][2].append('')
# row = self.mainWidget.varTable.rowCount()
self.mainWidget.varTable.setRowCount(row + 1) # reset the number of rows in the element variation table
def deleteVarEle(self):
"""
Purpose: Remove the last element variation
"""
# resets the fitting parameters
self.mainWidget.parameterFit = []
self.mainWidget.currentVal = []
current_layer = self.mainWidget.layerBox.currentIndex() # retrieves the current layer
current_element = self.mainWidget.elementBox.currentIndex() # retrieves the current element
element = self.mainWidget.structTableInfo[current_layer][current_element][0] # retrieves element symbol
row = len(self.mainWidget.varData[element][current_layer][0]) # retrieves the current number of rows
# removes the element variation from the variation data and magnetic data
if row != 2: # no longer a polymorphous element
for lay in range(len(self.mainWidget.varData[element])):
if type(self.mainWidget.varData[element][lay][0]) == np.ndarray:
self.mainWidget.varData[element][lay][0] = self.mainWidget.varData[element][lay][0][:-1]
self.mainWidget.varData[element][lay][1] = self.mainWidget.varData[element][lay][1][:-1]
self.mainWidget.varData[element][lay][2] = self.mainWidget.varData[element][lay][2][:-1]
self.mainWidget.magData[element][lay][0] = self.mainWidget.magData[element][lay][0][:-1]
self.mainWidget.magData[element][lay][1] = self.mainWidget.magData[element][lay][1][:-1]
self.mainWidget.magData[element][lay][2] = self.mainWidget.magData[element][lay][2][:-1]
else: # still a polymorphous element
self.mainWidget.varData[element][lay][0].pop() # remove layer
self.mainWidget.varData[element][lay][1].pop() # remove layer
self.mainWidget.varData[element][lay][2].pop() # remove layer
self.mainWidget.magData[element][lay][0].pop() # remove layer
self.mainWidget.magData[element][lay][1].pop()
self.mainWidget.magData[element][lay][2].pop()
self.mainWidget.varTable.setRowCount(row - 1) # set the variation table with the correct number of rows
class ReadOnlyDelegate(QStyledItemDelegate):
# This class is used to set an item delegate to read only
def createEditor(self, parent, option, index):
return
class magneticWidget(QDialog):
"""
Purpose: This widget is used to set the magnetic info based on the user's input
"""
def __init__(self, mainWidget, sample):
"""
:param mainWidget: This is the sampleWidget which allows for access of information from this widget
:param sample: This is the most recent sample model (slab class)
"""
super().__init__()
pagelayout = QHBoxLayout() # page layout
self.mainWidget = mainWidget # sampleWidget
self.sample = sample # resets sample
idx = self.mainWidget.layerBox.currentIndex() # retrieves current index
self.mainWidget.layerBox.currentIndexChanged.connect(self.mainWidget.setTableMag) # set table signal
# Magnetization direction Widget format
magLabel = QLabel('Magnetization Direction')
magLayout = QVBoxLayout()
# magnetization direction (use phi and theta in future versions)
# - instead of using a combobox consider using two QLineEdit widgets (one for phi and one for theta)
self.mainWidget.magDirBox.addItem('x-direction')
self.mainWidget.magDirBox.addItem('y-direction')
self.mainWidget.magDirBox.addItem('z-direction')
# magnetic layout
magLayout.addWidget(magLabel)
magLayout.addWidget(self.mainWidget.magDirBox)
magLayout.addStretch(1)
# magnetic direction signal setup
self.mainWidget.magDirBox.currentIndexChanged.connect(self.magDirectionChange)
# pre-sets the magTable and its headers
self.mainWidget.magTable.setRowCount(3)
self.mainWidget.magTable.setColumnCount(2)
self.mainWidget.magTable.setHorizontalHeaderLabels(
['Magnetic Density (mol/cm^3)', 'Form Factor'])
# bold header font
afont = QtGui.QFont()
afont.setBold(True)
self.mainWidget.magTable.horizontalHeader().setFont(afont)
self.mainWidget.magTable.verticalHeader().setFont(afont)
self.mainWidget.setTableMag() # set magTable
# set the page layout
pagelayout.addWidget(self.mainWidget.magTable)
pagelayout.addLayout(magLayout)
self.setLayout(pagelayout)
# setting the
self.mainWidget.magTable.setStyleSheet('background-color: white;')
self.setStyleSheet('background-color: lightgrey;')
def magDirectionChange(self):
"""
Purpose: change the magnetization direction for sampleWidget
:return:
"""
#lay = self.mainWidget.layerBox.currentIndex() # retrieves layer
mag = self.mainWidget.magDirBox.currentIndex() # retrieves mag-direction index
m = len(self.mainWidget.structTableInfo)
# changes the magnetization direction for all layer
for i in range(m):
if mag == 0:
self.mainWidget.magDirection[i] = 'x'
elif mag == 1:
self.mainWidget.magDirection[i] = 'y'
elif mag == 2:
self.mainWidget.magDirection[i] = 'z'
class sampleWidget(QWidget):
"""
Purpose: This widget contains all the information about the sample and all it's properties.
"""
# sample widget that contains all the information about the sample parameters
def __init__(self, parent,sample):
super(sampleWidget, self).__init__()
# ------------------------------- parameter initialization -------------------------------------#
self.parent = parent # parent Widget
self.data_dict = {} # dictionary that contains the experimental data
self.sample = sample # variable used to define sample info
self.structTableInfo = [] # used to keep track of the table info instead of constantly switching
self.parameterFit = [] # keeps track of parameters to fit
self.varData = {ele: [[['', ''], ['', ''], ['', '']] for i in range(len(sample.structure))] for ele in
sample.myelements} # element variation data [identifier, ratio, form factor]
self.eShift = dict() # keep track of the energy shift
self.ffScale = dict() # keeps track of the form factor scaling information
self.currentVal = [] # keep track of the fitting parameter values
self.orbitals = dict() # orbital dictionary
self.change_eShift = True # boolean used to determine if eShift is changed
self.varTable = QTableWidget() # element variation table
self.elementBox = QComboBox() # used to select which element to change element variation
self.elementBox.setStyleSheet('background-color: white;')
self.variationElements = sample.poly_elements # retrieve the variation elements from the sample
self.struct_ff = [] # list that contains the structural form factors
self.mag_ff = [] # list that contains the magnetic form factors
self.magData = {ele: [[[''], [''], ['']] for i in range(len(sample.structure))] for ele in
sample.myelements} # initialize the magnetic data [identifier, density, form factor]
self.magGo = True # boolean currently not in use
self.magDirection = ['z' for i in range(len(sample.structure))] # initialize the magnetization direction
self.magDirBox = QComboBox() # magnetization direction selection (x,y,z direction)
self.magDirBox.setStyleSheet('background-color: white;')
self.getData() # gets the element variation and magnetic information
self.magTable = QTableWidget() # magnetization property table
self.resetX = False # boolean used to determine if fitting parameters are reset
self.change_elements = False # boolean used to determine if elements are currently being changed
self.element_index = 0 # keeps track of which positional element is being used (A-site, B-site, or O-site)
self.previousLayer = 0 # what was the previous layer
self.changeLayer = False # no longer in use
self.firstStruct = True # no longer in use
self.sf_dict = {} # atomic form factor dictionary
# ------------------------------- Widget Layout -------------------------------------#
# setting up step size
self._step_size = '0.1' # density profile step size
self.step_size = QLineEdit()
self.step_size.textChanged.connect(self.changeStepSize)
self.step_size.setText(self._step_size)
self.step_size.setMaximumWidth(100)
step_size_label = QLabel('Step Size (Å):')
step_size_label.setMaximumWidth(65)
step_size_layout = QHBoxLayout()
step_size_layout.addWidget(step_size_label)
step_size_layout.addWidget(self.step_size)
# setting up Adaptive Layer Segmentation (ALS) precision value for reflectivity scans
self._precision = '1e-6'
self.precisionWidget = QLineEdit()
self.precisionWidget.textChanged.connect(self.changePrecision)
self.precisionWidget.setText(self._precision)
self.precisionWidget.setMaximumWidth(100)
precision_label = QLabel('Precision (qz):')
precision_label.setMaximumWidth(65)
precision_layout = QHBoxLayout()
precision_layout.addWidget(precision_label)
precision_layout.addWidget(self.precisionWidget)
# setting up ALS precision for energy scans
self._Eprecision = '1e-8'
self.Eprecision = QLineEdit()
self.Eprecision.textChanged.connect(self.changeEPrecision)
self.Eprecision.setText(self._Eprecision)
self.Eprecision.setMaximumWidth(100)
Eprecision_label = QLabel('Precision (E):')
Eprecision_label.setMaximumWidth(65)
Eprecision_layout = QHBoxLayout()
Eprecision_layout.addWidget(Eprecision_label)
Eprecision_layout.addWidget(self.Eprecision)
pagelayout = QHBoxLayout() # page layout
cblayout = QVBoxLayout() # combobox and button layout
# bottons for adding, copying, and deleteing layers
addlayerButton = QPushButton('Add Layer') # add layer
addlayerButton.clicked.connect(self._addLayer)
copylayerButton = QPushButton('Copy Current Layer') # copy layer
copylayerButton.clicked.connect(self._copyLayer)
deletelayerButton = QPushButton('Remove Current Layer') # delete layer
deletelayerButton.clicked.connect(self._removeLayer)
# Layer Box
self.structInfo = self.sample.structure
self.layerBox = QComboBox(self)
# initializing layerList based on number of layers
layerList = []
for i in range(len(self.sample.structure)):
if i == 0:
layerList.append('Substrate')
else:
layerList.append('Layer ' + str(i))
# change this for an arbitrary sample model
self.layerBox.addItems(layerList)
self.layerBox.currentIndexChanged.connect(self.setTable)
# changes the table on the screen when new layer selected
# buttons for adding and removing layers
cblayout.addStretch(1)
cblayout.addWidget(addlayerButton) # include add layer button to cblayout
cblayout.addWidget(copylayerButton)
cblayout.addWidget(deletelayerButton)
cblayout.addSpacing(50) # format the layout
cblayout.addLayout(step_size_layout)
cblayout.addLayout(precision_layout)
cblayout.addLayout(Eprecision_layout)
# layer combo box
cblayout.addWidget(self.layerBox)
cblayout.addStretch(1)
self.sampleInfoLayout = QStackedLayout() # stacked layout for the different parameter types
# setting up structural parameter table
self.structTable = QTableWidget()
self.structTable.setRowCount(3)
self.structTable.setColumnCount(7)
self.structTable.setHorizontalHeaderLabels(
['Element', 'Thickness (Å)', 'Density (mol/cm^3)', 'Roughness (Å)', 'Linked Roughness (Å)', 'Scattering Factor', 'Stoichiometry'])
# bold and resize sample workspace headers
afont = QtGui.QFont()
afont.setBold(True)
self.structTable.horizontalHeader().setSectionResizeMode(1) # makes headers fit table (consider removing this)
self.structTable.horizontalHeader().setFont(afont)
self._setStructFromSample(sample) # setting the structural table
# setTable
self.setTable()
# initializing energy shift table (include ff scaling later)
self.energyShiftTable = QTableWidget()
#self.energyShiftTable.setHorizontalHeaderLabels(['Energy Shift (eV)'])
if len(self.orbitals) == 0:
self.energyShiftTable.setColumnCount(2)
self.energyShiftTable.setVerticalHeaderLabels(['Energy Shift (eV)', 'Scale'])
else:
self.energyShiftTable.setColumnCount(7)
self.energyShiftTable.setVerticalHeaderLabels(
['Energy Shift (eV)', 'Scale', 'nd','dExy', 'dExzyz', 'dEx2y2', 'dEzz'])
self.energyShiftTable.verticalHeader().setFont(afont)
self.energyShiftTable.horizontalHeader().setFont(afont)
# variationWidget and magneticWidget initialization
self.elementVariation = variationWidget(self, self.sample)
self.elementMagnetic = magneticWidget(self, self.sample)
# adding widgets to the stacked layout
self.sampleInfoLayout.addWidget(self.structTable)
self.sampleInfoLayout.addWidget(self.elementVariation)
self.sampleInfoLayout.addWidget(self.elementMagnetic)
self.sampleInfoLayout.addWidget(self.energyShiftTable)
# setting up data fitting implementation
self.structTable.viewport().installEventFilter(self)
self.varTable.viewport().installEventFilter(self)
self.magTable.viewport().installEventFilter(self)
self.energyShiftTable.viewport().installEventFilter(self)
selectlayout = QVBoxLayout()
selectlayout.addStretch(1)
# buttons for choosing which parameters to choose
# Sample Workspace
self.structButton = QPushButton('Structure')
self.structButton.setStyleSheet('background: blue; color: white')
self.structButton.clicked.connect(self._structural)
self.structButton.clicked.connect(self.setTableVar)
self.structButton.clicked.connect(self.setTableMag)
selectlayout.addWidget(self.structButton)
# element variation workspace (polymorphous == element variation)
self.polyButton = QPushButton('Element Variation')
self.polyButton.setStyleSheet('background: lightGrey')
self.polyButton.clicked.connect(self._elementVariation)
self.polyButton.clicked.connect(self.setTableVar)
selectlayout.addWidget(self.polyButton)
# Magnetic Workspace
self.magButton = QPushButton('Magnetic')
self.magButton.setStyleSheet('background: lightGrey')
self.magButton.clicked.connect(self._magnetic)
self.magButton.clicked.connect(self.setTableMag)
selectlayout.addWidget(self.magButton)
# Form Factor Workspace
self.shiftButton = QPushButton('Form Factor') # energy shift button
self.shiftButton.setStyleSheet('background: lightGrey')
self.shiftButton.clicked.connect(self._energy_shift)
selectlayout.addWidget(self.shiftButton)
# determine which widget is currently in use
self.structBool = True
self.polyBool = False
self.magBool = False
selectlayout.addSpacing(50) # to make the widget look nice
# button used to plot the density profile
dpButton = QPushButton('Density Profile')
dpButton.clicked.connect(self._densityprofile)
dpButton.setStyleSheet("background-color : cyan")
selectlayout.addWidget(dpButton)
selectlayout.addStretch(1)
# set the page layout
pagelayout.addLayout(cblayout)
pagelayout.addLayout(self.sampleInfoLayout)
pagelayout.addLayout(selectlayout)
mylayout = QVBoxLayout() # layout that includes the plotting layout and the graph
mylayout.addLayout(pagelayout)
# Adding the plotting Widget
self.densityWidget = pg.PlotWidget()
self.densityWidget.setBackground('w')
self.densityWidget.addLegend()
mylayout.addWidget(self.densityWidget)
# change parameters when clicked
self.structTable.itemChanged.connect(self.changeStructValues)
self.varTable.itemChanged.connect(self.changeVarValues)
self.magTable.itemChanged.connect(self.changeMagValues)
self.energyShiftTable.itemChanged.connect(self.changeEShiftValues)
delegate = ReadOnlyDelegate()
self.structTable.setItemDelegateForColumn(0, delegate)
self.setLayout(mylayout)
def check_element_number(self):
"""
Purpose: Checks to make sure that there is a sample defined and the number of elements in each layer is the same
:return: booleans not_empty and equal_elements where True states there are no defined layers and there
are not an equal number of elements in each layer, respectively.
"""
not_empty = True
equal_elements = True
n = len(self.structTableInfo)
if n == 0: # no defined layers
not_empty = False
else:
substrate = len(self.structTableInfo[0])
for i in range(n):
if len(self.structTableInfo[0]) != substrate: # unequal number of elements throughout the sample layers
equal_elements = False
return not_empty, equal_elements
def _energy_shift(self):
"""
Purpose: Change to energy shift info
:return:
"""
self.sampleInfoLayout.setCurrentIndex(3)
self.structButton.setStyleSheet('background: lightGrey')
self.polyButton.setStyleSheet('background: lightGrey')
self.magButton.setStyleSheet('background: lightGrey')
self.shiftButton.setStyleSheet('background: blue; color: white')
self.setTableEShift()
def setTableEShift(self):
"""
Purpose: Reset energy table
"""
self.energyShiftTable.blockSignals(True) # block energy shift signals
keys = list(self.eShift.keys()) # retrieve energy shift keys
# set the energy shift form factor names and their values
self.energyShiftTable.setColumnCount(len(keys))
self.energyShiftTable.setHorizontalHeaderLabels(keys)
if len(self.orbitals) == 0:
self.energyShiftTable.setRowCount(2)
self.energyShiftTable.setVerticalHeaderLabels(['Energy Shift (eV)', 'Scale'])
else:
self.energyShiftTable.setRowCount(7)
self.energyShiftTable.setVerticalHeaderLabels(
['Energy Shift (eV)', 'Scale', 'nd','dExy', 'dExzyz', 'dEx2y2', 'dEzz'])
for column, key in enumerate(keys): # setting energy shift
my_key = key.split('-')[1]
if my_key in list(self.orbitals.keys()):
item0 = QTableWidgetItem(str(self.parent.nd)) # nd
item1 = QTableWidgetItem(str(self.orbitals[my_key][0])) # dExy
item2 = QTableWidgetItem(str(self.orbitals[my_key][1])) # dExzyz
item3 = QTableWidgetItem(str(self.orbitals[my_key][2])) # dEx2y2
item4 = QTableWidgetItem(str(self.orbitals[my_key][3])) # dEzz
self.energyShiftTable.setItem(2, column, item0) # nd
self.energyShiftTable.setItem(3, column, item1) # dExy
self.energyShiftTable.setItem(4, column, item2) # dExzyz
self.energyShiftTable.setItem(5, column, item3) # dEx2y2
self.energyShiftTable.setItem(6, column, item4) # dEzz
else:
# False makes sure the orbitals are not incorporated into the calculation (only works for Ti)
item0 = QTableWidgetItem('False') # nd
item1 = QTableWidgetItem('False') # dExy
item2 = QTableWidgetItem('False') # dExzyz
item3 = QTableWidgetItem('False') # dEx2y2
item4 = QTableWidgetItem('False') # dEzz
self.energyShiftTable.setItem(2, column, item0) # nd
self.energyShiftTable.setItem(3, column, item1) # dExy
self.energyShiftTable.setItem(4, column, item2) # dExzyz
self.energyShiftTable.setItem(5, column, item3) # dEx2y2
self.energyShiftTable.setItem(6, column, item4) # dEzz
for column, key in enumerate(keys): # setting energy shift
item = QTableWidgetItem(str(self.eShift[key]))
self.energyShiftTable.setItem(0, column, item)
for column, key in enumerate(keys): # setting scaling factor
item = QTableWidgetItem(str(self.ffScale[key]))
self.energyShiftTable.setItem(1, column, item)
# set the parameter fit colors
copy_of_list = copy.deepcopy(self.parameterFit)
# checks table if any values are selected to fit and color the appropriate cells
my_fits = []
my_rows = []
for fit in copy_of_list:
for column, key in enumerate(keys):
if len(fit) == 3:
if fit[0] == 'SCATTERING FACTOR':
if key[:3] == 'ff-':
if fit[0] == 'SCATTERING FACTOR' and fit[1] == 'STRUCTURAL' and fit[2] == key[3:]:
my_fits.append(column)
my_rows.append(0)
else:
self.energyShiftTable.item(0, column).setBackground(QtGui.QColor(255, 255, 255))
elif fit[0] == 'ORBITAL':
test_key = ''
if key[:3] == 'ff-':
test_key = key[3:]
if key[:3] == 'ffm':
test_key = key[4:]
if test_key == fit[2]:
my_fits.append(column)
if fit[1] == 'DEXY':
my_rows.append(3)
elif fit[1] == 'DEXZYZ':
my_rows.append(4)
elif fit[1] == 'DEX2Y2':
my_rows.append(5)
elif fit[1] == 'DEZZ':
my_rows.append(6)
else:
self.energyShiftTable.item(0, column).setBackground(QtGui.QColor(255, 255, 255))
else:
self.energyShiftTable.item(0, column).setBackground(QtGui.QColor(255, 255, 255))
elif key[:3] == 'ffm':
if fit[0] == 'SCATTERING FACTOR' and fit[1] == 'MAGNETIC' and fit[2] == key[4:]:
my_fits.append(column)
my_rows.append(0)
else:
self.energyShiftTable.item(0, column).setBackground(QtGui.QColor(255, 255, 255))
for i,col in enumerate(my_fits): # cells to color
self.energyShiftTable.item(my_rows[i], col).setBackground(QtGui.QColor(0, 255, 0))
# checks to see if user has any form factors in the current working directory
# - sets form factors in directory to blue
for col, key in enumerate(keys):
if key.startswith('ff-'):
name = key.strip('ff-')
if name in self.struct_ff:
self.energyShiftTable.horizontalHeaderItem(col).setForeground(QtGui.QColor(0, 0, 255))
elif key.startswith('ffm'):