Repository: conan-io/conan-center-index

Issue:
[request] armadillo/10.6.1
### Package Details
  * Package Name/Version: **armadillo/10.6.1**
  * Website: **http://arma.sourceforge.net/**
  * Source code: **https://gitlab.com/conradsnicta/armadillo-code**


### Description Of The Library / Tool

Armadillo: fast C++ library for linear algebra (matrix maths) & scientific computing.

* Armadillo is a high quality linear algebra library (matrix maths) for the C++ language, aiming towards a good balance between speed and ease of use
* Provides high-level syntax and functionality deliberately similar to Matlab
* Useful for algorithm development directly in C++, or quick conversion of research code into production environments
* Provides efficient classes for vectors, matrices and cubes; dense and sparse matrices are supported
* Integer, floating point and complex numbers are supported
* A sophisticated expression evaluator (based on template meta-programming) automatically combines several operations to increase speed and efficiency
* Various matrix decompositions (eigen, SVD, QR, etc) are provided through integration with LAPACK, or one of its high performance drop-in replacements (eg. MKL or OpenBLAS)
* Can automatically use OpenMP multi-threading (parallelisation) to speed up computationally expensive operations
* Distributed under the permissive Apache 2.0 license, useful for both open-source and proprietary (closed-source) software
* Can be used for machine learning, pattern recognition, computer vision, signal processing, bioinformatics, statistics, finance, etc 

### Other information

Originally requested at https://github.com/conan-io/wishlist/issues/96, this request is replicated here for tracking purposes with the new CCI. As discussed in the above thread, previous implementations of conan recipes that could be used as exemplars for a CCI recipe are:

* https://github.com/darcamo/conan-armadillo
* https://github.com/Yevgnen/conan-armadillo

Also, the existing vcpkg recipe:

* https://github.com/microsoft/vcpkg/pull/2954

