# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import cuest_scf

import os
filedir = os.path.dirname(os.path.realpath(__file__))
basisdir = os.path.join(filedir, '..', '..', 'data', 'gbs')

cuest_handle = cuest_scf.CuestHandle()

def run_rhf(
    *,
    primary_name,
    ):

    # Reasonable default - as coarse as 1.0E-10 yields high-precision results
    # Coarser can save some CoreDF memory and lower runtime
    threshold_pq = 1.0E-14

    molecule_filename = os.path.join(filedir, 'paxlovid.xyz')

    molecule = cuest_scf.Molecule.parse_from_xyz_file(molecule_filename)
    charge = 0

    auxiliary_name = 'def2-universal-jkfit'
    minao_name = 'minao-1'

    primary_filename = os.path.join(basisdir, '%s.gbs' % (primary_name))
    auxiliary_filename = os.path.join(basisdir, '%s.gbs' % (auxiliary_name))
    minao_filename = os.path.join(basisdir, '%s.gbs' % (minao_name))

    primary = cuest_scf.AOBasis.parse_from_gbs_file(primary_filename, molecule=molecule)
    auxiliary = cuest_scf.AOBasis.parse_from_gbs_file(auxiliary_filename, molecule=molecule)
    minao = cuest_scf.AOBasis.parse_from_gbs_file(minao_filename, molecule=molecule)

    rhf = cuest_scf.RHF(
        cuest_handle=cuest_handle,
        molecule=molecule,
        charge=charge,
        xc_functional_name='B3LYP1',
        primary=primary,
        auxiliary=auxiliary,
        minao=minao,
        primary_name=primary_name,
        auxiliary_name=auxiliary_name,
        minao_name=minao_name,
        threshold_pq=threshold_pq,
        g_convergence=1.0E-8, # Tighter for gradient check
        )

    rhf.solve()

    E = rhf.compute_energy()

    G = rhf.compute_gradient()

    return E, G

def test_b3lyp1():

    import numpy as np

    E2 = -1.7706120600238987E+03 # External code

    # External code we trust
    G2 = np.array([
        [ -0.0075339,   -0.0084374,    0.0100722,],
        [ -0.0129560,    0.0085460,    0.0005812,],
        [  0.0044460,    0.0022805,    0.0010485,],
        [ -0.0020780,   -0.0035621,   -0.0209380,],
        [  0.0085310,    0.0155217,    0.0060141,],
        [ -0.0078534,   -0.0143469,   -0.0203861,],
        [ -0.0039985,    0.0014855,    0.0145814,],
        [  0.0129959,   -0.0260229,   -0.0206831,],
        [  0.0072781,    0.0073819,    0.0204639,],
        [  0.0376327,   -0.0016436,   -0.0066890,],
        [  0.0007142,    0.0060887,   -0.0293362,],
        [  0.0132803,   -0.0171365,    0.0107301,],
        [ -0.0079049,    0.0167075,    0.0061632,],
        [ -0.0049003,   -0.0178073,    0.0007884,],
        [ -0.0050590,    0.0118507,    0.0137120,],
        [  0.0172510,   -0.0082200,   -0.0076180,],
        [  0.0083788,   -0.0033765,   -0.0019021,],
        [ -0.0035890,    0.0030032,    0.0026220,],
        [  0.0043617,   -0.0019763,   -0.0035190,],
        [ -0.0268906,    0.0019632,   -0.0069977,],
        [ -0.0186261,    0.0274140,    0.0066706,],
        [  0.0017896,   -0.0036556,    0.0113431,],
        [ -0.0113727,   -0.0156116,    0.0042445,],
        [ -0.0052975,    0.0008700,   -0.0042885,],
        [ -0.0055663,   -0.0056535,    0.0005495,],
        [  0.0050889,    0.0027446,   -0.0019316,],
        [ -0.0003030,    0.0040000,   -0.0006066,],
        [ -0.0031004,   -0.0063099,   -0.0115974,],
        [ -0.0066769,    0.0058210,    0.0058223,],
        [ -0.0351842,    0.0081270,    0.0334958,],
        [ -0.0159176,    0.0189273,   -0.0111036,],
        [  0.0025764,    0.0080068,    0.0079594,],
        [  0.0013813,   -0.0236045,   -0.0069523,],
        [  0.0318463,   -0.0078926,   -0.0303061,],
        [ -0.0009451,   -0.0115094,    0.0105833,],
        [  0.0008331,    0.0022270,    0.0000064,],
        [  0.0019510,    0.0046119,   -0.0002960,],
        [ -0.0029637,   -0.0033469,    0.0003911,],
        [  0.0040628,    0.0045491,    0.0016492,],
        [ -0.0008783,   -0.0055257,    0.0018305,],
        [ -0.0012048,    0.0001091,   -0.0009157,],
        [  0.0015754,   -0.0009992,   -0.0017813,],
        [  0.0009701,    0.0001342,    0.0012882,],
        [  0.0001547,    0.0015543,    0.0007688,],
        [ -0.0010241,   -0.0002313,   -0.0017558,],
        [  0.0012272,   -0.0006063,    0.0008811,],
        [  0.0015072,    0.0000462,   -0.0046693,],
        [ -0.0014937,   -0.0028328,    0.0087670,],
        [  0.0046354,    0.0016553,   -0.0012502,],
        [  0.0014366,    0.0047094,    0.0063387,],
        [ -0.0022968,    0.0000738,    0.0005287,],
        [  0.0039856,    0.0014382,    0.0012495,],
        [  0.0060950,    0.0005016,   -0.0011365,],
        [ -0.0008533,    0.0031655,   -0.0023299,],
        [ -0.0007804,    0.0005323,    0.0018985,],
        [ -0.0002041,   -0.0011274,   -0.0012298,],
        [ -0.0009716,   -0.0005681,   -0.0004076,],
        [  0.0029277,    0.0017253,   -0.0028413,],
        [  0.0021441,   -0.0008873,    0.0014046,],
        [  0.0032778,    0.0023101,    0.0021565,],
        [ -0.0008991,    0.0014957,    0.0035126,],
        [  0.0002489,    0.0004832,   -0.0023380,],
        [ -0.0015628,   -0.0015595,   -0.0014371,],
        [  0.0024810,   -0.0005167,   -0.0010865,],
        [  0.0024702,    0.0014808,    0.0032646,],
        [ -0.0018446,    0.0033328,    0.0073731,],
        [  0.0031945,    0.0080925,   -0.0024241,],
        ])

    E1, G1 = run_rhf(primary_name='def2-tzvp')
    G1 = G1.to_numpy()
    dE = abs(E1 - E2)
    dG = np.max(np.abs(G1 - G2))

    print(dE)
    print(dG)

    assert(dE < 1.0E-6)
    assert(dG < 1.0E-6)


# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import cuest_scf

import os
filedir = os.path.dirname(os.path.realpath(__file__))
basisdir = os.path.join(filedir, '..', '..', 'data', 'gbs')

cuest_handle = cuest_scf.CuestHandle()

def run_rhf(
    *,
    primary_name,
    ):

    # Reasonable default - as coarse as 1.0E-10 yields high-precision results
    # Coarser can save some CoreDF memory and lower runtime
    threshold_pq = 1.0E-14

    molecule_filename = os.path.join(filedir, 'paxlovid.xyz')

    molecule = cuest_scf.Molecule.parse_from_xyz_file(molecule_filename)
    charge = 0

    auxiliary_name = 'def2-universal-jkfit'
    minao_name = 'minao-1'

    primary_filename = os.path.join(basisdir, '%s.gbs' % (primary_name))
    auxiliary_filename = os.path.join(basisdir, '%s.gbs' % (auxiliary_name))
    minao_filename = os.path.join(basisdir, '%s.gbs' % (minao_name))

    primary = cuest_scf.AOBasis.parse_from_gbs_file(primary_filename, molecule=molecule)
    auxiliary = cuest_scf.AOBasis.parse_from_gbs_file(auxiliary_filename, molecule=molecule)
    minao = cuest_scf.AOBasis.parse_from_gbs_file(minao_filename, molecule=molecule)

    rhf = cuest_scf.RHF(
        cuest_handle=cuest_handle,
        molecule=molecule,
        charge=charge,
        xc_functional_name='B3LYP1',
        primary=primary,
        auxiliary=auxiliary,
        minao=minao,
        primary_name=primary_name,
        auxiliary_name=auxiliary_name,
        minao_name=minao_name,
        threshold_pq=threshold_pq,
        g_convergence=1.0E-8, # Tighter for gradient check
        )

    rhf.solve()

    E = rhf.compute_energy()

    G = rhf.compute_gradient()

    return E, G

