From 62d295869caf6c581585b03caa0cf8993384226f Mon Sep 17 00:00:00 2001 From: Tobias Reiter Date: Sun, 12 Apr 2026 15:10:44 +0200 Subject: [PATCH 1/4] feat(geom model): apply to single material --- .../models/psGeometricDistributionModels.hpp | 16 +++ include/viennaps/process/psGeometricModel.hpp | 5 + .../process/psGeometricProcessStrategy.hpp | 106 ++++++++++-------- include/viennaps/psDomain.hpp | 44 +++++++- 4 files changed, 124 insertions(+), 47 deletions(-) diff --git a/include/viennaps/models/psGeometricDistributionModels.hpp b/include/viennaps/models/psGeometricDistributionModels.hpp index bff0d5d6..f6eb8798 100644 --- a/include/viennaps/models/psGeometricDistributionModels.hpp +++ b/include/viennaps/models/psGeometricDistributionModels.hpp @@ -32,6 +32,14 @@ class SphereDistribution : public ProcessModelCPU { assert(geomModel != nullptr); geomModel->addMaskMaterial(material); } + + void applyToSingleMaterial(const Material material) { + auto geomModel = this->getGeometricModel(); + assert(geomModel != nullptr); + geomModel->setSingleMaterial(true); + geomModel->getMaskMaterials().clear(); + geomModel->addMaskMaterial(material); + } }; template @@ -66,6 +74,14 @@ class BoxDistribution : public ProcessModelCPU { assert(geomModel != nullptr); geomModel->addMaskMaterial(material); } + + void applyToSingleMaterial(const Material material) { + auto geomModel = this->getGeometricModel(); + assert(geomModel != nullptr); + geomModel->setSingleMaterial(true); + geomModel->getMaskMaterials().clear(); + geomModel->addMaskMaterial(material); + } }; template diff --git a/include/viennaps/process/psGeometricModel.hpp b/include/viennaps/process/psGeometricModel.hpp index 549887c2..fd1e6b84 100644 --- a/include/viennaps/process/psGeometricModel.hpp +++ b/include/viennaps/process/psGeometricModel.hpp @@ -16,6 +16,7 @@ VIENNAPS_TEMPLATE_ND(NumericType, D) class GeometricModel { SmartPointer> mask = nullptr; std::vector maskMaterials; bool isDepo = false; + bool applySingleMaterial = false; public: GeometricModel() = default; @@ -49,6 +50,10 @@ VIENNAPS_TEMPLATE_ND(NumericType, D) class GeometricModel { auto &getMaskMaterials() const { return maskMaterials; } auto isDeposition() const { return isDepo; } void setDeposition(bool deposition) { isDepo = deposition; } + auto isSingleMaterial() const { return applySingleMaterial; } + void setSingleMaterial(bool singleMaterial) { + applySingleMaterial = singleMaterial; + } }; } // namespace viennaps diff --git a/include/viennaps/process/psGeometricProcessStrategy.hpp b/include/viennaps/process/psGeometricProcessStrategy.hpp index fbe8ee01..09aabbc6 100644 --- a/include/viennaps/process/psGeometricProcessStrategy.hpp +++ b/include/viennaps/process/psGeometricProcessStrategy.hpp @@ -44,54 +44,71 @@ class GeometricProcessStrategy : public ProcessStrategy { return ProcessResult::INVALID_INPUT; } - auto maskLevelSet = SmartPointer>::New( - domain->getGrid()); - bool foundMaterial = false; - - auto const &levelSets = domain->getLevelSets(); - for (auto &material : maskMaterials) { - for (int j = 0; j < levelSets.size(); ++j) { - if (materialMap->getMaterialAtIdx(j) == material) { - auto lsCopy = SmartPointer>::New( - levelSets[j]); - - // remove all lower level sets that are not mask materials - for (int k = j - 1; k >= 0; --k) { - if (MaterialMap::isMaterial(materialMap->getMaterialAtIdx(k), - maskMaterials)) - continue; - - viennals::BooleanOperation( - lsCopy, levelSets[k], - viennals::BooleanOperationEnum::RELATIVE_COMPLEMENT) - .apply(); - } - - if (foundMaterial) { - // union with mask level set - viennals::BooleanOperation( - maskLevelSet, lsCopy, viennals::BooleanOperationEnum::UNION) - .apply(); - } else { - maskLevelSet = lsCopy; - foundMaterial = true; + if (geometricModel->isSingleMaterial() && maskMaterials.size() == 1) { + // operation should be applied to a single material, so we use the + // surface as mask and exclude the material from the surface + mask = viennals::Domain::New(domain->getSurface()); + auto materialLS = domain->getMaterialLevelSet(maskMaterials[0]); + if (materialLS) { + viennals::BooleanOperation( + mask, materialLS, + viennals::BooleanOperationEnum::RELATIVE_COMPLEMENT) + .apply(); + VIENNACORE_LOG_DEBUG( + "Created mask level set from single mask material: " + + materialMap->toString(maskMaterials[0])); + } + } else { + auto maskLevelSet = + viennals::Domain::New(domain->getGrid()); + bool foundMaterial = false; + auto const &levelSets = domain->getLevelSets(); + for (auto &material : maskMaterials) { + for (int j = 0; j < levelSets.size(); ++j) { + if (materialMap->getMaterialAtIdx(j) == material) { + auto lsCopy = + viennals::Domain::New(levelSets[j]); + + // remove all lower level sets that are not mask materials + for (int k = j - 1; k >= 0; --k) { + if (MaterialMap::isMaterial(materialMap->getMaterialAtIdx(k), + maskMaterials)) + continue; + + viennals::BooleanOperation( + lsCopy, levelSets[k], + viennals::BooleanOperationEnum::RELATIVE_COMPLEMENT) + .apply(); + } + + if (foundMaterial) { + // union with mask level set + viennals::BooleanOperation( + maskLevelSet, lsCopy, + viennals::BooleanOperationEnum::UNION) + .apply(); + } else { + maskLevelSet = lsCopy; + foundMaterial = true; + } } } } - } - if (maskLevelSet->getNumberOfPoints() > 0) { - mask = maskLevelSet; - if (Logger::hasDebug()) { - auto dbgMesh = viennals::Mesh::New(); - viennals::ToMesh(mask, dbgMesh).apply(); - viennals::VTKWriter(dbgMesh, "geometric_mask_debug") - .apply(); + if (maskLevelSet->getNumberOfPoints() > 0) { + mask = maskLevelSet; + if (Logger::hasDebug()) { + auto dbgMesh = viennals::Mesh::New(); + viennals::ToMesh(mask, dbgMesh).apply(); + viennals::VTKWriter(dbgMesh, "geometric_mask_debug") + .apply(); + } + } else { + VIENNACORE_LOG_WARNING( + "None of the specified mask materials were found in the " + "domain, " + "cannot create mask level set from mask materials."); } - } else { - VIENNACORE_LOG_WARNING( - "None of the specified mask materials were found in the domain, " - "cannot create mask level set from mask materials."); } } } @@ -104,8 +121,7 @@ class GeometricProcessStrategy : public ProcessStrategy { // last level set. for (int i = context.domain->getNumberOfLevelSets() - 1; i >= 0; --i) { viennals::BooleanOperation( - context.domain->getLevelSets()[i], - context.domain->getLevelSets().back(), + context.domain->getLevelSets()[i], context.domain->getSurface(), viennals::BooleanOperationEnum::INTERSECT) .apply(); } diff --git a/include/viennaps/psDomain.hpp b/include/viennaps/psDomain.hpp index 73670eb6..ae876fa6 100644 --- a/include/viennaps/psDomain.hpp +++ b/include/viennaps/psDomain.hpp @@ -447,6 +447,7 @@ VIENNAPS_TEMPLATE_ND(NumericType, D) class Domain { return levelSets_.back()->getGrid().getBoundaryConditions(); } + // Returns a set of all materials present in the domain. auto getMaterialsInDomain() const { std::set materials; for (std::size_t i = 0; i < materialMap_->size(); i++) { @@ -455,6 +456,45 @@ VIENNAPS_TEMPLATE_ND(NumericType, D) class Domain { return materials; } + // Returns a Level-Set representing the specified material in the domain. If + // the material is not present in the domain, it returns an empty Level-Set. + auto getMaterialLevelSet(const Material material) const { + lsDomainType levelSet; + bool foundMaterial = false; + + for (int i = 0; i < levelSets_.size(); i++) { + if (materialMap_->getMaterialAtIdx(i) == material) { + auto lsCopy = + SmartPointer>::New(levelSets_[i]); + + // remove all lower level sets + for (int k = i - 1; k >= 0; --k) { + viennals::BooleanOperation( + lsCopy, levelSets_[k], + viennals::BooleanOperationEnum::RELATIVE_COMPLEMENT) + .apply(); + } + + if (foundMaterial) { + // add to level set + viennals::BooleanOperation( + levelSet, lsCopy, viennals::BooleanOperationEnum::UNION) + .apply(); + } else { + levelSet = lsCopy; + foundMaterial = true; + } + } + } + + if (!foundMaterial) { + VIENNACORE_LOG_WARNING("Material " + MaterialMap::toString(material) + + " not found in domain."); + } + + return levelSet; + } + void print(std::ostream &out = std::cout, bool hrle = false) const { constexpr std::string_view separator = "*****************************************\n"; @@ -655,8 +695,8 @@ VIENNAPS_TEMPLATE_ND(NumericType, D) class Domain { private: void materialMapCheck() const { if (materialMap_->size() != levelSets_.size()) { - VIENNACORE_LOG_WARNING( - "Size mismatch in material map and number of Level-Sets in domain."); + VIENNACORE_LOG_WARNING("Size mismatch in material map and number of " + "Level-Sets in domain."); } } From d65ac1e0d1160799f86007e8a622c28ba5ac2dfb Mon Sep 17 00:00:00 2001 From: Tobias Reiter Date: Sun, 12 Apr 2026 15:20:23 +0200 Subject: [PATCH 2/4] python bindings --- include/viennaps/models/psGeometricDistributionModels.hpp | 2 -- python/pyWrapDimension.hpp | 7 ++++++- python/viennaps/_core/__init__.pyi | 4 ++-- python/viennaps/d2/__init__.pyi | 4 ++++ python/viennaps/d3/__init__.pyi | 4 ++++ 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/include/viennaps/models/psGeometricDistributionModels.hpp b/include/viennaps/models/psGeometricDistributionModels.hpp index f6eb8798..19907fa7 100644 --- a/include/viennaps/models/psGeometricDistributionModels.hpp +++ b/include/viennaps/models/psGeometricDistributionModels.hpp @@ -37,7 +37,6 @@ class SphereDistribution : public ProcessModelCPU { auto geomModel = this->getGeometricModel(); assert(geomModel != nullptr); geomModel->setSingleMaterial(true); - geomModel->getMaskMaterials().clear(); geomModel->addMaskMaterial(material); } }; @@ -79,7 +78,6 @@ class BoxDistribution : public ProcessModelCPU { auto geomModel = this->getGeometricModel(); assert(geomModel != nullptr); geomModel->setSingleMaterial(true); - geomModel->getMaskMaterials().clear(); geomModel->addMaskMaterial(material); } }; diff --git a/python/pyWrapDimension.hpp b/python/pyWrapDimension.hpp index 3dff3ed2..7a008e89 100644 --- a/python/pyWrapDimension.hpp +++ b/python/pyWrapDimension.hpp @@ -1055,6 +1055,9 @@ template void bindApi(py::module &module) { }), py::arg("radius")) .def("addMaskMaterial", &SphereDistribution::addMaskMaterial, + py::arg("material")) + .def("applyToSingleMaterial", + &SphereDistribution::applyToSingleMaterial, py::arg("material")); // Box Distribution @@ -1070,7 +1073,9 @@ template void bindApi(py::module &module) { }), py::arg("halfAxes")) .def("addMaskMaterial", &BoxDistribution::addMaskMaterial, - py::arg("material")); + py::arg("material")) + .def("applyToSingleMaterial", + &BoxDistribution::applyToSingleMaterial, py::arg("material")); // Custom Sphere Distribution py::class_, diff --git a/python/viennaps/_core/__init__.pyi b/python/viennaps/_core/__init__.pyi index 9ef42d98..6d941552 100644 --- a/python/viennaps/_core/__init__.pyi +++ b/python/viennaps/_core/__init__.pyi @@ -6,10 +6,10 @@ import collections.abc import enum import typing import viennals._core -from viennaps import d2 import viennaps.d2 -import viennaps.d3 +from viennaps import d2 from viennaps import d3 +import viennaps.d3 from . import constants from . import gpu from . import util diff --git a/python/viennaps/d2/__init__.pyi b/python/viennaps/d2/__init__.pyi index 379610a0..b508ad65 100644 --- a/python/viennaps/d2/__init__.pyi +++ b/python/viennaps/d2/__init__.pyi @@ -27,6 +27,8 @@ class BoxDistribution(ProcessModel): ... def addMaskMaterial(self, material: viennaps._core.Material) -> None: ... + def applyToSingleMaterial(self, material: viennaps._core.Material) -> None: + ... class CF4O2Etching(ProcessModel): @typing.overload def __init__(self) -> None: @@ -934,6 +936,8 @@ class SphereDistribution(ProcessModel): ... def addMaskMaterial(self, material: viennaps._core.Material) -> None: ... + def applyToSingleMaterial(self, material: viennaps._core.Material) -> None: + ... class StencilLocalLaxFriedrichsScalar: @staticmethod def setMaxDissipation(maxDissipation: typing.SupportsFloat | typing.SupportsIndex) -> None: diff --git a/python/viennaps/d3/__init__.pyi b/python/viennaps/d3/__init__.pyi index cba880fe..920e5bf0 100644 --- a/python/viennaps/d3/__init__.pyi +++ b/python/viennaps/d3/__init__.pyi @@ -27,6 +27,8 @@ class BoxDistribution(ProcessModel): ... def addMaskMaterial(self, material: viennaps._core.Material) -> None: ... + def applyToSingleMaterial(self, material: viennaps._core.Material) -> None: + ... class CF4O2Etching(ProcessModel): @typing.overload def __init__(self) -> None: @@ -934,6 +936,8 @@ class SphereDistribution(ProcessModel): ... def addMaskMaterial(self, material: viennaps._core.Material) -> None: ... + def applyToSingleMaterial(self, material: viennaps._core.Material) -> None: + ... class StencilLocalLaxFriedrichsScalar: @staticmethod def setMaxDissipation(maxDissipation: typing.SupportsFloat | typing.SupportsIndex) -> None: From 11e25d708aafd26bebafd36c92dae2f36b0ac99f Mon Sep 17 00:00:00 2001 From: Tobias Reiter Date: Sun, 12 Apr 2026 15:45:49 +0200 Subject: [PATCH 3/4] fix: missing bindings for disk mesh --- python/pyWrapDimension.hpp | 2 ++ python/viennaps/_core/__init__.pyi | 2 +- python/viennaps/d2/__init__.pyi | 4 ++++ python/viennaps/d3/__init__.pyi | 4 ++++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/python/pyWrapDimension.hpp b/python/pyWrapDimension.hpp index 7a008e89..c65d0925 100644 --- a/python/pyWrapDimension.hpp +++ b/python/pyWrapDimension.hpp @@ -564,6 +564,7 @@ template void bindApi(py::module &module) { "Get the surface mesh of the domain") .def("getHullMesh", &Domain::getHullMesh, py::arg("bottomExtension") = 0.0, py::arg("sharpCorners") = false) + .def("getDiskMesh", &Domain::getDiskMesh) // Save to file .def("saveLevelSetMesh", &Domain::saveLevelSetMesh, py::arg("filename"), py::arg("width") = 1, @@ -576,6 +577,7 @@ template void bindApi(py::module &module) { .def("saveHullMesh", &Domain::saveHullMesh, py::arg("filename"), py::arg("bottomExtension") = 0.0, py::arg("sharpCorners") = false, "Save the hull of the domain.") + .def("saveDiskMesh", &Domain::saveDiskMesh, py::arg("filename")) .def("saveVolumeMesh", &Domain::saveVolumeMesh, py::arg("filename"), py::arg("wrappingLayerEpsilon") = 1e-2, "Save the volume representation of the domain.") diff --git a/python/viennaps/_core/__init__.pyi b/python/viennaps/_core/__init__.pyi index 6d941552..06a5c00b 100644 --- a/python/viennaps/_core/__init__.pyi +++ b/python/viennaps/_core/__init__.pyi @@ -6,8 +6,8 @@ import collections.abc import enum import typing import viennals._core -import viennaps.d2 from viennaps import d2 +import viennaps.d2 from viennaps import d3 import viennaps.d3 from . import constants diff --git a/python/viennaps/d2/__init__.pyi b/python/viennaps/d2/__init__.pyi index b508ad65..fad136ef 100644 --- a/python/viennaps/d2/__init__.pyi +++ b/python/viennaps/d2/__init__.pyi @@ -304,6 +304,8 @@ class Domain: """ Get the cell set. """ + def getDiskMesh(self) -> viennals._core.Mesh: + ... def getGrid(self) -> viennals.d2.hrleGrid: """ Get the grid @@ -380,6 +382,8 @@ class Domain: ... def removeTopLevelSet(self) -> None: ... + def saveDiskMesh(self, filename: str) -> None: + ... def saveHullMesh(self, filename: str, bottomExtension: typing.SupportsFloat | typing.SupportsIndex = 0.0, sharpCorners: bool = False) -> None: """ Save the hull of the domain. diff --git a/python/viennaps/d3/__init__.pyi b/python/viennaps/d3/__init__.pyi index 920e5bf0..d5740e58 100644 --- a/python/viennaps/d3/__init__.pyi +++ b/python/viennaps/d3/__init__.pyi @@ -304,6 +304,8 @@ class Domain: """ Get the cell set. """ + def getDiskMesh(self) -> viennals._core.Mesh: + ... def getGrid(self) -> viennals.d3.hrleGrid: """ Get the grid @@ -380,6 +382,8 @@ class Domain: ... def removeTopLevelSet(self) -> None: ... + def saveDiskMesh(self, filename: str) -> None: + ... def saveHullMesh(self, filename: str, bottomExtension: typing.SupportsFloat | typing.SupportsIndex = 0.0, sharpCorners: bool = False) -> None: """ Save the hull of the domain. From c0265a9967b4f2e228dabed19f319ad8d6f7d4e0 Mon Sep 17 00:00:00 2001 From: Tobias Reiter Date: Sun, 12 Apr 2026 16:36:56 +0200 Subject: [PATCH 4/4] fix: missing python bindings --- include/viennaps/psDomain.hpp | 14 ++++++-------- python/pyWrapDimension.hpp | 4 ++++ python/viennaps/_core/__init__.pyi | 2 +- python/viennaps/d2/__init__.pyi | 4 ++++ python/viennaps/d3/__init__.pyi | 4 ++++ 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/include/viennaps/psDomain.hpp b/include/viennaps/psDomain.hpp index ae876fa6..d1670579 100644 --- a/include/viennaps/psDomain.hpp +++ b/include/viennaps/psDomain.hpp @@ -459,35 +459,33 @@ VIENNAPS_TEMPLATE_ND(NumericType, D) class Domain { // Returns a Level-Set representing the specified material in the domain. If // the material is not present in the domain, it returns an empty Level-Set. auto getMaterialLevelSet(const Material material) const { - lsDomainType levelSet; - bool foundMaterial = false; + lsDomainType levelSet = nullptr; for (int i = 0; i < levelSets_.size(); i++) { if (materialMap_->getMaterialAtIdx(i) == material) { auto lsCopy = SmartPointer>::New(levelSets_[i]); - // remove all lower level sets - for (int k = i - 1; k >= 0; --k) { + // remove lower level set + if (i > 0) { viennals::BooleanOperation( - lsCopy, levelSets_[k], + lsCopy, levelSets_[i - 1], viennals::BooleanOperationEnum::RELATIVE_COMPLEMENT) .apply(); } - if (foundMaterial) { + if (levelSet) { // add to level set viennals::BooleanOperation( levelSet, lsCopy, viennals::BooleanOperationEnum::UNION) .apply(); } else { levelSet = lsCopy; - foundMaterial = true; } } } - if (!foundMaterial) { + if (!levelSet) { VIENNACORE_LOG_WARNING("Material " + MaterialMap::toString(material) + " not found in domain."); } diff --git a/python/pyWrapDimension.hpp b/python/pyWrapDimension.hpp index c65d0925..60b85987 100644 --- a/python/pyWrapDimension.hpp +++ b/python/pyWrapDimension.hpp @@ -538,6 +538,10 @@ template void bindApi(py::module &module) { .def("getLevelSets", &Domain::getLevelSets) .def("getMaterialsInDomain", &Domain::getMaterialsInDomain, "Get the material IDs present in the domain.") + .def("getMaterialLevelSet", &Domain::getMaterialLevelSet, + py::arg("material"), + "Returns a Level-Set representing the specified material in the " + "domain.") .def("getSurface", &Domain::getSurface, "Get the surface level set.") .def("getCellSet", &Domain::getCellSet, "Get the cell set.") diff --git a/python/viennaps/_core/__init__.pyi b/python/viennaps/_core/__init__.pyi index 06a5c00b..9ef42d98 100644 --- a/python/viennaps/_core/__init__.pyi +++ b/python/viennaps/_core/__init__.pyi @@ -8,8 +8,8 @@ import typing import viennals._core from viennaps import d2 import viennaps.d2 -from viennaps import d3 import viennaps.d3 +from viennaps import d3 from . import constants from . import gpu from . import util diff --git a/python/viennaps/d2/__init__.pyi b/python/viennaps/d2/__init__.pyi index fad136ef..001d57de 100644 --- a/python/viennaps/d2/__init__.pyi +++ b/python/viennaps/d2/__init__.pyi @@ -322,6 +322,10 @@ class Domain: """ def getLevelSets(self) -> list[viennals.d2.Domain]: ... + def getMaterialLevelSet(self, material: viennaps._core.Material) -> viennals.d2.Domain: + """ + Returns a Level-Set representing the specified material in the domain. + """ def getMaterialMap(self) -> viennaps._core.MaterialMap: ... def getMaterialsInDomain(self) -> set[viennaps._core.Material]: diff --git a/python/viennaps/d3/__init__.pyi b/python/viennaps/d3/__init__.pyi index d5740e58..16ad1878 100644 --- a/python/viennaps/d3/__init__.pyi +++ b/python/viennaps/d3/__init__.pyi @@ -322,6 +322,10 @@ class Domain: """ def getLevelSets(self) -> list[viennals.d3.Domain]: ... + def getMaterialLevelSet(self, material: viennaps._core.Material) -> viennals.d3.Domain: + """ + Returns a Level-Set representing the specified material in the domain. + """ def getMaterialMap(self) -> viennaps._core.MaterialMap: ... def getMaterialsInDomain(self) -> set[viennaps._core.Material]: