Use LOSC library in psi4¶
Introduction¶
psi4_losc
is a Python module that extends the psi4,
an open-source package for quantum chemistry calculations, to perform the
calculations of LOSC method. The psi4_losc
module interacts with the psi4
package via its Python interface. Using this psi4_losc
module along with
psi4
(the python model of psi4), you can
perform post-SCF-LOSC 1 2 and SCF-LOSC 3
calculations for aufbau/non-aufbau systems
with integer/fractional numbers of electrons.
Installing psi4¶
Warning
psi4_losc
module is developed based on psi4 version 1.3.2
.
To have stable performance, it would be better to install this version of
psi4. Other versions of psi4
are not tested.
To perform LOSC calculation with psi4_losc
in psi4
, you need to
install psi4
package first. If never use psi4 before, you are suggested to
just install the conda binary package of psi4:
conda install psi4=1.3.2 -c psi4
If you want to set up psi4 from the scratch, you can get more guidance for the installation of psi4 from its documentation.
Basic Guide¶
LOSC can significantly eliminate the delocalization error exisiting in conventional density functional approximations (DFAs), such as local density approximations (LDA), generalized gradient approximations (GGAs) and hybrid functionals like B3LYP. With minimal delocalization error from LOSC-DFA calculations, you can expect good description of physical properties, such as the ionization potentials (IPs), electron affinities (EAs), the photoemision spectra.
For most cases, you would be interested in performing LOSC calculations for aufbau systems (ground-state) with integer number of electrons. There are two ways to apply LOSC to an associated DFA:
the post-SCF-LOSC approach 1 2
Directly applying LOSC correction to the SCF-DFA calculations. In this approach, the total energy and orbital energies from the associated DFA are corrected. The electron density is not corrected from LOSC.
the SCF-LOSC approach 3
Using the LOSC effective Hamiltonian to correct the associated DFA Hamiltonian and perform the SCF calculation for LOSC-DFA. In this approach, the total energy, orbital energies and electron densities are all corrected.
To perform the calculation of LOSC via psi4_losc
, always import following:
import psi4
import psi4_losc
No matter doing SCF-LOSC or post-SCF-LOSC, an SCF calculation for the associated DFA should be performed in advance to generate the corresponding converged psi4 wavefunction object.
Note
Although psi4 supports calculations with point group symmetry, LOSC
does not support symmetry because the usage of localized orbitals. So you
need to turn off the symmetry in psi4 calculation (set symettr c1
for
the system).
Now, taking a simple molecule, a stretched H2 molecule, as an example. You first calculate the SCF-DFA in psi4.
# A stretched H2 molecule with 10A bond length with symmetry turned off.
mol = psi4.geometry("""
0 1
H 0 0 0
H 10 0 0
symmetry c1 # turn off symmetry
""")
# some basic setting in psi4.
psi4.set_options({'basis': '6-31g',
'guess': 'core',
'reference': 'uks'})
# Here we do a b3lyp calculation and let psi4 return the wfn object.
E_dfa, dfa_wfn = psi4.energy('b3lyp', return_wfn=True)
print(E_dfa)
# You would see E_dfa = -0.8923613725795544
post-SCF-LOSC for integer system with aufbau occupation¶
Following the aforementioned H2 example, you can do a post-SCF-LOSC calculation
via calling psi4_losc.post_scf_losc()
function.
There are two key arguments you need to specify: the first one
is the DFA functional type, and the second one the corresponding converged
DFA wavefunction object. psi4_losc.post_scf_losc()
will return two
variables: the corrected total energy and orbital energies.
# do post-SCF-LOSC-B3LYP calculation to obtain corrected total energy
# and the orbital energies.
E_losc, Orb_losc = psi4_losc.post_scf_losc(psi4_losc.B3LYP, dfa_wfn)
# You would see total energies:
# E_losc = -0.758073589993662
# E_losc - E_dfa = 0.13428778258589236
print(E_losc)
print(E_losc - E_dfa)
# You would see all LOSC corrected orbital energies in a.u. for
# both alpha and beta spin:
# [array([-6.98665404, -5.54668941, 24.04349262, 24.04349294]),
# array([-6.98665404, -5.54668941, 24.04349262, 24.04349294])]
print(Orb_losc)
SCF-LOSC for integer system with aufbau occupation¶
Following the aforementioned H2 example, you can do an SCF-LOSC calculation via
calling psi4_losc.scf_losc()
function. The input arguments are very similar
to psi4_losc.post_scf_losc()
function. But the return type is a psi4
wavefunction object.
# do SCF-LOSC-B3LYP calculation to obtain a LOSC-B3LYP wavefunction object.
losc_wfn = psi4_losc.scf_losc(psi4_losc.B3LYP, dfa_wfn)
# You would see total energies:
# E_losc = -0.7578046520485341
# E_losc - E_dfa = 0.1345567205310203
E_losc = losc_wfn.energy()
print(E_losc)
print(E_losc - E_dfa)
# You would see all LOSC corrected orbital energies in a.u. for alpha spin.
# Orb_losc = [-0.25675479 -0.20383709 0.91139381 0.91139408]
import numpy as np
Orb_losc = np.asarray(losc_wfn.epsilon_a())
print(Orb_losc)
Advanced¶
Configure LOSC calculations with options¶
You can configure the options for the LOSC calculations in psi4_losc
module.
These configurations include option settings related to LOSC curvatures,
localizations and so on. To configure the options, use the defined variable
psi4_losc.options
, which is an object of class
psi4_losc.losc_options.Options
. The basic methods of psi4_losc.options
are the following
Method |
Description |
---|---|
|
get a LOSC parameter. |
|
set a LOSC parameter. |
|
set a number of LOSC parameters. |
Following are some examples of configuring the LOSC options.
# set LOSC curvature version 2.
psi4_losc.options.set_param('curvature', 'version', 2)
# set LOSC localizer version 2.
psi4_losc.options.set_param('localizer', 'version', 2)
# set both curvature and localizer version in one call.
psi4_losc.options.set_params({
'localizer': {'version': 2},
'curvature': {'version': 2},
})
# get LOSC curvature version.
version = psi4_losc.options.get_param('curvature', 'version')
# get LOSC localizer version.
version = psi4_losc.options.get_param('localizer', 'version')
See all the supported options in this section.
Setting energy window for LOSC calculation¶
The LOSC corrections are constructed from a set of localized orbitals (LOs).
These LOs are obtained by a unitary transformation from a set of canonical
orbitals (COs) from the associated DFA. In default (for
psi4_losc.post_scf_losc
and psi4_losc.scf_losc
), all the COs (the same
number of basis sets) are involved
to perform the localization. As results, LOSC corrects all the orbital energies.
To simplify the calculation, you can only select a subset of COs to perform the
LOSC calculation, and you should expect this simplified approach to produce
similar results from the calculation will all COs localized.
psi4_losc
module accepts the selection of COs with an energy window
(in eV) setting, that is selecting all the COs whose orbital energies are
inside the window. To enable the window, you use the window key argument.
# do post-SCF-LOSC-B3LYP calculation with setting a window of -30 - 10 eV.
E_losc, Orb_losc = psi4_losc.post_scf_losc(psi4_losc.B3LYP, dfa_wfn,
window=[-30, 10])
# do SCF-LOSC-B3LYP calculation with setting a window of -30 - 10 eV.
losc_wfn = psi4_losc.scf_losc(psi4_losc.B3LYP, dfa_wfn, window=[-30, 10])
Note
Using window setting in the calculation of LOSC reduces the space of LOs and makes the calculation much faster. However, keep in mind that doing so would only produce corrections to orbital energies of the selected COs. The orbital energies of non-selected COs will not be touched.
To use the window setting, you should select most of the valence orbitals. The usual energy window is -30 - 10 eV. Using this window should give you very similar results in total energy, orbital energies and electron density to the ones calculated from all COs localized. Excluding the core orbitals to the localization is a reasonble choice. This is because: (1) core orbitals usually are already localized, thus, they do not contribute the total energy correction because of the integer occupation number (see the LOSC energy correction formular 1); (2) the core orbital energies are not as much interesting as the valence orbital energies.
Systems with fractional numbers of electrons or non-aufbau occupation¶
Psi4 mainly supports the calculations of systems with integer number of electrons and aufbau occupations. However, it would be interesting to calculate systems with fractional number of electrons (such as study of the delocalization error) or non-aufbau occupation (such as \(\Delta\)-SCF calculations).
psi4_losc.scf
module provides the extended SCF procedure to enable
calculations for systems with fractional numbers of electrons or non-aufbau
occupation. See this for more details.
References¶
psi4_losc Module¶
Full list of psi4 Python API used in psi4_losc module¶
Warning
The list may not be complete. The list is for the package
developers to show the interaction between psi4_losc
module and
psi4
Python API.
psi4.driver
-
OptionsState.restore()
driver.scf_wavefunction_factory()
-
-
-
-
-
-
-
API of psi4_losc module¶
-
psi4_losc.
B3LYP
= <py_losc.DFAInfo> object: {name: B3LYP, gga_x: 0.8, hf_x: 0.2}¶ The information of a density functional approximation.
-
psi4_losc.
BLYP
= <py_losc.DFAInfo> object: {name: BLYP, gga_x: 1.0, hf_x: 0.0}¶ The information of a density functional approximation.
-
psi4_losc.
PBE
= <py_losc.DFAInfo> object: {name: PBE, gga_x: 1.0, hf_x: 0.0}¶ The information of a density functional approximation.
-
psi4_losc.
PBE0
= <py_losc.DFAInfo> object: {name: PBE0, gga_x: 0.75, hf_x: 0.25}¶ The information of a density functional approximation.
-
psi4_losc.
GGA
= <py_losc.DFAInfo> object: {name: Pure GGA functional, gga_x: 1.0, hf_x: 0.0}¶ The information of a density functional approximation.
-
psi4_losc.
post_scf_losc
(dfa_info, dfa_wfn, orbital_energy_unit='eV', verbose=1, return_losc_data=False, window=None)[source]¶ Perform the post-SCF-LOSC calculation for the associated DFA.
- Parameters
dfa_info (py_losc.DFAInfo) – The information of the parent DFA, including the weights of exchanges.
dfa_wfn (psi4.core.HF) – The converged wavefunction from a parent DFA.
orbital_energy_unit ({'eV', 'au'}, default to 'eV') –
The units of orbital energies used to print in the output.
’au’ : atomic unit, hartree.
’eV’ : electronvolt.
verbose (int, default=1) – print level. 0 means print nothing. 1 means normal print level. A larger number means more details.
return_losc_data (bool, default=false.) – Return the data of LOSC or not.
window ([float, float], optional) – This variable specifies the orbital energy window in eV, which is used to select ALL the COs whose energies are in this window to do the LOSC localization. If not given, the default bahavior is to use ALL the COs to do localization.
- Returns
energy (float) – The total energy from the post-SCF-LOSC-DFA calculation.
eig ([np.array, …]) – All orbital energies (same number to basis set) from post-SCF-LOSC-DFA. For RKS, eig only includes the alpha orbital energies. For UKS, eig includes both alpha and beta orbital energies in order.
losc_data (dict) – losc_data will be returned, if return_losc_data is true. If returned, losc_data contains the data of LOSC calculations.
See also
py_losc.DFAInfo()
constructor of the DFA information class.
psi4.energy()
psi4 SCF calculator, which only supports calculations for integer systems with aufbau occupations.
psi4_losc.scf.scf()
psi4_losc SCF calculator (self-implemented), which supports calculations for integer/fractional systems with aufbau/non-aufbau occupations.
Notes
Ideally, we would like to extract the weights of exchanges from the psi4 superfunctional objects. However, it looks like psi4 does not support this functionality very well. So we require the user take the responsibility to construct the py_losc.DFAInfo object manually.
This function supports post-SCF-LOSC calculations for integer/fractional systems with aufbau/non-aufbau occupations:
If you want to calculate integer system with aufbau occupations, use psi4.energy to get the input dfa_wfn.
If you want to calculate integer/non-aufbau system or fractional system (either aufbau or non-aufbau occupation), use psi4_losc.scf.scf to get the input dfa_wfn.
-
psi4_losc.
scf_losc
(dfa_info, dfa_wfn, orbital_energy_unit='eV', verbose=1, window=None)[source]¶ Perform the SCF-LOSC (frozen-LO) calculation based on a DFA wavefunction.
This function use psi4.energy() to do the SCF procedure. This function only supports calculations for integer systems.
- Parameters
dfa_info (py_losc.DFAInfo) – The information of the parent DFA, including the weights of exchanges.
dfa_wfn (psi4.core.HF) – The converged wavefunction from a parent DFA.
orbital_energy_unit ({'eV', 'au'}, default='eV') –
The units of orbital energies used to print in the output.
’au’: atomic unit, hartree.
’eV’: electronvolt.
verbose (int, default=1) – The print level to control post_scf_losc and psi4.energy.
window ([float, float], optional) – The orbital energy window in eV to select COs to do localization. See post_scf_losc.
- Returns
wfn – The psi4 wavefunction object that has LOSC contribution included.
- Return type
psi4.core.RHF or psi4.core.UHF
See also
Notes
The psi4.energy function is used internally to drive the SCF procedure. So, ideally, this function supports all the types of SCF calculations that are supported by psi4, such as integer/aufbau systems, MOM calculations. However, these are not fully tested. But for normal SCF, meaning integer and aufbau system, this function works fine.
SCF-LOSC requires to use the DFA wfn as the initial guess. The psi4 guess setting for SCF will be ignored. To use DFA wfn as the initial guess, currently it is limited by psi4.energy interface in which directly passing the wfn as an argument is rejected. So, the only choice is to write the DFA wfn into scratch file, then let psi4 use guess read option to set up the initial guess. So, calling this function will generate a psi4 wavefunction scratch file! You need to take care of it at the exit.
This function only supports calculations for ground state integer systems.
psi4_losc.scf Module¶
Full list of psi4 Python API used in psi4_losc.scf module¶
Warning
The list may not be complete. The list is for the package
developers to show the interaction between psi4_losc.scf
module and
psi4
Python API.
psi4.driver
-
OptionsState.restore()
driver.scf_wavefunction_factory()
-
-
-
-
-
-
-
-
-
API of psi4_losc.scf module¶
Warning
This module is not fully tested. Use it with caution.
Extended SCF procedure built on psi4 package to perform SCF-DFA and SCF-LOSC-DFA calculations with compabilities for fractional systems.
-
psi4_losc.scf.
scf
(name, guess_wfn=None, occ={}, verbose=1, orbital_energy_unit='eV')[source]¶ Perform the SCF calculation for a conventional DFA. It supports the systems with fractional occupations.
- Parameters
name (str) – The name of the psi4 superfunctional, including DFT functional or HF method. The style is the same as psi4.energy function.
guess_wfn (psi4.core.RHF or psi4.core.UHF, default=None.) – The wavefunction used to set the initial guess of the SCF calculation. Setting this variable will copy the coefficient matrix from guess_wfn as the initial SCF guess. Setting this variable will ignore the psi4 guess setting. If you use this variable, make sure you are passing a reasonable guess wfn to the function. This function does not validate the input guess_wfn.
occ (dict, default={}) –
A dictionary that specifies the customized occupation number. The occupation is obtained based on the aufbau occupation number of the current molecule to give the final set of occupation numbers for the SCF calculation.
The structure of the occ is:
>>> { ... # for alpha spin ... "alpha": { ... 0: 0.5, # the 1st orbital (0-based) ... 'homo': 0.6, # HOMO ... 'lumo': 0.7, # LUMO ... }, ... # for beta spin ... "beta": { ... 2: 0.5, # the 3rd orbital (0-based) ... }, ... }
All the integer index for orbitals is 0-based. The str index for orbitals should be {‘homo’, ‘lumo’}, and are case-insensitive. All the occupation numbers should be in range of [0, 1].
verbose (int, default to 1) –
Print level to the psi4 output file.
0 means print nothing.
1 means normal print level.
A larger number means more details.
orbital_energy_unit ({'eV', 'au'}, default='eV') –
The units of orbital energies used to print in the output.
’au’: atomic unit, hartree.
’eV’: electronvolt.
- Returns
wfn – The SCF wavefunction. - Following members in the returned wfn object are updated:
wfn.S(): AO overlap matrix.
wfn.H(): Core matrix.
wfn.Ca(), wfn.Cb(): CO coefficient matrix.
wfn.Fa(), wfn.Fb(): Fock matrix.
wfn.Da(), wfn.Db(): density matrix.
wfn.Va(), wfn.Vb(): DFA (just the DFA, if it is LOSC-DFA) Vxc matrix.
wfn.epsilon_a(), wfn.epsilon_b()`: orbital energies.
wfn.energy(): total energy.
- Return type
psi4.core.RHF or psi4.core.UHF
Other members are not touched. Accessing and using other members in the returned wfn may be a undefined behavior.
The input argument occ is added as a new attribute to the returned wfn object as wfn.losc_data[‘occ’] = occ.
Notes
The option settings are handeld by psi4 SCF module.
There are double memory cost for all the updated matrices/vectors in the returned wfn (such as C, D, F matrices): one is for the allocation in the python code, the other one is for the allocation in psi4.core C++ code. At return, matrices/vectors will be copied from the python side to the psi4 C++ side code. This is limited by the psi4.core interface. We have to pay the price.
Examples
A simple example to run an SCF calculation for a system with fractional number of electrons and non-aufbau occupation.
import psi4 import psi4_losc.scf # Create H2 molecule with charge=0 and mult=1. # The aufbau occupation numbers for H2 are: # alpha occ: 1, 0, 0, 0, ... # beta occ: 1, 0, 0, 0, ... H2 = psi4.geometry(''' 0 1 H 0 0 0 H 1 0 0 ''') psi4.set_options({'basis': '3-21g'}) # A customized occupation setting that gives: # alpha occ: 0.5, 0, 0, 0, ... # beta occ: 1, 0, 0, 0.7, ... customized_occ = { "alpha": {"homo": 0.5}, # update HOMO occ to be 0.5 "beta": {"3": 0.7}, # update 4-th orbital occ to be 0.7 } # Do SCF-B3LYP calculation with the customized occupation. psi4_losc.scf.scf('b3lyp', occ=customized_occ)
-
psi4_losc.scf.
scf_losc
(dfa_info, dfa_wfn, orbital_energy_unit='eV', verbose=1)[source]¶ Perform the SCF-LOSC (frozen-LO) calculation based on a DFA wavefunction.
- Parameters
dfa_info (py_losc.DFAInfo) – The information of the parent DFA, including the weights of exchanges.
dfa_wfn (psi4.core.HF like psi4 object) – The converged wavefunction from a parent DFA.
orbital_energy_unit ({'eV', 'au'}, default='eV') –
The units of orbital energies used to print in the output.
’au’: atomic unit, hartree.
’eV’: electronvolt.
verbose (int, default=1) –
print level.
0 means print nothing.
1 means normal print level.
A larger number means more details.
- Returns
wfn – The wavefunction for the SCF-LOSC calculation.
Following members in the returned wfn object are calculated:
wfn.S(): AO overlap matrix.
wfn.H(): Core matrix.
wfn.Ca(), wfn.Cb(): CO coefficient matrix.
wfn.Fa(), wfn.Fb(): Fock matrix.
wfn.Da(), wfn.Db(): density matrix.
wfn.Va(), wfn.Vb(): DFA (just the DFA, if it is LOSC-DFA) Vxc matrix.
wfn.epsilon_a(), wfn.epsilon_b()`: orbital energies.
wfn.energy(): total energy.
Accessing and using other members in the returned wfn is a undefined behavior.
- Return type
psi4.core.RHF or psi4.core.UHF
See also
py_losc.DFAInfo()
constructor of the DFA information class.
psi4.energy()
return a DFA SCF wavefunction. psi4.energy() only supports calculations for integer systems with aufbau occupations.
scf()
return a DFA SCF wavefunction. psi4_losc.scf.scf() supports calculations for integer/fractional systems with aufbau/non-aufbau occupations.
Notes
The option settings are handled by psi4 SCF module.
There are double memory cost for all the updated matrices/vectors in the returned wfn (such as C, D, F matrices): one is for the allocation in the python code, the other one is for the allocation in psi4.core C++ code. At return, matrices/vectors will be copied from the python side to the psi4 C++ side code. This is limited by the psi4 core interface. We have to pay the price.
This function supports SCF-LOSC (frozen-LO) calculations for integer/fractional systems with aufbau/non-aufbau occupations:
If you want to calculate integer system with aufbau occupations, use psi4.energy or scf to generate the input dfa_wfn.
If you want to calculate integer system with non-aufbau occupation, or fractional system with aufbau/non-aufbau occupations, use scf to generate the input dfa_wfn.
Examples
A simple example to run an SCF-LOSC calculation for a system with fractional number of electrons and non-aufbau occupation.
import psi4 import psi4_losc # for psi4_losc.B3LYP import psi4_losc.scf # Create H2 molecule with charge=0 and mult=1. H2 = psi4.geometry(''' 0 1 H 0 0 0 H 1 0 0 ''') psi4.set_options({'basis': '3-21g'}) # Specify an non-aufbau and fractional occupation. customized_occ = { "alpha": {"homo": 0.5}, # update HOMO occ to be 0.5 "beta": {"3": 0.7}, # update 4-th orbital occ to be 0.7 } # First, do SCF-B3LYP calculation with the customized occupation. # alpha occ: 0.5, 0, 0, 0, ... # beta occ: 1, 0, 0, 0.7, ... dfa_wfn = psi4_losc.scf.scf('b3lyp', occ=customized_occ) # Second, do SCF-LOSC-B3LYP calculation with the customized occupation. losc_wfn = psi4_losc.scf.scf_losc(psi4_losc.B3LYP, dfa_wfn)
Configure LOSC options in psi4_losc¶
The configuration of LOSC calculation for psi4_losc module is controlled by psi4_losc.options variable. These configurations include the adjustments to the LOSC curvature, and localizations. The details are shown below.
-
psi4_losc.
options
= <psi4_losc.losc_options.Options object>¶ The Options class object that controls the LOSC calculations in psi4_losc module.
See also
-
class
psi4_losc.losc_options.
Options
[source]¶ Options class that controls the LOSC calculations in psi4_losc module.
The options are specified by three labels:
module: the module of the options.
key: the key of the option in module.
value: the value of the option in module.
All `module` and `key` values are str and case-insensitive at input. The case-insensitivity of `value` depends on the option.
The curvature module: module = “curvature”
Below lists all the valid key-value options for the curvature module.
- version{2, 1}, default=2.
The version of LOSC curvature.
- v1_parameter_tau: float, default=1.2378
The parameter \(\tau\) in LOSC curvature version 1. Require version=1 to use this setting.
- v2_parameter_tau: float, default=1.2378
The parameter \(\tau\) in LOSC curvature version 2. Require version=2 to use this setting.
- v2_parameter_zeta: float, default=8.0
The parameter \(\zeta\) in LOSC curvature version 2. Require version=2 to use this setting.
- df_molecular_fragment_size: int, default=2
The size in the number of atoms to split the module. This is used to achieve the block-wise construction of two-electron integral of curvature with density fitting.
- df_basis: str, default=”aug-cc-pvtz-ri”
The basis set used in density fitting for LOSC curvature. The name of basis set follows the rules in psi4. df_basis is case-insensitive.
The localizer module: module = “localizer”
Below lists all the valid key-value options for the localization module.
- version{2}, default=2.
The version of LOSC localizer.
- max_iter: int, default=1000
The maximum number of iterations in localization.
- convergence: float, default=1e-10
The convergence tolerance for the localization.
- random_permutation: bool, default=True
Use the random permutation for Jacob-Sweep algorithm in the localization or not.
- v2_parameter_gamma: float, default=0.707
The parameter \(\gamma\) in LOSC localizer version 2. Require version=2 to use this setting.
- v2_parameter_c: float, default=1000
The parameter \(C\) in LOSC localizer version 2. Require version=2 to use this setting.
-
get_param
(module, key)[source]¶ Get the value of an option.
- Parameters
module (str) – The module name of the option.
key (str) – The key name of the option in option module module.
- Returns
The value of the option.
- Return type
value
See also
Options()
all valid options and the return type of the value.
Literature¶
- 1(1,2,3)
Li, C.; Zheng, X.; Su, N. Q.; Yang, W. Localized Orbital Scaling Correction for System- atic Elimination of Delocalization Error in Density Functional Approximations. Natl. Sci. Rev. 2018, 5, 203−215. 203-215.
- 2(1,2)
Su, N. Q.; Mahler, A.; Yang, W. Preserving Symmetry and Degeneracy in the Localized Orbital Scaling Correction Approach. J. Phys. Chem. Lett. 2020, 11, 1528−1535.
- 3(1,2)
Mei, Y.; Chen, Z.; Yang, W. Self-Consistent Calculation of the Localized Orbital Scaling Correction for Correct Electron Densities and Energy-Level Alignments in Density Functional Theory. J. Phys. Chem. Lett. 2020, 11, 23, 10269–10277.