diff --git a/adcc/AdcMatrix.py b/adcc/AdcMatrix.py
index 54bec069..bdd414e4 100644
--- a/adcc/AdcMatrix.py
+++ b/adcc/AdcMatrix.py
@@ -28,7 +28,7 @@
from .LazyMp import LazyMp
from .adc_pp import matrix as ppmatrix
from .timings import Timer, timed_member_call
-from .AdcMethod import AdcMethod
+from .AdcMethod import AdcMethod, IsrMethod, Method
from .functions import ones_like
from .Intermediates import Intermediates
from .AmplitudeVector import AmplitudeVector
@@ -73,17 +73,22 @@ class AdcMatrixlike:
_special_block_orders = {
"adc2x": {"ph_ph": 2, "ph_pphh": 1, "pphh_ph": 1, "pphh_pphh": 1},
+ "isr1s": {"ph_ph": 1, "ph_pphh": None, "pphh_ph": None, "pphh_pphh": None},
}
@classmethod
- def _default_block_orders(cls, method: AdcMethod) -> dict[str, int]:
+ def _default_block_orders(cls,
+ method: Method
+ ) -> dict[str, int]:
"""
Determines the default block orders for the given adc method.
"""
# check if we have a special method like adc2x
# I guess base_method should also contain the adc_type prefix so
# we don't need to separate different adc_types
- block_orders = cls._special_block_orders.get(method.base_method.name, None)
+ block_orders = cls._special_block_orders.get(
+ method.base_method.name, None
+ )
if block_orders is not None:
return block_orders.copy()
# otherwise assume that we have a "normal" PP/IP/...-ADC(n) method
@@ -96,9 +101,23 @@ def _default_block_orders(cls, method: AdcMethod) -> dict[str, int]:
raise ValueError(f"Unknown adc type {method.adc_type} for method "
f"{method.name}. Can not determine default block "
"orders.")
- spaces = [
- "p" * i + min_space + "h" * i for i in range(0, (method.level // 2) + 1)
- ]
+ # ADC matrices have first-order coupling whereas ISR matrices
+ # have a zeroth-order coupling between adjacent excitation classes
+ # see https://doi.org/10.1063/1.1752875
+ if isinstance(method, AdcMethod):
+ # First-order coupling between adjacent excitation classes.
+ spaces = [
+ "p" * i + min_space + "h" * i
+ for i in range(0, (method.level.to_int() // 2) + 1)
+ ]
+ elif isinstance(method, IsrMethod):
+ # Zeroth-order coupling between adjacent excitation classes.
+ spaces = [
+ "p" * i + min_space + "h" * i
+ for i in range(0, ((method.level.to_int() + 1) // 2) + 1)
+ ]
+ else:
+ raise ValueError(f"Invalid method: {method.name}")
# exploit the fact that the spaces are sorted from small to high:
# If we walk the adc matrix in any direction we always have to subtract 1!
# Therefore, we can determine the order according to the position of the
@@ -107,26 +126,27 @@ def _default_block_orders(cls, method: AdcMethod) -> dict[str, int]:
ret = {}
for ((i1, bra), (i2, ket)) in \
itertools.product(enumerate(spaces), repeat=2):
- order = method.level - i1 - i2
- assert order >= 0
+ order = method.level.to_int() - i1 - i2
+ # For ISR matrices allow missing diagonal blocks.
+ order = None if order < 0 else order
ret[f"{bra}_{ket}"] = order
return ret
@classmethod
def _validate_block_orders(cls, block_orders: dict[str, int],
- method: AdcMethod,
+ method: Method,
allow_missing_diagonal_blocks: bool = False) -> None:
"""
- Validates that the given block_orders form a valid adc matrix for the given
- adc method.
+ Validates that the given block_orders form a valid adc/isr matrix for the
+ given adc(isr) method.
Parameters
----------
block_orders: dict[str, int]
The block orders to validate. Block orders should be of the form
{'ph_ph': 2, 'ph_pphh': 1, ...}
- method: AdcMethod
- The adc method/adc type (PP-ADC, ...) for which to validate
+ method: Method
+ The adc/isr method/adc type (PP-ADC/ISR, ...) for which to validate
the block_orders.
allow_missing_diagonal_blocks: bool, optional
If set, couplings between missing diagonal blocks are allowed, e.g.,
@@ -137,6 +157,7 @@ def _validate_block_orders(cls, block_orders: dict[str, int],
for block, order in block_orders.items():
if order is None:
continue
+ assert order >= 0
# ensure that the block is valid for the given adc type
bra, ket = block.split("_")
if not cls._is_valid_space(bra, method) or \
@@ -164,11 +185,11 @@ def _validate_block_orders(cls, block_orders: dict[str, int],
f"{ket_diag} are in the matrix too.")
@classmethod
- def _is_valid_space(cls, space: str, method: AdcMethod) -> bool:
+ def _is_valid_space(cls, space: str, method: Method) -> bool:
"""
Checks whether the given space ('ph' for instance) is valid for the given
adc method. Thereby we only verify that the space matches the adc_type of
- adc method!
+ method!
"""
n_particle, n_hole = space.count("p"), space.count("h")
# ensure that the space is of the form pp...hh...
@@ -236,11 +257,7 @@ def __init__(self, method, hf_or_mp, block_orders=None, intermediates=None,
self.intermediates = Intermediates(self.ground_state)
self.block_orders = self._default_block_orders(self.method)
- if block_orders is None:
- if method.level > 3:
- raise NotImplementedError("The ADC secular matrix is not "
- f"implemented for method {method.name}.")
- else:
+ if block_orders is not None:
self.block_orders.update(block_orders)
self._validate_block_orders(
block_orders=self.block_orders, method=self.method,
diff --git a/adcc/AdcMethod.py b/adcc/AdcMethod.py
index d1781e28..af308d1c 100644
--- a/adcc/AdcMethod.py
+++ b/adcc/AdcMethod.py
@@ -20,76 +20,125 @@
## along with adcc. If not, see .
##
## ---------------------------------------------------------------------
+from typing import Optional, TypeVar
+from enum import Enum
+T = TypeVar("T", bound="Method")
-def get_valid_methods():
- valid_prefixes = ["cvs"]
- valid_bases = ["adc0", "adc1", "adc2", "adc2x", "adc3"]
- ret = valid_bases + [p + "-" + m for p in valid_prefixes
- for m in valid_bases]
- return ret
+class MethodLevel(Enum):
+ # numeric levels
+ ZERO = 0
+ ONE = 1
+ TWO = 2
+ THREE = 3
+ FOUR = 4
+ FIVE = 5
+ # special levels
+ TWO_X = "2x"
+ ONE_S = "1s"
+ THREE_D = "3d"
-class AdcMethod:
- available_methods = get_valid_methods()
+ def to_str(self) -> str:
+ return str(self.value)
- def __init__(self, method):
- if method not in self.available_methods:
- raise ValueError("Invalid method " + str(method) + ". Only "
- + ",".join(self.available_methods) + " are known.")
+ def to_int(self) -> int:
+ # numerical methods
+ if isinstance(self.value, int):
+ return self.value
+ # return base int for special methods
+ elif isinstance(self.value, str):
+ return int(self.value[0])
+ else:
+ raise ValueError
+
+
+class Method:
+ # this has to be set on the child classes
+ _method_base_name: Optional[str] = None
+ max_level: int = 0
+ special_levels: tuple[MethodLevel, ...] = tuple()
+ def __init__(self, method: str):
+ assert self._method_base_name is not None
+
+ # validate base method type
split = method.split("-")
- self.__base_method = split[-1]
+ if not split[-1].startswith(self._method_base_name):
+ raise ValueError(f"{split[-1]} is not a valid method type")
+
+ # validate method level
+ level = split[-1][len(self._method_base_name):]
+ if level.isnumeric():
+ self.level: MethodLevel = MethodLevel(int(level))
+ else:
+ self.level: MethodLevel = MethodLevel(level)
+ self._validate_level(self.level)
+
+ assert self._base_method == split[-1]
+
+ # validate prefix
split = split[:-1]
- self.is_core_valence_separated = "cvs" in split
+ if split and split[0] not in ["cvs"]:
+ raise ValueError(f"{split[0]} is not a valid method prefix")
+
+ self.is_core_valence_separated: bool = "cvs" in split
# NOTE: added this to make the testdata generation ready for IP/EA
- self.adc_type = "pp"
+ self.adc_type: str = "pp"
- try:
- if self.__base_method == "adc2x":
- self.level = 2
- else:
- self.level = int(self.__base_method[-1])
- except ValueError:
- raise ValueError("Not a valid base method: " + self.__base_method)
+ def _validate_level(self, level: MethodLevel) -> None:
+ if isinstance(level.value, int):
+ if level.value <= self.max_level:
+ return
- def at_level(self, newlevel):
- """
- Return an equivalent method, where only the level is changed
- (e.g. calling this on a CVS method returns a CVS method)
- """
- if self.is_core_valence_separated:
- return AdcMethod("cvs-adc" + str(newlevel))
- else:
- return AdcMethod("adc" + str(newlevel))
+ # special cases
+ if level in self.special_levels:
+ return
+
+ raise NotImplementedError(f"{self._base_method} is not implemented.")
@property
- def name(self):
+ def name(self) -> str:
+ """The name of the Method as string."""
if self.is_core_valence_separated:
- return "cvs-" + self.__base_method
+ return "cvs-" + self._base_method
else:
- return self.__base_method
+ return self._base_method
@property
- def property_method(self):
- """
- The name of the canonical method to use for computing properties
- for this ADC method. This only differs from the name property
- for the ADC(2)-x family of methods.
- """
- if self.__base_method == "adc2x":
- return AdcMethod(self.name.replace("adc2x", "adc2")).name
- else:
- return self.name
+ def _base_method(self) -> str:
+ return self._method_base_name + self.level.to_str()
@property
- def base_method(self):
+ def base_method(self: T) -> T:
"""
The base (full) method, i.e. with all approximations such as
CVS stripped off.
"""
- return AdcMethod(self.__base_method)
+ return self.__class__(self._base_method)
+
+ def at_level(self: T, newlevel: int) -> T:
+ """
+ Return an equivalent method, where only the level is changed
+ (e.g. calling this on a CVS method returns a CVS method)
+ """
+ assert self._method_base_name is not None
+ if self.is_core_valence_separated:
+ return self.__class__("cvs-" + self._method_base_name + str(newlevel))
+ else:
+ return self.__class__(self._method_base_name + str(newlevel))
+
+ def as_method(self, method_cls: type[T]) -> T:
+ """
+ Return a equivalent Method with the method base name replaced
+ by the provided name.
+ """
+ assert self._method_base_name is not None
+ assert method_cls._method_base_name is not None
+ return method_cls(
+ self.name.replace(self._method_base_name, method_cls._method_base_name)
+ )
def __eq__(self, other):
return self.name == other.name
@@ -99,3 +148,15 @@ def __ne__(self, other):
def __repr__(self):
return "Method(name={})".format(self.name)
+
+
+class AdcMethod(Method):
+ _method_base_name = "adc"
+ max_level = 3
+ special_levels = (MethodLevel.TWO_X,)
+
+
+class IsrMethod(Method):
+ _method_base_name = "isr"
+ max_level = 2
+ special_levels = (MethodLevel.ONE_S,)
diff --git a/adcc/ElectronicStates.py b/adcc/ElectronicStates.py
index 0ff92f44..82d6fa59 100644
--- a/adcc/ElectronicStates.py
+++ b/adcc/ElectronicStates.py
@@ -3,7 +3,7 @@
import warnings
from .AdcMatrix import AdcMatrix
-from .AdcMethod import AdcMethod
+from .AdcMethod import AdcMethod, IsrMethod, MethodLevel
from .AmplitudeVector import AmplitudeVector
from .FormatDominantElements import FormatDominantElements
from .FormatIndex import (
@@ -83,14 +83,16 @@ def __init__(self, data, method: str = None,
self.method: AdcMethod = AdcMethod(self.method)
if property_method is None:
- if self.method.level < 3:
- property_method = self.method
+ if self.method.level in [MethodLevel.TWO_X, MethodLevel.THREE]:
+ # Auto-select ISR(2) properties for ADC(2)-x and ADC(3) calc
+ warnings.warn(f"ISR({self.method.level.to_str()}) not implemented."
+ f" Property method is selected as ISR(2).")
+ property_method = self.method.at_level(2).as_method(IsrMethod)
else:
- # Auto-select ADC(2) properties for ADC(3) calc
- property_method = self.method.at_level(2)
- elif not isinstance(property_method, AdcMethod):
- property_method = AdcMethod(property_method)
- self._property_method: AdcMethod = property_method
+ property_method = self.method.as_method(IsrMethod)
+ elif not isinstance(property_method, IsrMethod):
+ property_method = IsrMethod(property_method)
+ self._property_method: IsrMethod = property_method
# Special stuff for special solvers
if isinstance(data, EigenSolverStateBase):
@@ -138,8 +140,8 @@ def size(self) -> int:
return self._excitation_energy.size
@property
- def property_method(self) -> AdcMethod:
- """The method used to evaluate ADC properties"""
+ def property_method(self) -> IsrMethod:
+ """The method used to evaluate ISR properties"""
return self._property_method
@property
@@ -187,7 +189,7 @@ def state_dm(self) -> list[OneParticleDensity]:
def _state_dm(self, state_n: int) -> OneParticleDensity:
"""List of state density matrices of all computed states"""
- mp_density = self.ground_state.density(self.property_method.level)
+ mp_density = self.ground_state.density(self.property_method.level.to_int())
diffdm = self._state_diffdm(state_n)
return mp_density + diffdm
@@ -213,7 +215,9 @@ def state_dm_2p(self) -> list[TwoParticleDensity]:
def _state_dm_2p(self, state_n: int) -> TwoParticleDensity:
"""List of two particle state density matrices of all computed states"""
- mp_density = self.ground_state.density_2p(self.property_method.level)
+ mp_density = self.ground_state.density_2p(
+ self.property_method.level.to_int()
+ )
diffdm = self._state_diffdm_2p(state_n)
return mp_density + diffdm
@@ -226,10 +230,10 @@ def state_ssq(self) -> np.ndarray:
def _state_ssq(self, state_n: int) -> float:
"""Computes the of a single state."""
pmethod = self.property_method
- if pmethod.level == 0:
+ if pmethod.level.to_int() == 0:
gs_ssq = self.reference_state.ssq
else:
- gs_ssq = self.ground_state.ssq(pmethod.level)
+ gs_ssq = self.ground_state.ssq(pmethod.level.to_int())
ssq_1p_op = self.operators.ssq_1p
ssq_2p_op = self.operators.ssq_2p
@@ -248,10 +252,10 @@ def state_dipole_moment(self) -> np.ndarray:
def _state_dipole_moment(self, state_n: int) -> np.ndarray:
"""Computes the state dipole moment for a single state"""
pmethod = self.property_method
- if pmethod.level == 0:
+ if pmethod.level.to_int() == 0:
gs_dip_moment = self.reference_state.dipole_moment
else:
- gs_dip_moment = self.ground_state.dipole_moment(pmethod.level)
+ gs_dip_moment = self.ground_state.dipole_moment(pmethod.level.to_int())
dipole_integrals = self.operators.electric_dipole
ddm = self._state_diffdm(state_n)
diff --git a/adcc/IsrMatrix.py b/adcc/IsrMatrix.py
index 82dfd09f..8c5e823a 100644
--- a/adcc/IsrMatrix.py
+++ b/adcc/IsrMatrix.py
@@ -22,8 +22,10 @@
## ---------------------------------------------------------------------
import libadcc
+from itertools import product
+
from .AdcMatrix import AdcMatrixlike
-from .AdcMethod import AdcMethod
+from .AdcMethod import IsrMethod
from .adc_pp import bmatrix as ppbmatrix
from .AmplitudeVector import AmplitudeVector
from .LazyMp import LazyMp
@@ -37,11 +39,11 @@ class IsrMatrix(AdcMatrixlike):
def __init__(self, method, hf_or_mp, operator, block_orders=None):
"""
Initialise an ISR matrix of a given one-particle operator
- for the provided ADC method.
+ for the provided ISR method.
Parameters
----------
- method : str or adcc.AdcMethod
+ method : str or adcc.IsrMethod
Method to use.
hf_or_mp : adcc.ReferenceState or adcc.LazyMp
HF reference or MP ground state.
@@ -60,8 +62,8 @@ def __init__(self, method, hf_or_mp, operator, block_orders=None):
"either a LazyMp, a ReferenceState or a "
"HartreeFockSolution_i.")
- if not isinstance(method, AdcMethod):
- method = AdcMethod(method)
+ if not isinstance(method, IsrMethod):
+ method = IsrMethod(method)
if isinstance(operator, (list, tuple)):
self.operator = tuple(operator)
@@ -84,8 +86,7 @@ def __init__(self, method, hf_or_mp, operator, block_orders=None):
self.block_orders = self._default_block_orders(self.method)
if block_orders is None:
# only implemented through PP-ADC(2)
- if method.adc_type != "pp" or method.name.endswith("adc2x") \
- or method.level > 2:
+ if method.adc_type != "pp":
raise NotImplementedError("The B-matrix is not implemented "
f"for method {method.name}.")
else:
@@ -100,15 +101,15 @@ def __init__(self, method, hf_or_mp, operator, block_orders=None):
variant = None
if self.is_core_valence_separated:
variant = "cvs"
- blocks = [{
+ blocks = tuple({
block: ppbmatrix.block(self.ground_state, op,
block.split("_"), order=order,
variant=variant)
for block, order in self.block_orders.items() if order is not None
- } for op in self.operator]
- self.blocks = [{
+ } for op in self.operator)
+ self.blocks = tuple({
b: bl[b].apply for b in bl
- } for bl in blocks]
+ } for bl in blocks)
@timed_member_call()
def matvec(self, v):
@@ -119,9 +120,17 @@ def matvec(self, v):
If a list of OneParticleOperator objects was passed to the class
instantiation operator, a list of AmplitudeVector objects is returned.
"""
+ # Check which blocks are present in the AmplitudeVector.
+ # Missing blocks can be treated as zero vectors, so their
+ # contribution to the matvec is zero and does not need to be computed.
+ avail_blocks = [f"{bra}_{ket}" for bra, ket in product(v.blocks, repeat=2)]
+ filtered_blocks = [
+ {blk: v for blk, v in comp.items() if blk in avail_blocks}
+ for comp in self.blocks
+ ]
ret = [
sum(block(v) for block in bl_ph.values())
- for bl_ph in self.blocks
+ for bl_ph in filtered_blocks
]
if len(ret) == 1:
return ret[0]
@@ -139,7 +148,7 @@ def rmatvec(self, v):
AmplitudeVector(ph=-1.0 * mv.ph, pphh=-1.0 * mv.pphh)
for mv in self.matvec(v)
]
- # operators without any symmetry
+ # operators without any symmetry
else:
return NotImplemented
diff --git a/adcc/__init__.py b/adcc/__init__.py
index 004e66ff..c4ac3f13 100644
--- a/adcc/__init__.py
+++ b/adcc/__init__.py
@@ -29,7 +29,7 @@
from .Symmetry import Symmetry
from .MoSpaces import MoSpaces
from .AdcMatrix import AdcMatrix
-from .AdcMethod import AdcMethod
+from .AdcMethod import AdcMethod, IsrMethod
from .functions import (copy, direct_sum, dot, einsum, empty_like,
evaluate, lincomb, linear_combination, nosym_like,
ones_like, transpose, zeros_like)
@@ -55,7 +55,7 @@
from .exceptions import InputError
__all__ = ["run_adc", "InputError", "AdcMatrix",
- "AdcMethod", "Symmetry", "ReferenceState", "MoSpaces",
+ "AdcMethod", "IsrMethod", "Symmetry", "ReferenceState", "MoSpaces",
"einsum", "copy", "dot", "empty_like", "evaluate",
"lincomb", "nosym_like", "ones_like", "transpose",
"linear_combination", "zeros_like", "direct_sum",
@@ -93,7 +93,7 @@ def adc0(*args, **kwargs):
@with_runadc_doc
def cis(*args, **kwargs):
state = run_adc(*args, **kwargs, method="adc1")
- return ExcitedStates(state, property_method="adc0")
+ return ExcitedStates(state, property_method="isr0")
@with_runadc_doc
diff --git a/adcc/adc_pp/bmatrix.py b/adcc/adc_pp/bmatrix.py
index 4177e34e..55262759 100644
--- a/adcc/adc_pp/bmatrix.py
+++ b/adcc/adc_pp/bmatrix.py
@@ -35,9 +35,9 @@
"""
`apply` is a function mapping an AmplitudeVector to the contribution of this
-block to the result of applying the ADC matrix.
+block to the result of applying the ISR matrix.
"""
-AdcBlock = namedtuple("AdcBlock", ["apply"])
+IsrBlock = namedtuple("IsrBlock", ["apply"])
def block(ground_state, operator, spaces, order, variant=None):
@@ -84,7 +84,7 @@ def apply(ampl):
+ 1.0 * einsum('ic,ac->ia', ampl.ph, op.vv)
- 1.0 * einsum('ka,ki->ia', ampl.ph, op.oo)
))
- return AdcBlock(apply)
+ return IsrBlock(apply)
def block_pphh_pphh_0(ground_state, op):
@@ -99,7 +99,7 @@ def apply(ampl):
+ 2.0 * einsum('kiab,kj->ijab', ampl.pphh, op.oo)
).antisymmetrise(0, 1)
))
- return AdcBlock(apply)
+ return IsrBlock(apply)
#
@@ -111,7 +111,7 @@ def apply(ampl):
- 2.0 * einsum('ilad,ld->ia', ampl.pphh, op.ov)
+ 2.0 * einsum('ilca,lc->ia', ampl.pphh, op.ov)
))
- return AdcBlock(apply)
+ return IsrBlock(apply)
def block_pphh_ph_0(ground_state, op):
@@ -124,7 +124,7 @@ def apply(ampl):
- 1.0 * einsum('jb,ai->ijab', ampl.ph, op.vo)
).antisymmetrise(0, 1).antisymmetrise(2, 3)
))
- return AdcBlock(apply)
+ return IsrBlock(apply)
#
@@ -150,7 +150,7 @@ def apply(ampl):
- 2.0 * einsum('klad,kled,ei->ia', ampl.pphh, t2, op.vo)
- 2.0 * einsum('ilcd,nlcd,an->ia', ampl.pphh, t2, op.vo)
))
- return AdcBlock(apply)
+ return IsrBlock(apply)
def block_pphh_ph_1(ground_state, op):
@@ -179,7 +179,7 @@ def apply(ampl):
+ 1.0 * einsum('jc,niab,nc->ijab', ampl.ph, t2, op.ov)
).antisymmetrise(0, 1)
))
- return AdcBlock(apply)
+ return IsrBlock(apply)
#
@@ -221,4 +221,4 @@ def apply(ampl):
- 1.0 * einsum('kc,kncf,imaf,mn->ia', ampl.ph, t2, t2, op.oo)
+ 1.0 * einsum('kc,knce,inaf,ef->ia', ampl.ph, t2, t2, op.vv)
))
- return AdcBlock(apply)
+ return IsrBlock(apply)
diff --git a/adcc/adc_pp/modified_transition_moments.py b/adcc/adc_pp/modified_transition_moments.py
index ade7a68a..39b86b97 100644
--- a/adcc/adc_pp/modified_transition_moments.py
+++ b/adcc/adc_pp/modified_transition_moments.py
@@ -24,28 +24,28 @@
from adcc import block as b
from adcc.LazyMp import LazyMp
-from adcc.AdcMethod import AdcMethod
+from adcc.AdcMethod import IsrMethod
from adcc.functions import einsum, evaluate
from adcc.Intermediates import Intermediates
from adcc.AmplitudeVector import AmplitudeVector
-def mtm_adc0(mp, op, intermediates):
+def mtm_isr0(mp, op, intermediates):
f1 = op.vo.transpose()
return AmplitudeVector(ph=f1)
-def mtm_adc1(mp, op, intermediates):
- ampl = mtm_adc0(mp, op, intermediates)
+def mtm_isr1(mp, op, intermediates):
+ ampl = mtm_isr0(mp, op, intermediates)
f1 = - 1.0 * einsum("ijab,jb->ia", mp.t2(b.oovv), op.ov)
return ampl + AmplitudeVector(ph=f1)
-def mtm_adc2(mp, op, intermediates):
+def mtm_isr2(mp, op, intermediates):
t2 = mp.t2(b.oovv)
p0 = mp.mp2_diffdm
- ampl = mtm_adc1(mp, op, intermediates)
+ ampl = mtm_isr1(mp, op, intermediates)
f1 = (
+ 0.5 * einsum("ijab,jkbc,ck->ia", t2, t2, op.vo)
+ 0.5 * einsum("ij,aj->ia", p0.oo, op.vo)
@@ -61,14 +61,14 @@ def mtm_adc2(mp, op, intermediates):
return ampl + AmplitudeVector(ph=f1, pphh=f2)
-def mtm_cvs_adc0(mp, op, intermediates):
+def mtm_cvs_isr0(mp, op, intermediates):
f1 = op.vc.transpose()
return AmplitudeVector(ph=f1)
-def mtm_cvs_adc2(mp, op, intermediates):
+def mtm_cvs_isr2(mp, op, intermediates):
- ampl = mtm_cvs_adc0(mp, op, intermediates)
+ ampl = mtm_cvs_isr0(mp, op, intermediates)
f1 = (
- 0.5 * einsum("bI,ab->Ia", op.vc, intermediates.cvs_p0.vv)
- 1.0 * einsum("jI,ja->Ia", op.oc, intermediates.cvs_p0.ov)
@@ -78,24 +78,25 @@ def mtm_cvs_adc2(mp, op, intermediates):
DISPATCH = {
- "adc0": mtm_adc0,
- "adc1": mtm_adc1,
- "adc2": mtm_adc2,
- "adc2x": mtm_adc2, # Identical to ADC(2)
- "cvs-adc0": mtm_cvs_adc0,
- "cvs-adc1": mtm_cvs_adc0, # Identical to CVS-ADC(0)
- "cvs-adc2": mtm_cvs_adc2,
+ "isr0": mtm_isr0,
+ "isr1s": mtm_isr1, # Identical to ISR(1)
+ "isr1": mtm_isr1,
+ "isr2": mtm_isr2,
+ "cvs-isr0": mtm_cvs_isr0,
+ "cvs-isr1s": mtm_cvs_isr0, # Identical to CVS-ISR(0)
+ "cvs-isr1": mtm_cvs_isr0, # Identical to CVS-ISR(0)
+ "cvs-isr2": mtm_cvs_isr2,
}
def modified_transition_moments(method, ground_state, operator=None,
intermediates=None):
"""Compute the modified transition moments (MTM) for the provided
- ADC method with reference to the passed ground state.
+ ISR method with reference to the passed ground state.
Parameters
----------
- method: adc.Method
+ method: adcc.IsrMethod
Provide a method at which to compute the MTMs
ground_state : adcc.LazyMp
The MP ground state
@@ -109,8 +110,8 @@ def modified_transition_moments(method, ground_state, operator=None,
-------
adcc.AmplitudeVector or list of adcc.AmplitudeVector
"""
- if not isinstance(method, AdcMethod):
- method = AdcMethod(method)
+ if not isinstance(method, IsrMethod):
+ method = IsrMethod(method)
if not isinstance(ground_state, LazyMp):
raise TypeError("ground_state should be a LazyMp object.")
if intermediates is None:
diff --git a/adcc/adc_pp/state2state_transition_dm.py b/adcc/adc_pp/state2state_transition_dm.py
index b9c3755e..2caf5a17 100644
--- a/adcc/adc_pp/state2state_transition_dm.py
+++ b/adcc/adc_pp/state2state_transition_dm.py
@@ -22,7 +22,7 @@
## ---------------------------------------------------------------------
from adcc import block as b
from adcc.LazyMp import LazyMp
-from adcc.AdcMethod import AdcMethod
+from adcc.AdcMethod import IsrMethod
from adcc.functions import einsum
from adcc.Intermediates import Intermediates
from adcc.AmplitudeVector import AmplitudeVector
@@ -32,7 +32,7 @@
from .util import check_doubles_amplitudes, check_singles_amplitudes
-def s2s_tdm_adc0(mp, amplitude_l, amplitude_r, intermediates):
+def s2s_tdm_isr0(mp, amplitude_l, amplitude_r, intermediates):
check_singles_amplitudes([b.o, b.v], amplitude_l, amplitude_r)
ul1 = amplitude_l.ph
ur1 = amplitude_r.ph
@@ -43,9 +43,24 @@ def s2s_tdm_adc0(mp, amplitude_l, amplitude_r, intermediates):
return dm
-def s2s_tdm_adc2(mp, amplitude_l, amplitude_r, intermediates):
+def s2s_tdm_isr1(mp, amplitude_l, amplitude_r, intermediates):
+ dm = s2s_tdm_isr0(mp, amplitude_l, amplitude_r, intermediates)
+
+ try:
+ check_doubles_amplitudes([b.o, b.o, b.v, b.v], amplitude_l, amplitude_r)
+ ul1, ul2 = amplitude_l.ph, amplitude_l.pphh
+ ur1, ur2 = amplitude_r.ph, amplitude_r.pphh
+ dm.ov += -2.0 * einsum("jb,ijab->ia", ul1, ur2)
+ dm.vo += -2.0 * einsum("ijab,jb->ai", ul2, ur1)
+ except ValueError:
+ pass
+
+ return dm
+
+
+def s2s_tdm_isr2(mp, amplitude_l, amplitude_r, intermediates):
check_doubles_amplitudes([b.o, b.o, b.v, b.v], amplitude_l, amplitude_r)
- dm = s2s_tdm_adc0(mp, amplitude_l, amplitude_r, intermediates)
+ dm = s2s_tdm_isr1(mp, amplitude_l, amplitude_r, intermediates)
ul1, ul2 = amplitude_l.ph, amplitude_l.pphh
ur1, ur2 = amplitude_r.ph, amplitude_r.pphh
@@ -59,8 +74,8 @@ def s2s_tdm_adc2(mp, amplitude_l, amplitude_r, intermediates):
rul1 = einsum('ijab,jb->ia', t2, ul1).evaluate()
rur1 = einsum('ijab,jb->ia', t2, ur1).evaluate()
- dm.oo = (
- p1_oo - 2.0 * einsum('ikab,jkab->ij', ur2, ul2)
+ dm.oo += (
+ - 2.0 * einsum('ikab,jkab->ij', ur2, ul2)
+ 0.5 * einsum('ik,kj->ij', p1_oo, p0.oo)
+ 0.5 * einsum('ik,kj->ij', p0.oo, p1_oo)
- 0.5 * einsum('ikcd,lk,jlcd->ij', t2, p1_oo, t2)
@@ -69,8 +84,8 @@ def s2s_tdm_adc2(mp, amplitude_l, amplitude_r, intermediates):
- 0.5 * einsum('ikac,kc,ja->ij', t2, rur1, ul1)
- 1.0 * einsum('ia,ja->ij', rul1, rur1)
)
- dm.vv = (
- p1_vv + 2.0 * einsum('ijac,ijbc->ab', ul2, ur2)
+ dm.vv += (
+ + 2.0 * einsum('ijac,ijbc->ab', ul2, ur2)
- 0.5 * einsum("ac,cb->ab", p1_vv, p0.vv)
- 0.5 * einsum("ac,cb->ab", p0.vv, p1_vv)
- 0.5 * einsum("klbc,klad,cd->ab", t2, t2, p1_vv)
@@ -80,19 +95,18 @@ def s2s_tdm_adc2(mp, amplitude_l, amplitude_r, intermediates):
+ 1.0 * einsum("ia,ib->ab", rur1, rul1)
)
+ # (TODO Move to intermediates)
p1_ov = -2.0 * einsum("jb,ijab->ia", ul1, ur2).evaluate()
p1_vo = -2.0 * einsum("ijab,jb->ai", ul2, ur1).evaluate()
- dm.ov = (
- p1_ov
+ dm.ov += (
- einsum("ijab,bj->ia", t2, p1_vo)
- einsum("ib,ba->ia", p0.ov, p1_vv)
+ einsum("ij,ja->ia", p1_oo, p0.ov)
- einsum("ib,klca,klcb->ia", ur1, t2, ul2)
- einsum("ikcd,jkcd,ja->ia", t2, ul2, ur1)
)
- dm.vo = (
- p1_vo
+ dm.vo += (
- einsum("ijab,jb->ai", t2, p1_ov)
- einsum("ib,ab->ai", p0.ov, p1_vv)
+ einsum("ji,ja->ai", p1_oo, p0.ov)
@@ -103,10 +117,10 @@ def s2s_tdm_adc2(mp, amplitude_l, amplitude_r, intermediates):
# Ref: https://doi.org/10.1080/00268976.2013.859313
-DISPATCH = {"adc0": s2s_tdm_adc0,
- "adc1": s2s_tdm_adc0, # same as ADC(0)
- "adc2": s2s_tdm_adc2,
- "adc2x": s2s_tdm_adc2, # same as ADC(2)
+DISPATCH = {"isr0": s2s_tdm_isr0,
+ "isr1s": s2s_tdm_isr0, # Identical to ISR(0)
+ "isr1": s2s_tdm_isr1,
+ "isr2": s2s_tdm_isr2,
}
@@ -118,8 +132,8 @@ def state2state_transition_dm(method, ground_state, amplitude_from,
Parameters
----------
- method : str, AdcMethod
- The method to use for the computation (e.g. "adc2")
+ method : str, IsrMethod
+ The method to use for the computation (e.g. "isr2")
ground_state : LazyMp
The ground state upon which the excitation was based
amplitude_from : AmplitudeVector
@@ -129,8 +143,8 @@ def state2state_transition_dm(method, ground_state, amplitude_from,
intermediates : adcc.Intermediates
Intermediates from the ADC calculation to reuse
"""
- if not isinstance(method, AdcMethod):
- method = AdcMethod(method)
+ if not isinstance(method, IsrMethod):
+ method = IsrMethod(method)
if not isinstance(ground_state, LazyMp):
raise TypeError("ground_state should be a LazyMp object.")
if not isinstance(amplitude_from, AmplitudeVector):
diff --git a/adcc/adc_pp/state_diffdm.py b/adcc/adc_pp/state_diffdm.py
index 83876f4e..e0f738cf 100644
--- a/adcc/adc_pp/state_diffdm.py
+++ b/adcc/adc_pp/state_diffdm.py
@@ -24,7 +24,7 @@
from adcc import block as b
from adcc.LazyMp import LazyMp
-from adcc.AdcMethod import AdcMethod
+from adcc.AdcMethod import IsrMethod
from adcc.functions import einsum
from adcc.Intermediates import Intermediates
from adcc.AmplitudeVector import AmplitudeVector
@@ -34,7 +34,7 @@
from .util import check_doubles_amplitudes, check_singles_amplitudes
-def diffdm_adc0(mp, amplitude, intermediates):
+def diffdm_isr0(mp, amplitude, intermediates):
# C is either c(ore) or o(ccupied)
C = b.c if mp.has_core_occupied_space else b.o
check_singles_amplitudes([C, b.v], amplitude)
@@ -46,22 +46,36 @@ def diffdm_adc0(mp, amplitude, intermediates):
return dm
-def diffdm_adc2(mp, amplitude, intermediates):
- dm = diffdm_adc0(mp, amplitude, intermediates) # Get ADC(1) result
+def diffdm_isr1(mp, amplitude, intermediates):
+ dm = diffdm_isr0(mp, amplitude, intermediates) # Get ISR(0) result
+
+ try:
+ # ISR(1)-d
+ check_doubles_amplitudes([b.o, b.o, b.v, b.v], amplitude)
+ u1, u2 = amplitude.ph, amplitude.pphh
+ dm.ov += -2 * einsum("jb,ijab->ia", u1, u2)
+ except ValueError:
+ # no doubles contribution
+ pass
+ return dm
+
+
+def diffdm_isr2(mp, amplitude, intermediates):
+ dm = diffdm_isr1(mp, amplitude, intermediates) # Get ISR(1) result
check_doubles_amplitudes([b.o, b.o, b.v, b.v], amplitude)
u1, u2 = amplitude.ph, amplitude.pphh
t2 = mp.t2(b.oovv)
p0 = mp.mp2_diffdm
- p1_oo = dm.oo.evaluate() # ADC(1) diffdm
- p1_vv = dm.vv.evaluate() # ADC(1) diffdm
+ p1_oo = dm.oo.evaluate() # ISR(1) diffdm
+ p1_vv = dm.vv.evaluate() # ISR(1) diffdm
# Zeroth order doubles contributions
p2_oo = -einsum("ikab,jkab->ij", u2, u2)
p2_vv = einsum("ijac,ijbc->ab", u2, u2)
p2_ov = -2 * einsum("jb,ijab->ia", u1, u2).evaluate()
- # ADC(2) ISR intermediate (TODO Move to intermediates)
+ # ISR(2) intermediate (TODO Move to intermediates)
ru1 = einsum("ijab,jb->ia", t2, u1).evaluate()
# Compute second-order contributions to the density matrix
@@ -85,8 +99,7 @@ def diffdm_adc2(mp, amplitude, intermediates):
).symmetrise()
)
- dm.ov = ( # adc2_p_ov
- + p2_ov
+ dm.ov += ( # adc2_p_ov
- einsum("ijab,jb->ia", t2, p2_ov)
- einsum("ib,ba->ia", p0.ov, p1_vv)
+ einsum("ij,ja->ia", p1_oo, p0.ov)
@@ -96,52 +109,66 @@ def diffdm_adc2(mp, amplitude, intermediates):
return dm
-def diffdm_cvs_adc2(mp, amplitude, intermediates):
- dm = diffdm_adc0(mp, amplitude, intermediates) # Get ADC(1) result
+def diffdm_cvs_isr1(mp, amplitude, intermediates):
+ dm = diffdm_isr0(mp, amplitude, intermediates) # Get ISR(0) result
+
+ try:
+ # ISR(1)-d
+ check_doubles_amplitudes([b.o, b.c, b.v, b.v], amplitude)
+ u1, u2 = amplitude.ph, amplitude.pphh
+ p2_ov = -sqrt(2) * einsum("jb,ijab->ia", u1, u2)
+ dm.ov += p2_ov
+ except ValueError:
+ # no doubles contribution
+ pass
+ return dm
+
+
+def diffdm_cvs_isr2(mp, amplitude, intermediates):
+ dm = diffdm_cvs_isr1(mp, amplitude, intermediates) # Get cvs-ISR(1) result
check_doubles_amplitudes([b.o, b.c, b.v, b.v], amplitude)
u1, u2 = amplitude.ph, amplitude.pphh
t2 = mp.t2(b.oovv)
p0 = intermediates.cvs_p0
- p1_vv = dm.vv.evaluate() # ADC(1) diffdm
+ p1_vv = dm.vv.evaluate() # ISR(1) diffdm
# Zeroth order doubles contributions
- p2_ov = -sqrt(2) * einsum("jb,ijab->ia", u1, u2)
p2_vo = -sqrt(2) * einsum("ijab,jb->ai", u2, u1)
p2_oo = -einsum("ljab,kjab->kl", u2, u2)
p2_vv = 2 * einsum("ijac,ijbc->ab", u2, u2)
# Second order contributions
- # cvs_adc2_dp_oo
- dm.oo = p2_oo + einsum("ab,ikac,jkbc->ij", p1_vv, t2, t2)
+ # cvs_isr2_dp_oo
+ dm.oo += p2_oo + einsum("ab,ikac,jkbc->ij", p1_vv, t2, t2)
- dm.ov = p2_ov + ( # cvs_adc2_dp_ov
+ dm.ov += ( # cvs_isr2_dp_ov
- einsum("ka,ab->kb", p0.ov, p1_vv)
- einsum("lkdb,dl->kb", t2, p2_vo)
+ 1 / sqrt(2) * einsum("ib,klad,liad->kb", u1, t2, u2)
)
- dm.vv = p1_vv + p2_vv - 0.5 * ( # cvs_adc2_dp_vv
+ dm.vv += p2_vv - 0.5 * ( # cvs_isr2_dp_vv
+ einsum("cb,ac->ab", p1_vv, p0.vv)
+ einsum("cb,ac->ab", p0.vv, p1_vv)
+ einsum("ijbc,ijad,cd->ab", t2, t2, p1_vv)
)
- # Add 2nd order correction to CVS-ADC(1) diffdm
+ # Add 2nd order correction to CVS-ISR(1) diffdm
dm.cc -= einsum("kIab,kJab->IJ", u2, u2)
return dm
# dict controlling the dispatch of the state_diffdm function
DISPATCH = {
- "adc0": diffdm_adc0,
- "adc1": diffdm_adc0, # same as ADC(0)
- "adc2": diffdm_adc2,
- "adc2x": diffdm_adc2,
- "cvs-adc0": diffdm_adc0,
- "cvs-adc1": diffdm_adc0, # same as ADC(0)
- "cvs-adc2": diffdm_cvs_adc2,
- "cvs-adc2x": diffdm_cvs_adc2,
+ "isr0": diffdm_isr0,
+ "isr1s": diffdm_isr0, # Identical to ISR(0)
+ "isr1": diffdm_isr1,
+ "isr2": diffdm_isr2,
+ "cvs-isr0": diffdm_isr0,
+ "cvs-isr1s": diffdm_isr0, # Identical to ISR(0)
+ "cvs-isr1": diffdm_cvs_isr1,
+ "cvs-isr2": diffdm_cvs_isr2,
}
@@ -152,8 +179,8 @@ def state_diffdm(method, ground_state, amplitude, intermediates=None):
Parameters
----------
- method : str, AdcMethod
- The method to use for the computation (e.g. "adc2")
+ method : str, IsrMethod
+ The method to use for the computation (e.g. "isr2")
ground_state : LazyMp
The ground state upon which the excitation was based
amplitude : AmplitudeVector
@@ -161,8 +188,8 @@ def state_diffdm(method, ground_state, amplitude, intermediates=None):
intermediates : adcc.Intermediates
Intermediates from the ADC calculation to reuse
"""
- if not isinstance(method, AdcMethod):
- method = AdcMethod(method)
+ if not isinstance(method, IsrMethod):
+ method = IsrMethod(method)
if not isinstance(ground_state, LazyMp):
raise TypeError("ground_state should be a LazyMp object.")
if not isinstance(amplitude, AmplitudeVector):
diff --git a/adcc/adc_pp/state_diffdm_2p.py b/adcc/adc_pp/state_diffdm_2p.py
index b6c31f5c..a6e41057 100644
--- a/adcc/adc_pp/state_diffdm_2p.py
+++ b/adcc/adc_pp/state_diffdm_2p.py
@@ -22,7 +22,7 @@
## ---------------------------------------------------------------------
from adcc import block as b
from adcc.LazyMp import LazyMp
-from adcc.AdcMethod import AdcMethod
+from adcc.AdcMethod import IsrMethod
from adcc.functions import einsum, zeros_like
from adcc.Intermediates import Intermediates
from adcc.AmplitudeVector import AmplitudeVector
@@ -32,7 +32,7 @@
from .util import check_doubles_amplitudes, check_singles_amplitudes
-def diffdm_adc0_2p(mp, amplitude, intermediates):
+def diffdm_isr0_2p(mp, amplitude, intermediates):
check_singles_amplitudes([b.o, b.v], amplitude)
u1 = amplitude.ph
@@ -42,7 +42,7 @@ def diffdm_adc0_2p(mp, amplitude, intermediates):
dm = TwoParticleDensity(mp, symmetry=OperatorSymmetry.HERMITIAN)
- # ADC(1) diffdm
+ # one-particle ISR(0) diffdm
p1_oo = -einsum("ia,la->il", u1, u1).evaluate()
p1_vv = einsum("ka,kb->ab", u1, u1).evaluate()
@@ -59,8 +59,8 @@ def diffdm_adc0_2p(mp, amplitude, intermediates):
return dm
-def diffdm_adc1_2p(mp, amplitude, intermediates):
- dm = diffdm_adc0_2p(mp, amplitude, intermediates) # Get ADC(0) result
+def diffdm_isr1s_2p(mp, amplitude, intermediates):
+ dm = diffdm_isr0_2p(mp, amplitude, intermediates) # Get ISR(0) result
u1 = amplitude.ph
hf = mp.reference_state
@@ -69,10 +69,10 @@ def diffdm_adc1_2p(mp, amplitude, intermediates):
t2 = mp.t2(b.oovv)
# TODO move to intermediates!
- # ADC(1) diffdm
+ # one-particle ISR(0) diffdm
p1_oo = -einsum("ia,la->il", u1, u1).evaluate()
- # ADC(2) ISR intermediate (TODO Move to intermediates)
+ # ISR(2) ISR intermediate (TODO Move to intermediates)
ru1 = einsum("ijab,jb->ia", t2, u1).evaluate()
# new ones
ru1_ooov = einsum("kc,ijac->ijka", u1, t2).evaluate()
@@ -91,11 +91,65 @@ def diffdm_adc1_2p(mp, amplitude, intermediates):
einsum("jk,ikab->ijab", p1_oo, t2)
).antisymmetrise(0, 1)
)
+
return dm
-def diffdm_adc2_2p(mp, amplitude, intermediates):
- dm = diffdm_adc1_2p(mp, amplitude, intermediates) # Get ADC(1) result
+def diffdm_isr1_2p(mp, amplitude, intermediates):
+ dm = diffdm_isr0_2p(mp, amplitude, intermediates) # Get ISR(0) result
+ u1 = amplitude.ph
+
+ hf = mp.reference_state
+ d_oo = zeros_like(hf.foo)
+ d_oo.set_mask("ii", 1)
+
+ t2 = mp.t2(b.oovv)
+ # TODO move to intermediates!
+ # one-particle ISR(0) diffdm
+ p1_oo = -einsum("ia,la->il", u1, u1).evaluate()
+
+ # ISR(2) ISR intermediate (TODO Move to intermediates)
+ ru1 = einsum("ijab,jb->ia", t2, u1).evaluate()
+ # new ones
+ ru1_ooov = einsum("kc,ijac->ijka", u1, t2).evaluate()
+
+ dm.oovv += (
+ # N^4: O^2V^2 / N^4: O^2V^2
+ + 4.0 * (
+ einsum("ib,ja->ijab", ru1, u1)
+ ).antisymmetrise(0, 1).antisymmetrise(2, 3)
+ # N^5: O^3V^2 / N^4: O^2V^2
+ + 2.0 * (
+ einsum("ijka,kb->ijab", ru1_ooov, u1)
+ ).antisymmetrise(2, 3)
+ # N^5: O^3V^2 / N^4: O^2V^2
+ - 2.0 * (
+ einsum("jk,ikab->ijab", p1_oo, t2)
+ ).antisymmetrise(0, 1)
+ )
+
+ try:
+ # ISR(1)-d
+ check_doubles_amplitudes([b.o, b.o, b.v, b.v], amplitude)
+ u2 = amplitude.pphh
+
+ dm.ooov += (
+ # N^5: O^3V^2 / N^4: O^2V^2
+ - 2.0 * einsum("kb,ijab->ijka", u1, u2)
+ )
+
+ dm.ovvv += (
+ # N^5: O^2V^3 / N^4: O^1V^3
+ - 2.0 * einsum("ja,ijbc->iabc", u1, u2)
+ )
+ except ValueError:
+ # no doubles contribution
+ pass
+ return dm
+
+
+def diffdm_isr2_2p(mp, amplitude, intermediates):
+ dm = diffdm_isr1_2p(mp, amplitude, intermediates) # Get ISR(1) result
check_doubles_amplitudes([b.o, b.o, b.v, b.v], amplitude)
u1, u2 = amplitude.ph, amplitude.pphh
hf = mp.reference_state
@@ -160,8 +214,6 @@ def diffdm_adc2_2p(mp, amplitude, intermediates):
).antisymmetrise(0, 1).antisymmetrise(2, 3).symmetrise([(0, 2), (1, 3)])
)
dm.ooov += (
- # N^5: O^3V^2 / N^4: O^2V^2
- - 2.0 * einsum("kb,ijab->ijka", u1, u2)
# N^6: O^4V^2 / N^4: O^2V^2
+ 1.0 * einsum("ijkl,la->ijka", einsum("klbc,ijbc->ijkl", u2, t2), u1)
# N^5: O^3V^2 / N^4: O^2V^2
@@ -293,8 +345,6 @@ def diffdm_adc2_2p(mp, amplitude, intermediates):
).symmetrise([(0, 2), (1, 3)])
)
dm.ovvv += (
- # N^5: O^2V^3 / N^4: O^1V^3
- - 2.0 * einsum("ja,ijbc->iabc", u1, u2)
# N^6: O^3V^3 / N^4: O^1V^3
+ 1.0 * einsum("ijka,jkbc->iabc", einsum("id,jkad->ijka", u1, u2), t2)
# N^5: O^2V^3 / N^4: O^1V^3
@@ -340,10 +390,10 @@ def diffdm_adc2_2p(mp, amplitude, intermediates):
# dict controlling the dispatch of the state_diffdm function
DISPATCH = {
- "adc0": diffdm_adc0_2p,
- "adc1": diffdm_adc1_2p,
- "adc2": diffdm_adc2_2p,
- "adc2x": diffdm_adc2_2p, # same as ADC(2)
+ "isr0": diffdm_isr0_2p,
+ "isr1s": diffdm_isr1s_2p,
+ "isr1": diffdm_isr1_2p,
+ "isr2": diffdm_isr2_2p,
}
@@ -354,8 +404,8 @@ def state_diffdm_2p(method, ground_state, amplitude, intermediates=None):
Parameters
----------
- method : str, AdcMethod
- The method to use for the computation (e.g. "adc2")
+ method : str, IsrMethod
+ The method to use for the computation (e.g. "isr2")
ground_state : LazyMp
The ground state upon which the excitation was based
amplitude : AmplitudeVector
@@ -363,8 +413,8 @@ def state_diffdm_2p(method, ground_state, amplitude, intermediates=None):
intermediates : adcc.Intermediates
Intermediates from the ADC calculation to reuse
"""
- if not isinstance(method, AdcMethod):
- method = AdcMethod(method)
+ if not isinstance(method, IsrMethod):
+ method = IsrMethod(method)
if not isinstance(ground_state, LazyMp):
raise TypeError("ground_state should be a LazyMp object.")
if not isinstance(amplitude, AmplitudeVector):
diff --git a/adcc/adc_pp/transition_dm.py b/adcc/adc_pp/transition_dm.py
index 65a40d0f..67a9629d 100644
--- a/adcc/adc_pp/transition_dm.py
+++ b/adcc/adc_pp/transition_dm.py
@@ -24,7 +24,7 @@
from adcc import block as b
from adcc.LazyMp import LazyMp
-from adcc.AdcMethod import AdcMethod
+from adcc.AdcMethod import IsrMethod
from adcc.functions import einsum
from adcc.Intermediates import Intermediates
from adcc.AmplitudeVector import AmplitudeVector
@@ -34,67 +34,65 @@
from .util import check_doubles_amplitudes, check_singles_amplitudes
-def tdm_adc0(mp, amplitude, intermediates):
+def tdm_isr0(mp, amplitude, intermediates):
# C is either c(ore) or o(ccupied)
C = b.c if mp.has_core_occupied_space else b.o
check_singles_amplitudes([C, b.v], amplitude)
u1 = amplitude.ph
- # Transition density matrix for (CVS-)ADC(0)
+ # Transition density matrix for (CVS-)ISR(0)
dm = OneParticleDensity(mp, symmetry=OperatorSymmetry.NOSYMMETRY)
dm[b.v + C] = u1.transpose()
return dm
-def tdm_adc1(mp, amplitude, intermediates):
- dm = tdm_adc0(mp, amplitude, intermediates) # Get ADC(0) result
- # adc1_dp0_ov
+def tdm_isr1(mp, amplitude, intermediates):
+ dm = tdm_isr0(mp, amplitude, intermediates) # Get ISR(0) result
+ # isr1_dp0_ov
dm.ov = -einsum("ijab,jb->ia", mp.t2(b.oovv), amplitude.ph)
return dm
-def tdm_cvs_adc2(mp, amplitude, intermediates):
- # Get CVS-ADC(1) result (same as CVS-ADC(0))
- dm = tdm_adc0(mp, amplitude, intermediates)
+def tdm_cvs_isr2(mp, amplitude, intermediates):
+ # Get CVS-ISR(1) result (same as CVS-ISR(0))
+ dm = tdm_isr0(mp, amplitude, intermediates)
check_doubles_amplitudes([b.o, b.c, b.v, b.v], amplitude)
- u1 = amplitude.ph
- u2 = amplitude.pphh
+ u1, u2 = amplitude.ph, amplitude.pphh
t2 = mp.t2(b.oovv)
p0 = intermediates.cvs_p0
- # Compute CVS-ADC(2) tdm
- dm.oc = ( # cvs_adc2_dp0_oc
+ # Compute CVS-ISR(2) tdm
+ dm.oc = ( # cvs_isr2_dp0_oc
- einsum("ja,Ia->jI", p0.ov, u1)
+ (1 / sqrt(2)) * einsum("kIab,jkab->jI", u2, t2)
)
- # cvs_adc2_dp0_vc
+ # cvs_isr2_dp0_vc
dm.vc -= 0.5 * einsum("ab,Ib->aI", p0.vv, u1)
return dm
-def tdm_adc2(mp, amplitude, intermediates):
- dm = tdm_adc1(mp, amplitude, intermediates) # Get ADC(1) result
+def tdm_isr2(mp, amplitude, intermediates):
+ dm = tdm_isr1(mp, amplitude, intermediates) # Get ISR(1) result
check_doubles_amplitudes([b.o, b.o, b.v, b.v], amplitude)
- u1 = amplitude.ph
- u2 = amplitude.pphh
+ u1, u2 = amplitude.ph, amplitude.pphh
t2 = mp.t2(b.oovv)
td2 = mp.td2(b.oovv)
p0 = mp.mp2_diffdm
- # Compute ADC(2) tdm
- dm.oo = ( # adc2_dp0_oo
+ # Compute ISR(2) tdm
+ dm.oo = ( # isr2_dp0_oo
- einsum("ia,ja->ij", p0.ov, u1)
- einsum("ikab,jkab->ji", u2, t2)
)
- dm.vv = ( # adc2_dp0_vv
+ dm.vv = ( # isr2_dp0_vv
+ einsum("ia,ib->ab", u1, p0.ov)
+ einsum("ijac,ijbc->ab", u2, t2)
)
- dm.ov -= einsum("ijab,jb->ia", td2, u1) # adc2_dp0_ov
- dm.vo += 0.5 * ( # adc2_dp0_vo
+ dm.ov -= einsum("ijab,jb->ia", td2, u1) # isr2_dp0_ov
+ dm.vo += 0.5 * ( # isr2_dp0_vo
+ einsum("ijab,jkbc,kc->ai", t2, t2, u1)
- einsum("ab,ib->ai", p0.vv, u1)
+ einsum("ja,ij->ai", u1, p0.oo)
@@ -103,14 +101,13 @@ def tdm_adc2(mp, amplitude, intermediates):
DISPATCH = {
- "adc0": tdm_adc0,
- "adc1": tdm_adc1,
- "adc2": tdm_adc2,
- "adc2x": tdm_adc2,
- "cvs-adc0": tdm_adc0,
- "cvs-adc1": tdm_adc0, # No extra contribs for CVS-ADC(1)
- "cvs-adc2": tdm_cvs_adc2,
- "cvs-adc2x": tdm_cvs_adc2,
+ "isr0": tdm_isr0,
+ "isr1s": tdm_isr1, # Identical to ISR(1)
+ "isr1": tdm_isr1,
+ "isr2": tdm_isr2,
+ "cvs-isr0": tdm_isr0,
+ "cvs-isr1": tdm_isr0, # No extra contribs for CVS-ISR(1)
+ "cvs-isr2": tdm_cvs_isr2,
}
@@ -121,8 +118,8 @@ def transition_dm(method, ground_state, amplitude, intermediates=None):
Parameters
----------
- method : str, AdcMethod
- The method to use for the computation (e.g. "adc2")
+ method : str, IsrMethod
+ The method to use for the computation (e.g. "isr2")
ground_state : LazyMp
The ground state upon which the excitation was based
amplitude : AmplitudeVector
@@ -130,8 +127,8 @@ def transition_dm(method, ground_state, amplitude, intermediates=None):
intermediates : adcc.Intermediates
Intermediates from the ADC calculation to reuse
"""
- if not isinstance(method, AdcMethod):
- method = AdcMethod(method)
+ if not isinstance(method, IsrMethod):
+ method = IsrMethod(method)
if not isinstance(ground_state, LazyMp):
raise TypeError("ground_state should be a LazyMp object.")
if not isinstance(amplitude, AmplitudeVector):
diff --git a/adcc/tests/AdcMatrix_dense_export_test.py b/adcc/tests/AdcMatrix_dense_export_test.py
index b449ce26..dadf150b 100644
--- a/adcc/tests/AdcMatrix_dense_export_test.py
+++ b/adcc/tests/AdcMatrix_dense_export_test.py
@@ -48,13 +48,13 @@ def test_h2o(self, case: str, method: str):
method = f"cvs-{method}"
method: adcc.AdcMethod = adcc.AdcMethod(method)
n_states = 7
- if method.level == 1: # only few states available
+ if method.level.to_int() == 1: # only few states available
n_states = 5
if method.is_core_valence_separated:
n_states = 1 if "fv" in case else 2
elif "fc" in case and "fv" in case:
n_states = 4
- elif method.level == 2 and method.is_core_valence_separated:
+ elif method.level.to_int() == 2 and method.is_core_valence_separated:
# there seems to be a difference for higher cvs-adc2 states...
n_states = 3
diff --git a/adcc/tests/AdcMatrix_test.py b/adcc/tests/AdcMatrix_test.py
index 325eb2f3..c77d9c6c 100644
--- a/adcc/tests/AdcMatrix_test.py
+++ b/adcc/tests/AdcMatrix_test.py
@@ -226,7 +226,9 @@ def test_properties(self, system: str, case: str, method: str):
# check that the blocks are correct
blocks = matrix.axis_blocks
if matrix.method.adc_type == "pp":
- assert blocks == ["ph", "pphh", "ppphhh"][:matrix.method.level // 2 + 1]
+ assert blocks == (
+ ["ph", "pphh", "ppphhh"][:matrix.method.level.to_int() // 2 + 1]
+ )
else:
raise NotImplementedError(f"Unknown adc type {matrix.method.adc_type}.")
assert sorted(matrix.axis_spaces.keys(), key=len) == blocks
diff --git a/adcc/tests/AdcMethod_test.py b/adcc/tests/AdcMethod_test.py
new file mode 100644
index 00000000..87a2f97d
--- /dev/null
+++ b/adcc/tests/AdcMethod_test.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python3
+## vi: tabstop=4 shiftwidth=4 softtabstop=4 expandtab
+## ---------------------------------------------------------------------
+##
+## Copyright (C) 2026 by the adcc authors
+##
+## This file is part of adcc.
+##
+## adcc is free software: you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## adcc is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with adcc. If not, see .
+##
+## ---------------------------------------------------------------------
+import pytest
+
+import adcc
+
+adc_methods = [("adc1", None), ("adc2x", None), ("cvs-adc3", None),
+ ("adc", ValueError), ("cvs_adc2", ValueError),
+ ("xyz-adc2", ValueError), ("adc5", NotImplementedError),
+ ("isr2", ValueError)]
+
+isr_methods = [("isr1", None), ("cvs-isr2", None),
+ ("adc", ValueError), ("cvs_isr2", ValueError),
+ ("xyz-isr2", ValueError), ("isr5", NotImplementedError),
+ ("adc2", ValueError)]
+
+
+class TestAdcMethod:
+ @pytest.mark.parametrize("method, expected_exception", adc_methods)
+ def test_validate_adcmethod(self, method, expected_exception):
+ if expected_exception:
+ with pytest.raises(expected_exception):
+ adcc.AdcMethod(method)
+ else:
+ adc_method = adcc.AdcMethod(method)
+ assert adc_method.name == method
+
+ def test_adcmethod(self):
+ method = adcc.AdcMethod("adc2")
+ cvs_method = adcc.AdcMethod("cvs-adc2")
+
+ assert method.name == cvs_method.base_method.name
+
+ method_new_level = method.at_level(1)
+ assert method_new_level._method_base_name == method._method_base_name
+ assert method_new_level.level.to_int() == 1
+
+ as_isr_method = method.as_method(adcc.IsrMethod)
+ assert isinstance(as_isr_method, adcc.IsrMethod)
+
+
+class TestIsrMethod:
+ @pytest.mark.parametrize("method, expected_exception", isr_methods)
+ def test_validate_isrmethod(self, method, expected_exception):
+ if expected_exception:
+ with pytest.raises(expected_exception):
+ adcc.IsrMethod(method)
+ else:
+ isr_method = adcc.IsrMethod(method)
+ assert isr_method.name == method
+
+ def test_isrmethod(self):
+ method = adcc.IsrMethod("isr2")
+ cvs_method = adcc.IsrMethod("cvs-isr2")
+
+ assert method.name == cvs_method.base_method.name
+
+ method_new_level = method.at_level(1)
+ assert method_new_level._method_base_name == method._method_base_name
+ assert method_new_level.level.to_int() == 1
+
+ as_adc_method = method.as_method(adcc.AdcMethod)
+ assert isinstance(as_adc_method, adcc.AdcMethod)
diff --git a/adcc/tests/IsrMatrix_test.py b/adcc/tests/IsrMatrix_test.py
index 3a174fa9..f50f23bf 100644
--- a/adcc/tests/IsrMatrix_test.py
+++ b/adcc/tests/IsrMatrix_test.py
@@ -48,7 +48,7 @@ def test_matrix_vector_product(self, system: str, case: str, kind: str,
state = testdata_cache.adcc_states(
system=system, method=method, kind=kind, case=case
)
-
+ method = method.replace("adc", "isr")
n_ref = len(state.excitation_vector)
mp = state.ground_state
if operator_kind == "electric": # example of a symmetric operator
@@ -96,6 +96,8 @@ def test_matvec(self):
state = testdata_cache.adcc_states(
system=system, method=method, kind=kind, case="gen"
)
+
+ method = "isr2"
assert len(state.excitation_vector) > 1
mp = state.ground_state
dips = state.reference_state.operators.electric_dipole
diff --git a/adcc/tests/adc_pp/modified_transition_moments_test.py b/adcc/tests/adc_pp/modified_transition_moments_test.py
index 4c05f1d3..cb34ed66 100644
--- a/adcc/tests/adc_pp/modified_transition_moments_test.py
+++ b/adcc/tests/adc_pp/modified_transition_moments_test.py
@@ -54,7 +54,7 @@ def test_modified_transition_moments(system: str, case: str, method: str, kind:
)[kind]
n_ref = len(state.excitation_vector)
-
+ method = method.replace("adc", "isr")
if op_kind == "electric":
dips = state.reference_state.operators.electric_dipole
ref_tdm = ref["transition_dipole_moments"]
diff --git a/adcc/tests/adc_pp/state_diffdm_test.py b/adcc/tests/adc_pp/state_diffdm_test.py
index 24c07cd5..780fca75 100644
--- a/adcc/tests/adc_pp/state_diffdm_test.py
+++ b/adcc/tests/adc_pp/state_diffdm_test.py
@@ -24,7 +24,7 @@
import pytest
from adcc import block as b
-from adcc.AdcMethod import AdcMethod
+from adcc.AdcMethod import IsrMethod, MethodLevel
from adcc.functions import evaluate, einsum
from adcc.OneParticleDensity import OneParticleDensity
from adcc.NParticleOperator import OperatorSymmetry
@@ -51,17 +51,27 @@ def calculate_adcn_excitation_energy(self, state):
mp = state.ground_state
n_states = len(state.excitation_energy)
excitation_energy = np.zeros((n_states))
- method = state.method
- level = method.level
+ if state.method.name == "adc3":
+ # so we don't forget to switch to the actual implementation
+ with pytest.raises(NotImplementedError):
+ method = IsrMethod("isr3")
+
+ # TODO switch to ISR(3) implementation
+ method = IsrMethod("isr2")
+ method.level = MethodLevel(3)
+
+ else:
+ method = state.property_method
+ level = method.level.to_int()
method_order_minus_one = None
if level - 1 >= 0:
- method_order_minus_one = AdcMethod("adc" + str(level - 1))
+ method_order_minus_one = IsrMethod("isr" + str(level - 1))
for es in range(n_states):
evec = state.excitation_vector[es]
# TODO switch to ISR(3) implemntation
- if method.level == 3:
+ if method.level.to_int() == 3:
# so we don't forget to switch to the actual implementation
with pytest.raises(NotImplementedError):
state_diffdm(method, mp, evec)
@@ -77,14 +87,6 @@ def calculate_adcn_excitation_energy(self, state):
if method_order_minus_one is not None:
# two particle part
dens_2p = state_diffdm_2p(method_order_minus_one, mp, evec)
- # go for ISR(1)-d for ADC(2)
- if method_order_minus_one.level == 1:
- dens_2p.ooov += (
- - 2.0 * einsum("kb,ijab->ijka", evec.ph, evec.pphh)
- )
- dens_2p.ovvv += (
- - 2.0 * einsum("ja,ijbc->iabc", evec.ph, evec.pphh)
- )
for block in dens_2p.blocks:
# compute
# 1/4 [(1 - P_pq) (1 - P_rs) 1 / (n_occ - 1) delta_qs]
diff --git a/adcc/tests/backends/backends_pcm_test.py b/adcc/tests/backends/backends_pcm_test.py
index 6ca2bb6f..85d281b6 100644
--- a/adcc/tests/backends/backends_pcm_test.py
+++ b/adcc/tests/backends/backends_pcm_test.py
@@ -119,7 +119,7 @@ def test_pcm_linear_response(self, system, case, method, backend):
atol=1e-5
)
- state_cis = adcc.ExcitedStates(state, property_method="adc0")
+ state_cis = adcc.ExcitedStates(state, property_method="isr0")
assert_allclose(
state_cis.oscillator_strength,
result["lr_osc_strength"], atol=1e-3
diff --git a/adcc/tests/functionality_test.py b/adcc/tests/functionality_test.py
index 699f1769..ba37653c 100644
--- a/adcc/tests/functionality_test.py
+++ b/adcc/tests/functionality_test.py
@@ -78,10 +78,10 @@ def base_test(self, system: testcases.TestCase, case: str, method: str,
refmp = getattr(testdata_cache, f"{generator}_data")(
system=system, method="mp", case=case
)
- if res.method.level >= 2:
+ if res.method.level.to_int() >= 2:
assert res.ground_state.energy_correction(2) == \
approx(refmp["mp2"]["energy"])
- if res.method.level >= 3:
+ if res.method.level.to_int() >= 3:
if not res.method.is_core_valence_separated:
# TODO The latter check can be removed once CVS-MP3 energies
# are implemented
@@ -126,7 +126,8 @@ def base_test(self, system: testcases.TestCase, case: str, method: str,
def test_functionality(self, system: str, case: str, method: str, kind: str,
generator: str):
method: adcc.AdcMethod = adcc.AdcMethod(method)
- if generator == "adcman" and "cvs" in case and method.level == 0:
+ if generator == "adcman" and "cvs" in case \
+ and method.level.to_int() == 0:
pytest.skip("CVS-ADC(0) adcman data is not available")
system: testcases.TestCase = testcases.get_by_filename(system).pop()
@@ -135,12 +136,12 @@ def test_functionality(self, system: str, case: str, method: str, kind: str,
kwargs = {n_states: 3}
# only few states available for h2o sto3g
if system.name == "h2o" and system.basis == "sto-3g":
- if method.level < 2: # adc0/adc1
+ if method.level.to_int() < 2: # adc0/adc1
if "cvs" in case and "fv" in case:
kwargs[n_states] = 1
elif "cvs" in case:
kwargs[n_states] = 2
- elif method.level < 4: # adc2/adc3
+ elif method.level.to_int() < 4: # adc2/adc3
if "cvs" in case and "fv" in case: # only 5 states available
kwargs["n_guesses"] = 3
diff --git a/adcc/tests/state_densities_test.py b/adcc/tests/state_densities_test.py
index b2e8954d..7da0119d 100644
--- a/adcc/tests/state_densities_test.py
+++ b/adcc/tests/state_densities_test.py
@@ -54,7 +54,8 @@ class TestStateDensities:
@pytest.mark.parametrize("system,case,kind", cases)
def test_state_diffdm(self, system: str, case: str, kind: str, method: str,
generator: str):
- if "cvs" in case and AdcMethod(method).level == 0 and generator == "adcman":
+ if ("cvs" in case and AdcMethod(method).level.to_int() == 0
+ and generator == "adcman"):
pytest.skip("No CVS-ADC(0) adcman reference data available.")
refdata = testdata_cache._load_data(
system=system, method=method, case=case, source=generator
@@ -82,7 +83,8 @@ def test_state_diffdm(self, system: str, case: str, kind: str, method: str,
[c for c in cases if c[2] != "triplet"])
def test_ground_to_excited_tdm(self, system: str, case: str, kind: str,
method: str, generator: str):
- if "cvs" in case and AdcMethod(method).level == 0 and generator == "adcman":
+ if ("cvs" in case and AdcMethod(method).level.to_int() == 0
+ and generator == "adcman"):
pytest.skip("No CVS-ADC(0) adcman reference data available.")
refdata = testdata_cache._load_data(
system=system, method=method, case=case, source=generator