Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 7 additions & 18 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,13 @@ 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 `TimexLCASettings`, which holds everything one calculation needs - the demand, the method, the background selection, and every timeline, LCI and LCIA option - so it doubles as the record of what was run. `TimexLCA.from_settings(settings)` builds an object from one, and `TimexLCA.run()` executes the whole pipeline (`build_timeline()`, `lci()`, `static_lcia()`, and `dynamic_lcia()` unless disabled)
* Added repeated `TimexLCA.run()` calls on one object, with per-run overrides (`tlca.run(time_horizon=20)`) that leave the settings unmodified. The background caches, and the timeline itself where its parameters are unchanged, are reused between calls; only a changed demand or method recalculates the base LCA. Changing the background databases is refused, since they fix the columns of the time-explicit matrices
* Added `TimexLCA.compare([settings, ...])` for scenario comparison, returning a `ComparisonResult` whose `summary` DataFrame holds one row per calculation: its scores, the settings behind them, `scenario_*` columns, timeline size, and runtime. It builds one `TimexLCA` per distinct background and runs every calculation sharing that background on it, so a scenario × demand grid only pays for a new object when the background actually changes. `keep_objects=True` keeps the objects for digging into single results; `on_error="record"` puts failures in the table instead of aborting a long sweep
* Removed two noisy log messages: the per-producer warning that a producer was not found in every time-explicit vintage (it fires once per producer, and a background that doesn't cover every producer is normal - the resulting temporal market shares are on the timeline either way), and the `lci()` note listing the mapped databases the timeline does not source from
* Added `TimexLCA(scenario={..., "years": [...]}, create_missing=True)`, which builds background databases the scenario names but the project does not hold, using premise (optional extra: `pip install "bw_timex[premise]"`). Only missing years are built, ecoinvent is imported first if absent, and credentials are read from `PREMISE_KEY` / `ECOINVENT_USERNAME` / `ECOINVENT_PASSWORD` unless passed explicitly. Also available standalone as `bw_timex.ensure_scenario_databases`
* Added `lci(group_background_by_time=...)`, selecting how the background unit LCIs are solved: `None` (default) picks whichever strategy needs fewer solves, `True` forces per-time-step solving, `False` forces per-process solving. Pin it to `False` when re-running `lci()` several times in one session - grouped right-hand sides are sums specific to a run and are never cached, while per-process unit LCIs are, which makes every run after the first free. Requesting `True` where it cannot apply logs a warning and is ignored
* Added per-time-step background solving to `lci(expand_technosphere=False, keep_activity_dimension=False)`. With no activity dimension every temporal-market row landing at the same point in time is summed into the same column anyway, so their background demands can be summed *before* the solve rather than after (`sum_r B A^-1 d_r` = `B A^-1 sum_r d_r`). That trades one solve per distinct background process for one solve per `(time, block)` pair - a market interpolates between vintages in different databases, so a grouped demand is split across the blocks it touches. Which is cheaper is a property of the model, so `lci()` counts both and takes the smaller: on a 123-process foreground with a yearly grouping it is 174 solves against 37, while on the premise EV case (a three-node foreground) per-process stays ahead at 16 against 38, and a warm unit-LCI cache always wins because grouped right-hand sides cannot be cached. On a wide foreground over a premise background (80 distinct background targets, 43k-node vintages) that is 160 solves against 15 (yearly) or 28 (monthly), worth ~1.3-2.5x on cold-run `lci()` wall clock; on models whose background is small the solves are cheap enough that fewer of them changes nothing. Scores, emission timing and the dynamic inventory are unchanged; only the row order of `dynamic_inventory` differs, since the grouped entries are emitted after the timeline loop
* Reduced the runtime of `lci()` on timelines that reuse background processes, by memoizing the *translated* background unit LCIs rather than only their cached payloads. The supply/aggregate caches store node-id-keyed payloads so they can be shared between solvers, but every read had to scatter one back into the reading solver's index space - a `searchsorted` plus a dense allocation - and `lci(expand_technosphere=False)` asks for the same background activity once per timeline row that consumes it. On a 300-activity block read 40 times over, that walk drops from 3.4 s to 1.3 s with the same 300 solves. Aggregates are memoized on first sight; supply columns, which are an order of magnitude larger and read exactly once by the matrix build, only from the second request, so the memory-bound path pays nothing for the memo that `disaggregate_background_lci()` benefits from
* Reduced the runtime of background unit LCIs by memoizing each block's slice of the biosphere matrix, which `unit_aggregate` previously rebuilt on every background activity (0.216 s per 300 activities, against 0.001 s for a single slice). Results are unchanged
* Reduced runtime and memory of `lci()` by solving background unit LCIs against the database they come from instead of the whole time-explicit matrix. A time-explicit technosphere is block lower triangular - the new foreground, temporalized and temporal-market columns sit on top of unmodified copies of the background databases - so each background unit LCI only needs its own block. On a premise EV case (262k time-explicit columns) `lci()` drops from ~25 s to ~22 s, with identical scores; individual background solves go from 0.174 s to 0.048 s
* Reduced the memory of background LCI caching by an order of magnitude: the cache now holds the supply column of a background process (0.35 MB, ~19k nonzeros on ecoinvent) instead of its full inventory matrix `B @ diag(x)` (5.5 MB, ~457k nonzeros). The disaggregated background inventory of a temporal market is rebuilt from those columns when `disaggregate_background_lci()` asks for it, which costs ~6 ms per market and is what previously made large time-explicit systems run out of memory. Results are unchanged
* Fixed background unit LCIs being cached under a key that could not tell a background process from its time-explicit copy (`traverse_background=True`), where a copy could be served the original's supply column
* 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 pruning of unused vintages in `lci()`: only the databases the timeline actually sources from (plus the dynamic ones, the demand's own, and the traversed processes') are loaded into the time-explicit matrices. Vintages that get no temporal market share - e.g. a 2050 database for a system ending in 2042, or another study's databases in the same project - contributed no matrix entries but still added their processes as columns. Scores are unchanged; on a premise EV case study with three unused databases, the expanded technosphere shrank from 262,141 to 174,758 columns and `lci()` from 31.3 s to 23.1 s
* 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))
* Added `TimexLCA.run()`, which executes the whole pipeline (`build_timeline()`, `lci()`, `static_lcia()` and `dynamic_lcia()`) in one call, taking every argument of the four stages, either explicitly or through a `TimexLCASettings` object ([#223](https://github.com/brightway-lca/bw_timex/pull/223))
* Added `TimexLCASettings`, which holds everything one calculation needs - the demand, the method, the background selection, and every timeline, LCI and LCIA option - so it doubles as the record of what was run. Pass one straight to the constructor, `TimexLCA(settings)`, and `run()` needs no arguments ([#223](https://github.com/brightway-lca/bw_timex/pull/223))
* Added a `__repr__` for `TimexLCA` ([#223](https://github.com/brightway-lca/bw_timex/pull/223))
* Added `TimexLCA.compare([settings, ...])` for scenario comparison, returning a `ComparisonResult` whose `summary` DataFrame holds one row per calculation: its scores, the settings behind them, `scenario_*` columns, timeline size, and runtime ([#223](https://github.com/brightway-lca/bw_timex/pull/223))
* Added `TimexLCA(scenario={..., "years": [...]}, create_missing=True)`, which builds background databases the scenario names but the project does not hold, using premise (optional extra: `pip install "bw_timex[premise]"`). Only missing years are built, ecoinvent is imported first if absent, and credentials are read from `PREMISE_KEY` / `ECOINVENT_USERNAME` / `ECOINVENT_PASSWORD` unless passed explicitly. Also available standalone as `bw_timex.ensure_scenario_databases` ([#222](https://github.com/brightway-lca/bw_timex/pull/222))
* 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)). Multiple scenarios can be differentiated through a `scenario` metadata field. Database metadata can also be set through new utility function `set_database_metadata` ([#222](https://github.com/brightway-lca/bw_timex/pull/222))
* Added batched solve for background processes by first collecting demands and solving inventory with diagonalized demand vector ([#220](https://github.com/brightway-lca/bw_timex/pull/220))

## [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))
Expand Down
Loading
Loading