chapel-lang / chapel

a Productive Parallel Programming Language
https://chapel-lang.org
Other
1.78k stars 420 forks source link

Math module - should we rename conjg? #19010

Closed lydia-duncan closed 1 year ago

lydia-duncan commented 2 years ago

I think conjugate is sufficiently short that it would be okay to use the full word. For context, this name already deviates from C, which uses conj. That would also be a reasonable alternative, though I lean towards conjugate.

Vote: 🎉 - keep as is 🚀 - rename to conj ❤️ - rename to conjugate (comment for other options and I'll add them)

damianmoz commented 2 years ago

The name conjg() has been around for at least 50 years and comes from Fortran. It might even be 60 years. Long before my time or the time of anybody I know. I have no idea why C changed the name, well actually it was Stroustrup with C++, especially as C++ came to the complex number party a lot latter than Fortran, and C later still.

I vote to keep as is. It would make porting old code more onerous if it was anything else.

bradcray commented 2 years ago

When I see conjg(), my head guesses "conjugate gradient" incorrectly, unfortunately. In addition, given that we generally followed C math.h, I'm not certain how we ended up with conjg() over conj().

If I'm searching correctly, it looks like Python and Julia also use conj() and that Swift uses conjugate()?

While I hate going against Damian on things like this, left to my own devices based on these observations, I'd probably go with conj() or possibly conjugate() to reflect the more modern languages and avoid the mis-interpretation my mind jumps to.

damianmoz commented 2 years ago

I am super impressed that you know about conjugate gradient (methods). Wow!

Anyway, my email was throwing up names for discussion. I actually agree with conj().

When I started that floating point manipulation stuff, I sort of fell in love with making things a method of a type. even going overboard and doing things like

assert(x.abs == abs(x)):

In the end I had methods like those below which operated on a complex expression.

inline proc (complex(?w)).cmplx(p : real(?w), q : real(w)) // plus variants with different parameter mixes
inline proc (complex(?w)).tuple return (this.re, this.im); // which I found was a poor performer
inline proc (complex(?w)).mul(t : real(w)) return (this.re *t, this.im * t):complex(w); 
inline proc (complex(?w)).mod return cabs(this); // mod - See ISO-80000-2:2019
inline proc (complex(?w)).arg : real (w/2) return atan2(this.im, this.re); // arg] See ISO-80000-2:2019
inline proc (complex(?w)).conj return (this.re, -this.im):complex(w);
inline proc (complex(?w)).proj // see my latest posting on Discourse for code

Because they were objects of a type, the data dictionary of names was very small so I had no conflict.

We tried with methods called conjugate and projection and modulus and argument and plugged them into a few formulae we had, and we ended up with an unreadable mess in many of those formulae which were in any way complicated. There was not a lot of enthusiasm for long names from others. My own experience with complex numbers is in things like fast Fourier transforms transforms, linear algebra with complex matrices, integration of complex functions, and some other stuff where I work in a complex space for simplicity of expression and readability. Some colleagues (from whom I pinched some formula) worked in other areas including geophysics and engineering. I have not asked the chemist in my circle for his comments on complex as he is busy with other stuff. The statisticians didn't work with complex. And the physicists didn't reply

And that is my own, and some others, 2c.

P.S. Strictly speaking, cproj() in Math.chpl has a bug if you want to be IEEE 754 strict or if you are trying to port existing C/C++ routines.

bradcray commented 2 years ago

I am super impressed that you know about conjugate gradient (methods). Wow!

Credit the NAS Parallel Benchmarks with that one...

I actually agree with conj().

:+1:

ghbrown commented 2 years ago

I like conj() as well, in case people were at an impasse or unsure, etc.

lydia-duncan commented 1 year ago

Summary in prep for an upcoming ad-hoc subteam discussion on this issue.

1. What is the API being presented?

  /* Returns the complex conjugate of the complex argument `z`.

     :rtype: A complex number of the same type as `z`.
  */
  inline proc conjg(z: complex(?w)) { … }

  /* Returns the complex conjugate of the imaginary argument `z`.

     :rtype: An imaginary number of the same type as `z`.
  */
  inline proc conjg(z: imag(?w)) { … }

  /* Returns the argument `z`.

     :rtype: A number that is not complex or imaginary of the same type as `z`.
  */
  inline proc conjg(z: int(?w)) { … }
  inline proc conjg(z: uint(?w)) { … }
  inline proc conjg(z: real(?w)) { … }

It should explicitly declare its return type.

The argument name will be (will have been) considered in another discussion. This discussion will be focused on the function name

How is it intended to be used?

It is mostly used in math involving complex numbers. See https://en.wikipedia.org/wiki/Complex_conjugate

How is it being used in Arkouda and CHAMPS?

Not used in arkouda. 1 use in CHAMPS, common/src/eigenValuesSolvers.chpl on line 772

2. What's the history of the feature, if it already exists?


This function was added prior to many language features, such as the inline and proc keywords. It was initially added in an ancient “_chpl_complex” module in May 2006, which would get folded into the Math module later that year.

Documentation was added in 2015.

It was modified in 2016 when additional complex math functions were provided so that it wrapped the C99 versions instead of implementing it ourselves. It was called out at the time as the only one with a different name than its C counterpart, as a result of having already existed using that name (I think).

There have been a lot of fiddly changes over the years, mostly due to promotion preventing some clean ups. The non-complex overloads were added in 2017 after one of these attempts.

3. What's the precedent in other languages, if they support it? As relevant, we especially care about:

a. Python

Python provides the conjugate() abstract method on complex numbers (https://docs.python.org/3/library/numbers.html#numbers.Complex.conjugate), while numpy uses conj() (https://numpy.org/doc/stable/reference/generated/numpy.conj.html)

b. C/C++

In complex.h, C/C++ provides conj(), with additional versions for different sizes of complex (conjf and conjl) (https://en.cppreference.com/w/c/numeric/complex/conj)

c. Rust

Rust doesn’t provide it in its standard libraries. The argmin crate provides a conjugategradient module with a ConjugateGradient struct in it with some methods (https://docs.rs/argmin/0.8.1/argmin/solver/conjugategradient/struct.ConjugateGradient.html), but conjugate gradients are different from the complex conjugate.

d. Swift

Swift provides a conjugate function (https://developer.apple.com/documentation/accelerate/vdsp/3240867-conjugate)

e. Julia

Julia provides a conj function (https://docs.julialang.org/en/v1/base/math/#Base.conj)

f. Go

In the cmplx submodule of the math module, Go provides Conj (https://pkg.go.dev/math/cmplx@go1.20.6#Conj)

4. Are there known Github issues with the feature?


5. Are there features it is related to? What impact would changing or adding this feature have on those other features?


It’s related to complex numbers, as well as the carg and cproj functions. Renaming it will not impact those features.

6. How do you propose to solve the problem?


A. Leave as is

B. rename to conj

C. rename to conjugate

I think we should rename it to conj. Users in this thread have already expressed support for this option.

Assuming it doesn't mess with promotion, I also think we should make the return types explicit.

lydia-duncan commented 1 year ago

In our ad-hoc subteam, we decided to rename the function to conj

Some things that came up when considering this decision:

There was some support for the longer name, but stronger support for conj

damianmoz commented 1 year ago

On conj(), wise move. I thought that this issue was only about the conjugate value.

On those other two, I cannot see why you do not exploit the benefit of a Chapel signature.

There is unlikely to be many other projections associated with a single complex number all by its lonesome except the one onto a Riemann sphere. So

inline proc proj(z : complex(?w))
{
   // keep the internal code as is
}

seems pretty safe. C routines do not have the luxury of burying the argument types into the name.

I could say the same thing about a routine called argument of a complex number. It could be done in pure Chapel.

inline proc arg(z : complex(?w)) do return atan2(z.im, z.re);

That said, I do not know my simple attempt has any ramification for code generation on a GPU.

My 2c.

bradcray commented 1 year ago

@damianmoz : It sounds like your position has changed since posting the following comments, is that right?

@lydia-duncan / @jabraham17 : I'm guessing that carg/cproj were not really discussed in this meeting beyond Jade asking the question, is that right?

lydia-duncan commented 1 year ago

Yeah, we only brought them up as related to the conjg function. That said, since Damian's comment here I have started looking into prep to potentially add them to the set of topics in the Math bundle for next sprint.

damianmoz commented 1 year ago

Yes - The consensus of the small group with whom I was working has changed.

When I first raised the whole naming thing with the others, and the complex intrinsics in particular, it was known that both conjg() and carg() were riddled through existing code. The former was a direct legacy from Fortran days and we had forgotten that. A lot of us moved from Fortran to C or C++ in the 80s and 90s. The name conjg() was even reflected in internal documentation of the mathematics. It was also realized that a C at the start of an intrinsic function dates back forever, being an attempt to provide a sort of signature-in-the-name for complex intrinsics. Chapel (and C++) already provide a genuine signature without impacting on the name.

We took the time (last year) to change the name in a few programs from conjg to conj() which is also the long standardized C-library name. The task was a lot less painless than I thought. No clashes popped up. But it was tedious. It was known that conjg was used a lot but had not realized how many times it had been used in several of these codes.

Also, we now have an automatic abbreviation tool of names (and the inverse deduction of the original name from a limited dictionary of topic specific largely English words). Out of that, names like proj(), arg() and conj() just pop out, all being the root of a word (either 4 or 3 letters long).

There is still the argument to retain the name carg() because the name/word argument is so ubiquitous in mathematics that it needs to have some differentiating feature that is visually impactful. I prefer the NIST's approach to the name of the phase of a complex angle but that is another story

Long names like conjugate() and projection() when embedded in equations make those equations pretty unreadable which is very undesirable.