Skip to content
1 change: 1 addition & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ JonathanSmith1936
Joseph Signorelli
Joshua A. Kelly
Josy P. Pullockara
Kajetan R. Gulaj
Kedar Naik
Kürşat Yurt
LaSerpe
Expand Down
7 changes: 4 additions & 3 deletions Common/include/containers/CPyWrapperMatrixView.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class CPyWrapperMatrixView {

/*--- Define the functions required by the interface macro. ---*/
inline const su2double& Access(unsigned long row, unsigned long col) const {
if (row > rows_ || col > cols_) SU2_MPI::Error(name_ + " out of bounds", "CPyWrapperMatrixView");
if (row >= rows_ || col >= cols_) SU2_MPI::Error(name_ + " out of bounds", "CPyWrapperMatrixView");
return data_[row * cols_ + col];
}
inline su2double& Access(unsigned long row, unsigned long col) {
Expand Down Expand Up @@ -133,7 +133,7 @@ class CPyWrapperMarkerMatrixView {

/*--- Define the functions required by the interface macro. ---*/
inline const su2double& Access(unsigned long row, unsigned long col) const {
if (row > rows_ || col > cols_) SU2_MPI::Error(name_ + " out of bounds", "CPyWrapperMarkerMatrixView");
if (row >= rows_ || col >= cols_) SU2_MPI::Error(name_ + " out of bounds", "CPyWrapperMarkerMatrixView");
return data_[vertices_[row]->GetNode() * cols_ + col];
}
inline su2double& Access(unsigned long row, unsigned long col) {
Expand Down Expand Up @@ -183,7 +183,8 @@ class CPyWrapper3DMatrixView {

/*--- Define the functions required by the interface macro. ---*/
inline const su2double& Access(unsigned long row, unsigned long col, unsigned long dim) const {
if (row > rows_ || col > cols_ || dim > dims_) SU2_MPI::Error(name_ + " out of bounds", "CPyWrapper3DMatrixView");
if (row >= rows_ || col >= cols_ || dim >= dims_)
SU2_MPI::Error(name_ + " out of bounds", "CPyWrapper3DMatrixView");
return data_[row * (cols_ * dims_) + col * dims_ + dim];
}
inline su2double& Access(unsigned long row, unsigned long col, unsigned long dim) {
Expand Down
36 changes: 36 additions & 0 deletions TestCases/py_wrapper/matrix_view_bounds/heat_rectangle.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SU2 configuration file %
% Case description: Bounds checks of the Python wrapper matrix views %
% File Version 8.5.0 "Harrier" %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

SOLVER= HEAT_EQUATION
INC_NONDIM= DIMENSIONAL

FREESTREAM_TEMPERATURE= 300.0
MATERIAL_DENSITY= 1000.0
SPECIFIC_HEAT_CP= 500.0
THERMAL_CONDUCTIVITY_CONSTANT= 10.0

MARKER_ISOTHERMAL= ( x_minus, 310.0, x_plus, 300.0 )
MARKER_HEATFLUX= ( y_minus, 0.0, y_plus, 0.0 )
MARKER_MONITORING= ( x_minus )

NUM_METHOD_GRAD= GREEN_GAUSS
CFL_NUMBER= 10.0
CFL_ADAPT= NO

LINEAR_SOLVER= FGMRES
LINEAR_SOLVER_PREC= ILU
LINEAR_SOLVER_ERROR= 1E-6
LINEAR_SOLVER_ITER= 10

MESH_FORMAT= RECTANGLE
MESH_BOX_SIZE= ( 9, 5, 0 )
MESH_BOX_LENGTH= ( 0.1, 0.05, 0 )

OUTPUT_FILES= NONE
SCREEN_OUTPUT= INNER_ITER, RMS_RES, MAX_RES, TOTAL_HEATFLUX

ITER= 11
CONV_RESIDUAL_MINVAL= -12
90 changes: 90 additions & 0 deletions TestCases/py_wrapper/matrix_view_bounds/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env python

## \file run.py
# \brief Bounds checks of the Python wrapper matrix views.
# \version 8.5.0 "Harrier"
#
# SU2 Project Website: https://su2code.github.io
#
# The SU2 Project is maintained by the SU2 Foundation
# (http://su2foundation.org)
#
# Copyright 2012-2026, SU2 Contributors (cf. AUTHORS.md)
#
# SU2 is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# SU2 is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with SU2. If not, see <http://www.gnu.org/licenses/>.

import subprocess
import sys
import pysu2

def Views(driver):
"""
One view of each class: CPyWrapperMatrixView, CPyWrapperMarkerMatrixView, CPyWrapper3DMatrixView.
"""
iSolver = driver.GetSolverIndices()['HEAT']
iMarker = driver.GetMarkerIndices()['x_plus']
return {'Solution': driver.Solution(iSolver),
'MarkerSolution': driver.MarkerSolution(iSolver, iMarker),
'Gradient': driver.Gradient(iSolver)}

def main():
"""
Run the heat solver, then check that the views accept the last valid index and
stop with an error at the first index past the end, in every dimension.
Usage: run.py <cfg> [<view> <dimension>], the second form is the child process.
"""
cfg = sys.argv[1]

if len(sys.argv) == 4:
# Child process: access the view with the first index past the end of one dimension.
driver = pysu2.CSinglezoneDriver(cfg, 1, 0)
view = Views(driver)[sys.argv[2]]
iDim = int(sys.argv[3])
index = [0] * len(view.Shape())
index[iDim] = view.Shape()[iDim]
print(f'{sys.argv[2]}{tuple(index)} returned {view(*index)}')
return

# SU2_MPI::Error ends the process, so each access past the end runs in a child process,
# which must stop with the "out of bounds" error.
failed = []
for name, nDim in (('Solution', 2), ('MarkerSolution', 2), ('Gradient', 3)):
for iDim in range(nDim):
child = subprocess.run([sys.executable, __file__, cfg, name, str(iDim)],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
if child.returncode == 0 or 'out of bounds' not in child.stdout:
returned = [line for line in child.stdout.splitlines() if ' returned ' in line]
failed.append(f'{name}, index past the end of dimension {iDim}: exit code {child.returncode}, '
f'no out of bounds error. {" ".join(returned)}')

driver = pysu2.CSinglezoneDriver(cfg, 1, 0)
driver.StartSolver()

# The last valid index of each view returns the value set through it.
for name, view in Views(driver).items():
index = [n - 1 for n in view.Shape()]
value = view(*index) + 1.0
view.Set(*index, value)
if view(*index) != value:
failed.append(f'{name}{tuple(index)} returned {view(*index)} instead of {value}')

driver.Finalize()

for message in failed:
print(f'FAILED: {message}')
if failed:
sys.exit(1)

if __name__ == '__main__':
main()
13 changes: 13 additions & 0 deletions TestCases/serial_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -1768,6 +1768,19 @@ def main():
test_list.append(pywrapper_custom_inlet)
pass_list.append(pywrapper_custom_inlet.run_test(args.tsan, args.asan))

# Bounds checks of the matrix views
pywrapper_matrix_view_bounds = TestCase('pywrapper_matrix_view_bounds')
pywrapper_matrix_view_bounds.cfg_dir = "py_wrapper/matrix_view_bounds"
pywrapper_matrix_view_bounds.cfg_file = "heat_rectangle.cfg"
pywrapper_matrix_view_bounds.test_iter = 10
pywrapper_matrix_view_bounds.test_vals = [-5.122599, -4.820574, 127.760000]
pywrapper_matrix_view_bounds.command = TestCase.Command(exec = "python", param = "run.py")
pywrapper_matrix_view_bounds.timeout = 1600
pywrapper_matrix_view_bounds.tol = 0.00001
pywrapper_matrix_view_bounds.enabled_with_asan = False
test_list.append(pywrapper_matrix_view_bounds)
pass_list.append(pywrapper_matrix_view_bounds.run_test(args.tsan, args.asan))

# Tests summary
print('==================================================================')
print('Summary of the serial tests')
Expand Down