.. _program_listing_file_src_pointsymmetry.cpp: Program Listing for File pointsymmetry.cpp ========================================== |exhale_lsh| :ref:`Return to documentation for file ` (``src/pointsymmetry.cpp``) .. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS .. code-block:: cpp /* This file is part of brille. Copyright © 2019,2020 Greg Tucker brille is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. brille 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with brille. If not, see . */ #include #include #include #include "pointsymmetry.hpp" #include #include "pointgroup.hpp" #include #include "approx.hpp" using namespace brille; /*****************************************************************************\ | PointSymmetry class Member functions: | \*****************************************************************************/ size_t PointSymmetry::add(const int *r){ Matrix newR; for (size_t i=0; i<9; ++i) newR[i] = r[i]; return this->add(newR); } size_t PointSymmetry::add(const Matrix& r){ this->R.push_back(r); return this->R.size(); } size_t PointSymmetry::add(const std::string& s){ Matrix newR{0,0,0,0,0,0,0,0,0}; // a valid encoded Motion will be of the form // ∑ₙiₙαₙ + f where each iₙ is an integer, αₙ∈{x,y,z} // and f one of 0, ±1/12, ±1/6, ±1/4, ±1/3, ±1/2 // but we should support any rational or floating point value for f int n{0}, i{1}; char c{' '}; std::istringstream stream(s); size_t ret{0}; while (stream.good()){ c = char(stream.get()); switch (c){ case ';': // end of one motion ret = this->add(newR); newR = {0,0,0,0,0,0,0,0,0}; n=0; i=1; break; case ',': // end of motion component i=1; break; case 'x': newR[n*3] = i; i=1; break; case 'y': newR[n*3+1] = i; i=1; break; case 'z': newR[n*3+2] = i; i=1; break; default: // not ;,xyz std::stringstream tmp; tmp << c; bool haspoint{false}, hasslash{false}; while (std::strchr(";,xyz-+",stream.peek())==nullptr){ hasslash |= '/'==stream.peek(); haspoint |= '.'==stream.peek(); tmp << stream.get(); } if (haspoint && hasslash) throw std::logic_error("Number has both point and slash?!"); if (!(haspoint^hasslash)) tmp >> i; // TT (error) or FF (integer) // either floating point or rational numbers represent part of the // translation which strictly shouldn't be here for pointgroup // operations, but we should just skip for now } } if (';'!=c) ret = this->add(newR); return ret; } bool PointSymmetry::set(const size_t i, const int *r){ if ( i>=this->size() ) return false; for(size_t j=0; j<9; j++) this->R[i][j] = r[j]; return true; } bool PointSymmetry::get(const size_t i, int *r) const { if ( i>=this->size() ) return false; for(size_t j=0; j<9; j++) r[j]=this->R[i][j]; return true; } int * PointSymmetry::data(const size_t i) { return (isize()) ? this->R[i].data() : nullptr; } const int * PointSymmetry::data(const size_t i) const { return (isize()) ? this->R[i].data() : nullptr; } Matrix PointSymmetry::get(const size_t i) const { return (isize()) ? this->R[i] : Matrix({0,0,0,0,0,0,0,0,0}); } Matrix PointSymmetry::get_proper(const size_t i) const { Matrix out{0,0,0, 0,0,0, 0,0,0}; if (isize()){ out = this->R[i]; if (this->isometry(i) < 0) for (size_t j=0; j<9u; ++j) out[j] *= -1; } return out; } Matrix PointSymmetry::get_inverse(const size_t i) const { Matrix identity{1,0,0, 0,1,0, 0,0,1}, result; if (i>=this->size()) return identity; // We *could* calculate R⁻¹ but might run into rounding/type issues. // We know that there must be an element of R for which RᵢRⱼ=E, // the identity element. Let's look for Rⱼ instead. for (size_t j=0; jsize(); ++j){ brille::utils::multiply_matrix_matrix(result.data(), this->R[j].data(), this->R[i].data()); if (brille::approx::matrix(3, identity.data(), result.data())) return this->R[j]; } throw std::runtime_error("Incomplete pointgroup. Missing inverse of operation."); } size_t PointSymmetry::get_inverse_index(const size_t i) const { if (i>=this->size()) return i; Matrix identity{1,0,0, 0,1,0, 0,0,1}, result; // We *could* calculate R⁻¹ but might run into rounding/type issues. // We know that there must be an element of R for which RᵢRⱼ=E, // the identity element. Let's look for Rⱼ instead. for (size_t j=0; jsize(); ++j){ brille::utils::multiply_matrix_matrix(result.data(), this->R[j].data(), this->R[i].data()); if (brille::approx::matrix(3, identity.data(), result.data())) return j; } throw std::runtime_error("Incomplete pointgroup. Missing inverse of operation."); } size_t PointSymmetry::find_index(const Matrix& a) const { auto iseq = [ap=a.data()](const Matrix& r){ return brille::approx::matrix(3,ap,r.data());}; auto itr = std::find_if(this->R.begin(), this->R.end(), iseq); return std::distance(this->R.begin(), itr); } // const Matrix& PointSymmetry::get(const size_t i) const { // if (i>=this->size()) // throw std::out_of_range("The requested symmetry operation is out of range"); // return this->R[i]; // } size_t PointSymmetry::resize(const size_t newsize){ this->R.resize(newsize); return this->R.size(); } void PointSymmetry::print(const size_t i) const { for (size_t j=0; j<3u; ++j){ for (size_t k=0; k<3u; ++k) std::cout << " " << R[i][j*3u+k]; std::cout << std::endl; } } void PointSymmetry::print(void) const { for (size_t i=0; iR.size(); ++i){ this->print(i); std::cout << std::endl; } } void PointSymmetry::sort(const int mode) { auto oup = [](Matrix a, Matrix b){ return rotation_order(a.data()) < rotation_order(b.data());}; auto odw = [](Matrix a, Matrix b){ return rotation_order(a.data()) > rotation_order(b.data());}; auto iup = [](Matrix a, Matrix b){ return isometry_value(a.data()) < isometry_value(b.data());}; auto idw = [](Matrix a, Matrix b){ return isometry_value(a.data()) > isometry_value(b.data());}; switch(mode){ // sort by isometry, decreasing case -2: std::sort(this->R.begin(),this->R.end(),idw); break; // sort by order, decreasing case -1: std::sort(this->R.begin(),this->R.end(),odw); break; // sort by isometry, increasing case 2: std::sort(this->R.begin(),this->R.end(),iup); break; // sort by order, increasing default: std::sort(this->R.begin(),this->R.end(),oup); } } void PointSymmetry::permute(const std::vector& p){ std::string msg; std::vector o(this->size()); std::iota(o.begin(), o.end(), 0u); // 0, 1, 2, …, size()-1 std::vector s=p; std::sort(s.begin(), s.end()); // includes requires *sorted* ranges if (!std::includes(o.begin(), o.end(), s.begin(), s.end()) || p.size()!=this->size()){ msg="The provided permutation vector ["; for (auto x: p) msg += " " + std::to_string(x); msg += " ] is invalid."; msg += " A permutation of ["; for (auto x: o) msg += " " + std::to_string(x); msg += " ] was expected."; throw std::runtime_error(msg); } // swapping requires we have the inverse permutation for (size_t i=0; isize();){ if (s[i]==i) { ++i; } else { std::swap(this->R[i], this->R[s[i]]); std::swap(s[i], s[s[i]]); } } // confirm that the permuting worked: if (!std::is_sorted(s.begin(), s.end())){ msg="Undoing the permutation ["; for (auto x: p) msg += " " + std::to_string(x); msg +=" ] failed. End result is ["; for (auto x: s) msg += " " + std::to_string(x); msg +=" ]"; throw std::runtime_error(msg); } } std::vector PointSymmetry::orders(void) const { std::vector o; for (auto r: this->R) o.push_back(rotation_order(r.data())); return o; } std::vector PointSymmetry::isometries(void) const { std::vector i; for (auto r: this->R) i.push_back(isometry_value(r.data())); return i; } Vectors PointSymmetry::axes(void) const { Vectors ax; for (auto r: this->R) ax.push_back(brille::rotation_axis_and_perpendicular_vectors(r.data())[0]); return ax; } bool PointSymmetry::has_space_inversion() const { // space inversion means that a pointgroup operation has isometry -1. for (auto op: this->R) if (-1 == isometry_value(op.data())) return true; return false; } bool PointSymmetry::has(const Matrix& m) const { for (auto r: R) if (brille::approx::matrix(m.data(), r.data())) return true; return false; } Matrix PointSymmetry::pop(const size_t i) { Matrix out = this->get(i); this->erase(i); return out; } size_t PointSymmetry::erase(const size_t i){ if (i>=this->size()) throw std::out_of_range("The requested symmetry operation is out of range"); this->R.erase(this->R.begin()+i); return this->size(); } int PointSymmetry::order(const size_t i) const { if (i>=this->size()) throw std::out_of_range("The requested symmetry operation is out of range"); return rotation_order(this->R[i].data()); } int PointSymmetry::isometry(const size_t i) const { if (i>=this->size()) throw std::out_of_range("The requested symmetry operation is out of range"); return isometry_value(this->R[i].data()); } Vector PointSymmetry::axis(const size_t i) const { if (i>=this->size()) throw std::out_of_range("The requested symmetry operation is out of range"); return brille::rotation_axis_and_perpendicular_vectors(this->R[i].data())[0]; } Vector PointSymmetry::perpendicular_axis(const size_t i) const { if (i>=this->size()) throw std::out_of_range("The requested symmetry operation is out of range"); return brille::rotation_axis_and_perpendicular_vectors(this->R[i].data())[1]; } Vectors PointSymmetry::perpendicular_axes(void) const { Vectors ax; for (auto r: this->R) ax.push_back(brille::rotation_axis_and_perpendicular_vectors(r.data())[1]); return ax; } PointSymmetry PointSymmetry::generate(void) const { Matrix x, y, E{1,0,0, 0,1,0, 0,0,1}; // E, the identity operation PointSymmetry gen; gen.add(E); // any generated operations always need the identity operation size_t gensize; for (size_t i=0; isize(); ++i){ x = E; // for each of R, R¹,…, Rⁿ⁻¹ for (int count=this->order(i); count--;){ // post-decrimate to run the loop count times brille::utils::multiply_matrix_matrix(y.data(), this->data(i), x.data()); x = y; // multiply Rⁱ by the operators in gen gensize = gen.size(); // only multiply against pre-existing operations for (size_t j=0; jgetall()), listB, listC; while (listA.size()){ // move the first element of A to B listB.add(listA.get(0)); // use the operations in B as generators listC = listB.generate(); // remove from A all generated operations (in C) for (size_t i=0; i all_axis = this->axes(); std::vector eqv_idx(this->size()); std::vector unq_idx; Vectors unq_axis; for (size_t i=0; isize(); ++i){ bool isunique = true; for (auto j: unq_idx) if (brille::approx::vector(all_axis[j].data(), all_axis[i].data())){ eqv_idx[i] = j; isunique = false; break; } if (isunique){ unq_idx.push_back(i); eqv_idx[i]=i; unq_axis.push_back(all_axis[i]); } } // equal-valued entries of eqv_idx have the same stationary axis // but the order of the selected unique rotation may not be what we want for (size_t i=0; isize(); ++j) if (idx == eqv_idx[j] && this->order(j) > this->order(unq_idx[i])) unq_idx[i] = j; } /* Now each unique index points to a symmetry operation of highest order along its unique axis. What is not determined at this point is whether each symmetry operation is N or ̄N -- both of which are of order N. In order to ensure we return just the rotation part of any symmetry operation, we must multiply any rotoinversion operations by the inversion operation.*/ int onebar[9]{-1,0,0, 0,-1,0, 0,0,-1}; PointSymmetry out; for (auto i: unq_idx) if (this->order(i) > min_order){ out.add(this->get(i)); if (this->isometry(i) < 0) brille::utils::multiply_matrix_matrix(out.data(out.size()-1), onebar, this->data(i)); } out.sort(); // double-check that the orders are sorted return out; } PointSymmetry PointSymmetry::higher(const int min_order) const { PointSymmetry out; for (size_t i=0; isize(); ++i) if (this->order(i) > min_order) out.add(this->get(i)); out.sort(); return out; }