History and Snapshot System¶
Overview¶
The flow cartogram algorithm runs for up to hundreds of iterations. The history system provides two complementary mechanisms for tracking what happened:
ConvergenceHistoryrecords four scalar error metrics at every iteration. It is cheap to maintain and supports full convergence analysis without large memory overhead.HistoryholdsCartogramSnapshotobjects at selected iterations. Each snapshot includes the full set of morphed geometries and per-geometry error arrays. Snapshots are expensive relative to scalar records, so they are saved selectively.
These two mechanisms are independent and serve different purposes: ConvergenceHistory is for plotting and detecting convergence trends; History snapshots are for examining or exporting the deformed geometries at specific points in the run.
Both are stored on the Cartogram result object. A third variant, CartogramInternalsSnapshot, captures raw grid-level fields for debugging and is stored separately in Cartogram.internals.
Relevant source files: history.py, cartogram.py.
Snapshot Class Hierarchy¶
BaseSnapshot (abstract)
├── CartogramSnapshot — user-facing morphed state
└── CartogramInternalsSnapshot — grid-level debug state
BaseSnapshot¶
BaseSnapshot is the abstract interface required by the History container. Any snapshot class that implements this interface can be stored in a History object.
Required interface:
- iteration: int — mandatory field identifying which iteration the snapshot belongs to
- has_variable(name: str) -> bool — returns True if the named attribute exists and is not None
- get_variable(name: str) -> Any — retrieves the attribute value
- get_all_variables() -> dict — returns all non-None fields as a dictionary
CartogramSnapshot (External Snapshots)¶
Captures the user-facing algorithm state at a saved iteration. Fields:
| Field | Type | Description |
|---|---|---|
iteration |
int |
Iteration number |
geometry |
sequence of geometries | Deformed polygon geometries at this iteration |
landmarks |
sequence of geometries or None |
Deformed landmark geometries, if landmarks were provided |
coords |
array-like or None |
Deformed coordinates, in the same format as the input |
errors |
MorphErrors or None |
Full error metrics including per-geometry log₂ arrays and scalar aggregates |
density |
np.ndarray or None |
Per-geometry density values at this iteration |
MorphErrors holds both per-geometry arrays (log_errors, errors_pct) and scalars (mean_log_error, max_log_error, mean_error_pct, max_error_pct).
CartogramInternalsSnapshot (Internal Snapshots)¶
Captures the raw grid-level state used by the FFT solver. Created only when MorphOptions.save_internals = True. Fields:
| Field | Type | Description |
|---|---|---|
iteration |
int |
Iteration number |
rho |
np.ndarray |
Density field on the grid, shape (ny, nx) |
vx, vy |
np.ndarray |
Effective velocity components after any modulation, shape (ny, nx) |
geometry_mask |
np.ndarray |
Cell-to-geometry assignment: value \(k\) means the cell is inside geometry \(k\); \(-1\) means exterior, shape (ny, nx) |
Internal snapshots are stored in Cartogram.internals (a separate History instance), not in Cartogram.snapshots. This keeps external and internal histories independent.
History Container¶
History is a generic ordered container for any BaseSnapshot subclass. It supports several access patterns:
Index access:
history[0] # first snapshot
history[-1] # most recent snapshot
history[1:3] # slice returning a list
Iteration-based lookup:
history.get_snapshot(iteration_num) # returns snapshot or None
history.get_variable_at_iteration('errors', 50) # variable value at specific iteration
Extracting a variable across all snapshots:
Convenience:
history.latest() # most recent snapshot
history.get_iterations() # sorted list of all iteration numbers
ConvergenceHistory¶
ConvergenceHistory stores scalar error metrics for every iteration using pre-allocated NumPy arrays. Memory is approximately 40 bytes per iteration, independent of the number of geometries.
Stored arrays:
| Array | Description |
|---|---|
iterations |
Iteration numbers |
mean_log_errors |
Mean log₂ area error across all geometries |
max_log_errors |
Maximum log₂ area error |
mean_errors_pct |
Mean percentage area error |
max_errors_pct |
Maximum percentage area error |
Access patterns:
convergence[i] # returns ErrorRecord for index i
convergence.get_by_iteration(50) # lookup by iteration number
convergence.to_dict() # returns dict of arrays (for plotting)
ErrorRecord is a lightweight dataclass with five fields: iteration, mean_log_error, max_log_error, mean_error_pct, max_error_pct.
ConvergenceHistory calls finalize() internally at the end of the algorithm run to trim the pre-allocated arrays to the actual number of iterations completed.
When Snapshots Are Taken¶
The algorithm loop in morph_geometries() controls when snapshots are created:
ConvergenceHistory: updated at every iteration, unconditionally.CartogramSnapshot: created at:- every
snapshot_everyiterations (if the option is set; default isNone, meaning periodic saving is disabled) - the final iteration, regardless of termination reason (convergence, stall, or iteration limit)
CartogramInternalsSnapshot: created at the same moments asCartogramSnapshot, but only ifsave_internals=True.
This design means that a Cartogram object always contains at minimum one CartogramSnapshot (the final state) and a complete ConvergenceHistory.