Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make pandas and mpl dependencies for optional features #141

Merged
merged 5 commits into from
Jan 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions adcc/ElectronicTransition.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,13 @@
import warnings
import numpy as np

from .misc import cached_property
from .misc import cached_property, requires_module
from .timings import Timer, timed_member_call
from .visualisation import ExcitationSpectrum
from .OneParticleOperator import product_trace
from .AdcMethod import AdcMethod

from scipy import constants
from matplotlib import pyplot as plt
from .Excitation import mark_excitation_property
from .solver.SolverStateBase import EigenSolverStateBase

Expand Down Expand Up @@ -237,6 +236,7 @@ def cross_section(self):
prefac = 2.0 * np.pi ** 2 / fine_structure_au
return prefac * self.oscillator_strength

@requires_module("matplotlib")
def plot_spectrum(self, broadening="lorentzian", xaxis="eV",
yaxis="cross_section", width=0.01, **kwargs):
"""One-shot plotting function for the spectrum generated by all states
Expand Down Expand Up @@ -265,6 +265,7 @@ def plot_spectrum(self, broadening="lorentzian", xaxis="eV",
gamma parameter. The value should be given in atomic units
and will be converted to the unit of the energy axis.
"""
from matplotlib import pyplot as plt
if xaxis == "eV":
eV = constants.value("Hartree energy in eV")
energies = self.excitation_energy * eV
Expand Down
5 changes: 3 additions & 2 deletions adcc/ExcitedStates.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,12 @@
## ---------------------------------------------------------------------
import warnings
import numpy as np
import pandas as pd

from adcc import dot
from scipy import constants

