#' Get indices of ghost nodes on an edge
#'
#' @description
#' Get an array of the indices of all ghost nodes on a side of a rectangular
#' grid of (real) nodes. Nodes are number per row --> per column. Ghost nodes
#' are placed outside the edges of all sides of the rectangular grid
#'
#' @param nx,ny number of real nodes in x and y-direction
#' @param edge edge number, numbered clockwise. `1` for left (negative x-face),
#' `2` for top (positive y-face), `3` for right (positive x-face) and `4`
#' for bottom (negative y-face)
#' @param i0 node starting number.
#' @return array of indices of ghost nodes on selected edge
#' @export
index_ghost <- function(nx, ny, edge, i0 = 0) {
if (edge == 1) {
return(i0 + seq(ny)*(nx + 2) - 1)
} else if (edge == 2) {
return(i0 + seq(nx) + (nx + 2)*(ny + 1) - 2)
} else if (edge == 3) {
return(i0 + (seq(ny) + 1)*(nx + 2) - 2)
} else if (edge == 4) {
return(i0 + seq(nx))
}
}
#' Get indices of all real nodes
#'
#' @description
#' Get an array of the indices of all real nodes in a rectangular grid of
#' (real) nodes. Nodes are number per row --> per column. Ghost nodes
#' are placed outside the edges of all sides of the rectangular grid
#'
#' @inheritParams index_ghost
#' @param ... potential extra arguments. Required to use the fuction with the
#' `purrr::pmap()` function
#' @return array of indices for all real nodes (non-ghost nodes)
#' @export
index_real <- function(nx, ny, i0 = 0, ...) {
return(i0 + rep(seq(nx), ny) + rep(seq(ny), each = nx)*(nx + 2) - 1)
}
#' Get index of real nodes on an edge
#'
#' @description
#' Get indices of real nodes lying on an edge. Indices are numbered row-first
#' (x first).
#'
#' @inheritParams index_ghost
#' @param real_only if `FALSE`, ghost nodes are assumed to be included in the
#' numbering of indices. If `TRUE`, all ghost nodes are assumed to have been
#' removed, and only real points are numbered
#' @param ... additional arguments, in case the function is passed to
#' `purrr::pmap()`
#' @return array with indices of real nodes on edge
#' @export
index_real_edge <- function(nx, ny, edge, i0 = 0, real_only = FALSE, ...) {
if (real_only == TRUE) {
if (edge == 1) {
return(i0 + 1 + (seq(ny) - 1)*nx)
} else if (edge == 2) {
return(i0 + seq(nx) + nx*(ny - 1))
} else if (edge == 3) {
return(i0 + seq(ny)*nx)
} else if (edge == 4) {
return(i0 + seq(nx))
}
} else {
if (edge == 1) {
return(i0 + seq(ny)*(nx + 2))
} else if (edge == 2) {
return(i0 + seq(nx) + ny*(nx + 2) - 1)
} else if (edge == 3) {
return(i0 + (seq(ny) + 1)*(nx + 2) - 3)
} else if (edge == 4) {
return(i0 + seq(nx) + nx + 1)
}
}
}
#' Determine total number of nodes
#'
#' @description
#' Determine the total number of nodes on a grid of nodes, including any
#' ghost nodes outside all four sides of the grid
#'
#' @inheritParams index_ghost
#' @param if `real = FALSE`, all nodes, including ghost nodes are counted.
#' if `real = TRUE`, only real nodes are counted
#' @return number of nodes (scalar)
#' @export
nodes_total <- function(nx, ny, real = FALSE) {
if (real == FALSE) {
return(nx*ny + 2*(nx + ny))
} else {
return(nx*ny)
}
}
#' Get indices of nodes bordering nodes
#'
#' @description
#' Get the indices of neighbouring nodes in a grid with real and ghost nodes.
#' This function inherently assumes those nodes exists. Make sure you don't
#' request any nodes outside the grid.
#'
#' @param i array with node indices
#' @param nx,ny number of nodes in x and y-direction
#' @param edge side on which to look, numbered clockwise. `1` for left
#' (negative x-face), `2` for top (positive y-face), `3` for right
#' (positive x-face) and `4` for bottom (negative y-face)
#' @export
index_neighbour <- function(i, nx, ny, edge, i0 = 0) {
tibble::tibble(i = i, nx = nx, ny = ny, edge = edge, i0 = i0) %>%
dplyr::mutate(
inew = ifelse(
edge == 1,
i - 1,
ifelse(
edge == 2,
ifelse(
((i - i0) >= (nx + 2)*ny) | ((i - i0) <= nx),
i + (nx + 1),
i + (nx + 2)
),
ifelse(
edge == 3,
i + 1,
ifelse(
((i - i0) <= (2*nx + 1)) | ((i - i0) > ((ny + 1)*(nx + 2) - 2)),
i - (nx + 1),
i - (nx + 2)
)
)
)
)
) %>%
dplyr::pull(inew)
}
#' Get finite difference matrix entries for real nodes
#'
#' @description
#' Get row, column and value arrays for all non-zero entries in the second
#' order finite difference grid, for all real points in a grid.
#'
#' @param nx,ny number of nodes in x and y-direction
#' @param hx,hy grid size in x and y-direction
#' @param kx,ky permeability in x and y-direction
#' @param i0 starting node
#' @return tibble with non-zero matrix entries. Fields `row` for row index,
#' `col` for column index and `val` for values. These can be used to
#' construct full solver matrix later
#' @export
findiff_entries_real <- function(nx, ny, hx = 1, hy = 1, kx = 1, ky = 1, i0 = 0, ...) {
#indices of real points and their neighbours
i <- i0 + index_real(nx, ny)
i1 <- index_neighbour(i, nx, ny, 1, i0 = i0)
i2 <- index_neighbour(i, nx, ny, 2, i0 = i0)
i3 <- index_neighbour(i, nx, ny, 3, i0 = i0)
i4 <- index_neighbour(i, nx, ny, 4, i0 = i0)
#generate lists
tibble::tibble(
row = c(i, i, i, i, i),
col = c(i, i1, i3, i4, i2),
val = c(
rep(-2*kx/hx^2 - 2*ky/hy^2, nx*ny),
rep(kx/hx^2, nx*ny),
rep(kx/hx^2, nx*ny),
rep(ky/hy^2, nx*ny),
rep(ky/hy^2, nx*ny)
)
)
}
#' Get finite difference matrix entries for all boundaries
#'
#' @description
#' Get row, column and value arrays for all non-zero entries in the problem
#' finite difference grid, for all boundaries of each rectangular grid section
#'
#' @param db tibble with all properties for the rectangular grids.
#' @return tibble with non-zero matrix entries. Fields `row` for row index,
#' `col` for column index and `val` for values. These can be used to
#' construct full solver matrix later
#' @export
findiff_entries_bc <- function(db) {
#initiate list and left-hand sides
mlist <- vector(mode = "list", length = 4*nrow(db))
lhs <- rep(0, sum(db$ntotal))
#loop through all blocks
for (i in 1:nrow(db)) {
## left edge (1)
edge <- 1
ilist <- (i - 1)*4 + edge
ig <- index_ghost(db$nx[i], db$ny[i], edge, i0 = db$i0[i])
if (db$type1[i] == "h") {
mlist[[ilist]] <- tibble::tibble(
row = ig,
col = ig + 1,
val = 1
)
lhs[ig] <- db$value1[i]
} else if (db$type1[i] == "q") {
mlist[[ilist]] <- tibble::tibble(
row = c(ig, ig),
col = c(ig, ig + 2),
val = -db$kx[i]/db$hx[i]*c(rep(-0.5, length(ig)), rep(0.5, length(ig)))
)
lhs[ig] <- db$value1[i]
} else if (db$type1[i] == "c") {
ibn <- db$value1[i]
ign <- index_ghost(db$nx[ibn], db$ny[ibn], 3, i0 = db$i0[ibn])
#same flow
mlist[[ilist]] <- tibble::tibble(
row = c(ig, ig, ig, ig),
col = c(ig, ig + 2, ign - 2, ign),
val = c(
rep(db$kx[i]/db$hx[i], length(ig)),
rep(-db$kx[i]/db$hx[i], length(ig)),
rep(-db$kx[ibn]/db$hx[ibn], length(ign)),
rep(db$kx[ibn]/db$hx[ibn], length(ign))
)
)
}
## top edge (2)
edge <- 2
ilist <- (i - 1)*4 + edge
ig <- index_ghost(db$nx[i], db$ny[i], edge, i0 = db$i0[i])
if (db$type2[i] == "h") {
mlist[[ilist]] <- tibble::tibble(
row = ig,
col = ig - db$nx[i] - 1,
val = 1
)
lhs[ig] <- db$value2[i]
} else if (db$type2[i] == "q") {
mlist[[ilist]] <- tibble::tibble(
row = c(ig, ig),
col = c(ig - 2*db$nx[i] - 3, ig),
val = -db$ky[i]/db$hy[i]*c(rep(-0.5, length(ig)), rep(0.5, length(ig)))
)
lhs[ig] <- db$value2[i]
} else if (db$type2[i] == "c") {
ibn <- db$value2[i]
ign <- index_ghost(db$nx[ibn], db$ny[ibn], 4, i0 = db$i0[ibn])
#same head
mlist[[ilist]] <- tibble::tibble(
row = c(ig, ig),
col = c(ig - db$nx[i] - 1, ign + db$nx[ibn] + 1),
val = c(rep(1, length(ig)), rep(-1, length(ign)))
)
}
## right edge (3)
edge <- 3
ilist <- (i - 1)*4 + edge
ig <- index_ghost(db$nx[i], db$ny[i], edge, i0 = db$i0[i])
if (db$type3[i] == "h") {
mlist[[ilist]] <- tibble::tibble(
row = ig,
col = ig - 1,
val = 1
)
lhs[ig] <- db$value3[i]
} else if (db$type3[i] == "q") {
mlist[[ilist]] <- tibble::tibble(
row = c(ig, ig),
col = c(ig - 2, ig),
val = -db$kx[i]/db$hx[i]*c(rep(-0.5, length(ig)), rep(0.5, length(ig)))
)
lhs[ig] <- db$value3[i]
} else if (db$type3[i] == "c") {
ibn <- db$value3[i]
ign <- index_ghost(db$nx[ibn], db$ny[ibn], 1, i0 = db$i0[ibn])
#same head
mlist[[ilist]] <- tibble::tibble(
row = c(ig, ig),
col = c(ig - 1, ign + 1),
val = c(rep(1, length(ig)), rep(-1, length(ign)))
)
}
## bottom edge (4)
edge <- 4
ilist <- (i - 1)*4 + edge
ig <- index_ghost(db$nx[i], db$ny[i], edge, i0 = db$i0[i])
if (db$type4[i] == "h") {
mlist[[ilist]] <- tibble::tibble(
row = ig,
col = ig + db$nx[i] + 1,
val = 1
)
lhs[ig] <- db$value4[i]
} else if (db$type4[i] == "q") {
mlist[[ilist]] <- tibble::tibble(
row = c(ig, ig),
col = c(ig, ig + 2*db$nx[i] + 3),
val = -db$ky[i]/db$hy[i]*c(rep(-0.5, length(ig)), rep(0.5, length(ig)))
)
lhs[ig] <- db$value4[i]
} else if (db$type4[i] == "c") {
ibn <- db$value4[i]
ign <- index_ghost(db$nx[ibn], db$ny[ibn], 2, i0 = db$i0[ibn])
#same flow
mlist[[ilist]] <- tibble::tibble(
row = c(ig, ig, ig, ig),
col = c(ig, ig + 2*db$nx[i] + 3, ign - 2*db$nx[ibn] - 3, ign),
val = c(
rep(db$ky[i]/db$hy[i], length(ig)),
rep(-db$ky[i]/db$hy[i], length(ig)),
rep(-db$ky[ibn]/db$hy[ibn], length(ign)),
rep(db$ky[ibn]/db$hy[ibn], length(ign))
)
)
}
}
#return
return(list(
lhs = lhs,
matrix_el = dplyr::bind_rows(mlist)
))
}
#' Get coordinates of all real nodes in the finite difference grid
#'
#' @description
#' Function obtains the x,y positions of all real nodes in the finite difference
#' grid
#'
#' @param db tibble with all properties for the rectangular grids
#' @return a tibble with fields `x` and `y` for positions, and `id` to indicate
#' which grid the point belongs to
#' @export
nodal_coordinates_real <- function(db) {
#get x-y positions relative to bottom left corner point
dp <- purrr::pmap_dfr(
db %>% dplyr::mutate(id = seq(nrow(db))),
function(id, nx, ny, hx, hy, x0, y0, ...) {
tibble::tibble(
id = id,
x = x0 + hx*rep((seq(nx) - 1), ny),
y = y0 + hy*rep((seq(ny) - 1), each = nx)
)
}
)
return(dp)
}
#' Generate tibble describing flow net problem
#'
#' @description
#' The flow net problem consists of connected rectangular domains. The
#' permabilities can be set seperately for each domain.
#'
#' @param x0 x and y position of the bottom left corner of the first domain
#' @param width,height width (x-direction) and height (y-direction) of each
#' domain in the problem
#' @param kx,ky permeabilities in x and y-direction. If scalars, they are set
#' equal for each domain
#' @param type1,type2,type3,type4 Type of boundary condition on each of the
#' four edges of each domain. Edges are numbered clockwise:
#'
#' * `type1` for the left edge (negative x-face)
#' * `type2` for the top edge (positive y-face)
#' * `type3` for the right edge (positive x-face)
#' * `type4` for the bottom edge (negative y-face)
#'
#' Possible values for the boundary condition types are:
#'
#' * `type = "q"`: known flow (in positive direction)
#' * `type = "h"`: known head
#' * `type = "c"`: face is connected to a face in another domain
#'
#' @param value1,value2,value3,value4 values of boundary conditions:
#'
#' * if `type = "q"`, `value` is the flow rate in positive direction
#' * if `type = "h"`: `value` is the known head
#' * if `type = "c"`: `value` gives the number of the domain that the face
#' is connected to (domains are numbered 1, 2, 3, in order of appearance)
#'
#' @param grid_size requested finite difference grid size. The grid size used
#' may vary slighly per domain, in order to fit a integer number of nodes in
#' the domain with a given size
#' @importFrom rlang .data
#' @return a tibble describing the entire tibble. In addition to the input,
#' fields are added for `x0` and `y0` (the x,y position of the bottom left)
#' point in each domain, `nx` and `ny` (the number of real nodes in each
#' domain in each direction), and `hx`, and `hy` (the distance between
#' nodes in each direction)
#' @export
generate_flownet_properties <- function(
x0 = 0,
y0 = 0,
width = c(15, 15, 10),
height = c(5, 10, 10),
kx = 1e-6,
ky = 1e-6,
type1 = c("q","q","c"),
type2 = c("h","c","q"),
type3 = c("q","c","h"),
type4 = c("c","q","q"),
value1 = c(0, 0, 2),
value2 = c(20, 1, 0),
value3 = c(0, 3, 10),
value4 = c(2, 0, 0),
grid_size = 0.5
){
#create tibble with all input properties
df <- tibble::tibble(
width = width,
height = height,
kx = kx,
ky = ky,
type1 = type1,
type2 = type2,
type3 = type3,
type4 = type4,
value1 = value1,
value2 = value2,
value3 = value3,
value4 = value4,
)
#add grid sizes
# * nx,ny: the number of grid nodes in x and y-directions
# * hx,hy: the distance between grid points in x and y-directions
# * ntotal: the total number of grid points for each rectangle, including
# ghost nodes
# * i0: the index of the first node in each rectangle, minus 1
# * i0_real: the index of the first node in each rectangle, after removal
# of all ghost nodes, minus 1
df <- dplyr::mutate(
df,
nx = ceiling(1 + .data$width / grid_size),
ny = ceiling(1 + .data$height / grid_size),
hx = .data$width/(.data$nx - 1),
hy = .data$height/(.data$ny - 1),
ntotal = nodes_total(.data$nx, .data$ny),
i0 = cumsum(c(0, utils::head(.data$ntotal, -1))),
i0_real = cumsum(c(0, utils::head(.data$nx, -1)*utils::head(.data$ny, -1)))
)
#positions of origins for each block
df$x0 <- x0
df$y0 <- y0
if (nrow(df) > 1) {
for (i in seq(nrow(df) - 1)) {
#find rows of connecting segments
ic1 <- which((df$type1 == "c") & (df$value1 == i))
ic1 <- ic1[ic1 > i]
ic2 <- which((df$type2 == "c") & (df$value2 == i))
ic2 <- ic2[ic2 > i]
ic3 <- which((df$type3 == "c") & (df$value3 == i))
ic3 <- ic3[ic3 > i]
ic4 <- which((df$type4 == "c") & (df$value4 == i))
ic4 <- ic4[ic4 > i]
#adjust position
df$x0[ic1] <- df$x0[i] + df$width[i]
df$y0[ic1] <- df$y0[i]
df$x0[ic2] <- df$x0[i]
df$y0[ic2] <- df$y0[i] - df$height[ic2]
df$x0[ic3] <- df$x0[i] - df$width[ic3]
df$y0[ic3] <- df$y0[i]
df$x0[ic4] <- df$x0[i]
df$y0[ic4] <- df$y0[i] + df$height[i]
}
}
#return
df
}
#' Solve flow net head problem for concatenated rectangular domains
#'
#' @description
#' Solves a flow net problem for a problem consisting of connected rectangular
#' grids. This function solves the hydroloc head using boundary conditions and
#' a finite difference approach.
#'
#' @param df tibble with problem description, generated by function
#' `generate_flownet_properties()`
#' @return a tibble with heads (field `h`) and flow rates in x and y-direction
#' (`qx`, `qy`) for each real point in the finite difference grid. Each point
#' is characterised by a position (`x`, `y`) and an index `id` indicating which
#' domain the point belongs to
#' @examples
#' df <- generate_flownet_properties()
#' dp <- solve_flownet(df)
#' @export
solve_flownet <- function(df) {
#generate all matrix entries for all real nodes
entries_real <- purrr::pmap_dfr(df, findiff_entries_real)
#generate matrix entries and left-hand side of equation for all boundaries
entries_bc <- findiff_entries_bc(df)
#generate sparse matrix
mat <- Matrix::spMatrix(
nrow = sum(df$ntotal),
ncol = sum(df$ntotal),
i = c(entries_real$row, entries_bc$matrix_el$row),
j = c(entries_real$col, entries_bc$matrix_el$col),
x = c(entries_real$val, entries_bc$matrix_el$val)
)
#solve matrix equation for head h
h_sol <- Matrix::solve(mat, entries_bc$lhs, sparse = TRUE)
#only keep real values
i_real <- unlist(purrr::pmap(df, index_real))
#return all unique nodes and positions
dp <- dplyr::mutate(nodal_coordinates_real(df), h = h_sol[i_real, 1])
#add flow velocities
layer <- rep(seq(nrow(df)), df$ntotal)[i_real]
i1 <- index_neighbour(i_real, df$nx[layer], df$ny[layer], 1, i0 = df$i0[layer])
i2 <- index_neighbour(i_real, df$nx[layer], df$ny[layer], 2, i0 = df$i0[layer])
i3 <- index_neighbour(i_real, df$nx[layer], df$ny[layer], 3, i0 = df$i0[layer])
i4 <- index_neighbour(i_real, df$nx[layer], df$ny[layer], 4, i0 = df$i0[layer])
dp$qx <- -df$kx[layer]*(0.5*h_sol[i3] - 0.5*h_sol[i1])*df$hx[layer]
dp$qy <- -df$ky[layer]*(0.5*h_sol[i2] - 0.5*h_sol[i4])*df$hy[layer]
#return
return(dp)
}
#' Calculate the flow potential at every point
#'
#' @description
#' Function takes the solved heads and flow from the finite difference solver
#' for the head, and calculates the flow potential for every real point in the
#' grid. This is done by solving the first-order system of equations
#' dpsi/dx = qy & dpsi/dy = -qx by means of finite differences.
#' After solving the system, it is ensured that flow potentials match for each
#' interval, and afterwards the results are scaled to between 0 and 1.
#'
#' @param df tibble with problem prescription
#' @param dp tibble with finite difference solution. Should contain fields for
#' `id` (domain identifier), `x` and `y` (for position of node) and `qx` and
#' `qy` for flow rate in x and y-directions
#' @examples
#' df <- generate_flownet_properties()
#' dp <- solve_flownet(df)
#' dp2 <- flow_potential(df, dp)
#' ggplot2::ggplot(dp2, ggplot2::aes(x = x, y = y, fill = psi)) + ggplot2::geom_tile()
flow_potential <- function(df, dp) {
#number of real nodes
ntotal_real <- sum(df$nx * df$ny)
#matrix elements for a single domain
flow_potential_matrixel_single <- function(x, y, qx, qy, i0, ntotal_real) {
#initial calcs
xu <- unique(x)
yu <- unique(y)
nx <- length(xu)
ny <- length(yu)
hx <- (max(xu) - min(xu)) / (nx - 1)
hy <- (max(yu) - min(yu)) / (ny - 1)
#first order differentiation - x-direction
row_x_single <- c(rep(1, 3), rep(seq(2, nx - 1), each = 2), rep(nx, 3))
col_x_single <- c(1, 2, 3, rep(seq(2, nx - 1), each = 2) + rep(c(-1, 1), (nx - 2)), nx - c(2, 1, 0))
val_x_single <- c(-1.5, 2, -0.5, rep(c(-0.5, 0.5), (nx - 2)), 0.5, -2, 1.5)/hx
row_x <- i0[1] + rep(row_x_single, ny) + rep((seq(ny) - 1)*nx, each = length(row_x_single))
col_x <- i0[1] + rep(col_x_single, ny) + rep((seq(ny) - 1)*nx, each = length(col_x_single))
val_x <- rep(val_x_single, ny)
#first order differentiation - y-direction
row_y_single <- c(rep(1, 3), rep(seq(2, ny - 1), each = 2), rep(ny, 3))
col_y_single <- c(1, 2, 3, rep(seq(2, ny - 1), each = 2) + rep(c(-1, 1), (ny - 2)), ny - c(2, 1, 0))
val_y_single <- c(-1.5, 2, -0.5, rep(c(-0.5, 0.5), (ny - 2)), 0.5, -2, 1.5)/hy
row_y <- ntotal_real + i0[1] + rep((row_y_single - 1)*nx, nx) + rep(seq(nx), each = length(row_y_single))
col_y <- i0[1] + rep((col_y_single - 1)*nx, nx) + rep(seq(nx), each = length(col_y_single))
val_y <- rep(val_y_single, nx)
#return sparse non-zero elements
return(tibble::tibble(
row = c(row_x, row_y),
col = c(col_x, col_y),
val = c(val_x, val_y)
))
}
#get all non-zero sparse elements for matrix equations
mat_el1 <- dp %>%
dplyr::mutate(i0 = df$i0_real[.data$id]) %>%
dplyr::group_by(.data$id) %>%
dplyr::summarize(flow_potential_matrixel_single(.data$x, .data$y, .data$qx, .data$qy, .data$i0, ntotal_real))
#get left-hand side for matrix equations
lhs_el1 <- c(-dp$qy, dp$qx)
#function to get matrix elements for a single connection
flow_potential_connel_single <- function(df, id1, id2, edge, i0) {
edge2 <- 1 + (edge + 1)%%4
i1 <- index_real_edge(df$nx[id1], df$ny[id1], edge, i0 = df$i0_real[id1], real_only = TRUE)
i2 <- index_real_edge(df$nx[id2], df$ny[id2], edge2, i0 = df$i0_real[id2], real_only = TRUE)
tibble::tibble(
row = i0 + rep(seq(length(i1)), 2),
col = c(i1, i2),
val = c(rep(1, length(i1)), rep(-1, length(i2)))
)
}
#get all domain connections in positive direction
dconn <- tibble::tibble(
type = c(df$type1, df$type2, df$type3, df$type4),
value = c(df$value1, df$value2, df$value3, df$value4),
id = rep(seq(nrow(df)), 4),
edge = rep(seq(4), each = nrow(df))
) %>%
dplyr::filter((.data$type == "c") & (.data$edge %in% c(2, 3))) %>%
dplyr::mutate(
i0_temp = ifelse(.data$edge == 2, df$nx[.data$id], df$ny[.data$id]),
i0_real = cumsum(c(0, utils::head(.data$i0_temp, -1))) + 2*sum(df$nx*df$ny)
)
mat_el2 <- dconn %>%
dplyr::rowwise() %>%
dplyr::summarise(flow_potential_connel_single(df, .data$id, .data$value, .data$edge, .data$i0_real))
lhs_el2 <- rep(0, length(unique(mat_el2$row)))
#bind all matrices and vectors together
mat <- Matrix::sparseMatrix(
i = c(mat_el1$row, mat_el2$row),
j = c(mat_el1$col, mat_el2$col),
x = c(mat_el1$val, mat_el2$val),
dims = c(2*ntotal_real + length(lhs_el2), ntotal_real)
)
lhs <- c(lhs_el1, lhs_el2)
#solve and assign
dp <- dplyr::mutate(dp, psi = Matrix::solve((Matrix::t(mat) %*% mat), (Matrix::t(mat) %*% lhs))[, 1])
#scale to between zero and one
dp$psi <- (dp$psi - min(dp$psi))/(max(dp$psi) - min(dp$psi))
#return
return(dp)
}
#' Plot flow net
#' @param dp flow net solution tibble
#' @examples
#' df <- generate_flownet_properties(grid_size = 0.5)
#' dp <- solve_flownet(df)
#' ds <- get_streamlines(df, dp)
#' ggplot_flownet(dp, ds = ds)
#' ggplot_flownet(dp)
ggplot_flownet <- function(dp, ds = NULL, Nd = 5){
plt <- ggplot2::ggplot() +
ggplot2::geom_tile(
data = dp,
ggplot2::aes(x = x, y = y, fill = psi)
) +
ggplot2::geom_contour(
data = dp,
ggplot2::aes(x = x, y = y, z = psi),
bins = Nd + 1
) +
ggplot2::coord_equal(ratio = 1)
if (!is.null(ds)) {
plt <- plt +
ggplot2::geom_path(
data = ds,
ggplot2::aes(x = x, y = y, group = as.factor(id))
)
}
return(plt)
}
#' Bilinear interpolation of data in long format
#'
#' @description
#' Bilinearly interpolates values in 2-dimensional fields.
#'
#' @param x array with x-data for each point. Should be sorted in row-first
#' order, e.g. 1, 2, 3, 1, 2, 3, 1, 2, 3 for a 3x3 matrix
#' @param x array with x-data for each point. Should be sorted in row-first
#' order, e.g. 1, 1, 1, 2, 2, 2, 3, 3, 3 for a 3x3 matrix
#' @param xout,yout arrays with x and y positions for output
#' @param ... named fields to be interpolated. fields should have same length
#' as `x` and `y`
#' @return a tibble with fields `x` and `y` for position and columns
#' `names(...)` with interpolated values for each fields in input `...`
#' @export
interp_bilinear_longform <- function(x, y, xout, yout, ...) {
#convert input to be interpolated to tibble
z <- tibble::as_tibble(list(...))
#unique x and y values
xu <- sort(unique(x))
yu <- sort(unique(y))
#length of vectors
nx <- length(xu)
ny <- length(yu)
#relative index of requested values in a grid position
ix <- 1 + (xout - min(xu)) / (max(xu) - min(xu)) * (nx - 1)
iy <- 1 + (yout - min(yu)) / (max(yu) - min(yu)) * (ny - 1)
#interpolate and return
tibble::tibble(
x = xout,
y = yout
) %>%
dplyr::mutate(
z[floor(ix) + nx*(floor(iy) - 1), ]*(1 - ix%%1)*(1 - iy%%1) +
z[ceiling(ix) + nx*(floor(iy) - 1), ]*(ix%%1)*(1 - iy%%1) +
z[floor(ix) + nx*(ceiling(iy) - 1), ]*(1 - ix%%1)*(iy%%1) +
z[ceiling(ix) + nx*(ceiling(iy) - 1), ]*(ix%%1)*(iy%%1)
)
}
#' Bilinear interpolation on a regular grid
#'
#' @description
#' Interpolate on a 2d grid using bilinear interpolation
#'
#' @param x,y arrays of grid x and y-values in increasing order
#' @param z matrix of values to interpolate. Matrix has `length(x)` number
#' of rows and `length(y)` number of columns
#' @param xout,yout arrays of x and y values at which to interpolate
#' @examples
#' x = c(1, 2, 3)
#' y = c(2, 3)
#' z = matrix(c(1, 2, 2, 4, 4, 7), nrow = 3, byrow = TRUE)
#' xout = c(1.0, 2.4)
#' yout = c(2.6, 3.0)
#' interp_bilinear(x, y, z, xout, yout)
#' @export
interp_bilinear <- function(x, y, z, xout, yout) {
#relative index of requested values
ix <- 1 + (xout - min(x)) / (max(x) - min(x)) * (length(x) - 1)
iy <- 1 + (yout - min(y)) / (max(y) - min(y)) * (length(y) - 1)
#interpolation
return(
z[matrix(c(floor(ix), floor(iy)), ncol = 2)]*(1 - ix%%1)*(1 - iy%%1) +
z[matrix(c(floor(ix), ceiling(iy)), ncol = 2)]*(1 - ix%%1)*(iy%%1) +
z[matrix(c(ceiling(ix), floor(iy)), ncol = 2)]*(ix%%1)*(1 - iy%%1) +
z[matrix(c(ceiling(ix), ceiling(iy)), ncol = 2)]*(ix%%1)*(iy%%1)
)
}
if (FALSE) {
dh <- dconn %>%
dplyr::filter(type == "h") %>%
dplyr::summarize(dh = max(value) - min(value)) %>%
dplyr::pull(dh)
#connection data in long format
dconn <- tibble::tibble(
type = c(df$type1, df$type2, df$type3, df$type4),
value = c(df$value1, df$value2, df$value3, df$value4),
id = rep(seq(nrow(df)), 4),
edge = rep(seq(4), each = nrow(df))
)
#select an edge at which head is highest
dedg <- dconn %>%
dplyr::filter(type == "h") %>%
dplyr::slice_max(.data$value)
#indices of nodes on edge
iedg <- index_real_edge(df$nx[dedg$id], df$ny[dedg$id], dedg$edge, i0 = df$i0_real[dedg$id], real_only = TRUE)
#integrate flow through edge
if (dedg$edge %in% c(1, 3)) {
L <- diff(dp$y[iedg])
q <- dp$qx[iedg]
k <- df$kx[dedg$id]
} else {
L <- diff(dp$x[iedg])
q <- dp$qy[iedg]
k <- df$ky[dedg$id]
}
Q <- sum(L*0.5*(utils::head(q, -1) + utils::tail(q, -1)))
#average gradient of flow potential across edge
dpsi <- max(dp$psi[iedg]) - min(dp$psi[iedg])
#number of equipotential intervals
Nd <- round(abs(Nf*k*dh/Q))
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.