SparseVector Basis::eval(const DenseVector &x) const { // Set correct starting size and values: one element of 1. SparseMatrix tv1(1,1); //tv1.resize(1); tv1.reserve(1); tv1.insert(0,0) = 1; SparseMatrix tv2 = tv1; // Evaluate all basisfunctions in each dimension i and generate the tensor product of the function values for (int dim = 0; dim < x.size(); dim++) { SparseVector xi = bases.at(dim).evaluate(x(dim)); // Avoid matrix copy if (dim % 2 == 0) { tv1 = kroneckerProduct(tv2, xi); // tv1 = tv1 x xi } else { tv2 = kroneckerProduct(tv1, xi); // tv2 = tv1 x xi } } // Return correct vector if (tv1.size() == numBasisFunctions()) { return tv1; } return tv2; }
SparseVector kroneckerProductVectors(const std::vector<SparseVector> &vectors) { // Create two temp matrices SparseMatrix temp1(1,1); temp1.insert(0,0) = 1; SparseMatrix temp2 = temp1; // Multiply from left int counter = 0; for (const auto &vec : vectors) { // Avoid copy if (counter % 2 == 0) temp1 = kroneckerProduct(temp2, vec); else temp2 = kroneckerProduct(temp1, vec); ++counter; } // Return correct product if (counter % 2 == 0) return temp2; return temp1; }
// Old implementation of Jacobian DenseMatrix BSplineBasis::evalBasisJacobianOld(DenseVector &x) const { // Jacobian basis matrix DenseMatrix J; J.setZero(getNumBasisFunctions(), numVariables); // Calculate partial derivatives for (unsigned int i = 0; i < numVariables; i++) { // One column in basis jacobian DenseVector bi; bi.setOnes(1); for (unsigned int j = 0; j < numVariables; j++) { DenseVector temp = bi; DenseVector xi; if (j == i) { // Differentiated basis xi = bases.at(j).evaluateFirstDerivative(x(j)); } else { // Normal basis xi = bases.at(j).evaluate(x(j)); } bi = kroneckerProduct(temp, xi); } // Fill out column J.block(0,i,bi.rows(),1) = bi.block(0,0,bi.rows(),1); } return J; }
/* * Calculate coefficients of B-spline representing a multivariate polynomial * * The polynomial f(x), with x in R^n, has m terms on the form * f(x) = c(0)*x(0)^E(0,0)*x(1)^E(0,1)*...*x(n-1)^E(0,n-1) * +c(1)*x(0)^E(1,0)*x(1)^E(1,1)*...*x(n-1)^E(1,n-1) * +... * +c(m-1)*x(0)^E(m-1,0)*x(1)^E(m-1,1)*...*x(n-1)^E(m-1,n-1) * where c in R^m is a vector with coefficients for each of the m terms, * and E in N^(mxn) is a matrix with the exponents of each variable in each of the m terms, * e.g. the first row of E defines the first term with variable exponents E(0,0) to E(0,n-1). * * Note: E must be a matrix of nonnegative integers */ DenseMatrix getBSplineBasisCoefficients(DenseVector c, DenseMatrix E, std::vector<double> lb, std::vector<double> ub) { unsigned int dim = E.cols(); unsigned int terms = E.rows(); assert(dim >= 1); // At least one variable assert(terms >= 1); // At least one term (assumes that c is a column vector) assert(terms == c.rows()); assert(dim == lb.size()); assert(dim == ub.size()); // Get highest power of each variable DenseVector powers = E.colwise().maxCoeff(); // Store in std vector std::vector<unsigned int> powers2; for (unsigned int i = 0; i < powers.size(); ++i) powers2.push_back(powers(i)); // Calculate tensor product transformation matrix T DenseMatrix T = getTransformationMatrix(powers2, lb, ub); // Compute power basis coefficients (lambda vector) SparseMatrix L(T.cols(),1); L.setZero(); for (unsigned int i = 0; i < terms; i++) { SparseMatrix Li(1,1); Li.insert(0,0) = 1; for (unsigned int j = 0; j < dim; j++) { int e = E(i,j); SparseVector li(powers(j)+1); li.reserve(1); li.insert(e) = 1; SparseMatrix temp = Li; Li = kroneckerProduct(temp, li); } L += c(i)*Li; } // Compute B-spline coefficients DenseMatrix C = T*L; return C; }
SparseMatrix Basis::evalBasisJacobian(DenseVector &x) const { // Jacobian basis matrix SparseMatrix J(numBasisFunctions(), numVariables); //J.setZero(numBasisFunctions(), numInputs); // Calculate partial derivatives for (unsigned int i = 0; i < numVariables; i++) { // One column in basis jacobian SparseMatrix Ji(1,1); Ji.insert(0,0) = 1; for (unsigned int j = 0; j < numVariables; j++) { SparseMatrix temp = Ji; SparseMatrix xi; if (j == i) { // Differentiated basis xi = bases.at(j).evaluateDerivative(x(j),1); } else { // Normal basis xi = bases.at(j).evaluate(x(j)); } Ji = kroneckerProduct(temp, xi); //myKroneckerProduct(temp,xi,Ji); //Ji = kroneckerProduct(Ji, xi).eval(); } // Fill out column for (int k = 0; k < Ji.outerSize(); ++k) for (SparseMatrix::InnerIterator it(Ji,k); it; ++it) { if (it.value() != 0) J.insert(it.row(),i) = it.value(); } //J.block(0,i,Ji.rows(),1) = bi.block(0,0,Ji.rows(),1); } J.makeCompressed(); return J; }
/* * Calculates the reparameterization matrix that changes the domain from [0,1] to [a,b] */ DenseMatrix getReparameterizationMatrixND(std::vector<unsigned int> degrees, std::vector<double> lb, std::vector<double> ub) { unsigned int dim = degrees.size(); assert(dim >= 1); // At least one variable assert(dim == lb.size()); assert(dim == ub.size()); // Calculate tensor product transformation matrix T SparseMatrix R(1,1); R.insert(0,0) = 1; for (unsigned int i = 0; i < dim; i++) { SparseMatrix temp(R); unsigned int deg = degrees.at(i); DenseMatrix Ri = getReparameterizationMatrix1D(deg, lb.at(i), ub.at(i)); R = kroneckerProduct(temp, Ri); } return R; }
/* * Transformation matrix taking a power basis to a B-spline basis */ DenseMatrix getTransformationMatrix(std::vector<unsigned int> degrees, std::vector<double> lb, std::vector<double> ub) { unsigned int dim = degrees.size(); assert(dim >= 1); // At least one variable assert(dim == lb.size()); assert(dim == ub.size()); // Calculate tensor product transformation matrix T SparseMatrix T(1,1); T.insert(0,0) = 1; for (unsigned int i = 0; i < dim; i++) { SparseMatrix temp(T); unsigned int deg = degrees.at(i); DenseMatrix Mi = getBSplineToPowerBasisMatrix1D(deg); DenseMatrix Ri = getReparameterizationMatrix1D(deg, lb.at(i), ub.at(i)); DenseMatrix Ti = Mi.colPivHouseholderQr().solve(Ri); T = kroneckerProduct(temp, Ti); } return T; }
/* * Returns the Hessian evaluated at x. * The Hessian is an n x n matrix, * where n is the dimension of x. */ DenseMatrix BSpline::evalHessian(DenseVector x) const { checkInput(x); #ifndef NDEBUG if (!pointInDomain(x)) throw Exception("BSpline::evalHessian: Evaluation at point outside domain."); #endif // NDEBUG DenseMatrix H; H.setZero(1,1); DenseMatrix identity = DenseMatrix::Identity(numVariables, numVariables); DenseMatrix caug = kroneckerProduct(identity, coefficients.transpose()); DenseMatrix DB = basis.evalBasisHessian(x); H = caug*DB; // Fill in upper triangular of Hessian for (size_t i = 0; i < numVariables; ++i) for (size_t j = i+1; j < numVariables; ++j) H(i,j) = H(j,i); return H; }
void test_kronecker_product() { // DM = dense matrix; SM = sparse matrix Matrix<double, 2, 3> DM_a; MatrixXd DM_b(3,2); SparseMatrix<double> SM_a(2,3); SparseMatrix<double> SM_b(3,2); SM_a.insert(0,0) = DM_a(0,0) = -0.4461540300782201; SM_a.insert(0,1) = DM_a(0,1) = -0.8057364375283049; SM_a.insert(0,2) = DM_a(0,2) = 0.3896572459516341; SM_a.insert(1,0) = DM_a(1,0) = -0.9076572187376921; SM_a.insert(1,1) = DM_a(1,1) = 0.6469156566545853; SM_a.insert(1,2) = DM_a(1,2) = -0.3658010398782789; SM_b.insert(0,0) = DM_b(0,0) = 0.9004440976767099; SM_b.insert(0,1) = DM_b(0,1) = -0.2368830858139832; SM_b.insert(1,0) = DM_b(1,0) = -0.9311078389941825; SM_b.insert(1,1) = DM_b(1,1) = 0.5310335762980047; SM_b.insert(2,0) = DM_b(2,0) = -0.1225112806872035; SM_b.insert(2,1) = DM_b(2,1) = 0.5903998022741264; SparseMatrix<double,RowMajor> SM_row_a(SM_a), SM_row_b(SM_b); // test kroneckerProduct(DM_block,DM,DM_fixedSize) Matrix<double, 6, 6> DM_fix_ab; DM_fix_ab(0,0)=37.0; kroneckerProduct(DM_a.block(0,0,2,3),DM_b,DM_fix_ab); CALL_SUBTEST(check_kronecker_product(DM_fix_ab)); // test kroneckerProduct(DM,DM,DM_block) MatrixXd DM_block_ab(10,15); DM_block_ab(0,0)=37.0; kroneckerProduct(DM_a,DM_b,DM_block_ab.block(2,5,6,6)); CALL_SUBTEST(check_kronecker_product(DM_block_ab.block(2,5,6,6))); // test kroneckerProduct(DM,DM,DM) MatrixXd DM_ab(1,5); DM_ab(0,0)=37.0; kroneckerProduct(DM_a,DM_b,DM_ab); CALL_SUBTEST(check_kronecker_product(DM_ab)); // test kroneckerProduct(SM,DM,SM) SparseMatrix<double> SM_ab(1,20); SM_ab.insert(0,0)=37.0; kroneckerProduct(SM_a,DM_b,SM_ab); CALL_SUBTEST(check_kronecker_product(SM_ab)); SparseMatrix<double,RowMajor> SM_ab2(10,3); SM_ab2.insert(0,0)=37.0; kroneckerProduct(SM_a,DM_b,SM_ab2); CALL_SUBTEST(check_kronecker_product(SM_ab2)); // test kroneckerProduct(DM,SM,SM) SM_ab.insert(0,0)=37.0; kroneckerProduct(DM_a,SM_b,SM_ab); CALL_SUBTEST(check_kronecker_product(SM_ab)); SM_ab2.insert(0,0)=37.0; kroneckerProduct(DM_a,SM_b,SM_ab2); CALL_SUBTEST(check_kronecker_product(SM_ab2)); // test kroneckerProduct(SM,SM,SM) SM_ab.resize(2,33); SM_ab.insert(0,0)=37.0; kroneckerProduct(SM_a,SM_b,SM_ab); CALL_SUBTEST(check_kronecker_product(SM_ab)); SM_ab2.resize(5,11); SM_ab2.insert(0,0)=37.0; kroneckerProduct(SM_a,SM_b,SM_ab2); CALL_SUBTEST(check_kronecker_product(SM_ab2)); // test kroneckerProduct(SM,SM,SM) with sparse pattern SM_a.resize(4,5); SM_b.resize(3,2); SM_a.resizeNonZeros(0); SM_b.resizeNonZeros(0); SM_a.insert(1,0) = -0.1; SM_a.insert(0,3) = -0.2; SM_a.insert(2,4) = 0.3; SM_a.finalize(); SM_b.insert(0,0) = 0.4; SM_b.insert(2,1) = -0.5; SM_b.finalize(); SM_ab.resize(1,1); SM_ab.insert(0,0)=37.0; kroneckerProduct(SM_a,SM_b,SM_ab); CALL_SUBTEST(check_sparse_kronecker_product(SM_ab)); // test dimension of result of kroneckerProduct(DM,DM,DM) MatrixXd DM_a2(2,1); MatrixXd DM_b2(5,4); MatrixXd DM_ab2; kroneckerProduct(DM_a2,DM_b2,DM_ab2); CALL_SUBTEST(check_dimension(DM_ab2,2*5,1*4)); DM_a2.resize(10,9); DM_b2.resize(4,8); kroneckerProduct(DM_a2,DM_b2,DM_ab2); CALL_SUBTEST(check_dimension(DM_ab2,10*4,9*8)); }
SparseMatrix BSplineBasis::evalBasisHessian(DenseVector &x) const { // Hessian basis matrix /* Hij = B1 x ... x DBi x ... x DBj x ... x Bn * (Hii = B1 x ... x DDBi x ... x Bn) * Where B are basis functions evaluated at x, * DB are the derivative of the basis functions, * and x is the kronecker product. * Hij is in R^(numBasisFunctions x 1) * so that basis hessian H is in R^(numBasisFunctions*numInputs x numInputs) * The real B-spline Hessian is calculated as (c^T x 1^(numInputs x 1))*H */ SparseMatrix H(getNumBasisFunctions()*numVariables, numVariables); //H.setZero(numBasisFunctions()*numInputs, numInputs); // Calculate partial derivatives // Utilizing that Hessian is symmetric // Filling out lower left triangular for (unsigned int i = 0; i < numVariables; i++) // row { for (unsigned int j = 0; j <= i; j++) // col { // One column in basis jacobian SparseMatrix Hi(1,1); Hi.insert(0,0) = 1; for (unsigned int k = 0; k < numVariables; k++) { SparseMatrix temp = Hi; SparseMatrix Bk; if (i == j && k == i) { // Diagonal element Bk = bases.at(k).evaluateDerivative(x(k),2); } else if (k == i || k == j) { Bk = bases.at(k).evaluateDerivative(x(k),1); } else { Bk = bases.at(k).evaluate(x(k)); } Hi = kroneckerProduct(temp, Bk); } // Fill out column for (int k = 0; k < Hi.outerSize(); ++k) for (SparseMatrix::InnerIterator it(Hi,k); it; ++it) { if (it.value() != 0) { int row = i*getNumBasisFunctions()+it.row(); int col = j; H.insert(row,col) = it.value(); } } } } H.makeCompressed(); return H; }
bool ConstraintBSpline::controlPointBoundsDeduction() const { // Get variable bounds auto xlb = bspline.getDomainLowerBound(); auto xub = bspline.getDomainUpperBound(); // Use these instead? // for (unsigned int i = 0; i < bspline.getNumVariables(); i++) // { // xlb.at(i) = variables.at(i)->getLowerBound(); // xub.at(i) = variables.at(i)->getUpperBound(); // } double lowerBound = variables.back()->getLowerBound(); // f(x) = y > lowerBound double upperBound = variables.back()->getUpperBound(); // f(x) = y < upperBound // Get knot vectors and basis degrees auto knotVectors = bspline.getKnotVectors(); auto basisDegrees = bspline.getBasisDegrees(); // Compute n value for each variable // Total number of control points is ns(0)*...*ns(d-1) std::vector<unsigned int> numBasisFunctions = bspline.getNumBasisFunctions(); // Get matrix of coefficients DenseMatrix cps = controlPoints; DenseMatrix coeffs = cps.block(bspline.getNumVariables(), 0, 1, cps.cols()); for (unsigned int d = 0; d < bspline.getNumVariables(); d++) { if (assertNear(xlb.at(d), xub.at(d))) continue; auto n = numBasisFunctions.at(d); auto p = basisDegrees.at(d); std::vector<double> knots = knotVectors.at(d); assert(knots.size() == n+p+1); // Tighten lower bound unsigned int i = 1; for (; i <= n; i++) { // Knot interval of interest: [t_0, t_i] // Selection matrix DenseMatrix S = DenseMatrix::Ones(1,1); for (unsigned int d2 = 0; d2 < bspline.getNumVariables(); d2++) { DenseMatrix temp(S); DenseMatrix Sd_full = DenseMatrix::Identity(numBasisFunctions.at(d2),numBasisFunctions.at(d2)); DenseMatrix Sd(Sd_full); if (d == d2) Sd = Sd_full.block(0,0,n,i); S = kroneckerProduct(temp, Sd); } // Control points that have support in [t_0, t_i] DenseMatrix selc = coeffs*S; DenseVector minCP = selc.rowwise().minCoeff(); DenseVector maxCP = selc.rowwise().maxCoeff(); double minv = minCP(0); double maxv = maxCP(0); // Investigate feasibility if (minv > upperBound || maxv < lowerBound) continue; // infeasible else break; // feasible } // New valid lower bound on x(d) is knots(i-1) if (i > 1) { if (!variables.at(d)->updateLowerBound(knots.at(i-1))) return false; } // Tighten upper bound i = 1; for (; i <= n; i++) { // Knot interval of interest: [t_{n+p-i}, t_{n+p}] // Selection matrix DenseMatrix S = DenseMatrix::Ones(1,1); for (unsigned int d2 = 0; d2 < bspline.getNumVariables(); d2++) { DenseMatrix temp(S); DenseMatrix Sd_full = DenseMatrix::Identity(numBasisFunctions.at(d2),numBasisFunctions.at(d2)); DenseMatrix Sd(Sd_full); if (d == d2) Sd = Sd_full.block(0,n-i,n,i); S = kroneckerProduct(temp, Sd); } // Control points that have support in [t_{n+p-i}, t_{n+p}] DenseMatrix selc = coeffs*S; DenseVector minCP = selc.rowwise().minCoeff(); DenseVector maxCP = selc.rowwise().maxCoeff(); double minv = minCP(0); double maxv = maxCP(0); // Investigate feasibility if (minv > upperBound || maxv < lowerBound) continue; // infeasible else break; // feasible } // New valid lower bound on x(d) is knots(n+p-(i-1)) if (i > 1) { if (!variables.at(d)->updateUpperBound(knots.at(n+p-(i-1)))) return false; // NOTE: the upper bound seems to not be tight! can we use knots.at(n+p-i)? } } return true; }
void test_kronecker_product() { // DM = dense matrix; SM = sparse matrix Matrix<double, 2, 3> DM_a; SparseMatrix<double> SM_a(2,3); SM_a.insert(0,0) = DM_a.coeffRef(0,0) = -0.4461540300782201; SM_a.insert(0,1) = DM_a.coeffRef(0,1) = -0.8057364375283049; SM_a.insert(0,2) = DM_a.coeffRef(0,2) = 0.3896572459516341; SM_a.insert(1,0) = DM_a.coeffRef(1,0) = -0.9076572187376921; SM_a.insert(1,1) = DM_a.coeffRef(1,1) = 0.6469156566545853; SM_a.insert(1,2) = DM_a.coeffRef(1,2) = -0.3658010398782789; MatrixXd DM_b(3,2); SparseMatrix<double> SM_b(3,2); SM_b.insert(0,0) = DM_b.coeffRef(0,0) = 0.9004440976767099; SM_b.insert(0,1) = DM_b.coeffRef(0,1) = -0.2368830858139832; SM_b.insert(1,0) = DM_b.coeffRef(1,0) = -0.9311078389941825; SM_b.insert(1,1) = DM_b.coeffRef(1,1) = 0.5310335762980047; SM_b.insert(2,0) = DM_b.coeffRef(2,0) = -0.1225112806872035; SM_b.insert(2,1) = DM_b.coeffRef(2,1) = 0.5903998022741264; SparseMatrix<double,RowMajor> SM_row_a(SM_a), SM_row_b(SM_b); // test DM_fixedSize = kroneckerProduct(DM_block,DM) Matrix<double, 6, 6> DM_fix_ab = kroneckerProduct(DM_a.topLeftCorner<2,3>(),DM_b); CALL_SUBTEST(check_kronecker_product(DM_fix_ab)); CALL_SUBTEST(check_kronecker_product(kroneckerProduct(DM_a.topLeftCorner<2,3>(),DM_b))); for(int i=0;i<DM_fix_ab.rows();++i) for(int j=0;j<DM_fix_ab.cols();++j) VERIFY_IS_APPROX(kroneckerProduct(DM_a,DM_b).coeff(i,j), DM_fix_ab(i,j)); // test DM_block = kroneckerProduct(DM,DM) MatrixXd DM_block_ab(10,15); DM_block_ab.block<6,6>(2,5) = kroneckerProduct(DM_a,DM_b); CALL_SUBTEST(check_kronecker_product(DM_block_ab.block<6,6>(2,5))); // test DM = kroneckerProduct(DM,DM) MatrixXd DM_ab = kroneckerProduct(DM_a,DM_b); CALL_SUBTEST(check_kronecker_product(DM_ab)); CALL_SUBTEST(check_kronecker_product(kroneckerProduct(DM_a,DM_b))); // test SM = kroneckerProduct(SM,DM) SparseMatrix<double> SM_ab = kroneckerProduct(SM_a,DM_b); CALL_SUBTEST(check_kronecker_product(SM_ab)); SparseMatrix<double,RowMajor> SM_ab2 = kroneckerProduct(SM_a,DM_b); CALL_SUBTEST(check_kronecker_product(SM_ab2)); CALL_SUBTEST(check_kronecker_product(kroneckerProduct(SM_a,DM_b))); // test SM = kroneckerProduct(DM,SM) SM_ab.setZero(); SM_ab.insert(0,0)=37.0; SM_ab = kroneckerProduct(DM_a,SM_b); CALL_SUBTEST(check_kronecker_product(SM_ab)); SM_ab2.setZero(); SM_ab2.insert(0,0)=37.0; SM_ab2 = kroneckerProduct(DM_a,SM_b); CALL_SUBTEST(check_kronecker_product(SM_ab2)); CALL_SUBTEST(check_kronecker_product(kroneckerProduct(DM_a,SM_b))); // test SM = kroneckerProduct(SM,SM) SM_ab.resize(2,33); SM_ab.insert(0,0)=37.0; SM_ab = kroneckerProduct(SM_a,SM_b); CALL_SUBTEST(check_kronecker_product(SM_ab)); SM_ab2.resize(5,11); SM_ab2.insert(0,0)=37.0; SM_ab2 = kroneckerProduct(SM_a,SM_b); CALL_SUBTEST(check_kronecker_product(SM_ab2)); CALL_SUBTEST(check_kronecker_product(kroneckerProduct(SM_a,SM_b))); // test SM = kroneckerProduct(SM,SM) with sparse pattern SM_a.resize(4,5); SM_b.resize(3,2); SM_a.resizeNonZeros(0); SM_b.resizeNonZeros(0); SM_a.insert(1,0) = -0.1; SM_a.insert(0,3) = -0.2; SM_a.insert(2,4) = 0.3; SM_a.finalize(); SM_b.insert(0,0) = 0.4; SM_b.insert(2,1) = -0.5; SM_b.finalize(); SM_ab.resize(1,1); SM_ab.insert(0,0)=37.0; SM_ab = kroneckerProduct(SM_a,SM_b); CALL_SUBTEST(check_sparse_kronecker_product(SM_ab)); // test dimension of result of DM = kroneckerProduct(DM,DM) MatrixXd DM_a2(2,1); MatrixXd DM_b2(5,4); MatrixXd DM_ab2 = kroneckerProduct(DM_a2,DM_b2); CALL_SUBTEST(check_dimension(DM_ab2,2*5,1*4)); DM_a2.resize(10,9); DM_b2.resize(4,8); DM_ab2 = kroneckerProduct(DM_a2,DM_b2); CALL_SUBTEST(check_dimension(DM_ab2,10*4,9*8)); for(int i = 0; i < g_repeat; i++) { double density = Eigen::internal::random<double>(0.01,0.5); int ra = Eigen::internal::random<int>(1,50); int ca = Eigen::internal::random<int>(1,50); int rb = Eigen::internal::random<int>(1,50); int cb = Eigen::internal::random<int>(1,50); SparseMatrix<float,ColMajor> sA(ra,ca), sB(rb,cb), sC; SparseMatrix<float,RowMajor> sC2; MatrixXf dA(ra,ca), dB(rb,cb), dC; initSparse(density, dA, sA); initSparse(density, dB, sB); sC = kroneckerProduct(sA,sB); dC = kroneckerProduct(dA,dB); VERIFY_IS_APPROX(MatrixXf(sC),dC); sC = kroneckerProduct(sA.transpose(),sB); dC = kroneckerProduct(dA.transpose(),dB); VERIFY_IS_APPROX(MatrixXf(sC),dC); sC = kroneckerProduct(sA.transpose(),sB.transpose()); dC = kroneckerProduct(dA.transpose(),dB.transpose()); VERIFY_IS_APPROX(MatrixXf(sC),dC); sC = kroneckerProduct(sA,sB.transpose()); dC = kroneckerProduct(dA,dB.transpose()); VERIFY_IS_APPROX(MatrixXf(sC),dC); sC2 = kroneckerProduct(sA,sB); dC = kroneckerProduct(dA,dB); VERIFY_IS_APPROX(MatrixXf(sC2),dC); } }