1 /*  This file is part of Jellyfish.
2 
3     This work is dual-licensed under 3-Clause BSD License or GPL 3.0.
4     You can choose between one of them if you use this work.
5 
6 `SPDX-License-Identifier: BSD-3-Clause OR  GPL-3.0`
7 */
8 
9 #include <cstdlib>
10 #include <new>
11 #include <stdexcept>
12 #include <vector>
13 #include <sstream>
14 #include <assert.h>
15 #include <jellyfish/rectangular_binary_matrix.hpp>
16 
17 #ifdef HAVE_CONFIG_H
18 #include <config.h>
19 #endif
20 
21 #ifdef HAVE_POSIX_MEMALIGN
__mem_align(void ** memptr,size_t alignement,size_t size)22 inline int __mem_align(void** memptr, size_t alignement, size_t size) {
23   return posix_memalign(memptr, alignement, size);
24 }
25 #elif HAVE_ALIGNED_ALLOC
__mem_align(void ** memptr,size_t alignment,size_t size)26 inline int __mem_align(void** memptr, size_t alignment, size_t size) {
27   *memptr = aligned_alloc(alignment, size);
28   return memptr == NULL;
29 }
30 #else
31 #error No function to allocate aligned memory
32 #endif
33 
alloc(unsigned int r,unsigned int c)34 uint64_t *jellyfish::RectangularBinaryMatrix::alloc(unsigned int r, unsigned int c) {
35   if(r > (sizeof(uint64_t) * 8) || r == 0 || c == 0) {
36     std::ostringstream err;
37     err << "Invalid matrix size " << r << "x" << c;
38     throw std::out_of_range(err.str());
39   }
40   void *mem;
41   // Make sure the number of words allocated is a multiple of
42   // 8. Necessary for loop unrolling of vector multiplication
43   size_t alloc_columns = (c / 8 + (c % 8 != 0)) * 8;
44   if(__mem_align(&mem, sizeof(uint64_t) * 2, alloc_columns * sizeof(uint64_t)))
45     throw std::bad_alloc();
46   memset(mem, '\0', sizeof(uint64_t) * alloc_columns);
47   return (uint64_t *)mem;
48 }
49 
init_low_identity(bool simplify)50 void jellyfish::RectangularBinaryMatrix::init_low_identity(bool simplify) {
51   if(!_columns) return;
52   if(_c == _r && simplify) {
53     free(_columns);
54     _columns = NULL;
55     return;
56   }
57   memset(_columns, '\0', sizeof(uint64_t) * _c);
58   unsigned int row = std::min(_c, _r);
59   unsigned int col = _c - row;
60   _columns[col] = (uint64_t)1 << (row - 1);
61   for(unsigned int i = col + 1; i < _c; ++i)
62     _columns[i] = _columns[i - 1] >> 1;
63 }
64 
is_low_identity() const65 bool jellyfish::RectangularBinaryMatrix::is_low_identity() const {
66   if(!_columns) return true;
67   unsigned int row = std::min(_c, _r);
68   unsigned int col = _c - row;
69 
70   for(unsigned int i = 0; i < col; ++i)
71     if(_columns[i])
72       return false;
73   if(_columns[col] != (uint64_t)1 << (row - 1))
74     return false;
75   for(unsigned int i = col + 1; i < _c; ++i)
76     if(_columns[i] != _columns[i - 1] >> 1)
77       return false;
78   return true;
79 }
80 
pseudo_multiplication(const jellyfish::RectangularBinaryMatrix & rhs) const81 jellyfish::RectangularBinaryMatrix jellyfish::RectangularBinaryMatrix::pseudo_multiplication(const jellyfish::RectangularBinaryMatrix &rhs) const {
82   if(_r != rhs._r || _c != rhs._c)
83     throw std::domain_error("Matrices of different size");
84   if(!_columns) return rhs;
85   if(!rhs._columns) return *this;
86 
87   RectangularBinaryMatrix res(_r, _c);
88 
89   // v is a vector. The lower part is equal to the given column of rhs
90   // and the high part is the identity matrix.
91   //  uint64_t v[nb_words()];
92   uint64_t *v = new uint64_t[nb_words()];
93   memset(v, '\0', sizeof(uint64_t) * nb_words());
94   unsigned int j = nb_words() - 1;
95   v[j]           = msb();
96   const unsigned int row = std::min(_c, _r);
97   const unsigned int col = _c - row;
98 
99   unsigned int i;
100   for(i = 0; i < col; ++i) {
101     // Set the lower part to rhs and do vector multiplication
102     v[0] ^= rhs[i];
103     res.get(i) = this->times(&v[0]);
104     //res.get(i) = this->times_loop(v);
105 
106     // Zero the lower part and shift the one down the diagonal.
107     v[0]  ^= rhs[i];
108     v[j] >>= 1;
109     if(!v[j])
110       v[--j] = (uint64_t)1 << (sizeof(uint64_t) * 8 - 1);
111   }
112   // No more identity part to deal with
113   memset(v, '\0', sizeof(uint64_t) * nb_words());
114   for( ; i < _c; ++i) {
115     v[0] = rhs[i];
116     res.get(i) = this->times(v);
117     //res.get(i) = this->times_loop(v);
118   }
119 
120   delete[] v;
121   return res;
122 }
123 
pseudo_rank() const124 unsigned int jellyfish::RectangularBinaryMatrix::pseudo_rank() const {
125   if(!_columns) return _c;
126 
127   unsigned int            rank = _c;
128   RectangularBinaryMatrix pivot(*this);
129 
130   // Make the matrix lower triangular.
131   unsigned int srow = std::min(_r, _c);
132   unsigned int scol = _c - srow;
133   uint64_t mask = (uint64_t)1 << (srow - 1);
134   for(unsigned int i = scol; i < _c; ++i, mask >>= 1) {
135     if(!(pivot.get(i) & mask)) {
136       // current column has a 0 in the diagonal. XOR it with another
137       // column to get a 1.
138       unsigned int j;
139       for(j = i + 1; j < _c; ++j)
140         if(pivot.get(j) & mask)
141           break;
142       if(j == _c) {
143         // Did not find one, the matrix is not full rank.
144         rank = i;
145         break;
146       }
147       pivot.get(i) ^= pivot.get(j);
148     }
149 
150     // Zero out every ones on the ith row in the upper part of the
151     // matrix.
152     for(unsigned int j = i + 1; j < _c; ++j)
153       if(pivot.get(j) & mask)
154         pivot.get(j) ^= pivot.get(i);
155   }
156 
157   return rank;
158 }
159 
pseudo_inverse() const160 jellyfish::RectangularBinaryMatrix jellyfish::RectangularBinaryMatrix::pseudo_inverse() const {
161   if(!_columns) return *this;
162 
163   RectangularBinaryMatrix pivot(*this);
164   RectangularBinaryMatrix res(_r, _c); res.init_low_identity(false);
165   unsigned int            i, j;
166   uint64_t                mask;
167 
168   // Do gaussian elimination on the columns and apply the same
169   // operation to res.
170 
171   // Make pivot lower triangular.
172   unsigned int srow = std::min(_r, _c);
173   unsigned int scol = _c - srow;
174   mask = (uint64_t)1 << (srow - 1);
175   for(i = scol; i < _c; ++i, mask >>= 1) {
176     if(!(pivot.get(i) & mask)) {
177       // current column has a 0 in the diagonal. XOR it with another
178       // column to get a 1.
179       unsigned int j;
180       for(j = i + 1; j < _c; ++j)
181         if(pivot.get(j) & mask)
182           break;
183       if(j == _c)
184         throw std::domain_error("Matrix is singular");
185       pivot.get(i) ^= pivot.get(j);
186       res.get(i)   ^= res.get(j);
187     }
188     // Zero out every ones on the ith row in the upper part of the
189     // matrix.
190     for(j = i + 1; j < _c; ++j) {
191       if(pivot.get(j) & mask) {
192         pivot.get(j) ^= pivot.get(i);
193         res.get(j)   ^= res.get(i);
194       }
195     }
196   }
197 
198   // Make pivot the lower identity
199   mask = (uint64_t)1 << (srow - 1);
200   for(i = scol; i < _c; ++i, mask >>= 1) {
201     for(j = 0; j < i; ++j) {
202       if(pivot.get(j) & mask) {
203         pivot.get(j) ^= pivot.get(i);
204         res.get(j)   ^= res.get(i);
205       }
206     }
207   }
208 
209   return res;
210 }
211 
print(std::ostream & os) const212 void jellyfish::RectangularBinaryMatrix::print(std::ostream &os) const {
213   if(!_columns) {
214     for(unsigned int i = 0; i < _c; ++i) {
215       for(unsigned int j = 0; j < _c; ++j)
216         os << (i == j ? '1' : '0');
217       os << '\n';
218     }
219   } else {
220       uint64_t mask = (uint64_t)1 << (_r - 1);
221       for( ; mask; mask >>= 1) {
222         for(unsigned int j = 0; j < _c; ++j)
223           os << (mask & _columns[j] ? '1' : '0');
224         os << '\n';
225       }
226   }
227 }
228 
229 template<typename T>
print_vector(std::ostream & os,const T & v) const230 void jellyfish::RectangularBinaryMatrix::print_vector(std::ostream &os, const T &v) const {
231   uint64_t mask = msb();
232   for(int i = nb_words() - 1; i >= 0; --i) {
233     for( ; mask; mask >>= 1)
234       os << (v[i] & mask ? "1" : "0");
235     mask  = (uint64_t)1 << (sizeof(uint64_t) * 8 - 1);
236   }
237   os << "\n";
238 }
239 
randomize_pseudo_inverse(uint64_t (* rng)())240 jellyfish::RectangularBinaryMatrix jellyfish::RectangularBinaryMatrix::randomize_pseudo_inverse(uint64_t (*rng)()) {
241   while(true) {
242     randomize(rng);
243     try {
244       return pseudo_inverse();
245     } catch(std::domain_error &e) { }
246   }
247 }
248