From 8c30188bb7d724e3c1cce8c9a1ec7cfe6b8a6d42 Mon Sep 17 00:00:00 2001 From: Federico Negri Date: Tue, 6 Feb 2024 23:15:04 +0100 Subject: [PATCH 1/7] draft of dcs migration guide --- .gitignore | 3 +- doc/source/api/jms.rst | 7 - doc/source/conf.py | 1 + doc/source/user_guide/dcs_migration.rst | 652 ++++++++++++++++++++++++ doc/source/user_guide/exceptions.rst | 36 ++ doc/source/user_guide/index.rst | 149 +----- pyproject.toml | 1 + 7 files changed, 704 insertions(+), 145 deletions(-) create mode 100644 doc/source/user_guide/dcs_migration.rst create mode 100644 doc/source/user_guide/exceptions.rst diff --git a/.gitignore b/.gitignore index 958366d9a..b3f19e26b 100644 --- a/.gitignore +++ b/.gitignore @@ -163,4 +163,5 @@ doc/source/api/schemas test_results*.xml mapdl_motorbike_frame.zip examples/mapdl_motorbike_frame/task_input_file* -build_info.* \ No newline at end of file +build_info.* +doc/source/api/ansys \ No newline at end of file diff --git a/doc/source/api/jms.rst b/doc/source/api/jms.rst index a124d0e05..ef37f2c4e 100644 --- a/doc/source/api/jms.rst +++ b/doc/source/api/jms.rst @@ -123,13 +123,6 @@ Design exploration algorithm :members: -Evaluator -^^^^^^^^^ - -.. autoclass:: ansys.hps.client.jms.Evaluator - :members: - - Task definition template ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/doc/source/conf.py b/doc/source/conf.py index 09a1d8d0c..16b43405d 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -43,6 +43,7 @@ "sphinx.ext.intersphinx", "sphinx_copybutton", "sphinx_design", + "sphinx_tabs.tabs", ] exclude_patterns = ["_autoapi_templates", "_build", "Thumbs.db", ".DS_Store"] diff --git a/doc/source/user_guide/dcs_migration.rst b/doc/source/user_guide/dcs_migration.rst new file mode 100644 index 000000000..44b39b89c --- /dev/null +++ b/doc/source/user_guide/dcs_migration.rst @@ -0,0 +1,652 @@ +Migrate DCS Python Client scripts +=================================== + +Few words about HPS being the successor of DCS, similarly pyHPS is the successor of the DCS Python Client. + +DCS Python client doc: https://storage.ansys.com/dcs_python_client/v241/index.html + + +Key Terminology and API Changes +------------------------------- + + +The Design Points Service (DPS) from DCS has evolved into the Job Management Service (JMS) as part of HPS. +Some key entities that were part of the DPS API have been renamed in JMS: + +* Design Point -> Job +* Configuration -> Job Definition +* Process Step -> Task Definition +* Parameter Location -> Parameter Mapping + +Other key changes that impact the Python client and existing scripts: + +* All resource IDs are string now (as opposed to integers) +* In DCS, the client specifies the ID of the project when creating it. + In HPS, the client only specifies the name of the project. The ID is assigned server-side. +* Some nested endpoints have been removed, for instance: + + .. code:: + + GET /dps/api/projects/{project_id}/configurations/{configuration_id}/design_points + GET /dps/api/projects/{project_id}/design_points/{design_point_id}/tasks + + do not have a counterpart in the JMS API. +* In the DPS API, the Configuration resource (now Job Definition) used to include Process Steps, Parameter Definitions and Parameter Locations. + These are now separate resources (with corresponding endpoints) in the JMS API, namely Task Definitions, Parameter Definitions and Parameter Mappings. +* Within a Task Definition (previously Process Step), you can now also specify: + + - Multiple software requirements + - Environment variables + - An execution context + - Custom resource requirements + - HPC resource requirements + + The field ``cpu_core_usage`` has been renamed to ``num_cores``. + ``memory`` and ``disk_space`` are now expressed in bytes (MB before). + + +Python Client Changes +--------------------- + +Installation +~~~~~~~~~~~~ + +The DCS Python client used to be distributed as a wheel part of the Ansys installation. +PyHPS can instead be installed following [ref to how to install]. + + +Client and API objects +~~~~~~~~~~~~~~~~~~~~~~ + +Besides obvious changes to reflect the API changes, we have also done some work to streamline the structure of the package. +The main ``Client`` object now only stores the connection, without exposing any service API. +Similarly, resources like Projects and Jobs do not expose anymore API endpoints. +Instead objects like the ``JmsApi``, the ``ProjectApi`` and the ``RmsApi`` wrap around HPS REST APIs. + +For instance, this is how you instantiate a Client object and retrieve projects: + +.. .. list-table:: +.. :widths: 1 1 +.. :header-rows: 1 + +.. * - DCS Client +.. - PyHPS + +.. * - .. code-block:: python + +.. from ansys.dcs.client.dps import Client + +.. client = Client( +.. dcs_url="https://localhost/dcs", +.. username="dcadmin", +.. password="dcadmin" +.. ) +.. projects = client.get_projects() + +.. - .. code-block:: python + +.. from ansys.rep.client import Client, JmsApi + +.. client = Client( +.. url="https://localhost:8443/hps", +.. username="repadmin", +.. password="repadmin" +.. ) +.. jms_api = JmsApi(client) +.. projects = jms_api.get_projects() + +.. tabs:: + + .. code-tab:: python DCS Client + + from ansys.dcs.client.dps import Client + + client = Client( + dcs_url="https://localhost/dcs", + username="dcadmin", + password="dcadmin" + ) + projects = client.get_projects() + + .. code-tab:: python PyHPS + + from ansys.rep.client import Client, JmsApi + + client = Client( + url="https://localhost:8443/hps", + username="repadmin", + password="repadmin" + ) + jms_api = JmsApi(client) + projects = jms_api.get_projects() + +Project ID +~~~~~~~~~~ + +As mentioned above, in HPS the client only specifies the name of the project. +The project ID is assigned server-side. This is how you create a project in the two cases: + +.. tabs:: + + .. code-tab:: python DCS Client + + from ansys.dcs.client.dps import Client, Project + + client = Client(...) + + proj = Project( + id="my_new_project", + display_name="My New Project" + ) + proj = client.create_project(proj) + + .. code-tab:: python PyHPS + + from ansys.hps.client import Client + from ansys.hps.client.jms import JmsApi, Project + + client = Client(...) + + jms_api = JmsApi(client) + proj = Project(name="My New Project") + proj = jms_api.create_project(proj) + +Removed Nested Endpoints +~~~~~~~~~~~~~~~~~~~~~~~~ + +Following the changes in the API, nested endpoints are removed. + +Exceptions +~~~~~~~~~~ + +Exceptions handling works the same. The ``DCSError`` has been renamed to ``HPSError``. + +.. tabs:: + + .. code-tab:: python DCS Client + + from ansys.dcs.client import DCSError + from ansys.dcs.client.dps import Client + + try: + client = Client( + dcs_url="https://localhost/dcs/", + username="dcadmin", + password="wrong_psw" + ) + except DCSError as e: + print(e) + + .. code-tab:: python PyHPS + + from ansys.hps.client import Client, HPSError + + try: + client = Client( + url="https://localhost:8443/hps", + username="repuser", + password="wrong_psw" + ) + except HPSError as e: + print(e) + +Evaluators +~~~~~~~~~~ + +The evaluators resources and corresponding endpoints have been moved to the new Resource Management Service (RMS). +This is reflected in PyHPS accordingly. + +.. tabs:: + + .. code-tab:: python DCS Client + + from ansys.dcs.client.dps import Client + + client = Client(...) + + evaluators = client.get_evaluators() + + .. code-tab:: python PyHPS + + from ansys.hps.client import Client, RmsApi + + client = Client(...) + + rms_api = RmsApi(client) + evaluators = rms_api.get_evaluators() + + +Example Project +--------------- + +Import modules and instantiate the client. + +.. tabs:: + + .. code-tab:: python DCS Client + + import os + + from ansys.dcs.client.dps import Client + from ansys.dcs.client.dps.resource import ( + Configuration, + DesignPoint, + File, + FitnessDefinition, + Project, + SuccessCriteria + ) + + client = Client( + dcs_url="https://127.0.0.1/dcs", + username="dcadmin", + password="dcadmin" + ) + + .. code-tab:: python PyHPS + + import os + + from ansys.hps.client import Client, JmsApi + from ansys.hps.client.jms import ( + File, + FitnessDefinition, + FloatParameterDefinition, + Job, + JobDefinition, + ParameterMapping, + Project, + ProjectApi, + ResourceRequirements, + Software, + StringParameterDefinition, + SuccessCriteria, + TaskDefinition, + ) + + client = Client( + url="https://localhost:8443/hps", + username="repuser", + password="repuser" + ) + + +Create an empty project and a job definition + +.. tabs:: + + .. code-tab:: python DCS Client + + proj = Project( + id="mapdl_motorbike_frame", + display_name="MAPDL Motorbike Frame", + priority=1, + active=True + ) + proj = client.create_project(proj, replace=True) + + cfg = Configuration(name="Configuration.1", active=True) + + .. code-tab:: python PyHPS + + jms_api = JmsApi(client) + proj = Project(name="MAPDL Motorbike Frame", priority=1, active=True) + proj = jms_api.create_project(proj) + + project_api = ProjectApi(client, proj.id) + + job_def = JobDefinition(name="JobDefinition.1", active=True) + +File resources + +.. tabs:: + + .. code-tab:: python DCS Client + + cwd = os.path.dirname(__file__) + files = [] + + # Input File + files.append( + File( + name="mac", + evaluation_path="motorbike_frame.mac", + type="text/plain", + src=os.path.join(cwd, "motorbike_frame.mac") + ) + ) + + # Output Files + files.append( File( name="results", evaluation_path="motorbike_frame_results.txt", type="text/plain" ) ) + files.append( File( name="img", evaluation_path="file000.jpg", type="image/jpeg", collect=True) ) + files.append( File( name="img2", evaluation_path="file001.jpg", type="image/jpeg", collect=True) ) + files.append( File( name="out", evaluation_path="file.out", type="text/plain", collect=True) ) + + # create file resources on the server + files = proj.create_files(files) + + # For convenience, we keep a reference to the input and result files. + mac_file = files[0] + result_file = files[1] + + .. code-tab:: python PyHPS + + cwd = os.path.dirname(__file__) + files = [] + + # Input File + files.append( + File( + name="mac", + evaluation_path="motorbike_frame.mac", + type="text/plain", + src=os.path.join(cwd, "motorbike_frame.mac"), + ) + ) + + # Output Files + files.append( + File( + name="results", + evaluation_path="motorbike_frame_results.txt", + type="text/plain", + src=os.path.join(cwd, "motorbike_frame_results.txt"), + ) + ) + files.append(File(name="img", evaluation_path="file000.jpg", type="image/jpeg", collect=True)) + files.append(File(name="img2", evaluation_path="file001.jpg", type="image/jpeg", collect=True)) + files.append( + File(name="out", evaluation_path="file.out", type="text/plain", collect=True, monitor=True) + ) + + # create file resources on the server + files = project_api.create_files(files) + + # For convenience, we keep a reference to the input and result files. + mac_file = files[0] + result_file = files[1] + + +Parameters definition + +.. tabs:: + + .. code-tab:: python DCS Client + + # Input params: Dimensions of three custom tubes + float_input_params=[] + for i in range(1,4): + pd = cfg.add_float_parameter_definition( + name='tube%i_radius' %i, + lower_limit=4.0, + upper_limit=20.0,default=12.0 + ) + cfg.add_parameter_location( + key_string='radius(%i)' % i, + tokenizer="=", + parameter_definition_name=pd.name, + file_id=mac_file.id + ) + float_input_params.append(pd) + pd = cfg.add_float_parameter_definition( + name='tube%i_thickness' %i, + lower_limit=0.5, + upper_limit=2.5, + default=1.0 ) + cfg.add_parameter_location( + key_string='thickness(%i)' % i, + tokenizer="=", + parameter_definition_name=pd.name, + file_id=mac_file.id + ) + float_input_params.append(pd) + + # Input params: Custom types used for all the different tubes of the frame + str_input_params=[] + for i in range(1,22): + pd = cfg.add_string_parameter_definition( + name="tube%s" %i, + default="1", + value_list=["1","2","3"] + ) + cfg.add_parameter_location( + key_string='tubes(%i)' % i, + tokenizer="=", + parameter_definition_name=pd.name, + file_id=mac_file.id + ) + str_input_params.append(pd) + + # Output Parames + for pname in ["weight", "torsion_stiffness", "max_stress"]: + pd = cfg.add_float_parameter_definition(name=pname) + cfg.add_parameter_location( + key_string=pname, + tokenizer="=", + parameter_definition_name=pd.name, + file_id=result_file.id + ) + + .. code-tab:: python PyHPS + + # Input params: Dimensions of three custom tubes + float_input_params = [] + for i in range(1, 4): + float_input_params.extend( + [ + FloatParameterDefinition( + name="tube%i_radius" % i, lower_limit=4.0, upper_limit=20.0, default=12.0 + ), + FloatParameterDefinition( + name="tube%i_thickness" % i, lower_limit=0.5, upper_limit=2.5, default=1.0 + ), + ] + ) + + float_input_params = project_api.create_parameter_definitions(float_input_params) + param_mappings = [] + pi = 0 + for i in range(1, 4): + param_mappings.append( + ParameterMapping( + key_string="radius(%i)" % i, + tokenizer="=", + parameter_definition_id=float_input_params[pi].id, + file_id=mac_file.id, + ) + ) + pi += 1 + param_mappings.append( + ParameterMapping( + key_string="thickness(%i)" % i, + tokenizer="=", + parameter_definition_id=float_input_params[pi].id, + file_id=mac_file.id, + ) + ) + pi += 1 + + # Input params: Custom types used for all the different tubes of the frame + str_input_params = [] + for i in range(1, 22): + str_input_params.append( + StringParameterDefinition(name="tube%s" % i, default="1", value_list=["1", "2", "3"]) + ) + str_input_params = project_api.create_parameter_definitions(str_input_params) + + for i in range(1, 22): + param_mappings.append( + ParameterMapping( + key_string="tubes(%i)" % i, + tokenizer="=", + parameter_definition_id=str_input_params[i - 1].id, + file_id=mac_file.id, + ) + ) + + # Output Params + output_params = [] + for pname in ["weight", "torsion_stiffness", "max_stress"]: + output_params.append(FloatParameterDefinition(name=pname)) + output_params = project_api.create_parameter_definitions(output_params) + for pd in output_params: + param_mappings.append( + ParameterMapping( + key_string=pd.name, + tokenizer="=", + parameter_definition_id=pd.id, + file_id=result_file.id, + ) + ) + +Process Step + +.. tabs:: + + .. code-tab:: python DCS Client + + cfg.add_process_step( + name="MAPDL_run", + application_name="ANSYS Mechanical APDL", + application_version="2024 R1", + execution_command="%executable% -b -i %file:mac% -o file.out", + max_execution_time=20.0, + cpu_core_usage=1, + execution_level=0, + memory=250, + disk_space=5, + input_file_ids=[f.id for f in files[:1]], + output_file_ids=[f.id for f in files[1:]], + success_criteria= SuccessCriteria( + return_code=0, + expressions= ["values['tube1_radius']>=4.0", "values['tube1_thickness']>=0.5"], + required_output_file_ids=[ f.id for f in files[2:] ], + require_all_output_files=False, + required_output_parameter_names=["weight", "torsion_stiffness", "max_stress"], + require_all_output_parameters=False + ) + ) + .. code-tab:: python PyHPS + + task_def = TaskDefinition( + name="MAPDL_run", + software_requirements=[ + Software(name="Ansys Mechanical APDL", version="2024 R1"), + ], + execution_command="%executable% -b -i %file:mac% -o file.out -np %resource:num_cores%", + max_execution_time=20.0, + resource_requirements=ResourceRequirements( + num_cores=1.0, + memory=250 * 1024 * 1024, # 250 MB + disk_space=5 * 1024 * 1024, # 5 MB + ), + execution_level=0, + num_trials=1, + input_file_ids=[f.id for f in files[:1]], + output_file_ids=[f.id for f in files[1:]], + success_criteria=SuccessCriteria( + return_code=0, + expressions=["values['tube1_radius']>=4.0", "values['tube1_thickness']>=0.5"], + required_output_file_ids=[ f.id for f in files[2:] ], + require_all_output_files=False, + required_output_parameter_names=["weight", "torsion_stiffness", "max_stress"], + require_all_output_parameters=True, + ), + ) + + +Fitness definition + +.. tabs:: + + .. code-tab:: python DCS Client + + fd = FitnessDefinition(error_fitness=10.0) + fd.add_fitness_term(name="weight", type="design_objective", weighting_factor=1.0, + expression="map_design_objective( values['weight'], 7.5, 5.5)") + fd.add_fitness_term(name="torsional_stiffness", type="target_constraint", weighting_factor=1.0, + expression="map_target_constraint( values['torsion_stiffness'], 1313.0, 5.0, 30.0 )" ) + fd.add_fitness_term(name="max_stress", type="limit_constraint", weighting_factor=1.0, + expression="map_limit_constraint( values['max_stress'], 451.0, 50.0 )") + cfg.fitness_definition =fd + + .. code-tab:: python PyHPS + + fd = FitnessDefinition(error_fitness=10.0) + fd.add_fitness_term( + name="weight", + type="design_objective", + weighting_factor=1.0, + expression="map_design_objective( values['weight'], 7.5, 5.5)", + ) + fd.add_fitness_term( + name="torsional_stiffness", + type="target_constraint", + weighting_factor=1.0, + expression="map_target_constraint( values['torsion_stiffness'], 1313.0, 5.0, 30.0 )", + ) + fd.add_fitness_term( + name="max_stress", + type="limit_constraint", + weighting_factor=1.0, + expression="map_limit_constraint( values['max_stress'], 451.0, 50.0 )", + ) + job_def.fitness_definition = fd + +Create the job definition + +.. tabs:: + + .. code-tab:: python DCS Client + + cfg = proj.create_configurations([cfg])[0] + + .. code-tab:: python PyHPS + + task_defs = [task_def] + + task_defs = project_api.create_task_definitions(task_defs) + param_mappings = project_api.create_parameter_mappings(param_mappings) + + job_def.parameter_definition_ids = [ + pd.id for pd in float_input_params + str_input_params + output_params + ] + job_def.parameter_mapping_ids = [pm.id for pm in param_mappings] + job_def.task_definition_ids = [td.id for td in task_defs] + + job_def = project_api.create_job_definitions([job_def])[0] + +Design Points + +.. tabs:: + + .. code-tab:: python DCS Client + + import random + + dps = [] + for i in range(10): + values = { + p.name: p.lower_limit + random.random() * (p.upper_limit - p.lower_limit) + for p in float_input_params + } + values.update({ p.name: random.choice(p.value_list) for p in str_input_params}) + dps.append( DesignPoint( name=f"DesignPoint.{i}", values=values, eval_status="pending") ) + + dps = cfg.create_design_points(dps) + + .. code-tab:: python PyHPS + + import random + + jobs = [] + for i in range(10): + values = { + p.name: p.lower_limit + random.random() * (p.upper_limit - p.lower_limit) + for p in float_input_params + } + values.update({p.name: random.choice(p.value_list) for p in str_input_params}) + jobs.append( + Job(name=f"Job.{i}", values=values, eval_status="pending", job_definition_id=job_def.id) + ) + jobs = project_api.create_jobs(jobs) \ No newline at end of file diff --git a/doc/source/user_guide/exceptions.rst b/doc/source/user_guide/exceptions.rst new file mode 100644 index 000000000..5cb93a433 --- /dev/null +++ b/doc/source/user_guide/exceptions.rst @@ -0,0 +1,36 @@ +Exception handling +------------------ + +All exceptions that the Ansys REP client explicitly raises inherit from the :exc:`ansys.hps.client.HPSError` +base class. Client errors are raised for 4xx HTTP status codes, while API errors are raised for 5xx HTTP +status codes (server-side errors). + +For example, instantiating a client with invalid credentials returns a 401 client error: + +.. code-block:: python + + from ansys.hps.client import Client, HPSError + + try: + client = Client(url="https://localhost:8443/rep/", username="repuser", password="wrong_psw") + except HPSError as e: + print(e) + + #Output: + # 401 Client Error: invalid_grant for: POST https://localhost:8443/rep/auth/realms/rep/protocol/openid-connect/token + # Invalid user credentials + +A *get* call on a non-existing resource returns a 404 client error: + +.. code-block:: python + + from ansys.hps.client.jms import JmsApi + + jms_api = JmsApi(client) + try: + jms_api.get_project(id="non_existing_project") + except HPSError as e: + print(e) + + #Output: + #404 Client Error: Not Found for: GET https://localhost:8443/rep//jms/api/v1/projects/non_existing_project diff --git a/doc/source/user_guide/index.rst b/doc/source/user_guide/index.rst index 6fc0a07ea..009d9dc51 100644 --- a/doc/source/user_guide/index.rst +++ b/doc/source/user_guide/index.rst @@ -15,6 +15,17 @@ prerequisites: in the Ansys Help. - A Python shell with PyHPS installed. For more information, see :ref:`getting_started`. +.. + This toctreemust be a top level index to get it to show up in + pydata_sphinx_theme + +.. toctree:: + :maxdepth: 1 + :hidden: + + query_parameters + exceptions + dcs_migration Connect to an HPS deployment ---------------------------- @@ -64,105 +75,6 @@ example. username and password. If your REP server is hosted at a different URL or you want to specify different credentials, adjust the script before running it. -Query parameters ----------------- - -Most ``get`` functions support filtering by query parameters. - -Query by property values -^^^^^^^^^^^^^^^^^^^^^^^^ - -You can query resources by the value of their properties: - -.. code-block:: python - - project = jms_api.get_project_by_name(name="Mapdl Motorbike Frame") - project_api = ProjectApi(client, project.id) - - # Get all jobs - jobs = project_api.get_jobs() - - # Get all evaluated jobs - jobs = project_api.get_jobs(eval_status="evaluated") - - -In general, query parameters support these operators: - -- ``lt``: Less than -- ``le``: Less than or equal to -- ``=``: Equal to -- ``ne``: Not equal to -- ``ge``: Greater than or equal to -- ``gt``: Greater than -- ``in``: Value found in list -- ``contains``: Property contains the given string - -.. code-block:: python - - # Equal - jobs = project_api.get_jobs(eval_status="evaluated") - - # In - jobs = project_api.get_jobs(eval_status=["prolog", "running"]) - - # Contains - query_params = {"note.contains": "search_string"} - jobs = project_api.get_jobs(**query_params) - - # Less than - query_params = {"fitness.lt": 1.8} - jobs = project_api.get_jobs(**query_params) - - -Query by fields -^^^^^^^^^^^^^^^ - -When you query a resource, the REST API returns a set of fields by default. You can specify which fields -you want returned by using the ``fields`` query parameter. (The query returns all specified fields in -addition to the ID of the resource, which is always returned.) To request that all fields be returned, -use ``fields="all"``. - -.. code-block:: python - - # Get ID and parameter values for all evaluated jobs - jobs = project_api.get_jobs(fields=["id", "values"], eval_status="evaluated") - - # Get all jobs with all fields - jobs = project_api.get_jobs(fields="all") - -Sorting by property values -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -You can sort resource collections by the values of their properties. -Prefixing with ``-`` (minus) denotes descending order. - -.. code-block:: python - - # Get all jobs sorted by fitness value in ascending order - jobs = project_api.get_jobs(sort="fitness") - - # Get all jobs sorted by fitness value in descending order - jobs = project_api.get_jobs(sort="-fitness") - - # Get all jobs sorted by 'tube1' and 'weight' parameters - jobs = project_api.get_jobs(sort=["values.tube1", "values.weight"]) - print([(job.values["tube1"], job.values["weight"]) for job in jobs]) - -Paginating items in a collection -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -You can use the ``offset`` and ``limit`` query parameters to paginate items in a collection. - -.. code-block:: python - - # Get the name and elapsed time of a maximum of 5 evaluated jobs, sorted by creation time - jobs = project_api.get_jobs(fields=["name", "elapsed_time"], sort="-creation_time", - eval_status="evaluated", limit=5) - - # Query the next 10 jobs - jobs = project_api.get_jobs(fields=["name", "elapsed_time"], sort="-creation_time", - eval_status="evaluated", limit=10, offset=5) - Objects versus dictionaries --------------------------- @@ -319,41 +231,4 @@ Administrative users with the Keycloak "manage-users" role can create users as w # } new_user.password = "new_password" - auth_api.update_user(new_user) - -Exception handling ------------------- - -All exceptions that the Ansys REP client explicitly raises inherit from the :exc:`ansys.hps.client.HPSError` -base class. Client errors are raised for 4xx HTTP status codes, while API errors are raised for 5xx HTTP -status codes (server-side errors). - -For example, instantiating a client with invalid credentials returns a 401 client error: - -.. code-block:: python - - from ansys.hps.client import Client, HPSError - - try: - client = Client(url="https://localhost:8443/rep/", username="repuser", password="wrong_psw") - except HPSError as e: - print(e) - - #Output: - # 401 Client Error: invalid_grant for: POST https://localhost:8443/rep/auth/realms/rep/protocol/openid-connect/token - # Invalid user credentials - -A *get* call on a non-existing resource returns a 404 client error: - -.. code-block:: python - - from ansys.hps.client.jms import JmsApi - - jms_api = JmsApi(client) - try: - jms_api.get_project(id="non_existing_project") - except HPSError as e: - print(e) - - #Output: - #404 Client Error: Not Found for: GET https://localhost:8443/rep//jms/api/v1/projects/non_existing_project + auth_api.update_user(new_user) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 27a5a89ec..7ae7861d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,7 @@ doc = [ "sphinx_design==0.5.0", "sphinx-jinja==2.0.2", "sphinxnotes-strike==1.2.1", + "sphinx-tabs==3.4.5", ] build = [ From 55e944bc6966d884b5e5d809d1c044c2dbdaaf32 Mon Sep 17 00:00:00 2001 From: Federico Negri Date: Tue, 6 Feb 2024 23:19:11 +0100 Subject: [PATCH 2/7] add missing file --- doc/source/user_guide/query_parameters.rst | 100 +++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 doc/source/user_guide/query_parameters.rst diff --git a/doc/source/user_guide/query_parameters.rst b/doc/source/user_guide/query_parameters.rst new file mode 100644 index 000000000..f13e65346 --- /dev/null +++ b/doc/source/user_guide/query_parameters.rst @@ -0,0 +1,100 @@ + +Query parameters +---------------- + +Most ``get`` functions support filtering by query parameters. + +Query by property values +^^^^^^^^^^^^^^^^^^^^^^^^ + +You can query resources by the value of their properties: + +.. code-block:: python + + project = jms_api.get_project_by_name(name="Mapdl Motorbike Frame") + project_api = ProjectApi(client, project.id) + + # Get all jobs + jobs = project_api.get_jobs() + + # Get all evaluated jobs + jobs = project_api.get_jobs(eval_status="evaluated") + + +In general, query parameters support these operators: + +- ``lt``: Less than +- ``le``: Less than or equal to +- ``=``: Equal to +- ``ne``: Not equal to +- ``ge``: Greater than or equal to +- ``gt``: Greater than +- ``in``: Value found in list +- ``contains``: Property contains the given string + +.. code-block:: python + + # Equal + jobs = project_api.get_jobs(eval_status="evaluated") + + # In + jobs = project_api.get_jobs(eval_status=["prolog", "running"]) + + # Contains + query_params = {"note.contains": "search_string"} + jobs = project_api.get_jobs(**query_params) + + # Less than + query_params = {"fitness.lt": 1.8} + jobs = project_api.get_jobs(**query_params) + + +Query by fields +^^^^^^^^^^^^^^^ + +When you query a resource, the REST API returns a set of fields by default. You can specify which fields +you want returned by using the ``fields`` query parameter. (The query returns all specified fields in +addition to the ID of the resource, which is always returned.) To request that all fields be returned, +use ``fields="all"``. + +.. code-block:: python + + # Get ID and parameter values for all evaluated jobs + jobs = project_api.get_jobs(fields=["id", "values"], eval_status="evaluated") + + # Get all jobs with all fields + jobs = project_api.get_jobs(fields="all") + +Sorting by property values +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can sort resource collections by the values of their properties. +Prefixing with ``-`` (minus) denotes descending order. + +.. code-block:: python + + # Get all jobs sorted by fitness value in ascending order + jobs = project_api.get_jobs(sort="fitness") + + # Get all jobs sorted by fitness value in descending order + jobs = project_api.get_jobs(sort="-fitness") + + # Get all jobs sorted by 'tube1' and 'weight' parameters + jobs = project_api.get_jobs(sort=["values.tube1", "values.weight"]) + print([(job.values["tube1"], job.values["weight"]) for job in jobs]) + +Paginating items in a collection +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can use the ``offset`` and ``limit`` query parameters to paginate items in a collection. + +.. code-block:: python + + # Get the name and elapsed time of a maximum of 5 evaluated jobs, sorted by creation time + jobs = project_api.get_jobs(fields=["name", "elapsed_time"], sort="-creation_time", + eval_status="evaluated", limit=5) + + # Query the next 10 jobs + jobs = project_api.get_jobs(fields=["name", "elapsed_time"], sort="-creation_time", + eval_status="evaluated", limit=10, offset=5) + From 53400857979428d1ede44f7f8edb1e665f644c24 Mon Sep 17 00:00:00 2001 From: Federico Negri Date: Fri, 9 Feb 2024 09:38:41 +0100 Subject: [PATCH 3/7] incorporate feedback and complete the example --- doc/source/user_guide/dcs_migration.rst | 137 ++++++++++++++---------- 1 file changed, 78 insertions(+), 59 deletions(-) diff --git a/doc/source/user_guide/dcs_migration.rst b/doc/source/user_guide/dcs_migration.rst index 44b39b89c..b3ded3292 100644 --- a/doc/source/user_guide/dcs_migration.rst +++ b/doc/source/user_guide/dcs_migration.rst @@ -1,69 +1,75 @@ Migrate DCS Python Client scripts =================================== -Few words about HPS being the successor of DCS, similarly pyHPS is the successor of the DCS Python Client. +Just as HPS is the successor to DCS (Distributed Compute Services), PyHPS is the successor to the DCS Python Client. +For more information, see the latest `DCS Python Client documentation `_. -DCS Python client doc: https://storage.ansys.com/dcs_python_client/v241/index.html - -Key Terminology and API Changes +Key terminology and API changes ------------------------------- +The Design Points Service (DPS) in DCS has evolved into the Job Management Service (JMS) in HPS. +Some key entities that were part of DPS are renamed in JMS: -The Design Points Service (DPS) from DCS has evolved into the Job Management Service (JMS) as part of HPS. -Some key entities that were part of the DPS API have been renamed in JMS: +.. list-table:: + :header-rows: 1 -* Design Point -> Job -* Configuration -> Job Definition -* Process Step -> Task Definition -* Parameter Location -> Parameter Mapping + * - DPS + - JMS + * - Configuration + - Job Definition + * - Design Point + - Job + * - Process Step + - Task Definition + * - Parameter Location + - Parameter Mapping -Other key changes that impact the Python client and existing scripts: +Other key changes also impact scripts that use the DCS Python Client: -* All resource IDs are string now (as opposed to integers) +* All resource IDs are strings now (as opposed to integers). * In DCS, the client specifies the ID of the project when creating it. - In HPS, the client only specifies the name of the project. The ID is assigned server-side. -* Some nested endpoints have been removed, for instance: + In HPS, the client specifies only the name of the project. The ID is assigned server-side. +* Some nested endpoints have been removed. For example, the following endpoints do not have a counterpart in the JMS API: .. code:: GET /dps/api/projects/{project_id}/configurations/{configuration_id}/design_points GET /dps/api/projects/{project_id}/design_points/{design_point_id}/tasks - do not have a counterpart in the JMS API. -* In the DPS API, the Configuration resource (now Job Definition) used to include Process Steps, Parameter Definitions and Parameter Locations. - These are now separate resources (with corresponding endpoints) in the JMS API, namely Task Definitions, Parameter Definitions and Parameter Mappings. -* Within a Task Definition (previously Process Step), you can now also specify: +* In the DPS API, the configuration resource (now job definition) used to include process steps, parameter definitions, and parameter locations. + These are now separate resources (with corresponding endpoints) in the JMS API, namely task definitions, parameter definitions, and parameter mappings. +* Within a task definition (previously process step), you can now also specify the following information: - - Multiple software requirements - - Environment variables - - An execution context - - Custom resource requirements - - HPC resource requirements + * Multiple software requirements + * Environment variables + * Execution context + * Custom resource requirements + * HPC resource requirements - The field ``cpu_core_usage`` has been renamed to ``num_cores``. - ``memory`` and ``disk_space`` are now expressed in bytes (MB before). + The ``cpu_core_usage`` field has been renamed to ``num_cores``. + The ``memory`` and ``disk_space`` fields are expressed in bytes rather than MB. -Python Client Changes +Python client changes --------------------- Installation ~~~~~~~~~~~~ -The DCS Python client used to be distributed as a wheel part of the Ansys installation. -PyHPS can instead be installed following [ref to how to install]. +The DCS Python client used to be distributed as a wheel in the Ansys installation. +You can install PyHPS as indicated in :ref:`getting_started`. Client and API objects ~~~~~~~~~~~~~~~~~~~~~~ -Besides obvious changes to reflect the API changes, we have also done some work to streamline the structure of the package. -The main ``Client`` object now only stores the connection, without exposing any service API. -Similarly, resources like Projects and Jobs do not expose anymore API endpoints. -Instead objects like the ``JmsApi``, the ``ProjectApi`` and the ``RmsApi`` wrap around HPS REST APIs. +With respect to the DCS Python Client, the PyHPS structure has been streamlined. +The main ``Client`` object stores only the connection, without exposing any service API. +Similarly, resources like ``Project`` and ``Job`` do not expose API endpoints. +Instead, objects like ``JmsApi``, ``ProjectApi``, and ``RmsApi`` wrap around HPS REST APIs. -For instance, this is how you instantiate a Client object and retrieve projects: +This code shows how to instantiate a ``Client`` object and retrieve projects: .. .. list-table:: .. :widths: 1 1 @@ -97,7 +103,7 @@ For instance, this is how you instantiate a Client object and retrieve projects: .. tabs:: - .. code-tab:: python DCS Client + .. code-tab:: python DCS client from ansys.dcs.client.dps import Client @@ -123,12 +129,12 @@ For instance, this is how you instantiate a Client object and retrieve projects: Project ID ~~~~~~~~~~ -As mentioned above, in HPS the client only specifies the name of the project. -The project ID is assigned server-side. This is how you create a project in the two cases: +As mentioned earlier, in HPS the client only specifies the name of the project. +The project ID is assigned server-side. This code shows how to create a project: .. tabs:: - .. code-tab:: python DCS Client + .. code-tab:: python DCS client from ansys.dcs.client.dps import Client, Project @@ -151,7 +157,7 @@ The project ID is assigned server-side. This is how you create a project in the proj = Project(name="My New Project") proj = jms_api.create_project(proj) -Removed Nested Endpoints +Removed nested endpoints ~~~~~~~~~~~~~~~~~~~~~~~~ Following the changes in the API, nested endpoints are removed. @@ -159,11 +165,11 @@ Following the changes in the API, nested endpoints are removed. Exceptions ~~~~~~~~~~ -Exceptions handling works the same. The ``DCSError`` has been renamed to ``HPSError``. +Exceptions handling works the same. The ``DCSError`` class has been renamed to ``HPSError``. .. tabs:: - .. code-tab:: python DCS Client + .. code-tab:: python DCS client from ansys.dcs.client import DCSError from ansys.dcs.client.dps import Client @@ -198,7 +204,7 @@ This is reflected in PyHPS accordingly. .. tabs:: - .. code-tab:: python DCS Client + .. code-tab:: python DCS client from ansys.dcs.client.dps import Client @@ -216,14 +222,20 @@ This is reflected in PyHPS accordingly. evaluators = rms_api.get_evaluators() -Example Project +Example project --------------- -Import modules and instantiate the client. +This example shows how to migrate a script that creates a DCS project consisting +of an Ansys APDL beam model of a tubular motorbike-frame. +The script was originally included in the `DCS Python Client documentation `_ +and is now available as a PyHPS script in :ref:`example_mapdl_motorbike_frame`. + +Import modules and instantiate the client +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. tabs:: - .. code-tab:: python DCS Client + .. code-tab:: python DCS client import os @@ -272,14 +284,15 @@ Import modules and instantiate the client. Create an empty project and a job definition +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. tabs:: - .. code-tab:: python DCS Client + .. code-tab:: python DCS client proj = Project( id="mapdl_motorbike_frame", - display_name="MAPDL Motorbike Frame", + display_name="MAPDL motorbike frame", priority=1, active=True ) @@ -290,18 +303,19 @@ Create an empty project and a job definition .. code-tab:: python PyHPS jms_api = JmsApi(client) - proj = Project(name="MAPDL Motorbike Frame", priority=1, active=True) + proj = Project(name="MAPDL motorbike frame", priority=1, active=True) proj = jms_api.create_project(proj) project_api = ProjectApi(client, proj.id) job_def = JobDefinition(name="JobDefinition.1", active=True) -File resources +Create file resources +~~~~~~~~~~~~~~~~~~~~~ .. tabs:: - .. code-tab:: python DCS Client + .. code-tab:: python DCS client cwd = os.path.dirname(__file__) files = [] @@ -367,11 +381,12 @@ File resources result_file = files[1] -Parameters definition +Create parameter definitions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. tabs:: - .. code-tab:: python DCS Client + .. code-tab:: python DCS client # Input params: Dimensions of three custom tubes float_input_params=[] @@ -499,11 +514,12 @@ Parameters definition ) ) -Process Step +Create process steps (now tasks) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. tabs:: - .. code-tab:: python DCS Client + .. code-tab:: python DCS client cfg.add_process_step( name="MAPDL_run", @@ -555,11 +571,12 @@ Process Step ) -Fitness definition +Add a fitness definition +~~~~~~~~~~~~~~~~~~~~~~~~ .. tabs:: - .. code-tab:: python DCS Client + .. code-tab:: python DCS client fd = FitnessDefinition(error_fitness=10.0) fd.add_fitness_term(name="weight", type="design_objective", weighting_factor=1.0, @@ -593,11 +610,12 @@ Fitness definition ) job_def.fitness_definition = fd -Create the job definition +Create a configuration (now job definition) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. tabs:: - .. code-tab:: python DCS Client + .. code-tab:: python DCS client cfg = proj.create_configurations([cfg])[0] @@ -616,11 +634,12 @@ Create the job definition job_def = project_api.create_job_definitions([job_def])[0] -Design Points +Create design points (now jobs) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. tabs:: - .. code-tab:: python DCS Client + .. code-tab:: python DCS client import random From a0c7479adf0e80e46ba503182dd1e3a80a1c2924 Mon Sep 17 00:00:00 2001 From: Federico Negri Date: Fri, 9 Feb 2024 09:43:31 +0100 Subject: [PATCH 4/7] remove duplicate header --- doc/source/user_guide/exceptions.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/doc/source/user_guide/exceptions.rst b/doc/source/user_guide/exceptions.rst index 78ecafcaf..e3965d0a9 100644 --- a/doc/source/user_guide/exceptions.rst +++ b/doc/source/user_guide/exceptions.rst @@ -1,9 +1,6 @@ Exception handling ------------------ -Exception handling ------------------- - All exceptions that the Ansys HPS client explicitly raises inherit from the :exc:`ansys.hps.client.HPSError` base class. Client errors are raised for 4xx HTTP status codes, while API errors are raised for 5xx HTTP status codes (server-side errors). From 24cf9d902f64792e88cd8c6f1590e5422fa47a2b Mon Sep 17 00:00:00 2001 From: Federico Negri Date: Fri, 9 Feb 2024 17:42:30 +0100 Subject: [PATCH 5/7] use sentence case for table and remove commented code --- doc/source/user_guide/dcs_migration.rst | 42 ++++--------------------- 1 file changed, 6 insertions(+), 36 deletions(-) diff --git a/doc/source/user_guide/dcs_migration.rst b/doc/source/user_guide/dcs_migration.rst index b3ded3292..01cd37de5 100644 --- a/doc/source/user_guide/dcs_migration.rst +++ b/doc/source/user_guide/dcs_migration.rst @@ -17,13 +17,13 @@ Some key entities that were part of DPS are renamed in JMS: * - DPS - JMS * - Configuration - - Job Definition - * - Design Point + - Job definition + * - Design point - Job - * - Process Step - - Task Definition - * - Parameter Location - - Parameter Mapping + * - Process step + - Task definition + * - Parameter location + - Parameter mapping Other key changes also impact scripts that use the DCS Python Client: @@ -71,36 +71,6 @@ Instead, objects like ``JmsApi``, ``ProjectApi``, and ``RmsApi`` wrap around HPS This code shows how to instantiate a ``Client`` object and retrieve projects: -.. .. list-table:: -.. :widths: 1 1 -.. :header-rows: 1 - -.. * - DCS Client -.. - PyHPS - -.. * - .. code-block:: python - -.. from ansys.dcs.client.dps import Client - -.. client = Client( -.. dcs_url="https://localhost/dcs", -.. username="dcadmin", -.. password="dcadmin" -.. ) -.. projects = client.get_projects() - -.. - .. code-block:: python - -.. from ansys.rep.client import Client, JmsApi - -.. client = Client( -.. url="https://localhost:8443/hps", -.. username="repadmin", -.. password="repadmin" -.. ) -.. jms_api = JmsApi(client) -.. projects = jms_api.get_projects() - .. tabs:: .. code-tab:: python DCS client From c1329dfa32a81d10b83bb95b8f811bedbe0c4a84 Mon Sep 17 00:00:00 2001 From: Kathy Pippert Date: Fri, 9 Feb 2024 15:56:03 -0500 Subject: [PATCH 6/7] Very minor edits --- doc/source/user_guide/dcs_migration.rst | 22 +++++++++++----------- doc/source/user_guide/index.rst | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/doc/source/user_guide/dcs_migration.rst b/doc/source/user_guide/dcs_migration.rst index 01cd37de5..828b1ffd8 100644 --- a/doc/source/user_guide/dcs_migration.rst +++ b/doc/source/user_guide/dcs_migration.rst @@ -1,7 +1,7 @@ -Migrate DCS Python Client scripts -=================================== +Migration of DCS Python client scripts +====================================== -Just as HPS is the successor to DCS (Distributed Compute Services), PyHPS is the successor to the DCS Python Client. +Just as HPS is the successor to DCS (Distributed Compute Services), PyHPS is the successor to the DCS Python client. For more information, see the latest `DCS Python Client documentation `_. @@ -25,7 +25,7 @@ Some key entities that were part of DPS are renamed in JMS: * - Parameter location - Parameter mapping -Other key changes also impact scripts that use the DCS Python Client: +Here are some other key changes impacting scripts that use the DCS Python Client: * All resource IDs are strings now (as opposed to integers). * In DCS, the client specifies the ID of the project when creating it. @@ -48,7 +48,7 @@ Other key changes also impact scripts that use the DCS Python Client: * HPC resource requirements The ``cpu_core_usage`` field has been renamed to ``num_cores``. - The ``memory`` and ``disk_space`` fields are expressed in bytes rather than MB. + The ``memory`` and ``disk_space`` fields are expressed in bytes rather than megabytes. Python client changes @@ -64,7 +64,7 @@ You can install PyHPS as indicated in :ref:`getting_started`. Client and API objects ~~~~~~~~~~~~~~~~~~~~~~ -With respect to the DCS Python Client, the PyHPS structure has been streamlined. +With respect to the DCS Python client, the PyHPS structure has been streamlined. The main ``Client`` object stores only the connection, without exposing any service API. Similarly, resources like ``Project`` and ``Job`` do not expose API endpoints. Instead, objects like ``JmsApi``, ``ProjectApi``, and ``RmsApi`` wrap around HPS REST APIs. @@ -135,7 +135,7 @@ Following the changes in the API, nested endpoints are removed. Exceptions ~~~~~~~~~~ -Exceptions handling works the same. The ``DCSError`` class has been renamed to ``HPSError``. +Exception handling works the same. However, the ``DCSError`` class has been renamed to ``HPSError``. .. tabs:: @@ -169,7 +169,7 @@ Exceptions handling works the same. The ``DCSError`` class has been renamed to ` Evaluators ~~~~~~~~~~ -The evaluators resources and corresponding endpoints have been moved to the new Resource Management Service (RMS). +The evaluator resources and corresponding endpoints have been moved to the new Resource Management Service (RMS). This is reflected in PyHPS accordingly. .. tabs:: @@ -198,7 +198,7 @@ Example project This example shows how to migrate a script that creates a DCS project consisting of an Ansys APDL beam model of a tubular motorbike-frame. The script was originally included in the `DCS Python Client documentation `_ -and is now available as a PyHPS script in :ref:`example_mapdl_motorbike_frame`. +and is now available as a PyHPS script in the :ref:`example_mapdl_motorbike_frame` example. Import modules and instantiate the client ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -309,7 +309,7 @@ Create file resources # create file resources on the server files = proj.create_files(files) - # For convenience, we keep a reference to the input and result files. + # For convenience, keep a reference to the input and result files. mac_file = files[0] result_file = files[1] @@ -346,7 +346,7 @@ Create file resources # create file resources on the server files = project_api.create_files(files) - # For convenience, we keep a reference to the input and result files. + # For convenience, keep a reference to the input and result files. mac_file = files[0] result_file = files[1] diff --git a/doc/source/user_guide/index.rst b/doc/source/user_guide/index.rst index 2a2841e95..afd18679c 100644 --- a/doc/source/user_guide/index.rst +++ b/doc/source/user_guide/index.rst @@ -4,8 +4,8 @@ User guide ========== This section walks you through the basics of how to interact with HPS. -For more elaborate examples, see :ref:`Examples `. For descriptions -of PyHPS endpoints, see :ref:`API reference `. +For more elaborate examples, see :ref:`examples`. For descriptions +of PyHPS endpoints, see :ref:`api_reference`. To reproduce the code samples provided in this section, you must have these prerequisites: From f361144f852bd40029f0184ce6037401257ce89d Mon Sep 17 00:00:00 2001 From: Federico Negri Date: Sat, 10 Feb 2024 07:18:38 +0100 Subject: [PATCH 7/7] use repuser instead of repadmin --- doc/source/user_guide/dcs_migration.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/source/user_guide/dcs_migration.rst b/doc/source/user_guide/dcs_migration.rst index 828b1ffd8..c8787a751 100644 --- a/doc/source/user_guide/dcs_migration.rst +++ b/doc/source/user_guide/dcs_migration.rst @@ -86,12 +86,12 @@ This code shows how to instantiate a ``Client`` object and retrieve projects: .. code-tab:: python PyHPS - from ansys.rep.client import Client, JmsApi + from ansys.hps.client import Client, JmsApi client = Client( url="https://localhost:8443/hps", - username="repadmin", - password="repadmin" + username="repuser", + password="repuser" ) jms_api = JmsApi(client) projects = jms_api.get_projects() @@ -638,4 +638,4 @@ Create design points (now jobs) jobs.append( Job(name=f"Job.{i}", values=values, eval_status="pending", job_definition_id=job_def.id) ) - jobs = project_api.create_jobs(jobs) \ No newline at end of file + jobs = project_api.create_jobs(jobs)