Bulk Job Submit.#

Example Script to bulk submit jobs to the ConceptEV API. This example copies the template into a project and runs a number of jobs with different combinations of components. The combinations of components are specified in a CSV file format.

Perform Required imports#

import datetime

import pandas as pd

from ansys.conceptev.core import app
from ansys.conceptev.core.exceptions import ResponseError

Set up inputs.#

Change the following variables to match your data. The current filename for combinations can be used as an example. The current base_concept_id is a template concept that will be copied. Component Order is dictionary that maps the column names in the combinations file to the component names in the API.

filename = "resources/combinations.csv"  # See example file for format.
base_concept_id = "2465235f-ad2e-4923-9125-e2e69ccf5816"  # Truck template.
component_order = {
    "front_transmission_id": "Front Transmission",
    "front_motor_id": "Front Motor",
    "front_inverter_id": "Front Inverter",
    "rear_transmission_id": "Rear Transmission",
    "rear_motor_id": "Rear Motor",
    "rear_inverter_id": "Rear Inverter",
    "battery_id": "Battery",
    "front_clutch_id": "Front Clutch",
    "rear_clutch_id": "Rear Clutch",
}


def update_architecture(components, combo, base_architecture):
    # Update Architecture to match the new combinations.
    arch = {key: components[combo[value]] for key, value in component_order.items()}
    arch["number_of_front_wheels"] = base_architecture["number_of_front_wheels"]
    arch["number_of_front_motors"] = base_architecture["number_of_front_motors"]
    arch["number_of_rear_wheels"] = base_architecture["number_of_rear_wheels"]
    arch["number_of_rear_motors"] = base_architecture["number_of_rear_motors"]
    arch["wheelbase"] = base_architecture["wheelbase"]
    return arch

Create a client and create a new project from template.#

Authenticate and get a token Create an API client. Get the account ID and HPC ID. Copy the template into a new project. Add a clutch to the concept. Get the component IDs for the new concept. Get the architecture for the new concept.

msal_app = app.auth.create_msal_app()
token = app.auth.get_ansyId_token(msal_app)
# Use API client for the Ansys ConceptEV service
with app.get_http_client(token) as client:
    client.timeout = 200  # Extend timeout for uploading files.
    accounts = app.get_account_ids(token)
    account_id = accounts["conceptev_saas@ansys.com"]
    hpc_id = app.get_default_hpc(token, account_id)

    project = app.create_new_project(
        client, account_id, hpc_id, f"New Project {datetime.datetime.now()}"
    )
    project_id = project["projectId"]
    design_instance_id = app.create_design_instance(
        project_id, f"New Concept {datetime.datetime.now()}", token
    )
    app.copy_concept(base_concept_id, design_instance_id, client)
    base_concept_id = design_instance_id
    app.post(
        client,
        "/components",
        data={
            "item_type": "component",
            "name": "Disconnect Clutch",
            "mass": 0,
            "moment_of_inertia": 0,
            "cost": 0,
            "component_type": "ClutchInput",
            "efficiency": "95",
            "switch_energy": "10",
            "engaged_power": 0,
        },
        params={"design_instance_id": base_concept_id},
    )
    base_components = app.get_component_id_map(client, base_concept_id)

    base_concept = app.get(client, f"/concepts/{base_concept_id}")
    base_architecture = app.get(
        client,
        f"/architectures/{base_concept['architecture_id']}",
        params={"design_instance_id": base_concept_id},
    )
Encryption unavailable. Opting in to plain text.
Traceback (most recent call last):
  File "/home/runner/work/pyconceptev/pyconceptev/.venv/lib/python3.13/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 51, in build_persistence
    return build_encrypted_persistence(location)
  File "/home/runner/work/pyconceptev/pyconceptev/.venv/lib/python3.13/site-packages/msal_extensions/persistence.py", line 98, in build_encrypted_persistence
    return LibsecretPersistence(location)
  File "/home/runner/work/pyconceptev/pyconceptev/.venv/lib/python3.13/site-packages/msal_extensions/persistence.py", line 314, in __init__
    from .libsecret import (  # This uncertain import is deferred till runtime
        LibSecretAgent, trial_run)
  File "/home/runner/work/pyconceptev/pyconceptev/.venv/lib/python3.13/site-packages/msal_extensions/libsecret.py", line 20, in <module>
        raise ImportError("""Unable to import module 'gi'
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<5 lines>...
    """)  # Message via exception rather than log
    ^^^^
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

