Repository: fatiando/harmonica (python)

Issue:
Inverted sign in upward derivative filter
**Description of the problem:**
<!--
Please be as detailed as you can when describing an issue. The more information
we have, the easier it will be for us to track this down.
-->

The upward derivative returned has an inverted sign. It looks OK when inspecting plots because a lot of us are used to the sign convention of positive downwards. But since we use heights (and thus positive upward), the sign should be the opposite of what we expect. This is meant to improve consistency between small-scale and global data (which often has a "radial" component that is positive upward).

The fix should be relatively simple. Just add a minus sign to https://github.com/fatiando/harmonica/blob/main/harmonica/filters/_filters.py#L63

**Full code that generated the error**
<!--
Include any data files or inputs required to run the code. It really helps if
we can run the code on our own machines.
-->

```python
import harmonica as hm
import verde as vd
import numpy as np
import matplotlib.pyplot as plt

model = [
    [35e3, 40e3, 50e3, 55e3, -6e3, -1e3],
]
magnetization = [
    hm.magnetic_angles_to_vec(20, -40, 15),
]
inc, dec = -40, 15
fe, fn, fu = hm.magnetic_angles_to_vec(1, inc, dec)
region = [0, 100e3, 0, 80e3]
coordinates = vd.grid_coordinates(region, spacing=500, extra_coords=1000)

be, bn, bu = hm.prism_magnetic(coordinates, model, np.transpose(magnetization), field="b")
anomalia = fe * be + fn * bn + fu * bu

grid = vd.make_xarray_grid(coordinates, anomalia, data_names="tfa", extra_coords_names="upward")
grid["d_up"] = hm.derivative_upward(grid.tfa)

fig, axes = plt.subplots(1, 2, layout="constrained", figsize=(10, 3))
grid.tfa.plot(ax=axes[0])
grid.d_up.plot(ax=axes[1])
```

**Full error message**


This produces the following result:

![image](https://github.com/fatiando/harmonica/assets/290082/8ec8fbf2-b293-434c-96b1-d7f0d6036124)

If we use positive upward, then the positive parts of the anomaly should be decreasing towards 0 (negative derivative) and the negative parts of the anomaly should be increasing towards 0 (positive derivative).


**System information**

* Operating system: Linux
* Python installation (Anaconda, system, ETS): Miniforge
* Version of Python: 3.11
* Version of this package: current main ([bbaf5a1](https://github.com/fatiando/harmonica/commit/bbaf5a191c7a3ccf9835a2d3b565be26a3500d26))
* If using conda, paste the output of `conda list` below: Not using conda.

<details>
<summary>Output of conda list</summary>
<pre>

PASTE OUTPUT OF CONDA LIST HERE

</pre>
</details>


Pull request description:
Fix inverted sign in upward derivative filter
In order to apply the convention of positive upwards, this code:
1. Inverts signal of expected wavenumber at derivative_upward_kernel function;
2. Inverts signal of wavenumber at derivative_upward_kernel function;
3. Invert signal of upward derivative on test_transformations.py
<!--
Thank you for contributing a pull request to Fatiando! 💖

👆🏽 ABOVE: Describe the changes proposed and WHY you made them.

👇🏽 BELOW: Link to any relevant issue or pull request.

Please ensure you have taken a look at the CONTRIBUTING.md file 
in this repository (if available) and the general guidelines at 
https://github.com/fatiando/community/blob/main/CONTRIBUTING.md
-->

**Relevant issues/PRs:**
Fixes #454
<!--
Example: "Fixes #1234" / "See also #345" / "Relevant to #111"
Use keywords (e.g., Fixes, Closes) to create the links and automatically
close issues when this PR is merged. 
See https://github.com/blog/1506-closing-issues-via-pull-requests
-->


Patch:
diff --git a/harmonica/filters/_filters.py b/harmonica/filters/_filters.py
index defe9e8..a561cef 100644
--- a/harmonica/filters/_filters.py
+++ b/harmonica/filters/_filters.py
@@ -60,7 +60,7 @@ def derivative_upward_kernel(fft_grid, order=1):
     k_easting = 2 * np.pi * freq_easting
     k_northing = 2 * np.pi * freq_northing
     # Compute the filter for upward derivative in frequency domain
-    da_filter = np.sqrt(k_easting**2 + k_northing**2) ** order
+    da_filter = (-np.sqrt(k_easting**2 + k_northing**2)) ** order
     return da_filter
 
 


Test patch:
diff --git a/harmonica/tests/test_filters.py b/harmonica/tests/test_filters.py
index 035af9d..1a699fc 100644
--- a/harmonica/tests/test_filters.py
+++ b/harmonica/tests/test_filters.py
@@ -261,15 +261,14 @@ def test_derivative_upward_kernel(sample_fft_grid, order):
     """
     # Load pre-computed outcome
     expected = (
-        np.array(
+        -np.array(
             [
                 [0.00756596, 0.00565487, 0.00756596],
                 [0.00502655, 0.0, 0.00502655],
                 [0.00756596, 0.00565487, 0.00756596],
             ]
         )
-        ** order
-    )
+    ) ** order
     # Check if the filter returns the expected output
     npt.assert_allclose(
         expected, derivative_upward_kernel(sample_fft_grid, order=order), rtol=2e-6
diff --git a/harmonica/tests/test_transformations.py b/harmonica/tests/test_transformations.py
index 16c5b84..78d4592 100644
--- a/harmonica/tests/test_transformations.py
+++ b/harmonica/tests/test_transformations.py
@@ -260,17 +260,20 @@ def test_derivative_upward(sample_potential, sample_g_z):
     # Calculate upward derivative and unpad it
     derivative = derivative_upward(potential_padded)
     derivative = xrft.unpad(derivative, pad_width)
-    # Compare against g_z (trim the borders to ignore boundary effects)
+    # Compare against g_up (trim the borders to ignore boundary effects)
     trim = 6
     derivative = derivative[trim:-trim, trim:-trim]
-    g_z = sample_g_z[trim:-trim, trim:-trim] * 1e-5  # convert to SI units
-    rms = root_mean_square_error(derivative, g_z)
-    assert rms / np.abs(g_z).max() < 0.015
+    g_up = -sample_g_z[trim:-trim, trim:-trim] * 1e-5  # convert to SI units
+    rms = root_mean_square_error(derivative, g_up)
+    assert rms / np.abs(g_up).max() < 0.015
 
 
 def test_derivative_upward_order2(sample_potential, sample_g_zz):
     """
     Test higher order of derivative_upward function against the sample grid
+
+    Note: We omit the minus sign here because the second derivative is positive
+    for both downward (negative) and upward (positive) derivatives.
     """
     # Pad the potential field grid to improve accuracy
     pad_width = {