From 5ef572275c1bc10cd34027170625fef0e2899584 Mon Sep 17 00:00:00 2001 From: Igor Krivenko Date: Tue, 15 Dec 2020 16:01:14 +0100 Subject: [PATCH] A failed attempt to allow derived generator types in Python This functionality requires the `shared_ptr` branch of libcommute. Unfortunately, pybind11 is not quite ready for this. https://github.com/pybind/pybind11/issues/1389 https://github.com/pybind/pybind11/pull/1566 --- pycommute/expression.cpp | 117 +++++++++++++--------------- tests/test_expression.py | 0 tests/test_expression_arithmetic.py | 0 tests/test_factories.py | 0 tests/test_gamma.py | 59 ++++++++++++++ tests/test_generator.py | 12 +-- 6 files changed, 119 insertions(+), 69 deletions(-) mode change 100644 => 100755 tests/test_expression.py mode change 100644 => 100755 tests/test_expression_arithmetic.py mode change 100644 => 100755 tests/test_factories.py create mode 100755 tests/test_gamma.py diff --git a/pycommute/expression.cpp b/pycommute/expression.cpp index bea943e..5589043 100644 --- a/pycommute/expression.cpp +++ b/pycommute/expression.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -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 -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; +using mon_type = monomial; // // Use operator<< to create a string representation @@ -58,14 +59,6 @@ template std::string print(T const& obj) { return ss.str(); } -// -// Some commonly used type shorthands -// - -using dynamic_indices::dyn_indices; -using gen_type = generator; -using mon_type = monomial; - //////////////////////////////////////////////////////////////////////////////// // @@ -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(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(indices); return py::make_iterator(ind.begin(), ind.end()); @@ -132,27 +116,12 @@ void register_dyn_indices(py::module_ & m) { void register_linear_function(py::module_ & m) { - auto copy_terms = [](auto const& terms) { - std::vector, double>> res; - res.reserve(terms.size()); - for(auto const& t : terms) - res.emplace_back(t.first->clone(), t.second); - return res; - }; - py::class_(m, "LinearFunctionGen", "Linear combination of algebra generators plus a constant term" ) .def(py::init<>(), "Construct a function that is identically zero.") .def(py::init(), "Construct a constant.", py::arg("const_term")) - .def(py::init([copy_terms]( - double const_term, - std::vector> const& terms) { - return std::make_unique( - const_term, - std::move(copy_terms(terms)) - ); - }), + .def(py::init(), "Construct from a constant term and a list of coefficient/generator pairs.", py::arg("const_term"), py::arg("terms") @@ -160,14 +129,7 @@ void register_linear_function(py::module_ & m) { .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> 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, @@ -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 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 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(cloned); + auto ptr = cloned.cast(); + return std::shared_ptr(keep_python_state_alive, ptr); } double swap_with(gen_type const& g2, linear_function_t & f) const override { @@ -235,6 +201,10 @@ 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 { @@ -242,6 +212,7 @@ class gen_type_publicist : public gen_type { using gen_type::equal; using gen_type::less; using gen_type::greater; + using gen_type::to_string; }; // @@ -251,11 +222,12 @@ class gen_type_publicist : public gen_type { template void register_generator(py::module_ & m, Gen & g) { g + .def(py::init()) // 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()); @@ -348,7 +320,7 @@ This method should be overridden in derived classes.)eol", py::arg("g2") ) // String representation - .def("__repr__", &print); + .def("__repr__", &gen_type_publicist::to_string); // Swap generators of potentially different algebras m.def("swap_with", &swap_with, R"eol( @@ -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_, gen_type>(m, "GeneratorFermion", + py::class_, + gen_type, + std::shared_ptr> + >( + m, + "GeneratorFermion", "Generator of the fermionic algebra" ) .def(py::init(), R"eol( @@ -398,7 +375,12 @@ operator with indices passed as positional arguments.)eol", void register_generator_boson(py::module_ & m) { - py::class_, gen_type>(m, "GeneratorBoson", + py::class_, + gen_type, + std::shared_ptr> + >( + m, + "GeneratorBoson", "Generator of the bosonic algebra" ) .def(py::init(), R"eol( @@ -442,7 +424,12 @@ void register_generator_spin(py::module_ & m) { "Label for the 3rd spin projection operators S_z" ); - py::class_, gen_type>(m, "GeneratorSpin", + py::class_, + gen_type, + std::shared_ptr> + >( + m, + "GeneratorSpin", "Generator of the spin algebra" ) .def(py::init(), R"eol( @@ -513,7 +500,7 @@ void register_monomial(py::module_ & m) { py::class_(m, "Monomial", "Monomial: a product of algebra generators") .def(py::init<>(), "Construct an identity monomial.") - .def(py::init>(), + .def(py::init>(), "Construct from a list of algebra generators." ) .def("__len__", &mon_type::size, "Number of generators in this monomial.") @@ -971,7 +958,9 @@ PYBIND11_MODULE(expression, m) { // type at the point where generator::linear_function_t is wrapped. // - py::class_ g(m, "Generator", + py::class_> g( + m, + "Generator", "Abstract algebra generator" ); @@ -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); diff --git a/tests/test_expression.py b/tests/test_expression.py old mode 100644 new mode 100755 diff --git a/tests/test_expression_arithmetic.py b/tests/test_expression_arithmetic.py old mode 100644 new mode 100755 diff --git a/tests/test_factories.py b/tests/test_factories.py old mode 100644 new mode 100755 diff --git a/tests/test_gamma.py b/tests/test_gamma.py new file mode 100755 index 0000000..1096b98 --- /dev/null +++ b/tests/test_gamma.py @@ -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 +# +# 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)) diff --git a/tests/test_generator.py b/tests/test_generator.py index c5157c6..6d8dc27 100755 --- a/tests/test_generator.py +++ b/tests/test_generator.py @@ -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)) @@ -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)) @@ -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: @@ -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: @@ -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: @@ -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)