diff --git a/.gitignore b/.gitignore index 5515ed710..a5965059b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,7 @@ build/ export/ .do-not-setup-on-localhost + +# Sphinx documentation +docs/html screenshots/ diff --git a/docs/source/_static/images/xps_etfa_dft.png b/docs/source/_static/images/xps_etfa_dft.png new file mode 100644 index 000000000..d92fba9b3 Binary files /dev/null and b/docs/source/_static/images/xps_etfa_dft.png differ diff --git a/docs/source/_static/images/xps_etfa_exp.jpg b/docs/source/_static/images/xps_etfa_exp.jpg new file mode 100644 index 000000000..f95c01f6c Binary files /dev/null and b/docs/source/_static/images/xps_etfa_exp.jpg differ diff --git a/docs/source/_static/images/xps_step_1.png b/docs/source/_static/images/xps_step_1.png new file mode 100644 index 000000000..bc278e97a Binary files /dev/null and b/docs/source/_static/images/xps_step_1.png differ diff --git a/docs/source/_static/images/xps_step_2_setting_tab.png b/docs/source/_static/images/xps_step_2_setting_tab.png new file mode 100644 index 000000000..4476bc01a Binary files /dev/null and b/docs/source/_static/images/xps_step_2_setting_tab.png differ diff --git a/docs/source/_static/images/xps_step_3.png b/docs/source/_static/images/xps_step_3.png new file mode 100644 index 000000000..10d9edff8 Binary files /dev/null and b/docs/source/_static/images/xps_step_3.png differ diff --git a/docs/source/_static/images/xps_step_4_output.png b/docs/source/_static/images/xps_step_4_output.png new file mode 100644 index 000000000..af0b33b00 Binary files /dev/null and b/docs/source/_static/images/xps_step_4_output.png differ diff --git a/docs/source/_static/images/xps_step_4_pa_exp.png b/docs/source/_static/images/xps_step_4_pa_exp.png new file mode 100644 index 000000000..d27a16aca Binary files /dev/null and b/docs/source/_static/images/xps_step_4_pa_exp.png differ diff --git a/docs/source/_static/images/xps_step_4_xps_tab.png b/docs/source/_static/images/xps_step_4_xps_tab.png new file mode 100644 index 000000000..41d0c7855 Binary files /dev/null and b/docs/source/_static/images/xps_step_4_xps_tab.png differ diff --git a/docs/source/howto/index.rst b/docs/source/howto/index.rst index 0194629ee..08361e63e 100644 --- a/docs/source/howto/index.rst +++ b/docs/source/howto/index.rst @@ -11,3 +11,4 @@ How-to guides import_structure upgrade_uninstall xas + xps diff --git a/docs/source/howto/xps.rst b/docs/source/howto/xps.rst new file mode 100644 index 000000000..f8ac2ddda --- /dev/null +++ b/docs/source/howto/xps.rst @@ -0,0 +1,161 @@ +============================ +How to calculate XPS spectra +============================ + +Overview +======== +This tutorial will guide you through the process of setting up and running an XPS calculation for Phenylacetylene molecule. + + +Steps +===== + +To start, go ahead and :doc:`launch ` the app, then follow the steps below. + + +Step 1 Select a structure +-------------------------------- +For this tutorial task, please use the `From Examples` tab, and select the Phenylacetylene molecule structure. + +Click the `Confirm` button to proceed. + +.. figure:: /_static/images/xps_step_1.png + :align: center + + +Step 2 Configure workflow +-------------------------------- + +In the **Basic Settings** tab, set the following parameters: + +- In the **Structure optimization** section, select ``Structure as is``. +- Set **Electronic Type** to ``Insulator`` +- In the **properties** section, select ``X-ray photoelectron spectroscopy (XPS)`` + + +Then go to the **Advanced settings** tab, navigate to `Accuracy and precision`, tick the `Override` box on the right-hand-side and in the dropdown box under `Exchange-correlation functional` select `PBE`. + +.. image:: ../_static/images/XAS_Plugin-Set_Adv_Options-Alt-Annotated-Cropped.png + :align: center + + +.. note:: + At present, core-hole pseudopotentials for Si and O are only available for the PBE functional. + +Then go to the **XPS setting** tab and, in the **Select core-level** section, select ``C_1s`` by ticking the appropriate box. + +.. image:: ../_static/images/xps_step_2_setting_tab.png + :align: center + + +Click the **Confirm** button to proceed. + + +Step 3 Choose computational resources +--------------------------------------- +We need to use a `pw` code on the high-performance computer to run XPS calculation for this system. +Please read the relevant :doc:`How-To ` section to setup code on a remote machine. + +.. image:: ../_static/images/xps_step_3.png + :align: center + + +Then, click the **Submit** button. + + + +Step 4 Check the status and results +----------------------------------------- +The job may take 5~10 minutes to finish if your jobs are running immediately without waiting in the queue. + +While the calculation is running, you can monitor its status as shown in the :ref:`basic tutorial `. +When the job is finished, you can view result spectra in the `XPS` tab. + +.. tip:: + + If the `XPS` tab is now shown when the jobs is finished. + Click the ``QeAppWorkChain`` item on top of the nodetree to refresh the step. + +Here is the result of the XPS calculation. +You can click the **Binding energy** button to view the calculated binding energies. +You can change which element to view XPS spectra for using the dropdown box in the top left. + +.. figure:: /_static/images/xps_step_4_xps_tab.png + :align: center + +One can upload the experimental XPS spectra, and compare to the calculated XPS spectra. +There is a button on the bottom left of the XPS tab to upload the experimental data. +Here is an example of the comparison between the calculated and experimental XPS spectra [1] for the C_1s core level of Phenylacetylene. + +.. figure:: /_static/images/xps_step_4_pa_exp.png + :align: center + +The calculated spectra agrees well with the experimental data, underscoring the reliability of DFT calculations. + + +.. tip:: + + One can also read the exact binding energies from the the output of the calculation, by clicking the `outputs` tab on the node tree of the WorkChain, as shown below. + + .. figure:: /_static/images/xps_step_4_output.png + :align: center + + + The DFT calculated binding energies do not include spin-orbit splitting of the core level state. + We can include the spin-orbit splitting using its experimental value. + Take `f` orbit as a example, we need subtracting :math:`3/7` of the experimental spin-orbit splitting or adding :math:`4/7` of the DFT calculated value, to get the position of the :math:`4f_{7/2}` and :math:`4f_{5/2}` peaks, respectively. Here is a table of the spin-orbit splitting for different orbitals. + + +----------------+-------------------+-------------------+ + | Orbit | Substracting | Adding | + +================+===================+===================+ + | 1s | 0 | 0 | + +----------------+-------------------+-------------------+ + | 2p | :math:`1/3` | :math:`2/3` | + +----------------+-------------------+-------------------+ + | 3d | :math:`2/5` | :math:`3/5` | + +----------------+-------------------+-------------------+ + | 4f | :math:`3/7` | :math:`4/7` | + +----------------+-------------------+-------------------+ + + + +Congratulations, you have finished this tutorial! + + +Another example +==================== +ETFA is commonly used as example for XPS measurements and calculations due to the extreme chemical shifts of its four different carbon atoms. [2] + +.. tip:: + + One can select the ETFA molecule from the `From Example` tab, and follow the same steps as above to run the XPS calculation for this molecule. + +Here is the result of the XPS calculation for the ETFA molecule. + +.. figure:: /_static/images/xps_etfa_dft.png + :align: center + +Here is the chemical shift from experiment. [2] + +.. figure:: /_static/images/xps_etfa_exp.jpg + :align: center + + +The calculated relative shifts align well with the trends observed in experimental data, underscoring the reliability of DFT calculations. +Although there are minor discrepancies in the absolute shift values, this is a recognized limitation stemming from the approximations in the exchange-correlation functional within DFT frameworks. [3] + +Questions +========= + +If you have any questions, please, do not hesitate to ask on the AiiDA discourse forum: https://aiida.discourse.group/. + + + +References +========== + +[1] V. Carravetta, *et al.*, *Chem. Phys.* 264, 175 (2001) https://doi.org/10.1016/S0301-0104(00)00396-7 + +[2] O. Travnikova, *et al.*, , *Relat. Phenom.* 185, 191 (2012) https://doi.org/10.1016/j.elspec.2012.05.009 + +[3] B.P. Klein, *et al.*, , *J. Phys. Condens. Matter* 33, 154005 (2021) https://doi.org/10.1088/1361-648X/abdf00 diff --git a/src/aiidalab_qe/app/structure/__init__.py b/src/aiidalab_qe/app/structure/__init__.py index 183899ab2..215c7479f 100644 --- a/src/aiidalab_qe/app/structure/__init__.py +++ b/src/aiidalab_qe/app/structure/__init__.py @@ -33,6 +33,8 @@ ("Gold (fcc)", file_path / "examples" / "Au.cif"), ("Cobalt (hcp)", file_path / "examples" / "Co.cif"), ("Lithium carbonate", file_path / "examples" / "Li2CO3.cif"), + ("Phenylacetylene molecule", file_path / "examples" / "Phenylacetylene.xyz"), + ("ETFA molecule", file_path / "examples" / "ETFA.xyz"), ] OptimadeQueryWidget.title = "OPTIMADE" # monkeypatch diff --git a/src/aiidalab_qe/app/structure/examples/ETFA.xyz b/src/aiidalab_qe/app/structure/examples/ETFA.xyz new file mode 100644 index 000000000..798a6382f --- /dev/null +++ b/src/aiidalab_qe/app/structure/examples/ETFA.xyz @@ -0,0 +1,16 @@ +14 +Lattice="12.30241974 0.0 0.0 0.0 14.640175719999998 0.0 0.0 0.0 15.46505195" Properties=species:S:1:pos:R:3 pbc="T T T" +C 6.21899370 8.39832269 5.55679337 +C 6.34421988 8.42035817 7.11190719 +C 6.34626493 7.04152238 9.04380147 +C 6.24209921 5.56976459 9.37839976 +F 6.36233512 9.64017572 5.05341708 +F 7.17124959 7.60031062 5.00000000 +F 5.00000000 7.92279567 5.17742669 +O 6.50098824 9.43606057 7.75095904 +O 6.24663056 7.16890953 7.58195838 +H 7.30241974 7.48055705 9.35298092 +H 5.53775445 7.63550949 9.48679926 +H 5.28506039 5.15498764 9.04352071 +H 7.05371060 5.00000000 8.91281931 +H 6.31087194 5.44012134 10.46505195 diff --git a/src/aiidalab_qe/app/structure/examples/Phenylacetylene.xyz b/src/aiidalab_qe/app/structure/examples/Phenylacetylene.xyz new file mode 100644 index 000000000..e6393c580 --- /dev/null +++ b/src/aiidalab_qe/app/structure/examples/Phenylacetylene.xyz @@ -0,0 +1,16 @@ +14 +Lattice="17.572400000000002 0.0 0.0 0.0 14.3161 0.0 0.0 0.0 10.0002" Properties=species:S:1:pos:R:3 pbc="T T T" +C 8.87580000 7.15810000 5.00010000 +C 8.17830000 8.36610000 5.00010000 +C 8.17830000 5.95000000 5.00010000 +C 6.78350000 8.36630000 5.00010000 +C 6.78340000 5.95020000 5.00010000 +C 6.08610000 7.15830000 5.00010000 +C 10.30480000 7.15810000 5.00010000 +C 11.50750000 7.15840000 5.00010000 +H 8.70750000 9.31610000 5.00000000 +H 8.70750000 5.00000000 5.00010000 +H 6.24030000 9.30680000 5.00010000 +H 6.24010000 5.00980000 5.00010000 +H 5.00000000 7.15830000 5.00020000 +H 12.57240000 7.15850000 5.00020000 diff --git a/src/aiidalab_qe/plugins/xps/result.py b/src/aiidalab_qe/plugins/xps/result.py index 1660df5ae..64faebc97 100644 --- a/src/aiidalab_qe/plugins/xps/result.py +++ b/src/aiidalab_qe/plugins/xps/result.py @@ -77,6 +77,7 @@ class Result(ResultPanel): def __init__(self, node=None, **kwargs): super().__init__(node=node, **kwargs) + self.experimental_data = None # Placeholder for experimental data def _update_view(self): import plotly.graph_objects as go @@ -101,17 +102,17 @@ def _update_view(self): value="chemical_shift", ) gamma = ipw.FloatSlider( - value=0.3, - min=0.1, - max=1, + value=0.1, + min=0.01, + max=0.5, description="Lorentzian profile ($\gamma$)", disabled=False, style={"description_width": "initial"}, ) sigma = ipw.FloatSlider( - value=0.3, - min=0.1, - max=1, + value=0.1, + min=0.01, + max=0.5, description="Gaussian profile ($\sigma$)", disabled=False, style={"description_width": "initial"}, @@ -122,6 +123,21 @@ def _update_view(self): disabled=False, style={"description_width": "initial"}, ) + # Create a description label + upload_description = ipw.HTML( + value="Upload Experimental Data (csv format):", + placeholder="", + description="", + ) + + # Create the upload button + upload_btn = ipw.FileUpload( + description="Choose File", + multiple=False, + ) + upload_container = ipw.VBox([upload_description, upload_btn]) + upload_btn.observe(self._handle_upload, names="value") + paras = ipw.HBox( children=[ gamma, @@ -145,21 +161,23 @@ def _update_view(self): layout=ipw.Layout(width="20%"), ) # init figure - g = go.FigureWidget( + self.g = go.FigureWidget( layout=go.Layout( title=dict(text="XPS"), barmode="overlay", ) ) - g.layout.xaxis.title = "Chemical shift (eV)" - g.layout.xaxis.autorange = "reversed" + self.g.layout.xaxis.title = "Chemical shift (eV)" + self.g.layout.xaxis.autorange = "reversed" # spectra = xps_spectra_broadening( chemical_shifts, equivalent_sites_data, gamma=gamma.value, sigma=sigma.value ) # only plot the selected spectrum for site, d in spectra[spectrum_select.value].items(): - g.add_scatter(x=d[0], y=d[1], fill="tozeroy", name=site.replace("_", " ")) + self.g.add_scatter( + x=d[0], y=d[1], fill="tozeroy", name=site.replace("_", " ") + ) def response(change): data = [] @@ -183,29 +201,29 @@ def response(change): } ) fill_type = "tozeroy" if fill.value else None - with g.batch_update(): - if len(g.data) == len(data): + with self.g.batch_update(): + if len(self.g.data) == len(data): for i in range(len(data)): - g.data[i].x = data[i]["x"] - g.data[i].y = data[i]["y"] - g.data[i].fill = fill_type - g.data[i].name = data[i]["site"].replace("_", " ") + self.g.data[i].x = data[i]["x"] + self.g.data[i].y = data[i]["y"] + self.g.data[i].fill = fill_type + self.g.data[i].name = data[i]["site"].replace("_", " ") else: - g.data = [] + self.g.data = [] for d in data: - g.add_scatter( + self.g.add_scatter( x=d["x"], y=d["y"], fill=fill_type, name=d["site"] ) - g.layout.barmode = "overlay" - g.layout.xaxis.title = xaxis + self.g.layout.barmode = "overlay" + self.g.layout.xaxis.title = xaxis + self.plot_experimental_data() spectra_type.observe(response, names="value") spectrum_select.observe(response, names="value") gamma.observe(response, names="value") sigma.observe(response, names="value") fill.observe(response, names="value") - correction_energies_table = self._get_corrections() self.children = [ spectra_type, ipw.HBox( @@ -217,30 +235,28 @@ def response(change): voigt_profile_help, paras, fill, - g, - correction_energies_table, + self.g, + upload_container, ] - def _get_corrections(self): - from aiida.orm.utils.serialize import deserialize_unsafe - - ui_parameters = self.node.base.extras.get("ui_parameters", {}) - ui_parameters = deserialize_unsafe(ui_parameters) - correction_energies = ui_parameters["xps"]["correction_energies"] - # create a table for the correction energies using ipywidgets - # These offsets mainly depend on chemical element and core level, and are determined by comparing the calculated core electron binding energy to the experimental one. - correction_energies_table = ipw.HTML( - """

