From f3c73c92ac93edb64d87e21278bf5bc776f47d50 Mon Sep 17 00:00:00 2001 From: filoferra <66474175+filoferra@users.noreply.github.com> Date: Tue, 27 Oct 2026 12:14:25 +0100 Subject: [PATCH 1/2] Add with_local_arrays! for a kernel over two Vecs with_local_arrays!(f!, g_fx, l_x) checks out both local arrays, on the device when a GPU extension is loaded, calls f!(fx, lx), and hands them back also when f! throws. local_arrays and restore_local_arrays! now warn: an exception between the two calls left both Vecs checked out. --- CHANGELOG.md | 10 +++++ scripts/renames.jl | 23 ++++++++++ src/deprecations.jl | 4 ++ src/public_names.jl | 5 ++- src/vec.jl | 95 ++++++++++++++++++++++++++------------- test/core/deprecations.jl | 13 ++++++ test/vecmat/vec.jl | 24 ++++++++++ 7 files changed, 140 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 983764038..bd2ecb1ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## Unreleased + +### Deprecated + +- `local_arrays` and `restore_local_arrays!` warn: use `with_local_arrays!`. An exception between the two calls left both Vecs checked out. + +### Added + +- `with_local_arrays!(f!, g_fx, l_x)` calls `f!(fx, lx)` with the local arrays of the two Vecs, on the device when a GPU extension is loaded, and hands them back afterwards, also when `f!` throws. + ## v0.5.4 ### Deprecated diff --git a/scripts/renames.jl b/scripts/renames.jl index 319a840c7..d522589be 100644 --- a/scripts/renames.jl +++ b/scripts/renames.jl @@ -700,6 +700,26 @@ const EXTRA_SHIMS = NamedTuple{(:name, :code, :test), Tuple{String, String, Stri foreach(PETSc.destroy!, (y, x, A)) end """, + ), ( + name = "local_arrays and restore_local_arrays!", + code = """ + # The pair warns in src/vec.jl: `with_local_arrays!` replaces it and restores + # the arrays also when the kernel throws. + """, + test = """ + @testset "local_arrays and restore_local_arrays!" begin + petsclib = PETSc.petsclibs[1] + PETSc.initialize(petsclib) + T = petsclib.PetscScalar + x = PETSc.PetscVec(petsclib, T[1, 2]) + fx = PETSc.PetscVec(petsclib, 2) + arrays = warns(() -> PETSc.local_arrays(petsclib, fx, x)) + arrays[1] .= 3 .* arrays[2] + @test warns(() -> PETSc.restore_local_arrays!(petsclib, fx, x, arrays...)) === nothing + @test fx[:] == T[3, 6] + foreach(PETSc.destroy!, (fx, x)) + end + """, ), ] @@ -708,6 +728,8 @@ const EXTRA_SHIMS = NamedTuple{(:name, :code, :test), Tuple{String, String, Stri # --------------------------------------------------------------------------- const INTERNAL = Set{Symbol}([ + # the checkout behind with_local_arrays! and the deprecated local_arrays + :checkout_local_arrays, # the per-library body of the MatShell constructor :create_matshell, # leading underscore dropped (naming.md "Internals") @@ -990,6 +1012,7 @@ const UNCHANGED_PUBLIC = Symbol[ :PetscNotInitialized, :ConvergenceError, # new in v0.5.3 :with_options, # new in v0.5.3 + :with_local_arrays!, # new in v0.5.5 :with_destroy, # new in v0.5.3 :track!, # new in v0.5.3 :untrack!, # new in v0.5.3 diff --git a/src/deprecations.jl b/src/deprecations.jl index df02100fc..3755e2092 100644 --- a/src/deprecations.jl +++ b/src/deprecations.jl @@ -424,6 +424,10 @@ function MatShell( throw(ArgumentError("MatShell takes the operator first, then the library")) end +# local_arrays and restore_local_arrays! +# The pair warns in src/vec.jl: `with_local_arrays!` replaces it and restores +# the arrays also when the kernel throws. + # Renamed types: a `const` alias, so `isa` and dispatch keep working diff --git a/src/public_names.jl b/src/public_names.jl index ff47bb400..664c6f5ab 100644 --- a/src/public_names.jl +++ b/src/public_names.jl @@ -44,5 +44,6 @@ public @bd_fn, @jacobian_fn, @residual_fn, @simple_fn, AO, AbstractAO, AbstractI solve_time, star_fd_coloring, stencil, step!, step_number, step_rejections, tao_usable_after_reinitialize, timestep, tolerances, track!, type_name, unsafe_local_array, unset_library!, untrack!, user_ctx, vertex_location, - vtk_merge_tensor, with_destroy, with_field_views!, with_local_array!, with_options, - with_product_coordinates, wrap_local_array, zero_rows!, zero_rows_local! + vtk_merge_tensor, with_destroy, with_field_views!, with_local_array!, + with_local_arrays!, with_options, with_product_coordinates, wrap_local_array, + zero_rows!, zero_rows_local! diff --git a/src/vec.jl b/src/vec.jl index 954ae270c..3f2dff9d6 100644 --- a/src/vec.jl +++ b/src/vec.jl @@ -891,38 +891,69 @@ function LinearAlgebra.norm(v::AbstractPetscVec, p::Real) throw(ArgumentError("PETSc computes the vector norm only for p = 1, 2 and Inf, got p = $p")) end -# ── GPU-aware array access helpers ──────────────────────────────────────────── -# -# `local_arrays` calls `VecGetArrayAndMemType` on both Vecs, converts the -# returned `PetscMemType` values to backend singletons, and dispatches to -# `_local_arrays`. The base package handles the pure-CPU case -# (host × host (both backends nothing)). GPU extensions add `_local_arrays` -# methods for their backend combinations and a matching -# `_restore_local_arrays!` method dispatched by `restore_local_arrays!`. -# -# Return tuple: (fx, lx, fx_arr, lx_arr, fx_bounce) -# CPU: fx, lx are plain Arrays with VecRestore finalizers; -# fx_arr = lx_arr = fx_bounce = nothing -# GPU: fx, lx are device arrays; fx_arr, lx_arr are raw PETSc arrays -# (needed for restore); fx_bounce is a scratch device array or nothing. +# ── GPU-aware array access for a pair of Vecs ───────────────────────────────── """ - local_arrays(petsclib, g_fx, l_x) -> (fx, lx, fx_arr, lx_arr, fx_bounce) + with_local_arrays!(f!, g_fx::AbstractPetscVec, l_x::AbstractPetscVec) + +Call `f!(fx, lx)` with the local array of `g_fx` (written) and of `l_x` (read), +as arrays a compute kernel can use, and hand both back to PETSc afterwards, +also when `f!` throws. Returns what `f!` returns. + +Where each array lives is decided for the pair: with a GPU backend extension +loaded and both Vecs on the device, `fx` and `lx` wrap the device memory. When +`g_fx` is on the host, `fx` is a device scratch array whose contents are copied +back into `g_fx` on return, so the kernel can run on the device either way. +Without a GPU, both are plain host arrays. + +```julia +with_local_arrays!(g_fx, l_x) do fx, lx + residual_kernel!(fx, lx) +end +``` + +# External Links +$(doc_external("Vec/VecGetArrayAndMemType")) +""" +function with_local_arrays!( + f!, + g_fx::AbstractPetscVec{PetscLib}, + l_x::AbstractPetscVec, +) where {PetscLib} + petsclib = getlib(PetscLib) + fx, lx, fx_arr, lx_arr, fx_bounce = checkout_local_arrays(petsclib, g_fx, l_x) + try + return f!(fx, lx) + finally + _restore_local_arrays!(petsclib, g_fx, l_x, fx, lx, fx_arr, lx_arr, fx_bounce) + end +end -Return arrays for `g_fx` (read-write) and `l_x` (read-only) suitable for -passing to a compute kernel. Dispatches on the memory location of each Vec -via `memtype_backend`. +""" + local_arrays(petsclib, g_fx, l_x) -> (fx, lx, fx_arr, lx_arr, fx_bounce) -On the pure-CPU path (`host × host (both backends nothing)`) `fx`/`lx` are plain -`Array`s and `fx_arr = lx_arr = fx_bounce = nothing`. When a GPU backend -extension is loaded and a Vec lives on the device the returned `fx`/`lx` are -device arrays. An optional bounce buffer `fx_bounce` is allocated when `g_fx` -is host-resident; its contents must be written back by `restore_local_arrays!` -after the kernel completes. +Check out the arrays of `g_fx` (read-write) and `l_x` (read-only) for a compute +kernel; [`restore_local_arrays!`](@ref) must hand them back. -See also: [`restore_local_arrays!`](@ref) +Deprecated: use [`with_local_arrays!`](@ref), which restores the arrays also +when the kernel throws. """ function local_arrays(petsclib, g_fx, l_x) + # TODO: remove in v0.6 with the other 0.5 deprecations. + @warn "local_arrays is deprecated, use with_local_arrays!(f!, g_fx, l_x)" maxlog = 1 + return checkout_local_arrays(petsclib, g_fx, l_x) +end + +# Checks out both arrays and picks where they live: `VecGetArrayAndMemType` gives +# each Vec's memory type, and `_local_arrays` dispatches on the pair of backends. +# The base package handles host × host; GPU extensions add the other cases and a +# matching `_restore_local_arrays!`. +# +# Returns (fx, lx, fx_arr, lx_arr, fx_bounce): +# CPU: fx, lx are the PETSc arrays; fx_arr = lx_arr = fx_bounce = nothing +# GPU: fx, lx are device arrays; fx_arr, lx_arr are the raw PETSc arrays +# (needed for restore); fx_bounce is a scratch device array or nothing. +function checkout_local_arrays(petsclib, g_fx, l_x) T = petsclib.PetscScalar fx_arr, fx_mtype = LibPETSc.VecGetArrayAndMemType(petsclib, as_petsc_vec(g_fx)) lx_arr, lx_mtype = LibPETSc.VecGetArrayReadAndMemType(petsclib, as_petsc_vec(l_x)) @@ -932,8 +963,8 @@ function local_arrays(petsclib, g_fx, l_x) ) end -# CPU base case: return arrays directly. restore_local_arrays! calls VecRestore -# explicitly — no finalizers to avoid the double-finalization crash. +# CPU base case: the PETSc arrays themselves, restored explicitly (no finalizers, +# which could restore a second time and crash PETSc). function _local_arrays( petsclib, g_fx, l_x, ::Type, fx_arr, lx_arr, ::Nothing, ::Nothing, ) @@ -943,15 +974,15 @@ end """ restore_local_arrays!(petsclib, g_fx, l_x, fx, lx, fx_arr, lx_arr, fx_bounce) -Restore PETSc Vecs after a kernel launched via [`local_arrays`](@ref). +Hand back the arrays [`local_arrays`](@ref) checked out. -Dispatches to `_restore_local_arrays!`. On the CPU path (`fx_arr`, -`lx_arr`, `fx_bounce` all `nothing`) this simply finalizes `fx` and `lx`, -triggering the registered `VecRestoreArray*AndMemType` finalizers. GPU backend -extensions add a `_restore_local_arrays!` method for their array types. +Deprecated: use [`with_local_arrays!`](@ref). """ function restore_local_arrays!(petsclib, g_fx, l_x, fx, lx, fx_arr, lx_arr, fx_bounce) + # TODO: remove in v0.6 with the other 0.5 deprecations. + @warn "restore_local_arrays! is deprecated, use with_local_arrays!(f!, g_fx, l_x)" maxlog = 1 _restore_local_arrays!(petsclib, g_fx, l_x, fx, lx, fx_arr, lx_arr, fx_bounce) + return nothing end # CPU base case: call VecRestore directly (no finalizers). diff --git a/test/core/deprecations.jl b/test/core/deprecations.jl index 4210ad2d3..1159009fd 100644 --- a/test/core/deprecations.jl +++ b/test/core/deprecations.jl @@ -354,4 +354,17 @@ const SHIMS = [ foreach(PETSc.destroy!, (y, x, A)) end + @testset "local_arrays and restore_local_arrays!" begin + petsclib = PETSc.petsclibs[1] + PETSc.initialize(petsclib) + T = petsclib.PetscScalar + x = PETSc.PetscVec(petsclib, T[1, 2]) + fx = PETSc.PetscVec(petsclib, 2) + arrays = warns(() -> PETSc.local_arrays(petsclib, fx, x)) + arrays[1] .= 3 .* arrays[2] + @test warns(() -> PETSc.restore_local_arrays!(petsclib, fx, x, arrays...)) === nothing + @test fx[:] == T[3, 6] + foreach(PETSc.destroy!, (fx, x)) + end + end diff --git a/test/vecmat/vec.jl b/test/vecmat/vec.jl index 68f49a1ae..a633fcc6e 100644 --- a/test/vecmat/vec.jl +++ b/test/vecmat/vec.jl @@ -304,6 +304,30 @@ end end end +@testset "with_local_arrays!" begin + petsclib = PETSc.petsclibs[1] + PETSc.initialize(petsclib) + PetscScalar = petsclib.PetscScalar + x = PETSc.PetscVec(petsclib, PetscScalar[1, 2, 3]) + fx = PETSc.PetscVec(petsclib, 3) + + # fx is written from x; the block's value comes back + @test PETSc.with_local_arrays!(fx, x) do f, a + f .= 2 .* a + :done + end === :done + @test fx[:] == PetscScalar[2, 4, 6] + + # a throwing block still hands both arrays back, so the Vecs stay usable + @test_throws ErrorException PETSc.with_local_arrays!((f, a) -> error("kernel failed"), fx, x) + fx .= 1 + @test fx[:] == PetscScalar[1, 1, 1] + @test x[:] == PetscScalar[1, 2, 3] + + foreach(PETSc.destroy!, (fx, x)) + PETSc.finalize(petsclib) +end + @testset "with_local_array! typed (Array)" begin for petsclib in PETSc.petsclibs PETSc.initialize(petsclib) From 69ad94b858ebddf3cdfc3a0d0e3dae2ce94a7521 Mon Sep 17 00:00:00 2001 From: filoferra <66474175+filoferra@users.noreply.github.com> Date: Tue, 27 Oct 2026 13:55:30 +0100 Subject: [PATCH 2/2] Hand back local coordinates in a do block with_local_coordinate_array(f, dm) calls f with the local coordinates of a DMDA or DMStag, addressed with global indices, and hands the array back when f returns or throws. local_coordinate_array now warns and returns a copy: its array used to be handed back by a finalizer, which crashed Julia when the DM was destroyed first. --- CHANGELOG.md | 2 ++ docs/src/man/dmstag.md | 6 ++-- docs/src/man/naming.md | 2 +- examples/dmda/porosity_waves.jl | 6 ++-- scripts/renames.jl | 21 ++++++++++++++ src/deprecations.jl | 4 +++ src/dm.jl | 51 +++++++++++++++++++++++++-------- src/public_names.jl | 4 +-- src/vec.jl | 7 +++-- test/core/deprecations.jl | 13 +++++++++ test/dm/dmda.jl | 15 +++++----- 11 files changed, 102 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd2ecb1ee..3c658075c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,12 @@ ### Deprecated - `local_arrays` and `restore_local_arrays!` warn: use `with_local_arrays!`. An exception between the two calls left both Vecs checked out. +- `local_coordinate_array(dm)` warns: use `with_local_coordinate_array(f, dm)`. It now returns a copy. It used to leave the coordinate vector checked out until the garbage collector freed the array, and if the DM was destroyed first, that late hand-back crashed Julia. ### Added - `with_local_arrays!(f!, g_fx, l_x)` calls `f!(fx, lx)` with the local arrays of the two Vecs, on the device when a GPU extension is loaded, and hands them back afterwards, also when `f!` throws. +- `with_local_coordinate_array(f, dm)` calls `f(coord)` with the local coordinates of a `DMDA` or `DMStag`, addressed with global indices, and hands the array back when `f` returns or throws. ## v0.5.4 diff --git a/docs/src/man/dmstag.md b/docs/src/man/dmstag.md index fe8a94f90..78a373c34 100644 --- a/docs/src/man/dmstag.md +++ b/docs/src/man/dmstag.md @@ -173,8 +173,10 @@ PETSc.set_uniform_coordinates!(dm, xmin, xmax) # 1D PETSc.set_uniform_coordinates!(dm, xmin, xmax, ymin, ymax) # 2D PETSc.set_uniform_coordinates!(dm, xmin, xmax, ymin, ymax, zmin, zmax) # 3D -# Get local coordinate array -coords = PETSc.local_coordinate_array(dm) +# Local coordinates as an array addressed with global indices, valid inside the block +PETSc.with_local_coordinate_array(dm) do coords + coords[1, i, j] # x of point (i, j) +end # Read the per-axis coordinates: x[i, 1] is the lower face of element i, x[i, 2] its centre PETSc.with_product_coordinates(dm) do x, y diff --git a/docs/src/man/naming.md b/docs/src/man/naming.md index ba8783bae..e47ab64ca 100644 --- a/docs/src/man/naming.md +++ b/docs/src/man/naming.md @@ -62,7 +62,7 @@ set_type!(ksp, :gmres) ghost_corners(dm) ownership_range(A) local_indices(dm) -local_coordinate_array(dm) +with_local_coordinate_array(f, dm) project_function!(vec, dm, f) ``` diff --git a/examples/dmda/porosity_waves.jl b/examples/dmda/porosity_waves.jl index 711e86ca6..2e8f9b46e 100644 --- a/examples/dmda/porosity_waves.jl +++ b/examples/dmda/porosity_waves.jl @@ -99,7 +99,7 @@ bcs = ( PETSc.DM_BOUNDARY_GHOSTED, )[1:dim] -# `corners`, `info`, `local_coordinate_array` and `reshape_local_array` are +# `corners`, `info`, `with_local_coordinate_array` and `reshape_local_array` are # dimension-correct (docs/src/man/naming.md §12): on a `dim`-dimensional DMDA # they answer with `dim`-tuples, `CartesianIndex{dim}` and `1 + dim` axes. @@ -131,7 +131,7 @@ PETSc.set_uniform_coordinates!(da, (-20, -10, -10), (150, 10, 10)) # Cache local coordinates as a plain Julia array. This avoids repeatedly # mapping/restoring PETSc coordinate vectors during AD/Jacobian evaluations. -coord_cache = copy(PETSc.local_coordinate_array(da)) +coord_cache = PETSc.with_local_coordinate_array(copy, da) # Create the PETSC snes object snes = PETSc.SNES(petsclib, comm; opts...) @@ -431,7 +431,7 @@ while (it < max_it && time < max_time) # Visualisation if (mod(it, 40) == 0 && CreatePlots == true) - coord = PETSc.local_coordinate_array(da) + coord = PETSc.with_local_coordinate_array(copy, da) if dim==1 x = collect(dof_slice(coord, 1)) time_vec = ones(size(x)) * time diff --git a/scripts/renames.jl b/scripts/renames.jl index d522589be..2c239d566 100644 --- a/scripts/renames.jl +++ b/scripts/renames.jl @@ -720,6 +720,26 @@ const EXTRA_SHIMS = NamedTuple{(:name, :code, :test), Tuple{String, String, Stri foreach(PETSc.destroy!, (fx, x)) end """, + ), ( + name = "local_coordinate_array", + code = """ + # Warns in src/dm.jl and returns a copy; `with_local_coordinate_array` hands + # the array back when its block returns. + """, + test = """ + @testset "local_coordinate_array" begin + petsclib = PETSc.petsclibs[1] + PETSc.initialize(petsclib) + B = PETSc.DM_BOUNDARY_NONE + da = PETSc.DMDA(petsclib, PETSc.MPI.COMM_SELF, (B,), (3,), 1) + PETSc.set_uniform_coordinates!(da, (0.0,), (1.0,)) + coord = warns(() -> PETSc.local_coordinate_array(da)) + @test coord[1, 3] ≈ 1 + # a copy: nothing is left checked out of the coordinate vector + PETSc.destroy!(da) + @test coord[1, 3] ≈ 1 + end + """, ), ] @@ -1013,6 +1033,7 @@ const UNCHANGED_PUBLIC = Symbol[ :ConvergenceError, # new in v0.5.3 :with_options, # new in v0.5.3 :with_local_arrays!, # new in v0.5.5 + :with_local_coordinate_array, # new in v0.5.5 :with_destroy, # new in v0.5.3 :track!, # new in v0.5.3 :untrack!, # new in v0.5.3 diff --git a/src/deprecations.jl b/src/deprecations.jl index 3755e2092..04127261d 100644 --- a/src/deprecations.jl +++ b/src/deprecations.jl @@ -428,6 +428,10 @@ end # The pair warns in src/vec.jl: `with_local_arrays!` replaces it and restores # the arrays also when the kernel throws. +# local_coordinate_array +# Warns in src/dm.jl and returns a copy; `with_local_coordinate_array` hands +# the array back when its block returns. + # Renamed types: a `const` alias, so `isa` and dispatch keep working diff --git a/src/dm.jl b/src/dm.jl index 7571d83cb..3151291aa 100644 --- a/src/dm.jl +++ b/src/dm.jl @@ -546,13 +546,44 @@ $(doc_external("DM/DMGetCoordinatesLocal")) local_coordinates(dm::AbstractPetscDM{PetscLib}) where {PetscLib} = LibPETSc.DMGetCoordinatesLocal(getlib(PetscLib), dm) +""" + with_local_coordinate_array(f, dm::Union{DMDA, DMStag}) + +Call `f(coord)` with the coordinates of the local part of `dm`, ghost points +included, and hand the array back to PETSc afterwards, also when `f` throws. +Returns what `f` returns. + +`coord` is an `OffsetArray` addressed with global indices, the coordinate +direction first: `coord[1, i, j]` is the x coordinate of point `(i, j)`. It +reads PETSc's memory, so it is valid only inside `f`; return `copy(coord)` to +keep the values. + +```julia +coord = with_local_coordinate_array(copy, da) +``` + +# External Links +$(doc_external("DM/DMGetCoordinatesLocal")) +$(doc_external("Vec/VecGetArrayRead")) +""" +function with_local_coordinate_array( + f, + dm::Union{DMDA{PetscLib}, DMStag{PetscLib}}, +) where {PetscLib} + dim = LibPETSc.DMGetCoordinateDim(PetscLib, dm) + return with_local_array!(local_coordinates(dm); write = false) do a + f(reshape_local_array(a, dm, dim)) + end +end + """ local_coordinate_array(da::Union{DMDA, DMStag}) -Return coordinate arrays for the local portion of the domain. +Return coordinate arrays for the local portion of the domain, as `OffsetArray`s +addressed with global indices, ghost points included. -The returned arrays are `OffsetArray`s that can be addressed using global indices, -accounting for ghost points. +Deprecated: use [`with_local_coordinate_array`](@ref). The arrays are a copy, +so writing to them does not change the coordinates. # External Links $(doc_external("DM/DMGetCoordinatesLocal")) @@ -560,15 +591,11 @@ $(doc_external("DM/DMGetCoordinatesLocal")) function local_coordinate_array( da::Union{DMDA{PetscLib}, DMStag{PetscLib}}, ) where {PetscLib} - # retrieve local coordinates - coord_vec = local_coordinates(da) - # array - array1D = unsafe_local_array(coord_vec; read = true, write = false) - dim = [PetscLib.PetscInt(0)] - dim = LibPETSc.DMGetCoordinateDim(PetscLib, da) - dim = dim[1] - - return reshape_local_array(array1D, da, dim) + # TODO: remove in v0.6 with the other 0.5 deprecations. + @warn "local_coordinate_array is deprecated, use with_local_coordinate_array(f, dm)" maxlog = 1 + # A copy: an array restored by a finalizer can run after `da` and its + # coordinate vector are destroyed, and restoring a freed Vec crashes. + return with_local_coordinate_array(copy, da) end diff --git a/src/public_names.jl b/src/public_names.jl index 664c6f5ab..d37177ac7 100644 --- a/src/public_names.jl +++ b/src/public_names.jl @@ -45,5 +45,5 @@ public @bd_fn, @jacobian_fn, @residual_fn, @simple_fn, AO, AbstractAO, AbstractI tao_usable_after_reinitialize, timestep, tolerances, track!, type_name, unsafe_local_array, unset_library!, untrack!, user_ctx, vertex_location, vtk_merge_tensor, with_destroy, with_field_views!, with_local_array!, - with_local_arrays!, with_options, with_product_coordinates, wrap_local_array, - zero_rows!, zero_rows_local! + with_local_arrays!, with_local_coordinate_array, with_options, + with_product_coordinates, wrap_local_array, zero_rows!, zero_rows_local! diff --git a/src/vec.jl b/src/vec.jl index 3f2dff9d6..66e92f5f9 100644 --- a/src/vec.jl +++ b/src/vec.jl @@ -439,8 +439,11 @@ Return an `Array{PetscScalar}` containing local portion of the PETSc `vec` Use `read=false` if the array is write-only; `write=false` if read-only. -!!! note - `Base.finalize` should be called on the `Array` before the data can be used. +The caller hands the array back: `vec` stays checked out, and the next +`VecGetArray` or assembly on it fails, until `Base.finalize` runs on the array +(or the garbage collector gets to it). Prefer [`with_local_array!`](@ref), which +hands it back when its block returns or throws; [`acquire_local_array`](@ref) and +[`release_local_array`](@ref) are the explicit pair with no finalizer. # External Links $(doc_external("Vec/VecGetArray")) diff --git a/test/core/deprecations.jl b/test/core/deprecations.jl index 1159009fd..69e418bde 100644 --- a/test/core/deprecations.jl +++ b/test/core/deprecations.jl @@ -367,4 +367,17 @@ const SHIMS = [ foreach(PETSc.destroy!, (fx, x)) end + @testset "local_coordinate_array" begin + petsclib = PETSc.petsclibs[1] + PETSc.initialize(petsclib) + B = PETSc.DM_BOUNDARY_NONE + da = PETSc.DMDA(petsclib, PETSc.MPI.COMM_SELF, (B,), (3,), 1) + PETSc.set_uniform_coordinates!(da, (0.0,), (1.0,)) + coord = warns(() -> PETSc.local_coordinate_array(da)) + @test coord[1, 3] ≈ 1 + # a copy: nothing is left checked out of the coordinate vector + PETSc.destroy!(da) + @test coord[1, 3] ≈ 1 + end + end diff --git a/test/dm/dmda.jl b/test/dm/dmda.jl index be07893e7..f01873826 100644 --- a/test/dm/dmda.jl +++ b/test/dm/dmda.jl @@ -532,15 +532,16 @@ end PETSc.set_uniform_coordinates!(da_2D, (xmin, ymin), (xmax, ymax)) - # Retrieve local coordinate array (shaped accordingly) - coord = PETSc.local_coordinate_array(da_2D) - - # Check + # Local coordinates, addressed with global indices corners = PETSc.corners(da_2D) - for i in ((corners.lower):(corners.upper)) - @test coord[1, i] ≈ (i[1] - 1) * Δx + xmin - @test coord[2, i] ≈ (i[2] - 1) * Δy + ymin + PETSc.with_local_coordinate_array(da_2D) do coord + for i in ((corners.lower):(corners.upper)) + @test coord[1, i] ≈ (i[1] - 1) * Δx + xmin + @test coord[2, i] ≈ (i[2] - 1) * Δy + ymin + end end + # the coordinate vector is handed back, so it can be checked out again + @test PETSc.with_local_coordinate_array(c -> c[1, corners.lower], da_2D) ≈ xmin # Retrieve local array of the 2 DOF DMDA and set values x_g = PETSc.global_vec(da_2D)