diff --git a/.cursor/rules/docstrings.md b/.cursor/rules/docstrings.md new file mode 100644 index 000000000..a66102925 --- /dev/null +++ b/.cursor/rules/docstrings.md @@ -0,0 +1,46 @@ + +# PyOptiSLang Docstring Rules + +schema_version: 1 + +## Context +Docstring style and quality are validated through documentation tooling (NumPy docstring checks). + +## Scope +Apply rules only to: +- documented and exported user-facing APIs +- user-facing classes and methods + +Do not modify unless explicitly requested: +- internal/proxy classes +- transport-specific implementations (for example tcp layer) + +## Constraints +Do not: +- change docstring style +- reformat existing docstrings unnecessarily +- rewrite already-correct docstrings + +## Improvement Triggers +Improve docstrings only when needed to: +- clarify ambiguous descriptions +- make behavior explicit +- ensure parameters and return values are clearly explained +- add missing exception documentation where relevant + +## Consistency Rules +- align docstrings with type hints +- use consistent terminology across modules +- avoid redundant or repeated descriptions + +## LLM Writing Guidance +Prefer: +- domain-specific terminology (Project, System, Node, Design) +- wording that reflects actual API behavior + +Avoid: +- vague descriptions (for example "process", "handle", "do stuff") + +## Change Strategy +- make targeted improvements only +- keep diffs minimal and localized diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..fb802ce1e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,252 @@ +# PyOptiSLang – AI Agent Guide + +## Purpose +This repository provides a Python API for interacting with PyOptiSLang +instances and executing parametric studies within engineering workflows. + +Typical usage follows this lifecycle: +- Create or connect to an OptiSLang instance +- Open or create a project +- Interact with the project structure (systems, nodes, parameters) +- Configure workflows (e.g., parameters, criteria, responses) +- Execute studies and retrieve results + +Interaction with a Project is central to most use cases. Users typically +operate on projects by modifying their structure (systems and nodes), +configuring parameters and evaluation settings, and triggering execution. + +--- + +## Scope +This guidance applies to: +- Source code interpretation +- Source code modifications +- API design changes +- Documentation updates + +--- + +## Public API Rules +Public API includes: +- Documented and exported user-facing APIs (for example core entry points and documented classes) +- Public methods on those user-facing APIs + +Treat as implementation details unless explicitly requested: +- transport-specific modules (for example `core.tcp`) +- internal/proxy implementation classes used behind public interfaces + +All public APIs MUST: +- have NumPy-style docstrings +- clearly describe behavior and intent +- align with type hints + +--- + +## Docstring Guidelines +Docstring style and quality are validated through NumPy docstring checks in documentation tooling. + +Focus on: +- semantic clarity +- explicit behavior +- complete parameter and return descriptions +- documenting raised exceptions + +--- + +## Code Quality Guidelines +- Follow PEP8 +- Maintain backward compatibility +- Avoid broad exception handling +- Prefer explicit over implicit logic + +--- + +## Domain Concepts +The PyOptiSLang API follows a hierarchical structure: + +Optislang → Application → Project → RootSystem → Nodes + + +### Key Modules and Entry Points + +- `ansys.optislang.core/...`: Core API interfaces and functionality +- `ansys.optislang.core.tcp/...`: Transport-specific implementations +- `ansys.optislang.parametric/...`: Convenience abstractions + +Primary entry points: +- `Optislang` +- `Application` +- `Project` + + +### Core Hierarchy + +- Optislang + - Entry point responsible for starting or connecting to an OptiSLang server instance. + - Establishes communication with the backend and provides access to higher-level APIs. + +- Application + - Provides application-level functionality such as: + - project management (open, save, save_as) + - version information + - Acts as the entry point to project-level operations. + +- Project + - Central entity representing a workflow project. + - Provides access to: + - project execution (start, stop, reset) + - project status + - evaluation of designs at project level + - Provides access to and wraps parts of the RootSystem functionality. + +- RootSystem + - Top-level parametric system within a project. + - Contains the full workflow graph of nodes. + + +### Node Hierarchy + +The workflow is composed of nodes with the following inheritance structure: + +- Node + - Base class for all elements in the workflow graph. + +- System (inherits from Node) + - Can contain child nodes (composite structure). + +- ParametricSystem (inherits from System) + - Provides parametric evaluation capabilities. + - Contains: + - ParameterManager + - CriteriaManager + - ResponseManager + - DesignManager + - Responsible for generating and managing designs. + + +- RootSystem (inherits from ParametricSystem) + - Represents the top-level workflow system of a project. + + +- IntegrationNode (inherits from Node) + - Represents executable workflow steps that operate on designs. + - Typically receives designs from a parent ParametricSystem or predecessor nodes. + - Can: + - map parameter values to external representations (e.g., input files, solver settings) + - execute external tools or solvers + - extract responses from defined result locations + - Supports registration of locations for parameters, variables, and responses. + + +Nodes can be created, configured, and connected to define workflows. + + +### Object Access and Creation + +Objects are typically accessed and managed through their parent structures: + +- Projects provide execution control and access to the root system +- Topology changes should be performed through `project.root_system` (or descendant systems) +- Systems provide method to create and/or access to child nodes (e.g., `get_nodes`, search methods, `create_node`) +- Parametric systems expose managers for: + - parameters + - criteria + - responses + - designs +- Integration nodes expose locations for parameter and response registration + +Direct instantiation of internal objects is uncommon and should be avoided +unless explicitly required. + +Most workflows are constructed by navigating and modifying existing structures +rather than creating standalone objects by direct communication with the server. + + +### Additional Notes + +- All systems and nodes exist within a Project context. +- Users typically interact with high-level APIs on the Project rather than directly manipulating the RootSystem. +- Modifications to workflows involve creating, configuring, and connecting nodes within systems. + + +### Implementation Notes + +The API uses a proxy pattern: + +- Core modules mostly define interfaces and high-level abstractions +- Concrete implementations are located in transport-specific layers (e.g., `tcp` module) + +When modifying code: +- Prefer working at the API/interface level unless implementation changes are required +- Be aware that modifying only interface definitions may not affect runtime behavior + + +### Testing Guidelines + +- All public API changes must include tests +- Use pytest framework +- Prefer integration-style tests for workflow logic without excessive mocking + + + +### Convenience Layer: Parametric Module + +The `parametric` module provides higher-level abstractions for creating +and executing common workflow patterns. + +Key components: + +- ParametricDesignStudyManager + - Manages multiple design studies + - Owns or uses an Optislang instance (connects or creates) + - Provides creation, access, and persistence of studies + +- ParametricDesignStudy + - Represents a configured study + - Supports execution, reset, and lifecycle operations + +- DesignStudyTemplate + - Base class for workflow templates + - Defines `create_design_study`, which generates configured studies + +Notes: +- This module acts as a convenience layer to simplify common workflows +- Design study templates and settings can be prepared before server connection/project availability +- Creating, resetting, or executing a design study requires an active loaded project +- It does not strictly follow the proxy pattern used in the core API +- Particularly useful when working with complex nodes such as ProxySolverNode + + +### Project parametric concepts + +- **Parameter**: Input variable used in evaluations +- **Criteria**: Objectives, constraints, or intermediate evaluation quantities +- **Response**: Output values obtained from evaluations +- **Design**: A single evaluation instance defined by a parameter set + + +### Notes for LLMs + +- Most user-facing operations should go through the `Project` or higher-level APIs +- Direct interaction with low-level structures (e.g., RootSystem internals) + should be limited unless necessary +- The API uses a proxy pattern; core modules define interfaces, while actual + implementations may reside in transport-specific layers (e.g., TCP) +- Prefer `Application`/`Project` APIs over deprecated passthrough methods on `Optislang` + +--- + +## LLM Optimization Goals +When modifying or generating code: +- reduce ambiguity +- prefer explicit naming +- preserve API stability +- avoid unnecessary refactoring + +--- + +## Constraints +Do NOT: +- change docstring style (enforced externally) +- modify private APIs unless required +- introduce breaking changes \ No newline at end of file diff --git a/llms.txt b/llms.txt new file mode 100644 index 000000000..3e36b6a04 --- /dev/null +++ b/llms.txt @@ -0,0 +1,64 @@ + +schema_version: 1 + +metadata: + project: PyOptiSLang + language: python + description: Python API for interacting with OptiSLang instances and executing parametric studies. + +documentation: + docstring_style: numpy + docstring_validation: enforced via documentation tooling + +api_model: + entry_points: + - Optislang + - Application + - Project + core_hierarchy: Optislang -> Application -> Project -> RootSystem -> Nodes + node_types: + - Node + - System + - ParametricSystem + - RootSystem + - IntegrationNode + +workflow: + pattern: + - connect/create Optislang instance + - open/create Project + - modify workflow (systems, nodes) + - configure parameters/criteria/responses + - execute project + - retrieve results + +object_management: + access: + - objects accessed via parent structures + - avoid direct instantiation of internal classes + routing: + execution_and_status: use Project APIs + topology_changes: perform via project.root_system (or descendant systems) + +parametric_layer: + module: parametric + components: + - ParametricDesignStudyManager + - ParametricDesignStudy + - DesignStudyTemplate + constraints: + - parametric module is a convenience layer for workflow creation + - design study templates and settings can be prepared before server connection/project availability + - creating, resetting, or executing a design study requires an active loaded project + - not strictly proxy-based + +llm_guidance: + prefer: + - documented/exported user-facing APIs + - Application/Project APIs over deprecated Optislang passthrough methods + - explicit behavior and naming + - API stability + avoid: + - transport-specific modules (e.g. core.tcp) + allowed_when_explicitly_requested: + - transport-specific/internal proxy classes diff --git a/src/ansys/optislang/core/nodes.py b/src/ansys/optislang/core/nodes.py index 1692a0e3f..51d2037bd 100644 --- a/src/ansys/optislang/core/nodes.py +++ b/src/ansys/optislang/core/nodes.py @@ -355,12 +355,12 @@ def exists(self) -> bool: # pragma: no cover @abstractmethod def get_ancestors(self) -> Tuple[Node, ...]: # pragma: no cover - """Get tuple of ordered ancestors starting from root system at position 0. + """Get ordered ancestors of this node, starting with the root system.. Returns ------- Tuple[Node, ...] - Tuple of ordered ancestors, starting from root system at position. + Ancestor nodes ordered from root system at index 0 to the direct parent. Raises ------ diff --git a/src/ansys/optislang/core/project.py b/src/ansys/optislang/core/project.py index 84827215c..4a158902c 100644 --- a/src/ansys/optislang/core/project.py +++ b/src/ansys/optislang/core/project.py @@ -151,7 +151,7 @@ def get_name(self) -> Optional[str]: # pragma: no cover Returns ------- - str + Optional[str] Name of the optiSLang project. If no project is loaded in the optiSLang, ``None`` is returned. @@ -253,7 +253,7 @@ def get_status(self) -> Optional[str]: # pragma: no cover Returns ------- - str + Optional[str] Status of the optiSLang project. If no project is loaded in optiSLang, ``None`` is returned. diff --git a/src/ansys/optislang/core/project_parametric.py b/src/ansys/optislang/core/project_parametric.py index 9fcb5a64b..6051301ba 100644 --- a/src/ansys/optislang/core/project_parametric.py +++ b/src/ansys/optislang/core/project_parametric.py @@ -20,7 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -"""Contains ``Parameter``, ``Criterion``, ``Response``, it's child classes and enumerations.""" +"""Contains ``Parameter``, ``Criterion``, and ``Response``, their subclasses, and related enums.""" from __future__ import annotations import ast @@ -410,20 +410,20 @@ def __init__( ---------- name: str, optional Criterion name, by default ``""``. - type_: Union[CriterionKind, str], optional - Type of criterion, e. g. 'objective', by default ``CriterionType.VARIABLE``. + type_: Union[CriterionType, str], optional + Criterion category, for example ``"objective"``, by default ``CriterionType.VARIABLE``. expression: str, optional - Criterion expression, by default ``"0"``. + Left-hand expression used for evaluation, by default ``"0"``. expression_value: Optional[Union[tuple, bool, float, complex, list, dict]], optional - Expression value, by default ``None``. + Evaluated value of the expression, by default ``None``. expression_value_type: Optional[CriterionValueType], optional - Expression value type, by default ``None``. - criterion: Union[CriterionType, str], optional - Type of comparison symbol, e. g. 'min', by default ``ComparisonType.IGNORE``. + Type of the evaluated expression value, by default ``None``. + criterion: Union[ComparisonType, str], optional + Comparison operator, for example ``"min"``, by default ``ComparisonType.IGNORE``. value: Optional[ Union[Tuple[CriterionValueType, str], bool, float, complex, list, dict] ], optional - Value of the criterion, by default ``None``. + Value of the criterion, depending on criterion type, by default ``None``. value_type: Optional[CriterionValueType], optional Type of the criterion value, by default ``None``. """ @@ -693,12 +693,12 @@ def from_dict(cls, criterion_dict: dict) -> Criterion: @staticmethod def _extract_criterion_properties_from_dict(criterion_dict: dict) -> Dict[str, Any]: - """Get type of criterion from optiSLang output. + """Extract normalized criterion properties from a dictionary returned by the server. Parameters ---------- criterion_dict : dict - Output from `get_actor_properties` query. + Criterion payload from ``get_actor_properties`` query. Returns ------- @@ -908,7 +908,7 @@ def _parse_str_to_vector(string: str): Parameters ---------- string: str - Expression describing matrix. + Expression describing vector. """ # split by square bracket end splitted_str = string.split("]") diff --git a/src/ansys/optislang/parametric/design_study.py b/src/ansys/optislang/parametric/design_study.py index 0dad70063..a2739ce1b 100644 --- a/src/ansys/optislang/parametric/design_study.py +++ b/src/ansys/optislang/parametric/design_study.py @@ -253,7 +253,7 @@ def __init__( parametric_system: ParametricSystem, solver_node: IntegrationNode, ): - """Initialize the ManagedAlgorithm. + """Initialize the ManagedParametricSystem. Parameters ---------- @@ -300,7 +300,7 @@ def __init__( solver_node: ProxySolverNode, callback: Callable, ): - """Initialize the ManagedAlgorithm. + """Initialize the ProxySolverManagedParametricSystem. Parameters ---------- @@ -498,7 +498,7 @@ def __init__( self.__is_complete = False def delete(self) -> None: - """Delete the managed algorithm from the project.""" + """Delete all managed instances from the current project.""" for item in self.__managed_instances: item.instance.delete() @@ -605,11 +605,11 @@ def contains_proxy_solver(self) -> bool: # region proxy solver related def get_designs(self) -> Optional[List[Design]]: - """Call ``get_designs`` command on proxy solver node in use. + """Call ``get_designs`` command on the proxy solver node in use. Returns ------- - Optional[Design] + Optional[List[Design]] List of designs, if proxy_solver is being executed, else ``None``. """ if self.__current_proxy_solver is not None: @@ -619,12 +619,12 @@ def get_designs(self) -> Optional[List[Design]]: return None def set_designs(self, designs: List[Design]): - """Call ``set_designs`` command on proxy solver node in use. + """Call ``set_designs`` command on the proxy solver node in use. Parameters ---------- - List[Design] - List of solved design instances. + designs: List[Design] + Solved designs to send back to the proxy solver node in use. """ if self.__current_proxy_solver is not None: responses: list[dict] = self.__class__.__convert_design_object_to_response(designs) @@ -645,9 +645,10 @@ def __get_proxy_solver( Returns ------- Optional[ProxySolverNode] - Proxy solver (if defined in current designs study, else ``None``). + First proxy solver found in the provided instances or in all managed instances, + if instances are not provided. Returns ``None`` if no proxy solver is found. """ - for item in self.__managed_instances: + for item in self.__managed_instances if not instances else instances: if isinstance(item, ProxySolverManagedParametricSystem): return item.solver_node return None @@ -661,7 +662,7 @@ def __set_managed_instances_exec_options( Parameters ---------- - execution_option: ExecutionOption + execution_options: ExecutionOption Execution option to be set to all managed instances. Multiple options can be specified using bitwise operator. instances: Optional[Iterable[ManagedInstance]], optional @@ -821,7 +822,7 @@ def append_design_study( ) def clear_design_studies(self, delete: Optional[bool] = False) -> None: - """Remove all designs studies managed by the instance of ParametricDesignStudyManager. + """Remove all design studies managed by the instance of ParametricDesignStudyManager. Parameters ---------- @@ -859,7 +860,7 @@ def create_design_study(self, template: DesignStudyTemplate) -> ParametricDesign raise RuntimeError("No project loaded.") def dispose(self): - """Dispose the instance of SolverManager and close the associated optiSLang instance.""" + """Clear all managed design studies and dispose the associated optiSLang instance.""" self.__design_studies.clear() if self.optislang: self.optislang.dispose() diff --git a/src/ansys/optislang/parametric/design_study_templates.py b/src/ansys/optislang/parametric/design_study_templates.py index 011fb384e..95c55fea6 100644 --- a/src/ansys/optislang/parametric/design_study_templates.py +++ b/src/ansys/optislang/parametric/design_study_templates.py @@ -113,7 +113,7 @@ def multi_design_launch_num(self) -> int: Returns ------- - Optional[int] + int Number of designs to be sent/received in one batch. """ return self.__multi_design_launch_num @@ -163,11 +163,11 @@ def __init__( multi_design_launch_num: Optional[int] = None, additional_settings: Optional[dict] = {}, ): - """Initialize the MopSolverNode. + """Initialize the MopSolverNodeSettings. Parameters ---------- - input_file : Union[str, Path, OptislangPath] + input_file : Optional[Union[str, Path, OptislangPath]] Path to the MOP file. multi_design_launch_num : Optional[int], optional Number of designs to be sent/received in one batch, by default 1. @@ -214,7 +214,7 @@ def multi_design_launch_num(self) -> int: Returns ------- - Optional[int] + int Number of designs to be sent/received in one batch. """ return self.__multi_design_launch_num @@ -255,25 +255,23 @@ def callback(self, value: Callable): def __init__( self, callback: Callable, - multi_design_launch_num: Optional[int] = None, + multi_design_launch_num: int = 1, additional_settings: Optional[dict] = {}, ): - """Initialize the MopSolverNode. + """Initialize the ProxySolverNodeSettings. Parameters ---------- callback: Callable - A callback function to handle design evaluation results. - multi_design_launch_num : Optional[int], optional + A callback function used by the proxy solver to evaluate designs. + multi_design_launch_num : int, optional Number of designs to be sent/received in one batch, by default 1. additional_settings : Optional[dict], optional Additional settings for the solver node. """ super().__init__(additional_settings=additional_settings) self.callback = callback - self.multi_design_launch_num = ( - multi_design_launch_num if multi_design_launch_num is not None else 1 - ) + self.multi_design_launch_num = multi_design_launch_num def convert_properties_to_dict(self) -> dict: """Get properties dictionary. @@ -353,7 +351,7 @@ def __init__( input_code: Optional[str] = None, additional_settings: Optional[dict] = {}, ): - """Initialize the PythonSolverNode. + """Initialize the PythonSolverNodeSettings. Parameters ---------- @@ -624,7 +622,7 @@ def create_solver_node( all available locations as parameters/responses. solver_name : Optional[str], optional Solver node name. - solver_settings : Optional[GeneralSolverNodeSettings], optional + solver_settings : Optional[GeneralNodeSettings], optional Solver node settings. solver_connections: Optional[Iterable[Tuple[OutputSlot, str]]] Iterable of tuples specifying the connection from each predecessor node to the @@ -833,7 +831,7 @@ def __init__( Settings for the parametric system. solver_name : Optional[str], optional Name for the solver node. - solver_settings : Optional[GeneralSolverNodeSettings], optional + solver_settings : Optional[GeneralNodeSettings], optional Settings for the solver node. start_designs : Iterable[Design], optional Designs to be used as start designs for the parametric system. @@ -941,7 +939,7 @@ def __init__( the selected algorithm type. solver_name : Optional[str], optional Name for the solver node. - solver_settings : Optional[GeneralSolverNodeSettings], optional + solver_settings : Optional[GeneralNodeSettings], optional Settings for the solver node. Settings must be compatible with the selected solver type. start_designs : Optional[Iterable[Design]], optional @@ -1323,14 +1321,14 @@ def go_to_optislang( ---------- project_path: Union[str,Path] Path to save the generated optiSLang project file. - connector_type : str + connector_type : NodeType The type of connector actor. omdb_files : Union[Union[str, Path], List[Union[str, Path]], ParametricDesignStudyManager] OMDB files to include in the project. Can be a path to a folder, a list of paths, or an instance of ``ParametricDesignStudyManager``. parameters: Optional[Iterable[Parameter]], optional Parameters to be included in the parametric system, by default `None`. - response: Optional[Iterable[Response]], optional + responses: Optional[Iterable[Response]], optional Responses to be included in the parametric system, by default `None`. connector_settings : Optional[GeneralNodeSettings], optional Settings for the connector actor, by default `None`. @@ -1340,8 +1338,9 @@ def go_to_optislang( Returns ------- - Path - The path to the generated optiSLang project file. + Optislang + The instance of ``Optislang`` with the project containing parametric system + and the specified connector. """ kwargs.pop("project_path", None) kwargs.pop("batch", None) @@ -1382,9 +1381,9 @@ def create_optislang_project_with_solver_node( a list of paths, or an instance of `ParametricDesignStudyManager`. parameters: Optional[Iterable[Parameter]], optional Parameters to be included in the parametric system, by default `None`. - responses: Optional[Iterable[Parameter]], optional - Response to be included in the parametric system, by default `None`. - connector_settings : Optional[GeneralSolverNodeSettings], optional + responses: Optional[Iterable[Response]], optional + Responses to be included in the parametric system, by default `None`. + connector_settings : Optional[GeneralNodeSettings], optional Settings for the connector actor, by default `None`. **kwargs Additional keyword arguments, used to initialize the optislang instance.