Repository: computationalgeography/lue (language: python)

Issue:
Add support for negate
- Unary operation
- Add operator as well


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/conda.yml b/.github/workflows/conda.yml
index ea71c9090..d776c1f50 100644
--- a/.github/workflows/conda.yml
+++ b/.github/workflows/conda.yml
@@ -16,7 +16,7 @@ jobs:
     build:
         name: ${{ matrix.os }} / ${{ matrix.target-platform }}
         runs-on: ${{ matrix.os }}
-        timeout-minutes: 180
+        timeout-minutes: 240
         defaults:
             run:
                 shell: bash -l {0}
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index c83430d16..850040e72 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -36,7 +36,7 @@ jobs:
 
         name: ${{ matrix.os }} / ${{ matrix.compiler.cxx }} / python - ${{ matrix.python_version }}
 
-        timeout-minutes: 180
+        timeout-minutes: 240
 
         defaults:
           run:
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index 5aef6f958..c0c885f8d 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -31,7 +31,7 @@ jobs:
 
         name: macos-${{ matrix.macos_version }} / ${{ matrix.compiler.cxx }}
 
-        timeout-minutes: 180
+        timeout-minutes: 240
 
         defaults:
           run:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e4d7f329e..a2735f67b 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -11,18 +11,18 @@ exclude: |
   )$
 repos:
 - repo: https://github.com/pre-commit/pre-commit-hooks
-  rev: v4.5.0
+  rev: v4.6.0
   hooks:
   - id: trailing-whitespace
   - id: end-of-file-fixer
   # - id: check-yaml
   - id: check-added-large-files
 - repo: https://github.com/psf/black
-  rev: 23.3.0
+  rev: 24.4.2
   hooks:
   - id: black
 - repo: https://github.com/pre-commit/mirrors-mypy
-  rev: v1.7.0
+  rev: v1.10.0
   hooks:
   - id: mypy
     # args: [--verbose]
@@ -37,7 +37,7 @@ repos:
         source/qa/script/.*|
       )$
 - repo: https://github.com/pycqa/isort
-  rev: 5.12.0
+  rev: 5.13.2
   hooks:
   - id: isort
     files: |
diff --git a/document/doc/pcraster/status.rst b/document/doc/pcraster/status.rst
index 664600ebf..90c303049 100644
--- a/document/doc/pcraster/status.rst
+++ b/document/doc/pcraster/status.rst
@@ -59,7 +59,7 @@ added to the LUE framework. The number of model developers requesting one is one
      -
    * - ``-``
      - ✅
-     -
+     - subtract and negate
    * - ``/`` or div
      - ✅
      - div is only part of `lue.pcraster`
@@ -311,13 +311,15 @@ added to the LUE framework. The number of model developers requesting one is one
      - minimum
    * - mapnormal
      - ✅
-     - normal(array, dtype, 0, 1), normal(array_shape, partition_shape, dtype, 0, 1)
+     - | normal(array, dtype, 0, 1)
+       | normal(array_shape, partition_shape, dtype, 0, 1)
    * - maptotal
      - ✅
      - sum
    * - mapuniform
      - ✅
-     - uniform(array, dtype, 0, 1), uniform(array_shape, partition_shape, dtype, 0, 1)
+     - | uniform(array, dtype, 0, 1)
+       | uniform(array_shape, partition_shape, dtype, 0, 1)
    * - | markwhilesumle
        | markwhilesumge
      - ❌
diff --git a/environment/cmake/LueConfiguration.cmake b/environment/cmake/LueConfiguration.cmake
index 2b8a52ff6..cd4b32d95 100644
--- a/environment/cmake/LueConfiguration.cmake
+++ b/environment/cmake/LueConfiguration.cmake
@@ -337,11 +337,15 @@ if(LUE_PYBIND11_REQUIRED)
 
     # Disable support for numpy 2.0 for now
     # https://github.com/computationalgeography/lue/issues/664
