diff --git a/Makefile b/Makefile index 15b3afa..e503456 100644 --- a/Makefile +++ b/Makefile @@ -42,12 +42,15 @@ endif CFLAGS += $(shell $(PYTHON) tools/python_include_dirs.py) # Set external projects and their include directories -DEPS := $(addprefix deps/,eigen spectra parallel-hashmap pybind11) -CFLAGS += $(addprefix -Ideps/,eigen spectra/include parallel-hashmap pybind11/include) +DEPS := $(addprefix deps/,eigen spectra parallel-hashmap pybind11 rapidhash) +CFLAGS += $(addprefix -Ideps/,eigen spectra/include parallel-hashmap pybind11/include rapidhash) # This C++ compile flag is needed in order for Macs to find system libraries ifeq ($(shell uname -s),Darwin) CFLAGS += -undefined dynamic_lookup +PYCI_EXTENSION := dylib +else +PYCI_EXTENSION := so endif # Set PyCI version number @@ -61,6 +64,9 @@ DEFS := -D_PYCI_VERSION='$(PYCI_VERSION)' DEFS += -D_GIT_BRANCH='$(shell git rev-parse --abbrev-ref HEAD)' DEFS += -D_BUILD_TIME='$(shell date -u +%F\ %T)' DEFS += -D_COMPILER_VERSION='$(shell $(CXX) --version | head -n 1)' +ifeq ($(shell uname -s),Darwin) +DEFS += -D_USE_RAPIDHASH='1' +endif # Set objects OBJECTS := $(patsubst %.cpp,%.o,$(wildcard pyci/src/*.cpp)) @@ -70,7 +76,7 @@ OBJECTS := $(patsubst %.cpp,%.o,$(wildcard pyci/src/*.cpp)) # ------------- .PHONY: all -all: pyci/_pyci.so.$(PYCI_VERSION) pyci/_pyci.so.$(VERSION_MAJOR) pyci/_pyci.so +all: pyci/_pyci.$(PYCI_EXTENSION).$(PYCI_VERSION) pyci/_pyci.$(PYCI_EXTENSION).$(VERSION_MAJOR) pyci/_pyci.$(PYCI_EXTENSION) .PHONY: test test: @@ -78,29 +84,30 @@ test: .PHONY: clean clean: - rm -rf pyci/src/*.o pyci/_pyci.so* + rm -rf pyci/src/*.o pyci/_pyci.$(PYCI_EXTENSION)* .PHONY: cleandeps cleandeps: rm -rf deps +.PHONY: compile_flags.txt +compile_flags.txt: + echo "$(CFLAGS)" | tr ' ' '\n' > $(@) + # Make targets # ------------ -compile_flags.txt: - echo "$(CFLAGS)" | tr ' ' '\n' > $(@) - pyci/src/%.o: pyci/src/%.cpp pyci/include/pyci.h $(DEPS) $(CXX) $(CFLAGS) $(DEFS) -c $(<) -o $(@) -pyci/_pyci.so.$(PYCI_VERSION): $(OBJECTS) +pyci/_pyci.$(PYCI_EXTENSION).$(PYCI_VERSION): $(OBJECTS) $(CXX) $(CFLAGS) $(DEFS) -shared $(^) -o $(@) -pyci/_pyci.so.$(VERSION_MAJOR): pyci/_pyci.so.$(PYCI_VERSION) +pyci/_pyci.$(PYCI_EXTENSION).$(VERSION_MAJOR): pyci/_pyci.$(PYCI_EXTENSION).$(PYCI_VERSION) ln -sf $(notdir $(<)) $(@) -pyci/_pyci.so: pyci/_pyci.so.$(PYCI_VERSION) +pyci/_pyci.$(PYCI_EXTENSION): pyci/_pyci.$(PYCI_EXTENSION).$(PYCI_VERSION) ln -sf $(notdir $(<)) $(@) deps/eigen: @@ -114,3 +121,6 @@ deps/parallel-hashmap: deps/pybind11: [ -d $@ ] || git clone https://github.com/pybind/pybind11.git $@ + +deps/rapidhash: + [ -d $@ ] || git clone https://github.com/nicoshev/rapidhash.git $@ diff --git a/pyci/cepa0.py b/pyci/cepa0.py new file mode 100644 index 0000000..c1a4049 --- /dev/null +++ b/pyci/cepa0.py @@ -0,0 +1,232 @@ +# This file is part of PyCI. +# +# PyCI 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. +# +# PyCI 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 PyCI. If not, see . + +r"""PyCI CEPA0 module.""" + +import numpy as np + +from scipy.sparse.linalg import lgmres, lsqr + +from . import pyci + +__all__ = [ + "CEPA0", +] + + +class CEPA0: + r"""CEPA0 solver class.""" + + @property + def shape(self) -> tuple[int, int]: + r""" + Shape of the linear operator. + + Returns + ------- + nrow : int + Number of rows. + ncol : int + Number of columns. + + """ + return self.op.shape + + @property + def dtype(self) -> np.dtype: + r""" + Data type of the linear operator. + + Returns + ------- + dtype : np.dtype + Data type of the linear operator. + + """ + return self.op.dtype + + def __init__( + self, + *args: tuple[pyci.wavefunction, pyci.secondquant_op] | pyci.sparse_op, + ref: int = 0, + damp: float = np.inf, + ) -> None: + r""" + Initialize the CEPA0 solver. + + Parameters + ---------- + args : (pyci.sparse_op,) or (pyci.secondquant_op, pyci.wavefunction) + System to solve. + ref : int, default=0 + Index of reference determinant. + damp : float, default=np.inf + Damping parameter. + + """ + if len(args) == 1: + self.op = args[0] + if self.op.symmetric: + raise TypeError("Cannot run CEPA0 on symmetric matrix operator") + elif self.op.shape[0] < self.op.shape[1]: + raise ValueError("Cannot solve underdetermined system (nrow < ncol)") + elif len(args) == 2: + self.op = pyci.sparse_op(*args, symmetric=False) + else: + raise ValueError("must pass `ham, wfn` or `op`") + self.ref = ref + self.h_ref = self.op.get_element(ref, ref) + self.damp = -1.0 / damp ** 2 + + def __call__(self, x: np.ndarray) -> np.ndarray: + r""" + Apply the linear operator to a vector ``x``. + + Parameters + ---------- + x : np.ndarray + Vector to which linear operator will be applied. + + Returns + ------- + y : np.ndarray + Result of applying linear operator to ``x``. + + """ + return self.matvec(x) + + def matvec(self, x: np.ndarray) -> np.ndarray: + r""" + Apply the linear operator to a vector ``x``. + + Parameters + ---------- + x : np.ndarray + Vector to which linear operator will be applied. + + Returns + ------- + y : np.ndarray + Result of applying linear operator to ``x``. + + """ + op = self.op + ref = self.ref + h_ref = self.h_ref + damp = self.damp + y = np.zeros(op.shape[0], dtype=pyci.c_double) + for i in range(op.shape[0]): + h_ref_damp = np.exp(damp * x[i] ** 2) * h_ref + if i == ref: + for j in range(self.op.shape[1]): + if j == ref: + y[i] += x[j] + else: + y[i] -= op.get_element(i, j) * x[j] + else: + for j in range(self.op.shape[1]): + if i == j: + y[i] += (h_ref_damp - op.get_element(i, j)) * x[j] + elif j != ref: + y[i] -= op.get_element(i, j) * x[j] + return y + + def rmatvec(self, x: np.ndarray) -> np.ndarray: + r""" + Apply the transpose of the linear operator to a vector ``x``. + + Parameters + ---------- + x : np.ndarray + Vector to which linear operator will be applied. + + Returns + ------- + y : np.ndarray + Result of applying linear operator to ``x``. + + """ + op = self.op + h_ref = self.h_ref + damp = self.damp + y = np.zeros(op.shape[1], dtype=pyci.c_double) + for i in range(op.shape[0]): + h_ref_damp = np.exp(damp * x[i] ** 2) * h_ref + for j in range(self.op.shape[1]): + if j == i: + y[j] += (h_ref_damp - op.get_element(i, j)) * x[i] + else: + y[j] -= op.get_element(i, j) * x[i] + y[self.ref] = x[self.ref] + return y + + def rhs(self, x: np.ndarray) -> np.ndarray: + r""" + Construct the right-hand side vector for the linear system of equations. + + Parameters + ---------- + x : np.ndarray + Initial guess. + + Returns + ------- + rhs : np.ndarray + Right-hand side vector. + + """ + op = self.op + ref = self.ref + y = np.zeros(op.shape[0], dtype=pyci.c_double) + for i in range(op.shape[0]): + y[i] = op.get_element(i, ref) + y -= self.matvec(x) + return y + + def solve( + self, c0: np.ndarray = None, e0: float = None, maxiter: int = 1000, tol: float = 1.0e-12, + ) -> tuple[np.ndarray, np.ndarray]: + r""" + Solve the CEPA0 problem. + + Parameters + ---------- + c0 : np.ndarray, optional + Initial guess for lowest eigenvector. + e0 : float, optional + Initial guess for energy. + maxiter : int, default=1000 + Maximum number of iterations to perform. + tol : float, default=1.0e-12 + Convergence tolerance. + + Returns + ------- + es : np.ndarray + Energy. + cs : np.ndarray + Coefficient vector. + + """ + c0 = np.zeros(self.op.shape[1], dtype=pyci.c_double) if c0 is None else c0 / c0[self.ref] + c0[self.ref] = self.op.get_element(self.ref, self.ref) if e0 is None else e0 - self.op.ecore + if self.op.shape[0] == self.op.shape[1]: + cs = lgmres(self, self.rhs(c0), maxiter=maxiter, tol=tol, atol=tol)[0] + else: + cs = lsqr(self, self.rhs(c0), maxiter=maxiter, atol=tol, btol=tol)[0] + cs += c0 + es = np.full(1, cs[self.ref] + self.op.ecore, dtype=pyci.c_double) + cs[self.ref] = 1 + return es, cs[None, :] diff --git a/pyci/include/pyci.h b/pyci/include/pyci.h index 49159d0..8407672 100644 --- a/pyci/include/pyci.h +++ b/pyci/include/pyci.h @@ -41,7 +41,11 @@ #include +#ifdef _USE_RAPIDHASH +#include +#else #include +#endif #include @@ -118,14 +122,24 @@ inline int Ctz(const unsigned long long t) { /* Hash function. */ +#ifdef _USE_RAPIDHASH +typedef std::uint64_t Hash; + +template +Hash spookyhash(T length, const U *data) { + return rapidhash(reinterpret_cast(data), length * sizeof(U)); +} +#else typedef std::pair Hash; template Hash spookyhash(T length, const U *data) { Hash h(0x23a23cf5033c3c81UL, 0xb3816f6a2c68e530UL); - SpookyHash::Hash128(reinterpret_cast(data), length * sizeof(U), &h.first, &h.second); + SpookyHash::Hash128(reinterpret_cast(data), length * sizeof(U), &h.first, + &h.second); return h; } +#endif /* Vector template types. */ @@ -162,7 +176,8 @@ template using Array = pybind11::array_t; template -using ColMajorArray = pybind11::array_t; +using ColMajorArray = + pybind11::array_t; /* Forward-declare classes. */ @@ -260,14 +275,14 @@ pybind11::tuple py_compute_rdms_fullci(const FullCIWfn &, const Array); pybind11::tuple py_compute_rdms_genci(const GenCIWfn &, const Array); -pybind11::tuple py_compute_transition_rdms_doci(const DOCIWfn &, const DOCIWfn &, const Array, - const Array); +pybind11::tuple py_compute_transition_rdms_doci(const DOCIWfn &, const DOCIWfn &, + const Array, const Array); -pybind11::tuple py_compute_transition_rdms_fullci(const FullCIWfn &, const FullCIWfn &, const Array, - const Array); +pybind11::tuple py_compute_transition_rdms_fullci(const FullCIWfn &, const FullCIWfn &, + const Array, const Array); -pybind11::tuple py_compute_transition_rdms_genci(const GenCIWfn &, const GenCIWfn &, const Array, - const Array); +pybind11::tuple py_compute_transition_rdms_genci(const GenCIWfn &, const GenCIWfn &, + const Array, const Array); template double py_compute_overlap(const WfnType &, const WfnType &, const Array, @@ -710,21 +725,19 @@ class Objective { std::vector val_paramcons; public: + Objective(const SparseOp &, const Wfn &, const std::size_t = 0UL, const long * = nullptr, + const double * = nullptr, const std::size_t = 0UL, const long * = nullptr, + const double * = nullptr); - Objective(const SparseOp &, const Wfn &, - const std::size_t = 0UL, const long * = nullptr, const double * = nullptr, - const std::size_t = 0UL, const long * = nullptr, const double * = nullptr); - - Objective(const SparseOp &, const Wfn &, - const pybind11::object, const pybind11::object, + Objective(const SparseOp &, const Wfn &, const pybind11::object, const pybind11::object, const pybind11::object, const pybind11::object); Objective(const Objective &); Objective(Objective &&) noexcept; - void init(const std::size_t, const long *, const double *, - const std::size_t, const long *, const double *); + void init(const std::size_t, const long *, const double *, const std::size_t, const long *, + const double *); void objective(const SparseOp &, const double *, double *); @@ -758,13 +771,12 @@ class AP1roGObjective : public Objective { std::vector part_list; public: - AP1roGObjective(const SparseOp &, const DOCIWfn &, - const std::size_t = 0UL, const long * = nullptr, const double * = nullptr, - const std::size_t = 0UL, const long * = nullptr, const double * = nullptr); + AP1roGObjective(const SparseOp &, const DOCIWfn &, const std::size_t = 0UL, + const long * = nullptr, const double * = nullptr, const std::size_t = 0UL, + const long * = nullptr, const double * = nullptr); - AP1roGObjective(const SparseOp &, const DOCIWfn &, - const pybind11::object, const pybind11::object, - const pybind11::object, const pybind11::object); + AP1roGObjective(const SparseOp &, const DOCIWfn &, const pybind11::object, + const pybind11::object, const pybind11::object, const pybind11::object); AP1roGObjective(const AP1roGObjective &); @@ -790,13 +802,12 @@ class APIGObjective : public Objective { std::vector part_list; public: - APIGObjective(const SparseOp &, const DOCIWfn &, - const std::size_t = 0UL, const long * = nullptr, const double * = nullptr, - const std::size_t = 0UL, const long * = nullptr, const double * = nullptr); + APIGObjective(const SparseOp &, const DOCIWfn &, const std::size_t = 0UL, + const long * = nullptr, const double * = nullptr, const std::size_t = 0UL, + const long * = nullptr, const double * = nullptr); - APIGObjective(const SparseOp &, const DOCIWfn &, - const pybind11::object, const pybind11::object, - const pybind11::object, const pybind11::object); + APIGObjective(const SparseOp &, const DOCIWfn &, const pybind11::object, const pybind11::object, + const pybind11::object, const pybind11::object); APIGObjective(const APIGObjective &); diff --git a/pyci/include/sort_with_arg.h b/pyci/include/sort_with_arg.h index 30abeae..f666d54 100644 --- a/pyci/include/sort_with_arg.h +++ b/pyci/include/sort_with_arg.h @@ -15,7 +15,11 @@ #pragma once +#include + #include +#include +#include /* see */ @@ -63,8 +67,13 @@ struct value_reference_t { }; template -struct value_iterator_t : iterator, ptrdiff_t, - value_t<_Data, _Order> *, value_reference_t<_Data, _Order>> { +struct value_iterator_t { + using iterator_category = std::random_access_iterator_tag; + using difference_type = ptrdiff_t; + using value_type = value_t<_Data, _Order>; + using pointer = value_t<_Data, _Order> *; + using reference = value_reference_t<_Data, _Order>; + _Data *itData; _Order *itVal; value_iterator_t(_Data *_itData, _Order *_itVal) : itData(_itData), itVal(_itVal) {