diff --git a/python/src/configuration.cpp b/python/src/configuration.cpp index 6a86f7b..b5517c9 100644 --- a/python/src/configuration.cpp +++ b/python/src/configuration.cpp @@ -25,6 +25,7 @@ #include "casm/configuration/dof_space_analysis.hh" #include "casm/configuration/io/json/Configuration_json_io.hh" #include "casm/configuration/io/json/Supercell_json_io.hh" +#include "casm/configuration/supercell_name.hh" #include "casm/configuration/io/json/analysis_json_io.hh" #include "casm/configuration/irreps/VectorSpaceSymReport.hh" #include "casm/configuration/make_simple_structure.hh" @@ -580,47 +581,118 @@ PYBIND11_MODULE(_configuration, m) { R"pbdoc( Data structure for holding / reading / writing supercells. - Supercell objects hold the symmetry representations needed for applying symmetry - to :class:`~libcasm.configuration.Configuration` in that supercell. Since the - same symmetry representations apply to all configurations in a particular - supercell, a single shared :class:`~libcasm.configuration.Supercell` can be - used for all configurations in that supercell. The SupercellSet is a container - for storing and accessing these supercells, so that when a new - configuration is constructed in an already existing supercell, that supercell can - be found and shared. - - The SupercellSet holds :class:`~libcasm.configuration.SupercellRecord`, each of - which contains a unique shared Supercell as well as its name, whether it is in - `canonical form `_, - and the name of the canonical equivalent supercell. Supercells - which are distinct (have different `transformation_matrix_to_super`) but - symmetrically equivalent (lattice points are mapped by a crystal group operation) - may be stored as separate SupercellRecord in the same SupercellSet. - - SupercellRecord can be added directly, by the shared Supercell, by - transformation matrix, or by the canonical equivalent supercell name. - When an attempt is made to add a SupercellRecord that is already in the - SupercellSet, the existing SupercellRecord is returned. + Notes + ----- - .. rubric:: Special Methods + - :class:`~libcasm.configuration.Supercell` objects hold the symmetry \ + representations needed for applying symmetry to \ + :class:`~libcasm.configuration.Configuration`. Since the same \ + representations apply to all configurations in a given supercell, a \ + single shared :class:`~libcasm.configuration.Supercell` can be used \ + for all configurations in that supercell. The \ + :class:`~libcasm.configuration.SupercellSet` is a container for \ + storing and accessing these shared supercells. + - The :class:`~libcasm.configuration.SupercellSet` holds \ + :class:`~libcasm.configuration.SupercellRecord`, each of which \ + contains a unique shared :class:`~libcasm.configuration.Supercell` \ + as well as its name, whether it is in \ + `canonical form `_, \ + and the name of the canonical equivalent supercell. + - Supercells which are distinct (have different \ + ``transformation_matrix_to_super``) but symmetrically equivalent \ + (lattice points are mapped by a crystal point group operation) may be \ + stored as separate :class:`~libcasm.configuration.SupercellRecord` in \ + the same :class:`~libcasm.configuration.SupercellSet`. + - When an attempt is made to add a \ + :class:`~libcasm.configuration.SupercellRecord` that is already in \ + the :class:`~libcasm.configuration.SupercellSet`, the existing \ + :class:`~libcasm.configuration.SupercellRecord` is returned. + + + Examples + -------- + + .. rubric:: Constructing SupercellSet and adding Supercell + + A :class:`~libcasm.configuration.SupercellSet` can be constructed + and :class:`~libcasm.configuration.Supercell` added to the set as + follows: - Additional methods: + .. code-block:: Python + + import numpy as np + from libcasm.configuration import ( + Supercell, + SupercellSet, + ) + + # construct SupercellSet + supercells = SupercellSet(prim=prim) + + # add Supercell by transformation matrix + T = np.array([[2, 0, 0], [0, 2, 0], [0, 0, 1]]) + supercells.add(T) + + # add Supercell object directly + supercells.add(Supercell(prim, T)) + + # add by canonical supercell name + supercells.add("SCEL4_2_2_1_0_0_0") + + + .. rubric:: Using SupercellSet + + The contents of :class:`~libcasm.configuration.SupercellSet` are of + type :class:`~libcasm.configuration.SupercellRecord`, which can be + iterated over using a standard for loop: + + .. code-block:: Python + + # iterate over SupercellSet contents + for record in supercells: + supercell_name = record.supercell_name + supercell = record.supercell + # do something ... + + The convention ``x in set`` can be used to check the contents of + :class:`~libcasm.configuration.SupercellSet`, using `x` of the + following types: - - ``for record in supercell_set``: Iterate over SupercellRecord in the - SupercellSet - - ``if record in supercell_set``: Check if a - :class:`~libcasm.configuration.SupercellRecord` is in the SupercellSet - - ``if supercell in supercell_set``: Check if a - :class:`~libcasm.configuration.Supercell` is in the SupercellSet - - ``if transformation_matrix_to_super in supercell_set``: Check if a - :class:`~libcasm.configuration.Supercell` with `transformation_matrix_to_super` - (``np.ndarray[np.int[3,3]]``) is in the SupercellSet - - ``if canonical_supercell_name in supercell_set``: Check if a canonical - :class:`~libcasm.configuration.Supercell` with name - `canonical_supercell_name` (``str``) is in the SupercellSet. - - ``len(supercell_set)``: Return the number of SupercellRecord in the - SupercellSet + - ``str``: to check by canonical supercell name + - :class:`~libcasm.configuration.Supercell`: to check by supercell + - :class:`~libcasm.configuration.SupercellRecord`: to check by record + - ``np.ndarray[np.int[3,3]]``: to check by transformation matrix + .. code-block:: Python + + # check if a Supercell is in SupercellSet by transformation matrix + T = np.array([[2, 0, 0], [0, 2, 0], [0, 0, 1]]) + if T in supercells: + # do something ... + + # check by canonical supercell name + if "SCEL4_2_2_1_0_0_0" in supercells: + # do something ... + + The value ``None`` is returned by `get` methods if the requested + supercell is not present: + + .. code-block:: Python + + # get a supercell by canonical name and use it + record = supercells.get("SCEL4_2_2_1_0_0_0") + if record is not None: + supercell = record.supercell + # do something ... + else: + # do something else ... + + # get by transformation matrix + T = np.array([[2, 0, 0], [0, 2, 0], [0, 0, 1]]) + record = supercells.get(T) + if record is not None: + supercell_name = record.supercell_name + # do something ... )pbdoc"); @@ -1393,6 +1465,33 @@ PYBIND11_MODULE(_configuration, m) { The :class:`~libcasm.configuration.Supercell` constructed from the dict. )pbdoc", py::arg("data"), py::arg("supercells")) + .def( + "supercell_name", + [](std::shared_ptr const &supercell) { + auto const &superlattice = supercell->superlattice; + return config::make_supercell_name(superlattice.prim_lattice(), + superlattice.superlattice()); + }, + R"pbdoc( + Returns the supercell name. + + The name has the format ``SCELV_A_B_C_D_E_F``, where ``V`` is the + supercell volume and ``A_B_C_D_E_F`` are entries in the Hermite + normal form of the transformation matrix. + + .. code-block:: + + H = [[A, F, E], + [0, B, D], + [0, 0, C]] + + V = A * B * C + + Returns + ------- + supercell_name : str + The supercell name. + )pbdoc") .def( "to_dict", [](std::shared_ptr const &supercell) { @@ -1989,6 +2088,141 @@ PYBIND11_MODULE(_configuration, m) { contained in the set. )pbdoc", py::arg("record")) + // get + .def( + "get_supercell", + [](config::SupercellSet &m, + std::shared_ptr supercell) -> py::object { + auto it = m.find(supercell); + if (it == m.end()) { + return py::none(); + } + return py::object(py::cast(*it)); + }, + py::return_value_policy::reference_internal, + R"pbdoc( + Find a SupercellRecord by supercell and return a const reference, \ + else return None. + )pbdoc", + py::arg("supercell")) + .def( + "get", + [](config::SupercellSet &m, + std::shared_ptr supercell) -> py::object { + auto it = m.find(supercell); + if (it == m.end()) { + return py::none(); + } + return py::object(py::cast(*it)); + }, + py::return_value_policy::reference_internal, + R"pbdoc( + Find a SupercellRecord by supercell and return a const reference, \ + else return None (equivalent to \ + :func:`~libcasm.configuration.SupercellSet.get_supercell`). + )pbdoc", + py::arg("supercell")) + .def( + "get_by_transformation_matrix_to_super", + [](config::SupercellSet &m, + Eigen::Matrix3l const &transformation_matrix_to_super) + -> py::object { + auto it = m.find(transformation_matrix_to_super); + if (it == m.end()) { + return py::none(); + } + return py::object(py::cast(*it)); + }, + py::return_value_policy::reference_internal, + R"pbdoc( + Find a SupercellRecord by transformation matrix and return a const \ + reference, else return None. + )pbdoc", + py::arg("transformation_matrix_to_super").noconvert()) + .def( + "get", + [](config::SupercellSet &m, + Eigen::Matrix3l const &transformation_matrix_to_super) + -> py::object { + auto it = m.find(transformation_matrix_to_super); + if (it == m.end()) { + return py::none(); + } + return py::object(py::cast(*it)); + }, + py::return_value_policy::reference_internal, + R"pbdoc( + Find a SupercellRecord by transformation matrix and return a const \ + reference, else return None (equivalent to \ + :func:`~libcasm.configuration.SupercellSet.get_by_transformation_matrix_to_super`). + )pbdoc", + py::arg("transformation_matrix_to_super").noconvert()) + .def( + "get_record", + [](config::SupercellSet &m, + config::SupercellRecord const &record) -> py::object { + auto it = m.find(record); + if (it == m.end()) { + return py::none(); + } + return py::object(py::cast(*it)); + }, + py::return_value_policy::reference_internal, + R"pbdoc( + Find a SupercellRecord by record and return a const reference, \ + else return None. + )pbdoc", + py::arg("record")) + .def( + "get", + [](config::SupercellSet &m, + config::SupercellRecord const &record) -> py::object { + auto it = m.find(record); + if (it == m.end()) { + return py::none(); + } + return py::object(py::cast(*it)); + }, + py::return_value_policy::reference_internal, + R"pbdoc( + Find a SupercellRecord by record and return a const reference, \ + else return None (equivalent to \ + :func:`~libcasm.configuration.SupercellSet.get_record`). + )pbdoc", + py::arg("record")) + .def( + "get_by_canonical_name", + [](config::SupercellSet &m, + std::string supercell_name) -> py::object { + auto it = m.find_canonical_by_name(supercell_name); + if (it == m.end()) { + return py::none(); + } + return py::object(py::cast(*it)); + }, + py::return_value_policy::reference_internal, + R"pbdoc( + Find a SupercellRecord by canonical supercell name and return a \ + const reference, else return None. + )pbdoc", + py::arg("supercell_name")) + .def( + "get", + [](config::SupercellSet &m, + std::string supercell_name) -> py::object { + auto it = m.find_canonical_by_name(supercell_name); + if (it == m.end()) { + return py::none(); + } + return py::object(py::cast(*it)); + }, + py::return_value_policy::reference_internal, + R"pbdoc( + Find a SupercellRecord by canonical supercell name and return a \ + const reference, else return None (equivalent to \ + :func:`~libcasm.configuration.SupercellSet.get_by_canonical_name`). + )pbdoc", + py::arg("supercell_name")) // for x in set .def( "__iter__", diff --git a/python/tests/configuration/test_supercell.py b/python/tests/configuration/test_supercell.py index 22b29d6..fb642a1 100644 --- a/python/tests/configuration/test_supercell.py +++ b/python/tests/configuration/test_supercell.py @@ -188,3 +188,23 @@ def test_supercell_io(simple_cubic_binary_prim): print(supercell1) out = f.getvalue() assert "transformation_matrix_to_super" in out + + +def test_supercell_name(simple_cubic_binary_prim): + prim = config.Prim(simple_cubic_binary_prim) + T = np.array( + [ + [2, 0, 0], + [0, 1, 0], + [0, 0, 1], + ] + ) + supercell = config.Supercell(prim, T) + + # supercell_name() should match to_dict()["supercell_name"] + assert supercell.supercell_name() == supercell.to_dict()["supercell_name"] + + # supercell_name() should match SupercellRecord.supercell_name + supercells = config.SupercellSet(prim) + record = supercells.add(supercell) + assert supercell.supercell_name() == record.supercell_name diff --git a/python/tests/configuration/test_supercell_set.py b/python/tests/configuration/test_supercell_set.py index bdf3f82..b1528e0 100644 --- a/python/tests/configuration/test_supercell_set.py +++ b/python/tests/configuration/test_supercell_set.py @@ -287,3 +287,60 @@ def test_SupercellRecord_repr(simple_cubic_binary_prim): assert "supercell_name" in out assert "canonical_supercell_name" in out assert "is_canonical" in out + + +def test_SupercellSet_get_1(simple_cubic_binary_prim): + prim = config.Prim(simple_cubic_binary_prim) + supercells = config.SupercellSet(prim) + + T = np.array( + [ + [2, 1, 0], + [0, 1, 0], + [0, 0, 1], + ] + ) + supercell = config.make_canonical_supercell(config.Supercell(prim, T)) + record = supercells.add(supercell) + supercell_name = record.supercell_name + + # get by supercell + record_1 = supercells.get(supercell) + assert record_1 is not None + assert record_1 == record + + record_2 = supercells.get_supercell(supercell) + assert record_2 == record_1 + + # get by transformation matrix + canonical_T = supercell.transformation_matrix_to_super + record_3 = supercells.get(canonical_T) + assert record_3 is not None + assert record_3 == record + + record_4 = supercells.get_by_transformation_matrix_to_super(canonical_T) + assert record_4 == record_3 + + # get by record + record_5 = supercells.get(record) + assert record_5 is not None + assert record_5 == record + + record_6 = supercells.get_record(record) + assert record_6 == record_5 + + # get by canonical name + record_7 = supercells.get(supercell_name) + assert record_7 is not None + assert record_7 == record + + record_8 = supercells.get_by_canonical_name(supercell_name) + assert record_8 == record_7 + + # after removal, all get variants return None + supercells.remove(supercell) + + assert supercells.get(supercell) is None + assert supercells.get(canonical_T) is None + assert supercells.get(record) is None + assert supercells.get(supercell_name) is None