Skip to content

Commit

Permalink
Merge branch 'cmf2' into flux_connection_director
Browse files Browse the repository at this point in the history
# Conflicts:
#	cmf/__init__.py
#	cmf/cmf_core_src/cmf_wrap.cpp
#	cmf/cmf_core_src/docstrings.i
#	setup.py
#	tools/Doxyfile
  • Loading branch information
Philipp Kraft committed Jan 19, 2024
2 parents ab93c5c + d7ad78c commit 41976e2
Show file tree
Hide file tree
Showing 44 changed files with 1,674 additions and 710 deletions.
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ __pycache__/
*.VC.opendb

# Distribution / packaging
.Python
build/
.Pythonbuild/
develop-eggs/
dist/
downloads/
Expand Down Expand Up @@ -100,3 +99,5 @@ cmake-build-*/
*.sbn
*.sbx
nohup.out
/demo/results/
/build/
5 changes: 1 addition & 4 deletions cmf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@
from .timetools import StopWatch, datetime_to_cmf, timerange


__version__ = '2.0.0b6.flux_connection_director'
__compiletime__ = 'Mon Jan 18 17:51:17 2021'
__version__ = '2.0.0b7'

from .cmf_core import connect_cells_with_flux as __ccwf

Expand All @@ -41,5 +40,3 @@ def connect_cells_with_flux(cells, connection, start_at_layer=0):
raise TypeError("flux_connection does not implement the cell_connector protocol")




972 changes: 508 additions & 464 deletions cmf/cmf_core.py

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion cmf/cmf_core_src/math/real.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ typedef double real;
const real REAL_MAX = std::numeric_limits<real>::max();
#endif

const std::string __compiledate__ = std::string("cmf compiled ") + std::string(__DATE__) + " - " + std::string(__TIME__);

// Some helper functions
/// Returns the minimum of two values
Expand Down
41 changes: 27 additions & 14 deletions cmf/cmf_core_src/upslope/surfacewater.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,30 @@ void DiffusiveSurfaceRunoff::connect_cells( cmf::upslope::Cell& cell1,cmf::upslo
new DiffusiveSurfaceRunoff(sw_upper,lower.get_surfacewater(),w);
}

}
/// Returns the square root of the gradient with singularity prevention
///
/// \param grad Gradient in m/m
/// \return sign(grad) * sqrt(grad) but with a flatted singularity at grad = 0
real sqrt_slope(real grad) {

const real s_sqrt = sign(grad) * sqrt(std::abs(grad));
// linear slope width is a value in which slope range the slope should be altered
// to prevent a singularity in dq/ds
if (cmf::options.diffusive_slope_singularity_protection > 0.0) {
// Only a shortcut for faster writing
const real &s0 = cmf::options.diffusive_slope_singularity_protection;
// Weight of linear part
real w_lin = exp(-square((grad / s0)));
// linear part using the slope at s0/4
real s_lin = grad / (2. * sqrt(s0 / 4));
// Weighted sum of sqrt(s) and a*s
return w_lin * s_lin + (1 - w_lin) * s_sqrt;
} else {
return s_sqrt;

}

}

real DiffusiveSurfaceRunoff::calc_q( cmf::math::Time t )
Expand Down Expand Up @@ -101,21 +125,9 @@ real DiffusiveSurfaceRunoff::calc_q( cmf::math::Time t )
// Get slope
real grad = dPsi/m_distance;

// Get signed square root for
real s_sqrt = sign(grad) * sqrt(std::abs(grad));
// Get signed square root for gradient
real s_sqrt = sqrt_slope(grad);

// linear slope width is a value in which slope range the slope should be altered
// to prevent a singularity in dq/ds
if (cmf::options.diffusive_slope_singularity_protection > 0.0) {
// Only a shortcut for faster writing
const real & s0 = cmf::options.diffusive_slope_singularity_protection;
// Weight of linear part
real w_lin = exp(-square((grad/s0)));
// linear part using the slope at s0/4
real s_lin =grad/(2.*sqrt(s0/4));
// Weighted sum of sqrt(s) and a*s
s_sqrt = w_lin * s_lin + (1-w_lin) * s_sqrt;
}

return prevent_negative_volume(m_flowwidth * pow(d,5/3.) * s_sqrt/left->get_nManning() * 86400.);
}
Expand All @@ -126,3 +138,4 @@ void DiffusiveSurfaceRunoff::NewNodes()
wright = SurfaceWater::cast(right_node());
owright=OpenWaterStorage::cast(right_node());
}

