Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Handle names in parent path for direct settings objects #3435

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -3,7 +3,7 @@
import os

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 @@ -32,12 +32,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 @@ -48,19 +58,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