Skip to content

Conversation

lucascolley
Copy link
Member

Reference issue

Closes gh-20994

What does this implement/fix?

A regression was introduced for complex input when moving from np.sqrt to math.sqrt. This changes to cmath.sqrt.

Additional information

I had to include extra handling where the imaginary part is zero - not sure if this is the right way to do things.

use `cmath` instead of `math` for `sqrt`
[skip cirrus] [skip circle]
@github-actions github-actions bot added scipy.spatial defect A clear bug or issue that prevents SciPy from being installed or used as expected labels Jun 20, 2024
@lucascolley lucascolley added this to the 1.15.0 milestone Jun 20, 2024
@lucascolley lucascolley requested a review from dschmitz89 June 20, 2024 11:14
Copy link
Contributor

@dschmitz89 dschmitz89 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I also confirmed that performane is still improved compared to numpy.
I will still leave this for review by spatial people as that is not my territory. I wonder if these distances were actually meant for complex input for example.

image

@fancidev
Copy link
Contributor

fancidev commented Jun 21, 2024

I find this post that discusses cosine distance for complex vector. The author suggests something equivalent to

1 - np.dot(u,v)/(np.linalg.norm(u)*np.linalg.norm(v))

This definition appears to agree with the doc. In addition, for the input of the test case, this expression gives the same answer as WolframAlpha linked in the referenced issue. The test case gives a different answer.

So it appears to me math.sqrt is ok, but instead of doing np.dot for the denominator the code should do np.vdot or equivalent. But keep np.dot for the numerator.

I guess the original cosine distance code was meant for real vector only. It happened to give an answer for complex vector, but that might not be the expected answer (if we expect an answer equal to WolframAlpha’s).

For this reason, I suppose the referenced issue is more an “enhancement” than a “bug”.

@WarrenWeckesser
Copy link
Member

WarrenWeckesser commented Jun 21, 2024

To handle complex input in both the cosine and correlation distances, the complex dot product must be used. Otherwise distance.cosine(u, u) is not 0 whenever u has a nonzero imaginary part. I don't know why Wolfram Alpha appears to not do this; perhaps they also intended the function for real vectors only.

@fancidev
Copy link
Contributor

fancidev commented Jun 21, 2024

In addition, even with complex inner product, subtracting the result from one seems unnatural to me. So is the last line that np.clip the result between 0 and 2. In short, they all appear to be designed for real input.

@WarrenWeckesser
Copy link
Member

Indeed, what is the point of a distance function returning a complex number? If we were implementing functions that must be mathematical metrics, then we would have to ensure that the functions returned real values. We don't actually require that the distances be metrics, but it seems reasonable to still require that values returned are real. Things like premetrics and semimetrics (that drop some axioms required of a metric) still require that the distance is a real value.

I think the two options that we have here are to simply not accept complex vectors (i.e. raise an error on complex input), or implement something that returns a real value. That "something" should not be something we invent ad hoc, but a formula that has some mathematical justification.

@lucascolley
Copy link
Member Author

Thanks Warren. We recently deprecated complex input for some of the interpolators. @h-vetinari @j-bowhay do you think a similar strategy sounds reasonable here?

@fancidev
Copy link
Contributor

fancidev commented Jun 23, 2024

That "something" should not be something we invent ad hoc, but a formula that has some mathematical justification.

I found another MathOverflow post that discusses about the angle between complex vectors. One of the answers also mentions a review paper of various possible definitions.

My take-away from these materials is that if a real result is expected, there appears to be only one option, which is to treat vectors in $\mathbb{C}^n$ as vectors in $\mathbb{R}^{2n}$ (in the obvious manner) and compute the usual cosine angle using this formula.

If the result is allowed to be complex, then there are a few more possibilities, each finding themselves useful in some places.

Btw, the reference issue is about distance.cosine but the PR updates both distance.cosine and distance.correlation. These two are not equivalent because it might be expected that the correlation between two complex random variables is complex. (Though a complex result might not be what is expected from a distance function in spatial.)

@lucascolley
Copy link
Member Author

Btw, this PR is about distance.cosine but the title is distance.correlation. It might be expected that the correlation between two a complex random variables is complex, but that again might not be what is expected by a distance function in spatial.

The errors observed in cosine are due to the mishandling of complex values in correlation - the implementation of cosine(u, v, w) is

correlation(u, v, w=w, centered=False)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
defect A clear bug or issue that prevents SciPy from being installed or used as expected scipy.spatial
Projects
None yet
Development

Successfully merging this pull request may close these issues.

BUG: spatial.distance.cosine with complex arguments raises ValueError
4 participants