10 changes: 6 additions & 4 deletions cmf/cmf_core_src/upslope/surfacewater.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,10 @@ namespace cmf {
private:
static void connect_cells(cmf::upslope::Cell& c1,cmf::upslope::Cell& c2,ptrdiff_t dummy);
std::weak_ptr<cmf::upslope::SurfaceWater> wleft;
real m_distance, m_flowwidth;
protected:
virtual real calc_q(cmf::math::Time t);
void NewNodes();
real m_distance, m_flowwidth;
public:
/// Creates a KinematicSurfaceRunoff between a SurfaceWater (left) with another (right) node.
///
Expand Down Expand Up @@ -187,10 +188,11 @@ namespace cmf {
std::weak_ptr<cmf::upslope::SurfaceWater> wleft;
std::weak_ptr<cmf::river::OpenWaterStorage> owright;
std::weak_ptr<cmf::upslope::SurfaceWater> wright;
real calc_q(cmf::math::Time t) override;
void NewNodes() override;
real m_distance, m_flowwidth;
public:
protected:
real calc_q(cmf::math::Time t) override;
void NewNodes() override;
public:
DiffusiveSurfaceRunoff(cmf::upslope::SurfaceWater::ptr left,cmf::water::flux_node::ptr right,real flowwidth,real distance=-1)
: flux_connection(left,right,"Diffusive surface runoff"), m_distance(distance), m_flowwidth(flowwidth) {

Expand Down
18 changes: 18 additions & 0 deletions cmf/cmf_core_src/water/simple_connections.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,21 @@ real cmf::water::WaterbalanceFlux::calc_q(cmf::math::Time t) {
if (q > 0) return q;
else return 0.0;
}

cmf::water::PartitionFluxRoute::PartitionFluxRoute(flux_node::ptr source, flux_node::ptr target1,
flux_node::ptr target2, real _fraction, bool _no_back_flow)
: flux_connection(target1, target2, "PartitionFluxRoute"), fraction(_fraction), no_back_flow(_no_back_flow), m_source(source)
{
if (fraction < 0.0 || fraction > 1.0) {
throw std::runtime_error("PartitionFluxRoute: fraction must be in [0..1]");
}
RecalcAlways = true;
}

real cmf::water::PartitionFluxRoute::calc_q(cmf::math::Time t) {
real q_original = source()->flux_to(*left_node(), t);
if (q_original < 0 && no_back_flow) {
return 0.0;
}
return fraction * q_original;
}
33 changes: 32 additions & 1 deletion cmf/cmf_core_src/water/simple_connections.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,38 @@ namespace cmf {
WaterbalanceFlux(flux_node::ptr source, flux_node::ptr target);
};

/// @ingroup connections
/// @ingroup connections
/// @brief Routes a fraction of the flux calculated from a master flux_connection between source to target1
/// directly further to target2 without any timelag. The connection connects target 1 and target 2.
///
/// \f[ q_{t1,t2} = f \cdot q_{s, t1}(t) \f]
///
/// Where \f$ q_{t1,t2}\f$ is the flux from t1 to t2, f is the fraction and \f$ q_{s,t1}(t)\f$ is the original
/// flux
class PartitionFluxRoute : public flux_connection
{
private:
std::weak_ptr<flux_node> m_source;
protected:
real calc_q(cmf::math::Time t) override;
void NewNodes() {}
public:
/// @param source Water storage from which the water flows out. The flux is defined by some other flux connection between
/// @param target1 Target node (boundary condition or storage). The target of the master flux connection
/// @param target2 Target node (boundary condition or storage). Does not influence the strength of the flow
/// @param fraction Fraction of the source->target1 flow to be routed further to target2
/// @param no_back_flow If true (default), no flow between target 1 and target 2 occurs, if water is flowing from
/// target 1 to source. If set to false, in the case of water flowing from target 1 to source,
/// the fraction is also flowing from target 2 to target 1. No test if target 2 has water is made.
PartitionFluxRoute(flux_node::ptr source, flux_node::ptr target1, flux_node::ptr target2, real fraction, bool no_back_flow=true);
flux_node::ptr source() const {
return m_source.expired() ? flux_node::ptr() : flux_node::ptr(m_source);
}
real fraction;
bool no_back_flow;
};

/// @ingroup connections
/// @brief Flux from one node to another, controlled by the user or an external program,
/// by changing the flux constant
///
Expand Down
202 changes: 202 additions & 0 deletions cmf/geometry/irregular_grid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import numpy as np
import scipy.spatial as scsp
from shapely.geometry import Polygon

from .. import project as Project
from . import create_cell, mesh_project

def delaunay_cells(project: Project, x, y, z):
"""
Creates a mesh using delaunay triangles between the given coordinates as cells.
cell.x, cell.y and cell.z represent the centroid of the cell. This results in loss of information.
In most cases one would use the voronoi_cells function
x, y and z must have the same length
:param project:
:param x: sequence of x coordinates
:param y: sequence of y coordinates
:param z: sequence of heights
:return: list of cells
"""
points = np.array([x, y]).T
tri = scsp.Delaunay(points)

def get_edge_length(tri: scsp.Delaunay, tri1: int, tri2: int):
try:
n1, n2 = set(tri.simplices[tri1]) & set(tri.simplices[tri2])
except ValueError:
raise ValueError(
f'Triangle {tri1}:{tri.simplices[tri1]} and {tri2}:{tri.simplices[tri2]} do not share an edge')
c1, c2 = tri.points[[n1, n2]]
return np.sqrt(((c1 - c2) ** 2).sum())

def get_polygons(tri: scsp.Delaunay):

return [
Polygon(tri.points[s])
for s in tri.simplices
]

def get_heights(tri: scsp.Delaunay, z):
return z[tri.simplices].mean(1)

polys = get_polygons(tri)
z_cell = get_heights(tri, z)

cells = [
create_cell(project, polygon, height)
for polygon, height in zip(polys, z_cell)
]

for i, c in enumerate(cells):
for n in tri.neighbors[i]:
if n >= 0:
l = get_edge_length(tri, i, n)
c.topology.AddNeighbor(cells[n], l)

return cells


# %%
def __voronoi_finite_polygons_2d(vor, radius=None):
"""
Reconstruct infinite voronoi regions in a 2D diagram to finite
regions.
Parameters
----------
vor : Voronoi
Input diagram
radius : float, optional
Distance to 'points at infinity'.
Returns
-------
regions : list of tuples
Indices of vertices in each revised Voronoi regions.
vertices : list of tuples
Coordinates for revised Voronoi vertices. Same as coordinates
of input vertices, with 'points at infinity' appended to the
end.
"""

if vor.points.shape[1] != 2:
raise ValueError("Requires 2D input")

new_regions = []
new_vertices = vor.vertices.tolist()

center = vor.points.mean(axis=0)
if radius is None:
radius = vor.points.ptp(axis=0).max()

# Construct a map containing all ridges for a given point
all_ridges = {}
for (p1, p2), (v1, v2) in zip(vor.ridge_points, vor.ridge_vertices):
all_ridges.setdefault(p1, []).append((p2, v1, v2))
all_ridges.setdefault(p2, []).append((p1, v1, v2))

# Reconstruct infinite regions
for p1, region in enumerate(vor.point_region):
vertices = vor.regions[region]

if all(v >= 0 for v in vertices):
# finite region
new_regions.append(vertices)
continue
elif p1 not in all_ridges:
continue
# reconstruct a non-finite region
ridges = all_ridges[p1]
new_region = [v for v in vertices if v >= 0]

for p2, v1, v2 in ridges:
if v2 < 0:
v1, v2 = v2, v1
if v1 >= 0:
# finite ridge: already in the region
continue

# Compute the missing endpoint of an infinite ridge

t = vor.points[p2] - vor.points[p1] # tangent
t /= np.linalg.norm(t)
n = np.array([-t[1], t[0]]) # normal

midpoint = vor.points[[p1, p2]].mean(axis=0)
direction = np.sign(np.dot(midpoint - center, n)) * n
far_point = vor.vertices[v2] + direction * radius

new_region.append(len(new_vertices))
new_vertices.append(far_point.tolist())

# sort region counterclockwise
vs = np.asarray([new_vertices[v] for v in new_region])
c = vs.mean(axis=0)
angles = np.arctan2(vs[:, 1] - c[1], vs[:, 0] - c[0])
new_region = np.array(new_region)[np.argsort(angles)]

# finish
new_regions.append(new_region.tolist())

return new_regions, np.asarray(new_vertices)

def voronoi_polygons(x, y, buffer=0.0, mask=None):
"""
Creates closed voronoi polygons for x and y coordinates. With voronoi_cells, cmf cells are easily created
from x, y and z coordinates. Use this function directly only, if you need to process the voronoi polygons further,
eg. with unions of landuse or soil maps.
If this throws an unclear error, make sure you do not have any duplicate coordinates
:param x: a sequence of x values
:param y: a sequence of y values
:param buffer: a float indicating the buffer around the hull. Expressed as a fraction of the sqrt of the hull's area
:param mask: a polygon to use as a mask. If given, the convex hull is ignored
:return: A list of polygons
"""
points = np.array([x, y]).T
vor = scsp.Voronoi(points)
if not mask:
hull = Polygon(points[scsp.ConvexHull(points).vertices])
mask = hull.buffer(buffer * hull.area ** 0.5)
regions, vertices = __voronoi_finite_polygons_2d(vor)
return [Polygon(vertices[r]).intersection(mask) for r in regions]


def voronoi_cells(project: Project, x, y, z, buffer=0.0, mask=None):
"""
Creates for each (x, y, z) coordinate a voronoi (nearest neighbor) cell.
The cells are cut of at a buffer around the convex hull of the point or using a mask.
For each coordinate a cell is created.
If this throws an unclear error, make sure you do not have any duplicate coordinates
:param project: The cmf project
:param x: a sequence of x values
:param y: a sequence of x values
:param z: a sequence of x values
:param buffer: a float indicating the buffer around the hull. Expressed as a fraction of the sqrt of the hull's area
:param mask: a polygon to use as a mask. If given, the convex hull is ignored
:return: list of cells
"""
def make_cell(project, x, y, z, shape, Id=None, with_surfacewater=False):
cell = project.NewCell(x, y, z, shape.area, with_surfacewater=with_surfacewater)
cell.geometry = shape
if Id is not None:
cell.Id = Id
return cell

vpolys = voronoi_polygons(x, y, z, buffer, mask)
cells = [
make_cell(project, x[i], y[i], z[i], vpolys[i], Id=i, with_surfacewater=True)
for i in range(len(x))
]

mesh_project(project)

return cells

Loading

0 comments on commit 41976e2

Please sign in to comment.