Read combinations from a csv file and check they match the combinations file.#

Read combinations from a csv file. Get the component types from the component_order dictionary. Turn them into set. Check that the component types are in the combinations file. Get the component names from the combinations. Check the component names are in the base components.

combinations = pd.read_csv(filename, na_filter=False)
combinations = combinations.to_dict("records")

# Check the component types are in the header of the combinations file.
component_types = set(component_order.values())
component_types_from_combo_header = set(combinations[0].keys())
assert component_types <= component_types_from_combo_header, component_types.difference(
    component_types_from_combo_header
)
# Check the component names in the combinations file are in the base components.
component_names_from_combo = set([value for combo in combinations for value in combo.values()])
component_names_from_base = set(base_components.keys())
assert (
    component_names_from_combo <= component_names_from_base
), component_names_from_combo.difference(component_names_from_base)

Submit jobs for each combination.#

Create a new design instance with title. Create an output list to store the created designs. Copy the base Concept into that new design instance. Get the component IDs for the new design instance as they change when copied. Change the base concept to use the new components. Update the architecture on the server. Update the local concept instance with the new architecture id. Create and submit a job using the new concept (with the new architecture).

with app.get_http_client(token) as client:
    created_designs = []
    # Submit jobs for each combination
    for combo in combinations:
        try:
            # Create a new design instance with title.
            title = f"F_{combo['Front Motor']}_R_{combo['Rear Motor']} {datetime.datetime.now()}"
            design_instance_id = app.create_design_instance(project_id, title=title, token=token)

            # Copy base Concept into that new design instance.
            concept = app.copy_concept(base_concept_id, design_instance_id, client)
            print(f"ID of the cloned concept: {concept['id']}")
            # Save that in output list.
            created_designs.append(
                {
                    "Project Name": title,
                    "Design Instance Id": design_instance_id,
                    "Concept_ID": concept["id"],
                },
            )
            # Get the component IDs for the new design instance as they change when copied.
            params = {"design_instance_id": design_instance_id}
            components = app.get_component_id_map(client, design_instance_id)
            # Change the base concept to use the new components.
            updated_architecture = update_architecture(components, combo, base_architecture)
            # Update the architecture on the server.
            created_arch = app.post(client, "/architectures", data=updated_architecture)
            print(f"Created architecture: {created_arch}\n")

            # Update the local concept instance with the new architecture id.
            concept["architecture_id"] = created_arch["id"]

            # Create and submit a job using the new concept (with the new architecture)
            job_info = app.create_submit_job(
                client,
                concept,
                account_id,
                hpc_id,
                job_name=f"cli_job: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')}",
            )
            print(f"Submitted job for combination {combo}: {job_info}")
        except ResponseError as err:
            print(f"Failed to submit job for combination {combo}: {err}")
            continue  # If one job fails to submit keep trying the other jobs.
ID of the cloned concept: 67e13d24af05df2bce8ecb68
Created architecture: {'id': '67e13d24af05df2bce8ecb86', 'wheelbase': None, 'components_cost': 15820.0, 'components_mass': 561.0, 'max_wheel_speed': 1071.4285714285716, 'number_of_front_wheels': 2, 'number_of_front_motors': 1, 'front_clutch_id': '67e13d24af05df2bce8ecb75', 'front_transmission_id': '67e13d24af05df2bce8ecb69', 'front_motor_id': '67e13d24af05df2bce8ecb6e', 'front_inverter_id': '67e13d24af05df2bce8ecb72', 'number_of_rear_wheels': 2, 'number_of_rear_motors': 1, 'rear_clutch_id': None, 'rear_transmission_id': '67e13d24af05df2bce8ecb6a', 'rear_motor_id': '67e13d24af05df2bce8ecb6c', 'rear_inverter_id': '67e13d24af05df2bce8ecb71', 'battery_id': '67e13d24af05df2bce8ecb74'}

