Download this example as a Jupyter notebook or a
Python script.
Transport phase#
This example shows how to explore the transport phase results of a sustainability summary query.
The following supporting files are required for this example:
For help on constructing an XML BoM, see BoM examples.
Info:
This example requires Granta MI Restricted Substances and Sustainability Reports 2025 R2 or later.
If you would like to run an example of exploring the transport phase results of a summary query for an earlier version of the reports bundle, refer to the version of the documentation that corresponds to that version of the reports bundle.
Run a sustainability summary query#
[1]:
from ansys.grantami.bomanalytics import Connection, queries
MASS_UNIT = "kg"
ENERGY_UNIT = "MJ"
DISTANCE_UNIT = "km"
server_url = "http://my_grantami_server/mi_servicelayer"
cxn = Connection(server_url).with_credentials("user_name", "password").connect()
xml_file_path = "../supporting-files/sustainability-bom-2412.xml"
with open(xml_file_path) as f:
bom = f.read()
sustainability_summary_query = (
queries.BomSustainabilitySummaryQuery()
.with_bom(bom)
.with_units(mass=MASS_UNIT, energy=ENERGY_UNIT, distance=DISTANCE_UNIT)
)
sustainability_summary = cxn.run(sustainability_summary_query)
Transport phase#
The environmental contribution from the transport phase is summarized in the transport_details property. Results include the individual environmental impact for each transport stage included in the input BoM.
A BoM may include many transport stages, each describing transportation throughout the product lifecycle. Print the first three only.
[2]:
sustainability_summary.transport_details[:3]
[2]:
[<TransportSummaryResult('Component 11A raw material', EE%=3.7006354945461486, CC%=3.5405664742758214)>,
<TransportSummaryResult('Component 11A as-cast to machining shop', EE%=0.5550953241819223, CC%=0.5310849711413731)>,
<TransportSummaryResult('Finished component 11A to warehouse', EE%=1.6938432542209843, CC%=1.6205769650676105)>]
Convert all to a DataFrame. To see the distribution of results, use the DataFrame.describe() method.
[3]:
import pandas as pd
EE_HEADER = f"EE [{ENERGY_UNIT}]"
CC_HEADER = f"CC [{MASS_UNIT}]"
DISTANCE_HEADER = f"Distance [{DISTANCE_UNIT}]"
transport_df_full = pd.DataFrame.from_records(
[
{
"Name": item.name,
DISTANCE_HEADER: item.distance.value,
"EE%": item.embodied_energy_percentage,
EE_HEADER: item.embodied_energy.value,
"CC%": item.climate_change_percentage,
CC_HEADER: item.climate_change.value,
}
for item in sustainability_summary.transport_details
]
)
transport_df_full.describe()
[3]:
| Distance [km] | EE% | EE [MJ] | CC% | CC [kg] | |
|---|---|---|---|---|---|
| count | 23.000000 | 23.000000 | 23.000000 | 23.000000 | 23.000000 |
| mean | 792.391304 | 4.347826 | 4.343345 | 4.347826 | 0.302918 |
| std | 1616.453659 | 10.551622 | 10.540748 | 10.719638 | 0.746851 |
| min | 50.000000 | 0.013185 | 0.013171 | 0.012614 | 0.000879 |
| 25% | 150.000000 | 0.362618 | 0.362244 | 0.346933 | 0.024171 |
| 50% | 400.000000 | 1.372837 | 1.371422 | 1.313456 | 0.091510 |
| 75% | 700.000000 | 2.505972 | 2.503390 | 2.397578 | 0.167042 |
| max | 8000.000000 | 47.529731 | 47.480747 | 48.247186 | 3.361442 |
Most of these transport stages contribute little to the overall sustainability impact. To make a visualization more insightful, group all transport stages that contribute less than 5% of embodied energy or climate change in a single ‘Other’ transport stage.
[4]:
# Define the criterion
criterion = (transport_df_full["EE%"] < 5.0) | (transport_df_full["CC%"] < 5.0)
# Aggregate all rows that meet the criterion
transport_df_below_5_pct = transport_df_full.loc[criterion].sum(numeric_only=True).to_frame().T
transport_df_below_5_pct["Name"] = "Other"
# Sort all rows that do not meet the criterion by embodied energy
transport_df_over_5_pct = transport_df_full.loc[~(criterion)].sort_values(by="EE%", ascending=False)
# Concatenate the rows together
transport_df = pd.concat([transport_df_over_5_pct, transport_df_below_5_pct], ignore_index=True)
transport_df
[4]:
| Name | Distance [km] | EE% | EE [MJ] | CC% | CC [kg] | |
|---|---|---|---|---|---|---|
| 0 | Finished component 11B to warehouse | 8000.0 | 47.529731 | 47.480747 | 48.247186 | 3.361442 |
| 1 | Product from warehouse to distributor (air) | 500.0 | 23.278252 | 23.254261 | 23.629634 | 1.646306 |
| 2 | Product from warehouse to distributor (truck 1) | 350.0 | 5.273438 | 5.268003 | 5.045338 | 0.351515 |
| 3 | Other | 9375.0 | 23.918579 | 23.893929 | 23.077842 | 1.607862 |
This example produces multiple plots which all consist of a pair of pie charts representing the “Embodied Energy” and “Climate Change CO2 equivalent” impacts respectively. Define a helper function to create these plots.
[5]:
import plotly.graph_objects as go
from plotly.subplots import make_subplots
def plot_impact(df, title, textinfo="percent+label", hoverinfo="value+name", labels=True):
fig = make_subplots(
rows=1,
cols=2,
specs=[[{"type": "domain"}, {"type": "domain"}]],
subplot_titles=["Embodied Energy", "Climate Change"],
)
fig.add_trace(go.Pie(labels=df["Name"], values=df[EE_HEADER], name=ENERGY_UNIT), 1, 1)
fig.add_trace(go.Pie(labels=df["Name"], values=df[CC_HEADER], name=MASS_UNIT), 1, 2)
fig.update_layout(title_text=title, legend=dict(orientation="h"))
if labels:
fig.update_traces(textposition="inside", textinfo=textinfo, hoverinfo=hoverinfo)
fig.show()
Use this function to plot the environment impact for all transport stages.
[6]:
plot_impact(transport_df, "Transport stages - environmental impact", labels=False)
Transport impact per unit distance#
In some situations, it might be useful to calculate the environmental impact per distance travelled and add the results as new columns in the dataframe.
[7]:
EE_PER_DISTANCE = f"EE [{ENERGY_UNIT}/{DISTANCE_UNIT}]"
CC_PER_DISTANCE = f"CC [{MASS_UNIT}/{DISTANCE_UNIT}]"
transport_df[EE_PER_DISTANCE] = transport_df.apply(lambda row: row[EE_HEADER] / row[DISTANCE_HEADER], axis=1)
transport_df[CC_PER_DISTANCE] = transport_df.apply(lambda row: row[CC_HEADER] / row[DISTANCE_HEADER], axis=1)
transport_df
[7]:
| Name | Distance [km] | EE% | EE [MJ] | CC% | CC [kg] | EE [MJ/km] | CC [kg/km] | |
|---|---|---|---|---|---|---|---|---|
| 0 | Finished component 11B to warehouse | 8000.0 | 47.529731 | 47.480747 | 48.247186 | 3.361442 | 0.005935 | 0.000420 |
| 1 | Product from warehouse to distributor (air) | 500.0 | 23.278252 | 23.254261 | 23.629634 | 1.646306 | 0.046509 | 0.003293 |
| 2 | Product from warehouse to distributor (truck 1) | 350.0 | 5.273438 | 5.268003 | 5.045338 | 0.351515 | 0.015051 | 0.001004 |
| 3 | Other | 9375.0 | 23.918579 | 23.893929 | 23.077842 | 1.607862 | 0.002549 | 0.000172 |
[8]:
fig = make_subplots(
rows=1, cols=2, specs=[[{"type": "domain"}, {"type": "domain"}]], subplot_titles=[EE_PER_DISTANCE, CC_PER_DISTANCE]
)
fig.add_trace(
go.Pie(labels=transport_df["Name"], values=transport_df[EE_PER_DISTANCE], name=f"{ENERGY_UNIT}/{DISTANCE_UNIT}"),
1,
1,
)
fig.add_trace(
go.Pie(labels=transport_df["Name"], values=transport_df[CC_PER_DISTANCE], name=f"{MASS_UNIT}/{DISTANCE_UNIT}"), 1, 2
)
fig.update_layout(
title_text="Transport stages impact - Relative to distance travelled",
legend=dict(orientation="h")
)
fig.show()
Transport impact aggregated by category#
The environmental impacts from transportation associated with distribution and manufacturing phases are summarized in the distribution_transport_summary and manufacturing_transport_summary properties.
[9]:
sustainability_summary.distribution_transport_summary
[9]:
<TransportSummaryByCategoryResult(EE%=30.435059969423882, CC%=30.476878034635334)>
[10]:
dist_summary = sustainability_summary.distribution_transport_summary
distribution = {
"Name": "Distribution",
DISTANCE_HEADER: dist_summary.distance.value,
"EE%": dist_summary.embodied_energy_percentage,
EE_HEADER: dist_summary.embodied_energy.value,
"CC%": dist_summary.climate_change_percentage,
CC_HEADER: dist_summary.climate_change.value,
}
manuf_summary = sustainability_summary.manufacturing_transport_summary
manufacturing = {
"Name": "Manufacturing",
DISTANCE_HEADER: manuf_summary.distance.value,
"EE%": manuf_summary.embodied_energy_percentage,
EE_HEADER: manuf_summary.embodied_energy.value,
"CC%": manuf_summary.climate_change_percentage,
CC_HEADER: manuf_summary.climate_change.value,
}
transport_by_category_df = pd.DataFrame.from_records([distribution, manufacturing])
transport_by_category_df
[10]:
| Name | Distance [km] | EE% | EE [MJ] | CC% | CC [kg] | |
|---|---|---|---|---|---|---|
| 0 | Distribution | 975.0 | 30.43506 | 30.403694 | 30.476878 | 2.123362 |
| 1 | Manufacturing | 17250.0 | 69.56494 | 69.493247 | 69.523122 | 4.843763 |
[11]:
plot_impact(transport_by_category_df, "Transport impact - grouped by category")
Transport impact aggregated by part#
The environmental contributions from transportation are summarized by the associated part in the transport_details_aggregated_by_part property. This property groups parts that contribute less than 5% embodied energy or climate change automatically.
[12]:
sustainability_summary.transport_details_aggregated_by_part
[12]:
[<TransportSummaryByPartResult('Component 11B', EE%=49.00062821794582, CC%=49.65446009256159)>,
<TransportSummaryByPartResult('Assembly', EE%=30.435059969423882, CC%=30.476878034635334)>,
<TransportSummaryByPartResult('Component 1C', EE%=6.475059185865298, CC%=6.194983944301699)>,
<TransportSummaryByPartResult('Component 11A', EE%=5.949574072949056, CC%=5.692228410484804)>,
<TransportSummaryByPartResult('Other', EE%=8.139678553815914, CC%=7.981449518016565)>]
[13]:
transport_by_part_df = pd.DataFrame.from_records(
[
{
"Name": item.part_name,
"Parent part name": item.parent_part_name,
DISTANCE_HEADER: item.distance.value,
"EE%": item.embodied_energy_percentage,
EE_HEADER: item.embodied_energy.value,
"CC%": item.climate_change_percentage,
CC_HEADER: item.climate_change.value,
"Transport types": "; ".join(item.transport_types),
}
for item in sustainability_summary.transport_details_aggregated_by_part
]
)
transport_by_part_df
[13]:
| Name | Parent part name | Distance [km] | EE% | EE [MJ] | CC% | CC [kg] | Transport types | |
|---|---|---|---|---|---|---|---|---|
| 0 | Component 11B | Subassembly | 8750.0 | 49.000628 | 48.950128 | 49.654460 | 3.459488 | Aircraft, long haul dedicated-freight; Truck 7... |
| 1 | Assembly | None | 975.0 | 30.435060 | 30.403694 | 30.476878 | 2.123362 | Aircraft, long haul dedicated-freight; Truck 7... |
| 2 | Component 1C | Assembly | 1700.0 | 6.475059 | 6.468386 | 6.194984 | 0.431612 | Truck 7.5-16t, EURO 3 |
| 3 | Component 11A | Subassembly | 1650.0 | 5.949574 | 5.943442 | 5.692228 | 0.396585 | Truck 7.5-16t, EURO 3 |
| 4 | Other | None | 5150.0 | 8.139679 | 8.131290 | 7.981450 | 0.556078 | Train, diesel; Truck 7.5-16t, EURO 3 |
[14]:
plot_impact(transport_by_part_df, "Transport impact - grouped by part")