-    set(PYBIND11_NUMPY_1_ONLY TRUE)
+    target_compile_definitions(pybind11::module
+        INTERFACE
+            PYBIND11_NUMPY_1_ONLY)
 
     # Silence pybind11 for now
     # https://github.com/computationalgeography/lue/issues/484
-    set(PYBIND11_NO_ASSERT_GIL_HELD_INCREF_DECREF TRUE)
+    target_compile_definitions(pybind11::module
+        INTERFACE
+            PYBIND11_NO_ASSERT_GIL_HELD_INCREF_DECREF)
 endif()
 
 
diff --git a/source/framework/algorithm/CMakeLists.txt b/source/framework/algorithm/CMakeLists.txt
index 9716ef18b..5deb43553 100644
--- a/source/framework/algorithm/CMakeLists.txt
+++ b/source/framework/algorithm/CMakeLists.txt
@@ -255,6 +255,19 @@ block()
                         '{"Policies":"${Policies}","Element":"${Element}","rank":"${rank}"}'
                 )
                 list(APPEND generated_source_files "${output_pathname}")
+
+                # Instantiate negate
+                set(output_pathname "${CMAKE_CURRENT_BINARY_DIR}/${offset}/negate-${count}.cpp")
+
+                generate_template_instantiation(
+                    INPUT_PATHNAME
+                        "${CMAKE_CURRENT_SOURCE_DIR}/${offset}/negate.cpp.in"
+                    OUTPUT_PATHNAME
+                        "${output_pathname}"
+                    DICTIONARY
+                        '{"Policies":"${Policies}","Element":"${Element}","rank":"${rank}"}'
+                )
+                list(APPEND generated_source_files "${output_pathname}")
             endforeach()
         endforeach()
     endforeach()