Offset Energies (δ)

-
When comparing the calculated binding energies to the experimental data, these should be corrected by the given offset listed below.
- """ - ) - for core_level, value in correction_energies.items(): - element = core_level.split("_")[0] - if element not in self.spectrum_select_options: - continue - exp = -value["exp"] - sign = "" if exp < 0 else "+" - correction_energies_table.value += ( - f"" - ) - return correction_energies_table + def _handle_upload(self, change): + """Process the uploaded experimental data file.""" + import pandas as pd + + uploaded_file = next(iter(change.new.values())) + content = uploaded_file["content"] + content_str = content.decode("utf-8") + + from io import StringIO + + df = pd.read_csv(StringIO(content_str), header=None) + + self.experimental_data = df + self.plot_experimental_data() + + def plot_experimental_data(self): + """Plot the experimental data alongside the calculated data.""" + if self.experimental_data is not None: + x = self.experimental_data[0] + y = self.experimental_data[1] + self.g.add_scatter(x=x, y=y, mode="lines", name="Experimental Data") diff --git a/src/aiidalab_qe/plugins/xps/workchain.py b/src/aiidalab_qe/plugins/xps/workchain.py index 44b697a01..a58dce82c 100644 --- a/src/aiidalab_qe/plugins/xps/workchain.py +++ b/src/aiidalab_qe/plugins/xps/workchain.py @@ -40,7 +40,10 @@ def get_builder(codes, structure, parameters, **kwargs): if pseudo.label == f"{element}_gs" ][0], } - correction_energies[element] = all_correction_energies[label]["core"] + correction_energies[element] = ( + all_correction_energies[label]["core"] + - all_correction_energies[label]["exp"] + ) elements_list.append(element) # is_molecule_input = (
Core-levelValue (eV)
{core_level}{sign}{exp}