// Copyright(C) 1999-2022 National Technology & Engineering Solutions // of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with // NTESS, the U.S. Government retains certain rights in this software. // // See packages/seacas/LICENSE for details #include // for EntityType, etc #include #include #include #include // for assert #include // for atan2, cos, sin #include // for nullptr, exit, etc #include "vtk_fmt.h" #include VTK_FMT(fmt/ostream.h) #include #include #include #include // for tokenize #include // for vector namespace Iogs { GeneratedMesh::GeneratedMesh(int64_t /*num_x */, int64_t /* num_y */, int64_t /* num_z */, int proc_count, int my_proc) : processorCount(proc_count), myProcessor(my_proc) { initialize(); } GeneratedMesh::GeneratedMesh(const std::string ¶meters, int proc_count, int my_proc) : processorCount(proc_count), myProcessor(my_proc) { // Possible that the 'parameters' has the working directory path // prepended to the parameter list. Strip off everything in front // of the last '/' (if any)... auto params = Ioss::tokenize(parameters, "/"); auto groups = Ioss::tokenize(params.back(), "|+"); // First 'group' is the interval specification -- IxJxK auto tokens = Ioss::tokenize(groups[0], "x"); assert(tokens.size() == 3); numX = std::stoll(tokens[0]); numY = std::stoll(tokens[1]); numZ = std::stoll(tokens[2]); initialize(); parse_options(groups); } GeneratedMesh::GeneratedMesh() { initialize(); } GeneratedMesh::~GeneratedMesh() = default; void GeneratedMesh::initialize() { if (processorCount > numZ) { std::ostringstream errmsg; fmt::print(errmsg, "ERROR: ({})\n" " The number of mesh intervals in the Z direction ({})\n" " must be at least as large as the number of processors ({}).\n" " The current parameters do not meet that requirement. Execution will " "terminate.\n", __func__, numZ, processorCount); IOSS_ERROR(errmsg); } if (processorCount > 1) { myNumZ = numZ / processorCount; if (myProcessor < (numZ % processorCount)) { myNumZ++; } // Determine myStartZ for this processor... size_t extra = numZ % processorCount; if (extra > myProcessor) { extra = myProcessor; } size_t per_proc = numZ / processorCount; myStartZ = myProcessor * per_proc + extra; } else { myNumZ = numZ; } for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { rotmat[i][j] = 0.0; } rotmat[i][i] = 1.0; } variableCount[Ioss::COMMSET] = 0; variableCount[Ioss::EDGEBLOCK] = 0; variableCount[Ioss::EDGESET] = 0; variableCount[Ioss::ELEMENTBLOCK] = 0; variableCount[Ioss::ELEMENTSET] = 0; variableCount[Ioss::FACEBLOCK] = 0; variableCount[Ioss::FACESET] = 0; variableCount[Ioss::INVALID_TYPE] = 0; variableCount[Ioss::NODEBLOCK] = 0; variableCount[Ioss::REGION] = 0; variableCount[Ioss::SIDEBLOCK] = 0; variableCount[Ioss::SIDESET] = 0; variableCount[Ioss::SUPERELEMENT] = 0; } int64_t GeneratedMesh::add_sideset(ShellLocation loc) { sidesets.push_back(loc); return sidesets.size(); } void GeneratedMesh::set_bbox(double xmin, double ymin, double zmin, double xmax, double ymax, double zmax) { // NOTE: All calculations are based on the currently // active interval settings. If scale or offset or zdecomp // specified later in the option list, you may not get the // desired bounding box. if (numX == 0 || numY == 0 || numZ == 0) { std::ostringstream errmsg; fmt::print(errmsg, "ERROR: ({})\n" " All interval counts must be greater than 0.\n" " numX = {}, numY = {}, numZ = {}\n", __func__, numX, numY, numZ); IOSS_ERROR(errmsg); } double x_range = xmax - xmin; double y_range = ymax - ymin; double z_range = zmax - zmin; sclX = x_range / static_cast(numX); sclY = y_range / static_cast(numY); sclZ = z_range / static_cast(numZ); offX = xmin; offY = ymin; offZ = zmin; } void GeneratedMesh::set_scale(double scl_x, double scl_y, double scl_z) { sclX = scl_x; sclY = scl_y; sclZ = scl_z; } void GeneratedMesh::set_offset(double off_x, double off_y, double off_z) { offX = off_x; offY = off_y; offZ = off_z; } void GeneratedMesh::parse_options(const std::vector &groups) { for (size_t i = 1; i < groups.size(); i++) { auto option = Ioss::tokenize(groups[i], ":"); // option[0] is the type of the option and option[1] is the argument to the option. if (option[0] == "sideset") { // Option of the form "sideset:xXyYzZ" // The argument specifies whether there is a sideset // at the location. 'x' is minX, 'X' is maxX, etc. for (auto opt : option[1]) { switch (opt) { case 'x': add_sideset(MX); break; case 'X': add_sideset(PX); break; case 'y': add_sideset(MY); break; case 'Y': add_sideset(PY); break; case 'z': add_sideset(MZ); break; case 'Z': add_sideset(PZ); break; default: std::ostringstream errmsg; fmt::print(errmsg, "ERROR: Unrecognized sideset location option '{}'.", opt); IOSS_ERROR(errmsg); } } } else if (option[0] == "scale") { // Option of the form "scale:xs,ys,zs auto tokens = Ioss::tokenize(option[1], ","); assert(tokens.size() == 3); sclX = std::stod(tokens[0]); sclY = std::stod(tokens[1]); sclZ = std::stod(tokens[2]); } else if (option[0] == "offset") { // Option of the form "offset:xo,yo,zo auto tokens = Ioss::tokenize(option[1], ","); assert(tokens.size() == 3); offX = std::stod(tokens[0]); offY = std::stod(tokens[1]); offZ = std::stod(tokens[2]); } else if (option[0] == "zdecomp") { // Option of the form "zdecomp:1,1,2,2,1,2,... // Specifies the number of intervals in the z direction // for each processor. The number of tokens must match // the number of processors. Note that the new numZ will // be the sum of the intervals specified in this command. auto tokens = Ioss::tokenize(option[1], ","); assert(tokens.size() == processorCount); Ioss::Int64Vector Zs; numZ = 0; for (size_t j = 0; j < processorCount; j++) { Zs.push_back(std::stoll(tokens[j])); numZ += Zs[j]; } myNumZ = Zs[myProcessor]; myStartZ = 0; for (size_t j = 0; j < myProcessor; j++) { myStartZ += Zs[j]; } } else if (option[0] == "bbox") { // Bounding-Box Option of the form "bbox:xmin,ymin,zmin,xmax,ymax,zmaxo auto tokens = Ioss::tokenize(option[1], ","); assert(tokens.size() == 6); double xmin = std::stod(tokens[0]); double ymin = std::stod(tokens[1]); double zmin = std::stod(tokens[2]); double xmax = std::stod(tokens[3]); double ymax = std::stod(tokens[4]); double zmax = std::stod(tokens[5]); set_bbox(xmin, ymin, zmin, xmax, ymax, zmax); } else if (option[0] == "rotate") { // Rotate Option of the form "rotate:axis,angle,axis,angle,... auto tokens = Ioss::tokenize(option[1], ","); assert(tokens.size() % 2 == 0); for (size_t ir = 0; ir < tokens.size();) { std::string axis = tokens[ir++]; double angle_degree = std::stod(tokens[ir++]); set_rotation(axis, angle_degree); } } else if (option[0] == "times") { timestepCount = std::stoll(option[1]); } else if (option[0] == "variables") { // Variables Option of the form "variables:global,10,element,100,..." auto tokens = Ioss::tokenize(option[1], ","); assert(tokens.size() % 2 == 0); for (size_t ir = 0; ir < tokens.size();) { std::string type = tokens[ir++]; int count = std::stoi(tokens[ir++]); set_variable_count(type, count); } if (timestepCount == 0) { timestepCount = 1; } } else if (option[0] == "help") { fmt::print(Ioss::OUTPUT(), "\nValid Options for GeneratedMesh parameter string:\n" "\tIxJxK -- specifies intervals; must be first option. Ex: 4x10x12\n" "\toffset:xoff, yoff, zoff\n" "\tscale: xscl, yscl, zscl\n" "\tzdecomp:n1,n2,n3,...,n#proc\n" "\tbbox: xmin, ymin, zmin, xmax, ymax, zmax\n" "\trotate: axis,angle,axis,angle,...\n" "\tsideset:xXyYzZ (specifies which plane to apply sideset)\n" "\tvariables:type,count,... " "type=global|element|node|nodal|sideset|surface\n" "\ttimes:count (number of timesteps to generate)\n" "\tshow -- show mesh parameters\n" "\thelp -- show this list\n\n"); } else if (option[0] == "show") { show_parameters(); } else { fmt::print(Ioss::WARNING(), "Unrecognized option '{}'. It will be ignored.\n", option[0]); } } } void GeneratedMesh::show_parameters() const { if (myProcessor == 0) { fmt::print(Ioss::OUTPUT(), "\nMesh Parameters:\n" "\tIntervals: {} by {} by {}\n" "\tX = {} * (0..{}) + {}\tRange: {} <= X <= {}\n" "\tY = {} * (0..{}) + {}\tRange: {} <= Y <= {}\n" "\tZ = {} * (0..{}) + {}\tRange: {} <= Z <= {}\n\n" "\tNode Count (total) = {:12}\n" "\tCell Count (total) = {:12}\n" "\tBlock Count = {:12}\n" "\tSideSet Count = {:12}\n" "\tTimestep Count = {:12}\n\n", numX, numY, numZ, sclX, numX, offX, offX, offX + numX * sclX, sclY, numY, offY, offY, offY + numY * sclY, sclZ, numZ, offZ, offZ, offZ + numZ * sclZ, fmt::group_digits(node_count()), fmt::group_digits(element_count()), fmt::group_digits(structured_block_count()), fmt::group_digits(sideset_count()), fmt::group_digits(timestep_count())); if (doRotation) { fmt::print(Ioss::OUTPUT(), "\tRotation Matrix: \n\t"); for (auto &elem : rotmat) { for (double jj : elem) { fmt::print(Ioss::OUTPUT(), "{:14.e}\t", jj); } fmt::print(Ioss::OUTPUT(), "\n\t"); } fmt::print(Ioss::OUTPUT(), "\n"); } } } int64_t GeneratedMesh::node_count() const { return (numX + 1) * (numY + 1) * (numZ + 1); } int64_t GeneratedMesh::node_count_proc() const { return (numX + 1) * (numY + 1) * (myNumZ + 1); } int64_t GeneratedMesh::structured_block_count() const { return 1; } int64_t GeneratedMesh::sideset_count() const { return sidesets.size(); } int64_t GeneratedMesh::element_count() const { int64_t count = element_count(1); return count; } int64_t GeneratedMesh::element_count_proc() const { int64_t count = 0; for (int64_t i = 0; i < structured_block_count(); i++) { count += element_count_proc(i + 1); } return count; } int64_t GeneratedMesh::element_count(int64_t block_number) const { assert(block_number <= structured_block_count()); return numX * numY * numZ; } int64_t GeneratedMesh::element_count_proc(int64_t block_number) const { assert(block_number <= structured_block_count()); return numX * numY * myNumZ; } int64_t GeneratedMesh::sideset_side_count(int64_t id) const { // id is position in sideset list + 1 assert(id > 0 && (size_t)id <= sidesets.size()); ShellLocation loc = sidesets[id - 1]; switch (loc) { case MX: case PX: return numY * numZ; case MY: case PY: return numX * numZ; case MZ: case PZ: return numX * numY; } return 0; } int64_t GeneratedMesh::sideset_side_count_proc(int64_t id) const { // id is position in sideset list + 1 assert(id > 0 && (size_t)id <= sidesets.size()); ShellLocation loc = sidesets[id - 1]; switch (loc) { case MX: case PX: return numY * myNumZ; case MY: case PY: return numX * myNumZ; case MZ: if (myProcessor == 0) { return numX * numY; } else { return 0; } case PZ: if (myProcessor == processorCount - 1) { return numX * numY; } else { return 0; } } return 0; } std::pair GeneratedMesh::topology_type(int64_t block_number) const { assert(block_number <= structured_block_count() && block_number > 0); return std::make_pair(std::string(Ioss::Hex8::name), 8); } void GeneratedMesh::node_map(Ioss::Int64Vector &map) const { map.resize(node_count_proc()); int64_t offset = myStartZ * (numX + 1) * (numY + 1); std::iota(map.begin(), map.end(), offset + 1); } void GeneratedMesh::node_map(Ioss::IntVector &map) const { map.resize(node_count_proc()); int offset = myStartZ * (numX + 1) * (numY + 1); std::iota(map.begin(), map.end(), offset + 1); } int64_t GeneratedMesh::communication_node_count_proc() const { int64_t count = (numX + 1) * (numY + 1); if (myProcessor != 0 && myProcessor != processorCount - 1) { count *= 2; } return count; } void GeneratedMesh::owning_processor(int *owner, int64_t num_node) { for (int64_t i = 0; i < num_node; i++) { owner[i] = myProcessor; } if (myProcessor != 0) { int64_t count = (numX + 1) * (numY + 1); for (int64_t i = 0; i < count; i++) { owner[i] = myProcessor - 1; } } } void GeneratedMesh::build_node_map(Ioss::Int64Vector &map, std::vector &proc, int64_t slab, size_t slabOffset, size_t adjacentProc, size_t index) { int64_t offset = (myStartZ + slabOffset) * (numX + 1) * (numY + 1); for (int64_t i = 0; i < slab; i++) { map[index] = offset + i + 1; proc[index++] = static_cast(adjacentProc); } } void GeneratedMesh::node_communication_map(Ioss::Int64Vector &map, std::vector &proc) { bool isFirstProc = myProcessor == 0; bool isLastProc = myProcessor == processorCount - 1; int64_t count = (numX + 1) * (numY + 1); int64_t slab = count; if (!isFirstProc && !isLastProc) { count *= 2; } map.resize(count); proc.resize(count); size_t offset = 0; if (!isFirstProc) { build_node_map(map, proc, slab, 0, myProcessor - 1, offset); offset += slab; } if (!isLastProc) { build_node_map(map, proc, slab, myNumZ, myProcessor + 1, offset); } } void GeneratedMesh::element_map(int64_t block_number, Ioss::Int64Vector &map) const { raw_element_map(block_number, map); } void GeneratedMesh::element_map(int64_t block_number, Ioss::IntVector &map) const { raw_element_map(block_number, map); } template void GeneratedMesh::raw_element_map(int64_t block_number, std::vector &map) const { assert(block_number <= structured_block_count() && block_number > 0); INT count = element_count_proc(block_number); map.reserve(count); if (block_number == 1) { // Hex/Tet block... count = element_count_proc(1); INT offset = myStartZ * numX * numY; for (INT i = 0; i < count; i++) { map.push_back(offset + i + 1); } } } void GeneratedMesh::element_map(Ioss::Int64Vector &map) const { raw_element_map(map); } void GeneratedMesh::element_map(Ioss::IntVector &map) const { raw_element_map(map); } template void GeneratedMesh::raw_element_map(std::vector &map) const { INT count = element_count_proc(); map.reserve(count); // Hex block... count = element_count_proc(1); INT offset = myStartZ * numX * numY; for (INT i = 0; i < count; i++) { map.push_back(offset + i + 1); } } void GeneratedMesh::element_surface_map(ShellLocation loc, Ioss::Int64Vector &map) const { int64_t count = 0; map.resize(2 * count); int64_t index = 0; int64_t offset = 0; // For hex elements switch (loc) { case MX: offset = myStartZ * numX * numY + 1; // 1-based elem id for (size_t k = 0; k < myNumZ; ++k) { for (size_t j = 0; j < numY; ++j) { map[index++] = offset; map[index++] = 3; // 0-based local face id offset += numX; } } break; case PX: offset = myStartZ * numX * numY + numX; for (size_t k = 0; k < myNumZ; ++k) { for (size_t j = 0; j < numY; ++j) { map[index++] = offset; // 1-based elem id map[index++] = 1; // 0-based local face id offset += numX; } } break; case MY: offset = myStartZ * numX * numY + 1; for (size_t k = 0; k < myNumZ; ++k) { for (size_t i = 0; i < numX; ++i) { map[index++] = offset++; map[index++] = 0; // 0-based local face id } offset += numX * (numY - 1); } break; case PY: offset = myStartZ * numX * numY + numX * (numY - 1) + 1; for (size_t k = 0; k < myNumZ; ++k) { for (size_t i = 0; i < numX; ++i) { map[index++] = offset++; map[index++] = 2; // 0-based local face id } offset += numX * (numY - 1); } break; case MZ: if (myProcessor == 0) { offset = 1; for (size_t i = 0; i < numY; i++) { for (size_t j = 0; j < numX; j++) { map[index++] = offset++; map[index++] = 4; } } } break; case PZ: if (myProcessor == processorCount - 1) { offset = (numZ - 1) * numX * numY + 1; for (size_t i = 0, k = 0; i < numY; i++) { for (size_t j = 0; j < numX; j++, k++) { map[index++] = offset++; map[index++] = 5; } } } break; } } void GeneratedMesh::coordinates(std::vector &coord) const { /* create global coordinates */ int64_t count = node_count_proc(); coord.resize(count * 3); coordinates(&coord[0]); } void GeneratedMesh::coordinates(double *coord) const { /* create global coordinates */ int64_t count = node_count_proc(); int64_t k = 0; for (size_t m = myStartZ; m < myStartZ + myNumZ + 1; m++) { for (size_t i = 0; i < numY + 1; i++) { for (size_t j = 0; j < numX + 1; j++) { coord[k++] = sclX * static_cast(j) + offX; coord[k++] = sclY * static_cast(i) + offY; coord[k++] = sclZ * static_cast(m) + offZ; } } } if (doRotation) { for (int64_t i = 0; i < count * 3; i += 3) { double xn = coord[i + 0]; double yn = coord[i + 1]; double zn = coord[i + 2]; coord[i + 0] = xn * rotmat[0][0] + yn * rotmat[1][0] + zn * rotmat[2][0]; coord[i + 1] = xn * rotmat[0][1] + yn * rotmat[1][1] + zn * rotmat[2][1]; coord[i + 2] = xn * rotmat[0][2] + yn * rotmat[1][2] + zn * rotmat[2][2]; } } } void GeneratedMesh::coordinates(std::vector &x, std::vector &y, std::vector &z) const { /* create global coordinates */ int64_t count = node_count_proc(); x.reserve(count); y.reserve(count); z.reserve(count); for (size_t m = myStartZ; m < myStartZ + myNumZ + 1; m++) { for (size_t i = 0; i < numY + 1; i++) { for (size_t j = 0; j < numX + 1; j++) { x.push_back(sclX * static_cast(j) + offX); y.push_back(sclY * static_cast(i) + offY); z.push_back(sclZ * static_cast(m) + offZ); } } } if (doRotation) { for (int64_t i = 0; i < count; i++) { double xn = x[i]; double yn = y[i]; double zn = z[i]; x.push_back(xn * rotmat[0][0] + yn * rotmat[1][0] + zn * rotmat[2][0]); y.push_back(xn * rotmat[0][1] + yn * rotmat[1][1] + zn * rotmat[2][1]); z.push_back(xn * rotmat[0][2] + yn * rotmat[1][2] + zn * rotmat[2][2]); } } } void GeneratedMesh::coordinates(int component, std::vector &xyz) const { assert(!doRotation); /* create global coordinates */ size_t count = node_count_proc(); xyz.reserve(count); if (component == 1) { for (size_t m = myStartZ; m < myStartZ + myNumZ + 1; m++) { for (size_t i = 0; i < numY + 1; i++) { for (size_t j = 0; j < numX + 1; j++) { xyz.push_back(sclX * static_cast(j) + offX); } } } } else if (component == 2) { for (size_t m = myStartZ; m < myStartZ + myNumZ + 1; m++) { for (size_t i = 0; i < numY + 1; i++) { for (size_t j = 0; j < numX + 1; j++) { xyz.push_back(sclY * static_cast(i) + offY); } } } } else if (component == 3) { for (size_t m = myStartZ; m < myStartZ + myNumZ + 1; m++) { for (size_t i = 0; i < numY + 1; i++) { for (size_t j = 0; j < numX + 1; j++) { xyz.push_back(sclZ * static_cast(m) + offZ); } } } } } void GeneratedMesh::coordinates(int component, int /* zone */, double *xyz) const { assert(!doRotation); /* create global coordinates */ if (component == 0) { size_t jjj = 0; for (size_t m = 0; m < numZ + 1; m++) { for (size_t i = 0; i < numY + 1; i++) { for (size_t j = 0; j < numX + 1; j++) { xyz[jjj++] = (sclX * static_cast(j) + offX); xyz[jjj++] = (sclY * static_cast(i) + offY); xyz[jjj++] = (sclZ * static_cast(m) + offZ); } } } } else if (component == 1) { size_t jjj = 0; for (size_t m = 0; m < numZ + 1; m++) { for (size_t i = 0; i < numY + 1; i++) { for (size_t j = 0; j < numX + 1; j++) { xyz[jjj++] = (sclX * static_cast(j) + offX); } } } } else if (component == 2) { size_t jjj = 0; for (size_t m = 0; m < numZ + 1; m++) { for (size_t i = 0; i < numY + 1; i++) { for (size_t j = 0; j < numX + 1; j++) { xyz[jjj++] = (sclY * static_cast(i) + offY); } } } } else if (component == 3) { size_t jjj = 0; for (size_t m = 0; m < numZ + 1; m++) { for (size_t i = 0; i < numY + 1; i++) { for (size_t j = 0; j < numX + 1; j++) { xyz[jjj++] = (sclZ * static_cast(m) + offZ); } } } } } void GeneratedMesh::connectivity(int64_t block_number, Ioss::Int64Vector &connect) const { if (block_number == 1) { // HEX Element Block connect.resize(element_count_proc(block_number) * 8); } raw_connectivity(block_number, &connect[0]); } void GeneratedMesh::connectivity(int64_t block_number, Ioss::IntVector &connect) const { if (block_number == 1) { // HEX Element Block connect.resize(element_count_proc(block_number) * 8); } raw_connectivity(block_number, &connect[0]); } void GeneratedMesh::connectivity(int64_t block_number, int64_t *connect) const { raw_connectivity(block_number, connect); } void GeneratedMesh::connectivity(int64_t block_number, int *connect) const { raw_connectivity(block_number, connect); } template void GeneratedMesh::raw_connectivity(int64_t block_number, INT *connect) const { assert(block_number <= structured_block_count()); INT xp1yp1 = (numX + 1) * (numY + 1); /* build connectivity array (node list) for mesh */ if (block_number == 1) { // main block elements // Hex elements size_t cnt = 0; for (size_t m = myStartZ; m < myNumZ + myStartZ; m++) { for (size_t i = 0, k = 0; i < numY; i++) { for (size_t j = 0; j < numX; j++, k++) { size_t base = (m * xp1yp1) + k + i + 1; connect[cnt++] = base; connect[cnt++] = base + 1; connect[cnt++] = base + numX + 2; connect[cnt++] = base + numX + 1; connect[cnt++] = xp1yp1 + base; connect[cnt++] = xp1yp1 + base + 1; connect[cnt++] = xp1yp1 + base + numX + 2; connect[cnt++] = xp1yp1 + base + numX + 1; } } } } return; } void GeneratedMesh::sideset_elem_sides(int64_t id, Ioss::Int64Vector &elem_sides) const { // id is position in sideset list + 1 assert(id > 0 && (size_t)id <= sidesets.size()); ShellLocation loc = sidesets[id - 1]; element_surface_map(loc, elem_sides); } std::vector GeneratedMesh::sideset_touching_blocks(int64_t /*set_id*/) const { std::vector result(1, "block_1"); return result; } void GeneratedMesh::set_variable_count(const std::string &type, size_t count) { if (type == "global") { variableCount[Ioss::REGION] = count; } else if (type == "element") { variableCount[Ioss::ELEMENTBLOCK] = count; } else if (type == "nodal" || type == "node") { variableCount[Ioss::NODEBLOCK] = count; } else if (type == "surface" || type == "sideset") { variableCount[Ioss::SIDEBLOCK] = count; } else { fmt::print(Ioss::WARNING(), "(Iogs::GeneratedMesh::set_variable_count)\n" " Unrecognized variable type '{}'. Valid types are:\n" " global, element, node, nodal, surface, sideset.\n", type); } } void GeneratedMesh::set_rotation(const std::string &axis, double angle_degrees) { // PI / 180. Used in converting angle in degrees to radians static double degang = std::atan2(0.0, -1.0) / 180.0; doRotation = true; int n1 = -1; int n2 = -1; int n3 = -1; if (axis == "x" || axis == "X") { n1 = 1; n2 = 2; n3 = 0; } else if (axis == "y" || axis == "Y") { n1 = 2; n2 = 0; n3 = 1; } else if (axis == "z" || axis == "Z") { n1 = 0; n2 = 1; n3 = 2; } else { fmt::print("\nInvalid axis specification '{}'. Valid options are 'x', 'y', or 'z'\n", axis); return; } double ang = angle_degrees * degang; // Convert angle in degrees to radians double cosang = std::cos(ang); double sinang = std::sin(ang); assert(n1 >= 0 && n2 >= 0 && n3 >= 0); std::array, 3> by; by[n1][n1] = cosang; by[n2][n1] = -sinang; by[n1][n3] = 0.0; by[n1][n2] = sinang; by[n2][n2] = cosang; by[n2][n3] = 0.0; by[n3][n1] = 0.0; by[n3][n2] = 0.0; by[n3][n3] = 1.0; std::array, 3> res; for (int i = 0; i < 3; i++) { res[i][0] = rotmat[i][0] * by[0][0] + rotmat[i][1] * by[1][0] + rotmat[i][2] * by[2][0]; res[i][1] = rotmat[i][0] * by[0][1] + rotmat[i][1] * by[1][1] + rotmat[i][2] * by[2][1]; res[i][2] = rotmat[i][0] * by[0][2] + rotmat[i][1] * by[1][2] + rotmat[i][2] * by[2][2]; } rotmat = res; } } // namespace Iogs