diff --git a/.gitignore b/.gitignore index 6c409a4..cd98d5b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,5 @@ **/*output/ figures/**/*.csv -quickstart/**/*.csv +*.psd diff --git a/DebyeCalculator/debye_calculator.py b/DebyeCalculator/debye_calculator.py index 3cd3dd6..dcd3863 100644 --- a/DebyeCalculator/debye_calculator.py +++ b/DebyeCalculator/debye_calculator.py @@ -93,8 +93,10 @@ def __init__( self.biso = biso # Initialise ranges - self.q = torch.linspace(self.qmin, self.qmax, int((self.qmax+self.qstep) / self.qstep)).unsqueeze(-1).to(device=self.device) - self.r = torch.linspace(self.rmin, self.rmax, int((self.rmax+self.rstep) / self.rstep)).unsqueeze(-1).to(device=self.device) + #self.q = torch.linspace(self.qmin, self.qmax-self.qstep, int((self.qmax+self.qstep) / self.qstep)).unsqueeze(-1).to(device=self.device) + #self.r = torch.linspace(self.rmin, self.rmax-self.rstep, int((self.rmax+self.rstep) / self.rstep)).unsqueeze(-1).to(device=self.device) + self.q = torch.arange(self.qmin, self.qmax, self.qstep).unsqueeze(-1).to(device=self.device) + self.r = torch.arange(self.rmin, self.rmax, self.rstep).unsqueeze(-1).to(device=self.device) # Form factor coefficients with open(pkg_resources.resource_filename(__name__, 'form_factor_coef.yaml'), 'r') as yaml_file: @@ -141,8 +143,12 @@ def update_parameters( # Re-initialise ranges if np.any([k in ['qmin','qmax','qstep','rmin', 'rmax', 'rstep'] for k in kwargs.keys()]): - self.q = torch.linspace(self.qmin, self.qmax, int((self.qmax+self.qstep) / self.qstep)).unsqueeze(-1).to(device=self.device) - self.r = torch.linspace(self.rmin, self.rmax, int((self.rmax+self.rstep) / self.rstep)).unsqueeze(-1).to(device=self.device) + #self.q = torch.linspace(self.qmin, self.qmax-self.qstep, int(self.qmax / self.qstep)).unsqueeze(-1).to(device=self.device) + #self.r = torch.linspace(self.rmin, self.rmax-self.rstep, int(self.rmax / self.rstep)).unsqueeze(-1).to(device=self.device) + #self.q = torch.linspace(self.qmin, self.qmax-self.qstep, int((self.qmax+self.qstep) / self.qstep)).unsqueeze(-1).to(device=self.device) + #self.r = torch.linspace(self.rmin, self.rmax-self.rstep, int((self.rmax+self.rstep) / self.rstep)).unsqueeze(-1).to(device=self.device) + self.q = torch.arange(self.qmin, self.qmax, self.qstep).unsqueeze(-1).to(device=self.device) + self.r = torch.arange(self.rmin, self.rmax, self.rstep).unsqueeze(-1).to(device=self.device) def _initialise_structures( self, diff --git a/DebyeCalculator/SASCalculator.py b/DebyeCalculator/submodules/SASCalculator.py similarity index 100% rename from DebyeCalculator/SASCalculator.py rename to DebyeCalculator/submodules/SASCalculator.py diff --git a/DebyeCalculator/submodules/__init__.py b/DebyeCalculator/submodules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/DebyeCalculator/generate_nanoparticles.py b/DebyeCalculator/submodules/generate_nanoparticles.py similarity index 100% rename from DebyeCalculator/generate_nanoparticles.py rename to DebyeCalculator/submodules/generate_nanoparticles.py diff --git a/DebyeCalculator/images/a.png b/DebyeCalculator/submodules/images/a.png similarity index 100% rename from DebyeCalculator/images/a.png rename to DebyeCalculator/submodules/images/a.png diff --git a/DebyeCalculator/images/a_inv.png b/DebyeCalculator/submodules/images/a_inv.png similarity index 100% rename from DebyeCalculator/images/a_inv.png rename to DebyeCalculator/submodules/images/a_inv.png diff --git a/DebyeCalculator/images/a_sq.png b/DebyeCalculator/submodules/images/a_sq.png similarity index 100% rename from DebyeCalculator/images/a_sq.png rename to DebyeCalculator/submodules/images/a_sq.png diff --git a/DebyeCalculator/images/batch_size.png b/DebyeCalculator/submodules/images/batch_size.png similarity index 100% rename from DebyeCalculator/images/batch_size.png rename to DebyeCalculator/submodules/images/batch_size.png diff --git a/DebyeCalculator/images/choose_hardware.png b/DebyeCalculator/submodules/images/choose_hardware.png similarity index 100% rename from DebyeCalculator/images/choose_hardware.png rename to DebyeCalculator/submodules/images/choose_hardware.png diff --git a/DebyeCalculator/images/enter_path.png b/DebyeCalculator/submodules/images/enter_path.png similarity index 100% rename from DebyeCalculator/images/enter_path.png rename to DebyeCalculator/submodules/images/enter_path.png diff --git a/DebyeCalculator/images/file_1.png b/DebyeCalculator/submodules/images/file_1.png similarity index 100% rename from DebyeCalculator/images/file_1.png rename to DebyeCalculator/submodules/images/file_1.png diff --git a/DebyeCalculator/images/file_2.png b/DebyeCalculator/submodules/images/file_2.png similarity index 100% rename from DebyeCalculator/images/file_2.png rename to DebyeCalculator/submodules/images/file_2.png diff --git a/DebyeCalculator/images/fq.png b/DebyeCalculator/submodules/images/fq.png similarity index 100% rename from DebyeCalculator/images/fq.png rename to DebyeCalculator/submodules/images/fq.png diff --git a/DebyeCalculator/images/global_biso.png b/DebyeCalculator/submodules/images/global_biso.png similarity index 100% rename from DebyeCalculator/images/global_biso.png rename to DebyeCalculator/submodules/images/global_biso.png diff --git a/DebyeCalculator/images/gr.png b/DebyeCalculator/submodules/images/gr.png similarity index 100% rename from DebyeCalculator/images/gr.png rename to DebyeCalculator/submodules/images/gr.png diff --git a/DebyeCalculator/images/iq.png b/DebyeCalculator/submodules/images/iq.png similarity index 100% rename from DebyeCalculator/images/iq.png rename to DebyeCalculator/submodules/images/iq.png diff --git a/DebyeCalculator/images/iq_scaling.png b/DebyeCalculator/submodules/images/iq_scaling.png similarity index 100% rename from DebyeCalculator/images/iq_scaling.png rename to DebyeCalculator/submodules/images/iq_scaling.png diff --git a/DebyeCalculator/images/max_norm.png b/DebyeCalculator/submodules/images/max_norm.png similarity index 100% rename from DebyeCalculator/images/max_norm.png rename to DebyeCalculator/submodules/images/max_norm.png diff --git a/DebyeCalculator/images/qdamp.png b/DebyeCalculator/submodules/images/qdamp.png similarity index 100% rename from DebyeCalculator/images/qdamp.png rename to DebyeCalculator/submodules/images/qdamp.png diff --git a/DebyeCalculator/images/qslider.png b/DebyeCalculator/submodules/images/qslider.png similarity index 100% rename from DebyeCalculator/images/qslider.png rename to DebyeCalculator/submodules/images/qslider.png diff --git a/DebyeCalculator/images/qstep.png b/DebyeCalculator/submodules/images/qstep.png similarity index 100% rename from DebyeCalculator/images/qstep.png rename to DebyeCalculator/submodules/images/qstep.png diff --git a/DebyeCalculator/images/radiation_type.png b/DebyeCalculator/submodules/images/radiation_type.png similarity index 100% rename from DebyeCalculator/images/radiation_type.png rename to DebyeCalculator/submodules/images/radiation_type.png diff --git a/DebyeCalculator/images/radius_a.png b/DebyeCalculator/submodules/images/radius_a.png similarity index 100% rename from DebyeCalculator/images/radius_a.png rename to DebyeCalculator/submodules/images/radius_a.png diff --git a/DebyeCalculator/images/rslider.png b/DebyeCalculator/submodules/images/rslider.png similarity index 100% rename from DebyeCalculator/images/rslider.png rename to DebyeCalculator/submodules/images/rslider.png diff --git a/DebyeCalculator/images/rstep.png b/DebyeCalculator/submodules/images/rstep.png similarity index 100% rename from DebyeCalculator/images/rstep.png rename to DebyeCalculator/submodules/images/rstep.png diff --git a/DebyeCalculator/images/rthres.png b/DebyeCalculator/submodules/images/rthres.png similarity index 100% rename from DebyeCalculator/images/rthres.png rename to DebyeCalculator/submodules/images/rthres.png diff --git a/DebyeCalculator/images/scattering_parameters.png b/DebyeCalculator/submodules/images/scattering_parameters.png similarity index 100% rename from DebyeCalculator/images/scattering_parameters.png rename to DebyeCalculator/submodules/images/scattering_parameters.png diff --git a/DebyeCalculator/images/select_files.png b/DebyeCalculator/submodules/images/select_files.png similarity index 100% rename from DebyeCalculator/images/select_files.png rename to DebyeCalculator/submodules/images/select_files.png diff --git a/DebyeCalculator/images/show_hide.png b/DebyeCalculator/submodules/images/show_hide.png similarity index 100% rename from DebyeCalculator/images/show_hide.png rename to DebyeCalculator/submodules/images/show_hide.png diff --git a/DebyeCalculator/images/sq.png b/DebyeCalculator/submodules/images/sq.png similarity index 100% rename from DebyeCalculator/images/sq.png rename to DebyeCalculator/submodules/images/sq.png diff --git a/DebyeCalculator/submodules/interact.py b/DebyeCalculator/submodules/interact.py new file mode 100644 index 0000000..20e8868 --- /dev/null +++ b/DebyeCalculator/submodules/interact.py @@ -0,0 +1,419 @@ +import ipywidgets as widgets +from ipywidgets import HBox, VBox +import numpy as np +import matplotlib.pyplot as plt +from IPython.display import display +from IPython.display import clear_output +from glob import glob +import os +import threading +import time +import sys +import base64 +from IPython.display import display, HTML +from datetime import datetime + +from debye_calculator import DebyeCalculator + +def run_interact( + debye_calc: DebyeCalculator, + _cont_updates: bool = False +): + qmin = debye_calc.qmin + qmax = debye_calc.qmax + qstep = debye_calc.qstep + qdamp = debye_calc.qdamp + rmin = debye_calc.rmin + rmax = debye_calc.rmax + rstep = debye_calc.rstep + rthres = debye_calc.rthres + biso = debye_calc.biso + device = debye_calc.device + batch_size = debye_calc.batch_size + lorch_mod = debye_calc.lorch_mod + radiation_type = debye_calc.radiation_type + profile = False + + # Buttons + radtype_button = widgets.ToggleButtons(options=['xray', 'neutron'], + value=radiation_type, + description='Rad. type', + layout = widgets.Layout(width='900px'), + button_style='info' + ) + select_radius = widgets.FloatText( + min = 0, + max = 50, + step=0.01, + value=5, + description='Radius (.cif):', + disabled = True, + ) + device_button = widgets.ToggleButtons( + options=['cpu', 'cuda'], + value=device, + description='Hardware:', + button_style='info', + ) + batch_size_button = widgets.IntText( + min = 100, + max = 10000, + value=batch_size, + description='Batch Size:', + ) + qslider = widgets.FloatRangeSlider( + value=[qmin, qmax], + min=0.0, + max=50.0, + step=0.01, + description='Qmin/Qmax:', + continuous_update=_cont_updates, + orientation='horizontal', + readout=True, + style={'font_weight':'bold', 'slider_color': 'white'}, + layout = widgets.Layout(width='900px'), + ) + rslider = widgets.FloatRangeSlider( + value=[rmin, rmax], + min=0, + max=100.0, + step=rstep, + description='rmin/rmax:', + continuous_update=_cont_updates, + orientation='horizontal', + readout=True, + style={'font_weight':'bold', 'slider_color': 'white'}, + layout = widgets.Layout(width='900px'), + ) + qdamp_slider = widgets.FloatSlider( + min=0.00, + max=0.10, + value=qdamp, + step=0.01, + description='Qdamp:', + layout = widgets.Layout(width='900px'), + continuous_update=_cont_updates, + ) + biso_slider = widgets.FloatSlider( + min=0.00, + max=1.00, + value=biso, + step=0.01, + description='B-iso:', + continuous_update=_cont_updates, + layout = widgets.Layout(width='900px'), + ) + qstep_box = widgets.FloatText( + min = 0.001, + max = 1, + step=0.001, + value=qstep, + description='Qstep:', + ) + rstep_box = widgets.FloatText( + min = 0.001, + max = 1, + step=0.001, + value=rstep, + description='rstep:', + ) + rthres_box = widgets.FloatText( + min = 0.001, + max = 1, + step=0.001, + value=rthres, + description='rthres:', + ) + lorch_mod_button = widgets.Checkbox( + value=lorch_mod, + description='Lorch mod.:', + ) + scale_type_button = widgets.ToggleButtons( + options=['linear', 'logarithmic'], + value='linear', + description='Axes scaling:', + button_style='info' + ) + + # Download options + def create_download_link(filename_prefix, data, header=None): + + # Collect Metadata + metadata ={ + 'qmin': qslider.value[0], + 'qmax': qslider.value[1], + 'qdamp': qdamp_slider.value, + 'qstep': qstep_box.value, + 'rmin': rslider.value[0], + 'rmax': rslider.value[1], + 'rstep': rstep_box.value, + 'rthres': rthres_box.value, + 'biso': biso_slider.value, + 'device': device_button.value, + 'batch_size': batch_size_button.value, + 'lorch_mod': lorch_mod_button.value, + 'radiation_type': radtype_button.value + } + + # Join content + output = '' + content = "\n".join([",".join(map(str, np.around(row,len(str(qstep_box.value))))) for row in data]) + for k,v in metadata.items(): + output += f'{k}:{v}\n' + output += '\n' + if header: + output += header + '\n' + output += content + + # Encode as base64 + b64 = base64.b64encode(output.encode()).decode() + + # Add Time + t = datetime.now() + year = f'{t.year}'[-2:] + month = f'{t.month}'.zfill(2) + day = f'{t.day}'.zfill(2) + hours = f'{t.hour}'.zfill(2) + minutes = f'{t.minute}'.zfill(2) + seconds = f'{t.second}'.zfill(2) + + # Make filename + filename = filename_prefix + '_' + select_file.value.split('/')[-1].split('.')[0] + '_' + month + day + year + '_' + hours + minutes + seconds + '.csv' + + # Make href and return + href = f'Download {filename}' + return href + + def create_structure_download_link(filename_prefix, ase_atoms): + + # Get atomic properties + positions = ase_atoms.get_positions() + elements = ase_atoms.get_chemical_symbols() + num_atoms = len(ase_atoms) + + # Make header + header = str(num_atoms) + "\n\n" + + # Join content + content = header + "\n".join([el + '\t' + "\t".join(map(str,np.around(row, 3))) for row, el in zip(positions, elements)]) + + # Encode as base64 + b64 = base64.b64encode(content.encode()).decode() + + # Add Time + t = datetime.now() + year = f'{t.year}'[-2:] + month = f'{t.month}'.zfill(2) + day = f'{t.day}'.zfill(2) + hours = f'{t.hour}'.zfill(2) + minutes = f'{t.minute}'.zfill(2) + seconds = f'{t.second}'.zfill(2) + + # Make ilename + filename = filename_prefix + '_' + select_file.value.split('/')[-1].split('.')[0] + str(select_radius.value) + '_' + month + day + year + '_' + hours + minutes + seconds + '.xyz' + + # Make href and return + href = f'Download {filename}' + return href + + # Download buttons + download_button = widgets.Button(description="Download Data") + + @download_button.on_click + def on_download_button_click(button): + # Try to compile all the data and create html link to download files + try: + # Data + iq_data = np.column_stack([q, iq]) + sq_data = np.column_stack([q, sq]) + fq_data = np.column_stack([q, fq]) + gr_data = np.column_stack([r, gr]) + + # Clear warning message + sys.stdout.write('\x1b[1A') + sys.stdout.write('\x1b[1A') + sys.stdout.write('\x1b[1A') + sys.stdout.write('\x1b[2K') + + # Display download links + display(HTML(create_download_link('iq', iq_data, "q,I(Q)"))) + display(HTML(create_download_link('sq', sq_data, "q,S(Q)"))) + display(HTML(create_download_link('fq', fq_data, "q,F(Q)"))) + display(HTML(create_download_link('gr', gr_data, "r,G(r)"))) + + if not select_radius.disabled: + ase_atoms, _ = DebyeCalculator().generate_nanoparticles(select_file.value, select_radius.value) + + display(HTML(create_structure_download_link('structure', ase_atoms[0]))) + + except Exception as e: + raise(e) + print('FAILED: No data has been selected', end="\r") + + # Folder dropdown widget + folder = widgets.Text(description='Data Dir.:', placeholder='Provide data directory', disabled=False) + + # Create a dropdown menu widget for selection of XYZ file and an output area + DEFAULT_MSG = '' + select_file = widgets.Dropdown(description='Select File:', options=[DEFAULT_MSG], value=DEFAULT_MSG, disabled=True) + + # Define a function to update the scattering patterns based on the selected parameters + def update_options(change): + folder = change.new + paths = sorted(glob(os.path.join(folder, '*.xyz')) + glob(os.path.join(folder, '*.cif'))) + if len(paths): + select_file.options = ['Select data file'] + paths + select_file.value = 'Select data file' + select_file.disabled = False + else: + select_file.options = [DEFAULT_MSG] + select_file.value = DEFAULT_MSG + select_file.disabled = True + + # Link the update function to the dropdown widget's value change event + folder.observe(update_options, names='value') + + def update_options_radius(change): + #select_radius = change.new + selected_ext = select_file.value.split('.')[-1] + if selected_ext == 'xyz': + select_radius.disabled = True + elif selected_ext == 'cif': + select_radius.disabled = False + + select_file.observe(update_options_radius, names='value') + + plot_button = widgets.Button( + description='Plot', + ) + + def update_figure(r, q, iq, sq, fq, gr, _unity_sq=True): + + fig, axs = plt.subplots(2, 2, figsize=(12, 8), dpi=75) + axs = axs.flatten() + + if scale_type_button.value == 'logarithmic': + axs[0].set_xscale('log') + axs[0].set_yscale('log') + + axs[0].plot(q, iq) + axs[0].set(xlabel='$Q$ [$\AA^{-1}$]', ylabel='$I(Q)$ [counts]') + + axs[1].axhline(1, alpha=0.5, ls='--', c='g') + axs[1].plot(q, sq+int(_unity_sq)) + axs[1].set(xlabel='$Q$ [$\AA^{-1}$]', ylabel='$S(Q)$') + + axs[2].axhline(0, alpha=0.5, ls='--', c='g') + axs[2].plot(q, fq) + axs[2].set(xlabel='$Q$ [$\AA^{-1}$]', ylabel='$F(Q)$') + + axs[3].plot(r, gr) + axs[3].set(xlabel='$r$ [$\AA$]', ylabel='$G_r(r)$') + + labels = ['Scattering Intensity, I(Q)', + 'Structure Function, S(Q)', + 'Reduced Structure Function, F(Q)', + 'Reduced Pair Distribution Function, G(r)'] + + for ax, label in zip(axs, labels): + ax.relim() + ax.autoscale_view() + ax.set_title(label) + ax.grid(alpha=0.2) + + fig.suptitle("XYZ file: " + select_file.value.split('/')[-1].split('.')[0]) + fig.tight_layout() + + @plot_button.on_click + def update_parameters(b=None): + + clear_output(wait=True) + display_tabs() + + global r, q, iq, sq, fq, gr + + try: + path_ext = select_file.value.split('.')[-1] + except Exception as e: + return + + if (select_file.value is not None) and select_file.value != DEFAULT_MSG and path_ext in ['xyz', 'cif']: + try: + debye_calc = DebyeCalculator( + device=device_button.value, + batch_size=batch_size_button.value, + radiation_type=radtype_button.value, + qmin=qslider.value[0], + qmax=qslider.value[1], + qstep=qstep_box.value, + qdamp=qdamp_slider.value, + rmin=rslider.value[0], + rmax=rslider.value[1], + rstep=rstep_box.value, + rthres=rthres_box.value, + biso=biso_slider.value, + lorch_mod=lorch_mod_button.value + ) + if not select_radius.disabled and select_radius.value > 8: + print('Generating...') + r, q, iq, sq, fq, gr = debye_calc._get_all(select_file.value, select_radius.value) + + clear_output(wait=True) + display_tabs() + update_figure(r, q, iq, sq, fq, gr) + + except Exception as e: + raise(e) + print(f'FAILED: Could not load data file: {path}', end='\r') + + # Make File Tab + file_tab = VBox(children = [ + folder, + select_file, + ]) + + # Make Generate Tab + generate_tab = VBox(children = [ + folder, + select_file, + select_radius, + ]) + + # Make Scattering Tab + scattering_tab = VBox(children = [ + radtype_button, + HBox(children=[qslider, qstep_box]), + HBox(children=[rslider, rstep_box]), + HBox(children=[qdamp_slider, rthres_box]), + HBox(children=[biso_slider, lorch_mod_button]), + ]) + + # Make Plotting Tab + plotting_tab = VBox(children = [ + scale_type_button, + ]) + + # Make Hardware Tab + hardware_tab = VBox(children = [ + device_button, + batch_size_button, + ]) + + # Display Tabs + tabs = widgets.Tab(children=[ + file_tab, + generate_tab, + scattering_tab, + plotting_tab, + hardware_tab, + ]) + + tabs.set_title(0, 'XYZ File Select') + tabs.set_title(1, 'CIF Nanoparticle Generation') + tabs.set_title(2, 'Scattering Parameters') + tabs.set_title(3, 'Plotting Options') + tabs.set_title(4, 'Hardware Options') + + def display_tabs(): + display(VBox(children=[tabs, HBox(children=[plot_button, download_button])])) + display_tabs() diff --git a/DebyeCalculator/produce_figure_data.py b/DebyeCalculator/submodules/produce_figure_data.py similarity index 100% rename from DebyeCalculator/produce_figure_data.py rename to DebyeCalculator/submodules/produce_figure_data.py diff --git a/DebyeCalculator/profiling.py b/DebyeCalculator/submodules/profiling.py similarity index 100% rename from DebyeCalculator/profiling.py rename to DebyeCalculator/submodules/profiling.py diff --git a/quickstart/tutorial_output/timings_diffpy.csv b/quickstart/tutorial_output/timings_diffpy.csv index 7c99ee1..f951122 100644 --- a/quickstart/tutorial_output/timings_diffpy.csv +++ b/quickstart/tutorial_output/timings_diffpy.csv @@ -1,9 +1,11 @@ # diameter, n_atoms, mu, sigma -4.213586,5.000000,0.000700,0.000415 -8.068403,23.000000,0.001456,0.000088 -10.603959,47.000000,0.004030,0.000025 -12.640759,77.000000,0.009765,0.000441 -15.952379,161.000000,0.038989,0.001225 -17.373060,185.000000,0.050135,0.000977 -18.686039,287.000000,0.122994,0.003120 -21.067930,383.000000,0.213805,0.002892 +31.811878,1367.000000,2.307051,0.000000 +29.495102,1079.000000,1.525399,0.000000 +27.843697,845.000000,0.969698,0.000000 +25.164200,701.000000,0.681239,0.000000 +22.163090,473.000000,0.309264,0.000000 +21.067930,383.000000,0.210706,0.000000 +18.686039,287.000000,0.117608,0.000000 +17.373060,185.000000,0.046238,0.000000 +15.952379,161.000000,0.036190,0.000000 +12.640759,77.000000,0.009377,0.000000