Skip to content

Commit aa6b442

Browse files
committed
feat: implement ghost node handling and reset functionality in BlockRodSystem
- Added methods to retrieve ghost node, element, and voronoi indices. - Implemented reset functionality for ghost values of specific variables and all variables. - Added unit tests for ghost index retrieval and reset operations in both C++ and Python.
1 parent 336d02b commit aa6b442

7 files changed

Lines changed: 692 additions & 378 deletions

File tree

backend/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ FetchContent_Declare(
3636
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
3737
GIT_TAG v3.5.4
3838
)
39+
set(CATCH_BUILD_TESTING OFF CACHE BOOL "" FORCE)
40+
set(CATCH_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
3941
FetchContent_MakeAvailable(Catch2)
4042

4143
# Fetch Eigen3 for numerical computations

backend/src/_api.cpp

Lines changed: 114 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,25 +80,71 @@ auto get_block_variable_by_name(BlockType& block, const std::string& var_name) {
8080
}
8181
}
8282

83+
// Helper to reset ghost for a variable by name
84+
template<typename BlockType, typename VariablesTuple, std::size_t Index>
85+
void reset_ghost_for_variable_by_name_impl(BlockType& block, const std::string& var_name) {
86+
using CurrentVar = std::tuple_element_t<Index, VariablesTuple>;
87+
88+
// Check if current variable's name matches
89+
if (var_name == std::string(CurrentVar::name)) {
90+
block.template reset_ghost_for_variable<CurrentVar>();
91+
return;
92+
}
93+
94+
// Recurse to next variable if not last
95+
if constexpr (Index + 1 < std::tuple_size_v<VariablesTuple>) {
96+
reset_ghost_for_variable_by_name_impl<BlockType, VariablesTuple, Index + 1>(block, var_name);
97+
} else {
98+
throw std::runtime_error("Unknown variable name: " + var_name);
99+
}
100+
}
101+
102+
// Helper function to reset ghost for a variable by name
103+
template<typename BlockType>
104+
void reset_ghost_for_variable_by_name(BlockType& block, const std::string& var_name) {
105+
using VariablesTuple = typename BlockType::Variables;
106+
107+
if constexpr (std::tuple_size_v<VariablesTuple> > 0) {
108+
reset_ghost_for_variable_by_name_impl<BlockType, VariablesTuple, 0>(block, var_name);
109+
} else {
110+
throw std::runtime_error("System has no variables");
111+
}
112+
}
113+
83114
// Helper to convert Eigen Block view to numpy array
84115
template<typename BlockExpr>
85116
py::array_t<double> block_to_numpy(BlockExpr&& block_expr, py::object parent) {
86117
// Evaluate the expression to get dimensions
87118
auto rows = static_cast<py::ssize_t>(block_expr.rows());
88119
auto cols = static_cast<py::ssize_t>(block_expr.cols());
89120

90-
// Compute strides based on storage order
91-
auto strides = compute_strides(
92-
static_cast<std::size_t>(rows),
93-
static_cast<std::size_t>(cols)
94-
);
121+
// Get actual strides from the Eigen Block expression
122+
// For Eigen Blocks, innerStride() is the stride between elements in the same row/column
123+
// and outerStride() is the stride between rows/columns depending on storage order
124+
// For column-major: innerStride() = 1 (between rows), outerStride() = underlying_rows (between columns)
125+
// For row-major: innerStride() = 1 (between columns), outerStride() = underlying_cols (between rows)
126+
auto inner_stride = static_cast<py::ssize_t>(block_expr.innerStride() * sizeof(double));
127+
auto outer_stride = static_cast<py::ssize_t>(block_expr.outerStride() * sizeof(double));
128+
129+
// For numpy, strides are in bytes and represent the step size for each dimension
130+
// For column-major (Eigen default): row_stride = inner_stride, col_stride = outer_stride
131+
// For row-major: row_stride = outer_stride, col_stride = inner_stride
132+
py::ssize_t row_stride, col_stride;
133+
if constexpr (IsColMajor) {
134+
// Column-major: stride between rows is inner_stride, between columns is outer_stride
135+
row_stride = inner_stride;
136+
col_stride = outer_stride;
137+
} else {
138+
// Row-major: stride between rows is outer_stride, between columns is inner_stride
139+
row_stride = outer_stride;
140+
col_stride = inner_stride;
141+
}
95142

96-
// Create numpy array view (non-owning)
143+
// Create numpy array view (non-owning) with correct strides
97144
return py::array_t<double>(
98145
{rows, cols},
99-
{static_cast<py::ssize_t>(strides.first),
100-
static_cast<py::ssize_t>(strides.second)},
101-
block_expr.data(),
146+
{row_stride, col_stride},
147+
const_cast<double*>(block_expr.data()),
102148
parent // Keep parent object alive
103149
);
104150
}
@@ -261,6 +307,65 @@ PYBIND11_MODULE(_memory_block, m) {
261307
262308
This operation updates the dynamic variables including forces
263309
and torques based on the current state.
310+
)pbdoc")
311+
.def_property_readonly("ghost_nodes_idx", [](const BlockRodSystem& block) {
312+
auto indices = block.ghost_nodes_idx();
313+
// Convert to numpy array (pybind11 will handle the conversion automatically)
314+
return py::cast(indices);
315+
},
316+
R"pbdoc(
317+
Get indices of ghost nodes between rods.
318+
319+
Returns:
320+
numpy.ndarray: An array of ghost node indices (length: n_rods - 1).
321+
The array does not own the data.
322+
)pbdoc",
323+
py::keep_alive<0, 1>())
324+
.def_property_readonly("ghost_elems_idx", [](const BlockRodSystem& block) {
325+
auto indices = block.ghost_elems_idx();
326+
// Convert to numpy array (pybind11 will handle the conversion automatically)
327+
return py::cast(indices);
328+
},
329+
R"pbdoc(
330+
Get indices of ghost elements between rods.
331+
332+
Returns:
333+
numpy.ndarray: An array of ghost element indices (length: 2 * (n_rods - 1)).
334+
The array does not own the data.
335+
)pbdoc",
336+
py::keep_alive<0, 1>())
337+
.def_property_readonly("ghost_voronoi_idx", [](const BlockRodSystem& block) {
338+
auto indices = block.ghost_voronoi_idx();
339+
// Convert to numpy array (pybind11 will handle the conversion automatically)
340+
return py::cast(indices);
341+
},
342+
R"pbdoc(
343+
Get indices of ghost voronoi nodes between rods.
344+
345+
Returns:
346+
numpy.ndarray: An array of ghost voronoi indices (length: 3 * (n_rods - 1)).
347+
The array does not own the data.
348+
)pbdoc",
349+
py::keep_alive<0, 1>())
350+
.def("reset_ghost_for_variable", [](BlockRodSystem& block, const std::string& var_name) {
351+
// Helper to reset ghost for a variable by name
352+
reset_ghost_for_variable_by_name(block, var_name);
353+
},
354+
R"pbdoc(
355+
Reset ghost values for a specific variable by name.
356+
357+
Args:
358+
var_name: Name of the variable (e.g., "position", "velocity", "director")
359+
)pbdoc",
360+
py::arg("var_name"))
361+
.def("reset_ghost", [](BlockRodSystem& block) {
362+
block.reset_ghost();
363+
},
364+
R"pbdoc(
365+
Reset ghost values for all variables.
366+
367+
This operation sets all ghost node/element/voronoi values to their
368+
default ghost_value as defined in each variable type.
264369
)pbdoc");
265370

