Copyright (C) 2023 - 2024 ANSYS, Inc. and/or its affiliates. SPDX-License-Identifier: MIT
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Simple workflow#
This example shows how to use PyConcentEV to perform basic operations.
Perform required imports#
Perform required imports.
[1]:
import datetime
import os
from pathlib import Path
[2]:
import plotly.graph_objects as go
[3]:
from ansys.conceptev.core import app, auth
ERROR:root:Encryption unavailable. Opting in to plain text.
Traceback (most recent call last):
File "/home/runner/work/pyconceptev/pyconceptev/.venv/lib/python3.11/site-packages/msal_extensions/libsecret.py", line 18, in <module>
import gi # https://github.com/AzureAD/microsoft-authentication-extensions-for-python/wiki/Encryption-on-Linux # pylint: disable=line-too-long
^^^^^^^^^
ModuleNotFoundError: No module named 'gi'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/runner/work/pyconceptev/pyconceptev/src/ansys/conceptev/core/auth.py", line 75, in build_persistence
return LibsecretPersistence(
^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/pyconceptev/pyconceptev/.venv/lib/python3.11/site-packages/msal_extensions/persistence.py", line 314, in __init__
from .libsecret import ( # This uncertain import is deferred till runtime
File "/home/runner/work/pyconceptev/pyconceptev/.venv/lib/python3.11/site-packages/msal_extensions/libsecret.py", line 20, in <module>
raise ImportError("""Unable to import module 'gi'
ImportError: Unable to import module 'gi'
Runtime dependency of PyGObject is missing.
Depends on your Linux distro, you could install it system-wide by something like:
sudo apt install python3-gi python3-gi-cairo gir1.2-secret-1
If necessary, please refer to PyGObject's doc:
https://pygobject.readthedocs.io/en/latest/getting_started.html
[4]:
# ## Set up environment variables
# AnsysID is the only supported method.
# We only use the other one here for automated testing. So set to True.
use_ansys_id = False # True
[5]:
if not (use_ansys_id):
# Set environment variables for ConceptEV username and password if they don't exist!
if os.environ.get("CONCEPTEV_USERNAME") is None:
os.environ["CONCEPTEV_USERNAME"] = "joe.blogs@my_work.com"
if os.environ.get("CONCEPTEV_PASSWORD") is None:
os.environ["CONCEPTEV_PASSWORD"] = "sup3r_s3cr3t_passw0rd"
Define example data#
You can obtain example data from the schema sections of the API documentation.
[6]:
MOTOR_LAB_FILE = Path("resources") / "e9.lab"
MOTOR_LOSS_MAP_FILE = Path("resources") / "e9.xlsx"
AERO_1 = {
"name": "New Aero Config",
"drag_coefficient": 0.3,
"cross_sectional_area": 2,
"config_type": "aero",
}
AERO_2 = {
"name": "Second Aero Configuration",
"drag_coefficient": 0.6,
"cross_sectional_area": 3,
"config_type": "aero",
}
MASS = {
"name": "New Mass Config",
"mass": 3000,
"config_type": "mass",
}
WHEEL = {
"name": "New Wheel Config",
"rolling_radius": 0.3,
"config_type": "wheel",
}
TRANSMISSION = {
"gear_ratios": [5],
"headline_efficiencies": [0.95],
"max_torque": 500,
"max_speed": 2000,
"static_drags": [0.5],
"friction_ratios": [60],
"windage_ratios": [40],
"component_type": "TransmissionLossCoefficients",
}
BATTERY = {
"capacity": 86400000,
"charge_acceptance_limit": 0,
"component_type": "BatteryFixedVoltages",
"internal_resistance": 0.1,
"name": "New Battery",
"voltage_max": 400,
"voltage_mid": 350,
"voltage_min": 300,
}
motor_data = {"name": "e9", "component_type": "MotorLabID", "inverter_losses_included": False}
if use_ansys_id:
# Get a token from Ansys ID (Preferred)
msal_app = auth.create_msal_app()
token = auth.get_ansyId_token(msal_app)
else:
# Get a token from OCM (Deprecated)
token = app.get_token()
# Use API client for the Ansys ConceptEV service
with app.get_http_client(token) as client:
health = app.get(client, "/health")
print(f"API is healthy: {health}\n")
accounts = app.get_account_ids(token)
# Uncomment to print accounts IDs
# print(f"Account IDs: {accounts}\n")
account_id = accounts["conceptev_saas@ansys.com"]
hpc_id = app.get_default_hpc(token, account_id)
# Uncomment to print HPC ID
# print(f"HPC ID: {hpc_id}\n")
# Create a project
project = app.create_new_project(
client, account_id, hpc_id, f"New Project +{datetime.datetime.now()}"
)
print(f"ID of the created project: {project['projectId']}")
# Create a concept with that project
concept = app.create_new_concept(
client, project["projectId"], f"New Concept +{datetime.datetime.now()}"
)
print(f"ID of the created concept: {concept['id']}")
# ### Perform basic operations
#
# Perform basic operations on the design instance associated with the new project.
API is healthy: {'name': 'ConceptEV', 'version': '0.2.7'}
ID of the created project: 9b08a81e-f7e9-44b2-99ca-dd228059b9aa
ID of the created concept: 673b224031e8085039dc6038
[7]:
design_instance_id = concept["design_instance_id"]
with app.get_http_client(token, design_instance_id) as client:
# Create configurations
created_aero = app.post(client, "/configurations", data=AERO_1)
created_aero2 = app.post(client, "/configurations", data=AERO_2)
created_mass = app.post(client, "/configurations", data=MASS)
created_wheel = app.post(client, "/configurations", data=WHEEL)
# Read all aero configurations
configurations = app.get(
client, f"/concepts/{design_instance_id}/configurations", params={"config_type": "aero"}
)
# Uncomment to print configurations
# print(f"List of configurations: {configurations}\n")
# Get a specific aero configuration
aero = app.get(client, "/configurations", id=created_aero["id"])
print(f"First created areo configuration: {aero}\n")
# Create component
created_transmission = app.post(client, "/components", data=TRANSMISSION)
# Create component from file
motor_lab = app.post_component_file(client, MOTOR_LAB_FILE, "motor_lab_file")
motor_data["data_id"] = motor_lab[0]
motor_data["max_speed"] = motor_lab[1]
created_motor_lab = app.post(client, "/components", data=motor_data)
print(f"Created motor: {created_motor_lab}\n")
# Create loss map motor component from file
client.timeout = 2000
motor_loss_map = app.post_component_file(client, MOTOR_LOSS_MAP_FILE, "motor_torque_grid_file")
loss_map_motor_data = {
"name": "e9_loss_map",
"component_type": "MotorLossMapID",
"poles": 8,
"data_id": motor_loss_map[0],
}
created_motor = app.post(client, "/components", data=loss_map_motor_data)
print(f"Created motor: {created_motor}\n")
# Extend client timeout to get loss map from the motor
client.timeout = 2000
motor_loss_map = app.post(
client,
"/components:get_display_data",
data={},
params={"component_id": created_motor_lab["id"]},
)
# Show a figure of the loss map from the motor in you browser
x = motor_loss_map["currents"]
y = motor_loss_map["phase_advances"]
z = motor_loss_map["losses_total"]
fig = go.Figure(data=go.Contour(x=x, y=y, z=z))
fig.show()
created_battery = app.post(client, "/components", data=BATTERY)
# Create an architecture
architecture = {
"number_of_front_wheels": 2,
"number_of_front_motors": 1,
"front_transmission_id": created_transmission["id"],
"front_motor_id": created_motor["id"],
"number_of_rear_wheels": 2,
"number_of_rear_motors": 0,
"battery_id": created_battery["id"],
}
created_arch = app.post(client, "/architectures", data=architecture)
print(f"Created architecture: {created_arch}\n")
# Create a requirement
requirement = {
"speed": 10,
"acceleration": 1,
"aero_id": created_aero["id"],
"mass_id": created_mass["id"],
"wheel_id": created_wheel["id"],
"state_of_charge": 0.9,
"requirement_type": "static_acceleration",
"name": "Static Requirement 1",
}
created_requirement = app.post(client, "requirements", data=requirement)
print(f"Created requirement: {created_requirement}")
First created areo configuration: {'item_type': 'config', 'name': 'New Aero Config', 'drag_coefficient': 0.3, 'cross_sectional_area': 2.0, 'config_type': 'aero', 'id': '673b224031e8085039dc6039'}
Created motor: {'item_type': 'component', 'name': 'e9', 'mass': 0.0, 'cost': 0.0, 'id': '673b224019a32098968aeab9', 'data_id': '673b224031e8085039dc603c', 'submitted_job': None, 'inverter_losses_included': False, 'max_speed': 10000.0, 'stator_winding_temp': None, 'rotor_temp': None, 'stator_current_limit': None, 'component_type': 'MotorLabID'}
Created motor: {'item_type': 'component', 'name': 'e9_loss_map', 'mass': 0.0, 'cost': 0.0, 'id': '673b224519a32098968aeabc', 'data_id': '673b224519a32098968aeaba', 'submitted_job': None, 'poles': 8, 'voltages': [400.0, 300.0], 'inverter_losses_included': False, 'component_type': 'MotorLossMapID'}
Created architecture: {'id': '673b224931e8085039dc603e', 'wheelbase': None, 'components_cost': 0.0, 'components_mass': 0.0, 'max_wheel_speed': 2000.0, 'number_of_front_wheels': 2, 'number_of_front_motors': 1, 'front_clutch_id': None, 'front_transmission_id': '673b224031e8085039dc603b', 'front_motor_id': '673b224519a32098968aeabc', 'front_inverter_id': None, 'number_of_rear_wheels': 2, 'number_of_rear_motors': 0, 'rear_clutch_id': None, 'rear_transmission_id': None, 'rear_motor_id': None, 'rear_inverter_id': None, 'battery_id': '673b224619a32098968aeabd'}
Created requirement: {'requirement_type': 'static_acceleration', 'id': '673b224919a32098968aeabf', 'name': 'Static Requirement 1', 'speed': 9.999999999999998, 'acceleration': 1.0, 'aero_id': '673b224031e8085039dc6039', 'mass_id': '673b224019a32098968aeab6', 'wheel_id': '673b224019a32098968aeab7', 'deceleration_limit_id': None, 'state_of_charge': 0.9000000000000001, 'altitude': 0.0, 'headwind': 0.0, 'gradient': 0.0}
Following code is not working in the pipelne but should work in a local environment.
Submit a job and show the result. with app.get_http_client(token, design_instance_id) as client:
# Create and submit a job
concept = app.get(client, "/concepts", id=design_instance_id, params={"populated": True})
job_info = app.create_submit_job(client, concept, account_id, hpc_id)
# Read the results and show the result in your browser
results = app.read_results(client, job_info, calculate_units=False, filtered=True)
x = results[0]["capability_curve"]["speeds"]
y = results[0]["capability_curve"]["torques"]
fig = go.Figure(data=go.Scatter(x=x, y=y))
fig.show()