Submitted job for combination {'Front Transmission': 'Single Speed 10:1', 'Front Motor': 'IM_300kW_20kRPM_800V_50Bars36Slots_500A', 'Front Inverter': 'MOSFET Inverter Model', 'Rear Transmission': 'Single Speed 14:1', 'Rear Motor': 'IPM_345kW_15kRPM_800V_8Poles48Slots_460A_LABupTo15krpm', 'Rear Inverter': 'IGBT Inverter Model', 'Battery': 'CellA_800Vdc_100kWh', 'Front Clutch': 'Disconnect Clutch', 'Rear Clutch': 'N/A'}: {'job_id': '93ce93fd-1911-472c-8e07-98a99bc1f682', 'job_name': 'cli_job: 2025-03-24 11:08:20.859469', 'docker_tag': 'default', 'simulation_id': '864ecaab-03d8-4317-a355-57c5ffc2093b'}
ID of the cloned concept: 67e13d28af05df2bce8ecb88
Created architecture: {'id': '67e13d29af05df2bce8ecba6', 'wheelbase': None, 'components_cost': 15820.0, 'components_mass': 563.0, 'max_wheel_speed': 1071.4285714285716, 'number_of_front_wheels': 2, 'number_of_front_motors': 1, 'front_clutch_id': '67e13d28af05df2bce8ecb95', 'front_transmission_id': '67e13d28af05df2bce8ecb89', 'front_motor_id': '67e13d28af05df2bce8ecb90', 'front_inverter_id': '67e13d28af05df2bce8ecb92', 'number_of_rear_wheels': 2, 'number_of_rear_motors': 1, 'rear_clutch_id': None, 'rear_transmission_id': '67e13d28af05df2bce8ecb8a', 'rear_motor_id': '67e13d28af05df2bce8ecb8c', 'rear_inverter_id': '67e13d28af05df2bce8ecb91', 'battery_id': '67e13d28af05df2bce8ecb94'}

Submitted job for combination {'Front Transmission': 'Single Speed 10:1', 'Front Motor': 'WRSM_330kW_15kRPM_800V_8Poles48Slots_500A_LABupTo15krpm', 'Front Inverter': 'MOSFET Inverter Model', 'Rear Transmission': 'Single Speed 14:1', 'Rear Motor': 'IPM_345kW_15kRPM_800V_8Poles48Slots_460A_LABupTo15krpm', 'Rear Inverter': 'IGBT Inverter Model', 'Battery': 'CellA_800Vdc_100kWh', 'Front Clutch': 'Disconnect Clutch', 'Rear Clutch': 'N/A'}: {'job_id': 'f30822ae-a8bb-41ba-9087-e5809493fa1b', 'job_name': 'cli_job: 2025-03-24 11:08:25.927377', 'docker_tag': 'default', 'simulation_id': '9007a4c7-44f5-49bf-91a8-85fb6e9c7265'}

Save the list of created designs to a file.#

Create a pandas dataframe. Export to Excel.

all_results = pd.DataFrame(created_designs)
all_results.to_excel("created_designs.xlsx")

Delete the extra project on the server.#

Delete the project on the server.

Warning

This will delete the project and all its contents. Only needed for keep test environment clean.

with app.get_http_client(token) as client:
    for concept in created_designs:
        client.params = client.params.set("design_instance_id", concept["Design Instance Id"])
        app.delete(client, "concepts", id=concept["Concept_ID"])
    app.delete_project(project_id, token)
    print(f"Deleted project {project_id}")
Deleted project 340555bc-545a-4e53-880a-ed4dac6e3e31

Total running time of the script: (0 minutes 17.062 seconds)

Gallery generated by Sphinx-Gallery