From b31bb2f7606e5e1dc57c7772c3555c95f925f012 Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 10:13:17 +0200 Subject: [PATCH 01/24] docs: spec for representative_time database metadata --- ...-21-representative-time-metadata-design.md | 276 ++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-21-representative-time-metadata-design.md diff --git a/docs/superpowers/specs/2026-08-21-representative-time-metadata-design.md b/docs/superpowers/specs/2026-08-21-representative-time-metadata-design.md new file mode 100644 index 00000000..edc1c2ea --- /dev/null +++ b/docs/superpowers/specs/2026-08-21-representative-time-metadata-design.md @@ -0,0 +1,276 @@ +# Representative time as database metadata + +## Problem + +`TimexLCA` learns what a background database represents in one way only: the +`database_dates` argument the user hand-writes at every call site. + +```python +database_dates = { + "ei310_remind_SSP2-PkBudg500_2030": datetime(2030, 1, 1), + "ei310_remind_SSP2-PkBudg500_2040": datetime(2040, 1, 1), + "ei310_remind_SSP2-PkBudg500_2050": datetime(2050, 1, 1), + "foreground": "dynamic", +} +``` + +The information is already in the database — a premise export knows the year it was +built for — but it lives only in the database *name*, so every study re-types it, and +a typo either raises (`Database 'x' not available`) or, worse, silently maps a vintage +to the wrong year. + +premise [PR #303](https://github.com/polca/premise/pull/303) (merged to `master`, not +in 2.4.9.2) closes the gap on the producing side: exported Brightway databases now +carry what they represent in their `bd.databases[name]` metadata. + +```python +{ + # written by brightway + "format": "Ecoinvent XML", "depends": [...], "backend": "sqlite", + "number": 43648, "modified": "...", "processed": "...", + # written by premise + "premise_version": "2.4.9.1", + "iam_model": "remind", + "pathway": "SSP2-PkBudg500", + "representative_time": "2050-01-01T00:00:00", + "ecoinvent_version": "3.10.1", + "system_model": "cutoff", +} +``` + +Multi-scenario exports (superstructure, scenario arrays) instead carry a `scenarios` +list of such mappings, and a top-level `representative_time` only when all their +scenarios share a year. User (external) scenarios are listed under +`external_scenarios`. + +## Goal + +`TimexLCA` reads the databases' own metadata by default, so the common case needs no +timing argument at all: + +```python +tlca = TimexLCA(demand={("foreground", "A"): 1}, method=("GWP", "example")) +``` + +A project holding several IAM scenarios stays unambiguous: `TimexLCA` refuses to guess +and tells the user how to pick. + +```python +tlca = TimexLCA( + demand={("foreground", "A"): 1}, + method=("GWP", "example"), + scenario={"pathway": "SSP2-PkBudg500"}, +) +``` + +Databases that carry no metadata (hand-built vintages, the foreground) get it from a +one-line helper instead of a repeated argument. + +## Non-goals + +- Removing or deprecating `database_dates`. It stays, unchanged in meaning, and + scripts that pass it behave exactly as they do today. +- Reading anything from database *names*. No year parsing, no naming convention. +- Making superstructure / scenario-array databases usable in `TimexLCA`. They are + recognised and skipped, not supported. +- Writing metadata on import of ecoinvent or any other database. Only the explicit + helper writes. +- Changing how a resolved `database_dates` mapping is used downstream. Everything + after resolution — timeline, temporal markets, matrix modification — is untouched. + +## Design + +### Public interface + +```python +TimexLCA( + demand: dict, + method: tuple, + database_dates: dict = None, + scenario: dict = None, + use_global_lci_cache: bool = True, +) +``` + +`scenario` is a mapping of database metadata key to required value. Any key that +appears in database metadata is allowed — `iam_model`, `pathway`, `system_model`, +`ecoinvent_version`, `premise_version`, and whatever premise adds later. It is a dict +rather than a set of explicit keywords so that the signature stays closed (a +misspelled `use_global_lci_cache` raises `TypeError` instead of being swallowed as a +filter), the call site reads as background selection, and one dict can be reused +across a comparison loop. + +```python +from bw_timex import set_database_metadata + +set_database_metadata("db_2030", representative_time=datetime(2030, 1, 1)) +set_database_metadata( + "my_2050_variant", + representative_time="2050-01-01", + iam_model="remind", + pathway="SSP2-PkBudg500", +) +``` + +### Resolution + +`TimexLCA.__init__` resolves `self.database_dates` before anything else, in +`_resolve_database_dates`. Two mutually exclusive branches: + +**`database_dates` given.** It is the whole mapping. Metadata is not read, `scenario` +must be `None` (passing both raises `ValueError`), and demand databases missing from +it raise in validation as they do today. This keeps every existing script +bit-for-bit unchanged: a legacy call in a project that also holds ten premise +vintages must not silently pull those ten in. + +**`database_dates` not given.** Resolve from metadata: + +1. **Candidates.** Every database in `bd.databases` whose metadata has a + `representative_time`. +2. **Skip multi-scenario databases.** A candidate that also has a non-empty + `scenarios` list is dropped with a `logger.info` naming it. `bw_timex` needs one + technosphere per point in time and cannot pick a scenario out of a superstructure + database. Such a database can still be used by naming it in `database_dates`. +3. **Filter by `scenario`.** A candidate is dropped only if it *declares* a filtered + key with a different value. A candidate that does not declare the key at all is + kept — a hand-built 2020 database, an untouched ecoinvent, or the foreground has no + `pathway`, and filtering it out would break every mixed setup. + `external_scenarios` (a list) compares order-insensitively as a set; all other + values compare with `==` after `str` coercion of both sides. +4. **Ambiguity check.** Over the surviving candidates that declare at least one + scenario key, build a signature from + `("iam_model", "pathway", "system_model", "ecoinvent_version", "external_scenarios")` + (missing key → `None`). Bookkeeping keys such as `premise_version` are deliberately + not part of the signature: re-running premise must not look like a second scenario. + More than one distinct signature raises `ValueError`, reporting only the keys whose + values actually differ: + + ``` + Several background scenarios found in this project: + pathway='SSP2-PkBudg500': ei310_remind_SSP2-PkBudg500_2030, + ei310_remind_SSP2-PkBudg500_2040, + ei310_remind_SSP2-PkBudg500_2050 + pathway='SSP2-Base': ei310_remind_SSP2-Base_2030, + ei310_remind_SSP2-Base_2040, + ei310_remind_SSP2-Base_2050 + Select one, e.g. scenario={'pathway': 'SSP2-PkBudg500'}, or map the databases + explicitly with database_dates. + ``` + + Databases that declare no scenario key at all never appear in this check and are + always kept. +5. **Normalize values.** `datetime` passes through; a string parses with + `datetime.fromisoformat`; the literal `"dynamic"` passes through. Anything else + raises `ValueError` naming the database, the key and the offending value. +6. **Demand databases.** Every database holding a demand key that is not already + mapped is added as `"dynamic"`. +7. **Nothing found.** If no database carries `representative_time`, log the existing + "no remapping will be done" message and fall back to today's behaviour: demand + databases marked `"dynamic"`. + +An unknown filter key — one that no candidate database declares — raises rather than +filtering everything away, listing the keys and values present in the project. That is +what buys back the autocomplete a dict does not give. + +### Setter helper + +`bw_timex.utils.set_database_metadata(database, **metadata)`, re-exported from +`bw_timex`: + +- `database` may be a name or a `bd.Database`; unregistered → `ValueError`. +- `representative_time` accepts a `datetime` (serialized with `.isoformat()`), an ISO + string (validated by round-tripping through `fromisoformat`), or `"dynamic"`. + Brightway metadata is stored as JSON, so a `datetime` object left in it breaks + `bd.databases.flush()`; converting is the point of the helper. +- Any other key is written as given, after a JSON-serializability check. +- Writes into `bd.databases[name]` and calls `bd.databases.flush()`, so the value + survives a project reload. +- Returns the resulting metadata mapping. + +### Validation + +`TimexLCAInputs` gains `scenario: Optional[dict]`, validating that keys are strings +and values are scalars or lists of scalars, and that `scenario` and `database_dates` +are not both given. The metadata-side errors (unparseable value, ambiguity, unknown +filter key) are raised in `_resolve_database_dates`, which owns the metadata, not in +the pydantic model. + +`set_database_metadata` gets its own `DatabaseMetadataInputs` model, matching how the +other user-facing helpers in `utils.py` validate. + +## Interactions and limits + +- **Several databases per date** ([#205](https://github.com/brightway-lca/bw_timex/pull/205)) + still works: metadata discovery can map two databases to the same + `representative_time`, which is exactly the modified-copy case. Two full ecoinvent + copies of the same vintage (e.g. from two premise runs) collide on process identity + and raise there, as designed; the fix is a `scenario` filter on `premise_version` or + an explicit `database_dates`. +- **Setup cost.** `TimexLCA.__init__` loads node metadata for every database in + `database_dates`, so auto-discovery costs one node-metadata load per matching + database. A project holding vintages from an unrelated study pays for them; the + escape hatches are `scenario` or `database_dates`. +- **premise version.** The metadata is written by premise `master` (post-2.4.9.2). + Databases written by older premise carry nothing, and the docs say so; those users + either write metadata with the helper or keep using `database_dates`. + +## Documentation + +- `docs/content/getting_started/quickstart.md`: step 3 becomes "the databases already + know when they are"; `database_dates` shown once as the explicit alternative; the + cheat-sheet row for background timing updated. +- `docs/content/getting_started/adding_temporal_information.md` and + `build_process_timeline.md`: update the passages that name `database_dates`. +- New section in `docs/content/getting_started/` on what a database represents: + the metadata keys, `set_database_metadata`, scenario selection and its error, the + premise-version caveat, and `database_dates` as the explicit override. +- `docs/api/utils.md` picks up the new helper through the existing `::: bw_timex.utils` + block; no edit needed beyond the intro sentence. +- `CHANGES.md`: entry under `[Unreleased]`. + +## Notebooks + +Every notebook that builds its own databases writes metadata with +`set_database_metadata` and drops the `database_dates` argument; the premise notebooks +rely on premise-written metadata and show `scenario` where a project holds more than +one pathway. + +- `notebooks/tutorials/1_getting_started.ipynb` +- `notebooks/tutorials/2_electric_vehicle_from_scratch.ipynb` +- `notebooks/tutorials/3_dynamic_characterization.ipynb` +- `notebooks/tutorials/4_import_model_from_excel.ipynb` +- `notebooks/advanced/background_temporal_distributions.ipynb` +- `notebooks/advanced/background_temporal_distributions_premise.ipynb` +- `notebooks/advanced/uncertainty_with_datapackages.ipynb` +- `notebooks/teaching/ev_walkthrough_premise.ipynb` +- `notebooks/teaching/exercise_ev_vs_petrol_solutions.ipynb` +- `notebooks/examples/electric_vehicle_premise.ipynb` +- `notebooks/examples/electric_vehicle_premise_detailed.ipynb` +- `notebooks/development/benchmarking.ipynb` + +`notebooks/examples/paper_case_study.ipynb` is **not** touched: it reproduces a +published study and must keep its exact code. + +## Testing + +New `tests/test_database_metadata.py`, on the existing small fixtures: + +- Timing resolved from `representative_time` metadata with no `database_dates`. +- ISO string and `datetime` metadata values both resolve; `"dynamic"` in metadata + marks a database dynamic; a garbage value raises naming the database. +- Demand database defaults to `"dynamic"` when its metadata says nothing. +- `database_dates` is exclusive: a project full of metadata-carrying databases plus an + explicit `database_dates` resolves to exactly that mapping. +- `database_dates` together with `scenario` raises. +- `scenario` filter selects one pathway out of two; databases without scenario + metadata survive the filter. +- Two scenario sets and no `scenario` raises, and the message names the differing key + and both values. +- Same scenario written by two premise versions does not raise (bookkeeping keys are + outside the signature). +- A database carrying `scenarios` is skipped, and named in the log. +- An unknown filter key raises listing the available keys. +- `set_database_metadata` round-trips through `bd.databases.flush()` and a re-read; + a `datetime` lands as an ISO string; an unregistered database raises. +- End-to-end: an existing scenario test rewritten to use metadata gives the same + score as the `database_dates` version. From 0d67cad5c9ff7574431cf0ca3d7628e0b4f342c8 Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 10:15:10 +0200 Subject: [PATCH 02/24] docs: put database metadata handling in its own module in the spec --- ...-21-representative-time-metadata-design.md | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/superpowers/specs/2026-08-21-representative-time-metadata-design.md b/docs/superpowers/specs/2026-08-21-representative-time-metadata-design.md index edc1c2ea..773202ed 100644 --- a/docs/superpowers/specs/2026-08-21-representative-time-metadata-design.md +++ b/docs/superpowers/specs/2026-08-21-representative-time-metadata-design.md @@ -172,10 +172,18 @@ An unknown filter key — one that no candidate database declares — raises rat filtering everything away, listing the keys and values present in the project. That is what buys back the autocomplete a dict does not give. +### Module layout + +Discovery, filtering and the setter live in a new module, +`bw_timex/database_metadata.py`. `timex_lca.py` is already large and this is a +self-contained responsibility with its own tests; `utils.py` holds exchange- and +plot-level helpers. `TimexLCA` imports `resolve_database_dates_from_metadata` from it, +and `set_database_metadata` is re-exported from the `bw_timex` top-level namespace. + ### Setter helper -`bw_timex.utils.set_database_metadata(database, **metadata)`, re-exported from -`bw_timex`: +`bw_timex.database_metadata.set_database_metadata(database, **metadata)`, re-exported +from `bw_timex`: - `database` may be a name or a `bd.Database`; unregistered → `ValueError`. - `representative_time` accepts a `datetime` (serialized with `.isoformat()`), an ISO @@ -195,8 +203,8 @@ are not both given. The metadata-side errors (unparseable value, ambiguity, unkn filter key) are raised in `_resolve_database_dates`, which owns the metadata, not in the pydantic model. -`set_database_metadata` gets its own `DatabaseMetadataInputs` model, matching how the -other user-facing helpers in `utils.py` validate. +`set_database_metadata` gets its own `DatabaseMetadataInputs` model in +`validation.py`, matching how the other user-facing helpers validate. ## Interactions and limits @@ -224,8 +232,8 @@ other user-facing helpers in `utils.py` validate. - New section in `docs/content/getting_started/` on what a database represents: the metadata keys, `set_database_metadata`, scenario selection and its error, the premise-version caveat, and `database_dates` as the explicit override. -- `docs/api/utils.md` picks up the new helper through the existing `::: bw_timex.utils` - block; no edit needed beyond the intro sentence. +- New `docs/api/database_metadata.md` (`::: bw_timex.database_metadata`), added to the + API nav in `zensical.toml`. - `CHANGES.md`: entry under `[Unreleased]`. ## Notebooks From ecd2bdcb7fd7b06afa1e523a893874fe0051ab86 Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 10:19:51 +0200 Subject: [PATCH 03/24] docs: implementation plan for representative_time database metadata --- ...2026-08-21-representative-time-metadata.md | 1510 +++++++++++++++++ 1 file changed, 1510 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-21-representative-time-metadata.md diff --git a/docs/superpowers/plans/2026-08-21-representative-time-metadata.md b/docs/superpowers/plans/2026-08-21-representative-time-metadata.md new file mode 100644 index 00000000..50756a97 --- /dev/null +++ b/docs/superpowers/plans/2026-08-21-representative-time-metadata.md @@ -0,0 +1,1510 @@ +# Representative time as database metadata — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** `TimexLCA` learns the point in time each background database represents from that database's own Brightway metadata (`representative_time`, as written by premise), so `database_dates` becomes an optional explicit override instead of a required argument. + +**Architecture:** A new module `bw_timex/database_metadata.py` owns everything about database metadata: writing it (`set_database_metadata`) and resolving a `{database: datetime | "dynamic"}` mapping out of the project (`resolve_database_dates_from_metadata`), including scenario filtering and the ambiguity error. `TimexLCA.__init__` calls it in one place and is otherwise untouched: everything downstream still consumes `self.database_dates`. + +**Tech Stack:** Python 3.10+, `bw2data` (database metadata lives in `bd.databases[name]`, a JSON-serialized dict), `pydantic` (input validation, see `bw_timex/validation.py`), `loguru`, `pytest` with `bw2data.tests.bw2test` fixtures. + +**Spec:** `docs/superpowers/specs/2026-08-21-representative-time-metadata-design.md` + +## Global Constraints + +- Run everything with the project venv: `.venv/bin/python`, `.venv/bin/pytest`. +- `database_dates` semantics do not change. When it is passed, it is the whole mapping and metadata is never read. +- `bd.databases` is serialized to JSON. A `datetime` written into it breaks `bd.databases.flush()`. Every date stored in metadata is an ISO 8601 string. +- Metadata keys, exactly as premise writes them: `representative_time`, `iam_model`, `pathway`, `system_model`, `ecoinvent_version`, `premise_version`, `external_scenarios`, `scenarios`. +- Scenario identity keys (the ambiguity signature) are exactly: `("iam_model", "pathway", "system_model", "ecoinvent_version", "external_scenarios")`. `premise_version` is deliberately not one of them. +- `notebooks/examples/paper_case_study.ipynb` must not be modified by any task. +- No Claude/AI attribution in commit messages. + +--- + +### Task 1: `database_metadata` module — writing metadata + +**Files:** +- Create: `bw_timex/database_metadata.py` +- Modify: `bw_timex/validation.py` (append a `DatabaseMetadataInputs` model) +- Modify: `bw_timex/__init__.py` (export `set_database_metadata`) +- Create: `tests/test_database_metadata.py` + +**Interfaces:** +- Consumes: nothing from earlier tasks. +- Produces: + - `bw_timex.database_metadata.set_database_metadata(database: str | bd.Database, **metadata) -> dict` + - constants `REPRESENTATIVE_TIME: str = "representative_time"`, `SCENARIOS: str = "scenarios"`, `DYNAMIC: str = "dynamic"`, `SCENARIO_SIGNATURE_KEYS: tuple[str, ...]`, `BRIGHTWAY_METADATA_KEYS: frozenset[str]` + - `bw_timex.database_metadata._normalize_representative_time(value, database: str) -> datetime | str` + +- [ ] **Step 1: Write the failing tests** + +Create `tests/test_database_metadata.py`: + +```python +"""Tests for reading and writing what a Brightway database represents.""" + +from datetime import datetime + +import bw2data as bd +import pytest + +from bw_timex import set_database_metadata + +# ─── Tests for set_database_metadata ─── + + +@pytest.mark.usefixtures("temporal_grouping_db_monthly") +class TestSetDatabaseMetadata: + + def test_datetime_is_stored_as_iso_string(self): + set_database_metadata("db_2022", representative_time=datetime(2022, 1, 1)) + assert bd.databases["db_2022"]["representative_time"] == "2022-01-01T00:00:00" + + def test_iso_string_is_stored_as_given(self): + set_database_metadata("db_2022", representative_time="2022-01-01") + assert bd.databases["db_2022"]["representative_time"] == "2022-01-01" + + def test_dynamic_is_allowed(self): + set_database_metadata("foreground", representative_time="dynamic") + assert bd.databases["foreground"]["representative_time"] == "dynamic" + + def test_scenario_fields_are_stored(self): + set_database_metadata( + "db_2022", + representative_time=datetime(2022, 1, 1), + iam_model="remind", + pathway="SSP2-PkBudg500", + ) + assert bd.databases["db_2022"]["iam_model"] == "remind" + assert bd.databases["db_2022"]["pathway"] == "SSP2-PkBudg500" + + def test_database_object_is_accepted(self): + set_database_metadata( + bd.Database("db_2022"), representative_time=datetime(2022, 1, 1) + ) + assert bd.databases["db_2022"]["representative_time"] == "2022-01-01T00:00:00" + + def test_existing_metadata_is_kept(self): + before = bd.databases["db_2022"]["backend"] + set_database_metadata("db_2022", representative_time=datetime(2022, 1, 1)) + assert bd.databases["db_2022"]["backend"] == before + + def test_survives_flush_and_reload(self): + set_database_metadata("db_2022", representative_time=datetime(2022, 1, 1)) + bd.databases.__init__() # re-read from disk + assert bd.databases["db_2022"]["representative_time"] == "2022-01-01T00:00:00" + + def test_unregistered_database_raises(self): + with pytest.raises(ValueError, match="not registered"): + set_database_metadata("no_such_db", representative_time=datetime(2022, 1, 1)) + + def test_unparseable_representative_time_raises(self): + with pytest.raises(ValueError, match="representative_time"): + set_database_metadata("db_2022", representative_time="whenever") + + def test_non_serializable_value_raises(self): + with pytest.raises(ValueError, match="JSON"): + set_database_metadata("db_2022", pathway=object()) + + def test_no_metadata_raises(self): + with pytest.raises(ValueError, match="at least one"): + set_database_metadata("db_2022") +``` + +- [ ] **Step 2: Run the tests to verify they fail** + +Run: `.venv/bin/pytest tests/test_database_metadata.py -v` +Expected: FAIL — `ImportError: cannot import name 'set_database_metadata' from 'bw_timex'` + +- [ ] **Step 3: Write the module** + +Create `bw_timex/database_metadata.py`: + +```python +"""Read and write what a Brightway database represents. + +`bw_timex` needs to know which point in time each background database stands +for. That information is stored in the database's own Brightway metadata +(`bw2data.databases[name]`), where premise also writes it when it exports a +prospective database: + +```python +{ + "premise_version": "2.4.9.1", + "iam_model": "remind", + "pathway": "SSP2-PkBudg500", + "representative_time": "2050-01-01T00:00:00", + "ecoinvent_version": "3.10.1", + "system_model": "cutoff", +} +``` + +Brightway stores this mapping as JSON, so dates are kept as ISO 8601 strings. +""" + +from __future__ import annotations + +import json +from datetime import datetime +from typing import Any + +import bw2data as bd + +REPRESENTATIVE_TIME = "representative_time" +SCENARIOS = "scenarios" +DYNAMIC = "dynamic" + +#: Metadata keys that identify the scenario a database represents. Two +#: databases differing in any of these represent different scenarios. +#: `premise_version` is deliberately absent: re-running premise on the same +#: pathway must not look like a second scenario. +SCENARIO_SIGNATURE_KEYS = ( + "iam_model", + "pathway", + "system_model", + "ecoinvent_version", + "external_scenarios", +) + +#: Keys Brightway maintains itself, filtered out when reporting to the user +#: which metadata a project's databases carry. +BRIGHTWAY_METADATA_KEYS = frozenset( + { + "backend", + "depends", + "dirty", + "format", + "geocollections", + "modified", + "number", + "processed", + "searchable", + } +) + + +def _database_name(database: Any) -> str: + """The name of a database given either as a name or as a `bd.Database`.""" + name = getattr(database, "name", database) + if not isinstance(name, str): + raise ValueError( + f"database must be a database name or a bw2data Database, got " + f"{type(database).__name__}." + ) + return name + + +def _normalize_representative_time(value: Any, database: str) -> datetime | str: + """Turn a stored `representative_time` into a datetime or `"dynamic"`.""" + if isinstance(value, datetime): + return value + if isinstance(value, str): + if value == DYNAMIC: + return DYNAMIC + try: + return datetime.fromisoformat(value) + except ValueError: + raise ValueError( + f"Database '{database}' has an invalid `{REPRESENTATIVE_TIME}` " + f"metadata value: {value!r}. Expected an ISO 8601 datetime string " + f"(e.g. '2030-01-01'), a datetime, or '{DYNAMIC}'." + ) from None + raise ValueError( + f"Database '{database}' has an invalid `{REPRESENTATIVE_TIME}` metadata " + f"value of type {type(value).__name__}: {value!r}. Expected an ISO 8601 " + f"datetime string, a datetime, or '{DYNAMIC}'." + ) + + +def set_database_metadata(database: str | bd.Database, **metadata) -> dict: + """ + Store what a database represents in its Brightway metadata. + + Use this for databases that don't bring the metadata themselves, e.g. + databases you built yourself or that were exported by a premise version + older than the one writing scenario metadata. `TimexLCA` reads + `representative_time` from all databases of the project to map them to + points in time, so this replaces passing `database_dates`. + + Parameters + ---------- + database : str or bw2data.Database + Name of the database, or the database itself. Must be registered. + **metadata : + Metadata to store. `representative_time` accepts a `datetime`, an ISO + 8601 string, or `"dynamic"` and is always stored as a string, because + Brightway serializes database metadata to JSON. Any other key is stored + as given and must be JSON-serializable. Keys that premise writes, and + that `TimexLCA(scenario=...)` can select on, are `iam_model`, + `pathway`, `system_model`, `ecoinvent_version` and `premise_version`. + + Returns + ------- + dict + The database's metadata after the update. + + Examples + -------- + ```python + set_database_metadata("db_2030", representative_time=datetime(2030, 1, 1)) + set_database_metadata( + "my_2050_variant", + representative_time="2050-01-01", + iam_model="remind", + pathway="SSP2-PkBudg500", + ) + ``` + """ + from .validation import DatabaseMetadataInputs + + name = _database_name(database) + DatabaseMetadataInputs(database=name, metadata=metadata) + + if name not in bd.databases: + raise ValueError( + f"Database '{name}' is not registered in this Brightway project. " + f"Available databases: {sorted(bd.databases)}." + ) + + serialized = {} + for key, value in metadata.items(): + if key == REPRESENTATIVE_TIME: + normalized = _normalize_representative_time(value, name) + serialized[key] = ( + normalized if normalized == DYNAMIC else normalized.isoformat() + ) + continue + try: + json.dumps(value) + except TypeError: + raise ValueError( + f"Metadata value for '{key}' is not JSON-serializable: {value!r}. " + f"Brightway stores database metadata as JSON." + ) from None + serialized[key] = value + + bd.databases[name].update(serialized) + bd.databases.flush() + return bd.databases[name] +``` + +- [ ] **Step 4: Add the validation model** + +Append to `bw_timex/validation.py`: + +```python +class DatabaseMetadataInputs(BaseModel): + """Validates inputs to set_database_metadata""" + + model_config = {"arbitrary_types_allowed": True} + + database: str + metadata: dict + + @field_validator("metadata") + @classmethod + def validate_metadata(cls, v: dict) -> dict: + if not v: + raise ValueError( + "Provide at least one metadata field, e.g. " + "`representative_time=datetime(2030, 1, 1)`." + ) + for key in v: + if not isinstance(key, str): + raise ValueError( + f"Metadata keys must be strings, got {type(key).__name__}: {key}." + ) + return v +``` + +- [ ] **Step 5: Export it** + +In `bw_timex/__init__.py`, add the import next to the other helper imports and the name to `__all__` (in the `# utils` block, alphabetically after `plot_characterized_inventory_as_waterfall`): + +```python +from .database_metadata import set_database_metadata +``` + +```python + "set_database_metadata", +``` + +- [ ] **Step 6: Run the tests to verify they pass** + +Run: `.venv/bin/pytest tests/test_database_metadata.py -v` +Expected: PASS (12 tests) + +- [ ] **Step 7: Commit** + +```bash +git add bw_timex/database_metadata.py bw_timex/validation.py bw_timex/__init__.py tests/test_database_metadata.py +git commit -m "feat: add set_database_metadata to store what a database represents" +``` + +--- + +### Task 2: Resolve database dates from metadata + +**Files:** +- Modify: `bw_timex/database_metadata.py` +- Modify: `tests/test_database_metadata.py` + +**Interfaces:** +- Consumes: `REPRESENTATIVE_TIME`, `SCENARIOS`, `DYNAMIC`, `_normalize_representative_time`, `set_database_metadata` from Task 1. +- Produces: `resolve_database_dates_from_metadata(scenario: dict | None = None) -> dict[str, datetime | str]` — every registered database carrying `representative_time`, mapped to a `datetime` or `"dynamic"`. Multi-scenario databases are excluded. Scenario filtering and the ambiguity error come in Task 3; this task's version accepts the argument and ignores it. + +- [ ] **Step 1: Write the failing tests** + +Append to `tests/test_database_metadata.py`: + +```python +from bw_timex.database_metadata import resolve_database_dates_from_metadata + +# ─── Tests for resolving database dates from metadata ─── + + +@pytest.mark.usefixtures("temporal_grouping_db_monthly") +class TestResolveFromMetadata: + + def test_empty_project_metadata_resolves_to_nothing(self): + assert resolve_database_dates_from_metadata() == {} + + def test_iso_strings_resolve_to_datetimes(self): + set_database_metadata("db_2022", representative_time="2022-01-01") + set_database_metadata("db_2024", representative_time="2024-01-01") + assert resolve_database_dates_from_metadata() == { + "db_2022": datetime(2022, 1, 1), + "db_2024": datetime(2024, 1, 1), + } + + def test_dynamic_metadata_resolves_to_dynamic(self): + set_database_metadata("db_2022", representative_time="2022-01-01") + set_database_metadata("foreground", representative_time="dynamic") + resolved = resolve_database_dates_from_metadata() + assert resolved["foreground"] == "dynamic" + assert resolved["db_2022"] == datetime(2022, 1, 1) + + def test_databases_without_metadata_are_ignored(self): + set_database_metadata("db_2022", representative_time="2022-01-01") + assert set(resolve_database_dates_from_metadata()) == {"db_2022"} + + def test_multi_scenario_database_is_skipped(self): + set_database_metadata("db_2022", representative_time="2022-01-01") + set_database_metadata( + "db_2024", + representative_time="2024-01-01", + scenarios=[ + {"pathway": "SSP2-Base", "representative_time": "2024-01-01"}, + {"pathway": "SSP2-PkBudg500", "representative_time": "2024-01-01"}, + ], + ) + assert set(resolve_database_dates_from_metadata()) == {"db_2022"} + + def test_invalid_metadata_value_raises_naming_the_database(self): + bd.databases["db_2022"]["representative_time"] = "whenever" + bd.databases.flush() + with pytest.raises(ValueError, match="db_2022"): + resolve_database_dates_from_metadata() +``` + +- [ ] **Step 2: Run the tests to verify they fail** + +Run: `.venv/bin/pytest tests/test_database_metadata.py::TestResolveFromMetadata -v` +Expected: FAIL — `ImportError: cannot import name 'resolve_database_dates_from_metadata'` + +- [ ] **Step 3: Implement discovery** + +Append to `bw_timex/database_metadata.py` (and add `from loguru import logger` to the imports): + +```python +def _candidate_databases() -> dict[str, dict]: + """Registered databases that declare a `representative_time`. + + Multi-scenario databases (superstructure and scenario-array exports, which + carry a `scenarios` list) are skipped: `bw_timex` needs one technosphere + per point in time and cannot pick a scenario out of such a database. They + can still be used by naming them in `database_dates`. + """ + candidates = {} + for name in bd.databases: + metadata = bd.databases[name] + if REPRESENTATIVE_TIME not in metadata: + continue + if metadata.get(SCENARIOS): + logger.info( + f"Skipping database '{name}': it holds " + f"{len(metadata[SCENARIOS])} scenarios, so the point in time it " + f"represents is ambiguous. Map it explicitly with `database_dates` " + f"if you want to use it anyway." + ) + continue + candidates[name] = metadata + return candidates + + +def resolve_database_dates_from_metadata( + scenario: dict | None = None, +) -> dict[str, datetime | str]: + """ + Map the databases of the current project to the points in time they represent. + + Reads the `representative_time` metadata of every registered database (see + [`set_database_metadata`][bw_timex.database_metadata.set_database_metadata]). + + Parameters + ---------- + scenario : dict, optional + Metadata a database must match to be included, e.g. + `{"iam_model": "remind", "pathway": "SSP2-PkBudg500"}`. Databases that + don't declare a filtered key at all are kept. + + Returns + ------- + dict + Mapping of database name to `datetime` or `"dynamic"`, ready to be used + as `TimexLCA.database_dates`. + """ + candidates = _candidate_databases() + return { + name: _normalize_representative_time(metadata[REPRESENTATIVE_TIME], name) + for name, metadata in candidates.items() + } +``` + +- [ ] **Step 4: Run the tests to verify they pass** + +Run: `.venv/bin/pytest tests/test_database_metadata.py -v` +Expected: PASS (18 tests) + +- [ ] **Step 5: Commit** + +```bash +git add bw_timex/database_metadata.py tests/test_database_metadata.py +git commit -m "feat: resolve database dates from representative_time metadata" +``` + +--- + +### Task 3: Scenario filtering and the ambiguity error + +**Files:** +- Modify: `bw_timex/database_metadata.py` +- Modify: `tests/test_database_metadata.py` + +**Interfaces:** +- Consumes: `resolve_database_dates_from_metadata`, `_candidate_databases`, `SCENARIO_SIGNATURE_KEYS`, `BRIGHTWAY_METADATA_KEYS` from Tasks 1–2. +- Produces: `resolve_database_dates_from_metadata(scenario)` now filters, and raises `ValueError` on an unknown filter key or on several scenario sets. No new public names. + +- [ ] **Step 1: Write the failing tests** + +Append to `tests/test_database_metadata.py`: + +```python +# ─── Tests for scenario selection ─── + + +@pytest.mark.usefixtures("temporal_grouping_db_monthly") +class TestScenarioSelection: + + @pytest.fixture(autouse=True) + def two_scenarios(self): + """db_2022 and db_2024 hold the same year in two different pathways.""" + set_database_metadata( + "db_2022", + representative_time="2022-01-01", + iam_model="remind", + pathway="SSP2-PkBudg500", + premise_version="2.4.9.1", + ) + set_database_metadata( + "db_2024", + representative_time="2024-01-01", + iam_model="remind", + pathway="SSP2-Base", + premise_version="2.4.9.1", + ) + + def test_two_scenario_sets_without_selection_raises(self): + with pytest.raises(ValueError, match="Several background scenarios"): + resolve_database_dates_from_metadata() + + def test_error_names_the_differing_key_and_values(self): + with pytest.raises(ValueError) as excinfo: + resolve_database_dates_from_metadata() + message = str(excinfo.value) + assert "pathway" in message + assert "SSP2-PkBudg500" in message + assert "SSP2-Base" in message + # iam_model is identical in both sets, so it isn't part of the report + assert "iam_model" not in message + + def test_scenario_selects_one_set(self): + resolved = resolve_database_dates_from_metadata( + scenario={"pathway": "SSP2-Base"} + ) + assert resolved == {"db_2024": datetime(2024, 1, 1)} + + def test_databases_without_scenario_metadata_survive_the_filter(self): + set_database_metadata("foreground", representative_time="dynamic") + resolved = resolve_database_dates_from_metadata( + scenario={"pathway": "SSP2-Base"} + ) + assert resolved == { + "db_2024": datetime(2024, 1, 1), + "foreground": "dynamic", + } + + def test_several_filter_keys_are_combined(self): + resolved = resolve_database_dates_from_metadata( + scenario={"iam_model": "remind", "pathway": "SSP2-Base"} + ) + assert set(resolved) == {"db_2024"} + + def test_filter_matching_nothing_resolves_to_nothing(self): + assert resolve_database_dates_from_metadata( + scenario={"pathway": "SSP2-PkBudg1150"} + ) == {} + + def test_unknown_filter_key_raises_listing_available_keys(self): + with pytest.raises(ValueError) as excinfo: + resolve_database_dates_from_metadata(scenario={"pathwya": "SSP2-Base"}) + message = str(excinfo.value) + assert "pathwya" in message + assert "pathway" in message + + def test_same_scenario_from_two_premise_versions_is_not_ambiguous(self): + set_database_metadata("db_2024", pathway="SSP2-PkBudg500") + set_database_metadata("db_2024", premise_version="2.4.9.2") + assert set(resolve_database_dates_from_metadata()) == {"db_2022", "db_2024"} +``` + +- [ ] **Step 2: Run the tests to verify they fail** + +Run: `.venv/bin/pytest tests/test_database_metadata.py::TestScenarioSelection -v` +Expected: FAIL — no error is raised, `resolve_database_dates_from_metadata` currently ignores `scenario` + +- [ ] **Step 3: Implement filtering and the ambiguity check** + +In `bw_timex/database_metadata.py`, add `from collections import defaultdict` to the imports and insert before `resolve_database_dates_from_metadata`: + +```python +def _as_set(value: Any) -> set: + """Compare list-valued metadata (e.g. `external_scenarios`) order-insensitively.""" + if isinstance(value, (list, tuple, set)): + return {str(item) for item in value} + return {str(value)} + + +def _values_match(declared: Any, wanted: Any) -> bool: + if isinstance(declared, (list, tuple, set)) or isinstance(wanted, (list, tuple, set)): + return _as_set(declared) == _as_set(wanted) + return str(declared) == str(wanted) + + +def _check_filter_keys(scenario: dict, candidates: dict[str, dict]) -> None: + """Reject filter keys no database declares, instead of silently matching nothing.""" + declared = set() + for metadata in candidates.values(): + declared.update(set(metadata) - BRIGHTWAY_METADATA_KEYS) + unknown = sorted(set(scenario) - declared) + if not unknown: + return + available = ", ".join(sorted(declared)) or "none" + raise ValueError( + f"No database in this project declares the metadata key(s) " + f"{unknown}. Keys declared by the databases of this project: {available}. " + f"Add the metadata with `bw_timex.set_database_metadata`, or check the " + f"spelling of your `scenario` filter." + ) + + +def _scenario_signature(metadata: dict) -> tuple: + return tuple( + (key, tuple(sorted(_as_set(metadata[key]))) if key in metadata else None) + for key in SCENARIO_SIGNATURE_KEYS + ) + + +def _format_scenario_sets(groups: dict[tuple, list[str]]) -> str: + """One line per scenario set, naming only the keys that actually differ.""" + differing = [ + key + for index, key in enumerate(SCENARIO_SIGNATURE_KEYS) + if len({signature[index][1] for signature in groups}) > 1 + ] + lines = [] + for signature, names in groups.items(): + values = dict(signature) + description = ", ".join( + f"{key}={', '.join(values[key]) if values[key] else 'not set'}" + for key in differing + ) + lines.append(f" {description}: {', '.join(sorted(names))}") + return "\n".join(lines) + + +def _check_unambiguous(candidates: dict[str, dict]) -> None: + groups = defaultdict(list) + for name, metadata in candidates.items(): + if any(key in metadata for key in SCENARIO_SIGNATURE_KEYS): + groups[_scenario_signature(metadata)].append(name) + if len(groups) <= 1: + return + raise ValueError( + f"Several background scenarios found in this project:\n" + f"{_format_scenario_sets(groups)}\n" + f"Select one, e.g. scenario={{'pathway': '...'}}, or map the databases " + f"explicitly with `database_dates`." + ) +``` + +Then replace the body of `resolve_database_dates_from_metadata` with: + +```python + candidates = _candidate_databases() + if scenario: + _check_filter_keys(scenario, candidates) + candidates = { + name: metadata + for name, metadata in candidates.items() + if all( + key not in metadata or _values_match(metadata[key], wanted) + for key, wanted in scenario.items() + ) + } + _check_unambiguous(candidates) + return { + name: _normalize_representative_time(metadata[REPRESENTATIVE_TIME], name) + for name, metadata in candidates.items() + } +``` + +- [ ] **Step 4: Run the tests to verify they pass** + +Run: `.venv/bin/pytest tests/test_database_metadata.py -v` +Expected: PASS (26 tests) + +- [ ] **Step 5: Commit** + +```bash +git add bw_timex/database_metadata.py tests/test_database_metadata.py +git commit -m "feat: select background scenarios by database metadata" +``` + +--- + +### Task 4: Wire it into `TimexLCA` + +**Files:** +- Modify: `bw_timex/timex_lca.py` (imports, class docstring `Examples` block, `__init__` signature + docstring, the `database_dates` fallback block at `timex_lca.py:146-160`) +- Modify: `bw_timex/validation.py` (`TimexLCAInputs`) +- Modify: `tests/test_database_metadata.py` + +**Interfaces:** +- Consumes: `resolve_database_dates_from_metadata(scenario)` from Task 3. +- Produces: `TimexLCA(demand, method, database_dates=None, scenario=None, use_global_lci_cache=True)`; `TimexLCA.scenario` holds the filter that was used; `TimexLCA.database_dates` is the resolved mapping, exactly as before for callers who pass `database_dates`. + +- [ ] **Step 1: Write the failing tests** + +Append to `tests/test_database_metadata.py`: + +```python +from bw_timex import TimexLCA + +# ─── Tests for TimexLCA using database metadata ─── + + +@pytest.mark.usefixtures("temporal_grouping_db_monthly") +class TestTimexLCAFromMetadata: + + @pytest.fixture + def fu(self): + return bd.get_node(database="foreground", code="A") + + def test_no_arguments_uses_metadata(self, fu): + set_database_metadata("db_2022", representative_time="2022-01-01") + set_database_metadata("db_2024", representative_time="2024-01-01") + tlca = TimexLCA(demand={fu.key: 1}, method=("GWP", "example")) + assert tlca.database_dates == { + "db_2022": datetime(2022, 1, 1), + "db_2024": datetime(2024, 1, 1), + "foreground": "dynamic", + } + + def test_demand_database_metadata_is_respected(self, fu): + set_database_metadata("db_2022", representative_time="2022-01-01") + set_database_metadata("foreground", representative_time="dynamic") + tlca = TimexLCA(demand={fu.key: 1}, method=("GWP", "example")) + assert tlca.database_dates["foreground"] == "dynamic" + + def test_scenario_is_forwarded(self, fu): + set_database_metadata( + "db_2022", representative_time="2022-01-01", pathway="SSP2-Base" + ) + set_database_metadata( + "db_2024", representative_time="2024-01-01", pathway="SSP2-PkBudg500" + ) + tlca = TimexLCA( + demand={fu.key: 1}, + method=("GWP", "example"), + scenario={"pathway": "SSP2-Base"}, + ) + assert tlca.database_dates == { + "db_2022": datetime(2022, 1, 1), + "foreground": "dynamic", + } + + def test_database_dates_is_exclusive(self, fu): + set_database_metadata("db_2022", representative_time="2022-01-01") + set_database_metadata("db_2024", representative_time="2024-01-01") + tlca = TimexLCA( + demand={fu.key: 1}, + method=("GWP", "example"), + database_dates={ + "db_2024": datetime(2024, 1, 1), + "foreground": "dynamic", + }, + ) + assert tlca.database_dates == { + "db_2024": datetime(2024, 1, 1), + "foreground": "dynamic", + } + + def test_database_dates_with_scenario_raises(self, fu): + with pytest.raises(ValueError, match="only applies when"): + TimexLCA( + demand={fu.key: 1}, + method=("GWP", "example"), + database_dates={"foreground": "dynamic"}, + scenario={"pathway": "SSP2-Base"}, + ) + + def test_no_metadata_anywhere_falls_back_to_dynamic_demand(self, fu): + tlca = TimexLCA(demand={fu.key: 1}, method=("GWP", "example")) + assert tlca.database_dates == {"foreground": "dynamic"} + + def test_metadata_and_database_dates_give_the_same_score(self, fu): + explicit = TimexLCA( + demand={fu.key: 1}, + method=("GWP", "example"), + database_dates={ + "db_2022": datetime(2022, 1, 1), + "db_2024": datetime(2024, 1, 1), + "foreground": "dynamic", + }, + ) + explicit.build_timeline(starting_datetime=datetime(2024, 1, 2)) + explicit.lci() + explicit.static_lcia() + + set_database_metadata("db_2022", representative_time="2022-01-01") + set_database_metadata("db_2024", representative_time="2024-01-01") + from_metadata = TimexLCA(demand={fu.key: 1}, method=("GWP", "example")) + from_metadata.build_timeline(starting_datetime=datetime(2024, 1, 2)) + from_metadata.lci() + from_metadata.static_lcia() + + assert from_metadata.static_score == pytest.approx(explicit.static_score) +``` + +- [ ] **Step 2: Run the tests to verify they fail** + +Run: `.venv/bin/pytest tests/test_database_metadata.py::TestTimexLCAFromMetadata -v` +Expected: FAIL — `TypeError: TimexLCA.__init__() got an unexpected keyword argument 'scenario'`, and `test_no_arguments_uses_metadata` fails because only the demand database is mapped + +- [ ] **Step 3: Change the signature and resolution** + +In `bw_timex/timex_lca.py`, add to the imports: + +```python +from .database_metadata import resolve_database_dates_from_metadata +``` + +Change the signature: + +```python + def __init__( + self, + demand: dict, + method: tuple, + database_dates: dict = None, + scenario: dict = None, + use_global_lci_cache: bool = True, + ) -> None: +``` + +Replace the `database_dates` docstring entry and add one for `scenario`: + +``` + database_dates : dict, optional + Dictionary mapping database names to the point in time they + represent, as a `datetime`, or to `"dynamic"` for databases whose + processes are distributed over time (typically the foreground). + Several databases may share the same date, e.g. to keep your own + modified copies of background processes in their own database + instead of writing them into the shared background database for + that vintage. If not given, the mapping is read from the + databases' own `representative_time` metadata (which premise + writes when exporting, and which you can set yourself with + `bw_timex.set_database_metadata`). Passing this argument replaces + the metadata entirely: only the databases listed here are used. + scenario : dict, optional + Metadata a background database must match to be used, e.g. + `{"iam_model": "remind", "pathway": "SSP2-PkBudg500"}`. Only + needed when the project holds several scenarios - `TimexLCA` + raises and lists them otherwise. Databases that don't declare the + filtered key (your foreground, a hand-built vintage) are always + kept. Cannot be combined with `database_dates`. +``` + +Replace the fallback block (`self.database_dates = database_dates` through the `if not self.database_dates:` block) with: + +```python + self.scenario = scenario + self.database_dates = self._resolve_database_dates( + demand=demand, database_dates=database_dates, scenario=scenario + ) +``` + +Add the method right after `__init__`: + +```python + @staticmethod + def _resolve_database_dates( + demand: dict, database_dates: dict | None, scenario: dict | None + ) -> dict: + """Map databases to the points in time they represent. + + Either from the explicit `database_dates` argument, which is then the + whole mapping, or from the databases' own `representative_time` + metadata. Databases holding the demand default to `"dynamic"`. + """ + if database_dates: + if scenario: + raise ValueError( + "`scenario` selects background databases by their metadata and " + "only applies when `database_dates` is not given. Pass one or " + "the other." + ) + return dict(database_dates) + + resolved = resolve_database_dates_from_metadata(scenario) + + if not resolved: + logger.info( + "No database_dates provided, and no database in this project carries " + "`representative_time` metadata. Treating the databases containing the " + "functional unit as dynamic. No remapping of inventories to time " + "explicit databases will be done." + ) + + for key in demand: + database = bd.get_node(id=get_id(key))["database"] + resolved.setdefault(database, "dynamic") + + return resolved +``` + +- [ ] **Step 4: Accept `scenario` in the input validation** + +In `bw_timex/validation.py`, add the field and its validator to `TimexLCAInputs`: + +```python + scenario: Optional[dict] = None +``` + +```python + @field_validator("scenario") + @classmethod + def validate_scenario(cls, v: Optional[dict]) -> Optional[dict]: + if v is None: + return v + if not v: + raise ValueError("scenario must be a non-empty dictionary if provided.") + for key, value in v.items(): + if not isinstance(key, str): + raise ValueError( + f"scenario keys must be strings (database metadata keys), got " + f"{type(key).__name__}." + ) + if not isinstance(value, (str, int, float, bool, list, tuple)): + raise ValueError( + f"scenario values must be scalars or lists of scalars, got " + f"{type(value).__name__} for key '{key}'." + ) + return v +``` + +And pass it in `timex_lca.py`, where `TimexLCAInputs` is instantiated: + +```python + TimexLCAInputs( + demand=self.demand, + method=self.method, + database_dates=self.database_dates, + scenario=self.scenario, + ) +``` + +- [ ] **Step 5: Update the class docstring example** + +In the `Examples` block of the `TimexLCA` class docstring, put the metadata path first and keep the explicit mapping as the alternative: + +```python + from bw_timex import TimexLCA, set_database_metadata + + demand = {("my_foreground_database", "my_process"): 1} + method = ("some_method_family", "some_category", "some_method") + + # Databases exported by premise already know the point in time they + # represent. For your own databases, say so once: + set_database_metadata("my_background_database_one", representative_time=datetime(2020, 1, 1)) + set_database_metadata("my_background_database_two", representative_time=datetime(2030, 1, 1)) + + tlca = TimexLCA(demand, method) + + # ... or map the databases explicitly, which then replaces the metadata: + tlca = TimexLCA( + demand, + method, + database_dates={ + "my_background_database_one": datetime(2020, 1, 1), + "my_background_database_two": datetime(2030, 1, 1), + # Several databases may share the same date, e.g. to keep your own + # modified copies of background processes in their own database: + "my_modified_background_2020": datetime(2020, 1, 1), + "my_foreground_database": "dynamic", + }, + ) + + tlca.build_timeline() # has many optional arguments + tlca.lci() + tlca.static_lcia() + print(tlca.static_score) + # also available: "GWP", "pGWP", "pGTP", "prospective_radiative_forcing" + tlca.dynamic_lcia(metric="radiative_forcing") + print(tlca.dynamic_score) +``` + +- [ ] **Step 6: Run the new tests** + +Run: `.venv/bin/pytest tests/test_database_metadata.py -v` +Expected: PASS (33 tests) + +- [ ] **Step 7: Run the whole suite to prove nothing regressed** + +Run: `.venv/bin/pytest -x -q` +Expected: PASS, same count as on `main` plus the new tests. Every existing test passes `database_dates`, so the exclusive branch must keep them green. + +- [ ] **Step 8: Commit** + +```bash +git add bw_timex/timex_lca.py bw_timex/validation.py tests/test_database_metadata.py +git commit -m "feat: read database timing from metadata by default in TimexLCA" +``` + +--- + +### Task 5: Documentation + +**Files:** +- Create: `docs/content/background_database_metadata.md` +- Create: `docs/api/database_metadata.md` +- Modify: `zensical.toml` (User Guide nav, API nav) +- Modify: `docs/content/getting_started/quickstart.md:58-70`, `:96`, `:128-132` +- Modify: `docs/content/getting_started/adding_temporal_information.md:250-282` +- Modify: `docs/content/getting_started/build_process_timeline.md:11-21` +- Modify: `CHANGES.md` + +**Interfaces:** +- Consumes: `set_database_metadata`, `TimexLCA(scenario=...)` from Tasks 1–4. +- Produces: no code. + +- [ ] **Step 1: Write the new reference page** + +Create `docs/content/background_database_metadata.md`: + +````markdown +--- +icon: lucide/calendar-clock +tags: + - background databases +--- + +# What a database represents + +`bw_timex` needs to know which point in time each background database stands for. +That information lives in the database's own Brightway metadata, so it only has to +be recorded once - not in every script. + +```python +import bw2data as bd + +bd.databases["ei_cutoff_3.10.1_remind_SSP2-PkBudg500_2050"] +``` + +```python +{ + # written by brightway + "format": "Ecoinvent XML", "backend": "sqlite", "number": 43648, ..., + # written by premise + "premise_version": "2.4.9.1", + "iam_model": "remind", + "pathway": "SSP2-PkBudg500", + "representative_time": "2050-01-01T00:00:00", + "ecoinvent_version": "3.10.1", + "system_model": "cutoff", +} +``` + +Only `representative_time` is required. `TimexLCA` reads it from every database of +your project, so a study on premise databases needs no timing argument at all: + +```python +tlca = TimexLCA(demand={("foreground", "A"): 1}, method=("our", "method")) +``` + +!!! info "premise version" + + premise writes this metadata from the version following 2.4.9.2 onwards. For + databases written by an earlier version, set it yourself as shown below - it is + a one-liner per database. + +## Setting it yourself + +For databases you built yourself, use +[`set_database_metadata`][bw_timex.database_metadata.set_database_metadata]: + +```python +from datetime import datetime +from bw_timex import set_database_metadata + +set_database_metadata("background_2020", representative_time=datetime(2020, 1, 1)) +set_database_metadata("background_2030", representative_time=datetime(2030, 1, 1)) +``` + +The value is stored as an ISO 8601 string, because Brightway keeps database +metadata as JSON. You only do this once per database: it is stored in the project, +not in your script. + +Your foreground doesn't represent a point in time - its processes get distributed +over time. `TimexLCA` treats the databases holding your functional unit as +`"dynamic"` automatically, but you can also say so explicitly: + +```python +set_database_metadata("foreground", representative_time="dynamic") +``` + +## Several databases for the same point in time + +More than one database may carry the same date. This is useful when you modify +background processes: keep the modified copies in your own database per point in +time, instead of writing them into ecoinvent or premise. + +```python +set_database_metadata("my_background_2020", representative_time=datetime(2020, 1, 1)) +set_database_metadata("my_background_2030", representative_time=datetime(2030, 1, 1)) +``` + +For each process, `bw_timex` interpolates only between the databases that actually +contain it, matched on `name`, `reference product` and `location`. + +## Choosing a scenario + +A project often holds more than one IAM scenario. `bw_timex` refuses to guess and +tells you what it found: + +``` +Several background scenarios found in this project: + pathway=SSP2-PkBudg500: ei_..._2030, ei_..._2040, ei_..._2050 + pathway=SSP2-Base: ei_..._2030, ei_..._2040, ei_..._2050 +Select one, e.g. scenario={'pathway': '...'}, or map the databases explicitly with +`database_dates`. +``` + +Pick one with the `scenario` argument, which filters the databases on their +metadata: + +```python +tlca = TimexLCA( + demand={("foreground", "A"): 1}, + method=("our", "method"), + scenario={"pathway": "SSP2-PkBudg500"}, +) +``` + +Any metadata key works - `iam_model`, `pathway`, `system_model`, +`ecoinvent_version`, `premise_version`, or anything you set yourself. Databases +that don't carry the key at all (your foreground, your own vintages) are never +filtered out. + +Comparing scenarios is then a loop over filters: + +```python +scores = {} +for pathway in ("SSP2-Base", "SSP2-PkBudg500"): + tlca = TimexLCA(demand, method, scenario={"pathway": pathway}) + tlca.build_timeline() + tlca.lci() + tlca.static_lcia() + scores[pathway] = tlca.static_score +``` + +!!! warning "Superstructure databases" + + Databases holding several scenarios at once (premise superstructure or + scenario-array exports) are skipped: they have no single technosphere per point + in time. Use one database per scenario and year. + +## Mapping the databases explicitly + +`database_dates` still does what it always did, and takes over completely: when you +pass it, metadata is not read at all and only the databases you list are used. + +```python +tlca = TimexLCA( + demand={("foreground", "A"): 1}, + method=("our", "method"), + database_dates={ + "background": datetime(2020, 1, 1), + "background_2030": datetime(2030, 1, 1), + "foreground": "dynamic", + }, +) +``` + +Use it when you want to restrict a calculation to a subset of the databases in your +project, or when a database's metadata is wrong and you don't want to change it. +```` + +- [ ] **Step 2: Add both pages to the nav** + +In `zensical.toml`, add the User Guide entry after the Walkthrough block (after the line `]},` that closes `Walkthrough`, before `{ "What LCA should I do?" ...`): + +```toml + { "What a database represents" = "content/background_database_metadata.md" }, +``` + +And in the API nav block, next to the other API pages: + +```toml + { "Database metadata" = "api/database_metadata.md" }, +``` + +Create `docs/api/database_metadata.md`, following `docs/api/utils.md`: + +```markdown +--- +icon: lucide/calendar-clock +tags: + - api +--- + +# Database metadata + +Reading and writing what a Brightway database represents: the point in time +(`representative_time`) and, for prospective databases, the scenario it was built +for. + +::: bw_timex.database_metadata +``` + +- [ ] **Step 3: Update the quickstart** + +In `docs/content/getting_started/quickstart.md`, replace step 3 and the `TimexLCA` call: + +```python +# 3. Say what your time-specific background databases represent +# (premise databases already know - skip this for them) +set_database_metadata("background", representative_time=datetime(2020, 1, 1)) +set_database_metadata("background_2030", representative_time=datetime(2030, 1, 1)) + +# 4. Create the TimexLCA object +tlca = TimexLCA( + demand={("foreground", "A"): 1}, + method=("our", "method"), +) +``` + +Add `set_database_metadata` to the `from bw_timex import ...` line at the top of that +code block. In the cheat sheet, change the background row to: + +``` +| *How the background changes* over time | one database per point in time, each with `representative_time` metadata | background databases | +``` + +And replace the trailing paragraph about `database_dates` (lines 128-132) with: + +```markdown +Absolute dates (`dtype="datetime64[s]"`) are also allowed in a `TemporalDistribution`, +e.g. for the timing of the functional unit itself. Relative dates +(`dtype="timedelta64[Y]"`) are relative to the consuming process. Several databases may +represent the same point in time, e.g. if you keep modified copies of background +processes in their own database instead of writing them into the shared vintage. See +[What a database represents](../background_database_metadata.md) for scenario selection +and for mapping databases explicitly with `database_dates`. +``` + +- [ ] **Step 4: Update walkthrough step 1** + +In `docs/content/getting_started/adding_temporal_information.md`, replace the paragraph and code block at lines 250-262 with: + +````markdown +So, as you can see, the processes at specific time steps reside within a separate normal +Brightway database. `bw_timex` picks these up automatically, as long as each database +says which point in time it represents: + +```python +from datetime import datetime +from bw_timex import set_database_metadata + +set_database_metadata("background", representative_time=datetime(2020, 1, 1)) +set_database_metadata("background_2030", representative_time=datetime(2030, 1, 1)) +``` + +You only do this once per database - it is stored in your Brightway project. Databases +exported by [premise](https://premise.readthedocs.io/en/latest/introduction.html) bring +this metadata with them, so there is nothing to do for those. The foreground doesn't +represent a specific point in time and is distributed over time instead; `bw_timex` +treats the databases holding your functional unit that way automatically. +```` + +Replace the code block in the "Several databases for the same point in time" section +(lines 274-282) with: + +```python +set_database_metadata("ecoinvent_2020", representative_time=datetime(2020, 1, 1)) +set_database_metadata("ecoinvent_2030", representative_time=datetime(2030, 1, 1)) +set_database_metadata("my_background_2020", representative_time=datetime(2020, 1, 1)) +set_database_metadata("my_background_2030", representative_time=datetime(2030, 1, 1)) +``` + +- [ ] **Step 5: Update walkthrough step 2** + +In `docs/content/getting_started/build_process_timeline.md`, replace lines 11-21 with: + +````markdown +With all the temporal information prepared, we can now instantiate our TimexLCA object. +This is just like a normal Brightway LCA object - the timing of the background databases +comes from their metadata: + +```python +from bw_timex import TimexLCA + +tlca = TimexLCA( + demand={("foreground", "A"): 1}, + method=("our", "method"), +) +``` + +If your project holds several scenarios, select one with +`scenario={"pathway": "SSP2-PkBudg500"}`; to map the databases by hand instead, pass +`database_dates`. Both are covered in +[What a database represents](../background_database_metadata.md). +```` + +- [ ] **Step 6: Add the changelog entry** + +Under `## [Unreleased]` in `CHANGES.md`: + +```markdown +* Added `representative_time` database metadata as the default timing source: `TimexLCA` now maps background databases to points in time by reading their Brightway metadata (as written by premise), making `database_dates` optional ([#217](https://github.com/brightway-lca/bw_timex/issues/217)) +* Added `set_database_metadata` to record what a database represents (`representative_time`, and scenario fields such as `iam_model` or `pathway`) for databases that don't bring the metadata themselves +* Added `TimexLCA(scenario={...})` to select one background scenario when a project holds several; `TimexLCA` raises and lists the scenarios it found if the choice is ambiguous +``` + +- [ ] **Step 7: Verify the docs build** + +Run: `.venv/bin/python -m zensical build 2>&1 | tail -20` +Expected: build succeeds, no warning about `background_database_metadata.md` or `api/database_metadata.md` being missing from the nav. If `zensical` is not installed in the venv, run `.venv/bin/python -c "import tomllib, pathlib; tomllib.loads(pathlib.Path('zensical.toml').read_text())"` to at least prove the nav edit is valid TOML, and say in the commit that the build was not run. + +- [ ] **Step 8: Commit** + +```bash +git add docs zensical.toml CHANGES.md +git commit -m "docs: document representative_time database metadata" +``` + +--- + +### Task 6: Tutorial notebooks + +**Files:** +- Modify: `notebooks/tutorials/1_getting_started.ipynb` +- Modify: `notebooks/tutorials/2_electric_vehicle_from_scratch.ipynb` +- Modify: `notebooks/tutorials/3_dynamic_characterization.ipynb` +- Modify: `notebooks/tutorials/4_import_model_from_excel.ipynb` + +**Interfaces:** +- Consumes: `set_database_metadata`, `TimexLCA()` without `database_dates` from Tasks 1–4. +- Produces: no code. + +These notebooks build their own small databases, so they can be re-executed. + +- [ ] **Step 1: Find every occurrence** + +Run: `grep -n "database_dates" notebooks/tutorials/*.ipynb` +Note which cells build the mapping and which pass it to `TimexLCA`. + +- [ ] **Step 2: Edit the cells** + +In each notebook, use `NotebookEdit` to: +1. Replace the cell that builds `database_dates` with `set_database_metadata` calls, one per background database, keeping the surrounding markdown explanation in sync (it must no longer say "we define a dictionary that maps databases to dates"). +2. Drop the `database_dates=database_dates` argument from the `TimexLCA(...)` call. +3. Add `set_database_metadata` to the `from bw_timex import ...` cell. + +Pattern: + +```python +# before +database_dates = { + "db_2020": datetime.strptime("2020", "%Y"), + "db_2030": datetime.strptime("2030", "%Y"), + "foreground": "dynamic", +} +tlca = TimexLCA(demand={fu.key: 1}, method=method, database_dates=database_dates) + +# after +set_database_metadata("db_2020", representative_time=datetime(2020, 1, 1)) +set_database_metadata("db_2030", representative_time=datetime(2030, 1, 1)) +tlca = TimexLCA(demand={fu.key: 1}, method=method) +``` + +- [ ] **Step 3: Re-execute each notebook** + +Run, one notebook at a time: + +```bash +.venv/bin/jupyter nbconvert --to notebook --execute --inplace notebooks/tutorials/1_getting_started.ipynb +``` + +Expected: completes without error. If a notebook needs data that isn't in the repo, do +not execute it — leave the stored outputs, and note that in the commit message. + +- [ ] **Step 4: Check the diff for accidental churn** + +Run: `git diff --stat notebooks/tutorials` +Expected: only the edited cells plus their re-executed outputs. If execution rewrote +every cell id or bumped unrelated metadata, restore and re-run with +`--ClearMetadataPreprocessor.enabled=True` off, keeping the diff readable. + +- [ ] **Step 5: Commit** + +```bash +git add notebooks/tutorials +git commit -m "docs: use database metadata instead of database_dates in the tutorials" +``` + +--- + +### Task 7: Remaining notebooks + +**Files:** +- Modify: `notebooks/advanced/background_temporal_distributions.ipynb` +- Modify: `notebooks/advanced/uncertainty_with_datapackages.ipynb` +- Modify: `notebooks/advanced/background_temporal_distributions_premise.ipynb` +- Modify: `notebooks/teaching/ev_walkthrough_premise.ipynb` +- Modify: `notebooks/teaching/exercise_ev_vs_petrol_solutions.ipynb` +- Modify: `notebooks/examples/electric_vehicle_premise.ipynb` +- Modify: `notebooks/examples/electric_vehicle_premise_detailed.ipynb` +- Modify: `notebooks/development/benchmarking.ipynb` +- **Do not touch:** `notebooks/examples/paper_case_study.ipynb` + +**Interfaces:** +- Consumes: `set_database_metadata`, `TimexLCA(scenario=...)` from Tasks 1–4. +- Produces: no code. + +The first two build their own databases and can be re-executed. The rest need premise +or ecoinvent databases that aren't in the repo: edit the cell sources only and leave +the stored outputs alone. + +- [ ] **Step 1: Edit the two self-contained notebooks** + +`background_temporal_distributions.ipynb` and `uncertainty_with_datapackages.ipynb`: +same replacement as Task 6 Step 2, then re-execute: + +```bash +.venv/bin/jupyter nbconvert --to notebook --execute --inplace notebooks/advanced/background_temporal_distributions.ipynb +.venv/bin/jupyter nbconvert --to notebook --execute --inplace notebooks/advanced/uncertainty_with_datapackages.ipynb +``` + +- [ ] **Step 2: Edit the premise/ecoinvent notebooks** + +For each of the six remaining notebooks, replace the `database_dates` cell. These use +premise databases, which carry the metadata already, so the mapping usually +disappears entirely: + +```python +# before +database_dates = { + "ei312_REMIND-EU_SSP2_NDC_2020": datetime.strptime("2020", "%Y"), + "ei312_REMIND-EU_SSP2_NDC_2030": datetime.strptime("2030", "%Y"), + "foreground": "dynamic", +} +tlca = TimexLCA(demand={fu.key: 1}, method=method, database_dates=database_dates) + +# after +# The premise databases carry the point in time they represent in their +# metadata, so bw_timex finds them by itself. +tlca = TimexLCA(demand={fu.key: 1}, method=method) +``` + +Two things to get right per notebook: +- If the notebook creates its own modified copies of background processes in extra + databases (the electric-vehicle notebooks do, e.g. `..., without EOL` copies), those + copies need `set_database_metadata(..., representative_time=...)` with the same date + as the vintage they were copied from, or they drop out of the mapping. +- If the notebook's project could hold more than one pathway, show the `scenario` + argument in the markdown right below, e.g. + `scenario={"pathway": "SSP2-PkBudg500"}`. + +Update the surrounding markdown text wherever it explains `database_dates`. + +- [ ] **Step 3: Verify no notebook lost its outputs** + +Run: `git diff --stat notebooks` +Expected: for the six premise notebooks, only source cells change - no `outputs` churn. + +- [ ] **Step 4: Confirm the paper case study is untouched** + +Run: `git status --porcelain notebooks/examples/paper_case_study.ipynb` +Expected: no output. + +- [ ] **Step 5: Commit** + +```bash +git add notebooks +git commit -m "docs: use database metadata instead of database_dates in the notebooks" +``` + +--- + +### Task 8: Final verification + +**Files:** none + +- [ ] **Step 1: Full test suite** + +Run: `.venv/bin/pytest -q` +Expected: all pass. + +- [ ] **Step 2: Nothing still teaches the old default** + +Run: `grep -rn "database_dates" --include="*.md" --include="*.ipynb" docs notebooks | grep -v paper_case_study | grep -v superpowers` +Expected: only the places that deliberately document `database_dates` as the explicit +override — `background_database_metadata.md`, the quickstart's closing paragraph, and +step 2's pointer. Anything else is a leftover. + +- [ ] **Step 3: Public API test still describes the namespace** + +Run: `.venv/bin/pytest tests/test_public_api.py -v` +Expected: PASS. If it asserts an exact `__all__`, add `set_database_metadata` to it. + +- [ ] **Step 4: Commit any fixes and push the branch** + +```bash +git add -A +git commit -m "fix: address leftovers from the metadata migration" +git push -u origin feat/representative-time-metadata +``` From 0fb6c2ecaf59dd520b9d65f5aa3cbf9b59607899 Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 10:24:43 +0200 Subject: [PATCH 04/24] feat: add set_database_metadata to store what a database represents --- bw_timex/__init__.py | 2 + bw_timex/database_metadata.py | 167 ++++++++++++++++++++++++++++++++ bw_timex/validation.py | 24 +++++ tests/test_database_metadata.py | 68 +++++++++++++ 4 files changed, 261 insertions(+) create mode 100644 bw_timex/database_metadata.py create mode 100644 tests/test_database_metadata.py diff --git a/bw_timex/__init__.py b/bw_timex/__init__.py index 6ea64abc..286e4d61 100644 --- a/bw_timex/__init__.py +++ b/bw_timex/__init__.py @@ -5,6 +5,7 @@ ) from ._lci_cache import clear_background_lci_cache +from .database_metadata import set_database_metadata from .dynamic_biosphere_builder import DynamicBiosphereBuilder from .edge_extractor import EdgeExtractor from .helper_classes import SetList @@ -44,4 +45,5 @@ "get_temporal_evolution_factor", "interactive_td_widget", "plot_characterized_inventory_as_waterfall", + "set_database_metadata", ] diff --git a/bw_timex/database_metadata.py b/bw_timex/database_metadata.py new file mode 100644 index 00000000..ec68fe53 --- /dev/null +++ b/bw_timex/database_metadata.py @@ -0,0 +1,167 @@ +"""Read and write what a Brightway database represents. + +`bw_timex` needs to know which point in time each background database stands +for. That information is stored in the database's own Brightway metadata +(`bw2data.databases[name]`), where premise also writes it when it exports a +prospective database: + +```python +{ + "premise_version": "2.4.9.1", + "iam_model": "remind", + "pathway": "SSP2-PkBudg500", + "representative_time": "2050-01-01T00:00:00", + "ecoinvent_version": "3.10.1", + "system_model": "cutoff", +} +``` + +Brightway stores this mapping as JSON, so dates are kept as ISO 8601 strings. +""" + +from __future__ import annotations + +import json +from datetime import datetime +from typing import Any + +import bw2data as bd + +REPRESENTATIVE_TIME = "representative_time" +SCENARIOS = "scenarios" +DYNAMIC = "dynamic" + +#: Metadata keys that identify the scenario a database represents. Two +#: databases differing in any of these represent different scenarios. +#: `premise_version` is deliberately absent: re-running premise on the same +#: pathway must not look like a second scenario. +SCENARIO_SIGNATURE_KEYS = ( + "iam_model", + "pathway", + "system_model", + "ecoinvent_version", + "external_scenarios", +) + +#: Keys Brightway maintains itself, filtered out when reporting to the user +#: which metadata a project's databases carry. +BRIGHTWAY_METADATA_KEYS = frozenset( + { + "backend", + "depends", + "dirty", + "format", + "geocollections", + "modified", + "number", + "processed", + "searchable", + } +) + + +def _database_name(database: Any) -> str: + """The name of a database given either as a name or as a `bd.Database`.""" + name = getattr(database, "name", database) + if not isinstance(name, str): + raise ValueError( + f"database must be a database name or a bw2data Database, got " + f"{type(database).__name__}." + ) + return name + + +def _normalize_representative_time(value: Any, database: str) -> datetime | str: + """Turn a stored `representative_time` into a datetime or `"dynamic"`.""" + if isinstance(value, datetime): + return value + if isinstance(value, str): + if value == DYNAMIC: + return DYNAMIC + try: + return datetime.fromisoformat(value) + except ValueError: + raise ValueError( + f"Database '{database}' has an invalid `{REPRESENTATIVE_TIME}` " + f"metadata value: {value!r}. Expected an ISO 8601 datetime string " + f"(e.g. '2030-01-01'), a datetime, or '{DYNAMIC}'." + ) from None + raise ValueError( + f"Database '{database}' has an invalid `{REPRESENTATIVE_TIME}` metadata " + f"value of type {type(value).__name__}: {value!r}. Expected an ISO 8601 " + f"datetime string, a datetime, or '{DYNAMIC}'." + ) + + +def set_database_metadata(database: str | bd.Database, **metadata) -> dict: + """ + Store what a database represents in its Brightway metadata. + + Use this for databases that don't bring the metadata themselves, e.g. + databases you built yourself or that were exported by a premise version + older than the one writing scenario metadata. `TimexLCA` reads + `representative_time` from all databases of the project to map them to + points in time, so this replaces passing `database_dates`. + + Parameters + ---------- + database : str or bw2data.Database + Name of the database, or the database itself. Must be registered. + **metadata : + Metadata to store. `representative_time` accepts a `datetime`, an ISO + 8601 string, or `"dynamic"` and is always stored as a string, because + Brightway serializes database metadata to JSON. Any other key is stored + as given and must be JSON-serializable. Keys that premise writes, and + that `TimexLCA(scenario=...)` can select on, are `iam_model`, + `pathway`, `system_model`, `ecoinvent_version` and `premise_version`. + + Returns + ------- + dict + The database's metadata after the update. + + Examples + -------- + ```python + set_database_metadata("db_2030", representative_time=datetime(2030, 1, 1)) + set_database_metadata( + "my_2050_variant", + representative_time="2050-01-01", + iam_model="remind", + pathway="SSP2-PkBudg500", + ) + ``` + """ + from .validation import DatabaseMetadataInputs + + name = _database_name(database) + DatabaseMetadataInputs(database=name, metadata=metadata) + + if name not in bd.databases: + raise ValueError( + f"Database '{name}' is not registered in this Brightway project. " + f"Available databases: {sorted(bd.databases)}." + ) + + serialized = {} + for key, value in metadata.items(): + if key == REPRESENTATIVE_TIME: + normalized = _normalize_representative_time(value, name) + if isinstance(value, str): + # already a string (an ISO 8601 date or "dynamic"): store as given + serialized[key] = value + else: + serialized[key] = normalized.isoformat() + continue + try: + json.dumps(value) + except TypeError: + raise ValueError( + f"Metadata value for '{key}' is not JSON-serializable: {value!r}. " + f"Brightway stores database metadata as JSON." + ) from None + serialized[key] = value + + bd.databases[name].update(serialized) + bd.databases.flush() + return bd.databases[name] diff --git a/bw_timex/validation.py b/bw_timex/validation.py index 215dd81c..05cbf705 100644 --- a/bw_timex/validation.py +++ b/bw_timex/validation.py @@ -243,3 +243,27 @@ def validate_bio_flows(cls, v: list) -> list: f"bio_flows must contain integer database IDs, got {type(item).__name__}: {item}." ) return v + + +class DatabaseMetadataInputs(BaseModel): + """Validates inputs to set_database_metadata""" + + model_config = {"arbitrary_types_allowed": True} + + database: str + metadata: dict + + @field_validator("metadata") + @classmethod + def validate_metadata(cls, v: dict) -> dict: + if not v: + raise ValueError( + "Provide at least one metadata field, e.g. " + "`representative_time=datetime(2030, 1, 1)`." + ) + for key in v: + if not isinstance(key, str): + raise ValueError( + f"Metadata keys must be strings, got {type(key).__name__}: {key}." + ) + return v diff --git a/tests/test_database_metadata.py b/tests/test_database_metadata.py new file mode 100644 index 00000000..c2fb37a2 --- /dev/null +++ b/tests/test_database_metadata.py @@ -0,0 +1,68 @@ +"""Tests for reading and writing what a Brightway database represents.""" + +from datetime import datetime + +import bw2data as bd +import pytest + +from bw_timex import set_database_metadata + +# ─── Tests for set_database_metadata ─── + + +@pytest.mark.usefixtures("temporal_grouping_db_monthly") +class TestSetDatabaseMetadata: + + def test_datetime_is_stored_as_iso_string(self): + set_database_metadata("db_2022", representative_time=datetime(2022, 1, 1)) + assert bd.databases["db_2022"]["representative_time"] == "2022-01-01T00:00:00" + + def test_iso_string_is_stored_as_given(self): + set_database_metadata("db_2022", representative_time="2022-01-01") + assert bd.databases["db_2022"]["representative_time"] == "2022-01-01" + + def test_dynamic_is_allowed(self): + set_database_metadata("foreground", representative_time="dynamic") + assert bd.databases["foreground"]["representative_time"] == "dynamic" + + def test_scenario_fields_are_stored(self): + set_database_metadata( + "db_2022", + representative_time=datetime(2022, 1, 1), + iam_model="remind", + pathway="SSP2-PkBudg500", + ) + assert bd.databases["db_2022"]["iam_model"] == "remind" + assert bd.databases["db_2022"]["pathway"] == "SSP2-PkBudg500" + + def test_database_object_is_accepted(self): + set_database_metadata( + bd.Database("db_2022"), representative_time=datetime(2022, 1, 1) + ) + assert bd.databases["db_2022"]["representative_time"] == "2022-01-01T00:00:00" + + def test_existing_metadata_is_kept(self): + before = bd.databases["db_2022"]["backend"] + set_database_metadata("db_2022", representative_time=datetime(2022, 1, 1)) + assert bd.databases["db_2022"]["backend"] == before + + def test_survives_flush_and_reload(self): + set_database_metadata("db_2022", representative_time=datetime(2022, 1, 1)) + bd.databases.__init__() # re-read from disk + assert bd.databases["db_2022"]["representative_time"] == "2022-01-01T00:00:00" + + def test_unregistered_database_raises(self): + with pytest.raises(ValueError, match="not registered"): + set_database_metadata("no_such_db", representative_time=datetime(2022, 1, 1)) + + def test_unparseable_representative_time_raises(self): + with pytest.raises(ValueError, match="representative_time"): + set_database_metadata("db_2022", representative_time="whenever") + + def test_non_serializable_value_raises(self): + with pytest.raises(ValueError, match="JSON"): + set_database_metadata("db_2022", pathway=object()) + + def test_no_metadata_raises(self): + with pytest.raises(ValueError, match="at least one"): + set_database_metadata("db_2022") From 84b8c47454fa73765f0852e9e2390e147712a34c Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 10:28:47 +0200 Subject: [PATCH 05/24] feat: resolve database dates from representative_time metadata --- bw_timex/database_metadata.py | 55 +++++++++++++++++++++++++++++++++ tests/test_database_metadata.py | 48 ++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/bw_timex/database_metadata.py b/bw_timex/database_metadata.py index ec68fe53..dc4a47a6 100644 --- a/bw_timex/database_metadata.py +++ b/bw_timex/database_metadata.py @@ -26,6 +26,7 @@ from typing import Any import bw2data as bd +from loguru import logger REPRESENTATIVE_TIME = "representative_time" SCENARIOS = "scenarios" @@ -165,3 +166,57 @@ def set_database_metadata(database: str | bd.Database, **metadata) -> dict: bd.databases[name].update(serialized) bd.databases.flush() return bd.databases[name] + + +def _candidate_databases() -> dict[str, dict]: + """Registered databases that declare a `representative_time`. + + Multi-scenario databases (superstructure and scenario-array exports, which + carry a `scenarios` list) are skipped: `bw_timex` needs one technosphere + per point in time and cannot pick a scenario out of such a database. They + can still be used by naming them in `database_dates`. + """ + candidates = {} + for name in bd.databases: + metadata = bd.databases[name] + if REPRESENTATIVE_TIME not in metadata: + continue + if metadata.get(SCENARIOS): + logger.info( + f"Skipping database '{name}': it holds " + f"{len(metadata[SCENARIOS])} scenarios, so the point in time it " + f"represents is ambiguous. Map it explicitly with `database_dates` " + f"if you want to use it anyway." + ) + continue + candidates[name] = metadata + return candidates + + +def resolve_database_dates_from_metadata( + scenario: dict | None = None, +) -> dict[str, datetime | str]: + """ + Map the databases of the current project to the points in time they represent. + + Reads the `representative_time` metadata of every registered database (see + [`set_database_metadata`][bw_timex.database_metadata.set_database_metadata]). + + Parameters + ---------- + scenario : dict, optional + Metadata a database must match to be included, e.g. + `{"iam_model": "remind", "pathway": "SSP2-PkBudg500"}`. Databases that + don't declare a filtered key at all are kept. + + Returns + ------- + dict + Mapping of database name to `datetime` or `"dynamic"`, ready to be used + as `TimexLCA.database_dates`. + """ + candidates = _candidate_databases() + return { + name: _normalize_representative_time(metadata[REPRESENTATIVE_TIME], name) + for name, metadata in candidates.items() + } diff --git a/tests/test_database_metadata.py b/tests/test_database_metadata.py index c2fb37a2..dc2c508e 100644 --- a/tests/test_database_metadata.py +++ b/tests/test_database_metadata.py @@ -6,6 +6,7 @@ import pytest from bw_timex import set_database_metadata +from bw_timex.database_metadata import resolve_database_dates_from_metadata # ─── Tests for set_database_metadata ─── @@ -66,3 +67,50 @@ def test_non_serializable_value_raises(self): def test_no_metadata_raises(self): with pytest.raises(ValueError, match="at least one"): set_database_metadata("db_2022") + + +# ─── Tests for resolving database dates from metadata ─── + + +@pytest.mark.usefixtures("temporal_grouping_db_monthly") +class TestResolveFromMetadata: + + def test_empty_project_metadata_resolves_to_nothing(self): + assert resolve_database_dates_from_metadata() == {} + + def test_iso_strings_resolve_to_datetimes(self): + set_database_metadata("db_2022", representative_time="2022-01-01") + set_database_metadata("db_2024", representative_time="2024-01-01") + assert resolve_database_dates_from_metadata() == { + "db_2022": datetime(2022, 1, 1), + "db_2024": datetime(2024, 1, 1), + } + + def test_dynamic_metadata_resolves_to_dynamic(self): + set_database_metadata("db_2022", representative_time="2022-01-01") + set_database_metadata("foreground", representative_time="dynamic") + resolved = resolve_database_dates_from_metadata() + assert resolved["foreground"] == "dynamic" + assert resolved["db_2022"] == datetime(2022, 1, 1) + + def test_databases_without_metadata_are_ignored(self): + set_database_metadata("db_2022", representative_time="2022-01-01") + assert set(resolve_database_dates_from_metadata()) == {"db_2022"} + + def test_multi_scenario_database_is_skipped(self): + set_database_metadata("db_2022", representative_time="2022-01-01") + set_database_metadata( + "db_2024", + representative_time="2024-01-01", + scenarios=[ + {"pathway": "SSP2-Base", "representative_time": "2024-01-01"}, + {"pathway": "SSP2-PkBudg500", "representative_time": "2024-01-01"}, + ], + ) + assert set(resolve_database_dates_from_metadata()) == {"db_2022"} + + def test_invalid_metadata_value_raises_naming_the_database(self): + bd.databases["db_2022"]["representative_time"] = "whenever" + bd.databases.flush() + with pytest.raises(ValueError, match="db_2022"): + resolve_database_dates_from_metadata() From 4d8996447431f823a944f808a5a8572a97724298 Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 10:34:19 +0200 Subject: [PATCH 06/24] feat: select background scenarios by database metadata --- bw_timex/database_metadata.py | 91 ++++++++++++++++++++++++++++++++- tests/test_database_metadata.py | 78 ++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 1 deletion(-) diff --git a/bw_timex/database_metadata.py b/bw_timex/database_metadata.py index dc4a47a6..1f6f1863 100644 --- a/bw_timex/database_metadata.py +++ b/bw_timex/database_metadata.py @@ -22,6 +22,7 @@ from __future__ import annotations import json +from collections import defaultdict from datetime import datetime from typing import Any @@ -193,6 +194,76 @@ def _candidate_databases() -> dict[str, dict]: return candidates +def _as_set(value: Any) -> set: + """Compare list-valued metadata (e.g. `external_scenarios`) order-insensitively.""" + if isinstance(value, (list, tuple, set)): + return {str(item) for item in value} + return {str(value)} + + +def _values_match(declared: Any, wanted: Any) -> bool: + if isinstance(declared, (list, tuple, set)) or isinstance(wanted, (list, tuple, set)): + return _as_set(declared) == _as_set(wanted) + return str(declared) == str(wanted) + + +def _check_filter_keys(scenario: dict, candidates: dict[str, dict]) -> None: + """Reject filter keys no database declares, instead of silently matching nothing.""" + declared = set() + for metadata in candidates.values(): + declared.update(set(metadata) - BRIGHTWAY_METADATA_KEYS) + unknown = sorted(set(scenario) - declared) + if not unknown: + return + available = ", ".join(sorted(declared)) or "none" + raise ValueError( + f"No database in this project declares the metadata key(s) " + f"{unknown}. Keys declared by the databases of this project: {available}. " + f"Add the metadata with `bw_timex.set_database_metadata`, or check the " + f"spelling of your `scenario` filter." + ) + + +def _scenario_signature(metadata: dict) -> tuple: + return tuple( + (key, tuple(sorted(_as_set(metadata[key]))) if key in metadata else None) + for key in SCENARIO_SIGNATURE_KEYS + ) + + +def _format_scenario_sets(groups: dict[tuple, list[str]]) -> str: + """One line per scenario set, naming only the keys that actually differ.""" + differing = [ + key + for index, key in enumerate(SCENARIO_SIGNATURE_KEYS) + if len({signature[index][1] for signature in groups}) > 1 + ] + lines = [] + for signature, names in groups.items(): + values = dict(signature) + description = ", ".join( + f"{key}={', '.join(values[key]) if values[key] else 'not set'}" + for key in differing + ) + lines.append(f" {description}: {', '.join(sorted(names))}") + return "\n".join(lines) + + +def _check_unambiguous(candidates: dict[str, dict]) -> None: + groups = defaultdict(list) + for name, metadata in candidates.items(): + if any(key in metadata for key in SCENARIO_SIGNATURE_KEYS): + groups[_scenario_signature(metadata)].append(name) + if len(groups) <= 1: + return + raise ValueError( + f"Several background scenarios found in this project:\n" + f"{_format_scenario_sets(groups)}\n" + f"Select one, e.g. scenario={{'pathway': '...'}}, or map the databases " + f"explicitly with `database_dates`." + ) + + def resolve_database_dates_from_metadata( scenario: dict | None = None, ) -> dict[str, datetime | str]: @@ -202,12 +273,19 @@ def resolve_database_dates_from_metadata( Reads the `representative_time` metadata of every registered database (see [`set_database_metadata`][bw_timex.database_metadata.set_database_metadata]). + If the project holds databases from more than one scenario (differing in + any of `SCENARIO_SIGNATURE_KEYS`, e.g. two premise pathways), this raises + a `ValueError` unless `scenario` narrows the selection down to one. + Parameters ---------- scenario : dict, optional Metadata a database must match to be included, e.g. `{"iam_model": "remind", "pathway": "SSP2-PkBudg500"}`. Databases that - don't declare a filtered key at all are kept. + don't declare a filtered key at all are kept, so a filter narrows down + an ambiguous project without excluding databases that carry no + scenario metadata (e.g. a dynamic foreground). Raises `ValueError` if + a filter key is not declared by any database in the project. Returns ------- @@ -216,6 +294,17 @@ def resolve_database_dates_from_metadata( as `TimexLCA.database_dates`. """ candidates = _candidate_databases() + if scenario: + _check_filter_keys(scenario, candidates) + candidates = { + name: metadata + for name, metadata in candidates.items() + if all( + key not in metadata or _values_match(metadata[key], wanted) + for key, wanted in scenario.items() + ) + } + _check_unambiguous(candidates) return { name: _normalize_representative_time(metadata[REPRESENTATIVE_TIME], name) for name, metadata in candidates.items() diff --git a/tests/test_database_metadata.py b/tests/test_database_metadata.py index dc2c508e..ef7cd83e 100644 --- a/tests/test_database_metadata.py +++ b/tests/test_database_metadata.py @@ -114,3 +114,81 @@ def test_invalid_metadata_value_raises_naming_the_database(self): bd.databases.flush() with pytest.raises(ValueError, match="db_2022"): resolve_database_dates_from_metadata() + + +# ─── Tests for scenario selection ─── + + +@pytest.mark.usefixtures("temporal_grouping_db_monthly") +class TestScenarioSelection: + + @pytest.fixture(autouse=True) + def two_scenarios(self, temporal_grouping_db_monthly): + """db_2022 and db_2024 hold the same year in two different pathways.""" + set_database_metadata( + "db_2022", + representative_time="2022-01-01", + iam_model="remind", + pathway="SSP2-PkBudg500", + premise_version="2.4.9.1", + ) + set_database_metadata( + "db_2024", + representative_time="2024-01-01", + iam_model="remind", + pathway="SSP2-Base", + premise_version="2.4.9.1", + ) + + def test_two_scenario_sets_without_selection_raises(self): + with pytest.raises(ValueError, match="Several background scenarios"): + resolve_database_dates_from_metadata() + + def test_error_names_the_differing_key_and_values(self): + with pytest.raises(ValueError) as excinfo: + resolve_database_dates_from_metadata() + message = str(excinfo.value) + assert "pathway" in message + assert "SSP2-PkBudg500" in message + assert "SSP2-Base" in message + # iam_model is identical in both sets, so it isn't part of the report + assert "iam_model" not in message + + def test_scenario_selects_one_set(self): + resolved = resolve_database_dates_from_metadata( + scenario={"pathway": "SSP2-Base"} + ) + assert resolved == {"db_2024": datetime(2024, 1, 1)} + + def test_databases_without_scenario_metadata_survive_the_filter(self): + set_database_metadata("foreground", representative_time="dynamic") + resolved = resolve_database_dates_from_metadata( + scenario={"pathway": "SSP2-Base"} + ) + assert resolved == { + "db_2024": datetime(2024, 1, 1), + "foreground": "dynamic", + } + + def test_several_filter_keys_are_combined(self): + resolved = resolve_database_dates_from_metadata( + scenario={"iam_model": "remind", "pathway": "SSP2-Base"} + ) + assert set(resolved) == {"db_2024"} + + def test_filter_matching_nothing_resolves_to_nothing(self): + assert resolve_database_dates_from_metadata( + scenario={"pathway": "SSP2-PkBudg1150"} + ) == {} + + def test_unknown_filter_key_raises_listing_available_keys(self): + with pytest.raises(ValueError) as excinfo: + resolve_database_dates_from_metadata(scenario={"pathwya": "SSP2-Base"}) + message = str(excinfo.value) + assert "pathwya" in message + assert "pathway" in message + + def test_same_scenario_from_two_premise_versions_is_not_ambiguous(self): + set_database_metadata("db_2024", pathway="SSP2-PkBudg500") + set_database_metadata("db_2024", premise_version="2.4.9.2") + assert set(resolve_database_dates_from_metadata()) == {"db_2022", "db_2024"} From 4586a57d2c2d79092183aab895476d261a544edd Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 10:41:23 +0200 Subject: [PATCH 07/24] feat: read database timing from metadata by default in TimexLCA --- bw_timex/timex_lca.py | 111 +++++++++++++++++++++++++------- bw_timex/validation.py | 21 ++++++ tests/test_database_metadata.py | 98 +++++++++++++++++++++++++++- 3 files changed, 205 insertions(+), 25 deletions(-) diff --git a/bw_timex/timex_lca.py b/bw_timex/timex_lca.py index 6c1c7068..0ae43a6d 100644 --- a/bw_timex/timex_lca.py +++ b/bw_timex/timex_lca.py @@ -31,6 +31,7 @@ from ._lci_cache import BACKGROUND_UNIT_LCI_CACHE, LCI_SOLVE_CACHE, NODES_CACHE FACTORIZE_SOLVES_THRESHOLD = 8 +from .database_metadata import resolve_database_dates_from_metadata from .dynamic_biosphere_builder import DynamicBiosphereBuilder from .helper_classes import InterDatabaseMapping, LazyActivity, TimeMappingDict from .matrix_modifier import MatrixModifier @@ -82,19 +83,32 @@ class TimexLCA: Examples -------- ```python + from bw_timex import TimexLCA, set_database_metadata + demand = {("my_foreground_database", "my_process"): 1} method = ("some_method_family", "some_category", "some_method") - database_dates = { - "my_background_database_one": datetime.strptime("2020", "%Y"), - "my_background_database_two": datetime.strptime("2030", "%Y"), - "my_background_database_three": datetime.strptime("2040", "%Y"), - # Several databases may share the same date, e.g. to keep your own - # modified copies of background processes in their own database: - "my_modified_background_2020": datetime.strptime("2020", "%Y"), - "my_foreground_database": "dynamic", - } - - tlca = TimexLCA(demand, method, database_dates) + + # Databases exported by premise already know the point in time they + # represent. For your own databases, say so once: + set_database_metadata("my_background_database_one", representative_time=datetime(2020, 1, 1)) + set_database_metadata("my_background_database_two", representative_time=datetime(2030, 1, 1)) + + tlca = TimexLCA(demand, method) + + # ... or map the databases explicitly, which then replaces the metadata: + tlca = TimexLCA( + demand, + method, + database_dates={ + "my_background_database_one": datetime(2020, 1, 1), + "my_background_database_two": datetime(2030, 1, 1), + # Several databases may share the same date, e.g. to keep your own + # modified copies of background processes in their own database: + "my_modified_background_2020": datetime(2020, 1, 1), + "my_foreground_database": "dynamic", + }, + ) + tlca.build_timeline() # has many optional arguments tlca.lci() tlca.static_lcia() @@ -110,6 +124,7 @@ def __init__( demand: dict, method: tuple, database_dates: dict = None, + scenario: dict = None, use_global_lci_cache: bool = True, ) -> None: """ @@ -126,10 +141,24 @@ def __init__( Tuple defining the LCIA method, such as `('foo', 'bar')` or default methods, such as `("EF v3.1", "climate change", "global warming potential (GWP100)")` database_dates : dict, optional - Dictionary mapping database names to dates. Several databases may - share the same date, e.g. to keep your own modified copies of - background processes in their own database instead of writing - them into the shared background database for that vintage. + Dictionary mapping database names to the point in time they + represent, as a `datetime`, or to `"dynamic"` for databases whose + processes are distributed over time (typically the foreground). + Several databases may share the same date, e.g. to keep your own + modified copies of background processes in their own database + instead of writing them into the shared background database for + that vintage. If not given, the mapping is read from the + databases' own `representative_time` metadata (which premise + writes when exporting, and which you can set yourself with + `bw_timex.set_database_metadata`). Passing this argument replaces + the metadata entirely: only the databases listed here are used. + scenario : dict, optional + Metadata a background database must match to be used, e.g. + `{"iam_model": "remind", "pathway": "SSP2-PkBudg500"}`. Only + needed when the project holds several scenarios - `TimexLCA` + raises and lists them otherwise. Databases that don't declare the + filtered key (your foreground, a hand-built vintage) are always + kept. Cannot be combined with `database_dates`. use_global_lci_cache : bool, optional If True (default), background unit LCI matrices are cached at module level and reused across `TimexLCA` objects within the @@ -146,17 +175,16 @@ def __init__( self.demand = demand self.method = method - self.database_dates = database_dates - - if not self.database_dates: - logger.info( - "No database_dates provided. Treating the databases containing the functional \ - unit as dynamic. No remapping of inventories to time explicit databases will be done." - ) - self.database_dates = {key[0]: "dynamic" for key in demand.keys()} + self.scenario = scenario + self.database_dates = self._resolve_database_dates( + demand=demand, database_dates=database_dates, scenario=scenario + ) TimexLCAInputs( - demand=self.demand, method=self.method, database_dates=self.database_dates + demand=self.demand, + method=self.method, + database_dates=self.database_dates, + scenario=self.scenario, ) # Filled in by `prepare_base_lca_inputs`: the databases the base LCA @@ -231,6 +259,41 @@ def __init__( logger.info("TimexLCA initialized.") + @staticmethod + def _resolve_database_dates( + demand: dict, database_dates: dict | None, scenario: dict | None + ) -> dict: + """Map databases to the points in time they represent. + + Either from the explicit `database_dates` argument, which is then the + whole mapping, or from the databases' own `representative_time` + metadata. Databases holding the demand default to `"dynamic"`. + """ + if database_dates: + if scenario: + raise ValueError( + "`scenario` selects background databases by their metadata and " + "only applies when `database_dates` is not given. Pass one or " + "the other." + ) + return dict(database_dates) + + resolved = resolve_database_dates_from_metadata(scenario) + + if not resolved: + logger.info( + "No database_dates provided, and no database in this project carries " + "`representative_time` metadata. Treating the databases containing the " + "functional unit as dynamic. No remapping of inventories to time " + "explicit databases will be done." + ) + + for key in demand: + database = bd.get_node(id=get_id(key))["database"] + resolved.setdefault(database, "dynamic") + + return resolved + ######################################## # Main functions to be called by users # ######################################## diff --git a/bw_timex/validation.py b/bw_timex/validation.py index 05cbf705..3bf9b4b0 100644 --- a/bw_timex/validation.py +++ b/bw_timex/validation.py @@ -18,6 +18,7 @@ class TimexLCAInputs(BaseModel): demand: dict method: tuple database_dates: Optional[dict] = None + scenario: Optional[dict] = None @field_validator("demand") @classmethod @@ -71,6 +72,26 @@ def validate_database_dates(cls, v: Optional[dict]) -> Optional[dict]: ) return v + @field_validator("scenario") + @classmethod + def validate_scenario(cls, v: Optional[dict]) -> Optional[dict]: + if v is None: + return v + if not v: + raise ValueError("scenario must be a non-empty dictionary if provided.") + for key, value in v.items(): + if not isinstance(key, str): + raise ValueError( + f"scenario keys must be strings (database metadata keys), got " + f"{type(key).__name__}." + ) + if not isinstance(value, (str, int, float, bool, list, tuple)): + raise ValueError( + f"scenario values must be scalars or lists of scalars, got " + f"{type(value).__name__} for key '{key}'." + ) + return v + @model_validator(mode="after") def validate_demand_in_dynamic_databases(self) -> "TimexLCAInputs": if self.database_dates is None: diff --git a/tests/test_database_metadata.py b/tests/test_database_metadata.py index ef7cd83e..3264e58e 100644 --- a/tests/test_database_metadata.py +++ b/tests/test_database_metadata.py @@ -5,7 +5,7 @@ import bw2data as bd import pytest -from bw_timex import set_database_metadata +from bw_timex import TimexLCA, set_database_metadata from bw_timex.database_metadata import resolve_database_dates_from_metadata # ─── Tests for set_database_metadata ─── @@ -192,3 +192,99 @@ def test_same_scenario_from_two_premise_versions_is_not_ambiguous(self): set_database_metadata("db_2024", pathway="SSP2-PkBudg500") set_database_metadata("db_2024", premise_version="2.4.9.2") assert set(resolve_database_dates_from_metadata()) == {"db_2022", "db_2024"} + + +# ─── Tests for TimexLCA using database metadata ─── + + +@pytest.mark.usefixtures("temporal_grouping_db_monthly") +class TestTimexLCAFromMetadata: + + @pytest.fixture + def fu(self): + return bd.get_node(database="foreground", code="A") + + def test_no_arguments_uses_metadata(self, fu): + set_database_metadata("db_2022", representative_time="2022-01-01") + set_database_metadata("db_2024", representative_time="2024-01-01") + tlca = TimexLCA(demand={fu.key: 1}, method=("GWP", "example")) + assert tlca.database_dates == { + "db_2022": datetime(2022, 1, 1), + "db_2024": datetime(2024, 1, 1), + "foreground": "dynamic", + } + + def test_demand_database_metadata_is_respected(self, fu): + set_database_metadata("db_2022", representative_time="2022-01-01") + set_database_metadata("foreground", representative_time="dynamic") + tlca = TimexLCA(demand={fu.key: 1}, method=("GWP", "example")) + assert tlca.database_dates["foreground"] == "dynamic" + + def test_scenario_is_forwarded(self, fu): + set_database_metadata( + "db_2022", representative_time="2022-01-01", pathway="SSP2-Base" + ) + set_database_metadata( + "db_2024", representative_time="2024-01-01", pathway="SSP2-PkBudg500" + ) + tlca = TimexLCA( + demand={fu.key: 1}, + method=("GWP", "example"), + scenario={"pathway": "SSP2-Base"}, + ) + assert tlca.database_dates == { + "db_2022": datetime(2022, 1, 1), + "foreground": "dynamic", + } + + def test_database_dates_is_exclusive(self, fu): + set_database_metadata("db_2022", representative_time="2022-01-01") + set_database_metadata("db_2024", representative_time="2024-01-01") + tlca = TimexLCA( + demand={fu.key: 1}, + method=("GWP", "example"), + database_dates={ + "db_2024": datetime(2024, 1, 1), + "foreground": "dynamic", + }, + ) + assert tlca.database_dates == { + "db_2024": datetime(2024, 1, 1), + "foreground": "dynamic", + } + + def test_database_dates_with_scenario_raises(self, fu): + with pytest.raises(ValueError, match="only applies when"): + TimexLCA( + demand={fu.key: 1}, + method=("GWP", "example"), + database_dates={"foreground": "dynamic"}, + scenario={"pathway": "SSP2-Base"}, + ) + + def test_no_metadata_anywhere_falls_back_to_dynamic_demand(self, fu): + tlca = TimexLCA(demand={fu.key: 1}, method=("GWP", "example")) + assert tlca.database_dates == {"foreground": "dynamic"} + + def test_metadata_and_database_dates_give_the_same_score(self, fu): + explicit = TimexLCA( + demand={fu.key: 1}, + method=("GWP", "example"), + database_dates={ + "db_2022": datetime(2022, 1, 1), + "db_2024": datetime(2024, 1, 1), + "foreground": "dynamic", + }, + ) + explicit.build_timeline(starting_datetime=datetime(2024, 1, 2)) + explicit.lci() + explicit.static_lcia() + + set_database_metadata("db_2022", representative_time="2022-01-01") + set_database_metadata("db_2024", representative_time="2024-01-01") + from_metadata = TimexLCA(demand={fu.key: 1}, method=("GWP", "example")) + from_metadata.build_timeline(starting_datetime=datetime(2024, 1, 2)) + from_metadata.lci() + from_metadata.static_lcia() + + assert from_metadata.static_score == pytest.approx(explicit.static_score) From f082c1fc31085dceb4abbb2416dc507c24842c5a Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 12:06:43 +0200 Subject: [PATCH 08/24] fix: raise on typo'd scenario filters instead of silently going non-time-explicit `_resolve_database_dates` now raises a ValueError naming the scenario filter and the values actually declared for its keys when the filter matches no database, instead of quietly falling back to a plain (non-time-explicit) LCA. The "no representative_time metadata anywhere" log is now accurate for that case only. Reworded the three "database is not marked as dynamic" / "remove one of the two databases from database_dates" error messages to also credit representative_time metadata as a source of timing, since database_dates may never have been passed. Documented the ValueError cases in _resolve_database_dates's docstring, and added tests covering the new typo error and TimexLCAInputs.validate_scenario, which had no direct test coverage. --- bw_timex/edge_extractor.py | 3 ++- bw_timex/timeline_builder.py | 3 ++- bw_timex/timex_lca.py | 28 ++++++++++++++++++++ bw_timex/validation.py | 3 ++- tests/test_database_metadata.py | 47 +++++++++++++++++++++++++++++++++ tests/test_timex_lca.py | 2 +- 6 files changed, 82 insertions(+), 4 deletions(-) diff --git a/bw_timex/edge_extractor.py b/bw_timex/edge_extractor.py index 45202374..ce5921f5 100644 --- a/bw_timex/edge_extractor.py +++ b/bw_timex/edge_extractor.py @@ -319,7 +319,8 @@ def _candidate_databases_for_node(self, node_id: int) -> dict: f"one database at {date:%Y-%m-%d}: '{candidates[date]}' and " f"'{db_name}'. bw_timex cannot tell which one to use. Give " "the copy a distinct name, reference product or location, " - "or remove one of the two databases from `database_dates`." + "or remove one of the two databases from `database_dates` or " + "its `representative_time` metadata." ) candidates[date] = db_name diff --git a/bw_timex/timeline_builder.py b/bw_timex/timeline_builder.py index 252882cc..958b303e 100644 --- a/bw_timex/timeline_builder.py +++ b/bw_timex/timeline_builder.py @@ -578,7 +578,8 @@ def candidate_databases_for_producers(self, producers: set) -> dict: f"at {date:%Y-%m-%d}: '{already}' and '{node['database']}'. " "bw_timex cannot tell which one its temporal market should use. " "Give the copy a distinct name, reference product or location, " - "or remove one of the two databases from `database_dates`." + "or remove one of the two databases from `database_dates` or " + "its `representative_time` metadata." ) candidates[producer][date] = node["database"] matches[producer][node["database"]] = node.id diff --git a/bw_timex/timex_lca.py b/bw_timex/timex_lca.py index 0ae43a6d..577b35ac 100644 --- a/bw_timex/timex_lca.py +++ b/bw_timex/timex_lca.py @@ -268,6 +268,17 @@ def _resolve_database_dates( Either from the explicit `database_dates` argument, which is then the whole mapping, or from the databases' own `representative_time` metadata. Databases holding the demand default to `"dynamic"`. + + Raises + ------ + ValueError + If both `database_dates` and `scenario` are given (`scenario` + only selects among databases resolved from metadata, so it makes + no sense once `database_dates` already gives the whole mapping), + or if `scenario` is given but matches no database's metadata at + all - almost always a typo in one of its keys or values, since a + filter that legitimately excludes everything would leave nothing + for `TimexLCA` to compute with. """ if database_dates: if scenario: @@ -281,6 +292,23 @@ def _resolve_database_dates( resolved = resolve_database_dates_from_metadata(scenario) if not resolved: + if scenario: + declared = {} + for name in bd.databases: + metadata = bd.databases[name] + for key in scenario: + if key in metadata: + declared.setdefault(key, set()).add(str(metadata[key])) + details = "; ".join( + f"'{key}': " + f"{sorted(declared[key]) if key in declared else 'not declared by any database'}" + for key in scenario + ) + raise ValueError( + f"scenario={scenario!r} matched no database in this project. " + f"Values actually declared for its key(s) by this project's " + f"databases: {details}. Check for a typo in the filter." + ) logger.info( "No database_dates provided, and no database in this project carries " "`representative_time` metadata. Treating the databases containing the " diff --git a/bw_timex/validation.py b/bw_timex/validation.py index 3bf9b4b0..0db07e9a 100644 --- a/bw_timex/validation.py +++ b/bw_timex/validation.py @@ -105,7 +105,8 @@ def validate_demand_in_dynamic_databases(self) -> "TimexLCAInputs": if act["database"] not in dynamic_database_names: raise ValueError( f"Demand activity {act} from database {act['database']}: " - f"This database is not marked as 'dynamic' in database_dates. " + f"This database is mapped to a date rather than 'dynamic' " + f"(via `database_dates` or its `representative_time` metadata). " f"Please check." ) return self diff --git a/tests/test_database_metadata.py b/tests/test_database_metadata.py index 3264e58e..32476cb5 100644 --- a/tests/test_database_metadata.py +++ b/tests/test_database_metadata.py @@ -7,6 +7,7 @@ from bw_timex import TimexLCA, set_database_metadata from bw_timex.database_metadata import resolve_database_dates_from_metadata +from bw_timex.validation import TimexLCAInputs # ─── Tests for set_database_metadata ─── @@ -237,6 +238,25 @@ def test_scenario_is_forwarded(self, fu): "foreground": "dynamic", } + def test_typo_in_scenario_value_raises(self, fu): + set_database_metadata( + "db_2022", representative_time="2022-01-01", pathway="SSP2-Base" + ) + set_database_metadata( + "db_2024", representative_time="2024-01-01", pathway="SSP2-PkBudg500" + ) + with pytest.raises(ValueError, match="SSP2-Basee") as excinfo: + TimexLCA( + demand={fu.key: 1}, + method=("GWP", "example"), + scenario={"pathway": "SSP2-Basee"}, + ) + message = str(excinfo.value) + # The filter that matched nothing, and what's actually declared for + # that key, must both be in the error so a typo is obvious. + assert "SSP2-Base" in message + assert "SSP2-PkBudg500" in message + def test_database_dates_is_exclusive(self, fu): set_database_metadata("db_2022", representative_time="2022-01-01") set_database_metadata("db_2024", representative_time="2024-01-01") @@ -288,3 +308,30 @@ def test_metadata_and_database_dates_give_the_same_score(self, fu): from_metadata.static_lcia() assert from_metadata.static_score == pytest.approx(explicit.static_score) + + +# ─── Tests for TimexLCAInputs.validate_scenario ─── + + +@pytest.mark.usefixtures("temporal_grouping_db_monthly") +class TestValidateScenario: + + @pytest.fixture + def fu(self): + return bd.get_node(database="foreground", code="A") + + def test_non_string_key_raises(self, fu): + with pytest.raises(ValueError, match="scenario keys must be strings"): + TimexLCAInputs( + demand={fu.key: 1}, + method=("GWP", "example"), + scenario={123: "SSP2-Base"}, + ) + + def test_non_scalar_value_raises(self, fu): + with pytest.raises(ValueError, match="scenario values must be scalars"): + TimexLCAInputs( + demand={fu.key: 1}, + method=("GWP", "example"), + scenario={"pathway": {"nested": "dict"}}, + ) diff --git a/tests/test_timex_lca.py b/tests/test_timex_lca.py index f2851b51..9b01d00d 100644 --- a/tests/test_timex_lca.py +++ b/tests/test_timex_lca.py @@ -535,7 +535,7 @@ def test_no_database_dates_defaults_to_dynamic(self): def test_demand_not_in_dynamic_db_raises(self): """Test error when demand activity's db is not marked dynamic (line 988).""" fu = bd.get_node(database="foreground", code="A") - with pytest.raises(ValueError, match="not marked as 'dynamic'"): + with pytest.raises(ValueError, match="mapped to a date rather than 'dynamic'"): TimexLCA( demand={fu.key: 1}, method=("GWP", "example"), From dbda9e975fac64d4790c92bd8a1dec90a0ad711f Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 12:11:08 +0200 Subject: [PATCH 09/24] docs: document representative_time database metadata --- CHANGES.md | 3 + docs/api/database_metadata.md | 13 ++ docs/content/background_database_metadata.md | 158 ++++++++++++++++++ .../adding_temporal_information.md | 31 ++-- .../getting_started/build_process_timeline.md | 10 +- docs/content/getting_started/quickstart.md | 23 +-- zensical.toml | 2 + 7 files changed, 212 insertions(+), 28 deletions(-) create mode 100644 docs/api/database_metadata.md create mode 100644 docs/content/background_database_metadata.md diff --git a/CHANGES.md b/CHANGES.md index 3c0686c5..990ba47e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +* Added `representative_time` database metadata as the default timing source: `TimexLCA` now maps background databases to points in time by reading their Brightway metadata (as written by premise), making `database_dates` optional ([#217](https://github.com/brightway-lca/bw_timex/issues/217)) +* Added `set_database_metadata` to record what a database represents (`representative_time`, and scenario fields such as `iam_model` or `pathway`) for databases that don't bring the metadata themselves +* Added `TimexLCA(scenario={...})` to select one background scenario when a project holds several; `TimexLCA` raises and lists the scenarios it found if the choice is ambiguous ## [1.2.1] - 2026-08-14 * Fixed `ShapeMismatch` in `lci()` for processes with more than one biosphere exchange, by sizing the biosphere `flip_array` to the number of matrix entries (only raised with `bw_processing` >= 1.5; no numeric results change) ([#213](https://github.com/brightway-lca/bw_timex/pull/213)) diff --git a/docs/api/database_metadata.md b/docs/api/database_metadata.md new file mode 100644 index 00000000..c8e5ba9b --- /dev/null +++ b/docs/api/database_metadata.md @@ -0,0 +1,13 @@ +--- +icon: lucide/calendar-clock +tags: + - api +--- + +# Database metadata + +Reading and writing what a Brightway database represents: the point in time +(`representative_time`) and, for prospective databases, the scenario it was built +for. + +::: bw_timex.database_metadata diff --git a/docs/content/background_database_metadata.md b/docs/content/background_database_metadata.md new file mode 100644 index 00000000..4a270795 --- /dev/null +++ b/docs/content/background_database_metadata.md @@ -0,0 +1,158 @@ +--- +icon: lucide/calendar-clock +tags: + - background databases +--- + +# What a database represents + +`bw_timex` needs to know which point in time each background database stands for. +That information lives in the database's own Brightway metadata, so it only has to +be recorded once - not in every script. + +```python +import bw2data as bd + +bd.databases["ei_cutoff_3.10.1_remind_SSP2-PkBudg500_2050"] +``` + +```python +{ + # written by brightway + "format": "Ecoinvent XML", "backend": "sqlite", "number": 43648, ..., + # written by premise + "premise_version": "2.4.9.1", + "iam_model": "remind", + "pathway": "SSP2-PkBudg500", + "representative_time": "2050-01-01T00:00:00", + "ecoinvent_version": "3.10.1", + "system_model": "cutoff", +} +``` + +Only `representative_time` is required. `TimexLCA` reads it from every database of +your project, so a study on premise databases needs no timing argument at all: + +```python +tlca = TimexLCA(demand={("foreground", "A"): 1}, method=("our", "method")) +``` + +!!! info "premise version" + + premise writes this metadata from the version following 2.4.9.2 onwards. For + databases written by an earlier version, set it yourself as shown below - it is + a one-liner per database. + +## Setting it yourself + +For databases you built yourself, use +[`set_database_metadata`][bw_timex.database_metadata.set_database_metadata]: + +```python +from datetime import datetime +from bw_timex import set_database_metadata + +set_database_metadata("background_2020", representative_time=datetime(2020, 1, 1)) +set_database_metadata("background_2030", representative_time=datetime(2030, 1, 1)) +``` + +The value is stored as an ISO 8601 string, because Brightway keeps database +metadata as JSON. You only do this once per database: it is stored in the project, +not in your script. + +Your foreground doesn't represent a point in time - its processes get distributed +over time. `TimexLCA` treats the databases holding your functional unit as +`"dynamic"` automatically, but you can also say so explicitly: + +```python +set_database_metadata("foreground", representative_time="dynamic") +``` + +## Several databases for the same point in time + +More than one database may carry the same date. This is useful when you modify +background processes: keep the modified copies in your own database per point in +time, instead of writing them into ecoinvent or premise. + +```python +set_database_metadata("my_background_2020", representative_time=datetime(2020, 1, 1)) +set_database_metadata("my_background_2030", representative_time=datetime(2030, 1, 1)) +``` + +For each process, `bw_timex` interpolates only between the databases that actually +contain it, matched on `name`, `reference product` and `location`. + +## Choosing a scenario + +A project often holds more than one IAM scenario. `bw_timex` refuses to guess and +tells you what it found: + +``` +Several background scenarios found in this project: + pathway=SSP2-PkBudg500: ei_..._2030, ei_..._2040, ei_..._2050 + pathway=SSP2-Base: ei_..._2030, ei_..._2040, ei_..._2050 +Select one, e.g. scenario={'pathway': '...'}, or map the databases explicitly with +`database_dates`. +``` + +Pick one with the `scenario` argument, which filters the databases on their +metadata: + +```python +tlca = TimexLCA( + demand={("foreground", "A"): 1}, + method=("our", "method"), + scenario={"pathway": "SSP2-PkBudg500"}, +) +``` + +Any metadata key works - `iam_model`, `pathway`, `system_model`, +`ecoinvent_version`, `premise_version`, or anything you set yourself. Databases +that don't carry the key at all (your foreground, your own vintages) are never +filtered out. A key no database declares, or a filter that matches nothing, is +also an error rather than a silent empty result - `bw_timex` reports what it +actually found so you can spot a typo. + +Comparing scenarios is then a loop over filters: + +```python +scores = {} +for pathway in ("SSP2-Base", "SSP2-PkBudg500"): + tlca = TimexLCA(demand, method, scenario={"pathway": pathway}) + tlca.build_timeline() + tlca.lci() + tlca.static_lcia() + scores[pathway] = tlca.static_score +``` + +!!! warning "Superstructure databases" + + Databases holding several scenarios at once (premise superstructure or + scenario-array exports) are skipped: they have no single technosphere per point + in time. Use one database per scenario and year. + +## Mapping the databases explicitly + +`database_dates` still does what it always did, and takes over completely: when you +pass it, metadata is not read at all and only the databases you list are used. It +cannot be combined with `scenario`. + +```python +tlca = TimexLCA( + demand={("foreground", "A"): 1}, + method=("our", "method"), + database_dates={ + "background": datetime(2020, 1, 1), + "background_2030": datetime(2030, 1, 1), + "foreground": "dynamic", + }, +) +``` + +Use it when you want to restrict a calculation to a subset of the databases in your +project, or when a database's metadata is wrong and you don't want to change it. +It is also the fastest option on a large project: without it, `TimexLCA` reads +metadata from and loads node data for *every* registered database that carries +`representative_time`, including ones your demand does not actually depend on, +which costs setup time at premise scale - narrow it down with `scenario`, or bypass +metadata resolution entirely with an explicit `database_dates`. diff --git a/docs/content/getting_started/adding_temporal_information.md b/docs/content/getting_started/adding_temporal_information.md index 76a08acc..51ce6fe9 100644 --- a/docs/content/getting_started/adding_temporal_information.md +++ b/docs/content/getting_started/adding_temporal_information.md @@ -247,20 +247,24 @@ end ) ``` -So, as you can see, the processes at specific time steps reside within a separate normal Brightway database. To hand them to `bw_timex`, we just need to define a dictionary that maps the names of time-specific databases to the point in time that they represent: +So, as you can see, the processes at specific time steps reside within a separate normal +Brightway database. `bw_timex` picks these up automatically, as long as each database +says which point in time it represents: ```python from datetime import datetime +from bw_timex import set_database_metadata -# Note: The foreground does not represent a specific point in time, but should -# later be dynamically distributed over time -database_dates = { - "background": datetime.strptime("2020", "%Y"), - "background_2030": datetime.strptime("2030", "%Y"), - "foreground": "dynamic", -} +set_database_metadata("background", representative_time=datetime(2020, 1, 1)) +set_database_metadata("background_2030", representative_time=datetime(2030, 1, 1)) ``` +You only do this once per database - it is stored in your Brightway project. Databases +exported by [premise](https://premise.readthedocs.io/en/latest/introduction.html) bring +this metadata with them, so there is nothing to do for those. The foreground doesn't +represent a specific point in time and is distributed over time instead; `bw_timex` +treats the databases holding your functional unit that way automatically. + !!! tip "Data sources" You can use whatever data source you want for the time-specific process data. A nice package from the Brightway cosmos that can help you is [premise](https://premise.readthedocs.io/en/latest/introduction.html). @@ -272,13 +276,10 @@ background processes: keep the modified copies in your own database per point in time, instead of writing them into ecoinvent or premise. ```python -database_dates = { - "ecoinvent_2020": datetime.strptime("2020", "%Y"), - "ecoinvent_2030": datetime.strptime("2030", "%Y"), - "my_background_2020": datetime.strptime("2020", "%Y"), # your modified copies - "my_background_2030": datetime.strptime("2030", "%Y"), - "foreground": "dynamic", -} +set_database_metadata("ecoinvent_2020", representative_time=datetime(2020, 1, 1)) +set_database_metadata("ecoinvent_2030", representative_time=datetime(2030, 1, 1)) +set_database_metadata("my_background_2020", representative_time=datetime(2020, 1, 1)) +set_database_metadata("my_background_2030", representative_time=datetime(2030, 1, 1)) ``` For each process, `bw_timex` interpolates only between the databases that actually diff --git a/docs/content/getting_started/build_process_timeline.md b/docs/content/getting_started/build_process_timeline.md index ebedea1f..ccbb0f87 100644 --- a/docs/content/getting_started/build_process_timeline.md +++ b/docs/content/getting_started/build_process_timeline.md @@ -8,7 +8,9 @@ tags: # Step 2 - Building the process timeline -With all the temporal information prepared, we can now instantiate our TimexLCA object. This is very similar to a normal Brightway LCA object, but with the additional argument of our `database_dates`: +With all the temporal information prepared, we can now instantiate our TimexLCA object. +This is just like a normal Brightway LCA object - the timing of the background databases +comes from their metadata: ```python from bw_timex import TimexLCA @@ -16,10 +18,14 @@ from bw_timex import TimexLCA tlca = TimexLCA( demand={("foreground", "A"): 1}, method=("our", "method"), - database_dates=database_dates, ) ``` +If your project holds several scenarios, select one with +`scenario={"pathway": "SSP2-PkBudg500"}`; to map the databases by hand instead, pass +`database_dates`. Both are covered in +[What a database represents](../background_database_metadata.md). + Using our new `tlca` object, we can now build the timeline of processes that leads to our functional unit "A". If not specified otherwise, it's assumed that the demand occurs in the current year. In our case, we're specifying the time of demand to the year 2024, with the attribute 'starting_datetime`.. Building the timeline is very simple: ```python tlca.build_timeline(starting_datetime=datetime.strptime("2024-01-01", "%Y-%m-%d")) diff --git a/docs/content/getting_started/quickstart.md b/docs/content/getting_started/quickstart.md index 61e62ed9..a8e0bf3b 100644 --- a/docs/content/getting_started/quickstart.md +++ b/docs/content/getting_started/quickstart.md @@ -38,6 +38,7 @@ from bw_timex import ( TemporalDistribution, TimexLCA, add_temporal_distribution_to_exchange, + set_database_metadata, ) # 1. Set up Brightway project @@ -55,18 +56,15 @@ add_temporal_distribution_to_exchange( output_database="foreground", ) -# 3. Map your time-specific background databases to points in time -database_dates = { - "background": datetime.strptime("2020", "%Y"), - "background_2030": datetime.strptime("2030", "%Y"), - "foreground": "dynamic", # gets distributed over time -} +# 3. Say what your time-specific background databases represent +# (premise databases already know - skip this for them) +set_database_metadata("background", representative_time=datetime(2020, 1, 1)) +set_database_metadata("background_2030", representative_time=datetime(2030, 1, 1)) # 4. Create the TimexLCA object tlca = TimexLCA( demand={("foreground", "A"): 1}, method=("our", "method"), - database_dates=database_dates, ) # 5. Build the process timeline @@ -93,7 +91,7 @@ tlca.plot_dynamic_characterized_inventory() | What you want to express | How | Where | |---|---|---| | *When* an exchange happens | `temporal_distribution` on the exchange | any foreground exchange (and background ones, if you traverse the background) | -| *How the background changes* over time | one database per point in time + `database_dates` | background databases | +| *How the background changes* over time | one database per point in time, each with `representative_time` metadata | background databases | | *How a foreground exchange changes* over time | `temporal_evolution_factors` / `temporal_evolution_amounts` on the exchange (`bw_timex>0.3.4`) | foreground exchanges | ```python @@ -128,8 +126,10 @@ add_temporal_evolution_to_exchange( Absolute dates (`dtype="datetime64[s]"`) are also allowed in a `TemporalDistribution`, e.g. for the timing of the functional unit itself. Relative dates (`dtype="timedelta64[Y]"`) are relative to the consuming process. Several databases may -share the same date in `database_dates`, e.g. if you keep modified copies of background -processes in their own database instead of writing them into the shared vintage. +represent the same point in time, e.g. if you keep modified copies of background +processes in their own database instead of writing them into the shared vintage. See +[What a database represents](../background_database_metadata.md) for scenario selection +and for mapping databases explicitly with `database_dates`. --- @@ -141,7 +141,8 @@ processes in their own database instead of writing them into the shared vintage. TimexLCA( demand={("foreground", "A"): 1}, # Node, (database, code) tuple, or int id method=("our", "method"), - database_dates=database_dates, # optional, but usually what you want + scenario=None, # optional: pick one scenario among several + database_dates=None, # optional: map databases explicitly instead of by metadata ) ``` diff --git a/zensical.toml b/zensical.toml index becaee26..9039539b 100644 --- a/zensical.toml +++ b/zensical.toml @@ -29,6 +29,7 @@ nav = [ { "Step 3 - Calculating the time-explicit LCI" = "content/getting_started/time_explicit_lci.md" }, { "Step 4 - Impact assessment" = "content/getting_started/lcia.md" }, ]}, + { "What a database represents" = "content/background_database_metadata.md" }, { "What LCA should I do?" = "content/decisiontree.md" }, ]}, { Theory = "content/theory.md" }, @@ -59,6 +60,7 @@ nav = [ { "Edge Extractor" = "api/edge_extractor.md" }, { "Helper Classes" = "api/helper_classes.md" }, { Utils = "api/utils.md" }, + { "Database metadata" = "api/database_metadata.md" }, ]}, ] From 07a6ddb42bd8420c49b2f93a8ae453a54b770a69 Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 12:15:08 +0200 Subject: [PATCH 10/24] docs: reword the plan's attribution constraint --- .../plans/2026-08-21-representative-time-metadata.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/superpowers/plans/2026-08-21-representative-time-metadata.md b/docs/superpowers/plans/2026-08-21-representative-time-metadata.md index 50756a97..f0fef0fb 100644 --- a/docs/superpowers/plans/2026-08-21-representative-time-metadata.md +++ b/docs/superpowers/plans/2026-08-21-representative-time-metadata.md @@ -18,7 +18,7 @@ - Metadata keys, exactly as premise writes them: `representative_time`, `iam_model`, `pathway`, `system_model`, `ecoinvent_version`, `premise_version`, `external_scenarios`, `scenarios`. - Scenario identity keys (the ambiguity signature) are exactly: `("iam_model", "pathway", "system_model", "ecoinvent_version", "external_scenarios")`. `premise_version` is deliberately not one of them. - `notebooks/examples/paper_case_study.ipynb` must not be modified by any task. -- No Claude/AI attribution in commit messages. +- Commit messages carry no AI attribution and no `Co-Authored-By` trailer. --- From 66a673140d475e651e0a7b44d5ecc16c941ab6f9 Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 12:23:17 +0200 Subject: [PATCH 11/24] docs: use database metadata instead of database_dates in the tutorials The tutorials taught the old, mandatory database_dates mapping. They now record each background database's representative_time once with set_database_metadata and let TimexLCA read it, matching the new default behaviour. Notebook 4 (import_model_from_excel) could not be re-executed in this environment: bw2io is not installed in .venv, unrelated to this change; its stored outputs are left as-is and its source cells are updated. --- notebooks/tutorials/1_getting_started.ipynb | 443 +++- .../2_electric_vehicle_from_scratch.ipynb | 492 ++-- .../3_dynamic_characterization.ipynb | 341 +-- .../tutorials/4_import_model_from_excel.ipynb | 2148 ++++++++--------- 4 files changed, 1863 insertions(+), 1561 deletions(-) diff --git a/notebooks/tutorials/1_getting_started.ipynb b/notebooks/tutorials/1_getting_started.ipynb index a5529b55..97ab91d1 100644 --- a/notebooks/tutorials/1_getting_started.ipynb +++ b/notebooks/tutorials/1_getting_started.ipynb @@ -61,6 +61,12 @@ "cell_type": "code", "execution_count": 1, "metadata": { + "execution": { + "iopub.execute_input": "2026-08-21T10:19:45.627532Z", + "iopub.status.busy": "2026-08-21T10:19:45.627194Z", + "iopub.status.idle": "2026-08-21T10:19:46.995674Z", + "shell.execute_reply": "2026-08-21T10:19:46.995268Z" + }, "jupyter": { "source_hidden": true } @@ -70,54 +76,97 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/timodiepers/Documents/Coding/bw_timex/.venv/lib/python3.13/site-packages/bw2calc/__init__.py:53: UserWarning: \n", - "It seems like you have an ARM architecture, but haven't installed scikit-umfpack:\n", - "\n", - " https://pypi.org/project/scikit-umfpack/\n", - "\n", - "Installing it could give you much faster calculations.\n", - "\n", - " warnings.warn(UMFPACK_WARNING)\n", - "100%|██████████| 1/1 [00:00<00:00, 12633.45it/s]" + "\r", + " 0%| | 0/1 [00:00\n", " \n", " 0\n", - " 2024-05-01\n", + " 2024-06-01\n", " glider\n", - " 2026-05-01\n", + " 2026-06-01\n", " production of an electric vehicle\n", " 588.0\n", - " {'background_2020': 0.567, 'background_2030': ...\n", + " {'background_2020': 0.558, 'background_2030': ...\n", " \n", " \n", " 1\n", - " 2024-06-01\n", + " 2024-07-01\n", " glider\n", - " 2026-06-01\n", + " 2026-07-01\n", " production of an electric vehicle\n", " 588.0\n", - " {'background_2020': 0.558, 'background_2030': ...\n", + " {'background_2020': 0.55, 'background_2030': 0...\n", " \n", " \n", " 2\n", - " 2025-05-01\n", + " 2025-06-01\n", " glider\n", - " 2026-05-01\n", + " 2026-06-01\n", " production of an electric vehicle\n", " 84.0\n", - " {'background_2020': 0.467, 'background_2030': ...\n", + " {'background_2020': 0.459, 'background_2030': ...\n", " \n", " \n", " 3\n", - " 2025-05-01\n", + " 2025-06-01\n", " powertrain\n", - " 2026-05-01\n", + " 2026-06-01\n", " production of an electric vehicle\n", " 80.0\n", - " {'background_2020': 0.467, 'background_2030': ...\n", + " {'background_2020': 0.459, 'background_2030': ...\n", " \n", " \n", " 4\n", - " 2025-05-01\n", + " 2025-06-01\n", " battery\n", - " 2026-05-01\n", + " 2026-06-01\n", " production of an electric vehicle\n", " 280.0\n", - " {'background_2020': 0.467, 'background_2030': ...\n", + " {'background_2020': 0.459, 'background_2030': ...\n", " \n", " \n", " 5\n", - " 2025-06-01\n", + " 2025-07-01\n", " glider\n", - " 2026-06-01\n", + " 2026-07-01\n", " production of an electric vehicle\n", " 84.0\n", - " {'background_2020': 0.459, 'background_2030': ...\n", + " {'background_2020': 0.45, 'background_2030': 0...\n", " \n", " \n", " 6\n", - " 2025-06-01\n", + " 2025-07-01\n", " powertrain\n", - " 2026-06-01\n", + " 2026-07-01\n", " production of an electric vehicle\n", " 80.0\n", - " {'background_2020': 0.459, 'background_2030': ...\n", + " {'background_2020': 0.45, 'background_2030': 0...\n", " \n", " \n", " 7\n", - " 2025-06-01\n", + " 2025-07-01\n", " battery\n", - " 2026-06-01\n", + " 2026-07-01\n", " production of an electric vehicle\n", " 280.0\n", - " {'background_2020': 0.459, 'background_2030': ...\n", + " {'background_2020': 0.45, 'background_2030': 0...\n", " \n", " \n", " 8\n", - " 2026-05-01\n", + " 2026-06-01\n", " glider\n", - " 2026-05-01\n", + " 2026-06-01\n", " production of an electric vehicle\n", " 168.0\n", - " {'background_2020': 0.367, 'background_2030': ...\n", + " {'background_2020': 0.359, 'background_2030': ...\n", " \n", " \n", " 9\n", - " 2026-05-01\n", + " 2026-06-01\n", " production of an electric vehicle\n", - " 2026-08-01\n", + " 2026-09-01\n", " driving an electric vehicle\n", " 0.2\n", " None\n", " \n", " \n", " 10\n", - " 2026-06-01\n", + " 2026-07-01\n", " glider\n", - " 2026-06-01\n", + " 2026-07-01\n", " production of an electric vehicle\n", " 168.0\n", - " {'background_2020': 0.359, 'background_2030': ...\n", + " {'background_2020': 0.35, 'background_2030': 0...\n", " \n", " \n", " 11\n", - " 2026-06-01\n", + " 2026-07-01\n", " production of an electric vehicle\n", - " 2026-08-01\n", + " 2026-09-01\n", " driving an electric vehicle\n", " 0.8\n", " None\n", " \n", " \n", " 12\n", - " 2026-08-01\n", + " 2026-09-01\n", " electricity\n", - " 2026-08-01\n", + " 2026-09-01\n", " driving an electric vehicle\n", " 1875.0\n", - " {'background_2020': 0.342, 'background_2030': ...\n", + " {'background_2020': 0.333, 'background_2030': ...\n", " \n", " \n", " 13\n", - " 2026-08-01\n", + " 2026-09-01\n", " driving an electric vehicle\n", - " 2026-08-01\n", + " 2026-09-01\n", " -1\n", " 1.0\n", " None\n", " \n", " \n", " 14\n", - " 2027-08-01\n", + " 2027-09-01\n", " electricity\n", - " 2026-08-01\n", + " 2026-09-01\n", " driving an electric vehicle\n", " 1875.0\n", - " {'background_2020': 0.242, 'background_2030': ...\n", + " {'background_2020': 0.234, 'background_2030': ...\n", " \n", " \n", " 15\n", - " 2028-08-01\n", + " 2028-09-01\n", " electricity\n", - " 2026-08-01\n", + " 2026-09-01\n", " driving an electric vehicle\n", " 1875.0\n", - " {'background_2020': 0.142, 'background_2030': ...\n", + " {'background_2020': 0.133, 'background_2030': ...\n", " \n", " \n", " 16\n", - " 2029-08-01\n", + " 2029-09-01\n", " electricity\n", - " 2026-08-01\n", + " 2026-09-01\n", " driving an electric vehicle\n", " 1875.0\n", - " {'background_2020': 0.042, 'background_2030': ...\n", + " {'background_2020': 0.033, 'background_2030': ...\n", " \n", " \n", " 17\n", - " 2030-08-01\n", + " 2030-09-01\n", " electricity\n", - " 2026-08-01\n", + " 2026-09-01\n", " driving an electric vehicle\n", " 1875.0\n", - " {'background_2030': 0.942, 'background_2040': ...\n", + " {'background_2030': 0.933, 'background_2040': ...\n", " \n", " \n", " 18\n", - " 2031-08-01\n", + " 2031-09-01\n", " electricity\n", - " 2026-08-01\n", + " 2026-09-01\n", " driving an electric vehicle\n", " 1875.0\n", - " {'background_2030': 0.842, 'background_2040': ...\n", + " {'background_2030': 0.834, 'background_2040': ...\n", " \n", " \n", " 19\n", - " 2032-08-01\n", + " 2032-09-01\n", " electricity\n", - " 2026-08-01\n", + " 2026-09-01\n", " driving an electric vehicle\n", " 1875.0\n", - " {'background_2030': 0.742, 'background_2040': ...\n", + " {'background_2030': 0.733, 'background_2040': ...\n", " \n", " \n", " 20\n", - " 2033-08-01\n", + " 2033-09-01\n", " electricity\n", - " 2026-08-01\n", + " 2026-09-01\n", " driving an electric vehicle\n", " 1875.0\n", - " {'background_2030': 0.642, 'background_2040': ...\n", + " {'background_2030': 0.633, 'background_2040': ...\n", " \n", " \n", " 21\n", - " 2034-08-01\n", + " 2034-09-01\n", " electricity\n", - " 2026-08-01\n", + " 2026-09-01\n", " driving an electric vehicle\n", " 1875.0\n", - " {'background_2030': 0.542, 'background_2040': ...\n", + " {'background_2030': 0.533, 'background_2040': ...\n", " \n", " \n", " 22\n", - " 2035-08-01\n", + " 2035-09-01\n", " electricity\n", - " 2026-08-01\n", + " 2026-09-01\n", " driving an electric vehicle\n", " 1875.0\n", - " {'background_2030': 0.442, 'background_2040': ...\n", + " {'background_2030': 0.433, 'background_2040': ...\n", " \n", " \n", " 23\n", - " 2036-08-01\n", + " 2036-09-01\n", " electricity\n", - " 2026-08-01\n", + " 2026-09-01\n", " driving an electric vehicle\n", " 1875.0\n", - " {'background_2030': 0.342, 'background_2040': ...\n", + " {'background_2030': 0.333, 'background_2040': ...\n", " \n", " \n", " 24\n", - " 2037-08-01\n", + " 2037-09-01\n", " electricity\n", - " 2026-08-01\n", + " 2026-09-01\n", " driving an electric vehicle\n", " 1875.0\n", - " {'background_2030': 0.242, 'background_2040': ...\n", + " {'background_2030': 0.233, 'background_2040': ...\n", " \n", " \n", " 25\n", - " 2038-08-01\n", + " 2038-09-01\n", " electricity\n", - " 2026-08-01\n", + " 2026-09-01\n", " driving an electric vehicle\n", " 1875.0\n", - " {'background_2030': 0.142, 'background_2040': ...\n", + " {'background_2030': 0.133, 'background_2040': ...\n", " \n", " \n", " 26\n", - " 2039-08-01\n", + " 2039-09-01\n", " electricity\n", - " 2026-08-01\n", + " 2026-09-01\n", " driving an electric vehicle\n", " 1875.0\n", - " {'background_2030': 0.042, 'background_2040': ...\n", + " {'background_2030': 0.033, 'background_2040': ...\n", " \n", " \n", " 27\n", - " 2040-08-01\n", + " 2040-09-01\n", " electricity\n", - " 2026-08-01\n", + " 2026-09-01\n", " driving an electric vehicle\n", " 1875.0\n", " {'background_2040': 1}\n", " \n", " \n", " 28\n", - " 2041-08-01\n", + " 2041-09-01\n", " electricity\n", - " 2026-08-01\n", + " 2026-09-01\n", " driving an electric vehicle\n", " 1875.0\n", " {'background_2040': 1}\n", " \n", " \n", " 29\n", - " 2042-08-01\n", + " 2042-09-01\n", " used electric vehicle\n", - " 2026-08-01\n", + " 2026-09-01\n", " driving an electric vehicle\n", " -1.0\n", " None\n", " \n", " \n", " 30\n", - " 2042-11-01\n", + " 2042-12-01\n", " glider_eol\n", - " 2042-08-01\n", + " 2042-09-01\n", " used electric vehicle\n", " -840.0\n", " {'background_2040': 1}\n", " \n", " \n", " 31\n", - " 2042-11-01\n", + " 2042-12-01\n", " powertrain_eol\n", - " 2042-08-01\n", + " 2042-09-01\n", " used electric vehicle\n", " -80.0\n", " {'background_2040': 1}\n", " \n", " \n", " 32\n", - " 2042-11-01\n", + " 2042-12-01\n", " battery_eol\n", - " 2042-08-01\n", + " 2042-09-01\n", " used electric vehicle\n", " -280.0\n", " {'background_2040': 1}\n", @@ -992,39 +1010,39 @@ ], "text/plain": [ " date_producer producer_name date_consumer \\\n", - "0 2024-05-01 glider 2026-05-01 \n", - "1 2024-06-01 glider 2026-06-01 \n", - "2 2025-05-01 glider 2026-05-01 \n", - "3 2025-05-01 powertrain 2026-05-01 \n", - "4 2025-05-01 battery 2026-05-01 \n", - "5 2025-06-01 glider 2026-06-01 \n", - "6 2025-06-01 powertrain 2026-06-01 \n", - "7 2025-06-01 battery 2026-06-01 \n", - "8 2026-05-01 glider 2026-05-01 \n", - "9 2026-05-01 production of an electric vehicle 2026-08-01 \n", - "10 2026-06-01 glider 2026-06-01 \n", - "11 2026-06-01 production of an electric vehicle 2026-08-01 \n", - "12 2026-08-01 electricity 2026-08-01 \n", - "13 2026-08-01 driving an electric vehicle 2026-08-01 \n", - "14 2027-08-01 electricity 2026-08-01 \n", - "15 2028-08-01 electricity 2026-08-01 \n", - "16 2029-08-01 electricity 2026-08-01 \n", - "17 2030-08-01 electricity 2026-08-01 \n", - "18 2031-08-01 electricity 2026-08-01 \n", - "19 2032-08-01 electricity 2026-08-01 \n", - "20 2033-08-01 electricity 2026-08-01 \n", - "21 2034-08-01 electricity 2026-08-01 \n", - "22 2035-08-01 electricity 2026-08-01 \n", - "23 2036-08-01 electricity 2026-08-01 \n", - "24 2037-08-01 electricity 2026-08-01 \n", - "25 2038-08-01 electricity 2026-08-01 \n", - "26 2039-08-01 electricity 2026-08-01 \n", - "27 2040-08-01 electricity 2026-08-01 \n", - "28 2041-08-01 electricity 2026-08-01 \n", - "29 2042-08-01 used electric vehicle 2026-08-01 \n", - "30 2042-11-01 glider_eol 2042-08-01 \n", - "31 2042-11-01 powertrain_eol 2042-08-01 \n", - "32 2042-11-01 battery_eol 2042-08-01 \n", + "0 2024-06-01 glider 2026-06-01 \n", + "1 2024-07-01 glider 2026-07-01 \n", + "2 2025-06-01 glider 2026-06-01 \n", + "3 2025-06-01 powertrain 2026-06-01 \n", + "4 2025-06-01 battery 2026-06-01 \n", + "5 2025-07-01 glider 2026-07-01 \n", + "6 2025-07-01 powertrain 2026-07-01 \n", + "7 2025-07-01 battery 2026-07-01 \n", + "8 2026-06-01 glider 2026-06-01 \n", + "9 2026-06-01 production of an electric vehicle 2026-09-01 \n", + "10 2026-07-01 glider 2026-07-01 \n", + "11 2026-07-01 production of an electric vehicle 2026-09-01 \n", + "12 2026-09-01 electricity 2026-09-01 \n", + "13 2026-09-01 driving an electric vehicle 2026-09-01 \n", + "14 2027-09-01 electricity 2026-09-01 \n", + "15 2028-09-01 electricity 2026-09-01 \n", + "16 2029-09-01 electricity 2026-09-01 \n", + "17 2030-09-01 electricity 2026-09-01 \n", + "18 2031-09-01 electricity 2026-09-01 \n", + "19 2032-09-01 electricity 2026-09-01 \n", + "20 2033-09-01 electricity 2026-09-01 \n", + "21 2034-09-01 electricity 2026-09-01 \n", + "22 2035-09-01 electricity 2026-09-01 \n", + "23 2036-09-01 electricity 2026-09-01 \n", + "24 2037-09-01 electricity 2026-09-01 \n", + "25 2038-09-01 electricity 2026-09-01 \n", + "26 2039-09-01 electricity 2026-09-01 \n", + "27 2040-09-01 electricity 2026-09-01 \n", + "28 2041-09-01 electricity 2026-09-01 \n", + "29 2042-09-01 used electric vehicle 2026-09-01 \n", + "30 2042-12-01 glider_eol 2042-09-01 \n", + "31 2042-12-01 powertrain_eol 2042-09-01 \n", + "32 2042-12-01 battery_eol 2042-09-01 \n", "\n", " consumer_name amount \\\n", "0 production of an electric vehicle 588.0 \n", @@ -1062,33 +1080,33 @@ "32 used electric vehicle -280.0 \n", "\n", " temporal_market_shares \n", - "0 {'background_2020': 0.567, 'background_2030': ... \n", - "1 {'background_2020': 0.558, 'background_2030': ... \n", - "2 {'background_2020': 0.467, 'background_2030': ... \n", - "3 {'background_2020': 0.467, 'background_2030': ... \n", - "4 {'background_2020': 0.467, 'background_2030': ... \n", - "5 {'background_2020': 0.459, 'background_2030': ... \n", - "6 {'background_2020': 0.459, 'background_2030': ... \n", - "7 {'background_2020': 0.459, 'background_2030': ... \n", - "8 {'background_2020': 0.367, 'background_2030': ... \n", + "0 {'background_2020': 0.558, 'background_2030': ... \n", + "1 {'background_2020': 0.55, 'background_2030': 0... \n", + "2 {'background_2020': 0.459, 'background_2030': ... \n", + "3 {'background_2020': 0.459, 'background_2030': ... \n", + "4 {'background_2020': 0.459, 'background_2030': ... \n", + "5 {'background_2020': 0.45, 'background_2030': 0... \n", + "6 {'background_2020': 0.45, 'background_2030': 0... \n", + "7 {'background_2020': 0.45, 'background_2030': 0... \n", + "8 {'background_2020': 0.359, 'background_2030': ... \n", "9 None \n", - "10 {'background_2020': 0.359, 'background_2030': ... \n", + "10 {'background_2020': 0.35, 'background_2030': 0... \n", "11 None \n", - "12 {'background_2020': 0.342, 'background_2030': ... \n", + "12 {'background_2020': 0.333, 'background_2030': ... \n", "13 None \n", - "14 {'background_2020': 0.242, 'background_2030': ... \n", - "15 {'background_2020': 0.142, 'background_2030': ... \n", - "16 {'background_2020': 0.042, 'background_2030': ... \n", - "17 {'background_2030': 0.942, 'background_2040': ... \n", - "18 {'background_2030': 0.842, 'background_2040': ... \n", - "19 {'background_2030': 0.742, 'background_2040': ... \n", - "20 {'background_2030': 0.642, 'background_2040': ... \n", - "21 {'background_2030': 0.542, 'background_2040': ... \n", - "22 {'background_2030': 0.442, 'background_2040': ... \n", - "23 {'background_2030': 0.342, 'background_2040': ... \n", - "24 {'background_2030': 0.242, 'background_2040': ... \n", - "25 {'background_2030': 0.142, 'background_2040': ... \n", - "26 {'background_2030': 0.042, 'background_2040': ... \n", + "14 {'background_2020': 0.234, 'background_2030': ... \n", + "15 {'background_2020': 0.133, 'background_2030': ... \n", + "16 {'background_2020': 0.033, 'background_2030': ... \n", + "17 {'background_2030': 0.933, 'background_2040': ... \n", + "18 {'background_2030': 0.834, 'background_2040': ... \n", + "19 {'background_2030': 0.733, 'background_2040': ... \n", + "20 {'background_2030': 0.633, 'background_2040': ... \n", + "21 {'background_2030': 0.533, 'background_2040': ... \n", + "22 {'background_2030': 0.433, 'background_2040': ... \n", + "23 {'background_2030': 0.333, 'background_2040': ... \n", + "24 {'background_2030': 0.233, 'background_2040': ... \n", + "25 {'background_2030': 0.133, 'background_2040': ... \n", + "26 {'background_2030': 0.033, 'background_2040': ... \n", "27 {'background_2040': 1} \n", "28 {'background_2040': 1} \n", "29 None \n", @@ -1121,10 +1139,10 @@ "execution_count": 9, "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:51:30.375893Z", - "iopub.status.busy": "2026-08-04T07:51:30.375778Z", - "iopub.status.idle": "2026-08-04T07:51:30.438287Z", - "shell.execute_reply": "2026-08-04T07:51:30.437578Z" + "iopub.execute_input": "2026-08-21T10:20:25.850819Z", + "iopub.status.busy": "2026-08-21T10:20:25.850755Z", + "iopub.status.idle": "2026-08-21T10:20:25.903806Z", + "shell.execute_reply": "2026-08-21T10:20:25.903420Z" } }, "outputs": [ @@ -1132,14 +1150,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:51:30.382\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m529\u001b[0m - \u001b[1mExpanding matrices...\u001b[0m\n" + "\u001b[32m2026-08-21 12:20:25.855\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m634\u001b[0m - \u001b[1mExpanding matrices...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:51:30.399\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m548\u001b[0m - \u001b[1mCalculating dynamic inventory...\u001b[0m\n" + "\u001b[32m2026-08-21 12:20:25.869\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m653\u001b[0m - \u001b[1mCalculating dynamic inventory...\u001b[0m\n" ] } ], @@ -1159,17 +1177,17 @@ "execution_count": 10, "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:51:30.440155Z", - "iopub.status.busy": "2026-08-04T07:51:30.440037Z", - "iopub.status.idle": "2026-08-04T07:51:30.443133Z", - "shell.execute_reply": "2026-08-04T07:51:30.442698Z" + "iopub.execute_input": "2026-08-21T10:20:25.904961Z", + "iopub.status.busy": "2026-08-21T10:20:25.904894Z", + "iopub.status.idle": "2026-08-21T10:20:25.907281Z", + "shell.execute_reply": "2026-08-21T10:20:25.906957Z" } }, "outputs": [ { "data": { "text/plain": [ - "15166.963039459766" + "15066.797664957092" ] }, "execution_count": 10, @@ -1194,10 +1212,10 @@ "execution_count": 11, "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:51:30.444727Z", - "iopub.status.busy": "2026-08-04T07:51:30.444645Z", - "iopub.status.idle": "2026-08-04T07:51:30.446842Z", - "shell.execute_reply": "2026-08-04T07:51:30.446413Z" + "iopub.execute_input": "2026-08-21T10:20:25.908315Z", + "iopub.status.busy": "2026-08-21T10:20:25.908252Z", + "iopub.status.idle": "2026-08-21T10:20:25.910008Z", + "shell.execute_reply": "2026-08-21T10:20:25.909688Z" } }, "outputs": [ @@ -1230,17 +1248,17 @@ "execution_count": 12, "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:51:30.447993Z", - "iopub.status.busy": "2026-08-04T07:51:30.447929Z", - "iopub.status.idle": "2026-08-04T07:51:30.458169Z", - "shell.execute_reply": "2026-08-04T07:51:30.457714Z" + "iopub.execute_input": "2026-08-21T10:20:25.910938Z", + "iopub.status.busy": "2026-08-21T10:20:25.910881Z", + "iopub.status.idle": "2026-08-21T10:20:25.918840Z", + "shell.execute_reply": "2026-08-21T10:20:25.918531Z" } }, "outputs": [ { "data": { "text/plain": [ - "np.float64(15166.963039459766)" + "np.float64(15066.797664957092)" ] }, "execution_count": 12, @@ -1271,16 +1289,16 @@ "execution_count": 13, "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:51:30.459429Z", - "iopub.status.busy": "2026-08-04T07:51:30.459353Z", - "iopub.status.idle": "2026-08-04T07:51:30.574216Z", - "shell.execute_reply": "2026-08-04T07:51:30.573700Z" + "iopub.execute_input": "2026-08-21T10:20:25.919972Z", + "iopub.status.busy": "2026-08-21T10:20:25.919899Z", + "iopub.status.idle": "2026-08-21T10:20:26.016856Z", + "shell.execute_reply": "2026-08-21T10:20:26.016514Z" } }, "outputs": [ { "data": { - "image/png": "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dMuKomONmoHl6NHEFz/WVOlaf9YRHHDQm7K/+eYb5++7du0yswyefPJJuX79/2/m1Zqvus/YKdfz5ctHcMBHAi5AsHHjRhk9erS54F966SXnco2IffrppyYqptErDRA47urXi9WT2Hf/e5ohkFD72Oto+iNXly5ditM+rU210j8E/jbFKynTolJ7v/72XvlzP6cU52wH+tkO9HPgo4/tQD/bgX62A/1sB/o58NnYx4FK787X9D65cuWK85rWI0hKgOD8+fMeZxzEXvbnn3+aZw0QeJI7d+4E14f3BFyA4Ouvv3ZOV4lNL7RSpUrJ3r17JSoqylkDQKfFeOJY7minua80mqUfGo28xa5DELu94+effvrJ1BmIHSBIqD4BAAAAAAAAAKSU3vGfMWNGOXv2bJzXHAP5iaXjm1rr9VYpqRzjoFpzoEKFCnHaO1LA+/qGX4gEBdqb4Cig4umCd0TMgoKCzIdCB+bz588vO3bsMHmxXOnvurxIkSKm2IZDtWrVzGvbt2+Ps20taKwefvhht/ZKq3DH197RBgAAAAAAAABSk97k/MADD8jKlSvd6ptqzYCdO3cmaVs6jqnjq99++63b8s8//9zt98qVK5usKb///rupORD7Ua5cuRSeFVJLwAUIqlSpYp6nTZsWJ3WQFizWC1/zXGXOnNlEpjp06GBmE7z99ttubfV3Xd6pUye35Y7fR40a5VbNXWcubNmyRerWretWgVsLdOiHYebMmW6FOvRnLdChETytiwAAAAAAAAAAt4PWZj18+LBJ+bNmzRpZsmSJKSCsKYb0ZurEateunSko3blzZ/n4449l3bp10q1bNxMIcBUaGmoKI0+fPl169OhhghM6dqr7HTRokLz33nu34SyRHAGXYqh58+byn//8R7777jtT2OKxxx4zRYr37NkjmzZtkjvuuMMM7jv06dNHVq9ebaa7aOohnfKibfWO//vvv99cwK5q1qwpHTt2NIWOtRhHgwYN5PTp07J06VLJnj27jBs3zq19tmzZTLBBPyjaXgMGSttrzq6PPvrIfGAAAAAAAAAApF15c+aTbZ8d9en+k0vHNHUcUmu36g3T4eHh0rdvXxMsOH78eKK3oynYly9fLgMHDpTBgwebLC168/PYsWOlffv2bm2ffvppk5ll0qRJ8sILL8iVK1dMCngds9UxXPh5gKBp06ZJmsai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", + "image/png": "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mWBQUs2fP9/MQNDZCE4a94YNG0qrVq3k+PHjsQIJmuGldevW8v7777uWawHjypUry8SJE2X8+PGu5VrrVo9Na9/Cu4IkwGzcuNFMldECwDpNRosBaxFiveA//fRTyZw5s7k43e/q14iXJ3Hv/vc0QyCx9jdaRws7x20PAAAAAAAAAGlFU5Z///330rx581hp0zVbiaZoTw6tpap1aZ3ggEOztrjbtWuXGXN99NFH5dq1a66HjtPqPnU77goUKEBwwEcCbgbB119/7YpGxaXRsDJlypicV5GRka4aABr18sRZ7rTTqS16sR45csR8sOLWIYjb3vn5xx9/NHUGtD6Cu8TqEwAAAAAAAABAaund+ZreJ0+ePPFey5cvnxnrTKqzZ896nHEQd9lff/1lnjVA4EnevHkTXR/eE3ABAqfC+unTpxP8QAQFBZmZBDowX7BgQdmxY4eZ9uKe20p/1+XFihUzubQcNWrUkEWLFsn27dvNz+60oLG6//77Y7X//PPPZf369VK1alWP7eNuBwAAAAAAAADSgt7xr2OhnsZLnYH8pNIboH/++ecbFrV2bpTWmgOVKlWK196pEevr9NkIwBRD1apVM89Tp06NlzpICxZrLiydxpI1a1Zz4XXq1MnMJnjrrbditdXfdXmXLl1iLXd+HzVqlCsY4cxc0Ire9evXj1VgQ/NvaQqhGTNmxMrDpT9r/i39gGpdBAAAAAAAAABIa5oF5Z577pEVK1aYrCju45M7d+5M1rb0Rme9Afubb76JtVxvkHZ33333mTHR33//3dQciPuoUKFCKs8KaSXgZhC0bNlSPvzwQ/n2229N3iqtiq1Fivfs2WOqdd9yyy1mcN+h1bFXrVplolmaekgjWtpW7/i/++67pVevXrG2X7t2bencubMpdKy5tho1aiQnT56UJUuWSM6cOU31b3c5cuQwwYYePXqY9howUNpep+R89NFHEhoa6qV3BwAAAAAAAIBthg4dKi1atDApf5566in5999/ZezYsSbFkGZbSaoOHTqYG7O7du0qr776qqn/unDhQhMIcKfjnVoYuV+/fmYMtEmTJmbsVGcsaFBC6x/oa/C9TIEYEdPBd71Q9VmjV3qnv17s7dq1k+eff17KlSvnaq9phVauXCljxowxUbTNmzebnFd9+vSRwYMHm4BCXBpMKF++vMyaNUumT59utqGz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", "text/plain": [ "
" ] diff --git a/notebooks/tutorials/3_dynamic_characterization.ipynb b/notebooks/tutorials/3_dynamic_characterization.ipynb index e983d080..34327a1b 100644 --- a/notebooks/tutorials/3_dynamic_characterization.ipynb +++ b/notebooks/tutorials/3_dynamic_characterization.ipynb @@ -18,10 +18,10 @@ "id": "5d3622ff", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:46.074356Z", - "iopub.status.busy": "2026-08-04T07:25:46.074157Z", - "iopub.status.idle": "2026-08-04T07:25:47.680720Z", - "shell.execute_reply": "2026-08-04T07:25:47.680287Z" + "iopub.execute_input": "2026-08-21T10:20:58.631769Z", + "iopub.status.busy": "2026-08-21T10:20:58.631399Z", + "iopub.status.idle": "2026-08-21T10:20:59.869756Z", + "shell.execute_reply": "2026-08-21T10:20:59.869258Z" } }, "outputs": [ @@ -29,7 +29,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001b[2m09:25:47+0200\u001b[0m [\u001b[33m\u001b[1mwarning \u001b[0m] \u001b[1mRemoving project from project timex_example_dynamic_characterization list, but not deleting data; if you switch to this project again you will have the same data again. To delete data permanently, pass `(..., delete_dir=True)`.\u001b[0m\n" + "\u001b[2m12:20:59+0200\u001b[0m [\u001b[33m\u001b[1mwarning \u001b[0m] \u001b[1mRemoving project from project timex_example_dynamic_characterization list, but not deleting data; if you switch to this project again you will have the same data again. To delete data permanently, pass `(..., delete_dir=True)`.\u001b[0m\n" ] }, { @@ -45,21 +45,21 @@ "output_type": "stream", "text": [ "\r", - "100%|██████████| 1/1 [00:00<00:00, 1726.76it/s]" + "100%|██████████| 1/1 [00:00<00:00, 2807.43it/s]" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[2m09:25:47+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n" + "\u001b[2m12:20:59+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[2m09:25:47+0200\u001b[0m [\u001b[33m\u001b[1mwarning \u001b[0m] \u001b[1mNot able to determine geocollections for all datasets. This database is not ready for regionalization.\u001b[0m\n" + "\u001b[2m12:20:59+0200\u001b[0m [\u001b[33m\u001b[1mwarning \u001b[0m] \u001b[1mNot able to determine geocollections for all datasets. This database is not ready for regionalization.\u001b[0m\n" ] }, { @@ -82,14 +82,14 @@ "output_type": "stream", "text": [ "\r", - "100%|██████████| 1/1 [00:00<00:00, 21732.15it/s]" + "100%|██████████| 1/1 [00:00<00:00, 22795.13it/s]" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[2m09:25:47+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n" + "\u001b[2m12:20:59+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n" ] }, { @@ -173,10 +173,10 @@ "id": "8d9405d9", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:47.682211Z", - "iopub.status.busy": "2026-08-04T07:25:47.682115Z", - "iopub.status.idle": "2026-08-04T07:25:47.683912Z", - "shell.execute_reply": "2026-08-04T07:25:47.683616Z" + "iopub.execute_input": "2026-08-21T10:20:59.871200Z", + "iopub.status.busy": "2026-08-21T10:20:59.871109Z", + "iopub.status.idle": "2026-08-21T10:20:59.872822Z", + "shell.execute_reply": "2026-08-21T10:20:59.872506Z" } }, "outputs": [], @@ -191,10 +191,10 @@ "id": "71bba776", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:47.685048Z", - "iopub.status.busy": "2026-08-04T07:25:47.684983Z", - "iopub.status.idle": "2026-08-04T07:25:47.990358Z", - "shell.execute_reply": "2026-08-04T07:25:47.989987Z" + "iopub.execute_input": "2026-08-21T10:20:59.873983Z", + "iopub.status.busy": "2026-08-21T10:20:59.873915Z", + "iopub.status.idle": "2026-08-21T10:20:59.884638Z", + "shell.execute_reply": "2026-08-21T10:20:59.884333Z" } }, "outputs": [ @@ -202,42 +202,42 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:47.974\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m142\u001b[0m - \u001b[1mInitializing TimexLCA object...\u001b[0m\n" + "\u001b[32m2026-08-21 12:20:59.874\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m174\u001b[0m - \u001b[1mInitializing TimexLCA object...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:47.975\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m149\u001b[0m - \u001b[1mNo database_dates provided. Treating the databases containing the functional unit as dynamic. No remapping of inventories to time explicit databases will be done.\u001b[0m\n" + "\u001b[32m2026-08-21 12:20:59.874\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m_resolve_database_dates\u001b[0m:\u001b[36m312\u001b[0m - \u001b[1mNo database_dates provided, and no database in this project carries `representative_time` metadata. Treating the databases containing the functional unit as dynamic. No remapping of inventories to time explicit databases will be done.\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:47.976\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m163\u001b[0m - \u001b[1mCalculating base LCA...\u001b[0m\n" + "\u001b[32m2026-08-21 12:20:59.875\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m194\u001b[0m - \u001b[1mCalculating base LCA...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:47.987\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m180\u001b[0m - \u001b[1mCollecting node infos...\u001b[0m\n" + "\u001b[32m2026-08-21 12:20:59.882\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m211\u001b[0m - \u001b[1mCollecting node infos...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:47.988\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m192\u001b[0m - \u001b[1mLoading node metadata from 1 database(s)...\u001b[0m\n" + "\u001b[32m2026-08-21 12:20:59.882\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m223\u001b[0m - \u001b[1mLoading node metadata from 1 database(s)...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:47.988\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m229\u001b[0m - \u001b[1mTimexLCA initialized.\u001b[0m\n" + "\u001b[32m2026-08-21 12:20:59.883\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m260\u001b[0m - \u001b[1mTimexLCA initialized.\u001b[0m\n" ] } ], @@ -253,10 +253,10 @@ "id": "c40754e8", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:47.991593Z", - "iopub.status.busy": "2026-08-04T07:25:47.991413Z", - "iopub.status.idle": "2026-08-04T07:25:48.040452Z", - "shell.execute_reply": "2026-08-04T07:25:48.040043Z" + "iopub.execute_input": "2026-08-21T10:20:59.885983Z", + "iopub.status.busy": "2026-08-21T10:20:59.885894Z", + "iopub.status.idle": "2026-08-21T10:20:59.903919Z", + "shell.execute_reply": "2026-08-21T10:20:59.903494Z" } }, "outputs": [ @@ -264,35 +264,35 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:47.992\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m358\u001b[0m - \u001b[1mNo edge filter function provided. Skipping all edges in background databases.\u001b[0m\n" + "\u001b[32m2026-08-21 12:20:59.886\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m453\u001b[0m - \u001b[1mNo edge filter function provided. Skipping all edges in background databases.\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:47.992\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m379\u001b[0m - \u001b[1mCreating activity time mapping...\u001b[0m\n" + "\u001b[32m2026-08-21 12:20:59.886\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m474\u001b[0m - \u001b[1mCreating activity time mapping...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:47.992\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m112\u001b[0m - \u001b[1mTraversing supply chain graph...\u001b[0m\n" + "\u001b[32m2026-08-21 12:20:59.886\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m112\u001b[0m - \u001b[1mTraversing supply chain graph...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:47.994\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m186\u001b[0m - \u001b[1mBuilding timeline...\u001b[0m\n" + "\u001b[32m2026-08-21 12:20:59.888\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m183\u001b[0m - \u001b[1mBuilding timeline...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:48.033\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36madd_column_temporal_market_shares_to_timeline\u001b[0m:\u001b[36m587\u001b[0m - \u001b[1mNo time-explicit databases are provided. Mapping to time-explicit databases is not possible.\u001b[0m\n" + "\u001b[32m2026-08-21 12:20:59.898\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36madd_column_temporal_market_shares_to_timeline\u001b[0m:\u001b[36m627\u001b[0m - \u001b[1mNo time-explicit databases are provided. Mapping to time-explicit databases is not possible.\u001b[0m\n" ] }, { @@ -369,10 +369,10 @@ "id": "1c833eff", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:48.041573Z", - "iopub.status.busy": "2026-08-04T07:25:48.041490Z", - "iopub.status.idle": "2026-08-04T07:25:48.054419Z", - "shell.execute_reply": "2026-08-04T07:25:48.053994Z" + "iopub.execute_input": "2026-08-21T10:20:59.905124Z", + "iopub.status.busy": "2026-08-21T10:20:59.905051Z", + "iopub.status.idle": "2026-08-21T10:20:59.916014Z", + "shell.execute_reply": "2026-08-21T10:20:59.915648Z" } }, "outputs": [ @@ -380,14 +380,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:48.045\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m529\u001b[0m - \u001b[1mExpanding matrices...\u001b[0m\n" + "\u001b[32m2026-08-21 12:20:59.908\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m634\u001b[0m - \u001b[1mExpanding matrices...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:48.048\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m548\u001b[0m - \u001b[1mCalculating dynamic inventory...\u001b[0m\n" + "\u001b[32m2026-08-21 12:20:59.910\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m653\u001b[0m - \u001b[1mCalculating dynamic inventory...\u001b[0m\n" ] } ], @@ -401,10 +401,10 @@ "id": "4a51cd8a", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:48.055579Z", - "iopub.status.busy": "2026-08-04T07:25:48.055510Z", - "iopub.status.idle": "2026-08-04T07:25:48.057993Z", - "shell.execute_reply": "2026-08-04T07:25:48.057687Z" + "iopub.execute_input": "2026-08-21T10:20:59.917251Z", + "iopub.status.busy": "2026-08-21T10:20:59.917178Z", + "iopub.status.idle": "2026-08-21T10:20:59.919509Z", + "shell.execute_reply": "2026-08-21T10:20:59.919216Z" } }, "outputs": [ @@ -458,10 +458,10 @@ "id": "b86341cc", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:48.059254Z", - "iopub.status.busy": "2026-08-04T07:25:48.059152Z", - "iopub.status.idle": "2026-08-04T07:25:48.060765Z", - "shell.execute_reply": "2026-08-04T07:25:48.060464Z" + "iopub.execute_input": "2026-08-21T10:20:59.920807Z", + "iopub.status.busy": "2026-08-21T10:20:59.920741Z", + "iopub.status.idle": "2026-08-21T10:20:59.922216Z", + "shell.execute_reply": "2026-08-21T10:20:59.921921Z" } }, "outputs": [], @@ -483,10 +483,10 @@ "id": "c01ea15d", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:48.061741Z", - "iopub.status.busy": "2026-08-04T07:25:48.061666Z", - "iopub.status.idle": "2026-08-04T07:25:48.063447Z", - "shell.execute_reply": "2026-08-04T07:25:48.063152Z" + "iopub.execute_input": "2026-08-21T10:20:59.923364Z", + "iopub.status.busy": "2026-08-21T10:20:59.923304Z", + "iopub.status.idle": "2026-08-21T10:20:59.925223Z", + "shell.execute_reply": "2026-08-21T10:20:59.924822Z" } }, "outputs": [], @@ -502,10 +502,10 @@ "id": "246683b4", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:48.064502Z", - "iopub.status.busy": "2026-08-04T07:25:48.064431Z", - "iopub.status.idle": "2026-08-04T07:25:48.077250Z", - "shell.execute_reply": "2026-08-04T07:25:48.076922Z" + "iopub.execute_input": "2026-08-21T10:20:59.926245Z", + "iopub.status.busy": "2026-08-21T10:20:59.926173Z", + "iopub.status.idle": "2026-08-21T10:20:59.935312Z", + "shell.execute_reply": "2026-08-21T10:20:59.934902Z" } }, "outputs": [ @@ -541,36 +541,36 @@ " 0\n", " 2038-01-01 05:49:12\n", " 1.922234e-13\n", - " 342931108592119808\n", - " 342931108659228673\n", + " 349135793641672704\n", + " 349135793700392961\n", " \n", " \n", " 1\n", " 2039-01-01 11:38:24\n", " 1.766044e-13\n", - " 342931108592119808\n", - " 342931108659228673\n", + " 349135793641672704\n", + " 349135793700392961\n", " \n", " \n", " 2\n", " 2040-01-01 17:27:36\n", " 1.622546e-13\n", - " 342931108592119808\n", - " 342931108659228673\n", + " 349135793641672704\n", + " 349135793700392961\n", " \n", " \n", " 3\n", " 2040-12-31 23:16:48\n", " 1.490707e-13\n", - " 342931108592119808\n", - " 342931108659228673\n", + " 349135793641672704\n", + " 349135793700392961\n", " \n", " \n", " 4\n", " 2042-01-01 05:06:00\n", " 1.369581e-13\n", - " 342931108592119808\n", - " 342931108659228673\n", + " 349135793641672704\n", + " 349135793700392961\n", " \n", " \n", " ...\n", @@ -583,36 +583,36 @@ " 84\n", " 2122-01-01 14:42:00\n", " 1.556748e-16\n", - " 342931108592119808\n", - " 342931108659228673\n", + " 349135793641672704\n", + " 349135793700392961\n", " \n", " \n", " 85\n", " 2123-01-01 20:31:12\n", " 1.430256e-16\n", - " 342931108592119808\n", - " 342931108659228673\n", + " 349135793641672704\n", + " 349135793700392961\n", " \n", " \n", " 86\n", " 2124-01-02 02:20:24\n", " 1.314042e-16\n", - " 342931108592119808\n", - " 342931108659228673\n", + " 349135793641672704\n", + " 349135793700392961\n", " \n", " \n", " 87\n", " 2125-01-01 08:09:36\n", " 1.207270e-16\n", - " 342931108592119808\n", - " 342931108659228673\n", + " 349135793641672704\n", + " 349135793700392961\n", " \n", " \n", " 88\n", " 2126-01-01 13:58:48\n", " 1.109175e-16\n", - " 342931108592119808\n", - " 342931108659228673\n", + " 349135793641672704\n", + " 349135793700392961\n", " \n", " \n", "\n", @@ -621,17 +621,17 @@ ], "text/plain": [ " date amount flow activity\n", - "0 2038-01-01 05:49:12 1.922234e-13 342931108592119808 342931108659228673\n", - "1 2039-01-01 11:38:24 1.766044e-13 342931108592119808 342931108659228673\n", - "2 2040-01-01 17:27:36 1.622546e-13 342931108592119808 342931108659228673\n", - "3 2040-12-31 23:16:48 1.490707e-13 342931108592119808 342931108659228673\n", - "4 2042-01-01 05:06:00 1.369581e-13 342931108592119808 342931108659228673\n", + "0 2038-01-01 05:49:12 1.922234e-13 349135793641672704 349135793700392961\n", + "1 2039-01-01 11:38:24 1.766044e-13 349135793641672704 349135793700392961\n", + "2 2040-01-01 17:27:36 1.622546e-13 349135793641672704 349135793700392961\n", + "3 2040-12-31 23:16:48 1.490707e-13 349135793641672704 349135793700392961\n", + "4 2042-01-01 05:06:00 1.369581e-13 349135793641672704 349135793700392961\n", ".. ... ... ... ...\n", - "84 2122-01-01 14:42:00 1.556748e-16 342931108592119808 342931108659228673\n", - "85 2123-01-01 20:31:12 1.430256e-16 342931108592119808 342931108659228673\n", - "86 2124-01-02 02:20:24 1.314042e-16 342931108592119808 342931108659228673\n", - "87 2125-01-01 08:09:36 1.207270e-16 342931108592119808 342931108659228673\n", - "88 2126-01-01 13:58:48 1.109175e-16 342931108592119808 342931108659228673\n", + "84 2122-01-01 14:42:00 1.556748e-16 349135793641672704 349135793700392961\n", + "85 2123-01-01 20:31:12 1.430256e-16 349135793641672704 349135793700392961\n", + "86 2124-01-02 02:20:24 1.314042e-16 349135793641672704 349135793700392961\n", + "87 2125-01-01 08:09:36 1.207270e-16 349135793641672704 349135793700392961\n", + "88 2126-01-01 13:58:48 1.109175e-16 349135793641672704 349135793700392961\n", "\n", "[89 rows x 4 columns]" ] @@ -655,10 +655,10 @@ "id": "5868fb38", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:48.078267Z", - "iopub.status.busy": "2026-08-04T07:25:48.078199Z", - "iopub.status.idle": "2026-08-04T07:25:48.210035Z", - "shell.execute_reply": "2026-08-04T07:25:48.209537Z" + "iopub.execute_input": "2026-08-21T10:20:59.936433Z", + "iopub.status.busy": "2026-08-21T10:20:59.936360Z", + "iopub.status.idle": "2026-08-21T10:21:00.005854Z", + "shell.execute_reply": "2026-08-21T10:21:00.005514Z" } }, "outputs": [ @@ -691,10 +691,10 @@ "id": "40c30c15", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:48.211241Z", - "iopub.status.busy": "2026-08-04T07:25:48.211152Z", - "iopub.status.idle": "2026-08-04T07:25:48.213039Z", - "shell.execute_reply": "2026-08-04T07:25:48.212647Z" + "iopub.execute_input": "2026-08-21T10:21:00.007107Z", + "iopub.status.busy": "2026-08-21T10:21:00.007037Z", + "iopub.status.idle": "2026-08-21T10:21:00.008985Z", + "shell.execute_reply": "2026-08-21T10:21:00.008606Z" } }, "outputs": [ @@ -726,10 +726,10 @@ "id": "7fdfc3c7", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:48.214579Z", - "iopub.status.busy": "2026-08-04T07:25:48.214508Z", - "iopub.status.idle": "2026-08-04T07:25:48.219142Z", - "shell.execute_reply": "2026-08-04T07:25:48.218700Z" + "iopub.execute_input": "2026-08-21T10:21:00.010257Z", + "iopub.status.busy": "2026-08-21T10:21:00.010186Z", + "iopub.status.idle": "2026-08-21T10:21:00.013918Z", + "shell.execute_reply": "2026-08-21T10:21:00.013515Z" } }, "outputs": [ @@ -768,10 +768,10 @@ "id": "bc445e2a", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:48.220319Z", - "iopub.status.busy": "2026-08-04T07:25:48.220247Z", - "iopub.status.idle": "2026-08-04T07:25:48.282452Z", - "shell.execute_reply": "2026-08-04T07:25:48.282076Z" + "iopub.execute_input": "2026-08-21T10:21:00.015127Z", + "iopub.status.busy": "2026-08-21T10:21:00.015055Z", + "iopub.status.idle": "2026-08-21T10:21:00.118518Z", + "shell.execute_reply": "2026-08-21T10:21:00.118105Z" } }, "outputs": [ @@ -866,10 +866,10 @@ "id": "d66dd743", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:48.284115Z", - "iopub.status.busy": "2026-08-04T07:25:48.284017Z", - "iopub.status.idle": "2026-08-04T07:25:48.289432Z", - "shell.execute_reply": "2026-08-04T07:25:48.289011Z" + "iopub.execute_input": "2026-08-21T10:21:00.120083Z", + "iopub.status.busy": "2026-08-21T10:21:00.119994Z", + "iopub.status.idle": "2026-08-21T10:21:00.124879Z", + "shell.execute_reply": "2026-08-21T10:21:00.124465Z" } }, "outputs": [ @@ -907,10 +907,10 @@ "id": "eecd5073", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:48.290573Z", - "iopub.status.busy": "2026-08-04T07:25:48.290488Z", - "iopub.status.idle": "2026-08-04T07:25:48.295083Z", - "shell.execute_reply": "2026-08-04T07:25:48.294772Z" + "iopub.execute_input": "2026-08-21T10:21:00.125934Z", + "iopub.status.busy": "2026-08-21T10:21:00.125871Z", + "iopub.status.idle": "2026-08-21T10:21:00.130095Z", + "shell.execute_reply": "2026-08-21T10:21:00.129698Z" } }, "outputs": [ @@ -951,10 +951,10 @@ "id": "e075e0f9", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:48.296266Z", - "iopub.status.busy": "2026-08-04T07:25:48.296192Z", - "iopub.status.idle": "2026-08-04T07:25:48.299830Z", - "shell.execute_reply": "2026-08-04T07:25:48.299417Z" + "iopub.execute_input": "2026-08-21T10:21:00.131242Z", + "iopub.status.busy": "2026-08-21T10:21:00.131173Z", + "iopub.status.idle": "2026-08-21T10:21:00.134639Z", + "shell.execute_reply": "2026-08-21T10:21:00.134300Z" } }, "outputs": [], @@ -1049,13 +1049,20 @@ "id": "15b6f25d", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:48.300870Z", - "iopub.status.busy": "2026-08-04T07:25:48.300804Z", - "iopub.status.idle": "2026-08-04T07:25:48.345136Z", - "shell.execute_reply": "2026-08-04T07:25:48.344834Z" + "iopub.execute_input": "2026-08-21T10:21:00.135710Z", + "iopub.status.busy": "2026-08-21T10:21:00.135637Z", + "iopub.status.idle": "2026-08-21T10:21:00.185094Z", + "shell.execute_reply": "2026-08-21T10:21:00.184658Z" } }, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[2m12:21:00+0200\u001b[0m [\u001b[33m\u001b[1mwarning \u001b[0m] \u001b[1mRemoving project from project __test_database1__ list, but not deleting data; if you switch to this project again you will have the same data again. To delete data permanently, pass `(..., delete_dir=True)`.\u001b[0m\n" + ] + }, { "name": "stderr", "output_type": "stream", @@ -1069,21 +1076,21 @@ "output_type": "stream", "text": [ "\r", - "100%|██████████| 3/3 [00:00<00:00, 37008.56it/s]" + "100%|██████████| 3/3 [00:00<00:00, 33288.13it/s]" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[2m09:25:48+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n" + "\u001b[2m12:21:00+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[2m09:25:48+0200\u001b[0m [\u001b[33m\u001b[1mwarning \u001b[0m] \u001b[1mNot able to determine geocollections for all datasets. This database is not ready for regionalization.\u001b[0m\n" + "\u001b[2m12:21:00+0200\u001b[0m [\u001b[33m\u001b[1mwarning \u001b[0m] \u001b[1mNot able to determine geocollections for all datasets. This database is not ready for regionalization.\u001b[0m\n" ] }, { @@ -1106,14 +1113,14 @@ "output_type": "stream", "text": [ "\r", - "100%|██████████| 1/1 [00:00<00:00, 10305.42it/s]" + "100%|██████████| 1/1 [00:00<00:00, 11618.57it/s]" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[2m09:25:48+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n" + "\u001b[2m12:21:00+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n" ] }, { @@ -1142,10 +1149,10 @@ "id": "6ef97c64", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:48.346390Z", - "iopub.status.busy": "2026-08-04T07:25:48.346317Z", - "iopub.status.idle": "2026-08-04T07:25:48.381016Z", - "shell.execute_reply": "2026-08-04T07:25:48.380549Z" + "iopub.execute_input": "2026-08-21T10:21:00.186207Z", + "iopub.status.busy": "2026-08-21T10:21:00.186141Z", + "iopub.status.idle": "2026-08-21T10:21:00.218153Z", + "shell.execute_reply": "2026-08-21T10:21:00.217707Z" } }, "outputs": [ @@ -1153,99 +1160,99 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:48.346\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m142\u001b[0m - \u001b[1mInitializing TimexLCA object...\u001b[0m\n" + "\u001b[32m2026-08-21 12:21:00.186\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m174\u001b[0m - \u001b[1mInitializing TimexLCA object...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:48.347\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m149\u001b[0m - \u001b[1mNo database_dates provided. Treating the databases containing the functional unit as dynamic. No remapping of inventories to time explicit databases will be done.\u001b[0m\n" + "\u001b[32m2026-08-21 12:21:00.186\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m_resolve_database_dates\u001b[0m:\u001b[36m312\u001b[0m - \u001b[1mNo database_dates provided, and no database in this project carries `representative_time` metadata. Treating the databases containing the functional unit as dynamic. No remapping of inventories to time explicit databases will be done.\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:48.347\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m163\u001b[0m - \u001b[1mCalculating base LCA...\u001b[0m\n" + "\u001b[32m2026-08-21 12:21:00.187\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m194\u001b[0m - \u001b[1mCalculating base LCA...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:48.352\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m180\u001b[0m - \u001b[1mCollecting node infos...\u001b[0m\n" + "\u001b[32m2026-08-21 12:21:00.191\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m211\u001b[0m - \u001b[1mCollecting node infos...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:48.353\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m192\u001b[0m - \u001b[1mLoading node metadata from 1 database(s)...\u001b[0m\n" + "\u001b[32m2026-08-21 12:21:00.192\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m223\u001b[0m - \u001b[1mLoading node metadata from 1 database(s)...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:48.354\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m229\u001b[0m - \u001b[1mTimexLCA initialized.\u001b[0m\n" + "\u001b[32m2026-08-21 12:21:00.192\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m260\u001b[0m - \u001b[1mTimexLCA initialized.\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:48.354\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m358\u001b[0m - \u001b[1mNo edge filter function provided. Skipping all edges in background databases.\u001b[0m\n" + "\u001b[32m2026-08-21 12:21:00.193\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m453\u001b[0m - \u001b[1mNo edge filter function provided. Skipping all edges in background databases.\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:48.354\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m379\u001b[0m - \u001b[1mCreating activity time mapping...\u001b[0m\n" + "\u001b[32m2026-08-21 12:21:00.193\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m474\u001b[0m - \u001b[1mCreating activity time mapping...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:48.354\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m112\u001b[0m - \u001b[1mTraversing supply chain graph...\u001b[0m\n" + "\u001b[32m2026-08-21 12:21:00.193\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m112\u001b[0m - \u001b[1mTraversing supply chain graph...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:48.356\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m186\u001b[0m - \u001b[1mBuilding timeline...\u001b[0m\n" + "\u001b[32m2026-08-21 12:21:00.195\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m183\u001b[0m - \u001b[1mBuilding timeline...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:48.366\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36madd_column_temporal_market_shares_to_timeline\u001b[0m:\u001b[36m587\u001b[0m - \u001b[1mNo time-explicit databases are provided. Mapping to time-explicit databases is not possible.\u001b[0m\n" + "\u001b[32m2026-08-21 12:21:00.203\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36madd_column_temporal_market_shares_to_timeline\u001b[0m:\u001b[36m627\u001b[0m - \u001b[1mNo time-explicit databases are provided. Mapping to time-explicit databases is not possible.\u001b[0m\n" ] }, { - "name": "stderr", + "name": "stdout", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:48.371\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m529\u001b[0m - \u001b[1mExpanding matrices...\u001b[0m\n" + "Starting graph traversal\n", + "Calculation count: 0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 09:25:48.374\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m548\u001b[0m - \u001b[1mCalculating dynamic inventory...\u001b[0m\n" + "\u001b[32m2026-08-21 12:21:00.209\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m634\u001b[0m - \u001b[1mExpanding matrices...\u001b[0m\n" ] }, { - "name": "stdout", + "name": "stderr", "output_type": "stream", "text": [ - "Starting graph traversal\n", - "Calculation count: 0\n" + "\u001b[32m2026-08-21 12:21:00.211\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m653\u001b[0m - \u001b[1mCalculating dynamic inventory...\u001b[0m\n" ] } ], @@ -1275,10 +1282,10 @@ "id": "f6ff1eb3", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:48.382353Z", - "iopub.status.busy": "2026-08-04T07:25:48.382270Z", - "iopub.status.idle": "2026-08-04T07:25:48.384529Z", - "shell.execute_reply": "2026-08-04T07:25:48.384192Z" + "iopub.execute_input": "2026-08-21T10:21:00.219399Z", + "iopub.status.busy": "2026-08-21T10:21:00.219328Z", + "iopub.status.idle": "2026-08-21T10:21:00.221486Z", + "shell.execute_reply": "2026-08-21T10:21:00.221206Z" } }, "outputs": [], @@ -1304,10 +1311,10 @@ "id": "e3b6c6b4", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:48.385687Z", - "iopub.status.busy": "2026-08-04T07:25:48.385615Z", - "iopub.status.idle": "2026-08-04T07:25:48.447661Z", - "shell.execute_reply": "2026-08-04T07:25:48.447223Z" + "iopub.execute_input": "2026-08-21T10:21:00.222709Z", + "iopub.status.busy": "2026-08-21T10:21:00.222642Z", + "iopub.status.idle": "2026-08-21T10:21:00.283087Z", + "shell.execute_reply": "2026-08-21T10:21:00.282626Z" } }, "outputs": [ @@ -1348,10 +1355,10 @@ "id": "4668245f", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:48.449664Z", - "iopub.status.busy": "2026-08-04T07:25:48.449521Z", - "iopub.status.idle": "2026-08-04T07:25:48.508544Z", - "shell.execute_reply": "2026-08-04T07:25:48.508087Z" + "iopub.execute_input": "2026-08-21T10:21:00.284483Z", + "iopub.status.busy": "2026-08-21T10:21:00.284388Z", + "iopub.status.idle": "2026-08-21T10:21:00.342640Z", + "shell.execute_reply": "2026-08-21T10:21:00.342270Z" } }, "outputs": [ @@ -1384,10 +1391,10 @@ "id": "9cae721c", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:48.509809Z", - "iopub.status.busy": "2026-08-04T07:25:48.509710Z", - "iopub.status.idle": "2026-08-04T07:25:48.561832Z", - "shell.execute_reply": "2026-08-04T07:25:48.561066Z" + "iopub.execute_input": "2026-08-21T10:21:00.344006Z", + "iopub.status.busy": "2026-08-21T10:21:00.343910Z", + "iopub.status.idle": "2026-08-21T10:21:00.395186Z", + "shell.execute_reply": "2026-08-21T10:21:00.394669Z" } }, "outputs": [ @@ -1435,10 +1442,10 @@ "id": "a2027c21", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:48.563316Z", - "iopub.status.busy": "2026-08-04T07:25:48.563207Z", - "iopub.status.idle": "2026-08-04T07:25:48.619445Z", - "shell.execute_reply": "2026-08-04T07:25:48.618938Z" + "iopub.execute_input": "2026-08-21T10:21:00.396421Z", + "iopub.status.busy": "2026-08-21T10:21:00.396334Z", + "iopub.status.idle": "2026-08-21T10:21:00.447183Z", + "shell.execute_reply": "2026-08-21T10:21:00.446833Z" } }, "outputs": [], @@ -1498,10 +1505,10 @@ "id": "f62763da", "metadata": { "execution": { - "iopub.execute_input": "2026-08-04T07:25:48.620785Z", - "iopub.status.busy": "2026-08-04T07:25:48.620708Z", - "iopub.status.idle": "2026-08-04T07:25:48.623869Z", - "shell.execute_reply": "2026-08-04T07:25:48.623461Z" + "iopub.execute_input": "2026-08-21T10:21:00.448493Z", + "iopub.status.busy": "2026-08-21T10:21:00.448421Z", + "iopub.status.idle": "2026-08-21T10:21:00.451482Z", + "shell.execute_reply": "2026-08-21T10:21:00.451096Z" } }, "outputs": [ diff --git a/notebooks/tutorials/4_import_model_from_excel.ipynb b/notebooks/tutorials/4_import_model_from_excel.ipynb index f3c33b8c..07845b36 100644 --- a/notebooks/tutorials/4_import_model_from_excel.ipynb +++ b/notebooks/tutorials/4_import_model_from_excel.ipynb @@ -1,1101 +1,1069 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Loading your LCA model with temporal distributions from an excel file\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This notebook is essentially a short version of the [electric vehicle tutorial](./2_electric_vehicle_from_scratch.ipynb) example notebook, but shows how to import the foreground model from an [excel file](../data/electric_vehicle_foreground.xlsx). For a more detailed explaination of how timex works, please see one of the other notebooks. \n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Set up a bw project\n", - "\n", - "import bw2data as bd\n", - "\n", - "bd.projects.set_current(\"electric_vehicle_standalone_excel\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Fresh start\n", - "for db in list(bd.databases):\n", - " del bd.databases[db]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "100%|██████████| 1/1 [00:00<00:00, 7256.58it/s]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[2m17:01:00+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\n" - ] - } - ], - "source": [ - "# Add some background databases\n", - "\n", - "biosphere = bd.Database(\"biosphere\")\n", - "biosphere.register()\n", - "biosphere.write(\n", - " {\n", - " (\"biosphere\", \"CO2\"): {\n", - " \"type\": \"emission\",\n", - " \"name\": \"carbon dioxide\",\n", - " },\n", - " }\n", - ")\n", - "\n", - "background_2020 = bd.Database(\"background_2020\")\n", - "background_2020.register()\n", - "\n", - "background_2030 = bd.Database(\"background_2030\")\n", - "background_2030.register()\n", - "\n", - "background_2040 = bd.Database(\"background_2040\")\n", - "background_2040.register()\n", - "\n", - "background_2020.write({})\n", - "background_2030.write({})\n", - "background_2040.write({})\n", - "\n", - "background_databases = [\n", - " background_2020,\n", - " background_2030,\n", - " background_2040,\n", - "]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We now create some very simple processes within these databases. These process get only one aggregated CO2-emission each. The amounts of these emissions change over time." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "process_co2_emissions = {\n", - " \"glider\": (10, 5, 2.5), # for 2020, 2030 and 2040\n", - " \"powertrain\": (20, 10, 7.5),\n", - " \"battery\": (10, 5, 4),\n", - " \"electricity\": (0.5, 0.25, 0.075),\n", - " \"glider_eol\": (0.01, 0.0075, 0.005),\n", - " \"powertrain_eol\": (0.01, 0.0075, 0.005),\n", - " \"battery_eol\": (1, 0.5, 0.25),\n", - "}\n", - "\n", - "node_co2 = biosphere.get(\"CO2\")\n", - "\n", - "for component_name, gwis in process_co2_emissions.items():\n", - " for database, gwi in zip(background_databases, gwis):\n", - " database.new_node(component_name, name=component_name, location=\"somewhere\").save()\n", - " component = database.get(component_name)\n", - " component[\"reference product\"] = component_name \n", - " component.save()\n", - " production_amount = -1 if \"eol\" in component_name else 1\n", - " component.new_edge(input=component, amount=production_amount, type=\"production\").save()\n", - " component.new_edge(input=node_co2, amount=gwi, type=\"biosphere\").save()\n", - "\n", - "# register the databases\n", - "for db in bd.databases:\n", - " bd.Database(db).process() \n", - " " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Case study setup\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this study, we consider the following production system for our ev. Purple boxes are foreground, cyan boxes are background (i.e., ecoinvent/premise)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```{mermaid}\n", - "flowchart LR\n", - " glider_production(glider production):::ei-->ev_production\n", - " powertrain_production(powertrain production):::ei-->ev_production\n", - " battery_production(battery production):::ei-->ev_production\n", - " ev_production(ev production):::fg-->driving\n", - " electricity_generation(electricity generation):::ei-->driving\n", - " driving(driving):::fg-->used_ev\n", - " used_ev(used ev):::fg-->glider_eol(glider eol):::ei\n", - " used_ev-->powertrain_eol(powertrain eol):::ei\n", - " used_ev-->battery_eol(battery eol):::ei\n", - "\n", - " classDef ei color:#222832, fill:#3fb1c5, stroke:none;\n", - " classDef fg color:#222832, fill:#9c5ffd, stroke:none;\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Importing the product system from Excel\n", - "\n", - "As an alternative to generating your temporal foreground system in code as above, you can also import the case study processes from a BW25-excel file.\n", - "You need `bw2io > 0.9.14`, which supports the import of `TemporalDistributions` in the ExcelImporter or CSVImporter. You can consult the sample excel file under notebooks/data/electric_vehicle_foreground.xlsx for valid input formats for the TDs.\n", - "\n", - "Please make sure you created and processed the biosphere and the background databases for 2020, 2030 and 2040 using the code above." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Extracted 1 worksheets in 0.00 seconds\n", - "Applying strategy: csv_restore_tuples\n", - "Applying strategy: csv_restore_booleans\n", - "Applying strategy: csv_numerize\n", - "Applying strategy: csv_drop_unknown\n", - "Applying strategy: csv_restore_temporal_distributions\n", - "Applying strategy: csv_add_missing_exchanges_section\n", - "Applying strategy: normalize_units\n", - "Applying strategy: strip_biosphere_exc_locations\n", - "Applying strategy: set_code_by_activity_hash\n", - "Applying strategy: link_iterable_by_fields\n", - "Applying strategy: assign_only_product_as_production\n", - "Applying strategy: link_technosphere_by_activity_hash\n", - "Applying strategy: drop_falsey_uncertainty_fields_but_keep_zeros\n", - "Applying strategy: convert_uncertainty_types_to_integers\n", - "Applying strategy: convert_activity_parameters_to_list\n", - "Applied 15 strategies in 0.02 seconds\n", - "Applying strategy: link_iterable_by_fields\n", - "Applying strategy: link_iterable_by_fields\n", - "Graph statistics for `foreground` importer:\n", - "3 graph nodes:\n", - "\tNone: 3\n", - "12 graph edges:\n", - "\ttechnosphere: 9\n", - "\tproduction: 3\n", - "12 edges to the following databases:\n", - "\tbackground_2020: 7\n", - "\tforeground: 5\n", - "0 unique unlinked edges (0 total):\n", - "\n", - "\n", - "\u001b[2m17:01:00+0200\u001b[0m [\u001b[33m\u001b[1mwarning \u001b[0m] \u001b[1mNot able to determine geocollections for all datasets. This database is not ready for regionalization.\u001b[0m\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "100%|██████████| 3/3 [00:00<00:00, 4463.61it/s]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[2m17:01:00+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n", - "Created database: foreground\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\n" - ] - } - ], - "source": [ - "if \"foreground\" in bd.databases:\n", - " del bd.databases[\"foreground\"] # to make sure we import the foreground from scratch from the excel file\n", - "\n", - "import bw2io as bi\n", - "\n", - "ei = bi.ExcelImporter(\"../data/electric_vehicle_foreground.xlsx\", sheet_name=\"easy_tds\") \n", - "ei.apply_strategies()\n", - "ei.match_database(\"background_2020\", fields=[\"name\", \"reference product\"])\n", - "ei.match_database(\"biosphere\", fields=[\"name\", \"categories\"])\n", - "\n", - "ei.statistics() #0 unique unlinked edges means that all foreground exchanges are linked and we can successfully import the database.\n", - "\n", - "ei.write_database()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "let's check if the ExcelImporter imported the `TemporalDistributions` correctly:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[ 0 31556952 63113904 94670856 126227808 157784760 189341712\n", - " 220898664 252455616 284012568 315569520 347126472 378683424 410240376\n", - " 441797328 473354280]\n", - "[0.0625 0.0625 0.0625 0.0625 0.0625 0.0625 0.0625 0.0625 0.0625 0.0625\n", - " 0.0625 0.0625 0.0625 0.0625 0.0625 0.0625]\n", - "timedelta64[s]\n" - ] - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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Jbgs1I4dO9xUF5FNjEOGDHGT244bN866du0af79m/te1PhX2dFknVd/UzPnOO++4vx84cGCC51WftNGjR7tw16xZM3cZKfV90yWk1q9fb82bN0+wfAAAgFBNZiv169d3M/jr+pkKWprq4qqrrnK36blup6hPmi4Npak0NL3G+PHj7ZNPPrFu3brZBx98kOS6ndK6dWt31QHNnTZv3jz3fD4IalkapQEAAJAdcsTFxZ3LliUjMDTAQVXHUqVKRVWHyawS9vWXsG8D1p/3P8z7v/AZOB6V+0BUVNIAAADChpAGAAAQQIQ0AACAACKkAQAABBAhDQAAIIAIaQAAAAFESAMAAAggQhoAAEAAEdIAAAACiJAGAAAQQIQ0AACAACKkAQAABBAhDQAAIIAIaQAAAAFESAMAAAggQhoAAEAAEdIAAAACiJAGAAAQQIQ0AACAACKkAQAABBAhDQAAIIAIaQAAAAFESAMAAAggQhoAAEAAEdIAAAACiJAGAAAQQIQ0AACAACKkAQAABBAhDQAAIIAIaQAAAAFESAMAAAggQhoAAEAshbRp06bZokWL0vXYxYsXu8cDAAAgm0PaAw88YC+88EK6Hjty5Ejr3bt3Zp8KAAAgdM6rufPcuXNZ90oAAABwYfuk/fjjj5Y3b94L8VQAAAAxIXd2Lvz48eP20Ucf2ddff20VKlTIzqcCAAAIZ0h75pln7Lnnnktw3yeffGJXXHFFuv7+9ttvz/irAwAACKncme2DliNHjnT1SStUqJD9+te/tocffjhzrxAAACCE0h3S7r//fuvSpYv7t8LZzTffbNWrV7fJkycn+3iFuMsuu8yKFi2ada8WAAAgJNId0goXLux+vLvuusv1MytdunR2vTYAAIDQyvTozvHjx9tDDz1kF8rq1autY8eOLhSWKFHCmjZtarNnz87QMk6cOGHPPvusqwAWL17cKlWqZH369LG9e/em+DcnT560sWPHWsOGDa1kyZLup3bt2ta/f/8kj33qqaesbdu2dsMNN9jVV19t5cqVc383btw4++mnnzK13gAAIJyydXRnVlm6dKl16NDBTePRvn17K1CggM2ZM8d69uxpO3futAcffDDNZZw9e9Y11+oqCTVr1rQ2bdrYpk2bbMqUKbZkyRJbuHChFStWLMHfxMXFueddtWqV1apVy3r06OHu37Ztm82aNctGjBiR4PETJ0608uXLW6NGjezKK6+0I0eO2LJly+yJJ55wV1xYsGCBawIGAADI9pCmEDJ//nzbvHmzCyUpDSZQHzUFq4w6ffq0q3blzJnT5s6da1WrVnX3DxgwwJo0aWJPPvmkq16l1ew6depUF9DuvPNOF6b0emTSpEnWt29fGzZsmI0aNSrB3+gqCarg6fGq4iV+XYlpqpHk5oP7/e9/b2+88Ya9/vrrdu+992Z4GwAAgPDJdEg7deqUCxw+eKU10tOHosxU0bZs2WJdu3aND2ii/nEKV7o8lapUjzzySKrLUcVMBg0alOC1qBr30ksv2YwZM2z48OGWL18+d/+KFStcKNTI1MQBTXLnTrrpUpqwt127di6kKcgCAABka0h78cUX7e2333aB57bbbrNbbrnFNfGp4pWVVKmTxo0bJ/mdKmmyfPnyNCfVXblyZbIDHfT61TypUapr1qyxOnXquPvVnOkD1v79++29995zfdeuvfZaa9asWbrnhxM1c8r111+f7r8BAADhlumQpsqTAo6aAtVvK7uo35ior1di6vyv/mlpVahUiVOfNHXkT46/X8/lQ9ratWvj7+vVq5cdOnQo/vF6TlXf1D8uOaNHj3YDBQ4ePOgm/FX4U8jUiFgAAIBsDWnbt2+3a665JlsDmvhwpElxk1OwYMEEASq1ZUROIRLJLztyOfv27XO3gwcPds2djz76qBUpUsRVxTSyU8GtYsWKVqVKlWRD2oEDB+L/36lTJxs5cqRdcskl6Vjj/1/5y0oaoRp5GzZhX38J+zZg/Xn/I/eDMOIzcDLBdriYMnIt80yHNAUeVbJilSpvUrlyZZswYUJ8PzYFrsOHD1u/fv3s5ZdftjFjxiT5W1/Z27Nnj+tT95e//MVNGfLmm2+65tK07Nq1y86cOZPl66TXE2ZhX38J+zZg/Xn/w47PwJ6Luv1z5cqVYqteloa0unXrutGSSqV58uSx7JJclSuSApMqXOlZhpof01ut8/9u0aJFkkEPLVu2dCFNzZipUYhVFU5NtWruHDhwYIpXaIikeeCykt4jfTD1erLzvQqqsK+/hH0bsP68/2He/4XPwMmo3AcyHdLU5KepN3ThdY2YzC6+L5r6hulSVJG0wTXthyanTU3ZsmXdgIaU+q75+yP7vWmQgUJYck2k/r70Nkvq9SlI+kEQWVkKzQjtmNm17GgQ9vWXsG8D1p/3P8z7v/AZyBNV+0Cmh2Kq0qSAptn41YFe01Vs2LDBduzYkeJPZit2snjx4iS/UyUv8jEp0bQaNWrUsI0bN7q+dJE0dciHH35o+fPnt2rVqsXfX69ePXerdUrM35feS2IpSKpal94+aQAAAJmupN10003x//7oo4/cT2rUZKipLDKqQYMGrhI2c+ZM11nfz5Wmpkt1xtdZQefOneMfv3v3bheIVNKMrIJ1797dzX02dOjQBJPZqvlx69at7moCfo400QS56kumUaz33Xefu9STLxlrPjU/PYenZej5Lr/88iTzyT322GOuj5um7gAAAMjWkJbW5LXn+/jISWM13YVGkbZq1SrBZaFUndMVB8qUKRP/+CFDhrjJbXW9TE2A6+mSULrWp8KeLuuk6puaOd955x339+ovlrhSqFGaCncKV7qMlJosdQmp9evXW/PmzRMsX3O1aXLdX/7yly5Uah41Ncfq8d99951dd9119uc//zlT2wAAAIRPpkPajz/+aBdK/fr1Xf83VbAUtFSd0qhLBbKU5ipLTH3SdGkoTcI7ffp0d4F4Vb26devmAlri63ZK69atXTOurtE5b948O3bsmOu3pufVJaM0SsNTOFOg+/jjj+3zzz93lT6FSYUzVQB/97vfcd1OAACQbjni4uIyV+JC1NAAB1UdS5UqFVUdJrNK2Ndfwr4NWH/e/zDv/8Jn4HhU7gNZew0nAAAAZAlCGgAAQCz1Sbv99tsz9HiNplRnfwAAAGRjSEvPxKx+mguN7Ew8az8AAACyIaRpiouU/PTTT/btt9+6a1VqzrJHHnnErr766sw+FQAAQOhkOqRp3rG0aBJXTT3x6quvuguNAwAAIAADBzQDvy4b9f3337tLSAEAACAgozt1eaZKlSrZe++9l91PBQAAEDMuyBQcJ06ccJdIAgAAQEBC2hdffGGbNm2yokWLZvdTAQAAxIxMDxzQ5RVSoik39u7da59++qmNGTPG/f+2227L7FMBAACETqZD2k033ZSuxymglS1b1h5//PHMPhUAAEDoZDqkKXylJn/+/FauXDlr2bKl9e7d2woVKpTZpwIAAAidTIe0H3/8MWtfCQAAAOJxgXUAAIAAIqQBAAAEUKabOyN99tlntmDBAtu4caMdPnzYChYsaBUrVrRmzZqle4ABAAAAsiikHThwwB544AEX0BIPJsiRI4c9/fTT1qJFC3dpqCuuuOJ8ngrnYddPZ2xlXE7LVfSMlct7YTfld0fP2KZDp618odx2bf5cF+W5S1565oI+b1DW+0I/dxjXmecO1zZnPwvPe32xn/u8Q5quInDHHXfY559/7sJZ1apVrXLlynb11Vfb7t277auvvrJ169bZ/PnzrX379vb+++/bpZdemrWvHmma8s1R+9PyODtreS3nF3E2qq7Zbyrmv3DP/Z84O3vOLGcOs1F1ilyc5zazx3+ey/5QysK13hfwucO4zjx3uLY5+1l43uuL/dxZ0idt4sSJLoSVKFHC3nrrLVuyZIlNmDDBBg8e7G71/7ffftuuvfZa97i///3vWfvKka6zALeT/e//un3oP3Hu/gv23P8rrur2oj23mT39bR5XUQzVel+g5w7jOvPc4drm7GcXdnuHeZtnWUibPXu2a9KcOnWqNWjQINnH1K9f315//XVXaZs1a1ZmnwqZpDKt38m8M+fMNh86HbrnPms5bMvhsxfluWN9m4dxnXnucG1z9rMLu73DvM2zLKRpkECFChVcM2dq9HsNItDjcWGpHV1l2ki5cpiVK5Q7dM+d087ZzwrmDN16X4jnDuM689zh2ubsZxd2e4d5myeW6aPWqVOnLF++fOl6rB6nx+PCUkdHtaNr5xLdvlinyAXpABm053785yetxGXhW+8L8dxhXGeeO1zbnP3swm7vMG/zxHLExcWlfn2nFPziF7+wnTt32tdff21FihRJ9coE119/veubtmrVqsw8Fc7T5gNH7dNNu+2W8ldbuSsubMdHteGrRFzuIo3M0XNfe+lpO7N/l5UqVcry5s0bmvWOfO7jx4/bjh07snUbBG2dI2Xn+gd5vbNz/aNhvbN6/aNpnbNyG0Tzep/P+l/M9fYyXbtr1KiRGwygKTgmTZqU7Epro+j3J0+etKZNm57va0UmqYJUo8jZC1JJSkw79sXauf1zHz9+znbsvzjPfTFcrOcO4zrz3OHa5uxnF961If1sn3dI+9Of/mTTp093U2yo31mPHj3cFBxXXXWV/fDDD24KjldffdX27dvnJrf94x//mLWvHAAAIIZlOqSp+VIjOxXO9u7dayNGjEjyGI3qLFasmAtrejwAAADS57yGKtx666326aefumbPDz74wI3gPHLkiBUoUMCN6GzevLn99re/5WoDAAAAGXTe40l1uacBAwa4HwAAAGSN7J84CgAAABlGSAMAAIjF5k5do3PBggW2efNmO3r0qBsskBxdQmrOnDnn+3QAAAChkOmQduzYMTeyUwMGJKVwFhnSAAAAkM0hbfjw4a6Cljt3bmvVqpVVq1bNTbdBGAMAALiIIW3WrFmWM2dON6Ft48aNs+ClAAAA4LwHDuhKAmXKlCGgAQAABO2KA5dddlnWvhoAAACcXyWtbdu29vXXX9vu3bszuwgAAABkdUh76KGHrHz58tazZ0/7/vvvM7sYAAAAZGVzZ8GCBW3evHl27733Wo0aNaxJkyZWrly5VJtAH3nkkcw+HQAAQKic12S206ZNs08++cTNmTZ37twUH6c51DQ1ByENAAAgm0OaAtoTTzzh/n3NNdfYDTfckK3zpK1evdrNzaZQePr0aatcubL17t3b7rjjjnQv48SJEzZq1Cg3bch3331nl19+ud122202cOBAu/LKK5P9m5MnT9rf/vY3mzlzpn377bfuvlKlSlndunVtxIgR8Y87cOCAu6LC/Pnzbf369a4JuECBAla9enW7//77XaURAAAg20PauHHjXCAbMGCAPfzww5YrVy7LLkuXLrUOHTpY3rx5rX379i78KBCpP9zOnTvtwQcfTHMZZ8+etS5dutiiRYusZs2a1qZNG9u0aZNNmTLFXdpq4cKFLmRGiouLc8+7atUqq1WrlrvCgmzbts3NExcZ0t566y3r27evC6z169e3EiVKuCD4zjvvuGUPHTrU/vjHP2bD1gEAALEo0yFN1+q86qqr7NFHH7XspKpZnz593MS5alKtWrWqu1/hUNWpJ5980o00LV26dKrLmTp1qgtod955p02cODG+4jdp0iQXroYNG+aqbJFUqVMFT4/v2LFjktcVSYMoVF1UZU6v1du4cWP869QyFOIAAACybXSnBg6oWpTdVEXbsmWLC1c+oEnhwoVduFJzpMJRWlQxk0GDBiVoklU1rmzZsjZjxgzXt85bsWKFC4WdOnVKEtBEl8OK1KBBA2vZsmWCgCYVKlRw1b9Tp07Zp59+msG1BwAAYZXpkHbrrbe6PlrHjx+37LRs2TJ3m9ylp3w/r+XLl6e6DL3GlStXusCUuOKmwNaoUSM7evSorVmzJv5+NWdKu3btbP/+/fbaa6/ZyJEjXX829T/LiEsuucTdZmeTMAAAiC2ZDmmPPfaYG7X55z//2bKT+o355sTEihcv7vqnqek1NarEqU+apghJjr/fP5esXbs2/j5dPF793tSvrFevXq6i50NcWg4dOmRvv/22609Xp06ddP0NAABApvuk7dmzx02poeDy3//+1+6+++4050nTiMiMUsiRQoUKpdjs6h+T1jLURJocv+zI5ejapDJ48GDX3Km+d0WKFLEFCxZY//79XVirWLGiValSJdXnVpPsDz/8YI8//rhdccUVlh5ZXZ1Uk3DkbdiEff0l7NuA9ef9j9wPwojPwMkE2+FiUtEm20Na69atXVOhqmlffvmlq6ylRo9Vs2G0UOVNNNXHhAkT4vuxqY/a4cOHrV+/fvbyyy/bmDFjUlzGkCFD3NQdTZs2dY9Pr127dtmZM2csqylYh1nY11/Cvg1Yf97/sOMzsOeibn91e0qpVS9LQ1rJkiWzbU60tKpckRSYVOFKzzIOHjyY7mqd/3eLFi2SrKcGCCh0RfZhS0yjRV988UU3HYf6s2WkP1pWD8jQmYM+mGoezpMnj4VN2Ndfwr4NWH/e/zDv/8Jn4GRU7gOZDmmff/65XQi+L5r6ht18880JfqcNfuTIETdhbGo0elOjLlPqu+bvj+z3pkEGCmHJNZH6+1JqllRA0xxqGlzxr3/9y/Lly2fZVQrNCO2Y2bXsaBD29ZewbwPWn/c/zPu/8BnIE1X7QKYHDmSERlb+6U9/ytTf+n5sixcvTvI7zXsW+ZiUKCTp+qKas2z79u0Jfqfm2g8//NDy58/vBgh49erVc7cbNmxIsjx/X3Jzs/mAptf0xhtvpNpHDwAA4IKHNHW8V3+t2rVrW/PmzePnKcsozT+mSpj6dq1bty7+fjVdakoMnRV07tw5/v7du3fbN998k6Rps3v37u5WAx0UzLzJkyfb1q1b3eCAyIqXJsgtWrSomz9Nfe4iS8a6PJWfniPSU0895QKa1pmABgAALtoF1pPrbP/+++/bP//5T/vggw/crPw+EKmSlakXmDu3vfTSS+7yTK1atUpwWagdO3a4mfzLlCmToLO+JrfVZau6du0af78uCTV79mwX9nRZJ1W61Mypyzbp73X9zkjqkzZ69GgX7po1a+YuI6W+b7qElK7NqeAZufzXX3/dnn/+efd6ta56zYmp+dNX6AAAALI9pKlypZCiiV413YQonOmi5RoNqek5KlWqlOnlq/O9LlyuCpaClmbv16hLBTKFtvRQnzRdGkqd+fU6x48f7y6w3q1bNxfQEl+3049g1VUHVB2bN2+euyKB+q3peXXJqMjBAL4ZVcF07NixKb4OQhoAAEiPHHFxcf/X9pcB6rCvCV1VNVOfMx/MNLu+QpRCz9dff80s+wGgAQ6qOpYqVSqqOkxmlbCvv4R9G7D+vP9h3v+Fz8DxqNwHMlxJ0yWYFMzUTPjTTz/FN2feeOONrklR19jUyEhVmbgMEgAAQDaHtBdeeME1F+oSSz6YqTlTHe4Vzm644YZMvgQAAABkOqRpaglN6qrRlJrgVSMq1aGeahkAAEAApuDQ6EW152q6CgIaAADARQ5pDz/8sLsU1NGjR90cYHfccYe7uLgqbLoaAAAAAC5CSHv88cfts88+c1NgKKBdeuml9t1337kJZWvWrOnmDXv11VdTvD4mAAAAsqm5U33SGjZsaK+88oqb0PW5556zqlWruoEEK1assL59+9p1113nHnvmzBk3uS0AAAAu4GWhNPv+vffeax999JEtW7bMevXqZVdccYWdOHHC/X7//v0usD3xxBP21VdfZfZpAAAAQilLrt2p6TeeeeYZV11Tk6dGfWqGf12/c8KECe5ySE2aNMmKpwIAAAiFLL3Auq42oAuTa2DB559/7i63VK5cOdccumbNmqx8KgAAgJiWpSEt0jXXXGP9+vVzl4x699137a677squpwIAAIg5WXKB9bTUrVvX/QAAAOAiV9IAAACQeYQ0AACAACKkAQAABBAhDQAAIIAIaQAAAAFESAMAAAggQhoAAEAAEdIAAAACiJAGAAAQQIQ0AACAACKkAQAABBAhDQAAIIAIaQAAAAFESAMAAAggQhoAAEAAEdIAAAACiJAGAAAQQIQ0AACAACKkAQAABBAhDQAAIIAIaQAAAAFESAMAAAggQhoAAEAAEdIAAAACiJAGAAAQQIQ0AACAACKkAQAABBAhDQAAIIAIaQAAAAEUNSFt9erV1rFjRytdurSVKFHCmjZtarNnz87QMk6cOGHPPvusVa9e3YoXL26VKlWyPn362N69e1P8m5MnT9rYsWOtYcOGVrJkSfdTu3Zt69+/f5LHzps3zx5++GG77bbb3GssUqSIDR8+PFPrCwAAwi23RYGlS5dahw4dLG/evNa+fXsrUKCAzZkzx3r27Gk7d+60Bx98MM1lnD171rp06WKLFi2ymjVrWps2bWzTpk02ZcoUW7JkiS1cuNCKFSuW4G/i4uLc865atcpq1aplPXr0cPdv27bNZs2aZSNGjEjweIW55cuXW6FChezqq6+2zZs3Z/GWAAAAYRH4kHb69GlX7cqZM6fNnTvXqlat6u4fMGCANWnSxJ588klr27atq7ClZurUqS6g3XnnnTZx4kTLkSOHu3/SpEnWt29fGzZsmI0aNSrB3/Tu3dtV8PR4VfESv67EnnjiCVehK1eunAtx99xzTxZsAQAAEEY5o6GKtmXLFheufECTwoULu3Cl5shp06aluRxVzGTQoEHxAU1UjStbtqzNmDHDjh07Fn//ihUrXCjs1KlTkoAmuXMnzbd16tSx8uXLJ1g+AABATIa0ZcuWudvGjRsn+Z0qaaImxtQcP37cVq5caRUqVEhScVOgatSokR09etTWrFkTf78qYdKuXTvbv3+/vfbaazZy5EibPn26HThwIEvWDQAAIGqbO9VvTFShSkxNi+qfllbfL1Xi1CdNzZDJ8ffruVQNk7Vr18bf16tXLzt06FD84/WcL730kusfBwAAEMqQ5sOROuMnp2DBggkCVGrLUBNpcvyyI5ezb98+dzt48GDX3Pnoo4+60ZoLFixwIzsV3CpWrGhVqlSxrKbKX1ZSk3DkbdiEff0l7NuA9ef9j9wPwojPwMkE2+Fi0iDImAlpF4sqb1K5cmWbMGFCfD8z9VE7fPiw9evXz15++WUbM2ZMlj/3rl277MyZM1m+3D179liYhX39JezbgPXn/Q87PgN7Lur2z5UrV4qtelEZ0pKrckVSYFKFKz3LOHjwYLqrdf7fLVq0SDIQoGXLli6kRfZhy0qaYy0r6cxBH0w1D+fJk8fCJuzrL2HfBqw/73+Y93/hM3AyKveBwIc03xdNfcNuvvnmBL/TBj9y5IibnDY1Gr2pKTxS6rvm74/s96ZBBgphyTWR+vuyulkyM6XQjNCOmV3LjgZhX38J+zZg/Xn/w7z/C5+BPFG1DwR+dGfdunXd7eLFi5P8TvOeRT4mJfny5bMaNWrYxo0bbfv27Ql+d+7cOfvwww8tf/78Vq1atfj769Wr5243bNiQZHn+vrTmZgMAAIjZkNagQQNXCZs5c6atW7cu/n41XWpKDJ0VdO7cOf7+3bt32zfffJOkabN79+7udujQoS6YeZMnT7atW7e6wQEKc54myC1atKibP+3LL79MUDL2l3rS9BwAAAChbO7UpLGa7kKXZ2rVqlWCy0Lt2LHDXXGgTJky8Y8fMmSIm9x23Lhx1rVr1/j7dUkoXetTYU+XdVL1Tc2c77zzjvv7gQMHJnhe9UkbPXq0C3fNmjVzl5FS3zddQmr9+vXWvHnzBMuXd999102AK3oO0f999U6jQR966KFs3V4AACA2BD6kSf369W3+/PmugqWgderUKTfqUoEsvXOVqU+aLg314osvuglpx48fb5dffrl169bNBbTE1+2U1q1bu5Cla3Tq4um6IoH6rel5dckojdKI9Pnnnye5+sEXX3zhfkTBkJAGAADSI0dcXNz/tf0hJmmAg6qOpUqViqoOk1kl7OsvYd8GrD/vf5j3f+EzcDwq94HA90kDAAAII0IaAABAABHSAAAAAoiQBgAAEECENAAAgAAipAEAAAQQIQ0AACCACGkAAAABREgDAAAIIEIaAABAABHSAAAAAoiQBgAAEECENAAAgAAipAEAAAQQIQ0AACCACGkAAAABREgDAAAIIEIaAABAABHSAAAAAoiQBgAAEECENAAAgAAipAEAAAQQIQ0AACCACGkAAAABREgDAAAIIEIaAABAABHSAAAAAoiQBgAAEECENAAAgAAipAEAAAQQIQ0AACCACGkAAAABREgDAAAIIEIaAABAABHSAAAAAoiQBgAAEECENAAAgAAipAEAAAQQIQ0AACCACGkAAAABREgDAAAIIEIaAABAAEVNSFu9erV17NjRSpcubSVKlLCmTZva7NmzM7SMEydO2LPPPmvVq1e34sWLW6VKlaxPnz62d+/eFP/m5MmTNnbsWGvYsKGVLFnS/dSuXdv69++f7OO//fZb69Gjh5UrV86uvvpqq1u3rr3yyit27ty5DK8zAAAIr9wWBZYuXWodOnSwvHnzWvv27a1AgQI2Z84c69mzp+3cudMefPDBNJdx9uxZ69Kliy1atMhq1qxpbdq0sU2bNtmUKVNsyZIltnDhQitWrFiCv4mLi3PPu2rVKqtVq5YLX7Jt2zabNWuWjRgxIsHjv/76a2vevLkdP37c2rVrZ9dcc40tWLDA+vXr5373/PPPZ/GWAQAAsSrwIe306dOu2pUzZ06bO3euVa1a1d0/YMAAa9KkiT355JPWtm1bV2FLzdSpU11Au/POO23ixImWI0cOd/+kSZOsb9++NmzYMBs1alSCv+ndu7er4OnxquIlfl2JaTmHDh2yGTNmWLNmzdx9TzzxhHt9fhm33HLLeW8TAAAQ+3JGQxVty5YtLlz5gCaFCxd2oUjNkdOmTUtzOaqYyaBBg+IDmqgaV7ZsWResjh07Fn//ihUrXCjs1KlTkoAmuXPnTtLM+Z///Mfq1asXH9AkT548LqjJP/7xjwyvPwAACKfAh7Rly5a528aNGyf5nSppsnz58lSXoebHlStXWoUKFZJU3BTYGjVqZEePHrU1a9bE36/mTFGz5f79++21116zkSNH2vTp0+3AgQMZep3qw5Y/f/40XycAAEDUNHeq35iUL18+ye/U+V/90zZv3pzqMlSJU580deZPjr9fz1WnTh3377Vr18bf16tXL9eM6ek5X3rpJdc/LvHrTO45cuXKZWXKlHH90tRMmrgKl1yozEqqNkbehk3Y11/Cvg1Yf97/yP0gjPgMnEywHS4m9a+PmZDmw1GhQoWS/X3BggUTBKjUlqEm0uT4ZUcuZ9++fe528ODBrrnz0UcftSJFiriBABrZqeBWsWJFq1KlSrqeQ69TQfHIkSNuOanZtWuXnTlzxrLanj17LMzCvv4S9m3A+vP+hx2fgT0XdfuraJNSwSgqQ9rFokAllStXtgkTJsT3Y1MftcOHD7sRmy+//LKNGTMmy59bU4xkJZ056IOpyqP6yIVN2Ndfwr4NWH/e/zDv/8Jn4GRU7gOBD2nJVbkiKTClVZnyyzh48GC6q3X+3y1atEgw0EBatmzpQlpkH7a0nkOvU8tRU2lWlkIzQjtmdi07GoR9/SXs24D15/0P8/4vfAbyRNU+EPiBA74vmu/zFUmpWM2HaZUONXpTU3ik1HfN3x/Z702DDFJqvvT3RfYd83+b3HOo6VJzq6lfWlr90QAAAKIipGnGflm8eHGS32nes8jHpCRfvnxWo0YN27hxo23fvj3B73QlgA8//NCNvqxWrVr8/ZpKQzZs2JBkef6+yJGiqb3Ojz/+2I0eTet1AgAARE1Ia9CggauEzZw509atWxd/v5oVNSWGSredO3eOv3/37t32zTffJGl27N69u7sdOnRogks0TZ482bZu3eoGByjMeZqAtmjRom7+tC+//DJBu/7w4cPjp+eIrLxpZOi///1v++CDDxI8/qmnnnL//s1vfpNl2wUAAMS2wLe9qXlQ013o8kytWrVKcFmoHTt2uCsOqBnRGzJkiJvcdty4cda1a9f4+3VJKF3rU2FPTY+qaqlp8p133nF/P3DgwATPqz5mo0ePduFOk9PqMlLq+6ZLSK1fv95d/ily+fLCCy/Ybbfd5u6/44473LU7NRpUj7/33nvdpaUAAABiopIm9evXt/nz57uQo6ClSzldddVV7jY91+0U9UnTpaE0lYam1xg/frx98skn1q1bN1f5SnzdTmndurW76oAqZPPmzXPP54OglqWhtJGuv/561wSrgQUKZ3/961/dYAFd4/O5557Loq0BAADCIEdcXNz/tf0hJmmAg6qOpUqViqpRLVkl7OsvYd8GrD/vf5j3f+EzcDwq94GoqKQBAACEDSENAAAggAhpAAAAAURIAwAACCBCGgAAQAAR0gAAAAKIkAYAABBAhDQAAIAAIqQBAAAEECENAAAggAhpAAAAAURIAwAACCBCGgAAQAAR0gAAAAKIkAYAABBAhDQAAIAAIqQBAAAEECENAAAggAhpAAAAAURIAwAACCBCGgAAQAAR0gAAAAKIkAYAABBAhDQAAIAAIqQBAAAEECENAAAggAhpAAAAAURIAwAACCBCGgAAQAAR0gAAAAKIkAYAABBAhDQAAIAAIqQBAAAEECENAAAggAhpAAAAAURIAwAACCBCGgAAQAAR0gAAAAKIkAYAABBAhDQAAIAAIqQBAAAEECEtJHLlymVhFvb1l7BvA9af9z/s+AzksmiTIy4u7tzFfhEAAABIiEoaAABAABHSAAAAAoiQBgAAEECENAAAgAAipAEAAAQQIQ0AACCACGkAAAABREgDAAAIIEIaEELnzjGHNQAEHSEthq1YscK++uor++GHHy72SwmMJUuW2C233GIff/yxhZHfFyJDWtgCW9j3gbSEbX+Qw4cP29ixY23t2rUWVmF836NhH8h9sV8Ast7cuXNt6NCh9t1339nJkyetdOnSds8999hvfvMby58/fyg3+a5du6x///42b948K1u2rPtAhsn3339vQ4YMsc8++8zOnj1r119/vf32t7+1+vXrW44cOSwMwr4PpGTmzJlu2xQpUsSaNGli11xzjdsntJ/kzBn75/Fjxoyxv/zlL1a8eHGrUqWKhdFrr71mn3/+uRUqVMhq1aplt956q+XLl8/CYkyA9wGu3Rlj/v73v9vAgQPtF7/4hfuwXXLJJe4DqC/hbt262RNPPOF2xDBRYB01apRdffXV1r17d2vdurXdcMMNFoYzYx1s33nnHevTp4/70tV6Hz161J0tKsCPGDHCbZNYF9Z9IDX//e9/7U9/+pNt3rzZhbETJ064oNajRw8bPHiwxbr333/fHnnkEdu+fbu1b9/e7rzzTmvQoEGowsmaNWvsD3/4g3377bd22WWXWVxcnLv/tttuc5+ZihUrWix7Pwr2AUJaDDlw4IC1atXK/fvll1+2qlWrun9//fXX9vTTT7uDtSpqOjCHwSeffGJdunRx20VVRH0AFV6D9AHMbqdPn3ZfPjoQjxw50ho3bmy5c+e2BQsWuC9nVZPGjx/v9ps8efJYrGEfSN4333zjwtixY8fsoYcessqVK7sD9OOPP24bN260xx57zHr27GlXXnllfNiPFVu3brW+ffvahx9+aHXq1LHOnTtb06ZNXQUxTPR+ax9Yv369O3nXttB3o8LZ7Nmz3f9VXbr55ptjrqq6NYr2AZo7Y4h2PB/IFND8B6tSpUr20ksv2apVq+yVV16xZs2auTOlWKdtcerUKatZs6arJJUrV87dr4rBpZde6sKbzh7z5s1rsUph7N///rcLZM2bN3f3ab/Qv4cPH+4qJgrtqjDVrl3bYo0OQGHfByL57wTtF9o2f/vb36xjx47xvy9YsKD7/tD3hZq+evXqFVMBTaZMmeIOzjph0Wcg1qtFKVm5cqXrn6kgppNY76mnnrJrr73WXnzxRXdi9+qrr8ZUQIu2fSC2tnzI7dy5093u37/f3foP1pkzZ1wzxpNPPun+r75JsX4gkl/96leuiqRwumHDBte8p8EU6n+gypFCiSpt+sD6Mn80d57VOqhs7ytokftAhQoV3K0Ciz/otmjRwu69914XZt988834/Saa+WrQu+++6/7fsmXLUO0DafH7g5o61RVCleXI/UVdJNRvT98XkydPtv/85z8xt03uu+8+1ydRJ7X6PMiWLVvsgw8+sBdeeMEF00mTJtm6desslqkPmqiKKn5bFCtWzFXWatSo4Vpf1F0m8ns1FtwXRfsAIS2G6MNWoEABN2Dg0KFD8ffnypXL3epgpZKuzqD9By9WbNq0yfbu3WtHjhyJPxCpqaZdu3burFDVoqlTp9oDDzzgzhTVzKMvIx2sVGFR3xyJ1qrBs88+6/pYTZgwwf1fTZq+YhT5haz7/TpqEImCijoJz5kzJxBfSOdD77HCqLbBtm3b3H3qfxmWfSAtClr+QKv1VvO2b+L23xGika86SKlJ9O2333Z9GGNhm/igedVVV8X3xVMf3oULF9qf//xn69q1qw0bNszeeOMN69evn2sCW7RoUfxBPFppFLOOCZ7fB4oWLepuVU0WhXb/e+0PzzzzTHzXGZ3ARWs17eOPP3bfcaoc+qJFNO0D0bnVkSx98appU81biasi2jFFO57oy/enn36K+i2pMz013akj+E033eT6nWm0WuQBRx+01atXu743Omh/9NFH9t5777kflbyvu+46tz2mTZvm/iaazhj1uhXOFEAUuLQd9Pr9OqhS5PugKbj4UXuemv86dOhgP/74oy1dujQqqyZ+G+ig4iuGvirmq0OxvA8kRycs//znP937riqi6L33B1o1ayp86feJ11fNwLfffrtVq1bN/V7T+EQb7c86uKpjvCrFEhk077rrLvvlL3/pKqjqp7t8+XLX0rBs2TK3P/zud79z4UUH7midqsUPFlOLgkKH5/eBK664woX0Tz/9NME+oN/rO0BVVjWF66T+rbfesmhc/y5durj1//LLL23x4sUJTkiiZR8gpMUQNVGor5nOmv71r38l+J12TH3wtFPqwK0ziGieP2337t3ubF8j9VQt0hdKo0aNXEfx+++/333ItL7qb6RmPTV7qcStZjCFOR2IChcu7A7Oav7VF7hG/0k0nDGqyqEvH3X8Vdl+3Lhx7gywbt267vXrR1+6OmPUF5WaQRVCItdP20f/VrNGqVKl4r+Io6VqovAQuQ3Ud0b9a0TNdP7sV52hY3EfSInCqjp7DxgwwH7961+7bfTwww+75hxP94mCnMKa/37wSpQoYXfccYcL9vquiKbgqs+Bgrn2e7UcaF3/+te/xp+46oRV4USj+rRO6pekMKfvE01No22n5j79XgFPTWDRdkKrz4GmktB66b33/VHFv89aV1WYX3/9dfd96r8zIh+jz4v2DQV97SfRtv5r1651Jxz6rKt1Sevn1y1a9oHo/SZCspT+9cFTk4/OHhJTnxwdlCK/sKPNnj17bNCgQe6Do8qIOj+r+VZfNr6/ne7zYaN69eruw6fwJvpQ6nf+w6pgq4EWGobuqw5BppChg5D6Vqn/hDr3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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "driving = bd.get_node(database=\"foreground\", name=\"driving an electric vehicle\")\n", - "ev_to_driving = next(exc for exc in driving.technosphere() if exc.input[\"name\"] == \"electricity\")\n", - "\n", - "print(ev_to_driving[\"temporal_distribution\"].date) # original resolution was timedelta64[M], which gets converted to seconds\n", - "print(ev_to_driving[\"temporal_distribution\"].amount)\n", - "print(ev_to_driving[\"temporal_distribution\"].date.dtype)\n", - "\n", - "ev_to_driving[\"temporal_distribution\"].graph(resolution=\"M\")" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "TemporalDistribution instance with 16 values and total: 1" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ev_to_driving[\"temporal_distribution\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Add a characterization method" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Finally, we need some characterization method. Again, this is just a simple made-up one:" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "bd.Method((\"GWP\", \"example\")).write(\n", - " [\n", - " ((\"biosphere\", \"CO2\"), 1),\n", - " ]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## LCA using `bw_timex`\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now that the data is set up, we can get startet with the actual time-explicit LCA. As usual, we need to select a method first:" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "method = (\"GWP\", \"example\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`bw_timex` needs to know the representative time of the databases:" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "from datetime import datetime\n", - "\n", - "database_dates = {\n", - " \"background_2020\": datetime.strptime(\"2020\", \"%Y\"),\n", - " \"background_2030\": datetime.strptime(\"2030\", \"%Y\"),\n", - " \"background_2040\": datetime.strptime(\"2040\", \"%Y\"),\n", - " \"foreground\": \"dynamic\", # flag databases that should be temporally distributed with \"dynamic\"\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we can instantiate a `TimexLCA`. It's structure is similar to a normal `bw2calc.LCA`, but with the additional argument `database_dates`.\n", - "\n", - "Not sure about the required inputs? Check the documentation using `?`. All our classes and methods have docstrings!" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "from bw_timex import TimexLCA" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's create a `TimexLCA` object for our EV life cycle:" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "driving = bd.get_node(database=\"foreground\", code=\"driving\", name=\"driving an electric vehicle\", unit=\"transport over an ev lifetime\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\u001b[32m2026-06-25 17:01:01.438\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m115\u001b[0m - \u001b[1mInitializing TimexLCA object...\u001b[0m\n", - "\u001b[32m2026-06-25 17:01:01.438\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m131\u001b[0m - \u001b[1mCalculating base LCA...\u001b[0m\n", - "\u001b[32m2026-06-25 17:01:01.447\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m148\u001b[0m - \u001b[1mCollecting node infos...\u001b[0m\n" - ] - } - ], - "source": [ - "# intialize the TimexLCA object with the functional unit, method, and database dates\n", - "tlca = TimexLCA({driving: 1}, method, database_dates)\n", - "# build the timeline with a temporal grouping of \"month\"\n", - "tlca.build_timeline(temporal_grouping=\"month\")\n", - "# calculate the time-explicit LCI\n", - "tlca.lci()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's check the dynamic invenrotry in a human readale format" - ] - }, + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Loading your LCA model with temporal distributions from an excel file\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook is essentially a short version of the [electric vehicle tutorial](./2_electric_vehicle_from_scratch.ipynb) example notebook, but shows how to import the foreground model from an [excel file](../data/electric_vehicle_foreground.xlsx). For a more detailed explaination of how timex works, please see one of the other notebooks. \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Set up a bw project\n", + "\n", + "import bw2data as bd\n", + "\n", + "bd.projects.set_current(\"electric_vehicle_standalone_excel\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Fresh start\n", + "for db in list(bd.databases):\n", + " del bd.databases[db]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 1/1 [00:00<00:00, 7256.58it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[2m17:01:00+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "# Add some background databases\n", + "\n", + "biosphere = bd.Database(\"biosphere\")\n", + "biosphere.register()\n", + "biosphere.write(\n", + " {\n", + " (\"biosphere\", \"CO2\"): {\n", + " \"type\": \"emission\",\n", + " \"name\": \"carbon dioxide\",\n", + " },\n", + " }\n", + ")\n", + "\n", + "background_2020 = bd.Database(\"background_2020\")\n", + "background_2020.register()\n", + "\n", + "background_2030 = bd.Database(\"background_2030\")\n", + "background_2030.register()\n", + "\n", + "background_2040 = bd.Database(\"background_2040\")\n", + "background_2040.register()\n", + "\n", + "background_2020.write({})\n", + "background_2030.write({})\n", + "background_2040.write({})\n", + "\n", + "background_databases = [\n", + " background_2020,\n", + " background_2030,\n", + " background_2040,\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now create some very simple processes within these databases. These process get only one aggregated CO2-emission each. The amounts of these emissions change over time." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "process_co2_emissions = {\n", + " \"glider\": (10, 5, 2.5), # for 2020, 2030 and 2040\n", + " \"powertrain\": (20, 10, 7.5),\n", + " \"battery\": (10, 5, 4),\n", + " \"electricity\": (0.5, 0.25, 0.075),\n", + " \"glider_eol\": (0.01, 0.0075, 0.005),\n", + " \"powertrain_eol\": (0.01, 0.0075, 0.005),\n", + " \"battery_eol\": (1, 0.5, 0.25),\n", + "}\n", + "\n", + "node_co2 = biosphere.get(\"CO2\")\n", + "\n", + "for component_name, gwis in process_co2_emissions.items():\n", + " for database, gwi in zip(background_databases, gwis):\n", + " database.new_node(component_name, name=component_name, location=\"somewhere\").save()\n", + " component = database.get(component_name)\n", + " component[\"reference product\"] = component_name \n", + " component.save()\n", + " production_amount = -1 if \"eol\" in component_name else 1\n", + " component.new_edge(input=component, amount=production_amount, type=\"production\").save()\n", + " component.new_edge(input=node_co2, amount=gwi, type=\"biosphere\").save()\n", + "\n", + "# register the databases\n", + "for db in bd.databases:\n", + " bd.Database(db).process() \n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Case study setup\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this study, we consider the following production system for our ev. Purple boxes are foreground, cyan boxes are background (i.e., ecoinvent/premise)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```{mermaid}\n", + "flowchart LR\n", + " glider_production(glider production):::ei-->ev_production\n", + " powertrain_production(powertrain production):::ei-->ev_production\n", + " battery_production(battery production):::ei-->ev_production\n", + " ev_production(ev production):::fg-->driving\n", + " electricity_generation(electricity generation):::ei-->driving\n", + " driving(driving):::fg-->used_ev\n", + " used_ev(used ev):::fg-->glider_eol(glider eol):::ei\n", + " used_ev-->powertrain_eol(powertrain eol):::ei\n", + " used_ev-->battery_eol(battery eol):::ei\n", + "\n", + " classDef ei color:#222832, fill:#3fb1c5, stroke:none;\n", + " classDef fg color:#222832, fill:#9c5ffd, stroke:none;\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Importing the product system from Excel\n", + "\n", + "As an alternative to generating your temporal foreground system in code as above, you can also import the case study processes from a BW25-excel file.\n", + "You need `bw2io > 0.9.14`, which supports the import of `TemporalDistributions` in the ExcelImporter or CSVImporter. You can consult the sample excel file under notebooks/data/electric_vehicle_foreground.xlsx for valid input formats for the TDs.\n", + "\n", + "Please make sure you created and processed the biosphere and the background databases for 2020, 2030 and 2040 using the code above." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Extracted 1 worksheets in 0.00 seconds\n", + "Applying strategy: csv_restore_tuples\n", + "Applying strategy: csv_restore_booleans\n", + "Applying strategy: csv_numerize\n", + "Applying strategy: csv_drop_unknown\n", + "Applying strategy: csv_restore_temporal_distributions\n", + "Applying strategy: csv_add_missing_exchanges_section\n", + "Applying strategy: normalize_units\n", + "Applying strategy: strip_biosphere_exc_locations\n", + "Applying strategy: set_code_by_activity_hash\n", + "Applying strategy: link_iterable_by_fields\n", + "Applying strategy: assign_only_product_as_production\n", + "Applying strategy: link_technosphere_by_activity_hash\n", + "Applying strategy: drop_falsey_uncertainty_fields_but_keep_zeros\n", + "Applying strategy: convert_uncertainty_types_to_integers\n", + "Applying strategy: convert_activity_parameters_to_list\n", + "Applied 15 strategies in 0.02 seconds\n", + "Applying strategy: link_iterable_by_fields\n", + "Applying strategy: link_iterable_by_fields\n", + "Graph statistics for `foreground` importer:\n", + "3 graph nodes:\n", + "\tNone: 3\n", + "12 graph edges:\n", + "\ttechnosphere: 9\n", + "\tproduction: 3\n", + "12 edges to the following databases:\n", + "\tbackground_2020: 7\n", + "\tforeground: 5\n", + "0 unique unlinked edges (0 total):\n", + "\n", + "\n", + "\u001b[2m17:01:00+0200\u001b[0m [\u001b[33m\u001b[1mwarning \u001b[0m] \u001b[1mNot able to determine geocollections for all datasets. This database is not ready for regionalization.\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 3/3 [00:00<00:00, 4463.61it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[2m17:01:00+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n", + "Created database: foreground\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "if \"foreground\" in bd.databases:\n", + " del bd.databases[\"foreground\"] # to make sure we import the foreground from scratch from the excel file\n", + "\n", + "import bw2io as bi\n", + "\n", + "ei = bi.ExcelImporter(\"../data/electric_vehicle_foreground.xlsx\", sheet_name=\"easy_tds\") \n", + "ei.apply_strategies()\n", + "ei.match_database(\"background_2020\", fields=[\"name\", \"reference product\"])\n", + "ei.match_database(\"biosphere\", fields=[\"name\", \"categories\"])\n", + "\n", + "ei.statistics() #0 unique unlinked edges means that all foreground exchanges are linked and we can successfully import the database.\n", + "\n", + "ei.write_database()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "let's check if the ExcelImporter imported the `TemporalDistributions` correctly:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 0 31556952 63113904 94670856 126227808 157784760 189341712\n", + " 220898664 252455616 284012568 315569520 347126472 378683424 410240376\n", + " 441797328 473354280]\n", + "[0.0625 0.0625 0.0625 0.0625 0.0625 0.0625 0.0625 0.0625 0.0625 0.0625\n", + " 0.0625 0.0625 0.0625 0.0625 0.0625 0.0625]\n", + "timedelta64[s]\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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Jbgs1I4dO9xUF5FNjEOGDHGT244bN866du0af79m/te1PhX2dFknVd/UzPnOO++4vx84cGCC51WftNGjR7tw16xZM3cZKfV90yWk1q9fb82bN0+wfAAAgFBNZiv169d3M/jr+pkKWprq4qqrrnK36blup6hPmi4Npak0NL3G+PHj7ZNPPrFu3brZBx98kOS6ndK6dWt31QHNnTZv3jz3fD4IalkapQEAAJAdcsTFxZ3LliUjMDTAQVXHUqVKRVWHyawS9vWXsG8D1p/3P8z7v/AZOB6V+0BUVNIAAADChpAGAAAQQIQ0AACAACKkAQAABBAhDQAAIIAIaQAAAAFESAMAAAggQhoAAEAAEdIAAAACiJAGAAAQQIQ0AACAACKkAQAABBAhDQAAIIAIaQAAAAFESAMAAAggQhoAAEAAEdIAAAACiJAGAAAQQIQ0AACAACKkAQAABBAhDQAAIIAIaQAAAAFESAMAAAggQhoAAEAAEdIAAAACiJAGAAAQQIQ0AACAACKkAQAABBAhDQAAIIAIaQAAAAFESAMAAAggQhoAAEAshbRp06bZokWL0vXYxYsXu8cDAAAgm0PaAw88YC+88EK6Hjty5Ejr3bt3Zp8KAAAgdM6rufPcuXNZ90oAAABwYfuk/fjjj5Y3b94L8VQAAAAxIXd2Lvz48eP20Ucf2ddff20VKlTIzqcCAAAIZ0h75pln7Lnnnktw3yeffGJXXHFFuv7+9ttvz/irAwAACKncme2DliNHjnT1SStUqJD9+te/tocffjhzrxAAACCE0h3S7r//fuvSpYv7t8LZzTffbNWrV7fJkycn+3iFuMsuu8yKFi2ada8WAAAgJNId0goXLux+vLvuusv1MytdunR2vTYAAIDQyvTozvHjx9tDDz1kF8rq1autY8eOLhSWKFHCmjZtarNnz87QMk6cOGHPPvusqwAWL17cKlWqZH369LG9e/em+DcnT560sWPHWsOGDa1kyZLup3bt2ta/f/8kj33qqaesbdu2dsMNN9jVV19t5cqVc383btw4++mnnzK13gAAIJyydXRnVlm6dKl16NDBTePRvn17K1CggM2ZM8d69uxpO3futAcffDDNZZw9e9Y11+oqCTVr1rQ2bdrYpk2bbMqUKbZkyRJbuHChFStWLMHfxMXFueddtWqV1apVy3r06OHu37Ztm82aNctGjBiR4PETJ0608uXLW6NGjezKK6+0I0eO2LJly+yJJ55wV1xYsGCBawIGAADI9pCmEDJ//nzbvHmzCyUpDSZQHzUFq4w6ffq0q3blzJnT5s6da1WrVnX3DxgwwJo0aWJPPvmkq16l1ew6depUF9DuvPNOF6b0emTSpEnWt29fGzZsmI0aNSrB3+gqCarg6fGq4iV+XYlpqpHk5oP7/e9/b2+88Ya9/vrrdu+992Z4GwAAgPDJdEg7deqUCxw+eKU10tOHosxU0bZs2WJdu3aND2ii/nEKV7o8lapUjzzySKrLUcVMBg0alOC1qBr30ksv2YwZM2z48OGWL18+d/+KFStcKNTI1MQBTXLnTrrpUpqwt127di6kKcgCAABka0h78cUX7e2333aB57bbbrNbbrnFNfGp4pWVVKmTxo0bJ/mdKmmyfPnyNCfVXblyZbIDHfT61TypUapr1qyxOnXquPvVnOkD1v79++29995zfdeuvfZaa9asWbrnhxM1c8r111+f7r8BAADhlumQpsqTAo6aAtVvK7uo35ior1di6vyv/mlpVahUiVOfNHXkT46/X8/lQ9ratWvj7+vVq5cdOnQo/vF6TlXf1D8uOaNHj3YDBQ4ePOgm/FX4U8jUiFgAAIBsDWnbt2+3a665JlsDmvhwpElxk1OwYMEEASq1ZUROIRLJLztyOfv27XO3gwcPds2djz76qBUpUsRVxTSyU8GtYsWKVqVKlWRD2oEDB+L/36lTJxs5cqRdcskl6Vjj/1/5y0oaoRp5GzZhX38J+zZg/Xn/I/eDMOIzcDLBdriYMnIt80yHNAUeVbJilSpvUrlyZZswYUJ8PzYFrsOHD1u/fv3s5ZdftjFjxiT5W1/Z27Nnj+tT95e//MVNGfLmm2+65tK07Nq1y86cOZPl66TXE2ZhX38J+zZg/Xn/w47PwJ6Luv1z5cqVYqteloa0unXrutGSSqV58uSx7JJclSuSApMqXOlZhpof01ut8/9u0aJFkkEPLVu2dCFNzZipUYhVFU5NtWruHDhwYIpXaIikeeCykt4jfTD1erLzvQqqsK+/hH0bsP68/2He/4XPwMmo3AcyHdLU5KepN3ThdY2YzC6+L5r6hulSVJG0wTXthyanTU3ZsmXdgIaU+q75+yP7vWmQgUJYck2k/r70Nkvq9SlI+kEQWVkKzQjtmNm17GgQ9vWXsG8D1p/3P8z7v/AZyBNV+0Cmh2Kq0qSAptn41YFe01Vs2LDBduzYkeJPZit2snjx4iS/UyUv8jEp0bQaNWrUsI0bN7q+dJE0dciHH35o+fPnt2rVqsXfX69ePXerdUrM35feS2IpSKpal94+aQAAAJmupN10003x//7oo4/cT2rUZKipLDKqQYMGrhI2c+ZM11nfz5Wmpkt1xtdZQefOneMfv3v3bheIVNKMrIJ1797dzX02dOjQBJPZqvlx69at7moCfo400QS56kumUaz33Xefu9STLxlrPjU/PYenZej5Lr/88iTzyT322GOuj5um7gAAAMjWkJbW5LXn+/jISWM13YVGkbZq1SrBZaFUndMVB8qUKRP/+CFDhrjJbXW9TE2A6+mSULrWp8KeLuuk6puaOd955x339+ovlrhSqFGaCncKV7qMlJosdQmp9evXW/PmzRMsX3O1aXLdX/7yly5Uah41Ncfq8d99951dd9119uc//zlT2wAAAIRPpkPajz/+aBdK/fr1Xf83VbAUtFSd0qhLBbKU5ipLTH3SdGkoTcI7ffp0d4F4Vb26devmAlri63ZK69atXTOurtE5b948O3bsmOu3pufVJaM0SsNTOFOg+/jjj+3zzz93lT6FSYUzVQB/97vfcd1OAACQbjni4uIyV+JC1NAAB1UdS5UqFVUdJrNK2Ndfwr4NWH/e/zDv/8Jn4HhU7gNZew0nAAAAZAlCGgAAQCz1Sbv99tsz9HiNplRnfwAAAGRjSEvPxKx+mguN7Ew8az8AAACyIaRpiouU/PTTT/btt9+6a1VqzrJHHnnErr766sw+FQAAQOhkOqRp3rG0aBJXTT3x6quvuguNAwAAIAADBzQDvy4b9f3337tLSAEAACAgozt1eaZKlSrZe++9l91PBQAAEDMuyBQcJ06ccJdIAgAAQEBC2hdffGGbNm2yokWLZvdTAQAAxIxMDxzQ5RVSoik39u7da59++qmNGTPG/f+2227L7FMBAACETqZD2k033ZSuxymglS1b1h5//PHMPhUAAEDoZDqkKXylJn/+/FauXDlr2bKl9e7d2woVKpTZpwIAAAidTIe0H3/8MWtfCQAAAOJxgXUAAIAAIqQBAAAEUKabOyN99tlntmDBAtu4caMdPnzYChYsaBUrVrRmzZqle4ABAAAAsiikHThwwB544AEX0BIPJsiRI4c9/fTT1qJFC3dpqCuuuOJ8ngrnYddPZ2xlXE7LVfSMlct7YTfld0fP2KZDp618odx2bf5cF+W5S1565oI+b1DW+0I/dxjXmecO1zZnPwvPe32xn/u8Q5quInDHHXfY559/7sJZ1apVrXLlynb11Vfb7t277auvvrJ169bZ/PnzrX379vb+++/bpZdemrWvHmma8s1R+9PyODtreS3nF3E2qq7Zbyrmv3DP/Z84O3vOLGcOs1F1ilyc5zazx3+ey/5QysK13hfwucO4zjx3uLY5+1l43uuL/dxZ0idt4sSJLoSVKFHC3nrrLVuyZIlNmDDBBg8e7G71/7ffftuuvfZa97i///3vWfvKka6zALeT/e//un3oP3Hu/gv23P8rrur2oj23mT39bR5XUQzVel+g5w7jOvPc4drm7GcXdnuHeZtnWUibPXu2a9KcOnWqNWjQINnH1K9f315//XVXaZs1a1ZmnwqZpDKt38m8M+fMNh86HbrnPms5bMvhsxfluWN9m4dxnXnucG1z9rMLu73DvM2zLKRpkECFChVcM2dq9HsNItDjcWGpHV1l2ki5cpiVK5Q7dM+d087ZzwrmDN16X4jnDuM689zh2ubsZxd2e4d5myeW6aPWqVOnLF++fOl6rB6nx+PCUkdHtaNr5xLdvlinyAXpABm053785yetxGXhW+8L8dxhXGeeO1zbnP3swm7vMG/zxHLExcWlfn2nFPziF7+wnTt32tdff21FihRJ9coE119/veubtmrVqsw8Fc7T5gNH7dNNu+2W8ldbuSsubMdHteGrRFzuIo3M0XNfe+lpO7N/l5UqVcry5s0bmvWOfO7jx4/bjh07snUbBG2dI2Xn+gd5vbNz/aNhvbN6/aNpnbNyG0Tzep/P+l/M9fYyXbtr1KiRGwygKTgmTZqU7Epro+j3J0+etKZNm57va0UmqYJUo8jZC1JJSkw79sXauf1zHz9+znbsvzjPfTFcrOcO4zrz3OHa5uxnF961If1sn3dI+9Of/mTTp093U2yo31mPHj3cFBxXXXWV/fDDD24KjldffdX27dvnJrf94x//mLWvHAAAIIZlOqSp+VIjOxXO9u7dayNGjEjyGI3qLFasmAtrejwAAADS57yGKtx666326aefumbPDz74wI3gPHLkiBUoUMCN6GzevLn99re/5WoDAAAA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nnL//s1vfpNl2wUAAMS2wLe9qXlQ013o8kytWrVKcFmoHTt2uCsOqBnRGzJkiJvcdty4cda1a9f4+3VJKF3rU2FPTY+qaqlp8p133nF/P3DgwATPqz5mo0ePduFOk9PqMlLq+6ZLSK1fv95d/ily+fLCCy/Ybbfd5u6/44473LU7NRpUj7/33nvdpaUAAABiopIm9evXt/nz57uQo6ClSzldddVV7jY91+0U9UnTpaE0lYam1xg/frx98skn1q1bN1f5SnzdTmndurW76oAqZPPmzXPP54OglqWhtJGuv/561wSrgQUKZ3/961/dYAFd4/O5557Loq0BAADCIEdcXNz/tf0hJmmAg6qOpUqViqpRLVkl7OsvYd8GrD/vf5j3f+EzcDwq94GoqKQBAACEDSENAAAggAhpAAAAAURIAwAACCBCGgAAQAAR0gAAAAKIkAYAABBAhDQAAIAAIqQBAAAEECENAAAggAhpAAAAAURIAwAACCBCGgAAQAAR0gAAAAKIkAYAABBAhDQAAIAAIqQBAAAEECENAAAggAhpAAAAAURIAwAACCBCGgAAQAAR0gAAAAKIkAYAABBAhDQAAIAAIqQBAAAEECENAAAggAhpAAAAAURIAwAACCBCGgAAQAAR0gAAAAKIkAYAABBAhDQAAIAAIqQBAAAEECENAAAggAhpAAAAAURIAwAACCBCGgAAQAAR0gAAAAKIkAYAABBAhDQAAIAAIqQBAAAEECEtJHLlymVhFvb1l7BvA9af9z/s+AzksmiTIy4u7tzFfhEAAABIiEoaAABAABHSAAAAAoiQBgAAEECENAAAgAAipAEAAAQQIQ0AACCACGkAAAABREgDAAAIIEIaEELnzjGHNQAEHSEthq1YscK++uor++GHHy72SwmMJUuW2C233GIff/yxhZHfFyJDWtgCW9j3gbSEbX+Qw4cP29ixY23t2rUWVmF836NhH8h9sV8Ast7cuXNt6NCh9t1339nJkyetdOnSds8999hvfvMby58/fyg3+a5du6x///42b948K1u2rPtAhsn3339vQ4YMsc8++8zOnj1r119/vf32t7+1+vXrW44cOSwMwr4PpGTmzJlu2xQpUsSaNGli11xzjdsntJ/kzBn75/Fjxoyxv/zlL1a8eHGrUqWKhdFrr71mn3/+uRUqVMhq1aplt956q+XLl8/CYkyA9wGu3Rlj/v73v9vAgQPtF7/4hfuwXXLJJe4DqC/hbt262RNPPOF2xDBRYB01apRdffXV1r17d2vdurXdcMMNFoYzYx1s33nnHevTp4/70tV6Hz161J0tKsCPGDHCbZNYF9Z9IDX//e9/7U9/+pNt3rzZhbETJ064oNajRw8bPHiwxbr333/fHnnkEdu+fbu1b9/e7rzzTmvQoEGowsmaNWvsD3/4g3377bd22WWXWVxcnLv/tttuc5+ZihUrWix7Pwr2AUJaDDlw4IC1atXK/fvll1+2qlWrun9//fXX9vTTT7uDtSpqOjCHwSeffGJdunRx20VVRH0AFV6D9AHMbqdPn3ZfPjoQjxw50ho3bmy5c+e2BQsWuC9nVZPGjx/v9ps8efJYrGEfSN4333zjwtixY8fsoYcessqVK7sD9OOPP24bN260xx57zHr27GlXXnllfNiPFVu3brW+ffvahx9+aHXq1LHOnTtb06ZNXQUxTPR+ax9Yv369O3nXttB3o8LZ7Nmz3f9VXbr55ptjrqq6NYr2AZo7Y4h2PB/IFND8B6tSpUr20ksv2apVq+yVV16xZs2auTOlWKdtcerUKatZs6arJJUrV87dr4rBpZde6sKbzh7z5s1rsUph7N///rcLZM2bN3f3ab/Qv4cPH+4qJgrtqjDVrl3bYo0OQGHfByL57wTtF9o2f/vb36xjx47xvy9YsKD7/tD3hZq+evXqFVMBTaZMmeIOzjph0Wcg1qtFKVm5cqXrn6kgppNY76mnnrJrr73WXnzxRXdi9+qrr8ZUQIu2fSC2tnzI7dy5093u37/f3foP1pkzZ1wzxpNPPun+r75JsX4gkl/96leuiqRwumHDBte8p8EU6n+gypFCiSpt+sD6Mn80d57VOqhs7ytokftAhQoV3K0Ciz/otmjRwu69914XZt988834/Saa+WrQu+++6/7fsmXLUO0DafH7g5o61RVCleXI/UVdJNRvT98XkydPtv/85z8xt03uu+8+1ydRJ7X6PMiWLVvsgw8+sBdeeMEF00mTJtm6desslqkPmqiKKn5bFCtWzFXWatSo4Vpf1F0m8ns1FtwXRfsAIS2G6MNWoEABN2Dg0KFD8ffnypXL3epgpZKuzqD9By9WbNq0yfbu3WtHjhyJPxCpqaZdu3burFDVoqlTp9oDDzzgzhTVzKMvIx2sVGFR3xyJ1qrBs88+6/pYTZgwwf1fTZq+YhT5haz7/TpqEImCijoJz5kzJxBfSOdD77HCqLbBtm3b3H3qfxmWfSAtClr+QKv1VvO2b+L23xGika86SKlJ9O2333Z9GGNhm/igedVVV8X3xVMf3oULF9qf//xn69q1qw0bNszeeOMN69evn2sCW7RoUfxBPFppFLOOCZ7fB4oWLepuVU0WhXb/e+0PzzzzTHzXGZ3ARWs17eOPP3bfcaoc+qJFNO0D0bnVkSx98appU81biasi2jFFO57oy/enn36K+i2pMz013akj+E033eT6nWm0WuQBRx+01atXu743Omh/9NFH9t5777kflbyvu+46tz2mTZvm/iaazhj1uhXOFEAUuLQd9Pr9OqhS5PugKbj4UXuemv86dOhgP/74oy1dujQqqyZ+G+ig4iuGvirmq0OxvA8kRycs//znP937riqi6L33B1o1ayp86feJ11fNwLfffrtVq1bN/V7T+EQb7c86uKpjvCrFEhk077rrLvvlL3/pKqjqp7t8+XLX0rBs2TK3P/zud79z4UUH7midqsUPFlOLgkKH5/eBK664woX0Tz/9NME+oN/rO0BVVjWF66T+rbfesmhc/y5durj1//LLL23x4sUJTkiiZR8gpMUQNVGor5nOmv71r38l+J12TH3wtFPqwK0ziGieP2337t3ubF8j9VQt0hdKo0aNXEfx+++/333ItL7qb6RmPTV7qcStZjCFOR2IChcu7A7Oav7VF7hG/0k0nDGqyqEvH3X8Vdl+3Lhx7gywbt267vXrR1+6OmPUF5WaQRVCItdP20f/VrNGqVKl4r+Io6VqovAQuQ3Ud0b9a0TNdP7sV52hY3EfSInCqjp7DxgwwH7961+7bfTwww+75hxP94mCnMKa/37wSpQoYXfccYcL9vquiKbgqs+Bgrn2e7UcaF3/+te/xp+46oRV4USj+rRO6pekMKfvE01No22n5j79XgFPTWDRdkKrz4GmktB66b33/VHFv89aV1WYX3/9dfd96r8zIh+jz4v2DQV97SfRtv5r1651Jxz6rKt1Sevn1y1a9oHo/SZCspT+9cFTk4/OHhJTnxwdlCK/sKPNnj17bNCgQe6Do8qIOj+r+VZfNr6/ne7zYaN69eruw6fwJvpQ6nf+w6pgq4EWGobuqw5BppChg5D6Vqn/hDr3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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "driving = bd.get_node(database=\"foreground\", name=\"driving an electric vehicle\")\n", + "ev_to_driving = next(exc for exc in driving.technosphere() if exc.input[\"name\"] == \"electricity\")\n", + "\n", + "print(ev_to_driving[\"temporal_distribution\"].date) # original resolution was timedelta64[M], which gets converted to seconds\n", + "print(ev_to_driving[\"temporal_distribution\"].amount)\n", + "print(ev_to_driving[\"temporal_distribution\"].date.dtype)\n", + "\n", + "ev_to_driving[\"temporal_distribution\"].graph(resolution=\"M\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "TemporalDistribution instance with 16 values and total: 1" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ev_to_driving[\"temporal_distribution\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Add a characterization method" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we need some characterization method. Again, this is just a simple made-up one:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "bd.Method((\"GWP\", \"example\")).write(\n", + " [\n", + " ((\"biosphere\", \"CO2\"), 1),\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## LCA using `bw_timex`\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that the data is set up, we can get startet with the actual time-explicit LCA. As usual, we need to select a method first:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "method = (\"GWP\", \"example\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "`bw_timex` needs to know the representative time of the databases. We record it once, as `representative_time` metadata on each database, using `set_database_metadata`:" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "from datetime import datetime\n\nfrom bw_timex import set_database_metadata\n\nset_database_metadata(\"background_2020\", representative_time=datetime(2020, 1, 1))\nset_database_metadata(\"background_2030\", representative_time=datetime(2030, 1, 1))\nset_database_metadata(\"background_2040\", representative_time=datetime(2040, 1, 1))\n# the \"foreground\" database, which holds our demand, is treated as \"dynamic\" automatically" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "Now, we can instantiate a `TimexLCA`. It's structure is similar to a normal `bw2calc.LCA` - `TimexLCA` reads the metadata we just set automatically, so no timing argument is needed.\n\nNot sure about the required inputs? Check the documentation using `?`. All our classes and methods have docstrings!" + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "from bw_timex import TimexLCA" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's create a `TimexLCA` object for our EV life cycle:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "driving = bd.get_node(database=\"foreground\", code=\"driving\", name=\"driving an electric vehicle\", unit=\"transport over an ev lifetime\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# intialize the TimexLCA object with the functional unit and method\ntlca = TimexLCA({driving: 1}, method)\n# build the timeline with a temporal grouping of \"month\"\ntlca.build_timeline(temporal_grouping=\"month\")\n# calculate the time-explicit LCI\ntlca.lci()" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's check the dynamic invenrotry in a human readale format" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ { - "cell_type": "code", - "execution_count": 39, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.microsoft.datawrangler.viewer.v0+json": { - "columns": [ - { - "name": "index", - "rawType": "int64", - "type": "integer" - }, - { - "name": "date", - "rawType": "datetime64[us]", - "type": "datetime" - }, - { - "name": "amount", - "rawType": "float64", - "type": "float" - }, - { - "name": "flow", - "rawType": "str", - "type": "string" - }, - { - "name": "activity", - "rawType": "str", - "type": "string" - } - ], - "ref": "96fb6524-5d6b-4129-bbe4-189bbf70b3a2", - "rows": [ - [ - "0", - "2025-01-01 00:00:00", - "13671.0", - "carbon dioxide", - "glider" - ], - [ - "1", - "2026-01-01 00:00:00", - "6090.0", - "carbon dioxide", - "battery" - ], - [ - "2", - "2026-01-01 00:00:00", - "3480.0", - "carbon dioxide", - "powertrain" - ], - [ - "3", - "2026-01-01 00:00:00", - "1827.0", - "carbon dioxide", - "glider" - ], - [ - "5", - "2027-01-01 00:00:00", - "3402.0", - "carbon dioxide", - "glider" - ], - [ - "4", - "2027-01-01 00:00:00", - "632.8125", - "carbon dioxide", - "electricity" - ], - [ - "6", - "2028-01-01 00:00:00", - "585.9375", - "carbon dioxide", - "electricity" - ], - [ - "7", - "2029-01-01 00:00:00", - "539.0625", - "carbon dioxide", - "electricity" - ], - [ - "8", - "2030-01-01 00:00:00", - "492.1875", - "carbon dioxide", - "electricity" - ], - [ - "9", - "2031-01-01 00:00:00", - "452.3437502793968", - "carbon dioxide", - "electricity" - ], - [ - "10", - "2032-01-01 00:00:00", - "419.5312508381903", - "carbon dioxide", - "electricity" - ], - [ - "11", - "2033-01-01 00:00:00", - "386.71875139698386", - "carbon dioxide", - "electricity" - ], - [ - "12", - "2034-01-01 00:00:00", - "353.9062519557774", - "carbon dioxide", - "electricity" - ], - [ - "13", - "2035-01-01 00:00:00", - "321.09375251457095", - "carbon dioxide", - "electricity" - ], - [ - "14", - "2036-01-01 00:00:00", - "288.2812530733645", - "carbon dioxide", - "electricity" - ], - [ - "15", - "2037-01-01 00:00:00", - "255.46875363215804", - "carbon dioxide", - "electricity" - ], - [ - "16", - "2038-01-01 00:00:00", - "222.65625419095159", - "carbon dioxide", - "electricity" - ], - [ - "17", - "2039-01-01 00:00:00", - "189.84375474974513", - "carbon dioxide", - "electricity" - ], - [ - "18", - "2040-01-01 00:00:00", - "157.03125530853868", - "carbon dioxide", - "electricity" - ], - [ - "22", - "2041-01-01 00:00:00", - "140.62500558793545", - "carbon dioxide", - "electricity" - ], - [ - "19", - "2041-01-01 00:00:00", - "23.22505974918207", - "carbon dioxide", - "battery_eol" - ], - [ - "21", - "2041-01-01 00:00:00", - "1.393503553803692", - "carbon dioxide", - "glider_eol" - ], - [ - "20", - "2041-01-01 00:00:00", - "0.13271462417178018", - "carbon dioxide", - "powertrain_eol" - ], - [ - "26", - "2042-01-01 00:00:00", - "140.62500558793545", - "carbon dioxide", - "electricity" - ], - [ - "23", - "2042-01-01 00:00:00", - "23.54988050163585", - "carbon dioxide", - "battery_eol" - ], - [ - "25", - "2042-01-01 00:00:00", - "1.4129927985153001", - "carbon dioxide", - "glider_eol" - ], - [ - "24", - "2042-01-01 00:00:00", - "0.13457074271574287", - "carbon dioxide", - "powertrain_eol" - ], - [ - "27", - "2043-01-01 00:00:00", - "23.22505974918207", - "carbon dioxide", - "battery_eol" - ], - [ - "29", - "2043-01-01 00:00:00", - "1.393503553803692", - "carbon dioxide", - "glider_eol" - ], - [ - "28", - "2043-01-01 00:00:00", - "0.13271462417178018", - "carbon dioxide", - "powertrain_eol" - ] - ], - "shape": { - "columns": 4, - "rows": 30 - } - }, - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
dateamountflowactivity
02025-01-0113671.000000carbon dioxideglider
12026-01-016090.000000carbon dioxidebattery
22026-01-013480.000000carbon dioxidepowertrain
32026-01-011827.000000carbon dioxideglider
52027-01-013402.000000carbon dioxideglider
42027-01-01632.812500carbon dioxideelectricity
62028-01-01585.937500carbon dioxideelectricity
72029-01-01539.062500carbon dioxideelectricity
82030-01-01492.187500carbon dioxideelectricity
92031-01-01452.343750carbon dioxideelectricity
102032-01-01419.531251carbon dioxideelectricity
112033-01-01386.718751carbon dioxideelectricity
122034-01-01353.906252carbon dioxideelectricity
132035-01-01321.093753carbon dioxideelectricity
142036-01-01288.281253carbon dioxideelectricity
152037-01-01255.468754carbon dioxideelectricity
162038-01-01222.656254carbon dioxideelectricity
172039-01-01189.843755carbon dioxideelectricity
182040-01-01157.031255carbon dioxideelectricity
222041-01-01140.625006carbon dioxideelectricity
192041-01-0123.225060carbon dioxidebattery_eol
212041-01-011.393504carbon dioxideglider_eol
202041-01-010.132715carbon dioxidepowertrain_eol
262042-01-01140.625006carbon dioxideelectricity
232042-01-0123.549881carbon dioxidebattery_eol
252042-01-011.412993carbon dioxideglider_eol
242042-01-010.134571carbon dioxidepowertrain_eol
272043-01-0123.225060carbon dioxidebattery_eol
292043-01-011.393504carbon dioxideglider_eol
282043-01-010.132715carbon dioxidepowertrain_eol
\n", - "
" - ], - "text/plain": [ - " date amount flow activity\n", - "0 2025-01-01 13671.000000 carbon dioxide glider\n", - "1 2026-01-01 6090.000000 carbon dioxide battery\n", - "2 2026-01-01 3480.000000 carbon dioxide powertrain\n", - "3 2026-01-01 1827.000000 carbon dioxide glider\n", - "5 2027-01-01 3402.000000 carbon dioxide glider\n", - "4 2027-01-01 632.812500 carbon dioxide electricity\n", - "6 2028-01-01 585.937500 carbon dioxide electricity\n", - "7 2029-01-01 539.062500 carbon dioxide electricity\n", - "8 2030-01-01 492.187500 carbon dioxide electricity\n", - "9 2031-01-01 452.343750 carbon dioxide electricity\n", - "10 2032-01-01 419.531251 carbon dioxide electricity\n", - "11 2033-01-01 386.718751 carbon dioxide electricity\n", - "12 2034-01-01 353.906252 carbon dioxide electricity\n", - "13 2035-01-01 321.093753 carbon dioxide electricity\n", - "14 2036-01-01 288.281253 carbon dioxide electricity\n", - "15 2037-01-01 255.468754 carbon dioxide electricity\n", - "16 2038-01-01 222.656254 carbon dioxide electricity\n", - "17 2039-01-01 189.843755 carbon dioxide electricity\n", - "18 2040-01-01 157.031255 carbon dioxide electricity\n", - "22 2041-01-01 140.625006 carbon dioxide electricity\n", - "19 2041-01-01 23.225060 carbon dioxide battery_eol\n", - "21 2041-01-01 1.393504 carbon dioxide glider_eol\n", - "20 2041-01-01 0.132715 carbon dioxide powertrain_eol\n", - "26 2042-01-01 140.625006 carbon dioxide electricity\n", - "23 2042-01-01 23.549881 carbon dioxide battery_eol\n", - "25 2042-01-01 1.412993 carbon dioxide glider_eol\n", - "24 2042-01-01 0.134571 carbon dioxide powertrain_eol\n", - "27 2043-01-01 23.225060 carbon dioxide battery_eol\n", - "29 2043-01-01 1.393504 carbon dioxide glider_eol\n", - "28 2043-01-01 0.132715 carbon dioxide powertrain_eol" - ] - }, - "execution_count": 39, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "tlca.create_labelled_dynamic_inventory_dataframe()" - ] + "name": "index", + "rawType": "int64", + "type": "integer" }, { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we can do all further analysis as detailed in the other example notebook [electric vehicle tutorial](./2_electric_vehicle_from_scratch.ipynb). Below you just find the quick calculations. For the full dynamic characterization please see the linked notebook." - ] + "name": "date", + "rawType": "datetime64[us]", + "type": "datetime" }, { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "34122.72503901273" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Static LCIA from time-explicit LCI\n", - "tlca.static_lcia()\n", - "tlca.static_score #kg CO2-eq" - ] + "name": "amount", + "rawType": "float64", + "type": "float" }, { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "At this point, we can already compare these time-explicit results to the results of an \"ordinary\", completely static LCA. These already exist within the TimexLCA class, originally to set the priorities for the graph traversal:" - ] + "name": "flow", + "rawType": "str", + "type": "string" }, { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "28089.199999794364" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# compare to fully static non time-explicit LCIA score\n", - "tlca.base_lca.score" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "timex_dev", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.0" + "name": "activity", + "rawType": "str", + "type": "string" } - }, - "nbformat": 4, - "nbformat_minor": 2 -} + ], + "ref": "96fb6524-5d6b-4129-bbe4-189bbf70b3a2", + "rows": [ + [ + "0", + "2025-01-01 00:00:00", + "13671.0", + "carbon dioxide", + "glider" + ], + [ + "1", + "2026-01-01 00:00:00", + "6090.0", + "carbon dioxide", + "battery" + ], + [ + "2", + "2026-01-01 00:00:00", + "3480.0", + "carbon dioxide", + "powertrain" + ], + [ + "3", + "2026-01-01 00:00:00", + "1827.0", + "carbon dioxide", + "glider" + ], + [ + "5", + "2027-01-01 00:00:00", + "3402.0", + "carbon dioxide", + "glider" + ], + [ + "4", + "2027-01-01 00:00:00", + "632.8125", + "carbon dioxide", + "electricity" + ], + [ + "6", + "2028-01-01 00:00:00", + "585.9375", + "carbon dioxide", + "electricity" + ], + [ + "7", + "2029-01-01 00:00:00", + "539.0625", + "carbon dioxide", + "electricity" + ], + [ + "8", + "2030-01-01 00:00:00", + "492.1875", + "carbon dioxide", + "electricity" + ], + [ + "9", + "2031-01-01 00:00:00", + "452.3437502793968", + "carbon dioxide", + "electricity" + ], + [ + "10", + "2032-01-01 00:00:00", + "419.5312508381903", + "carbon dioxide", + "electricity" + ], + [ + "11", + "2033-01-01 00:00:00", + "386.71875139698386", + "carbon dioxide", + "electricity" + ], + [ + "12", + "2034-01-01 00:00:00", + "353.9062519557774", + "carbon dioxide", + "electricity" + ], + [ + "13", + "2035-01-01 00:00:00", + "321.09375251457095", + "carbon dioxide", + "electricity" + ], + [ + "14", + "2036-01-01 00:00:00", + "288.2812530733645", + "carbon dioxide", + "electricity" + ], + [ + "15", + "2037-01-01 00:00:00", + "255.46875363215804", + "carbon dioxide", + "electricity" + ], + [ + "16", + "2038-01-01 00:00:00", + "222.65625419095159", + "carbon dioxide", + "electricity" + ], + [ + "17", + "2039-01-01 00:00:00", + "189.84375474974513", + "carbon dioxide", + "electricity" + ], + [ + "18", + "2040-01-01 00:00:00", + "157.03125530853868", + "carbon dioxide", + "electricity" + ], + [ + "22", + "2041-01-01 00:00:00", + "140.62500558793545", + "carbon dioxide", + "electricity" + ], + [ + "19", + "2041-01-01 00:00:00", + "23.22505974918207", + "carbon dioxide", + "battery_eol" + ], + [ + "21", + "2041-01-01 00:00:00", + "1.393503553803692", + "carbon dioxide", + "glider_eol" + ], + [ + "20", + "2041-01-01 00:00:00", + "0.13271462417178018", + "carbon dioxide", + "powertrain_eol" + ], + [ + "26", + "2042-01-01 00:00:00", + "140.62500558793545", + "carbon dioxide", + "electricity" + ], + [ + "23", + "2042-01-01 00:00:00", + "23.54988050163585", + "carbon dioxide", + "battery_eol" + ], + [ + "25", + "2042-01-01 00:00:00", + "1.4129927985153001", + "carbon dioxide", + "glider_eol" + ], + [ + "24", + "2042-01-01 00:00:00", + "0.13457074271574287", + "carbon dioxide", + "powertrain_eol" + ], + [ + "27", + "2043-01-01 00:00:00", + "23.22505974918207", + "carbon dioxide", + "battery_eol" + ], + [ + "29", + "2043-01-01 00:00:00", + "1.393503553803692", + "carbon dioxide", + "glider_eol" + ], + [ + "28", + "2043-01-01 00:00:00", + "0.13271462417178018", + "carbon dioxide", + "powertrain_eol" + ] + ], + "shape": { + "columns": 4, + "rows": 30 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
dateamountflowactivity
02025-01-0113671.000000carbon dioxideglider
12026-01-016090.000000carbon dioxidebattery
22026-01-013480.000000carbon dioxidepowertrain
32026-01-011827.000000carbon dioxideglider
52027-01-013402.000000carbon dioxideglider
42027-01-01632.812500carbon dioxideelectricity
62028-01-01585.937500carbon dioxideelectricity
72029-01-01539.062500carbon dioxideelectricity
82030-01-01492.187500carbon dioxideelectricity
92031-01-01452.343750carbon dioxideelectricity
102032-01-01419.531251carbon dioxideelectricity
112033-01-01386.718751carbon dioxideelectricity
122034-01-01353.906252carbon dioxideelectricity
132035-01-01321.093753carbon dioxideelectricity
142036-01-01288.281253carbon dioxideelectricity
152037-01-01255.468754carbon dioxideelectricity
162038-01-01222.656254carbon dioxideelectricity
172039-01-01189.843755carbon dioxideelectricity
182040-01-01157.031255carbon dioxideelectricity
222041-01-01140.625006carbon dioxideelectricity
192041-01-0123.225060carbon dioxidebattery_eol
212041-01-011.393504carbon dioxideglider_eol
202041-01-010.132715carbon dioxidepowertrain_eol
262042-01-01140.625006carbon dioxideelectricity
232042-01-0123.549881carbon dioxidebattery_eol
252042-01-011.412993carbon dioxideglider_eol
242042-01-010.134571carbon dioxidepowertrain_eol
272043-01-0123.225060carbon dioxidebattery_eol
292043-01-011.393504carbon dioxideglider_eol
282043-01-010.132715carbon dioxidepowertrain_eol
\n", + "
" + ], + "text/plain": [ + " date amount flow activity\n", + "0 2025-01-01 13671.000000 carbon dioxide glider\n", + "1 2026-01-01 6090.000000 carbon dioxide battery\n", + "2 2026-01-01 3480.000000 carbon dioxide powertrain\n", + "3 2026-01-01 1827.000000 carbon dioxide glider\n", + "5 2027-01-01 3402.000000 carbon dioxide glider\n", + "4 2027-01-01 632.812500 carbon dioxide electricity\n", + "6 2028-01-01 585.937500 carbon dioxide electricity\n", + "7 2029-01-01 539.062500 carbon dioxide electricity\n", + "8 2030-01-01 492.187500 carbon dioxide electricity\n", + "9 2031-01-01 452.343750 carbon dioxide electricity\n", + "10 2032-01-01 419.531251 carbon dioxide electricity\n", + "11 2033-01-01 386.718751 carbon dioxide electricity\n", + "12 2034-01-01 353.906252 carbon dioxide electricity\n", + "13 2035-01-01 321.093753 carbon dioxide electricity\n", + "14 2036-01-01 288.281253 carbon dioxide electricity\n", + "15 2037-01-01 255.468754 carbon dioxide electricity\n", + "16 2038-01-01 222.656254 carbon dioxide electricity\n", + "17 2039-01-01 189.843755 carbon dioxide electricity\n", + "18 2040-01-01 157.031255 carbon dioxide electricity\n", + "22 2041-01-01 140.625006 carbon dioxide electricity\n", + "19 2041-01-01 23.225060 carbon dioxide battery_eol\n", + "21 2041-01-01 1.393504 carbon dioxide glider_eol\n", + "20 2041-01-01 0.132715 carbon dioxide powertrain_eol\n", + "26 2042-01-01 140.625006 carbon dioxide electricity\n", + "23 2042-01-01 23.549881 carbon dioxide battery_eol\n", + "25 2042-01-01 1.412993 carbon dioxide glider_eol\n", + "24 2042-01-01 0.134571 carbon dioxide powertrain_eol\n", + "27 2043-01-01 23.225060 carbon dioxide battery_eol\n", + "29 2043-01-01 1.393504 carbon dioxide glider_eol\n", + "28 2043-01-01 0.132715 carbon dioxide powertrain_eol" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tlca.create_labelled_dynamic_inventory_dataframe()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can do all further analysis as detailed in the other example notebook [electric vehicle tutorial](./2_electric_vehicle_from_scratch.ipynb). Below you just find the quick calculations. For the full dynamic characterization please see the linked notebook." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "34122.72503901273" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Static LCIA from time-explicit LCI\n", + "tlca.static_lcia()\n", + "tlca.static_score #kg CO2-eq" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "At this point, we can already compare these time-explicit results to the results of an \"ordinary\", completely static LCA. These already exist within the TimexLCA class, originally to set the priorities for the graph traversal:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "28089.199999794364" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# compare to fully static non time-explicit LCIA score\n", + "tlca.base_lca.score" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "timex_dev", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file From 906460ee681b22f01d38f23b8d74a258853a1eaf Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 12:29:17 +0200 Subject: [PATCH 12/24] docs: reduce notebook 4 diff to its actual four-cell change The prior update to notebooks/tutorials/4_import_model_from_excel.ipynb was round-tripped through notebook tooling that re-serialized the whole file with different indentation and no trailing newline, turning a four-cell content change into a ~2100-line diff. Restore the file to the original's formatting (4-space indent, alphabetical keys, trailing newline) while keeping only the intended edits to cells 21, 22, 23, and 27. --- .../tutorials/4_import_model_from_excel.ipynb | 2116 ++++++++--------- 1 file changed, 1058 insertions(+), 1058 deletions(-) diff --git a/notebooks/tutorials/4_import_model_from_excel.ipynb b/notebooks/tutorials/4_import_model_from_excel.ipynb index 07845b36..e2700d2a 100644 --- a/notebooks/tutorials/4_import_model_from_excel.ipynb +++ b/notebooks/tutorials/4_import_model_from_excel.ipynb @@ -1,1069 +1,1069 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Loading your LCA model with temporal distributions from an excel file\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This notebook is essentially a short version of the [electric vehicle tutorial](./2_electric_vehicle_from_scratch.ipynb) example notebook, but shows how to import the foreground model from an [excel file](../data/electric_vehicle_foreground.xlsx). For a more detailed explaination of how timex works, please see one of the other notebooks. \n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Set up a bw project\n", - "\n", - "import bw2data as bd\n", - "\n", - "bd.projects.set_current(\"electric_vehicle_standalone_excel\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Fresh start\n", - "for db in list(bd.databases):\n", - " del bd.databases[db]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "100%|██████████| 1/1 [00:00<00:00, 7256.58it/s]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[2m17:01:00+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\n" - ] - } - ], - "source": [ - "# Add some background databases\n", - "\n", - "biosphere = bd.Database(\"biosphere\")\n", - "biosphere.register()\n", - "biosphere.write(\n", - " {\n", - " (\"biosphere\", \"CO2\"): {\n", - " \"type\": \"emission\",\n", - " \"name\": \"carbon dioxide\",\n", - " },\n", - " }\n", - ")\n", - "\n", - "background_2020 = bd.Database(\"background_2020\")\n", - "background_2020.register()\n", - "\n", - "background_2030 = bd.Database(\"background_2030\")\n", - "background_2030.register()\n", - "\n", - "background_2040 = bd.Database(\"background_2040\")\n", - "background_2040.register()\n", - "\n", - "background_2020.write({})\n", - "background_2030.write({})\n", - "background_2040.write({})\n", - "\n", - "background_databases = [\n", - " background_2020,\n", - " background_2030,\n", - " background_2040,\n", - "]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We now create some very simple processes within these databases. These process get only one aggregated CO2-emission each. The amounts of these emissions change over time." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "process_co2_emissions = {\n", - " \"glider\": (10, 5, 2.5), # for 2020, 2030 and 2040\n", - " \"powertrain\": (20, 10, 7.5),\n", - " \"battery\": (10, 5, 4),\n", - " \"electricity\": (0.5, 0.25, 0.075),\n", - " \"glider_eol\": (0.01, 0.0075, 0.005),\n", - " \"powertrain_eol\": (0.01, 0.0075, 0.005),\n", - " \"battery_eol\": (1, 0.5, 0.25),\n", - "}\n", - "\n", - "node_co2 = biosphere.get(\"CO2\")\n", - "\n", - "for component_name, gwis in process_co2_emissions.items():\n", - " for database, gwi in zip(background_databases, gwis):\n", - " database.new_node(component_name, name=component_name, location=\"somewhere\").save()\n", - " component = database.get(component_name)\n", - " component[\"reference product\"] = component_name \n", - " component.save()\n", - " production_amount = -1 if \"eol\" in component_name else 1\n", - " component.new_edge(input=component, amount=production_amount, type=\"production\").save()\n", - " component.new_edge(input=node_co2, amount=gwi, type=\"biosphere\").save()\n", - "\n", - "# register the databases\n", - "for db in bd.databases:\n", - " bd.Database(db).process() \n", - " " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Case study setup\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this study, we consider the following production system for our ev. Purple boxes are foreground, cyan boxes are background (i.e., ecoinvent/premise)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```{mermaid}\n", - "flowchart LR\n", - " glider_production(glider production):::ei-->ev_production\n", - " powertrain_production(powertrain production):::ei-->ev_production\n", - " battery_production(battery production):::ei-->ev_production\n", - " ev_production(ev production):::fg-->driving\n", - " electricity_generation(electricity generation):::ei-->driving\n", - " driving(driving):::fg-->used_ev\n", - " used_ev(used ev):::fg-->glider_eol(glider eol):::ei\n", - " used_ev-->powertrain_eol(powertrain eol):::ei\n", - " used_ev-->battery_eol(battery eol):::ei\n", - "\n", - " classDef ei color:#222832, fill:#3fb1c5, stroke:none;\n", - " classDef fg color:#222832, fill:#9c5ffd, stroke:none;\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Importing the product system from Excel\n", - "\n", - "As an alternative to generating your temporal foreground system in code as above, you can also import the case study processes from a BW25-excel file.\n", - "You need `bw2io > 0.9.14`, which supports the import of `TemporalDistributions` in the ExcelImporter or CSVImporter. You can consult the sample excel file under notebooks/data/electric_vehicle_foreground.xlsx for valid input formats for the TDs.\n", - "\n", - "Please make sure you created and processed the biosphere and the background databases for 2020, 2030 and 2040 using the code above." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Extracted 1 worksheets in 0.00 seconds\n", - "Applying strategy: csv_restore_tuples\n", - "Applying strategy: csv_restore_booleans\n", - "Applying strategy: csv_numerize\n", - "Applying strategy: csv_drop_unknown\n", - "Applying strategy: csv_restore_temporal_distributions\n", - "Applying strategy: csv_add_missing_exchanges_section\n", - "Applying strategy: normalize_units\n", - "Applying strategy: strip_biosphere_exc_locations\n", - "Applying strategy: set_code_by_activity_hash\n", - "Applying strategy: link_iterable_by_fields\n", - "Applying strategy: assign_only_product_as_production\n", - "Applying strategy: link_technosphere_by_activity_hash\n", - "Applying strategy: drop_falsey_uncertainty_fields_but_keep_zeros\n", - "Applying strategy: convert_uncertainty_types_to_integers\n", - "Applying strategy: convert_activity_parameters_to_list\n", - "Applied 15 strategies in 0.02 seconds\n", - "Applying strategy: link_iterable_by_fields\n", - "Applying strategy: link_iterable_by_fields\n", - "Graph statistics for `foreground` importer:\n", - "3 graph nodes:\n", - "\tNone: 3\n", - "12 graph edges:\n", - "\ttechnosphere: 9\n", - "\tproduction: 3\n", - "12 edges to the following databases:\n", - "\tbackground_2020: 7\n", - "\tforeground: 5\n", - "0 unique unlinked edges (0 total):\n", - "\n", - "\n", - "\u001b[2m17:01:00+0200\u001b[0m [\u001b[33m\u001b[1mwarning \u001b[0m] \u001b[1mNot able to determine geocollections for all datasets. This database is not ready for regionalization.\u001b[0m\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "100%|██████████| 3/3 [00:00<00:00, 4463.61it/s]" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[2m17:01:00+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n", - "Created database: foreground\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\n" - ] - } - ], - "source": [ - "if \"foreground\" in bd.databases:\n", - " del bd.databases[\"foreground\"] # to make sure we import the foreground from scratch from the excel file\n", - "\n", - "import bw2io as bi\n", - "\n", - "ei = bi.ExcelImporter(\"../data/electric_vehicle_foreground.xlsx\", sheet_name=\"easy_tds\") \n", - "ei.apply_strategies()\n", - "ei.match_database(\"background_2020\", fields=[\"name\", \"reference product\"])\n", - "ei.match_database(\"biosphere\", fields=[\"name\", \"categories\"])\n", - "\n", - "ei.statistics() #0 unique unlinked edges means that all foreground exchanges are linked and we can successfully import the database.\n", - "\n", - "ei.write_database()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "let's check if the ExcelImporter imported the `TemporalDistributions` correctly:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[ 0 31556952 63113904 94670856 126227808 157784760 189341712\n", - " 220898664 252455616 284012568 315569520 347126472 378683424 410240376\n", - " 441797328 473354280]\n", - "[0.0625 0.0625 0.0625 0.0625 0.0625 0.0625 0.0625 0.0625 0.0625 0.0625\n", - " 0.0625 0.0625 0.0625 0.0625 0.0625 0.0625]\n", - "timedelta64[s]\n" - ] - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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Jbgs1I4dO9xUF5FNjEOGDHGT244bN866du0af79m/te1PhX2dFknVd/UzPnOO++4vx84cGCC51WftNGjR7tw16xZM3cZKfV90yWk1q9fb82bN0+wfAAAgFBNZiv169d3M/jr+pkKWprq4qqrrnK36blup6hPmi4Npak0NL3G+PHj7ZNPPrFu3brZBx98kOS6ndK6dWt31QHNnTZv3jz3fD4IalkapQEAAJAdcsTFxZ3LliUjMDTAQVXHUqVKRVWHyawS9vWXsG8D1p/3P8z7v/AZOB6V+0BUVNIAAADChpAGAAAQQIQ0AACAACKkAQAABBAhDQAAIIAIaQAAAAFESAMAAAggQhoAAEAAEdIAAAACiJAGAAAQQIQ0AACAACKkAQAABBAhDQAAIIAIaQAAAAFESAMAAAggQhoAAEAAEdIAAAACiJAGAAAQQIQ0AACAACKkAQAABBAhDQAAIIAIaQAAAAFESAMAAAggQhoAAEAAEdIAAAACiJAGAAAQQIQ0AACAACKkAQAABBAhDQAAIIAIaQAAAAFESAMAAAggQhoAAEAshbRp06bZokWL0vXYxYsXu8cDAAAgm0PaAw88YC+88EK6Hjty5Ejr3bt3Zp8KAAAgdM6rufPcuXNZ90oAAABwYfuk/fjjj5Y3b94L8VQAAAAxIXd2Lvz48eP20Ucf2ddff20VKlTIzqcCAAAIZ0h75pln7Lnnnktw3yeffGJXXHFFuv7+9ttvz/irAwAACKncme2DliNHjnT1SStUqJD9+te/tocffjhzrxAAACCE0h3S7r//fuvSpYv7t8LZzTffbNWrV7fJkycn+3iFuMsuu8yKFi2ada8WAAAgJNId0goXLux+vLvuusv1MytdunR2vTYAAIDQyvTozvHjx9tDDz1kF8rq1autY8eOLhSWKFHCmjZtarNnz87QMk6cOGHPPvusqwAWL17cKlWqZH369LG9e/em+DcnT560sWPHWsOGDa1kyZLup3bt2ta/f/8kj33qqaesbdu2dsMNN9jVV19t5cqVc383btw4++mnnzK13gAAIJyydXRnVlm6dKl16NDBTePRvn17K1CggM2ZM8d69uxpO3futAcffDDNZZw9e9Y11+oqCTVr1rQ2bdrYpk2bbMqUKbZkyRJbuHChFStWLMHfxMXFueddtWqV1apVy3r06OHu37Ztm82aNctGjBiR4PETJ0608uXLW6NGjezKK6+0I0eO2LJly+yJJ55wV1xYsGCBawIGAADI9pCmEDJ//nzbvHmzCyUpDSZQHzUFq4w6ffq0q3blzJnT5s6da1WrVnX3DxgwwJo0aWJPPvmkq16l1ew6depUF9DuvPNOF6b0emTSpEnWt29fGzZsmI0aNSrB3+gqCarg6fGq4iV+XYlpqpHk5oP7/e9/b2+88Ya9/vrrdu+992Z4GwAAgPDJdEg7deqUCxw+eKU10tOHosxU0bZs2WJdu3aND2ii/nEKV7o8lapUjzzySKrLUcVMBg0alOC1qBr30ksv2YwZM2z48OGWL18+d/+KFStcKNTI1MQBTXLnTrrpUpqwt127di6kKcgCAABka0h78cUX7e2333aB57bbbrNbbrnFNfGp4pWVVKmTxo0bJ/mdKmmyfPnyNCfVXblyZbIDHfT61TypUapr1qyxOnXquPvVnOkD1v79++29995zfdeuvfZaa9asWbrnhxM1c8r111+f7r8BAADhlumQpsqTAo6aAtVvK7uo35ior1di6vyv/mlpVahUiVOfNHXkT46/X8/lQ9ratWvj7+vVq5cdOnQo/vF6TlXf1D8uOaNHj3YDBQ4ePOgm/FX4U8jUiFgAAIBsDWnbt2+3a665JlsDmvhwpElxk1OwYMEEASq1ZUROIRLJLztyOfv27XO3gwcPds2djz76qBUpUsRVxTSyU8GtYsWKVqVKlWRD2oEDB+L/36lTJxs5cqRdcskl6Vjj/1/5y0oaoRp5GzZhX38J+zZg/Xn/I/eDMOIzcDLBdriYMnIt80yHNAUeVbJilSpvUrlyZZswYUJ8PzYFrsOHD1u/fv3s5ZdftjFjxiT5W1/Z27Nnj+tT95e//MVNGfLmm2+65tK07Nq1y86cOZPl66TXE2ZhX38J+zZg/Xn/w47PwJ6Luv1z5cqVYqteloa0unXrutGSSqV58uSx7JJclSuSApMqXOlZhpof01ut8/9u0aJFkkEPLVu2dCFNzZipUYhVFU5NtWruHDhwYIpXaIikeeCykt4jfTD1erLzvQqqsK+/hH0bsP68/2He/4XPwMmo3AcyHdLU5KepN3ThdY2YzC6+L5r6hulSVJG0wTXthyanTU3ZsmXdgIaU+q75+yP7vWmQgUJYck2k/r70Nkvq9SlI+kEQWVkKzQjtmNm17GgQ9vWXsG8D1p/3P8z7v/AZyBNV+0Cmh2Kq0qSAptn41YFe01Vs2LDBduzYkeJPZit2snjx4iS/UyUv8jEp0bQaNWrUsI0bN7q+dJE0dciHH35o+fPnt2rVqsXfX69ePXerdUrM35feS2IpSKpal94+aQAAAJmupN10003x//7oo4/cT2rUZKipLDKqQYMGrhI2c+ZM11nfz5Wmpkt1xtdZQefOneMfv3v3bheIVNKMrIJ1797dzX02dOjQBJPZqvlx69at7moCfo400QS56kumUaz33Xefu9STLxlrPjU/PYenZej5Lr/88iTzyT322GOuj5um7gAAAMjWkJbW5LXn+/jISWM13YVGkbZq1SrBZaFUndMVB8qUKRP/+CFDhrjJbXW9TE2A6+mSULrWp8KeLuuk6puaOd955x339+ovlrhSqFGaCncKV7qMlJosdQmp9evXW/PmzRMsX3O1aXLdX/7yly5Uah41Ncfq8d99951dd9119uc//zlT2wAAAIRPpkPajz/+aBdK/fr1Xf83VbAUtFSd0qhLBbKU5ipLTH3SdGkoTcI7ffp0d4F4Vb26devmAlri63ZK69atXTOurtE5b948O3bsmOu3pufVJaM0SsNTOFOg+/jjj+3zzz93lT6FSYUzVQB/97vfcd1OAACQbjni4uIyV+JC1NAAB1UdS5UqFVUdJrNK2Ndfwr4NWH/e/zDv/8Jn4HhU7gNZew0nAAAAZAlCGgAAQCz1Sbv99tsz9HiNplRnfwAAAGRjSEvPxKx+mguN7Ew8az8AAACyIaRpiouU/PTTT/btt9+6a1VqzrJHHnnErr766sw+FQAAQOhkOqRp3rG0aBJXTT3x6quvuguNAwAAIAADBzQDvy4b9f3337tLSAEAACAgozt1eaZKlSrZe++9l91PBQAAEDMuyBQcJ06ccJdIAgAAQEBC2hdffGGbNm2yokWLZvdTAQAAxIxMDxzQ5RVSoik39u7da59++qmNGTPG/f+2227L7FMBAACETqZD2k033ZSuxymglS1b1h5//PHMPhUAAEDoZDqkKXylJn/+/FauXDlr2bKl9e7d2woVKpTZpwIAAAidTIe0H3/8MWtfCQAAAOJxgXUAAIAAIqQBAAAEUKabOyN99tlntmDBAtu4caMdPnzYChYsaBUrVrRmzZqle4ABAAAAsiikHThwwB544AEX0BIPJsiRI4c9/fTT1qJFC3dpqCuuuOJ8ngrnYddPZ2xlXE7LVfSMlct7YTfld0fP2KZDp618odx2bf5cF+W5S1565oI+b1DW+0I/dxjXmecO1zZnPwvPe32xn/u8Q5quInDHHXfY559/7sJZ1apVrXLlynb11Vfb7t277auvvrJ169bZ/PnzrX379vb+++/bpZdemrWvHmma8s1R+9PyODtreS3nF3E2qq7Zbyrmv3DP/Z84O3vOLGcOs1F1ilyc5zazx3+ey/5QysK13hfwucO4zjx3uLY5+1l43uuL/dxZ0idt4sSJLoSVKFHC3nrrLVuyZIlNmDDBBg8e7G71/7ffftuuvfZa97i///3vWfvKka6zALeT/e//un3oP3Hu/gv23P8rrur2oj23mT39bR5XUQzVel+g5w7jOvPc4drm7GcXdnuHeZtnWUibPXu2a9KcOnWqNWjQINnH1K9f315//XVXaZs1a1ZmnwqZpDKt38m8M+fMNh86HbrnPms5bMvhsxfluWN9m4dxnXnucG1z9rMLu73DvM2zLKRpkECFChVcM2dq9HsNItDjcWGpHV1l2ki5cpiVK5Q7dM+d087ZzwrmDN16X4jnDuM689zh2ubsZxd2e4d5myeW6aPWqVOnLF++fOl6rB6nx+PCUkdHtaNr5xLdvlinyAXpABm053785yetxGXhW+8L8dxhXGeeO1zbnP3swm7vMG/zxHLExcWlfn2nFPziF7+wnTt32tdff21FihRJ9coE119/veubtmrVqsw8Fc7T5gNH7dNNu+2W8ldbuSsubMdHteGrRFzuIo3M0XNfe+lpO7N/l5UqVcry5s0bmvWOfO7jx4/bjh07snUbBG2dI2Xn+gd5vbNz/aNhvbN6/aNpnbNyG0Tzep/P+l/M9fYyXbtr1KiRGwygKTgmTZqU7Epro+j3J0+etKZNm57va0UmqYJUo8jZC1JJSkw79sXauf1zHz9+znbsvzjPfTFcrOcO4zrz3OHa5uxnF961If1sn3dI+9Of/mTTp093U2yo31mPHj3cFBxXXXWV/fDDD24KjldffdX27dvnJrf94x//mLWvHAAAIIZlOqSp+VIjOxXO9u7dayNGjEjyGI3qLFasmAtrejwAAADS57yGKtx666326aefumbPDz74wI3gPHLkiBUoUMCN6GzevLn99re/5WoDAAAA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nnL//s1vfpNl2wUAAMS2wLe9qXlQ013o8kytWrVKcFmoHTt2uCsOqBnRGzJkiJvcdty4cda1a9f4+3VJKF3rU2FPTY+qaqlp8p133nF/P3DgwATPqz5mo0ePduFOk9PqMlLq+6ZLSK1fv95d/ily+fLCCy/Ybbfd5u6/44473LU7NRpUj7/33nvdpaUAAABiopIm9evXt/nz57uQo6ClSzldddVV7jY91+0U9UnTpaE0lYam1xg/frx98skn1q1bN1f5SnzdTmndurW76oAqZPPmzXPP54OglqWhtJGuv/561wSrgQUKZ3/961/dYAFd4/O5557Loq0BAADCIEdcXNz/tf0hJmmAg6qOpUqViqpRLVkl7OsvYd8GrD/vf5j3f+EzcDwq94GoqKQBAACEDSENAAAggAhpAAAAAURIAwAACCBCGgAAQAAR0gAAAAKIkAYAABBAhDQAAIAAIqQBAAAEECENAAAggAhpAAAAAURIAwAACCBCGgAAQAAR0gAAAAKIkAYAABBAhDQAAIAAIqQBAAAEECENAAAggAhpAAAAAURIAwAACCBCGgAAQAAR0gAAAAKIkAYAABBAhDQAAIAAIqQBAAAEECENAAAggAhpAAAAAURIAwAACCBCGgAAQAAR0gAAAAKIkAYAABBAhDQAAIAAIqQBAAAEECENAAAggAhpAAAAAURIAwAACCBCGgAAQAAR0gAAAAKIkAYAABBAhDQAAIAAIqQBAAAEECEtJHLlymVhFvb1l7BvA9af9z/s+AzksmiTIy4u7tzFfhEAAABIiEoaAABAABHSAAAAAoiQBgAAEECENAAAgAAipAEAAAQQIQ0AACCACGkAAAABREgDAAAIIEIaEELnzjGHNQAEHSEthq1YscK++uor++GHHy72SwmMJUuW2C233GIff/yxhZHfFyJDWtgCW9j3gbSEbX+Qw4cP29ixY23t2rUWVmF836NhH8h9sV8Ast7cuXNt6NCh9t1339nJkyetdOnSds8999hvfvMby58/fyg3+a5du6x///42b948K1u2rPtAhsn3339vQ4YMsc8++8zOnj1r119/vf32t7+1+vXrW44cOSwMwr4PpGTmzJlu2xQpUsSaNGli11xzjdsntJ/kzBn75/Fjxoyxv/zlL1a8eHGrUqWKhdFrr71mn3/+uRUqVMhq1aplt956q+XLl8/CYkyA9wGu3Rlj/v73v9vAgQPtF7/4hfuwXXLJJe4DqC/hbt262RNPPOF2xDBRYB01apRdffXV1r17d2vdurXdcMMNFoYzYx1s33nnHevTp4/70tV6Hz161J0tKsCPGDHCbZNYF9Z9IDX//e9/7U9/+pNt3rzZhbETJ064oNajRw8bPHiwxbr333/fHnnkEdu+fbu1b9/e7rzzTmvQoEGowsmaNWvsD3/4g3377bd22WWXWVxcnLv/tttuc5+ZihUrWix7Pwr2AUJaDDlw4IC1atXK/fvll1+2qlWrun9//fXX9vTTT7uDtSpqOjCHwSeffGJdunRx20VVRH0AFV6D9AHMbqdPn3ZfPjoQjxw50ho3bmy5c+e2BQsWuC9nVZPGjx/v9ps8efJYrGEfSN4333zjwtixY8fsoYcessqVK7sD9OOPP24bN260xx57zHr27GlXXnllfNiPFVu3brW+ffvahx9+aHXq1LHOnTtb06ZNXQUxTPR+ax9Yv369O3nXttB3o8LZ7Nmz3f9VXbr55ptjrqq6NYr2AZo7Y4h2PB/IFND8B6tSpUr20ksv2apVq+yVV16xZs2auTOlWKdtcerUKatZs6arJJUrV87dr4rBpZde6sKbzh7z5s1rsUph7N///rcLZM2bN3f3ab/Qv4cPH+4qJgrtqjDVrl3bYo0OQGHfByL57wTtF9o2f/vb36xjx47xvy9YsKD7/tD3hZq+evXqFVMBTaZMmeIOzjph0Wcg1qtFKVm5cqXrn6kgppNY76mnnrJrr73WXnzxRXdi9+qrr8ZUQIu2fSC2tnzI7dy5093u37/f3foP1pkzZ1wzxpNPPun+r75JsX4gkl/96leuiqRwumHDBte8p8EU6n+gypFCiSpt+sD6Mn80d57VOqhs7ytokftAhQoV3K0Ciz/otmjRwu69914XZt988834/Saa+WrQu+++6/7fsmXLUO0DafH7g5o61RVCleXI/UVdJNRvT98XkydPtv/85z8xt03uu+8+1ydRJ7X6PMiWLVvsgw8+sBdeeMEF00mTJtm6desslqkPmqiKKn5bFCtWzFXWatSo4Vpf1F0m8ns1FtwXRfsAIS2G6MNWoEABN2Dg0KFD8ffnypXL3epgpZKuzqD9By9WbNq0yfbu3WtHjhyJPxCpqaZdu3burFDVoqlTp9oDDzzgzhTVzKMvIx2sVGFR3xyJ1qrBs88+6/pYTZgwwf1fTZq+YhT5haz7/TpqEImCijoJz5kzJxBfSOdD77HCqLbBtm3b3H3qfxmWfSAtClr+QKv1VvO2b+L23xGika86SKlJ9O2333Z9GGNhm/igedVVV8X3xVMf3oULF9qf//xn69q1qw0bNszeeOMN69evn2sCW7RoUfxBPFppFLOOCZ7fB4oWLepuVU0WhXb/e+0PzzzzTHzXGZ3ARWs17eOPP3bfcaoc+qJFNO0D0bnVkSx98appU81biasi2jFFO57oy/enn36K+i2pMz013akj+E033eT6nWm0WuQBRx+01atXu743Omh/9NFH9t5777kflbyvu+46tz2mTZvm/iaazhj1uhXOFEAUuLQd9Pr9OqhS5PugKbj4UXuemv86dOhgP/74oy1dujQqqyZ+G+ig4iuGvirmq0OxvA8kRycs//znP937riqi6L33B1o1ayp86feJ11fNwLfffrtVq1bN/V7T+EQb7c86uKpjvCrFEhk077rrLvvlL3/pKqjqp7t8+XLX0rBs2TK3P/zud79z4UUH7midqsUPFlOLgkKH5/eBK664woX0Tz/9NME+oN/rO0BVVjWF66T+rbfesmhc/y5durj1//LLL23x4sUJTkiiZR8gpMUQNVGor5nOmv71r38l+J12TH3wtFPqwK0ziGieP2337t3ubF8j9VQt0hdKo0aNXEfx+++/333ItL7qb6RmPTV7qcStZjCFOR2IChcu7A7Oav7VF7hG/0k0nDGqyqEvH3X8Vdl+3Lhx7gywbt267vXrR1+6OmPUF5WaQRVCItdP20f/VrNGqVKl4r+Io6VqovAQuQ3Ud0b9a0TNdP7sV52hY3EfSInCqjp7DxgwwH7961+7bfTwww+75hxP94mCnMKa/37wSpQoYXfccYcL9vquiKbgqs+Bgrn2e7UcaF3/+te/xp+46oRV4USj+rRO6pekMKfvE01No22n5j79XgFPTWDRdkKrz4GmktB66b33/VHFv89aV1WYX3/9dfd96r8zIh+jz4v2DQV97SfRtv5r1651Jxz6rKt1Sevn1y1a9oHo/SZCspT+9cFTk4/OHhJTnxwdlCK/sKPNnj17bNCgQe6Do8qIOj+r+VZfNr6/ne7zYaN69eruw6fwJvpQ6nf+w6pgq4EWGobuqw5BppChg5D6Vqn/hDr3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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "driving = bd.get_node(database=\"foreground\", name=\"driving an electric vehicle\")\n", - "ev_to_driving = next(exc for exc in driving.technosphere() if exc.input[\"name\"] == \"electricity\")\n", - "\n", - "print(ev_to_driving[\"temporal_distribution\"].date) # original resolution was timedelta64[M], which gets converted to seconds\n", - "print(ev_to_driving[\"temporal_distribution\"].amount)\n", - "print(ev_to_driving[\"temporal_distribution\"].date.dtype)\n", - "\n", - "ev_to_driving[\"temporal_distribution\"].graph(resolution=\"M\")" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "TemporalDistribution instance with 16 values and total: 1" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ev_to_driving[\"temporal_distribution\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Add a characterization method" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Finally, we need some characterization method. Again, this is just a simple made-up one:" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "bd.Method((\"GWP\", \"example\")).write(\n", - " [\n", - " ((\"biosphere\", \"CO2\"), 1),\n", - " ]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## LCA using `bw_timex`\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now that the data is set up, we can get startet with the actual time-explicit LCA. As usual, we need to select a method first:" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "method = (\"GWP\", \"example\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": "`bw_timex` needs to know the representative time of the databases. We record it once, as `representative_time` metadata on each database, using `set_database_metadata`:" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": "from datetime import datetime\n\nfrom bw_timex import set_database_metadata\n\nset_database_metadata(\"background_2020\", representative_time=datetime(2020, 1, 1))\nset_database_metadata(\"background_2030\", representative_time=datetime(2030, 1, 1))\nset_database_metadata(\"background_2040\", representative_time=datetime(2040, 1, 1))\n# the \"foreground\" database, which holds our demand, is treated as \"dynamic\" automatically" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": "Now, we can instantiate a `TimexLCA`. It's structure is similar to a normal `bw2calc.LCA` - `TimexLCA` reads the metadata we just set automatically, so no timing argument is needed.\n\nNot sure about the required inputs? Check the documentation using `?`. All our classes and methods have docstrings!" - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "from bw_timex import TimexLCA" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's create a `TimexLCA` object for our EV life cycle:" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "driving = bd.get_node(database=\"foreground\", code=\"driving\", name=\"driving an electric vehicle\", unit=\"transport over an ev lifetime\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": "# intialize the TimexLCA object with the functional unit and method\ntlca = TimexLCA({driving: 1}, method)\n# build the timeline with a temporal grouping of \"month\"\ntlca.build_timeline(temporal_grouping=\"month\")\n# calculate the time-explicit LCI\ntlca.lci()" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's check the dynamic invenrotry in a human readale format" - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.microsoft.datawrangler.viewer.v0+json": { - "columns": [ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Loading your LCA model with temporal distributions from an excel file\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook is essentially a short version of the [electric vehicle tutorial](./2_electric_vehicle_from_scratch.ipynb) example notebook, but shows how to import the foreground model from an [excel file](../data/electric_vehicle_foreground.xlsx). For a more detailed explaination of how timex works, please see one of the other notebooks. \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Set up a bw project\n", + "\n", + "import bw2data as bd\n", + "\n", + "bd.projects.set_current(\"electric_vehicle_standalone_excel\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Fresh start\n", + "for db in list(bd.databases):\n", + " del bd.databases[db]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 1/1 [00:00<00:00, 7256.58it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[2m17:01:00+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "# Add some background databases\n", + "\n", + "biosphere = bd.Database(\"biosphere\")\n", + "biosphere.register()\n", + "biosphere.write(\n", + " {\n", + " (\"biosphere\", \"CO2\"): {\n", + " \"type\": \"emission\",\n", + " \"name\": \"carbon dioxide\",\n", + " },\n", + " }\n", + ")\n", + "\n", + "background_2020 = bd.Database(\"background_2020\")\n", + "background_2020.register()\n", + "\n", + "background_2030 = bd.Database(\"background_2030\")\n", + "background_2030.register()\n", + "\n", + "background_2040 = bd.Database(\"background_2040\")\n", + "background_2040.register()\n", + "\n", + "background_2020.write({})\n", + "background_2030.write({})\n", + "background_2040.write({})\n", + "\n", + "background_databases = [\n", + " background_2020,\n", + " background_2030,\n", + " background_2040,\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now create some very simple processes within these databases. These process get only one aggregated CO2-emission each. The amounts of these emissions change over time." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "process_co2_emissions = {\n", + " \"glider\": (10, 5, 2.5), # for 2020, 2030 and 2040\n", + " \"powertrain\": (20, 10, 7.5),\n", + " \"battery\": (10, 5, 4),\n", + " \"electricity\": (0.5, 0.25, 0.075),\n", + " \"glider_eol\": (0.01, 0.0075, 0.005),\n", + " \"powertrain_eol\": (0.01, 0.0075, 0.005),\n", + " \"battery_eol\": (1, 0.5, 0.25),\n", + "}\n", + "\n", + "node_co2 = biosphere.get(\"CO2\")\n", + "\n", + "for component_name, gwis in process_co2_emissions.items():\n", + " for database, gwi in zip(background_databases, gwis):\n", + " database.new_node(component_name, name=component_name, location=\"somewhere\").save()\n", + " component = database.get(component_name)\n", + " component[\"reference product\"] = component_name \n", + " component.save()\n", + " production_amount = -1 if \"eol\" in component_name else 1\n", + " component.new_edge(input=component, amount=production_amount, type=\"production\").save()\n", + " component.new_edge(input=node_co2, amount=gwi, type=\"biosphere\").save()\n", + "\n", + "# register the databases\n", + "for db in bd.databases:\n", + " bd.Database(db).process() \n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Case study setup\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this study, we consider the following production system for our ev. Purple boxes are foreground, cyan boxes are background (i.e., ecoinvent/premise)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```{mermaid}\n", + "flowchart LR\n", + " glider_production(glider production):::ei-->ev_production\n", + " powertrain_production(powertrain production):::ei-->ev_production\n", + " battery_production(battery production):::ei-->ev_production\n", + " ev_production(ev production):::fg-->driving\n", + " electricity_generation(electricity generation):::ei-->driving\n", + " driving(driving):::fg-->used_ev\n", + " used_ev(used ev):::fg-->glider_eol(glider eol):::ei\n", + " used_ev-->powertrain_eol(powertrain eol):::ei\n", + " used_ev-->battery_eol(battery eol):::ei\n", + "\n", + " classDef ei color:#222832, fill:#3fb1c5, stroke:none;\n", + " classDef fg color:#222832, fill:#9c5ffd, stroke:none;\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Importing the product system from Excel\n", + "\n", + "As an alternative to generating your temporal foreground system in code as above, you can also import the case study processes from a BW25-excel file.\n", + "You need `bw2io > 0.9.14`, which supports the import of `TemporalDistributions` in the ExcelImporter or CSVImporter. You can consult the sample excel file under notebooks/data/electric_vehicle_foreground.xlsx for valid input formats for the TDs.\n", + "\n", + "Please make sure you created and processed the biosphere and the background databases for 2020, 2030 and 2040 using the code above." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Extracted 1 worksheets in 0.00 seconds\n", + "Applying strategy: csv_restore_tuples\n", + "Applying strategy: csv_restore_booleans\n", + "Applying strategy: csv_numerize\n", + "Applying strategy: csv_drop_unknown\n", + "Applying strategy: csv_restore_temporal_distributions\n", + "Applying strategy: csv_add_missing_exchanges_section\n", + "Applying strategy: normalize_units\n", + "Applying strategy: strip_biosphere_exc_locations\n", + "Applying strategy: set_code_by_activity_hash\n", + "Applying strategy: link_iterable_by_fields\n", + "Applying strategy: assign_only_product_as_production\n", + "Applying strategy: link_technosphere_by_activity_hash\n", + "Applying strategy: drop_falsey_uncertainty_fields_but_keep_zeros\n", + "Applying strategy: convert_uncertainty_types_to_integers\n", + "Applying strategy: convert_activity_parameters_to_list\n", + "Applied 15 strategies in 0.02 seconds\n", + "Applying strategy: link_iterable_by_fields\n", + "Applying strategy: link_iterable_by_fields\n", + "Graph statistics for `foreground` importer:\n", + "3 graph nodes:\n", + "\tNone: 3\n", + "12 graph edges:\n", + "\ttechnosphere: 9\n", + "\tproduction: 3\n", + "12 edges to the following databases:\n", + "\tbackground_2020: 7\n", + "\tforeground: 5\n", + "0 unique unlinked edges (0 total):\n", + "\n", + "\n", + "\u001b[2m17:01:00+0200\u001b[0m [\u001b[33m\u001b[1mwarning \u001b[0m] \u001b[1mNot able to determine geocollections for all datasets. This database is not ready for regionalization.\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 3/3 [00:00<00:00, 4463.61it/s]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[2m17:01:00+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n", + "Created database: foreground\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "if \"foreground\" in bd.databases:\n", + " del bd.databases[\"foreground\"] # to make sure we import the foreground from scratch from the excel file\n", + "\n", + "import bw2io as bi\n", + "\n", + "ei = bi.ExcelImporter(\"../data/electric_vehicle_foreground.xlsx\", sheet_name=\"easy_tds\") \n", + "ei.apply_strategies()\n", + "ei.match_database(\"background_2020\", fields=[\"name\", \"reference product\"])\n", + "ei.match_database(\"biosphere\", fields=[\"name\", \"categories\"])\n", + "\n", + "ei.statistics() #0 unique unlinked edges means that all foreground exchanges are linked and we can successfully import the database.\n", + "\n", + "ei.write_database()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "let's check if the ExcelImporter imported the `TemporalDistributions` correctly:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 0 31556952 63113904 94670856 126227808 157784760 189341712\n", + " 220898664 252455616 284012568 315569520 347126472 378683424 410240376\n", + " 441797328 473354280]\n", + "[0.0625 0.0625 0.0625 0.0625 0.0625 0.0625 0.0625 0.0625 0.0625 0.0625\n", + " 0.0625 0.0625 0.0625 0.0625 0.0625 0.0625]\n", + "timedelta64[s]\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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Jbgs1I4dO9xUF5FNjEOGDHGT244bN866du0af79m/te1PhX2dFknVd/UzPnOO++4vx84cGCC51WftNGjR7tw16xZM3cZKfV90yWk1q9fb82bN0+wfAAAgFBNZiv169d3M/jr+pkKWprq4qqrrnK36blup6hPmi4Npak0NL3G+PHj7ZNPPrFu3brZBx98kOS6ndK6dWt31QHNnTZv3jz3fD4IalkapQEAAJAdcsTFxZ3LliUjMDTAQVXHUqVKRVWHyawS9vWXsG8D1p/3P8z7v/AZOB6V+0BUVNIAAADChpAGAAAQQIQ0AACAACKkAQAABBAhDQAAIIAIaQAAAAFESAMAAAggQhoAAEAAEdIAAAACiJAGAAAQQIQ0AACAACKkAQAABBAhDQAAIIAIaQAAAAFESAMAAAggQhoAAEAAEdIAAAACiJAGAAAQQIQ0AACAACKkAQAABBAhDQAAIIAIaQAAAAFESAMAAAggQhoAAEAAEdIAAAACiJAGAAAQQIQ0AACAACKkAQAABBAhDQAAIIAIaQAAAAFESAMAAAggQhoAAEAshbRp06bZokWL0vXYxYsXu8cDAAAgm0PaAw88YC+88EK6Hjty5Ejr3bt3Zp8KAAAgdM6rufPcuXNZ90oAAABwYfuk/fjjj5Y3b94L8VQAAAAxIXd2Lvz48eP20Ucf2ddff20VKlTIzqcCAAAIZ0h75pln7Lnnnktw3yeffGJXXHFFuv7+9ttvz/irAwAACKncme2DliNHjnT1SStUqJD9+te/tocffjhzrxAAACCE0h3S7r//fuvSpYv7t8LZzTffbNWrV7fJkycn+3iFuMsuu8yKFi2ada8WAAAgJNId0goXLux+vLvuusv1MytdunR2vTYAAIDQyvTozvHjx9tDDz1kF8rq1autY8eOLhSWKFHCmjZtarNnz87QMk6cOGHPPvusqwAWL17cKlWqZH369LG9e/em+DcnT560sWPHWsOGDa1kyZLup3bt2ta/f/8kj33qqaesbdu2dsMNN9jVV19t5cqVc383btw4++mnnzK13gAAIJyydXRnVlm6dKl16NDBTePRvn17K1CggM2ZM8d69uxpO3futAcffDDNZZw9e9Y11+oqCTVr1rQ2bdrYpk2bbMqUKbZkyRJbuHChFStWLMHfxMXFueddtWqV1apVy3r06OHu37Ztm82aNctGjBiR4PETJ0608uXLW6NGjezKK6+0I0eO2LJly+yJJ55wV1xYsGCBawIGAADI9pCmEDJ//nzbvHmzCyUpDSZQHzUFq4w6ffq0q3blzJnT5s6da1WrVnX3DxgwwJo0aWJPPvmkq16l1ew6depUF9DuvPNOF6b0emTSpEnWt29fGzZsmI0aNSrB3+gqCarg6fGq4iV+XYlpqpHk5oP7/e9/b2+88Ya9/vrrdu+992Z4GwAAgPDJdEg7deqUCxw+eKU10tOHosxU0bZs2WJdu3aND2ii/nEKV7o8lapUjzzySKrLUcVMBg0alOC1qBr30ksv2YwZM2z48OGWL18+d/+KFStcKNTI1MQBTXLnTrrpUpqwt127di6kKcgCAABka0h78cUX7e2333aB57bbbrNbbrnFNfGp4pWVVKmTxo0bJ/mdKmmyfPnyNCfVXblyZbIDHfT61TypUapr1qyxOnXquPvVnOkD1v79++29995zfdeuvfZaa9asWbrnhxM1c8r111+f7r8BAADhlumQpsqTAo6aAtVvK7uo35ior1di6vyv/mlpVahUiVOfNHXkT46/X8/lQ9ratWvj7+vVq5cdOnQo/vF6TlXf1D8uOaNHj3YDBQ4ePOgm/FX4U8jUiFgAAIBsDWnbt2+3a665JlsDmvhwpElxk1OwYMEEASq1ZUROIRLJLztyOfv27XO3gwcPds2djz76qBUpUsRVxTSyU8GtYsWKVqVKlWRD2oEDB+L/36lTJxs5cqRdcskl6Vjj/1/5y0oaoRp5GzZhX38J+zZg/Xn/I/eDMOIzcDLBdriYMnIt80yHNAUeVbJilSpvUrlyZZswYUJ8PzYFrsOHD1u/fv3s5ZdftjFjxiT5W1/Z27Nnj+tT95e//MVNGfLmm2+65tK07Nq1y86cOZPl66TXE2ZhX38J+zZg/Xn/w47PwJ6Luv1z5cqVYqteloa0unXrutGSSqV58uSx7JJclSuSApMqXOlZhpof01ut8/9u0aJFkkEPLVu2dCFNzZipUYhVFU5NtWruHDhwYIpXaIikeeCykt4jfTD1erLzvQqqsK+/hH0bsP68/2He/4XPwMmo3AcyHdLU5KepN3ThdY2YzC6+L5r6hulSVJG0wTXthyanTU3ZsmXdgIaU+q75+yP7vWmQgUJYck2k/r70Nkvq9SlI+kEQWVkKzQjtmNm17GgQ9vWXsG8D1p/3P8z7v/AZyBNV+0Cmh2Kq0qSAptn41YFe01Vs2LDBduzYkeJPZit2snjx4iS/UyUv8jEp0bQaNWrUsI0bN7q+dJE0dciHH35o+fPnt2rVqsXfX69ePXerdUrM35feS2IpSKpal94+aQAAAJmupN10003x//7oo4/cT2rUZKipLDKqQYMGrhI2c+ZM11nfz5Wmpkt1xtdZQefOneMfv3v3bheIVNKMrIJ1797dzX02dOjQBJPZqvlx69at7moCfo400QS56kumUaz33Xefu9STLxlrPjU/PYenZej5Lr/88iTzyT322GOuj5um7gAAAMjWkJbW5LXn+/jISWM13YVGkbZq1SrBZaFUndMVB8qUKRP/+CFDhrjJbXW9TE2A6+mSULrWp8KeLuuk6puaOd955x339+ovlrhSqFGaCncKV7qMlJosdQmp9evXW/PmzRMsX3O1aXLdX/7yly5Uah41Ncfq8d99951dd9119uc//zlT2wAAAIRPpkPajz/+aBdK/fr1Xf83VbAUtFSd0qhLBbKU5ipLTH3SdGkoTcI7ffp0d4F4Vb26devmAlri63ZK69atXTOurtE5b948O3bsmOu3pufVJaM0SsNTOFOg+/jjj+3zzz93lT6FSYUzVQB/97vfcd1OAACQbjni4uIyV+JC1NAAB1UdS5UqFVUdJrNK2Ndfwr4NWH/e/zDv/8Jn4HhU7gNZew0nAAAAZAlCGgAAQCz1Sbv99tsz9HiNplRnfwAAAGRjSEvPxKx+mguN7Ew8az8AAACyIaRpiouU/PTTT/btt9+6a1VqzrJHHnnErr766sw+FQAAQOhkOqRp3rG0aBJXTT3x6quvuguNAwAAIAADBzQDvy4b9f3337tLSAEAACAgozt1eaZKlSrZe++9l91PBQAAEDMuyBQcJ06ccJdIAgAAQEBC2hdffGGbNm2yokWLZvdTAQAAxIxMDxzQ5RVSoik39u7da59++qmNGTPG/f+2227L7FMBAACETqZD2k033ZSuxymglS1b1h5//PHMPhUAAEDoZDqkKXylJn/+/FauXDlr2bKl9e7d2woVKpTZpwIAAAidTIe0H3/8MWtfCQAAAOJxgXUAAIAAIqQBAAAEUKabOyN99tlntmDBAtu4caMdPnzYChYsaBUrVrRmzZqle4ABAAAAsiikHThwwB544AEX0BIPJsiRI4c9/fTT1qJFC3dpqCuuuOJ8ngrnYddPZ2xlXE7LVfSMlct7YTfld0fP2KZDp618odx2bf5cF+W5S1565oI+b1DW+0I/dxjXmecO1zZnPwvPe32xn/u8Q5quInDHHXfY559/7sJZ1apVrXLlynb11Vfb7t277auvvrJ169bZ/PnzrX379vb+++/bpZdemrWvHmma8s1R+9PyODtreS3nF3E2qq7Zbyrmv3DP/Z84O3vOLGcOs1F1ilyc5zazx3+ey/5QysK13hfwucO4zjx3uLY5+1l43uuL/dxZ0idt4sSJLoSVKFHC3nrrLVuyZIlNmDDBBg8e7G71/7ffftuuvfZa97i///3vWfvKka6zALeT/e//un3oP3Hu/gv23P8rrur2oj23mT39bR5XUQzVel+g5w7jOvPc4drm7GcXdnuHeZtnWUibPXu2a9KcOnWqNWjQINnH1K9f315//XVXaZs1a1ZmnwqZpDKt38m8M+fMNh86HbrnPms5bMvhsxfluWN9m4dxnXnucG1z9rMLu73DvM2zLKRpkECFChVcM2dq9HsNItDjcWGpHV1l2ki5cpiVK5Q7dM+d087ZzwrmDN16X4jnDuM689zh2ubsZxd2e4d5myeW6aPWqVOnLF++fOl6rB6nx+PCUkdHtaNr5xLdvlinyAXpABm053785yetxGXhW+8L8dxhXGeeO1zbnP3swm7vMG/zxHLExcWlfn2nFPziF7+wnTt32tdff21FihRJ9coE119/veubtmrVqsw8Fc7T5gNH7dNNu+2W8ldbuSsubMdHteGrRFzuIo3M0XNfe+lpO7N/l5UqVcry5s0bmvWOfO7jx4/bjh07snUbBG2dI2Xn+gd5vbNz/aNhvbN6/aNpnbNyG0Tzep/P+l/M9fYyXbtr1KiRGwygKTgmTZqU7Epro+j3J0+etKZNm57va0UmqYJUo8jZC1JJSkw79sXauf1zHz9+znbsvzjPfTFcrOcO4zrz3OHa5uxnF961If1sn3dI+9Of/mTTp093U2yo31mPHj3cFBxXXXWV/fDDD24KjldffdX27dvnJrf94x//mLWvHAAAIIZlOqSp+VIjOxXO9u7dayNGjEjyGI3qLFasmAtrejwAAADS57yGKtx666326aefumbPDz74wI3gPHLkiBUoUMCN6GzevLn99re/5WoDAAAA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nnL//s1vfpNl2wUAAMS2wLe9qXlQ013o8kytWrVKcFmoHTt2uCsOqBnRGzJkiJvcdty4cda1a9f4+3VJKF3rU2FPTY+qaqlp8p133nF/P3DgwATPqz5mo0ePduFOk9PqMlLq+6ZLSK1fv95d/ily+fLCCy/Ybbfd5u6/44473LU7NRpUj7/33nvdpaUAAABiopIm9evXt/nz57uQo6ClSzldddVV7jY91+0U9UnTpaE0lYam1xg/frx98skn1q1bN1f5SnzdTmndurW76oAqZPPmzXPP54OglqWhtJGuv/561wSrgQUKZ3/961/dYAFd4/O5557Loq0BAADCIEdcXNz/tf0hJmmAg6qOpUqViqpRLVkl7OsvYd8GrD/vf5j3f+EzcDwq94GoqKQBAACEDSENAAAggAhpAAAAAURIAwAACCBCGgAAQAAR0gAAAAKIkAYAABBAhDQAAIAAIqQBAAAEECENAAAggAhpAAAAAURIAwAACCBCGgAAQAAR0gAAAAKIkAYAABBAhDQAAIAAIqQBAAAEECENAAAggAhpAAAAAURIAwAACCBCGgAAQAAR0gAAAAKIkAYAABBAhDQAAIAAIqQBAAAEECENAAAggAhpAAAAAURIAwAACCBCGgAAQAAR0gAAAAKIkAYAABBAhDQAAIAAIqQBAAAEECENAAAggAhpAAAAAURIAwAACCBCGgAAQAAR0gAAAAKIkAYAABBAhDQAAIAAIqQBAAAEECEtJHLlymVhFvb1l7BvA9af9z/s+AzksmiTIy4u7tzFfhEAAABIiEoaAABAABHSAAAAAoiQBgAAEECENAAAgAAipAEAAAQQIQ0AACCACGkAAAABREgDAAAIIEIaEELnzjGHNQAEHSEthq1YscK++uor++GHHy72SwmMJUuW2C233GIff/yxhZHfFyJDWtgCW9j3gbSEbX+Qw4cP29ixY23t2rUWVmF836NhH8h9sV8Ast7cuXNt6NCh9t1339nJkyetdOnSds8999hvfvMby58/fyg3+a5du6x///42b948K1u2rPtAhsn3339vQ4YMsc8++8zOnj1r119/vf32t7+1+vXrW44cOSwMwr4PpGTmzJlu2xQpUsSaNGli11xzjdsntJ/kzBn75/Fjxoyxv/zlL1a8eHGrUqWKhdFrr71mn3/+uRUqVMhq1aplt956q+XLl8/CYkyA9wGu3Rlj/v73v9vAgQPtF7/4hfuwXXLJJe4DqC/hbt262RNPPOF2xDBRYB01apRdffXV1r17d2vdurXdcMMNFoYzYx1s33nnHevTp4/70tV6Hz161J0tKsCPGDHCbZNYF9Z9IDX//e9/7U9/+pNt3rzZhbETJ064oNajRw8bPHiwxbr333/fHnnkEdu+fbu1b9/e7rzzTmvQoEGowsmaNWvsD3/4g3377bd22WWXWVxcnLv/tttuc5+ZihUrWix7Pwr2AUJaDDlw4IC1atXK/fvll1+2qlWrun9//fXX9vTTT7uDtSpqOjCHwSeffGJdunRx20VVRH0AFV6D9AHMbqdPn3ZfPjoQjxw50ho3bmy5c+e2BQsWuC9nVZPGjx/v9ps8efJYrGEfSN4333zjwtixY8fsoYcessqVK7sD9OOPP24bN260xx57zHr27GlXXnllfNiPFVu3brW+ffvahx9+aHXq1LHOnTtb06ZNXQUxTPR+ax9Yv369O3nXttB3o8LZ7Nmz3f9VXbr55ptjrqq6NYr2AZo7Y4h2PB/IFND8B6tSpUr20ksv2apVq+yVV16xZs2auTOlWKdtcerUKatZs6arJJUrV87dr4rBpZde6sKbzh7z5s1rsUph7N///rcLZM2bN3f3ab/Qv4cPH+4qJgrtqjDVrl3bYo0OQGHfByL57wTtF9o2f/vb36xjx47xvy9YsKD7/tD3hZq+evXqFVMBTaZMmeIOzjph0Wcg1qtFKVm5cqXrn6kgppNY76mnnrJrr73WXnzxRXdi9+qrr8ZUQIu2fSC2tnzI7dy5093u37/f3foP1pkzZ1wzxpNPPun+r75JsX4gkl/96leuiqRwumHDBte8p8EU6n+gypFCiSpt+sD6Mn80d57VOqhs7ytokftAhQoV3K0Ciz/otmjRwu69914XZt988834/Saa+WrQu+++6/7fsmXLUO0DafH7g5o61RVCleXI/UVdJNRvT98XkydPtv/85z8xt03uu+8+1ydRJ7X6PMiWLVvsgw8+sBdeeMEF00mTJtm6desslqkPmqiKKn5bFCtWzFXWatSo4Vpf1F0m8ns1FtwXRfsAIS2G6MNWoEABN2Dg0KFD8ffnypXL3epgpZKuzqD9By9WbNq0yfbu3WtHjhyJPxCpqaZdu3burFDVoqlTp9oDDzzgzhTVzKMvIx2sVGFR3xyJ1qrBs88+6/pYTZgwwf1fTZq+YhT5haz7/TpqEImCijoJz5kzJxBfSOdD77HCqLbBtm3b3H3qfxmWfSAtClr+QKv1VvO2b+L23xGika86SKlJ9O2333Z9GGNhm/igedVVV8X3xVMf3oULF9qf//xn69q1qw0bNszeeOMN69evn2sCW7RoUfxBPFppFLOOCZ7fB4oWLepuVU0WhXb/e+0PzzzzTHzXGZ3ARWs17eOPP3bfcaoc+qJFNO0D0bnVkSx98appU81biasi2jFFO57oy/enn36K+i2pMz013akj+E033eT6nWm0WuQBRx+01atXu743Omh/9NFH9t5777kflbyvu+46tz2mTZvm/iaazhj1uhXOFEAUuLQd9Pr9OqhS5PugKbj4UXuemv86dOhgP/74oy1dujQqqyZ+G+ig4iuGvirmq0OxvA8kRycs//znP937riqi6L33B1o1ayp86feJ11fNwLfffrtVq1bN/V7T+EQb7c86uKpjvCrFEhk077rrLvvlL3/pKqjqp7t8+XLX0rBs2TK3P/zud79z4UUH7midqsUPFlOLgkKH5/eBK664woX0Tz/9NME+oN/rO0BVVjWF66T+rbfesmhc/y5durj1//LLL23x4sUJTkiiZR8gpMUQNVGor5nOmv71r38l+J12TH3wtFPqwK0ziGieP2337t3ubF8j9VQt0hdKo0aNXEfx+++/333ItL7qb6RmPTV7qcStZjCFOR2IChcu7A7Oav7VF7hG/0k0nDGqyqEvH3X8Vdl+3Lhx7gywbt267vXrR1+6OmPUF5WaQRVCItdP20f/VrNGqVKl4r+Io6VqovAQuQ3Ud0b9a0TNdP7sV52hY3EfSInCqjp7DxgwwH7961+7bfTwww+75hxP94mCnMKa/37wSpQoYXfccYcL9vquiKbgqs+Bgrn2e7UcaF3/+te/xp+46oRV4USj+rRO6pekMKfvE01No22n5j79XgFPTWDRdkKrz4GmktB66b33/VHFv89aV1WYX3/9dfd96r8zIh+jz4v2DQV97SfRtv5r1651Jxz6rKt1Sevn1y1a9oHo/SZCspT+9cFTk4/OHhJTnxwdlCK/sKPNnj17bNCgQe6Do8qIOj+r+VZfNr6/ne7zYaN69eruw6fwJvpQ6nf+w6pgq4EWGobuqw5BppChg5D6Vqn/hDr3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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "driving = bd.get_node(database=\"foreground\", name=\"driving an electric vehicle\")\n", + "ev_to_driving = next(exc for exc in driving.technosphere() if exc.input[\"name\"] == \"electricity\")\n", + "\n", + "print(ev_to_driving[\"temporal_distribution\"].date) # original resolution was timedelta64[M], which gets converted to seconds\n", + "print(ev_to_driving[\"temporal_distribution\"].amount)\n", + "print(ev_to_driving[\"temporal_distribution\"].date.dtype)\n", + "\n", + "ev_to_driving[\"temporal_distribution\"].graph(resolution=\"M\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "TemporalDistribution instance with 16 values and total: 1" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ev_to_driving[\"temporal_distribution\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Add a characterization method" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we need some characterization method. Again, this is just a simple made-up one:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "bd.Method((\"GWP\", \"example\")).write(\n", + " [\n", + " ((\"biosphere\", \"CO2\"), 1),\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## LCA using `bw_timex`\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that the data is set up, we can get startet with the actual time-explicit LCA. As usual, we need to select a method first:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "method = (\"GWP\", \"example\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "`bw_timex` needs to know the representative time of the databases. We record it once, as `representative_time` metadata on each database, using `set_database_metadata`:" + }, { - "name": "index", - "rawType": "int64", - "type": "integer" + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "from datetime import datetime\n\nfrom bw_timex import set_database_metadata\n\nset_database_metadata(\"background_2020\", representative_time=datetime(2020, 1, 1))\nset_database_metadata(\"background_2030\", representative_time=datetime(2030, 1, 1))\nset_database_metadata(\"background_2040\", representative_time=datetime(2040, 1, 1))\n# the \"foreground\" database, which holds our demand, is treated as \"dynamic\" automatically" }, { - "name": "date", - "rawType": "datetime64[us]", - "type": "datetime" + "cell_type": "markdown", + "metadata": {}, + "source": "Now, we can instantiate a `TimexLCA`. It's structure is similar to a normal `bw2calc.LCA` - `TimexLCA` reads the metadata we just set automatically, so no timing argument is needed.\n\nNot sure about the required inputs? Check the documentation using `?`. All our classes and methods have docstrings!" }, { - "name": "amount", - "rawType": "float64", - "type": "float" + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "from bw_timex import TimexLCA" + ] }, { - "name": "flow", - "rawType": "str", - "type": "string" + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's create a `TimexLCA` object for our EV life cycle:" + ] }, { - "name": "activity", - "rawType": "str", - "type": "string" + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "driving = bd.get_node(database=\"foreground\", code=\"driving\", name=\"driving an electric vehicle\", unit=\"transport over an ev lifetime\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# intialize the TimexLCA object with the functional unit and method\ntlca = TimexLCA({driving: 1}, method)\n# build the timeline with a temporal grouping of \"month\"\ntlca.build_timeline(temporal_grouping=\"month\")\n# calculate the time-explicit LCI\ntlca.lci()" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's check the dynamic invenrotry in a human readale format" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "index", + "rawType": "int64", + "type": "integer" + }, + { + "name": "date", + "rawType": "datetime64[us]", + "type": "datetime" + }, + { + "name": "amount", + "rawType": "float64", + "type": "float" + }, + { + "name": "flow", + "rawType": "str", + "type": "string" + }, + { + "name": "activity", + "rawType": "str", + "type": "string" + } + ], + "ref": "96fb6524-5d6b-4129-bbe4-189bbf70b3a2", + "rows": [ + [ + "0", + "2025-01-01 00:00:00", + "13671.0", + "carbon dioxide", + "glider" + ], + [ + "1", + "2026-01-01 00:00:00", + "6090.0", + "carbon dioxide", + "battery" + ], + [ + "2", + "2026-01-01 00:00:00", + "3480.0", + "carbon dioxide", + "powertrain" + ], + [ + "3", + "2026-01-01 00:00:00", + "1827.0", + "carbon dioxide", + "glider" + ], + [ + "5", + "2027-01-01 00:00:00", + "3402.0", + "carbon dioxide", + "glider" + ], + [ + "4", + "2027-01-01 00:00:00", + "632.8125", + "carbon dioxide", + "electricity" + ], + [ + "6", + "2028-01-01 00:00:00", + "585.9375", + "carbon dioxide", + "electricity" + ], + [ + "7", + "2029-01-01 00:00:00", + "539.0625", + "carbon dioxide", + "electricity" + ], + [ + "8", + "2030-01-01 00:00:00", + "492.1875", + "carbon dioxide", + "electricity" + ], + [ + "9", + "2031-01-01 00:00:00", + "452.3437502793968", + "carbon dioxide", + "electricity" + ], + [ + "10", + "2032-01-01 00:00:00", + "419.5312508381903", + "carbon dioxide", + "electricity" + ], + [ + "11", + "2033-01-01 00:00:00", + "386.71875139698386", + "carbon dioxide", + "electricity" + ], + [ + "12", + "2034-01-01 00:00:00", + "353.9062519557774", + "carbon dioxide", + "electricity" + ], + [ + "13", + "2035-01-01 00:00:00", + "321.09375251457095", + "carbon dioxide", + "electricity" + ], + [ + "14", + "2036-01-01 00:00:00", + "288.2812530733645", + "carbon dioxide", + "electricity" + ], + [ + "15", + "2037-01-01 00:00:00", + "255.46875363215804", + "carbon dioxide", + "electricity" + ], + [ + "16", + "2038-01-01 00:00:00", + "222.65625419095159", + "carbon dioxide", + "electricity" + ], + [ + "17", + "2039-01-01 00:00:00", + "189.84375474974513", + "carbon dioxide", + "electricity" + ], + [ + "18", + "2040-01-01 00:00:00", + "157.03125530853868", + "carbon dioxide", + "electricity" + ], + [ + "22", + "2041-01-01 00:00:00", + "140.62500558793545", + "carbon dioxide", + "electricity" + ], + [ + "19", + "2041-01-01 00:00:00", + "23.22505974918207", + "carbon dioxide", + "battery_eol" + ], + [ + "21", + "2041-01-01 00:00:00", + "1.393503553803692", + "carbon dioxide", + "glider_eol" + ], + [ + "20", + "2041-01-01 00:00:00", + "0.13271462417178018", + "carbon dioxide", + "powertrain_eol" + ], + [ + "26", + "2042-01-01 00:00:00", + "140.62500558793545", + "carbon dioxide", + "electricity" + ], + [ + "23", + "2042-01-01 00:00:00", + "23.54988050163585", + "carbon dioxide", + "battery_eol" + ], + [ + "25", + "2042-01-01 00:00:00", + "1.4129927985153001", + "carbon dioxide", + "glider_eol" + ], + [ + "24", + "2042-01-01 00:00:00", + "0.13457074271574287", + "carbon dioxide", + "powertrain_eol" + ], + [ + "27", + "2043-01-01 00:00:00", + "23.22505974918207", + "carbon dioxide", + "battery_eol" + ], + [ + "29", + "2043-01-01 00:00:00", + "1.393503553803692", + "carbon dioxide", + "glider_eol" + ], + [ + "28", + "2043-01-01 00:00:00", + "0.13271462417178018", + "carbon dioxide", + "powertrain_eol" + ] + ], + "shape": { + "columns": 4, + "rows": 30 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
dateamountflowactivity
02025-01-0113671.000000carbon dioxideglider
12026-01-016090.000000carbon dioxidebattery
22026-01-013480.000000carbon dioxidepowertrain
32026-01-011827.000000carbon dioxideglider
52027-01-013402.000000carbon dioxideglider
42027-01-01632.812500carbon dioxideelectricity
62028-01-01585.937500carbon dioxideelectricity
72029-01-01539.062500carbon dioxideelectricity
82030-01-01492.187500carbon dioxideelectricity
92031-01-01452.343750carbon dioxideelectricity
102032-01-01419.531251carbon dioxideelectricity
112033-01-01386.718751carbon dioxideelectricity
122034-01-01353.906252carbon dioxideelectricity
132035-01-01321.093753carbon dioxideelectricity
142036-01-01288.281253carbon dioxideelectricity
152037-01-01255.468754carbon dioxideelectricity
162038-01-01222.656254carbon dioxideelectricity
172039-01-01189.843755carbon dioxideelectricity
182040-01-01157.031255carbon dioxideelectricity
222041-01-01140.625006carbon dioxideelectricity
192041-01-0123.225060carbon dioxidebattery_eol
212041-01-011.393504carbon dioxideglider_eol
202041-01-010.132715carbon dioxidepowertrain_eol
262042-01-01140.625006carbon dioxideelectricity
232042-01-0123.549881carbon dioxidebattery_eol
252042-01-011.412993carbon dioxideglider_eol
242042-01-010.134571carbon dioxidepowertrain_eol
272043-01-0123.225060carbon dioxidebattery_eol
292043-01-011.393504carbon dioxideglider_eol
282043-01-010.132715carbon dioxidepowertrain_eol
\n", + "
" + ], + "text/plain": [ + " date amount flow activity\n", + "0 2025-01-01 13671.000000 carbon dioxide glider\n", + "1 2026-01-01 6090.000000 carbon dioxide battery\n", + "2 2026-01-01 3480.000000 carbon dioxide powertrain\n", + "3 2026-01-01 1827.000000 carbon dioxide glider\n", + "5 2027-01-01 3402.000000 carbon dioxide glider\n", + "4 2027-01-01 632.812500 carbon dioxide electricity\n", + "6 2028-01-01 585.937500 carbon dioxide electricity\n", + "7 2029-01-01 539.062500 carbon dioxide electricity\n", + "8 2030-01-01 492.187500 carbon dioxide electricity\n", + "9 2031-01-01 452.343750 carbon dioxide electricity\n", + "10 2032-01-01 419.531251 carbon dioxide electricity\n", + "11 2033-01-01 386.718751 carbon dioxide electricity\n", + "12 2034-01-01 353.906252 carbon dioxide electricity\n", + "13 2035-01-01 321.093753 carbon dioxide electricity\n", + "14 2036-01-01 288.281253 carbon dioxide electricity\n", + "15 2037-01-01 255.468754 carbon dioxide electricity\n", + "16 2038-01-01 222.656254 carbon dioxide electricity\n", + "17 2039-01-01 189.843755 carbon dioxide electricity\n", + "18 2040-01-01 157.031255 carbon dioxide electricity\n", + "22 2041-01-01 140.625006 carbon dioxide electricity\n", + "19 2041-01-01 23.225060 carbon dioxide battery_eol\n", + "21 2041-01-01 1.393504 carbon dioxide glider_eol\n", + "20 2041-01-01 0.132715 carbon dioxide powertrain_eol\n", + "26 2042-01-01 140.625006 carbon dioxide electricity\n", + "23 2042-01-01 23.549881 carbon dioxide battery_eol\n", + "25 2042-01-01 1.412993 carbon dioxide glider_eol\n", + "24 2042-01-01 0.134571 carbon dioxide powertrain_eol\n", + "27 2043-01-01 23.225060 carbon dioxide battery_eol\n", + "29 2043-01-01 1.393504 carbon dioxide glider_eol\n", + "28 2043-01-01 0.132715 carbon dioxide powertrain_eol" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tlca.create_labelled_dynamic_inventory_dataframe()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can do all further analysis as detailed in the other example notebook [electric vehicle tutorial](./2_electric_vehicle_from_scratch.ipynb). Below you just find the quick calculations. For the full dynamic characterization please see the linked notebook." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "34122.72503901273" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Static LCIA from time-explicit LCI\n", + "tlca.static_lcia()\n", + "tlca.static_score #kg CO2-eq" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "At this point, we can already compare these time-explicit results to the results of an \"ordinary\", completely static LCA. These already exist within the TimexLCA class, originally to set the priorities for the graph traversal:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "28089.199999794364" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# compare to fully static non time-explicit LCIA score\n", + "tlca.base_lca.score" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "timex_dev", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.0" } - ], - "ref": "96fb6524-5d6b-4129-bbe4-189bbf70b3a2", - "rows": [ - [ - "0", - "2025-01-01 00:00:00", - "13671.0", - "carbon dioxide", - "glider" - ], - [ - "1", - "2026-01-01 00:00:00", - "6090.0", - "carbon dioxide", - "battery" - ], - [ - "2", - "2026-01-01 00:00:00", - "3480.0", - "carbon dioxide", - "powertrain" - ], - [ - "3", - "2026-01-01 00:00:00", - "1827.0", - "carbon dioxide", - "glider" - ], - [ - "5", - "2027-01-01 00:00:00", - "3402.0", - "carbon dioxide", - "glider" - ], - [ - "4", - "2027-01-01 00:00:00", - "632.8125", - "carbon dioxide", - "electricity" - ], - [ - "6", - "2028-01-01 00:00:00", - "585.9375", - "carbon dioxide", - "electricity" - ], - [ - "7", - "2029-01-01 00:00:00", - "539.0625", - "carbon dioxide", - "electricity" - ], - [ - "8", - "2030-01-01 00:00:00", - "492.1875", - "carbon dioxide", - "electricity" - ], - [ - "9", - "2031-01-01 00:00:00", - "452.3437502793968", - "carbon dioxide", - "electricity" - ], - [ - "10", - "2032-01-01 00:00:00", - "419.5312508381903", - "carbon dioxide", - "electricity" - ], - [ - "11", - "2033-01-01 00:00:00", - "386.71875139698386", - "carbon dioxide", - "electricity" - ], - [ - "12", - "2034-01-01 00:00:00", - "353.9062519557774", - "carbon dioxide", - "electricity" - ], - [ - "13", - "2035-01-01 00:00:00", - "321.09375251457095", - "carbon dioxide", - "electricity" - ], - [ - "14", - "2036-01-01 00:00:00", - "288.2812530733645", - "carbon dioxide", - "electricity" - ], - [ - "15", - "2037-01-01 00:00:00", - "255.46875363215804", - "carbon dioxide", - "electricity" - ], - [ - "16", - "2038-01-01 00:00:00", - "222.65625419095159", - "carbon dioxide", - "electricity" - ], - [ - "17", - "2039-01-01 00:00:00", - "189.84375474974513", - "carbon dioxide", - "electricity" - ], - [ - "18", - "2040-01-01 00:00:00", - "157.03125530853868", - "carbon dioxide", - "electricity" - ], - [ - "22", - "2041-01-01 00:00:00", - "140.62500558793545", - "carbon dioxide", - "electricity" - ], - [ - "19", - "2041-01-01 00:00:00", - "23.22505974918207", - "carbon dioxide", - "battery_eol" - ], - [ - "21", - "2041-01-01 00:00:00", - "1.393503553803692", - "carbon dioxide", - "glider_eol" - ], - [ - "20", - "2041-01-01 00:00:00", - "0.13271462417178018", - "carbon dioxide", - "powertrain_eol" - ], - [ - "26", - "2042-01-01 00:00:00", - "140.62500558793545", - "carbon dioxide", - "electricity" - ], - [ - "23", - "2042-01-01 00:00:00", - "23.54988050163585", - "carbon dioxide", - "battery_eol" - ], - [ - "25", - "2042-01-01 00:00:00", - "1.4129927985153001", - "carbon dioxide", - "glider_eol" - ], - [ - "24", - "2042-01-01 00:00:00", - "0.13457074271574287", - "carbon dioxide", - "powertrain_eol" - ], - [ - "27", - "2043-01-01 00:00:00", - "23.22505974918207", - "carbon dioxide", - "battery_eol" - ], - [ - "29", - "2043-01-01 00:00:00", - "1.393503553803692", - "carbon dioxide", - "glider_eol" - ], - [ - "28", - "2043-01-01 00:00:00", - "0.13271462417178018", - "carbon dioxide", - "powertrain_eol" - ] - ], - "shape": { - "columns": 4, - "rows": 30 - } - }, - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
dateamountflowactivity
02025-01-0113671.000000carbon dioxideglider
12026-01-016090.000000carbon dioxidebattery
22026-01-013480.000000carbon dioxidepowertrain
32026-01-011827.000000carbon dioxideglider
52027-01-013402.000000carbon dioxideglider
42027-01-01632.812500carbon dioxideelectricity
62028-01-01585.937500carbon dioxideelectricity
72029-01-01539.062500carbon dioxideelectricity
82030-01-01492.187500carbon dioxideelectricity
92031-01-01452.343750carbon dioxideelectricity
102032-01-01419.531251carbon dioxideelectricity
112033-01-01386.718751carbon dioxideelectricity
122034-01-01353.906252carbon dioxideelectricity
132035-01-01321.093753carbon dioxideelectricity
142036-01-01288.281253carbon dioxideelectricity
152037-01-01255.468754carbon dioxideelectricity
162038-01-01222.656254carbon dioxideelectricity
172039-01-01189.843755carbon dioxideelectricity
182040-01-01157.031255carbon dioxideelectricity
222041-01-01140.625006carbon dioxideelectricity
192041-01-0123.225060carbon dioxidebattery_eol
212041-01-011.393504carbon dioxideglider_eol
202041-01-010.132715carbon dioxidepowertrain_eol
262042-01-01140.625006carbon dioxideelectricity
232042-01-0123.549881carbon dioxidebattery_eol
252042-01-011.412993carbon dioxideglider_eol
242042-01-010.134571carbon dioxidepowertrain_eol
272043-01-0123.225060carbon dioxidebattery_eol
292043-01-011.393504carbon dioxideglider_eol
282043-01-010.132715carbon dioxidepowertrain_eol
\n", - "
" - ], - "text/plain": [ - " date amount flow activity\n", - "0 2025-01-01 13671.000000 carbon dioxide glider\n", - "1 2026-01-01 6090.000000 carbon dioxide battery\n", - "2 2026-01-01 3480.000000 carbon dioxide powertrain\n", - "3 2026-01-01 1827.000000 carbon dioxide glider\n", - "5 2027-01-01 3402.000000 carbon dioxide glider\n", - "4 2027-01-01 632.812500 carbon dioxide electricity\n", - "6 2028-01-01 585.937500 carbon dioxide electricity\n", - "7 2029-01-01 539.062500 carbon dioxide electricity\n", - "8 2030-01-01 492.187500 carbon dioxide electricity\n", - "9 2031-01-01 452.343750 carbon dioxide electricity\n", - "10 2032-01-01 419.531251 carbon dioxide electricity\n", - "11 2033-01-01 386.718751 carbon dioxide electricity\n", - "12 2034-01-01 353.906252 carbon dioxide electricity\n", - "13 2035-01-01 321.093753 carbon dioxide electricity\n", - "14 2036-01-01 288.281253 carbon dioxide electricity\n", - "15 2037-01-01 255.468754 carbon dioxide electricity\n", - "16 2038-01-01 222.656254 carbon dioxide electricity\n", - "17 2039-01-01 189.843755 carbon dioxide electricity\n", - "18 2040-01-01 157.031255 carbon dioxide electricity\n", - "22 2041-01-01 140.625006 carbon dioxide electricity\n", - "19 2041-01-01 23.225060 carbon dioxide battery_eol\n", - "21 2041-01-01 1.393504 carbon dioxide glider_eol\n", - "20 2041-01-01 0.132715 carbon dioxide powertrain_eol\n", - "26 2042-01-01 140.625006 carbon dioxide electricity\n", - "23 2042-01-01 23.549881 carbon dioxide battery_eol\n", - "25 2042-01-01 1.412993 carbon dioxide glider_eol\n", - "24 2042-01-01 0.134571 carbon dioxide powertrain_eol\n", - "27 2043-01-01 23.225060 carbon dioxide battery_eol\n", - "29 2043-01-01 1.393504 carbon dioxide glider_eol\n", - "28 2043-01-01 0.132715 carbon dioxide powertrain_eol" - ] - }, - "execution_count": 39, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "tlca.create_labelled_dynamic_inventory_dataframe()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we can do all further analysis as detailed in the other example notebook [electric vehicle tutorial](./2_electric_vehicle_from_scratch.ipynb). Below you just find the quick calculations. For the full dynamic characterization please see the linked notebook." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "34122.72503901273" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Static LCIA from time-explicit LCI\n", - "tlca.static_lcia()\n", - "tlca.static_score #kg CO2-eq" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "At this point, we can already compare these time-explicit results to the results of an \"ordinary\", completely static LCA. These already exist within the TimexLCA class, originally to set the priorities for the graph traversal:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "28089.199999794364" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# compare to fully static non time-explicit LCIA score\n", - "tlca.base_lca.score" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "timex_dev", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.0" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} \ No newline at end of file + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 22e158005ea52e4582fdfbd801f037d87f16446f Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 12:48:15 +0200 Subject: [PATCH 13/24] docs: use database metadata instead of database_dates in the notebooks The eight remaining notebooks (two self-contained advanced examples plus six premise/ecoinvent notebooks) taught the old, mandatory database_dates mapping. They now rely on the representative_time metadata bw_timex reads automatically, matching the new default behaviour from Tasks 1-6. - background_temporal_distributions.ipynb and uncertainty_with_datapackages.ipynb were re-executed against their own self-contained databases. - The six premise/ecoinvent notebooks had only their source cells edited; stored outputs are unchanged except where they printed a now-obsolete database_dates dict. - The electric-vehicle notebooks now call set_database_metadata on their own "without EOL" background copies, using the same representative_time as the premise vintage they were copied from. - exercise_ev_vs_petrol_solutions.ipynb's project holds two IAM pathways, so its markdown now shows the scenario argument needed to pick one. --- .../background_temporal_distributions.ipynb | 400 ++++++----- ...round_temporal_distributions_premise.ipynb | 17 +- .../uncertainty_with_datapackages.ipynb | 628 +++++++++++------- notebooks/development/benchmarking.ipynb | 28 +- .../examples/electric_vehicle_premise.ipynb | 109 +-- .../electric_vehicle_premise_detailed.ipynb | 43 +- .../teaching/ev_walkthrough_premise.ipynb | 26 +- .../exercise_ev_vs_petrol_solutions.ipynb | 24 +- 8 files changed, 703 insertions(+), 572 deletions(-) diff --git a/notebooks/advanced/background_temporal_distributions.ipynb b/notebooks/advanced/background_temporal_distributions.ipynb index acd8fee4..1614071e 100644 --- a/notebooks/advanced/background_temporal_distributions.ipynb +++ b/notebooks/advanced/background_temporal_distributions.ipynb @@ -57,10 +57,10 @@ "id": "942222ef", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T08:08:47.650263Z", - "iopub.status.busy": "2026-06-22T08:08:47.650130Z", - "iopub.status.idle": "2026-06-22T08:08:48.990960Z", - "shell.execute_reply": "2026-06-22T08:08:48.990545Z" + "iopub.execute_input": "2026-08-21T10:31:55.868733Z", + "iopub.status.busy": "2026-08-21T10:31:55.868550Z", + "iopub.status.idle": "2026-08-21T10:31:57.383744Z", + "shell.execute_reply": "2026-08-21T10:31:57.383219Z" } }, "outputs": [ @@ -77,21 +77,21 @@ "output_type": "stream", "text": [ "\r", - "100%|██████████| 1/1 [00:00<00:00, 10754.63it/s]" + "100%|██████████| 1/1 [00:00<00:00, 10951.19it/s]" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[2m10:08:48+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n" + "\u001b[2m12:31:57+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[2m10:08:48+0200\u001b[0m [\u001b[33m\u001b[1mwarning \u001b[0m] \u001b[1mNot able to determine geocollections for all datasets. This database is not ready for regionalization.\u001b[0m\n" + "\u001b[2m12:31:57+0200\u001b[0m [\u001b[33m\u001b[1mwarning \u001b[0m] \u001b[1mNot able to determine geocollections for all datasets. This database is not ready for regionalization.\u001b[0m\n" ] }, { @@ -114,21 +114,21 @@ "output_type": "stream", "text": [ "\r", - "100%|██████████| 2/2 [00:00<00:00, 40920.04it/s]" + "100%|██████████| 2/2 [00:00<00:00, 34521.02it/s]" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[2m10:08:48+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n" + "\u001b[2m12:31:57+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[2m10:08:48+0200\u001b[0m [\u001b[33m\u001b[1mwarning \u001b[0m] \u001b[1mNot able to determine geocollections for all datasets. This database is not ready for regionalization.\u001b[0m\n" + "\u001b[2m12:31:57+0200\u001b[0m [\u001b[33m\u001b[1mwarning \u001b[0m] \u001b[1mNot able to determine geocollections for all datasets. This database is not ready for regionalization.\u001b[0m\n" ] }, { @@ -151,14 +151,14 @@ "output_type": "stream", "text": [ "\r", - "100%|██████████| 1/1 [00:00<00:00, 10866.07it/s]" + "100%|██████████| 1/1 [00:00<00:00, 30615.36it/s]" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[2m10:08:48+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n" + "\u001b[2m12:31:57+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n" ] }, { @@ -256,10 +256,10 @@ "id": "d2660de7", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T08:08:48.992384Z", - "iopub.status.busy": "2026-06-22T08:08:48.992289Z", - "iopub.status.idle": "2026-06-22T08:08:49.032820Z", - "shell.execute_reply": "2026-06-22T08:08:49.032374Z" + "iopub.execute_input": "2026-08-21T10:31:57.385045Z", + "iopub.status.busy": "2026-08-21T10:31:57.384951Z", + "iopub.status.idle": "2026-08-21T10:31:57.455766Z", + "shell.execute_reply": "2026-08-21T10:31:57.455229Z" } }, "outputs": [ @@ -267,7 +267,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001b[2m10:08:48+0200\u001b[0m [\u001b[33m\u001b[1mwarning \u001b[0m] \u001b[1mNot able to determine geocollections for all datasets. This database is not ready for regionalization.\u001b[0m\n" + "\u001b[2m12:31:57+0200\u001b[0m [\u001b[33m\u001b[1mwarning \u001b[0m] \u001b[1mNot able to determine geocollections for all datasets. This database is not ready for regionalization.\u001b[0m\n" ] }, { @@ -283,14 +283,14 @@ "output_type": "stream", "text": [ "\r", - "100%|██████████| 2/2 [00:00<00:00, 48489.06it/s]" + "100%|██████████| 2/2 [00:00<00:00, 60787.01it/s]" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[2m10:08:48+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n" + "\u001b[2m12:31:57+0200\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mVacuuming database \u001b[0m\n" ] }, { @@ -334,7 +334,7 @@ "id": "c4d74bee", "metadata": {}, "source": [ - "As in Getting Started, we record which background database represents which point in time:" + "As in [Getting Started](../tutorials/1_getting_started.ipynb), we record which point in time each background database represents, using `set_database_metadata` — `TimexLCA` will read it automatically:" ] }, { @@ -343,21 +343,39 @@ "id": "e21503fa", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T08:08:49.034110Z", - "iopub.status.busy": "2026-06-22T08:08:49.034022Z", - "iopub.status.idle": "2026-06-22T08:08:49.036177Z", - "shell.execute_reply": "2026-06-22T08:08:49.035812Z" + "iopub.execute_input": "2026-08-21T10:31:57.456900Z", + "iopub.status.busy": "2026-08-21T10:31:57.456834Z", + "iopub.status.idle": "2026-08-21T10:31:57.727620Z", + "shell.execute_reply": "2026-08-21T10:31:57.727191Z" } }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{'depends': ['biosphere'],\n", + " 'backend': 'sqlite',\n", + " 'number': 2,\n", + " 'modified': '2026-08-21T12:31:57.386123',\n", + " 'geocollections': [],\n", + " 'searchable': True,\n", + " 'processed': '2026-08-21T12:31:57.451530',\n", + " 'dirty': False,\n", + " 'representative_time': '2040-01-01T00:00:00'}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "from datetime import datetime\n", "\n", - "database_dates = {\n", - " \"background_2020\": datetime.strptime(\"2020\", \"%Y\"),\n", - " \"background_2040\": datetime.strptime(\"2040\", \"%Y\"),\n", - " \"foreground\": \"dynamic\",\n", - "}" + "from bw_timex import set_database_metadata\n", + "\n", + "set_database_metadata(\"background_2020\", representative_time=datetime(2020, 1, 1))\n", + "set_database_metadata(\"background_2040\", representative_time=datetime(2040, 1, 1))" ] }, { @@ -398,10 +416,10 @@ "id": "ab591abf", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T08:08:49.037461Z", - "iopub.status.busy": "2026-06-22T08:08:49.037363Z", - "iopub.status.idle": "2026-06-22T08:08:49.313368Z", - "shell.execute_reply": "2026-06-22T08:08:49.313021Z" + "iopub.execute_input": "2026-08-21T10:31:57.728861Z", + "iopub.status.busy": "2026-08-21T10:31:57.728781Z", + "iopub.status.idle": "2026-08-21T10:31:57.737286Z", + "shell.execute_reply": "2026-08-21T10:31:57.736915Z" } }, "outputs": [ @@ -409,14 +427,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.308\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m604\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: 2 None 'C' (None, somewhere, None) to 'B' (None, somewhere, None).\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.732\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m670\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: 2 None 'C' (None, somewhere, None) to 'B' (None, somewhere, None).\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.311\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m604\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: 2 None 'C' (None, somewhere, None) to 'B' (None, somewhere, None).\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.735\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m670\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: 2 None 'C' (None, somewhere, None) to 'B' (None, somewhere, None).\u001b[0m\n" ] } ], @@ -456,10 +474,10 @@ "id": "d44daabf", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T08:08:49.314829Z", - "iopub.status.busy": "2026-06-22T08:08:49.314709Z", - "iopub.status.idle": "2026-06-22T08:08:49.352568Z", - "shell.execute_reply": "2026-06-22T08:08:49.352155Z" + "iopub.execute_input": "2026-08-21T10:31:57.738406Z", + "iopub.status.busy": "2026-08-21T10:31:57.738335Z", + "iopub.status.idle": "2026-08-21T10:31:57.791869Z", + "shell.execute_reply": "2026-08-21T10:31:57.791482Z" } }, "outputs": [ @@ -467,49 +485,77 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.315\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m136\u001b[0m - \u001b[1mInitializing TimexLCA object...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.738\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m174\u001b[0m - \u001b[1mInitializing TimexLCA object...\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[32m2026-08-21 12:31:57.740\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m194\u001b[0m - \u001b[1mCalculating base LCA...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.316\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m153\u001b[0m - \u001b[1mCalculating base LCA...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.740\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mclean_databases\u001b[0m:\u001b[36m1355\u001b[0m - \u001b[1mReprocessing 2 modified database(s) before calculating: background_2020, background_2040. This can take a while for large databases.\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.331\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m170\u001b[0m - \u001b[1mCollecting node infos...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.747\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mclean_databases\u001b[0m:\u001b[36m1361\u001b[0m - \u001b[1mDone reprocessing modified databases.\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.332\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m336\u001b[0m - \u001b[1mNo edge filter function provided. Skipping all edges in background databases.\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.758\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m211\u001b[0m - \u001b[1mCollecting node infos...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.333\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m357\u001b[0m - \u001b[1mCreating activity time mapping...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.759\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m223\u001b[0m - \u001b[1mLoading node metadata from 3 database(s)...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.333\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m112\u001b[0m - \u001b[1mTraversing supply chain graph...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.760\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m260\u001b[0m - \u001b[1mTimexLCA initialized.\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.336\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m186\u001b[0m - \u001b[1mBuilding timeline...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.761\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m453\u001b[0m - \u001b[1mNo edge filter function provided. Skipping all edges in background databases.\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[32m2026-08-21 12:31:57.761\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m474\u001b[0m - \u001b[1mCreating activity time mapping...\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[32m2026-08-21 12:31:57.762\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m112\u001b[0m - \u001b[1mTraversing supply chain graph...\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[32m2026-08-21 12:31:57.767\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m183\u001b[0m - \u001b[1mBuilding timeline...\u001b[0m\n" ] }, { @@ -517,7 +563,7 @@ "output_type": "stream", "text": [ "Starting graph traversal\n", - "Calculation count: 1\n" + "Calculation count: 2\n" ] }, { @@ -552,6 +598,7 @@ " consumer\n", " consumer_name\n", " amount\n", + " cumulative_amount\n", " temporal_market_shares\n", " temporal_evolution\n", " temporal_evolution_reference\n", @@ -561,16 +608,17 @@ " \n", " 0\n", " 2024\n", - " 327359257374097410\n", + " 349138551637430274\n", " 2024-01-01\n", - " 327359257227296768\n", + " 349138551499018240\n", " B\n", " 2024\n", - " 327359257374097411\n", + " 349138551637430275\n", " 2024-01-01\n", - " 327359257286017024\n", + " 349138551557738496\n", " A\n", " 3.0\n", + " 3.0\n", " {'background_2020': 0.8, 'background_2040': 0.2}\n", " None\n", " producer\n", @@ -578,9 +626,9 @@ " \n", " 1\n", " 2024\n", - " 327359257374097411\n", + " 349138551637430275\n", " 2024-01-01\n", - " 327359257286017024\n", + " 349138551557738496\n", " A\n", " 2024\n", " -1\n", @@ -588,6 +636,7 @@ " -1\n", " -1\n", " 1.0\n", + " 1.0\n", " None\n", " None\n", " producer\n", @@ -598,16 +647,16 @@ ], "text/plain": [ " hash_producer time_mapped_producer date_producer producer \\\n", - "0 2024 327359257374097410 2024-01-01 327359257227296768 \n", - "1 2024 327359257374097411 2024-01-01 327359257286017024 \n", + "0 2024 349138551637430274 2024-01-01 349138551499018240 \n", + "1 2024 349138551637430275 2024-01-01 349138551557738496 \n", "\n", " producer_name hash_consumer time_mapped_consumer date_consumer \\\n", - "0 B 2024 327359257374097411 2024-01-01 \n", + "0 B 2024 349138551637430275 2024-01-01 \n", "1 A 2024 -1 2024-01-01 \n", "\n", - " consumer consumer_name amount \\\n", - "0 327359257286017024 A 3.0 \n", - "1 -1 -1 1.0 \n", + " consumer consumer_name amount cumulative_amount \\\n", + "0 349138551557738496 A 3.0 3.0 \n", + "1 -1 -1 1.0 1.0 \n", "\n", " temporal_market_shares temporal_evolution \\\n", "0 {'background_2020': 0.8, 'background_2040': 0.2} None \n", @@ -629,7 +678,6 @@ "tlca_static_bg = TimexLCA(\n", " demand={(\"foreground\", \"A\"): 1},\n", " method=(\"our\", \"method\"),\n", - " database_dates=database_dates,\n", ")\n", "tlca_static_bg.build_timeline(starting_datetime=\"2024-01-01\")\n", "tlca_static_bg.timeline" @@ -649,10 +697,10 @@ "id": "e4b25e4c", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T08:08:49.353869Z", - "iopub.status.busy": "2026-06-22T08:08:49.353795Z", - "iopub.status.idle": "2026-06-22T08:08:49.370227Z", - "shell.execute_reply": "2026-06-22T08:08:49.369872Z" + "iopub.execute_input": "2026-08-21T10:31:57.793072Z", + "iopub.status.busy": "2026-08-21T10:31:57.792984Z", + "iopub.status.idle": "2026-08-21T10:31:57.810484Z", + "shell.execute_reply": "2026-08-21T10:31:57.809829Z" } }, "outputs": [ @@ -660,14 +708,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.359\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m507\u001b[0m - \u001b[1mExpanding matrices...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.798\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m634\u001b[0m - \u001b[1mExpanding matrices...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.361\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m526\u001b[0m - \u001b[1mCalculating dynamic inventory...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.800\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m653\u001b[0m - \u001b[1mCalculating dynamic inventory...\u001b[0m\n" ] }, { @@ -703,10 +751,10 @@ "id": "936fb39b", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T08:08:49.371504Z", - "iopub.status.busy": "2026-06-22T08:08:49.371429Z", - "iopub.status.idle": "2026-06-22T08:08:49.395760Z", - "shell.execute_reply": "2026-06-22T08:08:49.395276Z" + "iopub.execute_input": "2026-08-21T10:31:57.811833Z", + "iopub.status.busy": "2026-08-21T10:31:57.811740Z", + "iopub.status.idle": "2026-08-21T10:31:57.838387Z", + "shell.execute_reply": "2026-08-21T10:31:57.838055Z" } }, "outputs": [ @@ -714,49 +762,63 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.371\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m136\u001b[0m - \u001b[1mInitializing TimexLCA object...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.812\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m174\u001b[0m - \u001b[1mInitializing TimexLCA object...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.372\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m153\u001b[0m - \u001b[1mCalculating base LCA...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.813\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m194\u001b[0m - \u001b[1mCalculating base LCA...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.378\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m170\u001b[0m - \u001b[1mCollecting node infos...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.818\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m211\u001b[0m - \u001b[1mCollecting node infos...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.379\u001b[0m | \u001b[33m\u001b[1mWARNING \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m303\u001b[0m - \u001b[33m\u001b[1mtraverse_background=True with graph_traversal='priority': non-referenced background variants are not placed on the priority heap; each variant subtree is walked in full via proxy reads when its parent edge is reached. The referenced-system heap exploration order is unchanged and explored amounts are exact (identical to graph_traversal='bfs' for these subtrees).\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.820\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m223\u001b[0m - \u001b[1mLoading node metadata from 3 database(s)...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.380\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m357\u001b[0m - \u001b[1mCreating activity time mapping...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.820\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m260\u001b[0m - \u001b[1mTimexLCA initialized.\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.380\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m112\u001b[0m - \u001b[1mTraversing supply chain graph...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.820\u001b[0m | \u001b[33m\u001b[1mWARNING \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m420\u001b[0m - \u001b[33m\u001b[1mtraverse_background=True with graph_traversal='priority': non-referenced background variants are not placed on the priority heap; each variant subtree is walked in full via proxy reads when its parent edge is reached. The referenced-system heap exploration order is unchanged and explored amounts are exact (identical to graph_traversal='bfs' for these subtrees).\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.384\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m186\u001b[0m - \u001b[1mBuilding timeline...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.820\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m474\u001b[0m - \u001b[1mCreating activity time mapping...\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[32m2026-08-21 12:31:57.821\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m112\u001b[0m - \u001b[1mTraversing supply chain graph...\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[32m2026-08-21 12:31:57.825\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m183\u001b[0m - \u001b[1mBuilding timeline...\u001b[0m\n" ] }, { @@ -860,7 +922,6 @@ "tlca_dynamic_bg = TimexLCA(\n", " demand={(\"foreground\", \"A\"): 1},\n", " method=(\"our\", \"method\"),\n", - " database_dates=database_dates,\n", ")\n", "tlca_dynamic_bg.build_timeline(\n", " starting_datetime=\"2024-01-01\",\n", @@ -887,10 +948,10 @@ "id": "bcd18cf7", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T08:08:49.396974Z", - "iopub.status.busy": "2026-06-22T08:08:49.396899Z", - "iopub.status.idle": "2026-06-22T08:08:49.414524Z", - "shell.execute_reply": "2026-06-22T08:08:49.414221Z" + "iopub.execute_input": "2026-08-21T10:31:57.839590Z", + "iopub.status.busy": "2026-08-21T10:31:57.839522Z", + "iopub.status.idle": "2026-08-21T10:31:57.858164Z", + "shell.execute_reply": "2026-08-21T10:31:57.857638Z" } }, "outputs": [ @@ -898,14 +959,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.401\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m507\u001b[0m - \u001b[1mExpanding matrices...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.844\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m634\u001b[0m - \u001b[1mExpanding matrices...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.405\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m526\u001b[0m - \u001b[1mCalculating dynamic inventory...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.848\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m653\u001b[0m - \u001b[1mCalculating dynamic inventory...\u001b[0m\n" ] }, { @@ -984,10 +1045,10 @@ "id": "b2d881bb", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T08:08:49.415999Z", - "iopub.status.busy": "2026-06-22T08:08:49.415925Z", - "iopub.status.idle": "2026-06-22T08:08:49.445670Z", - "shell.execute_reply": "2026-06-22T08:08:49.445208Z" + "iopub.execute_input": "2026-08-21T10:31:57.859348Z", + "iopub.status.busy": "2026-08-21T10:31:57.859284Z", + "iopub.status.idle": "2026-08-21T10:31:57.936962Z", + "shell.execute_reply": "2026-08-21T10:31:57.936627Z" } }, "outputs": [ @@ -995,56 +1056,84 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.420\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m604\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: 3 None 'B' (None, somewhere, None) to 'A' (None, somewhere, None).\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.905\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m670\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: 3 None 'B' (None, somewhere, None) to 'A' (None, somewhere, None).\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[32m2026-08-21 12:31:57.905\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m174\u001b[0m - \u001b[1mInitializing TimexLCA object...\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[32m2026-08-21 12:31:57.906\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m194\u001b[0m - \u001b[1mCalculating base LCA...\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[32m2026-08-21 12:31:57.907\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mclean_databases\u001b[0m:\u001b[36m1355\u001b[0m - \u001b[1mReprocessing 1 modified database(s) before calculating: foreground. This can take a while for large databases.\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.420\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m136\u001b[0m - \u001b[1mInitializing TimexLCA object...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.911\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mclean_databases\u001b[0m:\u001b[36m1361\u001b[0m - \u001b[1mDone reprocessing modified databases.\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.420\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m153\u001b[0m - \u001b[1mCalculating base LCA...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.916\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m211\u001b[0m - \u001b[1mCollecting node infos...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.429\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m170\u001b[0m - \u001b[1mCollecting node infos...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.918\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m223\u001b[0m - \u001b[1mLoading node metadata from 3 database(s)...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.430\u001b[0m | \u001b[33m\u001b[1mWARNING \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m303\u001b[0m - \u001b[33m\u001b[1mtraverse_background=True with graph_traversal='priority': non-referenced background variants are not placed on the priority heap; each variant subtree is walked in full via proxy reads when its parent edge is reached. The referenced-system heap exploration order is unchanged and explored amounts are exact (identical to graph_traversal='bfs' for these subtrees).\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.918\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m260\u001b[0m - \u001b[1mTimexLCA initialized.\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.431\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m357\u001b[0m - \u001b[1mCreating activity time mapping...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.918\u001b[0m | \u001b[33m\u001b[1mWARNING \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m420\u001b[0m - \u001b[33m\u001b[1mtraverse_background=True with graph_traversal='priority': non-referenced background variants are not placed on the priority heap; each variant subtree is walked in full via proxy reads when its parent edge is reached. The referenced-system heap exploration order is unchanged and explored amounts are exact (identical to graph_traversal='bfs' for these subtrees).\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.431\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m112\u001b[0m - \u001b[1mTraversing supply chain graph...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.918\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m474\u001b[0m - \u001b[1mCreating activity time mapping...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.435\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m186\u001b[0m - \u001b[1mBuilding timeline...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.919\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m112\u001b[0m - \u001b[1mTraversing supply chain graph...\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[32m2026-08-21 12:31:57.923\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m183\u001b[0m - \u001b[1mBuilding timeline...\u001b[0m\n" ] }, { @@ -1087,6 +1176,7 @@ " consumer\n", " consumer_name\n", " amount\n", + " cumulative_amount\n", " temporal_market_shares\n", " temporal_evolution\n", " temporal_evolution_reference\n", @@ -1096,16 +1186,17 @@ " \n", " 0\n", " 2024\n", - " 327359257374097410\n", + " 349138551637430274\n", " 2024-01-01\n", - " 327359257227296768\n", + " 349138551499018240\n", " B\n", " 2024\n", - " 327359257374097412\n", + " 349138551637430276\n", " 2024-01-01\n", - " 327359257286017024\n", + " 349138551557738496\n", " A\n", " 2.1\n", + " 2.1\n", " None\n", " None\n", " producer\n", @@ -1113,16 +1204,17 @@ " \n", " 1\n", " 2024\n", - " 327359257374097411\n", + " 349138551637430275\n", " 2024-01-01\n", - " 327359257227296769\n", + " 349138551499018241\n", " C\n", " 2024\n", - " 327359257374097410\n", + " 349138551637430274\n", " 2024-01-01\n", - " 327359257227296768\n", + " 349138551499018240\n", " B\n", " 1.2\n", + " 2.52\n", " {'background_2020': 0.8, 'background_2040': 0.2}\n", " None\n", " producer\n", @@ -1130,9 +1222,9 @@ " \n", " 2\n", " 2024\n", - " 327359257374097412\n", + " 349138551637430276\n", " 2024-01-01\n", - " 327359257286017024\n", + " 349138551557738496\n", " A\n", " 2024\n", " -1\n", @@ -1140,6 +1232,7 @@ " -1\n", " -1\n", " 1.0\n", + " 1.0\n", " None\n", " None\n", " producer\n", @@ -1147,16 +1240,17 @@ " \n", " 3\n", " 2030\n", - " 327359257374097413\n", + " 349138551637430277\n", " 2030-01-01\n", - " 327359257227296768\n", + " 349138551499018240\n", " B\n", " 2024\n", - " 327359257374097412\n", + " 349138551637430276\n", " 2024-01-01\n", - " 327359257286017024\n", + " 349138551557738496\n", " A\n", " 0.9\n", + " 0.9\n", " None\n", " None\n", " producer\n", @@ -1164,16 +1258,17 @@ " \n", " 4\n", " 2030\n", - " 327359257374097414\n", + " 349138551637430278\n", " 2030-01-01\n", - " 327359257227296769\n", + " 349138551499018241\n", " C\n", " 2030\n", - " 327359257374097413\n", + " 349138551637430277\n", " 2030-01-01\n", - " 327359257227296768\n", + " 349138551499018240\n", " B\n", " 1.2\n", + " 1.08\n", " {'background_2020': 0.5, 'background_2040': 0.5}\n", " None\n", " producer\n", @@ -1181,16 +1276,17 @@ " \n", " 5\n", " 2034\n", - " 327359257374097415\n", + " 349138551637430279\n", " 2034-01-01\n", - " 327359257227296769\n", + " 349138551499018241\n", " C\n", " 2024\n", - " 327359257374097410\n", + " 349138551637430274\n", " 2024-01-01\n", - " 327359257227296768\n", + " 349138551499018240\n", " B\n", " 0.8\n", + " 1.68\n", " {'background_2020': 0.3, 'background_2040': 0.7}\n", " None\n", " producer\n", @@ -1198,16 +1294,17 @@ " \n", " 6\n", " 2040\n", - " 327359257374097416\n", + " 349138551637430280\n", " 2040-01-01\n", - " 327359257227296769\n", + " 349138551499018241\n", " C\n", " 2030\n", - " 327359257374097413\n", + " 349138551637430277\n", " 2030-01-01\n", - " 327359257227296768\n", + " 349138551499018240\n", " B\n", " 0.8\n", + " 0.72\n", " {'background_2040': 1}\n", " None\n", " producer\n", @@ -1218,31 +1315,31 @@ ], "text/plain": [ " hash_producer time_mapped_producer date_producer producer \\\n", - "0 2024 327359257374097410 2024-01-01 327359257227296768 \n", - "1 2024 327359257374097411 2024-01-01 327359257227296769 \n", - "2 2024 327359257374097412 2024-01-01 327359257286017024 \n", - "3 2030 327359257374097413 2030-01-01 327359257227296768 \n", - "4 2030 327359257374097414 2030-01-01 327359257227296769 \n", - "5 2034 327359257374097415 2034-01-01 327359257227296769 \n", - "6 2040 327359257374097416 2040-01-01 327359257227296769 \n", + "0 2024 349138551637430274 2024-01-01 349138551499018240 \n", + "1 2024 349138551637430275 2024-01-01 349138551499018241 \n", + "2 2024 349138551637430276 2024-01-01 349138551557738496 \n", + "3 2030 349138551637430277 2030-01-01 349138551499018240 \n", + "4 2030 349138551637430278 2030-01-01 349138551499018241 \n", + "5 2034 349138551637430279 2034-01-01 349138551499018241 \n", + "6 2040 349138551637430280 2040-01-01 349138551499018241 \n", "\n", " producer_name hash_consumer time_mapped_consumer date_consumer \\\n", - "0 B 2024 327359257374097412 2024-01-01 \n", - "1 C 2024 327359257374097410 2024-01-01 \n", + "0 B 2024 349138551637430276 2024-01-01 \n", + "1 C 2024 349138551637430274 2024-01-01 \n", "2 A 2024 -1 2024-01-01 \n", - "3 B 2024 327359257374097412 2024-01-01 \n", - "4 C 2030 327359257374097413 2030-01-01 \n", - "5 C 2024 327359257374097410 2024-01-01 \n", - "6 C 2030 327359257374097413 2030-01-01 \n", + "3 B 2024 349138551637430276 2024-01-01 \n", + "4 C 2030 349138551637430277 2030-01-01 \n", + "5 C 2024 349138551637430274 2024-01-01 \n", + "6 C 2030 349138551637430277 2030-01-01 \n", "\n", - " consumer consumer_name amount \\\n", - "0 327359257286017024 A 2.1 \n", - "1 327359257227296768 B 1.2 \n", - "2 -1 -1 1.0 \n", - "3 327359257286017024 A 0.9 \n", - "4 327359257227296768 B 1.2 \n", - "5 327359257227296768 B 0.8 \n", - "6 327359257227296768 B 0.8 \n", + " consumer consumer_name amount cumulative_amount \\\n", + "0 349138551557738496 A 2.1 2.1 \n", + "1 349138551499018240 B 1.2 2.52 \n", + "2 -1 -1 1.0 1.0 \n", + "3 349138551557738496 A 0.9 0.9 \n", + "4 349138551499018240 B 1.2 1.08 \n", + "5 349138551499018240 B 0.8 1.68 \n", + "6 349138551499018240 B 0.8 0.72 \n", "\n", " temporal_market_shares temporal_evolution \\\n", "0 None None \n", @@ -1283,7 +1380,6 @@ "tlca_both = TimexLCA(\n", " demand={(\"foreground\", \"A\"): 1},\n", " method=(\"our\", \"method\"),\n", - " database_dates=database_dates,\n", ")\n", "tlca_both.build_timeline(starting_datetime=\"2024-01-01\", traverse_background=True)\n", "tlca_both.timeline" @@ -1303,10 +1399,10 @@ "id": "f62d8e8f", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T08:08:49.446963Z", - "iopub.status.busy": "2026-06-22T08:08:49.446878Z", - "iopub.status.idle": "2026-06-22T08:08:49.466326Z", - "shell.execute_reply": "2026-06-22T08:08:49.465882Z" + "iopub.execute_input": "2026-08-21T10:31:57.938366Z", + "iopub.status.busy": "2026-08-21T10:31:57.938273Z", + "iopub.status.idle": "2026-08-21T10:31:57.959100Z", + "shell.execute_reply": "2026-08-21T10:31:57.958588Z" } }, "outputs": [ @@ -1314,14 +1410,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.452\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m507\u001b[0m - \u001b[1mExpanding matrices...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.943\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m634\u001b[0m - \u001b[1mExpanding matrices...\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-06-22 10:08:49.456\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m526\u001b[0m - \u001b[1mCalculating dynamic inventory...\u001b[0m\n" + "\u001b[32m2026-08-21 12:31:57.948\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m653\u001b[0m - \u001b[1mCalculating dynamic inventory...\u001b[0m\n" ] }, { @@ -1373,7 +1469,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.9" + "version": "3.12.12" } }, "nbformat": 4, diff --git a/notebooks/advanced/background_temporal_distributions_premise.ipynb b/notebooks/advanced/background_temporal_distributions_premise.ipynb index 1d81b93b..44745a63 100644 --- a/notebooks/advanced/background_temporal_distributions_premise.ipynb +++ b/notebooks/advanced/background_temporal_distributions_premise.ipynb @@ -344,21 +344,12 @@ }, "outputs": [], "source": [ - "from datetime import datetime\n", - "\n", "method = (\n", " \"ecoinvent-3.12\",\n", " \"EF v3.1\",\n", " \"climate change\",\n", " \"global warming potential (GWP100)\",\n", - ")\n", - "\n", - "database_dates = {\n", - " \"ei312_REMIND-EU_SSP2_NDC_2020\": datetime.strptime(\"2020\", \"%Y\"),\n", - " \"ei312_REMIND-EU_SSP2_NDC_2030\": datetime.strptime(\"2030\", \"%Y\"),\n", - " \"ei312_REMIND-EU_SSP2_NDC_2040\": datetime.strptime(\"2040\", \"%Y\"),\n", - " \"foreground\": \"dynamic\",\n", - "}" + ")" ] }, { @@ -403,8 +394,10 @@ "\n", "# This single line runs a full static ecoinvent LCA of the demand under the hood\n", "# (the \"base LCA\"), which is the slowest step of the whole notebook (~30 s on a\n", - "# cold cache). We build exactly ONE TimexLCA and reuse it below.\n", - "tlca = TimexLCA({A: 1}, method, database_dates)" + "# cold cache). We build exactly ONE TimexLCA and reuse it below. premise wrote each\n", + "# background database's representative_time as its own metadata, so no database_dates\n", + "# mapping is needed here.\n", + "tlca = TimexLCA({A: 1}, method)" ] }, { diff --git a/notebooks/advanced/uncertainty_with_datapackages.ipynb b/notebooks/advanced/uncertainty_with_datapackages.ipynb index 18c9f60e..d0495e6d 100644 --- a/notebooks/advanced/uncertainty_with_datapackages.ipynb +++ b/notebooks/advanced/uncertainty_with_datapackages.ipynb @@ -28,10 +28,10 @@ "id": "4a51bde3", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T09:58:47.666306Z", - "iopub.status.busy": "2026-06-22T09:58:47.666230Z", - "iopub.status.idle": "2026-06-22T09:58:49.178820Z", - "shell.execute_reply": "2026-06-22T09:58:49.178284Z" + "iopub.execute_input": "2026-08-21T10:33:10.087083Z", + "iopub.status.busy": "2026-08-21T10:33:10.086889Z", + "iopub.status.idle": "2026-08-21T10:33:11.519297Z", + "shell.execute_reply": "2026-08-21T10:33:11.518861Z" } }, "outputs": [], @@ -47,10 +47,10 @@ "id": "2c185b3e", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T09:58:49.180409Z", - "iopub.status.busy": "2026-06-22T09:58:49.180313Z", - "iopub.status.idle": "2026-06-22T09:58:49.202938Z", - "shell.execute_reply": "2026-06-22T09:58:49.202471Z" + "iopub.execute_input": "2026-08-21T10:33:11.521035Z", + "iopub.status.busy": "2026-08-21T10:33:11.520936Z", + "iopub.status.idle": "2026-08-21T10:33:11.538879Z", + "shell.execute_reply": "2026-08-21T10:33:11.538520Z" } }, "outputs": [], @@ -65,10 +65,10 @@ "id": "9ad772c8", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T09:58:49.204427Z", - "iopub.status.busy": "2026-06-22T09:58:49.204347Z", - "iopub.status.idle": "2026-06-22T09:58:49.270249Z", - "shell.execute_reply": "2026-06-22T09:58:49.269606Z" + "iopub.execute_input": "2026-08-21T10:33:11.540024Z", + "iopub.status.busy": "2026-08-21T10:33:11.539954Z", + "iopub.status.idle": "2026-08-21T10:33:11.630086Z", + "shell.execute_reply": "2026-08-21T10:33:11.629704Z" } }, "outputs": [ @@ -76,14 +76,23 @@ "name": "stderr", "output_type": "stream", "text": [ - "100%|██████████| 1/1 [00:00<00:00, 5370.43it/s]" + "\r", + " 0%| | 0/1 [00:00,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ,\n", - " ]" + "[,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ]" ] }, "execution_count": 24, @@ -773,73 +857,73 @@ "id": "1757427b", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T09:58:50.086145Z", - "iopub.status.busy": "2026-06-22T09:58:50.086040Z", - "iopub.status.idle": "2026-06-22T09:58:50.089261Z", - "shell.execute_reply": "2026-06-22T09:58:50.088659Z" + "iopub.execute_input": "2026-08-21T10:33:12.280693Z", + "iopub.status.busy": "2026-08-21T10:33:12.280626Z", + "iopub.status.idle": "2026-08-21T10:33:12.283286Z", + "shell.execute_reply": "2026-08-21T10:33:12.282900Z" } }, "outputs": [ { "data": { "text/plain": [ - "{(('background_2020', 'glider'), 202001): 327395989872181248,\n", - " (('background_2030', 'glider'), 203001): 327395989914124288,\n", - " (('background_2040', 'glider'), 204001): 327395989947678720,\n", - " (('background_2020', 'powertrain'), 202001): 327395989981233152,\n", - " (('background_2030', 'powertrain'), 203001): 327395990014787584,\n", - " (('background_2040', 'powertrain'), 204001): 327395990044147712,\n", - " (('background_2020', 'battery'), 202001): 327395990077702144,\n", - " (('background_2030', 'battery'), 203001): 327395990111256576,\n", - " (('background_2040', 'battery'), 204001): 327395990144811008,\n", - " (('background_2020', 'electricity'), 202001): 327395990178365440,\n", - " (('background_2030', 'electricity'), 203001): 327395990207725568,\n", - " (('background_2040', 'electricity'), 204001): 327395990241280000,\n", - " (('background_2020', 'glider_eol'), 202001): 327395990270640128,\n", - " (('background_2030', 'glider_eol'), 203001): 327395990304194560,\n", - " (('background_2040', 'glider_eol'), 204001): 327395990337748992,\n", - " (('background_2020', 'powertrain_eol'), 202001): 327395990367109120,\n", - " (('background_2030', 'powertrain_eol'), 203001): 327395990404857856,\n", - " (('background_2040', 'powertrain_eol'), 204001): 327395990438412288,\n", - " (('background_2020', 'battery_eol'), 202001): 327395990467772416,\n", - " (('background_2030', 'battery_eol'), 203001): 327395990501326848,\n", - " (('background_2040', 'battery_eol'), 204001): 327395990534881280,\n", - " (('foreground', 'ev_production'), 'dynamic'): 327395990769762304,\n", - " (('foreground', 'driving'), 'dynamic'): 327395990794928128,\n", - " (('foreground', 'used_ev'), 'dynamic'): 327395990836871168,\n", - " (('temporalized', 'glider'), 202404): 327395990836871169,\n", - " (('temporalized', 'glider'), 202405): 327395990836871170,\n", - " (('temporalized', 'glider'), 202504): 327395990836871171,\n", - " (('temporalized', 'powertrain'), 202504): 327395990836871172,\n", - " (('temporalized', 'battery'), 202504): 327395990836871173,\n", - " (('temporalized', 'glider'), 202505): 327395990836871174,\n", - " (('temporalized', 'powertrain'), 202505): 327395990836871175,\n", - " (('temporalized', 'battery'), 202505): 327395990836871176,\n", - " (('temporalized', 'glider'), 202604): 327395990836871177,\n", - " (('temporalized', 'ev_production'), 202604): 327395990836871178,\n", - " (('temporalized', 'glider'), 202605): 327395990836871179,\n", - " (('temporalized', 'ev_production'), 202605): 327395990836871180,\n", - " (('temporalized', 'electricity'), 202607): 327395990836871181,\n", - " (('temporalized', 'driving'), 202607): 327395990836871182,\n", - " (('temporalized', 'electricity'), 202707): 327395990836871183,\n", - " (('temporalized', 'electricity'), 202807): 327395990836871184,\n", - " (('temporalized', 'electricity'), 202907): 327395990836871185,\n", - " (('temporalized', 'electricity'), 203007): 327395990836871186,\n", - " (('temporalized', 'electricity'), 203107): 327395990836871187,\n", - " (('temporalized', 'electricity'), 203207): 327395990836871188,\n", - " (('temporalized', 'electricity'), 203307): 327395990836871189,\n", - " (('temporalized', 'electricity'), 203407): 327395990836871190,\n", - " (('temporalized', 'electricity'), 203507): 327395990836871191,\n", - " (('temporalized', 'electricity'), 203607): 327395990836871192,\n", - " (('temporalized', 'electricity'), 203707): 327395990836871193,\n", - " (('temporalized', 'electricity'), 203807): 327395990836871194,\n", - " (('temporalized', 'electricity'), 203907): 327395990836871195,\n", - " (('temporalized', 'electricity'), 204007): 327395990836871196,\n", - " (('temporalized', 'electricity'), 204107): 327395990836871197,\n", - " (('temporalized', 'used_ev'), 204207): 327395990836871198,\n", - " (('temporalized', 'glider_eol'), 204210): 327395990836871199,\n", - " (('temporalized', 'powertrain_eol'), 204210): 327395990836871200,\n", - " (('temporalized', 'battery_eol'), 204210): 327395990836871201}" + "{(('background_2020', 'glider'), 202001): 349138863036841984,\n", + " (('background_2020', 'powertrain'), 202001): 349138863129116672,\n", + " (('background_2020', 'battery'), 202001): 349138863213002752,\n", + " (('background_2020', 'electricity'), 202001): 349138863296888832,\n", + " (('background_2020', 'glider_eol'), 202001): 349138863380774912,\n", + " (('background_2020', 'powertrain_eol'), 202001): 349138863468855296,\n", + " (('background_2020', 'battery_eol'), 202001): 349138863561129984,\n", + " (('background_2030', 'glider'), 203001): 349138863070396416,\n", + " (('background_2030', 'powertrain'), 203001): 349138863158476800,\n", + " (('background_2030', 'battery'), 203001): 349138863242362880,\n", + " (('background_2030', 'electricity'), 203001): 349138863326248960,\n", + " (('background_2030', 'glider_eol'), 203001): 349138863405940736,\n", + " (('background_2030', 'powertrain_eol'), 203001): 349138863498215424,\n", + " (('background_2030', 'battery_eol'), 203001): 349138863594684416,\n", + " (('background_2040', 'glider'), 204001): 349138863099756544,\n", + " (('background_2040', 'powertrain'), 204001): 349138863183642624,\n", + " (('background_2040', 'battery'), 204001): 349138863267528704,\n", + " (('background_2040', 'electricity'), 204001): 349138863351414784,\n", + " (('background_2040', 'glider_eol'), 204001): 349138863439495168,\n", + " (('background_2040', 'powertrain_eol'), 204001): 349138863527575552,\n", + " (('background_2040', 'battery_eol'), 204001): 349138863624044544,\n", + " (('foreground', 'ev_production'), 'dynamic'): 349138863716319232,\n", + " (('foreground', 'driving'), 'dynamic'): 349138863728902144,\n", + " (('foreground', 'used_ev'), 'dynamic'): 349138863741485056,\n", + " (('temporalized', 'glider'), 202406): 349138863741485057,\n", + " (('temporalized', 'glider'), 202407): 349138863741485058,\n", + " (('temporalized', 'glider'), 202506): 349138863741485059,\n", + " (('temporalized', 'powertrain'), 202506): 349138863741485060,\n", + " (('temporalized', 'battery'), 202506): 349138863741485061,\n", + " (('temporalized', 'glider'), 202507): 349138863741485062,\n", + " (('temporalized', 'powertrain'), 202507): 349138863741485063,\n", + " (('temporalized', 'battery'), 202507): 349138863741485064,\n", + " (('temporalized', 'glider'), 202606): 349138863741485065,\n", + " (('temporalized', 'ev_production'), 202606): 349138863741485066,\n", + " (('temporalized', 'glider'), 202607): 349138863741485067,\n", + " (('temporalized', 'ev_production'), 202607): 349138863741485068,\n", + " (('temporalized', 'electricity'), 202609): 349138863741485069,\n", + " (('temporalized', 'driving'), 202609): 349138863741485070,\n", + " (('temporalized', 'electricity'), 202709): 349138863741485071,\n", + " (('temporalized', 'electricity'), 202809): 349138863741485072,\n", + " (('temporalized', 'electricity'), 202909): 349138863741485073,\n", + " (('temporalized', 'electricity'), 203009): 349138863741485074,\n", + " (('temporalized', 'electricity'), 203109): 349138863741485075,\n", + " (('temporalized', 'electricity'), 203209): 349138863741485076,\n", + " (('temporalized', 'electricity'), 203309): 349138863741485077,\n", + " (('temporalized', 'electricity'), 203409): 349138863741485078,\n", + " (('temporalized', 'electricity'), 203509): 349138863741485079,\n", + " (('temporalized', 'electricity'), 203609): 349138863741485080,\n", + " (('temporalized', 'electricity'), 203709): 349138863741485081,\n", + " (('temporalized', 'electricity'), 203809): 349138863741485082,\n", + " (('temporalized', 'electricity'), 203909): 349138863741485083,\n", + " (('temporalized', 'electricity'), 204009): 349138863741485084,\n", + " (('temporalized', 'electricity'), 204109): 349138863741485085,\n", + " (('temporalized', 'used_ev'), 204209): 349138863741485086,\n", + " (('temporalized', 'glider_eol'), 204212): 349138863741485087,\n", + " (('temporalized', 'powertrain_eol'), 204212): 349138863741485088,\n", + " (('temporalized', 'battery_eol'), 204212): 349138863741485089}" ] }, "execution_count": 25, @@ -865,17 +949,17 @@ "id": "334e70ad", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T09:58:50.090600Z", - "iopub.status.busy": "2026-06-22T09:58:50.090508Z", - "iopub.status.idle": "2026-06-22T09:58:50.092733Z", - "shell.execute_reply": "2026-06-22T09:58:50.092275Z" + "iopub.execute_input": "2026-08-21T10:33:12.284258Z", + "iopub.status.busy": "2026-08-21T10:33:12.284196Z", + "iopub.status.idle": "2026-08-21T10:33:12.286016Z", + "shell.execute_reply": "2026-08-21T10:33:12.285706Z" } }, "outputs": [ { "data": { "text/plain": [ - "327395990836871182" + "349138863741485070" ] }, "execution_count": 26, @@ -884,7 +968,11 @@ } ], "source": [ - "id_driving = tlca.activity_time_mapping[(('temporalized', 'driving'), 202607)]\n", + "id_driving = next(\n", + " matrix_id\n", + " for ((database, name), time), matrix_id in tlca.activity_time_mapping.items()\n", + " if database == \"temporalized\" and name == \"driving\"\n", + ")\n", "id_driving" ] }, @@ -894,17 +982,17 @@ "id": "65284e77", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T09:58:50.094081Z", - "iopub.status.busy": "2026-06-22T09:58:50.093998Z", - "iopub.status.idle": "2026-06-22T09:58:50.096271Z", - "shell.execute_reply": "2026-06-22T09:58:50.095818Z" + "iopub.execute_input": "2026-08-21T10:33:12.287070Z", + "iopub.status.busy": "2026-08-21T10:33:12.287003Z", + "iopub.status.idle": "2026-08-21T10:33:12.289051Z", + "shell.execute_reply": "2026-08-21T10:33:12.288726Z" } }, "outputs": [ { "data": { "text/plain": [ - "327395990836871186" + "349138863741485074" ] }, "execution_count": 27, @@ -913,7 +1001,11 @@ } ], "source": [ - "id_electricity = tlca.activity_time_mapping[(('temporalized', 'electricity'), 203007)] # the electricity \"temporal market\" seen by driving in 2030\n", + "id_electricity = next(\n", + " matrix_id\n", + " for ((database, name), time), matrix_id in tlca.activity_time_mapping.items()\n", + " if database == \"temporalized\" and name == \"electricity\" and time // 100 == 2030\n", + ") # the electricity \"temporal market\" seen by driving in 2030\n", "id_electricity" ] }, @@ -936,10 +1028,10 @@ "id": "4410b8c8", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T09:58:50.098074Z", - "iopub.status.busy": "2026-06-22T09:58:50.097995Z", - "iopub.status.idle": "2026-06-22T09:58:50.099800Z", - "shell.execute_reply": "2026-06-22T09:58:50.099286Z" + "iopub.execute_input": "2026-08-21T10:33:12.290144Z", + "iopub.status.busy": "2026-08-21T10:33:12.290078Z", + "iopub.status.idle": "2026-08-21T10:33:12.291792Z", + "shell.execute_reply": "2026-08-21T10:33:12.291457Z" } }, "outputs": [], @@ -953,10 +1045,10 @@ "id": "0c0ad4ea", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T09:58:50.101050Z", - "iopub.status.busy": "2026-06-22T09:58:50.100967Z", - "iopub.status.idle": "2026-06-22T09:58:50.103058Z", - "shell.execute_reply": "2026-06-22T09:58:50.102624Z" + "iopub.execute_input": "2026-08-21T10:33:12.292676Z", + "iopub.status.busy": "2026-08-21T10:33:12.292615Z", + "iopub.status.idle": "2026-08-21T10:33:12.294117Z", + "shell.execute_reply": "2026-08-21T10:33:12.293806Z" } }, "outputs": [], @@ -970,10 +1062,10 @@ "id": "afcdeb2e", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T09:58:50.104683Z", - "iopub.status.busy": "2026-06-22T09:58:50.104602Z", - "iopub.status.idle": "2026-06-22T09:58:50.106663Z", - "shell.execute_reply": "2026-06-22T09:58:50.106008Z" + "iopub.execute_input": "2026-08-21T10:33:12.295240Z", + "iopub.status.busy": "2026-08-21T10:33:12.295162Z", + "iopub.status.idle": "2026-08-21T10:33:12.296645Z", + "shell.execute_reply": "2026-08-21T10:33:12.296368Z" } }, "outputs": [], @@ -995,10 +1087,10 @@ "id": "d686892a", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T09:58:50.107861Z", - "iopub.status.busy": "2026-06-22T09:58:50.107773Z", - "iopub.status.idle": "2026-06-22T09:58:50.109584Z", - "shell.execute_reply": "2026-06-22T09:58:50.109100Z" + "iopub.execute_input": "2026-08-21T10:33:12.297543Z", + "iopub.status.busy": "2026-08-21T10:33:12.297483Z", + "iopub.status.idle": "2026-08-21T10:33:12.298957Z", + "shell.execute_reply": "2026-08-21T10:33:12.298621Z" } }, "outputs": [], @@ -1020,10 +1112,10 @@ "id": "d7a6820d", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T09:58:50.110912Z", - "iopub.status.busy": "2026-06-22T09:58:50.110842Z", - "iopub.status.idle": "2026-06-22T09:58:50.112459Z", - "shell.execute_reply": "2026-06-22T09:58:50.112125Z" + "iopub.execute_input": "2026-08-21T10:33:12.299920Z", + "iopub.status.busy": "2026-08-21T10:33:12.299858Z", + "iopub.status.idle": "2026-08-21T10:33:12.301259Z", + "shell.execute_reply": "2026-08-21T10:33:12.301001Z" } }, "outputs": [], @@ -1042,10 +1134,10 @@ "id": "a4b37318", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T09:58:50.113611Z", - "iopub.status.busy": "2026-06-22T09:58:50.113542Z", - "iopub.status.idle": "2026-06-22T09:58:50.115030Z", - "shell.execute_reply": "2026-06-22T09:58:50.114605Z" + "iopub.execute_input": "2026-08-21T10:33:12.302155Z", + "iopub.status.busy": "2026-08-21T10:33:12.302091Z", + "iopub.status.idle": "2026-08-21T10:33:12.303488Z", + "shell.execute_reply": "2026-08-21T10:33:12.303189Z" } }, "outputs": [], @@ -1067,10 +1159,10 @@ "id": "5bcf08f3", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T09:58:50.116390Z", - "iopub.status.busy": "2026-06-22T09:58:50.116278Z", - "iopub.status.idle": "2026-06-22T09:58:50.118346Z", - "shell.execute_reply": "2026-06-22T09:58:50.117908Z" + "iopub.execute_input": "2026-08-21T10:33:12.304545Z", + "iopub.status.busy": "2026-08-21T10:33:12.304488Z", + "iopub.status.idle": "2026-08-21T10:33:12.306002Z", + "shell.execute_reply": "2026-08-21T10:33:12.305660Z" } }, "outputs": [], @@ -1099,10 +1191,10 @@ "id": "3b427d9a", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T09:58:50.119761Z", - "iopub.status.busy": "2026-06-22T09:58:50.119643Z", - "iopub.status.idle": "2026-06-22T09:58:50.121422Z", - "shell.execute_reply": "2026-06-22T09:58:50.121042Z" + "iopub.execute_input": "2026-08-21T10:33:12.306865Z", + "iopub.status.busy": "2026-08-21T10:33:12.306797Z", + "iopub.status.idle": "2026-08-21T10:33:12.308301Z", + "shell.execute_reply": "2026-08-21T10:33:12.307994Z" } }, "outputs": [], @@ -1116,10 +1208,10 @@ "id": "309d820a", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T09:58:50.123000Z", - "iopub.status.busy": "2026-06-22T09:58:50.122869Z", - "iopub.status.idle": "2026-06-22T09:58:50.154074Z", - "shell.execute_reply": "2026-06-22T09:58:50.153468Z" + "iopub.execute_input": "2026-08-21T10:33:12.309269Z", + "iopub.status.busy": "2026-08-21T10:33:12.309204Z", + "iopub.status.idle": "2026-08-21T10:33:12.337079Z", + "shell.execute_reply": "2026-08-21T10:33:12.336691Z" } }, "outputs": [], @@ -1134,10 +1226,10 @@ "id": "da2ed768", "metadata": { "execution": { - "iopub.execute_input": "2026-06-22T09:58:50.156072Z", - "iopub.status.busy": "2026-06-22T09:58:50.155925Z", - "iopub.status.idle": "2026-06-22T09:58:50.196688Z", - "shell.execute_reply": "2026-06-22T09:58:50.196121Z" + "iopub.execute_input": "2026-08-21T10:33:12.338028Z", + "iopub.status.busy": "2026-08-21T10:33:12.337960Z", + "iopub.status.idle": "2026-08-21T10:33:12.377360Z", + "shell.execute_reply": "2026-08-21T10:33:12.377075Z" } }, "outputs": [ @@ -1145,10 +1237,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "baseline 15261.765039012731\n", - "reduction 15050.671288882346\n", - "heavy reduction 14930.046288807838\n", - "zero 14809.421288733332\n" + "baseline 15066.797664957096\n", + "reduction 14858.307039782376\n", + "heavy reduction 14739.169539682538\n", + "zero 14620.0320395827\n" ] } ], @@ -1175,7 +1267,14 @@ "cell_type": "code", "execution_count": 38, "id": "97ce71ed", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-21T10:33:12.378504Z", + "iopub.status.busy": "2026-08-21T10:33:12.378444Z", + "iopub.status.idle": "2026-08-21T10:33:12.380384Z", + "shell.execute_reply": "2026-08-21T10:33:12.380101Z" + } + }, "outputs": [], "source": [ "import stats_arrays as sa\n", @@ -1211,15 +1310,22 @@ "cell_type": "code", "execution_count": 39, "id": "d47e6edb", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-21T10:33:12.381581Z", + "iopub.status.busy": "2026-08-21T10:33:12.381507Z", + "iopub.status.idle": "2026-08-21T10:33:14.713573Z", + "shell.execute_reply": "2026-08-21T10:33:14.713135Z" + } + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "mean 15260.4\n", - "std 58.8\n", - "5-95% 15169.4 - 15359.2\n" + "mean 15073.7\n", + "std 57.9\n", + "5-95% 14973.3 - 15162.1\n" ] } ], @@ -1267,7 +1373,14 @@ "cell_type": "code", "execution_count": 40, "id": "8c04ae6b", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-21T10:33:14.715048Z", + "iopub.status.busy": "2026-08-21T10:33:14.714953Z", + "iopub.status.idle": "2026-08-21T10:33:14.718604Z", + "shell.execute_reply": "2026-08-21T10:33:14.718196Z" + } + }, "outputs": [ { "name": "stdout", @@ -1352,15 +1465,22 @@ "cell_type": "code", "execution_count": 41, "id": "faa49834", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-21T10:33:14.719792Z", + "iopub.status.busy": "2026-08-21T10:33:14.719723Z", + "iopub.status.idle": "2026-08-21T10:33:17.137677Z", + "shell.execute_reply": "2026-08-21T10:33:17.137237Z" + } + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "mean 15260.4\n", - "std 324.8\n", - "5-95% 14718.3 - 15737.5\n" + "mean 15085.6\n", + "std 305.1\n", + "5-95% 14578.6 - 15574.3\n" ] } ], @@ -1406,7 +1526,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.9" + "version": "3.12.12" } }, "nbformat": 4, diff --git a/notebooks/development/benchmarking.ipynb b/notebooks/development/benchmarking.ipynb index 56750df9..4c82aab6 100644 --- a/notebooks/development/benchmarking.ipynb +++ b/notebooks/development/benchmarking.ipynb @@ -337,15 +337,23 @@ "metadata": {}, "outputs": [], "source": [ - "def _database_dates(db_name):\n", - " database_dates = {\n", - " \"ei_cutoff_3.11_image_SSP2-M_2020 2025-05-30\": datetime.strptime(\"2020\", \"%Y\"),\n", - " \"ei_cutoff_3.11_image_SSP2-M_2030 2025-05-30\": datetime.strptime(\"2030\", \"%Y\"),\n", - " \"ei_cutoff_3.11_image_SSP2-M_2040 2025-05-30\": datetime.strptime(\"2040\", \"%Y\"),\n", - " \"ei_cutoff_3.11_image_SSP2-M_2050 2025-05-30\": datetime.strptime(\"2050\", \"%Y\"),\n", - " db_name: \"dynamic\",\n", - " }\n", - " return database_dates" + "from bw_timex import set_database_metadata\n", + "\n", + "# The background databases represent fixed points in time, so we only need to record\n", + "# this once. Each test database created below holds the functional unit, so bw_timex\n", + "# treats it as \"dynamic\" automatically - no per-database mapping needed in the loop.\n", + "set_database_metadata(\n", + " \"ei_cutoff_3.11_image_SSP2-M_2020 2025-05-30\", representative_time=datetime.strptime(\"2020\", \"%Y\")\n", + ")\n", + "set_database_metadata(\n", + " \"ei_cutoff_3.11_image_SSP2-M_2030 2025-05-30\", representative_time=datetime.strptime(\"2030\", \"%Y\")\n", + ")\n", + "set_database_metadata(\n", + " \"ei_cutoff_3.11_image_SSP2-M_2040 2025-05-30\", representative_time=datetime.strptime(\"2040\", \"%Y\")\n", + ")\n", + "set_database_metadata(\n", + " \"ei_cutoff_3.11_image_SSP2-M_2050 2025-05-30\", representative_time=datetime.strptime(\"2050\", \"%Y\")\n", + ")" ] }, { @@ -1088,7 +1096,6 @@ " nr_tiers.append(n_tier)\n", " # Create a unique database name based on the number of tiers and processes\n", " db_name = f\"test_{n_tier}tiers_{n_process}processes\"\n", - " database_dates = _database_dates(db_name)\n", " test_db_names.append(db_name)\n", " # print(f\"Processing database {db_name} with {n_tier} tiers and {n_process} processes per tier...\")\n", " # Check if the database already exists\n", @@ -1117,7 +1124,6 @@ " t0 = time.time()\n", " tlca = TimexLCA(demand={FU_node: 1},\n", " method=method,\n", - " database_dates=database_dates,\n", " )\n", " t1 = time.time()\n", " time_initialize.append(t1 - t0)\n", diff --git a/notebooks/examples/electric_vehicle_premise.ipynb b/notebooks/examples/electric_vehicle_premise.ipynb index 81b6d6c0..b7aed1fa 100644 --- a/notebooks/examples/electric_vehicle_premise.ipynb +++ b/notebooks/examples/electric_vehicle_premise.ipynb @@ -67,7 +67,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "cell-4", "metadata": { "docs_summary": "Show the code that builds the ev system", @@ -88,6 +88,10 @@ "source": [ "# Standard brightway modelling of the ev system - nothing time-explicit yet.\n", "\n", + "from datetime import datetime\n", + "\n", + "from bw_timex import set_database_metadata\n", + "\n", "ELECTRICITY_CONSUMPTION = 0.2 # kWh/km\n", "MILEAGE = 150_000 # km\n", "LIFETIME = 15 # years\n", @@ -159,6 +163,11 @@ "\n", " modified_db.process()\n", "\n", + " # These copies represent the same point in time as the premise database they were\n", + " # copied from, so we record that here too - otherwise bw_timex would not know when\n", + " # they occur and drop them from the temporal mapping.\n", + " set_database_metadata(modified_name, representative_time=datetime.strptime(year, \"%Y\"))\n", + "\n", "# Background processes our foreground links to\n", "ev_background_2020 = modified_dbs[db_2020.name]\n", "glider_production = ev_background_2020.get(code=\"glider_production_without_eol\")\n", @@ -362,15 +371,15 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 07:10:04.975\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m670\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: 840 kilogram 'glider production, passenger car, without EOL' (kilogram, GLO, None) to 'production of an electric vehicle' (unit, GLO, None).\u001b[0m\n", - "\u001b[32m2026-08-04 07:10:04.980\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m670\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: 80 kilogram 'powertrain production, for electric passenger car, without EOL' (kilogram, GLO, None) to 'production of an electric vehicle' (unit, GLO, None).\u001b[0m\n", - "\u001b[32m2026-08-04 07:10:04.985\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m670\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: 280 kilogram 'battery production, Li-ion, LiMn2O4, rechargeable, without EOL' (kilogram, GLO, None) to 'production of an electric vehicle' (unit, GLO, None).\u001b[0m\n", - "\u001b[32m2026-08-04 07:10:04.989\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m670\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: 1 unit 'production of an electric vehicle' (unit, GLO, None) to 'driving an electric vehicle' (transport over an ev lifetime, GLO, None).\u001b[0m\n", - "\u001b[32m2026-08-04 07:10:05.120\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m670\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: 30000.0 kilowatt hour 'market group for electricity, low voltage' (kilowatt hour, DEU, None) to 'driving an electric vehicle' (transport over an ev lifetime, GLO, None).\u001b[0m\n", - "\u001b[32m2026-08-04 07:10:05.125\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m670\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: -1 unit 'used electric vehicle' (unit, GLO, None) to 'driving an electric vehicle' (transport over an ev lifetime, GLO, None).\u001b[0m\n", - "\u001b[32m2026-08-04 07:10:05.252\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m670\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: -840 kilogram 'treatment of used glider, passenger car, shredding' (kilogram, GLO, None) to 'used electric vehicle' (unit, GLO, None).\u001b[0m\n", - "\u001b[32m2026-08-04 07:10:05.378\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m670\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: -80 kilogram 'treatment of used powertrain for electric passenger car, manual dismantling' (kilogram, GLO, None) to 'used electric vehicle' (unit, GLO, None).\u001b[0m\n", - "\u001b[32m2026-08-04 07:10:05.541\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m670\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: -280 kilogram 'market for used Li-ion battery' (kilogram, GLO, None) to 'used electric vehicle' (unit, GLO, None).\u001b[0m\n" + "\u001b[32m2026-08-14 14:45:12.822\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m670\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: 840 kilogram 'glider production, passenger car, without EOL' (kilogram, GLO, None) to 'production of an electric vehicle' (unit, GLO, None).\u001b[0m\n", + "\u001b[32m2026-08-14 14:45:12.826\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m670\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: 80 kilogram 'powertrain production, for electric passenger car, without EOL' (kilogram, GLO, None) to 'production of an electric vehicle' (unit, GLO, None).\u001b[0m\n", + "\u001b[32m2026-08-14 14:45:12.831\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m670\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: 280 kilogram 'battery production, Li-ion, LiMn2O4, rechargeable, without EOL' (kilogram, GLO, None) to 'production of an electric vehicle' (unit, GLO, None).\u001b[0m\n", + "\u001b[32m2026-08-14 14:45:12.836\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m670\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: 1 unit 'production of an electric vehicle' (unit, GLO, None) to 'driving an electric vehicle' (transport over an ev lifetime, GLO, None).\u001b[0m\n", + "\u001b[32m2026-08-14 14:45:12.962\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m670\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: 30000.0 kilowatt hour 'market group for electricity, low voltage' (kilowatt hour, DEU, None) to 'driving an electric vehicle' (transport over an ev lifetime, GLO, None).\u001b[0m\n", + "\u001b[32m2026-08-14 14:45:12.967\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m670\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: -1 unit 'used electric vehicle' (unit, GLO, None) to 'driving an electric vehicle' (transport over an ev lifetime, GLO, None).\u001b[0m\n", + "\u001b[32m2026-08-14 14:45:13.093\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m670\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: -840 kilogram 'treatment of used glider, passenger car, shredding' (kilogram, GLO, None) to 'used electric vehicle' (unit, GLO, None).\u001b[0m\n", + "\u001b[32m2026-08-14 14:45:13.221\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m670\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: -80 kilogram 'treatment of used powertrain for electric passenger car, manual dismantling' (kilogram, GLO, None) to 'used electric vehicle' (unit, GLO, None).\u001b[0m\n", + "\u001b[32m2026-08-14 14:45:13.344\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.utils\u001b[0m:\u001b[36madd_temporal_distribution_to_exchange\u001b[0m:\u001b[36m670\u001b[0m - \u001b[1mAdded temporal distribution to exchange Exchange: -280 kilogram 'market for used Li-ion battery' (kilogram, GLO, None) to 'used electric vehicle' (unit, GLO, None).\u001b[0m\n" ] } ], @@ -450,12 +459,12 @@ "source": [ "## Time-explicit LCA\n", "\n", - "Besides the functional unit and the impact assessment method, `bw_timex` needs to know which point in time each background database represents. Databases that carry temporal distributions - here our foreground - are flagged as `\"dynamic\"`." + "Besides the functional unit and the impact assessment method, `bw_timex` needs to know which point in time each background database represents. premise writes this as `representative_time` metadata on the databases it creates, so `bw_timex` finds it automatically; we set the same metadata on our own `ev_background_` copies above, since they represent the same points in time. Databases that carry temporal distributions - here our foreground - are flagged as `\"dynamic\"` automatically." ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "cell-12", "metadata": { "execution": { @@ -467,21 +476,9 @@ }, "outputs": [], "source": [ - "from datetime import datetime\n", - "\n", "functional_unit = {driving: 1} # transport over 1 ev lifetime\n", "\n", - "method = (\"ecoinvent-3.12\", \"EF v3.1\", \"climate change\", \"global warming potential (GWP100)\")\n", - "\n", - "database_dates = {\n", - " db_2020.name: datetime.strptime(\"2020\", \"%Y\"),\n", - " db_2030.name: datetime.strptime(\"2030\", \"%Y\"),\n", - " db_2040.name: datetime.strptime(\"2040\", \"%Y\"),\n", - " \"ev_background_2020\": datetime.strptime(\"2020\", \"%Y\"),\n", - " \"ev_background_2030\": datetime.strptime(\"2030\", \"%Y\"),\n", - " \"ev_background_2040\": datetime.strptime(\"2040\", \"%Y\"),\n", - " \"foreground\": \"dynamic\",\n", - "}" + "method = (\"ecoinvent-3.12\", \"EF v3.1\", \"climate change\", \"global warming potential (GWP100)\")" ] }, { @@ -489,12 +486,12 @@ "id": "cell-13", "metadata": {}, "source": [ - "With that, we can set up a `TimexLCA`. It works like a normal `bw2calc.LCA`, with `database_dates` as the extra argument:" + "With that, we can set up a `TimexLCA`. It works like a normal `bw2calc.LCA`:" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "cell-14", "metadata": { "execution": { @@ -504,27 +501,11 @@ "shell.execute_reply": "2026-08-03T19:56:48.510756Z" } }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\u001b[32m2026-08-04 07:10:08.209\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m142\u001b[0m - \u001b[1mInitializing TimexLCA object...\u001b[0m\n", - "\u001b[32m2026-08-04 07:10:08.210\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m163\u001b[0m - \u001b[1mCalculating base LCA...\u001b[0m\n", - "\u001b[32m2026-08-04 07:10:08.210\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mclean_databases\u001b[0m:\u001b[36m1214\u001b[0m - \u001b[1mReprocessing 1 modified database(s) before calculating: foreground. This can take a while for large databases.\u001b[0m\n", - "\u001b[32m2026-08-04 07:10:08.219\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mclean_databases\u001b[0m:\u001b[36m1220\u001b[0m - \u001b[1mDone reprocessing modified databases.\u001b[0m\n", - "/Users/timodiepers/Documents/Coding/bw_timex/.venv/lib/python3.12/site-packages/scikits/umfpack/umfpack.py:737: UmfpackWarning: (almost) singular matrix! (estimated cond. number: 3.90e+13)\n", - " warnings.warn(msg, UmfpackWarning)\n", - "\u001b[32m2026-08-04 07:10:08.947\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m180\u001b[0m - \u001b[1mCollecting node infos...\u001b[0m\n", - "\u001b[32m2026-08-04 07:10:08.985\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m192\u001b[0m - \u001b[1mLoading node metadata from 7 database(s)...\u001b[0m\n", - "\u001b[32m2026-08-04 07:10:09.793\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m229\u001b[0m - \u001b[1mTimexLCA initialized.\u001b[0m\n" - ] - } - ], + "outputs": [], "source": [ "from bw_timex import TimexLCA\n", "\n", - "tlca = TimexLCA(functional_unit, method, database_dates)" + "tlca = TimexLCA(functional_unit, method)" ] }, { @@ -552,11 +533,11 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 07:10:23.270\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m358\u001b[0m - \u001b[1mNo edge filter function provided. Skipping all edges in background databases.\u001b[0m\n", - "\u001b[32m2026-08-04 07:10:29.312\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m379\u001b[0m - \u001b[1mCreating activity time mapping...\u001b[0m\n", - "\u001b[32m2026-08-04 07:10:29.413\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m112\u001b[0m - \u001b[1mTraversing supply chain graph...\u001b[0m\n", - "\u001b[32m2026-08-04 07:10:29.437\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m186\u001b[0m - \u001b[1mBuilding timeline...\u001b[0m\n", - "\u001b[32m2026-08-04 07:10:29.536\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36mget_weights_for_interpolation_between_nearest_years\u001b[0m:\u001b[36m705\u001b[0m - \u001b[1mReference date 2040-08-01 00:00:00 is higher than all provided dates. Data will be taken from the closest lower year.\u001b[0m\n" + "\u001b[32m2026-08-14 14:45:15.012\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m362\u001b[0m - \u001b[1mNo edge filter function provided. Skipping all edges in background databases.\u001b[0m\n", + "\u001b[32m2026-08-14 14:45:18.897\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m383\u001b[0m - \u001b[1mCreating activity time mapping...\u001b[0m\n", + "\u001b[32m2026-08-14 14:45:18.994\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m112\u001b[0m - \u001b[1mTraversing supply chain graph...\u001b[0m\n", + "\u001b[32m2026-08-14 14:45:19.016\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36mbuild_timeline\u001b[0m:\u001b[36m183\u001b[0m - \u001b[1mBuilding timeline...\u001b[0m\n", + "\u001b[32m2026-08-14 14:45:19.087\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timeline_builder\u001b[0m:\u001b[36mget_weights_for_interpolation_between_nearest_years\u001b[0m:\u001b[36m754\u001b[0m - \u001b[1mReference date 2040-08-01 00:00:00 is higher than all provided dates. Data will be taken from the closest lower year.\u001b[0m\n" ] }, { @@ -1034,8 +1015,8 @@ "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2026-08-04 07:10:32.439\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m529\u001b[0m - \u001b[1mExpanding matrices...\u001b[0m\n", - "\u001b[32m2026-08-04 07:10:32.457\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m548\u001b[0m - \u001b[1mCalculating dynamic inventory...\u001b[0m\n", + "\u001b[32m2026-08-14 14:45:19.548\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m543\u001b[0m - \u001b[1mExpanding matrices...\u001b[0m\n", + "\u001b[32m2026-08-14 14:45:19.565\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mbw_timex.timex_lca\u001b[0m:\u001b[36mlci\u001b[0m:\u001b[36m562\u001b[0m - \u001b[1mCalculating dynamic inventory...\u001b[0m\n", "/Users/timodiepers/Documents/Coding/bw_timex/.venv/lib/python3.12/site-packages/scikits/umfpack/umfpack.py:737: UmfpackWarning: (almost) singular matrix! (estimated cond. number: 4.97e+12)\n", " warnings.warn(msg, UmfpackWarning)\n", "/Users/timodiepers/Documents/Coding/bw_timex/.venv/lib/python3.12/site-packages/scikits/umfpack/umfpack.py:737: UmfpackWarning: (almost) singular matrix! (estimated cond. number: 4.97e+12)\n", @@ -1132,7 +1113,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "id": "cell-24", "metadata": { "execution": { @@ -1142,25 +1123,7 @@ "shell.execute_reply": "2026-08-03T19:57:06.669247Z" } }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\u001b[32m2026-08-04 07:10:51.241\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mdynamic_characterization.dynamic_characterization\u001b[0m:\u001b[36mcharacterize\u001b[0m:\u001b[36m126\u001b[0m - \u001b[1mNo custom dynamic characterization functions provided. Using default dynamic characterization functions. The flows that are characterized are based on the selection of the initially chosen impact category.\u001b[0m\n" - ] - }, - { - "data": { - "text/plain": [ - "np.float64(10655.763775235002)" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "tlca.dynamic_lcia(metric=\"GWP\")\n", "tlca.dynamic_score # kg CO2-eq (GWP100)" @@ -1217,7 +1180,7 @@ ], "metadata": { "kernelspec": { - "display_name": ".venv (3.12.12)", + "display_name": ".venv (3.12.12.final.0)", "language": "python", "name": "python3" }, diff --git a/notebooks/examples/electric_vehicle_premise_detailed.ipynb b/notebooks/examples/electric_vehicle_premise_detailed.ipynb index 5eb41ba7..3eb935fa 100644 --- a/notebooks/examples/electric_vehicle_premise_detailed.ipynb +++ b/notebooks/examples/electric_vehicle_premise_detailed.ipynb @@ -216,6 +216,10 @@ "# location) with the copies this notebook creates below in ev_background_ - same\n", "# date, different database. bw_timex would then refuse to resolve the resulting\n", "# ambiguity. This cleanup is safe: these nodes were created by this notebook, not premise.\n", + "from datetime import datetime\n", + "\n", + "from bw_timex import set_database_metadata\n", + "\n", "for db in [db_2020, db_2030, db_2040]:\n", " for code in [\"glider_production_without_eol\", \"powertrain_production_without_eol\", \"battery_production_without_eol\"]:\n", " try:\n", @@ -263,7 +267,12 @@ " # For the battery, some waste treatment is buried in the process \"battery cell production, Li-ion, \n", " # LiMn2O4\" - but not for the whole mass of the battery(?). For simplicity, we just leave it in there.\n", "\n", - " modified_db.process()" + " modified_db.process()\n", + "\n", + " # These copies represent the same point in time as the premise database they were\n", + " # copied from, so we record that here too - otherwise bw_timex would not know when\n", + " # they occur and drop them from the temporal mapping.\n", + " set_database_metadata(modified_name, representative_time=datetime.strptime(year, \"%Y\"))" ] }, { @@ -694,40 +703,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "`bw_timex` also needs to know the representative time of the databases:" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "execution": { - "iopub.execute_input": "2026-08-03T20:00:28.316357Z", - "iopub.status.busy": "2026-08-03T20:00:28.316297Z", - "iopub.status.idle": "2026-08-03T20:00:28.318261Z", - "shell.execute_reply": "2026-08-03T20:00:28.317973Z" - } - }, - "outputs": [], - "source": [ - "from datetime import datetime\n", - "\n", - "database_dates = {\n", - " db_2020.name: datetime.strptime(\"2020\", \"%Y\"),\n", - " db_2030.name: datetime.strptime(\"2030\", \"%Y\"),\n", - " db_2040.name: datetime.strptime(\"2040\", \"%Y\"),\n", - " \"ev_background_2020\": datetime.strptime(\"2020\", \"%Y\"),\n", - " \"ev_background_2030\": datetime.strptime(\"2030\", \"%Y\"),\n", - " \"ev_background_2040\": datetime.strptime(\"2040\", \"%Y\"),\n", - " \"foreground\": \"dynamic\", # flag databases that should be temporally distributed with \"dynamic\"\n", - "}" + "premise writes the point in time each background database represents as `representative_time` metadata on the database itself, so `bw_timex` finds it automatically - no mapping needed in this script. We set the same metadata on our own `ev_background_` copies above, since they represent the same points in time." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Now, we can instantiate a `TimexLCA`. It's structure is similar to a normal `bw2calc.LCA`, but with the additional argument `database_dates`.\n", + "Now, we can instantiate a `TimexLCA`. It's structure is similar to a normal `bw2calc.LCA`.\n", "\n", "Not sure about the required inputs? Check the documentation using `?`. All our classes and methods have docstrings!" ] @@ -807,7 +790,7 @@ } ], "source": [ - "tlca = TimexLCA({driving: 1}, method, database_dates)" + "tlca = TimexLCA({driving: 1}, method)" ] }, { diff --git a/notebooks/teaching/ev_walkthrough_premise.ipynb b/notebooks/teaching/ev_walkthrough_premise.ipynb index 70430bf9..f2ecc4eb 100644 --- a/notebooks/teaching/ev_walkthrough_premise.ipynb +++ b/notebooks/teaching/ev_walkthrough_premise.ipynb @@ -382,25 +382,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Specify prospective background database dates\n", + "### Prospective background databases\n", "\n", - "Created with [`premise`](https://github.com/polca/premise) following the REMIND-EU SSP2 NDC scenario.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "from datetime import datetime\n", - "\n", - "database_dates = {\n", - " \"ei312_REMIND-EU_SSP2_NDC_2020\": datetime.strptime(\"2020\", \"%Y\"),\n", - " \"ei312_REMIND-EU_SSP2_NDC_2030\": datetime.strptime(\"2030\", \"%Y\"),\n", - " \"ei312_REMIND-EU_SSP2_NDC_2040\": datetime.strptime(\"2040\", \"%Y\"),\n", - " \"foreground\": \"dynamic\", # Doesn't have a fixed date, but will be distributed over time\n", - "}" + "Created with [`premise`](https://github.com/polca/premise) following the REMIND-EU SSP2 NDC scenario.\n", + "premise writes the point in time each database represents as `representative_time` metadata on the\n", + "database itself, so `bw_timex` finds it automatically - no mapping needed in this script." ] }, { @@ -430,7 +416,7 @@ "source": [ "from bw_timex import TimexLCA\n", "\n", - "tlca = TimexLCA({driving: 1}, method, database_dates)" + "tlca = TimexLCA({driving: 1}, method)" ] }, { @@ -1604,7 +1590,7 @@ } ], "source": [ - "tlca = TimexLCA(demand={driving: 1}, method=method, database_dates=database_dates)\n", + "tlca = TimexLCA(demand={driving: 1}, method=method)\n", "tlca.build_timeline(starting_datetime=\"2025-01-01\", temporal_grouping=\"month\", graph_traversal=\"bfs\")\n", "tlca.lci()\n", "tlca.static_lcia()\n", diff --git a/notebooks/teaching/exercise_ev_vs_petrol_solutions.ipynb b/notebooks/teaching/exercise_ev_vs_petrol_solutions.ipynb index 6fbf578f..73f8b644 100644 --- a/notebooks/teaching/exercise_ev_vs_petrol_solutions.ipynb +++ b/notebooks/teaching/exercise_ev_vs_petrol_solutions.ipynb @@ -602,25 +602,9 @@ "source": [ "Now we can start using `bw_timex` to build the process timeline, build the time-explicit inventory and calculate the time explicit scores.\n", "\n", - "Start by creating a dictionary mapping the respective databases to the relevant timestamps. \n", - "Then instantiate your timexLCA object, build the timeline, calculate the LCI, and the LCIA for both options.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from datetime import datetime\n", + "premise writes the point in time each background database represents as `representative_time` metadata on the database itself, so `bw_timex` finds it automatically - no `database_dates` mapping needed here. But this project holds *two* IAM scenarios (NPi and PkBudg650), so we need to pick one with the `scenario` argument, e.g. `scenario={\"pathway\": \"SSP2-NPi\"}`.\n", "\n", - "database_dates = {\n", - " db_2020_NPi.name: datetime.strptime(\"2020\", \"%Y\"),\n", - " db_2030_NPi.name: datetime.strptime(\"2030\", \"%Y\"),\n", - " db_2040_NPi.name: datetime.strptime(\"2040\", \"%Y\"),\n", - " db_2050_NPi.name: datetime.strptime(\"2050\", \"%Y\"),\n", - " foreground.name: \"dynamic\", # flag databases that should be temporally distributed with \"dynamic\"\n", - "}" + "Then instantiate your timexLCA object, build the timeline, calculate the LCI, and the LCIA for both options.\n" ] }, { @@ -631,7 +615,7 @@ "source": [ "from bw_timex import TimexLCA\n", "\n", - "tlca_BEV = TimexLCA({LC_BEV: 1}, method, database_dates)\n", + "tlca_BEV = TimexLCA({LC_BEV: 1}, method, scenario={\"pathway\": \"SSP2-NPi\"})\n", "tlca_BEV.build_timeline(temporal_grouping=\"year\") # build timeline with yearly steps" ] }, @@ -668,7 +652,7 @@ "metadata": {}, "outputs": [], "source": [ - "tlca_ICEC = TimexLCA({LC_ICEC: 1}, method, database_dates)\n", + "tlca_ICEC = TimexLCA({LC_ICEC: 1}, method, scenario={\"pathway\": \"SSP2-NPi\"})\n", "tlca_ICEC.build_timeline(temporal_grouping=\"year\") # build timeline with yearly steps\n", "tlca_ICEC.lci() # calculate the dynamic inventory\n", "tlca_ICEC.dynamic_lcia(metric=\"GWP\", time_horizon=100, fixed_time_horizon=True)" From 9a18058468ae4dfb2367c56dbf3a8866382e5e4d Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 12:59:18 +0200 Subject: [PATCH 14/24] fix: restore dynamic GWP output for cell 24 in EV premise notebook --- .../examples/electric_vehicle_premise.ipynb | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/notebooks/examples/electric_vehicle_premise.ipynb b/notebooks/examples/electric_vehicle_premise.ipynb index b7aed1fa..4a100969 100644 --- a/notebooks/examples/electric_vehicle_premise.ipynb +++ b/notebooks/examples/electric_vehicle_premise.ipynb @@ -1113,7 +1113,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "cell-24", "metadata": { "execution": { @@ -1123,7 +1123,25 @@ "shell.execute_reply": "2026-08-03T19:57:06.669247Z" } }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[32m2026-08-04 07:10:51.241\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mdynamic_characterization.dynamic_characterization\u001b[0m:\u001b[36mcharacterize\u001b[0m:\u001b[36m126\u001b[0m - \u001b[1mNo custom dynamic characterization functions provided. Using default dynamic characterization functions. The flows that are characterized are based on the selection of the initially chosen impact category.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "np.float64(10655.763775235002)" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "tlca.dynamic_lcia(metric=\"GWP\")\n", "tlca.dynamic_score # kg CO2-eq (GWP100)" From 3ff33c3369a635bf9cf110eff023c0a3b99e952c Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 13:03:51 +0200 Subject: [PATCH 15/24] fix: address leftovers from the metadata migration Make the premise_version example in database_metadata.py's docstring and background_database_metadata.md's callout self-consistent: both showed 2.4.9.1 alongside representative_time, but the callout says premise only writes that key from the version after 2.4.9.2 onwards. Bumped the example version to 2.4.9.3. Added a CHANGES.md bullet for the reworded database_dates-specific error messages and the new ValueError on a scenario filter that matches no database, which Task 5's changelog entries had missed. --- CHANGES.md | 1 + bw_timex/database_metadata.py | 2 +- docs/content/background_database_metadata.md | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 990ba47e..1c163bfa 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added `representative_time` database metadata as the default timing source: `TimexLCA` now maps background databases to points in time by reading their Brightway metadata (as written by premise), making `database_dates` optional ([#217](https://github.com/brightway-lca/bw_timex/issues/217)) * Added `set_database_metadata` to record what a database represents (`representative_time`, and scenario fields such as `iam_model` or `pathway`) for databases that don't bring the metadata themselves * Added `TimexLCA(scenario={...})` to select one background scenario when a project holds several; `TimexLCA` raises and lists the scenarios it found if the choice is ambiguous +* Fixed `TimexLCA(scenario={...})` silently falling back to a plain (non-time-explicit) LCA when the filter matched no database at all, e.g. a typo in a key or value; it now raises a `ValueError` naming the filter and what each of its keys is actually declared as across the project's databases. Also reworded the `database_dates`-specific error messages in `validation.py`, `timeline_builder.py`, and `edge_extractor.py` to also credit `representative_time` metadata as a source of timing ([#217](https://github.com/brightway-lca/bw_timex/issues/217)) ## [1.2.1] - 2026-08-14 * Fixed `ShapeMismatch` in `lci()` for processes with more than one biosphere exchange, by sizing the biosphere `flip_array` to the number of matrix entries (only raised with `bw_processing` >= 1.5; no numeric results change) ([#213](https://github.com/brightway-lca/bw_timex/pull/213)) diff --git a/bw_timex/database_metadata.py b/bw_timex/database_metadata.py index 1f6f1863..f2e4fc60 100644 --- a/bw_timex/database_metadata.py +++ b/bw_timex/database_metadata.py @@ -7,7 +7,7 @@ ```python { - "premise_version": "2.4.9.1", + "premise_version": "2.4.9.3", "iam_model": "remind", "pathway": "SSP2-PkBudg500", "representative_time": "2050-01-01T00:00:00", diff --git a/docs/content/background_database_metadata.md b/docs/content/background_database_metadata.md index 4a270795..1c4fcde8 100644 --- a/docs/content/background_database_metadata.md +++ b/docs/content/background_database_metadata.md @@ -21,7 +21,7 @@ bd.databases["ei_cutoff_3.10.1_remind_SSP2-PkBudg500_2050"] # written by brightway "format": "Ecoinvent XML", "backend": "sqlite", "number": 43648, ..., # written by premise - "premise_version": "2.4.9.1", + "premise_version": "2.4.9.3", "iam_model": "remind", "pathway": "SSP2-PkBudg500", "representative_time": "2050-01-01T00:00:00", From 2be1610f852210cbb96b2a8cc71ba13add78bbbf Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 13:16:23 +0200 Subject: [PATCH 16/24] fix: catch scenario filters that match nothing but leave survivors A database that declares no scenario keys survives every `scenario` filter, so the resolved mapping stayed non-empty and masked a filter that matched none of the actual scenario databases (e.g. a typo'd pathway value with an unrelated hand-built vintage also in the project). Raise based on whether any surviving database positively declares one of the filtered keys, not on whether the resolved mapping is empty. Also treat `database_dates={}` as an explicit (invalid) mapping rather than falling through to metadata resolution, and hoist the `DatabaseMetadataInputs` import in database_metadata.py to module scope now that there's no cycle to avoid. --- bw_timex/database_metadata.py | 4 +- bw_timex/timex_lca.py | 56 ++++++++++++--------- tests/test_database_metadata.py | 88 +++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 25 deletions(-) diff --git a/bw_timex/database_metadata.py b/bw_timex/database_metadata.py index f2e4fc60..0063b4cb 100644 --- a/bw_timex/database_metadata.py +++ b/bw_timex/database_metadata.py @@ -29,6 +29,8 @@ import bw2data as bd from loguru import logger +from .validation import DatabaseMetadataInputs + REPRESENTATIVE_TIME = "representative_time" SCENARIOS = "scenarios" DYNAMIC = "dynamic" @@ -134,8 +136,6 @@ def set_database_metadata(database: str | bd.Database, **metadata) -> dict: ) ``` """ - from .validation import DatabaseMetadataInputs - name = _database_name(database) DatabaseMetadataInputs(database=name, metadata=metadata) diff --git a/bw_timex/timex_lca.py b/bw_timex/timex_lca.py index 577b35ac..3c34228f 100644 --- a/bw_timex/timex_lca.py +++ b/bw_timex/timex_lca.py @@ -275,12 +275,18 @@ def _resolve_database_dates( If both `database_dates` and `scenario` are given (`scenario` only selects among databases resolved from metadata, so it makes no sense once `database_dates` already gives the whole mapping), - or if `scenario` is given but matches no database's metadata at - all - almost always a typo in one of its keys or values, since a - filter that legitimately excludes everything would leave nothing - for `TimexLCA` to compute with. + or if `scenario` is given but no surviving database positively + declares one of its keys - almost always a typo in one of its + keys or values, since a filter that legitimately excludes + everything would leave nothing for `TimexLCA` to compute with. + A database that doesn't declare a filtered key at all is kept by + the filter (see `resolve_database_dates_from_metadata`), so + checking whether the *resolved mapping* is empty is not enough: + it stays non-empty whenever such a database happens to be + present, even though the filter matched none of the databases it + was meant to select among. """ - if database_dates: + if database_dates is not None: if scenario: raise ValueError( "`scenario` selects background databases by their metadata and " @@ -291,24 +297,28 @@ def _resolve_database_dates( resolved = resolve_database_dates_from_metadata(scenario) - if not resolved: - if scenario: - declared = {} - for name in bd.databases: - metadata = bd.databases[name] - for key in scenario: - if key in metadata: - declared.setdefault(key, set()).add(str(metadata[key])) - details = "; ".join( - f"'{key}': " - f"{sorted(declared[key]) if key in declared else 'not declared by any database'}" - for key in scenario - ) - raise ValueError( - f"scenario={scenario!r} matched no database in this project. " - f"Values actually declared for its key(s) by this project's " - f"databases: {details}. Check for a typo in the filter." - ) + filter_matched = scenario and any( + key in bd.databases[name] for name in resolved for key in scenario + ) + + if scenario and not filter_matched: + declared = {} + for name in bd.databases: + metadata = bd.databases[name] + for key in scenario: + if key in metadata: + declared.setdefault(key, set()).add(str(metadata[key])) + details = "; ".join( + f"'{key}': " + f"{sorted(declared[key]) if key in declared else 'not declared by any database'}" + for key in scenario + ) + raise ValueError( + f"scenario={scenario!r} matched no database in this project. " + f"Values actually declared for its key(s) by this project's " + f"databases: {details}. Check for a typo in the filter." + ) + elif not resolved: logger.info( "No database_dates provided, and no database in this project carries " "`representative_time` metadata. Treating the databases containing the " diff --git a/tests/test_database_metadata.py b/tests/test_database_metadata.py index 32476cb5..31c58483 100644 --- a/tests/test_database_metadata.py +++ b/tests/test_database_metadata.py @@ -4,6 +4,7 @@ import bw2data as bd import pytest +from loguru import logger from bw_timex import TimexLCA, set_database_metadata from bw_timex.database_metadata import resolve_database_dates_from_metadata @@ -116,6 +117,60 @@ def test_invalid_metadata_value_raises_naming_the_database(self): with pytest.raises(ValueError, match="db_2022"): resolve_database_dates_from_metadata() + def test_multi_scenario_database_is_named_in_the_log(self): + set_database_metadata("db_2022", representative_time="2022-01-01") + set_database_metadata( + "db_2024", + representative_time="2024-01-01", + scenarios=[ + {"pathway": "SSP2-Base", "representative_time": "2024-01-01"}, + {"pathway": "SSP2-PkBudg500", "representative_time": "2024-01-01"}, + ], + ) + messages = [] + sink_id = logger.add(messages.append, level="INFO") + try: + resolve_database_dates_from_metadata() + finally: + logger.remove(sink_id) + assert any("db_2024" in message for message in messages) + + +# ─── Tests for order-insensitive `external_scenarios` comparison ─── + + +@pytest.mark.usefixtures("temporal_grouping_db_monthly") +class TestExternalScenariosOrderInsensitive: + + def test_ambiguity_signature_is_order_insensitive(self): + """Same `external_scenarios`, listed in a different order, must not + look like two different scenarios.""" + set_database_metadata( + "db_2022", + representative_time="2022-01-01", + external_scenarios=["scenario_a", "scenario_b"], + ) + set_database_metadata( + "db_2024", + representative_time="2024-01-01", + external_scenarios=["scenario_b", "scenario_a"], + ) + # Would raise "Several background scenarios found" if the signature + # depended on list order. + resolved = resolve_database_dates_from_metadata() + assert set(resolved) == {"db_2022", "db_2024"} + + def test_filter_value_is_order_insensitive(self): + set_database_metadata( + "db_2022", + representative_time="2022-01-01", + external_scenarios=["scenario_a", "scenario_b"], + ) + resolved = resolve_database_dates_from_metadata( + scenario={"external_scenarios": ["scenario_b", "scenario_a"]} + ) + assert resolved == {"db_2022": datetime(2022, 1, 1)} + # ─── Tests for scenario selection ─── @@ -257,6 +312,26 @@ def test_typo_in_scenario_value_raises(self, fu): assert "SSP2-Base" in message assert "SSP2-PkBudg500" in message + def test_typo_in_scenario_value_raises_even_with_a_non_scenario_survivor(self, fu): + """A database that declares no scenario keys survives every filter, + so the resolved mapping is non-empty even though the filter matched + none of the scenario databases it was meant to select among. The + error must still fire - checking whether `resolved` is empty is not + enough. + """ + set_database_metadata("db_2022", representative_time="2022-01-01") + set_database_metadata( + "db_2024", representative_time="2024-01-01", pathway="SSP2-Base" + ) + with pytest.raises(ValueError, match="SSP2-Basee") as excinfo: + TimexLCA( + demand={fu.key: 1}, + method=("GWP", "example"), + scenario={"pathway": "SSP2-Basee"}, + ) + message = str(excinfo.value) + assert "SSP2-Base" in message + def test_database_dates_is_exclusive(self, fu): set_database_metadata("db_2022", representative_time="2022-01-01") set_database_metadata("db_2024", representative_time="2024-01-01") @@ -282,6 +357,19 @@ def test_database_dates_with_scenario_raises(self, fu): scenario={"pathway": "SSP2-Base"}, ) + def test_empty_database_dates_dict_raises(self, fu): + """An empty dict is falsy but not `None`: it must still be treated as + an explicit (if invalid) `database_dates`, not fall through to + metadata resolution. + """ + set_database_metadata("db_2022", representative_time="2022-01-01") + with pytest.raises(ValueError, match="non-empty dictionary"): + TimexLCA( + demand={fu.key: 1}, + method=("GWP", "example"), + database_dates={}, + ) + def test_no_metadata_anywhere_falls_back_to_dynamic_demand(self, fu): tlca = TimexLCA(demand={fu.key: 1}, method=("GWP", "example")) assert tlca.database_dates == {"foreground": "dynamic"} From 9061cfe2b2e0a78abf74770d696141098dfde98a Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 13:16:27 +0200 Subject: [PATCH 17/24] docs: correct quickstart arg order and metadata cost description The quickstart cheat-sheet listed scenario before database_dates; the real signature order is (demand, method, database_dates, scenario, use_global_lci_cache). Reword the "every database with representative_time is pulled in" note: it's not only a setup-time cost, it's a results concern too, since everything in database_dates_static becomes a temporal-market interpolation candidate. --- docs/content/background_database_metadata.md | 19 ++++++++++++++----- docs/content/getting_started/quickstart.md | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/docs/content/background_database_metadata.md b/docs/content/background_database_metadata.md index 1c4fcde8..440f394d 100644 --- a/docs/content/background_database_metadata.md +++ b/docs/content/background_database_metadata.md @@ -151,8 +151,17 @@ tlca = TimexLCA( Use it when you want to restrict a calculation to a subset of the databases in your project, or when a database's metadata is wrong and you don't want to change it. -It is also the fastest option on a large project: without it, `TimexLCA` reads -metadata from and loads node data for *every* registered database that carries -`representative_time`, including ones your demand does not actually depend on, -which costs setup time at premise scale - narrow it down with `scenario`, or bypass -metadata resolution entirely with an explicit `database_dates`. + +Without it, `TimexLCA` reads metadata from and loads node data for *every* +registered database that carries `representative_time`, including ones your demand +does not actually depend on. At premise scale, that is not just setup time: every +database that ends up in `database_dates_static` becomes a candidate producer for +temporal market interpolation, so an unrelated study's vintages sitting in the same +project can change your results, not only how long setup takes. `bw_timex` guards +against the case where this goes visibly wrong - the [ambiguous-scenario +check](#choosing-a-scenario) and the same-date collision error raised when two +databases hold the same producer at the same date - but it cannot catch a +same-named, same-dated producer that is a legitimate, silent match. Narrow the +selection down with `scenario`, or bypass metadata resolution entirely with an +explicit `database_dates`, whenever your project holds background data you don't +want considered. diff --git a/docs/content/getting_started/quickstart.md b/docs/content/getting_started/quickstart.md index a8e0bf3b..44283b1d 100644 --- a/docs/content/getting_started/quickstart.md +++ b/docs/content/getting_started/quickstart.md @@ -141,8 +141,8 @@ and for mapping databases explicitly with `database_dates`. TimexLCA( demand={("foreground", "A"): 1}, # Node, (database, code) tuple, or int id method=("our", "method"), - scenario=None, # optional: pick one scenario among several database_dates=None, # optional: map databases explicitly instead of by metadata + scenario=None, # optional: pick one scenario among several ) ``` From 8d4ad8daa2b01961a68eff895c2beca82a17cc35 Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 13:40:04 +0200 Subject: [PATCH 18/24] docs: state that premise >= 2.4.9.2 writes the database metadata --- bw_timex/database_metadata.py | 16 ++++++++++------ bw_timex/timex_lca.py | 13 +++++++------ docs/content/background_database_metadata.md | 8 ++++---- .../adding_temporal_information.md | 5 +++-- .../getting_started/build_process_timeline.md | 2 ++ docs/content/getting_started/quickstart.md | 2 +- 6 files changed, 27 insertions(+), 19 deletions(-) diff --git a/bw_timex/database_metadata.py b/bw_timex/database_metadata.py index 0063b4cb..ad36ff56 100644 --- a/bw_timex/database_metadata.py +++ b/bw_timex/database_metadata.py @@ -7,7 +7,7 @@ ```python { - "premise_version": "2.4.9.3", + "premise_version": "2.4.9.2", "iam_model": "remind", "pathway": "SSP2-PkBudg500", "representative_time": "2050-01-01T00:00:00", @@ -16,6 +16,9 @@ } ``` +premise writes this metadata from version 2.4.9.2 onwards. Databases exported by +an earlier premise carry none of it, and need `set_database_metadata`. + Brightway stores this mapping as JSON, so dates are kept as ISO 8601 strings. """ @@ -102,8 +105,8 @@ def set_database_metadata(database: str | bd.Database, **metadata) -> dict: Store what a database represents in its Brightway metadata. Use this for databases that don't bring the metadata themselves, e.g. - databases you built yourself or that were exported by a premise version - older than the one writing scenario metadata. `TimexLCA` reads + databases you built yourself or that were exported by premise < 2.4.9.2, + which is the first version writing this metadata. `TimexLCA` reads `representative_time` from all databases of the project to map them to points in time, so this replaces passing `database_dates`. @@ -115,9 +118,10 @@ def set_database_metadata(database: str | bd.Database, **metadata) -> dict: Metadata to store. `representative_time` accepts a `datetime`, an ISO 8601 string, or `"dynamic"` and is always stored as a string, because Brightway serializes database metadata to JSON. Any other key is stored - as given and must be JSON-serializable. Keys that premise writes, and - that `TimexLCA(scenario=...)` can select on, are `iam_model`, - `pathway`, `system_model`, `ecoinvent_version` and `premise_version`. + as given and must be JSON-serializable. Keys that premise (>= 2.4.9.2) + writes, and that `TimexLCA(scenario=...)` can select on, are + `iam_model`, `pathway`, `system_model`, `ecoinvent_version` and + `premise_version`. Returns ------- diff --git a/bw_timex/timex_lca.py b/bw_timex/timex_lca.py index 3c34228f..3c7c4169 100644 --- a/bw_timex/timex_lca.py +++ b/bw_timex/timex_lca.py @@ -88,8 +88,8 @@ class TimexLCA: demand = {("my_foreground_database", "my_process"): 1} method = ("some_method_family", "some_category", "some_method") - # Databases exported by premise already know the point in time they - # represent. For your own databases, say so once: + # Databases exported by premise >= 2.4.9.2 already know the point in + # time they represent. For your own databases, say so once: set_database_metadata("my_background_database_one", representative_time=datetime(2020, 1, 1)) set_database_metadata("my_background_database_two", representative_time=datetime(2030, 1, 1)) @@ -149,13 +149,14 @@ def __init__( instead of writing them into the shared background database for that vintage. If not given, the mapping is read from the databases' own `representative_time` metadata (which premise - writes when exporting, and which you can set yourself with - `bw_timex.set_database_metadata`). Passing this argument replaces + >= 2.4.9.2 writes when exporting, and which you can set yourself + with `bw_timex.set_database_metadata`). Passing this argument replaces the metadata entirely: only the databases listed here are used. scenario : dict, optional Metadata a background database must match to be used, e.g. - `{"iam_model": "remind", "pathway": "SSP2-PkBudg500"}`. Only - needed when the project holds several scenarios - `TimexLCA` + `{"iam_model": "remind", "pathway": "SSP2-PkBudg500"}`, as + written by premise >= 2.4.9.2. Only needed when the project + holds several scenarios - `TimexLCA` raises and lists them otherwise. Databases that don't declare the filtered key (your foreground, a hand-built vintage) are always kept. Cannot be combined with `database_dates`. diff --git a/docs/content/background_database_metadata.md b/docs/content/background_database_metadata.md index 440f394d..f600e7b8 100644 --- a/docs/content/background_database_metadata.md +++ b/docs/content/background_database_metadata.md @@ -21,7 +21,7 @@ bd.databases["ei_cutoff_3.10.1_remind_SSP2-PkBudg500_2050"] # written by brightway "format": "Ecoinvent XML", "backend": "sqlite", "number": 43648, ..., # written by premise - "premise_version": "2.4.9.3", + "premise_version": "2.4.9.2", "iam_model": "remind", "pathway": "SSP2-PkBudg500", "representative_time": "2050-01-01T00:00:00", @@ -39,9 +39,9 @@ tlca = TimexLCA(demand={("foreground", "A"): 1}, method=("our", "method")) !!! info "premise version" - premise writes this metadata from the version following 2.4.9.2 onwards. For - databases written by an earlier version, set it yourself as shown below - it is - a one-liner per database. + Only premise **>= 2.4.9.2** writes this metadata. Databases exported by an + earlier premise carry none of it - set it yourself as shown below, it is a + one-liner per database. ## Setting it yourself diff --git a/docs/content/getting_started/adding_temporal_information.md b/docs/content/getting_started/adding_temporal_information.md index 51ce6fe9..8f4826d2 100644 --- a/docs/content/getting_started/adding_temporal_information.md +++ b/docs/content/getting_started/adding_temporal_information.md @@ -260,8 +260,9 @@ set_database_metadata("background_2030", representative_time=datetime(2030, 1, 1 ``` You only do this once per database - it is stored in your Brightway project. Databases -exported by [premise](https://premise.readthedocs.io/en/latest/introduction.html) bring -this metadata with them, so there is nothing to do for those. The foreground doesn't +exported by [premise](https://premise.readthedocs.io/en/latest/introduction.html) +**>= 2.4.9.2** bring this metadata with them, so there is nothing to do for those; +for databases from an earlier premise, set it yourself as above. The foreground doesn't represent a specific point in time and is distributed over time instead; `bw_timex` treats the databases holding your functional unit that way automatically. diff --git a/docs/content/getting_started/build_process_timeline.md b/docs/content/getting_started/build_process_timeline.md index ccbb0f87..2d5770b4 100644 --- a/docs/content/getting_started/build_process_timeline.md +++ b/docs/content/getting_started/build_process_timeline.md @@ -21,6 +21,8 @@ tlca = TimexLCA( ) ``` +The metadata is written by premise >= 2.4.9.2, or by you with +`set_database_metadata` (see [Step 1](adding_temporal_information.md)). If your project holds several scenarios, select one with `scenario={"pathway": "SSP2-PkBudg500"}`; to map the databases by hand instead, pass `database_dates`. Both are covered in diff --git a/docs/content/getting_started/quickstart.md b/docs/content/getting_started/quickstart.md index 44283b1d..6068deb3 100644 --- a/docs/content/getting_started/quickstart.md +++ b/docs/content/getting_started/quickstart.md @@ -57,7 +57,7 @@ add_temporal_distribution_to_exchange( ) # 3. Say what your time-specific background databases represent -# (premise databases already know - skip this for them) +# (databases from premise >= 2.4.9.2 already know - skip this for them) set_database_metadata("background", representative_time=datetime(2020, 1, 1)) set_database_metadata("background_2030", representative_time=datetime(2030, 1, 1)) From 42330d3356a40454e0a6d32e7ec69959a02fe5fc Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 13:41:47 +0200 Subject: [PATCH 19/24] docs: qualify premise metadata claim with version requirement in notebooks premise only writes representative_time metadata from 2.4.9.2 onwards (current release is 2.4.9.1), so notebooks claiming premise databases already know their point in time now say so and point to set_database_metadata for earlier versions. --- .../background_temporal_distributions_premise.ipynb | 6 +++--- notebooks/examples/electric_vehicle_premise.ipynb | 2 +- notebooks/examples/electric_vehicle_premise_detailed.ipynb | 2 +- notebooks/teaching/ev_walkthrough_premise.ipynb | 5 +++-- notebooks/teaching/exercise_ev_vs_petrol_solutions.ipynb | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/notebooks/advanced/background_temporal_distributions_premise.ipynb b/notebooks/advanced/background_temporal_distributions_premise.ipynb index 44745a63..838e4b94 100644 --- a/notebooks/advanced/background_temporal_distributions_premise.ipynb +++ b/notebooks/advanced/background_temporal_distributions_premise.ipynb @@ -394,9 +394,9 @@ "\n", "# This single line runs a full static ecoinvent LCA of the demand under the hood\n", "# (the \"base LCA\"), which is the slowest step of the whole notebook (~30 s on a\n", - "# cold cache). We build exactly ONE TimexLCA and reuse it below. premise wrote each\n", - "# background database's representative_time as its own metadata, so no database_dates\n", - "# mapping is needed here.\n", + "# cold cache). We build exactly ONE TimexLCA and reuse it below. premise >= 2.4.9.2\n", + "# writes each background database's representative_time as its own metadata, so no\n", + "# database_dates mapping is needed here.\n", "tlca = TimexLCA({A: 1}, method)" ] }, diff --git a/notebooks/examples/electric_vehicle_premise.ipynb b/notebooks/examples/electric_vehicle_premise.ipynb index 4a100969..1d5570af 100644 --- a/notebooks/examples/electric_vehicle_premise.ipynb +++ b/notebooks/examples/electric_vehicle_premise.ipynb @@ -459,7 +459,7 @@ "source": [ "## Time-explicit LCA\n", "\n", - "Besides the functional unit and the impact assessment method, `bw_timex` needs to know which point in time each background database represents. premise writes this as `representative_time` metadata on the databases it creates, so `bw_timex` finds it automatically; we set the same metadata on our own `ev_background_` copies above, since they represent the same points in time. Databases that carry temporal distributions - here our foreground - are flagged as `\"dynamic\"` automatically." + "Besides the functional unit and the impact assessment method, `bw_timex` needs to know which point in time each background database represents. premise >= 2.4.9.2 writes this as `representative_time` metadata on the databases it creates, so `bw_timex` finds it automatically; for an earlier premise, set it yourself with `set_database_metadata` - which is what we did for our own `ev_background_` copies above, since they represent the same points in time. Databases that carry temporal distributions - here our foreground - are flagged as `\"dynamic\"` automatically." ] }, { diff --git a/notebooks/examples/electric_vehicle_premise_detailed.ipynb b/notebooks/examples/electric_vehicle_premise_detailed.ipynb index 3eb935fa..1056e8d4 100644 --- a/notebooks/examples/electric_vehicle_premise_detailed.ipynb +++ b/notebooks/examples/electric_vehicle_premise_detailed.ipynb @@ -703,7 +703,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "premise writes the point in time each background database represents as `representative_time` metadata on the database itself, so `bw_timex` finds it automatically - no mapping needed in this script. We set the same metadata on our own `ev_background_` copies above, since they represent the same points in time." + "premise >= 2.4.9.2 writes the point in time each background database represents as `representative_time` metadata on the database itself, so `bw_timex` finds it automatically - no mapping needed in this script. For databases from an earlier premise, set it yourself with `set_database_metadata`, as we did for our own `ev_background_` copies above, since they represent the same points in time." ] }, { diff --git a/notebooks/teaching/ev_walkthrough_premise.ipynb b/notebooks/teaching/ev_walkthrough_premise.ipynb index f2ecc4eb..2499a715 100644 --- a/notebooks/teaching/ev_walkthrough_premise.ipynb +++ b/notebooks/teaching/ev_walkthrough_premise.ipynb @@ -385,8 +385,9 @@ "### Prospective background databases\n", "\n", "Created with [`premise`](https://github.com/polca/premise) following the REMIND-EU SSP2 NDC scenario.\n", - "premise writes the point in time each database represents as `representative_time` metadata on the\n", - "database itself, so `bw_timex` finds it automatically - no mapping needed in this script." + "premise >= 2.4.9.2 writes the point in time each database represents as `representative_time`\n", + "metadata on the database itself, so `bw_timex` finds it automatically - no mapping needed in this\n", + "script. For databases from an earlier premise, set it yourself with `set_database_metadata`." ] }, { diff --git a/notebooks/teaching/exercise_ev_vs_petrol_solutions.ipynb b/notebooks/teaching/exercise_ev_vs_petrol_solutions.ipynb index 73f8b644..67e57e1b 100644 --- a/notebooks/teaching/exercise_ev_vs_petrol_solutions.ipynb +++ b/notebooks/teaching/exercise_ev_vs_petrol_solutions.ipynb @@ -602,7 +602,7 @@ "source": [ "Now we can start using `bw_timex` to build the process timeline, build the time-explicit inventory and calculate the time explicit scores.\n", "\n", - "premise writes the point in time each background database represents as `representative_time` metadata on the database itself, so `bw_timex` finds it automatically - no `database_dates` mapping needed here. But this project holds *two* IAM scenarios (NPi and PkBudg650), so we need to pick one with the `scenario` argument, e.g. `scenario={\"pathway\": \"SSP2-NPi\"}`.\n", + "premise >= 2.4.9.2 writes the point in time each background database represents as `representative_time` metadata on the database itself, so `bw_timex` finds it automatically - no `database_dates` mapping needed here (for databases from an earlier premise, set it yourself with `set_database_metadata`). But this project holds *two* IAM scenarios (NPi and PkBudg650), so we need to pick one with the `scenario` argument, e.g. `scenario={\"pathway\": \"SSP2-NPi\"}`.\n", "\n", "Then instantiate your timexLCA object, build the timeline, calculate the LCI, and the LCIA for both options.\n" ] From 674c03756bae54248c3395b60d3b967c9c388cf6 Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 13:42:40 +0200 Subject: [PATCH 20/24] docs: name the premise version in the changelog entry --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 1c163bfa..2144e2f3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] -* Added `representative_time` database metadata as the default timing source: `TimexLCA` now maps background databases to points in time by reading their Brightway metadata (as written by premise), making `database_dates` optional ([#217](https://github.com/brightway-lca/bw_timex/issues/217)) +* Added `representative_time` database metadata as the default timing source: `TimexLCA` now maps background databases to points in time by reading their Brightway metadata (as written by premise >= 2.4.9.2), making `database_dates` optional ([#217](https://github.com/brightway-lca/bw_timex/issues/217)) * Added `set_database_metadata` to record what a database represents (`representative_time`, and scenario fields such as `iam_model` or `pathway`) for databases that don't bring the metadata themselves * Added `TimexLCA(scenario={...})` to select one background scenario when a project holds several; `TimexLCA` raises and lists the scenarios it found if the choice is ambiguous * Fixed `TimexLCA(scenario={...})` silently falling back to a plain (non-time-explicit) LCA when the filter matched no database at all, e.g. a typo in a key or value; it now raises a `ValueError` naming the filter and what each of its keys is actually declared as across the project's databases. Also reworded the `database_dates`-specific error messages in `validation.py`, `timeline_builder.py`, and `edge_extractor.py` to also credit `representative_time` metadata as a source of timing ([#217](https://github.com/brightway-lca/bw_timex/issues/217)) From 31a30caba6680832f0019887985f25c1c436573a Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 13:49:26 +0200 Subject: [PATCH 21/24] docs: correct the auto-dynamic rule and frame database_dates as the fallback --- bw_timex/timex_lca.py | 16 +++++++++++----- docs/content/background_database_metadata.md | 13 +++++++++++++ docs/content/getting_started/quickstart.md | 9 +++++++-- .../examples/electric_vehicle_premise.ipynb | 2 +- .../2_electric_vehicle_from_scratch.ipynb | 2 +- 5 files changed, 33 insertions(+), 9 deletions(-) diff --git a/bw_timex/timex_lca.py b/bw_timex/timex_lca.py index 3c7c4169..52d4f26e 100644 --- a/bw_timex/timex_lca.py +++ b/bw_timex/timex_lca.py @@ -141,9 +141,13 @@ def __init__( Tuple defining the LCIA method, such as `('foo', 'bar')` or default methods, such as `("EF v3.1", "climate change", "global warming potential (GWP100)")` database_dates : dict, optional - Dictionary mapping database names to the point in time they - represent, as a `datetime`, or to `"dynamic"` for databases whose - processes are distributed over time (typically the foreground). + Fallback for mapping the databases yourself instead of letting + `bw_timex` read their metadata - useful for databases written by + premise < 2.4.9.2, which carry no metadata, or when you want to + override what the metadata says. Dictionary mapping database names + to the point in time they represent, as a `datetime`, or to + `"dynamic"` for databases whose processes are distributed over + time (typically the foreground). Several databases may share the same date, e.g. to keep your own modified copies of background processes in their own database instead of writing them into the shared background database for @@ -154,8 +158,10 @@ def __init__( the metadata entirely: only the databases listed here are used. scenario : dict, optional Metadata a background database must match to be used, e.g. - `{"iam_model": "remind", "pathway": "SSP2-PkBudg500"}`, as - written by premise >= 2.4.9.2. Only needed when the project + `{"iam_model": "remind", "pathway": "SSP2-PkBudg500"}`. Reads the + scenario metadata written by premise >= 2.4.9.2 (or by you, with + `bw_timex.set_database_metadata`), so it does nothing for + databases that carry none. Only needed when the project holds several scenarios - `TimexLCA` raises and lists them otherwise. Databases that don't declare the filtered key (your foreground, a hand-built vintage) are always diff --git a/docs/content/background_database_metadata.md b/docs/content/background_database_metadata.md index f600e7b8..073d4869 100644 --- a/docs/content/background_database_metadata.md +++ b/docs/content/background_database_metadata.md @@ -68,6 +68,19 @@ over time. `TimexLCA` treats the databases holding your functional unit as set_database_metadata("foreground", representative_time="dynamic") ``` +Only the databases holding the functional unit get that treatment - nothing +inspects exchanges for temporal distributions. So if your foreground is split +across several databases, every one of them that does **not** hold the functional +unit has to be marked itself: + +```python +set_database_metadata("my_intermediate_foreground", representative_time="dynamic") +``` + +A foreground database that is neither marked nor holds the functional unit is +missing from the mapping entirely, and `build_timeline()` fails with a `KeyError` +on the first node it cannot place. + ## Several databases for the same point in time More than one database may carry the same date. This is useful when you modify diff --git a/docs/content/getting_started/quickstart.md b/docs/content/getting_started/quickstart.md index 6068deb3..305b1894 100644 --- a/docs/content/getting_started/quickstart.md +++ b/docs/content/getting_started/quickstart.md @@ -141,11 +141,16 @@ and for mapping databases explicitly with `database_dates`. TimexLCA( demand={("foreground", "A"): 1}, # Node, (database, code) tuple, or int id method=("our", "method"), - database_dates=None, # optional: map databases explicitly instead of by metadata - scenario=None, # optional: pick one scenario among several + database_dates=None, # fallback: map the databases yourself, overriding metadata + scenario=None, # pick one scenario, when the project holds several ) ``` +Both are optional. `scenario` narrows down what `bw_timex` reads from the database +metadata (written by premise >= 2.4.9.2, or by you with `set_database_metadata`); +`database_dates` is the fallback for when you'd rather write the mapping out yourself, +and it overrides the metadata entirely. + ### `build_timeline()` | Argument | Default | Description | diff --git a/notebooks/examples/electric_vehicle_premise.ipynb b/notebooks/examples/electric_vehicle_premise.ipynb index 1d5570af..1ce28a9f 100644 --- a/notebooks/examples/electric_vehicle_premise.ipynb +++ b/notebooks/examples/electric_vehicle_premise.ipynb @@ -459,7 +459,7 @@ "source": [ "## Time-explicit LCA\n", "\n", - "Besides the functional unit and the impact assessment method, `bw_timex` needs to know which point in time each background database represents. premise >= 2.4.9.2 writes this as `representative_time` metadata on the databases it creates, so `bw_timex` finds it automatically; for an earlier premise, set it yourself with `set_database_metadata` - which is what we did for our own `ev_background_` copies above, since they represent the same points in time. Databases that carry temporal distributions - here our foreground - are flagged as `\"dynamic\"` automatically." + "Besides the functional unit and the impact assessment method, `bw_timex` needs to know which point in time each background database represents. premise >= 2.4.9.2 writes this as `representative_time` metadata on the databases it creates, so `bw_timex` finds it automatically; for an earlier premise, set it yourself with `set_database_metadata` - which is what we did for our own `ev_background_` copies above, since they represent the same points in time. The database holding the functional unit - here our foreground - is flagged as `\"dynamic\"` automatically. Any other foreground database needs `set_database_metadata(db, representative_time=\"dynamic\")`." ] }, { diff --git a/notebooks/tutorials/2_electric_vehicle_from_scratch.ipynb b/notebooks/tutorials/2_electric_vehicle_from_scratch.ipynb index 44cb5fa3..b3da86a7 100644 --- a/notebooks/tutorials/2_electric_vehicle_from_scratch.ipynb +++ b/notebooks/tutorials/2_electric_vehicle_from_scratch.ipynb @@ -500,7 +500,7 @@ "source": [ "## Time-explicit LCA\n", "\n", - "Besides the functional unit and the impact assessment method, `bw_timex` needs to know which point in time each background database represents. That information is recorded once, as `representative_time` metadata on each database, using `set_database_metadata`. The `foreground`, which carries our temporal distributions, is treated as `\"dynamic\"` automatically." + "Besides the functional unit and the impact assessment method, `bw_timex` needs to know which point in time each background database represents. That information is recorded once, as `representative_time` metadata on each database, using `set_database_metadata`. The `foreground` holds our functional unit, so it is treated as `\"dynamic\"` automatically." ] }, { From d65dea404de8b8a39be13a9f491f6d03d8b60f37 Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 13:52:02 +0200 Subject: [PATCH 22/24] docs: qualify scenario filtering and note database_dates fallback in notebooks The scenario argument filters on the same premise >= 2.4.9.2 metadata as representative_time, so it needs the same version qualifier. Several notebooks also only mentioned set_database_metadata as the escape hatch for pre-2.4.9.2 premise, without pointing out that database_dates works as a full mapping/override too. --- notebooks/examples/electric_vehicle_premise.ipynb | 2 +- notebooks/examples/electric_vehicle_premise_detailed.ipynb | 2 +- notebooks/teaching/ev_walkthrough_premise.ipynb | 3 ++- notebooks/teaching/exercise_ev_vs_petrol_solutions.ipynb | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/notebooks/examples/electric_vehicle_premise.ipynb b/notebooks/examples/electric_vehicle_premise.ipynb index 1ce28a9f..65ea0a4d 100644 --- a/notebooks/examples/electric_vehicle_premise.ipynb +++ b/notebooks/examples/electric_vehicle_premise.ipynb @@ -459,7 +459,7 @@ "source": [ "## Time-explicit LCA\n", "\n", - "Besides the functional unit and the impact assessment method, `bw_timex` needs to know which point in time each background database represents. premise >= 2.4.9.2 writes this as `representative_time` metadata on the databases it creates, so `bw_timex` finds it automatically; for an earlier premise, set it yourself with `set_database_metadata` - which is what we did for our own `ev_background_` copies above, since they represent the same points in time. The database holding the functional unit - here our foreground - is flagged as `\"dynamic\"` automatically. Any other foreground database needs `set_database_metadata(db, representative_time=\"dynamic\")`." + "Besides the functional unit and the impact assessment method, `bw_timex` needs to know which point in time each background database represents. premise >= 2.4.9.2 writes this as `representative_time` metadata on the databases it creates, so `bw_timex` finds it automatically; for an earlier premise, set it yourself with `set_database_metadata` - which is what we did for our own `ev_background_` copies above, since they represent the same points in time. You can also skip metadata entirely and pass `database_dates` to `TimexLCA` yourself, which then takes over completely. The database holding the functional unit - here our foreground - is flagged as `\"dynamic\"` automatically. Any other foreground database needs `set_database_metadata(db, representative_time=\"dynamic\")`." ] }, { diff --git a/notebooks/examples/electric_vehicle_premise_detailed.ipynb b/notebooks/examples/electric_vehicle_premise_detailed.ipynb index 1056e8d4..0c135557 100644 --- a/notebooks/examples/electric_vehicle_premise_detailed.ipynb +++ b/notebooks/examples/electric_vehicle_premise_detailed.ipynb @@ -703,7 +703,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "premise >= 2.4.9.2 writes the point in time each background database represents as `representative_time` metadata on the database itself, so `bw_timex` finds it automatically - no mapping needed in this script. For databases from an earlier premise, set it yourself with `set_database_metadata`, as we did for our own `ev_background_` copies above, since they represent the same points in time." + "premise >= 2.4.9.2 writes the point in time each background database represents as `representative_time` metadata on the database itself, so `bw_timex` finds it automatically - no mapping needed in this script. For databases from an earlier premise, set it yourself with `set_database_metadata`, as we did for our own `ev_background_` copies above, since they represent the same points in time. `database_dates` is the same fallback, passed straight to `TimexLCA`, and it also lets you override what the metadata says for any database." ] }, { diff --git a/notebooks/teaching/ev_walkthrough_premise.ipynb b/notebooks/teaching/ev_walkthrough_premise.ipynb index 2499a715..3c8295e4 100644 --- a/notebooks/teaching/ev_walkthrough_premise.ipynb +++ b/notebooks/teaching/ev_walkthrough_premise.ipynb @@ -387,7 +387,8 @@ "Created with [`premise`](https://github.com/polca/premise) following the REMIND-EU SSP2 NDC scenario.\n", "premise >= 2.4.9.2 writes the point in time each database represents as `representative_time`\n", "metadata on the database itself, so `bw_timex` finds it automatically - no mapping needed in this\n", - "script. For databases from an earlier premise, set it yourself with `set_database_metadata`." + "script. For databases from an earlier premise, set it yourself with `set_database_metadata`, or\n", + "pass `database_dates` to `TimexLCA` to map (or override) it directly." ] }, { diff --git a/notebooks/teaching/exercise_ev_vs_petrol_solutions.ipynb b/notebooks/teaching/exercise_ev_vs_petrol_solutions.ipynb index 67e57e1b..f33322d5 100644 --- a/notebooks/teaching/exercise_ev_vs_petrol_solutions.ipynb +++ b/notebooks/teaching/exercise_ev_vs_petrol_solutions.ipynb @@ -602,7 +602,7 @@ "source": [ "Now we can start using `bw_timex` to build the process timeline, build the time-explicit inventory and calculate the time explicit scores.\n", "\n", - "premise >= 2.4.9.2 writes the point in time each background database represents as `representative_time` metadata on the database itself, so `bw_timex` finds it automatically - no `database_dates` mapping needed here (for databases from an earlier premise, set it yourself with `set_database_metadata`). But this project holds *two* IAM scenarios (NPi and PkBudg650), so we need to pick one with the `scenario` argument, e.g. `scenario={\"pathway\": \"SSP2-NPi\"}`.\n", + "premise >= 2.4.9.2 writes the point in time each background database represents as `representative_time` metadata on the database itself, so `bw_timex` finds it automatically - no `database_dates` mapping needed here (for databases from an earlier premise, set it yourself with `set_database_metadata`). But this project holds *two* IAM scenarios (NPi and PkBudg650), so we need to pick one with the `scenario` argument, e.g. `scenario={\"pathway\": \"SSP2-NPi\"}` - it filters on that same premise >= 2.4.9.2 metadata, so an earlier premise leaves no `pathway` key to filter on either.\n", "\n", "Then instantiate your timexLCA object, build the timeline, calculate the LCI, and the LCIA for both options.\n" ] From 42b97a35de8d0cb2b034385b1cfaf0bb680465a8 Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 13:57:01 +0200 Subject: [PATCH 23/24] docs: docs edits --- .gitignore | 5 +++++ docs/content/background_database_metadata.md | 2 +- docs/content/getting_started/build_process_timeline.md | 2 +- docs/content/getting_started/quickstart.md | 2 +- .../plans/2026-08-21-representative-time-metadata.md | 6 +++--- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 5beb133a..4ce94a32 100644 --- a/.gitignore +++ b/.gitignore @@ -205,3 +205,8 @@ pyrightconfig.json /docs/content/examples/tutorials/ /docs/content/examples/examples/ /docs/content/examples/advanced/ + +.claude +.superpowers + +.uv-cache \ No newline at end of file diff --git a/docs/content/background_database_metadata.md b/docs/content/background_database_metadata.md index 073d4869..08373653 100644 --- a/docs/content/background_database_metadata.md +++ b/docs/content/background_database_metadata.md @@ -4,7 +4,7 @@ tags: - background databases --- -# What a database represents +# Time-specific background databases `bw_timex` needs to know which point in time each background database stands for. That information lives in the database's own Brightway metadata, so it only has to diff --git a/docs/content/getting_started/build_process_timeline.md b/docs/content/getting_started/build_process_timeline.md index 2d5770b4..54ddaf56 100644 --- a/docs/content/getting_started/build_process_timeline.md +++ b/docs/content/getting_started/build_process_timeline.md @@ -26,7 +26,7 @@ The metadata is written by premise >= 2.4.9.2, or by you with If your project holds several scenarios, select one with `scenario={"pathway": "SSP2-PkBudg500"}`; to map the databases by hand instead, pass `database_dates`. Both are covered in -[What a database represents](../background_database_metadata.md). +[Time-specific background databases](../background_database_metadata.md). Using our new `tlca` object, we can now build the timeline of processes that leads to our functional unit "A". If not specified otherwise, it's assumed that the demand occurs in the current year. In our case, we're specifying the time of demand to the year 2024, with the attribute 'starting_datetime`.. Building the timeline is very simple: ```python diff --git a/docs/content/getting_started/quickstart.md b/docs/content/getting_started/quickstart.md index 305b1894..e860237e 100644 --- a/docs/content/getting_started/quickstart.md +++ b/docs/content/getting_started/quickstart.md @@ -128,7 +128,7 @@ e.g. for the timing of the functional unit itself. Relative dates (`dtype="timedelta64[Y]"`) are relative to the consuming process. Several databases may represent the same point in time, e.g. if you keep modified copies of background processes in their own database instead of writing them into the shared vintage. See -[What a database represents](../background_database_metadata.md) for scenario selection +[Time-specific background databases](../background_database_metadata.md) for scenario selection and for mapping databases explicitly with `database_dates`. --- diff --git a/docs/superpowers/plans/2026-08-21-representative-time-metadata.md b/docs/superpowers/plans/2026-08-21-representative-time-metadata.md index f0fef0fb..e23a9643 100644 --- a/docs/superpowers/plans/2026-08-21-representative-time-metadata.md +++ b/docs/superpowers/plans/2026-08-21-representative-time-metadata.md @@ -1242,7 +1242,7 @@ e.g. for the timing of the functional unit itself. Relative dates (`dtype="timedelta64[Y]"`) are relative to the consuming process. Several databases may represent the same point in time, e.g. if you keep modified copies of background processes in their own database instead of writing them into the shared vintage. See -[What a database represents](../background_database_metadata.md) for scenario selection +[Time-specific background databases](../background_database_metadata.md) for scenario selection and for mapping databases explicitly with `database_dates`. ``` @@ -1301,7 +1301,7 @@ tlca = TimexLCA( If your project holds several scenarios, select one with `scenario={"pathway": "SSP2-PkBudg500"}`; to map the databases by hand instead, pass `database_dates`. Both are covered in -[What a database represents](../background_database_metadata.md). +[Time-specific background databases](../background_database_metadata.md). ```` - [ ] **Step 6: Add the changelog entry** @@ -1310,7 +1310,7 @@ Under `## [Unreleased]` in `CHANGES.md`: ```markdown * Added `representative_time` database metadata as the default timing source: `TimexLCA` now maps background databases to points in time by reading their Brightway metadata (as written by premise), making `database_dates` optional ([#217](https://github.com/brightway-lca/bw_timex/issues/217)) -* Added `set_database_metadata` to record what a database represents (`representative_time`, and scenario fields such as `iam_model` or `pathway`) for databases that don't bring the metadata themselves +* Added `set_database_metadata` to record Time-specific background databases (`representative_time`, and scenario fields such as `iam_model` or `pathway`) for databases that don't bring the metadata themselves * Added `TimexLCA(scenario={...})` to select one background scenario when a project holds several; `TimexLCA` raises and lists the scenarios it found if the choice is ambiguous ``` From 107b0475f620f66d0039657feb2931a68ffaf2a0 Mon Sep 17 00:00:00 2001 From: TimoDiepers Date: Fri, 21 Aug 2026 15:26:52 +0200 Subject: [PATCH 24/24] docs: fine tuning --- CHANGES.md | 1 + bw_timex/__init__.py | 3 + bw_timex/errors.py | 15 ++ bw_timex/timeline_builder.py | 67 +++++++ docs/api/errors.md | 12 ++ docs/api/index.md | 2 + docs/content/background_database_metadata.md | 180 ------------------ .../adding_temporal_information.md | 14 +- .../getting_started/build_process_timeline.md | 34 +++- docs/content/getting_started/quickstart.md | 7 +- tests/conftest.py | 1 + tests/fixtures/split_foreground_db_fixture.py | 93 +++++++++ tests/test_unmapped_database.py | 87 +++++++++ zensical.toml | 2 +- 14 files changed, 329 insertions(+), 189 deletions(-) create mode 100644 bw_timex/errors.py create mode 100644 docs/api/errors.md delete mode 100644 docs/content/background_database_metadata.md create mode 100644 tests/fixtures/split_foreground_db_fixture.py create mode 100644 tests/test_unmapped_database.py diff --git a/CHANGES.md b/CHANGES.md index 2144e2f3..fb0cde46 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added `representative_time` database metadata as the default timing source: `TimexLCA` now maps background databases to points in time by reading their Brightway metadata (as written by premise >= 2.4.9.2), making `database_dates` optional ([#217](https://github.com/brightway-lca/bw_timex/issues/217)) * Added `set_database_metadata` to record what a database represents (`representative_time`, and scenario fields such as `iam_model` or `pathway`) for databases that don't bring the metadata themselves * Added `TimexLCA(scenario={...})` to select one background scenario when a project holds several; `TimexLCA` raises and lists the scenarios it found if the choice is ambiguous +* Added `UnmappedDatabaseError`, raised by `build_timeline()` when the graph traversal reaches a database that is mapped to no point in time - typically a second foreground database that neither holds the functional unit (which is marked `"dynamic"` automatically) nor was marked itself. This previously surfaced as a bare `KeyError` on a node id; the error now names the database, an affected process, and how to map it ([#217](https://github.com/brightway-lca/bw_timex/issues/217)) * Fixed `TimexLCA(scenario={...})` silently falling back to a plain (non-time-explicit) LCA when the filter matched no database at all, e.g. a typo in a key or value; it now raises a `ValueError` naming the filter and what each of its keys is actually declared as across the project's databases. Also reworded the `database_dates`-specific error messages in `validation.py`, `timeline_builder.py`, and `edge_extractor.py` to also credit `representative_time` metadata as a source of timing ([#217](https://github.com/brightway-lca/bw_timex/issues/217)) ## [1.2.1] - 2026-08-14 diff --git a/bw_timex/__init__.py b/bw_timex/__init__.py index 286e4d61..cf857177 100644 --- a/bw_timex/__init__.py +++ b/bw_timex/__init__.py @@ -8,6 +8,7 @@ from .database_metadata import set_database_metadata from .dynamic_biosphere_builder import DynamicBiosphereBuilder from .edge_extractor import EdgeExtractor +from .errors import UnmappedDatabaseError from .helper_classes import SetList from .matrix_modifier import MatrixModifier from .timeline_builder import TimelineBuilder @@ -36,6 +37,8 @@ "DynamicBiosphereBuilder", "EdgeExtractor", "SetList", + # errors + "UnmappedDatabaseError", # utils "add_flows_to_characterization_functions", "add_temporal_distribution_to_exchange", diff --git a/bw_timex/errors.py b/bw_timex/errors.py new file mode 100644 index 00000000..94f3fc5b --- /dev/null +++ b/bw_timex/errors.py @@ -0,0 +1,15 @@ +"""Errors raised by `bw_timex`.""" + +from __future__ import annotations + + +class UnmappedDatabaseError(ValueError): + """A database reached by the graph traversal is missing from the mapping. + + `bw_timex` places every traversed process in time via the database it + lives in, so each of them must either represent a point in time or be + marked as `"dynamic"`. Databases holding the functional unit are treated + as dynamic automatically; every other database has to say what it + represents, through its `representative_time` metadata or through + `database_dates`. + """ diff --git a/bw_timex/timeline_builder.py b/bw_timex/timeline_builder.py index 958b303e..9b070542 100644 --- a/bw_timex/timeline_builder.py +++ b/bw_timex/timeline_builder.py @@ -10,6 +10,7 @@ from loguru import logger from .edge_extractor import Edge, EdgeExtractor, EdgeExtractorBFS +from .errors import UnmappedDatabaseError from .utils import ( convert_date_string_to_datetime, extract_date_as_integer, @@ -306,6 +307,8 @@ def build_timeline(self) -> pd.DataFrame: grouped_edges = self._drop_edges_of_unsupplied_consumers(grouped_edges) + self._check_traversed_databases_are_mapped(grouped_edges) + # add new processes to activity_time_mapping static_dbs = set(self.database_dates_static.keys()) if self.traverse_background else set() for row in grouped_edges.itertuples(): @@ -381,6 +384,70 @@ def build_timeline(self) -> pd.DataFrame: # underlying functions called by build_timeline() # ################################################### + def _check_traversed_databases_are_mapped(self, grouped_edges: pd.DataFrame) -> None: + """ + Check that every traversed process lives in a database that is mapped. + + `bw_timex` places a process in time via the database it lives in, so + every database the traversal reaches must either represent a point in + time or be marked as `"dynamic"`. Only the databases holding the + functional unit are treated as dynamic automatically - a foreground + split across several databases has to mark the other ones itself. A + database that is mapped nowhere has no node metadata loaded for it, + which would otherwise surface as a bare `KeyError` on a node id. + + Parameters + ---------- + grouped_edges : pd.DataFrame + The timeline edges, with `producer` and `consumer` node ids. + + Returns + ------- + None + + Raises + ------ + UnmappedDatabaseError + If any traversed process is in a database that is not mapped. + """ + node_ids = set(grouped_edges["producer"]).union(grouped_edges["consumer"]) + unmapped = sorted( + node_id + for node_id in node_ids + if node_id != -1 and node_id not in self.nodes + ) + if not unmapped: + return + + examples = {} + for node_id in unmapped: + node = bd.get_node(id=node_id) + examples.setdefault(node["database"], []).append(node["name"]) + + databases = ", ".join( + f"'{database}' (e.g. '{names[0]}'" + + (f", and {len(names) - 1} more" if len(names) > 1 else "") + + ")" + for database, names in examples.items() + ) + first = next(iter(examples)) + raise UnmappedDatabaseError( + f"The graph traversal reached processes in database(s) that are not " + f"mapped to a point in time: {databases}. `bw_timex` places every " + f"traversed process in time via its database, and only the " + f"database(s) holding the functional unit are treated as 'dynamic' " + f"automatically, so a foreground split across several databases has " + f"to mark the other ones itself.\n" + f"If '{first}' is part of your foreground, mark it as dynamic:\n" + f" bw_timex.set_database_metadata('{first}', representative_time='dynamic')\n" + f"If it represents a point in time, give it that date instead:\n" + f" bw_timex.set_database_metadata('{first}', " + f"representative_time=datetime(2030, 1, 1))\n" + f"Databases mapped for this calculation: " + f"{sorted(self.database_dates)}. When passing `database_dates` " + f"explicitly, it must list every database the traversal reaches." + ) + def check_database_names(self) -> None: """ Check that the strings of the databases exist in the databases of the Brightway project. diff --git a/docs/api/errors.md b/docs/api/errors.md new file mode 100644 index 00000000..42c5edd4 --- /dev/null +++ b/docs/api/errors.md @@ -0,0 +1,12 @@ +--- +icon: lucide/triangle-alert +tags: + - api +--- + +# Errors + +Errors raised by `bw_timex`, importable from the top level (e.g. +`from bw_timex import UnmappedDatabaseError`). + +::: bw_timex.errors diff --git a/docs/api/index.md b/docs/api/index.md index a242c0e0..9bd56183 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -15,4 +15,6 @@ The main user-facing class is [`TimexLCA`](timex_lca.md). It orchestrates the ot - [`dynamic_biosphere_builder`](dynamic_biosphere_builder.md) — builds the dynamic biosphere matrix carrying emission timing. - [`edge_extractor`](edge_extractor.md) — extracts and convolves temporal distributions during graph traversal. - [`helper_classes`](helper_classes.md) — supporting data structures used across the package. +- [`database_metadata`](database_metadata.md) — reads and writes what a database represents: its `representative_time` and its scenario. +- [`errors`](errors.md) — the errors `bw_timex` raises. - [`utils`](utils.md) — utility functions. diff --git a/docs/content/background_database_metadata.md b/docs/content/background_database_metadata.md deleted file mode 100644 index 08373653..00000000 --- a/docs/content/background_database_metadata.md +++ /dev/null @@ -1,180 +0,0 @@ ---- -icon: lucide/calendar-clock -tags: - - background databases ---- - -# Time-specific background databases - -`bw_timex` needs to know which point in time each background database stands for. -That information lives in the database's own Brightway metadata, so it only has to -be recorded once - not in every script. - -```python -import bw2data as bd - -bd.databases["ei_cutoff_3.10.1_remind_SSP2-PkBudg500_2050"] -``` - -```python -{ - # written by brightway - "format": "Ecoinvent XML", "backend": "sqlite", "number": 43648, ..., - # written by premise - "premise_version": "2.4.9.2", - "iam_model": "remind", - "pathway": "SSP2-PkBudg500", - "representative_time": "2050-01-01T00:00:00", - "ecoinvent_version": "3.10.1", - "system_model": "cutoff", -} -``` - -Only `representative_time` is required. `TimexLCA` reads it from every database of -your project, so a study on premise databases needs no timing argument at all: - -```python -tlca = TimexLCA(demand={("foreground", "A"): 1}, method=("our", "method")) -``` - -!!! info "premise version" - - Only premise **>= 2.4.9.2** writes this metadata. Databases exported by an - earlier premise carry none of it - set it yourself as shown below, it is a - one-liner per database. - -## Setting it yourself - -For databases you built yourself, use -[`set_database_metadata`][bw_timex.database_metadata.set_database_metadata]: - -```python -from datetime import datetime -from bw_timex import set_database_metadata - -set_database_metadata("background_2020", representative_time=datetime(2020, 1, 1)) -set_database_metadata("background_2030", representative_time=datetime(2030, 1, 1)) -``` - -The value is stored as an ISO 8601 string, because Brightway keeps database -metadata as JSON. You only do this once per database: it is stored in the project, -not in your script. - -Your foreground doesn't represent a point in time - its processes get distributed -over time. `TimexLCA` treats the databases holding your functional unit as -`"dynamic"` automatically, but you can also say so explicitly: - -```python -set_database_metadata("foreground", representative_time="dynamic") -``` - -Only the databases holding the functional unit get that treatment - nothing -inspects exchanges for temporal distributions. So if your foreground is split -across several databases, every one of them that does **not** hold the functional -unit has to be marked itself: - -```python -set_database_metadata("my_intermediate_foreground", representative_time="dynamic") -``` - -A foreground database that is neither marked nor holds the functional unit is -missing from the mapping entirely, and `build_timeline()` fails with a `KeyError` -on the first node it cannot place. - -## Several databases for the same point in time - -More than one database may carry the same date. This is useful when you modify -background processes: keep the modified copies in your own database per point in -time, instead of writing them into ecoinvent or premise. - -```python -set_database_metadata("my_background_2020", representative_time=datetime(2020, 1, 1)) -set_database_metadata("my_background_2030", representative_time=datetime(2030, 1, 1)) -``` - -For each process, `bw_timex` interpolates only between the databases that actually -contain it, matched on `name`, `reference product` and `location`. - -## Choosing a scenario - -A project often holds more than one IAM scenario. `bw_timex` refuses to guess and -tells you what it found: - -``` -Several background scenarios found in this project: - pathway=SSP2-PkBudg500: ei_..._2030, ei_..._2040, ei_..._2050 - pathway=SSP2-Base: ei_..._2030, ei_..._2040, ei_..._2050 -Select one, e.g. scenario={'pathway': '...'}, or map the databases explicitly with -`database_dates`. -``` - -Pick one with the `scenario` argument, which filters the databases on their -metadata: - -```python -tlca = TimexLCA( - demand={("foreground", "A"): 1}, - method=("our", "method"), - scenario={"pathway": "SSP2-PkBudg500"}, -) -``` - -Any metadata key works - `iam_model`, `pathway`, `system_model`, -`ecoinvent_version`, `premise_version`, or anything you set yourself. Databases -that don't carry the key at all (your foreground, your own vintages) are never -filtered out. A key no database declares, or a filter that matches nothing, is -also an error rather than a silent empty result - `bw_timex` reports what it -actually found so you can spot a typo. - -Comparing scenarios is then a loop over filters: - -```python -scores = {} -for pathway in ("SSP2-Base", "SSP2-PkBudg500"): - tlca = TimexLCA(demand, method, scenario={"pathway": pathway}) - tlca.build_timeline() - tlca.lci() - tlca.static_lcia() - scores[pathway] = tlca.static_score -``` - -!!! warning "Superstructure databases" - - Databases holding several scenarios at once (premise superstructure or - scenario-array exports) are skipped: they have no single technosphere per point - in time. Use one database per scenario and year. - -## Mapping the databases explicitly - -`database_dates` still does what it always did, and takes over completely: when you -pass it, metadata is not read at all and only the databases you list are used. It -cannot be combined with `scenario`. - -```python -tlca = TimexLCA( - demand={("foreground", "A"): 1}, - method=("our", "method"), - database_dates={ - "background": datetime(2020, 1, 1), - "background_2030": datetime(2030, 1, 1), - "foreground": "dynamic", - }, -) -``` - -Use it when you want to restrict a calculation to a subset of the databases in your -project, or when a database's metadata is wrong and you don't want to change it. - -Without it, `TimexLCA` reads metadata from and loads node data for *every* -registered database that carries `representative_time`, including ones your demand -does not actually depend on. At premise scale, that is not just setup time: every -database that ends up in `database_dates_static` becomes a candidate producer for -temporal market interpolation, so an unrelated study's vintages sitting in the same -project can change your results, not only how long setup takes. `bw_timex` guards -against the case where this goes visibly wrong - the [ambiguous-scenario -check](#choosing-a-scenario) and the same-date collision error raised when two -databases hold the same producer at the same date - but it cannot catch a -same-named, same-dated producer that is a legitimate, silent match. Narrow the -selection down with `scenario`, or bypass metadata resolution entirely with an -explicit `database_dates`, whenever your project holds background data you don't -want considered. diff --git a/docs/content/getting_started/adding_temporal_information.md b/docs/content/getting_started/adding_temporal_information.md index 8f4826d2..f7b4c0c2 100644 --- a/docs/content/getting_started/adding_temporal_information.md +++ b/docs/content/getting_started/adding_temporal_information.md @@ -266,9 +266,21 @@ for databases from an earlier premise, set it yourself as above. The foreground represent a specific point in time and is distributed over time instead; `bw_timex` treats the databases holding your functional unit that way automatically. +!!! tip "Foreground split across several databases" + + Only the databases holding the functional unit become dynamic automatically. Mark + any other foreground database yourself: + + ```python + set_database_metadata("my_intermediate_foreground", representative_time="dynamic") + ``` + + Otherwise `build_timeline()` raises an `UnmappedDatabaseError`, naming the database + it could not place in time. + !!! tip "Data sources" - You can use whatever data source you want for the time-specific process data. A nice package from the Brightway cosmos that can help you is [premise](https://premise.readthedocs.io/en/latest/introduction.html). + You can use whatever data source you want for the time-specific process data. [premise](https://premise.readthedocs.io/en/latest/introduction.html) is a nice package from the Brightway cosmos, but you can also use any custom scenario. ### Several databases for the same point in time diff --git a/docs/content/getting_started/build_process_timeline.md b/docs/content/getting_started/build_process_timeline.md index 54ddaf56..00d800ab 100644 --- a/docs/content/getting_started/build_process_timeline.md +++ b/docs/content/getting_started/build_process_timeline.md @@ -23,10 +23,36 @@ tlca = TimexLCA( The metadata is written by premise >= 2.4.9.2, or by you with `set_database_metadata` (see [Step 1](adding_temporal_information.md)). -If your project holds several scenarios, select one with -`scenario={"pathway": "SSP2-PkBudg500"}`; to map the databases by hand instead, pass -`database_dates`. Both are covered in -[Time-specific background databases](../background_database_metadata.md). + +If your project holds several IAM scenarios, say which one you want - `bw_timex` lists +what it found rather than guessing: + +```python +tlca = TimexLCA( + demand={("foreground", "A"): 1}, + method=("our", "method"), + scenario={"pathway": "SSP2-PkBudg500"}, +) +``` + +Any metadata key filters (`iam_model`, `pathway`, `system_model`, ...), and databases +that don't carry it - your foreground, your own vintages - are kept. Comparing scenarios +is the same script in a loop over filters. + +!!! tip "Mapping the databases by hand" + + `database_dates` maps database names to dates yourself and replaces the metadata + entirely: + + ```python + tlca = TimexLCA(demand, method, database_dates={ + "background": datetime(2020, 1, 1), + "background_2030": datetime(2030, 1, 1), + "foreground": "dynamic", + }) + ``` + + Handy to restrict a calculation to a subset of your project. Using our new `tlca` object, we can now build the timeline of processes that leads to our functional unit "A". If not specified otherwise, it's assumed that the demand occurs in the current year. In our case, we're specifying the time of demand to the year 2024, with the attribute 'starting_datetime`.. Building the timeline is very simple: ```python diff --git a/docs/content/getting_started/quickstart.md b/docs/content/getting_started/quickstart.md index e860237e..c1b292d4 100644 --- a/docs/content/getting_started/quickstart.md +++ b/docs/content/getting_started/quickstart.md @@ -6,7 +6,7 @@ tags: # Quick Start -A condensed reference for using `bw_timex`. For a step-by-step introduction, see the [Walkthrough](index.md). For the underlying framework, see the [Theory](../theory.md) page. For more, see the [Examples](../examples/index.md) or the [API Reference](../../api/index.md). +Condensed reference for `bw_timex`. For a step-by-step introduction, see the [Walkthrough](index.md). For the underlying framework, see the [Theory](../theory.md) page. For more, see the [Examples](../examples/index.md) or the [API Reference](../../api/index.md). --- @@ -128,8 +128,9 @@ e.g. for the timing of the functional unit itself. Relative dates (`dtype="timedelta64[Y]"`) are relative to the consuming process. Several databases may represent the same point in time, e.g. if you keep modified copies of background processes in their own database instead of writing them into the shared vintage. See -[Time-specific background databases](../background_database_metadata.md) for scenario selection -and for mapping databases explicitly with `database_dates`. +[Step 1](adding_temporal_information.md) for the database metadata, and +[Step 2](build_process_timeline.md) for scenario selection and for mapping databases +explicitly with `database_dates`. --- diff --git a/tests/conftest.py b/tests/conftest.py index d66c76d9..54a6d45b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -47,3 +47,4 @@ zero_weight_first_background_td_db, ) from .fixtures.vehicle_explicit_db_fixture import vehicle_explicit_db +from .fixtures.split_foreground_db_fixture import split_foreground_db diff --git a/tests/fixtures/split_foreground_db_fixture.py b/tests/fixtures/split_foreground_db_fixture.py new file mode 100644 index 00000000..fb305666 --- /dev/null +++ b/tests/fixtures/split_foreground_db_fixture.py @@ -0,0 +1,93 @@ +import bw2data as bd +import numpy as np +import pytest +from bw2data.tests import bw2test +from bw_timex import TemporalDistribution + + +@pytest.fixture +@bw2test +def split_foreground_db(): + """A foreground split across two databases, only one of which holds the FU. + + ``foreground`` holds the functional unit, which consumes an intermediate + process living in a *second* foreground database, + ``intermediate_foreground``. That second database represents no point in + time either, but nothing marks it as dynamic automatically: only the + database holding the functional unit gets that treatment. Unless the user + marks it (or lists it in ``database_dates``), it is missing from the + mapping and its nodes cannot be placed in time. + """ + bd.Database("bio").write( + {("bio", "co2"): {"name": "carbon dioxide", "unit": "kg", "type": "emission"}} + ) + bd.Method(("GWP", "example")).write([(("bio", "co2"), 1.0)]) + + for year, co2 in (("2020", 10), ("2030", 5)): + bd.Database(f"background_{year}").write( + { + (f"background_{year}", "electricity"): { + "name": "electricity", + "unit": "kWh", + "location": "GLO", + "reference product": "electricity", + "exchanges": [ + { + "input": (f"background_{year}", "electricity"), + "amount": 1, + "type": "production", + }, + {"input": ("bio", "co2"), "amount": co2, "type": "biosphere"}, + ], + } + } + ) + + bd.Database("intermediate_foreground").write( + { + ("intermediate_foreground", "assembly"): { + "name": "assembly", + "unit": "unit", + "location": "GLO", + "reference product": "assembly", + "exchanges": [ + { + "input": ("intermediate_foreground", "assembly"), + "amount": 1, + "type": "production", + }, + { + "input": ("background_2020", "electricity"), + "amount": 2, + "type": "technosphere", + }, + ], + } + } + ) + + bd.Database("foreground").write( + { + ("foreground", "fu"): { + "name": "fu", + "unit": "unit", + "location": "GLO", + "reference product": "fu", + "exchanges": [ + {"input": ("foreground", "fu"), "amount": 1, "type": "production"}, + { + "input": ("intermediate_foreground", "assembly"), + "amount": 1, + "type": "technosphere", + "temporal_distribution": TemporalDistribution( + date=np.array([5], dtype="timedelta64[Y]"), + amount=np.array([1.0]), + ), + }, + ], + } + } + ) + + for db in bd.databases: + bd.Database(db).process() diff --git a/tests/test_unmapped_database.py b/tests/test_unmapped_database.py new file mode 100644 index 00000000..5a6e3dd7 --- /dev/null +++ b/tests/test_unmapped_database.py @@ -0,0 +1,87 @@ +"""A database that is reached by the traversal but is missing from the mapping. + +Only the databases holding the functional unit are treated as `"dynamic"` +automatically. A second foreground database - an intermediate one, which does +not hold the functional unit - is therefore missing from the mapping unless the +user marks it, and its nodes cannot be placed in time. +""" + +from datetime import datetime + +import pytest + +from bw_timex import TimexLCA, set_database_metadata +from bw_timex.errors import UnmappedDatabaseError + +DATABASE_DATES = { + "foreground": "dynamic", + "background_2020": datetime(2020, 1, 1), + "background_2030": datetime(2030, 1, 1), +} + + +def _set_background_metadata(): + set_database_metadata("background_2020", representative_time=datetime(2020, 1, 1)) + set_database_metadata("background_2030", representative_time=datetime(2030, 1, 1)) + + +@pytest.mark.usefixtures("split_foreground_db") +class TestUnmappedDatabase: + + def test_explicit_database_dates_raise_unmapped_database_error(self): + tlca = TimexLCA( + demand={("foreground", "fu"): 1}, + method=("GWP", "example"), + database_dates=DATABASE_DATES, + ) + with pytest.raises(UnmappedDatabaseError) as excinfo: + tlca.build_timeline() + assert "intermediate_foreground" in str(excinfo.value) + + def test_error_names_an_affected_process(self): + tlca = TimexLCA( + demand={("foreground", "fu"): 1}, + method=("GWP", "example"), + database_dates=DATABASE_DATES, + ) + with pytest.raises(UnmappedDatabaseError, match="assembly"): + tlca.build_timeline() + + def test_error_points_at_the_fix(self): + tlca = TimexLCA( + demand={("foreground", "fu"): 1}, + method=("GWP", "example"), + database_dates=DATABASE_DATES, + ) + with pytest.raises(UnmappedDatabaseError, match="set_database_metadata"): + tlca.build_timeline() + + def test_metadata_resolution_raises_the_same_error(self): + _set_background_metadata() + tlca = TimexLCA(demand={("foreground", "fu"): 1}, method=("GWP", "example")) + with pytest.raises(UnmappedDatabaseError, match="intermediate_foreground"): + tlca.build_timeline() + + def test_marking_the_database_dynamic_fixes_it(self): + _set_background_metadata() + set_database_metadata("intermediate_foreground", representative_time="dynamic") + tlca = TimexLCA(demand={("foreground", "fu"): 1}, method=("GWP", "example")) + tlca.build_timeline() + assert "assembly" in set(tlca.timeline["producer_name"]) + + def test_listing_the_database_in_database_dates_fixes_it(self): + tlca = TimexLCA( + demand={("foreground", "fu"): 1}, + method=("GWP", "example"), + database_dates={**DATABASE_DATES, "intermediate_foreground": "dynamic"}, + ) + tlca.build_timeline() + assert "assembly" in set(tlca.timeline["producer_name"]) + + def test_unmapped_database_error_is_a_value_error(self): + assert issubclass(UnmappedDatabaseError, ValueError) + + def test_unmapped_database_error_is_exposed_at_top_level(self): + import bw_timex + + assert bw_timex.UnmappedDatabaseError is UnmappedDatabaseError diff --git a/zensical.toml b/zensical.toml index 9039539b..b84763f7 100644 --- a/zensical.toml +++ b/zensical.toml @@ -29,7 +29,6 @@ nav = [ { "Step 3 - Calculating the time-explicit LCI" = "content/getting_started/time_explicit_lci.md" }, { "Step 4 - Impact assessment" = "content/getting_started/lcia.md" }, ]}, - { "What a database represents" = "content/background_database_metadata.md" }, { "What LCA should I do?" = "content/decisiontree.md" }, ]}, { Theory = "content/theory.md" }, @@ -61,6 +60,7 @@ nav = [ { "Helper Classes" = "api/helper_classes.md" }, { Utils = "api/utils.md" }, { "Database metadata" = "api/database_metadata.md" }, + { Errors = "api/errors.md" }, ]}, ]