Note that armadillo has optional dependencies on [LAPACK](https://github.com/conan-io/conan-center-index/issues/4509), BLAS/[OpenBLAS](https://github.com/conan-io/conan-center-index/issues/4813), [ATLAS](https://github.com/conan-io/conan-center-index/issues/6757), [HDF5](https://github.com/conan-io/conan-center-index/issues/1073), [ARPACK](https://github.com/conan-io/conan-center-index/issues/6755), and [SuperLU](https://github.com/conan-io/conan-center-index/issues/6756) that should ideally be captured in this recipe. Armadillo ships with a cmake build recipe that by default searches for these dependencies in system libraries.


Discussion:

Resolving patch:
diff --git a/recipes/armadillo/all/conanfile.py b/recipes/armadillo/all/conanfile.py
new file mode 100644
--- /dev/null
+++ b/recipes/armadillo/all/conanfile.py
@@ -0,0 +1,321 @@
+from conans import ConanFile, CMake, tools
+from conans.errors import ConanInvalidConfiguration
+import os
+
+
+class ArmadilloConan(ConanFile):
+    name = "armadillo"
+    license = "Apache-2.0"
+    url = "https://github.com/conan-io/conan-center-index"
+    homepage = "http://arma.sourceforge.net"
+    description = "Armadillo is a high quality C++ library for linear algebra and scientific computing, aiming towards a good balance between speed and ease of use."
+    topics = (
+        "linear algebra",
+        "scientific computing",
+        "matrix",
+        "vector",
+        "math",
+        "blas",
+        "lapack",
+        "mkl",
+        "hdf5",
+    )
+    settings = "os", "compiler", "build_type", "arch"
+    options = {
+        "shared": [True, False],
+        "fPIC": [True, False],
+        "use_blas": [
+            False,
+            "openblas",
+            "intel_mkl",
+            "system_blas",
+            "system_flexiblas",
+            "framework_accelerate",
+        ],
+        "use_lapack": [
+            False,
+            "openblas",
+            "intel_mkl",
+            "system_lapack",
+            "system_atlas",
+            "framework_accelerate",
+        ],
+        "use_hdf5": [True, False],
+        "use_superlu": [False, "system_superlu"],
+        "use_extern_rng": [True, False],
+        "use_arpack": [False, "system_arpack"],
+        "use_wrapper": [True, False],
+    }
+    default_options = {
+        "shared": False,
+        "fPIC": True,
+        "use_blas": "openblas",
+        "use_lapack": False,
+        "use_hdf5": True,
+        "use_superlu": False,
+        "use_extern_rng": False,
+        "use_arpack": False,
+        "use_wrapper": False,
+    }
+    # Values that must be set for multiple options to be valid
+    _co_dependencies = {
+        "intel_mkl": [
+            "use_blas",
+            "use_lapack",
+        ],
+        "framework_accelerate": [
+            "use_blas",
+            "use_lapack",
+        ],
+    }
+    exports_sources = ["CMakeLists.txt", "patches/*"]
+    generators = (
+        "cmake",
+        "cmake_find_package",
+    )
+    _cmake = None
+
+    @property
+    def _source_subfolder(self):
+        return "source_subfolder"
+
+    @property
+    def _build_subfolder(self):
+        return "build_subfolder"
+
+    def config_options(self):
+        if self.settings.os == "Windows":
+            del self.options.fPIC
+        if self.settings.os == "Macos":
+            # Macos will default to Accelerate framework
+            self.options.use_blas = "framework_accelerate"
+            self.options.use_lapack = "framework_accelerate"
+
+    def configure(self):
+        if self.options.shared:
+            del self.options.fPIC
+
+    def validate(self):
+        if self.settings.compiler.cppstd:
+            tools.check_min_cppstd(self, "11")
+
+        if self.settings.os != "Macos" and (
+            self.options.use_blas == "framework_accelerate"
+            or self.options.use_lapack == "framework_accelerate"
+        ):
+            raise ConanInvalidConfiguration(
+                "framework_accelerate can only be used on Macos"
+            )
+
+        for value, options in self._co_dependencies.items():
+            options_without_value = [
+                x for x in options if getattr(self.options, x) != value
+            ]
+            if options_without_value and (len(options) != len(options_without_value)):
+                raise ConanInvalidConfiguration(
+                    "Options {} must all be set to '{}' to use this feature. To fix this, set option {} to '{}'.".format(
+                        ", ".join(options),
+                        value,
+                        ", ".join(options_without_value),
+                        value,
+                    )
+                )
+
+        if (
+            self.options.use_lapack == "openblas"
+            and self.options.use_blas != "openblas"
+        ):
+            raise ConanInvalidConfiguration(
+                "OpenBLAS can only provide LAPACK functionality when also providing BLAS functionality. Set use_blas=openblas and try again."
+            )
+
+        deprecated_opts = list(
+            set(
+                [
+                    opt
+                    for opt in [
+                        str(self.options.use_blas),
+                        str(self.options.use_lapack),
+                    ]
+                    if "system" in opt
+                ]
+            )
+        )
+
+        for opt in deprecated_opts:
+            self.output.warn(
+                f"DEPRECATION NOTICE: Value {opt} uses armadillo's default dependency search and will be replaced when this package becomes available in ConanCenter"
+            )
+
+    def requirements(self):
+        # Optional requirements
+        # TODO: "atlas/3.10.3" # Pending https://github.com/conan-io/conan-center-index/issues/6757
+        # TODO: "superlu/5.2.2" # Pending https://github.com/conan-io/conan-center-index/issues/6756
+        # TODO: "arpack/1.0" # Pending https://github.com/conan-io/conan-center-index/issues/6755
+        # TODO: "flexiblas/3.0.4" # Pending https://github.com/conan-io/conan-center-index/issues/6827
+
+        if self.options.use_hdf5:
+            # Use the conan dependency if the system lib isn't being used
+            self.requires("hdf5/1.12.0")
+
+        if self.options.use_blas == "openblas":
+            self.requires("openblas/0.3.15")
+            # Note that if you're relying on this to build LAPACK, you _must_ have
+            # a fortran compiler installed. If you don't, OpenBLAS will build successfully but
+            # without LAPACK support, which isn't obvious.
+            # This can be achieved by setting the FC environment variable in your conan profile
+            self.options["openblas"].build_lapack = (
+                self.options.use_lapack == "openblas"
+            )
+        if (
+            self.options.use_blas == "intel_mkl"
+            and self.options.use_lapack == "intel_mkl"
+        ):
+            # Consumers can override this requirement with their own by using
+            # self.requires("intel-mkl/version@user/channel, override=True) in their consumer
+            # conanfile.py
+            if (
+                self.options.use_blas == "intel_mkl"
+                or self.options.use_lapack == "intel_mkl"
+            ):
+                self.output.warn(
+                    "The intel-mkl package does not exist in CCI. To use an Intel MKL package, override this requirement with your own recipe."
+                )
+            self.requires("intel-mkl/2021.4")
+
+    def _configure_cmake(self):
+        if self._cmake:
+            return self._cmake
+        self._cmake = CMake(self)
+        self._cmake.definitions["ARMA_USE_LAPACK"] = self.options.use_lapack
+        self._cmake.definitions["ARMA_USE_BLAS"] = self.options.use_blas
+        self._cmake.definitions["ARMA_USE_ATLAS"] = (
+            self.options.use_lapack == "system_atlas"
+        )
+        self._cmake.definitions["ARMA_USE_HDF5"] = self.options.use_hdf5
+        self._cmake.definitions["ARMA_USE_ARPACK"] = self.options.use_arpack
+        self._cmake.definitions["ARMA_USE_EXTERN_RNG"] = self.options.use_extern_rng
+        self._cmake.definitions["ARMA_USE_SUPERLU"] = self.options.use_superlu
+        self._cmake.definitions["ARMA_USE_WRAPPER"] = self.options.use_wrapper
+        self._cmake.definitions["ARMA_USE_ACCELERATE"] = (
+            self.options.use_blas == "framework_accelerate"
+            or self.options.use_lapack == "framework_accelerate"
+        ) and self.settings.os == "Macos"
+        self._cmake.definitions["DETECT_HDF5"] = self.options.use_hdf5
+        self._cmake.definitions["USE_OPENBLAS"] = self.options.use_blas == "openblas"
+        self._cmake.definitions["USE_MKL"] = (
+            self.options.use_blas == "intel_mkl"
+            and self.options.use_lapack == "intel_mkl"
+        )
+        self._cmake.definitions["USE_SYSTEM_LAPACK"] = (
+            self.options.use_lapack == "system_lapack"
+        )
+        self._cmake.definitions["USE_SYSTEM_BLAS"] = (
+            self.options.use_blas == "system_blas"
+        )
+        self._cmake.definitions["USE_SYSTEM_ATLAS"] = (
+            self.options.use_lapack == "system_atlas"
+        )
+        self._cmake.definitions["USE_SYSTEM_HDF5"] = False
+        self._cmake.definitions["USE_SYSTEM_ARPACK"] = self.options.use_arpack
+        self._cmake.definitions["USE_SYSTEM_SUPERLU"] = self.options.use_superlu
+        self._cmake.definitions["USE_SYSTEM_OPENBLAS"] = False
+        self._cmake.definitions["USE_SYSTEM_FLEXIBLAS"] = (
+            self.options.use_blas == "system_flexiblas"
+        )
+        self._cmake.definitions["ALLOW_FLEXIBLAS_LINUX"] = (
+            self.options.use_blas == "system_flexiblas" and self.settings.os == "Linux"
+        )
+        self._cmake.definitions["ALLOW_OPENBLAS_MACOS"] = (
+            self.options.use_blas == "openblas"
+        ) and self.settings.os == "Macos"
+        self._cmake.definitions["ALLOW_BLAS_LAPACK_MACOS"] = (
+            self.options.use_blas != "framework_accelerate"
+        )
+        self._cmake.definitions["BUILD_SHARED_LIBS"] = self.options.shared
+        self._cmake.configure(build_folder=self._build_subfolder)
+        return self._cmake
+
+    def source(self):
+        tools.get(
+            **self.conan_data["sources"][self.version],
+            strip_root=True,
+            destination=self._source_subfolder,
+            filename="{name}-{version}.tar.xz".format(
+                name=self.name, version=self.version
+            ),
+        )
+
+    def _patch_sources(self):
+        for patch in self.conan_data.get("patches", {}).get(self.version, []):
+            tools.patch(**patch)
+
+    def build(self):
+        self._patch_sources()
+        cmake = self._configure_cmake()
+        cmake.build()
+
+    def package(self):
+        self.copy("LICENSE.txt", dst="licenses", src=self._source_subfolder)
+        self.copy("NOTICE.txt", dst="licenses", src=self._source_subfolder)
+        cmake = self._configure_cmake()
+        cmake.install()
+        tools.rmdir(os.path.join(self.package_folder, "lib", "pkgconfig"))
+        tools.rmdir(os.path.join(self.package_folder, "share"))
+
+    def package_info(self):
+        self.cpp_info.libs = ["armadillo"]
+        self.cpp_info.names["pkg_config"] = "armadillo"
+
+        if self.settings.build_type == "Release":
+            self.cpp_info.defines.append("ARMA_NO_DEBUG")
+
+        # The wrapper library links everything together. If disabled, system libs must be
+        # linked manually
+        if not self.options.use_wrapper:
+            self.cpp_info.defines.append("ARMA_DONT_USE_WRAPPER")
+            if self.options.use_blas == "framework_accelerate":
+                self.cpp_info.frameworks.append("Accelerate")
+
+        if self.options.use_hdf5:
+            self.cpp_info.defines.append("ARMA_USE_HDF5")
+        else:
+            self.cpp_info.defines.append("ARMA_DONT_USE_HDF5")
+
+        if self.options.use_blas:
+            self.cpp_info.defines.append("ARMA_USE_BLAS")
+            if self.options.use_blas == "system_blas" and not self.options.use_wrapper:
+                self.cpp_info.system_libs.extend(["blas"])
+        else:
+            self.cpp_info.defines.append("ARMA_DONT_USE_BLAS")
+
+        if self.options.use_lapack:
+            self.cpp_info.defines.append("ARMA_USE_LAPACK")
+            if (
+                self.options.use_lapack == "system_lapack"
+                and not self.options.use_wrapper
+            ):
+                self.cpp_info.system_libs.extend(["lapack"])
+        else:
+            self.cpp_info.defines.append("ARMA_DONT_USE_LAPACK")
+
+        if self.options.use_arpack:
+            self.cpp_info.defines.append("ARMA_USE_ARPACK")
+            if not self.options.use_wrapper:
+                self.cpp_info.system_libs.extend(["arpack"])
+        else:
+            self.cpp_info.defines.append("ARMA_DONT_USE_ARPACK")
+
+        if self.options.use_superlu:
+            self.cpp_info.defines.append("ARMA_USE_SUPERLU")
+            if not self.options.use_wrapper:
+                self.cpp_info.system_libs.extend(["superlu"])
+        else:
+            self.cpp_info.defines.append("ARMA_DONT_USE_SUPERLU")
+
+        if self.options.use_lapack == "system_atlas":
+            self.cpp_info.defines.append("ARMA_USE_ATLAS")
+            if not self.options.use_wrapper:
+                self.cpp_info.system_libs.extend(["atlas"])
+        else:
+            self.cpp_info.defines.append("ARMA_DONT_USE_ATLAS")


Test patch:
diff --git a/recipes/armadillo/all/test_package/CMakeLists.txt b/recipes/armadillo/all/test_package/CMakeLists.txt
new file mode 100644
--- /dev/null
+++ b/recipes/armadillo/all/test_package/CMakeLists.txt
@@ -0,0 +1,11 @@
+cmake_minimum_required(VERSION 3.6)
+project(PackageTest CXX)
+
+include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
+conan_basic_setup(TARGETS)
+
+find_package(armadillo REQUIRED)
+
+add_executable(example example.cpp)
+target_link_libraries(example armadillo::armadillo)
+set_property(TARGET example PROPERTY CXX_STANDARD 11)
diff --git a/recipes/armadillo/all/test_package/conanfile.py b/recipes/armadillo/all/test_package/conanfile.py
new file mode 100644
--- /dev/null
+++ b/recipes/armadillo/all/test_package/conanfile.py
@@ -0,0 +1,18 @@
+import os
+
+from conans import ConanFile, CMake, tools
+
+
+class ArmadilloTestConan(ConanFile):
+    settings = "os", "compiler", "build_type", "arch"
+    generators = "cmake", "cmake_find_package"
+
+    def build(self):
+        cmake = CMake(self)
+        cmake.configure()
+        cmake.build()
+
+    def test(self):
+        if not tools.cross_building(self):
+            bin_path = os.path.join("bin", "example")
+            self.run(bin_path, run_environment=True)
diff --git a/recipes/armadillo/all/test_package/example.cpp b/recipes/armadillo/all/test_package/example.cpp
new file mode 100644
--- /dev/null
+++ b/recipes/armadillo/all/test_package/example.cpp
@@ -0,0 +1,159 @@
+#include <iostream>
+#include <armadillo>
+
+using namespace std;
+using namespace arma;
+
+// Armadillo documentation is available at:
+// http://arma.sourceforge.net/docs.html
+
+// NOTE: the C++11 "auto" keyword is not recommended for use with Armadillo objects and functions
+
+int
+main(int argc, char** argv)
+  {
+  cout << "Armadillo version: " << arma_version::as_string() << endl;
+  
+  // construct a matrix according to given size and form of element initialisation
+  mat A(2,3,fill::zeros);
+  
+  // .n_rows and .n_cols are read only
+  cout << "A.n_rows: " << A.n_rows << endl;
+  cout << "A.n_cols: " << A.n_cols << endl;
+  
+  A(1,2) = 456.0;  // access an element (indexing starts at 0)
+  A.print("A:");
+  
+  A = 5.0;         // scalars are treated as a 1x1 matrix
+  A.print("A:");
+  
+  A.set_size(4,5); // change the size (data is not preserved)
+  
+  A.fill(5.0);     // set all elements to a specific value
+  A.print("A:");
+  
+  A = { { 0.165300, 0.454037, 0.995795, 0.124098, 0.047084 },
+        { 0.688782, 0.036549, 0.552848, 0.937664, 0.866401 },
+        { 0.348740, 0.479388, 0.506228, 0.145673, 0.491547 },
+        { 0.148678, 0.682258, 0.571154, 0.874724, 0.444632 },
+        { 0.245726, 0.595218, 0.409327, 0.367827, 0.385736 } };
+        
+  A.print("A:");
+  
+#ifdef ARMA_USE_LAPACK
+  // determinant
+  cout << "det(A): " << det(A) << endl;
+  
+  // inverse
+  cout << "inv(A): " << endl << inv(A) << endl;
+#else
+  cout << "LAPACK not available. Skipping calls to functions det() and inv()" << endl;
+#endif
+  
+  // save matrix as a text file
+  A.save("A.txt", raw_ascii);
+  
+  // load from file
+  mat B;
+  B.load("A.txt");
+  
+  // submatrices
+  cout << "B( span(0,2), span(3,4) ):" << endl << B( span(0,2), span(3,4) ) << endl;
+  
+  cout << "B( 0,3, size(3,2) ):" << endl << B( 0,3, size(3,2) ) << endl;
+  
+  cout << "B.row(0): " << endl << B.row(0) << endl;
+  
+  cout << "B.col(1): " << endl << B.col(1) << endl;
+  
+  // transpose
+  cout << "B.t(): " << endl << B.t() << endl;
+  
+  // maximum from each column (traverse along rows)
+  cout << "max(B): " << endl << max(B) << endl;
+  
+  // maximum from each row (traverse along columns)
+  cout << "max(B,1): " << endl << max(B,1) << endl;
+  
+  // maximum value in B
+  cout << "max(max(B)) = " << max(max(B)) << endl;
+  
+  // sum of each column (traverse along rows)
+  cout << "sum(B): " << endl << sum(B) << endl;
+  
+  // sum of each row (traverse along columns)
+  cout << "sum(B,1) =" << endl << sum(B,1) << endl;
+  
+  // sum of all elements
+  cout << "accu(B): " << accu(B) << endl;
+  
+  // trace = sum along diagonal
+  cout << "trace(B): " << trace(B) << endl;
+  
+  // generate the identity matrix
+  mat C = eye<mat>(4,4);
+  
+  // random matrix with values uniformly distributed in the [0,1] interval
+  mat D = randu<mat>(4,4);
+  D.print("D:");
+  
+  // row vectors are treated like a matrix with one row
+  rowvec r = { 0.59119, 0.77321, 0.60275, 0.35887, 0.51683 };
+  r.print("r:");
+  
+  // column vectors are treated like a matrix with one column
+  vec q = { 0.14333, 0.59478, 0.14481, 0.58558, 0.60809 };
+  q.print("q:");
+  
+  // convert matrix to vector; data in matrices is stored column-by-column
+  vec v = vectorise(A);
+  v.print("v:");
+  
+  // dot or inner product
+  cout << "as_scalar(r*q): " << as_scalar(r*q) << endl;
+  
+  // outer product
+  cout << "q*r: " << endl << q*r << endl;
+  
+  // multiply-and-accumulate operation (no temporary matrices are created)
+  cout << "accu(A % B) = " << accu(A % B) << endl;
+  
+  // example of a compound operation
+  B += 2.0 * A.t();
+  B.print("B:");
+  
+  // imat specifies an integer matrix
+  imat AA = { { 1, 2, 3 },
+              { 4, 5, 6 },
+              { 7, 8, 9 } };
+  
+  imat BB = { { 3, 2, 1 }, 
+              { 6, 5, 4 },
+              { 9, 8, 7 } };
+  
+  // comparison of matrices (element-wise); output of a relational operator is a umat
+  umat ZZ = (AA >= BB);
+  ZZ.print("ZZ:");
+  
+  // cubes ("3D matrices")
+  cube Q( B.n_rows, B.n_cols, 2 );
+  
+  Q.slice(0) = B;
+  Q.slice(1) = 2.0 * B;
+  
+  Q.print("Q:");
+  
+  // 2D field of matrices; 3D fields are also supported
+  field<mat> F(4,3); 
+  
+  for(uword col=0; col < F.n_cols; ++col)
+      for(uword row=0; row < F.n_rows; ++row)
+        {
+        F(row,col) = randu<mat>(2,3);  // each element in field<mat> is a matrix
+        }
+  
+  F.print("F:");
+  
+  return 0;
+  }
+