from . import adc_pp
from .misc import cached_property
from .misc import cached_property, requires_module
from .timings import timed_member_call
from .Excitation import Excitation, mark_excitation_property
from .FormatIndex import (FormatIndexAdcc, FormatIndexBase,
Expand Down Expand Up @@ -439,11 +438,13 @@ def describe_amplitudes(self, tolerance=0.01, index_format=None):
ret += separator
return ret[:-1]

@requires_module("pandas")
def to_dataframe(self):
"""
Exports the ExcitedStates object as :class:`pandas.DataFrame`.
Atomic units are used for all values.
"""
import pandas as pd
propkeys = self.excitation_property_keys
propkeys.extend([k.name for k in self._excitation_energy_corrections])
data = {
Expand Down
2 changes: 1 addition & 1 deletion adcc/LazyMp.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
class LazyMp:
def __init__(self, hf):
"""
Initialise the class dealing with the M/oller-Plesset ground state.
Initialise the class dealing with the Møller-Plesset ground state.
"""
if isinstance(hf, libadcc.HartreeFockSolution_i):
hf = ReferenceState(hf)
Expand Down
8 changes: 6 additions & 2 deletions adcc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from .LazyMp import LazyMp
from .Tensor import Tensor
from .Symmetry import Symmetry
from .MoSpaces import MoSpaces
from .AdcMatrix import AdcBlockView, AdcMatrix
from .AdcMethod import AdcMethod
from .functions import (contract, copy, direct_sum, dot, einsum, empty_like,
Expand All @@ -36,6 +37,8 @@
from .memory_pool import memory_pool
from .State2States import State2States
from .ExcitedStates import ExcitedStates
from .Excitation import Excitation
from .ElectronicTransition import ElectronicTransition
from .DataHfProvider import DataHfProvider, DictHfProvider
from .ReferenceState import ReferenceState
from .AmplitudeVector import AmplitudeVector
Expand All @@ -49,13 +52,14 @@
from .exceptions import InputError

__all__ = ["run_adc", "InputError", "AdcMatrix", "AdcBlockView",
"AdcMethod", "Symmetry", "ReferenceState",
"AdcMethod", "Symmetry", "ReferenceState", "MoSpaces",
"einsum", "contract", "copy", "dot", "empty_like", "evaluate",
"lincomb", "nosym_like", "ones_like", "transpose",
"linear_combination", "zeros_like", "direct_sum",
"memory_pool", "set_n_threads", "get_n_threads", "AmplitudeVector",
"HartreeFockProvider", "ExcitedStates", "State2States",
"Tensor", "DictHfProvider", "DataHfProvider", "OneParticleOperator",
"Excitation", "ElectronicTransition", "Tensor", "DictHfProvider",
"DataHfProvider", "OneParticleOperator",
"guesses_singlet", "guesses_triplet", "guesses_any",
"guess_symmetries", "guesses_spin_flip", "guess_zero", "LazyMp",
"adc0", "cis", "adc1", "adc2", "adc2x", "adc3",
Expand Down
32 changes: 1 addition & 31 deletions adcc/backends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,41 +24,11 @@
import h5py
import warnings

from pkg_resources import parse_version
from ..misc import is_module_available

__all__ = ["import_scf_results", "run_hf", "have_backend", "available"]


def is_module_available(module, min_version=None):
"""Check using importlib if a module is available."""
import importlib

try:
mod = importlib.import_module(module)
except ImportError:
return False

if not min_version: # No version check
return True

if not hasattr(mod, "__version__"):
warnings.warn(
"Could not check host program {} minimal version, "
"since __version__ tag not found. Proceeding anyway."
"".format(module)
)
return True

if parse_version(mod.__version__) < parse_version(min_version):
warnings.warn(
"Found host program module {}, but its version {} is below "
"the least required (== {}). This host program will be ignored."
"".format(module, mod.__version__, min_version)
)
return False
return True


# Cache for the list of available backends ... cannot be filled right now,
# since this can lead to import loops when adcc is e.g. used from Psi4
__status = dict()
Expand Down
50 changes: 50 additions & 0 deletions adcc/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@
## along with adcc. If not, see <http://www.gnu.org/licenses/>.
##
## ---------------------------------------------------------------------
import warnings
import numpy as np
from functools import wraps
from pkg_resources import parse_version


def cached_property(f):
Expand Down Expand Up @@ -115,6 +117,54 @@ def caller(self, fctn=fctn, args=args):
return inner_decorator


def is_module_available(module, min_version=None):
"""Check using importlib if a module is available."""
import importlib

try:
mod = importlib.import_module(module)
except ImportError:
return False

if not min_version: # No version check
return True

if not hasattr(mod, "__version__"):
warnings.warn(
f"Could not check module {module} minimal version, "
"since __version__ tag not found. Proceeding anyway."
)
return True

if parse_version(mod.__version__) < parse_version(min_version):
warnings.warn(
f"Found module {module}, but its version {mod.__version__} is below "
f"the least required (== {min_version}). This module will be ignored."
)
return False
return True


def requires_module(name, min_version=None):
"""
Decorator to check if the module 'name' is available,
throw ModuleNotFoundError on call if not.
"""
def inner(function):
def wrapper(*args, **kwargs):
fname = function.__name__
if not is_module_available(name, min_version):
raise ModuleNotFoundError(
f"Function '{fname}' needs module {name}, but it was "
f"not found. Solve by running 'pip install {name}' or "
f"'conda install {name}' on your system."
)
return function(*args, **kwargs)
wrapper.__doc__ = function.__doc__
return wrapper
return inner


def assert_allclose_signfix(actual, desired, atol=0, **kwargs):
"""
Call assert_allclose, but beforehand normalise the sign
Expand Down
4 changes: 3 additions & 1 deletion adcc/visualisation/Spectrum.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import numpy as np

from . import shapefctns
from matplotlib import pyplot as plt
from ..misc import requires_module


class Spectrum:
Expand Down Expand Up @@ -127,6 +127,7 @@ def copy(self):
cpy.ylabel = self.ylabel
return cpy

@requires_module("matplotlib")
def plot(self, *args, style=None, **kwargs):
"""Plot the Spectrum represented by this class.

Expand All @@ -139,6 +140,7 @@ def plot(self, *args, style=None, **kwargs):
types of spectra commonly plotted. Valid are "discrete" and
"continuous". By default no special style is chosen.
"""
from matplotlib import pyplot as plt
if style == "discrete":
p = plt.plot(self.x, self.y, "x", *args, **kwargs)
plt.vlines(self.x, 0, self.y, linestyle="dashed",
Expand Down
12 changes: 8 additions & 4 deletions docs/calculations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,10 @@ obtained using the function ``adcc.get_n_threads()``.
Plotting spectra
----------------

.. note::
For plotting spectra, `Matplotlib <https://matplotlib.org>`_
needs to be installed. See :ref:`optional-dependencies` for details.

Having computed a set of ADC excited states as discussed in the
previous sections, these can be visualised
in a simulated absorption spectrum
Expand Down Expand Up @@ -354,7 +358,7 @@ as shown in the next example.
state.plot_spectrum()
plt.show()

This code uses the :func:`adcc.ExcitedStates.plot_spectrum`
This code uses the :func:`adcc.ElectronicTransition.plot_spectrum`
function and the `Matplotlib <https://matplotlib.org>`_ package
to produce a plot such as

Expand All @@ -364,13 +368,13 @@ In this image crosses represent the actual computed value
for the absorption cross section for the obtained excited states.
To form the actual spectrum (solid blue line) these discrete
peaks are artificially broadened with an empirical broadening parameter.
Notice, that the :func:`adcc.ExcitedStates.plot_spectrum`
Notice, that the :func:`adcc.ElectronicTransition.plot_spectrum`
function does only prepare the spectrum inside Matplotlib,
such that ``plt.show()`` needs to be called in order to actuall *see* the plot.
This allows to *simulaneously* plot the spectrum from multiple
calculations in one figure if desired.

The :func:`adcc.ExcitedStates.plot_spectrum` function takes a number
The :func:`adcc.ElectronicTransition.plot_spectrum` function takes a number
of parameters to alter the default plotting behaviour:

- **Broadening parameters**: The default broadening can be completely disabled
Expand Down Expand Up @@ -401,7 +405,7 @@ of parameters to alter the default plotting behaviour:
See the `Matplotlib documentation <https://matplotlib.org/api/_as_gen/matplotlib.pyplot.plot.html>`_ for details.

In the same manner, one can model the ECD spectrum of chiral molecules
with the :func:`adcc.ExcitedStates.plot_spectrum` function. An example
with the :func:`adcc.ElectronicTransition.plot_spectrum` function. An example
script for obtaining the ECD spectrum of (R)- and (S)-2-methyloxirane with ADC(2) can be
found in the `examples folder <https://code.adc-connect.org/tree/master/examples/methyloxirane>`_.
The only difference to plotting a UV/Vis spectrum as shown above is to specify
Expand Down
3 changes: 3 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@ def determine_tag():
napoleon_numpy_docstring = True
napoleon_include_init_with_doc = True

# __init__ docstring appears in documentation
autoclass_content = "init"

# Breathe settings
libadcc_doc_dir = (
os.path.abspath(os.path.dirname(__file__))
Expand Down
25 changes: 25 additions & 0 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ Please get in touch
by `opening an issue <https://github.com/adc-connect/adcc/issues>`_
if you cannot get adcc to work.

Some specialty features of adcc require extra dependencies (e.g., Pandas and
Matplotlib), see :ref:`optional-dependencies` for details.

Installing adcc
---------------

Expand Down Expand Up @@ -154,6 +157,28 @@ the :ref:`devnotes` will provide
you with some useful pointers to get started.


.. _optional-dependencies:

Optional dependencies for analysis features
-------------------------------------------

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my experience users rarely read such a block text in the docs. I think it's wise to first show a list of "optional packages" along with a code block listing the conda or pip command to install them. You can then add the more detailed info in a paragraph afterwards.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are right. I removed the large block text and replaced it with a concise list, looks much better now. I'll have a look at the docs from build artifacts and will merge the PR if everything looks fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to be.

- **Matplotlib**: Plotting spectra with :func:`adcc.ElectronicTransition.plot_spectrum`


- **Pandas**: Export to `pandas.DataFrame` via :func:`adcc.ExcitedStates.to_dataframe`


Installation of optional packages
.................................

- Using pip: Either install optional dependencies directly with adcc via
``pip install adcc[analysis]`` or run manually for each package, e.g., ``pip install matplotlib``

- Using conda: Install each package manually, e.g., ``conda install matplotlib``.

Note that all other core features of adcc still work without
these packages installed.


.. _troubleshooting:

Expand Down
2 changes: 1 addition & 1 deletion examples/hydrogen_fluoride/psi4_631g_sf_adc2.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
no_com
""")

psi4.set_num_threads(adcc.thread_pool.n_cores)
psi4.set_num_threads(adcc.get_n_threads())
psi4.core.be_quiet()
psi4.set_options({'basis': "6-31g",
'e_convergence': 1e-14,
Expand Down
1 change: 1 addition & 0 deletions examples/hydrogen_fluoride/pyscf_631g_sf_adc2.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
scfres = scf.UHF(mol)
scfres.conv_tol = 1e-14
scfres.conv_tol_grad = 1e-10
scfres.max_cycle = 500
scfres.kernel()

# Run solver and print results
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def run_spin_flip(distance):
states = adcc.adc2(scfres, n_spin_flip=1)

ene = scfres.energy_tot() + states.ground_state.energy_correction(2)
return ene + states.eigenvalues[0]
return ene + states.excitation_energy[0]


def run_progression(outfile="631g_adc2_dissociation.nptxt"):
Expand Down
13 changes: 4 additions & 9 deletions examples/neon/test_cg_alpha.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,16 @@

from pyscf import gto, scf
from adcc.solver.preconditioner import JacobiPreconditioner
from adcc.AmplitudeVector import AmplitudeVector
from adcc.solver import IndexSymmetrisation
from adcc.solver.conjugate_gradient import conjugate_gradient, default_print
from adcc.modified_transition_moments import compute_modified_transition_moments
from adcc.adc_pp.modified_transition_moments import modified_transition_moments


class ShiftedMat(adcc.AdcMatrix):
def __init__(self, method, mp_results, omega=0.0):
self.omega = omega
super().__init__(method, mp_results)
diagonal = AmplitudeVector(*tuple(
self.diagonal(block) for block in self.blocks
))
self.omegamat = adcc.ones_like(diagonal) * omega
self.omegamat = adcc.ones_like(self.diagonal()) * omega

def __matmul__(self, other):
return super().__matmul__(other) - self.omegamat * other
Expand All @@ -39,9 +35,8 @@ def __matmul__(self, other):

refstate = adcc.ReferenceState(scfres)
matrix = ShiftedMat("adc3", refstate, omega=0.0)
rhs = compute_modified_transition_moments(
matrix, refstate.operators.electric_dipole[0], "adc2"
)
rhs = modified_transition_moments("adc2", matrix.ground_state,
refstate.operators.electric_dipole[0])
preconditioner = JacobiPreconditioner(matrix)
freq = 0.0
preconditioner.update_shifts(freq)
Expand Down
Loading