266371
// BlockView class

backend/src/block.h

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class Block : public SystemType, public OperationsType<Block<SystemType, Operati
2929
compute_width_and_indices(n_elems_per_rod);
3030
depth_ = SystemType::get_depth();
3131
data_ = MatrixType(static_cast<Eigen::Index>(depth_), static_cast<Eigen::Index>(width_));
32+
reset_ghost(); // Initialize all ghost values
3233
}
3334

3435
std::pair<std::size_t, std::size_t> shape() const {
@@ -72,6 +73,100 @@ class Block : public SystemType, public OperationsType<Block<SystemType, Operati
7273
// Get the n_elems_per_rod vector (for BlockView construction)
7374
const std::vector<std::size_t>& get_n_elems_per_rod() const { return rod_n_elems_; }
7475

76+
// Get ghost node indices
77+
// Returns indices of ghost nodes between rods (length: n_rods - 1)
78+
// Matches Python implementation: np.cumsum(n_nodes_in_rods[:-1]) + np.arange(n_rods - 1)
79+
std::vector<std::size_t> ghost_nodes_idx() const {
80+
std::vector<std::size_t> indices;
81+
if (rod_n_elems_.size() < 2) {
82+
return indices; // No ghost nodes if less than 2 rods
83+
}
84+
85+
indices.reserve(rod_n_elems_.size() - 1);
86+
std::size_t cumulative_nodes = 0;
87+
for (std::size_t i = 0; i < rod_n_elems_.size() - 1; ++i) {
88+
cumulative_nodes += rod_n_elems_[i] + 1; // n_elems + 1 = n_nodes
89+
indices.push_back(cumulative_nodes + i); // Add i to account for previous ghost nodes
90+
}
91+
return indices;
92+
}
93+
94+
// Get ghost element indices
95+
// Returns indices of ghost elements between rods (length: 2 * (n_rods - 1))
96+
std::vector<std::size_t> ghost_elems_idx() const {
97+
std::vector<std::size_t> indices;
98+
auto ghost_nodes = ghost_nodes_idx();
99+
if (ghost_nodes.empty()) {
100+
return indices;
101+
}
102+
103+
indices.reserve(2 * ghost_nodes.size());
104+
for (std::size_t i = 0; i < ghost_nodes.size(); ++i) {
105+
indices.push_back(ghost_nodes[i] - 1); // Element before ghost node
106+
indices.push_back(ghost_nodes[i]); // Element at ghost node
107+
}
108+
return indices;
109+
}
110+
111+
// Get ghost voronoi indices
112+
// Returns indices of ghost voronoi nodes between rods (length: 3 * (n_rods - 1))
113+
std::vector<std::size_t> ghost_voronoi_idx() const {
114+
std::vector<std::size_t> indices;
115+
auto ghost_nodes = ghost_nodes_idx();
116+
if (ghost_nodes.empty()) {
117+
return indices;
118+
}
119+
120+
indices.reserve(3 * ghost_nodes.size());
121+
for (std::size_t i = 0; i < ghost_nodes.size(); ++i) {
122+
indices.push_back(ghost_nodes[i] - 2); // Voronoi 2 before ghost node
123+
indices.push_back(ghost_nodes[i] - 1); // Voronoi 1 before ghost node
124+
indices.push_back(ghost_nodes[i]); // Voronoi at ghost node
125+
}
126+
return indices;
127+
}
128+
129+
// Reset ghost values for a specific variable
130+
// Uses VariableTag::ghost_value and appropriate ghost indices based on placement
131+
template<typename VariableTag>
132+
void reset_ghost_for_variable() {
133+
static_assert(tuple_contains_v<VariableTag, system_variables_t<SystemType>>,
134+
"VariableTag is not a valid member of tuple SystemType::Variables");
135+
136+
// Compute row offset for this variable
137+
constexpr std::size_t row_offset = compute_variable_offset<VariableTag, SystemType>();
138+
constexpr std::size_t var_dimension = get_dimension_v<VariableTag>;
139+
140+
// Get appropriate ghost indices based on placement
141+
std::vector<std::size_t> ghost_indices;
142+
if constexpr (std::is_base_of_v<Placement::OnNode, VariableTag>) {
143+
ghost_indices = ghost_nodes_idx();
144+
} else if constexpr (std::is_base_of_v<Placement::OnElement, VariableTag>) {
145+
ghost_indices = ghost_elems_idx();
146+
} else if constexpr (std::is_base_of_v<Placement::OnVoronoi, VariableTag>) {
147+
ghost_indices = ghost_voronoi_idx();
148+
}
149+
150+
// Set ghost values at each ghost index
151+
// Note: ghost indices are in the full width coordinate system
152+
const auto& ghost_val = VariableTag::ghost_value;
153+
for (std::size_t ghost_col : ghost_indices) {
154+
// Access data_ directly using row and column offsets
155+
// ghost_val is a MatrixType (column vector), so we access it as (row, 0)
156+
Eigen::Index data_col = static_cast<Eigen::Index>(ghost_col);
157+
for (std::size_t row = 0; row < var_dimension; ++row) {
158+
Eigen::Index data_row = static_cast<Eigen::Index>(row_offset + row);
159+
data_(data_row, data_col) = ghost_val(static_cast<Eigen::Index>(row), 0);
160+
}
161+
}
162+
}
163+
164+
// Reset ghost values for all variables
165+
// Iterates over all variables and calls reset_ghost_for_variable for each
166+
void reset_ghost() {
167+
reset_ghost_impl<system_variables_t<SystemType>, 0>();
168+
}
169+
75170
// Get a view for a specific variable across all rods
76171
// Returns a view into the variable's data (rows) and adjusted columns based on placement
77172
// - OnNode: full width
@@ -154,6 +249,18 @@ class Block : public SystemType, public OperationsType<Block<SystemType, Operati
154249

155250
}
156251

252+
// Helper to iterate over all variables and reset ghost values
253+
template<typename VariablesTuple, std::size_t Index>
254+
void reset_ghost_impl() {
255+
using CurrentVar = std::tuple_element_t<Index, VariablesTuple>;
256+
reset_ghost_for_variable<CurrentVar>();
257+
258+
// Recurse to next variable if not last
259+
if constexpr (Index + 1 < std::tuple_size_v<VariablesTuple>) {
260+
reset_ghost_impl<VariablesTuple, Index + 1>();
261+
}
262+
}
263+
157264
};
158265

159266

0 commit comments

Comments
 (0)