Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

linear sum input order #660

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open

linear sum input order #660

wants to merge 2 commits into from

Conversation

maggul
Copy link
Collaborator

@maggul maggul commented Feb 4, 2025

An N_VLinearSum input/output issue arises when N_VLinearCombination is not provided by the user or when N_VLinearSum follows a different path than the one followed by SUNDIALS. The output is allowed as an input only in the first component for external library compatibility.

To that end, we have changed the order of inputs if the output vector is in the second component.

N_VLinearSum(a, x, b, z, z) →N_VLinearSum(b, z, a, x, z)

@maggul maggul marked this pull request as ready for review February 4, 2025 20:20
@@ -2344,7 +2344,7 @@ int Test_N_VScaleAddMultiVectorArray(N_Vector V, sunindextype local_length,
{
for (j = 0; j < nsums; j++)
{
N_VLinearSum(c[j], X[k], ONE, Y[j][k], Z[j][k]);
N_VLinearSum(ONE, Y[j][k], c[j], X[k], Z[j][k]);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't see why this one was changed, since the output Z[j][k] is not one of the inputs.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I made changes whenever I saw (x, z, z). However, it might actually appear as (x, y, z), but y and z could be pointing to the same memory for some other reason. Since I didn’t have the chance to verify this everywhere* while adjusting the code, I wanted to at least reduce the likelihood of the second argument and the output being the same by replacing Y with X, which holds only one vector. If necessary, I can undo the changes and track each vector in the array to see if they ever become identical.

*After making the adjustment, I tested whether (x, z, z) was still hidden somewhere or remained unchanged by forcing "N_VLinearSum" to return without performing its operation and checking the results on DEV_TEST. Since none of the tests failed, this strongly indicates that we haven't returned from "N_VLinearSum" without executing its task.

@@ -6934,9 +6934,9 @@ static int IDASensTestError(IDAMem IDA_mem, sunrealtype ck, sunrealtype* err_k,
if (IDA_mem->ida_kk > 2)
{
/* Update error at order k-2 */
retval = N_VLinearSumVectorArray(IDA_mem->ida_Ns, ONE,
retval = N_VLinearSumVectorArray(IDA_mem->ida_Ns, ONE, tempv, ONE,
Copy link
Collaborator

Choose a reason for hiding this comment

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

So the issue was with both LinearSum and LinearSumVectorArray (since you adjusted the order for N_VLinearSumVectorArray both here and on line 7038)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Functions like N_VLinearSumVectorArray and several others use N_VLinearSum under the hood. For example, when nvec = 1, the execution directly enters N_VLinearSum. Therefore, this change was necessary to cover such cases.

@@ -584,7 +584,7 @@ SUNErrCode N_VScaleAddMulti(int nvec, sunrealtype* a, N_Vector x, N_Vector* Y,
{
for (i = 0; i < nvec; i++)
{
x->ops->nvlinearsum(a[i], x, SUN_RCONST(1.0), Y[i], Z[i]);
x->ops->nvlinearsum(SUN_RCONST(1.0), Y[i], a[i], x, Z[i]);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why did this one need to be modified (the output Z[i] is not one of the inputs)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Similarly, to reduce the probability. Again, I can revert it if necessary and run the DEV_TEST.

@@ -773,7 +773,7 @@ SUNErrCode N_VScaleAddMultiVectorArray(int nvec, int nsum, sunrealtype* a,
{
for (j = 0; j < nsum; j++)
{
X[0]->ops->nvlinearsum(a[j], X[i], SUN_RCONST(1.0), Y[j][i], Z[j][i]);
X[0]->ops->nvlinearsum(SUN_RCONST(1.0), Y[j][i], a[j], X[i], Z[j][i]);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why did this one need to be modified (the output Z[j][i] is not one of the inputs)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I made changes whenever I saw (x, z, z). However, it might actually appear as (x, y, z), but y and z could be pointing to the same memory for some other reason. Since I didn’t have the chance to verify this everywhere* while adjusting the code, I wanted to at least reduce the likelihood of the second argument and the output being the same by replacing Y with X, which holds only one vector. If necessary, I can undo the changes and track each vector in the array to see if they ever become identical.

*After making the adjustment, I tested whether (x, z, z) was still hidden somewhere or remained unchanged by forcing "N_VLinearSum" to return without performing its operation and checking the results on DEV_TEST. Since none of the tests failed, this strongly indicates that we haven't returned from "N_VLinearSum" without executing its task.

@@ -2756,7 +2756,7 @@ int Test_N_VLinearSumVectorArray(N_Vector V, sunindextype local_length, int myid
N_VConst(NEG_ONE, Y[2]);

start_time = get_time();
ierr = N_VLinearSumVectorArray(3, ONE, X, ONE, Y, Y);
ierr = N_VLinearSumVectorArray(3, ONE, Y, ONE, X, Y);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Again, was there also an issue with N_VLinearSumVectorArray (since you changed it in this file on this line, as well as lines 2802 and 2845)?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'll note that if there was also an issue with N_VLinearSumVectorArray then you should edit the documentation for that function in doc/shared/nvectors/NVector_Operations.rst to also mention the requirement that the output can equal only the first vector input. If there was not an issue with N_VLinearSumVectorArray then perhaps the edits to those calls in this PR should be reverted?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think it is really an issue since when nvec = 1, the execution directly enters N_VLinearSum. If we document this convention, then we should also be consistent with the order herein as well, even if no chance is necessary here since nvec = 3.

Copy link
Collaborator

@Steven-Roberts Steven-Roberts left a comment

Choose a reason for hiding this comment

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

You mentioned that some external libraries do not support certain orderings of inputs and output. Could you add a comment in the nvector implementations that have this constraint?

Also the changelog will need to be update.

@@ -204,7 +204,7 @@ operations below.
.. math::
z_i = a x_i + b y_i, \quad i=0,\ldots,n-1.

The output vector *z* can be the same as either of the input vectors (*x* or *y*).
The output vector *z* can be the same as the input vector (*x*).
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
The output vector *z* can be the same as the input vector (*x*).
The output vector *z* can be the same as the input vector *x* but not *y*.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@Steven-Roberts: Regarding your comment above, do you think we should add anything else beyond including but not *y* here? I'll also update the changelog to mention this modification. Is there anything else we should consider beyond these two changes?

Copy link
Collaborator

@Steven-Roberts Steven-Roberts Feb 5, 2025

Choose a reason for hiding this comment

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

Can you also add comments in the code indicating why this change was made. It's not entirely clear to me yet. You said external library compatibility, but what external library is incompatible with y==z? Is it one of the NVector implementations, e.g., LAPACK, RAJA, etc? If so, can you add a comment in the relevant operation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In SUNDIALS, the linear sum operation is defined as follows:
N_VLinearSum(a, x, b, z, z) := (z = a*x + b*z).

However, an external library might implement it differently:
EX_LinearSum(a, x, b, z, z) := (z = a*x, then z += b*z).

After the first step, the second component z no longer refers to the original input value, leading to incorrect results. To prevent this issue, we restrict the output variable z to be an input only in the first component. This way external libraries that follow the alternative approach can keep their existing linear sum.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks for explaining. That's clearer now. I suppose the external library could do some checks to avoid this, but it does seem simpler to just avoid it altogether. It offers some potential for optimization with restrict keyword too. An analogous restriction on SUNMatScaleAdd would be useful for a separate task I'm working on.

@@ -1418,7 +1418,7 @@ static int SolutionError(sunrealtype t, N_Vector u, N_Vector e, UserData* udata)
if (flag != 0) { return -1; }

// Compute absolute error
N_VLinearSum(ONE, u, -ONE, e, e);
N_VLinearSum(-ONE, e, ONE, u, e);
Copy link
Member

@balos1 balos1 Feb 6, 2025

Choose a reason for hiding this comment

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

To be clear, with the new assumption, the old code would still work fine with any existing N_Vector implementation (our own or someone else's), right?

That is, we are imposing a new assumption on how we call N_VLinearSum from our own code, but users could violate the assumption and things would still work.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In SUNDIALS implementations, this modification only changes the order of summation. Instead of computing:
Z[i] = a * X[i] + b * Z[i]
it will now compute:
Z[i] = b * Z[i] + a * X[i]
This change should not affect existing working implementations. Additionally, in its current state, using the output variable as the second input argument does not cause any issues, as long as users rely on N_VLinearSum or follow our approach of performing the linear sum in a single step (adding all terms at once).

Copy link
Collaborator

Choose a reason for hiding this comment

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

To answer @balos1: yes. All existing NVector implementations (either provided by SUNDIALS or user-supplied) will still work. All this PR does is relax one assumption made by how SUNDIALS uses N_VLinearSum, allowing new user-supplied NVectors to be constructed more easily than at present.

@@ -6,6 +6,10 @@

### New Features and Enhancements

The `N_VLinearSum` and `N_VLinearSumVectorArray` operations now assume that the output
can be used as input only in the first component, ensuring compatibility with external
Copy link
Member

Choose a reason for hiding this comment

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

This seems sort of like we are breaking the API, which would require a new major version. The reason I say "sort of" is because the existing vectors would still work since our change is in the assumption not the implementations. However, the change in the assumption now instructs us to create new vectors that would possibly not work with old code.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I disagree, since nothing about this PR breaks any existing/working code.

The only thing that would "break" is if a user creates an N_Vector that works with SUNDIALS v7.2.1, and then tries to use that same vector with an older version of SUNDIALS. If anything, the documentation update could add a .. versionchanged comment to note that previous SUNDIALS versions required that all vectors support the case where either input could also be the output.

Copy link
Member

Choose a reason for hiding this comment

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

I disagree, since nothing about this PR breaks any existing/working code.

That why I said it was "sort of" a breaking change. It changes the contract of the API, therefore pedantically speaking it is a breaking change to the API. However, in practice, its impact is more like a "new feature".

@drreynolds
Copy link
Collaborator

You mentioned that some external libraries do not support certain orderings of inputs and output. Could you add a comment in the nvector implementations that have this constraint?

Also the changelog will need to be update.

Mustafa was not referring to external libraries with NVector implementations that are included in SUNDIALS; this comment is relevant to user-supplied NVector interfaces to external libraries (in this case, Gkeyll). I do not see any need to add comments to any NVector implementations in the SUNDIALS repository, only to make the assumptions on the SUNDIALS end clear.

Copy link
Collaborator

@drreynolds drreynolds left a comment

Choose a reason for hiding this comment

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

Here are a few suggestions to resolve @balos1's concern about this PR being a kind-of breaking change.

@@ -204,7 +204,7 @@ operations below.
.. math::
z_i = a x_i + b y_i, \quad i=0,\ldots,n-1.

The output vector *z* can be the same as either of the input vectors (*x* or *y*).
The output vector *z* can be the same as the input vector *x* but not *y*.
Copy link
Collaborator

@drreynolds drreynolds Feb 6, 2025

Choose a reason for hiding this comment

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

Suggested change
The output vector *z* can be the same as the input vector *x* but not *y*.
The output vector *z* can be the same as the input vector *x* but not *y*.
.. versionchanged:: x.y.z
Prior versions of SUNDIALS assumed that the output vector *z* could be used as
**either** of the input vectors (*x* or *y*).

Copy link
Member

Choose a reason for hiding this comment

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

I think a user could easily interpret this statement as a breaking change. I.e., they may think "oh, to update to sundials X.Y.Z I need to go update my code now to ensure that z is not the same as y". I suggest:

Prior versions of SUNDIALS assumed that the output vector *z* could be used as  **either** of the input vectors (*x* or *y*). 
While all of the `N_Vector` implementations included with SUNDIALS still allow the output vector *z* to be the same as the input vector *x* or *y*, the API no longer requires it. 

@@ -606,6 +606,8 @@ of its mathematical operations below.
where *a* and *b* are scalars, :math:`x_j` and :math:`y_j` are
vectors in the vector arrays *X* and *Y* respectively, and
:math:`z_j` is a vector in the output vector array *Z*. The operation returns a :c:type:`SUNErrCode`.

The output vector array *Z* can be the same as the input vector array *X* but not *Y*.
Copy link
Collaborator

@drreynolds drreynolds Feb 6, 2025

Choose a reason for hiding this comment

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

Suggested change
The output vector array *Z* can be the same as the input vector array *X* but not *Y*.
The output vector array *Z* can be the same as the input vector array *X* but not *Y*.
.. versionchanged:: x.y.z
Prior versions of SUNDIALS assumed that the input vector :math:`x` could be the same
as one of the output vectors :math:`z_j`.

doc/shared/nvectors/NVector_Operations.rst Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants