From 9065a722326bbf5d19f12412f4b0d13e6466df65 Mon Sep 17 00:00:00 2001 From: Igor Krivenko Date: Tue, 15 Dec 2020 16:41:12 +0100 Subject: [PATCH] Remove wrapper for `linear_function` and `generator` methods that use it Those methods are meant to be called only by Python classes derived from `generator`. Supporting derived generator classes is too painful with current pybind11 (https://github.com/pybind/pybind11/pull/1566), so I have decided to disallow this functionality altogether. --- pycommute/expression.cpp | 175 ++---------------------------------- tests/test_generator.py | 185 +-------------------------------------- 2 files changed, 8 insertions(+), 352 deletions(-) diff --git a/pycommute/expression.cpp b/pycommute/expression.cpp index bea943e..9a84afb 100644 --- a/pycommute/expression.cpp +++ b/pycommute/expression.cpp @@ -126,57 +126,6 @@ void register_dyn_indices(py::module_ & m) { //////////////////////////////////////////////////////////////////////////////// -// -// Register generator::linear_function_t -// - -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)) - ); - }), - "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> const& terms) { - f.terms = std::move(copy_terms(terms)); - }, - "List of pairs of algebra generators and their respective coefficients." - ) - .def_property_readonly("vanishing", &gen_type::linear_function_t::vanishing, - "Is this linear function identically zero?" - ); -} - -//////////////////////////////////////////////////////////////////////////////// - // // Helper classes for abstract base generator // @@ -211,46 +160,17 @@ class gen_type_trampoline : public gen_type { double swap_with(gen_type const& g2, linear_function_t & f) const override { PYBIND11_OVERRIDE_PURE(double, gen_type, swap_with, g2, f); } - - bool simplify_prod(gen_type const& g2, linear_function_t & f) const override { - PYBIND11_OVERRIDE(bool, gen_type, simplify_prod, g2, f); - } - - bool reduce_power(int power, linear_function_t & f) const override { - PYBIND11_OVERRIDE(bool, gen_type, reduce_power, power, f); - } - - void conj(linear_function_t & f) const override { - PYBIND11_OVERRIDE(void, gen_type, conj, f); - } - - bool equal(gen_type const& g) const override { - PYBIND11_OVERRIDE(bool, gen_type, equal, g); - } - - bool less(gen_type const& g) const override { - PYBIND11_OVERRIDE(bool, gen_type, less, g); - } - - bool greater(gen_type const& g) const override { - PYBIND11_OVERRIDE(bool, gen_type, greater, g); - } -}; - -class gen_type_publicist : public gen_type { -public: - using gen_type::equal; - using gen_type::less; - using gen_type::greater; }; // // Register generator // -template -void register_generator(py::module_ & m, Gen & g) { - g +void register_generator(py::module_ & m) { + + py::class_(m, "Generator", + "Abstract algebra generator" + ) // Algebra ID .def_property_readonly("algebra_id", &gen_type::algebra_id, @@ -262,70 +182,6 @@ void register_generator(py::module_ & m, Gen & g) { }, "Indices carried by this generator." ) - // Product transformation methods - .def("swap_with", &gen_type::swap_with, R"eol( -Given a pair of generators g1 = 'self' and g2 such that g1 > g2, swap_with() -must signal what transformation g1 * g2 -> c * g2 * g1 + f(g) should be applied -to the product g1 * g2 to put it into the canonical order. -swap_with() returns the constant 'c' and writes the linear function f(g) into -'f'. 'c' is allowed to be zero. - -This method should be overridden in derived classes.)eol", - py::arg("g2"), - py::arg("f") - ) - .def("simplify_prod", &gen_type::simplify_prod, R"eol( -Given a pair of generators g1 = 'self' and g2 such that g1 * g2 is in the -canonical order (g1 <= g2), optionally apply a simplifying transformation -g1 * g2 -> f(g). If a simplification is actually possible, simplify_prod() -must return True and write the linear function f(g) into 'f'. -Otherwise return False. - -This method should be overridden in derived classes.)eol", - py::arg("g2"), - py::arg("f") - ) - .def("reduce_power", &gen_type::reduce_power, R"eol( -Given a generator g1 = 'self' and a power > 2, optionally apply a simplifying -transformation g1^power -> f(g). If a simplification is actually possible, -reduce_power() must return True and write the linear function f(g) into 'f'. -Otherwise return False. - -N.B. Simplifications for power = 2 must be carried out by simplify_prod(). - -This method should be overridden in derived classes.)eol", - py::arg("power"), py::arg("f") - ) - // Comparison methods - .def("equal", &gen_type_publicist::equal, R"eol( -Determine whether two generators 'self' and 'g' belonging to the same algebra -are equal. - -This method should be overridden in derived classes.)eol", - py::arg("g") - ) - .def("less", &gen_type_publicist::less, R"eol( -Determine whether two generators 'self' and 'g' belonging to the same algebra -satisfy self < g. - -This method should be overridden in derived classes.)eol", - py::arg("g") - ) - .def("greater", &gen_type_publicist::greater, R"eol( -Determine whether two generators 'self' and 'g' belonging to the same algebra -satisfy self > g. - -This method should be overridden in derived classes.)eol", - py::arg("g") - ) - // Hermitian conjugate - .def("conj", &gen_type::conj, R"eol( -Return the Hermitian conjugate of this generator as a linear function of -generators via 'f'. - -This method should be overridden in derived classes.)eol", - py::arg("f") - ) // Comparison operators .def("__eq__", [](gen_type const& g1, gen_type const& g2){ return g1 == g2; }, @@ -349,14 +205,6 @@ This method should be overridden in derived classes.)eol", ) // String representation .def("__repr__", &print); - - // Swap generators of potentially different algebras - m.def("swap_with", &swap_with, R"eol( -Check if 'g1' and 'g2' belong to the same algebra and call g1.swap_with(g2, f) -accordingly. Generators of different algebras always commute, and for such -generators swap_with() returns 1 and sets 'f' to the trivial function.)eol", - py::arg("g1"), py::arg("g2"), py::arg("f") - ); } //////////////////////////////////////////////////////////////////////////////// @@ -966,17 +814,6 @@ PYBIND11_MODULE(expression, m) { register_dyn_indices(m); - // - // Register generator early on so that pybind11 knows about this - // type at the point where generator::linear_function_t is wrapped. - // - - py::class_ g(m, "Generator", - "Abstract algebra generator" - ); - - register_linear_function(m); - // // Algebra IDs // @@ -985,7 +822,7 @@ PYBIND11_MODULE(expression, m) { m.attr("BOSON") = boson; m.attr("SPIN") = spin; - register_generator(m, g); + register_generator(m); register_generator_fermion(m); register_generator_boson(m); diff --git a/tests/test_generator.py b/tests/test_generator.py index c5157c6..f42d8fc 100755 --- a/tests/test_generator.py +++ b/tests/test_generator.py @@ -11,11 +11,11 @@ from unittest import TestCase from itertools import product -from pycommute.expression import Indices, LinearFunctionGen +from pycommute.expression import Indices from pycommute.expression import FERMION, GeneratorFermion, make_fermion from pycommute.expression import BOSON, GeneratorBoson, make_boson from pycommute.expression import SPIN, GeneratorSpin, make_spin -from pycommute.expression import SpinComponent, swap_with +from pycommute.expression import SpinComponent from pycommute.expression import is_fermion, is_boson, is_spin # All other Generator* tests @@ -33,107 +33,6 @@ def check_less_greater(self, v): self.assertEqual((x < y), (i < j)) self.assertEqual((x > y), (i > j)) - def check_linear_function_0(self, lf, const_term): - self.assertEqual(lf.const_term, const_term) - self.assertEqual(len(lf.terms), 0) - - def check_linear_function_1(self, lf, const_term, coeff, gen): - self.assertEqual(lf.const_term, const_term) - self.assertEqual(len(lf.terms), 1) - self.assertEqual(lf.terms[0], (gen, coeff)) - - def check_generator_spin_swap_with(self, v, one_half = False): - f = LinearFunctionGen() - for i in range(len(v)): - for j in range(i + 1, len(v)): - c = v[j].swap_with(v[i], f) - if one_half: - if j%3 == 1 and i == j - 1: # S_- S_+ = 1/2 - S_z - self.assertEqual(c, 0) - self.check_linear_function_1(f, 1/2, -1, v[i + 2]) - elif j%3 == 2 and i == j - 2: # S_z S_+ = 1/2 S_+ - self.assertEqual(c, 0) - self.check_linear_function_1(f, 0, 1/2, v[i]) - elif j%3 == 2 and i == j - 1: # S_z S_- = -1/2 S_- - self.assertEqual(c, 0) - self.check_linear_function_1(f, 0, -1/2, v[i]) - else: - self.assertEqual(c, 1) - self.check_linear_function_0(f, 0) - else: - self.assertEqual(c, 1) - if j%3 == 1 and i == j - 1: # S_- S_+ = S_+ * S_- - 2*S_z - self.check_linear_function_1(f, 0, -2, v[i + 2]) - elif j%3 == 2 and i == j - 2: # S_z S_+ = S_+ * S_z + S_+ - self.check_linear_function_1(f, 0, 1, v[i]) - elif j%3 == 2 and i == j - 1: # S_z S_- = S_- * S_z - S_- - self.check_linear_function_1(f, 0, -1, v[i]) - else: - self.check_linear_function_0(f, 0) - - def check_generator_spin_simplify_prod(self, v, one_half = False): - f = LinearFunctionGen() - for i in range(len(v)): - for j in range(i, len(v)): - c = v[i].simplify_prod(v[j], f) - if one_half: - if i%3 == 0 and j == i: # S_+ * S_+ = 0 - self.assertTrue(c) - self.check_linear_function_0(f, 0) - elif i%3 == 1 and j == i: # S_- * S_- = 0 - self.assertTrue(c) - self.check_linear_function_0(f, 0) - elif i%3 == 2 and j == i: # S_z * S_z = 1/4 - self.assertTrue(c) - self.check_linear_function_0(f, 1/4) - elif i%3 == 0 and j == i + 1: # S_+ * S_- = 1/2 + S_z - self.assertTrue(c) - self.check_linear_function_1(f, 1/2, 1, v[i + 2]) - elif i%3 == 0 and j == i + 2: # S_+ * S_z = -1/2 S_+ - self.assertTrue(c) - self.check_linear_function_1(f, 0, -1/2, v[i]) - elif i%3 == 1 and j == i + 1: # S_- * S_z = 1/2 S_- - self.assertTrue(c) - self.check_linear_function_1(f, 0, 1/2, v[i]) - else: - self.assertFalse(c) - else: - # No simplifications for higher spins - self.assertFalse(c) - - def check_conj(self, v, ref): - f = LinearFunctionGen() - for n, g in enumerate(v): - g.conj(f) - self.check_linear_function_1(f, 0, 1.0, v[ref[n]]) - - # Test LinearFunctionGen - def test_LinearFunctionGen(self): - lf0 = LinearFunctionGen() - self.assertEqual(lf0.const_term, 0) - self.assertTrue(lf0.vanishing) - self.assertEqual(lf0.terms, []) - lf0.const_term = 4.0 - self.assertEqual(lf0.const_term, 4.0) - self.assertFalse(lf0.vanishing) - - lf1 = LinearFunctionGen(5.0) - self.assertEqual(lf1.const_term, 5.0) - self.assertFalse(lf1.vanishing) - self.assertEqual(lf1.terms, []) - - lf2 = LinearFunctionGen(3.0, [(make_fermion(True, "dn", 0), 2.0), - (make_boson(False, "y"), -1.0)]) - self.assertEqual(lf2.const_term, 3.0) - self.assertFalse(lf2.vanishing) - self.assertEqual(lf2.terms, [(make_fermion(True, "dn", 0), 2.0), - (make_boson(False, "y"), -1.0)]) - lf2.terms = [(make_spin(SpinComponent.PLUS, 1), 6.0)] - self.assertEqual(lf2.terms, [(make_spin(SpinComponent.PLUS, 1), 6.0)]) - lf2.const_term = 0 - lf2.terms = [] - self.assertTrue(lf2.vanishing) - # Test constructors def test_init(self): f1 = GeneratorFermion(True, Indices(0, "up")) @@ -203,12 +102,8 @@ def test_fermion(self): Indices("up", 0), Indices("dn", 0)]) - lin_f = LinearFunctionGen() - for op in self.fermion_ops: self.assertEqual(op.algebra_id, FERMION) - self.assertFalse(op.reduce_power(3, lin_f)) - self.assertFalse(op.reduce_power(4, lin_f)) for i, op in enumerate(self.fermion_ops): self.assertEqual(op.dagger, (i < 2)) @@ -221,16 +116,6 @@ def test_fermion(self): self.assertFalse(is_boson(op)) self.assertFalse(is_spin(op)) - f = LinearFunctionGen() - for i in range(len(self.fermion_ops)): - for j in range(i + 1, len(self.fermion_ops)): - c = self.fermion_ops[j].swap_with(self.fermion_ops[i], f) - self.assertEqual(c, -1) - self.check_linear_function_0(f, \ - ((j == 2 and i == 1) or (j == 3 and i == 0))) - - self.check_conj(self.fermion_ops, [3, 2, 1, 0]) - self.assertListEqual(list(map(str, self.fermion_ops)), ["C+(dn,0)", "C+(up,0)", "C(up,0)", "C(dn,0)"]) @@ -238,12 +123,8 @@ def test_boson(self): self.assertListEqual(list(map(lambda g: g.indices, self.boson_ops)), [Indices("x"), Indices("y"), Indices("y"), Indices("x")]) - lin_f = LinearFunctionGen() - for op in self.boson_ops: self.assertEqual(op.algebra_id, BOSON) - self.assertFalse(op.reduce_power(3, lin_f)) - self.assertFalse(op.reduce_power(4, lin_f)) for i, op in enumerate(self.boson_ops): self.assertEqual(op.dagger, (i < 2)) @@ -256,16 +137,6 @@ def test_boson(self): self.assertTrue(is_boson(op)) self.assertFalse(is_spin(op)) - f = LinearFunctionGen() - for i in range(len(self.boson_ops)): - for j in range(i + 1, len(self.boson_ops)): - c = self.boson_ops[j].swap_with(self.boson_ops[i], f) - self.assertEqual(c, 1) - self.check_linear_function_0(f, \ - ((j == 2 and i == 1) or (j == 3 and i == 0))) - - self.check_conj(self.boson_ops, [3, 2, 1, 0]) - self.assertListEqual(list(map(str, self.boson_ops)), ["A+(x)", "A+(y)", "A(y)", "A(x)"]) @@ -273,20 +144,10 @@ def test_spin12(self): self.assertListEqual(list(map(lambda g: g.indices, self.spin_ops)), [Indices(1)]*3 + [Indices(2)]*3) - lin_f = LinearFunctionGen() - for op in self.spin_ops: self.assertEqual(op.algebra_id, SPIN) self.assertEqual(op.spin, 0.5) self.assertEqual(op.multiplicity, 2) - if op.component == SpinComponent.Z: - self.assertFalse(op.reduce_power(3, lin_f)) - self.assertFalse(op.reduce_power(4, lin_f)) - else: - self.assertTrue(op.reduce_power(3, lin_f)) - self.assertTrue(lin_f.vanishing) - self.assertTrue(op.reduce_power(4, lin_f)) - self.assertTrue(lin_f.vanishing) self.check_equality(self.spin_ops) self.check_less_greater(self.spin_ops) @@ -296,11 +157,6 @@ def test_spin12(self): self.assertFalse(is_boson(op)) self.assertTrue(is_spin(op)) - self.check_generator_spin_swap_with(self.spin_ops, True) - self.check_generator_spin_simplify_prod(self.spin_ops, True) - - self.check_conj(self.spin_ops, [1, 0, 2, 4, 3, 5]) - self.assertListEqual(list(map(str, self.spin_ops)), ["S+(1)","S-(1)","Sz(1)","S+(2)","S-(2)","Sz(2)"]) @@ -308,20 +164,10 @@ def test_spin1(self): self.assertListEqual(list(map(lambda g: g.indices, self.spin1_ops)), [Indices(1)]*3 + [Indices(2)]*3) - lin_f = LinearFunctionGen() - for op in self.spin1_ops: self.assertEqual(op.algebra_id, SPIN) self.assertEqual(op.spin, 1) self.assertEqual(op.multiplicity, 3) - if op.component == SpinComponent.Z: - self.assertFalse(op.reduce_power(3, lin_f)) - self.assertFalse(op.reduce_power(4, lin_f)) - else: - self.assertTrue(op.reduce_power(3, lin_f)) - self.check_linear_function_0(lin_f, 0) - self.assertTrue(op.reduce_power(4, lin_f)) - self.check_linear_function_0(lin_f, 0) self.check_equality(self.spin1_ops) self.check_less_greater(self.spin1_ops) @@ -331,11 +177,6 @@ def test_spin1(self): self.assertFalse(is_boson(op)) self.assertTrue(is_spin(op)) - self.check_generator_spin_swap_with(self.spin1_ops) - self.check_generator_spin_simplify_prod(self.spin1_ops) - - self.check_conj(self.spin1_ops, [1, 0, 2, 4, 3, 5]) - self.assertListEqual(list(map(str, self.spin1_ops)), ["S1+(1)","S1-(1)","S1z(1)","S1+(2)","S1-(2)","S1z(2)"]) @@ -343,19 +184,10 @@ def test_spin32(self): self.assertListEqual(list(map(lambda g: g.indices, self.spin32_ops)), [Indices(1)]*3 + [Indices(2)]*3) - lin_f = LinearFunctionGen() - for op in self.spin32_ops: self.assertEqual(op.algebra_id, SPIN) self.assertEqual(op.spin, 3/2) self.assertEqual(op.multiplicity, 4) - if op.component == SpinComponent.Z: - self.assertFalse(op.reduce_power(3, lin_f)) - self.assertFalse(op.reduce_power(4, lin_f)) - else: - self.assertFalse(op.reduce_power(3, lin_f)) - self.assertTrue(op.reduce_power(4, lin_f)) - self.check_linear_function_0(lin_f, 0) self.check_equality(self.spin32_ops) self.check_less_greater(self.spin32_ops) @@ -365,11 +197,6 @@ def test_spin32(self): self.assertFalse(is_boson(op)) self.assertTrue(is_spin(op)) - self.check_generator_spin_swap_with(self.spin32_ops) - self.check_generator_spin_simplify_prod(self.spin32_ops) - - self.check_conj(self.spin32_ops, [1, 0, 2, 4, 3, 5]) - self.assertListEqual(list(map(str, self.spin32_ops)), ["S3/2+(1)","S3/2-(1)","S3/2z(1)","S3/2+(2)","S3/2-(2)","S3/2z(2)"]) @@ -382,11 +209,3 @@ def test_different_algebras(self): self.check_equality(all_ops) self.check_less_greater(all_ops) - - f = LinearFunctionGen() - 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: - self.assertEqual(c, 1) - self.check_linear_function_0(f, 0)