ViennaCL - The Vienna Computing Library
1.5.1
|
00001 #ifndef VIENNACL_COMPRESSED_compressed_compressed_matrix_HPP_ 00002 #define VIENNACL_COMPRESSED_compressed_compressed_matrix_HPP_ 00003 00004 /* ========================================================================= 00005 Copyright (c) 2010-2014, Institute for Microelectronics, 00006 Institute for Analysis and Scientific Computing, 00007 TU Wien. 00008 Portions of this software are copyright by UChicago Argonne, LLC. 00009 00010 ----------------- 00011 ViennaCL - The Vienna Computing Library 00012 ----------------- 00013 00014 Project Head: Karl Rupp rupp@iue.tuwien.ac.at 00015 00016 (A list of authors and contributors can be found in the PDF manual) 00017 00018 License: MIT (X11), see file LICENSE in the base directory 00019 ============================================================================= */ 00020 00025 #include <vector> 00026 #include <list> 00027 #include <map> 00028 #include "viennacl/forwards.h" 00029 #include "viennacl/vector.hpp" 00030 00031 #include "viennacl/linalg/sparse_matrix_operations.hpp" 00032 00033 #include "viennacl/tools/tools.hpp" 00034 #include "viennacl/tools/entry_proxy.hpp" 00035 00036 namespace viennacl 00037 { 00038 namespace detail 00039 { 00040 template <typename CPU_MATRIX, typename SCALARTYPE> 00041 void copy_impl(const CPU_MATRIX & cpu_matrix, 00042 compressed_compressed_matrix<SCALARTYPE> & gpu_matrix, 00043 vcl_size_t nonzero_rows, 00044 vcl_size_t nonzeros) 00045 { 00046 assert( (gpu_matrix.size1() == 0 || viennacl::traits::size1(cpu_matrix) == gpu_matrix.size1()) && bool("Size mismatch") ); 00047 assert( (gpu_matrix.size2() == 0 || viennacl::traits::size2(cpu_matrix) == gpu_matrix.size2()) && bool("Size mismatch") ); 00048 00049 viennacl::backend::typesafe_host_array<unsigned int> row_buffer(gpu_matrix.handle1(), nonzero_rows + 1); 00050 viennacl::backend::typesafe_host_array<unsigned int> row_indices(gpu_matrix.handle3(), nonzero_rows); 00051 viennacl::backend::typesafe_host_array<unsigned int> col_buffer(gpu_matrix.handle2(), nonzeros); 00052 std::vector<SCALARTYPE> elements(nonzeros); 00053 00054 vcl_size_t row_index = 0; 00055 vcl_size_t data_index = 0; 00056 00057 for (typename CPU_MATRIX::const_iterator1 row_it = cpu_matrix.begin1(); 00058 row_it != cpu_matrix.end1(); 00059 ++row_it) 00060 { 00061 bool row_empty = true; 00062 00063 for (typename CPU_MATRIX::const_iterator2 col_it = row_it.begin(); 00064 col_it != row_it.end(); 00065 ++col_it) 00066 { 00067 SCALARTYPE entry = *col_it; 00068 if (entry != SCALARTYPE(0)) 00069 { 00070 if (row_empty) 00071 { 00072 assert(row_index < nonzero_rows && bool("Provided count of nonzero rows exceeded!")); 00073 00074 row_empty = false; 00075 row_buffer.set(row_index, data_index); 00076 row_indices.set(row_index, col_it.index1()); 00077 ++row_index; 00078 } 00079 00080 col_buffer.set(data_index, col_it.index2()); 00081 elements[data_index] = entry; 00082 ++data_index; 00083 } 00084 } 00085 } 00086 row_buffer.set(row_index, data_index); 00087 00088 gpu_matrix.set(row_buffer.get(), 00089 row_indices.get(), 00090 col_buffer.get(), 00091 &elements[0], 00092 cpu_matrix.size1(), 00093 cpu_matrix.size2(), 00094 nonzero_rows, 00095 nonzeros); 00096 } 00097 } 00098 00099 //provide copy-operation: 00114 template <typename CPU_MATRIX, typename SCALARTYPE> 00115 void copy(const CPU_MATRIX & cpu_matrix, 00116 compressed_compressed_matrix<SCALARTYPE> & gpu_matrix ) 00117 { 00118 //std::cout << "copy for (" << cpu_matrix.size1() << ", " << cpu_matrix.size2() << ", " << cpu_matrix.nnz() << ")" << std::endl; 00119 00120 if ( cpu_matrix.size1() > 0 && cpu_matrix.size2() > 0 ) 00121 { 00122 //determine nonzero rows and total nonzeros: 00123 vcl_size_t num_entries = 0; 00124 vcl_size_t nonzero_rows = 0; 00125 for (typename CPU_MATRIX::const_iterator1 row_it = cpu_matrix.begin1(); 00126 row_it != cpu_matrix.end1(); 00127 ++row_it) 00128 { 00129 bool row_empty = true; 00130 for (typename CPU_MATRIX::const_iterator2 col_it = row_it.begin(); 00131 col_it != row_it.end(); 00132 ++col_it) 00133 { 00134 if (*col_it != SCALARTYPE(0)) 00135 { 00136 ++num_entries; 00137 00138 if (row_empty) 00139 { 00140 row_empty = false; 00141 ++nonzero_rows; 00142 } 00143 } 00144 } 00145 } 00146 00147 if (num_entries == 0) //we copy an empty matrix 00148 num_entries = 1; 00149 00150 //set up matrix entries: 00151 detail::copy_impl(cpu_matrix, gpu_matrix, nonzero_rows, num_entries); 00152 } 00153 } 00154 00155 00156 //adapted for std::vector< std::map < > > argument: 00162 template <typename SizeType, typename SCALARTYPE> 00163 void copy(const std::vector< std::map<SizeType, SCALARTYPE> > & cpu_matrix, 00164 compressed_compressed_matrix<SCALARTYPE> & gpu_matrix ) 00165 { 00166 vcl_size_t nonzero_rows = 0; 00167 vcl_size_t nonzeros = 0; 00168 vcl_size_t max_col = 0; 00169 for (vcl_size_t i=0; i<cpu_matrix.size(); ++i) 00170 { 00171 if (cpu_matrix[i].size() > 0) 00172 ++nonzero_rows; 00173 nonzeros += cpu_matrix[i].size(); 00174 if (cpu_matrix[i].size() > 0) 00175 max_col = std::max<vcl_size_t>(max_col, (cpu_matrix[i].rbegin())->first); 00176 } 00177 00178 viennacl::detail::copy_impl(tools::const_sparse_matrix_adapter<SCALARTYPE, SizeType>(cpu_matrix, cpu_matrix.size(), max_col + 1), 00179 gpu_matrix, 00180 nonzero_rows, 00181 nonzeros); 00182 } 00183 00184 00185 // 00186 // gpu to cpu: 00187 // 00197 template <typename CPU_MATRIX, typename SCALARTYPE> 00198 void copy(const compressed_compressed_matrix<SCALARTYPE> & gpu_matrix, 00199 CPU_MATRIX & cpu_matrix ) 00200 { 00201 assert( (cpu_matrix.size1() == gpu_matrix.size1()) && bool("Size mismatch") ); 00202 assert( (cpu_matrix.size2() == gpu_matrix.size2()) && bool("Size mismatch") ); 00203 00204 if ( gpu_matrix.size1() > 0 && gpu_matrix.size2() > 0 ) 00205 { 00206 //get raw data from memory: 00207 viennacl::backend::typesafe_host_array<unsigned int> row_buffer(gpu_matrix.handle1(), gpu_matrix.nnz1() + 1); 00208 viennacl::backend::typesafe_host_array<unsigned int> row_indices(gpu_matrix.handle1(), gpu_matrix.nnz1()); 00209 viennacl::backend::typesafe_host_array<unsigned int> col_buffer(gpu_matrix.handle2(), gpu_matrix.nnz()); 00210 std::vector<SCALARTYPE> elements(gpu_matrix.nnz()); 00211 00212 //std::cout << "GPU->CPU, nonzeros: " << gpu_matrix.nnz() << std::endl; 00213 00214 viennacl::backend::memory_read(gpu_matrix.handle1(), 0, row_buffer.raw_size(), row_buffer.get()); 00215 viennacl::backend::memory_read(gpu_matrix.handle3(), 0, row_indices.raw_size(), row_indices.get()); 00216 viennacl::backend::memory_read(gpu_matrix.handle2(), 0, col_buffer.raw_size(), col_buffer.get()); 00217 viennacl::backend::memory_read(gpu_matrix.handle(), 0, sizeof(SCALARTYPE)* gpu_matrix.nnz(), &(elements[0])); 00218 00219 //fill the cpu_matrix: 00220 vcl_size_t data_index = 0; 00221 for (vcl_size_t i = 1; i < row_buffer.size(); ++i) 00222 { 00223 while (data_index < row_buffer[i]) 00224 { 00225 if (col_buffer[data_index] >= gpu_matrix.size2()) 00226 { 00227 std::cerr << "ViennaCL encountered invalid data at colbuffer[" << data_index << "]: " << col_buffer[data_index] << std::endl; 00228 return; 00229 } 00230 00231 if (elements[data_index] != static_cast<SCALARTYPE>(0.0)) 00232 cpu_matrix(row_indices[i-1], col_buffer[data_index]) = elements[data_index]; 00233 ++data_index; 00234 } 00235 } 00236 } 00237 } 00238 00239 00245 template <typename SCALARTYPE> 00246 void copy(const compressed_compressed_matrix<SCALARTYPE> & gpu_matrix, 00247 std::vector< std::map<unsigned int, SCALARTYPE> > & cpu_matrix) 00248 { 00249 tools::sparse_matrix_adapter<SCALARTYPE> temp(cpu_matrix, cpu_matrix.size(), cpu_matrix.size()); 00250 copy(gpu_matrix, temp); 00251 } 00252 00253 00255 00262 template<class SCALARTYPE> 00263 class compressed_compressed_matrix 00264 { 00265 public: 00266 typedef viennacl::backend::mem_handle handle_type; 00267 typedef scalar<typename viennacl::tools::CHECK_SCALAR_TEMPLATE_ARGUMENT<SCALARTYPE>::ResultType> value_type; 00268 typedef vcl_size_t size_type; 00269 00271 compressed_compressed_matrix() : rows_(0), cols_(0), nonzero_rows_(0), nonzeros_(0) {} 00272 00281 explicit compressed_compressed_matrix(vcl_size_t rows, vcl_size_t cols, vcl_size_t nonzero_rows = 0, vcl_size_t nonzeros = 0, viennacl::context ctx = viennacl::context()) 00282 : rows_(rows), cols_(cols), nonzero_rows_(nonzero_rows), nonzeros_(nonzeros) 00283 { 00284 row_buffer_.switch_active_handle_id(ctx.memory_type()); 00285 row_indices_.switch_active_handle_id(ctx.memory_type()); 00286 col_buffer_.switch_active_handle_id(ctx.memory_type()); 00287 elements_.switch_active_handle_id(ctx.memory_type()); 00288 00289 #ifdef VIENNACL_WITH_OPENCL 00290 if (ctx.memory_type() == OPENCL_MEMORY) 00291 { 00292 row_buffer_.opencl_handle().context(ctx.opencl_context()); 00293 row_indices_.opencl_handle().context(ctx.opencl_context()); 00294 col_buffer_.opencl_handle().context(ctx.opencl_context()); 00295 elements_.opencl_handle().context(ctx.opencl_context()); 00296 } 00297 #endif 00298 if (rows > 0) 00299 { 00300 viennacl::backend::memory_create(row_buffer_, viennacl::backend::typesafe_host_array<unsigned int>().element_size() * (rows + 1), ctx); 00301 } 00302 if (nonzeros > 0) 00303 { 00304 viennacl::backend::memory_create(col_buffer_, viennacl::backend::typesafe_host_array<unsigned int>().element_size() * nonzeros, ctx); 00305 viennacl::backend::memory_create(elements_, sizeof(SCALARTYPE) * nonzeros, ctx); 00306 } 00307 } 00308 00315 explicit compressed_compressed_matrix(vcl_size_t rows, vcl_size_t cols, viennacl::context ctx) 00316 : rows_(rows), cols_(cols), nonzeros_(0) 00317 { 00318 row_buffer_.switch_active_handle_id(ctx.memory_type()); 00319 col_buffer_.switch_active_handle_id(ctx.memory_type()); 00320 elements_.switch_active_handle_id(ctx.memory_type()); 00321 00322 #ifdef VIENNACL_WITH_OPENCL 00323 if (ctx.memory_type() == OPENCL_MEMORY) 00324 { 00325 row_buffer_.opencl_handle().context(ctx.opencl_context()); 00326 col_buffer_.opencl_handle().context(ctx.opencl_context()); 00327 elements_.opencl_handle().context(ctx.opencl_context()); 00328 } 00329 #endif 00330 if (rows > 0) 00331 { 00332 viennacl::backend::memory_create(row_buffer_, viennacl::backend::typesafe_host_array<unsigned int>().element_size() * (rows + 1), ctx); 00333 } 00334 } 00335 00336 explicit compressed_compressed_matrix(viennacl::context ctx) : rows_(0), cols_(0), nonzero_rows_(0), nonzeros_(0) 00337 { 00338 row_buffer_.switch_active_handle_id(ctx.memory_type()); 00339 row_indices_.switch_active_handle_id(ctx.memory_type()); 00340 col_buffer_.switch_active_handle_id(ctx.memory_type()); 00341 elements_.switch_active_handle_id(ctx.memory_type()); 00342 00343 #ifdef VIENNACL_WITH_OPENCL 00344 if (ctx.memory_type() == OPENCL_MEMORY) 00345 { 00346 row_buffer_.opencl_handle().context(ctx.opencl_context()); 00347 row_indices_.opencl_handle().context(ctx.opencl_context()); 00348 col_buffer_.opencl_handle().context(ctx.opencl_context()); 00349 elements_.opencl_handle().context(ctx.opencl_context()); 00350 } 00351 #endif 00352 } 00353 00354 00355 #ifdef VIENNACL_WITH_OPENCL 00356 explicit compressed_compressed_matrix(cl_mem mem_row_buffer, cl_mem mem_row_indices, cl_mem mem_col_buffer, cl_mem mem_elements, 00357 vcl_size_t rows, vcl_size_t cols, vcl_size_t nonzero_rows, vcl_size_t nonzeros) : 00358 rows_(rows), cols_(cols), nonzero_rows_(nonzero_rows), nonzeros_(nonzeros) 00359 { 00360 row_buffer_.switch_active_handle_id(viennacl::OPENCL_MEMORY); 00361 row_buffer_.opencl_handle() = mem_row_buffer; 00362 row_buffer_.opencl_handle().inc(); //prevents that the user-provided memory is deleted once the matrix object is destroyed. 00363 row_buffer_.raw_size(sizeof(cl_uint) * (nonzero_rows + 1)); 00364 00365 row_indices_.switch_active_handle_id(viennacl::OPENCL_MEMORY); 00366 row_indices_.opencl_handle() = mem_row_indices; 00367 row_indices_.opencl_handle().inc(); //prevents that the user-provided memory is deleted once the matrix object is destroyed. 00368 row_indices_.raw_size(sizeof(cl_uint) * nonzero_rows); 00369 00370 col_buffer_.switch_active_handle_id(viennacl::OPENCL_MEMORY); 00371 col_buffer_.opencl_handle() = mem_col_buffer; 00372 col_buffer_.opencl_handle().inc(); //prevents that the user-provided memory is deleted once the matrix object is destroyed. 00373 col_buffer_.raw_size(sizeof(cl_uint) * nonzeros); 00374 00375 elements_.switch_active_handle_id(viennacl::OPENCL_MEMORY); 00376 elements_.opencl_handle() = mem_elements; 00377 elements_.opencl_handle().inc(); //prevents that the user-provided memory is deleted once the matrix object is destroyed. 00378 elements_.raw_size(sizeof(SCALARTYPE) * nonzeros); 00379 } 00380 #endif 00381 00382 00384 compressed_compressed_matrix & operator=(compressed_compressed_matrix const & other) 00385 { 00386 assert( (rows_ == 0 || rows_ == other.size1()) && bool("Size mismatch") ); 00387 assert( (cols_ == 0 || cols_ == other.size2()) && bool("Size mismatch") ); 00388 00389 rows_ = other.size1(); 00390 cols_ = other.size2(); 00391 nonzero_rows_ = other.nnz1(); 00392 nonzeros_ = other.nnz(); 00393 00394 viennacl::backend::typesafe_memory_copy<unsigned int>(other.row_buffer_, row_buffer_); 00395 viennacl::backend::typesafe_memory_copy<unsigned int>(other.row_indices_, row_indices_); 00396 viennacl::backend::typesafe_memory_copy<unsigned int>(other.col_buffer_, col_buffer_); 00397 viennacl::backend::typesafe_memory_copy<SCALARTYPE>(other.elements_, elements_); 00398 00399 return *this; 00400 } 00401 00402 00414 void set(const void * row_jumper, 00415 const void * row_indices, 00416 const void * col_buffer, 00417 const SCALARTYPE * elements, 00418 vcl_size_t rows, 00419 vcl_size_t cols, 00420 vcl_size_t nonzero_rows, 00421 vcl_size_t nonzeros) 00422 { 00423 assert( (rows > 0) && bool("Error in compressed_compressed_matrix::set(): Number of rows must be larger than zero!")); 00424 assert( (cols > 0) && bool("Error in compressed_compressed_matrix::set(): Number of columns must be larger than zero!")); 00425 assert( (nonzero_rows > 0) && bool("Error in compressed_compressed_matrix::set(): Number of nonzero rows must be larger than zero!")); 00426 assert( (nonzeros > 0) && bool("Error in compressed_compressed_matrix::set(): Number of nonzeros must be larger than zero!")); 00427 //std::cout << "Setting memory: " << cols + 1 << ", " << nonzeros << std::endl; 00428 00429 viennacl::backend::memory_create(row_buffer_, viennacl::backend::typesafe_host_array<unsigned int>(row_buffer_).element_size() * (rows + 1), viennacl::traits::context(row_buffer_), row_jumper); 00430 viennacl::backend::memory_create(row_indices_, viennacl::backend::typesafe_host_array<unsigned int>(row_indices_).element_size() * (rows + 1), viennacl::traits::context(row_indices_), row_indices); 00431 viennacl::backend::memory_create(col_buffer_, viennacl::backend::typesafe_host_array<unsigned int>(col_buffer_).element_size() * nonzeros, viennacl::traits::context(col_buffer_), col_buffer); 00432 viennacl::backend::memory_create(elements_, sizeof(SCALARTYPE) * nonzeros, viennacl::traits::context(elements_), elements); 00433 00434 nonzeros_ = nonzeros; 00435 nonzero_rows_ = nonzero_rows; 00436 rows_ = rows; 00437 cols_ = cols; 00438 } 00439 00441 const vcl_size_t & size1() const { return rows_; } 00443 const vcl_size_t & size2() const { return cols_; } 00445 const vcl_size_t & nnz1() const { return nonzero_rows_; } 00447 const vcl_size_t & nnz() const { return nonzeros_; } 00448 00450 const handle_type & handle1() const { return row_buffer_; } 00452 const handle_type & handle2() const { return col_buffer_; } 00454 const handle_type & handle3() const { return row_indices_; } 00456 const handle_type & handle() const { return elements_; } 00457 00459 handle_type & handle1() { return row_buffer_; } 00461 handle_type & handle2() { return col_buffer_; } 00463 handle_type & handle3() { return row_indices_; } 00465 handle_type & handle() { return elements_; } 00466 00467 void switch_memory_context(viennacl::context new_ctx) 00468 { 00469 viennacl::backend::switch_memory_context<unsigned int>(row_buffer_, new_ctx); 00470 viennacl::backend::switch_memory_context<unsigned int>(row_indices_, new_ctx); 00471 viennacl::backend::switch_memory_context<unsigned int>(col_buffer_, new_ctx); 00472 viennacl::backend::switch_memory_context<SCALARTYPE>(elements_, new_ctx); 00473 } 00474 00475 viennacl::memory_types memory_context() const 00476 { 00477 return row_buffer_.get_active_handle_id(); 00478 } 00479 00480 private: 00481 00482 vcl_size_t rows_; 00483 vcl_size_t cols_; 00484 vcl_size_t nonzero_rows_; 00485 vcl_size_t nonzeros_; 00486 handle_type row_buffer_; 00487 handle_type row_indices_; 00488 handle_type col_buffer_; 00489 handle_type elements_; 00490 }; 00491 00492 00493 00494 // 00495 // Specify available operations: 00496 // 00497 00500 namespace linalg 00501 { 00502 namespace detail 00503 { 00504 // x = A * y 00505 template <typename T> 00506 struct op_executor<vector_base<T>, op_assign, vector_expression<const compressed_compressed_matrix<T>, const vector_base<T>, op_prod> > 00507 { 00508 static void apply(vector_base<T> & lhs, vector_expression<const compressed_compressed_matrix<T>, const vector_base<T>, op_prod> const & rhs) 00509 { 00510 // check for the special case x = A * x 00511 if (viennacl::traits::handle(lhs) == viennacl::traits::handle(rhs.rhs())) 00512 { 00513 viennacl::vector<T> temp(lhs); 00514 viennacl::linalg::prod_impl(rhs.lhs(), rhs.rhs(), temp); 00515 lhs = temp; 00516 } 00517 else 00518 viennacl::linalg::prod_impl(rhs.lhs(), rhs.rhs(), lhs); 00519 } 00520 }; 00521 00522 template <typename T> 00523 struct op_executor<vector_base<T>, op_inplace_add, vector_expression<const compressed_compressed_matrix<T>, const vector_base<T>, op_prod> > 00524 { 00525 static void apply(vector_base<T> & lhs, vector_expression<const compressed_compressed_matrix<T>, const vector_base<T>, op_prod> const & rhs) 00526 { 00527 viennacl::vector<T> temp(lhs); 00528 viennacl::linalg::prod_impl(rhs.lhs(), rhs.rhs(), temp); 00529 lhs += temp; 00530 } 00531 }; 00532 00533 template <typename T> 00534 struct op_executor<vector_base<T>, op_inplace_sub, vector_expression<const compressed_compressed_matrix<T>, const vector_base<T>, op_prod> > 00535 { 00536 static void apply(vector_base<T> & lhs, vector_expression<const compressed_compressed_matrix<T>, const vector_base<T>, op_prod> const & rhs) 00537 { 00538 viennacl::vector<T> temp(lhs); 00539 viennacl::linalg::prod_impl(rhs.lhs(), rhs.rhs(), temp); 00540 lhs -= temp; 00541 } 00542 }; 00543 00544 00545 // x = A * vec_op 00546 template <typename T, typename LHS, typename RHS, typename OP> 00547 struct op_executor<vector_base<T>, op_assign, vector_expression<const compressed_compressed_matrix<T>, const vector_expression<const LHS, const RHS, OP>, op_prod> > 00548 { 00549 static void apply(vector_base<T> & lhs, vector_expression<const compressed_compressed_matrix<T>, const vector_expression<const LHS, const RHS, OP>, op_prod> const & rhs) 00550 { 00551 viennacl::vector<T> temp(rhs.rhs()); 00552 viennacl::linalg::prod_impl(rhs.lhs(), temp, lhs); 00553 } 00554 }; 00555 00556 // x = A * vec_op 00557 template <typename T, typename LHS, typename RHS, typename OP> 00558 struct op_executor<vector_base<T>, op_inplace_add, vector_expression<const compressed_compressed_matrix<T>, vector_expression<const LHS, const RHS, OP>, op_prod> > 00559 { 00560 static void apply(vector_base<T> & lhs, vector_expression<const compressed_compressed_matrix<T>, vector_expression<const LHS, const RHS, OP>, op_prod> const & rhs) 00561 { 00562 viennacl::vector<T> temp(rhs.rhs(), viennacl::traits::context(rhs)); 00563 viennacl::vector<T> temp_result(lhs); 00564 viennacl::linalg::prod_impl(rhs.lhs(), temp, temp_result); 00565 lhs += temp_result; 00566 } 00567 }; 00568 00569 // x = A * vec_op 00570 template <typename T, typename LHS, typename RHS, typename OP> 00571 struct op_executor<vector_base<T>, op_inplace_sub, vector_expression<const compressed_compressed_matrix<T>, const vector_expression<const LHS, const RHS, OP>, op_prod> > 00572 { 00573 static void apply(vector_base<T> & lhs, vector_expression<const compressed_compressed_matrix<T>, const vector_expression<const LHS, const RHS, OP>, op_prod> const & rhs) 00574 { 00575 viennacl::vector<T> temp(rhs.rhs(), viennacl::traits::context(rhs)); 00576 viennacl::vector<T> temp_result(lhs); 00577 viennacl::linalg::prod_impl(rhs.lhs(), temp, temp_result); 00578 lhs -= temp_result; 00579 } 00580 }; 00581 00582 } // namespace detail 00583 } // namespace linalg 00584 00586 } 00587 00588 #endif