Skip to content

Commit

Permalink
feat: Handle names in parent path for direct settings objects (#3435)
Browse files Browse the repository at this point in the history
* feat: update base

* feat: Add test
  • Loading branch information
mkundu1 authored Nov 12, 2024
1 parent 5dc2cb7 commit 96d9b57
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 32 deletions.
49 changes: 39 additions & 10 deletions src/ansys/fluent/core/codegen/builtin_settingsgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from zipimport import zipimporter

from ansys.fluent.core import CODEGEN_OUTDIR, FluentVersion
from ansys.fluent.core.solver.flobject import CreatableNamedObjectMixin
from ansys.fluent.core.solver.flobject import CreatableNamedObjectMixin, NamedObject
from ansys.fluent.core.solver.settings_builtin_data import DATA

_PY_FILE = CODEGEN_OUTDIR / "solver" / "settings_builtin.py"
Expand Down Expand Up @@ -33,12 +33,22 @@ def _get_settings_root(version: str):
return settings.root


def _get_named_object_type(root, path):
for comp in path.split("."):
root = root._child_classes[comp]
return (
"Creatable" if issubclass(root, CreatableNamedObjectMixin) else "NonCreatable"
)
def _get_named_objects_in_path(root, path, kind):
named_objects = []
cls = root
comps = path.split(".")
for i, comp in enumerate(comps):
cls = cls._child_classes[comp]
if i < len(comps) - 1 and issubclass(cls, NamedObject):
named_objects.append(comp)
cls = cls.child_object_type
final_type = ""
if kind == "NamedObject":
if issubclass(cls, CreatableNamedObjectMixin):
final_type = "Creatable"
else:
final_type = "NonCreatable"
return named_objects, final_type


def generate(version: str):
Expand All @@ -49,19 +59,38 @@ def generate(version: str):
with open(_PY_FILE, "w") as f:
f.write('"""Solver settings."""\n\n')
f.write(
"from ansys.fluent.core.solver.settings_builtin_bases import _SingletonSetting, _CreatableNamedObjectSetting, _NonCreatableNamedObjectSetting\n\n\n"
"from ansys.fluent.core.solver.settings_builtin_bases import _SingletonSetting, _CreatableNamedObjectSetting, _NonCreatableNamedObjectSetting, Solver\n"
"from ansys.fluent.core.solver.flobject import SettingsBase\n\n\n"
)
f.write("__all__ = [\n")
for name, _ in DATA.items():
f.write(f' "{name}",\n')
f.write("]\n\n")
for name, v in DATA.items():
kind, path = v
path = path[FluentVersion(version)] if isinstance(path, dict) else path
named_objects, final_type = _get_named_objects_in_path(root, path, kind)
if kind == "NamedObject":
path = path[FluentVersion(version)] if isinstance(path, dict) else path
kind = f"{_get_named_object_type(root, path)}NamedObject"
kind = f"{final_type}NamedObject"
f.write(f"class {name}(_{kind}Setting):\n")
f.write(f' """{name} setting."""\n\n')
f.write(f" def __init__(self")
for named_object in named_objects:
f.write(f", {named_object}: str")
f.write(", settings_source: SettingsBase | Solver | None = None")
if kind == "NonCreatableNamedObject":
f.write(", name: str = None")
elif kind == "CreatableNamedObject":
f.write(", name: str = None, new_instance_name: str = None")
f.write("):\n")
f.write(f" super().__init__(settings_source=settings_source")
if kind == "NonCreatableNamedObject":
f.write(", name=name")
elif kind == "CreatableNamedObject":
f.write(", name=name, new_instance_name=new_instance_name")
for named_object in named_objects:
f.write(f", {named_object}={named_object}")
f.write(")\n\n")

with open(_PYI_FILE, "w") as f:
for version in FluentVersion:
Expand Down
50 changes: 28 additions & 22 deletions src/ansys/fluent/core/solver/settings_builtin_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import Protocol, runtime_checkable

from ansys.fluent.core.solver.flobject import SettingsBase
from ansys.fluent.core.solver.flobject import NamedObject, SettingsBase
from ansys.fluent.core.solver.settings_builtin_data import DATA
from ansys.fluent.core.utils.fluent_version import FluentVersion

Expand All @@ -28,55 +28,60 @@ def is_root_obj(obj):
)


def _get_settings_obj(settings_source: SettingsBase | Solver, cls_name: str):
obj = _get_settings_root(settings_source)
path = DATA[cls_name][1]
def _get_settings_obj(settings_root, builtin_settings_obj):
builtin_cls_name = builtin_settings_obj.__class__.__name__
obj = settings_root
path = DATA[builtin_cls_name][1]
if isinstance(path, dict):
version = FluentVersion(obj.version)
path = path.get(version)
if path is None:
raise RuntimeError(
f"{cls_name} is not supported in Fluent version {version}."
f"{builtin_cls_name} is not supported in Fluent version {version}."
)
for comp in path.split("."):
comps = path.split(".")
for i, comp in enumerate(comps):
obj = SettingsBase.__getattribute__(obj, comp) # bypass InactiveObjectError
if i < len(comps) - 1 and isinstance(obj, NamedObject):
obj_name = getattr(builtin_settings_obj, comp)
obj = obj[obj_name]
return obj


class _SingletonSetting:
# Covers both groups and named-object containers
def __init__(self, settings_source: SettingsBase | Solver | None = None):
self.__dict__.update(dict(settings_source=None))
def __init__(self, settings_source: SettingsBase | Solver | None = None, **kwargs):
self.__dict__.update(dict(settings_source=None) | kwargs)
if settings_source is not None:
self.settings_source = settings_source

def __setattr__(self, name, value):
if name == "settings_source":
obj = _get_settings_obj(value, self.__class__.__name__)
settings_root = _get_settings_root(value)
obj = _get_settings_obj(settings_root, self)
self.__class__ = obj.__class__
self.__dict__.clear()
self.__dict__.update(
obj.__dict__ | dict(settings_source=_get_settings_root(value))
)
self.__dict__.update(obj.__dict__ | dict(settings_source=settings_root))
else:
super().__setattr__(name, value)


class _NonCreatableNamedObjectSetting:
def __init__(self, name: str, settings_source: SettingsBase | Solver | None = None):
self.__dict__.update(dict(settings_source=None, name=name))
def __init__(
self, name: str, settings_source: SettingsBase | Solver | None = None, **kwargs
):
self.__dict__.update(dict(settings_source=None, name=name) | kwargs)
if settings_source is not None:
self.settings_source = settings_source

def __setattr__(self, name, value):
if name == "settings_source":
obj = _get_settings_obj(value, self.__class__.__name__)
settings_root = _get_settings_root(value)
obj = _get_settings_obj(settings_root, self)
obj = obj[self.name]
self.__class__ = obj.__class__
self.__dict__.clear()
self.__dict__.update(
obj.__dict__ | dict(settings_source=_get_settings_root(value))
)
self.__dict__.update(obj.__dict__ | dict(settings_source=settings_root))
else:
super().__setattr__(name, value)

Expand All @@ -87,18 +92,21 @@ def __init__(
settings_source: SettingsBase | Solver | None = None,
name: str | None = None,
new_instance_name: str | None = None,
**kwargs,
):
if name and new_instance_name:
raise ValueError("Cannot specify both name and new_instance_name.")
self.__dict__.update(
dict(settings_source=None, name=name, new_instance_name=new_instance_name)
| kwargs
)
if settings_source is not None:
self.settings_source = settings_source

def __setattr__(self, name, value):
if name == "settings_source":
obj = _get_settings_obj(value, self.__class__.__name__)
settings_root = _get_settings_root(value)
obj = _get_settings_obj(settings_root, self)
if self.name:
obj = obj[self.name]
elif self.new_instance_name:
Expand All @@ -107,8 +115,6 @@ def __setattr__(self, name, value):
obj = obj.create()
self.__class__ = obj.__class__
self.__dict__.clear()
self.__dict__.update(
obj.__dict__ | dict(settings_source=_get_settings_root(value))
)
self.__dict__.update(obj.__dict__ | dict(settings_source=settings_root))
else:
super().__setattr__(name, value)
4 changes: 4 additions & 0 deletions src/ansys/fluent/core/solver/settings_builtin_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -946,4 +946,8 @@
FluentVersion.v231: "results.report.simulation_reports",
},
),
"ParametricStudies": ("Singleton", "parametric_studies"),
"ParametricStudy": ("NamedObject", "parametric_studies"),
"DesignPoints": ("Singleton", "parametric_studies.design_points"),
"DesignPoint": ("NamedObject", "parametric_studies.design_points"),
}
28 changes: 28 additions & 0 deletions tests/test_builtin_settings.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from pathlib import Path
import tempfile

import pytest

try:
Expand All @@ -19,6 +22,8 @@
CumulativePlots,
CustomFieldFunctions,
CustomVectors,
DesignPoint,
DesignPoints,
DiscretePhase,
DiscretePhaseHistogram,
DynamicMesh,
Expand Down Expand Up @@ -56,6 +61,8 @@
NamedExpressions,
Optics,
OutputParameters,
ParametricStudies,
ParametricStudy,
ParticleTracks,
PartitionSurfaces,
Pathlines,
Expand Down Expand Up @@ -112,6 +119,7 @@
)
except ImportError:
pass # for no-codegen testing workflow
import ansys.fluent.core as pyfluent
from ansys.fluent.core.examples import download_file
from ansys.fluent.core.utils.fluent_version import FluentVersion

Expand Down Expand Up @@ -630,6 +638,26 @@ def test_builtin_settings(mixing_elbow_case_data_session):
else:
with pytest.raises(RuntimeError):
CustomVectors(settings_source=solver)
tmp_save_path = tempfile.mkdtemp(dir=pyfluent.EXAMPLES_PATH)
project_file = Path(tmp_save_path) / "mixing_elbow_param.flprj"
solver.settings.parametric_studies.initialize(project_filename=str(project_file))
assert ParametricStudies(settings_source=solver) == solver.parametric_studies
assert (
ParametricStudy(settings_source=solver, name="mixing_elbow-Solve")
== solver.parametric_studies["mixing_elbow-Solve"]
)
assert (
DesignPoints(settings_source=solver, parametric_studies="mixing_elbow-Solve")
== solver.parametric_studies["mixing_elbow-Solve"].design_points
)
assert (
DesignPoint(
settings_source=solver,
parametric_studies="mixing_elbow-Solve",
name="Base DP",
)
== solver.parametric_studies["mixing_elbow-Solve"].design_points["Base DP"]
)


@pytest.mark.codegen_required
Expand Down

0 comments on commit 96d9b57

Please sign in to comment.