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

JP-2096: Cube build stage3 #6093

Merged
merged 25 commits into from
Jul 29, 2021

Conversation

jemorrison
Copy link
Collaborator

@jemorrison jemorrison commented Jun 2, 2021

Description

Speeding up cube_build using numba. Numba was installed using 'pip'
The purpose of this PR is to speed up cube_build using numba. Some of the modules in ifu_cube.py were moved out
of the class and made independent routines. Some of these routines were broken up into simpler routines. In this process the weighting by the Miri psi option was removed. It is not being used and it was getting cumbersome to it as an option. Removing Miri ps weighting as an option also means that the resolution reference files is not longer needed.

Including this PR in the JW pipeline requires numba to be added. But so far there seems little down side to including numba. It is fast, stable and easy to use.

Closes #6064
Fixes JP-2096

@jemorrison
Copy link
Collaborator Author

PR ready for review.

@jemorrison jemorrison requested review from jdavies-st and nden June 8, 2021 16:21
@jemorrison
Copy link
Collaborator Author

See information on JP-2096 on numba and speed tests D Law has run with the code.

@jemorrison
Copy link
Collaborator Author

Once it is approved that numba can be used in JWST Pipeline I can make further speed improvements in the blotting routines and have them use numba. I am going to hold off on that until it is confirmed numba is fine to use the jwst pipeline

@hbushouse hbushouse added this to the Build 7.9 milestone Jun 8, 2021
@codecov
Copy link

codecov bot commented Jun 11, 2021

Codecov Report

Merging #6093 (b173565) into master (d123c28) will decrease coverage by 1.13%.
The diff coverage is 39.72%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #6093      +/-   ##
==========================================
- Coverage   77.69%   76.56%   -1.14%     
==========================================
  Files         402      404       +2     
  Lines       34412    35259     +847     
==========================================
+ Hits        26736    26995     +259     
- Misses       7676     8264     +588     
Flag Coverage Δ *Carryforward flag
nightly 77.69% <71.26%> (ø) Carriedforward from d123c28
unit 56.07% <15.47%> (?)

*This pull request uses carry forward flags. Click here to find out more.

Impacted Files Coverage Δ
jwst/cube_build/cube_build.py 84.06% <ø> (+6.29%) ⬆️
setup.py 0.00% <ø> (ø)
jwst/cube_build/cube_internal_cal.py 7.69% <7.69%> (ø)
jwst/cube_build/blot_cube.py 22.72% <22.72%> (ø)
jwst/cube_build/ifu_cube.py 60.36% <44.80%> (-12.40%) ⬇️
jwst/cube_build/cube_build_wcs_util.py 28.39% <77.77%> (-3.49%) ⬇️
jwst/cube_build/blot_cube_build.py 76.10% <83.33%> (-23.90%) ⬇️
jwst/cube_build/cube_build_step.py 59.21% <100.00%> (-7.60%) ⬇️
... and 13 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d123c28...b173565. Read the comment docs.

@hbushouse
Copy link
Collaborator

@jdavies-st or @eslavich any ideas what's killing the CI test "CI/Installed package with --pyargs"?

@eslavich
Copy link
Collaborator

It looks like jwst/assign_wcs/tests/test_niriss.py::test_niriss_wfss_available_frames failed due to a reference file that didn't download correctly, which in turn caused the dreaded "cascade of open files errors". I suspect if we rerun the CI jobs they'll pass, I'll do that now.

@jdavies-st
Copy link
Collaborator

If you search the log for "FAILURES" you can see it is having trouble asdf.open() on a downloaded CRDS NIRISS WFSS reference file.

@jdavies-st
Copy link
Collaborator

We should be opening those reffiles in a cleaner way, i.e. with a with context manager so that if they fail to load, we don't get a cascade of errors.

@jemorrison
Copy link
Collaborator Author

@jdavies-st @nden if you have time now could you look this over or suggest someone else to look at it.
It is using numba to speed up cube_build