diff --git a/source/framework/algorithm/include/lue/framework/algorithm/default_policies/negate.hpp b/source/framework/algorithm/include/lue/framework/algorithm/default_policies/negate.hpp
new file mode 100644
index 000000000..6095b183b
--- /dev/null
+++ b/source/framework/algorithm/include/lue/framework/algorithm/default_policies/negate.hpp
@@ -0,0 +1,29 @@
+#pragma once
+#include "lue/framework/algorithm/negate.hpp"
+#include "lue/framework/algorithm/operator.hpp"
+
+
+namespace lue {
+    namespace policy::negate {
+
+        template<typename Element>
+        using DefaultPolicies = policy::
+            DefaultPolicies<AllValuesWithinDomain<Element>, OutputElements<Element>, InputElements<Element>>;
+
+    }  // namespace policy::negate
+
+
+    namespace default_policies {
+
+        template<typename Element, Rank rank>
+        auto negate(PartitionedArray<Element, rank> const& array)
+        {
+            using Policies = policy::negate::DefaultPolicies<Element>;
+
+            return negate(Policies{}, array);
+        }
+
+        LUE_UNARY_LOGICAL_OPERATOR(-, negate)
+
+    }  // namespace default_policies
+}  // namespace lue
diff --git a/source/framework/algorithm/include/lue/framework/algorithm/definition/negate.hpp b/source/framework/algorithm/include/lue/framework/algorithm/definition/negate.hpp
new file mode 100644
index 000000000..17355bed3
--- /dev/null
+++ b/source/framework/algorithm/include/lue/framework/algorithm/definition/negate.hpp
@@ -0,0 +1,46 @@
+#pragma once
+#include "lue/framework/algorithm/definition/unary_local_operation.hpp"
+#include "lue/framework/algorithm/local_operation_export.hpp"
+#include "lue/framework/algorithm/negate.hpp"
+#include "lue/macro.hpp"
+
+
+namespace lue {
+    namespace detail {
+
+        template<typename InputElement>
+        class Negate
+        {
+
+            public:
+
+                static_assert(std::is_signed_v<InputElement> || std::is_floating_point_v<InputElement>);
+
+                using OutputElement = InputElement;
+
+                auto operator()(InputElement const& input_element) const noexcept -> OutputElement
+                {
+                    return -input_element;
+                }
+        };
+
+    }  // namespace detail
+
+
+    template<typename Policies, Rank rank>
+    auto negate(
+        Policies const& policies, PartitionedArray<policy::InputElementT<Policies, 0>, rank> const& array)
+        -> PartitionedArray<policy::OutputElementT<Policies, 0>, rank>
+    {
+        return unary_local_operation(policies, array, detail::Negate<policy::InputElementT<Policies, 0>>{});
+    }
+
+}  // namespace lue
+
+
+#define LUE_INSTANTIATE_NEGATE(Policies, rank)                                                               \
+                                                                                                             \
+    template LUE_LOCAL_OPERATION_EXPORT PartitionedArray<policy::OutputElementT<Policies, 0>, rank>          \
+    negate<ArgumentType<void(Policies)>, rank>(                                                              \
+        ArgumentType<void(Policies)> const&,                                                                 \
+        PartitionedArray<policy::InputElementT<Policies, 0>, rank> const&);
diff --git a/source/framework/algorithm/include/lue/framework/algorithm/negate.hpp b/source/framework/algorithm/include/lue/framework/algorithm/negate.hpp
new file mode 100644
index 000000000..b554eeeb0
--- /dev/null
+++ b/source/framework/algorithm/include/lue/framework/algorithm/negate.hpp
@@ -0,0 +1,13 @@
+#pragma once
+#include "lue/framework/algorithm/policy.hpp"
+#include "lue/framework/partitioned_array.hpp"
+
+
+namespace lue {
+
+    template<typename Policies, Rank rank>
+    auto negate(
+        Policies const& policies, PartitionedArray<policy::InputElementT<Policies, 0>, rank> const& array)
+        -> PartitionedArray<policy::OutputElementT<Policies, 0>, rank>;
+
+}  // namespace lue
diff --git a/source/framework/algorithm/include/lue/framework/algorithm/value_policies/negate.hpp b/source/framework/algorithm/include/lue/framework/algorithm/value_policies/negate.hpp
new file mode 100644
index 000000000..925cf65d6
--- /dev/null
+++ b/source/framework/algorithm/include/lue/framework/algorithm/value_policies/negate.hpp
@@ -0,0 +1,31 @@
+#pragma once
+#include "lue/framework/algorithm/negate.hpp"
+#include "lue/framework/algorithm/operator.hpp"
+
+
+namespace lue {
+    namespace policy::negate {
+
+        template<typename Element>
+        using DefaultValuePolicies = policy::DefaultValuePolicies<
+            AllValuesWithinDomain<Element>,
+            OutputElements<Element>,
+            InputElements<Element>>;
+
+    }  // namespace policy::negate
+
+
+    namespace value_policies {
+
+        template<typename Element, Rank rank>
+        auto negate(PartitionedArray<Element, rank> const& array)
+        {
+            using Policies = policy::negate::DefaultValuePolicies<Element>;
+
+            return negate(Policies{}, array);
+        }
+
+        LUE_UNARY_LOGICAL_OPERATOR(-, negate)
+
+    }  // namespace value_policies
+}  // namespace lue
diff --git a/source/framework/algorithm/src/local_operation/negate.cpp.in b/source/framework/algorithm/src/local_operation/negate.cpp.in
new file mode 100644
index 000000000..8d25b4cc0
--- /dev/null
+++ b/source/framework/algorithm/src/local_operation/negate.cpp.in
@@ -0,0 +1,13 @@
+#include "lue/framework/algorithm/default_policies/negate.hpp"
+#include "lue/framework/algorithm/definition/negate.hpp"
+#include "lue/framework/algorithm/value_policies/negate.hpp"
+
+
+namespace lue {
+
+    LUE_INSTANTIATE_NEGATE(
+            ESC(policy::negate::{{ Policies }}<{{ Element }}>),
+            {{ rank }}
+        );
+
+}  // namespace lue
diff --git a/source/framework/python/CMakeLists.txt b/source/framework/python/CMakeLists.txt
index 1e2dc9dae..af125ac86 100644
--- a/source/framework/python/CMakeLists.txt
+++ b/source/framework/python/CMakeLists.txt
@@ -99,6 +99,7 @@ add_library(lue_py_framework SHARED
     src/algorithm/local_operation/logical_exclusive_or.cpp
     src/algorithm/local_operation/logical_inclusive_or.cpp
     src/algorithm/local_operation/logical_not.cpp
+    src/algorithm/local_operation/negate.cpp
     src/algorithm/local_operation/round.cpp
     src/algorithm/local_operation/unique_id.cpp
 
diff --git a/source/framework/python/include/lue/py/framework/algorithm/negate.hpp b/source/framework/python/include/lue/py/framework/algorithm/negate.hpp
new file mode 100644
index 000000000..42a9790d1
--- /dev/null
+++ b/source/framework/python/include/lue/py/framework/algorithm/negate.hpp
@@ -0,0 +1,13 @@
+#pragma once
+#include "lue/framework/algorithm/value_policies/negate.hpp"
+
+
+namespace lue::framework {
+
+    template<typename Element, Rank rank>
+    auto negate(PartitionedArray<Element, rank> const& array) -> PartitionedArray<Element, rank>
+    {
+        return value_policies::negate(array);
+    }
+
+}  // namespace lue::framework
diff --git a/source/framework/python/include/lue/py/framework/core/partitioned_array.hpp b/source/framework/python/include/lue/py/framework/core/partitioned_array.hpp
index 6c73154b7..f7303d05f 100644
--- a/source/framework/python/include/lue/py/framework/core/partitioned_array.hpp
+++ b/source/framework/python/include/lue/py/framework/core/partitioned_array.hpp
@@ -11,6 +11,7 @@
 #include "lue/py/framework/algorithm/logical_inclusive_or.hpp"
 #include "lue/py/framework/algorithm/logical_not.hpp"
 #include "lue/py/framework/algorithm/multiply.hpp"
+#include "lue/py/framework/algorithm/negate.hpp"
 #include "lue/py/framework/algorithm/not_equal_to.hpp"
 #include "lue/py/framework/algorithm/pow.hpp"
 #include "lue/py/framework/algorithm/subtract.hpp"
@@ -290,6 +291,12 @@ namespace lue::framework {
                 "__abs__",
                 [](Array const& argument) { return lfr::abs<ElementT<Array>, rank>(argument); },
                 pybind11::is_operator());
+
+            // -a
+            class_.def(
+                "__neg__",
+                [](Array const& argument) { return lfr::negate<ElementT<Array>, rank>(argument); },
+                pybind11::is_operator());
         }
 
         if constexpr (std::is_integral_v<Element>)
