mirror of https://github.com/fredrikekre/HYPRE.jl
38 changed files with 2937 additions and 1833 deletions
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
# Runic formatting |
||||
# https://github.com/fredrikekre/HYPRE.jl/commit/640d77944e846a1f94e248bf2dea53310314f457 |
||||
640d77944e846a1f94e248bf2dea53310314f457 |
||||
# Switch from ccall() to @ccall in generated output |
||||
# https://github.com/fredrikekre/HYPRE.jl/commit/b4790048a7803298004bde24658ac90215a837a4 |
||||
b4790048a7803298004bde24658ac90215a837a4 |
||||
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates |
||||
version: 2 |
||||
updates: |
||||
- package-ecosystem: "github-actions" |
||||
directory: "/" # Location of package manifests |
||||
schedule: |
||||
interval: "monthly" |
||||
@ -0,0 +1,73 @@
@@ -0,0 +1,73 @@
|
||||
name: Code checks |
||||
|
||||
on: |
||||
pull_request: |
||||
push: |
||||
branches: ["master"] |
||||
|
||||
jobs: |
||||
|
||||
pre-commit: |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- uses: actions/checkout@v4 |
||||
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 |
||||
|
||||
explicit-imports: |
||||
runs-on: ubuntu-latest |
||||
name: "ExplicitImports.jl" |
||||
steps: |
||||
- uses: actions/checkout@v4 |
||||
# - uses: julia-actions/setup-julia@v2 |
||||
# with: |
||||
# version: '1' |
||||
- uses: julia-actions/cache@v2 |
||||
# - uses: julia-actions/julia-buildpkg@v1 |
||||
- name: Install dependencies |
||||
shell: julia --project=@explicit-imports {0} |
||||
run: | |
||||
# Add ExplicitImports.jl and packages that HYPRE has extensions for |
||||
using Pkg |
||||
Pkg.develop([ |
||||
PackageSpec(name = "HYPRE", path = pwd()), |
||||
]) |
||||
Pkg.add([ |
||||
PackageSpec(name = "ExplicitImports", version = "1.9"), |
||||
PackageSpec(name = "PartitionedArrays"), |
||||
PackageSpec(name = "SparseArrays"), |
||||
PackageSpec(name = "SparseMatricesCSR"), |
||||
]) |
||||
- name: ExplicitImports.jl code checks |
||||
shell: julia --project=@explicit-imports {0} |
||||
run: | |
||||
using HYPRE, ExplicitImports, PartitionedArrays, SparseArrays, SparseMatricesCSR |
||||
# Check HYPRE |
||||
check_no_implicit_imports(HYPRE) |
||||
check_no_stale_explicit_imports(HYPRE) |
||||
check_all_qualified_accesses_via_owners(HYPRE) |
||||
check_no_self_qualified_accesses(HYPRE) |
||||
# Check extension modules |
||||
for ext in (:HYPREPartitionedArrays, :HYPRESparseArrays, :HYPRESparseMatricesCSR) |
||||
extmod = Base.get_extension(HYPRE, ext) |
||||
if extmod !== nothing |
||||
check_no_implicit_imports(extmod) |
||||
check_no_stale_explicit_imports(extmod) |
||||
check_all_qualified_accesses_via_owners(extmod) |
||||
check_no_self_qualified_accesses(extmod) |
||||
else |
||||
@warn "$(ext) extension not available." |
||||
end |
||||
end |
||||
|
||||
runic: |
||||
name: Runic |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- uses: actions/checkout@v4 |
||||
- uses: julia-actions/setup-julia@v2 |
||||
with: |
||||
version: '1' |
||||
- uses: julia-actions/cache@v2 |
||||
- uses: fredrikekre/runic-action@v1 |
||||
with: |
||||
version: '1' |
||||
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
--- |
||||
name: Documentation |
||||
on: |
||||
push: |
||||
branches: |
||||
- 'master' |
||||
- 'release-' |
||||
tags: ['*'] |
||||
pull_request: |
||||
|
||||
jobs: |
||||
docs: |
||||
name: Julia 1.11 - ubuntu-latest |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- uses: actions/checkout@v4 |
||||
- uses: julia-actions/setup-julia@v2 |
||||
with: |
||||
version: '1.11' |
||||
- uses: julia-actions/cache@v2 |
||||
- name: Install dependencies |
||||
run: julia --project=docs -e 'using Pkg; Pkg.instantiate()' |
||||
- name: Build and deploy |
||||
env: |
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
||||
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} |
||||
run: julia --project=docs --color=yes docs/make.jl |
||||
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
name: Test |
||||
|
||||
on: |
||||
push: |
||||
branches: |
||||
- 'master' |
||||
- 'release-' |
||||
tags: ['*'] |
||||
pull_request: |
||||
|
||||
jobs: |
||||
test: |
||||
name: Julia ${{ matrix.version }} - ${{ matrix.os }} |
||||
runs-on: ${{ matrix.os }} |
||||
strategy: |
||||
matrix: |
||||
version: |
||||
- '1.10' |
||||
- '1' |
||||
- 'nightly' |
||||
os: |
||||
- ubuntu-latest |
||||
include: |
||||
- os: windows-latest |
||||
version: '1' |
||||
- os: macOS-latest |
||||
version: '1' |
||||
steps: |
||||
- uses: actions/checkout@v4 |
||||
- uses: julia-actions/setup-julia@v2 |
||||
with: |
||||
version: ${{ matrix.version }} |
||||
- uses: julia-actions/cache@v2 |
||||
- uses: julia-actions/julia-buildpkg@v1 |
||||
- uses: julia-actions/julia-runtest@v1 |
||||
- uses: julia-actions/julia-processcoverage@v1 |
||||
- uses: codecov/codecov-action@v5 |
||||
with: |
||||
files: lcov.info |
||||
env: |
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} |
||||
@ -1,55 +0,0 @@
@@ -1,55 +0,0 @@
|
||||
name: CI |
||||
|
||||
on: |
||||
push: |
||||
branches: |
||||
- 'master' |
||||
- 'release-' |
||||
tags: '*' |
||||
pull_request: |
||||
|
||||
jobs: |
||||
test: |
||||
name: Julia ${{ matrix.version }} - ${{ matrix.os }} |
||||
runs-on: ${{ matrix.os }} |
||||
strategy: |
||||
matrix: |
||||
version: |
||||
- '1.6' |
||||
- '1' |
||||
- 'nightly' |
||||
os: |
||||
- ubuntu-latest |
||||
include: |
||||
- os: windows-latest |
||||
version: '1' |
||||
- os: macOS-latest |
||||
version: '1' |
||||
steps: |
||||
- uses: actions/checkout@v2 |
||||
- uses: julia-actions/setup-julia@v1 |
||||
with: |
||||
version: ${{ matrix.version }} |
||||
- uses: julia-actions/cache@v1 |
||||
- uses: julia-actions/julia-buildpkg@v1 |
||||
- uses: julia-actions/julia-runtest@v1 |
||||
- uses: julia-actions/julia-processcoverage@v1 |
||||
- uses: codecov/codecov-action@v2 |
||||
with: |
||||
files: ./lcov.info |
||||
docs: |
||||
name: Documentation |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- uses: actions/checkout@v2 |
||||
- uses: julia-actions/setup-julia@v1 |
||||
with: |
||||
version: '1' |
||||
- uses: julia-actions/cache@v1 |
||||
- name: Install dependencies |
||||
run: julia --project=docs -e 'using Pkg; Pkg.instantiate()' |
||||
- name: Build and deploy |
||||
env: |
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
||||
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} |
||||
run: julia --project=docs --color=yes docs/make.jl |
||||
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
repos: |
||||
- repo: https://github.com/pre-commit/pre-commit-hooks |
||||
rev: v3.2.0 |
||||
hooks: |
||||
- id: check-added-large-files |
||||
- id: check-case-conflict |
||||
- id: check-toml |
||||
- id: check-yaml |
||||
- id: end-of-file-fixer |
||||
- id: mixed-line-ending |
||||
- id: trailing-whitespace |
||||
@ -0,0 +1,112 @@
@@ -0,0 +1,112 @@
|
||||
# HYPRE.jl changelog |
||||
|
||||
All notable changes to this project will be documented in this file. |
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), |
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). |
||||
|
||||
## [v1.7.0] - 2024-10-09 |
||||
### Changed |
||||
- Support for Julia 1.6 have been dropped and for this and future releases Julia 1.10 or |
||||
later will be required. ([#27]) |
||||
- Constant struct fields of `HYPREMatrix` and `HYPREVector` are now marked with `const`. |
||||
([#28]) |
||||
|
||||
## [v1.6.0] - 2024-09-29 |
||||
### Changed |
||||
- PartitionedArrays.jl dependency upgraded from release series 0.3.x to release series |
||||
0.5.x. ([#17], [#18]) |
||||
- CEnum.jl dependency upgraded to release series 0.5.x (release series 0.4.x still |
||||
allowed). ([#17], [#18]) |
||||
- PartitionedArrays.jl support (`PSparseMatrix`, `PVector`) is now provided by a package |
||||
extension. ([#23]) |
||||
- SparseMatricesCSR.jl support (`SparseMatrixCSR`) is now provided by a package extension. |
||||
([#24]) |
||||
- SparseArrays.jl support (`SparseMatrixCSC`) is now provided by a package extension. |
||||
([#25]) |
||||
|
||||
## [v1.5.0] - 2023-05-26 |
||||
### Changed |
||||
- PartitionedArrays.jl dependency upgraded from version 0.2.x to version 0.3.x. |
||||
([#16]) |
||||
|
||||
## [v1.4.0] - 2023-01-20 |
||||
### Added |
||||
- New function `HYPRE.GetFinalRelativeResidualNorm(s::HYPRESolver)` for getting the final |
||||
residual norm from a solver. This function dispatches on the solver to the corresponding |
||||
C API wrapper `LibHYPRE.HYPRE_${Solver}GetFinalRelativeResidualNorm`. ([#14]) |
||||
- New function `HYPRE.GetNumIterations(s::HYPRESolver)` for getting the number of |
||||
iterations from a solver. This function dispatches on the solver to the corresponding C |
||||
API wrapper `LibHYPRE.HYPRE_${Solver}GetNumIterations`. ([#14]) |
||||
|
||||
## [v1.3.1] - 2023-01-14 |
||||
### Fixed |
||||
- Solvers now keep an reference to the added preconditioner to make sure the preconditioner |
||||
is not finalized before the solver. This fixes crashes (segfaults) that could happen in |
||||
case no other reference to the preconditioner existed in the program. ([#12]) |
||||
- The proper conversion methods for `ccall` are now defined for `HYPREMatrix`, |
||||
`HYPREVector`, and `HYPRESolver` such that they can be passed direcly to `HYPRE_*` |
||||
functions and let `ccall` guarantee the GC preservation of these objects. Although not |
||||
observed in practice, this fixes a possible race condition where the matrix/vector/solver |
||||
could be finalized too early. ([#13]) |
||||
|
||||
## [v1.3.0] - 2022-12-30 |
||||
### Added |
||||
- Rectangular matrices can now be assembled by the new method |
||||
`HYPRE.assemble!(::HYPREMatrixAssembler, i::Vector, j::Vector, a::Matrix)` where `i` are |
||||
the rows and `j` the columns. ([#7]) |
||||
### Fixed |
||||
- All created HYPRE objects (`HYPREMatrix`, `HYPREVector`, and `HYPRESolver`s) are now kept |
||||
track of internally and explicitly `finalize`d (if they haven't been GC'd) before |
||||
finalizing HYPRE. This fixes a "race condition" where MPI and/or HYPRE would finalize |
||||
before these Julia objects are garbage collected and finalized. ([#8]) |
||||
### Deprecated |
||||
- The method `HYPRE.assemble!(A::HYPREMatrixAssembler, ij::Vector, a::Matrix)` have been |
||||
deprecated in favor of `HYPRE.assemble!(A::HYPREMatrixAssembler, i::Vector, j::Vector, |
||||
a::Matrix)`, i.e. it is now required to explicitly pass rows and column indices |
||||
individually. The motivation behind this is to support assembling of rectangular |
||||
matrices. Note that `HYPRE.assemble!(A::HYPREAssembler, ij::Vector, a::Matrix, |
||||
b::Vector)` is still supported, where `ij` are used as row and column indices for `a`, as |
||||
well as row indices for `b`. ([#6]) |
||||
|
||||
## [v1.2.0] - 2022-10-12 |
||||
### Added |
||||
- Added assembler interface to assemble `HYPREMatrix` and/or `HYPREVector` directly without |
||||
an intermediate sparse structure in Julia. ([#5]) |
||||
|
||||
## [v1.1.0] - 2022-10-05 |
||||
### Added |
||||
- Added support for MPI.jl version 0.20.x (in addition to the existing version 0.19.x |
||||
support). ([#2]) |
||||
|
||||
## [v1.0.0] - 2022-07-28 |
||||
Initial release of HYPRE.jl. |
||||
|
||||
|
||||
<!-- Links generated by Changelog.jl --> |
||||
|
||||
[v1.0.0]: https://github.com/fredrikekre/HYPRE.jl/releases/tag/v1.0.0 |
||||
[v1.1.0]: https://github.com/fredrikekre/HYPRE.jl/releases/tag/v1.1.0 |
||||
[v1.2.0]: https://github.com/fredrikekre/HYPRE.jl/releases/tag/v1.2.0 |
||||
[v1.3.0]: https://github.com/fredrikekre/HYPRE.jl/releases/tag/v1.3.0 |
||||
[v1.3.1]: https://github.com/fredrikekre/HYPRE.jl/releases/tag/v1.3.1 |
||||
[v1.4.0]: https://github.com/fredrikekre/HYPRE.jl/releases/tag/v1.4.0 |
||||
[v1.5.0]: https://github.com/fredrikekre/HYPRE.jl/releases/tag/v1.5.0 |
||||
[v1.6.0]: https://github.com/fredrikekre/HYPRE.jl/releases/tag/v1.6.0 |
||||
[v1.7.0]: https://github.com/fredrikekre/HYPRE.jl/releases/tag/v1.7.0 |
||||
[#2]: https://github.com/fredrikekre/HYPRE.jl/issues/2 |
||||
[#5]: https://github.com/fredrikekre/HYPRE.jl/issues/5 |
||||
[#6]: https://github.com/fredrikekre/HYPRE.jl/issues/6 |
||||
[#7]: https://github.com/fredrikekre/HYPRE.jl/issues/7 |
||||
[#8]: https://github.com/fredrikekre/HYPRE.jl/issues/8 |
||||
[#12]: https://github.com/fredrikekre/HYPRE.jl/issues/12 |
||||
[#13]: https://github.com/fredrikekre/HYPRE.jl/issues/13 |
||||
[#14]: https://github.com/fredrikekre/HYPRE.jl/issues/14 |
||||
[#16]: https://github.com/fredrikekre/HYPRE.jl/issues/16 |
||||
[#17]: https://github.com/fredrikekre/HYPRE.jl/issues/17 |
||||
[#18]: https://github.com/fredrikekre/HYPRE.jl/issues/18 |
||||
[#23]: https://github.com/fredrikekre/HYPRE.jl/issues/23 |
||||
[#24]: https://github.com/fredrikekre/HYPRE.jl/issues/24 |
||||
[#25]: https://github.com/fredrikekre/HYPRE.jl/issues/25 |
||||
[#27]: https://github.com/fredrikekre/HYPRE.jl/issues/27 |
||||
[#28]: https://github.com/fredrikekre/HYPRE.jl/issues/28 |
||||
@ -1,11 +1,8 @@
@@ -1,11 +1,8 @@
|
||||
SRCDIR:=$(shell dirname $(abspath $(firstword $(MAKEFILE_LIST)))) |
||||
|
||||
default: livedocs |
||||
default: liveserver |
||||
|
||||
instantiate: |
||||
julia --project=${SRCDIR} -e 'using Pkg; Pkg.instantiate()' |
||||
liveserver: |
||||
julia --project=${SRCDIR} ${SRCDIR}/liveserver.jl |
||||
|
||||
livedocs: instantiate |
||||
julia --project=${SRCDIR} -e 'using LiveServer; LiveServer.servedocs(; foldername=pwd())' -- liveserver |
||||
|
||||
.PHONY: default instantiate livedocs |
||||
.PHONY: default liveserver |
||||
|
||||
@ -1,3 +1,4 @@
@@ -1,3 +1,4 @@
|
||||
[deps] |
||||
Changelog = "5217a498-cd5d-4ec6-b8c2-9b85a09b6e3e" |
||||
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" |
||||
HYPRE = "b5ffcf37-a2bd-41ab-a3da-4bd9bc8ad771" |
||||
|
||||
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env julia |
||||
|
||||
# Root of the repository |
||||
const repo_root = dirname(@__DIR__) |
||||
|
||||
# Make sure docs environment is active and instantiated |
||||
import Pkg |
||||
Pkg.activate(@__DIR__) |
||||
Pkg.instantiate() |
||||
|
||||
# Communicate with docs/make.jl that we are running in live mode |
||||
push!(ARGS, "liveserver") |
||||
|
||||
# Run LiveServer.servedocs(...) |
||||
import LiveServer |
||||
LiveServer.servedocs(; |
||||
# Documentation root where make.jl and src/ are located |
||||
foldername = joinpath(repo_root, "docs"), |
||||
# Extra source folder to watch for changes |
||||
include_dirs = [ |
||||
# Watch the src folder so docstrings can be Revise'd |
||||
joinpath(repo_root, "src"), |
||||
], |
||||
) |
||||
@ -0,0 +1,300 @@
@@ -0,0 +1,300 @@
|
||||
module HYPREPartitionedArrays |
||||
|
||||
using HYPRE.LibHYPRE: @check, HYPRE_BigInt, HYPRE_Complex, HYPRE_IJMatrixSetValues, |
||||
HYPRE_IJVectorGetValues, HYPRE_IJVectorInitialize, HYPRE_IJVectorSetValues, HYPRE_Int |
||||
using HYPRE: HYPRE, HYPREMatrix, HYPRESolver, HYPREVector, Internals |
||||
using MPI: MPI |
||||
using PartitionedArrays: PartitionedArrays, AbstractLocalIndices, MPIArray, PSparseMatrix, |
||||
PVector, SplitMatrix, ghost_to_global, local_values, own_to_global, own_values, |
||||
partition |
||||
using SparseArrays: SparseArrays, SparseMatrixCSC, nonzeros, nzrange, rowvals |
||||
using SparseMatricesCSR: SparseMatrixCSR, colvals |
||||
|
||||
################################################## |
||||
# PartitionedArrays.PSparseMatrix -> HYPREMatrix # |
||||
################################################## |
||||
|
||||
function Internals.to_hypre_data( |
||||
A::SplitMatrix{<:SparseMatrixCSC}, r::AbstractLocalIndices, c::AbstractLocalIndices |
||||
) |
||||
# Own/ghost to global index mappings |
||||
own_to_global_row = own_to_global(r) |
||||
own_to_global_col = own_to_global(c) |
||||
ghost_to_global_col = ghost_to_global(c) |
||||
|
||||
# HYPRE requires contiguous row indices |
||||
ilower = own_to_global_row[1] |
||||
iupper = own_to_global_row[end] |
||||
@assert iupper - ilower + 1 == length(own_to_global_row) |
||||
|
||||
# Extract sparse matrices from the SplitMatrix. We are only interested in the owned |
||||
# rows, so only consider own-own and own-ghost blocks. |
||||
Aoo = A.blocks.own_own::SparseMatrixCSC |
||||
Aoo_rows = rowvals(Aoo) |
||||
Aoo_vals = nonzeros(Aoo) |
||||
Aog = A.blocks.own_ghost::SparseMatrixCSC |
||||
Aog_rows = rowvals(Aog) |
||||
Aog_vals = nonzeros(Aog) |
||||
@assert size(Aoo, 1) == size(Aog, 1) == length(own_to_global_row) |
||||
|
||||
# Initialize the data buffers HYPRE wants |
||||
nrows = HYPRE_Int(length(own_to_global_row)) # Total number of rows |
||||
ncols = zeros(HYPRE_Int, nrows) # Number of colums for each row |
||||
rows = collect(HYPRE_BigInt, ilower:iupper) # The row indices |
||||
# cols = Vector{HYPRE_BigInt}(undef, nnz) # The column indices |
||||
# values = Vector{HYPRE_Complex}(undef, nnz) # The values |
||||
|
||||
# First pass to count nnz per row (note that global column indices and column |
||||
# permutation doesn't matter for this pass) |
||||
@inbounds for own_col in 1:size(Aoo, 2) |
||||
for k in nzrange(Aoo, own_col) |
||||
own_row = Aoo_rows[k] |
||||
ncols[own_row] += 1 |
||||
end |
||||
end |
||||
@inbounds for ghost_col in 1:size(Aog, 2) |
||||
for k in nzrange(Aog, ghost_col) |
||||
own_row = Aog_rows[k] |
||||
ncols[own_row] += 1 |
||||
end |
||||
end |
||||
|
||||
# Initialize remaining buffers now that nnz is known |
||||
nnz = sum(ncols) |
||||
cols = Vector{HYPRE_BigInt}(undef, nnz) |
||||
values = Vector{HYPRE_Complex}(undef, nnz) |
||||
|
||||
# Keep track of the last index used for every row |
||||
lastinds = zeros(Int, nrows) |
||||
cumsum!((@view lastinds[2:end]), (@view ncols[1:(end - 1)])) |
||||
|
||||
# Second pass to populate the output. Here we need to map column |
||||
# indices from own/ghost to global |
||||
@inbounds for own_col in 1:size(Aoo, 2) |
||||
for k in nzrange(Aoo, own_col) |
||||
own_row = Aoo_rows[k] |
||||
i = lastinds[own_row] += 1 |
||||
values[i] = Aoo_vals[k] |
||||
cols[i] = own_to_global_col[own_col] |
||||
end |
||||
end |
||||
@inbounds for ghost_col in 1:size(Aog, 2) |
||||
for k in nzrange(Aog, ghost_col) |
||||
own_row = Aog_rows[k] |
||||
i = lastinds[own_row] += 1 |
||||
values[i] = Aog_vals[k] |
||||
cols[i] = ghost_to_global_col[ghost_col] |
||||
end |
||||
end |
||||
|
||||
# Sanity checks and return |
||||
@assert nrows == length(ncols) == length(rows) |
||||
return nrows, ncols, rows, cols, values |
||||
end |
||||
|
||||
function Internals.to_hypre_data( |
||||
A::SplitMatrix{<:SparseMatrixCSR}, r::AbstractLocalIndices, c::AbstractLocalIndices |
||||
) |
||||
# Own/ghost to global index mappings |
||||
own_to_global_row = own_to_global(r) |
||||
own_to_global_col = own_to_global(c) |
||||
ghost_to_global_col = ghost_to_global(c) |
||||
|
||||
# HYPRE requires contiguous row indices |
||||
ilower = own_to_global_row[1] |
||||
iupper = own_to_global_row[end] |
||||
@assert iupper - ilower + 1 == length(own_to_global_row) |
||||
|
||||
# Extract sparse matrices from the SplitMatrix. We are only interested in the owned |
||||
# rows, so only consider own-own and own-ghost blocks. |
||||
Aoo = A.blocks.own_own::SparseMatrixCSR |
||||
Aoo_cols = colvals(Aoo) |
||||
Aoo_vals = nonzeros(Aoo) |
||||
Aog = A.blocks.own_ghost::SparseMatrixCSR |
||||
Aog_cols = colvals(Aog) |
||||
Aog_vals = nonzeros(Aog) |
||||
@assert size(Aoo, 1) == size(Aog, 1) == length(own_to_global_row) |
||||
|
||||
# Initialize the data buffers HYPRE wants |
||||
nnz = SparseArrays.nnz(Aoo) + SparseArrays.nnz(Aog) |
||||
nrows = HYPRE_Int(iupper - ilower + 1) # Total number of rows |
||||
ncols = zeros(HYPRE_Int, nrows) # Number of columns for each row |
||||
rows = collect(HYPRE_BigInt, ilower:iupper) # The row indices |
||||
cols = Vector{HYPRE_BigInt}(undef, nnz) # The column indices |
||||
values = Vector{HYPRE_Complex}(undef, nnz) # The values |
||||
|
||||
# For CSR we only need a single pass to over the owned rows to collect everything |
||||
i = 0 |
||||
for own_row in 1:size(Aoo, 1) |
||||
nzro = nzrange(Aoo, own_row) |
||||
nzrg = nzrange(Aog, own_row) |
||||
ncols[own_row] = length(nzro) + length(nzrg) |
||||
for k in nzro |
||||
i += 1 |
||||
own_col = Aoo_cols[k] |
||||
cols[i] = own_to_global_col[own_col] |
||||
values[i] = Aoo_vals[k] |
||||
end |
||||
for k in nzrg |
||||
i += 1 |
||||
ghost_col = Aog_cols[k] |
||||
cols[i] = ghost_to_global_col[ghost_col] |
||||
values[i] = Aog_vals[k] |
||||
end |
||||
end |
||||
|
||||
# Sanity checks and return |
||||
@assert nnz == i |
||||
@assert nrows == length(ncols) == length(rows) |
||||
return nrows, ncols, rows, cols, values |
||||
end |
||||
|
||||
function Internals.get_comm(A::Union{PSparseMatrix{<:Any, <:M}, PVector{<:Any, <:M}}) where {M <: MPIArray} |
||||
return partition(A).comm |
||||
end |
||||
|
||||
Internals.get_comm(_::Union{PSparseMatrix, PVector}) = MPI.COMM_SELF |
||||
|
||||
function Internals.get_proc_rows(A::Union{PSparseMatrix, PVector}) |
||||
ilower::HYPRE_BigInt = typemax(HYPRE_BigInt) |
||||
iupper::HYPRE_BigInt = typemin(HYPRE_BigInt) |
||||
map(partition(axes(A, 1))) do a |
||||
# This is a map over the local process' owned indices. For MPI it will |
||||
# be a single value but for DebugArray / Array it will have multiple |
||||
# values. |
||||
o_to_g = own_to_global(a) |
||||
ilower_part = o_to_g[1] |
||||
iupper_part = o_to_g[end] |
||||
ilower = min(ilower, convert(HYPRE_BigInt, ilower_part)) |
||||
iupper = max(iupper, convert(HYPRE_BigInt, iupper_part)) |
||||
end |
||||
return ilower, iupper |
||||
end |
||||
|
||||
function HYPRE.HYPREMatrix(B::PSparseMatrix) |
||||
# Use the same communicator as the matrix |
||||
comm = Internals.get_comm(B) |
||||
# Fetch rows owned by this process |
||||
ilower, iupper = Internals.get_proc_rows(B) |
||||
# Create the IJ matrix |
||||
A = HYPREMatrix(comm, ilower, iupper) |
||||
# Set all the values |
||||
map(local_values(B), partition(axes(B, 1)), partition(axes(B, 2))) do Bv, Br, Bc |
||||
nrows, ncols, rows, cols, values = Internals.to_hypre_data(Bv, Br, Bc) |
||||
@check HYPRE_IJMatrixSetValues(A, nrows, ncols, rows, cols, values) |
||||
return nothing |
||||
end |
||||
# Finalize |
||||
Internals.assemble_matrix(A) |
||||
return A |
||||
end |
||||
|
||||
############################################ |
||||
# PartitionedArrays.PVector -> HYPREVector # |
||||
############################################ |
||||
|
||||
function HYPRE.HYPREVector(v::PVector) |
||||
# Use the same communicator as the matrix |
||||
comm = Internals.get_comm(v) |
||||
# Fetch rows owned by this process |
||||
ilower, iupper = Internals.get_proc_rows(v) |
||||
# Create the IJ vector |
||||
b = HYPREVector(comm, ilower, iupper) |
||||
# Set all the values |
||||
map(own_values(v), partition(axes(v, 1))) do vo, vr |
||||
o_to_g = own_to_global(vr) |
||||
|
||||
ilower_part = o_to_g[1] |
||||
iupper_part = o_to_g[end] |
||||
|
||||
# Option 1: Set all values |
||||
nvalues = HYPRE_Int(iupper_part - ilower_part + 1) |
||||
indices = collect(HYPRE_BigInt, ilower_part:iupper_part) |
||||
# TODO: Could probably just pass the full vector even if it is too long |
||||
# values = convert(Vector{HYPRE_Complex}, vv) |
||||
values = collect(HYPRE_Complex, vo) |
||||
|
||||
# # Option 2: Set only non-zeros |
||||
# indices = HYPRE_BigInt[] |
||||
# values = HYPRE_Complex[] |
||||
# for (i, vi) in zip(ilower_part:iupper_part, vo) |
||||
# if !iszero(vi) |
||||
# push!(indices, i) |
||||
# push!(values, vi) |
||||
# end |
||||
# end |
||||
# nvalues = length(indices) |
||||
|
||||
@check HYPRE_IJVectorSetValues(b, nvalues, indices, values) |
||||
return nothing |
||||
end |
||||
# Finalize |
||||
Internals.assemble_vector(b) |
||||
return b |
||||
end |
||||
|
||||
function copy_check(dst::HYPREVector, src::PVector) |
||||
il_dst, iu_dst = Internals.get_proc_rows(dst) |
||||
il_src, iu_src = Internals.get_proc_rows(src) |
||||
if il_dst != il_src && iu_dst != iu_src |
||||
# TODO: Why require this? |
||||
msg = "row owner mismatch between dst ($(il_dst:iu_dst)) and src ($(il_src:iu_src))" |
||||
throw(ArgumentError(msg)) |
||||
end |
||||
return |
||||
end |
||||
|
||||
# TODO: Other eltypes could be support by using a intermediate buffer |
||||
function Base.copy!(dst::PVector{<:AbstractVector{HYPRE_Complex}}, src::HYPREVector) |
||||
copy_check(src, dst) |
||||
map(own_values(dst), partition(axes(dst, 1))) do ov, vr |
||||
o_to_g = own_to_global(vr) |
||||
il_src_part = o_to_g[1] |
||||
iu_src_part = o_to_g[end] |
||||
nvalues = HYPRE_Int(iu_src_part - il_src_part + 1) |
||||
indices = collect(HYPRE_BigInt, il_src_part:iu_src_part) |
||||
values = ov |
||||
@check HYPRE_IJVectorGetValues(src, nvalues, indices, values) |
||||
end |
||||
return dst |
||||
end |
||||
|
||||
function Base.copy!(dst::HYPREVector, src::PVector{<:AbstractVector{HYPRE_Complex}}) |
||||
copy_check(dst, src) |
||||
# Re-initialize the vector |
||||
@check HYPRE_IJVectorInitialize(dst) |
||||
map(own_values(src), partition(axes(src, 1))) do ov, vr |
||||
o_to_g = own_to_global(vr) |
||||
ilower_src_part = o_to_g[1] |
||||
iupper_src_part = o_to_g[end] |
||||
nvalues = HYPRE_Int(iupper_src_part - ilower_src_part + 1) |
||||
indices = collect(HYPRE_BigInt, ilower_src_part:iupper_src_part) |
||||
values = ov |
||||
@check HYPRE_IJVectorSetValues(dst, nvalues, indices, values) |
||||
end |
||||
# TODO: It shouldn't be necessary to assemble here since we only set owned rows (?) |
||||
# @check HYPRE_IJVectorAssemble(dst) |
||||
# TODO: Necessary to recreate the ParVector? Running some examples it seems like it is |
||||
# not needed. |
||||
return dst |
||||
end |
||||
|
||||
###################################### |
||||
# PartitionedArrays solver interface # |
||||
###################################### |
||||
|
||||
# TODO: Would it be useful with a method that copied the solution to b instead? |
||||
|
||||
function HYPRE.solve(solver::HYPRESolver, A::PSparseMatrix, b::PVector) |
||||
hypre_x = HYPRE.solve(solver, HYPREMatrix(A), HYPREVector(b)) |
||||
x = copy!(similar(b, HYPRE_Complex), hypre_x) |
||||
return x |
||||
end |
||||
function HYPRE.solve!(solver::HYPRESolver, x::PVector, A::PSparseMatrix, b::PVector) |
||||
hypre_x = HYPREVector(x) |
||||
HYPRE.solve!(solver, hypre_x, HYPREMatrix(A), HYPREVector(b)) |
||||
copy!(x, hypre_x) |
||||
return x |
||||
end |
||||
|
||||
end # module HYPREPartitionedArrays |
||||
@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
|
||||
module HYPRESparseArrays |
||||
|
||||
using HYPRE.LibHYPRE: @check, HYPRE_BigInt, HYPRE_Complex, HYPRE_Int |
||||
using HYPRE: |
||||
HYPRE, HYPREMatrix, HYPRESolver, HYPREVector, HYPRE_IJMatrixSetValues, Internals |
||||
using MPI: MPI |
||||
using SparseArrays: SparseArrays, SparseMatrixCSC, nonzeros, nzrange, rowvals |
||||
|
||||
################################## |
||||
# SparseMatrixCSC -> HYPREMatrix # |
||||
################################## |
||||
|
||||
function Internals.to_hypre_data(A::SparseMatrixCSC, ilower, iupper) |
||||
Internals.check_n_rows(A, ilower, iupper) |
||||
nnz = SparseArrays.nnz(A) |
||||
A_rows = rowvals(A) |
||||
A_vals = nonzeros(A) |
||||
|
||||
# Initialize the data buffers HYPRE wants |
||||
nrows = HYPRE_Int(iupper - ilower + 1) # Total number of rows |
||||
ncols = zeros(HYPRE_Int, nrows) # Number of colums for each row |
||||
rows = collect(HYPRE_BigInt, ilower:iupper) # The row indices |
||||
cols = Vector{HYPRE_BigInt}(undef, nnz) # The column indices |
||||
values = Vector{HYPRE_Complex}(undef, nnz) # The values |
||||
|
||||
# First pass to count nnz per row |
||||
@inbounds for j in 1:size(A, 2) |
||||
for i in nzrange(A, j) |
||||
row = A_rows[i] |
||||
ncols[row] += 1 |
||||
end |
||||
end |
||||
|
||||
# Keep track of the last index used for every row |
||||
lastinds = zeros(Int, nrows) |
||||
cumsum!((@view lastinds[2:end]), (@view ncols[1:(end - 1)])) |
||||
|
||||
# Second pass to populate the output |
||||
@inbounds for j in 1:size(A, 2) |
||||
for i in nzrange(A, j) |
||||
row = A_rows[i] |
||||
k = lastinds[row] += 1 |
||||
val = A_vals[i] |
||||
cols[k] = j |
||||
values[k] = val |
||||
end |
||||
end |
||||
@assert nrows == length(ncols) == length(rows) |
||||
return nrows, ncols, rows, cols, values |
||||
end |
||||
|
||||
# Note: keep in sync with the SparseMatrixCSR method |
||||
function HYPRE.HYPREMatrix(comm::MPI.Comm, B::SparseMatrixCSC, ilower, iupper) |
||||
A = HYPREMatrix(comm, ilower, iupper) |
||||
nrows, ncols, rows, cols, values = Internals.to_hypre_data(B, ilower, iupper) |
||||
@check HYPRE_IJMatrixSetValues(A, nrows, ncols, rows, cols, values) |
||||
Internals.assemble_matrix(A) |
||||
return A |
||||
end |
||||
|
||||
# Note: keep in sync with the SparseMatrixCSC method |
||||
function HYPRE.HYPREMatrix(B::SparseMatrixCSC, ilower = 1, iupper = size(B, 1)) |
||||
return HYPREMatrix(MPI.COMM_SELF, B, ilower, iupper) |
||||
end |
||||
|
||||
|
||||
#################################### |
||||
# SparseMatrixCSC solver interface # |
||||
#################################### |
||||
|
||||
# Note: keep in sync with the SparseMatrixCSR method |
||||
function HYPRE.solve(solver::HYPRESolver, A::SparseMatrixCSC, b::Vector) |
||||
hypre_x = HYPRE.solve(solver, HYPREMatrix(A), HYPREVector(b)) |
||||
x = copy!(similar(b, HYPRE_Complex), hypre_x) |
||||
return x |
||||
end |
||||
|
||||
# Note: keep in sync with the SparseMatrixCSR method |
||||
function HYPRE.solve!(solver::HYPRESolver, x::Vector, A::SparseMatrixCSC, b::Vector) |
||||
hypre_x = HYPREVector(x) |
||||
HYPRE.solve!(solver, hypre_x, HYPREMatrix(A), HYPREVector(b)) |
||||
copy!(x, hypre_x) |
||||
return x |
||||
end |
||||
|
||||
end # module HYPRESparseMatricesCSR |
||||
@ -0,0 +1,80 @@
@@ -0,0 +1,80 @@
|
||||
module HYPRESparseMatricesCSR |
||||
|
||||
using HYPRE.LibHYPRE: @check, HYPRE_BigInt, HYPRE_Complex, HYPRE_Int |
||||
using HYPRE: HYPRE, HYPREMatrix, HYPRESolver, HYPREVector, HYPRE_IJMatrixSetValues, Internals |
||||
using MPI: MPI |
||||
using SparseArrays: SparseArrays, nonzeros, nzrange |
||||
using SparseMatricesCSR: SparseMatrixCSR, colvals |
||||
|
||||
|
||||
################################## |
||||
# SparseMatrixCSR -> HYPREMatrix # |
||||
################################## |
||||
|
||||
function Internals.to_hypre_data(A::SparseMatrixCSR, ilower, iupper) |
||||
Internals.check_n_rows(A, ilower, iupper) |
||||
nnz = SparseArrays.nnz(A) |
||||
A_cols = colvals(A) |
||||
A_vals = nonzeros(A) |
||||
|
||||
# Initialize the data buffers HYPRE wants |
||||
nrows = HYPRE_Int(iupper - ilower + 1) # Total number of rows |
||||
ncols = Vector{HYPRE_Int}(undef, nrows) # Number of colums for each row |
||||
rows = collect(HYPRE_BigInt, ilower:iupper) # The row indices |
||||
cols = Vector{HYPRE_BigInt}(undef, nnz) # The column indices |
||||
values = Vector{HYPRE_Complex}(undef, nnz) # The values |
||||
|
||||
# Loop over the rows and collect all values |
||||
k = 0 |
||||
@inbounds for i in 1:size(A, 1) |
||||
nzr = nzrange(A, i) |
||||
ncols[i] = length(nzr) |
||||
for j in nzr |
||||
k += 1 |
||||
col = A_cols[j] |
||||
val = A_vals[j] |
||||
cols[k] = col |
||||
values[k] = val |
||||
end |
||||
end |
||||
@assert nnz == k |
||||
@assert nrows == length(ncols) == length(rows) |
||||
return nrows, ncols, rows, cols, values |
||||
end |
||||
|
||||
|
||||
# Note: keep in sync with the SparseMatrixCSC method |
||||
function HYPRE.HYPREMatrix(comm::MPI.Comm, B::SparseMatrixCSR, ilower, iupper) |
||||
A = HYPREMatrix(comm, ilower, iupper) |
||||
nrows, ncols, rows, cols, values = Internals.to_hypre_data(B, ilower, iupper) |
||||
@check HYPRE_IJMatrixSetValues(A, nrows, ncols, rows, cols, values) |
||||
Internals.assemble_matrix(A) |
||||
return A |
||||
end |
||||
|
||||
# Note: keep in sync with the SparseMatrixCSC method |
||||
function HYPRE.HYPREMatrix(B::SparseMatrixCSR, ilower = 1, iupper = size(B, 1)) |
||||
return HYPREMatrix(MPI.COMM_SELF, B, ilower, iupper) |
||||
end |
||||
|
||||
|
||||
#################################### |
||||
# SparseMatrixCSR solver interface # |
||||
#################################### |
||||
|
||||
# Note: keep in sync with the SparseMatrixCSC method |
||||
function HYPRE.solve(solver::HYPRESolver, A::SparseMatrixCSR, b::Vector) |
||||
hypre_x = HYPRE.solve(solver, HYPREMatrix(A), HYPREVector(b)) |
||||
x = copy!(similar(b, HYPRE_Complex), hypre_x) |
||||
return x |
||||
end |
||||
|
||||
# Note: keep in sync with the SparseMatrixCSC method |
||||
function HYPRE.solve!(solver::HYPRESolver, x::Vector, A::SparseMatrixCSR, b::Vector) |
||||
hypre_x = HYPREVector(x) |
||||
HYPRE.solve!(solver, hypre_x, HYPREMatrix(A), HYPREVector(b)) |
||||
copy!(x, hypre_x) |
||||
return x |
||||
end |
||||
|
||||
end # module HYPRESparseMatricesCSR |
||||
@ -1,2 +1,14 @@
@@ -1,2 +1,14 @@
|
||||
default: |
||||
julia --project generator.jl |
||||
MAKEDIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) |
||||
LIBHYPRE:=$(shell dirname $(MAKEDIR))/lib/LibHYPRE.jl |
||||
|
||||
generate: $(LIBHYPRE) |
||||
|
||||
clean: |
||||
rm -f $(LIBHYPRE) |
||||
|
||||
.PHONY: generate clean |
||||
|
||||
$(LIBHYPRE): Project.toml Manifest.toml $(MAKEDIR)/generator.toml $(MAKEDIR)/generator.jl |
||||
julia --project generator.jl && \
|
||||
sed -i -e '1s/^/local libHYPRE # Silence of the Langs(erver)\n\n/' -e 's/using HYPRE_jll/using HYPRE_jll: HYPRE_jll, libHYPRE/' -e 's/using CEnum/using CEnum: @cenum/' $(LIBHYPRE) && \
|
||||
julia-1.11 --project=@runic -e 'using Runic; exit(Runic.main(ARGS))' -- -i $(LIBHYPRE) |
||||
|
||||
@ -1,6 +1,17 @@
@@ -1,6 +1,17 @@
|
||||
########################### |
||||
## Start gen/prologue.jl ## |
||||
########################### |
||||
|
||||
using MPI: MPI, MPI_Comm |
||||
if isdefined(MPI, :API) # MPI >= 0.20.0 |
||||
|
||||
if isdefined(MPI, :API) |
||||
# MPI >= 0.20.0 |
||||
using MPI.API: MPI_INT, MPI_DOUBLE |
||||
else # MPI < 0.20.0 |
||||
else |
||||
# MPI < 0.20.0 |
||||
using MPI: MPI_INT, MPI_DOUBLE |
||||
end |
||||
|
||||
######################### |
||||
## End gen/prologue.jl ## |
||||
######################### |
||||
|
||||
@ -0,0 +1,117 @@
@@ -0,0 +1,117 @@
|
||||
# SPDX-License-Identifier: MIT |
||||
|
||||
using HYPRE |
||||
using MPI |
||||
using Test |
||||
|
||||
MPI.Init() |
||||
HYPRE.Init() |
||||
|
||||
include("test_utils.jl") |
||||
|
||||
comm = MPI.COMM_WORLD |
||||
comm_rank = MPI.Comm_rank(comm) |
||||
comm_size = MPI.Comm_size(comm) |
||||
|
||||
if comm_size != 2 |
||||
error("Must run with 2 ranks.") |
||||
end |
||||
|
||||
if comm_rank == 0 |
||||
ilower = 1 |
||||
iupper = 10 |
||||
N = 2:10 |
||||
else |
||||
ilower = 11 |
||||
iupper = 20 |
||||
N = 11:19 |
||||
end |
||||
|
||||
function values_and_indices(n) |
||||
idx = [n - 1, n, n + 1] |
||||
a = Float64[ |
||||
# runic: off |
||||
n -2n -n |
||||
-2n n -2n |
||||
-n -2n n |
||||
# runic: on |
||||
] |
||||
b = Float64[n, n / 2, n / 3] |
||||
return idx, a, b |
||||
end |
||||
|
||||
########################## |
||||
## HYPREMatrixAssembler ## |
||||
########################## |
||||
|
||||
# Dense local matrix |
||||
|
||||
A = HYPREMatrix(comm, ilower, iupper) |
||||
AM = zeros(20, 20) |
||||
for i in 1:2 |
||||
assembler = HYPRE.start_assemble!(A) |
||||
fill!(AM, 0) |
||||
for n in N |
||||
idx, a, _ = values_and_indices(n) |
||||
HYPRE.assemble!(assembler, idx, idx, a) |
||||
AM[idx, idx] += a |
||||
end |
||||
f = HYPRE.finish_assemble!(assembler) |
||||
@test f === A |
||||
MPI.Allreduce!(AM, +, comm) |
||||
@test getindex_debug(A, ilower:iupper, 1:20) == AM[ilower:iupper, 1:20] |
||||
MPI.Barrier(comm) |
||||
end |
||||
|
||||
########################## |
||||
## HYPREVectorAssembler ## |
||||
########################## |
||||
|
||||
# Dense local vector |
||||
|
||||
b = HYPREVector(comm, ilower, iupper) |
||||
bv = zeros(20) |
||||
for i in 1:2 |
||||
assembler = HYPRE.start_assemble!(b) |
||||
fill!(bv, 0) |
||||
for n in N |
||||
idx, _, a = values_and_indices(n) |
||||
HYPRE.assemble!(assembler, idx, a) |
||||
bv[idx] += a |
||||
end |
||||
f = HYPRE.finish_assemble!(assembler) |
||||
@test f === b |
||||
MPI.Allreduce!(bv, +, comm) |
||||
@test getindex_debug(b, ilower:iupper) == bv[ilower:iupper] |
||||
MPI.Barrier(comm) |
||||
end |
||||
|
||||
#################### |
||||
## HYPREAssembler ## |
||||
#################### |
||||
|
||||
# Dense local arrays |
||||
|
||||
A = HYPREMatrix(comm, ilower, iupper) |
||||
AM = zeros(20, 20) |
||||
b = HYPREVector(comm, ilower, iupper) |
||||
bv = zeros(20) |
||||
for i in 1:2 |
||||
assembler = HYPRE.start_assemble!(A, b) |
||||
fill!(AM, 0) |
||||
fill!(bv, 0) |
||||
for n in N |
||||
idx, a, c = values_and_indices(n) |
||||
HYPRE.assemble!(assembler, idx, a, c) |
||||
AM[idx, idx] += a |
||||
bv[idx] += c |
||||
end |
||||
F, f = HYPRE.finish_assemble!(assembler) |
||||
@test F === A |
||||
@test f === b |
||||
MPI.Allreduce!(AM, +, comm) |
||||
MPI.Allreduce!(bv, +, comm) |
||||
@test getindex_debug(A, ilower:iupper, 1:20) == AM[ilower:iupper, 1:20] |
||||
@test getindex_debug(b, ilower:iupper) == bv[ilower:iupper] |
||||
MPI.Barrier(comm) |
||||
end |
||||
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
# SPDX-License-Identifier: MIT |
||||
|
||||
using HYPRE |
||||
using HYPRE.LibHYPRE |
||||
using HYPRE.LibHYPRE: @check |
||||
|
||||
function getindex_debug(A::HYPREMatrix, i::AbstractVector, j::AbstractVector) |
||||
nrows = HYPRE_Int(length(i)) |
||||
ncols = fill(HYPRE_Int(length(j)), length(i)) |
||||
rows = convert(Vector{HYPRE_BigInt}, i) |
||||
cols = convert(Vector{HYPRE_BigInt}, repeat(j, length(i))) |
||||
values = Vector{HYPRE_Complex}(undef, length(i) * length(j)) |
||||
@check HYPRE_IJMatrixGetValues(A.ijmatrix, nrows, ncols, rows, cols, values) |
||||
return permutedims(reshape(values, (length(j), length(i)))) |
||||
end |
||||
|
||||
function getindex_debug(b::HYPREVector, i::AbstractVector) |
||||
nvalues = HYPRE_Int(length(i)) |
||||
indices = convert(Vector{HYPRE_BigInt}, i) |
||||
values = Vector{HYPRE_Complex}(undef, length(i)) |
||||
@check HYPRE_IJVectorGetValues(b.ijvector, nvalues, indices, values) |
||||
return values |
||||
end |
||||
Loading…
Reference in new issue