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

Active space approaches #19

Merged
merged 41 commits into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
0c9172c
Undoes some space changes that don't make much sense
obackhouse May 31, 2023
6c2b9e7
Some preparation for CCSDt
obackhouse May 31, 2023
17e2e95
Simplifications for active spaces
obackhouse Jun 1, 2023
a24a9cf
CCSDt ansatz fix
obackhouse Jun 1, 2023
0d2644b
Merge branch 'master' into dev
obackhouse Jun 6, 2023
7200c6f
Changes to active_cluster_ranks
obackhouse Jun 6, 2023
94b7737
Trying to to fix stuff
obackhouse Jun 7, 2023
17b2e7b
Linting
obackhouse Jun 21, 2023
2eb0eb5
Improves active space handling
obackhouse Jun 22, 2023
1719395
More amplitudes for RCCSDt
obackhouse Jun 23, 2023
4a534b2
Towards U active space
obackhouse Jun 28, 2023
7739656
CCSDt' ansatz setup
obackhouse Jun 29, 2023
4a3f1f9
Fix
obackhouse Jun 29, 2023
5d5b975
Some fixes for SplitEBCC methods
obackhouse Jun 29, 2023
308ec92
Same CCSDt' slices for all spins
obackhouse Jun 29, 2023
72aec81
Adds CCSDt'
obackhouse Jun 29, 2023
aae0ce4
Fix some bugs after refactoring
obackhouse Jul 3, 2023
10b2f27
Fix more refactoring bugs
obackhouse Jul 3, 2023
d8b8454
Fix bug in empty einsums with TBLIS
obackhouse Jul 3, 2023
973e30c
Nuke SplitEBCC methods
obackhouse Jul 3, 2023
805b91a
Nuke Split ansatzes
obackhouse Jul 4, 2023
d8920ed
Fix return value for CCSDt'
obackhouse Jul 4, 2023
0697163
Merge branch 'master' into active_space
obackhouse Jul 4, 2023
0e4f882
Fix for EE EOM test
obackhouse Jul 4, 2023
6110dbf
Fix to options dumping
obackhouse Jul 4, 2023
f4a6645
Adds GCCSDt' tests
obackhouse Jul 4, 2023
e3460fe
Linting
obackhouse Jul 4, 2023
27a9511
Fix Q ansatz specification for GEBCC
obackhouse Jul 4, 2023
750dec7
Fix test name for GCCSDTQ
obackhouse Jul 4, 2023
13c7c18
Optimises GCCSDt'
obackhouse Jul 4, 2023
c0595aa
Adds RCCSDt' tests
obackhouse Jul 4, 2023
c6c48f6
Adds note for me
obackhouse Jul 4, 2023
98d3047
Adds CCSDt' example
obackhouse Jul 4, 2023
bfa5382
Remove debug_space hack
obackhouse Jul 5, 2023
6eaacfd
Adds UCCSDt'
obackhouse Jul 5, 2023
0d99e84
Linting
obackhouse Jul 5, 2023
882d3f8
Fixes for both frozen and active spaces
obackhouse Jul 5, 2023
11a3a89
Fixes conversion routines for frozen and active spaces
obackhouse Jul 5, 2023
147a352
Fix for bosonic conversion
obackhouse Jul 5, 2023
711930e
Linting
obackhouse Jul 5, 2023
64474fb
Adds CCSDt' to FEATURES.md
obackhouse Jul 5, 2023
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
1 change: 1 addition & 0 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ The following table summarises the available methods and routines for the ansatz
| CCSDT | RUG | RUG | RUG | | | | RUG | RUG | - |
| CCSDTQ | g | g | | | | | | | - |
| CCSD(T) | RuG | RuG | | | | | | | - |
| CCSDt' | RUG | RUG | | | | | | | - |
| CC2 | RUG | RUG | RUG | | | | RUG | RUG | - |
| CC3 | RUG | RUG | | | | | | | - |
| QCISD | RUG | RUG | | | | | | | - |
Expand Down
220 changes: 118 additions & 102 deletions ebcc/ansatz.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"CC2": ("CC2", "", 0, 0),
"CC3": ("CC3", "", 0, 0),
"QCISD": ("QCISD", "", 0, 0),
"CCSDt'": ("CCSDt'", "", 0, 0),
"CCSD-S-1-1": ("CCSD", "S", 1, 1),
"CCSD-SD-1-1": ("CCSD", "SD", 1, 1),
"CCSD-SD-1-2": ("CCSD", "SD", 1, 2),
Expand All @@ -37,6 +38,7 @@ def name_to_identifier(name):
iden = name.replace("(", "x").replace(")", "x")
iden = iden.replace("[", "y").replace("]", "y")
iden = iden.replace("-", "_")
iden = iden.replace("'", "p")

return iden

Expand All @@ -55,6 +57,7 @@ def identifity_to_name(iden):
name = name.replace("x", "(", 1).replace("x", ")", 1)
while "y" in name:
name = name.replace("y", "(", 1).replace("y", ")", 1)
name = name.replace("p", "'")

return name

