User Guide
==========
This guide walks you through practical usage patterns of the `edges` library,
covering common workflows such as simple LCIA, regionalized impact assessment,
parameterized methods, uncertainty analysis, and scenario-based modeling.
---
Simple LCIA
-----------
For non-regionalized methods with fixed CFs:
.. code-block:: python
import bw2data
from edges import EdgeLCIA
bw2data.projects.set_current("some project")
act = bw2data.Database("some db").random()
# here, the user provides his/her own LCIA method file
lcia = EdgeLCIA(
demand={act: 1},
filepath="lcia_example_1.json"
)
# solves the system and generates the inventory matrix
lcia.lci()
# map exchanges that should receive a CF
lcia.map_exchanges()
# evaluate the CF values
lcia.evaluate_cfs()
# populate the characterized_inventory matrix and a score
lcia.lcia()
print(lcia.score)
# optional but RECOMMENDED, generate a dataframe with all characterized exchanges
# this allows you to check whether exchanges have been given the correct CFs
# include_unmatched=True allows you to see which exchanges were not matched (and if some should have been)
df = lcia.generate_cf_table()
print(df.head())
---
Using Built-in Method Files
---------------------------
You can list available method files with:
.. code-block:: python
from edges import get_available_methods
print(get_available_methods())
Use the name in the `method=` argument when instantiating `EdgeLCIA`.
.. note::
The matcher backend is `CLIPSpy `_
(Python wrapper for `CLIPS `_). Use the default
``matcher_backend="clips"``.
---
Regionalized LCIA
-----------------
When using region-specific methods like AWARE or ImpactWorld+:
.. code-block:: python
import bw2data
from edges import EdgeLCIA
bw2data.projects.set_current("some project")
act = bw2data.Database("some db").random()
# here, we use a method already included in `edges`
lcia = EdgeLCIA(
demand={act: 1},
method=("AWARE 2.0", "Country", "all", "yearly"),
)
lcia.lci()
lcia.map_exchanges()
# this is a regionalized LCIa method
# so a few extra steps are necessary to ensure
# that exchanges with suppliers located in aggregated regions (e.g., RER)
# or dynamic regions (e.g., RoW) also get a CF
lcia.map_aggregate_locations()
lcia.map_dynamic_locations()
lcia.map_contained_locations()
lcia.map_remaining_locations_to_global()
lcia.evaluate_cfs()
lcia.lcia()
print(lcia.score)
For deterministic regionalized runs, ``generate_cf_table(split_aggregate_consumers=True)``
replaces weighted fallback rows for aggregate or dynamic consumer regions with
country-specific rows in the exported table.
.. note::
Methods can mix both ``supplier.matrix = "biosphere"`` and
``supplier.matrix = "technosphere"`` entries in the same JSON. ``edges``
builds both edge families during ``lci()``, sums both contributions in
``lcia()``, and exposes them in ``generate_cf_table()`` via the
``supplier matrix`` and ``direction`` columns.
---
Using a Custom Method JSON
--------------------------
Your method file should follow the expected CF JSON schema:
Supplier and consumer matching keys can include ``unit`` when otherwise identical
flows need to remain distinct during CF matching.
.. code-block:: python
import bw2data
from edges import EdgeLCIA
bw2data.projects.set_current("some project")
act = bw2data.Database("some db").random()
lcia = EdgeLCIA(
demand={act: 1},
method="my_custom_method.json",
)
lcia.lci()
lcia.map_exchanges()
lcia.evaluate_cfs(parameters={"H": 100, "C_CH4": 1866})
lcia.lcia()
---
Parameterized CFs
-----------------
If the method uses symbolic expressions, pass parameter values:
Expressions support arithmetic, parameter names, literal values, and bare
allowlisted function calls such as ``GWP(...)``. They do not support arbitrary
Python syntax such as attribute access, subscripting, comprehensions, imports,
or method calls. Any Python callable supplied through ``allowed_functions`` is
trusted code and should come from the user or another trusted source.
.. code-block:: python
import bw2data
from edges import EdgeLCIA
bw2data.projects.set_current("some project")
act = bw2data.Database("some db").random()
# Define scenario parameters (e.g., atmospheric CO₂ concentration
# and time horizon)
params = {
"some scenario": {
"co2ppm": {
"2020": 410,
"2050": 450,
"2100": 500
},
"h": {
"2020": 100,
"2050": 100,
"2100": 100
}
}
}
# Define an LCIA method name (the content will be taken from the JSON file)
method = ('GWP', 'scenario-dependent', '100 years')
lcia = EdgeLCIA(
demand={act: 1},
method=method,
parameters=params,
filepath="lcia_parameterized_gwp.json",
)
lcia.lci()
lcia.map_exchanges()
# Run scenarios efficiently
results = []
for idx in {"2020", "2050", "2100"}:
lcia.evaluate_cfs(idx)
lcia.lcia()
df = lcia.generate_cf_table()
scenario_result = {
"scenario": idx,
"co2ppm": params["some scenario"]["co2ppm"][idx],
"score": lcia.score,
"CF_table": df
}
results.append(scenario_result)
print(f"Scenario (CO₂ {params['some scenario']['co2ppm'][idx]} ppm): Impact = {lcia.score}")
This allows integration with scenario data (e.g., from RCPs or IAMs).
---
Uncertainty-aware LCIA
-----------------------
If CFs include uncertainty (e.g., lognormal, discrete empirical),
you can get statistics. In this mode, ``use_distributions=True`` samples
the characterization factors only; the Brightway inventory remains fixed:
.. code-block:: python
import bw2data
from edges import EdgeLCIA
bw2data.projects.set_current("some project")
act = bw2data.Database("some db").random()
lcia = EdgeLCIA(
demand={act: 1},
method=("AWARE 2.0", "Country", "all", "yearly"),
use_distributions=True,
iterations=10_000
)
lcia.lci()
lcia.map_exchanges()
lcia.map_aggregate_locations()
lcia.map_dynamic_locations()
lcia.map_contained_locations()
lcia.map_remaining_locations_to_global()
lcia.evaluate_cfs()
lcia.lcia()
print(lcia.score.mean())
#plot histogram of results distirbution
import matplotlib.pyplot as plt
plt.hist(lcia.score, bins=100)
# get dataframe with statistics
df = lcia.generate_cf_table()
---
Joint inventory + characterization Monte Carlo
----------------------------------------------
To propagate uncertainty from both the LCIA method and the inventory,
combine ``use_distributions=True`` with ``inventory_use_distributions=True``.
This reuses Brightway's stochastic inventory workflow and evaluates one score
per joint inventory + CF draw:
.. code-block:: python
import bw2data
from edges import EdgeLCIA
bw2data.projects.set_current("some project")
act = bw2data.Database("some db").random()
lcia = EdgeLCIA(
demand={act: 1},
method=("AWARE 2.0", "Country", "all", "yearly"),
use_distributions=True,
inventory_use_distributions=True,
store_inventory_samples=True,
iterations=1_000,
)
lcia.lci()
lcia.map_exchanges()
lcia.map_aggregate_locations()
lcia.map_dynamic_locations()
lcia.map_contained_locations()
lcia.map_remaining_locations_to_global()
lcia.evaluate_cfs()
lcia.lcia()
# one score per joint Monte Carlo iteration
print(lcia.score.mean())
# with store_inventory_samples=True, amount statistics are also available
df = lcia.generate_cf_table()
print(df[["amount (mean)", "CF (mean)", "impact (mean)"]].head())
.. note::
``use_distributions=True`` on its own keeps the current ``edges`` behavior
of varying only the characterization factors. Add
``inventory_use_distributions=True`` only when you want a joint Monte Carlo.
.. note::
``store_inventory_samples=False`` by default, to avoid storing a potentially
large 3D inventory tensor. Without stored inventory samples, the score vector
is still available, but ``generate_cf_table()`` cannot report per-iteration
amount statistics for the joint run.
.. note::
``split_aggregate_consumers=True`` is only available for deterministic
tables. It is not supported in uncertainty mode.
.. note::
If you pass your own ``bw2calc.LCA`` object via ``lca=``, initialize it with
``use_distributions=True`` before using
``inventory_use_distributions=True`` in ``EdgeLCIA``.
.. note::
Joint Monte Carlo is slower than CF-only uncertainty, because the inventory
must be re-sampled and re-assessed at every iteration.
---
To know more on how uncertainty works in `edges`, see:
- examples/uncertainty.ipynb
Working with Technosphere CFs (e.g., GeoPolRisk)
------------------------------------------------
.. code-block:: python
import bw2data
from edges import EdgeLCIA
bw2data.projects.set_current("some project")
act = bw2data.Database("some db").random()
lcia = EdgeLCIA(
demand={act:1},
method=("GeoPolRisk", "paired", "2024")
)
lcia.lci()
lcia.map_exchanges()
lcia.map_aggregate_locations()
lcia.map_contained_locations()
lcia.map_remaining_locations_to_global()
lcia.evaluate_cfs()
lcia.lcia()
df = lcia.generate_cf_table(split_aggregate_consumers=True)
df.to_csv("results.csv")
---
Working with Mixed Supplier Methods (e.g., IBIF all pressures)
--------------------------------------------------------------
Mixed methods combine biosphere-supplier rows and technosphere-supplier rows in
one method file. The standard workflow is unchanged:
.. code-block:: python
import bw2data
from edges import EdgeLCIA
bw2data.projects.set_current("some project")
act = bw2data.Database("some db").random()
lcia = EdgeLCIA(
demand={act: 1},
method=("IBIF", "biodiversity", "all pressures", "overall")
)
lcia.lci()
lcia.apply_strategies()
lcia.evaluate_cfs()
lcia.lcia()
df = lcia.generate_cf_table(split_aggregate_consumers=True)
In the exported table, ``supplier matrix`` tells you whether a row came from
the biosphere or technosphere side, and ``direction`` distinguishes
``biosphere-technosphere`` from ``technosphere-technosphere`` matches.
The built-in IBIF ``all pressures`` methods now use this mixed format:
- ``("IBIF", "biodiversity", "all pressures", "overall")`` combines
emissions, land occupation, and road infrastructure pressure.
- ``("IBIF", "biodiversity", "all pressures", "vertebrates")`` combines
emissions, land occupation, and road infrastructure pressure for the
vertebrate scope.
- ``("IBIF", "biodiversity", "all pressures", "plants")`` remains biosphere-only,
because the source IBIF release does not provide a road CF column for plants.
.. note::
Most core workflows support mixed methods, including ``lci()``,
``evaluate_cfs()``, ``lcia()``, ``redo_lcia()``, and
``generate_cf_table()``. Some higher-level analysis helpers still assume a
single supplier matrix and may raise ``NotImplementedError``.
---
Scenario-based Fossil Resource Scarcity
---------------------------------------
Supports expressions depending on extraction volume and discount rate:
.. code-block:: python
import bw2data
from edges import EdgeLCIA
bw2data.projects.set_current("some project")
act = bw2data.Database("some db").random()
lcia = EdgeLCIA(
demand={act: 1},
method="SCP_1.0.json",
)
lcia.lci()
lcia.map_exchanges()
lcia.evaluate_cfs(parameters={"MCI_OIL": 0.5, "P_OIL": 450, "d": 0.03})
lcia.lcia()
---
Exporting Results
-----------------
You can inspect or save the detailed contribution table:
.. code-block:: python
df = lcia.generate_cf_table(split_aggregate_consumers=True)
df.to_csv("edge_lcia_detailed_results.csv")
For deterministic runs, ``split_aggregate_consumers=True`` replaces weighted
fallback rows with country-level consumer rows using the exact shares stored
during geographic fallback matching.
Direct matches are left unchanged. The option only expands weighted fallback
rows created during geographic fallback mapping.
For mixed methods, the exported table can contain both biosphere and
technosphere supplier rows. Use the ``supplier matrix`` and ``direction``
columns to filter the table by contribution family.
If you want to inspect the raw split for a given exchange instead of only the
expanded table, look at ``reporting_split`` on deterministic
``lcia.scenario_cfs`` entries after ``evaluate_cfs()``:
.. code-block:: python
for cf in lcia.scenario_cfs:
if cf.get("reporting_split"):
print(cf["positions"], cf["consumer"].get("location"))
print(cf["reporting_split"])