diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5b931f519..5f170d2a2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ repos: - repo: https://github.com/astral-sh/uv-pre-commit # uv version. - rev: 0.11.19 + rev: 0.11.23 hooks: # Dependency management - id: uv-lock @@ -47,7 +47,7 @@ repos: # Python Linting & Formatting with Ruff - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: "v0.15.16" + rev: "v0.15.18" hooks: - id: ruff name: ruff (linter) diff --git a/docs/source/computational_implementation.rst b/docs/source/computational_implementation.rst index 023964f3c..943452766 100644 --- a/docs/source/computational_implementation.rst +++ b/docs/source/computational_implementation.rst @@ -320,6 +320,11 @@ dimensionality of 3, and (following the :ref:`naming scheme :code:`region`, the second an element of :code:`time_optimize`, and third a demand commodity. +.. seealso:: + The same component pattern (index set, parameter, and constraint declared in a + ``model.py`` and implemented in component modules) can be packaged as an + optional, self-contained add-on. See :ref:`extensions` for the extension + framework and a copy-from template. diff --git a/docs/source/extensions.rst b/docs/source/extensions.rst new file mode 100644 index 000000000..47c0fd7d8 --- /dev/null +++ b/docs/source/extensions.rst @@ -0,0 +1,248 @@ +.. _extensions: + +Extension Framework +=================== + +Temoa includes a lightweight framework for adding **optional model components** +without modifying the core model. An extension can contribute its own database +tables, Pyomo components (sets, parameters, and constraints), and data-loading +rules. Extensions are declared once and then enabled per run through +configuration. + +This page describes how the framework works and how to author a new extension. +A ready-to-copy scaffold lives at ``temoa/extensions/template``. + +Overview +-------- + +Use an extension when you want to add modeling capability that is: + +* **Optional** -- only active when explicitly enabled. +* **Self-contained** -- owns its own tables and model components. +* **Non-invasive** -- adds to the core model rather than editing it. + +If a feature is fundamental to every Temoa run, it belongs in +``temoa/components`` (a core component) instead of an extension. + +Each extension is described by a single :class:`ExtensionSpec` (declarative +metadata plus hook functions). The spec is registered with the framework, after +which users can enable the extension by id. + +Lifecycle +--------- + +When a run is configured with ``extensions = ["..."]``, the framework threads the +enabled specs through configuration, model construction, and data loading: + +.. code-block:: text + + config: extensions = ["my_ext"] + | + v + resolve_extension_specs() validate ids -> ExtensionSpec list + | + v + TemoaModel(extensions=[...]) + | + +--> apply_model_extension_hooks() + | calls spec.register_model_components(model) + | -> attaches Params / Sets / Constraints to the model + | + v + HybridLoader + +--> ensure_enabled_extension_tables_exist() (offer to append schema) + +--> assert_disabled_extension_tables_are_empty() + +--> merge_regional_group_tables() + +--> build_manifest() -> appends spec.build_manifest_items(model) + -> loads each owned table into its component + +The relevant code lives in :mod:`temoa.extensions.framework`, +:mod:`temoa.core.model`, and :mod:`temoa.data_io.hybrid_loader`. + +``ExtensionSpec`` reference +--------------------------- + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Field + - Purpose + * - ``extension_id`` + - Unique, lowercase id. This is what users put in ``extensions = [...]``. + * - ``owned_tables`` + - Tuple of database tables owned exclusively by this extension. Used by the + disabled/enabled table guards. + * - ``regional_group_tables`` + - Map of ``table -> column`` for tables whose region column may hold a + regional *group* name. Merged into the loader's regional-group handling. + * - ``register_model_components`` + - Hook ``Callable[[TemoaModel], None]`` that attaches model components. + * - ``build_manifest_items`` + - Hook ``Callable[[TemoaModel], list[LoadItem]]`` describing how to load the + extension's data. + * - ``schema_sql_path`` + - Path to a ``.sql`` file applied (with consent) when the extension is + enabled but its tables are missing. + * - ``fail_if_tables_populated_when_disabled`` + - When ``True``, loading fails if the extension is disabled but its owned + tables contain data, preventing silently-ignored inputs. + +Recommended package layout +-------------------------- + +Mirror the structure of the core model (``temoa/core`` + ``temoa/components``) so +extension code is organized the same way as the rest of the codebase: + +.. code-block:: text + + temoa/extensions// + __init__.py # re-export the ExtensionSpec + extension.py # the ExtensionSpec definition + data_manifest.py # build_manifest_items() + tables.sql # CREATE TABLE IF NOT EXISTS for owned tables + core/ + __init__.py + model.py # typing subtype + register_model_components() + components/ + __init__.py + .py # one module per constraint family + +Centralize the component *declarations* in ``core/model.py`` (just as +``temoa/core/model.py`` does) and keep the index-set and constraint-rule logic in +``components/`` modules (just as ``temoa/components`` does). + +.. _extensions-typing: + +The typing pattern +------------------ + +Core model components are declared as attribute assignments inside +``TemoaModel.__init__`` (for example ``self.time_optimize = Set(...)``), which is +why the type checker knows about them. An extension instead adds attributes from +*outside* the class, so without help the type checker reports +``"TemoaModel" has no attribute ...`` and provides no autocomplete. + +Three rules make typing carry over cleanly: + +1. **Declare a ``TYPE_CHECKING``-only subtype.** In ``core/model.py``, define a + subclass of ``TemoaModel`` that annotates every component the extension adds. + It inherits all core attributes, so component code sees both core and + extension members. + + .. code-block:: python + + if TYPE_CHECKING: + from temoa.core.model import TemoaModel + + class ExampleModel(TemoaModel): + example_new_capacity_limit: Param + example_new_capacity_limit_constraint_rpt: Set + example_new_capacity_limit_constraint: Constraint + +2. **Annotate component functions with the subtype.** Index-set and + constraint-rule functions take ``model: ExampleModel``. This restores both + mypy coverage and editor autocomplete. + +3. **Keep spec hooks on the base type and ``cast`` internally.** Functions stored + on the ``ExtensionSpec`` (``register_model_components`` and + ``build_manifest_items``) must keep ``model: TemoaModel`` to match the hook + callable types. Narrowing the parameter is a contravariance error. ``cast`` + once at the top: + + .. code-block:: python + + def register_model_components(model: TemoaModel) -> None: + m = cast('ExampleModel', model) + m.example_new_capacity_limit = Param(...) + +.. note:: + Extension attribute names share the single ``TemoaModel`` namespace at + runtime. Keep them unique across extensions to avoid collisions. + +Adding a new extension +---------------------- + +#. **Copy the template.** Duplicate ``temoa/extensions/template`` to + ``temoa/extensions//``. +#. **Rename ids and tables.** Update ``extension_id``, ``owned_tables``, + ``regional_group_tables``, params, sets, and constraints to your domain. +#. **Declare components.** Add annotations to the typing subtype in + ``core/model.py`` and create the components in ``register_model_components``. +#. **Write the constraint logic.** Add index-set and rule functions under + ``components/`` and annotate them with your subtype. +#. **Describe data loading.** Add one ``LoadItem`` per owned table in + ``data_manifest.py``. +#. **Define the schema.** Add a ``CREATE TABLE IF NOT EXISTS`` per owned table to + ``tables.sql``. +#. **Register the spec.** Import your spec in + :func:`temoa.extensions.framework.get_known_extension_specs` and add it to the + ``specs`` list. (Until you do this, the extension is inert -- enabling it + raises an "Unknown extension id" error.) +#. **Enable it.** Add the id to your configuration TOML: + + .. code-block:: toml + + extensions = [""] + +Data loading and ``LoadItem`` +----------------------------- + +``build_manifest_items`` returns one :class:`temoa.data_io.loader_manifest.LoadItem` +per database table the extension reads. Common fields: + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Field + - Purpose + * - ``component`` + - The Pyomo ``Set`` or ``Param`` to populate. + * - ``table`` + - Source table name in the database. + * - ``columns`` + - Columns to select; for a ``Param`` the final column is the value. + * - ``index_length`` + - Number of leading columns that form the index. + * - ``validator_name`` / ``validation_map`` + - Source-trace validation: a viable-set name on the loader and which index + columns it applies to. + * - ``is_table_required`` + - Set ``False`` for optional extension inputs so a missing table is not an + error. + +Verification +------------ + +After authoring an extension, confirm: + +#. **Types** -- ``mypy temoa/extensions/`` reports no issues. +#. **Imports** -- ``python -c "import temoa.extensions..extension"``. +#. **Wiring** -- a model built with the extension enabled attaches the expected + components, and the test suite passes. + +The template extension +----------------------- + +``temoa/extensions/template`` is a complete, type-checked, but deliberately +**unregistered** scaffold. Because it is not listed in +``get_known_extension_specs``, it cannot be enabled until you register it, so it +never affects normal runs. Copy the folder as the starting point for a new +extension; every file carries ``# TEMPLATE:`` comments explaining what to change. + +.. _extension-catalog: + +Available extensions +-------------------- + +The extensions that ship with Temoa are documented on their own pages below. +Each page describes the extension's parameters and constraints. This list grows +as new extensions are added. + +.. toctree:: + :maxdepth: 1 + + extensions/growth_rates + extensions/discrete_capacity + extensions/eos diff --git a/docs/source/extensions/discrete_capacity.rst b/docs/source/extensions/discrete_capacity.rst new file mode 100644 index 000000000..aa3d988bc --- /dev/null +++ b/docs/source/extensions/discrete_capacity.rst @@ -0,0 +1,93 @@ +.. _extension-discrete-capacity: + +Discrete Capacity +================================ + +The **discrete_capacity** extension adds optional constraints that force the +capacity of a technology or technology group to be deployed in discrete, +indivisible units rather than as a continuous quantity. This is useful for +modeling lumpy investments or retirements such as a power plant, reactor, or +other facility that can only exist in whole units of a fixed size. + +Enabling the extension introduces integer decision variables, which turns the +model into a mixed-integer program (MIP). It is disabled by default and enabled +per run through configuration: + +.. code-block:: toml + + extensions = ["discrete_capacity"] + +Its parameters live in the extension-owned tables of the same name and are loaded +only when the extension is enabled. See :ref:`extensions` for how the extension +framework wires these components into the model. + +Parameters +---------- + +.. csv-table:: + :header: "Parameter", "Database Table", "Model Element", "Notes" + :widths: 15, 20, 25, 40 + + ":math:`\text{LDNC}_{r,t}`", ":code:`limit_discrete_new_capacity`", ":code:`limit_discrete_new_capacity`", "unit size for new capacity additions; new capacity is forced to an discrete multiple of this size; :code:`tech_or_group` column accepts a technology name or group name" + ":math:`\text{LDC}_{r,t}`", ":code:`limit_discrete_capacity`", ":code:`limit_discrete_capacity`", "unit size for total available (retirement-adjusted) capacity; net capacity is forced to an integer multiple of this size; :code:`tech_or_group` column accepts a technology name or group name" + + +limit_discrete_new_capacity +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{LDNC}_{r \in R, t \in T}` + +The :code:`limit_discrete_new_capacity` parameter specifies the unit size for new +capacity additions of a technology (or group). The new capacity built in each +vintage is forced to be an integer multiple of this unit size. The +:code:`tech_or_group` column accepts a technology name or group name, and the +:code:`capacity` column gives the size of a single unit. + + +limit_discrete_capacity +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{LDC}_{r \in R, t \in T}` + +The :code:`limit_discrete_capacity` parameter specifies the unit size for the +total available (retirement-adjusted) capacity of a technology (or group). The +net capacity available in each period is forced to be an integer multiple of this +unit size. The :code:`tech_or_group` column accepts a technology name or group +name, and the :code:`capacity` column gives the size of a single unit. + + +Decision Variables +------------------ + +Enabling the extension adds two integer decision variables, one per constraint. +Each counts the number of discrete units, which is multiplied by the +corresponding unit size to recover the capacity quantity. + +v_discrete_new_capacity +~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`DNCAP_{r, t, v}` + +The number of discrete units of new capacity built for technology (or group) +:math:`t` in region :math:`r` and vintage :math:`v`. It is restricted to +non-negative integers, and the total new capacity equals this count multiplied by +the unit size :math:`LDNC_{r, t}`. + + +v_discrete_capacity +~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`DCAP_{r, p, t}` + +The number of discrete units of total available (retirement-adjusted) capacity +for technology (or group) :math:`t` in region :math:`r` and period :math:`p`. It +is restricted to non-negative integers, and the net available capacity equals this +count multiplied by the unit size :math:`LDC_{r, t}`. + + +Constraints +----------- + +.. autofunction:: temoa.extensions.discrete_capacity.components.discrete_capacity.limit_discrete_new_capacity_constraint_rule + +.. autofunction:: temoa.extensions.discrete_capacity.components.discrete_capacity.limit_discrete_capacity_constraint_rule diff --git a/docs/source/extensions/eos.rst b/docs/source/extensions/eos.rst new file mode 100644 index 000000000..d71d34e59 --- /dev/null +++ b/docs/source/extensions/eos.rst @@ -0,0 +1,306 @@ +.. _extension-eos: + +Economies of Scale (EOS) +======================== + +The **eos** extension adds piecewise-linear cost curves to three cost +types: investment (:code:`cost_invest_eos`), fixed O&M +(:code:`cost_fixed_eos`), and variable O&M (:code:`cost_variable_eos`). +It is disabled by default and enabled per run through configuration: + +.. code-block:: toml + + extensions = ["eos"] + +All three features share the same binary-integer formulation: a set of +contiguous segments, exactly one of which is active per cluster per period. +They differ in what quantity drives the cost curve (cumulative capacity, +available capacity, or activity) and in how the resulting cost is +discounted and added to the objective. + +.. figure:: ../images/eos_cost_curve.png + :align: center + :width: 90% + :alt: EOS piecewise-linear cost curve + + **EOS piecewise-linear cost curve.** The curve is composed of contiguous + segments :math:`n=0,1,2` (three shown here), each with its own quantity + bounds :math:`[\underline{Q}_{n}, \overline{Q}_{n}]` and corresponding + total-cost bounds :math:`[\underline{C}_{n}, \overline{C}_{n}]`. The + slope :math:`m_n` is the marginal cost within segment :math:`n`. In this + period, segment :math:`n=1` is **active** (red shading): the binary + variable is 1, and the quantity variable :math:`\textbf{Q}_p` lies within + its bounds. + +Overview +-------- + +.. list-table:: + :header-rows: 1 + :widths: 22 20 20 38 + + * - Feature + - Table + - Cost type + - Curve driven by + * - :ref:`cost_invest_eos ` + - :code:`cost_invest_eos` + - Investment (:code:`cost_invest`) + - **Cumulative capacity** built up to period :math:`p` (learning curve) + * - :ref:`cost_fixed_eos ` + - :code:`cost_fixed_eos` + - Fixed O&M (:code:`cost_fixed`) + - **Available capacity** in period :math:`p` + * - :ref:`cost_variable_eos ` + - :code:`cost_variable_eos` + - Variable O&M (:code:`cost_variable`) + - **Total activity** (output flow) in period :math:`p` + +Technologies subject to EOS are specified as *clusters* identified by a +region/group label :math:`r` and a technology/group label :math:`t`. +Each cluster has a piecewise-linear cost curve defined by contiguous +segments :math:`n \in N_{r,t}` (for invest) or +:math:`n \in N_{r,p,t}` (for fixed/variable, which are +period-specific). + +.. note:: + + The :code:`tech_or_group` column accepts either an individual technology + name or a technology-group name. When a group is used, all member + technologies share the same cost curve and their contributions to the + driving quantity (capacity or activity) are summed. For + :code:`cost_invest_eos`, all technologies in a cluster must share the + same :code:`lifetime_process`, :code:`lifetime_survival_curve`, + :code:`loan_lifetime_process`, and :code:`loan_rate` in every period + they appear together. + +.. _eos-invest: + +cost_invest_eos — Investment Cost Curve +---------------------------------------- + +Concept +~~~~~~~ + +:code:`cost_invest_eos` replaces (or supplements) the flat per-unit +investment cost for a cluster with a piecewise-linear curve whose marginal +cost changes with *cumulative* installed capacity — a learning-curve +effect. The curve is global across time: each successive period inherits +the capacity built in all previous periods. The incremental investment cost +charged in period :math:`p` is the difference between the curve evaluated at +cumulative capacity up to :math:`p` and the curve evaluated at the end of +the previous period, so that each unit of new capacity is charged at the +marginal cost appropriate to the total deployment level at the time of +construction. + +.. note:: + + The cumulative capacity **includes pre-existing capacity** (from the + :code:`existing_capacity` table), but pre-existing capacity does not + incur an investment cost — it is subtracted out when computing the + incremental period cost. The first segment's lower bound can almost + always be set to 0 regardless of how much capacity already exists. + +.. note:: + + EOS invest processes can carry a flat investment cost via + :code:`cost_invest` in addition to the piecewise curve. + +Table: cost_invest_eos +~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{CIEOS}_{r \in R,\, t \in T,\, n \in \mathbb{Z}^+}` + +The segment index :math:`n` is an integer; segments must be numbered +consecutively and their capacity bounds must be contiguous. All four bound +columns must be non-negative and strictly increasing within a segment. + +.. csv-table:: + :header: "Column", "Symbol", "Description" + :widths: 22, 18, 60 + + ":code:`region`", ":math:`r`", "region or region-group label" + ":code:`tech_or_group`", ":math:`t`", "technology or technology-group label" + ":code:`segment`", ":math:`n`", "integer segment index (contiguous)" + ":code:`capacity_lower`", ":math:`\underline{K}_{r,t,n}`", "lower cumulative-capacity bound of the segment" + ":code:`capacity_upper`", ":math:`\overline{K}_{r,t,n}`", "upper cumulative-capacity bound of the segment" + ":code:`cost_lower`", ":math:`\underline{C}_{r,t,n}`", "investment cost at :math:`\underline{K}_{r,t,n}` (same units as :code:`cost_invest`)" + ":code:`cost_upper`", ":math:`\overline{C}_{r,t,n}`", "investment cost at :math:`\overline{K}_{r,t,n}`" + +Sets +~~~~ + +.. csv-table:: + :header: "Set", "Indices", "Description" + :widths: 38, 18, 44 + + ":code:`cost_invest_eos_rtn`", ":math:`(r, t, n)`", "valid cluster–segment combinations; populated directly from the table" + ":code:`cost_invest_eos_segment_rptn`", ":math:`(r, p, t, n)`", "valid cluster–period–segment combinations; derived from :code:`cost_invest_eos_rtn` and :code:`process_vintages`" + ":code:`cost_invest_eos_period_rpt`", ":math:`(r, p, t)`", "projection of :code:`cost_invest_eos_segment_rptn` onto :math:`(r, p, t)`" + +Variables +~~~~~~~~~ + +.. csv-table:: + :header: "Variable", "Domain", "Indices", "Description" + :widths: 36, 12, 24, 28 + + ":math:`\textbf{CIECAP}_{r,p,t,n}` (:code:`v_cost_invest_eos_cumulative_capacity`)", ":math:`\mathbb{R}_{\ge 0}`", ":math:`(r, p, t, n) \in \Theta_{\text{cost\_invest\_eos\_segment\_rptn}}`", "cumulative capacity assigned to segment :math:`n` of cluster :math:`(r,t)` in period :math:`p`" + ":math:`\textbf{CIEB}_{r,p,t,n}` (:code:`v_cost_invest_eos_segment_binary`)", ":math:`\{0, 1\}`", ":math:`(r, p, t, n) \in \Theta_{\text{cost\_invest\_eos\_segment\_rptn}}`", "1 if segment :math:`n` is the active segment for cluster :math:`(r,t)` in period :math:`p`" + +Constraints +~~~~~~~~~~~ + +.. autofunction:: temoa.extensions.economies_of_scale.components.cost_invest_eos.cost_invest_eos_segment_binary_constraint + +.. autofunction:: temoa.extensions.economies_of_scale.components.cost_invest_eos.cost_invest_eos_capacity_lower_bound_constraint + +.. autofunction:: temoa.extensions.economies_of_scale.components.cost_invest_eos.cost_invest_eos_capacity_upper_bound_constraint + +.. autofunction:: temoa.extensions.economies_of_scale.components.cost_invest_eos.cost_invest_eos_cumulative_capacity_constraint + +Objective Contribution +~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: temoa.extensions.economies_of_scale.components.cost_invest_eos.period_cost + + +.. _eos-fixed: + +cost_fixed_eos — Fixed O&M Cost Curve +-------------------------------------- + +Concept +~~~~~~~ + +:code:`cost_fixed_eos` replaces (or supplements) the flat per-unit fixed +O&M cost for a cluster with a piecewise-linear curve whose unit cost +changes with *available capacity* in each period independently. Unlike +:code:`cost_invest_eos`, the cost curve is period-specific: segments can +differ from one period to the next, allowing the modeller to represent +O&M cost trends over time separately from capacity-scale effects. + +Table: cost_fixed_eos +~~~~~~~~~~~~~~~~~~~~~ + +:math:`{CFEOS}_{r \in R,\, p \in P,\, t \in T,\, n \in \mathbb{Z}^+}` + +.. csv-table:: + :header: "Column", "Symbol", "Description" + :widths: 22, 18, 60 + + ":code:`region`", ":math:`r`", "region or region-group label" + ":code:`period`", ":math:`p`", "model period" + ":code:`tech_or_group`", ":math:`t`", "technology or technology-group label" + ":code:`segment`", ":math:`n`", "integer segment index (contiguous)" + ":code:`capacity_lower`", ":math:`\underline{K}_{r,p,t,n}`", "lower capacity bound of the segment" + ":code:`capacity_upper`", ":math:`\overline{K}_{r,p,t,n}`", "upper capacity bound of the segment" + ":code:`cost_lower`", ":math:`\underline{C}_{r,p,t,n}`", "fixed O&M cost at :math:`\underline{K}_{r,p,t,n}` (same units as :code:`cost_fixed`)" + ":code:`cost_upper`", ":math:`\overline{C}_{r,p,t,n}`", "fixed O&M cost at :math:`\overline{K}_{r,p,t,n}`" + +Sets +~~~~ + +.. csv-table:: + :header: "Set", "Indices", "Description" + :widths: 36, 20, 44 + + ":code:`cost_fixed_eos_rptn`", ":math:`(r, p, t, n)`", "valid cluster–period–segment combinations" + ":code:`cost_fixed_eos_period_rpt`", ":math:`(r, p, t)`", "projection of :code:`cost_fixed_eos_rptn` onto :math:`(r, p, t)`" + +Variables +~~~~~~~~~ + +.. csv-table:: + :header: "Variable", "Domain", "Indices", "Description" + :widths: 36, 12, 24, 28 + + ":math:`\textbf{CFECAP}_{r,p,t,n}` (:code:`v_cost_fixed_eos_capacity`)", ":math:`\mathbb{R}_{\ge 0}`", ":math:`(r, p, t, n) \in \Theta_{\text{cost\_fixed\_eos\_rptn}}`", "capacity assigned to segment :math:`n` of cluster :math:`(r,p,t)`" + ":math:`\textbf{CFEB}_{r,p,t,n}` (:code:`v_cost_fixed_eos_segment_binary`)", ":math:`\{0, 1\}`", ":math:`(r, p, t, n) \in \Theta_{\text{cost\_fixed\_eos\_rptn}}`", "1 if segment :math:`n` is active for cluster :math:`(r,p,t)`" + +Constraints +~~~~~~~~~~~ + +.. autofunction:: temoa.extensions.economies_of_scale.components.cost_fixed_eos.cost_fixed_eos_segment_binary_constraint + +.. autofunction:: temoa.extensions.economies_of_scale.components.cost_fixed_eos.cost_fixed_eos_capacity_lower_bound_constraint + +.. autofunction:: temoa.extensions.economies_of_scale.components.cost_fixed_eos.cost_fixed_eos_capacity_upper_bound_constraint + +.. autofunction:: temoa.extensions.economies_of_scale.components.cost_fixed_eos.cost_fixed_eos_capacity_constraint + +Objective Contribution +~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: temoa.extensions.economies_of_scale.components.cost_fixed_eos.period_cost + + +.. _eos-variable: + +cost_variable_eos — Variable O&M Cost Curve +-------------------------------------------- + +Concept +~~~~~~~ + +:code:`cost_variable_eos` replaces (or supplements) the flat per-unit +variable O&M cost for a cluster with a piecewise-linear curve whose unit +cost changes with *total activity* (output flow) in each period +independently. Like :code:`cost_fixed_eos`, the cost curve is +period-specific. This allows the modeller to represent dispatch-scale +economies (e.g. fuel-handling efficiency at high utilisation rates) that +vary over time. + +Table: cost_variable_eos +~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{CVEOS}_{r \in R,\, p \in P,\, t \in T,\, n \in \mathbb{Z}^+}` + +.. csv-table:: + :header: "Column", "Symbol", "Description" + :widths: 22, 18, 60 + + ":code:`region`", ":math:`r`", "region or region-group label" + ":code:`period`", ":math:`p`", "model period" + ":code:`tech_or_group`", ":math:`t`", "technology or technology-group label" + ":code:`segment`", ":math:`n`", "integer segment index (contiguous)" + ":code:`activity_lower`", ":math:`\underline{A}_{r,p,t,n}`", "lower activity bound of the segment" + ":code:`activity_upper`", ":math:`\overline{A}_{r,p,t,n}`", "upper activity bound of the segment" + ":code:`cost_lower`", ":math:`\underline{C}_{r,p,t,n}`", "variable O&M cost at :math:`\underline{A}_{r,p,t,n}` (same units as :code:`cost_variable`)" + ":code:`cost_upper`", ":math:`\overline{C}_{r,p,t,n}`", "variable O&M cost at :math:`\overline{A}_{r,p,t,n}`" + +Sets +~~~~ + +.. csv-table:: + :header: "Set", "Indices", "Description" + :widths: 36, 20, 44 + + ":code:`cost_variable_eos_rptn`", ":math:`(r, p, t, n)`", "valid cluster–period–segment combinations" + ":code:`cost_variable_eos_period_rpt`", ":math:`(r, p, t)`", "projection of :code:`cost_variable_eos_rptn` onto :math:`(r, p, t)`" + +Variables +~~~~~~~~~ + +.. csv-table:: + :header: "Variable", "Domain", "Indices", "Description" + :widths: 36, 12, 24, 28 + + ":math:`\textbf{CVEACT}_{r,p,t,n}` (:code:`v_cost_variable_eos_activity`)", ":math:`\mathbb{R}_{\ge 0}`", ":math:`(r, p, t, n) \in \Theta_{\text{cost\_variable\_eos\_rptn}}`", "activity assigned to segment :math:`n` of cluster :math:`(r,p,t)`" + ":math:`\textbf{CVEB}_{r,p,t,n}` (:code:`v_cost_variable_eos_segment_binary`)", ":math:`\{0, 1\}`", ":math:`(r, p, t, n) \in \Theta_{\text{cost\_variable\_eos\_rptn}}`", "1 if segment :math:`n` is active for cluster :math:`(r,p,t)`" + +Constraints +~~~~~~~~~~~ + +.. autofunction:: temoa.extensions.economies_of_scale.components.cost_variable_eos.cost_variable_eos_segment_binary_constraint + +.. autofunction:: temoa.extensions.economies_of_scale.components.cost_variable_eos.cost_variable_eos_activity_lower_bound_constraint + +.. autofunction:: temoa.extensions.economies_of_scale.components.cost_variable_eos.cost_variable_eos_activity_upper_bound_constraint + +.. autofunction:: temoa.extensions.economies_of_scale.components.cost_variable_eos.cost_variable_eos_activity_constraint + +Objective Contribution +~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: temoa.extensions.economies_of_scale.components.cost_variable_eos.period_cost diff --git a/docs/source/extensions/growth_rates.rst b/docs/source/extensions/growth_rates.rst new file mode 100644 index 000000000..261b1e742 --- /dev/null +++ b/docs/source/extensions/growth_rates.rst @@ -0,0 +1,99 @@ +.. _extension-growth-rates: + +Growth Rate Limits +================================== + +The **growth_rates** extension adds optional constraints that bound how quickly +the capacity (or new capacity) of a technology or technology group may change +between consecutive model periods. It is disabled by default and enabled per +run through configuration: + +.. code-block:: toml + + extensions = ["growth_rates"] + +Its parameters live in the extension-owned tables of the same name and are loaded +only when the extension is enabled. See :ref:`extensions` for how the extension +framework wires these components into the model. + +Parameters +---------- + +.. csv-table:: + :header: "Parameter", "Database Table", "Model Element", "Notes" + :widths: 15, 20, 25, 40 + + ":math:`\text{LGC}_{r,t}`", ":code:`limit_growth_capacity`", ":code:`limit_growth_capacity`", "capacity growth rate limits; :code:`tech_or_group` column accepts a technology name or group name" + ":math:`\text{LDGC}_{r,t}`", ":code:`limit_degrowth_capacity`", ":code:`limit_degrowth_capacity`", "capacity degrowth rate limits; :code:`tech_or_group` column accepts a technology name or group name" + ":math:`\text{LGNC}_{r,t}`", ":code:`limit_growth_new_capacity`", ":code:`limit_growth_new_capacity`", "new capacity growth rate limits; :code:`tech_or_group` column accepts a technology name or group name" + ":math:`\text{LDGNC}_{r,t}`", ":code:`limit_degrowth_new_capacity`", ":code:`limit_degrowth_new_capacity`", "new capacity degrowth rate limits; :code:`tech_or_group` column accepts a technology name or group name" + ":math:`\mathrm{LGNC}_{\Delta,r,t}`", ":code:`limit_growth_new_capacity_delta`", ":code:`limit_growth_new_capacity_delta`", "new capacity growth acceleration limits; :code:`tech_or_group` column accepts a technology name or group name" + ":math:`\mathrm{LDGNC}_{\Delta,r,t}`", ":code:`limit_degrowth_new_capacity_delta`", ":code:`limit_degrowth_new_capacity_delta`", "new capacity degrowth deceleration limits; :code:`tech_or_group` column accepts a technology name or group name" + + +limit_growth_capacity +~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{LGC}_{r \in R, t \in T}` + +The :code:`limit_growth_capacity` parameter defines the maximum annual rate at +which the total capacity of a technology (or group) can grow between periods. +The :code:`tech_or_group` column accepts a technology name or group name. + + +limit_degrowth_capacity +~~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{LDGC}_{r \in R, t \in T}` + +The :code:`limit_degrowth_capacity` parameter defines the maximum annual rate +at which the total capacity of a technology (or group) can shrink between +periods. The :code:`tech_or_group` column accepts a technology name or group name. + + +limit_growth_new_capacity +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{LGNC}_{r \in R, t \in T}` + +The :code:`limit_growth_new_capacity` parameter constrains the rate of increase +in new capacity deployment between consecutive periods. + + +limit_degrowth_new_capacity +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{LDGNC}_{r \in R, t \in T}` + +The :code:`limit_degrowth_new_capacity` parameter constrains the rate of decrease +in new capacity deployment between consecutive periods. + + +limit_growth_new_capacity_delta +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`\mathrm{LGNC}_{\Delta,r \in R, t \in T}` + +The :code:`limit_growth_new_capacity_delta` parameter constrains the acceleration +of new capacity growth between periods. This essentially adds "inertia" to the +growth of new capacity deployment. + + +limit_degrowth_new_capacity_delta +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`\mathrm{LDGNC}_{\Delta,r \in R, t \in T}` + +The :code:`limit_degrowth_new_capacity_delta` parameter constrains the +deceleration of new capacity degrowth between periods, essentially adding +"inertia" to the degrowth of new capacity deployment. + + +Constraints +----------- + +.. autofunction:: temoa.extensions.growth_rates.components.growth_capacity.limit_growth_capacity + +.. autofunction:: temoa.extensions.growth_rates.components.growth_new_capacity.limit_growth_new_capacity + +.. autofunction:: temoa.extensions.growth_rates.components.growth_new_capacity_delta.limit_growth_new_capacity_delta diff --git a/docs/source/images/eos_cost_curve.png b/docs/source/images/eos_cost_curve.png new file mode 100644 index 000000000..9772ea4e9 Binary files /dev/null and b/docs/source/images/eos_cost_curve.png differ diff --git a/docs/source/images/eos_cost_curve.svg b/docs/source/images/eos_cost_curve.svg new file mode 100644 index 000000000..75ad57d2c --- /dev/null +++ b/docs/source/images/eos_cost_curve.svg @@ -0,0 +1,702 @@ + + + + + + + + 2026-07-08T13:23:55.421448 + image/svg+xml + + + Matplotlib v3.11.0, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Q + n + = + 0 + = + 0 + + + + + + + + + + + + + + + + ̄ + = + Q + Q + n + n + = + 0 + = + 1 + + + + + + + + + + + + + + + + ̄ + = + Q + Q + n + n + = + 1 + = + 2 + + + + + + + + + + + + + + + + ̄ + Q + n + = + 2 + + + + + + + + + + + + + + + Q + p + + + + + + + + + Q + u + a + n + t + i + t + y +   +   +   +   + ( + c + a + p + a + c + i + t + y +   + o + r +   + a + c + t + i + v + i + t + y + ) + Q + + + + + + + + + + + + + + + + + + + C + n + = + 0 + = + 0 + + + + + + + + + + + + + + + + ̄ + = + C + C + n + n + = + 0 + = + 1 + + + + + + + + + + + + + + + + ̄ + = + C + C + n + n + = + 1 + = + 2 + + + + + + + + + + + + + + + + ̄ + C + n + = + 2 + + + + + + + + + T + o + t + a + l +   + c + o + s + t +   +   + C + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + m + n + C + Q + = +   +   + ( + s + l + o + p + e + ) + Δ + Δ + + + + + + + + + n + = + 0 + + + + + + + + n + = + 1 + + + + + + + + n + = + 2 + + + + + + + + active segment + + + + b + n + = + 1 + = + 1 + + + + + EOS piecewise-linear cost curve + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + c + t + i + v + e +   + s + e + g + m + e + n + t +   + i + n +   + p + e + r + i + o + d +   + p + + + + + + + + Active segment cost line + + + + + + Inactive segment cost line + + + + + + + + + Q + p +   + ( + c + u + r + r + e + n + t +   + p + e + r + i + o + d + ) + + + + + + + + + + + + diff --git a/docs/source/images/etl_cost_curve.png b/docs/source/images/etl_cost_curve.png new file mode 100644 index 000000000..af24dd914 Binary files /dev/null and b/docs/source/images/etl_cost_curve.png differ diff --git a/docs/source/images/etl_cost_curve.svg b/docs/source/images/etl_cost_curve.svg new file mode 100644 index 000000000..2d6cc457b --- /dev/null +++ b/docs/source/images/etl_cost_curve.svg @@ -0,0 +1,683 @@ + + + + + + + + 2026-07-07T18:07:34.827185 + image/svg+xml + + + Matplotlib v3.11.0, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + ̄ + = + K + K + t + t + , + 1 + , + 2 + + + + + + + + + + + + + + + + ̄ + = + K + K + t + t + , + 2 + , + 3 + + + + + + + + + + + + + + + + ̄ + K + t + , + 3 + + + + + + + + + + + + + + + E + T + L + C + A + P + p + + + + + + + + + C + u + m + u + l + a + t + i + v + e +   + i + n + s + t + a + l + l + e + d +   + c + a + p + a + c + i + t + y +   +   + K + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + C + t + , + 1 + + + + + + + + + + + + + + + + C + t + , + 2 + + + + + + + + + + + + + + + + C + t + , + 3 + + + + + + + + + + T + o + t + a + l +   + i + n + v + e + s + t + m + e + n + t +   + c + o + s + t +   +   + C + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + m + t + n + C + K + , + Δ + Δ + = +   +   + ( + s + l + o + p + e + ) + + + + + + + + + n + = + 1 + + + + + + + + n + = + 2 + + + + + + + + n + = + 3 + + + + + + + + active + segment + + + ETL piecewise-linear cost curve + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + c + t + i + v + e +   + s + e + g + m + e + n + t +   + i + n +   + p + e + r + i + o + d +   + p + + + + + + + + Active segment cost line + + + + + + Inactive segment cost line + + + + + + + + + E + T + L + C + A + P + p +   + ( + c + u + r + r + e + n + t +   + p + e + r + i + o + d + ) + + + + + + + + + + + + diff --git a/docs/source/index.rst b/docs/source/index.rst index f9043f39d..3d7f9e3b4 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -27,6 +27,7 @@ Temoa Project Documentation myopic stochastics unit_checking + extensions .. toctree:: :maxdepth: 1 diff --git a/docs/source/mathematical_formulation.rst b/docs/source/mathematical_formulation.rst index cc4d3da10..86bd4b4fc 100644 --- a/docs/source/mathematical_formulation.rst +++ b/docs/source/mathematical_formulation.rst @@ -711,35 +711,6 @@ controls the bound direction. The group columns accept either a single technology name or a technology group name. -limit_degrowth_capacity -~~~~~~~~~~~~~~~~~~~~~~~ - -:math:`{LDGC}_{r \in R, t \in T}` - -The :code:`limit_degrowth_capacity` parameter defines the maximum annual rate -at which the total capacity of a technology (or group) can shrink between -periods. The :code:`tech_or_group` column accepts a technology name or group name. - - -limit_degrowth_new_capacity -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:math:`{LDGNC}_{r \in R, t \in T}` - -The :code:`limit_degrowth_new_capacity` parameter constrains the rate of decrease -in new capacity deployment between consecutive periods. - - -limit_degrowth_new_capacity_delta -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:math:`\mathrm{LDGNC}_{\Delta,r \in R, t \in T}` - -The :code:`limit_degrowth_new_capacity_delta` parameter constrains the -deceleration of new capacity degrowth between periods, essentially adding -"intertia" to the degrowth of new capacity deployment. - - limit_emission ~~~~~~~~~~~~~~ @@ -751,35 +722,6 @@ fits within the modeler-specified limit on emission :math:`e` in time period bound, or equality. -limit_growth_capacity -~~~~~~~~~~~~~~~~~~~~~ - -:math:`{LGC}_{r \in R, t \in T}` - -The :code:`limit_growth_capacity` parameter defines the maximum annual rate at -which the total capacity of a technology (or group) can grow between periods. -The :code:`tech_or_group` column accepts a technology name or group name. - - -limit_growth_new_capacity -~~~~~~~~~~~~~~~~~~~~~~~~~ - -:math:`{LGNC}_{r \in R, t \in T}` - -The :code:`limit_growth_new_capacity` parameter constrains the rate of increase -in new capacity deployment between consecutive periods. - - -limit_growth_new_capacity_delta -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:math:`\mathrm{LGNC}_{\Delta,r \in R, t \in T}` - -The :code:`limit_growth_new_capacity_delta` parameter constrains the acceleration -of new capacity growth between periods. This essentially adds "inertia" to the -growth of new capacity deployment. - - limit_new_capacity ~~~~~~~~~~~~~~~~~~ @@ -1437,12 +1379,6 @@ User-Specific Constraints .. autofunction:: temoa.components.storage.limit_storage_fraction_constraint -.. autofunction:: temoa.components.limits.limit_growth_capacity - -.. autofunction:: temoa.components.limits.limit_growth_new_capacity - -.. autofunction:: temoa.components.limits.limit_growth_new_capacity_delta - General Caveats --------------- diff --git a/docs/source/param_desc_and_tables.rst b/docs/source/param_desc_and_tables.rst index 40ff0737d..aef95320e 100644 --- a/docs/source/param_desc_and_tables.rst +++ b/docs/source/param_desc_and_tables.rst @@ -94,20 +94,6 @@ capacity, activity and emissions**. ":math:`\text{LE}_{r,p,e}`", ":code:`limit_emission`", ":code:`limit_emission`", "limit on emissions by region and period" ":math:`\text{LS}_{r,t}`", ":code:`limit_resource`", ":code:`limit_resource`", "cumulative activity limit across time periods (not supported in myopic); :code:`tech_or_group` column accepts a technology name or group name" -Parameters in the table below relate to the specification of **growth and degrowth -limits**. - -.. csv-table:: - :header: "Parameter", "Database Table", "Model Element", "Notes" - :widths: 15, 20, 25, 40 - - ":math:`\text{LGC}_{r,t}`", ":code:`limit_growth_capacity`", ":code:`limit_growth_capacity`", "capacity growth rate limits; :code:`tech_or_group` column accepts a technology name or group name" - ":math:`\text{LDGC}_{r,t}`", ":code:`limit_degrowth_capacity`", ":code:`limit_degrowth_capacity`", "capacity degrowth rate limits; :code:`tech_or_group` column accepts a technology name or group name" - ":math:`\text{LGNC}_{r,t}`", ":code:`limit_growth_new_capacity`", ":code:`limit_growth_new_capacity`", "new capacity growth rate limits; :code:`tech_or_group` column accepts a technology name or group name" - ":math:`\text{LDGNC}_{r,t}`", ":code:`limit_degrowth_new_capacity`", ":code:`limit_degrowth_new_capacity`", "new capacity degrowth rate limits; :code:`tech_or_group` column accepts a technology name or group name" - ":math:`\mathrm{LGNC}_{\Delta,r,t}`", ":code:`limit_growth_new_capacity_delta`", ":code:`limit_growth_new_capacity_delta`", "new capacity growth acceleration limits; :code:`tech_or_group` column accepts a technology name or group name" - ":math:`\mathrm{LDGNC}_{\Delta,r,t}`", ":code:`limit_degrowth_new_capacity_delta`", ":code:`limit_degrowth_new_capacity_delta`", "new capacity degrowth deceleration limits; :code:`tech_or_group` column accepts a technology name or group name" - Parameters in the table below relate to the specification of **operational and split limits**. diff --git a/pyproject.toml b/pyproject.toml index 0e4b35570..602f9066b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,9 +86,7 @@ path = "temoa/__about__.py" [tool.ruff] line-length = 100 indent-width = 4 -exclude = ["stubs", - "temoa/extensions/" -] +exclude = ["stubs"] [tool.ruff.format] quote-style = "single" diff --git a/scripts/generate_etl_figure.py b/scripts/generate_etl_figure.py new file mode 100644 index 000000000..4e42ee48c --- /dev/null +++ b/scripts/generate_etl_figure.py @@ -0,0 +1,229 @@ +""" +Generates docs/source/images/eos_cost_curve.{png,svg} — a labelled diagram +of the piecewise-linear cost curve shared by all three features of the +economies of scale (EOS) extension: cost_invest_eos, cost_fixed_eos, and +cost_variable_eos. +""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import matplotlib +import matplotlib.patches as mpatches +import matplotlib.pyplot as plt +import matplotlib.ticker as ticker + +matplotlib.rcParams.update( + { + 'font.family': 'sans-serif', + 'font.size': 10, + 'axes.spines.top': False, + 'axes.spines.right': False, + 'pdf.fonttype': 42, + 'svg.fonttype': 'none', + } +) + +OUT_DIR = Path(__file__).parent.parent / 'docs' / 'source' / 'images' + +# ——— Segment data (decreasing marginal cost — economies of scale) ————————————— +# Each entry: (q_lower, q_upper, cost_lower, cost_upper) +segments = [ + (0.0, 2.0, 0.0, 6.0), # n=1 slope = 3.0 + (2.0, 5.0, 6.0, 12.0), # n=2 slope = 2.0 + (5.0, 9.0, 12.0, 16.0), # n=3 slope = 1.0 +] + +PALETTE = { + 'curve': '#1f1f1f', + 'active': '#d44040', + 'inactive': '#aaaaaa', + 'fill': '#fde8e8', + 'grid': '#e8e8e8', + 'annot': '#555555', + 'tick': '#333333', +} + +# Active segment (0-indexed) — the one the optimizer has selected in this period +ACTIVE = 1 +# Quantity realised in this period (sits inside the active segment) +Q_P = 3.5 + +fig, ax = plt.subplots(figsize=(8.0, 5.0)) +fig.patch.set_facecolor('white') +ax.set_facecolor('white') + +# ——— Shade the active segment background —————————————————————————————————————— +ql_a, qu_a, cl_a, cu_a = segments[ACTIVE] +ax.axvspan(ql_a, qu_a, color=PALETTE['fill'], zorder=0, linewidth=0) + +# ——— Draw all segment lines ———————————————————————————————————————————————————— +for i, (ql, qu, cl, cu) in enumerate(segments): + color = PALETTE['active'] if i == ACTIVE else PALETTE['curve'] + lw = 2.2 if i == ACTIVE else 1.6 + ax.plot([ql, qu], [cl, cu], color=color, linewidth=lw, solid_capstyle='round', zorder=3) + dot_color = PALETTE['active'] if i == ACTIVE else PALETTE['curve'] + ax.plot(ql, cl, 'o', ms=5, color=dot_color, zorder=4) + ax.plot(qu, cu, 'o', ms=5, color=dot_color, zorder=4) + +# ——— Vertical line at current-period quantity ————————————————————————————————— +slope_a = (cu_a - cl_a) / (qu_a - ql_a) +cost_p = cl_a + slope_a * (Q_P - ql_a) + +# ——— Capacity/activity bound tick labels on x-axis ——————————————————————————— +boundaries = [] +for i, (ql, _qu, _cl, _cu) in enumerate(segments): + n = i # 0-based + if ql == 0: + boundaries.append((ql, r'$\underline{Q}_{n=0} = 0$')) + else: + boundaries.append((ql, rf'$\bar{{Q}}_{{n={n - 1}}} = \underline{{Q}}_{{n={n}}}$')) + +_, qu_last, _, _ = segments[-1] +boundaries.append((qu_last, rf'$\bar{{Q}}_{{n={len(segments) - 1}}}$')) + +x_ticks = [b[0] for b in boundaries] + [Q_P] +x_labels = [b[1] for b in boundaries] + [r'$\mathbf{Q}_p$'] + +ax.set_xticks(x_ticks) +ax.set_xticklabels(x_labels, fontsize=8.5, color=PALETTE['tick']) + +# ——— Cost bound tick labels on y-axis ———————————————————————————————————————— +y_boundaries = [] +for i, (_ql, _qu, cl, _cu) in enumerate(segments): + n = i # 0-based + if cl == 0: + y_boundaries.append((cl, r'$\underline{C}_{n=0} = 0$')) + else: + # shared upper bound of prev segment / lower bound of this segment + y_boundaries.append((cl, rf'$\bar{{C}}_{{n={n - 1}}} = \underline{{C}}_{{n={n}}}$')) + +_, _, _, cu_last = segments[-1] +y_boundaries.append((cu_last, rf'$\bar{{C}}_{{n={len(segments) - 1}}}$')) + +y_ticks = [b[0] for b in y_boundaries] +y_labels = [b[1] for b in y_boundaries] + +ax.set_yticks(y_ticks) +ax.set_yticklabels(y_labels, fontsize=8.5, color=PALETTE['tick']) + +# ——— Dashed reference lines to axes from segment endpoints ——————————————————— +dash_kw: dict[str, Any] = { + 'color': PALETTE['inactive'], + 'linewidth': 0.8, + 'linestyle': ':', + 'zorder': 1, +} +for ql, qu, cl, cu in segments: + ax.plot([ql, ql], [0, cl], **dash_kw) + ax.plot([qu, qu], [0, cu], **dash_kw) + ax.plot([0, ql], [cl, cl], **dash_kw) + ax.plot([0, qu], [cu, cu], **dash_kw) + +# dashed lines from Q_P up to cost curve, then across to y-axis +ax.plot([Q_P, Q_P], [0, cost_p], color=PALETTE['active'], linewidth=0.9, linestyle='--', zorder=2) +ax.plot( + [0, Q_P], [cost_p, cost_p], color=PALETTE['active'], linewidth=0.9, linestyle='--', zorder=2 +) +ax.plot(0, cost_p, '<', ms=5, color=PALETTE['active'], zorder=4, clip_on=False) +ax.plot(Q_P, 0, 'v', ms=5, color=PALETTE['active'], zorder=4, clip_on=False) + +# ——— Slope annotation on active segment —————————————————————————————————————— +mid_q = (ql_a + qu_a) / 2 +mid_c = cl_a + slope_a * (mid_q - ql_a) +dq, dc = 0.6, slope_a * 0.6 +ax.annotate( + '', + xy=(mid_q + dq, mid_c + dc), + xytext=(mid_q + dq, mid_c), + arrowprops={'arrowstyle': '-', 'color': PALETTE['annot'], 'lw': 0.9}, +) +ax.annotate( + '', + xy=(mid_q + dq, mid_c), + xytext=(mid_q, mid_c), + arrowprops={'arrowstyle': '-', 'color': PALETTE['annot'], 'lw': 0.9}, +) +ax.text( + mid_q + dq + 0.08, + mid_c + dc / 2, + r'$m_n = \frac{\Delta C}{\Delta Q}$ (slope)', + va='center', + ha='left', + fontsize=8.5, + color=PALETTE['annot'], +) + +# ——— Segment labels ——————————————————————————————————————————————————————————— +for i, (ql, qu, cl, cu) in enumerate(segments): + n = i # 0-based + mid_q = (ql + qu) / 2 + mid_c = (cl + cu) / 2 + color = PALETTE['active'] if i == ACTIVE else PALETTE['annot'] + weight = 'bold' if i == ACTIVE else 'normal' + ax.text( + mid_q, + mid_c + 0.7, + f'$n={n}$', + ha='center', + va='bottom', + fontsize=9, + color=color, + fontweight=weight, + ) + +# ——— Active-segment label ————————————————————————————————————————————————————— +ax.text( + (ql_a + qu_a) / 2, + 1.5, + 'active segment\n' r'$b_{n=1} = 1$', + ha='center', + va='bottom', + fontsize=8.5, + color=PALETTE['active'], + bbox={'boxstyle': 'round,pad=0.3', 'fc': PALETTE['fill'], 'ec': PALETTE['active'], 'lw': 0.8}, +) + +# ——— Axes formatting —————————————————————————————————————————————————————————— +ax.set_xlim(-0.3, 10.2) +ax.set_ylim(-0.3, 18.5) +ax.set_xlabel('Quantity $Q$ (capacity or activity)', labelpad=8) +ax.set_ylabel('Total cost $C$', labelpad=8) +ax.set_title('EOS piecewise-linear cost curve', pad=10, fontsize=11) + +ax.xaxis.set_minor_locator(ticker.NullLocator()) +ax.yaxis.set_minor_locator(ticker.NullLocator()) + +# ——— Legend ——————————————————————————————————————————————————————————————————— +handles = [ + mpatches.Patch( + facecolor=PALETTE['fill'], + edgecolor=PALETTE['active'], + lw=0.8, + label='Active segment in period $p$', + ), + plt.Line2D([0], [0], color=PALETTE['active'], lw=2.2, label='Active segment cost line'), + plt.Line2D([0], [0], color=PALETTE['curve'], lw=1.6, label='Inactive segment cost line'), + plt.Line2D( + [0], + [0], + color=PALETTE['active'], + lw=1.2, + linestyle='--', + label=r'$\mathbf{Q}_p$ (current period)', + ), +] +ax.legend( + handles=handles, fontsize=8, loc='upper left', frameon=True, framealpha=0.9, edgecolor='#cccccc' +) + +fig.tight_layout() + +for ext in ('png', 'svg'): + out = OUT_DIR / f'eos_cost_curve.{ext}' + fig.savefig(out, dpi=150, bbox_inches='tight', facecolor='white') + print(f'Saved {out}') + +plt.close(fig) diff --git a/temoa/__init__.py b/temoa/__init__.py index 67ef283bd..75a8d2419 100644 --- a/temoa/__init__.py +++ b/temoa/__init__.py @@ -19,7 +19,6 @@ solve_instance, ) from temoa._internal.table_data_puller import ( - loan_costs, poll_capacity_results, poll_cost_results, poll_emissions, @@ -27,6 +26,7 @@ ) from temoa._internal.table_writer import TableWriter from temoa._internal.temoa_sequencer import TemoaSequencer +from temoa.components.costs import poll_loan_costs from temoa.core.config import TemoaConfig from temoa.core.model import TemoaModel from temoa.core.modes import TemoaMode @@ -48,7 +48,7 @@ 'solve_instance', 'handle_results', 'save_lp', - 'loan_costs', + 'poll_loan_costs', 'poll_capacity_results', 'poll_emissions', 'poll_flow_results', diff --git a/temoa/_internal/exchange_tech_cost_ledger.py b/temoa/_internal/exchange_tech_cost_ledger.py index 12b170f5f..361ad8dd1 100644 --- a/temoa/_internal/exchange_tech_cost_ledger.py +++ b/temoa/_internal/exchange_tech_cost_ledger.py @@ -57,7 +57,11 @@ def add_cost_record( if not r1 and r2: raise ValueError(f'problem splitting region-region: {link}') # add to the "seen" records for appropriate cost type - self.cost_records[cost_type][r1, r2, tech, vintage, period] = cost + key = (r1, r2, tech, vintage, period) + if key in self.cost_records[cost_type]: + self.cost_records[cost_type][key] += cost + else: + self.cost_records[cost_type][key] = cost def get_use_ratio( self, exporter: Region, importer: Region, period: Period, tech: Technology, vintage: Vintage diff --git a/temoa/_internal/run_actions.py b/temoa/_internal/run_actions.py index d0299a1bb..7e7b5ec5b 100644 --- a/temoa/_internal/run_actions.py +++ b/temoa/_internal/run_actions.py @@ -124,6 +124,7 @@ def build_instance( silent: bool = False, keep_lp_file: bool = False, lp_path: Path | None = None, + extensions: Iterable[str] | None = None, ) -> TemoaModel: """ Build a Temoa Instance from data @@ -134,7 +135,7 @@ def build_instance( :param model_name: Optional name for this instance :return: a built TemoaModel """ - model = TemoaModel() + model = TemoaModel(extensions=tuple(extensions or ())) model.dual = Suffix(direction=Suffix.IMPORT) # self.model.rc = Suffix(direction=Suffix.IMPORT) @@ -243,7 +244,11 @@ def solve_instance( with task_timer(f'Solving model {instance.name}', silent=silent): try: # currently, the highs solver call will puke if the suffixes are passed + # it also doesn't seem to support duals properly so disable those if solver_name == 'appsi_highs': + dual_suffix = instance.component('dual') + if dual_suffix is not None: + dual_suffix.direction = Suffix.LOCAL result = optimizer.solve(instance) else: result = optimizer.solve(instance, suffixes=solver_suffixes_list) diff --git a/temoa/_internal/table_data_puller.py b/temoa/_internal/table_data_puller.py index 832d3557c..3973832a7 100644 --- a/temoa/_internal/table_data_puller.py +++ b/temoa/_internal/table_data_puller.py @@ -20,6 +20,7 @@ from temoa._internal.exchange_tech_cost_ledger import CostType, ExchangeTechCostLedger from temoa.components import costs from temoa.components.utils import get_variable_efficiency +from temoa.extensions.economies_of_scale.core import data_puller as eos from temoa.types.model_types import EI, FI, SLI, CapData, FlowType if TYPE_CHECKING: @@ -340,30 +341,30 @@ def poll_cost_results( cap = value(model.v_new_capacity[r, t, v]) if abs(cap) < epsilon: continue + cost_invest = value(model.cost_invest[r, t, v]) loan_life = value(loan_lifetime_process[r, t, v]) - loan_rate = value(model.loan_rate[r, t, v]) + loan_annualize = value(model.loan_annualize[r, t, v]) if model.is_survival_curve_process[r, t, v]: - model_loan_cost, undiscounted_cost = loan_costs_survival_curve( + discounted_cost, undiscounted_cost = costs.poll_loan_costs_survival_curve( model=model, r=r, t=t, v=v, - loan_rate=loan_rate, loan_life=loan_life, + loan_annualize=loan_annualize, capacity=cap, - invest_cost=value(model.cost_invest[r, t, v]), + invest_cost=cost_invest, p_0=p_0_true, p_e=p_e, global_discount_rate=global_discount_rate, - vintage=v, ) else: - model_loan_cost, undiscounted_cost = loan_costs( - loan_rate=loan_rate, + discounted_cost, undiscounted_cost = costs.poll_loan_costs( loan_life=loan_life, + loan_annualize=loan_annualize, capacity=cap, - invest_cost=value(model.cost_invest[r, t, v]), + invest_cost=cost_invest, process_life=value(model.lifetime_process[r, t, v]), p_0=p_0_true, p_e=p_e, @@ -377,7 +378,7 @@ def poll_cost_results( period=v, tech=t, vintage=v, - cost=model_loan_cost, + cost=discounted_cost, cost_type=CostType.D_INVEST, ) exchange_costs.add_cost_record( @@ -392,7 +393,7 @@ def poll_cost_results( # The period `p` for an investment cost is its vintage `v`. key = (cast('Region', r), cast('Period', v), cast('Technology', t), cast('Vintage', v)) entries[key].update( - {CostType.D_INVEST: model_loan_cost, CostType.INVEST: undiscounted_cost} + {CostType.D_INVEST: discounted_cost, CostType.INVEST: undiscounted_cost} ) for r, p, t, v in model.cost_fixed.sparse_keys(): @@ -489,107 +490,18 @@ def poll_cost_results( CostType.VARIABLE: float(value(undiscounted_var_cost)), } ) - exchange_entries = exchange_costs.get_entries() - return entries, exchange_entries - -def loan_costs( - loan_rate: float, # this is referred to as loan_rate in parameters - loan_life: float, - capacity: float, - invest_cost: float, - process_life: int, - p_0: int, - p_e: int, - global_discount_rate: float, - vintage: int, - **kwargs: object, -) -> tuple[float, float]: - """ - Calculate Loan costs by calling the loan annualize and loan cost functions in temoa_rules - :return: tuple of [model-view discounted cost, un-discounted annuity] - """ - # dev note: this is a passthrough function. Sole intent is to use the EXACT formula the - # model uses for these costs - loan_ar = costs.pv_to_annuity(rate=loan_rate, periods=int(loan_life)) - model_ic = costs.loan_cost( - capacity, - invest_cost, - loan_annualize=float(value(loan_ar)), - lifetime_loan_process=loan_life, - lifetime_process=process_life, - p_0=p_0, - p_e=p_e, - global_discount_rate=global_discount_rate, - vintage=vintage, - ) - # Override the GDR to get the undiscounted value - global_discount_rate = 0 - undiscounted_cost = costs.loan_cost( - capacity, - invest_cost, - loan_annualize=float(value(loan_ar)), - lifetime_loan_process=loan_life, - lifetime_process=process_life, - p_0=p_0, - p_e=p_e, - global_discount_rate=global_discount_rate, - vintage=vintage, + # Get nonlinear costs from the EOS extension, if active + eos.poll_costs( + model=model, + exchange_costs=exchange_costs, + entries=entries, + p_0=p_0_true, + epsilon=epsilon, ) - return float(value(model_ic)), float(value(undiscounted_cost)) - - -def loan_costs_survival_curve( - model: TemoaModel, - r: Region, - t: Technology, - v: Vintage, - loan_rate: float, # this is referred to as loan_rate in parameters - loan_life: float, - capacity: float, - invest_cost: float, - p_0: Period, - p_e: Period, - global_discount_rate: float, - vintage: Vintage, - **kwargs: object, -) -> tuple[float, float]: - """ - Calculate Loan costs by calling the loan annualize and loan cost functions in temoa_rules - :return: tuple of [model-view discounted cost, un-discounted annuity] - """ - # dev note: this is a passthrough function. Sole intent is to use the EXACT formula the - # model uses for these costs - loan_ar = costs.pv_to_annuity(rate=loan_rate, periods=int(loan_life)) - model_ic = costs.loan_cost_survival_curve( - model, - r, - t, - v, - capacity, - invest_cost, - loan_annualize=float(value(loan_ar)), - lifetime_loan_process=loan_life, - p_0=p_0, - p_e=p_e, - global_discount_rate=global_discount_rate, - ) - # Override the GDR to get the undiscounted value - global_discount_rate = 0 - undiscounted_cost = costs.loan_cost_survival_curve( - model, - r, - t, - v, - capacity, - invest_cost, - loan_annualize=float(value(loan_ar)), - lifetime_loan_process=loan_life, - p_0=p_0, - p_e=p_e, - global_discount_rate=global_discount_rate, - ) - return float(value(model_ic)), float(value(undiscounted_cost)) + + exchange_entries = exchange_costs.get_entries() + return entries, exchange_entries def poll_emissions( diff --git a/temoa/_internal/temoa_sequencer.py b/temoa/_internal/temoa_sequencer.py index 08ae1290b..bd44044e3 100644 --- a/temoa/_internal/temoa_sequencer.py +++ b/temoa/_internal/temoa_sequencer.py @@ -140,7 +140,11 @@ def build_model(self) -> TemoaModel: tune_sqlite_connection(con, self.config) hybrid_loader = HybridLoader(db_connection=con, config=self.config) data_portal = hybrid_loader.load_data_portal(myopic_index=None) - instance = build_instance(data_portal, silent=self.config.silent) + instance = build_instance( + data_portal, + silent=self.config.silent, + extensions=self.config.extensions, + ) logger.info('Model build process complete.') return instance @@ -220,6 +224,7 @@ def _run_check_mode(self) -> None: silent=self.config.silent, keep_lp_file=self.config.save_lp_file, lp_path=self.config.output_path, + extensions=self.config.extensions, ) if not self.config.price_check: logger.warning('Price check is automatically enabled for CHECK mode.') @@ -238,6 +243,7 @@ def _run_perfect_foresight(self) -> None: silent=self.config.silent, keep_lp_file=self.config.save_lp_file, lp_path=self.config.output_path, + extensions=self.config.extensions, ) if self.config.price_check: price_checker(instance) diff --git a/temoa/cli.py b/temoa/cli.py index 371b0d733..9feee14d4 100644 --- a/temoa/cli.py +++ b/temoa/cli.py @@ -557,13 +557,14 @@ def _copy_tutorial_resources(target_config: Path, target_database: Path) -> None """ Copy tutorial resource files directly to target locations. - The database is generated from the SQL source file to ensure it uses - the latest schema with unit-compliant data (single source of truth). + The database is generated by applying the canonical v4 schema first, + then loading tutorial data SQL. Args: target_config: Path where configuration file should be copied target_database: Path where database file should be created """ + import contextlib import sqlite3 try: @@ -572,6 +573,7 @@ def _copy_tutorial_resources(target_config: Path, target_database: Path) -> None config_resource = base / 'config_sample.toml' sql_resource = base / 'utopia.sql' mc_settings_resource = base / 'mc_settings.csv' + schema_resource = resources.files('temoa.db_schema') / 'temoa_schema_v4.sql' # Copy configuration file with config_resource.open('rb') as source: @@ -582,11 +584,21 @@ def _copy_tutorial_resources(target_config: Path, target_database: Path) -> None if target_database.exists(): target_database.unlink() - # Generate database from SQL source (single source of truth) + # Generate database from canonical schema and tutorial data source + schema_content = schema_resource.read_text(encoding='utf-8') sql_content = sql_resource.read_text(encoding='utf-8') - with sqlite3.connect(target_database) as conn: + with contextlib.closing(sqlite3.connect(target_database)) as conn: + conn.executescript(schema_content) + conn.execute('PRAGMA foreign_keys = OFF;') conn.executescript(sql_content) + conn.execute('PRAGMA foreign_keys = ON;') + fk_violations = conn.execute('PRAGMA foreign_key_check;').fetchall() + if fk_violations: + raise sqlite3.IntegrityError( + f'Foreign key check failed after tutorial data load: {fk_violations[:5]}' + ) + conn.commit() # Copy Monte Carlo settings with mc_settings_resource.open('rb') as source: @@ -600,6 +612,7 @@ def _copy_tutorial_resources(target_config: Path, target_database: Path) -> None fallback_config = Path(__file__).parent / 'tutorial_assets' / 'config_sample.toml' fallback_sql = Path(__file__).parent / 'tutorial_assets' / 'utopia.sql' fallback_mc = Path(__file__).parent / 'tutorial_assets' / 'mc_settings.csv' + fallback_schema = Path(__file__).parent / 'db_schema' / 'temoa_schema_v4.sql' if not fallback_config.exists(): raise FileNotFoundError( @@ -613,6 +626,12 @@ def _copy_tutorial_resources(target_config: Path, target_database: Path) -> None f'SQL: {fallback_sql}' ) from e + if not fallback_schema.exists(): + raise FileNotFoundError( + f'Schema not found. Tried package resources and fallback path:\n' + f'Schema: {fallback_schema}' + ) from e + # Copy config file using fallback path shutil.copy2(fallback_config, target_config) @@ -620,10 +639,20 @@ def _copy_tutorial_resources(target_config: Path, target_database: Path) -> None if target_database.exists(): target_database.unlink() - # Generate database from SQL source + # Generate database from canonical schema and tutorial data source + sql_content = fallback_sql.read_text(encoding='utf-8') - with sqlite3.connect(target_database) as conn: - conn.executescript(fallback_sql.read_text(encoding='utf-8')) + with contextlib.closing(sqlite3.connect(target_database)) as conn: + conn.executescript(fallback_schema.read_text(encoding='utf-8')) + conn.execute('PRAGMA foreign_keys = OFF;') + conn.executescript(sql_content) + conn.execute('PRAGMA foreign_keys = ON;') + fk_violations = conn.execute('PRAGMA foreign_key_check;').fetchall() + if fk_violations: + raise sqlite3.IntegrityError( + f'Foreign key check failed after tutorial data load: {fk_violations[:5]}' + ) from None + conn.commit() # Copy mc_settings from fallback shutil.copy2(fallback_mc, target_config.parent / 'mc_settings.csv') diff --git a/temoa/components/capacity.py b/temoa/components/capacity.py index 1383e09f7..82e553126 100644 --- a/temoa/components/capacity.py +++ b/temoa/components/capacity.py @@ -18,7 +18,7 @@ from deprecated import deprecated from pyomo.environ import value -from .utils import get_capacity_factor +from .utils import get_adjusted_existing_capacity, get_capacity_factor if TYPE_CHECKING: from temoa.core.model import TemoaModel @@ -292,7 +292,9 @@ def annual_retirement_constraint( # Exact EOL. No v_capacity or v_retired_capacity for this period. if p == model.time_optimize.first(): # Must be existing capacity. Apply survival curve to existing cap - cap_begin = model.existing_capacity[r, t, v] * model.lifetime_survival_curve[r, p, t, v] + cap_begin = get_adjusted_existing_capacity(model, r, t, v) * value( + model.lifetime_survival_curve[r, p, t, v] + ) else: # Get previous capacity and continue survival curve p_prev = model.time_optimize.prev(p) @@ -545,8 +547,9 @@ def adjusted_capacity_constraint( the time when that retirement occurred (treated here as at the beginning of each period). """ + built_capacity: ExprLike if v in model.time_exist: - built_capacity = value(model.existing_capacity[r, t, v]) + built_capacity = get_adjusted_existing_capacity(model, r, t, v) else: built_capacity = model.v_new_capacity[r, t, v] diff --git a/temoa/components/costs.py b/temoa/components/costs.py index 7814c2c4e..9edaf1a23 100644 --- a/temoa/components/costs.py +++ b/temoa/components/costs.py @@ -124,7 +124,7 @@ def lifetime_loan_process_indices(model: TemoaModel) -> set[tuple[Region, Techno def loan_cost( capacity: float | Var | ComponentData, - invest_cost: float, + invest_cost: float | Expression, loan_annualize: float, lifetime_loan_process: float | int, lifetime_process: float, @@ -190,7 +190,7 @@ def loan_cost_survival_curve( t: Technology, v: Vintage, capacity: float | Var | ComponentData, - invest_cost: float, + invest_cost: float | Expression, loan_annualize: float, lifetime_loan_process: float | int, p_0: Period, @@ -266,7 +266,7 @@ def loan_cost_survival_curve( def fixed_or_variable_cost( cap_or_flow: float | Var | ComponentData, - cost_factor: float, + cost_factor: float | Expression, cost_years: float | ComponentData, global_discount_rate: float | None, p_0: float, @@ -715,3 +715,102 @@ def param_loan_annualize_rule( lln = value(model.loan_lifetime_process[r, t, v]) annualized_rate = pv_to_annuity(dr, lln) return annualized_rate + + +# ============================================================================ +# DATA PULLING UTILITIES +# ============================================================================ + + +def poll_loan_costs( + loan_life: float, + loan_annualize: float, + capacity: float, + invest_cost: float, + process_life: int, + p_0: int, + p_e: int, + global_discount_rate: float, + vintage: int, + **kwargs: object, +) -> tuple[float, float]: + """ + Calculate Loan costs by calling the loan annualize and loan cost functions in temoa_rules + :return: tuple of [model-view discounted cost, un-discounted annuity] + """ + # dev note: this is a passthrough function. Sole intent is to use the EXACT formula the + # model uses for these costs + discounted_cost = loan_cost( + capacity, + invest_cost, + loan_annualize=loan_annualize, + lifetime_loan_process=loan_life, + lifetime_process=process_life, + p_0=p_0, + p_e=p_e, + global_discount_rate=global_discount_rate, + vintage=vintage, + ) + # Override the GDR to get the undiscounted value + undiscounted_cost = loan_cost( + capacity, + invest_cost, + loan_annualize=loan_annualize, + lifetime_loan_process=loan_life, + lifetime_process=process_life, + p_0=p_0, + p_e=p_e, + global_discount_rate=0, + vintage=vintage, + ) + return float(value(discounted_cost)), float(value(undiscounted_cost)) + + +def poll_loan_costs_survival_curve( + model: TemoaModel, + r: Region, + t: Technology, + v: Vintage, + loan_life: float, + loan_annualize: float, + capacity: float, + invest_cost: float, + p_0: Period, + p_e: Period, + global_discount_rate: float, + **kwargs: object, +) -> tuple[float, float]: + """ + Calculate Loan costs by calling the loan annualize and loan cost functions in temoa_rules + :return: tuple of [model-view discounted cost, un-discounted annuity] + """ + # dev note: this is a passthrough function. Sole intent is to use the EXACT formula the + # model uses for these costs + discounted_cost = loan_cost_survival_curve( + model, + r, + t, + v, + capacity, + invest_cost, + loan_annualize, + lifetime_loan_process=loan_life, + p_0=p_0, + p_e=p_e, + global_discount_rate=global_discount_rate, + ) + # Override the GDR to get the undiscounted value + undiscounted_cost = loan_cost_survival_curve( + model, + r, + t, + v, + capacity, + invest_cost, + loan_annualize, + lifetime_loan_process=loan_life, + p_0=p_0, + p_e=p_e, + global_discount_rate=0, + ) + return float(value(discounted_cost)), float(value(undiscounted_cost)) diff --git a/temoa/components/limits.py b/temoa/components/limits.py index f7f5d5d9c..702adb171 100644 --- a/temoa/components/limits.py +++ b/temoa/components/limits.py @@ -20,7 +20,11 @@ import temoa.components.geography as geography import temoa.components.technology as technology -from temoa.components.utils import Operator, get_variable_efficiency, operator_expression +from temoa.components.utils import ( + Operator, + get_variable_efficiency, + operator_expression, +) if TYPE_CHECKING: from pyomo.core import Expression @@ -130,64 +134,6 @@ def limit_tech_output_split_average_constraint_indices( } -def limit_growth_capacity_indices(model: TemoaModel) -> set[tuple[Region, Period, Technology, str]]: - return { - (r, p, t, op) - for r, t, op in model.limit_growth_capacity.sparse_keys() - for p in model.time_optimize - } - - -def limit_degrowth_capacity_indices( - model: TemoaModel, -) -> set[tuple[Region, Period, Technology, str]]: - return { - (r, p, t, op) - for r, t, op in model.limit_degrowth_capacity.sparse_keys() - for p in model.time_optimize - } - - -def limit_growth_new_capacity_indices( - model: TemoaModel, -) -> set[tuple[Region, Period, Technology, str]]: - return { - (r, p, t, op) - for r, t, op in model.limit_growth_new_capacity.sparse_keys() - for p in model.time_optimize - } - - -def limit_degrowth_new_capacity_indices( - model: TemoaModel, -) -> set[tuple[Region, Period, Technology, str]]: - return { - (r, p, t, op) - for r, t, op in model.limit_degrowth_new_capacity.sparse_keys() - for p in model.time_optimize - } - - -def limit_growth_new_capacity_delta_indices( - model: TemoaModel, -) -> set[tuple[Region, Period, Technology, str]]: - return { - (r, p, t, op) - for r, t, op in model.limit_growth_new_capacity_delta.sparse_keys() - for p in model.time_optimize - } - - -def limit_degrowth_new_capacity_delta_indices( - model: TemoaModel, -) -> set[tuple[Region, Period, Technology, str]]: - return { - (r, p, t, op) - for r, t, op in model.limit_degrowth_new_capacity_delta.sparse_keys() - for p in model.time_optimize - } - - def limit_seasonal_capacity_factor_constraint_indices( model: TemoaModel, ) -> set[tuple[Region, Period, Season, Technology, str]]: @@ -498,10 +444,8 @@ def limit_annual_capacity_factor_constraint( regions = geography.gather_group_regions(model, r) techs = technology.gather_group_techs(model, t) - if TYPE_CHECKING: - activity_rptvo = cast('Expression', 0) - else: - activity_rptvo = 0 + activity_rptvo = 0 + for _t in techs: if _t not in model.tech_annual: activity_rptvo += quicksum( @@ -948,393 +892,6 @@ def limit_emission_constraint( return expr -def limit_growth_capacity_constraint_rule( - model: TemoaModel, r: Region, p: Period, t: Technology, op: str -) -> ExprLike: - r"""Constrain ramp up rate of available capacity""" - return limit_growth_capacity(model, r, p, t, op, False) - - -def limit_degrowth_capacity_constraint_rule( - model: TemoaModel, r: Region, p: Period, t: Technology, op: str -) -> ExprLike: - r"""Constrain ramp down rate of available capacity""" - return limit_growth_capacity(model, r, p, t, op, True) - - -def limit_growth_capacity( - model: TemoaModel, r: Region, p: Period, t: Technology, op: str, degrowth: bool = False -) -> ExprLike: - r""" - Constrain the change of capacity available between periods. - Forces the model to ramp up and down the availability of new technologies - more smoothly. Has constant (seed, :math:`S_{r,t}`) and proportional - (rate, :math:`R_{r,t}`) terms. This can be defined for a technology group - instead of one technology, in which case, capacity available is summed over - all technologies in the group. In the first period, previous available - capacity :math:`\mathbf{CAPAVL}_{r,p,t}` is replaced by previous existing - capacity, if any can be found. - - .. math:: - :label: Limit (De)Growth Capacity - - \begin{aligned}\text{Growth:}\\ - &\mathbf{CAPAVL}_{r,p,t} - \quad \le, \ge, \text{or} = \quad - S_{r,t} + (1+R_{r,t}) \cdot \mathbf{CAPAVL}_{r,p_{prev},t} - \end{aligned} - - \qquad \forall \{r, p, t\} \in \Theta_{\text{limit\_growth\_capacity}} - - - \begin{aligned}\text{Degrowth:}\\ - &\mathbf{CAPAVL}_{r,p_{prev},t} - \quad \le, \ge, \text{or} = \quad S_{r,t} + (1+R_{r,t}) \cdot \mathbf{CAPAVL}_{r,p,t} - \end{aligned} - - \qquad \forall \{r, p, t\} \in \Theta_{\text{limit\_degrowth\_capacity}} - """ - - regions = geography.gather_group_regions(model, r) - techs = technology.gather_group_techs(model, t) - - growth = model.limit_degrowth_capacity if degrowth else model.limit_growth_capacity - rate = 1 + value(growth[r, t, op][0]) - seed = value(growth[r, t, op][1]) - cap_rpt = model.v_capacity_available_by_period_and_tech - - # relevant r, p, t indices - cap_indices = {(_r, _p, _t) for _r, _p, _t in cap_rpt.keys() if _t in techs and _r in regions} - # periods the technology can have capacity in this region (sorted) - periods = sorted({_p for _r, _p, _t in cap_rpt}) - - if len(periods) == 0: - if p == model.time_optimize.first(): - msg = ( - 'Tried to set {}rowthCapacity constraint {} but there are no periods where this ' - 'technology is available in this region. Constraint skipped.' - ).format('Deg' if degrowth else 'G', (r, t)) - logger.warning(msg) - return Constraint.Skip - - # Only warn in p0 so we dont dump multiple warnings - if p == periods[0]: - if seed == 0: - msg = ( - 'No constant term (seed) provided for {}rowthCapacity constraint {}. ' - 'No capacity will be built in any period following one with zero capacity.' - ).format('Deg' if degrowth else 'G', (r, t)) - logger.info(msg) - gaps = [ - _p - for _p in model.time_optimize - if _p not in periods and min(periods) < _p < max(periods) - ] - if gaps: - msg = ( - 'Constructing {}rowthCapacity constraint {} and there are period gaps in which' - 'capacity cannot exist in this region ({}). Capacity in these periods ' - 'will be treated as zero which may cause infeasibility or other problems.' - ).format('Deg' if degrowth else 'G', (r, t), gaps) - logger.warning(msg) - - # sum available capacity in this period - capacity = quicksum(cap_rpt[_r, _p, _t] for _r, _p, _t in cap_indices if _p == p) - - if p == model.time_optimize.first(): - # First future period. Grab available capacity in last existing period - # Adjust in-line for past PLF because we are constraining available capacity - p_prev = model.time_exist.last() - capacity_prev = sum( - value(model.existing_capacity[_r, _t, _v]) - * min(1.0, (_v + value(model.lifetime_process[_r, _t, _v]) - p_prev) / (p - p_prev)) - for _r, _t, _v in model.existing_capacity.sparse_keys() - if _r in regions - and _t in techs - and _v + value(model.lifetime_process[_r, _t, _v]) > p_prev - ) - else: - # Otherwise, grab previous future period - p_prev = model.time_optimize.prev(p) - capacity_prev = quicksum(cap_rpt[_r, _p, _t] for _r, _p, _t in cap_indices if _p == p_prev) - - if degrowth: - expr = operator_expression(capacity_prev, Operator(op), seed + capacity * rate) - else: - expr = operator_expression(capacity, Operator(op), seed + capacity_prev * rate) - - # Check if any variables are actually included before returning - if isinstance(expr, bool): - return Constraint.Skip - return expr - - -def limit_growth_new_capacity_constraint_rule( - model: TemoaModel, r: Region, p: Period, t: Technology, op: str -) -> ExprLike: - r"""Constrain ramp up rate of new capacity deployment""" - return limit_growth_new_capacity(model, r, p, t, op, False) - - -def limit_degrowth_new_capacity_constraint_rule( - model: TemoaModel, r: Region, p: Period, t: Technology, op: str -) -> ExprLike: - r"""Constrain ramp down rate of new capacity deployment""" - return limit_growth_new_capacity(model, r, p, t, op, True) - - -def limit_growth_new_capacity( - model: TemoaModel, r: Region, p: Period, t: Technology, op: str, degrowth: bool = False -) -> ExprLike: - r""" - Constrain the change of new capacity deployed between periods. - Forces the model to ramp up and down the deployment of new technologies - more smoothly. Has constant (seed, :math:`S_{r,t}`) and proportional - (rate, :math:`R_{r,t}`) terms. This can be defined for a technology group - instead of one technology, in which case, new capacity is summed over - all technologies in the group. In the first period, previous new capacity - :math:`\mathbf{NCAP}_{r,t,v_prev}` is replaced by previous existing capacity, - if any can be found. - - .. math:: - :label: Limit (De)Growth New Capacity - - \begin{aligned}\text{Growth:}\\ - &\mathbf{NCAP}_{r,t,v} - \quad \le, \ge, \text{or} = \quad - S_{r,t} + (1+R_{r,t}) \cdot \mathbf{NCAP}_{r,t,v_{prev}} - \text{ where } v=p - \end{aligned} - - \qquad \forall \{r, p, t\} \in \Theta_{\text{limit\_growth\_capacity}} - - \begin{aligned}\text{Degrowth:}\\ - &\mathbf{NCAP}_{r,t,v_{prev}} - \quad \le, \ge, \text{or} = \quad S_{r,t} + (1+R_{r,t}) \cdot \mathbf{NCAP}_{r,t,v} - \text{ where } v=p - \end{aligned} - - \qquad \forall \{r, p, t\} \in \Theta_{\text{limit\_degrowth\_capacity}} - """ - - regions = geography.gather_group_regions(model, r) - techs = technology.gather_group_techs(model, t) - - growth = model.limit_degrowth_new_capacity if degrowth else model.limit_growth_new_capacity - rate = 1 + value(growth[r, t, op][0]) - seed = value(growth[r, t, op][1]) - new_cap_rtv = model.v_new_capacity - - # relevant r, t, v indices - cap_rtv = {(_r, _t, _v) for _r, _t, _v in new_cap_rtv.keys() if _t in techs and _r in regions} - # periods the technology can be built in this region (sorted) - periods = sorted({_v for _r, _t, _v in cap_rtv}) - - if len(periods) == 0: - if p == model.time_optimize.first(): - msg = ( - 'Tried to set {}rowthNewCapacity constraint {} but there are no periods where this ' - 'technology can be built in this region. Constraint skipped.' - ).format('Deg' if degrowth else 'G', (r, t)) - logger.warning(msg) - return Constraint.Skip - - # Only warn in p0 so we dont dump multiple warnings - if p == periods[0]: - if seed == 0: - msg = ( - 'No constant term (seed) provided for {}rowthNewCapacity constraint {}. ' - 'No capacity will be built in any period following one with zero new capacity.' - ).format('Deg' if degrowth else 'G', (r, t)) - logger.info(msg) - gaps = [ - _p - for _p in model.time_optimize - if _p not in periods and min(periods) < _p < max(periods) - ] - if gaps: - msg = ( - 'Constructing {}rowthNewCapacity constraint {} and there are period gaps in which' - 'new capacity cannot be built in this region ({}). New capacity in these periods ' - 'will be treated as zero which may cause infeasibility or other problems.' - ).format('Deg' if degrowth else 'G', (r, t), gaps) - logger.warning(msg) - - # sum new capacity in this period - new_cap = quicksum(new_cap_rtv[_r, _t, _v] for _r, _t, _v in cap_rtv if _v == p) - - if p == model.time_optimize.first(): - # First future period. Grab last existing vintage - p_prev = model.time_exist.last() - new_cap_prev = sum( - value(model.existing_capacity[_r, _t, _v]) - for _r, _t, _v in model.existing_capacity.sparse_keys() - if _r in regions and _t in techs and _v == p_prev - ) - else: - # Otherwise, grab previous future vintage - p_prev = model.time_optimize.prev(p) - new_cap_prev = sum(new_cap_rtv[_r, _t, _v] for _r, _t, _v in cap_rtv if _v == p_prev) - - if degrowth: - expr = operator_expression(new_cap_prev, Operator(op), seed + new_cap * rate) - else: - expr = operator_expression(new_cap, Operator(op), seed + new_cap_prev * rate) - - # Check if any variables are actually included before returning - if isinstance(expr, bool): - return Constraint.Skip - return expr - - -def limit_growth_new_capacity_delta_constraint_rule( - model: TemoaModel, r: Region, p: Period, t: Technology, op: str -) -> ExprLike: - r"""Constrain ramp up rate of change in new capacity deployment""" - return limit_growth_new_capacity_delta(model, r, p, t, op, False) - - -def limit_degrowth_new_capacity_delta_constraint_rule( - model: TemoaModel, r: Region, p: Period, t: Technology, op: str -) -> ExprLike: - r"""Constrain ramp down rate of change in new capacity deployment""" - return limit_growth_new_capacity_delta(model, r, p, t, op, True) - - -def limit_growth_new_capacity_delta( - model: TemoaModel, r: Region, p: Period, t: Technology, op: str, degrowth: bool = False -) -> ExprLike: - r""" - Constrain the acceleration of new capacity deployed between periods. - Forces the model to ramp up and down the change in deployment of new technologies - more smoothly. Has constant (seed, :math:`S_{r,t}`) and proportional - (rate, :math:`R_{r,t}`) terms. It is recommended to leave the rate term empty - as it would prevent the possibility of inflection in the rate of deployment. - This constraint can be defined for a technology group instead of one technology, - in which case, new capacity is summed over all technologies in the group. In the - first period, previous new capacities are replaced by previous existing capacities, - if any can be found. - - .. math:: - :label: Limit (De)Growth New Capacity Delta - - \begin{aligned}\text{Growth:}\\ - &\mathbf{NCAP}_{r,t,v_i} - \mathbf{NCAP}_{r,t,v_{i-1}} - \quad \le, \ge, \text{or} = \quad S_{r,t} + (1+R_{r,t}) \cdot - (\mathbf{NCAP}_{r,t,v_{i-1}} - \mathbf{NCAP}_{r,t,v_{i-2}}) - \end{aligned} - - \text{ where } v_i=p - - \qquad \forall \{r, p, t\} \in \Theta_{\text{limit\_growth\_capacityDelta}} - - \begin{aligned}\text{Degrowth:}\\ - &\mathbf{NCAP}_{r,t,v_{i-1}} - \mathbf{NCAP}_{r,t,v_{i-2}} - \quad \le, \ge, \text{or} = \quad - S_{r,t} + (1+R_{r,t}) \cdot (\mathbf{NCAP}_{r,t,v_i} - \mathbf{NCAP}_{r,t,v_{i-1}}) - \end{aligned} - - \text{ where } v_i=p - - \qquad \forall \{r, p, t\} \in \Theta_{\text{limit\_degrowth\_capacityDelta}} - """ - - regions = geography.gather_group_regions(model, r) - techs = technology.gather_group_techs(model, t) - - growth = ( - model.limit_degrowth_new_capacity_delta - if degrowth - else model.limit_growth_new_capacity_delta - ) - rate = 1 + value(growth[r, t, op][0]) - seed = value(growth[r, t, op][1]) - new_cap_rtv = model.v_new_capacity - - # relevant r, t, v indices - cap_rtv = {(_r, _t, _v) for _r, _t, _v in new_cap_rtv.keys() if _t in techs and _r in regions} - # periods the technology can be built in this region (sorted) - periods = sorted({_v for _r, _t, _v in cap_rtv}) - - if len(periods) == 0: - if p == model.time_optimize.first(): - msg = ( - 'Tried to set {}rowthNewCapacityDelta constraint {} but there are no periods where ' - 'this technology can be built in this region. Constraint skipped.' - ).format('Deg' if degrowth else 'G', (r, t)) - logger.warning(msg) - return Constraint.Skip - - # Only warn in p0 so we dont dump multiple warnings - if p == periods[0]: - if seed == 0: - msg = ( - 'No constant term (seed) provided for {}rowthNewCapacityDelta constraint {}. ' - 'This is not recommended as deployment rates cannot inflect (change from ' - 'accelerating to decelerating or vice-versa).' - ).format('Deg' if degrowth else 'G', (r, t)) - logger.warning(msg) - gaps = [ - _p - for _p in model.time_optimize - if _p not in periods and min(periods) < _p < max(periods) - ] - if gaps: - msg = ( - 'Constructing {}rowthNewCapacityDelta constraint {} and there are period gaps in ' - 'which new capacity cannot be built in this region ({}). New capacity in these ' - 'periods will be treated as zero which may cause infeasibility or other problems.' - ).format('Deg' if degrowth else 'G', (r, t), gaps) - logger.warning(msg) - - # sum new capacity in this period - new_cap = sum(new_cap_rtv[_r, _t, _v] for _r, _t, _v in cap_rtv if _v == p) - - if p == model.time_optimize.first(): - # First planning period, pull last two existing vintages - p_prev = model.time_exist.last() - new_cap_prev = sum( - value(model.existing_capacity[_r, _t, _v]) - for _r, _t, _v in model.existing_capacity.sparse_keys() - if _r in regions and _t in techs and _v == p_prev - ) - p_prev2 = model.time_exist.prev(p_prev) - new_cap_prev2 = sum( - value(model.existing_capacity[_r, _t, _v]) - for _r, _t, _v in model.existing_capacity.sparse_keys() - if _r in regions and _t in techs and _v == p_prev2 - ) - else: - # Not the first future period. Grab previous future period - p_prev = model.time_optimize.prev(p) - new_cap_prev = sum(new_cap_rtv[_r, _t, _v] for _r, _t, _v in cap_rtv if _v == p_prev) - if p == model.time_optimize.at(2): # apparently pyomo sets are indexed 1-based - # Second future period, grab last existing vintage - p_prev2 = model.time_exist.last() - new_cap_prev2 = sum( - value(model.existing_capacity[_r, _t, _v]) - for _r, _t, _v in model.existing_capacity.sparse_keys() - if _r in regions and _t in techs and _v == p_prev2 - ) - else: - # At least the third future period. Grab last two future vintages - p_prev2 = model.time_optimize.prev(p_prev) - new_cap_prev2 = sum(new_cap_rtv[_r, _t, _v] for _r, _t, _v in cap_rtv if _v == p_prev2) - - nc_delta_prev = new_cap_prev - new_cap_prev2 - nc_delta = new_cap - new_cap_prev - - if degrowth: - expr = operator_expression(nc_delta_prev, Operator(op), seed + nc_delta * rate) - else: - expr = operator_expression(nc_delta, Operator(op), seed + nc_delta_prev * rate) - - # Check if any variables are actually included before returning - if isinstance(expr, bool): - return Constraint.Skip - return expr - - def limit_activity_constraint( model: TemoaModel, r: Region, p: Period, t: Technology, op: str ) -> ExprLike: diff --git a/temoa/components/technology.py b/temoa/components/technology.py index e9cc254d0..9957db979 100644 --- a/temoa/components/technology.py +++ b/temoa/components/technology.py @@ -17,6 +17,8 @@ from pyomo.environ import value +from temoa.components.utils import get_adjusted_existing_capacity + if TYPE_CHECKING: from collections.abc import Iterable @@ -52,7 +54,23 @@ def model_process_life_indices( the periods in which a process is active, distinct from TechLifeFracIndices that returns indices only for processes that EOL mid-period. """ - return model.active_activity_rptv + indices = { + (r, model.time_exist.last(), t, v) for r, t, v in model.existing_capacity.sparse_keys() + } + indices = indices | model.active_activity_rptv + + return indices + + +def lifetime_tech_indices(model: TemoaModel) -> set[tuple[Region, Technology]]: + """ + Based on the efficiency parameter's indices, this function returns the set of + process indices that may be specified in the lifetime_tech parameter. + """ + indices = {(r, t) for r, _, t, _, _ in set(model.efficiency.sparse_keys())} + indices = indices | {(r, t) for r, t, _ in model.existing_capacity.sparse_keys()} + + return indices def lifetime_process_indices(model: TemoaModel) -> set[tuple[Region, Technology, Vintage]]: @@ -60,7 +78,7 @@ def lifetime_process_indices(model: TemoaModel) -> set[tuple[Region, Technology, Based on the efficiency parameter's indices, this function returns the set of process indices that may be specified in the lifetime_process parameter. """ - indices = {(r, t, v) for r, i, t, v, o in set(model.efficiency.sparse_keys())} + indices = {(r, t, v) for r, _, t, v, _ in set(model.efficiency.sparse_keys())} indices = indices | set(model.existing_capacity.sparse_keys()) return indices @@ -424,14 +442,18 @@ def check_existing_capacity(model: TemoaModel) -> None: ) logger.warning(msg) continue - if t not in model.tech_all: + adjusted_cap = get_adjusted_existing_capacity(model, r, t, v) + if adjusted_cap <= 0.0: + # Was retired in a previous period (myopic mode) continue + p = model.time_optimize.first() life = value(model.lifetime_process[r, t, v]) - if (r, t, v) not in model.process_periods and v + life > model.time_optimize.first(): + if (r, t, v) not in model.process_periods and v + life > p: + surviving_cap = adjusted_cap * value(model.lifetime_survival_curve[r, p, t, v]) msg = ( - f'Existing capacity {r, t, v} with lifetime {life} and capacity {cap} ' - 'should extend into future periods but it is not in process periods. ' - 'Was it included in the Efficiency table?' + f'Existing capacity {r, t, v} with lifetime {life} and surviving capacity ' + f'{surviving_cap} should extend into future periods but is not an active ' + 'process. It may be missing from the Efficiency table or have too little ' + 'capacity to output and carry forward if running in myopic mode.' ) - logger.error(msg) - raise ValueError(msg) + logger.warning(msg) diff --git a/temoa/components/time.py b/temoa/components/time.py index 0877a40b6..cdbb05db4 100644 --- a/temoa/components/time.py +++ b/temoa/components/time.py @@ -184,11 +184,6 @@ def init_set_time_optimize(model: TemoaModel) -> list[int]: return sorted(model.time_future)[:-1] -def init_set_vintage_exist(model: TemoaModel) -> list[int]: - """Initializes the `vintage_exist` set.""" - return sorted(model.time_exist) - - def init_set_vintage_optimize(model: TemoaModel) -> list[int]: """Initializes the `vintage_optimize` set.""" return sorted(model.time_optimize) @@ -196,6 +191,11 @@ def init_set_vintage_optimize(model: TemoaModel) -> list[int]: def param_period_length(model: TemoaModel, p: Period) -> int: """Rule to calculate the length of each optimization period in years.""" + if model.time_exist and p == model.time_exist.last(): + # Need this for one specific use case (capacity growth constraints) + return model.time_future.first() - model.time_exist.last() + elif p in model.time_exist: + return -1 # Period length is not defined for existing periods except the last periods: list[int] = sorted(model.time_future) i: int = periods.index(p) return periods[i + 1] - periods[i] diff --git a/temoa/components/utils.py b/temoa/components/utils.py index 943b90183..7a8c91fae 100644 --- a/temoa/components/utils.py +++ b/temoa/components/utils.py @@ -12,10 +12,10 @@ from logging import getLogger from typing import TYPE_CHECKING -from pyomo.environ import value +from pyomo.environ import Expression, value if TYPE_CHECKING: - from pyomo.core import Expression + from pyomo.core.expr.numeric_expr import NumericValue from temoa.core.model import TemoaModel from temoa.types import ( @@ -39,7 +39,11 @@ class Operator(StrEnum): GREATER_EQUAL = 'ge' -def operator_expression(lhs: Expression, operator: Operator, rhs: Expression) -> ExprLike: +def operator_expression( + lhs: Expression | NumericValue | float, + operator: Operator, + rhs: Expression | NumericValue | float, +) -> ExprLike: match operator: case Operator.EQUAL: return lhs == rhs @@ -87,3 +91,20 @@ def get_capacity_factor( if model.is_capacity_factor_process[r, t, v]: return value(model.capacity_factor_process[r, s, d, t, v]) return value(model.capacity_factor_tech[r, s, d, t]) + + +def get_adjusted_existing_capacity( + model: TemoaModel, r: Region, t: Technology, v: Vintage +) -> float: + """ + Returns the built existing capacity adjusted for any early retirements. + + Needed for early retirements in myopic mode. Takes into account survival curves + and any early retirements that may have occurred prior to this planning step. + """ + capacity_adjustment = sum( + value(model.retired_existing_capacity[r, _p, t, v]) + / (value(model.lifetime_survival_curve[r, _p, t, v]) or 1) + for _p in model.time_exist + ) + return value(model.existing_capacity[r, t, v]) - capacity_adjustment diff --git a/temoa/core/config.py b/temoa/core/config.py index de3e34302..449024bd8 100644 --- a/temoa/core/config.py +++ b/temoa/core/config.py @@ -5,6 +5,7 @@ from pathlib import Path from temoa.core.modes import TemoaMode +from temoa.extensions.framework import normalize_extension_ids, resolve_extension_specs logger = getLogger(__name__) @@ -71,6 +72,7 @@ def __init__( output_threshold_emission: float | None = None, output_threshold_cost: float | None = None, sqlite: dict[str, object] | None = None, + extensions: list[str] | tuple[str, ...] | None = None, ): if '-' in scenario: raise ValueError( @@ -160,6 +162,9 @@ def __init__( self.output_threshold_emission = output_threshold_emission self.output_threshold_cost = output_threshold_cost self.sqlite_inputs = sqlite or {} + self.extensions = normalize_extension_ids(extensions) + # Validate extension ids eagerly so config failures happen before model build. + resolve_extension_specs(self.extensions) # SQLite performance settings # journal_mode: DELETE | TRUNCATE | PERSIST | MEMORY | WAL | OFF @@ -326,6 +331,7 @@ def __repr__(self) -> str: msg += '{:>{}s}: {}\n'.format('Scenario', width, self.scenario) msg += '{:>{}s}: {}\n'.format('Scenario mode', width, self.scenario_mode.name) + msg += '{:>{}s}: {}\n'.format('Enabled extensions', width, ', '.join(self.extensions)) msg += '{:>{}s}: {}\n'.format('Config file', width, self.config_file) msg += '{:>{}s}: {}\n'.format('Data source', width, self.input_database) msg += '{:>{}s}: {}\n'.format('Output database target', width, self.output_database) diff --git a/temoa/core/model.py b/temoa/core/model.py index 6c492e2a2..24d4dbb08 100755 --- a/temoa/core/model.py +++ b/temoa/core/model.py @@ -9,6 +9,7 @@ """ import logging +from collections.abc import Sequence from typing import TYPE_CHECKING from pyomo.core import BuildCheck, Set, Var @@ -38,6 +39,10 @@ technology, time, ) +from temoa.extensions.economies_of_scale.core.model import ( + register_early_eos_components, +) +from temoa.extensions.framework import apply_model_extension_hooks, resolve_extension_specs from temoa.model_checking.validators import ( no_slash_or_pipe, region_check, @@ -99,8 +104,15 @@ class TemoaModel(AbstractModel): # this is used in several places outside this class, and this provides no-build access to it default_lifetime_tech = 40 - def __init__(self, *args: object, **kwargs: object) -> None: + def __init__( + self, + *args: object, + extensions: Sequence[str] | None = None, + **kwargs: object, + ) -> None: AbstractModel.__init__(self, *args, **kwargs) + self.enabled_extensions = tuple(extensions or ()) + self.extension_specs = resolve_extension_specs(self.enabled_extensions) ################################################ # Internally used Data Containers # @@ -212,9 +224,9 @@ def __init__(self, *args: object, **kwargs: object) -> None: ordered=True, initialize=time.init_set_time_optimize, within=self.time_future ) # Define time period vintages to track capacity installation - self.vintage_exist = Set(ordered=True, initialize=time.init_set_vintage_exist) + self.vintage_exist = Set(ordered=True) self.vintage_optimize = Set(ordered=True, initialize=time.init_set_vintage_optimize) - self.vintage_all = Set(initialize=self.time_exist | self.time_optimize) + self.vintage_all = Set(initialize=self.vintage_exist | self.time_optimize) # Perform some basic validation on the specified time periods. self.validate_time = BuildAction(rule=time.validate_time) @@ -335,7 +347,9 @@ def __init__(self, *args: object, **kwargs: object) -> None: # Define time-related parameters # Basic period construction self.time_sequencing = Set() # How do states carry between time segments? - self.period_length = Param(self.time_optimize, initialize=time.param_period_length) + self.period_length = Param( + self.time_optimize | self.time_exist, initialize=time.param_period_length + ) self.days_per_period = Param(domain=PositiveReals, default=365.0) self.time_of_day_hours = Param(self.time_of_day, domain=PositiveReals, default=1.0) self.segment_fraction_per_season = Param(self.time_season) @@ -383,6 +397,10 @@ def __init__(self, *args: object, **kwargs: object) -> None: self.capacity_to_activity = Param(self.regional_indices, self.tech_all, default=1) self.existing_capacity = Param(self.regional_indices, self.tech_exist, self.vintage_exist) + # This is needed to handle past retirements in myopic mode. Maybe it will find other uses. + self.retired_existing_capacity = Param( + self.regional_indices, self.time_exist, self.tech_exist, self.vintage_exist, default=0 + ) # Dev Note: The below is temporarily useful for passing down to validator to find # set violations @@ -431,9 +449,8 @@ def __init__(self, *args: object, **kwargs: object) -> None: default=1, ) - self.lifetime_tech = Param( - self.regional_indices, self.tech_all, default=TemoaModel.default_lifetime_tech - ) + self.lifetime_tech_rt = Set(dimen=2, initialize=technology.lifetime_tech_indices) + self.lifetime_tech = Param(self.lifetime_tech_rt, default=TemoaModel.default_lifetime_tech) self.lifetime_process_rtv = Set(dimen=3, initialize=technology.lifetime_process_indices) self.lifetime_process = Param( @@ -443,7 +460,7 @@ def __init__(self, *args: object, **kwargs: object) -> None: self.lifetime_survival_curve = Param( self.regional_indices, Integers, - self.tech_all, + self.tech_all | self.tech_exist, self.vintage_all, default=technology.get_default_survival, validate=validate_0to1, @@ -549,6 +566,9 @@ def __init__(self, *args: object, **kwargs: object) -> None: ) self.cost_invest = Param(self.cost_invest_rtv) + # Inject cost_invest_eos extension components prior to loan params, if active + register_early_eos_components(self) + self.default_loan_rate = Param(domain=NonNegativeReals) self.loan_rate = Param( self.cost_invest_rtv, domain=NonNegativeReals, default=costs.get_default_loan_rate @@ -620,25 +640,6 @@ def __init__(self, *args: object, **kwargs: object) -> None: self.limit_annual_capacity_factor_constraint_rtvo, validate=validate_0to1 ) - self.limit_growth_capacity = Param( - self.regional_global_indices, self.tech_or_group, self.operator - ) - self.limit_degrowth_capacity = Param( - self.regional_global_indices, self.tech_or_group, self.operator - ) - self.limit_growth_new_capacity = Param( - self.regional_global_indices, self.tech_or_group, self.operator - ) - self.limit_degrowth_new_capacity = Param( - self.regional_global_indices, self.tech_or_group, self.operator - ) - self.limit_growth_new_capacity_delta = Param( - self.regional_global_indices, self.tech_or_group, self.operator - ) - self.limit_degrowth_new_capacity_delta = Param( - self.regional_global_indices, self.tech_or_group, self.operator - ) - self.limit_emission_constraint_rpe = Set( within=self.regional_global_indices * self.time_optimize @@ -987,51 +988,6 @@ def __init__(self, *args: object, **kwargs: object) -> None: ['Starting LimitGrowth and Activity Constraints'], rule=progress_check ) - self.limit_growth_capacity_constraint_rpt = Set( - dimen=4, initialize=limits.limit_growth_capacity_indices - ) - self.limit_growth_capacity_constraint = Constraint( - self.limit_growth_capacity_constraint_rpt, - rule=limits.limit_growth_capacity_constraint_rule, - ) - self.limit_degrowth_capacity_constraint_rpt = Set( - dimen=4, initialize=limits.limit_degrowth_capacity_indices - ) - self.limit_degrowth_capacity_constraint = Constraint( - self.limit_degrowth_capacity_constraint_rpt, - rule=limits.limit_degrowth_capacity_constraint_rule, - ) - - self.limit_growth_new_capacity_constraint_rpt = Set( - dimen=4, initialize=limits.limit_growth_new_capacity_indices - ) - self.limit_growth_new_capacity_constraint = Constraint( - self.limit_growth_new_capacity_constraint_rpt, - rule=limits.limit_growth_new_capacity_constraint_rule, - ) - self.limit_degrowth_new_capacity_constraint_rpt = Set( - dimen=4, initialize=limits.limit_degrowth_new_capacity_indices - ) - self.limit_degrowth_new_capacity_constraint = Constraint( - self.limit_degrowth_new_capacity_constraint_rpt, - rule=limits.limit_degrowth_new_capacity_constraint_rule, - ) - - self.limit_growth_new_capacity_delta_constraint_rpt = Set( - dimen=4, initialize=limits.limit_growth_new_capacity_delta_indices - ) - self.limit_growth_new_capacity_delta_constraint = Constraint( - self.limit_growth_new_capacity_delta_constraint_rpt, - rule=limits.limit_growth_new_capacity_delta_constraint_rule, - ) - self.limit_degrowth_new_capacity_delta_constraint_rpt = Set( - dimen=4, initialize=limits.limit_degrowth_new_capacity_delta_indices - ) - self.limit_degrowth_new_capacity_delta_constraint = Constraint( - self.limit_degrowth_new_capacity_delta_constraint_rpt, - rule=limits.limit_degrowth_new_capacity_delta_constraint_rule, - ) - self.limit_activity_constraint = Constraint( self.limit_activity_constraint_rpt, rule=limits.limit_activity_constraint ) @@ -1144,6 +1100,8 @@ def __init__(self, *args: object, **kwargs: object) -> None: rule=emissions.linked_emissions_tech_constraint, ) + apply_model_extension_hooks(self, self.extension_specs) + self.progress_marker_9 = BuildAction(['Finished Constraints'], rule=progress_check) diff --git a/temoa/data_io/component_manifest.py b/temoa/data_io/component_manifest.py index e22fdf673..7521b34c8 100644 --- a/temoa/data_io/component_manifest.py +++ b/temoa/data_io/component_manifest.py @@ -16,11 +16,14 @@ to add a new `LoadItem` to this manifest. """ +from collections.abc import Sequence + from temoa.core.model import TemoaModel from temoa.data_io.loader_manifest import LoadItem +from temoa.extensions.framework import append_extension_manifest_items, resolve_extension_specs -def build_manifest(model: TemoaModel) -> list[LoadItem]: +def build_manifest(model: TemoaModel, extension_ids: Sequence[str] | None = None) -> list[LoadItem]: """ Builds the manifest of all data components to be loaded into the Pyomo model. @@ -288,6 +291,14 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: is_period_filtered=False, # Custom loader handles all logic is_table_required=False, ), + LoadItem( + component=model.retired_existing_capacity, + table='output_retired_capacity', + columns=['region', 'period', 'tech', 'vintage', 'cap_early'], + custom_loader_name='_load_retired_existing_capacity', + is_period_filtered=False, # Custom loader handles all logic + is_table_required=False, + ), LoadItem( component=model.cost_invest, table='cost_invest', @@ -296,6 +307,7 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: validation_map=(0, 1, 2), is_period_filtered=False, is_table_required=False, + index_set=model.cost_invest_rtv, ), LoadItem( component=model.cost_fixed, @@ -316,6 +328,7 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: table='cost_emission', columns=['region', 'period', 'emis_comm', 'cost'], is_table_required=False, + index_set=model.cost_emission_rpe, ), LoadItem( component=model.loan_rate, @@ -380,6 +393,7 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: component=model.demand, table='demand', columns=['region', 'period', 'commodity', 'demand'], + index_set=model.demand_constraint_rpc, ), LoadItem( component=model.demand_specific_distribution, @@ -419,8 +433,7 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: component=model.lifetime_tech, table='lifetime_tech', columns=['region', 'tech', 'lifetime'], - validator_name='viable_rt', - validation_map=(0, 1), + custom_loader_name='_load_lifetime_tech', is_period_filtered=False, is_table_required=False, ), @@ -428,8 +441,7 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: component=model.lifetime_process, table='lifetime_process', columns=['region', 'tech', 'vintage', 'lifetime'], - validator_name='viable_rtv', - validation_map=(0, 1, 2), + custom_loader_name='_load_lifetime_process', is_period_filtered=False, is_table_required=False, ), @@ -437,8 +449,7 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: component=model.lifetime_survival_curve, table='lifetime_survival_curve', columns=['region', 'period', 'tech', 'vintage', 'fraction'], - validator_name='viable_rtv', - validation_map=(0, 2, 3), + custom_loader_name='_load_lifetime_survival_curve', is_period_filtered=False, is_table_required=False, ), @@ -493,6 +504,7 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: columns=['region', 'period', 'tech_group', 'requirement'], custom_loader_name='_load_rps_requirement', is_table_required=False, + index_set=model.renewable_portfolio_standard_constraint_rpg, ), LoadItem( component=model.capacity_credit, @@ -542,6 +554,7 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: validation_map=(0, 3), is_period_filtered=False, is_table_required=False, + index_set=model.limit_storage_fraction_param_rsdt, ), LoadItem( component=model.emission_activity, @@ -606,6 +619,7 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: validator_name='viable_rpt', validation_map=(0, 1, 2), is_table_required=False, + index_set=model.limit_capacity_constraint_rpt, ), LoadItem( component=model.limit_new_capacity, @@ -615,6 +629,7 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: validation_map=(0, 1, 2), is_period_filtered=False, is_table_required=False, + index_set=model.limit_new_capacity_constraint_rtv, ), LoadItem( component=model.limit_capacity_share, @@ -623,6 +638,7 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: validator_name='viable_rpt', validation_map=(0, 1, 2), is_table_required=False, + index_set=model.limit_capacity_share_constraint_rpgg, ), LoadItem( component=model.limit_new_capacity_share, @@ -632,6 +648,7 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: validation_map=(0, 1, 3), is_period_filtered=False, is_table_required=False, + index_set=model.limit_new_capacity_share_constraint_rggv, ), LoadItem( component=model.limit_activity, @@ -640,6 +657,7 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: validator_name='viable_rpt', validation_map=(0, 1, 2), is_table_required=False, + index_set=model.limit_activity_constraint_rpt, ), LoadItem( component=model.limit_activity_share, @@ -648,6 +666,7 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: validator_name='viable_rpt', validation_map=(0, 1, 2), is_table_required=False, + index_set=model.limit_activity_share_constraint_rpgg, ), LoadItem( component=model.limit_resource, @@ -657,6 +676,7 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: validation_map=(0, 1), is_period_filtered=False, is_table_required=False, + index_set=model.limit_resource_constraint_rt, ), LoadItem( component=model.limit_seasonal_capacity_factor, @@ -666,6 +686,7 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: validation_map=(0, 2), is_period_filtered=False, is_table_required=False, + index_set=model.limit_seasonal_capacity_factor_constraint_rst, ), LoadItem( component=model.limit_annual_capacity_factor, @@ -675,12 +696,14 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: validation_map=(0, 1, 2, 3), is_period_filtered=False, is_table_required=False, + index_set=model.limit_annual_capacity_factor_constraint_rtvo, ), LoadItem( component=model.limit_emission, table='limit_emission', columns=['region', 'period', 'emis_comm', 'operator', 'value'], is_table_required=False, + index_set=model.limit_emission_constraint_rpe, ), LoadItem( component=model.limit_tech_input_split, @@ -729,4 +752,5 @@ def build_manifest(model: TemoaModel) -> list[LoadItem]: is_table_required=False, ), ] - return manifest + extension_specs = resolve_extension_specs(extension_ids) + return append_extension_manifest_items(model, manifest, extension_specs) diff --git a/temoa/data_io/hybrid_loader.py b/temoa/data_io/hybrid_loader.py index a4e2ce4d2..42ad02e97 100644 --- a/temoa/data_io/hybrid_loader.py +++ b/temoa/data_io/hybrid_loader.py @@ -24,6 +24,7 @@ import time from collections import defaultdict from logging import getLogger +from operator import itemgetter from sqlite3 import Connection, Cursor, OperationalError from typing import TYPE_CHECKING, cast @@ -33,6 +34,12 @@ from temoa.core.model import TemoaModel from temoa.core.modes import TemoaMode from temoa.data_io.component_manifest import build_manifest +from temoa.extensions.framework import ( + assert_disabled_extension_tables_are_empty, + ensure_enabled_extension_tables_exist, + merge_regional_group_tables, + resolve_extension_specs, +) from temoa.extensions.myopic.myopic_index import MyopicIndex from temoa.model_checking import element_checker, network_model_data from temoa.model_checking.commodity_network_manager import CommodityNetworkManager @@ -43,11 +50,12 @@ from temoa.core.config import TemoaConfig from temoa.data_io.loader_manifest import LoadItem + from temoa.types.core_types import Region, Technology, Vintage logger = getLogger(__name__) # A manifest of tables that may contain region groups, used by a custom loader. -tables_with_regional_groups = { +BASE_REGIONAL_GROUP_TABLES = { 'limit_annual_capacity_factor': 'region', 'limit_emission': 'region', 'limit_seasonal_capacity_factor': 'region', @@ -58,12 +66,6 @@ 'limit_capacity_share': 'region', 'limit_new_capacity_share': 'region', 'limit_resource': 'region', - 'limit_growth_capacity': 'region', - 'limit_degrowth_capacity': 'region', - 'limit_growth_new_capacity': 'region', - 'limit_degrowth_new_capacity': 'region', - 'limit_growth_new_capacity_delta': 'region', - 'limit_degrowth_new_capacity_delta': 'region', } @@ -88,16 +90,29 @@ def __init__(self, db_connection: Connection, config: TemoaConfig) -> None: self.con = db_connection self.config = config self.myopic_index: MyopicIndex | None = None + self.extension_specs = resolve_extension_specs(self.config.extensions) + self.model = TemoaModel(extensions=self.config.extensions) + self.tables_with_regional_groups = merge_regional_group_tables( + BASE_REGIONAL_GROUP_TABLES, self.extension_specs + ) + ensure_enabled_extension_tables_exist( + self.con, + self.extension_specs, + input_database=str(self.config.input_database), + silent=self.config.silent, + ) + assert_disabled_extension_tables_are_empty(self.con, self.extension_specs) # Build the data loading manifest and a name-based map for quick lookup - model = TemoaModel() - self.manifest = build_manifest(model) + self.manifest = build_manifest(self.model, extension_ids=self.config.extensions) self.manifest_map = {item.component.name: item for item in self.manifest} # --- Data containers and filters populated during loading --- self.manager: CommodityNetworkManager | None = None self.efficiency_values: list[tuple[object, ...]] = [] self.data: dict[str, object] | None = None + self.viable_existing_rt: set[tuple[Region, Technology]] = set() + self.viable_existing_rtv: set[tuple[Region, Technology, Vintage]] = set() # --- Viable sets for source-trace filtering --- self.viable_techs: ViableSet | None = None @@ -201,7 +216,7 @@ def create_data_dict(self, myopic_index: MyopicIndex | None = None) -> dict[str, data: dict[str, object] = {} cur = self.con.cursor() - model = TemoaModel() + model = self.model # Load critical time sets first, as they index other components if myopic_index: @@ -231,6 +246,10 @@ def create_data_dict(self, myopic_index: MyopicIndex | None = None) -> dict[str, for item in self.manifest: # 1. Fetch data from the database raw_data = self._fetch_data(cur, item, myopic_index) + if item.index_length and len(item.columns) - item.index_length > 1: + raw_data = [ + (*row[0 : item.index_length], row[item.index_length :]) for row in raw_data + ] # 2. Validate/filter data filtered_data = self._filter_data(raw_data, item, use_raw_data) @@ -517,7 +536,7 @@ def _load_regional_global_indices( """ Aggregates region and group names from the Region table and all Limit tables. """ - model = TemoaModel() + model = self.model cur = self.con.cursor() regions_and_groups: set[str] = set() @@ -526,7 +545,7 @@ def _load_regional_global_indices( t[0] for t in cur.execute('SELECT region FROM main.region').fetchall() ) - for table, field_name in tables_with_regional_groups.items(): + for table, field_name in self.tables_with_regional_groups.items(): if self.table_exists(table): regions_and_groups.update( t[0] for t in cur.execute(f'SELECT {field_name} FROM main.{table}').fetchall() @@ -629,6 +648,120 @@ def _load_existing_capacity( if rows_to_load: tech_exist_data = sorted({(row[1],) for row in rows_to_load}) self._load_component_data(data, model.tech_exist, tech_exist_data) + vintage_exist_data = sorted({(row[2],) for row in rows_to_load}) + self._load_component_data(data, model.vintage_exist, vintage_exist_data) + + # Collect existing capacity data indices + self.viable_existing_techs = {row[1] for row in rows_to_load} + self.viable_existing_rt = {(row[0], row[1]) for row in rows_to_load} + self.viable_existing_rtv = {(row[0], row[1], row[2]) for row in rows_to_load} + + def _load_retired_existing_capacity( + self, + data: dict[str, object], + raw_data: Sequence[tuple[object, ...]], + filtered_data: Sequence[tuple[object, ...]], + ) -> None: + """ + Only needed in myopic to bring past early retirement decisions forward + """ + if not self.table_exists('output_retired_capacity'): + logger.info( + "Table 'output_retired_capacity' not found. Skipping loading " + 'of retired existing capacity.' + ) + return + + model = TemoaModel() + cur = self.con.cursor() + mi = self.myopic_index + + if not mi: + # for now, we only use this in myopic mode + return + + rows_to_load = [] + prev_period_res = cur.execute( + 'SELECT MAX(period) FROM time_period WHERE period < ?', (mi.base_year,) + ).fetchone() + prev_period = prev_period_res[0] if prev_period_res else -1 + rows_to_load = cur.execute( + 'SELECT region, period, tech, vintage, cap_early FROM output_retired_capacity WHERE ' + 'period <= ? AND scenario = ? AND cap_early > 0 ', + (prev_period, self.config.scenario), + ).fetchall() + + self._load_component_data(data, model.retired_existing_capacity, rows_to_load) + + # --- Lifetime components --- + def _load_lifetime_tech( + self, + data: dict[str, object], + raw_data: Sequence[tuple[object, ...]], + filtered_data: Sequence[tuple[object, ...]], + ) -> None: + """Loads the lifetime_tech component.""" + model = TemoaModel() + cur = self.con.cursor() + rows_to_load = cur.execute('SELECT region, tech, lifetime FROM lifetime_tech').fetchall() + rt_getter = itemgetter(0, 1) + if self.viable_rt: + valid_rt = self.viable_rt.members | self.viable_existing_rt + rows_to_load = [item for item in rows_to_load if rt_getter(item) in valid_rt] + self._load_component_data(data, model.lifetime_tech, rows_to_load) + + def _load_lifetime_process( + self, + data: dict[str, object], + raw_data: Sequence[tuple[object, ...]], + filtered_data: Sequence[tuple[object, ...]], + ) -> None: + """Loads the lifetime_process component.""" + model = TemoaModel() + cur = self.con.cursor() + mi = self.myopic_index + + if mi: + rows_to_load = cur.execute( + 'SELECT region, tech, vintage, lifetime FROM lifetime_process WHERE vintage <= ?', + (mi.last_demand_year,), + ).fetchall() + else: + rows_to_load = cur.execute( + 'SELECT region, tech, vintage, lifetime FROM lifetime_process' + ).fetchall() + rtv_getter = itemgetter(0, 1, 2) + if self.viable_rtv: + valid_rtv = self.viable_rtv.member_tuples | self.viable_existing_rtv + rows_to_load = [item for item in rows_to_load if rtv_getter(item) in valid_rtv] + self._load_component_data(data, model.lifetime_process, rows_to_load) + + def _load_lifetime_survival_curve( + self, + data: dict[str, object], + raw_data: Sequence[tuple[object, ...]], + filtered_data: Sequence[tuple[object, ...]], + ) -> None: + """Loads the lifetime_survival_curve component.""" + model = TemoaModel() + cur = self.con.cursor() + mi = self.myopic_index + + if mi: + rows_to_load = cur.execute( + 'SELECT region, period, tech, vintage, fraction FROM lifetime_survival_curve ' + 'WHERE vintage <= ?', + (mi.last_demand_year,), + ).fetchall() + else: + rows_to_load = cur.execute( + 'SELECT region, period, tech, vintage, fraction FROM lifetime_survival_curve' + ).fetchall() + rtv_getter = itemgetter(0, 2, 3) + if self.viable_rtv: + valid_rtv = self.viable_rtv.member_tuples | self.viable_existing_rtv + rows_to_load = [item for item in rows_to_load if rtv_getter(item) in valid_rtv] + self._load_component_data(data, model.lifetime_survival_curve, rows_to_load) # --- Singleton and Configuration-based Components --- def _load_global_discount_rate( @@ -837,36 +970,11 @@ def load_param_idx_sets(self, data: dict[str, object]) -> dict[str, object]: :param data: The main data dictionary. :return: A dictionary of the new index sets to be added. """ - model = TemoaModel() - param_idx_sets = { - model.cost_invest.name: model.cost_invest_rtv.name, - model.cost_emission.name: model.cost_emission_rpe.name, - model.demand.name: model.demand_constraint_rpc.name, - model.limit_emission.name: model.limit_emission_constraint_rpe.name, - model.limit_activity.name: model.limit_activity_constraint_rpt.name, - model.limit_seasonal_capacity_factor.name: ( - model.limit_seasonal_capacity_factor_constraint_rst.name - ), - model.limit_activity_share.name: model.limit_activity_share_constraint_rpgg.name, - model.limit_annual_capacity_factor.name: ( - model.limit_annual_capacity_factor_constraint_rtvo.name - ), - model.limit_capacity.name: model.limit_capacity_constraint_rpt.name, - model.limit_capacity_share.name: model.limit_capacity_share_constraint_rpgg.name, - model.limit_new_capacity.name: model.limit_new_capacity_constraint_rtv.name, - model.limit_new_capacity_share.name: ( - model.limit_new_capacity_share_constraint_rggv.name - ), - model.limit_resource.name: model.limit_resource_constraint_rt.name, - model.limit_storage_fraction.name: model.limit_storage_fraction_param_rsdt.name, - model.renewable_portfolio_standard.name: ( - model.renewable_portfolio_standard_constraint_rpg.name - ), - } - res: dict[str, object] = {} - for p_name, s_name in param_idx_sets.items(): - param_data = data.get(p_name) + for item in self.manifest: + if item.index_set is None: + continue + param_data = data.get(item.component.name) if isinstance(param_data, dict): - res[s_name] = list(param_data.keys()) + res[item.index_set.name] = list(param_data.keys()) return res diff --git a/temoa/data_io/loader_manifest.py b/temoa/data_io/loader_manifest.py index ad6ce930d..a43d44cb6 100644 --- a/temoa/data_io/loader_manifest.py +++ b/temoa/data_io/loader_manifest.py @@ -32,6 +32,8 @@ class LoadItem: table: The name of the source table in the SQLite database. columns: A list of column names to select from the table. The last column is assumed to be the value for a `Param`. + index_length: Optional. The number of columns that form the index for + a `Param`, for tables with multiple value columns. validator_name: Optional. The name of a `ViableSet` attribute on the `HybridLoader` instance, used for source-trace filtering. validation_map: A tuple indicating which column indices in the data @@ -45,11 +47,15 @@ class LoadItem: `HybridLoader` to handle non-standard loading logic for this component. fallback_data: Optional. A list of default data tuples to use if the table is missing or returns no data. + index_set: Optional. The Pyomo `Set` that is indexed by the keys of + this `Param`. When set, the loader will automatically derive the + set's data from the param's loaded keys during finalization. """ component: ComponentType table: str columns: list[str] + index_length: int | None = None validator_name: str | None = None validation_map: tuple[int, ...] = field(default_factory=tuple) where_clause: str | None = None @@ -58,3 +64,4 @@ class LoadItem: custom_loader_name: str | None = None fallback_data: list[tuple[object, ...]] | None = None order_by: list[str] | None = None + index_set: ComponentType | None = None diff --git a/temoa/db_schema/temoa_schema_v4.sql b/temoa/db_schema/temoa_schema_v4.sql index 77b146356..c97f741b6 100644 --- a/temoa/db_schema/temoa_schema_v4.sql +++ b/temoa/db_schema/temoa_schema_v4.sql @@ -389,78 +389,6 @@ CREATE TABLE IF NOT EXISTS operator REPLACE INTO operator VALUES('e','equal to'); REPLACE INTO operator VALUES('le','less than or equal to'); REPLACE INTO operator VALUES('ge','greater than or equal to'); -CREATE TABLE IF NOT EXISTS limit_growth_capacity -( - region TEXT, - tech_or_group TEXT, - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - rate REAL NOT NULL DEFAULT 0, - seed REAL NOT NULL DEFAULT 0, - seed_units TEXT, - notes TEXT, - PRIMARY KEY (region, tech_or_group, operator) -); -CREATE TABLE IF NOT EXISTS limit_degrowth_capacity -( - region TEXT, - tech_or_group TEXT, - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - rate REAL NOT NULL DEFAULT 0, - seed REAL NOT NULL DEFAULT 0, - seed_units TEXT, - notes TEXT, - PRIMARY KEY (region, tech_or_group, operator) -); -CREATE TABLE IF NOT EXISTS limit_growth_new_capacity -( - region TEXT, - tech_or_group TEXT, - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - rate REAL NOT NULL DEFAULT 0, - seed REAL NOT NULL DEFAULT 0, - seed_units TEXT, - notes TEXT, - PRIMARY KEY (region, tech_or_group, operator) -); -CREATE TABLE IF NOT EXISTS limit_degrowth_new_capacity -( - region TEXT, - tech_or_group TEXT, - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - rate REAL NOT NULL DEFAULT 0, - seed REAL NOT NULL DEFAULT 0, - seed_units TEXT, - notes TEXT, - PRIMARY KEY (region, tech_or_group, operator) -); -CREATE TABLE IF NOT EXISTS limit_growth_new_capacity_delta -( - region TEXT, - tech_or_group TEXT, - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - rate REAL NOT NULL DEFAULT 0, - seed REAL NOT NULL DEFAULT 0, - seed_units TEXT, - notes TEXT, - PRIMARY KEY (region, tech_or_group, operator) -); -CREATE TABLE IF NOT EXISTS limit_degrowth_new_capacity_delta -( - region TEXT, - tech_or_group TEXT, - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - rate REAL NOT NULL DEFAULT 0, - seed REAL NOT NULL DEFAULT 0, - seed_units TEXT, - notes TEXT, - PRIMARY KEY (region, tech_or_group, operator) -); CREATE TABLE IF NOT EXISTS limit_storage_level_fraction ( region TEXT, @@ -994,7 +922,7 @@ CREATE TABLE IF NOT EXISTS output_cost region TEXT, sector TEXT REFERENCES sector_label (sector), period INTEGER REFERENCES time_period (period), - tech TEXT REFERENCES technology (tech), + tech TEXT, vintage INTEGER REFERENCES time_period (period), d_invest REAL, d_fixed REAL, @@ -1006,8 +934,7 @@ CREATE TABLE IF NOT EXISTS output_cost emiss REAL, units TEXT, PRIMARY KEY (scenario, region, period, tech, vintage), - FOREIGN KEY (vintage) REFERENCES time_period (period), - FOREIGN KEY (tech) REFERENCES technology (tech) + FOREIGN KEY (vintage) REFERENCES time_period (period) ); CREATE TABLE IF NOT EXISTS time_season diff --git a/temoa/extensions/discrete_capacity/__init__.py b/temoa/extensions/discrete_capacity/__init__.py new file mode 100644 index 000000000..c1a499cd1 --- /dev/null +++ b/temoa/extensions/discrete_capacity/__init__.py @@ -0,0 +1,3 @@ +from temoa.extensions.discrete_capacity.extension import DISCRETE_CAPACITY_EXTENSION + +__all__ = ['DISCRETE_CAPACITY_EXTENSION'] diff --git a/temoa/extensions/discrete_capacity/components/__init__.py b/temoa/extensions/discrete_capacity/components/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/temoa/extensions/discrete_capacity/components/discrete_capacity.py b/temoa/extensions/discrete_capacity/components/discrete_capacity.py new file mode 100644 index 000000000..6d0c6bbd9 --- /dev/null +++ b/temoa/extensions/discrete_capacity/components/discrete_capacity.py @@ -0,0 +1,108 @@ +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING + +from pyomo.environ import Constraint, quicksum, value + +import temoa.components.geography as geography +import temoa.components.technology as technology + +logger = logging.getLogger(__name__) + +if TYPE_CHECKING: + from temoa.extensions.discrete_capacity.core.model import DiscreteCapacityModel + from temoa.types import ExprLike, Period, Region, Technology, Vintage + + +def limit_discrete_new_capacity_indices( + model: DiscreteCapacityModel, +) -> set[tuple[Region, Technology, Vintage]]: + + indices = { + (r, t, v) + for r, t in model.limit_discrete_new_capacity.sparse_keys() + for _r in geography.gather_group_regions(model, r) + for _t in technology.gather_group_techs(model, t) + for v in model.vintage_optimize + if (_r, _t, v) in model.process_periods + } + return indices + + +def limit_discrete_new_capacity_constraint_rule( + model: DiscreteCapacityModel, r: Region, t: Technology, v: Vintage +) -> ExprLike: + r""" + The limit_discrete_new_capacity constraint requires the total new capacity + for a technology (or technology group) in a region to be discrete, equal + to some integer multiple of the specified capacity. + + .. math:: + :label: limit_discrete_new_capacity + + \textbf{NCAP}_{r, t, v} = LDNC_{r, t} \cdot \textbf{DNCAP}_{r, v, t} + + \forall \{r, t, v\} \in \Theta_{\text{limit\_discrete\_new\_capacity}} + """ + + regions = geography.gather_group_regions(model, r) + techs = technology.gather_group_techs(model, t) + unit_cap = value(model.limit_discrete_new_capacity[r, t]) + + new_capacity = quicksum( + model.v_new_capacity[_r, _t, v] + for _r in regions + for _t in techs + if (_r, _t, v) in model.process_periods + ) + expr = new_capacity == unit_cap * model.v_discrete_new_capacity[r, t, v] + if isinstance(expr, bool): + return Constraint.Skip + return expr + + +def limit_discrete_capacity_indices( + model: DiscreteCapacityModel, +) -> set[tuple[Region, Period, Technology]]: + indices = { + (r, p, t) + for r, t in model.limit_discrete_capacity.sparse_keys() + for _r in geography.gather_group_regions(model, r) + for _t in technology.gather_group_techs(model, t) + for p in model.time_optimize + if (_r, p, _t) in model.v_capacity_available_by_period_and_tech + } + return indices + + +def limit_discrete_capacity_constraint_rule( + model: DiscreteCapacityModel, r: Region, p: Period, t: Technology +) -> ExprLike: + r""" + The limit_discrete_capacity constraint requires the total available capacity + for a technology (or technology group) in a region to be discrete, equal + to some integer multiple of the specified capacity. + + .. math:: + :label: limit_discrete_capacity + + \textbf{CAPAVL}_{r, p, t} = LDC_{r, t} \cdot \textbf{DCAP}_{r, p, t} + + \forall \{r, p, t\} \in \Theta_{\text{limit\_discrete\_net\_capacity}} + """ + + regions = geography.gather_group_regions(model, r) + techs = technology.gather_group_techs(model, t) + capacity = value(model.limit_discrete_capacity[r, t]) + + net_capacity = quicksum( + model.v_capacity_available_by_period_and_tech[_r, p, _t] + for _r in regions + for _t in techs + if (_r, p, _t) in model.v_capacity_available_by_period_and_tech + ) + expr = net_capacity == capacity * model.v_discrete_capacity[r, p, t] + if isinstance(expr, bool): + return Constraint.Skip + return expr diff --git a/temoa/extensions/discrete_capacity/core/__init__.py b/temoa/extensions/discrete_capacity/core/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/temoa/extensions/discrete_capacity/core/model.py b/temoa/extensions/discrete_capacity/core/model.py new file mode 100644 index 000000000..d968a2430 --- /dev/null +++ b/temoa/extensions/discrete_capacity/core/model.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, cast + +from pyomo.environ import Constraint, Integers, NonNegativeReals, Param, Set, Var + +from temoa.extensions.discrete_capacity.components import discrete_capacity + +if TYPE_CHECKING: + from temoa.core.model import TemoaModel + + class DiscreteCapacityModel(TemoaModel): + limit_discrete_new_capacity: Param + limit_discrete_new_capacity_constraint_rtv: Set + limit_discrete_new_capacity_constraint: Constraint + v_discrete_new_capacity: Var + + limit_discrete_capacity: Param + limit_discrete_capacity_constraint_rpt: Set + limit_discrete_capacity_constraint: Constraint + v_discrete_capacity: Var + + +def register_model_components(model: TemoaModel) -> None: + m = cast('DiscreteCapacityModel', model) + + m.limit_discrete_new_capacity = Param( + m.regional_global_indices, m.tech_or_group, within=NonNegativeReals + ) + m.limit_discrete_new_capacity_constraint_rtv = Set( + dimen=3, initialize=discrete_capacity.limit_discrete_new_capacity_indices + ) + m.v_discrete_new_capacity = Var( + m.limit_discrete_new_capacity_constraint_rtv, within=Integers, bounds=(0, None) + ) + m.limit_discrete_new_capacity_constraint = Constraint( + m.limit_discrete_new_capacity_constraint_rtv, + rule=discrete_capacity.limit_discrete_new_capacity_constraint_rule, + ) + + m.limit_discrete_capacity = Param( + m.regional_global_indices, m.tech_or_group, within=NonNegativeReals + ) + m.limit_discrete_capacity_constraint_rpt = Set( + dimen=3, initialize=discrete_capacity.limit_discrete_capacity_indices + ) + m.v_discrete_capacity = Var( + m.limit_discrete_capacity_constraint_rpt, within=Integers, bounds=(0, None) + ) + m.limit_discrete_capacity_constraint = Constraint( + m.limit_discrete_capacity_constraint_rpt, + rule=discrete_capacity.limit_discrete_capacity_constraint_rule, + ) diff --git a/temoa/extensions/discrete_capacity/data_manifest.py b/temoa/extensions/discrete_capacity/data_manifest.py new file mode 100644 index 000000000..35702e203 --- /dev/null +++ b/temoa/extensions/discrete_capacity/data_manifest.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, cast + +from temoa.data_io.loader_manifest import LoadItem + +if TYPE_CHECKING: + from temoa.core.model import TemoaModel + from temoa.extensions.discrete_capacity.core.model import DiscreteCapacityModel + + +def build_manifest_items(model: TemoaModel) -> list[LoadItem]: + m = cast('DiscreteCapacityModel', model) + return [ + LoadItem( + component=m.limit_discrete_new_capacity, + table='limit_discrete_new_capacity', + columns=['region', 'tech_or_group', 'capacity'], + validator_name='viable_rt', + validation_map=(0, 1), + is_table_required=False, + is_period_filtered=False, + ), + LoadItem( + component=m.limit_discrete_capacity, + table='limit_discrete_capacity', + columns=['region', 'tech_or_group', 'capacity'], + validator_name='viable_rt', + validation_map=(0, 1), + is_table_required=False, + is_period_filtered=False, + ), + ] diff --git a/temoa/extensions/discrete_capacity/extension.py b/temoa/extensions/discrete_capacity/extension.py new file mode 100644 index 000000000..d9a062c89 --- /dev/null +++ b/temoa/extensions/discrete_capacity/extension.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from pathlib import Path + +from temoa.extensions.discrete_capacity.core.model import register_model_components +from temoa.extensions.discrete_capacity.data_manifest import build_manifest_items +from temoa.extensions.framework import ExtensionSpec + +DISCRETE_CAPACITY_EXTENSION = ExtensionSpec( + extension_id='discrete_capacity', + owned_tables=('limit_discrete_new_capacity', 'limit_discrete_capacity'), + regional_group_tables={ + 'limit_discrete_new_capacity': 'region', + 'limit_discrete_capacity': 'region', + }, + register_model_components=register_model_components, + build_manifest_items=build_manifest_items, + schema_sql_path=str(Path(__file__).parent / 'tables.sql'), + fail_if_tables_populated_when_disabled=True, +) diff --git a/temoa/extensions/discrete_capacity/tables.sql b/temoa/extensions/discrete_capacity/tables.sql new file mode 100644 index 000000000..210308d98 --- /dev/null +++ b/temoa/extensions/discrete_capacity/tables.sql @@ -0,0 +1,16 @@ +CREATE TABLE IF NOT EXISTS limit_discrete_new_capacity( + region TEXT NOT NULL, + tech_or_group TEXT NOT NULL, + capacity REAL NOT NULL DEFAULT 0, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group) +); +CREATE TABLE IF NOT EXISTS limit_discrete_capacity( + region TEXT NOT NULL, + tech_or_group TEXT NOT NULL, + capacity REAL NOT NULL DEFAULT 0, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group) +); diff --git a/temoa/extensions/economies_of_scale/__init__.py b/temoa/extensions/economies_of_scale/__init__.py new file mode 100644 index 000000000..42728b346 --- /dev/null +++ b/temoa/extensions/economies_of_scale/__init__.py @@ -0,0 +1,3 @@ +from temoa.extensions.economies_of_scale.extension import EOS_EXTENSION + +__all__ = ['EOS_EXTENSION'] diff --git a/temoa/extensions/economies_of_scale/components/__init__.py b/temoa/extensions/economies_of_scale/components/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/temoa/extensions/economies_of_scale/components/cost_fixed_eos.py b/temoa/extensions/economies_of_scale/components/cost_fixed_eos.py new file mode 100644 index 000000000..9a902eb43 --- /dev/null +++ b/temoa/extensions/economies_of_scale/components/cost_fixed_eos.py @@ -0,0 +1,313 @@ +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING, cast + +from pyomo.environ import quicksum, value + +import temoa.components.geography as geography +import temoa.components.technology as technology +from temoa.components.costs import fixed_or_variable_cost + +if TYPE_CHECKING: + from pyomo.core import Expression + from pyomo.core.base.objective import ObjectiveData + from pyomo.core.base.var import VarData + + from temoa.extensions.economies_of_scale.core.model import EOSModel + from temoa.types import ExprLike, Period, Region, Technology + +logger = logging.getLogger(__name__) + + +def period_cost_indices(model: EOSModel) -> set[tuple[Region, Period, Technology]]: + return {(r, p, t) for r, p, t, _ in model.cost_fixed_eos_rptn} + + +def initialize_components(model: EOSModel) -> None: + """ + Organise some data and put up some guard rails + """ + + # Collect segment indices n for each (r,p,t) cluster; order doesn't matter + for r, p, t, n in model.cost_fixed_eos_rptn: + model.cost_fixed_eos_segments.setdefault((r, p, t), set()).add(n) + + # Check that costs and capacities are monotonically increasing + for (r, p, t), segs in model.cost_fixed_eos_segments.items(): + sorted_segs = sorted(segs) + for i, n in enumerate(sorted_segs): + cap_lower, cap_upper, cost_lower, cost_upper = model.cost_fixed_eos[r, p, t, n] + + # Check cost curve is nonnegative and monotonically increasing + # If someone wants to break this assumption they should have the + # skills to edit this code. + if not all( + ( + cap_lower >= 0, + cap_upper >= 0, + cost_lower >= 0, + cost_upper >= 0, + cap_upper > cap_lower, + cost_upper > cost_lower, + ) + ): + msg = ( + 'Negative values or non-increasing segment bounds found ' + f'in cost_fixed_eos table for {r, p, t, n}' + ) + logger.error(msg) + raise ValueError(msg) + + # Backcheck for monotonic growth + if i == 0: + continue + + _, prev_cap_upper, _, prev_cost_upper = model.cost_fixed_eos[ + r, p, t, sorted_segs[i - 1] + ] + + if not all( + ( + abs(cap_lower - prev_cap_upper) <= 0.001, + abs(cost_lower - prev_cost_upper) <= 0.001, + ) + ): + msg = ( + 'Segments in cost_fixed_eos table do not align on their bounds. This would ' + 'leave gaps in the cost curve and could lead to infeasibility. Check ' + f'({r, p, t, sorted_segs[i - 1]}) to ({r, p, t, n})' + ) + logger.error(msg) + raise ValueError(msg) + + +# --- Enforce the rules of cumulative capacity progression up the cost curve --- +def cost_fixed_eos_segment_binary_constraint( + model: EOSModel, r: Region, p: Period, t: Technology +) -> ExprLike: + r""" + Enforce exactly one active segment of the cost curve for each cluster + in each period. + + Each technology cluster :math:`(r, p, t)` has a piecewise-linear cost curve + split into :math:`N_{r,p,t}` segments. The binary variable + :math:`\textbf{CFEB}_{r,p,t,n}` selects which segment is currently active. + Exactly one segment must be active at all times: + + + .. math:: + :label: Cost Fixed EOS Segment Binary Constraint + + \sum_{n \in N_{r,p,t}} \textbf{CFEB}_{r,p,t,n} = 1 + + \qquad \forall \{r, p, t\} \in \Theta_{\text{cost\_fixed\_eos\_period\_rpt}} + + where :math:`\textbf{CFEB}_{r,p,t,n}` is + :code:`v_cost_fixed_eos_segment_binary[r, p, t, n]` (:math:`\in \{0, 1\}`) and + :math:`N_{r,p,t}` is the set of segment indices declared for cluster + :math:`(r, p, t)` in the :code:`cost_fixed_eos` table. + """ + count_active_segments = quicksum( + model.v_cost_fixed_eos_segment_binary[r, p, t, n] + for n in model.cost_fixed_eos_segments[r, p, t] + ) + return count_active_segments == 1 + + +def cost_fixed_eos_capacity_lower_bound_constraint( + model: EOSModel, r: Region, p: Period, t: Technology, n: int +) -> ExprLike: + r""" + Enforce the lower bound of the active EOS cost-curve segment on the + capacity variable for that segment. + + When segment :math:`n` is inactive (:math:`\textbf{CFEB}_{r,p,t,n} = 0`), the + right-hand side collapses to zero so the constraint is non-binding. When + segment :math:`n` is active (:math:`\textbf{CFEB}_{r,p,t,n} = 1`), it + enforces that the segment capacity is at least the lower boundary + :math:`\underline{K}_{r,p,t,n}` of that segment: + + .. math:: + :label: EOS Capacity Lower Bound + + \textbf{CFECAP}_{r,p,t,n} + \ge \textbf{CFEB}_{r,p,t,n} \cdot \underline{K}_{r,p,t,n} + + \qquad \forall \{r, p, t, n\} \in \Theta_{\text{cost\_fixed\_eos\_rptn}} + + where :math:`\textbf{CFECAP}_{r,p,t,n}` is + :code:`v_cost_fixed_eos_capacity[r, p, t, n]`, :math:`\textbf{CFEB}_{r,p,t,n}` + is :code:`v_cost_fixed_eos_segment_binary[r, p, t, n]`, and + :math:`\underline{K}_{r,p,t,n}` is :code:`cost_fixed_eos[r, p, t, n][0]` + (the :code:`capacity_lower` column). + """ + cap_lower = model.v_cost_fixed_eos_segment_binary[r, p, t, n] * value( + model.cost_fixed_eos[r, p, t, n][0] + ) + return model.v_cost_fixed_eos_capacity[r, p, t, n] >= cap_lower + + +def cost_fixed_eos_capacity_upper_bound_constraint( + model: EOSModel, r: Region, p: Period, t: Technology, n: int +) -> ExprLike: + r""" + Enforce the upper bound of the active EOS cost-curve segment on the + capacity variable for that segment. + + This is the mirror of the lower bound. When segment :math:`n` is inactive + the right-hand side collapses to zero, driving + :math:`\textbf{CFECAP}_{r,p,t,n}` to zero. When it is active, + the segment capacity cannot exceed the upper boundary + :math:`\overline{K}_{r,p,t,n}` of that segment: + + .. math:: + :label: EOS Capacity Upper Bound + + \textbf{CFECAP}_{r,p,t,n} + \le \textbf{CFEB}_{r,p,t,n} \cdot \overline{K}_{r,p,t,n} + + \qquad \forall \{r, p, t, n\} \in \Theta_{\text{cost\_fixed\_eos\_rptn}} + + where :math:`\overline{K}_{r,p,t,n}` is :code:`cost_fixed_eos[r, p, t, n][1]` + (the :code:`capacity_upper` column). + + Together the lower and upper bound constraints implement a "big-M"-style + activation: only the single active segment can carry non-zero capacity, + and that capacity is pinned within the segment's range. + """ + cap_upper = model.v_cost_fixed_eos_segment_binary[r, p, t, n] * value( + model.cost_fixed_eos[r, p, t, n][1] + ) + return model.v_cost_fixed_eos_capacity[r, p, t, n] <= cap_upper + + +def cost_fixed_eos_capacity_constraint( + model: EOSModel, r: Region, p: Period, t: Technology +) -> ExprLike: + r""" + Equate the EOS capacity expression to the actual capacity available + in the base model. + + The EOS formulation tracks capacity internally through + :math:`\textbf{CFECAP}_{r,p,t,n}`. This constraint ties those + internal variables to the real decision variables + :math:`\textbf{CAPAVL}_{r,p,t}` from the base Temoa model, + so the cost curve reflects actually-available capacity + rather than a free parameter: + + .. math:: + :label: Cost Fixed EOS Capacity + + \sum_{n \in N_{r,p,t}} \textbf{CFECAP}_{r,p,t,n} + = + \sum_{\substack{r' \in R(r),\, t' \in T(t)}} + \textbf{CAPAVL}_{r',p,t'} + + \qquad \forall \{r, p, t\} \in \Theta_{\text{cost\_fixed\_eos\_period\_rpt}} + + where :math:`R(r)` and :math:`T(t)` expand any group labels to the + individual regions and technologies they contain, and + :math:`\textbf{CAPAVL}_{r',p,t'}` is + :code:`v_capacity_available_by_period_and_tech[r', p, t']`. + """ + + regions = geography.gather_group_regions(model, r) + techs = technology.gather_group_techs(model, t) + + cap_eos = quicksum( + model.v_cost_fixed_eos_capacity[r, p, t, n] for n in model.cost_fixed_eos_segments[r, p, t] + ) + capacity = quicksum( + model.v_capacity_available_by_period_and_tech[_r, p, _t] + for _t in techs + for _r in regions + if (_r, p, _t) in model.v_capacity_available_by_period_and_tech + ) + return capacity == cap_eos + + +# --- Calculating costs --- +def cost_fixed_eos_segment_cost( + model: EOSModel, + r: Region, + p: Period, + t: Technology, + n: int, + capacity: ExprLike, + binary: VarData | bool = True, +) -> ExprLike: + """Fixed cost within a segment, if capacity were in that segment""" + cap_lower, cap_upper, cost_lower, cost_upper = model.cost_fixed_eos[r, p, t, n] + m = (value(cost_upper) - value(cost_lower)) / (value(cap_upper) - value(cap_lower)) + return m * capacity + binary * (value(cost_lower) - m * value(cap_lower)) + + +def period_cost(model: EOSModel, r: Region, p: Period, t: Technology) -> Expression: + r""" + EOS clusters do not contribute to the base-model + :math:`\text{CostFixed}` sum. Instead, the extension adds a separate + discounted fixed-cost term to :code:`total_cost` via a + :code:`BuildAction` (``append_cost_fixed_eos_to_total_cost``). + + The *period cost* for cluster :math:`(r, p, t)` is evaluated from the + piecewise-linear cost curve at the capacity available in period :math:`p`: + + .. math:: + + \text{EOS\_PeriodCost}_{r,p,t} = + \sum_{n \in N_{r,p,t}} + \left[ + m_{r,p,t,n} \cdot \textbf{CFECAP}_{r,p,t,n} + + \textbf{CFEB}_{r,p,t,n} + \left(\underline{C}_{r,p,t,n} - m_{r,p,t,n} + \cdot \underline{K}_{r,p,t,n}\right) + \right] + + \text{with slope }m_{r,p,t,n} = \dfrac{\overline{C}_{r,p,t,n} - + \underline{C}_{r,p,t,n}}{\overline{K}_{r,p,t,n} - \underline{K}_{r,p,t,n}} + + Note the terms in this summation are only non-zero for the active segment. + + This cost is then discounted and amortised using the same + :func:`~temoa.components.costs.fixed_or_variable_cost` function as for + cost_fixed in the base model. The result is added to :code:`total_cost` + once per cluster-period combination in + :math:`\Theta_{\text{cost\_fixed\_eos\_period\_rpt}}`. + """ + return quicksum( + cost_fixed_eos_segment_cost( + model, + r, + p, + t, + n, + model.v_cost_fixed_eos_capacity[r, p, t, n], + model.v_cost_fixed_eos_segment_binary[r, p, t, n], + ) + for n in model.cost_fixed_eos_segments[r, p, t] + ) + + +def total_cost(model: EOSModel) -> None: + """ + Discounted fixed costs for all EOS clusters in the planning horizon + """ + + p_0 = min(model.time_optimize) + global_discount_rate = value(model.global_discount_rate) + + total_cost = quicksum( + fixed_or_variable_cost( + 1, + period_cost(model, r, p, t), + value(model.period_length[p]), + global_discount_rate, + p_0, + p=p, + ) + for r, p, t in model.cost_fixed_eos_period_rpt + ) + + # Append to total cost objective + model.total_cost.set_value(cast('ObjectiveData', model.total_cost).expr + total_cost) diff --git a/temoa/extensions/economies_of_scale/components/cost_invest_eos.py b/temoa/extensions/economies_of_scale/components/cost_invest_eos.py new file mode 100644 index 000000000..a59c60e77 --- /dev/null +++ b/temoa/extensions/economies_of_scale/components/cost_invest_eos.py @@ -0,0 +1,474 @@ +from __future__ import annotations + +import logging +from itertools import product as cross_product +from typing import TYPE_CHECKING, cast + +from pyomo.environ import quicksum, value + +import temoa.components.geography as geography +import temoa.components.technology as technology +from temoa.components.costs import loan_cost, loan_cost_survival_curve + +if TYPE_CHECKING: + from pyomo.core import Expression + from pyomo.core.base.objective import ObjectiveData + from pyomo.core.base.var import VarData + + from temoa.extensions.economies_of_scale.core.model import EOSModel + from temoa.types import ExprLike, Period, Region, Technology + +logger = logging.getLogger(__name__) + + +# --- Initialise EOS invest model components --- +def cost_invest_eos_cumulative_capacity_indices( + model: EOSModel, +) -> set[tuple[Region, Period, Technology, int]]: + return { + (r, cast('Period', v), t, n) + for r, t, n in model.cost_invest_eos.sparse_keys() + for _r in geography.gather_group_regions(model, r) + for _t in technology.gather_group_techs(model, t) + for _p in model.time_optimize + for v in model.process_vintages.get((_r, _p, _t), set()) + if v == _p + } + + +def cost_invest_eos_period_cost_indices(model: EOSModel) -> set[tuple[Region, Period, Technology]]: + return {(r, p, t) for r, p, t, _ in model.cost_invest_eos_segment_rptn} + + +def append_cost_invest_eos_rtv(model: EOSModel) -> None: + """ + EOS invest processes most likely do not have a cost_invest parameter, as + that is handled by the EOS cost curve. As a result they would not have + loan parameters either. We add these rtv indices to the base + cost_invest param so EOS invest processes have loan parameters for + discounting in the objective function. + """ + for r, p, t in model.cost_invest_eos_period_rpt: + regions = geography.gather_group_regions(model, r) + techs = technology.gather_group_techs(model, t) + + valid_rtv = { + (_r, _t, p) for _r in regions for _t in techs if (_r, _t, p) in model.process_periods + } + for rtv in valid_rtv: + if rtv not in model.cost_invest_rtv: + model.cost_invest_rtv.add(rtv) + + +def initialize_cost_invest_eos(model: EOSModel) -> None: + """ + Gather segment data and validate guard rails for EOS invest clusters. + """ + + # Collect segment indices n for each (r,t) cluster; order doesn't matter + for r, t, n in model.cost_invest_eos.sparse_keys(): + model.cost_invest_eos_segments.setdefault((r, t), set()).add(n) + + # Check that costs and capacities are monotonically increasing + for (r, t), segs in model.cost_invest_eos_segments.items(): + sorted_segs = sorted(segs) + for i, n in enumerate(sorted_segs): + cap_lower, cap_upper, cost_lower, cost_upper = model.cost_invest_eos[r, t, n] + + # Check cost curve is nonnegative and monotonically increasing. + # If someone wants to break this assumption they should have the + # skills to edit this code. + if not all( + ( + cap_lower >= 0, + cap_upper >= 0, + cost_lower >= 0, + cost_upper >= 0, + cap_upper > cap_lower, + cost_upper > cost_lower, + ) + ): + msg = ( + 'Negative values or non-increasing segment bounds found ' + f'in cost_invest_eos table for {r, t, n}' + ) + logger.error(msg) + raise ValueError(msg) + + # Backcheck for monotonic growth + if i == 0: + continue + + _, prev_cap_upper, _, prev_cost_upper = model.cost_invest_eos[r, t, sorted_segs[i - 1]] + + if not all( + ( + abs(cap_lower - prev_cap_upper) <= 0.001, + abs(cost_lower - prev_cost_upper) <= 0.001, + ) + ): + msg = ( + 'Segments in cost_invest_eos table do not align on their bounds. This would ' + 'leave gaps in the cost curve and could lead to infeasibility. Check ' + f'({r, t, sorted_segs[i - 1]}) to ({r, t, n})' + ) + logger.error(msg) + raise ValueError(msg) + + # Shortens some lines below in error checks + life = model.lifetime_process + loan_life = model.loan_lifetime_process + loan_rate = model.loan_rate + + # Get a viable built process for each EOS invest period, for discounting + # parameters. Check that all r, t combos in the cluster share the same + # lifetime and loan parameters. + for r, p, t in model.cost_invest_eos_period_rpt: + regions = geography.gather_group_regions(model, r) + techs = technology.gather_group_techs(model, t) + + for _r, _t in cross_product(regions, techs): + if (_r, _t, p) in model.process_periods: + # Store first valid process as our reference for this period + if (r, p, t) not in model.cost_invest_eos_reference_process: + model.cost_invest_eos_reference_process[r, p, t] = _r, _t + + # Check that the assumptions hold + r0, t0 = model.cost_invest_eos_reference_process[r, p, t] + if any( + ( + abs(life[r0, t0, p] - life[_r, _t, p]) >= 0.001, + abs(loan_life[r0, t0, p] - loan_life[_r, _t, p]) >= 0.001, + abs(loan_rate[r0, t0, p] - loan_rate[_r, _t, p]) >= 0.001, + ) + ): + msg = ( + 'Processes assigned to the same cost_invest_eos cost curve must all have ' + 'the same process lifetime, loan lifetime, and loan rate. These two ' + 'processes do not match:\n ' + f'({r0}, {t0}, {p}) : lifetime = {life[r0, t0, p]}, ' + f'loan life = {loan_life[r0, t0, p]}, loan rate = {loan_rate[r0, t0, p]}\n' + f'({_r}, {_t}, {p}) : lifetime = {life[_r, _t, p]}, ' + f'loan life = {loan_life[_r, _t, p]}, loan rate = {loan_rate[_r, _t, p]}' + ) + logger.error(msg) + raise ValueError(msg) + + +# --- Enforce the rules of cumulative capacity progression up the cost curve --- +def cost_invest_eos_segment_binary_constraint( + model: EOSModel, r: Region, p: Period, t: Technology +) -> ExprLike: + r""" + Enforce exactly one active segment of the EOS invest cost curve for each + cluster in each period. + + Each technology cluster :math:`(r, t)` has a piecewise-linear investment + cost curve split into :math:`N_{r,t}` segments. The binary variable + :math:`\textbf{CIEB}_{r,p,t,n}` selects which segment is currently active. + Exactly one segment must be active at all times: + + .. math:: + :label: Cost Invest EOS Segment Binary Constraint + + \sum_{n \in N_{r,t}} \textbf{CIEB}_{r,p,t,n} = 1 + + \qquad \forall \{r, p, t\} \in \Theta_{\text{cost\_invest\_eos\_period\_rpt}} + + where :math:`\textbf{CIEB}_{r,p,t,n}` is + :code:`v_cost_invest_eos_segment_binary[r, p, t, n]` (:math:`\in \{0, 1\}`) and + :math:`N_{r,t}` is the set of segment indices declared for cluster + :math:`(r, t)` in the :code:`cost_invest_eos` table. + """ + count_active_segments = quicksum( + model.v_cost_invest_eos_segment_binary[r, p, t, n] + for n in model.cost_invest_eos_segments[r, t] + ) + return count_active_segments == 1 + + +def cost_invest_eos_capacity_lower_bound_constraint( + model: EOSModel, r: Region, p: Period, t: Technology, n: int +) -> ExprLike: + r""" + Enforce the lower bound of the active EOS invest cost-curve segment on + cumulative capacity. + + When segment :math:`n` is inactive (:math:`\textbf{CIEB}_{r,p,t,n} = 0`), the + right-hand side collapses to zero so the constraint is non-binding. When + segment :math:`n` is active (:math:`\textbf{CIEB}_{r,p,t,n} = 1`), it + enforces that cumulative capacity has reached at least the lower boundary + :math:`\underline{K}_{r,t,n}` of that segment: + + .. math:: + :label: Cost Invest EOS Capacity Lower Bound + + \textbf{CIECAP}_{r,p,t,n} + \ge \textbf{CIEB}_{r,p,t,n} \cdot \underline{K}_{r,t,n} + + \qquad \forall \{r, p, t, n\} \in \Theta_{\text{cost\_invest\_eos\_segment\_rptn}} + + where :math:`\textbf{CIECAP}_{r,p,t,n}` is + :code:`v_cost_invest_eos_cumulative_capacity[r, p, t, n]`, + :math:`\textbf{CIEB}_{r,p,t,n}` is + :code:`v_cost_invest_eos_segment_binary[r, p, t, n]`, and + :math:`\underline{K}_{r,t,n}` is :code:`cost_invest_eos[r, t, n][0]` + (the :code:`capacity_lower` column). + """ + cap_lower = model.v_cost_invest_eos_segment_binary[r, p, t, n] * value( + model.cost_invest_eos[r, t, n][0] + ) + return model.v_cost_invest_eos_cumulative_capacity[r, p, t, n] >= cap_lower + + +def cost_invest_eos_capacity_upper_bound_constraint( + model: EOSModel, r: Region, p: Period, t: Technology, n: int +) -> ExprLike: + r""" + Enforce the upper bound of the active EOS invest cost-curve segment on + cumulative capacity. + + This is the mirror of the lower bound. When segment :math:`n` is inactive + the right-hand side collapses to zero, driving + :math:`\textbf{CIECAP}_{r,p,t,n}` to zero. When it is active, + cumulative capacity cannot exceed the upper boundary + :math:`\overline{K}_{r,t,n}` of that segment: + + .. math:: + :label: Cost Invest EOS Capacity Upper Bound + + \textbf{CIECAP}_{r,p,t,n} + \le \textbf{CIEB}_{r,p,t,n} \cdot \overline{K}_{r,t,n} + + \qquad \forall \{r, p, t, n\} \in \Theta_{\text{cost\_invest\_eos\_segment\_rptn}} + + where :math:`\overline{K}_{r,t,n}` is :code:`cost_invest_eos[r, t, n][1]` + (the :code:`capacity_upper` column). + + Together the lower and upper bound constraints implement a "big-M"-style + activation: only the single active segment can carry non-zero capacity, + and that capacity is pinned within the segment's range. + """ + cap_upper = model.v_cost_invest_eos_segment_binary[r, p, t, n] * value( + model.cost_invest_eos[r, t, n][1] + ) + return model.v_cost_invest_eos_cumulative_capacity[r, p, t, n] <= cap_upper + + +def cost_invest_eos_cumulative_capacity_constraint( + model: EOSModel, r: Region, p: Period, t: Technology +) -> ExprLike: + r""" + Equate the EOS invest cumulative-capacity expression to the actual capacity + built in the base model. + + The EOS invest formulation tracks cumulative capacity internally through + :math:`\textbf{CIECAP}_{r,p,t,n}`. This constraint ties those internal + variables to the real decision variables :math:`\textbf{NCAP}_{r,t,v}` and + the parameter :math:`\textbf{ECAP}_{r,t,v}` from the base Temoa model, so + the cost curve reflects actually-built capacity rather than a free + parameter: + + .. math:: + :label: Cost Invest EOS Cumulative Capacity + + \sum_{n \in N_{r,t}} \textbf{CIECAP}_{r,p,t,n} + = + \sum_{\substack{r' \in R(r),\, t' \in T(t),\\ v \in V}} + \textbf{ECAP}_{r',t',v} + + + \sum_{\substack{r' \in R(r),\, t' \in T(t),\\ v \le p,\, v \in \mathcal{V}(r',t')}} + \textbf{NCAP}_{r',t',v} + + \qquad \forall \{r, p, t\} \in \Theta_{\text{cost\_invest\_eos\_period\_rpt}} + + where :math:`R(r)` and :math:`T(t)` expand any group labels to the + individual regions and technologies they contain, and + :math:`\mathcal{V}(r', t')` is the set of all optimised vintages for that + process. + """ + + regions = geography.gather_group_regions(model, r) + techs = technology.gather_group_techs(model, t) + + # What EOS invest thinks the cumulative capacity is for this cluster in this period + cap_eos = quicksum( + model.v_cost_invest_eos_cumulative_capacity[r, p, t, n] + for n in model.cost_invest_eos_segments[r, t] + ) + + # Sum all actually built capacity so far + cap_deployed = quicksum( + value(model.existing_capacity[_r, _t, _v]) + for _r, _t, _v in model.existing_capacity.sparse_keys() + if _r in regions and _t in techs + ) + cap_deployed += quicksum( + model.v_new_capacity[_r, _t, _v] + for _v in model.vintage_optimize + if _v <= p + for _r in regions + for _t in techs + if (_r, _t, _v) in model.v_new_capacity + ) + + return cap_deployed == cap_eos + + +# --- Calculating investment costs --- +def cost_invest_eos_segment_cost( + model: EOSModel, + r: Region, + t: Technology, + n: int, + cumulative_capacity: ExprLike, + binary: VarData | bool = True, +) -> ExprLike: + """Cumulative investment cost within an EOS invest segment, if capacity were in that segment""" + cap_lower, cap_upper, cost_lower, cost_upper = model.cost_invest_eos[r, t, n] + m = (value(cost_upper) - value(cost_lower)) / (value(cap_upper) - value(cap_lower)) + return m * cumulative_capacity + binary * (value(cost_lower) - m * value(cap_lower)) + + +def cost_invest_eos_cluster_cumulative_cost( + model: EOSModel, r: Region, p: Period, t: Technology +) -> Expression: + """ + Cumulative investment cost for an EOS invest cluster up to period p, + accounting for which segment of the cost curve is active. + """ + return quicksum( + cost_invest_eos_segment_cost( + model, + r, + t, + n, + model.v_cost_invest_eos_cumulative_capacity[r, p, t, n], + model.v_cost_invest_eos_segment_binary[r, p, t, n], + ) + for n in model.cost_invest_eos_segments[r, t] + ) + + +def period_cost(model: EOSModel, r: Region, p: Period, t: Technology) -> Expression: + r""" + EOS invest clusters do not contribute to the base-model + :math:`\text{CostInvest}` sum. Instead, the extension adds a separate + discounted-investment term to :code:`total_cost` via a + :code:`BuildAction` (``append_cost_invest_eos_to_total_cost``). + + The *period cost* for cluster :math:`(r, p, t)` is the *incremental* + cumulative cost: the cost of all capacity built up to period :math:`p` + minus the cost of all capacity built up to the preceding EOS invest period + (or the cost attributable to pre-existing capacity in the first period): + + .. math:: + + \text{EOS\_Invest\_PeriodCost}_{r,p,t} = + \mathcal{C}_{r,p,t} - \mathcal{C}_{r,p_{\text{prev}},t} + + where the cumulative cost for a cluster in a given period is: + + .. math:: + + \mathcal{C}_{r,p,t} = + \sum_{n \in N_{r,t}} + \left[ + m_{r,t,n} \cdot \textbf{CIECAP}_{r,p,t,n} + + \textbf{CIEB}_{r,p,t,n} + \left(\underline{C}_{r,t,n} - m_{r,t,n} \cdot \underline{K}_{r,t,n}\right) + \right] + + \text{with slope }m_{r,p,t,n} = \dfrac{\overline{C}_{r,p,t,n} - + \underline{C}_{r,p,t,n}}{\overline{K}_{r,p,t,n} - \underline{K}_{r,p,t,n}} + + Note the terms in this summation are only non-zero for the active segment. + + This incremental cost is then discounted and amortised using the same + :func:`~temoa.components.costs.loan_cost` function as for cost_invest in + the base model, using the reference process :math:`(r_0, t_0)` for loan + parameters. The result is added to :code:`total_cost` once per + cluster-period combination in + :math:`\Theta_{\text{cost\_invest\_eos\_period\_rpt}}`. + """ + + cumulative_cost = cost_invest_eos_cluster_cumulative_cost(model, r, p, t) + prev_cum_cost: ExprLike = 0.0 + + # Subtract previously accumulated investment costs to get the incremental for discounting + prev_periods = { + _p for _r, _p, _t in model.cost_invest_eos_period_rpt if _r == r and _t == t and _p < p + } + if len(prev_periods) > 0: + # Endogenous previous cumulative investment costs to subtract + p_prev = max(prev_periods) + prev_cum_cost = cost_invest_eos_cluster_cumulative_cost(model, r, p_prev, t) + else: + # Existing capacity costs to subtract (needed for myopic) + regions = geography.gather_group_regions(model, r) + techs = technology.gather_group_techs(model, t) + existing_capacity = quicksum( + value(model.existing_capacity[_r, _t, _v]) + for _r, _t, _v in model.existing_capacity + if _r in regions and _t in techs + ) + if existing_capacity: + for n in model.cost_invest_eos_segments[r, t]: + cap_lower, cap_upper, _, _ = model.cost_invest_eos[r, t, n] + if value(cap_lower) <= existing_capacity <= value(cap_upper): + prev_cum_cost = cost_invest_eos_segment_cost(model, r, t, n, existing_capacity) + continue # in case we're exactly on the boundary of two segments + if not prev_cum_cost: + msg = ( + 'Existing capacity for a cost_invest_eos cluster is outside the bounds of ' + 'the cost curve. Check the cost_invest_eos table and existing_capacity ' + f'for {r, t}: {existing_capacity}' + ) + logger.error(msg) + raise ValueError(msg) + + return cumulative_cost - prev_cum_cost + + +def total_cost(model: EOSModel) -> None: + """ + Discounted investment costs for all EOS invest clusters in the planning horizon + """ + + p_0 = min(model.time_optimize) + p_e = model.time_future.last() + global_discount_rate = value(model.global_discount_rate) + + total_cost = 0.0 + + for _r, _p, _t in model.cost_invest_eos_period_rpt: + _r0, _t0 = model.cost_invest_eos_reference_process[_r, _p, _t] + if model.is_survival_curve_process[_r0, _t0, _p]: + total_cost += loan_cost_survival_curve( + model, + _r0, + _t0, + _p, + 1, + period_cost(model, _r, _p, _t), + value(model.loan_annualize[_r0, _t0, _p]), + value(model.loan_lifetime_process[_r0, _t0, _p]), + p_0, + p_e, + global_discount_rate, + ) + else: + total_cost += loan_cost( + 1, + period_cost(model, _r, _p, _t), + value(model.loan_annualize[_r0, _t0, _p]), + value(model.loan_lifetime_process[_r0, _t0, _p]), + value(model.lifetime_process[_r0, _t0, _p]), + p_0, + p_e, + global_discount_rate, + vintage=_p, + ) + + # Append to total cost objective + model.total_cost.set_value(cast('ObjectiveData', model.total_cost).expr + total_cost) diff --git a/temoa/extensions/economies_of_scale/components/cost_variable_eos.py b/temoa/extensions/economies_of_scale/components/cost_variable_eos.py new file mode 100644 index 000000000..e65868cc6 --- /dev/null +++ b/temoa/extensions/economies_of_scale/components/cost_variable_eos.py @@ -0,0 +1,334 @@ +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING, cast + +from pyomo.environ import quicksum, value + +import temoa.components.geography as geography +import temoa.components.technology as technology +from temoa.components.costs import fixed_or_variable_cost + +if TYPE_CHECKING: + from pyomo.core import Expression + from pyomo.core.base.objective import ObjectiveData + from pyomo.core.base.var import VarData + + from temoa.extensions.economies_of_scale.core.model import EOSModel + from temoa.types import ExprLike, Period, Region, Technology + +logger = logging.getLogger(__name__) + + +def period_cost_indices(model: EOSModel) -> set[tuple[Region, Period, Technology]]: + return {(r, p, t) for r, p, t, _ in model.cost_variable_eos_rptn} + + +def initialize_components(model: EOSModel) -> None: + """ + Organise some data and put up some guard rails + """ + + # Collect segment indices n for each (r,p,t) cluster; order doesn't matter + for r, p, t, n in model.cost_variable_eos_rptn: + model.cost_variable_eos_segments.setdefault((r, p, t), set()).add(n) + + # Check that costs and activities are monotonically increasing + for (r, p, t), segs in model.cost_variable_eos_segments.items(): + sorted_segs = sorted(segs) + for i, n in enumerate(sorted_segs): + act_lower, act_upper, cost_lower, cost_upper = model.cost_variable_eos[r, p, t, n] + + # Check cost curve is nonnegative and monotonically increasing + # If someone wants to break this assumption they should have the + # skills to edit this code. + if not all( + ( + act_lower >= 0, + act_upper >= 0, + cost_lower >= 0, + cost_upper >= 0, + act_upper > act_lower, + cost_upper > cost_lower, + ) + ): + msg = ( + 'Negative values or non-increasing segment bounds found ' + f'in cost_variable_eos table for {r, p, t, n}' + ) + logger.error(msg) + raise ValueError(msg) + + # Backcheck for monotonic growth + if i == 0: + continue + + _, prev_act_upper, _, prev_cost_upper = model.cost_variable_eos[ + r, p, t, sorted_segs[i - 1] + ] + + if not all( + ( + abs(act_lower - prev_act_upper) <= 0.001, + abs(cost_lower - prev_cost_upper) <= 0.001, + ) + ): + msg = ( + 'Segments in cost_variable_eos table do not align on their bounds. This would ' + 'leave gaps in the cost curve and could lead to infeasibility. Check ' + f'({r, p, t, sorted_segs[i - 1]}) to ({r, p, t, n})' + ) + logger.error(msg) + raise ValueError(msg) + + +# --- Enforce the rules of activity progression up the cost curve --- +def cost_variable_eos_segment_binary_constraint( + model: EOSModel, r: Region, p: Period, t: Technology +) -> ExprLike: + r""" + Enforce exactly one active segment of the cost curve for each cluster + in each period. + + Each technology cluster :math:`(r, p, t)` has a piecewise-linear cost curve + split into :math:`N_{r,p,t}` segments. The binary variable + :math:`\textbf{CVEB}_{r,p,t,n}` selects which segment is currently active. + Exactly one segment must be active at all times: + + .. math:: + :label: Cost Variable EOS Segment Binary Constraint + + \sum_{n \in N_{r,p,t}} \textbf{CVEB}_{r,p,t,n} = 1 + + \qquad \forall \{r, p, t\} \in \Theta_{\text{cost\_variable\_eos\_period\_rpt}} + + where :math:`\textbf{CVEB}_{r,p,t,n}` is + :code:`v_cost_variable_eos_segment_binary[r, p, t, n]` (:math:`\in \{0, 1\}`) and + :math:`N_{r,p,t}` is the set of segment indices declared for cluster + :math:`(r, p, t)` in the :code:`cost_variable_eos` table. + """ + count_active_segments = quicksum( + model.v_cost_variable_eos_segment_binary[r, p, t, n] + for n in model.cost_variable_eos_segments[r, p, t] + ) + return count_active_segments == 1 + + +def cost_variable_eos_activity_lower_bound_constraint( + model: EOSModel, r: Region, p: Period, t: Technology, n: int +) -> ExprLike: + r""" + Enforce the lower bound of the active EOS cost-curve segment on the + activity variable for that segment. + + When segment :math:`n` is inactive (:math:`\textbf{CVEB}_{r,p,t,n} = 0`), the + right-hand side collapses to zero so the constraint is non-binding. When + segment :math:`n` is active (:math:`\textbf{CVEB}_{r,p,t,n} = 1`), it + enforces that the segment activity is at least the lower boundary + :math:`\underline{A}_{r,p,t,n}` of that segment: + + .. math:: + :label: Cost Variable EOS Activity Lower Bound + + \textbf{CVEACT}_{r,p,t,n} + \ge \textbf{CVEB}_{r,p,t,n} \cdot \underline{A}_{r,p,t,n} + + \qquad \forall \{r, p, t, n\} \in \Theta_{\text{cost\_variable\_eos\_rptn}} + + where :math:`\textbf{CVEACT}_{r,p,t,n}` is + :code:`v_cost_variable_eos_activity[r, p, t, n]`, :math:`\textbf{CVEB}_{r,p,t,n}` + is :code:`v_cost_variable_eos_segment_binary[r, p, t, n]`, and + :math:`\underline{A}_{r,p,t,n}` is :code:`cost_variable_eos[r, p, t, n][0]` + (the :code:`activity_lower` column). + """ + act_lower = model.v_cost_variable_eos_segment_binary[r, p, t, n] * value( + model.cost_variable_eos[r, p, t, n][0] + ) + return model.v_cost_variable_eos_activity[r, p, t, n] >= act_lower + + +def cost_variable_eos_activity_upper_bound_constraint( + model: EOSModel, r: Region, p: Period, t: Technology, n: int +) -> ExprLike: + r""" + Enforce the upper bound of the active EOS cost-curve segment on the + activity variable for that segment. + + This is the mirror of the lower bound. When segment :math:`n` is inactive + the right-hand side collapses to zero, driving + :math:`\textbf{CVEACT}_{r,p,t,n}` to zero. When it is active, + the segment activity cannot exceed the upper boundary + :math:`\overline{A}_{r,p,t,n}` of that segment: + + .. math:: + :label: Cost Variable EOS Activity Upper Bound + + \textbf{CVEACT}_{r,p,t,n} + \le \textbf{CVEB}_{r,p,t,n} \cdot \overline{A}_{r,p,t,n} + + \qquad \forall \{r, p, t, n\} \in \Theta_{\text{cost\_variable\_eos\_rptn}} + + where :math:`\overline{A}_{r,p,t,n}` is :code:`cost_variable_eos[r, p, t, n][1]` + (the :code:`activity_upper` column). + + Together the lower and upper bound constraints implement a "big-M"-style + activation: only the single active segment can carry non-zero activity, + and that activity is pinned within the segment's range. + """ + act_upper = model.v_cost_variable_eos_segment_binary[r, p, t, n] * value( + model.cost_variable_eos[r, p, t, n][1] + ) + return model.v_cost_variable_eos_activity[r, p, t, n] <= act_upper + + +def cost_variable_eos_activity_constraint( + model: EOSModel, r: Region, p: Period, t: Technology +) -> ExprLike: + r""" + Equate the EOS activity expression to the actual total activity in the + base model. + + The EOS formulation tracks activity internally through + :math:`\textbf{CVEACT}_{r,p,t,n}`. This constraint ties those + internal variables to the real flow decision variables from the base + Temoa model, so the cost curve reflects actual activity rather than + a free parameter: + + .. math:: + :label: Cost Variable EOS Activity + + \sum_{n \in N_{r,p,t}} \textbf{CVEACT}_{r,p,t,n} + = + \sum_{\substack{r' \in R(r),\, t' \in T(t) \setminus T_a,\\ + v,\, s,\, d,\, i,\, o}} + \textbf{FO}_{r',p,s,d,i,t',v,o} + + + \sum_{\substack{r' \in R(r),\, t' \in T(t) \cap T_a,\\ + v,\, i,\, o}} + \textbf{FOA}_{r',p,i,t',v,o} + + \qquad \forall \{r, p, t\} \in \Theta_{\text{cost\_variable\_eos\_period\_rpt}} + + where :math:`R(r)` and :math:`T(t)` expand any group labels to the + individual regions and technologies they contain, :math:`T_a` is the set + of annual technologies (:code:`tech_annual`), + :math:`\textbf{FO}` is :code:`v_flow_out` summed over all vintages, + seasons :math:`s`, times of day :math:`d`, inputs :math:`i`, and outputs + :math:`o` active in period :math:`p`, and :math:`\textbf{FOA}` is + :code:`v_flow_out_annual` for annual technologies (no :math:`s,d` indices). + """ + + regions = geography.gather_group_regions(model, r) + techs = technology.gather_group_techs(model, t) + + activity_eos = quicksum( + model.v_cost_variable_eos_activity[r, p, t, n] + for n in model.cost_variable_eos_segments[r, p, t] + ) + activity = quicksum( + model.v_flow_out[_r, p, s, d, S_i, _t, S_v, S_o] + for _t in techs + if _t not in model.tech_annual + for _r in regions + for S_v in model.process_vintages.get((_r, p, _t), []) + for S_i in model.process_inputs[_r, p, _t, S_v] + for S_o in model.process_outputs_by_input[_r, p, _t, S_v, S_i] + for s in model.time_season + for d in model.time_of_day + ) + activity += quicksum( + model.v_flow_out_annual[_r, p, S_i, _t, S_v, S_o] + for _t in techs + if _t in model.tech_annual + for _r in regions + for S_v in model.process_vintages.get((_r, p, _t), []) + for S_i in model.process_inputs[_r, p, _t, S_v] + for S_o in model.process_outputs_by_input[_r, p, _t, S_v, S_i] + ) + return activity == activity_eos + + +# --- Calculating costs --- +def cost_variable_eos_segment_cost( + model: EOSModel, + r: Region, + p: Period, + t: Technology, + n: int, + activity: ExprLike, + binary: VarData | bool = True, +) -> ExprLike: + """Variable cost within a segment, if activity were in that segment""" + act_lower, act_upper, cost_lower, cost_upper = model.cost_variable_eos[r, p, t, n] + m = (value(cost_upper) - value(cost_lower)) / (value(act_upper) - value(act_lower)) + return m * activity + binary * (value(cost_lower) - m * value(act_lower)) + + +def period_cost(model: EOSModel, r: Region, p: Period, t: Technology) -> Expression: + r""" + EOS clusters do not contribute to the base-model + :math:`\text{CostVariable}` sum. Instead, the extension adds a separate + discounted variable-cost term to :code:`total_cost` via a + :code:`BuildAction` (``append_cost_variable_eos_to_total_cost``). + + The *period cost* for cluster :math:`(r, p, t)` is evaluated from the + piecewise-linear cost curve at the total activity in period :math:`p`: + + .. math:: + + \text{EOS\_PeriodCost}_{r,p,t} = + \sum_{n \in N_{r,p,t}} + \left[ + m_{r,p,t,n} \cdot \textbf{CVEACT}_{r,p,t,n} + + \textbf{CVEB}_{r,p,t,n} + \left(\underline{C}_{r,p,t,n} - m_{r,p,t,n} + \cdot \underline{A}_{r,p,t,n}\right) + \right] + + \text{with slope }m_{r,p,t,n} = \dfrac{\overline{C}_{r,p,t,n} - + \underline{C}_{r,p,t,n}}{\overline{A}_{r,p,t,n} - \underline{A}_{r,p,t,n}} + + Note the terms in this summation are only non-zero for the active segment. + + This cost is then discounted and amortised using the same + :func:`~temoa.components.costs.fixed_or_variable_cost` function as for + cost_variable in the base model. The result is added to :code:`total_cost` + once per cluster-period combination in + :math:`\Theta_{\text{cost\_variable\_eos\_period\_rpt}}`. + """ + return quicksum( + cost_variable_eos_segment_cost( + model, + r, + p, + t, + n, + model.v_cost_variable_eos_activity[r, p, t, n], + model.v_cost_variable_eos_segment_binary[r, p, t, n], + ) + for n in model.cost_variable_eos_segments[r, p, t] + ) + + +def total_cost(model: EOSModel) -> None: + """ + Discounted fixed costs for all EOS clusters in the planning horizon + """ + + p_0 = min(model.time_optimize) + global_discount_rate = value(model.global_discount_rate) + + total_cost = quicksum( + fixed_or_variable_cost( + 1, + period_cost(model, r, p, t), + value(model.period_length[p]), + global_discount_rate, + p_0, + p=p, + ) + for r, p, t in model.cost_variable_eos_period_rpt + ) + + # Append to total cost objective + model.total_cost.set_value(cast('ObjectiveData', model.total_cost).expr + total_cost) diff --git a/temoa/extensions/economies_of_scale/core/__init__.py b/temoa/extensions/economies_of_scale/core/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/temoa/extensions/economies_of_scale/core/data_puller.py b/temoa/extensions/economies_of_scale/core/data_puller.py new file mode 100644 index 000000000..d34a6279c --- /dev/null +++ b/temoa/extensions/economies_of_scale/core/data_puller.py @@ -0,0 +1,181 @@ +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING, cast + +from pyomo.environ import value + +from temoa._internal.exchange_tech_cost_ledger import CostType, ExchangeTechCostLedger +from temoa.components import costs +from temoa.extensions.economies_of_scale.components import ( + cost_fixed_eos, + cost_invest_eos, + cost_variable_eos, +) + +if TYPE_CHECKING: + from temoa.core.model import TemoaModel + from temoa.extensions.economies_of_scale.core.model import EOSModel + from temoa.types import Period, Region, Technology, Vintage + +logger = logging.getLogger(__name__) + + +# --- Poll cost results --- +def poll_costs( + model: TemoaModel, + exchange_costs: ExchangeTechCostLedger, + entries: dict[tuple[Region, Period, Technology, Vintage], dict[CostType, float]], + p_0: Period, + epsilon: float, +) -> None: + """ + Poll the fixed and variable costs for all EOS clusters in the planning horizon + and add them to the cost entries for the model. + """ + if 'eos' not in model.enabled_extensions: + return + model = cast('EOSModel', model) + + global_discount_rate = value(model.global_discount_rate) + + for r, p, t in model.cost_invest_eos_period_rpt: + cost = value(cost_invest_eos.period_cost(model, r, p, t)) + if cost < epsilon: + continue + + # gather details... + r0, t0 = model.cost_invest_eos_reference_process[r, p, t] + loan_life = value(model.loan_lifetime_process[r0, t0, p]) + loan_annualize = value(model.loan_annualize[r0, t0, p]) + life = value(model.lifetime_process[r0, t0, p]) + + if model.is_survival_curve_process[r0, t0, p]: + discounted_cost, undiscounted_cost = costs.poll_loan_costs_survival_curve( + model=model, + r=r0, + t=t0, + v=p, + loan_life=loan_life, + loan_annualize=loan_annualize, + capacity=1, + invest_cost=cost, + p_0=p_0, + p_e=model.time_future.last(), + global_discount_rate=global_discount_rate, + ) + else: + discounted_cost, undiscounted_cost = costs.poll_loan_costs( + loan_life=loan_life, + loan_annualize=loan_annualize, + capacity=1, + invest_cost=cost, + process_life=life, + p_0=p_0, + p_e=model.time_future.last(), + global_discount_rate=global_discount_rate, + vintage=p, + ) + + # screen for linked region... + if '-' in r: + exchange_costs.add_cost_record( + r, + period=p, + tech=t, + vintage=p, + cost=discounted_cost, + cost_type=CostType.D_INVEST, + ) + exchange_costs.add_cost_record( + r, + period=p, + tech=t, + vintage=p, + cost=undiscounted_cost, + cost_type=CostType.INVEST, + ) + else: + # The period `p` for an investment cost is its vintage `v`. + key = (cast('Region', r), cast('Period', p), cast('Technology', t), cast('Vintage', p)) + entry = entries[key] + entry[CostType.D_INVEST] = entry.get(CostType.D_INVEST, 0.0) + discounted_cost + entry[CostType.INVEST] = entry.get(CostType.INVEST, 0.0) + undiscounted_cost + + for r, p, t in model.cost_fixed_eos_period_rpt: + fixed_cost = value(cost_fixed_eos.period_cost(model, r, p, t)) + if fixed_cost < epsilon: + continue + + ud_fixed = float(fixed_cost * value(model.period_length[p])) + d_fixed = costs.fixed_or_variable_cost( + 1, + fixed_cost, + value(model.period_length[p]), + global_discount_rate=global_discount_rate, + p_0=p_0, + p=p, + ) + d_fixed = float(value(d_fixed)) + + if '-' in r: + exchange_costs.add_cost_record( + r, + period=p, + tech=t, + vintage=p, + cost=d_fixed, + cost_type=CostType.D_FIXED, + ) + exchange_costs.add_cost_record( + r, + period=p, + tech=t, + vintage=p, + cost=ud_fixed, + cost_type=CostType.FIXED, + ) + else: + key = (cast('Region', r), cast('Period', p), cast('Technology', t), cast('Vintage', p)) + entry = entries[key] + entry[CostType.D_FIXED] = entry.get(CostType.D_FIXED, 0.0) + d_fixed + entry[CostType.FIXED] = entry.get(CostType.FIXED, 0.0) + ud_fixed + + for r, p, t in model.cost_variable_eos_period_rpt: + variable_cost = value(cost_variable_eos.period_cost(model, r, p, t)) + if variable_cost < epsilon: + continue + + ud_variable = float(variable_cost * value(model.period_length[p])) + d_variable = costs.fixed_or_variable_cost( + 1, + variable_cost, + value(model.period_length[p]), + global_discount_rate=global_discount_rate, + p_0=p_0, + p=p, + ) + d_variable = float(value(d_variable)) + + if '-' in r: + exchange_costs.add_cost_record( + r, + period=p, + tech=t, + vintage=p, + cost=d_variable, + cost_type=CostType.D_VARIABLE, + ) + exchange_costs.add_cost_record( + r, + period=p, + tech=t, + vintage=p, + cost=ud_variable, + cost_type=CostType.VARIABLE, + ) + else: + key = (cast('Region', r), cast('Period', p), cast('Technology', t), cast('Vintage', p)) + entry = entries[key] + entry[CostType.D_VARIABLE] = entry.get(CostType.D_VARIABLE, 0.0) + d_variable + entry[CostType.VARIABLE] = entry.get(CostType.VARIABLE, 0.0) + ud_variable diff --git a/temoa/extensions/economies_of_scale/core/model.py b/temoa/extensions/economies_of_scale/core/model.py new file mode 100644 index 000000000..6b1298a41 --- /dev/null +++ b/temoa/extensions/economies_of_scale/core/model.py @@ -0,0 +1,210 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, cast + +from pyomo.environ import ( + Any, + Binary, + BuildAction, + Constraint, + Integers, + NonNegativeReals, + Param, + Set, + Var, +) + +import temoa.extensions.economies_of_scale.components.cost_fixed_eos as cost_fixed_eos +import temoa.extensions.economies_of_scale.components.cost_invest_eos as cost_invest_eos +import temoa.extensions.economies_of_scale.components.cost_variable_eos as cost_variable_eos + +if TYPE_CHECKING: + from temoa.core.model import TemoaModel + from temoa.types.core_types import Period, Region, Technology + + class EOSModel(TemoaModel): + """Inherits the base TemoaModel class and declares EOS extension + components for type checking/hinting purposes""" + + # Param (data from database table) + cost_invest_eos: Param + cost_fixed_eos: Param + cost_variable_eos: Param + + # Primitive components (instantiation helpers) + cost_invest_eos_segments: dict[tuple[Region, Technology], set[int]] + cost_invest_eos_reference_process: dict[ + tuple[Region, Period, Technology], tuple[Region, Technology] + ] + cost_fixed_eos_segments: dict[tuple[Region, Period, Technology], set[int]] + cost_variable_eos_segments: dict[tuple[Region, Period, Technology], set[int]] + + # Sets (indexing sets for variables and constraints) + cost_invest_eos_rtn: Set + cost_invest_eos_segment_rptn: Set + cost_invest_eos_period_rpt: Set + cost_fixed_eos_rptn: Set + cost_variable_eos_rptn: Set + cost_fixed_eos_period_rpt: Set + cost_variable_eos_period_rpt: Set + + # Instantiation build actions + append_cost_invest_eos_to_cost_invest_rtv: BuildAction + initialize_cost_invest_eos: BuildAction + append_cost_invest_eos_to_total_cost: BuildAction + initialize_cost_fixed_eos: BuildAction + append_cost_fixed_eos_to_total_cost: BuildAction + initialize_cost_variable_eos: BuildAction + append_cost_variable_eos_to_total_cost: BuildAction + + # Decision variables + v_cost_invest_eos_cumulative_capacity: Var + v_cost_invest_eos_segment_binary: Var + v_cost_fixed_eos_capacity: Var + v_cost_fixed_eos_segment_binary: Var + v_cost_variable_eos_activity: Var + v_cost_variable_eos_segment_binary: Var + + # Constraints + cost_invest_eos_segment_binary_constraint: Constraint + cost_invest_eos_capacity_lower_bound_constraint: Constraint + cost_invest_eos_capacity_upper_bound_constraint: Constraint + cost_invest_eos_cumulative_capacity_constraint: Constraint + cost_fixed_eos_segment_binary_constraint: Constraint + cost_fixed_eos_capacity_lower_bound_constraint: Constraint + cost_fixed_eos_capacity_upper_bound_constraint: Constraint + cost_fixed_eos_capacity_constraint: Constraint + cost_variable_eos_segment_binary_constraint: Constraint + cost_variable_eos_activity_lower_bound_constraint: Constraint + cost_variable_eos_activity_upper_bound_constraint: Constraint + cost_variable_eos_activity_constraint: Constraint + + +def register_early_eos_components(model: TemoaModel) -> None: + """Build cost_invest_eos components that must be instantiated before loan parameters.""" + if 'eos' not in model.enabled_extensions: + return + m = cast('EOSModel', model) + + m.cost_invest_eos_rtn = Set( + within=model.regional_global_indices * model.tech_or_group * Integers + ) + m.cost_invest_eos = Param(m.cost_invest_eos_rtn, domain=Any) + + m.cost_invest_eos_segments = {} + m.cost_invest_eos_reference_process = {} + + m.cost_invest_eos_segment_rptn = Set( + dimen=4, initialize=cost_invest_eos.cost_invest_eos_cumulative_capacity_indices + ) + m.cost_invest_eos_period_rpt = Set( + dimen=3, initialize=cost_invest_eos.cost_invest_eos_period_cost_indices + ) + + m.append_cost_invest_eos_to_cost_invest_rtv = BuildAction( + rule=cost_invest_eos.append_cost_invest_eos_rtv + ) + + +def register_model_components(model: TemoaModel) -> None: + """Build remaining components that can be instantiated at the end of model instantiation""" + m = cast('EOSModel', model) + + # --- cost_invest_eos --- + m.initialize_cost_invest_eos = BuildAction(rule=cost_invest_eos.initialize_cost_invest_eos) + + m.v_cost_invest_eos_cumulative_capacity = Var( + m.cost_invest_eos_segment_rptn, domain=NonNegativeReals, initialize=0 + ) + m.v_cost_invest_eos_segment_binary = Var( + m.cost_invest_eos_segment_rptn, domain=Binary, initialize=0 + ) + + m.cost_invest_eos_segment_binary_constraint = Constraint( + m.cost_invest_eos_period_rpt, + rule=cost_invest_eos.cost_invest_eos_segment_binary_constraint, + ) + m.cost_invest_eos_cumulative_capacity_constraint = Constraint( + m.cost_invest_eos_period_rpt, + rule=cost_invest_eos.cost_invest_eos_cumulative_capacity_constraint, + ) + m.cost_invest_eos_capacity_lower_bound_constraint = Constraint( + m.cost_invest_eos_segment_rptn, + rule=cost_invest_eos.cost_invest_eos_capacity_lower_bound_constraint, + ) + m.cost_invest_eos_capacity_upper_bound_constraint = Constraint( + m.cost_invest_eos_segment_rptn, + rule=cost_invest_eos.cost_invest_eos_capacity_upper_bound_constraint, + ) + + m.append_cost_invest_eos_to_total_cost = BuildAction(rule=cost_invest_eos.total_cost) + + # --- cost_fixed_eos --- + m.cost_fixed_eos_rptn = Set( + within=model.regional_global_indices * model.time_optimize * model.tech_or_group * Integers + ) + m.cost_fixed_eos = Param(m.cost_fixed_eos_rptn, domain=Any) + + m.cost_fixed_eos_segments = {} + + m.cost_fixed_eos_period_rpt = Set(dimen=3, initialize=cost_fixed_eos.period_cost_indices) + + m.initialize_cost_fixed_eos = BuildAction(rule=cost_fixed_eos.initialize_components) + + m.v_cost_fixed_eos_capacity = Var(m.cost_fixed_eos_rptn, domain=NonNegativeReals, initialize=0) + m.v_cost_fixed_eos_segment_binary = Var(m.cost_fixed_eos_rptn, domain=Binary, initialize=0) + + m.cost_fixed_eos_segment_binary_constraint = Constraint( + m.cost_fixed_eos_period_rpt, rule=cost_fixed_eos.cost_fixed_eos_segment_binary_constraint + ) + m.cost_fixed_eos_capacity_constraint = Constraint( + m.cost_fixed_eos_period_rpt, rule=cost_fixed_eos.cost_fixed_eos_capacity_constraint + ) + m.cost_fixed_eos_capacity_lower_bound_constraint = Constraint( + m.cost_fixed_eos_rptn, + rule=cost_fixed_eos.cost_fixed_eos_capacity_lower_bound_constraint, + ) + m.cost_fixed_eos_capacity_upper_bound_constraint = Constraint( + m.cost_fixed_eos_rptn, + rule=cost_fixed_eos.cost_fixed_eos_capacity_upper_bound_constraint, + ) + + m.append_cost_fixed_eos_to_total_cost = BuildAction(rule=cost_fixed_eos.total_cost) + + # -- cost_variable_eos --- + m.cost_variable_eos_segments = {} + + m.cost_variable_eos_rptn = Set( + within=model.regional_global_indices * model.time_optimize * model.tech_or_group * Integers + ) + m.cost_variable_eos = Param(m.cost_variable_eos_rptn, domain=Any) + + m.cost_variable_eos_period_rpt = Set(dimen=3, initialize=cost_variable_eos.period_cost_indices) + + m.initialize_cost_variable_eos = BuildAction(rule=cost_variable_eos.initialize_components) + + m.v_cost_variable_eos_activity = Var( + m.cost_variable_eos_rptn, domain=NonNegativeReals, initialize=0 + ) + m.v_cost_variable_eos_segment_binary = Var( + m.cost_variable_eos_rptn, domain=Binary, initialize=0 + ) + + m.cost_variable_eos_segment_binary_constraint = Constraint( + m.cost_variable_eos_period_rpt, + rule=cost_variable_eos.cost_variable_eos_segment_binary_constraint, + ) + m.cost_variable_eos_activity_constraint = Constraint( + m.cost_variable_eos_period_rpt, + rule=cost_variable_eos.cost_variable_eos_activity_constraint, + ) + m.cost_variable_eos_activity_lower_bound_constraint = Constraint( + m.cost_variable_eos_rptn, + rule=cost_variable_eos.cost_variable_eos_activity_lower_bound_constraint, + ) + m.cost_variable_eos_activity_upper_bound_constraint = Constraint( + m.cost_variable_eos_rptn, + rule=cost_variable_eos.cost_variable_eos_activity_upper_bound_constraint, + ) + + m.append_cost_variable_eos_to_total_cost = BuildAction(rule=cost_variable_eos.total_cost) diff --git a/temoa/extensions/economies_of_scale/data_manifest.py b/temoa/extensions/economies_of_scale/data_manifest.py new file mode 100644 index 000000000..a74bd11d3 --- /dev/null +++ b/temoa/extensions/economies_of_scale/data_manifest.py @@ -0,0 +1,74 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, cast + +from temoa.data_io.loader_manifest import LoadItem + +if TYPE_CHECKING: + from temoa.core.model import TemoaModel + from temoa.extensions.economies_of_scale.core.model import EOSModel + + +def build_manifest_items(model: TemoaModel) -> list[LoadItem]: + m = cast('EOSModel', model) + return [ + LoadItem( + component=m.cost_invest_eos, + table='cost_invest_eos', + columns=[ + 'region', + 'tech_or_group', + 'segment', + 'capacity_lower', + 'capacity_upper', + 'cost_lower', + 'cost_upper', + ], + index_length=3, + validator_name='viable_rt', + validation_map=(0, 1), + is_table_required=False, + is_period_filtered=False, + index_set=m.cost_invest_eos_rtn, + ), + LoadItem( + component=m.cost_fixed_eos, + table='cost_fixed_eos', + columns=[ + 'region', + 'period', + 'tech_or_group', + 'segment', + 'capacity_lower', + 'capacity_upper', + 'cost_lower', + 'cost_upper', + ], + index_length=4, + validator_name='viable_rpt', + validation_map=(0, 1, 2), + is_table_required=False, + is_period_filtered=True, + index_set=m.cost_fixed_eos_rptn, + ), + LoadItem( + component=m.cost_variable_eos, + table='cost_variable_eos', + columns=[ + 'region', + 'period', + 'tech_or_group', + 'segment', + 'activity_lower', + 'activity_upper', + 'cost_lower', + 'cost_upper', + ], + index_length=4, + validator_name='viable_rpt', + validation_map=(0, 1, 2), + is_table_required=False, + is_period_filtered=True, + index_set=m.cost_variable_eos_rptn, + ), + ] diff --git a/temoa/extensions/economies_of_scale/extension.py b/temoa/extensions/economies_of_scale/extension.py new file mode 100644 index 000000000..c0b5d8cf5 --- /dev/null +++ b/temoa/extensions/economies_of_scale/extension.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +from pathlib import Path + +from temoa.extensions.economies_of_scale.core.model import register_model_components +from temoa.extensions.economies_of_scale.data_manifest import build_manifest_items +from temoa.extensions.framework import ExtensionSpec + +EOS_EXTENSION = ExtensionSpec( + extension_id='eos', + owned_tables=('cost_invest_eos', 'cost_fixed_eos', 'cost_variable_eos'), + regional_group_tables={ + 'cost_invest_eos': 'region', + 'cost_fixed_eos': 'region', + 'cost_variable_eos': 'region', + }, + register_model_components=register_model_components, + build_manifest_items=build_manifest_items, + schema_sql_path=str(Path(__file__).parent / 'tables.sql'), + fail_if_tables_populated_when_disabled=True, +) diff --git a/temoa/extensions/economies_of_scale/tables.sql b/temoa/extensions/economies_of_scale/tables.sql new file mode 100644 index 000000000..2efd7fa18 --- /dev/null +++ b/temoa/extensions/economies_of_scale/tables.sql @@ -0,0 +1,53 @@ +CREATE TABLE IF NOT EXISTS cost_invest_eos +( + region TEXT NOT NULL, + tech_or_group TEXT NOT NULL, + segment INTEGER NOT NULL, + capacity_lower REAL NOT NULL, + capacity_upper REAL NOT NULL, + cost_lower REAL NOT NULL, + cost_upper REAL NOT NULL, + units TEXT, + notes TEXT, + CHECK (capacity_lower >= 0), + CHECK (capacity_upper >= 0), + CHECK (cost_lower >= 0), + CHECK (cost_upper >= 0), + PRIMARY KEY (region, tech_or_group, segment) +); +CREATE TABLE IF NOT EXISTS cost_fixed_eos +( + region TEXT NOT NULL, + period INTEGER NOT NULL, + tech_or_group TEXT NOT NULL, + segment INTEGER NOT NULL, + capacity_lower REAL NOT NULL, + capacity_upper REAL NOT NULL, + cost_lower REAL NOT NULL, + cost_upper REAL NOT NULL, + units TEXT, + notes TEXT, + CHECK (capacity_lower >= 0), + CHECK (capacity_upper >= 0), + CHECK (cost_lower >= 0), + CHECK (cost_upper >= 0), + PRIMARY KEY (region, period, tech_or_group, segment) +); +CREATE TABLE IF NOT EXISTS cost_variable_eos +( + region TEXT NOT NULL, + period INTEGER NOT NULL, + tech_or_group TEXT NOT NULL, + segment INTEGER NOT NULL, + activity_lower REAL NOT NULL, + activity_upper REAL NOT NULL, + cost_lower REAL NOT NULL, + cost_upper REAL NOT NULL, + units TEXT, + notes TEXT, + CHECK (activity_lower >= 0), + CHECK (activity_upper >= 0), + CHECK (cost_lower >= 0), + CHECK (cost_upper >= 0), + PRIMARY KEY (region, period, tech_or_group, segment) +); diff --git a/temoa/extensions/framework.py b/temoa/extensions/framework.py new file mode 100644 index 000000000..7927e3bf3 --- /dev/null +++ b/temoa/extensions/framework.py @@ -0,0 +1,231 @@ +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass, field +from logging import getLogger +from pathlib import Path +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Mapping, Sequence + from sqlite3 import Connection + + from temoa.core.model import TemoaModel + from temoa.data_io.loader_manifest import LoadItem + +ModelHook = Callable[['TemoaModel'], None] +ManifestHook = Callable[['TemoaModel'], list['LoadItem']] + +logger = getLogger(__name__) + + +@dataclass(frozen=True) +class ExtensionSpec: + """Declarative metadata and hooks for an optional modeling extension.""" + + extension_id: str + owned_tables: tuple[str, ...] = () + regional_group_tables: dict[str, str] = field(default_factory=dict) + register_model_components: ModelHook | None = None + build_manifest_items: ManifestHook | None = None + schema_sql_path: str | None = None + fail_if_tables_populated_when_disabled: bool = False + + +def normalize_extension_ids(extension_ids: Sequence[str | object] | None) -> tuple[str, ...]: + """Normalize configured extension ids while preserving user-provided order.""" + if not extension_ids: + return () + + normalized: list[str] = [] + seen: set[str] = set() + for ext_id in extension_ids: + if not isinstance(ext_id, str): + msg = f'Extension ids must be strings. Received: {type(ext_id).__name__}' + logger.error(msg) + raise ValueError(msg) + cleaned = ext_id.strip().lower() + if not cleaned: + continue + if cleaned not in seen: + normalized.append(cleaned) + seen.add(cleaned) + + return tuple(normalized) + + +def get_known_extension_specs() -> dict[str, ExtensionSpec]: + """Return all extension specs known to this installation.""" + from temoa.extensions.discrete_capacity.extension import DISCRETE_CAPACITY_EXTENSION + from temoa.extensions.economies_of_scale.extension import EOS_EXTENSION + from temoa.extensions.growth_rates.extension import GROWTH_RATES_EXTENSION + + specs = [GROWTH_RATES_EXTENSION, DISCRETE_CAPACITY_EXTENSION, EOS_EXTENSION] + return {spec.extension_id: spec for spec in specs} + + +def resolve_extension_specs(extension_ids: Sequence[str] | None) -> tuple[ExtensionSpec, ...]: + """Validate enabled extension ids and return corresponding specs in user order.""" + normalized_ids = normalize_extension_ids(extension_ids) + known = get_known_extension_specs() + unknown = [ext_id for ext_id in normalized_ids if ext_id not in known] + if unknown: + known_ids = ', '.join(sorted(known)) + unknown_ids = ', '.join(sorted(unknown)) + msg = f'Unknown extension id(s): {unknown_ids}. Known extension ids: {known_ids}.' + logger.error(msg) + raise ValueError(msg) + + return tuple(known[ext_id] for ext_id in normalized_ids) + + +def apply_model_extension_hooks(model: TemoaModel, specs: Sequence[ExtensionSpec]) -> None: + """Attach extension-owned model components to a model instance.""" + for spec in specs: + if spec.register_model_components is not None: + spec.register_model_components(model) + + +def append_extension_manifest_items( + model: TemoaModel, manifest: list[LoadItem], specs: Sequence[ExtensionSpec] +) -> list[LoadItem]: + """Append extension-specific manifest items to the base manifest.""" + merged = list(manifest) + for spec in specs: + if spec.build_manifest_items is not None: + merged.extend(spec.build_manifest_items(model)) + return merged + + +def merge_regional_group_tables( + base_tables: Mapping[str, str], specs: Sequence[ExtensionSpec] +) -> dict[str, str]: + """Merge base regional-group table map with extension-contributed entries.""" + merged = dict(base_tables) + for spec in specs: + for table_name, field_name in spec.regional_group_tables.items(): + existing = merged.get(table_name) + if existing is not None and existing != field_name: + msg = ( + f"Regional-group table '{table_name}' has conflicting field mappings: " + f"'{existing}' vs '{field_name}' from extension '{spec.extension_id}'." + ) + logger.error(msg) + raise ValueError(msg) + merged[table_name] = field_name + return merged + + +def assert_disabled_extension_tables_are_empty( + con: Connection, enabled_specs: Sequence[ExtensionSpec] +) -> None: + """Fail if disabled extensions with strict guards own tables populated with data.""" + enabled_ids = {spec.extension_id for spec in enabled_specs} + for spec in get_known_extension_specs().values(): + if spec.extension_id in enabled_ids: + continue + if not spec.fail_if_tables_populated_when_disabled: + continue + + populated: list[str] = [] + for table in spec.owned_tables: + if _table_has_rows(con, table): + populated.append(table) + + if populated: + table_list = ', '.join(sorted(populated)) + msg = ( + f"Extension '{spec.extension_id}' is not enabled, but extension-owned table(s) " + f'contain data: {table_list}. Enable the extension or remove those rows.' + ) + logger.warning(msg) + + +def ensure_enabled_extension_tables_exist( + con: Connection, + enabled_specs: Sequence[ExtensionSpec], + *, + input_database: str, + silent: bool, +) -> None: + """Ensure enabled extensions have required tables, offering to append schema if missing.""" + for spec in enabled_specs: + missing_tables = [table for table in spec.owned_tables if not _table_exists(con, table)] + if not missing_tables: + continue + + missing_list = ', '.join(sorted(missing_tables)) + if not spec.schema_sql_path: + msg = ( + f"Extension '{spec.extension_id}' is enabled, but required table(s) are missing: " + f'{missing_list}. No schema SQL path is registered for this extension.' + ) + logger.error(msg) + raise RuntimeError(msg) + + should_apply = False + if not silent: + prompt = ( + f"Extension '{spec.extension_id}' is enabled but missing table(s): {missing_list}. " + f"Append schema from '{spec.schema_sql_path}' to input database '{input_database} " + 'now? [y/N]: ' + ) + response = input(prompt).strip().lower() + should_apply = response in {'y', 'yes'} + + if not should_apply: + msg = ( + f"Extension '{spec.extension_id}' is enabled, but required table(s) are missing: " + f'{missing_list}. Re-run and accept the prompt, or append schema manually from ' + f"'{spec.schema_sql_path}' to '{input_database}'." + ) + logger.error(msg) + raise RuntimeError(msg) + + _append_extension_schema(con, spec) + still_missing = [table for table in spec.owned_tables if not _table_exists(con, table)] + if still_missing: + still_missing_list = ', '.join(sorted(still_missing)) + msg = ( + f"Schema append for extension '{spec.extension_id}' completed " + f'but table(s) are still missing: {still_missing_list}.' + ) + logger.error(msg) + raise RuntimeError(msg) + + +def _table_has_rows(con: Connection, table_name: str) -> bool: + cur = con.cursor() + table_exists = cur.execute( + "SELECT 1 FROM sqlite_master WHERE type='table' AND name = ?", (table_name,) + ).fetchone() + if not table_exists: + return False + + query = f'SELECT 1 FROM main.{table_name} LIMIT 1' + return cur.execute(query).fetchone() is not None + + +def _table_exists(con: Connection, table_name: str) -> bool: + cur = con.cursor() + exists = cur.execute( + "SELECT 1 FROM sqlite_master WHERE type='table' AND name = ?", (table_name,) + ).fetchone() + return bool(exists) + + +def _append_extension_schema(con: Connection, spec: ExtensionSpec) -> None: + if spec.schema_sql_path is None: + msg = f"Extension '{spec.extension_id}' has no schema SQL path configured." + logger.error(msg) + raise RuntimeError(msg) + + schema_path = Path(spec.schema_sql_path) + if not schema_path.is_file(): + msg = f"Schema SQL file for extension '{spec.extension_id}' not found: {schema_path}" + logger.error(msg) + raise FileNotFoundError(msg) + + sql = schema_path.read_text(encoding='utf-8') + con.executescript(sql) + con.commit() diff --git a/temoa/extensions/get_comm_tech.py b/temoa/extensions/get_comm_tech.py index 8c7167c28..717ee9e56 100644 --- a/temoa/extensions/get_comm_tech.py +++ b/temoa/extensions/get_comm_tech.py @@ -31,9 +31,7 @@ def get_tperiods(inp_f: str) -> dict[str, list[int]]: for row in cur: x.append(row[0]) for y in x: - cur.execute( - 'SELECT DISTINCT period FROM output_flow_out WHERE scenario = ?', (y,) - ) + cur.execute('SELECT DISTINCT period FROM output_flow_out WHERE scenario = ?', (y,)) periods_list[y] = [] for per in cur: z = per[0] diff --git a/temoa/extensions/growth_rates/__init__.py b/temoa/extensions/growth_rates/__init__.py new file mode 100644 index 000000000..7156dc7f8 --- /dev/null +++ b/temoa/extensions/growth_rates/__init__.py @@ -0,0 +1,3 @@ +from temoa.extensions.growth_rates.extension import GROWTH_RATES_EXTENSION + +__all__ = ['GROWTH_RATES_EXTENSION'] diff --git a/temoa/extensions/growth_rates/components/__init__.py b/temoa/extensions/growth_rates/components/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/temoa/extensions/growth_rates/components/growth_capacity.py b/temoa/extensions/growth_rates/components/growth_capacity.py new file mode 100644 index 000000000..434ba90e1 --- /dev/null +++ b/temoa/extensions/growth_rates/components/growth_capacity.py @@ -0,0 +1,126 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pyomo.environ import Constraint, quicksum, value + +import temoa.components.geography as geography +import temoa.components.technology as technology +from temoa.components.utils import Operator, get_adjusted_existing_capacity, operator_expression + +if TYPE_CHECKING: + from temoa.extensions.growth_rates.core.model import GrowthRatesModel + from temoa.types import ExprLike, Period, Region, Technology + + +def limit_growth_capacity_indices( + model: GrowthRatesModel, +) -> set[tuple[Region, Period, Technology, str]]: + return { + (r, p, t, op) + for r, t, op in model.limit_growth_capacity.sparse_keys() + for p in model.time_optimize + } + + +def limit_degrowth_capacity_indices( + model: GrowthRatesModel, +) -> set[tuple[Region, Period, Technology, str]]: + return { + (r, p, t, op) + for r, t, op in model.limit_degrowth_capacity.sparse_keys() + for p in model.time_optimize + } + + +def limit_growth_capacity_constraint_rule( + model: GrowthRatesModel, r: Region, p: Period, t: Technology, op: str +) -> ExprLike: + return limit_growth_capacity(model, r, p, t, op, False) + + +def limit_degrowth_capacity_constraint_rule( + model: GrowthRatesModel, r: Region, p: Period, t: Technology, op: str +) -> ExprLike: + return limit_growth_capacity(model, r, p, t, op, True) + + +def limit_growth_capacity( + model: GrowthRatesModel, + r: Region, + p: Period, + t: Technology, + op: str, + degrowth: bool = False, +) -> ExprLike: + r""" + Constrain the change of capacity available between periods. + Forces the model to ramp up and down the availability of new technologies + more smoothly. Has constant (seed, :math:`S_{r,t}`) and proportional + (rate, :math:`R_{r,t}`) terms. This can be defined for a technology group + instead of one technology, in which case, capacity available is summed over + all technologies in the group. In the first period, previous available + capacity :math:`\mathbf{CAPAVL}_{r,p,t}` is replaced by previous existing + capacity, if any can be found. + + .. math:: + :label: Limit (De)Growth Capacity + + \begin{aligned}\text{Growth:}\\ + &\mathbf{CAPAVL}_{r,p,t} + \quad \le, \ge, \text{or} = \quad + S_{r,t} + (1+R_{r,t}) \cdot \mathbf{CAPAVL}_{r,p_{prev},t} + \end{aligned} + + \qquad \forall \{r, p, t\} \in \Theta_{\text{limit\_growth\_capacity}} + + + \begin{aligned}\text{Degrowth:}\\ + &\mathbf{CAPAVL}_{r,p_{prev},t} + \quad \le, \ge, \text{or} = \quad S_{r,t} + (1+R_{r,t}) \cdot \mathbf{CAPAVL}_{r,p,t} + \end{aligned} + + \qquad \forall \{r, p, t\} \in \Theta_{\text{limit\_degrowth\_capacity}} + """ + + regions = geography.gather_group_regions(model, r) + techs = technology.gather_group_techs(model, t) + + growth = model.limit_degrowth_capacity if degrowth else model.limit_growth_capacity + rate = 1 + value(growth[r, t, op][0]) + seed = value(growth[r, t, op][1]) + cap_rpt = model.v_capacity_available_by_period_and_tech + + cap_indices = {(_r, _p, _t) for _r, _p, _t in cap_rpt.keys() if _t in techs and _r in regions} + periods = sorted({_p for _r, _p, _t in cap_indices}) + + if len(periods) == 0: + return Constraint.Skip + + capacity = quicksum(cap_rpt[_r, _p, _t] for _r, _p, _t in cap_indices if _p == p) + + capacity_prev = 0.0 + + if p == model.time_optimize.first(): + if model.time_exist: + p_prev = model.time_exist.last() + capacity_prev = quicksum( + get_adjusted_existing_capacity(model, _r, _t, _v) + * value(model.process_life_frac[_r, p_prev, _t, _v]) + for _r, _t, _v in model.existing_capacity.sparse_keys() + if _r in regions + and _t in techs + and _v + value(model.lifetime_process[_r, _t, _v]) > p_prev + ) + else: + p_prev = model.time_optimize.prev(p) + capacity_prev = quicksum(cap_rpt[_r, _p, _t] for _r, _p, _t in cap_indices if _p == p_prev) + + if degrowth: + expr = operator_expression(capacity_prev, Operator(op), seed + capacity * rate) + else: + expr = operator_expression(capacity, Operator(op), seed + capacity_prev * rate) + + if isinstance(expr, bool): + return Constraint.Skip + return expr diff --git a/temoa/extensions/growth_rates/components/growth_new_capacity.py b/temoa/extensions/growth_rates/components/growth_new_capacity.py new file mode 100644 index 000000000..51c279fd4 --- /dev/null +++ b/temoa/extensions/growth_rates/components/growth_new_capacity.py @@ -0,0 +1,124 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pyomo.environ import Constraint, quicksum, value + +import temoa.components.geography as geography +import temoa.components.technology as technology +from temoa.components.utils import Operator, operator_expression + +if TYPE_CHECKING: + from temoa.extensions.growth_rates.core.model import GrowthRatesModel + from temoa.types import ExprLike, Period, Region, Technology + + +def limit_growth_new_capacity_indices( + model: GrowthRatesModel, +) -> set[tuple[Region, Period, Technology, str]]: + return { + (r, p, t, op) + for r, t, op in model.limit_growth_new_capacity.sparse_keys() + for p in model.time_optimize + } + + +def limit_degrowth_new_capacity_indices( + model: GrowthRatesModel, +) -> set[tuple[Region, Period, Technology, str]]: + return { + (r, p, t, op) + for r, t, op in model.limit_degrowth_new_capacity.sparse_keys() + for p in model.time_optimize + } + + +def limit_growth_new_capacity_constraint_rule( + model: GrowthRatesModel, r: Region, p: Period, t: Technology, op: str +) -> ExprLike: + return limit_growth_new_capacity(model, r, p, t, op, False) + + +def limit_degrowth_new_capacity_constraint_rule( + model: GrowthRatesModel, r: Region, p: Period, t: Technology, op: str +) -> ExprLike: + return limit_growth_new_capacity(model, r, p, t, op, True) + + +def limit_growth_new_capacity( + model: GrowthRatesModel, + r: Region, + p: Period, + t: Technology, + op: str, + degrowth: bool = False, +) -> ExprLike: + r""" + Constrain the change of new capacity deployed between periods. + Forces the model to ramp up and down the deployment of new technologies + more smoothly. Has constant (seed, :math:`S_{r,t}`) and proportional + (rate, :math:`R_{r,t}`) terms. This can be defined for a technology group + instead of one technology, in which case, new capacity is summed over + all technologies in the group. In the first period, previous new capacity + :math:`\mathbf{NCAP}_{r,t,v_prev}` is replaced by previous existing capacity, + if any can be found. + + .. math:: + :label: Limit (De)Growth New Capacity + + \begin{aligned}\text{Growth:}\\ + &\mathbf{NCAP}_{r,t,v} + \quad \le, \ge, \text{or} = \quad + S_{r,t} + (1+R_{r,t}) \cdot \mathbf{NCAP}_{r,t,v_{prev}} + \text{ where } v=p + \end{aligned} + + \qquad \forall \{r, p, t\} \in \Theta_{\text{limit\_growth\_capacity}} + + \begin{aligned}\text{Degrowth:}\\ + &\mathbf{NCAP}_{r,t,v_{prev}} + \quad \le, \ge, \text{or} = \quad S_{r,t} + (1+R_{r,t}) \cdot \mathbf{NCAP}_{r,t,v} + \text{ where } v=p + \end{aligned} + + \qquad \forall \{r, p, t\} \in \Theta_{\text{limit\_degrowth\_capacity}} + """ + + regions = geography.gather_group_regions(model, r) + techs = technology.gather_group_techs(model, t) + + growth = model.limit_degrowth_new_capacity if degrowth else model.limit_growth_new_capacity + rate = 1 + value(growth[r, t, op][0]) + seed = value(growth[r, t, op][1]) + new_cap_rtv = model.v_new_capacity + + cap_rtv = {(_r, _t, _v) for _r, _t, _v in new_cap_rtv.keys() if _t in techs and _r in regions} + periods = sorted({_v for _r, _t, _v in cap_rtv}) + + if len(periods) == 0: + return Constraint.Skip + + new_cap = quicksum(new_cap_rtv[_r, _t, _v] for _r, _t, _v in cap_rtv if _v == p) + + new_cap_prev = 0.0 + + if p == model.time_optimize.first(): + if model.time_exist: + p_prev = model.time_exist.last() + new_cap_prev = quicksum( + value(model.existing_capacity[_r, _t, _v]) + for _r, _t, _v in model.existing_capacity.sparse_keys() + if _r in regions and _t in techs and _v == p_prev + ) + else: + p_prev = model.time_optimize.prev(p) + new_cap_prev = quicksum(new_cap_rtv[_r, _t, _v] for _r, _t, _v in cap_rtv if _v == p_prev) + + if degrowth: + expr = operator_expression(new_cap_prev, Operator(op), seed + new_cap * rate) + else: + expr = operator_expression(new_cap, Operator(op), seed + new_cap_prev * rate) + + if isinstance(expr, bool): + return Constraint.Skip + return expr diff --git a/temoa/extensions/growth_rates/components/growth_new_capacity_delta.py b/temoa/extensions/growth_rates/components/growth_new_capacity_delta.py new file mode 100644 index 000000000..a158ce6b7 --- /dev/null +++ b/temoa/extensions/growth_rates/components/growth_new_capacity_delta.py @@ -0,0 +1,156 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pyomo.environ import Constraint, quicksum, value + +import temoa.components.geography as geography +import temoa.components.technology as technology +from temoa.components.utils import Operator, operator_expression + +if TYPE_CHECKING: + from temoa.extensions.growth_rates.core.model import GrowthRatesModel + from temoa.types import ExprLike, Period, Region, Technology + + +def limit_growth_new_capacity_delta_indices( + model: GrowthRatesModel, +) -> set[tuple[Region, Period, Technology, str]]: + return { + (r, p, t, op) + for r, t, op in model.limit_growth_new_capacity_delta.sparse_keys() + for p in model.time_optimize + } + + +def limit_degrowth_new_capacity_delta_indices( + model: GrowthRatesModel, +) -> set[tuple[Region, Period, Technology, str]]: + return { + (r, p, t, op) + for r, t, op in model.limit_degrowth_new_capacity_delta.sparse_keys() + for p in model.time_optimize + } + + +def limit_growth_new_capacity_delta_constraint_rule( + model: GrowthRatesModel, r: Region, p: Period, t: Technology, op: str +) -> ExprLike: + return limit_growth_new_capacity_delta(model, r, p, t, op, False) + + +def limit_degrowth_new_capacity_delta_constraint_rule( + model: GrowthRatesModel, r: Region, p: Period, t: Technology, op: str +) -> ExprLike: + return limit_growth_new_capacity_delta(model, r, p, t, op, True) + + +def limit_growth_new_capacity_delta( + model: GrowthRatesModel, + r: Region, + p: Period, + t: Technology, + op: str, + degrowth: bool = False, +) -> ExprLike: + r""" + Constrain the acceleration of new capacity deployed between periods. + Forces the model to ramp up and down the change in deployment of new technologies + more smoothly. Has constant (seed, :math:`S_{r,t}`) and proportional + (rate, :math:`R_{r,t}`) terms. It is recommended to leave the rate term empty + as it would prevent the possibility of inflection in the rate of deployment. + This constraint can be defined for a technology group instead of one technology, + in which case, new capacity is summed over all technologies in the group. In the + first period, previous new capacities are replaced by previous existing capacities, + if any can be found. + + .. math:: + :label: Limit (De)Growth New Capacity Delta + + \begin{aligned}\text{Growth:}\\ + &\mathbf{NCAP}_{r,t,v_i} - \mathbf{NCAP}_{r,t,v_{i-1}} + \quad \le, \ge, \text{or} = \quad S_{r,t} + (1+R_{r,t}) \cdot + (\mathbf{NCAP}_{r,t,v_{i-1}} - \mathbf{NCAP}_{r,t,v_{i-2}}) + \end{aligned} + + \text{ where } v_i=p + + \qquad \forall \{r, p, t\} \in \Theta_{\text{limit\_growth\_capacityDelta}} + + \begin{aligned}\text{Degrowth:}\\ + &\mathbf{NCAP}_{r,t,v_{i-1}} - \mathbf{NCAP}_{r,t,v_{i-2}} + \quad \le, \ge, \text{or} = \quad + S_{r,t} + (1+R_{r,t}) \cdot (\mathbf{NCAP}_{r,t,v_i} - \mathbf{NCAP}_{r,t,v_{i-1}}) + \end{aligned} + + \text{ where } v_i=p + + \qquad \forall \{r, p, t\} \in \Theta_{\text{limit\_degrowth\_capacityDelta}} + """ + + regions = geography.gather_group_regions(model, r) + techs = technology.gather_group_techs(model, t) + + growth = ( + model.limit_degrowth_new_capacity_delta + if degrowth + else model.limit_growth_new_capacity_delta + ) + rate = 1 + value(growth[r, t, op][0]) + seed = value(growth[r, t, op][1]) + new_cap_rtv = model.v_new_capacity + + cap_rtv = {(_r, _t, _v) for _r, _t, _v in new_cap_rtv.keys() if _t in techs and _r in regions} + periods = sorted({_v for _r, _t, _v in cap_rtv}) + + if len(periods) == 0: + return Constraint.Skip + + new_cap = quicksum(new_cap_rtv[_r, _t, _v] for _r, _t, _v in cap_rtv if _v == p) + + new_cap_prev = 0.0 + new_cap_prev2 = 0.0 + + if p == model.time_optimize.first(): + if model.time_exist: + p_prev = model.time_exist.last() + new_cap_prev = quicksum( + value(model.existing_capacity[_r, _t, _v]) + for _r, _t, _v in model.existing_capacity.sparse_keys() + if _r in regions and _t in techs and _v == p_prev + ) + if len(model.time_exist) >= 2: + p_prev2 = model.time_exist.prev(p_prev) + new_cap_prev2 = quicksum( + value(model.existing_capacity[_r, _t, _v]) + for _r, _t, _v in model.existing_capacity.sparse_keys() + if _r in regions and _t in techs and _v == p_prev2 + ) + else: + p_prev = model.time_optimize.prev(p) + new_cap_prev = quicksum(new_cap_rtv[_r, _t, _v] for _r, _t, _v in cap_rtv if _v == p_prev) + if p == model.time_optimize.at(2): + if model.time_exist: + p_prev2 = model.time_exist.last() + new_cap_prev2 = quicksum( + value(model.existing_capacity[_r, _t, _v]) + for _r, _t, _v in model.existing_capacity.sparse_keys() + if _r in regions and _t in techs and _v == p_prev2 + ) + else: + p_prev2 = model.time_optimize.prev(p_prev) + new_cap_prev2 = quicksum( + new_cap_rtv[_r, _t, _v] for _r, _t, _v in cap_rtv if _v == p_prev2 + ) + + nc_delta_prev = new_cap_prev - new_cap_prev2 + nc_delta = new_cap - new_cap_prev + + if degrowth: + expr = operator_expression(nc_delta_prev, Operator(op), seed + nc_delta * rate) + else: + expr = operator_expression(nc_delta, Operator(op), seed + nc_delta_prev * rate) + + if isinstance(expr, bool): + return Constraint.Skip + return expr diff --git a/temoa/extensions/growth_rates/core/__init__.py b/temoa/extensions/growth_rates/core/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/temoa/extensions/growth_rates/core/model.py b/temoa/extensions/growth_rates/core/model.py new file mode 100644 index 000000000..30d846d9f --- /dev/null +++ b/temoa/extensions/growth_rates/core/model.py @@ -0,0 +1,115 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, cast + +from pyomo.environ import Any, Constraint, Param, Set + +from temoa.extensions.growth_rates.components import ( + growth_capacity, + growth_new_capacity, + growth_new_capacity_delta, +) + +if TYPE_CHECKING: + from temoa.core.model import TemoaModel + + class GrowthRatesModel(TemoaModel): + """TemoaModel extended with growth-rates components. + + This subtype exists only for static type checking. At runtime the + growth-rates components are attached to the core ``TemoaModel`` + instance by :func:`register_model_components`. + """ + + # Params + limit_growth_capacity: Param + limit_degrowth_capacity: Param + limit_growth_new_capacity: Param + limit_degrowth_new_capacity: Param + limit_growth_new_capacity_delta: Param + limit_degrowth_new_capacity_delta: Param + + # Constraint index sets + limit_growth_capacity_constraint_rpt: Set + limit_degrowth_capacity_constraint_rpt: Set + limit_growth_new_capacity_constraint_rpt: Set + limit_degrowth_new_capacity_constraint_rpt: Set + limit_growth_new_capacity_delta_constraint_rpt: Set + limit_degrowth_new_capacity_delta_constraint_rpt: Set + + # Constraints + limit_growth_capacity_constraint: Constraint + limit_degrowth_capacity_constraint: Constraint + limit_growth_new_capacity_constraint: Constraint + limit_degrowth_new_capacity_constraint: Constraint + limit_growth_new_capacity_delta_constraint: Constraint + limit_degrowth_new_capacity_delta_constraint: Constraint + + +def register_model_components(model: TemoaModel) -> None: + """Register growth rates model components on the core Temoa model.""" + m = cast('GrowthRatesModel', model) + + m.limit_growth_capacity = Param( + m.regional_global_indices, m.tech_or_group, m.operator, within=Any + ) + m.limit_degrowth_capacity = Param( + m.regional_global_indices, m.tech_or_group, m.operator, within=Any + ) + m.limit_growth_new_capacity = Param( + m.regional_global_indices, m.tech_or_group, m.operator, within=Any + ) + m.limit_degrowth_new_capacity = Param( + m.regional_global_indices, m.tech_or_group, m.operator, within=Any + ) + m.limit_growth_new_capacity_delta = Param( + m.regional_global_indices, m.tech_or_group, m.operator, within=Any + ) + m.limit_degrowth_new_capacity_delta = Param( + m.regional_global_indices, m.tech_or_group, m.operator, within=Any + ) + + m.limit_growth_capacity_constraint_rpt = Set( + dimen=4, initialize=growth_capacity.limit_growth_capacity_indices + ) + m.limit_growth_capacity_constraint = Constraint( + m.limit_growth_capacity_constraint_rpt, + rule=growth_capacity.limit_growth_capacity_constraint_rule, + ) + m.limit_degrowth_capacity_constraint_rpt = Set( + dimen=4, initialize=growth_capacity.limit_degrowth_capacity_indices + ) + m.limit_degrowth_capacity_constraint = Constraint( + m.limit_degrowth_capacity_constraint_rpt, + rule=growth_capacity.limit_degrowth_capacity_constraint_rule, + ) + + m.limit_growth_new_capacity_constraint_rpt = Set( + dimen=4, initialize=growth_new_capacity.limit_growth_new_capacity_indices + ) + m.limit_growth_new_capacity_constraint = Constraint( + m.limit_growth_new_capacity_constraint_rpt, + rule=growth_new_capacity.limit_growth_new_capacity_constraint_rule, + ) + m.limit_degrowth_new_capacity_constraint_rpt = Set( + dimen=4, initialize=growth_new_capacity.limit_degrowth_new_capacity_indices + ) + m.limit_degrowth_new_capacity_constraint = Constraint( + m.limit_degrowth_new_capacity_constraint_rpt, + rule=growth_new_capacity.limit_degrowth_new_capacity_constraint_rule, + ) + + m.limit_growth_new_capacity_delta_constraint_rpt = Set( + dimen=4, initialize=growth_new_capacity_delta.limit_growth_new_capacity_delta_indices + ) + m.limit_growth_new_capacity_delta_constraint = Constraint( + m.limit_growth_new_capacity_delta_constraint_rpt, + rule=growth_new_capacity_delta.limit_growth_new_capacity_delta_constraint_rule, + ) + m.limit_degrowth_new_capacity_delta_constraint_rpt = Set( + dimen=4, initialize=growth_new_capacity_delta.limit_degrowth_new_capacity_delta_indices + ) + m.limit_degrowth_new_capacity_delta_constraint = Constraint( + m.limit_degrowth_new_capacity_delta_constraint_rpt, + rule=growth_new_capacity_delta.limit_degrowth_new_capacity_delta_constraint_rule, + ) diff --git a/temoa/extensions/growth_rates/data_manifest.py b/temoa/extensions/growth_rates/data_manifest.py new file mode 100644 index 000000000..d81b1d355 --- /dev/null +++ b/temoa/extensions/growth_rates/data_manifest.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, cast + +from temoa.data_io.loader_manifest import LoadItem + +if TYPE_CHECKING: + from temoa.core.model import TemoaModel + from temoa.extensions.growth_rates.core.model import GrowthRatesModel + + +def build_manifest_items(model: TemoaModel) -> list[LoadItem]: + """Return LoadItems for growth/degrowth constraints.""" + m = cast('GrowthRatesModel', model) + return [ + LoadItem( + component=m.limit_growth_capacity, + table='limit_growth_capacity', + columns=['region', 'tech_or_group', 'operator', 'rate', 'seed'], + index_length=3, + validator_name='viable_rt', + validation_map=(0, 1), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=m.limit_growth_new_capacity, + table='limit_growth_new_capacity', + columns=['region', 'tech_or_group', 'operator', 'rate', 'seed'], + index_length=3, + validator_name='viable_rt', + validation_map=(0, 1), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=m.limit_growth_new_capacity_delta, + table='limit_growth_new_capacity_delta', + columns=['region', 'tech_or_group', 'operator', 'rate', 'seed'], + index_length=3, + validator_name='viable_rt', + validation_map=(0, 1), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=m.limit_degrowth_capacity, + table='limit_degrowth_capacity', + columns=['region', 'tech_or_group', 'operator', 'rate', 'seed'], + index_length=3, + validator_name='viable_rt', + validation_map=(0, 1), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=m.limit_degrowth_new_capacity, + table='limit_degrowth_new_capacity', + columns=['region', 'tech_or_group', 'operator', 'rate', 'seed'], + index_length=3, + validator_name='viable_rt', + validation_map=(0, 1), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=m.limit_degrowth_new_capacity_delta, + table='limit_degrowth_new_capacity_delta', + columns=['region', 'tech_or_group', 'operator', 'rate', 'seed'], + index_length=3, + validator_name='viable_rt', + validation_map=(0, 1), + is_period_filtered=False, + is_table_required=False, + ), + ] diff --git a/temoa/extensions/growth_rates/extension.py b/temoa/extensions/growth_rates/extension.py new file mode 100644 index 000000000..15847fc0c --- /dev/null +++ b/temoa/extensions/growth_rates/extension.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +from pathlib import Path + +from temoa.extensions.framework import ExtensionSpec +from temoa.extensions.growth_rates.core.model import register_model_components +from temoa.extensions.growth_rates.data_manifest import build_manifest_items + +GROWTH_RATES_EXTENSION = ExtensionSpec( + extension_id='growth_rates', + owned_tables=( + 'limit_growth_capacity', + 'limit_degrowth_capacity', + 'limit_growth_new_capacity', + 'limit_degrowth_new_capacity', + 'limit_growth_new_capacity_delta', + 'limit_degrowth_new_capacity_delta', + ), + regional_group_tables={ + 'limit_growth_capacity': 'region', + 'limit_degrowth_capacity': 'region', + 'limit_growth_new_capacity': 'region', + 'limit_degrowth_new_capacity': 'region', + 'limit_growth_new_capacity_delta': 'region', + 'limit_degrowth_new_capacity_delta': 'region', + }, + register_model_components=register_model_components, + build_manifest_items=build_manifest_items, + schema_sql_path=str(Path(__file__).parent / 'tables.sql'), + fail_if_tables_populated_when_disabled=True, +) diff --git a/temoa/extensions/growth_rates/tables.sql b/temoa/extensions/growth_rates/tables.sql new file mode 100644 index 000000000..e2fece833 --- /dev/null +++ b/temoa/extensions/growth_rates/tables.sql @@ -0,0 +1,72 @@ +CREATE TABLE IF NOT EXISTS limit_growth_capacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_degrowth_capacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_growth_new_capacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_degrowth_new_capacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_growth_new_capacity_delta +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_degrowth_new_capacity_delta +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); diff --git a/temoa/extensions/method_of_morris/morris.py b/temoa/extensions/method_of_morris/morris.py index 77d770f1c..4ae1f308f 100644 --- a/temoa/extensions/method_of_morris/morris.py +++ b/temoa/extensions/method_of_morris/morris.py @@ -1,8 +1,8 @@ from __future__ import annotations +import contextlib import csv import sqlite3 -from importlib import resources from pathlib import Path from typing import Any @@ -21,8 +21,9 @@ seed = 42 -def evaluate(param_names: dict[int, list[Any]], param_values: Any, - data: dict[str, Any], k: int) -> list[Any]: +def evaluate( + param_names: dict[int, list[Any]], param_values: Any, data: dict[str, Any], k: int +) -> list[Any]: m = len(param_values) for j in range(0, m): names = param_names[j] @@ -38,7 +39,7 @@ def evaluate(param_names: dict[int, list[Any]], param_values: Any, raise ValueError(f'Unrecognized parameter: {names[0]}') dp = DataPortal(data_dict={None: data}) - instance = run_actions.build_instance(loaded_portal=dp) + instance = run_actions.build_instance(loaded_portal=dp, extensions=config.extensions) mdl, res = run_actions.solve_instance(instance=instance, solver_name=config.solver_name) status = run_actions.check_solve_status(res) if not status: @@ -46,7 +47,6 @@ def evaluate(param_names: dict[int, list[Any]], param_values: Any, with TableWriter(config) as table_writer: table_writer.write_results(model=mdl) - import contextlib with contextlib.closing(sqlite3.connect(db_file)) as con: cur = con.cursor() cur.execute('SELECT * FROM output_objective') @@ -73,12 +73,11 @@ def evaluate(param_names: dict[int, list[Any]], param_values: Any, if not db_file.exists() or not config_path.exists(): raise FileNotFoundError( - "Example Morris data files not found in current directory. " - "Please see MM_README.md for instructions on how to generate " + 'Example Morris data files not found in current directory. ' + 'Please see MM_README.md for instructions on how to generate ' "or provide the required 'morris_utopia.sqlite' and 'morris_utopia.toml'." ) -import contextlib with contextlib.closing(sqlite3.connect(str(db_file))) as con: with open(param_file, 'w') as file: param_names = {} @@ -152,7 +151,12 @@ def evaluate(param_names: dict[int, list[Any]], param_values: Any, problem = read_param_file(str(param_file), delimiter=' ') param_values = sample( - problem, N=10, num_levels=8, optimal_trajectories=False, local_optimization=False, seed=seed + problem, + N=10, + num_levels=8, + optimal_trajectories=False, + local_optimization=False, + seed=seed, ) print(param_values) print(param_names) diff --git a/temoa/extensions/method_of_morris/morris_evaluate.py b/temoa/extensions/method_of_morris/morris_evaluate.py index 21b03febf..9695b1406 100644 --- a/temoa/extensions/method_of_morris/morris_evaluate.py +++ b/temoa/extensions/method_of_morris/morris_evaluate.py @@ -2,6 +2,7 @@ This module contains the core "evaluation" function for Method Of Morris. It needs to be isolated (outside of class) to enable parallelization. """ + from __future__ import annotations import logging @@ -19,7 +20,6 @@ from temoa.core.config import TemoaConfig - def configure_worker_logger(log_queue: Any, log_level: int) -> logging.Logger: """configure the logger""" worker_logger = logging.getLogger('MM evaluate') @@ -35,8 +35,15 @@ def configure_worker_logger(log_queue: Any, log_level: int) -> logging.Logger: return worker_logger -def evaluate(param_info: dict[int, list[Any]], mm_sample: Any, data: dict[str, Any], - i: int, config: TemoaConfig, log_queue: Any, log_level: int) -> list[float]: +def evaluate( + param_info: dict[int, list[Any]], + mm_sample: Any, + data: dict[str, Any], + i: int, + config: TemoaConfig, + log_queue: Any, + log_level: int, +) -> list[float]: """ Run model for params provided and return objective value and emission value Note: This function needs to be a static instance to enable the parallel @@ -67,7 +74,11 @@ def evaluate(param_info: dict[int, list[Any]], mm_sample: Any, data: dict[str, A logger.debug('\n '.join(log_entry)) dp = DataPortal(data_dict={None: data}) - instance = run_actions.build_instance(loaded_portal=dp, silent=True) + instance = run_actions.build_instance( + loaded_portal=dp, + silent=True, + extensions=config.extensions, + ) mdl, res = run_actions.solve_instance( instance=instance, solver_name=config.solver_name, silent=True ) @@ -77,6 +88,7 @@ def evaluate(param_info: dict[int, list[Any]], mm_sample: Any, data: dict[str, A with TableWriter(config) as table_writer: table_writer.write_mm_results(model=mdl, iteration=i) import contextlib + with contextlib.closing(sqlite3.connect(config.input_database)) as con: cur = con.cursor() scenario_name = config.scenario + f'-{i}' diff --git a/temoa/extensions/method_of_morris/morris_sequencer.py b/temoa/extensions/method_of_morris/morris_sequencer.py index 6ae6bdf35..6c3d1bc76 100644 --- a/temoa/extensions/method_of_morris/morris_sequencer.py +++ b/temoa/extensions/method_of_morris/morris_sequencer.py @@ -2,6 +2,7 @@ An event sequencer to control the flow of a Method of Morris calculation. This code uses multiprocessing via the joblib library """ + from __future__ import annotations import csv @@ -30,7 +31,6 @@ from temoa.core.config import TemoaConfig - logger = logging.getLogger(__name__) solver_options_file = ( @@ -87,7 +87,7 @@ def __init__(self, config: TemoaConfig): # the amount to perturb the marked params pert = morris_inputs.get('perturbation') if pert: - self.mm_perturbation = float(cast(str | float, pert)) + self.mm_perturbation = float(cast('str | float', pert)) logger.info('Morris perturbation: %0.2f', self.mm_perturbation) else: self.mm_perturbation = 0.10 @@ -201,8 +201,9 @@ def start(self) -> Any: # 7. Return the cost objective Mu_Star for testing purposes... return cost_mu_star - def process_results(self, problem: dict[str, Any], mm_samples: Any, - morris_results: list[Any]) -> Any: + def process_results( + self, problem: dict[str, Any], mm_samples: Any, morris_results: list[Any] + ) -> Any: """ Process the results of the runs on the mm_samples :param problem: the problem structure diff --git a/temoa/extensions/modeling_to_generate_alternatives/hull.py b/temoa/extensions/modeling_to_generate_alternatives/hull.py index d0f79c132..f03909ed0 100644 --- a/temoa/extensions/modeling_to_generate_alternatives/hull.py +++ b/temoa/extensions/modeling_to_generate_alternatives/hull.py @@ -1,6 +1,7 @@ """ A thin wrapper on Scipy's ConvexHull to make it more manageable """ + from __future__ import annotations from logging import getLogger @@ -119,8 +120,8 @@ def update(self) -> None: def add_point(self, point: np.ndarray) -> None: if len(point) != self.dim: msg = ( - f'Tried adding a point to hull (dim: {self.dim}) with wrong dimensions {len(point)}. ' - f'Point: {point}' + f'Tried adding a point to hull (dim: {self.dim}) with ' + f'wrong dimensions {len(point)}. Point: {point}' ) logger.error(msg) raise ValueError(msg) diff --git a/temoa/extensions/modeling_to_generate_alternatives/manager_factory.py b/temoa/extensions/modeling_to_generate_alternatives/manager_factory.py index 3970c4e87..16b41cbcb 100644 --- a/temoa/extensions/modeling_to_generate_alternatives/manager_factory.py +++ b/temoa/extensions/modeling_to_generate_alternatives/manager_factory.py @@ -14,8 +14,6 @@ from temoa.extensions.modeling_to_generate_alternatives.vector_manager import VectorManager - - def get_manager( axis: MgaAxis, weighting: MgaWeighting, diff --git a/temoa/extensions/modeling_to_generate_alternatives/mga_sequencer.py b/temoa/extensions/modeling_to_generate_alternatives/mga_sequencer.py index 83b228fec..a261ba458 100644 --- a/temoa/extensions/modeling_to_generate_alternatives/mga_sequencer.py +++ b/temoa/extensions/modeling_to_generate_alternatives/mga_sequencer.py @@ -1,6 +1,7 @@ """ Performs top-level control over an MGA model run """ + from __future__ import annotations import logging @@ -26,10 +27,6 @@ from temoa.extensions.modeling_to_generate_alternatives.vector_manager import VectorManager - - - - import pyomo.environ as pyo from pyomo.opt import check_optimal_termination @@ -124,13 +121,13 @@ def __init__(self, config: TemoaConfig): except KeyError: logger.warning('No/bad MGA Weighting specified. Using default: Hull Expansion') self.mga_weighting = MgaWeighting.HULL_EXPANSION - self.num_workers = int(cast(str | int, all_options.get('num_workers', 1))) + self.num_workers = int(cast('str | int', all_options.get('num_workers', 1))) logger.info('MGA workers are set to %s', self.num_workers) - self.iteration_limit = int(cast(str | int, mga_inputs.get('iteration_limit', 20))) + self.iteration_limit = int(cast('str | int', mga_inputs.get('iteration_limit', 20))) logger.info('Set MGA iteration limit to: %d', self.iteration_limit) - self.time_limit_hrs = float(cast(str | float, mga_inputs.get('time_limit_hrs', 12))) + self.time_limit_hrs = float(cast('str | float', mga_inputs.get('time_limit_hrs', 12))) logger.info('Set MGA time limit hours to: %0.1f', self.time_limit_hrs) - self.cost_epsilon = float(cast(str | float, mga_inputs.get('cost_epsilon', 0.05))) + self.cost_epsilon = float(cast('str | float', mga_inputs.get('cost_epsilon', 0.05))) logger.info('Set MGA cost (relaxation) epsilon to: %0.3f', self.cost_epsilon) # internal records @@ -164,7 +161,10 @@ def start(self) -> None: hybrid_loader = HybridLoader(db_connection=self.con, config=self.config) data_portal: DataPortal = hybrid_loader.load_data_portal(myopic_index=None) instance: TemoaModel = build_instance( - loaded_portal=data_portal, model_name=self.config.scenario, silent=self.config.silent + loaded_portal=data_portal, + model_name=self.config.scenario, + silent=self.config.silent, + extensions=self.config.extensions, ) if self.config.price_check: good_prices = price_checker(instance) @@ -355,8 +355,10 @@ def solve_instance(self, instance: TemoaModel) -> bool: elapsed.total_seconds(), status.name, ) - return status == pyo.TerminationCondition.optimal or \ - str(status) == 'convergenceCriteriaSatisfied' + return ( + status == pyo.TerminationCondition.optimal + or str(status) == 'convergenceCriteriaSatisfied' + ) def process_solve_results(self, instance: TemoaModel) -> None: """write the results as required""" diff --git a/temoa/extensions/modeling_to_generate_alternatives/tech_activity_vector_manager.py b/temoa/extensions/modeling_to_generate_alternatives/tech_activity_vector_manager.py index 62f9a99a4..5f6c19460 100644 --- a/temoa/extensions/modeling_to_generate_alternatives/tech_activity_vector_manager.py +++ b/temoa/extensions/modeling_to_generate_alternatives/tech_activity_vector_manager.py @@ -137,7 +137,9 @@ def initialize(self) -> None: for idx_annual in self.base_model.active_flow_rpitvo or set(): tech = idx_annual[3] self.technology_size[tech] += 1 - self.variable_index_mapping[tech][self.base_model.v_flow_out_annual.name].append(idx_annual) + self.variable_index_mapping[tech][self.base_model.v_flow_out_annual.name].append( + idx_annual + ) logger.debug('Catalogued %d Technology Variables', sum(self.technology_size.values())) @property @@ -364,8 +366,9 @@ def tracker(self) -> None: A little function to track the size of the hull, after it is built initially Note: This hull is a "throw away" and only used for volume calc, but it is pretty quick """ - if self.hull is not None and \ - self.hull_points is not None: # don't try until after first hull is built + if ( + self.hull is not None and self.hull_points is not None + ): # don't try until after first hull is built hull = Hull(self.hull_points) volume = hull.volume logger.info('Tracking hull at %0.2f', volume) diff --git a/temoa/extensions/modeling_to_generate_alternatives/vector_manager.py b/temoa/extensions/modeling_to_generate_alternatives/vector_manager.py index 3b0d1ea27..e3649e768 100644 --- a/temoa/extensions/modeling_to_generate_alternatives/vector_manager.py +++ b/temoa/extensions/modeling_to_generate_alternatives/vector_manager.py @@ -1,6 +1,7 @@ """ An ABC to serve as a framework for future Vector Managers """ + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/temoa/extensions/modeling_to_generate_alternatives/worker.py b/temoa/extensions/modeling_to_generate_alternatives/worker.py index efb1c3ff6..d42dc95a0 100644 --- a/temoa/extensions/modeling_to_generate_alternatives/worker.py +++ b/temoa/extensions/modeling_to_generate_alternatives/worker.py @@ -1,6 +1,7 @@ """ Class to contain Workers that execute solves in separate processes """ + from __future__ import annotations import logging.handlers @@ -52,15 +53,16 @@ def __init__( self.solver_name = solver_name self.solver_options = solver_options or {} self.solver_log_path = solver_log_path - self.opt = None # Initialize in run() + self.opt = None # Initialize in run() self.log_root_name = log_root_name self.log_level = log_level self.solve_count = 0 def run(self) -> None: - logger: logging.Logger = getLogger('.'.join( - (self.log_root_name, 'worker', str(self.worker_number)))) + logger: logging.Logger = getLogger( + '.'.join((self.log_root_name, 'worker', str(self.worker_number))) + ) logger.propagate = False # prevent duplicate logs # add a handler that pushes to the queue handler = logging.handlers.QueueHandler(self.log_queue) @@ -133,7 +135,8 @@ def run(self) -> None: status = solve_res['Solver'].termination_condition logger.info( 'Worker %d did not solve. Results status: %s', - self.worker_number, status + self.worker_number, + status, ) except AttributeError: pass diff --git a/temoa/extensions/monte_carlo/example_builds/scenario_analyzer.py b/temoa/extensions/monte_carlo/example_builds/scenario_analyzer.py index 08b688946..a025b1107 100644 --- a/temoa/extensions/monte_carlo/example_builds/scenario_analyzer.py +++ b/temoa/extensions/monte_carlo/example_builds/scenario_analyzer.py @@ -1,7 +1,7 @@ """ Simple analyzer--example only """ -from importlib import resources + from math import sqrt from pathlib import Path from sqlite3 import Connection @@ -25,15 +25,15 @@ cur = conn.cursor() # Check if results exist before attempting to plot obj_values = cur.execute( - "SELECT total_system_cost FROM output_objective WHERE scenario LIKE ?", - (f"{scenario_name}-%",) + 'SELECT total_system_cost FROM output_objective WHERE scenario LIKE ?', + (f'{scenario_name}-%',), ).fetchall() if len(obj_values) == 0: raise RuntimeError( f"No results found for scenario '{scenario_name}-*' in '{db_resource}'. " "Please run 'temoa run tutorial_config.toml' or run the tutorial model " - "to populate output_objective with results first." + 'to populate output_objective with results first.' ) obj_values_tuple = tuple(t[0] for t in obj_values) diff --git a/temoa/extensions/monte_carlo/example_builds/scenario_maker.py b/temoa/extensions/monte_carlo/example_builds/scenario_maker.py index 0e72dd483..8561f556a 100644 --- a/temoa/extensions/monte_carlo/example_builds/scenario_maker.py +++ b/temoa/extensions/monte_carlo/example_builds/scenario_maker.py @@ -21,10 +21,10 @@ """ from pathlib import Path +from typing import cast import matplotlib.pyplot as plt import numpy as np -from typing import cast # distro for the related cost vars @@ -34,7 +34,7 @@ num_runs = 1000 cov = np.array([[0.4, -0.1], [-0.1, 0.1]]) price_devs = np.random.multivariate_normal([0, 0], cov, size=num_runs) -corr_matrix = cast(np.typing.NDArray[np.float64], np.corrcoef(price_devs.T)) +corr_matrix = cast('np.typing.NDArray[np.float64]', np.corrcoef(price_devs.T)) print(f'correlation check: {corr_matrix[0, 1]}') # verify with a peek... diff --git a/temoa/extensions/monte_carlo/mc_run.py b/temoa/extensions/monte_carlo/mc_run.py index 7c2874a7a..cca3d8b53 100644 --- a/temoa/extensions/monte_carlo/mc_run.py +++ b/temoa/extensions/monte_carlo/mc_run.py @@ -1,6 +1,5 @@ -""" +""" """ -""" from __future__ import annotations from collections import defaultdict, namedtuple @@ -27,8 +26,6 @@ """a record of a data element change, for an element acted on by a Tweak""" - - class Tweak: """ objects of this class represent individual tweaks to single (or wildcard) @@ -235,9 +232,11 @@ def __init__(self, config: TemoaConfig, data_store: dict[str, Any]): "Monte Carlo mode requires 'run_settings' path in 'monte_carlo_inputs'." ) - self.settings_file = Path(cast(str, settings_path)) + self.settings_file = Path(cast('str', settings_path)) if not self.settings_file.exists(): - raise FileNotFoundError(f'Monte Carlo run settings file not found: {self.settings_file}') + raise FileNotFoundError( + f'Monte Carlo run settings file not found: {self.settings_file}' + ) def prescreen_input_file(self) -> bool: """ @@ -330,9 +329,7 @@ def element_locator( ) raw_indices = param_data.keys() matches = [ - k - for k in raw_indices - if all(k[idx] == target_index[idx] for idx in non_wildcard_locs) + k for k in raw_indices if all(k[idx] == target_index[idx] for idx in non_wildcard_locs) ] return matches diff --git a/temoa/extensions/monte_carlo/mc_sequencer.py b/temoa/extensions/monte_carlo/mc_sequencer.py index 0df6fd25e..eb99da89a 100644 --- a/temoa/extensions/monte_carlo/mc_sequencer.py +++ b/temoa/extensions/monte_carlo/mc_sequencer.py @@ -3,6 +3,7 @@ A sequencer for Monte Carlo Runs """ + from __future__ import annotations import logging @@ -31,12 +32,9 @@ from temoa.core.config import TemoaConfig - logger = getLogger(__name__) -solver_options_path = ( - resources.files('temoa.extensions.monte_carlo') / 'MC_solver_options.toml' -) +solver_options_path = resources.files('temoa.extensions.monte_carlo') / 'MC_solver_options.toml' class MCSequencer: @@ -117,7 +115,6 @@ def start(self) -> None: # 4. copy & modify the base data to make per-dataset runs # 5. farm out the runs to workers - # 0. Set up database for scenario self.writer.clear_scenario() self.writer.make_mc_tweaks_table() # add the output table for tweaks, if not exists @@ -125,6 +122,7 @@ def start(self) -> None: # 1. Load data import contextlib + with contextlib.closing(sqlite3.connect(self.config.input_database)) as con: hybrid_loader = HybridLoader(db_connection=con, config=self.config) data_store = hybrid_loader.create_data_dict(myopic_index=None) @@ -138,6 +136,7 @@ def start(self) -> None: # 4. Set up the workers import multiprocessing + ctx = multiprocessing.get_context('spawn') num_workers = self.num_workers @@ -151,10 +150,10 @@ def start(self) -> None: log_queue: Queue[logging.LogRecord] = ctx.Queue() # make workers workers: list[BaseProcess] = [] - kwargs = { - 'solver_name': self.config.solver_name, - 'solver_options': self.worker_solver_options, - } + # kwargs = { + # 'solver_name': self.config.solver_name, + # 'solver_options': self.worker_solver_options, + # } # construct path for the solver logs s_path = self.config.output_path / 'solver_logs' if not s_path.exists(): diff --git a/temoa/extensions/monte_carlo/mc_worker.py b/temoa/extensions/monte_carlo/mc_worker.py index 7bf987ea9..5ab992fa7 100644 --- a/temoa/extensions/monte_carlo/mc_worker.py +++ b/temoa/extensions/monte_carlo/mc_worker.py @@ -9,6 +9,7 @@ new obj functions """ + from __future__ import annotations import logging.handlers @@ -60,7 +61,7 @@ def __init__( self.results_queue = results_queue self.solver_name = solver_name self.solver_options = solver_options - self.opt = None # Initialize in run() + self.opt = None # Initialize in run() self.log_queue = log_queue self.log_level = log_level self.root_logger_name = log_root_name diff --git a/temoa/extensions/myopic/evolution_updater.py b/temoa/extensions/myopic/evolution_updater.py index 20808e79a..efeb9946d 100644 --- a/temoa/extensions/myopic/evolution_updater.py +++ b/temoa/extensions/myopic/evolution_updater.py @@ -1,15 +1,17 @@ -import sqlite3 import logging +import sqlite3 + from temoa.extensions.myopic.myopic_index import MyopicIndex logger = logging.getLogger(__name__) + def iterate( - idx: MyopicIndex | None = None, - prev_base_year: int | None = None, - last_instance_status: str | None = None, - db_con: sqlite3.Connection | None = None, - ) -> None: + idx: MyopicIndex | None = None, + prev_base_year: int | None = None, + last_instance_status: str | None = None, + db_con: sqlite3.Connection | None = None, +) -> None: """ This function is called at the end of each myopic iteration, after the results have been recorded to the myopic database. @@ -29,7 +31,7 @@ def iterate( """ assert idx is not None - logger.info(f"Running myopic iteration updater for base year {idx.base_year}") + logger.info('Running myopic iteration updater for base year %s', idx.base_year) # Update your myopic database here. diff --git a/temoa/extensions/myopic/myopic_progress_mapper.py b/temoa/extensions/myopic/myopic_progress_mapper.py index 3dc3c2955..e474702a5 100644 --- a/temoa/extensions/myopic/myopic_progress_mapper.py +++ b/temoa/extensions/myopic/myopic_progress_mapper.py @@ -3,7 +3,6 @@ """ from datetime import datetime, timedelta - from typing import Literal from temoa.extensions.myopic.myopic_index import MyopicIndex @@ -38,7 +37,7 @@ def draw_header(self) -> None: print(time_buffer, end='') print('*' * tot_length) print() - print(f"{'HH:MM:SS':10s}", end='') + print(f'{"HH:MM:SS":10s}', end='') print(' ', end='') for year in self.years: print(f'{self.leader}{year}{self.trailer} ', end='') @@ -47,8 +46,9 @@ def draw_header(self) -> None: def timestamp(self) -> str: delta = datetime.now() - self.hack return ( - f'Elapsed: {int(delta.total_seconds()//3600):02d}:' - f'{int(delta.total_seconds()%3600//60):02d}:{int(delta.total_seconds())%60:02d} ' + f'Elapsed: {int(delta.total_seconds() // 3600):02d}:' + f'{int(delta.total_seconds() % 3600 // 60):02d}:' + f'{int(delta.total_seconds()) % 60:02d} ' ) def report( diff --git a/temoa/extensions/myopic/myopic_sequencer.py b/temoa/extensions/myopic/myopic_sequencer.py index fbee15c45..e32ed347f 100644 --- a/temoa/extensions/myopic/myopic_sequencer.py +++ b/temoa/extensions/myopic/myopic_sequencer.py @@ -9,7 +9,7 @@ from collections import deque from importlib import resources, util from pathlib import Path -from sqlite3 import Connection, Cursor +from sqlite3 import Connection from typing import Any, cast from temoa._internal import run_actions @@ -87,7 +87,7 @@ def __init__(self, config: TemoaConfig | None) -> None: default_cap_threshold = 1e-3 if config and config.myopic_inputs: self.capacity_epsilon = float( - cast(Any, config.myopic_inputs.get('capacity_threshold', default_cap_threshold)) + cast('Any', config.myopic_inputs.get('capacity_threshold', default_cap_threshold)) ) else: self.capacity_epsilon = default_cap_threshold @@ -110,10 +110,10 @@ def __init__(self, config: TemoaConfig | None) -> None: ) raise RuntimeError('No myopic options received. See log file.') else: - self.view_depth = int(cast(Any, myopic_options.get('view_depth'))) + self.view_depth = int(cast('Any', myopic_options.get('view_depth'))) if not isinstance(self.view_depth, int) or self.view_depth < 1: raise ValueError(f'view_depth is not a positive integer {self.view_depth}') - self.step_size = int(cast(Any, myopic_options.get('step_size'))) + self.step_size = int(cast('Any', myopic_options.get('step_size'))) if not isinstance(self.step_size, int) or self.step_size < 1: raise ValueError(f'step_size is not a positive integer {self.step_size}') if self.step_size > self.view_depth: @@ -224,8 +224,9 @@ def start(self) -> None: raise RuntimeError('Illegal state in myopic iteration.') logger.info('Processing Myopic Index: %s', idx) - # 4. If evolving, call the evolution script and pass it the myopic index and last instance status - if self.evolving and last_instance_status is not None: # don't evolve before first iteration (pointless) + # 4. If evolving, call the evolution script and pass it the myopic index + # and last instance status, unless is first iteration (pointless) + if self.evolving and last_instance_status is not None: self.run_evolution_script( idx=idx, last_base_year=last_base_year, @@ -252,6 +253,7 @@ def start(self) -> None: keep_lp_file=self.config.save_lp_file, lp_path=self.config.output_path / ''.join(('LP', str(idx.base_year))), # base year folder + extensions=self.config.extensions, ) # 8. Run checks... @@ -297,7 +299,7 @@ def start(self) -> None: self.table_writer.write_dual_variables(results=results, iteration=idx.base_year) # prep next loop - last_base_year = idx.base_year if idx else last_base_year # update + last_base_year = idx.base_year if idx else last_base_year # update # delete anything in the output_objective table, it is nonsensical... assert self.output_con is not None @@ -306,13 +308,15 @@ def start(self) -> None: ) self.output_con.commit() - # Total system cost is, theoretically, sum of discounted costs from output_cost table - total_cost = self.get_current_total_cost(last_base_year if last_base_year is not None else 0) + total_cost = self.get_current_total_cost( + last_base_year if last_base_year is not None else 0 + ) assert self.output_con is not None self.output_con.execute( - f"INSERT INTO output_objective(scenario, objective_name, total_system_cost) VALUES('{self.config.scenario}', 'total_cost', {total_cost})" + 'INSERT INTO output_objective(scenario, objective_name, total_system_cost) ' + f"VALUES('{self.config.scenario}', 'total_cost', {total_cost})" ) self.output_con.commit() @@ -384,12 +388,12 @@ def initialize_myopic_efficiency_table(self) -> None: print(list(res)) def run_evolution_script( - self, - idx: MyopicIndex | None, - last_base_year: int | None, - last_instance_status: str | None, - con: sqlite3.Connection | None - ) -> None: + self, + idx: MyopicIndex | None, + last_base_year: int | None, + last_instance_status: str | None, + con: sqlite3.Connection | None, + ) -> None: """ Run the evolution script to update the myopic database before the next iteration. """ @@ -401,21 +405,21 @@ def run_evolution_script( # import the script as a module and call the iterate function script_path = Path(self.evolution_script).expanduser() if not script_path.is_file(): - msg = f"Myopic evolution script not found: {script_path}" + msg = f'Myopic evolution script not found: {script_path}' logger.error(msg) raise FileNotFoundError(msg) - spec = util.spec_from_file_location("evolution_script", script_path) + spec = util.spec_from_file_location('evolution_script', script_path) if spec is None or spec.loader is None: - msg = f"Could not load evolution script module spec from: {script_path}" + msg = f'Could not load evolution script module spec from: {script_path}' logger.error(msg) raise RuntimeError(msg) evolution_module = util.module_from_spec(spec) spec.loader.exec_module(evolution_module) - iterate = getattr(evolution_module, "iterate", None) + iterate = getattr(evolution_module, 'iterate', None) if not callable(iterate): - msg = f"Evolution script must define callable iterate(...): {script_path}" + msg = f'Evolution script must define callable iterate(...): {script_path}' logger.error(msg) raise AttributeError(msg) @@ -570,21 +574,22 @@ def characterize_run(self, future_periods: list[int] | None = None) -> None: raise RuntimeError('view_depth not initialized') if len(future_periods) < self.view_depth + 1: msg = ( - 'Not enough future periods for view depth. Need {} including end period. Got {}.' - ).format(self.view_depth+1, len(future_periods)) + 'Not enough future periods for view depth. ' + f'Need {self.view_depth + 1} including end period. Got {len(future_periods)}.' + ) logger.error(msg) raise RuntimeError(msg) self.optimization_periods = future_periods.copy() if self.step_size is None: raise RuntimeError('step_size not initialized') last_base_year = ((len(future_periods) - 2) // self.step_size) * self.step_size - base_years = list(range(0, last_base_year+1, self.step_size)) + base_years = list(range(0, last_base_year + 1, self.step_size)) if not self.evolving: # Remove redundant iterations near end of horizon if not evolving - catch_Pe = [i for i in base_years if i + self.view_depth >= len(future_periods) - 1] - if len(catch_Pe) > 1: + catch_pe = [i for i in base_years if i + self.view_depth >= len(future_periods) - 1] + if len(catch_pe) > 1: # keep only one iteration that captures the end of the horizon - base_years = base_years[:-len(catch_Pe) + 1] + base_years = base_years[: -len(catch_pe) + 1] for n, idx in enumerate(base_years): depth = min(self.view_depth, len(future_periods) - idx - 1) if idx == base_years[-1]: @@ -592,13 +597,13 @@ def characterize_run(self, future_periods: list[int] | None = None) -> None: step = depth else: # record to next base year - step = base_years[n+1] - idx + step = base_years[n + 1] - idx if depth < 1: msg = ( 'Calculated MyopicIndex with non-positive depth. ' 'This should never happen. Code error likely. ' - 'idx: {}, step: {}, depth: {}, future_periods: {}' - ).format(idx, step, depth, future_periods) + f'idx: {idx}, step: {step}, depth: {depth}, future_periods: {future_periods}' + ) logger.error(msg) raise RuntimeError(msg) myopic_idx = MyopicIndex( @@ -707,9 +712,7 @@ def clear_results_after(self, period: int) -> None: def report_total_demand(self, mi: MyopicIndex) -> None: assert self.output_con is not None assert self.cursor is not None - self.cursor.execute( - "SELECT SUM(demand) FROM output_demand WHERE scenario='original'" - ) + self.cursor.execute("SELECT SUM(demand) FROM output_demand WHERE scenario='original'") self.output_con.commit() def write_myopic_efficiency(self, mi: MyopicIndex, status: str) -> None: @@ -736,9 +739,7 @@ def write_myopic_efficiency(self, mi: MyopicIndex, status: str) -> None: def report_cumulative_capacity(self, mi: MyopicIndex) -> None: assert self.output_con is not None assert self.cursor is not None - self.cursor.execute( - "SELECT SUM(capacity) FROM output_capacity WHERE scenario='original'" - ) + self.cursor.execute("SELECT SUM(capacity) FROM output_capacity WHERE scenario='original'") self.output_con.commit() def __del__(self) -> None: diff --git a/temoa/extensions/single_vector_mga/output_summary.py b/temoa/extensions/single_vector_mga/output_summary.py index 54e387b15..80369ae7b 100644 --- a/temoa/extensions/single_vector_mga/output_summary.py +++ b/temoa/extensions/single_vector_mga/output_summary.py @@ -1,6 +1,7 @@ """ A tabular summation of the results from an SVMGA run """ + from __future__ import annotations import sqlite3 diff --git a/temoa/extensions/single_vector_mga/sv_mga_sequencer.py b/temoa/extensions/single_vector_mga/sv_mga_sequencer.py index 8498390bf..543af6678 100644 --- a/temoa/extensions/single_vector_mga/sv_mga_sequencer.py +++ b/temoa/extensions/single_vector_mga/sv_mga_sequencer.py @@ -9,7 +9,7 @@ import sqlite3 import sys from collections.abc import Iterable -from typing import Any, cast, TYPE_CHECKING +from typing import TYPE_CHECKING, Any, cast from pyomo.core import Constraint, Expression, Objective, value from pyomo.opt import check_optimal_termination @@ -79,6 +79,7 @@ def start(self) -> None: silent=self.config.silent, keep_lp_file=self.config.save_lp_file, lp_path=lp_path, + extensions=self.config.extensions, ) if self.config.price_check: good_prices = price_checker(instance) diff --git a/temoa/extensions/stochastics/scenario_creator.py b/temoa/extensions/stochastics/scenario_creator.py index 757fa2a70..ac54bcb77 100644 --- a/temoa/extensions/stochastics/scenario_creator.py +++ b/temoa/extensions/stochastics/scenario_creator.py @@ -3,7 +3,6 @@ import logging import sqlite3 from typing import TYPE_CHECKING, Any, cast -from collections.abc import Iterable from mpisppy.utils.sputils import attach_root_node # type: ignore[import-untyped] @@ -12,6 +11,8 @@ from temoa.data_io.hybrid_loader import HybridLoader if TYPE_CHECKING: + from collections.abc import Iterable + from temoa.core.config import TemoaConfig from temoa.data_io.hybrid_loader import LoadItem from temoa.extensions.stochastics.stochastic_config import StochasticConfig @@ -36,6 +37,7 @@ def scenario_creator(scenario_name: str, **kwargs: Any) -> Any: # 1. Load base data try: import contextlib + with contextlib.closing(sqlite3.connect(temoa_config.input_database)) as con: hybrid_loader = HybridLoader(db_connection=con, config=temoa_config) data_dict = hybrid_loader.create_data_dict(myopic_index=None) @@ -45,7 +47,10 @@ def scenario_creator(scenario_name: str, **kwargs: Any) -> Any: table_index_map: dict[str, list[str]] = {} for item in cast('Iterable[LoadItem]', hybrid_loader.manifest): if item.table not in table_index_map and item.columns: - table_index_map[item.table] = list(item.columns[:-1]) + if item.index_length: + table_index_map[item.table] = list(item.columns[: item.index_length]) + else: + table_index_map[item.table] = list(item.columns[:-1]) except Exception as e: logger.exception('Failed to connect to database %s', temoa_config.input_database) raise RuntimeError(f'Failed to connect to database {temoa_config.input_database}') from e @@ -55,7 +60,7 @@ def scenario_creator(scenario_name: str, **kwargs: Any) -> Any: if p.scenario != scenario_name: continue - target_param = cast(dict[Any, Any] | None, data_dict.get(p.table)) + target_param = cast('dict[Any, Any] | None', data_dict.get(p.table)) if target_param is None: logger.warning( 'Table %s not found in data_dict for scenario %s', p.table, scenario_name @@ -96,7 +101,7 @@ def scenario_creator(scenario_name: str, **kwargs: Any) -> Any: # 3. Build instance data_portal = HybridLoader.data_portal_from_data(data_dict) - instance = build_instance(data_portal, silent=True) + instance = build_instance(data_portal, silent=True, extensions=temoa_config.extensions) # 4. Attach root node (Stage 1) periods = sorted(instance.time_optimize) diff --git a/temoa/extensions/stochastics/stochastic_config.py b/temoa/extensions/stochastics/stochastic_config.py index a46df1e32..3164be085 100644 --- a/temoa/extensions/stochastics/stochastic_config.py +++ b/temoa/extensions/stochastics/stochastic_config.py @@ -33,7 +33,6 @@ class StochasticConfig: perturbations: list[Perturbation] = field(default_factory=list) solver_options: dict[str, Any] = field(default_factory=dict) - @classmethod def from_toml(cls, path: Path) -> Self: with open(path, 'rb') as f: diff --git a/temoa/extensions/stochastics/stochastic_sequencer.py b/temoa/extensions/stochastics/stochastic_sequencer.py index 9950ee6cc..5c904ce30 100644 --- a/temoa/extensions/stochastics/stochastic_sequencer.py +++ b/temoa/extensions/stochastics/stochastic_sequencer.py @@ -34,7 +34,9 @@ def __init__(self, config: TemoaConfig) -> None: self.stoch_config = StochasticConfig.from_toml(sc_path) except Exception as e: logger.exception('Failed to load stochastic config from %s', sc_path) - raise ValueError(f'Error parsing stochastic config {sc_path}. Original error: {e}') from e + raise ValueError( + f'Error parsing stochastic config {sc_path}. Original error: {e}' + ) from e def start(self) -> None: """ diff --git a/temoa/extensions/template/__init__.py b/temoa/extensions/template/__init__.py new file mode 100644 index 000000000..fb2119770 --- /dev/null +++ b/temoa/extensions/template/__init__.py @@ -0,0 +1,21 @@ +"""Template extension package. + +TEMPLATE: This is a copy-from scaffold for building a new optional Temoa +modeling extension. It is intentionally NOT registered in +``temoa.extensions.framework.get_known_extension_specs``, so it is inert: +importing it works and it type-checks, but it cannot be enabled until you +register it. + +To create a real extension: + 1. Copy this whole folder to ``temoa/extensions//``. + 2. Rename the ``extension_id``, tables, params, and constraints. + 3. Register the spec in ``get_known_extension_specs`` (see framework.py). + 4. Add your tables to the database schema / ``tables.sql``. + 5. Enable it via config: ``extensions = [""]``. + +See ``docs/source/extensions.rst`` for the full guide. +""" + +from temoa.extensions.template.extension import EXAMPLE_EXTENSION + +__all__ = ['EXAMPLE_EXTENSION'] diff --git a/temoa/extensions/template/components/__init__.py b/temoa/extensions/template/components/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/temoa/extensions/template/components/example_limit.py b/temoa/extensions/template/components/example_limit.py new file mode 100644 index 000000000..a00570bce --- /dev/null +++ b/temoa/extensions/template/components/example_limit.py @@ -0,0 +1,62 @@ +"""Example constraint family for the template extension. + +TEMPLATE: A component module holds the index-set function(s) and constraint +rule(s) for a single family of constraints, mirroring the modules under +``temoa/components``. Group related constraints together; create one module per +family. + +This example caps the new capacity built in each period for a technology (or +technology group) in a region. Replace it with your own logic. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pyomo.environ import Constraint, quicksum, value + +import temoa.components.geography as geography +import temoa.components.technology as technology + +if TYPE_CHECKING: + from temoa.extensions.template.core.model import ExampleModel + from temoa.types import ExprLike, Period, Region, Technology + + +def example_new_capacity_limit_indices( + model: ExampleModel, +) -> set[tuple[Region, Period, Technology]]: + """Build the sparse (region, period, tech-or-group) index for the constraint. + + TEMPLATE: Annotate ``model`` with the extension subtype (``ExampleModel``) so + the extension-owned param ``example_new_capacity_limit`` and its + ``sparse_keys()`` method are visible to the type checker. + """ + return { + (r, p, t) + for r, t in model.example_new_capacity_limit.sparse_keys() + for p in model.time_optimize + } + + +def example_new_capacity_limit_constraint_rule( + model: ExampleModel, r: Region, p: Period, t: Technology +) -> ExprLike: + """Cap total new capacity built in period ``p`` for region/group ``r``/``t``.""" + regions = geography.gather_group_regions(model, r) + techs = technology.gather_group_techs(model, t) + + limit = value(model.example_new_capacity_limit[r, t]) + + new_cap_rtv = model.v_new_capacity + new_cap = quicksum( + new_cap_rtv[_r, _t, _v] + for _r, _t, _v in new_cap_rtv.keys() + if _r in regions and _t in techs and _v == p + ) + + if isinstance(new_cap, (int, float)): + # No decision variables in this period/region/group: nothing to constrain. + return Constraint.Skip + + return new_cap <= limit diff --git a/temoa/extensions/template/core/__init__.py b/temoa/extensions/template/core/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/temoa/extensions/template/core/model.py b/temoa/extensions/template/core/model.py new file mode 100644 index 000000000..2e3e5a121 --- /dev/null +++ b/temoa/extensions/template/core/model.py @@ -0,0 +1,69 @@ +"""Type-checking model subtype and component registration for the template extension. + +TEMPLATE: This file plays the same role as ``temoa/core/model.py`` does for the +core model: it declares the extension-owned model components (so the type +checker knows about them) and attaches them to a live ``TemoaModel`` instance. + +Two things live here: + 1. ``ExampleModel`` - a ``TYPE_CHECKING``-only subtype of ``TemoaModel`` that + declares every Param/Set/Constraint this extension adds. It exists purely + for static typing; nothing instantiates it. + 2. ``register_model_components`` - the runtime hook that attaches those + components to the core model. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, cast + +from pyomo.environ import Any, Constraint, Param, Set + +from temoa.extensions.template.components import example_limit + +if TYPE_CHECKING: + from temoa.core.model import TemoaModel + + class ExampleModel(TemoaModel): + """TemoaModel extended with template-extension components. + + TEMPLATE: Declare one annotation per component you create in + ``register_model_components`` below. Inheriting from ``TemoaModel`` means + all core sets/params/vars (e.g. ``time_optimize``, ``v_new_capacity``) + are already visible to the type checker here and in your component + modules. + """ + + # Params (loaded from the database via data_manifest.py) + example_new_capacity_limit: Param + + # Constraint index sets + example_new_capacity_limit_constraint_rpt: Set + + # Constraints + example_new_capacity_limit_constraint: Constraint + + +def register_model_components(model: TemoaModel) -> None: + """Attach template-extension components to the core Temoa model. + + TEMPLATE: Keep the public signature as ``model: TemoaModel`` so this stays + assignable to ``ExtensionSpec.register_model_components`` (a + ``Callable[[TemoaModel], None]``). Narrowing the parameter to ``ExampleModel`` + would be a contravariance error in mypy. Instead, ``cast`` once and operate on + the typed alias ``m`` below. + """ + m = cast('ExampleModel', model) + + # Param: a per-(region, tech-or-group) cap on cumulative new capacity. + m.example_new_capacity_limit = Param(m.regional_global_indices, m.tech_or_group, within=Any) + + # Sparse index set for the constraint, built from the param's populated keys. + m.example_new_capacity_limit_constraint_rpt = Set( + dimen=3, initialize=example_limit.example_new_capacity_limit_indices + ) + + # The constraint itself. + m.example_new_capacity_limit_constraint = Constraint( + m.example_new_capacity_limit_constraint_rpt, + rule=example_limit.example_new_capacity_limit_constraint_rule, + ) diff --git a/temoa/extensions/template/data_manifest.py b/temoa/extensions/template/data_manifest.py new file mode 100644 index 000000000..e9c8ac02a --- /dev/null +++ b/temoa/extensions/template/data_manifest.py @@ -0,0 +1,44 @@ +"""Data-loading manifest for the template extension. + +TEMPLATE: ``build_manifest_items`` returns one ``LoadItem`` per database table +this extension reads. The loader uses each item to query, validate, and populate +the matching Pyomo component declared in core/model.py. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, cast + +from temoa.data_io.loader_manifest import LoadItem + +if TYPE_CHECKING: + from temoa.core.model import TemoaModel + from temoa.extensions.template.core.model import ExampleModel + + +def build_manifest_items(model: TemoaModel) -> list[LoadItem]: + """Return the LoadItems for the template extension. + + TEMPLATE: As with ``register_model_components``, keep the public parameter + typed as ``TemoaModel`` (to match ``ExtensionSpec.build_manifest_items``) and + ``cast`` to the extension subtype so the extension-owned components type-check. + """ + m = cast('ExampleModel', model) + return [ + LoadItem( + # The Pyomo component to populate. + component=m.example_new_capacity_limit, + # Source table name. + table='example_new_capacity_limit', + # Columns to select; for a Param the final column is the value. + columns=['region', 'tech_or_group', 'value'], + # Number of leading columns that form the index (here: region, tech). + index_length=2, + # Source-trace validator on the loader (filters to viable r/t pairs). + validator_name='viable_rt', + # Which index columns to validate (region=0, tech=1). + validation_map=(0, 1), + # Extension tables are optional inputs, so do not require the table. + is_table_required=False, + ), + ] diff --git a/temoa/extensions/template/extension.py b/temoa/extensions/template/extension.py new file mode 100644 index 000000000..9c8509d85 --- /dev/null +++ b/temoa/extensions/template/extension.py @@ -0,0 +1,34 @@ +"""Declarative spec that wires the template extension into Temoa. + +TEMPLATE: ``ExtensionSpec`` is pure metadata plus hook functions. Temoa reads it +to know which tables the extension owns, how to guard them, which model +components to attach, and how to load the extension's data. +""" + +from __future__ import annotations + +from pathlib import Path + +from temoa.extensions.framework import ExtensionSpec +from temoa.extensions.template.core.model import register_model_components +from temoa.extensions.template.data_manifest import build_manifest_items + +EXAMPLE_EXTENSION = ExtensionSpec( + # Unique, lowercase id. This is what users put in ``extensions = [...]``. + extension_id='template', + # Database tables owned exclusively by this extension. + owned_tables=('example_new_capacity_limit',), + # Tables whose ``region`` column may contain a regional group name. Maps + # table -> the column that holds the region/group. Merged into the loader's + # regional-group handling. + regional_group_tables={'example_new_capacity_limit': 'region'}, + # Hook: attach model components to the core model (see core/model.py). + register_model_components=register_model_components, + # Hook: describe how to load this extension's data (see data_manifest.py). + build_manifest_items=build_manifest_items, + # Schema applied (with consent) when enabled but tables are missing. + schema_sql_path=str(Path(__file__).parents[0] / 'tables.sql'), + # If True, loading fails when this extension is DISABLED but its tables hold + # data, preventing silently-ignored inputs. + fail_if_tables_populated_when_disabled=True, +) diff --git a/temoa/extensions/template/tables.sql b/temoa/extensions/template/tables.sql new file mode 100644 index 000000000..bc8e3a75d --- /dev/null +++ b/temoa/extensions/template/tables.sql @@ -0,0 +1,14 @@ +-- TEMPLATE: Schema for the template extension's owned tables. +-- One CREATE TABLE per entry in ``owned_tables`` (see extension.py). Use +-- ``IF NOT EXISTS`` so the schema can be appended to an existing database when +-- the extension is enabled but its tables are missing. + +CREATE TABLE IF NOT EXISTS example_new_capacity_limit +( + region TEXT, + tech_or_group TEXT, + value REAL NOT NULL DEFAULT 0, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group) +); diff --git a/temoa/tutorial_assets/config_sample.toml b/temoa/tutorial_assets/config_sample.toml index 6b522212d..df3d7b20a 100644 --- a/temoa/tutorial_assets/config_sample.toml +++ b/temoa/tutorial_assets/config_sample.toml @@ -18,6 +18,12 @@ scenario = "zulu" # [perfect_foresight, MGA, myopic, method_of_morris, build_only, check, monte_carlo] scenario_mode = "perfect_foresight" +# Optional extensions list +# Use this to enable modular model features that are not part of the base core model. +# Example: extensions = ["unit_commitment", "growth_rates"] +# Leave empty for default core-only behavior. +extensions = [] + # Input database (Mandatory) input_database = "utopia.sqlite" diff --git a/temoa/tutorial_assets/utopia.sql b/temoa/tutorial_assets/utopia.sql index a58fc9910..1814f34f9 100644 --- a/temoa/tutorial_assets/utopia.sql +++ b/temoa/tutorial_assets/utopia.sql @@ -1,1478 +1,449 @@ -BEGIN TRANSACTION; -CREATE TABLE capacity_credit -( - region TEXT, - period INTEGER - REFERENCES time_period (period), - tech TEXT - REFERENCES technology (tech), - vintage INTEGER, - credit REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage), - CHECK (credit >= 0 AND credit <= 1) -); -CREATE TABLE capacity_factor_process -( - region TEXT, - season TEXT - REFERENCES time_season (season), - tod TEXT - REFERENCES time_of_day (tod), - tech TEXT - REFERENCES technology (tech), - vintage INTEGER, - factor REAL, - notes TEXT, - PRIMARY KEY (region, season, tod, tech, vintage), - CHECK (factor >= 0 AND factor <= 1) -); -INSERT INTO "capacity_factor_process" VALUES('utopia','inter','day','E31',2000,0.2753,''); -INSERT INTO "capacity_factor_process" VALUES('utopia','inter','night','E31',2000,0.2753,''); -INSERT INTO "capacity_factor_process" VALUES('utopia','winter','day','E31',2000,0.2753,''); -INSERT INTO "capacity_factor_process" VALUES('utopia','winter','night','E31',2000,0.2753,''); -INSERT INTO "capacity_factor_process" VALUES('utopia','summer','day','E31',2000,0.2753,''); -INSERT INTO "capacity_factor_process" VALUES('utopia','summer','night','E31',2000,0.2753,''); -INSERT INTO "capacity_factor_process" VALUES('utopia','inter','day','E31',2010,0.2756,''); -INSERT INTO "capacity_factor_process" VALUES('utopia','inter','night','E31',2010,0.2756,''); -INSERT INTO "capacity_factor_process" VALUES('utopia','winter','day','E31',2010,0.2756,''); -INSERT INTO "capacity_factor_process" VALUES('utopia','winter','night','E31',2010,0.2756,''); -INSERT INTO "capacity_factor_process" VALUES('utopia','summer','day','E31',2010,0.2756,''); -INSERT INTO "capacity_factor_process" VALUES('utopia','summer','night','E31',2010,0.2756,''); -CREATE TABLE capacity_factor_tech -( - region TEXT, - season TEXT - REFERENCES time_season (season), - tod TEXT - REFERENCES time_of_day (tod), - tech TEXT - REFERENCES technology (tech), - factor REAL, - notes TEXT, - PRIMARY KEY (region, season, tod, tech), - CHECK (factor >= 0 AND factor <= 1) -); -INSERT INTO "capacity_factor_tech" VALUES('utopia','inter','day','E01',0.8,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','inter','night','E01',0.8,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','winter','day','E01',0.8,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','winter','night','E01',0.8,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','summer','day','E01',0.8,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','summer','night','E01',0.8,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','inter','day','E21',0.8,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','inter','night','E21',0.8,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','winter','day','E21',0.8,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','winter','night','E21',0.8,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','summer','day','E21',0.8,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','summer','night','E21',0.8,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','inter','day','E31',0.275,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','inter','night','E31',0.275,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','winter','day','E31',0.275,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','winter','night','E31',0.275,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','summer','day','E31',0.275,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','summer','night','E31',0.275,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','inter','day','E51',0.17,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','inter','night','E51',0.17,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','winter','day','E51',0.17,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','winter','night','E51',0.17,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','summer','day','E51',0.17,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','summer','night','E51',0.17,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','inter','day','E70',0.8,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','inter','night','E70',0.8,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','winter','day','E70',0.8,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','winter','night','E70',0.8,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','summer','day','E70',0.8,''); -INSERT INTO "capacity_factor_tech" VALUES('utopia','summer','night','E70',0.8,''); -CREATE TABLE capacity_to_activity -( - region TEXT, - tech TEXT - REFERENCES technology (tech), - c2a REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO "capacity_to_activity" VALUES('utopia','E01',31.54,'PJ / (GW * year)',''); -INSERT INTO "capacity_to_activity" VALUES('utopia','E21',31.54,'PJ / (GW * year)',''); -INSERT INTO "capacity_to_activity" VALUES('utopia','E31',31.54,'PJ / (GW * year)',''); -INSERT INTO "capacity_to_activity" VALUES('utopia','E51',31.54,'PJ / (GW * year)',''); -INSERT INTO "capacity_to_activity" VALUES('utopia','E70',31.54,'PJ / (GW * year)',''); -INSERT INTO "capacity_to_activity" VALUES('utopia','RHE',1.0,'PJ / (GW * year)',''); -INSERT INTO "capacity_to_activity" VALUES('utopia','RHO',1.0,'PJ / (GW * year)',''); -INSERT INTO "capacity_to_activity" VALUES('utopia','RL1',1.0,'PJ / (GW * year)',''); -INSERT INTO "capacity_to_activity" VALUES('utopia','SRE',1.0,'PJ / (GW * year)',''); -INSERT INTO "capacity_to_activity" VALUES('utopia','TXD',1.0,'PJ / (GW * year)',''); -INSERT INTO "capacity_to_activity" VALUES('utopia','TXE',1.0,'PJ / (GW * year)',''); -INSERT INTO "capacity_to_activity" VALUES('utopia','TXG',1.0,'PJ / (GW * year)',''); -CREATE TABLE commodity -( - name TEXT - PRIMARY KEY, - flag TEXT - REFERENCES commodity_type (label), - description TEXT, - units TEXT -); -INSERT INTO "commodity" VALUES('ethos','s','# dummy commodity to supply inputs','PJ'); -INSERT INTO "commodity" VALUES('DSL','p','# diesel','PJ'); -INSERT INTO "commodity" VALUES('ELC','p','# electricity','PJ'); -INSERT INTO "commodity" VALUES('FEQ','p','# fossil equivalent','PJ'); -INSERT INTO "commodity" VALUES('GSL','p','# gasoline','PJ'); -INSERT INTO "commodity" VALUES('HCO','p','# coal','PJ'); -INSERT INTO "commodity" VALUES('HYD','p','# water','PJ'); -INSERT INTO "commodity" VALUES('OIL','p','# crude oil','PJ'); -INSERT INTO "commodity" VALUES('URN','p','# uranium','PJ'); -INSERT INTO "commodity" VALUES('co2','e','#CO2 emissions','Mt'); -INSERT INTO "commodity" VALUES('nox','e','#NOX emissions','Mt'); -INSERT INTO "commodity" VALUES('RH','d','# residential heating','PJ'); -INSERT INTO "commodity" VALUES('RL','d','# residential lighting','PJ'); -INSERT INTO "commodity" VALUES('TX','d','# transportation','PJ'); -CREATE TABLE commodity_type -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO "commodity_type" VALUES('w','waste commodity'); -INSERT INTO "commodity_type" VALUES('wa','waste annual commodity'); -INSERT INTO "commodity_type" VALUES('wp','waste physical commodity'); -INSERT INTO "commodity_type" VALUES('a','annual commodity'); -INSERT INTO "commodity_type" VALUES('s','source commodity'); -INSERT INTO "commodity_type" VALUES('p','physical commodity'); -INSERT INTO "commodity_type" VALUES('e','emissions commodity'); -INSERT INTO "commodity_type" VALUES('d','demand commodity'); -CREATE TABLE construction_input -( - region TEXT, - input_comm TEXT - REFERENCES commodity (name), - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - value REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, input_comm, tech, vintage) -); -CREATE TABLE cost_emission -( - region TEXT, - period INTEGER - REFERENCES time_period (period), - emis_comm TEXT NOT NULL - REFERENCES commodity (name), - cost REAL NOT NULL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, emis_comm) -); -CREATE TABLE cost_fixed -( - region TEXT NOT NULL, - period INTEGER NOT NULL - REFERENCES time_period (period), - tech TEXT NOT NULL - REFERENCES technology (tech), - vintage INTEGER NOT NULL - REFERENCES time_period (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage) -); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'E01',1960,40.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'E01',1970,40.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'E01',1980,40.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'E01',1990,40.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'E01',1970,70.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'E01',1980,70.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'E01',1990,70.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'E01',2000,70.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'E01',1980,100.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'E01',1990,100.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'E01',2000,100.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'E01',2010,100.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'E21',1990,500.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'E21',1990,500.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'E21',1990,500.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'E21',2000,500.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'E21',2000,500.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'E21',2010,500.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'E31',1980,75.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'E31',1990,75.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'E31',1980,75.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'E31',1990,75.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'E31',2000,75.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'E31',1980,75.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'E31',1990,75.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'E31',2000,75.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'E31',2010,75.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'E51',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'E51',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'E51',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'E51',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'E51',2000,30.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'E51',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'E51',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'E51',2000,30.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'E51',2010,30.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'E70',1960,30.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'E70',1970,30.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'E70',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'E70',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'E70',1970,30.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'E70',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'E70',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'E70',2000,30.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'E70',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'E70',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'E70',2000,30.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'E70',2010,30.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'RHO',1970,1.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'RHO',1980,1.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'RHO',1990,1.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'RHO',1980,1.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'RHO',1990,1.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'RHO',2000,1.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'RHO',1990,1.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'RHO',2000,1.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'RHO',2010,1.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'RL1',1980,9.46,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'RL1',1990,9.46,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'RL1',2000,9.46,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'RL1',2010,9.46,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'TXD',1970,52.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'TXD',1980,52.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'TXD',1990,52.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'TXD',1980,52.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'TXD',1990,52.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'TXD',2000,52.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'TXD',2000,52.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'TXD',2010,52.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'TXE',1990,100.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'TXE',1990,90.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'TXE',2000,90.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'TXE',2000,80.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'TXE',2010,80.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'TXG',1970,48.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'TXG',1980,48.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',1990,'TXG',1990,48.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'TXG',1980,48.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'TXG',1990,48.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2000,'TXG',2000,48.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'TXG',2000,48.0,'Mdollar / (PJ^2 / GW / year)',''); -INSERT INTO "cost_fixed" VALUES('utopia',2010,'TXG',2010,48.0,'Mdollar / (PJ^2 / GW / year)',''); -CREATE TABLE cost_invest -( - region TEXT, - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -INSERT INTO "cost_invest" VALUES('utopia','E01',1990,2000.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','E01',2000,1300.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','E01',2010,1200.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','E21',1990,5000.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','E21',2000,5000.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','E21',2010,5000.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','E31',1990,3000.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','E31',2000,3000.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','E31',2010,3000.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','E51',1990,900.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','E51',2000,900.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','E51',2010,900.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','E70',1990,1000.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','E70',2000,1000.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','E70',2010,1000.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','RHE',1990,90.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','RHE',2000,90.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','RHE',2010,90.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','RHO',1990,100.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','RHO',2000,100.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','RHO',2010,100.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','SRE',1990,100.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','SRE',2000,100.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','SRE',2010,100.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','TXD',1990,1044.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','TXD',2000,1044.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','TXD',2010,1044.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','TXE',1990,2000.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','TXE',2000,1750.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','TXE',2010,1500.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','TXG',1990,1044.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','TXG',2000,1044.0,'Mdollar / (PJ^2 / GW)',''); -INSERT INTO "cost_invest" VALUES('utopia','TXG',2010,1044.0,'Mdollar / (PJ^2 / GW)',''); -CREATE TABLE cost_variable -( - region TEXT NOT NULL, - period INTEGER NOT NULL - REFERENCES time_period (period), - tech TEXT NOT NULL - REFERENCES technology (tech), - vintage INTEGER NOT NULL - REFERENCES time_period (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage) -); -INSERT INTO "cost_variable" VALUES('utopia',1990,'IMPDSL1',1990,10.0,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2000,'IMPDSL1',1990,10.0,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2010,'IMPDSL1',1990,10.0,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',1990,'IMPGSL1',1990,15.0,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2000,'IMPGSL1',1990,15.0,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2010,'IMPGSL1',1990,15.0,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',1990,'IMPHCO1',1990,2.0,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2000,'IMPHCO1',1990,2.0,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2010,'IMPHCO1',1990,2.0,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',1990,'IMPOIL1',1990,8.0,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2000,'IMPOIL1',1990,8.0,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2010,'IMPOIL1',1990,8.0,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',1990,'IMPURN1',1990,2.0,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2000,'IMPURN1',1990,2.0,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2010,'IMPURN1',1990,2.0,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',1990,'E01',1960,0.3,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',1990,'E01',1970,0.3,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',1990,'E01',1980,0.3,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',1990,'E01',1990,0.3,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2000,'E01',1970,0.3,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2000,'E01',1980,0.3,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2000,'E01',1990,0.3,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2000,'E01',2000,0.3,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2010,'E01',1980,0.3,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2010,'E01',1990,0.3,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2010,'E01',2000,0.3,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2010,'E01',2010,0.3,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',1990,'E21',1990,1.5,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2000,'E21',1990,1.5,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2010,'E21',1990,1.5,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2000,'E21',2000,1.5,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2010,'E21',2000,1.5,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2010,'E21',2010,1.5,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',1990,'E70',1960,0.4,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',1990,'E70',1970,0.4,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',1990,'E70',1980,0.4,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',1990,'E70',1990,0.4,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2000,'E70',1970,0.4,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2000,'E70',1980,0.4,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2000,'E70',1990,0.4,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2000,'E70',2000,0.4,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2010,'E70',1980,0.4,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2010,'E70',1990,0.4,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2010,'E70',2000,0.4,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2010,'E70',2010,0.4,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',1990,'SRE',1990,10.0,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2000,'SRE',1990,10.0,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2000,'SRE',2000,10.0,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2010,'SRE',1990,10.0,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2010,'SRE',2000,10.0,'Mdollar / (PJ)',''); -INSERT INTO "cost_variable" VALUES('utopia',2010,'SRE',2010,10.0,'Mdollar / (PJ)',''); -CREATE TABLE demand -( - region TEXT, - period INTEGER - REFERENCES time_period (period), - commodity TEXT - REFERENCES commodity (name), - demand REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, commodity) -); -INSERT INTO "demand" VALUES('utopia',1990,'RH',25.2,'PJ',''); -INSERT INTO "demand" VALUES('utopia',2000,'RH',37.8,'PJ',''); -INSERT INTO "demand" VALUES('utopia',2010,'RH',56.7,'PJ',''); -INSERT INTO "demand" VALUES('utopia',1990,'RL',5.6,'PJ',''); -INSERT INTO "demand" VALUES('utopia',2000,'RL',8.4,'PJ',''); -INSERT INTO "demand" VALUES('utopia',2010,'RL',12.6,'PJ',''); -INSERT INTO "demand" VALUES('utopia',1990,'TX',5.2,'PJ',''); -INSERT INTO "demand" VALUES('utopia',2000,'TX',7.8,'PJ',''); -INSERT INTO "demand" VALUES('utopia',2010,'TX',11.69,'PJ',''); -CREATE TABLE demand_specific_distribution -( - region TEXT, - period INTEGER - REFERENCES time_period (period), - season TEXT - REFERENCES time_season (season), - tod TEXT - REFERENCES time_of_day (tod), - demand_name TEXT - REFERENCES commodity (name), - dsd REAL, - notes TEXT, - PRIMARY KEY (region, period, season, tod, demand_name), - CHECK (dsd >= 0 AND dsd <= 1) -); -INSERT INTO "demand_specific_distribution" VALUES('utopia',1990,'inter','day','RH',0.12,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',1990,'inter','night','RH',0.06,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',1990,'winter','day','RH',0.5467,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',1990,'winter','night','RH',0.2733,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',1990,'inter','day','RL',0.15,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',1990,'inter','night','RL',0.05,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',1990,'summer','day','RL',0.15,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',1990,'summer','night','RL',0.05,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',1990,'winter','day','RL',0.5,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',1990,'winter','night','RL',0.1,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',2000,'inter','day','RH',0.12,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',2000,'inter','night','RH',0.06,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',2000,'winter','day','RH',0.5467,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',2000,'winter','night','RH',0.2733,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',2000,'inter','day','RL',0.15,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',2000,'inter','night','RL',0.05,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',2000,'summer','day','RL',0.15,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',2000,'summer','night','RL',0.05,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',2000,'winter','day','RL',0.5,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',2000,'winter','night','RL',0.1,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',2010,'inter','day','RH',0.12,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',2010,'inter','night','RH',0.06,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',2010,'winter','day','RH',0.5467,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',2010,'winter','night','RH',0.2733,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',2010,'inter','day','RL',0.15,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',2010,'inter','night','RL',0.05,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',2010,'summer','day','RL',0.15,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',2010,'summer','night','RL',0.05,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',2010,'winter','day','RL',0.5,''); -INSERT INTO "demand_specific_distribution" VALUES('utopia',2010,'winter','night','RL',0.1,''); -CREATE TABLE efficiency -( - region TEXT, - input_comm TEXT - REFERENCES commodity (name), - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - output_comm TEXT - REFERENCES commodity (name), - efficiency REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, input_comm, tech, vintage, output_comm), - CHECK (efficiency > 0) -); -INSERT INTO "efficiency" VALUES('utopia','ethos','IMPDSL1',1990,'DSL',1.0,'PJ / (PJ)',''); -INSERT INTO "efficiency" VALUES('utopia','ethos','IMPGSL1',1990,'GSL',1.0,'PJ / (PJ)',''); -INSERT INTO "efficiency" VALUES('utopia','ethos','IMPHCO1',1990,'HCO',1.0,'PJ / (PJ)',''); -INSERT INTO "efficiency" VALUES('utopia','ethos','IMPOIL1',1990,'OIL',1.0,'PJ / (PJ)',''); -INSERT INTO "efficiency" VALUES('utopia','ethos','IMPURN1',1990,'URN',1.0,'PJ / (PJ)',''); -INSERT INTO "efficiency" VALUES('utopia','ethos','IMPFEQ',1990,'FEQ',1.0,'PJ / (PJ)',''); -INSERT INTO "efficiency" VALUES('utopia','ethos','IMPHYD',1990,'HYD',1.0,'PJ / (PJ)',''); -INSERT INTO "efficiency" VALUES('utopia','HCO','E01',1960,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); -INSERT INTO "efficiency" VALUES('utopia','HCO','E01',1970,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); -INSERT INTO "efficiency" VALUES('utopia','HCO','E01',1980,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); -INSERT INTO "efficiency" VALUES('utopia','HCO','E01',1990,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); -INSERT INTO "efficiency" VALUES('utopia','HCO','E01',2000,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); -INSERT INTO "efficiency" VALUES('utopia','HCO','E01',2010,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); -INSERT INTO "efficiency" VALUES('utopia','FEQ','E21',1990,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); -INSERT INTO "efficiency" VALUES('utopia','FEQ','E21',2000,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); -INSERT INTO "efficiency" VALUES('utopia','FEQ','E21',2010,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); -INSERT INTO "efficiency" VALUES('utopia','URN','E21',1990,'ELC',0.4,'PJ / (PJ)','# 1/2.5'); -INSERT INTO "efficiency" VALUES('utopia','URN','E21',2000,'ELC',0.4,'PJ / (PJ)','# 1/2.5'); -INSERT INTO "efficiency" VALUES('utopia','URN','E21',2010,'ELC',0.4,'PJ / (PJ)','# 1/2.5'); -INSERT INTO "efficiency" VALUES('utopia','HYD','E31',1980,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); -INSERT INTO "efficiency" VALUES('utopia','HYD','E31',1990,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); -INSERT INTO "efficiency" VALUES('utopia','HYD','E31',2000,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); -INSERT INTO "efficiency" VALUES('utopia','HYD','E31',2010,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); -INSERT INTO "efficiency" VALUES('utopia','DSL','E70',1960,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); -INSERT INTO "efficiency" VALUES('utopia','DSL','E70',1970,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); -INSERT INTO "efficiency" VALUES('utopia','DSL','E70',1980,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); -INSERT INTO "efficiency" VALUES('utopia','DSL','E70',1990,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); -INSERT INTO "efficiency" VALUES('utopia','DSL','E70',2000,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); -INSERT INTO "efficiency" VALUES('utopia','DSL','E70',2010,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); -INSERT INTO "efficiency" VALUES('utopia','ELC','E51',1980,'ELC',0.72,'PJ / (PJ)','# 1/1.3889'); -INSERT INTO "efficiency" VALUES('utopia','ELC','E51',1990,'ELC',0.72,'PJ / (PJ)','# 1/1.3889'); -INSERT INTO "efficiency" VALUES('utopia','ELC','E51',2000,'ELC',0.72,'PJ / (PJ)','# 1/1.3889'); -INSERT INTO "efficiency" VALUES('utopia','ELC','E51',2010,'ELC',0.72,'PJ / (PJ)','# 1/1.3889'); -INSERT INTO "efficiency" VALUES('utopia','ELC','RHE',1990,'RH',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','ELC','RHE',2000,'RH',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','ELC','RHE',2010,'RH',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','DSL','RHO',1970,'RH',0.7,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','DSL','RHO',1980,'RH',0.7,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','DSL','RHO',1990,'RH',0.7,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','DSL','RHO',2000,'RH',0.7,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','DSL','RHO',2010,'RH',0.7,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','ELC','RL1',1980,'RL',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','ELC','RL1',1990,'RL',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','ELC','RL1',2000,'RL',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','ELC','RL1',2010,'RL',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','OIL','SRE',1990,'DSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); -INSERT INTO "efficiency" VALUES('utopia','OIL','SRE',2000,'DSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); -INSERT INTO "efficiency" VALUES('utopia','OIL','SRE',2010,'DSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); -INSERT INTO "efficiency" VALUES('utopia','OIL','SRE',1990,'GSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); -INSERT INTO "efficiency" VALUES('utopia','OIL','SRE',2000,'GSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); -INSERT INTO "efficiency" VALUES('utopia','OIL','SRE',2010,'GSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); -INSERT INTO "efficiency" VALUES('utopia','DSL','TXD',1970,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','DSL','TXD',1980,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','DSL','TXD',1990,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','DSL','TXD',2000,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','DSL','TXD',2010,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','ELC','TXE',1990,'TX',0.827,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','ELC','TXE',2000,'TX',0.827,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','ELC','TXE',2010,'TX',0.827,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','GSL','TXG',1970,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','GSL','TXG',1980,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','GSL','TXG',1990,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','GSL','TXG',2000,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); -INSERT INTO "efficiency" VALUES('utopia','GSL','TXG',2010,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); -CREATE TABLE efficiency_variable -( - region TEXT, - season TEXT - REFERENCES time_season (season), - tod TEXT - REFERENCES time_of_day (tod), - input_comm TEXT - REFERENCES commodity (name), - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - output_comm TEXT - REFERENCES commodity (name), - efficiency REAL, - notes TEXT, - PRIMARY KEY (region, season, tod, input_comm, tech, vintage, output_comm), - CHECK (efficiency > 0) -); -CREATE TABLE emission_activity -( - region TEXT, - emis_comm TEXT - REFERENCES commodity (name), - input_comm TEXT - REFERENCES commodity (name), - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - output_comm TEXT - REFERENCES commodity (name), - activity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, emis_comm, input_comm, tech, vintage, output_comm) -); -INSERT INTO "emission_activity" VALUES('utopia','co2','ethos','IMPDSL1',1990,'DSL',0.075,'Mt / (PJ)',''); -INSERT INTO "emission_activity" VALUES('utopia','co2','ethos','IMPGSL1',1990,'GSL',0.075,'Mt / (PJ)',''); -INSERT INTO "emission_activity" VALUES('utopia','co2','ethos','IMPHCO1',1990,'HCO',8.9e-02,'Mt / (PJ)',''); -INSERT INTO "emission_activity" VALUES('utopia','co2','ethos','IMPOIL1',1990,'OIL',0.075,'Mt / (PJ)',''); -INSERT INTO "emission_activity" VALUES('utopia','nox','DSL','TXD',1970,'TX',1.0,'Mt / (PJ)',''); -INSERT INTO "emission_activity" VALUES('utopia','nox','DSL','TXD',1980,'TX',1.0,'Mt / (PJ)',''); -INSERT INTO "emission_activity" VALUES('utopia','nox','DSL','TXD',1990,'TX',1.0,'Mt / (PJ)',''); -INSERT INTO "emission_activity" VALUES('utopia','nox','DSL','TXD',2000,'TX',1.0,'Mt / (PJ)',''); -INSERT INTO "emission_activity" VALUES('utopia','nox','DSL','TXD',2010,'TX',1.0,'Mt / (PJ)',''); -INSERT INTO "emission_activity" VALUES('utopia','nox','GSL','TXG',1970,'TX',1.0,'Mt / (PJ)',''); -INSERT INTO "emission_activity" VALUES('utopia','nox','GSL','TXG',1980,'TX',1.0,'Mt / (PJ)',''); -INSERT INTO "emission_activity" VALUES('utopia','nox','GSL','TXG',1990,'TX',1.0,'Mt / (PJ)',''); -INSERT INTO "emission_activity" VALUES('utopia','nox','GSL','TXG',2000,'TX',1.0,'Mt / (PJ)',''); -INSERT INTO "emission_activity" VALUES('utopia','nox','GSL','TXG',2010,'TX',1.0,'Mt / (PJ)',''); -CREATE TABLE emission_embodied -( - region TEXT, - emis_comm TEXT - REFERENCES commodity (name), - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - value REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, emis_comm, tech, vintage) -); -CREATE TABLE emission_end_of_life -( - region TEXT, - emis_comm TEXT - REFERENCES commodity (name), - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - value REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, emis_comm, tech, vintage) -); -CREATE TABLE end_of_life_output -( - region TEXT, - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - output_comm TEXT - REFERENCES commodity (name), - value REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage, output_comm) -); -CREATE TABLE existing_capacity -( - region TEXT, - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - capacity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -INSERT INTO "existing_capacity" VALUES('utopia','E01',1960,0.175,'GW',''); -INSERT INTO "existing_capacity" VALUES('utopia','E01',1970,0.175,'GW',''); -INSERT INTO "existing_capacity" VALUES('utopia','E01',1980,0.15,'GW',''); -INSERT INTO "existing_capacity" VALUES('utopia','E31',1980,0.1,'GW',''); -INSERT INTO "existing_capacity" VALUES('utopia','E51',1980,0.5,'GW',''); -INSERT INTO "existing_capacity" VALUES('utopia','E70',1960,0.05,'GW',''); -INSERT INTO "existing_capacity" VALUES('utopia','E70',1970,0.05,'GW',''); -INSERT INTO "existing_capacity" VALUES('utopia','E70',1980,0.2,'GW',''); -INSERT INTO "existing_capacity" VALUES('utopia','RHO',1970,12.5,'GW',''); -INSERT INTO "existing_capacity" VALUES('utopia','RHO',1980,12.5,'GW',''); -INSERT INTO "existing_capacity" VALUES('utopia','RL1',1980,5.6,'GW',''); -INSERT INTO "existing_capacity" VALUES('utopia','TXD',1970,0.4,'GW',''); -INSERT INTO "existing_capacity" VALUES('utopia','TXD',1980,0.2,'GW',''); -INSERT INTO "existing_capacity" VALUES('utopia','TXG',1970,3.1,'GW',''); -INSERT INTO "existing_capacity" VALUES('utopia','TXG',1980,1.5,'GW',''); -CREATE TABLE lifetime_process -( - region TEXT, - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - lifetime REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -INSERT INTO "lifetime_process" VALUES('utopia','RL1',1980,20.0,'year','#forexistingcap'); -INSERT INTO "lifetime_process" VALUES('utopia','TXD',1970,30.0,'year','#forexistingcap'); -INSERT INTO "lifetime_process" VALUES('utopia','TXD',1980,30.0,'year','#forexistingcap'); -INSERT INTO "lifetime_process" VALUES('utopia','TXG',1970,30.0,'year','#forexistingcap'); -INSERT INTO "lifetime_process" VALUES('utopia','TXG',1980,30.0,'year','#forexistingcap'); -CREATE TABLE lifetime_survival_curve -( - region TEXT NOT NULL, - period INTEGER NOT NULL, - tech TEXT NOT NULL - REFERENCES technology (tech), - vintage INTEGER NOT NULL - REFERENCES time_period (period), - fraction REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage) -); -CREATE TABLE lifetime_tech -( - region TEXT, - tech TEXT - REFERENCES technology (tech), - lifetime REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO "lifetime_tech" VALUES('utopia','E01',40.0,'year',''); -INSERT INTO "lifetime_tech" VALUES('utopia','E21',40.0,'year',''); -INSERT INTO "lifetime_tech" VALUES('utopia','E31',100.0,'year',''); -INSERT INTO "lifetime_tech" VALUES('utopia','E51',100.0,'year',''); -INSERT INTO "lifetime_tech" VALUES('utopia','E70',40.0,'year',''); -INSERT INTO "lifetime_tech" VALUES('utopia','RHE',30.0,'year',''); -INSERT INTO "lifetime_tech" VALUES('utopia','RHO',30.0,'year',''); -INSERT INTO "lifetime_tech" VALUES('utopia','RL1',10.0,'year',''); -INSERT INTO "lifetime_tech" VALUES('utopia','SRE',50.0,'year',''); -INSERT INTO "lifetime_tech" VALUES('utopia','TXD',15.0,'year',''); -INSERT INTO "lifetime_tech" VALUES('utopia','TXE',15.0,'year',''); -INSERT INTO "lifetime_tech" VALUES('utopia','TXG',15.0,'year',''); -INSERT INTO "lifetime_tech" VALUES('utopia','IMPDSL1',1000.0,'year',''); -INSERT INTO "lifetime_tech" VALUES('utopia','IMPGSL1',1000.0,'year',''); -INSERT INTO "lifetime_tech" VALUES('utopia','IMPHCO1',1000.0,'year',''); -INSERT INTO "lifetime_tech" VALUES('utopia','IMPOIL1',1000.0,'year',''); -INSERT INTO "lifetime_tech" VALUES('utopia','IMPURN1',1000.0,'year',''); -INSERT INTO "lifetime_tech" VALUES('utopia','IMPHYD',1000.0,'year',''); -INSERT INTO "lifetime_tech" VALUES('utopia','IMPFEQ',1000.0,'year',''); -CREATE TABLE limit_activity -( - region TEXT, - period INTEGER - REFERENCES time_period (period), - tech_or_group TEXT, - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - activity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech_or_group, operator) -); -CREATE TABLE limit_activity_share -( - region TEXT, - period INTEGER - REFERENCES time_period (period), - sub_group TEXT, - super_group TEXT, - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - share REAL, - notes TEXT, - PRIMARY KEY (region, period, sub_group, super_group, operator) -); -CREATE TABLE limit_annual_capacity_factor -( - region TEXT, - tech_or_group TEXT, - vintage INTEGER - REFERENCES time_period (period), - output_comm TEXT - REFERENCES commodity (name), - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - factor REAL, - notes TEXT, - PRIMARY KEY (region, tech_or_group, vintage, output_comm, operator), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE limit_capacity -( - region TEXT, - period INTEGER - REFERENCES time_period (period), - tech_or_group TEXT, - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - capacity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech_or_group, operator) -); -INSERT INTO "limit_capacity" VALUES('utopia',1990,'E31','ge',0.13,'GW',''); -INSERT INTO "limit_capacity" VALUES('utopia',2000,'E31','ge',0.13,'GW',''); -INSERT INTO "limit_capacity" VALUES('utopia',2010,'E31','ge',0.13,'GW',''); -INSERT INTO "limit_capacity" VALUES('utopia',1990,'SRE','ge',0.1,'GW',''); -INSERT INTO "limit_capacity" VALUES('utopia',1990,'E31','le',0.13,'GW',''); -INSERT INTO "limit_capacity" VALUES('utopia',2000,'E31','le',0.17,'GW',''); -INSERT INTO "limit_capacity" VALUES('utopia',2010,'E31','le',2.1e-01,'GW',''); -INSERT INTO "limit_capacity" VALUES('utopia',1990,'RHE','le',0.0,'GW',''); -INSERT INTO "limit_capacity" VALUES('utopia',1990,'TXD','le',0.6,'GW',''); -INSERT INTO "limit_capacity" VALUES('utopia',2000,'TXD','le',1.76,'GW',''); -INSERT INTO "limit_capacity" VALUES('utopia',2010,'TXD','le',4.76,'GW',''); -CREATE TABLE limit_capacity_share -( - region TEXT, - period INTEGER - REFERENCES time_period (period), - sub_group TEXT, - super_group TEXT, - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - share REAL, - notes TEXT, - PRIMARY KEY (region, period, sub_group, super_group, operator) -); -CREATE TABLE limit_degrowth_capacity -( - region TEXT, - tech_or_group TEXT, - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - rate REAL NOT NULL DEFAULT 0, - seed REAL NOT NULL DEFAULT 0, - seed_units TEXT, - notes TEXT, - PRIMARY KEY (region, tech_or_group, operator) -); -CREATE TABLE limit_degrowth_new_capacity -( - region TEXT, - tech_or_group TEXT, - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - rate REAL NOT NULL DEFAULT 0, - seed REAL NOT NULL DEFAULT 0, - seed_units TEXT, - notes TEXT, - PRIMARY KEY (region, tech_or_group, operator) -); -CREATE TABLE limit_degrowth_new_capacity_delta -( - region TEXT, - tech_or_group TEXT, - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - rate REAL NOT NULL DEFAULT 0, - seed REAL NOT NULL DEFAULT 0, - seed_units TEXT, - notes TEXT, - PRIMARY KEY (region, tech_or_group, operator) -); -CREATE TABLE limit_emission -( - region TEXT, - period INTEGER - REFERENCES time_period (period), - emis_comm TEXT - REFERENCES commodity (name), - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - value REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, emis_comm, operator) -); -CREATE TABLE limit_growth_capacity -( - region TEXT, - tech_or_group TEXT, - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - rate REAL NOT NULL DEFAULT 0, - seed REAL NOT NULL DEFAULT 0, - seed_units TEXT, - notes TEXT, - PRIMARY KEY (region, tech_or_group, operator) -); -CREATE TABLE limit_growth_new_capacity -( - region TEXT, - tech_or_group TEXT, - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - rate REAL NOT NULL DEFAULT 0, - seed REAL NOT NULL DEFAULT 0, - seed_units TEXT, - notes TEXT, - PRIMARY KEY (region, tech_or_group, operator) -); -CREATE TABLE limit_growth_new_capacity_delta -( - region TEXT, - tech_or_group TEXT, - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - rate REAL NOT NULL DEFAULT 0, - seed REAL NOT NULL DEFAULT 0, - seed_units TEXT, - notes TEXT, - PRIMARY KEY (region, tech_or_group, operator) -); -CREATE TABLE limit_new_capacity -( - region TEXT, - tech_or_group TEXT, - vintage INTEGER - REFERENCES time_period (period), - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - new_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech_or_group, vintage, operator) -); -CREATE TABLE limit_new_capacity_share -( - region TEXT, - sub_group TEXT, - super_group TEXT, - vintage INTEGER - REFERENCES time_period (period), - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - share REAL, - notes TEXT, - PRIMARY KEY (region, sub_group, super_group, vintage, operator) -); -CREATE TABLE limit_resource -( - region TEXT, - tech_or_group TEXT, - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - cum_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech_or_group, operator) -); -CREATE TABLE limit_seasonal_capacity_factor -( - region TEXT - REFERENCES region (region), - season TEXT - REFERENCES time_season (season), - tech_or_group TEXT, - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - factor REAL, - notes TEXT, - PRIMARY KEY(region, season, tech_or_group, operator) -); -CREATE TABLE limit_storage_level_fraction -( - region TEXT, - season TEXT, - tod TEXT - REFERENCES time_of_day (tod), - tech TEXT - REFERENCES technology (tech), - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - fraction REAL, - notes TEXT, - CHECK (fraction >= 0 AND fraction <= 1), - PRIMARY KEY(region, season, tod, tech, operator) -); -CREATE TABLE limit_tech_input_split -( - region TEXT, - period INTEGER - REFERENCES time_period (period), - input_comm TEXT - REFERENCES commodity (name), - tech TEXT - REFERENCES technology (tech), - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech, operator) -); -CREATE TABLE limit_tech_input_split_annual -( - region TEXT, - period INTEGER - REFERENCES time_period (period), - input_comm TEXT - REFERENCES commodity (name), - tech TEXT - REFERENCES technology (tech), - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech, operator) -); -CREATE TABLE limit_tech_output_split -( - region TEXT, - period INTEGER - REFERENCES time_period (period), - tech TEXT - REFERENCES technology (tech), - output_comm TEXT - REFERENCES commodity (name), - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, output_comm, operator) -); -INSERT INTO "limit_tech_output_split" VALUES('utopia',1990,'SRE','DSL','ge',0.7,''); -INSERT INTO "limit_tech_output_split" VALUES('utopia',2000,'SRE','DSL','ge',0.7,''); -INSERT INTO "limit_tech_output_split" VALUES('utopia',2010,'SRE','DSL','ge',0.7,''); -INSERT INTO "limit_tech_output_split" VALUES('utopia',1990,'SRE','GSL','ge',0.3,''); -INSERT INTO "limit_tech_output_split" VALUES('utopia',2000,'SRE','GSL','ge',0.3,''); -INSERT INTO "limit_tech_output_split" VALUES('utopia',2010,'SRE','GSL','ge',0.3,''); -CREATE TABLE limit_tech_output_split_annual -( - region TEXT, - period INTEGER - REFERENCES time_period (period), - tech TEXT - REFERENCES technology (tech), - output_comm TEXT - REFERENCES commodity (name), - operator TEXT NOT NULL DEFAULT "le" - REFERENCES operator (operator), - proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, output_comm, operator) -); -CREATE TABLE linked_tech -( - primary_region TEXT, - primary_tech TEXT - REFERENCES technology (tech), - emis_comm TEXT - REFERENCES commodity (name), - driven_tech TEXT - REFERENCES technology (tech), - notes TEXT, - PRIMARY KEY (primary_region, primary_tech, emis_comm) -); -CREATE TABLE loan_lifetime_process -( - region TEXT, - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - lifetime REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -CREATE TABLE loan_rate -( - region TEXT, - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -CREATE TABLE metadata -( - element TEXT, - value INT, - notes TEXT, - PRIMARY KEY (element) -); -INSERT INTO "metadata" VALUES('DB_MAJOR',4,''); -INSERT INTO "metadata" VALUES('DB_MINOR',0,''); -CREATE TABLE metadata_real -( - element TEXT, - value REAL, - notes TEXT, - - PRIMARY KEY (element) -); -INSERT INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); -INSERT INTO "metadata_real" VALUES('global_discount_rate',0.05,''); -CREATE TABLE myopic_efficiency -( - base_year integer, - region text, - input_comm TEXT - REFERENCES commodity (name), - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - output_comm TEXT - REFERENCES commodity (name), - efficiency real, - lifetime integer, - PRIMARY KEY (region, input_comm, tech, vintage, output_comm) -); -CREATE TABLE operator -( - operator TEXT PRIMARY KEY, - notes TEXT -); -INSERT INTO "operator" VALUES('e','equal to'); -INSERT INTO "operator" VALUES('le','less than or equal to'); -INSERT INTO "operator" VALUES('ge','greater than or equal to'); -CREATE TABLE output_built_capacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES sector_label (sector), - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - capacity REAL, - units TEXT, - PRIMARY KEY (region, scenario, tech, vintage) -); -CREATE TABLE output_cost -( - scenario TEXT, - region TEXT, - sector TEXT REFERENCES sector_label (sector), - period INTEGER REFERENCES time_period (period), - tech TEXT REFERENCES technology (tech), - vintage INTEGER REFERENCES time_period (period), - d_invest REAL, - d_fixed REAL, - d_var REAL, - d_emiss REAL, - invest REAL, - fixed REAL, - var REAL, - emiss REAL, - units TEXT, - PRIMARY KEY (scenario, region, period, tech, vintage), - FOREIGN KEY (vintage) REFERENCES time_period (period), - FOREIGN KEY (tech) REFERENCES technology (tech) -); -CREATE TABLE output_curtailment -( - scenario TEXT, - region TEXT, - sector TEXT, - period INTEGER - REFERENCES time_period (period), - season TEXT - REFERENCES time_season (season), - tod TEXT - REFERENCES time_of_day (tod), - input_comm TEXT - REFERENCES commodity (name), - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - output_comm TEXT - REFERENCES commodity (name), - curtailment REAL, - units TEXT, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE output_dual_variable -( - scenario TEXT, - constraint_name TEXT, - dual REAL, - PRIMARY KEY (constraint_name, scenario) -); -CREATE TABLE output_emission -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES sector_label (sector), - period INTEGER - REFERENCES time_period (period), - emis_comm TEXT - REFERENCES commodity (name), - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - emission REAL, - units TEXT, - PRIMARY KEY (region, scenario, period, emis_comm, tech, vintage) -); -CREATE TABLE output_flow_in -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES sector_label (sector), - period INTEGER - REFERENCES time_period (period), - season TEXT - REFERENCES time_season (season), - tod TEXT - REFERENCES time_of_day (tod), - input_comm TEXT - REFERENCES commodity (name), - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - output_comm TEXT - REFERENCES commodity (name), - flow REAL, - units TEXT, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE output_flow_out -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES sector_label (sector), - period INTEGER - REFERENCES time_period (period), - season TEXT - REFERENCES time_season (season), - tod TEXT - REFERENCES time_of_day (tod), - input_comm TEXT - REFERENCES commodity (name), - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - output_comm TEXT - REFERENCES commodity (name), - flow REAL, - units TEXT, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE output_flow_out_summary -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES sector_label (sector), - period INTEGER - REFERENCES time_period (period), - input_comm TEXT - REFERENCES commodity (name), - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - output_comm TEXT - REFERENCES commodity (name), - flow REAL, - PRIMARY KEY (scenario, region, period, input_comm, tech, vintage, output_comm) -); -CREATE TABLE output_net_capacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES sector_label (sector), - period INTEGER - REFERENCES time_period (period), - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - capacity REAL, - units TEXT, - PRIMARY KEY (region, scenario, period, tech, vintage) -); -CREATE TABLE output_objective -( - scenario TEXT, - objective_name TEXT, - total_system_cost REAL -); -CREATE TABLE output_retired_capacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES sector_label (sector), - period INTEGER - REFERENCES time_period (period), - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - cap_eol REAL, - cap_early REAL, - units TEXT, - PRIMARY KEY (region, scenario, period, tech, vintage) -); -CREATE TABLE output_storage_level -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES sector_label (sector), - period INTEGER - REFERENCES time_period (period), - season TEXT, - tod TEXT - REFERENCES time_of_day (tod), - tech TEXT - REFERENCES technology (tech), - vintage INTEGER - REFERENCES time_period (period), - level REAL, - units TEXT, - PRIMARY KEY (scenario, region, period, season, tod, tech, vintage) -); -CREATE TABLE planning_reserve_margin -( - region TEXT - PRIMARY KEY - REFERENCES region (region), - margin REAL, - notes TEXT -); -CREATE TABLE ramp_down_hourly -( - region TEXT, - tech TEXT - REFERENCES technology (tech), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE ramp_up_hourly -( - region TEXT, - tech TEXT - REFERENCES technology (tech), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE region -( - region TEXT - PRIMARY KEY, - notes TEXT -); -INSERT INTO "region" VALUES('utopia',NULL); -CREATE TABLE reserve_capacity_derate -( - region TEXT, - season TEXT - REFERENCES time_season (season), - tech TEXT - REFERENCES technology (tech), - vintage INTEGER, - factor REAL, - notes TEXT, - PRIMARY KEY (region, season, tech, vintage), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE rps_requirement -( - region TEXT NOT NULL - REFERENCES region (region), - period INTEGER NOT NULL - REFERENCES time_period (period), - tech_group TEXT NOT NULL - REFERENCES tech_group (group_name), - requirement REAL NOT NULL, - notes TEXT -); -CREATE TABLE sector_label -( - sector TEXT PRIMARY KEY, - notes TEXT -); -INSERT INTO "sector_label" VALUES('supply',NULL); -INSERT INTO "sector_label" VALUES('electric',NULL); -INSERT INTO "sector_label" VALUES('transport',NULL); -INSERT INTO "sector_label" VALUES('commercial',NULL); -INSERT INTO "sector_label" VALUES('residential',NULL); -INSERT INTO "sector_label" VALUES('industrial',NULL); -CREATE TABLE storage_duration -( - region TEXT, - tech TEXT, - duration REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE tech_group -( - group_name TEXT - PRIMARY KEY, - notes TEXT -); -CREATE TABLE tech_group_member -( - group_name TEXT - REFERENCES tech_group (group_name), - tech TEXT - REFERENCES technology (tech), - PRIMARY KEY (group_name, tech) -); -CREATE TABLE technology -( - tech TEXT NOT NULL PRIMARY KEY, - flag TEXT NOT NULL, - sector TEXT, - category TEXT, - sub_category TEXT, - unlim_cap INTEGER NOT NULL DEFAULT 0, - annual INTEGER NOT NULL DEFAULT 0, - reserve INTEGER NOT NULL DEFAULT 0, - curtail INTEGER NOT NULL DEFAULT 0, - retire INTEGER NOT NULL DEFAULT 0, - flex INTEGER NOT NULL DEFAULT 0, - exchange INTEGER NOT NULL DEFAULT 0, - seas_stor INTEGER NOT NULL DEFAULT 0, - description TEXT, - FOREIGN KEY (flag) REFERENCES technology_type (label) -); -INSERT INTO "technology" VALUES('IMPDSL1','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported diesel'); -INSERT INTO "technology" VALUES('IMPGSL1','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported gasoline'); -INSERT INTO "technology" VALUES('IMPHCO1','p','supply','coal','',1,0,0,0,0,0,0,0,' imported coal'); -INSERT INTO "technology" VALUES('IMPOIL1','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported crude oil'); -INSERT INTO "technology" VALUES('IMPURN1','p','supply','nuclear','',1,0,0,0,0,0,0,0,' imported uranium'); -INSERT INTO "technology" VALUES('IMPFEQ','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported fossil equivalent'); -INSERT INTO "technology" VALUES('IMPHYD','p','supply','hydro','',1,0,0,0,0,0,0,0,' imported water -- doesnt exist in Utopia'); -INSERT INTO "technology" VALUES('E01','pb','electric','coal','',0,0,0,0,0,0,0,0,' coal power plant'); -INSERT INTO "technology" VALUES('E21','pb','electric','nuclear','',0,0,0,0,0,0,0,0,' nuclear power plant'); -INSERT INTO "technology" VALUES('E31','pb','electric','hydro','',0,0,0,0,0,0,0,0,' hydro power'); -INSERT INTO "technology" VALUES('E51','ps','electric','electric','',0,0,0,0,0,0,0,0,' electric storage'); -INSERT INTO "technology" VALUES('E70','p','electric','petroleum','',0,0,0,0,0,0,0,0,' diesel power plant'); -INSERT INTO "technology" VALUES('RHE','p','residential','electric','',0,0,0,0,0,0,0,0,' electric residential heating'); -INSERT INTO "technology" VALUES('RHO','p','residential','petroleum','',0,0,0,0,0,0,0,0,' diesel residential heating'); -INSERT INTO "technology" VALUES('RL1','p','residential','electric','',0,0,0,0,0,0,0,0,' residential lighting'); -INSERT INTO "technology" VALUES('SRE','p','supply','petroleum','',0,0,0,0,0,0,0,0,' crude oil processor'); -INSERT INTO "technology" VALUES('TXD','p','transport','petroleum','',0,0,0,0,0,0,0,0,' diesel powered vehicles'); -INSERT INTO "technology" VALUES('TXE','p','transport','electric','',0,0,0,0,0,0,0,0,' electric powered vehicles'); -INSERT INTO "technology" VALUES('TXG','p','transport','petroleum','',0,0,0,0,0,0,0,0,' gasoline powered vehicles'); -CREATE TABLE technology_type -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO "technology_type" VALUES('p','production technology'); -INSERT INTO "technology_type" VALUES('pb','baseload production technology'); -INSERT INTO "technology_type" VALUES('ps','storage production technology'); -CREATE TABLE time_of_day -( - sequence INTEGER UNIQUE, - tod TEXT - PRIMARY KEY, - hours REAL NOT NULL DEFAULT 1, - notes TEXT, - CHECK (hours > 0) -); -INSERT INTO "time_of_day" (sequence, tod, hours) VALUES(1,'day',16); -INSERT INTO "time_of_day" (sequence, tod, hours) VALUES(2,'night',8); -CREATE TABLE time_period -( - sequence INTEGER UNIQUE, - period INTEGER - PRIMARY KEY, - flag TEXT - REFERENCES time_period_type (label) -); -INSERT INTO "time_period" VALUES(1,1960,'e'); -INSERT INTO "time_period" VALUES(2,1970,'e'); -INSERT INTO "time_period" VALUES(3,1980,'e'); -INSERT INTO "time_period" VALUES(4,1990,'f'); -INSERT INTO "time_period" VALUES(5,2000,'f'); -INSERT INTO "time_period" VALUES(6,2010,'f'); -INSERT INTO "time_period" VALUES(7,2020,'f'); -CREATE TABLE time_period_type -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO "time_period_type" VALUES('e','existing vintages'); -INSERT INTO "time_period_type" VALUES('f','future'); -CREATE TABLE time_season -( - sequence INTEGER UNIQUE, - season TEXT, - segment_fraction REAL NOT NULL, - notes TEXT, - PRIMARY KEY (season), - CHECK (segment_fraction >= 0 AND segment_fraction <= 1) -); -INSERT INTO "time_season" VALUES(0,'inter',0.25,NULL); -INSERT INTO "time_season" VALUES(1,'summer',0.25,NULL); -INSERT INTO "time_season" VALUES(2,'winter',0.5,NULL); -CREATE TABLE time_season_sequential -( - sequence INTEGER UNIQUE, - seas_seq TEXT, - season TEXT REFERENCES time_season(season), - segment_fraction REAL NOT NULL, - notes TEXT, - PRIMARY KEY (seas_seq), - CHECK (segment_fraction >= 0 AND segment_fraction <= 1) -); -CREATE INDEX region_tech_vintage ON myopic_efficiency (region, tech, vintage); +BEGIN TRANSACTION; +REPLACE INTO "capacity_factor_process" VALUES('utopia','inter','day','E31',2000,0.2753,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','inter','night','E31',2000,0.2753,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','winter','day','E31',2000,0.2753,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','winter','night','E31',2000,0.2753,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','summer','day','E31',2000,0.2753,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','summer','night','E31',2000,0.2753,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','inter','day','E31',2010,0.2756,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','inter','night','E31',2010,0.2756,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','winter','day','E31',2010,0.2756,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','winter','night','E31',2010,0.2756,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','summer','day','E31',2010,0.2756,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','summer','night','E31',2010,0.2756,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','inter','day','E01',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','inter','night','E01',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','winter','day','E01',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','winter','night','E01',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','summer','day','E01',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','summer','night','E01',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','inter','day','E21',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','inter','night','E21',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','winter','day','E21',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','winter','night','E21',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','summer','day','E21',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','summer','night','E21',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','inter','day','E31',0.275,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','inter','night','E31',0.275,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','winter','day','E31',0.275,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','winter','night','E31',0.275,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','summer','day','E31',0.275,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','summer','night','E31',0.275,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','inter','day','E51',0.17,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','inter','night','E51',0.17,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','winter','day','E51',0.17,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','winter','night','E51',0.17,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','summer','day','E51',0.17,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','summer','night','E51',0.17,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','inter','day','E70',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','inter','night','E70',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','winter','day','E70',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','winter','night','E70',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','summer','day','E70',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','summer','night','E70',0.8,''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','E01',31.54,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','E21',31.54,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','E31',31.54,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','E51',31.54,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','E70',31.54,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','RHE',1.0,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','RHO',1.0,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','RL1',1.0,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','SRE',1.0,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','TXD',1.0,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','TXE',1.0,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','TXG',1.0,'PJ / (GW * year)',''); +REPLACE INTO "commodity" VALUES('ethos','s','# dummy commodity to supply inputs','PJ'); +REPLACE INTO "commodity" VALUES('DSL','p','# diesel','PJ'); +REPLACE INTO "commodity" VALUES('ELC','p','# electricity','PJ'); +REPLACE INTO "commodity" VALUES('FEQ','p','# fossil equivalent','PJ'); +REPLACE INTO "commodity" VALUES('GSL','p','# gasoline','PJ'); +REPLACE INTO "commodity" VALUES('HCO','p','# coal','PJ'); +REPLACE INTO "commodity" VALUES('HYD','p','# water','PJ'); +REPLACE INTO "commodity" VALUES('OIL','p','# crude oil','PJ'); +REPLACE INTO "commodity" VALUES('URN','p','# uranium','PJ'); +REPLACE INTO "commodity" VALUES('co2','e','#CO2 emissions','Mt'); +REPLACE INTO "commodity" VALUES('nox','e','#NOX emissions','Mt'); +REPLACE INTO "commodity" VALUES('RH','d','# residential heating','PJ'); +REPLACE INTO "commodity" VALUES('RL','d','# residential lighting','PJ'); +REPLACE INTO "commodity" VALUES('TX','d','# transportation','PJ'); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E01',1960,40.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E01',1970,40.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E01',1980,40.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E01',1990,40.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E01',1970,70.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E01',1980,70.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E01',1990,70.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E01',2000,70.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E01',1980,100.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E01',1990,100.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E01',2000,100.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E01',2010,100.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E21',1990,500.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E21',1990,500.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E21',1990,500.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E21',2000,500.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E21',2000,500.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E21',2010,500.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E31',1980,75.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E31',1990,75.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E31',1980,75.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E31',1990,75.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E31',2000,75.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E31',1980,75.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E31',1990,75.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E31',2000,75.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E31',2010,75.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E51',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E51',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E51',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E51',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E51',2000,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E51',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E51',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E51',2000,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E51',2010,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E70',1960,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E70',1970,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E70',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E70',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E70',1970,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E70',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E70',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E70',2000,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E70',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E70',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E70',2000,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E70',2010,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'RHO',1970,1.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'RHO',1980,1.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'RHO',1990,1.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'RHO',1980,1.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'RHO',1990,1.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'RHO',2000,1.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'RHO',1990,1.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'RHO',2000,1.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'RHO',2010,1.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'RL1',1980,9.46,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'RL1',1990,9.46,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'RL1',2000,9.46,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'RL1',2010,9.46,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'TXD',1970,52.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'TXD',1980,52.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'TXD',1990,52.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'TXD',1980,52.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'TXD',1990,52.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'TXD',2000,52.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'TXD',2000,52.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'TXD',2010,52.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'TXE',1990,100.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'TXE',1990,90.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'TXE',2000,90.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'TXE',2000,80.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'TXE',2010,80.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'TXG',1970,48.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'TXG',1980,48.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'TXG',1990,48.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'TXG',1980,48.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'TXG',1990,48.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'TXG',2000,48.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'TXG',2000,48.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'TXG',2010,48.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E01',1990,2000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E01',2000,1300.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E01',2010,1200.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E21',1990,5000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E21',2000,5000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E21',2010,5000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E31',1990,3000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E31',2000,3000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E31',2010,3000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E51',1990,900.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E51',2000,900.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E51',2010,900.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E70',1990,1000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E70',2000,1000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E70',2010,1000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','RHE',1990,90.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','RHE',2000,90.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','RHE',2010,90.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','RHO',1990,100.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','RHO',2000,100.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','RHO',2010,100.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','SRE',1990,100.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','SRE',2000,100.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','SRE',2010,100.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','TXD',1990,1044.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','TXD',2000,1044.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','TXD',2010,1044.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','TXE',1990,2000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','TXE',2000,1750.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','TXE',2010,1500.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','TXG',1990,1044.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','TXG',2000,1044.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','TXG',2010,1044.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'IMPDSL1',1990,10.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'IMPDSL1',1990,10.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'IMPDSL1',1990,10.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'IMPGSL1',1990,15.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'IMPGSL1',1990,15.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'IMPGSL1',1990,15.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'IMPHCO1',1990,2.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'IMPHCO1',1990,2.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'IMPHCO1',1990,2.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'IMPOIL1',1990,8.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'IMPOIL1',1990,8.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'IMPOIL1',1990,8.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'IMPURN1',1990,2.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'IMPURN1',1990,2.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'IMPURN1',1990,2.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'E01',1960,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'E01',1970,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'E01',1980,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'E01',1990,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'E01',1970,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'E01',1980,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'E01',1990,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'E01',2000,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E01',1980,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E01',1990,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E01',2000,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E01',2010,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'E21',1990,1.5,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'E21',1990,1.5,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E21',1990,1.5,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'E21',2000,1.5,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E21',2000,1.5,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E21',2010,1.5,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'E70',1960,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'E70',1970,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'E70',1980,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'E70',1990,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'E70',1970,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'E70',1980,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'E70',1990,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'E70',2000,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E70',1980,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E70',1990,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E70',2000,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E70',2010,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'SRE',1990,10.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'SRE',1990,10.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'SRE',2000,10.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'SRE',1990,10.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'SRE',2000,10.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'SRE',2010,10.0,'Mdollar / (PJ)',''); +REPLACE INTO "demand" VALUES('utopia',1990,'RH',25.2,'PJ',''); +REPLACE INTO "demand" VALUES('utopia',2000,'RH',37.8,'PJ',''); +REPLACE INTO "demand" VALUES('utopia',2010,'RH',56.7,'PJ',''); +REPLACE INTO "demand" VALUES('utopia',1990,'RL',5.6,'PJ',''); +REPLACE INTO "demand" VALUES('utopia',2000,'RL',8.4,'PJ',''); +REPLACE INTO "demand" VALUES('utopia',2010,'RL',12.6,'PJ',''); +REPLACE INTO "demand" VALUES('utopia',1990,'TX',5.2,'PJ',''); +REPLACE INTO "demand" VALUES('utopia',2000,'TX',7.8,'PJ',''); +REPLACE INTO "demand" VALUES('utopia',2010,'TX',11.69,'PJ',''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',1990,'inter','day','RH',0.12,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',1990,'inter','night','RH',0.06,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',1990,'winter','day','RH',0.5467,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',1990,'winter','night','RH',0.2733,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',1990,'inter','day','RL',0.15,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',1990,'inter','night','RL',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',1990,'summer','day','RL',0.15,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',1990,'summer','night','RL',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',1990,'winter','day','RL',0.5,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',1990,'winter','night','RL',0.1,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2000,'inter','day','RH',0.12,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2000,'inter','night','RH',0.06,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2000,'winter','day','RH',0.5467,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2000,'winter','night','RH',0.2733,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2000,'inter','day','RL',0.15,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2000,'inter','night','RL',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2000,'summer','day','RL',0.15,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2000,'summer','night','RL',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2000,'winter','day','RL',0.5,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2000,'winter','night','RL',0.1,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2010,'inter','day','RH',0.12,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2010,'inter','night','RH',0.06,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2010,'winter','day','RH',0.5467,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2010,'winter','night','RH',0.2733,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2010,'inter','day','RL',0.15,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2010,'inter','night','RL',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2010,'summer','day','RL',0.15,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2010,'summer','night','RL',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2010,'winter','day','RL',0.5,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2010,'winter','night','RL',0.1,''); +REPLACE INTO "efficiency" VALUES('utopia','ethos','IMPDSL1',1990,'DSL',1.0,'PJ / (PJ)',''); +REPLACE INTO "efficiency" VALUES('utopia','ethos','IMPGSL1',1990,'GSL',1.0,'PJ / (PJ)',''); +REPLACE INTO "efficiency" VALUES('utopia','ethos','IMPHCO1',1990,'HCO',1.0,'PJ / (PJ)',''); +REPLACE INTO "efficiency" VALUES('utopia','ethos','IMPOIL1',1990,'OIL',1.0,'PJ / (PJ)',''); +REPLACE INTO "efficiency" VALUES('utopia','ethos','IMPURN1',1990,'URN',1.0,'PJ / (PJ)',''); +REPLACE INTO "efficiency" VALUES('utopia','ethos','IMPFEQ',1990,'FEQ',1.0,'PJ / (PJ)',''); +REPLACE INTO "efficiency" VALUES('utopia','ethos','IMPHYD',1990,'HYD',1.0,'PJ / (PJ)',''); +REPLACE INTO "efficiency" VALUES('utopia','HCO','E01',1960,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','HCO','E01',1970,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','HCO','E01',1980,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','HCO','E01',1990,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','HCO','E01',2000,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','HCO','E01',2010,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','FEQ','E21',1990,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','FEQ','E21',2000,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','FEQ','E21',2010,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','URN','E21',1990,'ELC',0.4,'PJ / (PJ)','# 1/2.5'); +REPLACE INTO "efficiency" VALUES('utopia','URN','E21',2000,'ELC',0.4,'PJ / (PJ)','# 1/2.5'); +REPLACE INTO "efficiency" VALUES('utopia','URN','E21',2010,'ELC',0.4,'PJ / (PJ)','# 1/2.5'); +REPLACE INTO "efficiency" VALUES('utopia','HYD','E31',1980,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','HYD','E31',1990,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','HYD','E31',2000,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','HYD','E31',2010,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','E70',1960,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','E70',1970,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','E70',1980,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','E70',1990,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','E70',2000,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','E70',2010,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','E51',1980,'ELC',0.72,'PJ / (PJ)','# 1/1.3889'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','E51',1990,'ELC',0.72,'PJ / (PJ)','# 1/1.3889'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','E51',2000,'ELC',0.72,'PJ / (PJ)','# 1/1.3889'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','E51',2010,'ELC',0.72,'PJ / (PJ)','# 1/1.3889'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','RHE',1990,'RH',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','RHE',2000,'RH',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','RHE',2010,'RH',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','RHO',1970,'RH',0.7,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','RHO',1980,'RH',0.7,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','RHO',1990,'RH',0.7,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','RHO',2000,'RH',0.7,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','RHO',2010,'RH',0.7,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','RL1',1980,'RL',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','RL1',1990,'RL',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','RL1',2000,'RL',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','RL1',2010,'RL',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','OIL','SRE',1990,'DSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); +REPLACE INTO "efficiency" VALUES('utopia','OIL','SRE',2000,'DSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); +REPLACE INTO "efficiency" VALUES('utopia','OIL','SRE',2010,'DSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); +REPLACE INTO "efficiency" VALUES('utopia','OIL','SRE',1990,'GSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); +REPLACE INTO "efficiency" VALUES('utopia','OIL','SRE',2000,'GSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); +REPLACE INTO "efficiency" VALUES('utopia','OIL','SRE',2010,'GSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','TXD',1970,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','TXD',1980,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','TXD',1990,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','TXD',2000,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','TXD',2010,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','TXE',1990,'TX',0.827,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','TXE',2000,'TX',0.827,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','TXE',2010,'TX',0.827,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','GSL','TXG',1970,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','GSL','TXG',1980,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','GSL','TXG',1990,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','GSL','TXG',2000,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','GSL','TXG',2010,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "emission_activity" VALUES('utopia','co2','ethos','IMPDSL1',1990,'DSL',0.075,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','co2','ethos','IMPGSL1',1990,'GSL',0.075,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','co2','ethos','IMPHCO1',1990,'HCO',8.9e-02,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','co2','ethos','IMPOIL1',1990,'OIL',0.075,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','nox','DSL','TXD',1970,'TX',1.0,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','nox','DSL','TXD',1980,'TX',1.0,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','nox','DSL','TXD',1990,'TX',1.0,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','nox','DSL','TXD',2000,'TX',1.0,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','nox','DSL','TXD',2010,'TX',1.0,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','nox','GSL','TXG',1970,'TX',1.0,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','nox','GSL','TXG',1980,'TX',1.0,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','nox','GSL','TXG',1990,'TX',1.0,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','nox','GSL','TXG',2000,'TX',1.0,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','nox','GSL','TXG',2010,'TX',1.0,'Mt / (PJ)',''); +REPLACE INTO "existing_capacity" VALUES('utopia','E01',1960,0.175,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','E01',1970,0.175,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','E01',1980,0.15,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','E31',1980,0.1,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','E51',1980,0.5,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','E70',1960,0.05,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','E70',1970,0.05,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','E70',1980,0.2,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','RHO',1970,12.5,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','RHO',1980,12.5,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','RL1',1980,5.6,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','TXD',1970,0.4,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','TXD',1980,0.2,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','TXG',1970,3.1,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','TXG',1980,1.5,'GW',''); +REPLACE INTO "lifetime_process" VALUES('utopia','RL1',1980,20.0,'year','#forexistingcap'); +REPLACE INTO "lifetime_process" VALUES('utopia','TXD',1970,30.0,'year','#forexistingcap'); +REPLACE INTO "lifetime_process" VALUES('utopia','TXD',1980,30.0,'year','#forexistingcap'); +REPLACE INTO "lifetime_process" VALUES('utopia','TXG',1970,30.0,'year','#forexistingcap'); +REPLACE INTO "lifetime_process" VALUES('utopia','TXG',1980,30.0,'year','#forexistingcap'); +REPLACE INTO "lifetime_tech" VALUES('utopia','E01',40.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','E21',40.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','E31',100.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','E51',100.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','E70',40.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','RHE',30.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','RHO',30.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','RL1',10.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','SRE',50.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','TXD',15.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','TXE',15.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','TXG',15.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','IMPDSL1',1000.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','IMPGSL1',1000.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','IMPHCO1',1000.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','IMPOIL1',1000.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','IMPURN1',1000.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','IMPHYD',1000.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','IMPFEQ',1000.0,'year',''); +REPLACE INTO "limit_capacity" VALUES('utopia',1990,'E31','ge',0.13,'GW',''); +REPLACE INTO "limit_capacity" VALUES('utopia',2000,'E31','ge',0.13,'GW',''); +REPLACE INTO "limit_capacity" VALUES('utopia',2010,'E31','ge',0.13,'GW',''); +REPLACE INTO "limit_capacity" VALUES('utopia',1990,'SRE','ge',0.1,'GW',''); +REPLACE INTO "limit_capacity" VALUES('utopia',1990,'E31','le',0.13,'GW',''); +REPLACE INTO "limit_capacity" VALUES('utopia',2000,'E31','le',0.17,'GW',''); +REPLACE INTO "limit_capacity" VALUES('utopia',2010,'E31','le',2.1e-01,'GW',''); +REPLACE INTO "limit_capacity" VALUES('utopia',1990,'RHE','le',0.0,'GW',''); +REPLACE INTO "limit_capacity" VALUES('utopia',1990,'TXD','le',0.6,'GW',''); +REPLACE INTO "limit_capacity" VALUES('utopia',2000,'TXD','le',1.76,'GW',''); +REPLACE INTO "limit_capacity" VALUES('utopia',2010,'TXD','le',4.76,'GW',''); +REPLACE INTO "limit_tech_output_split" VALUES('utopia',1990,'SRE','DSL','ge',0.7,''); +REPLACE INTO "limit_tech_output_split" VALUES('utopia',2000,'SRE','DSL','ge',0.7,''); +REPLACE INTO "limit_tech_output_split" VALUES('utopia',2010,'SRE','DSL','ge',0.7,''); +REPLACE INTO "limit_tech_output_split" VALUES('utopia',1990,'SRE','GSL','ge',0.3,''); +REPLACE INTO "limit_tech_output_split" VALUES('utopia',2000,'SRE','GSL','ge',0.3,''); +REPLACE INTO "limit_tech_output_split" VALUES('utopia',2010,'SRE','GSL','ge',0.3,''); +REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); +REPLACE INTO "metadata_real" VALUES('global_discount_rate',0.05,''); +REPLACE INTO "region" VALUES('utopia',NULL); +REPLACE INTO "sector_label" VALUES('supply',NULL); +REPLACE INTO "sector_label" VALUES('electric',NULL); +REPLACE INTO "sector_label" VALUES('transport',NULL); +REPLACE INTO "sector_label" VALUES('commercial',NULL); +REPLACE INTO "sector_label" VALUES('residential',NULL); +REPLACE INTO "sector_label" VALUES('industrial',NULL); +REPLACE INTO "technology" VALUES('IMPDSL1','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported diesel'); +REPLACE INTO "technology" VALUES('IMPGSL1','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported gasoline'); +REPLACE INTO "technology" VALUES('IMPHCO1','p','supply','coal','',1,0,0,0,0,0,0,0,' imported coal'); +REPLACE INTO "technology" VALUES('IMPOIL1','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported crude oil'); +REPLACE INTO "technology" VALUES('IMPURN1','p','supply','nuclear','',1,0,0,0,0,0,0,0,' imported uranium'); +REPLACE INTO "technology" VALUES('IMPFEQ','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported fossil equivalent'); +REPLACE INTO "technology" VALUES('IMPHYD','p','supply','hydro','',1,0,0,0,0,0,0,0,' imported water -- doesnt exist in Utopia'); +REPLACE INTO "technology" VALUES('E01','pb','electric','coal','',0,0,0,0,0,0,0,0,' coal power plant'); +REPLACE INTO "technology" VALUES('E21','pb','electric','nuclear','',0,0,0,0,0,0,0,0,' nuclear power plant'); +REPLACE INTO "technology" VALUES('E31','pb','electric','hydro','',0,0,0,0,0,0,0,0,' hydro power'); +REPLACE INTO "technology" VALUES('E51','ps','electric','electric','',0,0,0,0,0,0,0,0,' electric storage'); +REPLACE INTO "technology" VALUES('E70','p','electric','petroleum','',0,0,0,0,0,0,0,0,' diesel power plant'); +REPLACE INTO "technology" VALUES('RHE','p','residential','electric','',0,0,0,0,0,0,0,0,' electric residential heating'); +REPLACE INTO "technology" VALUES('RHO','p','residential','petroleum','',0,0,0,0,0,0,0,0,' diesel residential heating'); +REPLACE INTO "technology" VALUES('RL1','p','residential','electric','',0,0,0,0,0,0,0,0,' residential lighting'); +REPLACE INTO "technology" VALUES('SRE','p','supply','petroleum','',0,0,0,0,0,0,0,0,' crude oil processor'); +REPLACE INTO "technology" VALUES('TXD','p','transport','petroleum','',0,0,0,0,0,0,0,0,' diesel powered vehicles'); +REPLACE INTO "technology" VALUES('TXE','p','transport','electric','',0,0,0,0,0,0,0,0,' electric powered vehicles'); +REPLACE INTO "technology" VALUES('TXG','p','transport','petroleum','',0,0,0,0,0,0,0,0,' gasoline powered vehicles'); +REPLACE INTO "time_of_day" (sequence, tod, hours) VALUES(1,'day',16); +REPLACE INTO "time_of_day" (sequence, tod, hours) VALUES(2,'night',8); +REPLACE INTO "time_period" VALUES(1,1960,'e'); +REPLACE INTO "time_period" VALUES(2,1970,'e'); +REPLACE INTO "time_period" VALUES(3,1980,'e'); +REPLACE INTO "time_period" VALUES(4,1990,'f'); +REPLACE INTO "time_period" VALUES(5,2000,'f'); +REPLACE INTO "time_period" VALUES(6,2010,'f'); +REPLACE INTO "time_period" VALUES(7,2020,'f'); +REPLACE INTO "time_season" VALUES(0,'inter',0.25,NULL); +REPLACE INTO "time_season" VALUES(1,'summer',0.25,NULL); +REPLACE INTO "time_season" VALUES(2,'winter',0.5,NULL); COMMIT; diff --git a/tests/conftest.py b/tests/conftest.py index 3f813d5b8..5714aa84e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,6 +10,7 @@ from temoa._internal.temoa_sequencer import TemoaSequencer from temoa.core.config import TemoaConfig from temoa.core.model import TemoaModel +from temoa.extensions.framework import get_known_extension_specs logger = logging.getLogger(__name__) @@ -57,6 +58,13 @@ def _build_test_db( # Force FK OFF again as schema file might turn it on at the end con.execute('PRAGMA foreign_keys = OFF') + # 1b. Load extension-owned tables so data scripts can populate them. + # Extension tables are not part of the central schema, so apply each + # known extension's schema (CREATE TABLE IF NOT EXISTS) here. + for spec in get_known_extension_specs().values(): + if spec.schema_sql_path: + con.executescript(Path(spec.schema_sql_path).read_text(encoding='utf-8')) + # 2. Load data scripts for script_path in data_scripts: with open(script_path) as f: @@ -88,6 +96,7 @@ def refresh_databases() -> None: ('mediumville.sql', 'mediumville.sqlite'), ('seasonal_storage.sql', 'seasonal_storage.sqlite'), ('survival_curve.sql', 'survival_curve.sqlite'), + ('myopic_capacities.sql', 'myopic_capacities.sqlite'), ('annualised_demand.sql', 'annualised_demand.sqlite'), # Feature tests (separate for temporal consistency) ('emissions.sql', 'emissions.sqlite'), @@ -207,6 +216,11 @@ def system_test_run( silent=True, ) + # Allow parametrized tests to override myopic settings per case. + myopic_overrides = request.param.get('myopic') + if myopic_overrides is not None: + config.myopic_inputs = {**(config.myopic_inputs or {}), **myopic_overrides} + sequencer = TemoaSequencer(config=config) sequencer.start() diff --git a/tests/legacy_test_values.py b/tests/legacy_test_values.py index af31669c3..3fb7d2e13 100644 --- a/tests/legacy_test_values.py +++ b/tests/legacy_test_values.py @@ -68,7 +68,8 @@ class ExpectedVals(Enum): # added 2025/06/12 prior to addition of dynamic reserve margin # reduced 2025/06/16 after fixing bug in db ExpectedVals.OBJ_VALUE: 7035.7275, - ExpectedVals.EFF_DOMAIN_SIZE: 2800, + # halved after realising mediumville had an unused existing time period + ExpectedVals.EFF_DOMAIN_SIZE: 1400, ExpectedVals.EFF_INDEX_SIZE: 18, # increased after reviving RampSeason constraints # reduced 2025/07/25 by 24 after annualising demands diff --git a/tests/test_cli.py b/tests/test_cli.py index 19144fd40..5ba1fa1ee 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -12,7 +12,7 @@ # Path to the configuration file template we will use for tests. TESTING_CONFIGS_DIR = Path(__file__).parent / 'testing_configs' -UTOPIA_SQL_FIXTURE = Path(__file__).parent.parent / 'temoa' / 'tutorial_assets' / 'utopia.sql' +UTOPIA_SQL_FIXTURE = Path(__file__).parent / 'testing_data' / 'utopia_v3.sql' UTOPIA_CONFIG_TEMPLATE = TESTING_CONFIGS_DIR / 'config_utopia.toml' diff --git a/tests/test_extensions.py b/tests/test_extensions.py new file mode 100644 index 000000000..e283dd929 --- /dev/null +++ b/tests/test_extensions.py @@ -0,0 +1,95 @@ +"""Tests for the optional model extensions and their wiring into TemoaModel.""" + +from __future__ import annotations + +from pathlib import Path + +import pytest +from pyomo.environ import Constraint + +from temoa.core.model import TemoaModel +from temoa.extensions.framework import ( + get_known_extension_specs, + resolve_extension_specs, +) + +# Parametrize over every registered extension so new extensions are covered +# automatically without listing their components here. +EXTENSION_IDS = sorted(get_known_extension_specs()) + + +def _component_map(model: TemoaModel) -> dict[str, object]: + """Map of component name -> component object declared on a model.""" + return {component.name: component for component in model.component_objects()} + + +# Cache abstract models so each extension is only built once per test session. +_abstract_models: dict[str | None, TemoaModel] = {} + + +def _model_with(extension_id: str | None) -> TemoaModel: + if extension_id not in _abstract_models: + extensions = [extension_id] if extension_id else None + _abstract_models[extension_id] = TemoaModel(extensions=extensions) + return _abstract_models[extension_id] + + +def _added_components(extension_id: str) -> dict[str, object]: + """Components present with the extension enabled but absent without it.""" + base = _component_map(_model_with(None)) + enabled = _component_map(_model_with(extension_id)) + return {name: comp for name, comp in enabled.items() if name not in base} + + +@pytest.mark.parametrize('extension_id', EXTENSION_IDS) +def test_extension_registered(extension_id: str) -> None: + """The extension is discoverable by its id and resolves to its spec.""" + spec = get_known_extension_specs()[extension_id] + assert spec.extension_id == extension_id + assert resolve_extension_specs([extension_id]) == (spec,) + + +@pytest.mark.parametrize('extension_id', EXTENSION_IDS) +def test_extension_spec_fields(extension_id: str) -> None: + """The spec declares consistent region-group columns and a registration hook.""" + spec = get_known_extension_specs()[extension_id] + # Region-group tables must refer to declared owned tables via string columns. + assert set(spec.regional_group_tables) <= set(spec.owned_tables) + assert all(isinstance(column, str) and column for column in spec.regional_group_tables.values()) + # Every extension attaches model components; data loading is optional. + assert spec.register_model_components is not None + + +@pytest.mark.parametrize('extension_id', EXTENSION_IDS) +def test_extension_schema_matches_owned_tables(extension_id: str) -> None: + """If the extension ships a schema, it defines every owned table. + + Extensions that only add components feeding from existing tables own no + tables and need not provide a schema file. + """ + spec = get_known_extension_specs()[extension_id] + if spec.schema_sql_path is None: + # No schema means the extension introduces no tables of its own. + assert not spec.owned_tables + return + schema_path = Path(spec.schema_sql_path) + assert schema_path.is_file() + schema_sql = schema_path.read_text(encoding='utf-8') + for table in spec.owned_tables: + assert table in schema_sql + + +@pytest.mark.parametrize('extension_id', EXTENSION_IDS) +def test_extension_adds_components(extension_id: str) -> None: + """Enabling the extension attaches new model components, including constraints.""" + added = _added_components(extension_id) + assert added, f'{extension_id} should attach components to the model' + assert any(isinstance(component, Constraint) for component in added.values()), ( + f'{extension_id} should contribute at least one constraint' + ) + + +def test_unknown_extension_id_rejected() -> None: + """Resolving an unregistered extension id raises a descriptive error.""" + with pytest.raises(ValueError, match='Unknown extension id'): + resolve_extension_specs(['not_a_real_extension']) diff --git a/tests/test_full_runs.py b/tests/test_full_runs.py index 45ca7d1bf..9956449e5 100644 --- a/tests/test_full_runs.py +++ b/tests/test_full_runs.py @@ -5,7 +5,7 @@ import logging import sqlite3 from pathlib import Path -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, TypedDict import pytest from pyomo.core import Constraint, Var @@ -37,6 +37,37 @@ stochastic_files = [{'name': 'stochastic utopia', 'filename': 'config_utopia_stochastic.toml'}] +class MyopicSettings(TypedDict): + view_depth: int + step_size: int + evolving: bool + + +class MyopicStressCase(TypedDict): + name: str + filename: str + myopic: MyopicSettings + + +myopic_stress_tests: list[MyopicStressCase] = [ + { + 'name': ( + f'myopic capacities | {"evolving" if evolving else "non-evolving"}' + f' | view={view_depth} step={step_size}' + ), + 'filename': 'config_myopic_capacities.toml', + 'myopic': { + 'view_depth': view_depth, + 'step_size': step_size, + 'evolving': evolving, + }, + } + for evolving in (False, True) + for view_depth in [1, 3, 6] + for step_size in range(1, view_depth + 1, 2) +] + + @pytest.mark.parametrize( 'system_test_run', argvalues=legacy_config_files, @@ -129,6 +160,56 @@ def test_myopic_utopia( ) +@pytest.mark.parametrize( + 'system_test_run', + argvalues=myopic_stress_tests, + indirect=True, + ids=[d['name'] for d in myopic_stress_tests], +) +def test_myopic_stress_tests( + system_test_run: tuple[str, SolverResults | None, TemoaModel | None, TemoaSequencer], +) -> None: + """ + The idea of these is that they should be tightly constrained so that if anything + is wrong the model will fail to find a feasible solution. Use lots of equality constraints. + + Two new paths test limit_discrete_capacity and limit_discrete_new_capacity with EoS costs + and survival curves. + """ + _, _, _, sequencer = system_test_run + import contextlib + + with contextlib.closing(sqlite3.connect(sequencer.config.output_database)) as con: + cur = con.cursor() + dc_fixed = cur.execute( + "SELECT SUM(fixed) FROM output_cost WHERE tech='tech_discrete_cap'" + ).fetchone()[0] + dc_variable = cur.execute( + "SELECT SUM(var) FROM output_cost WHERE tech='tech_discrete_cap'" + ).fetchone()[0] + dnc_invest = cur.execute( + "SELECT SUM(invest) FROM output_cost WHERE tech='tech_discrete_new_cap'" + ).fetchone()[0] + + assert dc_fixed == pytest.approx(37, rel=1e-5), ( + 'tech_discrete_cap fixed cost did not match expected' + ) + assert dc_variable == pytest.approx(24, rel=1e-5), ( + 'tech_discrete_cap variable cost did not match expected' + ) + assert dnc_invest == pytest.approx(2.5, rel=1e-5), ( + 'tech_discrete_new_cap invest cost did not match expected' + ) + + # This part is just a very rough check on the objective function. Constraints inside the + # model are extremely tight so other changes will likely lead to infeasibility + res = cur.execute('SELECT SUM(total_system_cost) FROM main.output_objective').fetchone() + obj = res[0] + assert obj == pytest.approx(309, rel=0.01), ( + 'objective function value did not match expected for myopic stress test' + ) + + @pytest.mark.parametrize( 'system_test_run', argvalues=stochastic_files, diff --git a/tests/test_table_writer.py b/tests/test_table_writer.py index 3f30d2e1f..fdfe741fb 100644 --- a/tests/test_table_writer.py +++ b/tests/test_table_writer.py @@ -2,14 +2,14 @@ import pytest -from temoa._internal.table_data_puller import loan_costs +from temoa.components.costs import poll_loan_costs class LoanCostInput(TypedDict): capacity: float invest_cost: float loan_life: float - loan_rate: float + loan_annualize: float global_discount_rate: float process_life: int p_0: int @@ -31,7 +31,7 @@ class LoanCostTestCase(TypedDict): 'capacity': 100_000.0, # units 'invest_cost': 1.0, # $/unit of capacity 'loan_life': 40.0, - 'loan_rate': 0.10, + 'loan_annualize': 0.102259414, 'global_discount_rate': 0.000000000001, 'process_life': 40, 'p_0': 2020, # the "myopic base year" to which all prices are discounted @@ -47,7 +47,7 @@ class LoanCostTestCase(TypedDict): 'capacity': 100_000.0, 'invest_cost': 1.0, 'loan_life': 40.0, - 'loan_rate': 0.08, + 'loan_annualize': 0.083860162, 'global_discount_rate': 0.05, 'process_life': 50, 'p_0': 2020, @@ -65,7 +65,7 @@ class LoanCostTestCase(TypedDict): 'capacity': 100_000.0, # units 'invest_cost': 1.0, # $/unit of capacity 'loan_life': 40.0, - 'loan_rate': 0.10, + 'loan_annualize': 0.102259414, 'global_discount_rate': 0, 'process_life': 40, 'p_0': 2020, # the "myopic base year" to which all prices are discounted @@ -84,7 +84,7 @@ def test_loan_costs(test_case: LoanCostTestCase) -> None: Test the loan cost calculations """ # we will test with a 1% error to accommodate the approximation of GDR=0 - model_cost, undiscounted_cost = loan_costs(**test_case['input']) + model_cost, undiscounted_cost = poll_loan_costs(**test_case['input']) assert model_cost == pytest.approx(test_case['expected_model_cost'], rel=0.01) assert undiscounted_cost == pytest.approx(test_case['expected_undiscounted_cost'], rel=0.01) @@ -99,6 +99,6 @@ def test_loan_costs_with_zero_gdr(test_case: LoanCostTestCase) -> None: Test the formula with zero for GDR to make sure it is handled correctly. The formula risks division by zero if this is not correct. """ - model_cost, undiscounted_cost = loan_costs(**test_case['input']) + model_cost, undiscounted_cost = poll_loan_costs(**test_case['input']) assert model_cost == pytest.approx(test_case['expected_model_cost'], abs=0.01) assert undiscounted_cost == pytest.approx(test_case['expected_undiscounted_cost'], abs=0.01) diff --git a/tests/testing_configs/config_myopic_capacities.toml b/tests/testing_configs/config_myopic_capacities.toml new file mode 100644 index 000000000..4ff76c5b3 --- /dev/null +++ b/tests/testing_configs/config_myopic_capacities.toml @@ -0,0 +1,24 @@ +scenario = "test myopic capacities" +scenario_mode = "myopic" +extensions= ["growth_rates", "discrete_capacity", "eos"] +input_database = "tests/testing_outputs/myopic_capacities.sqlite" +output_database = "tests/testing_outputs/myopic_capacities.sqlite" +neos = false +solver_name = "appsi_highs" +save_excel = true +save_duals = true +save_lp_file = false +time_sequencing = "seasonal_timeslices" +days_per_period = 365 +reserve_margin = "static" + +[MGA] +slack = 0.1 +iterations = 4 +weight = "integer" + +[myopic] +view_depth = 1 +step_size = 1 +evolving = false +evolution_script = '' diff --git a/tests/testing_data/mediumville.sql b/tests/testing_data/mediumville.sql index 9df6b3f7a..2f253a8ce 100644 --- a/tests/testing_data/mediumville.sql +++ b/tests/testing_data/mediumville.sql @@ -181,7 +181,6 @@ REPLACE INTO "technology_type" VALUES('pb','baseload production technology'); REPLACE INTO "technology_type" VALUES('ps','storage production technology'); REPLACE INTO "time_of_day" VALUES(1,'d1',12,NULL); REPLACE INTO "time_of_day" VALUES(2,'d2',12,NULL); -REPLACE INTO "time_period" VALUES(1,2020,'e'); REPLACE INTO "time_period" VALUES(2,2025,'f'); REPLACE INTO "time_period" VALUES(3,2030,'f'); REPLACE INTO "time_period_type" VALUES('e','existing vintages'); diff --git a/tests/testing_data/mediumville_sets.json b/tests/testing_data/mediumville_sets.json index 82bf63afd..acc5b16ea 100644 --- a/tests/testing_data/mediumville_sets.json +++ b/tests/testing_data/mediumville_sets.json @@ -31,6 +31,7 @@ "flow_in_storage_rpsditvo": "ff680754a218ee843941e77bd07cbc5e5afd74e2b7b29a76ea86621624e8c0fd", "flow_var_annual_rpitvo": "6f5c569787fbf1e0a4076011f75fcd50a840bac85bb57b026f4fc5682739f888", "flow_var_rpsditvo": "e8c35c3b7b1f0c0e10e6f53e94d0c6b8a884c0dd048675cbeb9e7df33794161b", + "lifetime_tech_rt": "6786f2e9acdccce907ebad50d2ea70481bff72a749253e78168d29b480b07f65", "lifetime_process_rtv": "212639322f29aa7687eb9962fb9d74ef60961d720a2a753e2af53473b9e1648f", "limit_activity_constraint_rpt": "90461349933d0b32167abdca242233004b66212b57ed57213dce6f6818203963", "limit_activity_share_constraint_rpgg": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", @@ -38,13 +39,7 @@ "limit_annual_capacity_factor_constraint_rtvo": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "limit_capacity_constraint_rpt": "118826dabd14af75ae09adbd5bdbba750528b3d59907cbfba3690223ffc7def7", "limit_capacity_share_constraint_rpgg": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", - "limit_degrowth_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", - "limit_degrowth_new_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", - "limit_degrowth_new_capacity_delta_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "limit_emission_constraint_rpe": "9bf30514ecc261370f67cd0c2e18194f45ff14c15764d274cc342a5a27eaf2a0", - "limit_growth_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", - "limit_growth_new_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", - "limit_growth_new_capacity_delta_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "limit_new_capacity_constraint_rtv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "limit_new_capacity_share_constraint_rggv": "296e4148565f752973655692bae04ebf50877bd13fe9cec8f9563f347a61d885", "limit_resource_constraint_rt": "b9b44ae8bc9642da0a81202877bd69f0b35ce193e31590dd3325ac0828edb5cf", @@ -101,7 +96,7 @@ "tech_uncap": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "tech_upramping": "ddcf6ff7665c2a8acc4dff1b43655fae1b5a265135cbee18cec638df4e954346", "tech_with_capacity": "24f75b6f705a36033456d02638e7c50667908c45c474151fb8490666d928c63f", - "time_exist": "91a2461c25439830d94bf4b3d3a3b020343f75e74561a913b1f972b2ac42e943", + "time_exist": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "time_future": "6b150df8e12ac4dd15396b52f304c7935a41d2cc4da498186552819973171389", "time_manual": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "time_of_day": "f9bbfe9130c510cba59a13e8b385b4d0206196abbdfe8032b995502bb7215f76", @@ -109,7 +104,7 @@ "time_season": "1e413891065e24bbf66543d4747ff12b1c65bf23c4ff9857b0bf0520eccd7f21", "time_season_sequential": "1e413891065e24bbf66543d4747ff12b1c65bf23c4ff9857b0bf0520eccd7f21", "time_sequencing": "91f69c8abab9959c1f8c90f5aaa56db29bccc67e37d12673ab41c54e4179d7ca", - "vintage_all": "947dd08ad4812a98649b4306e4a0ca1b51374d86f1a8e27c3a8af3b189bcc0f6", - "vintage_exist": "91a2461c25439830d94bf4b3d3a3b020343f75e74561a913b1f972b2ac42e943", + "vintage_all": "9c9380fb50cd4f4f9e2032bd9a18645ae4fc1d30335672c62c897bcb9e099ba5", + "vintage_exist": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "vintage_optimize": "9c9380fb50cd4f4f9e2032bd9a18645ae4fc1d30335672c62c897bcb9e099ba5" } diff --git a/tests/testing_data/myopic_capacities.sql b/tests/testing_data/myopic_capacities.sql new file mode 100644 index 000000000..ca11c4158 --- /dev/null +++ b/tests/testing_data/myopic_capacities.sql @@ -0,0 +1,337 @@ +REPLACE INTO metadata VALUES('DB_MAJOR',4,''); +REPLACE INTO metadata VALUES('DB_MINOR',0,''); +REPLACE INTO metadata_real VALUES('global_discount_rate',0.05000000000000000277,'Discount Rate for future costs'); +REPLACE INTO metadata_real VALUES('default_loan_rate',0.05000000000000000277,'Default Loan Rate if not specified in loan_rate table'); +REPLACE INTO sector_label VALUES('energy',NULL); +REPLACE INTO commodity VALUES('source','s',NULL,NULL); +REPLACE INTO commodity VALUES('demand','d',NULL,NULL); +REPLACE INTO commodity_type VALUES('p','physical commodity'); +REPLACE INTO commodity_type VALUES('a','annual commodity'); +REPLACE INTO commodity_type VALUES('e','emissions commodity'); +REPLACE INTO commodity_type VALUES('d','demand commodity'); +REPLACE INTO commodity_type VALUES('s','source commodity'); +REPLACE INTO commodity_type VALUES('w','waste commodity'); +REPLACE INTO commodity_type VALUES('wa','waste annual commodity'); +REPLACE INTO commodity_type VALUES('wp','waste physical commodity'); +REPLACE INTO cost_fixed VALUES('region',2025,'tech_ancient',1994,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2025,'tech_old',2010,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2030,'tech_old',2010,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2035,'tech_old',2010,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2040,'tech_old',2010,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2025,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2030,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2035,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2040,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2045,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2050,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2030,'tech_future',2030,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2035,'tech_future',2035,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2040,'tech_future',2040,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2045,'tech_future',2045,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2050,'tech_future',2050,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2035,'tech_future',2030,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2040,'tech_future',2035,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2045,'tech_future',2040,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2050,'tech_future',2045,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2040,'tech_future',2030,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2045,'tech_future',2035,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2050,'tech_future',2040,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2045,'tech_future',2030,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2050,'tech_future',2035,1.0,NULL,NULL); +REPLACE INTO cost_fixed VALUES('region',2050,'tech_future',2030,1.0,NULL,NULL); +REPLACE INTO cost_invest VALUES('region','tech_current',2025,1.0,NULL,NULL); +REPLACE INTO cost_invest VALUES('region','tech_future',2030,1.0,NULL,NULL); +REPLACE INTO cost_invest VALUES('region','tech_future',2035,1.0,NULL,NULL); +REPLACE INTO cost_invest VALUES('region','tech_future',2040,1.0,NULL,NULL); +REPLACE INTO cost_invest VALUES('region','tech_future',2045,1.0,NULL,NULL); +REPLACE INTO cost_invest VALUES('region','tech_future',2050,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2025,'tech_ancient',1994,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2025,'tech_old',2010,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2030,'tech_old',2010,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2035,'tech_old',2010,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2040,'tech_old',2010,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2025,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2030,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2035,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2040,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2045,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2050,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2030,'tech_future',2030,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2035,'tech_future',2035,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2040,'tech_future',2040,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2045,'tech_future',2045,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2050,'tech_future',2050,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2035,'tech_future',2030,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2040,'tech_future',2035,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2045,'tech_future',2040,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2050,'tech_future',2045,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2040,'tech_future',2030,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2045,'tech_future',2035,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2050,'tech_future',2040,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2045,'tech_future',2030,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2050,'tech_future',2035,1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2050,'tech_future',2030,1.0,NULL,NULL); +REPLACE INTO demand VALUES('region',2025,'demand',1.0,NULL,NULL); +REPLACE INTO demand VALUES('region',2030,'demand',1.0,NULL,NULL); +REPLACE INTO demand VALUES('region',2035,'demand',1.0,NULL,NULL); +REPLACE INTO demand VALUES('region',2040,'demand',1.0,NULL,NULL); +REPLACE INTO demand VALUES('region',2045,'demand',1.0,NULL,NULL); +REPLACE INTO demand VALUES('region',2050,'demand',1.0,NULL,NULL); +REPLACE INTO efficiency VALUES('region','source','tech_old',2010,'demand',1.0,NULL,NULL); +REPLACE INTO efficiency VALUES('region','source','tech_current',2025,'demand',1.0,NULL,NULL); +REPLACE INTO efficiency VALUES('region','source','tech_future',2030,'demand',1.0,NULL,NULL); +REPLACE INTO efficiency VALUES('region','source','tech_future',2035,'demand',1.0,NULL,NULL); +REPLACE INTO efficiency VALUES('region','source','tech_future',2040,'demand',1.0,NULL,NULL); +REPLACE INTO efficiency VALUES('region','source','tech_future',2045,'demand',1.0,NULL,NULL); +REPLACE INTO efficiency VALUES('region','source','tech_future',2050,'demand',1.0,NULL,NULL); +REPLACE INTO efficiency VALUES('region','source','tech_ancient',1994,'demand',1.0,NULL,NULL); +REPLACE INTO efficiency VALUES('region','source','tech_retire',2010,'demand',1.0,NULL,NULL); +REPLACE INTO existing_capacity VALUES('region','tech_ancient',1990,3.0,NULL,NULL); +REPLACE INTO existing_capacity VALUES('region','tech_old',2010,0.6999999999999999556,NULL,NULL); +REPLACE INTO existing_capacity VALUES('region','tech_ancient',1994,3.0,NULL,NULL); +REPLACE INTO existing_capacity VALUES('region','tech_retire',2010,1.0,NULL,NULL); +REPLACE INTO lifetime_tech VALUES('region','tech_ancient',35.0,NULL,NULL); +REPLACE INTO lifetime_tech VALUES('region','tech_old',35.0,NULL,NULL); +REPLACE INTO lifetime_tech VALUES('region','tech_current',35.0,NULL,NULL); +REPLACE INTO lifetime_tech VALUES('region','tech_future',35.0,NULL,NULL); +REPLACE INTO lifetime_tech VALUES('region','tech_retire',35.0,NULL,NULL); +REPLACE INTO operator VALUES('e','equal to'); +REPLACE INTO operator VALUES('le','less than or equal to'); +REPLACE INTO operator VALUES('ge','greater than or equal to'); +REPLACE INTO limit_growth_capacity VALUES('region','tech_ancient','le',1.0,1.0,NULL,NULL); +REPLACE INTO limit_growth_capacity VALUES('region','tech_old','le',1.0,1.0,NULL,NULL); +REPLACE INTO limit_growth_capacity VALUES('region','tech_current','le',1.0,1.0,NULL,NULL); +REPLACE INTO limit_growth_capacity VALUES('region','tech_future','le',1.0,1.0,NULL,NULL); +REPLACE INTO limit_degrowth_capacity VALUES('region','tech_ancient','le',1.0,2.0,NULL,NULL); +REPLACE INTO limit_degrowth_capacity VALUES('region','tech_old','le',1.0,1.0,NULL,NULL); +REPLACE INTO limit_degrowth_capacity VALUES('region','tech_current','le',1.0,1.0,NULL,NULL); +REPLACE INTO limit_degrowth_capacity VALUES('region','tech_future','le',1.0,1.0,NULL,NULL); +REPLACE INTO limit_growth_new_capacity VALUES('region','tech_ancient','le',1.0,1.0,NULL,NULL); +REPLACE INTO limit_growth_new_capacity VALUES('region','tech_old','le',1.0,1.0,NULL,NULL); +REPLACE INTO limit_growth_new_capacity VALUES('region','tech_current','le',1.0,1.0,NULL,NULL); +REPLACE INTO limit_growth_new_capacity VALUES('region','tech_future','le',1.0,1.0,NULL,NULL); +REPLACE INTO limit_degrowth_new_capacity VALUES('region','tech_ancient','le',1.0,1.0,NULL,NULL); +REPLACE INTO limit_degrowth_new_capacity VALUES('region','tech_old','le',1.0,1.0,NULL,NULL); +REPLACE INTO limit_degrowth_new_capacity VALUES('region','tech_current','le',1.0,1.0,NULL,NULL); +REPLACE INTO limit_degrowth_new_capacity VALUES('region','tech_future','le',1.0,1.0,NULL,NULL); +REPLACE INTO limit_growth_new_capacity_delta VALUES('region','tech_ancient','le',0.0,2.0,NULL,NULL); +REPLACE INTO limit_growth_new_capacity_delta VALUES('region','tech_old','le',0.0,2.0,NULL,NULL); +REPLACE INTO limit_growth_new_capacity_delta VALUES('region','tech_current','le',0.0,2.0,NULL,NULL); +REPLACE INTO limit_growth_new_capacity_delta VALUES('region','tech_future','le',0.0,2.0,NULL,NULL); +REPLACE INTO limit_degrowth_new_capacity_delta VALUES('region','tech_ancient','le',0.0,2.0,NULL,NULL); +REPLACE INTO limit_degrowth_new_capacity_delta VALUES('region','tech_old','le',0.0,2.0,NULL,NULL); +REPLACE INTO limit_degrowth_new_capacity_delta VALUES('region','tech_current','le',0.0,2.0,NULL,NULL); +REPLACE INTO limit_degrowth_new_capacity_delta VALUES('region','tech_future','le',0.0,2.0,NULL,NULL); +REPLACE INTO limit_activity VALUES('region',2025,'tech_ancient','e',0.04800000000000000099,NULL,NULL); +REPLACE INTO limit_activity VALUES('region',2025,'tech_current','e',0.6520000000000000239,NULL,NULL); +REPLACE INTO limit_activity VALUES('region',2030,'tech_current','e',0.5823319839999999692,NULL,NULL); +REPLACE INTO limit_activity VALUES('region',2035,'tech_current','e',0.4500000000000000111,NULL,NULL); +REPLACE INTO limit_activity VALUES('region',2040,'tech_current','e',0.25,NULL,NULL); +REPLACE INTO limit_activity VALUES('region',2030,'tech_future','e',0.2964180160000000063,NULL,NULL); +REPLACE INTO limit_activity VALUES('region',2035,'tech_future','e',0.5100000000000000088,NULL,NULL); +REPLACE INTO limit_activity VALUES('region',2040,'tech_future','e',0.7349999999999999867,NULL,NULL); +REPLACE INTO limit_activity VALUES('region',2045,'tech_future','e',1.0,NULL,NULL); +REPLACE INTO limit_activity VALUES('region',2050,'tech_future','e',1.0,NULL,NULL); +REPLACE INTO limit_activity VALUES('region',2025,'tech_old','e',0.2999910000000000076,NULL,NULL); +REPLACE INTO limit_activity VALUES('region',2030,'tech_old','e',0.1212499999999999967,NULL,NULL); +REPLACE INTO limit_activity VALUES('region',2035,'tech_old','e',0.04000000000000000083,NULL,NULL); +REPLACE INTO limit_activity VALUES('region',2040,'tech_old','e',0.01499999999999999945,NULL,NULL); +REPLACE INTO limit_capacity VALUES('region',2025,'tech_ancient','e',0.04800000000000000099,NULL,NULL); +REPLACE INTO limit_capacity VALUES('region',2025,'tech_current','e',0.6520000000000000239,NULL,NULL); +REPLACE INTO limit_capacity VALUES('region',2030,'tech_current','e',0.5823319839999999692,NULL,NULL); +REPLACE INTO limit_capacity VALUES('region',2035,'tech_current','e',0.4500000000000000111,NULL,NULL); +REPLACE INTO limit_capacity VALUES('region',2040,'tech_current','e',0.25,NULL,NULL); +REPLACE INTO limit_capacity VALUES('region',2045,'tech_current','e',0.0,NULL,NULL); +REPLACE INTO limit_capacity VALUES('region',2050,'tech_current','e',0.0,NULL,NULL); +REPLACE INTO limit_capacity VALUES('region',2030,'tech_future','e',0.2964180160000000063,NULL,NULL); +REPLACE INTO limit_capacity VALUES('region',2035,'tech_future','e',0.5100000000000000088,NULL,NULL); +REPLACE INTO limit_capacity VALUES('region',2040,'tech_future','e',0.7349999999999999867,NULL,NULL); +REPLACE INTO limit_capacity VALUES('region',2045,'tech_future','e',1.0,NULL,NULL); +REPLACE INTO limit_capacity VALUES('region',2050,'tech_future','e',1.0,NULL,NULL); +REPLACE INTO limit_capacity VALUES('region',2025,'tech_old','e',0.2999999999999999889,NULL,NULL); +REPLACE INTO limit_capacity VALUES('region',2025,'tech_retire','e',9.000000000000000228e-06,NULL,NULL); +REPLACE INTO limit_capacity VALUES('region',2030,'tech_old','e',0.1212499999999999967,NULL,NULL); +REPLACE INTO limit_capacity VALUES('region',2035,'tech_old','e',0.04000000000000000083,NULL,NULL); +REPLACE INTO limit_capacity VALUES('region',2040,'tech_old','e',0.01499999999999999945,NULL,NULL); +REPLACE INTO limit_new_capacity VALUES('region','tech_current',2025,'e',0.659919028340081093,NULL,NULL); +REPLACE INTO limit_new_capacity VALUES('region','tech_future',2030,'e',0.3000182348178138114,NULL,NULL); +REPLACE INTO limit_new_capacity VALUES('region','tech_future',2035,'e',0.232573854939435165,NULL,NULL); +REPLACE INTO limit_new_capacity VALUES('region','tech_future',2040,'e',0.2884229446031822408,NULL,NULL); +REPLACE INTO limit_new_capacity VALUES('region','tech_future',2045,'e',0.4110596210476472612,NULL,NULL); +REPLACE INTO limit_new_capacity VALUES('region','tech_future',2050,'e',0.2251165192346591404,NULL,NULL); +REPLACE INTO region VALUES('region',NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',1990,'tech_ancient',1990,1.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2025,'tech_ancient',1990,0.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2010,'tech_old',2010,1.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2015,'tech_old',2010,0.969999999999999974,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2020,'tech_old',2010,0.8800000000000001154,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2025,'tech_old',2010,0.6199999999999999956,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2030,'tech_old',2010,0.2700000000000000177,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2035,'tech_old',2010,0.08000000000000000166,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2045,'tech_old',2010,0.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2025,'tech_current',2025,1.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2030,'tech_current',2025,0.969999999999999974,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2035,'tech_current',2025,0.8800000000000001154,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2040,'tech_current',2025,0.6199999999999999956,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2045,'tech_current',2025,0.2700000000000000177,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2050,'tech_current',2025,0.08000000000000000166,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2060,'tech_current',2025,0.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2030,'tech_future',2030,1.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2035,'tech_future',2030,0.969999999999999974,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2040,'tech_future',2030,0.8800000000000001154,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2045,'tech_future',2030,0.6199999999999999956,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2050,'tech_future',2030,0.2700000000000000177,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2055,'tech_future',2030,0.08000000000000000166,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2065,'tech_future',2030,0.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2035,'tech_future',2035,1.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2040,'tech_future',2035,0.969999999999999974,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2045,'tech_future',2035,0.8800000000000001154,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2050,'tech_future',2035,0.6199999999999999956,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2055,'tech_future',2035,0.2700000000000000177,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2060,'tech_future',2035,0.08000000000000000166,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2070,'tech_future',2035,0.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2040,'tech_future',2040,1.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2045,'tech_future',2040,0.969999999999999974,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2050,'tech_future',2040,0.8800000000000001154,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2055,'tech_future',2040,0.6199999999999999956,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2060,'tech_future',2040,0.2700000000000000177,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2065,'tech_future',2040,0.08000000000000000166,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2075,'tech_future',2040,0.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2045,'tech_future',2045,1.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2050,'tech_future',2045,0.969999999999999974,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2055,'tech_future',2045,0.8800000000000001154,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2060,'tech_future',2045,0.6199999999999999956,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2065,'tech_future',2045,0.2700000000000000177,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2070,'tech_future',2045,0.08000000000000000166,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2080,'tech_future',2045,0.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2050,'tech_future',2050,1.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2055,'tech_future',2050,0.969999999999999974,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2060,'tech_future',2050,0.8800000000000001154,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2065,'tech_future',2050,0.6199999999999999956,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2070,'tech_future',2050,0.2700000000000000177,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2075,'tech_future',2050,0.08000000000000000166,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2085,'tech_future',2050,0.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',1994,'tech_ancient',1994,1.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',1999,'tech_ancient',1994,0.969999999999999974,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2004,'tech_ancient',1994,0.8800000000000000044,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2009,'tech_ancient',1994,0.6199999999999999956,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2014,'tech_ancient',1994,0.2700000000000000177,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2019,'tech_ancient',1994,0.08000000000000000166,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2029,'tech_ancient',1994,0.0,NULL); +REPLACE INTO technology_type VALUES('p','production technology'); +REPLACE INTO technology_type VALUES('pb','baseload production technology'); +REPLACE INTO technology_type VALUES('ps','storage production technology'); +REPLACE INTO time_of_day VALUES(0,'d',24.0,NULL); +REPLACE INTO time_period VALUES(-3,1990,'e'); +REPLACE INTO time_period VALUES(-2,1994,'e'); +REPLACE INTO time_period VALUES(-1,2010,'e'); +REPLACE INTO time_period VALUES(0,2025,'f'); +REPLACE INTO time_period VALUES(1,2030,'f'); +REPLACE INTO time_period VALUES(2,2035,'f'); +REPLACE INTO time_period VALUES(3,2040,'f'); +REPLACE INTO time_period VALUES(4,2045,'f'); +REPLACE INTO time_period VALUES(5,2050,'f'); +REPLACE INTO time_period VALUES(6,2055,'f'); +REPLACE INTO time_period_type VALUES('e','existing vintages'); +REPLACE INTO time_period_type VALUES('f','future'); +REPLACE INTO technology VALUES('tech_ancient','p','energy',NULL,NULL,0,0,0,0,1,0,0,0,NULL); +REPLACE INTO technology VALUES('tech_old','p','energy',NULL,NULL,0,0,0,0,1,0,0,0,NULL); +REPLACE INTO technology VALUES('tech_current','p','energy',NULL,NULL,0,0,0,0,1,0,0,0,NULL); +REPLACE INTO technology VALUES('tech_future','p','energy',NULL,NULL,0,0,0,0,1,0,0,0,NULL); +REPLACE INTO technology VALUES('tech_retire','p','energy',NULL,NULL,0,0,0,0,1,0,0,0,NULL); +REPLACE INTO time_season VALUES(0,'s',1.0,NULL); +REPLACE INTO commodity VALUES('demand_dc','d',NULL,NULL); +REPLACE INTO commodity VALUES('demand_dnc','d',NULL,NULL); +REPLACE INTO technology VALUES('tech_discrete_cap','p','energy',NULL,NULL,0,0,0,0,1,0,0,0,NULL); +REPLACE INTO technology VALUES('tech_dc_dummy','p','energy',NULL,NULL,1,0,0,0,0,0,0,0,NULL); +REPLACE INTO technology VALUES('tech_discrete_new_cap','p','energy',NULL,NULL,0,0,0,0,1,0,0,0,NULL); +REPLACE INTO technology VALUES('tech_dnc_dummy','p','energy',NULL,NULL,1,0,0,0,0,0,0,0,NULL); +REPLACE INTO lifetime_tech VALUES('region','tech_discrete_cap',35.0,NULL,NULL); +REPLACE INTO lifetime_tech VALUES('region','tech_dc_dummy',35.0,NULL,NULL); +REPLACE INTO lifetime_tech VALUES('region','tech_discrete_new_cap',5.0,NULL,NULL); +REPLACE INTO lifetime_tech VALUES('region','tech_dnc_dummy',35.0,NULL,NULL); +REPLACE INTO loan_rate VALUES('region','tech_discrete_new_cap',2025,0.0,NULL); +REPLACE INTO loan_rate VALUES('region','tech_discrete_new_cap',2030,0.0,NULL); +REPLACE INTO loan_rate VALUES('region','tech_discrete_new_cap',2035,0.0,NULL); +REPLACE INTO loan_rate VALUES('region','tech_discrete_new_cap',2040,0.0,NULL); +REPLACE INTO loan_rate VALUES('region','tech_discrete_new_cap',2045,0.0,NULL); +REPLACE INTO loan_rate VALUES('region','tech_discrete_new_cap',2050,0.0,NULL); +REPLACE INTO efficiency VALUES('region','source','tech_discrete_cap',2025,'demand_dc',1.0,NULL,NULL); +REPLACE INTO efficiency VALUES('region','source','tech_dc_dummy',2025,'demand_dc',1.0,NULL,NULL); +REPLACE INTO efficiency VALUES('region','source','tech_discrete_new_cap',2025,'demand_dnc',1.0,NULL,NULL); +REPLACE INTO efficiency VALUES('region','source','tech_discrete_new_cap',2030,'demand_dnc',1.0,NULL,NULL); +REPLACE INTO efficiency VALUES('region','source','tech_discrete_new_cap',2035,'demand_dnc',1.0,NULL,NULL); +REPLACE INTO efficiency VALUES('region','source','tech_discrete_new_cap',2040,'demand_dnc',1.0,NULL,NULL); +REPLACE INTO efficiency VALUES('region','source','tech_discrete_new_cap',2045,'demand_dnc',1.0,NULL,NULL); +REPLACE INTO efficiency VALUES('region','source','tech_discrete_new_cap',2050,'demand_dnc',1.0,NULL,NULL); +REPLACE INTO efficiency VALUES('region','source','tech_dnc_dummy',2025,'demand_dnc',1.0,NULL,NULL); +REPLACE INTO demand VALUES('region',2025,'demand_dc',2.0,NULL,NULL); +REPLACE INTO demand VALUES('region',2030,'demand_dc',2.0,NULL,NULL); +REPLACE INTO demand VALUES('region',2035,'demand_dc',2.0,NULL,NULL); +REPLACE INTO demand VALUES('region',2040,'demand_dc',2.0,NULL,NULL); +REPLACE INTO demand VALUES('region',2045,'demand_dc',2.0,NULL,NULL); +REPLACE INTO demand VALUES('region',2050,'demand_dc',2.0,NULL,NULL); +REPLACE INTO demand VALUES('region',2025,'demand_dnc',1.0,NULL,NULL); +REPLACE INTO demand VALUES('region',2030,'demand_dnc',1.0,NULL,NULL); +REPLACE INTO demand VALUES('region',2035,'demand_dnc',1.0,NULL,NULL); +REPLACE INTO demand VALUES('region',2040,'demand_dnc',1.0,NULL,NULL); +REPLACE INTO demand VALUES('region',2045,'demand_dnc',1.0,NULL,NULL); +REPLACE INTO demand VALUES('region',2050,'demand_dnc',1.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2025,'tech_dc_dummy',2025,10.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2030,'tech_dc_dummy',2025,10.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2035,'tech_dc_dummy',2025,10.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2040,'tech_dc_dummy',2025,10.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2045,'tech_dc_dummy',2025,10.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2050,'tech_dc_dummy',2025,10.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2025,'tech_dnc_dummy',2025,10.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2030,'tech_dnc_dummy',2025,10.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2035,'tech_dnc_dummy',2025,10.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2040,'tech_dnc_dummy',2025,10.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2045,'tech_dnc_dummy',2025,10.0,NULL,NULL); +REPLACE INTO cost_variable VALUES('region',2050,'tech_dnc_dummy',2025,10.0,NULL,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2025,'tech_discrete_cap',2025,1.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2030,'tech_discrete_cap',2025,0.8,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2035,'tech_discrete_cap',2025,0.6,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2040,'tech_discrete_cap',2025,0.4,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2045,'tech_discrete_cap',2025,0.2,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2060,'tech_discrete_cap',2025,0.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2025,'tech_discrete_new_cap',2025,1.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2030,'tech_discrete_new_cap',2025,0.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2030,'tech_discrete_new_cap',2030,1.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2035,'tech_discrete_new_cap',2030,0.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2035,'tech_discrete_new_cap',2035,1.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2040,'tech_discrete_new_cap',2035,0.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2040,'tech_discrete_new_cap',2040,1.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2045,'tech_discrete_new_cap',2040,0.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2045,'tech_discrete_new_cap',2045,1.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2050,'tech_discrete_new_cap',2045,0.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2050,'tech_discrete_new_cap',2050,1.0,NULL); +REPLACE INTO lifetime_survival_curve VALUES('region',2055,'tech_discrete_new_cap',2050,0.0,NULL); +REPLACE INTO limit_discrete_capacity VALUES('region','tech_discrete_cap',0.2,NULL,NULL); +REPLACE INTO limit_discrete_new_capacity VALUES('region','tech_discrete_new_cap',0.2,NULL,NULL); +REPLACE INTO cost_invest_eos VALUES('region','tech_discrete_new_cap',1,0.0,0.5,0.0,1.0,NULL,NULL); +REPLACE INTO cost_invest_eos VALUES('region','tech_discrete_new_cap',2,0.5,2.0,1.0,2.5,NULL,NULL); +REPLACE INTO cost_fixed_eos VALUES('region',2025,'tech_discrete_cap',1,0.0,0.5,0.0,1.0,NULL,NULL); +REPLACE INTO cost_fixed_eos VALUES('region',2025,'tech_discrete_cap',2,0.5,2.0,1.0,2.5,NULL,NULL); +REPLACE INTO cost_fixed_eos VALUES('region',2030,'tech_discrete_cap',1,0.0,0.5,0.0,1.0,NULL,NULL); +REPLACE INTO cost_fixed_eos VALUES('region',2030,'tech_discrete_cap',2,0.5,2.0,1.0,2.5,NULL,NULL); +REPLACE INTO cost_fixed_eos VALUES('region',2035,'tech_discrete_cap',1,0.0,0.5,0.0,1.0,NULL,NULL); +REPLACE INTO cost_fixed_eos VALUES('region',2035,'tech_discrete_cap',2,0.5,2.0,1.0,2.5,NULL,NULL); +REPLACE INTO cost_fixed_eos VALUES('region',2040,'tech_discrete_cap',1,0.0,0.5,0.0,1.0,NULL,NULL); +REPLACE INTO cost_fixed_eos VALUES('region',2040,'tech_discrete_cap',2,0.5,2.0,1.0,2.5,NULL,NULL); +REPLACE INTO cost_fixed_eos VALUES('region',2045,'tech_discrete_cap',1,0.0,0.5,0.0,1.0,NULL,NULL); +REPLACE INTO cost_fixed_eos VALUES('region',2045,'tech_discrete_cap',2,0.5,2.0,1.0,2.5,NULL,NULL); +REPLACE INTO cost_fixed_eos VALUES('region',2050,'tech_discrete_cap',1,0.0,0.5,0.0,1.0,NULL,NULL); +REPLACE INTO cost_fixed_eos VALUES('region',2050,'tech_discrete_cap',2,0.5,2.0,1.0,2.5,NULL,NULL); +REPLACE INTO cost_variable_eos VALUES('region',2025,'tech_discrete_cap',1,0.0,0.5,0.0,0.75,NULL,NULL); +REPLACE INTO cost_variable_eos VALUES('region',2025,'tech_discrete_cap',2,0.5,2.0,0.75,1.5,NULL,NULL); +REPLACE INTO cost_variable_eos VALUES('region',2030,'tech_discrete_cap',1,0.0,0.5,0.0,0.75,NULL,NULL); +REPLACE INTO cost_variable_eos VALUES('region',2030,'tech_discrete_cap',2,0.5,2.0,0.75,1.5,NULL,NULL); +REPLACE INTO cost_variable_eos VALUES('region',2035,'tech_discrete_cap',1,0.0,0.5,0.0,0.75,NULL,NULL); +REPLACE INTO cost_variable_eos VALUES('region',2035,'tech_discrete_cap',2,0.5,2.0,0.75,1.5,NULL,NULL); +REPLACE INTO cost_variable_eos VALUES('region',2040,'tech_discrete_cap',1,0.0,0.5,0.0,0.75,NULL,NULL); +REPLACE INTO cost_variable_eos VALUES('region',2040,'tech_discrete_cap',2,0.5,2.0,0.75,1.5,NULL,NULL); +REPLACE INTO cost_variable_eos VALUES('region',2045,'tech_discrete_cap',1,0.0,0.5,0.0,0.75,NULL,NULL); +REPLACE INTO cost_variable_eos VALUES('region',2045,'tech_discrete_cap',2,0.5,2.0,0.75,1.5,NULL,NULL); +REPLACE INTO cost_variable_eos VALUES('region',2050,'tech_discrete_cap',1,0.0,0.5,0.0,0.75,NULL,NULL); +REPLACE INTO cost_variable_eos VALUES('region',2050,'tech_discrete_cap',2,0.5,2.0,0.75,1.5,NULL,NULL); diff --git a/tests/testing_data/test_system_sets.json b/tests/testing_data/test_system_sets.json index 35cd0b4cf..f5a33a549 100644 --- a/tests/testing_data/test_system_sets.json +++ b/tests/testing_data/test_system_sets.json @@ -31,6 +31,7 @@ "flow_in_storage_rpsditvo": "8c97fbeacbca1a772ba04f63d0a82f7703500f83c0dc602114b17c64ad4d2e28", "flow_var_annual_rpitvo": "f98f5f41c5cba8ce2a894734abbc5e90310b695bee3c24c52acd8b4800c5ab85", "flow_var_rpsditvo": "784d98e451a8dcf0122f475c2e229162d99e403bdea85f7c608a3d86e850db44", + "lifetime_tech_rt": "984cc175a7aa58e77bb626e3237ca9bf625f5d9b15250870bef1cddc5f57f642", "lifetime_process_rtv": "5033502364848a3a3f295f1b3d051531ecd5c1e5f8bbaecd61afcd18181225ab", "limit_activity_constraint_rpt": "2561103e7e6dd3dee3ba5832290ab5e933f4af08661dff4f2e661b6f4ffefc86", "limit_activity_share_constraint_rpgg": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", @@ -38,13 +39,7 @@ "limit_annual_capacity_factor_constraint_rtvo": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "limit_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "limit_capacity_share_constraint_rpgg": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", - "limit_degrowth_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", - "limit_degrowth_new_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", - "limit_degrowth_new_capacity_delta_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "limit_emission_constraint_rpe": "6f11e4ee041970584903d5afba7ee1147824b233260422564fdf0c701e9fabde", - "limit_growth_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", - "limit_growth_new_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", - "limit_growth_new_capacity_delta_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "limit_new_capacity_constraint_rtv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "limit_new_capacity_share_constraint_rggv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "limit_resource_constraint_rt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", @@ -63,7 +58,7 @@ "new_capacity_var_rtv": "bc0ec9e7f812410cb2af924cbc99a31c6e99cd95fc2de26193daad38f33cc132", "operator": "74d830836f1399fb336a0432dde7d7bd36cffa3ff76b1c42d7945350cfb9bf91", "ordered_season_sequential": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", - "process_life_frac_rptv": "5501652d0145dbf7c2e8c006aabd3dbb85ca64ed5caf1d8f45a1957274af81ca", + "process_life_frac_rptv": "d1b75ede9f90899b1c1cbc56489bf9d12c52abc6c0466eb5fe4dccaf6f3be800", "ramp_down_day_constraint_rpsdtv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "ramp_down_season_constraint_rpsstv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "ramp_up_day_constraint_rpsdtv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", diff --git a/tests/testing_data/utopia_sets.json b/tests/testing_data/utopia_sets.json index 628c177da..6d9adc1a5 100644 --- a/tests/testing_data/utopia_sets.json +++ b/tests/testing_data/utopia_sets.json @@ -31,6 +31,7 @@ "flow_in_storage_rpsditvo": "99eb864a26adcb6633c95462649bca3ef7096f67682702915d7769b54b5de386", "flow_var_annual_rpitvo": "4053866a304af0a6b9a1d293ddaf358dfee5b170dcb01d41e6bc52437908a98a", "flow_var_rpsditvo": "350739a59b14969da094cf1c95524134b9c9acd0989710456a8dcc6596d0a401", + "lifetime_tech_rt": "7b3703e1f021594993a54c55427a599ed03fc512cf34826b7a7670eb93edfb9a", "lifetime_process_rtv": "2cfc288b15f25957dfc70f6396d97ad655ecbed91c5a11582329749f1fb3dbd7", "limit_activity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "limit_activity_share_constraint_rpgg": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", @@ -38,13 +39,7 @@ "limit_annual_capacity_factor_constraint_rtvo": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "limit_capacity_constraint_rpt": "5a43de033187da68c612c5bcef6a76edb6ab9806464b6d865e46d289ccbbd815", "limit_capacity_share_constraint_rpgg": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", - "limit_degrowth_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", - "limit_degrowth_new_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", - "limit_degrowth_new_capacity_delta_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "limit_emission_constraint_rpe": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", - "limit_growth_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", - "limit_growth_new_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", - "limit_growth_new_capacity_delta_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "limit_new_capacity_constraint_rtv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "limit_new_capacity_share_constraint_rggv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "limit_resource_constraint_rt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", @@ -63,7 +58,7 @@ "new_capacity_var_rtv": "d4f1cc8b432075001befddab648d54d1f82a29099236948845393a193b6add5b", "operator": "74d830836f1399fb336a0432dde7d7bd36cffa3ff76b1c42d7945350cfb9bf91", "ordered_season_sequential": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", - "process_life_frac_rptv": "37ad263f78986f4d38c63278a0d1a5a85b3eb8b833e8bfabbbea254eaf3a3efb", + "process_life_frac_rptv": "63d17957ee2bebf664ffa6a39cea5e16ec65ad07db1df24dcea6b15bb9ebf589", "ramp_down_day_constraint_rpsdtv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "ramp_down_season_constraint_rpsstv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", "ramp_up_day_constraint_rpsdtv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", diff --git a/tests/testing_data/utopia_v3.sql b/tests/testing_data/utopia_v3.sql new file mode 100644 index 000000000..c480d93b6 --- /dev/null +++ b/tests/testing_data/utopia_v3.sql @@ -0,0 +1,1558 @@ +PRAGMA foreign_keys=OFF; +BEGIN TRANSACTION; +CREATE TABLE MetaData +( + element TEXT, + value INT, + notes TEXT, + PRIMARY KEY (element) +); +INSERT INTO MetaData VALUES('DB_MAJOR',3,'DB major version number'); +INSERT INTO MetaData VALUES('DB_MINOR',1,'DB minor version number'); +INSERT INTO MetaData VALUES ('days_per_period', 365, 'count of days in each period'); +CREATE TABLE MetaDataReal +( + element TEXT, + value REAL, + notes TEXT, + + PRIMARY KEY (element) +); +INSERT INTO MetaDataReal VALUES('default_loan_rate',0.05000000000000000277,'Default Loan Rate if not specified in LoanRate table'); +INSERT INTO MetaDataReal VALUES('global_discount_rate',0.05000000000000000277,''); +CREATE TABLE OutputDualVariable +( + scenario TEXT, + constraint_name TEXT, + dual REAL, + PRIMARY KEY (constraint_name, scenario) +); +CREATE TABLE OutputObjective +( + scenario TEXT, + objective_name TEXT, + total_system_cost REAL +); +CREATE TABLE SeasonLabel +( + season TEXT PRIMARY KEY, + notes TEXT +); +INSERT INTO SeasonLabel VALUES('inter',NULL); +INSERT INTO SeasonLabel VALUES('summer',NULL); +INSERT INTO SeasonLabel VALUES('winter',NULL); +CREATE TABLE SectorLabel +( + sector TEXT PRIMARY KEY, + notes TEXT +); +INSERT INTO SectorLabel VALUES('supply',NULL); +INSERT INTO SectorLabel VALUES('electric',NULL); +INSERT INTO SectorLabel VALUES('transport',NULL); +INSERT INTO SectorLabel VALUES('commercial',NULL); +INSERT INTO SectorLabel VALUES('residential',NULL); +INSERT INTO SectorLabel VALUES('industrial',NULL); +CREATE TABLE CapacityCredit +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER, + credit REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage), + CHECK (credit >= 0 AND credit <= 1) +); +CREATE TABLE CapacityFactorProcess +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tod TEXT + REFERENCES TimeOfDay (tod), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER, + factor REAL, + notes TEXT, + PRIMARY KEY (region, period, season, tod, tech, vintage), + CHECK (factor >= 0 AND factor <= 1) +); +INSERT INTO CapacityFactorProcess VALUES('utopia',2000,'inter','day','E31',2000,0.2752999999999999892,''); +INSERT INTO CapacityFactorProcess VALUES('utopia',2000,'inter','night','E31',2000,0.2752999999999999892,''); +INSERT INTO CapacityFactorProcess VALUES('utopia',2000,'winter','day','E31',2000,0.2752999999999999892,''); +INSERT INTO CapacityFactorProcess VALUES('utopia',2000,'winter','night','E31',2000,0.2752999999999999892,''); +INSERT INTO CapacityFactorProcess VALUES('utopia',2000,'summer','day','E31',2000,0.2752999999999999892,''); +INSERT INTO CapacityFactorProcess VALUES('utopia',2000,'summer','night','E31',2000,0.2752999999999999892,''); +INSERT INTO CapacityFactorProcess VALUES('utopia',2010,'inter','day','E31',2000,0.2752999999999999892,''); +INSERT INTO CapacityFactorProcess VALUES('utopia',2010,'inter','night','E31',2000,0.2752999999999999892,''); +INSERT INTO CapacityFactorProcess VALUES('utopia',2010,'winter','day','E31',2000,0.2752999999999999892,''); +INSERT INTO CapacityFactorProcess VALUES('utopia',2010,'winter','night','E31',2000,0.2752999999999999892,''); +INSERT INTO CapacityFactorProcess VALUES('utopia',2010,'summer','day','E31',2000,0.2752999999999999892,''); +INSERT INTO CapacityFactorProcess VALUES('utopia',2010,'summer','night','E31',2000,0.2752999999999999892,''); +INSERT INTO CapacityFactorProcess VALUES('utopia',2010,'inter','day','E31',2010,0.2756000000000000116,''); +INSERT INTO CapacityFactorProcess VALUES('utopia',2010,'inter','night','E31',2010,0.2756000000000000116,''); +INSERT INTO CapacityFactorProcess VALUES('utopia',2010,'winter','day','E31',2010,0.2756000000000000116,''); +INSERT INTO CapacityFactorProcess VALUES('utopia',2010,'winter','night','E31',2010,0.2756000000000000116,''); +INSERT INTO CapacityFactorProcess VALUES('utopia',2010,'summer','day','E31',2010,0.2756000000000000116,''); +INSERT INTO CapacityFactorProcess VALUES('utopia',2010,'summer','night','E31',2010,0.2756000000000000116,''); +CREATE TABLE CapacityFactorTech +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tod TEXT + REFERENCES TimeOfDay (tod), + tech TEXT + REFERENCES Technology (tech), + factor REAL, + notes TEXT, + PRIMARY KEY (region, period, season, tod, tech), + CHECK (factor >= 0 AND factor <= 1) +); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'inter','day','E01',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'inter','night','E01',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'winter','day','E01',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'winter','night','E01',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'summer','day','E01',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'summer','night','E01',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'inter','day','E21',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'inter','night','E21',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'winter','day','E21',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'winter','night','E21',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'summer','day','E21',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'summer','night','E21',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'inter','day','E31',0.2750000000000000222,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'inter','night','E31',0.2750000000000000222,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'winter','day','E31',0.2750000000000000222,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'winter','night','E31',0.2750000000000000222,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'summer','day','E31',0.2750000000000000222,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'summer','night','E31',0.2750000000000000222,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'inter','day','E51',0.1700000000000000122,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'inter','night','E51',0.1700000000000000122,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'winter','day','E51',0.1700000000000000122,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'winter','night','E51',0.1700000000000000122,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'summer','day','E51',0.1700000000000000122,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'summer','night','E51',0.1700000000000000122,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'inter','day','E70',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'inter','night','E70',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'winter','day','E70',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'winter','night','E70',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'summer','day','E70',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',1990,'summer','night','E70',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'inter','day','E01',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'inter','night','E01',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'winter','day','E01',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'winter','night','E01',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'summer','day','E01',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'summer','night','E01',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'inter','day','E21',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'inter','night','E21',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'winter','day','E21',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'winter','night','E21',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'summer','day','E21',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'summer','night','E21',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'inter','day','E31',0.2750000000000000222,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'inter','night','E31',0.2750000000000000222,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'winter','day','E31',0.2750000000000000222,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'winter','night','E31',0.2750000000000000222,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'summer','day','E31',0.2750000000000000222,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'summer','night','E31',0.2750000000000000222,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'inter','day','E51',0.1700000000000000122,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'inter','night','E51',0.1700000000000000122,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'winter','day','E51',0.1700000000000000122,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'winter','night','E51',0.1700000000000000122,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'summer','day','E51',0.1700000000000000122,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'summer','night','E51',0.1700000000000000122,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'inter','day','E70',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'inter','night','E70',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'winter','day','E70',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'winter','night','E70',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'summer','day','E70',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2000,'summer','night','E70',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'inter','day','E01',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'inter','night','E01',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'winter','day','E01',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'winter','night','E01',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'summer','day','E01',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'summer','night','E01',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'inter','day','E21',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'inter','night','E21',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'winter','day','E21',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'winter','night','E21',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'summer','day','E21',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'summer','night','E21',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'inter','day','E31',0.2750000000000000222,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'inter','night','E31',0.2750000000000000222,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'winter','day','E31',0.2750000000000000222,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'winter','night','E31',0.2750000000000000222,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'summer','day','E31',0.2750000000000000222,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'summer','night','E31',0.2750000000000000222,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'inter','day','E51',0.1700000000000000122,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'inter','night','E51',0.1700000000000000122,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'winter','day','E51',0.1700000000000000122,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'winter','night','E51',0.1700000000000000122,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'summer','day','E51',0.1700000000000000122,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'summer','night','E51',0.1700000000000000122,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'inter','day','E70',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'inter','night','E70',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'winter','day','E70',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'winter','night','E70',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'summer','day','E70',0.8000000000000000444,''); +INSERT INTO CapacityFactorTech VALUES('utopia',2010,'summer','night','E70',0.8000000000000000444,''); +CREATE TABLE CapacityToActivity +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + c2a REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +INSERT INTO CapacityToActivity VALUES('utopia','E01',31.53999999999999915,''); +INSERT INTO CapacityToActivity VALUES('utopia','E21',31.53999999999999915,''); +INSERT INTO CapacityToActivity VALUES('utopia','E31',31.53999999999999915,''); +INSERT INTO CapacityToActivity VALUES('utopia','E51',31.53999999999999915,''); +INSERT INTO CapacityToActivity VALUES('utopia','E70',31.53999999999999915,''); +INSERT INTO CapacityToActivity VALUES('utopia','RHE',1.0,''); +INSERT INTO CapacityToActivity VALUES('utopia','RHO',1.0,''); +INSERT INTO CapacityToActivity VALUES('utopia','RL1',1.0,''); +INSERT INTO CapacityToActivity VALUES('utopia','SRE',1.0,''); +INSERT INTO CapacityToActivity VALUES('utopia','TXD',1.0,''); +INSERT INTO CapacityToActivity VALUES('utopia','TXE',1.0,''); +INSERT INTO CapacityToActivity VALUES('utopia','TXG',1.0,''); +CREATE TABLE Commodity +( + name TEXT + PRIMARY KEY, + flag TEXT + REFERENCES CommodityType (label), + description TEXT +); +INSERT INTO Commodity VALUES('ethos','s','# dummy commodity to supply inputs (makes graph easier to read)'); +INSERT INTO Commodity VALUES('DSL','p','# diesel'); +INSERT INTO Commodity VALUES('ELC','p','# electricity'); +INSERT INTO Commodity VALUES('FEQ','p','# fossil equivalent'); +INSERT INTO Commodity VALUES('GSL','p','# gasoline'); +INSERT INTO Commodity VALUES('HCO','p','# coal'); +INSERT INTO Commodity VALUES('HYD','p','# water'); +INSERT INTO Commodity VALUES('OIL','p','# crude oil'); +INSERT INTO Commodity VALUES('URN','p','# uranium'); +INSERT INTO Commodity VALUES('co2','e','#CO2 emissions'); +INSERT INTO Commodity VALUES('nox','e','#NOX emissions'); +INSERT INTO Commodity VALUES('RH','d','# residential heating'); +INSERT INTO Commodity VALUES('RL','d','# residential lighting'); +INSERT INTO Commodity VALUES('TX','d','# transportation'); +CREATE TABLE CommodityType +( + label TEXT + PRIMARY KEY, + description TEXT +); +INSERT INTO CommodityType VALUES('w','waste commodity'); +INSERT INTO CommodityType VALUES('wa','waste annual commodity'); +INSERT INTO CommodityType VALUES('wp','waste physical commodity'); +INSERT INTO CommodityType VALUES('a','annual commodity'); +INSERT INTO CommodityType VALUES('s','source commodity'); +INSERT INTO CommodityType VALUES('p','physical commodity'); +INSERT INTO CommodityType VALUES('e','emissions commodity'); +INSERT INTO CommodityType VALUES('d','demand commodity'); +CREATE TABLE ConstructionInput +( + region TEXT, + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, input_comm, tech, vintage) +); +CREATE TABLE CostEmission +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + emis_comm TEXT NOT NULL + REFERENCES Commodity (name), + cost REAL NOT NULL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, emis_comm) +); +CREATE TABLE CostFixed +( + region TEXT NOT NULL, + period INTEGER NOT NULL + REFERENCES TimePeriod (period), + tech TEXT NOT NULL + REFERENCES Technology (tech), + vintage INTEGER NOT NULL + REFERENCES TimePeriod (period), + cost REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage) +); +INSERT INTO CostFixed VALUES('utopia',1990,'E01',1960,40.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'E01',1970,40.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'E01',1980,40.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'E01',1990,40.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'E01',1970,70.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'E01',1980,70.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'E01',1990,70.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'E01',2000,70.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'E01',1980,100.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'E01',1990,100.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'E01',2000,100.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'E01',2010,100.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'E21',1990,500.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'E21',1990,500.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'E21',1990,500.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'E21',2000,500.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'E21',2000,500.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'E21',2010,500.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'E31',1980,75.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'E31',1990,75.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'E31',1980,75.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'E31',1990,75.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'E31',2000,75.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'E31',1980,75.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'E31',1990,75.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'E31',2000,75.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'E31',2010,75.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'E51',1980,30.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'E51',1990,30.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'E51',1980,30.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'E51',1990,30.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'E51',2000,30.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'E51',1980,30.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'E51',1990,30.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'E51',2000,30.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'E51',2010,30.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'E70',1960,30.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'E70',1970,30.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'E70',1980,30.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'E70',1990,30.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'E70',1970,30.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'E70',1980,30.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'E70',1990,30.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'E70',2000,30.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'E70',1980,30.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'E70',1990,30.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'E70',2000,30.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'E70',2010,30.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'RHO',1970,1.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'RHO',1980,1.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'RHO',1990,1.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'RHO',1980,1.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'RHO',1990,1.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'RHO',2000,1.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'RHO',1990,1.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'RHO',2000,1.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'RHO',2010,1.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'RL1',1980,9.46000000000000086,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'RL1',1990,9.46000000000000086,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'RL1',2000,9.46000000000000086,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'RL1',2010,9.46000000000000086,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'TXD',1970,52.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'TXD',1980,52.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'TXD',1990,52.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'TXD',1980,52.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'TXD',1990,52.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'TXD',2000,52.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'TXD',2000,52.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'TXD',2010,52.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'TXE',1990,100.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'TXE',1990,90.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'TXE',2000,90.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'TXE',2000,80.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'TXE',2010,80.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'TXG',1970,48.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'TXG',1980,48.0,'',''); +INSERT INTO CostFixed VALUES('utopia',1990,'TXG',1990,48.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'TXG',1980,48.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'TXG',1990,48.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2000,'TXG',2000,48.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'TXG',2000,48.0,'',''); +INSERT INTO CostFixed VALUES('utopia',2010,'TXG',2010,48.0,'',''); +CREATE TABLE CostInvest +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + cost REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +INSERT INTO CostInvest VALUES('utopia','E01',1990,2000.0,'',''); +INSERT INTO CostInvest VALUES('utopia','E01',2000,1300.0,'',''); +INSERT INTO CostInvest VALUES('utopia','E01',2010,1200.0,'',''); +INSERT INTO CostInvest VALUES('utopia','E21',1990,5000.0,'',''); +INSERT INTO CostInvest VALUES('utopia','E21',2000,5000.0,'',''); +INSERT INTO CostInvest VALUES('utopia','E21',2010,5000.0,'',''); +INSERT INTO CostInvest VALUES('utopia','E31',1990,3000.0,'',''); +INSERT INTO CostInvest VALUES('utopia','E31',2000,3000.0,'',''); +INSERT INTO CostInvest VALUES('utopia','E31',2010,3000.0,'',''); +INSERT INTO CostInvest VALUES('utopia','E51',1990,900.0,'',''); +INSERT INTO CostInvest VALUES('utopia','E51',2000,900.0,'',''); +INSERT INTO CostInvest VALUES('utopia','E51',2010,900.0,'',''); +INSERT INTO CostInvest VALUES('utopia','E70',1990,1000.0,'',''); +INSERT INTO CostInvest VALUES('utopia','E70',2000,1000.0,'',''); +INSERT INTO CostInvest VALUES('utopia','E70',2010,1000.0,'',''); +INSERT INTO CostInvest VALUES('utopia','RHE',1990,90.0,'',''); +INSERT INTO CostInvest VALUES('utopia','RHE',2000,90.0,'',''); +INSERT INTO CostInvest VALUES('utopia','RHE',2010,90.0,'',''); +INSERT INTO CostInvest VALUES('utopia','RHO',1990,100.0,'',''); +INSERT INTO CostInvest VALUES('utopia','RHO',2000,100.0,'',''); +INSERT INTO CostInvest VALUES('utopia','RHO',2010,100.0,'',''); +INSERT INTO CostInvest VALUES('utopia','SRE',1990,100.0,'',''); +INSERT INTO CostInvest VALUES('utopia','SRE',2000,100.0,'',''); +INSERT INTO CostInvest VALUES('utopia','SRE',2010,100.0,'',''); +INSERT INTO CostInvest VALUES('utopia','TXD',1990,1044.0,'',''); +INSERT INTO CostInvest VALUES('utopia','TXD',2000,1044.0,'',''); +INSERT INTO CostInvest VALUES('utopia','TXD',2010,1044.0,'',''); +INSERT INTO CostInvest VALUES('utopia','TXE',1990,2000.0,'',''); +INSERT INTO CostInvest VALUES('utopia','TXE',2000,1750.0,'',''); +INSERT INTO CostInvest VALUES('utopia','TXE',2010,1500.0,'',''); +INSERT INTO CostInvest VALUES('utopia','TXG',1990,1044.0,'',''); +INSERT INTO CostInvest VALUES('utopia','TXG',2000,1044.0,'',''); +INSERT INTO CostInvest VALUES('utopia','TXG',2010,1044.0,'',''); +CREATE TABLE CostVariable +( + region TEXT NOT NULL, + period INTEGER NOT NULL + REFERENCES TimePeriod (period), + tech TEXT NOT NULL + REFERENCES Technology (tech), + vintage INTEGER NOT NULL + REFERENCES TimePeriod (period), + cost REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage) +); +INSERT INTO CostVariable VALUES('utopia',1990,'IMPDSL1',1990,10.0,'',''); +INSERT INTO CostVariable VALUES('utopia',2000,'IMPDSL1',1990,10.0,'',''); +INSERT INTO CostVariable VALUES('utopia',2010,'IMPDSL1',1990,10.0,'',''); +INSERT INTO CostVariable VALUES('utopia',1990,'IMPGSL1',1990,15.0,'',''); +INSERT INTO CostVariable VALUES('utopia',2000,'IMPGSL1',1990,15.0,'',''); +INSERT INTO CostVariable VALUES('utopia',2010,'IMPGSL1',1990,15.0,'',''); +INSERT INTO CostVariable VALUES('utopia',1990,'IMPHCO1',1990,2.0,'',''); +INSERT INTO CostVariable VALUES('utopia',2000,'IMPHCO1',1990,2.0,'',''); +INSERT INTO CostVariable VALUES('utopia',2010,'IMPHCO1',1990,2.0,'',''); +INSERT INTO CostVariable VALUES('utopia',1990,'IMPOIL1',1990,8.0,'',''); +INSERT INTO CostVariable VALUES('utopia',2000,'IMPOIL1',1990,8.0,'',''); +INSERT INTO CostVariable VALUES('utopia',2010,'IMPOIL1',1990,8.0,'',''); +INSERT INTO CostVariable VALUES('utopia',1990,'IMPURN1',1990,2.0,'',''); +INSERT INTO CostVariable VALUES('utopia',2000,'IMPURN1',1990,2.0,'',''); +INSERT INTO CostVariable VALUES('utopia',2010,'IMPURN1',1990,2.0,'',''); +INSERT INTO CostVariable VALUES('utopia',1990,'E01',1960,0.2999999999999999889,'',''); +INSERT INTO CostVariable VALUES('utopia',1990,'E01',1970,0.2999999999999999889,'',''); +INSERT INTO CostVariable VALUES('utopia',1990,'E01',1980,0.2999999999999999889,'',''); +INSERT INTO CostVariable VALUES('utopia',1990,'E01',1990,0.2999999999999999889,'',''); +INSERT INTO CostVariable VALUES('utopia',2000,'E01',1970,0.2999999999999999889,'',''); +INSERT INTO CostVariable VALUES('utopia',2000,'E01',1980,0.2999999999999999889,'',''); +INSERT INTO CostVariable VALUES('utopia',2000,'E01',1990,0.2999999999999999889,'',''); +INSERT INTO CostVariable VALUES('utopia',2000,'E01',2000,0.2999999999999999889,'',''); +INSERT INTO CostVariable VALUES('utopia',2010,'E01',1980,0.2999999999999999889,'',''); +INSERT INTO CostVariable VALUES('utopia',2010,'E01',1990,0.2999999999999999889,'',''); +INSERT INTO CostVariable VALUES('utopia',2010,'E01',2000,0.2999999999999999889,'',''); +INSERT INTO CostVariable VALUES('utopia',2010,'E01',2010,0.2999999999999999889,'',''); +INSERT INTO CostVariable VALUES('utopia',1990,'E21',1990,1.5,'',''); +INSERT INTO CostVariable VALUES('utopia',2000,'E21',1990,1.5,'',''); +INSERT INTO CostVariable VALUES('utopia',2010,'E21',1990,1.5,'',''); +INSERT INTO CostVariable VALUES('utopia',2000,'E21',2000,1.5,'',''); +INSERT INTO CostVariable VALUES('utopia',2010,'E21',2000,1.5,'',''); +INSERT INTO CostVariable VALUES('utopia',2010,'E21',2010,1.5,'',''); +INSERT INTO CostVariable VALUES('utopia',1990,'E70',1960,0.4000000000000000222,'',''); +INSERT INTO CostVariable VALUES('utopia',1990,'E70',1970,0.4000000000000000222,'',''); +INSERT INTO CostVariable VALUES('utopia',1990,'E70',1980,0.4000000000000000222,'',''); +INSERT INTO CostVariable VALUES('utopia',1990,'E70',1990,0.4000000000000000222,'',''); +INSERT INTO CostVariable VALUES('utopia',2000,'E70',1970,0.4000000000000000222,'',''); +INSERT INTO CostVariable VALUES('utopia',2000,'E70',1980,0.4000000000000000222,'',''); +INSERT INTO CostVariable VALUES('utopia',2000,'E70',1990,0.4000000000000000222,'',''); +INSERT INTO CostVariable VALUES('utopia',2000,'E70',2000,0.4000000000000000222,'',''); +INSERT INTO CostVariable VALUES('utopia',2010,'E70',1980,0.4000000000000000222,'',''); +INSERT INTO CostVariable VALUES('utopia',2010,'E70',1990,0.4000000000000000222,'',''); +INSERT INTO CostVariable VALUES('utopia',2010,'E70',2000,0.4000000000000000222,'',''); +INSERT INTO CostVariable VALUES('utopia',2010,'E70',2010,0.4000000000000000222,'',''); +INSERT INTO CostVariable VALUES('utopia',1990,'SRE',1990,10.0,'',''); +INSERT INTO CostVariable VALUES('utopia',2000,'SRE',1990,10.0,'',''); +INSERT INTO CostVariable VALUES('utopia',2000,'SRE',2000,10.0,'',''); +INSERT INTO CostVariable VALUES('utopia',2010,'SRE',1990,10.0,'',''); +INSERT INTO CostVariable VALUES('utopia',2010,'SRE',2000,10.0,'',''); +INSERT INTO CostVariable VALUES('utopia',2010,'SRE',2010,10.0,'',''); +CREATE TABLE Demand +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + commodity TEXT + REFERENCES Commodity (name), + demand REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, commodity) +); +INSERT INTO Demand VALUES('utopia',1990,'RH',25.19999999999999929,'',''); +INSERT INTO Demand VALUES('utopia',2000,'RH',37.79999999999999715,'',''); +INSERT INTO Demand VALUES('utopia',2010,'RH',56.69999999999999574,'',''); +INSERT INTO Demand VALUES('utopia',1990,'RL',5.599999999999999645,'',''); +INSERT INTO Demand VALUES('utopia',2000,'RL',8.400000000000000355,'',''); +INSERT INTO Demand VALUES('utopia',2010,'RL',12.59999999999999965,'',''); +INSERT INTO Demand VALUES('utopia',1990,'TX',5.200000000000000177,'',''); +INSERT INTO Demand VALUES('utopia',2000,'TX',7.799999999999999823,'',''); +INSERT INTO Demand VALUES('utopia',2010,'TX',11.68999999999999951,'',''); +CREATE TABLE DemandSpecificDistribution +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tod TEXT + REFERENCES TimeOfDay (tod), + demand_name TEXT + REFERENCES Commodity (name), + dsd REAL, + notes TEXT, + PRIMARY KEY (region, period, season, tod, demand_name), + CHECK (dsd >= 0 AND dsd <= 1) +); +INSERT INTO DemandSpecificDistribution VALUES('utopia',1990,'inter','day','RH',0.1199999999999999956,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',1990,'inter','night','RH',0.05999999999999999778,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',1990,'winter','day','RH',0.5466999999999999638,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',1990,'winter','night','RH',0.2732999999999999874,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',1990,'inter','day','RL',0.1499999999999999945,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',1990,'inter','night','RL',0.05000000000000000277,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',1990,'summer','day','RL',0.1499999999999999945,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',1990,'summer','night','RL',0.05000000000000000277,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',1990,'winter','day','RL',0.5,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',1990,'winter','night','RL',0.1000000000000000055,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',2000,'inter','day','RH',0.1199999999999999956,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',2000,'inter','night','RH',0.05999999999999999778,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',2000,'winter','day','RH',0.5466999999999999638,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',2000,'winter','night','RH',0.2732999999999999874,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',2000,'inter','day','RL',0.1499999999999999945,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',2000,'inter','night','RL',0.05000000000000000277,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',2000,'summer','day','RL',0.1499999999999999945,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',2000,'summer','night','RL',0.05000000000000000277,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',2000,'winter','day','RL',0.5,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',2000,'winter','night','RL',0.1000000000000000055,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',2010,'inter','day','RH',0.1199999999999999956,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',2010,'inter','night','RH',0.05999999999999999778,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',2010,'winter','day','RH',0.5466999999999999638,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',2010,'winter','night','RH',0.2732999999999999874,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',2010,'inter','day','RL',0.1499999999999999945,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',2010,'inter','night','RL',0.05000000000000000277,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',2010,'summer','day','RL',0.1499999999999999945,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',2010,'summer','night','RL',0.05000000000000000277,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',2010,'winter','day','RL',0.5,''); +INSERT INTO DemandSpecificDistribution VALUES('utopia',2010,'winter','night','RL',0.1000000000000000055,''); +CREATE TABLE EndOfLifeOutput +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage, output_comm) +); +CREATE TABLE Efficiency +( + region TEXT, + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + efficiency REAL, + notes TEXT, + PRIMARY KEY (region, input_comm, tech, vintage, output_comm), + CHECK (efficiency > 0) +); +INSERT INTO Efficiency VALUES('utopia','ethos','IMPDSL1',1990,'DSL',1.0,''); +INSERT INTO Efficiency VALUES('utopia','ethos','IMPGSL1',1990,'GSL',1.0,''); +INSERT INTO Efficiency VALUES('utopia','ethos','IMPHCO1',1990,'HCO',1.0,''); +INSERT INTO Efficiency VALUES('utopia','ethos','IMPOIL1',1990,'OIL',1.0,''); +INSERT INTO Efficiency VALUES('utopia','ethos','IMPURN1',1990,'URN',1.0,''); +INSERT INTO Efficiency VALUES('utopia','ethos','IMPFEQ',1990,'FEQ',1.0,''); +INSERT INTO Efficiency VALUES('utopia','ethos','IMPHYD',1990,'HYD',1.0,''); +INSERT INTO Efficiency VALUES('utopia','HCO','E01',1960,'ELC',0.3200000000000000066,'# 1/3.125'); +INSERT INTO Efficiency VALUES('utopia','HCO','E01',1970,'ELC',0.3200000000000000066,'# 1/3.125'); +INSERT INTO Efficiency VALUES('utopia','HCO','E01',1980,'ELC',0.3200000000000000066,'# 1/3.125'); +INSERT INTO Efficiency VALUES('utopia','HCO','E01',1990,'ELC',0.3200000000000000066,'# 1/3.125'); +INSERT INTO Efficiency VALUES('utopia','HCO','E01',2000,'ELC',0.3200000000000000066,'# 1/3.125'); +INSERT INTO Efficiency VALUES('utopia','HCO','E01',2010,'ELC',0.3200000000000000066,'# 1/3.125'); +INSERT INTO Efficiency VALUES('utopia','FEQ','E21',1990,'ELC',0.3200000000000000066,'# 1/3.125'); +INSERT INTO Efficiency VALUES('utopia','FEQ','E21',2000,'ELC',0.3200000000000000066,'# 1/3.125'); +INSERT INTO Efficiency VALUES('utopia','FEQ','E21',2010,'ELC',0.3200000000000000066,'# 1/3.125'); +INSERT INTO Efficiency VALUES('utopia','URN','E21',1990,'ELC',0.4000000000000000222,'# 1/2.5'); +INSERT INTO Efficiency VALUES('utopia','URN','E21',2000,'ELC',0.4000000000000000222,'# 1/2.5'); +INSERT INTO Efficiency VALUES('utopia','URN','E21',2010,'ELC',0.4000000000000000222,'# 1/2.5'); +INSERT INTO Efficiency VALUES('utopia','HYD','E31',1980,'ELC',0.3200000000000000066,'# 1/3.125'); +INSERT INTO Efficiency VALUES('utopia','HYD','E31',1990,'ELC',0.3200000000000000066,'# 1/3.125'); +INSERT INTO Efficiency VALUES('utopia','HYD','E31',2000,'ELC',0.3200000000000000066,'# 1/3.125'); +INSERT INTO Efficiency VALUES('utopia','HYD','E31',2010,'ELC',0.3200000000000000066,'# 1/3.125'); +INSERT INTO Efficiency VALUES('utopia','DSL','E70',1960,'ELC',0.2939999999999999836,'# 1/3.4'); +INSERT INTO Efficiency VALUES('utopia','DSL','E70',1970,'ELC',0.2939999999999999836,'# 1/3.4'); +INSERT INTO Efficiency VALUES('utopia','DSL','E70',1980,'ELC',0.2939999999999999836,'# 1/3.4'); +INSERT INTO Efficiency VALUES('utopia','DSL','E70',1990,'ELC',0.2939999999999999836,'# 1/3.4'); +INSERT INTO Efficiency VALUES('utopia','DSL','E70',2000,'ELC',0.2939999999999999836,'# 1/3.4'); +INSERT INTO Efficiency VALUES('utopia','DSL','E70',2010,'ELC',0.2939999999999999836,'# 1/3.4'); +INSERT INTO Efficiency VALUES('utopia','ELC','E51',1980,'ELC',0.7199999999999999734,'# 1/1.3889'); +INSERT INTO Efficiency VALUES('utopia','ELC','E51',1990,'ELC',0.7199999999999999734,'# 1/1.3889'); +INSERT INTO Efficiency VALUES('utopia','ELC','E51',2000,'ELC',0.7199999999999999734,'# 1/1.3889'); +INSERT INTO Efficiency VALUES('utopia','ELC','E51',2010,'ELC',0.7199999999999999734,'# 1/1.3889'); +INSERT INTO Efficiency VALUES('utopia','ELC','RHE',1990,'RH',1.0,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','ELC','RHE',2000,'RH',1.0,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','ELC','RHE',2010,'RH',1.0,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','DSL','RHO',1970,'RH',0.6999999999999999556,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','DSL','RHO',1980,'RH',0.6999999999999999556,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','DSL','RHO',1990,'RH',0.6999999999999999556,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','DSL','RHO',2000,'RH',0.6999999999999999556,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','DSL','RHO',2010,'RH',0.6999999999999999556,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','ELC','RL1',1980,'RL',1.0,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','ELC','RL1',1990,'RL',1.0,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','ELC','RL1',2000,'RL',1.0,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','ELC','RL1',2010,'RL',1.0,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','OIL','SRE',1990,'DSL',1.0,'# direct translation from PRC_INP2, PRC_OUT'); +INSERT INTO Efficiency VALUES('utopia','OIL','SRE',2000,'DSL',1.0,'# direct translation from PRC_INP2, PRC_OUT'); +INSERT INTO Efficiency VALUES('utopia','OIL','SRE',2010,'DSL',1.0,'# direct translation from PRC_INP2, PRC_OUT'); +INSERT INTO Efficiency VALUES('utopia','OIL','SRE',1990,'GSL',1.0,'# direct translation from PRC_INP2, PRC_OUT'); +INSERT INTO Efficiency VALUES('utopia','OIL','SRE',2000,'GSL',1.0,'# direct translation from PRC_INP2, PRC_OUT'); +INSERT INTO Efficiency VALUES('utopia','OIL','SRE',2010,'GSL',1.0,'# direct translation from PRC_INP2, PRC_OUT'); +INSERT INTO Efficiency VALUES('utopia','DSL','TXD',1970,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','DSL','TXD',1980,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','DSL','TXD',1990,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','DSL','TXD',2000,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','DSL','TXD',2010,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','ELC','TXE',1990,'TX',0.8269999999999999574,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','ELC','TXE',2000,'TX',0.8269999999999999574,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','ELC','TXE',2010,'TX',0.8269999999999999574,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','GSL','TXG',1970,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','GSL','TXG',1980,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','GSL','TXG',1990,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','GSL','TXG',2000,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); +INSERT INTO Efficiency VALUES('utopia','GSL','TXG',2010,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); +CREATE TABLE EfficiencyVariable +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tod TEXT + REFERENCES TimeOfDay (tod), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + efficiency REAL, + notes TEXT, + PRIMARY KEY (region, period, season, tod, input_comm, tech, vintage, output_comm), + CHECK (efficiency > 0) +); +CREATE TABLE EmissionActivity +( + region TEXT, + emis_comm TEXT + REFERENCES Commodity (name), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + activity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, emis_comm, input_comm, tech, vintage, output_comm) +); +INSERT INTO EmissionActivity VALUES('utopia','co2','ethos','IMPDSL1',1990,'DSL',0.07499999999999999723,'',''); +INSERT INTO EmissionActivity VALUES('utopia','co2','ethos','IMPGSL1',1990,'GSL',0.07499999999999999723,'',''); +INSERT INTO EmissionActivity VALUES('utopia','co2','ethos','IMPHCO1',1990,'HCO',0.0889999999999999819,'',''); +INSERT INTO EmissionActivity VALUES('utopia','co2','ethos','IMPOIL1',1990,'OIL',0.07499999999999999723,'',''); +INSERT INTO EmissionActivity VALUES('utopia','nox','DSL','TXD',1970,'TX',1.0,'',''); +INSERT INTO EmissionActivity VALUES('utopia','nox','DSL','TXD',1980,'TX',1.0,'',''); +INSERT INTO EmissionActivity VALUES('utopia','nox','DSL','TXD',1990,'TX',1.0,'',''); +INSERT INTO EmissionActivity VALUES('utopia','nox','DSL','TXD',2000,'TX',1.0,'',''); +INSERT INTO EmissionActivity VALUES('utopia','nox','DSL','TXD',2010,'TX',1.0,'',''); +INSERT INTO EmissionActivity VALUES('utopia','nox','GSL','TXG',1970,'TX',1.0,'',''); +INSERT INTO EmissionActivity VALUES('utopia','nox','GSL','TXG',1980,'TX',1.0,'',''); +INSERT INTO EmissionActivity VALUES('utopia','nox','GSL','TXG',1990,'TX',1.0,'',''); +INSERT INTO EmissionActivity VALUES('utopia','nox','GSL','TXG',2000,'TX',1.0,'',''); +INSERT INTO EmissionActivity VALUES('utopia','nox','GSL','TXG',2010,'TX',1.0,'',''); +CREATE TABLE EmissionEmbodied +( + region TEXT, + emis_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, emis_comm, tech, vintage) +); +CREATE TABLE EmissionEndOfLife +( + region TEXT, + emis_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, emis_comm, tech, vintage) +); +CREATE TABLE ExistingCapacity +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + capacity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +INSERT INTO ExistingCapacity VALUES('utopia','E01',1960,0.1749999999999999889,'',''); +INSERT INTO ExistingCapacity VALUES('utopia','E01',1970,0.1749999999999999889,'',''); +INSERT INTO ExistingCapacity VALUES('utopia','E01',1980,0.1499999999999999945,'',''); +INSERT INTO ExistingCapacity VALUES('utopia','E31',1980,0.1000000000000000055,'',''); +INSERT INTO ExistingCapacity VALUES('utopia','E51',1980,0.5,'',''); +INSERT INTO ExistingCapacity VALUES('utopia','E70',1960,0.05000000000000000277,'',''); +INSERT INTO ExistingCapacity VALUES('utopia','E70',1970,0.05000000000000000277,'',''); +INSERT INTO ExistingCapacity VALUES('utopia','E70',1980,0.2000000000000000111,'',''); +INSERT INTO ExistingCapacity VALUES('utopia','RHO',1970,12.5,'',''); +INSERT INTO ExistingCapacity VALUES('utopia','RHO',1980,12.5,'',''); +INSERT INTO ExistingCapacity VALUES('utopia','RL1',1980,5.599999999999999645,'',''); +INSERT INTO ExistingCapacity VALUES('utopia','TXD',1970,0.4000000000000000222,'',''); +INSERT INTO ExistingCapacity VALUES('utopia','TXD',1980,0.2000000000000000111,'',''); +INSERT INTO ExistingCapacity VALUES('utopia','TXG',1970,3.100000000000000088,'',''); +INSERT INTO ExistingCapacity VALUES('utopia','TXG',1980,1.5,'',''); +CREATE TABLE TechGroup +( + group_name TEXT + PRIMARY KEY, + notes TEXT +); +CREATE TABLE LoanLifetimeProcess +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + lifetime REAL, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE LoanRate +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + rate REAL, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE LifetimeProcess +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + lifetime REAL, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +INSERT INTO LifetimeProcess VALUES('utopia','RL1',1980,20.0,'#forexistingcap'); +INSERT INTO LifetimeProcess VALUES('utopia','TXD',1970,30.0,'#forexistingcap'); +INSERT INTO LifetimeProcess VALUES('utopia','TXD',1980,30.0,'#forexistingcap'); +INSERT INTO LifetimeProcess VALUES('utopia','TXG',1970,30.0,'#forexistingcap'); +INSERT INTO LifetimeProcess VALUES('utopia','TXG',1980,30.0,'#forexistingcap'); +CREATE TABLE LifetimeTech +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + lifetime REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +INSERT INTO LifetimeTech VALUES('utopia','E01',40.0,''); +INSERT INTO LifetimeTech VALUES('utopia','E21',40.0,''); +INSERT INTO LifetimeTech VALUES('utopia','E31',100.0,''); +INSERT INTO LifetimeTech VALUES('utopia','E51',100.0,''); +INSERT INTO LifetimeTech VALUES('utopia','E70',40.0,''); +INSERT INTO LifetimeTech VALUES('utopia','RHE',30.0,''); +INSERT INTO LifetimeTech VALUES('utopia','RHO',30.0,''); +INSERT INTO LifetimeTech VALUES('utopia','RL1',10.0,''); +INSERT INTO LifetimeTech VALUES('utopia','SRE',50.0,''); +INSERT INTO LifetimeTech VALUES('utopia','TXD',15.0,''); +INSERT INTO LifetimeTech VALUES('utopia','TXE',15.0,''); +INSERT INTO LifetimeTech VALUES('utopia','TXG',15.0,''); +INSERT INTO LifetimeTech VALUES('utopia','IMPDSL1',1000.0,''); +INSERT INTO LifetimeTech VALUES('utopia','IMPGSL1',1000.0,''); +INSERT INTO LifetimeTech VALUES('utopia','IMPHCO1',1000.0,''); +INSERT INTO LifetimeTech VALUES('utopia','IMPOIL1',1000.0,''); +INSERT INTO LifetimeTech VALUES('utopia','IMPURN1',1000.0,''); +INSERT INTO LifetimeTech VALUES('utopia','IMPHYD',1000.0,''); +INSERT INTO LifetimeTech VALUES('utopia','IMPFEQ',1000.0,''); +CREATE TABLE Operator +( + operator TEXT PRIMARY KEY, + notes TEXT +); +INSERT INTO Operator VALUES('e','equal to'); +INSERT INTO Operator VALUES('le','less than or equal to'); +INSERT INTO Operator VALUES('ge','greater than or equal to'); +CREATE TABLE LimitGrowthCapacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE LimitDegrowthCapacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE LimitGrowthNewCapacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE LimitDegrowthNewCapacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE LimitGrowthNewCapacityDelta +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE LimitDegrowthNewCapacityDelta +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE LimitStorageLevelFraction +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tod TEXT + REFERENCES TimeOfDay (tod), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + fraction REAL, + notes TEXT, + PRIMARY KEY(region, period, season, tod, tech, vintage, operator) +); +CREATE TABLE LimitActivity +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + activity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech_or_group, operator) +); +CREATE TABLE LimitActivityShare +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + sub_group TEXT, + super_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + share REAL, + notes TEXT, + PRIMARY KEY (region, period, sub_group, super_group, operator) +); +CREATE TABLE LimitAnnualCapacityFactor +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + factor REAL, + notes TEXT, + PRIMARY KEY (region, tech, vintage, operator), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE LimitCapacity +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + capacity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech_or_group, operator) +); +INSERT INTO LimitCapacity VALUES('utopia',1990,'E31','ge',0.1300000000000000044,'',''); +INSERT INTO LimitCapacity VALUES('utopia',2000,'E31','ge',0.1300000000000000044,'',''); +INSERT INTO LimitCapacity VALUES('utopia',2010,'E31','ge',0.1300000000000000044,'',''); +INSERT INTO LimitCapacity VALUES('utopia',1990,'SRE','ge',0.1000000000000000055,'',''); +INSERT INTO LimitCapacity VALUES('utopia',1990,'E31','le',0.1300000000000000044,'',''); +INSERT INTO LimitCapacity VALUES('utopia',2000,'E31','le',0.1700000000000000122,'',''); +INSERT INTO LimitCapacity VALUES('utopia',2010,'E31','le',0.21000000000000002,'',''); +INSERT INTO LimitCapacity VALUES('utopia',1990,'RHE','le',0.0,'',''); +INSERT INTO LimitCapacity VALUES('utopia',1990,'TXD','le',0.5999999999999999778,'',''); +INSERT INTO LimitCapacity VALUES('utopia',2000,'TXD','le',1.760000000000000008,'',''); +INSERT INTO LimitCapacity VALUES('utopia',2010,'TXD','le',4.759999999999999787,'',''); +CREATE TABLE LimitCapacityShare +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + sub_group TEXT, + super_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + share REAL, + notes TEXT, + PRIMARY KEY (region, period, sub_group, super_group, operator) +); +CREATE TABLE LimitNewCapacity +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + new_cap REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech_or_group, operator) +); +CREATE TABLE LimitNewCapacityShare +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + sub_group TEXT, + super_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + share REAL, + notes TEXT, + PRIMARY KEY (region, period, sub_group, super_group, operator) +); +CREATE TABLE LimitResource +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + cum_act REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE LimitSeasonalCapacityFactor +( + region TEXT + REFERENCES Region (region), + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tech TEXT + REFERENCES Technology (tech), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + factor REAL, + notes TEXT, + PRIMARY KEY(region, period, season, tech, operator) +); +CREATE TABLE LimitTechInputSplit +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, input_comm, tech, operator) +); +CREATE TABLE LimitTechInputSplitAnnual +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, input_comm, tech, operator) +); +CREATE TABLE LimitTechOutputSplit +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + output_comm TEXT + REFERENCES Commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, output_comm, operator) +); +INSERT INTO LimitTechOutputSplit VALUES('utopia',1990,'SRE','DSL','ge',0.6999999999999999556,''); +INSERT INTO LimitTechOutputSplit VALUES('utopia',2000,'SRE','DSL','ge',0.6999999999999999556,''); +INSERT INTO LimitTechOutputSplit VALUES('utopia',2010,'SRE','DSL','ge',0.6999999999999999556,''); +INSERT INTO LimitTechOutputSplit VALUES('utopia',1990,'SRE','GSL','ge',0.2999999999999999889,''); +INSERT INTO LimitTechOutputSplit VALUES('utopia',2000,'SRE','GSL','ge',0.2999999999999999889,''); +INSERT INTO LimitTechOutputSplit VALUES('utopia',2010,'SRE','GSL','ge',0.2999999999999999889,''); +CREATE TABLE LimitTechOutputSplitAnnual +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + output_comm TEXT + REFERENCES Commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, output_comm, operator) +); +CREATE TABLE LimitEmission +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + emis_comm TEXT + REFERENCES Commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, emis_comm, operator) +); +CREATE TABLE LinkedTech +( + primary_region TEXT, + primary_tech TEXT + REFERENCES Technology (tech), + emis_comm TEXT + REFERENCES Commodity (name), + driven_tech TEXT + REFERENCES Technology (tech), + notes TEXT, + PRIMARY KEY (primary_region, primary_tech, emis_comm) +); +CREATE TABLE OutputCurtailment +( + scenario TEXT, + region TEXT, + sector TEXT, + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES TimePeriod (period), + tod TEXT + REFERENCES TimeOfDay (tod), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + curtailment REAL, + PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) +); +CREATE TABLE OutputNetCapacity +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES SectorLabel (sector), + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + capacity REAL, + PRIMARY KEY (region, scenario, period, tech, vintage) +); +CREATE TABLE OutputBuiltCapacity +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES SectorLabel (sector), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + capacity REAL, + PRIMARY KEY (region, scenario, tech, vintage) +); +CREATE TABLE OutputRetiredCapacity +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES SectorLabel (sector), + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + cap_eol REAL, + cap_early REAL, + PRIMARY KEY (region, scenario, period, tech, vintage) +); +CREATE TABLE OutputFlowIn +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES SectorLabel (sector), + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tod TEXT + REFERENCES TimeOfDay (tod), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + flow REAL, + PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) +); +CREATE TABLE OutputFlowOut +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES SectorLabel (sector), + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tod TEXT + REFERENCES TimeOfDay (tod), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + flow REAL, + PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) +); +CREATE TABLE OutputStorageLevel +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES SectorLabel (sector), + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tod TEXT + REFERENCES TimeOfDay (tod), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + level REAL, + PRIMARY KEY (scenario, region, period, season, tod, tech, vintage) +); +CREATE TABLE PlanningReserveMargin +( + region TEXT + PRIMARY KEY + REFERENCES Region (region), + margin REAL, + notes TEXT +); +CREATE TABLE RampDownHourly +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + rate REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE RampUpHourly +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + rate REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE Region +( + region TEXT + PRIMARY KEY, + notes TEXT +); +INSERT INTO Region VALUES('utopia',NULL); +CREATE TABLE ReserveCapacityDerate +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER, + factor REAL, + notes TEXT, + PRIMARY KEY (region, period, season, tech, vintage), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE TimeSegmentFraction +( + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tod TEXT + REFERENCES TimeOfDay (tod), + segfrac REAL, + notes TEXT, + PRIMARY KEY (period, season, tod), + CHECK (segfrac >= 0 AND segfrac <= 1) +); +INSERT INTO TimeSegmentFraction VALUES(1990,'inter','day',0.166699999999999987,'# I-D'); +INSERT INTO TimeSegmentFraction VALUES(1990,'inter','night',0.08329999999999999905,'# I-N'); +INSERT INTO TimeSegmentFraction VALUES(1990,'summer','day',0.166699999999999987,'# S-D'); +INSERT INTO TimeSegmentFraction VALUES(1990,'summer','night',0.08329999999999999905,'# S-N'); +INSERT INTO TimeSegmentFraction VALUES(1990,'winter','day',0.3332999999999999852,'# W-D'); +INSERT INTO TimeSegmentFraction VALUES(1990,'winter','night',0.166699999999999987,'# W-N'); +INSERT INTO TimeSegmentFraction VALUES(2000,'inter','day',0.166699999999999987,'# I-D'); +INSERT INTO TimeSegmentFraction VALUES(2000,'inter','night',0.08329999999999999905,'# I-N'); +INSERT INTO TimeSegmentFraction VALUES(2000,'summer','day',0.166699999999999987,'# S-D'); +INSERT INTO TimeSegmentFraction VALUES(2000,'summer','night',0.08329999999999999905,'# S-N'); +INSERT INTO TimeSegmentFraction VALUES(2000,'winter','day',0.3332999999999999852,'# W-D'); +INSERT INTO TimeSegmentFraction VALUES(2000,'winter','night',0.166699999999999987,'# W-N'); +INSERT INTO TimeSegmentFraction VALUES(2010,'inter','day',0.166699999999999987,'# I-D'); +INSERT INTO TimeSegmentFraction VALUES(2010,'inter','night',0.08329999999999999905,'# I-N'); +INSERT INTO TimeSegmentFraction VALUES(2010,'summer','day',0.166699999999999987,'# S-D'); +INSERT INTO TimeSegmentFraction VALUES(2010,'summer','night',0.08329999999999999905,'# S-N'); +INSERT INTO TimeSegmentFraction VALUES(2010,'winter','day',0.3332999999999999852,'# W-D'); +INSERT INTO TimeSegmentFraction VALUES(2010,'winter','night',0.166699999999999987,'# W-N'); +CREATE TABLE StorageDuration +( + region TEXT, + tech TEXT, + duration REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE LifetimeSurvivalCurve +( + region TEXT NOT NULL, + period INTEGER NOT NULL, + tech TEXT NOT NULL + REFERENCES Technology (tech), + vintage INTEGER NOT NULL + REFERENCES TimePeriod (period), + fraction REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage) +); +CREATE TABLE TechnologyType +( + label TEXT + PRIMARY KEY, + description TEXT +); +INSERT INTO TechnologyType VALUES('p','production technology'); +INSERT INTO TechnologyType VALUES('pb','baseload production technology'); +INSERT INTO TechnologyType VALUES('ps','storage production technology'); +CREATE TABLE TimeOfDay +( + sequence INTEGER UNIQUE, + tod TEXT + PRIMARY KEY +); +INSERT INTO TimeOfDay VALUES(1,'day'); +INSERT INTO TimeOfDay VALUES(2,'night'); +CREATE TABLE TimePeriod +( + sequence INTEGER UNIQUE, + period INTEGER + PRIMARY KEY, + flag TEXT + REFERENCES TimePeriodType (label) +); +INSERT INTO TimePeriod VALUES(1,1960,'e'); +INSERT INTO TimePeriod VALUES(2,1970,'e'); +INSERT INTO TimePeriod VALUES(3,1980,'e'); +INSERT INTO TimePeriod VALUES(4,1990,'f'); +INSERT INTO TimePeriod VALUES(5,2000,'f'); +INSERT INTO TimePeriod VALUES(6,2010,'f'); +INSERT INTO TimePeriod VALUES(7,2020,'f'); +CREATE TABLE TimeSeason +( + period INTEGER + REFERENCES TimePeriod (period), + sequence INTEGER, + season TEXT + REFERENCES SeasonLabel (season), + notes TEXT, + PRIMARY KEY (period, sequence, season) +); +INSERT INTO TimeSeason VALUES(1990,1,'inter',NULL); +INSERT INTO TimeSeason VALUES(1990,2,'summer',NULL); +INSERT INTO TimeSeason VALUES(1990,3,'winter',NULL); +INSERT INTO TimeSeason VALUES(2000,1,'inter',NULL); +INSERT INTO TimeSeason VALUES(2000,2,'summer',NULL); +INSERT INTO TimeSeason VALUES(2000,3,'winter',NULL); +INSERT INTO TimeSeason VALUES(2010,1,'inter',NULL); +INSERT INTO TimeSeason VALUES(2010,2,'summer',NULL); +INSERT INTO TimeSeason VALUES(2010,3,'winter',NULL); +CREATE TABLE TimeSeasonSequential +( + period INTEGER + REFERENCES TimePeriod (period), + sequence INTEGER, + seas_seq TEXT, + season TEXT + REFERENCES SeasonLabel (season), + num_days REAL NOT NULL, + notes TEXT, + PRIMARY KEY (period, sequence, seas_seq, season), + CHECK (num_days > 0) +); +CREATE TABLE TimePeriodType +( + label TEXT + PRIMARY KEY, + description TEXT +); +INSERT INTO TimePeriodType VALUES('e','existing vintages'); +INSERT INTO TimePeriodType VALUES('f','future'); +CREATE TABLE OutputEmission +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES SectorLabel (sector), + period INTEGER + REFERENCES TimePeriod (period), + emis_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + emission REAL, + PRIMARY KEY (region, scenario, period, emis_comm, tech, vintage) +); +CREATE TABLE RPSRequirement +( + region TEXT NOT NULL + REFERENCES Region (region), + period INTEGER NOT NULL + REFERENCES TimePeriod (period), + tech_group TEXT NOT NULL + REFERENCES TechGroup (group_name), + requirement REAL NOT NULL, + notes TEXT +); +CREATE TABLE TechGroupMember +( + group_name TEXT + REFERENCES TechGroup (group_name), + tech TEXT + REFERENCES Technology (tech), + PRIMARY KEY (group_name, tech) +); +CREATE TABLE Technology +( + tech TEXT NOT NULL PRIMARY KEY, + flag TEXT NOT NULL, + sector TEXT, + category TEXT, + sub_category TEXT, + unlim_cap INTEGER NOT NULL DEFAULT 0, + annual INTEGER NOT NULL DEFAULT 0, + reserve INTEGER NOT NULL DEFAULT 0, + curtail INTEGER NOT NULL DEFAULT 0, + retire INTEGER NOT NULL DEFAULT 0, + flex INTEGER NOT NULL DEFAULT 0, + exchange INTEGER NOT NULL DEFAULT 0, + seas_stor INTEGER NOT NULL DEFAULT 0, + description TEXT, + FOREIGN KEY (flag) REFERENCES TechnologyType (label) +); +INSERT INTO Technology VALUES('IMPDSL1','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported diesel'); +INSERT INTO Technology VALUES('IMPGSL1','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported gasoline'); +INSERT INTO Technology VALUES('IMPHCO1','p','supply','coal','',1,0,0,0,0,0,0,0,' imported coal'); +INSERT INTO Technology VALUES('IMPOIL1','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported crude oil'); +INSERT INTO Technology VALUES('IMPURN1','p','supply','nuclear','',1,0,0,0,0,0,0,0,' imported uranium'); +INSERT INTO Technology VALUES('IMPFEQ','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported fossil equivalent'); +INSERT INTO Technology VALUES('IMPHYD','p','supply','hydro','',1,0,0,0,0,0,0,0,' imported water -- doesnt exist in Utopia'); +INSERT INTO Technology VALUES('E01','pb','electric','coal','',0,0,0,0,0,0,0,0,' coal power plant'); +INSERT INTO Technology VALUES('E21','pb','electric','nuclear','',0,0,0,0,0,0,0,0,' nuclear power plant'); +INSERT INTO Technology VALUES('E31','pb','electric','hydro','',0,0,0,0,0,0,0,0,' hydro power'); +INSERT INTO Technology VALUES('E51','ps','electric','electric','',0,0,0,0,0,0,0,0,' electric storage'); +INSERT INTO Technology VALUES('E70','p','electric','petroleum','',0,0,0,0,0,0,0,0,' diesel power plant'); +INSERT INTO Technology VALUES('RHE','p','residential','electric','',0,0,0,0,0,0,0,0,' electric residential heating'); +INSERT INTO Technology VALUES('RHO','p','residential','petroleum','',0,0,0,0,0,0,0,0,' diesel residential heating'); +INSERT INTO Technology VALUES('RL1','p','residential','electric','',0,0,0,0,0,0,0,0,' residential lighting'); +INSERT INTO Technology VALUES('SRE','p','supply','petroleum','',0,0,0,0,0,0,0,0,' crude oil processor'); +INSERT INTO Technology VALUES('TXD','p','transport','petroleum','',0,0,0,0,0,0,0,0,' diesel powered vehicles'); +INSERT INTO Technology VALUES('TXE','p','transport','electric','',0,0,0,0,0,0,0,0,' electric powered vehicles'); +INSERT INTO Technology VALUES('TXG','p','transport','petroleum','',0,0,0,0,0,0,0,0,' gasoline powered vehicles'); +CREATE TABLE OutputCost +( + scenario TEXT, + region TEXT, + sector TEXT REFERENCES SectorLabel (sector), + period INTEGER REFERENCES TimePeriod (period), + tech TEXT REFERENCES Technology (tech), + vintage INTEGER REFERENCES TimePeriod (period), + d_invest REAL, + d_fixed REAL, + d_var REAL, + d_emiss REAL, + invest REAL, + fixed REAL, + var REAL, + emiss REAL, + PRIMARY KEY (scenario, region, period, tech, vintage), + FOREIGN KEY (vintage) REFERENCES TimePeriod (period), + FOREIGN KEY (tech) REFERENCES Technology (tech) +); +COMMIT;