Skip to content

Added terrain refinement option in tagging #1633

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 34 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
27f85d7
Added terrain refiner
ews-ffarella Jun 2, 2025
42d53ea
Added possibility to specify a polygon as input
ews-ffarella Jun 2, 2025
8e479b5
Inner rings
ews-ffarella Jun 2, 2025
70b09e4
Added checks for inner rings
ews-ffarella Jun 2, 2025
c863448
Real example
ews-ffarella Jun 2, 2025
8013e4b
fmt
ews-ffarella Jun 2, 2025
00331b5
Documentation
ews-ffarella Jun 2, 2025
05de011
Really?
ews-ffarella Jun 2, 2025
87c95ba
Big refractor, added unit test and integration test. Made Polygon reu…
ews-ffarella Jun 3, 2025
a3298c6
Merge pull request #3 from Exawind/main
ews-ffarella Jun 3, 2025
bcd0030
clang
ews-ffarella Jun 3, 2025
e29f820
Relase mode
ews-ffarella Jun 3, 2025
1f120d4
clang tidy
ews-ffarella Jun 3, 2025
3aaad45
clang-tidy
ews-ffarella Jun 3, 2025
b7a1d49
More advanced tests and edge fix
ews-ffarella Jun 3, 2025
dc0fcae
Restore terrain_box_amr
ews-ffarella Jun 3, 2025
d51529a
Do not print out the polygons
ews-ffarella Jun 3, 2025
4aaeda5
Update test/CMakeLists.txt: remove missing reg test
mbkuhn Jun 3, 2025
11cda56
add to documentation
mbkuhn Jun 3, 2025
10927be
Merge branch 'main' into 1-terrain-based-refinement
ews-ffarella Jun 3, 2025
b5a40e2
[ci skip] Max level actually apply to the current grid level.
ews-ffarella Jun 4, 2025
01913ce
Merge branch '1-terrain-based-refinement' of github.com:ews-ffarella/…
ews-ffarella Jun 4, 2025
9ff4b82
Added more static methods to polygon
ews-ffarella Jun 4, 2025
169b12e
Optimize tagging
ews-ffarella Jun 4, 2025
94e3eef
Update docs
ews-ffarella Jun 4, 2025
d445abb
Fmt
ews-ffarella Jun 4, 2025
0829baf
tidy
ews-ffarella Jun 4, 2025
0310ba6
Merge branch 'main' into 1-terrain-based-refinement
ews-ffarella Jun 5, 2025
671375e
Merge branch '1-terrain-based-refinement' of github.com:ews-ffarella/…
ews-ffarella Jun 5, 2025
4da98d0
Added optimization
ews-ffarella Jun 5, 2025
e77ebdb
Remove bad macros for GPU
ews-ffarella Jun 12, 2025
6cae8df
Always initialize terrain drag
ews-ffarella Jun 13, 2025
75f9b45
Refactor TerrainRefinement: per-level buffer ratios and verbosity, im…
ews-ffarella Jun 14, 2025
f02a28a
Merge branch 'main' into 1-terrain-based-refinement
ews-ffarella Jun 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions amr-wind/utilities/tagging/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ target_sources(${amr_wind_lib_name} PRIVATE
VorticityMagRefinement.cpp
OversetRefinement.cpp
GeometryRefinement.cpp
TerrainRefinement.cpp

# Geometry based refinement options
BoxRefiner.cpp
Expand Down
78 changes: 78 additions & 0 deletions amr-wind/utilities/tagging/TerrainRefinement.H
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#ifndef FIELDREFINEMENT_H
#define FIELDREFINEMENT_H

#include "amr-wind/CFDSim.H"
#include "amr-wind/utilities/tagging/poly_ops.H"
#include "amr-wind/utilities/tagging/RefinementCriteria.H"

namespace amr_wind {
// class CFDSim;
class Field;
// class IntField;

/** AMR refinement using a given distance to terrain
* \ingroup amr_utils
*
* ```
* tagging.labels = terrain_lvl1 terrain_lvl2
* tagging.terrain_lvl1.type = TerrainRefinement
* tagging.terrain_lvl1.vertical_distance = 100
* tagging.terrain_lvl1.level = 1
* tagging.terrain_lvl1.poly_outer = ...
* tagging.terrain_lvl1.poly_inners = ...
* tagging.terrain_lvl1.box_lo = ...
* tagging.terrain_lvl1.box_hi = ...
* tagging.terrain_lvl2.type = TerrainRefinement
* tagging.terrain_lvl2.vertical_distance = 40
* tagging.terrain_lvl2.level = 2
* tagging.terrain_lvl2.poly_outer = ...
*
* poly_outer should be define as: number_of_points pt0_x pt0_y ... ptN_x ptN_y
* poly_outer poly_inners be define as: number_of_inner_rings
* number_of_points_in_ring0 r0_pt0_x r0_pt0_x ... r0_ptN_x r0_ptN_x
* number_of_points_in_ringN rN_pt0_x rN_pt0_x ... rN_ptN_x rN_ptN_x
* ```
*/

class TerrainRefinement : public RefinementCriteria::Register<TerrainRefinement>
{
public:
static std::string identifier() { return "TerrainRefinement"; }

explicit TerrainRefinement(const CFDSim& sim);

~TerrainRefinement() override = default;

//! Read input file and initialize boxarray used to refine each level
void initialize(const std::string& key) override;

void operator()(
const int level,
amrex::TagBoxArray& tags,
const amrex::Real time,
const int ngrow) override;

private:
const CFDSim& m_sim;

// Pointer to the terrain_height field.
Field* m_terrain_height{nullptr};
// Pointer to the terrain_blank field so that we try not to refine under the
// terrain
IntField* m_terrain_blank{nullptr};
// Target grid refinement level
int m_max_lev{0};
// Distance above the terrain to refine
amrex::Real m_vertical_distance{0.0};
// The usual tagging bbox
amrex::RealBox m_tagging_box;

// Optional: The coordinates of the polygon's exterior ring
amrex::Vector<amr_wind::polygon_utils::Point> m_poly_outer;
// Optional: The coordinates of the polygon's interior ring(s)
amrex::Vector<amrex::Vector<amr_wind::polygon_utils::Point>> m_poly_rings;
};

} // namespace amr_wind

#endif /* TERRAINREFINEMENT_H */
148 changes: 148 additions & 0 deletions amr-wind/utilities/tagging/TerrainRefinement.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#include "amr-wind/utilities/tagging/TerrainRefinement.H"
#include "amr-wind/CFDSim.H"

#include "AMReX.H"
#include "AMReX_ParmParse.H"

namespace amr_wind {

TerrainRefinement::TerrainRefinement(const CFDSim& sim)
: m_sim(sim), m_tagging_box(m_sim.repo().mesh().Geom(0).ProbDomain())
{}

void TerrainRefinement::initialize(const std::string& key)
{
amrex::ParmParse pp(key);

const auto& repo = m_sim.repo();

const bool is_terrain = repo.field_exists("terrain_height");
if (!is_terrain) {
amrex::Abort("Need terrain blanking variable to use this refinement");
}
m_terrain_height = &(m_sim.repo().get_field("terrain_height"));
m_terrain_blank = &(m_sim.repo().get_int_field("terrain_blank"));

// Outer radial extent of the cylinder, always read in from input file
pp.get("vertical_distance", m_vertical_distance);
pp.get("level", m_max_lev);
if (m_max_lev <= 0) {
amrex::Abort("TerrainRefinement: level should be strictly above 0");
}

amrex::Vector<amrex::Real> poly_outer;
amrex::Vector<amrex::Real> poly_inners;
pp.queryarr("poly_outer", poly_outer);
if (poly_outer.size() > 0) {
pp.getarr("poly_outer", poly_outer);
const int n = poly_outer.size();
const int n_points = static_cast<int>(poly_outer[0]);
const int n_expected = n_points * 2 + 1;
if (n_expected != n) {
amrex::Abort(
"Expected a list of " + std::to_string(n_expected) +
" numbers, found " + std::to_string(n - 1) + "!");
}
m_poly_outer.resize(n_points);
for (int i = 0; i < n_points; ++i) {
const auto pt_x = poly_outer[1 + 2 * i];
const auto pt_y = poly_outer[1 + 2 * i + 1];
m_poly_outer[i] = amr_wind::polygon_utils::Point({pt_x, pt_y});
}
pp.queryarr("poly_inners", poly_inners);
if (poly_inners.size() > 0) {
const int n_rings = static_cast<int>(poly_inners[0]);
m_poly_rings.resize(n_rings);
int offset = 1;
for (int ring_i = 0; ring_i < n_rings; ++ring_i) {
const int n_pts = static_cast<int>(poly_inners[offset]);
m_poly_rings[ring_i].resize(n_pts);
offset += 1;
for (int pt_i = 0; pt_i < n_pts; ++pt_i) {
const auto pt_x = poly_inners[offset + 2 * pt_i];
const auto pt_y = poly_inners[offset + 2 * pt_i + 1];
m_poly_rings[ring_i][pt_i] =
amr_wind::polygon_utils::Point({pt_x, pt_y});
}
offset += 2 * n_pts;
}
if ((n_rings > 0) && (offset != poly_inners.size())) {
amrex::Abort(
"Expected a list of " + std::to_string(offset) +
" numbers, found " + std::to_string(poly_inners.size()) +
"!");
}
}
}

amrex::Vector<amrex::Real> box_lo(AMREX_SPACEDIM, 0);
amrex::Vector<amrex::Real> box_hi(AMREX_SPACEDIM, 0);
if (pp.queryarr("box_lo", box_lo, 0, static_cast<int>(box_lo.size())) ==
1) {
m_tagging_box.setLo(box_lo);
}
if (pp.queryarr("box_hi", box_hi, 0, static_cast<int>(box_hi.size())) ==
1) {
m_tagging_box.setHi(box_hi);
}

amrex::Print() << "Created terrain refinement with level " << m_max_lev
<< " and vertical distance " << m_vertical_distance
<< std::endl;
}

void TerrainRefinement::operator()(
int level, amrex::TagBoxArray& tags, amrex::Real time, int /*ngrow*/)
{
const bool do_tag = level < m_max_lev;
if (!do_tag) {
return;
}
const auto& repo = m_sim.repo();
const auto& geom = repo.mesh().Geom(level);
const auto& prob_lo = geom.ProbLoArray();
const auto& dx = geom.CellSizeArray();
const auto tagging_box = m_tagging_box;

const auto& tag_arrs = tags.arrays();
const auto& mfab = (*m_terrain_height)(level);
const auto& mterrain_h_arrs = mfab.const_arrays();
const auto& mterrain_b_arrs = (*m_terrain_blank)(level).const_arrays();

amrex::ParallelFor(
mfab, [=] AMREX_GPU_DEVICE(int nbx, int i, int j, int k) noexcept {
const amrex::Real z = prob_lo[2] + (k + 0.5) * dx[2];
const amrex::Real terrainHt = mterrain_h_arrs[nbx](i, j, k);
const amrex::Real cellHt = z - terrainHt;

const amrex::RealVect coord = {AMREX_D_DECL(
prob_lo[0] + (i + 0.5) * dx[0], prob_lo[1] + (j + 0.5) * dx[1],
prob_lo[2] + (k + 0.5) * dx[2])};

const auto testPt =
amr_wind::polygon_utils::Point({coord[0], coord[1]});
bool in_poly = m_poly_outer.size()
? amr_wind::polygon_utils::is_point_in_polygon(
m_poly_outer, testPt)
: true;
if (in_poly) {
for (int ring_i = 0; ring_i < m_poly_rings.size(); ++ring_i) {
const bool in_ring =
amr_wind::polygon_utils::is_point_in_polygon(
m_poly_rings[ring_i], testPt);
if (in_ring) {
in_poly = false;
break;
}
}
}

if (((cellHt >= -0.5) * dx[2] && (cellHt <= m_vertical_distance)) &&
(mterrain_b_arrs[nbx](i, j, k) < 1) && in_poly &&
(tagging_box.contains(coord))) {
tag_arrs[nbx](i, j, k) = amrex::TagBox::SET;
}
});
}

} // namespace amr_wind
84 changes: 84 additions & 0 deletions amr-wind/utilities/tagging/poly_ops.H
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is ugly as ***. This was a quick proof of concept, taken from internet. I had used this mehod before in Openfoam.

As we probably won't have super complex polygons, I think this is fast enough ...

AMReX probably already has a class for this, but I haven't found it.

Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#ifndef POLY_OPS_H
#define POLY_OPS_H

#include "AMReX.H"
#include <cmath>
#include "AMReX_REAL.H"
#include "AMReX_Gpu.H"

// Taken from https://www.geeksforgeeks.org/point-in-polygon-in-cpp/
namespace amr_wind::polygon_utils {

struct Point
{
amrex::Real x, y;
};

// Function to compute the cross product of vectors (p1p2)
// and (p1p3)

AMREX_FORCE_INLINE amrex::Real
cross_product(const Point& p1, const Point& p2, const Point& p3)
{
return (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x);
}

// Function to check if point p lies on segment p1p2
AMREX_FORCE_INLINE bool
is_point_on_segment(const Point& p, const Point& p1, const Point& p2)
{
// Check if point p lies on the line segment p1p2 and
// within the bounding box of p1p2
return cross_product(p1, p2, p) == 0 && p.x >= std::min(p1.x, p2.x) &&
p.x <= std::max(p1.x, p2.x) && p.y >= std::min(p1.y, p2.y) &&
p.y <= std::max(p1.y, p2.y);
}

// Function to compute the winding number of a point with
// respect to a polygon
int winding_number(const amrex::Vector<Point>& polygon, const Point& point)
{
int n = polygon.size();
int windingNumber = 0;

// Iterate through each edge of the polygon
for (int i = 0; i < n; i++) {
Point p1 = polygon[i];
// Next vertex in the polygon
Point p2 = polygon[(i + 1) % n];

// Check if the point lies on the current edge
if (is_point_on_segment(point, p1, p2)) {
// Point is on the polygon boundary
return 0;
}

// Calculate the cross product to determine winding
// direction
if (p1.y <= point.y) {
if (p2.y > point.y && cross_product(p1, p2, point) > 0) {
windingNumber++;
}
} else {
if (p2.y <= point.y && cross_product(p1, p2, point) < 0) {
windingNumber--;
}
}
}
// Return the winding number
return windingNumber;
}

// Function to check if a point is inside a polygon using
// the winding number algorithm
bool is_point_in_polygon(
const amrex::Vector<Point>& polygon, const Point& point)
{
// Compute the winding number for the point with respect
// to the polygon
return winding_number(polygon, point) != 0;
}

} // namespace amr_wind::polygon_utils

#endif /* POLY_OPS_H */
Loading