Skip to content

Commit

Permalink
A failed attempt to allow derived generator types in Python
Browse files Browse the repository at this point in the history
This functionality requires the `shared_ptr` branch of libcommute.

Unfortunately, pybind11 is not quite ready for this.

pybind/pybind11#1389
pybind/pybind11#1566
  • Loading branch information
krivenko committed Dec 15, 2020
1 parent 3200831 commit 5ef5722
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 69 deletions.
117 changes: 54 additions & 63 deletions pycommute/expression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <cassert>
#include <complex>
#include <functional>
#include <memory>
#include <stdexcept>
#include <string>
#include <sstream>
Expand All @@ -41,12 +42,12 @@ using namespace libcommute;
namespace py = pybind11;

//
// Just because std::to_string(std::string) is not part of STL ...
// Some commonly used type shorthands
//

template<typename T>
std::string to_string(T&& x) { using std::to_string; return to_string(x); }
std::string to_string(std::string const& x) { return x; }
using dynamic_indices::dyn_indices;
using gen_type = generator<dyn_indices>;
using mon_type = monomial<dyn_indices>;

//
// Use operator<< to create a string representation
Expand All @@ -58,14 +59,6 @@ template<typename T> std::string print(T const& obj) {
return ss.str();
}

//
// Some commonly used type shorthands
//

using dynamic_indices::dyn_indices;
using gen_type = generator<dyn_indices>;
using mon_type = monomial<dyn_indices>;

////////////////////////////////////////////////////////////////////////////////

