.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "examples/workflow_creation/03_3_proxy_solver_oco.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_examples_workflow_creation_03_3_proxy_solver_oco.py: .. _ref_proxy_solver_oco: Proxy solver with OCO --------------------- This example demonstrates how to obtain designs from a parametric system and process them externally. It creates a proxy solver node inside an OCO parametric system, modifies the maximum number of designs in the OCO settings, and solves its designs externally. This is a unified approach for "optiSLang inside" solutions. **Workflow overview (mapped to code sections below):** 1. **"Perform required imports"** -- Import pyOptislang classes for workflow creation. 2. **"Create solver"** -- Define a Python calculator function that serves as the external solver. This function receives design dicts with parameter values and returns response values. 3. **"Create optiSLang instance"** -- Discover an optiSLang >= 25.1 installation and start a headless optiSLang server. 4. **"Create workflow"** -- Build the parametric workflow: a. Create an ``OCO`` system (``node_types.OCO``) -- optiSLang Combined Optimization that automatically selects and combines multiple optimization algorithms (gradient-based, evolutionary, surrogate-based). b. Read and modify algorithm settings via ``get_property("Settings")``: the OCO settings use a ``{"sequence": [{"First": key, "Second": value}, ...]}`` format. This example modifies ``"Maximum number of samples"`` to 150 (default 200). c. Set fast-running solver properties (``AutoSaveMode``, ``SolveTwice``, etc.). d. Add a ``ProxySolver`` node (``DesignFlow.RECEIVE_SEND``) inside the OCO system and configure batch size via ``MultiDesignLaunchNum``. e. Load 5 input parameters (X1..X5) and 1 response (Y) into the proxy solver via ``load()``. f. Register them as system-level parameters/responses and set bounds [-3.14, 3.14]. g. Add a minimization criterion on Y. 5. **"Run workflow"** -- Start the project in non-blocking mode and loop externally: poll ``proxy_solver.get_designs()``, compute responses via the calculator, and return results via ``proxy_solver.set_designs()`` until the system finishes. 6. **"Stop and cancel project"** -- Dispose of the optiSLang instance. **Key APIs used:** - ``node_types.OCO`` -- optiSLang Combined Optimization algorithm - ``get_property("Settings")`` / ``set_property("Settings", ...)`` -- read/write OCO-specific settings; the dict uses ``{"sequence": [{"First": name, "Second": value}, ...]}`` format - ``node_types.ProxySolver`` with ``DesignFlow.RECEIVE_SEND`` -- external solver integration - ``proxy_solver.get_designs()`` / ``proxy_solver.set_designs()`` -- design exchange loop - ``register_locations_as_parameter()`` / ``register_locations_as_response()`` -- register solver slots as system-level parameters and responses .. GENERATED FROM PYTHON SOURCE LINES 74-80 Perform required imports ~~~~~~~~~~~~~~~~~~~~~~~~ Import the pyOptislang core classes: ``Optislang`` for server management, ``node_types`` for algorithm and solver type constants, ``DesignFlow``/``ParametricSystem``/``ProxySolverNode`` for workflow construction, and parametric classes for defining parameters, criteria, and bounds. .. GENERATED FROM PYTHON SOURCE LINES 80-92 .. code-block:: Python import time from ansys.optislang.core import Optislang import ansys.optislang.core.node_types as node_types from ansys.optislang.core.nodes import DesignFlow, ParametricSystem, ProxySolverNode from ansys.optislang.core.project_parametric import ( ComparisonType, ObjectiveCriterion, OptimizationParameter, ) from ansys.optislang.core.utils import find_all_osl_exec .. GENERATED FROM PYTHON SOURCE LINES 93-100 Create solver ~~~~~~~~~~~~~ Define the external solver functions that will evaluate designs outside optiSLang. ``calculator()`` computes response Y from 5 input parameters using a nonlinear formula. ``calculate()`` processes a batch of design dicts received from the proxy solver: each design contains ``hid`` (design ID) and ``parameters`` (list of name/value pairs). It returns a list of result dicts with ``hid`` and ``responses`` (list of name/value pairs). .. GENERATED FROM PYTHON SOURCE LINES 100-143 .. code-block:: Python def calculator(hid, X1, X2, X3, X4, X5): from math import sin Y = 0.5 * X1 + X2 + 0.5 * X1 * X2 + 5 * sin(X3) + 0.2 * X4 + 0.1 * X5 return Y def calculate(designs): result_design_list = [] print(f"Calculate {len(designs)} designs") for design in designs: hid = design["hid"] parameters = design["parameters"] X1 = 0.0 X2 = 0.0 X3 = 0.0 X4 = 0.0 X5 = 0.0 for parameter in parameters: if parameter["name"] == "X1": X1 = parameter["value"] elif parameter["name"] == "X2": X2 = parameter["value"] elif parameter["name"] == "X3": X3 = parameter["value"] elif parameter["name"] == "X4": X4 = parameter["value"] elif parameter["name"] == "X5": X5 = parameter["value"] Y = calculator(hid, X1, X2, X3, X4, X5) result_design = {} result_design["hid"] = hid responses = [{"name": "Y", "value": Y}] result_design["responses"] = responses result_design_list.append(result_design) print(f"Return {len(result_design_list)} designs") return result_design_list .. GENERATED FROM PYTHON SOURCE LINES 144-150 Create optiSLang instance ~~~~~~~~~~~~~~~~~~~~~~~~~ Discover available optiSLang installations using ``find_all_osl_exec()``. The ProxySolver node requires optiSLang >= 25R1 (version code 251). ``Optislang(executable=...)`` starts a headless optiSLang server process and establishes a TCP connection for remote control. .. GENERATED FROM PYTHON SOURCE LINES 150-163 .. code-block:: Python available_optislang_executables = find_all_osl_exec() if not available_optislang_executables: raise KeyError("No optiSLang installation was found, please specify path manually.") version, executables = available_optislang_executables.popitem(last=False) if not version >= 251: raise KeyError("OptiSLang installation >= 25R1 wasn't found, please specify path manually.") osl = Optislang(executable=executables[0]) print(f"Using optiSLang version {osl.osl_version_string}") .. GENERATED FROM PYTHON SOURCE LINES 164-168 Create workflow ~~~~~~~~~~~~~~~ Build the complete parametric workflow. The root system is the top-level container for all nodes in an optiSLang project. .. GENERATED FROM PYTHON SOURCE LINES 168-269 .. code-block:: Python root_system = osl.application.project.root_system # **Step 4a: Create the OCO algorithm system.** # ``node_types.OCO`` (optiSLang Combined Optimization) automatically selects and combines # multiple optimization algorithms -- gradient-based (NLPQL, MISQP), evolutionary (EA, PSO), # surrogate-based (ARSM, Kriging), and others -- to find optimal solutions efficiently. algorithm_system: ParametricSystem = root_system.create_node(type_=node_types.OCO, name="OCO") # **Step 4b: Read and modify algorithm settings.** # ``get_property("Settings")`` returns the OCO-specific settings dict. # Unlike Sensitivity/AMOP, OCO settings use a list-of-pairs format: # ``{"sequence": [{"First": key_name, "Second": value}, ...]}`` # This example modifies ``"Maximum number of samples"`` from the default 200 to 150, # which controls how many total designs OCO will evaluate. # The loop searches for the matching entry by name and updates its value. max_num_designs = 150 oco_settings = algorithm_system.get_property("Settings") max_num_samples_entry_found = False for entry in oco_settings["sequence"]: if entry["First"] == "Maximum number of samples": entry["Second"] = max_num_designs max_num_samples_entry_found = True break if not max_num_samples_entry_found: raise KeyError( 'Could not apply OCO setting "Maximum number of samples": ' 'the entry was not found in Settings["sequence"].' ) algorithm_system.set_property("Settings", oco_settings) # **Step 4c: Set fast-running solver properties.** # These reduce I/O overhead for fast external solvers: # - ``AutoSaveMode``: disable auto-saving to avoid filesystem delays. # - ``SolveTwice``: re-evaluate the reference design for verification. # - ``UpdateResultFile``: skip writing intermediate result files. # Note: OCO does not support ``WriteDesignStartSetFlag`` (unlike Sensitivity). algorithm_system.set_property("AutoSaveMode", "no_auto_save") algorithm_system.set_property("SolveTwice", True) algorithm_system.set_property("UpdateResultFile", "never") # **Step 4d: Add the Proxy Solver node.** # The ``ProxySolver`` with ``DesignFlow.RECEIVE_SEND`` acts as a bridge: optiSLang sends # designs to it, and the external Python code retrieves them via ``get_designs()``, # evaluates them, and returns results via ``set_designs()``. # ``MultiDesignLaunchNum`` controls the batch size (99 = up to 99 designs per batch; # set to -1 to receive all pending designs at once). proxy_solver: ProxySolverNode = algorithm_system.create_node( type_=node_types.ProxySolver, name="Calculator", design_flow=DesignFlow.RECEIVE_SEND ) multi_design_launch_num = 99 # set -1 to solve all designs simultaneously proxy_solver.set_property("MultiDesignLaunchNum", multi_design_launch_num) proxy_solver.set_property("ForwardHPCLicenseContextEnvironment", True) # **Step 4e: Load parameters and responses into the proxy solver.** # Define 5 input parameters (X1..X5) with reference value 1.0 and 1 output response (Y) # with reference value 3.0. The ``load()`` call configures the proxy solver's interface # so optiSLang knows which values to send and expect back. load_json = {} load_json["parameters"] = [] load_json["responses"] = [] for i in range(1, 6): parameter = {"dir": {"value": "input"}, "name": f"X{i}", "value": 1.0} load_json["parameters"].append(parameter) response = {"dir": {"value": "output"}, "name": "Y", "value": 3.0} load_json["responses"].append(response) proxy_solver.load(args=load_json) # **Step 4f: Register parameters/responses and set bounds.** # ``register_locations_as_parameter()`` promotes the proxy solver's input slots to # system-level parameters visible to the OCO algorithm. # ``register_locations_as_response()`` does the same for outputs. # Then modify each parameter to an ``OptimizationParameter`` with bounds [-3.14, 3.14]. proxy_solver.register_locations_as_parameter() proxy_solver.register_locations_as_response() for i in range(1, 6): algorithm_system.parameter_manager.modify_parameter( OptimizationParameter(name=f"X{i}", reference_value=1.0, range=(-3.14, 3.14)) ) # **Step 4g: Add optimization criterion.** # Add a minimization objective on the response Y. OCO will use its combined # optimization strategy to find the parameter combination that minimizes Y. algorithm_system.criteria_manager.add_criterion( ObjectiveCriterion(name="obj", expression="Y", criterion=ComparisonType.MIN) ) .. GENERATED FROM PYTHON SOURCE LINES 270-280 Optionally save project ~~~~~~~~~~~~~~~~~~~~~~~ If you want to save the project to some desired location, uncomment and edit these lines: .. code:: python dir_path = Path(r"") project_name = "proxy_solver_oco_workflow.opf" osl.application.save_as(dir_path / project_name) .. GENERATED FROM PYTHON SOURCE LINES 283-291 Run workflow ~~~~~~~~~~~~ **Step 5: Execute the workflow with the external proxy solver loop.** ``start(wait_for_finished=False)`` launches the optiSLang project execution in the background. The while-loop then acts as the external solver: it polls ``get_designs()`` to receive pending design batches from optiSLang, evaluates them with the ``calculate()`` function, and returns the computed responses via ``set_designs()``. The loop continues until ``get_status()`` reports ``"Processing done"``. .. GENERATED FROM PYTHON SOURCE LINES 291-304 .. code-block:: Python osl.application.project.start(wait_for_finished=False) while not osl.project.root_system.get_status() == "Processing done": design_list = proxy_solver.get_designs() if len(design_list): responses_dict = calculate(design_list) proxy_solver.set_designs(responses_dict) time.sleep(0.1) print("Solved Successfully!") .. GENERATED FROM PYTHON SOURCE LINES 305-309 Stop and cancel project ~~~~~~~~~~~~~~~~~~~~~~~ **Step 6: Dispose of the optiSLang instance.** ``dispose()`` stops the optiSLang server process and cleans up resources. .. GENERATED FROM PYTHON SOURCE LINES 309-311 .. code-block:: Python osl.dispose() .. GENERATED FROM PYTHON SOURCE LINES 312-321 View generated workflow ~~~~~~~~~~~~~~~~~~~~~~~ This image shows the generated workflow. However, it is important to note, that this workflow is only usable through pyoptislang and cannot be used interactively! .. image:: ../../_static/03_3_ProxySolverOCO.png :width: 400 :alt: Result of script. .. _sphx_glr_download_examples_workflow_creation_03_3_proxy_solver_oco.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: 03_3_proxy_solver_oco.ipynb <03_3_proxy_solver_oco.ipynb>` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: 03_3_proxy_solver_oco.py <03_3_proxy_solver_oco.py>` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: 03_3_proxy_solver_oco.zip <03_3_proxy_solver_oco.zip>` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_