diff --git a/source/framework/python/src/algorithm/local_operation.cpp b/source/framework/python/src/algorithm/local_operation.cpp
index e94736df4..d8e0bf184 100644
--- a/source/framework/python/src/algorithm/local_operation.cpp
+++ b/source/framework/python/src/algorithm/local_operation.cpp
@@ -49,6 +49,7 @@ namespace lue::framework {
     void bind_logical_inclusive_or(pybind11::module& module);
     void bind_logical_not(pybind11::module& module);
     void bind_multiply(pybind11::module& module);
+    void bind_negate(pybind11::module& module);
     void bind_normal(pybind11::module& module);
     void bind_not_equal_to(pybind11::module& module);
     void bind_pow(pybind11::module& module);
@@ -111,6 +112,7 @@ namespace lue::framework {
         bind_logical_inclusive_or(module);
         bind_logical_not(module);
         bind_multiply(module);
+        bind_negate(module);
         bind_normal(module);
         bind_not_equal_to(module);
         bind_pow(module);
diff --git a/source/framework/python/src/algorithm/local_operation/negate.cpp b/source/framework/python/src/algorithm/local_operation/negate.cpp
new file mode 100644
index 000000000..a3ba62e26
--- /dev/null
+++ b/source/framework/python/src/algorithm/local_operation/negate.cpp
@@ -0,0 +1,15 @@
+#include "lue/py/framework/algorithm/negate.hpp"
+#include <pybind11/pybind11.h>
+
+
+namespace lue::framework {
+
+    void bind_negate(pybind11::module& module)
+    {
+        module.def("negate", negate<std::int32_t, 2>);
+        module.def("negate", negate<std::int64_t, 2>);
+        module.def("negate", negate<float, 2>);
+        module.def("negate", negate<double, 2>);
+    }
+
+}  // namespace lue::framework


Test patch:
diff --git a/source/framework/algorithm/test/CMakeLists.txt b/source/framework/algorithm/test/CMakeLists.txt
index e765c9b89..7341fd72e 100644
--- a/source/framework/algorithm/test/CMakeLists.txt
+++ b/source/framework/algorithm/test/CMakeLists.txt
@@ -54,6 +54,7 @@ set(local_operation_names
     logical_inclusive_or
     logical_not
     multiply
+    negate
     normal
     not_equal_to
     pow
diff --git a/source/framework/algorithm/test/negate_test.cpp b/source/framework/algorithm/test/negate_test.cpp
new file mode 100644
index 000000000..0818017a9
--- /dev/null
+++ b/source/framework/algorithm/test/negate_test.cpp
@@ -0,0 +1,49 @@
+#define BOOST_TEST_MODULE lue framework algorithm negate
+#include "lue/framework/algorithm/create_partitioned_array.hpp"
+#include "lue/framework/algorithm/default_policies/all.hpp"
+#include "lue/framework/algorithm/default_policies/equal_to.hpp"
+#include "lue/framework/algorithm/default_policies/negate.hpp"
+
+#include "lue/framework/algorithm/value_policies/all.hpp"
+#include "lue/framework/algorithm/value_policies/negate.hpp"
+#include "lue/framework/algorithm/value_policies/none.hpp"
+#include "lue/framework/algorithm/value_policies/valid.hpp"
+
+#include "lue/framework/test/array.hpp"
+#include "lue/framework/test/hpx_unit_test.hpp"
+#include <limits>
+
+
+namespace detail {
+
+    template<typename Element, std::size_t rank>
+    void test_array()
+    {
+        using namespace lue::default_policies;
+
+        using Array = lue::PartitionedArray<Element, rank>;
+
+        auto const array_shape{lue::Test<Array>::shape()};
+        auto const partition_shape{lue::Test<Array>::partition_shape()};
+
+        Array array{lue::create_partitioned_array(array_shape, partition_shape, Element{-5})};
+
+        BOOST_CHECK(all(negate(array) == Element{5}).get());
+        BOOST_CHECK(all(negate(negate(array)) == Element{-5}).get());
+    }
+
+}  // namespace detail
+
+
+#define TEST_CASE(rank, Element)                                                                             \
+                                                                                                             \
+    BOOST_AUTO_TEST_CASE(array_##rank##d_##Element)                                                          \
+    {                                                                                                        \
+        detail::test_array<Element, rank>();                                                                 \
+    }
+
+// TEST_CASE(1, double)
+TEST_CASE(2, int32_t)
+TEST_CASE(2, double)
+
+#undef TEST_CASE
diff --git a/source/framework/python/test/core/partitioned_array_test.py b/source/framework/python/test/core/partitioned_array_test.py
index 8e5e15edb..59b8f2bce 100644
--- a/source/framework/python/test/core/partitioned_array_test.py
+++ b/source/framework/python/test/core/partitioned_array_test.py
@@ -134,3 +134,8 @@ def test_operator_bool(self):
         array = self.array
         with self.assertRaises(ValueError):
             tmp = bool(array)
+
+    @lue_test.framework_test_case
+    def test_operator_negate(self):
+        array = self.array
+        tmp = -array