From 2206d8b2289cd0e902f0644c2d45bc0bd994790a Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Sun, 8 Mar 2020 16:08:52 +0000 Subject: [PATCH] * impl - FFi for linalg op * fix - cpplint * impl - benchmark ffi for ops * rm - FFI for ops with param * fix - makefile * fix - not include unordered_map --- Makefile | 4 +- benchmark/python/ffi/benchmark_ffi.py | 5 ++ python/mxnet/ndarray/numpy/linalg.py | 15 ++-- python/mxnet/numpy/linalg.py | 4 +- python/mxnet/symbol/numpy/linalg.py | 4 +- src/api/operator/numpy/linalg/np_eigvals.cc | 48 +++++++++++++ src/api/operator/numpy/linalg/np_pinv.cc | 72 +++++++++++++++++++ src/api/operator/numpy/linalg/np_potrf.cc | 48 +++++++++++++ src/api/operator/numpy/linalg/np_tensorinv.cc | 48 +++++++++++++ .../operator/numpy/linalg/np_tensorsolve.cc | 56 +++++++++++++++ src/operator/numpy/linalg/np_eigvals-inl.h | 6 ++ src/operator/numpy/linalg/np_pinv-inl.h | 14 ++++ src/operator/numpy/linalg/np_potrf.cc | 3 +- src/operator/numpy/linalg/np_tensorinv-inl.h | 6 ++ .../numpy/linalg/np_tensorsolve-inl.h | 6 ++ src/operator/tensor/la_op.h | 6 ++ 16 files changed, 330 insertions(+), 15 deletions(-) create mode 100644 src/api/operator/numpy/linalg/np_eigvals.cc create mode 100644 src/api/operator/numpy/linalg/np_pinv.cc create mode 100644 src/api/operator/numpy/linalg/np_potrf.cc create mode 100644 src/api/operator/numpy/linalg/np_tensorinv.cc create mode 100644 src/api/operator/numpy/linalg/np_tensorsolve.cc diff --git a/Makefile b/Makefile index 49ba8fe00e7f..e5d6bb288134 100644 --- a/Makefile +++ b/Makefile @@ -464,9 +464,9 @@ endif all: lib/libmxnet.a lib/libmxnet.so $(BIN) extra-packages extension_libs -SRC = $(wildcard src/*/*/*/*.cc src/*/*/*.cc src/*/*.cc src/*.cc) +SRC = $(wildcard src/*/*/*/*/*.cc src/*/*/*/*.cc src/*/*/*.cc src/*/*.cc src/*.cc) OBJ = $(patsubst %.cc, build/%.o, $(SRC)) -CUSRC = $(wildcard src/*/*/*/*.cu src/*/*/*.cu src/*/*.cu src/*.cu) +CUSRC = $(wildcard src/*/*/*/*.cu src/*/*/*/*.cu src/*/*/*.cu src/*/*.cu src/*.cu) CUOBJ = $(patsubst %.cu, build/%_gpu.o, $(CUSRC)) ifeq ($(USE_TVM_OP), 1) diff --git a/benchmark/python/ffi/benchmark_ffi.py b/benchmark/python/ffi/benchmark_ffi.py index 88af3cf3d55e..74e1e76fa896 100644 --- a/benchmark/python/ffi/benchmark_ffi.py +++ b/benchmark/python/ffi/benchmark_ffi.py @@ -55,6 +55,11 @@ def prepare_workloads(): OpArgMngr.add_workload("cumsum", pool['3x2'], axis=0, out=pool['3x2']) OpArgMngr.add_workload("add", pool['2x2'], pool['2x2']) OpArgMngr.add_workload("random.uniform", low=0, high=1, size=1) + OpArgMngr.add_workload("linalg.cholesky", pool['1x1']) + OpArgMngr.add_workload("linalg.eigvalsh", pool['1x1'], UPLO='L') + OpArgMngr.add_workload("linalg.pinv", pool['2x3x3'], pool['1'], hermitian=False) + OpArgMngr.add_workload("linalg.tensorinv", pool['1x1'], ind=2) + OpArgMngr.add_workload("linalg.tensorsolve", pool['1x1x1'], pool['1x1x1'], (2, 0, 1)) def benchmark_helper(f, *args, **kwargs): diff --git a/python/mxnet/ndarray/numpy/linalg.py b/python/mxnet/ndarray/numpy/linalg.py index 0acedf4bbab4..bc4a6aff22c3 100644 --- a/python/mxnet/ndarray/numpy/linalg.py +++ b/python/mxnet/ndarray/numpy/linalg.py @@ -19,6 +19,7 @@ from . import _op as _mx_nd_np from . import _internal as _npi +from . import _api_internal __all__ = ['norm', 'svd', 'cholesky', 'inv', 'det', 'slogdet', 'solve', 'tensorinv', 'tensorsolve', 'pinv', 'eigvals', 'eig', 'eigvalsh', 'eigh'] @@ -91,9 +92,7 @@ def pinv(a, rcond=1e-15, hermitian=False): """ if hermitian is True: raise NotImplementedError("hermitian is not supported yet...") - if _mx_nd_np._np.isscalar(rcond): - return _npi.pinv_scalar_rcond(a, rcond, hermitian) - return _npi.pinv(a, rcond, hermitian) + return _api_internal.pinv(a, rcond, hermitian) # pylint: disable=too-many-return-statements @@ -332,7 +331,7 @@ def svd(a): return tuple(_npi.svd(a)) -def cholesky(a): +def cholesky(a, lower=True): r""" Cholesky decomposition. @@ -388,7 +387,7 @@ def cholesky(a): array([[16., 4.], [ 4., 10.]]) """ - return _npi.cholesky(a) + return _api_internal.cholesky(a, lower) def inv(a): @@ -649,7 +648,7 @@ def tensorinv(a, ind=2): >>> np.allclose(np.tensordot(ainv, b, 1), np.linalg.tensorsolve(a, b)) True """ - return _npi.tensorinv(a, ind) + return _api_internal.tensorinv(a, ind) def tensorsolve(a, b, axes=None): @@ -697,7 +696,7 @@ def tensorsolve(a, b, axes=None): >>> np.allclose(np.tensordot(a, x, axes=3), b) True """ - return _npi.tensorsolve(a, b, axes) + return _api_internal.tensorsolve(a, b, axes) def eigvals(a): @@ -824,7 +823,7 @@ def eigvalsh(a, UPLO='L'): >>> LA.eigvalsh(a, UPLO='L') array([-2.87381886, 5.10144682, 6.38623114]) # in ascending order """ - return _npi.eigvalsh(a, UPLO) + return _api_internal.eigvalsh(a, UPLO) def eig(a): diff --git a/python/mxnet/numpy/linalg.py b/python/mxnet/numpy/linalg.py index 4d0c5b01698e..fd265dafbd0f 100644 --- a/python/mxnet/numpy/linalg.py +++ b/python/mxnet/numpy/linalg.py @@ -232,7 +232,7 @@ def svd(a): return _mx_nd_np.linalg.svd(a) -def cholesky(a): +def cholesky(a, lower=True): r""" Cholesky decomposition. @@ -288,7 +288,7 @@ def cholesky(a): array([[16., 4.], [ 4., 10.]]) """ - return _mx_nd_np.linalg.cholesky(a) + return _mx_nd_np.linalg.cholesky(a, lower) def inv(a): diff --git a/python/mxnet/symbol/numpy/linalg.py b/python/mxnet/symbol/numpy/linalg.py index d326b37f0635..300fc7d9c862 100644 --- a/python/mxnet/symbol/numpy/linalg.py +++ b/python/mxnet/symbol/numpy/linalg.py @@ -322,7 +322,7 @@ def svd(a): return _npi.svd(a) -def cholesky(a): +def cholesky(a, lower=True): r""" Cholesky decomposition. @@ -378,7 +378,7 @@ def cholesky(a): array([[16., 4.], [ 4., 10.]]) """ - return _npi.cholesky(a) + return _npi.cholesky(a, lower) def inv(a): diff --git a/src/api/operator/numpy/linalg/np_eigvals.cc b/src/api/operator/numpy/linalg/np_eigvals.cc new file mode 100644 index 000000000000..ca5297435d0e --- /dev/null +++ b/src/api/operator/numpy/linalg/np_eigvals.cc @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * \file np_eigvals.cc + * \brief Implementation of the API of functions in src/operator/numpy/linalg/np_eigvals.cc + */ + +#include +#include +#include "../../utils.h" +#include "../../../../operator/numpy/linalg/np_eigvals-inl.h" + +namespace mxnet { + +MXNET_REGISTER_API("_npi.eigvalsh") +.set_body([](runtime::MXNetArgs args, runtime::MXNetRetValue* ret) { + using namespace runtime; + const nnvm::Op* op = Op::Get("_npi_eigvalsh"); + nnvm::NodeAttrs attrs; + op::EigvalshParam param; + param.UPLO = *((args[1].operator std::string()).c_str()); + attrs.parsed = param; + attrs.op = op; + SetAttrDict(&attrs); + int num_outputs = 0; + NDArray* inputs[] = {args[0].operator mxnet::NDArray*()}; + auto ndoutputs = Invoke(op, &attrs, 1, inputs, &num_outputs, nullptr); + *ret = reinterpret_cast(ndoutputs[0]); +}); + +} // namespace mxnet diff --git a/src/api/operator/numpy/linalg/np_pinv.cc b/src/api/operator/numpy/linalg/np_pinv.cc new file mode 100644 index 000000000000..cf13a49c74a9 --- /dev/null +++ b/src/api/operator/numpy/linalg/np_pinv.cc @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * \file np_pinv.cc + * \brief Implementation of the API of functions in src/operator/numpy/linalg/np_pinv.cc + */ + +#include +#include +#include "../../utils.h" +#include "../../../../operator/numpy/linalg/np_pinv-inl.h" + +namespace mxnet { + +inline static void _npi_pinv(runtime::MXNetArgs args, runtime::MXNetRetValue* ret) { + using namespace runtime; + const nnvm::Op* op = Op::Get("_npi_pinv"); + op::PinvParam param; + nnvm::NodeAttrs attrs; + param.hermitian = args[2].operator bool(); + attrs.parsed = param; + attrs.op = op; + SetAttrDict(&attrs); + int num_outputs = 0; + NDArray* inputs[] = {args[0].operator mxnet::NDArray*(), args[1].operator mxnet::NDArray*()}; + auto ndoutputs = Invoke(op, &attrs, 2, inputs, &num_outputs, nullptr); + *ret = reinterpret_cast(ndoutputs[0]); +} + +inline static void _npi_pinv_scalar_rcond(runtime::MXNetArgs args, runtime::MXNetRetValue* ret) { + using namespace runtime; + const nnvm::Op* op = Op::Get("_npi_pinv_scalar_rcond"); + op::PinvScalarRcondParam param; + nnvm::NodeAttrs attrs; + param.rcond = args[1].operator double(); + param.hermitian = args[2].operator bool(); + attrs.parsed = param; + attrs.op = op; + SetAttrDict(&attrs); + int num_outputs = 0; + NDArray* inputs[] = {args[0].operator mxnet::NDArray*()}; + auto ndoutputs = Invoke(op, &attrs, 1, inputs, &num_outputs, nullptr); + *ret = reinterpret_cast(ndoutputs[0]); +} + +MXNET_REGISTER_API("_npi.pinv") +.set_body([](runtime::MXNetArgs args, runtime::MXNetRetValue* ret) { + if (args[1].type_code() == kDLFloat || args[1].type_code() == kDLInt) { + _npi_pinv_scalar_rcond(args, ret); + } else { + _npi_pinv(args, ret); + } +}); + +} // namespace mxnet diff --git a/src/api/operator/numpy/linalg/np_potrf.cc b/src/api/operator/numpy/linalg/np_potrf.cc new file mode 100644 index 000000000000..1d9dfd8d0e13 --- /dev/null +++ b/src/api/operator/numpy/linalg/np_potrf.cc @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * \file np_potrf.cc + * \brief Implementation of the API of functions in src/operator/numpy/linalg/np_potrf.cc + */ + +#include +#include +#include "../../utils.h" +#include "../../../../operator/numpy/linalg/np_potrf-inl.h" + +namespace mxnet { + +MXNET_REGISTER_API("_npi.cholesky") +.set_body([](runtime::MXNetArgs args, runtime::MXNetRetValue* ret) { + using namespace runtime; + const nnvm::Op* op = Op::Get("_npi_cholesky"); + nnvm::NodeAttrs attrs; + op::LaCholeskyParam param; + param.lower = args[1].operator bool(); + attrs.parsed = param; + attrs.op = op; + SetAttrDict(&attrs); + int num_outputs = 0; + NDArray* inputs[] = {args[0].operator mxnet::NDArray*()}; + auto ndoutputs = Invoke(op, &attrs, 1, inputs, &num_outputs, nullptr); + *ret = reinterpret_cast(ndoutputs[0]); +}); + +} // namespace mxnet diff --git a/src/api/operator/numpy/linalg/np_tensorinv.cc b/src/api/operator/numpy/linalg/np_tensorinv.cc new file mode 100644 index 000000000000..75fe3bf3c593 --- /dev/null +++ b/src/api/operator/numpy/linalg/np_tensorinv.cc @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * \file np_tensorinv.cc + * \brief Implementation of the API of functions in src/operator/numpy/linalg/np_tensorinv.cc + */ + +#include +#include +#include "../../utils.h" +#include "../../../../operator/numpy/linalg/np_tensorinv-inl.h" + +namespace mxnet { + +MXNET_REGISTER_API("_npi.tensorinv") +.set_body([](runtime::MXNetArgs args, runtime::MXNetRetValue* ret) { + using namespace runtime; + const nnvm::Op* op = Op::Get("_npi_tensorinv"); + nnvm::NodeAttrs attrs; + op::TensorinvParam param; + param.ind = args[1].operator int(); + attrs.parsed = param; + attrs.op = op; + SetAttrDict(&attrs); + int num_outputs = 0; + NDArray* inputs[] = {args[0].operator mxnet::NDArray*()}; + auto ndoutputs = Invoke(op, &attrs, 1, inputs, &num_outputs, nullptr); + *ret = reinterpret_cast(ndoutputs[0]); +}); + +} // namespace mxnet diff --git a/src/api/operator/numpy/linalg/np_tensorsolve.cc b/src/api/operator/numpy/linalg/np_tensorsolve.cc new file mode 100644 index 000000000000..c55f80497ec6 --- /dev/null +++ b/src/api/operator/numpy/linalg/np_tensorsolve.cc @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * \file np_tensorsolve.cc + * \brief Implementation of the API of functions in src/operator/numpy/linalg/np_tensorsolve.cc + */ + +#include +#include +#include "../../utils.h" +#include "../../../../operator/numpy/linalg/np_tensorsolve-inl.h" + +namespace mxnet { + +MXNET_REGISTER_API("_npi.tensorsolve") +.set_body([](runtime::MXNetArgs args, runtime::MXNetRetValue* ret) { + using namespace runtime; + const nnvm::Op* op = Op::Get("_npi_tensorsolve"); + nnvm::NodeAttrs attrs; + op::TensorsolveParam param; + if (args[2].type_code() == kNull) { + param.a_axes = Tuple(); + } else { + if (args[2].type_code() == kDLInt) { + param.a_axes = Tuple(1, args[2].operator int64_t()); + } else { + param.a_axes = Tuple(args[2].operator ObjectRef()); + } + } + attrs.parsed = param; + attrs.op = op; + SetAttrDict(&attrs); + int num_outputs = 0; + NDArray* inputs[] = {args[0].operator mxnet::NDArray*(), args[1].operator mxnet::NDArray*()}; + auto ndoutputs = Invoke(op, &attrs, 2, inputs, &num_outputs, nullptr); + *ret = reinterpret_cast(ndoutputs[0]); +}); + +} // namespace mxnet diff --git a/src/operator/numpy/linalg/np_eigvals-inl.h b/src/operator/numpy/linalg/np_eigvals-inl.h index 81b46d237206..26b351ac8eab 100644 --- a/src/operator/numpy/linalg/np_eigvals-inl.h +++ b/src/operator/numpy/linalg/np_eigvals-inl.h @@ -27,6 +27,7 @@ #include #include +#include #include "../../operator_common.h" #include "../../mshadow_op.h" #include "../../tensor/la_op.h" @@ -312,6 +313,11 @@ struct EigvalshParam : public dmlc::Parameter { .set_default('L') .describe("Specifies whether the calculation is done with the lower or upper triangular part."); } + void SetAttrDict(std::unordered_map* dict) { + std::ostringstream UPLO_s; + UPLO_s << UPLO; + (*dict)["UPLO"] = UPLO_s.str(); + } }; template diff --git a/src/operator/numpy/linalg/np_pinv-inl.h b/src/operator/numpy/linalg/np_pinv-inl.h index 76bcc9a1ab64..b3b8e0c76c64 100644 --- a/src/operator/numpy/linalg/np_pinv-inl.h +++ b/src/operator/numpy/linalg/np_pinv-inl.h @@ -27,6 +27,7 @@ #include #include +#include #include #include "../../operator_common.h" #include "../../mshadow_op.h" @@ -48,6 +49,11 @@ struct PinvParam : public dmlc::Parameter { .set_default(false) .describe("If True, A is assumed to be Hermitian (symmetric if real-valued)."); } + void SetAttrDict(std::unordered_map* dict) { + std::ostringstream hermitian_s; + hermitian_s << hermitian; + (*dict)["hermitian"] = hermitian_s.str(); + } }; struct PinvScalarRcondParam : public dmlc::Parameter { @@ -61,6 +67,14 @@ struct PinvScalarRcondParam : public dmlc::Parameter { .set_default(false) .describe("If True, A is assumed to be Hermitian (symmetric if real-valued)."); } + void SetAttrDict(std::unordered_map* dict) { + std::ostringstream rcond_s; + std::ostringstream hermitian_s; + rcond_s << rcond; + hermitian_s << hermitian; + (*dict)["rcond"] = rcond_s.str(); + (*dict)["hermitian"] = hermitian_s.str(); + } }; template diff --git a/src/operator/numpy/linalg/np_potrf.cc b/src/operator/numpy/linalg/np_potrf.cc index cad2b3084c21..40e900872365 100644 --- a/src/operator/numpy/linalg/np_potrf.cc +++ b/src/operator/numpy/linalg/np_potrf.cc @@ -56,7 +56,8 @@ NNVM_REGISTER_OP(_npi_cholesky) { return std::vector>{{0, 0}}; }) .set_attr("FCompute", LaOpForward) .set_attr("FGradient", ElemwiseGradUseOut{"_backward_linalg_potrf"}) -.add_argument("A", "NDArray-or-Symbol", "Tensor of input matrices to be decomposed"); +.add_argument("A", "NDArray-or-Symbol", "Tensor of input matrices to be decomposed") +.add_arguments(LaCholeskyParam::__FIELDS__()); } // namespace op } // namespace mxnet diff --git a/src/operator/numpy/linalg/np_tensorinv-inl.h b/src/operator/numpy/linalg/np_tensorinv-inl.h index 4f92ccf9d125..414c3f09ec45 100644 --- a/src/operator/numpy/linalg/np_tensorinv-inl.h +++ b/src/operator/numpy/linalg/np_tensorinv-inl.h @@ -27,6 +27,7 @@ #include #include +#include #include "../../operator_common.h" #include "../../mshadow_op.h" #include "../../tensor/la_op.h" @@ -44,6 +45,11 @@ struct TensorinvParam : public dmlc::Parameter { .set_default(2) .describe("Number of first indices that are involved in the inverse sum."); } + void SetAttrDict(std::unordered_map* dict) { + std::ostringstream ind_s; + ind_s << ind; + (*dict)["ind"] = ind_s.str(); + } }; template diff --git a/src/operator/numpy/linalg/np_tensorsolve-inl.h b/src/operator/numpy/linalg/np_tensorsolve-inl.h index 829a119b64a2..bbde4d40434a 100644 --- a/src/operator/numpy/linalg/np_tensorsolve-inl.h +++ b/src/operator/numpy/linalg/np_tensorsolve-inl.h @@ -27,6 +27,7 @@ #include #include +#include #include "../../operator_common.h" #include "../../mshadow_op.h" #include "../../tensor/la_op.h" @@ -46,6 +47,11 @@ struct TensorsolveParam : public dmlc::Parameter { .set_default(mxnet::Tuple()) .describe("Tuple of ints, optional. Axes in a to reorder to the right, before inversion."); } + void SetAttrDict(std::unordered_map* dict) { + std::ostringstream a_axes_s; + a_axes_s << a_axes; + (*dict)["a_axes"] = a_axes_s.str(); + } }; // Fix negative axes. diff --git a/src/operator/tensor/la_op.h b/src/operator/tensor/la_op.h index e15390ecde5a..cf80f28cb8b2 100644 --- a/src/operator/tensor/la_op.h +++ b/src/operator/tensor/la_op.h @@ -29,6 +29,7 @@ #include #include #include +#include #include "../mshadow_op.h" #include "../mxnet_op.h" #include "../operator_common.h" @@ -91,6 +92,11 @@ struct LaCholeskyParam : public dmlc::Parameter { .describe ("True if the triangular matrix is lower triangular, false if it is upper triangular."); } + void SetAttrDict(std::unordered_map* dict) { + std::ostringstream lower_s; + lower_s << lower; + (*dict)["lower"] = lower_s.str(); + } }; // Parameters for matrix-matrix multiplication where one is a triangular matrix.