diff --git a/README.md b/README.md
index 4392b7a4..29fe6c6d 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@
[](https://github.com/ViennaTools/ViennaPS/actions/workflows/python.yml)
[](https://github.com/ViennaTools/ViennaPS/actions/workflows/build.yml)
+[](https://pypi.org/project/ViennaPS/)
@@ -14,9 +15,23 @@ ViennaPS is a header-only C++ process simulation library, which includes surface
> [!NOTE]
> ViennaPS is under heavy development and improved daily. If you do have suggestions or find bugs, please let us know!
+## Quick Start
+
+To install ViennaPS for Python, simply run:
+
+```sh
+pip install ViennaPS
+```
+
+To use ViennaPS in C++, clone the repository and follow the installation steps below.
+
+For full documentation, visit [ViennaPS Documentation](https://viennatools.github.io/ViennaPS/).
+
## Releases
Releases are tagged on the master branch and available in the [releases section](https://github.com/ViennaTools/ViennaPS/releases).
+ViennaPS is also available on the [Python Package Index (PyPI)](https://pypi.org/project/ViennaPS/) for most platforms.
+
## Building
### Supported Operating Systems
@@ -54,7 +69,7 @@ If the dependencies are not found on the system, they will be built from source.
> [!NOTE]
> __For more detailed installation instructions and troubleshooting tips, please refer to the ViennaPS [documentation](https://viennatools.github.io/ViennaPS/inst/).__
-ViennaPS operates as a header-only library, eliminating the need for a formal installation process. Nonetheless, we advise following the procedure to neatly organize and relocate all header files to a designated directory:
+ViennaPS is a header-only library, so no formal installation is required. However, following the steps below helps organize and manage dependencies more effectively:
```bash
git clone https://github.com/ViennaTools/ViennaPS.git
@@ -77,7 +92,7 @@ cd ViennaPS
pip install .
```
-> Some functionalities of the ViennaPS Python module only work in combination with the ViennaLS Python module. It is therefore recommended to additionally install the ViennaLS Python module on your system. Instructions to do so can be found in the [ViennaLS Git Repository](https://github.com/ViennaTools/viennals).
+> Some features of the ViennaPS Python module require the ViennaLS Python module. It is therefore recommended to additionally install the ViennaLS Python module on your system. Instructions to do so can be found in the [ViennaLS Git Repository](https://github.com/ViennaTools/viennals).
## Using the Python package
@@ -149,6 +164,10 @@ This [example](https://github.com/ViennaTools/ViennaPS/tree/master/examples/hole
The image presents the results of different flux configurations, as tested in _testFluxes.py_. Each structure represents a variation in flux conditions, leading to differences in hole shape, depth, and profile characteristics. The variations highlight the influence of ion and neutral fluxes on the etching process.
+> [!NOTE]
+> The underlying model may change in future releases, so running this example in newer versions of ViennaPS might not always reproduce exactly the same results.
+> The images shown here were generated using **ViennaPS v3.3.0**.
+
@@ -228,7 +247,7 @@ cmake --build build --target format
## Authors
-Current contributors: Tobias Reiter, Noah Karnel, Lado Filipovic
+Current contributors: Tobias Reiter, Lado Filipovic, Noah Karnel
Contact us via: viennatools@iue.tuwien.ac.at
diff --git a/examples/cantileverWetEtching/cantileverWetEtching.cpp b/examples/cantileverWetEtching/cantileverWetEtching.cpp
index 7bcbcfa1..99ba018d 100644
--- a/examples/cantileverWetEtching/cantileverWetEtching.cpp
+++ b/examples/cantileverWetEtching/cantileverWetEtching.cpp
@@ -36,12 +36,13 @@ int main(int argc, char **argv) {
const NumericType gridDelta = 5.; // um
// Read GDS file and convert to level set
- typename ls::Domain::BoundaryType boundaryCons[D];
+ ps::BoundaryType boundaryCons[D];
for (int i = 0; i < D - 1; i++)
- boundaryCons[i] = ls::Domain::BoundaryType::
- REFLECTIVE_BOUNDARY; // boundary conditions in x and y direction
- boundaryCons[D - 1] = ls::Domain::BoundaryType::
- INFINITE_BOUNDARY; // open boundary in z direction
+ boundaryCons[i] =
+ ps::BoundaryType::REFLECTIVE_BOUNDARY; // boundary conditions in x and y
+ // direction
+ boundaryCons[D - 1] =
+ ps::BoundaryType::INFINITE_BOUNDARY; // open boundary in z direction
auto gds_mask =
ps::SmartPointer>::New(gridDelta);
gds_mask->setBoundaryConditions(boundaryCons);
diff --git a/examples/cantileverWetEtching/cantileverWetEtching.py b/examples/cantileverWetEtching/cantileverWetEtching.py
index 4188ccb2..b6970c13 100644
--- a/examples/cantileverWetEtching/cantileverWetEtching.py
+++ b/examples/cantileverWetEtching/cantileverWetEtching.py
@@ -22,9 +22,9 @@
# read GDS mask file
boundaryConditions = [
- vls.BoundaryConditionEnum.REFLECTIVE_BOUNDARY,
- vls.BoundaryConditionEnum.REFLECTIVE_BOUNDARY,
- vls.BoundaryConditionEnum.INFINITE_BOUNDARY,
+ vps.BoundaryType.REFLECTIVE_BOUNDARY,
+ vps.BoundaryType.REFLECTIVE_BOUNDARY,
+ vps.BoundaryType.INFINITE_BOUNDARY,
]
gds_mask = vps.GDSGeometry(gridDelta)
@@ -62,7 +62,7 @@
process.setProcessModel(model)
process.setProcessDuration(5.0 * 60.0) # 5 minutes of etching
process.setIntegrationScheme(
- vls.IntegrationSchemeEnum.STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER
+ vps.IntegrationScheme.STENCIL_LOCAL_LAX_FRIEDRICHS_1ST_ORDER
)
for n in range(minutes):
diff --git a/examples/oxideRegrowth/oxideRegrowth.cpp b/examples/oxideRegrowth/oxideRegrowth.cpp
index be453bce..20a83f4c 100644
--- a/examples/oxideRegrowth/oxideRegrowth.cpp
+++ b/examples/oxideRegrowth/oxideRegrowth.cpp
@@ -32,12 +32,12 @@ int main(int argc, char **argv) {
return -1;
}
- auto domain = ps::SmartPointer>::New();
+ auto domain = ps::SmartPointer>::New(
+ params.get("gridDelta"), params.get("xExtent"), params.get("yExtent"));
ps::MakeStack(
- domain, params.get("gridDelta"), params.get("xExtent"),
- params.get("yExtent"), params.get("numLayers"), params.get("layerHeight"),
- params.get("substrateHeight"), 0. /*hole radius*/,
- params.get("trenchWidth"), 0., false)
+ domain, params.get("numLayers"), params.get("layerHeight"),
+ params.get("substrateHeight"), 0. /*holeRadius*/,
+ params.get("trenchWidth"), 0. /*maskHeight*/, 0. /*taperAngle*/)
.apply();
// copy top layer for deposition
domain->duplicateTopLevelSet(ps::Material::Polymer);
diff --git a/include/viennaps/models/psSF6O2Etching.hpp b/include/viennaps/models/psSF6O2Etching.hpp
index f720634c..f0695267 100644
--- a/include/viennaps/models/psSF6O2Etching.hpp
+++ b/include/viennaps/models/psSF6O2Etching.hpp
@@ -251,24 +251,23 @@ class SF6O2Ion
params.Ions.n_l);
}
// Gaussian distribution around the Eref_peak scaled by the particle energy
- NumericType NewEnergy;
+ NumericType newEnergy;
std::normal_distribution normalDist(E * Eref_peak, 0.1 * E);
do {
- NewEnergy = normalDist(Rng);
- } while (NewEnergy > E || NewEnergy < 0.);
-
- NumericType sticking = 0.;
- // NumericType sticking = 1.;
- // if (incAngle > params.Ions.thetaRMin) {
- // sticking =
- // 1. - std::min((incAngle - params.Ions.thetaRMin) /
- // (params.Ions.thetaRMax - params.Ions.thetaRMin),
- // NumericType(1.));
- // }
+ newEnergy = normalDist(Rng);
+ } while (newEnergy > E || newEnergy < 0.);
+
+ NumericType sticking = 1.;
+ if (incAngle > params.Ions.thetaRMin) {
+ sticking =
+ 1. - std::clamp((incAngle - params.Ions.thetaRMin) /
+ (params.Ions.thetaRMax - params.Ions.thetaRMin),
+ NumericType(0.), NumericType(1.));
+ }
// Set the flag to stop tracing if the energy is below the threshold
- if (NewEnergy > params.Si.Eth_ie) {
- E = NewEnergy;
+ if (newEnergy > params.Si.Eth_ie) {
+ E = newEnergy;
auto direction = viennaray::ReflectionConedCosine(
rayDir, geomNormal, Rng,
M_PI_2 - std::min(incAngle, params.Ions.minAngle));
@@ -303,12 +302,17 @@ class SF6O2Ion
};
template
-class SF6O2Etchant
- : public viennaray::Particle, NumericType> {
+class SF6O2Neutral
+ : public viennaray::Particle, NumericType> {
const SF6O2Parameters ¶ms;
+ const std::string fluxLabel;
+ const std::unordered_map &beta_map;
public:
- SF6O2Etchant(const SF6O2Parameters &pParams) : params(pParams) {}
+ SF6O2Neutral(const SF6O2Parameters &pParams,
+ const std::string pFluxLabel,
+ std::unordered_map &pBetaMap)
+ : params(pParams), fluxLabel(pFluxLabel), beta_map(pBetaMap) {}
void surfaceCollision(NumericType rayWeight, const Vec3D &,
const Vec3D &, const unsigned int primID,
@@ -347,70 +351,13 @@ class SF6O2Etchant
}
NumericType getSourceDistributionPower() const override final { return 1.; }
std::vector getLocalDataLabels() const override final {
- return {"etchantFlux"};
+ return {fluxLabel};
}
private:
NumericType sticking(const int matieralId) const {
- auto beta = params.beta_F.find(matieralId);
- if (beta != params.beta_F.end())
- return beta->second;
-
- // default value
- return 1.0;
- }
-};
-
-template
-class SF6O2Oxygen
- : public viennaray::Particle, NumericType> {
- const SF6O2Parameters ¶ms;
-
-public:
- SF6O2Oxygen(const SF6O2Parameters &pParams) : params(pParams) {}
-
- void surfaceCollision(NumericType rayWeight, const Vec3D &,
- const Vec3D &, const unsigned int primID,
- const int materialId,
- viennaray::TracingData &localData,
- const viennaray::TracingData *globalData,
- RNG &) override final {
- NumericType S_eff = 1.;
- if (params.fluxIncludeSticking) {
- const auto &phi_F = globalData->getVectorData(0)[primID];
- const auto &phi_O = globalData->getVectorData(1)[primID];
- NumericType beta = sticking(materialId);
- S_eff = beta * std::max(1. - phi_O - phi_F, 0.);
- }
-
- localData.getVectorData(0)[primID] += rayWeight * S_eff;
- }
- std::pair>
- surfaceReflection(NumericType rayWeight, const Vec3D &rayDir,
- const Vec3D &geomNormal,
- const unsigned int primID, const int materialId,
- const viennaray::TracingData *globalData,
- RNG &rngState) override final {
-
- NumericType S_eff;
- const auto &phi_F = globalData->getVectorData(0)[primID];
- const auto &phi_O = globalData->getVectorData(1)[primID];
- NumericType beta = sticking(materialId);
- S_eff = beta * std::max(1. - phi_O - phi_F, 0.);
-
- auto direction =
- viennaray::ReflectionDiffuse(geomNormal, rngState);
- return std::pair>{S_eff, direction};
- }
- NumericType getSourceDistributionPower() const override final { return 1.; }
- std::vector getLocalDataLabels() const override final {
- return {"oxygenFlux"};
- }
-
-private:
- NumericType sticking(const int matieralId) const {
- auto beta = params.beta_O.find(matieralId);
- if (beta != params.beta_O.end())
+ auto beta = beta_map.find(matieralId);
+ if (beta != beta_map.end())
return beta->second;
// default value
@@ -422,7 +369,6 @@ class SF6O2Oxygen
/// Model for etching Si in a SF6/O2 plasma. The model is based on the paper by
/// Belen et al., Vac. Sci. Technol. A 23, 99โ113 (2005),
/// DOI: https://doi.org/10.1116/1.1830495
-/// The resulting rate is in units of um / s.
template
class SF6O2Etching : public ProcessModel {
public:
@@ -467,25 +413,28 @@ class SF6O2Etching : public ProcessModel {
}
// particles
+ this->particles.clear();
auto ion = std::make_unique>(params);
- auto etchant = std::make_unique>(params);
- auto oxygen = std::make_unique>(params);
+ this->insertNextParticleType(ion);
+ auto etchant = std::make_unique>(
+ params, "etchantFlux", params.beta_F);
+ this->insertNextParticleType(etchant);
+ if (params.oxygenFlux > 0) {
+ auto oxygen = std::make_unique>(
+ params, "oxygenFlux", params.beta_O);
+ this->insertNextParticleType(oxygen);
+ }
// surface model
auto surfModel =
SmartPointer>::New(params);
+ this->setSurfaceModel(surfModel);
// velocity field
auto velField = SmartPointer>::New(2);
-
- this->setSurfaceModel(surfModel);
this->setVelocityField(velField);
+
this->setProcessName("SF6O2Etching");
- this->particles.clear();
- this->insertNextParticleType(ion);
- this->insertNextParticleType(etchant);
- if (params.oxygenFlux > 0)
- this->insertNextParticleType(oxygen);
}
SF6O2Parameters params;
diff --git a/include/viennaps/psProcess.hpp b/include/viennaps/psProcess.hpp
index 3578a89a..619089db 100644
--- a/include/viennaps/psProcess.hpp
+++ b/include/viennaps/psProcess.hpp
@@ -67,7 +67,7 @@ template class Process {
// Set the threshold for the coverage delta metric to reach convergence.
void setCoverageDeltaThreshold(NumericType treshold) {
- coverageDeltaTreshold = treshold;
+ coverageDeltaThreshold = treshold;
}
// Set the source direction, where the rays should be traced from.
@@ -156,7 +156,10 @@ template class Process {
// A single flux calculation is performed on the domain surface. The result is
// stored as point data on the nodes of the mesh.
- SmartPointer> calculateFlux() const {
+ SmartPointer> calculateFlux() {
+ model->initialize(domain, 0.);
+ const auto name = model->getProcessName().value_or("default");
+ const auto logLevel = Logger::getLogLevel();
// Generate disk mesh from domain
auto mesh = SmartPointer>::New();
@@ -166,21 +169,13 @@ template class Process {
}
meshConverter.apply();
- model->initialize(domain, 0.);
- if (model->getSurfaceModel()->getCoverages() != nullptr) {
- Logger::getInstance()
- .addWarning(
- "Coverages are not supported for single-pass flux calculation.")
- .print();
- return mesh;
- }
-
viennaray::Trace rayTracer;
initializeRayTracer(rayTracer);
auto points = mesh->getNodes();
auto normals = *mesh->getCellData().getVectorData("Normals");
auto materialIds = *mesh->getCellData().getScalarData("MaterialIds");
+
if (rayTracingParams.diskRadius == 0.) {
rayTracer.setGeometry(points, normals, domain->getGrid().getGridDelta());
} else {
@@ -189,24 +184,116 @@ template class Process {
}
rayTracer.setMaterialIds(materialIds);
- for (auto &particle : model->getParticleTypes()) {
- rayTracer.setParticleType(particle);
- rayTracer.apply();
+ const bool useProcessParams =
+ model->getSurfaceModel()->getProcessParameters() != nullptr;
+ bool useCoverages = false;
- // fill up rates vector with rates from this particle type
- auto &localData = rayTracer.getLocalData();
- int numRates = particle->getLocalDataLabels().size();
- for (int i = 0; i < numRates; ++i) {
- auto rate = std::move(localData.getVectorData(i));
+ // Initialize coverages
+ model->getSurfaceModel()->initializeSurfaceData(points.size());
+ if (!coveragesInitialized_)
+ model->getSurfaceModel()->initializeCoverages(points.size());
+ auto coverages = model->getSurfaceModel()->getCoverages();
+ std::ofstream covMetricFile;
+ if (coverages != nullptr) {
+ Timer timer;
+ useCoverages = true;
+ Logger::getInstance().addInfo("Using coverages.").print();
- // normalize fluxes
- rayTracer.normalizeFlux(rate, rayTracingParams.normalizationType);
- if (rayTracingParams.smoothingNeighbors > 0)
- rayTracer.smoothFlux(rate, rayTracingParams.smoothingNeighbors);
- mesh->getCellData().insertNextScalarData(
- std::move(rate), localData.getVectorDataLabel(i));
+ if (maxIterations == std::numeric_limits::max() &&
+ coverageDeltaThreshold == 0.) {
+ maxIterations = 10;
+ Logger::getInstance()
+ .addWarning("No coverage initialization parameters set. Using " +
+ std::to_string(maxIterations) +
+ " initialization iterations.")
+ .print();
}
- }
+
+ if (!coveragesInitialized_) {
+ timer.start();
+ Logger::getInstance().addInfo("Initializing coverages ... ").print();
+ // debug output
+ if (logLevel >= 5)
+ covMetricFile.open(name + "_covMetric.txt");
+
+ for (unsigned iteration = 0; iteration < maxIterations; ++iteration) {
+ // We need additional signal handling when running the C++ code from
+ // the Python bindings to allow interrupts in the Python scripts
+#ifdef VIENNAPS_PYTHON_BUILD
+ if (PyErr_CheckSignals() != 0)
+ throw pybind11::error_already_set();
+#endif
+ // save current coverages to compare with the new ones
+ auto prevStepCoverages =
+ SmartPointer>::New(*coverages);
+
+ auto fluxes =
+ calculateFluxes(rayTracer, useCoverages, useProcessParams);
+
+ // update coverages in the model
+ model->getSurfaceModel()->updateCoverages(fluxes, materialIds);
+
+ // metric to check for convergence
+ coverages = model->getSurfaceModel()->getCoverages();
+ auto metric =
+ calculateCoverageDeltaMetric(coverages, prevStepCoverages);
+
+ if (logLevel >= 3) {
+ mergeScalarData(mesh->getCellData(), coverages);
+ mergeScalarData(mesh->getCellData(), fluxes);
+ auto surfaceData = model->getSurfaceModel()->getSurfaceData();
+ if (surfaceData)
+ mergeScalarData(mesh->getCellData(), surfaceData);
+ printDiskMesh(mesh, name + "_covInit_" + std::to_string(iteration) +
+ ".vtp");
+
+ Logger::getInstance()
+ .addInfo("Iteration: " + std::to_string(iteration + 1))
+ .print();
+
+ std::stringstream stream;
+ stream << std::setprecision(4) << std::fixed;
+ stream << "Coverage delta metric: ";
+ for (int i = 0; i < coverages->getScalarDataSize(); i++) {
+ stream << coverages->getScalarDataLabel(i) << ": " << metric[i]
+ << "\t";
+ }
+ Logger::getInstance().addInfo(stream.str()).print();
+
+ // log metric to file
+ if (logLevel >= 5) {
+ for (auto val : metric) {
+ covMetricFile << val << ";";
+ }
+ covMetricFile << "\n";
+ }
+ }
+
+ if (checkCoveragesConvergence(metric)) {
+ Logger::getInstance()
+ .addInfo("Coverages converged after " +
+ std::to_string(iteration + 1) + " iterations.")
+ .print();
+ break;
+ }
+ } // end coverage initialization loop
+ coveragesInitialized_ = true;
+
+ if (logLevel >= 5)
+ covMetricFile.close();
+
+ timer.finish();
+ Logger::getInstance()
+ .addTiming("Coverage initialization", timer)
+ .print();
+ }
+ } // end coverage initialization
+
+ auto fluxes = calculateFluxes(rayTracer, useCoverages, useProcessParams);
+ mergeScalarData(mesh->getCellData(), fluxes);
+ auto surfaceData = model->getSurfaceModel()->getSurfaceData();
+ if (surfaceData)
+ mergeScalarData(mesh->getCellData(), surfaceData);
return mesh;
}
@@ -357,7 +444,7 @@ template class Process {
Logger::getInstance().addInfo("Using coverages.").print();
if (maxIterations == std::numeric_limits::max() &&
- coverageDeltaTreshold == 0.) {
+ coverageDeltaThreshold == 0.) {
maxIterations = 10;
Logger::getInstance()
.addWarning("No coverage initialization parameters set. Using " +
@@ -413,7 +500,10 @@ template class Process {
if (logLevel >= 3) {
mergeScalarData(diskMesh->getCellData(), coverages);
mergeScalarData(diskMesh->getCellData(), fluxes);
- printDiskMesh(diskMesh, name + "_covIinit_" +
+ auto surfaceData = model->getSurfaceModel()->getSurfaceData();
+ if (surfaceData)
+ mergeScalarData(diskMesh->getCellData(), surfaceData);
+ printDiskMesh(diskMesh, name + "_covInit_" +
std::to_string(iteration) + ".vtp");
Logger::getInstance()
.addInfo("Iteration: " + std::to_string(iteration + 1))
@@ -505,7 +595,7 @@ template class Process {
// update coverages in the model
model->getSurfaceModel()->updateCoverages(fluxes, materialIds);
- if (coverageDeltaTreshold > 0) {
+ if (coverageDeltaThreshold > 0) {
auto metric =
calculateCoverageDeltaMetric(coverages, prevStepCoverages);
while (!checkCoveragesConvergence(metric)) {
@@ -557,6 +647,9 @@ template class Process {
auto coverages = model->getSurfaceModel()->getCoverages();
mergeScalarData(diskMesh->getCellData(), coverages);
}
+ auto surfaceData = model->getSurfaceModel()->getSurfaceData();
+ if (surfaceData)
+ mergeScalarData(diskMesh->getCellData(), surfaceData);
mergeScalarData(diskMesh->getCellData(), fluxes);
printDiskMesh(diskMesh, name + "_" + std::to_string(counter) + ".vtp");
if (domain->getCellSet()) {
@@ -799,7 +892,7 @@ template class Process {
bool
checkCoveragesConvergence(const std::vector &deltaMetric) const {
for (auto val : deltaMetric) {
- if (val > coverageDeltaTreshold)
+ if (val > coverageDeltaThreshold)
return false;
}
return true;
@@ -913,7 +1006,7 @@ template class Process {
std::vector> particleDataLogs;
NumericType processDuration;
unsigned maxIterations = std::numeric_limits::max();
- NumericType coverageDeltaTreshold = 0.;
+ NumericType coverageDeltaThreshold = 0.;
bool coveragesInitialized_ = false;
NumericType processTime = 0.;
diff --git a/include/viennaps/psSurfaceModel.hpp b/include/viennaps/psSurfaceModel.hpp
index 3549c3d0..52c41b4a 100644
--- a/include/viennaps/psSurfaceModel.hpp
+++ b/include/viennaps/psSurfaceModel.hpp
@@ -47,6 +47,8 @@ template class SurfaceModel {
auto getCoverages() const { return coverages; }
auto getProcessParameters() const { return processParams; }
+
+ auto getSurfaceData() const { return surfaceData; }
};
} // namespace viennaps
diff --git a/python/pyWrap.cpp b/python/pyWrap.cpp
index 8de2223c..d8d58c87 100644
--- a/python/pyWrap.cpp
+++ b/python/pyWrap.cpp
@@ -723,7 +723,9 @@ PYBIND11_MODULE(VIENNAPS_MODULE_NAME, module) {
.def_readwrite("exponent", &SF6O2Parameters::IonType::exponent)
.def_readwrite("inflectAngle", &SF6O2Parameters::IonType::inflectAngle)
.def_readwrite("n_l", &SF6O2Parameters::IonType::n_l)
- .def_readwrite("minAngle", &SF6O2Parameters::IonType::minAngle);
+ .def_readwrite("minAngle", &SF6O2Parameters::IonType::minAngle)
+ .def_readwrite("thetaRMin", &SF6O2Parameters::IonType::thetaRMin)
+ .def_readwrite("thetaRMax", &SF6O2Parameters::IonType::thetaRMax);
pybind11::class_>(module, "SF6O2Parameters")
.def(pybind11::init<>())
diff --git a/python/viennaps2d/viennaps2d.pyi b/python/viennaps2d/viennaps2d.pyi
index 1cfd0d4d..f2ee5510 100644
--- a/python/viennaps2d/viennaps2d.pyi
+++ b/python/viennaps2d/viennaps2d.pyi
@@ -867,6 +867,8 @@ class SF6O2ParametersIons:
minAngle: float
n_l: float
sigmaEnergy: float
+ thetaRMin: float
+ thetaRMax: float
def __init__(self) -> None: ...
class SF6O2ParametersMask:
diff --git a/python/viennaps3d/viennaps3d.pyi b/python/viennaps3d/viennaps3d.pyi
index 252cd35f..031f3d50 100644
--- a/python/viennaps3d/viennaps3d.pyi
+++ b/python/viennaps3d/viennaps3d.pyi
@@ -824,6 +824,8 @@ class SF6O2ParametersIons:
minAngle: float
n_l: float
sigmaEnergy: float
+ thetaRMin: float
+ thetaRMax: float
def __init__(self) -> None: ...
class SF6O2ParametersMask: