11#ifndef EIGEN_SPARSEDENSEPRODUCT_H
12#define EIGEN_SPARSEDENSEPRODUCT_H
15#include "./InternalHeaderCheck.h"
22struct product_promote_storage_type<Sparse, Dense, OuterProduct> {
26struct product_promote_storage_type<Dense, Sparse, OuterProduct> {
34struct has_compressed_storage : std::is_base_of<SparseCompressedBase<T>, T> {};
36template <
typename SparseLhsType,
typename DenseRhsType,
typename DenseResType,
typename AlphaType,
38 bool ColPerCol = ((DenseRhsType::Flags &
RowMajorBit) == 0) || DenseRhsType::ColsAtCompileTime == 1>
39struct sparse_time_dense_product_impl;
42template <
typename SparseLhsType,
typename DenseRhsType,
typename DenseResType>
43struct sparse_time_dense_product_impl<SparseLhsType, DenseRhsType, DenseResType,
typename DenseResType::Scalar,
45 using Lhs = internal::remove_all_t<SparseLhsType>;
46 using Res = internal::remove_all_t<DenseResType>;
47 using LhsInnerIterator =
typename evaluator<Lhs>::InnerIterator;
48 using LhsEval = evaluator<Lhs>;
49 using ResScalar =
typename Res::Scalar;
51 static void run(
const SparseLhsType& lhs,
const DenseRhsType& rhs, DenseResType& res,
52 const typename Res::Scalar& alpha) {
54 Index n = lhs.outerSize();
56 for (Index c = 0; c < rhs.cols(); ++c) {
57 runCol(lhsEval, lhs, rhs, res, alpha, n, c, bool_constant<has_compressed_storage<Lhs>::value>());
62 static void runCol(
const LhsEval& ,
const SparseLhsType& lhs,
const DenseRhsType& rhs, DenseResType& res,
63 const ResScalar& alpha, Index n, Index c, std::true_type ) {
64 runColImpl(lhs, rhs, res, alpha, n, c, bool_constant<
bool(DenseRhsType::Flags &
DirectAccessBit)>());
67 template <
typename RhsT>
68 static void runColImpl(
const SparseLhsType& lhs,
const RhsT& rhs, DenseResType& res,
const ResScalar& alpha, Index n,
69 Index c, std::true_type) {
71 const auto* vals = mat.valuePtr();
72 const auto* inds = mat.innerIndexPtr();
74 const auto* outer = mat.outerIndexPtr();
75 const auto* innerNnz = mat.innerNonZeroPtr();
77 if (rhs.innerStride() == 1) {
78 const auto* x = rhs.data() + c * rhs.outerStride();
79#ifdef EIGEN_HAS_OPENMP
80 Index threads = Eigen::nbThreads();
81 if (threads > 1 && mat.nonZeros() > 20000) {
82#pragma omp parallel for schedule(dynamic, (n + threads * 4 - 1) / (threads * 4)) num_threads(threads)
83 for (Index i = 0; i < n; ++i) {
84 Index k = outer ? outer[i] : 0;
85 const Index end = innerNnz ? (outer ? outer[i] : 0) + innerNnz[i] : (outer ? outer[i + 1] : mat.nonZeros());
86 ResScalar sum0(0), sum1(0);
87 for (; k < end; ++k) {
88 sum0 += vals[k] * x[inds[k]];
91 sum1 += vals[k] * x[inds[k]];
94 res.coeffRef(i, c) += alpha * (sum0 + sum1);
99 for (Index i = 0; i < n; ++i) {
100 Index k = outer ? outer[i] : 0;
101 const Index end = innerNnz ? (outer ? outer[i] : 0) + innerNnz[i] : (outer ? outer[i + 1] : mat.nonZeros());
103 ResScalar sum0(0), sum1(0);
104 for (; k < end; ++k) {
105 sum0 += vals[k] * x[inds[k]];
108 sum1 += vals[k] * x[inds[k]];
111 res.coeffRef(i, c) += alpha * (sum0 + sum1);
115 runColImpl(lhs, rhs, res, alpha, n, c, std::false_type());
120 template <
typename RhsT>
121 static void runColImpl(
const SparseLhsType& lhs,
const RhsT& rhs, DenseResType& res,
const ResScalar& alpha, Index n,
122 Index c, std::false_type) {
123 const Lhs& mat = lhs;
124 const auto* vals = mat.valuePtr();
125 const auto* inds = mat.innerIndexPtr();
126 const auto* outer = mat.outerIndexPtr();
127 const auto* innerNnz = mat.innerNonZeroPtr();
129 for (Index i = 0; i < n; ++i) {
130 Index k = outer ? outer[i] : 0;
131 const Index end = innerNnz ? (outer ? outer[i] : 0) + innerNnz[i] : (outer ? outer[i + 1] : mat.nonZeros());
132 ResScalar sum0(0), sum1(0);
133 for (; k < end; ++k) {
134 sum0 += vals[k] * rhs.coeff(inds[k], c);
137 sum1 += vals[k] * rhs.coeff(inds[k], c);
140 res.coeffRef(i, c) += alpha * (sum0 + sum1);
145 static void runCol(
const LhsEval& lhsEval,
const SparseLhsType& ,
const DenseRhsType& rhs, DenseResType& res,
146 const ResScalar& alpha, Index n, Index c, std::false_type ) {
147#ifdef EIGEN_HAS_OPENMP
148 Index threads = Eigen::nbThreads();
149 if (threads > 1 && lhsEval.nonZerosEstimate() > 20000) {
150#pragma omp parallel for schedule(dynamic, (n + threads * 4 - 1) / (threads * 4)) num_threads(threads)
151 for (Index i = 0; i < n; ++i) processRow(lhsEval, rhs, res, alpha, i, c);
155 for (Index i = 0; i < n; ++i) processRow(lhsEval, rhs, res, alpha, i, c);
159 static void processRow(
const LhsEval& lhsEval,
const DenseRhsType& rhs, DenseResType& res,
const ResScalar& alpha,
160 Index i, Index col) {
163 for (LhsInnerIterator it(lhsEval, i); it; ++it) {
164 tmp_a += it.value() * rhs.coeff(it.index(), col);
167 tmp_b += it.value() * rhs.coeff(it.index(), col);
170 res.coeffRef(i, col) += alpha * (tmp_a + tmp_b);
175template <
typename SparseLhsType,
typename DenseRhsType,
typename DenseResType,
typename AlphaType>
176struct sparse_time_dense_product_impl<SparseLhsType, DenseRhsType, DenseResType, AlphaType,
ColMajor, true> {
177 using Lhs = internal::remove_all_t<SparseLhsType>;
178 using Rhs = internal::remove_all_t<DenseRhsType>;
179 using Res = internal::remove_all_t<DenseResType>;
180 using LhsEval = evaluator<Lhs>;
181 using LhsInnerIterator =
typename LhsEval::InnerIterator;
183 static void run(
const SparseLhsType& lhs,
const DenseRhsType& rhs, DenseResType& res,
const AlphaType& alpha) {
184 runImpl(lhs, rhs, res, alpha, bool_constant<has_compressed_storage<Lhs>::value>());
188 static void runImpl(
const SparseLhsType& lhs,
const DenseRhsType& rhs, DenseResType& res,
const AlphaType& alpha,
190 using LhsScalar =
typename Lhs::Scalar;
191 using StorageIndex =
typename Lhs::StorageIndex;
192 const Lhs& mat = lhs;
193 const LhsScalar* vals = mat.valuePtr();
194 const StorageIndex* inds = mat.innerIndexPtr();
196 const auto* outer = mat.outerIndexPtr();
197 const auto* innerNnz = mat.innerNonZeroPtr();
201 if (res.innerStride() == 1) {
202 const Index n = lhs.outerSize();
206#if defined(EIGEN_HAS_OPENMP) && !defined(EIGEN_AVOID_THREAD_LOCAL)
207 using ResScalar =
typename Res::Scalar;
208 const Index m = res.rows();
209 const Index threads = Eigen::nbThreads();
220 if (outer && threads > 1 && mat.nonZeros() > 20000 && mat.nonZeros() >= Index(threads) * m) {
228 thread_local static std::vector<ResScalar> scratch_buf;
229 const std::size_t need =
static_cast<std::size_t
>(threads) *
static_cast<std::size_t
>(m);
230 if (scratch_buf.size() < need) scratch_buf.assign(need, ResScalar(0));
231 ResScalar* scratch_ptr = scratch_buf.data();
238 std::vector<Index> part(
static_cast<std::size_t
>(threads) + 1);
244 const StorageIndex*
const part_last = outer + n + 1;
245 const StorageIndex* part_lo = outer;
246 for (Index t = 1; t < threads; ++t) {
247 const Index target = (t * mat.nonZeros()) / threads;
248 part_lo = std::lower_bound(part_lo, part_last, StorageIndex(target));
249 part[t] = part_lo - outer;
251 for (Index c = 0; c < rhs.cols(); ++c) {
252 typename Res::Scalar* y = res.data() + c * res.outerStride();
253#pragma omp parallel for schedule(static, 1) num_threads(threads)
254 for (Index t = 0; t < threads; ++t) {
255 ResScalar* yt = scratch_ptr + t * m;
256 const Index j_lo = part[t], j_hi = part[t + 1];
257 for (Index j = j_lo; j < j_hi; ++j) {
258 typename ScalarBinaryOpTraits<AlphaType, typename Rhs::Scalar>::ReturnType rhs_j(alpha *
260 const Index start = outer ? outer[j] : 0;
261 const Index end = innerNnz ? start + innerNnz[j] : (outer ? outer[j + 1] : mat.nonZeros());
263 for (; k + 3 < end; k += 4) {
264 yt[inds[k]] += vals[k] * rhs_j;
265 yt[inds[k + 1]] += vals[k + 1] * rhs_j;
266 yt[inds[k + 2]] += vals[k + 2] * rhs_j;
267 yt[inds[k + 3]] += vals[k + 3] * rhs_j;
269 for (; k < end; ++k) yt[inds[k]] += vals[k] * rhs_j;
282 constexpr Index kReduceBlock = 512;
283#pragma omp parallel for schedule(static) num_threads(threads)
284 for (Index i0 = 0; i0 < m; i0 += kReduceBlock) {
285 const Index i1 = numext::mini(i0 + kReduceBlock, m);
286 const Index len = i1 - i0;
287 EIGEN_ALIGN_MAX ResScalar acc[kReduceBlock];
288 for (Index ii = 0; ii < len; ++ii) acc[ii] = ResScalar(0);
289 for (Index t = 0; t < threads; ++t) {
290 ResScalar* row = scratch_ptr + t * m + i0;
291 for (Index ii = 0; ii < len; ++ii) {
293 row[ii] = ResScalar(0);
296 for (Index ii = 0; ii < len; ++ii) y[i0 + ii] += acc[ii];
302 for (Index c = 0; c < rhs.cols(); ++c) {
303 typename Res::Scalar* y = res.data() + c * res.outerStride();
304 for (Index j = 0; j < n; ++j) {
305 typename ScalarBinaryOpTraits<AlphaType, typename Rhs::Scalar>::ReturnType rhs_j(alpha * rhs.coeff(j, c));
306 const Index start = outer ? outer[j] : 0;
307 const Index end = innerNnz ? start + innerNnz[j] : (outer ? outer[j + 1] : mat.nonZeros());
310 for (; k + 3 < end; k += 4) {
311 y[inds[k]] += vals[k] * rhs_j;
312 y[inds[k + 1]] += vals[k + 1] * rhs_j;
313 y[inds[k + 2]] += vals[k + 2] * rhs_j;
314 y[inds[k + 3]] += vals[k + 3] * rhs_j;
316 for (; k < end; ++k) y[inds[k]] += vals[k] * rhs_j;
324 for (Index c = 0; c < rhs.cols(); ++c) {
325 for (Index j = 0; j < lhs.outerSize(); ++j) {
326 typename ScalarBinaryOpTraits<AlphaType, typename Rhs::Scalar>::ReturnType rhs_j(alpha * rhs.coeff(j, c));
327 const Index start = outer ? outer[j] : 0;
328 const Index end = innerNnz ? start + innerNnz[j] : (outer ? outer[j + 1] : mat.nonZeros());
329 for (Index k = start; k < end; ++k) res.coeffRef(inds[k], c) += vals[k] * rhs_j;
335 static void runImpl(
const SparseLhsType& lhs,
const DenseRhsType& rhs, DenseResType& res,
const AlphaType& alpha,
337 LhsEval lhsEval(lhs);
338 for (Index c = 0; c < rhs.cols(); ++c) {
339 for (Index j = 0; j < lhs.outerSize(); ++j) {
340 typename ScalarBinaryOpTraits<AlphaType, typename Rhs::Scalar>::ReturnType rhs_j(alpha * rhs.coeff(j, c));
341 for (LhsInnerIterator it(lhsEval, j); it; ++it) res.coeffRef(it.index(), c) += it.value() * rhs_j;
348template <
typename SparseLhsType,
typename DenseRhsType,
typename DenseResType>
349struct sparse_time_dense_product_impl<SparseLhsType, DenseRhsType, DenseResType, typename DenseResType::Scalar,
351 using Lhs = internal::remove_all_t<SparseLhsType>;
352 using Res = internal::remove_all_t<DenseResType>;
353 using LhsEval = evaluator<Lhs>;
354 using LhsInnerIterator =
typename LhsEval::InnerIterator;
356 static constexpr bool IsCompressedLhs = has_compressed_storage<Lhs>::value;
358 static void run(
const SparseLhsType& lhs,
const DenseRhsType& rhs, DenseResType& res,
359 const typename Res::Scalar& alpha) {
360 Index n = lhs.rows();
361 LhsEval lhsEval(lhs);
363#ifdef EIGEN_HAS_OPENMP
364 Index threads = Eigen::nbThreads();
367 if (threads > 1 && lhsEval.nonZerosEstimate() * rhs.cols() > 20000) {
368#pragma omp parallel for schedule(dynamic, (n + threads * 4 - 1) / (threads * 4)) num_threads(threads)
369 for (Index i = 0; i < n; ++i) processRow(lhsEval, lhs, rhs, res, alpha, i, bool_constant<IsCompressedLhs>());
373 for (Index i = 0; i < n; ++i) processRow(lhsEval, lhs, rhs, res, alpha, i, bool_constant<IsCompressedLhs>());
378 static void processRow(
const LhsEval& ,
const SparseLhsType& lhs,
const DenseRhsType& rhs, Res& res,
379 const typename Res::Scalar& alpha, Index i, std::true_type ) {
380 using LhsScalar =
typename Lhs::Scalar;
381 using StorageIndex =
typename Lhs::StorageIndex;
382 const Lhs& mat = lhs;
383 const LhsScalar* vals = mat.valuePtr();
384 const StorageIndex* inds = mat.innerIndexPtr();
386 const Index start = mat.outerIndexPtr() ? mat.outerIndexPtr()[i] : 0;
387 const auto* innerNnz = mat.innerNonZeroPtr();
389 innerNnz ? start + innerNnz[i] : (mat.outerIndexPtr() ? mat.outerIndexPtr()[i + 1] : mat.nonZeros());
390 typename Res::RowXpr res_i(res.row(i));
391 for (Index k = start; k < end; ++k) res_i += (alpha * vals[k]) * rhs.row(inds[k]);
394 static void processRow(
const LhsEval& lhsEval,
const SparseLhsType& ,
const DenseRhsType& rhs, Res& res,
395 const typename Res::Scalar& alpha, Index i, std::false_type ) {
396 typename Res::RowXpr res_i(res.row(i));
397 for (LhsInnerIterator it(lhsEval, i); it; ++it) res_i += (alpha * it.value()) * rhs.row(it.index());
402template <
typename SparseLhsType,
typename DenseRhsType,
typename DenseResType>
403struct sparse_time_dense_product_impl<SparseLhsType, DenseRhsType, DenseResType, typename DenseResType::Scalar,
405 using Lhs = internal::remove_all_t<SparseLhsType>;
406 using Rhs = internal::remove_all_t<DenseRhsType>;
407 using Res = internal::remove_all_t<DenseResType>;
408 using LhsInnerIterator =
typename evaluator<Lhs>::InnerIterator;
410 static void run(
const SparseLhsType& lhs,
const DenseRhsType& rhs, DenseResType& res,
411 const typename Res::Scalar& alpha) {
412 runImpl(lhs, rhs, res, alpha, bool_constant<has_compressed_storage<Lhs>::value>());
416 static void runImpl(
const SparseLhsType& lhs,
const DenseRhsType& rhs, DenseResType& res,
417 const typename Res::Scalar& alpha, std::true_type ) {
418 using LhsScalar =
typename Lhs::Scalar;
419 using StorageIndex =
typename Lhs::StorageIndex;
420 const Lhs& mat = lhs;
421 const LhsScalar* vals = mat.valuePtr();
422 const StorageIndex* inds = mat.innerIndexPtr();
424 const auto* outer = mat.outerIndexPtr();
425 const auto* innerNnz = mat.innerNonZeroPtr();
426 for (Index j = 0; j < lhs.outerSize(); ++j) {
427 typename Rhs::ConstRowXpr rhs_j(rhs.row(j));
428 const Index start = outer ? outer[j] : 0;
429 const Index end = innerNnz ? start + innerNnz[j] : (outer ? outer[j + 1] : mat.nonZeros());
430 for (Index k = start; k < end; ++k) res.row(inds[k]) += (alpha * vals[k]) * rhs_j;
434 static void runImpl(
const SparseLhsType& lhs,
const DenseRhsType& rhs, DenseResType& res,
435 const typename Res::Scalar& alpha, std::false_type ) {
436 evaluator<Lhs> lhsEval(lhs);
437 for (Index j = 0; j < lhs.outerSize(); ++j) {
438 typename Rhs::ConstRowXpr rhs_j(rhs.row(j));
439 for (LhsInnerIterator it(lhsEval, j); it; ++it) res.row(it.index()) += (alpha * it.value()) * rhs_j;
444template <
typename SparseLhsType,
typename DenseRhsType,
typename DenseResType,
typename AlphaType>
445inline void sparse_time_dense_product(
const SparseLhsType& lhs,
const DenseRhsType& rhs, DenseResType& res,
446 const AlphaType& alpha) {
447 sparse_time_dense_product_impl<SparseLhsType, DenseRhsType, DenseResType, AlphaType>::run(lhs, rhs, res, alpha);
454template <
typename Lhs,
typename Rhs,
int ProductType>
455struct generic_product_impl<Lhs, Rhs, SparseShape, DenseShape, ProductType>
456 : generic_product_impl_base<Lhs, Rhs, generic_product_impl<Lhs, Rhs, SparseShape, DenseShape, ProductType> > {
457 using Scalar =
typename Product<Lhs, Rhs>::Scalar;
459 template <
typename Dest>
460 static void scaleAndAddTo(Dest& dst,
const Lhs& lhs,
const Rhs& rhs,
const Scalar& alpha) {
461 using LhsNested =
typename nested_eval<Lhs, ((Rhs::Flags &
RowMajorBit) == 0) ? 1 : Rhs::ColsAtCompileTime>::type;
462 using RhsNested =
typename nested_eval<Rhs, ((Lhs::Flags &
RowMajorBit) == 0) ? 1 : Dynamic>::type;
463 LhsNested lhsNested(lhs);
464 RhsNested rhsNested(rhs);
465 internal::sparse_time_dense_product(lhsNested, rhsNested, dst, alpha);
469template <
typename Lhs,
typename Rhs,
int ProductType>
470struct generic_product_impl<Lhs, Rhs, SparseTriangularShape, DenseShape, ProductType>
471 : generic_product_impl<Lhs, Rhs, SparseShape, DenseShape, ProductType> {};
473template <
typename Lhs,
typename Rhs,
int ProductType>
474struct generic_product_impl<Lhs, Rhs, DenseShape, SparseShape, ProductType>
475 : generic_product_impl_base<Lhs, Rhs, generic_product_impl<Lhs, Rhs, DenseShape, SparseShape, ProductType> > {
476 using Scalar =
typename Product<Lhs, Rhs>::Scalar;
478 template <
typename Dst>
479 static void scaleAndAddTo(Dst& dst,
const Lhs& lhs,
const Rhs& rhs,
const Scalar& alpha) {
480 using LhsNested =
typename nested_eval<Lhs, ((Rhs::Flags &
RowMajorBit) == 0) ? Dynamic : 1>::type;
482 typename nested_eval<Rhs, ((Lhs::Flags &
RowMajorBit) ==
RowMajorBit) ? 1 : Lhs::RowsAtCompileTime>::type;
483 LhsNested lhsNested(lhs);
484 RhsNested rhsNested(rhs);
487 Transpose<Dst> dstT(dst);
488 internal::sparse_time_dense_product(rhsNested.transpose(), lhsNested.transpose(), dstT, alpha);
492template <
typename Lhs,
typename Rhs,
int ProductType>
493struct generic_product_impl<Lhs, Rhs, DenseShape, SparseTriangularShape, ProductType>
494 : generic_product_impl<Lhs, Rhs, DenseShape, SparseShape, ProductType> {};
496template <
typename LhsT,
typename RhsT,
bool NeedToTranspose>
497struct sparse_dense_outer_product_evaluator {
499 using Lhs1 = std::conditional_t<NeedToTranspose, RhsT, LhsT>;
500 using ActualRhs = std::conditional_t<NeedToTranspose, LhsT, RhsT>;
501 using ProdXprType = Product<LhsT, RhsT, DefaultProduct>;
505 using ActualLhs = std::conditional_t<std::is_same<typename internal::traits<Lhs1>::StorageKind, Sparse>::value, Lhs1,
507 using LhsArg = std::conditional_t<std::is_same<typename internal::traits<Lhs1>::StorageKind, Sparse>::value,
508 const Lhs1&, SparseView<Lhs1>>;
510 using LhsEval = evaluator<ActualLhs>;
511 using RhsEval = evaluator<ActualRhs>;
512 using LhsIterator =
typename evaluator<ActualLhs>::InnerIterator;
513 using Scalar =
typename ProdXprType::Scalar;
516 enum { Flags = NeedToTranspose ?
RowMajorBit : 0, CoeffReadCost = HugeCost };
518 class InnerIterator :
public LhsIterator {
520 InnerIterator(
const sparse_dense_outer_product_evaluator& xprEval, Index outer)
521 : LhsIterator(xprEval.m_lhsXprImpl, 0),
524 m_factor(get(xprEval.m_rhsXprImpl, outer, typename internal::traits<ActualRhs>::StorageKind())) {}
526 EIGEN_STRONG_INLINE Index outer()
const {
return m_outer; }
527 EIGEN_STRONG_INLINE Index row()
const {
return NeedToTranspose ? m_outer : LhsIterator::index(); }
528 EIGEN_STRONG_INLINE Index col()
const {
return NeedToTranspose ? LhsIterator::index() : m_outer; }
530 EIGEN_STRONG_INLINE Scalar value()
const {
return LhsIterator::value() * m_factor; }
531 EIGEN_STRONG_INLINE
operator bool()
const {
return LhsIterator::operator bool() && (!m_empty); }
534 Scalar get(
const RhsEval& rhs, Index outer, Dense = Dense())
const {
return rhs.coeff(outer); }
536 Scalar get(
const RhsEval& rhs, Index outer, Sparse = Sparse()) {
537 typename RhsEval::InnerIterator it(rhs, outer);
538 if (it && it.index() == 0 && it.value() != Scalar(0))
return it.value();
548 sparse_dense_outer_product_evaluator(
const Lhs1& lhs,
const ActualRhs& rhs)
549 : m_lhs(lhs), m_lhsXprImpl(m_lhs), m_rhsXprImpl(rhs) {
550 EIGEN_INTERNAL_CHECK_COST_VALUE(CoeffReadCost);
554 sparse_dense_outer_product_evaluator(
const ActualRhs& rhs,
const Lhs1& lhs)
555 : m_lhs(lhs), m_lhsXprImpl(m_lhs), m_rhsXprImpl(rhs) {
556 EIGEN_INTERNAL_CHECK_COST_VALUE(CoeffReadCost);
561 evaluator<ActualLhs> m_lhsXprImpl;
562 evaluator<ActualRhs> m_rhsXprImpl;
566template <
typename Lhs,
typename Rhs>
567struct product_evaluator<Product<Lhs, Rhs, DefaultProduct>, OuterProduct, SparseShape, DenseShape>
568 : sparse_dense_outer_product_evaluator<Lhs, Rhs, Lhs::IsRowMajor> {
569 using Base = sparse_dense_outer_product_evaluator<Lhs, Rhs, Lhs::IsRowMajor>;
571 using XprType = Product<Lhs, Rhs>;
572 using PlainObject =
typename XprType::PlainObject;
574 explicit product_evaluator(
const XprType& xpr) : Base(xpr.lhs(), xpr.rhs()) {}
577template <
typename Lhs,
typename Rhs>
578struct product_evaluator<Product<Lhs, Rhs, DefaultProduct>, OuterProduct, DenseShape, SparseShape>
579 : sparse_dense_outer_product_evaluator<Lhs, Rhs, Rhs::IsRowMajor> {
580 using Base = sparse_dense_outer_product_evaluator<Lhs, Rhs, Rhs::IsRowMajor>;
582 using XprType = Product<Lhs, Rhs>;
583 using PlainObject =
typename XprType::PlainObject;
585 explicit product_evaluator(
const XprType& xpr) : Base(xpr.lhs(), xpr.rhs()) {}
@ ColMajor
Definition Constants.h:319
@ RowMajor
Definition Constants.h:321
constexpr unsigned int DirectAccessBit
Definition Constants.h:160
constexpr unsigned int RowMajorBit
Definition Constants.h:71