Skip to content

Commit

Permalink
Merge pull request #3018 from boutproject/move-invert3x3
Browse files Browse the repository at this point in the history
Move `invert3x3` out of general purpose `utils.hxx` header
  • Loading branch information
bendudson authored Nov 26, 2024
2 parents 7032b8c + 5a60a52 commit 97c62ca
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 159 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ set(BOUT_SOURCES
./src/mesh/interpolation/interpolation_z.cxx
./src/mesh/interpolation/lagrange_4pt_xz.cxx
./src/mesh/interpolation/monotonic_hermite_spline_xz.cxx
./src/mesh/invert3x3.hxx
./src/mesh/mesh.cxx
./src/mesh/parallel/fci.cxx
./src/mesh/parallel/fci.hxx
Expand Down
8 changes: 1 addition & 7 deletions externalpackages/PVODE/include/pvode/band.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@

namespace pvode {


/******************************************************************
* *
* Type: BandMat *
Expand Down Expand Up @@ -118,7 +117,6 @@ namespace pvode {
* *
******************************************************************/


typedef struct bandmat_type {
integer size;
integer mu, ml, smu;
Expand All @@ -128,7 +126,6 @@ typedef struct bandmat_type {

/* BandMat accessor macros */


/******************************************************************
* *
* Macro : PVODE_BAND_ELEM *
Expand All @@ -143,7 +140,6 @@ typedef struct bandmat_type {

#define PVODE_BAND_ELEM(A,i,j) ((A->data)[j][i-j+(A->smu)])


/******************************************************************
* *
* Macro : PVODE_BAND_COL *
Expand All @@ -159,7 +155,6 @@ typedef struct bandmat_type {

#define PVODE_BAND_COL(A,j) (((A->data)[j])+(A->smu))


/******************************************************************
* *
* Macro : PVODE_BAND_COL_ELEM *
Expand All @@ -173,8 +168,7 @@ typedef struct bandmat_type {
* *
******************************************************************/

#define PVODE_BAND_COL_ELEM(col_j,i,j) (col_j[i-j])

#define PVODE_BAND_COL_ELEM(col_j, i, j) (col_j[i - j])

/* Functions that use the BandMat representation for a band matrix */

Expand Down
10 changes: 2 additions & 8 deletions externalpackages/PVODE/precon/band.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@

namespace pvode {


/******************************************************************
* *
* Type: BandMat *
Expand Down Expand Up @@ -118,7 +117,6 @@ namespace pvode {
* *
******************************************************************/


typedef struct bandmat_type {
integer size;
integer mu, ml, smu;
Expand All @@ -128,7 +126,6 @@ typedef struct bandmat_type {

/* BandMat accessor macros */


/******************************************************************
* *
* Macro : PVODE_BAND_ELEM *
Expand All @@ -143,7 +140,6 @@ typedef struct bandmat_type {

#define PVODE_BAND_ELEM(A,i,j) ((A->data)[j][i-j+(A->smu)])


/******************************************************************
* *
* Macro : PVODE_BAND_COL *
Expand All @@ -157,8 +153,7 @@ typedef struct bandmat_type {
* *
******************************************************************/

#define PVODE_BAND_COL(A,j) (((A->data)[j])+(A->smu))

#define BAND_COL(A, j) (((A->data)[j]) + (A->smu))

/******************************************************************
* *
Expand All @@ -173,8 +168,7 @@ typedef struct bandmat_type {
* *
******************************************************************/

#define PVODE_BAND_COL_ELEM(col_j,i,j) (col_j[i-j])

#define PVODE_BAND_COL_ELEM(col_j, i, j) (col_j[i - j])

/* Functions that use the BandMat representation for a band matrix */

Expand Down
53 changes: 0 additions & 53 deletions include/bout/utils.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -410,59 +410,6 @@ bool operator==(const Tensor<T>& lhs, const Tensor<T>& rhs) {
return std::equal(lhs.begin(), lhs.end(), rhs.begin());
}

/**************************************************************************
* Matrix routines
**************************************************************************/
/// Explicit inversion of a 3x3 matrix \p a
///
/// The input \p small determines how small the determinant must be for
/// us to throw due to the matrix being singular (ill conditioned);
/// If small is less than zero then instead of throwing we return 1.
/// This is ugly but can be used to support some use cases.
template <typename T>
int invert3x3(Matrix<T>& a, BoutReal small = 1.0e-15) {
TRACE("invert3x3");

// Calculate the first co-factors
T A = a(1, 1) * a(2, 2) - a(1, 2) * a(2, 1);
T B = a(1, 2) * a(2, 0) - a(1, 0) * a(2, 2);
T C = a(1, 0) * a(2, 1) - a(1, 1) * a(2, 0);

// Calculate the determinant
T det = a(0, 0) * A + a(0, 1) * B + a(0, 2) * C;

if (std::abs(det) < std::abs(small)) {
if (small >= 0) {
throw BoutException("Determinant of matrix < {:e} --> Poorly conditioned", small);
} else {
return 1;
}
}

// Calculate the rest of the co-factors
T D = a(0, 2) * a(2, 1) - a(0, 1) * a(2, 2);
T E = a(0, 0) * a(2, 2) - a(0, 2) * a(2, 0);
T F = a(0, 1) * a(2, 0) - a(0, 0) * a(2, 1);
T G = a(0, 1) * a(1, 2) - a(0, 2) * a(1, 1);
T H = a(0, 2) * a(1, 0) - a(0, 0) * a(1, 2);
T I = a(0, 0) * a(1, 1) - a(0, 1) * a(1, 0);

// Now construct the output, overwrites input
T detinv = 1.0 / det;

a(0, 0) = A * detinv;
a(0, 1) = D * detinv;
a(0, 2) = G * detinv;
a(1, 0) = B * detinv;
a(1, 1) = E * detinv;
a(1, 2) = H * detinv;
a(2, 0) = C * detinv;
a(2, 1) = F * detinv;
a(2, 2) = I * detinv;

return 0;
}

/*!
* Get Random number between 0 and 1
*/
Expand Down
14 changes: 8 additions & 6 deletions src/mesh/coordinates.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
#include <bout/derivs.hxx>
#include <bout/fft.hxx>
#include <bout/interpolation.hxx>
#include <bout/output_bout_types.hxx>

#include <bout/globals.hxx>

#include "invert3x3.hxx"
#include "parallel/fci.hxx"
#include "parallel/shiftedmetricinterp.hxx"

Expand Down Expand Up @@ -1241,9 +1243,9 @@ int Coordinates::calcCovariant(const std::string& region) {
a(1, 2) = a(2, 1) = g23[i];
a(0, 2) = a(2, 0) = g13[i];

if (invert3x3(a)) {
output_error.write("\tERROR: metric tensor is singular at ({:d}, {:d})\n", i.x(),
i.y());
if (const auto det = bout::invert3x3(a); det.has_value()) {
output_error.write("\tERROR: metric tensor is singular at {}, determinant: {:d}\n",
i, det.value());
return 1;
}

Expand Down Expand Up @@ -1297,9 +1299,9 @@ int Coordinates::calcContravariant(const std::string& region) {
a(1, 2) = a(2, 1) = g_23[i];
a(0, 2) = a(2, 0) = g_13[i];

if (invert3x3(a)) {
output_error.write("\tERROR: metric tensor is singular at ({:d}, {:d})\n", i.x(),
i.y());
if (const auto det = bout::invert3x3(a); det.has_value()) {
output_error.write("\tERROR: metric tensor is singular at {}, determinant: {:d}\n",
i, det.value());
return 1;
}

Expand Down
77 changes: 77 additions & 0 deletions src/mesh/invert3x3.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*!*************************************************************************
* \file invert3x3.hxx
*
* A mix of short utilities for memory management, strings, and some
* simple but common calculations
*
**************************************************************************
* Copyright 2010-2024 B.D.Dudson, BOUT++ Team
*
* Contact: Ben Dudson, [email protected]
*
* This file is part of BOUT++.
*
* BOUT++ 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 3 of the License, or
* (at your option) any later version.
*
* BOUT++ 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 BOUT++. If not, see <http://www.gnu.org/licenses/>.
*
**************************************************************************/

#pragma once

#include <bout/utils.hxx>
#include <optional>

/// Explicit inversion of a 3x3 matrix \p a
///
/// If the matrix is singular (ill conditioned), the determinant is
/// return. Otherwise, an empty `std::optional` is return
namespace bout {
inline std::optional<BoutReal> invert3x3(Matrix<BoutReal>& a) {
TRACE("invert3x3");

// Calculate the first co-factors
const BoutReal A = a(1, 1) * a(2, 2) - a(1, 2) * a(2, 1);
const BoutReal B = a(1, 2) * a(2, 0) - a(1, 0) * a(2, 2);
const BoutReal C = a(1, 0) * a(2, 1) - a(1, 1) * a(2, 0);

// Calculate the determinant
const BoutReal det = a(0, 0) * A + a(0, 1) * B + a(0, 2) * C;
constexpr BoutReal small = 1.0e-15;
if (std::abs(det) < std::abs(small)) {
return det;
}

// Calculate the rest of the co-factors
const BoutReal D = a(0, 2) * a(2, 1) - a(0, 1) * a(2, 2);
const BoutReal E = a(0, 0) * a(2, 2) - a(0, 2) * a(2, 0);
const BoutReal F = a(0, 1) * a(2, 0) - a(0, 0) * a(2, 1);
const BoutReal G = a(0, 1) * a(1, 2) - a(0, 2) * a(1, 1);
const BoutReal H = a(0, 2) * a(1, 0) - a(0, 0) * a(1, 2);
const BoutReal I = a(0, 0) * a(1, 1) - a(0, 1) * a(1, 0);

// Now construct the output, overwrites input
const BoutReal detinv = 1.0 / det;

a(0, 0) = A * detinv;
a(0, 1) = D * detinv;
a(0, 2) = G * detinv;
a(1, 0) = B * detinv;
a(1, 1) = E * detinv;
a(1, 2) = H * detinv;
a(2, 0) = C * detinv;
a(2, 1) = F * detinv;
a(2, 2) = I * detinv;

return std::nullopt;
}
} // namespace bout
1 change: 1 addition & 0 deletions tests/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ set(serial_tests_source
./mesh/test_coordinates.cxx
./mesh/test_coordinates_accessor.cxx
./mesh/test_interpolation.cxx
./mesh/test_invert3x3.cxx
./mesh/test_mesh.cxx
./mesh/test_paralleltransform.cxx
./solver/test_fakesolver.cxx
Expand Down
74 changes: 74 additions & 0 deletions tests/unit/mesh/test_invert3x3.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include "../../src/mesh/invert3x3.hxx"

#include "gtest/gtest.h"

TEST(Invert3x3Test, Identity) {
Matrix<BoutReal> input(3, 3);
input = 0;
for (int i = 0; i < 3; i++) {
input(i, i) = 1.0;
}
auto expected = input;
bout::invert3x3(input);

for (int j = 0; j < 3; j++) {
for (int i = 0; i < 3; i++) {
EXPECT_EQ(input(i, j), expected(i, j));
}
}
}

TEST(Invert3x3Test, InvertTwice) {
std::vector<BoutReal> rawDataMat = {0.05567105, 0.92458227, 0.19954631,
0.28581972, 0.54009039, 0.13234403,
0.8841194, 0.161224, 0.74853209};
std::vector<BoutReal> rawDataInv = {-2.48021781, 4.27410022, -0.09449605,
0.6278449, 0.87275842, -0.32168092,
2.79424897, -5.23628123, 1.51684677};

Matrix<BoutReal> input(3, 3);
Matrix<BoutReal> expected(3, 3);

int counter = 0;
for (int j = 0; j < 3; j++) {
for (int i = 0; i < 3; i++) {
input(i, j) = rawDataMat[counter];
expected(i, j) = rawDataInv[counter];
counter++;
}
}

// Invert twice to check if we get back to where we started
bout::invert3x3(input);

for (int j = 0; j < 3; j++) {
for (int i = 0; i < 3; i++) {
// Note we only check to single tolerance here
EXPECT_FLOAT_EQ(input(i, j), expected(i, j));
}
}
}

TEST(Invert3x3Test, Singular) {
Matrix<BoutReal> input(3, 3);
input = 0;
auto result = bout::invert3x3(input);
EXPECT_TRUE(result.has_value());
}

TEST(Invert3x3Test, BadCondition) {
Matrix<BoutReal> input(3, 3);

input = 0.;
input(0, 0) = 1.0e-16;
input(1, 1) = 1.0;
input(2, 2) = 1.0;
EXPECT_TRUE(bout::invert3x3(input).has_value());

// not quite bad enough condition
input = 0.;
input(0, 0) = 1.0e-12;
input(1, 1) = 1.0;
input(2, 2) = 1.0;
EXPECT_FALSE(bout::invert3x3(input).has_value());
}
Loading

0 comments on commit 97c62ca

Please sign in to comment.