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