quad_utils_mod search: replace 3D scratch arrays with compressed sparse row format - #1102
Conversation
|
Q. global vs. regional. wrap where there is no wrap. (mad wrapping of region around globe) How bad is it to have mad-wrap? <-- --> DART/models/utilities/quad_utils_mod.f90 Lines 764 to 769 in acf0a14 |
|
@mgharamti this is the quad search I am playing with. I think the main concerns are calculating the course grid from the actual grid rather than the hardcoded 3 cases, and the search structure size. But open to any input. DART/models/utilities/quad_utils_mod.f90 Lines 730 to 751 in acf0a14 |
mgharamti
left a comment
There was a problem hiding this comment.
@hkershaw-brown, thanks for the work on this. I was always on the edge with the previous style of 3 hard-coded cases but didn't know how to go around that. I think I understand your design and strategy. CSR clearly avoids working with huge 3D arrays.
My first lazy question is why the 8 targets on average? I guess how did you choose it? It seems to me that search cost per obs is ~O(num_grids) and number of bins/boxes is ~O(N/TARGET_CANDIDATES) and so it's a performance tuning parameter. At least we need to explain it in the docs if you decide to go with it.
Second, could we add a post-fill consistency check that verifies fill_pos(i,j) == grid_start(i,j) + grid_num(i,j) for every coarse bin? This would catch any future divergence between the counting pass and filling pass, especially around edge cases such as longitude wrapping, masked/invalid quads, or boundary-touching cells.
| call get_quad_corners(h%ii%lons_2d, i, j, cyclic, pole, nx, ny, u_c_lons, istatus) | ||
| if (istatus /= 0) print *, 'get_quad_corners for lons returns failure' | ||
|
|
||
| call get_quad_corners(h%ii%lats_2d, i, j, cyclic, pole, nx, ny, u_c_lats, istatus) | ||
| if (istatus /= 0) print *, 'get_quad_corners for lats returns failure' |
There was a problem hiding this comment.
Do we need to cycle here rather than just print an error? If a failure produces bad corners, both passes may count/fill bad bins or one failure path could behave differently depending on the state (right?)
There was a problem hiding this comment.
I will take a closer look - I think I am missing the logic in the original code.
The loop limits are the x,y bounds of the lon and lat arrays.
Is the print just checking if the loop limits are wrong?
If you can go off the end of the arrays,
it seems like both should fail, or both should pass.
If one fails and one passes I think this should be a catastrophic error rather than a print, since lon,lat are for the same point.
My lazy choice: Dr. Google for what was a good choice for this kind of algorithm. I think the equivalent average target (
The grids are not uniform so 'average' is probably a disgusting assumption. I think it is well worth doing some runs with the test code and different (and variable) grid resolutions to count the actual number of quads per box with the new code (and counting the per course box with the 3 hardcoded POP cases). Maybe we have 799 quads in one box, 3 in another. Currently the 8 is just an ok choice where I was finding comparable speeds to the current version, but it is definitely a magic number that needs justifying. |
420ec74 to
62be280
Compare
print out box info, max, min, 0, how many are >2*mean #1102 (comment)
print out box info, max, min, 0, how many are >2*mean #1102 (comment)
24d04cf to
1b80a94
Compare
print out box info, max, min, 0, how many are >2*mean #1102 (comment)
1b80a94 to
ea2d4ce
Compare
| if ((index_x < 1 .or. index_x > nrx) .or. (index_y < 1 .or. index_y > nry)) then | ||
| string1 = 'unable to find right box' | ||
| write(string2,*) 'index_x may be out-of-range: ', 1, index_x, nrx | ||
| write(string3,*) 'index_y may be out-of-range: ', 1, index_y, nry | ||
| call error_handler(E_ERR,'update_reg_list',string1, & | ||
| source, revision, revdate, text2=string2, text3=string3) | ||
| endif |
There was a problem hiding this comment.
Shouldn't we keep this check? Even though this is now just the first pass, I think this check would still work at the end of count_reg_overlaps
Assuming I'm understanding the initial motivation for this check correctly, I believe it is still possible for index_x or index_y to return invalid box numbers due to wraparound shenanigans
There was a problem hiding this comment.
@hkershaw-brown last outstanding comment before approval
|
I think the updates here look really solid. Two-pass CSR looks clean and is an awesome way to reduce the memory overhead of the quad search. The computation time of get_quad_corners is tiny enough that doing it twice doesn't really make a difference in the runtime. Makes you wonder what possibilities could be out there for similar-ish updates elsewhere in the code Just checking though that this was tested on sufficiently large grids - I see that the timing / memory comparison on quad_search_perf was done with size Nx = 5000, Ny = 4500 Dynamic calculation of the dimensions for the coarse grid is also clearly a huge win, but I am also curious about the value for TARGET_CANDIDATES being 8. Earlier you mentioned you were planning to do more tests: "I think it is well worth doing some runs with the test code and different (and variable) grid resolutions to count the actual number of quads per box with the new code (and counting the per course box with the 3 hardcoded POP cases). " |
get_close uses a two pass, I am not sure why quad utils did not originally use a two pass. But for sure there are all sorts of places in the code that could be improved.
Its just a heuristic, here is the comment to Moha on the same question. It was ~10 on main for the hardcoded choice.
This is one of the reasons for keeping the diagnostics prints in the log file about how many candidates are in each box. |
|
@mgharamti any remaining issues with this pull request? I believe I addressed the requested changes. Cheers, |
mgharamti
left a comment
There was a problem hiding this comment.
Looks good and ready to ship!
See #979 The GRID_QUAD_FULLY_IRREGULAR coarse-index build previously allocated reg_list_lon/lat(nrx, nry, max_reg_list_num) as a 3D temporary, reaching ~5.5 GB at nrx=nry=900. It also errored out if any coarse box accumulated more than max_reg_list_num=800 candidate quads. Replace with a two-pass compressed sparse row build: - Pass 1: count overlaps into grid_num; peak memory ~11 MB - Prefix sum: derive grid_start and exact total entry count - Pass 2: fill flat lists using a cursor array; no cap, no abort Replace hardcoded num_reg with calculation based on grid num_reg = max(10, min(nx_or_ny, int(sqrt(nx*ny / TARGET_CANDIDATES)))) where TARGET_CANDIDATES=8, giving ~8 candidate quads per coarse box regardless of grid resolution. Remove no longer used max_reg_list_num from quad_irreg_grid_coords
print out box info, max, min, 0, how many are >2*mean #1102 (comment)
Can you use quad_utils with a periodic y domain? e.g. wrf idealized. Not at the moment I guess.
b0c90c1 to
ef3a1bc
Compare


Description:
Quad_utils_mod box search for fully irregular grids:
The runtime calculation of storage requirements is needed in particular for regional CESM where people are creating their own regional grids, but is applicable to any model using quad_utils.
The per-code memory is quite high for the box storage (see #979 & https://github.com/hkershaw-brown/quad_search_perf/blob/main/README.md). This pull request does not address distributing memory for the search storage (may be needed later), but does reduce the per core memory.
Compressed Sparse Row format:
The GRID_QUAD_FULLY_IRREGULAR coarse-index search-info array setup previously allocated reg_list_lon/lat(nrx, nry, max_reg_list_num) as a 3D temporary, reaching ~5.5 GB at nrx=nry=900. It also errored out if any coarse box accumulated more than max_reg_list_num=800 candidate quads. Noted in issue #979
Replace with a two-pass compressed sparse row build:
Replace hardcoded num_reg with calculation based on grid
num_reg = max(10, min(nx_or_ny, int(sqrt(nx*ny / TARGET_CANDIDATES))))
where TARGET_CANDIDATES=8, giving ~8 candidate quads per coarse box regardless of grid resolution.
Remove no longer used max_reg_list_num from quad_irreg_grid_coords
To do:
Fixes issue
Fixes #979
Types of changes
Documentation changes needed?
Tests
Please describe any tests you ran to verify your changes.
Looking at brute force vs. current method vs new method: https://github.com/hkershaw-brown/quad_search_perf
I've only created global test grids.regional grids now,Quad_util_developer tests bitwise, but this are fairly limited.
Currently running ROMS_rutgers case to compare memory (still in queue)
Checklist for merging
Checklist for release
Testing Datasets