From ef172764dabb49c69e143d4ddfa173a10a143dca Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Mon, 11 Jan 2021 17:03:07 +0000 Subject: [PATCH 1/5] Add unit test for LaplaceXZPetsc --- tests/integrated/CMakeLists.txt | 1 + tests/unit/CMakeLists.txt | 1 + .../invert/laplacexz/test_laplacexz_petsc.cxx | 161 ++++++++++++++++++ 3 files changed, 163 insertions(+) create mode 100644 tests/unit/invert/laplacexz/test_laplacexz_petsc.cxx diff --git a/tests/integrated/CMakeLists.txt b/tests/integrated/CMakeLists.txt index bac6fb0507..aa3f2b2c3e 100644 --- a/tests/integrated/CMakeLists.txt +++ b/tests/integrated/CMakeLists.txt @@ -23,6 +23,7 @@ add_subdirectory(test-laplace) add_subdirectory(test-laplace-petsc3d) add_subdirectory(test-laplacexy-fv) add_subdirectory(test-laplacexy-short) +add_subdirectory(test-laplacexz) add_subdirectory(test-multigrid_laplace) add_subdirectory(test-naulin-laplace) add_subdirectory(test-options-netcdf) diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 8acedea32c..108820250f 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -43,6 +43,7 @@ add_executable(serial_tests ./include/test_mask.cxx ./invert/test_fft.cxx ./invert/laplace/test_laplace_petsc3damg.cxx + ./invert/laplacexz/test_laplacexz_petsc.cxx ./mesh/data/test_gridfromoptions.cxx ./mesh/parallel/test_shiftedmetric.cxx ./mesh/test_boundary_factory.cxx diff --git a/tests/unit/invert/laplacexz/test_laplacexz_petsc.cxx b/tests/unit/invert/laplacexz/test_laplacexz_petsc.cxx new file mode 100644 index 0000000000..c3d5b086b3 --- /dev/null +++ b/tests/unit/invert/laplacexz/test_laplacexz_petsc.cxx @@ -0,0 +1,161 @@ +#include "bout/build_config.hxx" + +#include +#include + +#include "gtest/gtest.h" +#include "test_extras.hxx" +#include "invert_laplace.hxx" +#include "../../../../src/invert/laplacexz/impls/petsc/laplacexz-petsc.hxx" + +#include "bout/mesh.hxx" +#include "bout/griddata.hxx" +#include "options.hxx" +#include "field2d.hxx" +#include "field3d.hxx" +#include "derivs.hxx" +#include "difops.hxx" +#include "vecops.hxx" +#include "bout/petsc_interface.hxx" + +#if BOUT_HAS_PETSC + +/// Global mesh +namespace bout{ +namespace globals{ +extern Mesh *mesh; +} // namespace globals +} // namespace bout + +// The unit tests use the global mesh +using namespace bout::globals; + +class ForwardOperatorXZ { +public: + ForwardOperatorXZ() {} + ForwardOperatorXZ(Mesh* mesh, bool xin_neumann, bool xout_neumann) + : A(1.0, mesh), B(0.0, mesh), coords(mesh->getCoordinates(CELL_CENTER)), + inner_x_neumann(xin_neumann), outer_x_neumann(xout_neumann) {} + + Field3D operator()(Field3D& f) const { + const auto AJ = A * coords->J; + const auto xx_coef = AJ * coords->g11; + const auto zz_coef = AJ * coords->g33; + const auto xz_coef = AJ * coords->g13; + const auto ddx_f = DDX(f); + const auto ddz_f = DDZ(f); + + const auto xx = (DDX(xx_coef) * ddx_f) + (xx_coef * D2DX2(f)); + const auto zz = (DDZ(zz_coef) * ddz_f) + (zz_coef * D2DZ2(f)); + const auto xz = (DDX(xz_coef) * ddz_f) + (xz_coef * D2DXDZ(f)); + const auto zx = (DDZ(xz_coef) * ddx_f) + (xz_coef * D2DXDZ(f)); + + auto result = ((xx + zz + xz + zx) / coords->J) + (B * f); + applyBoundaries(result, f); + return result; + } + + Field3D A, B; + Coordinates* coords; + +private: + bool inner_x_neumann, outer_x_neumann; // If false then use Dirichlet conditions + // lower_y_neumann, upper_y_neumann; + + void applyBoundaries(Field3D& newF, Field3D& f) const { + BOUT_FOR(i, f.getMesh()->getRegion3D("RGN_INNER_X")) { + if (inner_x_neumann) { + newF[i] = (f[i.xp()] - f[i]) / coords->dx[i] / sqrt(coords->g_11[i]); + } else { + newF[i] = 0.5 * (f[i] + f[i.xp()]); + } + } + + BOUT_FOR(i, f.getMesh()->getRegion3D("RGN_OUTER_X")) { + if (outer_x_neumann) { + newF[i] = (f[i] - f[i.xm()]) / coords->dx[i] / sqrt(coords->g_11[i]); + } else { + newF[i] = 0.5 * (f[i.xm()] + f[i]); + } + } + } +}; + +class LaplaceXZPetscTest : public FakeMeshFixture, + public testing::WithParamInterface> { +public: + WithQuietOutput info{output_info}, warn{output_warn}, progress{output_progress}, all{output}; + LaplaceXZPetscTest() + : FakeMeshFixture(), solver(bout::globals::mesh, getOptions(GetParam())), + forward(bout::globals::mesh, std::get<0>(GetParam()), std::get<1>(GetParam())) { + PetscErrorPrintf = PetscErrorPrintfNone; + const BoutReal nx = mesh->GlobalNx; + const BoutReal ny = mesh->GlobalNy; + const BoutReal nz = mesh->GlobalNz; + static_cast(bout::globals::mesh) + ->setGridDataSource(new GridFromOptions(Options::getRoot())); + + auto* coords = bout::globals::mesh->getCoordinates(); + + coords->geometry(); + f3.allocate(); + A.allocate(); + B.allocate(); + + BOUT_FOR(i, mesh->getRegion3D("RGN_ALL")) { + const BoutReal x = i.x() / nx - 0.5; + const BoutReal y = i.y() / ny - 0.5; + const BoutReal z = i.z() / nz - 0.5; + f3[i] = 1e3 * exp(-0.5 * sqrt(x * x + y * y + z * z) / sigmasq); + A[i] = x + y + sin(2 * 3.14159265358979323846 * z); + B[i] = 1.0; + } + } + + ~LaplaceXZPetscTest() { + Options::cleanup(); + PetscErrorPrintf = PetscErrorPrintfDefault; + } + + LaplaceXZpetsc solver; + Field3D f3, A, B; + static constexpr BoutReal sigmasq = 0.02; + static constexpr BoutReal tol = 1e-8; + ForwardOperatorXZ forward; + +private: + + static Options* getOptions(std::tuple param) { + Options *options = Options::getRoot()->getSection("laplacexz"); + (*options)["type"] = "petsc"; + (*options)["inner_boundary_flags"] = (std::get<0>(param) ? INVERT_AC_GRAD : 0) + INVERT_RHS; + (*options)["outer_boundary_flags"] = (std::get<1>(param) ? INVERT_AC_GRAD : 0) + INVERT_RHS; + (*options)["fourth_order"] = false; + (*options)["atol"] = tol/30; // Need to specify smaller than desired tolerance to + (*options)["rtol"] = tol/30; // ensure it is satisfied for every element. + return options; + } +}; + +INSTANTIATE_TEST_SUITE_P(LaplaceXZTest, LaplaceXZPetscTest, + testing::Values(std::make_tuple(true, false))); + +TEST_P(LaplaceXZPetscTest, TestSolve3D){ + Field3D expected = f3; + solver.setCoefs(A, B); + forward.A = A; + forward.B = B; + const Field3D actual = solver.solve(forward(f3), 0.0); + EXPECT_TRUE(IsFieldEqual(actual, expected, "RGN_NOBNDRY", tol)); +} + +TEST_P(LaplaceXZPetscTest, TestSolve3DGuess){ + Field3D expected = f3, guess = f3*1.01; + solver.setCoefs(A, B); + forward.A = A; + forward.B = B; + const Field3D actual = solver.solve(forward(f3), guess); + EXPECT_TRUE(IsFieldEqual(actual, expected, "RGN_NOBNDRY", tol)); +} + +#endif // BOUT_HAS_PETSC From b22cbb38aff446aff08011374a19643971936f7b Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Wed, 3 Mar 2021 09:23:54 +0000 Subject: [PATCH 2/5] Add CMake file for integrated LaplaceXZ test --- tests/integrated/test-laplacexz/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/integrated/test-laplacexz/CMakeLists.txt diff --git a/tests/integrated/test-laplacexz/CMakeLists.txt b/tests/integrated/test-laplacexz/CMakeLists.txt new file mode 100644 index 0000000000..724fee0e7b --- /dev/null +++ b/tests/integrated/test-laplacexz/CMakeLists.txt @@ -0,0 +1,6 @@ +bout_add_integrated_test(test-laplacexz + SOURCES test-laplacexz.cxx + REQUIRES BOUT_HAS_PETSC + CONFLICTS BOUT_USE_METRIC_3D + USE_RUNTEST + USE_DATA_BOUT_INP) From 65e88e66704c76bd7bc4363df430dfefd0f97e7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Schw=C3=B6rer?= Date: Tue, 29 Jun 2021 13:36:53 +0200 Subject: [PATCH 3/5] Fix test laplacexz for next --- tests/integrated/test-laplacexz/test-laplacexz.cxx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integrated/test-laplacexz/test-laplacexz.cxx b/tests/integrated/test-laplacexz/test-laplacexz.cxx index 85da15cb95..5d55046378 100644 --- a/tests/integrated/test-laplacexz/test-laplacexz.cxx +++ b/tests/integrated/test-laplacexz/test-laplacexz.cxx @@ -13,6 +13,8 @@ #include #include +using bout::globals::mesh; + int main(int argc, char** argv) { BoutInitialise(argc, argv); @@ -37,7 +39,7 @@ int main(int argc, char** argv) { Field3D x2 = inv->solve(rhs2, 0.0); SAVE_ONCE2(rhs2, x2); - dump.write(); + bout::globals::dump.write(); BoutFinalise(); return 0; From 41cd3fe9271888c423a94faa58ab0abd62441746 Mon Sep 17 00:00:00 2001 From: dschwoerer Date: Thu, 1 Jul 2021 09:13:20 +0000 Subject: [PATCH 4/5] [skip ci] Apply black changes --- src/field/gen_fieldops.py | 172 +++++++++++++++++++++++--------------- 1 file changed, 106 insertions(+), 66 deletions(-) diff --git a/src/field/gen_fieldops.py b/src/field/gen_fieldops.py index ea9329ea01..646580559f 100755 --- a/src/field/gen_fieldops.py +++ b/src/field/gen_fieldops.py @@ -26,19 +26,21 @@ try: import jinja2 except ImportError: - raise ImportError('Missing Python module "jinja2". See "Field2D/Field3D Arithmetic ' - 'Operators" in the BOUT++ user manual for more information') + raise ImportError( + 'Missing Python module "jinja2". See "Field2D/Field3D Arithmetic ' + 'Operators" in the BOUT++ user manual for more information' + ) # This allows us to open either a file or stdout with the same code @contextlib.contextmanager -def smart_open(filename, mode='r'): +def smart_open(filename, mode="r"): """Open stdin or stdout using a contextmanager From: http://stackoverflow.com/a/29824059/2043465 """ - if filename == '-': - if mode is None or mode == '' or 'r' in mode: + if filename == "-": + if mode is None or mode == "" or "r" in mode: fh = sys.stdin else: fh = sys.stdout @@ -47,18 +49,20 @@ def smart_open(filename, mode='r'): try: yield fh finally: - if filename != '-': + if filename != "-": fh.close() # The arthimetic operators # OrderedDict to (try to) ensure consistency between python 2 & 3 -operators = OrderedDict([ - ('*', 'multiplication'), - ('/', 'division'), - ('+', 'addition'), - ('-', 'subtraction'), -]) +operators = OrderedDict( + [ + ("*", "multiplication"), + ("/", "division"), + ("+", "addition"), + ("-", "subtraction"), + ] +) header = """// This file is autogenerated - see gen_fieldops.py #include @@ -78,8 +82,15 @@ class Field(object): """ - def __init__(self, field_type, dimensions, name=None, index_var=None, - jz_var='jz', mixed_base_ind_var='base_ind'): + def __init__( + self, + field_type, + dimensions, + name=None, + index_var=None, + jz_var="jz", + mixed_base_ind_var="base_ind", + ): # C++ type of the field, e.g. Field3D self.field_type = field_type # array: dimensions of the field @@ -91,14 +102,14 @@ def __init__(self, field_type, dimensions, name=None, index_var=None, # Name of jz variable self.jz_var = jz_var self.mixed_base_ind_var = mixed_base_ind_var - #Note region_type isn't actually used currently but - #may be useful in future. + # Note region_type isn't actually used currently but + # may be useful in future. if self.field_type == "Field3D": - self.region_type="3D" + self.region_type = "3D" elif self.field_type == "Field2D": - self.region_type="2D" + self.region_type = "2D" else: - self.region_type="_INVALID_" + self.region_type = "_INVALID_" @property def passByReference(self): @@ -107,7 +118,8 @@ def passByReference(self): """ return "{self.field_type}{ref} {self.name}".format( - self=self, ref="&" if self.field_type != "BoutReal" else "") + self=self, ref="&" if self.field_type != "BoutReal" else "" + ) @property def index(self): @@ -129,7 +141,9 @@ def mixed_index(self): if self.field_type == "BoutReal": return "{self.name}".format(self=self) elif self.field_type == "Field3D": - return "{self.name}[{self.mixed_base_ind_var} + {self.jz_var}]".format(self=self) + return "{self.name}[{self.mixed_base_ind_var} + {self.jz_var}]".format( + self=self + ) else: # Field2D return "{self.name}[{self.index_var}]".format(self=self) @@ -161,94 +175,120 @@ def __str__(self): def returnType(f1, f2): - """Determine a suitable return type, by seeing which field is 'larger'. - - """ + """Determine a suitable return type, by seeing which field is 'larger'.""" if f1 == f2: return copy(f1) - elif f1 == 'BoutReal': + elif f1 == "BoutReal": return copy(f2) - elif f2 == 'BoutReal': + elif f2 == "BoutReal": return copy(f1) - elif f1 == 'FieldPerp' or f2 == 'FieldPerp': + elif f1 == "FieldPerp" or f2 == "FieldPerp": return copy(fieldPerp) else: return copy(field3D) + if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Generate code for the Field arithmetic operators") + parser = argparse.ArgumentParser( + description="Generate code for the Field arithmetic operators" + ) # By default write to stdout - parser.add_argument("--filename", default="-", - help="Write output to FILENAME instead of stdout") + parser.add_argument( + "--filename", default="-", help="Write output to FILENAME instead of stdout" + ) # By default use OpenMP enabled loops but allow to disable - parser.add_argument("--no-openmp", action="store_false", default=False, dest = "noOpenMP", - help="Don't use OpenMP compatible loops") + parser.add_argument( + "--no-openmp", + action="store_false", + default=False, + dest="noOpenMP", + help="Don't use OpenMP compatible loops", + ) args = parser.parse_args() - #Setup - index_var = 'index' - jz_var = 'jz' + # Setup + index_var = "index" + jz_var = "jz" mixed_base_ind_var = "base_ind" region_name = '"RGN_ALL"' - + if args.noOpenMP: - region_loop = 'BOUT_FOR_SERIAL' + region_loop = "BOUT_FOR_SERIAL" else: - region_loop = 'BOUT_FOR' - + region_loop = "BOUT_FOR" + # Declare what fields we currently support: # Field perp is currently missing - field3D = Field('Field3D', ['x', 'y', 'z'], index_var=index_var, - jz_var = jz_var, mixed_base_ind_var = mixed_base_ind_var) - field2D = Field('Field2D', ['x', 'y'], index_var=index_var, - jz_var = jz_var, mixed_base_ind_var = mixed_base_ind_var) - fieldPerp = Field('FieldPerp', ['x', 'z'], index_var=index_var, - jz_var = jz_var, mixed_base_ind_var = mixed_base_ind_var) - boutreal = Field('BoutReal', [], index_var=index_var, - jz_var = jz_var, mixed_base_ind_var = mixed_base_ind_var) - + field3D = Field( + "Field3D", + ["x", "y", "z"], + index_var=index_var, + jz_var=jz_var, + mixed_base_ind_var=mixed_base_ind_var, + ) + field2D = Field( + "Field2D", + ["x", "y"], + index_var=index_var, + jz_var=jz_var, + mixed_base_ind_var=mixed_base_ind_var, + ) + fieldPerp = Field( + "FieldPerp", + ["x", "z"], + index_var=index_var, + jz_var=jz_var, + mixed_base_ind_var=mixed_base_ind_var, + ) + boutreal = Field( + "BoutReal", + [], + index_var=index_var, + jz_var=jz_var, + mixed_base_ind_var=mixed_base_ind_var, + ) + fields = [field3D, field2D, fieldPerp, boutreal] with smart_open(args.filename, "w") as f: f.write(header) f.write("\n") - env = jinja2.Environment(loader=jinja2.FileSystemLoader('.'), - trim_blocks=True) + env = jinja2.Environment(loader=jinja2.FileSystemLoader("."), trim_blocks=True) template = env.get_template("gen_fieldops.jinja") for lhs, rhs in itertools.product(fields, fields): # We don't have to define BoutReal BoutReal operations - if lhs == rhs == 'BoutReal': + if lhs == rhs == "BoutReal": continue rhs = copy(rhs) lhs = copy(lhs) # The output of the operation. The `larger` of the two fields. - out = returnType(rhs, lhs) - out.name = 'result' - lhs.name = 'lhs' - rhs.name = 'rhs' - + out = returnType(rhs, lhs) + out.name = "result" + lhs.name = "lhs" + rhs.name = "rhs" + for operator, operator_name in operators.items(): template_args = { - 'operator': operator, - 'operator_name': operator_name, + "operator": operator, + "operator_name": operator_name, # - 'out': out, - 'lhs': lhs, - 'rhs': rhs, + "out": out, + "lhs": lhs, + "rhs": rhs, # - 'region_loop': region_loop, - 'region_name': region_name, + "region_loop": region_loop, + "region_name": region_name, # - 'index_var': index_var, - 'mixed_base_ind': mixed_base_ind_var, - 'jz_var': jz_var, + "index_var": index_var, + "mixed_base_ind": mixed_base_ind_var, + "jz_var": jz_var, } with smart_open(args.filename, "a") as f: From c42b2d315071e6a861174582d654fd3b6aa1da0f Mon Sep 17 00:00:00 2001 From: dschwoerer Date: Wed, 16 Nov 2022 13:03:44 +0000 Subject: [PATCH 5/5] Apply clang-format changes --- .../invert/laplacexz/test_laplacexz_petsc.cxx | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/tests/unit/invert/laplacexz/test_laplacexz_petsc.cxx b/tests/unit/invert/laplacexz/test_laplacexz_petsc.cxx index c3d5b086b3..dcb04606a1 100644 --- a/tests/unit/invert/laplacexz/test_laplacexz_petsc.cxx +++ b/tests/unit/invert/laplacexz/test_laplacexz_petsc.cxx @@ -3,27 +3,27 @@ #include #include -#include "gtest/gtest.h" -#include "test_extras.hxx" -#include "invert_laplace.hxx" #include "../../../../src/invert/laplacexz/impls/petsc/laplacexz-petsc.hxx" +#include "invert_laplace.hxx" +#include "test_extras.hxx" +#include "gtest/gtest.h" -#include "bout/mesh.hxx" -#include "bout/griddata.hxx" -#include "options.hxx" -#include "field2d.hxx" -#include "field3d.hxx" #include "derivs.hxx" #include "difops.hxx" +#include "field2d.hxx" +#include "field3d.hxx" +#include "options.hxx" #include "vecops.hxx" +#include "bout/griddata.hxx" +#include "bout/mesh.hxx" #include "bout/petsc_interface.hxx" #if BOUT_HAS_PETSC /// Global mesh -namespace bout{ -namespace globals{ -extern Mesh *mesh; +namespace bout { +namespace globals { +extern Mesh* mesh; } // namespace globals } // namespace bout @@ -34,7 +34,7 @@ class ForwardOperatorXZ { public: ForwardOperatorXZ() {} ForwardOperatorXZ(Mesh* mesh, bool xin_neumann, bool xout_neumann) - : A(1.0, mesh), B(0.0, mesh), coords(mesh->getCoordinates(CELL_CENTER)), + : A(1.0, mesh), B(0.0, mesh), coords(mesh->getCoordinates(CELL_CENTER)), inner_x_neumann(xin_neumann), outer_x_neumann(xout_neumann) {} Field3D operator()(Field3D& f) const { @@ -59,8 +59,8 @@ class ForwardOperatorXZ { Coordinates* coords; private: - bool inner_x_neumann, outer_x_neumann; // If false then use Dirichlet conditions - // lower_y_neumann, upper_y_neumann; + bool inner_x_neumann, outer_x_neumann; // If false then use Dirichlet conditions + // lower_y_neumann, upper_y_neumann; void applyBoundaries(Field3D& newF, Field3D& f) const { BOUT_FOR(i, f.getMesh()->getRegion3D("RGN_INNER_X")) { @@ -84,7 +84,8 @@ class ForwardOperatorXZ { class LaplaceXZPetscTest : public FakeMeshFixture, public testing::WithParamInterface> { public: - WithQuietOutput info{output_info}, warn{output_warn}, progress{output_progress}, all{output}; + WithQuietOutput info{output_info}, warn{output_warn}, progress{output_progress}, + all{output}; LaplaceXZPetscTest() : FakeMeshFixture(), solver(bout::globals::mesh, getOptions(GetParam())), forward(bout::globals::mesh, std::get<0>(GetParam()), std::get<1>(GetParam())) { @@ -124,15 +125,16 @@ class LaplaceXZPetscTest : public FakeMeshFixture, ForwardOperatorXZ forward; private: - static Options* getOptions(std::tuple param) { - Options *options = Options::getRoot()->getSection("laplacexz"); + Options* options = Options::getRoot()->getSection("laplacexz"); (*options)["type"] = "petsc"; - (*options)["inner_boundary_flags"] = (std::get<0>(param) ? INVERT_AC_GRAD : 0) + INVERT_RHS; - (*options)["outer_boundary_flags"] = (std::get<1>(param) ? INVERT_AC_GRAD : 0) + INVERT_RHS; + (*options)["inner_boundary_flags"] = + (std::get<0>(param) ? INVERT_AC_GRAD : 0) + INVERT_RHS; + (*options)["outer_boundary_flags"] = + (std::get<1>(param) ? INVERT_AC_GRAD : 0) + INVERT_RHS; (*options)["fourth_order"] = false; - (*options)["atol"] = tol/30; // Need to specify smaller than desired tolerance to - (*options)["rtol"] = tol/30; // ensure it is satisfied for every element. + (*options)["atol"] = tol / 30; // Need to specify smaller than desired tolerance to + (*options)["rtol"] = tol / 30; // ensure it is satisfied for every element. return options; } }; @@ -140,7 +142,7 @@ class LaplaceXZPetscTest : public FakeMeshFixture, INSTANTIATE_TEST_SUITE_P(LaplaceXZTest, LaplaceXZPetscTest, testing::Values(std::make_tuple(true, false))); -TEST_P(LaplaceXZPetscTest, TestSolve3D){ +TEST_P(LaplaceXZPetscTest, TestSolve3D) { Field3D expected = f3; solver.setCoefs(A, B); forward.A = A; @@ -149,8 +151,8 @@ TEST_P(LaplaceXZPetscTest, TestSolve3D){ EXPECT_TRUE(IsFieldEqual(actual, expected, "RGN_NOBNDRY", tol)); } -TEST_P(LaplaceXZPetscTest, TestSolve3DGuess){ - Field3D expected = f3, guess = f3*1.01; +TEST_P(LaplaceXZPetscTest, TestSolve3DGuess) { + Field3D expected = f3, guess = f3 * 1.01; solver.setCoefs(A, B); forward.A = A; forward.B = B;