From f5d095da41fe1c8475a36bf1c3bd33c3e8c299c1 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Wed, 10 Apr 2024 00:12:26 -0600 Subject: [PATCH 01/97] first steps --- discretize/_extensions/tree.cpp | 111 +++++++++++++++++++++++++++----- discretize/_extensions/tree.h | 9 +++ 2 files changed, 104 insertions(+), 16 deletions(-) diff --git a/discretize/_extensions/tree.cpp b/discretize/_extensions/tree.cpp index 023de540c..2a6dee773 100644 --- a/discretize/_extensions/tree.cpp +++ b/discretize/_extensions/tree.cpp @@ -365,6 +365,100 @@ void Cell::shift_centers(double *shift){ } } +// intersections tests: + +bool Cell::intersects_point(double *x){ + // A simple bounding box check: + int_t last_point = (ndim < 3)? 3 : 7; + for(int_t i=0; i < n_dim; ++i){ + if(x[i] < points[0].location[i] || x[i] > points[last_point].location[i]){ + return false + } + } + return true; +} + +bool Cell:intersects_ball(double *x, double rsq){ + + // check if I intersect the ball + double xp = std::max(points[0]->location[0], std::min(center[0], points[3]->location[0])); + double yp = std::max(points[0]->location[1], std::min(center[1], points[3]->location[1])); + double zp = 0.0; + if (n_dim > 2){ + zp = std::max(points[0]->location[2], std::min(center[2], points[7]->location[2])); + } + + // xp, yp, zp is closest point in the cell to the center of the circle + // check if that point is in the circle! + double r2_test = (xp - center[0])*(xp - center[0]) + (yp - center[1]) *(yp - center[1]); + if (n_dim > 2){ + r2_test += (zp - center[2])*(zp - center[2]); + } + return r2_test < rsq; +} + + +bool Cell::intersects_line(double *x0, double *x1, double* dx, bool segment=true){ + // bounding box intersection if doing a segment test: + int_t last_point = (ndim < 3)? 3 : 7; + if (segment){ + for(int_t i=0; i points[last_point].location[i]){ + return false; + } + } + } + // Separating axis test + abs() + + // Check to see if I intersect the segment + double t0x, t0y, t0z, t1x, t1y, t1z; + double tminx, tminy, tminz, tmaxx, tmaxy, tmaxz; + double tmin, tmax; + + t0x = (points[0]->location[0] - x0[0]) * diff_inv[0]; + t1x = (points[3]->location[0] - x0[0]) * diff_inv[0]; + if (t0x <= t1x){ + tminx = t0x; + tmaxx = t1x; + }else{ + tminx = t1x; + tmaxx = t0x; + } + + t0y = (points[0]->location[1] - x0[1]) * diff_inv[1]; + t1y = (points[3]->location[1] - x0[1]) * diff_inv[1]; + if (t0y <= t1y){ + tminy = t0y; + tmaxy = t1y; + }else{ + tminy = t1y; + tmaxy = t0y; + } + + tmin = std::max(tminx, tminy); + tmax = std::min(tmaxx, tmaxy); + if (n_dim > 2){ + t0z = (points[0]->location[2] - x0[2]) * diff_inv[2]; + t1z = (points[7]->location[2] - x0[2]) * diff_inv[2]; + if (t0z <= t1z){ + tminz = t0z; + tmaxz = t1z; + }else{ + tminz = t1z; + tmaxz = t0z; + } + tmin = std::max(tmin, tminz); + tmax = std::min(tmax, tmaxz); + } + // now can test if I intersect! + return tmax >= 0 && tmin <= 1 && tmin <= tmax +} + + void Cell::insert_cell(node_map_t& nodes, double *new_cell, int_t p_level, double *xs, double *ys, double *zs, bool diag_balance){ //Inserts a cell at min(max_level,p_level) that contains the given point if(p_level > level){ @@ -385,22 +479,7 @@ void Cell::refine_ball(node_map_t& nodes, double* center, double r2, int_t p_lev if (level >= p_level || level == max_level){ return; } - // check if I intersect the ball - double xp = std::max(points[0]->location[0], std::min(center[0], points[3]->location[0])); - double yp = std::max(points[0]->location[1], std::min(center[1], points[3]->location[1])); - double zp = 0.0; - if (n_dim > 2){ - zp = std::max(points[0]->location[2], std::min(center[2], points[7]->location[2])); - } - - // xp, yp, zp is closest point in the cell to the center of the circle - // check if that point is in the circle! - double r2_test = (xp - center[0])*(xp - center[0]) + (yp - center[1]) *(yp - center[1]); - if (n_dim > 2){ - r2_test += (zp - center[2])*(zp - center[2]); - } - if (r2_test >= r2){ - // I do not intersect the ball + if (!intersects_ball(center, r2)){ return; } // if I intersect cell, I will need to be divided (if I'm not already) diff --git a/discretize/_extensions/tree.h b/discretize/_extensions/tree.h index f3b0bef93..c99d314f6 100644 --- a/discretize/_extensions/tree.h +++ b/discretize/_extensions/tree.h @@ -116,6 +116,15 @@ class Cell{ Cell(Node *pts[4], Cell *parent); ~Cell(); + // intersection tests + bool intersects_point(double *x); + bool intersects_ball(double *x, double rsq); + bool intersects_line(double *x0, double *x1, bool segment=true); + bool intersects_plane(double *x0, double *x1, double *x2, bool segment=true); + bool intersects_box(double *x0, double *xF); + bool intersects_tetra(double *x0, double *x1, double *x2); + bool intersects_vert_triang_prism(double *x0, double *x1, double *x2, double h); + bool inline is_leaf(){ return children[0]==NULL;}; void spawn(node_map_t& nodes, Cell *kids[8], double* xs, double *ys, double *zs); void divide(node_map_t& nodes, double* xs, double* ys, double* zs, bool balance=true, bool diag_balance=false); From 8e7bc0d6f40fded4817c6f3bdea047f6dcb7b43c Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Thu, 11 Apr 2024 08:57:35 -0600 Subject: [PATCH 02/97] adding intersections --- .gitignore | 2 + discretize/_extensions/tree.cpp | 560 +++++++++++++++----------------- discretize/_extensions/tree.h | 58 +++- discretize/_extensions/tree.pxd | 2 +- 4 files changed, 307 insertions(+), 315 deletions(-) diff --git a/.gitignore b/.gitignore index 50ec0789a..e9991a8a9 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,5 @@ examples/*.tar.gz # setuptools_scm discretize/version.py + +.idea/ diff --git a/discretize/_extensions/tree.cpp b/discretize/_extensions/tree.cpp index 2a6dee773..ec1a2496d 100644 --- a/discretize/_extensions/tree.cpp +++ b/discretize/_extensions/tree.cpp @@ -378,7 +378,7 @@ bool Cell::intersects_point(double *x){ return true; } -bool Cell:intersects_ball(double *x, double rsq){ +bool Cell::intersects_ball(double *x, double rsq){ // check if I intersect the ball double xp = std::max(points[0]->location[0], std::min(center[0], points[3]->location[0])); @@ -397,221 +397,60 @@ bool Cell:intersects_ball(double *x, double rsq){ return r2_test < rsq; } +bool Cell::intersects_line(double *x0, double *x1, double* i_dx, bool segment=true){ + // bounding box intersection if doing a segment test + Node *p0 = points[0]; + Node *p1 = (ndim < 3) ? points[3] : points[7]; -bool Cell::intersects_line(double *x0, double *x1, double* dx, bool segment=true){ - // bounding box intersection if doing a segment test: - int_t last_point = (ndim < 3)? 3 : 7; - if (segment){ - for(int_t i=0; i::infinity(); + double t_far = std::numeric_limits::infinity(); + double t0, t1, dx_sign, dx_abs; + + for(int_t i=0; ilocation[i] || x0[i] > p1->location[i])){ + return false; + } + if (segment){ + if(std:max(x0[i], x1[i]) < p0->location[i]){ return false; } - if(std:min(x0[i], x1[i] > points[last_point].location[i]){ + if(std:min(x0[i], x1[i]) > p1->location[i]){ return false; } } - } - // Separating axis test - abs() - - // Check to see if I intersect the segment - double t0x, t0y, t0z, t1x, t1y, t1z; - double tminx, tminy, tminz, tmaxx, tmaxy, tmaxz; - double tmin, tmax; - - t0x = (points[0]->location[0] - x0[0]) * diff_inv[0]; - t1x = (points[3]->location[0] - x0[0]) * diff_inv[0]; - if (t0x <= t1x){ - tminx = t0x; - tmaxx = t1x; - }else{ - tminx = t1x; - tmaxx = t0x; - } - - t0y = (points[0]->location[1] - x0[1]) * diff_inv[1]; - t1y = (points[3]->location[1] - x0[1]) * diff_inv[1]; - if (t0y <= t1y){ - tminy = t0y; - tmaxy = t1y; - }else{ - tminy = t1y; - tmaxy = t0y; - } - - tmin = std::max(tminx, tminy); - tmax = std::min(tmaxx, tmaxy); - if (n_dim > 2){ - t0z = (points[0]->location[2] - x0[2]) * diff_inv[2]; - t1z = (points[7]->location[2] - x0[2]) * diff_inv[2]; - if (t0z <= t1z){ - tminz = t0z; - tmaxz = t1z; - }else{ - tminz = t1z; - tmaxz = t0z; - } - tmin = std::max(tmin, tminz); - tmax = std::min(tmax, tmaxz); - } - // now can test if I intersect! - return tmax >= 0 && tmin <= 1 && tmin <= tmax -} - - -void Cell::insert_cell(node_map_t& nodes, double *new_cell, int_t p_level, double *xs, double *ys, double *zs, bool diag_balance){ - //Inserts a cell at min(max_level,p_level) that contains the given point - if(p_level > level){ - // Need to go look in children, - // Need to spawn children if i don't have any... - if(is_leaf()){ - divide(nodes, xs, ys, zs, true, diag_balance); + if (x0[i] != x1[i]){ + t0 = (p0->location[i] - x0[i]) * i_dx[i]; + t1 = (p1->location[i] - x0[i]) * i_dx[i]; + if (t0 > t1){ + std::swap(t0, t1); + } + t_near = std::max(t_near, t0); + t_far = std::min(t_far, t1); + if (t_near > t_far || (segment && (t_far < 0 || t_near > 1))){ + return false; + } } - int ix = new_cell[0] > children[0]->points[3]->location[0]; - int iy = new_cell[1] > children[0]->points[3]->location[1]; - int iz = n_dim>2 && new_cell[2]>children[0]->points[7]->location[2]; - children[ix + 2*iy + 4*iz]->insert_cell(nodes, new_cell, p_level, xs, ys, zs, diag_balance); - } -}; - -void Cell::refine_ball(node_map_t& nodes, double* center, double r2, int_t p_level, double *xs, double *ys, double* zs, bool diag_balance){ - // early exit if my level is higher than or equal to target - if (level >= p_level || level == max_level){ - return; - } - if (!intersects_ball(center, r2)){ - return; - } - // if I intersect cell, I will need to be divided (if I'm not already) - if(is_leaf()){ - divide(nodes, xs, ys, zs, true, diag_balance); - } - // recurse into children - children[0]->refine_ball(nodes, center, r2, p_level, xs, ys, zs, diag_balance); - children[1]->refine_ball(nodes, center, r2, p_level, xs, ys, zs, diag_balance); - children[2]->refine_ball(nodes, center, r2, p_level, xs, ys, zs, diag_balance); - children[3]->refine_ball(nodes, center, r2, p_level, xs, ys, zs, diag_balance); - if (n_dim > 2){ - children[4]->refine_ball(nodes, center, r2, p_level, xs, ys, zs, diag_balance); - children[5]->refine_ball(nodes, center, r2, p_level, xs, ys, zs, diag_balance); - children[6]->refine_ball(nodes, center, r2, p_level, xs, ys, zs, diag_balance); - children[7]->refine_ball(nodes, center, r2, p_level, xs, ys, zs, diag_balance); } + return true; } -void Cell::refine_box(node_map_t& nodes, double* x0, double* x1, int_t p_level, double *xs, double *ys, double* zs, bool enclosed, bool diag_balance){ - // early exit if my level is higher than target - if (level >= p_level || level == max_level){ - return; - } - if (!enclosed){ - // check if I overlap (not if an edge overlaps) - // If I do not overlap the cells then return - if (x0[0] >= points[3]->location[0] || x1[0] <= points[0]->location[0]){ - return; - } +bool Cell::intersects_box(double *x0, double *x1){ + Node *p0 = points[0]; + Node *p1 = (ndim < 3) ? points[3] : points[7]; - if (x0[1] >= points[3]->location[1] || x1[1] <= points[0]->location[1]){ - return; + for(int_t i; ilocation[i]){ + return false; } - - if (n_dim>2 && (x0[2] >= points[7]->location[2] || x1[2] <= points[0]->location[2])){ - return; - } - - // check to see if I am completely enclosed (for faster subdivision of children) - enclosed = ( - points[0]->location[0] > x0[0] && points[3]->location[0] < x1[0] && - points[0]->location[1] > x0[1] && points[3]->location[1] < x1[1] && - (n_dim == 2 || (n_dim == 3 && points[0]->location[2] > x0[2] && points[7]->location[2] < x1[2])) - ); - - } - // Will only be here if I intersect the box - if(is_leaf()){ - divide(nodes, xs, ys, zs, true, diag_balance); - } - // recurse into children - children[0]->refine_box(nodes, x0, x1, p_level, xs, ys, zs, enclosed, diag_balance); - children[1]->refine_box(nodes, x0, x1, p_level, xs, ys, zs, enclosed, diag_balance); - children[2]->refine_box(nodes, x0, x1, p_level, xs, ys, zs, enclosed, diag_balance); - children[3]->refine_box(nodes, x0, x1, p_level, xs, ys, zs, enclosed, diag_balance); - if (n_dim > 2){ - children[4]->refine_box(nodes, x0, x1, p_level, xs, ys, zs, enclosed, diag_balance); - children[5]->refine_box(nodes, x0, x1, p_level, xs, ys, zs, enclosed, diag_balance); - children[6]->refine_box(nodes, x0, x1, p_level, xs, ys, zs, enclosed, diag_balance); - children[7]->refine_box(nodes, x0, x1, p_level, xs, ys, zs, enclosed, diag_balance); - } -} - -void Cell::refine_line(node_map_t& nodes, double* x0, double* x1, double* diff_inv, int_t p_level, double *xs, double *ys, double* zs, bool diag_balance){ - // Return If I'm at max_level or p_level - if (level >= p_level || level == max_level){ - return; - } - // then check to see if I intersect the segment - double t0x, t0y, t0z, t1x, t1y, t1z; - double tminx, tminy, tminz, tmaxx, tmaxy, tmaxz; - double tmin, tmax; - - t0x = (points[0]->location[0] - x0[0]) * diff_inv[0]; - t1x = (points[3]->location[0] - x0[0]) * diff_inv[0]; - if (t0x <= t1x){ - tminx = t0x; - tmaxx = t1x; - }else{ - tminx = t1x; - tmaxx = t0x; - } - - t0y = (points[0]->location[1] - x0[1]) * diff_inv[1]; - t1y = (points[3]->location[1] - x0[1]) * diff_inv[1]; - if (t0y <= t1y){ - tminy = t0y; - tmaxy = t1y; - }else{ - tminy = t1y; - tmaxy = t0y; - } - - tmin = std::max(tminx, tminy); - tmax = std::min(tmaxx, tmaxy); - if (n_dim > 2){ - t0z = (points[0]->location[2] - x0[2]) * diff_inv[2]; - t1z = (points[7]->location[2] - x0[2]) * diff_inv[2]; - if (t0z <= t1z){ - tminz = t0z; - tmaxz = t1z; - }else{ - tminz = t1z; - tmaxz = t0z; - } - tmin = std::max(tmin, tminz); - tmax = std::min(tmax, tmaxz); - } - // now can test if I intersect! - if (tmax >= 0 && tmin <= 1 && tmin <= tmax){ - if(is_leaf()){ - divide(nodes, xs, ys, zs, true, diag_balance); - } - // recurse into children - for(int_t i = 0; i < (1<refine_line(nodes, x0, x1, diff_inv, p_level, xs, ys, zs, diag_balance); + if(std::min(x0[i], x1[i]) > p1->location[i]){ + return false; } } + return true; } -void Cell::refine_triangle( - node_map_t& nodes, - double* x0, double* x1, double* x2, - double* e0, double* e1, double* e2, - double* t_norm, - int_t p_level, double *xs, double *ys, double* zs, bool diag_balance -){ - // Return If I'm at max_level or p_level - if (level >= p_level || level == max_level){ - return; - } +bool Cell::intersects_triangle(double *x0, double *x1, double *x2, double *e0, double *e1, double *e2, double *t_norm){ // then check to see if I intersect the segment double v0[3], v1[3], v2[3], half[3]; double vmin, vmax; @@ -628,7 +467,7 @@ void Cell::refine_triangle( // Bounding box check if (vmin > half[i] || vmax < -half[i]){ - return; + return false; } } // first do the 3 edge cross tests that apply in 2D and 3D @@ -641,7 +480,7 @@ void Cell::refine_triangle( pmax = std::max(p1, p2); rad = std::abs(e0[1]) * half[0] + std::abs(e0[0]) * half[1]; if (pmin > rad || pmax < -rad){ - return; + return false; } // edge 1 cross z_hat @@ -652,7 +491,7 @@ void Cell::refine_triangle( pmax = std::max(p0, p1); rad = std::abs(e1[1]) * half[0] + std::abs(e1[0]) * half[1]; if (pmin > rad || pmax < -rad){ - return; + return false; } // edge 2 cross z_hat @@ -663,7 +502,7 @@ void Cell::refine_triangle( pmax = std::max(p1, p2); rad = std::abs(e2[1]) * half[0] + std::abs(e2[0]) * half[1]; if (pmin > rad || pmax < -rad){ - return; + return false; } if(n_dim > 2){ @@ -675,7 +514,7 @@ void Cell::refine_triangle( pmax = std::max(p0, p2); rad = std::abs(e0[2]) * half[1] + std::abs(e0[1]) * half[2]; if (pmin > rad || pmax < -rad){ - return; + return false; } // edge 0 cross y_hat p0 = -e0[2] * v0[0] + e0[0] * v0[2]; @@ -685,7 +524,7 @@ void Cell::refine_triangle( pmax = std::max(p0, p2); rad = std::abs(e0[2]) * half[0] + std::abs(e0[0]) * half[2]; if (pmin > rad || pmax < -rad){ - return; + return false; } // edge 1 cross x_hat p0 = e1[2] * v0[1] - e1[1] * v0[2]; @@ -695,7 +534,7 @@ void Cell::refine_triangle( pmax = std::max(p0, p2); rad = std::abs(e1[2]) * half[1] + std::abs(e1[1]) * half[2]; if (pmin > rad || pmax < -rad){ - return; + return false; } // edge 1 cross y_hat p0 = -e1[2] * v0[0] + e1[0] * v0[2]; @@ -705,7 +544,7 @@ void Cell::refine_triangle( pmax = std::max(p0, p2); rad = std::abs(e1[2]) * half[0] + std::abs(e1[0]) * half[2]; if (pmin > rad || pmax < -rad){ - return; + return false; } // edge 2 cross x_hat p0 = e2[2] * v0[1] - e2[1] * v0[2]; @@ -715,7 +554,7 @@ void Cell::refine_triangle( pmax = std::max(p0, p1); rad = std::abs(e2[2]) * half[1] + std::abs(e2[1]) * half[2]; if (pmin > rad || pmax < -rad){ - return; + return false; } // edge 2 cross y_hat p0 = -e2[2] * v0[0] + e2[0] * v0[2]; @@ -725,7 +564,7 @@ void Cell::refine_triangle( pmax = std::max(p0, p1); rad = std::abs(e2[2]) * half[0] + std::abs(e2[0]) * half[2]; if (pmin > rad || pmax < -rad){ - return; + return false; } // triangle normal axis @@ -741,30 +580,13 @@ void Cell::refine_triangle( } } if (pmin > 0 || pmax < 0){ - return; + return false; } } - // If here, then I intersect the triangle! - if(is_leaf()){ - divide(nodes, xs, ys, zs, true, diag_balance); - } - for(int_t i = 0; i < (1<refine_triangle( - nodes, x0, x1, x2, e0, e1, e2, t_norm, p_level, xs, ys, zs, diag_balance - ); - } + return true; } -void Cell::refine_vert_triang_prism( - node_map_t& nodes, - double* x0, double* x1, double* x2, double h, - double* e0, double* e1, double* e2, double* t_norm, - int_t p_level, double *xs, double *ys, double* zs, bool diag_balance -){ - // Return If I'm at max_level or p_level - if (level >= p_level || level == max_level){ - return; - } +bool Cell::intersects_vert_triang_prism(double *x0, double *x1, double *x2, double h, double* e0, double* e1, double* e2, double* t_norm){ // check all the AABB faces double v0[3], v1[3], v2[3], half[3]; double vmin, vmax; @@ -784,7 +606,7 @@ void Cell::refine_vert_triang_prism( // Bounding box check if (vmin > half[i] || vmax < -half[i]){ - return; + return false; } } // first do the 3 edge cross tests that apply in 2D and 3D @@ -797,7 +619,7 @@ void Cell::refine_vert_triang_prism( pmax = std::max(p1, p2); rad = std::abs(e0[1]) * half[0] + std::abs(e0[0]) * half[1]; if (pmin > rad || pmax < -rad){ - return; + return false; } // edge 1 cross z_hat @@ -808,7 +630,7 @@ void Cell::refine_vert_triang_prism( pmax = std::max(p0, p1); rad = std::abs(e1[1]) * half[0] + std::abs(e1[0]) * half[1]; if (pmin > rad || pmax < -rad){ - return; + return false; } // edge 2 cross z_hat @@ -819,7 +641,7 @@ void Cell::refine_vert_triang_prism( pmax = std::max(p1, p2); rad = std::abs(e2[1]) * half[0] + std::abs(e2[0]) * half[1]; if (pmin > rad || pmax < -rad){ - return; + return false; } // edge 0 cross x_hat @@ -831,7 +653,7 @@ void Cell::refine_vert_triang_prism( pmax = std::max(std::max(std::max(p0, p1), p2), p3); rad = std::abs(e0[2]) * half[1] + std::abs(e0[1]) * half[2]; if (pmin > rad || pmax < -rad){ - return; + return false; } // edge 0 cross y_hat p0 = -e0[2] * v0[0] + e0[0] * v0[2]; @@ -842,7 +664,7 @@ void Cell::refine_vert_triang_prism( pmax = std::max(std::max(std::max(p0, p1), p2), p3); rad = std::abs(e0[2]) * half[0] + std::abs(e0[0]) * half[2]; if (pmin > rad || pmax < -rad){ - return; + return false; } // edge 1 cross x_hat p0 = e1[2] * v0[1] - e1[1] * v0[2]; @@ -853,7 +675,7 @@ void Cell::refine_vert_triang_prism( pmax = std::max(std::max(std::max(p0, p1), p2), p3); rad = std::abs(e1[2]) * half[1] + std::abs(e1[1]) * half[2]; if (pmin > rad || pmax < -rad){ - return; + return false; } // edge 1 cross y_hat p0 = -e1[2] * v0[0] + e1[0] * v0[2]; @@ -864,7 +686,7 @@ void Cell::refine_vert_triang_prism( pmax = std::max(std::max(std::max(p0, p1), p2), p3); rad = std::abs(e1[2]) * half[0] + std::abs(e1[0]) * half[2]; if (pmin > rad || pmax < -rad){ - return; + return false; } // edge 2 cross x_hat p0 = e2[2] * v0[1] - e2[1] * v0[2]; @@ -875,7 +697,7 @@ void Cell::refine_vert_triang_prism( pmax = std::max(std::max(std::max(p0, p1), p2), p3); rad = std::abs(e2[2]) * half[1] + std::abs(e2[1]) * half[2]; if (pmin > rad || pmax < -rad){ - return; + return false; } // edge 2 cross y_hat p0 = -e2[2] * v0[0] + e2[0] * v0[2]; @@ -886,7 +708,7 @@ void Cell::refine_vert_triang_prism( pmax = std::max(std::max(std::max(p0, p1), p2), p3); rad = std::abs(e2[2]) * half[0] + std::abs(e2[0]) * half[2]; if (pmin > rad || pmax < -rad){ - return; + return false; } // triangle normal axis @@ -896,35 +718,17 @@ void Cell::refine_vert_triang_prism( pmax = std::max(p0, p1); rad = std::abs(t_norm[0]) * half[0] + std::abs(t_norm[1]) * half[1] + std::abs(t_norm[2]) * half[2]; if (pmin > rad || pmax < -rad){ - return; + return false; } // the axes defined by the three vertical prism faces // should already be tested by the e0, e1, e2 cross z_hat tests - - // If here, then I intersect the triangle! - if(is_leaf()){ - divide(nodes, xs, ys, zs, true, diag_balance); - } - for(int_t i = 0; i < (1<refine_vert_triang_prism( - nodes, x0, x1, x2, h, e0, e1, e2, t_norm, p_level, xs, ys, zs, diag_balance - ); - } + return true; } -void Cell::refine_tetra( - node_map_t& nodes, +bool Cell::intersects_tetra( double* x0, double* x1, double* x2, double* x3, - double edge_tans[6][3], double face_normals[4][3], - int_t p_level, double *xs, double *ys, double* zs, bool diag_balance + double edge_tans[6][3], double face_normals[4][3] ){ - // Return If I'm at max_level or p_level - if (level >= p_level || level == max_level){ - return; - } - if (n_dim < 3){ - return; - } // then check to see if I intersect the segment double v0[3], v1[3], v2[3], v3[3], half[3]; double p0, p1, p2, p3, pmin, pmax, rad; @@ -938,7 +742,7 @@ void Cell::refine_tetra( pmax = std::max(std::max(std::max(v0[i], v1[i]), v2[i]), v3[i]); // Bounding box check if (pmin > half[i] || pmax < -half[i]){ - return; + return false; } } // first do the 3 edge cross tests that apply in 2D and 3D @@ -954,7 +758,7 @@ void Cell::refine_tetra( pmax = std::max(std::max(std::max(p0, p1), p2), p3); rad = std::abs(edge_tans[i][2]) * half[1] + std::abs(edge_tans[i][1]) * half[2]; if (pmin > rad || pmax < -rad){ - return; + return false; } p0 = -edge_tans[i][2] * v0[0] + edge_tans[i][0] * v0[2]; @@ -965,7 +769,7 @@ void Cell::refine_tetra( pmax = std::max(std::max(std::max(p0, p1), p2), p3); rad = std::abs(edge_tans[i][2]) * half[0] + std::abs(edge_tans[i][0]) * half[2]; if (pmin > rad || pmax < -rad){ - return; + return false; } p0 = edge_tans[i][1] * v0[0] - edge_tans[i][0] * v0[1]; @@ -976,7 +780,7 @@ void Cell::refine_tetra( pmax = std::max(std::max(std::max(p0, p1), p2), p3); rad = std::abs(edge_tans[i][1]) * half[0] + std::abs(edge_tans[i][0]) * half[1]; if (pmin > rad || pmax < -rad){ - return; + return false; } } // triangle face normals @@ -990,17 +794,179 @@ void Cell::refine_tetra( pmax = std::max(std::max(std::max(p0, p1), p2), p3); rad = std::abs(axis[0]) * half[0] + std::abs(axis[1]) * half[1] + std::abs(axis[2]) * half[2]; if (pmin > rad || pmax < -rad){ - return; + return false; } } - // If here, then I intersect the tetrahedron! - if(is_leaf()){ - divide(nodes, xs, ys, zs, true, diag_balance); + return true; +} + +bool Cell::intersects_plane(double *x0, double *normal){ + double half + double s = 0; + double r = 0; + Node *p0 = points[0]; + Node *p1 = (ndim < 3) points[3] : points[7]; + for(int_t i=0;ilocation[i]; + r += half + std::abs(normal[i]); + s += normal[i] * half[i] - x0[i]; + } + return std::abs(s) <= r; +} + +void Cell::insert_cell(node_map_t& nodes, double *new_cell, int_t p_level, double *xs, double *ys, double *zs, bool diag_balance){ + //Inserts a cell at min(max_level,p_level) that contains the given point + if(p_level > level){ + // Need to go look in children, + // Need to spawn children if i don't have any... + if(is_leaf()){ + divide(nodes, xs, ys, zs, true, diag_balance); + } + int ix = new_cell[0] > children[0]->points[3]->location[0]; + int iy = new_cell[1] > children[0]->points[3]->location[1]; + int iz = n_dim>2 && new_cell[2]>children[0]->points[7]->location[2]; + children[ix + 2*iy + 4*iz]->insert_cell(nodes, new_cell, p_level, xs, ys, zs, diag_balance); } - for(int_t i = 0; i < (1<refine_tetra( - nodes, x0, x1, x2, x3, edge_tans, face_normals, p_level, xs, ys, zs, diag_balance - ); +}; + +void Cell::refine_ball(node_map_t& nodes, double* center, double r2, int_t p_level, double *xs, double *ys, double* zs, bool diag_balance){ + // early exit if my level is higher than or equal to target + if (level >= p_level || level == max_level){ + return; + } + // if I intersect cell, I will need to be divided (if I'm not already) + if (intersects_ball(center, r2)){ + if(is_leaf()){ + divide(nodes, xs, ys, zs, true, diag_balance); + } + // recurse into children + for(int_t i = 0; i < (1<refine_ball(nodes, center, r2, p_level, xs, ys, zs, diag_balance); + } + } +} + +void Cell::refine_box(node_map_t& nodes, double* x0, double* x1, int_t p_level, double *xs, double *ys, double* zs, bool diag_balance){ + // early exit if my level is higher than target + if (level >= p_level || level == max_level){ + return; + } + // If I intersect cell, I will need to be divided (if I'm not already) + if (intersects_box(x0, x1)){ + if(is_leaf()){ + divide(nodes, xs, ys, zs, true, diag_balance); + } + // recurse into children + for(int_t i = 0; i < (1<refine_box(nodes, x0, x1, p_level, xs, ys, zs, diag_balance); + } + } +} + +void Cell::refine_line(node_map_t& nodes, double* x0, double* x1, double* diff_inv, int_t p_level, double *xs, double *ys, double* zs, bool diag_balance){ + // Return If I'm at max_level or p_level + if (level >= p_level || level == max_level){ + return; + } + // then check to see if I intersect the segment + if (intersects_line(x0, x1, diff_inv, segment=true)){ + if(is_leaf()){ + divide(nodes, xs, ys, zs, true, diag_balance); + } + // recurse into children + for(int_t i = 0; i < (1<refine_line(nodes, x0, x1, diff_inv, p_level, xs, ys, zs, diag_balance); + } + } +} + +void Cell::refine_triangle( + node_map_t& nodes, + double* x0, double* x1, double* x2, + double* e0, double* e1, double* e2, + double* t_norm, + int_t p_level, double *xs, double *ys, double* zs, bool diag_balance +){ + // Return if I'm at max_level or p_level + if (level >= p_level || level == max_level){ + return; + } + if (intersects_triangle(x0, x1, x2, e0, e1, e2, t_norm)){ + if(is_leaf()){ + divide(nodes, xs, ys, zs, true, diag_balance); + } + for(int_t i = 0; i < (1<refine_triangle( + nodes, x0, x1, x2, e0, e1, e2, t_norm, p_level, xs, ys, zs, diag_balance + ); + } + } +} + +void Cell::refine_vert_triang_prism( + node_map_t& nodes, + double* x0, double* x1, double* x2, double h, + double* e0, double* e1, double* e2, double* t_norm, + int_t p_level, double *xs, double *ys, double* zs, bool diag_balance +){ + // Return If I'm at max_level or p_level + if (level >= p_level || level == max_level){ + return; + } + if(intersects_vert_triang_prism(x0, x1, x2, h, e0, e1, e2, t_norm)){ + if(is_leaf()){ + divide(nodes, xs, ys, zs, true, diag_balance); + } + for(int_t i = 0; i < (1<refine_vert_triang_prism( + nodes, x0, x1, x2, h, e0, e1, e2, t_norm, p_level, xs, ys, zs, diag_balance + ); + } + } +} + +void Cell::refine_tetra( + node_map_t& nodes, + double* x0, double* x1, double* x2, double* x3, + double edge_tans[6][3], double face_normals[4][3], + int_t p_level, double *xs, double *ys, double* zs, bool diag_balance +){ + // Return If I'm at max_level or p_level + if (level >= p_level || level == max_level){ + return; + } + if (n_dim < 3){ + return; + } + if (intersects_tetra(x0, x1, x2, x3, edge_tans, face_normals)){ + if(is_leaf()){ + divide(nodes, xs, ys, zs, true, diag_balance); + } + for(int_t i = 0; i < (1<refine_tetra( + nodes, x0, x1, x2, x3, edge_tans, face_normals, p_level, xs, ys, zs, diag_balance + ); + } + } +} + +void Cell::void refine_plane(node_map_t& nodes, double* x0, double* normal, int_t p_level, double *xs, double *ys, double* zs, bool diag_balance=false){ + // Return If I'm at max_level or p_level + if (level >= p_level || level == max_level){ + return; + } + if (n_dim < 3){ + return; + } + if (intersects_plane(x0, normal)){ + if(is_leaf()){ + divide(nodes, xs, ys, zs, true, diag_balance); + } + for(int_t i = 0; i < (1<refine_plane( + nodes, x0, normal, p_level, xs, ys, zs, diag_balance + ); + } } } @@ -1336,26 +1302,15 @@ Cell* Cell::containing_cell(double x, double y, double z){ return children[ix + 2*iy + 4*iz]->containing_cell(x, y, z); }; -void Cell::find_overlapping_cells(int_vec_t& cells, double xm, double xp, double ym, double yp, double zm, double zp){ - // If I do not overlap the cells - if (xm > points[3]->location[0] || xp < points[0]->location[0]){ - return; - } - - if (ym > points[3]->location[1] || yp < points[0]->location[1]){ - return; - } - - if (n_dim>2 && (zm > points[7]->location[2] || zp < points[0]->location[2])){ - return; - } - - if(this->is_leaf()){ - cells.push_back(index); - return; - } - for(int_t i = 0; i < (1<find_overlapping_cells(cells, xm, xp, ym, yp, zm, zp); +void Cell::find_overlapping_cells(int_vec_t& cells, double* x0, double* x1){ + if(intersects_box(x0, x1)){ + if(this->is_leaf()){ + cells.push_back(index); + return; + } + for(int_t i = 0; i < (1<find_overlapping_cells(cells, x0, x1); + } } } @@ -2198,18 +2153,31 @@ Cell* Tree::containing_cell(double x, double y, double z){ return roots[iz][iy][ix]->containing_cell(x, y, z); } -int_vec_t Tree::find_overlapping_cells(double xm, double xp, double ym, double yp, double zm, double zp){ +int_vec_t Tree::find_overlapping_cells(double *x0, double *x1){ int_vec_t overlaps; for(int_t iz=0; izfind_overlapping_cells(overlaps, xm, xp, ym, yp, zm, zp); + roots[iz][iy][ix]->find_overlapping_cells(overlaps, x0, x1); } } } return overlaps; } +int_vec_t find_cells_along_line(double *x0, double *x1, bool segment=true){ + int_vec_t intersections; + for(int_t iz=0; izfind_overlapping_cells(overlaps, x0, x1); + } + } + } + return intersections; + } +} + void Tree::shift_cell_centers(double *shift){ for(int_t iz=0; iz Date: Fri, 12 Apr 2024 23:58:04 -0600 Subject: [PATCH 03/97] add functionality for generalized geometric object tests. --- discretize/_extensions/geom.cpp | 507 +++++++++++++++++++ discretize/_extensions/geom.h | 86 ++++ discretize/_extensions/geom.pxd | 24 + discretize/_extensions/meson.build | 2 +- discretize/_extensions/tree.cpp | 721 +--------------------------- discretize/_extensions/tree.h | 127 ++--- discretize/_extensions/tree.pxd | 11 +- discretize/_extensions/tree_ext.pyx | 153 +++--- 8 files changed, 768 insertions(+), 863 deletions(-) create mode 100644 discretize/_extensions/geom.cpp create mode 100644 discretize/_extensions/geom.h create mode 100644 discretize/_extensions/geom.pxd diff --git a/discretize/_extensions/geom.cpp b/discretize/_extensions/geom.cpp new file mode 100644 index 000000000..056118042 --- /dev/null +++ b/discretize/_extensions/geom.cpp @@ -0,0 +1,507 @@ +#include +#include +#include "geom.h" +#include +#include +// simple geometric objects for intersection tests with an aabb + +Ball::Ball(int_t dim, double* x0, double r){ + this->dim = dim; + this->x0 = x0; + this->r = r; + this->rsq = r * r; +} + +bool Ball::intersects_cell(double *a, double *b) const{ + // check if I intersect the ball + double dx; + double r2_test = 0.0; + for(int_t i=0; idim = dim; + this->x0 = x0; + this->x1 = x1; + this->segment = segment; + for(int_t i=0; i::infinity(); + double t_far = std::numeric_limits::infinity(); + double t0, t1; + + for(int_t i=0; i b[i])){ + return false; + } + if (segment){ + if(std::max(x0[i], x1[i]) < a[i]){ + return false; + } + if(std::min(x0[i], x1[i]) > b[i]){ + return false; + } + } + if (x0[i] != x1[i]){ + t0 = (a[i] - x0[i]) * inv_dx[i]; + t1 = (b[i] - x0[i]) * inv_dx[i]; + if (t0 > t1){ + std::swap(t0, t1); + } + t_near = std::max(t_near, t0); + t_far = std::min(t_far, t1); + if (t_near > t_far || (segment && (t_far < 0 || t_near > 1))){ + return false; + } + } + } + return true; +} + +Box::Box(int_t dim, double* x0, double *x1){ + this->dim = dim; + this->x0 = x0; + this->x1 = x1; +} + +bool Box::intersects_cell(double *a, double *b) const{ + for(int_t i=0; i b[i]){ + return false; + } + } + return true; +} + +Plane::Plane(int_t dim, double* origin, double *normal){ + this->dim = dim; + this->origin = origin; + this->normal = normal; +} + +bool Plane::intersects_cell(double *a, double *b) const{ + double half; + double s = 0.0; + double r = 0.0; + for(int_t i=0;idim = dim; + this->x0 = x0; + this->x1 = x1; + this->x2 = x2; + + for(int_t i=0; i 2){ + normal[0] = e0[1] * e1[2] - e0[2] * e1[1]; + normal[1] = e0[2] * e1[0] - e0[0] * e1[2]; + normal[2] = e0[0] * e1[1] - e0[1] * e1[0]; + } +} + +bool Triangle::intersects_cell(double *a, double *b) const{ + double center; + double v0[3], v1[3], v2[3], half[3]; + double vmin, vmax; + double p0, p1, p2, pmin, pmax, rad; + for(int_t i=0; i < dim; ++i){ + center = 0.5 * (b[i] + a[i]); + v0[i] = x0[i] - center; + v1[i] = x1[i] - center; + vmin = std::min(v0[i], v1[i]); + vmax = std::max(v0[i], v1[i]); + v2[i] = x2[i] - center; + vmin = std::min(vmin, v2[i]); + vmax = std::max(vmax, v2[i]); + half[i] = center - a[i]; + + // Bounding box check + if (vmin > half[i] || vmax < -half[i]){ + return false; + } + } + // first do the 3 edge cross tests that apply in 2D and 3D + + // edge 0 cross z_hat + //p0 = e0[1] * v0[0] - e0[0] * v0[1]; + p1 = e0[1] * v1[0] - e0[0] * v1[1]; + p2 = e0[1] * v2[0] - e0[0] * v2[1]; + pmin = std::min(p1, p2); + pmax = std::max(p1, p2); + rad = std::abs(e0[1]) * half[0] + std::abs(e0[0]) * half[1]; + if (pmin > rad || pmax < -rad){ + return false; + } + + // edge 1 cross z_hat + p0 = e1[1] * v0[0] - e1[0] * v0[1]; + p1 = e1[1] * v1[0] - e1[0] * v1[1]; + //p2 = e1[1] * v2[0] - e1[0] * v2[1]; + pmin = std::min(p0, p1); + pmax = std::max(p0, p1); + rad = std::abs(e1[1]) * half[0] + std::abs(e1[0]) * half[1]; + if (pmin > rad || pmax < -rad){ + return false; + } + + // edge 2 cross z_hat + //p0 = e2[1] * v0[0] - e2[0] * v0[1]; + p1 = e2[1] * v1[0] - e2[0] * v1[1]; + p2 = e2[1] * v2[0] - e2[0] * v2[1]; + pmin = std::min(p1, p2); + pmax = std::max(p1, p2); + rad = std::abs(e2[1]) * half[0] + std::abs(e2[0]) * half[1]; + if (pmin > rad || pmax < -rad){ + return false; + } + + if(dim > 2){ + // edge 0 cross x_hat + p0 = e0[2] * v0[1] - e0[1] * v0[2]; + //p1 = e0[2] * v1[1] - e0[1] * v1[2]; + p2 = e0[2] * v2[1] - e0[1] * v2[2]; + pmin = std::min(p0, p2); + pmax = std::max(p0, p2); + rad = std::abs(e0[2]) * half[1] + std::abs(e0[1]) * half[2]; + if (pmin > rad || pmax < -rad){ + return false; + } + // edge 0 cross y_hat + p0 = -e0[2] * v0[0] + e0[0] * v0[2]; + //p1 = -e0[2] * v1[0] + e0[0] * v1[2]; + p2 = -e0[2] * v2[0] + e0[0] * v2[2]; + pmin = std::min(p0, p2); + pmax = std::max(p0, p2); + rad = std::abs(e0[2]) * half[0] + std::abs(e0[0]) * half[2]; + if (pmin > rad || pmax < -rad){ + return false; + } + // edge 1 cross x_hat + p0 = e1[2] * v0[1] - e1[1] * v0[2]; + //p1 = e1[2] * v1[1] - e1[1] * v1[2]; + p2 = e1[2] * v2[1] - e1[1] * v2[2]; + pmin = std::min(p0, p2); + pmax = std::max(p0, p2); + rad = std::abs(e1[2]) * half[1] + std::abs(e1[1]) * half[2]; + if (pmin > rad || pmax < -rad){ + return false; + } + // edge 1 cross y_hat + p0 = -e1[2] * v0[0] + e1[0] * v0[2]; + //p1 = -e1[2] * v1[0] + e1[0] * v1[2]; + p2 = -e1[2] * v2[0] + e1[0] * v2[2]; + pmin = std::min(p0, p2); + pmax = std::max(p0, p2); + rad = std::abs(e1[2]) * half[0] + std::abs(e1[0]) * half[2]; + if (pmin > rad || pmax < -rad){ + return false; + } + // edge 2 cross x_hat + p0 = e2[2] * v0[1] - e2[1] * v0[2]; + p1 = e2[2] * v1[1] - e2[1] * v1[2]; + //p2 = e2[2] * v2[1] - e2[1] * v2[2]; + pmin = std::min(p0, p1); + pmax = std::max(p0, p1); + rad = std::abs(e2[2]) * half[1] + std::abs(e2[1]) * half[2]; + if (pmin > rad || pmax < -rad){ + return false; + } + // edge 2 cross y_hat + p0 = -e2[2] * v0[0] + e2[0] * v0[2]; + p1 = -e2[2] * v1[0] + e2[0] * v1[2]; + //p2 = -e2[2] * v2[0] + e2[0] * v2[2]; + pmin = std::min(p0, p1); + pmax = std::max(p0, p1); + rad = std::abs(e2[2]) * half[0] + std::abs(e2[0]) * half[2]; + if (pmin > rad || pmax < -rad){ + return false; + } + + // triangle normal axis + pmin = 0.0; + pmax = 0.0; + for(int_t i=0; i 0){ + pmin += normal[i] * (-half[i] - v0[i]); + pmax += normal[i] * (half[i] - v0[i]); + }else{ + pmin += normal[i] * (half[i] - v0[i]); + pmax += normal[i] * (-half[i] - v0[i]); + } + } + if (pmin > 0 || pmax < 0){ + return false; + } + } + return true; +} + +VerticalTriangularPrism::VerticalTriangularPrism(int_t dim, double* x0, double *x1, double *x2, double h) : Triangle(dim, x0, x1, x2){ + this->h = h; +} + +bool VerticalTriangularPrism::intersects_cell(double *a, double *b) const{ + double center; + double v0[3], v1[3], v2[3], half[3]; + double vmin, vmax; + double p0, p1, p2, p3, pmin, pmax, rad; + for(int_t i=0; i < dim; ++i){ + center = 0.5 * (a[i] + b[i]); + v0[i] = x0[i] - center; + v1[i] = x1[i] - center; + vmin = std::min(v0[i], v1[i]); + vmax = std::max(v0[i], v1[i]); + v2[i] = x2[i] - center; + vmin = std::min(vmin, v2[i]); + vmax = std::max(vmax, v2[i]); + if(i == 2){ + vmax += h; + } + half[i] = center - a[i]; + + // Bounding box check + if (vmin > half[i] || vmax < -half[i]){ + return false; + } + } + // first do the 3 edge cross tests that apply in 2D and 3D + + // edge 0 cross z_hat + //p0 = e0[1] * v0[0] - e0[0] * v0[1]; + p1 = e0[1] * v1[0] - e0[0] * v1[1]; + p2 = e0[1] * v2[0] - e0[0] * v2[1]; + pmin = std::min(p1, p2); + pmax = std::max(p1, p2); + rad = std::abs(e0[1]) * half[0] + std::abs(e0[0]) * half[1]; + if (pmin > rad || pmax < -rad){ + return false; + } + + // edge 1 cross z_hat + p0 = e1[1] * v0[0] - e1[0] * v0[1]; + p1 = e1[1] * v1[0] - e1[0] * v1[1]; + //p2 = e1[1] * v2[0] - e1[0] * v2[1]; + pmin = std::min(p0, p1); + pmax = std::max(p0, p1); + rad = std::abs(e1[1]) * half[0] + std::abs(e1[0]) * half[1]; + if (pmin > rad || pmax < -rad){ + return false; + } + + // edge 2 cross z_hat + //p0 = e2[1] * v0[0] - e2[0] * v0[1]; + p1 = e2[1] * v1[0] - e2[0] * v1[1]; + p2 = e2[1] * v2[0] - e2[0] * v2[1]; + pmin = std::min(p1, p2); + pmax = std::max(p1, p2); + rad = std::abs(e2[1]) * half[0] + std::abs(e2[0]) * half[1]; + if (pmin > rad || pmax < -rad){ + return false; + } + + // edge 0 cross x_hat + p0 = e0[2] * v0[1] - e0[1] * v0[2]; + p1 = e0[2] * v0[1] - e0[1] * (v0[2] + h); + p2 = e0[2] * v2[1] - e0[1] * v2[2]; + p3 = e0[2] * v2[1] - e0[1] * (v2[2] + h); + pmin = std::min(std::min(std::min(p0, p1), p2), p3); + pmax = std::max(std::max(std::max(p0, p1), p2), p3); + rad = std::abs(e0[2]) * half[1] + std::abs(e0[1]) * half[2]; + if (pmin > rad || pmax < -rad){ + return false; + } + // edge 0 cross y_hat + p0 = -e0[2] * v0[0] + e0[0] * v0[2]; + p1 = -e0[2] * v0[0] + e0[0] * (v0[2] + h); + p2 = -e0[2] * v2[0] + e0[0] * v2[2]; + p3 = -e0[2] * v2[0] + e0[0] * (v2[2] + h); + pmin = std::min(std::min(std::min(p0, p1), p2), p3); + pmax = std::max(std::max(std::max(p0, p1), p2), p3); + rad = std::abs(e0[2]) * half[0] + std::abs(e0[0]) * half[2]; + if (pmin > rad || pmax < -rad){ + return false; + } + // edge 1 cross x_hat + p0 = e1[2] * v0[1] - e1[1] * v0[2]; + p1 = e1[2] * v0[1] - e1[1] * (v0[2] + h); + p2 = e1[2] * v2[1] - e1[1] * v2[2]; + p3 = e1[2] * v2[1] - e1[1] * (v2[2] + h); + pmin = std::min(std::min(std::min(p0, p1), p2), p3); + pmax = std::max(std::max(std::max(p0, p1), p2), p3); + rad = std::abs(e1[2]) * half[1] + std::abs(e1[1]) * half[2]; + if (pmin > rad || pmax < -rad){ + return false; + } + // edge 1 cross y_hat + p0 = -e1[2] * v0[0] + e1[0] * v0[2]; + p1 = -e1[2] * v0[0] + e1[0] * (v0[2] + h); + p2 = -e1[2] * v2[0] + e1[0] * v2[2]; + p3 = -e1[2] * v2[0] + e1[0] * (v2[2] + h); + pmin = std::min(std::min(std::min(p0, p1), p2), p3); + pmax = std::max(std::max(std::max(p0, p1), p2), p3); + rad = std::abs(e1[2]) * half[0] + std::abs(e1[0]) * half[2]; + if (pmin > rad || pmax < -rad){ + return false; + } + // edge 2 cross x_hat + p0 = e2[2] * v0[1] - e2[1] * v0[2]; + p1 = e2[2] * v0[1] - e2[1] * (v0[2] + h); + p2 = e2[2] * v1[1] - e2[1] * v1[2]; + p3 = e2[2] * v1[1] - e2[1] * (v1[2] + h); + pmin = std::min(std::min(std::min(p0, p1), p2), p3); + pmax = std::max(std::max(std::max(p0, p1), p2), p3); + rad = std::abs(e2[2]) * half[1] + std::abs(e2[1]) * half[2]; + if (pmin > rad || pmax < -rad){ + return false; + } + // edge 2 cross y_hat + p0 = -e2[2] * v0[0] + e2[0] * v0[2]; + p1 = -e2[2] * v0[0] + e2[0] * (v0[2] + h); + p2 = -e2[2] * v1[0] + e2[0] * v1[2]; + p3 = -e2[2] * v1[0] + e2[0] * (v1[2] + h); + pmin = std::min(std::min(std::min(p0, p1), p2), p3); + pmax = std::max(std::max(std::max(p0, p1), p2), p3); + rad = std::abs(e2[2]) * half[0] + std::abs(e2[0]) * half[2]; + if (pmin > rad || pmax < -rad){ + return false; + } + + // triangle normal axis + p0 = normal[0] * v0[0] + normal[1] * v0[1] + normal[2] * v0[2]; + p1 = normal[0] * v0[0] + normal[1] * v0[1] + normal[2] * (v0[2] + h); + pmin = std::min(p0, p1); + pmax = std::max(p0, p1); + rad = std::abs(normal[0]) * half[0] + std::abs(normal[1]) * half[1] + std::abs(normal[2]) * half[2]; + if (pmin > rad || pmax < -rad){ + return false; + } + // the axes defined by the three vertical prism faces + // should already be tested by the e0, e1, e2 cross z_hat tests + return true; +} + +Tetrahedron::Tetrahedron(int_t dim, double* x0, double *x1, double *x2, double *x3){ + this->dim = dim; + this->x0 = x0; + this->x1 = x1; + this->x2 = x2; + this->x3 = x3; + for(int_t i=0; i half[i] || pmax < -half[i]){ + return false; + } + } + // first do the 3 edge cross tests that apply in 2D and 3D + const double *axis; + + for(int_t i=0; i<6; ++i){ + // edge cross [1, 0, 0] + p0 = edge_tans[i][2] * v0[1] - edge_tans[i][1] * v0[2]; + p1 = edge_tans[i][2] * v1[1] - edge_tans[i][1] * v1[2]; + p2 = edge_tans[i][2] * v2[1] - edge_tans[i][1] * v2[2]; + p3 = edge_tans[i][2] * v3[1] - edge_tans[i][1] * v3[2]; + pmin = std::min(std::min(std::min(p0, p1), p2), p3); + pmax = std::max(std::max(std::max(p0, p1), p2), p3); + rad = std::abs(edge_tans[i][2]) * half[1] + std::abs(edge_tans[i][1]) * half[2]; + if (pmin > rad || pmax < -rad){ + return false; + } + + p0 = -edge_tans[i][2] * v0[0] + edge_tans[i][0] * v0[2]; + p1 = -edge_tans[i][2] * v1[0] + edge_tans[i][0] * v1[2]; + p2 = -edge_tans[i][2] * v2[0] + edge_tans[i][0] * v2[2]; + p3 = -edge_tans[i][2] * v3[0] + edge_tans[i][0] * v3[2]; + pmin = std::min(std::min(std::min(p0, p1), p2), p3); + pmax = std::max(std::max(std::max(p0, p1), p2), p3); + rad = std::abs(edge_tans[i][2]) * half[0] + std::abs(edge_tans[i][0]) * half[2]; + if (pmin > rad || pmax < -rad){ + return false; + } + + p0 = edge_tans[i][1] * v0[0] - edge_tans[i][0] * v0[1]; + p1 = edge_tans[i][1] * v1[0] - edge_tans[i][0] * v1[1]; + p2 = edge_tans[i][1] * v2[0] - edge_tans[i][0] * v2[1]; + p3 = edge_tans[i][1] * v3[0] - edge_tans[i][0] * v3[1]; + pmin = std::min(std::min(std::min(p0, p1), p2), p3); + pmax = std::max(std::max(std::max(p0, p1), p2), p3); + rad = std::abs(edge_tans[i][1]) * half[0] + std::abs(edge_tans[i][0]) * half[1]; + if (pmin > rad || pmax < -rad){ + return false; + } + } + // triangle face normals + for(int_t i=0; i<4; ++i){ + axis = face_normals[i]; + p0 = axis[0] * v0[0] + axis[1] * v0[1] + axis[2] * v0[2]; + p1 = axis[0] * v1[0] + axis[1] * v1[1] + axis[2] * v1[2]; + p2 = axis[0] * v2[0] + axis[1] * v2[1] + axis[2] * v2[2]; + p3 = axis[0] * v3[0] + axis[1] * v3[1] + axis[2] * v3[2]; + pmin = std::min(std::min(std::min(p0, p1), p2), p3); + pmax = std::max(std::max(std::max(p0, p1), p2), p3); + rad = std::abs(axis[0]) * half[0] + std::abs(axis[1]) * half[1] + std::abs(axis[2]) * half[2]; + if (pmin > rad || pmax < -rad){ + return false; + } + } + return true; +} \ No newline at end of file diff --git a/discretize/_extensions/geom.h b/discretize/_extensions/geom.h new file mode 100644 index 000000000..06f96f874 --- /dev/null +++ b/discretize/_extensions/geom.h @@ -0,0 +1,86 @@ +#ifndef __GEOM_H +#define __GEOM_H +// simple geometric objects for intersection tests with an aabb + +typedef std::size_t int_t; + +class Geometric{ + public: + int_t dim; + virtual bool intersects_cell(double *a, double *b) const = 0; +}; + +class Ball : public Geometric{ + public: + double *x0; + double r; + double rsq; + + Ball(int_t dim, double* x0, double r); + virtual bool intersects_cell(double *a, double *b) const; +}; + +class Line : public Geometric{ + public: + double *x0; + double *x1; + double inv_dx[3]; + bool segment; + + Line(int_t dim, double* x0, double *x1, bool segment); + virtual bool intersects_cell(double *a, double *b) const; +}; + +class Box : public Geometric{ + public: + double *x0; + double *x1; + + Box(int_t dim, double* x0, double *x1); + virtual bool intersects_cell(double *a, double *b) const; +}; + +class Plane : public Geometric{ + public: + double *origin; + double *normal; + + Plane(int_t dim, double* origin, double *normal); + virtual bool intersects_cell(double *a, double *b) const; +}; + +class Triangle : public Geometric{ + public: + double *x0; + double *x1; + double *x2; + double e0[3]; + double e1[3]; + double e2[3]; + double normal[3]; + + Triangle(int_t dim, double* x0, double *x1, double *x2); + virtual bool intersects_cell(double *a, double *b) const; +}; + +class VerticalTriangularPrism : public Triangle{ + public: + double h; + VerticalTriangularPrism(int_t dim, double* x0, double *x1, double *x2, double h); + virtual bool intersects_cell(double *a, double *b) const; +}; + +class Tetrahedron : public Geometric{ + public: + double *x0; + double *x1; + double *x2; + double *x3; + double edge_tans[6][3]; + double face_normals[4][3]; + + Tetrahedron(int_t dim, double* x0, double *x1, double *x2, double *x3); + virtual bool intersects_cell(double *a, double *b) const; + }; + +#endif \ No newline at end of file diff --git a/discretize/_extensions/geom.pxd b/discretize/_extensions/geom.pxd new file mode 100644 index 000000000..c2efe6bab --- /dev/null +++ b/discretize/_extensions/geom.pxd @@ -0,0 +1,24 @@ +from libcpp cimport bool + +cdef extern from "geom.h": + ctypedef int int_t + cdef cppclass Ball: + Ball(int_t dim, double * x0, double r) + + cdef cppclass Line: + Line(int_t dim, double * x0, double *x1, bool segment) + + cdef cppclass Box: + Box(int_t dim, double * x0, double *x1) + + cdef cppclass Plane: + Plane(int_t dim, double * origin, double *normal) + + cdef cppclass Triangle: + Triangle(int_t dim, double * x0, double *x1, double *x2) + + cdef cppclass VerticalTriangularPrism: + VerticalTriangularPrism(int_t dim, double * x0, double *x1, double *x2, double h) + + cdef cppclass Tetrahedron: + Tetrahedron(int_t dim, double * x0, double *x1, double *x2, double *x3) \ No newline at end of file diff --git a/discretize/_extensions/meson.build b/discretize/_extensions/meson.build index 590fca517..fc4af86d2 100644 --- a/discretize/_extensions/meson.build +++ b/discretize/_extensions/meson.build @@ -70,7 +70,7 @@ py.extension_module( py.extension_module( 'tree_ext', - ['tree_ext.pyx' , 'tree.cpp'], + ['tree_ext.pyx' , 'tree.cpp', 'geom.cpp'], include_directories: incdir_numpy, cpp_args: cython_cpp_args, install: true, diff --git a/discretize/_extensions/tree.cpp b/discretize/_extensions/tree.cpp index ec1a2496d..5773b62b3 100644 --- a/discretize/_extensions/tree.cpp +++ b/discretize/_extensions/tree.cpp @@ -1,6 +1,7 @@ #include #include #include "tree.h" +#include "geom.h" #include #include #include @@ -369,451 +370,16 @@ void Cell::shift_centers(double *shift){ bool Cell::intersects_point(double *x){ // A simple bounding box check: - int_t last_point = (ndim < 3)? 3 : 7; + double *p0 = points[0]->location; + double *p1 = (n_dim < 3)? points[3]->location : points[7]->location; for(int_t i=0; i < n_dim; ++i){ - if(x[i] < points[0].location[i] || x[i] > points[last_point].location[i]){ - return false - } - } - return true; -} - -bool Cell::intersects_ball(double *x, double rsq){ - - // check if I intersect the ball - double xp = std::max(points[0]->location[0], std::min(center[0], points[3]->location[0])); - double yp = std::max(points[0]->location[1], std::min(center[1], points[3]->location[1])); - double zp = 0.0; - if (n_dim > 2){ - zp = std::max(points[0]->location[2], std::min(center[2], points[7]->location[2])); - } - - // xp, yp, zp is closest point in the cell to the center of the circle - // check if that point is in the circle! - double r2_test = (xp - center[0])*(xp - center[0]) + (yp - center[1]) *(yp - center[1]); - if (n_dim > 2){ - r2_test += (zp - center[2])*(zp - center[2]); - } - return r2_test < rsq; -} - -bool Cell::intersects_line(double *x0, double *x1, double* i_dx, bool segment=true){ - // bounding box intersection if doing a segment test - Node *p0 = points[0]; - Node *p1 = (ndim < 3) ? points[3] : points[7]; - - double t_near = -std::numeric_limits::infinity(); - double t_far = std::numeric_limits::infinity(); - double t0, t1, dx_sign, dx_abs; - - for(int_t i=0; ilocation[i] || x0[i] > p1->location[i])){ - return false; - } - if (segment){ - if(std:max(x0[i], x1[i]) < p0->location[i]){ - return false; - } - if(std:min(x0[i], x1[i]) > p1->location[i]){ - return false; - } - } - if (x0[i] != x1[i]){ - t0 = (p0->location[i] - x0[i]) * i_dx[i]; - t1 = (p1->location[i] - x0[i]) * i_dx[i]; - if (t0 > t1){ - std::swap(t0, t1); - } - t_near = std::max(t_near, t0); - t_far = std::min(t_far, t1); - if (t_near > t_far || (segment && (t_far < 0 || t_near > 1))){ - return false; - } - } - } - return true; -} - -bool Cell::intersects_box(double *x0, double *x1){ - Node *p0 = points[0]; - Node *p1 = (ndim < 3) ? points[3] : points[7]; - - for(int_t i; ilocation[i]){ - return false; - } - if(std::min(x0[i], x1[i]) > p1->location[i]){ - return false; - } - } - return true; -} - -bool Cell::intersects_triangle(double *x0, double *x1, double *x2, double *e0, double *e1, double *e2, double *t_norm){ - // then check to see if I intersect the segment - double v0[3], v1[3], v2[3], half[3]; - double vmin, vmax; - double p0, p1, p2, pmin, pmax, rad; - for(int_t i=0; i < n_dim; ++i){ - v0[i] = x0[i] - location[i]; - v1[i] = x1[i] - location[i]; - vmin = std::min(v0[i], v1[i]); - vmax = std::max(v0[i], v1[i]); - v2[i] = x2[i] - location[i]; - vmin = std::min(vmin, v2[i]); - vmax = std::max(vmax, v2[i]); - half[i] = location[i] - points[0]->location[i]; - - // Bounding box check - if (vmin > half[i] || vmax < -half[i]){ - return false; - } - } - // first do the 3 edge cross tests that apply in 2D and 3D - - // edge 0 cross z_hat - //p0 = e0[1] * v0[0] - e0[0] * v0[1]; - p1 = e0[1] * v1[0] - e0[0] * v1[1]; - p2 = e0[1] * v2[0] - e0[0] * v2[1]; - pmin = std::min(p1, p2); - pmax = std::max(p1, p2); - rad = std::abs(e0[1]) * half[0] + std::abs(e0[0]) * half[1]; - if (pmin > rad || pmax < -rad){ - return false; - } - - // edge 1 cross z_hat - p0 = e1[1] * v0[0] - e1[0] * v0[1]; - p1 = e1[1] * v1[0] - e1[0] * v1[1]; - //p2 = e1[1] * v2[0] - e1[0] * v2[1]; - pmin = std::min(p0, p1); - pmax = std::max(p0, p1); - rad = std::abs(e1[1]) * half[0] + std::abs(e1[0]) * half[1]; - if (pmin > rad || pmax < -rad){ - return false; - } - - // edge 2 cross z_hat - //p0 = e2[1] * v0[0] - e2[0] * v0[1]; - p1 = e2[1] * v1[0] - e2[0] * v1[1]; - p2 = e2[1] * v2[0] - e2[0] * v2[1]; - pmin = std::min(p1, p2); - pmax = std::max(p1, p2); - rad = std::abs(e2[1]) * half[0] + std::abs(e2[0]) * half[1]; - if (pmin > rad || pmax < -rad){ - return false; - } - - if(n_dim > 2){ - // edge 0 cross x_hat - p0 = e0[2] * v0[1] - e0[1] * v0[2]; - //p1 = e0[2] * v1[1] - e0[1] * v1[2]; - p2 = e0[2] * v2[1] - e0[1] * v2[2]; - pmin = std::min(p0, p2); - pmax = std::max(p0, p2); - rad = std::abs(e0[2]) * half[1] + std::abs(e0[1]) * half[2]; - if (pmin > rad || pmax < -rad){ - return false; - } - // edge 0 cross y_hat - p0 = -e0[2] * v0[0] + e0[0] * v0[2]; - //p1 = -e0[2] * v1[0] + e0[0] * v1[2]; - p2 = -e0[2] * v2[0] + e0[0] * v2[2]; - pmin = std::min(p0, p2); - pmax = std::max(p0, p2); - rad = std::abs(e0[2]) * half[0] + std::abs(e0[0]) * half[2]; - if (pmin > rad || pmax < -rad){ - return false; - } - // edge 1 cross x_hat - p0 = e1[2] * v0[1] - e1[1] * v0[2]; - //p1 = e1[2] * v1[1] - e1[1] * v1[2]; - p2 = e1[2] * v2[1] - e1[1] * v2[2]; - pmin = std::min(p0, p2); - pmax = std::max(p0, p2); - rad = std::abs(e1[2]) * half[1] + std::abs(e1[1]) * half[2]; - if (pmin > rad || pmax < -rad){ - return false; - } - // edge 1 cross y_hat - p0 = -e1[2] * v0[0] + e1[0] * v0[2]; - //p1 = -e1[2] * v1[0] + e1[0] * v1[2]; - p2 = -e1[2] * v2[0] + e1[0] * v2[2]; - pmin = std::min(p0, p2); - pmax = std::max(p0, p2); - rad = std::abs(e1[2]) * half[0] + std::abs(e1[0]) * half[2]; - if (pmin > rad || pmax < -rad){ - return false; - } - // edge 2 cross x_hat - p0 = e2[2] * v0[1] - e2[1] * v0[2]; - p1 = e2[2] * v1[1] - e2[1] * v1[2]; - //p2 = e2[2] * v2[1] - e2[1] * v2[2]; - pmin = std::min(p0, p1); - pmax = std::max(p0, p1); - rad = std::abs(e2[2]) * half[1] + std::abs(e2[1]) * half[2]; - if (pmin > rad || pmax < -rad){ - return false; - } - // edge 2 cross y_hat - p0 = -e2[2] * v0[0] + e2[0] * v0[2]; - p1 = -e2[2] * v1[0] + e2[0] * v1[2]; - //p2 = -e2[2] * v2[0] + e2[0] * v2[2]; - pmin = std::min(p0, p1); - pmax = std::max(p0, p1); - rad = std::abs(e2[2]) * half[0] + std::abs(e2[0]) * half[2]; - if (pmin > rad || pmax < -rad){ - return false; - } - - // triangle normal axis - pmin = 0.0; - pmax = 0.0; - for(int_t i=0; i 0){ - pmin += t_norm[i] * (-half[i] - v0[i]); - pmax += t_norm[i] * (half[i] - v0[i]); - }else{ - pmin += t_norm[i] * (half[i] - v0[i]); - pmax += t_norm[i] * (-half[i] - v0[i]); - } - } - if (pmin > 0 || pmax < 0){ - return false; - } - } - return true; -} - -bool Cell::intersects_vert_triang_prism(double *x0, double *x1, double *x2, double h, double* e0, double* e1, double* e2, double* t_norm){ - // check all the AABB faces - double v0[3], v1[3], v2[3], half[3]; - double vmin, vmax; - double p0, p1, p2, p3, pmin, pmax, rad; - for(int_t i=0; i < n_dim; ++i){ - v0[i] = x0[i] - location[i]; - v1[i] = x1[i] - location[i]; - vmin = std::min(v0[i], v1[i]); - vmax = std::max(v0[i], v1[i]); - v2[i] = x2[i] - location[i]; - vmin = std::min(vmin, v2[i]); - vmax = std::max(vmax, v2[i]); - if(i == 2){ - vmax += h; - } - half[i] = location[i] - points[0]->location[i]; - - // Bounding box check - if (vmin > half[i] || vmax < -half[i]){ + if(x[i] < p0[i] || x[i] > p1[i]){ return false; } } - // first do the 3 edge cross tests that apply in 2D and 3D - - // edge 0 cross z_hat - //p0 = e0[1] * v0[0] - e0[0] * v0[1]; - p1 = e0[1] * v1[0] - e0[0] * v1[1]; - p2 = e0[1] * v2[0] - e0[0] * v2[1]; - pmin = std::min(p1, p2); - pmax = std::max(p1, p2); - rad = std::abs(e0[1]) * half[0] + std::abs(e0[0]) * half[1]; - if (pmin > rad || pmax < -rad){ - return false; - } - - // edge 1 cross z_hat - p0 = e1[1] * v0[0] - e1[0] * v0[1]; - p1 = e1[1] * v1[0] - e1[0] * v1[1]; - //p2 = e1[1] * v2[0] - e1[0] * v2[1]; - pmin = std::min(p0, p1); - pmax = std::max(p0, p1); - rad = std::abs(e1[1]) * half[0] + std::abs(e1[0]) * half[1]; - if (pmin > rad || pmax < -rad){ - return false; - } - - // edge 2 cross z_hat - //p0 = e2[1] * v0[0] - e2[0] * v0[1]; - p1 = e2[1] * v1[0] - e2[0] * v1[1]; - p2 = e2[1] * v2[0] - e2[0] * v2[1]; - pmin = std::min(p1, p2); - pmax = std::max(p1, p2); - rad = std::abs(e2[1]) * half[0] + std::abs(e2[0]) * half[1]; - if (pmin > rad || pmax < -rad){ - return false; - } - - // edge 0 cross x_hat - p0 = e0[2] * v0[1] - e0[1] * v0[2]; - p1 = e0[2] * v0[1] - e0[1] * (v0[2] + h); - p2 = e0[2] * v2[1] - e0[1] * v2[2]; - p3 = e0[2] * v2[1] - e0[1] * (v2[2] + h); - pmin = std::min(std::min(std::min(p0, p1), p2), p3); - pmax = std::max(std::max(std::max(p0, p1), p2), p3); - rad = std::abs(e0[2]) * half[1] + std::abs(e0[1]) * half[2]; - if (pmin > rad || pmax < -rad){ - return false; - } - // edge 0 cross y_hat - p0 = -e0[2] * v0[0] + e0[0] * v0[2]; - p1 = -e0[2] * v0[0] + e0[0] * (v0[2] + h); - p2 = -e0[2] * v2[0] + e0[0] * v2[2]; - p3 = -e0[2] * v2[0] + e0[0] * (v2[2] + h); - pmin = std::min(std::min(std::min(p0, p1), p2), p3); - pmax = std::max(std::max(std::max(p0, p1), p2), p3); - rad = std::abs(e0[2]) * half[0] + std::abs(e0[0]) * half[2]; - if (pmin > rad || pmax < -rad){ - return false; - } - // edge 1 cross x_hat - p0 = e1[2] * v0[1] - e1[1] * v0[2]; - p1 = e1[2] * v0[1] - e1[1] * (v0[2] + h); - p2 = e1[2] * v2[1] - e1[1] * v2[2]; - p3 = e1[2] * v2[1] - e1[1] * (v2[2] + h); - pmin = std::min(std::min(std::min(p0, p1), p2), p3); - pmax = std::max(std::max(std::max(p0, p1), p2), p3); - rad = std::abs(e1[2]) * half[1] + std::abs(e1[1]) * half[2]; - if (pmin > rad || pmax < -rad){ - return false; - } - // edge 1 cross y_hat - p0 = -e1[2] * v0[0] + e1[0] * v0[2]; - p1 = -e1[2] * v0[0] + e1[0] * (v0[2] + h); - p2 = -e1[2] * v2[0] + e1[0] * v2[2]; - p3 = -e1[2] * v2[0] + e1[0] * (v2[2] + h); - pmin = std::min(std::min(std::min(p0, p1), p2), p3); - pmax = std::max(std::max(std::max(p0, p1), p2), p3); - rad = std::abs(e1[2]) * half[0] + std::abs(e1[0]) * half[2]; - if (pmin > rad || pmax < -rad){ - return false; - } - // edge 2 cross x_hat - p0 = e2[2] * v0[1] - e2[1] * v0[2]; - p1 = e2[2] * v0[1] - e2[1] * (v0[2] + h); - p2 = e2[2] * v1[1] - e2[1] * v1[2]; - p3 = e2[2] * v1[1] - e2[1] * (v1[2] + h); - pmin = std::min(std::min(std::min(p0, p1), p2), p3); - pmax = std::max(std::max(std::max(p0, p1), p2), p3); - rad = std::abs(e2[2]) * half[1] + std::abs(e2[1]) * half[2]; - if (pmin > rad || pmax < -rad){ - return false; - } - // edge 2 cross y_hat - p0 = -e2[2] * v0[0] + e2[0] * v0[2]; - p1 = -e2[2] * v0[0] + e2[0] * (v0[2] + h); - p2 = -e2[2] * v1[0] + e2[0] * v1[2]; - p3 = -e2[2] * v1[0] + e2[0] * (v1[2] + h); - pmin = std::min(std::min(std::min(p0, p1), p2), p3); - pmax = std::max(std::max(std::max(p0, p1), p2), p3); - rad = std::abs(e2[2]) * half[0] + std::abs(e2[0]) * half[2]; - if (pmin > rad || pmax < -rad){ - return false; - } - - // triangle normal axis - p0 = t_norm[0] * v0[0] + t_norm[1] * v0[1] + t_norm[2] * v0[2]; - p1 = t_norm[0] * v0[0] + t_norm[1] * v0[1] + t_norm[2] * (v0[2] + h); - pmin = std::min(p0, p1); - pmax = std::max(p0, p1); - rad = std::abs(t_norm[0]) * half[0] + std::abs(t_norm[1]) * half[1] + std::abs(t_norm[2]) * half[2]; - if (pmin > rad || pmax < -rad){ - return false; - } - // the axes defined by the three vertical prism faces - // should already be tested by the e0, e1, e2 cross z_hat tests return true; } -bool Cell::intersects_tetra( - double* x0, double* x1, double* x2, double* x3, - double edge_tans[6][3], double face_normals[4][3] -){ - // then check to see if I intersect the segment - double v0[3], v1[3], v2[3], v3[3], half[3]; - double p0, p1, p2, p3, pmin, pmax, rad; - for(int_t i=0; i < n_dim; ++i){ - v0[i] = x0[i] - location[i]; - v1[i] = x1[i] - location[i]; - v2[i] = x2[i] - location[i]; - v3[i] = x3[i] - location[i]; - half[i] = location[i] - points[0]->location[i]; - pmin = std::min(std::min(std::min(v0[i], v1[i]), v2[i]), v3[i]); - pmax = std::max(std::max(std::max(v0[i], v1[i]), v2[i]), v3[i]); - // Bounding box check - if (pmin > half[i] || pmax < -half[i]){ - return false; - } - } - // first do the 3 edge cross tests that apply in 2D and 3D - double *axis; - - for(int_t i=0; i<6; ++i){ - // edge cross [1, 0, 0] - p0 = edge_tans[i][2] * v0[1] - edge_tans[i][1] * v0[2]; - p1 = edge_tans[i][2] * v1[1] - edge_tans[i][1] * v1[2]; - p2 = edge_tans[i][2] * v2[1] - edge_tans[i][1] * v2[2]; - p3 = edge_tans[i][2] * v3[1] - edge_tans[i][1] * v3[2]; - pmin = std::min(std::min(std::min(p0, p1), p2), p3); - pmax = std::max(std::max(std::max(p0, p1), p2), p3); - rad = std::abs(edge_tans[i][2]) * half[1] + std::abs(edge_tans[i][1]) * half[2]; - if (pmin > rad || pmax < -rad){ - return false; - } - - p0 = -edge_tans[i][2] * v0[0] + edge_tans[i][0] * v0[2]; - p1 = -edge_tans[i][2] * v1[0] + edge_tans[i][0] * v1[2]; - p2 = -edge_tans[i][2] * v2[0] + edge_tans[i][0] * v2[2]; - p3 = -edge_tans[i][2] * v3[0] + edge_tans[i][0] * v3[2]; - pmin = std::min(std::min(std::min(p0, p1), p2), p3); - pmax = std::max(std::max(std::max(p0, p1), p2), p3); - rad = std::abs(edge_tans[i][2]) * half[0] + std::abs(edge_tans[i][0]) * half[2]; - if (pmin > rad || pmax < -rad){ - return false; - } - - p0 = edge_tans[i][1] * v0[0] - edge_tans[i][0] * v0[1]; - p1 = edge_tans[i][1] * v1[0] - edge_tans[i][0] * v1[1]; - p2 = edge_tans[i][1] * v2[0] - edge_tans[i][0] * v2[1]; - p3 = edge_tans[i][1] * v3[0] - edge_tans[i][0] * v3[1]; - pmin = std::min(std::min(std::min(p0, p1), p2), p3); - pmax = std::max(std::max(std::max(p0, p1), p2), p3); - rad = std::abs(edge_tans[i][1]) * half[0] + std::abs(edge_tans[i][0]) * half[1]; - if (pmin > rad || pmax < -rad){ - return false; - } - } - // triangle face normals - for(int_t i=0; i<4; ++i){ - axis = face_normals[i]; - p0 = axis[0] * v0[0] + axis[1] * v0[1] + axis[2] * v0[2]; - p1 = axis[0] * v1[0] + axis[1] * v1[1] + axis[2] * v1[2]; - p2 = axis[0] * v2[0] + axis[1] * v2[1] + axis[2] * v2[2]; - p3 = axis[0] * v3[0] + axis[1] * v3[1] + axis[2] * v3[2]; - pmin = std::min(std::min(std::min(p0, p1), p2), p3); - pmax = std::max(std::max(std::max(p0, p1), p2), p3); - rad = std::abs(axis[0]) * half[0] + std::abs(axis[1]) * half[1] + std::abs(axis[2]) * half[2]; - if (pmin > rad || pmax < -rad){ - return false; - } - } - return true; -} - -bool Cell::intersects_plane(double *x0, double *normal){ - double half - double s = 0; - double r = 0; - Node *p0 = points[0]; - Node *p1 = (ndim < 3) points[3] : points[7]; - for(int_t i=0;ilocation[i]; - r += half + std::abs(normal[i]); - s += normal[i] * half[i] - x0[i]; - } - return std::abs(s) <= r; -} - void Cell::insert_cell(node_map_t& nodes, double *new_cell, int_t p_level, double *xs, double *ys, double *zs, bool diag_balance){ //Inserts a cell at min(max_level,p_level) that contains the given point if(p_level > level){ @@ -829,147 +395,6 @@ void Cell::insert_cell(node_map_t& nodes, double *new_cell, int_t p_level, doubl } }; -void Cell::refine_ball(node_map_t& nodes, double* center, double r2, int_t p_level, double *xs, double *ys, double* zs, bool diag_balance){ - // early exit if my level is higher than or equal to target - if (level >= p_level || level == max_level){ - return; - } - // if I intersect cell, I will need to be divided (if I'm not already) - if (intersects_ball(center, r2)){ - if(is_leaf()){ - divide(nodes, xs, ys, zs, true, diag_balance); - } - // recurse into children - for(int_t i = 0; i < (1<refine_ball(nodes, center, r2, p_level, xs, ys, zs, diag_balance); - } - } -} - -void Cell::refine_box(node_map_t& nodes, double* x0, double* x1, int_t p_level, double *xs, double *ys, double* zs, bool diag_balance){ - // early exit if my level is higher than target - if (level >= p_level || level == max_level){ - return; - } - // If I intersect cell, I will need to be divided (if I'm not already) - if (intersects_box(x0, x1)){ - if(is_leaf()){ - divide(nodes, xs, ys, zs, true, diag_balance); - } - // recurse into children - for(int_t i = 0; i < (1<refine_box(nodes, x0, x1, p_level, xs, ys, zs, diag_balance); - } - } -} - -void Cell::refine_line(node_map_t& nodes, double* x0, double* x1, double* diff_inv, int_t p_level, double *xs, double *ys, double* zs, bool diag_balance){ - // Return If I'm at max_level or p_level - if (level >= p_level || level == max_level){ - return; - } - // then check to see if I intersect the segment - if (intersects_line(x0, x1, diff_inv, segment=true)){ - if(is_leaf()){ - divide(nodes, xs, ys, zs, true, diag_balance); - } - // recurse into children - for(int_t i = 0; i < (1<refine_line(nodes, x0, x1, diff_inv, p_level, xs, ys, zs, diag_balance); - } - } -} - -void Cell::refine_triangle( - node_map_t& nodes, - double* x0, double* x1, double* x2, - double* e0, double* e1, double* e2, - double* t_norm, - int_t p_level, double *xs, double *ys, double* zs, bool diag_balance -){ - // Return if I'm at max_level or p_level - if (level >= p_level || level == max_level){ - return; - } - if (intersects_triangle(x0, x1, x2, e0, e1, e2, t_norm)){ - if(is_leaf()){ - divide(nodes, xs, ys, zs, true, diag_balance); - } - for(int_t i = 0; i < (1<refine_triangle( - nodes, x0, x1, x2, e0, e1, e2, t_norm, p_level, xs, ys, zs, diag_balance - ); - } - } -} - -void Cell::refine_vert_triang_prism( - node_map_t& nodes, - double* x0, double* x1, double* x2, double h, - double* e0, double* e1, double* e2, double* t_norm, - int_t p_level, double *xs, double *ys, double* zs, bool diag_balance -){ - // Return If I'm at max_level or p_level - if (level >= p_level || level == max_level){ - return; - } - if(intersects_vert_triang_prism(x0, x1, x2, h, e0, e1, e2, t_norm)){ - if(is_leaf()){ - divide(nodes, xs, ys, zs, true, diag_balance); - } - for(int_t i = 0; i < (1<refine_vert_triang_prism( - nodes, x0, x1, x2, h, e0, e1, e2, t_norm, p_level, xs, ys, zs, diag_balance - ); - } - } -} - -void Cell::refine_tetra( - node_map_t& nodes, - double* x0, double* x1, double* x2, double* x3, - double edge_tans[6][3], double face_normals[4][3], - int_t p_level, double *xs, double *ys, double* zs, bool diag_balance -){ - // Return If I'm at max_level or p_level - if (level >= p_level || level == max_level){ - return; - } - if (n_dim < 3){ - return; - } - if (intersects_tetra(x0, x1, x2, x3, edge_tans, face_normals)){ - if(is_leaf()){ - divide(nodes, xs, ys, zs, true, diag_balance); - } - for(int_t i = 0; i < (1<refine_tetra( - nodes, x0, x1, x2, x3, edge_tans, face_normals, p_level, xs, ys, zs, diag_balance - ); - } - } -} - -void Cell::void refine_plane(node_map_t& nodes, double* x0, double* normal, int_t p_level, double *xs, double *ys, double* zs, bool diag_balance=false){ - // Return If I'm at max_level or p_level - if (level >= p_level || level == max_level){ - return; - } - if (n_dim < 3){ - return; - } - if (intersects_plane(x0, normal)){ - if(is_leaf()){ - divide(nodes, xs, ys, zs, true, diag_balance); - } - for(int_t i = 0; i < (1<refine_plane( - nodes, x0, normal, p_level, xs, ys, zs, diag_balance - ); - } - } -} - void Cell::refine_func(node_map_t& nodes, function test_func, double *xs, double *ys, double *zs, bool diag_balance){ // return if I'm at the maximum level if (level == max_level){ @@ -1302,18 +727,6 @@ Cell* Cell::containing_cell(double x, double y, double z){ return children[ix + 2*iy + 4*iz]->containing_cell(x, y, z); }; -void Cell::find_overlapping_cells(int_vec_t& cells, double* x0, double* x1){ - if(intersects_box(x0, x1)){ - if(this->is_leaf()){ - cells.push_back(index); - return; - } - for(int_t i = 0; i < (1<find_overlapping_cells(cells, x0, x1); - } - } -} - Cell::~Cell(){ if(is_leaf()){ return; @@ -1483,107 +896,6 @@ void Tree::refine_function(function test_func, bool diagonal_balance){ roots[iz][iy][ix]->refine_func(nodes, test_func, xs, ys, zs, diagonal_balance); }; -void Tree::refine_box(double* x0, double* x1, int_t p_level, bool diagonal_balance){ - for(int_t iz=0; izrefine_box(nodes, x0, x1, p_level, xs, ys, zs, false, diagonal_balance); -}; - -void Tree::refine_ball(double* center, double r, int_t p_level, bool diagonal_balance){ - double r2 = r*r; - for(int_t iz=0; izrefine_ball(nodes, center, r2, p_level, xs, ys, zs, diagonal_balance); -}; - -void Tree::refine_line(double* x0, double* x1, int_t p_level, bool diagonal_balance){ - double diff_inv[3]; - for(int_t i=0; irefine_line(nodes, x0, x1, diff_inv, p_level, xs, ys, zs, diagonal_balance); -}; - -void Tree::refine_triangle(double* x0, double* x1, double* x2, int_t p_level, bool diagonal_balance){ - double e0[3], e1[3], e2[3], t_norm[3]; - for(int_t i=0; i 2){ - t_norm[0] = e0[1] * e1[2] - e0[2] * e1[1]; - t_norm[1] = e0[2] * e1[0] - e0[0] * e1[2]; - t_norm[2] = e0[0] * e1[1] - e0[1] * e1[0]; - } - for(int_t iz=0; izrefine_triangle( - nodes, x0, x1, x2, e0, e1, e2, t_norm, p_level, xs, ys, zs, diagonal_balance - ); -}; - -void Tree::refine_vert_triang_prism(double* x0, double* x1, double* x2, double h, int_t p_level, bool diagonal_balance){ - double e0[3], e1[3], e2[3], t_norm[3]; - for(int_t i=0; i 2){ - t_norm[0] = e0[1] * e1[2] - e0[2] * e1[1]; - t_norm[1] = e0[2] * e1[0] - e0[0] * e1[2]; - t_norm[2] = e0[0] * e1[1] - e0[1] * e1[0]; - } - for(int_t iz=0; izrefine_vert_triang_prism( - nodes, x0, x1, x2, h, e0, e1, e2, t_norm, p_level, xs, ys, zs, diagonal_balance - ); -}; - -void Tree::refine_tetra(double* x0, double* x1, double* x2, double* x3, int_t p_level, bool diagonal_balance){ - double t_edges[6][3]; - double face_normals[4][3]; - for(int_t i=0; irefine_tetra( - nodes, x0, x1, x2, x3, t_edges, face_normals, p_level, xs, ys, zs, diagonal_balance - ); -}; - void Tree::finalize_lists(){ for(int_t iz=0; izcontaining_cell(x, y, z); } -int_vec_t Tree::find_overlapping_cells(double *x0, double *x1){ - int_vec_t overlaps; - for(int_t iz=0; izfind_overlapping_cells(overlaps, x0, x1); - } - } - } - return overlaps; - } - -int_vec_t find_cells_along_line(double *x0, double *x1, bool segment=true){ - int_vec_t intersections; - for(int_t iz=0; izfind_overlapping_cells(overlaps, x0, x1); - } - } - } - return intersections; - } -} - void Tree::shift_cell_centers(double *shift){ for(int_t iz=0; iz #include +#include "geom.h" + typedef std::size_t int_t; inline int_t key_func(int_t x, int_t y){ @@ -121,50 +123,6 @@ class Cell{ Cell* containing_cell(double, double, double); void insert_cell(node_map_t &nodes, double *new_center, int_t p_level, double* xs, double *ys, double *zs, bool diag_balance=false); - bool intersects_ball(double *x, double rsq); - void find_intersect_ball_cells(int_vec_t& cells, double *x, double rsq); - void refine_ball(node_map_t& nodes, double* center, double r2, int_t p_level, double *xs, double *ys, double* zs, bool diag_balance=false); - - bool intersects_line(double *x0, double *x1, double* i_dx, bool segment=true); - void find_intersect_line_cells(int_vec_t& cells, double *x0, double *x1, double* i_dx, bool segment=true); - void refine_line(node_map_t& nodes, double* x0, double* x1, double* diff_inv, int_t p_level, double *xs, double *ys, double* zs, bool diag_balance=false); - - bool intersects_box(double *x0, double *x1); - void find_overlapping_cells(int_vec_t& cells, double* x0, double* x1); - void refine_box(node_map_t& nodes, double* x0, double* x1, int_t p_level, double *xs, double *ys, double* zs, bool diag_balance=false); - - bool intersects_plane(double *x0, double *normal); - void find_intersect_plane_cells(int_vec_t& cells, double *x0, double *normal); - void refine_plane(node_map_t& nodes, double* x0, double* normal, int_t p_level, double *xs, double *ys, double* zs, bool diag_balance=false); - - bool intersects_triangle(double *x0, double *x1, double *x2, double *e0, double *e1, double *e2, double *t_norm); - void find_intersect_triangle_cells(int_vec_t& cells, double *x0, double *x1, double *x2, double *e0, double *e1, double *e2, double *t_norm); - void refine_triangle(node_map_t& nodes, - double* x0, double* x1, double* x2, double* e0, double* e1, double* e2, double* t_norm, - int_t p_level, double *xs, double *ys, double* zs, bool diag_balance=false - ); - - bool intersects_vert_triang_prism(double *x0, double *x1, double *x2, double h, double* e0, double* e1, double* e2, double* t_norm); - void find_intersect_vert_triang_prism_cells(int_vec_t& cells, double *x0, double *x1, double *x2, double h, double* e0, double* e1, double* e2, double* t_norm); - void refine_vert_triang_prism(node_map_t& nodes, - double* x0, double* x1, double* x2, double h, - double* e0, double* e1, double* e2, double* t_norm, - int_t p_level, double *xs, double *ys, double* zs, bool diag_balance=false - ); - - bool intersects_tetra( - double* x0, double* x1, double* x2, double* x3, - double edge_tans[6][3], double face_normals[4][3] - ); - void find_intersect_tetra_cells(int_vec_t& cells, double* x0, double* x1, double* x2, double* x3, - double edge_tans[6][3], double face_normals[4][3]); - void refine_tetra( - node_map_t& nodes, - double* x0, double* x1, double* x2, double* x3, - double edge_tans[6][3], double face_normals[4][3], - int_t p_level, double *xs, double *ys, double* zs, bool diag_balance - ); - void refine_func(node_map_t& nodes, function test_func, double *xs, double *ys, double* zs, bool diag_balance=false); bool inline is_leaf(){ return children[0]==NULL;}; @@ -173,9 +131,42 @@ class Cell{ void set_neighbor(Cell* other, int_t direction); void build_cell_vector(cell_vec_t& cells); - - void shift_centers(double * shift); + + template + void refine_geom(node_map_t& nodes, const T& geom, int_t p_level, double *xs, double *ys, double* zs, bool diag_balance=false){ + // early exit if my level is higher than or equal to target + if (level >= p_level || level == max_level){ + return; + } + double *a = points[0]->location; + double *b = (n_dim<3)? points[3]->location : points[7]->location; + // if I intersect cell, I will need to be divided (if I'm not already) + if (geom.intersects_cell(a, b)){ + if(is_leaf()){ + divide(nodes, xs, ys, zs, true, diag_balance); + } + // recurse into children + for(int_t i = 0; i < (1<refine_geom(nodes, geom, p_level, xs, ys, zs, diag_balance); + } + } + } + + template + void find_cells_geom(int_vec_t &cells, const T& geom){ + double *a = points[0]->location; + double *b = (n_dim<3)? points[3]->location : points[7]->location; + if(geom.intersects_cell(a, b)){ + if(this->is_leaf()){ + cells.push_back(index); + return; + } + for(int_t i = 0; i < (1<find_cells_geom(cells, geom); + } + } + } }; class Tree{ @@ -204,27 +195,37 @@ class Tree{ void set_levels(int_t l_x, int_t l_y, int_t l_z); void set_xs(double *x , double *y, double *z); void initialize_roots(); - void refine_function(function test_func, bool diagonal_balance=false); - void refine_ball(double *center, double r, int_t p_level, bool diagonal_balance=false); - void refine_box(double* x0, double* x1, int_t p_level, bool diagonal_balance=false); - void refine_line(double* x0, double* x1, int_t p_level, bool diag_balance=false); - void refine_triangle( - double* x0, double* x1, double* x2, int_t p_level, bool diag_balance=false - ); - void refine_tetra( - double* x0, double* x1, double* x2, double* x3, int_t p_level, bool diag_balance=false - ); - void refine_vert_triang_prism( - double* x0, double* x1, double* x2, double h, int_t p_level, bool diagonal_balance=false - ); void number(); void finalize_lists(); - void insert_cell(double *new_center, int_t p_level, bool diagonal_balance=false); + void shift_cell_centers(double *shift); + void insert_cell(double *new_center, int_t p_level, bool diagonal_balance=false); Cell* containing_cell(double, double, double); - int_vec_t find_overlapping_cells(double *x0, double *x1); - int_vec_t find_cells_along_line(double *x0, double *x1, bool segment=true); - void shift_cell_centers(double *shift); + + void refine_function(function test_func, bool diagonal_balance=false); + + template + void refine_geom(const T& geom, int_t p_level, bool diagonal_balance=false){ + for(int_t iz=0; izrefine_geom(nodes, geom, p_level, xs, ys, zs, diagonal_balance); + }; + + template + int_vec_t find_cells_geom(const T& geom){ + int_vec_t intersections; + for(int_t iz=0; izfind_cells_geom(intersections, geom); + } + } + } + return intersections; + }; + }; + #endif diff --git a/discretize/_extensions/tree.pxd b/discretize/_extensions/tree.pxd index 52a2028aa..f9f91cf0c 100644 --- a/discretize/_extensions/tree.pxd +++ b/discretize/_extensions/tree.pxd @@ -85,16 +85,13 @@ cdef extern from "tree.h": void set_levels(int_t, int_t, int_t) void set_xs(double*, double*, double*) void refine_function(PyWrapper *, bool) - void refine_ball(double*, double, int_t, bool) - void refine_box(double*, double*, int_t, bool) - void refine_line(double*, double*, int_t, bool) - void refine_triangle(double*, double*, double*, int_t, bool) - void refine_vert_triang_prism(double*, double*, double*, double, int_t, bool) - void refine_tetra(double*, double*, double*, double*, int_t, bool) + + void refine_geom[T](const T&, int_t, bool) + void number() void initialize_roots() void insert_cell(double *new_center, int_t p_level, bool) void finalize_lists() Cell * containing_cell(double, double, double) - vector[int_t] find_overlapping_cells(double* x0, double* x1) + vector[int_t] find_cells_geom[T](const T& geom) void shift_cell_centers(double*) diff --git a/discretize/_extensions/tree_ext.pyx b/discretize/_extensions/tree_ext.pyx index c8750ae7e..1ac12e033 100644 --- a/discretize/_extensions/tree_ext.pyx +++ b/discretize/_extensions/tree_ext.pyx @@ -9,6 +9,7 @@ from libcpp cimport bool from numpy.math cimport INFINITY from .tree cimport int_t, Tree as c_Tree, PyWrapper, Node, Edge, Face, Cell as c_Cell +from . cimport geom import scipy.sparse as sp import numpy as np @@ -586,14 +587,17 @@ cdef class _TreeMesh: diagonal_balance = self._diagonal_balance cdef bool diag_balance = diagonal_balance + cdef geom.Ball *ball cdef int_t i cdef int l cdef int max_level = self.max_level for i in range(ls.shape[0]): + ball = new geom.Ball(self._dim, &cs[i, 0], rs[i]) l = ls[i] if l < 0: l = (max_level + 1) - (abs(l) % (max_level + 1)) - self.tree.refine_ball(&cs[i, 0], rs[i], l, diag_balance) + self.tree.refine_geom(ball[0], l, diag_balance) + del ball if finalize: self.finalize() @@ -672,13 +676,16 @@ cdef class _TreeMesh: diagonal_balance = self._diagonal_balance cdef bool diag_balance = diagonal_balance + cdef geom.Box *box cdef int l cdef int max_level = self.max_level for i in range(ls.shape[0]): + box = new geom.Box(self._dim, &x0[i, 0], &x1[i, 0]) l = ls[i] if l < 0: l = (max_level + 1) - (abs(l) % (max_level + 1)) - self.tree.refine_box(&x0[i, 0], &x1[i, 0], l, diag_balance) + self.tree.refine_geom(box[0], l, diag_balance) + del box if finalize: self.finalize() @@ -748,14 +755,18 @@ cdef class _TreeMesh: diagonal_balance = self._diagonal_balance cdef bool diag_balance = diagonal_balance + cdef geom.Line *line + cdef int l cdef int max_level = self.max_level cdef int i for i in range(n_segments): + line = new geom.Line(self._dim, &line_nodes[i, 0], &line_nodes[i+1, 0], True) l = ls[i] if l < 0: l = (max_level + 1) - (abs(l) % (max_level + 1)) - self.tree.refine_line(&line_nodes[i, 0], &line_nodes[i+1, 0], l, diag_balance) + self.tree.refine_geom(line[0], l, diag_balance) + del line if finalize: self.finalize() @@ -827,13 +838,16 @@ cdef class _TreeMesh: diagonal_balance = self._diagonal_balance cdef bool diag_balance = diagonal_balance + cdef geom.Triangle *triang cdef int l cdef int max_level = self.max_level for i in range(n_triangles): + triang = new geom.Triangle(self._dim, &tris[i, 0, 0], &tris[i, 1, 0], &tris[i, 2, 0]) l = ls[i] if l < 0: l = (max_level + 1) - (abs(l) % (max_level + 1)) - self.tree.refine_triangle(&tris[i, 0, 0], &tris[i, 1, 0], &tris[i, 2, 0], l, diag_balance) + self.tree.refine_geom(triang[0], l, diag_balance) + del triang if finalize: self.finalize() @@ -923,13 +937,16 @@ cdef class _TreeMesh: diagonal_balance = self._diagonal_balance cdef bool diag_balance = diagonal_balance + cdef geom.VerticalTriangularPrism *vert_prism cdef int l cdef int max_level = self.max_level for i in range(n_triangles): + vert_prism = new geom.VerticalTriangularPrism(self._dim, &tris[i, 0, 0], &tris[i, 1, 0], &tris[i, 2, 0], hs[i]) l = ls[i] if l < 0: l = (max_level + 1) - (abs(l) % (max_level + 1)) - self.tree.refine_vert_triang_prism(&tris[i, 0, 0], &tris[i, 1, 0], &tris[i, 2, 0], hs[i], l, diag_balance) + self.tree.refine_geom(vert_prism[0], l, diag_balance) + del vert_prism if finalize: self.finalize() @@ -1007,13 +1024,16 @@ cdef class _TreeMesh: diagonal_balance = self._diagonal_balance cdef bool diag_balance = diagonal_balance + cdef geom.Tetrahedron *tet cdef int l cdef int max_level = self.max_level for i in range(n_triangles): l = ls[i] + tet = new geom.Tetrahedron(self._dim, &tris[i, 0, 0], &tris[i, 1, 0], &tris[i, 2, 0], &tris[i, 3, 0]) if l < 0: l = (max_level + 1) - (abs(l) % (max_level + 1)) - self.tree.refine_tetra(&tris[i, 0, 0], &tris[i, 1, 0], &tris[i, 2, 0], &tris[i, 3, 0], l, diag_balance) + self.tree.refine_geom(tet[0], l, diag_balance) + del tet if finalize: self.finalize() @@ -6181,8 +6201,10 @@ cdef class _TreeMesh: cdef vector[int_t] *overlapping_cells cdef double *weights cdef double over_lap_vol - cdef double x1m, x1p, y1m, y1p, z1m, z1p - cdef double x2m, x2p, y2m, y2p, z2m, z2p + cdef double x1m[3] + cdef double x1p[3] + cdef double *x2m + cdef double *x2p cdef double[:] origin = meshin._origin cdef double[:] xF if self.dim == 2: @@ -6259,16 +6281,20 @@ cdef class _TreeMesh: return output return P + cdef geom.Box *box for cell in self.tree.cells: - x1m = min(cell.points[0].location[0], xF[0]) - y1m = min(cell.points[0].location[1], xF[1]) + x1m[0] = min(cell.points[0].location[0], xF[0]) + x1m[1] = min(cell.points[0].location[1], xF[1]) - x1p = max(cell.points[3].location[0], origin[0]) - y1p = max(cell.points[3].location[1], origin[1]) + x1p[0] = max(cell.points[3].location[0], origin[0]) + x1p[1] = max(cell.points[3].location[1], origin[1]) if self._dim==3: - z1m = min(cell.points[0].location[2], xF[2]) - z1p = max(cell.points[7].location[2], origin[2]) - overlapping_cell_inds = meshin.tree.find_overlapping_cells(x1m, x1p, y1m, y1p, z1m, z1p) + x1m[2] = min(cell.points[0].location[2], xF[2]) + x1p[2] = max(cell.points[7].location[2], origin[2]) + + box = new geom.Box(self._dim, x1m, x1p) + overlapping_cell_inds = meshin.tree.find_cells_geom(box[0]) + del box n_overlap = overlapping_cell_inds.size() weights = malloc(n_overlap*sizeof(double)) i = 0 @@ -6276,26 +6302,13 @@ cdef class _TreeMesh: nnz_row = 0 for in_cell_ind in overlapping_cell_inds: in_cell = meshin.tree.cells[in_cell_ind] - x2m = in_cell.points[0].location[0] - y2m = in_cell.points[0].location[1] - z2m = in_cell.points[0].location[2] - x2p = in_cell.points[3].location[0] - y2p = in_cell.points[3].location[1] - z2p = in_cell.points[7].location[2] if self._dim==3 else 0.0 + x2m = &in_cell.points[0].location[0] + x2p = &in_cell.points[7].location[0] if self._dim==3 else &in_cell.points[3].location[0] - if x1m == xF[0] or x1p == origin[0]: - over_lap_vol = 1.0 - else: - over_lap_vol = min(x1p, x2p) - max(x1m, x2m) - if y1m == xF[1] or y1p == origin[1]: - over_lap_vol *= 1.0 - else: - over_lap_vol *= min(y1p, y2p) - max(y1m, y2m) - if self._dim==3: - if z1m == xF[2] or z1p == origin[2]: - over_lap_vol *= 1.0 - else: - over_lap_vol *= min(z1p, z2p) - max(z1m, z2m) + over_lap_vol = 1.0 + for i_dim in range(self._dim): + if x1m[i] != xF[i] and x1p[i] != origin[i]: + over_lap_vol *= min(x1p[i], x2p[i]) - max(x1m[i], x2m[i]) weights[i] = over_lap_vol if build_mat and weights[i] != 0.0: @@ -6329,8 +6342,10 @@ cdef class _TreeMesh: cdef vector[int_t] *overlapping_cells cdef double *weights cdef double over_lap_vol - cdef double x1m, x1p, y1m, y1p, z1m, z1p - cdef double x2m, x2p, y2m, y2p, z2m, z2p + cdef double x1m[3] + cdef double x1p[3] + cdef double *x2m + cdef double *x2p cdef double[:] origin cdef double[:] xF @@ -6405,15 +6420,17 @@ cdef class _TreeMesh: #for cell in self.tree.cells: for iz in range(nz): - z1m = min(nodes_z[iz], xF[2]) - z1p = max(nodes_z[iz+1], origin[2]) + x1m[2] = min(nodes_z[iz], xF[2]) + x1p[2] = max(nodes_z[iz+1], origin[2]) for iy in range(ny): - y1m = min(nodes_y[iy], xF[1]) - y1p = max(nodes_y[iy+1], origin[1]) + x1m[1] = min(nodes_y[iy], xF[1]) + x1p[1] = max(nodes_y[iy+1], origin[1]) for ix in range(nx): - x1m = min(nodes_x[ix], xF[0]) - x1p = max(nodes_x[ix+1], origin[0]) - overlapping_cell_inds = self.tree.find_overlapping_cells(x1m, x1p, y1m, y1p, z1m, z1p) + x1m[0] = min(nodes_x[ix], xF[0]) + x1p[0] = max(nodes_x[ix+1], origin[0]) + box = new geom.Box(self._dim, x1m, x1p) + overlapping_cell_inds = self.tree.find_cells_geom(box[0]) + del box n_overlap = overlapping_cell_inds.size() weights = malloc(n_overlap*sizeof(double)) i = 0 @@ -6421,26 +6438,13 @@ cdef class _TreeMesh: nnz_row = 0 for in_cell_ind in overlapping_cell_inds: in_cell = self.tree.cells[in_cell_ind] - x2m = in_cell.points[0].location[0] - y2m = in_cell.points[0].location[1] - z2m = in_cell.points[0].location[2] - x2p = in_cell.points[3].location[0] - y2p = in_cell.points[3].location[1] - z2p = in_cell.points[7].location[2] if self._dim==3 else 0.0 - - if x1m == xF[0] or x1p == origin[0]: - over_lap_vol = 1.0 - else: - over_lap_vol = min(x1p, x2p) - max(x1m, x2m) - if y1m == xF[1] or y1p == origin[1]: - over_lap_vol *= 1.0 - else: - over_lap_vol *= min(y1p, y2p) - max(y1m, y2m) - if self._dim==3: - if z1m == xF[2] or z1p == origin[2]: - over_lap_vol *= 1.0 - else: - over_lap_vol *= min(z1p, z2p) - max(z1m, z2m) + x2m = &in_cell.points[0].location[0] + x2p = &in_cell.points[7].location[0] if self._dim==3 else &in_cell.points[3].location[0] + + over_lap_vol = 1.0 + for i_dim in range(self._dim): + if x1m[i] != xF[i] and x1p[i] != origin[i]: + over_lap_vol *= min(x1p[i], x2p[i]) - max(x1m[i], x2m[i]) weights[i] = over_lap_vol if build_mat and weights[i] != 0.0: @@ -6655,24 +6659,23 @@ cdef class _TreeMesh: list of int The indices of cells which overlap the axis aligned rectangle. """ - cdef double xm, ym, zm, xp, yp, zp + cdef double xm[3] + cdef double xp[3] cdef double[:] origin = self._origin cdef double[:] xF if self.dim == 2: xF = np.array([self._xs[-1], self._ys[-1]]) else: xF = np.array([self._xs[-1], self._ys[-1], self._zs[-1]]) - xm = min(rectangle[0], xF[0]) - xp = max(rectangle[1], origin[0]) - ym = min(rectangle[2], xF[1]) - yp = max(rectangle[3], origin[1]) - if self.dim==3: - zm = min(rectangle[4], xF[2]) - zp = max(rectangle[5], origin[2]) - else: - zm = 0.0 - zp = 0.0 - return self.tree.find_overlapping_cells(xm, xp, ym, yp, zm, zp) + for i_d in range(self._dim): + xm[i_d] = min(rectangle[i_d], xF[i_d]) + xp[i_d] = max(rectangle[i_d], origin[i_d]) + + cdef geom.Box *box = new geom.Box(self._dim, xm, xp) + cdef vector[int_t] cell_inds = self.tree.find_cells_geom(box[0]) + del box + + return cell_inds cdef inline double _clip01(double x) nogil: From 54482d08414d87b96b3f6d92dc349ffe016dda27 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Fri, 12 Apr 2024 23:59:36 -0600 Subject: [PATCH 04/97] First steps at using random generators. --- discretize/tests.py | 52 +++++++++++++-------- discretize/utils/mesh_utils.py | 9 ++-- tests/base/test_interpolation.py | 7 ++- tests/base/test_operators.py | 16 ++++++- tests/base/test_tensor.py | 3 ++ tests/base/test_tensor_innerproduct.py | 11 ++++- tests/boundaries/test_boundary_integrals.py | 5 ++ tests/boundaries/test_boundary_poisson.py | 12 +++++ tests/cyl/test_cyl3D.py | 2 +- tests/tree/test_tree_operators.py | 3 -- 10 files changed, 88 insertions(+), 32 deletions(-) diff --git a/discretize/tests.py b/discretize/tests.py index 501520e12..f5ebbe2b5 100644 --- a/discretize/tests.py +++ b/discretize/tests.py @@ -78,11 +78,10 @@ "You break it, you fix it.", ] -# Initiate random number generator -rng = np.random.default_rng() +_happiness_rng = np.random.default_rng() -def setup_mesh(mesh_type, nC, nDim): +def setup_mesh(mesh_type, nC, nDim, rng=None): """Generate arbitrary mesh for testing. For the mesh type, number of cells along each axis and dimension specified, @@ -106,13 +105,15 @@ def setup_mesh(mesh_type, nC, nDim): discretize.base.BaseMesh A discretize mesh of class specified by the input argument *mesh_type* """ + if "random" in mesh_type: + rng = np.random.default_rng(rng) if "TensorMesh" in mesh_type: if "uniform" in mesh_type: h = [nC, nC, nC] elif "random" in mesh_type: - h1 = np.random.rand(nC) * nC * 0.5 + nC * 0.5 - h2 = np.random.rand(nC) * nC * 0.5 + nC * 0.5 - h3 = np.random.rand(nC) * nC * 0.5 + nC * 0.5 + h1 = rng.random(nC) * nC * 0.5 + nC * 0.5 + h2 = rng.random(nC) * nC * 0.5 + nC * 0.5 + h3 = rng.random(nC) * nC * 0.5 + nC * 0.5 h = [hi / np.sum(hi) for hi in [h1, h2, h3]] # normalize else: raise Exception("Unexpected mesh_type") @@ -127,14 +128,14 @@ def setup_mesh(mesh_type, nC, nDim): else: h = [nC, nC, nC] elif "random" in mesh_type: - h1 = np.random.rand(nC) * nC * 0.5 + nC * 0.5 + h1 = rng.random(nC) * nC * 0.5 + nC * 0.5 if "symmetric" in mesh_type: h2 = [ 2 * np.pi, ] else: - h2 = np.random.rand(nC) * nC * 0.5 + nC * 0.5 - h3 = np.random.rand(nC) * nC * 0.5 + nC * 0.5 + h2 = rng.random(nC) * nC * 0.5 + nC * 0.5 + h3 = rng.random(nC) * nC * 0.5 + nC * 0.5 h = [hi / np.sum(hi) for hi in [h1, h2, h3]] # normalize h[1] = h[1] * 2 * np.pi else: @@ -179,9 +180,9 @@ def setup_mesh(mesh_type, nC, nDim): if "uniform" in mesh_type or "notatree" in mesh_type: h = [nC, nC, nC] elif "random" in mesh_type: - h1 = np.random.rand(nC) * nC * 0.5 + nC * 0.5 - h2 = np.random.rand(nC) * nC * 0.5 + nC * 0.5 - h3 = np.random.rand(nC) * nC * 0.5 + nC * 0.5 + h1 = rng.random(nC) * nC * 0.5 + nC * 0.5 + h2 = rng.random(nC) * nC * 0.5 + nC * 0.5 + h3 = rng.random(nC) * nC * 0.5 + nC * 0.5 h = [hi / np.sum(hi) for hi in [h1, h2, h3]] # normalize else: raise Exception("Unexpected mesh_type") @@ -302,6 +303,7 @@ class for the given operator. Within the test class, the user sets the parameter meshTypes = ["uniformTensorMesh"] _meshType = meshTypes[0] meshDimension = 3 + rng = np.random.default_rng() def setupMesh(self, nC): """Generate mesh and set as current mesh for testing. @@ -316,7 +318,7 @@ def setupMesh(self, nC): Float Maximum cell width for the mesh """ - mesh, max_h = setup_mesh(self._meshType, nC, self.meshDimension) + mesh, max_h = setup_mesh(self._meshType, nC, self.meshDimension, rng=self.rng) self.M = mesh return max_h @@ -335,7 +337,7 @@ def getError(self): """ return 1.0 - def orderTest(self): + def orderTest(self, rng=None): """Perform an order test. For number of cells specified in meshSizes setup mesh, call getError @@ -496,9 +498,9 @@ class in older versions of `discretize`. np.testing.assert_allclose(orders[-1], expected_order, rtol=rtol) elif test_type == "all": np.testing.assert_allclose(orders, expected_order, rtol=rtol) - print(np.random.choice(happiness)) + print(_happiness_rng.choice(happiness)) except AssertionError as err: - print(np.random.choice(sadness)) + print(_happiness_rng.choice(sadness)) raise err return orders @@ -552,6 +554,7 @@ def check_derivative( tolerance=0.85, eps=1e-10, ax=None, + rng=None, ): """Perform a basic derivative check. @@ -580,6 +583,9 @@ def check_derivative( ax : matplotlib.pyplot.Axes, optional An axis object for the convergence plot if *plotIt = True*. Otherwise, the function will create a new axis. + rng : numpy.random.Generator, int, optional + The random number generator to use for the adjoint test, if an integer or None + it used to seed a new `numpy.random.default_rng`. Returns ------- @@ -611,6 +617,8 @@ def check_derivative( __tracebackhide__ = True # matplotlib is a soft dependencies for discretize, # lazy-loaded to decrease load time of discretize. + rng = np.random.default_rng(rng) + try: import matplotlib import matplotlib.pyplot as plt @@ -627,7 +635,7 @@ def check_derivative( x0 = mkvc(x0) if dx is None: - dx = np.random.randn(len(x0)) + dx = rng.standard_normal(len(x0)) h = np.logspace(-1, -num, num) E0 = np.ones(h.shape) @@ -700,7 +708,7 @@ def _plot_it(axes, passed): f" {tolerance} of the expected order {expectedOrder}." ) print("{0!s} PASS! {1!s}".format("=" * 25, "=" * 25)) - print(np.random.choice(happiness) + "\n") + print(_happiness_rng.choice(happiness) + "\n") if plotIt: _plot_it(ax, True) except AssertionError as err: @@ -709,7 +717,7 @@ def _plot_it(axes, passed): "*" * 57, "<" * 25, ">" * 25, "*" * 57 ) ) - print(np.random.choice(sadness) + "\n") + print(_happiness_rng.choice(sadness) + "\n") if plotIt: _plot_it(ax, False) raise err @@ -773,6 +781,7 @@ def assert_isadjoint( rtol=1e-6, atol=0.0, assert_error=True, + rng=None, ): r"""Do a dot product test for the forward operator and its adjoint operator. @@ -823,6 +832,9 @@ def assert_isadjoint( assertion error if failed). If set to False, the result of the test is returned as boolean and a message is printed. + rng : numpy.random.Generator, int, optional + The random number generator to use for the adjoint test, if an integer or None + it used to seed a new `numpy.random.default_rng`. Returns ------- @@ -837,6 +849,8 @@ def assert_isadjoint( """ __tracebackhide__ = True + rng = np.random.default_rng(rng) + def random(size, iscomplex): """Create random data of size and dtype of .""" out = rng.standard_normal(size) diff --git a/discretize/utils/mesh_utils.py b/discretize/utils/mesh_utils.py index 2f5a73891..5c7530298 100644 --- a/discretize/utils/mesh_utils.py +++ b/discretize/utils/mesh_utils.py @@ -28,7 +28,7 @@ def random_model(shape, seed=None, anisotropy=None, its=100, bounds=None): ---------- shape : (dim) tuple of int shape of the model. - seed : int, optional + seed : numpy.random.Generator, int, optional pick which model to produce, prints the seed if you don't choose anisotropy : numpy.ndarray, optional this is the kernel that is convolved with the model @@ -67,15 +67,14 @@ def random_model(shape, seed=None, anisotropy=None, its=100, bounds=None): if bounds is None: bounds = [0, 1] + rng = np.random.default_rng(seed) if seed is None: - seed = np.random.randint(1e3) - print("Using a seed of: ", seed) + print("Using a seed of: ", rng.bit_generator.seed_seq) if type(shape) in num_types: shape = (shape,) # make it a tuple for consistency - np.random.seed(seed) - mr = np.random.rand(*shape) + mr = rng.random(*shape) if anisotropy is None: if len(shape) == 1: smth = np.array([1, 10.0, 1], dtype=float) diff --git a/tests/base/test_interpolation.py b/tests/base/test_interpolation.py index c9826128a..cea6abd02 100644 --- a/tests/base/test_interpolation.py +++ b/tests/base/test_interpolation.py @@ -3,7 +3,7 @@ import discretize -np.random.seed(182) +gen = np.random.default_rng(182) MESHTYPES = ["uniformTensorMesh", "randomTensorMesh"] TOLERANCES = [0.9, 0.5, 0.5] @@ -49,6 +49,7 @@ class TestInterpolation1D(discretize.tests.OrderTest): tolerance = TOLERANCES meshDimension = 1 meshSizes = [8, 16, 32, 64, 128] + rng = gen def getError(self): funX = lambda x: np.cos(2 * np.pi * x) @@ -100,6 +101,7 @@ class TestInterpolation2d(discretize.tests.OrderTest): tolerance = TOLERANCES meshDimension = 2 meshSizes = [8, 16, 32, 64] + rng = gen def getError(self): funX = lambda x, y: np.cos(2 * np.pi * y) @@ -185,6 +187,7 @@ class TestInterpolationSymCyl(discretize.tests.OrderTest): tolerance = 0.6 meshDimension = 3 meshSizes = [32, 64, 128, 256] + rng = gen def getError(self): funX = lambda x, y: np.cos(2 * np.pi * y) @@ -252,6 +255,7 @@ class TestInterpolationCyl(discretize.tests.OrderTest): meshTypes = ["uniformCylMesh", "randomCylMesh"] # MESHTYPES + meshDimension = 3 meshSizes = [8, 16, 32, 64] + rng = gen def getError(self): func = lambda x, y, z: np.cos(2 * np.pi * x) + np.cos(y) + np.cos(2 * np.pi * z) @@ -320,6 +324,7 @@ class TestInterpolation3D(discretize.tests.OrderTest): tolerance = TOLERANCES meshDimension = 3 meshSizes = [8, 16, 32, 64] + rng = gen def getError(self): funX = lambda x, y, z: np.cos(2 * np.pi * y) diff --git a/tests/base/test_operators.py b/tests/base/test_operators.py index aa84d76f5..1679c2a25 100644 --- a/tests/base/test_operators.py +++ b/tests/base/test_operators.py @@ -5,7 +5,7 @@ # Tolerance TOL = 1e-14 -np.random.seed(1) +gen = np.random.default_rng(1) MESHTYPES = [ "uniformTensorMesh", @@ -45,6 +45,7 @@ class TestCurl(discretize.tests.OrderTest): name = "Curl" meshTypes = MESHTYPES + rng = gen def getError(self): # fun: i (cos(y)) + j (cos(z)) + k (cos(x)) @@ -82,6 +83,7 @@ class TestCurl2D(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 2 meshSizes = [8, 16, 32, 64] + rng = gen def getError(self): # Test function @@ -108,6 +110,7 @@ def test_order(self): # 1 # because of the averaging involved in the ghost point. u_b = (u_n + u_g)/2 # ) # meshSizes = [8, 16, 32, 64] +# rng = gen # # def getError(self): # # Test function @@ -135,6 +138,7 @@ class TestCellGrad2D_Dirichlet(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 2 meshSizes = [8, 16, 32, 64] + rng = gen def getError(self): # Test function @@ -163,6 +167,7 @@ class TestCellGrad3D_Dirichlet(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 3 meshSizes = [8, 16, 32] + rng = gen def getError(self): # Test function @@ -214,6 +219,7 @@ class TestCellGrad2D_Neumann(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 2 meshSizes = [8, 16, 32, 64] + rng = gen def getError(self): # Test function @@ -242,6 +248,7 @@ class TestCellGrad3D_Neumann(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 3 meshSizes = [8, 16, 32] + rng = gen def getError(self): # Test function @@ -292,6 +299,7 @@ class TestFaceDiv3D(discretize.tests.OrderTest): name = "Face Divergence 3D" meshTypes = MESHTYPES meshSizes = [8, 16, 32, 64] + rng = gen def getError(self): # Test function @@ -327,6 +335,7 @@ class TestFaceDiv2D(discretize.tests.OrderTest): meshTypes = MESHTYPES meshDimension = 2 meshSizes = [8, 16, 32, 64] + rng = gen def getError(self): # Test function @@ -351,6 +360,7 @@ def test_order(self): class TestNodalGrad(discretize.tests.OrderTest): name = "Nodal Gradient" meshTypes = MESHTYPES + rng = gen def getError(self): # Test function @@ -378,6 +388,7 @@ class TestNodalGrad2D(discretize.tests.OrderTest): name = "Nodal Gradient 2D" meshTypes = MESHTYPES meshDimension = 2 + rng = gen def getError(self): # Test function @@ -405,6 +416,7 @@ class TestAveraging1D(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 1 meshSizes = [16, 32, 64] + rng = gen def getError(self): num = self.getAve(self.M) * self.getHere(self.M) @@ -526,6 +538,7 @@ class TestAveraging2D(discretize.tests.OrderTest): meshTypes = MESHTYPES meshDimension = 2 meshSizes = [16, 32, 64] + rng = gen def getError(self): num = self.getAve(self.M) * self.getHere(self.M) @@ -645,6 +658,7 @@ class TestAveraging3D(discretize.tests.OrderTest): meshTypes = MESHTYPES meshDimension = 3 meshSizes = [16, 32, 64] + rng = gen def getError(self): num = self.getAve(self.M) * self.getHere(self.M) diff --git a/tests/base/test_tensor.py b/tests/base/test_tensor.py index 9349cafa7..cf42f226f 100644 --- a/tests/base/test_tensor.py +++ b/tests/base/test_tensor.py @@ -6,6 +6,8 @@ TOL = 1e-10 +gen = np.random.default_rng(123) + class BasicTensorMeshTests(unittest.TestCase): def setUp(self): @@ -276,6 +278,7 @@ def test_cell_nodes(self, mesh): class TestPoissonEqn(discretize.tests.OrderTest): name = "Poisson Equation" meshSizes = [10, 16, 20] + rng = gen def getError(self): # Create some functions to integrate diff --git a/tests/base/test_tensor_innerproduct.py b/tests/base/test_tensor_innerproduct.py index 156403255..8ca27543d 100644 --- a/tests/base/test_tensor_innerproduct.py +++ b/tests/base/test_tensor_innerproduct.py @@ -4,7 +4,7 @@ from discretize import TensorMesh from discretize.utils import sdinv -np.random.seed(50) +gen = np.random.default_rng(50) class TestInnerProducts(discretize.tests.OrderTest): @@ -14,6 +14,7 @@ class TestInnerProducts(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh", "uniformCurv", "rotateCurv"] meshDimension = 3 meshSizes = [16, 32] + rng = gen def getError(self): call = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2]) @@ -173,6 +174,7 @@ class TestInnerProductsFaceProperties3D(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 3 meshSizes = [16, 32] + rng = gen def getError(self): call = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2]) @@ -319,6 +321,7 @@ class TestInnerProductsEdgeProperties3D(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 3 meshSizes = [16, 32] + rng = gen def getError(self): call = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2]) @@ -407,6 +410,7 @@ class TestInnerProducts2D(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh", "uniformCurv", "rotateCurv"] meshDimension = 2 meshSizes = [4, 8, 16, 32, 64, 128] + rng = gen def getError(self): z = 5 # Because 5 is just such a great number. @@ -552,6 +556,7 @@ class TestInnerProductsFaceProperties2D(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 2 meshSizes = [8, 16, 32] + rng = gen def getError(self): call = lambda fun, xy: fun(xy[:, 0], xy[:, 1]) @@ -642,6 +647,7 @@ class TestInnerProductsEdgeProperties2D(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 2 meshSizes = [8, 16, 32] + rng = gen def getError(self): call = lambda fun, xy: fun(xy[:, 0], xy[:, 1]) @@ -704,6 +710,7 @@ class TestInnerProducts1D(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 1 meshSizes = [4, 8, 16, 32, 64, 128] + rng = gen def getError(self): y = 12 # Because 12 is just such a great number. @@ -750,7 +757,7 @@ class TestTensorSizeErrorRaises(unittest.TestCase): def setUp(self): self.mesh3D = TensorMesh([4, 4, 4]) - self.model = np.random.rand(self.mesh3D.nC) + self.model = np.ones(self.mesh3D.nC) def test_edge_inner_product_surface(self): self.assertRaises( diff --git a/tests/boundaries/test_boundary_integrals.py b/tests/boundaries/test_boundary_integrals.py index dbe224a76..6574b55dd 100644 --- a/tests/boundaries/test_boundary_integrals.py +++ b/tests/boundaries/test_boundary_integrals.py @@ -3,6 +3,8 @@ import discretize from discretize.utils import cart2cyl, cyl2cart +gen = np.random.default_rng(2552) + def u(*args): if len(args) == 1: @@ -86,6 +88,7 @@ class Test1DBoundaryIntegral(discretize.tests.OrderTest): meshDimension = 1 expectedOrders = 2 meshSizes = [4, 8, 16, 32, 64, 128] + rng = gen def getError(self): mesh = self.M @@ -136,6 +139,7 @@ class Test2DBoundaryIntegral(discretize.tests.OrderTest): meshDimension = 2 expectedOrders = [2, 2, 2, 2, 1] meshSizes = [4, 8, 16, 32, 64, 128] + rng = gen def getError(self): mesh = self.M @@ -216,6 +220,7 @@ class Test3DBoundaryIntegral(discretize.tests.OrderTest): meshDimension = 3 expectedOrders = [2, 1, 2, 2, 2, 2] meshSizes = [4, 8, 16, 32] + rng = gen def getError(self): mesh = self.M diff --git a/tests/boundaries/test_boundary_poisson.py b/tests/boundaries/test_boundary_poisson.py index a88e9924c..25f4f17bd 100644 --- a/tests/boundaries/test_boundary_poisson.py +++ b/tests/boundaries/test_boundary_poisson.py @@ -6,6 +6,8 @@ from discretize import utils from pymatsolver import Solver, Pardiso +gen = np.random.default_rng(42) + class TestCC1D_InhomogeneousDirichlet(discretize.tests.OrderTest): name = "1D - Dirichlet" @@ -13,6 +15,7 @@ class TestCC1D_InhomogeneousDirichlet(discretize.tests.OrderTest): meshDimension = 1 expectedOrders = 2 meshSizes = [4, 8, 16, 32, 64, 128] + rng = gen def getError(self): # Test function @@ -56,6 +59,7 @@ class TestCC2D_InhomogeneousDirichlet(discretize.tests.OrderTest): meshDimension = 2 expectedOrders = [2, 2, 1] meshSizes = [4, 8, 16, 32, 64] + rng = gen def getError(self): # Test function @@ -99,6 +103,7 @@ class TestCC1D_InhomogeneousNeumann(discretize.tests.OrderTest): meshDimension = 1 expectedOrders = 2 meshSizes = [4, 8, 16, 32, 64, 128] + rng = gen def getError(self): # Test function @@ -166,6 +171,7 @@ class TestCC2D_InhomogeneousNeumann(discretize.tests.OrderTest): expectedOrders = [2, 2, 1] meshSizes = [4, 8, 16, 32] # meshSizes = [4] + rng = gen def getError(self): # Test function @@ -228,6 +234,7 @@ class TestCC1D_InhomogeneousMixed(discretize.tests.OrderTest): meshDimension = 1 expectedOrders = 2 meshSizes = [4, 8, 16, 32, 64, 128] + rng = gen def getError(self): # Test function @@ -286,6 +293,7 @@ class TestCC2D_InhomogeneousMixed(discretize.tests.OrderTest): meshDimension = 2 expectedOrders = [2, 2, 1] meshSizes = [2, 4, 8, 16] + rng = gen # meshSizes = [4] def getError(self): @@ -353,6 +361,7 @@ class TestCC3D_InhomogeneousMixed(discretize.tests.OrderTest): meshDimension = 3 expectedOrders = [2, 2, 2] meshSizes = [2, 4, 8, 16, 32] + rng = gen def getError(self): # Test function @@ -441,6 +450,7 @@ class TestN1D_boundaries(discretize.tests.OrderTest): meshDimension = 1 expectedOrders = 2 meshSizes = [2, 4, 8, 16, 32, 64, 128] + rng = gen # meshSizes = [4] def getError(self): @@ -520,6 +530,7 @@ class TestN2D_boundaries(discretize.tests.OrderTest): expectedOrders = 2 tolerance = [0.8, 0.8, 0.6] meshSizes = [8, 16, 32, 64] + rng = gen # meshSizes = [4] def getError(self): @@ -621,6 +632,7 @@ class TestN3D_boundaries(discretize.tests.OrderTest): expectedOrders = 2 tolerance = 0.6 meshSizes = [2, 4, 8, 16, 32] + rng = gen # meshSizes = [4] def getError(self): diff --git a/tests/cyl/test_cyl3D.py b/tests/cyl/test_cyl3D.py index 1c98b60ce..94d9ed6c6 100644 --- a/tests/cyl/test_cyl3D.py +++ b/tests/cyl/test_cyl3D.py @@ -4,7 +4,7 @@ import discretize from discretize import utils -np.random.seed(16) +rng = np.random.default_rng(16) TOL = 1e-1 diff --git a/tests/tree/test_tree_operators.py b/tests/tree/test_tree_operators.py index 22b2e2c8b..8d6d94f4b 100644 --- a/tests/tree/test_tree_operators.py +++ b/tests/tree/test_tree_operators.py @@ -30,9 +30,6 @@ ) ) -# np.random.seed(None) -# np.random.seed(7) - class TestCellGrad2D(discretize.tests.OrderTest): name = "Cell Gradient 2D, using cellGradx and cellGrady" From b59a00ca1f85b94fb36be8bdad430dc343aa1e6d Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Sat, 13 Apr 2024 00:15:24 -0600 Subject: [PATCH 05/97] update `get_cells_along_line` --- discretize/_extensions/tree_ext.pyx | 137 +++++----------------------- 1 file changed, 21 insertions(+), 116 deletions(-) diff --git a/discretize/_extensions/tree_ext.pyx b/discretize/_extensions/tree_ext.pyx index 1ac12e033..846e0071a 100644 --- a/discretize/_extensions/tree_ext.pyx +++ b/discretize/_extensions/tree_ext.pyx @@ -2584,122 +2584,6 @@ cdef class _TreeMesh: return np.where(is_on_boundary) - @cython.cdivision(True) - def get_cells_along_line(self, x0, x1): - """Find the cells along a line segment defined by two points. - - Parameters - ---------- - x0,x1 : (dim) array_like - Begining and ending point of the line segment. - - Returns - ------- - list of int - Indices for cells that contain the a line defined by the two input - points, ordered in the direction of the line. - """ - cdef np.float64_t ax, ay, az, bx, by, bz - - cdef int dim = self.dim - ax = x0[0] - ay = x0[1] - az = x0[2] if dim==3 else 0 - - bx = x1[0] - by = x1[1] - bz = x1[2] if dim==3 else 0 - - cdef vector[long long int] cell_indexes; - - #find initial cell - cdef c_Cell *cur_cell = self.tree.containing_cell(ax, ay, az) - cell_indexes.push_back(cur_cell.index) - #find last cell - cdef c_Cell *last_cell = self.tree.containing_cell(bx, by, bz) - cdef c_Cell *next_cell - cdef int ix, iy, iz - cdef double tx, ty, tz, ipx, ipy, ipz - - if dim==3: - last_point = 7 - else: - last_point = 3 - - cdef int iter = 0 - - while cur_cell.index != last_cell.index: - #find which direction to look: - p0 = cur_cell.points[0].location - pF = cur_cell.points[last_point].location - - if ax>bx: - tx = (p0[0]-ax)/(bx-ax) - elif axby: - ty = (p0[1]-ay)/(by-ay) - elif aybz: - tz = (p0[2]-az)/(bz-az) - elif azbx: # go -x - next_cell = next_cell.neighbors[0] - else: # go +x - next_cell = next_cell.neighbors[1] - if ty<=tx and ty<=tz: - # step in y direction - if ay>by: # go -y - next_cell = next_cell.neighbors[2] - else: # go +y - next_cell = next_cell.neighbors[3] - if dim==3 and tz<=tx and tz<=ty: - # step in z direction - if az>bz: # go -z - next_cell = next_cell.neighbors[4] - else: # go +z - next_cell = next_cell.neighbors[5] - - # check if next_cell is not a leaf - # (if so need to traverse down the children and find the closest leaf cell) - while not next_cell.is_leaf(): - # should be able to use cp to check which cell to go to - cp = next_cell.children[0].points[last_point].location - # this basically finds the child cell closest to the intersection point - ix = ipx>cp[0] or (ipx==cp[0] and axcp[1] or (ipy==cp[1] and aycp[2] or (ipz==cp[2] and az Date: Tue, 16 Apr 2024 00:55:11 -0600 Subject: [PATCH 06/97] update volume average tests to pytest --- discretize/_extensions/interputils_cython.pyx | 9 +- discretize/_extensions/tree.cpp | 4 +- discretize/_extensions/tree.h | 13 +- discretize/_extensions/tree.pxd | 2 + discretize/_extensions/tree_ext.pyx | 92 +-- tests/base/test_volume_avg.py | 568 ++++-------------- 6 files changed, 193 insertions(+), 495 deletions(-) diff --git a/discretize/_extensions/interputils_cython.pyx b/discretize/_extensions/interputils_cython.pyx index 6c95d06bc..c3072c808 100644 --- a/discretize/_extensions/interputils_cython.pyx +++ b/discretize/_extensions/interputils_cython.pyx @@ -228,10 +228,11 @@ def _tensor_volume_averaging(mesh_in, mesh_out, values=None, output=None): # If given a values array, do the operation val_in = values.reshape(mesh_in_shape, order='F').astype(np.float64) if output is None: - v_o = np.zeros(mesh_out_shape, order='F') + output = np.zeros(mesh_out.n_cells, dtype=np.float64) else: - v_o = output.reshape(mesh_out_shape, order='F') - v_o.fill(0) + output = np.require(output, dtype=np.float64, requirements=['A', 'W']) + v_o = output.reshape(mesh_out_shape, order='F') + v_o.fill(0) val_out = v_o for i3 in range(w_shape[2]): i3i = i3_in[i3] @@ -245,7 +246,7 @@ def _tensor_volume_averaging(mesh_in, mesh_out, values=None, output=None): i1i = i1_in[i1] i1o = i1_out[i1] val_out[i1o, i2o, i3o] += w_32*w1[i1]*val_in[i1i, i2i, i3i]/vol[i1o, i2o, i3o] - return v_o.reshape(-1, order='F') + return output # Else, build and return a sparse matrix representing the operation i_i = np.empty(w_shape, dtype=np.int32, order='F') diff --git a/discretize/_extensions/tree.cpp b/discretize/_extensions/tree.cpp index 5773b62b3..8899490d1 100644 --- a/discretize/_extensions/tree.cpp +++ b/discretize/_extensions/tree.cpp @@ -370,8 +370,8 @@ void Cell::shift_centers(double *shift){ bool Cell::intersects_point(double *x){ // A simple bounding box check: - double *p0 = points[0]->location; - double *p1 = (n_dim < 3)? points[3]->location : points[7]->location; + double *p0 = min_node()->location; + double *p1 = max_node()->location; for(int_t i=0; i < n_dim; ++i){ if(x[i] < p0[i] || x[i] > p1[i]){ return false; diff --git a/discretize/_extensions/tree.h b/discretize/_extensions/tree.h index 311d5f897..aea1e6e22 100644 --- a/discretize/_extensions/tree.h +++ b/discretize/_extensions/tree.h @@ -118,6 +118,9 @@ class Cell{ Cell(Node *pts[4], Cell *parent); ~Cell(); + inline Node* min_node(){ return points[0];}; + inline Node* max_node(){ return points[(1<= p_level || level == max_level){ return; } - double *a = points[0]->location; - double *b = (n_dim<3)? points[3]->location : points[7]->location; + double *a = min_node()->location; + double *b = max_node()->location; // if I intersect cell, I will need to be divided (if I'm not already) if (geom.intersects_cell(a, b)){ if(is_leaf()){ @@ -155,8 +158,8 @@ class Cell{ template void find_cells_geom(int_vec_t &cells, const T& geom){ - double *a = points[0]->location; - double *b = (n_dim<3)? points[3]->location : points[7]->location; + double *a = min_node()->location; + double *b = max_node()->location; if(geom.intersects_cell(a, b)){ if(this->is_leaf()){ cells.push_back(index); diff --git a/discretize/_extensions/tree.pxd b/discretize/_extensions/tree.pxd index f9f91cf0c..57d600636 100644 --- a/discretize/_extensions/tree.pxd +++ b/discretize/_extensions/tree.pxd @@ -62,6 +62,8 @@ cdef extern from "tree.h": long long int index double volume inline bool is_leaf() + inline Node* min_node() + inline Node* max_node() cdef cppclass PyWrapper: PyWrapper() diff --git a/discretize/_extensions/tree_ext.pyx b/discretize/_extensions/tree_ext.pyx index 846e0071a..9c45492b1 100644 --- a/discretize/_extensions/tree_ext.pyx +++ b/discretize/_extensions/tree_ext.pyx @@ -6065,14 +6065,16 @@ cdef class _TreeMesh: cdef c_Cell * out_cell cdef c_Cell * in_cell - cdef np.float64_t[:] vals = np.array([]) - cdef np.float64_t[:] outs = np.array([]) + cdef np.float64_t[:] vals + cdef np.float64_t[:] outs cdef int_t build_mat = 1 if values is not None: vals = values if output is None: - output = np.empty(self.n_cells) + output = np.empty(self.n_cells, dtype=np.float64) + else: + output = np.require(output, dtype=np.float64, requirements=['A', 'W']) output[:] = 0 outs = output @@ -6087,8 +6089,8 @@ cdef class _TreeMesh: cdef double over_lap_vol cdef double x1m[3] cdef double x1p[3] - cdef double *x2m - cdef double *x2p + cdef double x2m[3] + cdef double x2p[33] cdef double[:] origin = meshin._origin cdef double[:] xF if self.dim == 2: @@ -6166,15 +6168,11 @@ cdef class _TreeMesh: return P cdef geom.Box *box + cdef int_t last_point_ind = 7 if self._dim==3 else 3 for cell in self.tree.cells: - x1m[0] = min(cell.points[0].location[0], xF[0]) - x1m[1] = min(cell.points[0].location[1], xF[1]) - - x1p[0] = max(cell.points[3].location[0], origin[0]) - x1p[1] = max(cell.points[3].location[1], origin[1]) - if self._dim==3: - x1m[2] = min(cell.points[0].location[2], xF[2]) - x1p[2] = max(cell.points[7].location[2], origin[2]) + for i_d in range(self._dim): + x1m[i_d] = min(cell.min_node().location[i_d], xF[i_d]) + x1p[i_d] = max(cell.max_node().location[i_d], origin[i_d]) box = new geom.Box(self._dim, x1m, x1p) overlapping_cell_inds = meshin.tree.find_cells_geom(box[0]) @@ -6186,13 +6184,13 @@ cdef class _TreeMesh: nnz_row = 0 for in_cell_ind in overlapping_cell_inds: in_cell = meshin.tree.cells[in_cell_ind] - x2m = &in_cell.points[0].location[0] - x2p = &in_cell.points[7].location[0] if self._dim==3 else &in_cell.points[3].location[0] + x2m = in_cell.min_node().location + x2p = in_cell.max_node().location over_lap_vol = 1.0 - for i_dim in range(self._dim): - if x1m[i] != xF[i] and x1p[i] != origin[i]: - over_lap_vol *= min(x1p[i], x2p[i]) - max(x1m[i], x2m[i]) + for i_d in range(self._dim): + if x1m[i_d]< xF[i_d] and x1p[i_d] > origin[i_d]: + over_lap_vol *= min(x1p[i_d], x2p[i_d]) - max(x1m[i_d], x2m[i_d]) weights[i] = over_lap_vol if build_mat and weights[i] != 0.0: @@ -6201,10 +6199,11 @@ cdef class _TreeMesh: weight_sum += weights[i] i += 1 - for i in range(n_overlap): - weights[i] /= weight_sum - if build_mat and weights[i] != 0.0: - all_weights.push_back(weights[i]) + if weight_sum > 0: + for i in range(n_overlap): + weights[i] /= weight_sum + if build_mat and weights[i] != 0.0: + all_weights.push_back(weights[i]) if not build_mat: for i in range(n_overlap): @@ -6228,8 +6227,8 @@ cdef class _TreeMesh: cdef double over_lap_vol cdef double x1m[3] cdef double x1p[3] - cdef double *x2m - cdef double *x2p + cdef double x2m[3] + cdef double x2p[3] cdef double[:] origin cdef double[:] xF @@ -6280,6 +6279,7 @@ cdef class _TreeMesh: cdef double[:] nodes_z = np.array([0.0, 0.0]) if self._dim==3: nodes_z = out_tens_mesh.nodes_z + cdef int_t nx = len(nodes_x)-1 cdef int_t ny = len(nodes_y)-1 cdef int_t nz = len(nodes_z)-1 @@ -6288,17 +6288,17 @@ cdef class _TreeMesh: if values is not None: vals = values if output is None: - output = np.empty((nx, ny, nz), order='F') + output = np.empty(out_tens_mesh.n_cells, dtype=np.float64) else: - output = output.reshape((nx, ny, nz), order='F') + output = np.require(output, dtype=np.float64, requirements=['A', 'W']) output[:] = 0 - outs = output + outs = output.reshape((nx, ny, nz), order='F') build_mat = 0 if build_mat: indptr.push_back(0) - cdef int_t ix, iy, iz, in_cell_ind, i + cdef int_t ix, iy, iz, in_cell_ind, i, i_dim cdef int_t n_overlap cdef double weight_sum @@ -6312,9 +6312,11 @@ cdef class _TreeMesh: for ix in range(nx): x1m[0] = min(nodes_x[ix], xF[0]) x1p[0] = max(nodes_x[ix+1], origin[0]) + box = new geom.Box(self._dim, x1m, x1p) overlapping_cell_inds = self.tree.find_cells_geom(box[0]) del box + n_overlap = overlapping_cell_inds.size() weights = malloc(n_overlap*sizeof(double)) i = 0 @@ -6322,13 +6324,13 @@ cdef class _TreeMesh: nnz_row = 0 for in_cell_ind in overlapping_cell_inds: in_cell = self.tree.cells[in_cell_ind] - x2m = &in_cell.points[0].location[0] - x2p = &in_cell.points[7].location[0] if self._dim==3 else &in_cell.points[3].location[0] + x2m = in_cell.min_node().location + x2p = in_cell.max_node().location over_lap_vol = 1.0 - for i_dim in range(self._dim): - if x1m[i] != xF[i] and x1p[i] != origin[i]: - over_lap_vol *= min(x1p[i], x2p[i]) - max(x1m[i], x2m[i]) + for i_d in range(self._dim): + if x1m[i_d]< xF[i_d] and x1p[i_d] > origin[i_d]: + over_lap_vol *= min(x1p[i_d], x2p[i_d]) - max(x1m[i_d], x2m[i_d]) weights[i] = over_lap_vol if build_mat and weights[i] != 0.0: @@ -6336,10 +6338,12 @@ cdef class _TreeMesh: row_inds.push_back(in_cell_ind) weight_sum += weights[i] i += 1 - for i in range(n_overlap): - weights[i] /= weight_sum - if build_mat and weights[i] != 0.0: - all_weights.push_back(weights[i]) + + if weight_sum > 0: + for i in range(n_overlap): + weights[i] /= weight_sum + if build_mat and weights[i] != 0.0: + all_weights.push_back(weights[i]) if not build_mat: for i in range(n_overlap): @@ -6352,7 +6356,7 @@ cdef class _TreeMesh: overlapping_cell_inds.clear() if not build_mat: - return output.reshape(-1, order='F') + return output return sp.csr_matrix((all_weights, row_inds, indptr), shape=(out_tens_mesh.n_cells, self.n_cells)) @cython.boundscheck(False) @@ -6410,14 +6414,16 @@ cdef class _TreeMesh: else: xF = np.array([nodes_x[-1], nodes_y[-1], nodes_z[-1]]) - cdef np.float64_t[::1, :, :] vals = np.array([[[]]]) - cdef np.float64_t[:] outs = np.array([]) + cdef np.float64_t[::1, :, :] vals + cdef np.float64_t[:] outs cdef int_t build_mat = 1 if values is not None: vals = values.reshape((nx, ny, nz), order='F') if output is None: - output = np.empty(self.n_cells) + output = np.empty(self.n_cells, dtype=np.float64) + else: + output = np.require(output, dtype=np.float64, requirements=['A', 'W']) output[:] = 0 outs = output @@ -6552,8 +6558,8 @@ cdef class _TreeMesh: else: xF = np.array([self._xs[-1], self._ys[-1], self._zs[-1]]) for i_d in range(self._dim): - xm[i_d] = min(rectangle[i_d], xF[i_d]) - xp[i_d] = max(rectangle[i_d], origin[i_d]) + xm[i_d] = min(rectangle[2 * i_d], xF[i_d]) + xp[i_d] = max(rectangle[2 * i_d + 1], origin[i_d]) cdef geom.Box *box = new geom.Box(self._dim, xm, xp) cdef vector[int_t] cell_inds = self.tree.find_cells_geom(box[0]) diff --git a/tests/base/test_volume_avg.py b/tests/base/test_volume_avg.py index ed319bf9e..b2d7a3e2f 100644 --- a/tests/base/test_volume_avg.py +++ b/tests/base/test_volume_avg.py @@ -1,444 +1,130 @@ import numpy as np -import unittest +import pytest import discretize from discretize.utils import volume_average -from numpy.testing import assert_array_equal, assert_allclose - - -class TestVolumeAverage(unittest.TestCase): - def test_tensor_to_tensor(self): - h1 = np.random.rand(16) - h1 /= h1.sum() - h2 = np.random.rand(16) - h2 /= h2.sum() - - h1s = [] - h2s = [] - for i in range(3): - print(f"Tensor to Tensor {i+1}D: ", end="") - h1s.append(h1) - h2s.append(h2) - mesh1 = discretize.TensorMesh(h1s) - mesh2 = discretize.TensorMesh(h2s) - - in_put = np.random.rand(mesh1.nC) - out_put = np.empty(mesh2.nC) - # test the three ways of calling... - out1 = volume_average(mesh1, mesh2, in_put, out_put) - assert_array_equal(out1, out_put) - - out2 = volume_average(mesh1, mesh2, in_put) - assert_allclose(out1, out2) - - Av = volume_average(mesh1, mesh2) - out3 = Av @ in_put - assert_allclose(out1, out3) - - vol1 = np.sum(mesh1.cell_volumes * in_put) - vol2 = np.sum(mesh2.cell_volumes * out3) - print(vol1, vol2) - self.assertAlmostEqual(vol1, vol2) - - def test_tree_to_tree(self): - h1 = np.random.rand(16) - h1 /= h1.sum() - h2 = np.random.rand(16) - h2 /= h2.sum() - - h1s = [h1] - h2s = [h2] - insert_1 = [0.25] - insert_2 = [0.75] - for i in range(1, 3): - print(f"Tree to Tree {i+1}D: ", end="") - h1s.append(h1) - h2s.append(h2) - insert_1.append(0.25) - insert_2.append(0.75) - mesh1 = discretize.TreeMesh(h1s) - mesh1.insert_cells([insert_1], [4]) - mesh2 = discretize.TreeMesh(h2s) - mesh2.insert_cells([insert_2], [4]) - - in_put = np.random.rand(mesh1.nC) - out_put = np.empty(mesh2.nC) - # test the three ways of calling... - out1 = volume_average(mesh1, mesh2, in_put, out_put) - assert_array_equal(out1, out_put) - - out2 = volume_average(mesh1, mesh2, in_put) - assert_allclose(out1, out2) - - Av = volume_average(mesh1, mesh2) - out3 = Av @ in_put - assert_allclose(out1, out3) - - vol1 = np.sum(mesh1.cell_volumes * in_put) - vol2 = np.sum(mesh2.cell_volumes * out3) - print(vol1, vol2) - self.assertAlmostEqual(vol1, vol2) - - def test_tree_to_tensor(self): - h1 = np.random.rand(16) - h1 /= h1.sum() - h2 = np.random.rand(16) - h2 /= h2.sum() - - h1s = [h1] - h2s = [h2] - insert_1 = [0.25] - for i in range(1, 3): - print(f"Tree to Tensor {i+1}D: ", end="") - h1s.append(h1) - h2s.append(h2) - insert_1.append(0.25) - mesh1 = discretize.TreeMesh(h1s) - mesh1.insert_cells([insert_1], [4]) - mesh2 = discretize.TensorMesh(h2s) - - in_put = np.random.rand(mesh1.nC) - out_put = np.empty(mesh2.nC) - # test the three ways of calling... - out1 = volume_average(mesh1, mesh2, in_put, out_put) - assert_array_equal(out1, out_put) - - out2 = volume_average(mesh1, mesh2, in_put) - assert_allclose(out1, out2) - - Av = volume_average(mesh1, mesh2) - out3 = Av @ in_put - assert_allclose(out1, out3) - - vol1 = np.sum(mesh1.cell_volumes * in_put) - vol2 = np.sum(mesh2.cell_volumes * out3) - print(vol1, vol2) - self.assertAlmostEqual(vol1, vol2) - - def test_tensor_to_tree(self): - h1 = np.random.rand(16) - h1 /= h1.sum() - h2 = np.random.rand(16) - h2 /= h2.sum() - - h1s = [h1] - h2s = [h2] - insert_2 = [0.75] - for i in range(1, 3): - print(f"Tensor to Tree {i+1}D: ", end="") - h1s.append(h1) - h2s.append(h2) - insert_2.append(0.75) - mesh1 = discretize.TensorMesh(h1s) - mesh2 = discretize.TreeMesh(h2s) - mesh2.insert_cells([insert_2], [4]) - - in_put = np.random.rand(mesh1.nC) - out_put = np.empty(mesh2.nC) - # test the three ways of calling... - out1 = volume_average(mesh1, mesh2, in_put, out_put) - assert_array_equal(out1, out_put) - - out2 = volume_average(mesh1, mesh2, in_put) - assert_allclose(out1, out2) - - Av = volume_average(mesh1, mesh2) - out3 = Av @ in_put - assert_allclose(out1, out3) - - vol1 = np.sum(mesh1.cell_volumes * in_put) - vol2 = np.sum(mesh2.cell_volumes * out3) - print(vol1, vol2) - self.assertAlmostEqual(vol1, vol2) - - def test_errors(self): - h1 = np.random.rand(16) - h1 /= h1.sum() - h2 = np.random.rand(16) - h2 /= h2.sum() - mesh1D = discretize.TensorMesh([h1]) - mesh2D = discretize.TensorMesh([h1, h1]) - mesh3D = discretize.TensorMesh([h1, h1, h1]) - - hr = np.r_[1, 1, 0.5] - hz = np.r_[2, 1] - meshCyl = discretize.CylindricalMesh([hr, 1, hz], np.r_[0.0, 0.0, 0.0]) - mesh2 = discretize.TreeMesh([h2, h2]) - mesh2.insert_cells([0.75, 0.75], [4]) - - with self.assertRaises(TypeError): - # Gives a wrong typed object to the function - volume_average(mesh1D, h1) - with self.assertRaises(NotImplementedError): - # Gives a wrong typed mesh - volume_average(meshCyl, mesh2) - with self.assertRaises(ValueError): - # Gives mismatching mesh dimensions - volume_average(mesh2D, mesh3D) - - model1 = np.random.randn(mesh2D.nC) - bad_model1 = np.random.randn(3) - bad_model2 = np.random.rand(1) - # gives input values with incorrect lengths - with self.assertRaises(ValueError): - volume_average(mesh2D, mesh2, bad_model1) - with self.assertRaises(ValueError): - volume_average(mesh2D, mesh2, model1, bad_model2) - - def test_tree_to_tree_same_base(self): - h1 = np.random.rand(16) - h1 /= h1.sum() - - h1s = [h1] - insert_1 = [0.25] - insert_2 = [0.75] - for i in range(1, 3): - print(f"Tree to Tree {i+1}D: same base", end="") - h1s.append(h1) - insert_1.append(0.25) - insert_2.append(0.75) - mesh1 = discretize.TreeMesh(h1s) - mesh1.insert_cells([insert_1], [4]) - mesh2 = discretize.TreeMesh(h1s) - mesh2.insert_cells([insert_2], [4]) - - in_put = np.random.rand(mesh1.nC) - out_put = np.empty(mesh2.nC) - # test the three ways of calling... - out1 = volume_average(mesh1, mesh2, in_put, out_put) - assert_array_equal(out1, out_put) - - out2 = volume_average(mesh1, mesh2, in_put) - assert_allclose(out1, out2) - - Av = volume_average(mesh1, mesh2) - out3 = Av @ in_put - assert_allclose(out1, out3) - - vol1 = np.sum(mesh1.cell_volumes * in_put) - vol2 = np.sum(mesh2.cell_volumes * out3) - print(vol1, vol2) - self.assertAlmostEqual(vol1, vol2) - - def test_tree_to_tensor_same_base(self): - h1 = np.random.rand(16) - h1 /= h1.sum() - - h1s = [h1] - insert_1 = [0.25] - for i in range(1, 3): - print(f"Tree to Tensor {i+1}D same base: ", end="") - h1s.append(h1) - insert_1.append(0.25) - mesh1 = discretize.TreeMesh(h1s) - mesh1.insert_cells([insert_1], [4]) - mesh2 = discretize.TensorMesh(h1s) - - in_put = np.random.rand(mesh1.nC) - out_put = np.empty(mesh2.nC) - # test the three ways of calling... - out1 = volume_average(mesh1, mesh2, in_put, out_put) - assert_array_equal(out1, out_put) - - out2 = volume_average(mesh1, mesh2, in_put) - assert_allclose(out1, out2) - - Av = volume_average(mesh1, mesh2) - out3 = Av @ in_put - assert_allclose(out1, out3) - - vol1 = np.sum(mesh1.cell_volumes * in_put) - vol2 = np.sum(mesh2.cell_volumes * out3) - print(vol1, vol2) - self.assertAlmostEqual(vol1, vol2) - - def test_tensor_to_tree_same_base(self): - h1 = np.random.rand(16) - h1 /= h1.sum() - - h1s = [h1] - insert_2 = [0.75] - for i in range(1, 3): - print(f"Tensor to Tree {i+1}D same base: ", end="") - h1s.append(h1) - insert_2.append(0.75) - mesh1 = discretize.TensorMesh(h1s) - mesh2 = discretize.TreeMesh(h1s) - mesh2.insert_cells([insert_2], [4]) - - in_put = np.random.rand(mesh1.nC) - out_put = np.empty(mesh2.nC) - # test the three ways of calling... - out1 = volume_average(mesh1, mesh2, in_put, out_put) - assert_array_equal(out1, out_put) - - out2 = volume_average(mesh1, mesh2, in_put) - assert_allclose(out1, out2) - - Av = volume_average(mesh1, mesh2) - out3 = Av @ in_put - assert_allclose(out1, out3) - - vol1 = np.sum(mesh1.cell_volumes * in_put) - vol2 = np.sum(mesh2.cell_volumes * out3) - print(vol1, vol2) - self.assertAlmostEqual(vol1, vol2) - - def test_tensor_to_tensor_sub(self): - h1 = np.ones(32) - h2 = np.ones(16) - - h1s = [] - h2s = [] - for i in range(3): - print(f"Tensor to smaller Tensor {i+1}D: ", end="") - h1s.append(h1) - h2s.append(h2) - mesh1 = discretize.TensorMesh(h1s) - mesh2 = discretize.TensorMesh(h2s) - - in_put = np.random.rand(mesh1.nC) - out_put = np.empty(mesh2.nC) - # test the three ways of calling... - out1 = volume_average(mesh1, mesh2, in_put, out_put) - assert_array_equal(out1, out_put) - - out2 = volume_average(mesh1, mesh2, in_put) - assert_allclose(out1, out2) - - Av = volume_average(mesh1, mesh2) - out3 = Av @ in_put - assert_allclose(out1, out3) - - # get cells in extent of smaller mesh - cells = mesh1.gridCC < [16] * (i + 1) - if i > 0: - cells = np.all(cells, axis=1) - - vol1 = np.sum(mesh1.cell_volumes[cells] * in_put[cells]) - vol2 = np.sum(mesh2.cell_volumes * out3) - print(vol1, vol2) - self.assertAlmostEqual(vol1, vol2) - - def test_tree_to_tree_sub(self): - h1 = np.ones(32) - h2 = np.ones(16) - - h1s = [h1] - h2s = [h2] - insert_1 = [12] - insert_2 = [4] - for i in range(1, 3): - print(f"Tree to smaller Tree {i+1}D: ", end="") - h1s.append(h1) - h2s.append(h2) - insert_1.append(12) - insert_2.append(4) - mesh1 = discretize.TreeMesh(h1s) - mesh1.insert_cells([insert_1], [4]) - mesh2 = discretize.TreeMesh(h2s) - mesh2.insert_cells([insert_2], [4]) - - in_put = np.random.rand(mesh1.nC) - out_put = np.empty(mesh2.nC) - # test the three ways of calling... - out1 = volume_average(mesh1, mesh2, in_put, out_put) - assert_array_equal(out1, out_put) - - out2 = volume_average(mesh1, mesh2, in_put) - assert_allclose(out1, out2) - - Av = volume_average(mesh1, mesh2) - out3 = Av @ in_put - assert_allclose(out1, out3) - - # get cells in extent of smaller mesh - cells = mesh1.gridCC < [16] * (i + 1) - if i > 0: - cells = np.all(cells, axis=1) - - vol1 = np.sum(mesh1.cell_volumes[cells] * in_put[cells]) - vol2 = np.sum(mesh2.cell_volumes * out3) - print(vol1, vol2) - self.assertAlmostEqual(vol1, vol2) - - def test_tree_to_tensor_sub(self): - h1 = np.ones(32) - h2 = np.ones(16) - - h1s = [h1] - insert_1 = [12] - h2s = [h2] - for i in range(1, 3): - print(f"Tree to smaller Tensor {i+1}D: ", end="") - h1s.append(h1) - h2s.append(h2) - insert_1.append(12) - mesh1 = discretize.TreeMesh(h1s) - mesh1.insert_cells([insert_1], [4]) - mesh2 = discretize.TensorMesh(h2s) - - in_put = np.random.rand(mesh1.nC) - out_put = np.empty(mesh2.nC) - # test the three ways of calling... - out1 = volume_average(mesh1, mesh2, in_put, out_put) - assert_array_equal(out1, out_put) - - out2 = volume_average(mesh1, mesh2, in_put) - assert_allclose(out1, out2) - - Av = volume_average(mesh1, mesh2) - out3 = Av @ in_put - assert_allclose(out1, out3) - - # get cells in extent of smaller mesh - cells = mesh1.gridCC < [16] * (i + 1) - if i > 0: - cells = np.all(cells, axis=1) - - vol1 = np.sum(mesh1.cell_volumes[cells] * in_put[cells]) - vol2 = np.sum(mesh2.cell_volumes * out3) - print(vol1, vol2) - self.assertAlmostEqual(vol1, vol2) - - def test_tensor_to_tree_sub(self): - h1 = np.ones(32) - h2 = np.ones(16) - - h1s = [h1] - h2s = [h2] - insert_2 = [4] - for i in range(1, 3): - print(f"Tensor to smaller Tree {i+1}D: ", end="") - h1s.append(h1) - h2s.append(h2) - insert_2.append(4) - mesh1 = discretize.TensorMesh(h1s) - mesh2 = discretize.TreeMesh(h2s) - mesh2.insert_cells([insert_2], [4]) - - in_put = np.random.rand(mesh1.nC) - out_put = np.empty(mesh2.nC) - # test the three ways of calling... - out1 = volume_average(mesh1, mesh2, in_put, out_put) - assert_array_equal(out1, out_put) - - out2 = volume_average(mesh1, mesh2, in_put) - assert_allclose(out1, out2) - - Av = volume_average(mesh1, mesh2) - out3 = Av @ in_put - assert_allclose(out1, out3) - - # get cells in extent of smaller mesh - cells = mesh1.gridCC < [16] * (i + 1) - if i > 0: - cells = np.all(cells, axis=1) - - vol1 = np.sum(mesh1.cell_volumes[cells] * in_put[cells]) - vol2 = np.sum(mesh2.cell_volumes * out3) - print(vol1, vol2) - self.assertAlmostEqual(vol1, vol2) - - -if __name__ == "__main__": - unittest.main() +from numpy.testing import assert_allclose + + +def generate_mesh(dim, mesh_type, tree_point=None, sub_mesh=False, seed=0): + if seed is None: + h = np.ones(16) + else: + rng = np.random.default_rng(seed) + h = rng.random(16) + h /= h.sum() + if sub_mesh: + h = h[:8] + if tree_point is not None: + tree_point = tree_point * 0.5 + hs = [h] * dim + mesh = mesh_type(hs) + if isinstance(mesh, discretize.TreeMesh): + mesh.insert_cells(tree_point, -1) + return mesh + + +@pytest.mark.parametrize("same_base", [True, False]) +@pytest.mark.parametrize("sub_mesh", [True, False]) +@pytest.mark.parametrize( + "dim, mesh1_type, mesh2_type", + [ + (1, discretize.TensorMesh, discretize.TensorMesh), + (2, discretize.TensorMesh, discretize.TensorMesh), + (3, discretize.TensorMesh, discretize.TensorMesh), + (2, discretize.TreeMesh, discretize.TensorMesh), + (3, discretize.TreeMesh, discretize.TensorMesh), + (2, discretize.TensorMesh, discretize.TreeMesh), + (3, discretize.TensorMesh, discretize.TreeMesh), + (2, discretize.TreeMesh, discretize.TreeMesh), + (3, discretize.TreeMesh, discretize.TreeMesh), + ], +) +def test_volume_average(dim, mesh1_type, mesh2_type, same_base, sub_mesh, seed=102): + if dim == 1: + if mesh1_type is discretize.TreeMesh or mesh2_type is discretize.TreeMesh: + pytest.skip("TreeMesh only in 2D or higher") + + p1 = p2 = None + if mesh1_type is discretize.TreeMesh: + p1 = np.asarray([0.25] * dim) + if mesh2_type is discretize.TreeMesh: + p2 = np.asarray([0.75] * dim) + + rng = np.random.default_rng(seed) + + if not sub_mesh: + seed1, seed2 = rng.integers(554, size=(2,)) + if same_base: + seed2 = seed1 + else: + seed1 = seed2 = None + + mesh1 = generate_mesh(dim, mesh1_type, tree_point=p1, seed=seed1) + mesh2 = generate_mesh(dim, mesh2_type, tree_point=p2, sub_mesh=sub_mesh, seed=seed2) + + model_in = rng.random(mesh1.nC) + model_out1 = np.empty(mesh2.nC) + + # test the three ways of calling... + + # providing an output array + out1 = volume_average(mesh1, mesh2, model_in, model_out1) + # assert_array_equal(out1, model_out1) + assert out1 is model_out1 + + # only providing input array + out2 = volume_average(mesh1, mesh2, model_in) + assert_allclose(out1, out2) + + # not providing either (which constructs a sparse matrix representing the operation) + Av = volume_average(mesh1, mesh2) + out3 = Av @ model_in + assert_allclose(out1, out3) + + # test for mass conserving properties: + if sub_mesh: + # get cells in extent of smaller mesh + cells = mesh1.cell_centers < 8 + if dim > 1: + cells = np.all(cells, axis=1) + + mass1 = np.sum(mesh1.cell_volumes[cells] * model_in[cells]) + else: + mass1 = np.sum(mesh1.cell_volumes * model_in) + mass2 = np.sum(mesh2.cell_volumes * out3) + assert_allclose(mass1, mass2) + + +def test_errors(): + h1 = np.random.rand(16) + h1 /= h1.sum() + h2 = np.random.rand(16) + h2 /= h2.sum() + mesh1D = discretize.TensorMesh([h1]) + mesh2D = discretize.TensorMesh([h1, h1]) + mesh3D = discretize.TensorMesh([h1, h1, h1]) + + hr = np.r_[1, 1, 0.5] + hz = np.r_[2, 1] + meshCyl = discretize.CylindricalMesh([hr, 1, hz], np.r_[0.0, 0.0, 0.0]) + mesh2 = discretize.TreeMesh([h2, h2]) + mesh2.insert_cells([0.75, 0.75], [4]) + + with pytest.raises(TypeError): + # Gives a wrong typed object to the function + volume_average(mesh1D, h1) + with pytest.raises(NotImplementedError): + # Gives a wrong typed mesh + volume_average(meshCyl, mesh2) + with pytest.raises(ValueError): + # Gives mismatching mesh dimensions + volume_average(mesh2D, mesh3D) + + model1 = np.random.randn(mesh2D.nC) + bad_model1 = np.random.randn(3) + bad_model2 = np.random.rand(1) + # gives input values with incorrect lengths + with pytest.raises(ValueError): + volume_average(mesh2D, mesh2, bad_model1) + with pytest.raises(ValueError): + volume_average(mesh2D, mesh2, model1, bad_model2) From a715316e11d21455c9afeccdde38cd6e06e7654d Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Wed, 17 Apr 2024 14:36:50 -0600 Subject: [PATCH 07/97] add refine_plane method --- discretize/_extensions/geom.cpp | 27 ++++--- discretize/_extensions/geom.h | 3 +- discretize/_extensions/geom.pxd | 2 +- discretize/_extensions/tree_ext.pyx | 115 +++++++++++++++++++++++++++- 4 files changed, 127 insertions(+), 20 deletions(-) diff --git a/discretize/_extensions/geom.cpp b/discretize/_extensions/geom.cpp index 056118042..76f2153fd 100644 --- a/discretize/_extensions/geom.cpp +++ b/discretize/_extensions/geom.cpp @@ -23,11 +23,10 @@ bool Ball::intersects_cell(double *a, double *b) const{ return r2_test < rsq; } -Line::Line(int_t dim, double* x0, double *x1, bool segment){ +Line::Line(int_t dim, double* x0, double *x1){ this->dim = dim; this->x0 = x0; this->x1 = x1; - this->segment = segment; for(int_t i=0; i b[i])){ return false; } - if (segment){ - if(std::max(x0[i], x1[i]) < a[i]){ - return false; - } - if(std::min(x0[i], x1[i]) > b[i]){ - return false; - } + if(std::max(x0[i], x1[i]) < a[i]){ + return false; + } + if(std::min(x0[i], x1[i]) > b[i]){ + return false; } if (x0[i] != x1[i]){ t0 = (a[i] - x0[i]) * inv_dx[i]; @@ -59,7 +56,7 @@ bool Line::intersects_cell(double *a, double *b) const{ } t_near = std::max(t_near, t0); t_far = std::min(t_far, t1); - if (t_near > t_far || (segment && (t_far < 0 || t_near > 1))){ + if (t_near > t_far || t_far < 0 || t_near > 1){ return false; } } @@ -92,13 +89,15 @@ Plane::Plane(int_t dim, double* origin, double *normal){ } bool Plane::intersects_cell(double *a, double *b) const{ - double half; + double center; + double half_width; double s = 0.0; double r = 0.0; for(int_t i=0;i>> import discretize + >>> import matplotlib.pyplot as plt + >>> tree_mesh = discretize.TreeMesh([32, 32]) + >>> tree_mesh.max_level + 5 + + Next we define the origin and normal of the plane, and the level we want + to refine to. + + >>> origin = [0, 0.25] + >>> normal = [-1, -1] + >>> level = -1 + >>> tree_mesh.refine_plane(origin, normal, level) + + Now lets look at the mesh, and overlay the plane on it to ensure it refined + where we wanted it to. + + >>> ax = tree_mesh.plot_grid() + >>> ax.axline(origin, slope=-normal[0]/normal[1], color='C1') + >>> plt.show() + + """ + origins = np.asarray(origins) + normals = np.asarray(normals) + levels = np.asarray(levels) + try: + broadcast_shape = np.broadcast_shapes(origins.shape, normals.shape, levels.shape) + except ValueError as err: + raise ValueError( + f"Incompatible shapes for origins:{origins.shape}, " + f"normals: {normals.shape}, and levels: {levels.shape}" + ) + if origins.shape[-1] != self.dim: + raise ValueError( + f"origins last dimension ({origins.shape[-1]}) should be equal to {self.dim}." + ) + if normals.shape[-1] != self.dim: + raise ValueError( + f"normals last dimension ({normals.shape[-1]}) should be equal to {self.dim}." + ) + + cdef double[:, :] x_0s = np.require( + np.atleast_2d(origins), dtype=np.float64, requirements='C' + ) + cdef double[:, :] norms = np.require( + np.atleast_2d(normals), dtype=np.float64, requirements='C' + ) + cdef int[:] ls = np.require( + np.atleast_1d(levels), dtype=np.int32, requirements='C' + ) + cdef int n_planes = broadcast_shape[0]; # number of broadcasted planes to process. + + cdef int origin_step = 1 if x_0s.shape[0] == n_planes else 0 + cdef int normal_step = 1 if norms.shape[0] == n_planes else 0 + cdef int level_step = 1 if ls.shape[0] == n_planes else 0 + + if diagonal_balance is None: + diagonal_balance = self._diagonal_balance + cdef bool diag_balance = diagonal_balance + + cdef geom.Plane *plane + + cdef int l + cdef int max_level = self.max_level + cdef int i_plane, i_o, i_n, i_l + i_0 = i_n = i_l = 0 + for i in range(n_planes): + plane = new geom.Plane(self._dim, &x_0s[i_0, 0], &norms[i_n, 0]) + l = ls[i_l] + if l < 0: + l = (max_level + 1) - (abs(l) % (max_level + 1)) + + self.tree.refine_geom(plane[0], l, diag_balance) + del plane + + i_0 += origin_step + i_n += normal_step + i_l += level_step + if finalize: + self.finalize() + @cython.cdivision(True) def refine_triangle(self, triangle, levels, finalize=True, diagonal_balance=None): """Refine the :class:`~discretize.TreeMesh` along the triangle to the desired level. @@ -6584,8 +6692,9 @@ cdef class _TreeMesh: cdef double[:] start = np.require(x0, dtype=np.float64, requirements='A') cdef double[:] end = np.require(x0, dtype=np.float64, requirements='A') - cdef geom.Line *line = new geom.Line(self._dim, &start[0], &end[0], True) + cdef geom.Line *line = new geom.Line(self._dim, &start[0], &end[0]) cdef vector[int_t] cell_inds = self.tree.find_cells_geom(line[0]) + del line return cell_inds From 0c196652a4a7c434f946802e0041098470238106 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Thu, 18 Apr 2024 13:39:55 -0600 Subject: [PATCH 08/97] update intersection tests. --- discretize/_extensions/geom.cpp | 106 ++++- discretize/_extensions/geom.h | 11 + discretize/_extensions/geom.pxd | 21 +- discretize/_extensions/tree_ext.pyx | 626 +++++++++++++++++----------- discretize/mixins/mpl_mod.py | 2 +- discretize/tests.py | 116 ++++++ discretize/tree_mesh.py | 10 +- tests/tree/test_refine.py | 343 +++++---------- 8 files changed, 712 insertions(+), 523 deletions(-) diff --git a/discretize/_extensions/geom.cpp b/discretize/_extensions/geom.cpp index 76f2153fd..472e41cf8 100644 --- a/discretize/_extensions/geom.cpp +++ b/discretize/_extensions/geom.cpp @@ -3,10 +3,30 @@ #include "geom.h" #include #include + +// Define the 3D cross product as a pre-processor macro +#define CROSS3D(e0, e1, out) \ + out[0] = e0[1] * e1[2] - e0[2] * e1[1]; \ + out[1] = e0[2] * e1[0] - e0[0] * e1[2]; \ + out[2] = e0[0] * e1[1] - e0[1] * e0[0]; + // simple geometric objects for intersection tests with an aabb -Ball::Ball(int_t dim, double* x0, double r){ +Geometric::Geometric(){ + dim = 0; +} + +Geometric::Geometric(int_t dim){ this->dim = dim; +} + +Ball::Ball() : Geometric(){ + x0 = NULL; + r = 0; + rsq = 0; +} + +Ball::Ball(int_t dim, double* x0, double r) : Geometric(dim){ this->x0 = x0; this->r = r; this->rsq = r * r; @@ -23,8 +43,13 @@ bool Ball::intersects_cell(double *a, double *b) const{ return r2_test < rsq; } -Line::Line(int_t dim, double* x0, double *x1){ - this->dim = dim; +Line::Line() : Geometric(){ + x0 = NULL; + x1 = NULL; + for(int_t i=0; i<3; ++i) inv_dx[i] = 1; +} + +Line::Line(int_t dim, double* x0, double *x1) : Geometric(dim){ this->x0 = x0; this->x1 = x1; for(int_t i=0; idim = dim; +Box::Box() : Geometric(){ + x0 = NULL; + x1 = NULL; +} + +Box::Box(int_t dim, double* x0, double *x1) : Geometric(dim){ this->x0 = x0; this->x1 = x1; } @@ -82,8 +111,12 @@ bool Box::intersects_cell(double *a, double *b) const{ return true; } -Plane::Plane(int_t dim, double* origin, double *normal){ - this->dim = dim; +Plane::Plane() : Geometric(){ + origin = NULL; + normal = NULL; +} + +Plane::Plane(int_t dim, double* origin, double *normal) : Geometric(dim){ this->origin = origin; this->normal = normal; } @@ -102,8 +135,19 @@ bool Plane::intersects_cell(double *a, double *b) const{ return std::abs(s) <= r; } -Triangle::Triangle(int_t dim, double* x0, double *x1, double *x2){ - this->dim = dim; +Triangle::Triangle() : Geometric(){ + x0 = NULL; + x1 = NULL; + x2 = NULL; + for(int_t i=0; i<3; ++i){ + e0[i] = 0.0; + e1[i] = 0.0; + e2[i] = 0.0; + normal[i] = 0.0; + } +} + +Triangle::Triangle(int_t dim, double* x0, double *x1, double *x2) : Geometric(dim){ this->x0 = x0; this->x1 = x1; this->x2 = x2; @@ -257,6 +301,10 @@ bool Triangle::intersects_cell(double *a, double *b) const{ return true; } +VerticalTriangularPrism::VerticalTriangularPrism() : Triangle(){ + h = 0; +} + VerticalTriangularPrism::VerticalTriangularPrism(int_t dim, double* x0, double *x1, double *x2, double h) : Triangle(dim, x0, x1, x2){ this->h = h; } @@ -401,8 +449,24 @@ bool VerticalTriangularPrism::intersects_cell(double *a, double *b) const{ return true; } -Tetrahedron::Tetrahedron(int_t dim, double* x0, double *x1, double *x2, double *x3){ - this->dim = dim; +Tetrahedron::Tetrahedron() : Geometric(){ + x0 = NULL; + x1 = NULL; + x2 = NULL; + x3 = NULL; + for(int_t i=0; i<6; ++i){ + for(int_t j=0; j<3; ++j){ + edge_tans[i][j] = 0.0; + } + } + for(int_t i=0; i<4; ++i){ + for(int_t j=0; j<3; ++j){ + face_normals[i][j] = 0.0; + } + } +} + +Tetrahedron::Tetrahedron(int_t dim, double* x0, double *x1, double *x2, double *x3) : Geometric(dim){ this->x0 = x0; this->x1 = x1; this->x2 = x2; @@ -415,21 +479,17 @@ Tetrahedron::Tetrahedron(int_t dim, double* x0, double *x1, double *x2, double * edge_tans[4][i] = x3[i] - x1[i]; edge_tans[5][i] = x3[i] - x2[i]; } - face_normals[0][0] = edge_tans[0][1] * edge_tans[1][2] - edge_tans[0][2] * edge_tans[1][1]; - face_normals[0][1] = edge_tans[0][2] * edge_tans[1][0] - edge_tans[0][0] * edge_tans[1][2]; - face_normals[0][2] = edge_tans[0][0] * edge_tans[1][1] - edge_tans[0][1] * edge_tans[1][0]; + // cross e0, e1 (x0, x1, x2) + CROSS3D(edge_tans[0], edge_tans[1], face_normals[0]) - face_normals[1][0] = edge_tans[0][1] * edge_tans[3][2] - edge_tans[0][2] * edge_tans[3][1]; - face_normals[1][1] = edge_tans[0][2] * edge_tans[3][0] - edge_tans[0][0] * edge_tans[3][2]; - face_normals[1][2] = edge_tans[0][0] * edge_tans[3][1] - edge_tans[0][1] * edge_tans[3][0]; + // cross e0, e3 (x0, x1, x3) + CROSS3D(edge_tans[0], edge_tans[3], face_normals[1]) - face_normals[2][0] = edge_tans[1][1] * edge_tans[4][2] - edge_tans[1][2] * edge_tans[4][1]; - face_normals[2][1] = edge_tans[1][2] * edge_tans[4][0] - edge_tans[1][0] * edge_tans[4][2]; - face_normals[2][2] = edge_tans[1][0] * edge_tans[4][1] - edge_tans[1][1] * edge_tans[4][0]; + // cross e1, e3 (x0, x2, x3) + CROSS3D(edge_tans[1], edge_tans[3], face_normals[2]) - face_normals[3][0] = edge_tans[2][1] * edge_tans[5][2] - edge_tans[2][2] * edge_tans[5][1]; - face_normals[3][1] = edge_tans[2][2] * edge_tans[5][0] - edge_tans[2][0] * edge_tans[5][2]; - face_normals[3][2] = edge_tans[2][0] * edge_tans[5][1] - edge_tans[2][1] * edge_tans[5][0]; + // cross e2, e5 (x1, x2, x3) + CROSS3D(edge_tans[2], edge_tans[5], face_normals[3]) } bool Tetrahedron::intersects_cell(double *a, double *b) const{ diff --git a/discretize/_extensions/geom.h b/discretize/_extensions/geom.h index 033d1f564..1139f56e3 100644 --- a/discretize/_extensions/geom.h +++ b/discretize/_extensions/geom.h @@ -7,6 +7,9 @@ typedef std::size_t int_t; class Geometric{ public: int_t dim; + + Geometric(); + Geometric(int_t dim); virtual bool intersects_cell(double *a, double *b) const = 0; }; @@ -16,6 +19,7 @@ class Ball : public Geometric{ double r; double rsq; + Ball(); Ball(int_t dim, double* x0, double r); virtual bool intersects_cell(double *a, double *b) const; }; @@ -26,6 +30,7 @@ class Line : public Geometric{ double *x1; double inv_dx[3]; + Line(); Line(int_t dim, double* x0, double *x1); virtual bool intersects_cell(double *a, double *b) const; }; @@ -35,6 +40,7 @@ class Box : public Geometric{ double *x0; double *x1; + Box(); Box(int_t dim, double* x0, double *x1); virtual bool intersects_cell(double *a, double *b) const; }; @@ -44,6 +50,7 @@ class Plane : public Geometric{ double *origin; double *normal; + Plane(); Plane(int_t dim, double* origin, double *normal); virtual bool intersects_cell(double *a, double *b) const; }; @@ -58,6 +65,7 @@ class Triangle : public Geometric{ double e2[3]; double normal[3]; + Triangle(); Triangle(int_t dim, double* x0, double *x1, double *x2); virtual bool intersects_cell(double *a, double *b) const; }; @@ -65,6 +73,8 @@ class Triangle : public Geometric{ class VerticalTriangularPrism : public Triangle{ public: double h; + + VerticalTriangularPrism(); VerticalTriangularPrism(int_t dim, double* x0, double *x1, double *x2, double h); virtual bool intersects_cell(double *a, double *b) const; }; @@ -78,6 +88,7 @@ class Tetrahedron : public Geometric{ double edge_tans[6][3]; double face_normals[4][3]; + Tetrahedron(); Tetrahedron(int_t dim, double* x0, double *x1, double *x2, double *x3); virtual bool intersects_cell(double *a, double *b) const; }; diff --git a/discretize/_extensions/geom.pxd b/discretize/_extensions/geom.pxd index 01ef8d62f..097cfdcc0 100644 --- a/discretize/_extensions/geom.pxd +++ b/discretize/_extensions/geom.pxd @@ -3,22 +3,29 @@ from libcpp cimport bool cdef extern from "geom.h": ctypedef int int_t cdef cppclass Ball: - Ball(int_t dim, double * x0, double r) + Ball() except + + Ball(int_t dim, double * x0, double r) except + cdef cppclass Line: - Line(int_t dim, double * x0, double *x1) + Line() except + + Line(int_t dim, double * x0, double *x1) except + cdef cppclass Box: - Box(int_t dim, double * x0, double *x1) + Box() except + + Box(int_t dim, double * x0, double *x1) except + cdef cppclass Plane: - Plane(int_t dim, double * origin, double *normal) + Plane() except + + Plane(int_t dim, double * origin, double *normal) except + cdef cppclass Triangle: - Triangle(int_t dim, double * x0, double *x1, double *x2) + Triangle() except + + Triangle(int_t dim, double * x0, double *x1, double *x2) except + cdef cppclass VerticalTriangularPrism: - VerticalTriangularPrism(int_t dim, double * x0, double *x1, double *x2, double h) + VerticalTriangularPrism() except + + VerticalTriangularPrism(int_t dim, double * x0, double *x1, double *x2, double h) except + cdef cppclass Tetrahedron: - Tetrahedron(int_t dim, double * x0, double *x1, double *x2, double *x3) \ No newline at end of file + Tetrahedron() except + + Tetrahedron(int_t dim, double * x0, double *x1, double *x2, double *x3) except + \ No newline at end of file diff --git a/discretize/_extensions/tree_ext.pyx b/discretize/_extensions/tree_ext.pyx index 43f982117..7fac3b43c 100644 --- a/discretize/_extensions/tree_ext.pyx +++ b/discretize/_extensions/tree_ext.pyx @@ -562,42 +562,37 @@ cdef class _TreeMesh: >>> ax.add_patch(circ) >>> plt.show() """ - points = np.require(np.atleast_2d(points), dtype=np.float64, - requirements='C') - if points.shape[1] != self.dim: - raise ValueError(f"points array must be (N, {self.dim})") + points = self._require_ndarray_with_dim('points', points, ndim=2, dtype=np.float64) + radii = np.require(np.atleast_1d(radii), dtype=np.float64, requirements='C') + levels = np.require(np.atleast_1d(levels), dtype=np.int32, requirements='C') + + cdef int_t n_balls = _check_first_dim_broadcast(points=points, radii=radii, levels=levels) + cdef double[:, :] cs = points - radii = np.require(np.atleast_1d(radii), dtype=np.float64, - requirements='C') - if radii.shape[0] == 1: - radii = np.full(points.shape[0], radii[0], dtype=np.float64) cdef double[:] rs = radii - if points.shape[0] != rs.shape[0]: - raise ValueError("radii length must match the points array's first dimension") - - levels = np.require(np.atleast_1d(levels), dtype=np.int32, - requirements='C') - if levels.shape[0] == 1: - levels = np.full(points.shape[0], levels[0], dtype=np.int32) cdef int[:] ls = levels - if points.shape[0] != ls.shape[0]: - raise ValueError("level length must match the points array's first dimension") + + cdef int_t cs_step = cs.shape[0] > 1 + cdef int_t rs_step = rs.shape[0] > 1 + cdef int_t l_step = ls.shape[0] > 1 + cdef int_t i_c=0, i_r=0, i_l=0 if diagonal_balance is None: diagonal_balance = self._diagonal_balance cdef bool diag_balance = diagonal_balance - cdef geom.Ball *ball + cdef geom.Ball ball cdef int_t i cdef int l cdef int max_level = self.max_level - for i in range(ls.shape[0]): - ball = new geom.Ball(self._dim, &cs[i, 0], rs[i]) - l = ls[i] - if l < 0: - l = (max_level + 1) - (abs(l) % (max_level + 1)) - self.tree.refine_geom(ball[0], l, diag_balance) - del ball + for i in range(n_balls): + ball = geom.Ball(self._dim, &cs[i_c, 0], rs[i_r]) + l = _wrap_levels(ls[i_l], max_level) + self.tree.refine_geom(ball, l, diag_balance) + + i_c += cs_step + i_r += rs_step + i_l += l_step if finalize: self.finalize() @@ -652,40 +647,36 @@ cdef class _TreeMesh: >>> ax.add_patch(rect) >>> plt.show() """ - x0s = np.require(np.atleast_2d(x0s), dtype=np.float64, - requirements='C') - if x0s.shape[1] != self.dim: - raise ValueError(f"x0s array must be (N, {self.dim})") - x1s = np.require(np.atleast_2d(x1s), dtype=np.float64, - requirements='C') - if x1s.shape[1] != self.dim: - raise ValueError(f"x1s array must be (N, {self.dim})") - if x1s.shape[0] != x0s.shape[0]: - raise ValueError(f"x0s and x1s must have the same length") + x0s = self._require_ndarray_with_dim('x0s', x0s, ndim=2, dtype=np.float64) + x1s = self._require_ndarray_with_dim('x1s', x1s, ndim=2, dtype=np.float64) + levels = np.require(np.atleast_1d(levels), dtype=np.int32, requirements='C') + + cdef int_t n_boxes = _check_first_dim_broadcast(x0s=x0s, x1s=x1s, levels=levels) + cdef double[:, :] x0 = x0s cdef double[:, :] x1 = x1s - levels = np.require(np.atleast_1d(levels), dtype=np.int32, - requirements='C') - if levels.shape[0] == 1: - levels = np.full(x0.shape[0], levels[0], dtype=np.int32) cdef int[:] ls = levels - if x0.shape[0] != ls.shape[0]: - raise ValueError("level length must match the points array's first dimension") + + cdef int_t x0_step = x0.shape[0] > 1 + cdef int_t x1_step = x1.shape[0] > 1 + cdef int_t l_step = ls.shape[0] > 1 + cdef int_t i_x0=0, i_x1=0, i_l=0 if diagonal_balance is None: diagonal_balance = self._diagonal_balance cdef bool diag_balance = diagonal_balance - cdef geom.Box *box + cdef geom.Box box cdef int l cdef int max_level = self.max_level - for i in range(ls.shape[0]): - box = new geom.Box(self._dim, &x0[i, 0], &x1[i, 0]) - l = ls[i] - if l < 0: - l = (max_level + 1) - (abs(l) % (max_level + 1)) - self.tree.refine_geom(box[0], l, diag_balance) - del box + for i in range(n_boxes): + box = geom.Box(self._dim, &x0[i_x0, 0], &x1[i_x1, 0]) + l = _wrap_levels(ls[i_l], max_level) + self.tree.refine_geom(box, l, diag_balance) + + i_x0 += x0_step + i_x1 += x1_step + i_l += l_step if finalize: self.finalize() @@ -736,37 +727,32 @@ cdef class _TreeMesh: >>> plt.show() """ - path = np.require(np.atleast_2d(path), dtype=np.float64, - requirements='C') - if path.shape[1] != self.dim: - raise ValueError(f"path array must be (N, {self.dim})") + path = self._require_ndarray_with_dim('path', path, ndim=2, dtype=np.float64) + levels = np.require(np.atleast_1d(levels), dtype=np.int32, requirements='C') + cdef int_t n_segments = _check_first_dim_broadcast(path=path[:-1], levels=levels) cdef double[:, :] line_nodes = path - levels = np.require(np.atleast_1d(levels), dtype=np.int32, - requirements='C') - cdef int n_segments = line_nodes.shape[0] - 1; - if levels.shape[0] == 1: - levels = np.full(n_segments, levels[0], dtype=np.int32) - if n_segments != levels.shape[0]: - raise ValueError(f"inconsistent number of line segments {n_segments} and levels {levels.shape[0]}") - cdef int[:] ls = levels + cdef int_t line_step = line_nodes.shape[0] > 2 + cdef int_t l_step = levels.shape[0] > 1 + cdef int_t i_line=0, i_l=0 + if diagonal_balance is None: diagonal_balance = self._diagonal_balance cdef bool diag_balance = diagonal_balance - cdef geom.Line *line + cdef geom.Line line cdef int l cdef int max_level = self.max_level cdef int i for i in range(n_segments): - line = new geom.Line(self._dim, &line_nodes[i, 0], &line_nodes[i+1, 0]) - l = ls[i] - if l < 0: - l = (max_level + 1) - (abs(l) % (max_level + 1)) - self.tree.refine_geom(line[0], l, diag_balance) - del line + line = geom.Line(self._dim, &line_nodes[i_line, 0], &line_nodes[i_line+1, 0]) + l = _wrap_levels(ls[i_l], max_level) + self.tree.refine_geom(line, l, diag_balance) + + i_line += line_step + i_l += l_step if finalize: self.finalize() @@ -819,60 +805,36 @@ cdef class _TreeMesh: >>> plt.show() """ - origins = np.asarray(origins) - normals = np.asarray(normals) - levels = np.asarray(levels) - try: - broadcast_shape = np.broadcast_shapes(origins.shape, normals.shape, levels.shape) - except ValueError as err: - raise ValueError( - f"Incompatible shapes for origins:{origins.shape}, " - f"normals: {normals.shape}, and levels: {levels.shape}" - ) - if origins.shape[-1] != self.dim: - raise ValueError( - f"origins last dimension ({origins.shape[-1]}) should be equal to {self.dim}." - ) - if normals.shape[-1] != self.dim: - raise ValueError( - f"normals last dimension ({normals.shape[-1]}) should be equal to {self.dim}." - ) + origins = self._require_ndarray_with_dim('origins', origins, ndim=2, dtype=np.float64) + normals = self._require_ndarray_with_dim('normals', normals, ndim=2, dtype=np.float64) + levels = np.require(np.atleast_1d(levels), dtype=np.int32, requirements='C') - cdef double[:, :] x_0s = np.require( - np.atleast_2d(origins), dtype=np.float64, requirements='C' - ) - cdef double[:, :] norms = np.require( - np.atleast_2d(normals), dtype=np.float64, requirements='C' - ) - cdef int[:] ls = np.require( - np.atleast_1d(levels), dtype=np.int32, requirements='C' - ) - cdef int n_planes = broadcast_shape[0]; # number of broadcasted planes to process. + cdef int n_planes = _check_first_dim_broadcast(origins=origins, normals=normals, levels=levels) + + cdef double[:, :] x_0s = origins + cdef double[:, :] norms = normals + cdef int[:] ls = levels - cdef int origin_step = 1 if x_0s.shape[0] == n_planes else 0 - cdef int normal_step = 1 if norms.shape[0] == n_planes else 0 - cdef int level_step = 1 if ls.shape[0] == n_planes else 0 + cdef int_t origin_step = x_0s.shape[0] > 1 + cdef int_t normal_step = norms.shape[0] > 1 + cdef int_t level_step = ls.shape[0] > 1 + cdef int_t i_o=0, i_n=0, i_l=0 if diagonal_balance is None: diagonal_balance = self._diagonal_balance cdef bool diag_balance = diagonal_balance - cdef geom.Plane *plane + cdef geom.Plane plane cdef int l cdef int max_level = self.max_level - cdef int i_plane, i_o, i_n, i_l - i_0 = i_n = i_l = 0 + cdef int i_plane for i in range(n_planes): - plane = new geom.Plane(self._dim, &x_0s[i_0, 0], &norms[i_n, 0]) - l = ls[i_l] - if l < 0: - l = (max_level + 1) - (abs(l) % (max_level + 1)) - - self.tree.refine_geom(plane[0], l, diag_balance) - del plane + plane = geom.Plane(self._dim, &x_0s[i_o, 0], &norms[i_n, 0]) + l = _wrap_levels(ls[i_l], max_level) + self.tree.refine_geom(plane, l, diag_balance) - i_0 += origin_step + i_o += origin_step i_n += normal_step i_l += level_step if finalize: @@ -925,37 +887,33 @@ cdef class _TreeMesh: >>> plt.show() """ - triangle = np.require(np.atleast_2d(triangle), dtype=np.float64, requirements="C") - if triangle.ndim == 2: - triangle = triangle[None, ...] - if triangle.shape[-1] != self.dim or triangle.shape[-2] != 3: + triangle = self._require_ndarray_with_dim('triangle', triangle, ndim=3, dtype=np.float64) + if triangle.shape[-2] != 3: raise ValueError(f"triangle array must be (N, 3, {self.dim})") - cdef double[:, :, :] tris = triangle - - levels = np.require(np.atleast_1d(levels), dtype=np.int32, - requirements='C') - cdef int n_triangles = triangle.shape[0]; - if levels.shape[0] == 1: - levels = np.full(n_triangles, levels[0], dtype=np.int32) - if n_triangles != levels.shape[0]: - raise ValueError(f"inconsistent number of triangles {n_triangles} and levels {levels.shape[0]}") + levels = np.require(np.atleast_1d(levels), dtype=np.int32, requirements='C') + cdef int n_triangles = _check_first_dim_broadcast(triangle=triangle, levels=levels) + cdef double[:, :, :] tris = triangle cdef int[:] ls = levels + cdef int_t tri_step = tris.shape[0] > 1 + cdef int_t l_step = ls.shape[0] > 1 + cdef int_t i_tri=0, i_l=0 + if diagonal_balance is None: diagonal_balance = self._diagonal_balance cdef bool diag_balance = diagonal_balance - cdef geom.Triangle *triang + cdef geom.Triangle triang cdef int l cdef int max_level = self.max_level for i in range(n_triangles): - triang = new geom.Triangle(self._dim, &tris[i, 0, 0], &tris[i, 1, 0], &tris[i, 2, 0]) - l = ls[i] - if l < 0: - l = (max_level + 1) - (abs(l) % (max_level + 1)) - self.tree.refine_geom(triang[0], l, diag_balance) - del triang + triang = geom.Triangle(self._dim, &tris[i_tri, 0, 0], &tris[i_tri, 1, 0], &tris[i_tri, 2, 0]) + l = _wrap_levels(ls[i_l], max_level) + self.tree.refine_geom(triang, l, diag_balance) + + i_tri += tri_step + i_l += l_step if finalize: self.finalize() @@ -1016,45 +974,42 @@ cdef class _TreeMesh: """ if self.dim == 2: raise NotImplementedError("refine_vertical_trianglular_prism only implemented in 3D.") - triangle = np.require(np.atleast_2d(triangle), dtype=np.float64, requirements="C") - if triangle.ndim == 2: - triangle = triangle[None, ...] - if triangle.shape[-1] != self.dim or triangle.shape[-2] != 3: + triangle = self._require_ndarray_with_dim('triangle', triangle, ndim=3, dtype=np.float64) + if triangle.shape[-2] != 3: raise ValueError(f"triangle array must be (N, 3, {self.dim})") - cdef double[:, :, :] tris = triangle h = np.require(np.atleast_1d(h), dtype=np.float64, requirements="C") - levels = np.require(np.atleast_1d(levels), dtype=np.int32, - requirements='C') - cdef int n_triangles = triangle.shape[0]; - if levels.shape[0] == 1: - levels = np.full(n_triangles, levels[0], dtype=np.int32) - if h.shape[0] == 1: - h = np.full(n_triangles, h[0], dtype=np.float64) - if n_triangles != levels.shape[0]: - raise ValueError(f"inconsistent number of triangles {n_triangles} and levels {levels.shape[0]}") - if n_triangles != h.shape[0]: - raise ValueError(f"inconsistent number of triangles {n_triangles} and heights {h.shape[0]}") if np.any(h < 0): raise ValueError("All heights must be positive.") - cdef int[:] ls = levels + levels = np.require(np.atleast_1d(levels), dtype=np.int32, requirements='C') + + cdef int_t n_triangles = _check_first_dim_broadcast(triangle=triangle, h=h, levels=levels) + + cdef double[:, :, :] tris = triangle cdef double[:] hs = h + cdef int[:] ls = levels + + cdef int_t tri_step = tris.shape[0] > 1 + cdef int_t h_step = hs.shape[0] > 1 + cdef int_t l_step = ls.shape[0] > 1 + cdef int_t i_tri=0, i_h=0, i_l=0 if diagonal_balance is None: diagonal_balance = self._diagonal_balance cdef bool diag_balance = diagonal_balance - cdef geom.VerticalTriangularPrism *vert_prism + cdef geom.VerticalTriangularPrism vert_prism cdef int l cdef int max_level = self.max_level for i in range(n_triangles): - vert_prism = new geom.VerticalTriangularPrism(self._dim, &tris[i, 0, 0], &tris[i, 1, 0], &tris[i, 2, 0], hs[i]) - l = ls[i] - if l < 0: - l = (max_level + 1) - (abs(l) % (max_level + 1)) - self.tree.refine_geom(vert_prism[0], l, diag_balance) - del vert_prism + vert_prism = geom.VerticalTriangularPrism(self._dim, &tris[i_tri, 0, 0], &tris[i_tri, 1, 0], &tris[i_tri, 2, 0], hs[i_h]) + l = _wrap_levels(ls[i_l], max_level) + self.tree.refine_geom(vert_prism, l, diag_balance) + + i_tri += tri_step + i_h += h_step + i_l += l_step if finalize: self.finalize() @@ -1111,37 +1066,34 @@ cdef class _TreeMesh: """ if self.dim == 2: return self.refine_triangle(tetra, levels, finalize=finalize, diagonal_balance=diagonal_balance) - tetra = np.require(np.atleast_2d(tetra), dtype=np.float64, requirements="C") - if tetra.ndim == 2: - tetra = tetra[None, ...] - if tetra.shape[-1] != self.dim or tetra.shape[-2] != self.dim+1: + tetra = self._require_ndarray_with_dim('tetra', tetra, ndim=3, dtype=np.float64) + if tetra.shape[-2] != self.dim+1: raise ValueError(f"tetra array must be (N, {self.dim+1}, {self.dim})") - cdef double[:, :, :] tris = tetra + levels = np.require(np.atleast_1d(levels), dtype=np.int32, requirements='C') - levels = np.require(np.atleast_1d(levels), dtype=np.int32, - requirements='C') - cdef int n_triangles = tetra.shape[0]; - if levels.shape[0] == 1: - levels = np.full(n_triangles, levels[0], dtype=np.int32) - if n_triangles != levels.shape[0]: - raise ValueError(f"inconsistent number of triangles {n_triangles} and levels {levels.shape[0]}") + cdef int_t n_triangles = _check_first_dim_broadcast(tetra=tetra, levels=levels) + cdef double[:, :, :] tris = tetra cdef int[:] ls = levels + cdef int_t tri_step = tris.shape[0] > 1 + cdef int_t l_step = ls.shape[0] > 1 + cdef int_t i_tri=0, i_l=0 + if diagonal_balance is None: diagonal_balance = self._diagonal_balance cdef bool diag_balance = diagonal_balance - cdef geom.Tetrahedron *tet + cdef geom.Tetrahedron tet cdef int l cdef int max_level = self.max_level for i in range(n_triangles): - l = ls[i] - tet = new geom.Tetrahedron(self._dim, &tris[i, 0, 0], &tris[i, 1, 0], &tris[i, 2, 0], &tris[i, 3, 0]) - if l < 0: - l = (max_level + 1) - (abs(l) % (max_level + 1)) - self.tree.refine_geom(tet[0], l, diag_balance) - del tet + l = _wrap_levels(ls[i_l], max_level) + tet = geom.Tetrahedron(self._dim, &tris[i_tri, 0, 0], &tris[i_tri, 1, 0], &tris[i_tri, 2, 0], &tris[i_tri, 3, 0]) + self.tree.refine_geom(tet, l, diag_balance) + + i_tri += tri_step + i_l += l_step if finalize: self.finalize() @@ -1178,25 +1130,29 @@ cdef class _TreeMesh: ----------------------- Total : 40 """ - points = np.require(np.atleast_2d(points), dtype=np.float64, - requirements='C') - if points.shape[1] != self.dim: - raise ValueError(f"points array must be (N, {self.dim})") + points = self._require_ndarray_with_dim('points', points, ndim=2, dtype=np.float64) + levels = np.require(np.atleast_1d(levels), dtype=np.int32, requirements='C') + cdef int_t n_points = _check_first_dim_broadcast(points=points, levels=levels) + cdef double[:, :] cs = points - cdef int[:] ls = np.require(np.atleast_1d(levels), dtype=np.int32, - requirements='C') - if points.shape[0] != ls.shape[0]: - raise ValueError("level length must match the points array's first dimension") + cdef int[:] ls = levels + cdef int l cdef int max_level = self.max_level if diagonal_balance is None: diagonal_balance = self._diagonal_balance cdef bool diag_balance = diagonal_balance + + cdef int_t p_step = cs.shape[0] > 1 + cdef int_t l_step = ls.shape[0] > 1 + cdef int_t i_p=0, i_l=0 + for i in range(ls.shape[0]): - l = ls[i] - if l < 0: - l = (max_level + 1) - (abs(l) % (max_level + 1)) - self.tree.insert_cell(&cs[i, 0], l, diagonal_balance) + l = _wrap_levels(ls[i_l], max_level) + self.tree.insert_cell(&cs[i_p, 0], l, diagonal_balance) + + i_l += l_step + i_p += p_step if finalize: self.finalize() @@ -1235,6 +1191,184 @@ cdef class _TreeMesh: """Number the cells, nodes, faces, and edges of the TreeMesh.""" self.tree.number() + def get_containing_cells(self, points): + """ + + Parameters + ---------- + points : (dim) or (n_point, dim) array_like + The locations to query for the containing cells + + Returns + ------- + numpy.intp or (n_point) numpy.ndarray of numpy.intp + The indexes of cells containing each point. + + """ + cdef double[:,:] d_locs = self._require_ndarray_with_dim( + 'locs', points, ndim=2, dtype=np.float64 + ) + cdef int_t n_locs = d_locs.shape[0] + cdef np.int64_t[:] indexes = np.empty(n_locs, dtype=np.int64) + cdef double x, y, z + for i in range(n_locs): + x = d_locs[i, 0] + y = d_locs[i, 1] + if self._dim == 3: + z = d_locs[i, 2] + else: + z = 0 + indexes[i] = self.tree.containing_cell(x, y, z).index + if n_locs==1: + return indexes[0] + return np.array(indexes) + + def get_cells_within_ball(self, center, double radius): + """Find the indices of cells that overlap a ball + + Parameters + ---------- + center : (dim) array_like + center of the ball. + radius : float + radius of the ball + + Returns + ------- + numpy.ndarray of numpy.intp + The indices of cells which overlap the ball. + """ + cdef double[:] a = self._require_ndarray_with_dim('center', center, dtype=np.float64) + + cdef geom.Ball ball = geom.Ball(self._dim, &a[0], radius) + return np.array(self.tree.find_cells_geom(ball)) + + def get_cells_along_line(self, x0, x1): + """Find the cells along a line segment. + + Parameters + ---------- + x0,x1 : (dim) array_like + Beginning and ending point of the line segment. + + Returns + ------- + numpy.ndarray of numpy.intp + Indices for cells that contain the a line defined by the two input + points, ordered in the direction of the line. + """ + cdef double[:] start = self._require_ndarray_with_dim('x0', x0, dtype=np.float64) + cdef double[:] end = self._require_ndarray_with_dim('x1', x1, dtype=np.float64) + + cdef geom.Line line = geom.Line(self._dim, &start[0], &end[0]) + return np.array(self.tree.find_cells_geom(line)) + + def get_cells_within_aabb(self, x_min, x_max): + """Find the indices of cells that overlap an axis aligned bounding box (aabb) + + Parameters + ---------- + x_min : (dim, ) array_like + Minimum extent of the box. + x_max : (dim, ) array_like + Maximum extent of the box. + + Returns + ------- + numpy.ndarray of numpy.intp + The indices of cells which overlap the axis aligned bounding box. + """ + cdef double[:] a = self._require_ndarray_with_dim('x_min', x_min, dtype=np.float64) + cdef double[:] b = self._require_ndarray_with_dim('x_max', x_max, dtype=np.float64) + + cdef geom.Box box = geom.Box(self._dim, &a[0], &b[0]) + return np.array(self.tree.find_cells_geom(box)) + + def get_cells_along_plane(self, origin, normal): + """Find the indices of cells that intersect a plane. + + Parameters + ---------- + origin : (dim) array_like + normal : (dim) array_like + + Returns + ------- + numpy.ndarray of numpy.intp + The indices of cells which intersect the plane. + """ + cdef double[:] orig = self._require_ndarray_with_dim('origin', origin, dtype=np.float64) + cdef double[:] norm = self._require_ndarray_with_dim('normal', normal, dtype=np.float64) + + cdef geom.Plane plane = geom.Plane(self._dim, &orig[0], &norm[0]) + return np.array(self.tree.find_cells_geom(plane)) + + def get_cells_within_triangle(self, x0, x1, x2): + """Find the indices of cells that overlap a triangle. + + Parameters + ---------- + x0, x1, x2 : (dim) array_like + The three points of the triangle. + + Returns + ------- + numpy.ndarray of numpy.intp + The indices of cells which overlap the triangle. + """ + cdef double[:] a = self._require_ndarray_with_dim('x0', x0, dtype=np.float64) + cdef double[:] b = self._require_ndarray_with_dim('x1', x1, dtype=np.float64) + cdef double[:] c = self._require_ndarray_with_dim('x2', x2, dtype=np.float64) + + cdef geom.Triangle triangle = geom.Triangle(self._dim, &a[0], &b[0], &c[0]) + return np.array(self.tree.find_cells_geom(triangle)) + + def get_cells_within_vertical_trianglular_prism(self, x0, x1, x2, double h): + """Find the indices of cells that overlap a vertical triangular prism. + + Parameters + ---------- + x0, x1, x2 : (dim) array_like + The three points of the triangle, assumes the top and bottom + faces are parallel. + h : float + The height of the prism. + + Returns + ------- + numpy.ndarray of numpy.intp + The indices of cells which overlap the vertical triangular prism. + """ + if self.dim == 2: + raise NotImplementedError("vertical_trianglular_prism only implemented in 3D.") + cdef double[:] a = self._require_ndarray_with_dim('x0', x0, dtype=np.float64) + cdef double[:] b = self._require_ndarray_with_dim('x1', x1, dtype=np.float64) + cdef double[:] c = self._require_ndarray_with_dim('x2', x2, dtype=np.float64) + + cdef geom.VerticalTriangularPrism vert = geom.VerticalTriangularPrism(self._dim, &a[0], &b[0], &c[0], h) + return np.array(self.tree.find_cells_geom(vert)) + + def get_cells_within_tetrahedron(self, x0, x1, x2, x3): + """Find the indices of cells that overlap a tetrahedron. + + Parameters + ---------- + x0, x1, x2, x3: (dim) array_like + The four points of the tetrahedron. + + Returns + ------- + numpy.ndarray of numpy.intp + The indices of cells which overlap the triangle. + """ + cdef double[:] a = self._require_ndarray_with_dim('x0', x0, dtype=np.float64) + cdef double[:] b = self._require_ndarray_with_dim('x1', x1, dtype=np.float64) + cdef double[:] c = self._require_ndarray_with_dim('x2', x2, dtype=np.float64) + cdef double[:] d = self._require_ndarray_with_dim('x3', x3, dtype=np.float64) + + cdef geom.Tetrahedron tet = geom.Tetrahedron(self._dim, &a[0], &b[0], &c[0], &d[0]) + return np.array(self.tree.find_cells_geom(tet)) + def _set_origin(self, origin): if not isinstance(origin, (list, tuple, np.ndarray)): raise ValueError('origin must be a list, tuple or numpy array') @@ -5191,34 +5325,6 @@ cdef class _TreeMesh: is_b |= (nodes[:, 2] == z0) | (nodes[:, 2] == zF) return sp.eye(self.n_nodes, format='csr')[is_b] - def _get_containing_cell_index(self, loc): - cdef double x, y, z - x = loc[0] - y = loc[1] - if self._dim == 3: - z = loc[2] - else: - z = 0 - return self.tree.containing_cell(x, y, z).index - - def _get_containing_cell_indexes(self, locs): - locs = np.require(np.atleast_2d(locs), dtype=np.float64, requirements='C') - cdef double[:,:] d_locs = locs - cdef int_t n_locs = d_locs.shape[0] - cdef np.int64_t[:] indexes = np.empty(n_locs, dtype=np.int64) - cdef double x, y, z - for i in range(n_locs): - x = d_locs[i, 0] - y = d_locs[i, 1] - if self._dim == 3: - z = d_locs[i, 2] - else: - z = 0 - indexes[i] = self.tree.containing_cell(x, y, z).index - if n_locs==1: - return indexes[0] - return np.array(indexes) - def _count_cells_per_index(self): cdef np.int64_t[:] counts = np.zeros(self.max_level+1, dtype=np.int64) for cell in self.tree.cells: @@ -6352,7 +6458,7 @@ cdef class _TreeMesh: same_base = False if same_base: - in_cell_inds = self._get_containing_cell_indexes(out_tens_mesh.cell_centers) + in_cell_inds = self.get_containing_cells(out_tens_mesh.cell_centers) # Every cell input cell is gauranteed to be a lower level than the output tenser mesh # therefore all weights a 1.0 if values is not None: @@ -6491,7 +6597,7 @@ cdef class _TreeMesh: if same_base: - out_cell_inds = self._get_containing_cell_indexes(in_tens_mesh.cell_centers) + out_cell_inds = self.get_containing_cells(in_tens_mesh.cell_centers) ws = in_tens_mesh.cell_volumes/self.cell_volumes[out_cell_inds] if values is not None: if output is None: @@ -6648,7 +6754,7 @@ cdef class _TreeMesh: Parameters ---------- - rectangle: (dim * 2) array_like + rectangle: (2 * dim) array_like array ordered ``[x_min, x_max, y_min, y_max, (z_min, z_max)]`` describing the axis aligned rectangle of interest. @@ -6657,46 +6763,72 @@ cdef class _TreeMesh: list of int The indices of cells which overlap the axis aligned rectangle. """ - cdef double xm[3] - cdef double xp[3] - cdef double[:] origin = self._origin - cdef double[:] xF - if self.dim == 2: - xF = np.array([self._xs[-1], self._ys[-1]]) - else: - xF = np.array([self._xs[-1], self._ys[-1], self._zs[-1]]) - for i_d in range(self._dim): - xm[i_d] = min(rectangle[2 * i_d], xF[i_d]) - xp[i_d] = max(rectangle[2 * i_d + 1], origin[i_d]) - - cdef geom.Box *box = new geom.Box(self._dim, xm, xp) - cdef vector[int_t] cell_inds = self.tree.find_cells_geom(box[0]) - del box + return self.get_cells_within_aabb(*rectangle.reshape(self.dim, 2).T) - return cell_inds - - def get_cells_along_line(self, x0, x1): - """Find the cells along a line segment defined by two points. + def _require_ndarray_with_dim(self, name, arr, ndim=1, dtype=None, requirements=None): + """Returns an ndarray that has dim along it's last dimension, with ndim dims, Parameters ---------- - x0,x1 : (dim) array_like - Begining and ending point of the line segment. + name : str + name of the parameter for raised error + arr : array_like + ndim : {1, 2, 3} + dtype, optional + dtype input to np.requires + requirements, optional + requirements input to np.requires, defaults to 'C'. Returns ------- - list of int - Indices for cells that contain the a line defined by the two input - points, ordered in the direction of the line. + numpy.ndarray + validated array """ - cdef double[:] start = np.require(x0, dtype=np.float64, requirements='A') - cdef double[:] end = np.require(x0, dtype=np.float64, requirements='A') - - cdef geom.Line *line = new geom.Line(self._dim, &start[0], &end[0]) - cdef vector[int_t] cell_inds = self.tree.find_cells_geom(line[0]) - del line - return cell_inds + if requirements is None: + requirements = 'C' + if ndim == 1: + arr = np.atleast_1d(arr) + elif ndim > 1: + arr = np.atleast_2d(arr) + if ndim == 3 and arr.ndim != 3: + arr = arr[None, ...] + else: + arr = np.asarray(arr) + if arr.ndim != ndim: + raise ValueError(f"{name} must have at most {ndim} dimensions.") + if arr.shape[-1] != self.dim: + raise ValueError( + f"Expected the last dimension of {name}.shape={arr.shape} to be {self.dim}." + ) + return np.require(arr, dtype=dtype, requirements=requirements) + + +def _check_first_dim_broadcast(**kwargs): + """Perform a check to make sure that the first dimensions of the inputs will broadcast.""" + n_items = 1 + err = False + for key, arr in kwargs.items(): + test_len = arr.shape[0] + if test_len != 1: + if n_items == 1: + n_items = test_len + elif test_len != n_items: + err = True + break + if err: + message = "First dimensions of" + for key, arr in kwargs.items(): + message += f" {key}: {arr.shape}," + message = message[:-1] + message += " do not broadcast." + raise ValueError(message) + return n_items cdef inline double _clip01(double x) nogil: return min(1, max(x, 0)) + +cdef inline int _wrap_levels(int l, int max_level): + if l < 0: + l = (max_level + 1) - (abs(l) % (max_level + 1)) + return l diff --git a/discretize/mixins/mpl_mod.py b/discretize/mixins/mpl_mod.py index 9a9c614d0..e604c8d9c 100644 --- a/discretize/mixins/mpl_mod.py +++ b/discretize/mixins/mpl_mod.py @@ -2158,7 +2158,7 @@ def __plot_slice_tree( ) XS[normalInd] = np.ones_like(XS[antiNormalInd[0]]) * slice_loc loc_grid = np.c_[XS[0].reshape(-1), XS[1].reshape(-1), XS[2].reshape(-1)] - inds = np.unique(self._get_containing_cell_indexes(loc_grid)) + inds = np.unique(self.get_containing_cells(loc_grid)) grid2d = self.gridCC[inds][:, antiNormalInd] levels = self._cell_levels_by_indexes(inds) - level_diff diff --git a/discretize/tests.py b/discretize/tests.py index 501520e12..e06dbf7ad 100644 --- a/discretize/tests.py +++ b/discretize/tests.py @@ -876,6 +876,122 @@ def random(size, iscomplex): return passed +def assert_cell_intersects_geometric( + cell, points, edges=None, faces=None, as_refine=False +): + """Asserst if a cell intersects a convex geometric object. + + Used to determine if a axis aligned cell with the properties + + Parameters + ---------- + cell : tree_mesh.TreeCell + Must have cell.origin and cell.h properties + points : (*, dim) array_like + The points of the geometric object. + edges : (*, 2) array_like of int, optional + The 2 indices into points defining each edge + faces : (*, 3) array_like of int, optional + The 3 indices into points which lie on each face. These are used + to define the face normals from the three points as + ``norm = cross(p1 - p0, p2 - p0)``. + as_refine : bool, or int + If ``True`` (or a nonzero integer), this function will not assert and instead + return either 0, -1, or the integer making it suitable (but slow) for + refining a TreeMesh. + + Returns + ------- + int + + Raises + ------ + AssertionError + """ + __tracebackhide__ = True + + x0 = cell.origin + xF = x0 + cell.h + + points = np.atleast_2d(points) + if edges is not None: + edges = np.atleast_2d(edges) + if edges.shape[-1] != 2: + raise ValueError("Last dimension of edges must be 2.") + if faces is not None: + faces = np.atleast_2d(faces) + if faces.shape[-1] != 3: + raise ValueError("Last dimension of faces must be 3.") + + do_asserts = not as_refine + level = -1 + if as_refine and not isinstance(as_refine, bool): + level = int(as_refine) + + dim = points.shape[-1] + # first the bounding box tests (associated with the 3 face normals of the cell + mins = points.min(axis=0) + for i_d in range(dim): + if do_asserts: + assert mins[i_d] <= xF[i_d] + else: + if mins[i_d] > xF[i_d]: + return 0 + + maxs = points.max(axis=0) + for i_d in range(dim): + if do_asserts: + assert maxs[i_d] >= x0[i_d] + else: + if maxs[i_d] < x0[i_d]: + return 0 + + # create array of all the box points + if edges is not None or faces is not None: + box_points = np.meshgrid(*list(zip(x0, xF))) + box_points = np.stack(box_points, axis=-1).reshape(-1, dim) + + def project_min_max(points, axis): + ps = points @ axis + return ps.min(), ps.max() + + if edges is not None and dim > 1: + box_dirs = np.eye(dim) + edge_dirs = points[edges[:, 1]] - points[edges[:, 0]] + # perform the edge-edge intersection tests + # these project all points onto the axis formed by the cross + # product of the geometric edges and the bounding box's edges/faces normals + for i in range(edges.shape[0]): + for j in range(dim): + if dim == 3: + axis = np.cross(edge_dirs[i], box_dirs[j]) + else: + axis = [-edge_dirs[i, 1], edge_dirs[i, 0]] + bmin, bmax = project_min_max(box_points, axis) + gmin, gmax = project_min_max(points, axis) + if do_asserts: + assert bmax >= gmin and bmin <= gmax + else: + if bmax < gmin or bmin > gmax: + return 0 + + if faces is not None and dim > 2: + face_normals = np.cross( + points[faces[:, 1]] - points[faces[:, 0]], + points[faces[:, 2]] - points[faces[:, 0]], + ) + for i in range(faces.shape[0]): + bmin, bmax = project_min_max(box_points, face_normals[i]) + gmin, gmax = project_min_max(points, face_normals[i]) + if do_asserts: + assert bmax >= gmin and bmin <= gmax + else: + if bmax < gmin or bmin > gmax: + return 0 + if not do_asserts: + return level + + # DEPRECATIONS setupMesh = deprecate_function( setup_mesh, "setupMesh", removal_version="1.0.0", error=True diff --git a/discretize/tree_mesh.py b/discretize/tree_mesh.py index ad9227361..c76edb552 100644 --- a/discretize/tree_mesh.py +++ b/discretize/tree_mesh.py @@ -89,7 +89,6 @@ from discretize.base import BaseTensorMesh from discretize.operators import InnerProducts, DiffOperators from discretize.mixins import InterfaceMixins, TreeMeshIO -from discretize.utils import as_array_n_by_dim from discretize._extensions.tree_ext import _TreeMesh, TreeCell # NOQA F401 import numpy as np import scipy.sparse as sp @@ -925,9 +924,7 @@ def face_z_divergence(self): # NOQA D102 def point2index(self, locs): # NOQA D102 # Documentation inherited from discretize.base.BaseMesh - locs = as_array_n_by_dim(locs, self.dim) - inds = self._get_containing_cell_indexes(locs) - return inds + return self.get_containing_cell_indexes(locs) def cell_levels_by_index(self, indices): """Fast function to return a list of levels for the given cell indices. @@ -958,14 +955,11 @@ def get_interpolation_matrix( # NOQA D102 "The zerosOutside keyword argument has been removed, please use zeros_outside. " "This will be removed in discretize 1.0.0" ) - locs = as_array_n_by_dim(locs, self.dim) location_type = self._parse_location_type(location_type) - if self.dim == 2 and "z" in location_type: raise NotImplementedError("Unable to interpolate from Z edges/faces in 2D") - locs = np.require(np.atleast_2d(locs), dtype=np.float64, requirements="C") - + locs = self._require_ndarray_with_dim("locs", locs, ndim=2, dtype=np.float64) if location_type == "nodes": Av = self._getNodeIntMat(locs, zeros_outside) elif location_type in ["edges_x", "edges_y", "edges_z"]: diff --git a/tests/tree/test_refine.py b/tests/tree/test_refine.py index e2ec53da4..c37b1b554 100644 --- a/tests/tree/test_refine.py +++ b/tests/tree/test_refine.py @@ -1,92 +1,70 @@ import discretize import numpy as np import pytest +from discretize.tests import assert_cell_intersects_geometric def test_2d_line(): - segments = np.array([[0.1, 0.3], [0.3, 0.9]]) + segments = np.array([[0.12, 0.33], [0.32, 0.93]]) - mesh = discretize.TreeMesh([64, 64]) - mesh.refine_line(segments, mesh.max_level) + mesh1 = discretize.TreeMesh([64, 64]) + mesh1.refine_line(segments, -1) - cells = mesh.get_cells_along_line(segments[0], segments[1]) - levels = mesh.cell_levels_by_index(cells) + def refine_line(cell): + return assert_cell_intersects_geometric( + cell, segments, edges=[0, 1], as_refine=True + ) - np.testing.assert_equal(levels, mesh.max_level) + mesh2 = discretize.TreeMesh([64, 64]) + mesh2.refine(refine_line) + + assert mesh2.equals(mesh1) def test_3d_line(): - segments = np.array([[0.1, 0.3, 0.2], [0.3, 0.9, 0.7]]) + segments = np.array([[0.12, 0.33, 0.19], [0.32, 0.93, 0.68]]) + + mesh1 = discretize.TreeMesh([64, 64, 64]) + mesh1.refine_line(segments, -1) - mesh = discretize.TreeMesh([64, 64, 64]) - mesh.refine_line(segments, mesh.max_level) + def refine_line(cell): + return assert_cell_intersects_geometric( + cell, segments, edges=[0, 1], as_refine=True + ) - cells = mesh.get_cells_along_line(segments[0], segments[1]) - levels = mesh.cell_levels_by_index(cells) + mesh2 = discretize.TreeMesh([64, 64, 64]) + mesh2.refine(refine_line) - np.testing.assert_equal(levels, mesh.max_level) + assert mesh2.equals(mesh1) def test_line_errors(): mesh = discretize.TreeMesh([64, 64]) - segments2D = np.array([[0.1, 0.3], [0.3, 0.9]]) - segments3D = np.array([[0.1, 0.3, 0.2], [0.3, 0.9, 0.7]]) + rng = np.random.default_rng(512) + segments2D = rng.random((5, 2)) + segments3D = rng.random((5, 3)) # incorrect dimension with pytest.raises(ValueError): mesh.refine_line(segments3D, mesh.max_level, finalize=False) # incorrect number of levels + # 4 segments won't broadcast to 2 levels with pytest.raises(ValueError): mesh.refine_line(segments2D, [mesh.max_level, 3], finalize=False) def test_triangle2d(): - # define a slower function that is surely accurate triangle = np.array([[0.14, 0.31], [0.32, 0.96], [0.23, 0.87]]) - edges = np.stack( - [ - triangle[1] - triangle[0], - triangle[2] - triangle[1], - triangle[2] - triangle[0], - ] - ) + edges = [[0, 1], [0, 2], [1, 2]] - def project_min_max(points, axis): - ps = points @ axis - return ps.min(), ps.max() - - def refine_triangle2d(cell): - # The underlying C functions are more optimized - # but this is more explicit - x0 = cell.origin - xF = x0 + cell.h - - mins = triangle.min(axis=0) - if np.any(mins > xF): - return 0 - maxs = triangle.max(axis=0) - if np.any(maxs < x0): - return 0 - - box_points = np.array( - [ - [x0[0], x0[1]], - [x0[0], xF[1]], - [xF[0], x0[1]], - [xF[0], xF[1]], - ] + def refine_triangle(cell): + return assert_cell_intersects_geometric( + cell, triangle, edges=edges, as_refine=True ) - for i in range(3): - axis = [-edges[i, 1], edges[i, 0]] - bmin, bmax = project_min_max(box_points, axis) - tmin, tmax = project_min_max(triangle, axis) - if bmax < tmin or bmin > tmax: - return 0 - return -1 mesh1 = discretize.TreeMesh([64, 64]) - mesh1.refine(refine_triangle2d) + mesh1.refine(refine_triangle) mesh2 = discretize.TreeMesh([64, 64]) mesh2.refine_triangle(triangle, -1) @@ -95,60 +73,14 @@ def refine_triangle2d(cell): def test_triangle3d(): - # define a slower function that is surely accurate triangle = np.array([[0.14, 0.31, 0.23], [0.32, 0.96, 0.41], [0.23, 0.87, 0.72]]) - edges = np.stack( - [ - triangle[1] - triangle[0], - triangle[2] - triangle[1], - triangle[2] - triangle[0], - ] - ) - triangle_norm = np.cross(edges[0], edges[1]) - triangle_proj = triangle[0] @ triangle_norm - - def project_min_max(points, axis): - ps = points @ axis - return ps.min(), ps.max() - - box_normals = np.eye(3) + edges = [[0, 1], [0, 2], [1, 2]] + faces = [0, 1, 2] def refine_triangle(cell): - # The underlying C functions are more optimized - # but this is more explicit - x0 = cell.origin - xF = x0 + cell.h - - mins = triangle.min(axis=0) - if np.any(mins > xF): - return 0 - maxs = triangle.max(axis=0) - if np.any(maxs < x0): - return 0 - - box_points = np.array( - [ - [x0[0], x0[1], x0[2]], - [x0[0], xF[1], x0[2]], - [xF[0], x0[1], x0[2]], - [xF[0], xF[1], x0[2]], - [x0[0], x0[1], xF[2]], - [x0[0], xF[1], xF[2]], - [xF[0], x0[1], xF[2]], - [xF[0], xF[1], xF[2]], - ] + return assert_cell_intersects_geometric( + cell, triangle, edges=edges, faces=faces, as_refine=True ) - for i in range(3): - for j in range(3): - axis = np.cross(edges[i], box_normals[j]) - bmin, bmax = project_min_max(box_points, axis) - tmin, tmax = project_min_max(triangle, axis) - if bmax < tmin or bmin > tmax: - return 0 - bmin, bmax = project_min_max(box_points, triangle_norm) - if bmax < triangle_proj or bmin > triangle_proj: - return 0 - return -1 mesh1 = discretize.TreeMesh([64, 64, 64]) mesh1.refine(refine_triangle) @@ -184,49 +116,15 @@ def test_tetra2d(): # It actually calls triangle refine... just double check that works # define a slower function that is surely accurate triangle = np.array([[0.14, 0.31], [0.32, 0.96], [0.23, 0.87]]) - edges = np.stack( - [ - triangle[1] - triangle[0], - triangle[2] - triangle[1], - triangle[2] - triangle[0], - ] - ) + edges = [[0, 1], [0, 2], [1, 2]] - def project_min_max(points, axis): - ps = points @ axis - return ps.min(), ps.max() - - def refine_triangle2d(cell): - # The underlying C functions are more optimized - # but this is more explicit - x0 = cell.origin - xF = x0 + cell.h - - mins = triangle.min(axis=0) - if np.any(mins > xF): - return 0 - maxs = triangle.max(axis=0) - if np.any(maxs < x0): - return 0 - - box_points = np.array( - [ - [x0[0], x0[1]], - [x0[0], xF[1]], - [xF[0], x0[1]], - [xF[0], xF[1]], - ] + def refine_triangle(cell): + return assert_cell_intersects_geometric( + cell, triangle, edges=edges, as_refine=True ) - for i in range(3): - axis = [-edges[i, 1], edges[i, 0]] - bmin, bmax = project_min_max(box_points, axis) - tmin, tmax = project_min_max(triangle, axis) - if bmax < tmin or bmin > tmax: - return 0 - return -1 mesh1 = discretize.TreeMesh([64, 64]) - mesh1.refine(refine_triangle2d) + mesh1.refine(refine_triangle) mesh2 = discretize.TreeMesh([64, 64]) mesh2.refine_tetrahedron(triangle, -1) @@ -235,84 +133,21 @@ def refine_triangle2d(cell): def test_tetra3d(): - # define a slower function that is surely accurate simplex = np.array( [[0.32, 0.21, 0.15], [0.82, 0.19, 0.34], [0.14, 0.82, 0.29], [0.32, 0.27, 0.83]] ) - edges = np.stack( - [ - simplex[1] - simplex[0], - simplex[2] - simplex[0], - simplex[2] - simplex[1], - simplex[3] - simplex[0], - simplex[3] - simplex[1], - simplex[3] - simplex[2], - ] - ) - - def project_min_max(points, axis): - ps = points @ axis - return ps.min(), ps.max() - - box_normals = np.eye(3) + edges = [[0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3]] + faces = [ + [0, 1, 2], + [0, 1, 3], + [0, 2, 3], + [1, 2, 3], + ] def refine_simplex(cell): - x0 = cell.origin - xF = x0 + cell.h - simp = simplex - - # Bounding box tests - # 3(x2) box face normals - mins = simp.min(axis=0) - if np.any(mins > xF): - return 0 - maxs = simp.max(axis=0) - if np.any(maxs < x0): - return 0 - - box_points = np.array( - [ - [x0[0], x0[1], x0[2]], - [x0[0], xF[1], x0[2]], - [xF[0], x0[1], x0[2]], - [xF[0], xF[1], x0[2]], - [x0[0], x0[1], xF[2]], - [x0[0], xF[1], xF[2]], - [xF[0], x0[1], xF[2]], - [xF[0], xF[1], xF[2]], - ] + return assert_cell_intersects_geometric( + cell, simplex, edges=edges, faces=faces, as_refine=True ) - # 3 box edges tangents and 6 simplex edge tangents - for i in range(6): - for j in range(3): - axis = np.cross(edges[i], box_normals[j]) - bmin, bmax = project_min_max(box_points, axis) - tmin, tmax = project_min_max(simp, axis) - if bmax < tmin or bmin > tmax: - return 0 - - # 4 simplex faces - axis = np.cross(edges[0], edges[1]) - tmin, tmax = project_min_max(simp, axis) - bmin, bmax = project_min_max(box_points, axis) - if bmax < tmin or bmin > tmax: - return 0 - axis = np.cross(edges[0], edges[3]) - tmin, tmax = project_min_max(simp, axis) - bmin, bmax = project_min_max(box_points, axis) - if bmax < tmin or bmin > tmax: - return 0 - axis = np.cross(edges[1], edges[4]) - tmin, tmax = project_min_max(simp, axis) - bmin, bmax = project_min_max(box_points, axis) - if bmax < tmin or bmin > tmax: - return 0 - axis = np.cross(edges[2], edges[5]) - tmin, tmax = project_min_max(simp, axis) - bmin, bmax = project_min_max(box_points, axis) - if bmax < tmin or bmin > tmax: - return 0 - return -1 mesh1 = discretize.TreeMesh([32, 32, 32]) mesh1.refine(refine_simplex) @@ -346,12 +181,13 @@ def test_tetra_errors(): def test_box_errors(): mesh = discretize.TreeMesh([64, 64]) - x0s = np.array([0.1, 0.2]) - x0s2d = np.array([[0.1, 0.1], [0.5, 0.5]]) - x1s2d = np.array([[0.2, 0.3], [0.8, 0.9]]) + rng = np.random.default_rng(32) + x0s = rng.random((3, 2)) + x0s2d = 0.5 * rng.random((2, 2)) + x1s2d = 0.5 * rng.random((2, 2)) + 0.5 - x0s3d = np.array([[0.1, 0.1, 0.1], [0.5, 0.5, 0.5]]) - x1s3d = np.array([[0.2, 0.3, 0.1], [0.8, 0.9, 0.75]]) + x0s3d = 0.5 * rng.random((2, 3)) + x1s3d = 0.5 * rng.random((2, 3)) + 0.5 # incorrect dimension on x0 with pytest.raises(ValueError): @@ -412,27 +248,27 @@ def test_refine_triang_prism(): ) h = 0.48 - simps = np.array([[0, 1, 2]]) - - n_ps = len(xyz) - simps1 = np.c_[simps[:, 0], simps[:, 1], simps[:, 2], simps[:, 0]] + [0, 0, 0, n_ps] - simps2 = np.c_[simps[:, 0], simps[:, 1], simps[:, 2], simps[:, 1]] + [ - n_ps, - n_ps, - n_ps, - 0, + all_points = np.concatenate([xyz, xyz + [0, 0, h]]) + # only need to define the unique edge tangents (minus axis-aligned ones) + edges = [ + [0, 1], + [0, 2], + [1, 2], ] - simps3 = np.c_[simps[:, 1], simps[:, 2], simps[:, 0], simps[:, 2]] + [ - 0, - 0, - n_ps, - n_ps, + + # and define unique face normals (absent any face parallel to an axis, + # or with normal defined by an axis and an edge above.) + faces = [ + [0, 1, 2], ] - simps = np.r_[simps1, simps2, simps3] - points = np.r_[xyz, xyz + [0, 0, h]] + def refine_vert(cell): + return assert_cell_intersects_geometric( + cell, all_points, edges=edges, faces=faces, as_refine=True + ) + mesh1 = discretize.TreeMesh([32, 32, 32]) - mesh1.refine_tetrahedron(points[simps], -1) + mesh1.refine(refine_vert) mesh2 = discretize.TreeMesh([32, 32, 32]) mesh2.refine_vertical_trianglular_prism(xyz, h, -1) @@ -620,3 +456,36 @@ def test_refine_surface_errors(): with pytest.raises(IndexError): mesh.refine_surface(points, 20) + + +def test_refine_plane2D(): + p0 = [2, 2] + normal = [-1, 1] + p1 = [-2, -2] + + mesh1 = discretize.TreeMesh([64, 64]) + mesh1.refine_plane(p0, normal, -1) + + mesh2 = discretize.TreeMesh([64, 64]) + mesh2.refine_line(np.stack([p0, p1]), -1) + + assert mesh1.equals(mesh2) + + +def test_refine_plane3D(): + p0 = [20, 20, 20] + normal = [-1, -1, 2] + # define 4 corner points (including p0) of a plane to create triangles + # to verify the refine functionallity + p1 = [20, -20, 0] + p2 = [-20, 20, 0] + p3 = [-20, -20, -20] + tris = np.stack([[p0, p1, p2], [p1, p2, p3]]) + + mesh1 = discretize.TreeMesh([64, 64, 64]) + mesh1.refine_plane(p0, normal, -1) + + mesh2 = discretize.TreeMesh([64, 64, 64]) + mesh2.refine_triangle(tris, -1) + + assert mesh1.equals(mesh2) From ee5e57a0fdc52580b577c355db139d9fb4ec5117 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Thu, 18 Apr 2024 14:47:38 -0600 Subject: [PATCH 09/97] add intersections tests and unify the calling for the refine and intersection functions. --- discretize/_extensions/geom.cpp | 2 +- discretize/_extensions/tree_ext.pyx | 68 +++++---- discretize/tests.py | 4 +- tests/tree/test_intersections.py | 225 ++++++++++++++++++++++++++++ 4 files changed, 263 insertions(+), 36 deletions(-) create mode 100644 tests/tree/test_intersections.py diff --git a/discretize/_extensions/geom.cpp b/discretize/_extensions/geom.cpp index 472e41cf8..da5a541ad 100644 --- a/discretize/_extensions/geom.cpp +++ b/discretize/_extensions/geom.cpp @@ -8,7 +8,7 @@ #define CROSS3D(e0, e1, out) \ out[0] = e0[1] * e1[2] - e0[2] * e1[1]; \ out[1] = e0[2] * e1[0] - e0[0] * e1[2]; \ - out[2] = e0[0] * e1[1] - e0[1] * e0[0]; + out[2] = e0[0] * e1[1] - e0[1] * e1[0]; // simple geometric objects for intersection tests with an aabb diff --git a/discretize/_extensions/tree_ext.pyx b/discretize/_extensions/tree_ext.pyx index 7fac3b43c..8d43b536f 100644 --- a/discretize/_extensions/tree_ext.pyx +++ b/discretize/_extensions/tree_ext.pyx @@ -1201,7 +1201,7 @@ cdef class _TreeMesh: Returns ------- - numpy.intp or (n_point) numpy.ndarray of numpy.intp + int or (n_point) numpy.ndarray of int The indexes of cells containing each point. """ @@ -1235,7 +1235,7 @@ cdef class _TreeMesh: Returns ------- - numpy.ndarray of numpy.intp + numpy.ndarray of int The indices of cells which overlap the ball. """ cdef double[:] a = self._require_ndarray_with_dim('center', center, dtype=np.float64) @@ -1253,7 +1253,7 @@ cdef class _TreeMesh: Returns ------- - numpy.ndarray of numpy.intp + numpy.ndarray of int Indices for cells that contain the a line defined by the two input points, ordered in the direction of the line. """ @@ -1275,7 +1275,7 @@ cdef class _TreeMesh: Returns ------- - numpy.ndarray of numpy.intp + numpy.ndarray of int The indices of cells which overlap the axis aligned bounding box. """ cdef double[:] a = self._require_ndarray_with_dim('x_min', x_min, dtype=np.float64) @@ -1294,7 +1294,7 @@ cdef class _TreeMesh: Returns ------- - numpy.ndarray of numpy.intp + numpy.ndarray of int The indices of cells which intersect the plane. """ cdef double[:] orig = self._require_ndarray_with_dim('origin', origin, dtype=np.float64) @@ -1303,32 +1303,33 @@ cdef class _TreeMesh: cdef geom.Plane plane = geom.Plane(self._dim, &orig[0], &norm[0]) return np.array(self.tree.find_cells_geom(plane)) - def get_cells_within_triangle(self, x0, x1, x2): + def get_cells_within_triangle(self, triangle): """Find the indices of cells that overlap a triangle. Parameters ---------- - x0, x1, x2 : (dim) array_like - The three points of the triangle. + triangle : (3, dim) array_like + The three points of the triangle. Returns ------- - numpy.ndarray of numpy.intp + numpy.ndarray of int The indices of cells which overlap the triangle. """ - cdef double[:] a = self._require_ndarray_with_dim('x0', x0, dtype=np.float64) - cdef double[:] b = self._require_ndarray_with_dim('x1', x1, dtype=np.float64) - cdef double[:] c = self._require_ndarray_with_dim('x2', x2, dtype=np.float64) + triangle = self._require_ndarray_with_dim('triangle', triangle, ndim=2, dtype=np.float64) + if triangle.shape[0] != 3: + raise ValueError(f"Triangle array must have three points, saw {triangle.shape[0]}") + cdef double[:, :] tri = triangle - cdef geom.Triangle triangle = geom.Triangle(self._dim, &a[0], &b[0], &c[0]) - return np.array(self.tree.find_cells_geom(triangle)) + cdef geom.Triangle poly = geom.Triangle(self._dim, &tri[0, 0], &tri[1, 0], &tri[2, 0]) + return np.array(self.tree.find_cells_geom(poly)) - def get_cells_within_vertical_trianglular_prism(self, x0, x1, x2, double h): + def get_cells_within_vertical_trianglular_prism(self, triangle, double h): """Find the indices of cells that overlap a vertical triangular prism. Parameters ---------- - x0, x1, x2 : (dim) array_like + triangle : (3, dim) array_like The three points of the triangle, assumes the top and bottom faces are parallel. h : float @@ -1336,38 +1337,41 @@ cdef class _TreeMesh: Returns ------- - numpy.ndarray of numpy.intp + numpy.ndarray of int The indices of cells which overlap the vertical triangular prism. """ if self.dim == 2: raise NotImplementedError("vertical_trianglular_prism only implemented in 3D.") - cdef double[:] a = self._require_ndarray_with_dim('x0', x0, dtype=np.float64) - cdef double[:] b = self._require_ndarray_with_dim('x1', x1, dtype=np.float64) - cdef double[:] c = self._require_ndarray_with_dim('x2', x2, dtype=np.float64) + triangle = self._require_ndarray_with_dim('triangle', triangle, ndim=2, dtype=np.float64) + if triangle.shape[0] != 3: + raise ValueError(f"Triangle array must have three points, saw {triangle.shape[0]}") + cdef double[:, :] tri = triangle - cdef geom.VerticalTriangularPrism vert = geom.VerticalTriangularPrism(self._dim, &a[0], &b[0], &c[0], h) + cdef geom.VerticalTriangularPrism vert = geom.VerticalTriangularPrism(self._dim, &tri[0, 0], &tri[1, 0], &tri[2, 0], h) return np.array(self.tree.find_cells_geom(vert)) - def get_cells_within_tetrahedron(self, x0, x1, x2, x3): + def get_cells_within_tetrahedron(self, tetra): """Find the indices of cells that overlap a tetrahedron. Parameters ---------- - x0, x1, x2, x3: (dim) array_like - The four points of the tetrahedron. + tetra : (dim+1, dim) array_like + The points of the tetrahedron(s). Returns ------- - numpy.ndarray of numpy.intp + numpy.ndarray of int The indices of cells which overlap the triangle. """ - cdef double[:] a = self._require_ndarray_with_dim('x0', x0, dtype=np.float64) - cdef double[:] b = self._require_ndarray_with_dim('x1', x1, dtype=np.float64) - cdef double[:] c = self._require_ndarray_with_dim('x2', x2, dtype=np.float64) - cdef double[:] d = self._require_ndarray_with_dim('x3', x3, dtype=np.float64) - - cdef geom.Tetrahedron tet = geom.Tetrahedron(self._dim, &a[0], &b[0], &c[0], &d[0]) - return np.array(self.tree.find_cells_geom(tet)) + if self.dim == 2: + return self.get_cells_within_triangle(tetra) + tetra = self._require_ndarray_with_dim('tetra', tetra, ndim=2, dtype=np.float64) + if tetra.shape[0] != 4: + raise ValueError(f"A tetrahedron is defined by 4 points in 3D, not {tetra.shape[0]}.") + cdef double[:, :] tet = tetra + + cdef geom.Tetrahedron poly = geom.Tetrahedron(self._dim, &tet[0, 0], &tet[1, 0], &tet[2, 0], &tet[3, 0]) + return np.array(self.tree.find_cells_geom(poly)) def _set_origin(self, origin): if not isinstance(origin, (list, tuple, np.ndarray)): diff --git a/discretize/tests.py b/discretize/tests.py index e06dbf7ad..399183927 100644 --- a/discretize/tests.py +++ b/discretize/tests.py @@ -879,9 +879,7 @@ def random(size, iscomplex): def assert_cell_intersects_geometric( cell, points, edges=None, faces=None, as_refine=False ): - """Asserst if a cell intersects a convex geometric object. - - Used to determine if a axis aligned cell with the properties + """Assert if a cell intersects a convex polygon. Parameters ---------- diff --git a/tests/tree/test_intersections.py b/tests/tree/test_intersections.py new file mode 100644 index 000000000..f6620f5c9 --- /dev/null +++ b/tests/tree/test_intersections.py @@ -0,0 +1,225 @@ +import discretize +import numpy as np +import pytest +from discretize.tests import assert_cell_intersects_geometric + +pytestmark = pytest.mark.parametrize("dim", [2, 3]) + + +def test_point_locator(dim): + point = [0.44] * dim + mesh = discretize.TreeMesh([32] * dim) + + mesh.insert_cells(point, -1) + + ind = mesh.get_containing_cells(point) + cell = mesh[ind] + assert_cell_intersects_geometric(cell, point) + + +def test_ball_locator(dim): + center = [0.44] * dim + radius = 0.12 + + mesh = discretize.TreeMesh([32] * dim) + mesh.refine_ball(center, radius, -1) + + cells = mesh.get_cells_within_ball(center, radius) + + r2 = radius * radius + + def ball_intersects(cell): + a = cell.origin + b = a + cell.h + dr = np.maximum(a, np.minimum(center, b)) - center + r2_test = np.sum(dr * dr) + return r2_test < r2 + + # ensure it found all of the cells by using a brute force search + test = [] + for cell in mesh: + if ball_intersects(cell): + test.append(cell.index) + np.testing.assert_array_equal(test, cells) + + +def test_line_locator(dim): + p0 = [0.1] * dim + p1 = [0.8] * dim + points = np.stack([p0, p1]) + + mesh = discretize.TreeMesh([32] * dim) + mesh.refine_line(points, -1) + + cell_inds = mesh.get_cells_along_line(p0, p1) + + # ensure it found all of the cells by using a brute force search + test = [] + for cell in mesh: + try: + assert_cell_intersects_geometric(cell, points, edges=[0, 1]) + test.append(cell.index) + except AssertionError: + pass + np.testing.assert_array_equal(test, cell_inds) + + +def test_box_locator(dim): + xmin = [0.2] * dim + xmax = [0.4] * dim + points = np.stack([xmin, xmax]) + + mesh = discretize.TreeMesh([32] * dim) + mesh.refine_box(xmin, xmax, -1) + + cell_inds = mesh.get_cells_within_aabb(xmin, xmax) + + # ensure it found all of the cells by using a brute force search + test = [] + for cell in mesh: + try: + assert_cell_intersects_geometric(cell, points) + test.append(cell.index) + except AssertionError: + pass + np.testing.assert_array_equal(test, cell_inds) + + +def test_plane_locator(dim): + if dim == 2: + p0 = [2, 2] + normal = [-1, 1] + p1 = [-2, -2] + points = np.stack([p0, p1]) + edges = [0, 1] + faces = None + elif dim == 3: + p0 = [20, 20, 20] + normal = [-1, -1, 2] + # define 4 corner points (including p0) of a plane to create triangles + # to verify the refine functionallity + p1 = [20, -20, 0] + p2 = [-20, 20, 0] + p3 = [-20, -20, -20] + points = np.stack([p0, p1, p2, p3]) + edges = [[0, 1], [0, 2]] + faces = [[0, 1, 2]] + + mesh = discretize.TreeMesh([16] * dim) + mesh.refine_plane(p0, normal, -1) + + cell_inds = mesh.get_cells_along_plane(p0, normal) + + # ensure it found all of the cells by using a brute force search + test = [] + for cell in mesh: + try: + assert_cell_intersects_geometric(cell, points, edges=edges, faces=faces) + test.append(cell.index) + except AssertionError: + pass + np.testing.assert_array_equal(test, cell_inds) + + +def test_triangle_locator(dim): + triangle = np.array([[0.14, 0.31, 0.23], [0.32, 0.96, 0.41], [0.23, 0.87, 0.72]])[ + :, :dim + ] + edges = [[0, 1], [0, 2], [1, 2]] + faces = [0, 1, 2] + + mesh = discretize.TreeMesh([32] * dim) + mesh.refine_triangle(triangle, -1) + + cell_inds = mesh.get_cells_within_triangle(triangle) + + # test it throws an error without giving enough points to triangle. + with pytest.raises(ValueError): + mesh.get_cells_within_triangle(triangle[:-1]) + + # ensure it found all of the cells by using a brute force search + test = [] + for cell in mesh: + try: + assert_cell_intersects_geometric(cell, triangle, edges=edges, faces=faces) + test.append(cell.index) + except AssertionError: + pass + np.testing.assert_array_equal(test, cell_inds) + + +def test_vert_tri_prism_locator(dim): + xyz = np.array( + [ + [0.41, 0.21, 0.11], + [0.21, 0.61, 0.22], + [0.71, 0.71, 0.31], + ] + ) + h = 0.48 + + points = np.concatenate([xyz, xyz + [0, 0, h]]) + # only need to define the unique edge tangents (minus axis-aligned ones) + edges = [ + [0, 1], + [0, 2], + [1, 2], + ] + faces = [ + [0, 1, 2], + ] + + mesh = discretize.TreeMesh([16] * dim) + if dim == 2: + with pytest.raises(NotImplementedError): + mesh.refine_vertical_trianglular_prism(xyz, h, -1) + else: + mesh.refine_vertical_trianglular_prism(xyz, h, -1) + + # test it throws an error on incorrect number of points for the triangle + with pytest.raises(ValueError): + mesh.get_cells_within_vertical_trianglular_prism(xyz[:-1], h) + + cell_inds = mesh.get_cells_within_vertical_trianglular_prism(xyz, h) + + # ensure it found all of the cells by using a brute force search + test = [] + for cell in mesh: + try: + assert_cell_intersects_geometric(cell, points, edges=edges, faces=faces) + test.append(cell.index) + except AssertionError: + pass + np.testing.assert_array_equal(test, cell_inds) + + +def test_tetrahedron_locator(dim): + simplex = np.array( + [[0.32, 0.21, 0.15], [0.82, 0.19, 0.34], [0.14, 0.82, 0.29], [0.32, 0.27, 0.83]] + )[: dim + 1, :dim] + edges = [[0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3]][: (dim - 1) * 3] + faces = [ + [0, 1, 2], + [0, 1, 3], + [0, 2, 3], + [1, 2, 3], + ][: 3 * dim - 5] + + mesh = discretize.TreeMesh([16] * dim) + mesh.refine_tetrahedron(simplex, -1) + + cell_inds = mesh.get_cells_within_tetrahedron(simplex) + + # test it throws an error without giving enough points to triangle. + with pytest.raises(ValueError): + mesh.get_cells_within_tetrahedron(simplex[:-1]) + + # ensure it found all of the cells by using a brute force search + test = [] + for cell in mesh: + try: + assert_cell_intersects_geometric(cell, simplex, edges=edges, faces=faces) + test.append(cell.index) + except AssertionError: + pass + np.testing.assert_array_equal(test, cell_inds) From 7360f31b3743b9758bc661125af851e391af9665 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Thu, 18 Apr 2024 16:08:58 -0600 Subject: [PATCH 10/97] update vtk writeout and matplotlib slicing --- discretize/_extensions/tree_ext.pyx | 19 ++- discretize/mixins/mesh_io.py | 40 +++-- discretize/mixins/mpl_mod.py | 29 ++-- discretize/mixins/vtk_mod.py | 24 ++- tests/tree/test_tree_balancing.py | 2 +- tests/tree/test_tree_io.py | 253 +++++++--------------------- 6 files changed, 124 insertions(+), 243 deletions(-) diff --git a/discretize/_extensions/tree_ext.pyx b/discretize/_extensions/tree_ext.pyx index 8d43b536f..6612773b7 100644 --- a/discretize/_extensions/tree_ext.pyx +++ b/discretize/_extensions/tree_ext.pyx @@ -5335,13 +5335,22 @@ cdef class _TreeMesh: counts[cell.level] += 1 return np.array(counts) - def _cell_levels_by_indexes(self, index): - index = np.require(np.atleast_1d(index), dtype=np.int64, requirements='C') - cdef np.int64_t[:] inds = index - cdef int_t n_cells = inds.shape[0] + def _cell_levels_by_indexes(self, index=None): + cdef np.int64_t[:] inds + cdef bool do_all = index is None + cdef int_t n_cells + if not do_all: + index = np.require(np.atleast_1d(index), dtype=np.int64, requirements='C') + inds = index + n_cells = inds.shape[0] + else: + n_cells = self.n_cells + cdef np.int64_t[:] levels = np.empty(n_cells, dtype=np.int64) + cdef int_t ii for i in range(n_cells): - levels[i] = self.tree.cells[inds[i]].level + ii = i if do_all else inds[i] + levels[i] = self.tree.cells[ii].level if n_cells == 1: return levels[0] else: diff --git a/discretize/mixins/mesh_io.py b/discretize/mixins/mesh_io.py index 8bfd39e61..b573b8e0c 100644 --- a/discretize/mixins/mesh_io.py +++ b/discretize/mixins/mesh_io.py @@ -133,7 +133,7 @@ def unpackdx(fid, nrows): return tensMsh @classmethod - def read_UBC(cls, file_name, directory=""): + def read_UBC(cls, file_name, directory=None): """Read 2D or 3D tensor mesh from UBC-GIF formatted file. Parameters @@ -149,6 +149,8 @@ def read_UBC(cls, file_name, directory=""): The tensor mesh """ # Check the expected mesh dimensions + if directory is None: + directory = "" fname = os.path.join(directory, file_name) # Read the file as line strings, remove lines with comment = ! msh = np.genfromtxt(fname, delimiter="\n", dtype=str, comments="!", max_rows=1) @@ -221,7 +223,7 @@ def _readModelUBC_3D(mesh, file_name): model = mkvc(model) return model - def read_model_UBC(mesh, file_name, directory=""): + def read_model_UBC(mesh, file_name, directory=None): """Read UBC-GIF formatted model file for 2D or 3D tensor mesh. Parameters @@ -236,6 +238,8 @@ def read_model_UBC(mesh, file_name, directory=""): (n_cells) numpy.ndarray The model defined on the mesh """ + if directory is None: + directory = "" fname = os.path.join(directory, file_name) if mesh.dim == 3: model = mesh._readModelUBC_3D(fname) @@ -245,7 +249,7 @@ def read_model_UBC(mesh, file_name, directory=""): raise Exception("mesh must be a Tensor Mesh 2D or 3D") return model - def write_model_UBC(mesh, file_name, model, directory=""): + def write_model_UBC(mesh, file_name, model, directory=None): """Write 2D or 3D tensor model to UBC-GIF formatted file. Parameters @@ -257,6 +261,8 @@ def write_model_UBC(mesh, file_name, model, directory=""): directory : str, optional output directory """ + if directory is None: + directory = "" fname = os.path.join(directory, file_name) if mesh.dim == 3: # Reshape model to a matrix @@ -372,7 +378,7 @@ def writeF(fx, outStr=""): f.write(outStr) f.close() - def write_UBC(mesh, file_name, models=None, directory="", comment_lines=""): + def write_UBC(mesh, file_name, models=None, directory=None, comment_lines=""): """Write 2D or 3D tensor mesh (and models) to UBC-GIF formatted file(s). Parameters @@ -387,6 +393,8 @@ def write_UBC(mesh, file_name, models=None, directory="", comment_lines=""): comment_lines : str, optional comment lines preceded are preceeded with '!' """ + if directory is None: + directory = "" fname = os.path.join(directory, file_name) if mesh.dim == 3: mesh._writeUBC_3DMesh(fname, comment_lines=comment_lines) @@ -444,7 +452,7 @@ class TreeMeshIO(object): """ @classmethod - def read_UBC(TreeMesh, file_name, directory=""): + def read_UBC(TreeMesh, file_name, directory=None): """Read 3D tree mesh (OcTree mesh) from UBC-GIF formatted file. Parameters @@ -459,6 +467,8 @@ def read_UBC(TreeMesh, file_name, directory=""): discretize.TreeMesh The tree mesh """ + if directory is None: + directory = "" fname = os.path.join(directory, file_name) fileLines = np.genfromtxt(fname, dtype=str, delimiter="\n", comments="!") nCunderMesh = np.array(fileLines[0].split("!")[0].split(), dtype=int) @@ -493,7 +503,7 @@ def read_UBC(TreeMesh, file_name, directory=""): mesh.__setstate__((indArr, levels)) return mesh - def read_model_UBC(mesh, file_name): + def read_model_UBC(mesh, file_name, directory=None): """Read UBC-GIF formatted file model file for 3D tree mesh (OcTree). Parameters @@ -502,7 +512,7 @@ def read_model_UBC(mesh, file_name): full path to the UBC-GIF formatted model file or just its name if directory is specified. It can also be a list of file_names. directory : str - directory where the UBC-GIF file lives (optional) + directory where the UBC-GIF file(s) lives (optional) Returns ------- @@ -510,10 +520,12 @@ def read_model_UBC(mesh, file_name): The model defined on the mesh. If **file_name** is a ``dict``, it is a dictionary of models indexed by the file names. """ + if directory is None: + directory = "" if type(file_name) is list: out = {} for f in file_name: - out[f] = mesh.read_model_UBC(f) + out[f] = mesh.read_model_UBC(f, directory=directory) return out modArr = np.loadtxt(file_name) @@ -527,7 +539,7 @@ def read_model_UBC(mesh, file_name): model = modArr[un_order].copy() # ensure a contiguous array return model - def write_UBC(mesh, file_name, models=None, directory=""): + def write_UBC(mesh, file_name, models=None, directory=None): """Write OcTree mesh (and models) to UBC-GIF formatted files. Parameters @@ -540,6 +552,8 @@ def write_UBC(mesh, file_name, models=None, directory=""): directory : str, optional output directory (optional) """ + if directory is None: + directory = "" uniform_hs = np.array([np.allclose(h, h[0]) for h in mesh.h]) if np.any(~uniform_hs): raise Exception("UBC form does not support variable cell widths") @@ -570,13 +584,9 @@ def write_UBC(mesh, file_name, models=None, directory=""): if not isinstance(models, dict): raise TypeError("models must be a dict") for key in models: - if not isinstance(key, str): - raise TypeError( - "The dict key must be a string representing the file name" - ) mesh.write_model_UBC(key, models[key], directory=directory) - def write_model_UBC(mesh, file_name, model, directory=""): + def write_model_UBC(mesh, file_name, model, directory=None): """Write 3D tree model (OcTree) to UBC-GIF formatted file. Parameters @@ -588,6 +598,8 @@ def write_model_UBC(mesh, file_name, model, directory=""): directory : str output directory (optional) """ + if directory is None: + directory = "" if type(file_name) is list: for f, m in zip(file_name, model): mesh.write_model_UBC(f, m) diff --git a/discretize/mixins/mpl_mod.py b/discretize/mixins/mpl_mod.py index e604c8d9c..5c831ad87 100644 --- a/discretize/mixins/mpl_mod.py +++ b/discretize/mixins/mpl_mod.py @@ -2142,29 +2142,26 @@ def __plot_slice_tree( if not isinstance(ind, (np.integer, int)): raise ValueError("ind must be an integer") - cc_tensor = [None, None, None] - for i in range(3): - cc_tensor[i] = np.cumsum(np.r_[self.origin[i], self.h[i]]) - cc_tensor[i] = (cc_tensor[i][1:] + cc_tensor[i][:-1]) * 0.5 + cc_tensor = [self.cell_centers_x, self.cell_centers_y, self.cell_centers_z] slice_loc = cc_tensor[normalInd][ind] + slice_origin = self.origin.copy() + slice_origin[normalInd] = slice_loc + normal = [0, 0, 0] + normal[normalInd] = 1 + # create a temporary TreeMesh with the slice through temp_mesh = discretize.TreeMesh(h2d, x2d) level_diff = self.max_level - temp_mesh.max_level - XS = [None, None, None] - XS[antiNormalInd[0]], XS[antiNormalInd[1]] = np.meshgrid( - cc_tensor[antiNormalInd[0]], cc_tensor[antiNormalInd[1]] - ) - XS[normalInd] = np.ones_like(XS[antiNormalInd[0]]) * slice_loc - loc_grid = np.c_[XS[0].reshape(-1), XS[1].reshape(-1), XS[2].reshape(-1)] - inds = np.unique(self.get_containing_cells(loc_grid)) - - grid2d = self.gridCC[inds][:, antiNormalInd] + # get list of cells which intersect the slicing plane + inds = self.get_cells_along_plane(slice_origin, normal) levels = self._cell_levels_by_indexes(inds) - level_diff + grid2d = self.cell_centers[inds][:, antiNormalInd] + temp_mesh.insert_cells(grid2d, levels) - tm_gridboost = np.empty((temp_mesh.nC, 3)) - tm_gridboost[:, antiNormalInd] = temp_mesh.gridCC + tm_gridboost = np.empty((temp_mesh.n_cells, 3)) + tm_gridboost[:, antiNormalInd] = temp_mesh.cell_centers tm_gridboost[:, normalInd] = slice_loc # interpolate values to self.gridCC if not "CC" or "CCv" @@ -2197,7 +2194,7 @@ def __plot_slice_tree( v = np.linalg.norm(v, axis=1) # interpolate values from self.gridCC to grid2d - ind_3d_to_2d = self._get_containing_cell_indexes(tm_gridboost) + ind_3d_to_2d = self.get_containing_cells(tm_gridboost) v2d = v[ind_3d_to_2d] out = temp_mesh.plot_image( diff --git a/discretize/mixins/vtk_mod.py b/discretize/mixins/vtk_mod.py index 71923ecdd..910dbccfa 100644 --- a/discretize/mixins/vtk_mod.py +++ b/discretize/mixins/vtk_mod.py @@ -205,24 +205,22 @@ def __tree_mesh_to_vtk(mesh, models=None): # Make the data parts for the vtu object # Points - ptsMat = np.vstack((mesh.gridN, mesh.gridhN)) + nodes = mesh.total_nodes # Adjust if result was 2D (voxels are pixels in 2D): - VTK_CELL_TYPE = _vtk.VTK_VOXEL - if ptsMat.shape[1] == 2: - # Add Z values of 0.0 if 2D - ptsMat = np.c_[ptsMat, np.zeros(ptsMat.shape[0])] - VTK_CELL_TYPE = _vtk.VTK_PIXEL - if ptsMat.shape[1] != 3: - raise RuntimeError("Points of the mesh are improperly defined.") + VTK_CELL_TYPE = _vtk.VTK_VOXEL if mesh.dim == 3 else _vtk.VTK_PIXEL + # Rotate the points to the cartesian system - ptsMat = np.dot(ptsMat, mesh.rotation_matrix) + nodes = np.dot(nodes, mesh.rotation_matrix) + if mesh.dim == 2: + nodes = np.pad(nodes, ((0, 0), (0, 1))) + # Grab the points vtkPts = _vtk.vtkPoints() - vtkPts.SetData(_nps.numpy_to_vtk(ptsMat, deep=True)) + vtkPts.SetData(_nps.numpy_to_vtk(nodes, deep=True)) + # Cells - cellArray = [c for c in mesh] - cellConn = np.array([cell.nodes for cell in cellArray]) + cellConn = mesh.cell_nodes cellsMat = np.concatenate( (np.ones((cellConn.shape[0], 1), dtype=int) * cellConn.shape[1], cellConn), axis=1, @@ -238,7 +236,7 @@ def __tree_mesh_to_vtk(mesh, models=None): output.SetPoints(vtkPts) output.SetCells(VTK_CELL_TYPE, cellsArr) # Add the level of refinement as a cell array - cell_levels = np.array([cell._level for cell in cellArray]) + cell_levels = mesh._cell_levels_by_indexes() refineLevelArr = _nps.numpy_to_vtk(cell_levels, deep=1) refineLevelArr.SetName("octreeLevel") output.GetCellData().AddArray(refineLevelArr) diff --git a/tests/tree/test_tree_balancing.py b/tests/tree/test_tree_balancing.py index 994d522c1..8721c2daf 100644 --- a/tests/tree/test_tree_balancing.py +++ b/tests/tree/test_tree_balancing.py @@ -150,7 +150,7 @@ def test_refine_tetra(): mesh1.refine_tetrahedron(simplex, -1) bad_nodes = check_for_diag_unbalance(mesh1) - assert len(bad_nodes) == 62 + assert len(bad_nodes) == 64 mesh2 = discretize.TreeMesh([32, 32, 32], diagonal_balance=True) mesh2.refine_tetrahedron(simplex, -1) diff --git a/tests/tree/test_tree_io.py b/tests/tree/test_tree_io.py index 91689b94c..e149edcb6 100644 --- a/tests/tree/test_tree_io.py +++ b/tests/tree/test_tree_io.py @@ -1,9 +1,8 @@ import numpy as np -import unittest -import os import discretize import pickle import json +import pytest try: import vtk # NOQA F401 @@ -13,206 +12,72 @@ has_vtk = False -class TestOcTreeMeshIO(unittest.TestCase): - def setUp(self): +@pytest.fixture(params=[2, 3]) +def mesh(request): + dim = request.param + if dim == 2: + mesh = discretize.TreeMesh([8, 8]) + mesh.refine(2, finalize=False) + mesh.refine_ball([0.25, 0.25], 0.25, 3) + else: h = np.ones(16) mesh = discretize.TreeMesh([h, 2 * h, 3 * h]) cell_points = np.array([[0.5, 0.5, 0.5], [0.5, 2.5, 0.5]]) cell_levels = np.array([4, 4]) mesh.insert_cells(cell_points, cell_levels) - self.mesh = mesh + return mesh - def test_UBC3Dfiles(self): - mesh = self.mesh - # Make a vector + +def test_UBCfiles(mesh, tmp_path): + # Make a vector + vec = np.arange(mesh.n_cells) + # Write and read + mesh_file = tmp_path / "temp.msh" + model_file = tmp_path / "arange.txt" + + mesh.write_UBC(mesh_file, {model_file: vec}) + meshUBC = discretize.TreeMesh.read_UBC(mesh_file) + vecUBC = meshUBC.read_model_UBC(model_file) + + assert mesh is not meshUBC + assert mesh.equals(meshUBC) + np.testing.assert_array_equal(vec, vecUBC) + + # Write it again with another IO function + mesh.write_model_UBC([model_file], [vec]) + vecUBC2 = mesh.read_model_UBC(model_file) + np.testing.assert_array_equal(vec, vecUBC2) + + +if has_vtk: + + def test_write_VTU_files(mesh, tmp_path): vec = np.arange(mesh.nC) - # Write and read - mesh.write_UBC("temp.msh", {"arange.txt": vec}) - meshUBC = discretize.TreeMesh.read_UBC("temp.msh") - vecUBC = meshUBC.read_model_UBC("arange.txt") - - self.assertEqual(mesh.nC, meshUBC.nC) - self.assertEqual(mesh.__str__(), meshUBC.__str__()) - self.assertTrue(np.allclose(mesh.gridCC, meshUBC.gridCC)) - self.assertTrue(np.allclose(vec, vecUBC)) - self.assertTrue(np.allclose(np.array(mesh.h), np.array(meshUBC.h))) - - # Write it again with another IO function - mesh.write_model_UBC(["arange.txt"], [vec]) - vecUBC2 = mesh.read_model_UBC("arange.txt") - self.assertTrue(np.allclose(vec, vecUBC2)) - - print("IO of UBC octree files is working") - os.remove("temp.msh") - os.remove("arange.txt") - - def test_UBC2Dfiles(self): - mesh0 = discretize.TreeMesh([8, 8]) - - def refine(cell): - xyz = cell.center - dist = ((xyz - 0.25) ** 2).sum() ** 0.5 - if dist < 0.25: - return 3 - return 2 - - mesh0.refine(refine) - - mod0 = np.arange(mesh0.nC) - mesh0.write_UBC("tmp.msh", {"arange.txt": mod0}) - mesh1 = discretize.TreeMesh.read_UBC("tmp.msh") - mod1 = mesh1.read_model_UBC("arange.txt") - - self.assertEqual(mesh0.nC, mesh1.nC) - self.assertEqual(mesh0.__str__(), mesh1.__str__()) - self.assertTrue(np.allclose(mesh0.gridCC, mesh1.gridCC)) - self.assertTrue(np.allclose(np.array(mesh0.h), np.array(mesh1.h))) - self.assertTrue(np.allclose(mod0, mod1)) - print("IO of UBC like 2D TreeMesh is working") - - if has_vtk: - - def test_VTUfiles(self): - mesh = self.mesh - vec = np.arange(mesh.nC) - mesh.write_vtk("temp.vtu", {"arange": vec}) - print("Writing of VTU files is working") - os.remove("temp.vtu") - - -class TestPickle(unittest.TestCase): - def test_pickle2D(self): - mesh0 = discretize.TreeMesh([8, 8]) - - def refine(cell): - xyz = cell.center - dist = ((xyz - 0.25) ** 2).sum() ** 0.5 - if dist < 0.25: - return 3 - return 2 - - mesh0.refine(refine) - - byte_string = pickle.dumps(mesh0) - mesh1 = pickle.loads(byte_string) - - self.assertEqual(mesh0.nC, mesh1.nC) - self.assertEqual(mesh0.__str__(), mesh1.__str__()) - self.assertTrue(np.allclose(mesh0.gridCC, mesh1.gridCC)) - self.assertTrue(np.allclose(np.array(mesh0.h), np.array(mesh1.h))) - print("Pickling of 2D TreeMesh is working") - - def test_pickle3D(self): - mesh0 = discretize.TreeMesh([8, 8, 8]) - - def refine(cell): - xyz = cell.center - dist = ((xyz - 0.25) ** 2).sum() ** 0.5 - if dist < 0.25: - return 3 - return 2 - - mesh0.refine(refine) - - byte_string = pickle.dumps(mesh0) - mesh1 = pickle.loads(byte_string) - - self.assertEqual(mesh0.nC, mesh1.nC) - self.assertEqual(mesh0.__str__(), mesh1.__str__()) - self.assertTrue(np.allclose(mesh0.gridCC, mesh1.gridCC)) - self.assertTrue(np.allclose(np.array(mesh0.h), np.array(mesh1.h))) - print("Pickling of 3D TreeMesh is working") - - -class TestSerialize(unittest.TestCase): - def test_dic_serialize2D(self): - mesh0 = discretize.TreeMesh([8, 8]) - - def refine(cell): - xyz = cell.center - dist = ((xyz - 0.25) ** 2).sum() ** 0.5 - if dist < 0.25: - return 3 - return 2 - - mesh0.refine(refine) - - mesh_dict = mesh0.serialize() - mesh1 = discretize.TreeMesh.deserialize(mesh_dict) - - self.assertEqual(mesh0.nC, mesh1.nC) - self.assertEqual(mesh0.__str__(), mesh1.__str__()) - self.assertTrue(np.allclose(mesh0.gridCC, mesh1.gridCC)) - self.assertTrue(np.allclose(np.array(mesh0.h), np.array(mesh1.h))) - print("dic serialize 2D is working") - - def test_save_load_json2D(self): - mesh0 = discretize.TreeMesh([8, 8]) - - def refine(cell): - xyz = cell.center - dist = ((xyz - 0.25) ** 2).sum() ** 0.5 - if dist < 0.25: - return 3 - return 2 - - mesh0.refine(refine) - - mesh0.save("tree.json") - with open("tree.json", "r") as outfile: - jsondict = json.load(outfile) - mesh1 = discretize.TreeMesh.deserialize(jsondict) - - self.assertEqual(mesh0.nC, mesh1.nC) - self.assertEqual(mesh0.__str__(), mesh1.__str__()) - self.assertTrue(np.allclose(mesh0.gridCC, mesh1.gridCC)) - self.assertTrue(np.allclose(np.array(mesh0.h), np.array(mesh1.h))) - print("json serialize 2D is working") - - def test_dic_serialize3D(self): - mesh0 = discretize.TreeMesh([8, 8, 8]) - - def refine(cell): - xyz = cell.center - dist = ((xyz - 0.25) ** 2).sum() ** 0.5 - if dist < 0.25: - return 3 - return 2 + mesh_file = tmp_path / "temp.vtu" + mesh.write_vtk(mesh_file, {"arange": vec}) + + +def test_pickle(mesh): + byte_string = pickle.dumps(mesh) + mesh_pickle = pickle.loads(byte_string) + + assert mesh is not mesh_pickle + assert mesh.equals(mesh_pickle) + - mesh0.refine(refine) - - mesh_dict = mesh0.serialize() - mesh1 = discretize.TreeMesh.deserialize(mesh_dict) - - self.assertEqual(mesh0.nC, mesh1.nC) - self.assertEqual(mesh0.__str__(), mesh1.__str__()) - self.assertTrue(np.allclose(mesh0.gridCC, mesh1.gridCC)) - self.assertTrue(np.allclose(np.array(mesh0.h), np.array(mesh1.h))) - print("dic serialize 3D is working") +def test_dic_serialize(mesh): + mesh_dict = mesh.serialize() + mesh2 = discretize.TreeMesh.deserialize(mesh_dict) + assert mesh is not mesh2 + assert mesh.equals(mesh2) - def test_save_load_json3D(self): - mesh0 = discretize.TreeMesh([8, 8, 8]) - - def refine(cell): - xyz = cell.center - dist = ((xyz - 0.25) ** 2).sum() ** 0.5 - if dist < 0.25: - return 3 - return 2 - - mesh0.refine(refine) - - mesh0.save("tree.json") - with open("tree.json", "r") as outfile: - jsondict = json.load(outfile) - mesh1 = discretize.TreeMesh.deserialize(jsondict) - - self.assertEqual(mesh0.nC, mesh1.nC) - self.assertEqual(mesh0.__str__(), mesh1.__str__()) - self.assertTrue(np.allclose(mesh0.gridCC, mesh1.gridCC)) - self.assertTrue(np.allclose(np.array(mesh0.h), np.array(mesh1.h))) - print("json serialize 3D is working") +def test_json_serialize(mesh, tmp_path): + json_file = tmp_path / "tree.json" -if __name__ == "__main__": - unittest.main() + mesh.save(json_file) + with open(json_file, "r") as outfile: + jsondict = json.load(outfile) + mesh2 = discretize.TreeMesh.deserialize(jsondict) + assert mesh is not mesh2 + assert mesh.equals(mesh2) From 9e3c19e06c1cbaebf4c55f5d4349a5775deedff5 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Thu, 18 Apr 2024 16:35:45 -0600 Subject: [PATCH 11/97] unify refine_line and get_cells_along_line arguments. --- discretize/_extensions/tree_ext.pyx | 11 +++++++---- tests/tree/test_intersections.py | 13 +++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/discretize/_extensions/tree_ext.pyx b/discretize/_extensions/tree_ext.pyx index 6612773b7..97dcee5a5 100644 --- a/discretize/_extensions/tree_ext.pyx +++ b/discretize/_extensions/tree_ext.pyx @@ -1243,12 +1243,12 @@ cdef class _TreeMesh: cdef geom.Ball ball = geom.Ball(self._dim, &a[0], radius) return np.array(self.tree.find_cells_geom(ball)) - def get_cells_along_line(self, x0, x1): + def get_cells_along_line(self, segment): """Find the cells along a line segment. Parameters ---------- - x0,x1 : (dim) array_like + segment : (2, dim) array-like Beginning and ending point of the line segment. Returns @@ -1257,8 +1257,11 @@ cdef class _TreeMesh: Indices for cells that contain the a line defined by the two input points, ordered in the direction of the line. """ - cdef double[:] start = self._require_ndarray_with_dim('x0', x0, dtype=np.float64) - cdef double[:] end = self._require_ndarray_with_dim('x1', x1, dtype=np.float64) + segment = self._require_ndarray_with_dim('segment', segment, ndim=2, dtype=np.float64) + if segment.shape[0] != 2: + raise ValueError(f"A line segment has two points, not {segment.shape[0]}") + cdef double[:] start = segment[0] + cdef double[:] end = segment[1] cdef geom.Line line = geom.Line(self._dim, &start[0], &end[0]) return np.array(self.tree.find_cells_geom(line)) diff --git a/tests/tree/test_intersections.py b/tests/tree/test_intersections.py index f6620f5c9..ef4742caf 100644 --- a/tests/tree/test_intersections.py +++ b/tests/tree/test_intersections.py @@ -44,20 +44,21 @@ def ball_intersects(cell): def test_line_locator(dim): - p0 = [0.1] * dim - p1 = [0.8] * dim - points = np.stack([p0, p1]) + segment = np.array([[0.12, 0.33, 0.19], [0.32, 0.93, 0.68]])[:, :dim] mesh = discretize.TreeMesh([32] * dim) - mesh.refine_line(points, -1) + mesh.refine_line(segment, -1) - cell_inds = mesh.get_cells_along_line(p0, p1) + with pytest.raises(ValueError): + mesh.get_cells_along_line(segment[:-1]) + + cell_inds = mesh.get_cells_along_line(segment) # ensure it found all of the cells by using a brute force search test = [] for cell in mesh: try: - assert_cell_intersects_geometric(cell, points, edges=[0, 1]) + assert_cell_intersects_geometric(cell, segment, edges=[0, 1]) test.append(cell.index) except AssertionError: pass From edc06bcd569c4f3034478805a80ca3df6d9d09d7 Mon Sep 17 00:00:00 2001 From: Omid Bagherpur Date: Mon, 29 Apr 2024 11:50:52 -0400 Subject: [PATCH 12/97] Update 1_mesh_overview.py --- tutorials/mesh_generation/1_mesh_overview.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/mesh_generation/1_mesh_overview.py b/tutorials/mesh_generation/1_mesh_overview.py index c6d918533..f7bb20428 100644 --- a/tutorials/mesh_generation/1_mesh_overview.py +++ b/tutorials/mesh_generation/1_mesh_overview.py @@ -21,7 +21,7 @@ # # - **Tree meshes** (:class:`discretize.TreeMesh`): also referred to as QuadTree or OcTree meshes # -# - **Curvilinear meshes** (:class:`discretize.CurviMesh`): also referred to as logically rectangular non-orthogonal +# - **Curvilinear meshes** (:class:`discretize.CurvilinearMesh`): also referred to as logically rectangular non-orthogonal # # Examples for each mesh type are shown below. # From 51627180bdaa2c0d89b5384f037eae50248b9921 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Thu, 9 May 2024 16:10:35 -0600 Subject: [PATCH 13/97] add support for previous calling convention --- discretize/_extensions/tree_ext.pyx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/discretize/_extensions/tree_ext.pyx b/discretize/_extensions/tree_ext.pyx index 97dcee5a5..f724190e7 100644 --- a/discretize/_extensions/tree_ext.pyx +++ b/discretize/_extensions/tree_ext.pyx @@ -1243,7 +1243,7 @@ cdef class _TreeMesh: cdef geom.Ball ball = geom.Ball(self._dim, &a[0], radius) return np.array(self.tree.find_cells_geom(ball)) - def get_cells_along_line(self, segment): + def get_cells_along_line(self, *args): """Find the cells along a line segment. Parameters @@ -1257,6 +1257,12 @@ cdef class _TreeMesh: Indices for cells that contain the a line defined by the two input points, ordered in the direction of the line. """ + if len(args) == 1: + segment = args[0] + elif len(args) == 2: + segment = np.stack(args) + else: + raise TypeError('get_cells_along_line() takes 1 or 2 positional arguments') segment = self._require_ndarray_with_dim('segment', segment, ndim=2, dtype=np.float64) if segment.shape[0] != 2: raise ValueError(f"A line segment has two points, not {segment.shape[0]}") From b719523a48bae590be69eb573910aa193e218bc5 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Thu, 9 May 2024 16:25:44 -0600 Subject: [PATCH 14/97] pass through rng for OrderTest class --- discretize/tests.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/discretize/tests.py b/discretize/tests.py index f5ebbe2b5..057f1a79a 100644 --- a/discretize/tests.py +++ b/discretize/tests.py @@ -305,7 +305,7 @@ class for the given operator. Within the test class, the user sets the parameter meshDimension = 3 rng = np.random.default_rng() - def setupMesh(self, nC): + def setupMesh(self, nC, rng=None): """Generate mesh and set as current mesh for testing. Parameters @@ -318,7 +318,9 @@ def setupMesh(self, nC): Float Maximum cell width for the mesh """ - mesh, max_h = setup_mesh(self._meshType, nC, self.meshDimension, rng=self.rng) + if rng is None: + rng = self.rng + mesh, max_h = setup_mesh(self._meshType, nC, self.meshDimension, rng=rng) self.M = mesh return max_h @@ -363,7 +365,7 @@ def orderTest(self, rng=None): ) def test_func(n_cells): - max_h = self.setupMesh(n_cells) + max_h = self.setupMesh(n_cells, rng=rng) err = self.getError() return err, max_h From 15acf3e314d5af3567a2ab64a2d8c2a1cfa96411 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Thu, 9 May 2024 16:30:21 -0600 Subject: [PATCH 15/97] add bit of docs --- discretize/tests.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/discretize/tests.py b/discretize/tests.py index 057f1a79a..378b30e6f 100644 --- a/discretize/tests.py +++ b/discretize/tests.py @@ -99,6 +99,9 @@ def setup_mesh(mesh_type, nC, nDim, rng=None): number of base mesh cells and must be a power of 2. nDim : int The dimension of the mesh. Must be 1, 2 or 3. + rng : numpy.random.Generator, int, optional + The random number generator to use for the adjoint test, if an integer or None + it used to seed a new `numpy.random.default_rng`. Only used if mesh_type is 'random' Returns ------- @@ -237,6 +240,9 @@ class for the given operator. Within the test class, the user sets the parameter for the meshes used in the convergence test; e.g. [4, 8, 16, 32] meshDimension : int Mesh dimension. Must be 1, 2 or 3 + rng : numpy.random.Generator, int, optional + The random number generator to use for the adjoint test, if an integer or None + it used to seed a new `numpy.random.default_rng`. Only used if mesh_type is 'random' Notes ----- @@ -312,6 +318,9 @@ def setupMesh(self, nC, rng=None): ---------- nC : int Number of cells along each axis. + rng : numpy.random.Generator, int, optional + The random number generator to use for the adjoint test, if an integer or None + it used to seed a new `numpy.random.default_rng`. Only used if mesh_type is 'random' Returns ------- From 4a37b431cf6124bf7e7c84cd80fc7dec90cafcec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dieter=20Werthm=C3=BCller?= Date: Mon, 29 Jul 2024 11:53:52 +0200 Subject: [PATCH 16/97] Fix slicer re #363 --- discretize/mixins/mpl_mod.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/discretize/mixins/mpl_mod.py b/discretize/mixins/mpl_mod.py index 9a9c614d0..c331a6d4a 100644 --- a/discretize/mixins/mpl_mod.py +++ b/discretize/mixins/mpl_mod.py @@ -2568,6 +2568,11 @@ def __init__( clim[0] *= 0.99 clim[1] *= 1.01 + # Edge-case. If the entire vector is zero, `clim` is still + # [0, 0] after the above check, hence `clim[0]==clim[1]`. + if clim[0] == clim[1]: + clim = [-0.1, 0.1] + # ensure vmin/vmax of the norm is consistent with clim if "norm" in self.pc_props: self.pc_props["norm"].vmin = clim[0] From b71f252330a680d9b8864ce59ad8d3a6179715b5 Mon Sep 17 00:00:00 2001 From: Santiago Soler Date: Fri, 30 Aug 2024 11:10:32 -0700 Subject: [PATCH 17/97] Add `TensorMesh.cell_bounds` property Add a new `cell_bounds` property to `TensorMesh` that returns the bounds of each cell in the mesh. This implements a faster way to obtain the bounds of each cell in the mesh than trying to access them through `TensorCell.bounds`. --- discretize/tensor_mesh.py | 32 ++++++++++++++++++++++++++++++++ tests/base/test_tensor.py | 14 +++++++++++--- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/discretize/tensor_mesh.py b/discretize/tensor_mesh.py index 842dd0106..5862cf93d 100644 --- a/discretize/tensor_mesh.py +++ b/discretize/tensor_mesh.py @@ -590,6 +590,38 @@ def face_boundary_indices(self): indzu = self.gridFz[:, 2] == max(self.gridFz[:, 2]) return indxd, indxu, indyd, indyu, indzd, indzu + @property + def cell_bounds(self): + """The bounds of each cell. + + Return a 2D array with the coordinates that define the bounds of each + cell in the mesh. Each row of the array contains the bounds for + a particular cell in the following order: ``x1``, ``x2``, ``y1``, + ``y2``, ``z1``, ``z2``, where ``x1 < x2``, ``y1 < y2`` and ``z1 < z2``. + """ + # Define indexing for meshgrid and order for ravel + indexing = "ij" + order = "F" + + if self.dim == 1: + x = self.nodes_x + bounds = (x[:-1], x[1:]) + elif self.dim == 2: + x, y = self.nodes_x, self.nodes_y + x1, y1 = np.meshgrid(x[:-1], y[:-1], indexing=indexing) + x2, y2 = np.meshgrid(x[1:], y[1:], indexing=indexing) + bounds = (x1, x2, y1, y2) + else: + x, y, z = self.nodes_x, self.nodes_y, self.nodes_z + x1, y1, z1 = np.meshgrid(x[:-1], y[:-1], z[:-1], indexing=indexing) + x2, y2, z2 = np.meshgrid(x[1:], y[1:], z[1:], indexing=indexing) + bounds = (x1, x2, y1, y2, z1, z2) + + cell_bounds = np.hstack( + tuple(v.ravel(order=order)[:, np.newaxis] for v in bounds) + ) + return cell_bounds + @property def cell_nodes(self): """The index of all nodes for each cell. diff --git a/tests/base/test_tensor.py b/tests/base/test_tensor.py index 9349cafa7..3d5e0039d 100644 --- a/tests/base/test_tensor.py +++ b/tests/base/test_tensor.py @@ -251,9 +251,9 @@ def test_serialization(self): self.assertTrue(np.all(self.mesh2.gridCC == mesh.gridCC)) -class TestTensorMeshCellNodes: +class TestTensorMeshProperties: """ - Test TensorMesh.cell_nodes + Test some of the properties in TensorMesh """ @pytest.fixture(params=[1, 2, 3], ids=["dims-1", "dims-2", "dims-3"]) @@ -261,17 +261,25 @@ def mesh(self, request): """Sample TensorMesh.""" if request.param == 1: h = [10] + origin = (-35.5,) elif request.param == 2: h = [10, 15] + origin = (-35.5, 105.3) else: h = [10, 15, 20] - return discretize.TensorMesh(h) + origin = (-35.5, 105.3, -27.3) + return discretize.TensorMesh(h, origin=origin) def test_cell_nodes(self, mesh): """Test TensorMesh.cell_nodes.""" expected_cell_nodes = np.array([cell.nodes for cell in mesh]) np.testing.assert_allclose(mesh.cell_nodes, expected_cell_nodes) + def test_cell_bounds(self, mesh): + """Test TensorMesh.cell_bounds.""" + expected_cell_bounds = np.array([cell.bounds for cell in mesh]) + np.testing.assert_allclose(mesh.cell_bounds, expected_cell_bounds) + class TestPoissonEqn(discretize.tests.OrderTest): name = "Poisson Equation" From e67746338be4374f817a0e488861af2ba7917482 Mon Sep 17 00:00:00 2001 From: Santiago Soler Date: Fri, 30 Aug 2024 14:31:36 -0700 Subject: [PATCH 18/97] Add `TreeCell.bounds` method Add a new `TreeCell.bounds` method that return the bounds of a cell in a `TreeMesh`. --- discretize/_extensions/tree_ext.pyx | 33 +++++++++++++++++++++++++ tests/tree/test_tree.py | 37 +++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/discretize/_extensions/tree_ext.pyx b/discretize/_extensions/tree_ext.pyx index c8750ae7e..4a2fd8723 100644 --- a/discretize/_extensions/tree_ext.pyx +++ b/discretize/_extensions/tree_ext.pyx @@ -217,6 +217,39 @@ cdef class TreeCell: """ return self._cell.index + @property + @cython.boundscheck(False) + def bounds(self): + """ + Bounds of the cell. + + Coordinates that define the bounds of the cell. Bounds are returned in + the following order: ``x1``, ``x2``, ``y1``, ``y2``, ``z1``, ``z2``. + + Returns + ------- + bounds : (2 * dim) array + Array with the cell bounds. + """ + bounds = np.empty(self._dim * 2, dtype=np.float64) + if(self._dim == 1): + bounds[0] = self._x0 + bounds[1] = self._x0 + self._wx + elif(self._dim == 2): + bounds[0] = self._x0 + bounds[1] = self._x0 + self._wx + bounds[2] = self._y0 + bounds[3] = self._y0 + self._wy + else: + bounds[0] = self._x0 + bounds[1] = self._x0 + self._wx + bounds[2] = self._y0 + bounds[3] = self._y0 + self._wy + bounds[4] = self._z0 + bounds[5] = self._z0 + self._wz + return bounds + + @property def neighbors(self): """Indices for this cell's neighbors within its parent tree mesh. diff --git a/tests/tree/test_tree.py b/tests/tree/test_tree.py index d63310a3d..e3b8dc095 100644 --- a/tests/tree/test_tree.py +++ b/tests/tree/test_tree.py @@ -378,6 +378,43 @@ def test_total_nodes(self, sample_mesh): ) +class TestTreeCellBounds: + """Test ``TreeCell.bounds`` method""" + + @pytest.fixture(params=["2D", "3D"]) + def mesh(self, request): + """Return a sample TreeMesh""" + nc = 16 + if request.param == "2D": + h = [nc, nc] + origin = (-32.4, 245.4) + mesh = discretize.TreeMesh(h, origin) + p1 = (origin[0] + 0.4, origin[1] + 0.4) + p2 = (origin[0] + 0.6, origin[1] + 0.6) + mesh.refine_box(p1, p2, levels=5, finalize=True) + else: + h = [nc, nc, nc] + origin = (-32.4, 245.4, 192.3) + mesh = discretize.TreeMesh(h, origin) + p1 = (origin[0] + 0.4, origin[1] + 0.4, origin[2] + 0.7) + p2 = (origin[0] + 0.6, origin[1] + 0.6, origin[2] + 0.9) + mesh.refine_box(p1, p2, levels=5, finalize=True) + return mesh + + def test_bounds(self, mesh): + """Test bounds method of one of the cells in the mesh.""" + cell = mesh[16] + nodes = mesh.nodes[cell.nodes] + x1, x2 = nodes[0][0], nodes[-1][0] + y1, y2 = nodes[0][1], nodes[-1][1] + if mesh.dim == 2: + expected_bounds = np.array([x1, x2, y1, y2]) + else: + z1, z2 = nodes[0][2], nodes[-1][2] + expected_bounds = np.array([x1, x2, y1, y2, z1, z2]) + np.testing.assert_allclose(cell.bounds, expected_bounds) + + class Test2DInterpolation(unittest.TestCase): def setUp(self): def topo(x): From 5e932b11bf6ca743e663bc2d5f36e1e81066102e Mon Sep 17 00:00:00 2001 From: Santiago Soler Date: Fri, 30 Aug 2024 15:36:14 -0700 Subject: [PATCH 19/97] Minor improvement to array lookup --- discretize/_extensions/tree_ext.pyx | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/discretize/_extensions/tree_ext.pyx b/discretize/_extensions/tree_ext.pyx index 4a2fd8723..60b199b81 100644 --- a/discretize/_extensions/tree_ext.pyx +++ b/discretize/_extensions/tree_ext.pyx @@ -232,21 +232,22 @@ cdef class TreeCell: Array with the cell bounds. """ bounds = np.empty(self._dim * 2, dtype=np.float64) + cdef np.float64_t[:] bounds_view = bounds if(self._dim == 1): - bounds[0] = self._x0 - bounds[1] = self._x0 + self._wx + bounds_view[0] = self._x0 + bounds_view[1] = self._x0 + self._wx elif(self._dim == 2): - bounds[0] = self._x0 - bounds[1] = self._x0 + self._wx - bounds[2] = self._y0 - bounds[3] = self._y0 + self._wy + bounds_view[0] = self._x0 + bounds_view[1] = self._x0 + self._wx + bounds_view[2] = self._y0 + bounds_view[3] = self._y0 + self._wy else: - bounds[0] = self._x0 - bounds[1] = self._x0 + self._wx - bounds[2] = self._y0 - bounds[3] = self._y0 + self._wy - bounds[4] = self._z0 - bounds[5] = self._z0 + self._wz + bounds_view[0] = self._x0 + bounds_view[1] = self._x0 + self._wx + bounds_view[2] = self._y0 + bounds_view[3] = self._y0 + self._wy + bounds_view[4] = self._z0 + bounds_view[5] = self._z0 + self._wz return bounds From cf307cc736f1ed6706fed99ca11497328f30a2ed Mon Sep 17 00:00:00 2001 From: Santiago Soler Date: Fri, 30 Aug 2024 16:03:20 -0700 Subject: [PATCH 20/97] Start drafting `cell_bounds` method Start drafting a `cell_bounds` method for the `TreeMesh`. --- discretize/_extensions/tree.h | 1 + discretize/_extensions/tree.pxd | 1 + discretize/_extensions/tree_ext.pyx | 10 ++++++++++ 3 files changed, 12 insertions(+) diff --git a/discretize/_extensions/tree.h b/discretize/_extensions/tree.h index f3b0bef93..a65e5059a 100644 --- a/discretize/_extensions/tree.h +++ b/discretize/_extensions/tree.h @@ -110,6 +110,7 @@ class Cell{ long long int index; // non root parents will have a -1 value double location[3]; double volume; + double bounds[6]; Cell(); Cell(Node *pts[4], int_t ndim, int_t maxlevel);//, function func); diff --git a/discretize/_extensions/tree.pxd b/discretize/_extensions/tree.pxd index bb2fc35ce..ae6d1e9b1 100644 --- a/discretize/_extensions/tree.pxd +++ b/discretize/_extensions/tree.pxd @@ -58,6 +58,7 @@ cdef extern from "tree.h": Face *faces[6] int_t location_ind[3] double location[3] + double bounds[6] int_t key, level, max_level long long int index double volume diff --git a/discretize/_extensions/tree_ext.pyx b/discretize/_extensions/tree_ext.pyx index 60b199b81..291ea8b3d 100644 --- a/discretize/_extensions/tree_ext.pyx +++ b/discretize/_extensions/tree_ext.pyx @@ -1137,6 +1137,16 @@ cdef class _TreeMesh: """ return self._finalized + @property + def cell_bounds(self): + cell_bounds = np.empty((self.n_nodes, 2 * self._dim), dtype=np.float64) + cdef np.float64_t[:, :] cell_bounds_view = cell_bounds + + for cell in self.tree.cells: + cell_bounds_view[cell.index, :] = cell.bounds + + return cell_bounds + def number(self): """Number the cells, nodes, faces, and edges of the TreeMesh.""" self.tree.number() From ab74637b3ca004f7590a89e5f0c88cc6ce9ed692 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Sun, 1 Sep 2024 00:19:58 -0600 Subject: [PATCH 21/97] update tests to use random generator simplex passes tree tests passing cyl passing boundaries passing, also removes pymatsolver imports base passes --- discretize/base/base_mesh.py | 24 +++---- discretize/mixins/mpl_mod.py | 4 +- discretize/tests.py | 47 +++++++------ discretize/tree_mesh.py | 3 +- discretize/utils/interpolation_utils.py | 7 +- discretize/utils/matrix_utils.py | 20 +++--- discretize/utils/mesh_utils.py | 3 +- examples/plot_cahn_hilliard.py | 4 +- examples/plot_dc_resistivity.py | 8 +-- tests/base/test_coordutils.py | 19 +++--- tests/base/test_interpolation.py | 32 ++++----- tests/base/test_operators.py | 28 ++------ tests/base/test_tensor.py | 4 +- tests/base/test_tensor_innerproduct.py | 9 --- tests/base/test_tensor_innerproduct_derivs.py | 24 +++---- tests/base/test_tensor_omf.py | 3 +- tests/base/test_tests.py | 9 ++- tests/base/test_utils.py | 45 +++++++------ tests/base/test_view.py | 4 +- tests/base/test_volume_avg.py | 66 +++++++++++-------- tests/boundaries/test_boundary_integrals.py | 6 +- tests/boundaries/test_boundary_maxwell.py | 8 +-- tests/boundaries/test_boundary_poisson.py | 36 ++++------ tests/boundaries/test_errors.py | 9 ++- tests/boundaries/test_tensor_boundary.py | 11 ++-- .../test_tensor_boundary_poisson.py | 11 ++-- tests/cyl/test_cyl.py | 10 +-- tests/cyl/test_cyl3D.py | 2 - tests/cyl/test_cylOperators.py | 4 +- tests/cyl/test_cyl_innerproducts.py | 14 ++-- tests/cyl/test_cyl_operators.py | 6 +- tests/simplex/test_inner_products.py | 14 ++-- tests/simplex/test_utils.py | 20 +++--- tests/tree/test_refine.py | 23 +++---- tests/tree/test_tree.py | 44 +++++++------ tests/tree/test_tree_innerproduct_derivs.py | 32 ++++----- tests/tree/test_tree_interpolation.py | 2 - tests/tree/test_tree_operators.py | 23 +++---- tests/tree/test_tree_plotting.py | 26 ++++---- tests/tree/test_tree_utils.py | 1 - tutorials/inner_products/1_basic.py | 10 +-- .../inner_products/2_physical_properties.py | 14 ++-- tutorials/inner_products/4_advanced.py | 6 +- tutorials/pde/1_poisson.py | 5 +- tutorials/pde/2_advection_diffusion.py | 6 +- 45 files changed, 350 insertions(+), 356 deletions(-) diff --git a/discretize/base/base_mesh.py b/discretize/base/base_mesh.py index 617c270c6..90fb2e331 100644 --- a/discretize/base/base_mesh.py +++ b/discretize/base/base_mesh.py @@ -2419,15 +2419,15 @@ def get_face_inner_product_deriv( >>> import numpy as np >>> import matplotlib as mpl >>> mpl.rcParams.update({'font.size': 14}) - >>> np.random.seed(45) + >>> rng = np.random.default_rng(45) >>> mesh = TensorMesh([[(1, 4)], [(1, 4)]]) Define a model, and a random vector to multiply the derivative with, then we grab the respective derivative function and calculate the sparse matrix, - >>> m = np.random.rand(mesh.nC) # physical property parameters - >>> u = np.random.rand(mesh.nF) # vector of shape (n_faces) + >>> m = rng.random(mesh.nC) # physical property parameters + >>> u = rng.random(mesh.nF) # vector of shape (n_faces) >>> Mf = mesh.get_face_inner_product(m) >>> F = mesh.get_face_inner_product_deriv(m) # Function handle >>> dFdm_u = F(u) @@ -2458,8 +2458,8 @@ def get_face_inner_product_deriv( function handle :math:`\mathbf{F}(\mathbf{u})` and plot the evaluation of this function on a spy plot. - >>> m = np.random.rand(mesh.nC, 3) # anisotropic physical property parameters - >>> u = np.random.rand(mesh.nF) # vector of shape (n_faces) + >>> m = rng.random((mesh.nC, 3)) # anisotropic physical property parameters + >>> u = rng.random(mesh.nF) # vector of shape (n_faces) >>> Mf = mesh.get_face_inner_product(m) >>> F = mesh.get_face_inner_product_deriv(m) # Function handle >>> dFdm_u = F(u) @@ -2602,14 +2602,14 @@ def get_edge_inner_product_deriv( >>> import numpy as np >>> import matplotlib as mpl >>> mpl.rcParams.update({'font.size': 14}) - >>> np.random.seed(45) + >>> rng = np.random.default_rng(45) >>> mesh = TensorMesh([[(1, 4)], [(1, 4)]]) Next we create a random isotropic model vector, and a random vector to multiply the derivative with (for illustration purposes). - >>> m = np.random.rand(mesh.nC) # physical property parameters - >>> u = np.random.rand(mesh.nF) # vector of shape (n_edges) + >>> m = rng.random(mesh.nC) # physical property parameters + >>> u = rng.random(mesh.nF) # vector of shape (n_edges) >>> Me = mesh.get_edge_inner_product(m) >>> F = mesh.get_edge_inner_product_deriv(m) # Function handle >>> dFdm_u = F(u) @@ -2640,8 +2640,8 @@ def get_edge_inner_product_deriv( function handle :math:`\mathbf{F}(\mathbf{u})` and plot the evaluation of this function on a spy plot. - >>> m = np.random.rand(mesh.nC, 3) # physical property parameters - >>> u = np.random.rand(mesh.nF) # vector of shape (n_edges) + >>> m = rng.random((mesh.nC, 3)) # physical property parameters + >>> u = rng.random(mesh.nF) # vector of shape (n_edges) >>> Me = mesh.get_edge_inner_product(m) >>> F = mesh.get_edge_inner_product_deriv(m) # Function handle >>> dFdm_u = F(u) @@ -4128,9 +4128,9 @@ def get_interpolation_matrix( >>> from discretize import TensorMesh >>> import numpy as np >>> import matplotlib.pyplot as plt - >>> np.random.seed(14) + >>> rng = np.random.default_rng(14) - >>> locs = np.random.rand(50)*0.8+0.1 + >>> locs = rng.random(50)*0.8+0.1 >>> dense = np.linspace(0, 1, 200) >>> fun = lambda x: np.cos(2*np.pi*x) diff --git a/discretize/mixins/mpl_mod.py b/discretize/mixins/mpl_mod.py index 331439020..be417829a 100644 --- a/discretize/mixins/mpl_mod.py +++ b/discretize/mixins/mpl_mod.py @@ -470,7 +470,7 @@ def plot_slice( >>> from matplotlib import pyplot as plt >>> import discretize - >>> from pymatsolver import Solver + >>> from scipy.sparse.linalg import spsolve >>> hx = [(5, 2, -1.3), (2, 4), (5, 2, 1.3)] >>> hy = [(2, 2, -1.3), (2, 6), (2, 2, 1.3)] >>> hz = [(2, 2, -1.3), (2, 6), (2, 2, 1.3)] @@ -482,7 +482,7 @@ def plot_slice( >>> q[[4, 4], [4, 4], [2, 6]]=[-1, 1] >>> q = discretize.utils.mkvc(q) >>> A = M.face_divergence * M.cell_gradient - >>> b = Solver(A) * (q) + >>> b = spsolve(A, q) and finaly, plot the vector values of the result, which are defined on faces diff --git a/discretize/tests.py b/discretize/tests.py index adb62a4a4..4848371e4 100644 --- a/discretize/tests.py +++ b/discretize/tests.py @@ -100,8 +100,9 @@ def setup_mesh(mesh_type, nC, nDim, rng=None): nDim : int The dimension of the mesh. Must be 1, 2 or 3. rng : numpy.random.Generator, int, optional - The random number generator to use for the adjoint test, if an integer or None - it used to seed a new `numpy.random.default_rng`. Only used if mesh_type is 'random' + If ``random`` is in `mesh_type`, this is the random number generator to use for + creating a random mesh. If an integer or None it is used to seed a new + `numpy.random.default_rng`. Returns ------- @@ -309,9 +310,9 @@ class for the given operator. Within the test class, the user sets the parameter meshTypes = ["uniformTensorMesh"] _meshType = meshTypes[0] meshDimension = 3 - rng = np.random.default_rng() + rng = None - def setupMesh(self, nC, rng=None): + def setupMesh(self, nC): """Generate mesh and set as current mesh for testing. Parameters @@ -327,9 +328,7 @@ def setupMesh(self, nC, rng=None): Float Maximum cell width for the mesh """ - if rng is None: - rng = self.rng - mesh, max_h = setup_mesh(self._meshType, nC, self.meshDimension, rng=rng) + mesh, max_h = setup_mesh(self._meshType, nC, self.meshDimension, rng=self.rng) self.M = mesh return max_h @@ -373,8 +372,11 @@ def orderTest(self, rng=None): "expectedOrders must have the same length as the meshTypes" ) + if rng is not None: + self.rng = rng + def test_func(n_cells): - max_h = self.setupMesh(n_cells, rng=rng) + max_h = self.setupMesh(n_cells) err = self.getError() return err, max_h @@ -574,8 +576,8 @@ def check_derivative( Parameters ---------- - fctn : function - Function handle + fctn : callable + The function to test. x0 : numpy.ndarray Point at which to check derivative num : int, optional @@ -584,7 +586,7 @@ def check_derivative( If *True*, plot the convergence of the approximation of the derivative dx : numpy.ndarray, optional Step direction. By default, this parameter is set to *None* and a random - step direction is chosen. + step direction is chosen using `rng`. expectedOrder : int, optional The expected order of convergence for the numerical derivative tolerance : float, optional @@ -595,8 +597,9 @@ def check_derivative( An axis object for the convergence plot if *plotIt = True*. Otherwise, the function will create a new axis. rng : numpy.random.Generator, int, optional - The random number generator to use for the adjoint test, if an integer or None - it used to seed a new `numpy.random.default_rng`. + If `dx` is ``None``, This is the random number generator to use for + generating a step direction. If an integer or None, it is used to seed + a new `numpy.random.default_rng`. Returns ------- @@ -608,10 +611,11 @@ def check_derivative( >>> from discretize import tests, utils >>> import numpy as np >>> import matplotlib.pyplot as plt + >>> rng = np.random.default_rng(786412) >>> def simplePass(x): ... return np.sin(x), utils.sdiag(np.cos(x)) - >>> passed = tests.check_derivative(simplePass, np.random.randn(5)) + >>> passed = tests.check_derivative(simplePass, rng.standard_normal(5), rng=rng) ==================== check_derivative ==================== iter h |ft-f0| |ft-f0-h*J0*dx| Order --------------------------------------------------------- @@ -628,7 +632,6 @@ def check_derivative( __tracebackhide__ = True # matplotlib is a soft dependencies for discretize, # lazy-loaded to decrease load time of discretize. - rng = np.random.default_rng(rng) try: import matplotlib @@ -646,6 +649,7 @@ def check_derivative( x0 = mkvc(x0) if dx is None: + rng = np.random.default_rng(rng) dx = rng.standard_normal(len(x0)) h = np.logspace(-1, -num, num) @@ -712,11 +716,14 @@ def _plot_it(axes, passed): # Thus it has no higher order derivatives. pass else: - test = np.mean(order1) > tolerance * expectedOrder + order_mean = np.mean(order1) + expected = tolerance * expectedOrder + test = order_mean > expected if not test: raise AssertionError( - f"\n Order mean {np.mean(order1)} is not greater than" - f" {tolerance} of the expected order {expectedOrder}." + f"\n Order mean {order_mean} is not greater than" + f" {expected} = tolerance: {tolerance} " + f"* expected order: {expectedOrder}." ) print("{0!s} PASS! {1!s}".format("=" * 25, "=" * 25)) print(_happiness_rng.choice(happiness) + "\n") @@ -844,8 +851,8 @@ def assert_isadjoint( returned as boolean and a message is printed. rng : numpy.random.Generator, int, optional - The random number generator to use for the adjoint test, if an integer or None - it used to seed a new `numpy.random.default_rng`. + The random number generator to use for the adjoint test. If an integer or None + it is used to seed a new `numpy.random.default_rng`. Returns ------- diff --git a/discretize/tree_mesh.py b/discretize/tree_mesh.py index fe2db06cf..0478ad79e 100644 --- a/discretize/tree_mesh.py +++ b/discretize/tree_mesh.py @@ -436,7 +436,8 @@ def refine_bounding_box( >>> import matplotlib.pyplot as plt >>> import matplotlib.patches as patches >>> mesh = discretize.TreeMesh([32, 32]) - >>> points = np.random.rand(20, 2) * 0.25 + 3/8 + >>> rng = np.random.default_rng(852) + >>> points = rng.random((20, 2)) * 0.25 + 3/8 Now we want to refine to the maximum level, with no padding the in `x` direction and `2` cells in `y`. At the second highest level we want 2 padding diff --git a/discretize/utils/interpolation_utils.py b/discretize/utils/interpolation_utils.py index 9c6ba159a..2c3c866e2 100644 --- a/discretize/utils/interpolation_utils.py +++ b/discretize/utils/interpolation_utils.py @@ -84,11 +84,11 @@ def interpolation_matrix(locs, x, y=None, z=None): >>> from discretize import TensorMesh >>> import numpy as np >>> import matplotlib.pyplot as plt - >>> np.random.seed(14) + >>> rng = np.random.default_rng(14) Create an interpolation matrix - >>> locs = np.random.rand(50)*0.8+0.1 + >>> locs = rng.random(50)*0.8+0.1 >>> x = np.linspace(0, 1, 7) >>> dense = np.linspace(0, 1, 200) >>> fun = lambda x: np.cos(2*np.pi*x) @@ -217,6 +217,7 @@ def volume_average(mesh_in, mesh_out, values=None, output=None): >>> import numpy as np >>> from discretize import TensorMesh + >>> rng = np.random.default_rng(853) >>> h1 = np.ones(32) >>> h2 = np.ones(16)*2 >>> mesh_in = TensorMesh([h1, h1]) @@ -226,7 +227,7 @@ def volume_average(mesh_in, mesh_out, values=None, output=None): interpolate it to the output mesh. >>> from discretize.utils import volume_average - >>> model1 = np.random.rand(mesh_in.nC) + >>> model1 = rng.random(mesh_in.nC) >>> model2 = volume_average(mesh_in, mesh_out, model1) Because these two meshes' cells are perfectly aligned, but the output mesh diff --git a/discretize/utils/matrix_utils.py b/discretize/utils/matrix_utils.py index 70fd73ef9..75d922723 100644 --- a/discretize/utils/matrix_utils.py +++ b/discretize/utils/matrix_utils.py @@ -35,8 +35,9 @@ def mkvc(x, n_dims=1, **kwargs): >>> from discretize.utils import mkvc >>> import numpy as np + >>> rng = np.random.default_rng(856) - >>> a = np.random.rand(3, 2) + >>> a = rng.random(3, 2) >>> a array([[0.33534155, 0.25334363], [0.07147884, 0.81080958], @@ -570,7 +571,8 @@ def get_subarray(A, ind): >>> from discretize.utils import get_subarray >>> import numpy as np - >>> A = np.random.rand(3, 3) + >>> rng = np.random.default_rng(421) + >>> A = rng.random((3, 3)) >>> A array([[1.07969034e-04, 9.78613931e-01, 6.62123429e-01], [8.80722877e-01, 7.61035691e-01, 7.42546796e-01], @@ -1167,6 +1169,7 @@ def make_property_tensor(mesh, tensor): >>> import numpy as np >>> import matplotlib.pyplot as plt >>> import matplotlib as mpl + >>> rng = np.random.default_rng(421) Define a 2D tensor mesh @@ -1176,9 +1179,9 @@ def make_property_tensor(mesh, tensor): Define a physical property for all cases (2D) >>> sigma_scalar = 4. - >>> sigma_isotropic = np.random.randint(1, 10, mesh.nC) - >>> sigma_anisotropic = np.random.randint(1, 10, (mesh.nC, 2)) - >>> sigma_tensor = np.random.randint(1, 10, (mesh.nC, 3)) + >>> sigma_isotropic = rng.integers(1, 10, mesh.nC) + >>> sigma_anisotropic = rng.integers(1, 10, (mesh.nC, 2)) + >>> sigma_tensor = rng.integers(1, 10, (mesh.nC, 3)) Construct the property tensor in each case @@ -1319,6 +1322,7 @@ def inverse_property_tensor(mesh, tensor, return_matrix=False, **kwargs): >>> import numpy as np >>> import matplotlib.pyplot as plt >>> import matplotlib as mpl + >>> rng = np.random.default_rng(421) Define a 2D tensor mesh @@ -1328,9 +1332,9 @@ def inverse_property_tensor(mesh, tensor, return_matrix=False, **kwargs): Define a physical property for all cases (2D) >>> sigma_scalar = 4. - >>> sigma_isotropic = np.random.randint(1, 10, mesh.nC) - >>> sigma_anisotropic = np.random.randint(1, 10, (mesh.nC, 2)) - >>> sigma_tensor = np.random.randint(1, 10, (mesh.nC, 3)) + >>> sigma_isotropic = rng.integers(1, 10, mesh.nC) + >>> sigma_anisotropic = rng.integers(1, 10, (mesh.nC, 2)) + >>> sigma_tensor = rng.integers(1, 10, (mesh.nC, 3)) Construct the property tensor in each case diff --git a/discretize/utils/mesh_utils.py b/discretize/utils/mesh_utils.py index d5a7b500d..22ea4fac7 100644 --- a/discretize/utils/mesh_utils.py +++ b/discretize/utils/mesh_utils.py @@ -428,8 +428,9 @@ def mesh_builder_xyz( >>> import discretize >>> import matplotlib.pyplot as plt >>> import numpy as np + >>> rng = np.random.default_rng(87142) - >>> xy_loc = np.random.randn(8,2) + >>> xy_loc = rng.standard_normal((8,2)) >>> mesh = discretize.utils.mesh_builder_xyz( ... xy_loc, [0.1, 0.1], depth_core=0.5, ... padding_distance=[[1,2], [1,0]], diff --git a/examples/plot_cahn_hilliard.py b/examples/plot_cahn_hilliard.py index 6ccfd2c2f..2784df405 100644 --- a/examples/plot_cahn_hilliard.py +++ b/examples/plot_cahn_hilliard.py @@ -44,9 +44,9 @@ """ import discretize -from pymatsolver import Solver import numpy as np import matplotlib.pyplot as plt +from scipy.sparse.linalg import spsolve def run(plotIt=True, n=60): @@ -92,7 +92,7 @@ def run(plotIt=True, n=60): MAT = dt * A * d2fdphi2 - I - dt * A * L rhs = (dt * A * d2fdphi2 - I) * phi - dt * A * dfdphi - phi = Solver(MAT) * rhs + phi = spsolve(MAT, rhs) if elapsed > capture[jj]: PHIS += [(elapsed, phi.copy())] diff --git a/examples/plot_dc_resistivity.py b/examples/plot_dc_resistivity.py index d8ee4abda..4f1cd8eac 100644 --- a/examples/plot_dc_resistivity.py +++ b/examples/plot_dc_resistivity.py @@ -6,9 +6,9 @@ """ import discretize -from pymatsolver import SolverLU import numpy as np import matplotlib.pyplot as plt +from scipy.solver.linalg import spsolve def run(plotIt=True): @@ -37,12 +37,10 @@ def DCfun(mesh, pts): # Step3: Solve DC problem (LU solver) AtM, rhstM = DCfun(tM, pts) - AinvtM = SolverLU(AtM) - phitM = AinvtM * rhstM + phitM = spsolve(AtM, rhstM) ArM, rhsrM = DCfun(rM, pts) - AinvrM = SolverLU(ArM) - phirM = AinvrM * rhsrM + phirM = spsolve(ArM, rhsrM) if not plotIt: return diff --git a/tests/base/test_coordutils.py b/tests/base/test_coordutils.py index 97d565df3..efa15ba37 100644 --- a/tests/base/test_coordutils.py +++ b/tests/base/test_coordutils.py @@ -4,15 +4,16 @@ tol = 1e-15 +rng = np.random.default_rng(523) + class coorutilsTest(unittest.TestCase): def test_rotation_matrix_from_normals(self): - np.random.seed(0) - v0 = np.random.rand(3) + v0 = rng.random(3) v0 *= 1.0 / np.linalg.norm(v0) np.random.seed(5) - v1 = np.random.rand(3) + v1 = rng.random(3) v1 *= 1.0 / np.linalg.norm(v1) Rf = utils.rotation_matrix_from_normals(v0, v1) @@ -22,12 +23,11 @@ def test_rotation_matrix_from_normals(self): self.assertTrue(np.linalg.norm(utils.mkvc(Ri.dot(v1) - v0)) < tol) def test_rotate_points_from_normals(self): - np.random.seed(10) - v0 = np.random.rand(3) + v0 = rng.random(3) v0 *= 1.0 / np.linalg.norm(v0) np.random.seed(15) - v1 = np.random.rand(3) + v1 = rng.random(3) v1 *= 1.0 / np.linalg.norm(v1) v2 = utils.mkvc(utils.rotate_points_from_normals(utils.mkvc(v0, 2).T, v0, v1)) @@ -35,16 +35,15 @@ def test_rotate_points_from_normals(self): self.assertTrue(np.linalg.norm(v2 - v1) < tol) def test_rotateMatrixFromNormals(self): - np.random.seed(20) - n0 = np.random.rand(3) + n0 = rng.random(3) n0 *= 1.0 / np.linalg.norm(n0) np.random.seed(25) - n1 = np.random.rand(3) + n1 = rng.random(3) n1 *= 1.0 / np.linalg.norm(n1) np.random.seed(30) - scale = np.random.rand(100, 1) + scale = rng.random((100, 1)) XYZ0 = scale * n0 XYZ1 = scale * n1 diff --git a/tests/base/test_interpolation.py b/tests/base/test_interpolation.py index cea6abd02..bd46a2c93 100644 --- a/tests/base/test_interpolation.py +++ b/tests/base/test_interpolation.py @@ -3,8 +3,6 @@ import discretize -gen = np.random.default_rng(182) - MESHTYPES = ["uniformTensorMesh", "randomTensorMesh"] TOLERANCES = [0.9, 0.5, 0.5] call1 = lambda fun, xyz: fun(xyz) @@ -43,13 +41,13 @@ class TestInterpolation1D(discretize.tests.OrderTest): - LOCS = np.random.rand(50) * 0.6 + 0.2 name = "Interpolation 1D" meshTypes = MESHTYPES tolerance = TOLERANCES meshDimension = 1 meshSizes = [8, 16, 32, 64, 128] - rng = gen + rng = np.random.default_rng(5136) + LOCS = rng.random(50) * 0.6 + 0.2 def getError(self): funX = lambda x: np.cos(2 * np.pi * x) @@ -96,12 +94,12 @@ def test_outliers(self): class TestInterpolation2d(discretize.tests.OrderTest): name = "Interpolation 2D" - LOCS = np.random.rand(50, 2) * 0.6 + 0.2 meshTypes = MESHTYPES tolerance = TOLERANCES meshDimension = 2 meshSizes = [8, 16, 32, 64] - rng = gen + rng = np.random.default_rng(2457) + LOCS = rng.random((50, 2)) * 0.6 + 0.2 def getError(self): funX = lambda x, y: np.cos(2 * np.pi * y) @@ -180,14 +178,12 @@ def test_exceptions(self): class TestInterpolationSymCyl(discretize.tests.OrderTest): name = "Interpolation Symmetric 3D" - LOCS = np.c_[ - np.random.rand(4) * 0.6 + 0.2, np.zeros(4), np.random.rand(4) * 0.6 + 0.2 - ] meshTypes = ["uniform_symmetric_CylMesh"] # MESHTYPES + tolerance = 0.6 meshDimension = 3 meshSizes = [32, 64, 128, 256] - rng = gen + rng = np.random.default_rng(81756234) + LOCS = np.c_[rng.random(4) * 0.6 + 0.2, np.zeros(4), rng.random(4) * 0.6 + 0.2] def getError(self): funX = lambda x, y: np.cos(2 * np.pi * y) @@ -247,15 +243,15 @@ def test_orderEy(self): class TestInterpolationCyl(discretize.tests.OrderTest): name = "Interpolation Cylindrical 3D" - LOCS = np.c_[ - np.random.rand(20) * 0.6 + 0.2, - 2 * np.pi * (np.random.rand(20) * 0.6 + 0.2), - np.random.rand(20) * 0.6 + 0.2, - ] meshTypes = ["uniformCylMesh", "randomCylMesh"] # MESHTYPES + meshDimension = 3 meshSizes = [8, 16, 32, 64] - rng = gen + rng = np.random.default_rng(876234) + LOCS = np.c_[ + rng.random(20) * 0.6 + 0.2, + 2 * np.pi * (rng.random(20) * 0.6 + 0.2), + rng.random(20) * 0.6 + 0.2, + ] def getError(self): func = lambda x, y, z: np.cos(2 * np.pi * x) + np.cos(y) + np.cos(2 * np.pi * z) @@ -318,13 +314,13 @@ def test_orderEz(self): class TestInterpolation3D(discretize.tests.OrderTest): + rng = np.random.default_rng(234) name = "Interpolation" - LOCS = np.random.rand(50, 3) * 0.6 + 0.2 + LOCS = rng.random((50, 3)) * 0.6 + 0.2 meshTypes = MESHTYPES tolerance = TOLERANCES meshDimension = 3 meshSizes = [8, 16, 32, 64] - rng = gen def getError(self): funX = lambda x, y, z: np.cos(2 * np.pi * y) diff --git a/tests/base/test_operators.py b/tests/base/test_operators.py index 1679c2a25..b5fc225a3 100644 --- a/tests/base/test_operators.py +++ b/tests/base/test_operators.py @@ -45,7 +45,6 @@ class TestCurl(discretize.tests.OrderTest): name = "Curl" meshTypes = MESHTYPES - rng = gen def getError(self): # fun: i (cos(y)) + j (cos(z)) + k (cos(x)) @@ -83,7 +82,6 @@ class TestCurl2D(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 2 meshSizes = [8, 16, 32, 64] - rng = gen def getError(self): # Test function @@ -110,7 +108,6 @@ def test_order(self): # 1 # because of the averaging involved in the ghost point. u_b = (u_n + u_g)/2 # ) # meshSizes = [8, 16, 32, 64] -# rng = gen # # def getError(self): # # Test function @@ -138,7 +135,6 @@ class TestCellGrad2D_Dirichlet(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 2 meshSizes = [8, 16, 32, 64] - rng = gen def getError(self): # Test function @@ -167,7 +163,6 @@ class TestCellGrad3D_Dirichlet(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 3 meshSizes = [8, 16, 32] - rng = gen def getError(self): # Test function @@ -219,7 +214,6 @@ class TestCellGrad2D_Neumann(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 2 meshSizes = [8, 16, 32, 64] - rng = gen def getError(self): # Test function @@ -248,7 +242,6 @@ class TestCellGrad3D_Neumann(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 3 meshSizes = [8, 16, 32] - rng = gen def getError(self): # Test function @@ -299,7 +292,6 @@ class TestFaceDiv3D(discretize.tests.OrderTest): name = "Face Divergence 3D" meshTypes = MESHTYPES meshSizes = [8, 16, 32, 64] - rng = gen def getError(self): # Test function @@ -335,7 +327,6 @@ class TestFaceDiv2D(discretize.tests.OrderTest): meshTypes = MESHTYPES meshDimension = 2 meshSizes = [8, 16, 32, 64] - rng = gen def getError(self): # Test function @@ -360,7 +351,6 @@ def test_order(self): class TestNodalGrad(discretize.tests.OrderTest): name = "Nodal Gradient" meshTypes = MESHTYPES - rng = gen def getError(self): # Test function @@ -388,7 +378,6 @@ class TestNodalGrad2D(discretize.tests.OrderTest): name = "Nodal Gradient 2D" meshTypes = MESHTYPES meshDimension = 2 - rng = gen def getError(self): # Test function @@ -416,7 +405,6 @@ class TestAveraging1D(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 1 meshSizes = [16, 32, 64] - rng = gen def getError(self): num = self.getAve(self.M) * self.getHere(self.M) @@ -518,8 +506,8 @@ def test_orderE2FV(self): class TestAverating2DSimple(unittest.TestCase): def setUp(self): - hx = np.random.rand(10) - hy = np.random.rand(10) + hx = gen.random(10) + hy = gen.random(10) self.mesh = discretize.TensorMesh([hx, hy]) def test_constantEdges(self): @@ -538,7 +526,6 @@ class TestAveraging2D(discretize.tests.OrderTest): meshTypes = MESHTYPES meshDimension = 2 meshSizes = [16, 32, 64] - rng = gen def getError(self): num = self.getAve(self.M) * self.getHere(self.M) @@ -637,9 +624,9 @@ def test_orderCC2FV(self): class TestAverating3DSimple(unittest.TestCase): def setUp(self): - hx = np.random.rand(10) - hy = np.random.rand(10) - hz = np.random.rand(10) + hx = gen.random(10) + hy = gen.random(10) + hz = gen.random(10) self.mesh = discretize.TensorMesh([hx, hy, hz]) def test_constantEdges(self): @@ -658,7 +645,6 @@ class TestAveraging3D(discretize.tests.OrderTest): meshTypes = MESHTYPES meshDimension = 3 meshSizes = [16, 32, 64] - rng = gen def getError(self): num = self.getAve(self.M) * self.getHere(self.M) @@ -792,7 +778,7 @@ def test_DivCurl(self): mesh, _ = discretize.tests.setup_mesh( meshType, self.meshSize, self.meshDimension ) - v = np.random.rand(mesh.nE) + v = gen.random(mesh.nE) divcurlv = mesh.face_divergence * (mesh.edge_curl * v) rel_err = np.linalg.norm(divcurlv) / np.linalg.norm(v) passed = rel_err < self.tol @@ -806,7 +792,7 @@ def test_CurlGrad(self): mesh, _ = discretize.tests.setup_mesh( meshType, self.meshSize, self.meshDimension ) - v = np.random.rand(mesh.nN) + v = gen.random(mesh.nN) curlgradv = mesh.edge_curl * (mesh.nodal_gradient * v) rel_err = np.linalg.norm(curlgradv) / np.linalg.norm(v) passed = rel_err < self.tol diff --git a/tests/base/test_tensor.py b/tests/base/test_tensor.py index cf42f226f..5d880f5bc 100644 --- a/tests/base/test_tensor.py +++ b/tests/base/test_tensor.py @@ -2,7 +2,7 @@ import numpy as np import unittest import discretize -from pymatsolver import Solver +from scipy.sparse.linalg import spsolve TOL = 1e-10 @@ -299,7 +299,7 @@ def getError(self): err = np.linalg.norm((sA - sN), np.inf) else: fA = fun(self.M.gridCC) - fN = Solver(D * G) * (sol(self.M.gridCC)) + fN = spsolve(D * G, sol(self.M.gridCC)) err = np.linalg.norm((fA - fN), np.inf) return err diff --git a/tests/base/test_tensor_innerproduct.py b/tests/base/test_tensor_innerproduct.py index 8ca27543d..7f2ebd9db 100644 --- a/tests/base/test_tensor_innerproduct.py +++ b/tests/base/test_tensor_innerproduct.py @@ -4,8 +4,6 @@ from discretize import TensorMesh from discretize.utils import sdinv -gen = np.random.default_rng(50) - class TestInnerProducts(discretize.tests.OrderTest): """Integrate an function over a unit cube domain @@ -14,7 +12,6 @@ class TestInnerProducts(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh", "uniformCurv", "rotateCurv"] meshDimension = 3 meshSizes = [16, 32] - rng = gen def getError(self): call = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2]) @@ -174,7 +171,6 @@ class TestInnerProductsFaceProperties3D(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 3 meshSizes = [16, 32] - rng = gen def getError(self): call = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2]) @@ -321,7 +317,6 @@ class TestInnerProductsEdgeProperties3D(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 3 meshSizes = [16, 32] - rng = gen def getError(self): call = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2]) @@ -410,7 +405,6 @@ class TestInnerProducts2D(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh", "uniformCurv", "rotateCurv"] meshDimension = 2 meshSizes = [4, 8, 16, 32, 64, 128] - rng = gen def getError(self): z = 5 # Because 5 is just such a great number. @@ -556,7 +550,6 @@ class TestInnerProductsFaceProperties2D(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 2 meshSizes = [8, 16, 32] - rng = gen def getError(self): call = lambda fun, xy: fun(xy[:, 0], xy[:, 1]) @@ -647,7 +640,6 @@ class TestInnerProductsEdgeProperties2D(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 2 meshSizes = [8, 16, 32] - rng = gen def getError(self): call = lambda fun, xy: fun(xy[:, 0], xy[:, 1]) @@ -710,7 +702,6 @@ class TestInnerProducts1D(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh"] meshDimension = 1 meshSizes = [4, 8, 16, 32, 64, 128] - rng = gen def getError(self): y = 12 # Because 12 is just such a great number. diff --git a/tests/base/test_tensor_innerproduct_derivs.py b/tests/base/test_tensor_innerproduct_derivs.py index 0273192c6..68d1cf2b8 100644 --- a/tests/base/test_tensor_innerproduct_derivs.py +++ b/tests/base/test_tensor_innerproduct_derivs.py @@ -3,7 +3,7 @@ import discretize from discretize import TensorMesh -np.random.seed(50) +rng = np.random.default_rng(542) class TestInnerProductsDerivsTensor(unittest.TestCase): @@ -19,8 +19,8 @@ def doTestFace( mesh.number(balance=False) elif meshType == "Tensor": mesh = discretize.TensorMesh(h) - v = np.random.rand(mesh.nF) - sig = np.random.rand(1) if rep == 0 else np.random.rand(mesh.nC * rep) + v = rng.random(mesh.nF) + sig = rng.random(1) if rep == 0 else rng.random(mesh.nC * rep) def fun(sig): M = mesh.get_face_inner_product( @@ -56,8 +56,8 @@ def doTestEdge( mesh.number(balance=False) elif meshType == "Tensor": mesh = discretize.TensorMesh(h) - v = np.random.rand(mesh.nE) - sig = np.random.rand(1) if rep == 0 else np.random.rand(mesh.nC * rep) + v = rng.random(mesh.nE) + sig = rng.random(1) if rep == 0 else rng.random(mesh.nC * rep) def fun(sig): M = mesh.get_edge_inner_product( @@ -341,8 +341,8 @@ def doTestFace(self, h, rep, meshType, invert_model=False, invert_matrix=False): mesh.number(balance=False) elif meshType == "Tensor": mesh = discretize.TensorMesh(h) - v = np.random.rand(mesh.nF) - sig = np.random.rand(1) if rep == 0 else np.random.rand(mesh.nF * rep) + v = rng.random(mesh.nF) + sig = rng.random(1) if rep == 0 else rng.random(mesh.nF * rep) def fun(sig): M = mesh.get_face_inner_product_surface( @@ -372,8 +372,8 @@ def doTestEdge(self, h, rep, meshType, invert_model=False, invert_matrix=False): mesh.number(balance=False) elif meshType == "Tensor": mesh = discretize.TensorMesh(h) - v = np.random.rand(mesh.nE) - sig = np.random.rand(1) if rep == 0 else np.random.rand(mesh.nF * rep) + v = rng.random(mesh.nE) + sig = rng.random(1) if rep == 0 else rng.random(mesh.nF * rep) def fun(sig): M = mesh.get_edge_inner_product_surface( @@ -477,8 +477,8 @@ def doTestEdge(self, h, rep, meshType, invert_model=False, invert_matrix=False): mesh.number(balance=False) elif meshType == "Tensor": mesh = discretize.TensorMesh(h) - v = np.random.rand(mesh.nE) - sig = np.random.rand(1) if rep == 0 else np.random.rand(mesh.nE * rep) + v = rng.random(mesh.nE) + sig = rng.random(1) if rep == 0 else rng.random(mesh.nE * rep) def fun(sig): M = mesh.get_edge_inner_product_line( @@ -544,7 +544,7 @@ class TestTensorSizeErrorRaises(unittest.TestCase): def setUp(self): self.mesh3D = TensorMesh([4, 4, 4]) - self.model = np.random.rand(self.mesh3D.nC) + self.model = rng.random(self.mesh3D.nC) def test_edge_inner_product_surface_deriv(self): self.assertRaises( diff --git a/tests/base/test_tensor_omf.py b/tests/base/test_tensor_omf.py index 467cff20d..d4179d545 100644 --- a/tests/base/test_tensor_omf.py +++ b/tests/base/test_tensor_omf.py @@ -50,6 +50,7 @@ def test_to_omf(self): self.assertTrue(np.allclose(models[name], arr)) def test_from_omf(self): + rng = np.random.default_rng(52134) omf_element = omf.VolumeElement( name="vol_ir", geometry=omf.VolumeGridGeometry( @@ -65,7 +66,7 @@ def test_from_omf(self): omf.ScalarData( name="Random Data", location="cells", - array=np.random.rand(10, 15, 20).flatten(), + array=rng.random((10, 15, 20)).flatten(), ) ], ) diff --git a/tests/base/test_tests.py b/tests/base/test_tests.py index d2f279cf7..e8fc560c3 100644 --- a/tests/base/test_tests.py +++ b/tests/base/test_tests.py @@ -73,20 +73,23 @@ def test_simplePass(self): def simplePass(x): return np.sin(x), sp.diags(np.cos(x)) - check_derivative(simplePass, np.random.randn(5), plotIt=False) + rng = np.random.default_rng(5322) + check_derivative(simplePass, rng.standard_normal(5), plotIt=False, rng=rng) def test_simpleFunction(self): def simpleFunction(x): return np.sin(x), lambda xi: np.cos(x) * xi - check_derivative(simpleFunction, np.random.randn(5), plotIt=False) + rng = np.random.default_rng(5322) + check_derivative(simpleFunction, rng.standard_normal(5), plotIt=False, rng=rng) def test_simpleFail(self): def simpleFail(x): return np.sin(x), -sp.diags(np.cos(x)) + rng = np.random.default_rng(5322) with pytest.raises(AssertionError): - check_derivative(simpleFail, np.random.randn(5), plotIt=False) + check_derivative(simpleFail, rng.standard_normal(5), plotIt=False, rng=rng) @pytest.mark.parametrize("test_type", ["mean", "min", "last", "all", "mean_at_least"]) diff --git a/tests/base/test_utils.py b/tests/base/test_utils.py index d34303f13..bf4ddc5b8 100644 --- a/tests/base/test_utils.py +++ b/tests/base/test_utils.py @@ -123,7 +123,8 @@ def test_index_cube_3D(self): ) def test_invXXXBlockDiagonal(self): - a = [np.random.rand(5, 1) for i in range(4)] + rng = np.random.default_rng(78352) + a = [rng.random((5, 1)) for i in range(4)] B = inverse_2x2_block_diagonal(*a) @@ -137,7 +138,7 @@ def test_invXXXBlockDiagonal(self): Z2 = B * A - sp.identity(10) self.assertTrue(np.linalg.norm(Z2.todense().ravel(), 2) < TOL) - a = [np.random.rand(5, 1) for i in range(9)] + a = [rng.random((5, 1)) for i in range(9)] B = inverse_3x3_block_diagonal(*a) A = sp.vstack( @@ -153,10 +154,11 @@ def test_invXXXBlockDiagonal(self): self.assertTrue(np.linalg.norm(Z3.todense().ravel(), 2) < TOL) def test_inverse_property_tensor2D(self): + rng = np.random.default_rng(763) M = discretize.TensorMesh([6, 6]) - a1 = np.random.rand(M.nC) - a2 = np.random.rand(M.nC) - a3 = np.random.rand(M.nC) + a1 = rng.random(M.nC) + a2 = rng.random(M.nC) + a3 = rng.random(M.nC) prop1 = a1 prop2 = np.c_[a1, a2] prop3 = np.c_[a1, a2, a3] @@ -173,10 +175,11 @@ def test_inverse_property_tensor2D(self): self.assertTrue(np.linalg.norm(Z.todense().ravel(), 2) < TOL) def test_TensorType2D(self): + rng = np.random.default_rng(8546) M = discretize.TensorMesh([6, 6]) - a1 = np.random.rand(M.nC) - a2 = np.random.rand(M.nC) - a3 = np.random.rand(M.nC) + a1 = rng.random(M.nC) + a2 = rng.random(M.nC) + a3 = rng.random(M.nC) prop1 = a1 prop2 = np.c_[a1, a2] prop3 = np.c_[a1, a2, a3] @@ -188,13 +191,14 @@ def test_TensorType2D(self): self.assertTrue(TensorType(M, None) == -1) def test_TensorType3D(self): + rng = np.random.default_rng(78352) M = discretize.TensorMesh([6, 6, 7]) - a1 = np.random.rand(M.nC) - a2 = np.random.rand(M.nC) - a3 = np.random.rand(M.nC) - a4 = np.random.rand(M.nC) - a5 = np.random.rand(M.nC) - a6 = np.random.rand(M.nC) + a1 = rng.random(M.nC) + a2 = rng.random(M.nC) + a3 = rng.random(M.nC) + a4 = rng.random(M.nC) + a5 = rng.random(M.nC) + a6 = rng.random(M.nC) prop1 = a1 prop2 = np.c_[a1, a2, a3] prop3 = np.c_[a1, a2, a3, a4, a5, a6] @@ -206,13 +210,14 @@ def test_TensorType3D(self): self.assertTrue(TensorType(M, None) == -1) def test_inverse_property_tensor3D(self): + rng = np.random.default_rng(78352) M = discretize.TensorMesh([6, 6, 6]) - a1 = np.random.rand(M.nC) - a2 = np.random.rand(M.nC) - a3 = np.random.rand(M.nC) - a4 = np.random.rand(M.nC) - a5 = np.random.rand(M.nC) - a6 = np.random.rand(M.nC) + a1 = rng.random(M.nC) + a2 = rng.random(M.nC) + a3 = rng.random(M.nC) + a4 = rng.random(M.nC) + a5 = rng.random(M.nC) + a6 = rng.random(M.nC) prop1 = a1 prop2 = np.c_[a1, a2, a3] prop3 = np.c_[a1, a2, a3, a4, a5, a6] diff --git a/tests/base/test_view.py b/tests/base/test_view.py index bcab25849..64baf98df 100644 --- a/tests/base/test_view.py +++ b/tests/base/test_view.py @@ -6,8 +6,6 @@ import pytest -np.random.seed(16) - TOL = 1e-1 @@ -51,7 +49,7 @@ def test_incorrectAxesWarnings(self): def test_plot_image(self): with self.assertRaises(NotImplementedError): - self.mesh.plot_image(np.random.rand(self.mesh.nC)) + self.mesh.plot_image(np.empty(self.mesh.nC)) if __name__ == "__main__": diff --git a/tests/base/test_volume_avg.py b/tests/base/test_volume_avg.py index ed319bf9e..652c6a521 100644 --- a/tests/base/test_volume_avg.py +++ b/tests/base/test_volume_avg.py @@ -7,9 +7,10 @@ class TestVolumeAverage(unittest.TestCase): def test_tensor_to_tensor(self): - h1 = np.random.rand(16) + rng = np.random.default_rng(68723) + h1 = rng.random(16) h1 /= h1.sum() - h2 = np.random.rand(16) + h2 = rng.random(16) h2 /= h2.sum() h1s = [] @@ -21,7 +22,7 @@ def test_tensor_to_tensor(self): mesh1 = discretize.TensorMesh(h1s) mesh2 = discretize.TensorMesh(h2s) - in_put = np.random.rand(mesh1.nC) + in_put = rng.random(mesh1.nC) out_put = np.empty(mesh2.nC) # test the three ways of calling... out1 = volume_average(mesh1, mesh2, in_put, out_put) @@ -40,9 +41,10 @@ def test_tensor_to_tensor(self): self.assertAlmostEqual(vol1, vol2) def test_tree_to_tree(self): - h1 = np.random.rand(16) + rng = np.random.default_rng(68723) + h1 = rng.random(16) h1 /= h1.sum() - h2 = np.random.rand(16) + h2 = rng.random(16) h2 /= h2.sum() h1s = [h1] @@ -60,7 +62,7 @@ def test_tree_to_tree(self): mesh2 = discretize.TreeMesh(h2s) mesh2.insert_cells([insert_2], [4]) - in_put = np.random.rand(mesh1.nC) + in_put = rng.random(mesh1.nC) out_put = np.empty(mesh2.nC) # test the three ways of calling... out1 = volume_average(mesh1, mesh2, in_put, out_put) @@ -79,9 +81,10 @@ def test_tree_to_tree(self): self.assertAlmostEqual(vol1, vol2) def test_tree_to_tensor(self): - h1 = np.random.rand(16) + rng = np.random.default_rng(68723) + h1 = rng.random(16) h1 /= h1.sum() - h2 = np.random.rand(16) + h2 = rng.random(16) h2 /= h2.sum() h1s = [h1] @@ -96,7 +99,7 @@ def test_tree_to_tensor(self): mesh1.insert_cells([insert_1], [4]) mesh2 = discretize.TensorMesh(h2s) - in_put = np.random.rand(mesh1.nC) + in_put = rng.random(mesh1.nC) out_put = np.empty(mesh2.nC) # test the three ways of calling... out1 = volume_average(mesh1, mesh2, in_put, out_put) @@ -115,9 +118,10 @@ def test_tree_to_tensor(self): self.assertAlmostEqual(vol1, vol2) def test_tensor_to_tree(self): - h1 = np.random.rand(16) + rng = np.random.default_rng(68723) + h1 = rng.random(16) h1 /= h1.sum() - h2 = np.random.rand(16) + h2 = rng.random(16) h2 /= h2.sum() h1s = [h1] @@ -132,7 +136,7 @@ def test_tensor_to_tree(self): mesh2 = discretize.TreeMesh(h2s) mesh2.insert_cells([insert_2], [4]) - in_put = np.random.rand(mesh1.nC) + in_put = rng.random(mesh1.nC) out_put = np.empty(mesh2.nC) # test the three ways of calling... out1 = volume_average(mesh1, mesh2, in_put, out_put) @@ -151,9 +155,10 @@ def test_tensor_to_tree(self): self.assertAlmostEqual(vol1, vol2) def test_errors(self): - h1 = np.random.rand(16) + rng = np.random.default_rng(68723) + h1 = rng.random(16) h1 /= h1.sum() - h2 = np.random.rand(16) + h2 = rng.random(16) h2 /= h2.sum() mesh1D = discretize.TensorMesh([h1]) mesh2D = discretize.TensorMesh([h1, h1]) @@ -175,9 +180,9 @@ def test_errors(self): # Gives mismatching mesh dimensions volume_average(mesh2D, mesh3D) - model1 = np.random.randn(mesh2D.nC) - bad_model1 = np.random.randn(3) - bad_model2 = np.random.rand(1) + model1 = rng.standard_normal(mesh2D.nC) + bad_model1 = rng.standard_normal(3) + bad_model2 = rng.random(1) # gives input values with incorrect lengths with self.assertRaises(ValueError): volume_average(mesh2D, mesh2, bad_model1) @@ -185,7 +190,8 @@ def test_errors(self): volume_average(mesh2D, mesh2, model1, bad_model2) def test_tree_to_tree_same_base(self): - h1 = np.random.rand(16) + rng = np.random.default_rng(68723) + h1 = rng.random(16) h1 /= h1.sum() h1s = [h1] @@ -201,7 +207,7 @@ def test_tree_to_tree_same_base(self): mesh2 = discretize.TreeMesh(h1s) mesh2.insert_cells([insert_2], [4]) - in_put = np.random.rand(mesh1.nC) + in_put = rng.random(mesh1.nC) out_put = np.empty(mesh2.nC) # test the three ways of calling... out1 = volume_average(mesh1, mesh2, in_put, out_put) @@ -220,7 +226,8 @@ def test_tree_to_tree_same_base(self): self.assertAlmostEqual(vol1, vol2) def test_tree_to_tensor_same_base(self): - h1 = np.random.rand(16) + rng = np.random.default_rng(867532) + h1 = rng.random(16) h1 /= h1.sum() h1s = [h1] @@ -233,7 +240,7 @@ def test_tree_to_tensor_same_base(self): mesh1.insert_cells([insert_1], [4]) mesh2 = discretize.TensorMesh(h1s) - in_put = np.random.rand(mesh1.nC) + in_put = rng.random(mesh1.nC) out_put = np.empty(mesh2.nC) # test the three ways of calling... out1 = volume_average(mesh1, mesh2, in_put, out_put) @@ -252,7 +259,8 @@ def test_tree_to_tensor_same_base(self): self.assertAlmostEqual(vol1, vol2) def test_tensor_to_tree_same_base(self): - h1 = np.random.rand(16) + rng = np.random.default_rng(91) + h1 = rng.random(16) h1 /= h1.sum() h1s = [h1] @@ -265,7 +273,7 @@ def test_tensor_to_tree_same_base(self): mesh2 = discretize.TreeMesh(h1s) mesh2.insert_cells([insert_2], [4]) - in_put = np.random.rand(mesh1.nC) + in_put = rng.random(mesh1.nC) out_put = np.empty(mesh2.nC) # test the three ways of calling... out1 = volume_average(mesh1, mesh2, in_put, out_put) @@ -284,6 +292,7 @@ def test_tensor_to_tree_same_base(self): self.assertAlmostEqual(vol1, vol2) def test_tensor_to_tensor_sub(self): + rng = np.random.default_rng(9867153) h1 = np.ones(32) h2 = np.ones(16) @@ -296,7 +305,7 @@ def test_tensor_to_tensor_sub(self): mesh1 = discretize.TensorMesh(h1s) mesh2 = discretize.TensorMesh(h2s) - in_put = np.random.rand(mesh1.nC) + in_put = rng.random(mesh1.nC) out_put = np.empty(mesh2.nC) # test the three ways of calling... out1 = volume_average(mesh1, mesh2, in_put, out_put) @@ -320,6 +329,7 @@ def test_tensor_to_tensor_sub(self): self.assertAlmostEqual(vol1, vol2) def test_tree_to_tree_sub(self): + rng = np.random.default_rng(987263) h1 = np.ones(32) h2 = np.ones(16) @@ -338,7 +348,7 @@ def test_tree_to_tree_sub(self): mesh2 = discretize.TreeMesh(h2s) mesh2.insert_cells([insert_2], [4]) - in_put = np.random.rand(mesh1.nC) + in_put = rng.random(mesh1.nC) out_put = np.empty(mesh2.nC) # test the three ways of calling... out1 = volume_average(mesh1, mesh2, in_put, out_put) @@ -362,6 +372,7 @@ def test_tree_to_tree_sub(self): self.assertAlmostEqual(vol1, vol2) def test_tree_to_tensor_sub(self): + rng = np.random.default_rng(5) h1 = np.ones(32) h2 = np.ones(16) @@ -377,7 +388,7 @@ def test_tree_to_tensor_sub(self): mesh1.insert_cells([insert_1], [4]) mesh2 = discretize.TensorMesh(h2s) - in_put = np.random.rand(mesh1.nC) + in_put = rng.random(mesh1.nC) out_put = np.empty(mesh2.nC) # test the three ways of calling... out1 = volume_average(mesh1, mesh2, in_put, out_put) @@ -401,6 +412,7 @@ def test_tree_to_tensor_sub(self): self.assertAlmostEqual(vol1, vol2) def test_tensor_to_tree_sub(self): + rng = np.random.default_rng(1) h1 = np.ones(32) h2 = np.ones(16) @@ -416,7 +428,7 @@ def test_tensor_to_tree_sub(self): mesh2 = discretize.TreeMesh(h2s) mesh2.insert_cells([insert_2], [4]) - in_put = np.random.rand(mesh1.nC) + in_put = rng.random(mesh1.nC) out_put = np.empty(mesh2.nC) # test the three ways of calling... out1 = volume_average(mesh1, mesh2, in_put, out_put) diff --git a/tests/boundaries/test_boundary_integrals.py b/tests/boundaries/test_boundary_integrals.py index 6574b55dd..a9a47e7bf 100644 --- a/tests/boundaries/test_boundary_integrals.py +++ b/tests/boundaries/test_boundary_integrals.py @@ -3,8 +3,6 @@ import discretize from discretize.utils import cart2cyl, cyl2cart -gen = np.random.default_rng(2552) - def u(*args): if len(args) == 1: @@ -88,7 +86,6 @@ class Test1DBoundaryIntegral(discretize.tests.OrderTest): meshDimension = 1 expectedOrders = 2 meshSizes = [4, 8, 16, 32, 64, 128] - rng = gen def getError(self): mesh = self.M @@ -139,7 +136,6 @@ class Test2DBoundaryIntegral(discretize.tests.OrderTest): meshDimension = 2 expectedOrders = [2, 2, 2, 2, 1] meshSizes = [4, 8, 16, 32, 64, 128] - rng = gen def getError(self): mesh = self.M @@ -220,7 +216,7 @@ class Test3DBoundaryIntegral(discretize.tests.OrderTest): meshDimension = 3 expectedOrders = [2, 1, 2, 2, 2, 2] meshSizes = [4, 8, 16, 32] - rng = gen + rng = np.random.default_rng(57681234) def getError(self): mesh = self.M diff --git a/tests/boundaries/test_boundary_maxwell.py b/tests/boundaries/test_boundary_maxwell.py index 1ffd35888..ea19ec063 100644 --- a/tests/boundaries/test_boundary_maxwell.py +++ b/tests/boundaries/test_boundary_maxwell.py @@ -1,7 +1,7 @@ import numpy as np import discretize from discretize import utils -from pymatsolver import Pardiso +from scipy.sparse.linalg import spsolve class TestFz2D_InhomogeneousDirichlet(discretize.tests.OrderTest): @@ -33,7 +33,7 @@ def getError(self): A = V @ C @ MeI @ C.T @ V + V rhs = V @ q_ana + V @ C @ MeI @ M_be @ ez_bc - ez_test = Pardiso(A.tocsr()) * rhs + ez_test = spsolve(A, rhs) if self._meshType == "rotateCurv": err = np.linalg.norm(mesh.cell_volumes * (ez_test - ez_ana)) else: @@ -52,7 +52,7 @@ class TestE3D_Inhomogeneous(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh", "uniformTree", "rotateCurv"] meshDimension = 3 expectedOrders = [2, 2, 1] - meshSizes = [8, 16, 32] + meshSizes = [4, 8, 16] def getError(self): # Test function @@ -99,7 +99,7 @@ def q_fun(x): A = C.T @ Mf @ C + Me rhs = Me @ q_ana + M_be * h_bc - e_test = Pardiso(A.tocsr(), is_symmetric=True, is_positive_definite=True) * rhs + e_test = spsolve(A, rhs) diff = e_test - e_ana if "Face" in self.myTest: diff --git a/tests/boundaries/test_boundary_poisson.py b/tests/boundaries/test_boundary_poisson.py index 0a199d1d6..3c8a0eaa7 100644 --- a/tests/boundaries/test_boundary_poisson.py +++ b/tests/boundaries/test_boundary_poisson.py @@ -4,9 +4,7 @@ import unittest import discretize from discretize import utils -from pymatsolver import Solver, Pardiso - -gen = np.random.default_rng(42) +from scipy.sparse.linalg import spsolve class TestCC1D_InhomogeneousDirichlet(discretize.tests.OrderTest): @@ -15,7 +13,6 @@ class TestCC1D_InhomogeneousDirichlet(discretize.tests.OrderTest): meshDimension = 1 expectedOrders = 2 meshSizes = [4, 8, 16, 32, 64, 128] - rng = gen def getError(self): # Test function @@ -42,7 +39,7 @@ def getError(self): A = V @ D @ MfI @ G rhs = V @ q_ana - V @ D @ MfI @ M_bf @ phi_bc - phi_test = Solver(A) * rhs + phi_test = spsolve(A, rhs) err = np.linalg.norm((phi_test - phi_ana)) / np.sqrt(mesh.n_cells) return err @@ -59,7 +56,6 @@ class TestCC2D_InhomogeneousDirichlet(discretize.tests.OrderTest): meshDimension = 2 expectedOrders = [2, 2, 1] meshSizes = [4, 8, 16, 32, 64] - rng = gen def getError(self): # Test function @@ -83,7 +79,7 @@ def getError(self): A = V @ D @ MfI @ G rhs = V @ q_ana - V @ D @ MfI @ M_bf @ phi_bc - phi_test = Solver(A) * rhs + phi_test = spsolve(A, rhs) if self._meshType == "rotateCurv": err = np.linalg.norm(mesh.cell_volumes * (phi_test - phi_ana)) else: @@ -103,7 +99,6 @@ class TestCC1D_InhomogeneousNeumann(discretize.tests.OrderTest): meshDimension = 1 expectedOrders = 2 meshSizes = [4, 8, 16, 32, 64, 128] - rng = gen def getError(self): # Test function @@ -171,7 +166,6 @@ class TestCC2D_InhomogeneousNeumann(discretize.tests.OrderTest): expectedOrders = [2, 2, 1] meshSizes = [4, 8, 16, 32] # meshSizes = [4] - rng = gen def getError(self): # Test function @@ -234,7 +228,6 @@ class TestCC1D_InhomogeneousMixed(discretize.tests.OrderTest): meshDimension = 1 expectedOrders = 2 meshSizes = [4, 8, 16, 32, 64, 128] - rng = gen def getError(self): # Test function @@ -268,10 +261,10 @@ def getError(self): rhs = V @ q_ana - V @ D @ MfI @ b_bc if self.myTest == "xc": - xc = Solver(A) * rhs + xc = spsolve(A, rhs) err = np.linalg.norm(xc - xc_ana) / np.sqrt(mesh.n_cells) elif self.myTest == "xcJ": - xc = Solver(A) * rhs + xc = spsolve(A, rhs) j = MfI @ ((-G + B_bc) @ xc + b_bc) err = np.linalg.norm(j - j_ana, np.inf) return err @@ -293,7 +286,6 @@ class TestCC2D_InhomogeneousMixed(discretize.tests.OrderTest): meshDimension = 2 expectedOrders = [2, 2, 1] meshSizes = [2, 4, 8, 16] - rng = gen # meshSizes = [4] def getError(self): @@ -340,7 +332,7 @@ def getError(self): A = V @ D @ MfI @ (-G + B_bc) rhs = V @ q_ana - V @ D @ MfI @ b_bc - phi_test = Solver(A) * rhs + phi_test = spsolve(A, rhs) if self._meshType == "rotateCurv": err = np.linalg.norm(mesh.cell_volumes * (phi_test - phi_ana)) @@ -360,8 +352,7 @@ class TestCC3D_InhomogeneousMixed(discretize.tests.OrderTest): meshTypes = ["uniformTensorMesh", "uniformTree", "rotateCurv"] meshDimension = 3 expectedOrders = [2, 2, 2] - meshSizes = [2, 4, 8, 16, 32] - rng = gen + meshSizes = [2, 4, 8, 16] def getError(self): # Test function @@ -430,7 +421,7 @@ def getError(self): A = V @ D @ MfI @ (-G + B_bc) rhs = V @ q_ana - V @ D @ MfI @ b_bc - phi_test = Pardiso(A.tocsr()) * rhs + phi_test = spsolve(A, rhs) if self._meshType == "rotateCurv": err = np.linalg.norm(mesh.cell_volumes * (phi_test - phi_ana)) @@ -450,7 +441,6 @@ class TestN1D_boundaries(discretize.tests.OrderTest): meshDimension = 1 expectedOrders = 2 meshSizes = [2, 4, 8, 16, 32, 64, 128] - rng = gen # meshSizes = [4] def getError(self): @@ -498,7 +488,7 @@ def getError(self): rhs = P_f.T @ rhs - (P_f.T @ A @ P_b) @ phi_ana[[0]] A = P_f.T @ A @ P_f - phi_test = Solver(A) * rhs + phi_test = spsolve(A, rhs) if self.boundary_type == "Nuemann": phi_test = P_f @ phi_test + P_b @ phi_ana[[0]] @@ -530,7 +520,6 @@ class TestN2D_boundaries(discretize.tests.OrderTest): expectedOrders = 2 tolerance = [0.8, 0.8, 0.6] meshSizes = [8, 16, 32, 64] - rng = gen # meshSizes = [4] def getError(self): @@ -601,7 +590,7 @@ def getError(self): rhs = P_f.T @ rhs - (P_f.T @ A @ P_b) @ phi_ana[[0]] A = P_f.T @ A @ P_f - phi_test = Solver(A) * rhs + phi_test = spsolve(A, rhs) if self.boundary_type == "Nuemann": phi_test = P_f @ phi_test + P_b @ phi_ana[[0]] @@ -631,8 +620,7 @@ class TestN3D_boundaries(discretize.tests.OrderTest): meshDimension = 3 expectedOrders = 2 tolerance = 0.6 - meshSizes = [2, 4, 8, 16, 32] - rng = gen + meshSizes = [2, 4, 8, 16] # meshSizes = [4] def getError(self): @@ -727,7 +715,7 @@ def getError(self): rhs = P_f.T @ rhs - (P_f.T @ A @ P_b) @ phi_ana[[0]] A = P_f.T @ A @ P_f - phi_test = Pardiso(A.tocsr()) * rhs + phi_test = spsolve(A, rhs) if self.boundary_type == "Nuemann": phi_test = P_f @ phi_test + P_b @ phi_ana[[0]] diff --git a/tests/boundaries/test_errors.py b/tests/boundaries/test_errors.py index b8475acda..41430b0a1 100644 --- a/tests/boundaries/test_errors.py +++ b/tests/boundaries/test_errors.py @@ -3,6 +3,9 @@ import discretize +rng = np.random.default_rng(53679) + + class RobinOperatorTest(unittest.TestCase): def setUp(self): self.mesh = discretize.TensorMesh([18, 20, 32]) @@ -28,7 +31,7 @@ def testCellGradBroadcasting(self): np.testing.assert_equal(bt, b1) self.assertEqual((B1 - B_t).nnz, 0) - gamma = np.random.rand(n_boundary_faces, 2) + gamma = rng.random((n_boundary_faces, 2)) B1, b1 = mesh.cell_gradient_weak_form_robin( alpha=0.5, beta=1.5, gamma=gamma[:, 0] ) @@ -78,7 +81,7 @@ def testEdgeDivBroadcasting(self): np.testing.assert_allclose(bt, b1) np.testing.assert_allclose(B1.data, B_t.data) - gamma = np.random.rand(n_boundary_faces, 2) + gamma = rng.random((n_boundary_faces, 2)) B1, b1 = mesh.edge_divergence_weak_form_robin( alpha=0.5, beta=1.5, gamma=gamma[:, 0] ) @@ -90,7 +93,7 @@ def testEdgeDivBroadcasting(self): np.testing.assert_allclose(B1.data, B3.data) np.testing.assert_allclose(np.c_[b1, b2], b3) - gamma = np.random.rand(n_boundary_nodes, 2) + gamma = rng.random((n_boundary_nodes, 2)) B1, b1 = mesh.edge_divergence_weak_form_robin( alpha=0.5, beta=1.5, gamma=gamma[:, 0] ) diff --git a/tests/boundaries/test_tensor_boundary.py b/tests/boundaries/test_tensor_boundary.py index c32a55bac..94be4aa0a 100644 --- a/tests/boundaries/test_tensor_boundary.py +++ b/tests/boundaries/test_tensor_boundary.py @@ -1,7 +1,7 @@ import numpy as np import unittest import discretize -from pymatsolver import Solver +from scipy.sparse.linalg import spsolve MESHTYPES = ["uniformTensorMesh"] @@ -225,8 +225,7 @@ def q_fun(x): if self.myTest == "xc": # TODO: fix the null space - Ainv = Solver(A) - xc = Ainv * rhs + xc = spsolve(A, rhs) err = np.linalg.norm((xc - xc_ana), np.inf) else: NotImplementedError @@ -320,8 +319,7 @@ def gamma_fun(alpha, beta, phi, phi_deriv): A = Div * MfrhoI * G if self.myTest == "xc": - Ainv = Solver(A) - xc = Ainv * rhs + xc = spsolve(A, rhs) err = np.linalg.norm((xc - xc_ana), np.inf) else: NotImplementedError @@ -452,8 +450,7 @@ def gamma_fun(alpha, beta, phi, phi_deriv): if self.myTest == "xc": # TODO: fix the null space - Ainv = Solver(A) - xc = Ainv * rhs + xc = spsolve(A, rhs) err = np.linalg.norm((xc - xc_ana), np.inf) else: NotImplementedError diff --git a/tests/boundaries/test_tensor_boundary_poisson.py b/tests/boundaries/test_tensor_boundary_poisson.py index df3763117..5f5b191d6 100644 --- a/tests/boundaries/test_tensor_boundary_poisson.py +++ b/tests/boundaries/test_tensor_boundary_poisson.py @@ -3,7 +3,7 @@ import unittest import discretize from discretize import utils -from pymatsolver import Solver, SolverCG +from scipy.sparse.linalg import spsolve MESHTYPES = ["uniformTensorMesh"] @@ -55,13 +55,12 @@ def getError(self): err = np.linalg.norm((q - q_ana), np.inf) elif self.myTest == "xc": # TODO: fix the null space - solver = SolverCG(A, maxiter=1000) - xc = solver * rhs + xc = spsolve(A, rhs) print("ACCURACY", np.linalg.norm(utils.mkvc(A * xc) - rhs)) err = np.linalg.norm((xc - xc_ana), np.inf) elif self.myTest == "xcJ": # TODO: fix the null space - xc = Solver(A) * rhs + xc = spsolve(A, rhs) print(np.linalg.norm(utils.mkvc(A * xc) - rhs)) j = McI * (G * xc + P * phi_bc) err = np.linalg.norm((j - j_ana), np.inf) @@ -141,10 +140,10 @@ def getError(self): elif self.myTest == "q": err = np.linalg.norm((q - q_ana), np.inf) elif self.myTest == "xc": - xc = Solver(A) * (rhs) + xc = spsolve(A, rhs) err = np.linalg.norm((xc - xc_ana), np.inf) elif self.myTest == "xcJ": - xc = Solver(A) * (rhs) + xc = spsolve(A, rhs) j = McI * (G * xc + P * bc) err = np.linalg.norm((j - j_ana), np.inf) diff --git a/tests/cyl/test_cyl.py b/tests/cyl/test_cyl.py index c3bdd22af..1b239bf7a 100644 --- a/tests/cyl/test_cyl.py +++ b/tests/cyl/test_cyl.py @@ -5,7 +5,7 @@ import discretize from discretize import tests, utils -np.random.seed(13) +rng = np.random.default_rng(87564123) class TestCylSymmetricMesh(unittest.TestCase): @@ -388,8 +388,8 @@ class TestCellGrad2D_Dirichlet(unittest.TestCase): # self.orderTest() def setUp(self): - hx = np.random.rand(10) - hz = np.random.rand(10) + hx = rng.random(10) + hz = rng.random(10) self.mesh = discretize.CylindricalMesh([hx, 1, hz]) def test_NotImplementedError(self): @@ -399,8 +399,8 @@ def test_NotImplementedError(self): class TestAveragingSimple(unittest.TestCase): def setUp(self): - hx = np.random.rand(10) - hz = np.random.rand(10) + hx = rng.random(10) + hz = rng.random(10) self.mesh = discretize.CylindricalMesh([hx, 1, hz]) def test_simpleEdges(self): diff --git a/tests/cyl/test_cyl3D.py b/tests/cyl/test_cyl3D.py index 94d9ed6c6..4a7110046 100644 --- a/tests/cyl/test_cyl3D.py +++ b/tests/cyl/test_cyl3D.py @@ -4,8 +4,6 @@ import discretize from discretize import utils -rng = np.random.default_rng(16) - TOL = 1e-1 diff --git a/tests/cyl/test_cylOperators.py b/tests/cyl/test_cylOperators.py index 1dca64497..4486fca19 100644 --- a/tests/cyl/test_cylOperators.py +++ b/tests/cyl/test_cylOperators.py @@ -6,15 +6,13 @@ import discretize from discretize import tests -np.random.seed(16) - TOL = 1e-1 # ----------------------------- Test Operators ------------------------------ # -MESHTYPES = ["uniformCylMesh", "randomCylMesh"] +MESHTYPES = ["uniformCylMesh"] call2 = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 2]) call3 = lambda fun, xyz: fun(xyz[:, 0], xyz[:, 1], xyz[:, 2]) cyl_row2 = lambda g, xfun, yfun: np.c_[call2(xfun, g), call2(yfun, g)] diff --git a/tests/cyl/test_cyl_innerproducts.py b/tests/cyl/test_cyl_innerproducts.py index b9752bfcd..d74859485 100644 --- a/tests/cyl/test_cyl_innerproducts.py +++ b/tests/cyl/test_cyl_innerproducts.py @@ -9,7 +9,7 @@ TOL = 1e-1 TOLD = 0.7 # tolerance on deriv checks -np.random.seed(99) +rng = np.random.default_rng(99) class FaceInnerProductFctsIsotropic(object): @@ -465,8 +465,8 @@ class TestCylInnerProducts_Deriv(unittest.TestCase): def setUp(self): n = 2 self.mesh = discretize.CylindricalMesh([n, 1, n]) - self.face_vec = np.random.rand(self.mesh.nF) - self.edge_vec = np.random.rand(self.mesh.nE) + self.face_vec = rng.random(self.mesh.nF) + self.edge_vec = rng.random(self.mesh.nE) # make up a smooth function self.x0 = 2 * self.mesh.gridCC[:, 0] ** 2 + self.mesh.gridCC[:, 2] ** 4 @@ -579,8 +579,8 @@ class TestCylInnerProductsAnisotropic_Deriv(unittest.TestCase): def setUp(self): n = 60 self.mesh = discretize.CylindricalMesh([n, 1, n]) - self.face_vec = np.random.rand(self.mesh.nF) - self.edge_vec = np.random.rand(self.mesh.nE) + self.face_vec = rng.random(self.mesh.nF) + self.edge_vec = rng.random(self.mesh.nE) # make up a smooth function self.x0 = np.array( [2 * self.mesh.gridCC[:, 0] ** 2 + self.mesh.gridCC[:, 2] ** 4] @@ -745,8 +745,8 @@ class TestCylInnerProductsFaceProperties_Deriv(unittest.TestCase): def setUp(self): n = 2 self.mesh = discretize.CylindricalMesh([n, 1, n]) - self.face_vec = np.random.rand(self.mesh.nF) - self.edge_vec = np.random.rand(self.mesh.nE) + self.face_vec = rng.random(self.mesh.nF) + self.edge_vec = rng.random(self.mesh.nE) # make up a smooth function self.x0 = np.r_[ 2 * self.mesh.gridFx[:, 0] ** 2 + self.mesh.gridFx[:, 2] ** 4, diff --git a/tests/cyl/test_cyl_operators.py b/tests/cyl/test_cyl_operators.py index e7e78c807..fa43d6b33 100644 --- a/tests/cyl/test_cyl_operators.py +++ b/tests/cyl/test_cyl_operators.py @@ -13,6 +13,8 @@ variable_names=list("RTZ"), ) +rng = np.random.default_rng(563279) + def lambdify_vector(variabls, u_vecs, func): funcs = [sp.lambdify(variabls, func.coeff(u_hat), "numpy") for u_hat in u_vecs] @@ -346,7 +348,7 @@ def get_error(n_cells): def test_mimetic_div_curl(mesh_type): mesh, _ = setup_mesh(mesh_type, 10) - v = np.random.rand(mesh.n_edges) + v = rng.random(mesh.n_edges) divcurlv = mesh.face_divergence @ (mesh.edge_curl @ v) np.testing.assert_allclose(divcurlv, 0, atol=1e-11) @@ -355,7 +357,7 @@ def test_mimetic_div_curl(mesh_type): def test_mimetic_curl_grad(mesh_type): mesh, _ = setup_mesh(mesh_type, 10) - v = np.random.rand(mesh.n_nodes) + v = rng.random(mesh.n_nodes) divcurlv = mesh.edge_curl @ (mesh.nodal_gradient @ v) np.testing.assert_allclose(divcurlv, 0, atol=1e-11) diff --git a/tests/simplex/test_inner_products.py b/tests/simplex/test_inner_products.py index 88d454f8a..83f6875a4 100644 --- a/tests/simplex/test_inner_products.py +++ b/tests/simplex/test_inner_products.py @@ -4,6 +4,8 @@ import scipy.sparse as sp from discretize.utils import example_simplex_mesh +rng = np.random.default_rng(4421) + def u(*args): if len(args) == 1: @@ -332,8 +334,8 @@ class TestInnerProductsDerivs(unittest.TestCase): def doTestFace(self, h, rep): nodes, simplices = example_simplex_mesh(h) mesh = discretize.SimplexMesh(nodes, simplices) - v = np.random.rand(mesh.n_faces) - sig = np.random.rand(1) if rep == 0 else np.random.rand(mesh.nC * rep) + v = rng.random(mesh.n_faces) + sig = rng.random(1) if rep == 0 else rng.random(mesh.nC * rep) def fun(sig): M = mesh.get_face_inner_product(sig) @@ -346,8 +348,8 @@ def fun(sig): def doTestEdge(self, h, rep): nodes, simplices = example_simplex_mesh(h) mesh = discretize.SimplexMesh(nodes, simplices) - v = np.random.rand(mesh.n_edges) - sig = np.random.rand(1) if rep == 0 else np.random.rand(mesh.nC * rep) + v = rng.random(mesh.n_edges) + sig = rng.random(1) if rep == 0 else rng.random(mesh.nC * rep) def fun(sig): M = mesh.get_edge_inner_product(sig) @@ -544,7 +546,7 @@ def setUp(self): def test_bad_model_size(self): mesh = self.mesh - bad_model = np.random.rand(mesh.n_cells, 5) + bad_model = rng.random((mesh.n_cells, 5)) with self.assertRaises(ValueError): mesh.get_face_inner_product(bad_model) with self.assertRaises(ValueError): @@ -552,7 +554,7 @@ def test_bad_model_size(self): def test_cant_invert(self): mesh = self.mesh - good_model = np.random.rand(mesh.n_cells) + good_model = rng.random(mesh.n_cells) with self.assertRaises(NotImplementedError): mesh.get_face_inner_product(good_model, invert_matrix=True) with self.assertRaises(NotImplementedError): diff --git a/tests/simplex/test_utils.py b/tests/simplex/test_utils.py index 87138c1b9..e0c4fc1a4 100644 --- a/tests/simplex/test_utils.py +++ b/tests/simplex/test_utils.py @@ -13,6 +13,8 @@ except ImportError: has_vtk = False +rng = np.random.default_rng(87916253) + class SimplexTests(unittest.TestCase): def test_init_errors(self): @@ -42,7 +44,7 @@ def test_init_errors(self): with self.assertRaises(ValueError): # pass bad dimensionality - discretize.SimplexMesh(np.random.rand(10, 4), simplices[:, :-1]) + discretize.SimplexMesh(np.ones((10, 4)), simplices[:, :-1]) def test_find_containing(self): n = 4 @@ -81,11 +83,11 @@ def test_image_plotting(self): points, simplices = discretize.utils.example_simplex_mesh((n, n)) mesh = discretize.SimplexMesh(points, simplices) - cc_dat = np.random.rand(mesh.n_cells) - n_dat = np.random.rand(mesh.n_nodes) - f_dat = np.random.rand(mesh.n_faces) - e_dat = np.random.rand(mesh.n_edges) - ccv_dat = np.random.rand(mesh.n_cells, 2) + cc_dat = rng.random(mesh.n_cells) + n_dat = rng.random(mesh.n_nodes) + f_dat = rng.random(mesh.n_faces) + e_dat = rng.random(mesh.n_edges) + ccv_dat = rng.random((mesh.n_cells, 2)) mesh.plot_image(cc_dat) mesh.plot_image(ccv_dat, v_type="CCv", view="vec") @@ -106,7 +108,7 @@ def test_image_plotting(self): points, simplices = discretize.utils.example_simplex_mesh((n, n, n)) mesh = discretize.SimplexMesh(points, simplices) - cc_dat = np.random.rand(mesh.n_cells) + cc_dat = rng.random(mesh.n_cells) mesh.plot_image(cc_dat) plt.close("all") @@ -128,7 +130,7 @@ def test_2D_vtk(self): n = 5 points, simplices = discretize.utils.example_simplex_mesh((n, n)) mesh = discretize.SimplexMesh(points, simplices) - cc_dat = np.random.rand(mesh.n_cells) + cc_dat = rng.random(mesh.n_cells) vtk_obj = mesh.to_vtk(models={"info": cc_dat}) @@ -149,7 +151,7 @@ def test_3D_vtk(self): n = 5 points, simplices = discretize.utils.example_simplex_mesh((n, n, n)) mesh = discretize.SimplexMesh(points, simplices) - cc_dat = np.random.rand(mesh.n_cells) + cc_dat = rng.random(mesh.n_cells) vtk_obj = mesh.to_vtk(models={"info": cc_dat}) diff --git a/tests/tree/test_refine.py b/tests/tree/test_refine.py index e2ec53da4..6b9ae81cb 100644 --- a/tests/tree/test_refine.py +++ b/tests/tree/test_refine.py @@ -160,9 +160,9 @@ def refine_triangle(cell): def test_triangle_errors(): - not_triangles_array = np.random.rand(4, 2, 2) - triangle3 = np.random.rand(3, 3) - triangles2 = np.random.rand(10, 3, 2) + not_triangles_array = np.empty((4, 2, 2)) + triangle3 = np.empty((3, 3)) + triangles2 = np.empty((10, 3, 2)) levels = np.full(8, -1) mesh1 = discretize.TreeMesh([64, 64]) @@ -324,9 +324,9 @@ def refine_simplex(cell): def test_tetra_errors(): - not_simplex_array = np.random.rand(4, 3, 3) - simplex = np.random.rand(4, 2) - simplices = np.random.rand(10, 4, 3) + not_simplex_array = np.empty((4, 3, 3)) + simplex = np.empty((4, 2)) + simplices = np.empty((10, 4, 3)) levels = np.full(8, -1) mesh1 = discretize.TreeMesh([32, 32, 32]) @@ -461,24 +461,25 @@ def test_refine_triang_prism_errors(): mesh.refine_vertical_trianglular_prism(xyz[:, :-1], h, -1) # incorrect levels and triangles - ps = np.random.rand(10, 3, 3) + ps = np.empty((10, 3, 3)) with pytest.raises(ValueError): mesh.refine_vertical_trianglular_prism(ps, h, [-1, -2]) # incorrect heights and triangles - ps = np.random.rand(10, 3, 3) + ps = np.empty((10, 3, 3)) with pytest.raises(ValueError): mesh.refine_vertical_trianglular_prism(ps, [h, h], -1) # negative heights - ps = np.random.rand(10, 3, 3) + ps = np.empty((10, 3, 3)) with pytest.raises(ValueError): mesh.refine_vertical_trianglular_prism(ps, -h, -1) def test_bounding_box(): # No padding - xyz = np.random.rand(20, 2) * 0.25 + 3 / 8 + rng = np.random.default_rng(51623978) + xyz = rng.random((20, 2)) * 0.25 + 3 / 8 mesh1 = discretize.TreeMesh([32, 32]) mesh1.refine_bounding_box(xyz, -1, None) @@ -512,7 +513,7 @@ def test_bounding_box(): def test_bounding_box_errors(): mesh1 = discretize.TreeMesh([32, 32]) - xyz = np.random.rand(20, 3) + xyz = np.empty((20, 3)) # incorrect padding shape with pytest.raises(ValueError): mesh1.refine_bounding_box(xyz, -1, [[2, 3, 4]]) diff --git a/tests/tree/test_tree.py b/tests/tree/test_tree.py index d63310a3d..878a5a73f 100644 --- a/tests/tree/test_tree.py +++ b/tests/tree/test_tree.py @@ -5,12 +5,14 @@ TOL = 1e-8 +rng = np.random.default_rng(6234) + class TestSimpleQuadTree(unittest.TestCase): def test_counts(self): nc = 8 - h1 = np.random.rand(nc) * nc * 0.5 + nc * 0.5 - h2 = np.random.rand(nc) * nc * 0.5 + nc * 0.5 + h1 = rng.random(nc) * nc * 0.5 + nc * 0.5 + h2 = rng.random(nc) * nc * 0.5 + nc * 0.5 h = [hi / np.sum(hi) for hi in [h1, h2]] # normalize M = discretize.TreeMesh(h) points = np.array([[0.1, 0.1]]) @@ -132,9 +134,9 @@ def test_serialization(self): class TestOcTree(unittest.TestCase): def test_counts(self): nc = 8 - h1 = np.random.rand(nc) * nc * 0.5 + nc * 0.5 - h2 = np.random.rand(nc) * nc * 0.5 + nc * 0.5 - h3 = np.random.rand(nc) * nc * 0.5 + nc * 0.5 + h1 = rng.random(nc) * nc * 0.5 + nc * 0.5 + h2 = rng.random(nc) * nc * 0.5 + nc * 0.5 + h3 = rng.random(nc) * nc * 0.5 + nc * 0.5 h = [hi / np.sum(hi) for hi in [h1, h2, h3]] # normalize M = discretize.TreeMesh(h, levels=3) points = np.array([[0.2, 0.1, 0.7], [0.8, 0.4, 0.2]]) @@ -311,8 +313,8 @@ def refinefcn(cell): def test_cell_nodes(self): # 2D nc = 8 - h1 = np.random.rand(nc) * nc * 0.5 + nc * 0.5 - h2 = np.random.rand(nc) * nc * 0.5 + nc * 0.5 + h1 = rng.random(nc) * nc * 0.5 + nc * 0.5 + h2 = rng.random(nc) * nc * 0.5 + nc * 0.5 h = [hi / np.sum(hi) for hi in [h1, h2]] # normalize M = discretize.TreeMesh(h) points = np.array([[0.2, 0.1], [0.8, 0.4]]) @@ -326,9 +328,9 @@ def test_cell_nodes(self): # 3D nc = 8 - h1 = np.random.rand(nc) * nc * 0.5 + nc * 0.5 - h2 = np.random.rand(nc) * nc * 0.5 + nc * 0.5 - h3 = np.random.rand(nc) * nc * 0.5 + nc * 0.5 + h1 = rng.random(nc) * nc * 0.5 + nc * 0.5 + h2 = rng.random(nc) * nc * 0.5 + nc * 0.5 + h3 = rng.random(nc) * nc * 0.5 + nc * 0.5 h = [hi / np.sum(hi) for hi in [h1, h2, h3]] # normalize M = discretize.TreeMesh(h, levels=3) points = np.array([[0.2, 0.1, 0.7], [0.8, 0.4, 0.2]]) @@ -346,8 +348,8 @@ class TestTreeMeshNodes: def sample_mesh(self, request): """Return a sample TreeMesh""" nc = 8 - h1 = np.random.rand(nc) * nc * 0.5 + nc * 0.5 - h2 = np.random.rand(nc) * nc * 0.5 + nc * 0.5 + h1 = rng.random(nc) * nc * 0.5 + nc * 0.5 + h2 = rng.random(nc) * nc * 0.5 + nc * 0.5 if request.param == "2D": h = [hi / np.sum(hi) for hi in [h1, h2]] # normalize mesh = discretize.TreeMesh(h) @@ -355,7 +357,7 @@ def sample_mesh(self, request): levels = np.array([1, 2]) mesh.insert_cells(points, levels, finalize=True) else: - h3 = np.random.rand(nc) * nc * 0.5 + nc * 0.5 + h3 = rng.random(nc) * nc * 0.5 + nc * 0.5 h = [hi / np.sum(hi) for hi in [h1, h2, h3]] # normalize mesh = discretize.TreeMesh(h, levels=3) points = np.array([[0.2, 0.1, 0.7], [0.8, 0.4, 0.2]]) @@ -407,12 +409,12 @@ def function(cell): self.M = M def test_fx(self): - r = np.random.rand(self.M.nFx) + r = rng.random(self.M.nFx) P = self.M.get_interpolation_matrix(self.M.gridFx, "Fx") self.assertLess(np.abs(P[:, : self.M.nFx] * r - r).max(), TOL) def test_fy(self): - r = np.random.rand(self.M.nFy) + r = rng.random(self.M.nFy) P = self.M.get_interpolation_matrix(self.M.gridFy, "Fy") self.assertLess(np.abs(P[:, self.M.nFx :] * r - r).max(), TOL) @@ -437,36 +439,36 @@ def function(cell): self.M = M def test_Fx(self): - r = np.random.rand(self.M.nFx) + r = rng.random(self.M.nFx) P = self.M.get_interpolation_matrix(self.M.gridFx, "Fx") self.assertLess(np.abs(P[:, : self.M.nFx] * r - r).max(), TOL) def test_Fy(self): - r = np.random.rand(self.M.nFy) + r = rng.random(self.M.nFy) P = self.M.get_interpolation_matrix(self.M.gridFy, "Fy") self.assertLess( np.abs(P[:, self.M.nFx : (self.M.nFx + self.M.nFy)] * r - r).max(), TOL ) def test_Fz(self): - r = np.random.rand(self.M.nFz) + r = rng.random(self.M.nFz) P = self.M.get_interpolation_matrix(self.M.gridFz, "Fz") self.assertLess(np.abs(P[:, (self.M.nFx + self.M.nFy) :] * r - r).max(), TOL) def test_Ex(self): - r = np.random.rand(self.M.nEx) + r = rng.random(self.M.nEx) P = self.M.get_interpolation_matrix(self.M.gridEx, "Ex") self.assertLess(np.abs(P[:, : self.M.nEx] * r - r).max(), TOL) def test_Ey(self): - r = np.random.rand(self.M.nEy) + r = rng.random(self.M.nEy) P = self.M.get_interpolation_matrix(self.M.gridEy, "Ey") self.assertLess( np.abs(P[:, self.M.nEx : (self.M.nEx + self.M.nEy)] * r - r).max(), TOL ) def test_Ez(self): - r = np.random.rand(self.M.nEz) + r = rng.random(self.M.nEz) P = self.M.get_interpolation_matrix(self.M.gridEz, "Ez") self.assertLess(np.abs(P[:, (self.M.nEx + self.M.nEy) :] * r - r).max(), TOL) diff --git a/tests/tree/test_tree_innerproduct_derivs.py b/tests/tree/test_tree_innerproduct_derivs.py index 96885a749..4bd060cea 100644 --- a/tests/tree/test_tree_innerproduct_derivs.py +++ b/tests/tree/test_tree_innerproduct_derivs.py @@ -2,6 +2,8 @@ import unittest import discretize +rng = np.random.default_rng(678423) + class TestInnerProductsDerivsTensor(unittest.TestCase): def doTestFace( @@ -15,8 +17,8 @@ def doTestFace( mesh.refine(lambda xc: 3) elif meshType == "Tensor": mesh = discretize.TensorMesh(h) - v = np.random.rand(mesh.nF) - sig = np.random.rand(1) if rep == 0 else np.random.rand(mesh.nC * rep) + v = rng.random(mesh.nF) + sig = rng.random(1) if rep == 0 else rng.random(mesh.nC * rep) def fun(sig): M = mesh.get_face_inner_product( @@ -38,7 +40,7 @@ def fun(sig): fast, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False) + return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=rng) def doTestEdge( self, h, rep, fast, meshType, invert_model=False, invert_matrix=False @@ -51,8 +53,8 @@ def doTestEdge( mesh.refine(lambda xc: 3) elif meshType == "Tensor": mesh = discretize.TensorMesh(h) - v = np.random.rand(mesh.nE) - sig = np.random.rand(1) if rep == 0 else np.random.rand(mesh.nC * rep) + v = rng.random(mesh.nE) + sig = rng.random(1) if rep == 0 else rng.random(mesh.nC * rep) def fun(sig): M = mesh.get_edge_inner_product( @@ -74,7 +76,7 @@ def fun(sig): fast, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False) + return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=rng) def test_FaceIP_2D_float_Tree(self): self.assertTrue(self.doTestFace([8, 8], 0, False, "Tree")) @@ -169,8 +171,8 @@ def doTestFace(self, h, rep, meshType, invert_model=False, invert_matrix=False): mesh.refine(lambda xc: 3) elif meshType == "Tensor": mesh = discretize.TensorMesh(h) - v = np.random.rand(mesh.nF) - sig = np.random.rand(1) if rep == 0 else np.random.rand(mesh.nF * rep) + v = rng.random(mesh.nF) + sig = rng.random(1) if rep == 0 else rng.random(mesh.nF * rep) def fun(sig): M = mesh.get_face_inner_product_surface( @@ -191,7 +193,7 @@ def fun(sig): # fast, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False) + return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=rng) def doTestEdge(self, h, rep, meshType, invert_model=False, invert_matrix=False): if meshType == "Curv": @@ -202,8 +204,8 @@ def doTestEdge(self, h, rep, meshType, invert_model=False, invert_matrix=False): mesh.refine(lambda xc: 3) elif meshType == "Tensor": mesh = discretize.TensorMesh(h) - v = np.random.rand(mesh.nE) - sig = np.random.rand(1) if rep == 0 else np.random.rand(mesh.nF * rep) + v = rng.random(mesh.nE) + sig = rng.random(1) if rep == 0 else rng.random(mesh.nF * rep) def fun(sig): M = mesh.get_edge_inner_product_surface( @@ -224,7 +226,7 @@ def fun(sig): # fast, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False) + return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=rng) def test_FaceIP_2D_float_fast_Tree(self): self.assertTrue(self.doTestFace([8, 8], 0, "Tree")) @@ -261,8 +263,8 @@ def doTestEdge(self, h, rep, meshType, invert_model=False, invert_matrix=False): mesh.refine(lambda xc: 3) elif meshType == "Tensor": mesh = discretize.TensorMesh(h) - v = np.random.rand(mesh.nE) - sig = np.random.rand(1) if rep == 0 else np.random.rand(mesh.nE * rep) + v = rng.random(mesh.nE) + sig = rng.random(1) if rep == 0 else rng.random(mesh.nE * rep) def fun(sig): M = mesh.get_edge_inner_product_line( @@ -283,7 +285,7 @@ def fun(sig): # fast, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False) + return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=rng) def test_EdgeIP_2D_float_fast_Tree(self): self.assertTrue(self.doTestEdge([8, 8], 0, "Tree")) diff --git a/tests/tree/test_tree_interpolation.py b/tests/tree/test_tree_interpolation.py index 2886a2970..d4688364b 100644 --- a/tests/tree/test_tree_interpolation.py +++ b/tests/tree/test_tree_interpolation.py @@ -42,7 +42,6 @@ class TestInterpolation2d(discretize.tests.OrderTest): """ name = "Interpolation 2D" - # LOCS = np.random.rand(50, 2)*0.6+0.2 # location_type = 'Ex' X, Y = np.mgrid[0:1:250j, 0:1:250j] LOCS = np.c_[X.reshape(-1), Y.reshape(-1)] @@ -129,7 +128,6 @@ def test_orderEy(self): class TestInterpolation3D(discretize.tests.OrderTest): name = "Interpolation" - # LOCS = np.random.rand(50, 3)*0.6+0.2 X, Y, Z = np.mgrid[0:1:50j, 0:1:50j, 0:1:50j] LOCS = np.c_[X.reshape(-1), Y.reshape(-1), Z.reshape(-1)] meshTypes = MESHTYPES diff --git a/tests/tree/test_tree_operators.py b/tests/tree/test_tree_operators.py index 8d6d94f4b..483385d70 100644 --- a/tests/tree/test_tree_operators.py +++ b/tests/tree/test_tree_operators.py @@ -38,6 +38,7 @@ class TestCellGrad2D(discretize.tests.OrderTest): meshSizes = [8, 16] # because of the averaging involved in the ghost point. u_b = (u_n + u_g)/2 expectedOrders = 1 + rng = np.random.default_rng(87964213) def getError(self): # Test function @@ -55,7 +56,6 @@ def getError(self): return err def test_order(self): - np.random.seed(7) self.orderTest() @@ -66,6 +66,7 @@ class TestCellGrad3D(discretize.tests.OrderTest): meshSizes = [8, 16] # because of the averaging involved in the ghost point. u_b = (u_n + u_g)/2 expectedOrders = 1 + rng = np.random.default_rng(6957832) def getError(self): # Test function @@ -105,7 +106,6 @@ def getError(self): return err def test_order(self): - np.random.seed(6) self.orderTest() @@ -114,6 +114,7 @@ class TestFaceDivxy2D(discretize.tests.OrderTest): meshTypes = MESHTYPES meshDimension = 2 meshSizes = [16, 32] + rng = np.random.default_rng(19647823) def getError(self): # Test function @@ -136,7 +137,6 @@ def getError(self): return err def test_order(self): - np.random.seed(4) self.orderTest() @@ -144,6 +144,7 @@ class TestFaceDiv3D(discretize.tests.OrderTest): name = "Face Divergence 3D" meshTypes = MESHTYPES meshSizes = [8, 16, 32] + rng = np.random.default_rng(81725364) def getError(self): fx = lambda x, y, z: np.sin(2 * np.pi * x) @@ -164,7 +165,6 @@ def getError(self): return np.linalg.norm((divF - divF_ana), np.inf) def test_order(self): - np.random.seed(7) self.orderTest() @@ -173,6 +173,7 @@ class TestFaceDivxyz3D(discretize.tests.OrderTest): meshTypes = MESHTYPES meshDimension = 3 meshSizes = [8, 16, 32] + rng = np.random.default_rng(6172824) def getError(self): # Test function @@ -202,13 +203,12 @@ def getError(self): return err def test_order(self): - np.random.seed(7) self.orderTest() class TestCurl(discretize.tests.OrderTest): name = "Curl" - meshTypes = ["notatreeTree", "uniformTree"] # , 'randomTree']#, 'uniformTree'] + meshTypes = ["notatreeTree", "uniformTree"] meshSizes = [8, 16] # , 32] expectedOrders = [2, 1] # This is due to linear interpolation in the Re projection @@ -238,13 +238,12 @@ def getError(self): return err def test_order(self): - np.random.seed(7) self.orderTest() class TestNodalGrad(discretize.tests.OrderTest): name = "Nodal Gradient" - meshTypes = ["notatreeTree", "uniformTree"] # ['randomTree', 'uniformTree'] + meshTypes = ["notatreeTree", "uniformTree"] meshSizes = [8, 16] # , 32] expectedOrders = [2, 1] @@ -267,13 +266,12 @@ def getError(self): return err def test_order(self): - np.random.seed(7) self.orderTest() class TestNodalGrad2D(discretize.tests.OrderTest): name = "Nodal Gradient 2D" - meshTypes = ["notatreeTree", "uniformTree"] # ['randomTree', 'uniformTree'] + meshTypes = ["notatreeTree", "uniformTree"] meshSizes = [8, 16] # , 32] expectedOrders = [2, 1] meshDimension = 2 @@ -296,7 +294,6 @@ def getError(self): return err def test_order(self): - np.random.seed(7) self.orderTest() @@ -918,7 +915,7 @@ def test_order1_edges_invert_model(self): class TestTreeAveraging2D(discretize.tests.OrderTest): """Integrate an function over a unit cube domain using edgeInnerProducts and faceInnerProducts.""" - meshTypes = ["notatreeTree", "uniformTree"] # 'randomTree'] + meshTypes = ["notatreeTree", "uniformTree"] meshDimension = 2 meshSizes = [4, 8, 16] expectedOrders = [2, 1] @@ -998,7 +995,7 @@ def test_orderCC2F(self): class TestAveraging3D(discretize.tests.OrderTest): name = "Averaging 3D" - meshTypes = ["notatreeTree", "uniformTree"] # , 'randomTree'] + meshTypes = ["notatreeTree", "uniformTree"] meshDimension = 3 meshSizes = [8, 16] expectedOrders = [2, 1] diff --git a/tests/tree/test_tree_plotting.py b/tests/tree/test_tree_plotting.py index a932ed037..8ed4623be 100644 --- a/tests/tree/test_tree_plotting.py +++ b/tests/tree/test_tree_plotting.py @@ -6,6 +6,8 @@ matplotlib.use("Agg") +rng = np.random.default_rng(4213678) + class TestOcTreePlotting(unittest.TestCase): def setUp(self): @@ -22,8 +24,8 @@ def test_plot_slice(self): mesh.plot_grid(faces=True, edges=True, nodes=True) # CC plot - mod_cc = np.random.rand(len(mesh)) + 1j * np.random.rand(len(mesh)) - mod_cc[np.random.rand(len(mesh)) < 0.2] = np.nan + mod_cc = rng.random(len(mesh)) + 1j * rng.random(len(mesh)) + mod_cc[rng.random(len(mesh)) < 0.2] = np.nan mesh.plot_slice(mod_cc, normal="X", grid=True) mesh.plot_slice(mod_cc, normal="Y", ax=ax) @@ -31,11 +33,11 @@ def test_plot_slice(self): mesh.plot_slice(mod_cc, view="imag", ax=ax) mesh.plot_slice(mod_cc, view="abs", ax=ax) - mod_ccv = np.random.rand(len(mesh), 3) + mod_ccv = rng.random((len(mesh), 3)) mesh.plot_slice(mod_ccv, v_type="CCv", view="vec", ax=ax) # F plot tests - mod_f = np.random.rand(mesh.n_faces) + mod_f = rng.random(mesh.n_faces) mesh.plot_slice(mod_f, v_type="Fx", ax=ax) mesh.plot_slice(mod_f, v_type="Fy", ax=ax) mesh.plot_slice(mod_f, v_type="Fz", ax=ax) @@ -43,7 +45,7 @@ def test_plot_slice(self): mesh.plot_slice(mod_f, v_type="F", view="vec", ax=ax) # E plot tests - mod_e = np.random.rand(mesh.n_edges) + mod_e = rng.random(mesh.n_edges) mesh.plot_slice(mod_e, v_type="Ex", ax=ax) mesh.plot_slice(mod_e, v_type="Ey", ax=ax) mesh.plot_slice(mod_e, v_type="Ez", ax=ax) @@ -51,7 +53,7 @@ def test_plot_slice(self): mesh.plot_slice(mod_e, v_type="E", view="vec", ax=ax) # Nodes - mod_n = np.random.rand(mesh.n_nodes) + mod_n = rng.random(mesh.n_nodes) mesh.plot_slice(mod_n, v_type="N") plt.close("all") @@ -70,32 +72,32 @@ def test_plot_slice(self): mesh.plot_grid(faces=True, edges=True, nodes=True) # CC plot - mod_cc = np.random.rand(len(mesh)) + 1j * np.random.rand(len(mesh)) - mod_cc[np.random.rand(len(mesh)) < 0.2] = np.nan + mod_cc = rng.random(len(mesh)) + 1j * rng.random(len(mesh)) + mod_cc[rng.random(len(mesh)) < 0.2] = np.nan mesh.plot_image(mod_cc) mesh.plot_image(mod_cc, ax=ax) mesh.plot_image(mod_cc, view="imag", ax=ax) mesh.plot_image(mod_cc, view="abs", ax=ax) - mod_ccv = np.random.rand(len(mesh), 2) + mod_ccv = rng.random((len(mesh), 2)) mesh.plot_image(mod_ccv, v_type="CCv", view="vec", ax=ax) # F plot tests - mod_f = np.random.rand(mesh.n_faces) + mod_f = rng.random(mesh.n_faces) mesh.plot_image(mod_f, v_type="Fx", ax=ax) mesh.plot_image(mod_f, v_type="Fy", ax=ax) mesh.plot_image(mod_f, v_type="F", ax=ax) mesh.plot_image(mod_f, v_type="F", view="vec", ax=ax) # E plot tests - mod_e = np.random.rand(mesh.n_edges) + mod_e = rng.random(mesh.n_edges) mesh.plot_image(mod_e, v_type="Ex", ax=ax) mesh.plot_image(mod_e, v_type="Ey", ax=ax) mesh.plot_image(mod_e, v_type="E", ax=ax) mesh.plot_image(mod_e, v_type="E", view="vec", ax=ax) # Nodes - mod_n = np.random.rand(mesh.n_nodes) + mod_n = rng.random(mesh.n_nodes) mesh.plot_image(mod_n, v_type="N", ax=ax) plt.close("all") diff --git a/tests/tree/test_tree_utils.py b/tests/tree/test_tree_utils.py index e868058a5..692d27f56 100644 --- a/tests/tree/test_tree_utils.py +++ b/tests/tree/test_tree_utils.py @@ -3,7 +3,6 @@ from discretize.utils import mesh_builder_xyz, refine_tree_xyz TOL = 1e-8 -np.random.seed(12) class TestRefineOcTree(unittest.TestCase): diff --git a/tutorials/inner_products/1_basic.py b/tutorials/inner_products/1_basic.py index bd86eec09..4d317251d 100644 --- a/tutorials/inner_products/1_basic.py +++ b/tutorials/inner_products/1_basic.py @@ -74,6 +74,8 @@ import matplotlib.pyplot as plt import numpy as np +rng = np.random.default_rng(8572) + # sphinx_gallery_thumbnail_number = 2 @@ -257,10 +259,10 @@ def fcn_y(xy, sig): Mf_inv = mesh.get_face_inner_product(invert_matrix=True) # Generate some random vectors -phi_c = np.random.rand(mesh.nC) -# phi_n = np.random.rand(mesh.nN) -vec_e = np.random.rand(mesh.nE) -vec_f = np.random.rand(mesh.nF) +phi_c = rng.random(mesh.nC) +# phi_n = rng.random(mesh.nN) +vec_e = rng.random(mesh.nE) +vec_f = rng.random(mesh.nF) # Generate some random vectors norm_c = np.linalg.norm(phi_c - Mc_inv.dot(Mc.dot(phi_c))) diff --git a/tutorials/inner_products/2_physical_properties.py b/tutorials/inner_products/2_physical_properties.py index 531e8b077..d5845da5d 100644 --- a/tutorials/inner_products/2_physical_properties.py +++ b/tutorials/inner_products/2_physical_properties.py @@ -75,6 +75,8 @@ import numpy as np import matplotlib.pyplot as plt +rng = np.random.default_rng(87236) + # sphinx_gallery_thumbnail_number = 1 ##################################################### @@ -181,17 +183,17 @@ mesh = TensorMesh([h, h, h]) # Isotropic case: (nC, ) numpy array -sig = np.random.rand(mesh.nC) # sig for each cell +sig = rng.random(mesh.nC) # sig for each cell Me1 = mesh.get_edge_inner_product(sig) # Edges inner product matrix Mf1 = mesh.get_face_inner_product(sig) # Faces inner product matrix # Linear case: (nC, dim) numpy array -sig = np.random.rand(mesh.nC, mesh.dim) +sig = rng.random((mesh.nC, mesh.dim)) Me2 = mesh.get_edge_inner_product(sig) Mf2 = mesh.get_face_inner_product(sig) # Anisotropic case: (nC, 3) for 2D and (nC, 6) for 3D -sig = np.random.rand(mesh.nC, 6) +sig = rng.random((mesh.nC, 6)) Me3 = mesh.get_edge_inner_product(sig) Mf3 = mesh.get_face_inner_product(sig) @@ -235,17 +237,17 @@ mesh = TensorMesh([h, h, h]) # Isotropic case: (nC, ) numpy array -sig = np.random.rand(mesh.nC) +sig = rng.random(mesh.nC) Me1_inv = mesh.get_edge_inner_product(sig, invert_matrix=True) Mf1_inv = mesh.get_face_inner_product(sig, invert_matrix=True) # Diagonal anisotropic: (nC, dim) numpy array -sig = np.random.rand(mesh.nC, mesh.dim) +sig = rng.random((mesh.nC, mesh.dim)) Me2_inv = mesh.get_edge_inner_product(sig, invert_matrix=True) Mf2_inv = mesh.get_face_inner_product(sig, invert_matrix=True) # Full anisotropic: (nC, 3) for 2D and (nC, 6) for 3D -sig = np.random.rand(mesh.nC, 6) +sig = rng.random((mesh.nC, 6)) Me3 = mesh.get_edge_inner_product(sig) Mf3 = mesh.get_face_inner_product(sig) diff --git a/tutorials/inner_products/4_advanced.py b/tutorials/inner_products/4_advanced.py index 8f97d1641..e384f5f24 100644 --- a/tutorials/inner_products/4_advanced.py +++ b/tutorials/inner_products/4_advanced.py @@ -21,6 +21,8 @@ import numpy as np import matplotlib.pyplot as plt +rng = np.random.default_rng(4321) + ##################################################### # Constitive Relations and Differential Operators @@ -74,8 +76,8 @@ # Make basic mesh h = np.ones(10) mesh = TensorMesh([h, h, h]) -sig = np.random.rand(mesh.nC) # isotropic -Sig = np.random.rand(mesh.nC, 6) # anisotropic +sig = rng.random(mesh.nC) # isotropic +Sig = rng.random((mesh.nC, 6)) # anisotropic # Inner product matricies Mc = sdiag(mesh.cell_volumes * sig) # Inner product matrix (centers) diff --git a/tutorials/pde/1_poisson.py b/tutorials/pde/1_poisson.py index 17dfc3095..d8665d1b7 100644 --- a/tutorials/pde/1_poisson.py +++ b/tutorials/pde/1_poisson.py @@ -92,7 +92,7 @@ from discretize import TensorMesh -from pymatsolver import SolverLU +from scipy.sparse.linalg import spsolve import matplotlib.pyplot as plt import numpy as np from discretize.utils import sdiag @@ -124,8 +124,7 @@ rho[kpos] = 1 # LU factorization and solve -AinvM = SolverLU(A) -phi = AinvM * rho +phi = spsolve(A, rho) # Compute electric fields E = Mf_inv * DIV.T * Mc * phi diff --git a/tutorials/pde/2_advection_diffusion.py b/tutorials/pde/2_advection_diffusion.py index 8783360db..7cc7bf326 100644 --- a/tutorials/pde/2_advection_diffusion.py +++ b/tutorials/pde/2_advection_diffusion.py @@ -164,7 +164,7 @@ # from discretize import TensorMesh -from pymatsolver import SolverLU +from scipy.sparse.linalg import splu import matplotlib.pyplot as plt import matplotlib as mpl import numpy as np @@ -224,7 +224,7 @@ B = I + dt * M s = Mc_inv * q -Binv = SolverLU(B) +Binv = splu(B) # Plot the vector field @@ -252,7 +252,7 @@ n = 3 for ii in range(300): - p = Binv * (p + s) + p = Binv.solve(p + s) if ii + 1 in (1, 25, 50, 100, 200, 300): ax[n] = fig.add_subplot(3, 3, n + 1) From 7a997c19a943e2cd2086e51c5f241363bcf2132f Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Sun, 1 Sep 2024 00:25:26 -0600 Subject: [PATCH 22/97] use seed in check derivative test calls --- tests/base/test_tensor_innerproduct_derivs.py | 12 +-- tests/base/test_tests.py | 6 +- tests/cyl/test_cyl_innerproducts.py | 88 ++++++++++++++----- tests/simplex/test_inner_products.py | 6 +- tests/tree/test_tree_innerproduct_derivs.py | 12 +-- 5 files changed, 87 insertions(+), 37 deletions(-) diff --git a/tests/base/test_tensor_innerproduct_derivs.py b/tests/base/test_tensor_innerproduct_derivs.py index 68d1cf2b8..e6e1ff21a 100644 --- a/tests/base/test_tensor_innerproduct_derivs.py +++ b/tests/base/test_tensor_innerproduct_derivs.py @@ -42,7 +42,7 @@ def fun(sig): fast, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False) + return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=452) def doTestEdge( self, h, rep, fast, meshType, invert_model=False, invert_matrix=False @@ -79,7 +79,9 @@ def fun(sig): fast, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False) + return discretize.tests.check_derivative( + fun, sig, num=5, plotIt=False, rng=4567 + ) def test_FaceIP_1D_float(self): self.assertTrue(self.doTestFace([10], 0, False, "Tensor")) @@ -360,7 +362,7 @@ def fun(sig): rep, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False) + return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=421) def doTestEdge(self, h, rep, meshType, invert_model=False, invert_matrix=False): if meshType == "Curv": @@ -391,7 +393,7 @@ def fun(sig): rep, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False) + return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=31) def test_FaceIP_2D_float(self): self.assertTrue(self.doTestFace([10, 4], 0, "Tensor")) @@ -500,7 +502,7 @@ def fun(sig): rep, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False) + return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=64) def test_EdgeIP_2D_float(self): self.assertTrue(self.doTestEdge([10, 4], 0, "Tensor")) diff --git a/tests/base/test_tests.py b/tests/base/test_tests.py index e8fc560c3..f14b0130b 100644 --- a/tests/base/test_tests.py +++ b/tests/base/test_tests.py @@ -74,14 +74,14 @@ def simplePass(x): return np.sin(x), sp.diags(np.cos(x)) rng = np.random.default_rng(5322) - check_derivative(simplePass, rng.standard_normal(5), plotIt=False, rng=rng) + check_derivative(simplePass, rng.standard_normal(5), plotIt=False, rng=42) def test_simpleFunction(self): def simpleFunction(x): return np.sin(x), lambda xi: np.cos(x) * xi rng = np.random.default_rng(5322) - check_derivative(simpleFunction, rng.standard_normal(5), plotIt=False, rng=rng) + check_derivative(simpleFunction, rng.standard_normal(5), plotIt=False, rng=23) def test_simpleFail(self): def simpleFail(x): @@ -89,7 +89,7 @@ def simpleFail(x): rng = np.random.default_rng(5322) with pytest.raises(AssertionError): - check_derivative(simpleFail, rng.standard_normal(5), plotIt=False, rng=rng) + check_derivative(simpleFail, rng.standard_normal(5), plotIt=False, rng=64) @pytest.mark.parametrize("test_type", ["mean", "min", "last", "all", "mean_at_least"]) diff --git a/tests/cyl/test_cyl_innerproducts.py b/tests/cyl/test_cyl_innerproducts.py index d74859485..b3e96f18e 100644 --- a/tests/cyl/test_cyl_innerproducts.py +++ b/tests/cyl/test_cyl_innerproducts.py @@ -478,7 +478,9 @@ def fun(x): print("Testing FaceInnerProduct Isotropic") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=532 + ) ) def test_FaceInnerProductIsotropicDerivInvProp(self): @@ -491,7 +493,9 @@ def fun(x): print("Testing FaceInnerProduct Isotropic InvProp") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=75 + ) ) def test_FaceInnerProductIsotropicDerivInvMat(self): @@ -504,7 +508,9 @@ def fun(x): print("Testing FaceInnerProduct Isotropic InvMat") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=1 + ) ) def test_FaceInnerProductIsotropicDerivInvPropInvMat(self): @@ -519,7 +525,9 @@ def fun(x): print("Testing FaceInnerProduct Isotropic InvProp InvMat") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=74 + ) ) def test_EdgeInnerProductIsotropicDeriv(self): @@ -530,7 +538,9 @@ def fun(x): print("Testing EdgeInnerProduct Isotropic") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=345 + ) ) def test_EdgeInnerProductIsotropicDerivInvProp(self): @@ -543,7 +553,9 @@ def fun(x): print("Testing EdgeInnerProduct Isotropic InvProp") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=643 + ) ) def test_EdgeInnerProductIsotropicDerivInvMat(self): @@ -556,7 +568,9 @@ def fun(x): print("Testing EdgeInnerProduct Isotropic InvMat") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=363 + ) ) def test_EdgeInnerProductIsotropicDerivInvPropInvMat(self): @@ -571,7 +585,9 @@ def fun(x): print("Testing EdgeInnerProduct Isotropic InvProp InvMat") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=773 + ) ) @@ -603,7 +619,9 @@ def fun(x): print("Testing FaceInnerProduct Anisotropic") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=2436 + ) ) def test_FaceInnerProductAnisotropicDerivInvProp(self): @@ -621,7 +639,9 @@ def fun(x): print("Testing FaceInnerProduct Anisotropic InvProp") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=634 + ) ) def test_FaceInnerProductAnisotropicDerivInvMat(self): @@ -639,7 +659,9 @@ def fun(x): print("Testing FaceInnerProduct Anisotropic InvMat") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=222 + ) ) def test_FaceInnerProductAnisotropicDerivInvPropInvMat(self): @@ -661,7 +683,9 @@ def fun(x): print("Testing FaceInnerProduct Anisotropic InvProp InvMat") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=654 + ) ) def test_EdgeInnerProductAnisotropicDeriv(self): @@ -679,7 +703,9 @@ def fun(x): print("Testing EdgeInnerProduct Anisotropic") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=7754 + ) ) def test_EdgeInnerProductAnisotropicDerivInvProp(self): @@ -697,7 +723,9 @@ def fun(x): print("Testing EdgeInnerProduct Anisotropic InvProp") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=1164 + ) ) def test_EdgeInnerProductAnisotropicDerivInvMat(self): @@ -715,7 +743,9 @@ def fun(x): print("Testing EdgeInnerProduct Anisotropic InvMat") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=643 + ) ) def test_EdgeInnerProductAnisotropicDerivInvPropInvMat(self): @@ -737,7 +767,9 @@ def fun(x): print("Testing EdgeInnerProduct Anisotropic InvProp InvMat") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=8654 + ) ) @@ -761,7 +793,9 @@ def fun(x): print("Testing FaceInnerProduct Isotropic (Face Properties)") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=234 + ) ) def test_FaceInnerProductIsotropicDerivInvProp(self): @@ -774,7 +808,9 @@ def fun(x): print("Testing FaceInnerProduct Isotropic InvProp (Face Properties)") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=7543 + ) ) def test_FaceInnerProductIsotropicDerivInvMat(self): @@ -787,7 +823,9 @@ def fun(x): print("Testing FaceInnerProduct Isotropic InvMat (Face Properties)") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=2745725 + ) ) def test_EdgeInnerProductIsotropicDeriv(self): @@ -798,7 +836,9 @@ def fun(x): print("Testing EdgeInnerProduct Isotropic (Face Properties)") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=6654 + ) ) def test_EdgeInnerProductIsotropicDerivInvProp(self): @@ -811,7 +851,9 @@ def fun(x): print("Testing EdgeInnerProduct Isotropic InvProp (Face Properties)") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=4564 + ) ) def test_EdgeInnerProductIsotropicDerivInvMat(self): @@ -824,5 +866,7 @@ def fun(x): print("Testing EdgeInnerProduct Isotropic InvMat (Face Properties)") return self.assertTrue( - tests.check_derivative(fun, self.x0, num=7, tolerance=TOLD, plotIt=False) + tests.check_derivative( + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=2355 + ) ) diff --git a/tests/simplex/test_inner_products.py b/tests/simplex/test_inner_products.py index 83f6875a4..676faa027 100644 --- a/tests/simplex/test_inner_products.py +++ b/tests/simplex/test_inner_products.py @@ -343,7 +343,9 @@ def fun(sig): return M * v, Md(v) print("Face", rep) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False) + return discretize.tests.check_derivative( + fun, sig, num=5, plotIt=False, rng=5352 + ) def doTestEdge(self, h, rep): nodes, simplices = example_simplex_mesh(h) @@ -357,7 +359,7 @@ def fun(sig): return M * v, Md(v) print("Edge", rep) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False) + return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=532) def test_FaceIP_2D_float(self): self.assertTrue(self.doTestFace([10, 4], 0)) diff --git a/tests/tree/test_tree_innerproduct_derivs.py b/tests/tree/test_tree_innerproduct_derivs.py index 4bd060cea..708680c7f 100644 --- a/tests/tree/test_tree_innerproduct_derivs.py +++ b/tests/tree/test_tree_innerproduct_derivs.py @@ -40,7 +40,9 @@ def fun(sig): fast, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=rng) + return discretize.tests.check_derivative( + fun, sig, num=5, plotIt=False, rng=4421 + ) def doTestEdge( self, h, rep, fast, meshType, invert_model=False, invert_matrix=False @@ -76,7 +78,7 @@ def fun(sig): fast, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=rng) + return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=643) def test_FaceIP_2D_float_Tree(self): self.assertTrue(self.doTestFace([8, 8], 0, False, "Tree")) @@ -193,7 +195,7 @@ def fun(sig): # fast, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=rng) + return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=677) def doTestEdge(self, h, rep, meshType, invert_model=False, invert_matrix=False): if meshType == "Curv": @@ -226,7 +228,7 @@ def fun(sig): # fast, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=rng) + return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=543) def test_FaceIP_2D_float_fast_Tree(self): self.assertTrue(self.doTestFace([8, 8], 0, "Tree")) @@ -285,7 +287,7 @@ def fun(sig): # fast, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=rng) + return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=23) def test_EdgeIP_2D_float_fast_Tree(self): self.assertTrue(self.doTestEdge([8, 8], 0, "Tree")) From 40df4b73f5a922703029d167809d5f36d5ec35a6 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Sun, 1 Sep 2024 00:30:44 -0600 Subject: [PATCH 23/97] use seed in adjoint tests --- tests/base/test_tests.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/base/test_tests.py b/tests/base/test_tests.py index f14b0130b..9a4475ca2 100644 --- a/tests/base/test_tests.py +++ b/tests/base/test_tests.py @@ -26,6 +26,7 @@ def test_defaults(self, capsys): mesh1.n_cells, mesh2.n_cells, assert_error=False, + rng=41, ) out2, _ = capsys.readouterr() assert out1 @@ -38,6 +39,7 @@ def test_defaults(self, capsys): lambda v: P.T * v, mesh1.n_cells, mesh2.n_cells, + rng=42, ) def test_different_shape(self): @@ -53,7 +55,13 @@ def adj(inp): out = np.expand_dims(inp, 1) return np.tile(out, nt) - assert_isadjoint(fwd, adj, shape_u=(4, nt), shape_v=(4,)) + assert_isadjoint( + fwd, + adj, + shape_u=(4, nt), + shape_v=(4,), + rng=42, + ) def test_complex_clinear(self): # The complex conjugate is self-adjoint, real-linear. @@ -65,6 +73,7 @@ def test_complex_clinear(self): complex_u=True, complex_v=True, clinear=False, + rng=112, ) From 2702e2d55526f0472bb08f150a80c5567bc73065 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Sun, 1 Sep 2024 00:35:50 -0600 Subject: [PATCH 24/97] incorrect expansion --- discretize/utils/mesh_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discretize/utils/mesh_utils.py b/discretize/utils/mesh_utils.py index 22ea4fac7..7aeeaf0d8 100644 --- a/discretize/utils/mesh_utils.py +++ b/discretize/utils/mesh_utils.py @@ -75,7 +75,7 @@ def random_model(shape, seed=None, anisotropy=None, its=100, bounds=None): if type(shape) in num_types: shape = (shape,) # make it a tuple for consistency - mr = rng.random(*shape) + mr = rng.random(shape) if anisotropy is None: if len(shape) == 1: smth = np.array([1, 10.0, 1], dtype=float) From 6c862848c2c889388d9cfa2bf1ccf1075b54ca1f Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Sun, 1 Sep 2024 00:35:57 -0600 Subject: [PATCH 25/97] correct import --- examples/plot_dc_resistivity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/plot_dc_resistivity.py b/examples/plot_dc_resistivity.py index 4f1cd8eac..8bfdf76f0 100644 --- a/examples/plot_dc_resistivity.py +++ b/examples/plot_dc_resistivity.py @@ -8,7 +8,7 @@ import discretize import numpy as np import matplotlib.pyplot as plt -from scipy.solver.linalg import spsolve +from scipy.sparse.linalg import spsolve def run(plotIt=True): From 567bce0247b45c6b65980bbf615b21a29c900094 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Sun, 1 Sep 2024 00:39:24 -0600 Subject: [PATCH 26/97] remove pymatsolver from testing and doc requirements --- .ci/environment_test.yml | 1 - pyproject.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/.ci/environment_test.yml b/.ci/environment_test.yml index 91748c856..bdff1b8ce 100644 --- a/.ci/environment_test.yml +++ b/.ci/environment_test.yml @@ -16,7 +16,6 @@ dependencies: - numpydoc>=1.5 - jupyter - graphviz - - pymatsolver>=0.1.2 - pillow - pooch # testing diff --git a/pyproject.toml b/pyproject.toml index ca54b91cd..3d777ef86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,6 @@ doc = [ "numpydoc>=1.5", "jupyter", "graphviz", - "pymatsolver>=0.1.2", "pillow", "pooch", "discretize[all]", From 7ffd4e4a5050ce7cc8c136d8868aaaaf5df20706 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Sun, 1 Sep 2024 12:30:28 -0600 Subject: [PATCH 27/97] add some spaces to environment_test --- .ci/environment_test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.ci/environment_test.yml b/.ci/environment_test.yml index bdff1b8ce..90568a70b 100644 --- a/.ci/environment_test.yml +++ b/.ci/environment_test.yml @@ -4,11 +4,13 @@ channels: dependencies: - numpy>=1.22.4 - scipy>=1.8 + # optionals - vtk>=6 - pyvista - omf - matplotlib + # documentation - sphinx - pydata-sphinx-theme==0.13.3 @@ -18,6 +20,7 @@ dependencies: - graphviz - pillow - pooch + # testing - sympy - pytest From 1fe51228ae4799972839154018be477fcb790686 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Sun, 1 Sep 2024 12:38:45 -0600 Subject: [PATCH 28/97] minor syntax errors in old docs --- docs/release/0.7.1-notes.rst | 2 +- docs/release/0.8.1-notes.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/release/0.7.1-notes.rst b/docs/release/0.7.1-notes.rst index 1eeab2052..292e52d77 100644 --- a/docs/release/0.7.1-notes.rst +++ b/docs/release/0.7.1-notes.rst @@ -42,4 +42,4 @@ Pull requests * `#256 `__: Update mpl_mod.py * `#258 `__: Numpy docstrings api review * `#262 `__: Fix wrong colour for fullspaces - again / -`#264 `__: patch for fullspace slicer colorscales +* `#264 `__: patch for fullspace slicer colorscales diff --git a/docs/release/0.8.1-notes.rst b/docs/release/0.8.1-notes.rst index e3536e2f3..a2ac19517 100644 --- a/docs/release/0.8.1-notes.rst +++ b/docs/release/0.8.1-notes.rst @@ -54,5 +54,5 @@ Pull requests * `#284 `__: Improve load time * `#285 `__: zeros_outside * `#286 `__: Cell node tree -* `#288 `__: Allow np.int_ +* `#288 `__: Allow ``np.int_`` * `#289 `__: 0.8.1 Release From 88b5d0a12c3cdb74895bd5307a91d50356007069 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Sun, 1 Sep 2024 12:39:43 -0600 Subject: [PATCH 29/97] fix invalid inline literal --- docs/release/0.10.0-notes.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/release/0.10.0-notes.rst b/docs/release/0.10.0-notes.rst index 7cf5c73f7..05e0e71ab 100644 --- a/docs/release/0.10.0-notes.rst +++ b/docs/release/0.10.0-notes.rst @@ -22,7 +22,7 @@ Build system ``discretize`` now uses a ``pyproject.toml`` file with a ``meson-python`` backend to build the compiled external modules (used for the ``TreeMesh``, ``SimplexMesh``, and interpolation functions. Moving away from a ``setup.py`` file allows us to reliably control the build environment -seperate from the install environment, as the build requirements are not the same as the runtime +separate from the install environment, as the build requirements are not the same as the runtime requirements. We will also begin distributing many more pre-compiled wheels on pypi for Windows, MacOS, and Linux @@ -30,11 +30,11 @@ systems from python 3.8 to 3.12. Our goal is to provide a pre-compiled wheel for scipy provides wheels for. Tensor Mesh ------------- -You can now directly index a ``TensorMesh``, and it will then return a ``TensorCell`` object. -This functionality mimics what is currently available in ``TreeMesh``s. +----------- +You can now directly index a ``TensorMesh`` and return a ``TensorCell`` object. +This functionality mimics what is currently available in ``TreeMesh``. -``Tensor Mesh`` also has a ``cell_nodes`` property that list the indices of each node of every +``TensorMesh`` also has a ``cell_nodes`` property that list the indices of each node of every cell (again similar to the ``TreeMesh``). Face Properties From 9ffbf260f76bb2a27357b00310f294a5c71cc3e6 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Sun, 1 Sep 2024 13:01:51 -0600 Subject: [PATCH 30/97] update docstrings --- discretize/tests.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/discretize/tests.py b/discretize/tests.py index 4848371e4..ea4340a29 100644 --- a/discretize/tests.py +++ b/discretize/tests.py @@ -227,7 +227,7 @@ class for the given operator. Within the test class, the user sets the parameter Name the convergence test meshTypes : list of str List denoting the mesh types on which the convergence will be tested. - List entries must of the list {'uniformTensorMesh', 'randomTensorMesh', + List entries must be of the list {'uniformTensorMesh', 'randomTensorMesh', 'uniformCylindricalMesh', 'randomCylindricalMesh', 'uniformTree', 'randomTree', 'uniformCurv', 'rotateCurv', 'sphereCurv'} expectedOrders : float or list of float (default = 2.0) @@ -242,8 +242,9 @@ class for the given operator. Within the test class, the user sets the parameter meshDimension : int Mesh dimension. Must be 1, 2 or 3 rng : numpy.random.Generator, int, optional - The random number generator to use for the adjoint test, if an integer or None - it used to seed a new `numpy.random.default_rng`. Only used if mesh_type is 'random' + If ``random`` is in `mesh_type`, this is the random number generator + used generate the random meshes, if an ``int`` or ``None``, it used to seed + a new `numpy.random.default_rng`. Notes ----- From 83276ddcf992528e9965ffd58d0b16ba244c7553 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Sun, 1 Sep 2024 15:05:48 -0600 Subject: [PATCH 31/97] Simplify vmin and vmax logic --- discretize/mixins/mpl_mod.py | 41 +++++++++++------------------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/discretize/mixins/mpl_mod.py b/discretize/mixins/mpl_mod.py index c331a6d4a..e7350631a 100644 --- a/discretize/mixins/mpl_mod.py +++ b/discretize/mixins/mpl_mod.py @@ -2551,35 +2551,18 @@ def __init__( else: aspect3 = 1.0 / aspect2 - # set color limits if clim is None (and norm doesn't have vmin, vmax). - if clim is None: - if "norm" in self.pc_props: - vmin = self.pc_props["norm"].vmin - vmax = self.pc_props["norm"].vmax - else: - vmin = vmax = None - clim = [ - np.nanmin(self.v) if vmin is None else vmin, - np.nanmax(self.v) if vmax is None else vmax, - ] - # In the case of a homogeneous fullspace provide a small range to - # avoid problems with colorbar and the three subplots. - if clim[0] == clim[1]: - clim[0] *= 0.99 - clim[1] *= 1.01 - - # Edge-case. If the entire vector is zero, `clim` is still - # [0, 0] after the above check, hence `clim[0]==clim[1]`. - if clim[0] == clim[1]: - clim = [-0.1, 0.1] - - # ensure vmin/vmax of the norm is consistent with clim - if "norm" in self.pc_props: - self.pc_props["norm"].vmin = clim[0] - self.pc_props["norm"].vmax = clim[1] - else: - self.pc_props["vmin"] = clim[0] - self.pc_props["vmax"] = clim[1] + # Ensure a consistent color normalization for the three plots. + if "norm" not in self.pc_props: + # Create a default normalizer + self.pc_props["norm"] = Normalize() + norm = self.pc_props["norm"] + if clim is not None: + # set the norm's min and max consistently. + norm.vmin, norm.vmax = clim + # Autoscales None values for norm.vmin and norm.vmax. + # self.v is a nan masked array, so this + # is safe. + norm.autoscale_None(self.v) # 2. Start populating figure From 1ebe3521643b74cbf94d7dea8c2e039bb7c89d8a Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Sun, 1 Sep 2024 15:12:09 -0600 Subject: [PATCH 32/97] import normalize --- discretize/mixins/mpl_mod.py | 1 + 1 file changed, 1 insertion(+) diff --git a/discretize/mixins/mpl_mod.py b/discretize/mixins/mpl_mod.py index 163984f60..feccf596d 100644 --- a/discretize/mixins/mpl_mod.py +++ b/discretize/mixins/mpl_mod.py @@ -2458,6 +2458,7 @@ def __init__( """Initialize interactive figure.""" _, plt = load_matplotlib() from matplotlib.widgets import Slider # Lazy loaded + from matplotlib.colors import Normalize # 0. Some checks, not very extensive if "pcolorOpts" in kwargs: From fd1d2b147c72b9b2e181c20813e31bdfee003858 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Sun, 1 Sep 2024 16:34:28 -0600 Subject: [PATCH 33/97] assign clim to vmin, vmax if not None This behavior will cause matplotlib to issue an error if both clim and norm are set. --- discretize/mixins/mpl_mod.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/discretize/mixins/mpl_mod.py b/discretize/mixins/mpl_mod.py index feccf596d..cfd868e00 100644 --- a/discretize/mixins/mpl_mod.py +++ b/discretize/mixins/mpl_mod.py @@ -2553,17 +2553,22 @@ def __init__( else: aspect3 = 1.0 / aspect2 + if clim is not None: + vmin, vmax = clim + self.pc_props["vmin"] = vmin + self.pc_props["vmax"] = vmax + # Ensure a consistent color normalization for the three plots. - if "norm" not in self.pc_props: + if norm := self.pc_props.get("norm", None) is None: # Create a default normalizer - self.pc_props["norm"] = Normalize() - norm = self.pc_props["norm"] - if clim is not None: - # set the norm's min and max consistently. - norm.vmin, norm.vmax = clim - # Autoscales None values for norm.vmin and norm.vmax. - # self.v is a nan masked array, so this - # is safe. + norm = Normalize() + if clim is not None: + norm.vmin = self.pc_props.pop("vmin") + norm.vmax = self.pc_props.pop("vmax") + self.pc_props["norm"] = norm + + # Auto scales None values for norm.vmin and norm.vmax. + # self.v is a nan masked array, so this is safe. norm.autoscale_None(self.v) # 2. Start populating figure From f89e5ff78ea048872d6f3d7576c5222ef055cf0e Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Sun, 1 Sep 2024 22:58:15 -0600 Subject: [PATCH 34/97] Just mimic matplotlib's error and add tests. --- discretize/mixins/mpl_mod.py | 16 +++++----- tests/base/test_slicer.py | 57 ++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 tests/base/test_slicer.py diff --git a/discretize/mixins/mpl_mod.py b/discretize/mixins/mpl_mod.py index cfd868e00..f770547b7 100644 --- a/discretize/mixins/mpl_mod.py +++ b/discretize/mixins/mpl_mod.py @@ -2553,19 +2553,19 @@ def __init__( else: aspect3 = 1.0 / aspect2 - if clim is not None: - vmin, vmax = clim - self.pc_props["vmin"] = vmin - self.pc_props["vmax"] = vmax - # Ensure a consistent color normalization for the three plots. - if norm := self.pc_props.get("norm", None) is None: + if (norm := self.pc_props.get("norm", None)) is None: # Create a default normalizer norm = Normalize() if clim is not None: - norm.vmin = self.pc_props.pop("vmin") - norm.vmax = self.pc_props.pop("vmax") + norm.vmin, norm.vmax = clim self.pc_props["norm"] = norm + else: + if clim is not None: + raise ValueError( + "Passing a Normalize instance simultaneously with clim is not supported. " + "Please pass vmin/vmax directly to the norm when creating it." + ) # Auto scales None values for norm.vmin and norm.vmax. # self.v is a nan masked array, so this is safe. diff --git a/tests/base/test_slicer.py b/tests/base/test_slicer.py new file mode 100644 index 000000000..22f6983f4 --- /dev/null +++ b/tests/base/test_slicer.py @@ -0,0 +1,57 @@ +import numpy as np +import pytest +from matplotlib.colors import Normalize +import discretize +from discretize.mixins.mpl_mod import Slicer + + +def test_slicer_errors(): + mesh = discretize.TensorMesh([10, 10, 10]) + model = np.ones(mesh.shape_cells) + with pytest.raises( + ValueError, + match="(Passing a Normalize instance simultaneously with clim is not supported).*", + ): + Slicer(mesh, model, clim=[0, 1], pcolor_opts={"norm": Normalize()}) + + +def test_slicer_default_clim(): + mesh = discretize.TensorMesh([10, 10, 10]) + model = np.ones(mesh.shape_cells) + model[0, 0, 0] = 0.5 + slc = Slicer(mesh, model) + norm = slc.pc_props["norm"] + assert (norm.vmin, norm.vmax) == (0.5, 1.0) + + +def test_slicer_set_clim(): + mesh = discretize.TensorMesh([10, 10, 10]) + model = np.ones(mesh.shape_cells) + slc = Slicer(mesh, model, clim=(0.5, 1.5)) + norm = slc.pc_props["norm"] + assert (norm.vmin, norm.vmax) == (0.5, 1.5) + + +def test_slicer_set_norm(): + mesh = discretize.TensorMesh([10, 10, 10]) + model = np.ones(mesh.shape_cells) + norm = Normalize(0.5, 1.5) + slc = Slicer(mesh, model, pcolor_opts={"norm": norm}) + norm = slc.pc_props["norm"] + assert (norm.vmin, norm.vmax) == (0.5, 1.5) + + +def test_slicer_ones_clim(): + mesh = discretize.TensorMesh([10, 10, 10]) + model = np.ones(mesh.shape_cells) + slc = Slicer(mesh, model) + norm = slc.pc_props["norm"] + assert (norm.vmin, norm.vmax) == (0.9, 1.1) + + +def test_slicer_zeros_clim(): + mesh = discretize.TensorMesh([10, 10, 10]) + model = np.zeros(mesh.shape_cells) + slc = Slicer(mesh, model) + norm = slc.pc_props["norm"] + assert (norm.vmin, norm.vmax) == (-0.1, 0.1) From 29f694c95af168286f8846d2898450c32ab4da74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dieter=20Werthm=C3=BCller?= Date: Mon, 2 Sep 2024 10:34:34 +0200 Subject: [PATCH 35/97] Add comment for jupyter lab --- examples/plot_slicer_demo.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/plot_slicer_demo.py b/examples/plot_slicer_demo.py index 2573e72fd..38dfb3055 100644 --- a/examples/plot_slicer_demo.py +++ b/examples/plot_slicer_demo.py @@ -9,10 +9,13 @@ Using the inversion result from the example notebook `plot_laguna_del_maule_inversion.ipynb `_ -In the notebook, you have to use :code:`%matplotlib notebook`. +You have to use :code:`%matplotlib notebook` in Jupyter Notebook, and +:code:`%matplotlib widget` in Jupyter Lab (latter requires the package +``ipympl``). """ # %matplotlib notebook +# %matplotlib widget import discretize import numpy as np import matplotlib.pyplot as plt From d44f849788f429f01f68ac997307397a77908840 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Mon, 2 Sep 2024 09:57:07 -0600 Subject: [PATCH 36/97] Fix for plot slicer --- discretize/mixins/mpl_mod.py | 9 +++------ examples/plot_slicer_demo.py | 7 +++++++ tests/base/test_slicer.py | 23 +++++++++++------------ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/discretize/mixins/mpl_mod.py b/discretize/mixins/mpl_mod.py index f770547b7..6dc62aa75 100644 --- a/discretize/mixins/mpl_mod.py +++ b/discretize/mixins/mpl_mod.py @@ -792,9 +792,6 @@ def plot_3d_slicer( # Connect figure to scrolling fig.canvas.mpl_connect("scroll_event", tracker.onscroll) - # Show figure - plt.show() - # TensorMesh plotting def __plot_grid_tensor( self, @@ -2513,7 +2510,7 @@ def __init__( # Store data in self as (nx, ny, nz) self.v = mesh.reshape(v.reshape((mesh.nC, -1), order="F"), "CC", "CC", "M") - self.v = np.ma.masked_where(np.isnan(self.v), self.v) + self.v = np.ma.masked_array(self.v, np.isnan(self.v)) # Store relevant information from mesh in self self.x = mesh.nodes_x # x-node locations @@ -2568,8 +2565,7 @@ def __init__( ) # Auto scales None values for norm.vmin and norm.vmax. - # self.v is a nan masked array, so this is safe. - norm.autoscale_None(self.v) + norm.autoscale_None(self.v[~self.v.mask].reshape(-1, order="A")) # 2. Start populating figure @@ -2662,6 +2658,7 @@ def __init__( # Remove transparent value if isinstance(transparent, str) and transparent.lower() == "slider": + clim = (norm.vmin, norm.vmax) # Sliders self.ax_smin = plt.axes([0.7, 0.11, 0.15, 0.03]) self.ax_smax = plt.axes([0.7, 0.15, 0.15, 0.03]) diff --git a/examples/plot_slicer_demo.py b/examples/plot_slicer_demo.py index 38dfb3055..bcd5f19a9 100644 --- a/examples/plot_slicer_demo.py +++ b/examples/plot_slicer_demo.py @@ -50,6 +50,7 @@ # ^^^^^^^^^^^^^^^^^^^ mesh.plot_3d_slicer(Lpout) +plt.show() ############################################################################### # 1.2 Create a function to improve plots, labeling after creation @@ -104,6 +105,7 @@ def beautify(title, fig=None): # mesh.plot_3d_slicer(Lpout) beautify("mesh.plot_3d_slicer(Lpout)") +plt.show() ############################################################################### # 1.3 Set `xslice`, `yslice`, and `zslice`; transparent region @@ -118,6 +120,7 @@ def beautify(title, fig=None): "mesh.plot_3d_slicer(" "\nLpout, 370000, 6002500, -2500, transparent=[[-0.02, 0.1]])" ) +plt.show() ############################################################################### # 1.4 Set `clim`, use `pcolor_opts` to show grid lines @@ -130,6 +133,7 @@ def beautify(title, fig=None): "mesh.plot_3d_slicer(\nLpout, clim=[-0.4, 0.2], " "pcolor_opts={'edgecolor': 'k', 'linewidth': 0.1})" ) +plt.show() ############################################################################### # 1.5 Use `pcolor_opts` to set `SymLogNorm`, and another `cmap` @@ -142,6 +146,7 @@ def beautify(title, fig=None): "mesh.plot_3d_slicer(Lpout," "\npcolor_opts={'norm': SymLogNorm(linthresh=0.01),'cmap': 'RdBu_r'})`" ) +plt.show() ############################################################################### # 1.6 Use :code:`aspect` and :code:`grid` @@ -159,6 +164,7 @@ def beautify(title, fig=None): mesh.plot_3d_slicer(Lpout, aspect=["equal", 1.5], grid=[4, 4, 3]) beautify("mesh.plot_3d_slicer(Lpout, aspect=['equal', 1.5], grid=[4, 4, 3])") +plt.show() ############################################################################### # 1.7 Transparency-slider @@ -169,6 +175,7 @@ def beautify(title, fig=None): mesh.plot_3d_slicer(Lpout, transparent="slider") beautify("mesh.plot_3d_slicer(Lpout, transparent='slider')") +plt.show() ############################################################################### diff --git a/tests/base/test_slicer.py b/tests/base/test_slicer.py index 22f6983f4..303761be8 100644 --- a/tests/base/test_slicer.py +++ b/tests/base/test_slicer.py @@ -5,8 +5,12 @@ from discretize.mixins.mpl_mod import Slicer -def test_slicer_errors(): - mesh = discretize.TensorMesh([10, 10, 10]) +@pytest.fixture() +def mesh(): + return discretize.TensorMesh([9, 10, 11]) + + +def test_slicer_errors(mesh): model = np.ones(mesh.shape_cells) with pytest.raises( ValueError, @@ -15,8 +19,7 @@ def test_slicer_errors(): Slicer(mesh, model, clim=[0, 1], pcolor_opts={"norm": Normalize()}) -def test_slicer_default_clim(): - mesh = discretize.TensorMesh([10, 10, 10]) +def test_slicer_default_clim(mesh): model = np.ones(mesh.shape_cells) model[0, 0, 0] = 0.5 slc = Slicer(mesh, model) @@ -24,16 +27,14 @@ def test_slicer_default_clim(): assert (norm.vmin, norm.vmax) == (0.5, 1.0) -def test_slicer_set_clim(): - mesh = discretize.TensorMesh([10, 10, 10]) +def test_slicer_set_clim(mesh): model = np.ones(mesh.shape_cells) slc = Slicer(mesh, model, clim=(0.5, 1.5)) norm = slc.pc_props["norm"] assert (norm.vmin, norm.vmax) == (0.5, 1.5) -def test_slicer_set_norm(): - mesh = discretize.TensorMesh([10, 10, 10]) +def test_slicer_set_norm(mesh): model = np.ones(mesh.shape_cells) norm = Normalize(0.5, 1.5) slc = Slicer(mesh, model, pcolor_opts={"norm": norm}) @@ -41,16 +42,14 @@ def test_slicer_set_norm(): assert (norm.vmin, norm.vmax) == (0.5, 1.5) -def test_slicer_ones_clim(): - mesh = discretize.TensorMesh([10, 10, 10]) +def test_slicer_ones_clim(mesh): model = np.ones(mesh.shape_cells) slc = Slicer(mesh, model) norm = slc.pc_props["norm"] assert (norm.vmin, norm.vmax) == (0.9, 1.1) -def test_slicer_zeros_clim(): - mesh = discretize.TensorMesh([10, 10, 10]) +def test_slicer_zeros_clim(mesh): model = np.zeros(mesh.shape_cells) slc = Slicer(mesh, model) norm = slc.pc_props["norm"] From abbd96b6acf2144694ef6a46b2d24afe93c7089c Mon Sep 17 00:00:00 2001 From: Santiago Soler Date: Tue, 3 Sep 2024 14:48:53 -0700 Subject: [PATCH 37/97] Add version switcher to discretize docs Configure PyData Sphinx Theme's version switcher and add a new `docs/_static/versions.json` file where versions and their respective urls are listed. --- docs/_static/versions.json | 13 +++++++++++++ docs/conf.py | 16 +++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 docs/_static/versions.json diff --git a/docs/_static/versions.json b/docs/_static/versions.json new file mode 100644 index 000000000..b7345d354 --- /dev/null +++ b/docs/_static/versions.json @@ -0,0 +1,13 @@ +[ + { + "version": "main", + "url": "https://discretize.simpeg.xyz/en/main/" + }, + { + "name": "v0.10.0 (latest)", + "version": "0.10.0", + "url": "https://discretize.simpeg.xyz/en/0.10.0/", + "preferred": true + } +] + diff --git a/docs/conf.py b/docs/conf.py index ced3d2480..8b0ed498b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,6 +15,7 @@ import sys from pathlib import Path from datetime import datetime +from packaging.version import parse import discretize from sphinx_gallery.sorting import FileNameSortKey import shutil @@ -246,6 +247,13 @@ def linkcode_resolve(domain, info): dict(name="Contact", url="https://mattermost.softwareunderground.org/simpeg"), ] +# Define discretize version for the version switcher +discretize_version = parse(discretize.__version__) +if discretize_version.is_devrelease: + switcher_version = "main" +else: + switcher_version = discretize_version.public + # Use Pydata Sphinx theme html_theme = "pydata_sphinx_theme" @@ -279,7 +287,13 @@ def linkcode_resolve(domain, info): "use_edit_page_button": False, "collapse_navigation": True, "navbar_align": "left", # make elements closer to logo on the left - "navbar_end": ["theme-switcher", "navbar-icon-links"], + "navbar_end": ["version-switcher", "theme-switcher", "navbar-icon-links"], + # Configure version switcher (remember to add it to the "navbar_end") + "switcher": { + "version_match": switcher_version, + "json_url": "https://discretize.simpeg.xyz/en/latest/_static/versions.json", + }, + "show_version_warning_banner": True, } html_logo = "images/discretize-logo.png" From cb802b5732ad73aa133d6e669d6453cd3dfef42f Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Tue, 3 Sep 2024 16:36:28 -0600 Subject: [PATCH 38/97] make use of new cell.min_node and cell.max_node functions --- discretize/_extensions/tree.h | 9 +++++++++ discretize/_extensions/tree.pxd | 5 ++++- discretize/_extensions/tree_ext.pyx | 21 ++++++++++++--------- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/discretize/_extensions/tree.h b/discretize/_extensions/tree.h index aea1e6e22..21c4d7b8d 100644 --- a/discretize/_extensions/tree.h +++ b/discretize/_extensions/tree.h @@ -82,6 +82,9 @@ class Edge{ Edge *parents[2]; Edge(); Edge(Node& p1, Node&p2); + double operator[](int_t index){ + return location[index]; + }; }; class Face{ @@ -98,6 +101,9 @@ class Face{ Face *parent; Face(); Face(Node& p1, Node& p2, Node& p3, Node& p4); + double operator[](int_t index){ + return location[index]; + }; }; class Cell{ @@ -111,6 +117,9 @@ class Cell{ int_t location_ind[3], key, level, max_level; long long int index; // non root parents will have a -1 value double location[3]; + double operator[](int_t index){ + return location[index]; + }; double volume; Cell(); diff --git a/discretize/_extensions/tree.pxd b/discretize/_extensions/tree.pxd index 57d600636..7b428fbc8 100644 --- a/discretize/_extensions/tree.pxd +++ b/discretize/_extensions/tree.pxd @@ -15,7 +15,7 @@ cdef extern from "tree.h": Node *parents[4] Node() Node(int_t, int_t, int_t, double, double, double) - int_t operator[](int_t) + double operator[](int_t) cdef cppclass Edge: int_t location_ind[3] @@ -29,6 +29,7 @@ cdef extern from "tree.h": Edge *parents[2] Edge() Edge(Node& p1, Node& p2) + double operator[](int_t) cdef cppclass Face: int_t location_ind[3] @@ -43,6 +44,7 @@ cdef extern from "tree.h": Face *parent Face() Face(Node& p1, Node& p2, Node& p3, Node& p4) + double operator[](int_t) ctypedef map[int_t, Node *] node_map_t ctypedef map[int_t, Edge *] edge_map_t @@ -64,6 +66,7 @@ cdef extern from "tree.h": inline bool is_leaf() inline Node* min_node() inline Node* max_node() + double operator[](int_t) cdef cppclass PyWrapper: PyWrapper() diff --git a/discretize/_extensions/tree_ext.pyx b/discretize/_extensions/tree_ext.pyx index 1a5512350..988418e35 100644 --- a/discretize/_extensions/tree_ext.pyx +++ b/discretize/_extensions/tree_ext.pyx @@ -55,18 +55,21 @@ cdef class TreeCell: cdef void _set(self, c_Cell* cell): self._cell = cell self._dim = cell.n_dim - self._x = cell.location[0] - self._x0 = cell.points[0].location[0] + cdef: + Node *min_n = cell.min_node() + Node *max_n = cell.max_node() + self._x = self._cell.location[0] + self._x0 = min_n.location[0] - self._y = cell.location[1] - self._y0 = cell.points[0].location[1] + self._y = self._cell.location[1] + self._y0 = min_n.location[1] - self._wx = cell.points[3].location[0] - self._x0 - self._wy = cell.points[3].location[1] - self._y0 + self._wx = max_n.location[0] - self._x0 + self._wy = max_n.location[1] - self._y0 if(self._dim > 2): - self._z = cell.location[2] - self._z0 = cell.points[0].location[2] - self._wz = cell.points[7].location[2] - self._z0 + self._z = self._cell.location[2] + self._z0 = min_n.location[2] + self._wz = max_n.location[2] - self._z0 @property def nodes(self): From cf9254a12c03c39f2c4ddfd672bcda6b86ce3cad Mon Sep 17 00:00:00 2001 From: Santiago Soler Date: Tue, 3 Sep 2024 16:12:27 -0700 Subject: [PATCH 39/97] Make deployment stage to depend on DocBuild --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ee9994deb..73cb8bfba 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -52,6 +52,7 @@ stages: dependsOn: - BuildWheels - BuildSource + - DocBuild condition: and(succeeded(), startsWith(variables['build.sourceBranch'], 'refs/tags/')) jobs: - template: .ci/azure/deploy.yml From af4f29a616346d79ac3716a0485c24c9348e4822 Mon Sep 17 00:00:00 2001 From: Santiago Soler Date: Tue, 3 Sep 2024 16:13:40 -0700 Subject: [PATCH 40/97] Store discretize version in a variable Store discretize version in an Azure variable after building the docs. --- .ci/azure/docs.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.ci/azure/docs.yml b/.ci/azure/docs.yml index 6fe0dac83..6611c2335 100644 --- a/.ci/azure/docs.yml +++ b/.ci/azure/docs.yml @@ -1,5 +1,5 @@ jobs: -- job: +- job: BuildDocs displayName: "Build Documentation" pool: vmImage: ubuntu-latest @@ -24,6 +24,13 @@ jobs: make -C docs html displayName: 'Building HTML' + - bash: | + source activate discretize-test + version=$(python -c "import discretize; print(discretize.__version__)") + echo "##vso[task.setvariable variable=version;isOutput=true]$version" + displayName: 'Store discretize version in variable' + name: discretizeVersion + - bash: | source activate discretize-test make -C docs linkcheck @@ -33,4 +40,4 @@ jobs: inputs: targetPath: 'docs/_build/html' artifact: 'html_docs' - parallel: true \ No newline at end of file + parallel: true From a12680ec7e8b8fec701d211482b289ce0e9db5fb Mon Sep 17 00:00:00 2001 From: Santiago Soler Date: Tue, 3 Sep 2024 16:17:23 -0700 Subject: [PATCH 41/97] Add an experimental stage Check if the version variable can be retrieved. --- azure-pipelines.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 73cb8bfba..ad18acefc 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -47,6 +47,35 @@ stages: jobs: - template: .ci/azure/sdist.yml +# ----------------------------------------------------------------------------- + + - stage: Experiment + displayName: "Experiment with pipelines (remove this stage)" + dependsOn: + - DocBuild + jobs: + - job: + displayName: "Experiment with variable" + pool: + vmImage: ubuntu-latest + + # Get stored variables. + # called "version". + variables: + # Get the discretize version used to build the docs in the "DocBuild" + # stage, "BuildDocs" job, and "discretizeVersion" task. The variable is + - name: VERSION + value: $[ stageDependencies.DocBuild.BuildDocs.outputs['discretizeVersion.version'] ] + + steps: + - bash: | + echo $VERSION + displayName: Echo variable + env: + VERSION: $(VERSION) # defined in variables section + +# ----------------------------------------------------------------------------- + - stage: Deploy displayName: "Deploy Source, Wheels, and Docs" dependsOn: From 2ada4682b31aa3dcad706014614b4845aeff5cc2 Mon Sep 17 00:00:00 2001 From: Santiago Soler Date: Tue, 3 Sep 2024 16:28:47 -0700 Subject: [PATCH 42/97] Echo the version before storing it --- .ci/azure/docs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.ci/azure/docs.yml b/.ci/azure/docs.yml index 6611c2335..005e20077 100644 --- a/.ci/azure/docs.yml +++ b/.ci/azure/docs.yml @@ -27,6 +27,7 @@ jobs: - bash: | source activate discretize-test version=$(python -c "import discretize; print(discretize.__version__)") + echo "discretize version:" "$version" echo "##vso[task.setvariable variable=version;isOutput=true]$version" displayName: 'Store discretize version in variable' name: discretizeVersion From 2dce5e3ab1d88b5b4c7b6645dc1f124a5256a2c9 Mon Sep 17 00:00:00 2001 From: Santiago Soler Date: Tue, 3 Sep 2024 16:29:36 -0700 Subject: [PATCH 43/97] Store version variable before building docs --- .ci/azure/docs.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.ci/azure/docs.yml b/.ci/azure/docs.yml index 005e20077..5746f5466 100644 --- a/.ci/azure/docs.yml +++ b/.ci/azure/docs.yml @@ -19,11 +19,6 @@ jobs: - bash: .ci/azure/setup_env.sh displayName: Setup discretize environment - - bash: | - source activate discretize-test - make -C docs html - displayName: 'Building HTML' - - bash: | source activate discretize-test version=$(python -c "import discretize; print(discretize.__version__)") @@ -32,6 +27,11 @@ jobs: displayName: 'Store discretize version in variable' name: discretizeVersion + - bash: | + source activate discretize-test + make -C docs html + displayName: 'Building HTML' + - bash: | source activate discretize-test make -C docs linkcheck From d254df0eeffa6249f63fec76d51b5665632747f7 Mon Sep 17 00:00:00 2001 From: Santiago Soler Date: Tue, 3 Sep 2024 16:40:33 -0700 Subject: [PATCH 44/97] Rename task to "GetVersion" --- .ci/azure/docs.yml | 2 +- azure-pipelines.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.ci/azure/docs.yml b/.ci/azure/docs.yml index 5746f5466..1035bf279 100644 --- a/.ci/azure/docs.yml +++ b/.ci/azure/docs.yml @@ -25,7 +25,7 @@ jobs: echo "discretize version:" "$version" echo "##vso[task.setvariable variable=version;isOutput=true]$version" displayName: 'Store discretize version in variable' - name: discretizeVersion + name: GetVersion - bash: | source activate discretize-test diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ad18acefc..54d160c7d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -60,12 +60,12 @@ stages: vmImage: ubuntu-latest # Get stored variables. - # called "version". variables: # Get the discretize version used to build the docs in the "DocBuild" - # stage, "BuildDocs" job, and "discretizeVersion" task. The variable is + # stage, "BuildDocs" job, and "GetVersion" task. The variable is + # called "version". - name: VERSION - value: $[ stageDependencies.DocBuild.BuildDocs.outputs['discretizeVersion.version'] ] + value: $[ stageDependencies.DocBuild.BuildDocs.outputs['GetVersion.version'] ] steps: - bash: | From abe0fa17cb3c8777c4139373faa7e78ba4ea9385 Mon Sep 17 00:00:00 2001 From: Santiago Soler Date: Tue, 3 Sep 2024 16:40:58 -0700 Subject: [PATCH 45/97] Deploy built docs to a new folder for that version --- .ci/azure/deploy.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.ci/azure/deploy.yml b/.ci/azure/deploy.yml index c7911e66a..c9838a2cd 100644 --- a/.ci/azure/deploy.yml +++ b/.ci/azure/deploy.yml @@ -3,6 +3,15 @@ jobs: displayName: "Deploy Docs and source" pool: vmImage: ubuntu-latest + + # Get stored variables. + variables: + # Get the discretize version used to build the docs in the "DocBuild" + # stage, "BuildDocs" job, and "GetVersion" task. The variable is + # called "version". + - name: VERSION + value: $[ stageDependencies.DocBuild.BuildDocs.outputs['GetVersion.version'] ] + steps: # No need to checkout the repo here! - checkout: none @@ -52,12 +61,12 @@ jobs: cd discretize-docs git gc --prune=now git remote prune origin - rm -rf en/main/* - cp -r html/* en/main/ + mv html en/$VERSION touch .nojekyll git add . git commit -am "Azure CI commit ref $(Build.SourceVersion)" git push displayName: Push documentation to discretize-docs env: - GH_TOKEN: $(gh.token) \ No newline at end of file + GH_TOKEN: $(gh.token) + VERSION: $(VERSION) # defined in variables section From 3030623036d45858c87801a8a0fc36f2e9ed52be Mon Sep 17 00:00:00 2001 From: Santiago Soler Date: Tue, 3 Sep 2024 16:55:29 -0700 Subject: [PATCH 46/97] Update latest symlink before pushing new docs --- .ci/azure/deploy.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.ci/azure/deploy.yml b/.ci/azure/deploy.yml index c9838a2cd..a743e9ff4 100644 --- a/.ci/azure/deploy.yml +++ b/.ci/azure/deploy.yml @@ -63,6 +63,9 @@ jobs: git remote prune origin mv html en/$VERSION touch .nojekyll + # Update latest symlink + ln -s en/$VERSION en/latest + # Commit and push git add . git commit -am "Azure CI commit ref $(Build.SourceVersion)" git push From 7c84ba282051675801d86acf60d605bd2c711223 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Tue, 3 Sep 2024 21:56:42 -0600 Subject: [PATCH 47/97] change rng argument to random_seed --- discretize/tests.py | 43 +++++++++--------- tests/base/test_tensor_innerproduct_derivs.py | 18 +++++--- tests/base/test_tests.py | 20 ++++++--- tests/cyl/test_cyl_innerproducts.py | 44 +++++++++---------- tests/simplex/test_inner_products.py | 6 ++- tests/tree/test_tree_innerproduct_derivs.py | 18 +++++--- 6 files changed, 86 insertions(+), 63 deletions(-) diff --git a/discretize/tests.py b/discretize/tests.py index ea4340a29..eb122aa3a 100644 --- a/discretize/tests.py +++ b/discretize/tests.py @@ -81,7 +81,7 @@ _happiness_rng = np.random.default_rng() -def setup_mesh(mesh_type, nC, nDim, rng=None): +def setup_mesh(mesh_type, nC, nDim, random_seed=None): """Generate arbitrary mesh for testing. For the mesh type, number of cells along each axis and dimension specified, @@ -99,9 +99,9 @@ def setup_mesh(mesh_type, nC, nDim, rng=None): number of base mesh cells and must be a power of 2. nDim : int The dimension of the mesh. Must be 1, 2 or 3. - rng : numpy.random.Generator, int, optional + random_seed : numpy.random.Generator, int, optional If ``random`` is in `mesh_type`, this is the random number generator to use for - creating a random mesh. If an integer or None it is used to seed a new + creating that random mesh. If an integer or None it is used to seed a new `numpy.random.default_rng`. Returns @@ -110,7 +110,7 @@ def setup_mesh(mesh_type, nC, nDim, rng=None): A discretize mesh of class specified by the input argument *mesh_type* """ if "random" in mesh_type: - rng = np.random.default_rng(rng) + rng = np.random.default_rng(random_seed) if "TensorMesh" in mesh_type: if "uniform" in mesh_type: h = [nC, nC, nC] @@ -221,7 +221,7 @@ class for the given operator. Within the test class, the user sets the parameter OrderTest inherits from :class:`unittest.TestCase`. - Parameters + Attributes ---------- name : str Name the convergence test @@ -241,7 +241,7 @@ class for the given operator. Within the test class, the user sets the parameter for the meshes used in the convergence test; e.g. [4, 8, 16, 32] meshDimension : int Mesh dimension. Must be 1, 2 or 3 - rng : numpy.random.Generator, int, optional + random_seed : numpy.random.Generator, int, optional If ``random`` is in `mesh_type`, this is the random number generator used generate the random meshes, if an ``int`` or ``None``, it used to seed a new `numpy.random.default_rng`. @@ -311,7 +311,7 @@ class for the given operator. Within the test class, the user sets the parameter meshTypes = ["uniformTensorMesh"] _meshType = meshTypes[0] meshDimension = 3 - rng = None + random_seed = None def setupMesh(self, nC): """Generate mesh and set as current mesh for testing. @@ -320,16 +320,15 @@ def setupMesh(self, nC): ---------- nC : int Number of cells along each axis. - rng : numpy.random.Generator, int, optional - The random number generator to use for the adjoint test, if an integer or None - it used to seed a new `numpy.random.default_rng`. Only used if mesh_type is 'random' Returns ------- Float Maximum cell width for the mesh """ - mesh, max_h = setup_mesh(self._meshType, nC, self.meshDimension, rng=self.rng) + mesh, max_h = setup_mesh( + self._meshType, nC, self.meshDimension, random_seed=self.random_seed + ) self.M = mesh return max_h @@ -348,7 +347,7 @@ def getError(self): """ return 1.0 - def orderTest(self, rng=None): + def orderTest(self, random_seed=None): """Perform an order test. For number of cells specified in meshSizes setup mesh, call getError @@ -373,8 +372,8 @@ def orderTest(self, rng=None): "expectedOrders must have the same length as the meshTypes" ) - if rng is not None: - self.rng = rng + if random_seed is not None: + self.random_seed = random_seed def test_func(n_cells): max_h = self.setupMesh(n_cells) @@ -568,7 +567,7 @@ def check_derivative( tolerance=0.85, eps=1e-10, ax=None, - rng=None, + random_seed=None, ): """Perform a basic derivative check. @@ -597,8 +596,8 @@ def check_derivative( ax : matplotlib.pyplot.Axes, optional An axis object for the convergence plot if *plotIt = True*. Otherwise, the function will create a new axis. - rng : numpy.random.Generator, int, optional - If `dx` is ``None``, This is the random number generator to use for + random_seed : numpy.random.Generator, int, optional + If `dx` is ``None``, this is the random number generator to use for generating a step direction. If an integer or None, it is used to seed a new `numpy.random.default_rng`. @@ -616,7 +615,7 @@ def check_derivative( >>> def simplePass(x): ... return np.sin(x), utils.sdiag(np.cos(x)) - >>> passed = tests.check_derivative(simplePass, rng.standard_normal(5), rng=rng) + >>> passed = tests.check_derivative(simplePass, rng.standard_normal(5), random_seed=rng) ==================== check_derivative ==================== iter h |ft-f0| |ft-f0-h*J0*dx| Order --------------------------------------------------------- @@ -650,7 +649,7 @@ def check_derivative( x0 = mkvc(x0) if dx is None: - rng = np.random.default_rng(rng) + rng = np.random.default_rng(random_seed) dx = rng.standard_normal(len(x0)) h = np.logspace(-1, -num, num) @@ -800,7 +799,7 @@ def assert_isadjoint( rtol=1e-6, atol=0.0, assert_error=True, - rng=None, + random_seed=None, ): r"""Do a dot product test for the forward operator and its adjoint operator. @@ -851,7 +850,7 @@ def assert_isadjoint( assertion error if failed). If set to False, the result of the test is returned as boolean and a message is printed. - rng : numpy.random.Generator, int, optional + random_seed : numpy.random.Generator, int, optional The random number generator to use for the adjoint test. If an integer or None it is used to seed a new `numpy.random.default_rng`. @@ -868,7 +867,7 @@ def assert_isadjoint( """ __tracebackhide__ = True - rng = np.random.default_rng(rng) + rng = np.random.default_rng(random_seed) def random(size, iscomplex): """Create random data of size and dtype of .""" diff --git a/tests/base/test_tensor_innerproduct_derivs.py b/tests/base/test_tensor_innerproduct_derivs.py index e6e1ff21a..af8a94f70 100644 --- a/tests/base/test_tensor_innerproduct_derivs.py +++ b/tests/base/test_tensor_innerproduct_derivs.py @@ -42,7 +42,9 @@ def fun(sig): fast, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=452) + return discretize.tests.check_derivative( + fun, sig, num=5, plotIt=False, random_seed=452 + ) def doTestEdge( self, h, rep, fast, meshType, invert_model=False, invert_matrix=False @@ -80,7 +82,7 @@ def fun(sig): ("harmonic" if invert_model and invert_matrix else "standard"), ) return discretize.tests.check_derivative( - fun, sig, num=5, plotIt=False, rng=4567 + fun, sig, num=5, plotIt=False, random_seed=4567 ) def test_FaceIP_1D_float(self): @@ -362,7 +364,9 @@ def fun(sig): rep, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=421) + return discretize.tests.check_derivative( + fun, sig, num=5, plotIt=False, random_seed=421 + ) def doTestEdge(self, h, rep, meshType, invert_model=False, invert_matrix=False): if meshType == "Curv": @@ -393,7 +397,9 @@ def fun(sig): rep, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=31) + return discretize.tests.check_derivative( + fun, sig, num=5, plotIt=False, random_seed=31 + ) def test_FaceIP_2D_float(self): self.assertTrue(self.doTestFace([10, 4], 0, "Tensor")) @@ -502,7 +508,9 @@ def fun(sig): rep, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=64) + return discretize.tests.check_derivative( + fun, sig, num=5, plotIt=False, random_seed=64 + ) def test_EdgeIP_2D_float(self): self.assertTrue(self.doTestEdge([10, 4], 0, "Tensor")) diff --git a/tests/base/test_tests.py b/tests/base/test_tests.py index 9a4475ca2..fbf789ca2 100644 --- a/tests/base/test_tests.py +++ b/tests/base/test_tests.py @@ -26,7 +26,7 @@ def test_defaults(self, capsys): mesh1.n_cells, mesh2.n_cells, assert_error=False, - rng=41, + random_seed=41, ) out2, _ = capsys.readouterr() assert out1 @@ -39,7 +39,7 @@ def test_defaults(self, capsys): lambda v: P.T * v, mesh1.n_cells, mesh2.n_cells, - rng=42, + random_seed=42, ) def test_different_shape(self): @@ -60,7 +60,7 @@ def adj(inp): adj, shape_u=(4, nt), shape_v=(4,), - rng=42, + random_seed=42, ) def test_complex_clinear(self): @@ -73,7 +73,7 @@ def test_complex_clinear(self): complex_u=True, complex_v=True, clinear=False, - rng=112, + random_seed=112, ) @@ -83,14 +83,18 @@ def simplePass(x): return np.sin(x), sp.diags(np.cos(x)) rng = np.random.default_rng(5322) - check_derivative(simplePass, rng.standard_normal(5), plotIt=False, rng=42) + check_derivative( + simplePass, rng.standard_normal(5), plotIt=False, random_seed=42 + ) def test_simpleFunction(self): def simpleFunction(x): return np.sin(x), lambda xi: np.cos(x) * xi rng = np.random.default_rng(5322) - check_derivative(simpleFunction, rng.standard_normal(5), plotIt=False, rng=23) + check_derivative( + simpleFunction, rng.standard_normal(5), plotIt=False, random_seed=23 + ) def test_simpleFail(self): def simpleFail(x): @@ -98,7 +102,9 @@ def simpleFail(x): rng = np.random.default_rng(5322) with pytest.raises(AssertionError): - check_derivative(simpleFail, rng.standard_normal(5), plotIt=False, rng=64) + check_derivative( + simpleFail, rng.standard_normal(5), plotIt=False, random_seed=64 + ) @pytest.mark.parametrize("test_type", ["mean", "min", "last", "all", "mean_at_least"]) diff --git a/tests/cyl/test_cyl_innerproducts.py b/tests/cyl/test_cyl_innerproducts.py index b3e96f18e..875b54140 100644 --- a/tests/cyl/test_cyl_innerproducts.py +++ b/tests/cyl/test_cyl_innerproducts.py @@ -479,7 +479,7 @@ def fun(x): print("Testing FaceInnerProduct Isotropic") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=532 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=532 ) ) @@ -494,7 +494,7 @@ def fun(x): print("Testing FaceInnerProduct Isotropic InvProp") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=75 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=75 ) ) @@ -509,7 +509,7 @@ def fun(x): print("Testing FaceInnerProduct Isotropic InvMat") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=1 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=1 ) ) @@ -526,7 +526,7 @@ def fun(x): print("Testing FaceInnerProduct Isotropic InvProp InvMat") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=74 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=74 ) ) @@ -539,7 +539,7 @@ def fun(x): print("Testing EdgeInnerProduct Isotropic") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=345 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=345 ) ) @@ -554,7 +554,7 @@ def fun(x): print("Testing EdgeInnerProduct Isotropic InvProp") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=643 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=643 ) ) @@ -569,7 +569,7 @@ def fun(x): print("Testing EdgeInnerProduct Isotropic InvMat") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=363 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=363 ) ) @@ -586,7 +586,7 @@ def fun(x): print("Testing EdgeInnerProduct Isotropic InvProp InvMat") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=773 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=773 ) ) @@ -620,7 +620,7 @@ def fun(x): print("Testing FaceInnerProduct Anisotropic") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=2436 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=2436 ) ) @@ -640,7 +640,7 @@ def fun(x): print("Testing FaceInnerProduct Anisotropic InvProp") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=634 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=634 ) ) @@ -660,7 +660,7 @@ def fun(x): print("Testing FaceInnerProduct Anisotropic InvMat") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=222 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=222 ) ) @@ -684,7 +684,7 @@ def fun(x): print("Testing FaceInnerProduct Anisotropic InvProp InvMat") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=654 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=654 ) ) @@ -704,7 +704,7 @@ def fun(x): print("Testing EdgeInnerProduct Anisotropic") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=7754 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=7754 ) ) @@ -724,7 +724,7 @@ def fun(x): print("Testing EdgeInnerProduct Anisotropic InvProp") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=1164 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=1164 ) ) @@ -744,7 +744,7 @@ def fun(x): print("Testing EdgeInnerProduct Anisotropic InvMat") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=643 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=643 ) ) @@ -768,7 +768,7 @@ def fun(x): print("Testing EdgeInnerProduct Anisotropic InvProp InvMat") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=8654 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=8654 ) ) @@ -794,7 +794,7 @@ def fun(x): print("Testing FaceInnerProduct Isotropic (Face Properties)") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=234 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=234 ) ) @@ -809,7 +809,7 @@ def fun(x): print("Testing FaceInnerProduct Isotropic InvProp (Face Properties)") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=7543 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=7543 ) ) @@ -824,7 +824,7 @@ def fun(x): print("Testing FaceInnerProduct Isotropic InvMat (Face Properties)") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=2745725 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=2745725 ) ) @@ -837,7 +837,7 @@ def fun(x): print("Testing EdgeInnerProduct Isotropic (Face Properties)") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=6654 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=6654 ) ) @@ -852,7 +852,7 @@ def fun(x): print("Testing EdgeInnerProduct Isotropic InvProp (Face Properties)") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=4564 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=4564 ) ) @@ -867,6 +867,6 @@ def fun(x): print("Testing EdgeInnerProduct Isotropic InvMat (Face Properties)") return self.assertTrue( tests.check_derivative( - fun, self.x0, num=7, tolerance=TOLD, plotIt=False, rng=2355 + fun, self.x0, num=7, tolerance=TOLD, plotIt=False, random_seed=2355 ) ) diff --git a/tests/simplex/test_inner_products.py b/tests/simplex/test_inner_products.py index 676faa027..a5cae4ce6 100644 --- a/tests/simplex/test_inner_products.py +++ b/tests/simplex/test_inner_products.py @@ -344,7 +344,7 @@ def fun(sig): print("Face", rep) return discretize.tests.check_derivative( - fun, sig, num=5, plotIt=False, rng=5352 + fun, sig, num=5, plotIt=False, random_seed=5352 ) def doTestEdge(self, h, rep): @@ -359,7 +359,9 @@ def fun(sig): return M * v, Md(v) print("Edge", rep) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=532) + return discretize.tests.check_derivative( + fun, sig, num=5, plotIt=False, random_seed=532 + ) def test_FaceIP_2D_float(self): self.assertTrue(self.doTestFace([10, 4], 0)) diff --git a/tests/tree/test_tree_innerproduct_derivs.py b/tests/tree/test_tree_innerproduct_derivs.py index 708680c7f..da4ff3026 100644 --- a/tests/tree/test_tree_innerproduct_derivs.py +++ b/tests/tree/test_tree_innerproduct_derivs.py @@ -41,7 +41,7 @@ def fun(sig): ("harmonic" if invert_model and invert_matrix else "standard"), ) return discretize.tests.check_derivative( - fun, sig, num=5, plotIt=False, rng=4421 + fun, sig, num=5, plotIt=False, random_seed=4421 ) def doTestEdge( @@ -78,7 +78,9 @@ def fun(sig): fast, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=643) + return discretize.tests.check_derivative( + fun, sig, num=5, plotIt=False, random_seed=643 + ) def test_FaceIP_2D_float_Tree(self): self.assertTrue(self.doTestFace([8, 8], 0, False, "Tree")) @@ -195,7 +197,9 @@ def fun(sig): # fast, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=677) + return discretize.tests.check_derivative( + fun, sig, num=5, plotIt=False, random_seed=677 + ) def doTestEdge(self, h, rep, meshType, invert_model=False, invert_matrix=False): if meshType == "Curv": @@ -228,7 +232,9 @@ def fun(sig): # fast, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=543) + return discretize.tests.check_derivative( + fun, sig, num=5, plotIt=False, random_seed=543 + ) def test_FaceIP_2D_float_fast_Tree(self): self.assertTrue(self.doTestFace([8, 8], 0, "Tree")) @@ -287,7 +293,9 @@ def fun(sig): # fast, ("harmonic" if invert_model and invert_matrix else "standard"), ) - return discretize.tests.check_derivative(fun, sig, num=5, plotIt=False, rng=23) + return discretize.tests.check_derivative( + fun, sig, num=5, plotIt=False, random_seed=23 + ) def test_EdgeIP_2D_float_fast_Tree(self): self.assertTrue(self.doTestEdge([8, 8], 0, "Tree")) From 7d3617ddb06f4e50040e66e373f2b77ab2b9cbfb Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Wed, 4 Sep 2024 15:14:42 -0600 Subject: [PATCH 48/97] choose working seeds --- tests/tree/test_tree_operators.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/tests/tree/test_tree_operators.py b/tests/tree/test_tree_operators.py index 483385d70..12a9b1ec2 100644 --- a/tests/tree/test_tree_operators.py +++ b/tests/tree/test_tree_operators.py @@ -38,7 +38,6 @@ class TestCellGrad2D(discretize.tests.OrderTest): meshSizes = [8, 16] # because of the averaging involved in the ghost point. u_b = (u_n + u_g)/2 expectedOrders = 1 - rng = np.random.default_rng(87964213) def getError(self): # Test function @@ -56,7 +55,7 @@ def getError(self): return err def test_order(self): - self.orderTest() + self.orderTest(random_seed=421) class TestCellGrad3D(discretize.tests.OrderTest): @@ -66,7 +65,6 @@ class TestCellGrad3D(discretize.tests.OrderTest): meshSizes = [8, 16] # because of the averaging involved in the ghost point. u_b = (u_n + u_g)/2 expectedOrders = 1 - rng = np.random.default_rng(6957832) def getError(self): # Test function @@ -106,7 +104,7 @@ def getError(self): return err def test_order(self): - self.orderTest() + self.orderTest(5532) class TestFaceDivxy2D(discretize.tests.OrderTest): @@ -114,7 +112,6 @@ class TestFaceDivxy2D(discretize.tests.OrderTest): meshTypes = MESHTYPES meshDimension = 2 meshSizes = [16, 32] - rng = np.random.default_rng(19647823) def getError(self): # Test function @@ -137,14 +134,13 @@ def getError(self): return err def test_order(self): - self.orderTest() + self.orderTest(random_seed=19647823) class TestFaceDiv3D(discretize.tests.OrderTest): name = "Face Divergence 3D" meshTypes = MESHTYPES meshSizes = [8, 16, 32] - rng = np.random.default_rng(81725364) def getError(self): fx = lambda x, y, z: np.sin(2 * np.pi * x) @@ -165,7 +161,7 @@ def getError(self): return np.linalg.norm((divF - divF_ana), np.inf) def test_order(self): - self.orderTest() + self.orderTest(random_seed=81725364) class TestFaceDivxyz3D(discretize.tests.OrderTest): @@ -173,7 +169,6 @@ class TestFaceDivxyz3D(discretize.tests.OrderTest): meshTypes = MESHTYPES meshDimension = 3 meshSizes = [8, 16, 32] - rng = np.random.default_rng(6172824) def getError(self): # Test function @@ -203,7 +198,7 @@ def getError(self): return err def test_order(self): - self.orderTest() + self.orderTest(random_seed=6172824) class TestCurl(discretize.tests.OrderTest): From 9e83796bead117d930c6705363171e48e56c5fc3 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Wed, 4 Sep 2024 15:28:41 -0600 Subject: [PATCH 49/97] more switches from rng->random_seed --- tests/base/test_interpolation.py | 28 ++++++++++++++++------------ tests/base/test_tensor.py | 1 - 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/tests/base/test_interpolation.py b/tests/base/test_interpolation.py index bd46a2c93..1b433e9bd 100644 --- a/tests/base/test_interpolation.py +++ b/tests/base/test_interpolation.py @@ -46,8 +46,8 @@ class TestInterpolation1D(discretize.tests.OrderTest): tolerance = TOLERANCES meshDimension = 1 meshSizes = [8, 16, 32, 64, 128] - rng = np.random.default_rng(5136) - LOCS = rng.random(50) * 0.6 + 0.2 + random_seed = np.random.default_rng(55124) + LOCS = random_seed.random(50) * 0.6 + 0.2 def getError(self): funX = lambda x: np.cos(2 * np.pi * x) @@ -98,8 +98,8 @@ class TestInterpolation2d(discretize.tests.OrderTest): tolerance = TOLERANCES meshDimension = 2 meshSizes = [8, 16, 32, 64] - rng = np.random.default_rng(2457) - LOCS = rng.random((50, 2)) * 0.6 + 0.2 + random_seed = np.random.default_rng(2457) + LOCS = random_seed.random((50, 2)) * 0.6 + 0.2 def getError(self): funX = lambda x, y: np.cos(2 * np.pi * y) @@ -182,8 +182,12 @@ class TestInterpolationSymCyl(discretize.tests.OrderTest): tolerance = 0.6 meshDimension = 3 meshSizes = [32, 64, 128, 256] - rng = np.random.default_rng(81756234) - LOCS = np.c_[rng.random(4) * 0.6 + 0.2, np.zeros(4), rng.random(4) * 0.6 + 0.2] + random_seed = np.random.default_rng(81756234) + LOCS = np.c_[ + random_seed.random(4) * 0.6 + 0.2, + np.zeros(4), + random_seed.random(4) * 0.6 + 0.2, + ] def getError(self): funX = lambda x, y: np.cos(2 * np.pi * y) @@ -246,11 +250,11 @@ class TestInterpolationCyl(discretize.tests.OrderTest): meshTypes = ["uniformCylMesh", "randomCylMesh"] # MESHTYPES + meshDimension = 3 meshSizes = [8, 16, 32, 64] - rng = np.random.default_rng(876234) + random_seed = np.random.default_rng(876234) LOCS = np.c_[ - rng.random(20) * 0.6 + 0.2, - 2 * np.pi * (rng.random(20) * 0.6 + 0.2), - rng.random(20) * 0.6 + 0.2, + random_seed.random(20) * 0.6 + 0.2, + 2 * np.pi * (random_seed.random(20) * 0.6 + 0.2), + random_seed.random(20) * 0.6 + 0.2, ] def getError(self): @@ -314,9 +318,9 @@ def test_orderEz(self): class TestInterpolation3D(discretize.tests.OrderTest): - rng = np.random.default_rng(234) + random_seed = np.random.default_rng(234) name = "Interpolation" - LOCS = rng.random((50, 3)) * 0.6 + 0.2 + LOCS = random_seed.random((50, 3)) * 0.6 + 0.2 meshTypes = MESHTYPES tolerance = TOLERANCES meshDimension = 3 diff --git a/tests/base/test_tensor.py b/tests/base/test_tensor.py index 5d880f5bc..6a0fd9078 100644 --- a/tests/base/test_tensor.py +++ b/tests/base/test_tensor.py @@ -278,7 +278,6 @@ def test_cell_nodes(self, mesh): class TestPoissonEqn(discretize.tests.OrderTest): name = "Poisson Equation" meshSizes = [10, 16, 20] - rng = gen def getError(self): # Create some functions to integrate From b71c4194c479e14a1508dfaf3ba6ea275b1774f3 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Wed, 4 Sep 2024 16:10:45 -0600 Subject: [PATCH 50/97] do version information once --- docs/_static/versions.json | 4 ++-- docs/conf.py | 30 +++++++++++------------------- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/docs/_static/versions.json b/docs/_static/versions.json index b7345d354..773e01e6c 100644 --- a/docs/_static/versions.json +++ b/docs/_static/versions.json @@ -5,8 +5,8 @@ }, { "name": "v0.10.0 (latest)", - "version": "0.10.0", - "url": "https://discretize.simpeg.xyz/en/0.10.0/", + "version": "v0.10.0", + "url": "https://discretize.simpeg.xyz/en/v0.10.0/", "preferred": true } ] diff --git a/docs/conf.py b/docs/conf.py index 8b0ed498b..ed548924f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -95,8 +95,16 @@ # # The full version, including alpha/beta/rc tags. release = version("discretize") +discretize_version = parse(release) + +# The short X.Y version. +version = discretize_version.public +if discretize_version.is_devrelease: + branch = "main" +else: + branch = f"v{version}" # The short X.Y version. -version = ".".join(release.split(".")[:2]) +# version = ".".join(release.split(".")[:2]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -145,7 +153,6 @@ if link_github: import inspect - from packaging.version import parse extensions.append("sphinx.ext.linkcode") @@ -198,16 +205,7 @@ def linkcode_resolve(domain, info): fn = os.path.relpath(fn, start=os.path.dirname(discretize.__file__)) except ValueError: return None - - discretize_version = parse(discretize.__version__) - tag = ( - "main" - if discretize_version.is_devrelease - else f"v{discretize_version.public}" - ) - return ( - f"https://github.com/simpeg/discretize/blob/{tag}/discretize/{fn}{linespec}" - ) + return f"https://github.com/simpeg/discretize/blob/{branch}/discretize/{fn}{linespec}" else: extensions.append("sphinx.ext.viewcode") @@ -247,12 +245,6 @@ def linkcode_resolve(domain, info): dict(name="Contact", url="https://mattermost.softwareunderground.org/simpeg"), ] -# Define discretize version for the version switcher -discretize_version = parse(discretize.__version__) -if discretize_version.is_devrelease: - switcher_version = "main" -else: - switcher_version = discretize_version.public # Use Pydata Sphinx theme html_theme = "pydata_sphinx_theme" @@ -290,7 +282,7 @@ def linkcode_resolve(domain, info): "navbar_end": ["version-switcher", "theme-switcher", "navbar-icon-links"], # Configure version switcher (remember to add it to the "navbar_end") "switcher": { - "version_match": switcher_version, + "version_match": branch, "json_url": "https://discretize.simpeg.xyz/en/latest/_static/versions.json", }, "show_version_warning_banner": True, From 613c48afa666ef58b64a131ca16c996bf734d1aa Mon Sep 17 00:00:00 2001 From: Santiago Soler Date: Wed, 4 Sep 2024 15:14:47 -0700 Subject: [PATCH 51/97] Remove the leading "v" in versions.json --- docs/_static/versions.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/_static/versions.json b/docs/_static/versions.json index 773e01e6c..d8a96bf52 100644 --- a/docs/_static/versions.json +++ b/docs/_static/versions.json @@ -4,9 +4,9 @@ "url": "https://discretize.simpeg.xyz/en/main/" }, { - "name": "v0.10.0 (latest)", - "version": "v0.10.0", - "url": "https://discretize.simpeg.xyz/en/v0.10.0/", + "name": "0.10.0 (latest)", + "version": "0.10.0", + "url": "https://discretize.simpeg.xyz/en/0.10.0/", "preferred": true } ] From b77a09c81b1cb563e3adc8cd3b692336f80cebbe Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Wed, 4 Sep 2024 16:26:15 -0600 Subject: [PATCH 52/97] add leading v back to the "version" and "url" fields. --- docs/_static/versions.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_static/versions.json b/docs/_static/versions.json index d8a96bf52..757f32e5e 100644 --- a/docs/_static/versions.json +++ b/docs/_static/versions.json @@ -5,8 +5,8 @@ }, { "name": "0.10.0 (latest)", - "version": "0.10.0", - "url": "https://discretize.simpeg.xyz/en/0.10.0/", + "version": "v0.10.0", + "url": "https://discretize.simpeg.xyz/en/v0.10.0/", "preferred": true } ] From 22f685185bef02a760e590a41c7efec2677c9a17 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Wed, 4 Sep 2024 17:11:22 -0600 Subject: [PATCH 53/97] Use azure pipelines information --- .ci/azure/deploy.yml | 26 +++++++++++++++----------- .ci/azure/docs.yml | 8 -------- azure-pipelines.yml | 37 ++++++------------------------------- 3 files changed, 21 insertions(+), 50 deletions(-) diff --git a/.ci/azure/deploy.yml b/.ci/azure/deploy.yml index a743e9ff4..0a0b1675f 100644 --- a/.ci/azure/deploy.yml +++ b/.ci/azure/deploy.yml @@ -4,14 +4,6 @@ jobs: pool: vmImage: ubuntu-latest - # Get stored variables. - variables: - # Get the discretize version used to build the docs in the "DocBuild" - # stage, "BuildDocs" job, and "GetVersion" task. The variable is - # called "version". - - name: VERSION - value: $[ stageDependencies.DocBuild.BuildDocs.outputs['GetVersion.version'] ] - steps: # No need to checkout the repo here! - checkout: none @@ -39,6 +31,11 @@ jobs: ls -l dist ls -l html + - bash: | + echo $IS_TAG + echo $IS_MAIN + echo $BRANCH_NAME + - bash: | git config --global user.name ${GH_NAME} git config --global user.email ${GH_EMAIL} @@ -51,6 +48,7 @@ jobs: - bash: | twine upload --skip-existing dist/* displayName: Deploy source and wheels + condition: $(variables.IS_TAG) env: TWINE_USERNAME: $(twine.username) TWINE_PASSWORD: $(twine.password) @@ -61,10 +59,17 @@ jobs: cd discretize-docs git gc --prune=now git remote prune origin - mv html en/$VERSION + mv html en/$BRANCH_NAME touch .nojekyll + displayName: Set Doc Folder + + - bash: # Update latest symlink - ln -s en/$VERSION en/latest + ln -s en/$BRANCH_NAME en/latest + displayName: Point Latest to tag + condition: $(variables.IS_TAG) + + - bash: # Commit and push git add . git commit -am "Azure CI commit ref $(Build.SourceVersion)" @@ -72,4 +77,3 @@ jobs: displayName: Push documentation to discretize-docs env: GH_TOKEN: $(gh.token) - VERSION: $(VERSION) # defined in variables section diff --git a/.ci/azure/docs.yml b/.ci/azure/docs.yml index 1035bf279..51b3fcb6f 100644 --- a/.ci/azure/docs.yml +++ b/.ci/azure/docs.yml @@ -19,14 +19,6 @@ jobs: - bash: .ci/azure/setup_env.sh displayName: Setup discretize environment - - bash: | - source activate discretize-test - version=$(python -c "import discretize; print(discretize.__version__)") - echo "discretize version:" "$version" - echo "##vso[task.setvariable variable=version;isOutput=true]$version" - displayName: 'Store discretize version in variable' - name: GetVersion - - bash: | source activate discretize-test make -C docs html diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 54d160c7d..e3298932f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -15,6 +15,11 @@ pr: exclude: - '*no-ci*' +variables: + BRANCH_NAME: $(Build.SourceBranchName) + IS_TAG: $[startsWith(variables['Build.SourceBranch'], 'refs/tags/')] + IS_MAIN: $[eq(variables['Build.SourceBranch'], 'refs/heads/main')] + stages: - stage: StyleChecks displayName: "Style Checks" @@ -47,42 +52,12 @@ stages: jobs: - template: .ci/azure/sdist.yml -# ----------------------------------------------------------------------------- - - - stage: Experiment - displayName: "Experiment with pipelines (remove this stage)" - dependsOn: - - DocBuild - jobs: - - job: - displayName: "Experiment with variable" - pool: - vmImage: ubuntu-latest - - # Get stored variables. - variables: - # Get the discretize version used to build the docs in the "DocBuild" - # stage, "BuildDocs" job, and "GetVersion" task. The variable is - # called "version". - - name: VERSION - value: $[ stageDependencies.DocBuild.BuildDocs.outputs['GetVersion.version'] ] - - steps: - - bash: | - echo $VERSION - displayName: Echo variable - env: - VERSION: $(VERSION) # defined in variables section - -# ----------------------------------------------------------------------------- - - stage: Deploy displayName: "Deploy Source, Wheels, and Docs" dependsOn: - BuildWheels - BuildSource - - DocBuild - condition: and(succeeded(), startsWith(variables['build.sourceBranch'], 'refs/tags/')) + condition: and(succeeded(), or(variables.IS_TAG, variables.IS_MAIN)) jobs: - template: .ci/azure/deploy.yml From 4dc6a068518df8be3dfbdbc88cc1d77e4e4d803a Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Thu, 5 Sep 2024 10:32:02 -0600 Subject: [PATCH 54/97] just use the variable --- .ci/azure/deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.ci/azure/deploy.yml b/.ci/azure/deploy.yml index 0a0b1675f..264becc01 100644 --- a/.ci/azure/deploy.yml +++ b/.ci/azure/deploy.yml @@ -48,7 +48,7 @@ jobs: - bash: | twine upload --skip-existing dist/* displayName: Deploy source and wheels - condition: $(variables.IS_TAG) + condition: variables.IS_TAG env: TWINE_USERNAME: $(twine.username) TWINE_PASSWORD: $(twine.password) @@ -67,7 +67,7 @@ jobs: # Update latest symlink ln -s en/$BRANCH_NAME en/latest displayName: Point Latest to tag - condition: $(variables.IS_TAG) + condition: variables.IS_TAG - bash: # Commit and push From 4c45e59e194699af7911da6611f71f4e5fae2e05 Mon Sep 17 00:00:00 2001 From: Santiago Soler Date: Thu, 5 Sep 2024 10:22:53 -0700 Subject: [PATCH 55/97] Simplify the cell_nodes function Instead of building the nodes from scratch with meshgrid, use the existing `cell_centers` and `h_gridded` properties of the `TensorMesh`. --- discretize/tensor_mesh.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/discretize/tensor_mesh.py b/discretize/tensor_mesh.py index 00899e7c8..8aef0e93f 100644 --- a/discretize/tensor_mesh.py +++ b/discretize/tensor_mesh.py @@ -602,27 +602,25 @@ def cell_bounds(self): a particular cell in the following order: ``x1``, ``x2``, ``y1``, ``y2``, ``z1``, ``z2``, where ``x1 < x2``, ``y1 < y2`` and ``z1 < z2``. """ - # Define indexing for meshgrid and order for ravel - indexing = "ij" - order = "F" - + centers, widths = self.cell_centers, self.h_gridded if self.dim == 1: - x = self.nodes_x - bounds = (x[:-1], x[1:]) + bounds = (centers - widths.ravel() / 2, centers + widths.ravel() /2) elif self.dim == 2: - x, y = self.nodes_x, self.nodes_y - x1, y1 = np.meshgrid(x[:-1], y[:-1], indexing=indexing) - x2, y2 = np.meshgrid(x[1:], y[1:], indexing=indexing) + x1 = centers[:, 0] - widths[:, 0] / 2 + x2 = centers[:, 0] + widths[:, 0] / 2 + y1 = centers[:, 1] - widths[:, 1] / 2 + y2 = centers[:, 1] + widths[:, 1] / 2 bounds = (x1, x2, y1, y2) else: - x, y, z = self.nodes_x, self.nodes_y, self.nodes_z - x1, y1, z1 = np.meshgrid(x[:-1], y[:-1], z[:-1], indexing=indexing) - x2, y2, z2 = np.meshgrid(x[1:], y[1:], z[1:], indexing=indexing) + x1 = centers[:, 0] - widths[:, 0] / 2 + x2 = centers[:, 0] + widths[:, 0] / 2 + y1 = centers[:, 1] - widths[:, 1] / 2 + y2 = centers[:, 1] + widths[:, 1] / 2 + z1 = centers[:, 2] - widths[:, 2] / 2 + z2 = centers[:, 2] + widths[:, 2] / 2 bounds = (x1, x2, y1, y2, z1, z2) - cell_bounds = np.hstack( - tuple(v.ravel(order=order)[:, np.newaxis] for v in bounds) - ) + cell_bounds = np.hstack(tuple(v[:, np.newaxis] for v in bounds)) return cell_bounds @property From e1c92b006c74ed2a7e0385c20139542326c5fb75 Mon Sep 17 00:00:00 2001 From: Santiago Soler Date: Thu, 5 Sep 2024 10:33:56 -0700 Subject: [PATCH 56/97] Run black --- discretize/tensor_mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discretize/tensor_mesh.py b/discretize/tensor_mesh.py index 8aef0e93f..6adb82e52 100644 --- a/discretize/tensor_mesh.py +++ b/discretize/tensor_mesh.py @@ -604,7 +604,7 @@ def cell_bounds(self): """ centers, widths = self.cell_centers, self.h_gridded if self.dim == 1: - bounds = (centers - widths.ravel() / 2, centers + widths.ravel() /2) + bounds = (centers - widths.ravel() / 2, centers + widths.ravel() / 2) elif self.dim == 2: x1 = centers[:, 0] - widths[:, 0] / 2 x2 = centers[:, 0] + widths[:, 0] / 2 From ad7db121405d8da5c3b28c6020d3906c5afbc42e Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Fri, 6 Sep 2024 12:51:03 -0600 Subject: [PATCH 57/97] fix condition check --- .ci/azure/deploy.yml | 4 ++-- azure-pipelines.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.ci/azure/deploy.yml b/.ci/azure/deploy.yml index 264becc01..4d093e6b7 100644 --- a/.ci/azure/deploy.yml +++ b/.ci/azure/deploy.yml @@ -48,7 +48,7 @@ jobs: - bash: | twine upload --skip-existing dist/* displayName: Deploy source and wheels - condition: variables.IS_TAG + condition: eq(variables.IS_TAG, true) env: TWINE_USERNAME: $(twine.username) TWINE_PASSWORD: $(twine.password) @@ -67,7 +67,7 @@ jobs: # Update latest symlink ln -s en/$BRANCH_NAME en/latest displayName: Point Latest to tag - condition: variables.IS_TAG + condition: eq(variables.IS_TAG, true) - bash: # Commit and push diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e3298932f..52e36b6e5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -57,7 +57,7 @@ stages: dependsOn: - BuildWheels - BuildSource - condition: and(succeeded(), or(variables.IS_TAG, variables.IS_MAIN)) + condition: and(succeeded(), or(eq(variables.IS_TAG, true), eq(variables.IS_MAIN, true))) jobs: - template: .ci/azure/deploy.yml From 0afc001d3ee77d690b40c15e0e8f1fed7770f01c Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Fri, 6 Sep 2024 13:01:45 -0600 Subject: [PATCH 58/97] update doc deploy steps --- .ci/azure/deploy.yml | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/.ci/azure/deploy.yml b/.ci/azure/deploy.yml index 4d093e6b7..77858497a 100644 --- a/.ci/azure/deploy.yml +++ b/.ci/azure/deploy.yml @@ -8,6 +8,12 @@ jobs: # No need to checkout the repo here! - checkout: none + - bash: | + echo $IS_TAG + echo $IS_MAIN + echo $BRANCH_NAME + displayName: Report branch parameters + # Just download all of the items already built - task: DownloadPipelineArtifact@2 inputs: @@ -30,11 +36,7 @@ jobs: - bash: | ls -l dist ls -l html - - - bash: | - echo $IS_TAG - echo $IS_MAIN - echo $BRANCH_NAME + displayName: Report downloaded cache contents. - bash: | git config --global user.name ${GH_NAME} @@ -55,23 +57,24 @@ jobs: # upload documentation to discretize-docs gh-pages on tags - bash: | - git clone --depth 1 https://${GH_TOKEN}@github.com/simpeg/discretize-docs.git + git clone -q --branch gh-pages --depth 1 https://${GH_TOKEN}@github.com/simpeg/discretize-docs.git cd discretize-docs - git gc --prune=now - git remote prune origin - mv html en/$BRANCH_NAME + mv ../html "en/$BRANCH_NAME" touch .nojekyll + git add "en/$BRANCH_NAME" .nojekyll displayName: Set Doc Folder - bash: # Update latest symlink - ln -s en/$BRANCH_NAME en/latest + cd discretize-docs + ln -s "en/$BRANCH_NAME" en/latest + git add en/latest displayName: Point Latest to tag condition: eq(variables.IS_TAG, true) - bash: # Commit and push - git add . + cd discretize-docs git commit -am "Azure CI commit ref $(Build.SourceVersion)" git push displayName: Push documentation to discretize-docs From 58477854e270c06307bad9ad3f3f5e6e605440fc Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Fri, 6 Sep 2024 13:36:47 -0600 Subject: [PATCH 59/97] update deploy steps --- .ci/azure/deploy.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.ci/azure/deploy.yml b/.ci/azure/deploy.yml index 77858497a..25999ab3a 100644 --- a/.ci/azure/deploy.yml +++ b/.ci/azure/deploy.yml @@ -58,23 +58,27 @@ jobs: # upload documentation to discretize-docs gh-pages on tags - bash: | git clone -q --branch gh-pages --depth 1 https://${GH_TOKEN}@github.com/simpeg/discretize-docs.git + displayName: Checkout doc repository + + - bash: | cd discretize-docs + rm -rf "en/$BRANCH_NAME" mv ../html "en/$BRANCH_NAME" touch .nojekyll - git add "en/$BRANCH_NAME" .nojekyll displayName: Set Doc Folder - bash: # Update latest symlink cd discretize-docs + rm -f en/latest ln -s "en/$BRANCH_NAME" en/latest - git add en/latest displayName: Point Latest to tag condition: eq(variables.IS_TAG, true) - bash: # Commit and push cd discretize-docs + git add --all git commit -am "Azure CI commit ref $(Build.SourceVersion)" git push displayName: Push documentation to discretize-docs From 97b867ddb6c7ccf8c5635e57767dd48fc4f1a718 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Fri, 6 Sep 2024 15:28:10 -0600 Subject: [PATCH 60/97] add | --- .ci/azure/deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.ci/azure/deploy.yml b/.ci/azure/deploy.yml index 25999ab3a..8062d3106 100644 --- a/.ci/azure/deploy.yml +++ b/.ci/azure/deploy.yml @@ -67,7 +67,7 @@ jobs: touch .nojekyll displayName: Set Doc Folder - - bash: + - bash: | # Update latest symlink cd discretize-docs rm -f en/latest @@ -75,7 +75,7 @@ jobs: displayName: Point Latest to tag condition: eq(variables.IS_TAG, true) - - bash: + - bash: | # Commit and push cd discretize-docs git add --all From 24ea07009745688ff97b0f3fc9b49c00832bafb9 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Fri, 6 Sep 2024 16:25:05 -0600 Subject: [PATCH 61/97] expose gh_token variable to checkout command --- .ci/azure/deploy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.ci/azure/deploy.yml b/.ci/azure/deploy.yml index 8062d3106..e81a4bb3e 100644 --- a/.ci/azure/deploy.yml +++ b/.ci/azure/deploy.yml @@ -59,6 +59,8 @@ jobs: - bash: | git clone -q --branch gh-pages --depth 1 https://${GH_TOKEN}@github.com/simpeg/discretize-docs.git displayName: Checkout doc repository + env: + GH_TOKEN: $(gh.token) - bash: | cd discretize-docs From 92619f32ff37f7bb9b4bc85996cbf0063b829b3c Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Sat, 7 Sep 2024 16:56:56 -0600 Subject: [PATCH 62/97] set target version as dev in version switcher on main --- docs/_static/versions.json | 6 +++--- docs/conf.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/_static/versions.json b/docs/_static/versions.json index 757f32e5e..7b2780fe4 100644 --- a/docs/_static/versions.json +++ b/docs/_static/versions.json @@ -1,10 +1,10 @@ [ - { - "version": "main", + { "name": "main", + "version": "dev", "url": "https://discretize.simpeg.xyz/en/main/" }, { - "name": "0.10.0 (latest)", + "name": "0.10.0 (stable)", "version": "v0.10.0", "url": "https://discretize.simpeg.xyz/en/v0.10.0/", "preferred": true diff --git a/docs/conf.py b/docs/conf.py index ed548924f..e0165cb41 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -282,8 +282,8 @@ def linkcode_resolve(domain, info): "navbar_end": ["version-switcher", "theme-switcher", "navbar-icon-links"], # Configure version switcher (remember to add it to the "navbar_end") "switcher": { - "version_match": branch, - "json_url": "https://discretize.simpeg.xyz/en/latest/_static/versions.json", + "version_match": "dev" if branch == "main" else branch, + "json_url": "https://discretize.simpeg.xyz/en/main/_static/versions.json", }, "show_version_warning_banner": True, } From c0f7025808d414e0227ad9e9b72df730e526eae4 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Sun, 8 Sep 2024 16:21:00 -0600 Subject: [PATCH 63/97] bump pydata_sphinx_theme version --- .ci/environment_test.yml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.ci/environment_test.yml b/.ci/environment_test.yml index 90568a70b..3a5d24e10 100644 --- a/.ci/environment_test.yml +++ b/.ci/environment_test.yml @@ -13,7 +13,7 @@ dependencies: # documentation - sphinx - - pydata-sphinx-theme==0.13.3 + - pydata-sphinx-theme==0.15.4 - sphinx-gallery>=0.1.13 - numpydoc>=1.5 - jupyter diff --git a/pyproject.toml b/pyproject.toml index 3d777ef86..7fe69ddea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ omf = ["omf"] all = ["discretize[plot,viz,omf]"] doc = [ "sphinx!=4.1.0", - "pydata-sphinx-theme==0.9.0", + "pydata-sphinx-theme==0.15.4", "sphinx-gallery==0.1.13", "numpydoc>=1.5", "jupyter", From 0926391079adb06ef89d7ae9525f58a2df71e535 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Sun, 8 Sep 2024 17:24:06 -0600 Subject: [PATCH 64/97] set seed on boundary integral tests --- tests/boundaries/test_boundary_integrals.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/boundaries/test_boundary_integrals.py b/tests/boundaries/test_boundary_integrals.py index a9a47e7bf..be0a0e409 100644 --- a/tests/boundaries/test_boundary_integrals.py +++ b/tests/boundaries/test_boundary_integrals.py @@ -216,7 +216,6 @@ class Test3DBoundaryIntegral(discretize.tests.OrderTest): meshDimension = 3 expectedOrders = [2, 1, 2, 2, 2, 2] meshSizes = [4, 8, 16, 32] - rng = np.random.default_rng(57681234) def getError(self): mesh = self.M @@ -269,14 +268,14 @@ def getError(self): def test_orderWeakCellGradIntegral(self): self.name = "3D - weak cell gradient integral w/boundary" self.myTest = "cell_grad" - self.orderTest() + self.orderTest(random_seed=51235) def test_orderWeakEdgeDivIntegral(self): self.name = "3D - weak edge divergence integral w/boundary" self.myTest = "edge_div" - self.orderTest() + self.orderTest(random_seed=51123) def test_orderWeakFaceCurlIntegral(self): self.name = "3D - weak face curl integral w/boundary" self.myTest = "face_curl" - self.orderTest() + self.orderTest(random_seed=5522) From e7d850d8a2bcadeb0924a6b77c1f34399c3ba877 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Wed, 25 Sep 2024 10:56:15 -0600 Subject: [PATCH 65/97] Fix caching of internal projection matrices change projection caching to depend on both the projection type and the with_volume kwarg --- discretize/unstructured_mesh.py | 7 ++++--- tests/simplex/test_operators.py | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/discretize/unstructured_mesh.py b/discretize/unstructured_mesh.py index f7a70e32d..5cf932ec4 100644 --- a/discretize/unstructured_mesh.py +++ b/discretize/unstructured_mesh.py @@ -405,7 +405,8 @@ def __get_inner_product_projection_matrices( ): if getattr(self, "_proj_stash", None) is None: self._proj_stash = {} - if i_type not in self._proj_stash: + key = (i_type, with_volume) + if key not in self._proj_stash: dim = self.dim n_cells = self.n_cells if i_type == "F": @@ -456,8 +457,8 @@ def __get_inner_product_projection_matrices( ) Ps.append(T @ P) - self._proj_stash[i_type] = (Ps, T_col_inds, T_ind_ptr) - Ps, T_col_inds, T_ind_ptr = self._proj_stash[i_type] + self._proj_stash[key] = (Ps, T_col_inds, T_ind_ptr) + Ps, T_col_inds, T_ind_ptr = self._proj_stash[key] if return_pointers: return Ps, (T_col_inds, T_ind_ptr) else: diff --git a/tests/simplex/test_operators.py b/tests/simplex/test_operators.py index 2e3916b31..f0a79fd97 100644 --- a/tests/simplex/test_operators.py +++ b/tests/simplex/test_operators.py @@ -1,5 +1,8 @@ import numpy as np +import pytest + import discretize +from discretize import SimplexMesh from discretize.utils import example_simplex_mesh @@ -179,3 +182,16 @@ def test_grad_order(self): self.name = "SimplexMesh grad order test" self._test_type = "Grad" self.orderTest() + + +@pytest.mark.parametrize("i_type", ["E", "F"]) +def test_simplex_projection_caching(i_type): + n = 5 + mesh = SimplexMesh(*example_simplex_mesh((n, n))) + P1 = mesh._SimplexMesh__get_inner_product_projection_matrices( + i_type, with_volume=False, return_pointers=False + ) + P2 = mesh._SimplexMesh__get_inner_product_projection_matrices( + i_type, with_volume=True, return_pointers=False + ) + assert P1 is not P2 From 730287ccc600c85213950f2da3390d30fa0407d2 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Wed, 25 Sep 2024 11:28:53 -0600 Subject: [PATCH 66/97] update broken links --- docs/content/additional_resources.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/additional_resources.rst b/docs/content/additional_resources.rst index 4e94eafd0..373fc7f22 100644 --- a/docs/content/additional_resources.rst +++ b/docs/content/additional_resources.rst @@ -14,14 +14,14 @@ found on the official websites of the packages Python for scientific computing ------------------------------- -* `Python for Scientists `_ Links to commonly used packages, Matlab to Python comparison +* `Learn Python `_ Links to commonly used packages, Matlab to Python comparison * `Python Wiki `_ Lists packages and resources for scientific computing in Python Numpy and Matlab ---------------- * `NumPy for Matlab Users `_ -* `Python vs Matlab `_ +* `Python vs Matlab `_ Lessons in Python ----------------- From ed74f9d67f2ff183bf7de3f2dd2b139ce85be7a4 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Wed, 9 Oct 2024 10:40:46 -0600 Subject: [PATCH 67/97] keep old method and simplify new names --- discretize/_extensions/tree_ext.pyx | 158 +++++++++++++++++++++++----- discretize/mixins/mpl_mod.py | 2 +- tests/tree/test_intersections.py | 23 ++-- 3 files changed, 145 insertions(+), 38 deletions(-) diff --git a/discretize/_extensions/tree_ext.pyx b/discretize/_extensions/tree_ext.pyx index 988418e35..f0bd9e37c 100644 --- a/discretize/_extensions/tree_ext.pyx +++ b/discretize/_extensions/tree_ext.pyx @@ -1195,7 +1195,7 @@ cdef class _TreeMesh: self.tree.number() def get_containing_cells(self, points): - """ + """Return the cells containing the given points. Parameters ---------- @@ -1226,8 +1226,8 @@ cdef class _TreeMesh: return indexes[0] return np.array(indexes) - def get_cells_within_ball(self, center, double radius): - """Find the indices of cells that overlap a ball + def get_cells_in_ball(self, center, double radius): + """Find the indices of cells that intersect a ball Parameters ---------- @@ -1246,8 +1246,8 @@ cdef class _TreeMesh: cdef geom.Ball ball = geom.Ball(self._dim, &a[0], radius) return np.array(self.tree.find_cells_geom(ball)) - def get_cells_along_line(self, *args): - """Find the cells along a line segment. + def get_cells_on_line(self, segment): + """Find the cells intersecting a line segment. Parameters ---------- @@ -1257,15 +1257,9 @@ cdef class _TreeMesh: Returns ------- numpy.ndarray of int - Indices for cells that contain the a line defined by the two input - points, ordered in the direction of the line. + Indices for cells that intersect the line defined by the two input + points. """ - if len(args) == 1: - segment = args[0] - elif len(args) == 2: - segment = np.stack(args) - else: - raise TypeError('get_cells_along_line() takes 1 or 2 positional arguments') segment = self._require_ndarray_with_dim('segment', segment, ndim=2, dtype=np.float64) if segment.shape[0] != 2: raise ValueError(f"A line segment has two points, not {segment.shape[0]}") @@ -1275,8 +1269,8 @@ cdef class _TreeMesh: cdef geom.Line line = geom.Line(self._dim, &start[0], &end[0]) return np.array(self.tree.find_cells_geom(line)) - def get_cells_within_aabb(self, x_min, x_max): - """Find the indices of cells that overlap an axis aligned bounding box (aabb) + def get_cells_in_aabb(self, x_min, x_max): + """Find the indices of cells that intersect an axis aligned bounding box (aabb) Parameters ---------- @@ -1296,7 +1290,7 @@ cdef class _TreeMesh: cdef geom.Box box = geom.Box(self._dim, &a[0], &b[0]) return np.array(self.tree.find_cells_geom(box)) - def get_cells_along_plane(self, origin, normal): + def get_cells_on_plane(self, origin, normal): """Find the indices of cells that intersect a plane. Parameters @@ -1315,8 +1309,8 @@ cdef class _TreeMesh: cdef geom.Plane plane = geom.Plane(self._dim, &orig[0], &norm[0]) return np.array(self.tree.find_cells_geom(plane)) - def get_cells_within_triangle(self, triangle): - """Find the indices of cells that overlap a triangle. + def get_cells_in_triangle(self, triangle): + """Find the indices of cells that intersect a triangle. Parameters ---------- @@ -1336,8 +1330,8 @@ cdef class _TreeMesh: cdef geom.Triangle poly = geom.Triangle(self._dim, &tri[0, 0], &tri[1, 0], &tri[2, 0]) return np.array(self.tree.find_cells_geom(poly)) - def get_cells_within_vertical_trianglular_prism(self, triangle, double h): - """Find the indices of cells that overlap a vertical triangular prism. + def get_cells_in_vertical_trianglular_prism(self, triangle, double h): + """Find the indices of cells that intersect a vertical triangular prism. Parameters ---------- @@ -1362,8 +1356,8 @@ cdef class _TreeMesh: cdef geom.VerticalTriangularPrism vert = geom.VerticalTriangularPrism(self._dim, &tri[0, 0], &tri[1, 0], &tri[2, 0], h) return np.array(self.tree.find_cells_geom(vert)) - def get_cells_within_tetrahedron(self, tetra): - """Find the indices of cells that overlap a tetrahedron. + def get_cells_in_tetrahedron(self, tetra): + """Find the indices of cells that intersect a tetrahedron. Parameters ---------- @@ -1376,7 +1370,7 @@ cdef class _TreeMesh: The indices of cells which overlap the triangle. """ if self.dim == 2: - return self.get_cells_within_triangle(tetra) + return self.get_cells_in_triangle(tetra) tetra = self._require_ndarray_with_dim('tetra', tetra, ndim=2, dtype=np.float64) if tetra.shape[0] != 4: raise ValueError(f"A tetrahedron is defined by 4 points in 3D, not {tetra.shape[0]}.") @@ -2842,6 +2836,122 @@ cdef class _TreeMesh: return np.where(is_on_boundary) + @cython.cdivision(True) + def get_cells_along_line(self, x0, x1): + """Find the cells in order along a line segment. + + Parameters + ---------- + x0,x1 : (dim) array_like + Begining and ending point of the line segment. + + Returns + ------- + list of int + Indices for cells that contain the a line defined by the two input + points, ordered in the direction of the line. + """ + cdef np.float64_t ax, ay, az, bx, by, bz + + cdef int dim = self.dim + ax = x0[0] + ay = x0[1] + az = x0[2] if dim==3 else 0 + + bx = x1[0] + by = x1[1] + bz = x1[2] if dim==3 else 0 + + cdef vector[long long int] cell_indexes; + + #find initial cell + cdef c_Cell *cur_cell = self.tree.containing_cell(ax, ay, az) + cell_indexes.push_back(cur_cell.index) + #find last cell + cdef c_Cell *last_cell = self.tree.containing_cell(bx, by, bz) + cdef c_Cell *next_cell + cdef int ix, iy, iz + cdef double tx, ty, tz, ipx, ipy, ipz + + if dim==3: + last_point = 7 + else: + last_point = 3 + + cdef int iter = 0 + + while cur_cell.index != last_cell.index: + #find which direction to look: + p0 = cur_cell.points[0].location + pF = cur_cell.points[last_point].location + + if ax>bx: + tx = (p0[0]-ax)/(bx-ax) + elif axby: + ty = (p0[1]-ay)/(by-ay) + elif aybz: + tz = (p0[2]-az)/(bz-az) + elif azbx: # go -x + next_cell = next_cell.neighbors[0] + else: # go +x + next_cell = next_cell.neighbors[1] + if ty<=tx and ty<=tz: + # step in y direction + if ay>by: # go -y + next_cell = next_cell.neighbors[2] + else: # go +y + next_cell = next_cell.neighbors[3] + if dim==3 and tz<=tx and tz<=ty: + # step in z direction + if az>bz: # go -z + next_cell = next_cell.neighbors[4] + else: # go +z + next_cell = next_cell.neighbors[5] + + # check if next_cell is not a leaf + # (if so need to traverse down the children and find the closest leaf cell) + while not next_cell.is_leaf(): + # should be able to use cp to check which cell to go to + cp = next_cell.children[0].points[last_point].location + # this basically finds the child cell closest to the intersection point + ix = ipx>cp[0] or (ipx==cp[0] and axcp[1] or (ipy==cp[1] and aycp[2] or (ipz==cp[2] and az Date: Wed, 9 Oct 2024 11:15:23 -0600 Subject: [PATCH 68/97] fix bug in old method. --- discretize/_extensions/tree_ext.pyx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/discretize/_extensions/tree_ext.pyx b/discretize/_extensions/tree_ext.pyx index f0bd9e37c..41c09d1e6 100644 --- a/discretize/_extensions/tree_ext.pyx +++ b/discretize/_extensions/tree_ext.pyx @@ -2907,6 +2907,10 @@ cdef class _TreeMesh: tz = INFINITY t = min(tx,ty,tz) + if t >= 1: + # then the segment ended in the current cell. + # do not bother checking anymore. + break #intersection point ipx = (bx-ax)*t+ax @@ -2914,19 +2918,19 @@ cdef class _TreeMesh: ipz = (bz-az)*t+az next_cell = cur_cell - if tx<=ty and tx<=tz: + if t == tx: # step in x direction if ax>bx: # go -x next_cell = next_cell.neighbors[0] else: # go +x next_cell = next_cell.neighbors[1] - if ty<=tx and ty<=tz: + if t == ty: # step in y direction if ay>by: # go -y next_cell = next_cell.neighbors[2] else: # go +y next_cell = next_cell.neighbors[3] - if dim==3 and tz<=tx and tz<=ty: + if dim==3 and t == tz: # step in z direction if az>bz: # go -z next_cell = next_cell.neighbors[4] From 2e4abfa730e42fc43b8aeab3fbb041054a59a372 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Wed, 9 Oct 2024 11:19:26 -0600 Subject: [PATCH 69/97] remove taking ownership of conda directory on mac Also then we cannot update conda --- .ci/azure/setup_env.sh | 1 - .ci/azure/test.yml | 4 ---- 2 files changed, 5 deletions(-) diff --git a/.ci/azure/setup_env.sh b/.ci/azure/setup_env.sh index 388feef43..e012bb0ca 100755 --- a/.ci/azure/setup_env.sh +++ b/.ci/azure/setup_env.sh @@ -7,7 +7,6 @@ do_doc=$(echo "${DOC_BUILD:-false}" | tr '[:upper:]' '[:lower:]') if ${is_azure} then - conda update --yes -n base conda if ${do_doc} then .ci/setup_headless_display.sh diff --git a/.ci/azure/test.yml b/.ci/azure/test.yml index 26187e26a..17c89d94f 100644 --- a/.ci/azure/test.yml +++ b/.ci/azure/test.yml @@ -40,10 +40,6 @@ jobs: displayName: Add conda to PATH condition: ne(variables.varOS, 'Windows_NT') - - bash: sudo chown -R $USER $CONDA - displayName: Take ownership of conda directory - condition: eq(variables.varOS, 'Darwin') - - powershell: Write-Host "##vso[task.prependpath]$env:CONDA\Scripts" displayName: Add conda to PATH condition: eq(variables.varOS, 'Windows_NT') From 797e497d3b1145d508a00f9a97842d66716f0f9b Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Wed, 9 Oct 2024 11:25:18 -0600 Subject: [PATCH 70/97] add break points if the next cell is NULL --- discretize/_extensions/tree_ext.pyx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/discretize/_extensions/tree_ext.pyx b/discretize/_extensions/tree_ext.pyx index 41c09d1e6..c0663f1ba 100644 --- a/discretize/_extensions/tree_ext.pyx +++ b/discretize/_extensions/tree_ext.pyx @@ -2924,18 +2924,24 @@ cdef class _TreeMesh: next_cell = next_cell.neighbors[0] else: # go +x next_cell = next_cell.neighbors[1] + if next_cell is NULL: + break if t == ty: # step in y direction if ay>by: # go -y next_cell = next_cell.neighbors[2] else: # go +y next_cell = next_cell.neighbors[3] + if next_cell is NULL: + break if dim==3 and t == tz: # step in z direction if az>bz: # go -z next_cell = next_cell.neighbors[4] else: # go +z next_cell = next_cell.neighbors[5] + if next_cell is NULL: + break # check if next_cell is not a leaf # (if so need to traverse down the children and find the closest leaf cell) From e93bc3bcdb02184dc60dddee77c7d8871166fb05 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Wed, 9 Oct 2024 15:34:47 -0600 Subject: [PATCH 71/97] download miniconda on macos-latest images. store arch as a variable More trial use variables[] syntax variable is uppercase echo variable some more tests try this again? set them up here? name as MINICONDA_ARCH_LABEL test add another if test this... test this way... use bash if-else syntax remove spaces make a shell script end the if re-add previous tests --- .ci/azure/setup_miniconda_macos.sh | 15 +++++++++++++++ .ci/azure/test.yml | 9 +++++---- 2 files changed, 20 insertions(+), 4 deletions(-) create mode 100755 .ci/azure/setup_miniconda_macos.sh diff --git a/.ci/azure/setup_miniconda_macos.sh b/.ci/azure/setup_miniconda_macos.sh new file mode 100755 index 000000000..31ca98131 --- /dev/null +++ b/.ci/azure/setup_miniconda_macos.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -ex #echo on and exit if any line fails + +echo "arch is $ARCH" +if [[ $ARCH == "X64" ]]; then + MINICONDA_ARCH_LABEL="x86_64" +else + MINICONDA_ARCH_LABEL="arm64" +fi +echo $MINICONDA_ARCH_LABEL +mkdir -p ~/miniconda3 +curl https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-$MINICONDA_ARCH_LABEL.sh -o ~/miniconda3/miniconda.sh +bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3 +rm ~/miniconda3/miniconda.sh +echo "##vso[task.setvariable variable=CONDA;]${HOME}/miniconda3" diff --git a/.ci/azure/test.yml b/.ci/azure/test.yml index 26187e26a..3952f84bd 100644 --- a/.ci/azure/test.yml +++ b/.ci/azure/test.yml @@ -35,15 +35,16 @@ jobs: vmImage: $(image) variables: varOS: $(Agent.OS) + ARCH: $(Agent.OSArchitecture) steps: + - bash: .ci/azure/setup_miniconda_macos.sh + displayName: Install miniconda on mac + condition: eq(variables.varOS, 'Darwin') + - bash: echo "##vso[task.prependpath]$CONDA/bin" displayName: Add conda to PATH condition: ne(variables.varOS, 'Windows_NT') - - bash: sudo chown -R $USER $CONDA - displayName: Take ownership of conda directory - condition: eq(variables.varOS, 'Darwin') - - powershell: Write-Host "##vso[task.prependpath]$env:CONDA\Scripts" displayName: Add conda to PATH condition: eq(variables.varOS, 'Windows_NT') From 2b3ef550425babbdbcd02f5b08d734b20069615f Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Sat, 12 Oct 2024 15:27:38 -0600 Subject: [PATCH 72/97] implement cell bounds with cython processes. --- discretize/_extensions/tree.h | 5 +- discretize/_extensions/tree.pxd | 1 - discretize/_extensions/tree_ext.pyx | 175 +++++++++++++++------------- 3 files changed, 94 insertions(+), 87 deletions(-) diff --git a/discretize/_extensions/tree.h b/discretize/_extensions/tree.h index 09b02bd2a..29c9aad23 100644 --- a/discretize/_extensions/tree.h +++ b/discretize/_extensions/tree.h @@ -121,11 +121,10 @@ class Cell{ return location[index]; }; double volume; - double bounds[6]; Cell(); - Cell(Node *pts[4], int_t ndim, int_t maxlevel);//, function func); - Cell(Node *pts[4], Cell *parent); + Cell(Node *pts[8], int_t ndim, int_t maxlevel);//, function func); + Cell(Node *pts[8], Cell *parent); ~Cell(); inline Node* min_node(){ return points[0];}; diff --git a/discretize/_extensions/tree.pxd b/discretize/_extensions/tree.pxd index 28f13512a..7b428fbc8 100644 --- a/discretize/_extensions/tree.pxd +++ b/discretize/_extensions/tree.pxd @@ -60,7 +60,6 @@ cdef extern from "tree.h": Face *faces[6] int_t location_ind[3] double location[3] - double bounds[6] int_t key, level, max_level long long int index double volume diff --git a/discretize/_extensions/tree_ext.pyx b/discretize/_extensions/tree_ext.pyx index 7ade07333..094a4cb07 100644 --- a/discretize/_extensions/tree_ext.pyx +++ b/discretize/_extensions/tree_ext.pyx @@ -55,21 +55,6 @@ cdef class TreeCell: cdef void _set(self, c_Cell* cell): self._cell = cell self._dim = cell.n_dim - cdef: - Node *min_n = cell.min_node() - Node *max_n = cell.max_node() - self._x = self._cell.location[0] - self._x0 = min_n.location[0] - - self._y = self._cell.location[1] - self._y0 = min_n.location[1] - - self._wx = max_n.location[0] - self._x0 - self._wy = max_n.location[1] - self._y0 - if(self._dim > 2): - self._z = self._cell.location[2] - self._z0 = min_n.location[2] - self._wz = max_n.location[2] - self._z0 @property def nodes(self): @@ -150,8 +135,10 @@ cdef class TreeCell: (dim) numpy.ndarray Cell center location for the tree cell """ - if self._dim == 2: return np.array([self._x, self._y]) - return np.array([self._x, self._y, self._z]) + loc = self._cell.location + if self._dim == 2: + return np.array([loc[0], loc[1]]) + return np.array([loc[0], loc[1], loc[2]]) @property def origin(self): @@ -166,8 +153,10 @@ cdef class TreeCell: (dim) numpy.ndarray Origin location ('anchor point') for the tree cell """ - if self._dim == 2: return np.array([self._x0, self._y0]) - return np.array([self._x0, self._y0, self._z0]) + loc = self._cell.min_node().location + if self._dim == 2: + return np.array([loc[0], loc[1]]) + return np.array([loc[0], loc[1], loc[2]]) @property def x0(self): @@ -196,8 +185,19 @@ cdef class TreeCell: (dim) numpy.ndarray Cell dimension along each axis direction """ - if self._dim == 2: return np.array([self._wx, self._wy]) - return np.array([self._wx, self._wy, self._wz]) + loc_min = self._cell.min_node().location + loc_max = self._cell.max_node().location + + if self._dim == 2: + return np.array([ + loc_max[0] - loc_min[0], + loc_max[1] - loc_min[1], + ]) + return np.array([ + loc_max[0] - loc_min[0], + loc_max[1] - loc_min[1], + loc_max[2] - loc_min[2], + ]) @property def dim(self): @@ -222,37 +222,40 @@ cdef class TreeCell: return self._cell.index @property - @cython.boundscheck(False) def bounds(self): """ Bounds of the cell. Coordinates that define the bounds of the cell. Bounds are returned in - the following order: ``x1``, ``x2``, ``y1``, ``y2``, ``z1``, ``z2``. + the following order: ``x0``, ``x1``, ``y0``, ``y1``, ``z0``, ``z1``. Returns ------- bounds : (2 * dim) array Array with the cell bounds. """ - bounds = np.empty(self._dim * 2, dtype=np.float64) - cdef np.float64_t[:] bounds_view = bounds - if(self._dim == 1): - bounds_view[0] = self._x0 - bounds_view[1] = self._x0 + self._wx - elif(self._dim == 2): - bounds_view[0] = self._x0 - bounds_view[1] = self._x0 + self._wx - bounds_view[2] = self._y0 - bounds_view[3] = self._y0 + self._wy - else: - bounds_view[0] = self._x0 - bounds_view[1] = self._x0 + self._wx - bounds_view[2] = self._y0 - bounds_view[3] = self._y0 + self._wy - bounds_view[4] = self._z0 - bounds_view[5] = self._z0 + self._wz - return bounds + loc_min = self._cell.min_node().location + loc_max = self._cell.max_node().location + + if self.dim == 2: + return np.array( + [ + loc_min[0], + loc_max[0], + loc_min[1], + loc_max[1], + ] + ) + return np.array( + [ + loc_min[0], + loc_max[0], + loc_min[1], + loc_max[1], + loc_min[2], + loc_max[2], + ] + ) @property @@ -276,63 +279,64 @@ cdef class TreeCell: neighbors = [-1]*self._dim*2 for i in range(self._dim*2): - if self._cell.neighbors[i] is NULL: + neighbor = self._cell.neighbors[i] + if neighbor is NULL: continue - elif self._cell.neighbors[i].is_leaf(): - neighbors[i] = self._cell.neighbors[i].index + elif neighbor.is_leaf(): + neighbors[i] = neighbor.index else: if self._dim==2: if i==0: - neighbors[i] = [self._cell.neighbors[i].children[1].index, - self._cell.neighbors[i].children[3].index] + neighbors[i] = [neighbor.children[1].index, + neighbor.children[3].index] elif i==1: - neighbors[i] = [self._cell.neighbors[i].children[0].index, - self._cell.neighbors[i].children[2].index] + neighbors[i] = [neighbor.children[0].index, + neighbor.children[2].index] elif i==2: - neighbors[i] = [self._cell.neighbors[i].children[2].index, - self._cell.neighbors[i].children[3].index] + neighbors[i] = [neighbor.children[2].index, + neighbor.children[3].index] else: - neighbors[i] = [self._cell.neighbors[i].children[0].index, - self._cell.neighbors[i].children[1].index] + neighbors[i] = [neighbor.children[0].index, + neighbor.children[1].index] else: if i==0: - neighbors[i] = [self._cell.neighbors[i].children[1].index, - self._cell.neighbors[i].children[3].index, - self._cell.neighbors[i].children[5].index, - self._cell.neighbors[i].children[7].index] + neighbors[i] = [neighbor.children[1].index, + neighbor.children[3].index, + neighbor.children[5].index, + neighbor.children[7].index] elif i==1: - neighbors[i] = [self._cell.neighbors[i].children[0].index, - self._cell.neighbors[i].children[2].index, - self._cell.neighbors[i].children[4].index, - self._cell.neighbors[i].children[6].index] + neighbors[i] = [neighbor.children[0].index, + neighbor.children[2].index, + neighbor.children[4].index, + neighbor.children[6].index] elif i==2: - neighbors[i] = [self._cell.neighbors[i].children[2].index, - self._cell.neighbors[i].children[3].index, - self._cell.neighbors[i].children[6].index, - self._cell.neighbors[i].children[7].index] + neighbors[i] = [neighbor.children[2].index, + neighbor.children[3].index, + neighbor.children[6].index, + neighbor.children[7].index] elif i==3: - neighbors[i] = [self._cell.neighbors[i].children[0].index, - self._cell.neighbors[i].children[1].index, - self._cell.neighbors[i].children[4].index, - self._cell.neighbors[i].children[5].index] + neighbors[i] = [neighbor.children[0].index, + neighbor.children[1].index, + neighbor.children[4].index, + neighbor.children[5].index] elif i==4: - neighbors[i] = [self._cell.neighbors[i].children[4].index, - self._cell.neighbors[i].children[5].index, - self._cell.neighbors[i].children[6].index, - self._cell.neighbors[i].children[7].index] + neighbors[i] = [neighbor.children[4].index, + neighbor.children[5].index, + neighbor.children[6].index, + neighbor.children[7].index] else: - neighbors[i] = [self._cell.neighbors[i].children[0].index, - self._cell.neighbors[i].children[1].index, - self._cell.neighbors[i].children[2].index, - self._cell.neighbors[i].children[3].index] + neighbors[i] = [neighbor.children[0].index, + neighbor.children[1].index, + neighbor.children[2].index, + neighbor.children[3].index] return neighbors @property def _index_loc(self): + loc_ind = self._cell.location_ind if self._dim == 2: - return tuple((self._cell.location_ind[0], self._cell.location_ind[1])) - return tuple((self._cell.location_ind[0], self._cell.location_ind[1], - self._cell.location_ind[2])) + return tuple((loc_ind[0], loc_ind[1])) + return tuple((loc_ind[0], loc_ind[1], loc_ind[2])) @property def _level(self): @@ -1226,13 +1230,18 @@ cdef class _TreeMesh: @property def cell_bounds(self): - cell_bounds = np.empty((self.n_nodes, 2 * self._dim), dtype=np.float64) - cdef np.float64_t[:, :] cell_bounds_view = cell_bounds + cell_bounds = np.empty((self.n_cells, self.dim, 2), dtype=np.float64) + cdef np.float64_t[:, :, ::1] cell_bounds_view = cell_bounds for cell in self.tree.cells: - cell_bounds_view[cell.index, :] = cell.bounds + min_loc = cell.min_node().location + max_loc = cell.max_node().location + + for i in range(self._dim): + cell_bounds_view[cell.index, i, 0] = min_loc[i] + cell_bounds_view[cell.index, i, 1] = max_loc[i] - return cell_bounds + return cell_bounds.reshape((self.n_cells, -1)) def number(self): """Number the cells, nodes, faces, and edges of the TreeMesh.""" From 9ba2a140bf279d0430bfcc444e23e302f5d74ebf Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Sat, 12 Oct 2024 15:41:55 -0600 Subject: [PATCH 73/97] add test for cell_bounds method on the mesh --- discretize/_extensions/tree_ext.pyx | 1 + tests/tree/test_tree.py | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/discretize/_extensions/tree_ext.pyx b/discretize/_extensions/tree_ext.pyx index 094a4cb07..07de49ebd 100644 --- a/discretize/_extensions/tree_ext.pyx +++ b/discretize/_extensions/tree_ext.pyx @@ -1229,6 +1229,7 @@ cdef class _TreeMesh: return self._finalized @property + @cython.boundscheck(False) def cell_bounds(self): cell_bounds = np.empty((self.n_cells, self.dim, 2), dtype=np.float64) cdef np.float64_t[:, :, ::1] cell_bounds_view = cell_bounds diff --git a/tests/tree/test_tree.py b/tests/tree/test_tree.py index 9a5b05051..c292d762a 100644 --- a/tests/tree/test_tree.py +++ b/tests/tree/test_tree.py @@ -414,7 +414,15 @@ def test_bounds(self, mesh): else: z1, z2 = nodes[0][2], nodes[-1][2] expected_bounds = np.array([x1, x2, y1, y2, z1, z2]) - np.testing.assert_allclose(cell.bounds, expected_bounds) + np.testing.assert_equal(cell.bounds, expected_bounds) + + def test_cell_bounds(self, mesh): + """Test cell_bounds method of the tree mesh.""" + cell_bounds = mesh.cell_bounds + cell_bounds_slow = np.empty((mesh.n_cells, 2 * mesh.dim)) + for i, cell in enumerate(mesh): + cell_bounds_slow[i] = cell.bounds + np.testing.assert_equal(cell_bounds, cell_bounds_slow) class Test2DInterpolation(unittest.TestCase): From d04bddf0929bb54b5f0ec1d47f8bbb20b8ea4d81 Mon Sep 17 00:00:00 2001 From: Santiago Soler Date: Tue, 15 Oct 2024 14:43:11 -0700 Subject: [PATCH 74/97] Add extra test --- tests/tree/test_tree.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/tree/test_tree.py b/tests/tree/test_tree.py index c292d762a..de487f7d2 100644 --- a/tests/tree/test_tree.py +++ b/tests/tree/test_tree.py @@ -416,6 +416,19 @@ def test_bounds(self, mesh): expected_bounds = np.array([x1, x2, y1, y2, z1, z2]) np.testing.assert_equal(cell.bounds, expected_bounds) + def test_bounds_relations(self, mesh): + """Test if bounds are in the right order for one cell in the mesh.""" + cell = mesh[16] + if mesh.dim == 2: + x1, x2, y1, y2 = cell.bounds + assert x1 < x2 + assert y1 < y2 + else: + x1, x2, y1, y2, z1, z2 = cell.bounds + assert x1 < x2 + assert y1 < y2 + assert z1 < z2 + def test_cell_bounds(self, mesh): """Test cell_bounds method of the tree mesh.""" cell_bounds = mesh.cell_bounds From 49f794b3c64fe802f44448533fc1d2f3d15e7509 Mon Sep 17 00:00:00 2001 From: Santiago Soler Date: Tue, 15 Oct 2024 15:07:33 -0700 Subject: [PATCH 75/97] Cleaner implementation of cell_bounds Co-authored-by: Joseph Capriotti --- discretize/tensor_mesh.py | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/discretize/tensor_mesh.py b/discretize/tensor_mesh.py index 6adb82e52..54343f13f 100644 --- a/discretize/tensor_mesh.py +++ b/discretize/tensor_mesh.py @@ -602,25 +602,15 @@ def cell_bounds(self): a particular cell in the following order: ``x1``, ``x2``, ``y1``, ``y2``, ``z1``, ``z2``, where ``x1 < x2``, ``y1 < y2`` and ``z1 < z2``. """ - centers, widths = self.cell_centers, self.h_gridded - if self.dim == 1: - bounds = (centers - widths.ravel() / 2, centers + widths.ravel() / 2) - elif self.dim == 2: - x1 = centers[:, 0] - widths[:, 0] / 2 - x2 = centers[:, 0] + widths[:, 0] / 2 - y1 = centers[:, 1] - widths[:, 1] / 2 - y2 = centers[:, 1] + widths[:, 1] / 2 - bounds = (x1, x2, y1, y2) - else: - x1 = centers[:, 0] - widths[:, 0] / 2 - x2 = centers[:, 0] + widths[:, 0] / 2 - y1 = centers[:, 1] - widths[:, 1] / 2 - y2 = centers[:, 1] + widths[:, 1] / 2 - z1 = centers[:, 2] - widths[:, 2] / 2 - z2 = centers[:, 2] + widths[:, 2] / 2 - bounds = (x1, x2, y1, y2, z1, z2) - - cell_bounds = np.hstack(tuple(v[:, np.newaxis] for v in bounds)) + nodes = self.nodes.reshape((*self.shape_nodes, -1), order="F") + + min_nodes = nodes[(slice(-1), )*self.dim] + min_nodes = min_nodes.reshape((self.n_cells, -1), order="F") + max_nodes = nodes[(slice(1, None), )*self.dim] + max_nodes = max_nodes.reshape((self.n_cells, -1), order="F") + + cell_bounds = np.stack((min_nodes, max_nodes), axis=-1) + cell_bounds = cell_bounds.reshape((self.n_cells, -1)) return cell_bounds @property From dfe5a54d7e0deac8aabbe7e06d9b5428594f61f9 Mon Sep 17 00:00:00 2001 From: Santiago Soler Date: Tue, 15 Oct 2024 15:10:05 -0700 Subject: [PATCH 76/97] Replace assert_allclose for assert_equal --- tests/base/test_tensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/base/test_tensor.py b/tests/base/test_tensor.py index b9ff8ac2c..b0c30dec4 100644 --- a/tests/base/test_tensor.py +++ b/tests/base/test_tensor.py @@ -275,12 +275,12 @@ def mesh(self, request): def test_cell_nodes(self, mesh): """Test TensorMesh.cell_nodes.""" expected_cell_nodes = np.array([cell.nodes for cell in mesh]) - np.testing.assert_allclose(mesh.cell_nodes, expected_cell_nodes) + np.testing.assert_equal(mesh.cell_nodes, expected_cell_nodes) def test_cell_bounds(self, mesh): """Test TensorMesh.cell_bounds.""" expected_cell_bounds = np.array([cell.bounds for cell in mesh]) - np.testing.assert_allclose(mesh.cell_bounds, expected_cell_bounds) + np.testing.assert_equal(mesh.cell_bounds, expected_cell_bounds) class TestPoissonEqn(discretize.tests.OrderTest): From 7bd7d1b2701904c51e57ef3e0dd0f29364b806fa Mon Sep 17 00:00:00 2001 From: Santiago Soler Date: Tue, 15 Oct 2024 15:14:41 -0700 Subject: [PATCH 77/97] Run black --- discretize/tensor_mesh.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/discretize/tensor_mesh.py b/discretize/tensor_mesh.py index 54343f13f..c8ddb3278 100644 --- a/discretize/tensor_mesh.py +++ b/discretize/tensor_mesh.py @@ -603,12 +603,12 @@ def cell_bounds(self): ``y2``, ``z1``, ``z2``, where ``x1 < x2``, ``y1 < y2`` and ``z1 < z2``. """ nodes = self.nodes.reshape((*self.shape_nodes, -1), order="F") - - min_nodes = nodes[(slice(-1), )*self.dim] + + min_nodes = nodes[(slice(-1),) * self.dim] min_nodes = min_nodes.reshape((self.n_cells, -1), order="F") - max_nodes = nodes[(slice(1, None), )*self.dim] + max_nodes = nodes[(slice(1, None),) * self.dim] max_nodes = max_nodes.reshape((self.n_cells, -1), order="F") - + cell_bounds = np.stack((min_nodes, max_nodes), axis=-1) cell_bounds = cell_bounds.reshape((self.n_cells, -1)) return cell_bounds From 8eaaec139ff3bf35a3de61787dce80db49f09259 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Thu, 17 Oct 2024 22:34:26 -0600 Subject: [PATCH 78/97] use simplified numpy dependancy from meson --- discretize/_extensions/meson.build | 40 ++---------------------------- 1 file changed, 2 insertions(+), 38 deletions(-) diff --git a/discretize/_extensions/meson.build b/discretize/_extensions/meson.build index fc4af86d2..907dd0658 100644 --- a/discretize/_extensions/meson.build +++ b/discretize/_extensions/meson.build @@ -1,38 +1,6 @@ # NumPy include directory -# The try-except is needed because when things are -# split across drives on Windows, there is no relative path and an exception -# gets raised. There may be other such cases, so add a catch-all and switch to -# an absolute path. Relative paths are needed when for example a virtualenv is -# placed inside the source tree; Meson rejects absolute paths to places inside -# the source tree. -# For cross-compilation it is often not possible to run the Python interpreter -# in order to retrieve numpy's include directory. It can be specified in the -# cross file instead: -# [properties] -# numpy-include-dir = /abspath/to/host-pythons/site-packages/numpy/core/include -# -# This uses the path as is, and avoids running the interpreter. -incdir_numpy = meson.get_external_property('numpy-include-dir', 'not-given') -if incdir_numpy == 'not-given' - incdir_numpy = run_command(py, - [ - '-c', - '''import os -import numpy as np -try: - incdir = os.path.relpath(np.get_include()) -except Exception: - incdir = np.get_include() -print(incdir) - ''' - ], - check: true - ).stdout().strip() -else - _incdir_numpy_abs = incdir_numpy -endif -inc_np = include_directories(incdir_numpy) -np_dep = declare_dependency(include_directories: inc_np) +np_dep = dependency('numpy') +numpy_nodepr_api = ['-DNPY_NO_DEPRECATED_API=NPY_1_9_API_VERSION'] # Deal with M_PI & friends; add `use_math_defines` to c_args or cpp_args # Cython doesn't always get this right itself (see, e.g., gh-16800), so @@ -44,7 +12,6 @@ else use_math_defines = [] endif -numpy_nodepr_api = '-DNPY_NO_DEPRECATED_API=NPY_1_9_API_VERSION' c_undefined_ok = ['-Wno-maybe-uninitialized'] cython_c_args = [numpy_nodepr_api, use_math_defines] @@ -61,7 +28,6 @@ module_path = 'discretize/_extensions' py.extension_module( 'interputils_cython', 'interputils_cython.pyx', - include_directories: incdir_numpy, c_args: cython_c_args, install: true, subdir: module_path, @@ -71,7 +37,6 @@ py.extension_module( py.extension_module( 'tree_ext', ['tree_ext.pyx' , 'tree.cpp', 'geom.cpp'], - include_directories: incdir_numpy, cpp_args: cython_cpp_args, install: true, subdir: module_path, @@ -82,7 +47,6 @@ py.extension_module( py.extension_module( 'simplex_helpers', 'simplex_helpers.pyx', - include_directories: incdir_numpy, cpp_args: cython_cpp_args, install: true, subdir: module_path, From b3b11a75c3c3485aa0d24a2d167d2186830f3d83 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Thu, 17 Oct 2024 23:34:03 -0600 Subject: [PATCH 79/97] numpy 2.0 updates --- .../operators/differential_operators.py | 8 +++- discretize/unstructured_mesh.py | 39 +++++++++++-------- discretize/utils/__init__.py | 2 + discretize/utils/matrix_utils.py | 25 ++++++++++++ meson.build | 2 +- tests/base/test_utils.py | 15 ++++++- tests/cyl/test_cyl.py | 16 ++++---- 7 files changed, 78 insertions(+), 29 deletions(-) diff --git a/discretize/operators/differential_operators.py b/discretize/operators/differential_operators.py index bbbcfed07..a7dbbdaad 100644 --- a/discretize/operators/differential_operators.py +++ b/discretize/operators/differential_operators.py @@ -13,6 +13,7 @@ av, av_extrap, make_boundary_bool, + cross2d, ) from discretize.utils.code_utils import deprecate_method, deprecate_property @@ -2237,7 +2238,12 @@ def boundary_edge_vector_integral(self): if self.dim > 2: Av *= 2 - w_cross_n = np.cross(-w, Av.T @ dA) + av_da = Av.T @ dA + + if self.dim == 2: + w_cross_n = cross2d(av_da, w) + else: + w_cross_n = np.cross(av_da, w) if self.dim == 2: return Pe.T @ sp.diags(w_cross_n, format="csr") diff --git a/discretize/unstructured_mesh.py b/discretize/unstructured_mesh.py index 5cf932ec4..408442cd0 100644 --- a/discretize/unstructured_mesh.py +++ b/discretize/unstructured_mesh.py @@ -3,7 +3,7 @@ import numpy as np import scipy.sparse as sp from scipy.spatial import KDTree -from discretize.utils import Identity, invert_blocks, spzeros +from discretize.utils import Identity, invert_blocks, spzeros, cross2d from discretize.base import BaseMesh from discretize._extensions.simplex_helpers import ( _build_faces_edges, @@ -264,7 +264,7 @@ def face_normals(self): # NOQA D102 if self.dim == 2: # Take the normal as being the cross product of edge_tangents # and a unit vector in a "3rd" dimension. - normal = np.cross(self.edge_tangents, [0, 0, 1])[:, :-1] + normal = np.c_[self.edge_tangents[:, 1], -self.edge_tangents[:, 0]] else: # define normal as |01 x 02| # therefore clockwise path about the normal is 0->1->2->0 @@ -346,7 +346,7 @@ def edge_curl(self): # NOQA D102 # cp = np.cross(l01, -l20) # cp is a bunch of 1s (where simplices are CCW) and -1s (where simplices are CW) # (but we take the sign here to guard against numerical precision) - cp = np.sign(np.cross(l20, l01)) + cp = np.sign(cross2d(l20, l01)) face_areas = face_areas * cp # don't due *= here @@ -812,10 +812,7 @@ def get_interpolation_matrix( # NOQA D102 n_items = self.n_edges elif location_type[:-2] == "faces": # grab the barycentric transforms associated with each simplex: - ts = transform[ - inds, - :, - ] + ts = transform[inds, :] ts = np.hstack((ts, -ts.sum(axis=1)[:, None])) # use Whitney 2 - form basis functions for face vector interp faces = self._simplex_faces[inds] @@ -823,18 +820,26 @@ def get_interpolation_matrix( # NOQA D102 # [1, 2], [0, 2], [0, 1] if self.dim == 2: + # i j k + # t0 t1 t2 + # 0 0 1 + # t1 * i - t0 * j + f0 = ( - barys[:, 1] * (np.cross(ts[:, 2], [0, 0, 1])[:, i_dir]) - + barys[:, 2] * (np.cross([0, 0, 1], ts[:, 1])[:, i_dir]) - ) * areas[faces[:, 0]] + cross2d(barys[:, 1:], ts[:, 1:, 1 - i_dir]) * areas[faces[:, 0]] + ) f1 = ( - barys[:, 0] * (np.cross(ts[:, 2], [0, 0, 1])[:, i_dir]) - + barys[:, 2] * (np.cross([0, 0, 1], ts[:, 0])[:, i_dir]) - ) * areas[faces[:, 1]] + cross2d(barys[:, [0, 2]], ts[:, [0, 2], 1 - i_dir]) + * areas[faces[:, 1]] + ) f2 = ( - barys[:, 0] * (np.cross(ts[:, 1], [0, 0, 1])[:, i_dir]) - + barys[:, 1] * (np.cross([0, 0, 1], ts[:, 0])[:, i_dir]) - ) * areas[faces[:, 2]] + cross2d(barys[:, :-1], ts[:, :-1, 1 - i_dir]) + * areas[faces[:, 2]] + ) + if i_dir == 1: + f0 *= -1 + f1 *= -1 + f2 *= -1 Aij = np.c_[f0, f1, f2].reshape(-1) ind_ptr = 3 * np.arange(n_loc + 1) else: @@ -1219,7 +1224,7 @@ def boundary_edge_vector_integral(self): # NOQA D102 Pe = self.project_edge_to_boundary_edge n_boundary_edges, n_edges = Pe.shape index = boundary_face_edges - w_cross_n = np.cross(-self.edge_tangents[index], dA) + w_cross_n = cross2d(dA, self.edge_tangents[index]) M_be = ( sp.csr_matrix((w_cross_n, (index, index)), shape=(n_edges, n_edges)) @ Pe.T diff --git a/discretize/utils/__init__.py b/discretize/utils/__init__.py index 5f4da7b9c..9a1dcffc7 100644 --- a/discretize/utils/__init__.py +++ b/discretize/utils/__init__.py @@ -77,6 +77,7 @@ invert_blocks make_property_tensor inverse_property_tensor + cross2d Mesh Utilities -------------- @@ -165,6 +166,7 @@ inv2X2BlockDiagonal, makePropertyTensor, invPropertyTensor, + cross2d, ) from discretize.utils.mesh_utils import ( meshTensor, diff --git a/discretize/utils/matrix_utils.py b/discretize/utils/matrix_utils.py index 75d922723..563f2509f 100644 --- a/discretize/utils/matrix_utils.py +++ b/discretize/utils/matrix_utils.py @@ -1434,6 +1434,31 @@ def inverse_property_tensor(mesh, tensor, return_matrix=False, **kwargs): return T +def cross2d(x, y): + """Compute the cross product of two vectors. + + This function will calculate the cross product as if + the third component of each of these vectors was zero. + + The returned direction is perpendicular to both inputs, + making it be solely in the third dimension. + + Parameters + ---------- + x, y : array_like + The vectors for the cross product. + + Returns + ------- + x_cross_y : numpy.ndarray + The cross product of x and y. + """ + x = np.asarray(x) + y = np.asarray(y) + # np.cross(x, y) is deprecated for 2D input + return x[..., 0] * y[..., 1] - x[..., 1] * y[..., 0] + + class Zero(object): """Carries out arithmetic operations between 0 and arbitrary quantities. diff --git a/meson.build b/meson.build index f48d064e3..eb8cd0e77 100644 --- a/meson.build +++ b/meson.build @@ -14,7 +14,7 @@ print(get_version())''' ).stdout().strip(), license: 'MIT', - meson_version: '>= 1.1.0', + meson_version: '>= 1.4.0', default_options: [ 'buildtype=debugoptimized', 'b_ndebug=if-release', diff --git a/tests/base/test_utils.py b/tests/base/test_utils.py index bf4ddc5b8..7c950e058 100644 --- a/tests/base/test_utils.py +++ b/tests/base/test_utils.py @@ -22,6 +22,7 @@ mesh_builder_xyz, refine_tree_xyz, unpack_widths, + cross2d, ) import discretize @@ -568,8 +569,8 @@ def test_active_from_xyz(self): mesh_tree, topo3D, grid_reference="N", method="nearest" ) - self.assertIn(indtopoCC.sum(), [6292, 6299]) - self.assertIn(indtopoN.sum(), [4632, 4639]) + self.assertIn(indtopoCC.sum(), [6285, 6292, 6299]) + self.assertIn(indtopoN.sum(), [4625, 4632, 4639]) # Test 3D CYL Mesh ncr = 10 # number of mesh cells in r @@ -607,5 +608,15 @@ def test_active_from_xyz(self): ) +def test_cross2d(): + x = np.linspace(3, 4, 20).reshape(10, 2) + y = np.linspace(1, 2, 20).reshape(10, 2) + + x_boost = np.c_[x, np.zeros(10)] + y_boost = np.c_[y, np.zeros(10)] + + np.testing.assert_allclose(np.cross(x_boost, y_boost)[:, -1], cross2d(x, y)) + + if __name__ == "__main__": unittest.main() diff --git a/tests/cyl/test_cyl.py b/tests/cyl/test_cyl.py index 1b239bf7a..e33d431b6 100644 --- a/tests/cyl/test_cyl.py +++ b/tests/cyl/test_cyl.py @@ -161,8 +161,8 @@ def test_getInterpMatCartMesh_Faces(self): fycc = Mr.aveFy2CC * Mr.reshape(frect, "F", "Fy") fzcc = Mr.reshape(frect, "F", "Fz") - indX = utils.closest_points_index(Mr, [0.45, -0.2, 0.5]) - indY = utils.closest_points_index(Mr, [-0.2, 0.45, 0.5]) + indX = utils.closest_points_index(Mr, [0.45, -0.2, 0.5])[0] + indY = utils.closest_points_index(Mr, [-0.2, 0.45, 0.5])[0] TOL = 1e-2 assert np.abs(float(fxcc[indX]) - 1) < TOL @@ -194,8 +194,8 @@ def test_getInterpMatCartMesh_Faces2Edges(self): eycc = Mr.aveEy2CC * Mr.reshape(ecart, "E", "Ey") ezcc = Mr.reshape(ecart, "E", "Ez") - indX = utils.closest_points_index(Mr, [0.45, -0.2, 0.5]) - indY = utils.closest_points_index(Mr, [-0.2, 0.45, 0.5]) + indX = utils.closest_points_index(Mr, [0.45, -0.2, 0.5])[0] + indY = utils.closest_points_index(Mr, [-0.2, 0.45, 0.5])[0] TOL = 1e-2 assert np.abs(float(excc[indX]) - 1) < TOL @@ -225,8 +225,8 @@ def test_getInterpMatCartMesh_Edges(self): eycc = Mr.aveEy2CC * Mr.reshape(ecart, "E", "Ey") ezcc = Mr.aveEz2CC * Mr.reshape(ecart, "E", "Ez") - indX = utils.closest_points_index(Mr, [0.45, -0.2, 0.5]) - indY = utils.closest_points_index(Mr, [-0.2, 0.45, 0.5]) + indX = utils.closest_points_index(Mr, [0.45, -0.2, 0.5])[0] + indY = utils.closest_points_index(Mr, [-0.2, 0.45, 0.5])[0] TOL = 1e-2 assert np.abs(float(excc[indX]) - 0) < TOL @@ -256,8 +256,8 @@ def test_getInterpMatCartMesh_Edges2Faces(self): eycc = Mr.aveFy2CC * Mr.reshape(frect, "F", "Fy") ezcc = Mr.reshape(frect, "F", "Fz") - indX = utils.closest_points_index(Mr, [0.45, -0.2, 0.5]) - indY = utils.closest_points_index(Mr, [-0.2, 0.45, 0.5]) + indX = utils.closest_points_index(Mr, [0.45, -0.2, 0.5])[0] + indY = utils.closest_points_index(Mr, [-0.2, 0.45, 0.5])[0] TOL = 1e-2 assert np.abs(float(excc[indX]) - 0) < TOL From d25a757f084fe384fb902453768305b078310a3e Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Fri, 18 Oct 2024 00:02:49 -0600 Subject: [PATCH 80/97] update build images --- .ci/azure/wheels.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.ci/azure/wheels.yml b/.ci/azure/wheels.yml index ea25e794e..aa6f71f9f 100644 --- a/.ci/azure/wheels.yml +++ b/.ci/azure/wheels.yml @@ -13,15 +13,15 @@ jobs: image: 'Ubuntu-20.04' CIBW_BUILD: 'cp312-*' osx-Python310: - image: 'macOS-12' + image: 'macOS-14' CIBW_BUILD: 'cp310-*' CIBW_ARCHS_MACOS: 'x86_64 arm64' osx-Python311: - image: 'macOS-12' + image: 'macOS-14' CIBW_BUILD: 'cp311-*' CIBW_ARCHS_MACOS: 'x86_64 arm64' osx-Python312: - image: 'macOS-12' + image: 'macOS-14' CIBW_BUILD: 'cp312-*' CIBW_ARCHS_MACOS: 'x86_64 arm64' win-Python310: @@ -50,8 +50,8 @@ jobs: - bash: | set -o errexit - python3 -m pip install --upgrade pip - pip3 install cibuildwheel==2.20.0 + python -m pip install --upgrade pip + python -m pip install cibuildwheel==2.21.3 displayName: Install dependencies - bash: cibuildwheel --output-dir wheelhouse . From 192352af8fc4459e0e11f3eb3507118fbbb31f8a Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Fri, 18 Oct 2024 00:16:12 -0600 Subject: [PATCH 81/97] add github action to build wheels --- .github/workflow/build_distributions.yml | 63 ++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .github/workflow/build_distributions.yml diff --git a/.github/workflow/build_distributions.yml b/.github/workflow/build_distributions.yml new file mode 100644 index 000000000..fe5c6156c --- /dev/null +++ b/.github/workflow/build_distributions.yml @@ -0,0 +1,63 @@ +name: Build + +on: [push, pull_request] + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + # macos-13 is an intel runner, macos-14 is apple silicon + os: [ubuntu-latest, windows-latest, macos-13, macos-14] + + steps: + - uses: actions/checkout@v4 + + - name: Build wheels + uses: pypa/cibuildwheel@v2.21.3 + # env: + # CIBW_SOME_OPTION: value + # ... + # with: + # package-dir: . + # output-dir: wheelhouse + # config-file: "{package}/pyproject.toml" + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl + + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build sdist + run: pipx run build --sdist + + - uses: actions/upload-artifact@v4 + with: + name: cibw-sdist + path: dist/*.tar.gz + + upload_pypi: + needs: [build_wheels, build_sdist] + runs-on: ubuntu-latest + environment: pypi + permissions: + id-token: write + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + steps: + - uses: actions/download-artifact@v4 + with: + # unpacks all CIBW artifacts into dist/ + pattern: cibw-* + path: dist + merge-multiple: true + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + # To test: repository-url: https://test.pypi.org/legacy/ \ No newline at end of file From e6ed7d7f0c319366bf09eff8572b9c547fa77994 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Fri, 18 Oct 2024 00:17:47 -0600 Subject: [PATCH 82/97] Create build_distributions.yml --- .github/workflows/build_distributions.yml | 63 +++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .github/workflows/build_distributions.yml diff --git a/.github/workflows/build_distributions.yml b/.github/workflows/build_distributions.yml new file mode 100644 index 000000000..6cee15ec1 --- /dev/null +++ b/.github/workflows/build_distributions.yml @@ -0,0 +1,63 @@ +ame: Build + +on: [push, pull_request] + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + # macos-13 is an intel runner, macos-14 is apple silicon + os: [ubuntu-latest, windows-latest, macos-13, macos-14] + + steps: + - uses: actions/checkout@v4 + + - name: Build wheels + uses: pypa/cibuildwheel@v2.21.3 + # env: + # CIBW_SOME_OPTION: value + # ... + # with: + # package-dir: . + # output-dir: wheelhouse + # config-file: "{package}/pyproject.toml" + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl + + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build sdist + run: pipx run build --sdist + + - uses: actions/upload-artifact@v4 + with: + name: cibw-sdist + path: dist/*.tar.gz + + upload_pypi: + needs: [build_wheels, build_sdist] + runs-on: ubuntu-latest + environment: pypi + permissions: + id-token: write + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + steps: + - uses: actions/download-artifact@v4 + with: + # unpacks all CIBW artifacts into dist/ + pattern: cibw-* + path: dist + merge-multiple: true + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + # To test: repository-url: https://test.pypi.org/legacy/ From 60558295bc58c2efd6422f4cba0375fbea9b82bd Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Fri, 18 Oct 2024 00:19:57 -0600 Subject: [PATCH 83/97] update build_distributions --- .github/workflows/build_distributions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_distributions.yml b/.github/workflows/build_distributions.yml index 6cee15ec1..9da87ff41 100644 --- a/.github/workflows/build_distributions.yml +++ b/.github/workflows/build_distributions.yml @@ -1,4 +1,4 @@ -ame: Build +name: Build Distribution artifacts on: [push, pull_request] From 9dc97c125e65889169e1294cb30ac46eb69f1ee7 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Fri, 18 Oct 2024 00:21:18 -0600 Subject: [PATCH 84/97] comment out the `with` --- .github/workflows/build_distributions.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_distributions.yml b/.github/workflows/build_distributions.yml index 9da87ff41..65836701c 100644 --- a/.github/workflows/build_distributions.yml +++ b/.github/workflows/build_distributions.yml @@ -59,5 +59,5 @@ jobs: merge-multiple: true - uses: pypa/gh-action-pypi-publish@release/v1 - with: - # To test: repository-url: https://test.pypi.org/legacy/ + # with: + # To test: repository-url: https://test.pypi.org/legacy/ From 3f31bf848088b70ba9afbb75fdb8ae6d63e8e214 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Fri, 18 Oct 2024 00:24:14 -0600 Subject: [PATCH 85/97] get the fetch depth (for setuptools_scm) --- .github/workflows/build_distributions.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build_distributions.yml b/.github/workflows/build_distributions.yml index 65836701c..6dff9ce6c 100644 --- a/.github/workflows/build_distributions.yml +++ b/.github/workflows/build_distributions.yml @@ -13,6 +13,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Build wheels uses: pypa/cibuildwheel@v2.21.3 @@ -34,6 +36,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Build sdist run: pipx run build --sdist From fa7f4fb52157cfd04a5c22c54d2ccb052c703376 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Fri, 18 Oct 2024 00:27:22 -0600 Subject: [PATCH 86/97] use visual studio compilers on windows wheels --- pyproject.toml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 7fe69ddea..c4b6f5fb9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -117,6 +117,13 @@ build-verbosity = "3" # test importing discretize to make sure externals are loadable. test-command = 'python -c "import discretize; print(discretize.__version__)"' + +# use the visual studio compilers +[tool.cibuildwheel.windows.config-settings] +setup-args = [ + '--vsenv' +] + [tool.coverage.run] branch = true source = ["discretize", "tests", "examples", "tutorials"] From a944b4b3cbff01f064cfcba6f6a9d3857e25674e Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Fri, 18 Oct 2024 01:06:31 -0600 Subject: [PATCH 87/97] remove wheel and sdist steps from azure --- .ci/azure/deploy.yml | 24 +---------------- .ci/azure/wheels.yml | 63 -------------------------------------------- azure-pipelines.yml | 21 ++------------- 3 files changed, 3 insertions(+), 105 deletions(-) delete mode 100644 .ci/azure/wheels.yml diff --git a/.ci/azure/deploy.yml b/.ci/azure/deploy.yml index e81a4bb3e..23e8d851c 100644 --- a/.ci/azure/deploy.yml +++ b/.ci/azure/deploy.yml @@ -1,6 +1,6 @@ jobs: - job: - displayName: "Deploy Docs and source" + displayName: "Deploy Docs" pool: vmImage: ubuntu-latest @@ -14,19 +14,6 @@ jobs: echo $BRANCH_NAME displayName: Report branch parameters - # Just download all of the items already built - - task: DownloadPipelineArtifact@2 - inputs: - buildType: 'current' - artifactName: 'wheels' - targetPath: 'dist' - - - task: DownloadPipelineArtifact@2 - inputs: - buildType: 'current' - artifactName: 'source_dist' - targetPath: 'dist' - - task: DownloadPipelineArtifact@2 inputs: buildType: 'current' @@ -34,7 +21,6 @@ jobs: targetPath: 'html' - bash: | - ls -l dist ls -l html displayName: Report downloaded cache contents. @@ -47,14 +33,6 @@ jobs: GH_NAME: $(gh.name) GH_EMAIL: $(gh.email) - - bash: | - twine upload --skip-existing dist/* - displayName: Deploy source and wheels - condition: eq(variables.IS_TAG, true) - env: - TWINE_USERNAME: $(twine.username) - TWINE_PASSWORD: $(twine.password) - # upload documentation to discretize-docs gh-pages on tags - bash: | git clone -q --branch gh-pages --depth 1 https://${GH_TOKEN}@github.com/simpeg/discretize-docs.git diff --git a/.ci/azure/wheels.yml b/.ci/azure/wheels.yml deleted file mode 100644 index aa6f71f9f..000000000 --- a/.ci/azure/wheels.yml +++ /dev/null @@ -1,63 +0,0 @@ -jobs: -- job: - displayName: "Build wheels on ${{ variables.image }}" - strategy: - matrix: - linux-Python310: - image: 'Ubuntu-20.04' - CIBW_BUILD: 'cp310-*' - linux-Python311: - image: 'Ubuntu-20.04' - CIBW_BUILD: 'cp311-*' - linux-Python312: - image: 'Ubuntu-20.04' - CIBW_BUILD: 'cp312-*' - osx-Python310: - image: 'macOS-14' - CIBW_BUILD: 'cp310-*' - CIBW_ARCHS_MACOS: 'x86_64 arm64' - osx-Python311: - image: 'macOS-14' - CIBW_BUILD: 'cp311-*' - CIBW_ARCHS_MACOS: 'x86_64 arm64' - osx-Python312: - image: 'macOS-14' - CIBW_BUILD: 'cp312-*' - CIBW_ARCHS_MACOS: 'x86_64 arm64' - win-Python310: - image: 'windows-2019' - CIBW_BUILD: 'cp310-*' - CIBW_ARCHS_WINDOWS: 'AMD64' - CIBW_CONFIG_SETTINGS: 'setup-args=--vsenv' - win-Python311: - image: 'windows-2019' - CIBW_BUILD: 'cp311-*' - CIBW_ARCHS_WINDOWS: 'AMD64' - CIBW_CONFIG_SETTINGS: 'setup-args=--vsenv' - win-Python312: - image: 'windows-2019' - CIBW_BUILD: 'cp312-*' - CIBW_ARCHS_WINDOWS: 'AMD64' - CIBW_CONFIG_SETTINGS: 'setup-args=--vsenv' - pool: - vmImage: $(image) - steps: - - task: UsePythonVersion@0 - - - bash: - git fetch --tags - displayName: Fetch tags - - - bash: | - set -o errexit - python -m pip install --upgrade pip - python -m pip install cibuildwheel==2.21.3 - displayName: Install dependencies - - - bash: cibuildwheel --output-dir wheelhouse . - displayName: Build wheels - - - task: PublishBuildArtifacts@1 - inputs: - PathtoPublish: 'wheelhouse' - ArtifactName: 'wheels' \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 52e36b6e5..b09e99393 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -36,27 +36,10 @@ stages: jobs: - template: .ci/azure/docs.yml - - stage: BuildWheels - dependsOn: - - Testing - - DocBuild - displayName: "Build Wheels" - jobs: - - template: .ci/azure/wheels.yml - - - stage: BuildSource - dependsOn: - - Testing - - DocBuild - displayName: "Build Source distribution" - jobs: - - template: .ci/azure/sdist.yml - - stage: Deploy - displayName: "Deploy Source, Wheels, and Docs" + displayName: "Deploy Docs" dependsOn: - - BuildWheels - - BuildSource + - DocBuild condition: and(succeeded(), or(eq(variables.IS_TAG, true), eq(variables.IS_MAIN, true))) jobs: - template: .ci/azure/deploy.yml From 7bef98199cd55d9b07953af88ada17694ce921a4 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Tue, 22 Oct 2024 16:36:17 -0600 Subject: [PATCH 88/97] avoid nastily creating a modified environment file per python version --- .ci/azure/setup_env.sh | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.ci/azure/setup_env.sh b/.ci/azure/setup_env.sh index e012bb0ca..bd21687a5 100755 --- a/.ci/azure/setup_env.sh +++ b/.ci/azure/setup_env.sh @@ -13,11 +13,8 @@ then fi fi -cp .ci/environment_test.yml environment_test_with_pyversion.yml -echo " - python="$PYTHON_VERSION >> environment_test_with_pyversion.yml - -conda env create --file environment_test_with_pyversion.yml -rm environment_test_with_pyversion.yml +conda create -n discretize-test python=$PYTHON_VERSION +conda env update --name discretize-test --file .ci/environment_test.yml --prune if ${is_azure} then From fc915950e0b8605b0e6a28f9b906400156d35f39 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Tue, 22 Oct 2024 16:39:02 -0600 Subject: [PATCH 89/97] use variable instead of repeating the name --- .ci/azure/setup_env.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.ci/azure/setup_env.sh b/.ci/azure/setup_env.sh index bd21687a5..32d54f482 100755 --- a/.ci/azure/setup_env.sh +++ b/.ci/azure/setup_env.sh @@ -13,15 +13,17 @@ then fi fi -conda create -n discretize-test python=$PYTHON_VERSION -conda env update --name discretize-test --file .ci/environment_test.yml --prune +env_name="discretize-test" + +conda create -n $env_name python=$PYTHON_VERSION +conda env update --name $env_name --file .ci/environment_test.yml --prune if ${is_azure} then - source activate discretize-test + source activate $env_name pip install pytest-azurepipelines else - conda activate discretize-test + conda activate $env_name fi # The --vsenv config setting will prefer msvc compilers on windows. From 9c4693e38e7a12b2f39bf3506de03141db01e933 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Tue, 22 Oct 2024 16:40:54 -0600 Subject: [PATCH 90/97] add fallback method for finding numpy include directory --- discretize/_extensions/meson.build | 32 ++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/discretize/_extensions/meson.build b/discretize/_extensions/meson.build index 907dd0658..a941b78a2 100644 --- a/discretize/_extensions/meson.build +++ b/discretize/_extensions/meson.build @@ -1,6 +1,34 @@ # NumPy include directory -np_dep = dependency('numpy') -numpy_nodepr_api = ['-DNPY_NO_DEPRECATED_API=NPY_1_9_API_VERSION'] +numpy_nodepr_api = ['-DNPY_NO_DEPRECATED_API=NPY_1_22_API_VERSION'] +np_dep = dependency('numpy', required: false) +if not np_dep.found() + # For cross-compilation it is often not possible to run the Python interpreter + # in order to retrieve numpy's include directory. It can be specified in the + # cross file instead: + # [properties] + # numpy-include-dir = /abspath/to/host-pythons/site-packages/numpy/core/include + # + # This uses the path as is, and avoids running the interpreter. + incdir_numpy = meson.get_external_property('numpy-include-dir', 'not-given') + if incdir_numpy == 'not-given' + incdir_numpy = run_command(py, + [ + '-c', + '''import os +import numpy as np +try: + incdir = os.path.relpath(np.get_include()) +except Exception: + incdir = np.get_include() +print(incdir) + ''' + ], + check: true + ).stdout().strip() + endif + inc_np = include_directories(incdir_numpy) + np_dep = declare_dependency(include_directories: inc_np) +endif # Deal with M_PI & friends; add `use_math_defines` to c_args or cpp_args # Cython doesn't always get this right itself (see, e.g., gh-16800), so From ffb24fcd371926a6b12a9415c03685a50605fded Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Tue, 22 Oct 2024 16:42:14 -0600 Subject: [PATCH 91/97] add lines to build in freethreaded mode when cython 3.1 is released --- discretize/_extensions/meson.build | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/discretize/_extensions/meson.build b/discretize/_extensions/meson.build index a941b78a2..bb41a64ff 100644 --- a/discretize/_extensions/meson.build +++ b/discretize/_extensions/meson.build @@ -49,6 +49,11 @@ if cy_line_trace cython_c_args += ['-DCYTHON_TRACE_NOGIL=1'] endif +cython_args = [] +if cy.version().version_compare('>=3.1.0') + cython_args += ['-Xfreethreading_compatible=True'] +endif + cython_cpp_args = cython_c_args module_path = 'discretize/_extensions' @@ -56,6 +61,7 @@ module_path = 'discretize/_extensions' py.extension_module( 'interputils_cython', 'interputils_cython.pyx', + cython_args: cython_args, c_args: cython_c_args, install: true, subdir: module_path, @@ -65,6 +71,7 @@ py.extension_module( py.extension_module( 'tree_ext', ['tree_ext.pyx' , 'tree.cpp', 'geom.cpp'], + cython_args: cython_args, cpp_args: cython_cpp_args, install: true, subdir: module_path, @@ -75,6 +82,7 @@ py.extension_module( py.extension_module( 'simplex_helpers', 'simplex_helpers.pyx', + cython_args: cython_args, cpp_args: cython_cpp_args, install: true, subdir: module_path, From 42fa9ccfc1332aa93f3d1ca25f54189630d45887 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Tue, 22 Oct 2024 16:43:09 -0600 Subject: [PATCH 92/97] remove extra file --- .github/workflow/build_distributions.yml | 63 ------------------------ 1 file changed, 63 deletions(-) delete mode 100644 .github/workflow/build_distributions.yml diff --git a/.github/workflow/build_distributions.yml b/.github/workflow/build_distributions.yml deleted file mode 100644 index fe5c6156c..000000000 --- a/.github/workflow/build_distributions.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Build - -on: [push, pull_request] - -jobs: - build_wheels: - name: Build wheels on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - # macos-13 is an intel runner, macos-14 is apple silicon - os: [ubuntu-latest, windows-latest, macos-13, macos-14] - - steps: - - uses: actions/checkout@v4 - - - name: Build wheels - uses: pypa/cibuildwheel@v2.21.3 - # env: - # CIBW_SOME_OPTION: value - # ... - # with: - # package-dir: . - # output-dir: wheelhouse - # config-file: "{package}/pyproject.toml" - - - uses: actions/upload-artifact@v4 - with: - name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} - path: ./wheelhouse/*.whl - - build_sdist: - name: Build source distribution - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Build sdist - run: pipx run build --sdist - - - uses: actions/upload-artifact@v4 - with: - name: cibw-sdist - path: dist/*.tar.gz - - upload_pypi: - needs: [build_wheels, build_sdist] - runs-on: ubuntu-latest - environment: pypi - permissions: - id-token: write - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') - steps: - - uses: actions/download-artifact@v4 - with: - # unpacks all CIBW artifacts into dist/ - pattern: cibw-* - path: dist - merge-multiple: true - - - uses: pypa/gh-action-pypi-publish@release/v1 - with: - # To test: repository-url: https://test.pypi.org/legacy/ \ No newline at end of file From 85fcf29c982c50120b96e089d1cc5ece3d048c67 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Wed, 23 Oct 2024 09:08:52 -0600 Subject: [PATCH 93/97] add --yes argument to environment initialization --- .ci/azure/setup_env.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/azure/setup_env.sh b/.ci/azure/setup_env.sh index 32d54f482..ac35b042e 100755 --- a/.ci/azure/setup_env.sh +++ b/.ci/azure/setup_env.sh @@ -15,7 +15,7 @@ fi env_name="discretize-test" -conda create -n $env_name python=$PYTHON_VERSION +conda create --yes -n $env_name python=$PYTHON_VERSION conda env update --name $env_name --file .ci/environment_test.yml --prune if ${is_azure} From 603925f0b979f67802d739221d16e2cd0605afd5 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Wed, 23 Oct 2024 09:10:43 -0600 Subject: [PATCH 94/97] Make deploy step depend testing and doc building --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b09e99393..b05c304a5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -39,6 +39,7 @@ stages: - stage: Deploy displayName: "Deploy Docs" dependsOn: + - Testing - DocBuild condition: and(succeeded(), or(eq(variables.IS_TAG, true), eq(variables.IS_MAIN, true))) jobs: From 60868b90e097f194472c6db2d6c88e1d1f8546d5 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Wed, 23 Oct 2024 10:43:31 -0600 Subject: [PATCH 95/97] install initial python from conda-forge --- .ci/azure/setup_env.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/azure/setup_env.sh b/.ci/azure/setup_env.sh index ac35b042e..89abe5cbf 100755 --- a/.ci/azure/setup_env.sh +++ b/.ci/azure/setup_env.sh @@ -15,7 +15,7 @@ fi env_name="discretize-test" -conda create --yes -n $env_name python=$PYTHON_VERSION +conda create --yes -n $env_name -c conda-forge python=$PYTHON_VERSION conda env update --name $env_name --file .ci/environment_test.yml --prune if ${is_azure} From 2e209fc1bbe457c053fa903f0bb7ce01f1adf57d Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Wed, 23 Oct 2024 10:54:48 -0600 Subject: [PATCH 96/97] revert environment creation changes --- .ci/azure/setup_env.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.ci/azure/setup_env.sh b/.ci/azure/setup_env.sh index 89abe5cbf..e012bb0ca 100755 --- a/.ci/azure/setup_env.sh +++ b/.ci/azure/setup_env.sh @@ -13,17 +13,18 @@ then fi fi -env_name="discretize-test" +cp .ci/environment_test.yml environment_test_with_pyversion.yml +echo " - python="$PYTHON_VERSION >> environment_test_with_pyversion.yml -conda create --yes -n $env_name -c conda-forge python=$PYTHON_VERSION -conda env update --name $env_name --file .ci/environment_test.yml --prune +conda env create --file environment_test_with_pyversion.yml +rm environment_test_with_pyversion.yml if ${is_azure} then - source activate $env_name + source activate discretize-test pip install pytest-azurepipelines else - conda activate $env_name + conda activate discretize-test fi # The --vsenv config setting will prefer msvc compilers on windows. From 40501f045194375f1c8500ad13d51164dd752569 Mon Sep 17 00:00:00 2001 From: Joseph Capriotti Date: Wed, 23 Oct 2024 18:52:39 -0600 Subject: [PATCH 97/97] rely on meson for numpy dependency --- discretize/_extensions/meson.build | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/discretize/_extensions/meson.build b/discretize/_extensions/meson.build index bb41a64ff..3da7263c9 100644 --- a/discretize/_extensions/meson.build +++ b/discretize/_extensions/meson.build @@ -1,34 +1,6 @@ # NumPy include directory numpy_nodepr_api = ['-DNPY_NO_DEPRECATED_API=NPY_1_22_API_VERSION'] -np_dep = dependency('numpy', required: false) -if not np_dep.found() - # For cross-compilation it is often not possible to run the Python interpreter - # in order to retrieve numpy's include directory. It can be specified in the - # cross file instead: - # [properties] - # numpy-include-dir = /abspath/to/host-pythons/site-packages/numpy/core/include - # - # This uses the path as is, and avoids running the interpreter. - incdir_numpy = meson.get_external_property('numpy-include-dir', 'not-given') - if incdir_numpy == 'not-given' - incdir_numpy = run_command(py, - [ - '-c', - '''import os -import numpy as np -try: - incdir = os.path.relpath(np.get_include()) -except Exception: - incdir = np.get_include() -print(incdir) - ''' - ], - check: true - ).stdout().strip() - endif - inc_np = include_directories(incdir_numpy) - np_dep = declare_dependency(include_directories: inc_np) -endif +np_dep = dependency('numpy') # Deal with M_PI & friends; add `use_math_defines` to c_args or cpp_args # Cython doesn't always get this right itself (see, e.g., gh-16800), so