@jemorrison jemorrison requested review from mcara, drlaw1558 and hbushouse and removed request for jdavies-st July 19, 2021 19:34
@jemorrison
Copy link
Collaborator Author

@hbushouse @nden @mcara
This PR is ready for review. This PR pulled out the computational python modules and converted them to c routines. There are now two c extensions 1. creating IFU cubes on sky 2. creating IFU cubes in detector plane.
I made a common set of c functions that both c extensions call in cube_utilts.c
As noted above in the comments - an unused weighting function - miripsf - was removed as an option.
The unit tests run and the regression tests also run (with a few differences due to improvements I made when creating c routines).

@drlaw1558
Copy link
Collaborator

Following the usual means of checking out a PR, I'm getting errors about being unable to find the C modules. E.g.,
from .cube_match_sky import cube_wrapper
returns
No module named jwst.cube_build.cube_match_sky

I'm probably just doing something wrong- do I need to do anything special to compile the code inside the src/ directory first or should python do that automatically?

@jemorrison
Copy link
Collaborator Author

To compile the c code. In the top jwst directory - the directory with setup.py
Type pip install -e .
That compiles the c code that is defined in setup.py
Let me you if you have problems with that

Comment on lines +32 to +33
index_x = np.where(xdistance <= roi_det)
index_y = np.where(ydistance <= roi_det)
Copy link
Member

Choose a reason for hiding this comment

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

I am not familiar with the algorithm, but it seems like index_x and index_y, in principle, could have different lengths or point to different "pixels". Would, this be an issue? Especially different lengths in the code just below.

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 am going to hold off making changes to blot_cube.py because I have another JP ticket to work just on blot cube after I get the c extensions in this PR committed. I will come back to these changes suggestions later this week.

Comment on lines +36 to +43

d1pix = x_cube[ipt] - xcenter[index_x]
d2pix = y_cube[ipt] - ycenter[index_y]

dxy = [(dx * dx + dy * dy) for dy in d2pix for dx in d1pix]
dxy = np.array(dxy)
dxy = np.sqrt(dxy)
weight_distance = np.exp(-dxy)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
d1pix = x_cube[ipt] - xcenter[index_x]
d2pix = y_cube[ipt] - ycenter[index_y]
dxy = [(dx * dx + dy * dy) for dy in d2pix for dx in d1pix]
dxy = np.array(dxy)
dxy = np.sqrt(dxy)
weight_distance = np.exp(-dxy)
weight_distance = np.exp(-np.sqrt(np.add.outer(
np.square(x_cube[ipt] - xcenter[index_x]),
np.square(y_cube[ipt] - ycenter[index_y])
).ravel()))

or, alternatively:

Suggested change
d1pix = x_cube[ipt] - xcenter[index_x]
d2pix = y_cube[ipt] - ycenter[index_y]
dxy = [(dx * dx + dy * dy) for dy in d2pix for dx in d1pix]
dxy = np.array(dxy)
dxy = np.sqrt(dxy)
weight_distance = np.exp(-dxy)
weight_distance = np.exp(-np.linalg.norm(np.meshgrid(
y_cube[ipt] - ycenter[index_y],
x_cube[ipt] - xcenter[index_x]
), axis=0).ravel())

Comment on lines +46 to +47
index2d = [iy * blot_xsize + ix for iy in index_y[0] for ix in (index_x[0] + xstart)]
index2d = np.array(index2d)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
index2d = [iy * blot_xsize + ix for iy in index_y[0] for ix in (index_x[0] + xstart)]
index2d = np.array(index2d)
index2d = np.add.outer(index_y[0] * blot_xsize, index_x[0] + xstart).ravel()

ts1 = time.time()
log.debug(f"Time to map 1 slice = {ts1-ts0:.1f}")
log.debug(f"Time to blot 1 slice on NIRspec = {ts1-ts0:.1f}")
Copy link
Member