Expand Down Expand Up @@ -173,124 +176,137 @@ def is_one_shot(self):
for ansatz in (self.fermion_ansatz, self.boson_ansatz)
)

@property
def correlated_cluster_ranks(self):
"""Get a list of cluster operator rank numbers for each of
the fermionic, bosonic, and coupling ansatzes, for the
correlated space (see space.py).
def fermionic_cluster_ranks(self, spin_type="G"):
"""Get a list of cluster operator ranks for the fermionic
space.

Returns
-------
ranks : tuple of tuple of int
Cluster operator ranks for the fermionic, bosonic, and
coupling ansatzes, for the correlated space.
ranks : list of tuples
List of cluster operator ranks, each element is a tuple
containing the name, the slices and the rank.
"""

ranks = []
if not self.fermion_ansatz:
return ranks

notations = {
"S": [1],
"D": [2],
"T": [3],
"Q": [4],
"2": [1, 2],
"3": [1, 2, 3],
"4": [1, 2, 3, 4],
"S": [("t1", "ov", 1)],
"D": [("t2", "oovv", 2)],
"T": [("t3", "ooovvv", 3)],
"t'": [("t3", "OOOVVV", 3)],
}

for i, op in enumerate([self.fermion_ansatz, self.boson_ansatz]):
# Remove any perturbative corrections
while "(" in op:
start = op.index("(")
end = op.index(")")
op = op[:start]
if (end + 1) < len(op):
op += op[end + 1 :]

# Check in order of longest -> shortest string in case
# one method name starts with a substring equal to the
# name of another method
if i == 0:
for method_type in sorted(METHOD_TYPES, key=len)[::-1]:
if op.startswith(method_type):
op = op.lstrip(method_type)
break

# If it's Moller-Plesset perturbation theory, we only
# need to initialise second-order amplitudes
if method_type == "MP":
op = "D"

# Remove any lower case characters, as these correspond
# to active space
op = "".join([char for char in op if char.isupper() or char.isnumeric()])

# Determine the ranks
ranks_entry = set()
for char in op:
for rank in notations[char]:
ranks_entry.add(rank)
ranks.append(tuple(sorted(list(ranks_entry))))

# Get the coupling ranks
for op in [self.fermion_coupling_rank, self.boson_coupling_rank]:
ranks.append(tuple(range(1, op + 1)))

return tuple(ranks)

@property
def active_cluster_ranks(self):
"""Get a list of cluster operator rank numbers for each of
the fermionic, bosonic, and coupling ansatzes, for the
active space (see space.py).
if spin_type == "R":
notations["Q"] = [("t4a", "oooovvvv", 4), ("t4b", "oooovvvv", 4)]
else:
notations["Q"] = [("t4", "oooovvvv", 4)]
notations["2"] = notations["S"] + notations["D"]
notations["3"] = notations["2"] + notations["T"]
notations["4"] = notations["3"] + notations["Q"]

# Remove any perturbative corrections
op = self.fermion_ansatz
while "(" in op:
start = op.index("(")
end = op.index(")")
op = op[:start]
if (end + 1) < len(op):
op += op[end + 1 :]

# Check in order of longest to shortest string in case one
# method name starts with a substring equal to the name of
# another method
for method_type in sorted(METHOD_TYPES, key=len)[::-1]:
if op.startswith(method_type):
op = op.lstrip(method_type)
break

# If it's MP we only ever need to initialise second-order
# amplitudes
if method_type == "MP":
op = "D"

# Determine the ranks
for key in sorted(notations.keys(), key=len)[::-1]:
if key in op:
ranks += notations[key]
op = op.replace(key, "")

# Check there are no duplicates
if len(ranks) != len(set(ranks)):
raise util.ModelNotImplemented("Duplicate ranks in %s" % self.fermion_ansatz)

# Sort the ranks by the cluster operator dimension
ranks = sorted(ranks, key=lambda x: x[2])

return ranks

def bosonic_cluster_ranks(self, spin_type="G"):
"""Get a list of cluster operator ranks for the bosonic
space.

Returns
-------
ranks : tuple of tuple of int
Cluster operator ranks for the fermionic, bosonic, and
coupling ansatzes, for the active space.
ranks : list of tuples
List of cluster operator ranks, each element is a tuple
containing the name, the slices and the rank.
"""

ranks = []
if not self.boson_ansatz:
return ranks

notations = {
"s": [1],
"d": [2],
"t": [3],
"q": [4],
"S": [("s1", "b", 1)],
"D": [("s2", "bb", 2)],
"T": [("s3", "bbb", 3)],
}
notations["2"] = notations["S"] + notations["D"]
notations["3"] = notations["2"] + notations["T"]

# Remove any perturbative corrections
op = self.boson_ansatz
while "(" in op:
start = op.index("(")
end = op.index(")")
op = op[:start]
if (end + 1) < len(op):
op += op[end + 1 :]

# Determine the ranks
for key in sorted(notations.keys(), key=len)[::-1]:
if key in op:
ranks += notations[key]
op = op.replace(key, "")

# Check there are no duplicates
if len(ranks) != len(set(ranks)):
raise util.ModelNotImplemented("Duplicate ranks in %s" % self.boson_ansatz)

# Sort the ranks by the cluster operator dimension
ranks = sorted(ranks, key=lambda x: x[2])

return ranks

def coupling_cluster_ranks(self, spin_type="G"):
"""Get a list of cluster operator ranks for the coupling
between fermionic and bosonic spaces.

Returns
-------
ranks : list of tuple
List of cluster operator ranks, each element is a tuple
containing the name, slice, fermionic rank and bosonic
rank.
"""

ranks = []

for fermion_rank in range(1, self.fermion_coupling_rank + 1):
for boson_rank in range(1, self.boson_coupling_rank + 1):
name = f"u{fermion_rank}{boson_rank}"
key = "b" * boson_rank + "o" * fermion_rank + "v" * fermion_rank
ranks.append((name, key, fermion_rank, boson_rank))

for i, op in enumerate([self.fermion_ansatz, self.boson_ansatz]):
# Remove any perturbative corrections
while "(" in op:
start = op.index("(")
end = op.index(")")
op = op[:start]
if (end + 1) < len(op):
op += op[end + 1 :]

# Check in order of longest -> shortest string in case
# one method name starts with a substring equal to the
# name of another method
if i == 0:
for method_type in sorted(METHOD_TYPES, key=len)[::-1]:
if op.startswith(method_type):
op = op.lstrip(method_type)
break

# Remove any lower case characters, as these correspond
# to active space
op = "".join([char for char in op if char.islower()])

# Determine the ranks
ranks_entry = set()
for char in op:
for rank in notations[char]:
ranks_entry.add(rank)
ranks.append(tuple(sorted(list(ranks_entry))))

# Get the coupling ranks
# FIXME how to handle? if it's ever supported
ranks.append(tuple())

return tuple(ranks)
return ranks
24 changes: 14 additions & 10 deletions ebcc/brueckner.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,21 +130,21 @@ def transform_amplitudes(self, u, amplitudes=None):
ca = u[nocc:, nocc:]

# Transform T amplitudes:
for n in self.cc.ansatz.correlated_cluster_ranks[0]:
args = [self.cc.amplitudes["t%d" % n], tuple(range(n * 2))]
for name, key, n in self.cc.ansatz.fermionic_cluster_ranks(spin_type=self.spin_type):
args = [self.cc.amplitudes[name], tuple(range(n * 2))]
for i in range(n):
args += [ci, (i, i + n * 2)]
for i in range(n):
args += [ca, (i + n, i + n * 3)]
args += [tuple(range(n * 2, n * 4))]
self.cc.amplitudes["t%d" % n] = util.einsum(*args)
self.cc.amplitudes[name] = util.einsum(*args)

# Transform S amplitudes:
for n in self.cc.ansatz.correlated_cluster_ranks[1]:
for name, key, n in self.cc.ansatz.bosonic_cluster_ranks(spin_type=self.spin_type):
raise util.ModelNotImplemented # TODO

# Transform U amplitudes:
for n in self.cc.ansatz.correlated_cluster_ranks[2]:
for name, key, nf, nb in self.cc.ansatz.coupling_cluster_ranks(spin_type=self.spin_type):
raise util.ModelNotImplemented # TODO

return self.cc.amplitudes
Expand Down Expand Up @@ -264,6 +264,10 @@ def kernel(self):

return self.cc.e_corr

@property
def spin_type(self):
return self.cc.spin_type


@util.inherit_docstrings
class BruecknerUEBCC(BruecknerREBCC):
Expand Down Expand Up @@ -335,22 +339,22 @@ def transform_amplitudes(self, u, amplitudes=None):
ca = {"a": u[0][nocc[0] :, nocc[0] :], "b": u[1][nocc[1] :, nocc[1] :]}

# Transform T amplitudes:
for n in self.cc.ansatz.correlated_cluster_ranks[0]:
for name, key, n in self.cc.ansatz.fermionic_cluster_ranks(spin_type=self.spin_type):
for comb in util.generate_spin_combinations(n, unique=True):
args = [getattr(self.cc.amplitudes["t%d" % n], comb), tuple(range(n * 2))]
args = [getattr(self.cc.amplitudes[name], comb), tuple(range(n * 2))]
for i in range(n):
args += [ci[comb[i]], (i, i + n * 2)]
for i in range(n):
args += [ca[comb[i + n]], (i + n, i + n * 3)]
args += [tuple(range(n * 2, n * 4))]
setattr(self.cc.amplitudes["t%d" % n], comb, util.einsum(*args))
setattr(self.cc.amplitudes[name], comb, util.einsum(*args))

# Transform S amplitudes:
for n in self.cc.ansatz.correlated_cluster_ranks[1]:
for name, key, n in self.cc.ansatz.bosonic_cluster_ranks(spin_type=self.spin_type):
raise util.ModelNotImplemented # TODO

# Transform U amplitudes:
for n in self.cc.ansatz.correlated_cluster_ranks[2]:
for name, key, nf, nb in self.cc.ansatz.coupling_cluster_ranks(spin_type=self.spin_type):
raise util.ModelNotImplemented # TODO

return self.cc.amplitudes
Expand Down
Loading