def test_b3lyp1():

    import numpy as np

    E2 = -1.7706120600238987E+03 # External code

    # External code we trust
    G2 = np.array([
        [ -0.0075339,   -0.0084374,    0.0100722,],
        [ -0.0129560,    0.0085460,    0.0005812,],
        [  0.0044460,    0.0022805,    0.0010485,],
        [ -0.0020780,   -0.0035621,   -0.0209380,],
        [  0.0085310,    0.0155217,    0.0060141,],
        [ -0.0078534,   -0.0143469,   -0.0203861,],
        [ -0.0039985,    0.0014855,    0.0145814,],
        [  0.0129959,   -0.0260229,   -0.0206831,],
        [  0.0072781,    0.0073819,    0.0204639,],
        [  0.0376327,   -0.0016436,   -0.0066890,],
        [  0.0007142,    0.0060887,   -0.0293362,],


# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import cuest_scf

import os
filedir = os.path.dirname(os.path.realpath(__file__))
basisdir = os.path.join(filedir, '..', '..', 'data', 'gbs')

cuest_handle = cuest_scf.CuestHandle()

def run_rhf(
    *,
    primary_name,
    ):

    # Reasonable default - as coarse as 1.0E-10 yields high-precision results
    # Coarser can save some CoreDF memory and lower runtime
    threshold_pq = 1.0E-14

    molecule_filename = os.path.join(filedir, 'paxlovid.xyz')

    molecule = cuest_scf.Molecule.parse_from_xyz_file(molecule_filename)
    charge = 0

    auxiliary_name = 'def2-universal-jkfit'
    minao_name = 'minao-1'

    primary_filename = os.path.join(basisdir, '%s.gbs' % (primary_name))
    auxiliary_filename = os.path.join(basisdir, '%s.gbs' % (auxiliary_name))
    minao_filename = os.path.join(basisdir, '%s.gbs' % (minao_name))

    primary = cuest_scf.AOBasis.parse_from_gbs_file(primary_filename, molecule=molecule)
    auxiliary = cuest_scf.AOBasis.parse_from_gbs_file(auxiliary_filename, molecule=molecule)
    minao = cuest_scf.AOBasis.parse_from_gbs_file(minao_filename, molecule=molecule)

    rhf = cuest_scf.RHF(
        cuest_handle=cuest_handle,
        molecule=molecule,
        charge=charge,
        xc_functional_name='HF',
        primary=primary,
        auxiliary=auxiliary,
        minao=minao,
        primary_name=primary_name,
        auxiliary_name=auxiliary_name,
        minao_name=minao_name,
        threshold_pq=threshold_pq,
        g_convergence=1.0E-8, # Tighter for gradient check
        pcm_num_angular_points_per_hydrogen_atom=110,
        pcm_num_angular_points_per_heavy_atom=110,
        pcm_epsilon=80.0,
        )

    rhf.solve()

    E = rhf.compute_energy()

    G = rhf.compute_gradient()

    return E, G

def test_rhf():

    import numpy as np

    E2 = -1.7603457152124370E+03 # External code

    # External code we trust
    G2 = np.array([
       [ -0.0184988,  -0.0343845,   0.0273796],
       [ -0.0379316,   0.0278096,  -0.0081553],
       [  0.0283527,   0.0232373,   0.0250976],
       [  0.0053911,  -0.0031072,  -0.0507543],
       [  0.0309708,   0.0302571,   0.0317611],
       [ -0.0188022,  -0.0369833,  -0.0517220],
       [ -0.0065059,   0.0053585,   0.0335458],
       [  0.0092830,  -0.0376305,  -0.0282209],
       [  0.0138367,   0.0106862,   0.0273180],
       [  0.0589491,   0.0023250,  -0.0163916],
       [  0.0051612,   0.0106007,  -0.0539697],
       [  0.0462200,  -0.0602542,   0.0402997],
       [ -0.0056771,   0.0223969,   0.0126297],
       [ -0.0116759,  -0.0260531,   0.0048811],
       [ -0.0100212,   0.0187439,   0.0116294],
       [  0.0232106,  -0.0156805,  -0.0132428],
       [  0.0125720,  -0.0095002,  -0.0004171],
       [ -0.0033301,   0.0088493,   0.0037391],
       [  0.0083042,  -0.0051456,  -0.0084316],
       [ -0.0420767,   0.0002935,  -0.0006771],
       [ -0.0338492,   0.0334450,  -0.0067477],
       [  0.0067036,  -0.0032993,   0.0169881],
       [ -0.0116312,  -0.0162091,   0.0035779],
       [  0.0063908,  -0.0078621,   0.0088007],
       [ -0.0090302,  -0.0103273,  -0.0032672],
       [  0.0089824,  -0.0003942,  -0.0005032],
       [ -0.0032007,   0.0061253,   0.0036111],
       [ -0.0080791,  -0.0064064,  -0.0207911],
       [ -0.0036370,   0.0047253,   0.0060230],
       [ -0.0535494,   0.0176575,   0.0591499],
       [ -0.0513839,   0.0653972,  -0.0429843],
       [ -0.0002590,   0.0084638,   0.0102601],
       [ -0.0023809,  -0.0365184,   0.0054908],
       [  0.0449690,  -0.0199295,  -0.0597143],
       [  0.0010033,  -0.0134220,   0.0149047],
       [  0.0056473,  -0.0016361,  -0.0018693],
       [  0.0035635,   0.0023108,  -0.0060661],
       [ -0.0059505,  -0.0020846,   0.0092711],
       [  0.0074199,   0.0076297,  -0.0044440],
       [ -0.0093278,  -0.0030117,   0.0029823],
       [ -0.0060894,  -0.0011337,  -0.0026556],
       [  0.0045012,  -0.0037991,  -0.0054157],
       [  0.0018104,  -0.0030781,   0.0055600],
       [  0.0002117,   0.0076567,   0.0022764],
       [ -0.0059578,  -0.0001824,  -0.0026886],
       [  0.0016187,  -0.0026258,   0.0056940],
       [ -0.0055311,  -0.0032632,  -0.0117384],
       [ -0.0054090,  -0.0015283,   0.0209616],
       [  0.0040352,   0.0022232,  -0.0110271],
       [  0.0075102,   0.0111909,   0.0184628],
       [ -0.0043351,   0.0017731,   0.0051392],
       [  0.0059134,   0.0066259,  -0.0003254],
       [  0.0115309,  -0.0026361,   0.0025671],
       [  0.0006331,   0.0080836,  -0.0051774],
       [ -0.0047574,   0.0029485,   0.0051459],
       [ -0.0036562,  -0.0030800,  -0.0048458],
       [ -0.0029043,  -0.0040270,  -0.0048385],
       [  0.0049893,   0.0054211,  -0.0058340],
       [  0.0058859,  -0.0037815,   0.0025470],
       [  0.0091569,   0.0031425,   0.0043669],
       [ -0.0025388,   0.0048138,   0.0093207],
       [ -0.0073157,  -0.0014136,  -0.0032850],
       [ -0.0036892,  -0.0058502,  -0.0061210],
       [  0.0088250,  -0.0019554,  -0.0007071],
       [  0.0080319,   0.0033563,  -0.0012707],
       [ -0.0081279,   0.0046624,   0.0033626],
       [  0.0055249,   0.0199835,  -0.0004452],
        ])

    E1, G1 = run_rhf(primary_name='def2-tzvp')
    G1 = G1.to_numpy()
    dE = abs(E1 - E2)
    dG = np.max(np.abs(G1 - G2))

    print(dE)
    print(dG)

    assert(dE < 1.0E-6)
    assert(dG < 1.0E-6)


# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import cuest_scf

import os
filedir = os.path.dirname(os.path.realpath(__file__))
basisdir = os.path.join(filedir, '..', '..', 'data', 'gbs')

cuest_handle = cuest_scf.CuestHandle()

def run_rhf(
    *,
    primary_name,
    ):

    # Reasonable default - as coarse as 1.0E-10 yields high-precision results
    # Coarser can save some CoreDF memory and lower runtime
    threshold_pq = 1.0E-14

    molecule_filename = os.path.join(filedir, 'paxlovid.xyz')

    molecule = cuest_scf.Molecule.parse_from_xyz_file(molecule_filename)
    charge = 0

    auxiliary_name = 'def2-universal-jkfit'
    minao_name = 'minao-1'

    primary_filename = os.path.join(basisdir, '%s.gbs' % (primary_name))
    auxiliary_filename = os.path.join(basisdir, '%s.gbs' % (auxiliary_name))
    minao_filename = os.path.join(basisdir, '%s.gbs' % (minao_name))

    primary = cuest_scf.AOBasis.parse_from_gbs_file(primary_filename, molecule=molecule)
    auxiliary = cuest_scf.AOBasis.parse_from_gbs_file(auxiliary_filename, molecule=molecule)
    minao = cuest_scf.AOBasis.parse_from_gbs_file(minao_filename, molecule=molecule)

    rhf = cuest_scf.RHF(
        cuest_handle=cuest_handle,
        molecule=molecule,
        charge=charge,
        xc_functional_name='HF',
        primary=primary,
        auxiliary=auxiliary,
        minao=minao,
        primary_name=primary_name,
        auxiliary_name=auxiliary_name,
        minao_name=minao_name,
        threshold_pq=threshold_pq,
        g_convergence=1.0E-8, # Tighter for gradient check
        pcm_num_angular_points_per_hydrogen_atom=110,
        pcm_num_angular_points_per_heavy_atom=110,
        pcm_epsilon=80.0,
        )

    rhf.solve()

    E = rhf.compute_energy()

    G = rhf.compute_gradient()

    return E, G

def test_rhf():

    import numpy as np

    E2 = -1.7603457152124370E+03 # External code

    # External code we trust
    G2 = np.array([
       [ -0.0184988,  -0.0343845,   0.0273796],
       [ -0.0379316,   0.0278096,  -0.0081553],
       [  0.0283527,   0.0232373,   0.0250976],
       [  0.0053911,  -0.0031072,  -0.0507543],
       [  0.0309708,   0.0302571,   0.0317611],
       [ -0.0188022,  -0.0369833,  -0.0517220],
       [ -0.0065059,   0.0053585,   0.0335458],
       [  0.0092830,  -0.0376305,  -0.0282209],
       [  0.0138367,   0.0106862,   0.0273180],
       [  0.0589491,   0.0023250,  -0.0163916],
       [  0.0051612,   0.0106007,  -0.0539697],


# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import cuest_scf

import os
filedir = os.path.dirname(os.path.realpath(__file__))
basisdir = os.path.join(filedir, '..', '..', 'data', 'gbs')

cuest_handle = cuest_scf.CuestHandle()

def run_rhf(
    *,
    primary_name,
    ):

    # Reasonable default - as coarse as 1.0E-10 yields high-precision results
    # Coarser can save some CoreDF memory and lower runtime
    threshold_pq = 1.0E-14

    molecule_filename = os.path.join(filedir, 'paxlovid.xyz')

    molecule = cuest_scf.Molecule.parse_from_xyz_file(molecule_filename)
    charge = 0

    auxiliary_name = 'def2-universal-jkfit'
    minao_name = 'minao-1'

    primary_filename = os.path.join(basisdir, '%s.gbs' % (primary_name))
    auxiliary_filename = os.path.join(basisdir, '%s.gbs' % (auxiliary_name))
    minao_filename = os.path.join(basisdir, '%s.gbs' % (minao_name))

    primary = cuest_scf.AOBasis.parse_from_gbs_file(primary_filename, molecule=molecule)
    auxiliary = cuest_scf.AOBasis.parse_from_gbs_file(auxiliary_filename, molecule=molecule)
    minao = cuest_scf.AOBasis.parse_from_gbs_file(minao_filename, molecule=molecule)

    rhf = cuest_scf.RHF(
        cuest_handle=cuest_handle,
        molecule=molecule,
        charge=charge,
        primary=primary,
        xc_functional_name='HF',
        auxiliary=auxiliary,
        minao=minao,
        primary_name=primary_name,
        auxiliary_name=auxiliary_name,
        minao_name=minao_name,
        threshold_pq=threshold_pq,
        )

    rhf.solve()

    return rhf.scalars['Escf']

def test_rhf():

    reference_values = {
        'sto-3g'    : -1.7371712774897262E+03,
        'def2-svp'  : -1.7583044466471508E+03,
        'def2-tzvp' : -1.7602954939152182E+03,
        # def2-QZVP really wants an 80 GB A100 - disabling by default
        # 'def2-qzvp' : -1.7603826412465432E+03,
    }

    for primary_name, E2 in reference_values.items():

        E1 = run_rhf(primary_name=primary_name)
        dE = abs(E1 - E2)
        assert(dE < 1.0E-6)

        print(dE)


# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import cuest_scf

import os
filedir = os.path.dirname(os.path.realpath(__file__))
basisdir = os.path.join(filedir, '..', '..', 'data', 'gbs')

cuest_handle = cuest_scf.CuestHandle()

def run_rhf(
    *,
    primary_name,
    ):

    # Reasonable default - as coarse as 1.0E-10 yields high-precision results
    # Coarser can save some CoreDF memory and lower runtime
    threshold_pq = 1.0E-14

    molecule_filename = os.path.join(filedir, 'paxlovid.xyz')

    molecule = cuest_scf.Molecule.parse_from_xyz_file(molecule_filename)
    charge = 0

    auxiliary_name = 'def2-universal-jkfit'
    minao_name = 'minao-1'

    primary_filename = os.path.join(basisdir, '%s.gbs' % (primary_name))
    auxiliary_filename = os.path.join(basisdir, '%s.gbs' % (auxiliary_name))
    minao_filename = os.path.join(basisdir, '%s.gbs' % (minao_name))

    primary = cuest_scf.AOBasis.parse_from_gbs_file(primary_filename, molecule=molecule)
    auxiliary = cuest_scf.AOBasis.parse_from_gbs_file(auxiliary_filename, molecule=molecule)
    minao = cuest_scf.AOBasis.parse_from_gbs_file(minao_filename, molecule=molecule)

    rhf = cuest_scf.RHF(
        cuest_handle=cuest_handle,
        molecule=molecule,
        charge=charge,
        primary=primary,
        xc_functional_name='HF',
        auxiliary=auxiliary,
        minao=minao,
        primary_name=primary_name,
        auxiliary_name=auxiliary_name,
        minao_name=minao_name,
        threshold_pq=threshold_pq,
        )

    rhf.solve()

    return rhf.scalars['Escf']

def test_rhf():

    reference_values = {
        'sto-3g'    : -1.7371712774897262E+03,
        'def2-svp'  : -1.7583044466471508E+03,
        'def2-tzvp' : -1.7602954939152182E+03,
        # def2-QZVP really wants an 80 GB A100 - disabling by default
        # 'def2-qzvp' : -1.7603826412465432E+03,
    }

    for primary_name, E2 in reference_values.items():

        E1 = run_rhf(primary_name=primary_name)
        dE = abs(E1 - E2)
        assert(dE < 1.0E-6)

        print(dE)


    minao = cuest_scf.AOBasis.parse_from_gbs_file(minao_filename, molecule=molecule)

    rhf = cuest_scf.RHF(
        cuest_handle=cuest_handle,
        molecule=molecule,
        charge=charge,
        primary=primary,
        xc_functional_name='HF',
        auxiliary=auxiliary,
        minao=minao,
        primary_name=primary_name,
        auxiliary_name=auxiliary_name,
        minao_name=minao_name,
        threshold_pq=threshold_pq,
        )

    rhf.solve()

    return rhf.scalars['Escf']

def test_rhf():

    reference_values = {
        'sto-3g'    : -1.7371712774897262E+03,
        'def2-svp'  : -1.7583044466471508E+03,
        'def2-tzvp' : -1.7602954939152182E+03,
        # def2-QZVP really wants an 80 GB A100 - disabling by default
        # 'def2-qzvp' : -1.7603826412465432E+03,
    }

    for primary_name, E2 in reference_values.items():

        E1 = run_rhf(primary_name=primary_name)
        dE = abs(E1 - E2)
        assert(dE < 1.0E-6)

        print(dE)


# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import cuest_scf

import os
filedir = os.path.dirname(os.path.realpath(__file__))
basisdir = os.path.join(filedir, '..', '..', 'data', 'gbs')

cuest_handle = cuest_scf.CuestHandle()

def run_rhf(
    *,
    primary_name,
    ):

    # Reasonable default - as coarse as 1.0E-10 yields high-precision results
    # Coarser can save some CoreDF memory and lower runtime
    threshold_pq = 1.0E-14

    molecule_filename = os.path.join(filedir, 'paxlovid.xyz')

    molecule = cuest_scf.Molecule.parse_from_xyz_file(molecule_filename)
    charge = 0

    auxiliary_name = 'def2-universal-jkfit'
    minao_name = 'minao-1'

    primary_filename = os.path.join(basisdir, '%s.gbs' % (primary_name))
    auxiliary_filename = os.path.join(basisdir, '%s.gbs' % (auxiliary_name))
    minao_filename = os.path.join(basisdir, '%s.gbs' % (minao_name))

    primary = cuest_scf.AOBasis.parse_from_gbs_file(primary_filename, molecule=molecule)
    auxiliary = cuest_scf.AOBasis.parse_from_gbs_file(auxiliary_filename, molecule=molecule)
    minao = cuest_scf.AOBasis.parse_from_gbs_file(minao_filename, molecule=molecule)

    rhf = cuest_scf.RHF(
        cuest_handle=cuest_handle,
        molecule=molecule,
        charge=charge,
        xc_functional_name='B97M-V',
        primary=primary,
        auxiliary=auxiliary,
        minao=minao,
        primary_name=primary_name,
        auxiliary_name=auxiliary_name,
        minao_name=minao_name,
        threshold_pq=threshold_pq,
        g_convergence=1.0E-8, # Tighter for gradient check
        )

    rhf.solve()

    E = rhf.compute_energy()

    G = rhf.compute_gradient()

    return E, G

def test_b97mv():

    import numpy as np

    E2 = -1.7704492618735078E+03 # External code

    # External code we trust
    G2 = np.array([
        [-0.0106596,   -0.0156799,    0.0145414,],
        [-0.0197646,    0.0137893,   -0.0018952,],
        [ 0.0088908,    0.0068987,    0.0064756,],
        [ 0.0006182,   -0.0042745,   -0.0271809,],
        [ 0.0114778,    0.0174686,    0.0108210,],
        [-0.0112086,   -0.0174245,   -0.0243939,],
        [-0.0049056,    0.0039996,    0.0214965,],
        [ 0.0131420,   -0.0238833,   -0.0197356,],
        [ 0.0069684,    0.0060010,    0.0215369,],
        [ 0.0360612,   -0.0017413,   -0.0056847,],
        [ 0.0010948,    0.0105423,   -0.0295461,],
        [ 0.0130757,   -0.0167972,    0.0104620,],
        [-0.0056738,    0.0133255,    0.0062157,],
        [-0.0053860,   -0.0188959,    0.0008743,],
        [-0.0062759,    0.0098235,    0.0106342,],
        [ 0.0172062,   -0.0082411,   -0.0046346,],
        [ 0.0057534,   -0.0003341,   -0.0062477,],
        [-0.0046297,   -0.0011964,    0.0020192,],
        [ 0.0010280,   -0.0005629,   -0.0000546,],
        [-0.0255061,    0.0011339,   -0.0057105,],
        [-0.0185690,    0.0252412,    0.0065229,],
        [-0.0000765,   -0.0033603,    0.0102771,],
        [-0.0117633,   -0.0154561,    0.0034439,],
        [-0.0055208,    0.0012165,   -0.0067281,],
        [-0.0032842,   -0.0025623,    0.0030395,],
        [ 0.0018487,    0.0056124,   -0.0044792,],
        [ 0.0033705,    0.0025224,   -0.0046141,],
        [-0.0008305,   -0.0063373,   -0.0079543,],
        [-0.0104291,    0.0039389,    0.0059832,],
        [-0.0324778,    0.0085888,    0.0316638,],
        [-0.0110840,    0.0127289,   -0.0076844,],
        [ 0.0049426,    0.0048814,    0.0045765,],
        [ 0.0016614,   -0.0221686,   -0.0066648,],
        [ 0.0301663,   -0.0081270,   -0.0294829,],
        [-0.0003956,   -0.0099346,    0.0050353,],
        [ 0.0021097,    0.0007955,    0.0002088,],
        [ 0.0021282,    0.0038946,   -0.0025163,],
        [-0.0026446,   -0.0039521,    0.0011721,],
        [ 0.0046033,    0.0046392,   -0.0000268,],
        [-0.0021881,   -0.0055075,    0.0015075,],
        [-0.0036517,   -0.0008585,   -0.0017543,],
        [ 0.0028364,   -0.0026402,   -0.0033413,],
        [ 0.0013393,   -0.0015564,    0.0034227,],
        [-0.0000501,    0.0026277,    0.0016033,],
        [-0.0034870,   -0.0006808,   -0.0022617,],
        [ 0.0014405,   -0.0017353,    0.0032705,],
        [ 0.0013591,   -0.0009285,   -0.0052868,],
        [-0.0025191,   -0.0031309,    0.0117281,],
        [ 0.0056894,    0.0010170,   -0.0023023,],
        [ 0.0019322,    0.0061851,    0.0088318,],
        [-0.0031094,    0.0008149,    0.0022319,],
        [ 0.0049323,    0.0034812,    0.0009759,],
        [ 0.0072225,    0.0006121,   -0.0003196,],
        [-0.0002752,    0.0054103,   -0.0035896,],
        [-0.0022374,    0.0017045,    0.0028161,],
        [-0.0016208,   -0.0018633,   -0.0027254,],
        [-0.0016862,   -0.0014531,   -0.0019444,],
        [ 0.0041234,    0.0034255,   -0.0042112,],
        [ 0.0031555,   -0.0010435,    0.0009888,],
        [ 0.0051177,    0.0025433,    0.0026988,],
        [-0.0014040,    0.0018589,    0.0051271,],
        [ 0.0001667,    0.0007933,   -0.0018817,],
        [-0.0024006,   -0.0026783,   -0.0028726,],
        [ 0.0044775,   -0.0008214,   -0.0012893,],
        [ 0.0047634,    0.0022124,    0.0021387,],
        [-0.0030317,    0.0038995,    0.0063474,],
        [ 0.0040435,    0.0121992,   -0.0016736,],
        ])

    E1, G1 = run_rhf(primary_name='def2-tzvp')
    G1 = G1.to_numpy()
    dE = abs(E1 - E2)
    dG = np.max(np.abs(G1 - G2))

    print(dE)
    print(dG)

    assert(dE < 1.0E-6)
    assert(dG < 1.0E-6)


# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import cuest_scf

import os
filedir = os.path.dirname(os.path.realpath(__file__))
basisdir = os.path.join(filedir, '..', '..', 'data', 'gbs')

cuest_handle = cuest_scf.CuestHandle()

def run_rhf(
    *,
    primary_name,
    ):

    # Reasonable default - as coarse as 1.0E-10 yields high-precision results
    # Coarser can save some CoreDF memory and lower runtime
    threshold_pq = 1.0E-14

    molecule_filename = os.path.join(filedir, 'paxlovid.xyz')

    molecule = cuest_scf.Molecule.parse_from_xyz_file(molecule_filename)
    charge = 0

    auxiliary_name = 'def2-universal-jkfit'
    minao_name = 'minao-1'

    primary_filename = os.path.join(basisdir, '%s.gbs' % (primary_name))
    auxiliary_filename = os.path.join(basisdir, '%s.gbs' % (auxiliary_name))
    minao_filename = os.path.join(basisdir, '%s.gbs' % (minao_name))

    primary = cuest_scf.AOBasis.parse_from_gbs_file(primary_filename, molecule=molecule)
    auxiliary = cuest_scf.AOBasis.parse_from_gbs_file(auxiliary_filename, molecule=molecule)
    minao = cuest_scf.AOBasis.parse_from_gbs_file(minao_filename, molecule=molecule)

    rhf = cuest_scf.RHF(
        cuest_handle=cuest_handle,
        molecule=molecule,
        charge=charge,
        xc_functional_name='B97M-V',
        primary=primary,
        auxiliary=auxiliary,
        minao=minao,
        primary_name=primary_name,
        auxiliary_name=auxiliary_name,
        minao_name=minao_name,
        threshold_pq=threshold_pq,
        g_convergence=1.0E-8, # Tighter for gradient check
        )

    rhf.solve()

    E = rhf.compute_energy()

    G = rhf.compute_gradient()

    return E, G

def test_b97mv():

    import numpy as np

    E2 = -1.7704492618735078E+03 # External code

    # External code we trust
    G2 = np.array([
        [-0.0106596,   -0.0156799,    0.0145414,],
        [-0.0197646,    0.0137893,   -0.0018952,],
        [ 0.0088908,    0.0068987,    0.0064756,],
        [ 0.0006182,   -0.0042745,   -0.0271809,],
        [ 0.0114778,    0.0174686,    0.0108210,],
        [-0.0112086,   -0.0174245,   -0.0243939,],
        [-0.0049056,    0.0039996,    0.0214965,],
        [ 0.0131420,   -0.0238833,   -0.0197356,],
        [ 0.0069684,    0.0060010,    0.0215369,],
        [ 0.0360612,   -0.0017413,   -0.0056847,],
        [ 0.0010948,    0.0105423,   -0.0295461,],


# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import cuest_scf

import os
filedir = os.path.dirname(os.path.realpath(__file__))
basisdir = os.path.join(filedir, '..', '..', 'data', 'gbs')

cuest_handle = cuest_scf.CuestHandle()

def run_rhf(
    *,
    primary_name,
    ):

    # Reasonable default - as coarse as 1.0E-10 yields high-precision results
    # Coarser can save some CoreDF memory and lower runtime
    threshold_pq = 1.0E-14

    molecule_filename = os.path.join(filedir, 'paxlovid.xyz')

    molecule = cuest_scf.Molecule.parse_from_xyz_file(molecule_filename)
    charge = 0

    auxiliary_name = 'def2-universal-jkfit'
    minao_name = 'minao-1'

    primary_filename = os.path.join(basisdir, '%s.gbs' % (primary_name))
    auxiliary_filename = os.path.join(basisdir, '%s.gbs' % (auxiliary_name))
    minao_filename = os.path.join(basisdir, '%s.gbs' % (minao_name))

    primary = cuest_scf.AOBasis.parse_from_gbs_file(primary_filename, molecule=molecule)
    auxiliary = cuest_scf.AOBasis.parse_from_gbs_file(auxiliary_filename, molecule=molecule)
    minao = cuest_scf.AOBasis.parse_from_gbs_file(minao_filename, molecule=molecule)

    rhf = cuest_scf.RHF(
        cuest_handle=cuest_handle,
        molecule=molecule,
        charge=charge,
        xc_functional_name='HF',
        primary=primary,
        auxiliary=auxiliary,
        minao=minao,
        primary_name=primary_name,
        auxiliary_name=auxiliary_name,
        minao_name=minao_name,
        threshold_pq=threshold_pq,
        g_convergence=1.0E-8, # Tighter for gradient check
        )

    rhf.solve()

    E = rhf.compute_energy()

    G = rhf.compute_gradient()

    return E, G

def test_rhf():

    import numpy as np

    E2 = -1.7602954939152182E+03 # Internal reference
    E3 = -1.7602954939140413E+03 # External code

    # External code we trust
    G2 = np.array([
       [ -0.0208597,   -0.0356780,    0.0281067],
       [ -0.0408624,    0.0282052,   -0.0084045],
       [  0.0253546,    0.0218243,    0.0242479],
       [  0.0090637,   -0.0044771,   -0.0618983],
       [  0.0344967,    0.0323332,    0.0372294],
       [ -0.0262053,   -0.0418410,   -0.0582010],
       [ -0.0098429,    0.0126417,    0.0546539],
       [  0.0112808,   -0.0335221,   -0.0275379],
       [  0.0127417,    0.0098751,    0.0221619],
       [  0.0508128,   -0.0012480,   -0.0168725],
       [  0.0014983,    0.0048573,   -0.0411949],
       [  0.0477820,   -0.0621228,    0.0412027],
       [ -0.0050371,    0.0225184,    0.0126377],
       [ -0.0116681,   -0.0265052,    0.0049367],
       [ -0.0106237,    0.0190111,    0.0112511],
       [  0.0227971,   -0.0156068,   -0.0105452],
       [  0.0121413,   -0.0101201,   -0.0016905],
       [ -0.0030149,    0.0082268,    0.0034705],
       [  0.0085837,   -0.0041588,   -0.0078855],
       [ -0.0434603,    0.0007589,    0.0141763],
       [ -0.0390102,    0.0286030,   -0.0122543],
       [  0.0058979,   -0.0037244,    0.0162883],
       [ -0.0113994,   -0.0164766,    0.0029828],
       [  0.0019021,   -0.0053864,    0.0070392],
       [ -0.0095757,   -0.0090207,   -0.0020937],
       [  0.0083523,    0.0004829,   -0.0004781],
       [ -0.0029454,    0.0059150,    0.0021849],
       [ -0.0057341,   -0.0055926,   -0.0192969],
       [ -0.0032889,    0.0036989,    0.0063679],
       [ -0.0385153,    0.0253514,    0.0658022],
       [ -0.0526046,    0.0668111,   -0.0438756],
       [  0.0006937,    0.0094747,    0.0103117],
       [  0.0037456,   -0.0390302,   -0.0276055],
       [  0.0532312,   -0.0174919,   -0.0588797],
       [  0.0006649,   -0.0157475,    0.0113979],
       [  0.0055180,   -0.0021850,   -0.0012850],
       [  0.0032198,    0.0022361,   -0.0062879],
       [ -0.0051346,   -0.0025929,    0.0084412],
       [  0.0064862,    0.0081799,   -0.0042450],
       [ -0.0085572,   -0.0029933,    0.0030293],
       [ -0.0064755,   -0.0008781,   -0.0026522],
       [  0.0047295,   -0.0035292,   -0.0056022],
       [  0.0018127,   -0.0028931,    0.0059839],
       [  0.0005288,    0.0079685,    0.0022861],
       [ -0.0063595,   -0.0008092,   -0.0032737],
       [  0.0018779,   -0.0028706,    0.0056260],
       [ -0.0043175,   -0.0031250,   -0.0122338],
       [ -0.0060574,   -0.0030624,    0.0232496],
       [  0.0064236,    0.0022386,   -0.0104025],
       [  0.0067458,    0.0127649,    0.0191777],
       [ -0.0055109,    0.0014812,    0.0051545],
       [  0.0061676,    0.0063676,   -0.0008665],
       [  0.0125817,   -0.0030474,    0.0010893],
       [  0.0012520,    0.0080011,   -0.0052251],
       [ -0.0048426,    0.0027812,    0.0058644],
       [ -0.0031752,   -0.0037730,   -0.0049010],
       [ -0.0037059,   -0.0042413,   -0.0042426],
       [  0.0051362,    0.0057527,   -0.0052669],
       [  0.0063753,   -0.0033135,    0.0032430],
       [  0.0093046,    0.0031410,    0.0034192],
       [ -0.0035042,    0.0025311,    0.0095088],
       [ -0.0073677,   -0.0005880,   -0.0040605],
       [ -0.0038629,   -0.0053111,   -0.0056777],
       [  0.0082404,   -0.0021031,   -0.0003102],
       [  0.0077767,    0.0035413,   -0.0001143],
       [ -0.0078661,    0.0050328,    0.0046209],
       [  0.0061681,    0.0224595,   -0.0017824],
        ])

    E1, G1 = run_rhf(primary_name='def2-tzvp')
    G1 = G1.to_numpy()
    dE = abs(E1 - E2)
    dG = np.max(np.abs(G1 - G2))

    print(dE)
    print(dG)

    assert(dE < 1.0E-6)
    assert(dG < 1.0E-6)


# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import cuest_scf

import os
filedir = os.path.dirname(os.path.realpath(__file__))
basisdir = os.path.join(filedir, '..', '..', 'data', 'gbs')

cuest_handle = cuest_scf.CuestHandle()

def run_rhf(
    *,
    primary_name,
    ):

    # Reasonable default - as coarse as 1.0E-10 yields high-precision results
    # Coarser can save some CoreDF memory and lower runtime
    threshold_pq = 1.0E-14

    molecule_filename = os.path.join(filedir, 'paxlovid.xyz')

    molecule = cuest_scf.Molecule.parse_from_xyz_file(molecule_filename)
    charge = 0

    auxiliary_name = 'def2-universal-jkfit'
    minao_name = 'minao-1'

    primary_filename = os.path.join(basisdir, '%s.gbs' % (primary_name))
    auxiliary_filename = os.path.join(basisdir, '%s.gbs' % (auxiliary_name))
    minao_filename = os.path.join(basisdir, '%s.gbs' % (minao_name))

    primary = cuest_scf.AOBasis.parse_from_gbs_file(primary_filename, molecule=molecule)
    auxiliary = cuest_scf.AOBasis.parse_from_gbs_file(auxiliary_filename, molecule=molecule)
    minao = cuest_scf.AOBasis.parse_from_gbs_file(minao_filename, molecule=molecule)

    rhf = cuest_scf.RHF(
        cuest_handle=cuest_handle,
        molecule=molecule,
        charge=charge,
        xc_functional_name='HF',
        primary=primary,
        auxiliary=auxiliary,
        minao=minao,
        primary_name=primary_name,
        auxiliary_name=auxiliary_name,
        minao_name=minao_name,
        threshold_pq=threshold_pq,
        g_convergence=1.0E-8, # Tighter for gradient check
        )

    rhf.solve()

    E = rhf.compute_energy()

    G = rhf.compute_gradient()

    return E, G

def test_rhf():

    import numpy as np

    E2 = -1.7602954939152182E+03 # Internal reference
    E3 = -1.7602954939140413E+03 # External code

    # External code we trust
    G2 = np.array([
       [ -0.0208597,   -0.0356780,    0.0281067],
       [ -0.0408624,    0.0282052,   -0.0084045],
       [  0.0253546,    0.0218243,    0.0242479],
       [  0.0090637,   -0.0044771,   -0.0618983],
       [  0.0344967,    0.0323332,    0.0372294],
       [ -0.0262053,   -0.0418410,   -0.0582010],
       [ -0.0098429,    0.0126417,    0.0546539],
       [  0.0112808,   -0.0335221,   -0.0275379],
       [  0.0127417,    0.0098751,    0.0221619],
       [  0.0508128,   -0.0012480,   -0.0168725],


# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import cuest_scf
import numpy as np
import os
filedir = os.path.dirname(os.path.realpath(__file__))
basisdir = os.path.join(filedir, '..', '..', 'data', 'gbs')

cuest_handle = cuest_scf.CuestHandle()

def run_rhf(
    *,
    primary_name,
    xc_grid_level,
    xc_grid_family,
    ):

    # Reasonable default - as coarse as 1.0E-10 yields high-precision results
    # Coarser can save some CoreDF memory and lower runtime
    threshold_pq = 1.0E-14

    molecule = cuest_scf.Molecule.parse_from_xyz_string("""
        C     -2.779381    1.358796   -0.017634
        N     -1.370791    1.405167   -0.366529
        F     -2.968627    1.790547    1.252788
        H     -3.367136    1.987227   -0.691709
        Cl    -3.330473   -0.311805   -0.186740
        O     -0.662015    0.806738    0.752014
        Br     0.836087    1.898704    1.267596
        H     -1.224487    0.696240   -1.080917""")
    charge = 0

    auxiliary_name = 'def2-universal-jkfit'
    minao_name = 'minao-1'

    primary_filename = os.path.join(basisdir, '%s.gbs' % (primary_name))
    auxiliary_filename = os.path.join(basisdir, '%s.gbs' % (auxiliary_name))
    minao_filename = os.path.join(basisdir, '%s.gbs' % (minao_name))

    primary = cuest_scf.AOBasis.parse_from_gbs_file(primary_filename, molecule=molecule)
    auxiliary = cuest_scf.AOBasis.parse_from_gbs_file(auxiliary_filename, molecule=molecule)
    minao = cuest_scf.AOBasis.parse_from_gbs_file(minao_filename, molecule=molecule)

    rhf = cuest_scf.RHF(
        cuest_handle=cuest_handle,
        molecule=molecule,
        charge=charge,
        xc_functional_name='BLYP',
        primary=primary,
        auxiliary=auxiliary,
        minao=minao,
        primary_name=primary_name,
        auxiliary_name=auxiliary_name,
        minao_name=minao_name,
        threshold_pq=threshold_pq,
        g_convergence=1.0E-8, # Tighter for gradient check
        xc_grid_level=xc_grid_level,
        xc_grid_family=xc_grid_family,
        )

    rhf.solve()

    E = rhf.compute_energy()

    G = rhf.compute_gradient()

    return E, G

def test_blyp1_grid1():

    import numpy as np

    E2 = -3.3026700806219742E+03 # External code

    # External code we trust
    G2 = np.array([
        [-0.0090490,  -0.0104565,  -0.0117094],
        [-0.0100463,  -0.0018479,  -0.0243732],
        [-0.0001783,  -0.0048707,   0.0004488],
        [ 0.0044712,  -0.0040387,   0.0094299],
        [ 0.0207582,   0.0281712,   0.0004648],
        [ 0.0022800,  -0.0044285,   0.0125760],
        [-0.0020055,  -0.0027708,  -0.0023172],
        [-0.0062302,   0.0002419,   0.0154801],
        ])

    E1, G1 = run_rhf(
        primary_name='def2-svp',
        xc_grid_family='GRID',
        xc_grid_level=1,
        )
    G1 = G1.to_numpy()
    dE = abs(E1 - E2)
    dG = np.max(np.abs(G1 - G2))

    print(dE)
    print(dG)

    assert(dE < 1.0E-6)
    assert(dG < 1.0E-6)

def test_blyp1_grid2():

    import numpy as np

    E2 = -3.3026741241897430E+03 # External code

    # External code we trust
    G2 = np.array([
        [-0.0086464,  -0.0111337,  -0.0115950],
        [-0.0121519,  -0.0020085,  -0.0230772],
        [ 0.0004133,  -0.0041646,  -0.0006530],
        [ 0.0037753,  -0.0052239,   0.0098708],
        [ 0.0207992,   0.0285744,   0.0007199],
        [ 0.0048984,  -0.0025692,   0.0129005],
        [-0.0053030,  -0.0049576,  -0.0035499],
        [-0.0037849,   0.0014830,   0.0153839],
        ])

    E1, G1 = run_rhf(
        primary_name='def2-svp',
        xc_grid_family='GRID',
        xc_grid_level=2,
        )
    G1 = G1.to_numpy()
    dE = abs(E1 - E2)
    dG = np.max(np.abs(G1 - G2))

    print(dE)
    print(dG)

    assert(dE < 1.0E-6)
    assert(dG < 1.0E-6)

def test_blyp1_grid3():

    import numpy as np

    E2 = -3.3026735923753740E+03 # External code

    # External code we trust
    G2 = np.array([
        [-0.0085272,  -0.0110104,  -0.0109514],
        [-0.0123139,  -0.0019728,  -0.0230565],
        [ 0.0004201,  -0.0042886,  -0.0013846],
        [ 0.0038463,  -0.0051010,   0.0097640],
        [ 0.0207357,   0.0284409,   0.0009540],
        [ 0.0034311,  -0.0037979,   0.0124144],
        [-0.0036314,  -0.0037463,  -0.0029304],
        [-0.0039608,   0.0014761,   0.0151904],
        ])

    E1, G1 = run_rhf(
        primary_name='def2-svp',
        xc_grid_family='GRID',
        xc_grid_level=3,
        )
    G1 = G1.to_numpy()
    dE = abs(E1 - E2)
    dG = np.max(np.abs(G1 - G2))

    print(dE)
    print(dG)

    assert(dE < 1.0E-6)
    assert(dG < 1.0E-6)

def test_blyp1_grid4():

    import numpy as np

    E2 = -3.3026735859656069E+03 # External code

    # External code we trust
    G2 = np.array([
        [-0.0084841,  -0.0110430,  -0.0108776],
        [-0.0123528,  -0.0020159,  -0.0231832],
        [ 0.0004196,  -0.0044269,  -0.0013943],
        [ 0.0037538,  -0.0050221,   0.0096938],
        [ 0.0207927,   0.0285202,   0.0009296],
        [ 0.0042107,  -0.0032913,   0.0127780],
        [-0.0043379,  -0.0042486,  -0.0032213],
        [-0.0040020,   0.0015277,   0.0152750],
        ])

    E1, G1 = run_rhf(
        primary_name='def2-svp',
        xc_grid_family='GRID',
        xc_grid_level=4,
        )
    G1 = G1.to_numpy()
    dE = abs(E1 - E2)
    dG = np.max(np.abs(G1 - G2))

    print(dE)
    print(dG)

    assert(dE < 1.0E-6)
    assert(dG < 1.0E-6)

def test_blyp1_grid5():

    import numpy as np

    E2 = -3.3026736646568511E+03 # External code

    # External code we trust
    G2 = np.array([
        [-0.0084501,  -0.0109916,  -0.0107023],
        [-0.0122833,  -0.0020737,  -0.0231218],
        [ 0.0004239,  -0.0044861,  -0.0015387],
        [ 0.0037366,  -0.0049973,   0.0096706],
        [ 0.0207652,   0.0285071,   0.0009151],
        [ 0.0038299,  -0.0034625,   0.0125407],
        [-0.0040454,  -0.0040482,  -0.0030641],
        [-0.0039768,   0.0015524,   0.0153005],
        ])

    E1, G1 = run_rhf(
        primary_name='def2-svp',
        xc_grid_family='GRID',
        xc_grid_level=5,
        )
    G1 = G1.to_numpy()
    dE = abs(E1 - E2)
    dG = np.max(np.abs(G1 - G2))

    print(dE)
    print(dG)

    assert(dE < 1.0E-6)
    assert(dG < 1.0E-6)

def test_blyp1_sg0():

    import numpy as np

    E2 = -3.3026843865092101E+03 # External code

    # External code we trust
    G2 = np.array([
        [-0.0085120,  -0.0110529,  -0.0103269],
        [-0.0114557,  -0.0016662,  -0.0232143],
        [ 0.0002911,  -0.0047466,  -0.0017821],
        [ 0.0038813,  -0.0050415,   0.0097291],
        [ 0.0209246,   0.0287900,  -0.0000189],
        [ 0.0030973,  -0.0048202,   0.0137675],
        [-0.0039907,  -0.0032770,  -0.0038259],
        [-0.0042358,   0.0018145,   0.0156714],
        ])

    E1, G1 = run_rhf(
        primary_name='def2-svp',
        xc_grid_family='SG',
        xc_grid_level=0,
        )
    G1 = G1.to_numpy()
    dE = abs(E1 - E2)
    dG = np.max(np.abs(G1 - G2))

    print(dE)
    print(dG)

    assert(dE < 1.0E-6)
    assert(dG < 1.0E-6)

def test_blyp1_sg1():

    E2 = -3.3026752311472001E+03 # External code

    # External code we trust
    G2 = np.array([
        [-0.0085473,  -0.0112361,  -0.0108816],
        [-0.0122674,  -0.0022214,  -0.0229901],
        [ 0.0005008,  -0.0043548,  -0.0012217],
        [ 0.0036445,  -0.0049861,   0.0095828],
        [ 0.0207480,   0.0285826,   0.0008645],
        [ 0.0044558,  -0.0031672,   0.0127055],
        [-0.0048013,  -0.0042453,  -0.0032890],
        [-0.0037329,   0.0016284,   0.0152296],
        ])

    E1, G1 = run_rhf(
        primary_name='def2-svp',
        xc_grid_family='SG',
        xc_grid_level=1,
        )
    G1 = G1.to_numpy()
    dE = abs(E1 - E2)
    dG = np.max(np.abs(G1 - G2))

    print(dE)
    print(dG)

    assert(dE < 1.0E-6)
    assert(dG < 1.0E-6)

def test_blyp1_sg2():

    E2 = -3.3026736460982765E+03 # External code

    # External code we trust
    G2 = np.array([
        [-0.0084323,  -0.0111196,  -0.0108407],
        [-0.0121870,  -0.0020630,  -0.0230002],
        [ 0.0003711,  -0.0044170,  -0.0012362],
        [ 0.0037095,  -0.0048980,   0.0095853],
        [ 0.0207128,   0.0284073,   0.0008231],
        [ 0.0026341,  -0.0039898,   0.0121270],
        [-0.0030084,  -0.0036482,  -0.0028789],
        [-0.0037998,   0.0017283,   0.0154206],
        ])

    E1, G1 = run_rhf(
        primary_name='def2-svp',
        xc_grid_family='SG',
        xc_grid_level=2,
        )
    G1 = G1.to_numpy()
    dE = abs(E1 - E2)
    dG = np.max(np.abs(G1 - G2))

    print(dE)
    print(dG)

    assert(dE < 1.0E-6)
    assert(dG < 1.0E-6)

def test_blyp1_sg3():

    E2 = -3.3026737110443828E+03 # External code

    # External code we trust
    G2 = np.array([
        [-0.0085442,  -0.0110449,  -0.0106980],
        [-0.0123756,  -0.0020051,  -0.0229404],
        [ 0.0003864,  -0.0045772,  -0.0015611],
        [ 0.0037599,  -0.0050463,   0.0096945],
        [ 0.0208292,   0.0286330,   0.0008832],
        [ 0.0038349,  -0.0035991,   0.0123249],
        [-0.0039995,  -0.0039321,  -0.0028949],
        [-0.0038911,   0.0015716,   0.0151917],
        ])

    E1, G1 = run_rhf(
        primary_name='def2-svp',
        xc_grid_family='SG',
        xc_grid_level=3,
        )
    G1 = G1.to_numpy()
    dE = abs(E1 - E2)
    dG = np.max(np.abs(G1 - G2))

    print(dE)
    print(dG)

    assert(dE < 1.0E-6)
    assert(dG < 1.0E-6)


# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import cuest_scf
import numpy as np
import os
filedir = os.path.dirname(os.path.realpath(__file__))
basisdir = os.path.join(filedir, '..', '..', 'data', 'gbs')

cuest_handle = cuest_scf.CuestHandle()

def run_rhf(
    *,
    primary_name,
    xc_grid_level,
    xc_grid_family,
    ):

    # Reasonable default - as coarse as 1.0E-10 yields high-precision results
    # Coarser can save some CoreDF memory and lower runtime
    threshold_pq = 1.0E-14

    molecule = cuest_scf.Molecule.parse_from_xyz_string("""
        C     -2.779381    1.358796   -0.017634
        N     -1.370791    1.405167   -0.366529
        F     -2.968627    1.790547    1.252788
        H     -3.367136    1.987227   -0.691709
        Cl    -3.330473   -0.311805   -0.186740
        O     -0.662015    0.806738    0.752014
        Br     0.836087    1.898704    1.267596
        H     -1.224487    0.696240   -1.080917""")
    charge = 0

    auxiliary_name = 'def2-universal-jkfit'
    minao_name = 'minao-1'

    primary_filename = os.path.join(basisdir, '%s.gbs' % (primary_name))
    auxiliary_filename = os.path.join(basisdir, '%s.gbs' % (auxiliary_name))
    minao_filename = os.path.join(basisdir, '%s.gbs' % (minao_name))

    primary = cuest_scf.AOBasis.parse_from_gbs_file(primary_filename, molecule=molecule)
    auxiliary = cuest_scf.AOBasis.parse_from_gbs_file(auxiliary_filename, molecule=molecule)
    minao = cuest_scf.AOBasis.parse_from_gbs_file(minao_filename, molecule=molecule)

    rhf = cuest_scf.RHF(
        cuest_handle=cuest_handle,
        molecule=molecule,
        charge=charge,
        xc_functional_name='BLYP',
        primary=primary,
        auxiliary=auxiliary,
        minao=minao,
        primary_name=primary_name,
        auxiliary_name=auxiliary_name,
        minao_name=minao_name,
        threshold_pq=threshold_pq,
        g_convergence=1.0E-8, # Tighter for gradient check
        xc_grid_level=xc_grid_level,
        xc_grid_family=xc_grid_family,
        )

    rhf.solve()

    E = rhf.compute_energy()

    G = rhf.compute_gradient()

    return E, G

def test_blyp1_grid1():

    import numpy as np

    E2 = -3.3026700806219742E+03 # External code

    # External code we trust
    G2 = np.array([
        [-0.0090490,  -0.0104565,  -0.0117094],
        [-0.0100463,  -0.0018479,  -0.0243732],
        [-0.0001783,  -0.0048707,   0.0004488],
        [ 0.0044712,  -0.0040387,   0.0094299],
        [ 0.0207582,   0.0281712,   0.0004648],
        [ 0.0022800,  -0.0044285,   0.0125760],
        [-0.0020055,  -0.0027708,  -0.0023172],
        [-0.0062302,   0.0002419,   0.0154801],
        ])


# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import numpy as np

class SADAtomStructure(object):

    def __init__(
        self,
        *,
        N : int,
        Ls : list,
        actives : list,
        ):

        self.N = N
        self.Ls = Ls
        self.actives = actives

        if not all(isinstance(_, int) for _ in Ls): raise RuntimeError('Ls is not list of int')
        if not all(isinstance(_, bool) for _ in actives): raise RuntimeError('actives is not list of bool')
        if list(sorted(actives)) != actives: raise RuntimeError('actives is not sorted')

        if self.N > 0 and self.nao_active == 0:
            raise RuntimeError('N > 0 and nao_active == 0')

    @property
    def nshell(self):
        return len(self.Ls)

    @property
    def max_L(self):
        return max(self.Ls) if self.nshell else 0

    @property
    def nao_inactive(self):
        return sum(2*L + 1 for L, active in zip(self.Ls, self.actives) if not active)

    @property
    def nao_active(self):
        return sum(2*L + 1 for L, active in zip(self.Ls, self.actives) if active)

    @property
    def nao(self):
        return self.nao_inactive + self.nao_active

    """
    Works in terms of RHF docc
    """
    @property
    def nocc(self):
        if self.nao == 0:
            return np.array([])
        nocc_total = self.N / 2
        if self.nao_inactive > nocc_total:
            raise RuntimeError('nao_inactive > nocc_total. N = %d' % self.N)
        if self.nao < nocc_total:
            raise RuntimeError('nao < nocc_total. N = %d' % self.N)
        nocc_active = nocc_total - self.nao_inactive
        n = nocc_active / self.nao_active
        return np.array(
            [1.0] * self.nao_inactive +
            [n] * self.nao_active)

    @property
    def string(self):
        s = 'SADAtomStructure: N = %d\n' % (self.N)
        for P in range(self.nshell):
            s += '%1d : %s\n' % (self.Ls[P], 'active' if self.actives[P] else 'inactive')
        return s

    @property
    def aufbau_string(self):
        symbols = ['s', 'p', 'd', 'f']
        if self.max_L >= len(symbols): raise RuntimeError('max_L too high for symbol table')

        s = ''
        s += '['
        for P in range(self.nshell):
            if P > 0 and self.actives[P] and not self.actives[P-1]:
                s += ']['
            L = self.Ls[P]
            s += str(sum(1 for L2 in self.Ls[:P] if L2 == L) + 1 + L)
            s += symbols[L]
        s += ']'

        return s

    def __str__(self):
        return self.aufbau_string

    """
    Technique used here:
      - In the S block, only the highest s shell is open.
      - In the P block, both the highest s and highest p shells are open.
      - In the D block, only the highest d shell is open.
      - In the F block, only the highest f shell is open.

    Specific choice:
      - The closed and open blocks are strictly canonicalized according to
        (n, l) ordering.
    """

    @staticmethod
    def build(N):
        if N == 0:
            # Gh [][]
            return SADAtomStructure(
                N=N,
                Ls=[],
                actives=[],
                )
        elif N >= 1 and N <= 2:
            # H  - He: [][1s]
            return SADAtomStructure(
                N=N,
                Ls=[0],
                actives=[True],
                )
        elif N >= 3 and N <= 4:
            # Li - Be: [1s][2s]
            return SADAtomStructure(
                N=N,
                Ls=[0, 0],
                actives=[False]*1 + [True]*1,
                )
        elif N >= 5 and N <= 10:
            # B  - Ne: [1s][2s2p]
            return SADAtomStructure(
                N=N,
                Ls=[0, 0, 1],
                actives=[False]*1 + [True]*2,
                )
        elif N >= 11 and N <= 12:
            # Na - Mg: [1s2s2p][3s]
            return SADAtomStructure(
                N=N,
                Ls=[0, 0, 1, 0],
                actives=[False]*3 + [True]*1,
                )
        elif N >= 13 and N <= 18:
            # Al - Ar: [1s2s2p][3s3p]
            return SADAtomStructure(
                N=N,
                Ls=[0, 0, 1, 0, 1],
                actives=[False]*3 + [True]*2,
                )
        elif N >= 19 and N <= 20:
            # K  - Ca: [1s2s2p3s3p][4s]
            return SADAtomStructure(
                N=N,
                Ls=[0, 0, 1, 0, 1, 0],
                actives=[False]*5 + [True]*1,
                )
        elif N >= 21 and N <= 30:
            # Sc - Zn: [1s2s2p3s3p4s][3d]
            return SADAtomStructure(
                N=N,
                Ls=[0, 0, 1, 0, 1, 0, 2],
                actives=[False]*6 + [True]*1,
                )
        elif N >= 31 and N <= 36:
            # Ga - Kr: [1s2s2p3s3p3d][4s4p]
            return SADAtomStructure(
                N=N,
                Ls=[0, 0, 1, 0, 1, 2, 0, 1],
                actives=[False]*6 + [True]*2,
                )
        # NOTE: We have the definitions for these down to Z=118, but have not
        # had time to format them - please reach out to cuEST product team if
        # you need these defined (note that you likely also need ECPs if this
        # deep)
        else:
            raise RuntimeError('Unknown SADAtomStructure for atomic number N = %d' % N)


# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import numpy as np

class SADAtomStructure(object):

    def __init__(
        self,
        *,
        N : int,
        Ls : list,
        actives : list,
        ):

        self.N = N
        self.Ls = Ls
        self.actives = actives

        if not all(isinstance(_, int) for _ in Ls): raise RuntimeError('Ls is not list of int')
        if not all(isinstance(_, bool) for _ in actives): raise RuntimeError('actives is not list of bool')
        if list(sorted(actives)) != actives: raise RuntimeError('actives is not sorted')

        if self.N > 0 and self.nao_active == 0:
            raise RuntimeError('N > 0 and nao_active == 0')

    @property
    def nshell(self):
        return len(self.Ls)

    @property
    def max_L(self):
        return max(self.Ls) if self.nshell else 0

    @property
    def nao_inactive(self):
        return sum(2*L + 1 for L, active in zip(self.Ls, self.actives) if not active)

    @property
    def nao_active(self):
        return sum(2*L + 1 for L, active in zip(self.Ls, self.actives) if active)

    @property
    def nao(self):
        return self.nao_inactive + self.nao_active


nshell

    def __init__(
        self,
        *,
        N : int,
        Ls : list,
        actives : list,
        ):

        self.N = N
        self.Ls = Ls
        self.actives = actives

        if not all(isinstance(_, int) for _ in Ls): raise RuntimeError('Ls is not list of int')
        if not all(isinstance(_, bool) for _ in actives): raise RuntimeError('actives is not list of bool')
        if list(sorted(actives)) != actives: raise RuntimeError('actives is not sorted')

        if self.N > 0 and self.nao_active == 0:
            raise RuntimeError('N > 0 and nao_active == 0')

    @property
    def nshell(self):
        return len(self.Ls)

    @property
    def max_L(self):
        return max(self.Ls) if self.nshell else 0

    @property
    def nao_inactive(self):
        return sum(2*L + 1 for L, active in zip(self.Ls, self.actives) if not active)

    @property
    def nao_active(self):
        return sum(2*L + 1 for L, active in zip(self.Ls, self.actives) if active)

    @property
    def nao(self):
        return self.nao_inactive + self.nao_active

    """
    Works in terms of RHF docc
    """


        Ls : list,
        actives : list,
        ):

        self.N = N
        self.Ls = Ls
        self.actives = actives

        if not all(isinstance(_, int) for _ in Ls): raise RuntimeError('Ls is not list of int')
        if not all(isinstance(_, bool) for _ in actives): raise RuntimeError('actives is not list of bool')
        if list(sorted(actives)) != actives: raise RuntimeError('actives is not sorted')

        if self.N > 0 and self.nao_active == 0:
            raise RuntimeError('N > 0 and nao_active == 0')

    @property
    def nshell(self):
        return len(self.Ls)

    @property
    def max_L(self):
        return max(self.Ls) if self.nshell else 0

    @property
    def nao_inactive(self):
        return sum(2*L + 1 for L, active in zip(self.Ls, self.actives) if not active)

    @property
    def nao_active(self):
        return sum(2*L + 1 for L, active in zip(self.Ls, self.actives) if active)

    @property
    def nao(self):
        return self.nao_inactive + self.nao_active

    """
    Works in terms of RHF docc
    """
    @property
    def nocc(self):
        if self.nao == 0:
            return np.array([])


        self.N = N
        self.Ls = Ls
        self.actives = actives

        if not all(isinstance(_, int) for _ in Ls): raise RuntimeError('Ls is not list of int')
        if not all(isinstance(_, bool) for _ in actives): raise RuntimeError('actives is not list of bool')
        if list(sorted(actives)) != actives: raise RuntimeError('actives is not sorted')

        if self.N > 0 and self.nao_active == 0:
            raise RuntimeError('N > 0 and nao_active == 0')

    @property
    def nshell(self):
        return len(self.Ls)

    @property
    def max_L(self):
        return max(self.Ls) if self.nshell else 0

    @property
    def nao_inactive(self):
        return sum(2*L + 1 for L, active in zip(self.Ls, self.actives) if not active)

    @property
    def nao_active(self):
        return sum(2*L + 1 for L, active in zip(self.Ls, self.actives) if active)

    @property
    def nao(self):
        return self.nao_inactive + self.nao_active

    """
    Works in terms of RHF docc
    """
    @property
    def nocc(self):
        if self.nao == 0:
            return np.array([])
        nocc_total = self.N / 2
        if self.nao_inactive > nocc_total:
            raise RuntimeError('nao_inactive > nocc_total. N = %d' % self.N)
        if self.nao < nocc_total:


        if not all(isinstance(_, int) for _ in Ls): raise RuntimeError('Ls is not list of int')
        if not all(isinstance(_, bool) for _ in actives): raise RuntimeError('actives is not list of bool')
        if list(sorted(actives)) != actives: raise RuntimeError('actives is not sorted')

        if self.N > 0 and self.nao_active == 0:
            raise RuntimeError('N > 0 and nao_active == 0')

    @property
    def nshell(self):
        return len(self.Ls)

    @property
    def max_L(self):
        return max(self.Ls) if self.nshell else 0

    @property
    def nao_inactive(self):
        return sum(2*L + 1 for L, active in zip(self.Ls, self.actives) if not active)

    @property
    def nao_active(self):
        return sum(2*L + 1 for L, active in zip(self.Ls, self.actives) if active)

    @property
    def nao(self):
        return self.nao_inactive + self.nao_active

    """
    Works in terms of RHF docc
    """
    @property
    def nocc(self):
        if self.nao == 0:
            return np.array([])
        nocc_total = self.N / 2
        if self.nao_inactive > nocc_total:
            raise RuntimeError('nao_inactive > nocc_total. N = %d' % self.N)
        if self.nao < nocc_total:
            raise RuntimeError('nao < nocc_total. N = %d' % self.N)
        nocc_active = nocc_total - self.nao_inactive
        n = nocc_active / self.nao_active
        return np.array(


nao

        if self.N > 0 and self.nao_active == 0:
            raise RuntimeError('N > 0 and nao_active == 0')

    @property
    def nshell(self):
        return len(self.Ls)

    @property
    def max_L(self):
        return max(self.Ls) if self.nshell else 0

    @property
    def nao_inactive(self):
        return sum(2*L + 1 for L, active in zip(self.Ls, self.actives) if not active)

    @property
    def nao_active(self):
        return sum(2*L + 1 for L, active in zip(self.Ls, self.actives) if active)

    @property
    def nao(self):
        return self.nao_inactive + self.nao_active

    """
    Works in terms of RHF docc
    """
    @property
    def nocc(self):
        if self.nao == 0:
            return np.array([])
        nocc_total = self.N / 2
        if self.nao_inactive > nocc_total:
            raise RuntimeError('nao_inactive > nocc_total. N = %d' % self.N)
        if self.nao < nocc_total:
            raise RuntimeError('nao < nocc_total. N = %d' % self.N)
        nocc_active = nocc_total - self.nao_inactive
        n = nocc_active / self.nao_active
        return np.array(
            [1.0] * self.nao_inactive +
            [n] * self.nao_active)

    @property


nocc

    @property
    def max_L(self):
        return max(self.Ls) if self.nshell else 0

    @property
    def nao_inactive(self):
        return sum(2*L + 1 for L, active in zip(self.Ls, self.actives) if not active)

    @property
    def nao_active(self):
        return sum(2*L + 1 for L, active in zip(self.Ls, self.actives) if active)

    @property
    def nao(self):
        return self.nao_inactive + self.nao_active

    """
    Works in terms of RHF docc
    """
    @property
    def nocc(self):
        if self.nao == 0:
            return np.array([])
        nocc_total = self.N / 2
        if self.nao_inactive > nocc_total:
            raise RuntimeError('nao_inactive > nocc_total. N = %d' % self.N)
        if self.nao < nocc_total:
            raise RuntimeError('nao < nocc_total. N = %d' % self.N)
        nocc_active = nocc_total - self.nao_inactive
        n = nocc_active / self.nao_active
        return np.array(
            [1.0] * self.nao_inactive +
            [n] * self.nao_active)

    @property
    def string(self):
        s = 'SADAtomStructure: N = %d\n' % (self.N)
        for P in range(self.nshell):
            s += '%1d : %s\n' % (self.Ls[P], 'active' if self.actives[P] else 'inactive')
        return s

    @property
    def aufbau_string(self):
        symbols = ['s', 'p', 'd', 'f']
        if self.max_L >= len(symbols): raise RuntimeError('max_L too high for symbol table')

        s = ''
        s += '['
        for P in range(self.nshell):
            if P > 0 and self.actives[P] and not self.actives[P-1]:
                s += ']['
            L = self.Ls[P]
            s += str(sum(1 for L2 in self.Ls[:P] if L2 == L) + 1 + L)


string

    """
    Works in terms of RHF docc
    """
    @property
    def nocc(self):
        if self.nao == 0:
            return np.array([])
        nocc_total = self.N / 2
        if self.nao_inactive > nocc_total:
            raise RuntimeError('nao_inactive > nocc_total. N = %d' % self.N)
        if self.nao < nocc_total:
            raise RuntimeError('nao < nocc_total. N = %d' % self.N)
        nocc_active = nocc_total - self.nao_inactive
        n = nocc_active / self.nao_active
        return np.array(
            [1.0] * self.nao_inactive +
            [n] * self.nao_active)

    @property
    def string(self):
        s = 'SADAtomStructure: N = %d\n' % (self.N)
        for P in range(self.nshell):
            s += '%1d : %s\n' % (self.Ls[P], 'active' if self.actives[P] else 'inactive')
        return s

    @property
    def aufbau_string(self):
        symbols = ['s', 'p', 'd', 'f']
        if self.max_L >= len(symbols): raise RuntimeError('max_L too high for symbol table')

        s = ''
        s += '['
        for P in range(self.nshell):
            if P > 0 and self.actives[P] and not self.actives[P-1]:
                s += ']['
            L = self.Ls[P]
            s += str(sum(1 for L2 in self.Ls[:P] if L2 == L) + 1 + L)
            s += symbols[L]
        s += ']'

        return s

    def __str__(self):
        return self.aufbau_string


            return np.array([])
        nocc_total = self.N / 2
        if self.nao_inactive > nocc_total:
            raise RuntimeError('nao_inactive > nocc_total. N = %d' % self.N)
        if self.nao < nocc_total:
            raise RuntimeError('nao < nocc_total. N = %d' % self.N)
        nocc_active = nocc_total - self.nao_inactive
        n = nocc_active / self.nao_active
        return np.array(
            [1.0] * self.nao_inactive +
            [n] * self.nao_active)

    @property
    def string(self):
        s = 'SADAtomStructure: N = %d\n' % (self.N)
        for P in range(self.nshell):
            s += '%1d : %s\n' % (self.Ls[P], 'active' if self.actives[P] else 'inactive')
        return s

    @property
    def aufbau_string(self):
        symbols = ['s', 'p', 'd', 'f']
        if self.max_L >= len(symbols): raise RuntimeError('max_L too high for symbol table')

        s = ''
        s += '['
        for P in range(self.nshell):
            if P > 0 and self.actives[P] and not self.actives[P-1]:
                s += ']['
            L = self.Ls[P]
            s += str(sum(1 for L2 in self.Ls[:P] if L2 == L) + 1 + L)
            s += symbols[L]
        s += ']'

        return s

    def __str__(self):
        return self.aufbau_string

    """
    Technique used here:
      - In the S block, only the highest s shell is open.
      - In the P block, both the highest s and highest p shells are open.
      - In the D block, only the highest d shell is open.
      - In the F block, only the highest f shell is open.

    Specific choice:
      - The closed and open blocks are strictly canonicalized according to
        (n, l) ordering.
    """

    @staticmethod
    def build(N):
        if N == 0:
            # Gh [][]


            s += '%1d : %s\n' % (self.Ls[P], 'active' if self.actives[P] else 'inactive')
        return s

    @property
    def aufbau_string(self):
        symbols = ['s', 'p', 'd', 'f']
        if self.max_L >= len(symbols): raise RuntimeError('max_L too high for symbol table')

        s = ''
        s += '['
        for P in range(self.nshell):
            if P > 0 and self.actives[P] and not self.actives[P-1]:
                s += ']['
            L = self.Ls[P]
            s += str(sum(1 for L2 in self.Ls[:P] if L2 == L) + 1 + L)
            s += symbols[L]
        s += ']'

        return s

    def __str__(self):
        return self.aufbau_string

    """
    Technique used here:
      - In the S block, only the highest s shell is open.
      - In the P block, both the highest s and highest p shells are open.
      - In the D block, only the highest d shell is open.
      - In the F block, only the highest f shell is open.

    Specific choice:
      - The closed and open blocks are strictly canonicalized according to
        (n, l) ordering.
    """

    @staticmethod
    def build(N):
        if N == 0:
            # Gh [][]
            return SADAtomStructure(
                N=N,
                Ls=[],


build

        s += ']'

        return s

    def __str__(self):
        return self.aufbau_string

    """
    Technique used here:
      - In the S block, only the highest s shell is open.
      - In the P block, both the highest s and highest p shells are open.
      - In the D block, only the highest d shell is open.
      - In the F block, only the highest f shell is open.

    Specific choice:
      - The closed and open blocks are strictly canonicalized according to
        (n, l) ordering.
    """

    @staticmethod
    def build(N):
        if N == 0:
            # Gh [][]
            return SADAtomStructure(
                N=N,
                Ls=[],
                actives=[],
                )
        elif N >= 1 and N <= 2:
            # H  - He: [][1s]
            return SADAtomStructure(
                N=N,
                Ls=[0],
                actives=[True],
                )
        elif N >= 3 and N <= 4:
            # Li - Be: [1s][2s]
            return SADAtomStructure(
                N=N,
                Ls=[0, 0],
                actives=[False]*1 + [True]*1,
                )
        elif N >= 5 and N <= 10:
            # B  - Ne: [1s][2s2p]
            return SADAtomStructure(
                N=N,
                Ls=[0, 0, 1],
                actives=[False]*1 + [True]*2,
                )
        elif N >= 11 and N <= 12:
            # Na - Mg: [1s2s2p][3s]
            return SADAtomStructure(
                N=N,
                Ls=[0, 0, 1, 0],
                actives=[False]*3 + [True]*1,
                )
        elif N >= 13 and N <= 18:
            # Al - Ar: [1s2s2p][3s3p]
            return SADAtomStructure(
                N=N,
                Ls=[0, 0, 1, 0, 1],
                actives=[False]*3 + [True]*2,
                )
        elif N >= 19 and N <= 20:
            # K  - Ca: [1s2s2p3s3p][4s]
            return SADAtomStructure(
                N=N,
                Ls=[0, 0, 1, 0, 1, 0],
                actives=[False]*5 + [True]*1,
                )
        elif N >= 21 and N <= 30:
            # Sc - Zn: [1s2s2p3s3p4s][3d]
            return SADAtomStructure(
                N=N,
                Ls=[0, 0, 1, 0, 1, 0, 2],
                actives=[False]*6 + [True]*1,
                )
        elif N >= 31 and N <= 36:
            # Ga - Kr: [1s2s2p3s3p3d][4s4p]
            return SADAtomStructure(
                N=N,
                Ls=[0, 0, 1, 0, 1, 2, 0, 1],
                actives=[False]*6 + [True]*2,
                )
        # NOTE: We have the definitions for these down to Z=118, but have not
        # had time to format them - please reach out to cuEST product team if
        # you need these defined (note that you likely also need ECPs if this
        # deep)
        else:
            raise RuntimeError('Unknown SADAtomStructure for atomic number N = %d' % N)


# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import cuest.bindings as ce

class XCFunctionalInfo(object):

    @staticmethod
    def string_to_enum(
        functional_name : str,
        ):

        if functional_name.lower() == 'hf':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_HF
        elif functional_name.lower() == 'b3lyp1':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_B3LYP1
        elif functional_name.lower() == 'b3lyp5':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_B3LYP5
        elif functional_name.lower() == 'b97':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_B97
        elif functional_name.lower() == 'blyp':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_BLYP
        elif functional_name.lower() == 'm06-l':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_M06L
        elif functional_name.lower() == 'pbe':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_PBE
        elif functional_name.lower() == 'pbe0':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_PBE0
        elif functional_name.lower() == 'r2scan':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_R2SCAN
        elif functional_name.lower() == 'svwn5':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_SVWN5
        elif functional_name.lower() == 'b97m-v':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_B97MV
        else:
            raise RuntimeError('Unknown DFT functional')

    @staticmethod
    def enum_to_string(
        functional_enum : ce.CuestXCIntPlanParametersFunctional,
        ):

        if functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_HF:
            return 'HF'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_B3LYP1:
            return 'B3LYP1'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_B3LYP5:
            return 'B3LYP5'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_B97:
            return 'B97'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_BLYP:
            return 'BLYP'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_M06L:
            return 'M06-L'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_PBE:
            return 'PBE'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_PBE0:
            return 'PBE0'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_R2SCAN:
            return 'r2SCAN'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_SVWN5:
            return 'SVWN5'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_B97MV:
            return 'B97M-V'
        else:
            raise RuntimeError('Unknown DFT functional')


# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import cuest.bindings as ce

class XCFunctionalInfo(object):

    @staticmethod
    def string_to_enum(
        functional_name : str,
        ):

        if functional_name.lower() == 'hf':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_HF
        elif functional_name.lower() == 'b3lyp1':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_B3LYP1
        elif functional_name.lower() == 'b3lyp5':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_B3LYP5
        elif functional_name.lower() == 'b97':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_B97
        elif functional_name.lower() == 'blyp':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_BLYP
        elif functional_name.lower() == 'm06-l':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_M06L
        elif functional_name.lower() == 'pbe':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_PBE
        elif functional_name.lower() == 'pbe0':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_PBE0
        elif functional_name.lower() == 'r2scan':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_R2SCAN
        elif functional_name.lower() == 'svwn5':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_SVWN5
        elif functional_name.lower() == 'b97m-v':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_B97MV
        else:
            raise RuntimeError('Unknown DFT functional')

    @staticmethod
    def enum_to_string(
        functional_enum : ce.CuestXCIntPlanParametersFunctional,
        ):

        if functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_HF:
            return 'HF'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_B3LYP1:
            return 'B3LYP1'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_B3LYP5:
            return 'B3LYP5'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_B97:
            return 'B97'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_BLYP:
            return 'BLYP'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_M06L:
            return 'M06-L'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_PBE:
            return 'PBE'


        elif functional_name.lower() == 'b97':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_B97
        elif functional_name.lower() == 'blyp':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_BLYP
        elif functional_name.lower() == 'm06-l':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_M06L
        elif functional_name.lower() == 'pbe':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_PBE
        elif functional_name.lower() == 'pbe0':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_PBE0
        elif functional_name.lower() == 'r2scan':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_R2SCAN
        elif functional_name.lower() == 'svwn5':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_SVWN5
        elif functional_name.lower() == 'b97m-v':
            return ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_B97MV
        else:
            raise RuntimeError('Unknown DFT functional')

    @staticmethod
    def enum_to_string(
        functional_enum : ce.CuestXCIntPlanParametersFunctional,
        ):

        if functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_HF:
            return 'HF'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_B3LYP1:
            return 'B3LYP1'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_B3LYP5:
            return 'B3LYP5'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_B97:
            return 'B97'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_BLYP:
            return 'BLYP'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_M06L:
            return 'M06-L'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_PBE:
            return 'PBE'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_PBE0:
            return 'PBE0'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_R2SCAN:
            return 'r2SCAN'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_SVWN5:
            return 'SVWN5'
        elif functional_enum == ce.CuestXCIntPlanParametersFunctional.CUEST_XCINTPLAN_PARAMETERS_FUNCTIONAL_B97MV:
            return 'B97M-V'
        else:
            raise RuntimeError('Unknown DFT functional')