Complex Coercion

One way to define a complex(w) param or const is

param b = 10.5 + 6.23i;

But that only works if one is using 64-bit floating point. For those of us who try and avoid the double precision sledgehammer and use 32-bit floating point or those who want to soon use real(16) floating point arithmetic and at some stage in the future real(128), it gets messier.

param b0 = (10.5 + 6.23i):complex(64);
param b1 = 10.5:real(16) + (6.23i):imag(16);
param b2 = (10.5 + 6.23i):complex(256);

And where one is using identifiers, the use of the imaginary unit i is not feasible

Yes, you can use a coercion like

const c0 = (x, y):complex(64);

But that won't work with a param. And when you want to make that generic:

const c1 = (x, y):complex(numBits(x.type)*2));

This is really messy, ugly, unreadable, and confusing. You get the drift.

Also, at one stage @bradcray mentioned there were some issues with a type.

And that is a bit weird to people trying to port existing C/C++ or Fortran code which has loads of CMPLX, CMPLXF, and CMPLX* in C, cmplx() and dcmplx() and other things in Fortran, and a plethora of ways in C++.

Consider

inline proc cmplx(param x : real(?w), param y : real(w)) param
{
    return x + y:imag(w);
}
inline proc cmplx(const x : real(?w), const y : real(w)) const
{
    return x + y:imag(w);
}
inline proc cmplx(param x : real(?w), param y : imag(w)) param
{
    return x + y;
}
inline proc cmplx(const x : real(?w), const y : imag(w)) const
{
    return x + y;
}
inline proc cmplx(param x : imag(?w), param y : real(w)) param
{
    compilerWarning("cmplx: mismatch ordering of param arguments");
    return y + x;
}
inline proc cmplx(const x : imag(?w), const y : real(w)) const
{
    compilerWarning("cmplx: mismatch ordering of const arguments");
    return y + x;
}
inline proc cmplx(param x : real(?w)) param
{
    return x + 0:imag(w);
}
inline proc cmplx(const x : real(?w)) const
{
    return x + 0:imag(w);
}
inline proc cmplx(param y : imag(?w)) param
{
    return 0:real(w) + y;
}
inline proc cmplx(const y : imag(?w)) const
{
    return 0:real(w) + y;
}

No need to remember types, param and const data are handled consistency. Easy to read. Low overhead. You can even have an extra overlead to catch the case of where the imaginary and real components have different precisions.

This ought to work, but doesn't today, for implementation reasons (namely: we don't have a way of representing param tuples within the compiler).

FWIW your suggestion of cmplx procedures seems reasonable to me & I think it is a good candidate for addition to the Math module since you find it useful.

I generally like the idea of these convenience functions, but if they were
to end up in a standard module, I would personally prefer to stylize them
as 'new complex(1.2, 3.4)' rather than 'cmplx()', mostly to avoid having
two things 'complex' and 'cmplx') that look very similar but have
different roles. A more self-descriptive name like 'createComplex(1.2,
3.4)' would also do the trick for me (and a user could rename it to
something like 'cmplx' on importing/using the module if desired).

Unfortunately for my preferred approach, the compiler prevents us from
defining initializers on 'complex' today, but it may not take much worse
to loosen that restriction.

Damian, I'd be curious if the extra characters in either solution felt
off-putting to you (and how much).

-Brad

Thanks for the feedback Brad.

I used cmplx() because

a) It followed non-controversial industry proven accepted practice

- nobody had to learn any new concepts
- it was written the way it was done in Fortran and C and C++
- existing practice showed no risk of confusion with the type name
- the subtle name difference with the type reflected their roles
-- close enough to be remembered but different enough to be very clear

b) It was better than Fortran or C or C++ - clock one up for Chapel!!

- it was truly generic
-- no cmplx/dcmplx like Fortran for single/double precision
-- no CMPLXF/CMPLX like C for single/double precision
- it was better than C++ which was a bit mixed up

c) The ease with which the need was satisfied in Chapel was obvious

- it highlighted the flexibility of the language
- it was another case where the choice of Chapel was vindicated

d) It was superior to (x, y):complex(w) because

- you did not have to remember the precise type
- the type was inherited from the context of its arguments
- I thought tuples at the time had a high overhead

e) It required no changes to Chapel

- we did not have to impose on the Chapel team (for which I feel guilty)
-- it was after a time where I had asked for fixes to real(32)
-- it was while I contemplated asking for a transmute feature
--- as it transpired - I waited a long while before I pushed that harder

f) Nobody onto whom I was pushing this complained. Always a good sign.

I originally only used it as a const routine. I was pleasantly surprised much later when my approach worked very nicely when it came to a param. This further vindicated my approach.

I still get confused as to when I need to use new in Chapel. Any time I do not need to use new is a huge bonus. I hated using it in C++ too.

The cmplx() intrinsic of Fortran and CMPLX of C has been saying 'create a complex instance' for 50+ and 25+ years respectively. Why use a new name?

Forcing people to import 'createComplex' with the name 'cmplx' is just complicating the experience. Why rock the boat?

The 'createComplex' approach is different to long established practice. It means learning something new for a user or re-educating programmers for managers. I cannot see a programmer preferring a new 13-character name against using a 5-character name which has become second nature.

The 13 letters of createComplex dominates any mathematical expression in which it appears. This long word with a capital letter detracts from

a) the clarity/tautness of the expression within the Chapel code, and
b) the ability of the Chapel language to reflect the actual mathematics

This makes it harder to debug for Chapel users and confusing to read by non-Chapel users trying to learn the code and those verifying/QA'ing the code for mathematical correctness

Algol68 has the dyadic operator I, i.e. uppercase 'i', which for real x or y, allows you to to create a complex number anywhere and with ease as

       x  I  y

But it is obtuse, looks nothing like a complex number in Mathematics, and is nothing like accepted practice. So I would object to this Algl68 feature on similar grounds to your idea of createComplex. Mind you, once people got used to it, they loved using the operator I. Weaning them off it during a move to C++ was tough!!

Note that our usage of complex numbers is only for things like the dynamic behavior of structures and my debugging complex linear algebra routines for clients. We need feedback from people like electrical engineers who eat, sleep and drink this stuff.

Thanks for listening.