Element-wise array comparison

Hello everyone,
We are trying to implement a function similar to numpy.maximum for our project. It is supposed to take in two arrays and return an array containing the element-wise maxima from the arrays.
Here is a simple function that does the work.

proc np_maximum(A:[?d1], B:[?d2]) {
/*
    	Array element-wise comparison
    	Returns an array containing the maximum elements from the 2 arrays
        	Parameters
        	----------
        	A: array_like
            		Input array-1
        	B: array_like
            		Input array-2
*/
	if(A.domain != B.domain) {
		writeln("The arrays' sizes do not match !!");
		exit();
	}
	var res: [1..A.shape(1), 1..A.shape(2)] real;
	forall (i,j) in A.domain do {
		res(i,j) = if A(i,j) > B(i,j) then A(i,j) else B(i,j);
	}
	return res;
}

Is there any inbuilt method to do the element-wise comparisons or a more efficient way to implement the same thing ?
Here's a link to the example - https://tio.run/##7VRNj9MwED3Hv2JaCdGUtCUBLg0BJUIgLqtVtbdVtTKt03o3sSvbpQtof3sZ202algUhpL3hS@z5eJ6Z9@LFmm5Ytd9vNYMZFUtZpzCZwEepQLkjiG39hSlYMcEUNVwKQhZSlHwF@NEGNGNLyOBV/Dp@43I9TCfBhZwl8e/MJqWAC3Ou1szbZAmMLtZ4WLqDQQdVin4j5BCWfICdVHfeqslXqiCfwnU8HluACA6bOShGq9T5iz/4Scmrypc8yCNXagS0WknFzbrOZhefxheXRZiSHRpYJQb9HLieQj9MoTHlHXdoIaWiVQUDHt2GwAUUY0SnuFlKEvBywJ8lkGXwMoQfUPioDKNf3CLmAwlYhWR0PKMYhkd3W0fxSx3FWR0bJRcgNjc1vef1FvubXr9fxvMIB4KbZI73k8mQWA5yO01gFauZMKMdxwoWst5QxTUybiNmzGyV0ECFH71l0mBTXKwcS4dLGgwNpUIVWE/ScAWHdUkVrZlh6mgatas1Iasu76bid6y12vVZbLbGO0dx6yn@Kj4hw4kjIW9I6WUtQXYgQdBO@KoRn37u1KmRQBDSYK8GNdrr4fAxnt1zY@cdIHdWboppL7h8rO3vNYhDJ7vmmISN@IJzpeRHpbhSEKpRAS8h9/t3jTRwuKIxOtF4u69EOb5sMSl56GgBGXt6LfhL/mvhybXw9h@0cNLT478tvoQFtsum/c6T0n1JoiI8fWxOQR/l/7egjSQd6H7/Ew

Hi j9sh —

This is the type of operation that Chapel is pretty good at doing without a special routine, where my first thought surprised me by not working (*), and my second thought was to write:

var Max = [(a, b) in zip(A, B)] max(a, b);

(see full example on TIO)

A few things to note about your approach:

  • the A.domain != B.domain check will check that the two arrays have the same indices rather than just the same shape. You may want to compare A.shape and B.shape if you're interested in permitting an array with indices 0..<n to compare to one whose indices were 1..n or 1..2*n by 2.
  • a lot of your code will permit it to only apply to 2D arrays. You could probably make this a rank-neutral routine fairly easily using changes like:
    • var res: [A.domain] real; — make res share A's domain whatever rank it is
    • forall idx in A.domain do ... A(idx)...B(idx) — use a single-variable tuple index idx rather than constraining it to be a 2D tuple

Some advantages of taking your approach (in a rank-neutral manner) over mine is that Chapel has a long-term bug with some zippered forall loops in which, if the sizes don't match, the extra iterations are dropped on the floor: zippered forall loops with size mismatches can silently drop iterations on the floor · Issue #11428 · chapel-lang/chapel · GitHub And even if you get an error due to size mismatch, it'll just halt your program in a vague way, whereas your error message is more specific to this case (you could also throw an error if desired).

A few style notes:

  • writeln(msg) + exit is essentially equivalent to halt(msg) in Chapel. A conditional + halt is equivalent to assert(test, msg)
  • the do { in your forall loop is fine, but redundant. When you use { ... }, there's no need for do. Or when your forall loop's body is a single statement, you can simply use do (the reason both can be used together is that { ... } is a single statement even though it contains multiple statements.

-Brad

  • = My first thought was just to write var Max = max(a, b); but this didn't work as I was expecting; it would for most scalar routines, but something about the overloads we've provided for max() don't allow it to. That seems like something we should fix.
1 Like

Hello Brad,
Thank you for the reply. It was really helpful and I was also able to make the functions rank-independent. And also thank you for the style notes, I'll keep them in mind.

1 Like

Glad to hear it! In the meantime, I've opened an issue about supporting min/max on arrays directly here: We should be able to apply min/max to arrays and get an array result, right? · Issue #19293 · chapel-lang/chapel · GitHub and a proposed fix enabling it here: Manually disable catch-all min()/max() overloads for arrays by bradcray · Pull Request #19288 · chapel-lang/chapel · GitHub

-Brad

1 Like

As a follow-up, we merged my PR early last week, so writing var M = max(A, B); will work in this month's 1.26.0 release. Writing a wrapper routine as you did may still be beneficial in terms of generating better error messages if there are size/shape/rank mismatches or the like, but I'm happy that this pattern which I thought should work will soon (and does on main today).

-Brad