//
Expand Down Expand Up @@ -106,16 +99,7 @@ void register_dyn_indices(py::module_ & m) {
&dyn_indices::operator dyn_indices::indices_t const&,
"Index sequence as a list of integers and strings"
)
.def("__repr__", [](dyn_indices const& indices) {
auto const& ind = static_cast<dyn_indices::indices_t const&>(indices);
const size_t N = ind.size();
std::string s;
for(size_t i = 0; i < N; ++i) {
std::visit([&s](auto const& x) { s += to_string(x); }, ind[i]);
if(i + 1 < N) s += ",";
}
return s;
})
.def("__repr__", [](dyn_indices const& indices) { return to_string(indices);})
.def("__iter__", [](const dyn_indices &indices) {
auto const& ind = static_cast<dyn_indices::indices_t const&>(indices);
return py::make_iterator(ind.begin(), ind.end());
Expand All @@ -132,42 +116,20 @@ void register_dyn_indices(py::module_ & m) {

void register_linear_function(py::module_ & m) {

auto copy_terms = [](auto const& terms) {
std::vector<std::pair<std::unique_ptr<gen_type>, double>> res;
res.reserve(terms.size());
for(auto const& t : terms)
res.emplace_back(t.first->clone(), t.second);
return res;
};

py::class_<gen_type::linear_function_t>(m, "LinearFunctionGen",
"Linear combination of algebra generators plus a constant term"
)
.def(py::init<>(), "Construct a function that is identically zero.")
.def(py::init<double>(), "Construct a constant.", py::arg("const_term"))
.def(py::init([copy_terms](
double const_term,
std::vector<std::pair<const gen_type*, double>> const& terms) {
return std::make_unique<gen_type::linear_function_t>(
const_term,
std::move(copy_terms(terms))
);
}),
.def(py::init<double, decltype(gen_type::linear_function_t::terms) const&>(),
"Construct from a constant term and a list of coefficient/generator pairs.",
py::arg("const_term"),
py::arg("terms")
)
.def_readwrite("const_term",
&gen_type::linear_function_t::const_term,
"Constant term.")
.def_property("terms",
[copy_terms](gen_type::linear_function_t const& f) {
return copy_terms(f.terms);
},
[copy_terms](gen_type::linear_function_t & f,
std::vector<std::pair<const gen_type*, double>> const& terms) {
f.terms = std::move(copy_terms(terms));
},
.def_readwrite("terms", &gen_type::linear_function_t::terms,
"List of pairs of algebra generators and their respective coefficients."
)
.def_property_readonly("vanishing", &gen_type::linear_function_t::vanishing,
Expand All @@ -193,19 +155,23 @@ class gen_type_trampoline : public gen_type {

public:

using gen_type::gen_type;

gen_type_trampoline(py::args args) : gen_type(std::move(init(args))) {}

int algebra_id() const override {
PYBIND11_OVERRIDE_PURE(int, gen_type, algebra_id, );
}

std::unique_ptr<gen_type> clone() const override {
// Generators are immutable, so one should use multiple references instead
// of creating deep copies in Python.
assert(false);
return nullptr;
virtual std::shared_ptr<gen_type> clone() const override {
std::cout << "clone() called" << std::endl;

// Workaround for pybind11 issue #1049 by Dean Moldovan
// https://github.com/pybind/pybind11/issues/1049#issuecomment-326688270

auto self = py::cast(this);
auto cloned = self.attr("clone")();
auto keep_python_state_alive = std::make_shared<py::object>(cloned);
auto ptr = cloned.cast<gen_type_trampoline*>();
return std::shared_ptr<gen_type>(keep_python_state_alive, ptr);
}

double swap_with(gen_type const& g2, linear_function_t & f) const override {
Expand Down Expand Up @@ -235,13 +201,18 @@ class gen_type_trampoline : public gen_type {
bool greater(gen_type const& g) const override {
PYBIND11_OVERRIDE(bool, gen_type, greater, g);
}

std::string to_string() const override {
PYBIND11_OVERRIDE(std::string, gen_type, to_string, );
}
};

class gen_type_publicist : public gen_type {
public:
using gen_type::equal;
using gen_type::less;
using gen_type::greater;
using gen_type::to_string;
};

//
Expand All @@ -251,11 +222,12 @@ class gen_type_publicist : public gen_type {
template<typename Gen>
void register_generator(py::module_ & m, Gen & g) {
g
.def(py::init<py::args>())
// Algebra ID
.def_property_readonly("algebra_id",
&gen_type::algebra_id,
"ID of the algebra this generator belongs to."
)
.def("algebra_id",
&gen_type::algebra_id,
"ID of the algebra this generator belongs to."
)
// Tuple of indices
.def_property_readonly("indices", [](gen_type const& g){
return std::get<0>(g.indices());
Expand Down Expand Up @@ -348,7 +320,7 @@ This method should be overridden in derived classes.)eol",
py::arg("g2")
)
// String representation
.def("__repr__", &print<gen_type>);
.def("__repr__", &gen_type_publicist::to_string);

// Swap generators of potentially different algebras
m.def("swap_with", &swap_with<dyn_indices>, R"eol(
Expand All @@ -367,7 +339,12 @@ generators swap_with() returns 1 and sets 'f' to the trivial function.)eol",

void register_generator_fermion(py::module_ & m) {

py::class_<generator_fermion<dyn_indices>, gen_type>(m, "GeneratorFermion",
py::class_<generator_fermion<dyn_indices>,
gen_type,
std::shared_ptr<generator_fermion<dyn_indices>>
>(
m,
"GeneratorFermion",
"Generator of the fermionic algebra"
)
.def(py::init<bool, dyn_indices const&>(), R"eol(
Expand Down Expand Up @@ -398,7 +375,12 @@ operator with indices passed as positional arguments.)eol",

void register_generator_boson(py::module_ & m) {

py::class_<generator_boson<dyn_indices>, gen_type>(m, "GeneratorBoson",
py::class_<generator_boson<dyn_indices>,
gen_type,
std::shared_ptr<generator_boson<dyn_indices>>
>(
m,
"GeneratorBoson",
"Generator of the bosonic algebra"
)
.def(py::init<bool, dyn_indices const&>(), R"eol(
Expand Down Expand Up @@ -442,7 +424,12 @@ void register_generator_spin(py::module_ & m) {
"Label for the 3rd spin projection operators S_z"
);

py::class_<generator_spin<dyn_indices>, gen_type>(m, "GeneratorSpin",
py::class_<generator_spin<dyn_indices>,
gen_type,
std::shared_ptr<generator_spin<dyn_indices>>
>(
m,
"GeneratorSpin",
"Generator of the spin algebra"
)
.def(py::init<spin_component, dyn_indices const&>(), R"eol(
Expand Down Expand Up @@ -513,7 +500,7 @@ void register_monomial(py::module_ & m) {
py::class_<mon_type>(m, "Monomial",
"Monomial: a product of algebra generators")
.def(py::init<>(), "Construct an identity monomial.")
.def(py::init<std::vector<gen_type*>>(),
.def(py::init<std::vector<mon_type::gen_ptr_type>>(),
"Construct from a list of algebra generators."
)
.def("__len__", &mon_type::size, "Number of generators in this monomial.")
Expand Down Expand Up @@ -971,7 +958,9 @@ PYBIND11_MODULE(expression, m) {
// type at the point where generator::linear_function_t is wrapped.
//

py::class_<gen_type, gen_type_trampoline> g(m, "Generator",
py::class_<gen_type, gen_type_trampoline, std::shared_ptr<gen_type>> g(
m,
"Generator",
"Abstract algebra generator"
);

Expand All @@ -984,6 +973,8 @@ PYBIND11_MODULE(expression, m) {
m.attr("FERMION") = fermion;
m.attr("BOSON") = boson;
m.attr("SPIN") = spin;
m.attr("MIN_USER_DEFINED_ALGEBRA_ID") =
LIBCOMMUTE_MIN_USER_DEFINED_ALGEBRA_ID;

register_generator(m, g);

Expand Down
Empty file modified tests/test_expression.py
100644 → 100755
Empty file.
Empty file modified tests/test_expression_arithmetic.py
100644 → 100755
Empty file.
Empty file modified tests/test_factories.py
100644 → 100755
Empty file.
59 changes: 59 additions & 0 deletions tests/test_gamma.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#
# This file is part of pycommute, Python bindings for the libcommute C++
# quantum operator algebra library.
#
# Copyright (C) 2020 Igor Krivenko <igor.s.krivenko@gmail.com>
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

from unittest import TestCase

from itertools import product
from pycommute.expression import *

class GeneratorGamma(Generator):

def __init__(self, index):
Generator.__init__(self, index)

def algebra_id(self):
return MIN_USER_DEFINED_ALGEBRA_ID

def swap_with(self, g2, f):
# c = -1, f(g) = 2\eta(g1, g2)
assert self > g2
diag = self.indices == g2.indices
f.set(diag * (2 if (self.indices[0] == 0) else -2))
return -1

def simplify_prod(self, g2, f):
# (\Gamma^0)^2 = I_4
# (\Gamma^k)^2 = -I_4 for k=1,2,3
if self == g2:
f.set(1 if (self.indices[0] == 0) else -1)
return True
else:
return False

def conj(self, f):
# Gamma^0 is Hermitian and Gamma^k are anti-Hermitian
f.set(0, self, 1 if (self.indices[0] == 0) else -1)

class TestIdentities(TestCase):

@classmethod
def setUpClass(cls):
# Metric
cls.eta = lambda mu, nu: (mu == nu) * (1 if mu == 0 else -1)
# Gamma matrices
cls.Gamma = [ExpressionC(1.0, Monomial([GeneratorGamma(mu)]))
for mu in range(4)]
cls.Gammac = [ExpressionC(cls.eta(mu, mu), Monomial([GeneratorGamma(mu)]))
for mu in range(4)]

def test_anticommutators(self):
for mu, nu in product(range(4), range(4)):
self.assertEqual(self.Gamma[mu] * self.Gamma[nu] + \
self.Gamma[nu] * self.Gamma[mu], 2 * self.eta(mu, nu))
12 changes: 6 additions & 6 deletions tests/test_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def test_fermion(self):
lin_f = LinearFunctionGen()

for op in self.fermion_ops:
self.assertEqual(op.algebra_id, FERMION)
self.assertEqual(op.algebra_id(), FERMION)
self.assertFalse(op.reduce_power(3, lin_f))
self.assertFalse(op.reduce_power(4, lin_f))

Expand Down Expand Up @@ -241,7 +241,7 @@ def test_boson(self):
lin_f = LinearFunctionGen()

for op in self.boson_ops:
self.assertEqual(op.algebra_id, BOSON)
self.assertEqual(op.algebra_id(), BOSON)
self.assertFalse(op.reduce_power(3, lin_f))
self.assertFalse(op.reduce_power(4, lin_f))

Expand Down Expand Up @@ -276,7 +276,7 @@ def test_spin12(self):
lin_f = LinearFunctionGen()

for op in self.spin_ops:
self.assertEqual(op.algebra_id, SPIN)
self.assertEqual(op.algebra_id(), SPIN)
self.assertEqual(op.spin, 0.5)
self.assertEqual(op.multiplicity, 2)
if op.component == SpinComponent.Z:
Expand Down Expand Up @@ -311,7 +311,7 @@ def test_spin1(self):
lin_f = LinearFunctionGen()

for op in self.spin1_ops:
self.assertEqual(op.algebra_id, SPIN)
self.assertEqual(op.algebra_id(), SPIN)
self.assertEqual(op.spin, 1)
self.assertEqual(op.multiplicity, 3)
if op.component == SpinComponent.Z:
Expand Down Expand Up @@ -346,7 +346,7 @@ def test_spin32(self):
lin_f = LinearFunctionGen()

for op in self.spin32_ops:
self.assertEqual(op.algebra_id, SPIN)
self.assertEqual(op.algebra_id(), SPIN)
self.assertEqual(op.spin, 3/2)
self.assertEqual(op.multiplicity, 4)
if op.component == SpinComponent.Z:
Expand Down Expand Up @@ -387,6 +387,6 @@ def test_different_algebras(self):
for i in range(len(all_ops)):
for j in range(i + 1, len(all_ops)):
c = swap_with(all_ops[j], all_ops[i], f)
if all_ops[j].algebra_id != all_ops[i].algebra_id:
if all_ops[j].algebra_id() != all_ops[i].algebra_id():
self.assertEqual(c, 1)
self.check_linear_function_0(f, 0)

0 comments on commit 5ef5722

Please sign in to comment.