Choose a reason for hiding this comment

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

Is timing for one slice relevant even for debugging purposes?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

it is for NIRSPEC. It can take several seconds per slice. Once we get it faster I will remove the debug timing

index2d = [iy * blot_xsize + ix for iy in index_y[0] for ix in (index_x[0])]
blot_flux[index2d] = blot_flux[index2d] + weighted_flux
blot_weight[index2d] = blot_weight[index2d] + weight_distance
blot_cube.blot_overlap(ipt, xstart,
Copy link
Member

Choose a reason for hiding this comment

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

Maybe just set xstart to 0?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

holding off on blot changes - I have opened a separate ticket on blotting

Comment on lines 650 to 657
int *idqv = NULL; // int vector for spaxel

if (mem_alloc_dq(ncube, &idqv)) return 1;

// Set all data to zero
for (long i = 0; i < ncube; i++){
idqv[i] = 0;
}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
int *idqv = NULL; // int vector for spaxel
if (mem_alloc_dq(ncube, &idqv)) return 1;
// Set all data to zero
for (long i = 0; i < ncube; i++){
idqv[i] = 0;
}
int *idqv; // int vector for spaxel
if (mem_alloc_dq(ncube, &idqv)) return 1;

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

Comment on lines 748 to 755
int *idqv = NULL; // int vector for spaxel

if (mem_alloc_dq(ncube, &idqv)) return 1;

// Set all data to zero
for (long i = 0; i < ncube; i++){
idqv[i] = 0;
}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
int *idqv = NULL; // int vector for spaxel
if (mem_alloc_dq(ncube, &idqv)) return 1;
// Set all data to zero
for (long i = 0; i < ncube; i++){
idqv[i] = 0;
}
int *idqv; // int vector for spaxel
if (mem_alloc_dq(ncube, &idqv)) return 1;

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

double c2_min;
double c1_max;
double c2_max;
int status = 0;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
int status = 0;
int status;

Comment on lines 820 to 833
double *fluxv = NULL, *weightv=NULL, *varv=NULL ; // vectors for spaxel
double *ifluxv = NULL; // vector for spaxel

// allocate memory to hold output
if (mem_alloc(ncube, &fluxv, &weightv, &varv, &ifluxv)) return 1;

double set_zero=0.0;
// Set all data to zero
for (int i = 0; i < ncube; i++){
varv[i] = set_zero;
fluxv[i] = set_zero;
ifluxv[i] = set_zero;
weightv[i] = set_zero;
}
Copy link
Member

Choose a reason for hiding this comment

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

This can be simplified as in cube_match_internal.c if using alloc_flux_arrays.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

good suggestion. I moved alloc_flux_arrays to cube_utils.c

Comment on lines 991 to 1000
if (mem_alloc(ncube, &fluxv, &weightv, &varv, &ifluxv)) return 1;

double set_zero=0.0;
// Set all data to zero
for (int i = 0; i < ncube; i++){
varv[i] = set_zero;
fluxv[i] = set_zero;
ifluxv[i] = set_zero;
weightv[i] = set_zero;
}
Copy link
Member

Choose a reason for hiding this comment

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

Again this can be simplified as in cube_match_internal.c if using alloc_flux_arrays.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

@@ -0,0 +1,484 @@
/*
The detector pixels are represented by a 'point could' on the sky. The IFU cube is
Copy link
Collaborator

Choose a reason for hiding this comment

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

typo: "could" -> "cloud"

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

/*
The detector pixels are represented by a 'point could' on the sky. The IFU cube is
represented by a 3-D regular grid. This module finds the point cloud members contained
in a region centered on the center of the cube spaxel. The size of the spaxel is spatial
Copy link
Collaborator

Choose a reason for hiding this comment

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

typo? Do you mean "size of the spaxel in spatial coords?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

The detector pixels are represented by a 'point could' on the sky. The IFU cube is
represented by a 3-D regular grid. This module finds the point cloud members contained
in a region centered on the center of the cube spaxel. The size of the spaxel is spatial
coordinates is cdetl1 and cdelt2, while the wavelength size is zcdelt3.
Copy link
Collaborator

Choose a reason for hiding this comment

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

typo? should "zcdelt3" be just "cdelt3"?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh, and "cdetl1" should be "cdelt1"

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

represented by a 3-D regular grid. This module finds the point cloud members contained
in a region centered on the center of the cube spaxel. The size of the spaxel is spatial
coordinates is cdetl1 and cdelt2, while the wavelength size is zcdelt3.
This module uses the e modified shephard weighting method to determine how to weight each point clold member
Copy link
Collaborator

Choose a reason for hiding this comment

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

do you really mean "the e modified shepard weighting" or is the "e" extraneous? And another instance of "clold" that should be "cloud".

@@ -0,0 +1,1441 @@
/*
The detector pixels are represented by a 'point could' on the sky. The IFU cube is
Copy link
Collaborator

Choose a reason for hiding this comment

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

"could" -> "cloud"

The detector pixels are represented by a 'point could' on the sky. The IFU cube is
represented by a 3-D regular grid. This module finds the point cloud members contained
in a region centered on the center of the cube spaxel. The size of the spaxel is spatial
coordinates is cdetl1 and cdelt2, while the wavelength size is zcdelt3.
Copy link
Collaborator

Choose a reason for hiding this comment

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

All the same typos as the same paragraph in the previous module (cdetl1, zcdelt3, ...)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed typos

setup.cfg Outdated
@@ -35,6 +35,7 @@ install_requires =
gwcs>=0.16.1
jsonschema>=3.0.2
numpy>=1.17
numba>=0.50.0
Copy link
Collaborator

Choose a reason for hiding this comment

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

Now that we're using C extensions instead of numba, can this be removed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes I forgot I put that in

@jemorrison
Copy link
Collaborator Author

@jdavies-st @eslavich
The PR failed on CI/Installed package with --pyargs
What does the mean ? I looked at the details but I am a bit lost how to fix it

@jdavies-st
Copy link
Collaborator

It looks like it was getting a corrupted CRDS reference file. I kicked off the CI again. Hopefully it won't be corrupted this time.

@jemorrison
Copy link
Collaborator Author

I made the changes suggested in the review. I reran the regression tests and they look good. There are some expected differences because I improved the DQ flags for edge cases and I made a few other little improvements.
I think this is ready to be merged

@jdavies-st
Copy link
Collaborator

FYI the doc build is failing because the new C module cannot be imported for the doc build. @eslavich do you recall what the issue was for C extensions and doc builds?

@hbushouse
Copy link
Collaborator

We made a slight change in docs/conf.py or something like that to remove a line that was causing it to look for things in a parent directory. Look at https://github.com/spacetelescope/jwst/pull/6207/files

@drlaw1558
Copy link
Collaborator

I'm seeing some larger changes in the data cubes than I would have expected given this just changes the language not the algorithm; let me investigate further and get back to you. Did anything change that should have affected the total cube FOV?

@jemorrison
Copy link
Collaborator Author

jemorrison commented Jul 22, 2021

@drlaw1558 I tweaked the DQ flagging. Hopefully improving it near boundaries. I also tweaked- for MIRI - how the wavelength range that is used to build the cube is determined. There was a small bug in the old code.

@eslavich
Copy link
Collaborator

We merged a fix for the doc build failure in #6230, so a rebase ought to get that unstuck.

@drlaw1558
Copy link
Collaborator

Ok, disregard my last comment- the changes that I was seeing were due to something changing earlier in the pipeline (that I'll have to run down elsewhere), not cube build. Performance looks good to me; when the test is set up properly SCI results from running spec3 in multiple different modes look identical before/after this change but with vastly improved runtimes.

Copy link
Collaborator

@drlaw1558 drlaw1558 left a comment

Choose a reason for hiding this comment

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

Focusing entirely on results, the performance of this PR looks good to me. Running some test cases with dithered exposures in multiple bands through a variety of different kinds of cube building I get identical SCI results before/after this change but with vastly improved runtimes. DQ arrays look improved.

@jemorrison
Copy link
Collaborator Author

@hbushouse can we merge this PR ?

@hbushouse hbushouse merged commit 7a8738b into spacetelescope:master Jul 29, 2021

// loop over each valid point on detector and find match to IFU cube based
// on along slice coordinate and wavelength
for (int ipixel= 0; ipixel< npt; ipixel++){
Copy link
Collaborator

Choose a reason for hiding this comment

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

@jemorrison I believe you need to declare the variables in the beginning of the file as certain compilers won't like the definition within the for loop.

double wave_min = 10000;
double along_max = -10000;
double wave_max = -10000;
for (int j = 0; j< 4; j++){
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same comment as above


int nplane = naxis1 * naxis2;
// loop over possible overlapping cube pixels
for(int zz =iz1; zz < iz2+1; zz++){
Copy link
Collaborator

Choose a reason for hiding this comment

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

and here

for(int zz =iz1; zz < iz2+1; zz++){
double zcenter = zcoord[zz];
int istart = zz * nplane;
for (int aa= ia1; aa< ia2 + 1; aa++){
Copy link
Collaborator

Choose a reason for hiding this comment

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

and here

@jemorrison
Copy link
Collaborator Author

@nden
As I was moving all the definition to top of code. I see that I am I doing this:

nxy = nx * ny
int wave_slice_dq[nxy]

The code works fine doing this on my Mac. I am now thinking this may not be allowed - dynamically allocated the array. Should I change how I do this ?

@jemorrison jemorrison deleted the cube_build_stage3 branch August 2, 2021 17:55
@jdavies-st jdavies-st modified the milestones: Build 7.9, Build 7.8.2 Sep 2, 2021
@jdavies-st jdavies-st changed the title Cube build stage3 - JP-2096 JP-2096: Cube build stage3 Sep 2, 2021
jdavies-st pushed a commit that referenced this pull request Sep 3, 2021
* updates using numba jit

* flake 8 fixes

* a few numba updates

* updates to support internal_cal and numba

* fix test - removing unused resolution file

* improved blotting speed using numba

* added c code for emsm

* fixed setup.py  to compile match_det_cube

* updates to c code

* some changes to c python interface

* more fixes to c code

* added cube_match_internal and pulled common c routines to cube_utils.c

* Clean up

* remove cube_cloud.py

* added weighting=msm as possibility for c extension cube weighting

* removed declaration of numba from routine

* fix typo

* flake8 fix

* remove printf from c code

* remove print in ifu_cube.py

* typo in cube_match_sky.c

* changes after review

* fix alloc arrays def

* Updated change log

* remove print statement

(cherry picked from commit 7a8738b)
loicalbert pushed a commit to talensgj/jwst that referenced this pull request Nov 5, 2021
* updates using numba jit

* flake 8 fixes

* a few numba updates

* updates to support internal_cal and numba

* fix test - removing unused resolution file

* improved blotting speed using numba

* added c code for emsm

* fixed setup.py  to compile match_det_cube

* updates to c code

* some changes to c python interface

* more fixes to c code

* added cube_match_internal and pulled common c routines to cube_utils.c

* Clean up

* remove cube_cloud.py

* added weighting=msm as possibility for c extension cube weighting

* removed declaration of numba from routine

* fix typo

* flake8 fix

* remove printf from c code

* remove print in ifu_cube.py

* typo in cube_match_sky.c

* changes after review

* fix alloc arrays def

* Updated change log

* remove print statement
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Move computationally intensive portion of cube build to C or use numba(stage 2 of cube_build improvements)
7 participants