From a4b89761922214523d633c5f79c4261293ff2487 Mon Sep 17 00:00:00 2001 From: filipovic Date: Sat, 6 Dec 2025 21:28:42 +0100 Subject: [PATCH 01/57] Limit time step at material interfaces and test with python SquareEtch example --- examples/SquareEtch/SquareEtch.py | 186 ++++++++++++++++++++++++++++++ include/viennals/lsAdvect.hpp | 71 ++++++++---- python/__init__.py | 9 ++ python/pyWrap.hpp | 5 + 4 files changed, 251 insertions(+), 20 deletions(-) create mode 100644 examples/SquareEtch/SquareEtch.py diff --git a/examples/SquareEtch/SquareEtch.py b/examples/SquareEtch/SquareEtch.py new file mode 100644 index 00000000..c0f190af --- /dev/null +++ b/examples/SquareEtch/SquareEtch.py @@ -0,0 +1,186 @@ +import viennals as vls +import argparse + +vls.Logger.setLogLevel(vls.LogLevel.INFO) + +# 1. Define the Velocity Field +# We inherit from viennals.VelocityField to define custom logic in Python +class EtchingField(vls.VelocityField): + def getScalarVelocity(self, coordinate, material, normalVector, pointId): + if material == 2: + return -0.1 + if material == 1: + return -1. + return 0.0 + + def getVectorVelocity(self, coordinate, material, normalVector, pointId): + return [0.0] * len(coordinate) + +# 1. Define the Velocity Field +# We inherit from viennals.VelocityField to define custom logic in Python +class DepositionField(vls.VelocityField): + def getScalarVelocity(self, coordinate, material, normalVector, pointId): + return 0.75 + + def getVectorVelocity(self, coordinate, material, normalVector, pointId): + return [0.0] * len(coordinate) + +def main(): + # 1. Parse Arguments + parser = argparse.ArgumentParser(description="Run Square Etch simulation in 2D or 3D.") + parser.add_argument( + "-D", "--dim", + type=int, + default=2, + choices=[2, 3], + help="Dimension of the simulation (2 or 3). Default is 2." + ) + args = parser.parse_args() + + DIMENSION = args.dim + vls.setDimension(DIMENSION) + vls.setNumThreads(8) + + extent = 30.0 + gridDelta = 0.47 + + # Define bounds and boundary conditions + trenchBottom = -2.0 + bounds = [-extent, extent, -extent, extent] + origin = [0.0, 0.0] + if DIMENSION == 3: + bounds.append(-extent) + bounds.append(extent) + origin.append(0.0) + boundaryCons = [] + for i in range(DIMENSION - 1): + boundaryCons.append(vls.BoundaryConditionEnum.REFLECTIVE_BOUNDARY) + boundaryCons.append(vls.BoundaryConditionEnum.INFINITE_BOUNDARY) + + planeNormal = list(origin) + downNormal = list(origin) + planeNormal[DIMENSION - 1] = 1.0 + downNormal[DIMENSION - 1] = -1.0 + + # Create the Substrate Domain + substrate = vls.Domain(bounds, boundaryCons, gridDelta) + + # Create initial flat geometry (Plane at y/z=0) + plane = vls.Plane(origin, planeNormal) + vls.MakeGeometry(substrate, plane).apply() + + # -------------------------------------- + # 3. Trench Geometry + # -------------------------------------- + trench = vls.Domain(bounds, boundaryCons, gridDelta) + + # Define Box Corners based on dimension + minCorner = list(origin) + maxCorner = list(origin) + originMask = list (origin) + for i in range(DIMENSION - 1): + minCorner[i] = -extent / 1.5 + maxCorner[i] = extent / 1.5 + minCorner[DIMENSION - 1] = trenchBottom + maxCorner[DIMENSION - 1] = 1.0 + originMask[DIMENSION - 1] = trenchBottom + 1e-9 + + box = vls.Box(minCorner, maxCorner) + vls.MakeGeometry(trench, box).apply() + + # Subtract trench from substrate (Relative Complement) + vls.BooleanOperation(substrate, trench, vls.BooleanOperationEnum.RELATIVE_COMPLEMENT).apply() + + # 4. Create the Mask Layer + # The mask prevents etching at the bottom of the trench in specific configurations, + # or acts as a hard stop/masking material. + mask = vls.Domain(bounds, boundaryCons, gridDelta) + + vls.MakeGeometry(mask, vls.Plane(originMask, downNormal)).apply() + + # Intersect with substrate geometry + vls.BooleanOperation(mask, substrate, vls.BooleanOperationEnum.INTERSECT).apply() + + # 5. Export Initial State + print(f"Running in {DIMENSION}D.") + print("Extracting initial meshes...") + mesh = vls.Mesh() + + # Save substrate + vls.ToSurfaceMesh(substrate, mesh).apply() + vls.VTKWriter(mesh, f"substrate-{DIMENSION}D.vtp").apply() + + # Save mask + vls.ToSurfaceMesh(mask, mesh).apply() + vls.VTKWriter(mesh, f"mask-{DIMENSION}D.vtp").apply() + + print("Creating new layer...") + polymer = vls.Domain(substrate) + + + # 6. Setup Advection + deposition = DepositionField() + etching = EtchingField() + + print("Advecting") + advectionKernel = vls.Advect() + + # the level set to be advected has to be inserted last + # the other could be taken as a mask layer for advection + advectionKernel.insertNextLevelSet(mask) + advectionKernel.insertNextLevelSet(substrate) + advectionKernel.insertNextLevelSet(polymer) + + advectionKernel.setSaveAdvectionVelocities(True) + # advectionKernel.setVelocityField(etching) + + advectionKernel.setVelocityField(deposition) + advectionKernel.apply() + + vls.ToSurfaceMesh(polymer, mesh).apply() + vls.VTKWriter(mesh, f"newLayer-{DIMENSION}D.vtp").apply() + + advectionKernel.setSaveAdvectionVelocities(True) + advectionKernel.setVelocityField(etching) + + # Use default integration scheme (Lax Friedrichs 1st order) as in the C++ else branch + # advectionKernel.setIntegrationScheme(viennals.IntegrationSchemeEnum.LAX_FRIEDRICHS_1ST_ORDER) + + # 7. Time Loop + finalTime = 10.0 + counter = 1 + currentTime = 0.0 + + # The advect kernel calculates stable time steps automatically. + # We call apply() repeatedly until we reach finalTime. + + # Note: In the C++ example, the loop structure is: + # for (double time = 0.; time < finalTime; time += advectionKernel.getAdvectedTime()) + # This implies one step is taken, then time is updated. + + # Save initial state + vls.ToSurfaceMesh(polymer, mesh).apply() + vls.VTKWriter(mesh, f"numerical-{DIMENSION}D-0.vtp").apply() + + while currentTime < finalTime: + advectionKernel.apply() + + stepTime = advectionKernel.getAdvectedTime() + currentTime += stepTime + + print(f"Advection step: {counter}, time: {stepTime:.4f} (Total: {currentTime:.4f})") + + # Export result + vls.ToSurfaceMesh(polymer, mesh).apply() + vls.VTKWriter(mesh, f"numerical-{DIMENSION}D-{counter}.vtp").apply() + + counter += 1 + + print(f"\nNumber of Advection steps taken: {counter}") + + # Final export + vls.ToSurfaceMesh(polymer, mesh).apply() + vls.VTKWriter(mesh, f"final-{DIMENSION}D.vtp").apply() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index 87f58a76..7a935091 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -520,48 +520,79 @@ template class Advect { } } - T valueBelow; - // get value of material below (before in levelSets list) - if (currentLevelSetId > 0) { - iterators[currentLevelSetId - 1].goToIndicesSequential( - it.getStartIndices()); - valueBelow = iterators[currentLevelSetId - 1].getValue(); - } else { - valueBelow = std::numeric_limits::max(); - } - - // if velocity is positive, set maximum time step possible without - // violating the cfl condition + // Case 1: Growth / Deposition (Velocity > 0) + // Limit the time step based on the standard CFL condition. T velocity = gradNDissipation.first - gradNDissipation.second; if (velocity > 0.) { maxStepTime += cfl / velocity; tempRates.push_back(std::make_pair(gradNDissipation, -std::numeric_limits::max())); break; - // if velocity is 0, maximum time step is infinite + // Case 2: Static (Velocity == 0) + // No time step limit imposed by this point. } else if (velocity == 0.) { maxStepTime = std::numeric_limits::max(); tempRates.push_back(std::make_pair(gradNDissipation, std::numeric_limits::max())); break; - // if the velocity is negative apply the velocity for as long as - // possible without infringing on material below + // Case 3: Etching (Velocity < 0) } else { + // Retrieve the LS value of the material interface directly below + // the current one. This block is moved into this else since it is + // only required for etching. + T valueBelow; + if (currentLevelSetId > 0) { + iterators[currentLevelSetId - 1].goToIndicesSequential( + it.getStartIndices()); + valueBelow = iterators[currentLevelSetId - 1].getValue(); + } else { + // If there is no material below, the limit is effectively + // infinite distance. + valueBelow = std::numeric_limits::max(); + } T difference = std::abs(valueBelow - value); + // Sub-case 3a: Micro-layer handling (Numerical Stability) + // If the layer is vanishingly thin (noise level), consume it + // immediately and continue to the next layer without limiting the + // global time step. This prevents the simulation from stalling due + // to near-zero time steps. + if (difference < 1e-4) { + maxStepTime -= difference / velocity; + tempRates.push_back(std::make_pair(gradNDissipation, valueBelow)); + cfl -= difference; + value = valueBelow; + continue; + } + + // Sub-case 3b: Thick Layer (Standard CFL) + // The material is thick enough to support a full CFL time step. if (difference >= cfl) { maxStepTime -= cfl / velocity; tempRates.push_back(std::make_pair( gradNDissipation, std::numeric_limits::max())); break; + + // Sub-case 3c: Interface Hit (Thin Layer) + // The material is thinner than the CFL distance. + // We must STOP exactly at the interface. } else { + // Calculate the time required to reach the material boundary. maxStepTime -= difference / velocity; - // the second part of the pair indicates how far we can move - // in this time step until the end of the material is reached tempRates.push_back(std::make_pair(gradNDissipation, valueBelow)); - cfl -= difference; - // use new LS value for next calculations - value = valueBelow; + + tempRates.push_back(std::make_pair( + gradNDissipation, std::numeric_limits::max())); + + Logger::getInstance() + .addDebug("Global time step limited by layer thickness!" + "\nInstead of " + + std::to_string(-cfl / velocity) + " it is set to " + + std::to_string(maxStepTime)) + .print(); + // We stop processing layers below. By breaking here, we force the + // simulation to pause exactly when the top layer is consumed. + break; } } } diff --git a/python/__init__.py b/python/__init__.py index c6882479..12459eb8 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -51,6 +51,15 @@ def setDimension(d: int): else: raise ValueError("Dimension must be 2 or 3.") +def getDimension() -> int: + """Get the current dimension of the simulation. + + Returns + ------- + int + The currently set dimension (2 or 3). + """ + return PROXY_DIM def __getattr__(name): # 1) common/top-level from _core diff --git a/python/pyWrap.hpp b/python/pyWrap.hpp index 7a011e54..b64169b9 100644 --- a/python/pyWrap.hpp +++ b/python/pyWrap.hpp @@ -190,6 +190,11 @@ template void bindApi(py::module &module) { .def("setIgnoreVoids", &Advect::setIgnoreVoids, "Set whether voids in the geometry should be ignored during " "advection or not.") + .def( + "setSaveAdvectionVelocities", + &Advect::setSaveAdvectionVelocities, + "Set whether the velocities applied to each point should be saved in " + "the level set for debug purposes.") .def("getAdvectedTime", &Advect::getAdvectedTime, "Get the time passed during advection.") .def("getNumberOfTimeSteps", &Advect::getNumberOfTimeSteps, From c3fc0d4c557e0abe45493d27142d3d8203df45b5 Mon Sep 17 00:00:00 2001 From: Tobias Reiter Date: Sun, 7 Dec 2025 12:04:18 +0100 Subject: [PATCH 02/57] Update logger --- CMakeLists.txt | 2 +- include/viennals/lsAdvect.hpp | 47 +++++++---------- include/viennals/lsBooleanOperation.hpp | 18 +++---- include/viennals/lsCalculateCurvatures.hpp | 18 +++---- include/viennals/lsCalculateNormalVectors.hpp | 33 ++++++------ include/viennals/lsCompareArea.hpp | 14 +++--- include/viennals/lsCompareChamfer.hpp | 32 +++++------- .../viennals/lsCompareCriticalDimensions.hpp | 16 +++--- include/viennals/lsCompareNarrowBand.hpp | 21 ++++---- include/viennals/lsCompareSparseField.hpp | 36 ++++++------- include/viennals/lsCurvatureFormulas.hpp | 8 ++- include/viennals/lsFromVolumeMesh.hpp | 20 +++----- include/viennals/lsGeometricAdvect.hpp | 10 ++-- .../lsGeometricAdvectDistributions.hpp | 9 ++-- include/viennals/lsMakeGeometry.hpp | 4 +- include/viennals/lsMarkVoidPoints.hpp | 6 +-- include/viennals/lsPointData.hpp | 50 ++++++++----------- include/viennals/lsReader.hpp | 5 +- include/viennals/lsSlice.hpp | 20 +++----- .../lsStencilLocalLaxFriedrichsScalar.hpp | 8 ++- include/viennals/lsToDiskMesh.hpp | 9 ++-- include/viennals/lsToMesh.hpp | 19 +++---- include/viennals/lsToSurfaceMesh.hpp | 12 ++--- include/viennals/lsVTKReader.hpp | 8 ++- include/viennals/lsVTKWriter.hpp | 24 ++++----- include/viennals/lsWriteVisualizationMesh.hpp | 8 ++- include/viennals/lsWriter.hpp | 9 ++-- 27 files changed, 184 insertions(+), 282 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ea37483..df98441c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,7 +111,7 @@ include(cmake/vtk.cmake) CPMAddPackage( NAME ViennaCore - VERSION 1.7.0 + VERSION 1.7.3 GIT_REPOSITORY "https://github.com/ViennaTools/ViennaCore" OPTIONS "VIENNACORE_FORMAT_EXCLUDE docs/ build/" EXCLUDE_FROM_ALL ${VIENNALS_BUILD_PYTHON}) diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index 7a935091..0a0a117f 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -385,15 +385,12 @@ template class Advect { double advect(double maxTimeStep) { // check whether a level set and velocities have been given if (levelSets.empty()) { - Logger::getInstance() - .addError("No level sets passed to Advect. Not advecting.") - .print(); + VIENNACORE_LOG_ERROR("No level sets passed to Advect. Not advecting."); return std::numeric_limits::max(); } if (velocities == nullptr) { - Logger::getInstance() - .addError("No velocity field passed to Advect. Not advecting.") - .print(); + VIENNACORE_LOG_ERROR( + "No velocity field passed to Advect. Not advecting."); return std::numeric_limits::max(); } @@ -438,19 +435,15 @@ template class Advect { voidMarkerPointer = pointData.getScalarData(MarkVoidPoints::voidPointLabel, true); if (voidMarkerPointer == nullptr) { - Logger::getInstance() - .addWarning("Advect: Cannot find void point markers. Not " - "ignoring void points.") - .print(); + VIENNACORE_LOG_WARNING("Advect: Cannot find void point markers. Not " + "ignoring void points."); ignoreVoids = false; } } const bool ignoreVoidPoints = ignoreVoids; if (!storedRates.empty()) { - Logger::getInstance() - .addWarning("Advect: Overwriting previously stored rates.") - .print(); + VIENNACORE_LOG_WARNING("Advect: Overwriting previously stored rates."); } storedRates.resize(topDomain.getNumberOfSegments()); @@ -584,12 +577,11 @@ template class Advect { tempRates.push_back(std::make_pair( gradNDissipation, std::numeric_limits::max())); - Logger::getInstance() - .addDebug("Global time step limited by layer thickness!" - "\nInstead of " + - std::to_string(-cfl / velocity) + " it is set to " + - std::to_string(maxStepTime)) - .print(); + VIENNACORE_LOG_DEBUG( + "Global time step limited by layer thickness!" + "\nInstead of " + + std::to_string(-cfl / velocity) + " it is set to " + + std::to_string(maxStepTime)); // We stop processing layers below. By breaking here, we force the // simulation to pause exactly when the top layer is consumed. break; @@ -624,10 +616,9 @@ template class Advect { // depth accordingly if there would be a material change. void moveSurface() { if (timeStepRatio >= 0.5) { - Logger::getInstance() - .addWarning("Integration time step ratio should be smaller than 0.5. " - "Advection might fail!") - .print(); + VIENNACORE_LOG_WARNING( + "Integration time step ratio should be smaller than 0.5. " + "Advection might fail!"); } auto &topDomain = levelSets.back()->getDomain(); @@ -847,7 +838,7 @@ template class Advect { void prepareLS() { // check whether a level set and velocities have been given if (levelSets.empty()) { - Logger::getInstance().addError("No level sets passed to Advect.").print(); + VIENNACORE_LOG_ERROR("No level sets passed to Advect."); return; } @@ -884,9 +875,7 @@ template class Advect { lsInternal::StencilLocalLaxFriedrichsScalar::prepareLS( levelSets.back()); } else { - Logger::getInstance() - .addError("Advect: Integration scheme not found.") - .print(); + VIENNACORE_LOG_ERROR("Advect: Integration scheme not found."); } } @@ -968,9 +957,7 @@ template class Advect { levelSets.back(), velocities, dissipationAlpha); currentTimeStep = integrateTime(is, maxTimeStep); } else { - Logger::getInstance() - .addError("Advect: Integration scheme not found.") - .print(); + VIENNACORE_LOG_ERROR("Advect: Integration scheme not found."); currentTimeStep = -1.; } } diff --git a/include/viennals/lsBooleanOperation.hpp b/include/viennals/lsBooleanOperation.hpp index b2661a0d..d77ffa41 100644 --- a/include/viennals/lsBooleanOperation.hpp +++ b/include/viennals/lsBooleanOperation.hpp @@ -281,7 +281,7 @@ template class BooleanOperation { SmartPointer> passedlsDomainB, BooleanOperationEnum passedOperation = BooleanOperationEnum::INTERSECT) : levelSetA(passedlsDomainA), levelSetB(passedlsDomainB), - operation(passedOperation){}; + operation(passedOperation) {}; /// Set which level set to perform the boolean operation on. void setLevelSet(SmartPointer> passedlsDomain) { @@ -315,18 +315,15 @@ template class BooleanOperation { /// Perform operation. void apply() { if (levelSetA == nullptr) { - Logger::getInstance() - .addError("No level set was passed to BooleanOperation.") - .print(); + VIENNACORE_LOG_ERROR("No level set was passed to BooleanOperation."); return; } if (static_cast(operation) < 3) { if (levelSetB == nullptr) { - Logger::getInstance() - .addError("Only one level set was passed to BooleanOperation, " - "although two were required.") - .print(); + VIENNACORE_LOG_ERROR( + "Only one level set was passed to BooleanOperation, " + "although two were required."); return; } } @@ -346,9 +343,8 @@ template class BooleanOperation { break; case BooleanOperationEnum::CUSTOM: if (operationComp == nullptr) { - Logger::getInstance() - .addError("No comparator supplied to custom BooleanOperation.") - .print(); + VIENNACORE_LOG_ERROR( + "No comparator supplied to custom BooleanOperation."); return; } booleanOpInternal(operationComp); diff --git a/include/viennals/lsCalculateCurvatures.hpp b/include/viennals/lsCalculateCurvatures.hpp index 6fcff3e3..3d53499a 100644 --- a/include/viennals/lsCalculateCurvatures.hpp +++ b/include/viennals/lsCalculateCurvatures.hpp @@ -49,7 +49,7 @@ template class CalculateCurvatures { type = passedType; } else { if (passedType != type) { - Logger::getInstance().addWarning( + VIENNACORE_LOG_WARNING( "CalculateCurvatures: Could not set curvature type because 2D " "only supports mean curvature."); } @@ -60,20 +60,18 @@ template class CalculateCurvatures { void apply() { if (levelSet == nullptr) { - Logger::getInstance() - .addError("No level set was passed to CalculateCurvatures.") - .print(); + VIENNACORE_LOG_ERROR("No level set was passed to CalculateCurvatures."); + return; } // need second neighbours if (unsigned minWidth = std::ceil((maxValue * 8) + 1); levelSet->getLevelSetWidth() < minWidth) { - Logger::getInstance() - .addWarning("CalculateCurvatures: Level set width must be " - "at least " + - std::to_string(minWidth) + ". Expanding level set to " + - std::to_string(minWidth) + ".") - .print(); + VIENNACORE_LOG_WARNING("CalculateCurvatures: Level set width must be " + "at least " + + std::to_string(minWidth) + + ". Expanding level set to " + + std::to_string(minWidth) + "."); Expand(levelSet, minWidth).apply(); } diff --git a/include/viennals/lsCalculateNormalVectors.hpp b/include/viennals/lsCalculateNormalVectors.hpp index d617fa9f..d24c77ce 100644 --- a/include/viennals/lsCalculateNormalVectors.hpp +++ b/include/viennals/lsCalculateNormalVectors.hpp @@ -56,11 +56,10 @@ template class CalculateNormalVectors { void setMaxValue(const T passedMaxValue) { if (passedMaxValue <= 0) { - Logger::getInstance() - .addWarning("CalculateNormalVectors: maxValue should be positive. " - "Using default value " + - std::to_string(DEFAULT_MAX_VALUE) + ".") - .print(); + VIENNACORE_LOG_WARNING( + "CalculateNormalVectors: maxValue should be positive. " + "Using default value " + + std::to_string(DEFAULT_MAX_VALUE) + "."); maxValue = DEFAULT_MAX_VALUE; } else { maxValue = passedMaxValue; @@ -81,20 +80,17 @@ template class CalculateNormalVectors { void apply() { if (levelSet == nullptr) { - Logger::getInstance() - .addError("No level set was passed to CalculateNormalVectors.") - .print(); + VIENNACORE_LOG_ERROR( + "No level set was passed to CalculateNormalVectors."); return; } if (levelSet->getLevelSetWidth() < (maxValue * 4) + 1) { - Logger::getInstance() - .addWarning("CalculateNormalVectors: Level set width must be " - "greater than " + - std::to_string((maxValue * 4) + 1) + - ". Expanding level set to " + - std::to_string((maxValue * 4) + 1) + ".") - .print(); + VIENNACORE_LOG_WARNING("CalculateNormalVectors: Level set width must be " + "greater than " + + std::to_string((maxValue * 4) + 1) + + ". Expanding level set to " + + std::to_string((maxValue * 4) + 1) + "."); Expand(levelSet, (maxValue * 4) + 1).apply(); } @@ -155,10 +151,9 @@ template class CalculateNormalVectors { denominator = std::sqrt(denominator); if (std::abs(denominator) < EPSILON) { - Logger::getInstance() - .addWarning("CalculateNormalVectors: Vector of length 0 at " + - neighborIt.getIndices().to_string()) - .print(); + VIENNACORE_LOG_WARNING( + "CalculateNormalVectors: Vector of length 0 at " + + neighborIt.getIndices().to_string()); for (unsigned i = 0; i < D; ++i) n[i] = 0.; } else { diff --git a/include/viennals/lsCompareArea.hpp b/include/viennals/lsCompareArea.hpp index 32763313..7115c223 100644 --- a/include/viennals/lsCompareArea.hpp +++ b/include/viennals/lsCompareArea.hpp @@ -200,19 +200,17 @@ template class CompareArea { if (levelSetTarget->getLevelSetWidth() < minimumWidth) { workingTarget = SmartPointer>::New(levelSetTarget); Expand(workingTarget, minimumWidth).apply(); - Logger::getInstance() - .addInfo("CompareArea: Expanded target level set to width " + - std::to_string(minimumWidth) + " to avoid undefined values.") - .print(); + VIENNACORE_LOG_INFO("CompareArea: Expanded target level set to width " + + std::to_string(minimumWidth) + + " to avoid undefined values."); } if (levelSetSample->getLevelSetWidth() < minimumWidth) { workingSample = SmartPointer>::New(levelSetSample); Expand(workingSample, minimumWidth).apply(); - Logger::getInstance() - .addInfo("CompareArea: Expanded sample level set to width " + - std::to_string(minimumWidth) + " to avoid undefined values.") - .print(); + VIENNACORE_LOG_INFO("CompareArea: Expanded sample level set to width " + + std::to_string(minimumWidth) + + " to avoid undefined values."); } // Set up dense cell iterators for both level sets diff --git a/include/viennals/lsCompareChamfer.hpp b/include/viennals/lsCompareChamfer.hpp index 202f2916..4c1d4b89 100644 --- a/include/viennals/lsCompareChamfer.hpp +++ b/include/viennals/lsCompareChamfer.hpp @@ -55,9 +55,7 @@ template class CompareChamfer { bool checkCompatibility() { if (levelSetTarget == nullptr || levelSetSample == nullptr) { - Logger::getInstance() - .addWarning("Missing level set in CompareChamfer.") - .print(); + VIENNACORE_LOG_WARNING("Missing level set in CompareChamfer."); return false; } @@ -66,10 +64,8 @@ template class CompareChamfer { const auto &gridSample = levelSetSample->getGrid(); if (gridTarget.getGridDelta() != gridSample.getGridDelta()) { - Logger::getInstance() - .addWarning("Grid delta mismatch in CompareChamfer. The grid " - "deltas of the two level sets must be equal.") - .print(); + VIENNACORE_LOG_WARNING("Grid delta mismatch in CompareChamfer. The grid " + "deltas of the two level sets must be equal."); return false; } @@ -136,19 +132,17 @@ template class CompareChamfer { if (levelSetTarget->getLevelSetWidth() < minimumWidth) { workingTarget = SmartPointer>::New(levelSetTarget); Expand(workingTarget, minimumWidth).apply(); - Logger::getInstance() - .addInfo("CompareChamfer: Expanded target level set to width " + - std::to_string(minimumWidth) + " for surface extraction.") - .print(); + VIENNACORE_LOG_INFO( + "CompareChamfer: Expanded target level set to width " + + std::to_string(minimumWidth) + " for surface extraction."); } if (levelSetSample->getLevelSetWidth() < minimumWidth) { workingSample = SmartPointer>::New(levelSetSample); Expand(workingSample, minimumWidth).apply(); - Logger::getInstance() - .addInfo("CompareChamfer: Expanded sample level set to width " + - std::to_string(minimumWidth) + " for surface extraction.") - .print(); + VIENNACORE_LOG_INFO( + "CompareChamfer: Expanded sample level set to width " + + std::to_string(minimumWidth) + " for surface extraction."); } // Extract surface meshes @@ -166,11 +160,9 @@ template class CompareChamfer { numSamplePoints = sampleNodes.size(); if (numTargetPoints == 0 || numSamplePoints == 0) { - Logger::getInstance() - .addWarning( - "CompareChamfer: One or both surfaces have no points. Cannot " - "compute Chamfer distance.") - .print(); + VIENNACORE_LOG_WARNING( + "CompareChamfer: One or both surfaces have no points. " + "Cannot compute Chamfer distance."); forwardDistance = std::numeric_limits::infinity(); backwardDistance = std::numeric_limits::infinity(); chamferDistance = std::numeric_limits::infinity(); diff --git a/include/viennals/lsCompareCriticalDimensions.hpp b/include/viennals/lsCompareCriticalDimensions.hpp index c5b839af..fd851b7a 100644 --- a/include/viennals/lsCompareCriticalDimensions.hpp +++ b/include/viennals/lsCompareCriticalDimensions.hpp @@ -67,9 +67,7 @@ template class CompareCriticalDimensions { bool checkInputs() { if (levelSetTarget == nullptr || levelSetSample == nullptr) { - Logger::getInstance() - .addWarning("Missing level set in SampleCriticalDimensions.") - .print(); + VIENNACORE_LOG_WARNING("Missing level set in CompareCriticalDimensions."); return false; } @@ -78,17 +76,15 @@ template class CompareCriticalDimensions { const auto &gridSample = levelSetSample->getGrid(); if (gridTarget.getGridDelta() != gridSample.getGridDelta()) { - Logger::getInstance() - .addWarning("Grid delta mismatch in CompareCriticalDimensions. The " - "grid deltas of the two level sets must be equal.") - .print(); + VIENNACORE_LOG_WARNING( + "Grid delta mismatch in CompareCriticalDimensions. The " + "grid deltas of the two level sets must be equal."); return false; } if (rangeSpecs.empty()) { - Logger::getInstance() - .addWarning("No ranges specified in CompareCriticalDimensions.") - .print(); + VIENNACORE_LOG_WARNING( + "No ranges specified in CompareCriticalDimensions."); return false; } diff --git a/include/viennals/lsCompareNarrowBand.hpp b/include/viennals/lsCompareNarrowBand.hpp index 8a902a13..c75f343f 100644 --- a/include/viennals/lsCompareNarrowBand.hpp +++ b/include/viennals/lsCompareNarrowBand.hpp @@ -87,24 +87,21 @@ template class CompareNarrowBand { // Expand the sample level set using lsExpand to a default width of 5 if (levelSetSample->getLevelSetWidth() < 5) { - Logger::getInstance() - .addWarning("Sample level set width is insufficient. Expanding it to " - "a width of 5.") - .print(); + VIENNACORE_LOG_WARNING( + "Sample level set width is insufficient. Expanding it to " + "a width of 5."); Expand(levelSetSample, 5).apply(); } // Check if target level set width is sufficient if (levelSetTarget->getLevelSetWidth() < levelSetSample->getLevelSetWidth() + 50) { - Logger::getInstance() - .addWarning( - "Target level set width is insufficient. It must exceed sample " - "width by least 50. \n" - " CORRECTION: The expansion was performed. \n" - "ALTERNATIVE: Alternatively, please expand the target yourself " - "using lsExpand before passing it to this function. \n") - .print(); + VIENNACORE_LOG_WARNING( + "Target level set width is insufficient. It must exceed sample " + "width by least 50. \n" + " CORRECTION: The expansion was performed. \n" + "ALTERNATIVE: Alternatively, please expand the target yourself " + "using lsExpand before passing it to this function. \n"); Expand(levelSetTarget, levelSetSample->getLevelSetWidth() + 50) .apply(); } diff --git a/include/viennals/lsCompareSparseField.hpp b/include/viennals/lsCompareSparseField.hpp index 0e8541e5..e4b70e9c 100644 --- a/include/viennals/lsCompareSparseField.hpp +++ b/include/viennals/lsCompareSparseField.hpp @@ -108,28 +108,24 @@ template class CompareSparseField { // Check if expanded level set width is sufficient if (levelSetExpanded->getLevelSetWidth() < expandedLevelSetWidth) { - Logger::getInstance() - .addWarning( - "Expanded level set width is insufficient. It must have a width " - "of at least " + - std::to_string(expandedLevelSetWidth) + ". \n" + - " CORRECTION: The expansion was performed. \n" - "ALTERNATIVE: Alternatively, please expand the expanded yourself " - "using lsExpand before passing it to this function. \n") - .print(); + VIENNACORE_LOG_WARNING( + "Expanded level set width is insufficient. It must have a width of " + "at least " + + std::to_string(expandedLevelSetWidth) + ". \n" + + " CORRECTION: The expansion was performed. \n" + "ALTERNATIVE: Alternatively, please expand the expanded yourself " + "using lsExpand before passing it to this function. \n"); Expand(levelSetExpanded, expandedLevelSetWidth).apply(); } // Reduce the iterated level set to a sparse field if necessary if (levelSetIterated->getLevelSetWidth() > 1) { - Logger::getInstance() - .addWarning( - "Iterated level set width is too large. It must be reduced to a " - "sparse field. \n" - " CORRECTION: The reduction was performed. \n" - "ALTERNATIVE: Alternatively, please reduce the iterated yourself " - "using lsReduce before passing it to this function. \n") - .print(); + VIENNACORE_LOG_WARNING( + "Iterated level set width is too large. It must be reduced to a " + "sparse field. \n" + " CORRECTION: The reduction was performed. \n" + "ALTERNATIVE: Alternatively, please reduce the iterated yourself " + "using lsReduce before passing it to this function. \n"); Reduce(levelSetIterated, 1).apply(); } @@ -203,10 +199,8 @@ template class CompareSparseField { /// automatically during the apply() call void setExpandedLevelSetWidth(int width) { if (width <= 0) { - Logger::getInstance() - .addWarning("Expansion width must be positive. Using default value " - "of 50.") - .print(); + VIENNACORE_LOG_WARNING( + "Expansion width must be positive. Using default value of 50."); expandedLevelSetWidth = 50; } else { expandedLevelSetWidth = width; diff --git a/include/viennals/lsCurvatureFormulas.hpp b/include/viennals/lsCurvatureFormulas.hpp index 773363df..f98e72c5 100644 --- a/include/viennals/lsCurvatureFormulas.hpp +++ b/include/viennals/lsCurvatureFormulas.hpp @@ -187,11 +187,9 @@ T gaussianCurvature(It &it, bool bigStencil = false) { else d = smallStencilFromIterator(it, gridDelta); if constexpr (D == 2) { - Logger::getInstance() - .addWarning( - "2D structures do not have a Gaussian Curvature, use " - "\"meanCurvature(IteratorType & neighborIterator)\" instead!") - .print(); + VIENNACORE_LOG_WARNING( + "2D structures do not have a Gaussian Curvature, use " + "\"meanCurvature(IteratorType & neighborIterator)\" instead!"); return 0.; } else { return gaussianCurvature3D(d); diff --git a/include/viennals/lsFromVolumeMesh.hpp b/include/viennals/lsFromVolumeMesh.hpp index 3714bde8..68b4d8d5 100644 --- a/include/viennals/lsFromVolumeMesh.hpp +++ b/include/viennals/lsFromVolumeMesh.hpp @@ -142,23 +142,19 @@ template class FromVolumeMesh { (it->first == currentSurfaceElement)) { if (Orientation(currentElementPoints)) { if (it->second.second != materialInts.back() + 1) { - Logger::getInstance() - .addWarning( - "Coinciding surface elements with same orientation in " - "Element: " + - std::to_string(i)) - .print(); + VIENNACORE_LOG_WARNING( + "Coinciding surface elements with same orientation in " + "Element: " + + std::to_string(i)); } it->second.second = (materialData == nullptr) ? 0 : (*materialData)[i]; } else { if (it->second.first != materialInts.back() + 1) { - Logger::getInstance() - .addWarning( - "Coinciding surface elements with same orientation in " - "Element: " + - std::to_string(i)) - .print(); + VIENNACORE_LOG_WARNING( + "Coinciding surface elements with same orientation in " + "Element: " + + std::to_string(i)); } it->second.first = (materialData == nullptr) ? 0 : (*materialData)[i]; diff --git a/include/viennals/lsGeometricAdvect.hpp b/include/viennals/lsGeometricAdvect.hpp index c2a5dcc5..60abb04e 100644 --- a/include/viennals/lsGeometricAdvect.hpp +++ b/include/viennals/lsGeometricAdvect.hpp @@ -236,9 +236,7 @@ template class GeometricAdvect { #ifndef NDEBUG // if in debug build { - Logger::getInstance() - .addDebug("GeomAdvect: Writing debug meshes") - .print(); + VIENNACORE_LOG_DEBUG("GeomAdvect: Writing debug meshes"); VTKWriter(surfaceMesh, FileFormatEnum::VTP, "DEBUG_lsGeomAdvectMesh_contributewoMask.vtp") .apply(); @@ -295,7 +293,7 @@ template class GeometricAdvect { { std::ostringstream oss; oss << "GeomAdvect: Min: " << min << ", Max: " << max << std::endl; - Logger::getInstance().addDebug(oss.str()).print(); + VIENNACORE_LOG_DEBUG(oss.str()); } #endif // set up multithreading @@ -499,7 +497,7 @@ template class GeometricAdvect { } #ifndef NDEBUG // if in debug build - Logger::getInstance().addDebug("GeomAdvect: Writing final mesh...").print(); + VIENNACORE_LOG_DEBUG("GeomAdvect: Writing final mesh..."); VTKWriter(mesh, FileFormatEnum::VTP, "DEBUG_lsGeomAdvectMesh_final.vtp") .apply(); #endif @@ -507,7 +505,7 @@ template class GeometricAdvect { FromMesh(levelSet, mesh).apply(); #ifndef NDEBUG // if in debug build - Logger::getInstance().addDebug("GeomAdvect: Writing final LS...").print(); + VIENNACORE_LOG_DEBUG("GeomAdvect: Writing final LS..."); ToMesh(levelSet, mesh).apply(); VTKWriter(mesh, FileFormatEnum::VTP, "DEBUG_lsGeomAdvectLS_final.vtp") .apply(); diff --git a/include/viennals/lsGeometricAdvectDistributions.hpp b/include/viennals/lsGeometricAdvectDistributions.hpp index 94b2f266..c6bab5e4 100644 --- a/include/viennals/lsGeometricAdvectDistributions.hpp +++ b/include/viennals/lsGeometricAdvectDistributions.hpp @@ -172,11 +172,10 @@ class BoxDistribution : public GeometricAdvectDistribution { for (unsigned i = 0; i < D; ++i) { if (std::abs(posExtent[i]) < gridDelta) { - Logger::getInstance() - .addWarning("One half-axis of BoxDistribution is smaller than " - "the grid Delta! This can lead to numerical errors " - "breaking the distribution!") - .print(); + VIENNACORE_LOG_WARNING( + "One half-axis of BoxDistribution is smaller than " + "the grid Delta! This can lead to numerical errors " + "breaking the distribution!"); } } } diff --git a/include/viennals/lsMakeGeometry.hpp b/include/viennals/lsMakeGeometry.hpp index af6f0f6e..50daf16a 100644 --- a/include/viennals/lsMakeGeometry.hpp +++ b/include/viennals/lsMakeGeometry.hpp @@ -116,9 +116,7 @@ template class MakeGeometry { /// a geometry from its convex hull. void setGeometry(SmartPointer> passedPointCloud) { if (passedPointCloud && passedPointCloud->empty()) { - Logger::getInstance() - .addWarning("Passing an empty point cloud to MakeGeometry. ") - .print(); + VIENNACORE_LOG_WARNING("Passing an empty point cloud to MakeGeometry. "); } pointCloud = passedPointCloud; geometry = GeometryEnum::CUSTOM; diff --git a/include/viennals/lsMarkVoidPoints.hpp b/include/viennals/lsMarkVoidPoints.hpp index a77c06ed..06fcdb60 100644 --- a/include/viennals/lsMarkVoidPoints.hpp +++ b/include/viennals/lsMarkVoidPoints.hpp @@ -136,10 +136,8 @@ template class MarkVoidPoints { break; default: - Logger::getInstance() - .addWarning("MarkVoidPoints: Invalid VoidTopSurfaceEnum set. " - "Using default values.") - .print(); + VIENNACORE_LOG_WARNING("MarkVoidPoints: Invalid VoidTopSurfaceEnum set. " + "Using default values."); reverseVoidDetection = false; detectLargestSurface = false; break; diff --git a/include/viennals/lsPointData.hpp b/include/viennals/lsPointData.hpp index 2bfe3802..1d603670 100644 --- a/include/viennals/lsPointData.hpp +++ b/include/viennals/lsPointData.hpp @@ -37,10 +37,8 @@ class PointData { if (index >= 0 && index < v.size()) return &(v[index]); else - Logger::getInstance() - .addWarning("PointData: Tried to access out of bounds index! " - "Returning nullptr instead.") - .print(); + VIENNACORE_LOG_WARNING("PointData: Tried to access out of bounds index! " + "Returning nullptr instead."); return nullptr; } @@ -145,12 +143,11 @@ class PointData { if (int i = getScalarDataIndex(searchLabel); i != -1) { return &(scalarData[i]); } - if (!noWarning) - Logger::getInstance() - .addWarning("PointData attempted to access scalar data labeled '" + - searchLabel + - "', which does not exist. Returning nullptr instead.") - .print(); + if (!noWarning) { + VIENNACORE_LOG_WARNING( + "PointData attempted to access scalar data labeled '" + searchLabel + + "', which does not exist. Returning nullptr instead."); + } return nullptr; } @@ -159,12 +156,11 @@ class PointData { if (int i = getScalarDataIndex(searchLabel); i != -1) { return &(scalarData[i]); } - if (!noWarning) - Logger::getInstance() - .addWarning("PointData attempted to access scalar data labeled '" + - searchLabel + - "', which does not exist. Returning nullptr instead.") - .print(); + if (!noWarning) { + VIENNACORE_LOG_WARNING( + "PointData attempted to access scalar data labeled '" + searchLabel + + "', which does not exist. Returning nullptr instead."); + } return nullptr; } @@ -206,12 +202,11 @@ class PointData { if (int i = getVectorDataIndex(searchLabel); i != -1) { return &(vectorData[i]); } - if (!noWarning) - Logger::getInstance() - .addWarning("PointData attempted to access vector data labeled '" + - searchLabel + - "', which does not exist. Returning nullptr instead.") - .print(); + if (!noWarning) { + VIENNACORE_LOG_WARNING( + "PointData attempted to access vector data labeled '" + searchLabel + + "', which does not exist. Returning nullptr instead."); + } return nullptr; } @@ -220,12 +215,11 @@ class PointData { if (int i = getVectorDataIndex(searchLabel); i != -1) { return &(vectorData[i]); } - if (!noWarning) - Logger::getInstance() - .addWarning("PointData attempted to access vector data labeled '" + - searchLabel + - "', which does not exist. Returning nullptr instead.") - .print(); + if (!noWarning) { + VIENNACORE_LOG_WARNING( + "PointData attempted to access vector data labeled '" + searchLabel + + "', which does not exist. Returning nullptr instead."); + } return nullptr; } diff --git a/include/viennals/lsReader.hpp b/include/viennals/lsReader.hpp index 77b843d5..a2a27c9e 100644 --- a/include/viennals/lsReader.hpp +++ b/include/viennals/lsReader.hpp @@ -48,9 +48,8 @@ template class Reader { return; } if (fileName.find(".lvst") != fileName.length() - 5) { - Logger::getInstance() - .addWarning("File name does not end in '.lvst', appending it.") - .print(); + VIENNACORE_LOG_WARNING( + "File name does not end in '.lvst', appending it."); fileName.append(".lvst"); } diff --git a/include/viennals/lsSlice.hpp b/include/viennals/lsSlice.hpp index c2d5811f..26911aa1 100644 --- a/include/viennals/lsSlice.hpp +++ b/include/viennals/lsSlice.hpp @@ -89,11 +89,9 @@ template class Slice { bool autoCreateSlice = false; if (sliceLevelSet == nullptr) { autoCreateSlice = true; - Logger::getInstance() - .addDebug("No slice level-set passed to Slice. Auto-created slice " - "level-set with bounds derived from " - "source domain") - .print(); + VIENNACORE_LOG_DEBUG("No slice level-set passed to Slice. Auto-creating " + "slice level-set with bounds derived from source " + "domain."); } const auto &sourceGrid = sourceLevelSet->getGrid(); @@ -106,12 +104,10 @@ template class Slice { if (std::fmod(slicePosition, gridDelta) != 0) { // If not, change slice position to the nearest grid point slicePosition = std::round(slicePosition / gridDelta) * gridDelta; - Logger::getInstance() - .addWarning("Slice position is not divisible by grid delta in " - "Slice. Adjusting slice position to the nearest " - "multiple of grid delta: " + - std::to_string(slicePosition)) - .print(); + VIENNACORE_LOG_WARNING("Slice position is not divisible by grid delta in " + "Slice. Adjusting slice position to the nearest " + "multiple of grid delta: " + + std::to_string(slicePosition)); } // slice index @@ -154,7 +150,7 @@ template class Slice { // Insert the extracted points into the slice domain, issue a warning if // there are no points to insert if (pointData.empty()) { - Logger::getInstance().addWarning("No points extracted in Slice").print(); + VIENNACORE_LOG_WARNING("No points extracted in Slice"); } else { if (autoCreateSlice) { // Create slice domain with bounds and BCs derived from source domain diff --git a/include/viennals/lsStencilLocalLaxFriedrichsScalar.hpp b/include/viennals/lsStencilLocalLaxFriedrichsScalar.hpp index ada3d635..911701a8 100644 --- a/include/viennals/lsStencilLocalLaxFriedrichsScalar.hpp +++ b/include/viennals/lsStencilLocalLaxFriedrichsScalar.hpp @@ -462,11 +462,9 @@ void PrepareStencilLocalLaxFriedrichs( std::vector>> &levelSets, std::vector isDepo) { if (isDepo.size() < levelSets.size()) { - Logger::getInstance() - .addWarning( - "PrepareStencilLocalLaxFriedrichs: isDepo does not have enough " - "elements. Assuming all higher layers are not depo layers.") - .print(); + VIENNACORE_LOG_WARNING( + "PrepareStencilLocalLaxFriedrichs: isDepo does not have enough " + "elements. Assuming all higher layers are not depo layers."); } // always resize, so it has the correct number of elements isDepo.resize(levelSets.size(), false); diff --git a/include/viennals/lsToDiskMesh.hpp b/include/viennals/lsToDiskMesh.hpp index 2bc33c10..e2e67d49 100644 --- a/include/viennals/lsToDiskMesh.hpp +++ b/include/viennals/lsToDiskMesh.hpp @@ -96,9 +96,7 @@ template class ToDiskMesh { return; } if (buildTranslator && translator == nullptr) { - Logger::getInstance() - .addWarning("No translator was passed to ToDiskMesh.") - .print(); + VIENNACORE_LOG_WARNING("No translator was passed to ToDiskMesh."); } mesh->clear(); @@ -229,9 +227,8 @@ template class ToDiskMesh { auto index = pointData.getVectorDataIndex( CalculateNormalVectors::normalVectorsLabel); if (index < 0) { - Logger::getInstance() - .addWarning("ToDiskMesh: Could not find normal vector data.") - .print(); + VIENNACORE_LOG_WARNING( + "ToDiskMesh: Could not find normal vector data."); } else { pointData.eraseVectorData(index); } diff --git a/include/viennals/lsToMesh.hpp b/include/viennals/lsToMesh.hpp index 1c907a4b..2edc24a3 100644 --- a/include/viennals/lsToMesh.hpp +++ b/include/viennals/lsToMesh.hpp @@ -54,7 +54,7 @@ template class ToMesh { return; } if (mesh == nullptr) { - Logger::getInstance().addError("No mesh was passed to ToMesh.").print(); + VIENNACORE_LOG_ERROR("No mesh was passed to ToMesh."); return; } @@ -62,9 +62,8 @@ template class ToMesh { // check if level set is empty if (levelSet->getNumberOfPoints() == 0) { - Logger::getInstance() - .addWarning("ToMesh: Level set is empty. No mesh will be created.") - .print(); + VIENNACORE_LOG_WARNING( + "ToMesh: Level set is empty. No mesh will be created."); return; } @@ -133,10 +132,8 @@ template class ToMesh { const auto ¤tData = *dataPointer; scalarData[i].push_back(currentData[it.getPointId()]); } else { - Logger::getInstance() - .addWarning("ToMesh: Tried to access out of bounds scalarData! " - "Ignoring.") - .print(); + VIENNACORE_LOG_WARNING( + "ToMesh: Tried to access out of bounds scalarData! Ignoring."); break; } } @@ -147,10 +144,8 @@ template class ToMesh { const auto ¤tData = *dataPointer; vectorData[i].push_back(currentData[it.getPointId()]); } else { - Logger::getInstance() - .addWarning("ToMesh: Tried to access out of bounds vectorData! " - "Ignoring.") - .print(); + VIENNACORE_LOG_WARNING( + "ToMesh: Tried to access out of bounds vectorData! Ignoring."); break; } } diff --git a/include/viennals/lsToSurfaceMesh.hpp b/include/viennals/lsToSurfaceMesh.hpp index 13f1a4c1..61fbb7ce 100644 --- a/include/viennals/lsToSurfaceMesh.hpp +++ b/include/viennals/lsToSurfaceMesh.hpp @@ -57,10 +57,8 @@ template class ToSurfaceMesh { } if (levelSet->getNumberOfPoints() == 0) { - Logger::getInstance() - .addWarning( - "ToSurfaceMesh: Level set is empty. No mesh will be created.") - .print(); + VIENNACORE_LOG_WARNING( + "ToSurfaceMesh: Level set is empty. No mesh will be created."); return; } @@ -72,10 +70,8 @@ template class ToSurfaceMesh { // test if level set function consists of at least 2 layers of // defined grid points if (levelSet->getLevelSetWidth() < 2) { - Logger::getInstance() - .addWarning("Levelset is less than 2 layers wide. Expanding levelset " - "to 2 layers.") - .print(); + VIENNACORE_LOG_WARNING("Levelset is less than 2 layers wide. Expanding " + "levelset to 2 layers."); Expand(levelSet, 2).apply(); } diff --git a/include/viennals/lsVTKReader.hpp b/include/viennals/lsVTKReader.hpp index 3929df7f..c000a82c 100644 --- a/include/viennals/lsVTKReader.hpp +++ b/include/viennals/lsVTKReader.hpp @@ -589,14 +589,14 @@ template class VTKReader { std::ostringstream oss; oss << "VTK Cell type " << cell_type << " is not supported. Cell ignored..." << std::endl; - Logger::getInstance().addWarning(oss.str()).print(); + VIENNACORE_LOG_WARNING(oss.str()); } } else { std::ostringstream oss; oss << "INVALID CELL TYPE! Expected number of nodes: " << number_nodes << ", Found number of nodes: " << elems_fake << "; Ignoring element..."; - Logger::getInstance().addError(oss.str()); + VIENNACORE_LOG_ERROR(oss.str()); // ignore rest of lines f.ignore(std::numeric_limits::max(), '\n'); } @@ -635,9 +635,7 @@ template class VTKReader { // consume one line, which defines the lookup table std::getline(f, temp); if (temp != "LOOKUP_TABLE default") { - Logger::getInstance() - .addWarning("Wrong lookup table for VTKLegacy: " + temp) - .print(); + VIENNACORE_LOG_WARNING("Wrong lookup table for VTKLegacy: " + temp); } // now read scalar values diff --git a/include/viennals/lsVTKWriter.hpp b/include/viennals/lsVTKWriter.hpp index f704c99d..7f37ae1b 100644 --- a/include/viennals/lsVTKWriter.hpp +++ b/include/viennals/lsVTKWriter.hpp @@ -143,13 +143,11 @@ template class VTKWriter { } // check filename if (fileName.empty()) { - Logger::getInstance() - .addError("No file name specified for VTKWriter.") - .print(); + VIENNACORE_LOG_ERROR("No file name specified for VTKWriter."); return; } if (mesh->nodes.empty()) { - Logger::getInstance().addWarning("Writing empty mesh.").print(); + VIENNACORE_LOG_WARNING("Writing empty mesh."); return; } @@ -190,17 +188,14 @@ template class VTKWriter { #else case FileFormatEnum::VTP: case FileFormatEnum::VTU: - Logger::getInstance() - .addWarning("VTKWriter was built without VTK support. Falling back " - "to VTK_LEGACY.") - .print(); + VIENNACORE_LOG_WARNING( + "VTKWriter was built without VTK support. Falling back " + "to VTK_LEGACY."); writeVTKLegacy(fileName); break; #endif default: - Logger::getInstance() - .addError("No valid file format set for VTKWriter.") - .print(); + VIENNACORE_LOG_ERROR("No valid file format set for VTKWriter."); } } @@ -485,10 +480,9 @@ template class VTKWriter { // WRITE POINT DATA if (mesh->pointData.getScalarDataSize() || mesh->pointData.getVectorDataSize()) { - Logger::getInstance() - .addWarning("Point data output not supported for legacy VTK output. " - "Point data is ignored.") - .print(); + VIENNACORE_LOG_WARNING( + "Point data output not supported for legacy VTK output. " + "Point data is ignored."); } // WRITE SCALAR DATA diff --git a/include/viennals/lsWriteVisualizationMesh.hpp b/include/viennals/lsWriteVisualizationMesh.hpp index 51be6816..ee838537 100644 --- a/include/viennals/lsWriteVisualizationMesh.hpp +++ b/include/viennals/lsWriteVisualizationMesh.hpp @@ -491,11 +491,9 @@ template class WriteVisualizationMesh { // check if level sets have enough layers for (unsigned i = 0; i < levelSets.size(); ++i) { if (levelSets[i]->getLevelSetWidth() < 2) { - Logger::getInstance() - .addWarning( - "WriteVisualizationMesh: Level Set " + std::to_string(i) + - " should have a width greater than 1! Conversion might fail!") - .print(); + VIENNACORE_LOG_WARNING( + "WriteVisualizationMesh: Level Set " + std::to_string(i) + + " should have a width greater than 1! Conversion might fail!"); } } diff --git a/include/viennals/lsWriter.hpp b/include/viennals/lsWriter.hpp index c55e148a..eb1a0f51 100644 --- a/include/viennals/lsWriter.hpp +++ b/include/viennals/lsWriter.hpp @@ -42,16 +42,13 @@ template class Writer { } // check filename if (fileName.empty()) { - Logger::getInstance() - .addError("No file name specified for Writer.") - .print(); + VIENNACORE_LOG_ERROR("No file name specified for Writer."); return; } if (fileName.find(".lvst") != fileName.length() - 5) { - Logger::getInstance() - .addWarning("File name does not end in '.lvst', appending it.") - .print(); + VIENNACORE_LOG_WARNING( + "File name does not end in '.lvst', appending it."); fileName.append(".lvst"); } From 1e43bb5155305e7294fd12c6e841423b4631bc0e Mon Sep 17 00:00:00 2001 From: filipovic Date: Tue, 9 Dec 2025 12:56:41 +0100 Subject: [PATCH 03/57] Reverted material interface behavior and added removeLastMaterial functionality --- include/viennals/lsAdvect.hpp | 36 ++++++------------------------ include/viennals/lsMaterialMap.hpp | 18 +++++++++++++++ 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index 0a0a117f..5b891e1f 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -513,9 +513,9 @@ template class Advect { } } + T velocity = gradNDissipation.first - gradNDissipation.second; // Case 1: Growth / Deposition (Velocity > 0) // Limit the time step based on the standard CFL condition. - T velocity = gradNDissipation.first - gradNDissipation.second; if (velocity > 0.) { maxStepTime += cfl / velocity; tempRates.push_back(std::make_pair(gradNDissipation, @@ -545,20 +545,7 @@ template class Advect { } T difference = std::abs(valueBelow - value); - // Sub-case 3a: Micro-layer handling (Numerical Stability) - // If the layer is vanishingly thin (noise level), consume it - // immediately and continue to the next layer without limiting the - // global time step. This prevents the simulation from stalling due - // to near-zero time steps. - if (difference < 1e-4) { - maxStepTime -= difference / velocity; - tempRates.push_back(std::make_pair(gradNDissipation, valueBelow)); - cfl -= difference; - value = valueBelow; - continue; - } - - // Sub-case 3b: Thick Layer (Standard CFL) + // Sub-case 3a: Thick Layer (Standard CFL) // The material is thick enough to support a full CFL time step. if (difference >= cfl) { maxStepTime -= cfl / velocity; @@ -566,25 +553,16 @@ template class Advect { gradNDissipation, std::numeric_limits::max())); break; - // Sub-case 3c: Interface Hit (Thin Layer) - // The material is thinner than the CFL distance. - // We must STOP exactly at the interface. + // Sub-case 3b: Interface Hit (Thin Layer) + // The material is thinner than the CFL distance. + // We will need two velocities for the two materials. } else { // Calculate the time required to reach the material boundary. maxStepTime -= difference / velocity; tempRates.push_back(std::make_pair(gradNDissipation, valueBelow)); - tempRates.push_back(std::make_pair( - gradNDissipation, std::numeric_limits::max())); - - VIENNACORE_LOG_DEBUG( - "Global time step limited by layer thickness!" - "\nInstead of " + - std::to_string(-cfl / velocity) + " it is set to " + - std::to_string(maxStepTime)); - // We stop processing layers below. By breaking here, we force the - // simulation to pause exactly when the top layer is consumed. - break; + cfl -= difference; + value = valueBelow; } } } diff --git a/include/viennals/lsMaterialMap.hpp b/include/viennals/lsMaterialMap.hpp index b69ced30..2b81cfda 100644 --- a/include/viennals/lsMaterialMap.hpp +++ b/include/viennals/lsMaterialMap.hpp @@ -2,6 +2,7 @@ #include #include +#include namespace viennals { @@ -25,6 +26,23 @@ class MaterialMap { materials.insert(passedMaterialId); } + void removeLastMaterial() { + if (materialMap.empty()) { + return; + } + + int idToRemove = materialMap.back(); + materialMap.pop_back(); + + bool isUnique = std::find(materialMap.begin(), + materialMap.end(), + idToRemove) == materialMap.end(); + + if (isUnique) { + materials.erase(idToRemove); + } + } + void setMaterialId(const std::size_t index, const int materialId) { if (index >= materialMap.size()) { materialMap.resize(index + 1, -1); // Initialize new elements with -1 From ee652580d01a445352067ad54de9c7dd1e4d4cb2 Mon Sep 17 00:00:00 2001 From: filipovic Date: Tue, 9 Dec 2025 13:51:57 +0100 Subject: [PATCH 04/57] format --- include/viennals/lsAdvect.hpp | 6 +++--- include/viennals/lsBooleanOperation.hpp | 2 +- include/viennals/lsMaterialMap.hpp | 5 ++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index 5b891e1f..00c865a7 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -553,9 +553,9 @@ template class Advect { gradNDissipation, std::numeric_limits::max())); break; - // Sub-case 3b: Interface Hit (Thin Layer) - // The material is thinner than the CFL distance. - // We will need two velocities for the two materials. + // Sub-case 3b: Interface Hit (Thin Layer) + // The material is thinner than the CFL distance. + // We will need two velocities for the two materials. } else { // Calculate the time required to reach the material boundary. maxStepTime -= difference / velocity; diff --git a/include/viennals/lsBooleanOperation.hpp b/include/viennals/lsBooleanOperation.hpp index d77ffa41..34f584db 100644 --- a/include/viennals/lsBooleanOperation.hpp +++ b/include/viennals/lsBooleanOperation.hpp @@ -281,7 +281,7 @@ template class BooleanOperation { SmartPointer> passedlsDomainB, BooleanOperationEnum passedOperation = BooleanOperationEnum::INTERSECT) : levelSetA(passedlsDomainA), levelSetB(passedlsDomainB), - operation(passedOperation) {}; + operation(passedOperation){}; /// Set which level set to perform the boolean operation on. void setLevelSet(SmartPointer> passedlsDomain) { diff --git a/include/viennals/lsMaterialMap.hpp b/include/viennals/lsMaterialMap.hpp index 2b81cfda..8adb907a 100644 --- a/include/viennals/lsMaterialMap.hpp +++ b/include/viennals/lsMaterialMap.hpp @@ -1,8 +1,8 @@ #pragma once +#include #include #include -#include namespace viennals { @@ -34,8 +34,7 @@ class MaterialMap { int idToRemove = materialMap.back(); materialMap.pop_back(); - bool isUnique = std::find(materialMap.begin(), - materialMap.end(), + bool isUnique = std::find(materialMap.begin(), materialMap.end(), idToRemove) == materialMap.end(); if (isUnique) { From a1dc1dae700f4f79bb3f09ff140bcb5fb2896f35 Mon Sep 17 00:00:00 2001 From: filipovic Date: Wed, 10 Dec 2025 01:03:09 +0100 Subject: [PATCH 05/57] Fix material interface stability with adaptive sub-stepping of thin layers (soft landing) --- include/viennals/lsAdvect.hpp | 47 ++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index 00c865a7..8642a90e 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -514,55 +514,62 @@ template class Advect { } T velocity = gradNDissipation.first - gradNDissipation.second; - // Case 1: Growth / Deposition (Velocity > 0) - // Limit the time step based on the standard CFL condition. if (velocity > 0.) { + // Case 1: Growth / Deposition (Velocity > 0) + // Limit the time step based on the standard CFL condition. maxStepTime += cfl / velocity; tempRates.push_back(std::make_pair(gradNDissipation, -std::numeric_limits::max())); break; + } else if (velocity == 0.) { // Case 2: Static (Velocity == 0) // No time step limit imposed by this point. - } else if (velocity == 0.) { maxStepTime = std::numeric_limits::max(); tempRates.push_back(std::make_pair(gradNDissipation, std::numeric_limits::max())); break; - // Case 3: Etching (Velocity < 0) } else { - // Retrieve the LS value of the material interface directly below - // the current one. This block is moved into this else since it is - // only required for etching. + // Case 3: Etching (Velocity < 0) + // Retrieve the interface location of the underlying material. T valueBelow; if (currentLevelSetId > 0) { iterators[currentLevelSetId - 1].goToIndicesSequential( it.getStartIndices()); valueBelow = iterators[currentLevelSetId - 1].getValue(); } else { - // If there is no material below, the limit is effectively - // infinite distance. valueBelow = std::numeric_limits::max(); } + // Calculate the top material thickness T difference = std::abs(valueBelow - value); - // Sub-case 3a: Thick Layer (Standard CFL) - // The material is thick enough to support a full CFL time step. if (difference >= cfl) { + // Sub-case 3a: Standard Advection + // Far from interface: Use full CFL time step. maxStepTime -= cfl / velocity; tempRates.push_back(std::make_pair( gradNDissipation, std::numeric_limits::max())); break; - // Sub-case 3b: Interface Hit (Thin Layer) - // The material is thinner than the CFL distance. - // We will need two velocities for the two materials. } else { - // Calculate the time required to reach the material boundary. - maxStepTime -= difference / velocity; - tempRates.push_back(std::make_pair(gradNDissipation, valueBelow)); - - cfl -= difference; - value = valueBelow; + // Sub-case 3b: Interface Interaction + if (difference > 0.05 * cfl) { + // Adaptive Sub-stepping: + // Approaching boundary: Force small steps (5% CFL) to gather + // flux statistics and prevent numerical overshoot ("Soft + // Landing"). + maxStepTime -= 0.05 * cfl / velocity; + tempRates.push_back(std::make_pair( + gradNDissipation, std::numeric_limits::min())); + } else { + // Terminal Step: + // Within tolerance: Snap to boundary, consume budget, and + // switch material. + tempRates.push_back( + std::make_pair(gradNDissipation, valueBelow)); + cfl -= difference; + value = valueBelow; + maxStepTime -= difference / velocity; + } } } } From 6112431f5070280de7de80100a907dffae5a9587 Mon Sep 17 00:00:00 2001 From: filipovic Date: Wed, 10 Dec 2025 02:59:20 +0100 Subject: [PATCH 06/57] Added WENO integration scheme --- examples/Deposition/Deposition.cpp | 3 +- examples/SquareEtch/SquareEtch.cpp | 5 +- include/viennals/lsAdvect.hpp | 12 +- include/viennals/lsWENO5.hpp | 176 +++++++++++++++++++++++++++++ python/pyWrap.cpp | 2 + python/viennals/_core.pyi | 3 + tests/Advection/Advection.cpp | 17 +-- 7 files changed, 206 insertions(+), 12 deletions(-) create mode 100644 include/viennals/lsWENO5.hpp diff --git a/examples/Deposition/Deposition.cpp b/examples/Deposition/Deposition.cpp index b34101b5..7b087f89 100644 --- a/examples/Deposition/Deposition.cpp +++ b/examples/Deposition/Deposition.cpp @@ -110,10 +110,11 @@ int main() { // the other could be taken as a mask layer for advection advectionKernel.insertNextLevelSet(substrate); advectionKernel.insertNextLevelSet(newLayer); - advectionKernel.setVelocityField(velocities); // advectionKernel.setAdvectionTime(4.); unsigned counter = 1; + advectionKernel.setIntegrationScheme( + ls::IntegrationSchemeEnum::WENO_5TH_ORDER); for (NumericType time = 0; time < 4.; time += advectionKernel.getAdvectedTime()) { advectionKernel.apply(); diff --git a/examples/SquareEtch/SquareEtch.cpp b/examples/SquareEtch/SquareEtch.cpp index fa2d044b..a63d8780 100644 --- a/examples/SquareEtch/SquareEtch.cpp +++ b/examples/SquareEtch/SquareEtch.cpp @@ -80,7 +80,7 @@ class analyticalField : public ls::VelocityField { int main() { constexpr int D = 2; - omp_set_num_threads(1); + omp_set_num_threads(8); // Change this to use the analytical velocity field const bool useAnalyticalVelocity = false; @@ -173,7 +173,8 @@ int main() { // Analytical velocity fields and dissipation coefficients // can only be used with this integration scheme advectionKernel.setIntegrationScheme( - ls::IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER); + // ls::IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER); + ls::IntegrationSchemeEnum::WENO_5TH_ORDER); } else { // for numerical velocities, just use the default // integration scheme, which is not accurate for certain diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index 8642a90e..45dbad76 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -23,6 +23,7 @@ #include #include #include +#include // Velocity accessor #include @@ -49,7 +50,8 @@ enum struct IntegrationSchemeEnum : unsigned { LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER = 6, LOCAL_LAX_FRIEDRICHS_1ST_ORDER = 7, LOCAL_LAX_FRIEDRICHS_2ND_ORDER = 8, - STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER = 9 + STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER = 9, + WENO_5TH_ORDER = 10 }; /// This class is used to advance level sets over time. @@ -859,6 +861,9 @@ template class Advect { IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { lsInternal::StencilLocalLaxFriedrichsScalar::prepareLS( levelSets.back()); + } else if (integrationScheme == IntegrationSchemeEnum::WENO_5TH_ORDER) { + // WENO5 requires a stencil radius of 3 (template parameter 3) + lsInternal::WENO5::prepareLS(levelSets.back()); } else { VIENNACORE_LOG_ERROR("Advect: Integration scheme not found."); } @@ -941,6 +946,11 @@ template class Advect { auto is = lsInternal::StencilLocalLaxFriedrichsScalar( levelSets.back(), velocities, dissipationAlpha); currentTimeStep = integrateTime(is, maxTimeStep); + } else if (integrationScheme == IntegrationSchemeEnum::WENO_5TH_ORDER) { + // Instantiate WENO5 with order 3 (neighbors +/- 3) + auto is = lsInternal::WENO5(levelSets.back(), velocities, + calculateNormalVectors); + currentTimeStep = integrateTime(is, maxTimeStep); } else { VIENNACORE_LOG_ERROR("Advect: Integration scheme not found."); currentTimeStep = -1.; diff --git a/include/viennals/lsWENO5.hpp b/include/viennals/lsWENO5.hpp new file mode 100644 index 00000000..5e6d5a6b --- /dev/null +++ b/include/viennals/lsWENO5.hpp @@ -0,0 +1,176 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +// Include your existing math library +#include + +namespace lsInternal { + +using namespace viennacore; + +/// Fifth-order Weighted Essentially Non-Oscillatory (WENO5) scheme. +/// This kernel acts as the grid-interface for the mathematical logic +/// defined in lsFiniteDifferences.hpp. +template class WENO5 { + SmartPointer> levelSet; + SmartPointer> velocities; + + // Iterator depth: WENO5 needs 3 neighbors on each side. + viennahrle::SparseStarIterator, order> + neighborIterator; + + const bool calculateNormalVectors = true; + + // Use the existing math engine with WENO5 scheme + using MathScheme = FiniteDifferences; + + static T pow2(const T &value) { return value * value; } + +public: + static void prepareLS(SmartPointer> passedlsDomain) { + // WENO5 uses a 7-point stencil (radius 3). + // Ensure we expand enough layers to access neighbors at +/- 3. + static_assert(order >= 3, "WENO5 requires an iterator order of at least 3"); + viennals::Expand(passedlsDomain, 2 * order + 1).apply(); + } + + WENO5(SmartPointer> passedlsDomain, + SmartPointer> vel, bool calcNormal = true) + : levelSet(passedlsDomain), velocities(vel), + neighborIterator(levelSet->getDomain()), + calculateNormalVectors(calcNormal) {} + + std::pair operator()(const viennahrle::Index &indices, + int material) { + auto &grid = levelSet->getGrid(); + double gridDelta = grid.getGridDelta(); + + VectorType coordinate{0., 0., 0.}; + for (unsigned i = 0; i < D; ++i) { + coordinate[i] = indices[i] * gridDelta; + } + + // Move iterator to the current grid point + neighborIterator.goToIndicesSequential(indices); + + T gradPosTotal = 0; + T gradNegTotal = 0; + + // --- OPTIMIZATION: Store derivatives to avoid re-calculation --- + T wenoGradMinus[D]; // Approximates derivative from left (phi_x^-) + T wenoGradPlus[D]; // Approximates derivative from right (phi_x^+) + + // Array to hold the stencil values: [x-3, x-2, x-1, Center, x+1, x+2, x+3] + T stencil[7]; + + for (int i = 0; i < D; i++) { + // 1. GATHER STENCIL + // We map the SparseStarIterator (which uses encoded directions) + // to the flat array expected by FiniteDifferences. + + // Center (Index 3 in the size-7 array) + stencil[3] = neighborIterator.getCenter().getValue(); + + // Neighbors +/- 1 + stencil[4] = neighborIterator.getNeighbor(i).getValue(); // +1 + stencil[2] = neighborIterator.getNeighbor(i + D).getValue(); // -1 + + // Neighbors +/- 2 + // Note: SparseStarIterator encodes higher distances sequentially + stencil[5] = neighborIterator.getNeighbor(D * 2 + i).getValue(); // +2 + stencil[1] = neighborIterator.getNeighbor(D * 2 + D + i).getValue(); // -2 + + // Neighbors +/- 3 + stencil[6] = neighborIterator.getNeighbor(D * 4 + i).getValue(); // +3 + stencil[0] = neighborIterator.getNeighbor(D * 4 + D + i).getValue(); // -3 + + // 2. COMPUTE DERIVATIVES + // Delegate the math to your existing library and store results + wenoGradMinus[i] = MathScheme::differenceNegative(stencil, gridDelta); + wenoGradPlus[i] = MathScheme::differencePositive(stencil, gridDelta); + + // 3. GODUNOV FLUX PREPARATION + // We accumulate the gradient magnitudes for the scalar velocity case. + // This is part of the standard level set equation logic (Osher-Sethian). + + // For Positive Scalar Velocity (Deposition): Use upwind selection + gradPosTotal += pow2(std::max(wenoGradMinus[i], T(0))) + + pow2(std::min(wenoGradPlus[i], T(0))); + + // For Negative Scalar Velocity (Etching): Use upwind selection + gradNegTotal += pow2(std::min(wenoGradMinus[i], T(0))) + + pow2(std::max(wenoGradPlus[i], T(0))); + } + + T vel_grad = 0.; + + // --- Standard Normal Vector Calculation (for velocity lookup) --- + Vec3D normalVector = {}; + if (calculateNormalVectors) { + T denominator = 0; + for (int i = 0; i < D; i++) { + // Simple Central Difference for the normal vector is sufficient + // and robust for velocity direction lookup. + T pos = neighborIterator.getNeighbor(i).getValue(); + T neg = neighborIterator.getNeighbor(i + D).getValue(); + normalVector[i] = (pos - neg) * 0.5; + denominator += normalVector[i] * normalVector[i]; + } + if (denominator > 0) { + denominator = 1. / std::sqrt(denominator); + for (unsigned i = 0; i < D; ++i) { + normalVector[i] *= denominator; + } + } + } + + // --- Retrieve Velocity --- + double scalarVelocity = velocities->getScalarVelocity( + coordinate, material, normalVector, + neighborIterator.getCenter().getPointId()); + Vec3D vectorVelocity = velocities->getVectorVelocity( + coordinate, material, normalVector, + neighborIterator.getCenter().getPointId()); + + // --- Apply Velocities --- + + // Scalar term (Etching/Deposition) + if (scalarVelocity > 0) { + vel_grad += std::sqrt(gradPosTotal) * scalarVelocity; + } else { + vel_grad += std::sqrt(gradNegTotal) * scalarVelocity; + } + + // Vector term (Advection) + // Here we REUSE the derivatives stored in wenoGradMinus/Plus. + // This is the optimization compared to recalculating. + for (int w = 0; w < D; w++) { + if (vectorVelocity[w] > 0.) { + vel_grad += vectorVelocity[w] * wenoGradMinus[w]; + } else { + vel_grad += vectorVelocity[w] * wenoGradPlus[w]; + } + } + + // WENO is an upwind scheme, so explicit dissipation is 0. + return {vel_grad, 0.}; + } + + void reduceTimeStepHamiltonJacobi(double &MaxTimeStep, + double gridDelta) const { + // --- STABILITY IMPROVEMENT --- + // High-order schemes like WENO5 combined with simple time integration (like + // the likely Forward Euler used in Advect) can be less stable at CFL=0.5. + // We enforce a safety factor here to ensure robustness. + constexpr double wenoSafetyFactor = 0.5; + MaxTimeStep *= wenoSafetyFactor; + } +}; + +} // namespace lsInternal \ No newline at end of file diff --git a/python/pyWrap.cpp b/python/pyWrap.cpp index 99ebabaf..2e46ecb6 100644 --- a/python/pyWrap.cpp +++ b/python/pyWrap.cpp @@ -103,6 +103,8 @@ PYBIND11_MODULE(VIENNALS_MODULE_NAME, module) { IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER) .value("STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER", IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) + .value("WENO_5TH_ORDER", + IntegrationSchemeEnum::WENO_5TH_ORDER) .finalize(); py::native_enum(module, "BooleanOperationEnum", diff --git a/python/viennals/_core.pyi b/python/viennals/_core.pyi index 6b851710..ce81c0f6 100644 --- a/python/viennals/_core.pyi +++ b/python/viennals/_core.pyi @@ -218,6 +218,9 @@ class IntegrationSchemeEnum(enum.IntEnum): STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER: typing.ClassVar[ IntegrationSchemeEnum ] # value = + WENO_5TH_ORDER: typing.ClassVar[ + IntegrationSchemeEnum + ] # value = @classmethod def __new__(cls, value): ... def __format__(self, format_spec): diff --git a/tests/Advection/Advection.cpp b/tests/Advection/Advection.cpp index fe462318..f749a776 100644 --- a/tests/Advection/Advection.cpp +++ b/tests/Advection/Advection.cpp @@ -50,14 +50,15 @@ int main() { double gridDelta = 0.6999999; std::vector integrationSchemes = { - // ls::IntegrationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER, - // ls::IntegrationSchemeEnum::ENGQUIST_OSHER_2ND_ORDER, - ls::IntegrationSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER}; - // ls::IntegrationSchemeEnum::LAX_FRIEDRICHS_2ND_ORDER, - // ls::IntegrationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER, - // ls::IntegrationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER, - // ls::IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_1ST_ORDER, - // ls::IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER}; + ls::IntegrationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER, + ls::IntegrationSchemeEnum::ENGQUIST_OSHER_2ND_ORDER, + ls::IntegrationSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER, + ls::IntegrationSchemeEnum::LAX_FRIEDRICHS_2ND_ORDER, + ls::IntegrationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER, + ls::IntegrationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER, + ls::IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_1ST_ORDER, + ls::IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER, + ls::IntegrationSchemeEnum::WENO_5TH_ORDER}; for (auto integrationScheme : integrationSchemes) { auto sphere1 = ls::Domain::New(gridDelta); From 347fe7d1145790aef5dd7c9aa90294b734053775 Mon Sep 17 00:00:00 2001 From: reiter Date: Wed, 10 Dec 2025 16:15:48 +0100 Subject: [PATCH 07/57] Add option to enable adaptive time stepping during advection --- include/viennals/lsAdvect.hpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index 45dbad76..328b5a0f 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -83,6 +83,7 @@ template class Advect { bool saveAdvectionVelocities = false; bool updatePointData = true; bool checkDissipation = true; + bool adaptiveTimeStepping = false; static constexpr double wrappingLayerEpsilon = 1e-4; // this vector will hold the maximum time step for each point and the @@ -443,6 +444,7 @@ template class Advect { } } const bool ignoreVoidPoints = ignoreVoids; + const bool useAdaptiveTimeStepping = adaptiveTimeStepping; if (!storedRates.empty()) { VIENNACORE_LOG_WARNING("Advect: Overwriting previously stored rates."); @@ -554,7 +556,7 @@ template class Advect { } else { // Sub-case 3b: Interface Interaction - if (difference > 0.05 * cfl) { + if (useAdaptiveTimeStepping && difference > 0.05 * cfl) { // Adaptive Sub-stepping: // Approaching boundary: Force small steps (5% CFL) to gather // flux statistics and prevent numerical overshoot ("Soft @@ -781,6 +783,11 @@ template class Advect { /// be advected. All others values are not changed. void setIgnoreVoids(bool iV) { ignoreVoids = iV; } + /// Set whether adaptive time stepping should be used + /// when approaching material boundaries during etching. + /// Defaults to false. + void setAdaptiveTimeStepping(bool aTS) { adaptiveTimeStepping = aTS; } + /// Set whether the velocities applied to each point should be saved in /// the level set for debug purposes. void setSaveAdvectionVelocities(bool sAV) { saveAdvectionVelocities = sAV; } From 2e305b435252dbb259cf502f8f026b98b8f00187 Mon Sep 17 00:00:00 2001 From: Tobias Reiter Date: Fri, 12 Dec 2025 09:29:27 +0100 Subject: [PATCH 08/57] Add python bindings --- python/pyWrap.hpp | 5 +++++ python/viennals/__init__.pyi | 16 ++++++++++++++-- python/viennals/_core.pyi | 6 +++--- python/viennals/d2.pyi | 15 +++++++++++++++ python/viennals/d3.pyi | 15 +++++++++++++++ 5 files changed, 52 insertions(+), 5 deletions(-) diff --git a/python/pyWrap.hpp b/python/pyWrap.hpp index b64169b9..24157a8a 100644 --- a/python/pyWrap.hpp +++ b/python/pyWrap.hpp @@ -190,6 +190,11 @@ template void bindApi(py::module &module) { .def("setIgnoreVoids", &Advect::setIgnoreVoids, "Set whether voids in the geometry should be ignored during " "advection or not.") + .def("setAdaptiveTimeStepping", &Advect::setAdaptiveTimeStepping, + "Set whether adaptive time stepping should be used when approaching " + "material boundaries during etching.") + .def("setSingleStep", &Advect::setSingleStep, + "Set whether only a single advection step should be performed.") .def( "setSaveAdvectionVelocities", &Advect::setSaveAdvectionVelocities, diff --git a/python/viennals/__init__.pyi b/python/viennals/__init__.pyi index f475901c..416f7a4d 100644 --- a/python/viennals/__init__.pyi +++ b/python/viennals/__init__.pyi @@ -147,6 +147,7 @@ __all__: list[str] = [ "Writer", "d2", "d3", + "getDimension", "hrleGrid", "setDimension", "setNumThreads", @@ -156,6 +157,17 @@ __all__: list[str] = [ def __dir__(): ... def __getattr__(name): ... def _windows_dll_path(): ... +def getDimension() -> int: + """ + Get the current dimension of the simulation. + + Returns + ------- + int + The currently set dimension (2 or 3). + + """ + def setDimension(d: int): """ Set the dimension of the simulation (2 or 3). @@ -168,6 +180,6 @@ def setDimension(d: int): """ PROXY_DIM: int = 2 -__version__: str = "5.2.0" -version: str = "5.2.0" +__version__: str = "5.2.1" +version: str = "5.2.1" _C = _core diff --git a/python/viennals/_core.pyi b/python/viennals/_core.pyi index ce81c0f6..e8926180 100644 --- a/python/viennals/_core.pyi +++ b/python/viennals/_core.pyi @@ -8,8 +8,8 @@ import enum import typing from viennals import d2 import viennals.d2 -from viennals import d3 import viennals.d3 +from viennals import d3 __all__: list[str] = [ "BooleanOperationEnum", @@ -731,5 +731,5 @@ class VoidTopSurfaceEnum(enum.IntEnum): def setNumThreads(arg0: typing.SupportsInt) -> None: ... -__version__: str = "5.2.0" -version: str = "5.2.0" +__version__: str = "5.2.1" +version: str = "5.2.1" diff --git a/python/viennals/d2.pyi b/python/viennals/d2.pyi index d09b7242..1fa57b43 100644 --- a/python/viennals/d2.pyi +++ b/python/viennals/d2.pyi @@ -112,6 +112,11 @@ class Advect: Prepare the level-set. """ + def setAdaptiveTimeStepping(self, arg0: bool) -> None: + """ + Set whether adaptive time stepping should be used when approaching material boundaries during etching. + """ + def setAdvectionTime(self, arg0: typing.SupportsFloat) -> None: """ Set the time until when the level set should be advected. @@ -137,6 +142,16 @@ class Advect: Set the integration scheme to use during advection. """ + def setSaveAdvectionVelocities(self, arg0: bool) -> None: + """ + Set whether the velocities applied to each point should be saved in the level set for debug purposes. + """ + + def setSingleStep(self, arg0: bool) -> None: + """ + Set whether only a single advection step should be performed. + """ + def setTimeStepRatio(self, arg0: typing.SupportsFloat) -> None: """ Set the maximum time step size relative to grid size. Advection is only stable for <0.5. diff --git a/python/viennals/d3.pyi b/python/viennals/d3.pyi index 39584f63..bbcc46d5 100644 --- a/python/viennals/d3.pyi +++ b/python/viennals/d3.pyi @@ -107,6 +107,11 @@ class Advect: Prepare the level-set. """ + def setAdaptiveTimeStepping(self, arg0: bool) -> None: + """ + Set whether adaptive time stepping should be used when approaching material boundaries during etching. + """ + def setAdvectionTime(self, arg0: typing.SupportsFloat) -> None: """ Set the time until when the level set should be advected. @@ -132,6 +137,16 @@ class Advect: Set the integration scheme to use during advection. """ + def setSaveAdvectionVelocities(self, arg0: bool) -> None: + """ + Set whether the velocities applied to each point should be saved in the level set for debug purposes. + """ + + def setSingleStep(self, arg0: bool) -> None: + """ + Set whether only a single advection step should be performed. + """ + def setTimeStepRatio(self, arg0: typing.SupportsFloat) -> None: """ Set the maximum time step size relative to grid size. Advection is only stable for <0.5. From b3fdb1cc207dd14701e14c2f165c797b68b4728d Mon Sep 17 00:00:00 2001 From: reiter Date: Mon, 15 Dec 2025 09:39:06 +0100 Subject: [PATCH 09/57] Bump version --- CMakeLists.txt | 6 +++--- README.md | 2 +- include/viennals/lsVersion.hpp | 6 +++--- pyproject.toml | 2 +- python/pyWrap.cpp | 3 +-- python/viennals/__init__.pyi | 4 ++-- python/viennals/_core.pyi | 4 ++-- 7 files changed, 13 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index df98441c..cf0b2064 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.20 FATAL_ERROR) project( ViennaLS LANGUAGES CXX - VERSION 5.2.1) + VERSION 5.3.0) # -------------------------------------------------------------------------------------------------------- # Library options @@ -111,9 +111,9 @@ include(cmake/vtk.cmake) CPMAddPackage( NAME ViennaCore - VERSION 1.7.3 + VERSION 1.8.0 GIT_REPOSITORY "https://github.com/ViennaTools/ViennaCore" - OPTIONS "VIENNACORE_FORMAT_EXCLUDE docs/ build/" + OPTIONS "VIENNACORE_FORMAT_EXCLUDE build/" EXCLUDE_FROM_ALL ${VIENNALS_BUILD_PYTHON}) CPMAddPackage( diff --git a/README.md b/README.md index 56b9a14f..603950cf 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ We recommend using [CPM.cmake](https://github.com/cpm-cmake/CPM.cmake) to consum * Installation with CPM ```cmake - CPMAddPackage("gh:viennatools/viennals@5.2.1") + CPMAddPackage("gh:viennatools/viennals@5.3.0") ``` * With a local installation diff --git a/include/viennals/lsVersion.hpp b/include/viennals/lsVersion.hpp index 74264f11..d59a619f 100644 --- a/include/viennals/lsVersion.hpp +++ b/include/viennals/lsVersion.hpp @@ -5,10 +5,10 @@ namespace viennals { // Version information generated by CMake -inline constexpr const char *version = "5.2.1"; +inline constexpr const char *version = "5.3.0"; inline constexpr int versionMajor = 5; -inline constexpr int versionMinor = 2; -inline constexpr int versionPatch = 1; +inline constexpr int versionMinor = 3; +inline constexpr int versionPatch = 0; // Utility functions for version comparison inline constexpr uint32_t versionAsInteger() { diff --git a/pyproject.toml b/pyproject.toml index 13ad8dc7..171980fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ requires = [ build-backend = "scikit_build_core.build" [project] -version = "5.2.1" +version = "5.3.0" name = "ViennaLS" readme = "README.md" license = {file = "LICENSE"} diff --git a/python/pyWrap.cpp b/python/pyWrap.cpp index 2e46ecb6..332cb05c 100644 --- a/python/pyWrap.cpp +++ b/python/pyWrap.cpp @@ -103,8 +103,7 @@ PYBIND11_MODULE(VIENNALS_MODULE_NAME, module) { IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER) .value("STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER", IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) - .value("WENO_5TH_ORDER", - IntegrationSchemeEnum::WENO_5TH_ORDER) + .value("WENO_5TH_ORDER", IntegrationSchemeEnum::WENO_5TH_ORDER) .finalize(); py::native_enum(module, "BooleanOperationEnum", diff --git a/python/viennals/__init__.pyi b/python/viennals/__init__.pyi index 416f7a4d..cad13652 100644 --- a/python/viennals/__init__.pyi +++ b/python/viennals/__init__.pyi @@ -180,6 +180,6 @@ def setDimension(d: int): """ PROXY_DIM: int = 2 -__version__: str = "5.2.1" -version: str = "5.2.1" +__version__: str = "5.3.0" +version: str = "5.3.0" _C = _core diff --git a/python/viennals/_core.pyi b/python/viennals/_core.pyi index e8926180..a6154b8b 100644 --- a/python/viennals/_core.pyi +++ b/python/viennals/_core.pyi @@ -731,5 +731,5 @@ class VoidTopSurfaceEnum(enum.IntEnum): def setNumThreads(arg0: typing.SupportsInt) -> None: ... -__version__: str = "5.2.1" -version: str = "5.2.1" +__version__: str = "5.3.0" +version: str = "5.3.0" From 500a15c043d433db9dad6f932205fb6c3bb82d26 Mon Sep 17 00:00:00 2001 From: reiter Date: Mon, 15 Dec 2025 10:02:26 +0100 Subject: [PATCH 10/57] Add option to set the adaptive time stepping threshold --- include/viennals/lsAdvect.hpp | 18 +++++++++++++----- python/pyWrap.hpp | 7 +++++++ python/viennals/_core.pyi | 2 +- python/viennals/d2.pyi | 10 ++++++++++ python/viennals/d3.pyi | 10 ++++++++++ 5 files changed, 41 insertions(+), 6 deletions(-) diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index 328b5a0f..af6e1f66 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -84,6 +84,7 @@ template class Advect { bool updatePointData = true; bool checkDissipation = true; bool adaptiveTimeStepping = false; + double adaptiveTimeStepThreshold = 0.05; static constexpr double wrappingLayerEpsilon = 1e-4; // this vector will hold the maximum time step for each point and the @@ -445,6 +446,7 @@ template class Advect { } const bool ignoreVoidPoints = ignoreVoids; const bool useAdaptiveTimeStepping = adaptiveTimeStepping; + const double atsThreshold = adaptiveTimeStepThreshold; if (!storedRates.empty()) { VIENNACORE_LOG_WARNING("Advect: Overwriting previously stored rates."); @@ -556,12 +558,11 @@ template class Advect { } else { // Sub-case 3b: Interface Interaction - if (useAdaptiveTimeStepping && difference > 0.05 * cfl) { + if (useAdaptiveTimeStepping && difference > atsThreshold * cfl) { // Adaptive Sub-stepping: - // Approaching boundary: Force small steps (5% CFL) to gather - // flux statistics and prevent numerical overshoot ("Soft - // Landing"). - maxStepTime -= 0.05 * cfl / velocity; + // Approaching boundary: Force small steps to gather flux + // statistics and prevent numerical overshoot ("Soft Landing"). + maxStepTime -= atsThreshold * cfl / velocity; tempRates.push_back(std::make_pair( gradNDissipation, std::numeric_limits::min())); } else { @@ -788,6 +789,13 @@ template class Advect { /// Defaults to false. void setAdaptiveTimeStepping(bool aTS) { adaptiveTimeStepping = aTS; } + /// Set the threshold (in fraction of the CFL condition) + /// below which adaptive time stepping is applied. + /// Defaults to 0.05 (5% of the CFL condition). + void setAdaptiveTimeStepThreshold(double threshold) { + adaptiveTimeStepThreshold = threshold; + } + /// Set whether the velocities applied to each point should be saved in /// the level set for debug purposes. void setSaveAdvectionVelocities(bool sAV) { saveAdvectionVelocities = sAV; } diff --git a/python/pyWrap.hpp b/python/pyWrap.hpp index 24157a8a..69f7cdd4 100644 --- a/python/pyWrap.hpp +++ b/python/pyWrap.hpp @@ -193,6 +193,10 @@ template void bindApi(py::module &module) { .def("setAdaptiveTimeStepping", &Advect::setAdaptiveTimeStepping, "Set whether adaptive time stepping should be used when approaching " "material boundaries during etching.") + .def("setAdaptiveTimeStepThreshold", + &Advect::setAdaptiveTimeStepThreshold, + "Set the threshold (in fraction of the CFL condition) below which " + "adaptive time stepping is applied. Defaults to 0.05.") .def("setSingleStep", &Advect::setSingleStep, "Set whether only a single advection step should be performed.") .def( @@ -216,6 +220,9 @@ template void bindApi(py::module &module) { "Set the integration scheme to use during advection.") .def("setDissipationAlpha", &Advect::setDissipationAlpha, "Set the dissipation value to use for Lax Friedrichs integration.") + .def("setUpdatePointData", &Advect::setUpdatePointData, + "Set whether the point data in the old LS should be translated to " + "the advected LS. Defaults to true.") .def("prepareLS", &Advect::prepareLS, "Prepare the level-set.") // need scoped release since we are calling a python method from // parallelised C++ code here diff --git a/python/viennals/_core.pyi b/python/viennals/_core.pyi index a6154b8b..f84b7449 100644 --- a/python/viennals/_core.pyi +++ b/python/viennals/_core.pyi @@ -8,8 +8,8 @@ import enum import typing from viennals import d2 import viennals.d2 -import viennals.d3 from viennals import d3 +import viennals.d3 __all__: list[str] = [ "BooleanOperationEnum", diff --git a/python/viennals/d2.pyi b/python/viennals/d2.pyi index 1fa57b43..55b4ed24 100644 --- a/python/viennals/d2.pyi +++ b/python/viennals/d2.pyi @@ -112,6 +112,11 @@ class Advect: Prepare the level-set. """ + def setAdaptiveTimeStepThreshold(self, arg0: typing.SupportsFloat) -> None: + """ + Set the threshold (in fraction of the CFL condition) below which adaptive time stepping is applied. Defaults to 0.05. + """ + def setAdaptiveTimeStepping(self, arg0: bool) -> None: """ Set whether adaptive time stepping should be used when approaching material boundaries during etching. @@ -157,6 +162,11 @@ class Advect: Set the maximum time step size relative to grid size. Advection is only stable for <0.5. """ + def setUpdatePointData(self, arg0: bool) -> None: + """ + Set whether the point data in the old LS should be translated to the advected LS. Defaults to true. + """ + def setVelocityField(self, arg0: viennals._core.VelocityField) -> None: """ Set the velocity to use for advection. diff --git a/python/viennals/d3.pyi b/python/viennals/d3.pyi index bbcc46d5..4513839d 100644 --- a/python/viennals/d3.pyi +++ b/python/viennals/d3.pyi @@ -107,6 +107,11 @@ class Advect: Prepare the level-set. """ + def setAdaptiveTimeStepThreshold(self, arg0: typing.SupportsFloat) -> None: + """ + Set the threshold (in fraction of the CFL condition) below which adaptive time stepping is applied. Defaults to 0.05. + """ + def setAdaptiveTimeStepping(self, arg0: bool) -> None: """ Set whether adaptive time stepping should be used when approaching material boundaries during etching. @@ -152,6 +157,11 @@ class Advect: Set the maximum time step size relative to grid size. Advection is only stable for <0.5. """ + def setUpdatePointData(self, arg0: bool) -> None: + """ + Set whether the point data in the old LS should be translated to the advected LS. Defaults to true. + """ + def setVelocityField(self, arg0: viennals._core.VelocityField) -> None: """ Set the velocity to use for advection. From 7c044534ebe49360e03c5fa0fa87eb022c2e558b Mon Sep 17 00:00:00 2001 From: reiter Date: Mon, 15 Dec 2025 10:24:21 +0100 Subject: [PATCH 11/57] Fix MultiSurfaceMesh ctors --- include/viennals/lsToMultiSurfaceMesh.hpp | 9 ++++++- python/pyWrap.hpp | 19 ++++++++++++--- python/viennals/d2.pyi | 29 ++++++++++++++++++++--- python/viennals/d3.pyi | 29 ++++++++++++++++++++--- 4 files changed, 76 insertions(+), 10 deletions(-) diff --git a/include/viennals/lsToMultiSurfaceMesh.hpp b/include/viennals/lsToMultiSurfaceMesh.hpp index 089fc747..ce012abd 100644 --- a/include/viennals/lsToMultiSurfaceMesh.hpp +++ b/include/viennals/lsToMultiSurfaceMesh.hpp @@ -63,6 +63,13 @@ class ToMultiSurfaceMesh { levelSets.push_back(passedLevelSet); } + ToMultiSurfaceMesh( + std::vector> const &passedLevelSets, + SmartPointer> passedMesh, double eps = 1e-12, + double minNodeDistFactor = 0.05) + : levelSets(passedLevelSets), mesh(passedMesh), epsilon(eps), + minNodeDistanceFactor(minNodeDistFactor) {} + ToMultiSurfaceMesh(SmartPointer> passedMesh, double eps = 1e-12, double minNodeDistFactor = 0.05) : mesh(passedMesh), epsilon(eps), @@ -122,7 +129,7 @@ class ToMultiSurfaceMesh { const bool checkNodeFlag = minNodeDistanceFactor > 0; const bool useMaterialMap = materialMap != nullptr; - auto quantize = [&](const Vec3D &p) -> I3 { + auto quantize = [&minNodeDistance](const Vec3D &p) -> I3 { const T inv = T(1) / minNodeDistance; return {(int)std::llround(p[0] * inv), (int)std::llround(p[1] * inv), (int)std::llround(p[2] * inv)}; diff --git a/python/pyWrap.hpp b/python/pyWrap.hpp index 69f7cdd4..8b2df580 100644 --- a/python/pyWrap.hpp +++ b/python/pyWrap.hpp @@ -772,11 +772,24 @@ template void bindApi(py::module &module) { py::class_, SmartPointer>>( module, "ToMultiSurfaceMesh") // constructors - .def(py::init(&SmartPointer>::template New<>)) + .def(py::init( + &SmartPointer>::template New), + py::arg("eps") = 1e-12, py::arg("minNodeDistFactor") = 0.05) .def(py::init(&SmartPointer>::template New< - SmartPointer> &, SmartPointer> &>)) + SmartPointer> &, SmartPointer> &, + double, double>), + py::arg("domain"), py::arg("mesh"), py::arg("eps") = 1e-12, + py::arg("minNodeDistFactor") = 0.05) .def(py::init(&SmartPointer>::template New< - SmartPointer> &>)) + std::vector>> &, + SmartPointer> &, double, double>), + py::arg("domains"), py::arg("mesh"), py::arg("eps") = 1e-12, + py::arg("minNodeDistFactor") = 0.05) + .def(py::init(&SmartPointer>::template New< + SmartPointer> &, double, double>), + py::arg("mesh"), py::arg("eps") = 1e-12, + py::arg("minNodeDistFactor") = 0.05) // methods .def("insertNextLevelSet", &ToMultiSurfaceMesh::insertNextLevelSet, "Insert next level set to output in the mesh.") diff --git a/python/viennals/d2.pyi b/python/viennals/d2.pyi index 55b4ed24..05b532e2 100644 --- a/python/viennals/d2.pyi +++ b/python/viennals/d2.pyi @@ -1325,11 +1325,34 @@ class ToMesh: class ToMultiSurfaceMesh: @typing.overload - def __init__(self) -> None: ... + def __init__( + self, + eps: typing.SupportsFloat = 1e-12, + minNodeDistFactor: typing.SupportsFloat = 0.05, + ) -> None: ... @typing.overload - def __init__(self, arg0: Domain, arg1: viennals._core.Mesh) -> None: ... + def __init__( + self, + domain: Domain, + mesh: viennals._core.Mesh, + eps: typing.SupportsFloat = 1e-12, + minNodeDistFactor: typing.SupportsFloat = 0.05, + ) -> None: ... @typing.overload - def __init__(self, arg0: viennals._core.Mesh) -> None: ... + def __init__( + self, + domains: collections.abc.Sequence[Domain], + mesh: viennals._core.Mesh, + eps: typing.SupportsFloat = 1e-12, + minNodeDistFactor: typing.SupportsFloat = 0.05, + ) -> None: ... + @typing.overload + def __init__( + self, + mesh: viennals._core.Mesh, + eps: typing.SupportsFloat = 1e-12, + minNodeDistFactor: typing.SupportsFloat = 0.05, + ) -> None: ... def apply(self) -> None: """ Convert the levelset to a surface mesh. diff --git a/python/viennals/d3.pyi b/python/viennals/d3.pyi index 4513839d..88bca087 100644 --- a/python/viennals/d3.pyi +++ b/python/viennals/d3.pyi @@ -955,11 +955,34 @@ class ToMesh: class ToMultiSurfaceMesh: @typing.overload - def __init__(self) -> None: ... + def __init__( + self, + eps: typing.SupportsFloat = 1e-12, + minNodeDistFactor: typing.SupportsFloat = 0.05, + ) -> None: ... @typing.overload - def __init__(self, arg0: Domain, arg1: viennals._core.Mesh) -> None: ... + def __init__( + self, + domain: Domain, + mesh: viennals._core.Mesh, + eps: typing.SupportsFloat = 1e-12, + minNodeDistFactor: typing.SupportsFloat = 0.05, + ) -> None: ... @typing.overload - def __init__(self, arg0: viennals._core.Mesh) -> None: ... + def __init__( + self, + domains: collections.abc.Sequence[Domain], + mesh: viennals._core.Mesh, + eps: typing.SupportsFloat = 1e-12, + minNodeDistFactor: typing.SupportsFloat = 0.05, + ) -> None: ... + @typing.overload + def __init__( + self, + mesh: viennals._core.Mesh, + eps: typing.SupportsFloat = 1e-12, + minNodeDistFactor: typing.SupportsFloat = 0.05, + ) -> None: ... def apply(self) -> None: """ Convert the levelset to a surface mesh. From 9bc958c842a226f6e6c832eb2c67bc27f37f7108 Mon Sep 17 00:00:00 2001 From: filipovic Date: Mon, 15 Dec 2025 20:43:38 +0100 Subject: [PATCH 12/57] Added Runge-Kutta 3 time integration --- examples/Epitaxy/Epitaxy.py | 96 +++++++ include/viennals/lsAdvect.hpp | 259 ++++++++++-------- include/viennals/lsAdvectForwardEuler.hpp | 14 + include/viennals/lsAdvectRungeKutta3.hpp | 133 +++++++++ python/pyWrap.hpp | 41 ++- tests/Advection/Advection.cpp | 4 +- tests/Advection/Advection.py | 69 +++++ tests/Advection/CMakeLists.txt | 10 + tests/Advection/StencilLaxFriedrichsTest.cpp | 81 ++++++ tests/Advection/TimeIntegrationComparison.cpp | 94 +++++++ tests/Advection/TimeIntegrationComparison.py | 72 +++++ 11 files changed, 745 insertions(+), 128 deletions(-) create mode 100644 examples/Epitaxy/Epitaxy.py create mode 100644 include/viennals/lsAdvectForwardEuler.hpp create mode 100644 include/viennals/lsAdvectRungeKutta3.hpp create mode 100644 tests/Advection/Advection.py create mode 100644 tests/Advection/StencilLaxFriedrichsTest.cpp create mode 100644 tests/Advection/TimeIntegrationComparison.cpp create mode 100644 tests/Advection/TimeIntegrationComparison.py diff --git a/examples/Epitaxy/Epitaxy.py b/examples/Epitaxy/Epitaxy.py new file mode 100644 index 00000000..d5990c12 --- /dev/null +++ b/examples/Epitaxy/Epitaxy.py @@ -0,0 +1,96 @@ +import viennals as vls +import time + +# Dimension +D = 3 +vls.setNumThreads(8) + +class EpitaxyVelocity(vls.VelocityField): + def __init__(self, velocities): + super().__init__() + self.velocities = velocities + self.R111 = 0.5 + self.R100 = 1.0 + # D is 3 in this context + self.low = 0.5773502691896257 if D > 2 else 0.7071067811865476 + self.high = 1.0 + + def getScalarVelocity(self, coord, material, normal, point_id): + vel = max(abs(normal[0]), abs(normal[2])) + factor = (self.R100 - self.R111) / (self.high - self.low) + vel = (vel - self.low) * factor + self.R111 + + if abs(normal[0]) < abs(normal[2]): + vel *= 2.0 + + mat_vel = 0.0 + if material < len(self.velocities): + mat_vel = self.velocities[material] + + return vel * mat_vel + + def getVectorVelocity(self, coord, material, normal, point_id): + return (0., 0., 0.) + +def write_surface(domain, filename): + mesh = vls.Mesh() + vls.ToSurfaceMesh(domain, mesh).apply() + vls.VTKWriter(mesh, filename).apply() + +def main(): + # Set dimension to 3D + vls.setDimension(D) + + # Parameters + grid_delta = 0.03 + fin_width = 0.5 + fin_height = 0.2 + + bounds = [-1.0, 1.0, -1.0, 1.0, -1.0, 1.0] + boundary_conditions = [ + vls.BoundaryConditionEnum.REFLECTIVE_BOUNDARY, + vls.BoundaryConditionEnum.REFLECTIVE_BOUNDARY, + vls.BoundaryConditionEnum.INFINITE_BOUNDARY + ] + + # Create Geometry + mask = vls.Domain(bounds, boundary_conditions, grid_delta) + plane = vls.Plane([0.0, 0.0, 0.0], [0.0, 0.0, 1.0]) + vls.MakeGeometry(mask, plane).apply() + + substrate = vls.Domain(bounds, boundary_conditions, grid_delta) + min_point = [-fin_width / 2, -fin_width / 2, 0.0] + max_point = [fin_width / 2, fin_width / 2, fin_height] + box = vls.Box(min_point, max_point) + vls.MakeGeometry(substrate, box).apply() + + vls.BooleanOperation(substrate, mask, vls.BooleanOperationEnum.UNION).apply() + + write_surface(mask, "mask.vtp") + write_surface(substrate, "substrate.vtp") + + level_sets = [mask, substrate] + vls.PrepareStencilLocalLaxFriedrichs(level_sets, [False, True]) + + # Advection + velocities = [0.0, -0.5] + velocity_field = EpitaxyVelocity(velocities) + + advection = vls.Advect() + for ls in level_sets: + advection.insertNextLevelSet(ls) + advection.setVelocityField(velocity_field) + advection.setIntegrationScheme(vls.IntegrationSchemeEnum.STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) + advection.setAdvectionTime(0.5) + + start_time = time.time() + advection.apply() + end_time = time.time() + print(f"Epitaxy took {end_time - start_time}s") + + vls.FinalizeStencilLocalLaxFriedrichs(level_sets) + + write_surface(substrate, "epitaxy.vtp") + +if __name__ == "__main__": + main() diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index 328b5a0f..5cc6333e 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -68,6 +68,7 @@ template class Advect { viennahrle::ConstSparseIterator::DomainType>; using hrleIndexType = viennahrle::IndexType; +protected: std::vector>> levelSets; SmartPointer> velocities = nullptr; IntegrationSchemeEnum integrationScheme = @@ -84,6 +85,7 @@ template class Advect { bool updatePointData = true; bool checkDissipation = true; bool adaptiveTimeStepping = false; + unsigned adaptiveTimeStepSubdivisions = 20; static constexpr double wrappingLayerEpsilon = 1e-4; // this vector will hold the maximum time step for each point and the @@ -211,6 +213,15 @@ template class Advect { auto &newDomain = newlsDomain->getDomain(); auto &domain = levelSets.back()->getDomain(); + // Determine cutoff and width based on integration scheme to avoid immediate + // re-expansion + T cutoff = 1.0; + int finalWidth = 2; + if (integrationScheme == IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + cutoff = 1.5; + finalWidth = 3; + } + newDomain.initialize(domain.getNewSegmentation(), domain.getAllocation() * (2.0 / levelSets.back()->getLevelSetWidth())); @@ -332,7 +343,7 @@ template class Advect { } } - if (distance <= 1.) { + if (distance <= cutoff) { domainSegment.insertNextDefinedPoint(it.getIndices(), distance); if (updateData) newDataSourceIds[p].push_back( @@ -356,7 +367,7 @@ template class Advect { } } - if (distance >= -1.) { + if (distance >= -cutoff) { domainSegment.insertNextDefinedPoint(it.getIndices(), distance); if (updateData) newDataSourceIds[p].push_back( @@ -380,45 +391,7 @@ template class Advect { newDomain.finalize(); newDomain.segment(); levelSets.back()->deepCopy(newlsDomain); - levelSets.back()->finalize(2); - } - - /// internal function used as a wrapper to call specialized integrateTime - /// with the chosen integrationScheme - double advect(double maxTimeStep) { - // check whether a level set and velocities have been given - if (levelSets.empty()) { - VIENNACORE_LOG_ERROR("No level sets passed to Advect. Not advecting."); - return std::numeric_limits::max(); - } - if (velocities == nullptr) { - VIENNACORE_LOG_ERROR( - "No velocity field passed to Advect. Not advecting."); - return std::numeric_limits::max(); - } - - if (currentTimeStep < 0. || storedRates.empty()) - applyIntegration(maxTimeStep); - - moveSurface(); - - rebuildLS(); - - // Adjust all level sets below the advected one - // This means, that when the top level-set and one below - // are etched, the lower one is moved with the top level-set - // TODO: Adjust lower layers also when they have grown, - // to allow for two different growth rates of materials - if (integrationScheme != - IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { - for (unsigned i = 0; i < levelSets.size() - 1; ++i) { - BooleanOperation(levelSets[i], levelSets.back(), - BooleanOperationEnum::INTERSECT) - .apply(); - } - } - - return getCurrentTimeStep(); + levelSets.back()->finalize(finalWidth); } /// Internal function used to calculate the deltas to be applied to the LS @@ -556,12 +529,13 @@ template class Advect { } else { // Sub-case 3b: Interface Interaction - if (useAdaptiveTimeStepping && difference > 0.05 * cfl) { + auto adaptiveFactor = 1.0 / adaptiveTimeStepSubdivisions; + if (useAdaptiveTimeStepping && difference > adaptiveFactor * cfl) { // Adaptive Sub-stepping: - // Approaching boundary: Force small steps (5% CFL) to gather + // Approaching boundary: Force small steps to gather // flux statistics and prevent numerical overshoot ("Soft // Landing"). - maxStepTime -= 0.05 * cfl / velocity; + maxStepTime -= adaptiveFactor * cfl / velocity; tempRates.push_back(std::make_pair( gradNDissipation, std::numeric_limits::min())); } else { @@ -601,9 +575,78 @@ template class Advect { return maxTimeStep; } + /// This function applies the integration scheme and calculates the rates and + /// the maximum time step, but it does **not** move the surface. + void computeRates(double maxTimeStep = std::numeric_limits::max()) { + prepareLS(); + if (integrationScheme == IntegrationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER) { + auto is = lsInternal::EngquistOsher(levelSets.back(), velocities, + calculateNormalVectors); + currentTimeStep = integrateTime(is, maxTimeStep); + } else if (integrationScheme == + IntegrationSchemeEnum::ENGQUIST_OSHER_2ND_ORDER) { + auto is = lsInternal::EngquistOsher(levelSets.back(), velocities, + calculateNormalVectors); + currentTimeStep = integrateTime(is, maxTimeStep); + } else if (integrationScheme == + IntegrationSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER) { + auto alphas = findGlobalAlphas(); + auto is = lsInternal::LaxFriedrichs(levelSets.back(), velocities, + dissipationAlpha, alphas, + calculateNormalVectors); + currentTimeStep = integrateTime(is, maxTimeStep); + } else if (integrationScheme == + IntegrationSchemeEnum::LAX_FRIEDRICHS_2ND_ORDER) { + auto alphas = findGlobalAlphas(); + auto is = lsInternal::LaxFriedrichs(levelSets.back(), velocities, + dissipationAlpha, alphas, + calculateNormalVectors); + currentTimeStep = integrateTime(is, maxTimeStep); + } else if (integrationScheme == + IntegrationSchemeEnum:: + LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER) { + auto is = lsInternal::LocalLaxFriedrichsAnalytical( + levelSets.back(), velocities); + currentTimeStep = integrateTime(is, maxTimeStep); + } else if (integrationScheme == + IntegrationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + auto is = lsInternal::LocalLocalLaxFriedrichs( + levelSets.back(), velocities, dissipationAlpha); + currentTimeStep = integrateTime(is, maxTimeStep); + } else if (integrationScheme == + IntegrationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER) { + auto is = lsInternal::LocalLocalLaxFriedrichs( + levelSets.back(), velocities, dissipationAlpha); + currentTimeStep = integrateTime(is, maxTimeStep); + } else if (integrationScheme == + IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + auto is = lsInternal::LocalLaxFriedrichs( + levelSets.back(), velocities, dissipationAlpha); + currentTimeStep = integrateTime(is, maxTimeStep); + } else if (integrationScheme == + IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER) { + auto is = lsInternal::LocalLaxFriedrichs( + levelSets.back(), velocities, dissipationAlpha); + currentTimeStep = integrateTime(is, maxTimeStep); + } else if (integrationScheme == + IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + auto is = lsInternal::StencilLocalLaxFriedrichsScalar( + levelSets.back(), velocities, dissipationAlpha); + currentTimeStep = integrateTime(is, maxTimeStep); + } else if (integrationScheme == IntegrationSchemeEnum::WENO_5TH_ORDER) { + // Instantiate WENO5 with order 3 (neighbors +/- 3) + auto is = lsInternal::WENO5(levelSets.back(), velocities, + dissipationAlpha); + currentTimeStep = integrateTime(is, maxTimeStep); + } else { + VIENNACORE_LOG_ERROR("Advect: Integration scheme not found."); + currentTimeStep = -1.; + } + } + // Level Sets below are also considered in order to adjust the advection // depth accordingly if there would be a material change. - void moveSurface() { + void updateLevelSet(double dt) { if (timeStepRatio >= 0.5) { VIENNACORE_LOG_WARNING( "Integration time step ratio should be smaller than 0.5. " @@ -611,9 +654,8 @@ template class Advect { } auto &topDomain = levelSets.back()->getDomain(); - auto &grid = levelSets.back()->getGrid(); - assert(currentTimeStep >= 0. && "No time step set!"); + assert(dt >= 0. && "No time step set!"); assert(storedRates.size() == topDomain.getNumberOfSegments()); // reduce to one layer thickness and apply new values directly to the @@ -645,7 +687,12 @@ template class Advect { for (unsigned localId = 0; localId < maxId; ++localId) { T &value = segment.definedValues[localId]; - double time = currentTimeStep; + + // // Skip points that were not part of computeRates (outer layers) + // if (std::abs(value) > 0.5) + // continue; + + double time = dt; // if there is a change in materials during one time step, deduct // the time taken to advect up to the end of the top material and @@ -786,7 +833,14 @@ template class Advect { /// Set whether adaptive time stepping should be used /// when approaching material boundaries during etching. /// Defaults to false. - void setAdaptiveTimeStepping(bool aTS) { adaptiveTimeStepping = aTS; } + void setAdaptiveTimeStepping(bool aTS = true, unsigned subdivisions = 20) { + adaptiveTimeStepping = aTS; + if (subdivisions < 1) { + VIENNACORE_LOG_WARNING("Advect: Adaptive time stepping subdivisions must be at least 1. Setting to 1."); + subdivisions = 1; + } + adaptiveTimeStepSubdivisions = subdivisions; + } /// Set whether the velocities applied to each point should be saved in /// the level set for debug purposes. @@ -876,8 +930,41 @@ template class Advect { } } - /// Perform the advection. - void apply() { + /// internal function used as a wrapper to call specialized integrateTime + /// with the chosen integrationScheme + virtual double advect(double maxTimeStep) { + // check whether a level set and velocities have been given + if (levelSets.empty()) { + VIENNACORE_LOG_ERROR("No level sets passed to Advect. Not advecting."); + return std::numeric_limits::max(); + } + if (velocities == nullptr) { + VIENNACORE_LOG_ERROR( + "No velocity field passed to Advect. Not advecting."); + return std::numeric_limits::max(); + } + + if (currentTimeStep < 0. || storedRates.empty()) + computeRates(maxTimeStep); + + updateLevelSet(currentTimeStep); + + rebuildLS(); + + // Adjust all level sets below the advected one + if (integrationScheme != + IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + for (unsigned i = 0; i < levelSets.size() - 1; ++i) { + BooleanOperation(levelSets[i], levelSets.back(), + BooleanOperationEnum::INTERSECT) + .apply(); + } + } + + return currentTimeStep; + } + + virtual void apply() { if (advectionTime == 0.) { advectedTime = advect(std::numeric_limits::max()); numberOfTimeSteps = 1; @@ -893,76 +980,6 @@ template class Advect { advectedTime = currentTime; } } - - /// This function applies the integration scheme and calculates the rates and - /// the maximum time step, but it does **not** move the surface. - void - applyIntegration(double maxTimeStep = std::numeric_limits::max()) { - prepareLS(); - if (integrationScheme == IntegrationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER) { - auto is = lsInternal::EngquistOsher(levelSets.back(), velocities, - calculateNormalVectors); - currentTimeStep = integrateTime(is, maxTimeStep); - } else if (integrationScheme == - IntegrationSchemeEnum::ENGQUIST_OSHER_2ND_ORDER) { - auto is = lsInternal::EngquistOsher(levelSets.back(), velocities, - calculateNormalVectors); - currentTimeStep = integrateTime(is, maxTimeStep); - } else if (integrationScheme == - IntegrationSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER) { - auto alphas = findGlobalAlphas(); - auto is = lsInternal::LaxFriedrichs(levelSets.back(), velocities, - dissipationAlpha, alphas, - calculateNormalVectors); - currentTimeStep = integrateTime(is, maxTimeStep); - } else if (integrationScheme == - IntegrationSchemeEnum::LAX_FRIEDRICHS_2ND_ORDER) { - auto alphas = findGlobalAlphas(); - auto is = lsInternal::LaxFriedrichs(levelSets.back(), velocities, - dissipationAlpha, alphas, - calculateNormalVectors); - currentTimeStep = integrateTime(is, maxTimeStep); - } else if (integrationScheme == - IntegrationSchemeEnum:: - LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER) { - auto is = lsInternal::LocalLaxFriedrichsAnalytical( - levelSets.back(), velocities); - currentTimeStep = integrateTime(is, maxTimeStep); - } else if (integrationScheme == - IntegrationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { - auto is = lsInternal::LocalLocalLaxFriedrichs( - levelSets.back(), velocities, dissipationAlpha); - currentTimeStep = integrateTime(is, maxTimeStep); - } else if (integrationScheme == - IntegrationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER) { - auto is = lsInternal::LocalLocalLaxFriedrichs( - levelSets.back(), velocities, dissipationAlpha); - currentTimeStep = integrateTime(is, maxTimeStep); - } else if (integrationScheme == - IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { - auto is = lsInternal::LocalLaxFriedrichs( - levelSets.back(), velocities, dissipationAlpha); - currentTimeStep = integrateTime(is, maxTimeStep); - } else if (integrationScheme == - IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER) { - auto is = lsInternal::LocalLaxFriedrichs( - levelSets.back(), velocities, dissipationAlpha); - currentTimeStep = integrateTime(is, maxTimeStep); - } else if (integrationScheme == - IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { - auto is = lsInternal::StencilLocalLaxFriedrichsScalar( - levelSets.back(), velocities, dissipationAlpha); - currentTimeStep = integrateTime(is, maxTimeStep); - } else if (integrationScheme == IntegrationSchemeEnum::WENO_5TH_ORDER) { - // Instantiate WENO5 with order 3 (neighbors +/- 3) - auto is = lsInternal::WENO5(levelSets.back(), velocities, - calculateNormalVectors); - currentTimeStep = integrateTime(is, maxTimeStep); - } else { - VIENNACORE_LOG_ERROR("Advect: Integration scheme not found."); - currentTimeStep = -1.; - } - } }; // add all template specializations for this class diff --git a/include/viennals/lsAdvectForwardEuler.hpp b/include/viennals/lsAdvectForwardEuler.hpp new file mode 100644 index 00000000..4c608530 --- /dev/null +++ b/include/viennals/lsAdvectForwardEuler.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace viennals { + +template class AdvectForwardEuler : public Advect { +public: + using Advect::Advect; +}; + +PRECOMPILE_PRECISION_DIMENSION(AdvectForwardEuler); + +} // namespace viennals \ No newline at end of file diff --git a/include/viennals/lsAdvectRungeKutta3.hpp b/include/viennals/lsAdvectRungeKutta3.hpp new file mode 100644 index 00000000..6658e714 --- /dev/null +++ b/include/viennals/lsAdvectRungeKutta3.hpp @@ -0,0 +1,133 @@ +#pragma once + +#include +#include + +namespace viennals { + +/// This class implements the Strong Stability Preserving (SSP) Runge-Kutta +/// 3rd order time integration scheme (also known as TVD RK3). +/// It performs time integration using three stages of Euler steps and convex +/// combinations to preserve stability properties. +template class AdvectRungeKutta3 : public Advect { + using ConstSparseIterator = + viennahrle::ConstSparseIterator::DomainType>; + using hrleIndexType = viennahrle::IndexType; + using Base = Advect; + + SmartPointer> originalLevelSet = nullptr; + +public: + using Base::Base; // inherit all constructors + + using Base::advectedTime; + using Base::advectionTime; + using Base::currentTimeStep; + using Base::levelSets; + using Base::numberOfTimeSteps; + using Base::performOnlySingleStep; + using Base::storedRates; + using Base::velocities; + +private: + + // Helper function for linear combination: target = wTarget * target + wSource + // * source + void combineLevelSets(double wTarget, const SmartPointer> &target, + double wSource, const SmartPointer> &source) { + // We write the result into levelSets.back() (which is passed as 'target' or + // 'source' usually, but here we assume target is the destination) + // Actually, to support u_new = a*u_old + b*u_new, we should write to + // levelSets.back(). + auto &domainDest = levelSets.back()->getDomain(); + const auto &domainTarget = target->getDomain(); + const auto &domainSource = source->getDomain(); + + if (domainTarget.getNumberOfSegments() != + domainSource.getNumberOfSegments()) { + VIENNACORE_LOG_ERROR( + "AdvectRungeKutta3: Topology mismatch in combineLevelSets."); + return; + } + +#pragma omp parallel for schedule(static) + for (int p = 0; p < static_cast(domainDest.getNumberOfSegments()); + ++p) { + auto &segDest = domainDest.getDomainSegment(p); + const auto &segTarget = domainTarget.getDomainSegment(p); + const auto &segSource = domainSource.getDomainSegment(p); + + if (segTarget.definedValues.size() == segSource.definedValues.size() && + segDest.definedValues.size() == segTarget.definedValues.size()) { + for (size_t i = 0; i < segDest.definedValues.size(); ++i) { + segDest.definedValues[i] = wTarget * segTarget.definedValues[i] + + wSource * segSource.definedValues[i]; + } + } + } + } + +public: + double advect(double maxTimeStep) override { + if (levelSets.empty()) { + VIENNACORE_LOG_ERROR("No level sets passed to Advect. Not advecting."); + return std::numeric_limits::max(); + } + if (velocities == nullptr) { + VIENNACORE_LOG_ERROR( + "No velocity field passed to Advect. Not advecting."); + return std::numeric_limits::max(); + } + + // 1. Prepare and Expand + Base::prepareLS(); + + // 2. Save u^n (Deep copy with identical topology) + if (originalLevelSet == nullptr) { + originalLevelSet = + SmartPointer>::New(levelSets.back()->getGrid()); + } + originalLevelSet->deepCopy(levelSets.back()); + + // 3. Stage 1: u^(1) = u^n + dt * L(u^n) + Base::computeRates(maxTimeStep); + double dt = Base::getCurrentTimeStep(); + Base::updateLevelSet(dt); + + // 4. Stage 2: u^(2) = 3/4 u^n + 1/4 (u^(1) + dt * L(u^(1))) + // Calculate rates based on u^(1) (current levelSets.back()) + Base::computeRates(dt); + // Update to get u^* = u^(1) + dt * L(u^(1)) + Base::updateLevelSet(dt); + // Combine: u^(2) = 0.75 * u^n + 0.25 * u^* + combineLevelSets(0.75, originalLevelSet, 0.25, levelSets.back()); + + // 5. Stage 3: u^(n+1) = 1/3 u^n + 2/3 (u^(2) + dt * L(u^(2))) + // Calculate rates based on u^(2) (current levelSets.back()) + Base::computeRates(dt); + // Update to get u^** = u^(2) + dt * L(u^(2)) + Base::updateLevelSet(dt); + // Combine: u^(n+1) = 1/3 * u^n + 2/3 * u^** + combineLevelSets(1.0 / 3.0, originalLevelSet, 2.0 / 3.0, levelSets.back()); + + // 6. Finalize: Re-segment and renormalize only at the end + Base::rebuildLS(); + + // Adjust lower layers + if (Base::integrationScheme != + IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + for (unsigned i = 0; i < levelSets.size() - 1; ++i) { + BooleanOperation(levelSets[i], levelSets.back(), + BooleanOperationEnum::INTERSECT) + .apply(); + } + } + + return dt; + } + +}; + +PRECOMPILE_PRECISION_DIMENSION(AdvectRungeKutta3); + +} // namespace viennals \ No newline at end of file diff --git a/python/pyWrap.hpp b/python/pyWrap.hpp index b64169b9..e027c450 100644 --- a/python/pyWrap.hpp +++ b/python/pyWrap.hpp @@ -4,6 +4,8 @@ // all header files which define API functions #include +#include +#include #include #include #include @@ -180,6 +182,8 @@ template void bindApi(py::module &module) { "Set the velocity to use for advection.") .def("setAdvectionTime", &Advect::setAdvectionTime, "Set the time until when the level set should be advected.") + .def("setSingleStep", &Advect::setSingleStep, py::arg("singleStep"), + "Set whether only a single advection step should be performed.") .def("setTimeStepRatio", &Advect::setTimeStepRatio, "Set the maximum time step size relative to grid size. Advection is " "only stable for <0.5.") @@ -190,11 +194,21 @@ template void bindApi(py::module &module) { .def("setIgnoreVoids", &Advect::setIgnoreVoids, "Set whether voids in the geometry should be ignored during " "advection or not.") + .def("setAdaptiveTimeStepping", &Advect::setAdaptiveTimeStepping, + py::arg("enabled") = true, py::arg("subdivisions") = 20, + "Enable/disable adaptive time stepping and set the number of " + "subdivisions.") .def( "setSaveAdvectionVelocities", &Advect::setSaveAdvectionVelocities, "Set whether the velocities applied to each point should be saved in " "the level set for debug purposes.") + .def("setCheckDissipation", &Advect::setCheckDissipation, + py::arg("check"), + "Enable/disable dissipation checking.") + .def("setUpdatePointData", &Advect::setUpdatePointData, + py::arg("update"), + "Enable/disable updating point data after advection.") .def("getAdvectedTime", &Advect::getAdvectedTime, "Get the time passed during advection.") .def("getNumberOfTimeSteps", &Advect::getNumberOfTimeSteps, @@ -215,11 +229,28 @@ template void bindApi(py::module &module) { // need scoped release since we are calling a python method from // parallelised C++ code here .def("apply", &Advect::apply, - py::call_guard(), "Perform advection.") - .def("applyIntegration", &Advect::applyIntegration, - py::call_guard(), - "Apply the integration scheme and calculate rates and maximum time " - "step, but it do **not** move the surface."); + py::call_guard(), "Perform advection."); + + // AdvectForwardEuler + py::class_, Advect, + SmartPointer>>(module, + "AdvectForwardEuler") + .def(py::init(&SmartPointer>::template New<>)) + .def(py::init(&SmartPointer>::template New< + SmartPointer> &>)) + .def(py::init(&SmartPointer>::template New< + SmartPointer> &, + SmartPointer> &>)); + + // AdvectRungeKutta3 + py::class_, Advect, + SmartPointer>>(module, "AdvectRungeKutta3") + .def(py::init(&SmartPointer>::template New<>)) + .def(py::init(&SmartPointer>::template New< + SmartPointer> &>)) + .def(py::init(&SmartPointer>::template New< + SmartPointer> &, + SmartPointer> &>)); py::class_>( module, "StencilLocalLaxFriedrichsScalar") diff --git a/tests/Advection/Advection.cpp b/tests/Advection/Advection.cpp index f749a776..3806505c 100644 --- a/tests/Advection/Advection.cpp +++ b/tests/Advection/Advection.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include #include #include #include @@ -90,7 +90,7 @@ int main() { sphere1->getPointData().insertNextScalarData(pointIDs, "originalIDs"); } - ls::Advect advectionKernel; + ls::AdvectForwardEuler advectionKernel; advectionKernel.insertNextLevelSet(sphere1); advectionKernel.setVelocityField(velocities); advectionKernel.setIntegrationScheme(integrationScheme); diff --git a/tests/Advection/Advection.py b/tests/Advection/Advection.py new file mode 100644 index 00000000..c7b66aa1 --- /dev/null +++ b/tests/Advection/Advection.py @@ -0,0 +1,69 @@ +import viennals as vls + +vls.setDimension(3) + +# Implement own velocity field +class VelocityField(vls.VelocityField): + def __init__(self): + super().__init__() + + def getScalarVelocity(self, coord, material, normal, pointId): + # Some arbitrary velocity function + velocity = 1.0 + (2.3 if normal[0] > 0 else 0.5) * abs(normal[0] * normal[0]) + return velocity + + def getVectorVelocity(self, coord, material, normal, pointId): + return [0.0, 0.0, 0.0] + +def main(): + # Set number of threads + vls.setNumThreads(8) + + gridDelta = 0.6999999 + + integrationSchemes = [ + vls.IntegrationSchemeEnum.ENGQUIST_OSHER_1ST_ORDER, + vls.IntegrationSchemeEnum.ENGQUIST_OSHER_2ND_ORDER, + vls.IntegrationSchemeEnum.LAX_FRIEDRICHS_1ST_ORDER, + vls.IntegrationSchemeEnum.LAX_FRIEDRICHS_2ND_ORDER, + vls.IntegrationSchemeEnum.LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER, + vls.IntegrationSchemeEnum.LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER, + vls.IntegrationSchemeEnum.LOCAL_LAX_FRIEDRICHS_1ST_ORDER, + vls.IntegrationSchemeEnum.LOCAL_LAX_FRIEDRICHS_2ND_ORDER, + vls.IntegrationSchemeEnum.WENO_5TH_ORDER + ] + + for scheme in integrationSchemes: + sphere1 = vls.Domain(gridDelta) + + origin = [5.0, 0.0, 0.0] + radius = 7.3 + + vls.MakeGeometry(sphere1, vls.Sphere(origin, radius)).apply() + + # Instantiate velocities + velocities = VelocityField() + + advectionKernel = vls.AdvectRungeKutta3() + advectionKernel.insertNextLevelSet(sphere1) + advectionKernel.setVelocityField(velocities) + advectionKernel.setIntegrationScheme(scheme) + advectionKernel.setSaveAdvectionVelocities(True) + + time = 0.0 + i = 0 + while time < 1.0 and i < 100: + advectionKernel.apply() + time += advectionKernel.getAdvectedTime() + + fileName = f"{scheme}_{i}.vtp" + mesh = vls.Mesh() + vls.ToMesh(sphere1, mesh).apply() + vls.VTKWriter(mesh, "points_" + fileName).apply() + vls.ToSurfaceMesh(sphere1, mesh).apply() + vls.VTKWriter(mesh, "surface_" + fileName).apply() + i += 1 + print(f"Done scheme {scheme}") + +if __name__ == "__main__": + main() diff --git a/tests/Advection/CMakeLists.txt b/tests/Advection/CMakeLists.txt index e5de2883..1d4630e0 100644 --- a/tests/Advection/CMakeLists.txt +++ b/tests/Advection/CMakeLists.txt @@ -5,3 +5,13 @@ target_link_libraries(${PROJECT_NAME} PRIVATE ViennaLS) add_dependencies(ViennaLS_Tests ${PROJECT_NAME}) add_test(NAME ${PROJECT_NAME} COMMAND $) + +add_executable(TimeIntegrationComparison TimeIntegrationComparison.cpp) +target_link_libraries(TimeIntegrationComparison PRIVATE ViennaLS) +add_dependencies(ViennaLS_Tests TimeIntegrationComparison) +add_test(NAME TimeIntegrationComparison COMMAND $) + +add_executable(StencilLaxFriedrichsTest StencilLaxFriedrichsTest.cpp) +target_link_libraries(StencilLaxFriedrichsTest PRIVATE ViennaLS) +add_dependencies(ViennaLS_Tests StencilLaxFriedrichsTest) +add_test(NAME StencilLaxFriedrichsTest COMMAND $) diff --git a/tests/Advection/StencilLaxFriedrichsTest.cpp b/tests/Advection/StencilLaxFriedrichsTest.cpp new file mode 100644 index 00000000..7408edae --- /dev/null +++ b/tests/Advection/StencilLaxFriedrichsTest.cpp @@ -0,0 +1,81 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace ls = viennals; + +// Define a constant scalar velocity field for expansion +template class ConstantScalarVelocity : public ls::VelocityField { +public: + T getScalarVelocity(const std::array & /*coordinate*/, int /*material*/, + const std::array & /*normalVector*/, + unsigned long /*pointId*/) override { + return 1.0; + } + + std::array getVectorVelocity(const std::array & /*coordinate*/, + int /*material*/, + const std::array & /*normalVector*/, + unsigned long /*pointId*/) override { + return {0.0, 0.0, 0.0}; + } +}; + +int main() { + // Define dimension and type + constexpr int D = 3; + using T = double; + + // Define grid and domain bounds + double gridDelta = 0.1; + double bounds[2 * D] = {-3.0, 3.0, -3.0, 3.0, -3.0, 3.0}; + ls::Domain::BoundaryType boundaryCons[D]; + for (int i = 0; i < D; ++i) + boundaryCons[i] = ls::Domain::BoundaryType::INFINITE_BOUNDARY; + + // Create initial level set (Sphere) + auto sphere = ls::SmartPointer>::New(bounds, boundaryCons, gridDelta); + T origin[3] = {0.0, 0.0, 0.0}; + T radius = 1.0; + ls::MakeGeometry(sphere, ls::Sphere::New(origin, radius)).apply(); + + // Output initial geometry + auto mesh = ls::SmartPointer>::New(); + ls::ToSurfaceMesh(sphere, mesh).apply(); + ls::VTKWriter(mesh, "sphere_initial.vtp").apply(); + + // Define constant velocity field + auto velocityField = ls::SmartPointer>::New(); + + // Setup Advection + ls::AdvectForwardEuler advectionKernel; + advectionKernel.insertNextLevelSet(sphere); + advectionKernel.setVelocityField(velocityField); + advectionKernel.setAdvectionTime(0.5); + + // Set the specific integration scheme + advectionKernel.setIntegrationScheme( + ls::IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER); + + // Run Advection + std::cout << "Running Stencil Local Lax Friedrichs Advection..." << std::endl; + advectionKernel.apply(); + + // Verify the result is a valid level set + LSTEST_ASSERT_VALID_LS(sphere, T, D); + + // Output final geometry + ls::ToSurfaceMesh(sphere, mesh).apply(); + ls::VTKWriter(mesh, "sphere_final.vtp").apply(); + + std::cout << "Test passed!" << std::endl; + + return 0; +} \ No newline at end of file diff --git a/tests/Advection/TimeIntegrationComparison.cpp b/tests/Advection/TimeIntegrationComparison.cpp new file mode 100644 index 00000000..c44cbb5f --- /dev/null +++ b/tests/Advection/TimeIntegrationComparison.cpp @@ -0,0 +1,94 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ls = viennals; + +// Define a constant velocity field +template class ConstantVelocity : public ls::VelocityField { + std::array velocity; + +public: + ConstantVelocity(std::array vel) : velocity(vel) {} + + T getScalarVelocity(const std::array & /*coordinate*/, int /*material*/, + const std::array & /*normalVector*/, + unsigned long /*pointId*/) override { + return 0; + } + + std::array getVectorVelocity(const std::array & /*coordinate*/, + int /*material*/, + const std::array & /*normalVector*/, + unsigned long /*pointId*/) override { + return velocity; + } +}; + +int main() { + // Define dimension and type + constexpr int D = 3; + using T = double; + + // Define grid and domain bounds + double gridDelta = 0.1; + double bounds[2 * D] = {-5.0, 5.0, -5.0, 5.0, -5.0, 5.0}; + ls::Domain::BoundaryType boundaryCons[D]; + for (int i = 0; i < D; ++i) + boundaryCons[i] = ls::Domain::BoundaryType::INFINITE_BOUNDARY; + + // Create initial level set (Sphere) + auto sphere = ls::SmartPointer>::New(bounds, boundaryCons, gridDelta); + T origin[3] = {0.0, 0.0, 0.0}; + T radius = 1.5; + ls::MakeGeometry(sphere, ls::Sphere::New(origin, radius)).apply(); + + // Create copies for Forward Euler and RK3 + auto sphereFE = ls::SmartPointer>::New(sphere); + auto sphereRK3 = ls::SmartPointer>::New(sphere); + + // Define constant velocity field (moving in x-direction) + std::array vel = {1.0, 0.0, 0.0}; + auto velocityField = ls::SmartPointer>::New(vel); + + // Setup Advection: Forward Euler + ls::AdvectForwardEuler advectFE; + advectFE.insertNextLevelSet(sphereFE); + advectFE.setVelocityField(velocityField); + advectFE.setAdvectionTime(2.0); + advectFE.setIntegrationScheme(ls::IntegrationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER); + + // Setup Advection: Runge-Kutta 3 + ls::AdvectRungeKutta3 advectRK3; + advectRK3.insertNextLevelSet(sphereRK3); + advectRK3.setVelocityField(velocityField); + advectRK3.setAdvectionTime(2.0); + advectRK3.setIntegrationScheme(ls::IntegrationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER); + + // Run Advection + std::cout << "Running Forward Euler Advection..." << std::endl; + advectFE.apply(); + LSTEST_ASSERT_VALID_LS(sphereFE, T, D); + + auto meshFE = ls::Mesh::New(); + ls::ToSurfaceMesh(sphereFE, meshFE).apply(); + ls::VTKWriter(meshFE, "sphereFE.vtp").apply(); + + std::cout << "Running Runge-Kutta 3 Advection..." << std::endl; + advectRK3.apply(); + LSTEST_ASSERT_VALID_LS(sphereRK3, T, D); + + auto meshRK3 = ls::Mesh::New(); + ls::ToSurfaceMesh(sphereRK3, meshRK3).apply(); + ls::VTKWriter(meshRK3, "sphereRK3.vtp").apply(); + + return 0; +} diff --git a/tests/Advection/TimeIntegrationComparison.py b/tests/Advection/TimeIntegrationComparison.py new file mode 100644 index 00000000..17b4c8e0 --- /dev/null +++ b/tests/Advection/TimeIntegrationComparison.py @@ -0,0 +1,72 @@ +import viennals as vls +vls.setDimension(3) + +# Define a constant velocity field +class ConstantVelocity(vls.VelocityField): + def __init__(self, vel): + super().__init__() + self.velocity = vel + + def getScalarVelocity(self, coord, material, normal, pointId): + return 0.0 + + def getVectorVelocity(self, coord, material, normal, pointId): + return self.velocity + +def main(): + # Define grid and domain bounds + gridDelta = 0.1 + bounds = [-5.0, 5.0, -5.0, 5.0, -5.0, 5.0] + boundaryCons = [vls.BoundaryConditionEnum.INFINITE_BOUNDARY] * 3 + + # Create initial level set (Sphere) + sphere = vls.Domain(bounds, boundaryCons, gridDelta) + origin = [0.0, 0.0, 0.0] + radius = 1.5 + vls.MakeGeometry(sphere, vls.Sphere(origin, radius)).apply() + + # Create copies for Forward Euler and RK3 + sphereFE = vls.Domain(sphere) + sphereRK3 = vls.Domain(sphere) + + # Define constant velocity field (moving in x-direction) + vel = [1.0, 0.0, 0.0] + velocityField = ConstantVelocity(vel) + + # Setup Advection: Forward Euler + advectFE = vls.AdvectForwardEuler() + advectFE.insertNextLevelSet(sphereFE) + advectFE.setVelocityField(velocityField) + advectFE.setAdvectionTime(2.0) + advectFE.setIntegrationScheme(vls.IntegrationSchemeEnum.ENGQUIST_OSHER_1ST_ORDER) + + # Setup Advection: Runge-Kutta 3 + advectRK3 = vls.AdvectRungeKutta3() + advectRK3.insertNextLevelSet(sphereRK3) + advectRK3.setVelocityField(velocityField) + advectRK3.setAdvectionTime(2.0) + advectRK3.setIntegrationScheme(vls.IntegrationSchemeEnum.ENGQUIST_OSHER_1ST_ORDER) + + # Run Advection + print("Running Forward Euler Advection...") + advectFE.apply() + + checkFE = vls.Check(sphereFE) + checkFE.apply() + + meshFE = vls.Mesh() + vls.ToSurfaceMesh(sphereFE, meshFE).apply() + vls.VTKWriter(meshFE, "sphereFE.vtp").apply() + + print("Running Runge-Kutta 3 Advection...") + advectRK3.apply() + + checkRK3 = vls.Check(sphereRK3) + checkRK3.apply() + + meshRK3 = vls.Mesh() + vls.ToSurfaceMesh(sphereRK3, meshRK3).apply() + vls.VTKWriter(meshRK3, "sphereRK3.vtp").apply() + +if __name__ == "__main__": + main() From b28471661b63655ea34e7b82af223e035e133a73 Mon Sep 17 00:00:00 2001 From: filipovic Date: Mon, 15 Dec 2025 20:54:28 +0100 Subject: [PATCH 13/57] Added Runge Kutta time integration scheme --- include/viennals/lsAdvect.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index 4a1a11ae..5cc6333e 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -418,7 +418,6 @@ template class Advect { } const bool ignoreVoidPoints = ignoreVoids; const bool useAdaptiveTimeStepping = adaptiveTimeStepping; - const double atsThreshold = adaptiveTimeStepThreshold; if (!storedRates.empty()) { VIENNACORE_LOG_WARNING("Advect: Overwriting previously stored rates."); From 58628fd1188c3074b927f31d913f52c77f4dde10 Mon Sep 17 00:00:00 2001 From: filipovic Date: Mon, 15 Dec 2025 21:14:56 +0100 Subject: [PATCH 14/57] Fixed errors from previous commit and format --- include/viennals/lsAdvect.hpp | 30 ++++------------ include/viennals/lsAdvectRungeKutta3.hpp | 8 ++--- python/pyWrap.hpp | 34 ++++--------------- tests/Advection/StencilLaxFriedrichsTest.cpp | 9 ++--- tests/Advection/TimeIntegrationComparison.cpp | 11 +++--- 5 files changed, 29 insertions(+), 63 deletions(-) diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index 9c3a7684..cff662a5 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -24,7 +24,6 @@ #include #include #include -#include // Velocity accessor #include @@ -53,8 +52,6 @@ enum struct IntegrationSchemeEnum : unsigned { LOCAL_LAX_FRIEDRICHS_2ND_ORDER = 8, STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER = 9, WENO_5TH_ORDER = 10 - STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER = 9, - WENO_5TH_ORDER = 10 }; /// This class is used to advance level sets over time. @@ -220,7 +217,8 @@ template class Advect { // re-expansion T cutoff = 1.0; int finalWidth = 2; - if (integrationScheme == IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + if (integrationScheme == + IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { cutoff = 1.5; finalWidth = 3; } @@ -498,8 +496,6 @@ template class Advect { T velocity = gradNDissipation.first - gradNDissipation.second; if (velocity > 0.) { - // Case 1: Growth / Deposition (Velocity > 0) - // Limit the time step based on the standard CFL condition. // Case 1: Growth / Deposition (Velocity > 0) // Limit the time step based on the standard CFL condition. maxStepTime += cfl / velocity; @@ -507,8 +503,6 @@ template class Advect { -std::numeric_limits::max())); break; } else if (velocity == 0.) { - // Case 2: Static (Velocity == 0) - // No time step limit imposed by this point. // Case 2: Static (Velocity == 0) // No time step limit imposed by this point. maxStepTime = std::numeric_limits::max(); @@ -516,17 +510,6 @@ template class Advect { std::numeric_limits::max())); break; } else { - // Case 3: Etching (Velocity < 0) - // Retrieve the interface location of the underlying material. - T valueBelow; - if (currentLevelSetId > 0) { - iterators[currentLevelSetId - 1].goToIndicesSequential( - it.getStartIndices()); - valueBelow = iterators[currentLevelSetId - 1].getValue(); - } else { - valueBelow = std::numeric_limits::max(); - } - // Calculate the top material thickness // Case 3: Etching (Velocity < 0) // Retrieve the interface location of the underlying material. T valueBelow; @@ -541,8 +524,6 @@ template class Advect { T difference = std::abs(valueBelow - value); if (difference >= cfl) { - // Sub-case 3a: Standard Advection - // Far from interface: Use full CFL time step. // Sub-case 3a: Standard Advection // Far from interface: Use full CFL time step. maxStepTime -= cfl / velocity; @@ -550,11 +531,11 @@ template class Advect { gradNDissipation, std::numeric_limits::max())); break; - } else { // Sub-case 3b: Interface Interaction auto adaptiveFactor = 1.0 / adaptiveTimeStepSubdivisions; - if (useAdaptiveTimeStepping && difference > adaptiveFactor * cfl) { + if (useAdaptiveTimeStepping && + difference > adaptiveFactor * cfl) { // Adaptive Sub-stepping: // Approaching boundary: Force small steps to gather // flux statistics and prevent numerical overshoot ("Soft @@ -863,7 +844,8 @@ template class Advect { void setAdaptiveTimeStepping(bool aTS = true, unsigned subdivisions = 20) { adaptiveTimeStepping = aTS; if (subdivisions < 1) { - VIENNACORE_LOG_WARNING("Advect: Adaptive time stepping subdivisions must be at least 1. Setting to 1."); + VIENNACORE_LOG_WARNING("Advect: Adaptive time stepping subdivisions must " + "be at least 1. Setting to 1."); subdivisions = 1; } adaptiveTimeStepSubdivisions = subdivisions; diff --git a/include/viennals/lsAdvectRungeKutta3.hpp b/include/viennals/lsAdvectRungeKutta3.hpp index 6658e714..8cf4978c 100644 --- a/include/viennals/lsAdvectRungeKutta3.hpp +++ b/include/viennals/lsAdvectRungeKutta3.hpp @@ -30,11 +30,12 @@ template class AdvectRungeKutta3 : public Advect { using Base::velocities; private: - // Helper function for linear combination: target = wTarget * target + wSource // * source - void combineLevelSets(double wTarget, const SmartPointer> &target, - double wSource, const SmartPointer> &source) { + void combineLevelSets(double wTarget, + const SmartPointer> &target, + double wSource, + const SmartPointer> &source) { // We write the result into levelSets.back() (which is passed as 'target' or // 'source' usually, but here we assume target is the destination) // Actually, to support u_new = a*u_old + b*u_new, we should write to @@ -125,7 +126,6 @@ template class AdvectRungeKutta3 : public Advect { return dt; } - }; PRECOMPILE_PRECISION_DIMENSION(AdvectRungeKutta3); diff --git a/python/pyWrap.hpp b/python/pyWrap.hpp index c8cd2754..0f33df3d 100644 --- a/python/pyWrap.hpp +++ b/python/pyWrap.hpp @@ -204,8 +204,7 @@ template void bindApi(py::module &module) { "Set whether the velocities applied to each point should be saved in " "the level set for debug purposes.") .def("setCheckDissipation", &Advect::setCheckDissipation, - py::arg("check"), - "Enable/disable dissipation checking.") + py::arg("check"), "Enable/disable dissipation checking.") .def("setUpdatePointData", &Advect::setUpdatePointData, py::arg("update"), "Enable/disable updating point data after advection.") @@ -228,9 +227,6 @@ template void bindApi(py::module &module) { .def("setUpdatePointData", &Advect::setUpdatePointData, "Set whether the point data in the old LS should be translated to " "the advected LS. Defaults to true.") - .def("setUpdatePointData", &Advect::setUpdatePointData, - "Set whether the point data in the old LS should be translated to " - "the advected LS. Defaults to true.") .def("prepareLS", &Advect::prepareLS, "Prepare the level-set.") // need scoped release since we are calling a python method from // parallelised C++ code here @@ -244,9 +240,9 @@ template void bindApi(py::module &module) { .def(py::init(&SmartPointer>::template New<>)) .def(py::init(&SmartPointer>::template New< SmartPointer> &>)) - .def(py::init(&SmartPointer>::template New< - SmartPointer> &, - SmartPointer> &>)); + .def(py::init( + &SmartPointer>::template New< + SmartPointer> &, SmartPointer> &>)); // AdvectRungeKutta3 py::class_, Advect, @@ -254,9 +250,9 @@ template void bindApi(py::module &module) { .def(py::init(&SmartPointer>::template New<>)) .def(py::init(&SmartPointer>::template New< SmartPointer> &>)) - .def(py::init(&SmartPointer>::template New< - SmartPointer> &, - SmartPointer> &>)); + .def(py::init( + &SmartPointer>::template New< + SmartPointer> &, SmartPointer> &>)); py::class_>( module, "StencilLocalLaxFriedrichsScalar") @@ -797,10 +793,6 @@ template void bindApi(py::module &module) { py::class_, SmartPointer>>( module, "ToMultiSurfaceMesh") // constructors - .def(py::init( - &SmartPointer>::template New), - py::arg("eps") = 1e-12, py::arg("minNodeDistFactor") = 0.05) .def(py::init( &SmartPointer>::template New), @@ -809,24 +801,12 @@ template void bindApi(py::module &module) { SmartPointer> &, SmartPointer> &, double, double>), py::arg("domain"), py::arg("mesh"), py::arg("eps") = 1e-12, - py::arg("minNodeDistFactor") = 0.05) - SmartPointer> &, SmartPointer> &, - double, double>), - py::arg("domain"), py::arg("mesh"), py::arg("eps") = 1e-12, py::arg("minNodeDistFactor") = 0.05) .def(py::init(&SmartPointer>::template New< std::vector>> &, SmartPointer> &, double, double>), py::arg("domains"), py::arg("mesh"), py::arg("eps") = 1e-12, py::arg("minNodeDistFactor") = 0.05) - .def(py::init(&SmartPointer>::template New< - SmartPointer> &, double, double>), - py::arg("mesh"), py::arg("eps") = 1e-12, - py::arg("minNodeDistFactor") = 0.05) - std::vector>> &, - SmartPointer> &, double, double>), - py::arg("domains"), py::arg("mesh"), py::arg("eps") = 1e-12, - py::arg("minNodeDistFactor") = 0.05) .def(py::init(&SmartPointer>::template New< SmartPointer> &, double, double>), py::arg("mesh"), py::arg("eps") = 1e-12, diff --git a/tests/Advection/StencilLaxFriedrichsTest.cpp b/tests/Advection/StencilLaxFriedrichsTest.cpp index 7408edae..1f6e20b0 100644 --- a/tests/Advection/StencilLaxFriedrichsTest.cpp +++ b/tests/Advection/StencilLaxFriedrichsTest.cpp @@ -1,13 +1,13 @@ -#include #include +#include #include #include #include #include -#include #include #include +#include namespace ls = viennals; @@ -41,7 +41,8 @@ int main() { boundaryCons[i] = ls::Domain::BoundaryType::INFINITE_BOUNDARY; // Create initial level set (Sphere) - auto sphere = ls::SmartPointer>::New(bounds, boundaryCons, gridDelta); + auto sphere = + ls::SmartPointer>::New(bounds, boundaryCons, gridDelta); T origin[3] = {0.0, 0.0, 0.0}; T radius = 1.0; ls::MakeGeometry(sphere, ls::Sphere::New(origin, radius)).apply(); @@ -59,7 +60,7 @@ int main() { advectionKernel.insertNextLevelSet(sphere); advectionKernel.setVelocityField(velocityField); advectionKernel.setAdvectionTime(0.5); - + // Set the specific integration scheme advectionKernel.setIntegrationScheme( ls::IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER); diff --git a/tests/Advection/TimeIntegrationComparison.cpp b/tests/Advection/TimeIntegrationComparison.cpp index c44cbb5f..5b1dbe21 100644 --- a/tests/Advection/TimeIntegrationComparison.cpp +++ b/tests/Advection/TimeIntegrationComparison.cpp @@ -1,5 +1,5 @@ -#include #include +#include #include #include @@ -46,7 +46,8 @@ int main() { boundaryCons[i] = ls::Domain::BoundaryType::INFINITE_BOUNDARY; // Create initial level set (Sphere) - auto sphere = ls::SmartPointer>::New(bounds, boundaryCons, gridDelta); + auto sphere = + ls::SmartPointer>::New(bounds, boundaryCons, gridDelta); T origin[3] = {0.0, 0.0, 0.0}; T radius = 1.5; ls::MakeGeometry(sphere, ls::Sphere::New(origin, radius)).apply(); @@ -64,14 +65,16 @@ int main() { advectFE.insertNextLevelSet(sphereFE); advectFE.setVelocityField(velocityField); advectFE.setAdvectionTime(2.0); - advectFE.setIntegrationScheme(ls::IntegrationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER); + advectFE.setIntegrationScheme( + ls::IntegrationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER); // Setup Advection: Runge-Kutta 3 ls::AdvectRungeKutta3 advectRK3; advectRK3.insertNextLevelSet(sphereRK3); advectRK3.setVelocityField(velocityField); advectRK3.setAdvectionTime(2.0); - advectRK3.setIntegrationScheme(ls::IntegrationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER); + advectRK3.setIntegrationScheme( + ls::IntegrationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER); // Run Advection std::cout << "Running Forward Euler Advection..." << std::endl; From 6e29432d0dcb9ec8a27e081fdbf27a67438b4a28 Mon Sep 17 00:00:00 2001 From: Tobias Reiter Date: Tue, 16 Dec 2025 15:41:45 +0100 Subject: [PATCH 15/57] Clean up --- include/viennals/lsAdvect.hpp | 54 ++++++++++++------------ include/viennals/lsAdvectRungeKutta3.hpp | 25 +---------- 2 files changed, 30 insertions(+), 49 deletions(-) diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index cff662a5..4aa2603b 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -769,6 +769,31 @@ template class Advect { storedRates.clear(); } + /// internal function used as a wrapper to call specialized integrateTime + /// with the chosen integrationScheme + virtual double advect(double maxTimeStep) { + prepareLS(); + + if (currentTimeStep < 0. || storedRates.empty()) + computeRates(maxTimeStep); + + updateLevelSet(currentTimeStep); + + rebuildLS(); + + // Adjust all level sets below the advected one + if (integrationScheme != + IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + for (unsigned i = 0; i < levelSets.size() - 1; ++i) { + BooleanOperation(levelSets[i], levelSets.back(), + BooleanOperationEnum::INTERSECT) + .apply(); + } + } + + return currentTimeStep; + } + public: static constexpr char velocityLabel[] = "AdvectionVelocities"; static constexpr char dissipationLabel[] = "Dissipation"; @@ -943,41 +968,18 @@ template class Advect { } } - /// internal function used as a wrapper to call specialized integrateTime - /// with the chosen integrationScheme - virtual double advect(double maxTimeStep) { + void apply() { // check whether a level set and velocities have been given if (levelSets.empty()) { VIENNACORE_LOG_ERROR("No level sets passed to Advect. Not advecting."); - return std::numeric_limits::max(); + return; } if (velocities == nullptr) { VIENNACORE_LOG_ERROR( "No velocity field passed to Advect. Not advecting."); - return std::numeric_limits::max(); - } - - if (currentTimeStep < 0. || storedRates.empty()) - computeRates(maxTimeStep); - - updateLevelSet(currentTimeStep); - - rebuildLS(); - - // Adjust all level sets below the advected one - if (integrationScheme != - IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { - for (unsigned i = 0; i < levelSets.size() - 1; ++i) { - BooleanOperation(levelSets[i], levelSets.back(), - BooleanOperationEnum::INTERSECT) - .apply(); - } + return; } - return currentTimeStep; - } - - virtual void apply() { if (advectionTime == 0.) { advectedTime = advect(std::numeric_limits::max()); numberOfTimeSteps = 1; diff --git a/include/viennals/lsAdvectRungeKutta3.hpp b/include/viennals/lsAdvectRungeKutta3.hpp index 8cf4978c..ed46e848 100644 --- a/include/viennals/lsAdvectRungeKutta3.hpp +++ b/include/viennals/lsAdvectRungeKutta3.hpp @@ -14,21 +14,12 @@ template class AdvectRungeKutta3 : public Advect { viennahrle::ConstSparseIterator::DomainType>; using hrleIndexType = viennahrle::IndexType; using Base = Advect; - + using Base::levelSets; SmartPointer> originalLevelSet = nullptr; public: using Base::Base; // inherit all constructors - using Base::advectedTime; - using Base::advectionTime; - using Base::currentTimeStep; - using Base::levelSets; - using Base::numberOfTimeSteps; - using Base::performOnlySingleStep; - using Base::storedRates; - using Base::velocities; - private: // Helper function for linear combination: target = wTarget * target + wSource // * source @@ -68,25 +59,13 @@ template class AdvectRungeKutta3 : public Advect { } } -public: double advect(double maxTimeStep) override { - if (levelSets.empty()) { - VIENNACORE_LOG_ERROR("No level sets passed to Advect. Not advecting."); - return std::numeric_limits::max(); - } - if (velocities == nullptr) { - VIENNACORE_LOG_ERROR( - "No velocity field passed to Advect. Not advecting."); - return std::numeric_limits::max(); - } - // 1. Prepare and Expand Base::prepareLS(); // 2. Save u^n (Deep copy with identical topology) if (originalLevelSet == nullptr) { - originalLevelSet = - SmartPointer>::New(levelSets.back()->getGrid()); + originalLevelSet = Domain::New(levelSets.back()->getGrid()); } originalLevelSet->deepCopy(levelSets.back()); From 594cd0883c226d4a6dcba0d36d14cfe46cb422f1 Mon Sep 17 00:00:00 2001 From: Tobias Reiter Date: Tue, 16 Dec 2025 15:46:56 +0100 Subject: [PATCH 16/57] Update Python stubs --- python/viennals/__init__.pyi | 22 +++++++++-------- python/viennals/d2.pyi | 48 ++++++++++++++++++++++++++---------- python/viennals/d3.pyi | 48 ++++++++++++++++++++++++++---------- 3 files changed, 82 insertions(+), 36 deletions(-) diff --git a/python/viennals/__init__.pyi b/python/viennals/__init__.pyi index cad13652..43c7db13 100644 --- a/python/viennals/__init__.pyi +++ b/python/viennals/__init__.pyi @@ -32,6 +32,8 @@ from viennals._core import VelocityField from viennals._core import VoidTopSurfaceEnum from viennals._core import setNumThreads from viennals.d2 import Advect +from viennals.d2 import AdvectForwardEuler +from viennals.d2 import AdvectRungeKutta3 from viennals.d2 import BooleanOperation from viennals.d2 import Box from viennals.d2 import BoxDistribution @@ -82,6 +84,8 @@ from . import d3 __all__: list[str] = [ "Advect", + "AdvectForwardEuler", + "AdvectRungeKutta3", "BooleanOperation", "BooleanOperationEnum", "BoundaryConditionEnum", @@ -161,22 +165,20 @@ def getDimension() -> int: """ Get the current dimension of the simulation. - Returns - ------- - int - The currently set dimension (2 or 3). - + Returns + ------- + int + The currently set dimension (2 or 3). """ def setDimension(d: int): """ Set the dimension of the simulation (2 or 3). - Parameters - ---------- - d: int - Dimension of the simulation (2 or 3). - + Parameters + ---------- + d: int + Dimension of the simulation (2 or 3). """ PROXY_DIM: int = 2 diff --git a/python/viennals/d2.pyi b/python/viennals/d2.pyi index 05b532e2..439ce917 100644 --- a/python/viennals/d2.pyi +++ b/python/viennals/d2.pyi @@ -9,6 +9,8 @@ import viennals._core __all__: list[str] = [ "Advect", + "AdvectForwardEuler", + "AdvectRungeKutta3", "BooleanOperation", "Box", "BoxDistribution", @@ -67,11 +69,6 @@ class Advect: Perform advection. """ - def applyIntegration(self, arg0: typing.SupportsFloat) -> None: - """ - Apply the integration scheme and calculate rates and maximum time step, but it do **not** move the surface. - """ - def clearLevelSets(self) -> None: """ Clear all level sets used for advection. @@ -112,14 +109,11 @@ class Advect: Prepare the level-set. """ - def setAdaptiveTimeStepThreshold(self, arg0: typing.SupportsFloat) -> None: - """ - Set the threshold (in fraction of the CFL condition) below which adaptive time stepping is applied. Defaults to 0.05. - """ - - def setAdaptiveTimeStepping(self, arg0: bool) -> None: + def setAdaptiveTimeStepping( + self, enabled: bool = True, subdivisions: typing.SupportsInt = 20 + ) -> None: """ - Set whether adaptive time stepping should be used when approaching material boundaries during etching. + Enable/disable adaptive time stepping and set the number of subdivisions. """ def setAdvectionTime(self, arg0: typing.SupportsFloat) -> None: @@ -132,6 +126,11 @@ class Advect: Set whether normal vectors are needed for the supplied velocity field. """ + def setCheckDissipation(self, check: bool) -> None: + """ + Enable/disable dissipation checking. + """ + def setDissipationAlpha(self, arg0: typing.SupportsFloat) -> None: """ Set the dissipation value to use for Lax Friedrichs integration. @@ -152,7 +151,7 @@ class Advect: Set whether the velocities applied to each point should be saved in the level set for debug purposes. """ - def setSingleStep(self, arg0: bool) -> None: + def setSingleStep(self, singleStep: bool) -> None: """ Set whether only a single advection step should be performed. """ @@ -162,6 +161,13 @@ class Advect: Set the maximum time step size relative to grid size. Advection is only stable for <0.5. """ + @typing.overload + def setUpdatePointData(self, update: bool) -> None: + """ + Enable/disable updating point data after advection. + """ + + @typing.overload def setUpdatePointData(self, arg0: bool) -> None: """ Set whether the point data in the old LS should be translated to the advected LS. Defaults to true. @@ -172,6 +178,22 @@ class Advect: Set the velocity to use for advection. """ +class AdvectForwardEuler(Advect): + @typing.overload + def __init__(self) -> None: ... + @typing.overload + def __init__(self, arg0: Domain) -> None: ... + @typing.overload + def __init__(self, arg0: Domain, arg1: viennals._core.VelocityField) -> None: ... + +class AdvectRungeKutta3(Advect): + @typing.overload + def __init__(self) -> None: ... + @typing.overload + def __init__(self, arg0: Domain) -> None: ... + @typing.overload + def __init__(self, arg0: Domain, arg1: viennals._core.VelocityField) -> None: ... + class BooleanOperation: @typing.overload def __init__(self) -> None: ... diff --git a/python/viennals/d3.pyi b/python/viennals/d3.pyi index 88bca087..b6da65ef 100644 --- a/python/viennals/d3.pyi +++ b/python/viennals/d3.pyi @@ -9,6 +9,8 @@ import viennals._core __all__: list[str] = [ "Advect", + "AdvectForwardEuler", + "AdvectRungeKutta3", "BooleanOperation", "Box", "BoxDistribution", @@ -62,11 +64,6 @@ class Advect: Perform advection. """ - def applyIntegration(self, arg0: typing.SupportsFloat) -> None: - """ - Apply the integration scheme and calculate rates and maximum time step, but it do **not** move the surface. - """ - def clearLevelSets(self) -> None: """ Clear all level sets used for advection. @@ -107,14 +104,11 @@ class Advect: Prepare the level-set. """ - def setAdaptiveTimeStepThreshold(self, arg0: typing.SupportsFloat) -> None: - """ - Set the threshold (in fraction of the CFL condition) below which adaptive time stepping is applied. Defaults to 0.05. - """ - - def setAdaptiveTimeStepping(self, arg0: bool) -> None: + def setAdaptiveTimeStepping( + self, enabled: bool = True, subdivisions: typing.SupportsInt = 20 + ) -> None: """ - Set whether adaptive time stepping should be used when approaching material boundaries during etching. + Enable/disable adaptive time stepping and set the number of subdivisions. """ def setAdvectionTime(self, arg0: typing.SupportsFloat) -> None: @@ -127,6 +121,11 @@ class Advect: Set whether normal vectors are needed for the supplied velocity field. """ + def setCheckDissipation(self, check: bool) -> None: + """ + Enable/disable dissipation checking. + """ + def setDissipationAlpha(self, arg0: typing.SupportsFloat) -> None: """ Set the dissipation value to use for Lax Friedrichs integration. @@ -147,7 +146,7 @@ class Advect: Set whether the velocities applied to each point should be saved in the level set for debug purposes. """ - def setSingleStep(self, arg0: bool) -> None: + def setSingleStep(self, singleStep: bool) -> None: """ Set whether only a single advection step should be performed. """ @@ -157,6 +156,13 @@ class Advect: Set the maximum time step size relative to grid size. Advection is only stable for <0.5. """ + @typing.overload + def setUpdatePointData(self, update: bool) -> None: + """ + Enable/disable updating point data after advection. + """ + + @typing.overload def setUpdatePointData(self, arg0: bool) -> None: """ Set whether the point data in the old LS should be translated to the advected LS. Defaults to true. @@ -167,6 +173,22 @@ class Advect: Set the velocity to use for advection. """ +class AdvectForwardEuler(Advect): + @typing.overload + def __init__(self) -> None: ... + @typing.overload + def __init__(self, arg0: Domain) -> None: ... + @typing.overload + def __init__(self, arg0: Domain, arg1: viennals._core.VelocityField) -> None: ... + +class AdvectRungeKutta3(Advect): + @typing.overload + def __init__(self) -> None: ... + @typing.overload + def __init__(self, arg0: Domain) -> None: ... + @typing.overload + def __init__(self, arg0: Domain, arg1: viennals._core.VelocityField) -> None: ... + class BooleanOperation: @typing.overload def __init__(self) -> None: ... From 6d8767f9c2ab11520a7c94124d0ac4eb7b4f8b64 Mon Sep 17 00:00:00 2001 From: filipovic Date: Wed, 17 Dec 2025 11:15:40 +0100 Subject: [PATCH 17/57] Fixed time step differences between FE and RK3 --- .../AirGapDeposition/AirGapDeposition.cpp | 163 ++++++++++++------ include/viennals/lsAdvectRungeKutta3.hpp | 16 +- 2 files changed, 126 insertions(+), 53 deletions(-) diff --git a/examples/AirGapDeposition/AirGapDeposition.cpp b/examples/AirGapDeposition/AirGapDeposition.cpp index 21777e31..0102826f 100644 --- a/examples/AirGapDeposition/AirGapDeposition.cpp +++ b/examples/AirGapDeposition/AirGapDeposition.cpp @@ -1,6 +1,8 @@ #include #include +#include +#include #include #include #include @@ -46,10 +48,48 @@ class velocityField : public ls::VelocityField { } }; +template +double runSimulation(AdvectKernelType &kernel, + ls::SmartPointer> newLayer, + double totalTime, double outputInterval, + std::string name) { + double passedTime = 0.0; + unsigned stepCounter = 0; + + while (passedTime < totalTime) { + double dt = outputInterval; + if (passedTime + dt > totalTime) { + dt = totalTime - passedTime; + } + + kernel.setAdvectionTime(dt); + kernel.apply(); + passedTime += kernel.getAdvectedTime(); + + std::cout << "\r" << name << " Advection time: " + << std::fixed << std::setprecision(1) + << std::setw(4) << std::setfill('0') << passedTime + << " / " + << std::setw(4) << std::setfill('0') << totalTime + << "s" << std::flush; + + auto mesh = ls::SmartPointer>::New(); + ls::ToSurfaceMesh(newLayer, mesh).apply(); + ls::VTKWriter writer( + mesh, "trench_" + name + "_" + std::to_string(stepCounter) + ".vtp"); + writer.addMetaData("time", passedTime); + writer.apply(); + + ++stepCounter; + } + std::cout << std::endl; + return passedTime; +} + int main() { constexpr int D = 2; - omp_set_num_threads(2); + omp_set_num_threads(8); NumericType extent = 30; NumericType gridDelta = 0.5; @@ -107,60 +147,85 @@ int main() { // Now grow new material - // create new levelset for new material, which will be grown - // since it has to wrap around the substrate, just copy it - std::cout << "Creating new layer..." << std::endl; - auto newLayer = ls::SmartPointer>::New(substrate); + // Create copies for FE and RK simulations + std::cout << "Creating new layers..." << std::endl; + auto substrateFE = + ls::SmartPointer>::New(substrate); + auto newLayerFE = + ls::SmartPointer>::New(substrateFE); + + auto substrateRK = + ls::SmartPointer>::New(substrate); + auto newLayerRK = + ls::SmartPointer>::New(substrateRK); auto velocities = ls::SmartPointer::New(); + double totalSimulationTime = 16.5; + double outputInterval = 0.5; + std::cout << "Advecting" << std::endl; - ls::Advect advectionKernel; - - // the level set to be advected has to be inserted last - // the other could be taken as a mask layer for advection - advectionKernel.insertNextLevelSet(substrate); - advectionKernel.insertNextLevelSet(newLayer); - - advectionKernel.setVelocityField(velocities); - advectionKernel.setIgnoreVoids(true); - - // Now advect the level set 50 times, outputting every - // advection step. Save the physical time that - // passed during the advection. - NumericType passedTime = 0.; - unsigned numberOfSteps = 60; - for (unsigned i = 0; i < numberOfSteps; ++i) { - advectionKernel.apply(); - passedTime += advectionKernel.getAdvectedTime(); - - std::cout << "\rAdvection step " + std::to_string(i) + " / " - << numberOfSteps << std::flush; - auto mesh = ls::SmartPointer>::New(); - ls::ToSurfaceMesh(newLayer, mesh).apply(); - ls::VTKWriter writer(mesh, - "trench" + std::to_string(i) + ".vtp"); - writer.addMetaData("time", passedTime); + + // FE Kernel + ls::AdvectForwardEuler advectionKernelFE; + advectionKernelFE.insertNextLevelSet(substrateFE); + advectionKernelFE.insertNextLevelSet(newLayerFE); + advectionKernelFE.setVelocityField(velocities); + advectionKernelFE.setIgnoreVoids(true); + + double passedTimeFE = runSimulation(advectionKernelFE, newLayerFE, + totalSimulationTime, outputInterval, "FE"); + + // RK Kernel + ls::AdvectRungeKutta3 advectionKernelRK; + advectionKernelRK.insertNextLevelSet(substrateRK); + advectionKernelRK.insertNextLevelSet(newLayerRK); + advectionKernelRK.setVelocityField(velocities); + advectionKernelRK.setIgnoreVoids(true); + + double passedTimeRK = runSimulation(advectionKernelRK, newLayerRK, + totalSimulationTime, outputInterval, "RK"); + + std::cout << "Time passed FE: " << passedTimeFE << std::endl; + std::cout << "Time passed RK: " << passedTimeRK << std::endl; + + // FE Output + { + ls::WriteVisualizationMesh writer; + writer.insertNextLevelSet(substrateFE); + writer.insertNextLevelSet(newLayerFE); + writer.addMetaData("time", passedTimeFE); + writer.setFileName("airgap_FE"); + writer.setExtractHullMesh(true); writer.apply(); + + ls::ToMultiSurfaceMesh multiMeshKernel; + multiMeshKernel.insertNextLevelSet(substrateFE); + multiMeshKernel.insertNextLevelSet(newLayerFE); + auto multiMesh = ls::SmartPointer>::New(); + multiMeshKernel.setMesh(multiMesh); + multiMeshKernel.apply(); + ls::VTKWriter(multiMesh, "multimesh_FE.vtp").apply(); + } + + // RK Output + { + ls::WriteVisualizationMesh writer; + writer.insertNextLevelSet(substrateRK); + writer.insertNextLevelSet(newLayerRK); + writer.addMetaData("time", passedTimeRK); + writer.setFileName("airgap_RK"); + writer.setExtractHullMesh(true); + writer.apply(); + + ls::ToMultiSurfaceMesh multiMeshKernel; + multiMeshKernel.insertNextLevelSet(substrateRK); + multiMeshKernel.insertNextLevelSet(newLayerRK); + auto multiMesh = ls::SmartPointer>::New(); + multiMeshKernel.setMesh(multiMesh); + multiMeshKernel.apply(); + ls::VTKWriter(multiMesh, "multimesh_RK.vtp").apply(); } - std::cout << std::endl; - std::cout << "Time passed during advection: " << passedTime << std::endl; - - ls::WriteVisualizationMesh writer; - writer.insertNextLevelSet(substrate); - writer.insertNextLevelSet(newLayer); - writer.addMetaData("time", passedTime); - writer.setFileName("airgap"); - writer.setExtractHullMesh(true); - writer.apply(); - - ls::ToMultiSurfaceMesh multiMeshKernel; - multiMeshKernel.insertNextLevelSet(substrate); - multiMeshKernel.insertNextLevelSet(newLayer); - auto multiMesh = ls::SmartPointer>::New(); - multiMeshKernel.setMesh(multiMesh); - multiMeshKernel.apply(); - ls::VTKWriter(multiMesh, "multimesh.vtp").apply(); return 0; } diff --git a/include/viennals/lsAdvectRungeKutta3.hpp b/include/viennals/lsAdvectRungeKutta3.hpp index ed46e848..34209380 100644 --- a/include/viennals/lsAdvectRungeKutta3.hpp +++ b/include/viennals/lsAdvectRungeKutta3.hpp @@ -69,26 +69,34 @@ template class AdvectRungeKutta3 : public Advect { } originalLevelSet->deepCopy(levelSets.back()); + double limit = maxTimeStep / 3.0; + double totalDt = 0.0; + // 3. Stage 1: u^(1) = u^n + dt * L(u^n) - Base::computeRates(maxTimeStep); + Base::computeRates(limit); double dt = Base::getCurrentTimeStep(); Base::updateLevelSet(dt); + totalDt += dt; // 4. Stage 2: u^(2) = 3/4 u^n + 1/4 (u^(1) + dt * L(u^(1))) // Calculate rates based on u^(1) (current levelSets.back()) - Base::computeRates(dt); + Base::computeRates(limit); + dt = Base::getCurrentTimeStep(); // Update to get u^* = u^(1) + dt * L(u^(1)) Base::updateLevelSet(dt); // Combine: u^(2) = 0.75 * u^n + 0.25 * u^* combineLevelSets(0.75, originalLevelSet, 0.25, levelSets.back()); + totalDt += dt; // 5. Stage 3: u^(n+1) = 1/3 u^n + 2/3 (u^(2) + dt * L(u^(2))) // Calculate rates based on u^(2) (current levelSets.back()) - Base::computeRates(dt); + Base::computeRates(limit); + dt = Base::getCurrentTimeStep(); // Update to get u^** = u^(2) + dt * L(u^(2)) Base::updateLevelSet(dt); // Combine: u^(n+1) = 1/3 * u^n + 2/3 * u^** combineLevelSets(1.0 / 3.0, originalLevelSet, 2.0 / 3.0, levelSets.back()); + totalDt += dt; // 6. Finalize: Re-segment and renormalize only at the end Base::rebuildLS(); @@ -103,7 +111,7 @@ template class AdvectRungeKutta3 : public Advect { } } - return dt; + return totalDt; } }; From 7cce26b66981c1b8bca3bfd9284a3415d6ccea62 Mon Sep 17 00:00:00 2001 From: filipovic Date: Wed, 17 Dec 2025 11:19:30 +0100 Subject: [PATCH 18/57] Format AirGapDeposition example --- examples/AirGapDeposition/AirGapDeposition.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/examples/AirGapDeposition/AirGapDeposition.cpp b/examples/AirGapDeposition/AirGapDeposition.cpp index 0102826f..62c0d49c 100644 --- a/examples/AirGapDeposition/AirGapDeposition.cpp +++ b/examples/AirGapDeposition/AirGapDeposition.cpp @@ -66,12 +66,10 @@ double runSimulation(AdvectKernelType &kernel, kernel.apply(); passedTime += kernel.getAdvectedTime(); - std::cout << "\r" << name << " Advection time: " - << std::fixed << std::setprecision(1) - << std::setw(4) << std::setfill('0') << passedTime - << " / " - << std::setw(4) << std::setfill('0') << totalTime - << "s" << std::flush; + std::cout << "\r" << name << " Advection time: " << std::fixed + << std::setprecision(1) << std::setw(4) << std::setfill('0') + << passedTime << " / " << std::setw(4) << std::setfill('0') + << totalTime << "s" << std::flush; auto mesh = ls::SmartPointer>::New(); ls::ToSurfaceMesh(newLayer, mesh).apply(); @@ -173,8 +171,8 @@ int main() { advectionKernelFE.setVelocityField(velocities); advectionKernelFE.setIgnoreVoids(true); - double passedTimeFE = runSimulation(advectionKernelFE, newLayerFE, - totalSimulationTime, outputInterval, "FE"); + double passedTimeFE = runSimulation( + advectionKernelFE, newLayerFE, totalSimulationTime, outputInterval, "FE"); // RK Kernel ls::AdvectRungeKutta3 advectionKernelRK; @@ -183,8 +181,8 @@ int main() { advectionKernelRK.setVelocityField(velocities); advectionKernelRK.setIgnoreVoids(true); - double passedTimeRK = runSimulation(advectionKernelRK, newLayerRK, - totalSimulationTime, outputInterval, "RK"); + double passedTimeRK = runSimulation( + advectionKernelRK, newLayerRK, totalSimulationTime, outputInterval, "RK"); std::cout << "Time passed FE: " << passedTimeFE << std::endl; std::cout << "Time passed RK: " << passedTimeRK << std::endl; From d276af68d19d7ee4632ffd606229e3422744f98a Mon Sep 17 00:00:00 2001 From: Roman Kostal Date: Thu, 18 Dec 2025 15:20:27 +0100 Subject: [PATCH 19/57] rename integration -> discretization where appropriate --- PythonAPI.md | 2 +- examples/Deposition/Deposition.cpp | 4 +- examples/Epitaxy/Epitaxy.cpp | 4 +- examples/Epitaxy/Epitaxy.py | 2 +- .../PeriodicBoundary/PeriodicBoundary.cpp | 4 +- examples/SquareEtch/SquareEtch.cpp | 14 +- examples/SquareEtch/SquareEtch.py | 4 +- include/viennals/lsAdvect.hpp | 141 +++++++++--------- include/viennals/lsAdvectForwardEuler.hpp | 2 +- include/viennals/lsAdvectRungeKutta3.hpp | 4 +- include/viennals/lsEngquistOsher.hpp | 6 +- include/viennals/lsFiniteDifferences.hpp | 6 +- include/viennals/lsLaxFriedrichs.hpp | 4 +- include/viennals/lsLocalLaxFriedrichs.hpp | 4 +- .../lsLocalLaxFriedrichsAnalytical.hpp | 8 +- .../viennals/lsLocalLocalLaxFriedrichs.hpp | 6 +- .../lsStencilLocalLaxFriedrichsScalar.hpp | 4 +- include/viennals/lsVelocityField.hpp | 6 +- python/pyWrap.cpp | 24 +-- python/pyWrap.hpp | 7 +- python/viennals/__init__.pyi | 4 +- python/viennals/_core.pyi | 48 +++--- python/viennals/d2.pyi | 6 +- python/viennals/d3.pyi | 6 +- tests/Advection/Advection.cpp | 26 ++-- tests/Advection/Advection.py | 24 +-- tests/Advection/StencilLaxFriedrichsTest.cpp | 6 +- tests/Advection/TimeIntegrationComparison.cpp | 8 +- tests/Advection/TimeIntegrationComparison.py | 4 +- tests/Advection2D/Advection2D.cpp | 4 +- .../GeometricAdvectPerformance.cpp | 4 +- 31 files changed, 202 insertions(+), 194 deletions(-) diff --git a/PythonAPI.md b/PythonAPI.md index ac7c7ad6..869b6bd2 100644 --- a/PythonAPI.md +++ b/PythonAPI.md @@ -60,7 +60,7 @@ These are imported directly from the **root** `viennals` module. ### **Enums** - `LogLevel` -- `IntegrationSchemeEnum` +- `DiscretizationSchemeEnum` - `BooleanOperationEnum` - `CurvatureEnum` - `FeatureDetectionEnum` diff --git a/examples/Deposition/Deposition.cpp b/examples/Deposition/Deposition.cpp index 7b087f89..1835ab74 100644 --- a/examples/Deposition/Deposition.cpp +++ b/examples/Deposition/Deposition.cpp @@ -113,8 +113,8 @@ int main() { advectionKernel.setVelocityField(velocities); // advectionKernel.setAdvectionTime(4.); unsigned counter = 1; - advectionKernel.setIntegrationScheme( - ls::IntegrationSchemeEnum::WENO_5TH_ORDER); + advectionKernel.setDiscretizationScheme( + ls::DiscretizationSchemeEnum::WENO_5TH_ORDER); for (NumericType time = 0; time < 4.; time += advectionKernel.getAdvectedTime()) { advectionKernel.apply(); diff --git a/examples/Epitaxy/Epitaxy.cpp b/examples/Epitaxy/Epitaxy.cpp index 5b8cb2a5..45674b01 100644 --- a/examples/Epitaxy/Epitaxy.cpp +++ b/examples/Epitaxy/Epitaxy.cpp @@ -82,8 +82,8 @@ int main(int argc, char *argv[]) { auto velocityField = SmartPointer::New(std::vector{0., -.5}); Advect advectionKernel(levelSets, velocityField); - advectionKernel.setIntegrationScheme( - IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER); + advectionKernel.setDiscretizationScheme( + DiscretizationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER); advectionKernel.setAdvectionTime(.5); Timer timer; diff --git a/examples/Epitaxy/Epitaxy.py b/examples/Epitaxy/Epitaxy.py index d5990c12..3eb726cc 100644 --- a/examples/Epitaxy/Epitaxy.py +++ b/examples/Epitaxy/Epitaxy.py @@ -80,7 +80,7 @@ def main(): for ls in level_sets: advection.insertNextLevelSet(ls) advection.setVelocityField(velocity_field) - advection.setIntegrationScheme(vls.IntegrationSchemeEnum.STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) + advection.setDiscretizationScheme(vls.DiscretizationSchemeEnum.STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) advection.setAdvectionTime(0.5) start_time = time.time() diff --git a/examples/PeriodicBoundary/PeriodicBoundary.cpp b/examples/PeriodicBoundary/PeriodicBoundary.cpp index 6d47107d..0dc914b3 100644 --- a/examples/PeriodicBoundary/PeriodicBoundary.cpp +++ b/examples/PeriodicBoundary/PeriodicBoundary.cpp @@ -90,8 +90,8 @@ int main() { ls::Advect advectionKernel; advectionKernel.insertNextLevelSet(substrate); advectionKernel.setVelocityField(velocities); - advectionKernel.setIntegrationScheme( - ls::IntegrationSchemeEnum::ENGQUIST_OSHER_2ND_ORDER); + advectionKernel.setDiscretizationScheme( + ls::DiscretizationSchemeEnum::ENGQUIST_OSHER_2ND_ORDER); // Now advect the level set 50 times, outputting every // advection step. Save the physical time that diff --git a/examples/SquareEtch/SquareEtch.cpp b/examples/SquareEtch/SquareEtch.cpp index a63d8780..b20b7812 100644 --- a/examples/SquareEtch/SquareEtch.cpp +++ b/examples/SquareEtch/SquareEtch.cpp @@ -171,21 +171,21 @@ int main() { if (useAnalyticalVelocity) { advectionKernel.setVelocityField(analyticalVelocities); // Analytical velocity fields and dissipation coefficients - // can only be used with this integration scheme - advectionKernel.setIntegrationScheme( - // ls::IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER); - ls::IntegrationSchemeEnum::WENO_5TH_ORDER); + // can only be used with this spatial discretization scheme + advectionKernel.setDiscretizationScheme( + // ls::DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER); + ls::DiscretizationSchemeEnum::WENO_5TH_ORDER); } else { // for numerical velocities, just use the default - // integration scheme, which is not accurate for certain + // spatial discretization scheme, which is not accurate for certain // velocity functions but very fast advectionKernel.setVelocityField(velocities); // For coordinate independent velocity functions // this numerical scheme is superior though. // However, it is slower. - // advectionKernel.setIntegrationScheme( - // ls::IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER); + // advectionKernel.setDiscretizationScheme( + // ls::DiscretizationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER); } // advect the level set until 50s have passed diff --git a/examples/SquareEtch/SquareEtch.py b/examples/SquareEtch/SquareEtch.py index c0f190af..6d40022c 100644 --- a/examples/SquareEtch/SquareEtch.py +++ b/examples/SquareEtch/SquareEtch.py @@ -143,8 +143,8 @@ def main(): advectionKernel.setSaveAdvectionVelocities(True) advectionKernel.setVelocityField(etching) - # Use default integration scheme (Lax Friedrichs 1st order) as in the C++ else branch - # advectionKernel.setIntegrationScheme(viennals.IntegrationSchemeEnum.LAX_FRIEDRICHS_1ST_ORDER) + # Use default spatial discretization scheme (Lax Friedrichs 1st order) as in the C++ else branch + # advectionKernel.setDiscretizationScheme(viennals.DiscretizationSchemeEnum.LAX_FRIEDRICHS_1ST_ORDER) # 7. Time Loop finalTime = 10.0 diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index 4aa2603b..ca6b9d58 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -16,7 +16,7 @@ #include #include -// Integration schemes +// Spatial discretization schemes #include #include #include @@ -38,9 +38,9 @@ namespace viennals { using namespace viennacore; -/// Enumeration for the different Integration schemes +/// Enumeration for the different spatial discretization schemes /// used by the advection kernel -enum struct IntegrationSchemeEnum : unsigned { +enum struct DiscretizationSchemeEnum : unsigned { ENGQUIST_OSHER_1ST_ORDER = 0, ENGQUIST_OSHER_2ND_ORDER = 1, LAX_FRIEDRICHS_1ST_ORDER = 2, @@ -71,8 +71,8 @@ template class Advect { protected: std::vector>> levelSets; SmartPointer> velocities = nullptr; - IntegrationSchemeEnum integrationScheme = - IntegrationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER; + DiscretizationSchemeEnum discretizationScheme = + DiscretizationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER; double timeStepRatio = 0.4999; double dissipationAlpha = 1.0; bool calculateNormalVectors = true; @@ -213,12 +213,12 @@ template class Advect { auto &newDomain = newlsDomain->getDomain(); auto &domain = levelSets.back()->getDomain(); - // Determine cutoff and width based on integration scheme to avoid immediate - // re-expansion + // Determine cutoff and width based on discretization scheme to avoid + // immediate re-expansion T cutoff = 1.0; int finalWidth = 2; - if (integrationScheme == - IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + if (discretizationScheme == + DiscretizationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { cutoff = 1.5; finalWidth = 3; } @@ -396,10 +396,10 @@ template class Advect { } /// Internal function used to calculate the deltas to be applied to the LS - /// values from the given velocities and the integration scheme to be used. + /// values from the given velocities and the spatial discretization scheme to be used. /// This function fills up the storedRates to be used when moving the LS - template - double integrateTime(IntegrationSchemeType IntegrationScheme, + template + double integrateTime(DiscretizationSchemeType discretizationScheme, double maxTimeStep) { auto &topDomain = levelSets.back()->getDomain(); @@ -457,7 +457,7 @@ template class Advect { iterators.emplace_back(ls->getDomain()); } - IntegrationSchemeType scheme(IntegrationScheme); + DiscretizationSchemeType scheme(discretizationScheme); for (ConstSparseIterator it(topDomain, startVector); it.getStartIndices() < endVector; ++it) { @@ -580,71 +580,74 @@ template class Advect { return maxTimeStep; } - /// This function applies the integration scheme and calculates the rates and - /// the maximum time step, but it does **not** move the surface. + /// This function applies the discretization scheme and calculates the rates + /// and the maximum time step, but it does **not** move the surface. void computeRates(double maxTimeStep = std::numeric_limits::max()) { prepareLS(); - if (integrationScheme == IntegrationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER) { + if (discretizationScheme == + DiscretizationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER) { auto is = lsInternal::EngquistOsher(levelSets.back(), velocities, calculateNormalVectors); currentTimeStep = integrateTime(is, maxTimeStep); - } else if (integrationScheme == - IntegrationSchemeEnum::ENGQUIST_OSHER_2ND_ORDER) { + } else if (discretizationScheme == + DiscretizationSchemeEnum::ENGQUIST_OSHER_2ND_ORDER) { auto is = lsInternal::EngquistOsher(levelSets.back(), velocities, calculateNormalVectors); currentTimeStep = integrateTime(is, maxTimeStep); - } else if (integrationScheme == - IntegrationSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER) { + } else if (discretizationScheme == + DiscretizationSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER) { auto alphas = findGlobalAlphas(); auto is = lsInternal::LaxFriedrichs(levelSets.back(), velocities, dissipationAlpha, alphas, calculateNormalVectors); currentTimeStep = integrateTime(is, maxTimeStep); - } else if (integrationScheme == - IntegrationSchemeEnum::LAX_FRIEDRICHS_2ND_ORDER) { + } else if (discretizationScheme == + DiscretizationSchemeEnum::LAX_FRIEDRICHS_2ND_ORDER) { auto alphas = findGlobalAlphas(); auto is = lsInternal::LaxFriedrichs(levelSets.back(), velocities, dissipationAlpha, alphas, calculateNormalVectors); currentTimeStep = integrateTime(is, maxTimeStep); - } else if (integrationScheme == - IntegrationSchemeEnum:: + } else if (discretizationScheme == + DiscretizationSchemeEnum:: LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER) { auto is = lsInternal::LocalLaxFriedrichsAnalytical( levelSets.back(), velocities); currentTimeStep = integrateTime(is, maxTimeStep); - } else if (integrationScheme == - IntegrationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + } else if (discretizationScheme == + DiscretizationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { auto is = lsInternal::LocalLocalLaxFriedrichs( levelSets.back(), velocities, dissipationAlpha); currentTimeStep = integrateTime(is, maxTimeStep); - } else if (integrationScheme == - IntegrationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER) { + } else if (discretizationScheme == + DiscretizationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER) { auto is = lsInternal::LocalLocalLaxFriedrichs( levelSets.back(), velocities, dissipationAlpha); currentTimeStep = integrateTime(is, maxTimeStep); - } else if (integrationScheme == - IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + } else if (discretizationScheme == + DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { auto is = lsInternal::LocalLaxFriedrichs( levelSets.back(), velocities, dissipationAlpha); currentTimeStep = integrateTime(is, maxTimeStep); - } else if (integrationScheme == - IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER) { + } else if (discretizationScheme == + DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER) { auto is = lsInternal::LocalLaxFriedrichs( levelSets.back(), velocities, dissipationAlpha); currentTimeStep = integrateTime(is, maxTimeStep); - } else if (integrationScheme == - IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + } else if (discretizationScheme == + DiscretizationSchemeEnum:: + STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { auto is = lsInternal::StencilLocalLaxFriedrichsScalar( levelSets.back(), velocities, dissipationAlpha); currentTimeStep = integrateTime(is, maxTimeStep); - } else if (integrationScheme == IntegrationSchemeEnum::WENO_5TH_ORDER) { + } else if (discretizationScheme == + DiscretizationSchemeEnum::WENO_5TH_ORDER) { // Instantiate WENO5 with order 3 (neighbors +/- 3) auto is = lsInternal::WENO5(levelSets.back(), velocities, dissipationAlpha); currentTimeStep = integrateTime(is, maxTimeStep); } else { - VIENNACORE_LOG_ERROR("Advect: Integration scheme not found."); + VIENNACORE_LOG_ERROR("Advect: Discretization scheme not found."); currentTimeStep = -1.; } } @@ -770,7 +773,7 @@ template class Advect { } /// internal function used as a wrapper to call specialized integrateTime - /// with the chosen integrationScheme + /// with the chosen spatial discretization scheme virtual double advect(double maxTimeStep) { prepareLS(); @@ -782,8 +785,8 @@ template class Advect { rebuildLS(); // Adjust all level sets below the advected one - if (integrationScheme != - IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + if (discretizationScheme != + DiscretizationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { for (unsigned i = 0; i < levelSets.size() - 1; ++i) { BooleanOperation(levelSets[i], levelSets.back(), BooleanOperationEnum::INTERSECT) @@ -896,10 +899,10 @@ template class Advect { /// Get whether normal vectors were calculated. bool getCalculateNormalVectors() const { return calculateNormalVectors; } - /// Set which integration scheme should be used out of the ones specified - /// in IntegrationSchemeEnum. - void setIntegrationScheme(IntegrationSchemeEnum scheme) { - integrationScheme = scheme; + /// Set which spatial discretization scheme should be used out of the ones + /// specified in DiscretizationSchemeEnum. + void setDiscretizationScheme(DiscretizationSchemeEnum scheme) { + discretizationScheme = scheme; } /// Set the alpha dissipation coefficient. @@ -915,8 +918,8 @@ template class Advect { /// be translated to the advected LS. Defaults to true. void setUpdatePointData(bool update) { updatePointData = update; } - // Prepare the levelset for advection, based on the provided integration - // scheme. + // Prepare the levelset for advection, based on the provided spatial + // discretization scheme. void prepareLS() { // check whether a level set and velocities have been given if (levelSets.empty()) { @@ -925,46 +928,50 @@ template class Advect { return; } - if (integrationScheme == IntegrationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER) { + if (discretizationScheme == + DiscretizationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER) { lsInternal::EngquistOsher::prepareLS(levelSets.back()); - } else if (integrationScheme == - IntegrationSchemeEnum::ENGQUIST_OSHER_2ND_ORDER) { + } else if (discretizationScheme == + DiscretizationSchemeEnum::ENGQUIST_OSHER_2ND_ORDER) { lsInternal::EngquistOsher::prepareLS(levelSets.back()); - } else if (integrationScheme == - IntegrationSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER) { + } else if (discretizationScheme == + DiscretizationSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER) { lsInternal::LaxFriedrichs::prepareLS(levelSets.back()); - } else if (integrationScheme == - IntegrationSchemeEnum::LAX_FRIEDRICHS_2ND_ORDER) { + } else if (discretizationScheme == + DiscretizationSchemeEnum::LAX_FRIEDRICHS_2ND_ORDER) { lsInternal::LaxFriedrichs::prepareLS(levelSets.back()); - } else if (integrationScheme == - IntegrationSchemeEnum:: + } else if (discretizationScheme == + DiscretizationSchemeEnum:: LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER) { lsInternal::LocalLaxFriedrichsAnalytical::prepareLS( levelSets.back()); - } else if (integrationScheme == - IntegrationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + } else if (discretizationScheme == + DiscretizationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { lsInternal::LocalLocalLaxFriedrichs::prepareLS(levelSets.back()); - } else if (integrationScheme == - IntegrationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER) { + } else if (discretizationScheme == + DiscretizationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER) { lsInternal::LocalLocalLaxFriedrichs::prepareLS(levelSets.back()); - } else if (integrationScheme == - IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + } else if (discretizationScheme == + DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { lsInternal::LocalLaxFriedrichs::prepareLS(levelSets.back()); - } else if (integrationScheme == - IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER) { + } else if (discretizationScheme == + DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER) { lsInternal::LocalLaxFriedrichs::prepareLS(levelSets.back()); - } else if (integrationScheme == - IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + } else if (discretizationScheme == + DiscretizationSchemeEnum:: + STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { lsInternal::StencilLocalLaxFriedrichsScalar::prepareLS( levelSets.back()); - } else if (integrationScheme == IntegrationSchemeEnum::WENO_5TH_ORDER) { + } else if (discretizationScheme == + DiscretizationSchemeEnum::WENO_5TH_ORDER) { // WENO5 requires a stencil radius of 3 (template parameter 3) lsInternal::WENO5::prepareLS(levelSets.back()); - } else if (integrationScheme == IntegrationSchemeEnum::WENO_5TH_ORDER) { + } else if (discretizationScheme == + DiscretizationSchemeEnum::WENO_5TH_ORDER) { // WENO5 requires a stencil radius of 3 (template parameter 3) lsInternal::WENO5::prepareLS(levelSets.back()); } else { - VIENNACORE_LOG_ERROR("Advect: Integration scheme not found."); + VIENNACORE_LOG_ERROR("Advect: Discretization scheme not found."); } } diff --git a/include/viennals/lsAdvectForwardEuler.hpp b/include/viennals/lsAdvectForwardEuler.hpp index 4c608530..6530f64f 100644 --- a/include/viennals/lsAdvectForwardEuler.hpp +++ b/include/viennals/lsAdvectForwardEuler.hpp @@ -11,4 +11,4 @@ template class AdvectForwardEuler : public Advect { PRECOMPILE_PRECISION_DIMENSION(AdvectForwardEuler); -} // namespace viennals \ No newline at end of file +} // namespace viennals diff --git a/include/viennals/lsAdvectRungeKutta3.hpp b/include/viennals/lsAdvectRungeKutta3.hpp index 34209380..51bfb048 100644 --- a/include/viennals/lsAdvectRungeKutta3.hpp +++ b/include/viennals/lsAdvectRungeKutta3.hpp @@ -102,8 +102,8 @@ template class AdvectRungeKutta3 : public Advect { Base::rebuildLS(); // Adjust lower layers - if (Base::integrationScheme != - IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + if (Base::discretizationScheme != + DiscretizationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { for (unsigned i = 0; i < levelSets.size() - 1; ++i) { BooleanOperation(levelSets[i], levelSets.back(), BooleanOperationEnum::INTERSECT) diff --git a/include/viennals/lsEngquistOsher.hpp b/include/viennals/lsEngquistOsher.hpp index f31bcfac..f61c5d0a 100644 --- a/include/viennals/lsEngquistOsher.hpp +++ b/include/viennals/lsEngquistOsher.hpp @@ -12,8 +12,8 @@ namespace lsInternal { using namespace viennacore; -/// Engquist-Osher integration scheme based on the -/// upwind integration scheme. Offers high performance +/// Engquist-Osher spatial discretization scheme based on the +/// upwind spatial discretization scheme. Offers high performance /// but lower accuracy for complex velocity fields. template class EngquistOsher { SmartPointer> levelSet; @@ -69,7 +69,7 @@ template class EngquistOsher { T diffPos = (phiPos - phi0) / deltaPos; T diffNeg = (phiNeg - phi0) / deltaNeg; - if (order == 2) { // if second order time integration scheme is used + if (order == 2) { // if second order spatial discretization scheme is used const T deltaPosPos = 2 * gridDelta; const T deltaNegNeg = -2 * gridDelta; diff --git a/include/viennals/lsFiniteDifferences.hpp b/include/viennals/lsFiniteDifferences.hpp index 79cc2ad6..e4258ef7 100644 --- a/include/viennals/lsFiniteDifferences.hpp +++ b/include/viennals/lsFiniteDifferences.hpp @@ -154,7 +154,7 @@ class FiniteDifferences { if constexpr (scheme == DifferentiationSchemeEnum::FIRST_ORDER) { return (values[1] - values[0]) / delta; } else if (scheme == DifferentiationSchemeEnum::SECOND_ORDER) { - // TODO: implement second order integration here + // TODO: implement second order differentiation here Logger::getInstance().addError("Second order scheme not implemented!"); return 0; } else if (scheme == DifferentiationSchemeEnum::WENO3) { @@ -177,8 +177,8 @@ class FiniteDifferences { if constexpr (scheme == DifferentiationSchemeEnum::FIRST_ORDER) { return (values[2] - values[1]) / delta; } else if (scheme == DifferentiationSchemeEnum::SECOND_ORDER) { - // TODO: implement second order integration here - Logger::getInstance().addError("Seconed order scheme not implemented!"); + // TODO: implement second order differentiation here + Logger::getInstance().addError("Second order scheme not implemented!"); return 0; } else if (scheme == DifferentiationSchemeEnum::WENO3) { return weno3(values, delta, true); diff --git a/include/viennals/lsLaxFriedrichs.hpp b/include/viennals/lsLaxFriedrichs.hpp index 45c9d929..7320b518 100644 --- a/include/viennals/lsLaxFriedrichs.hpp +++ b/include/viennals/lsLaxFriedrichs.hpp @@ -12,7 +12,7 @@ namespace lsInternal { using namespace viennacore; -/// Lax Friedrichs integration scheme with constant alpha +/// Lax Friedrichs spatial discretization scheme with constant alpha /// value for dissipation. This alpha value should be fitted /// based on the results of the advection and passed to the /// advection Kernel. @@ -76,7 +76,7 @@ template class LaxFriedrichs { T diffPos = (phiPos - phi0) / deltaPos; T diffNeg = (phiNeg - phi0) / deltaNeg; - if (order == 2) { // if second order time integration scheme is used + if (order == 2) { // if second order spatial discretization scheme is used const T deltaPosPos = 2 * gridDelta; const T deltaNegNeg = -2 * gridDelta; diff --git a/include/viennals/lsLocalLaxFriedrichs.hpp b/include/viennals/lsLocalLaxFriedrichs.hpp index 7cb64220..e73a9809 100644 --- a/include/viennals/lsLocalLaxFriedrichs.hpp +++ b/include/viennals/lsLocalLaxFriedrichs.hpp @@ -12,7 +12,7 @@ namespace lsInternal { using namespace viennacore; -/// Lax Friedrichs integration scheme, which uses a first neighbour +/// Lax Friedrichs spatial discretization scheme, which uses a first neighbour /// stencil to calculate the alpha values for all neighbours. /// The largest alpha value is then chosen for dissipation. /// Slower than lsLocalLocalLaxFriedrichs or lsEngquistOsher @@ -105,7 +105,7 @@ template class LocalLaxFriedrichs { T diffPos = (phiPos - phi0) / deltaPos; T diffNeg = (phiNeg - phi0) / deltaNeg; - if (order == 2) { // if second order time integration scheme is used + if (order == 2) { // if second order spatial discretization scheme is used posUnit[i] = 2; negUnit[i] = -2; diff --git a/include/viennals/lsLocalLaxFriedrichsAnalytical.hpp b/include/viennals/lsLocalLaxFriedrichsAnalytical.hpp index 02815238..1e276fc4 100644 --- a/include/viennals/lsLocalLaxFriedrichsAnalytical.hpp +++ b/include/viennals/lsLocalLaxFriedrichsAnalytical.hpp @@ -12,11 +12,11 @@ namespace lsInternal { using namespace viennacore; -/// Lax Friedrichs integration scheme, which uses alpha values +/// Lax Friedrichs spatial discretization scheme, which uses alpha values /// provided by the user in getDissipationAlphas in lsVelocityField. /// If it is possible to derive analytical solutions for the velocityField -/// and the alpha values, this integration scheme should be used and never -/// otherwise. +/// and the alpha values, this spatial discretization scheme should be used and +/// never otherwise. template class LocalLaxFriedrichsAnalytical { SmartPointer> levelSet; SmartPointer> velocities; @@ -104,7 +104,7 @@ template class LocalLaxFriedrichsAnalytical { T diffPos = (phiPos - phi0) / deltaPos; T diffNeg = (phiNeg - phi0) / deltaNeg; - if (order == 2) { // if second order time integration scheme is used + if (order == 2) { // if second order spatial discretization scheme is used posUnit[i] = 2; negUnit[i] = -2; diff --git a/include/viennals/lsLocalLocalLaxFriedrichs.hpp b/include/viennals/lsLocalLocalLaxFriedrichs.hpp index 7d4493e5..4361172f 100644 --- a/include/viennals/lsLocalLocalLaxFriedrichs.hpp +++ b/include/viennals/lsLocalLocalLaxFriedrichs.hpp @@ -12,8 +12,8 @@ namespace lsInternal { using namespace viennacore; -/// Lax Friedrichs integration scheme, which considers only the current -/// point for alpha calculation. Faster than lsLocalLaxFriedrichs but +/// Lax Friedrichs spatial discretization scheme, which considers only the +/// current point for alpha calculation. Faster than lsLocalLaxFriedrichs but /// not as accurate. template class LocalLocalLaxFriedrichs { SmartPointer> levelSet; @@ -79,7 +79,7 @@ template class LocalLocalLaxFriedrichs { T diffPos = (phiPos - phi0) / deltaPos; T diffNeg = (phiNeg - phi0) / deltaNeg; - if (order == 2) { // if second order time integration scheme is used + if (order == 2) { // if second order spatial discretization scheme is used const T deltaPosPos = 2 * gridDelta; const T deltaNegNeg = -2 * gridDelta; diff --git a/include/viennals/lsStencilLocalLaxFriedrichsScalar.hpp b/include/viennals/lsStencilLocalLaxFriedrichsScalar.hpp index 911701a8..1393cbb9 100644 --- a/include/viennals/lsStencilLocalLaxFriedrichsScalar.hpp +++ b/include/viennals/lsStencilLocalLaxFriedrichsScalar.hpp @@ -21,7 +21,7 @@ namespace lsInternal { using namespace viennacore; -/// Stencil Local Lax Friedrichs Integration Scheme. +/// Stencil Local Lax Friedrichs Discretization Scheme. /// It uses a stencil of order around active points, in order to /// evaluate dissipation values for each point, taking into account /// the mathematical nature of the speed function. @@ -447,7 +447,7 @@ namespace viennals { using namespace viennacore; /// This function creates the specialized layer wrapping which -/// produces better results for the SSLF integration scheme. +/// produces better results for the SSLF spatial discretization scheme. /// isDepo must contain whether the corresponding level sets /// are used for deposition or not. /// This function assumes that the layers where deposition is diff --git a/include/viennals/lsVelocityField.hpp b/include/viennals/lsVelocityField.hpp index 50444a1c..3b853690 100644 --- a/include/viennals/lsVelocityField.hpp +++ b/include/viennals/lsVelocityField.hpp @@ -29,9 +29,9 @@ template class VelocityField { return Vec3D{0, 0, 0}; } - /// If lsLocalLaxFriedrichsAnalytical is used as the advection scheme, - /// this is called to provide the analytical solution for the alpha - /// values, needed for stable integration. + /// If lsLocalLaxFriedrichsAnalytical is used as the spatial discretization + /// scheme, this is called to provide the analytical solution for the alpha + /// values, needed for numerical stability. virtual T getDissipationAlpha(int /*direction*/, int /*material*/, const Vec3D & /*centralDifferences*/) { return 0; diff --git a/python/pyWrap.cpp b/python/pyWrap.cpp index 332cb05c..42489b71 100644 --- a/python/pyWrap.cpp +++ b/python/pyWrap.cpp @@ -81,29 +81,29 @@ PYBIND11_MODULE(VIENNALS_MODULE_NAME, module) { .def("print", [](Logger &instance) { instance.print(std::cout); }); // ------ ENUMS ------ - py::native_enum(module, "IntegrationSchemeEnum", + py::native_enum(module, "DiscretizationSchemeEnum", "enum.IntEnum") .value("ENGQUIST_OSHER_1ST_ORDER", - IntegrationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER) + DiscretizationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER) .value("ENGQUIST_OSHER_2ND_ORDER", - IntegrationSchemeEnum::ENGQUIST_OSHER_2ND_ORDER) + DiscretizationSchemeEnum::ENGQUIST_OSHER_2ND_ORDER) .value("LAX_FRIEDRICHS_1ST_ORDER", - IntegrationSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER) + DiscretizationSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER) .value("LAX_FRIEDRICHS_2ND_ORDER", - IntegrationSchemeEnum::LAX_FRIEDRICHS_2ND_ORDER) + DiscretizationSchemeEnum::LAX_FRIEDRICHS_2ND_ORDER) .value("LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER", - IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER) + DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER) .value("LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER", - IntegrationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) + DiscretizationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) .value("LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER", - IntegrationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER) + DiscretizationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER) .value("LOCAL_LAX_FRIEDRICHS_1ST_ORDER", - IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_1ST_ORDER) + DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_1ST_ORDER) .value("LOCAL_LAX_FRIEDRICHS_2ND_ORDER", - IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER) + DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER) .value("STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER", - IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) - .value("WENO_5TH_ORDER", IntegrationSchemeEnum::WENO_5TH_ORDER) + DiscretizationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) + .value("WENO_5TH_ORDER", DiscretizationSchemeEnum::WENO_5TH_ORDER) .finalize(); py::native_enum(module, "BooleanOperationEnum", diff --git a/python/pyWrap.hpp b/python/pyWrap.hpp index 0f33df3d..055a30a0 100644 --- a/python/pyWrap.hpp +++ b/python/pyWrap.hpp @@ -220,10 +220,11 @@ template void bindApi(py::module &module) { .def("getCalculateNormalVectors", &Advect::getCalculateNormalVectors, "Get whether normal vectors are computed during advection.") - .def("setIntegrationScheme", &Advect::setIntegrationScheme, - "Set the integration scheme to use during advection.") + .def("setDiscretizationScheme", &Advect::setDiscretizationScheme, + "Set the spatial discretization scheme to use during advection.") .def("setDissipationAlpha", &Advect::setDissipationAlpha, - "Set the dissipation value to use for Lax Friedrichs integration.") + "Set the dissipation value to use for Lax Friedrichs spatial " + "discretization.") .def("setUpdatePointData", &Advect::setUpdatePointData, "Set whether the point data in the old LS should be translated to " "the advected LS. Defaults to true.") diff --git a/python/viennals/__init__.pyi b/python/viennals/__init__.pyi index 43c7db13..a895bf83 100644 --- a/python/viennals/__init__.pyi +++ b/python/viennals/__init__.pyi @@ -17,7 +17,7 @@ from viennals._core import CurvatureEnum from viennals._core import Extrude from viennals._core import FeatureDetectionEnum from viennals._core import FileFormatEnum -from viennals._core import IntegrationSchemeEnum +from viennals._core import DiscretizationSchemeEnum from viennals._core import LogLevel from viennals._core import Logger from viennals._core import MaterialMap @@ -116,7 +116,7 @@ __all__: list[str] = [ "FromVolumeMesh", "GeometricAdvect", "GeometricAdvectDistribution", - "IntegrationSchemeEnum", + "DiscretizationSchemeEnum", "LogLevel", "Logger", "MakeGeometry", diff --git a/python/viennals/_core.pyi b/python/viennals/_core.pyi index f84b7449..66b7a0ed 100644 --- a/python/viennals/_core.pyi +++ b/python/viennals/_core.pyi @@ -18,7 +18,7 @@ __all__: list[str] = [ "Extrude", "FeatureDetectionEnum", "FileFormatEnum", - "IntegrationSchemeEnum", + "DiscretizationSchemeEnum", "LogLevel", "Logger", "MaterialMap", @@ -187,40 +187,40 @@ class FileFormatEnum(enum.IntEnum): Convert to a string according to format_spec. """ -class IntegrationSchemeEnum(enum.IntEnum): +class DiscretizationSchemeEnum(enum.IntEnum): ENGQUIST_OSHER_1ST_ORDER: typing.ClassVar[ - IntegrationSchemeEnum - ] # value = + DiscretizationSchemeEnum + ] # value = ENGQUIST_OSHER_2ND_ORDER: typing.ClassVar[ - IntegrationSchemeEnum - ] # value = + DiscretizationSchemeEnum + ] # value = LAX_FRIEDRICHS_1ST_ORDER: typing.ClassVar[ - IntegrationSchemeEnum - ] # value = + DiscretizationSchemeEnum + ] # value = LAX_FRIEDRICHS_2ND_ORDER: typing.ClassVar[ - IntegrationSchemeEnum - ] # value = + DiscretizationSchemeEnum + ] # value = LOCAL_LAX_FRIEDRICHS_1ST_ORDER: typing.ClassVar[ - IntegrationSchemeEnum - ] # value = + DiscretizationSchemeEnum + ] # value = LOCAL_LAX_FRIEDRICHS_2ND_ORDER: typing.ClassVar[ - IntegrationSchemeEnum - ] # value = + DiscretizationSchemeEnum + ] # value = LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER: typing.ClassVar[ - IntegrationSchemeEnum - ] # value = + DiscretizationSchemeEnum + ] # value = LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER: typing.ClassVar[ - IntegrationSchemeEnum - ] # value = + DiscretizationSchemeEnum + ] # value = LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER: typing.ClassVar[ - IntegrationSchemeEnum - ] # value = + DiscretizationSchemeEnum + ] # value = STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER: typing.ClassVar[ - IntegrationSchemeEnum - ] # value = + DiscretizationSchemeEnum + ] # value = WENO_5TH_ORDER: typing.ClassVar[ - IntegrationSchemeEnum - ] # value = + DiscretizationSchemeEnum + ] # value = @classmethod def __new__(cls, value): ... def __format__(self, format_spec): diff --git a/python/viennals/d2.pyi b/python/viennals/d2.pyi index 439ce917..bc1cd4cd 100644 --- a/python/viennals/d2.pyi +++ b/python/viennals/d2.pyi @@ -133,7 +133,7 @@ class Advect: def setDissipationAlpha(self, arg0: typing.SupportsFloat) -> None: """ - Set the dissipation value to use for Lax Friedrichs integration. + Set the dissipation value to use for Lax Friedrichs spatial discretization. """ def setIgnoreVoids(self, arg0: bool) -> None: @@ -141,9 +141,9 @@ class Advect: Set whether voids in the geometry should be ignored during advection or not. """ - def setIntegrationScheme(self, arg0: viennals._core.IntegrationSchemeEnum) -> None: + def setDiscretizationScheme(self, arg0: viennals._core.DiscretizationSchemeEnum) -> None: """ - Set the integration scheme to use during advection. + Set the spatial discretization scheme to use during advection. """ def setSaveAdvectionVelocities(self, arg0: bool) -> None: diff --git a/python/viennals/d3.pyi b/python/viennals/d3.pyi index b6da65ef..4237a69f 100644 --- a/python/viennals/d3.pyi +++ b/python/viennals/d3.pyi @@ -128,7 +128,7 @@ class Advect: def setDissipationAlpha(self, arg0: typing.SupportsFloat) -> None: """ - Set the dissipation value to use for Lax Friedrichs integration. + Set the dissipation value to use for Lax Friedrichs spatial discretization. """ def setIgnoreVoids(self, arg0: bool) -> None: @@ -136,9 +136,9 @@ class Advect: Set whether voids in the geometry should be ignored during advection or not. """ - def setIntegrationScheme(self, arg0: viennals._core.IntegrationSchemeEnum) -> None: + def setDiscretizationScheme(self, arg0: viennals._core.DiscretizationSchemeEnum) -> None: """ - Set the integration scheme to use during advection. + Set the spatial discretization scheme to use during advection. """ def setSaveAdvectionVelocities(self, arg0: bool) -> None: diff --git a/tests/Advection/Advection.cpp b/tests/Advection/Advection.cpp index 3806505c..664f04e9 100644 --- a/tests/Advection/Advection.cpp +++ b/tests/Advection/Advection.cpp @@ -49,18 +49,18 @@ int main() { double gridDelta = 0.6999999; - std::vector integrationSchemes = { - ls::IntegrationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER, - ls::IntegrationSchemeEnum::ENGQUIST_OSHER_2ND_ORDER, - ls::IntegrationSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER, - ls::IntegrationSchemeEnum::LAX_FRIEDRICHS_2ND_ORDER, - ls::IntegrationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER, - ls::IntegrationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER, - ls::IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_1ST_ORDER, - ls::IntegrationSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER, - ls::IntegrationSchemeEnum::WENO_5TH_ORDER}; - - for (auto integrationScheme : integrationSchemes) { + std::vector discretizationSchemes = { + ls::DiscretizationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER, + ls::DiscretizationSchemeEnum::ENGQUIST_OSHER_2ND_ORDER, + ls::DiscretizationSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER, + ls::DiscretizationSchemeEnum::LAX_FRIEDRICHS_2ND_ORDER, + ls::DiscretizationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER, + ls::DiscretizationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER, + ls::DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_1ST_ORDER, + ls::DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER, + ls::DiscretizationSchemeEnum::WENO_5TH_ORDER}; + + for (auto discretizationScheme : discretizationSchemes) { auto sphere1 = ls::Domain::New(gridDelta); double origin[3] = {5., 0., 0.}; @@ -93,7 +93,7 @@ int main() { ls::AdvectForwardEuler advectionKernel; advectionKernel.insertNextLevelSet(sphere1); advectionKernel.setVelocityField(velocities); - advectionKernel.setIntegrationScheme(integrationScheme); + advectionKernel.setDiscretizationScheme(discretizationScheme); advectionKernel.setSaveAdvectionVelocities(true); double time = 0.; diff --git a/tests/Advection/Advection.py b/tests/Advection/Advection.py index c7b66aa1..ba6c3a63 100644 --- a/tests/Advection/Advection.py +++ b/tests/Advection/Advection.py @@ -21,19 +21,19 @@ def main(): gridDelta = 0.6999999 - integrationSchemes = [ - vls.IntegrationSchemeEnum.ENGQUIST_OSHER_1ST_ORDER, - vls.IntegrationSchemeEnum.ENGQUIST_OSHER_2ND_ORDER, - vls.IntegrationSchemeEnum.LAX_FRIEDRICHS_1ST_ORDER, - vls.IntegrationSchemeEnum.LAX_FRIEDRICHS_2ND_ORDER, - vls.IntegrationSchemeEnum.LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER, - vls.IntegrationSchemeEnum.LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER, - vls.IntegrationSchemeEnum.LOCAL_LAX_FRIEDRICHS_1ST_ORDER, - vls.IntegrationSchemeEnum.LOCAL_LAX_FRIEDRICHS_2ND_ORDER, - vls.IntegrationSchemeEnum.WENO_5TH_ORDER + discretizationSchemes = [ + vls.DiscretizationSchemeEnum.ENGQUIST_OSHER_1ST_ORDER, + vls.DiscretizationSchemeEnum.ENGQUIST_OSHER_2ND_ORDER, + vls.DiscretizationSchemeEnum.LAX_FRIEDRICHS_1ST_ORDER, + vls.DiscretizationSchemeEnum.LAX_FRIEDRICHS_2ND_ORDER, + vls.DiscretizationSchemeEnum.LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER, + vls.DiscretizationSchemeEnum.LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER, + vls.DiscretizationSchemeEnum.LOCAL_LAX_FRIEDRICHS_1ST_ORDER, + vls.DiscretizationSchemeEnum.LOCAL_LAX_FRIEDRICHS_2ND_ORDER, + vls.DiscretizationSchemeEnum.WENO_5TH_ORDER ] - for scheme in integrationSchemes: + for scheme in discretizationSchemes: sphere1 = vls.Domain(gridDelta) origin = [5.0, 0.0, 0.0] @@ -47,7 +47,7 @@ def main(): advectionKernel = vls.AdvectRungeKutta3() advectionKernel.insertNextLevelSet(sphere1) advectionKernel.setVelocityField(velocities) - advectionKernel.setIntegrationScheme(scheme) + advectionKernel.setDiscretizationScheme(scheme) advectionKernel.setSaveAdvectionVelocities(True) time = 0.0 diff --git a/tests/Advection/StencilLaxFriedrichsTest.cpp b/tests/Advection/StencilLaxFriedrichsTest.cpp index 1f6e20b0..cbc58754 100644 --- a/tests/Advection/StencilLaxFriedrichsTest.cpp +++ b/tests/Advection/StencilLaxFriedrichsTest.cpp @@ -61,9 +61,9 @@ int main() { advectionKernel.setVelocityField(velocityField); advectionKernel.setAdvectionTime(0.5); - // Set the specific integration scheme - advectionKernel.setIntegrationScheme( - ls::IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER); + // Set the specific spatial discretization scheme + advectionKernel.setDiscretizationScheme( + ls::DiscretizationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER); // Run Advection std::cout << "Running Stencil Local Lax Friedrichs Advection..." << std::endl; diff --git a/tests/Advection/TimeIntegrationComparison.cpp b/tests/Advection/TimeIntegrationComparison.cpp index 5b1dbe21..b9f1c21d 100644 --- a/tests/Advection/TimeIntegrationComparison.cpp +++ b/tests/Advection/TimeIntegrationComparison.cpp @@ -65,16 +65,16 @@ int main() { advectFE.insertNextLevelSet(sphereFE); advectFE.setVelocityField(velocityField); advectFE.setAdvectionTime(2.0); - advectFE.setIntegrationScheme( - ls::IntegrationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER); + advectFE.setDiscretizationScheme( + ls::DiscretizationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER); // Setup Advection: Runge-Kutta 3 ls::AdvectRungeKutta3 advectRK3; advectRK3.insertNextLevelSet(sphereRK3); advectRK3.setVelocityField(velocityField); advectRK3.setAdvectionTime(2.0); - advectRK3.setIntegrationScheme( - ls::IntegrationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER); + advectRK3.setDiscretizationScheme( + ls::DiscretizationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER); // Run Advection std::cout << "Running Forward Euler Advection..." << std::endl; diff --git a/tests/Advection/TimeIntegrationComparison.py b/tests/Advection/TimeIntegrationComparison.py index 17b4c8e0..3224e990 100644 --- a/tests/Advection/TimeIntegrationComparison.py +++ b/tests/Advection/TimeIntegrationComparison.py @@ -38,14 +38,14 @@ def main(): advectFE.insertNextLevelSet(sphereFE) advectFE.setVelocityField(velocityField) advectFE.setAdvectionTime(2.0) - advectFE.setIntegrationScheme(vls.IntegrationSchemeEnum.ENGQUIST_OSHER_1ST_ORDER) + advectFE.setDiscretizationScheme(vls.DiscretizationSchemeEnum.ENGQUIST_OSHER_1ST_ORDER) # Setup Advection: Runge-Kutta 3 advectRK3 = vls.AdvectRungeKutta3() advectRK3.insertNextLevelSet(sphereRK3) advectRK3.setVelocityField(velocityField) advectRK3.setAdvectionTime(2.0) - advectRK3.setIntegrationScheme(vls.IntegrationSchemeEnum.ENGQUIST_OSHER_1ST_ORDER) + advectRK3.setDiscretizationScheme(vls.DiscretizationSchemeEnum.ENGQUIST_OSHER_1ST_ORDER) # Run Advection print("Running Forward Euler Advection...") diff --git a/tests/Advection2D/Advection2D.cpp b/tests/Advection2D/Advection2D.cpp index 51aa24dc..85e31aca 100644 --- a/tests/Advection2D/Advection2D.cpp +++ b/tests/Advection2D/Advection2D.cpp @@ -105,8 +105,8 @@ int main() { ls::Advect advectionKernel; advectionKernel.insertNextLevelSet(sphere1); advectionKernel.setVelocityField(velocities); - advectionKernel.setIntegrationScheme( - ls::IntegrationSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER); + advectionKernel.setDiscretizationScheme( + ls::DiscretizationSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER); advectionKernel.setAdvectionTime(20.); advectionKernel.apply(); diff --git a/tests/GeometricAdvectPerformance/GeometricAdvectPerformance.cpp b/tests/GeometricAdvectPerformance/GeometricAdvectPerformance.cpp index 1229683d..15208d0f 100644 --- a/tests/GeometricAdvectPerformance/GeometricAdvectPerformance.cpp +++ b/tests/GeometricAdvectPerformance/GeometricAdvectPerformance.cpp @@ -139,8 +139,8 @@ int main() { auto velocities = ls::SmartPointer::New(); advectionKernel.setVelocityField(velocities); advectionKernel.setAdvectionTime(depositionDistance); - advectionKernel.setIntegrationScheme( - static_cast(i)); + advectionKernel.setDiscretizationScheme( + static_cast(i)); { auto start = std::chrono::high_resolution_clock::now(); advectionKernel.apply(); From cfdd2c4283ad2f475c5e4f76782caddf84aa4c10 Mon Sep 17 00:00:00 2001 From: Roman Kostal Date: Thu, 18 Dec 2025 15:43:31 +0100 Subject: [PATCH 20/57] fix formatting --- include/viennals/lsAdvect.hpp | 5 +++-- python/pyWrap.cpp | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index ca6b9d58..4a56192d 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -396,8 +396,9 @@ template class Advect { } /// Internal function used to calculate the deltas to be applied to the LS - /// values from the given velocities and the spatial discretization scheme to be used. - /// This function fills up the storedRates to be used when moving the LS + /// values from the given velocities and the spatial discretization scheme to + /// be used. This function fills up the storedRates to be used when moving the + /// LS template double integrateTime(DiscretizationSchemeType discretizationScheme, double maxTimeStep) { diff --git a/python/pyWrap.cpp b/python/pyWrap.cpp index 42489b71..67324ac1 100644 --- a/python/pyWrap.cpp +++ b/python/pyWrap.cpp @@ -82,7 +82,7 @@ PYBIND11_MODULE(VIENNALS_MODULE_NAME, module) { // ------ ENUMS ------ py::native_enum(module, "DiscretizationSchemeEnum", - "enum.IntEnum") + "enum.IntEnum") .value("ENGQUIST_OSHER_1ST_ORDER", DiscretizationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER) .value("ENGQUIST_OSHER_2ND_ORDER", @@ -91,8 +91,9 @@ PYBIND11_MODULE(VIENNALS_MODULE_NAME, module) { DiscretizationSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER) .value("LAX_FRIEDRICHS_2ND_ORDER", DiscretizationSchemeEnum::LAX_FRIEDRICHS_2ND_ORDER) - .value("LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER", - DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER) + .value( + "LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER", + DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER) .value("LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER", DiscretizationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) .value("LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER", From 07f825fafc0d30fbd94cfdad36d6c9c42f0c671c Mon Sep 17 00:00:00 2001 From: filipovic Date: Fri, 19 Dec 2025 07:18:47 +0100 Subject: [PATCH 21/57] Updated AirGapDeposition, Deposition, and SquareEtch examples to work in 3D. Added VoidEtching python example. Removed redundant prepareLS(); call in computeRates during advection. Updated RK3 substepping. --- .../AirGapDeposition/AirGapDeposition.cpp | 38 +++++--- examples/Deposition/Deposition.cpp | 30 ++++-- examples/SquareEtch/SquareEtch.cpp | 49 ++++++---- examples/VoidEtching/VoidEtching.py | 97 +++++++++++++++++++ include/viennals/lsAdvect.hpp | 2 +- include/viennals/lsAdvectRungeKutta3.hpp | 2 + 6 files changed, 180 insertions(+), 38 deletions(-) create mode 100644 examples/VoidEtching/VoidEtching.py diff --git a/examples/AirGapDeposition/AirGapDeposition.cpp b/examples/AirGapDeposition/AirGapDeposition.cpp index 62c0d49c..9352d6e1 100644 --- a/examples/AirGapDeposition/AirGapDeposition.cpp +++ b/examples/AirGapDeposition/AirGapDeposition.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include #include @@ -34,8 +33,9 @@ class velocityField : public ls::VelocityField { const std::array &normalVector, unsigned long /*pointId*/) { // velocity is proportional to the normal vector - NumericType velocity = - std::abs(normalVector[0]) + std::abs(normalVector[1]); + NumericType velocity = 0.; + for (int i = 0; i < 3; ++i) + velocity += std::abs(normalVector[i]); return velocity; } @@ -92,17 +92,25 @@ int main() { NumericType extent = 30; NumericType gridDelta = 0.5; - double bounds[2 * D] = {-extent, extent, -extent, extent}; + double bounds[2 * D]; + for (int i = 0; i < D; ++i) { + bounds[2 * i] = -extent; + bounds[2 * i + 1] = extent; + } + ls::Domain::BoundaryType boundaryCons[D]; - boundaryCons[0] = - ls::Domain::BoundaryType::REFLECTIVE_BOUNDARY; - boundaryCons[1] = ls::Domain::BoundaryType::INFINITE_BOUNDARY; + for (int i = 0; i < D - 1; ++i) + boundaryCons[i] = + ls::Domain::BoundaryType::REFLECTIVE_BOUNDARY; + boundaryCons[D - 1] = + ls::Domain::BoundaryType::INFINITE_BOUNDARY; auto substrate = ls::SmartPointer>::New( bounds, boundaryCons, gridDelta); - NumericType origin[2] = {0., 0.}; - NumericType planeNormal[2] = {0., 1.}; + NumericType origin[D] = {0.}; + NumericType planeNormal[D] = {0.}; + planeNormal[D - 1] = 1.; { auto plane = @@ -123,8 +131,14 @@ int main() { auto trench = ls::SmartPointer>::New( bounds, boundaryCons, gridDelta); NumericType xlimit = extent / 6.; - NumericType minCorner[D] = {-xlimit, -25.}; - NumericType maxCorner[D] = {xlimit, 1.}; + NumericType minCorner[D]; + NumericType maxCorner[D]; + for (int i = 0; i < D - 1; ++i) { + minCorner[i] = -xlimit; + maxCorner[i] = xlimit; + } + minCorner[D - 1] = -25.; + maxCorner[D - 1] = 1.; auto box = ls::SmartPointer>::New(minCorner, maxCorner); ls::MakeGeometry(trench, box).apply(); @@ -165,7 +179,7 @@ int main() { std::cout << "Advecting" << std::endl; // FE Kernel - ls::AdvectForwardEuler advectionKernelFE; + ls::Advect advectionKernelFE; advectionKernelFE.insertNextLevelSet(substrateFE); advectionKernelFE.insertNextLevelSet(newLayerFE); advectionKernelFE.setVelocityField(velocities); diff --git a/examples/Deposition/Deposition.cpp b/examples/Deposition/Deposition.cpp index 7b087f89..790fef02 100644 --- a/examples/Deposition/Deposition.cpp +++ b/examples/Deposition/Deposition.cpp @@ -52,18 +52,25 @@ int main() { NumericType extent = 30; NumericType gridDelta = 0.5; - double bounds[2 * D] = {-extent, extent, -extent, extent, -extent, extent}; + double bounds[2 * D]; + for (int i = 0; i < D; ++i) { + bounds[2 * i] = -extent; + bounds[2 * i + 1] = extent; + } + ls::Domain::BoundaryType boundaryCons[D]; for (unsigned i = 0; i < D - 1; ++i) boundaryCons[i] = ls::Domain::BoundaryType::REFLECTIVE_BOUNDARY; - boundaryCons[2] = ls::Domain::BoundaryType::INFINITE_BOUNDARY; + boundaryCons[D - 1] = + ls::Domain::BoundaryType::INFINITE_BOUNDARY; auto substrate = ls::SmartPointer>::New( bounds, boundaryCons, gridDelta); - NumericType origin[3] = {0., 0., 0.}; - NumericType planeNormal[3] = {0., 0., 1.}; + NumericType origin[D] = {0.}; + NumericType planeNormal[D] = {0.}; + planeNormal[D - 1] = 1.; { auto plane = @@ -76,8 +83,19 @@ int main() { bounds, boundaryCons, gridDelta); // make -x and +x greater than domain for numerical stability NumericType ylimit = extent / 4.; - NumericType minCorner[D] = {-extent - 1, -ylimit, -15.}; - NumericType maxCorner[D] = {extent + 1, ylimit, 1.}; + NumericType minCorner[D]; + NumericType maxCorner[D]; + if constexpr (D == 2) { + minCorner[0] = -ylimit; + maxCorner[0] = ylimit; + } else { + minCorner[0] = -extent - 1; + maxCorner[0] = extent + 1; + minCorner[1] = -ylimit; + maxCorner[1] = ylimit; + } + minCorner[D - 1] = -15.; + maxCorner[D - 1] = 1.; auto box = ls::SmartPointer>::New(minCorner, maxCorner); ls::MakeGeometry(trench, box).apply(); diff --git a/examples/SquareEtch/SquareEtch.cpp b/examples/SquareEtch/SquareEtch.cpp index a63d8780..5979880b 100644 --- a/examples/SquareEtch/SquareEtch.cpp +++ b/examples/SquareEtch/SquareEtch.cpp @@ -23,14 +23,18 @@ namespace ls = viennals; // Advection scheme will take care of numerical // artefacts itself. class velocityField : public ls::VelocityField { + int D; + public: + velocityField(int D) : D(D) {} + double getScalarVelocity(const std::array & /*coordinate*/, int material, const std::array &normalVector, unsigned long /*pointId*/) { // if the surface of material 1 is facing upwards, etch it anisotropically - if (material == 1 && normalVector[1] > 0.) { - return -std::abs(normalVector[1]); + if (material == 1 && normalVector[D - 1] > 0.) { + return -std::abs(normalVector[D - 1]); } else return 0.; } @@ -43,8 +47,11 @@ class velocityField : public ls::VelocityField { // to be used for advection. class analyticalField : public ls::VelocityField { const double velocity = -1; + int D; public: + analyticalField(int D) : D(D) {} + double getScalarVelocity(const std::array & /*coordinate*/, int material, const std::array &normalVector, @@ -52,11 +59,12 @@ class analyticalField : public ls::VelocityField { if (material != 1) return 0.; - return velocity * std::abs(normalVector[1]); + return velocity * std::abs(normalVector[D - 1]); } - double getDissipationAlpha(int direction, int material, - const std::array ¢ralDifferences) { + double getDissipationAlpha( + int direction, int material, + const std::array ¢ralDifferences) { if (material != 1) return 0; @@ -67,9 +75,7 @@ class analyticalField : public ls::VelocityField { gradient = std::sqrt(gradient); // alpha for different directions - if (direction == 0) { - return 0; - } else if (direction == 1) { + if (direction == D - 1) { return std::abs(velocity); } else { return 0; @@ -79,7 +85,7 @@ class analyticalField : public ls::VelocityField { int main() { - constexpr int D = 2; + constexpr int D = 3; omp_set_num_threads(8); // Change this to use the analytical velocity field @@ -111,8 +117,14 @@ int main() { auto trench = ls::SmartPointer>::New( bounds, boundaryCons, gridDelta); // trench bottom is the initial bottom of the trench - double minCorner[D] = {-extent / 1.5, trenchBottom}; - double maxCorner[D] = {extent / 1.5, 1.}; + double minCorner[D]; + double maxCorner[D]; + for (int i = 0; i < D - 1; ++i) { + minCorner[i] = -extent / 1.5; + maxCorner[i] = extent / 1.5; + } + minCorner[D - 1] = trenchBottom; + maxCorner[D - 1] = 1.; auto box = ls::SmartPointer>::New(minCorner, maxCorner); ls::MakeGeometry(trench, box).apply(); @@ -156,8 +168,8 @@ int main() { } // START ADVECTION - auto velocities = ls::SmartPointer::New(); - auto analyticalVelocities = ls::SmartPointer::New(); + velocityField velocities(D); + analyticalField analyticalVelocities(D); std::cout << "Advecting" << std::endl; ls::Advect advectionKernel; @@ -169,7 +181,8 @@ int main() { advectionKernel.setSaveAdvectionVelocities(true); if (useAnalyticalVelocity) { - advectionKernel.setVelocityField(analyticalVelocities); + advectionKernel.setVelocityField( + ls::SmartPointer(&analyticalVelocities, [](analyticalField *) {})); // Analytical velocity fields and dissipation coefficients // can only be used with this integration scheme advectionKernel.setIntegrationScheme( @@ -179,15 +192,13 @@ int main() { // for numerical velocities, just use the default // integration scheme, which is not accurate for certain // velocity functions but very fast - advectionKernel.setVelocityField(velocities); - - // For coordinate independent velocity functions - // this numerical scheme is superior though. - // However, it is slower. + advectionKernel.setVelocityField( + ls::SmartPointer(&velocities, [](velocityField *) {})); // advectionKernel.setIntegrationScheme( // ls::IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER); } + // advect the level set until 50s have passed double finalTime = 50; unsigned counter = 1; diff --git a/examples/VoidEtching/VoidEtching.py b/examples/VoidEtching/VoidEtching.py new file mode 100644 index 00000000..c7ab2aa2 --- /dev/null +++ b/examples/VoidEtching/VoidEtching.py @@ -0,0 +1,97 @@ +import viennals + +viennals.setDimension(3) + +# Implement own velocity field +class VelocityField(viennals.VelocityField): + def getScalarVelocity(self, coord, material, normal, pointId): + # isotropic etch rate + return -1.0 + + def getVectorVelocity(self, coord, material, normal, pointId): + return [0.0, 0.0, 0.0] + +def main(): + # Simulation parameters + extent = 30.0 + gridDelta = 1.0 + + # Bounds and boundary conditions + bounds = [-extent, extent, -extent, extent, -extent, extent] + boundaryCons = [ + viennals.BoundaryConditionEnum.REFLECTIVE_BOUNDARY, + viennals.BoundaryConditionEnum.REFLECTIVE_BOUNDARY, + viennals.BoundaryConditionEnum.INFINITE_BOUNDARY + ] + + # Create substrate domain + substrate = viennals.Domain(bounds, boundaryCons, gridDelta) + + + # Create initial plane geometry + origin = [0.0, 0.0, 0.0] + planeNormal = [0.0, 0.0, 1.0] + plane = viennals.Plane(origin, planeNormal) + viennals.MakeGeometry(substrate, plane).apply() + + # Create spheres used for booling + print("Creating spheres...") + sphere = viennals.Domain(bounds, boundaryCons, gridDelta) + + # Define boolean operation (Relative Complement: substrate - sphere) + boolOp = viennals.BooleanOperation( + substrate, sphere, viennals.BooleanOperationEnum.RELATIVE_COMPLEMENT + ) + + # Sphere 1 + origin = [-12.0, -5.0, -15.0] + radius = 10.0 + viennals.MakeGeometry(sphere, viennals.Sphere(origin, radius)).apply() + boolOp.apply() + + # Sphere 2 + origin = [-7.0, -30.0, -20.0] + radius = 8.0 + viennals.MakeGeometry(sphere, viennals.Sphere(origin, radius)).apply() + boolOp.apply() + + # Sphere 3 + origin = [5.0, 15.0, -2.0] + radius = 8.0 + viennals.MakeGeometry(sphere, viennals.Sphere(origin, radius)).apply() + boolOp.apply() + + # Sphere 4 + origin = [2.0, 8.0, -27.0] + radius = 8.0 + viennals.MakeGeometry(sphere, viennals.Sphere(origin, radius)).apply() + boolOp.apply() + + # Now etch the substrate isotropically + velocities = VelocityField() + + print("Advecting") + + advectionKernel = viennals.Advect() + advectionKernel.insertNextLevelSet(substrate) + advectionKernel.setVelocityField(velocities) + advectionKernel.setIgnoreVoids(True) + + passedTime = 0.0 + numberOfSteps = 50 + + for i in range(numberOfSteps): + print(f"\rAdvection step {i} / {numberOfSteps}", end="", flush=True) + + mesh = viennals.Mesh() + viennals.ToSurfaceMesh(substrate, mesh).apply() + viennals.VTKWriter(mesh, f"void-{i}.vtp").apply() + + advectionKernel.apply() + passedTime += advectionKernel.getAdvectedTime() + + print() + print(f"Time passed during advection: {passedTime}") + +if __name__ == "__main__": + main() diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index 4aa2603b..618d7029 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -583,7 +583,7 @@ template class Advect { /// This function applies the integration scheme and calculates the rates and /// the maximum time step, but it does **not** move the surface. void computeRates(double maxTimeStep = std::numeric_limits::max()) { - prepareLS(); + // prepareLS(); if (integrationScheme == IntegrationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER) { auto is = lsInternal::EngquistOsher(levelSets.back(), velocities, calculateNormalVectors); diff --git a/include/viennals/lsAdvectRungeKutta3.hpp b/include/viennals/lsAdvectRungeKutta3.hpp index 34209380..f807bf9f 100644 --- a/include/viennals/lsAdvectRungeKutta3.hpp +++ b/include/viennals/lsAdvectRungeKutta3.hpp @@ -80,6 +80,7 @@ template class AdvectRungeKutta3 : public Advect { // 4. Stage 2: u^(2) = 3/4 u^n + 1/4 (u^(1) + dt * L(u^(1))) // Calculate rates based on u^(1) (current levelSets.back()) + limit = (maxTimeStep - totalDt) / 2.0; Base::computeRates(limit); dt = Base::getCurrentTimeStep(); // Update to get u^* = u^(1) + dt * L(u^(1)) @@ -90,6 +91,7 @@ template class AdvectRungeKutta3 : public Advect { // 5. Stage 3: u^(n+1) = 1/3 u^n + 2/3 (u^(2) + dt * L(u^(2))) // Calculate rates based on u^(2) (current levelSets.back()) + limit = (maxTimeStep - totalDt); Base::computeRates(limit); dt = Base::getCurrentTimeStep(); // Update to get u^** = u^(2) + dt * L(u^(2)) From 6688fc9231097a2ee94af7a7131296a881c26fcc Mon Sep 17 00:00:00 2001 From: filipovic Date: Fri, 19 Dec 2025 07:21:23 +0100 Subject: [PATCH 22/57] format --- examples/SquareEtch/SquareEtch.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/examples/SquareEtch/SquareEtch.cpp b/examples/SquareEtch/SquareEtch.cpp index 5979880b..fc91df29 100644 --- a/examples/SquareEtch/SquareEtch.cpp +++ b/examples/SquareEtch/SquareEtch.cpp @@ -62,9 +62,8 @@ class analyticalField : public ls::VelocityField { return velocity * std::abs(normalVector[D - 1]); } - double getDissipationAlpha( - int direction, int material, - const std::array ¢ralDifferences) { + double getDissipationAlpha(int direction, int material, + const std::array ¢ralDifferences) { if (material != 1) return 0; @@ -181,8 +180,8 @@ int main() { advectionKernel.setSaveAdvectionVelocities(true); if (useAnalyticalVelocity) { - advectionKernel.setVelocityField( - ls::SmartPointer(&analyticalVelocities, [](analyticalField *) {})); + advectionKernel.setVelocityField(ls::SmartPointer( + &analyticalVelocities, [](analyticalField *) {})); // Analytical velocity fields and dissipation coefficients // can only be used with this integration scheme advectionKernel.setIntegrationScheme( @@ -198,7 +197,6 @@ int main() { // ls::IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER); } - // advect the level set until 50s have passed double finalTime = 50; unsigned counter = 1; From be39c430b6f42e3b3669b6225dbe275d92cf0445 Mon Sep 17 00:00:00 2001 From: filipovic Date: Fri, 19 Dec 2025 07:34:00 +0100 Subject: [PATCH 23/57] fixed SquareEtch example --- examples/SquareEtch/SquareEtch.cpp | 62 ++++++++++++++++++------------ 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/examples/SquareEtch/SquareEtch.cpp b/examples/SquareEtch/SquareEtch.cpp index fc91df29..e1266b5e 100644 --- a/examples/SquareEtch/SquareEtch.cpp +++ b/examples/SquareEtch/SquareEtch.cpp @@ -20,8 +20,9 @@ namespace ls = viennals; // Numerical velocity field. -// Advection scheme will take care of numerical -// artefacts itself. +// This class defines the etch rate based on the surface normal. +// It models a directional etch where only upward-facing surfaces of the +// material are removed. The advection scheme will handle numerical dissipation. class velocityField : public ls::VelocityField { int D; @@ -40,11 +41,11 @@ class velocityField : public ls::VelocityField { } }; -// Same velocity field, but analytical -// If the dissipation alphas can be derived, -// this will produce better results than numerical -// approximations. lsLocalLaxFriedrichsAnalytical has -// to be used for advection. +// Analytical velocity field. +// This class provides the scalar velocity and the dissipation coefficients +// (alphas) analytically. Using analytical dissipation with the Local +// Lax-Friedrichs scheme (lsLocalLaxFriedrichsAnalytical) typically yields +// better accuracy than numerical approximations. class analyticalField : public ls::VelocityField { const double velocity = -1; int D; @@ -87,22 +88,31 @@ int main() { constexpr int D = 3; omp_set_num_threads(8); - // Change this to use the analytical velocity field + // Toggle between numerical and analytical velocity fields. const bool useAnalyticalVelocity = false; double extent = 30; double gridDelta = 0.47; - double bounds[2 * D] = {-extent, extent, -extent, - extent}; //, -extent, extent}; + double bounds[2 * D]; + for (int i = 0; i < D; ++i) { + bounds[2 * i] = -extent; + bounds[2 * i + 1] = extent; + } + + // Define boundary conditions: + // Reflective boundaries for the sides (simulating an infinite array). + // Infinite boundary at the top (open direction). ls::Domain::BoundaryType boundaryCons[D]; for (unsigned i = 0; i < D - 1; ++i) boundaryCons[i] = ls::Domain::BoundaryType::REFLECTIVE_BOUNDARY; boundaryCons[D - 1] = ls::Domain::BoundaryType::INFINITE_BOUNDARY; + // Create substrate level set (initially an empty domain with bounds). auto substrate = ls::SmartPointer>::New( bounds, boundaryCons, gridDelta); + // Initialize the substrate as a flat surface at z=0 (or y=0 in 2D). double origin[3] = {0., 0., 0.}; double planeNormal[3] = {0., D == 2, D == 3}; { @@ -113,9 +123,11 @@ int main() { double trenchBottom = -2.; { + // Create the trench geometry. + // Define a box for the trench volume and subtract it from the substrate. auto trench = ls::SmartPointer>::New( bounds, boundaryCons, gridDelta); - // trench bottom is the initial bottom of the trench + // Define the box dimensions for the trench. double minCorner[D]; double maxCorner[D]; for (int i = 0; i < D - 1; ++i) { @@ -127,23 +139,26 @@ int main() { auto box = ls::SmartPointer>::New(minCorner, maxCorner); ls::MakeGeometry(trench, box).apply(); - // Create trench geometry + // Subtract the trench box from the substrate (Substrate \ Trench). ls::BooleanOperation( substrate, trench, ls::BooleanOperationEnum::RELATIVE_COMPLEMENT) .apply(); } - // in order only to etch the bottom of the trench, we need a mask layer + // Create a mask layer to restrict etching to the trench bottom. + // The mask will cover the top surface and sidewalls, but expose the bottom. auto mask = ls::SmartPointer>::New(bounds, boundaryCons, gridDelta); - // make downward facing plane to remove bottom of trench for the mask - // layer - // add small offset so bottom of trench is definetly gone + + // Create a half-space for the mask that exists above the trench bottom. + // Add a small offset to ensure the mask ends above the trench floor. origin[D - 1] = trenchBottom + 1e-9; planeNormal[D - 1] = -1.; ls::MakeGeometry( mask, ls::SmartPointer>::New(origin, planeNormal)) .apply(); + // Intersect the mask half-space with the substrate geometry. + // Result: Mask exists where Substrate exists AND z > TrenchBottom. ls::BooleanOperation(mask, substrate, ls::BooleanOperationEnum::INTERSECT) .apply(); @@ -167,21 +182,21 @@ int main() { } // START ADVECTION - velocityField velocities(D); - analyticalField analyticalVelocities(D); + auto velocities = ls::SmartPointer::New(D); + auto analyticalVelocities = ls::SmartPointer::New(D); std::cout << "Advecting" << std::endl; ls::Advect advectionKernel; - // the level set to be advected has to be inserted last - // the other is used as the mask layer for etching + // Insert level sets. + // The order determines the material ID: Mask is Material 0, Substrate is + // Material 1. The velocity field is configured to only move Material 1. advectionKernel.insertNextLevelSet(mask); advectionKernel.insertNextLevelSet(substrate); advectionKernel.setSaveAdvectionVelocities(true); if (useAnalyticalVelocity) { - advectionKernel.setVelocityField(ls::SmartPointer( - &analyticalVelocities, [](analyticalField *) {})); + advectionKernel.setVelocityField(analyticalVelocities); // Analytical velocity fields and dissipation coefficients // can only be used with this integration scheme advectionKernel.setIntegrationScheme( @@ -191,8 +206,7 @@ int main() { // for numerical velocities, just use the default // integration scheme, which is not accurate for certain // velocity functions but very fast - advectionKernel.setVelocityField( - ls::SmartPointer(&velocities, [](velocityField *) {})); + advectionKernel.setVelocityField(velocities); // advectionKernel.setIntegrationScheme( // ls::IntegrationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER); } From 916de96047ee226e100adb7399780ad4903d229c Mon Sep 17 00:00:00 2001 From: filipovic Date: Fri, 19 Dec 2025 07:40:00 +0100 Subject: [PATCH 24/57] prepareLS(); in computeRates was added again which made it redundant in advect. --- include/viennals/lsAdvect.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index 618d7029..762c4d77 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -583,7 +583,7 @@ template class Advect { /// This function applies the integration scheme and calculates the rates and /// the maximum time step, but it does **not** move the surface. void computeRates(double maxTimeStep = std::numeric_limits::max()) { - // prepareLS(); + prepareLS(); if (integrationScheme == IntegrationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER) { auto is = lsInternal::EngquistOsher(levelSets.back(), velocities, calculateNormalVectors); @@ -772,7 +772,7 @@ template class Advect { /// internal function used as a wrapper to call specialized integrateTime /// with the chosen integrationScheme virtual double advect(double maxTimeStep) { - prepareLS(); + // prepareLS(); if (currentTimeStep < 0. || storedRates.empty()) computeRates(maxTimeStep); From 04a7256ff38b881692cdce40ce77f3da177ed9ed Mon Sep 17 00:00:00 2001 From: Tobias Reiter Date: Mon, 22 Dec 2025 10:24:39 +0100 Subject: [PATCH 25/57] Rename to SpatialScheme, add legacy IntegrationSchemeEnum --- PythonAPI.md | 2 +- examples/Deposition/Deposition.cpp | 3 +- examples/Epitaxy/Epitaxy.cpp | 6 +- examples/Epitaxy/Epitaxy.py | 12 +- .../PeriodicBoundary/PeriodicBoundary.cpp | 4 +- examples/SquareEtch/SquareEtch.cpp | 10 +- examples/SquareEtch/SquareEtch.py | 64 ++++++---- include/viennals/lsAdvect.hpp | 117 ++++++++---------- include/viennals/lsAdvectRungeKutta3.hpp | 4 +- python/pyWrap.cpp | 34 ++--- python/pyWrap.hpp | 33 ++--- python/viennals/__init__.pyi | 6 +- python/viennals/_core.pyi | 88 ++++++------- python/viennals/d2.pyi | 9 +- python/viennals/d3.pyi | 9 +- tests/Advection/Advection.cpp | 26 ++-- tests/Advection/Advection.py | 23 ++-- tests/Advection/StencilLaxFriedrichsTest.cpp | 4 +- tests/Advection/TimeIntegrationComparison.cpp | 6 +- tests/Advection/TimeIntegrationComparison.py | 12 +- tests/Advection2D/Advection2D.cpp | 4 +- .../GeometricAdvectPerformance.cpp | 5 +- 22 files changed, 256 insertions(+), 225 deletions(-) diff --git a/PythonAPI.md b/PythonAPI.md index 869b6bd2..002965d9 100644 --- a/PythonAPI.md +++ b/PythonAPI.md @@ -60,7 +60,7 @@ These are imported directly from the **root** `viennals` module. ### **Enums** - `LogLevel` -- `DiscretizationSchemeEnum` +- `SpatialSchemeEnum` - `BooleanOperationEnum` - `CurvatureEnum` - `FeatureDetectionEnum` diff --git a/examples/Deposition/Deposition.cpp b/examples/Deposition/Deposition.cpp index 1835ab74..52cabd78 100644 --- a/examples/Deposition/Deposition.cpp +++ b/examples/Deposition/Deposition.cpp @@ -113,8 +113,7 @@ int main() { advectionKernel.setVelocityField(velocities); // advectionKernel.setAdvectionTime(4.); unsigned counter = 1; - advectionKernel.setDiscretizationScheme( - ls::DiscretizationSchemeEnum::WENO_5TH_ORDER); + advectionKernel.setSpatialScheme(ls::SpatialSchemeEnum::WENO_5TH_ORDER); for (NumericType time = 0; time < 4.; time += advectionKernel.getAdvectedTime()) { advectionKernel.apply(); diff --git a/examples/Epitaxy/Epitaxy.cpp b/examples/Epitaxy/Epitaxy.cpp index 45674b01..92ab5ede 100644 --- a/examples/Epitaxy/Epitaxy.cpp +++ b/examples/Epitaxy/Epitaxy.cpp @@ -21,7 +21,7 @@ class epitaxy final : public VelocityField { static constexpr double high = 1.0; public: - epitaxy(std::vector vel) : velocities(vel){}; + epitaxy(std::vector vel) : velocities(vel) {}; double getScalarVelocity(const std::array & /*coordinate*/, int material, const std::array &normal, @@ -82,8 +82,8 @@ int main(int argc, char *argv[]) { auto velocityField = SmartPointer::New(std::vector{0., -.5}); Advect advectionKernel(levelSets, velocityField); - advectionKernel.setDiscretizationScheme( - DiscretizationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER); + advectionKernel.setSpatialScheme( + SpatialSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER); advectionKernel.setAdvectionTime(.5); Timer timer; diff --git a/examples/Epitaxy/Epitaxy.py b/examples/Epitaxy/Epitaxy.py index 3eb726cc..84f47922 100644 --- a/examples/Epitaxy/Epitaxy.py +++ b/examples/Epitaxy/Epitaxy.py @@ -5,6 +5,7 @@ D = 3 vls.setNumThreads(8) + class EpitaxyVelocity(vls.VelocityField): def __init__(self, velocities): super().__init__() @@ -30,13 +31,15 @@ def getScalarVelocity(self, coord, material, normal, point_id): return vel * mat_vel def getVectorVelocity(self, coord, material, normal, point_id): - return (0., 0., 0.) + return (0.0, 0.0, 0.0) + def write_surface(domain, filename): mesh = vls.Mesh() vls.ToSurfaceMesh(domain, mesh).apply() vls.VTKWriter(mesh, filename).apply() + def main(): # Set dimension to 3D vls.setDimension(D) @@ -50,7 +53,7 @@ def main(): boundary_conditions = [ vls.BoundaryConditionEnum.REFLECTIVE_BOUNDARY, vls.BoundaryConditionEnum.REFLECTIVE_BOUNDARY, - vls.BoundaryConditionEnum.INFINITE_BOUNDARY + vls.BoundaryConditionEnum.INFINITE_BOUNDARY, ] # Create Geometry @@ -80,7 +83,9 @@ def main(): for ls in level_sets: advection.insertNextLevelSet(ls) advection.setVelocityField(velocity_field) - advection.setDiscretizationScheme(vls.DiscretizationSchemeEnum.STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) + advection.setSpatialScheme( + vls.SpatialSchemeEnum.STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER + ) advection.setAdvectionTime(0.5) start_time = time.time() @@ -92,5 +97,6 @@ def main(): write_surface(substrate, "epitaxy.vtp") + if __name__ == "__main__": main() diff --git a/examples/PeriodicBoundary/PeriodicBoundary.cpp b/examples/PeriodicBoundary/PeriodicBoundary.cpp index 0dc914b3..c36d0078 100644 --- a/examples/PeriodicBoundary/PeriodicBoundary.cpp +++ b/examples/PeriodicBoundary/PeriodicBoundary.cpp @@ -90,8 +90,8 @@ int main() { ls::Advect advectionKernel; advectionKernel.insertNextLevelSet(substrate); advectionKernel.setVelocityField(velocities); - advectionKernel.setDiscretizationScheme( - ls::DiscretizationSchemeEnum::ENGQUIST_OSHER_2ND_ORDER); + advectionKernel.setSpatialScheme( + ls::SpatialSchemeEnum::ENGQUIST_OSHER_2ND_ORDER); // Now advect the level set 50 times, outputting every // advection step. Save the physical time that diff --git a/examples/SquareEtch/SquareEtch.cpp b/examples/SquareEtch/SquareEtch.cpp index b20b7812..1bfc6534 100644 --- a/examples/SquareEtch/SquareEtch.cpp +++ b/examples/SquareEtch/SquareEtch.cpp @@ -172,9 +172,9 @@ int main() { advectionKernel.setVelocityField(analyticalVelocities); // Analytical velocity fields and dissipation coefficients // can only be used with this spatial discretization scheme - advectionKernel.setDiscretizationScheme( - // ls::DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER); - ls::DiscretizationSchemeEnum::WENO_5TH_ORDER); + advectionKernel.setSpatialScheme( + // ls::SpatialSchemeEnum::LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER); + ls::SpatialSchemeEnum::WENO_5TH_ORDER); } else { // for numerical velocities, just use the default // spatial discretization scheme, which is not accurate for certain @@ -184,8 +184,8 @@ int main() { // For coordinate independent velocity functions // this numerical scheme is superior though. // However, it is slower. - // advectionKernel.setDiscretizationScheme( - // ls::DiscretizationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER); + // advectionKernel.setSpatialScheme( + // ls::SpatialSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER); } // advect the level set until 50s have passed diff --git a/examples/SquareEtch/SquareEtch.py b/examples/SquareEtch/SquareEtch.py index 6d40022c..347a50b5 100644 --- a/examples/SquareEtch/SquareEtch.py +++ b/examples/SquareEtch/SquareEtch.py @@ -3,6 +3,7 @@ vls.Logger.setLogLevel(vls.LogLevel.INFO) + # 1. Define the Velocity Field # We inherit from viennals.VelocityField to define custom logic in Python class EtchingField(vls.VelocityField): @@ -10,12 +11,13 @@ def getScalarVelocity(self, coordinate, material, normalVector, pointId): if material == 2: return -0.1 if material == 1: - return -1. + return -1.0 return 0.0 def getVectorVelocity(self, coordinate, material, normalVector, pointId): return [0.0] * len(coordinate) + # 1. Define the Velocity Field # We inherit from viennals.VelocityField to define custom logic in Python class DepositionField(vls.VelocityField): @@ -25,25 +27,29 @@ def getScalarVelocity(self, coordinate, material, normalVector, pointId): def getVectorVelocity(self, coordinate, material, normalVector, pointId): return [0.0] * len(coordinate) + def main(): # 1. Parse Arguments - parser = argparse.ArgumentParser(description="Run Square Etch simulation in 2D or 3D.") + parser = argparse.ArgumentParser( + description="Run Square Etch simulation in 2D or 3D." + ) parser.add_argument( - "-D", "--dim", - type=int, - default=2, - choices=[2, 3], - help="Dimension of the simulation (2 or 3). Default is 2." + "-D", + "--dim", + type=int, + default=2, + choices=[2, 3], + help="Dimension of the simulation (2 or 3). Default is 2.", ) args = parser.parse_args() - + DIMENSION = args.dim vls.setDimension(DIMENSION) vls.setNumThreads(8) extent = 30.0 gridDelta = 0.47 - + # Define bounds and boundary conditions trenchBottom = -2.0 bounds = [-extent, extent, -extent, extent] @@ -73,14 +79,14 @@ def main(): # 3. Trench Geometry # -------------------------------------- trench = vls.Domain(bounds, boundaryCons, gridDelta) - + # Define Box Corners based on dimension minCorner = list(origin) maxCorner = list(origin) - originMask = list (origin) + originMask = list(origin) for i in range(DIMENSION - 1): minCorner[i] = -extent / 1.5 - maxCorner[i] = extent / 1.5 + maxCorner[i] = extent / 1.5 minCorner[DIMENSION - 1] = trenchBottom maxCorner[DIMENSION - 1] = 1.0 originMask[DIMENSION - 1] = trenchBottom + 1e-9 @@ -89,7 +95,9 @@ def main(): vls.MakeGeometry(trench, box).apply() # Subtract trench from substrate (Relative Complement) - vls.BooleanOperation(substrate, trench, vls.BooleanOperationEnum.RELATIVE_COMPLEMENT).apply() + vls.BooleanOperation( + substrate, trench, vls.BooleanOperationEnum.RELATIVE_COMPLEMENT + ).apply() # 4. Create the Mask Layer # The mask prevents etching at the bottom of the trench in specific configurations, @@ -97,7 +105,7 @@ def main(): mask = vls.Domain(bounds, boundaryCons, gridDelta) vls.MakeGeometry(mask, vls.Plane(originMask, downNormal)).apply() - + # Intersect with substrate geometry vls.BooleanOperation(mask, substrate, vls.BooleanOperationEnum.INTERSECT).apply() @@ -105,11 +113,11 @@ def main(): print(f"Running in {DIMENSION}D.") print("Extracting initial meshes...") mesh = vls.Mesh() - + # Save substrate vls.ToSurfaceMesh(substrate, mesh).apply() vls.VTKWriter(mesh, f"substrate-{DIMENSION}D.vtp").apply() - + # Save mask vls.ToSurfaceMesh(mask, mesh).apply() vls.VTKWriter(mesh, f"mask-{DIMENSION}D.vtp").apply() @@ -117,7 +125,6 @@ def main(): print("Creating new layer...") polymer = vls.Domain(substrate) - # 6. Setup Advection deposition = DepositionField() etching = EtchingField() @@ -142,9 +149,9 @@ def main(): advectionKernel.setSaveAdvectionVelocities(True) advectionKernel.setVelocityField(etching) - + # Use default spatial discretization scheme (Lax Friedrichs 1st order) as in the C++ else branch - # advectionKernel.setDiscretizationScheme(viennals.DiscretizationSchemeEnum.LAX_FRIEDRICHS_1ST_ORDER) + # advectionKernel.setSpatialScheme(viennals.SpatialSchemeEnum.LAX_FRIEDRICHS_1ST_ORDER) # 7. Time Loop finalTime = 10.0 @@ -153,7 +160,7 @@ def main(): # The advect kernel calculates stable time steps automatically. # We call apply() repeatedly until we reach finalTime. - + # Note: In the C++ example, the loop structure is: # for (double time = 0.; time < finalTime; time += advectionKernel.getAdvectedTime()) # This implies one step is taken, then time is updated. @@ -161,19 +168,21 @@ def main(): # Save initial state vls.ToSurfaceMesh(polymer, mesh).apply() vls.VTKWriter(mesh, f"numerical-{DIMENSION}D-0.vtp").apply() - + while currentTime < finalTime: advectionKernel.apply() - + stepTime = advectionKernel.getAdvectedTime() currentTime += stepTime - - print(f"Advection step: {counter}, time: {stepTime:.4f} (Total: {currentTime:.4f})") - + + print( + f"Advection step: {counter}, time: {stepTime:.4f} (Total: {currentTime:.4f})" + ) + # Export result vls.ToSurfaceMesh(polymer, mesh).apply() vls.VTKWriter(mesh, f"numerical-{DIMENSION}D-{counter}.vtp").apply() - + counter += 1 print(f"\nNumber of Advection steps taken: {counter}") @@ -182,5 +191,6 @@ def main(): vls.ToSurfaceMesh(polymer, mesh).apply() vls.VTKWriter(mesh, f"final-{DIMENSION}D.vtp").apply() + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index 4a56192d..3ab9d5b1 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -40,7 +40,7 @@ using namespace viennacore; /// Enumeration for the different spatial discretization schemes /// used by the advection kernel -enum struct DiscretizationSchemeEnum : unsigned { +enum struct SpatialSchemeEnum : unsigned { ENGQUIST_OSHER_1ST_ORDER = 0, ENGQUIST_OSHER_2ND_ORDER = 1, LAX_FRIEDRICHS_1ST_ORDER = 2, @@ -54,6 +54,9 @@ enum struct DiscretizationSchemeEnum : unsigned { WENO_5TH_ORDER = 10 }; +// Legacy naming (deprecated, will be removed in future versions) +using IntegrationSchemeEnum = SpatialSchemeEnum; + /// This class is used to advance level sets over time. /// Level sets are passed to the constructor in a std::vector, with /// the last element being the level set to advect, or "top level set", while @@ -71,8 +74,7 @@ template class Advect { protected: std::vector>> levelSets; SmartPointer> velocities = nullptr; - DiscretizationSchemeEnum discretizationScheme = - DiscretizationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER; + SpatialSchemeEnum spatialScheme = SpatialSchemeEnum::ENGQUIST_OSHER_1ST_ORDER; double timeStepRatio = 0.4999; double dissipationAlpha = 1.0; bool calculateNormalVectors = true; @@ -217,8 +219,8 @@ template class Advect { // immediate re-expansion T cutoff = 1.0; int finalWidth = 2; - if (discretizationScheme == - DiscretizationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + if (spatialScheme == + SpatialSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { cutoff = 1.5; finalWidth = 3; } @@ -400,7 +402,7 @@ template class Advect { /// be used. This function fills up the storedRates to be used when moving the /// LS template - double integrateTime(DiscretizationSchemeType discretizationScheme, + double integrateTime(DiscretizationSchemeType spatialScheme, double maxTimeStep) { auto &topDomain = levelSets.back()->getDomain(); @@ -458,7 +460,7 @@ template class Advect { iterators.emplace_back(ls->getDomain()); } - DiscretizationSchemeType scheme(discretizationScheme); + DiscretizationSchemeType scheme(spatialScheme); for (ConstSparseIterator it(topDomain, startVector); it.getStartIndices() < endVector; ++it) { @@ -585,64 +587,57 @@ template class Advect { /// and the maximum time step, but it does **not** move the surface. void computeRates(double maxTimeStep = std::numeric_limits::max()) { prepareLS(); - if (discretizationScheme == - DiscretizationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER) { + if (spatialScheme == SpatialSchemeEnum::ENGQUIST_OSHER_1ST_ORDER) { auto is = lsInternal::EngquistOsher(levelSets.back(), velocities, calculateNormalVectors); currentTimeStep = integrateTime(is, maxTimeStep); - } else if (discretizationScheme == - DiscretizationSchemeEnum::ENGQUIST_OSHER_2ND_ORDER) { + } else if (spatialScheme == SpatialSchemeEnum::ENGQUIST_OSHER_2ND_ORDER) { auto is = lsInternal::EngquistOsher(levelSets.back(), velocities, calculateNormalVectors); currentTimeStep = integrateTime(is, maxTimeStep); - } else if (discretizationScheme == - DiscretizationSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER) { + } else if (spatialScheme == SpatialSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER) { auto alphas = findGlobalAlphas(); auto is = lsInternal::LaxFriedrichs(levelSets.back(), velocities, dissipationAlpha, alphas, calculateNormalVectors); currentTimeStep = integrateTime(is, maxTimeStep); - } else if (discretizationScheme == - DiscretizationSchemeEnum::LAX_FRIEDRICHS_2ND_ORDER) { + } else if (spatialScheme == SpatialSchemeEnum::LAX_FRIEDRICHS_2ND_ORDER) { auto alphas = findGlobalAlphas(); auto is = lsInternal::LaxFriedrichs(levelSets.back(), velocities, dissipationAlpha, alphas, calculateNormalVectors); currentTimeStep = integrateTime(is, maxTimeStep); - } else if (discretizationScheme == - DiscretizationSchemeEnum:: - LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER) { + } else if (spatialScheme == + SpatialSchemeEnum::LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER) { auto is = lsInternal::LocalLaxFriedrichsAnalytical( levelSets.back(), velocities); currentTimeStep = integrateTime(is, maxTimeStep); - } else if (discretizationScheme == - DiscretizationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + } else if (spatialScheme == + SpatialSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { auto is = lsInternal::LocalLocalLaxFriedrichs( levelSets.back(), velocities, dissipationAlpha); currentTimeStep = integrateTime(is, maxTimeStep); - } else if (discretizationScheme == - DiscretizationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER) { + } else if (spatialScheme == + SpatialSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER) { auto is = lsInternal::LocalLocalLaxFriedrichs( levelSets.back(), velocities, dissipationAlpha); currentTimeStep = integrateTime(is, maxTimeStep); - } else if (discretizationScheme == - DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + } else if (spatialScheme == + SpatialSchemeEnum::LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { auto is = lsInternal::LocalLaxFriedrichs( levelSets.back(), velocities, dissipationAlpha); currentTimeStep = integrateTime(is, maxTimeStep); - } else if (discretizationScheme == - DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER) { + } else if (spatialScheme == + SpatialSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER) { auto is = lsInternal::LocalLaxFriedrichs( levelSets.back(), velocities, dissipationAlpha); currentTimeStep = integrateTime(is, maxTimeStep); - } else if (discretizationScheme == - DiscretizationSchemeEnum:: - STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + } else if (spatialScheme == + SpatialSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { auto is = lsInternal::StencilLocalLaxFriedrichsScalar( levelSets.back(), velocities, dissipationAlpha); currentTimeStep = integrateTime(is, maxTimeStep); - } else if (discretizationScheme == - DiscretizationSchemeEnum::WENO_5TH_ORDER) { + } else if (spatialScheme == SpatialSchemeEnum::WENO_5TH_ORDER) { // Instantiate WENO5 with order 3 (neighbors +/- 3) auto is = lsInternal::WENO5(levelSets.back(), velocities, dissipationAlpha); @@ -786,8 +781,8 @@ template class Advect { rebuildLS(); // Adjust all level sets below the advected one - if (discretizationScheme != - DiscretizationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + if (spatialScheme != + SpatialSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { for (unsigned i = 0; i < levelSets.size() - 1; ++i) { BooleanOperation(levelSets[i], levelSets.back(), BooleanOperationEnum::INTERSECT) @@ -901,9 +896,15 @@ template class Advect { bool getCalculateNormalVectors() const { return calculateNormalVectors; } /// Set which spatial discretization scheme should be used out of the ones - /// specified in DiscretizationSchemeEnum. - void setDiscretizationScheme(DiscretizationSchemeEnum scheme) { - discretizationScheme = scheme; + /// specified in SpatialSchemeEnum. + void setSpatialScheme(SpatialSchemeEnum scheme) { spatialScheme = scheme; } + + // Deprecated, will be remove in future versions: use setSpatialScheme instead + void setIntegrationScheme(IntegrationSchemeEnum scheme) { + VIENNACORE_LOG_WARNING( + "Advect::setIntegrationScheme is deprecated and will be removed in " + "future versions. Use setSpatialScheme instead."); + spatialScheme = scheme; } /// Set the alpha dissipation coefficient. @@ -929,46 +930,38 @@ template class Advect { return; } - if (discretizationScheme == - DiscretizationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER) { + if (spatialScheme == SpatialSchemeEnum::ENGQUIST_OSHER_1ST_ORDER) { lsInternal::EngquistOsher::prepareLS(levelSets.back()); - } else if (discretizationScheme == - DiscretizationSchemeEnum::ENGQUIST_OSHER_2ND_ORDER) { + } else if (spatialScheme == SpatialSchemeEnum::ENGQUIST_OSHER_2ND_ORDER) { lsInternal::EngquistOsher::prepareLS(levelSets.back()); - } else if (discretizationScheme == - DiscretizationSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER) { + } else if (spatialScheme == SpatialSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER) { lsInternal::LaxFriedrichs::prepareLS(levelSets.back()); - } else if (discretizationScheme == - DiscretizationSchemeEnum::LAX_FRIEDRICHS_2ND_ORDER) { + } else if (spatialScheme == SpatialSchemeEnum::LAX_FRIEDRICHS_2ND_ORDER) { lsInternal::LaxFriedrichs::prepareLS(levelSets.back()); - } else if (discretizationScheme == - DiscretizationSchemeEnum:: - LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER) { + } else if (spatialScheme == + SpatialSchemeEnum::LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER) { lsInternal::LocalLaxFriedrichsAnalytical::prepareLS( levelSets.back()); - } else if (discretizationScheme == - DiscretizationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + } else if (spatialScheme == + SpatialSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { lsInternal::LocalLocalLaxFriedrichs::prepareLS(levelSets.back()); - } else if (discretizationScheme == - DiscretizationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER) { + } else if (spatialScheme == + SpatialSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER) { lsInternal::LocalLocalLaxFriedrichs::prepareLS(levelSets.back()); - } else if (discretizationScheme == - DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + } else if (spatialScheme == + SpatialSchemeEnum::LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { lsInternal::LocalLaxFriedrichs::prepareLS(levelSets.back()); - } else if (discretizationScheme == - DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER) { + } else if (spatialScheme == + SpatialSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER) { lsInternal::LocalLaxFriedrichs::prepareLS(levelSets.back()); - } else if (discretizationScheme == - DiscretizationSchemeEnum:: - STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + } else if (spatialScheme == + SpatialSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { lsInternal::StencilLocalLaxFriedrichsScalar::prepareLS( levelSets.back()); - } else if (discretizationScheme == - DiscretizationSchemeEnum::WENO_5TH_ORDER) { + } else if (spatialScheme == SpatialSchemeEnum::WENO_5TH_ORDER) { // WENO5 requires a stencil radius of 3 (template parameter 3) lsInternal::WENO5::prepareLS(levelSets.back()); - } else if (discretizationScheme == - DiscretizationSchemeEnum::WENO_5TH_ORDER) { + } else if (spatialScheme == SpatialSchemeEnum::WENO_5TH_ORDER) { // WENO5 requires a stencil radius of 3 (template parameter 3) lsInternal::WENO5::prepareLS(levelSets.back()); } else { diff --git a/include/viennals/lsAdvectRungeKutta3.hpp b/include/viennals/lsAdvectRungeKutta3.hpp index 51bfb048..964460f2 100644 --- a/include/viennals/lsAdvectRungeKutta3.hpp +++ b/include/viennals/lsAdvectRungeKutta3.hpp @@ -102,8 +102,8 @@ template class AdvectRungeKutta3 : public Advect { Base::rebuildLS(); // Adjust lower layers - if (Base::discretizationScheme != - DiscretizationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + if (Base::spatialScheme != + SpatialSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { for (unsigned i = 0; i < levelSets.size() - 1; ++i) { BooleanOperation(levelSets[i], levelSets.back(), BooleanOperationEnum::INTERSECT) diff --git a/python/pyWrap.cpp b/python/pyWrap.cpp index 67324ac1..af6b0644 100644 --- a/python/pyWrap.cpp +++ b/python/pyWrap.cpp @@ -81,32 +81,34 @@ PYBIND11_MODULE(VIENNALS_MODULE_NAME, module) { .def("print", [](Logger &instance) { instance.print(std::cout); }); // ------ ENUMS ------ - py::native_enum(module, "DiscretizationSchemeEnum", - "enum.IntEnum") + py::native_enum(module, "SpatialSchemeEnum", + "enum.IntEnum") .value("ENGQUIST_OSHER_1ST_ORDER", - DiscretizationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER) + SpatialSchemeEnum::ENGQUIST_OSHER_1ST_ORDER) .value("ENGQUIST_OSHER_2ND_ORDER", - DiscretizationSchemeEnum::ENGQUIST_OSHER_2ND_ORDER) + SpatialSchemeEnum::ENGQUIST_OSHER_2ND_ORDER) .value("LAX_FRIEDRICHS_1ST_ORDER", - DiscretizationSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER) + SpatialSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER) .value("LAX_FRIEDRICHS_2ND_ORDER", - DiscretizationSchemeEnum::LAX_FRIEDRICHS_2ND_ORDER) - .value( - "LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER", - DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER) + SpatialSchemeEnum::LAX_FRIEDRICHS_2ND_ORDER) + .value("LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER", + SpatialSchemeEnum::LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER) .value("LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER", - DiscretizationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) + SpatialSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) .value("LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER", - DiscretizationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER) + SpatialSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER) .value("LOCAL_LAX_FRIEDRICHS_1ST_ORDER", - DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_1ST_ORDER) + SpatialSchemeEnum::LOCAL_LAX_FRIEDRICHS_1ST_ORDER) .value("LOCAL_LAX_FRIEDRICHS_2ND_ORDER", - DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER) + SpatialSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER) .value("STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER", - DiscretizationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) - .value("WENO_5TH_ORDER", DiscretizationSchemeEnum::WENO_5TH_ORDER) + SpatialSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) + .value("WENO_5TH_ORDER", SpatialSchemeEnum::WENO_5TH_ORDER) .finalize(); + module.attr("IntegrationSchemeEnum") = + module.attr("SpatialSchemeEnum"); // IntegrationSchemeEnum is deprecated + py::native_enum(module, "BooleanOperationEnum", "enum.IntEnum") .value("INTERSECT", BooleanOperationEnum::INTERSECT) @@ -164,7 +166,7 @@ PYBIND11_MODULE(VIENNALS_MODULE_NAME, module) { py::class_, SmartPointer>>(module, "PointData") // constructors .def(py::init(&SmartPointer>::New<>)) - // methods + // methods .def("insertNextScalarData", (void(PointData::*)(const PointData::ScalarDataType &, const std::string &)) & diff --git a/python/pyWrap.hpp b/python/pyWrap.hpp index 055a30a0..9001ae7d 100644 --- a/python/pyWrap.hpp +++ b/python/pyWrap.hpp @@ -220,8 +220,11 @@ template void bindApi(py::module &module) { .def("getCalculateNormalVectors", &Advect::getCalculateNormalVectors, "Get whether normal vectors are computed during advection.") - .def("setDiscretizationScheme", &Advect::setDiscretizationScheme, + .def("setSpatialScheme", &Advect::setSpatialScheme, "Set the spatial discretization scheme to use during advection.") + .def("setIntegrationScheme", &Advect::setIntegrationScheme, + "(DEPRECATED, use setSpatialScheme instead) Set the spatial " + "discretization scheme to use during advection.") .def("setDissipationAlpha", &Advect::setDissipationAlpha, "Set the dissipation value to use for Lax Friedrichs spatial " "discretization.") @@ -621,26 +624,26 @@ template void bindApi(py::module &module) { .def("setLevelSet", &MakeGeometry::setLevelSet, "Set the levelset in which to create the geometry.") .def("setGeometry", - (void(MakeGeometry::*)(SmartPointer>)) & - MakeGeometry::setGeometry) + (void (MakeGeometry::*)( + SmartPointer>))&MakeGeometry::setGeometry) .def("setGeometry", - (void(MakeGeometry::*)(SmartPointer>)) & - MakeGeometry::setGeometry) + (void (MakeGeometry::*)( + SmartPointer>))&MakeGeometry::setGeometry) .def("setGeometry", - (void(MakeGeometry::*)(SmartPointer>)) & - MakeGeometry::setGeometry) + (void (MakeGeometry::*)( + SmartPointer>))&MakeGeometry::setGeometry) .def("setGeometry", - (void(MakeGeometry::*)(SmartPointer>)) & - MakeGeometry::setGeometry) + (void (MakeGeometry::*)( + SmartPointer>))&MakeGeometry::setGeometry) .def("setGeometry", - (void(MakeGeometry::*)(SmartPointer>)) & - MakeGeometry::setGeometry) + (void (MakeGeometry::*)( + SmartPointer>))&MakeGeometry::setGeometry) .def("setIgnoreBoundaryConditions", - (void(MakeGeometry::*)(bool)) & - MakeGeometry::setIgnoreBoundaryConditions) + (void (MakeGeometry::*)( + bool))&MakeGeometry::setIgnoreBoundaryConditions) .def("setIgnoreBoundaryConditions", - (void(MakeGeometry::*)(std::array)) & - MakeGeometry::setIgnoreBoundaryConditions) + (void (MakeGeometry::*)(std::array))&MakeGeometry< + T, D>::setIgnoreBoundaryConditions) .def("apply", &MakeGeometry::apply, "Generate the geometry."); // MarkVoidPoints diff --git a/python/viennals/__init__.pyi b/python/viennals/__init__.pyi index a895bf83..9c32e40b 100644 --- a/python/viennals/__init__.pyi +++ b/python/viennals/__init__.pyi @@ -17,13 +17,14 @@ from viennals._core import CurvatureEnum from viennals._core import Extrude from viennals._core import FeatureDetectionEnum from viennals._core import FileFormatEnum -from viennals._core import DiscretizationSchemeEnum from viennals._core import LogLevel from viennals._core import Logger from viennals._core import MaterialMap from viennals._core import Mesh from viennals._core import PointData from viennals._core import Slice +from viennals._core import SpatialSchemeEnum +from viennals._core import SpatialSchemeEnum as IntegrationSchemeEnum from viennals._core import TransformEnum from viennals._core import TransformMesh from viennals._core import VTKReader @@ -116,7 +117,7 @@ __all__: list[str] = [ "FromVolumeMesh", "GeometricAdvect", "GeometricAdvectDistribution", - "DiscretizationSchemeEnum", + "IntegrationSchemeEnum", "LogLevel", "Logger", "MakeGeometry", @@ -133,6 +134,7 @@ __all__: list[str] = [ "Reduce", "RemoveStrayPoints", "Slice", + "SpatialSchemeEnum", "Sphere", "SphereDistribution", "StencilLocalLaxFriedrichsScalar", diff --git a/python/viennals/_core.pyi b/python/viennals/_core.pyi index 66b7a0ed..81ff81af 100644 --- a/python/viennals/_core.pyi +++ b/python/viennals/_core.pyi @@ -8,8 +8,8 @@ import enum import typing from viennals import d2 import viennals.d2 -from viennals import d3 import viennals.d3 +from viennals import d3 __all__: list[str] = [ "BooleanOperationEnum", @@ -18,13 +18,14 @@ __all__: list[str] = [ "Extrude", "FeatureDetectionEnum", "FileFormatEnum", - "DiscretizationSchemeEnum", + "IntegrationSchemeEnum", "LogLevel", "Logger", "MaterialMap", "Mesh", "PointData", "Slice", + "SpatialSchemeEnum", "TransformEnum", "TransformMesh", "VTKReader", @@ -187,47 +188,6 @@ class FileFormatEnum(enum.IntEnum): Convert to a string according to format_spec. """ -class DiscretizationSchemeEnum(enum.IntEnum): - ENGQUIST_OSHER_1ST_ORDER: typing.ClassVar[ - DiscretizationSchemeEnum - ] # value = - ENGQUIST_OSHER_2ND_ORDER: typing.ClassVar[ - DiscretizationSchemeEnum - ] # value = - LAX_FRIEDRICHS_1ST_ORDER: typing.ClassVar[ - DiscretizationSchemeEnum - ] # value = - LAX_FRIEDRICHS_2ND_ORDER: typing.ClassVar[ - DiscretizationSchemeEnum - ] # value = - LOCAL_LAX_FRIEDRICHS_1ST_ORDER: typing.ClassVar[ - DiscretizationSchemeEnum - ] # value = - LOCAL_LAX_FRIEDRICHS_2ND_ORDER: typing.ClassVar[ - DiscretizationSchemeEnum - ] # value = - LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER: typing.ClassVar[ - DiscretizationSchemeEnum - ] # value = - LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER: typing.ClassVar[ - DiscretizationSchemeEnum - ] # value = - LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER: typing.ClassVar[ - DiscretizationSchemeEnum - ] # value = - STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER: typing.ClassVar[ - DiscretizationSchemeEnum - ] # value = - WENO_5TH_ORDER: typing.ClassVar[ - DiscretizationSchemeEnum - ] # value = - @classmethod - def __new__(cls, value): ... - def __format__(self, format_spec): - """ - Convert to a string according to format_spec. - """ - class LogLevel: """ Members: @@ -539,6 +499,47 @@ class Slice: Set the path where the slice should be written to. """ +class SpatialSchemeEnum(enum.IntEnum): + ENGQUIST_OSHER_1ST_ORDER: typing.ClassVar[ + SpatialSchemeEnum + ] # value = + ENGQUIST_OSHER_2ND_ORDER: typing.ClassVar[ + SpatialSchemeEnum + ] # value = + LAX_FRIEDRICHS_1ST_ORDER: typing.ClassVar[ + SpatialSchemeEnum + ] # value = + LAX_FRIEDRICHS_2ND_ORDER: typing.ClassVar[ + SpatialSchemeEnum + ] # value = + LOCAL_LAX_FRIEDRICHS_1ST_ORDER: typing.ClassVar[ + SpatialSchemeEnum + ] # value = + LOCAL_LAX_FRIEDRICHS_2ND_ORDER: typing.ClassVar[ + SpatialSchemeEnum + ] # value = + LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER: typing.ClassVar[ + SpatialSchemeEnum + ] # value = + LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER: typing.ClassVar[ + SpatialSchemeEnum + ] # value = + LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER: typing.ClassVar[ + SpatialSchemeEnum + ] # value = + STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER: typing.ClassVar[ + SpatialSchemeEnum + ] # value = + WENO_5TH_ORDER: typing.ClassVar[ + SpatialSchemeEnum + ] # value = + @classmethod + def __new__(cls, value): ... + def __format__(self, format_spec): + """ + Convert to a string according to format_spec. + """ + class TransformEnum(enum.IntEnum): ROTATION: typing.ClassVar[TransformEnum] # value = SCALE: typing.ClassVar[TransformEnum] # value = @@ -733,3 +734,4 @@ def setNumThreads(arg0: typing.SupportsInt) -> None: ... __version__: str = "5.3.0" version: str = "5.3.0" +IntegrationSchemeEnum = SpatialSchemeEnum diff --git a/python/viennals/d2.pyi b/python/viennals/d2.pyi index bc1cd4cd..d9d57727 100644 --- a/python/viennals/d2.pyi +++ b/python/viennals/d2.pyi @@ -141,9 +141,9 @@ class Advect: Set whether voids in the geometry should be ignored during advection or not. """ - def setDiscretizationScheme(self, arg0: viennals._core.DiscretizationSchemeEnum) -> None: + def setIntegrationScheme(self, arg0: viennals._core.SpatialSchemeEnum) -> None: """ - Set the spatial discretization scheme to use during advection. + (DEPRECATED, use setSpatialScheme instead) Set the spatial discretization scheme to use during advection. """ def setSaveAdvectionVelocities(self, arg0: bool) -> None: @@ -156,6 +156,11 @@ class Advect: Set whether only a single advection step should be performed. """ + def setSpatialScheme(self, arg0: viennals._core.SpatialSchemeEnum) -> None: + """ + Set the spatial discretization scheme to use during advection. + """ + def setTimeStepRatio(self, arg0: typing.SupportsFloat) -> None: """ Set the maximum time step size relative to grid size. Advection is only stable for <0.5. diff --git a/python/viennals/d3.pyi b/python/viennals/d3.pyi index 4237a69f..de64dbbe 100644 --- a/python/viennals/d3.pyi +++ b/python/viennals/d3.pyi @@ -136,9 +136,9 @@ class Advect: Set whether voids in the geometry should be ignored during advection or not. """ - def setDiscretizationScheme(self, arg0: viennals._core.DiscretizationSchemeEnum) -> None: + def setIntegrationScheme(self, arg0: viennals._core.SpatialSchemeEnum) -> None: """ - Set the spatial discretization scheme to use during advection. + (DEPRECATED, use setSpatialScheme instead) Set the spatial discretization scheme to use during advection. """ def setSaveAdvectionVelocities(self, arg0: bool) -> None: @@ -151,6 +151,11 @@ class Advect: Set whether only a single advection step should be performed. """ + def setSpatialScheme(self, arg0: viennals._core.SpatialSchemeEnum) -> None: + """ + Set the spatial discretization scheme to use during advection. + """ + def setTimeStepRatio(self, arg0: typing.SupportsFloat) -> None: """ Set the maximum time step size relative to grid size. Advection is only stable for <0.5. diff --git a/tests/Advection/Advection.cpp b/tests/Advection/Advection.cpp index 664f04e9..4e0d4a2c 100644 --- a/tests/Advection/Advection.cpp +++ b/tests/Advection/Advection.cpp @@ -49,18 +49,18 @@ int main() { double gridDelta = 0.6999999; - std::vector discretizationSchemes = { - ls::DiscretizationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER, - ls::DiscretizationSchemeEnum::ENGQUIST_OSHER_2ND_ORDER, - ls::DiscretizationSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER, - ls::DiscretizationSchemeEnum::LAX_FRIEDRICHS_2ND_ORDER, - ls::DiscretizationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER, - ls::DiscretizationSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER, - ls::DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_1ST_ORDER, - ls::DiscretizationSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER, - ls::DiscretizationSchemeEnum::WENO_5TH_ORDER}; - - for (auto discretizationScheme : discretizationSchemes) { + std::vector spatialSchemes = { + ls::SpatialSchemeEnum::ENGQUIST_OSHER_1ST_ORDER, + ls::SpatialSchemeEnum::ENGQUIST_OSHER_2ND_ORDER, + ls::SpatialSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER, + ls::SpatialSchemeEnum::LAX_FRIEDRICHS_2ND_ORDER, + ls::SpatialSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER, + ls::SpatialSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER, + ls::SpatialSchemeEnum::LOCAL_LAX_FRIEDRICHS_1ST_ORDER, + ls::SpatialSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER, + ls::SpatialSchemeEnum::WENO_5TH_ORDER}; + + for (auto scheme : spatialSchemes) { auto sphere1 = ls::Domain::New(gridDelta); double origin[3] = {5., 0., 0.}; @@ -93,7 +93,7 @@ int main() { ls::AdvectForwardEuler advectionKernel; advectionKernel.insertNextLevelSet(sphere1); advectionKernel.setVelocityField(velocities); - advectionKernel.setDiscretizationScheme(discretizationScheme); + advectionKernel.setSpatialScheme(scheme); advectionKernel.setSaveAdvectionVelocities(true); double time = 0.; diff --git a/tests/Advection/Advection.py b/tests/Advection/Advection.py index ba6c3a63..aebf3cb0 100644 --- a/tests/Advection/Advection.py +++ b/tests/Advection/Advection.py @@ -2,6 +2,7 @@ vls.setDimension(3) + # Implement own velocity field class VelocityField(vls.VelocityField): def __init__(self): @@ -15,6 +16,7 @@ def getScalarVelocity(self, coord, material, normal, pointId): def getVectorVelocity(self, coord, material, normal, pointId): return [0.0, 0.0, 0.0] + def main(): # Set number of threads vls.setNumThreads(8) @@ -22,15 +24,15 @@ def main(): gridDelta = 0.6999999 discretizationSchemes = [ - vls.DiscretizationSchemeEnum.ENGQUIST_OSHER_1ST_ORDER, - vls.DiscretizationSchemeEnum.ENGQUIST_OSHER_2ND_ORDER, - vls.DiscretizationSchemeEnum.LAX_FRIEDRICHS_1ST_ORDER, - vls.DiscretizationSchemeEnum.LAX_FRIEDRICHS_2ND_ORDER, - vls.DiscretizationSchemeEnum.LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER, - vls.DiscretizationSchemeEnum.LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER, - vls.DiscretizationSchemeEnum.LOCAL_LAX_FRIEDRICHS_1ST_ORDER, - vls.DiscretizationSchemeEnum.LOCAL_LAX_FRIEDRICHS_2ND_ORDER, - vls.DiscretizationSchemeEnum.WENO_5TH_ORDER + vls.SpatialSchemeEnum.ENGQUIST_OSHER_1ST_ORDER, + vls.SpatialSchemeEnum.ENGQUIST_OSHER_2ND_ORDER, + vls.SpatialSchemeEnum.LAX_FRIEDRICHS_1ST_ORDER, + vls.SpatialSchemeEnum.LAX_FRIEDRICHS_2ND_ORDER, + vls.SpatialSchemeEnum.LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER, + vls.SpatialSchemeEnum.LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER, + vls.SpatialSchemeEnum.LOCAL_LAX_FRIEDRICHS_1ST_ORDER, + vls.SpatialSchemeEnum.LOCAL_LAX_FRIEDRICHS_2ND_ORDER, + vls.SpatialSchemeEnum.WENO_5TH_ORDER, ] for scheme in discretizationSchemes: @@ -47,7 +49,7 @@ def main(): advectionKernel = vls.AdvectRungeKutta3() advectionKernel.insertNextLevelSet(sphere1) advectionKernel.setVelocityField(velocities) - advectionKernel.setDiscretizationScheme(scheme) + advectionKernel.setSpatialScheme(scheme) advectionKernel.setSaveAdvectionVelocities(True) time = 0.0 @@ -65,5 +67,6 @@ def main(): i += 1 print(f"Done scheme {scheme}") + if __name__ == "__main__": main() diff --git a/tests/Advection/StencilLaxFriedrichsTest.cpp b/tests/Advection/StencilLaxFriedrichsTest.cpp index cbc58754..0cfef606 100644 --- a/tests/Advection/StencilLaxFriedrichsTest.cpp +++ b/tests/Advection/StencilLaxFriedrichsTest.cpp @@ -62,8 +62,8 @@ int main() { advectionKernel.setAdvectionTime(0.5); // Set the specific spatial discretization scheme - advectionKernel.setDiscretizationScheme( - ls::DiscretizationSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER); + advectionKernel.setSpatialScheme( + ls::SpatialSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER); // Run Advection std::cout << "Running Stencil Local Lax Friedrichs Advection..." << std::endl; diff --git a/tests/Advection/TimeIntegrationComparison.cpp b/tests/Advection/TimeIntegrationComparison.cpp index b9f1c21d..f1f2d018 100644 --- a/tests/Advection/TimeIntegrationComparison.cpp +++ b/tests/Advection/TimeIntegrationComparison.cpp @@ -65,16 +65,14 @@ int main() { advectFE.insertNextLevelSet(sphereFE); advectFE.setVelocityField(velocityField); advectFE.setAdvectionTime(2.0); - advectFE.setDiscretizationScheme( - ls::DiscretizationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER); + advectFE.setSpatialScheme(ls::SpatialSchemeEnum::ENGQUIST_OSHER_1ST_ORDER); // Setup Advection: Runge-Kutta 3 ls::AdvectRungeKutta3 advectRK3; advectRK3.insertNextLevelSet(sphereRK3); advectRK3.setVelocityField(velocityField); advectRK3.setAdvectionTime(2.0); - advectRK3.setDiscretizationScheme( - ls::DiscretizationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER); + advectRK3.setSpatialScheme(ls::SpatialSchemeEnum::ENGQUIST_OSHER_1ST_ORDER); // Run Advection std::cout << "Running Forward Euler Advection..." << std::endl; diff --git a/tests/Advection/TimeIntegrationComparison.py b/tests/Advection/TimeIntegrationComparison.py index 3224e990..b179a245 100644 --- a/tests/Advection/TimeIntegrationComparison.py +++ b/tests/Advection/TimeIntegrationComparison.py @@ -1,6 +1,8 @@ import viennals as vls + vls.setDimension(3) + # Define a constant velocity field class ConstantVelocity(vls.VelocityField): def __init__(self, vel): @@ -13,6 +15,7 @@ def getScalarVelocity(self, coord, material, normal, pointId): def getVectorVelocity(self, coord, material, normal, pointId): return self.velocity + def main(): # Define grid and domain bounds gridDelta = 0.1 @@ -38,19 +41,19 @@ def main(): advectFE.insertNextLevelSet(sphereFE) advectFE.setVelocityField(velocityField) advectFE.setAdvectionTime(2.0) - advectFE.setDiscretizationScheme(vls.DiscretizationSchemeEnum.ENGQUIST_OSHER_1ST_ORDER) + advectFE.setSpatialScheme(vls.SpatialSchemeEnum.ENGQUIST_OSHER_1ST_ORDER) # Setup Advection: Runge-Kutta 3 advectRK3 = vls.AdvectRungeKutta3() advectRK3.insertNextLevelSet(sphereRK3) advectRK3.setVelocityField(velocityField) advectRK3.setAdvectionTime(2.0) - advectRK3.setDiscretizationScheme(vls.DiscretizationSchemeEnum.ENGQUIST_OSHER_1ST_ORDER) + advectRK3.setSpatialScheme(vls.SpatialSchemeEnum.ENGQUIST_OSHER_1ST_ORDER) # Run Advection print("Running Forward Euler Advection...") advectFE.apply() - + checkFE = vls.Check(sphereFE) checkFE.apply() @@ -60,7 +63,7 @@ def main(): print("Running Runge-Kutta 3 Advection...") advectRK3.apply() - + checkRK3 = vls.Check(sphereRK3) checkRK3.apply() @@ -68,5 +71,6 @@ def main(): vls.ToSurfaceMesh(sphereRK3, meshRK3).apply() vls.VTKWriter(meshRK3, "sphereRK3.vtp").apply() + if __name__ == "__main__": main() diff --git a/tests/Advection2D/Advection2D.cpp b/tests/Advection2D/Advection2D.cpp index 85e31aca..e49a9172 100644 --- a/tests/Advection2D/Advection2D.cpp +++ b/tests/Advection2D/Advection2D.cpp @@ -105,8 +105,8 @@ int main() { ls::Advect advectionKernel; advectionKernel.insertNextLevelSet(sphere1); advectionKernel.setVelocityField(velocities); - advectionKernel.setDiscretizationScheme( - ls::DiscretizationSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER); + advectionKernel.setSpatialScheme( + ls::SpatialSchemeEnum::LAX_FRIEDRICHS_1ST_ORDER); advectionKernel.setAdvectionTime(20.); advectionKernel.apply(); diff --git a/tests/GeometricAdvectPerformance/GeometricAdvectPerformance.cpp b/tests/GeometricAdvectPerformance/GeometricAdvectPerformance.cpp index 15208d0f..64ebdb05 100644 --- a/tests/GeometricAdvectPerformance/GeometricAdvectPerformance.cpp +++ b/tests/GeometricAdvectPerformance/GeometricAdvectPerformance.cpp @@ -126,7 +126,7 @@ int main() { // ls::ToSurfaceMesh(newLayer, mesh).apply(); // ls::VTKWriter(mesh, "finalSurface.vtk").apply(); - // now rund lsAdvect for all other advection schemes + // now run lsAdvect for all other advection schemes // last scheme is SLLFS with i == 9 for (unsigned i = 0; i < 10; ++i) { if (i == 4) { @@ -139,8 +139,7 @@ int main() { auto velocities = ls::SmartPointer::New(); advectionKernel.setVelocityField(velocities); advectionKernel.setAdvectionTime(depositionDistance); - advectionKernel.setDiscretizationScheme( - static_cast(i)); + advectionKernel.setSpatialScheme(static_cast(i)); { auto start = std::chrono::high_resolution_clock::now(); advectionKernel.apply(); From 726ca9957e09a05778cf2881ab90987ca5926784 Mon Sep 17 00:00:00 2001 From: filipovic Date: Mon, 22 Dec 2025 15:46:38 +0100 Subject: [PATCH 26/57] Fixed RK3 time integration scheme to only need velocities on the sparse field --- include/viennals/lsAdvect.hpp | 24 ++++- include/viennals/lsAdvectRungeKutta3.hpp | 127 ++++++++++++----------- 2 files changed, 86 insertions(+), 65 deletions(-) diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index 762c4d77..4c18f1ed 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -54,6 +54,12 @@ enum struct IntegrationSchemeEnum : unsigned { WENO_5TH_ORDER = 10 }; +/// Enumeration for the different Time Integration schemes +enum struct TimeIntegrationSchemeEnum : unsigned { + FORWARD_EULER = 0, + RUNGE_KUTTA_3RD_ORDER = 1 +}; + /// This class is used to advance level sets over time. /// Level sets are passed to the constructor in a std::vector, with /// the last element being the level set to advect, or "top level set", while @@ -73,6 +79,8 @@ template class Advect { SmartPointer> velocities = nullptr; IntegrationSchemeEnum integrationScheme = IntegrationSchemeEnum::ENGQUIST_OSHER_1ST_ORDER; + TimeIntegrationSchemeEnum timeIntegrationScheme = + TimeIntegrationSchemeEnum::FORWARD_EULER; double timeStepRatio = 0.4999; double dissipationAlpha = 1.0; bool calculateNormalVectors = true; @@ -84,6 +92,7 @@ template class Advect { bool saveAdvectionVelocities = false; bool updatePointData = true; bool checkDissipation = true; + double integrationCutoff = 0.5; bool adaptiveTimeStepping = false; unsigned adaptiveTimeStepSubdivisions = 20; static constexpr double wrappingLayerEpsilon = 1e-4; @@ -132,7 +141,7 @@ template class Advect { for (ConstSparseIterator it(topDomain, startVector); it.getStartIndices() < endVector; ++it) { - if (!it.isDefined() || std::abs(it.getValue()) > 0.5) + if (!it.isDefined() || std::abs(it.getValue()) > integrationCutoff) continue; const T value = it.getValue(); @@ -462,7 +471,7 @@ template class Advect { for (ConstSparseIterator it(topDomain, startVector); it.getStartIndices() < endVector; ++it) { - if (!it.isDefined() || std::abs(it.getValue()) > 0.5) + if (!it.isDefined() || std::abs(it.getValue()) > integrationCutoff) continue; T value = it.getValue(); @@ -696,9 +705,9 @@ template class Advect { for (unsigned localId = 0; localId < maxId; ++localId) { T &value = segment.definedValues[localId]; - // // Skip points that were not part of computeRates (outer layers) - // if (std::abs(value) > 0.5) - // continue; + // Skip points that were not part of computeRates (outer layers) + if (std::abs(value) > integrationCutoff) + continue; double time = dt; @@ -902,6 +911,11 @@ template class Advect { integrationScheme = scheme; } + /// Set which time integration scheme should be used. + void setTimeIntegrationScheme(TimeIntegrationSchemeEnum scheme) { + timeIntegrationScheme = scheme; + } + /// Set the alpha dissipation coefficient. /// For lsLaxFriedrichs, this is used as the alpha value. /// For all other LaxFriedrichs schemes it is used as a diff --git a/include/viennals/lsAdvectRungeKutta3.hpp b/include/viennals/lsAdvectRungeKutta3.hpp index f807bf9f..10b9c5da 100644 --- a/include/viennals/lsAdvectRungeKutta3.hpp +++ b/include/viennals/lsAdvectRungeKutta3.hpp @@ -21,86 +21,93 @@ template class AdvectRungeKutta3 : public Advect { using Base::Base; // inherit all constructors private: - // Helper function for linear combination: target = wTarget * target + wSource - // * source + // Helper function for linear combination: + // target = wTarget * target + wSource * source void combineLevelSets(double wTarget, const SmartPointer> &target, - double wSource, - const SmartPointer> &source) { - // We write the result into levelSets.back() (which is passed as 'target' or - // 'source' usually, but here we assume target is the destination) - // Actually, to support u_new = a*u_old + b*u_new, we should write to - // levelSets.back(). - auto &domainDest = levelSets.back()->getDomain(); - const auto &domainTarget = target->getDomain(); - const auto &domainSource = source->getDomain(); - - if (domainTarget.getNumberOfSegments() != - domainSource.getNumberOfSegments()) { - VIENNACORE_LOG_ERROR( - "AdvectRungeKutta3: Topology mismatch in combineLevelSets."); - return; - } + double wSource) { -#pragma omp parallel for schedule(static) - for (int p = 0; p < static_cast(domainDest.getNumberOfSegments()); - ++p) { + auto &domainDest = levelSets.back()->getDomain(); + auto &grid = levelSets.back()->getGrid(); + +#pragma omp parallel num_threads(domainDest.getNumberOfSegments()) + { + int p = 0; +#ifdef _OPENMP + p = omp_get_thread_num(); +#endif auto &segDest = domainDest.getDomainSegment(p); - const auto &segTarget = domainTarget.getDomainSegment(p); - const auto &segSource = domainSource.getDomainSegment(p); - - if (segTarget.definedValues.size() == segSource.definedValues.size() && - segDest.definedValues.size() == segTarget.definedValues.size()) { - for (size_t i = 0; i < segDest.definedValues.size(); ++i) { - segDest.definedValues[i] = wTarget * segTarget.definedValues[i] + - wSource * segSource.definedValues[i]; + + viennahrle::Index start = (p == 0) + ? grid.getMinGridPoint() + : domainDest.getSegmentation()[p - 1]; + viennahrle::Index end = + (p != static_cast(domainDest.getNumberOfSegments()) - 1) + ? domainDest.getSegmentation()[p] + : grid.incrementIndices(grid.getMaxGridPoint()); + + viennahrle::ConstSparseIterator::DomainType> itDest( + domainDest, start); + viennahrle::ConstSparseIterator::DomainType> + itTarget(target->getDomain(), start); + + unsigned definedValueIndex = 0; + for (; itDest.getStartIndices() < end; ++itDest) { + if (itDest.isDefined()) { + itTarget.goToIndicesSequential(itDest.getStartIndices()); + T valSource = itDest.getValue(); + T valTarget = itTarget.getValue(); + segDest.definedValues[definedValueIndex++] = + wTarget * valTarget + wSource * valSource; } } } } double advect(double maxTimeStep) override { - // 1. Prepare and Expand - Base::prepareLS(); + Base::timeIntegrationScheme = + TimeIntegrationSchemeEnum::RUNGE_KUTTA_3RD_ORDER; - // 2. Save u^n (Deep copy with identical topology) + // 1. Save u^n (Deep copy to preserve topology) if (originalLevelSet == nullptr) { originalLevelSet = Domain::New(levelSets.back()->getGrid()); } originalLevelSet->deepCopy(levelSets.back()); - double limit = maxTimeStep / 3.0; - double totalDt = 0.0; + // 2. Determine the single time step 'dt' for all stages. + // This is the maximum stable time step for a forward Euler step from u^n. + Base::computeRates(maxTimeStep); + const double dt = Base::getCurrentTimeStep(); - // 3. Stage 1: u^(1) = u^n + dt * L(u^n) - Base::computeRates(limit); - double dt = Base::getCurrentTimeStep(); + // If dt is 0 or negative, no advection is possible or needed. + if (dt <= 0) { + return 0.; + } + + // Stage 1: u^(1) = u^n + dt * L(u^n) + // L(u^n) is already in storedRates from the computeRates call above. + // updateLevelSet modifies levelSets.back() from u^n to u^(1). Base::updateLevelSet(dt); - totalDt += dt; - - // 4. Stage 2: u^(2) = 3/4 u^n + 1/4 (u^(1) + dt * L(u^(1))) - // Calculate rates based on u^(1) (current levelSets.back()) - limit = (maxTimeStep - totalDt) / 2.0; - Base::computeRates(limit); - dt = Base::getCurrentTimeStep(); - // Update to get u^* = u^(1) + dt * L(u^(1)) + + // Stage 2: u^(2) = 3/4 u^n + 1/4 (u^(1) + dt * L(u^(1))) + // The current levelSets.back() is u^(1). Compute L(u^(1)). + Base::computeRates(dt); + // Update levelSets.back() from u^(1) to u* = u^(1) + dt * L(u^(1)). Base::updateLevelSet(dt); - // Combine: u^(2) = 0.75 * u^n + 0.25 * u^* - combineLevelSets(0.75, originalLevelSet, 0.25, levelSets.back()); - totalDt += dt; - - // 5. Stage 3: u^(n+1) = 1/3 u^n + 2/3 (u^(2) + dt * L(u^(2))) - // Calculate rates based on u^(2) (current levelSets.back()) - limit = (maxTimeStep - totalDt); - Base::computeRates(limit); - dt = Base::getCurrentTimeStep(); - // Update to get u^** = u^(2) + dt * L(u^(2)) + // Combine to get u^(2) = 0.75 * u^n + 0.25 * u*. + // The result is written to levelSets.back(). + combineLevelSets(0.75, originalLevelSet, 0.25); + + // Stage 3: u^(n+1) = 1/3 u^n + 2/3 (u^(2) + dt * L(u^(2))) + // The current levelSets.back() is u^(2). Compute L(u^(2)). + Base::computeRates(dt); + // Update levelSets.back() from u^(2) to u** = u^(2) + dt * L(u^(2)). Base::updateLevelSet(dt); - // Combine: u^(n+1) = 1/3 * u^n + 2/3 * u^** - combineLevelSets(1.0 / 3.0, originalLevelSet, 2.0 / 3.0, levelSets.back()); - totalDt += dt; + // Combine to get u^(n+1) = 1/3 * u^n + 2/3 * u**. + // The result is written to levelSets.back(). + combineLevelSets(1.0 / 3.0, originalLevelSet, 2.0 / 3.0); - // 6. Finalize: Re-segment and renormalize only at the end + // Finalize: Re-segment and renormalize the final result. Base::rebuildLS(); // Adjust lower layers @@ -113,7 +120,7 @@ template class AdvectRungeKutta3 : public Advect { } } - return totalDt; + return dt; } }; From 7c3db6f98aae0a2d527e85b6031235037b451049 Mon Sep 17 00:00:00 2001 From: filipovic Date: Sat, 27 Dec 2025 21:55:43 +0100 Subject: [PATCH 27/57] Fixed RK3 (Strong-Stability preserving Runge-Kutta 3rd order) temporal scheme. The scheme uses a constant velocity during the entire time step, while the gradient is averaged according to SSP-RK3. --- include/viennals/lsAdvect.hpp | 10 +-------- include/viennals/lsAdvectRungeKutta3.hpp | 26 +++++++++++------------- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index 4c18f1ed..21eecad9 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -421,8 +421,6 @@ template class Advect { voidMarkerPointer = pointData.getScalarData(MarkVoidPoints::voidPointLabel, true); if (voidMarkerPointer == nullptr) { - VIENNACORE_LOG_WARNING("Advect: Cannot find void point markers. Not " - "ignoring void points."); VIENNACORE_LOG_WARNING("Advect: Cannot find void point markers. Not " "ignoring void points."); ignoreVoids = false; @@ -433,7 +431,6 @@ template class Advect { if (!storedRates.empty()) { VIENNACORE_LOG_WARNING("Advect: Overwriting previously stored rates."); - VIENNACORE_LOG_WARNING("Advect: Overwriting previously stored rates."); } storedRates.resize(topDomain.getNumberOfSegments()); @@ -543,8 +540,7 @@ template class Advect { } else { // Sub-case 3b: Interface Interaction auto adaptiveFactor = 1.0 / adaptiveTimeStepSubdivisions; - if (useAdaptiveTimeStepping && - difference > adaptiveFactor * cfl) { + if (useAdaptiveTimeStepping && difference > 0.2 * cfl) { // Adaptive Sub-stepping: // Approaching boundary: Force small steps to gather // flux statistics and prevent numerical overshoot ("Soft @@ -665,9 +661,6 @@ template class Advect { VIENNACORE_LOG_WARNING( "Integration time step ratio should be smaller than 0.5. " "Advection might fail!"); - VIENNACORE_LOG_WARNING( - "Integration time step ratio should be smaller than 0.5. " - "Advection might fail!"); } auto &topDomain = levelSets.back()->getDomain(); @@ -934,7 +927,6 @@ template class Advect { void prepareLS() { // check whether a level set and velocities have been given if (levelSets.empty()) { - VIENNACORE_LOG_ERROR("No level sets passed to Advect."); VIENNACORE_LOG_ERROR("No level sets passed to Advect."); return; } diff --git a/include/viennals/lsAdvectRungeKutta3.hpp b/include/viennals/lsAdvectRungeKutta3.hpp index 10b9c5da..d3dc005b 100644 --- a/include/viennals/lsAdvectRungeKutta3.hpp +++ b/include/viennals/lsAdvectRungeKutta3.hpp @@ -23,9 +23,7 @@ template class AdvectRungeKutta3 : public Advect { private: // Helper function for linear combination: // target = wTarget * target + wSource * source - void combineLevelSets(double wTarget, - const SmartPointer> &target, - double wSource) { + void combineLevelSets(double wTarget, double wSource) { auto &domainDest = levelSets.back()->getDomain(); auto &grid = levelSets.back()->getGrid(); @@ -49,7 +47,7 @@ template class AdvectRungeKutta3 : public Advect { viennahrle::ConstSparseIterator::DomainType> itDest( domainDest, start); viennahrle::ConstSparseIterator::DomainType> - itTarget(target->getDomain(), start); + itTarget(originalLevelSet->getDomain(), start); unsigned definedValueIndex = 0; for (; itDest.getStartIndices() < end; ++itDest) { @@ -68,21 +66,21 @@ template class AdvectRungeKutta3 : public Advect { Base::timeIntegrationScheme = TimeIntegrationSchemeEnum::RUNGE_KUTTA_3RD_ORDER; - // 1. Save u^n (Deep copy to preserve topology) + // 1. Determine the single time step 'dt' for all stages. + Base::computeRates(maxTimeStep); + const double dt = Base::getCurrentTimeStep(); + + // 2. Save u^n (Deep copy to preserve topology) + // Ensure the level set is prepared (expanded) before taking the snapshot. + // This ensures originalLevelSet has sufficient padding for the first stage. if (originalLevelSet == nullptr) { originalLevelSet = Domain::New(levelSets.back()->getGrid()); } originalLevelSet->deepCopy(levelSets.back()); - // 2. Determine the single time step 'dt' for all stages. - // This is the maximum stable time step for a forward Euler step from u^n. - Base::computeRates(maxTimeStep); - const double dt = Base::getCurrentTimeStep(); - // If dt is 0 or negative, no advection is possible or needed. - if (dt <= 0) { + if (dt <= 0) return 0.; - } // Stage 1: u^(1) = u^n + dt * L(u^n) // L(u^n) is already in storedRates from the computeRates call above. @@ -96,7 +94,7 @@ template class AdvectRungeKutta3 : public Advect { Base::updateLevelSet(dt); // Combine to get u^(2) = 0.75 * u^n + 0.25 * u*. // The result is written to levelSets.back(). - combineLevelSets(0.75, originalLevelSet, 0.25); + combineLevelSets(0.75, 0.25); // Stage 3: u^(n+1) = 1/3 u^n + 2/3 (u^(2) + dt * L(u^(2))) // The current levelSets.back() is u^(2). Compute L(u^(2)). @@ -105,7 +103,7 @@ template class AdvectRungeKutta3 : public Advect { Base::updateLevelSet(dt); // Combine to get u^(n+1) = 1/3 * u^n + 2/3 * u**. // The result is written to levelSets.back(). - combineLevelSets(1.0 / 3.0, originalLevelSet, 2.0 / 3.0); + combineLevelSets(1.0 / 3.0, 2.0 / 3.0); // Finalize: Re-segment and renormalize the final result. Base::rebuildLS(); From 824bb7ab01074768c5c0dabe86577e042cecb6ca Mon Sep 17 00:00:00 2001 From: filipovic Date: Sun, 28 Dec 2025 22:55:51 +0100 Subject: [PATCH 28/57] Added compile-time "IntegrationScheme" naming depreciation narning. --- include/viennals/lsAdvect.hpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index bf355827..c3f28b71 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -55,7 +55,8 @@ enum struct SpatialSchemeEnum : unsigned { }; // Legacy naming (deprecated, will be removed in future versions) -using IntegrationSchemeEnum = SpatialSchemeEnum; +using IntegrationSchemeEnum [[deprecated("Use SpatialSchemeEnum instead")]] = + SpatialSchemeEnum; /// Enumeration for the different time integration schemes /// used to select the advection kernel @@ -901,8 +902,9 @@ template class Advect { /// specified in SpatialSchemeEnum. void setSpatialScheme(SpatialSchemeEnum scheme) { spatialScheme = scheme; } - // Deprecated, will be remove in future versions: use setSpatialScheme instead - void setIntegrationScheme(IntegrationSchemeEnum scheme) { + // Deprecated, will be removed in future versions: use setSpatialScheme instead + [[deprecated("Use setSpatialScheme instead")]] void setIntegrationScheme( + IntegrationSchemeEnum scheme) { VIENNACORE_LOG_WARNING( "Advect::setIntegrationScheme is deprecated and will be removed in " "future versions. Use setSpatialScheme instead."); From 10e4c0fb7778c54f82459380f17bdad017e221e0 Mon Sep 17 00:00:00 2001 From: filipovic Date: Sun, 28 Dec 2025 22:59:49 +0100 Subject: [PATCH 29/57] format --- include/viennals/lsAdvect.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index c3f28b71..a9c1d10f 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -902,9 +902,10 @@ template class Advect { /// specified in SpatialSchemeEnum. void setSpatialScheme(SpatialSchemeEnum scheme) { spatialScheme = scheme; } - // Deprecated, will be removed in future versions: use setSpatialScheme instead - [[deprecated("Use setSpatialScheme instead")]] void setIntegrationScheme( - IntegrationSchemeEnum scheme) { + // Deprecated and will be removed in future versions: + // use setSpatialScheme instead + [[deprecated("Use setSpatialScheme instead")]] void + setIntegrationScheme(IntegrationSchemeEnum scheme) { VIENNACORE_LOG_WARNING( "Advect::setIntegrationScheme is deprecated and will be removed in " "future versions. Use setSpatialScheme instead."); From 13cafdafc9ef4ca08b9a22256a3bb5df22d57c34 Mon Sep 17 00:00:00 2001 From: filipovic Date: Mon, 29 Dec 2025 23:57:10 +0100 Subject: [PATCH 30/57] Refactor Advect to use TemporalSchemeEnum and consolidate time integration --- .../AirGapDeposition/AirGapDeposition.cpp | 59 ++++++-- examples/AirGapDeposition/AirGapDeposition.py | 1 + include/viennals/lsAdvect.hpp | 87 ++++++++---- include/viennals/lsAdvectForwardEuler.hpp | 14 -- include/viennals/lsAdvectRungeKutta3.hpp | 126 ------------------ include/viennals/lsAdvectTimeIntegration.hpp | 124 +++++++++++++++++ python/pyWrap.cpp | 7 + python/pyWrap.hpp | 25 +--- tests/Advection/Advection.cpp | 4 +- tests/Advection/StencilLaxFriedrichsTest.cpp | 4 +- tests/Advection/TimeIntegrationComparison.cpp | 28 +++- 11 files changed, 275 insertions(+), 204 deletions(-) delete mode 100644 include/viennals/lsAdvectForwardEuler.hpp delete mode 100644 include/viennals/lsAdvectRungeKutta3.hpp create mode 100644 include/viennals/lsAdvectTimeIntegration.hpp diff --git a/examples/AirGapDeposition/AirGapDeposition.cpp b/examples/AirGapDeposition/AirGapDeposition.cpp index 9352d6e1..aa49d222 100644 --- a/examples/AirGapDeposition/AirGapDeposition.cpp +++ b/examples/AirGapDeposition/AirGapDeposition.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include #include @@ -166,6 +165,11 @@ int main() { auto newLayerFE = ls::SmartPointer>::New(substrateFE); + auto substrateRK2 = + ls::SmartPointer>::New(substrate); + auto newLayerRK2 = + ls::SmartPointer>::New(substrateRK2); + auto substrateRK = ls::SmartPointer>::New(substrate); auto newLayerRK = @@ -184,22 +188,40 @@ int main() { advectionKernelFE.insertNextLevelSet(newLayerFE); advectionKernelFE.setVelocityField(velocities); advectionKernelFE.setIgnoreVoids(true); + advectionKernelFE.setTemporalScheme(ls::TemporalSchemeEnum::FORWARD_EULER); double passedTimeFE = runSimulation( advectionKernelFE, newLayerFE, totalSimulationTime, outputInterval, "FE"); - // RK Kernel - ls::AdvectRungeKutta3 advectionKernelRK; + // RK2 Kernel + ls::Advect advectionKernelRK2; + advectionKernelRK2.insertNextLevelSet(substrateRK2); + advectionKernelRK2.insertNextLevelSet(newLayerRK2); + advectionKernelRK2.setVelocityField(velocities); + advectionKernelRK2.setIgnoreVoids(true); + advectionKernelRK2.setTemporalScheme( + ls::TemporalSchemeEnum::RUNGE_KUTTA_2ND_ORDER); + + double passedTimeRK2 = + runSimulation(advectionKernelRK2, newLayerRK2, totalSimulationTime, + outputInterval, "RK2"); + + // RK3 Kernel + ls::Advect advectionKernelRK; advectionKernelRK.insertNextLevelSet(substrateRK); advectionKernelRK.insertNextLevelSet(newLayerRK); advectionKernelRK.setVelocityField(velocities); advectionKernelRK.setIgnoreVoids(true); + advectionKernelRK.setTemporalScheme( + ls::TemporalSchemeEnum::RUNGE_KUTTA_3RD_ORDER); - double passedTimeRK = runSimulation( - advectionKernelRK, newLayerRK, totalSimulationTime, outputInterval, "RK"); + double passedTimeRK = + runSimulation(advectionKernelRK, newLayerRK, totalSimulationTime, + outputInterval, "RK3"); std::cout << "Time passed FE: " << passedTimeFE << std::endl; - std::cout << "Time passed RK: " << passedTimeRK << std::endl; + std::cout << "Time passed RK2: " << passedTimeRK2 << std::endl; + std::cout << "Time passed RK3: " << passedTimeRK << std::endl; // FE Output { @@ -220,13 +242,32 @@ int main() { ls::VTKWriter(multiMesh, "multimesh_FE.vtp").apply(); } - // RK Output + // RK2 Output + { + ls::WriteVisualizationMesh writer; + writer.insertNextLevelSet(substrateRK2); + writer.insertNextLevelSet(newLayerRK2); + writer.addMetaData("time", passedTimeRK2); + writer.setFileName("airgap_RK2"); + writer.setExtractHullMesh(true); + writer.apply(); + + ls::ToMultiSurfaceMesh multiMeshKernel; + multiMeshKernel.insertNextLevelSet(substrateRK2); + multiMeshKernel.insertNextLevelSet(newLayerRK2); + auto multiMesh = ls::SmartPointer>::New(); + multiMeshKernel.setMesh(multiMesh); + multiMeshKernel.apply(); + ls::VTKWriter(multiMesh, "multimesh_RK2.vtp").apply(); + } + + // RK3 Output { ls::WriteVisualizationMesh writer; writer.insertNextLevelSet(substrateRK); writer.insertNextLevelSet(newLayerRK); writer.addMetaData("time", passedTimeRK); - writer.setFileName("airgap_RK"); + writer.setFileName("airgap_RK3"); writer.setExtractHullMesh(true); writer.apply(); @@ -236,7 +277,7 @@ int main() { auto multiMesh = ls::SmartPointer>::New(); multiMeshKernel.setMesh(multiMesh); multiMeshKernel.apply(); - ls::VTKWriter(multiMesh, "multimesh_RK.vtp").apply(); + ls::VTKWriter(multiMesh, "multimesh_RK3.vtp").apply(); } return 0; diff --git a/examples/AirGapDeposition/AirGapDeposition.py b/examples/AirGapDeposition/AirGapDeposition.py index 08c505bf..5a56663d 100644 --- a/examples/AirGapDeposition/AirGapDeposition.py +++ b/examples/AirGapDeposition/AirGapDeposition.py @@ -73,6 +73,7 @@ def getVectorVelocity(self, coord, material, normal, pointId): advectionKernel.setVelocityField(velocities) advectionKernel.setIgnoreVoids(True) +advectionKernel.setTemporalScheme(vls.TemporalSchemeEnum.RUNGE_KUTTA_3RD_ORDER) # Now advect the level set 50 times, outputting every # advection step. Save the physical time that diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index a9c1d10f..3b03bdf1 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -34,6 +34,11 @@ #include #endif +// Forward declaration for Time Integration Schemes +namespace lsInternal { +template struct AdvectTimeIntegration; +} + namespace viennals { using namespace viennacore; @@ -62,7 +67,8 @@ using IntegrationSchemeEnum [[deprecated("Use SpatialSchemeEnum instead")]] = /// used to select the advection kernel enum struct TemporalSchemeEnum : unsigned { FORWARD_EULER = 0, - RUNGE_KUTTA_3RD_ORDER = 1 + RUNGE_KUTTA_2ND_ORDER = 1, + RUNGE_KUTTA_3RD_ORDER = 2 }; /// This class is used to advance level sets over time. @@ -79,6 +85,9 @@ template class Advect { viennahrle::ConstSparseIterator::DomainType>; using hrleIndexType = viennahrle::IndexType; + // Allow the time integration struct to access protected members + friend struct lsInternal::AdvectTimeIntegration>; + protected: std::vector>> levelSets; SmartPointer> velocities = nullptr; @@ -99,6 +108,7 @@ template class Advect { bool adaptiveTimeStepping = false; unsigned adaptiveTimeStepSubdivisions = 20; static constexpr double wrappingLayerEpsilon = 1e-4; + SmartPointer> originalLevelSet = nullptr; // this vector will hold the maximum time step for each point and the // corresponding velocity @@ -215,6 +225,45 @@ template class Advect { return finalAlphas; } + // Helper function for linear combination: + // target = wTarget * target + wSource * source + void combineLevelSets(double wTarget, double wSource) { + + auto &domainDest = levelSets.back()->getDomain(); + auto &grid = levelSets.back()->getGrid(); + +#pragma omp parallel num_threads(domainDest.getNumberOfSegments()) + { + int p = 0; +#ifdef _OPENMP + p = omp_get_thread_num(); +#endif + auto &segDest = domainDest.getDomainSegment(p); + + viennahrle::Index start = (p == 0) + ? grid.getMinGridPoint() + : domainDest.getSegmentation()[p - 1]; + viennahrle::Index end = + (p != static_cast(domainDest.getNumberOfSegments()) - 1) + ? domainDest.getSegmentation()[p] + : grid.incrementIndices(grid.getMaxGridPoint()); + + ConstSparseIterator itDest(domainDest, start); + ConstSparseIterator itTarget(originalLevelSet->getDomain(), start); + + unsigned definedValueIndex = 0; + for (; itDest.getStartIndices() < end; ++itDest) { + if (itDest.isDefined()) { + itTarget.goToIndicesSequential(itDest.getStartIndices()); + T valSource = itDest.getValue(); + T valTarget = itTarget.getValue(); + segDest.definedValues[definedValueIndex++] = + wTarget * valTarget + wSource * valSource; + } + } + } + } + void rebuildLS() { // TODO: this function uses Manhattan distances for renormalisation, // since this is the quickest. For visualisation applications, better @@ -774,26 +823,18 @@ template class Advect { /// internal function used as a wrapper to call specialized integrateTime /// with the chosen spatial discretization scheme virtual double advect(double maxTimeStep) { - // prepareLS(); - - if (currentTimeStep < 0. || storedRates.empty()) - computeRates(maxTimeStep); - - updateLevelSet(currentTimeStep); - - rebuildLS(); - - // Adjust all level sets below the advected one - if (spatialScheme != - SpatialSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { - for (unsigned i = 0; i < levelSets.size() - 1; ++i) { - BooleanOperation(levelSets[i], levelSets.back(), - BooleanOperationEnum::INTERSECT) - .apply(); - } + switch (temporalScheme) { + case TemporalSchemeEnum::RUNGE_KUTTA_2ND_ORDER: + return lsInternal::AdvectTimeIntegration< + T, D, Advect>::evolveRungeKutta2(*this, maxTimeStep); + case TemporalSchemeEnum::RUNGE_KUTTA_3RD_ORDER: + return lsInternal::AdvectTimeIntegration< + T, D, Advect>::evolveRungeKutta3(*this, maxTimeStep); + case TemporalSchemeEnum::FORWARD_EULER: + default: + return lsInternal::AdvectTimeIntegration< + T, D, Advect>::evolveForwardEuler(*this, maxTimeStep); } - - return currentTimeStep; } public: @@ -968,9 +1009,6 @@ template class Advect { } else if (spatialScheme == SpatialSchemeEnum::WENO_5TH_ORDER) { // WENO5 requires a stencil radius of 3 (template parameter 3) lsInternal::WENO5::prepareLS(levelSets.back()); - } else if (spatialScheme == SpatialSchemeEnum::WENO_5TH_ORDER) { - // WENO5 requires a stencil radius of 3 (template parameter 3) - lsInternal::WENO5::prepareLS(levelSets.back()); } else { VIENNACORE_LOG_ERROR("Advect: Discretization scheme not found."); } @@ -1009,3 +1047,6 @@ template class Advect { PRECOMPILE_PRECISION_DIMENSION(Advect) } // namespace viennals + +// Include implementation of time integration schemes +#include diff --git a/include/viennals/lsAdvectForwardEuler.hpp b/include/viennals/lsAdvectForwardEuler.hpp deleted file mode 100644 index 6530f64f..00000000 --- a/include/viennals/lsAdvectForwardEuler.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -namespace viennals { - -template class AdvectForwardEuler : public Advect { -public: - using Advect::Advect; -}; - -PRECOMPILE_PRECISION_DIMENSION(AdvectForwardEuler); - -} // namespace viennals diff --git a/include/viennals/lsAdvectRungeKutta3.hpp b/include/viennals/lsAdvectRungeKutta3.hpp deleted file mode 100644 index 66de28b1..00000000 --- a/include/viennals/lsAdvectRungeKutta3.hpp +++ /dev/null @@ -1,126 +0,0 @@ -#pragma once - -#include -#include - -namespace viennals { - -/// This class implements the Strong Stability Preserving (SSP) Runge-Kutta -/// 3rd order time integration scheme (also known as TVD RK3). -/// It performs time integration using three stages of Euler steps and convex -/// combinations to preserve stability properties. -template class AdvectRungeKutta3 : public Advect { - using ConstSparseIterator = - viennahrle::ConstSparseIterator::DomainType>; - using hrleIndexType = viennahrle::IndexType; - using Base = Advect; - using Base::levelSets; - SmartPointer> originalLevelSet = nullptr; - -public: - using Base::Base; // inherit all constructors - -private: - // Helper function for linear combination: - // target = wTarget * target + wSource * source - void combineLevelSets(double wTarget, double wSource) { - - auto &domainDest = levelSets.back()->getDomain(); - auto &grid = levelSets.back()->getGrid(); - -#pragma omp parallel num_threads(domainDest.getNumberOfSegments()) - { - int p = 0; -#ifdef _OPENMP - p = omp_get_thread_num(); -#endif - auto &segDest = domainDest.getDomainSegment(p); - - viennahrle::Index start = (p == 0) - ? grid.getMinGridPoint() - : domainDest.getSegmentation()[p - 1]; - viennahrle::Index end = - (p != static_cast(domainDest.getNumberOfSegments()) - 1) - ? domainDest.getSegmentation()[p] - : grid.incrementIndices(grid.getMaxGridPoint()); - - viennahrle::ConstSparseIterator::DomainType> itDest( - domainDest, start); - viennahrle::ConstSparseIterator::DomainType> - itTarget(originalLevelSet->getDomain(), start); - - unsigned definedValueIndex = 0; - for (; itDest.getStartIndices() < end; ++itDest) { - if (itDest.isDefined()) { - itTarget.goToIndicesSequential(itDest.getStartIndices()); - T valSource = itDest.getValue(); - T valTarget = itTarget.getValue(); - segDest.definedValues[definedValueIndex++] = - wTarget * valTarget + wSource * valSource; - } - } - } - } - - double advect(double maxTimeStep) override { - Base::temporalScheme = TemporalSchemeEnum::RUNGE_KUTTA_3RD_ORDER; - - // 1. Determine the single time step 'dt' for all stages. - Base::computeRates(maxTimeStep); - const double dt = Base::getCurrentTimeStep(); - - // 2. Save u^n (Deep copy to preserve topology) - // Ensure the level set is prepared (expanded) before taking the snapshot. - // This ensures originalLevelSet has sufficient padding for the first stage. - if (originalLevelSet == nullptr) { - originalLevelSet = Domain::New(levelSets.back()->getGrid()); - } - originalLevelSet->deepCopy(levelSets.back()); - - // If dt is 0 or negative, no advection is possible or needed. - if (dt <= 0) - return 0.; - - // Stage 1: u^(1) = u^n + dt * L(u^n) - // L(u^n) is already in storedRates from the computeRates call above. - // updateLevelSet modifies levelSets.back() from u^n to u^(1). - Base::updateLevelSet(dt); - - // Stage 2: u^(2) = 3/4 u^n + 1/4 (u^(1) + dt * L(u^(1))) - // The current levelSets.back() is u^(1). Compute L(u^(1)). - Base::computeRates(dt); - // Update levelSets.back() from u^(1) to u* = u^(1) + dt * L(u^(1)). - Base::updateLevelSet(dt); - // Combine to get u^(2) = 0.75 * u^n + 0.25 * u*. - // The result is written to levelSets.back(). - combineLevelSets(0.75, 0.25); - - // Stage 3: u^(n+1) = 1/3 u^n + 2/3 (u^(2) + dt * L(u^(2))) - // The current levelSets.back() is u^(2). Compute L(u^(2)). - Base::computeRates(dt); - // Update levelSets.back() from u^(2) to u** = u^(2) + dt * L(u^(2)). - Base::updateLevelSet(dt); - // Combine to get u^(n+1) = 1/3 * u^n + 2/3 * u**. - // The result is written to levelSets.back(). - combineLevelSets(1.0 / 3.0, 2.0 / 3.0); - - // Finalize: Re-segment and renormalize the final result. - Base::rebuildLS(); - - // Adjust lower layers - if (Base::spatialScheme != - SpatialSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { - for (unsigned i = 0; i < levelSets.size() - 1; ++i) { - BooleanOperation(levelSets[i], levelSets.back(), - BooleanOperationEnum::INTERSECT) - .apply(); - } - } - - return dt; - } -}; - -PRECOMPILE_PRECISION_DIMENSION(AdvectRungeKutta3); - -} // namespace viennals \ No newline at end of file diff --git a/include/viennals/lsAdvectTimeIntegration.hpp b/include/viennals/lsAdvectTimeIntegration.hpp new file mode 100644 index 00000000..9079b903 --- /dev/null +++ b/include/viennals/lsAdvectTimeIntegration.hpp @@ -0,0 +1,124 @@ +#pragma once + +#include +#include + +namespace lsInternal { + +template struct AdvectTimeIntegration { + + static double evolveForwardEuler(AdvectType &kernel, double maxTimeStep) { + if (kernel.currentTimeStep < 0. || kernel.storedRates.empty()) + kernel.computeRates(maxTimeStep); + + kernel.updateLevelSet(kernel.currentTimeStep); + + kernel.rebuildLS(); + + // Adjust all level sets below the advected one + if (kernel.spatialScheme != + viennals::SpatialSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + for (unsigned i = 0; i < kernel.levelSets.size() - 1; ++i) { + viennals::BooleanOperation( + kernel.levelSets[i], kernel.levelSets.back(), + viennals::BooleanOperationEnum::INTERSECT) + .apply(); + } + } + + return kernel.currentTimeStep; + } + + static double evolveRungeKutta2(AdvectType &kernel, double maxTimeStep) { + // TVD Runge-Kutta 2nd Order (Heun's Method) + // 1. Determine time step + kernel.computeRates(maxTimeStep); + const double dt = kernel.getCurrentTimeStep(); + + // 2. Save u^n + if (kernel.originalLevelSet == nullptr) { + kernel.originalLevelSet = + viennals::Domain::New(kernel.levelSets.back()->getGrid()); + } + kernel.originalLevelSet->deepCopy(kernel.levelSets.back()); + + if (dt <= 0) + return 0.; + + // Stage 1: u^(1) = u^n + dt * L(u^n) + kernel.updateLevelSet(dt); + + // Stage 2: u^(n+1) = 1/2 u^n + 1/2 (u^(1) + dt * L(u^(1))) + // Current level set is u^(1). Compute L(u^(1)). + kernel.computeRates(dt); + // Update to u* = u^(1) + dt * L(u^(1)) + kernel.updateLevelSet(dt); + // Combine: u^(n+1) = 0.5 * u^n + 0.5 * u* + kernel.combineLevelSets(0.5, 0.5); + + // Finalize + kernel.rebuildLS(); + + // Adjust lower layers + if (kernel.spatialScheme != + viennals::SpatialSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + for (unsigned i = 0; i < kernel.levelSets.size() - 1; ++i) { + viennals::BooleanOperation( + kernel.levelSets[i], kernel.levelSets.back(), + viennals::BooleanOperationEnum::INTERSECT) + .apply(); + } + } + return dt; + } + + static double evolveRungeKutta3(AdvectType &kernel, double maxTimeStep) { + // 1. Determine the single time step 'dt' for all stages. + kernel.computeRates(maxTimeStep); + const double dt = kernel.getCurrentTimeStep(); + + // 2. Save u^n (Deep copy to preserve topology) + if (kernel.originalLevelSet == nullptr) { + kernel.originalLevelSet = + viennals::Domain::New(kernel.levelSets.back()->getGrid()); + } + kernel.originalLevelSet->deepCopy(kernel.levelSets.back()); + + // If dt is 0 or negative, no advection is possible or needed. + if (dt <= 0) + return 0.; + + // Stage 1: u^(1) = u^n + dt * L(u^n) + kernel.updateLevelSet(dt); + + // Stage 2: u^(2) = 3/4 u^n + 1/4 (u^(1) + dt * L(u^(1))) + kernel.computeRates(dt); + kernel.updateLevelSet(dt); + // Combine to get u^(2) = 0.75 * u^n + 0.25 * u*. + kernel.combineLevelSets(0.75, 0.25); + + // Stage 3: u^(n+1) = 1/3 u^n + 2/3 (u^(2) + dt * L(u^(2))) + kernel.computeRates(dt); + kernel.updateLevelSet(dt); + // Combine to get u^(n+1) = 1/3 * u^n + 2/3 * u**. + kernel.combineLevelSets(1.0 / 3.0, 2.0 / 3.0); + + // Finalize: Re-segment and renormalize the final result. + kernel.rebuildLS(); + + // Adjust lower layers + if (kernel.spatialScheme != + viennals::SpatialSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { + for (unsigned i = 0; i < kernel.levelSets.size() - 1; ++i) { + viennals::BooleanOperation( + kernel.levelSets[i], kernel.levelSets.back(), + viennals::BooleanOperationEnum::INTERSECT) + .apply(); + } + } + + return dt; + } +}; + +} // namespace lsInternal diff --git a/python/pyWrap.cpp b/python/pyWrap.cpp index 511b1142..844c7fbe 100644 --- a/python/pyWrap.cpp +++ b/python/pyWrap.cpp @@ -109,6 +109,13 @@ PYBIND11_MODULE(VIENNALS_MODULE_NAME, module) { module.attr("IntegrationSchemeEnum") = module.attr("SpatialSchemeEnum"); // IntegrationSchemeEnum is deprecated + py::native_enum(module, "TemporalSchemeEnum", + "enum.IntEnum") + .value("FORWARD_EULER", TemporalSchemeEnum::FORWARD_EULER) + .value("RUNGE_KUTTA_2ND_ORDER", TemporalSchemeEnum::RUNGE_KUTTA_2ND_ORDER) + .value("RUNGE_KUTTA_3RD_ORDER", TemporalSchemeEnum::RUNGE_KUTTA_3RD_ORDER) + .finalize(); + py::native_enum(module, "BooleanOperationEnum", "enum.IntEnum") .value("INTERSECT", BooleanOperationEnum::INTERSECT) diff --git a/python/pyWrap.hpp b/python/pyWrap.hpp index fac9e723..bd56a936 100644 --- a/python/pyWrap.hpp +++ b/python/pyWrap.hpp @@ -4,8 +4,6 @@ // all header files which define API functions #include -#include -#include #include #include #include @@ -223,6 +221,8 @@ template void bindApi(py::module &module) { "Get whether normal vectors are computed during advection.") .def("setSpatialScheme", &Advect::setSpatialScheme, "Set the spatial discretization scheme to use during advection.") + .def("setTemporalScheme", &Advect::setTemporalScheme, + "Set the time integration scheme to use during advection.") .def("setIntegrationScheme", &Advect::setIntegrationScheme, "(DEPRECATED, use setSpatialScheme instead) Set the spatial " "discretization scheme to use during advection.") @@ -238,27 +238,6 @@ template void bindApi(py::module &module) { .def("apply", &Advect::apply, py::call_guard(), "Perform advection."); - // AdvectForwardEuler - py::class_, Advect, - SmartPointer>>(module, - "AdvectForwardEuler") - .def(py::init(&SmartPointer>::template New<>)) - .def(py::init(&SmartPointer>::template New< - SmartPointer> &>)) - .def(py::init( - &SmartPointer>::template New< - SmartPointer> &, SmartPointer> &>)); - - // AdvectRungeKutta3 - py::class_, Advect, - SmartPointer>>(module, "AdvectRungeKutta3") - .def(py::init(&SmartPointer>::template New<>)) - .def(py::init(&SmartPointer>::template New< - SmartPointer> &>)) - .def(py::init( - &SmartPointer>::template New< - SmartPointer> &, SmartPointer> &>)); - py::class_>( module, "StencilLocalLaxFriedrichsScalar") .def_static( diff --git a/tests/Advection/Advection.cpp b/tests/Advection/Advection.cpp index 4e0d4a2c..876fdae3 100644 --- a/tests/Advection/Advection.cpp +++ b/tests/Advection/Advection.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include #include #include #include @@ -90,7 +90,7 @@ int main() { sphere1->getPointData().insertNextScalarData(pointIDs, "originalIDs"); } - ls::AdvectForwardEuler advectionKernel; + ls::Advect advectionKernel; advectionKernel.insertNextLevelSet(sphere1); advectionKernel.setVelocityField(velocities); advectionKernel.setSpatialScheme(scheme); diff --git a/tests/Advection/StencilLaxFriedrichsTest.cpp b/tests/Advection/StencilLaxFriedrichsTest.cpp index 0cfef606..24429b1b 100644 --- a/tests/Advection/StencilLaxFriedrichsTest.cpp +++ b/tests/Advection/StencilLaxFriedrichsTest.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include #include #include #include @@ -56,7 +56,7 @@ int main() { auto velocityField = ls::SmartPointer>::New(); // Setup Advection - ls::AdvectForwardEuler advectionKernel; + ls::Advect advectionKernel; advectionKernel.insertNextLevelSet(sphere); advectionKernel.setVelocityField(velocityField); advectionKernel.setAdvectionTime(0.5); diff --git a/tests/Advection/TimeIntegrationComparison.cpp b/tests/Advection/TimeIntegrationComparison.cpp index f1f2d018..60f713d9 100644 --- a/tests/Advection/TimeIntegrationComparison.cpp +++ b/tests/Advection/TimeIntegrationComparison.cpp @@ -1,8 +1,7 @@ #include #include -#include -#include +#include #include #include #include @@ -52,8 +51,9 @@ int main() { T radius = 1.5; ls::MakeGeometry(sphere, ls::Sphere::New(origin, radius)).apply(); - // Create copies for Forward Euler and RK3 + // Create copies for Forward Euler, RK2 and RK3 auto sphereFE = ls::SmartPointer>::New(sphere); + auto sphereRK2 = ls::SmartPointer>::New(sphere); auto sphereRK3 = ls::SmartPointer>::New(sphere); // Define constant velocity field (moving in x-direction) @@ -61,18 +61,28 @@ int main() { auto velocityField = ls::SmartPointer>::New(vel); // Setup Advection: Forward Euler - ls::AdvectForwardEuler advectFE; + ls::Advect advectFE; advectFE.insertNextLevelSet(sphereFE); advectFE.setVelocityField(velocityField); advectFE.setAdvectionTime(2.0); advectFE.setSpatialScheme(ls::SpatialSchemeEnum::ENGQUIST_OSHER_1ST_ORDER); + advectFE.setTemporalScheme(ls::TemporalSchemeEnum::FORWARD_EULER); + + // Setup Advection: Runge-Kutta 2 + ls::Advect advectRK2; + advectRK2.insertNextLevelSet(sphereRK2); + advectRK2.setVelocityField(velocityField); + advectRK2.setAdvectionTime(2.0); + advectRK2.setSpatialScheme(ls::SpatialSchemeEnum::ENGQUIST_OSHER_1ST_ORDER); + advectRK2.setTemporalScheme(ls::TemporalSchemeEnum::RUNGE_KUTTA_2ND_ORDER); // Setup Advection: Runge-Kutta 3 - ls::AdvectRungeKutta3 advectRK3; + ls::Advect advectRK3; advectRK3.insertNextLevelSet(sphereRK3); advectRK3.setVelocityField(velocityField); advectRK3.setAdvectionTime(2.0); advectRK3.setSpatialScheme(ls::SpatialSchemeEnum::ENGQUIST_OSHER_1ST_ORDER); + advectRK3.setTemporalScheme(ls::TemporalSchemeEnum::RUNGE_KUTTA_3RD_ORDER); // Run Advection std::cout << "Running Forward Euler Advection..." << std::endl; @@ -83,6 +93,14 @@ int main() { ls::ToSurfaceMesh(sphereFE, meshFE).apply(); ls::VTKWriter(meshFE, "sphereFE.vtp").apply(); + std::cout << "Running Runge-Kutta 2 Advection..." << std::endl; + advectRK2.apply(); + LSTEST_ASSERT_VALID_LS(sphereRK2, T, D); + + auto meshRK2 = ls::Mesh::New(); + ls::ToSurfaceMesh(sphereRK2, meshRK2).apply(); + ls::VTKWriter(meshRK2, "sphereRK2.vtp").apply(); + std::cout << "Running Runge-Kutta 3 Advection..." << std::endl; advectRK3.apply(); LSTEST_ASSERT_VALID_LS(sphereRK3, T, D); From 0be959b89649b60ff636f5538ef213c26bf8c2a4 Mon Sep 17 00:00:00 2001 From: filipovic Date: Tue, 30 Dec 2025 08:35:45 +0100 Subject: [PATCH 31/57] Allow for calculating intermediate velocities during higher order time integration --- include/viennals/lsAdvect.hpp | 10 +++++++++ include/viennals/lsAdvectTimeIntegration.hpp | 22 ++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index 3b03bdf1..d830a83d 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -109,6 +110,8 @@ template class Advect { unsigned adaptiveTimeStepSubdivisions = 20; static constexpr double wrappingLayerEpsilon = 1e-4; SmartPointer> originalLevelSet = nullptr; + std::function>)> velocityUpdateCallback = + nullptr; // this vector will hold the maximum time step for each point and the // corresponding velocity @@ -969,6 +972,13 @@ template class Advect { /// be translated to the advected LS. Defaults to true. void setUpdatePointData(bool update) { updatePointData = update; } + /// Set a callback function that is called after the level set has been + /// updated during intermediate time integration steps (e.g. RK2, RK3). + void setVelocityUpdateCallback( + std::function>)> callback) { + velocityUpdateCallback = callback; + } + // Prepare the levelset for advection, based on the provided spatial // discretization scheme. void prepareLS() { diff --git a/include/viennals/lsAdvectTimeIntegration.hpp b/include/viennals/lsAdvectTimeIntegration.hpp index 9079b903..2cc087d1 100644 --- a/include/viennals/lsAdvectTimeIntegration.hpp +++ b/include/viennals/lsAdvectTimeIntegration.hpp @@ -2,6 +2,7 @@ #include #include +#include namespace lsInternal { @@ -48,6 +49,13 @@ template struct AdvectTimeIntegration { // Stage 1: u^(1) = u^n + dt * L(u^n) kernel.updateLevelSet(dt); + if (kernel.velocityUpdateCallback) { + if (!kernel.velocityUpdateCallback(kernel.levelSets.back())) { + VIENNACORE_LOG_WARNING( + "Velocity update callback returned false in RK2 stage 1."); + } + } + // Stage 2: u^(n+1) = 1/2 u^n + 1/2 (u^(1) + dt * L(u^(1))) // Current level set is u^(1). Compute L(u^(1)). kernel.computeRates(dt); @@ -91,12 +99,26 @@ template struct AdvectTimeIntegration { // Stage 1: u^(1) = u^n + dt * L(u^n) kernel.updateLevelSet(dt); + if (kernel.velocityUpdateCallback) { + if (!kernel.velocityUpdateCallback(kernel.levelSets.back())) { + VIENNACORE_LOG_WARNING( + "Velocity update callback returned false in RK3 stage 1."); + } + } + // Stage 2: u^(2) = 3/4 u^n + 1/4 (u^(1) + dt * L(u^(1))) kernel.computeRates(dt); kernel.updateLevelSet(dt); // Combine to get u^(2) = 0.75 * u^n + 0.25 * u*. kernel.combineLevelSets(0.75, 0.25); + if (kernel.velocityUpdateCallback) { + if (!kernel.velocityUpdateCallback(kernel.levelSets.back())) { + VIENNACORE_LOG_WARNING( + "Velocity update callback returned false in RK3 stage 2."); + } + } + // Stage 3: u^(n+1) = 1/3 u^n + 2/3 (u^(2) + dt * L(u^(2))) kernel.computeRates(dt); kernel.updateLevelSet(dt); From 10c07f5a00397e91becf4c4c3a6f91be0e3a1011 Mon Sep 17 00:00:00 2001 From: filipovic Date: Tue, 30 Dec 2025 21:55:00 +0100 Subject: [PATCH 32/57] Added python bindings to the velocity calculation callback function --- python/pyWrap.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python/pyWrap.hpp b/python/pyWrap.hpp index bd56a936..a83e6b58 100644 --- a/python/pyWrap.hpp +++ b/python/pyWrap.hpp @@ -1,6 +1,7 @@ #include #include #include +#include // all header files which define API functions #include @@ -232,6 +233,9 @@ template void bindApi(py::module &module) { .def("setUpdatePointData", &Advect::setUpdatePointData, "Set whether the point data in the old LS should be translated to " "the advected LS. Defaults to true.") + .def("setVelocityUpdateCallback", &Advect::setVelocityUpdateCallback, + "Set a callback function that is called after the level set has been " + "updated during intermediate time integration steps (e.g. RK2, RK3).") .def("prepareLS", &Advect::prepareLS, "Prepare the level-set.") // need scoped release since we are calling a python method from // parallelised C++ code here From 307e141b7f743ffea9fd9b62628f1eb59fc4c75b Mon Sep 17 00:00:00 2001 From: filipovic Date: Thu, 8 Jan 2026 15:23:09 +0100 Subject: [PATCH 33/57] Small fixes in FromMesh and MarkVoidPoints, add velocityUpdateCallback to support velocity calculation during stages in RK integration schemes. --- examples/AirGapDeposition/AirGapDeposition.cpp | 2 +- include/viennals/lsAdvect.hpp | 7 +------ include/viennals/lsAdvectIntegrationSchemes.hpp | 15 +++++++++++++++ include/viennals/lsFromMesh.hpp | 5 ++++- include/viennals/lsMarkVoidPoints.hpp | 2 +- python/pyWrap.cpp | 2 ++ python/pyWrap.hpp | 2 +- python/tests/run_all_tests.py | 3 ++- python/viennals/__init__.pyi | 2 -- 9 files changed, 27 insertions(+), 13 deletions(-) diff --git a/examples/AirGapDeposition/AirGapDeposition.cpp b/examples/AirGapDeposition/AirGapDeposition.cpp index aa49d222..59448c29 100644 --- a/examples/AirGapDeposition/AirGapDeposition.cpp +++ b/examples/AirGapDeposition/AirGapDeposition.cpp @@ -86,7 +86,7 @@ double runSimulation(AdvectKernelType &kernel, int main() { constexpr int D = 2; - omp_set_num_threads(8); + omp_set_num_threads(16); NumericType extent = 30; NumericType gridDelta = 0.5; diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index 9fc439c8..33309638 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -38,11 +38,6 @@ #include #endif -// // Forward declaration for Time Integration Schemes -// namespace lsInternal { -// template struct AdvectTimeIntegration; -// } - namespace viennals { using namespace viennacore; @@ -1043,4 +1038,4 @@ template class Advect { // add all template specializations for this class PRECOMPILE_PRECISION_DIMENSION(Advect) -} // namespace viennals \ No newline at end of file +} // namespace viennals diff --git a/include/viennals/lsAdvectIntegrationSchemes.hpp b/include/viennals/lsAdvectIntegrationSchemes.hpp index beb80d41..e61ed209 100644 --- a/include/viennals/lsAdvectIntegrationSchemes.hpp +++ b/include/viennals/lsAdvectIntegrationSchemes.hpp @@ -74,6 +74,9 @@ template struct AdvectTimeIntegration { // Stage 1: u^(1) = u^n + dt * L(u^n) kernel.updateLevelSet(dt); + if (kernel.velocityUpdateCallback) + kernel.velocityUpdateCallback(kernel.levelSets.back()); + // Stage 2: u^(n+1) = 1/2 u^n + 1/2 (u^(1) + dt * L(u^(1))) // Current level set is u^(1). Compute L(u^(1)). kernel.computeRates(dt); @@ -82,6 +85,9 @@ template struct AdvectTimeIntegration { // Combine: u^(n+1) = 0.5 * u^n + 0.5 * u* kernel.combineLevelSets(0.5, 0.5); + if (kernel.velocityUpdateCallback) + kernel.velocityUpdateCallback(kernel.levelSets.back()); + // Finalize kernel.rebuildLS(); @@ -109,18 +115,27 @@ template struct AdvectTimeIntegration { // Stage 1: u^(1) = u^n + dt * L(u^n) kernel.updateLevelSet(dt); + if (kernel.velocityUpdateCallback) + kernel.velocityUpdateCallback(kernel.levelSets.back()); + // Stage 2: u^(2) = 3/4 u^n + 1/4 (u^(1) + dt * L(u^(1))) kernel.computeRates(dt); kernel.updateLevelSet(dt); // Combine to get u^(2) = 0.75 * u^n + 0.25 * u*. kernel.combineLevelSets(0.75, 0.25); + if (kernel.velocityUpdateCallback) + kernel.velocityUpdateCallback(kernel.levelSets.back()); + // Stage 3: u^(n+1) = 1/3 u^n + 2/3 (u^(2) + dt * L(u^(2))) kernel.computeRates(dt); kernel.updateLevelSet(dt); // Combine to get u^(n+1) = 1/3 * u^n + 2/3 * u**. kernel.combineLevelSets(1.0 / 3.0, 2.0 / 3.0); + if (kernel.velocityUpdateCallback) + kernel.velocityUpdateCallback(kernel.levelSets.back()); + // Finalize: Re-segment and renormalize the final result. kernel.rebuildLS(); diff --git a/include/viennals/lsFromMesh.hpp b/include/viennals/lsFromMesh.hpp index 2d57cdac..fcbfeddf 100644 --- a/include/viennals/lsFromMesh.hpp +++ b/include/viennals/lsFromMesh.hpp @@ -53,7 +53,10 @@ template class FromMesh { auto &domain = levelSet->getDomain(); auto &nodes = mesh->getNodes(); - auto values = mesh->cellData.getScalarData("LSValues", true); + auto values = mesh->getPointData().getScalarData("LSValues", true); + if (values == nullptr) { + values = mesh->getCellData().getScalarData("LSValues", true); + } if (values == nullptr) { Logger::getInstance() diff --git a/include/viennals/lsMarkVoidPoints.hpp b/include/viennals/lsMarkVoidPoints.hpp index d2a2484d..92e1753e 100644 --- a/include/viennals/lsMarkVoidPoints.hpp +++ b/include/viennals/lsMarkVoidPoints.hpp @@ -317,7 +317,7 @@ template class MarkVoidPoints { auto maxComponent = *std::max_element(componentMarkers.begin(), componentMarkers.end()); assert(maxComponent >= 0); - numComponents = maxComponent; + numComponents = maxComponent + 1; auto componentMarkersPointer = pointData.getScalarData("ConnectedComponentId", true); diff --git a/python/pyWrap.cpp b/python/pyWrap.cpp index 844c7fbe..f231b5c7 100644 --- a/python/pyWrap.cpp +++ b/python/pyWrap.cpp @@ -231,9 +231,11 @@ PYBIND11_MODULE(VIENNALS_MODULE_NAME, module) { "Get a list of hexahedrons of the mesh.") .def("getPointData", (PointData & (Mesh::*)()) & Mesh::getPointData, + py::return_value_policy::reference_internal, "Return a reference to the point data of the mesh.") .def("getCellData", (PointData & (Mesh::*)()) & Mesh::getCellData, + py::return_value_policy::reference_internal, "Return a reference to the cell data of the mesh.") .def("insertNextNode", &Mesh::insertNextNode, "Insert a node in the mesh.") diff --git a/python/pyWrap.hpp b/python/pyWrap.hpp index a83e6b58..9020abfe 100644 --- a/python/pyWrap.hpp +++ b/python/pyWrap.hpp @@ -652,7 +652,7 @@ template void bindApi(py::module &module) { .def("setVoidTopSurface", &MarkVoidPoints::setVoidTopSurface, "Set the logic by which to choose the surface which is non-void. " "All other connected surfaces will then be marked as void points.") - .def("setSaveComponentsId", &MarkVoidPoints::setSaveComponentIds, + .def("setSaveComponentIds", &MarkVoidPoints::setSaveComponentIds, "Save the connectivity information of all LS points in the " "pointData of the level set.") .def("getNumberOfComponents", diff --git a/python/tests/run_all_tests.py b/python/tests/run_all_tests.py index 7a4674d5..51510322 100644 --- a/python/tests/run_all_tests.py +++ b/python/tests/run_all_tests.py @@ -329,7 +329,7 @@ def test_from_mesh_volume(self): # FromMesh requires "LSValues" in cellData corresponding to nodes ls_values = [-1.0, 1.0, 1.0, 1.0] - mesh.getCellData().insertNextScalarData(ls_values, "LSValues") + mesh.getPointData().insertNextScalarData(ls_values, "LSValues") dom = d3.Domain(0.2) # FromMesh should handle volume meshes if implemented in the wrapper logic @@ -581,6 +581,7 @@ def test_mark_void_points_advanced(self): print(" > MarkVoidPoints") marker = d3.MarkVoidPoints(dom) + marker.setSaveComponentIds(True) marker.apply() # Should find 3 connected components (2 spheres + 1 background) diff --git a/python/viennals/__init__.pyi b/python/viennals/__init__.pyi index a3c3b84f..79b08550 100644 --- a/python/viennals/__init__.pyi +++ b/python/viennals/__init__.pyi @@ -34,8 +34,6 @@ from viennals._core import VelocityField from viennals._core import VoidTopSurfaceEnum from viennals._core import setNumThreads from viennals.d2 import Advect -from viennals.d2 import AdvectForwardEuler -from viennals.d2 import AdvectRungeKutta3 from viennals.d2 import BooleanOperation from viennals.d2 import Box from viennals.d2 import BoxDistribution From f597f6923ecedf20a8cb6b0a14855ee6f78d2b8f Mon Sep 17 00:00:00 2001 From: filipovic Date: Thu, 8 Jan 2026 16:50:43 +0100 Subject: [PATCH 34/57] format --- python/pyWrap.hpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/python/pyWrap.hpp b/python/pyWrap.hpp index 9020abfe..aa617a36 100644 --- a/python/pyWrap.hpp +++ b/python/pyWrap.hpp @@ -1,7 +1,7 @@ +#include #include #include #include -#include // all header files which define API functions #include @@ -233,9 +233,10 @@ template void bindApi(py::module &module) { .def("setUpdatePointData", &Advect::setUpdatePointData, "Set whether the point data in the old LS should be translated to " "the advected LS. Defaults to true.") - .def("setVelocityUpdateCallback", &Advect::setVelocityUpdateCallback, - "Set a callback function that is called after the level set has been " - "updated during intermediate time integration steps (e.g. RK2, RK3).") + .def( + "setVelocityUpdateCallback", &Advect::setVelocityUpdateCallback, + "Set a callback function that is called after the level set has been " + "updated during intermediate time integration steps (e.g. RK2, RK3).") .def("prepareLS", &Advect::prepareLS, "Prepare the level-set.") // need scoped release since we are calling a python method from // parallelised C++ code here From 10685ff82dd8519232c75ad22e9770ad5f5442d5 Mon Sep 17 00:00:00 2001 From: filipovic Date: Thu, 8 Jan 2026 20:40:55 +0100 Subject: [PATCH 35/57] remove last velocity calculation when in single step mode --- include/viennals/lsAdvectIntegrationSchemes.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/include/viennals/lsAdvectIntegrationSchemes.hpp b/include/viennals/lsAdvectIntegrationSchemes.hpp index e61ed209..691c3ba5 100644 --- a/include/viennals/lsAdvectIntegrationSchemes.hpp +++ b/include/viennals/lsAdvectIntegrationSchemes.hpp @@ -85,7 +85,9 @@ template struct AdvectTimeIntegration { // Combine: u^(n+1) = 0.5 * u^n + 0.5 * u* kernel.combineLevelSets(0.5, 0.5); - if (kernel.velocityUpdateCallback) + // If we are in single step mode, the level set will be rebuilt immediately + // after this, invalidating the velocity field. Thus, we skip the update. + if (kernel.velocityUpdateCallback && !kernel.performOnlySingleStep) kernel.velocityUpdateCallback(kernel.levelSets.back()); // Finalize @@ -133,7 +135,9 @@ template struct AdvectTimeIntegration { // Combine to get u^(n+1) = 1/3 * u^n + 2/3 * u**. kernel.combineLevelSets(1.0 / 3.0, 2.0 / 3.0); - if (kernel.velocityUpdateCallback) + // If we are in single step mode, the level set will be rebuilt immediately + // after this, invalidating the velocity field. Thus, we skip the update. + if (kernel.velocityUpdateCallback && !kernel.performOnlySingleStep) kernel.velocityUpdateCallback(kernel.levelSets.back()); // Finalize: Re-segment and renormalize the final result. From 0b687a92b9787b601db45fd2dd5aaec1149e1152 Mon Sep 17 00:00:00 2001 From: filipovic Date: Thu, 8 Jan 2026 23:34:12 +0100 Subject: [PATCH 36/57] fixed failing PatternedSubstrate and VolumeToLevelSets examples, added 3D functionality to CompareSparseField and fixed ConvexHull generation for 3D. --- CMakeLists.txt | 1 + examples/Epitaxy/Epitaxy.cpp | 1 + .../VolumeToLevelSets/VolumeToLevelSets.cpp | 38 +++++++++-- include/viennals/lsCompareSparseField.hpp | 15 ++-- include/viennals/lsConvexHull.hpp | 49 ++++++++++++- .../CompareSparseField/CompareSparseField.cpp | 68 +++++++++++++------ 6 files changed, 135 insertions(+), 37 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b61b8e7f..e79b0695 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -126,6 +126,7 @@ CPMAddPackage( CPMFindPackage( NAME ViennaHRLE VERSION 0.7.0 + GIT_TAG minor-fixes GIT_REPOSITORY "https://github.com/ViennaTools/ViennaHRLE" EXCLUDE_FROM_ALL ${VIENNALS_BUILD_PYTHON}) diff --git a/examples/Epitaxy/Epitaxy.cpp b/examples/Epitaxy/Epitaxy.cpp index d744c25c..9cab0203 100644 --- a/examples/Epitaxy/Epitaxy.cpp +++ b/examples/Epitaxy/Epitaxy.cpp @@ -47,6 +47,7 @@ void writeSurface(SmartPointer> domain, int main(int argc, char *argv[]) { + omp_set_num_threads(8); // Create hole geometry double bounds[2 * D] = {-1.0, 1.0, -1.0, 1.0, -1.0, 1.0}; BoundaryConditionEnum boundaryConditions[2 * D] = { diff --git a/examples/VolumeToLevelSets/VolumeToLevelSets.cpp b/examples/VolumeToLevelSets/VolumeToLevelSets.cpp index fbabab4c..2d863e0f 100644 --- a/examples/VolumeToLevelSets/VolumeToLevelSets.cpp +++ b/examples/VolumeToLevelSets/VolumeToLevelSets.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -12,13 +13,30 @@ int main(int argc, char *argv[]) { using NumericType = double; constexpr int D = 3; - double gridDelta = 0.00023; + double gridDelta = 0.1; std::string fileName; if (argc > 1) { fileName = std::string(argv[1]); } else { fileName = "volumeInitial.vtu"; + // Generate a simple volume mesh if no file is provided + auto mesh = ls::SmartPointer>::New(); + // Create a simple cube [-1, 1]^3 consisting of 5 tetrahedra + mesh->nodes = {{-1., -1., -1.}, {1., -1., -1.}, {1., 1., -1.}, + {-1., 1., -1.}, {-1., -1., 1.}, {1., -1., 1.}, + {1., 1., 1.}, {-1., 1., 1.}}; + // 5-tetra decomposition of a cube + mesh->tetras = {{0, 1, 3, 4}, + {1, 2, 3, 6}, + {1, 4, 5, 6}, + {3, 4, 6, 7}, + {1, 3, 4, 6}}; + // Assign materials + std::vector materials = {0, 0, 1, 1, 1}; + mesh->cellData.insertNextScalarData(materials, "Material"); + + ls::VTKWriter(mesh, ls::FileFormatEnum::VTU, fileName).apply(); } auto mesh = ls::SmartPointer>::New(); @@ -32,7 +50,10 @@ int main(int argc, char *argv[]) { std::vector translator = {3, 2, 4, 7, 7, 6, 5, 7, 1, 0}; if (materialData != nullptr) { for (auto &cell : *materialData) { - cell = translator[std::round(cell)]; + int id = std::round(cell); + if (id >= 0 && static_cast(id) < translator.size()) { + cell = translator[id]; + } } } } @@ -41,12 +62,12 @@ int main(int argc, char *argv[]) { ls::VTKWriter(mesh, ls::FileFormatEnum::VTU, "ReadVolumeMesh.vtu").apply(); - double bounds[2 * D] = {-6, 6, 1e-10, 0.078, -0.034, 0.034}; + double bounds[2 * D] = {-2, 2, -2, 2, -2, 2}; ls::BoundaryConditionEnum boundaryCons[D]; for (unsigned i = 0; i < D; ++i) { boundaryCons[i] = ls::BoundaryConditionEnum::REFLECTIVE_BOUNDARY; } - boundaryCons[0] = ls::BoundaryConditionEnum::INFINITE_BOUNDARY; + // boundaryCons[0] = ls::BoundaryConditionEnum::INFINITE_BOUNDARY; auto domain = ls::SmartPointer>::New( bounds, boundaryCons, gridDelta); @@ -61,6 +82,15 @@ int main(int argc, char *argv[]) { ls::ToSurfaceMesh(levelSets[i], mesh).apply(); ls::VTKWriter(mesh, "LSsurface-" + std::to_string(i) + ".vtp") .apply(); + + std::cout << "---------------------------------" << std::endl; + std::cout << "LevelSet " << i << std::endl; + levelSets[i]->print(); + + auto meshVol = ls::SmartPointer>::New(); + ls::ToMesh(levelSets[i], meshVol).apply(); + ls::VTKWriter(meshVol, "LSvolume-" + std::to_string(i) + ".vtu") + .apply(); } return 0; diff --git a/include/viennals/lsCompareSparseField.hpp b/include/viennals/lsCompareSparseField.hpp index e4b70e9c..d1dd94cf 100644 --- a/include/viennals/lsCompareSparseField.hpp +++ b/include/viennals/lsCompareSparseField.hpp @@ -133,20 +133,12 @@ template class CompareSparseField { } public: - CompareSparseField() { - assert( - D == 2 && - "CompareSparseField is currently only implemented for 2D level sets."); - } + CompareSparseField() { } CompareSparseField(SmartPointer> passedLevelSetExpanded, SmartPointer> passedLevelSetIterated) : levelSetExpanded(passedLevelSetExpanded), - levelSetIterated(passedLevelSetIterated) { - assert( - D == 2 && - "CompareSparseField is currently only implemented for 2D level sets."); - } + levelSetIterated(passedLevelSetIterated) { } void setLevelSetExpanded(SmartPointer> passedLevelSet) { levelSetExpanded = passedLevelSet; @@ -278,7 +270,8 @@ template class CompareSparseField { // Calculate coordinates T xCoord = indices[0] * gridDelta; T yCoord = indices[1] * gridDelta; - T zCoord = 0.0; // Always use 0 for z-coordinate in 2D + T zCoord = (D == 3) ? indices[2] * gridDelta + : 0.0; // Always use 0 for z-coordinate in 2D // Skip if outside the specified x-range if (useXRange && (xCoord < xRangeMin || xCoord > xRangeMax)) { diff --git a/include/viennals/lsConvexHull.hpp b/include/viennals/lsConvexHull.hpp index 89450c60..fe583f12 100644 --- a/include/viennals/lsConvexHull.hpp +++ b/include/viennals/lsConvexHull.hpp @@ -42,6 +42,26 @@ template class ConvexHull { ++nextIndex; } + // Robust initialization: find a point that is not collinear + if constexpr (D == 3) { + unsigned searchIndex = nextIndex; + for (; searchIndex < points.size(); ++searchIndex) { + if (searchIndex == currentEdge[0] || searchIndex == currentEdge[1]) + continue; + + auto v1 = points[currentEdge[1]] - points[currentEdge[0]]; + auto v2 = points[searchIndex] - points[currentEdge[0]]; + auto cross = CrossProduct(v1, v2); + if (DotProduct(cross, cross) > 1e-10) { + nextIndex = searchIndex; + break; + } + } + } + + // Keep track of candidates we've already selected to prevent cycles + std::vector previousCandidates; + for (unsigned i = 0; i < points.size(); ++i) { if (i == currentEdge[0] || i == nextIndex) continue; @@ -67,6 +87,8 @@ template class ConvexHull { } auto product = DotProduct(distance, normal); + bool update = false; + // if dot product is very small, point is very close to plane // we need to check if we already have the correct point // or if it is the next correct one @@ -88,12 +110,37 @@ template class ConvexHull { edges[1][1] = i; if (!wasEdgeVisited(edges[0]) && !wasEdgeVisited(edges[1])) { - nextIndex = i; + // Tie-breaker: prefer closer points to avoid long chords + auto distI = DotProduct(distance, distance); + auto distNextVec = points[nextIndex] - points[currentEdge[0]]; + auto distNext = DotProduct(distNextVec, distNextVec); + if (distI < distNext) { + update = true; + } } } // check if point is to the right of current element else if (product > 0) { + update = true; + } + + if (update) { + // Check for cycles + bool cycleDetected = false; + for (unsigned prev : previousCandidates) { + if (prev == i) { + cycleDetected = true; + break; + } + } + + if (cycleDetected) { + continue; + } + + previousCandidates.push_back(nextIndex); nextIndex = i; + i = -1; } } return nextIndex; diff --git a/tests/CompareSparseField/CompareSparseField.cpp b/tests/CompareSparseField/CompareSparseField.cpp index 370d6c4b..f815b768 100644 --- a/tests/CompareSparseField/CompareSparseField.cpp +++ b/tests/CompareSparseField/CompareSparseField.cpp @@ -22,16 +22,17 @@ namespace ls = viennals; -int main() { - constexpr int D = 2; - - omp_set_num_threads(4); - +template void runTest() { double extent = 15; double gridDelta = 0.5; - double bounds[2 * D] = {-extent, extent, -extent, extent}; - ls::Domain::BoundaryType boundaryCons[D]; + double bounds[2 * D]; + for (unsigned i = 0; i < D; ++i) { + bounds[2 * i] = -extent; + bounds[2 * i + 1] = extent; + } + + typename ls::Domain::BoundaryType boundaryCons[D]; for (unsigned i = 0; i < D; ++i) boundaryCons[i] = ls::Domain::BoundaryType::REFLECTIVE_BOUNDARY; @@ -39,7 +40,9 @@ int main() { auto sphere1 = ls::SmartPointer>::New( bounds, boundaryCons, gridDelta); - double origin1[D] = {0., 0.}; + double origin1[D]; + for (int i = 0; i < D; ++i) + origin1[i] = 0.; double radius1 = 5.0; ls::MakeGeometry( @@ -50,7 +53,12 @@ int main() { auto sphere2 = ls::SmartPointer>::New( bounds, boundaryCons, gridDelta); - double origin2[D] = {2., 1.}; // Shifted center + double origin2[D]; + for (int i = 0; i < D; ++i) + origin2[i] = 0.; + origin2[0] = 2.; + if (D > 1) + origin2[1] = 1.; // double origin2[D] = {0., 0.}; // Same center double radius2 = 5.0; // Same radius @@ -65,23 +73,30 @@ int main() { // Reduce the sample level set to a sparse field ls::Reduce(sphere2, 1).apply(); + std::string dimString = std::to_string(D) + "D"; + // Export both spheres as VTK files for visualization { auto mesh = ls::SmartPointer>::New(); ls::ToMesh(sphere1, mesh).apply(); - ls::VTKWriter(mesh, "sphere1_expanded.vtp").apply(); + ls::VTKWriter(mesh, "sphere1_expanded_" + dimString + ".vtp") + .apply(); auto meshSurface = ls::SmartPointer>::New(); ls::ToSurfaceMesh(sphere1, meshSurface).apply(); - ls::VTKWriter(meshSurface, "sphere1_surface.vtp").apply(); + ls::VTKWriter(meshSurface, "sphere1_surface_" + dimString + ".vtp") + .apply(); } { auto mesh = ls::SmartPointer>::New(); ls::ToMesh(sphere2, mesh).apply(); - ls::VTKWriter(mesh, "sphere2_sparse_iterated.vtp").apply(); + ls::VTKWriter(mesh, + "sphere2_sparse_iterated_" + dimString + ".vtp") + .apply(); auto meshSurface = ls::SmartPointer>::New(); ls::ToSurfaceMesh(sphere2, meshSurface).apply(); - ls::VTKWriter(meshSurface, "sphere2_surface.vtp").apply(); + ls::VTKWriter(meshSurface, "sphere2_surface_" + dimString + ".vtp") + .apply(); } // Compare using sparse field comparison @@ -102,11 +117,12 @@ int main() { auto meshWithPointData = ls::SmartPointer>::New(); // Mesh with point data ls::ToMesh(sphere2, meshWithPointData).apply(); - ls::VTKWriter(meshWithPointData, "sphere2_LS_with_point_data.vtp") + ls::VTKWriter(meshWithPointData, + "sphere2_LS_with_point_data_" + dimString + ".vtp") .apply(); // Save mesh to file - ls::VTKWriter(mesh, "sparsefield.vtp").apply(); + ls::VTKWriter(mesh, "sparsefield_" + dimString + ".vtp").apply(); // Get the calculated difference metrics double sumSquaredDifferences = compareSparseField.getSumSquaredDifferences(); @@ -117,11 +133,15 @@ int main() { unsigned numSkippedPoints = compareSparseField.getNumSkippedPoints(); // Number of skipped points - std::cout << "\nComparison Results:" << std::endl; - std::cout << "Sphere 1 center: (" << origin1[0] << ", " << origin1[1] << ")" - << std::endl; - std::cout << "Sphere 2 center: (" << origin2[0] << ", " << origin2[1] << ")" - << std::endl; + std::cout << "\nComparison Results (" << dimString << "):" << std::endl; + std::cout << "Sphere 1 center: ("; + for (int i = 0; i < D; ++i) + std::cout << origin1[i] << ((i == D - 1) ? "" : ", "); + std::cout << ")" << std::endl; + std::cout << "Sphere 2 center: ("; + for (int i = 0; i < D; ++i) + std::cout << origin2[i] << ((i == D - 1) ? "" : ", "); + std::cout << ")" << std::endl; std::cout << "Sphere 1 level set width after expansion: " << sphere1->getLevelSetWidth() << std::endl; std::cout << "Sum of squared differences: " << sumSquaredDifferences @@ -177,7 +197,8 @@ int main() { // Create a mesh output with squared differences compareSparseField.setOutputMesh(mesh); compareSparseField.apply(); - ls::VTKWriter(mesh, "sparsefield_restricted.vtp").apply(); + ls::VTKWriter(mesh, "sparsefield_restricted_" + dimString + ".vtp") + .apply(); // Test with different expansion widths std::cout << "\nTesting with different expansion widths:" << std::endl; @@ -248,6 +269,11 @@ int main() { // << std::endl; // std::cout << "Performance ratio: " // << narrowband_ms.count() / sparse_ms.count() << "x" << std::endl; +} +int main() { + omp_set_num_threads(8); + runTest<2>(); + runTest<3>(); return 0; } From 5a013891a00150d13a7b47ff0c6a0afe95c319fd Mon Sep 17 00:00:00 2001 From: filipovic Date: Thu, 8 Jan 2026 23:36:38 +0100 Subject: [PATCH 37/57] format --- examples/VolumeToLevelSets/VolumeToLevelSets.cpp | 7 ++----- include/viennals/lsCompareSparseField.hpp | 4 ++-- tests/CompareSparseField/CompareSparseField.cpp | 3 +-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/examples/VolumeToLevelSets/VolumeToLevelSets.cpp b/examples/VolumeToLevelSets/VolumeToLevelSets.cpp index 2d863e0f..68ef3236 100644 --- a/examples/VolumeToLevelSets/VolumeToLevelSets.cpp +++ b/examples/VolumeToLevelSets/VolumeToLevelSets.cpp @@ -27,11 +27,8 @@ int main(int argc, char *argv[]) { {-1., 1., -1.}, {-1., -1., 1.}, {1., -1., 1.}, {1., 1., 1.}, {-1., 1., 1.}}; // 5-tetra decomposition of a cube - mesh->tetras = {{0, 1, 3, 4}, - {1, 2, 3, 6}, - {1, 4, 5, 6}, - {3, 4, 6, 7}, - {1, 3, 4, 6}}; + mesh->tetras = { + {0, 1, 3, 4}, {1, 2, 3, 6}, {1, 4, 5, 6}, {3, 4, 6, 7}, {1, 3, 4, 6}}; // Assign materials std::vector materials = {0, 0, 1, 1, 1}; mesh->cellData.insertNextScalarData(materials, "Material"); diff --git a/include/viennals/lsCompareSparseField.hpp b/include/viennals/lsCompareSparseField.hpp index d1dd94cf..0b012476 100644 --- a/include/viennals/lsCompareSparseField.hpp +++ b/include/viennals/lsCompareSparseField.hpp @@ -133,12 +133,12 @@ template class CompareSparseField { } public: - CompareSparseField() { } + CompareSparseField() {} CompareSparseField(SmartPointer> passedLevelSetExpanded, SmartPointer> passedLevelSetIterated) : levelSetExpanded(passedLevelSetExpanded), - levelSetIterated(passedLevelSetIterated) { } + levelSetIterated(passedLevelSetIterated) {} void setLevelSetExpanded(SmartPointer> passedLevelSet) { levelSetExpanded = passedLevelSet; diff --git a/tests/CompareSparseField/CompareSparseField.cpp b/tests/CompareSparseField/CompareSparseField.cpp index f815b768..a43ddb43 100644 --- a/tests/CompareSparseField/CompareSparseField.cpp +++ b/tests/CompareSparseField/CompareSparseField.cpp @@ -90,8 +90,7 @@ template void runTest() { { auto mesh = ls::SmartPointer>::New(); ls::ToMesh(sphere2, mesh).apply(); - ls::VTKWriter(mesh, - "sphere2_sparse_iterated_" + dimString + ".vtp") + ls::VTKWriter(mesh, "sphere2_sparse_iterated_" + dimString + ".vtp") .apply(); auto meshSurface = ls::SmartPointer>::New(); ls::ToSurfaceMesh(sphere2, meshSurface).apply(); From fea7a19ad29d477a23f614d4498b739116a1412d Mon Sep 17 00:00:00 2001 From: filipovic Date: Thu, 8 Jan 2026 23:38:59 +0100 Subject: [PATCH 38/57] remove GIT_TAG for ViennaHRLE --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e79b0695..b61b8e7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -126,7 +126,6 @@ CPMAddPackage( CPMFindPackage( NAME ViennaHRLE VERSION 0.7.0 - GIT_TAG minor-fixes GIT_REPOSITORY "https://github.com/ViennaTools/ViennaHRLE" EXCLUDE_FROM_ALL ${VIENNALS_BUILD_PYTHON}) From e920b82bb61ccfc97f354dc456bc5dffa3e28231 Mon Sep 17 00:00:00 2001 From: filipovic Date: Fri, 9 Jan 2026 13:58:57 +0100 Subject: [PATCH 39/57] Added 3D options and tests for comparing domains --- include/viennals/lsCompareArea.hpp | 89 +++++---- include/viennals/lsCompareChamfer.hpp | 47 ++--- .../viennals/lsCompareCriticalDimensions.hpp | 170 ++++++++---------- include/viennals/lsCompareNarrowBand.hpp | 39 ++-- include/viennals/lsCompareSparseField.hpp | 25 ++- python/pyWrap.hpp | 114 ++++++------ tests/CompareArea/CompareArea.cpp | 122 ++++++++----- tests/CompareChamfer/CompareChamfer.cpp | 126 +++++++------ .../CompareCriticalDimensions.cpp | 93 +++++++--- tests/CompareNarrowBand/CompareNarrowBand.cpp | 57 ++++-- .../CompareSparseField/CompareSparseField.cpp | 28 ++- 11 files changed, 536 insertions(+), 374 deletions(-) diff --git a/include/viennals/lsCompareArea.hpp b/include/viennals/lsCompareArea.hpp index 7115c223..0eb44f1b 100644 --- a/include/viennals/lsCompareArea.hpp +++ b/include/viennals/lsCompareArea.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -13,15 +14,15 @@ namespace viennals { using namespace viennacore; -/// Computes an estimate of the area where two level sets differ. -/// The area is calculated by iterating through the bounding box of the two +/// Computes an estimate of the volume/area where two level sets differ. +/// The volume is calculated by iterating through the bounding box of the two /// level sets and comparing the cell values. The grid delta is used as the unit -/// of area. Custom increment values can be set for specific x and y ranges, +/// of volume. Custom increment values can be set for specific x, y and z ranges, /// allowing to count certain areas multiple times or skip them. Optionally, a -/// passed mesh can be filled with the area information, allowing for +/// passed mesh can be filled with the volume information, allowing for /// visualization of the differences. -/// The code is currently itended for 2D level sets only. -template class CompareArea { +/// The code is intended for 2D and 3D level sets. +template class CompareDomain { using hrleDomainType = typename Domain::DomainType; using hrleIndexType = viennahrle::IndexType; @@ -36,11 +37,15 @@ template class CompareArea { hrleIndexType xRangeMax = std::numeric_limits::max(); hrleIndexType yRangeMin = std::numeric_limits::lowest(); hrleIndexType yRangeMax = std::numeric_limits::max(); + hrleIndexType zRangeMin = std::numeric_limits::lowest(); + hrleIndexType zRangeMax = std::numeric_limits::max(); bool useCustomXIncrement = false; bool useCustomYIncrement = false; + bool useCustomZIncrement = false; unsigned short int customXIncrement = 0; unsigned short int customYIncrement = 0; + unsigned short int customZIncrement = 0; unsigned short int defaultIncrement = 1; double gridDelta = 0.0; @@ -51,7 +56,7 @@ template class CompareArea { bool checkAndCalculateBounds() { if (levelSetTarget == nullptr || levelSetSample == nullptr) { Logger::getInstance() - .addError("Missing level set in CompareArea.") + .addError("Missing level set in CompareDomain.") .print(); return false; } @@ -62,7 +67,7 @@ template class CompareArea { if (gridTarget.getGridDelta() != gridSample.getGridDelta()) { Logger::getInstance() - .addError("Grid delta mismatch in CompareArea. The grid deltas of " + .addError("Grid delta mismatch in CompareDomain. The grid deltas of " "the two level sets must be equal.") .print(); return false; @@ -103,17 +108,13 @@ template class CompareArea { } public: - CompareArea() { - assert(D == 2 && - "CompareArea is currently only implemented for 2D level sets."); + CompareDomain() { } - CompareArea(SmartPointer> passedLevelSetTarget, - SmartPointer> passedLevelSetSample) + CompareDomain(SmartPointer> passedLevelSetTarget, + SmartPointer> passedLevelSetSample) : levelSetTarget(passedLevelSetTarget), levelSetSample(passedLevelSetSample) { - assert(D == 2 && - "CompareArea is currently only implemented for 2D level sets."); } /// Sets the target level set. @@ -149,6 +150,15 @@ template class CompareArea { useCustomYIncrement = true; } + /// Sets the z-range and custom increment value + void setZRangeAndIncrement(hrleIndexType minZRange, hrleIndexType maxZRange, + unsigned short int Zincrement) { + zRangeMin = minZRange; + zRangeMax = maxZRange; + customZIncrement = Zincrement; + useCustomZIncrement = true; + } + /// Set the output mesh where difference areas will be stored for /// visualization. Each cell in the mesh will have a cell data: /// 0: Areas where both level sets are inside @@ -157,17 +167,23 @@ template class CompareArea { outputMesh = passedMesh; } - /// Returns the computed area mismatch. - double getAreaMismatch() const { - return static_cast(differentCellsCount) * gridDelta * gridDelta; + /// Returns the computed volume/area mismatch. + double getVolumeMismatch() const { + return static_cast(differentCellsCount) * std::pow(gridDelta, D); } - /// Returns the computed area mismatch, with custom increments applied. - double getCustomAreaMismatch() const { - return static_cast(customDifferentCellCount) * gridDelta * - gridDelta; + /// Alias for getVolumeMismatch for 2D compatibility + double getAreaMismatch() const { return getVolumeMismatch(); } + + /// Returns the computed volume/area mismatch, with custom increments applied. + double getCustomVolumeMismatch() const { + return static_cast(customDifferentCellCount) * + std::pow(gridDelta, D); } + /// Alias for getCustomVolumeMismatch for 2D compatibility + double getCustomAreaMismatch() const { return getCustomVolumeMismatch(); } + /// Returns the number of cells where the level sets differ. unsigned long int getCellCount() const { return differentCellsCount; } @@ -177,7 +193,7 @@ template class CompareArea { return customDifferentCellCount; } - /// Computes the area difference between the two level sets. + /// Computes the volume/area difference between the two level sets. void apply() { // Calculate the bounds for iteration if (!checkAndCalculateBounds()) { @@ -200,7 +216,7 @@ template class CompareArea { if (levelSetTarget->getLevelSetWidth() < minimumWidth) { workingTarget = SmartPointer>::New(levelSetTarget); Expand(workingTarget, minimumWidth).apply(); - VIENNACORE_LOG_INFO("CompareArea: Expanded target level set to width " + + VIENNACORE_LOG_INFO("CompareDomain: Expanded target level set to width " + std::to_string(minimumWidth) + " to avoid undefined values."); } @@ -208,7 +224,7 @@ template class CompareArea { if (levelSetSample->getLevelSetWidth() < minimumWidth) { workingSample = SmartPointer>::New(levelSetSample); Expand(workingSample, minimumWidth).apply(); - VIENNACORE_LOG_INFO("CompareArea: Expanded sample level set to width " + + VIENNACORE_LOG_INFO("CompareDomain: Expanded sample level set to width " + std::to_string(minimumWidth) + " to avoid undefined values."); } @@ -227,9 +243,11 @@ template class CompareArea { if (generateMesh) { // Save the extent of the resulting mesh outputMesh->clear(); - for (unsigned i = 0; i < D; ++i) { - outputMesh->minimumExtent[i] = std::numeric_limits::max(); - outputMesh->maximumExtent[i] = std::numeric_limits::lowest(); + for (unsigned i = 0; i < 3; ++i) { + outputMesh->minimumExtent[i] = + (i < D) ? std::numeric_limits::max() : 0.0; + outputMesh->maximumExtent[i] = + (i < D) ? std::numeric_limits::lowest() : 0.0; } } @@ -270,6 +288,9 @@ template class CompareArea { bool inYRange = useCustomYIncrement && (itTarget.getIndices()[1] * gridDelta >= yRangeMin && itTarget.getIndices()[1] * gridDelta <= yRangeMax); + bool inZRange = (D < 3) || (useCustomZIncrement && + (itTarget.getIndices()[2] * gridDelta >= zRangeMin && + itTarget.getIndices()[2] * gridDelta <= zRangeMax)); // Calculate increment to add based on ranges unsigned short int incrementToAdd = defaultIncrement; @@ -281,6 +302,10 @@ template class CompareArea { } else if (inYRange) { incrementToAdd = customYIncrement; } + + if (D == 3 && inZRange) { + incrementToAdd += customZIncrement; + } // If cells differ, update the counters if (isDifferent) { @@ -350,7 +375,7 @@ template class CompareArea { // Insert points into the mesh outputMesh->nodes.resize(pointIdMapping.size()); for (auto it = pointIdMapping.begin(); it != pointIdMapping.end(); ++it) { - Vec3D coords; + Vec3D coords = {0.0, 0.0, 0.0}; for (unsigned i = 0; i < D; ++i) { coords[i] = gridDelta * it->first[i]; @@ -374,7 +399,11 @@ template class CompareArea { } }; +// Aliases for backward compatibility and clarity +template using CompareArea = CompareDomain; +template using CompareVolume = CompareDomain; + // Precompile for common precision and dimensions -PRECOMPILE_PRECISION_DIMENSION(CompareArea) +PRECOMPILE_PRECISION_DIMENSION(CompareDomain) } // namespace viennals diff --git a/include/viennals/lsCompareChamfer.hpp b/include/viennals/lsCompareChamfer.hpp index 4c1d4b89..ca98f964 100644 --- a/include/viennals/lsCompareChamfer.hpp +++ b/include/viennals/lsCompareChamfer.hpp @@ -27,8 +27,8 @@ using namespace viennacore; /// - RMS Chamfer distance: root mean square of nearest-neighbor distances /// - Maximum distance: maximum nearest-neighbor distance across both directions /// -/// The code is currently intended for 2D level sets only, where surfaces are -/// represented as line segments. +/// The code works for 2D and 3D level sets. Surfaces are represented as line +/// segments in 2D and triangles in 3D. /// /// Both level sets must have a width of at least 2 to extract surfaces. If /// not, they will be automatically expanded. @@ -74,18 +74,12 @@ template class CompareChamfer { public: CompareChamfer() { - static_assert( - D == 2, - "CompareChamfer is currently only implemented for 2D level sets."); } CompareChamfer(SmartPointer> passedLevelSetTarget, SmartPointer> passedLevelSetSample) : levelSetTarget(passedLevelSetTarget), levelSetSample(passedLevelSetSample) { - static_assert( - D == 2, - "CompareChamfer is currently only implemented for 2D level sets."); } /// Set the target level set @@ -171,8 +165,7 @@ template class CompareChamfer { return; } - // Convert nodes to format suitable for KDTree (only using x, y - // coordinates) + // Convert nodes to format suitable for KDTree std::vector> targetPoints(numTargetPoints); std::vector> samplePoints(numSamplePoints); @@ -255,10 +248,11 @@ template class CompareChamfer { outputMeshTarget->clear(); outputMeshTarget->nodes = targetNodes; - // Create vertices for visualization - outputMeshTarget->vertices.reserve(numTargetPoints); - for (unsigned i = 0; i < numTargetPoints; ++i) { - outputMeshTarget->vertices.push_back({i}); + // Copy topology for visualization + if constexpr (D == 2) { + outputMeshTarget->lines = targetSurfaceMesh->lines; + } else { + outputMeshTarget->triangles = targetSurfaceMesh->triangles; } // Add distance data @@ -266,9 +260,13 @@ template class CompareChamfer { std::move(targetDistances), "DistanceToSample"); // Set mesh extent + for (unsigned d = 0; d < 3; ++d) { + outputMeshTarget->minimumExtent[d] = + (d < D) ? std::numeric_limits::max() : 0.0; + outputMeshTarget->maximumExtent[d] = + (d < D) ? std::numeric_limits::lowest() : 0.0; + } for (unsigned d = 0; d < D; ++d) { - outputMeshTarget->minimumExtent[d] = std::numeric_limits::max(); - outputMeshTarget->maximumExtent[d] = std::numeric_limits::lowest(); for (const auto &node : targetNodes) { outputMeshTarget->minimumExtent[d] = std::min(outputMeshTarget->minimumExtent[d], node[d]); @@ -282,10 +280,11 @@ template class CompareChamfer { outputMeshSample->clear(); outputMeshSample->nodes = sampleNodes; - // Create vertices for visualization - outputMeshSample->vertices.reserve(numSamplePoints); - for (unsigned i = 0; i < numSamplePoints; ++i) { - outputMeshSample->vertices.push_back({i}); + // Copy topology for visualization + if constexpr (D == 2) { + outputMeshSample->lines = sampleSurfaceMesh->lines; + } else { + outputMeshSample->triangles = sampleSurfaceMesh->triangles; } // Add distance data @@ -293,9 +292,13 @@ template class CompareChamfer { std::move(sampleDistances), "DistanceToTarget"); // Set mesh extent + for (unsigned d = 0; d < 3; ++d) { + outputMeshSample->minimumExtent[d] = + (d < D) ? std::numeric_limits::max() : 0.0; + outputMeshSample->maximumExtent[d] = + (d < D) ? std::numeric_limits::lowest() : 0.0; + } for (unsigned d = 0; d < D; ++d) { - outputMeshSample->minimumExtent[d] = std::numeric_limits::max(); - outputMeshSample->maximumExtent[d] = std::numeric_limits::lowest(); for (const auto &node : sampleNodes) { outputMeshSample->minimumExtent[d] = std::min(outputMeshSample->minimumExtent[d], node[d]); diff --git a/include/viennals/lsCompareCriticalDimensions.hpp b/include/viennals/lsCompareCriticalDimensions.hpp index fd851b7a..20dec521 100644 --- a/include/viennals/lsCompareCriticalDimensions.hpp +++ b/include/viennals/lsCompareCriticalDimensions.hpp @@ -27,8 +27,6 @@ using namespace viennacore; /// zero. Multiple ranges can be specified to compare different critical /// dimensions. /// -/// The code is currently intended for 2D level sets only. -/// /// Note for the future: lsToDiskMesh could be used instead of lsToSurfaceMesh, /// which is probably more efficient but slightly less accurate. @@ -40,9 +38,9 @@ template class CompareCriticalDimensions { // Structure to hold a range specification struct RangeSpec { - bool isXRange; // true if X range, false if Y range - T rangeMin; - T rangeMax; + int measureDimension; + std::array minBounds; + std::array maxBounds; bool findMaximum; // true for maximum, false for minimum }; @@ -50,9 +48,9 @@ template class CompareCriticalDimensions { // Structure to hold critical dimension results struct CriticalDimensionResult { - bool isXRange; - T rangeMin; - T rangeMax; + int measureDimension; + std::array minBounds; + std::array maxBounds; bool findMaximum; T positionTarget; // Critical dimension position in target LS T positionSample; // Critical dimension position in sample LS @@ -94,32 +92,27 @@ template class CompareCriticalDimensions { // Extract surface positions from mesh nodes within the specified range // Returns the position coordinates (Y if isXRange=true, X if isXRange=false) std::vector findSurfaceCrossings(SmartPointer> surfaceMesh, - bool isXRange, T scanMin, T scanMax, - T perpMin, T perpMax) { + const RangeSpec &spec) { std::vector crossings; // Iterate through all surface mesh nodes for (const auto &node : surfaceMesh->nodes) { - T xCoord = node[0]; - T yCoord = node[1]; - // Check if point is in our scan range - bool inRange = false; - if (isXRange) { - // X range specified - check if X is in scan range - inRange = (xCoord >= scanMin && xCoord <= scanMax); - } else { - // Y range specified - check if Y is in scan range - inRange = (yCoord >= scanMin && yCoord <= scanMax); + bool inRange = true; + for (int i = 0; i < D; ++i) { + if (i == spec.measureDimension) + continue; + if (node[i] < spec.minBounds[i] || node[i] > spec.maxBounds[i]) { + inRange = false; + break; + } } if (inRange) { - // Extract perpendicular coordinate - T perpCoord = isXRange ? yCoord : xCoord; - - // Check if perpendicular coordinate is in range - if (perpCoord >= perpMin && perpCoord <= perpMax) { - crossings.push_back(perpCoord); + T val = node[spec.measureDimension]; + if (val >= spec.minBounds[spec.measureDimension] && + val <= spec.maxBounds[spec.measureDimension]) { + crossings.push_back(val); } } } @@ -143,16 +136,12 @@ template class CompareCriticalDimensions { public: CompareCriticalDimensions() { - static_assert(D == 2 && "CompareCriticalDimensions is currently only " - "implemented for 2D level sets."); } CompareCriticalDimensions(SmartPointer> passedLevelSetTarget, SmartPointer> passedLevelSetSample) : levelSetTarget(passedLevelSetTarget), levelSetSample(passedLevelSetSample) { - static_assert(D == 2 && "CompareCriticalDimensions is currently only " - "implemented for 2D level sets."); } void setLevelSetTarget(SmartPointer> passedLevelSet) { @@ -163,24 +152,39 @@ template class CompareCriticalDimensions { levelSetSample = passedLevelSet; } - /// Add an X range to find maximum or minimum Y position - void addXRange(T minX, T maxX, bool findMaximum = true) { + /// Add a generic range specification + void addRange(int measureDimension, const std::array &minBounds, + const std::array &maxBounds, bool findMaximum = true) { RangeSpec spec; - spec.isXRange = true; - spec.rangeMin = minX; - spec.rangeMax = maxX; + spec.measureDimension = measureDimension; + spec.minBounds = minBounds; + spec.maxBounds = maxBounds; spec.findMaximum = findMaximum; rangeSpecs.push_back(spec); } + /// Add an X range to find maximum or minimum Y position + void addXRange(T minX, T maxX, bool findMaximum = true) { + if constexpr (D == 2) { + std::array minBounds = {minX, std::numeric_limits::lowest()}; + std::array maxBounds = {maxX, std::numeric_limits::max()}; + addRange(1, minBounds, maxBounds, findMaximum); + } else { + VIENNACORE_LOG_WARNING( + "addXRange is only supported for 2D. Use addRange for 3D."); + } + } + /// Add a Y range to find maximum or minimum X position void addYRange(T minY, T maxY, bool findMaximum = true) { - RangeSpec spec; - spec.isXRange = false; - spec.rangeMin = minY; - spec.rangeMax = maxY; - spec.findMaximum = findMaximum; - rangeSpecs.push_back(spec); + if constexpr (D == 2) { + std::array minBounds = {std::numeric_limits::lowest(), minY}; + std::array maxBounds = {std::numeric_limits::max(), maxY}; + addRange(0, minBounds, maxBounds, findMaximum); + } else { + VIENNACORE_LOG_WARNING( + "addYRange is only supported for 2D. Use addRange for 3D."); + } } /// Clear all range specifications @@ -209,27 +213,6 @@ template class CompareCriticalDimensions { ToSurfaceMesh(levelSetTarget, surfaceMeshRef).apply(); ToSurfaceMesh(levelSetSample, surfaceMeshCmp).apply(); - // Get actual mesh extents instead of grid bounds - // This ensures we don't filter out surface points that extend beyond grid - // bounds - T xMin = std::numeric_limits::max(); - T xMax = std::numeric_limits::lowest(); - T yMin = std::numeric_limits::max(); - T yMax = std::numeric_limits::lowest(); - - for (const auto &node : surfaceMeshRef->nodes) { - xMin = std::min(xMin, node[0]); - xMax = std::max(xMax, node[0]); - yMin = std::min(yMin, node[1]); - yMax = std::max(yMax, node[1]); - } - for (const auto &node : surfaceMeshCmp->nodes) { - xMin = std::min(xMin, node[0]); - xMax = std::max(xMax, node[0]); - yMin = std::min(yMin, node[1]); - yMax = std::max(yMax, node[1]); - } - // Pre-allocate results vector to avoid race conditions results.resize(rangeSpecs.size()); @@ -238,33 +221,15 @@ template class CompareCriticalDimensions { for (size_t specIdx = 0; specIdx < rangeSpecs.size(); ++specIdx) { const auto &spec = rangeSpecs[specIdx]; CriticalDimensionResult result; - result.isXRange = spec.isXRange; - result.rangeMin = spec.rangeMin; - result.rangeMax = spec.rangeMax; + result.measureDimension = spec.measureDimension; + result.minBounds = spec.minBounds; + result.maxBounds = spec.maxBounds; result.findMaximum = spec.findMaximum; result.valid = false; - // Determine scan and perpendicular ranges - T scanMin, scanMax, perpMin, perpMax; - if (spec.isXRange) { - // X range specified - scan along X, find Y positions - scanMin = spec.rangeMin; - scanMax = spec.rangeMax; - perpMin = yMin; - perpMax = yMax; - } else { - // Y range specified - scan along Y, find X positions - scanMin = spec.rangeMin; - scanMax = spec.rangeMax; - perpMin = xMin; - perpMax = xMax; - } - // Find all surface crossings from the mesh nodes - auto crossingsRef = findSurfaceCrossings( - surfaceMeshRef, spec.isXRange, scanMin, scanMax, perpMin, perpMax); - auto crossingsCmp = findSurfaceCrossings( - surfaceMeshCmp, spec.isXRange, scanMin, scanMax, perpMin, perpMax); + auto crossingsRef = findSurfaceCrossings(surfaceMeshRef, spec); + auto crossingsCmp = findSurfaceCrossings(surfaceMeshCmp, spec); // Find critical dimensions auto [validRef, cdRef] = @@ -363,9 +328,11 @@ template class CompareCriticalDimensions { std::vector targetValues; std::vector sampleValues; - for (unsigned i = 0; i < D; ++i) { - outputMesh->minimumExtent[i] = std::numeric_limits::max(); - outputMesh->maximumExtent[i] = std::numeric_limits::lowest(); + for (unsigned i = 0; i < 3; ++i) { + outputMesh->minimumExtent[i] = + (i < D) ? std::numeric_limits::max() : 0.0; + outputMesh->maximumExtent[i] = + (i < D) ? std::numeric_limits::lowest() : 0.0; } unsigned pointId = 0; @@ -374,18 +341,21 @@ template class CompareCriticalDimensions { continue; // Create points for target and sample positions - Vec3D coordTarget, coordSample; - - if (result.isXRange) { - // Critical dimension is in Y, position is along X range - T xMid = (result.rangeMin + result.rangeMax) / 2.0; - coordTarget = {xMid, result.positionTarget, 0.0}; - coordSample = {xMid, result.positionSample, 0.0}; - } else { - // Critical dimension is in X, position is along Y range - T yMid = (result.rangeMin + result.rangeMax) / 2.0; - coordTarget = {result.positionTarget, yMid, 0.0}; - coordSample = {result.positionSample, yMid, 0.0}; + Vec3D coordTarget = {0.0, 0.0, 0.0}, coordSample = {0.0, 0.0, 0.0}; + + for (int i = 0; i < D; ++i) { + if (i == result.measureDimension) { + coordTarget[i] = result.positionTarget; + coordSample[i] = result.positionSample; + } else { + // Use midpoint of range for other dimensions + T mid = (result.minBounds[i] + result.maxBounds[i]) / 2.0; + // If bounds are infinite, use 0 + if (std::abs(mid) > std::numeric_limits::max() / 2.0) + mid = 0.0; + coordTarget[i] = mid; + coordSample[i] = mid; + } } // Add target point diff --git a/include/viennals/lsCompareNarrowBand.hpp b/include/viennals/lsCompareNarrowBand.hpp index c75f343f..feae7684 100644 --- a/include/viennals/lsCompareNarrowBand.hpp +++ b/include/viennals/lsCompareNarrowBand.hpp @@ -15,7 +15,7 @@ using namespace viennacore; /// Calculate distance measure between two level sets by comparing their SDF /// values on a narrow band. Returns the sum of squared differences between /// corresponding grid points. -/// The code is currently tended for 2D level sets only. +/// The code is intended for 2D and 3D level sets. template class CompareNarrowBand { using hrleIndexType = viennahrle::IndexType; SmartPointer> levelSetTarget = nullptr; @@ -27,8 +27,11 @@ template class CompareNarrowBand { T xRangeMax = std::numeric_limits::max(); T yRangeMin = std::numeric_limits::lowest(); T yRangeMax = std::numeric_limits::max(); + T zRangeMin = std::numeric_limits::lowest(); + T zRangeMax = std::numeric_limits::max(); bool useXRange = false; bool useYRange = false; + bool useZRange = false; // Fields to store the calculation results T sumSquaredDifferences = 0.0; @@ -136,18 +139,12 @@ template class CompareNarrowBand { public: CompareNarrowBand() { - assert( - D == 2 && - "CompareNarrowBand is currently only implemented for 2D level sets."); } CompareNarrowBand(SmartPointer> passedLevelSetTarget, SmartPointer> passedlevelSetSample) : levelSetTarget(passedLevelSetTarget), levelSetSample(passedlevelSetSample) { - assert( - D == 2 && - "CompareNarrowBand is currently only implemented for 2D level sets."); } void setLevelSetTarget(SmartPointer> passedLevelSet) { @@ -186,6 +183,20 @@ template class CompareNarrowBand { yRangeMax = std::numeric_limits::max(); } + /// Set the z-coordinate range to restrict the comparison area + void setZRange(T minZRange, T maxZRange) { + zRangeMin = minZRange; + zRangeMax = maxZRange; + useZRange = true; + } + + /// Clear the z-range restriction + void clearZRange() { + useZRange = false; + zRangeMin = std::numeric_limits::lowest(); + zRangeMax = std::numeric_limits::max(); + } + /// Set the output mesh where difference values will be stored void setOutputMesh(SmartPointer> passedMesh, bool outputMeshSquaredDiffs = true) { @@ -233,9 +244,11 @@ template class CompareNarrowBand { outputMesh->clear(); // Initialize mesh extent - for (unsigned i = 0; i < D; ++i) { - outputMesh->minimumExtent[i] = std::numeric_limits::max(); - outputMesh->maximumExtent[i] = std::numeric_limits::lowest(); + for (unsigned i = 0; i < 3; ++i) { + outputMesh->minimumExtent[i] = + (i < D) ? std::numeric_limits::max() : 0.0; + outputMesh->maximumExtent[i] = + (i < D) ? std::numeric_limits::lowest() : 0.0; } } @@ -244,6 +257,7 @@ template class CompareNarrowBand { // Check if current point is within specified x and y ranges T xCoord = itSample.getIndices()[0] * gridDelta; T yCoord = (D > 1) ? itSample.getIndices()[1] * gridDelta : 0; + T zCoord = (D > 2) ? itSample.getIndices()[2] * gridDelta : 0; // Skip if outside the specified x-range if (useXRange && (xCoord < xRangeMin || xCoord > xRangeMax)) { @@ -255,6 +269,11 @@ template class CompareNarrowBand { continue; } + // Skip if outside the specified z-range (only check in 3D) + if (D > 2 && useZRange && (zCoord < zRangeMin || zCoord > zRangeMax)) { + continue; + } + // Move the second iterator to the same position itTarget.goToIndicesSequential(itSample.getIndices()); diff --git a/include/viennals/lsCompareSparseField.hpp b/include/viennals/lsCompareSparseField.hpp index 0b012476..ba385363 100644 --- a/include/viennals/lsCompareSparseField.hpp +++ b/include/viennals/lsCompareSparseField.hpp @@ -29,7 +29,7 @@ using namespace viennacore; /// The iterated level set is expected to be sparse. The reduction is performed /// automatically if this is not the case. /// -/// The code is currently intended for 2D level sets only. +/// The code is intended for 2D and 3D level sets. template class CompareSparseField { using hrleIndexType = viennahrle::IndexType; @@ -42,8 +42,11 @@ template class CompareSparseField { T xRangeMax = std::numeric_limits::max(); T yRangeMin = std::numeric_limits::lowest(); T yRangeMax = std::numeric_limits::max(); + T zRangeMin = std::numeric_limits::lowest(); + T zRangeMax = std::numeric_limits::max(); bool useXRange = false; bool useYRange = false; + bool useZRange = false; // Fields to store the calculation results T sumSquaredDifferences = 0.0; @@ -176,6 +179,20 @@ template class CompareSparseField { yRangeMax = std::numeric_limits::max(); } + /// Set the z-coordinate range to restrict the comparison area + void setZRange(T minZRange, T maxZRange) { + zRangeMin = minZRange; + zRangeMax = maxZRange; + useZRange = true; + } + + /// Clear the z-range restriction + void clearZRange() { + useZRange = false; + zRangeMin = std::numeric_limits::lowest(); + zRangeMax = std::numeric_limits::max(); + } + /// Set the output mesh where difference values will be stored void setOutputMesh(SmartPointer> passedMesh) { outputMesh = passedMesh; @@ -285,6 +302,12 @@ template class CompareSparseField { continue; } + // Skip if outside the specified z-range + if (useZRange && (zCoord < zRangeMin || zCoord > zRangeMax)) { + itIterated.next(); + continue; + } + // Get iterated value T valueIterated = itIterated.getValue(); diff --git a/python/pyWrap.hpp b/python/pyWrap.hpp index aa617a36..818e5496 100644 --- a/python/pyWrap.hpp +++ b/python/pyWrap.hpp @@ -848,6 +848,59 @@ template void bindApi(py::module &module) { "Set the filename for the output file.") .def("apply", &Writer::apply, "Write to file."); + // CompareSparseField + py::class_, + SmartPointer>>(module, + "CompareSparseField") + // constructors + .def(py::init(&SmartPointer>::template New<>)) + .def(py::init( + &SmartPointer>::template New< + SmartPointer> &, SmartPointer> &>)) + // methods + .def("setLevelSetExpanded", + &CompareSparseField::setLevelSetExpanded, + "Sets the expanded level set for comparison.") + .def("setLevelSetIterated", + &CompareSparseField::setLevelSetIterated, + "Sets the iterated level set to compare against the expanded one.") + .def("setXRange", &CompareSparseField::setXRange, + "Set the x-coordinate range to restrict the comparison area") + .def("setYRange", &CompareSparseField::setYRange, + "Set the y-coordinate range to restrict the comparison area") + .def("clearXRange", &CompareSparseField::clearXRange, + "Clear the x-range restriction") + .def("clearYRange", &CompareSparseField::clearYRange, + "Clear the y-range restriction") + .def("setZRange", &CompareSparseField::setZRange, + "Set the z-coordinate range to restrict the comparison area") + .def("clearZRange", &CompareSparseField::clearZRange, + "Clear the z-range restriction") + .def("setOutputMesh", &CompareSparseField::setOutputMesh, + "Set the output mesh where difference values will be stored") + .def("setFillIteratedWithDistances", + &CompareSparseField::setFillIteratedWithDistances, + "Set whether to fill the iterated level set with distance values") + .def("setExpandedLevelSetWidth", + &CompareSparseField::setExpandedLevelSetWidth, + "Set the expansion width for the expanded level set") + .def("apply", &CompareSparseField::apply, + "Apply the comparison and calculate the sum of squared " + "differences.") + .def("getSumSquaredDifferences", + &CompareSparseField::getSumSquaredDifferences, + "Return the sum of squared differences calculated by apply().") + .def("getSumDifferences", &CompareSparseField::getSumDifferences, + "Return the sum of absolute differences calculated by apply().") + .def("getNumPoints", &CompareSparseField::getNumPoints, + "Return the number of points used in the comparison.") + .def("getNumSkippedPoints", + &CompareSparseField::getNumSkippedPoints, + "Return the number of points skipped during comparison.") + .def("getRMSE", &CompareSparseField::getRMSE, + "Calculate the root mean square error from previously computed " + "values."); + // WriteVisualizationMesh #ifdef VIENNALS_USE_VTK py::class_, @@ -890,7 +943,6 @@ template void bindApi(py::module &module) { "Make and write mesh."); #endif - if constexpr (D == 2) { // CompareArea py::class_, SmartPointer>>( module, "CompareArea") @@ -910,6 +962,8 @@ template void bindApi(py::module &module) { "Sets the x-range and custom increment value") .def("setYRangeAndIncrement", &CompareArea::setYRangeAndIncrement, "Sets the y-range and custom increment value") + .def("setZRangeAndIncrement", &CompareArea::setZRangeAndIncrement, + "Sets the z-range and custom increment value") .def("setOutputMesh", &CompareArea::setOutputMesh, "Set the output mesh where difference areas will be stored") .def("getAreaMismatch", &CompareArea::getAreaMismatch, @@ -979,6 +1033,10 @@ template void bindApi(py::module &module) { .def("setLevelSetSample", &CompareCriticalDimensions::setLevelSetSample, "Sets the sample level set.") + .def("addRange", &CompareCriticalDimensions::addRange, + py::arg("measureDimension"), py::arg("minBounds"), + py::arg("maxBounds"), py::arg("findMaximum") = true, + "Add a generic range specification.") .def("addXRange", &CompareCriticalDimensions::addXRange, py::arg("minX"), py::arg("maxX"), py::arg("findMaximum") = true, "Add an X range to find maximum or minimum Y position.") @@ -1044,6 +1102,10 @@ template void bindApi(py::module &module) { "Clear the x-range restriction") .def("clearYRange", &CompareNarrowBand::clearYRange, "Clear the y-range restriction") + .def("setZRange", &CompareNarrowBand::setZRange, + "Set the z-coordinate range to restrict the comparison area") + .def("clearZRange", &CompareNarrowBand::clearZRange, + "Clear the z-range restriction") .def("setOutputMesh", &CompareNarrowBand::setOutputMesh, "Set the output mesh where difference values will be stored") .def("setOutputMeshSquaredDifferences", @@ -1063,54 +1125,4 @@ template void bindApi(py::module &module) { .def("getRMSE", &CompareNarrowBand::getRMSE, "Calculate the root mean square error from previously computed " "values."); - - // CompareSparseField - py::class_, - SmartPointer>>(module, - "CompareSparseField") - // constructors - .def(py::init(&SmartPointer>::template New<>)) - .def(py::init( - &SmartPointer>::template New< - SmartPointer> &, SmartPointer> &>)) - // methods - .def("setLevelSetExpanded", - &CompareSparseField::setLevelSetExpanded, - "Sets the expanded level set for comparison.") - .def("setLevelSetIterated", - &CompareSparseField::setLevelSetIterated, - "Sets the iterated level set to compare against the expanded one.") - .def("setXRange", &CompareSparseField::setXRange, - "Set the x-coordinate range to restrict the comparison area") - .def("setYRange", &CompareSparseField::setYRange, - "Set the y-coordinate range to restrict the comparison area") - .def("clearXRange", &CompareSparseField::clearXRange, - "Clear the x-range restriction") - .def("clearYRange", &CompareSparseField::clearYRange, - "Clear the y-range restriction") - .def("setOutputMesh", &CompareSparseField::setOutputMesh, - "Set the output mesh where difference values will be stored") - .def("setFillIteratedWithDistances", - &CompareSparseField::setFillIteratedWithDistances, - "Set whether to fill the iterated level set with distance values") - .def("setExpandedLevelSetWidth", - &CompareSparseField::setExpandedLevelSetWidth, - "Set the expansion width for the expanded level set") - .def("apply", &CompareSparseField::apply, - "Apply the comparison and calculate the sum of squared " - "differences.") - .def("getSumSquaredDifferences", - &CompareSparseField::getSumSquaredDifferences, - "Return the sum of squared differences calculated by apply().") - .def("getSumDifferences", &CompareSparseField::getSumDifferences, - "Return the sum of absolute differences calculated by apply().") - .def("getNumPoints", &CompareSparseField::getNumPoints, - "Return the number of points used in the comparison.") - .def("getNumSkippedPoints", - &CompareSparseField::getNumSkippedPoints, - "Return the number of points skipped during comparison.") - .def("getRMSE", &CompareSparseField::getRMSE, - "Calculate the root mean square error from previously computed " - "values."); - } } diff --git a/tests/CompareArea/CompareArea.cpp b/tests/CompareArea/CompareArea.cpp index 9bb6d8ae..e1599748 100644 --- a/tests/CompareArea/CompareArea.cpp +++ b/tests/CompareArea/CompareArea.cpp @@ -1,4 +1,6 @@ +#include #include +#include #include #include @@ -15,16 +17,16 @@ namespace ls = viennals; -int main() { - constexpr int D = 2; - - omp_set_num_threads(4); - +template void runTest() { + std::cout << "Running " << D << "D Test..." << std::endl; double extent = 15; double gridDelta = 0.5; - double bounds[2 * D] = {-extent, extent, -extent, extent}; - ls::Domain::BoundaryType boundaryCons[D]; + double bounds[2 * D]; + for (int i = 0; i < 2 * D; ++i) + bounds[i] = (i % 2 == 0) ? -extent : extent; + + typename ls::Domain::BoundaryType boundaryCons[D]; for (unsigned i = 0; i < D; ++i) boundaryCons[i] = ls::Domain::BoundaryType::REFLECTIVE_BOUNDARY; @@ -32,7 +34,7 @@ int main() { auto sphere1 = ls::SmartPointer>::New( bounds, boundaryCons, gridDelta); - double origin1[D] = {0., 0.}; + std::vector origin1(D, 0.0); double radius1 = 5.0; ls::MakeGeometry( @@ -43,83 +45,105 @@ int main() { auto sphere2 = ls::SmartPointer>::New( bounds, boundaryCons, gridDelta); - double origin2[D] = {0., 0.}; + std::vector origin2(D, 0.0); double radius2 = 8.0; ls::MakeGeometry( sphere2, ls::SmartPointer>::New(origin2, radius2)) .apply(); + std::string suffix = "_" + std::to_string(D) + "D.vtp"; // Export both spheres as VTK files for visualization { auto mesh = ls::SmartPointer>::New(); ls::ToMesh(sphere1, mesh).apply(); - ls::VTKWriter(mesh, "sphere1.vtp").apply(); + ls::VTKWriter(mesh, "sphere1" + suffix).apply(); } { auto mesh = ls::SmartPointer>::New(); ls::ToMesh(sphere2, mesh).apply(); - ls::VTKWriter(mesh, "sphere2.vtp").apply(); + ls::VTKWriter(mesh, "sphere2" + suffix).apply(); } - // Compare the areas with mesh Output - ls::CompareArea compareArea(sphere1, sphere2); + // Compare the volumes/areas with mesh Output + // Using the new general name CompareDomain (CompareArea is now an alias) + ls::CompareDomain compareDomain(sphere1, sphere2); auto mesh = ls::SmartPointer>::New(); // Create mesh for output - compareArea.setOutputMesh(mesh); - compareArea.apply(); + compareDomain.setOutputMesh(mesh); + compareDomain.apply(); // save mesh to file - ls::VTKWriter(mesh, "areaDifference.vtu").apply(); - - // Calculate theoretical area difference (for circles in 2D) - // Area of circle = Ï€ * r² - double theoreticalArea1 = M_PI * radius1 * radius1; - double theoreticalArea2 = M_PI * radius2 * radius2; - double theoreticalDifference = std::abs(theoreticalArea2 - theoreticalArea1); + ls::VTKWriter(mesh, "volumeDifference" + suffix + ".vtu").apply(); + + // Calculate theoretical difference + double theoreticalDiff = 0.0; + if constexpr (D == 2) { + // Area of circle = Ï€ * r² + double area1 = M_PI * radius1 * radius1; + double area2 = M_PI * radius2 * radius2; + theoreticalDiff = std::abs(area2 - area1); + } else if constexpr (D == 3) { + // Volume of sphere = 4/3 * Ï€ * r³ + double vol1 = (4.0 / 3.0) * M_PI * std::pow(radius1, 3); + double vol2 = (4.0 / 3.0) * M_PI * std::pow(radius2, 3); + theoreticalDiff = std::abs(vol2 - vol1); + } - // Get the calculated area difference - double calculatedDifference = compareArea.getAreaMismatch(); - unsigned long int cellCount = compareArea.getCellCount(); + // Get the calculated difference + double calculatedDifference = compareDomain.getVolumeMismatch(); + unsigned long int cellCount = compareDomain.getCellCount(); std::cout << "Sphere 1 radius: " << radius1 << std::endl; std::cout << "Sphere 2 radius: " << radius2 << std::endl; - std::cout << "Theoretical area difference: " << theoreticalDifference - << std::endl; - std::cout << "Calculated area difference: " << calculatedDifference - << std::endl; + std::cout << "Theoretical difference: " << theoreticalDiff << std::endl; + std::cout << "Calculated difference: " << calculatedDifference << std::endl; std::cout << "Number of differing cells: " << cellCount << std::endl; - std::cout << "Error: " - << std::abs(calculatedDifference - theoreticalDifference) + std::cout << "Error: " << std::abs(calculatedDifference - theoreticalDiff) << std::endl; // Test custom increment and range functionality std::cout << "\nTesting custom increments and ranges:" << std::endl; - // Set custom increment for whole domain - compareArea.setDefaultIncrement(2); - compareArea.apply(); - std::cout << "Area difference with default increment of 2: " - << compareArea.getCustomAreaMismatch() << std::endl; + compareDomain.setDefaultIncrement(2); + compareDomain.apply(); + std::cout << "Difference with default increment of 2: " + << compareDomain.getCustomVolumeMismatch() << std::endl; std::cout << "Cell count with default increment of 2: " - << compareArea.getCustomCellCount() << std::endl; + << compareDomain.getCustomCellCount() << std::endl; // Set range-specific increment for x-range - compareArea.setDefaultIncrement(1); - compareArea.setXRangeAndIncrement(-5, 5, 3); - compareArea.apply(); - std::cout << "Area difference with x-range increment of 3: " - << compareArea.getCustomAreaMismatch() << std::endl; + compareDomain.setDefaultIncrement(1); + compareDomain.setXRangeAndIncrement(-5, 5, 3); + compareDomain.apply(); + std::cout << "Difference with x-range increment of 3: " + << compareDomain.getCustomVolumeMismatch() << std::endl; std::cout << "Cell count with x-range increment of 3: " - << compareArea.getCustomCellCount() << std::endl; + << compareDomain.getCustomCellCount() << std::endl; // Set range-specific increment for y-range - compareArea.setDefaultIncrement(1); - compareArea.setYRangeAndIncrement(-5, 5, 4); - compareArea.apply(); - std::cout << "Area difference with y-range increment of 4: " - << compareArea.getCustomAreaMismatch() << std::endl; + compareDomain.setDefaultIncrement(1); + compareDomain.setYRangeAndIncrement(-5, 5, 4); + compareDomain.apply(); + std::cout << "Difference with y-range increment of 4: " + << compareDomain.getCustomVolumeMismatch() << std::endl; std::cout << "Cell count with y-range increment of 4: " - << compareArea.getCustomCellCount() << std::endl; + << compareDomain.getCustomCellCount() << std::endl; + + if constexpr (D == 3) { + // Set range-specific increment for z-range + compareDomain.setDefaultIncrement(1); + compareDomain.setZRangeAndIncrement(-5, 5, 5); + compareDomain.apply(); + std::cout << "Difference with z-range increment of 5: " + << compareDomain.getCustomVolumeMismatch() << std::endl; + std::cout << "Cell count with z-range increment of 5: " + << compareDomain.getCustomCellCount() << std::endl; + } +} +int main() { + omp_set_num_threads(4); + runTest<2>(); + runTest<3>(); return 0; } diff --git a/tests/CompareChamfer/CompareChamfer.cpp b/tests/CompareChamfer/CompareChamfer.cpp index 82fc6ab9..9f6cf5ee 100644 --- a/tests/CompareChamfer/CompareChamfer.cpp +++ b/tests/CompareChamfer/CompareChamfer.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -24,72 +25,74 @@ namespace ls = viennals; -int main() { - constexpr int D = 2; - - omp_set_num_threads(4); - +template void runTest() { + std::cout << "Running " << D << "D Test..." << std::endl; double extent = 15; double gridDelta = 0.5; - double bounds[2 * D] = {-extent, extent, -extent, extent}; - ls::Domain::BoundaryType boundaryCons[D]; + double bounds[2 * D]; + for (int i = 0; i < 2 * D; ++i) + bounds[i] = (i % 2 == 0) ? -extent : extent; + + typename ls::Domain::BoundaryType boundaryCons[D]; for (unsigned i = 0; i < D; ++i) boundaryCons[i] = ls::Domain::BoundaryType::REFLECTIVE_BOUNDARY; - // Create first circle (target) - auto circle1 = ls::SmartPointer>::New( + // Create first sphere (target) + auto sphere1 = ls::SmartPointer>::New( bounds, boundaryCons, gridDelta); - double origin1[D] = {0., 0.}; + std::vector origin1(D, 0.0); double radius1 = 5.0; ls::MakeGeometry( - circle1, ls::SmartPointer>::New(origin1, radius1)) + sphere1, ls::SmartPointer>::New(origin1, radius1)) .apply(); - // Create second circle (sample) with different center - auto circle2 = ls::SmartPointer>::New( + // Create second sphere (sample) with different center + auto sphere2 = ls::SmartPointer>::New( bounds, boundaryCons, gridDelta); - double origin2[D] = {2., 1.}; // Shifted center - // double origin2[D] = {0., 0.}; // Same center (for testing) + std::vector origin2(D, 0.0); + origin2[0] = 2.; + origin2[1] = 1.; double radius2 = 5.0; // Same radius ls::MakeGeometry( - circle2, ls::SmartPointer>::New(origin2, radius2)) + sphere2, ls::SmartPointer>::New(origin2, radius2)) .apply(); - // Export both circles as VTK files for visualization - std::cout << "Exporting surface meshes..." << std::endl; + // Export both spheres as VTK files for visualization + std::string suffix = "_" + std::to_string(D) + "D.vtp"; + std::cout << "Exporting surface meshes to *" << suffix << "..." << std::endl; { auto meshSurface = ls::SmartPointer>::New(); - ls::ToSurfaceMesh(circle1, meshSurface).apply(); - ls::VTKWriter(meshSurface, "circle1_surface.vtp").apply(); - std::cout << " Circle 1 surface points: " << meshSurface->nodes.size() + ls::ToSurfaceMesh(sphere1, meshSurface).apply(); + ls::VTKWriter(meshSurface, "sphere1_surface" + suffix).apply(); + std::cout << " Sphere 1 surface points: " << meshSurface->nodes.size() << std::endl; } { auto meshSurface = ls::SmartPointer>::New(); - ls::ToSurfaceMesh(circle2, meshSurface).apply(); - ls::VTKWriter(meshSurface, "circle2_surface.vtp").apply(); - std::cout << " Circle 2 surface points: " << meshSurface->nodes.size() + ls::ToSurfaceMesh(sphere2, meshSurface).apply(); + ls::VTKWriter(meshSurface, "sphere2_surface" + suffix).apply(); + std::cout << " Sphere 2 surface points: " << meshSurface->nodes.size() << std::endl; } // Test 1: Basic Chamfer distance calculation std::cout << "\n=== Test 1: Basic Chamfer Distance ===" << std::endl; - std::cout << "Circle 1 center: (" << origin1[0] << ", " << origin1[1] << ")" - << std::endl; - std::cout << "Circle 2 center: (" << origin2[0] << ", " << origin2[1] << ")" + std::cout << "Sphere 1 center: (" << origin1[0] << ", " << origin1[1] << ")" << std::endl; - std::cout << "Expected geometric shift: " - << std::sqrt((origin2[0] - origin1[0]) * (origin2[0] - origin1[0]) + - (origin2[1] - origin1[1]) * (origin2[1] - origin1[1])) + std::cout << "Sphere 2 center: (" << origin2[0] << ", " << origin2[1] << ")" << std::endl; + double distSq = 0.0; + for (int i = 0; i < D; ++i) + distSq += (origin2[i] - origin1[i]) * (origin2[i] - origin1[i]); + std::cout << "Expected geometric shift: " << std::sqrt(distSq) << std::endl; - ls::CompareChamfer compareChamfer(circle1, circle2); + ls::CompareChamfer compareChamfer(sphere1, sphere2); // Create output meshes with distance information auto targetMesh = ls::SmartPointer>::New(); @@ -121,20 +124,20 @@ int main() { std::cout << " Execution time: " << chamfer_ms.count() << " ms" << std::endl; // Save meshes with distance data - ls::VTKWriter(targetMesh, "chamfer_target_distances.vtp").apply(); - ls::VTKWriter(sampleMesh, "chamfer_sample_distances.vtp").apply(); + ls::VTKWriter(targetMesh, "chamfer_target_distances" + suffix).apply(); + ls::VTKWriter(sampleMesh, "chamfer_sample_distances" + suffix).apply(); // Test 2: Compare with other metrics std::cout << "\n=== Test 2: Comparison with Other Metrics ===" << std::endl; // Sparse Field comparison - auto circle1_expanded = ls::SmartPointer>::New(circle1); - ls::Expand(circle1_expanded, 50).apply(); - auto circle2_reduced = ls::SmartPointer>::New(circle2); - ls::Reduce(circle2_reduced, 1).apply(); + auto sphere1_expanded = ls::SmartPointer>::New(sphere1); + ls::Expand(sphere1_expanded, 50).apply(); + auto sphere2_reduced = ls::SmartPointer>::New(sphere2); + ls::Reduce(sphere2_reduced, 1).apply(); - ls::CompareSparseField compareSparseField(circle1_expanded, - circle2_reduced); + ls::CompareSparseField compareSparseField(sphere1_expanded, + sphere2_reduced); auto t3 = std::chrono::high_resolution_clock::now(); compareSparseField.apply(); auto t4 = std::chrono::high_resolution_clock::now(); @@ -148,46 +151,46 @@ int main() { std::cout << " Execution time: " << sparse_ms.count() << " ms" << std::endl; // Area comparison - ls::CompareArea compareArea(circle1, circle2); + ls::CompareDomain compareDomain(sphere1, sphere2); auto t5 = std::chrono::high_resolution_clock::now(); - compareArea.apply(); + compareDomain.apply(); auto t6 = std::chrono::high_resolution_clock::now(); std::chrono::duration area_ms = t6 - t5; - std::cout << "\nArea Comparison Results:" << std::endl; - std::cout << " Area mismatch: " << compareArea.getAreaMismatch() + std::cout << "\nArea/Volume Comparison Results:" << std::endl; + std::cout << " Area/Volume mismatch: " << compareDomain.getVolumeMismatch() << std::endl; - std::cout << " Different cells: " << compareArea.getCellCount() << std::endl; + std::cout << " Different cells: " << compareDomain.getCellCount() << std::endl; std::cout << " Execution time: " << area_ms.count() << " ms" << std::endl; // Test 3: Different geometric configurations std::cout << "\n=== Test 3: Different Geometric Configurations ===" << std::endl; - // Test 3a: Identical circles (should give near-zero Chamfer distance) - auto circle3 = ls::SmartPointer>::New( + // Test 3a: Identical spheres (should give near-zero Chamfer distance) + auto sphere3 = ls::SmartPointer>::New( bounds, boundaryCons, gridDelta); ls::MakeGeometry( - circle3, ls::SmartPointer>::New(origin1, radius1)) + sphere3, ls::SmartPointer>::New(origin1, radius1)) .apply(); - ls::CompareChamfer compareIdentical(circle1, circle3); + ls::CompareChamfer compareIdentical(sphere1, sphere3); compareIdentical.apply(); - std::cout << "Identical circles:" << std::endl; + std::cout << "Identical spheres:" << std::endl; std::cout << " Chamfer distance: " << compareIdentical.getChamferDistance() << " (expected ~0)" << std::endl; // Test 3b: Different radii - auto circle4 = ls::SmartPointer>::New( + auto sphere4 = ls::SmartPointer>::New( bounds, boundaryCons, gridDelta); double radius4 = 7.0; // Larger radius ls::MakeGeometry( - circle4, ls::SmartPointer>::New(origin1, radius4)) + sphere4, ls::SmartPointer>::New(origin1, radius4)) .apply(); - ls::CompareChamfer compareDifferentSize(circle1, circle4); + ls::CompareChamfer compareDifferentSize(sphere1, sphere4); compareDifferentSize.apply(); std::cout << "\nDifferent radii (r1=" << radius1 << ", r2=" << radius4 @@ -202,14 +205,15 @@ int main() { << compareDifferentSize.getBackwardDistance() << std::endl; // Test 3c: Large shift - auto circle5 = ls::SmartPointer>::New( + auto sphere5 = ls::SmartPointer>::New( bounds, boundaryCons, gridDelta); - double origin5[D] = {5., 0.}; // Larger shift + std::vector origin5(D, 0.0); + origin5[0] = 5.; // Larger shift ls::MakeGeometry( - circle5, ls::SmartPointer>::New(origin5, radius1)) + sphere5, ls::SmartPointer>::New(origin5, radius1)) .apply(); - ls::CompareChamfer compareLargeShift(circle1, circle5); + ls::CompareChamfer compareLargeShift(sphere1, sphere5); compareLargeShift.apply(); std::cout << "\nLarge shift (5 units in x-direction):" << std::endl; @@ -220,10 +224,14 @@ int main() { // Test 4: Performance summary std::cout << "\n=== Performance Summary ===" << std::endl; std::cout << "Chamfer distance: " << chamfer_ms.count() << " ms" << std::endl; - std::cout << "Sparse field: " << sparse_ms.count() << " ms" << std::endl; - std::cout << "Area comparison: " << area_ms.count() << " ms" << std::endl; - std::cout << "\n=== All Tests Completed Successfully ===" << std::endl; +} + +int main() { + omp_set_num_threads(8); + + runTest<2>(); + runTest<3>(); return 0; } diff --git a/tests/CompareCriticalDimensions/CompareCriticalDimensions.cpp b/tests/CompareCriticalDimensions/CompareCriticalDimensions.cpp index b3d0c0f0..a4ef722c 100644 --- a/tests/CompareCriticalDimensions/CompareCriticalDimensions.cpp +++ b/tests/CompareCriticalDimensions/CompareCriticalDimensions.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include @@ -16,16 +18,16 @@ namespace ls = viennals; -int main() { - constexpr int D = 2; - - omp_set_num_threads(4); - +template void runTest() { + std::cout << "Running " << D << "D Test..." << std::endl; double extent = 15; double gridDelta = 0.1; - double bounds[2 * D] = {-extent, extent, -extent, extent}; - ls::Domain::BoundaryType boundaryCons[D]; + double bounds[2 * D]; + for (int i = 0; i < 2 * D; ++i) + bounds[i] = (i % 2 == 0) ? -extent : extent; + + typename ls::Domain::BoundaryType boundaryCons[D]; for (unsigned i = 0; i < D; ++i) boundaryCons[i] = ls::Domain::BoundaryType::REFLECTIVE_BOUNDARY; @@ -33,7 +35,7 @@ int main() { auto circle1 = ls::SmartPointer>::New( bounds, boundaryCons, gridDelta); - double origin1[D] = {0., 0.}; + std::vector origin1(D, 0.0); double radius1 = 5.0; ls::MakeGeometry( @@ -44,39 +46,61 @@ int main() { auto circle2 = ls::SmartPointer>::New( bounds, boundaryCons, gridDelta); - double origin2[D] = {1.5, 0.5}; // Shifted center - double radius2 = 5.0; // Same radius + std::vector origin2(D, 0.0); + origin2[0] = 1.5; + origin2[1] = 0.5; + double radius2 = 5.0; // Same radius ls::MakeGeometry( circle2, ls::SmartPointer>::New(origin2, radius2)) .apply(); + std::string suffix = "_" + std::to_string(D) + "D.vtp"; // Export both circles as VTK files for visualization { auto mesh = ls::SmartPointer>::New(); ls::ToSurfaceMesh(circle1, mesh).apply(); - ls::VTKWriter(mesh, "circle1_target.vtp").apply(); + ls::VTKWriter(mesh, "circle1_target" + suffix).apply(); } { auto mesh = ls::SmartPointer>::New(); ls::ToSurfaceMesh(circle2, mesh).apply(); - ls::VTKWriter(mesh, "circle2_sample.vtp").apply(); + ls::VTKWriter(mesh, "circle2_sample" + suffix).apply(); } // Compare critical dimensions ls::CompareCriticalDimensions compareCriticalDims(circle1, circle2); - // Add X ranges to find maximum and minimum Y positions (top and bottom) - // Search in the central X range where both circles overlap - compareCriticalDims.addXRange(-0, 0, true); // Find maximum Y (top) - compareCriticalDims.addXRange(-0, 0, false); // Find minimum Y (bottom) - - // Add Y ranges to find maximum and minimum X positions (right and left) - // Search in the central Y range where both circles overlap - compareCriticalDims.addYRange(-0, 0, true); // Find maximum X (right) - compareCriticalDims.addYRange(-0, 0, false); // Find minimum X (left) + if constexpr (D == 2) { + // Add X ranges to find maximum and minimum Y positions (top and bottom) + // Search in the central X range where both circles overlap + compareCriticalDims.addXRange(-0.1, 0.1, true); // Find maximum Y (top) + compareCriticalDims.addXRange(-0.1, 0.1, false); // Find minimum Y (bottom) + + // Add Y ranges to find maximum and minimum X positions (right and left) + // Search in the central Y range where both circles overlap + compareCriticalDims.addYRange(-0.1, 0.1, true); // Find maximum X (right) + compareCriticalDims.addYRange(-0.1, 0.1, false); // Find minimum X (left) + } else { + // For 3D, measure Z extent (top/bottom) at center (X=0, Y=0) + // And X extent (right/left) at center (Y=0, Z=0) + double inf = std::numeric_limits::max(); + double lowest = std::numeric_limits::lowest(); + + // Measure Z (dim 2) at X~0, Y~0 + std::array minB = {-0.1, -0.1, lowest}; + std::array maxB = {0.1, 0.1, inf}; + compareCriticalDims.addRange(2, minB, maxB, true); // Max Z + compareCriticalDims.addRange(2, minB, maxB, false); // Min Z + + // Measure X (dim 0) at Y~0, Z~0 + minB = {lowest, -0.1, -0.1}; + maxB = {inf, 0.1, 0.1}; + compareCriticalDims.addRange(0, minB, maxB, true); // Max X + compareCriticalDims.addRange(0, minB, maxB, false); // Min X + } // Create mesh for output auto mesh = ls::SmartPointer>::New(); @@ -86,7 +110,7 @@ int main() { compareCriticalDims.apply(); // Save mesh to file - ls::VTKWriter(mesh, "criticalDimensions.vtp").apply(); + ls::VTKWriter(mesh, "criticalDimensions" + suffix).apply(); // Debug: Print some surface mesh nodes to see actual positions std::cout << "\nDebug - Sample surface nodes from circle1:" << std::endl; @@ -145,13 +169,18 @@ int main() { std::cout << "Left/Right (max/min X): should be close to X-shift = " << std::abs(origin2[0] - origin1[0]) << std::endl; - // Additional test: Test with wider ranges - std::cout << "\n--- Testing with wider X range ---" << std::endl; - compareCriticalDims.clearRanges(); - compareCriticalDims.addXRange(-10, 10, true); // Find maximum Y - compareCriticalDims.addXRange(-10, 10, false); // Find minimum Y - compareCriticalDims.setOutputMesh(nullptr); // Don't create mesh - compareCriticalDims.apply(); + if constexpr (D == 2) { + // Additional test: Test with wider ranges + std::cout << "\n--- Testing with wider X range ---" << std::endl; + compareCriticalDims.clearRanges(); + compareCriticalDims.addXRange(-10, 10, true); // Find maximum Y + compareCriticalDims.addXRange(-10, 10, false); // Find minimum Y + compareCriticalDims.setOutputMesh(nullptr); // Don't create mesh + compareCriticalDims.apply(); + } else { + // Skip additional tests for 3D to keep it simple + return; + } std::cout << "Number of critical dimensions: " << compareCriticalDims.getNumCriticalDimensions() << std::endl; @@ -181,6 +210,12 @@ int main() { << std::endl; } } +} + +int main() { + omp_set_num_threads(4); + runTest<2>(); + runTest<3>(); return 0; } diff --git a/tests/CompareNarrowBand/CompareNarrowBand.cpp b/tests/CompareNarrowBand/CompareNarrowBand.cpp index 522f4b57..8efa10af 100644 --- a/tests/CompareNarrowBand/CompareNarrowBand.cpp +++ b/tests/CompareNarrowBand/CompareNarrowBand.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include @@ -17,16 +19,16 @@ namespace ls = viennals; -int main() { - constexpr int D = 2; - - omp_set_num_threads(4); - +template void runTest() { + std::cout << "Running " << D << "D Test..." << std::endl; double extent = 15; double gridDelta = 0.5; - double bounds[2 * D] = {-extent, extent, -extent, extent}; - ls::Domain::BoundaryType boundaryCons[D]; + double bounds[2 * D]; + for (int i = 0; i < 2 * D; ++i) + bounds[i] = (i % 2 == 0) ? -extent : extent; + + typename ls::Domain::BoundaryType boundaryCons[D]; for (unsigned i = 0; i < D; ++i) boundaryCons[i] = ls::Domain::BoundaryType::REFLECTIVE_BOUNDARY; @@ -34,7 +36,7 @@ int main() { auto sphere1 = ls::SmartPointer>::New( bounds, boundaryCons, gridDelta); - double origin1[D] = {0., 0.}; + std::vector origin1(D, 0.0); double radius1 = 5.0; ls::MakeGeometry( @@ -45,24 +47,27 @@ int main() { auto sphere2 = ls::SmartPointer>::New( bounds, boundaryCons, gridDelta); - double origin2[D] = {2., 1.}; // Shifted center - double radius2 = 5.0; // Same radius + std::vector origin2(D, 0.0); + origin2[0] = 2.; + origin2[1] = 1.; + double radius2 = 5.0; // Same radius ls::MakeGeometry( sphere2, ls::SmartPointer>::New(origin2, radius2)) .apply(); + std::string suffix = "_" + std::to_string(D) + "D.vtp"; // Export both spheres as VTK files for visualization { auto mesh = ls::SmartPointer>::New(); ls::ToMesh(sphere1, mesh).apply(); - ls::VTKWriter(mesh, "sphere1_narrowband.vtp").apply(); + ls::VTKWriter(mesh, "sphere1_narrowband" + suffix).apply(); } { auto mesh = ls::SmartPointer>::New(); ls::ToMesh(sphere2, mesh).apply(); - ls::VTKWriter(mesh, "sphere2_narrowband.vtp").apply(); + ls::VTKWriter(mesh, "sphere2_narrowband" + suffix).apply(); } // Compare the narrow bands @@ -75,7 +80,9 @@ int main() { compareNarrowBand.apply(); // Save mesh to file - ls::VTKWriter(mesh, "narrowband_absolute_differences.vtu").apply(); + ls::VTKWriter(mesh, + "narrowband_absolute_differences" + suffix + ".vtu") + .apply(); // Get the calculated difference metrics double sumSquaredDifferences = compareNarrowBand.getSumSquaredDifferences(); @@ -123,13 +130,33 @@ int main() { std::cout << "Number of points in both ranges: " << compareNarrowBand.getNumPoints() << std::endl; + if constexpr (D == 3) { + // Test with restricted Z range + compareNarrowBand.clearXRange(); + compareNarrowBand.clearYRange(); + compareNarrowBand.setZRange(-5, 5); + compareNarrowBand.apply(); + std::cout << "RMSE with Z range [-5, 5]: " << compareNarrowBand.getRMSE() + << std::endl; + std::cout << "Number of points in Z range: " + << compareNarrowBand.getNumPoints() << std::endl; + compareNarrowBand.clearZRange(); + } + // Create a mesh output with squared differences compareNarrowBand.setOutputMesh(mesh); compareNarrowBand.setOutputMeshSquaredDifferences(true); compareNarrowBand.apply(); - ls::VTKWriter(mesh, - "narrowband_resctricted-range_squared_differences.vtu") + ls::VTKWriter(mesh, "narrowband_resctricted-range_squared_" + "differences" + + suffix + ".vtu") .apply(); +} + +int main() { + omp_set_num_threads(4); + runTest<2>(); + runTest<3>(); return 0; } diff --git a/tests/CompareSparseField/CompareSparseField.cpp b/tests/CompareSparseField/CompareSparseField.cpp index a43ddb43..f5c673be 100644 --- a/tests/CompareSparseField/CompareSparseField.cpp +++ b/tests/CompareSparseField/CompareSparseField.cpp @@ -175,14 +175,26 @@ template void runTest() { std::cout << "Number of points in X range: " << compareSparseField.getNumPoints() << std::endl; - // // Test with restricted Y range - // compareSparseField.clearXRange(); - // compareSparseField.setYRange(-5, 5); - // compareSparseField.apply(); - // std::cout << "RMSE with Y range [-5, 5]: " << compareSparseField.getRMSE() - // << std::endl; - // std::cout << "Number of points in Y range: " - // << compareSparseField.getNumPoints() << std::endl; + // Test with restricted Y range + compareSparseField.clearXRange(); + compareSparseField.setYRange(-5, 5); + compareSparseField.apply(); + std::cout << "RMSE with Y range [-5, 5]: " << compareSparseField.getRMSE() + << std::endl; + std::cout << "Number of points in Y range: " + << compareSparseField.getNumPoints() << std::endl; + + if constexpr (D == 3) { + // Test with restricted Z range + compareSparseField.clearYRange(); + compareSparseField.setZRange(-5, 5); + compareSparseField.apply(); + std::cout << "RMSE with Z range [-5, 5]: " << compareSparseField.getRMSE() + << std::endl; + std::cout << "Number of points in Z range: " + << compareSparseField.getNumPoints() << std::endl; + compareSparseField.clearZRange(); + } // Test with both X and Y range restrictions compareSparseField.setXRange(-3, 3); From 04fb4873e7e6a850975373a951c9c4ca5da2a71e Mon Sep 17 00:00:00 2001 From: filipovic Date: Fri, 9 Jan 2026 14:01:03 +0100 Subject: [PATCH 40/57] format --- include/viennals/lsCompareArea.hpp | 27 +- include/viennals/lsCompareChamfer.hpp | 6 +- .../viennals/lsCompareCriticalDimensions.hpp | 6 +- include/viennals/lsCompareNarrowBand.hpp | 6 +- python/pyWrap.hpp | 369 +++++++++--------- tests/CompareChamfer/CompareChamfer.cpp | 10 +- 6 files changed, 209 insertions(+), 215 deletions(-) diff --git a/include/viennals/lsCompareArea.hpp b/include/viennals/lsCompareArea.hpp index 0eb44f1b..6cacaeab 100644 --- a/include/viennals/lsCompareArea.hpp +++ b/include/viennals/lsCompareArea.hpp @@ -17,11 +17,11 @@ using namespace viennacore; /// Computes an estimate of the volume/area where two level sets differ. /// The volume is calculated by iterating through the bounding box of the two /// level sets and comparing the cell values. The grid delta is used as the unit -/// of volume. Custom increment values can be set for specific x, y and z ranges, -/// allowing to count certain areas multiple times or skip them. Optionally, a -/// passed mesh can be filled with the volume information, allowing for -/// visualization of the differences. -/// The code is intended for 2D and 3D level sets. +/// of volume. Custom increment values can be set for specific x, y and z +/// ranges, allowing to count certain areas multiple times or skip them. +/// Optionally, a passed mesh can be filled with the volume information, +/// allowing for visualization of the differences. The code is intended for 2D +/// and 3D level sets. template class CompareDomain { using hrleDomainType = typename Domain::DomainType; using hrleIndexType = viennahrle::IndexType; @@ -108,14 +108,12 @@ template class CompareDomain { } public: - CompareDomain() { - } + CompareDomain() {} CompareDomain(SmartPointer> passedLevelSetTarget, SmartPointer> passedLevelSetSample) : levelSetTarget(passedLevelSetTarget), - levelSetSample(passedLevelSetSample) { - } + levelSetSample(passedLevelSetSample) {} /// Sets the target level set. void setLevelSetTarget(SmartPointer> passedLevelSet) { @@ -288,9 +286,10 @@ template class CompareDomain { bool inYRange = useCustomYIncrement && (itTarget.getIndices()[1] * gridDelta >= yRangeMin && itTarget.getIndices()[1] * gridDelta <= yRangeMax); - bool inZRange = (D < 3) || (useCustomZIncrement && - (itTarget.getIndices()[2] * gridDelta >= zRangeMin && - itTarget.getIndices()[2] * gridDelta <= zRangeMax)); + bool inZRange = + (D < 3) || (useCustomZIncrement && + (itTarget.getIndices()[2] * gridDelta >= zRangeMin && + itTarget.getIndices()[2] * gridDelta <= zRangeMax)); // Calculate increment to add based on ranges unsigned short int incrementToAdd = defaultIncrement; @@ -302,9 +301,9 @@ template class CompareDomain { } else if (inYRange) { incrementToAdd = customYIncrement; } - + if (D == 3 && inZRange) { - incrementToAdd += customZIncrement; + incrementToAdd += customZIncrement; } // If cells differ, update the counters diff --git a/include/viennals/lsCompareChamfer.hpp b/include/viennals/lsCompareChamfer.hpp index ca98f964..dc34dbe6 100644 --- a/include/viennals/lsCompareChamfer.hpp +++ b/include/viennals/lsCompareChamfer.hpp @@ -73,14 +73,12 @@ template class CompareChamfer { } public: - CompareChamfer() { - } + CompareChamfer() {} CompareChamfer(SmartPointer> passedLevelSetTarget, SmartPointer> passedLevelSetSample) : levelSetTarget(passedLevelSetTarget), - levelSetSample(passedLevelSetSample) { - } + levelSetSample(passedLevelSetSample) {} /// Set the target level set void setLevelSetTarget(SmartPointer> passedLevelSet) { diff --git a/include/viennals/lsCompareCriticalDimensions.hpp b/include/viennals/lsCompareCriticalDimensions.hpp index 20dec521..98e8270d 100644 --- a/include/viennals/lsCompareCriticalDimensions.hpp +++ b/include/viennals/lsCompareCriticalDimensions.hpp @@ -135,14 +135,12 @@ template class CompareCriticalDimensions { } public: - CompareCriticalDimensions() { - } + CompareCriticalDimensions() {} CompareCriticalDimensions(SmartPointer> passedLevelSetTarget, SmartPointer> passedLevelSetSample) : levelSetTarget(passedLevelSetTarget), - levelSetSample(passedLevelSetSample) { - } + levelSetSample(passedLevelSetSample) {} void setLevelSetTarget(SmartPointer> passedLevelSet) { levelSetTarget = passedLevelSet; diff --git a/include/viennals/lsCompareNarrowBand.hpp b/include/viennals/lsCompareNarrowBand.hpp index feae7684..6b952793 100644 --- a/include/viennals/lsCompareNarrowBand.hpp +++ b/include/viennals/lsCompareNarrowBand.hpp @@ -138,14 +138,12 @@ template class CompareNarrowBand { } public: - CompareNarrowBand() { - } + CompareNarrowBand() {} CompareNarrowBand(SmartPointer> passedLevelSetTarget, SmartPointer> passedlevelSetSample) : levelSetTarget(passedLevelSetTarget), - levelSetSample(passedlevelSetSample) { - } + levelSetSample(passedlevelSetSample) {} void setLevelSetTarget(SmartPointer> passedLevelSet) { levelSetTarget = passedLevelSet; diff --git a/python/pyWrap.hpp b/python/pyWrap.hpp index 818e5496..39a1fd00 100644 --- a/python/pyWrap.hpp +++ b/python/pyWrap.hpp @@ -849,14 +849,13 @@ template void bindApi(py::module &module) { .def("apply", &Writer::apply, "Write to file."); // CompareSparseField - py::class_, - SmartPointer>>(module, - "CompareSparseField") + py::class_, SmartPointer>>( + module, "CompareSparseField") // constructors .def(py::init(&SmartPointer>::template New<>)) - .def(py::init( - &SmartPointer>::template New< - SmartPointer> &, SmartPointer> &>)) + .def( + py::init(&SmartPointer>::template New< + SmartPointer> &, SmartPointer> &>)) // methods .def("setLevelSetExpanded", &CompareSparseField::setLevelSetExpanded, @@ -943,186 +942,186 @@ template void bindApi(py::module &module) { "Make and write mesh."); #endif - // CompareArea - py::class_, SmartPointer>>( - module, "CompareArea") - // constructors - .def(py::init(&SmartPointer>::template New<>)) - .def(py::init( - &SmartPointer>::template New< - SmartPointer> &, SmartPointer> &>)) - // methods - .def("setLevelSetTarget", &CompareArea::setLevelSetTarget, - "Sets the target level set.") - .def("setLevelSetSample", &CompareArea::setLevelSetSample, - "Sets the sample level set.") - .def("setDefaultIncrement", &CompareArea::setDefaultIncrement, - "Set default increment value") - .def("setXRangeAndIncrement", &CompareArea::setXRangeAndIncrement, - "Sets the x-range and custom increment value") - .def("setYRangeAndIncrement", &CompareArea::setYRangeAndIncrement, - "Sets the y-range and custom increment value") - .def("setZRangeAndIncrement", &CompareArea::setZRangeAndIncrement, - "Sets the z-range and custom increment value") - .def("setOutputMesh", &CompareArea::setOutputMesh, - "Set the output mesh where difference areas will be stored") - .def("getAreaMismatch", &CompareArea::getAreaMismatch, - "Returns the computed area mismatch.") - .def("getCustomAreaMismatch", &CompareArea::getCustomAreaMismatch, - "Returns the computed area mismatch, with custom increments " - "applied.") - .def("getCellCount", &CompareArea::getCellCount, - "Returns the number of cells where the level sets differ.") - .def("getCustomCellCount", &CompareArea::getCustomCellCount, - "Returns the number of cells where the level sets differ, with " - "custom increments applied.") - .def("apply", &CompareArea::apply, - "Computes the area difference between the two level sets."); + // CompareArea + py::class_, SmartPointer>>(module, + "CompareArea") + // constructors + .def(py::init(&SmartPointer>::template New<>)) + .def( + py::init(&SmartPointer>::template New< + SmartPointer> &, SmartPointer> &>)) + // methods + .def("setLevelSetTarget", &CompareArea::setLevelSetTarget, + "Sets the target level set.") + .def("setLevelSetSample", &CompareArea::setLevelSetSample, + "Sets the sample level set.") + .def("setDefaultIncrement", &CompareArea::setDefaultIncrement, + "Set default increment value") + .def("setXRangeAndIncrement", &CompareArea::setXRangeAndIncrement, + "Sets the x-range and custom increment value") + .def("setYRangeAndIncrement", &CompareArea::setYRangeAndIncrement, + "Sets the y-range and custom increment value") + .def("setZRangeAndIncrement", &CompareArea::setZRangeAndIncrement, + "Sets the z-range and custom increment value") + .def("setOutputMesh", &CompareArea::setOutputMesh, + "Set the output mesh where difference areas will be stored") + .def("getAreaMismatch", &CompareArea::getAreaMismatch, + "Returns the computed area mismatch.") + .def("getCustomAreaMismatch", &CompareArea::getCustomAreaMismatch, + "Returns the computed area mismatch, with custom increments " + "applied.") + .def("getCellCount", &CompareArea::getCellCount, + "Returns the number of cells where the level sets differ.") + .def("getCustomCellCount", &CompareArea::getCustomCellCount, + "Returns the number of cells where the level sets differ, with " + "custom increments applied.") + .def("apply", &CompareArea::apply, + "Computes the area difference between the two level sets."); - // CompareChamfer - py::class_, SmartPointer>>( - module, "CompareChamfer") - // constructors - .def(py::init(&SmartPointer>::template New<>)) - .def(py::init( - &SmartPointer>::template New< - SmartPointer> &, SmartPointer> &>)) - // methods - .def("setLevelSetTarget", &CompareChamfer::setLevelSetTarget, - "Set the target level set.") - .def("setLevelSetSample", &CompareChamfer::setLevelSetSample, - "Set the sample level set.") - .def("setOutputMeshTarget", &CompareChamfer::setOutputMeshTarget, - "Set output mesh for target surface points with distance data.") - .def("setOutputMeshSample", &CompareChamfer::setOutputMeshSample, - "Set output mesh for sample surface points with distance data.") - .def("apply", &CompareChamfer::apply, - "Apply the Chamfer distance calculation.") - .def("getForwardDistance", &CompareChamfer::getForwardDistance, - "Get the forward distance (average distance from target to " - "sample).") - .def("getBackwardDistance", &CompareChamfer::getBackwardDistance, - "Get the backward distance (average distance from sample to " - "target).") - .def("getChamferDistance", &CompareChamfer::getChamferDistance, - "Get the Chamfer distance (average of forward and backward).") - .def("getRMSChamferDistance", - &CompareChamfer::getRMSChamferDistance, - "Get the RMS Chamfer distance.") - .def("getMaxDistance", &CompareChamfer::getMaxDistance, - "Get the maximum nearest-neighbor distance.") - .def("getNumTargetPoints", &CompareChamfer::getNumTargetPoints, - "Get the number of target surface points.") - .def("getNumSamplePoints", &CompareChamfer::getNumSamplePoints, - "Get the number of sample surface points."); + // CompareChamfer + py::class_, SmartPointer>>( + module, "CompareChamfer") + // constructors + .def(py::init(&SmartPointer>::template New<>)) + .def( + py::init(&SmartPointer>::template New< + SmartPointer> &, SmartPointer> &>)) + // methods + .def("setLevelSetTarget", &CompareChamfer::setLevelSetTarget, + "Set the target level set.") + .def("setLevelSetSample", &CompareChamfer::setLevelSetSample, + "Set the sample level set.") + .def("setOutputMeshTarget", &CompareChamfer::setOutputMeshTarget, + "Set output mesh for target surface points with distance data.") + .def("setOutputMeshSample", &CompareChamfer::setOutputMeshSample, + "Set output mesh for sample surface points with distance data.") + .def("apply", &CompareChamfer::apply, + "Apply the Chamfer distance calculation.") + .def("getForwardDistance", &CompareChamfer::getForwardDistance, + "Get the forward distance (average distance from target to " + "sample).") + .def("getBackwardDistance", &CompareChamfer::getBackwardDistance, + "Get the backward distance (average distance from sample to " + "target).") + .def("getChamferDistance", &CompareChamfer::getChamferDistance, + "Get the Chamfer distance (average of forward and backward).") + .def("getRMSChamferDistance", + &CompareChamfer::getRMSChamferDistance, + "Get the RMS Chamfer distance.") + .def("getMaxDistance", &CompareChamfer::getMaxDistance, + "Get the maximum nearest-neighbor distance.") + .def("getNumTargetPoints", &CompareChamfer::getNumTargetPoints, + "Get the number of target surface points.") + .def("getNumSamplePoints", &CompareChamfer::getNumSamplePoints, + "Get the number of sample surface points."); - // CompareCriticalDimensions - py::class_, - SmartPointer>>( - module, "CompareCriticalDimensions") - // constructors - .def(py::init( - &SmartPointer>::template New<>)) - .def(py::init( - &SmartPointer>::template New< - SmartPointer> &, SmartPointer> &>)) - // methods - .def("setLevelSetTarget", - &CompareCriticalDimensions::setLevelSetTarget, - "Sets the target level set.") - .def("setLevelSetSample", - &CompareCriticalDimensions::setLevelSetSample, - "Sets the sample level set.") - .def("addRange", &CompareCriticalDimensions::addRange, - py::arg("measureDimension"), py::arg("minBounds"), - py::arg("maxBounds"), py::arg("findMaximum") = true, - "Add a generic range specification.") - .def("addXRange", &CompareCriticalDimensions::addXRange, - py::arg("minX"), py::arg("maxX"), py::arg("findMaximum") = true, - "Add an X range to find maximum or minimum Y position.") - .def("addYRange", &CompareCriticalDimensions::addYRange, - py::arg("minY"), py::arg("maxY"), py::arg("findMaximum") = true, - "Add a Y range to find maximum or minimum X position.") - .def("clearRanges", &CompareCriticalDimensions::clearRanges, - "Clear all range specifications.") - .def("setOutputMesh", &CompareCriticalDimensions::setOutputMesh, - "Set the output mesh where critical dimension locations will be " - "stored.") - .def("apply", &CompareCriticalDimensions::apply, - "Apply the comparison.") - .def("getNumCriticalDimensions", - &CompareCriticalDimensions::getNumCriticalDimensions, - "Get the number of critical dimensions compared.") - .def( - "getCriticalDimensionResult", - [](CompareCriticalDimensions &self, size_t index) { - T posRef, posCmp, diff; - bool valid = - self.getCriticalDimensionResult(index, posRef, posCmp, diff); - if (valid) { - return py::make_tuple(true, posRef, posCmp, diff); - } else { - return py::make_tuple(false, 0.0, 0.0, 0.0); - } - }, - py::arg("index"), - "Get a specific critical dimension result. Returns (valid, " - "positionTarget, positionSample, difference).") - .def("getMeanDifference", - &CompareCriticalDimensions::getMeanDifference, - "Get mean absolute difference across all valid critical " - "dimensions.") - .def("getMaxDifference", - &CompareCriticalDimensions::getMaxDifference, - "Get maximum difference across all valid critical dimensions.") - .def("getRMSE", &CompareCriticalDimensions::getRMSE, - "Get RMSE across all valid critical dimensions.") - .def("getAllDifferences", - &CompareCriticalDimensions::getAllDifferences, - "Get all valid differences as a list."); + // CompareCriticalDimensions + py::class_, + SmartPointer>>( + module, "CompareCriticalDimensions") + // constructors + .def(py::init( + &SmartPointer>::template New<>)) + .def( + py::init(&SmartPointer>::template New< + SmartPointer> &, SmartPointer> &>)) + // methods + .def("setLevelSetTarget", + &CompareCriticalDimensions::setLevelSetTarget, + "Sets the target level set.") + .def("setLevelSetSample", + &CompareCriticalDimensions::setLevelSetSample, + "Sets the sample level set.") + .def("addRange", &CompareCriticalDimensions::addRange, + py::arg("measureDimension"), py::arg("minBounds"), + py::arg("maxBounds"), py::arg("findMaximum") = true, + "Add a generic range specification.") + .def("addXRange", &CompareCriticalDimensions::addXRange, + py::arg("minX"), py::arg("maxX"), py::arg("findMaximum") = true, + "Add an X range to find maximum or minimum Y position.") + .def("addYRange", &CompareCriticalDimensions::addYRange, + py::arg("minY"), py::arg("maxY"), py::arg("findMaximum") = true, + "Add a Y range to find maximum or minimum X position.") + .def("clearRanges", &CompareCriticalDimensions::clearRanges, + "Clear all range specifications.") + .def("setOutputMesh", &CompareCriticalDimensions::setOutputMesh, + "Set the output mesh where critical dimension locations will be " + "stored.") + .def("apply", &CompareCriticalDimensions::apply, + "Apply the comparison.") + .def("getNumCriticalDimensions", + &CompareCriticalDimensions::getNumCriticalDimensions, + "Get the number of critical dimensions compared.") + .def( + "getCriticalDimensionResult", + [](CompareCriticalDimensions &self, size_t index) { + T posRef, posCmp, diff; + bool valid = + self.getCriticalDimensionResult(index, posRef, posCmp, diff); + if (valid) { + return py::make_tuple(true, posRef, posCmp, diff); + } else { + return py::make_tuple(false, 0.0, 0.0, 0.0); + } + }, + py::arg("index"), + "Get a specific critical dimension result. Returns (valid, " + "positionTarget, positionSample, difference).") + .def("getMeanDifference", + &CompareCriticalDimensions::getMeanDifference, + "Get mean absolute difference across all valid critical " + "dimensions.") + .def("getMaxDifference", + &CompareCriticalDimensions::getMaxDifference, + "Get maximum difference across all valid critical dimensions.") + .def("getRMSE", &CompareCriticalDimensions::getRMSE, + "Get RMSE across all valid critical dimensions.") + .def("getAllDifferences", + &CompareCriticalDimensions::getAllDifferences, + "Get all valid differences as a list."); - // CompareNarrowBand - py::class_, SmartPointer>>( - module, "CompareNarrowBand") - // constructors - .def(py::init(&SmartPointer>::template New<>)) - .def(py::init( - &SmartPointer>::template New< - SmartPointer> &, SmartPointer> &>)) - // methods - .def("setLevelSetTarget", &CompareNarrowBand::setLevelSetTarget, - "Sets the target level set.") - .def("setLevelSetSample", &CompareNarrowBand::setLevelSetSample, - "Sets the sample level set.") - .def("setXRange", &CompareNarrowBand::setXRange, - "Set the x-coordinate range to restrict the comparison area") - .def("setYRange", &CompareNarrowBand::setYRange, - "Set the y-coordinate range to restrict the comparison area") - .def("clearXRange", &CompareNarrowBand::clearXRange, - "Clear the x-range restriction") - .def("clearYRange", &CompareNarrowBand::clearYRange, - "Clear the y-range restriction") - .def("setZRange", &CompareNarrowBand::setZRange, - "Set the z-coordinate range to restrict the comparison area") - .def("clearZRange", &CompareNarrowBand::clearZRange, - "Clear the z-range restriction") - .def("setOutputMesh", &CompareNarrowBand::setOutputMesh, - "Set the output mesh where difference values will be stored") - .def("setOutputMeshSquaredDifferences", - &CompareNarrowBand::setOutputMeshSquaredDifferences, - "Set whether to output squared differences (true) or absolute " - "differences (false)") - .def("apply", &CompareNarrowBand::apply, - "Apply the comparison and calculate the sum of squared " - "differences.") - .def("getSumSquaredDifferences", - &CompareNarrowBand::getSumSquaredDifferences, - "Return the sum of squared differences calculated by apply().") - .def("getSumDifferences", &CompareNarrowBand::getSumDifferences, - "Return the sum of absolute differences calculated by apply().") - .def("getNumPoints", &CompareNarrowBand::getNumPoints, - "Return the number of points used in the comparison.") - .def("getRMSE", &CompareNarrowBand::getRMSE, - "Calculate the root mean square error from previously computed " - "values."); + // CompareNarrowBand + py::class_, SmartPointer>>( + module, "CompareNarrowBand") + // constructors + .def(py::init(&SmartPointer>::template New<>)) + .def( + py::init(&SmartPointer>::template New< + SmartPointer> &, SmartPointer> &>)) + // methods + .def("setLevelSetTarget", &CompareNarrowBand::setLevelSetTarget, + "Sets the target level set.") + .def("setLevelSetSample", &CompareNarrowBand::setLevelSetSample, + "Sets the sample level set.") + .def("setXRange", &CompareNarrowBand::setXRange, + "Set the x-coordinate range to restrict the comparison area") + .def("setYRange", &CompareNarrowBand::setYRange, + "Set the y-coordinate range to restrict the comparison area") + .def("clearXRange", &CompareNarrowBand::clearXRange, + "Clear the x-range restriction") + .def("clearYRange", &CompareNarrowBand::clearYRange, + "Clear the y-range restriction") + .def("setZRange", &CompareNarrowBand::setZRange, + "Set the z-coordinate range to restrict the comparison area") + .def("clearZRange", &CompareNarrowBand::clearZRange, + "Clear the z-range restriction") + .def("setOutputMesh", &CompareNarrowBand::setOutputMesh, + "Set the output mesh where difference values will be stored") + .def("setOutputMeshSquaredDifferences", + &CompareNarrowBand::setOutputMeshSquaredDifferences, + "Set whether to output squared differences (true) or absolute " + "differences (false)") + .def("apply", &CompareNarrowBand::apply, + "Apply the comparison and calculate the sum of squared " + "differences.") + .def("getSumSquaredDifferences", + &CompareNarrowBand::getSumSquaredDifferences, + "Return the sum of squared differences calculated by apply().") + .def("getSumDifferences", &CompareNarrowBand::getSumDifferences, + "Return the sum of absolute differences calculated by apply().") + .def("getNumPoints", &CompareNarrowBand::getNumPoints, + "Return the number of points used in the comparison.") + .def("getRMSE", &CompareNarrowBand::getRMSE, + "Calculate the root mean square error from previously computed " + "values."); } diff --git a/tests/CompareChamfer/CompareChamfer.cpp b/tests/CompareChamfer/CompareChamfer.cpp index 9f6cf5ee..d4aa7632 100644 --- a/tests/CompareChamfer/CompareChamfer.cpp +++ b/tests/CompareChamfer/CompareChamfer.cpp @@ -124,8 +124,10 @@ template void runTest() { std::cout << " Execution time: " << chamfer_ms.count() << " ms" << std::endl; // Save meshes with distance data - ls::VTKWriter(targetMesh, "chamfer_target_distances" + suffix).apply(); - ls::VTKWriter(sampleMesh, "chamfer_sample_distances" + suffix).apply(); + ls::VTKWriter(targetMesh, "chamfer_target_distances" + suffix) + .apply(); + ls::VTKWriter(sampleMesh, "chamfer_sample_distances" + suffix) + .apply(); // Test 2: Compare with other metrics std::cout << "\n=== Test 2: Comparison with Other Metrics ===" << std::endl; @@ -161,7 +163,8 @@ template void runTest() { std::cout << "\nArea/Volume Comparison Results:" << std::endl; std::cout << " Area/Volume mismatch: " << compareDomain.getVolumeMismatch() << std::endl; - std::cout << " Different cells: " << compareDomain.getCellCount() << std::endl; + std::cout << " Different cells: " << compareDomain.getCellCount() + << std::endl; std::cout << " Execution time: " << area_ms.count() << " ms" << std::endl; // Test 3: Different geometric configurations @@ -224,7 +227,6 @@ template void runTest() { // Test 4: Performance summary std::cout << "\n=== Performance Summary ===" << std::endl; std::cout << "Chamfer distance: " << chamfer_ms.count() << " ms" << std::endl; - } int main() { From 99552b73321e0f6b6a9b069f576945a558b007b5 Mon Sep 17 00:00:00 2001 From: filipovic Date: Tue, 13 Jan 2026 13:47:59 +0100 Subject: [PATCH 41/57] use CompareVolume for 3D and CompareArea for 2D, fix minor errors, improve python tests, add changes to python wrapper and API --- PythonAPI.md | 7 +- include/viennals/lsCalculateVisibilities.hpp | 167 +-- ...{lsCompareArea.hpp => lsCompareVolume.hpp} | 30 +- include/viennals/lsDetectFeatures.hpp | 9 +- include/viennals/lsMakeGeometry.hpp | 261 +++-- lib/specialisations.cpp | 6 +- python/pyWrap.hpp | 48 +- python/tests/run_all_tests.py | 1000 +++++++++-------- python/viennals/__init__.pyi | 3 +- python/viennals/d2.pyi | 117 +- python/viennals/d3.pyi | 325 +++++- tests/CompareArea/CompareArea.cpp | 149 --- tests/CompareChamfer/CompareChamfer.cpp | 12 +- .../CMakeLists.txt | 2 +- tests/CompareVolume/CompareVolume.cpp | 179 +++ 15 files changed, 1441 insertions(+), 874 deletions(-) rename include/viennals/{lsCompareArea.hpp => lsCompareVolume.hpp} (94%) delete mode 100644 tests/CompareArea/CompareArea.cpp rename tests/{CompareArea => CompareVolume}/CMakeLists.txt (86%) create mode 100644 tests/CompareVolume/CompareVolume.cpp diff --git a/PythonAPI.md b/PythonAPI.md index 002965d9..2c335a26 100644 --- a/PythonAPI.md +++ b/PythonAPI.md @@ -18,6 +18,11 @@ They must be imported from either `viennals.d2` or `viennals.d3`. - `CalculateNormalVectors` - `CalculateVisibilities` - `Check` +- `CompareChamfer` +- `CompareCriticalDimensions` +- `CompareNarrowBand` +- `CompareSparseField` +- `CompareVolume` - `PointCloud` - `ConvexHull` - `DetectFeatures` @@ -49,8 +54,6 @@ They must be imported from either `viennals.d2` or `viennals.d3`. ### **2D-Only Functions** - `CompareArea` -- `CompareNarrowBand` -- `CompareSparseField` --- diff --git a/include/viennals/lsCalculateVisibilities.hpp b/include/viennals/lsCalculateVisibilities.hpp index d068cee0..33f10fb9 100644 --- a/include/viennals/lsCalculateVisibilities.hpp +++ b/include/viennals/lsCalculateVisibilities.hpp @@ -26,109 +26,120 @@ template class CalculateVisibilities { auto &domain = levelSet->getDomain(); auto &grid = levelSet->getGrid(); - // *** Determine extents of domain *** - Vec3D minDefinedPoint; - Vec3D maxDefinedPoint; - // Initialize with extreme values + // Invert the vector + auto dir = Normalize(Inv(direction)); + std::vector visibilities(domain.getNumberOfPoints(), static_cast(-1)); + + // Check if the direction vector has a component in the simulation + // dimensions + T dirNorm = 0; for (int i = 0; i < D; ++i) { - minDefinedPoint[i] = std::numeric_limits::max(); - maxDefinedPoint[i] = std::numeric_limits::lowest(); + dirNorm += dir[i] * dir[i]; } - // Iterate through all defined points in the domain - for (viennahrle::SparseIterator it(domain); - !it.isFinished(); it.next()) { - if (!it.isDefined()) - continue; // Skip undefined points - - // Get the coordinate of the current point - auto point = it.getStartIndices(); + + if (dirNorm < epsilon) { + std::fill(visibilities.begin(), visibilities.end(), static_cast(1.0)); + } else { + // *** Determine extents of domain *** + Vec3D minDefinedPoint; + Vec3D maxDefinedPoint; + // Initialize with extreme values for (int i = 0; i < D; ++i) { - // Compare to update min and max defined points - T coord = point[i]; // * grid.getGridDelta(); - minDefinedPoint[i] = std::min(minDefinedPoint[i], coord); - maxDefinedPoint[i] = std::max(maxDefinedPoint[i], coord); + minDefinedPoint[i] = std::numeric_limits::max(); + maxDefinedPoint[i] = std::numeric_limits::lowest(); } - } - //**************************** + // Iterate through all defined points in the domain + for (viennahrle::SparseIterator it(domain); + !it.isFinished(); it.next()) { + if (!it.isDefined()) + continue; // Skip undefined points - // Invert the vector - auto dir = Normalize(Inv(direction)); - std::vector visibilities(domain.getNumberOfPoints(), static_cast(-1)); + // Get the coordinate of the current point + auto point = it.getStartIndices(); + for (int i = 0; i < D; ++i) { + // Compare to update min and max defined points + T coord = point[i]; // * grid.getGridDelta(); + minDefinedPoint[i] = std::min(minDefinedPoint[i], coord); + maxDefinedPoint[i] = std::max(maxDefinedPoint[i], coord); + } + } + //**************************** #pragma omp parallel num_threads(levelSet->getNumberOfSegments()) - { - int p = 0; + { + int p = 0; #ifdef _OPENMP - p = omp_get_thread_num(); + p = omp_get_thread_num(); #endif - const viennahrle::Index startVector = - (p == 0) ? grid.getMinGridPoint() : domain.getSegmentation()[p - 1]; + const viennahrle::Index startVector = + (p == 0) ? grid.getMinGridPoint() : domain.getSegmentation()[p - 1]; - const viennahrle::Index endVector = - (p != static_cast(domain.getNumberOfSegments() - 1)) - ? domain.getSegmentation()[p] - : grid.incrementIndices(grid.getMaxGridPoint()); + const viennahrle::Index endVector = + (p != static_cast(domain.getNumberOfSegments() - 1)) + ? domain.getSegmentation()[p] + : grid.incrementIndices(grid.getMaxGridPoint()); - for (viennahrle::SparseIterator it(domain, startVector); - it.getStartIndices() < endVector; ++it) { + for (viennahrle::SparseIterator it(domain, startVector); + it.getStartIndices() < endVector; ++it) { - if (!it.isDefined()) - continue; + if (!it.isDefined()) + continue; - // Starting position of the point - Vec3D currentPos; - for (int i = 0; i < D; ++i) { - currentPos[i] = it.getStartIndices(i); - } - - // Start tracing the ray - T minLevelSetValue = it.getValue(); // Starting level set value - Vec3D rayPos = currentPos; - - // Step once to skip immediate neighbor - for (int i = 0; i < D; ++i) - rayPos[i] += dir[i]; + // Starting position of the point + Vec3D currentPos; + for (int i = 0; i < D; ++i) { + currentPos[i] = it.getStartIndices(i); + } - bool visibility = true; + // Start tracing the ray + T minLevelSetValue = it.getValue(); // Starting level set value + Vec3D rayPos = currentPos; - while (true) { - // Update the ray position + // Step once to skip immediate neighbor for (int i = 0; i < D; ++i) rayPos[i] += dir[i]; - // Determine the nearest grid cell (round to nearest index) - viennahrle::Index nearestCell; - for (int i = 0; i < D; ++i) - nearestCell[i] = static_cast(rayPos[i]); - - // Check if the nearest cell is within bounds - bool outOfBounds = false; - for (int i = 0; i < D; ++i) { - if (nearestCell[i] < minDefinedPoint[i] || - nearestCell[i] > maxDefinedPoint[i]) { - outOfBounds = true; - break; + bool visibility = true; + + while (true) { + // Update the ray position + for (int i = 0; i < D; ++i) + rayPos[i] += dir[i]; + + // Determine the nearest grid cell (round to nearest index) + viennahrle::Index nearestCell; + for (int i = 0; i < D; ++i) + nearestCell[i] = static_cast(rayPos[i]); + + // Check if the nearest cell is within bounds + bool outOfBounds = false; + for (int i = 0; i < D; ++i) { + if (nearestCell[i] < minDefinedPoint[i] || + nearestCell[i] > maxDefinedPoint[i]) { + outOfBounds = true; + break; + } } - } - if (outOfBounds) - break; // Ray is outside the grid + if (outOfBounds) + break; // Ray is outside the grid - // Access the level set value at the nearest cell - T value = - viennahrle::SparseIterator(domain, nearestCell) - .getValue(); + // Access the level set value at the nearest cell + T value = + viennahrle::SparseIterator(domain, nearestCell) + .getValue(); - // Update the minimum value encountered - if (value < minLevelSetValue) { - visibility = false; - break; + // Update the minimum value encountered + if (value < minLevelSetValue) { + visibility = false; + break; + } } - } - // Update visibility for this point - visibilities[it.getPointId()] = visibility ? 1.0 : 0.0; + // Update visibility for this point + visibilities[it.getPointId()] = visibility ? 1.0 : 0.0; + } } } diff --git a/include/viennals/lsCompareArea.hpp b/include/viennals/lsCompareVolume.hpp similarity index 94% rename from include/viennals/lsCompareArea.hpp rename to include/viennals/lsCompareVolume.hpp index 6cacaeab..b117fb6c 100644 --- a/include/viennals/lsCompareArea.hpp +++ b/include/viennals/lsCompareVolume.hpp @@ -22,7 +22,7 @@ using namespace viennacore; /// Optionally, a passed mesh can be filled with the volume information, /// allowing for visualization of the differences. The code is intended for 2D /// and 3D level sets. -template class CompareDomain { +template class CompareVolume { using hrleDomainType = typename Domain::DomainType; using hrleIndexType = viennahrle::IndexType; @@ -53,10 +53,17 @@ template class CompareDomain { // Mesh output related members SmartPointer> outputMesh = nullptr; + // Helper to get the class name for logging + static const char *getClassName() { + if constexpr (D == 2) + return "CompareArea"; + return "CompareVolume"; + } + bool checkAndCalculateBounds() { if (levelSetTarget == nullptr || levelSetSample == nullptr) { Logger::getInstance() - .addError("Missing level set in CompareDomain.") + .addError(std::string("Missing level set in ") + getClassName() + ".") .print(); return false; } @@ -67,8 +74,8 @@ template class CompareDomain { if (gridTarget.getGridDelta() != gridSample.getGridDelta()) { Logger::getInstance() - .addError("Grid delta mismatch in CompareDomain. The grid deltas of " - "the two level sets must be equal.") + .addError(std::string("Grid delta mismatch in ") + getClassName() + + ". The grid deltas of the two level sets must be equal.") .print(); return false; } else { @@ -108,9 +115,9 @@ template class CompareDomain { } public: - CompareDomain() {} + CompareVolume() {} - CompareDomain(SmartPointer> passedLevelSetTarget, + CompareVolume(SmartPointer> passedLevelSetTarget, SmartPointer> passedLevelSetSample) : levelSetTarget(passedLevelSetTarget), levelSetSample(passedLevelSetSample) {} @@ -214,7 +221,8 @@ template class CompareDomain { if (levelSetTarget->getLevelSetWidth() < minimumWidth) { workingTarget = SmartPointer>::New(levelSetTarget); Expand(workingTarget, minimumWidth).apply(); - VIENNACORE_LOG_INFO("CompareDomain: Expanded target level set to width " + + VIENNACORE_LOG_INFO(std::string(getClassName()) + + ": Expanded target level set to width " + std::to_string(minimumWidth) + " to avoid undefined values."); } @@ -222,7 +230,8 @@ template class CompareDomain { if (levelSetSample->getLevelSetWidth() < minimumWidth) { workingSample = SmartPointer>::New(levelSetSample); Expand(workingSample, minimumWidth).apply(); - VIENNACORE_LOG_INFO("CompareDomain: Expanded sample level set to width " + + VIENNACORE_LOG_INFO(std::string(getClassName()) + + ": Expanded sample level set to width " + std::to_string(minimumWidth) + " to avoid undefined values."); } @@ -399,10 +408,9 @@ template class CompareDomain { }; // Aliases for backward compatibility and clarity -template using CompareArea = CompareDomain; -template using CompareVolume = CompareDomain; +template using CompareArea = CompareVolume; // Precompile for common precision and dimensions -PRECOMPILE_PRECISION_DIMENSION(CompareDomain) +PRECOMPILE_PRECISION_DIMENSION(CompareVolume) } // namespace viennals diff --git a/include/viennals/lsDetectFeatures.hpp b/include/viennals/lsDetectFeatures.hpp index 537fa616..a69de7ea 100644 --- a/include/viennals/lsDetectFeatures.hpp +++ b/include/viennals/lsDetectFeatures.hpp @@ -204,9 +204,12 @@ template class DetectFeatures { bool flag = false; - for (unsigned dir = 0; dir < (D * D * D); dir++) { - Vec3D currentNormal = - normals[neighborIt.getNeighbor(dir).getPointId()]; + constexpr unsigned numNeighbors = (D == 3) ? 27 : 9; + for (unsigned dir = 0; dir < numNeighbors; dir++) { + auto neighbor = neighborIt.getNeighbor(dir); + if (!neighbor.isDefined()) + continue; + Vec3D currentNormal = normals[neighbor.getPointId()]; if (currentNormal != zeroVector) { T skp = 0.; diff --git a/include/viennals/lsMakeGeometry.hpp b/include/viennals/lsMakeGeometry.hpp index 50daf16a..3a676b29 100644 --- a/include/viennals/lsMakeGeometry.hpp +++ b/include/viennals/lsMakeGeometry.hpp @@ -419,115 +419,176 @@ template class MakeGeometry { } void makeCylinder(SmartPointer> cylinder) { - if (D != 3) { - Logger::getInstance() - .addError("MakeGeometry: Cylinder can only be created in 3D!") - .print(); - return; - } - // generate the points on the edges of the cylinders and mesh - // them manually - // cylinder axis will be (0,0,1) - auto gridDelta = levelSet->getGrid().getGridDelta(); - - auto points = PointCloud::New(); - const unsigned numPoints = - std::ceil(2 * M_PI * cylinder->radius / gridDelta); - const double smallAngle = 2.0 * M_PI / static_cast(numPoints); - - auto mesh = Mesh::New(); - // insert midpoint at base - mesh->insertNextNode(Vec3D{0.0, 0.0, 0.0}); - { - constexpr double limit = 2 * M_PI - 1e-6; - std::vector> points; - if (cylinder->topRadius) - std::vector> pointsTop; - - // create and insert points at base - for (double angle = 0.; angle < limit; angle += smallAngle) { - Vec3D point; - point[0] = cylinder->radius * std::cos(angle); - point[1] = cylinder->radius * std::sin(angle); - point[2] = 0.0; - points.push_back(point); - mesh->insertNextNode(point); - } - - // insert midpoint at top - mesh->insertNextNode(Vec3D{0.0, 0.0, cylinder->height}); - - double angle = 0; - for (unsigned i = 0; i < numPoints; ++i) { - // create triangles at base - std::array triangle{}; - triangle[0] = (i + 1) % numPoints + 1; - triangle[1] = i + 1; - triangle[2] = 0; - mesh->insertNextTriangle(triangle); + if constexpr (D == 3) { + // generate the points on the edges of the cylinders and mesh + // them manually + // cylinder axis will be (0,0,1) + auto gridDelta = levelSet->getGrid().getGridDelta(); + + auto points = PointCloud::New(); + const unsigned numPoints = + std::ceil(2 * M_PI * cylinder->radius / gridDelta); + const double smallAngle = 2.0 * M_PI / static_cast(numPoints); + + auto mesh = Mesh::New(); + // insert midpoint at base + mesh->insertNextNode(Vec3D{0.0, 0.0, 0.0}); + { + constexpr double limit = 2 * M_PI - 1e-6; + std::vector> points; + if (cylinder->topRadius) + std::vector> pointsTop; + + // create and insert points at base + for (double angle = 0.; angle < limit; angle += smallAngle) { + Vec3D point; + point[0] = cylinder->radius * std::cos(angle); + point[1] = cylinder->radius * std::sin(angle); + point[2] = 0.0; + points.push_back(point); + mesh->insertNextNode(point); + } - // insert points at top - // If topRadius is specified, update the first two coordinates of the - // points - if (cylinder->topRadius) { - points[i][0] = cylinder->topRadius * std::cos(angle); - points[i][1] = cylinder->topRadius * std::sin(angle); - angle += smallAngle; + // insert midpoint at top + mesh->insertNextNode(Vec3D{0.0, 0.0, cylinder->height}); + + double angle = 0; + for (unsigned i = 0; i < numPoints; ++i) { + // create triangles at base + std::array triangle{}; + triangle[0] = (i + 1) % numPoints + 1; + triangle[1] = i + 1; + triangle[2] = 0; + mesh->insertNextTriangle(triangle); + + // insert points at top + // If topRadius is specified, update the first two coordinates of the + // points + if (cylinder->topRadius) { + points[i][0] = cylinder->topRadius * std::cos(angle); + points[i][1] = cylinder->topRadius * std::sin(angle); + angle += smallAngle; + } + points[i][2] = cylinder->height; + mesh->insertNextNode(points[i]); + + // insert triangles at top + triangle[0] = numPoints + 1; + triangle[1] = numPoints + i + 2; + triangle[2] = (i + 1) % numPoints + 2 + numPoints; + mesh->insertNextTriangle(triangle); } - points[i][2] = cylinder->height; - mesh->insertNextNode(points[i]); - // insert triangles at top - triangle[0] = numPoints + 1; - triangle[1] = numPoints + i + 2; - triangle[2] = (i + 1) % numPoints + 2 + numPoints; - mesh->insertNextTriangle(triangle); + // insert sidewall triangles + for (unsigned i = 0; i < numPoints; ++i) { + std::array triangle{}; + triangle[0] = i + 1; + triangle[1] = (i + 1) % numPoints + 1; + triangle[2] = i + numPoints + 2; + mesh->insertNextTriangle(triangle); + + triangle[0] = (i + 1) % numPoints + 1; + triangle[1] = (i + 1) % numPoints + 2 + numPoints; + triangle[2] = i + numPoints + 2; + mesh->insertNextTriangle(triangle); + } } - // insert sidewall triangles - for (unsigned i = 0; i < numPoints; ++i) { - std::array triangle{}; - triangle[0] = i + 1; - triangle[1] = (i + 1) % numPoints + 1; - triangle[2] = i + numPoints + 2; - mesh->insertNextTriangle(triangle); - - triangle[0] = (i + 1) % numPoints + 1; - triangle[1] = (i + 1) % numPoints + 2 + numPoints; - triangle[2] = i + numPoints + 2; - mesh->insertNextTriangle(triangle); + // rotate mesh + // normalise axis vector + T unit = std::sqrt( + DotProduct(cylinder->axisDirection, cylinder->axisDirection)); + Vec3D cylinderAxis; + for (int i = 0; i < 3; ++i) { + cylinderAxis[i] = cylinder->axisDirection[i] / unit; + } + // get rotation axis via cross product of (0,0,1) and axis of cylinder + Vec3D rotAxis = {-cylinderAxis[1], cylinderAxis[0], 0.0}; + // angle is acos of dot product + T rotationAngle = std::acos(cylinderAxis[2]); + + // rotate mesh + TransformMesh(mesh, TransformEnum::ROTATION, rotAxis, rotationAngle) + .apply(); + + // translate mesh + Vec3D translationVector; + for (int i = 0; i < 3; ++i) { + translationVector[i] = cylinder->origin[i]; + } + TransformMesh(mesh, TransformEnum::TRANSLATION, translationVector) + .apply(); + + // read mesh from surface + FromSurfaceMesh mesher(levelSet, mesh); + mesher.setRemoveBoundaryTriangles(ignoreBoundaryConditions); + mesher.apply(); + } else if constexpr (D == 2) { + VIENNACORE_LOG_WARNING( + "MakeGeometry: Cylinder in 2D creates a trench, not a cylinder."); + + auto mesh = Mesh::New(); + + // Calculate axis length for normalization + T axisLen = 0; + for (unsigned i = 0; i < D; ++i) + axisLen += cylinder->axisDirection[i] * cylinder->axisDirection[i]; + axisLen = std::sqrt(axisLen); + + // Normalized axis and perpendicular vector + // In 2D, if axis is (u, v), perpendicular is (v, -u) for CCW winding + T axis[2] = {cylinder->axisDirection[0] / axisLen, + cylinder->axisDirection[1] / axisLen}; + T perp[2] = {axis[1], -axis[0]}; + + T topRadius = + (cylinder->topRadius != 0.) ? cylinder->topRadius : cylinder->radius; + + // Define the 4 corners of the rectangle/trapezoid + std::array, 4> corners; + + // Base Right + corners[0][0] = cylinder->origin[0] + cylinder->radius * perp[0]; + corners[0][1] = cylinder->origin[1] + cylinder->radius * perp[1]; + corners[0][2] = 0.; + + // Top Right + corners[1][0] = cylinder->origin[0] + cylinder->height * axis[0] + + topRadius * perp[0]; + corners[1][1] = cylinder->origin[1] + cylinder->height * axis[1] + + topRadius * perp[1]; + corners[1][2] = 0.; + + // Top Left + corners[2][0] = cylinder->origin[0] + cylinder->height * axis[0] - + topRadius * perp[0]; + corners[2][1] = cylinder->origin[1] + cylinder->height * axis[1] - + topRadius * perp[1]; + corners[2][2] = 0.; + + // Base Left + corners[3][0] = cylinder->origin[0] - cylinder->radius * perp[0]; + corners[3][1] = cylinder->origin[1] - cylinder->radius * perp[1]; + corners[3][2] = 0.; + + for (const auto &p : corners) { + mesh->insertNextNode(p); } - } - - // rotate mesh - // normalise axis vector - T unit = - std::sqrt(DotProduct(cylinder->axisDirection, cylinder->axisDirection)); - Vec3D cylinderAxis; - for (int i = 0; i < 3; ++i) { - cylinderAxis[i] = cylinder->axisDirection[i] / unit; - } - // get rotation axis via cross product of (0,0,1) and axis of cylinder - Vec3D rotAxis = {-cylinderAxis[1], cylinderAxis[0], 0.0}; - // angle is acos of dot product - T rotationAngle = std::acos(cylinderAxis[2]); - // rotate mesh - TransformMesh(mesh, TransformEnum::ROTATION, rotAxis, rotationAngle) - .apply(); + mesh->insertNextLine({0, 1}); + mesh->insertNextLine({1, 2}); + mesh->insertNextLine({2, 3}); + mesh->insertNextLine({3, 0}); - // translate mesh - Vec3D translationVector; - for (int i = 0; i < 3; ++i) { - translationVector[i] = cylinder->origin[i]; + FromSurfaceMesh mesher(levelSet, mesh); + mesher.setRemoveBoundaryTriangles(ignoreBoundaryConditions); + mesher.apply(); + } else { + Logger::getInstance() + .addError("MakeGeometry: Cylinder can only be " + "created in 2D or 3D!") + .print(); } - TransformMesh(mesh, TransformEnum::TRANSLATION, translationVector) - .apply(); - - // read mesh from surface - FromSurfaceMesh mesher(levelSet, mesh); - mesher.setRemoveBoundaryTriangles(ignoreBoundaryConditions); - mesher.apply(); } void makeCustom(SmartPointer> pointCloud) { diff --git a/lib/specialisations.cpp b/lib/specialisations.cpp index 32c4eb4e..3e82cdcb 100644 --- a/lib/specialisations.cpp +++ b/lib/specialisations.cpp @@ -9,9 +9,10 @@ #include #include #include -#include +#include #include #include +#include #include #include #include @@ -46,7 +47,8 @@ PRECOMPILE_SPECIALIZE(CalculateCurvatures) PRECOMPILE_SPECIALIZE(CalculateNormalVectors) PRECOMPILE_SPECIALIZE(Check) PRECOMPILE_SPECIALIZE(ConvexHull) -PRECOMPILE_SPECIALIZE(CompareArea) +PRECOMPILE_SPECIALIZE(CompareChamfer) +PRECOMPILE_SPECIALIZE(CompareVolume) PRECOMPILE_SPECIALIZE(CompareNarrowBand) PRECOMPILE_SPECIALIZE(CompareSparseField) PRECOMPILE_SPECIALIZE(Domain) diff --git a/python/pyWrap.hpp b/python/pyWrap.hpp index 39a1fd00..f6582d83 100644 --- a/python/pyWrap.hpp +++ b/python/pyWrap.hpp @@ -10,11 +10,11 @@ #include #include #include -#include #include #include #include #include +#include #include #include #include @@ -942,41 +942,51 @@ template void bindApi(py::module &module) { "Make and write mesh."); #endif - // CompareArea - py::class_, SmartPointer>>(module, - "CompareArea") + // CompareVolume + py::class_, SmartPointer>>( + module, "CompareVolume") // constructors - .def(py::init(&SmartPointer>::template New<>)) + .def(py::init(&SmartPointer>::template New<>)) .def( - py::init(&SmartPointer>::template New< + py::init(&SmartPointer>::template New< SmartPointer> &, SmartPointer> &>)) // methods - .def("setLevelSetTarget", &CompareArea::setLevelSetTarget, + .def("setLevelSetTarget", &CompareVolume::setLevelSetTarget, "Sets the target level set.") - .def("setLevelSetSample", &CompareArea::setLevelSetSample, + .def("setLevelSetSample", &CompareVolume::setLevelSetSample, "Sets the sample level set.") - .def("setDefaultIncrement", &CompareArea::setDefaultIncrement, + .def("setDefaultIncrement", &CompareVolume::setDefaultIncrement, "Set default increment value") - .def("setXRangeAndIncrement", &CompareArea::setXRangeAndIncrement, + .def("setXRangeAndIncrement", &CompareVolume::setXRangeAndIncrement, "Sets the x-range and custom increment value") - .def("setYRangeAndIncrement", &CompareArea::setYRangeAndIncrement, + .def("setYRangeAndIncrement", &CompareVolume::setYRangeAndIncrement, "Sets the y-range and custom increment value") - .def("setZRangeAndIncrement", &CompareArea::setZRangeAndIncrement, + .def("setZRangeAndIncrement", &CompareVolume::setZRangeAndIncrement, "Sets the z-range and custom increment value") - .def("setOutputMesh", &CompareArea::setOutputMesh, + .def("setOutputMesh", &CompareVolume::setOutputMesh, "Set the output mesh where difference areas will be stored") - .def("getAreaMismatch", &CompareArea::getAreaMismatch, + .def("getVolumeMismatch", &CompareVolume::getVolumeMismatch, + "Returns the computed volume mismatch.") + .def("getAreaMismatch", &CompareVolume::getAreaMismatch, "Returns the computed area mismatch.") - .def("getCustomAreaMismatch", &CompareArea::getCustomAreaMismatch, + .def("getCustomVolumeMismatch", + &CompareVolume::getCustomVolumeMismatch, + "Returns the computed volume mismatch, with custom increments " + "applied.") + .def("getCustomAreaMismatch", &CompareVolume::getCustomAreaMismatch, "Returns the computed area mismatch, with custom increments " "applied.") - .def("getCellCount", &CompareArea::getCellCount, + .def("getCellCount", &CompareVolume::getCellCount, "Returns the number of cells where the level sets differ.") - .def("getCustomCellCount", &CompareArea::getCustomCellCount, + .def("getCustomCellCount", &CompareVolume::getCustomCellCount, "Returns the number of cells where the level sets differ, with " "custom increments applied.") - .def("apply", &CompareArea::apply, - "Computes the area difference between the two level sets."); + .def("apply", &CompareVolume::apply, + "Computes the volume difference between the two level sets."); + + if constexpr (D == 2) { + module.attr("CompareArea") = module.attr("CompareVolume"); + } // CompareChamfer py::class_, SmartPointer>>( diff --git a/python/tests/run_all_tests.py b/python/tests/run_all_tests.py index 51510322..be29aec2 100644 --- a/python/tests/run_all_tests.py +++ b/python/tests/run_all_tests.py @@ -65,141 +65,170 @@ def test_mesh_and_data(self): print(" > Done test_mesh_and_data") - def test_3d_algorithms(self): - print("\n[TEST] test_3d_algorithms") - # Use explicit d3 module to be safe - gridDelta = 0.2 - bounds = [-10.0, 10.0, -10.0, 10.0, -10.0, 10.0] - dom = d3.Domain(bounds, [vls.BoundaryConditionEnum.REFLECTIVE_BOUNDARY] * 3, gridDelta) - - # Geometries - origin = [0.0, 0.0, 0.0] - radius = 5.0 - print(" > MakeGeometry") - sphere_geom = d3.Sphere(origin, radius) - d3.MakeGeometry(dom, sphere_geom).apply() - self.assertGreater(dom.getNumberOfPoints(), 0) - - print(" > Advect") - # Advect - vel = TestVelocityField() - adv = d3.Advect() - adv.insertNextLevelSet(dom) - adv.setVelocityField(vel) - adv.setAdvectionTime(0.1) - adv.setSpatialScheme(vls.SpatialSchemeEnum.ENGQUIST_OSHER_1ST_ORDER) - adv.setTemporalScheme(vls.TemporalSchemeEnum.FORWARD_EULER) - adv.apply() - - print(" > GeometricAdvect") - # Geometric Advect & Distributions - dist = d3.SphereDistribution(radius) - gadv = d3.GeometricAdvect(dom, dist) - gadv.apply() - - # Check other distributions instantiation - print(" > Checking Distribution instantiation") - _ = d3.BoxDistribution([1.0, 1.0, 1.0]) - _ = d3.CustomSphereDistribution([1.0, 2.0]) - - print(" > Boolean") - # Boolean - dom2 = d3.Domain(dom) - bool_op = d3.BooleanOperation(dom, dom2, vls.BooleanOperationEnum.UNION) - bool_op.apply() - - print(" > Calc Curvatures") - # Calc Curvatures - curv = d3.CalculateCurvatures(dom) - curv.apply() - - print(" > Calc Normals") - # Calc Normals - norms = d3.CalculateNormalVectors(dom) - norms.apply() - - print(" > Calc Visibilities") - # Calc Visibilities - d3.CalculateVisibilities(dom, [0.0, 0.0, 1.0], "Visibility").apply() - - print(" > Check") - # Check - d3.Check(dom).apply() - - print(" > Prune") - # Prune - d3.Prune(dom).apply() - - print(" > Expand") - # Expand - d3.Expand(dom, 1).apply() - - print(" > Reduce") - # Reduce - d3.Reduce(dom, 1).apply() - - print(" > MarkVoidPoints") - # Mark Void Points - d3.MarkVoidPoints(dom).apply() - - # Expand to ensure sufficient width for feature detection (curvature) - d3.Prune(dom).apply() - d3.Expand(dom, 5).apply() - - print(" > DetectFeatures") - # Detect Features - d3.DetectFeatures(dom).apply() - - print(" > Mesh Conversions") - # Mesh Conversions - # Expand back to a safe width for meshing - d3.Expand(dom, 3).apply() - print(" >> ToMesh") + def test_algorithms(self): + print("\n[TEST] test_algorithms (2D & 3D)") + for module, dim in [(d2, 2), (d3, 3)]: + print(f" > Testing {dim}D") + gridDelta = 0.2 + bounds = [-10.0, 10.0] * dim + dom = module.Domain(bounds, [vls.BoundaryConditionEnum.REFLECTIVE_BOUNDARY] * dim, gridDelta) + + # Geometries + origin = [0.0] * dim + radius = 5.0 + print(" >> MakeGeometry") + sphere_geom = module.Sphere(origin, radius) + module.MakeGeometry(dom, sphere_geom).apply() + self.assertGreater(dom.getNumberOfPoints(), 0) + + print(" >> Advect") + # Advect + vel = TestVelocityField() + adv = module.Advect() + adv.insertNextLevelSet(dom) + adv.setVelocityField(vel) + adv.setAdvectionTime(0.1) + adv.setSpatialScheme(vls.SpatialSchemeEnum.ENGQUIST_OSHER_1ST_ORDER) + adv.setTemporalScheme(vls.TemporalSchemeEnum.FORWARD_EULER) + adv.apply() + + print(" >> GeometricAdvect") + # Geometric Advect & Distributions + dist = module.SphereDistribution(radius) + gadv = module.GeometricAdvect(dom, dist) + gadv.apply() + + # Check other distributions instantiation + print(" >> Checking Distribution instantiation") + _ = module.BoxDistribution([1.0] * 3) # BoxDistribution always takes 3D bounds in constructor? No, usually dim. + # Checking pyWrap.hpp: BoxDistribution takes std::array in constructor? + # py::class_ ... .def(py::init(... std::array)) + # It seems BoxDistribution constructor in Python bindings expects 3 args regardless of D? + # Let's check pyWrap.hpp again. + # .def(py::init(&SmartPointer>::template New>)) + # Yes, it seems hardcoded to 3 in the binding signature for BoxDistribution. + _ = module.BoxDistribution([1.0, 1.0, 1.0]) + _ = module.CustomSphereDistribution([1.0, 2.0]) + + print(" >> Boolean") + # Boolean + dom2 = module.Domain(dom) + bool_op = module.BooleanOperation(dom, dom2, vls.BooleanOperationEnum.UNION) + bool_op.apply() + + print(" >> CompareVolume") + # CompareVolume + dom_vol1 = module.Domain(gridDelta) + module.MakeGeometry(dom_vol1, module.Sphere([0.0] * dim, 5.0)).apply() + dom_vol2 = module.Domain(gridDelta) + module.MakeGeometry(dom_vol2, module.Sphere([0.0] * dim, 6.0)).apply() + cv = module.CompareVolume(dom_vol1, dom_vol2) + cv.apply() + self.assertGreater(cv.getVolumeMismatch(), 0.0) + + print(" >> Calc Curvatures") + # Calc Curvatures + curv = module.CalculateCurvatures(dom) + curv.apply() + + print(" >> Calc Normals") + # Calc Normals + norms = module.CalculateNormalVectors(dom) + norms.apply() + + print(" >> Calc Visibilities") + # Calc Visibilities + # CalculateVisibilities expects Vec3D (3 components) even in 2D + module.CalculateVisibilities(dom, [0.0, 0.0, 1.0], "Visibility").apply() + + print(" >> Check") + # Check + module.Check(dom).apply() + + print(" >> Prune") + # Prune + module.Prune(dom).apply() + + print(" >> Expand") + # Expand + module.Expand(dom, 1).apply() + + print(" >> Reduce") + # Reduce + module.Reduce(dom, 1).apply() + + print(" >> MarkVoidPoints") + # Mark Void Points + module.MarkVoidPoints(dom).apply() + + # Expand to ensure sufficient width for feature detection (curvature) + module.Prune(dom).apply() + module.Expand(dom, 5).apply() - mesh = vls.Mesh() - print(" >> ToMesh") - d3.ToMesh(dom, mesh).apply() - print(" >> ToSurfaceMesh") - d3.ToSurfaceMesh(dom, mesh).apply() - print(" >> ToDiskMesh") - d3.ToDiskMesh(dom, mesh).apply() - - print(" > IO") - # IO - vls.VTKWriter(mesh, "test_3d.vtp").apply() - if os.path.exists("test_3d.vtp"): - os.remove("test_3d.vtp") - - print(" > RemoveStrayPoints") - # Remove Stray Points - d3.RemoveStrayPoints(dom).apply() + print(" >> DetectFeatures") + # Detect Features + module.DetectFeatures(dom).apply() + + print(" >> Mesh Conversions") + # Mesh Conversions + # Expand back to a safe width for meshing + module.Expand(dom, 3).apply() + + mesh = vls.Mesh() + print(" >>> ToMesh") + module.ToMesh(dom, mesh).apply() + print(" >>> ToSurfaceMesh") + module.ToSurfaceMesh(dom, mesh).apply() + print(" >>> ToDiskMesh") + module.ToDiskMesh(dom, mesh).apply() + + print(" >> IO") + # IO + fname = f"test_{dim}d.vtp" + vls.VTKWriter(mesh, fname).apply() + if os.path.exists(fname): + os.remove(fname) + + print(" >> RemoveStrayPoints") + # Remove Stray Points + module.RemoveStrayPoints(dom).apply() - print(" > Done test_3d_algorithms") + print(" > Done test_algorithms") def test_geometries(self): print("\n[TEST] test_geometries") - # Initialize domain with bounds to ensure MakeGeometry(Plane) works efficiently - bounds = [-10.0, 10.0, -10.0, 10.0, -10.0, 10.0] - boundaryCons = [vls.BoundaryConditionEnum.REFLECTIVE_BOUNDARY, - vls.BoundaryConditionEnum.REFLECTIVE_BOUNDARY, - vls.BoundaryConditionEnum.INFINITE_BOUNDARY] - dom = d3.Domain(bounds, boundaryCons, 0.2) - - # Plane - print(" > Plane") - plane = d3.Plane([0.0, 0.0, 0.0], [0.0, 0.0, 1.0]) - d3.MakeGeometry(dom, plane).apply() - self.assertGreater(dom.getNumberOfPoints(), 0) - - # Box - print(" > Box") - box = d3.Box([-1.0, -1.0, -1.0], [1.0, 1.0, 1.0]) - d3.MakeGeometry(dom, box).apply() - - # Cylinder - print(" > Cylinder") - cyl = d3.Cylinder([0.0,0.0,0.0], [0.0,0.0,1.0], 2.0, 1.0, 1.0) - d3.MakeGeometry(dom, cyl).apply() + for module, dim in [(d2, 2), (d3, 3)]: + print(f" > Testing {dim}D") + # Initialize domain with bounds to ensure MakeGeometry(Plane) works efficiently + bounds = [-10.0, 10.0] * dim + boundaryCons = [vls.BoundaryConditionEnum.REFLECTIVE_BOUNDARY] * dim + # Make one boundary infinite for variety + boundaryCons[-1] = vls.BoundaryConditionEnum.INFINITE_BOUNDARY + dom = module.Domain(bounds, boundaryCons, 0.2) + + # Plane + print(" >> Plane") + normal = [0.0] * dim + normal[-1] = 1.0 + plane = module.Plane([0.0] * dim, normal) + module.MakeGeometry(dom, plane).apply() + self.assertGreater(dom.getNumberOfPoints(), 0) + + # Box + print(" >> Box") + box = module.Box([-1.0] * dim, [1.0] * dim) + module.MakeGeometry(dom, box).apply() + + # Cylinder + print(" >> Cylinder") + # Cylinder constructor takes 3D vectors for origin and axis even in 2D? + # pyWrap.hpp: .def(py::init(&SmartPointer>::template New & ...>)) + # It uses std::vector, so it should adapt to D. + axis = [0.0] * dim + axis[-1] = 1.0 + cyl = module.Cylinder([0.0] * dim, axis, 2.0, 1.0, 1.0) + module.MakeGeometry(dom, cyl).apply() + print(" > Done test_geometries") def test_io_and_transforms(self): @@ -234,153 +263,182 @@ def test_io_and_transforms(self): def test_mesh_conversion_roundtrip(self): print("\n[TEST] test_mesh_conversion_roundtrip") - dom = d3.Domain(0.2) - d3.MakeGeometry(dom, d3.Sphere([0.0,0.0,0.0], 5.0)).apply() - - mesh = vls.Mesh() - d3.ToSurfaceMesh(dom, mesh).apply() - - dom2 = d3.Domain(0.2) - d3.FromSurfaceMesh(dom2, mesh).apply() - self.assertGreater(dom2.getNumberOfPoints(), 0) + for module, dim in [(d2, 2), (d3, 3)]: + print(f" > Testing {dim}D") + dom = module.Domain(0.2) + module.MakeGeometry(dom, module.Sphere([0.0]*dim, 5.0)).apply() + + mesh = vls.Mesh() + module.ToSurfaceMesh(dom, mesh).apply() + + dom2 = module.Domain(0.2) + module.FromSurfaceMesh(dom2, mesh).apply() + self.assertGreater(dom2.getNumberOfPoints(), 0) print(" > Done test_mesh_conversion_roundtrip") def test_native_io(self): print("\n[TEST] test_native_io") - dom = d3.Domain(0.2) - d3.MakeGeometry(dom, d3.Sphere([0.0, 0.0, 0.0], 5.0)).apply() - - filename = "test_native.lvst" - print(" > Writer") - d3.Writer(dom, filename).apply() - - dom_in = d3.Domain(0.2) - print(" > Reader") - d3.Reader(dom_in, filename).apply() - self.assertGreater(dom_in.getNumberOfPoints(), 0) - - if os.path.exists(filename): - os.remove(filename) + for module, dim in [(d2, 2), (d3, 3)]: + print(f" > Testing {dim}D") + dom = module.Domain(0.2) + module.MakeGeometry(dom, module.Sphere([0.0]*dim, 5.0)).apply() + + filename = f"test_native_{dim}d.lvst" + print(" >> Writer") + module.Writer(dom, filename).apply() + + dom_in = module.Domain(0.2) + print(" >> Reader") + module.Reader(dom_in, filename).apply() + self.assertGreater(dom_in.getNumberOfPoints(), 0) + + if os.path.exists(filename): + os.remove(filename) print(" > Done test_native_io") def test_advanced_meshing(self): print("\n[TEST] test_advanced_meshing") - dom1 = d3.Domain(0.2) - d3.MakeGeometry(dom1, d3.Sphere([0.0, 0.0, 0.0], 5.0)).apply() - - dom2 = d3.Domain(0.2) - d3.MakeGeometry(dom2, d3.Sphere([2.0, 0.0, 0.0], 5.0)).apply() - - # ToMultiSurfaceMesh - print(" > ToMultiSurfaceMesh") - mesh_multi = vls.Mesh() - d3.ToMultiSurfaceMesh([dom1, dom2], mesh_multi).apply() - self.assertGreater(len(mesh_multi.getNodes()), 0) - - # ToVoxelMesh - print(" > ToVoxelMesh") - mesh_voxel = vls.Mesh() - d3.ToVoxelMesh([dom1, dom2], mesh_voxel).apply() - self.assertGreater(len(mesh_voxel.getNodes()), 0) + for module, dim in [(d2, 2), (d3, 3)]: + print(f" > Testing {dim}D") + dom1 = module.Domain(0.2) + module.MakeGeometry(dom1, module.Sphere([0.0]*dim, 5.0)).apply() + + dom2 = module.Domain(0.2) + center = [0.0]*dim + center[0] = 2.0 + module.MakeGeometry(dom2, module.Sphere(center, 5.0)).apply() + + # ToMultiSurfaceMesh + print(" >> ToMultiSurfaceMesh") + mesh_multi = vls.Mesh() + module.ToMultiSurfaceMesh([dom1, dom2], mesh_multi).apply() + self.assertGreater(len(mesh_multi.getNodes()), 0) + + # ToVoxelMesh + print(" >> ToVoxelMesh") + mesh_voxel = vls.Mesh() + module.ToVoxelMesh([dom1, dom2], mesh_voxel).apply() + self.assertGreater(len(mesh_voxel.getNodes()), 0) print(" > Done test_advanced_meshing") def test_stencil_functions(self): print("\n[TEST] test_stencil_functions") - dom = d3.Domain(0.2) - d3.MakeGeometry(dom, d3.Sphere([0.0, 0.0, 0.0], 5.0)).apply() - d3.Expand(dom, 5).apply() - - # Create a second domain to form a valid multi-layer system - dom2 = d3.Domain(0.2) - d3.MakeGeometry(dom2, d3.Sphere([0.0, 0.0, 0.0], 10.0)).apply() - d3.Expand(dom2, 5).apply() - - # PrepareStencilLocalLaxFriedrichs - print(" > PrepareStencilLocalLaxFriedrichs") - d3.PrepareStencilLocalLaxFriedrichs([dom, dom2], [False, True]) - - # Finalize - print(" > FinalizeStencilLocalLaxFriedrichs") - d3.FinalizeStencilLocalLaxFriedrichs([dom, dom2]) + for module, dim in [(d2, 2), (d3, 3)]: + print(f" > Testing {dim}D") + dom = module.Domain(0.2) + module.MakeGeometry(dom, module.Sphere([0.0]*dim, 5.0)).apply() + module.Expand(dom, 5).apply() + + # Create a second domain to form a valid multi-layer system + dom2 = module.Domain(0.2) + module.MakeGeometry(dom2, module.Sphere([0.0]*dim, 10.0)).apply() + module.Expand(dom2, 5).apply() + + # PrepareStencilLocalLaxFriedrichs + print(" >> PrepareStencilLocalLaxFriedrichs") + module.PrepareStencilLocalLaxFriedrichs([dom, dom2], [False, True]) + + # Finalize + print(" >> FinalizeStencilLocalLaxFriedrichs") + module.FinalizeStencilLocalLaxFriedrichs([dom, dom2]) print(" > Done test_stencil_functions") def test_geometric_advect_box(self): print("\n[TEST] test_geometric_advect_box") - dom = d3.Domain(0.2) - d3.MakeGeometry(dom, d3.Sphere([0.0, 0.0, 0.0], 5.0)).apply() - - print(" > BoxDistribution") - dist = d3.BoxDistribution([1.0, 1.0, 1.0]) - - print(" > GeometricAdvect") - d3.GeometricAdvect(dom, dist).apply() - self.assertGreater(dom.getNumberOfPoints(), 0) + for module, dim in [(d2, 2), (d3, 3)]: + print(f" > Testing {dim}D") + dom = module.Domain(0.2) + module.MakeGeometry(dom, module.Sphere([0.0]*dim, 5.0)).apply() + + print(" >> BoxDistribution") + # BoxDistribution constructor in Python bindings expects 3 args regardless of D + dist = module.BoxDistribution([1.0, 1.0, 1.0]) + + print(" >> GeometricAdvect") + module.GeometricAdvect(dom, dist).apply() + self.assertGreater(dom.getNumberOfPoints(), 0) print(" > Done test_geometric_advect_box") def test_from_mesh_volume(self): print("\n[TEST] test_from_mesh_volume") - # Create a simple tetrahedral mesh - mesh = vls.Mesh() - mesh.insertNextNode([0.0, 0.0, 0.0]) - mesh.insertNextNode([5.0, 0.0, 0.0]) - mesh.insertNextNode([0.0, 5.0, 0.0]) - mesh.insertNextNode([0.0, 0.0, 5.0]) - mesh.insertNextTetra([0, 1, 2, 3]) - - # FromMesh requires "LSValues" in cellData corresponding to nodes - ls_values = [-1.0, 1.0, 1.0, 1.0] - mesh.getPointData().insertNextScalarData(ls_values, "LSValues") - - dom = d3.Domain(0.2) - # FromMesh should handle volume meshes if implemented in the wrapper logic - print(" > FromMesh (Volume)") - d3.FromMesh(dom, mesh).apply() - self.assertGreater(dom.getNumberOfPoints(), 0) + for module, dim in [(d2, 2), (d3, 3)]: + print(f" > Testing {dim}D") + mesh = vls.Mesh() + if dim == 3: + # Create a simple tetrahedral mesh + mesh.insertNextNode([0.0, 0.0, 0.0]) + mesh.insertNextNode([5.0, 0.0, 0.0]) + mesh.insertNextNode([0.0, 5.0, 0.0]) + mesh.insertNextNode([0.0, 0.0, 5.0]) + mesh.insertNextTetra([0, 1, 2, 3]) + ls_values = [-1.0, 1.0, 1.0, 1.0] + else: + # Create a simple triangle mesh for 2D volume + mesh.insertNextNode([0.0, 0.0, 0.0]) + mesh.insertNextNode([5.0, 0.0, 0.0]) + mesh.insertNextNode([0.0, 5.0, 0.0]) + mesh.insertNextTriangle([0, 1, 2]) + ls_values = [-1.0, 1.0, 1.0] + + # FromMesh requires "LSValues" in cellData corresponding to nodes + mesh.getPointData().insertNextScalarData(ls_values, "LSValues") + + dom = module.Domain(0.2) + # FromMesh should handle volume meshes if implemented in the wrapper logic + print(" >> FromMesh (Volume)") + module.FromMesh(dom, mesh).apply() + self.assertGreater(dom.getNumberOfPoints(), 0) print(" > Done test_from_mesh_volume") def test_boolean_variants(self): print("\n[TEST] test_boolean_variants") - dom1 = d3.Domain(0.2) - d3.MakeGeometry(dom1, d3.Sphere([0.0, 0.0, 0.0], 5.0)).apply() - - dom2 = d3.Domain(0.2) - d3.MakeGeometry(dom2, d3.Sphere([2.0, 0.0, 0.0], 5.0)).apply() - - # Intersect - print(" > Intersect") - dom_int = d3.Domain(dom1) - d3.BooleanOperation(dom_int, dom2, vls.BooleanOperationEnum.INTERSECT).apply() - self.assertGreater(dom_int.getNumberOfPoints(), 0) - - # Relative Complement (Difference) - print(" > Relative Complement") - dom_diff = d3.Domain(dom1) - d3.BooleanOperation(dom_diff, dom2, vls.BooleanOperationEnum.RELATIVE_COMPLEMENT).apply() - self.assertGreater(dom_diff.getNumberOfPoints(), 0) - - # Invert - print(" > Invert") - dom_inv = d3.Domain(dom1) - d3.BooleanOperation(dom_inv, vls.BooleanOperationEnum.INVERT).apply() - # Inverted domain is infinite, but points should exist - self.assertGreater(dom_inv.getNumberOfPoints(), 0) + for module, dim in [(d2, 2), (d3, 3)]: + print(f" > Testing {dim}D") + dom1 = module.Domain(0.2) + module.MakeGeometry(dom1, module.Sphere([0.0]*dim, 5.0)).apply() + + dom2 = module.Domain(0.2) + center = [0.0]*dim + center[0] = 2.0 + module.MakeGeometry(dom2, module.Sphere(center, 5.0)).apply() + + # Intersect + print(" >> Intersect") + dom_int = module.Domain(dom1) + module.BooleanOperation(dom_int, dom2, vls.BooleanOperationEnum.INTERSECT).apply() + self.assertGreater(dom_int.getNumberOfPoints(), 0) + + # Relative Complement (Difference) + print(" >> Relative Complement") + dom_diff = module.Domain(dom1) + module.BooleanOperation(dom_diff, dom2, vls.BooleanOperationEnum.RELATIVE_COMPLEMENT).apply() + self.assertGreater(dom_diff.getNumberOfPoints(), 0) + + # Invert + print(" >> Invert") + dom_inv = module.Domain(dom1) + module.BooleanOperation(dom_inv, vls.BooleanOperationEnum.INVERT).apply() + # Inverted domain is infinite, but points should exist + self.assertGreater(dom_inv.getNumberOfPoints(), 0) print(" > Done test_boolean_variants") def test_domain_manipulation(self): print("\n[TEST] test_domain_manipulation") - dom = d3.Domain(0.2) - d3.MakeGeometry(dom, d3.Sphere([0.0, 0.0, 0.0], 5.0)).apply() - - # Deep Copy - print(" > deepCopy") - dom_copy = d3.Domain(0.2) - dom_copy.deepCopy(dom) - self.assertEqual(dom.getNumberOfPoints(), dom_copy.getNumberOfPoints()) - - # Level Set Width - print(" > setLevelSetWidth") - dom.setLevelSetWidth(3) - self.assertEqual(dom.getLevelSetWidth(), 3) + for module, dim in [(d2, 2), (d3, 3)]: + print(f" > Testing {dim}D") + dom = module.Domain(0.2) + module.MakeGeometry(dom, module.Sphere([0.0]*dim, 5.0)).apply() + + # Deep Copy + print(" >> deepCopy") + dom_copy = module.Domain(0.2) + dom_copy.deepCopy(dom) + self.assertEqual(dom.getNumberOfPoints(), dom_copy.getNumberOfPoints()) + + # Level Set Width + print(" >> setLevelSetWidth") + dom.setLevelSetWidth(3) + self.assertEqual(dom.getLevelSetWidth(), 3) print(" > Done test_domain_manipulation") def test_vtk_metadata(self): @@ -409,38 +467,39 @@ def test_write_visualization_mesh(self): print(" > WriteVisualizationMesh not available (VIENNALS_USE_VTK off?)") print(" > Done test_write_visualization_mesh") - def test_2d_algorithms(self): - print("\n[TEST] test_2d_algorithms") - # Use explicit d2 module - gridDelta = 0.2 - gridDelta = 0.5 - print(" > Creating 2D domains") - dom = d2.Domain(gridDelta) - - # Make Geometry - d2.MakeGeometry(dom, d2.Sphere([0.0, 0.0], 5.0)).apply() - - dom2 = d2.Domain(dom) - # Shift dom2 slightly - d2.MakeGeometry(dom2, d2.Sphere([0.1, 0.0], 5.0)).apply() - - print(" > Comparisons") - # Comparisons - d2.CompareArea(dom, dom2).apply() - d2.CompareChamfer(dom, dom2).apply() - - comp_crit = d2.CompareCriticalDimensions(dom, dom2) - comp_crit.addXRange(-1.0, 1.0, True) - comp_crit.apply() - - d2.CompareNarrowBand(dom, dom2).apply() - - print(" > Sparse Field") - # Sparse Field (needs expanded/reduced) - dom_exp = d2.Domain(dom) - d2.Expand(dom_exp, 55).apply() - d2.CompareSparseField(dom_exp, dom).apply() - print(" > Done test_2d_algorithms") + def test_comparisons(self): + print("\n[TEST] test_comparisons") + for module, dim in [(d2, 2), (d3, 3)]: + print(f" > Testing {dim}D") + gridDelta = 0.5 + dom = module.Domain(gridDelta) + + # Make Geometry + module.MakeGeometry(dom, module.Sphere([0.0]*dim, 5.0)).apply() + + dom2 = module.Domain(dom) + # Shift dom2 slightly + center = [0.0]*dim + center[0] = 0.1 + module.MakeGeometry(dom2, module.Sphere(center, 5.0)).apply() + + print(" >> Comparisons") + # Comparisons + module.CompareVolume(dom, dom2).apply() + module.CompareChamfer(dom, dom2).apply() + + comp_crit = module.CompareCriticalDimensions(dom, dom2) + comp_crit.addXRange(-1.0, 1.0, True) + comp_crit.apply() + + module.CompareNarrowBand(dom, dom2).apply() + + print(" >> Sparse Field") + # Sparse Field (needs expanded/reduced) + dom_exp = module.Domain(dom) + module.Expand(dom_exp, 55).apply() + module.CompareSparseField(dom_exp, dom).apply() + print(" > Done test_comparisons") def test_cross_dimension(self): print("\n[TEST] test_cross_dimension") @@ -470,58 +529,65 @@ def test_cross_dimension(self): def test_point_cloud_convex_hull(self): print("\n[TEST] test_point_cloud_convex_hull") - # PointCloud - print(" > Creating PointCloud") - points = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] - pc = d3.PointCloud(points) - - # ConvexHull - print(" > ConvexHull") - mesh = vls.Mesh() - hull = d3.ConvexHull(mesh, pc) - hull.apply() - self.assertGreater(len(mesh.getNodes()), 0) + for module, dim in [(d2, 2), (d3, 3)]: + print(f" > Testing {dim}D") + # PointCloud + print(" >> Creating PointCloud") + # Create points on axes + points = [[0.0]*dim] + [[1.0 if i == j else 0.0 for i in range(dim)] for j in range(dim)] + pc = module.PointCloud(points) + + # ConvexHull + print(" >> ConvexHull") + mesh = vls.Mesh() + hull = module.ConvexHull(mesh, pc) + hull.apply() + self.assertGreater(len(mesh.getNodes()), 0) print(" > Done test_point_cloud_convex_hull") def test_advection_callback(self): print("\n[TEST] test_advection_callback") - print(" > Setting up advection") - dom = d3.Domain(0.2) - d3.MakeGeometry(dom, d3.Sphere([0.0, 0.0, 0.0], 5.0)).apply() - - vel = TestVelocityField() - adv = d3.Advect() - adv.insertNextLevelSet(dom) - adv.setVelocityField(vel) - adv.setAdvectionTime(0.1) - adv.setTemporalScheme(vls.TemporalSchemeEnum.RUNGE_KUTTA_2ND_ORDER) - - # Use a mutable container to capture callback execution - callback_data = {'called': False} - def callback(d): - callback_data['called'] = True - return True - - print(" > Running advection with callback") - adv.setVelocityUpdateCallback(callback) - adv.apply() - - self.assertTrue(callback_data['called']) + for module, dim in [(d2, 2), (d3, 3)]: + print(f" > Testing {dim}D") + print(" >> Setting up advection") + dom = module.Domain(0.2) + module.MakeGeometry(dom, module.Sphere([0.0]*dim, 5.0)).apply() + + vel = TestVelocityField() + adv = module.Advect() + adv.insertNextLevelSet(dom) + adv.setVelocityField(vel) + adv.setAdvectionTime(0.1) + adv.setTemporalScheme(vls.TemporalSchemeEnum.RUNGE_KUTTA_2ND_ORDER) + + # Use a mutable container to capture callback execution + callback_data = {'called': False} + def callback(d): + callback_data['called'] = True + return True + + print(" >> Running advection with callback") + adv.setVelocityUpdateCallback(callback) + adv.apply() + + self.assertTrue(callback_data['called']) print(" > Done test_advection_callback") def test_make_geometry_boundary(self): print("\n[TEST] test_make_geometry_boundary") - print(" > Creating domain with bounds") - bounds = [-5.0, 5.0, -5.0, 5.0, -5.0, 5.0] - boundaryCons = [vls.BoundaryConditionEnum.REFLECTIVE_BOUNDARY] * 3 - dom = d3.Domain(bounds, boundaryCons, 1.0) - - print(" > MakeGeometry with IgnoreBoundaryConditions") - maker = d3.MakeGeometry(dom, d3.Sphere([0.0, 0.0, 0.0], 4.0)) - maker.setIgnoreBoundaryConditions(True) - maker.apply() - - self.assertGreater(dom.getNumberOfPoints(), 0) + for module, dim in [(d2, 2), (d3, 3)]: + print(f" > Testing {dim}D") + print(" >> Creating domain with bounds") + bounds = [-5.0, 5.0] * dim + boundaryCons = [vls.BoundaryConditionEnum.REFLECTIVE_BOUNDARY] * dim + dom = module.Domain(bounds, boundaryCons, 1.0) + + print(" >> MakeGeometry with IgnoreBoundaryConditions") + maker = module.MakeGeometry(dom, module.Sphere([0.0]*dim, 4.0)) + maker.setIgnoreBoundaryConditions(True) + maker.apply() + + self.assertGreater(dom.getNumberOfPoints(), 0) print(" > Done test_make_geometry_boundary") def test_mesh_operations(self): @@ -548,150 +614,186 @@ def test_mesh_operations(self): def test_advect_configuration(self): print("\n[TEST] test_advect_configuration") - print(" > Creating domain") - dom = d3.Domain(0.2) - d3.MakeGeometry(dom, d3.Sphere([0.0, 0.0, 0.0], 5.0)).apply() - - vel = TestVelocityField() - adv = d3.Advect() - adv.insertNextLevelSet(dom) - adv.setVelocityField(vel) - - # Test Single Step - print(" > Testing Single Step") - adv.setAdvectionTime(10.0) # Large time that would normally require multiple steps - adv.setSingleStep(True) - adv.apply() - self.assertEqual(adv.getNumberOfTimeSteps(), 1) + for module, dim in [(d2, 2), (d3, 3)]: + print(f" > Testing {dim}D") + print(" >> Creating domain") + dom = module.Domain(0.2) + module.MakeGeometry(dom, module.Sphere([0.0]*dim, 5.0)).apply() + + vel = TestVelocityField() + adv = module.Advect() + adv.insertNextLevelSet(dom) + adv.setVelocityField(vel) + + # Test Single Step + print(" >> Testing Single Step") + adv.setAdvectionTime(10.0) # Large time that would normally require multiple steps + adv.setSingleStep(True) + adv.apply() + self.assertEqual(adv.getNumberOfTimeSteps(), 1) print(" > Done test_advect_configuration") def test_mark_void_points_advanced(self): print("\n[TEST] test_mark_void_points_advanced") - print(" > Creating disjoint spheres") - dom = d3.Domain(0.2) - # Create two disjoint spheres - d3.MakeGeometry(dom, d3.Sphere([0.0, 0.0, 0.0], 3.0)).apply() - - dom2 = d3.Domain(0.2) - d3.MakeGeometry(dom2, d3.Sphere([10.0, 0.0, 0.0], 3.0)).apply() - - # Union them to create a domain with two separate components - print(" > Union") - d3.BooleanOperation(dom, dom2, vls.BooleanOperationEnum.UNION).apply() - - print(" > MarkVoidPoints") - marker = d3.MarkVoidPoints(dom) - marker.setSaveComponentIds(True) - marker.apply() - - # Should find 3 connected components (2 spheres + 1 background) - self.assertEqual(marker.getNumberOfComponents(), 3) + for module, dim in [(d2, 2), (d3, 3)]: + print(f" > Testing {dim}D") + print(" >> Creating disjoint spheres") + dom = module.Domain(0.2) + # Create two disjoint spheres + module.MakeGeometry(dom, module.Sphere([0.0]*dim, 3.0)).apply() + + dom2 = module.Domain(0.2) + center = [0.0]*dim + center[0] = 10.0 + module.MakeGeometry(dom2, module.Sphere(center, 3.0)).apply() + + # Union them to create a domain with two separate components + print(" >> Union") + module.BooleanOperation(dom, dom2, vls.BooleanOperationEnum.UNION).apply() + + print(" >> MarkVoidPoints") + marker = module.MarkVoidPoints(dom) + marker.setSaveComponentIds(True) + marker.apply() + + # Should find 3 connected components (2 spheres + 1 background) + self.assertEqual(marker.getNumberOfComponents(), 3) print(" > Done test_mark_void_points_advanced") def test_adaptive_time_stepping(self): print("\n[TEST] test_adaptive_time_stepping") - print(" > Setting up advection") - dom = d3.Domain(0.2) - d3.MakeGeometry(dom, d3.Sphere([0.0, 0.0, 0.0], 5.0)).apply() - - vel = TestVelocityField() - adv = d3.Advect() - adv.insertNextLevelSet(dom) - adv.setVelocityField(vel) - adv.setAdvectionTime(0.1) - - # Enable adaptive time stepping - print(" > Enabling Adaptive Time Stepping") - adv.setAdaptiveTimeStepping(True, 10) - adv.apply() - - self.assertGreater(dom.getNumberOfPoints(), 0) + for module, dim in [(d2, 2), (d3, 3)]: + print(f" > Testing {dim}D") + print(" >> Setting up advection") + dom = module.Domain(0.2) + module.MakeGeometry(dom, module.Sphere([0.0]*dim, 5.0)).apply() + + vel = TestVelocityField() + adv = module.Advect() + adv.insertNextLevelSet(dom) + adv.setVelocityField(vel) + adv.setAdvectionTime(0.1) + + # Enable adaptive time stepping + print(" >> Enabling Adaptive Time Stepping") + adv.setAdaptiveTimeStepping(True, 10) + adv.apply() + + self.assertGreater(dom.getNumberOfPoints(), 0) print(" > Done test_adaptive_time_stepping") def test_geometric_distribution_functions(self): print("\n[TEST] test_geometric_distribution_functions") - print(" > SphereDistribution") - # Sphere Distribution - dist = d3.SphereDistribution(5.0) - # Point inside (0,0,0) relative to center (0,0,0) - print(" > Checking isInside") - self.assertTrue(dist.isInside([0.0, 0.0, 0.0], [0.0, 0.0, 0.0], 1e-6)) - # Point outside (6,0,0) relative to center (0,0,0) - self.assertFalse(dist.isInside([0.0, 0.0, 0.0], [6.0, 0.0, 0.0], 1e-6)) - - # Signed Distance - print(" > Checking getSignedDistance") - # At 0,0,0 distance should be -5.0 - dist_val = dist.getSignedDistance([0.0, 0.0, 0.0], [0.0, 0.0, 0.0], 0) - self.assertAlmostEqual(dist_val, -5.0, delta=1e-5) + for module, dim in [(d2, 2), (d3, 3)]: + print(f" > Testing {dim}D") + print(" >> SphereDistribution") + # Sphere Distribution + dist = module.SphereDistribution(5.0) + # Point inside (0,0,0) relative to center (0,0,0) + print(" >> Checking isInside") + # isInside takes 3D points in bindings? + # pyWrap.hpp: .def("isInside", &GeometricAdvectDistribution::isInside) + # GeometricAdvectDistribution uses std::array in pyWrap.hpp for PylsGeometricAdvectDistribution trampoline + # but the base class is templated on D. + # However, the trampoline class PylsGeometricAdvectDistribution hardcodes vectorType to std::array. + # This suggests that even for 2D, the distribution functions might expect 3D coordinates in the Python binding layer if they go through the trampoline. + # But SphereDistribution is a direct binding. + # Let's assume it takes 3D points because of the trampoline definition in pyWrap.hpp which seems to force 3D for the virtual methods. + # Wait, SphereDistribution binding doesn't use the trampoline unless it inherits from it in Python, which it doesn't here. + # But the C++ class SphereDistribution uses VectorType. + # Let's try passing 3D points to be safe, as extra dimensions are usually ignored in 2D logic if passed as std::array to a function expecting std::array? No, pybind11 would complain. + # Actually, looking at pyWrap.hpp, PylsGeometricAdvectDistribution is defined with `typedef std::array vectorType;` + # This looks like a bug/limitation in the bindings for 2D if it enforces 3D. + # However, let's try using `[0.0]*3` which is safe for 3D and might work for 2D if the binding expects 3 args. + # If the binding expects 2 args for 2D, `[0.0]*3` will fail. + # Let's check `GeometricAdvectDistribution` binding in `pyWrap.hpp`. + # It uses `PylsGeometricAdvectDistribution` as trampoline. + # `PylsGeometricAdvectDistribution` has `typedef std::array vectorType;` regardless of D. + # This strongly suggests that the Python side expects 3-element lists/tuples even for 2D distributions. + + pt_zero = [0.0] * 3 + pt_out = [0.0] * 3 + pt_out[0] = 6.0 + + self.assertTrue(dist.isInside(pt_zero, pt_zero, 1e-6)) + self.assertFalse(dist.isInside(pt_zero, pt_out, 1e-6)) + + # Signed Distance + print(" >> Checking getSignedDistance") + dist_val = dist.getSignedDistance(pt_zero, pt_zero, 0) + self.assertAlmostEqual(dist_val, -5.0, delta=1e-5) print(" > Done test_geometric_distribution_functions") def test_advect_additional_options(self): print("\n[TEST] test_advect_additional_options") - print(" > Setting up advection") - dom = d3.Domain(0.2) - d3.MakeGeometry(dom, d3.Sphere([0.0, 0.0, 0.0], 5.0)).apply() - - vel = TestVelocityField() - adv = d3.Advect() - adv.insertNextLevelSet(dom) - adv.setVelocityField(vel) - adv.setAdvectionTime(0.1) - - print(" > Setting IgnoreVoids") - # Test Ignore Voids - adv.setIgnoreVoids(True) - - print(" > Setting DissipationAlpha") - # Test Dissipation Alpha - adv.setDissipationAlpha(0.5) - - print(" > Setting CheckDissipation") - # Test Check Dissipation - adv.setCheckDissipation(False) - - adv.apply() - - self.assertGreater(dom.getNumberOfPoints(), 0) + for module, dim in [(d2, 2), (d3, 3)]: + print(f" > Testing {dim}D") + print(" >> Setting up advection") + dom = module.Domain(0.2) + module.MakeGeometry(dom, module.Sphere([0.0]*dim, 5.0)).apply() + + vel = TestVelocityField() + adv = module.Advect() + adv.insertNextLevelSet(dom) + adv.setVelocityField(vel) + adv.setAdvectionTime(0.1) + + print(" >> Setting IgnoreVoids") + # Test Ignore Voids + adv.setIgnoreVoids(True) + + print(" >> Setting DissipationAlpha") + # Test Dissipation Alpha + adv.setDissipationAlpha(0.5) + + print(" >> Setting CheckDissipation") + # Test Check Dissipation + adv.setCheckDissipation(False) + + adv.apply() + + self.assertGreater(dom.getNumberOfPoints(), 0) print(" > Done test_advect_additional_options") def test_additional_options(self): print("\n[TEST] test_additional_options") - print(" > Creating domain with bounds") - bounds = [-10.0, 10.0, -10.0, 10.0, -10.0, 10.0] - boundaryCons = [vls.BoundaryConditionEnum.REFLECTIVE_BOUNDARY] * 3 - dom = d3.Domain(bounds, boundaryCons, 0.2) - d3.MakeGeometry(dom, d3.Sphere([0.0, 0.0, 0.0], 5.0)).apply() - - # ToMesh options - print(" > ToMesh options (OnlyDefined, OnlyActive)") - mesh = vls.Mesh() - tomesh = d3.ToMesh(dom, mesh) - tomesh.setOnlyDefined(True) - tomesh.setOnlyActive(True) - tomesh.apply() - self.assertGreater(len(mesh.getNodes()), 0) - - # DetectFeatures options - print(" > DetectFeatures options (Threshold, Method)") - df = d3.DetectFeatures(dom) - df.setDetectionThreshold(10.0) - df.setDetectionMethod(vls.FeatureDetectionEnum.NORMALS_ANGLE) - df.apply() - - # MarkVoidPoints options - print(" > MarkVoidPoints options (Reverse, LargestSurface)") - mvp = d3.MarkVoidPoints(dom) - mvp.setReverseVoidDetection(True) - mvp.setDetectLargestSurface(True) - mvp.apply() - - # RemoveStrayPoints options - print(" > RemoveStrayPoints options (VoidTopSurface)") - rsp = d3.RemoveStrayPoints(dom) - rsp.setVoidTopSurface(vls.VoidTopSurfaceEnum.LARGEST) - rsp.apply() - + for module, dim in [(d2, 2), (d3, 3)]: + print(f" > Testing {dim}D") + print(" >> Creating domain with bounds") + bounds = [-10.0, 10.0] * dim + boundaryCons = [vls.BoundaryConditionEnum.REFLECTIVE_BOUNDARY] * dim + dom = module.Domain(bounds, boundaryCons, 0.2) + module.MakeGeometry(dom, module.Sphere([0.0]*dim, 5.0)).apply() + + # ToMesh options + print(" >> ToMesh options (OnlyDefined, OnlyActive)") + mesh = vls.Mesh() + tomesh = module.ToMesh(dom, mesh) + tomesh.setOnlyDefined(True) + tomesh.setOnlyActive(True) + tomesh.apply() + self.assertGreater(len(mesh.getNodes()), 0) + + # DetectFeatures options + print(" >> DetectFeatures options (Threshold, Method)") + df = module.DetectFeatures(dom) + df.setDetectionThreshold(10.0) + df.setDetectionMethod(vls.FeatureDetectionEnum.NORMALS_ANGLE) + df.apply() + + # MarkVoidPoints options + print(" >> MarkVoidPoints options (Reverse, LargestSurface)") + mvp = module.MarkVoidPoints(dom) + mvp.setReverseVoidDetection(True) + mvp.setDetectLargestSurface(True) + mvp.apply() + + # RemoveStrayPoints options + print(" >> RemoveStrayPoints options (VoidTopSurface)") + rsp = module.RemoveStrayPoints(dom) + rsp.setVoidTopSurface(vls.VoidTopSurfaceEnum.LARGEST) + rsp.apply() + print(" > Done test_additional_options") def run_test_method(method_name): diff --git a/python/viennals/__init__.pyi b/python/viennals/__init__.pyi index 79b08550..54bc1a29 100644 --- a/python/viennals/__init__.pyi +++ b/python/viennals/__init__.pyi @@ -42,6 +42,7 @@ from viennals.d2 import CalculateNormalVectors from viennals.d2 import CalculateVisibilities from viennals.d2 import Check from viennals.d2 import CompareArea +from viennals.d2 import CompareVolume from viennals.d2 import CompareChamfer from viennals.d2 import CompareCriticalDimensions from viennals.d2 import CompareNarrowBand @@ -81,7 +82,7 @@ from viennals.d2 import hrleGrid from . import _core from . import d2 from . import d3 -__all__: list[str] = ['Advect', 'BooleanOperation', 'BooleanOperationEnum', 'BoundaryConditionEnum', 'Box', 'BoxDistribution', 'CalculateCurvatures', 'CalculateNormalVectors', 'CalculateVisibilities', 'Check', 'CompareArea', 'CompareChamfer', 'CompareCriticalDimensions', 'CompareNarrowBand', 'CompareSparseField', 'ConvexHull', 'CurvatureEnum', 'CustomSphereDistribution', 'Cylinder', 'DetectFeatures', 'Domain', 'Expand', 'Extrude', 'FeatureDetectionEnum', 'FileFormatEnum', 'FinalizeStencilLocalLaxFriedrichs', 'FromMesh', 'FromSurfaceMesh', 'FromVolumeMesh', 'GeometricAdvect', 'GeometricAdvectDistribution', 'IntegrationSchemeEnum', 'LogLevel', 'Logger', 'MakeGeometry', 'MarkVoidPoints', 'MaterialMap', 'Mesh', 'PROXY_DIM', 'Plane', 'PointCloud', 'PointData', 'PrepareStencilLocalLaxFriedrichs', 'Prune', 'Reader', 'Reduce', 'RemoveStrayPoints', 'Slice', 'SpatialSchemeEnum', 'Sphere', 'SphereDistribution', 'StencilLocalLaxFriedrichsScalar', 'TemporalSchemeEnum', 'ToDiskMesh', 'ToMesh', 'ToMultiSurfaceMesh', 'ToSurfaceMesh', 'ToVoxelMesh', 'TransformEnum', 'TransformMesh', 'VTKReader', 'VTKRenderWindow', 'VTKWriter', 'VelocityField', 'VoidTopSurfaceEnum', 'WriteVisualizationMesh', 'Writer', 'd2', 'd3', 'getDimension', 'hrleGrid', 'setDimension', 'setNumThreads', 'version'] +__all__: list[str] = ['Advect', 'BooleanOperation', 'BooleanOperationEnum', 'BoundaryConditionEnum', 'Box', 'BoxDistribution', 'CalculateCurvatures', 'CalculateNormalVectors', 'CalculateVisibilities', 'Check', 'CompareArea', 'CompareChamfer', 'CompareCriticalDimensions', 'CompareNarrowBand', 'CompareSparseField', 'CompareVolume', 'ConvexHull', 'CurvatureEnum', 'CustomSphereDistribution', 'Cylinder', 'DetectFeatures', 'Domain', 'Expand', 'Extrude', 'FeatureDetectionEnum', 'FileFormatEnum', 'FinalizeStencilLocalLaxFriedrichs', 'FromMesh', 'FromSurfaceMesh', 'FromVolumeMesh', 'GeometricAdvect', 'GeometricAdvectDistribution', 'IntegrationSchemeEnum', 'LogLevel', 'Logger', 'MakeGeometry', 'MarkVoidPoints', 'MaterialMap', 'Mesh', 'PROXY_DIM', 'Plane', 'PointCloud', 'PointData', 'PrepareStencilLocalLaxFriedrichs', 'Prune', 'Reader', 'Reduce', 'RemoveStrayPoints', 'Slice', 'SpatialSchemeEnum', 'Sphere', 'SphereDistribution', 'StencilLocalLaxFriedrichsScalar', 'TemporalSchemeEnum', 'ToDiskMesh', 'ToMesh', 'ToMultiSurfaceMesh', 'ToSurfaceMesh', 'ToVoxelMesh', 'TransformEnum', 'TransformMesh', 'VTKReader', 'VTKRenderWindow', 'VTKWriter', 'VelocityField', 'VoidTopSurfaceEnum', 'WriteVisualizationMesh', 'Writer', 'd2', 'd3', 'getDimension', 'hrleGrid', 'setDimension', 'setNumThreads', 'version'] def __dir__(): ... def __getattr__(name): diff --git a/python/viennals/d2.pyi b/python/viennals/d2.pyi index d79668d9..5d0953fb 100644 --- a/python/viennals/d2.pyi +++ b/python/viennals/d2.pyi @@ -5,7 +5,7 @@ from __future__ import annotations import collections.abc import typing import viennals._core -__all__: list[str] = ['Advect', 'BooleanOperation', 'Box', 'BoxDistribution', 'CalculateCurvatures', 'CalculateNormalVectors', 'CalculateVisibilities', 'Check', 'CompareArea', 'CompareChamfer', 'CompareCriticalDimensions', 'CompareNarrowBand', 'CompareSparseField', 'ConvexHull', 'CustomSphereDistribution', 'Cylinder', 'DetectFeatures', 'Domain', 'Expand', 'FinalizeStencilLocalLaxFriedrichs', 'FromMesh', 'FromSurfaceMesh', 'FromVolumeMesh', 'GeometricAdvect', 'GeometricAdvectDistribution', 'MakeGeometry', 'MarkVoidPoints', 'Plane', 'PointCloud', 'PrepareStencilLocalLaxFriedrichs', 'Prune', 'Reader', 'Reduce', 'RemoveStrayPoints', 'Sphere', 'SphereDistribution', 'StencilLocalLaxFriedrichsScalar', 'ToDiskMesh', 'ToMesh', 'ToMultiSurfaceMesh', 'ToSurfaceMesh', 'ToVoxelMesh', 'WriteVisualizationMesh', 'Writer', 'hrleGrid'] +__all__: list[str] = ['Advect', 'BooleanOperation', 'Box', 'BoxDistribution', 'CalculateCurvatures', 'CalculateNormalVectors', 'CalculateVisibilities', 'Check', 'CompareArea', 'CompareChamfer', 'CompareCriticalDimensions', 'CompareNarrowBand', 'CompareSparseField', 'CompareVolume', 'ConvexHull', 'CustomSphereDistribution', 'Cylinder', 'DetectFeatures', 'Domain', 'Expand', 'FinalizeStencilLocalLaxFriedrichs', 'FromMesh', 'FromSurfaceMesh', 'FromVolumeMesh', 'GeometricAdvect', 'GeometricAdvectDistribution', 'MakeGeometry', 'MarkVoidPoints', 'Plane', 'PointCloud', 'PrepareStencilLocalLaxFriedrichs', 'Prune', 'Reader', 'Reduce', 'RemoveStrayPoints', 'Sphere', 'SphereDistribution', 'StencilLocalLaxFriedrichsScalar', 'ToDiskMesh', 'ToMesh', 'ToMultiSurfaceMesh', 'ToSurfaceMesh', 'ToVoxelMesh', 'WriteVisualizationMesh', 'Writer', 'hrleGrid'] class Advect: @typing.overload def __init__(self) -> None: @@ -225,57 +225,6 @@ class Check: """ Set levelset for which to calculate normal vectors. """ -class CompareArea: - @typing.overload - def __init__(self) -> None: - ... - @typing.overload - def __init__(self, arg0: Domain, arg1: Domain) -> None: - ... - def apply(self) -> None: - """ - Computes the area difference between the two level sets. - """ - def getAreaMismatch(self) -> float: - """ - Returns the computed area mismatch. - """ - def getCellCount(self) -> int: - """ - Returns the number of cells where the level sets differ. - """ - def getCustomAreaMismatch(self) -> float: - """ - Returns the computed area mismatch, with custom increments applied. - """ - def getCustomCellCount(self) -> int: - """ - Returns the number of cells where the level sets differ, with custom increments applied. - """ - def setDefaultIncrement(self, arg0: typing.SupportsInt) -> None: - """ - Set default increment value - """ - def setLevelSetSample(self, arg0: Domain) -> None: - """ - Sets the sample level set. - """ - def setLevelSetTarget(self, arg0: Domain) -> None: - """ - Sets the target level set. - """ - def setOutputMesh(self, arg0: viennals._core.Mesh) -> None: - """ - Set the output mesh where difference areas will be stored - """ - def setXRangeAndIncrement(self, arg0: typing.SupportsInt, arg1: typing.SupportsInt, arg2: typing.SupportsInt) -> None: - """ - Sets the x-range and custom increment value - """ - def setYRangeAndIncrement(self, arg0: typing.SupportsInt, arg1: typing.SupportsInt, arg2: typing.SupportsInt) -> None: - """ - Sets the y-range and custom increment value - """ class CompareChamfer: @typing.overload def __init__(self) -> None: @@ -516,6 +465,70 @@ class CompareSparseField: """ Set the y-coordinate range to restrict the comparison area """ +class CompareVolume: + @typing.overload + def __init__(self) -> None: + ... + @typing.overload + def __init__(self, arg0: Domain, arg1: Domain) -> None: + ... + def apply(self) -> None: + """ + Computes the volume difference between the two level sets. + """ + def getAreaMismatch(self) -> float: + """ + Returns the computed area mismatch. + """ + def getCellCount(self) -> int: + """ + Returns the number of cells where the level sets differ. + """ + def getCustomAreaMismatch(self) -> float: + """ + Returns the computed area mismatch, with custom increments applied. + """ + def getCustomCellCount(self) -> int: + """ + Returns the number of cells where the level sets differ, with custom increments applied. + """ + def getCustomVolumeMismatch(self) -> float: + """ + Returns the computed volume mismatch, with custom increments applied. + """ + def getVolumeMismatch(self) -> float: + """ + Returns the computed volume mismatch. + """ + def setDefaultIncrement(self, arg0: typing.SupportsInt) -> None: + """ + Set default increment value + """ + def setLevelSetSample(self, arg0: Domain) -> None: + """ + Sets the sample level set. + """ + def setLevelSetTarget(self, arg0: Domain) -> None: + """ + Sets the target level set. + """ + def setOutputMesh(self, arg0: viennals._core.Mesh) -> None: + """ + Set the output mesh where difference areas will be stored + """ + def setXRangeAndIncrement(self, arg0: typing.SupportsInt, arg1: typing.SupportsInt, arg2: typing.SupportsInt) -> None: + """ + Sets the x-range and custom increment value + """ + def setYRangeAndIncrement(self, arg0: typing.SupportsInt, arg1: typing.SupportsInt, arg2: typing.SupportsInt) -> None: + """ + Sets the y-range and custom increment value + """ + def setZRangeAndIncrement(self, arg0: typing.SupportsInt, arg1: typing.SupportsInt, arg2: typing.SupportsInt) -> None: + """ + Sets the z-range and custom increment value + """ +CompareArea = CompareVolume class ConvexHull: @typing.overload def __init__(self) -> None: diff --git a/python/viennals/d3.pyi b/python/viennals/d3.pyi index 495756b0..e66039d8 100644 --- a/python/viennals/d3.pyi +++ b/python/viennals/d3.pyi @@ -5,7 +5,7 @@ from __future__ import annotations import collections.abc import typing import viennals._core -__all__: list[str] = ['Advect', 'BooleanOperation', 'Box', 'BoxDistribution', 'CalculateCurvatures', 'CalculateNormalVectors', 'CalculateVisibilities', 'Check', 'ConvexHull', 'CustomSphereDistribution', 'Cylinder', 'DetectFeatures', 'Domain', 'Expand', 'FinalizeStencilLocalLaxFriedrichs', 'FromMesh', 'FromSurfaceMesh', 'FromVolumeMesh', 'GeometricAdvect', 'GeometricAdvectDistribution', 'MakeGeometry', 'MarkVoidPoints', 'Plane', 'PointCloud', 'PrepareStencilLocalLaxFriedrichs', 'Prune', 'Reader', 'Reduce', 'RemoveStrayPoints', 'Sphere', 'SphereDistribution', 'StencilLocalLaxFriedrichsScalar', 'ToDiskMesh', 'ToMesh', 'ToMultiSurfaceMesh', 'ToSurfaceMesh', 'ToVoxelMesh', 'WriteVisualizationMesh', 'Writer', 'hrleGrid'] +__all__: list[str] = ['Advect', 'BooleanOperation', 'Box', 'BoxDistribution', 'CalculateCurvatures', 'CalculateNormalVectors', 'CalculateVisibilities', 'Check', 'CompareChamfer', 'CompareCriticalDimensions', 'CompareNarrowBand', 'CompareSparseField', 'CompareVolume', 'ConvexHull', 'CustomSphereDistribution', 'Cylinder', 'DetectFeatures', 'Domain', 'Expand', 'FinalizeStencilLocalLaxFriedrichs', 'FromMesh', 'FromSurfaceMesh', 'FromVolumeMesh', 'GeometricAdvect', 'GeometricAdvectDistribution', 'MakeGeometry', 'MarkVoidPoints', 'Plane', 'PointCloud', 'PrepareStencilLocalLaxFriedrichs', 'Prune', 'Reader', 'Reduce', 'RemoveStrayPoints', 'Sphere', 'SphereDistribution', 'StencilLocalLaxFriedrichsScalar', 'ToDiskMesh', 'ToMesh', 'ToMultiSurfaceMesh', 'ToSurfaceMesh', 'ToVoxelMesh', 'WriteVisualizationMesh', 'Writer', 'hrleGrid'] class Advect: @typing.overload def __init__(self) -> None: @@ -225,6 +225,329 @@ class Check: """ Set levelset for which to calculate normal vectors. """ +class CompareChamfer: + @typing.overload + def __init__(self) -> None: + ... + @typing.overload + def __init__(self, arg0: Domain, arg1: Domain) -> None: + ... + def apply(self) -> None: + """ + Apply the Chamfer distance calculation. + """ + def getBackwardDistance(self) -> float: + """ + Get the backward distance (average distance from sample to target). + """ + def getChamferDistance(self) -> float: + """ + Get the Chamfer distance (average of forward and backward). + """ + def getForwardDistance(self) -> float: + """ + Get the forward distance (average distance from target to sample). + """ + def getMaxDistance(self) -> float: + """ + Get the maximum nearest-neighbor distance. + """ + def getNumSamplePoints(self) -> int: + """ + Get the number of sample surface points. + """ + def getNumTargetPoints(self) -> int: + """ + Get the number of target surface points. + """ + def getRMSChamferDistance(self) -> float: + """ + Get the RMS Chamfer distance. + """ + def setLevelSetSample(self, arg0: Domain) -> None: + """ + Set the sample level set. + """ + def setLevelSetTarget(self, arg0: Domain) -> None: + """ + Set the target level set. + """ + def setOutputMeshSample(self, arg0: viennals._core.Mesh) -> None: + """ + Set output mesh for sample surface points with distance data. + """ + def setOutputMeshTarget(self, arg0: viennals._core.Mesh) -> None: + """ + Set output mesh for target surface points with distance data. + """ +class CompareCriticalDimensions: + @typing.overload + def __init__(self) -> None: + ... + @typing.overload + def __init__(self, arg0: Domain, arg1: Domain) -> None: + ... + def addRange(self, measureDimension: typing.SupportsInt, minBounds: typing.SupportsFloat, maxBounds: typing.SupportsFloat, findMaximum: bool = True) -> None: + """ + Add a generic range specification. + """ + def addXRange(self, minX: typing.SupportsFloat, maxX: typing.SupportsFloat, findMaximum: bool = True) -> None: + """ + Add an X range to find maximum or minimum Y position. + """ + def addYRange(self, minY: typing.SupportsFloat, maxY: typing.SupportsFloat, findMaximum: bool = True) -> None: + """ + Add a Y range to find maximum or minimum X position. + """ + def apply(self) -> None: + """ + Apply the comparison. + """ + def clearRanges(self) -> None: + """ + Clear all range specifications. + """ + def getAllDifferences(self) -> list[float]: + """ + Get all valid differences as a list. + """ + def getCriticalDimensionResult(self, index: typing.SupportsInt) -> tuple: + """ + Get a specific critical dimension result. Returns (valid, positionTarget, positionSample, difference). + """ + def getMaxDifference(self) -> float: + """ + Get maximum difference across all valid critical dimensions. + """ + def getMeanDifference(self) -> float: + """ + Get mean absolute difference across all valid critical dimensions. + """ + def getNumCriticalDimensions(self) -> int: + """ + Get the number of critical dimensions compared. + """ + def getRMSE(self) -> float: + """ + Get RMSE across all valid critical dimensions. + """ + def setLevelSetSample(self, arg0: Domain) -> None: + """ + Sets the sample level set. + """ + def setLevelSetTarget(self, arg0: Domain) -> None: + """ + Sets the target level set. + """ + def setOutputMesh(self, arg0: viennals._core.Mesh) -> None: + """ + Set the output mesh where critical dimension locations will be stored. + """ +class CompareNarrowBand: + @typing.overload + def __init__(self) -> None: + ... + @typing.overload + def __init__(self, arg0: Domain, arg1: Domain) -> None: + ... + def apply(self) -> None: + """ + Apply the comparison and calculate the sum of squared differences. + """ + def clearXRange(self) -> None: + """ + Clear the x-range restriction + """ + def clearYRange(self) -> None: + """ + Clear the y-range restriction + """ + def clearZRange(self) -> None: + """ + Clear the z-range restriction + """ + def getNumPoints(self) -> int: + """ + Return the number of points used in the comparison. + """ + def getRMSE(self) -> float: + """ + Calculate the root mean square error from previously computed values. + """ + def getSumDifferences(self) -> float: + """ + Return the sum of absolute differences calculated by apply(). + """ + def getSumSquaredDifferences(self) -> float: + """ + Return the sum of squared differences calculated by apply(). + """ + def setLevelSetSample(self, arg0: Domain) -> None: + """ + Sets the sample level set. + """ + def setLevelSetTarget(self, arg0: Domain) -> None: + """ + Sets the target level set. + """ + def setOutputMesh(self, arg0: viennals._core.Mesh, arg1: bool) -> None: + """ + Set the output mesh where difference values will be stored + """ + def setOutputMeshSquaredDifferences(self, arg0: bool) -> None: + """ + Set whether to output squared differences (true) or absolute differences (false) + """ + def setXRange(self, arg0: typing.SupportsFloat, arg1: typing.SupportsFloat) -> None: + """ + Set the x-coordinate range to restrict the comparison area + """ + def setYRange(self, arg0: typing.SupportsFloat, arg1: typing.SupportsFloat) -> None: + """ + Set the y-coordinate range to restrict the comparison area + """ + def setZRange(self, arg0: typing.SupportsFloat, arg1: typing.SupportsFloat) -> None: + """ + Set the z-coordinate range to restrict the comparison area + """ +class CompareSparseField: + @typing.overload + def __init__(self) -> None: + ... + @typing.overload + def __init__(self, arg0: Domain, arg1: Domain) -> None: + ... + def apply(self) -> None: + """ + Apply the comparison and calculate the sum of squared differences. + """ + def clearXRange(self) -> None: + """ + Clear the x-range restriction + """ + def clearYRange(self) -> None: + """ + Clear the y-range restriction + """ + def clearZRange(self) -> None: + """ + Clear the z-range restriction + """ + def getNumPoints(self) -> int: + """ + Return the number of points used in the comparison. + """ + def getNumSkippedPoints(self) -> int: + """ + Return the number of points skipped during comparison. + """ + def getRMSE(self) -> float: + """ + Calculate the root mean square error from previously computed values. + """ + def getSumDifferences(self) -> float: + """ + Return the sum of absolute differences calculated by apply(). + """ + def getSumSquaredDifferences(self) -> float: + """ + Return the sum of squared differences calculated by apply(). + """ + def setExpandedLevelSetWidth(self, arg0: typing.SupportsInt) -> None: + """ + Set the expansion width for the expanded level set + """ + def setFillIteratedWithDistances(self, arg0: bool) -> None: + """ + Set whether to fill the iterated level set with distance values + """ + def setLevelSetExpanded(self, arg0: Domain) -> None: + """ + Sets the expanded level set for comparison. + """ + def setLevelSetIterated(self, arg0: Domain) -> None: + """ + Sets the iterated level set to compare against the expanded one. + """ + def setOutputMesh(self, arg0: viennals._core.Mesh) -> None: + """ + Set the output mesh where difference values will be stored + """ + def setXRange(self, arg0: typing.SupportsFloat, arg1: typing.SupportsFloat) -> None: + """ + Set the x-coordinate range to restrict the comparison area + """ + def setYRange(self, arg0: typing.SupportsFloat, arg1: typing.SupportsFloat) -> None: + """ + Set the y-coordinate range to restrict the comparison area + """ + def setZRange(self, arg0: typing.SupportsFloat, arg1: typing.SupportsFloat) -> None: + """ + Set the z-coordinate range to restrict the comparison area + """ +class CompareVolume: + @typing.overload + def __init__(self) -> None: + ... + @typing.overload + def __init__(self, arg0: Domain, arg1: Domain) -> None: + ... + def apply(self) -> None: + """ + Computes the volume difference between the two level sets. + """ + def getAreaMismatch(self) -> float: + """ + Returns the computed area mismatch. + """ + def getCellCount(self) -> int: + """ + Returns the number of cells where the level sets differ. + """ + def getCustomAreaMismatch(self) -> float: + """ + Returns the computed area mismatch, with custom increments applied. + """ + def getCustomCellCount(self) -> int: + """ + Returns the number of cells where the level sets differ, with custom increments applied. + """ + def getCustomVolumeMismatch(self) -> float: + """ + Returns the computed volume mismatch, with custom increments applied. + """ + def getVolumeMismatch(self) -> float: + """ + Returns the computed volume mismatch. + """ + def setDefaultIncrement(self, arg0: typing.SupportsInt) -> None: + """ + Set default increment value + """ + def setLevelSetSample(self, arg0: Domain) -> None: + """ + Sets the sample level set. + """ + def setLevelSetTarget(self, arg0: Domain) -> None: + """ + Sets the target level set. + """ + def setOutputMesh(self, arg0: viennals._core.Mesh) -> None: + """ + Set the output mesh where difference areas will be stored + """ + def setXRangeAndIncrement(self, arg0: typing.SupportsInt, arg1: typing.SupportsInt, arg2: typing.SupportsInt) -> None: + """ + Sets the x-range and custom increment value + """ + def setYRangeAndIncrement(self, arg0: typing.SupportsInt, arg1: typing.SupportsInt, arg2: typing.SupportsInt) -> None: + """ + Sets the y-range and custom increment value + """ + def setZRangeAndIncrement(self, arg0: typing.SupportsInt, arg1: typing.SupportsInt, arg2: typing.SupportsInt) -> None: + """ + Sets the z-range and custom increment value + """ class ConvexHull: @typing.overload def __init__(self) -> None: diff --git a/tests/CompareArea/CompareArea.cpp b/tests/CompareArea/CompareArea.cpp deleted file mode 100644 index e1599748..00000000 --- a/tests/CompareArea/CompareArea.cpp +++ /dev/null @@ -1,149 +0,0 @@ -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -/** - Test for lsCompareArea that compares the area difference between two level - sets. This test creates two different spheres and measures their area - difference. \example CompareArea.cpp -*/ - -namespace ls = viennals; - -template void runTest() { - std::cout << "Running " << D << "D Test..." << std::endl; - double extent = 15; - double gridDelta = 0.5; - - double bounds[2 * D]; - for (int i = 0; i < 2 * D; ++i) - bounds[i] = (i % 2 == 0) ? -extent : extent; - - typename ls::Domain::BoundaryType boundaryCons[D]; - for (unsigned i = 0; i < D; ++i) - boundaryCons[i] = ls::Domain::BoundaryType::REFLECTIVE_BOUNDARY; - - // Create first sphere (target) - auto sphere1 = ls::SmartPointer>::New( - bounds, boundaryCons, gridDelta); - - std::vector origin1(D, 0.0); - double radius1 = 5.0; - - ls::MakeGeometry( - sphere1, ls::SmartPointer>::New(origin1, radius1)) - .apply(); - - // Create second sphere (sample) with different radius - auto sphere2 = ls::SmartPointer>::New( - bounds, boundaryCons, gridDelta); - - std::vector origin2(D, 0.0); - double radius2 = 8.0; - - ls::MakeGeometry( - sphere2, ls::SmartPointer>::New(origin2, radius2)) - .apply(); - - std::string suffix = "_" + std::to_string(D) + "D.vtp"; - // Export both spheres as VTK files for visualization - { - auto mesh = ls::SmartPointer>::New(); - ls::ToMesh(sphere1, mesh).apply(); - ls::VTKWriter(mesh, "sphere1" + suffix).apply(); - } - - { - auto mesh = ls::SmartPointer>::New(); - ls::ToMesh(sphere2, mesh).apply(); - ls::VTKWriter(mesh, "sphere2" + suffix).apply(); - } - - // Compare the volumes/areas with mesh Output - // Using the new general name CompareDomain (CompareArea is now an alias) - ls::CompareDomain compareDomain(sphere1, sphere2); - auto mesh = ls::SmartPointer>::New(); // Create mesh for output - compareDomain.setOutputMesh(mesh); - compareDomain.apply(); - // save mesh to file - ls::VTKWriter(mesh, "volumeDifference" + suffix + ".vtu").apply(); - - // Calculate theoretical difference - double theoreticalDiff = 0.0; - if constexpr (D == 2) { - // Area of circle = Ï€ * r² - double area1 = M_PI * radius1 * radius1; - double area2 = M_PI * radius2 * radius2; - theoreticalDiff = std::abs(area2 - area1); - } else if constexpr (D == 3) { - // Volume of sphere = 4/3 * Ï€ * r³ - double vol1 = (4.0 / 3.0) * M_PI * std::pow(radius1, 3); - double vol2 = (4.0 / 3.0) * M_PI * std::pow(radius2, 3); - theoreticalDiff = std::abs(vol2 - vol1); - } - - // Get the calculated difference - double calculatedDifference = compareDomain.getVolumeMismatch(); - unsigned long int cellCount = compareDomain.getCellCount(); - - std::cout << "Sphere 1 radius: " << radius1 << std::endl; - std::cout << "Sphere 2 radius: " << radius2 << std::endl; - std::cout << "Theoretical difference: " << theoreticalDiff << std::endl; - std::cout << "Calculated difference: " << calculatedDifference << std::endl; - std::cout << "Number of differing cells: " << cellCount << std::endl; - std::cout << "Error: " << std::abs(calculatedDifference - theoreticalDiff) - << std::endl; - - // Test custom increment and range functionality - std::cout << "\nTesting custom increments and ranges:" << std::endl; - // Set custom increment for whole domain - compareDomain.setDefaultIncrement(2); - compareDomain.apply(); - std::cout << "Difference with default increment of 2: " - << compareDomain.getCustomVolumeMismatch() << std::endl; - std::cout << "Cell count with default increment of 2: " - << compareDomain.getCustomCellCount() << std::endl; - - // Set range-specific increment for x-range - compareDomain.setDefaultIncrement(1); - compareDomain.setXRangeAndIncrement(-5, 5, 3); - compareDomain.apply(); - std::cout << "Difference with x-range increment of 3: " - << compareDomain.getCustomVolumeMismatch() << std::endl; - std::cout << "Cell count with x-range increment of 3: " - << compareDomain.getCustomCellCount() << std::endl; - - // Set range-specific increment for y-range - compareDomain.setDefaultIncrement(1); - compareDomain.setYRangeAndIncrement(-5, 5, 4); - compareDomain.apply(); - std::cout << "Difference with y-range increment of 4: " - << compareDomain.getCustomVolumeMismatch() << std::endl; - std::cout << "Cell count with y-range increment of 4: " - << compareDomain.getCustomCellCount() << std::endl; - - if constexpr (D == 3) { - // Set range-specific increment for z-range - compareDomain.setDefaultIncrement(1); - compareDomain.setZRangeAndIncrement(-5, 5, 5); - compareDomain.apply(); - std::cout << "Difference with z-range increment of 5: " - << compareDomain.getCustomVolumeMismatch() << std::endl; - std::cout << "Cell count with z-range increment of 5: " - << compareDomain.getCustomCellCount() << std::endl; - } -} - -int main() { - omp_set_num_threads(4); - runTest<2>(); - runTest<3>(); - return 0; -} diff --git a/tests/CompareChamfer/CompareChamfer.cpp b/tests/CompareChamfer/CompareChamfer.cpp index d4aa7632..0db957fb 100644 --- a/tests/CompareChamfer/CompareChamfer.cpp +++ b/tests/CompareChamfer/CompareChamfer.cpp @@ -3,9 +3,9 @@ #include #include -#include #include #include +#include #include #include #include @@ -152,18 +152,18 @@ template void runTest() { << std::endl; std::cout << " Execution time: " << sparse_ms.count() << " ms" << std::endl; - // Area comparison - ls::CompareDomain compareDomain(sphere1, sphere2); + // Area/Volume comparison + ls::CompareVolume compareVolume(sphere1, sphere2); auto t5 = std::chrono::high_resolution_clock::now(); - compareDomain.apply(); + compareVolume.apply(); auto t6 = std::chrono::high_resolution_clock::now(); std::chrono::duration area_ms = t6 - t5; std::cout << "\nArea/Volume Comparison Results:" << std::endl; - std::cout << " Area/Volume mismatch: " << compareDomain.getVolumeMismatch() + std::cout << " Area/Volume mismatch: " << compareVolume.getVolumeMismatch() << std::endl; - std::cout << " Different cells: " << compareDomain.getCellCount() + std::cout << " Different cells: " << compareVolume.getCellCount() << std::endl; std::cout << " Execution time: " << area_ms.count() << " ms" << std::endl; diff --git a/tests/CompareArea/CMakeLists.txt b/tests/CompareVolume/CMakeLists.txt similarity index 86% rename from tests/CompareArea/CMakeLists.txt rename to tests/CompareVolume/CMakeLists.txt index fdf14a4e..3311cd62 100644 --- a/tests/CompareArea/CMakeLists.txt +++ b/tests/CompareVolume/CMakeLists.txt @@ -1,4 +1,4 @@ -project(CompareArea LANGUAGES CXX) +project(CompareVolume LANGUAGES CXX) add_executable(${PROJECT_NAME} "${PROJECT_NAME}.cpp") target_link_libraries(${PROJECT_NAME} PRIVATE ViennaLS) diff --git a/tests/CompareVolume/CompareVolume.cpp b/tests/CompareVolume/CompareVolume.cpp new file mode 100644 index 00000000..e951d56c --- /dev/null +++ b/tests/CompareVolume/CompareVolume.cpp @@ -0,0 +1,179 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/** + Test for lsCompareArea that compares the area difference between two level + sets. This test creates two different spheres and measures their area + difference. \example CompareArea.cpp +*/ + +namespace ls = viennals; + +template void runTest() { + std::cout << "Running " << D << "D Test..." << std::endl; + double extent = 15; + double gridDelta = 0.5; + + double bounds[2 * D]; + for (int i = 0; i < 2 * D; ++i) + bounds[i] = (i % 2 == 0) ? -extent : extent; + + typename ls::Domain::BoundaryType boundaryCons[D]; + for (unsigned i = 0; i < D; ++i) + boundaryCons[i] = ls::Domain::BoundaryType::REFLECTIVE_BOUNDARY; + + // Create first sphere (target) + auto sphere1 = ls::SmartPointer>::New( + bounds, boundaryCons, gridDelta); + + std::vector origin1(D, 0.0); + double radius1 = 5.0; + + ls::MakeGeometry( + sphere1, ls::SmartPointer>::New(origin1, radius1)) + .apply(); + + // Create second sphere (sample) with different radius + auto sphere2 = ls::SmartPointer>::New( + bounds, boundaryCons, gridDelta); + + std::vector origin2(D, 0.0); + double radius2 = 8.0; + + ls::MakeGeometry( + sphere2, ls::SmartPointer>::New(origin2, radius2)) + .apply(); + + std::string suffix = "_" + std::to_string(D) + "D.vtp"; + // Export both spheres as VTK files for visualization + { + auto mesh = ls::SmartPointer>::New(); + ls::ToMesh(sphere1, mesh).apply(); + ls::VTKWriter(mesh, "sphere1" + suffix).apply(); + } + + { + auto mesh = ls::SmartPointer>::New(); + ls::ToMesh(sphere2, mesh).apply(); + ls::VTKWriter(mesh, "sphere2" + suffix).apply(); + } + + // Compare the volumes/areas with mesh Output + auto mesh = ls::SmartPointer>::New(); // Create mesh for output + + if constexpr (D == 2) { + ls::CompareArea compareArea(sphere1, sphere2); + compareArea.setOutputMesh(mesh); + compareArea.apply(); + ls::VTKWriter(mesh, "volumeDifference" + suffix + ".vtu").apply(); + + // Area of circle = Ï€ * r² + double area1 = M_PI * radius1 * radius1; + double area2 = M_PI * radius2 * radius2; + double theoreticalDiff = std::abs(area2 - area1); + + double calculatedDifference = compareArea.getAreaMismatch(); + unsigned long int cellCount = compareArea.getCellCount(); + + std::cout << "Sphere 1 radius: " << radius1 << std::endl; + std::cout << "Sphere 2 radius: " << radius2 << std::endl; + std::cout << "Theoretical difference: " << theoreticalDiff << std::endl; + std::cout << "Calculated difference: " << calculatedDifference << std::endl; + std::cout << "Number of differing cells: " << cellCount << std::endl; + std::cout << "Error: " << std::abs(calculatedDifference - theoreticalDiff) + << std::endl; + + std::cout << "\nTesting custom increments and ranges:" << std::endl; + compareArea.setDefaultIncrement(2); + compareArea.apply(); + std::cout << "Difference with default increment of 2: " + << compareArea.getCustomAreaMismatch() << std::endl; + std::cout << "Cell count with default increment of 2: " + << compareArea.getCustomCellCount() << std::endl; + + compareArea.setDefaultIncrement(1); + compareArea.setXRangeAndIncrement(-5, 5, 3); + compareArea.apply(); + std::cout << "Difference with x-range increment of 3: " + << compareArea.getCustomAreaMismatch() << std::endl; + std::cout << "Cell count with x-range increment of 3: " + << compareArea.getCustomCellCount() << std::endl; + + compareArea.setDefaultIncrement(1); + compareArea.setYRangeAndIncrement(-5, 5, 4); + compareArea.apply(); + std::cout << "Difference with y-range increment of 4: " + << compareArea.getCustomAreaMismatch() << std::endl; + std::cout << "Cell count with y-range increment of 4: " + << compareArea.getCustomCellCount() << std::endl; + + } else { + ls::CompareVolume compareVolume(sphere1, sphere2); + compareVolume.setOutputMesh(mesh); + compareVolume.apply(); + ls::VTKWriter(mesh, "volumeDifference" + suffix + ".vtu").apply(); + + // Volume of sphere = 4/3 * Ï€ * r³ + double vol1 = (4.0 / 3.0) * M_PI * std::pow(radius1, 3); + double vol2 = (4.0 / 3.0) * M_PI * std::pow(radius2, 3); + double theoreticalDiff = std::abs(vol2 - vol1); + + double calculatedDifference = compareVolume.getVolumeMismatch(); + unsigned long int cellCount = compareVolume.getCellCount(); + + std::cout << "Sphere 1 radius: " << radius1 << std::endl; + std::cout << "Sphere 2 radius: " << radius2 << std::endl; + std::cout << "Theoretical difference: " << theoreticalDiff << std::endl; + std::cout << "Calculated difference: " << calculatedDifference << std::endl; + std::cout << "Number of differing cells: " << cellCount << std::endl; + std::cout << "Error: " << std::abs(calculatedDifference - theoreticalDiff) + << std::endl; + + std::cout << "\nTesting custom increments and ranges:" << std::endl; + compareVolume.setDefaultIncrement(2); + compareVolume.apply(); + std::cout << "Difference with default increment of 2: " + << compareVolume.getCustomVolumeMismatch() << std::endl; + std::cout << "Cell count with default increment of 2: " + << compareVolume.getCustomCellCount() << std::endl; + + compareVolume.setDefaultIncrement(1); + compareVolume.setXRangeAndIncrement(-5, 5, 3); + compareVolume.apply(); + std::cout << "Difference with x-range increment of 3: " + << compareVolume.getCustomVolumeMismatch() << std::endl; + std::cout << "Cell count with x-range increment of 3: " + << compareVolume.getCustomCellCount() << std::endl; + + compareVolume.setDefaultIncrement(1); + compareVolume.setYRangeAndIncrement(-5, 5, 4); + compareVolume.apply(); + std::cout << "Difference with y-range increment of 4: " + << compareVolume.getCustomVolumeMismatch() << std::endl; + std::cout << "Cell count with y-range increment of 4: " + << compareVolume.getCustomCellCount() << std::endl; + + compareVolume.setDefaultIncrement(1); + compareVolume.setZRangeAndIncrement(-5, 5, 5); + compareVolume.apply(); + std::cout << "Difference with z-range increment of 5: " + << compareVolume.getCustomVolumeMismatch() << std::endl; + std::cout << "Cell count with z-range increment of 5: " + << compareVolume.getCustomCellCount() << std::endl; + } +} + +int main() { + omp_set_num_threads(4); + runTest<2>(); + runTest<3>(); + return 0; +} From 1ef34ab6e086e088f6f9e7f9f06789558d548c4c Mon Sep 17 00:00:00 2001 From: filipovic Date: Tue, 13 Jan 2026 14:21:17 +0100 Subject: [PATCH 42/57] Removed CompareArea from top level Python API, now it is only in d2 --- python/viennals/__init__.pyi | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/viennals/__init__.pyi b/python/viennals/__init__.pyi index 54bc1a29..9955c78b 100644 --- a/python/viennals/__init__.pyi +++ b/python/viennals/__init__.pyi @@ -41,7 +41,6 @@ from viennals.d2 import CalculateCurvatures from viennals.d2 import CalculateNormalVectors from viennals.d2 import CalculateVisibilities from viennals.d2 import Check -from viennals.d2 import CompareArea from viennals.d2 import CompareVolume from viennals.d2 import CompareChamfer from viennals.d2 import CompareCriticalDimensions @@ -82,7 +81,7 @@ from viennals.d2 import hrleGrid from . import _core from . import d2 from . import d3 -__all__: list[str] = ['Advect', 'BooleanOperation', 'BooleanOperationEnum', 'BoundaryConditionEnum', 'Box', 'BoxDistribution', 'CalculateCurvatures', 'CalculateNormalVectors', 'CalculateVisibilities', 'Check', 'CompareArea', 'CompareChamfer', 'CompareCriticalDimensions', 'CompareNarrowBand', 'CompareSparseField', 'CompareVolume', 'ConvexHull', 'CurvatureEnum', 'CustomSphereDistribution', 'Cylinder', 'DetectFeatures', 'Domain', 'Expand', 'Extrude', 'FeatureDetectionEnum', 'FileFormatEnum', 'FinalizeStencilLocalLaxFriedrichs', 'FromMesh', 'FromSurfaceMesh', 'FromVolumeMesh', 'GeometricAdvect', 'GeometricAdvectDistribution', 'IntegrationSchemeEnum', 'LogLevel', 'Logger', 'MakeGeometry', 'MarkVoidPoints', 'MaterialMap', 'Mesh', 'PROXY_DIM', 'Plane', 'PointCloud', 'PointData', 'PrepareStencilLocalLaxFriedrichs', 'Prune', 'Reader', 'Reduce', 'RemoveStrayPoints', 'Slice', 'SpatialSchemeEnum', 'Sphere', 'SphereDistribution', 'StencilLocalLaxFriedrichsScalar', 'TemporalSchemeEnum', 'ToDiskMesh', 'ToMesh', 'ToMultiSurfaceMesh', 'ToSurfaceMesh', 'ToVoxelMesh', 'TransformEnum', 'TransformMesh', 'VTKReader', 'VTKRenderWindow', 'VTKWriter', 'VelocityField', 'VoidTopSurfaceEnum', 'WriteVisualizationMesh', 'Writer', 'd2', 'd3', 'getDimension', 'hrleGrid', 'setDimension', 'setNumThreads', 'version'] +__all__: list[str] = ['Advect', 'BooleanOperation', 'BooleanOperationEnum', 'BoundaryConditionEnum', 'Box', 'BoxDistribution', 'CalculateCurvatures', 'CalculateNormalVectors', 'CalculateVisibilities', 'Check', 'CompareChamfer', 'CompareCriticalDimensions', 'CompareNarrowBand', 'CompareSparseField', 'CompareVolume', 'ConvexHull', 'CurvatureEnum', 'CustomSphereDistribution', 'Cylinder', 'DetectFeatures', 'Domain', 'Expand', 'Extrude', 'FeatureDetectionEnum', 'FileFormatEnum', 'FinalizeStencilLocalLaxFriedrichs', 'FromMesh', 'FromSurfaceMesh', 'FromVolumeMesh', 'GeometricAdvect', 'GeometricAdvectDistribution', 'IntegrationSchemeEnum', 'LogLevel', 'Logger', 'MakeGeometry', 'MarkVoidPoints', 'MaterialMap', 'Mesh', 'PROXY_DIM', 'Plane', 'PointCloud', 'PointData', 'PrepareStencilLocalLaxFriedrichs', 'Prune', 'Reader', 'Reduce', 'RemoveStrayPoints', 'Slice', 'SpatialSchemeEnum', 'Sphere', 'SphereDistribution', 'StencilLocalLaxFriedrichsScalar', 'TemporalSchemeEnum', 'ToDiskMesh', 'ToMesh', 'ToMultiSurfaceMesh', 'ToSurfaceMesh', 'ToVoxelMesh', 'TransformEnum', 'TransformMesh', 'VTKReader', 'VTKRenderWindow', 'VTKWriter', 'VelocityField', 'VoidTopSurfaceEnum', 'WriteVisualizationMesh', 'Writer', 'd2', 'd3', 'getDimension', 'hrleGrid', 'setDimension', 'setNumThreads', 'version'] def __dir__(): ... def __getattr__(name): From f0157921a876567017020274f560541c5ee0b69a Mon Sep 17 00:00:00 2001 From: reiter Date: Tue, 13 Jan 2026 14:49:03 +0100 Subject: [PATCH 43/57] Update python stubs --- python/viennals/__init__.pyi | 4 ++-- python/viennals/_core.pyi | 2 +- python/viennals/d2.pyi | 28 ++++++++++++++++++++++++++-- python/viennals/d3.pyi | 8 ++++++-- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/python/viennals/__init__.pyi b/python/viennals/__init__.pyi index 54bc1a29..6005a0da 100644 --- a/python/viennals/__init__.pyi +++ b/python/viennals/__init__.pyi @@ -41,12 +41,12 @@ from viennals.d2 import CalculateCurvatures from viennals.d2 import CalculateNormalVectors from viennals.d2 import CalculateVisibilities from viennals.d2 import Check -from viennals.d2 import CompareArea -from viennals.d2 import CompareVolume from viennals.d2 import CompareChamfer from viennals.d2 import CompareCriticalDimensions from viennals.d2 import CompareNarrowBand from viennals.d2 import CompareSparseField +from viennals.d2 import CompareVolume +from viennals.d2 import CompareVolume as CompareArea from viennals.d2 import ConvexHull from viennals.d2 import CustomSphereDistribution from viennals.d2 import Cylinder diff --git a/python/viennals/_core.pyi b/python/viennals/_core.pyi index adb22c02..3adc3a1c 100644 --- a/python/viennals/_core.pyi +++ b/python/viennals/_core.pyi @@ -7,8 +7,8 @@ import enum import typing from viennals import d2 import viennals.d2 -import viennals.d3 from viennals import d3 +import viennals.d3 __all__: list[str] = ['BooleanOperationEnum', 'BoundaryConditionEnum', 'CurvatureEnum', 'Extrude', 'FeatureDetectionEnum', 'FileFormatEnum', 'IntegrationSchemeEnum', 'LogLevel', 'Logger', 'MaterialMap', 'Mesh', 'PointData', 'Slice', 'SpatialSchemeEnum', 'TemporalSchemeEnum', 'TransformEnum', 'TransformMesh', 'VTKReader', 'VTKRenderWindow', 'VTKWriter', 'VelocityField', 'VoidTopSurfaceEnum', 'd2', 'd3', 'setNumThreads', 'version'] class BooleanOperationEnum(enum.IntEnum): INTERSECT: typing.ClassVar[BooleanOperationEnum] # value = diff --git a/python/viennals/d2.pyi b/python/viennals/d2.pyi index 5d0953fb..fcfd1336 100644 --- a/python/viennals/d2.pyi +++ b/python/viennals/d2.pyi @@ -114,6 +114,10 @@ class Advect: """ Set the velocity to use for advection. """ + def setVelocityUpdateCallback(self, arg0: collections.abc.Callable[[Domain], bool]) -> None: + """ + Set a callback function that is called after the level set has been updated during intermediate time integration steps (e.g. RK2, RK3). + """ class BooleanOperation: @typing.overload def __init__(self) -> None: @@ -287,6 +291,10 @@ class CompareCriticalDimensions: @typing.overload def __init__(self, arg0: Domain, arg1: Domain) -> None: ... + def addRange(self, measureDimension: typing.SupportsInt, minBounds: typing.Annotated[collections.abc.Sequence[typing.SupportsFloat], "FixedSize(2)"], maxBounds: typing.Annotated[collections.abc.Sequence[typing.SupportsFloat], "FixedSize(2)"], findMaximum: bool = True) -> None: + """ + Add a generic range specification. + """ def addXRange(self, minX: typing.SupportsFloat, maxX: typing.SupportsFloat, findMaximum: bool = True) -> None: """ Add an X range to find maximum or minimum Y position. @@ -358,6 +366,10 @@ class CompareNarrowBand: """ Clear the y-range restriction """ + def clearZRange(self) -> None: + """ + Clear the z-range restriction + """ def getNumPoints(self) -> int: """ Return the number of points used in the comparison. @@ -398,6 +410,10 @@ class CompareNarrowBand: """ Set the y-coordinate range to restrict the comparison area """ + def setZRange(self, arg0: typing.SupportsFloat, arg1: typing.SupportsFloat) -> None: + """ + Set the z-coordinate range to restrict the comparison area + """ class CompareSparseField: @typing.overload def __init__(self) -> None: @@ -417,6 +433,10 @@ class CompareSparseField: """ Clear the y-range restriction """ + def clearZRange(self) -> None: + """ + Clear the z-range restriction + """ def getNumPoints(self) -> int: """ Return the number of points used in the comparison. @@ -465,6 +485,10 @@ class CompareSparseField: """ Set the y-coordinate range to restrict the comparison area """ + def setZRange(self, arg0: typing.SupportsFloat, arg1: typing.SupportsFloat) -> None: + """ + Set the z-coordinate range to restrict the comparison area + """ class CompareVolume: @typing.overload def __init__(self) -> None: @@ -528,7 +552,6 @@ class CompareVolume: """ Sets the z-range and custom increment value """ -CompareArea = CompareVolume class ConvexHull: @typing.overload def __init__(self) -> None: @@ -856,7 +879,7 @@ class MarkVoidPoints: """ Reverse the logic of detecting the top surface. """ - def setSaveComponentsId(self, arg0: bool) -> None: + def setSaveComponentIds(self, arg0: bool) -> None: """ Save the connectivity information of all LS points in the pointData of the level set. """ @@ -1202,3 +1225,4 @@ def FinalizeStencilLocalLaxFriedrichs(levelSets: collections.abc.Sequence[Domain ... def PrepareStencilLocalLaxFriedrichs(levelSets: collections.abc.Sequence[Domain], isDepo: collections.abc.Sequence[bool]) -> None: ... +CompareArea = CompareVolume diff --git a/python/viennals/d3.pyi b/python/viennals/d3.pyi index e66039d8..3ad263f3 100644 --- a/python/viennals/d3.pyi +++ b/python/viennals/d3.pyi @@ -114,6 +114,10 @@ class Advect: """ Set the velocity to use for advection. """ + def setVelocityUpdateCallback(self, arg0: collections.abc.Callable[[Domain], bool]) -> None: + """ + Set a callback function that is called after the level set has been updated during intermediate time integration steps (e.g. RK2, RK3). + """ class BooleanOperation: @typing.overload def __init__(self) -> None: @@ -287,7 +291,7 @@ class CompareCriticalDimensions: @typing.overload def __init__(self, arg0: Domain, arg1: Domain) -> None: ... - def addRange(self, measureDimension: typing.SupportsInt, minBounds: typing.SupportsFloat, maxBounds: typing.SupportsFloat, findMaximum: bool = True) -> None: + def addRange(self, measureDimension: typing.SupportsInt, minBounds: typing.Annotated[collections.abc.Sequence[typing.SupportsFloat], "FixedSize(3)"], maxBounds: typing.Annotated[collections.abc.Sequence[typing.SupportsFloat], "FixedSize(3)"], findMaximum: bool = True) -> None: """ Add a generic range specification. """ @@ -875,7 +879,7 @@ class MarkVoidPoints: """ Reverse the logic of detecting the top surface. """ - def setSaveComponentsId(self, arg0: bool) -> None: + def setSaveComponentIds(self, arg0: bool) -> None: """ Save the connectivity information of all LS points in the pointData of the level set. """ From 15b87ee83a3575f6644e8d2da6ffe5686d191594 Mon Sep 17 00:00:00 2001 From: reiter Date: Tue, 13 Jan 2026 14:58:53 +0100 Subject: [PATCH 44/57] Small fixes --- include/viennals/lsMakeGeometry.hpp | 7 +- python/CMakeLists.txt | 1 - python/tests/__init__.py | 0 python/viennals/__init__.pyi | 108 ++++------------------------ 4 files changed, 16 insertions(+), 100 deletions(-) delete mode 100644 python/tests/__init__.py diff --git a/include/viennals/lsMakeGeometry.hpp b/include/viennals/lsMakeGeometry.hpp index 3a676b29..2a45bbd3 100644 --- a/include/viennals/lsMakeGeometry.hpp +++ b/include/viennals/lsMakeGeometry.hpp @@ -525,7 +525,7 @@ template class MakeGeometry { mesher.apply(); } else if constexpr (D == 2) { VIENNACORE_LOG_WARNING( - "MakeGeometry: Cylinder in 2D creates a trench, not a cylinder."); + "MakeGeometry: Cylinder in 2D creates a box, not a cylinder."); auto mesh = Mesh::New(); @@ -583,11 +583,6 @@ template class MakeGeometry { FromSurfaceMesh mesher(levelSet, mesh); mesher.setRemoveBoundaryTriangles(ignoreBoundaryConditions); mesher.apply(); - } else { - Logger::getInstance() - .addError("MakeGeometry: Cylinder can only be " - "created in 2D or 3D!") - .print(); } } diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 192e67c1..dd14b5d0 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -77,7 +77,6 @@ add_dependencies(${PROJECT_NAME} ${MOD}) set(VIENNALS_LIB_FOLDER ${CMAKE_BINARY_DIR}/${PKG}.libs) -# Not required for both targets, one will suffice viennacore_setup_vtk_env(${MOD} ${VIENNALS_LIB_FOLDER}) install( diff --git a/python/tests/__init__.py b/python/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/python/viennals/__init__.pyi b/python/viennals/__init__.pyi index bad2a07e..550f2a77 100644 --- a/python/viennals/__init__.pyi +++ b/python/viennals/__init__.pyi @@ -8,7 +8,6 @@ topography simulations. The main design goals are simplicity and efficiency, tailored towards scientific simulations. ViennaLS can also be used for visualisation applications, although this is not the main design target. """ - from __future__ import annotations import sys as _sys from viennals._core import BooleanOperationEnum @@ -23,8 +22,8 @@ from viennals._core import MaterialMap from viennals._core import Mesh from viennals._core import PointData from viennals._core import Slice -from viennals._core import SpatialSchemeEnum as IntegrationSchemeEnum from viennals._core import SpatialSchemeEnum +from viennals._core import SpatialSchemeEnum as IntegrationSchemeEnum from viennals._core import TemporalSchemeEnum from viennals._core import TransformEnum from viennals._core import TransformMesh @@ -42,13 +41,12 @@ from viennals.d2 import CalculateCurvatures from viennals.d2 import CalculateNormalVectors from viennals.d2 import CalculateVisibilities from viennals.d2 import Check -from viennals.d2 import CompareVolume from viennals.d2 import CompareChamfer from viennals.d2 import CompareCriticalDimensions from viennals.d2 import CompareNarrowBand from viennals.d2 import CompareSparseField -from viennals.d2 import CompareVolume from viennals.d2 import CompareVolume as CompareArea +from viennals.d2 import CompareVolume from viennals.d2 import ConvexHull from viennals.d2 import CustomSphereDistribution from viennals.d2 import Cylinder @@ -84,110 +82,34 @@ from viennals.d2 import hrleGrid from . import _core from . import d2 from . import d3 - -__all__: list[str] = [ - "Advect", - "BooleanOperation", - "BooleanOperationEnum", - "BoundaryConditionEnum", - "Box", - "BoxDistribution", - "CalculateCurvatures", - "CalculateNormalVectors", - "CalculateVisibilities", - "Check", - "CompareChamfer", - "CompareCriticalDimensions", - "CompareNarrowBand", - "CompareSparseField", - "CompareVolume", - "ConvexHull", - "CurvatureEnum", - "CustomSphereDistribution", - "Cylinder", - "DetectFeatures", - "Domain", - "Expand", - "Extrude", - "FeatureDetectionEnum", - "FileFormatEnum", - "FinalizeStencilLocalLaxFriedrichs", - "FromMesh", - "FromSurfaceMesh", - "FromVolumeMesh", - "GeometricAdvect", - "GeometricAdvectDistribution", - "IntegrationSchemeEnum", - "LogLevel", - "Logger", - "MakeGeometry", - "MarkVoidPoints", - "MaterialMap", - "Mesh", - "PROXY_DIM", - "Plane", - "PointCloud", - "PointData", - "PrepareStencilLocalLaxFriedrichs", - "Prune", - "Reader", - "Reduce", - "RemoveStrayPoints", - "Slice", - "SpatialSchemeEnum", - "Sphere", - "SphereDistribution", - "StencilLocalLaxFriedrichsScalar", - "TemporalSchemeEnum", - "ToDiskMesh", - "ToMesh", - "ToMultiSurfaceMesh", - "ToSurfaceMesh", - "ToVoxelMesh", - "TransformEnum", - "TransformMesh", - "VTKReader", - "VTKRenderWindow", - "VTKWriter", - "VelocityField", - "VoidTopSurfaceEnum", - "WriteVisualizationMesh", - "Writer", - "d2", - "d3", - "getDimension", - "hrleGrid", - "setDimension", - "setNumThreads", - "version", -] - -def __dir__(): ... -def __getattr__(name): ... -def _windows_dll_path(): ... +__all__: list[str] = ['Advect', 'BooleanOperation', 'BooleanOperationEnum', 'BoundaryConditionEnum', 'Box', 'BoxDistribution', 'CalculateCurvatures', 'CalculateNormalVectors', 'CalculateVisibilities', 'Check', 'CompareArea', 'CompareChamfer', 'CompareCriticalDimensions', 'CompareNarrowBand', 'CompareSparseField', 'CompareVolume', 'ConvexHull', 'CurvatureEnum', 'CustomSphereDistribution', 'Cylinder', 'DetectFeatures', 'Domain', 'Expand', 'Extrude', 'FeatureDetectionEnum', 'FileFormatEnum', 'FinalizeStencilLocalLaxFriedrichs', 'FromMesh', 'FromSurfaceMesh', 'FromVolumeMesh', 'GeometricAdvect', 'GeometricAdvectDistribution', 'IntegrationSchemeEnum', 'LogLevel', 'Logger', 'MakeGeometry', 'MarkVoidPoints', 'MaterialMap', 'Mesh', 'PROXY_DIM', 'Plane', 'PointCloud', 'PointData', 'PrepareStencilLocalLaxFriedrichs', 'Prune', 'Reader', 'Reduce', 'RemoveStrayPoints', 'Slice', 'SpatialSchemeEnum', 'Sphere', 'SphereDistribution', 'StencilLocalLaxFriedrichsScalar', 'TemporalSchemeEnum', 'ToDiskMesh', 'ToMesh', 'ToMultiSurfaceMesh', 'ToSurfaceMesh', 'ToVoxelMesh', 'TransformEnum', 'TransformMesh', 'VTKReader', 'VTKRenderWindow', 'VTKWriter', 'VelocityField', 'VoidTopSurfaceEnum', 'WriteVisualizationMesh', 'Writer', 'd2', 'd3', 'getDimension', 'hrleGrid', 'setDimension', 'setNumThreads', 'version'] +def __dir__(): + ... +def __getattr__(name): + ... +def _windows_dll_path(): + ... def getDimension() -> int: """ Get the current dimension of the simulation. - + Returns ------- int The currently set dimension (2 or 3). - + """ - def setDimension(d: int): """ Set the dimension of the simulation (2 or 3). - + Parameters ---------- d: int Dimension of the simulation (2 or 3). - + """ - PROXY_DIM: int = 2 -__version__: str = "5.4.0" -version: str = "5.4.0" +__version__: str = '5.4.0' +version: str = '5.4.0' _C = _core From 9a0a6ffe2d32a8037af3dae9b6b1c81e131052e3 Mon Sep 17 00:00:00 2001 From: Roman Kostal Date: Tue, 13 Jan 2026 18:09:20 +0100 Subject: [PATCH 45/57] Correct inconsistency in custom z-increments. --- include/viennals/lsCompareVolume.hpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/include/viennals/lsCompareVolume.hpp b/include/viennals/lsCompareVolume.hpp index b117fb6c..f568ef0b 100644 --- a/include/viennals/lsCompareVolume.hpp +++ b/include/viennals/lsCompareVolume.hpp @@ -302,17 +302,17 @@ template class CompareVolume { // Calculate increment to add based on ranges unsigned short int incrementToAdd = defaultIncrement; - if (inXRange && inYRange) { - // Apply both increments - incrementToAdd = customXIncrement + customYIncrement; - } else if (inXRange) { - incrementToAdd = customXIncrement; - } else if (inYRange) { - incrementToAdd = customYIncrement; - } - if (D == 3 && inZRange) { - incrementToAdd += customZIncrement; + // If any custom range is active, start from 0 and add the custom + // increments + if (inXRange || inYRange || (D == 3 && inZRange)) { + incrementToAdd = 0; + if (inXRange) + incrementToAdd += customXIncrement; + if (inYRange) + incrementToAdd += customYIncrement; + if (D == 3 && inZRange) + incrementToAdd += customZIncrement; } // If cells differ, update the counters From 0004c0fecebe33ed568fb5b9eac519aa11f715bf Mon Sep 17 00:00:00 2001 From: Roman Kostal Date: Tue, 13 Jan 2026 18:17:08 +0100 Subject: [PATCH 46/57] Small fixes and improvements to tests. --- tests/CompareChamfer/CompareChamfer.cpp | 21 ++++++++++++++++++++- tests/CompareVolume/CompareVolume.cpp | 12 ++++++------ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/tests/CompareChamfer/CompareChamfer.cpp b/tests/CompareChamfer/CompareChamfer.cpp index 0db957fb..a8886c9f 100644 --- a/tests/CompareChamfer/CompareChamfer.cpp +++ b/tests/CompareChamfer/CompareChamfer.cpp @@ -207,6 +207,19 @@ template void runTest() { std::cout << " Backward distance: " << compareDifferentSize.getBackwardDistance() << std::endl; + // Validation: Check if Chamfer distance is close to expected radius + // difference + double expectedDiff = std::abs(radius4 - radius1); + double actualChamfer = compareDifferentSize.getChamferDistance(); + double tolerance = 0.01 * expectedDiff; // 1% tolerance + if (std::abs(actualChamfer - expectedDiff) > tolerance) { + std::cerr << "ERROR: Chamfer distance " << actualChamfer + << " differs significantly from expected " << expectedDiff + << " (tolerance: " << tolerance << ")" << std::endl; + return; + } + std::cout << " Validation: PASSED (within 1% tolerance)" << std::endl; + // Test 3c: Large shift auto sphere5 = ls::SmartPointer>::New( bounds, boundaryCons, gridDelta); @@ -226,7 +239,13 @@ template void runTest() { // Test 4: Performance summary std::cout << "\n=== Performance Summary ===" << std::endl; - std::cout << "Chamfer distance: " << chamfer_ms.count() << " ms" << std::endl; + std::cout << "Chamfer distance: " << chamfer_ms.count() << " ms" + << std::endl; + std::cout << "Sparse Field: " << sparse_ms.count() << " ms" + << std::endl; + std::cout << "Area/Volume: " << area_ms.count() << " ms" + << std::endl; + std::cout << "==========================" << std::endl; } int main() { diff --git a/tests/CompareVolume/CompareVolume.cpp b/tests/CompareVolume/CompareVolume.cpp index e951d56c..04f803ff 100644 --- a/tests/CompareVolume/CompareVolume.cpp +++ b/tests/CompareVolume/CompareVolume.cpp @@ -10,9 +10,9 @@ #include /** - Test for lsCompareArea that compares the area difference between two level - sets. This test creates two different spheres and measures their area - difference. \example CompareArea.cpp + Test for lsCompareVolume that compares the area/volume difference between two + level sets. This test creates two different spheres and measures their + area/volume difference. \example CompareVolume.cpp */ namespace ls = viennals; @@ -52,18 +52,18 @@ template void runTest() { sphere2, ls::SmartPointer>::New(origin2, radius2)) .apply(); - std::string suffix = "_" + std::to_string(D) + "D.vtp"; + std::string suffix = "_" + std::to_string(D) + "D"; // Export both spheres as VTK files for visualization { auto mesh = ls::SmartPointer>::New(); ls::ToMesh(sphere1, mesh).apply(); - ls::VTKWriter(mesh, "sphere1" + suffix).apply(); + ls::VTKWriter(mesh, "sphere1" + suffix + ".vtp").apply(); } { auto mesh = ls::SmartPointer>::New(); ls::ToMesh(sphere2, mesh).apply(); - ls::VTKWriter(mesh, "sphere2" + suffix).apply(); + ls::VTKWriter(mesh, "sphere2" + suffix + ".vtp").apply(); } // Compare the volumes/areas with mesh Output From 0c8e61ac28ccf0e5f727c87dc718d649fde2d53b Mon Sep 17 00:00:00 2001 From: filipovic Date: Tue, 13 Jan 2026 20:36:27 +0100 Subject: [PATCH 47/57] Minor fix --- include/viennals/lsCompareSparseField.hpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/include/viennals/lsCompareSparseField.hpp b/include/viennals/lsCompareSparseField.hpp index ba385363..1041400b 100644 --- a/include/viennals/lsCompareSparseField.hpp +++ b/include/viennals/lsCompareSparseField.hpp @@ -248,9 +248,11 @@ template class CompareSparseField { outputMesh->clear(); // Initialize mesh extent - for (unsigned i = 0; i < D; ++i) { - outputMesh->minimumExtent[i] = std::numeric_limits::max(); - outputMesh->maximumExtent[i] = std::numeric_limits::lowest(); + for (unsigned i = 0; i < 3; ++i) { + outputMesh->minimumExtent[i] = + (i < D) ? std::numeric_limits::max() : 0.0; + outputMesh->maximumExtent[i] = + (i < D) ? std::numeric_limits::lowest() : 0.0; } // Reserve space for mesh data @@ -303,7 +305,7 @@ template class CompareSparseField { } // Skip if outside the specified z-range - if (useZRange && (zCoord < zRangeMin || zCoord > zRangeMax)) { + if (D == 3 && useZRange && (zCoord < zRangeMin || zCoord > zRangeMax)) { itIterated.next(); continue; } From 8d9e70005b47d42bc8e329c77ebce123ac2c6731 Mon Sep 17 00:00:00 2001 From: filipovic Date: Thu, 15 Jan 2026 01:25:52 +0100 Subject: [PATCH 48/57] initialize all Vec3Ds to 0, add 3rd order WENO spatial scheme, improve advection tests --- .gitignore | 3 +- examples/SquareEtch/SquareEtch.cpp | 7 +- include/viennals/lsAdvect.hpp | 20 +- .../viennals/lsAdvectIntegrationSchemes.hpp | 3 +- include/viennals/lsCalculateNormalVectors.hpp | 4 +- include/viennals/lsCalculateVisibilities.hpp | 6 +- include/viennals/lsCompareChamfer.hpp | 16 +- .../viennals/lsCompareCriticalDimensions.hpp | 11 +- include/viennals/lsCompareNarrowBand.hpp | 8 +- include/viennals/lsCompareSparseField.hpp | 8 +- include/viennals/lsCompareVolume.hpp | 10 +- include/viennals/lsEngquistOsher.hpp | 2 +- include/viennals/lsExtrude.hpp | 2 +- include/viennals/lsGeometricAdvect.hpp | 2 +- .../lsGeometricAdvectDistributions.hpp | 6 +- include/viennals/lsLaxFriedrichs.hpp | 2 +- include/viennals/lsLocalLaxFriedrichs.hpp | 8 +- .../lsLocalLaxFriedrichsAnalytical.hpp | 6 +- .../viennals/lsLocalLocalLaxFriedrichs.hpp | 2 +- include/viennals/lsMakeGeometry.hpp | 3 +- include/viennals/lsMesh.hpp | 6 +- .../lsStencilLocalLaxFriedrichsScalar.hpp | 14 +- include/viennals/lsToDiskMesh.hpp | 8 +- include/viennals/lsToMesh.hpp | 2 +- include/viennals/lsToSurfaceMesh.hpp | 2 +- include/viennals/lsToVoxelMesh.hpp | 2 +- include/viennals/lsWENO5.hpp | 72 ++++--- python/pyWrap.cpp | 1 + python/viennals/_core.pyi | 3 +- tests/Advection/Advection.cpp | 46 +++- tests/Advection/StencilLaxFriedrichsTest.cpp | 23 +- tests/Advection/TimeIntegrationComparison.cpp | 197 +++++++++++++++++- .../BooleanOperationExactZero.cpp | 4 +- .../GeometricAdvectMask.cpp | 2 +- 34 files changed, 368 insertions(+), 143 deletions(-) diff --git a/.gitignore b/.gitignore index 26732282..12a3b820 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ _generate/ *env* .mypy_cache/ .eggs/ -test.ipynb \ No newline at end of file +test.ipynb +testall.sh diff --git a/examples/SquareEtch/SquareEtch.cpp b/examples/SquareEtch/SquareEtch.cpp index b6bf661e..f7673913 100644 --- a/examples/SquareEtch/SquareEtch.cpp +++ b/examples/SquareEtch/SquareEtch.cpp @@ -200,8 +200,7 @@ int main() { // Analytical velocity fields and dissipation coefficients // can only be used with this spatial discretization scheme advectionKernel.setSpatialScheme( - // ls::SpatialSchemeEnum::LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER); - ls::SpatialSchemeEnum::WENO_5TH_ORDER); + ls::SpatialSchemeEnum::LOCAL_LAX_FRIEDRICHS_ANALYTICAL_1ST_ORDER); } else { // for numerical velocities, just use the default // spatial discretization scheme, which is not accurate for certain @@ -211,8 +210,8 @@ int main() { // For coordinate independent velocity functions // this numerical scheme is superior though. // However, it is slower. - // advectionKernel.setSpatialScheme( - // ls::SpatialSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER); + advectionKernel.setSpatialScheme( + ls::SpatialSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER); } // advect the level set until 50s have passed diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index 33309638..ae8dbc81 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -147,12 +147,12 @@ template class Advect { // move neighborIterator to current position neighborIterator.goToIndicesSequential(indices); - Vec3D coords; + Vec3D coords{}; for (unsigned i = 0; i < D; ++i) { coords[i] = indices[i] * gridDelta; } - Vec3D normal = {}; + Vec3D normal{}; T normalModulus = 0.; for (unsigned i = 0; i < D; ++i) { const T phiPos = neighborIterator.getNeighbor(i).getValue(); @@ -664,10 +664,15 @@ template class Advect { auto is = lsInternal::StencilLocalLaxFriedrichsScalar( levelSets.back(), velocities, dissipationAlpha); currentTimeStep = integrateTime(is, maxTimeStep); + } else if (spatialScheme == SpatialSchemeEnum::WENO_3RD_ORDER) { + // Instantiate WENO with order 3 + auto is = lsInternal::WENO(levelSets.back(), velocities, + dissipationAlpha); + currentTimeStep = integrateTime(is, maxTimeStep); } else if (spatialScheme == SpatialSchemeEnum::WENO_5TH_ORDER) { - // Instantiate WENO5 with order 3 (neighbors +/- 3) - auto is = lsInternal::WENO5(levelSets.back(), velocities, - dissipationAlpha); + // Instantiate WENO with order 5 + auto is = lsInternal::WENO(levelSets.back(), velocities, + dissipationAlpha); currentTimeStep = integrateTime(is, maxTimeStep); } else { VIENNACORE_LOG_ERROR("Advect: Discretization scheme not found."); @@ -998,9 +1003,10 @@ template class Advect { SpatialSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) { lsInternal::StencilLocalLaxFriedrichsScalar::prepareLS( levelSets.back()); + } else if (spatialScheme == SpatialSchemeEnum::WENO_3RD_ORDER) { + lsInternal::WENO::prepareLS(levelSets.back()); } else if (spatialScheme == SpatialSchemeEnum::WENO_5TH_ORDER) { - // WENO5 requires a stencil radius of 3 (template parameter 3) - lsInternal::WENO5::prepareLS(levelSets.back()); + lsInternal::WENO::prepareLS(levelSets.back()); } else { VIENNACORE_LOG_ERROR("Advect: Discretization scheme not found."); } diff --git a/include/viennals/lsAdvectIntegrationSchemes.hpp b/include/viennals/lsAdvectIntegrationSchemes.hpp index 691c3ba5..ca005ae5 100644 --- a/include/viennals/lsAdvectIntegrationSchemes.hpp +++ b/include/viennals/lsAdvectIntegrationSchemes.hpp @@ -18,7 +18,8 @@ enum class SpatialSchemeEnum : unsigned { LOCAL_LAX_FRIEDRICHS_1ST_ORDER = 7, LOCAL_LAX_FRIEDRICHS_2ND_ORDER = 8, STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER = 9, - WENO_5TH_ORDER = 10 + WENO_3RD_ORDER = 10, + WENO_5TH_ORDER = 11 }; // Legacy naming (deprecated, will be removed in future versions) diff --git a/include/viennals/lsCalculateNormalVectors.hpp b/include/viennals/lsCalculateNormalVectors.hpp index d24c77ce..3afd14a0 100644 --- a/include/viennals/lsCalculateNormalVectors.hpp +++ b/include/viennals/lsCalculateNormalVectors.hpp @@ -134,12 +134,12 @@ template class CalculateNormalVectors { continue; } else if (std::abs(center.getValue()) > maxValue) { // push an empty vector to keep ordering correct - Vec3D tmp = {}; + Vec3D tmp{}; normalVectors.push_back(tmp); continue; } - Vec3D n; + Vec3D n{}; T denominator = 0; for (int i = 0; i < D; i++) { diff --git a/include/viennals/lsCalculateVisibilities.hpp b/include/viennals/lsCalculateVisibilities.hpp index 33f10fb9..45bee138 100644 --- a/include/viennals/lsCalculateVisibilities.hpp +++ b/include/viennals/lsCalculateVisibilities.hpp @@ -41,8 +41,8 @@ template class CalculateVisibilities { std::fill(visibilities.begin(), visibilities.end(), static_cast(1.0)); } else { // *** Determine extents of domain *** - Vec3D minDefinedPoint; - Vec3D maxDefinedPoint; + Vec3D minDefinedPoint{}; + Vec3D maxDefinedPoint{}; // Initialize with extreme values for (int i = 0; i < D; ++i) { minDefinedPoint[i] = std::numeric_limits::max(); @@ -87,7 +87,7 @@ template class CalculateVisibilities { continue; // Starting position of the point - Vec3D currentPos; + Vec3D currentPos{}; for (int i = 0; i < D; ++i) { currentPos[i] = it.getStartIndices(i); } diff --git a/include/viennals/lsCompareChamfer.hpp b/include/viennals/lsCompareChamfer.hpp index dc34dbe6..87b81896 100644 --- a/include/viennals/lsCompareChamfer.hpp +++ b/include/viennals/lsCompareChamfer.hpp @@ -258,13 +258,9 @@ template class CompareChamfer { std::move(targetDistances), "DistanceToSample"); // Set mesh extent - for (unsigned d = 0; d < 3; ++d) { - outputMeshTarget->minimumExtent[d] = - (d < D) ? std::numeric_limits::max() : 0.0; - outputMeshTarget->maximumExtent[d] = - (d < D) ? std::numeric_limits::lowest() : 0.0; - } for (unsigned d = 0; d < D; ++d) { + outputMeshTarget->minimumExtent[d] = std::numeric_limits::max(); + outputMeshTarget->maximumExtent[d] = std::numeric_limits::lowest(); for (const auto &node : targetNodes) { outputMeshTarget->minimumExtent[d] = std::min(outputMeshTarget->minimumExtent[d], node[d]); @@ -290,13 +286,9 @@ template class CompareChamfer { std::move(sampleDistances), "DistanceToTarget"); // Set mesh extent - for (unsigned d = 0; d < 3; ++d) { - outputMeshSample->minimumExtent[d] = - (d < D) ? std::numeric_limits::max() : 0.0; - outputMeshSample->maximumExtent[d] = - (d < D) ? std::numeric_limits::lowest() : 0.0; - } for (unsigned d = 0; d < D; ++d) { + outputMeshSample->minimumExtent[d] = std::numeric_limits::max(); + outputMeshSample->maximumExtent[d] = std::numeric_limits::lowest(); for (const auto &node : sampleNodes) { outputMeshSample->minimumExtent[d] = std::min(outputMeshSample->minimumExtent[d], node[d]); diff --git a/include/viennals/lsCompareCriticalDimensions.hpp b/include/viennals/lsCompareCriticalDimensions.hpp index 98e8270d..fc51451d 100644 --- a/include/viennals/lsCompareCriticalDimensions.hpp +++ b/include/viennals/lsCompareCriticalDimensions.hpp @@ -326,11 +326,9 @@ template class CompareCriticalDimensions { std::vector targetValues; std::vector sampleValues; - for (unsigned i = 0; i < 3; ++i) { - outputMesh->minimumExtent[i] = - (i < D) ? std::numeric_limits::max() : 0.0; - outputMesh->maximumExtent[i] = - (i < D) ? std::numeric_limits::lowest() : 0.0; + for (unsigned i = 0; i < D; ++i) { + outputMesh->minimumExtent[i] = std::numeric_limits::max(); + outputMesh->maximumExtent[i] = std::numeric_limits::lowest(); } unsigned pointId = 0; @@ -339,7 +337,8 @@ template class CompareCriticalDimensions { continue; // Create points for target and sample positions - Vec3D coordTarget = {0.0, 0.0, 0.0}, coordSample = {0.0, 0.0, 0.0}; + Vec3D coordTarget{}; + Vec3D coordSample{}; for (int i = 0; i < D; ++i) { if (i == result.measureDimension) { diff --git a/include/viennals/lsCompareNarrowBand.hpp b/include/viennals/lsCompareNarrowBand.hpp index 6b952793..c8486b37 100644 --- a/include/viennals/lsCompareNarrowBand.hpp +++ b/include/viennals/lsCompareNarrowBand.hpp @@ -242,11 +242,9 @@ template class CompareNarrowBand { outputMesh->clear(); // Initialize mesh extent - for (unsigned i = 0; i < 3; ++i) { - outputMesh->minimumExtent[i] = - (i < D) ? std::numeric_limits::max() : 0.0; - outputMesh->maximumExtent[i] = - (i < D) ? std::numeric_limits::lowest() : 0.0; + for (unsigned i = 0; i < D; ++i) { + outputMesh->minimumExtent[i] = std::numeric_limits::max(); + outputMesh->maximumExtent[i] = std::numeric_limits::lowest(); } } diff --git a/include/viennals/lsCompareSparseField.hpp b/include/viennals/lsCompareSparseField.hpp index 1041400b..8ce256fb 100644 --- a/include/viennals/lsCompareSparseField.hpp +++ b/include/viennals/lsCompareSparseField.hpp @@ -248,11 +248,9 @@ template class CompareSparseField { outputMesh->clear(); // Initialize mesh extent - for (unsigned i = 0; i < 3; ++i) { - outputMesh->minimumExtent[i] = - (i < D) ? std::numeric_limits::max() : 0.0; - outputMesh->maximumExtent[i] = - (i < D) ? std::numeric_limits::lowest() : 0.0; + for (unsigned i = 0; i < D; ++i) { + outputMesh->minimumExtent[i] = std::numeric_limits::max(); + outputMesh->maximumExtent[i] = std::numeric_limits::lowest(); } // Reserve space for mesh data diff --git a/include/viennals/lsCompareVolume.hpp b/include/viennals/lsCompareVolume.hpp index f568ef0b..7ff813f5 100644 --- a/include/viennals/lsCompareVolume.hpp +++ b/include/viennals/lsCompareVolume.hpp @@ -250,11 +250,9 @@ template class CompareVolume { if (generateMesh) { // Save the extent of the resulting mesh outputMesh->clear(); - for (unsigned i = 0; i < 3; ++i) { - outputMesh->minimumExtent[i] = - (i < D) ? std::numeric_limits::max() : 0.0; - outputMesh->maximumExtent[i] = - (i < D) ? std::numeric_limits::lowest() : 0.0; + for (unsigned i = 0; i < D; ++i) { + outputMesh->minimumExtent[i] = std::numeric_limits::max(); + outputMesh->maximumExtent[i] = std::numeric_limits::lowest(); } } @@ -383,7 +381,7 @@ template class CompareVolume { // Insert points into the mesh outputMesh->nodes.resize(pointIdMapping.size()); for (auto it = pointIdMapping.begin(); it != pointIdMapping.end(); ++it) { - Vec3D coords = {0.0, 0.0, 0.0}; + Vec3D coords{}; for (unsigned i = 0; i < D; ++i) { coords[i] = gridDelta * it->first[i]; diff --git a/include/viennals/lsEngquistOsher.hpp b/include/viennals/lsEngquistOsher.hpp index f61c5d0a..c8f019df 100644 --- a/include/viennals/lsEngquistOsher.hpp +++ b/include/viennals/lsEngquistOsher.hpp @@ -121,7 +121,7 @@ template class EngquistOsher { // Calculate normal vector for velocity calculation // use std::array since it will be exposed to interface - Vec3D normalVector = {}; + Vec3D normalVector{}; if (calculateNormalVectors) { T denominator = 0; for (int i = 0; i < D; i++) { diff --git a/include/viennals/lsExtrude.hpp b/include/viennals/lsExtrude.hpp index 41f74423..c2d20580 100644 --- a/include/viennals/lsExtrude.hpp +++ b/include/viennals/lsExtrude.hpp @@ -19,7 +19,7 @@ template class Extrude { SmartPointer> outputLevelSet = nullptr; Vec2D extent = {0., 0.}; int extrusionAxis = 0; - std::array boundaryConds = {}; + std::array boundaryConds{}; public: Extrude() = default; diff --git a/include/viennals/lsGeometricAdvect.hpp b/include/viennals/lsGeometricAdvect.hpp index 60abb04e..5af9fffc 100644 --- a/include/viennals/lsGeometricAdvect.hpp +++ b/include/viennals/lsGeometricAdvect.hpp @@ -482,7 +482,7 @@ template class GeometricAdvect { { std::vector scalarData; for (auto it = newPoints[0].begin(); it != newPoints[0].end(); ++it) { - Vec3D node{0., 0., 0.}; + Vec3D node{}; for (unsigned i = 0; i < D; ++i) { node[i] = T((it->first)[i]) * gridDelta; } diff --git a/include/viennals/lsGeometricAdvectDistributions.hpp b/include/viennals/lsGeometricAdvectDistributions.hpp index c6bab5e4..55b8dd8f 100644 --- a/include/viennals/lsGeometricAdvectDistributions.hpp +++ b/include/viennals/lsGeometricAdvectDistributions.hpp @@ -108,7 +108,7 @@ class SphereDistribution : public GeometricAdvectDistribution { } std::array getBounds() const override { - std::array bounds = {}; + std::array bounds{}; for (unsigned i = 0; i < D; ++i) { bounds[2 * i] = -radius; bounds[2 * i + 1] = radius; @@ -157,7 +157,7 @@ class BoxDistribution : public GeometricAdvectDistribution { } std::array getBounds() const override { - std::array bounds = {}; + std::array bounds{}; for (unsigned i = 0; i < D; ++i) { bounds[2 * i] = -posExtent[i]; bounds[2 * i + 1] = posExtent[i]; @@ -240,7 +240,7 @@ class CustomSphereDistribution : public GeometricAdvectDistribution { } std::array getBounds() const override { - std::array bounds = {}; + std::array bounds{}; for (unsigned i = 0; i < D; ++i) { bounds[2 * i] = -maxRadius_; bounds[2 * i + 1] = maxRadius_; diff --git a/include/viennals/lsLaxFriedrichs.hpp b/include/viennals/lsLaxFriedrichs.hpp index 7320b518..04daf038 100644 --- a/include/viennals/lsLaxFriedrichs.hpp +++ b/include/viennals/lsLaxFriedrichs.hpp @@ -61,7 +61,7 @@ template class LaxFriedrichs { T grad = 0.; T dissipation = 0.; - Vec3D normalVector = {}; + Vec3D normalVector{}; T normalModulus = 0; for (int i = 0; i < D; i++) { // iterate over dimensions diff --git a/include/viennals/lsLocalLaxFriedrichs.hpp b/include/viennals/lsLocalLaxFriedrichs.hpp index e73a9809..371e82b6 100644 --- a/include/viennals/lsLocalLaxFriedrichs.hpp +++ b/include/viennals/lsLocalLaxFriedrichs.hpp @@ -86,7 +86,7 @@ template class LocalLaxFriedrichs { T grad = 0.; T dissipation = 0.; - Vec3D normalVector = {}; + Vec3D normalVector{}; T normalModulus = 0; for (int i = 0; i < D; i++) { // iterate over dimensions @@ -183,7 +183,7 @@ template class LocalLaxFriedrichs { } // calculate alphas - T alpha[D] = {}; + T alpha[D]{}; { // alpha calculation is always on order 1 stencil const viennahrle::IndexType minIndex = -1; @@ -192,11 +192,11 @@ template class LocalLaxFriedrichs { viennahrle::Index neighborIndex(minIndex); for (unsigned i = 0; i < numNeighbors; ++i) { - Vec3D coords; + Vec3D coords{}; for (unsigned dir = 0; dir < D; ++dir) { coords[dir] = coordinate[dir] + neighborIndex[dir] * gridDelta; } - Vec3D normal = {}; + Vec3D normal{}; double normalModulus = 0.; auto center = neighborIterator.getNeighbor(neighborIndex).getValue(); for (unsigned dir = 0; dir < D; ++dir) { diff --git a/include/viennals/lsLocalLaxFriedrichsAnalytical.hpp b/include/viennals/lsLocalLaxFriedrichsAnalytical.hpp index 1e276fc4..12ce9168 100644 --- a/include/viennals/lsLocalLaxFriedrichsAnalytical.hpp +++ b/include/viennals/lsLocalLaxFriedrichsAnalytical.hpp @@ -85,7 +85,7 @@ template class LocalLaxFriedrichsAnalytical { T grad = 0.; T dissipation = 0.; - Vec3D normalVector = {}; + Vec3D normalVector{}; T normalModulus = 0; for (int i = 0; i < D; i++) { // iterate over dimensions @@ -182,7 +182,7 @@ template class LocalLaxFriedrichsAnalytical { } // calculate alphas - T alpha[D] = {}; + T alpha[D]{}; { // alpha calculation is always on order 1 stencil constexpr viennahrle::IndexType minIndex = -1; @@ -192,7 +192,7 @@ template class LocalLaxFriedrichsAnalytical { viennahrle::Index neighborIndex(minIndex); for (unsigned i = 0; i < numNeighbors; ++i) { - Vec3D normal = {}; + Vec3D normal{}; auto center = neighborIterator.getNeighbor(neighborIndex).getValue(); for (unsigned dir = 0; dir < D; ++dir) { viennahrle::Index unity(0); diff --git a/include/viennals/lsLocalLocalLaxFriedrichs.hpp b/include/viennals/lsLocalLocalLaxFriedrichs.hpp index 4361172f..c6da41dd 100644 --- a/include/viennals/lsLocalLocalLaxFriedrichs.hpp +++ b/include/viennals/lsLocalLocalLaxFriedrichs.hpp @@ -64,7 +64,7 @@ template class LocalLocalLaxFriedrichs { T grad = 0.; T dissipation = 0.; - Vec3D normalVector = {}; + Vec3D normalVector{}; T normalModulus = 0; for (int i = 0; i < D; i++) { // iterate over dimensions diff --git a/include/viennals/lsMakeGeometry.hpp b/include/viennals/lsMakeGeometry.hpp index 2a45bbd3..7b13dac5 100644 --- a/include/viennals/lsMakeGeometry.hpp +++ b/include/viennals/lsMakeGeometry.hpp @@ -441,10 +441,9 @@ template class MakeGeometry { // create and insert points at base for (double angle = 0.; angle < limit; angle += smallAngle) { - Vec3D point; + Vec3D point{}; point[0] = cylinder->radius * std::cos(angle); point[1] = cylinder->radius * std::sin(angle); - point[2] = 0.0; points.push_back(point); mesh->insertNextNode(point); } diff --git a/include/viennals/lsMesh.hpp b/include/viennals/lsMesh.hpp index d2ce80c1..007bc70b 100644 --- a/include/viennals/lsMesh.hpp +++ b/include/viennals/lsMesh.hpp @@ -29,8 +29,8 @@ template class Mesh { std::vector> hexas; PointData pointData; PointData cellData; - Vec3D minimumExtent; - Vec3D maximumExtent; + Vec3D minimumExtent{}; + Vec3D maximumExtent{}; private: // iterator typedef @@ -272,6 +272,8 @@ template class Mesh { hexas.clear(); pointData.clear(); cellData.clear(); + minimumExtent = Vec3D{}; + maximumExtent = Vec3D{}; } void print() { diff --git a/include/viennals/lsStencilLocalLaxFriedrichsScalar.hpp b/include/viennals/lsStencilLocalLaxFriedrichsScalar.hpp index 1393cbb9..503ecf27 100644 --- a/include/viennals/lsStencilLocalLaxFriedrichsScalar.hpp +++ b/include/viennals/lsStencilLocalLaxFriedrichsScalar.hpp @@ -46,7 +46,7 @@ class StencilLocalLaxFriedrichsScalar { // Final dissipation coefficients that are used by the time integrator. If // D==2 last entries are 0. - Vec3D finalAlphas; + Vec3D finalAlphas{}; static constexpr unsigned numStencilPoints = hrleUtil::pow(2 * order + 1, D); static double maxDissipation; // default: std::numeric_limits::max(); @@ -124,7 +124,7 @@ class StencilLocalLaxFriedrichsScalar { } Vec3D calculateNormal(const viennahrle::Index &offset) { - Vec3D normal = {0.0, 0.0, 0.0}; + Vec3D normal{}; constexpr int startIndex = -1; T modulus = 0.; @@ -204,11 +204,7 @@ class StencilLocalLaxFriedrichsScalar { SmartPointer> vel, double a = 1.0) : levelSet(passedlsDomain), velocities(vel), - neighborIterator(levelSet->getDomain()), alphaFactor(a) { - for (int i = 0; i < 3; ++i) { - finalAlphas[i] = 0; - } - } + neighborIterator(levelSet->getDomain()), alphaFactor(a) {} static void setMaxDissipation(double maxDiss) { maxDissipation = maxDiss; } @@ -232,7 +228,7 @@ class StencilLocalLaxFriedrichsScalar { auto &grid = levelSet->getGrid(); double gridDelta = grid.getGridDelta(); - Vec3D coordinate{0., 0., 0.}; + Vec3D coordinate{}; for (unsigned i = 0; i < D; ++i) { coordinate[i] = indices[i] * gridDelta; } @@ -243,7 +239,7 @@ class StencilLocalLaxFriedrichsScalar { // if there is a vector velocity, we need to project it onto a scalar // velocity first using its normal vector // /*if (vectorVelocity != Vec3D({}))*/ { - Vec3D normalVector; + Vec3D normalVector{}; T denominator = 0; // normal modulus for (unsigned i = 0; i < D; i++) { viennahrle::Index neighborIndex(0); diff --git a/include/viennals/lsToDiskMesh.hpp b/include/viennals/lsToDiskMesh.hpp index e2e67d49..baee36f7 100644 --- a/include/viennals/lsToDiskMesh.hpp +++ b/include/viennals/lsToDiskMesh.hpp @@ -116,8 +116,8 @@ template class ToDiskMesh { std::vector materialIds; // save the extent of the resulting mesh - Vec3D minimumExtent; - Vec3D maximumExtent; + Vec3D minimumExtent{}; + Vec3D maximumExtent{}; for (unsigned i = 0; i < D; ++i) { minimumExtent[i] = std::numeric_limits::max(); maximumExtent[i] = std::numeric_limits::lowest(); @@ -181,7 +181,7 @@ template class ToDiskMesh { // insert corresponding node shifted by ls value in direction of the // normal vector - Vec3D node; + Vec3D node{}; node[2] = 0.; double max = 0.; for (unsigned i = 0; i < D; ++i) { @@ -210,7 +210,7 @@ template class ToDiskMesh { // add data into mesh // copy normal - Vec3D normal; + Vec3D normal{}; if (D == 2) normal[2] = 0.; for (unsigned i = 0; i < D; ++i) { diff --git a/include/viennals/lsToMesh.hpp b/include/viennals/lsToMesh.hpp index 2edc24a3..3669dc92 100644 --- a/include/viennals/lsToMesh.hpp +++ b/include/viennals/lsToMesh.hpp @@ -109,7 +109,7 @@ template class ToMesh { mesh->insertNextVertex(vertex); // insert corresponding node - Vec3D node; + Vec3D node{}; if (D == 2) node[2] = 0.; for (unsigned i = 0; i < D; ++i) { diff --git a/include/viennals/lsToSurfaceMesh.hpp b/include/viennals/lsToSurfaceMesh.hpp index 61fbb7ce..36e4f857 100644 --- a/include/viennals/lsToSurfaceMesh.hpp +++ b/include/viennals/lsToSurfaceMesh.hpp @@ -140,7 +140,7 @@ template class ToSurfaceMesh { } else { // if node does not exist yet // calculate coordinate of new node - Vec3D cc = {0., 0., 0.}; // initialise with zeros + Vec3D cc{}; // initialise with zeros std::size_t currentPointId = 0; for (int z = 0; z < D; z++) { if (z != dir) { diff --git a/include/viennals/lsToVoxelMesh.hpp b/include/viennals/lsToVoxelMesh.hpp index f6befa7b..97b36978 100644 --- a/include/viennals/lsToVoxelMesh.hpp +++ b/include/viennals/lsToVoxelMesh.hpp @@ -198,7 +198,7 @@ template class ToVoxelMesh { double gridDelta = grid.getGridDelta(); mesh->nodes.resize(pointIdMapping.size()); for (auto it = pointIdMapping.begin(); it != pointIdMapping.end(); ++it) { - Vec3D coords; + Vec3D coords{}; for (unsigned i = 0; i < D; ++i) { coords[i] = gridDelta * it->first[i]; diff --git a/include/viennals/lsWENO5.hpp b/include/viennals/lsWENO5.hpp index 5e6d5a6b..c69a71ff 100644 --- a/include/viennals/lsWENO5.hpp +++ b/include/viennals/lsWENO5.hpp @@ -14,34 +14,36 @@ namespace lsInternal { using namespace viennacore; -/// Fifth-order Weighted Essentially Non-Oscillatory (WENO5) scheme. +/// Weighted Essentially Non-Oscillatory (WENO) scheme. /// This kernel acts as the grid-interface for the mathematical logic /// defined in lsFiniteDifferences.hpp. -template class WENO5 { +template class WENO { SmartPointer> levelSet; SmartPointer> velocities; - // Iterator depth: WENO5 needs 3 neighbors on each side. - viennahrle::SparseStarIterator, order> + static constexpr int stencilRadius = (order + 1) / 2; + + // Iterator depth: WENO needs stencilRadius neighbors on each side. + viennahrle::SparseStarIterator, stencilRadius> neighborIterator; const bool calculateNormalVectors = true; - // Use the existing math engine with WENO5 scheme - using MathScheme = FiniteDifferences; + // Use the existing math engine with WENO scheme + using MathScheme = FiniteDifferences< + T, (order == 3) ? DifferentiationSchemeEnum::WENO3 + : DifferentiationSchemeEnum::WENO5>; static T pow2(const T &value) { return value * value; } public: static void prepareLS(SmartPointer> passedlsDomain) { - // WENO5 uses a 7-point stencil (radius 3). - // Ensure we expand enough layers to access neighbors at +/- 3. - static_assert(order >= 3, "WENO5 requires an iterator order of at least 3"); - viennals::Expand(passedlsDomain, 2 * order + 1).apply(); + // Ensure we expand enough layers to access neighbors. + viennals::Expand(passedlsDomain, 2 * stencilRadius + 1).apply(); } - WENO5(SmartPointer> passedlsDomain, - SmartPointer> vel, bool calcNormal = true) + WENO(SmartPointer> passedlsDomain, + SmartPointer> vel, bool calcNormal = true) : levelSet(passedlsDomain), velocities(vel), neighborIterator(levelSet->getDomain()), calculateNormalVectors(calcNormal) {} @@ -66,29 +68,23 @@ template class WENO5 { T wenoGradMinus[D]; // Approximates derivative from left (phi_x^-) T wenoGradPlus[D]; // Approximates derivative from right (phi_x^+) - // Array to hold the stencil values: [x-3, x-2, x-1, Center, x+1, x+2, x+3] - T stencil[7]; + // Array to hold the stencil values + T stencil[2 * stencilRadius + 1]; for (int i = 0; i < D; i++) { // 1. GATHER STENCIL // We map the SparseStarIterator (which uses encoded directions) // to the flat array expected by FiniteDifferences. - // Center (Index 3 in the size-7 array) - stencil[3] = neighborIterator.getCenter().getValue(); - - // Neighbors +/- 1 - stencil[4] = neighborIterator.getNeighbor(i).getValue(); // +1 - stencil[2] = neighborIterator.getNeighbor(i + D).getValue(); // -1 - - // Neighbors +/- 2 - // Note: SparseStarIterator encodes higher distances sequentially - stencil[5] = neighborIterator.getNeighbor(D * 2 + i).getValue(); // +2 - stencil[1] = neighborIterator.getNeighbor(D * 2 + D + i).getValue(); // -2 + // Center + stencil[stencilRadius] = neighborIterator.getCenter().getValue(); - // Neighbors +/- 3 - stencil[6] = neighborIterator.getNeighbor(D * 4 + i).getValue(); // +3 - stencil[0] = neighborIterator.getNeighbor(D * 4 + D + i).getValue(); // -3 + for (int k = 1; k <= stencilRadius; ++k) { + stencil[stencilRadius + k] = + neighborIterator.getNeighbor((k - 1) * 2 * D + i).getValue(); + stencil[stencilRadius - k] = + neighborIterator.getNeighbor((k - 1) * 2 * D + D + i).getValue(); + } // 2. COMPUTE DERIVATIVES // Delegate the math to your existing library and store results @@ -111,7 +107,7 @@ template class WENO5 { T vel_grad = 0.; // --- Standard Normal Vector Calculation (for velocity lookup) --- - Vec3D normalVector = {}; + Vec3D normalVector{}; if (calculateNormalVectors) { T denominator = 0; for (int i = 0; i < D; i++) { @@ -163,14 +159,16 @@ template class WENO5 { } void reduceTimeStepHamiltonJacobi(double &MaxTimeStep, - double gridDelta) const { - // --- STABILITY IMPROVEMENT --- - // High-order schemes like WENO5 combined with simple time integration (like - // the likely Forward Euler used in Advect) can be less stable at CFL=0.5. - // We enforce a safety factor here to ensure robustness. - constexpr double wenoSafetyFactor = 0.5; - MaxTimeStep *= wenoSafetyFactor; - } + double gridDelta) const {} + // // --- STABILITY IMPROVEMENT --- + // // High-order schemes like WENO5 combined with simple time integration + // (like + // // the likely Forward Euler used in Advect) can be less stable at + // CFL=0.5. + // // We enforce a safety factor here to ensure robustness. + // constexpr double wenoSafetyFactor = 0.5; + // MaxTimeStep *= wenoSafetyFactor; + // } }; } // namespace lsInternal \ No newline at end of file diff --git a/python/pyWrap.cpp b/python/pyWrap.cpp index f231b5c7..6b7420d8 100644 --- a/python/pyWrap.cpp +++ b/python/pyWrap.cpp @@ -103,6 +103,7 @@ PYBIND11_MODULE(VIENNALS_MODULE_NAME, module) { SpatialSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER) .value("STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER", SpatialSchemeEnum::STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER) + .value("WENO_3RD_ORDER", SpatialSchemeEnum::WENO_3RD_ORDER) .value("WENO_5TH_ORDER", SpatialSchemeEnum::WENO_5TH_ORDER) .finalize(); diff --git a/python/viennals/_core.pyi b/python/viennals/_core.pyi index 3adc3a1c..b049d566 100644 --- a/python/viennals/_core.pyi +++ b/python/viennals/_core.pyi @@ -376,7 +376,8 @@ class SpatialSchemeEnum(enum.IntEnum): LOCAL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER: typing.ClassVar[SpatialSchemeEnum] # value = LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER: typing.ClassVar[SpatialSchemeEnum] # value = STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER: typing.ClassVar[SpatialSchemeEnum] # value = - WENO_5TH_ORDER: typing.ClassVar[SpatialSchemeEnum] # value = + WENO_3RD_ORDER: typing.ClassVar[SpatialSchemeEnum] # value = + WENO_5TH_ORDER: typing.ClassVar[SpatialSchemeEnum] # value = @classmethod def __new__(cls, value): ... diff --git a/tests/Advection/Advection.cpp b/tests/Advection/Advection.cpp index 876fdae3..7a3ea988 100644 --- a/tests/Advection/Advection.cpp +++ b/tests/Advection/Advection.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -45,7 +46,7 @@ class velocityField : public ls::VelocityField { int main() { constexpr int D = 3; - omp_set_num_threads(1); + omp_set_num_threads(8); double gridDelta = 0.6999999; @@ -58,6 +59,7 @@ int main() { ls::SpatialSchemeEnum::LOCAL_LOCAL_LAX_FRIEDRICHS_2ND_ORDER, ls::SpatialSchemeEnum::LOCAL_LAX_FRIEDRICHS_1ST_ORDER, ls::SpatialSchemeEnum::LOCAL_LAX_FRIEDRICHS_2ND_ORDER, + ls::SpatialSchemeEnum::WENO_3RD_ORDER, ls::SpatialSchemeEnum::WENO_5TH_ORDER}; for (auto scheme : spatialSchemes) { @@ -97,19 +99,47 @@ int main() { advectionKernel.setSaveAdvectionVelocities(true); double time = 0.; - for (unsigned i = 0; time < 1.0 && i < 1e2; ++i) { + for (unsigned i = 0; time < 1.0 && i < 50; ++i) { advectionKernel.apply(); time += advectionKernel.getAdvectedTime(); - std::string fileName = std::to_string(i) + ".vtp"; - auto mesh = ls::Mesh<>::New(); - ls::ToMesh(sphere1, mesh).apply(); - ls::VTKWriter(mesh, "points_" + fileName).apply(); - ls::ToSurfaceMesh(sphere1, mesh).apply(); - ls::VTKWriter(mesh, "surface_" + fileName).apply(); + // std::string fileName = std::to_string(i) + ".vtp"; + // auto mesh = ls::Mesh<>::New(); + // ls::ToMesh(sphere1, mesh).apply(); + // ls::VTKWriter(mesh, "points_" + fileName).apply(); + // ls::ToSurfaceMesh(sphere1, mesh).apply(); + // ls::VTKWriter(mesh, "surface_" + fileName).apply(); } LSTEST_ASSERT_VALID_LS(sphere1, double, D) + + // Check critical dimensions against initial state + auto sphereRef = ls::Domain::New(gridDelta); + ls::MakeGeometry(sphereRef, + ls::Sphere::New(origin, radius)) + .apply(); + + ls::CompareCriticalDimensions compare(sphereRef, sphere1); + std::array minB, maxB; + minB.fill(std::numeric_limits::lowest()); + maxB.fill(std::numeric_limits::max()); + + // Measure Max X (Growth velocity ~3.3) + compare.addRange(0, minB, maxB, true); + // Measure Min X (Growth velocity ~1.5) + compare.addRange(0, minB, maxB, false); + // Measure Max Y (Growth velocity ~1.0) + compare.addRange(1, minB, maxB, true); + + compare.apply(); + + double posRef, posSample, diffx, diffy, diffz; + + compare.getCriticalDimensionResult(0, posRef, posSample, diffx); + compare.getCriticalDimensionResult(1, posRef, posSample, diffy); + compare.getCriticalDimensionResult(2, posRef, posSample, diffz); + VC_TEST_ASSERT(diffx > 3.45 && diffx < 3.55 && diffy > 1.45 && + diffy < 1.65 && diffz > 0.85 && diffz < 1.10); } // std::cout << sphere1->getNumberOfPoints() << std::endl; diff --git a/tests/Advection/StencilLaxFriedrichsTest.cpp b/tests/Advection/StencilLaxFriedrichsTest.cpp index 24429b1b..4efc2fef 100644 --- a/tests/Advection/StencilLaxFriedrichsTest.cpp +++ b/tests/Advection/StencilLaxFriedrichsTest.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -43,14 +44,14 @@ int main() { // Create initial level set (Sphere) auto sphere = ls::SmartPointer>::New(bounds, boundaryCons, gridDelta); - T origin[3] = {0.0, 0.0, 0.0}; + T origin[3]{}; T radius = 1.0; ls::MakeGeometry(sphere, ls::Sphere::New(origin, radius)).apply(); // Output initial geometry - auto mesh = ls::SmartPointer>::New(); - ls::ToSurfaceMesh(sphere, mesh).apply(); - ls::VTKWriter(mesh, "sphere_initial.vtp").apply(); + // auto mesh = ls::SmartPointer>::New(); + // ls::ToSurfaceMesh(sphere, mesh).apply(); + // ls::VTKWriter(mesh, "sphere_initial.vtp").apply(); // Define constant velocity field auto velocityField = ls::SmartPointer>::New(); @@ -72,9 +73,19 @@ int main() { // Verify the result is a valid level set LSTEST_ASSERT_VALID_LS(sphere, T, D); + // Check result against analytical solution + auto sphereRef = + ls::SmartPointer>::New(bounds, boundaryCons, gridDelta); + ls::MakeGeometry(sphereRef, ls::Sphere::New(origin, radius + 0.5)) + .apply(); + + auto chamfer = ls::CompareChamfer(sphereRef, sphere); + chamfer.apply(); + VC_TEST_ASSERT(chamfer.getChamferDistance() < 0.035); + // Output final geometry - ls::ToSurfaceMesh(sphere, mesh).apply(); - ls::VTKWriter(mesh, "sphere_final.vtp").apply(); + // ls::ToSurfaceMesh(sphere, mesh).apply(); + // ls::VTKWriter(mesh, "sphere_final.vtp").apply(); std::cout << "Test passed!" << std::endl; diff --git a/tests/Advection/TimeIntegrationComparison.cpp b/tests/Advection/TimeIntegrationComparison.cpp index 60f713d9..04a9b28f 100644 --- a/tests/Advection/TimeIntegrationComparison.cpp +++ b/tests/Advection/TimeIntegrationComparison.cpp @@ -2,12 +2,14 @@ #include #include +#include #include #include #include #include #include #include +#include namespace ls = viennals; @@ -36,6 +38,8 @@ int main() { // Define dimension and type constexpr int D = 3; using T = double; + omp_set_num_threads(8); + // Define grid and domain bounds double gridDelta = 0.1; @@ -47,14 +51,32 @@ int main() { // Create initial level set (Sphere) auto sphere = ls::SmartPointer>::New(bounds, boundaryCons, gridDelta); - T origin[3] = {0.0, 0.0, 0.0}; + T origin[3]{}; T radius = 1.5; ls::MakeGeometry(sphere, ls::Sphere::New(origin, radius)).apply(); + // Create analytical reference for error calculation at t=2.0. + // The sphere starts at x=0 and moves with v=1 for t=2, so it ends at x=2. + auto sphereRef = + ls::SmartPointer>::New(bounds, boundaryCons, gridDelta); + T originRef[3] = {2.0, 0.0, 0.0}; + ls::MakeGeometry(sphereRef, ls::Sphere::New(originRef, radius)) + .apply(); + // Create copies for Forward Euler, RK2 and RK3 auto sphereFE = ls::SmartPointer>::New(sphere); auto sphereRK2 = ls::SmartPointer>::New(sphere); auto sphereRK3 = ls::SmartPointer>::New(sphere); + auto sphereWENO3_FE = ls::SmartPointer>::New(sphere); + auto sphereWENO3_RK2 = ls::SmartPointer>::New(sphere); + auto sphereWENO3_RK3 = ls::SmartPointer>::New(sphere); + auto sphereWENO5_FE = ls::SmartPointer>::New(sphere); + auto sphereWENO5_RK2 = ls::SmartPointer>::New(sphere); + auto sphereWENO5_RK3 = ls::SmartPointer>::New(sphere); + + auto meshInit = ls::Mesh::New(); + ls::ToSurfaceMesh(sphere, meshInit).apply(); + ls::VTKWriter(meshInit, "sphereInit.vtp").apply(); // Define constant velocity field (moving in x-direction) std::array vel = {1.0, 0.0, 0.0}; @@ -84,30 +106,203 @@ int main() { advectRK3.setSpatialScheme(ls::SpatialSchemeEnum::ENGQUIST_OSHER_1ST_ORDER); advectRK3.setTemporalScheme(ls::TemporalSchemeEnum::RUNGE_KUTTA_3RD_ORDER); + // Setup Advection: WENO3 Forward Euler + ls::Advect advectWENO3_FE; + advectWENO3_FE.insertNextLevelSet(sphereWENO3_FE); + advectWENO3_FE.setVelocityField(velocityField); + advectWENO3_FE.setAdvectionTime(2.0); + advectWENO3_FE.setSpatialScheme(ls::SpatialSchemeEnum::WENO_3RD_ORDER); + advectWENO3_FE.setTemporalScheme(ls::TemporalSchemeEnum::FORWARD_EULER); + + // Setup Advection: WENO3 Runge-Kutta 2 + ls::Advect advectWENO3_RK2; + advectWENO3_RK2.insertNextLevelSet(sphereWENO3_RK2); + advectWENO3_RK2.setVelocityField(velocityField); + advectWENO3_RK2.setAdvectionTime(2.0); + advectWENO3_RK2.setSpatialScheme(ls::SpatialSchemeEnum::WENO_3RD_ORDER); + advectWENO3_RK2.setTemporalScheme( + ls::TemporalSchemeEnum::RUNGE_KUTTA_2ND_ORDER); + + // Setup Advection: WENO3 Runge-Kutta 3 + ls::Advect advectWENO3_RK3; + advectWENO3_RK3.insertNextLevelSet(sphereWENO3_RK3); + advectWENO3_RK3.setVelocityField(velocityField); + advectWENO3_RK3.setAdvectionTime(2.0); + advectWENO3_RK3.setSpatialScheme(ls::SpatialSchemeEnum::WENO_3RD_ORDER); + advectWENO3_RK3.setTemporalScheme( + ls::TemporalSchemeEnum::RUNGE_KUTTA_3RD_ORDER); + + // Setup Advection: WENO5 Forward Euler + ls::Advect advectWENO5_FE; + advectWENO5_FE.insertNextLevelSet(sphereWENO5_FE); + advectWENO5_FE.setVelocityField(velocityField); + advectWENO5_FE.setAdvectionTime(2.0); + advectWENO5_FE.setSpatialScheme(ls::SpatialSchemeEnum::WENO_5TH_ORDER); + advectWENO5_FE.setTemporalScheme(ls::TemporalSchemeEnum::FORWARD_EULER); + + // Setup Advection: WENO5 Runge-Kutta 2 + ls::Advect advectWENO5_RK2; + advectWENO5_RK2.insertNextLevelSet(sphereWENO5_RK2); + advectWENO5_RK2.setVelocityField(velocityField); + advectWENO5_RK2.setAdvectionTime(2.0); + advectWENO5_RK2.setSpatialScheme(ls::SpatialSchemeEnum::WENO_5TH_ORDER); + advectWENO5_RK2.setTemporalScheme( + ls::TemporalSchemeEnum::RUNGE_KUTTA_2ND_ORDER); + + // Setup Advection: WENO5 Runge-Kutta 3 + ls::Advect advectWENO5_RK3; + advectWENO5_RK3.insertNextLevelSet(sphereWENO5_RK3); + advectWENO5_RK3.setVelocityField(velocityField); + advectWENO5_RK3.setAdvectionTime(2.0); + advectWENO5_RK3.setSpatialScheme(ls::SpatialSchemeEnum::WENO_5TH_ORDER); + advectWENO5_RK3.setTemporalScheme( + ls::TemporalSchemeEnum::RUNGE_KUTTA_3RD_ORDER); + // Run Advection std::cout << "Running Forward Euler Advection..." << std::endl; + viennacore::Timer timer; + timer.start(); advectFE.apply(); + timer.finish(); + std::cout << "Time: " << timer.currentDuration / 1e9 << "s" << std::endl; LSTEST_ASSERT_VALID_LS(sphereFE, T, D); auto meshFE = ls::Mesh::New(); ls::ToSurfaceMesh(sphereFE, meshFE).apply(); ls::VTKWriter(meshFE, "sphereFE.vtp").apply(); + auto chamferFE = ls::CompareChamfer(sphereRef, sphereFE); + chamferFE.apply(); + std::cout << "Chamfer distance: " << chamferFE.getChamferDistance() << std::endl; + VC_TEST_ASSERT(chamferFE.getChamferDistance() < 0.038); + std::cout << "Running Runge-Kutta 2 Advection..." << std::endl; + timer.start(); advectRK2.apply(); + timer.finish(); + std::cout << "Time: " << timer.currentDuration / 1e9 << "s" << std::endl; LSTEST_ASSERT_VALID_LS(sphereRK2, T, D); auto meshRK2 = ls::Mesh::New(); ls::ToSurfaceMesh(sphereRK2, meshRK2).apply(); ls::VTKWriter(meshRK2, "sphereRK2.vtp").apply(); + auto chamferRK2 = ls::CompareChamfer(sphereRef, sphereRK2); + chamferRK2.apply(); + std::cout << "Chamfer distance: " << chamferRK2.getChamferDistance() << std::endl; + VC_TEST_ASSERT(chamferRK2.getChamferDistance() < 0.067); + std::cout << "Running Runge-Kutta 3 Advection..." << std::endl; + timer.start(); advectRK3.apply(); + timer.finish(); + std::cout << "Time: " << timer.currentDuration / 1e9 << "s" << std::endl; LSTEST_ASSERT_VALID_LS(sphereRK3, T, D); auto meshRK3 = ls::Mesh::New(); ls::ToSurfaceMesh(sphereRK3, meshRK3).apply(); ls::VTKWriter(meshRK3, "sphereRK3.vtp").apply(); + auto chamferRK3 = ls::CompareChamfer(sphereRef, sphereRK3); + chamferRK3.apply(); + std::cout << "Chamfer distance: " << chamferRK3.getChamferDistance() << std::endl; + VC_TEST_ASSERT(chamferRK3.getChamferDistance() < 0.068); + + std::cout << "Running WENO3 Forward Euler Advection..." << std::endl; + timer.start(); + advectWENO3_FE.apply(); + timer.finish(); + std::cout << "Time: " << timer.currentDuration / 1e9 << "s" << std::endl; + LSTEST_ASSERT_VALID_LS(sphereWENO3_FE, T, D); + + auto meshWENO3_FE = ls::Mesh::New(); + ls::ToSurfaceMesh(sphereWENO3_FE, meshWENO3_FE).apply(); + ls::VTKWriter(meshWENO3_FE, "sphereWENO3_FE.vtp").apply(); + + auto chamferWENO3_FE = ls::CompareChamfer(sphereRef, sphereWENO3_FE); + chamferWENO3_FE.apply(); + std::cout << "Chamfer distance: " << chamferWENO3_FE.getChamferDistance() << std::endl; + VC_TEST_ASSERT(chamferWENO3_FE.getChamferDistance() < 0.025); + + std::cout << "Running WENO3 Runge-Kutta 2 Advection..." << std::endl; + timer.start(); + advectWENO3_RK2.apply(); + timer.finish(); + std::cout << "Time: " << timer.currentDuration / 1e9 << "s" << std::endl; + LSTEST_ASSERT_VALID_LS(sphereWENO3_RK2, T, D); + + auto meshWENO3_RK2 = ls::Mesh::New(); + ls::ToSurfaceMesh(sphereWENO3_RK2, meshWENO3_RK2).apply(); + ls::VTKWriter(meshWENO3_RK2, "sphereWENO3_RK2.vtp").apply(); + + auto chamferWENO3_RK2 = ls::CompareChamfer(sphereRef, sphereWENO3_RK2); + chamferWENO3_RK2.apply(); + std::cout << "Chamfer distance: " << chamferWENO3_RK2.getChamferDistance() << std::endl; + VC_TEST_ASSERT(chamferWENO3_RK2.getChamferDistance() < 0.01); + + std::cout << "Running WENO3 Runge-Kutta 3 Advection..." << std::endl; + timer.start(); + advectWENO3_RK3.apply(); + timer.finish(); + std::cout << "Time: " << timer.currentDuration / 1e9 << "s" << std::endl; + LSTEST_ASSERT_VALID_LS(sphereWENO3_RK3, T, D); + + auto meshWENO3_RK3 = ls::Mesh::New(); + ls::ToSurfaceMesh(sphereWENO3_RK3, meshWENO3_RK3).apply(); + ls::VTKWriter(meshWENO3_RK3, "sphereWENO3_RK3.vtp").apply(); + + auto chamferWENO3_RK3 = ls::CompareChamfer(sphereRef, sphereWENO3_RK3); + chamferWENO3_RK3.apply(); + std::cout << "Chamfer distance: " << chamferWENO3_RK3.getChamferDistance() << std::endl; + VC_TEST_ASSERT(chamferWENO3_RK3.getChamferDistance() < 0.01); + + std::cout << "Running WENO5 Forward Euler Advection..." << std::endl; + timer.start(); + advectWENO5_FE.apply(); + timer.finish(); + std::cout << "Time: " << timer.currentDuration / 1e9 << "s" << std::endl; + LSTEST_ASSERT_VALID_LS(sphereWENO5_FE, T, D); + + auto meshWENO5_FE = ls::Mesh::New(); + ls::ToSurfaceMesh(sphereWENO5_FE, meshWENO5_FE).apply(); + ls::VTKWriter(meshWENO5_FE, "sphereWENO5_FE.vtp").apply(); + + auto chamferWENO5_FE = ls::CompareChamfer(sphereRef, sphereWENO5_FE); + chamferWENO5_FE.apply(); + std::cout << "Chamfer distance: " << chamferWENO5_FE.getChamferDistance() << std::endl; + VC_TEST_ASSERT(chamferWENO5_FE.getChamferDistance() < 0.018); + + std::cout << "Running WENO5 Runge-Kutta 2 Advection..." << std::endl; + timer.start(); + advectWENO5_RK2.apply(); + timer.finish(); + std::cout << "Time: " << timer.currentDuration / 1e9 << "s" << std::endl; + LSTEST_ASSERT_VALID_LS(sphereWENO5_RK2, T, D); + + auto meshWENO5_RK2 = ls::Mesh::New(); + ls::ToSurfaceMesh(sphereWENO5_RK2, meshWENO5_RK2).apply(); + ls::VTKWriter(meshWENO5_RK2, "sphereWENO5_RK2.vtp").apply(); + + auto chamferWENO5_RK2 = ls::CompareChamfer(sphereRef, sphereWENO5_RK2); + chamferWENO5_RK2.apply(); + std::cout << "Chamfer distance: " << chamferWENO5_RK2.getChamferDistance() << std::endl; + VC_TEST_ASSERT(chamferWENO5_RK2.getChamferDistance() < 0.004); + + std::cout << "Running WENO5 Runge-Kutta 3 Advection..." << std::endl; + timer.start(); + advectWENO5_RK3.apply(); + timer.finish(); + std::cout << "Time: " << timer.currentDuration / 1e9 << "s" << std::endl; + LSTEST_ASSERT_VALID_LS(sphereWENO5_RK3, T, D); + + auto meshWENO5_RK3 = ls::Mesh::New(); + ls::ToSurfaceMesh(sphereWENO5_RK3, meshWENO5_RK3).apply(); + ls::VTKWriter(meshWENO5_RK3, "sphereWENO5_RK3.vtp").apply(); + + auto chamferWENO5_RK3 = ls::CompareChamfer(sphereRef, sphereWENO5_RK3); + chamferWENO5_RK3.apply(); + std::cout << "Chamfer distance: " << chamferWENO5_RK3.getChamferDistance() << std::endl; + VC_TEST_ASSERT(chamferWENO5_RK3.getChamferDistance() < 0.0034); + return 0; } diff --git a/tests/BooleanOperationExactZero/BooleanOperationExactZero.cpp b/tests/BooleanOperationExactZero/BooleanOperationExactZero.cpp index dd77d52f..91b4aa0c 100644 --- a/tests/BooleanOperationExactZero/BooleanOperationExactZero.cpp +++ b/tests/BooleanOperationExactZero/BooleanOperationExactZero.cpp @@ -76,8 +76,8 @@ int main() { // now make substrate ( plane ) at the same height as the bottom of the mask auto substrate = LSType::New(mask->getGrid()); { - double origin[D] = {}; - double normal[D] = {}; + double origin[D]{}; + double normal[D]{}; origin[D - 1] = 1.; normal[D - 1] = 1.; ls::MakeGeometry( diff --git a/tests/GeometricAdvectMask/GeometricAdvectMask.cpp b/tests/GeometricAdvectMask/GeometricAdvectMask.cpp index ba104199..6d3382cb 100644 --- a/tests/GeometricAdvectMask/GeometricAdvectMask.cpp +++ b/tests/GeometricAdvectMask/GeometricAdvectMask.cpp @@ -43,7 +43,7 @@ int main() { // create mask { NumericType normal[3] = {0., (D == 2) ? 1. : 0., (D == 3) ? 1. : 0.}; - NumericType origin[3] = {}; + NumericType origin[3]{}; ls::MakeGeometry( mask, ls::SmartPointer>::New(origin, normal)) .apply(); From 1fd1f354ba6f089e0f52ff17586356a30e3721ac Mon Sep 17 00:00:00 2001 From: filipovic Date: Thu, 15 Jan 2026 01:27:51 +0100 Subject: [PATCH 49/57] rename lsWENO5.hpp to lsWENO.hpp --- include/viennals/lsAdvect.hpp | 2 +- include/viennals/{lsWENO5.hpp => lsWENO.hpp} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename include/viennals/{lsWENO5.hpp => lsWENO.hpp} (100%) diff --git a/include/viennals/lsAdvect.hpp b/include/viennals/lsAdvect.hpp index ae8dbc81..64ad12b6 100644 --- a/include/viennals/lsAdvect.hpp +++ b/include/viennals/lsAdvect.hpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include // Include implementation of time integration schemes #include diff --git a/include/viennals/lsWENO5.hpp b/include/viennals/lsWENO.hpp similarity index 100% rename from include/viennals/lsWENO5.hpp rename to include/viennals/lsWENO.hpp From 66e6105542df750542e66194f645d7b7294d4912 Mon Sep 17 00:00:00 2001 From: filipovic Date: Thu, 15 Jan 2026 10:43:01 +0100 Subject: [PATCH 50/57] assert WENO order 3 or 5 only is permitted --- include/viennals/lsWENO.hpp | 8 ++++-- tests/Advection/TimeIntegrationComparison.cpp | 28 ++++++++++++------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/include/viennals/lsWENO.hpp b/include/viennals/lsWENO.hpp index c69a71ff..5fbbc2b2 100644 --- a/include/viennals/lsWENO.hpp +++ b/include/viennals/lsWENO.hpp @@ -18,6 +18,8 @@ using namespace viennacore; /// This kernel acts as the grid-interface for the mathematical logic /// defined in lsFiniteDifferences.hpp. template class WENO { + static_assert(order == 3 || order == 5, "WENO order must be 3 or 5."); + SmartPointer> levelSet; SmartPointer> velocities; @@ -30,9 +32,9 @@ template class WENO { const bool calculateNormalVectors = true; // Use the existing math engine with WENO scheme - using MathScheme = FiniteDifferences< - T, (order == 3) ? DifferentiationSchemeEnum::WENO3 - : DifferentiationSchemeEnum::WENO5>; + using MathScheme = + FiniteDifferences; static T pow2(const T &value) { return value * value; } diff --git a/tests/Advection/TimeIntegrationComparison.cpp b/tests/Advection/TimeIntegrationComparison.cpp index 04a9b28f..c3d86d0d 100644 --- a/tests/Advection/TimeIntegrationComparison.cpp +++ b/tests/Advection/TimeIntegrationComparison.cpp @@ -40,7 +40,6 @@ int main() { using T = double; omp_set_num_threads(8); - // Define grid and domain bounds double gridDelta = 0.1; double bounds[2 * D] = {-5.0, 5.0, -5.0, 5.0, -5.0, 5.0}; @@ -173,7 +172,8 @@ int main() { auto chamferFE = ls::CompareChamfer(sphereRef, sphereFE); chamferFE.apply(); - std::cout << "Chamfer distance: " << chamferFE.getChamferDistance() << std::endl; + std::cout << "Chamfer distance: " << chamferFE.getChamferDistance() + << std::endl; VC_TEST_ASSERT(chamferFE.getChamferDistance() < 0.038); std::cout << "Running Runge-Kutta 2 Advection..." << std::endl; @@ -189,7 +189,8 @@ int main() { auto chamferRK2 = ls::CompareChamfer(sphereRef, sphereRK2); chamferRK2.apply(); - std::cout << "Chamfer distance: " << chamferRK2.getChamferDistance() << std::endl; + std::cout << "Chamfer distance: " << chamferRK2.getChamferDistance() + << std::endl; VC_TEST_ASSERT(chamferRK2.getChamferDistance() < 0.067); std::cout << "Running Runge-Kutta 3 Advection..." << std::endl; @@ -205,7 +206,8 @@ int main() { auto chamferRK3 = ls::CompareChamfer(sphereRef, sphereRK3); chamferRK3.apply(); - std::cout << "Chamfer distance: " << chamferRK3.getChamferDistance() << std::endl; + std::cout << "Chamfer distance: " << chamferRK3.getChamferDistance() + << std::endl; VC_TEST_ASSERT(chamferRK3.getChamferDistance() < 0.068); std::cout << "Running WENO3 Forward Euler Advection..." << std::endl; @@ -221,7 +223,8 @@ int main() { auto chamferWENO3_FE = ls::CompareChamfer(sphereRef, sphereWENO3_FE); chamferWENO3_FE.apply(); - std::cout << "Chamfer distance: " << chamferWENO3_FE.getChamferDistance() << std::endl; + std::cout << "Chamfer distance: " << chamferWENO3_FE.getChamferDistance() + << std::endl; VC_TEST_ASSERT(chamferWENO3_FE.getChamferDistance() < 0.025); std::cout << "Running WENO3 Runge-Kutta 2 Advection..." << std::endl; @@ -237,7 +240,8 @@ int main() { auto chamferWENO3_RK2 = ls::CompareChamfer(sphereRef, sphereWENO3_RK2); chamferWENO3_RK2.apply(); - std::cout << "Chamfer distance: " << chamferWENO3_RK2.getChamferDistance() << std::endl; + std::cout << "Chamfer distance: " << chamferWENO3_RK2.getChamferDistance() + << std::endl; VC_TEST_ASSERT(chamferWENO3_RK2.getChamferDistance() < 0.01); std::cout << "Running WENO3 Runge-Kutta 3 Advection..." << std::endl; @@ -253,7 +257,8 @@ int main() { auto chamferWENO3_RK3 = ls::CompareChamfer(sphereRef, sphereWENO3_RK3); chamferWENO3_RK3.apply(); - std::cout << "Chamfer distance: " << chamferWENO3_RK3.getChamferDistance() << std::endl; + std::cout << "Chamfer distance: " << chamferWENO3_RK3.getChamferDistance() + << std::endl; VC_TEST_ASSERT(chamferWENO3_RK3.getChamferDistance() < 0.01); std::cout << "Running WENO5 Forward Euler Advection..." << std::endl; @@ -269,7 +274,8 @@ int main() { auto chamferWENO5_FE = ls::CompareChamfer(sphereRef, sphereWENO5_FE); chamferWENO5_FE.apply(); - std::cout << "Chamfer distance: " << chamferWENO5_FE.getChamferDistance() << std::endl; + std::cout << "Chamfer distance: " << chamferWENO5_FE.getChamferDistance() + << std::endl; VC_TEST_ASSERT(chamferWENO5_FE.getChamferDistance() < 0.018); std::cout << "Running WENO5 Runge-Kutta 2 Advection..." << std::endl; @@ -285,7 +291,8 @@ int main() { auto chamferWENO5_RK2 = ls::CompareChamfer(sphereRef, sphereWENO5_RK2); chamferWENO5_RK2.apply(); - std::cout << "Chamfer distance: " << chamferWENO5_RK2.getChamferDistance() << std::endl; + std::cout << "Chamfer distance: " << chamferWENO5_RK2.getChamferDistance() + << std::endl; VC_TEST_ASSERT(chamferWENO5_RK2.getChamferDistance() < 0.004); std::cout << "Running WENO5 Runge-Kutta 3 Advection..." << std::endl; @@ -301,7 +308,8 @@ int main() { auto chamferWENO5_RK3 = ls::CompareChamfer(sphereRef, sphereWENO5_RK3); chamferWENO5_RK3.apply(); - std::cout << "Chamfer distance: " << chamferWENO5_RK3.getChamferDistance() << std::endl; + std::cout << "Chamfer distance: " << chamferWENO5_RK3.getChamferDistance() + << std::endl; VC_TEST_ASSERT(chamferWENO5_RK3.getChamferDistance() < 0.0034); return 0; From 73071b5a53efc277aa4f685eb93a19743b6561fc Mon Sep 17 00:00:00 2001 From: filipovic Date: Thu, 15 Jan 2026 14:27:51 +0100 Subject: [PATCH 51/57] update ViennaCore and ViennaHRLE versions --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b61b8e7f..ceced024 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,7 +112,7 @@ include(cmake/vtk.cmake) CPMAddPackage( NAME ViennaCore - VERSION 1.9.0 + VERSION 1.9.3 GIT_REPOSITORY "https://github.com/ViennaTools/ViennaCore" OPTIONS "VIENNACORE_FORMAT_EXCLUDE build/" EXCLUDE_FROM_ALL ${VIENNALS_BUILD_PYTHON}) @@ -125,7 +125,7 @@ CPMAddPackage( CPMFindPackage( NAME ViennaHRLE - VERSION 0.7.0 + VERSION 0.8.0 GIT_REPOSITORY "https://github.com/ViennaTools/ViennaHRLE" EXCLUDE_FROM_ALL ${VIENNALS_BUILD_PYTHON}) From d2ed3ed82e66c7558d20d39b15951d6a735a5510 Mon Sep 17 00:00:00 2001 From: filipovic Date: Thu, 15 Jan 2026 16:39:33 +0100 Subject: [PATCH 52/57] remove double lines in lsCalcVisibilities --- include/viennals/lsCalculateVisibilities.hpp | 62 -------------------- 1 file changed, 62 deletions(-) diff --git a/include/viennals/lsCalculateVisibilities.hpp b/include/viennals/lsCalculateVisibilities.hpp index 26e75a17..45bee138 100644 --- a/include/viennals/lsCalculateVisibilities.hpp +++ b/include/viennals/lsCalculateVisibilities.hpp @@ -64,48 +64,25 @@ template class CalculateVisibilities { } } //**************************** - // Get the coordinate of the current point - auto point = it.getStartIndices(); - for (int i = 0; i < D; ++i) { - // Compare to update min and max defined points - T coord = point[i]; // * grid.getGridDelta(); - minDefinedPoint[i] = std::min(minDefinedPoint[i], coord); - maxDefinedPoint[i] = std::max(maxDefinedPoint[i], coord); - } - } - //**************************** #pragma omp parallel num_threads(levelSet->getNumberOfSegments()) { int p = 0; - { - int p = 0; #ifdef _OPENMP p = omp_get_thread_num(); - p = omp_get_thread_num(); #endif - const viennahrle::Index startVector = - (p == 0) ? grid.getMinGridPoint() : domain.getSegmentation()[p - 1]; const viennahrle::Index startVector = (p == 0) ? grid.getMinGridPoint() : domain.getSegmentation()[p - 1]; - const viennahrle::Index endVector = - (p != static_cast(domain.getNumberOfSegments() - 1)) - ? domain.getSegmentation()[p] - : grid.incrementIndices(grid.getMaxGridPoint()); const viennahrle::Index endVector = (p != static_cast(domain.getNumberOfSegments() - 1)) ? domain.getSegmentation()[p] : grid.incrementIndices(grid.getMaxGridPoint()); - for (viennahrle::SparseIterator it(domain, startVector); - it.getStartIndices() < endVector; ++it) { for (viennahrle::SparseIterator it(domain, startVector); it.getStartIndices() < endVector; ++it) { - if (!it.isDefined()) - continue; if (!it.isDefined()) continue; @@ -115,27 +92,16 @@ template class CalculateVisibilities { currentPos[i] = it.getStartIndices(i); } - // Start tracing the ray - T minLevelSetValue = it.getValue(); // Starting level set value - Vec3D rayPos = currentPos; // Start tracing the ray T minLevelSetValue = it.getValue(); // Starting level set value Vec3D rayPos = currentPos; - // Step once to skip immediate neighbor - for (int i = 0; i < D; ++i) - rayPos[i] += dir[i]; // Step once to skip immediate neighbor for (int i = 0; i < D; ++i) rayPos[i] += dir[i]; bool visibility = true; - bool visibility = true; - while (true) { - // Update the ray position - for (int i = 0; i < D; ++i) - rayPos[i] += dir[i]; while (true) { // Update the ray position for (int i = 0; i < D; ++i) @@ -143,10 +109,6 @@ template class CalculateVisibilities { // Determine the nearest grid cell (round to nearest index) viennahrle::Index nearestCell; - for (int i = 0; i < D; ++i) - nearestCell[i] = static_cast(rayPos[i]); - // Determine the nearest grid cell (round to nearest index) - viennahrle::Index nearestCell; for (int i = 0; i < D; ++i) nearestCell[i] = static_cast(rayPos[i]); @@ -159,26 +121,11 @@ template class CalculateVisibilities { break; } } - // Check if the nearest cell is within bounds - bool outOfBounds = false; - for (int i = 0; i < D; ++i) { - if (nearestCell[i] < minDefinedPoint[i] || - nearestCell[i] > maxDefinedPoint[i]) { - outOfBounds = true; - break; - } - } - if (outOfBounds) - break; // Ray is outside the grid if (outOfBounds) break; // Ray is outside the grid // Access the level set value at the nearest cell - T value = - viennahrle::SparseIterator(domain, nearestCell) - .getValue(); - // Access the level set value at the nearest cell T value = viennahrle::SparseIterator(domain, nearestCell) .getValue(); @@ -189,19 +136,10 @@ template class CalculateVisibilities { break; } } - // Update the minimum value encountered - if (value < minLevelSetValue) { - visibility = false; - break; - } - } // Update visibility for this point visibilities[it.getPointId()] = visibility ? 1.0 : 0.0; } - // Update visibility for this point - visibilities[it.getPointId()] = visibility ? 1.0 : 0.0; - } } } From 003e2f9734cb6e434b00857596e35e692cdd00f2 Mon Sep 17 00:00:00 2001 From: filipovic Date: Thu, 15 Jan 2026 17:00:06 +0100 Subject: [PATCH 53/57] remove code duplication resulting from a faulty merge --- .../viennals/lsCompareCriticalDimensions.hpp | 54 +------ include/viennals/lsCompareVolume.hpp | 2 +- include/viennals/lsMakeGeometry.hpp | 145 +----------------- 3 files changed, 3 insertions(+), 198 deletions(-) diff --git a/include/viennals/lsCompareCriticalDimensions.hpp b/include/viennals/lsCompareCriticalDimensions.hpp index d7a9a425..e8b0a6ee 100644 --- a/include/viennals/lsCompareCriticalDimensions.hpp +++ b/include/viennals/lsCompareCriticalDimensions.hpp @@ -38,9 +38,6 @@ template class CompareCriticalDimensions { // Structure to hold a range specification struct RangeSpec { - int measureDimension; - std::array minBounds; - std::array maxBounds; int measureDimension; std::array minBounds; std::array maxBounds; @@ -51,9 +48,6 @@ template class CompareCriticalDimensions { // Structure to hold critical dimension results struct CriticalDimensionResult { - int measureDimension; - std::array minBounds; - std::array maxBounds; int measureDimension; std::array minBounds; std::array maxBounds; @@ -99,21 +93,12 @@ template class CompareCriticalDimensions { // Returns the position coordinates (Y if isXRange=true, X if isXRange=false) std::vector findSurfaceCrossings(SmartPointer> surfaceMesh, const RangeSpec &spec) { - const RangeSpec &spec) { std::vector crossings; // Iterate through all surface mesh nodes for (const auto &node : surfaceMesh->nodes) { // Check if point is in our scan range bool inRange = true; - for (int i = 0; i < D; ++i) { - if (i == spec.measureDimension) - continue; - if (node[i] < spec.minBounds[i] || node[i] > spec.maxBounds[i]) { - inRange = false; - break; - } - bool inRange = true; for (int i = 0; i < D; ++i) { if (i == spec.measureDimension) continue; @@ -125,10 +110,6 @@ template class CompareCriticalDimensions { if (inRange) { T val = node[spec.measureDimension]; - if (val >= spec.minBounds[spec.measureDimension] && - val <= spec.maxBounds[spec.measureDimension]) { - crossings.push_back(val); - T val = node[spec.measureDimension]; if (val >= spec.minBounds[spec.measureDimension] && val <= spec.maxBounds[spec.measureDimension]) { crossings.push_back(val); @@ -155,13 +136,11 @@ template class CompareCriticalDimensions { public: CompareCriticalDimensions() {} - CompareCriticalDimensions() {} CompareCriticalDimensions(SmartPointer> passedLevelSetTarget, SmartPointer> passedLevelSetSample) : levelSetTarget(passedLevelSetTarget), levelSetSample(passedLevelSetSample) {} - levelSetSample(passedLevelSetSample) {} void setLevelSetTarget(SmartPointer> passedLevelSet) { levelSetTarget = passedLevelSet; @@ -171,9 +150,6 @@ template class CompareCriticalDimensions { levelSetSample = passedLevelSet; } - /// Add a generic range specification - void addRange(int measureDimension, const std::array &minBounds, - const std::array &maxBounds, bool findMaximum = true) { /// Add a generic range specification void addRange(int measureDimension, const std::array &minBounds, const std::array &maxBounds, bool findMaximum = true) { @@ -181,9 +157,6 @@ template class CompareCriticalDimensions { spec.measureDimension = measureDimension; spec.minBounds = minBounds; spec.maxBounds = maxBounds; - spec.measureDimension = measureDimension; - spec.minBounds = minBounds; - spec.maxBounds = maxBounds; spec.findMaximum = findMaximum; rangeSpecs.push_back(spec); } @@ -200,18 +173,6 @@ template class CompareCriticalDimensions { } } - /// Add an X range to find maximum or minimum Y position - void addXRange(T minX, T maxX, bool findMaximum = true) { - if constexpr (D == 2) { - std::array minBounds = {minX, std::numeric_limits::lowest()}; - std::array maxBounds = {maxX, std::numeric_limits::max()}; - addRange(1, minBounds, maxBounds, findMaximum); - } else { - VIENNACORE_LOG_WARNING( - "addXRange is only supported for 2D. Use addRange for 3D."); - } - } - /// Add a Y range to find maximum or minimum X position void addYRange(T minY, T maxY, bool findMaximum = true) { if constexpr (D == 2) { @@ -222,14 +183,6 @@ template class CompareCriticalDimensions { VIENNACORE_LOG_WARNING( "addYRange is only supported for 2D. Use addRange for 3D."); } - if constexpr (D == 2) { - std::array minBounds = {std::numeric_limits::lowest(), minY}; - std::array maxBounds = {std::numeric_limits::max(), maxY}; - addRange(0, minBounds, maxBounds, findMaximum); - } else { - VIENNACORE_LOG_WARNING( - "addYRange is only supported for 2D. Use addRange for 3D."); - } } /// Clear all range specifications @@ -269,17 +222,12 @@ template class CompareCriticalDimensions { result.measureDimension = spec.measureDimension; result.minBounds = spec.minBounds; result.maxBounds = spec.maxBounds; - result.measureDimension = spec.measureDimension; - result.minBounds = spec.minBounds; - result.maxBounds = spec.maxBounds; result.findMaximum = spec.findMaximum; result.valid = false; // Find all surface crossings from the mesh nodes auto crossingsRef = findSurfaceCrossings(surfaceMeshRef, spec); auto crossingsCmp = findSurfaceCrossings(surfaceMeshCmp, spec); - auto crossingsRef = findSurfaceCrossings(surfaceMeshRef, spec); - auto crossingsCmp = findSurfaceCrossings(surfaceMeshCmp, spec); // Find critical dimensions auto [validRef, cdRef] = @@ -448,4 +396,4 @@ template class CompareCriticalDimensions { // Add template specializations for this class PRECOMPILE_PRECISION_DIMENSION(CompareCriticalDimensions) -} // namespace viennals +} // namespace viennals \ No newline at end of file diff --git a/include/viennals/lsCompareVolume.hpp b/include/viennals/lsCompareVolume.hpp index dbc80b84..f568ef0b 100644 --- a/include/viennals/lsCompareVolume.hpp +++ b/include/viennals/lsCompareVolume.hpp @@ -413,4 +413,4 @@ template using CompareArea = CompareVolume; // Precompile for common precision and dimensions PRECOMPILE_PRECISION_DIMENSION(CompareVolume) -} // namespace viennals \ No newline at end of file +} // namespace viennals diff --git a/include/viennals/lsMakeGeometry.hpp b/include/viennals/lsMakeGeometry.hpp index a6a6cc3d..37b4edf2 100644 --- a/include/viennals/lsMakeGeometry.hpp +++ b/include/viennals/lsMakeGeometry.hpp @@ -419,34 +419,17 @@ template class MakeGeometry { } void makeCylinder(SmartPointer> cylinder) { - if constexpr (D == 3) { - // generate the points on the edges of the cylinders and mesh - // them manually - // cylinder axis will be (0,0,1) - auto gridDelta = levelSet->getGrid().getGridDelta(); if constexpr (D == 3) { // generate the points on the edges of the cylinders and mesh // them manually // cylinder axis will be (0,0,1) auto gridDelta = levelSet->getGrid().getGridDelta(); - auto points = PointCloud::New(); - const unsigned numPoints = - std::ceil(2 * M_PI * cylinder->radius / gridDelta); - const double smallAngle = 2.0 * M_PI / static_cast(numPoints); auto points = PointCloud::New(); const unsigned numPoints = std::ceil(2 * M_PI * cylinder->radius / gridDelta); const double smallAngle = 2.0 * M_PI / static_cast(numPoints); - auto mesh = Mesh::New(); - // insert midpoint at base - mesh->insertNextNode(Vec3D{0.0, 0.0, 0.0}); - { - constexpr double limit = 2 * M_PI - 1e-6; - std::vector> points; - if (cylinder->topRadius) - std::vector> pointsTop; auto mesh = Mesh::New(); // insert midpoint at base mesh->insertNextNode(Vec3D{0.0, 0.0, 0.0}); @@ -465,19 +448,9 @@ template class MakeGeometry { mesh->insertNextNode(point); } - // insert midpoint at top - mesh->insertNextNode(Vec3D{0.0, 0.0, cylinder->height}); // insert midpoint at top mesh->insertNextNode(Vec3D{0.0, 0.0, cylinder->height}); - double angle = 0; - for (unsigned i = 0; i < numPoints; ++i) { - // create triangles at base - std::array triangle{}; - triangle[0] = (i + 1) % numPoints + 1; - triangle[1] = i + 1; - triangle[2] = 0; - mesh->insertNextTriangle(triangle); double angle = 0; for (unsigned i = 0; i < numPoints; ++i) { // create triangles at base @@ -487,16 +460,6 @@ template class MakeGeometry { triangle[2] = 0; mesh->insertNextTriangle(triangle); - // insert points at top - // If topRadius is specified, update the first two coordinates of the - // points - if (cylinder->topRadius) { - points[i][0] = cylinder->topRadius * std::cos(angle); - points[i][1] = cylinder->topRadius * std::sin(angle); - angle += smallAngle; - } - points[i][2] = cylinder->height; - mesh->insertNextNode(points[i]); // insert points at top // If topRadius is specified, update the first two coordinates of the // points @@ -514,20 +477,7 @@ template class MakeGeometry { triangle[2] = (i + 1) % numPoints + 2 + numPoints; mesh->insertNextTriangle(triangle); } - // insert triangles at top - triangle[0] = numPoints + 1; - triangle[1] = numPoints + i + 2; - triangle[2] = (i + 1) % numPoints + 2 + numPoints; - mesh->insertNextTriangle(triangle); - } - // insert sidewall triangles - for (unsigned i = 0; i < numPoints; ++i) { - std::array triangle{}; - triangle[0] = i + 1; - triangle[1] = (i + 1) % numPoints + 1; - triangle[2] = i + numPoints + 2; - mesh->insertNextTriangle(triangle); // insert sidewall triangles for (unsigned i = 0; i < numPoints; ++i) { std::array triangle{}; @@ -542,25 +492,7 @@ template class MakeGeometry { mesh->insertNextTriangle(triangle); } } - triangle[0] = (i + 1) % numPoints + 1; - triangle[1] = (i + 1) % numPoints + 2 + numPoints; - triangle[2] = i + numPoints + 2; - mesh->insertNextTriangle(triangle); - } - } - // rotate mesh - // normalise axis vector - T unit = std::sqrt( - DotProduct(cylinder->axisDirection, cylinder->axisDirection)); - Vec3D cylinderAxis; - for (int i = 0; i < 3; ++i) { - cylinderAxis[i] = cylinder->axisDirection[i] / unit; - } - // get rotation axis via cross product of (0,0,1) and axis of cylinder - Vec3D rotAxis = {-cylinderAxis[1], cylinderAxis[0], 0.0}; - // angle is acos of dot product - T rotationAngle = std::acos(cylinderAxis[2]); // rotate mesh // normalise axis vector T unit = std::sqrt( @@ -574,9 +506,6 @@ template class MakeGeometry { // angle is acos of dot product T rotationAngle = std::acos(cylinderAxis[2]); - // rotate mesh - TransformMesh(mesh, TransformEnum::ROTATION, rotAxis, rotationAngle) - .apply(); // rotate mesh TransformMesh(mesh, TransformEnum::ROTATION, rotAxis, rotationAngle) .apply(); @@ -588,79 +517,7 @@ template class MakeGeometry { } TransformMesh(mesh, TransformEnum::TRANSLATION, translationVector) .apply(); - // translate mesh - Vec3D translationVector; - for (int i = 0; i < 3; ++i) { - translationVector[i] = cylinder->origin[i]; - } - TransformMesh(mesh, TransformEnum::TRANSLATION, translationVector) - .apply(); - - // read mesh from surface - FromSurfaceMesh mesher(levelSet, mesh); - mesher.setRemoveBoundaryTriangles(ignoreBoundaryConditions); - mesher.apply(); - } else if constexpr (D == 2) { - VIENNACORE_LOG_WARNING( - "MakeGeometry: Cylinder in 2D creates a box, not a cylinder."); - - auto mesh = Mesh::New(); - - // Calculate axis length for normalization - T axisLen = 0; - for (unsigned i = 0; i < D; ++i) - axisLen += cylinder->axisDirection[i] * cylinder->axisDirection[i]; - axisLen = std::sqrt(axisLen); - - // Normalized axis and perpendicular vector - // In 2D, if axis is (u, v), perpendicular is (v, -u) for CCW winding - T axis[2] = {cylinder->axisDirection[0] / axisLen, - cylinder->axisDirection[1] / axisLen}; - T perp[2] = {axis[1], -axis[0]}; - T topRadius = - (cylinder->topRadius != 0.) ? cylinder->topRadius : cylinder->radius; - - // Define the 4 corners of the rectangle/trapezoid - std::array, 4> corners; - - // Base Right - corners[0][0] = cylinder->origin[0] + cylinder->radius * perp[0]; - corners[0][1] = cylinder->origin[1] + cylinder->radius * perp[1]; - corners[0][2] = 0.; - - // Top Right - corners[1][0] = cylinder->origin[0] + cylinder->height * axis[0] + - topRadius * perp[0]; - corners[1][1] = cylinder->origin[1] + cylinder->height * axis[1] + - topRadius * perp[1]; - corners[1][2] = 0.; - - // Top Left - corners[2][0] = cylinder->origin[0] + cylinder->height * axis[0] - - topRadius * perp[0]; - corners[2][1] = cylinder->origin[1] + cylinder->height * axis[1] - - topRadius * perp[1]; - corners[2][2] = 0.; - - // Base Left - corners[3][0] = cylinder->origin[0] - cylinder->radius * perp[0]; - corners[3][1] = cylinder->origin[1] - cylinder->radius * perp[1]; - corners[3][2] = 0.; - - for (const auto &p : corners) { - mesh->insertNextNode(p); - } - - mesh->insertNextLine({0, 1}); - mesh->insertNextLine({1, 2}); - mesh->insertNextLine({2, 3}); - mesh->insertNextLine({3, 0}); - - FromSurfaceMesh mesher(levelSet, mesh); - mesher.setRemoveBoundaryTriangles(ignoreBoundaryConditions); - mesher.apply(); - } // read mesh from surface FromSurfaceMesh mesher(levelSet, mesh); mesher.setRemoveBoundaryTriangles(ignoreBoundaryConditions); @@ -743,4 +600,4 @@ template class MakeGeometry { // add all template specialisations for this class PRECOMPILE_PRECISION_DIMENSION(MakeGeometry) -} // namespace viennals +} // namespace viennals \ No newline at end of file From 5760afbfe9753a8d589954fa6d09f5888f5b5d97 Mon Sep 17 00:00:00 2001 From: filipovic Date: Thu, 15 Jan 2026 17:04:40 +0100 Subject: [PATCH 54/57] format --- include/viennals/lsCompareCriticalDimensions.hpp | 2 +- include/viennals/lsMakeGeometry.hpp | 2 +- include/viennals/lsWENO.hpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/viennals/lsCompareCriticalDimensions.hpp b/include/viennals/lsCompareCriticalDimensions.hpp index e8b0a6ee..d2f2e6c2 100644 --- a/include/viennals/lsCompareCriticalDimensions.hpp +++ b/include/viennals/lsCompareCriticalDimensions.hpp @@ -396,4 +396,4 @@ template class CompareCriticalDimensions { // Add template specializations for this class PRECOMPILE_PRECISION_DIMENSION(CompareCriticalDimensions) -} // namespace viennals \ No newline at end of file +} // namespace viennals diff --git a/include/viennals/lsMakeGeometry.hpp b/include/viennals/lsMakeGeometry.hpp index 37b4edf2..7b13dac5 100644 --- a/include/viennals/lsMakeGeometry.hpp +++ b/include/viennals/lsMakeGeometry.hpp @@ -600,4 +600,4 @@ template class MakeGeometry { // add all template specialisations for this class PRECOMPILE_PRECISION_DIMENSION(MakeGeometry) -} // namespace viennals \ No newline at end of file +} // namespace viennals diff --git a/include/viennals/lsWENO.hpp b/include/viennals/lsWENO.hpp index 5fbbc2b2..1d7ca139 100644 --- a/include/viennals/lsWENO.hpp +++ b/include/viennals/lsWENO.hpp @@ -173,4 +173,4 @@ template class WENO { // } }; -} // namespace lsInternal \ No newline at end of file +} // namespace lsInternal From 55283b13923b21777a6394b617ee3fda21d64bd7 Mon Sep 17 00:00:00 2001 From: filipovic Date: Thu, 15 Jan 2026 17:13:04 +0100 Subject: [PATCH 55/57] fix memcmp error by including --- include/viennals/lsDomain.hpp | 4 +++- include/viennals/lsPointData.hpp | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/include/viennals/lsDomain.hpp b/include/viennals/lsDomain.hpp index 5277c282..d140b8c1 100644 --- a/include/viennals/lsDomain.hpp +++ b/include/viennals/lsDomain.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -221,7 +222,8 @@ template class Domain { // Check identifier char identifier[8]; stream.read(identifier, 8); - if (std::string(identifier).compare(0, 8, "lsDomain")) { + // if (std::string(identifier).compare(0, 8, "lsDomain")) { + if (std::memcmp(identifier, "lsDomain", 8) != 0) { Logger::getInstance() .addError( "Reading Domain from stream failed. Header could not be found.") diff --git a/include/viennals/lsPointData.hpp b/include/viennals/lsPointData.hpp index 1d603670..6922d617 100644 --- a/include/viennals/lsPointData.hpp +++ b/include/viennals/lsPointData.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -385,7 +386,8 @@ class PointData { std::istream &deserialize(std::istream &stream) { char identifier[11]; stream.read(identifier, 11); - if (std::string(identifier).compare(0, 11, "lsPointData")) { + // if (std::string(identifier).compare(0, 11, "lsPointData")) { + if (std::memcmp(identifier, "lsPointData", 11) != 0) { Logger::getInstance() .addError("Reading PointData from stream failed. Header could " "not be found.") From 44279d24d9a32c27906a3333e405eb0ca1c97b1b Mon Sep 17 00:00:00 2001 From: filipovic Date: Thu, 15 Jan 2026 17:14:54 +0100 Subject: [PATCH 56/57] remove commented lines from lsDomain and lsPointData --- include/viennals/lsDomain.hpp | 1 - include/viennals/lsPointData.hpp | 1 - 2 files changed, 2 deletions(-) diff --git a/include/viennals/lsDomain.hpp b/include/viennals/lsDomain.hpp index d140b8c1..47dcf21d 100644 --- a/include/viennals/lsDomain.hpp +++ b/include/viennals/lsDomain.hpp @@ -222,7 +222,6 @@ template class Domain { // Check identifier char identifier[8]; stream.read(identifier, 8); - // if (std::string(identifier).compare(0, 8, "lsDomain")) { if (std::memcmp(identifier, "lsDomain", 8) != 0) { Logger::getInstance() .addError( diff --git a/include/viennals/lsPointData.hpp b/include/viennals/lsPointData.hpp index 6922d617..9f5d9e44 100644 --- a/include/viennals/lsPointData.hpp +++ b/include/viennals/lsPointData.hpp @@ -386,7 +386,6 @@ class PointData { std::istream &deserialize(std::istream &stream) { char identifier[11]; stream.read(identifier, 11); - // if (std::string(identifier).compare(0, 11, "lsPointData")) { if (std::memcmp(identifier, "lsPointData", 11) != 0) { Logger::getInstance() .addError("Reading PointData from stream failed. Header could " From 810fb5e0384d327efe1fdee5f6c1a15a0a1fa0dd Mon Sep 17 00:00:00 2001 From: filipovic Date: Thu, 15 Jan 2026 17:19:48 +0100 Subject: [PATCH 57/57] changed order iof includes --- include/viennals/lsDomain.hpp | 2 +- include/viennals/lsPointData.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/viennals/lsDomain.hpp b/include/viennals/lsDomain.hpp index 47dcf21d..28633455 100644 --- a/include/viennals/lsDomain.hpp +++ b/include/viennals/lsDomain.hpp @@ -2,8 +2,8 @@ #include -#include #include +#include #include #include diff --git a/include/viennals/lsPointData.hpp b/include/viennals/lsPointData.hpp index 9f5d9e44..4bcadfa1 100644 --- a/include/viennals/lsPointData.hpp +++ b/include/viennals/lsPointData.hpp @@ -3,9 +3,9 @@ #include #include +#include #include #include -#include #include