{ "cells": [ { "cell_type": "markdown", "id": "07d94965", "metadata": {}, "source": [ "# Perform a BoM sustainability query\n", "\n", "The following supporting files are required for this example:\n", "\n", "* [bom-2301-assembly.xml](supporting-files/bom-2301-assembly.xml)" ] }, { "cell_type": "markdown", "id": "0bdecd1c", "metadata": {}, "source": [ "## Run a BoM sustainability query\n", "\n", "First, connect to Granta MI." ] }, { "cell_type": "code", "execution_count": null, "id": "30df4ac8", "metadata": {}, "outputs": [], "source": [ "from ansys.grantami.bomanalytics import Connection\n", "\n", "server_url = \"http://my_grantami_server/mi_servicelayer\"\n", "cxn = Connection(server_url).with_credentials(\"user_name\", \"password\").connect()" ] }, { "cell_type": "markdown", "id": "c3ff3413", "metadata": {}, "source": [ "Next, create a sustainability query. The query accepts a single BoM as argument, as well as optional\n", "configuration for units. If a unit is not specified, the default unit is used. Default units for the\n", "analysis are:\n", "`MJ` for energy, `kg` for mass, and `km` for distance." ] }, { "cell_type": "code", "execution_count": null, "id": "2d7cee22", "metadata": {}, "outputs": [], "source": [ "xml_file_path = \"supporting-files/bom-2301-assembly.xml\"\n", "with open(xml_file_path) as f:\n", " bom = f.read()\n", "\n", "from ansys.grantami.bomanalytics import queries\n", "\n", "query = queries.BomSustainabilityQuery().with_bom(bom)" ] }, { "cell_type": "markdown", "id": "8b637ab7", "metadata": {}, "source": [ "Finally, run the query. A `BomSustainabilityQueryResult` object is returned, which contains the\n", "results of the analysis." ] }, { "cell_type": "code", "execution_count": null, "id": "7b1bbc5b", "metadata": { "lines_to_end_of_cell_marker": 0, "lines_to_next_cell": 1 }, "outputs": [], "source": [ "result = cxn.run(query)\n", "result" ] }, { "cell_type": "markdown", "id": "372faac9", "metadata": {}, "source": [ "## The ``BomSustainabilityQueryResult`` class\n", "\n", "### Definition\n", "\n", "The structure of a BoM sustainabability query result mirrors the input BoM structure. However, each\n", "item in the result objects also includes the results of the sustainability analysis for that item.\n", "In addition to the properties described below, these objects also contain at least the following\n", "properties which define the results of the sustainability analysis:\n", "\n", "* ``.embodied_energy``\n", "* ``.climate_change``\n", "\n", "Additional properties are also available for each ``WithSustainabilityResult`` object,\n", "see the\n", "[Sustainability API](https://bomanalytics.grantami.docs.pyansys.com/version/stable/api/sustainability/index.html) for\n", "more details." ] }, { "cell_type": "markdown", "id": "f091b255", "metadata": {}, "source": [ "### The ``BomSustainabilityQueryResult.parts`` property\n", "\n", "The ``BomSustainabilityQueryResult.parts`` property contains the single 'root' part in the input\n", "BoM. This part in turn also has a ``.parts`` property, which contains the list of\n", "``PartWithSustainabilityResult`` objects which are children of the root part. This structure\n", "continues recursively to define all parts in the input BoM. These parts can be of two types:\n", "assemblies, or leaf parts.\n", "\n", "#### **Assemblies**\n", "\n", "Assemblies are ``PartWithSustainabilityResult`` objects that contain sub-parts. Assemblies do not\n", "contain materials directly.\n", "\n", "Assemblies include the following properties which describe child BoM items:\n", "\n", "- ``.parts``: the sub-parts of the assembly, defined as ``PartWithSustainabilityResult`` objects.\n", "- ``.processes``: the joining and finishing processes applied to the assembly, defined as\n", "``ProcessWithSustainabilityResult`` objects.\n", "\n", "The environmental impact of an assembly includes the sum of the environmental impacts of all\n", "sub-parts and processes applied to the assembly.\n", "\n", "#### **Leaf parts**\n", "\n", "Leaf parts are ``PartWithSustainabilityResult`` objects that do not include sub-parts. Leaf parts\n", "can contain the materials they are made of as direct children.\n", "\n", "Leaf parts include the following properties:\n", "\n", "- ``.materials``: the materials that the part is made of, defined as a list\n", "``MaterialWithSustainabilityResult`` objects.\n", "- ``.processes``: the joining and finishing processes applied to the part, defined as a list of\n", "``ProcessWithSustainabilityResult`` objects.\n", "\n", "The environmental impact of a leaf part includes the sum of the environmental impacts\n", "associated with the quantity of materials used in the part (see below for details), processes\n", "applied to the part directly, and processes applied to materials in the part." ] }, { "cell_type": "markdown", "id": "e5d37db4", "metadata": {}, "source": [ "#### **Materials**\n", "\n", "Materials are ``MaterialWithSustainabilityResult`` objects. They include the following properties:\n", "\n", "- ``.processes``: the primary and secondary processes applied to the mass of material, defined as a\n", "list of ``ProcessWithSustainabilityResult`` objects.\n", "\n", "The environmental impact of a material is calculated from database data and the mass of material used.\n", "Even though processes appear as children of materials in the hierarchy, their environmental impact is\n", "not summed up in the parent material's impact, as opposed to the environmental impact of parts." ] }, { "cell_type": "markdown", "id": "f7825553", "metadata": {}, "source": [ "#### **Processes**\n", "\n", "Processes are represented by ``ProcessWithSustainabilityResult`` objects. Processes are child items\n", "in the BoM and have no children themselves. The environmental impact of a process is calculated\n", "from database data and masses defined in the BoM." ] }, { "cell_type": "markdown", "id": "03c6b7dc", "metadata": {}, "source": [ "### The `BomSustainabilityQueryResult.transport` property\n", "\n", "The ``BomSustainabilityQueryResult.transport`` property contains the transport stages in the input\n", "BoM, defined as a list of ``TransportWithSustainabilityResult`` objects. Transport stages contain no\n", "BoM properties. The environmental impact of a transport stage is just the environmental\n", "impact associated with the transport stage itself." ] }, { "cell_type": "markdown", "id": "94585211", "metadata": {}, "source": [ "## Process the ``BomSustainabilityQueryResult`` object\n", "\n", "In order to visualize the results using [plotly](https://plotly.com/python/), the results will be\n", "loaded into a [pandas](https://pandas.pydata.org/) ``DataFrame``.\n", "\n", "The following cell defines functions which convert the BoM hierarchical structure into a flat list\n", "of items. Each function also converts each item into a dictionary of common values that the\n", "``DataFrame`` can interpret.\n", "\n", "Each row in the DataFrame contains an ``id`` which uniquely identifies the item, and a ``parent_id``\n", "which defines the parent item. The ``.identity`` property is used as an identifier as it is unique\n", "across all BoM items, and populated even if not initially populated on the BoM items." ] }, { "cell_type": "code", "execution_count": null, "id": "0486125c", "metadata": {}, "outputs": [], "source": [ "def traverse_bom(query_response):\n", " # Identify top-level assembly, which includes transport stages contributions.\n", " top_level_assembly = query_response.part\n", " top_level_assembly_id = top_level_assembly.identity\n", " yield to_dict(top_level_assembly, \"\")\n", " for part in top_level_assembly.parts:\n", " yield from traverse_part(part, top_level_assembly_id)\n", " for transport in query_response.transport_stages:\n", " yield to_dict(transport, top_level_assembly_id)\n", "\n", "\n", "def traverse_part(part, parent_id):\n", " yield to_dict(part, parent_id)\n", " part_id = part.identity\n", " for child_part in part.parts:\n", " yield from traverse_part(child_part, part_id)\n", " for child_material in part.materials:\n", " yield from traverse_material(child_material, part_id)\n", " for child_process in part.processes:\n", " yield to_dict(child_process, part_id)\n", "\n", "\n", "def traverse_material(material, parent_id):\n", " yield to_dict(material, parent_id)\n", " for child_process in material.processes:\n", " yield to_dict(child_process, parent_id)\n", "\n", "\n", "from ansys.grantami.bomanalytics._item_results import (\n", " PartWithSustainabilityResult,\n", " TransportWithSustainabilityResult,\n", " MaterialWithSustainabilityResult,\n", " ProcessWithSustainabilityResult,\n", ")\n", "\n", "\n", "def to_dict(item, parent):\n", " record = {\n", " \"id\": item.identity,\n", " \"parent_id\": parent,\n", " \"embodied energy [MJ]\": item.embodied_energy.value,\n", " \"climate change [kg CO2-eq]\": item.climate_change.value,\n", " }\n", " if isinstance(item, PartWithSustainabilityResult):\n", " record.update({\"type\": \"Part\", \"name\": item.input_part_number})\n", " elif isinstance(item, TransportWithSustainabilityResult):\n", " record.update({\"type\": \"Transport\", \"name\": item.name})\n", " elif isinstance(item, MaterialWithSustainabilityResult):\n", " record.update({\"type\": \"Material\", \"name\": item.name})\n", " elif isinstance(item, ProcessWithSustainabilityResult):\n", " record.update({\"type\": \"Process\", \"name\": item.name})\n", " return record" ] }, { "cell_type": "markdown", "id": "c99e26b1", "metadata": {}, "source": [ "Now call the ``traverse_bom`` function and print the first two dictionaries, representing the root\n", "part and the first assembly in the BoM." ] }, { "cell_type": "code", "execution_count": null, "id": "01c0ab05", "metadata": {}, "outputs": [], "source": [ "records = list(traverse_bom(result))\n", "records[:2]" ] }, { "cell_type": "markdown", "id": "2e73a855", "metadata": {}, "source": [ "Now, use the list of dictionaries to create a DataFrame. Display the first five rows of the\n", "DataFrame with the ``DataFrame.head()`` method." ] }, { "cell_type": "code", "execution_count": null, "id": "c4d46e2c", "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "df = pd.DataFrame.from_records(records)\n", "df.head()" ] }, { "cell_type": "markdown", "id": "763b4d29", "metadata": {}, "source": [ "Finally, visualize the data in a ``sunburst`` hierarchical chart:\n", "\n", "* The segments are represented hierarchically. The BoM is at the center, and items further down\n", "the hierarchy are further out in the plot.\n", "* Item type is represented by color.\n", "* The size of the segment represents the environmental impact of that item." ] }, { "cell_type": "code", "execution_count": null, "id": "64be0046", "metadata": {}, "outputs": [], "source": [ "import plotly.express as px\n", "\n", "fig = px.sunburst(\n", " df,\n", " names=df[\"name\"],\n", " ids=df[\"id\"],\n", " parents=df[\"parent_id\"],\n", " values=df[\"embodied energy [MJ]\"],\n", " branchvalues=\"total\",\n", " color=df[\"type\"],\n", " title=\"Embodied energy [MJ] breakdown\",\n", " width=800,\n", " height=800,\n", ")\n", "# Disable sorting, so that items appear in the same order as in the BoM.\n", "fig.update_traces(sort=False)\n", "fig.show()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 5 }