{ "cells": [ { "cell_type": "markdown", "id": "de6ad957", "metadata": {}, "source": [ "# Transport phase\n", "\n", "This example shows how to explore the transport phase results of a sustainability summary query.\n", "\n", "The following supporting files are required for this example:\n", "\n", "* [sustainability-bom-2412.xml](../supporting-files/sustainability-bom-2412.xml)\n", "\n", "For help on constructing an XML BoM, see [BoM examples](../6_BoMs/index.rst)." ] }, { "cell_type": "markdown", "id": "064f09cd", "metadata": {}, "source": [ "
\n", "\n", "**Info:**\n", "\n", "This example requires Granta MI Restricted Substances and Sustainability Reports 2025 R2 or later.\n", "\n", "If you would like to run an example of exploring the transport phase results of a summary query for an earlier version\n", "of the reports bundle, refer to the version of the documentation that corresponds to that version of the reports\n", "bundle.\n", "
" ] }, { "cell_type": "markdown", "id": "2253bdbc", "metadata": {}, "source": [ "## Run a sustainability summary query" ] }, { "cell_type": "code", "execution_count": null, "id": "f9681d38", "metadata": {}, "outputs": [], "source": [ "from ansys.grantami.bomanalytics import Connection, queries\n", "\n", "MASS_UNIT = \"kg\"\n", "ENERGY_UNIT = \"MJ\"\n", "DISTANCE_UNIT = \"km\"\n", "\n", "server_url = \"http://my_grantami_server/mi_servicelayer\"\n", "cxn = Connection(server_url).with_credentials(\"user_name\", \"password\").connect()\n", "\n", "xml_file_path = \"../supporting-files/sustainability-bom-2412.xml\"\n", "with open(xml_file_path) as f:\n", " bom = f.read()\n", "\n", "sustainability_summary_query = (\n", " queries.BomSustainabilitySummaryQuery()\n", " .with_bom(bom)\n", " .with_units(mass=MASS_UNIT, energy=ENERGY_UNIT, distance=DISTANCE_UNIT)\n", ")\n", "sustainability_summary = cxn.run(sustainability_summary_query)" ] }, { "cell_type": "markdown", "id": "b0d1d355", "metadata": {}, "source": [ "## Transport phase\n", "\n", "The environmental contribution from the transport phase is summarized in the ``transport_details`` property. Results\n", "include the individual environmental impact for each transport stage included in the input BoM.\n", "\n", "A BoM may include many transport stages, each describing transportation throughout the product lifecycle. Print the\n", "first three only." ] }, { "cell_type": "code", "execution_count": null, "id": "8423ad47", "metadata": {}, "outputs": [], "source": [ "sustainability_summary.transport_details[:3]" ] }, { "cell_type": "markdown", "id": "e357975f", "metadata": {}, "source": [ "Convert all to a DataFrame. To see the distribution of results, use the `DataFrame.describe()` method." ] }, { "cell_type": "code", "execution_count": null, "id": "fc57bf0b", "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "\n", "EE_HEADER = f\"EE [{ENERGY_UNIT}]\"\n", "CC_HEADER = f\"CC [{MASS_UNIT}]\"\n", "DISTANCE_HEADER = f\"Distance [{DISTANCE_UNIT}]\"\n", "\n", "transport_df_full = pd.DataFrame.from_records(\n", " [\n", " {\n", " \"Name\": item.name,\n", " DISTANCE_HEADER: item.distance.value,\n", " \"EE%\": item.embodied_energy_percentage,\n", " EE_HEADER: item.embodied_energy.value,\n", " \"CC%\": item.climate_change_percentage,\n", " CC_HEADER: item.climate_change.value,\n", " }\n", " for item in sustainability_summary.transport_details\n", " ]\n", ")\n", "transport_df_full.describe()" ] }, { "cell_type": "markdown", "id": "1e83d660", "metadata": {}, "source": [ "Most of these transport stages contribute little to the overall sustainability impact. To make a visualization more\n", "insightful, group all transport stages that contribute less than 5% of embodied energy or climate change in a single\n", "'Other' transport stage." ] }, { "cell_type": "code", "execution_count": null, "id": "a973fab7", "metadata": {}, "outputs": [], "source": [ "# Define the criterion\n", "criterion = (transport_df_full[\"EE%\"] < 5.0) | (transport_df_full[\"CC%\"] < 5.0)\n", "\n", "# Aggregate all rows that meet the criterion\n", "transport_df_below_5_pct = transport_df_full.loc[criterion].sum(numeric_only=True).to_frame().T\n", "transport_df_below_5_pct[\"Name\"] = \"Other\"\n", "\n", "# Sort all rows that do not meet the criterion by embodied energy\n", "transport_df_over_5_pct = transport_df_full.loc[~(criterion)].sort_values(by=\"EE%\", ascending=False)\n", "\n", "# Concatenate the rows together\n", "transport_df = pd.concat([transport_df_over_5_pct, transport_df_below_5_pct], ignore_index=True)\n", "transport_df" ] }, { "cell_type": "markdown", "id": "dd7fd5fa", "metadata": {}, "source": [ "This example produces multiple plots which all consist of a pair of pie charts representing the\n", "\"Embodied Energy\" and \"Climate Change CO2 equivalent\" impacts respectively. Define a\n", "helper function to create these plots." ] }, { "cell_type": "code", "execution_count": null, "id": "81e1a870", "metadata": { "lines_to_end_of_cell_marker": 0, "lines_to_next_cell": 1 }, "outputs": [], "source": [ "import plotly.graph_objects as go\n", "from plotly.subplots import make_subplots\n", "\n", "\n", "def plot_impact(df, title, textinfo=\"percent+label\", hoverinfo=\"value+name\", labels=True):\n", " fig = make_subplots(\n", " rows=1,\n", " cols=2,\n", " specs=[[{\"type\": \"domain\"}, {\"type\": \"domain\"}]],\n", " subplot_titles=[\"Embodied Energy\", \"Climate Change\"],\n", " )\n", " fig.add_trace(go.Pie(labels=df[\"Name\"], values=df[EE_HEADER], name=ENERGY_UNIT), 1, 1)\n", " fig.add_trace(go.Pie(labels=df[\"Name\"], values=df[CC_HEADER], name=MASS_UNIT), 1, 2)\n", " fig.update_layout(title_text=title, legend=dict(orientation=\"h\"))\n", " if labels:\n", " fig.update_traces(textposition=\"inside\", textinfo=textinfo, hoverinfo=hoverinfo)\n", " fig.show()" ] }, { "cell_type": "markdown", "id": "2ea120c3", "metadata": {}, "source": [ "Use this function to plot the environment impact for all transport stages." ] }, { "cell_type": "code", "execution_count": null, "id": "1c492a05", "metadata": {}, "outputs": [], "source": [ "plot_impact(transport_df, \"Transport stages - environmental impact\", labels=False)" ] }, { "cell_type": "markdown", "id": "d0cdacc8", "metadata": {}, "source": [ "### Transport impact per unit distance" ] }, { "cell_type": "markdown", "id": "eaf3f7ea", "metadata": {}, "source": [ "In some situations, it might be useful to calculate the environmental impact per distance travelled and add the\n", "results as new columns in the dataframe." ] }, { "cell_type": "code", "execution_count": null, "id": "cc9ab1d0", "metadata": {}, "outputs": [], "source": [ "EE_PER_DISTANCE = f\"EE [{ENERGY_UNIT}/{DISTANCE_UNIT}]\"\n", "CC_PER_DISTANCE = f\"CC [{MASS_UNIT}/{DISTANCE_UNIT}]\"\n", "transport_df[EE_PER_DISTANCE] = transport_df.apply(lambda row: row[EE_HEADER] / row[DISTANCE_HEADER], axis=1)\n", "transport_df[CC_PER_DISTANCE] = transport_df.apply(lambda row: row[CC_HEADER] / row[DISTANCE_HEADER], axis=1)\n", "transport_df" ] }, { "cell_type": "code", "execution_count": null, "id": "5319bd7d", "metadata": {}, "outputs": [], "source": [ "fig = make_subplots(\n", " rows=1, cols=2, specs=[[{\"type\": \"domain\"}, {\"type\": \"domain\"}]], subplot_titles=[EE_PER_DISTANCE, CC_PER_DISTANCE]\n", ")\n", "fig.add_trace(\n", " go.Pie(labels=transport_df[\"Name\"], values=transport_df[EE_PER_DISTANCE], name=f\"{ENERGY_UNIT}/{DISTANCE_UNIT}\"),\n", " 1,\n", " 1,\n", ")\n", "fig.add_trace(\n", " go.Pie(labels=transport_df[\"Name\"], values=transport_df[CC_PER_DISTANCE], name=f\"{MASS_UNIT}/{DISTANCE_UNIT}\"), 1, 2\n", ")\n", "fig.update_layout(\n", " title_text=\"Transport stages impact - Relative to distance travelled\",\n", " legend=dict(orientation=\"h\")\n", ")\n", "fig.show()" ] }, { "cell_type": "markdown", "id": "2c789efe", "metadata": {}, "source": [ "### Transport impact aggregated by category" ] }, { "cell_type": "markdown", "id": "3ca80145", "metadata": {}, "source": [ "The environmental impacts from transportation associated with distribution and manufacturing phases are summarized in\n", "the ``distribution_transport_summary`` and ``manufacturing_transport_summary`` properties." ] }, { "cell_type": "code", "execution_count": null, "id": "ad86d7d6", "metadata": {}, "outputs": [], "source": [ "sustainability_summary.distribution_transport_summary" ] }, { "cell_type": "code", "execution_count": null, "id": "9b3eeeab", "metadata": {}, "outputs": [], "source": [ "dist_summary = sustainability_summary.distribution_transport_summary\n", "distribution = {\n", " \"Name\": \"Distribution\",\n", " DISTANCE_HEADER: dist_summary.distance.value,\n", " \"EE%\": dist_summary.embodied_energy_percentage,\n", " EE_HEADER: dist_summary.embodied_energy.value,\n", " \"CC%\": dist_summary.climate_change_percentage,\n", " CC_HEADER: dist_summary.climate_change.value,\n", "}\n", "\n", "manuf_summary = sustainability_summary.manufacturing_transport_summary\n", "manufacturing = {\n", " \"Name\": \"Manufacturing\",\n", " DISTANCE_HEADER: manuf_summary.distance.value,\n", " \"EE%\": manuf_summary.embodied_energy_percentage,\n", " EE_HEADER: manuf_summary.embodied_energy.value,\n", " \"CC%\": manuf_summary.climate_change_percentage,\n", " CC_HEADER: manuf_summary.climate_change.value,\n", "}\n", "\n", "transport_by_category_df = pd.DataFrame.from_records([distribution, manufacturing])\n", "transport_by_category_df" ] }, { "cell_type": "code", "execution_count": null, "id": "7d9ba158", "metadata": {}, "outputs": [], "source": [ "plot_impact(transport_by_category_df, \"Transport impact - grouped by category\")" ] }, { "cell_type": "markdown", "id": "b0d4961d", "metadata": {}, "source": [ "### Transport impact aggregated by part" ] }, { "cell_type": "markdown", "id": "99c2f877", "metadata": {}, "source": [ "The environmental contributions from transportation are summarized by the associated part in the\n", "``transport_details_aggregated_by_part`` property. This property groups parts that contribute less than 5% embodied\n", "energy or climate change automatically." ] }, { "cell_type": "code", "execution_count": null, "id": "d68f1507", "metadata": {}, "outputs": [], "source": [ "sustainability_summary.transport_details_aggregated_by_part" ] }, { "cell_type": "code", "execution_count": null, "id": "73f7f165", "metadata": {}, "outputs": [], "source": [ "transport_by_part_df = pd.DataFrame.from_records(\n", " [\n", " {\n", " \"Name\": item.part_name,\n", " \"Parent part name\": item.parent_part_name,\n", " DISTANCE_HEADER: item.distance.value,\n", " \"EE%\": item.embodied_energy_percentage,\n", " EE_HEADER: item.embodied_energy.value,\n", " \"CC%\": item.climate_change_percentage,\n", " CC_HEADER: item.climate_change.value,\n", " \"Transport types\": \"; \".join(item.transport_types),\n", " }\n", " for item in sustainability_summary.transport_details_aggregated_by_part\n", " ]\n", ")\n", "transport_by_part_df" ] }, { "cell_type": "code", "execution_count": null, "id": "0390ae4a", "metadata": {}, "outputs": [], "source": [ "plot_impact(transport_by_part_df, \"Transport impact - grouped by part\")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 5 }