flextool 4.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flextool/__init__.py +41 -0
- flextool/_mem_sampler.py +193 -0
- flextool/_resources.py +43 -0
- flextool/calibrate/__init__.py +51 -0
- flextool/calibrate/__main__.py +11 -0
- flextool/calibrate/_cli.py +316 -0
- flextool/calibrate/_db_alt.py +166 -0
- flextool/calibrate/_final_outputs.py +110 -0
- flextool/calibrate/_guard.py +151 -0
- flextool/calibrate/_loop.py +558 -0
- flextool/calibrate/_readers.py +223 -0
- flextool/calibrate/_report.py +263 -0
- flextool/calibrate/_sizing.py +699 -0
- flextool/calibrate/_solve.py +134 -0
- flextool/calibrate/_solve_status.py +495 -0
- flextool/cli/__init__.py +9 -0
- flextool/cli/_console.py +51 -0
- flextool/cli/_timing.py +147 -0
- flextool/cli/cmd_execute_flextool_workflow.py +187 -0
- flextool/cli/cmd_export_to_tabular.py +56 -0
- flextool/cli/cmd_import_sensitivities.py +75 -0
- flextool/cli/cmd_migrate_database.py +13 -0
- flextool/cli/cmd_open_results_db.py +269 -0
- flextool/cli/cmd_read_matpower.py +66 -0
- flextool/cli/cmd_read_old_flextool.py +63 -0
- flextool/cli/cmd_read_self_describing_tabular_input.py +50 -0
- flextool/cli/cmd_read_tabular_input.py +81 -0
- flextool/cli/cmd_run_flextool.py +1095 -0
- flextool/cli/cmd_scenario_results.py +284 -0
- flextool/cli/cmd_solve_mps.py +169 -0
- flextool/cli/cmd_update_flextool.py +17 -0
- flextool/cli/cmd_write_outputs.py +125 -0
- flextool/common_utils/__init__.py +1 -0
- flextool/common_utils/plot_mem_shape.py +77 -0
- flextool/common_utils/precision.py +451 -0
- flextool/decomposition/__init__.py +0 -0
- flextool/decomposition/region_decomposition.py +128 -0
- flextool/decomposition/region_filter.py +1261 -0
- flextool/engine_polars/__init__.py +110 -0
- flextool/engine_polars/_axis_enums.py +742 -0
- flextool/engine_polars/_benders.py +3462 -0
- flextool/engine_polars/_block_layout.py +1479 -0
- flextool/engine_polars/_blocks.py +1515 -0
- flextool/engine_polars/_commodity_ladder.py +660 -0
- flextool/engine_polars/_cumulative_invest.py +1165 -0
- flextool/engine_polars/_db_loader.py +153 -0
- flextool/engine_polars/_db_reader.py +127 -0
- flextool/engine_polars/_dc_power_flow.py +445 -0
- flextool/engine_polars/_delay.py +442 -0
- flextool/engine_polars/_derived_arithmetic.py +432 -0
- flextool/engine_polars/_derived_block.py +990 -0
- flextool/engine_polars/_derived_branch.py +769 -0
- flextool/engine_polars/_derived_existing.py +1353 -0
- flextool/engine_polars/_derived_npv.py +1297 -0
- flextool/engine_polars/_derived_params.py +9850 -0
- flextool/engine_polars/_derived_profile.py +881 -0
- flextool/engine_polars/_derived_walks.py +276 -0
- flextool/engine_polars/_determinism.py +70 -0
- flextool/engine_polars/_direct_params.py +2186 -0
- flextool/engine_polars/_dump_csvs.py +1009 -0
- flextool/engine_polars/_emit_arc_unions.py +1631 -0
- flextool/engine_polars/_emit_calc_params.py +729 -0
- flextool/engine_polars/_emit_chain_params.py +709 -0
- flextool/engine_polars/_emit_co2_accumulators.py +400 -0
- flextool/engine_polars/_emit_dispatchers.py +690 -0
- flextool/engine_polars/_emit_energy_margin.py +125 -0
- flextool/engine_polars/_emit_energy_margin_adder.py +290 -0
- flextool/engine_polars/_emit_entity_annual.py +428 -0
- flextool/engine_polars/_emit_inflow_scaling.py +1420 -0
- flextool/engine_polars/_emit_leaf_sets.py +550 -0
- flextool/engine_polars/_emit_lp_scaling.py +665 -0
- flextool/engine_polars/_emit_mid_sets.py +859 -0
- flextool/engine_polars/_emit_pdt_params.py +759 -0
- flextool/engine_polars/_emit_per_solve.py +774 -0
- flextool/engine_polars/_emit_period_calc.py +504 -0
- flextool/engine_polars/_emit_period_params.py +2398 -0
- flextool/engine_polars/_emit_provider_io.py +141 -0
- flextool/engine_polars/_emit_reserve.py +574 -0
- flextool/engine_polars/_emit_solve_time.py +311 -0
- flextool/engine_polars/_emit_solve_writers.py +1249 -0
- flextool/engine_polars/_flex_data_accumulator.py +388 -0
- flextool/engine_polars/_flex_data_provider.py +478 -0
- flextool/engine_polars/_group_slack.py +1253 -0
- flextool/engine_polars/_inmemory_reader.py +140 -0
- flextool/engine_polars/_input_source.py +336 -0
- flextool/engine_polars/_invest_seeds.py +191 -0
- flextool/engine_polars/_native_input_writer.py +100 -0
- flextool/engine_polars/_native_run_model.py +1348 -0
- flextool/engine_polars/_orchestration.py +4314 -0
- flextool/engine_polars/_output_writer.py +439 -0
- flextool/engine_polars/_param_shapes.py +1595 -0
- flextool/engine_polars/_parquet_bundle.py +723 -0
- flextool/engine_polars/_pdt_join.py +167 -0
- flextool/engine_polars/_pdt_lookup.py +547 -0
- flextool/engine_polars/_per_solve_sets.py +335 -0
- flextool/engine_polars/_projection_params.py +2056 -0
- flextool/engine_polars/_provider_keys.py +173 -0
- flextool/engine_polars/_provider_translators.py +225 -0
- flextool/engine_polars/_recursive_solve.py +703 -0
- flextool/engine_polars/_region_filter.py +2508 -0
- flextool/engine_polars/_reserve.py +649 -0
- flextool/engine_polars/_solve_acceptance.py +331 -0
- flextool/engine_polars/_solve_config.py +1001 -0
- flextool/engine_polars/_solve_context.py +885 -0
- flextool/engine_polars/_solve_handoff.py +164 -0
- flextool/engine_polars/_solve_state.py +232 -0
- flextool/engine_polars/_solver_base.py +36 -0
- flextool/engine_polars/_solver_dispatch.py +511 -0
- flextool/engine_polars/_spinedb_reader.py +1165 -0
- flextool/engine_polars/_stochastic.py +593 -0
- flextool/engine_polars/_subprocess_solve.py +1838 -0
- flextool/engine_polars/_timeline.py +1416 -0
- flextool/engine_polars/_vectorize.py +438 -0
- flextool/engine_polars/_warm.py +858 -0
- flextool/engine_polars/autoscale/__init__.py +107 -0
- flextool/engine_polars/autoscale/_config.py +218 -0
- flextool/engine_polars/autoscale/_layer2.py +1253 -0
- flextool/engine_polars/autoscale/_layer2_types.py +584 -0
- flextool/engine_polars/autoscale/_quantity_types.py +621 -0
- flextool/engine_polars/autoscale/_report.py +336 -0
- flextool/engine_polars/chain.py +259 -0
- flextool/engine_polars/input.py +6638 -0
- flextool/engine_polars/model.py +4754 -0
- flextool/env_check.py +388 -0
- flextool/export_to_tabular/__init__.py +5 -0
- flextool/export_to_tabular/db_reader.py +224 -0
- flextool/export_to_tabular/excel_writer.py +3559 -0
- flextool/export_to_tabular/export_settings.yaml +377 -0
- flextool/export_to_tabular/export_to_excel.py +227 -0
- flextool/export_to_tabular/formatting.py +543 -0
- flextool/export_to_tabular/sheet_config.py +876 -0
- flextool/gui/__init__.py +0 -0
- flextool/gui/__main__.py +118 -0
- flextool/gui/calibrate_commands.py +184 -0
- flextool/gui/calibrate_jobs.py +424 -0
- flextool/gui/check_tree.py +142 -0
- flextool/gui/cli_format.py +83 -0
- flextool/gui/config_parser.py +68 -0
- flextool/gui/data_models.py +362 -0
- flextool/gui/db_editor_integration.py +202 -0
- flextool/gui/db_version_check.py +269 -0
- flextool/gui/dialogs/__init__.py +0 -0
- flextool/gui/dialogs/add_dialog.py +1098 -0
- flextool/gui/dialogs/calibrate_dialog.py +1259 -0
- flextool/gui/dialogs/file_picker.py +473 -0
- flextool/gui/dialogs/group_picker.py +299 -0
- flextool/gui/dialogs/migration_consent_dialog.py +106 -0
- flextool/gui/dialogs/migration_progress_dialog.py +237 -0
- flextool/gui/dialogs/plot_dialog.py +459 -0
- flextool/gui/dialogs/plot_settings_picker.py +2184 -0
- flextool/gui/dialogs/project_dialog.py +426 -0
- flextool/gui/dialogs/update_dialog.py +212 -0
- flextool/gui/downsampling.py +88 -0
- flextool/gui/error_handling.py +50 -0
- flextool/gui/execution_manager.py +1715 -0
- flextool/gui/execution_window.py +1377 -0
- flextool/gui/hover_tooltip.py +111 -0
- flextool/gui/input_sources.py +730 -0
- flextool/gui/main_window.py +6181 -0
- flextool/gui/network_graph.py +215 -0
- flextool/gui/output_actions.py +393 -0
- flextool/gui/output_log_window.py +159 -0
- flextool/gui/platform_utils.py +421 -0
- flextool/gui/plot_cache.py +88 -0
- flextool/gui/plot_canvas.py +543 -0
- flextool/gui/plot_config_reader.py +272 -0
- flextool/gui/project_utils.py +100 -0
- flextool/gui/result_viewer.py +4394 -0
- flextool/gui/scenario_key.py +162 -0
- flextool/gui/scenario_lists.py +516 -0
- flextool/gui/settings_io.py +360 -0
- flextool/gui/solve_reader.py +103 -0
- flextool/gui/tree_reorder.py +88 -0
- flextool/gui/ui_metrics.py +420 -0
- flextool/input_derivation/__init__.py +281 -0
- flextool/input_derivation/_commodity_ladder.py +375 -0
- flextool/input_derivation/_commodity_ladder_sets.py +70 -0
- flextool/input_derivation/_dc_power_flow.py +377 -0
- flextool/input_derivation/_method_constants.py +77 -0
- flextool/input_derivation/_process_method.py +258 -0
- flextool/input_derivation/_specs.py +1026 -0
- flextool/input_derivation/_validators.py +321 -0
- flextool/lean_parquet.py +159 -0
- flextool/model_builder/__init__.py +5 -0
- flextool/model_builder/build_model.py +589 -0
- flextool/model_builder/encoding.py +67 -0
- flextool/model_builder/names.py +34 -0
- flextool/model_builder/profiles.py +129 -0
- flextool/plot_outputs/__init__.py +14 -0
- flextool/plot_outputs/axis_helpers.py +355 -0
- flextool/plot_outputs/color_template.py +888 -0
- flextool/plot_outputs/config.py +171 -0
- flextool/plot_outputs/format_helpers.py +345 -0
- flextool/plot_outputs/legend_helpers.py +143 -0
- flextool/plot_outputs/orchestrator.py +1141 -0
- flextool/plot_outputs/perf.py +37 -0
- flextool/plot_outputs/plan.py +1787 -0
- flextool/plot_outputs/plot_bars.py +1510 -0
- flextool/plot_outputs/plot_bars_detail.py +753 -0
- flextool/plot_outputs/plot_lines.py +951 -0
- flextool/plot_outputs/shared_manifest.py +564 -0
- flextool/plot_outputs/subplot_helpers.py +137 -0
- flextool/process_inputs/__init__.py +188 -0
- flextool/process_inputs/import_old_excel_input.json +4159 -0
- flextool/process_inputs/read_matpower.py +451 -0
- flextool/process_inputs/read_old_flextool.py +1288 -0
- flextool/process_inputs/read_self_describing_excel.py +1423 -0
- flextool/process_inputs/read_tabular_with_specification.py +1114 -0
- flextool/process_inputs/write_old_flextool_to_db.py +3077 -0
- flextool/process_inputs/write_self_describing_to_db.py +977 -0
- flextool/process_inputs/write_to_input_db.py +269 -0
- flextool/process_outputs/__init__.py +7 -0
- flextool/process_outputs/_annualize.py +55 -0
- flextool/process_outputs/_inmemory_helpers.py +292 -0
- flextool/process_outputs/_output_meta.py +672 -0
- flextool/process_outputs/calc_capacity_flows.py +107 -0
- flextool/process_outputs/calc_connections.py +136 -0
- flextool/process_outputs/calc_costs.py +260 -0
- flextool/process_outputs/calc_group_flows.py +192 -0
- flextool/process_outputs/calc_slacks.py +103 -0
- flextool/process_outputs/calc_storage_vre.py +160 -0
- flextool/process_outputs/drop_levels.py +208 -0
- flextool/process_outputs/handoff_writers.py +1315 -0
- flextool/process_outputs/out_ancillary.py +544 -0
- flextool/process_outputs/out_capacity.py +179 -0
- flextool/process_outputs/out_costs.py +334 -0
- flextool/process_outputs/out_flowgroup.py +189 -0
- flextool/process_outputs/out_flows.py +301 -0
- flextool/process_outputs/out_group.py +475 -0
- flextool/process_outputs/out_node.py +190 -0
- flextool/process_outputs/persist_realized_slice.py +601 -0
- flextool/process_outputs/process_results.py +24 -0
- flextool/process_outputs/read_highs_solution.py +2256 -0
- flextool/process_outputs/read_parameters.py +1799 -0
- flextool/process_outputs/read_sets.py +1095 -0
- flextool/process_outputs/read_variables.py +553 -0
- flextool/process_outputs/solve_order.py +81 -0
- flextool/process_outputs/spinedb_replay.py +412 -0
- flextool/process_outputs/union_realized_slice.py +224 -0
- flextool/process_outputs/write_outputs.py +1286 -0
- flextool/process_outputs/write_spinedb.py +1267 -0
- flextool/representative_periods/__init__.py +5 -0
- flextool/representative_periods/clustering.py +165 -0
- flextool/representative_periods/force_include.py +563 -0
- flextool/representative_periods/netload.py +365 -0
- flextool/representative_periods/netload_inputs.py +345 -0
- flextool/representative_periods/netload_iterate.py +722 -0
- flextool/representative_periods/preprocess.py +948 -0
- flextool/representative_periods/scenario_stack.py +195 -0
- flextool/representative_periods/weights.py +124 -0
- flextool/scenario_comparison/__init__.py +13 -0
- flextool/scenario_comparison/config_builder.py +158 -0
- flextool/scenario_comparison/constants.py +20 -0
- flextool/scenario_comparison/data_models.py +222 -0
- flextool/scenario_comparison/db_reader.py +399 -0
- flextool/scenario_comparison/dispatch_data.py +1002 -0
- flextool/scenario_comparison/dispatch_mappings.py +205 -0
- flextool/scenario_comparison/dispatch_plots.py +691 -0
- flextool/scenario_comparison/input_entity_colors.py +319 -0
- flextool/scenario_comparison/orchestrator.py +453 -0
- flextool/scenario_comparison/plan_union.py +244 -0
- flextool/scenario_comparison/plot_settings_seed.py +205 -0
- flextool/schemas/AXIS_CONTRACT.md +71 -0
- flextool/schemas/canonical_databases/howto_aggregate_output.json +6225 -0
- flextool/schemas/canonical_databases/howto_connections.json +5606 -0
- flextool/schemas/canonical_databases/howto_demand.json +5518 -0
- flextool/schemas/canonical_databases/howto_hydro_reservoir.json +6239 -0
- flextool/schemas/canonical_databases/howto_hydro_reservoir_with_pump.json +5933 -0
- flextool/schemas/canonical_databases/howto_non_sync_and_curtailment.json +5794 -0
- flextool/schemas/canonical_databases/howto_ramp_and_start_up.json +5707 -0
- flextool/schemas/canonical_databases/howto_stochastics.json +6032 -0
- flextool/schemas/canonical_databases/templates_examples.json +13532 -0
- flextool/schemas/canonical_databases/templates_time_settings_only.json +5340 -0
- flextool/schemas/comparison_settings_template.json +197 -0
- flextool/schemas/default_plot_settings.yaml +260 -0
- flextool/schemas/default_plots.yaml +2293 -0
- flextool/schemas/flextool_axis_contract.json +303 -0
- flextool/schemas/flextool_axis_contract.schema.json +247 -0
- flextool/schemas/old_flextool_import_template.json +4443 -0
- flextool/schemas/output_info_template.json +48 -0
- flextool/schemas/output_settings_template.json +256 -0
- flextool/schemas/pre_v26/flextool_template_constant_default.json +2105 -0
- flextool/schemas/pre_v26/flextool_template_default_optional_output.json +2152 -0
- flextool/schemas/pre_v26/flextool_template_default_value.json +2094 -0
- flextool/schemas/pre_v26/flextool_template_drop_down.json +2080 -0
- flextool/schemas/pre_v26/flextool_template_lifetime_method.json +1990 -0
- flextool/schemas/pre_v26/flextool_template_optional_outputs.json +2094 -0
- flextool/schemas/pre_v26/flextool_template_output_node_flows.json +2105 -0
- flextool/schemas/pre_v26/flextool_template_results_master.json +493 -0
- flextool/schemas/pre_v26/flextool_template_rolling_start_remove.json +2087 -0
- flextool/schemas/pre_v26/flextool_template_rolling_window.json +2059 -0
- flextool/schemas/pre_v26/flextool_template_storage_binding_defaults.json +46 -0
- flextool/schemas/pre_v26/flextool_template_v2.json +1990 -0
- flextool/schemas/pre_v26/flextool_template_v25.json +3864 -0
- flextool/schemas/spinedb_results_schema.json +581 -0
- flextool/schemas/spinedb_schema.json +4636 -0
- flextool/solver_config/copt.opt.template +18 -0
- flextool/solver_config/cplex.opt.template +25 -0
- flextool/solver_config/gurobi.opt.template +18 -0
- flextool/solver_config/highs.opt.template +18 -0
- flextool/solver_config/xpress.opt.template +26 -0
- flextool/spinedb_backend/__init__.py +26 -0
- flextool/spinedb_backend/_axis_enums.py +1119 -0
- flextool/spinedb_backend/_backend.py +1139 -0
- flextool/update_flextool/__init__.py +12 -0
- flextool/update_flextool/canonical_databases.py +251 -0
- flextool/update_flextool/db_migration.py +7108 -0
- flextool/update_flextool/ensure_settings_db.py +138 -0
- flextool/update_flextool/export_database.py +103 -0
- flextool/update_flextool/extend_tests_fixture.py +772 -0
- flextool/update_flextool/generate_canonical.py +274 -0
- flextool/update_flextool/initialize_database.py +42 -0
- flextool/update_flextool/install_info.py +225 -0
- flextool/update_flextool/self_update.py +464 -0
- flextool/update_flextool/sync_master_json_template.py +125 -0
- flextool/update_flextool/test_fixtures.py +187 -0
- flextool-4.0.0.dist-info/METADATA +217 -0
- flextool-4.0.0.dist-info/RECORD +322 -0
- flextool-4.0.0.dist-info/WHEEL +5 -0
- flextool-4.0.0.dist-info/entry_points.txt +17 -0
- flextool-4.0.0.dist-info/licenses/LICENSE.txt +19 -0
- flextool-4.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
"""Stochastic branching of time periods.
|
|
2
|
+
|
|
3
|
+
Architecture notes
|
|
4
|
+
------------------
|
|
5
|
+
|
|
6
|
+
* :class:`StochasticSolver` adds branched versions of periods to the
|
|
7
|
+
per-solve active-time lists when ``state.solve.stochastic_branches``
|
|
8
|
+
declares branches for a solve. Branches receive LP variables
|
|
9
|
+
(active time) but are NOT committed (``realized_time_lists`` /
|
|
10
|
+
``fix_storage_time_lists`` are filtered to the realized branch only).
|
|
11
|
+
|
|
12
|
+
* :func:`write_timeline_matching_map` returns an in-memory dict
|
|
13
|
+
instead of writing CSV; downstream consumers read the dict directly.
|
|
14
|
+
|
|
15
|
+
* **R-O6 invariant** (``audit/a6_b_dim_alternative.md §1-3``): branches
|
|
16
|
+
do NOT enter ``invest_periods``. ``v_invest`` stays realized-only.
|
|
17
|
+
Recourse investment is a future capability requiring a separate
|
|
18
|
+
b-dim refactor; this module must not introduce per-branch invest
|
|
19
|
+
variables.
|
|
20
|
+
|
|
21
|
+
* Mutates ``state.timeline.stochastic_timesteps`` (a slight code smell
|
|
22
|
+
— it would be cleaner to return a fresh dict, but downstream readers
|
|
23
|
+
consume the mutated state).
|
|
24
|
+
|
|
25
|
+
Behavioural quirks
|
|
26
|
+
------------------
|
|
27
|
+
|
|
28
|
+
* **Zero-weight branch**: excluded from ``new_active_time_list`` but
|
|
29
|
+
only excluded from ``solve_branch__time_branch_lists`` if both
|
|
30
|
+
``branch != period`` AND the realized flag is not ``"yes"``. Subtle
|
|
31
|
+
three-way condition.
|
|
32
|
+
|
|
33
|
+
* **Continuation-after-branching**: when a roll's jump exceeds a
|
|
34
|
+
period length, branches continue into subsequent periods using the
|
|
35
|
+
same ``period + "_" + branch`` naming. The naming is "sticky"
|
|
36
|
+
across periods.
|
|
37
|
+
|
|
38
|
+
* **Single realized branch per period**: each period must have
|
|
39
|
+
exactly one ``realized: yes`` row (or zero before any branching has
|
|
40
|
+
occurred). Found 0 or > 1 → raise ``FlexToolConfigError``.
|
|
41
|
+
"""
|
|
42
|
+
from __future__ import annotations
|
|
43
|
+
|
|
44
|
+
import bisect
|
|
45
|
+
import logging
|
|
46
|
+
from collections import defaultdict
|
|
47
|
+
from typing import Any, TYPE_CHECKING
|
|
48
|
+
|
|
49
|
+
from flextool.engine_polars._solve_state import FlexToolConfigError
|
|
50
|
+
from flextool.engine_polars._timeline import make_step_jump
|
|
51
|
+
|
|
52
|
+
if TYPE_CHECKING:
|
|
53
|
+
from flextool.engine_polars._solve_state import RunnerState
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
# Free helpers
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _get_timeset_for_period(
|
|
62
|
+
period_timesets: list[tuple[str, str]],
|
|
63
|
+
real_period: str,
|
|
64
|
+
) -> str | None:
|
|
65
|
+
"""Find the timeset corresponding to *real_period* in *period_timesets*."""
|
|
66
|
+
for period_timeset in period_timesets:
|
|
67
|
+
if period_timeset[0] == real_period:
|
|
68
|
+
return period_timeset[1]
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def connect_two_timelines(
|
|
73
|
+
state: "RunnerState",
|
|
74
|
+
period: str,
|
|
75
|
+
first_solve: str,
|
|
76
|
+
second_solve: str,
|
|
77
|
+
period__branch: list[tuple[str, str]],
|
|
78
|
+
) -> tuple[dict[str, float], dict[str, float]]:
|
|
79
|
+
"""Compute cumulative durations from start for two solves' timelines.
|
|
80
|
+
|
|
81
|
+
Returns two ``{timestep: cumulative_hours}`` dicts so callers can
|
|
82
|
+
align timesteps between the two solves.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
state: Runner state (reads ``solve.timesets_used_by_solves`` and
|
|
86
|
+
``timeline.{timesets__timeline, timelines}``).
|
|
87
|
+
period: Period (or branch) name to align on.
|
|
88
|
+
first_solve / second_solve: Solve names.
|
|
89
|
+
period__branch: List of ``(real_period, branched_period)``
|
|
90
|
+
tuples. Used to map *period* back to its real period for
|
|
91
|
+
timeset lookup.
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
ValueError: When the timeset for *real_period* can't be located
|
|
95
|
+
in either solve's ``timesets_used_by_solves``.
|
|
96
|
+
"""
|
|
97
|
+
first_period_timesets = state.solve.timesets_used_by_solves[first_solve]
|
|
98
|
+
second_period_timesets = state.solve.timesets_used_by_solves[second_solve]
|
|
99
|
+
|
|
100
|
+
real_period: str | None = None
|
|
101
|
+
for row in period__branch:
|
|
102
|
+
if row[1] == period:
|
|
103
|
+
real_period = row[0]
|
|
104
|
+
|
|
105
|
+
first_timeset = _get_timeset_for_period(first_period_timesets, real_period)
|
|
106
|
+
second_timeset = _get_timeset_for_period(second_period_timesets, real_period)
|
|
107
|
+
|
|
108
|
+
if first_timeset is None:
|
|
109
|
+
raise ValueError(
|
|
110
|
+
f"Could not find first_timeset for real_period={real_period} "
|
|
111
|
+
f"in first_period_timesets={first_period_timesets}"
|
|
112
|
+
)
|
|
113
|
+
if second_timeset is None:
|
|
114
|
+
raise ValueError(
|
|
115
|
+
f"Could not find second_timeset for real_period={real_period} "
|
|
116
|
+
f"in second_period_timesets={second_period_timesets}"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
first_timeline = state.timeline.timesets__timeline[first_timeset]
|
|
120
|
+
second_timeline = state.timeline.timesets__timeline[second_timeset]
|
|
121
|
+
|
|
122
|
+
first_timeline_duration_from_start: dict[str, float] = dict()
|
|
123
|
+
second_timeline_duration_from_start: dict[str, float] = dict()
|
|
124
|
+
counter: float = 0
|
|
125
|
+
for timestep in state.timeline.timelines[first_timeline]:
|
|
126
|
+
first_timeline_duration_from_start[timestep[0]] = counter
|
|
127
|
+
counter += float(timestep[1])
|
|
128
|
+
counter = 0
|
|
129
|
+
for timestep in state.timeline.timelines[second_timeline]:
|
|
130
|
+
second_timeline_duration_from_start[timestep[0]] = counter
|
|
131
|
+
counter += float(timestep[1])
|
|
132
|
+
|
|
133
|
+
return first_timeline_duration_from_start, second_timeline_duration_from_start
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def find_previous_timestep(
|
|
137
|
+
state: "RunnerState",
|
|
138
|
+
from_active_time_list: dict[str, list[tuple[str, ...]]],
|
|
139
|
+
period_timestamp: tuple[str, str],
|
|
140
|
+
this_solve: str,
|
|
141
|
+
from_solve: str,
|
|
142
|
+
period__branch: list[tuple[str, str]],
|
|
143
|
+
) -> str:
|
|
144
|
+
"""Find the previous timestep in *from_solve* matching *period_timestamp* in *this_solve*."""
|
|
145
|
+
this_timeline_duration_from_start, from_timeline_duration_from_start = (
|
|
146
|
+
connect_two_timelines(
|
|
147
|
+
state, period_timestamp[0], this_solve, from_solve, period__branch
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
real_period: str | None = None
|
|
152
|
+
for row in period__branch:
|
|
153
|
+
if row[1] == period_timestamp[0]:
|
|
154
|
+
real_period = row[0]
|
|
155
|
+
from_start = this_timeline_duration_from_start[period_timestamp[1]]
|
|
156
|
+
last_timestep = from_active_time_list[real_period][0].timestep
|
|
157
|
+
previous_timestep = from_active_time_list[real_period][-1].timestep
|
|
158
|
+
for timestep in from_active_time_list[real_period]:
|
|
159
|
+
if from_timeline_duration_from_start[timestep.timestep] > from_start:
|
|
160
|
+
previous_timestep = last_timestep
|
|
161
|
+
break
|
|
162
|
+
last_timestep = timestep.timestep
|
|
163
|
+
return previous_timestep
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def find_next_timestep(
|
|
167
|
+
state: "RunnerState",
|
|
168
|
+
from_active_time_list: dict[str, list[tuple[str, ...]]],
|
|
169
|
+
period_timestamp: tuple[str, str],
|
|
170
|
+
this_solve: str,
|
|
171
|
+
from_solve: str,
|
|
172
|
+
) -> str:
|
|
173
|
+
"""Find the next timestep in *from_solve* matching *period_timestamp* in *this_solve*.
|
|
174
|
+
|
|
175
|
+
The ``period__branch`` arg is implicitly the identity mapping
|
|
176
|
+
``[(period, period)]`` here — used when the lookup happens before
|
|
177
|
+
any branches have been generated.
|
|
178
|
+
"""
|
|
179
|
+
this_timeline_duration_from_start, from_timeline_duration_from_start = (
|
|
180
|
+
connect_two_timelines(
|
|
181
|
+
state,
|
|
182
|
+
period_timestamp[0],
|
|
183
|
+
this_solve,
|
|
184
|
+
from_solve,
|
|
185
|
+
[(period_timestamp[0], period_timestamp[0])],
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
from_start = this_timeline_duration_from_start[period_timestamp[1]]
|
|
190
|
+
next_timestep = from_active_time_list[period_timestamp[0]][-1].timestep
|
|
191
|
+
for timestep in from_active_time_list[period_timestamp[0]]:
|
|
192
|
+
if from_timeline_duration_from_start[timestep.timestep] >= from_start:
|
|
193
|
+
next_timestep = timestep.timestep
|
|
194
|
+
break
|
|
195
|
+
return next_timestep
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def write_timeline_matching_map(
|
|
199
|
+
state: "RunnerState",
|
|
200
|
+
upper_active_time_list: dict[str, list[tuple[str, ...]]],
|
|
201
|
+
lower_active_time_list: dict[str, list[tuple[str, ...]]],
|
|
202
|
+
upper_solve: str,
|
|
203
|
+
lower_solve: str,
|
|
204
|
+
period__branch: list[tuple[str, str]],
|
|
205
|
+
) -> dict[tuple[str, str], str]:
|
|
206
|
+
"""Build the timeline matching map for nested storage fixing.
|
|
207
|
+
|
|
208
|
+
Maps each lower-solve ``(period, timestep)`` pair to the
|
|
209
|
+
corresponding upper-solve timestep using binary search on
|
|
210
|
+
cumulative timeline durations.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
``{(period, timestep): upper_timestep, ...}`` dict.
|
|
214
|
+
|
|
215
|
+
Note: the AMPL/.mod reference path also writes
|
|
216
|
+
``solve_data/timeline_matching_map.csv`` to disk. The native
|
|
217
|
+
engine keeps the result in-memory; the CSV write is a separate
|
|
218
|
+
concern that lives in the orchestrator (Γ.8.D) when file output
|
|
219
|
+
is requested for compatibility with downstream tools.
|
|
220
|
+
"""
|
|
221
|
+
# Pre-compute period -> real_period mapping.
|
|
222
|
+
period_to_real: dict[str, str] = {row[1]: row[0] for row in period__branch}
|
|
223
|
+
|
|
224
|
+
upper_period_timesets = state.solve.timesets_used_by_solves[upper_solve]
|
|
225
|
+
upper_periods: set[str] = {pt[0] for pt in upper_period_timesets}
|
|
226
|
+
|
|
227
|
+
matching_map: dict[tuple[str, str], str] = dict()
|
|
228
|
+
for period, lower_active_time in lower_active_time_list.items():
|
|
229
|
+
real_period = period_to_real.get(period)
|
|
230
|
+
|
|
231
|
+
# Skip periods that don't exist in the upper solve's timesets.
|
|
232
|
+
if real_period not in upper_periods:
|
|
233
|
+
continue
|
|
234
|
+
|
|
235
|
+
# Call connect_two_timelines once per period, not per timestep.
|
|
236
|
+
this_timeline_duration, from_timeline_duration = connect_two_timelines(
|
|
237
|
+
state, period, lower_solve, upper_solve, period__branch
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
upper_timesteps = upper_active_time_list[real_period]
|
|
241
|
+
|
|
242
|
+
from_durations: list[float] = []
|
|
243
|
+
from_timestep_names: list[str] = []
|
|
244
|
+
for ts in upper_timesteps:
|
|
245
|
+
from_durations.append(from_timeline_duration[ts.timestep])
|
|
246
|
+
from_timestep_names.append(ts.timestep)
|
|
247
|
+
|
|
248
|
+
default_timestep = upper_timesteps[-1].timestep
|
|
249
|
+
|
|
250
|
+
for timestep in lower_active_time:
|
|
251
|
+
period_timestep = (period, timestep.timestep)
|
|
252
|
+
from_start = this_timeline_duration[timestep.timestep]
|
|
253
|
+
|
|
254
|
+
# Binary search: find first index where duration > from_start.
|
|
255
|
+
idx = bisect.bisect_right(from_durations, from_start)
|
|
256
|
+
|
|
257
|
+
if idx == 0:
|
|
258
|
+
previous_timestep = upper_timesteps[0].timestep
|
|
259
|
+
elif idx >= len(from_durations):
|
|
260
|
+
previous_timestep = default_timestep
|
|
261
|
+
else:
|
|
262
|
+
previous_timestep = from_timestep_names[idx - 1]
|
|
263
|
+
|
|
264
|
+
matching_map[period_timestep] = previous_timestep
|
|
265
|
+
|
|
266
|
+
return matching_map
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# ---------------------------------------------------------------------------
|
|
270
|
+
# StochasticSolver
|
|
271
|
+
# ---------------------------------------------------------------------------
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
class StochasticSolver:
|
|
275
|
+
"""Apply stochastic branching to per-solve active-time lists.
|
|
276
|
+
|
|
277
|
+
See :meth:`create_stochastic_periods` for the entry point. The
|
|
278
|
+
standalone helpers above (``connect_two_timelines``,
|
|
279
|
+
``find_previous_timestep``, ``find_next_timestep``,
|
|
280
|
+
``write_timeline_matching_map``) are kept as module-level functions
|
|
281
|
+
so callers don't need a ``StochasticSolver`` instance for read-only
|
|
282
|
+
queries.
|
|
283
|
+
"""
|
|
284
|
+
|
|
285
|
+
def __init__(self, state: "RunnerState") -> None:
|
|
286
|
+
self.state = state
|
|
287
|
+
self.logger: logging.Logger = state.logger
|
|
288
|
+
|
|
289
|
+
# ------------------------------------------------------------------
|
|
290
|
+
# Convenience instance wrappers (kept for backwards compatibility).
|
|
291
|
+
# The underlying logic lives in module-level helpers above.
|
|
292
|
+
# ------------------------------------------------------------------
|
|
293
|
+
|
|
294
|
+
def connect_two_timelines(
|
|
295
|
+
self,
|
|
296
|
+
period: str,
|
|
297
|
+
first_solve: str,
|
|
298
|
+
second_solve: str,
|
|
299
|
+
period__branch: list[tuple[str, str]],
|
|
300
|
+
) -> tuple[dict[str, float], dict[str, float]]:
|
|
301
|
+
return connect_two_timelines(
|
|
302
|
+
self.state, period, first_solve, second_solve, period__branch
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
def find_previous_timestep(
|
|
306
|
+
self,
|
|
307
|
+
from_active_time_list: dict[str, list[tuple[str, ...]]],
|
|
308
|
+
period_timestamp: tuple[str, str],
|
|
309
|
+
this_solve: str,
|
|
310
|
+
from_solve: str,
|
|
311
|
+
period__branch: list[tuple[str, str]],
|
|
312
|
+
) -> str:
|
|
313
|
+
return find_previous_timestep(
|
|
314
|
+
self.state,
|
|
315
|
+
from_active_time_list,
|
|
316
|
+
period_timestamp,
|
|
317
|
+
this_solve,
|
|
318
|
+
from_solve,
|
|
319
|
+
period__branch,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
def find_next_timestep(
|
|
323
|
+
self,
|
|
324
|
+
from_active_time_list: dict[str, list[tuple[str, ...]]],
|
|
325
|
+
period_timestamp: tuple[str, str],
|
|
326
|
+
this_solve: str,
|
|
327
|
+
from_solve: str,
|
|
328
|
+
) -> str:
|
|
329
|
+
return find_next_timestep(
|
|
330
|
+
self.state,
|
|
331
|
+
from_active_time_list,
|
|
332
|
+
period_timestamp,
|
|
333
|
+
this_solve,
|
|
334
|
+
from_solve,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
def write_timeline_matching_map(
|
|
338
|
+
self,
|
|
339
|
+
upper_active_time_list: dict[str, list[tuple[str, ...]]],
|
|
340
|
+
lower_active_time_list: dict[str, list[tuple[str, ...]]],
|
|
341
|
+
upper_solve: str,
|
|
342
|
+
lower_solve: str,
|
|
343
|
+
period__branch: list[tuple[str, str]],
|
|
344
|
+
) -> dict[tuple[str, str], str]:
|
|
345
|
+
return write_timeline_matching_map(
|
|
346
|
+
self.state,
|
|
347
|
+
upper_active_time_list,
|
|
348
|
+
lower_active_time_list,
|
|
349
|
+
upper_solve,
|
|
350
|
+
lower_solve,
|
|
351
|
+
period__branch,
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
# ------------------------------------------------------------------
|
|
355
|
+
# create_stochastic_periods — main entry point
|
|
356
|
+
# ------------------------------------------------------------------
|
|
357
|
+
|
|
358
|
+
def create_stochastic_periods(
|
|
359
|
+
self,
|
|
360
|
+
stochastic_branches: dict[str, Any],
|
|
361
|
+
solves: list[str],
|
|
362
|
+
complete_solves: dict[str, str],
|
|
363
|
+
active_time_lists: dict[str, dict],
|
|
364
|
+
fix_storage_time_lists: dict[str, dict],
|
|
365
|
+
realized_time_lists: dict[str, dict],
|
|
366
|
+
) -> tuple[
|
|
367
|
+
defaultdict[str, list],
|
|
368
|
+
defaultdict[str, list],
|
|
369
|
+
dict[str, dict],
|
|
370
|
+
dict[str, Any],
|
|
371
|
+
dict[str, dict],
|
|
372
|
+
dict[str, dict],
|
|
373
|
+
defaultdict[str, Any],
|
|
374
|
+
]:
|
|
375
|
+
"""Apply stochastic branching to per-solve active-time lists.
|
|
376
|
+
|
|
377
|
+
Branches are added to ``active_time_lists`` for optimization
|
|
378
|
+
but NOT to ``realized_time_lists`` / ``fix_storage_time_lists``;
|
|
379
|
+
branches are future scenarios that aren't committed.
|
|
380
|
+
|
|
381
|
+
IMPORTANT: branches do NOT enter ``invest_periods`` either.
|
|
382
|
+
Per ``audit/a6_b_dim_alternative.md`` (R-O6), ``v_invest`` is
|
|
383
|
+
realized-only — recourse investment is a future capability.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
stochastic_branches: ``{solve: [(period, branch, start_step,
|
|
387
|
+
realized_yes_no, weight), ...]}``. Read from the DB.
|
|
388
|
+
solves: Flat list of all solve names (output of
|
|
389
|
+
:class:`RecursiveSolveBuilder`).
|
|
390
|
+
complete_solves: ``{solve: complete_solve_name}``.
|
|
391
|
+
active_time_lists / fix_storage_time_lists / realized_time_lists:
|
|
392
|
+
Per-solve time lists from the recursive builder.
|
|
393
|
+
|
|
394
|
+
Returns:
|
|
395
|
+
``(period__branch_lists, solve_branch__time_branch_lists,
|
|
396
|
+
active_time_lists, jump_lists, fix_storage_time_lists,
|
|
397
|
+
realized_time_lists, branch_start_time_lists)``.
|
|
398
|
+
|
|
399
|
+
Raises:
|
|
400
|
+
FlexToolConfigError: When a solve's start time isn't in any
|
|
401
|
+
``stochastic_branches`` row, or when more than one
|
|
402
|
+
``realized: yes`` row matches a period.
|
|
403
|
+
"""
|
|
404
|
+
period__branch_lists: defaultdict[str, list] = defaultdict(list)
|
|
405
|
+
solve_branch__time_branch_lists: defaultdict[str, list] = defaultdict(list)
|
|
406
|
+
jump_lists: dict[str, Any] = dict()
|
|
407
|
+
branch_start_time_lists: defaultdict[str, Any] = defaultdict()
|
|
408
|
+
|
|
409
|
+
for solve in solves:
|
|
410
|
+
new_realized_time_list: dict = dict()
|
|
411
|
+
new_fix_storage_time_list: dict = dict()
|
|
412
|
+
new_active_time_list: dict = dict()
|
|
413
|
+
|
|
414
|
+
info = stochastic_branches[complete_solves[solve]]
|
|
415
|
+
active_time_list = active_time_lists[solve]
|
|
416
|
+
realized_time_list = realized_time_lists[solve]
|
|
417
|
+
fix_storage_time_list = fix_storage_time_lists[solve]
|
|
418
|
+
|
|
419
|
+
branched = False
|
|
420
|
+
branches: list[str] = []
|
|
421
|
+
branch_start_time_lists[solve] = None
|
|
422
|
+
|
|
423
|
+
# First step of the solve (used for validation below).
|
|
424
|
+
first_step: tuple[str, str] | None = None
|
|
425
|
+
for period, active_time in active_time_list.items():
|
|
426
|
+
first_step = (period, active_time[0].timestep)
|
|
427
|
+
break
|
|
428
|
+
|
|
429
|
+
# Validate: when stochastic_branches has rows for this
|
|
430
|
+
# complete-solve, at least one row's start_step must match
|
|
431
|
+
# the solve's first step AND be marked realized.
|
|
432
|
+
found_start = False
|
|
433
|
+
for row in info:
|
|
434
|
+
if first_step is not None and first_step[1] == row[2] and "yes" == row[3]:
|
|
435
|
+
found_start = True
|
|
436
|
+
if found_start is False and len(info) != 0:
|
|
437
|
+
message = (
|
|
438
|
+
"A realized start time of the solve cannot be found from "
|
|
439
|
+
"the stochastic_branches parameter. "
|
|
440
|
+
"Check that stochastic_branches has a realized : yes, "
|
|
441
|
+
"branch for the start of the solve "
|
|
442
|
+
"and that the possible rolling_jump matches with the "
|
|
443
|
+
"branch starts"
|
|
444
|
+
)
|
|
445
|
+
self.logger.error(message)
|
|
446
|
+
raise FlexToolConfigError(message)
|
|
447
|
+
|
|
448
|
+
# Walk periods — at the first branch trigger, fan out into
|
|
449
|
+
# branches; subsequent periods continue the same branches
|
|
450
|
+
# (continuation-after-branching at lines 329-340 of the
|
|
451
|
+
# reference).
|
|
452
|
+
for period, active_time in active_time_list.items():
|
|
453
|
+
if not branched:
|
|
454
|
+
period__branch_lists[solve].append((period, period))
|
|
455
|
+
# Get all start times: row[0]=period, row[1]=branch,
|
|
456
|
+
# row[2]=start_step, row[3]=realized_yn, row[4]=weight.
|
|
457
|
+
start_times: defaultdict[str, list] = defaultdict(list)
|
|
458
|
+
for row in info:
|
|
459
|
+
if row[0] == period:
|
|
460
|
+
start_times[row[2]].append((row[1], row[4], row[3]))
|
|
461
|
+
|
|
462
|
+
# Trigger branching at the first step that appears
|
|
463
|
+
# in start_times.
|
|
464
|
+
for step in active_time:
|
|
465
|
+
if step.timestep in start_times.keys():
|
|
466
|
+
branched = True
|
|
467
|
+
branch_start_time_lists[solve] = (period, step.timestep)
|
|
468
|
+
|
|
469
|
+
# Add active time for base period.
|
|
470
|
+
new_active_time_list[period] = active_time
|
|
471
|
+
|
|
472
|
+
# Copy realized + fix_storage for base period only.
|
|
473
|
+
if period in realized_time_list:
|
|
474
|
+
new_realized_time_list[period] = realized_time_list[period]
|
|
475
|
+
if period in fix_storage_time_list:
|
|
476
|
+
new_fix_storage_time_list[period] = fix_storage_time_list[period]
|
|
477
|
+
|
|
478
|
+
# Create branches: each gets active time but
|
|
479
|
+
# NOT realized/fix_storage.
|
|
480
|
+
for branch__weight__real in start_times[step.timestep]:
|
|
481
|
+
branch = branch__weight__real[0]
|
|
482
|
+
branches.append(branch)
|
|
483
|
+
solve_branch = period + "_" + branch
|
|
484
|
+
|
|
485
|
+
# Zero-weight branch is excluded from
|
|
486
|
+
# both active time AND
|
|
487
|
+
# solve_branch__time_branch when the
|
|
488
|
+
# three-way condition holds.
|
|
489
|
+
if (
|
|
490
|
+
float(branch__weight__real[1]) != 0.0
|
|
491
|
+
and branch != period
|
|
492
|
+
and branch__weight__real[2] != "yes"
|
|
493
|
+
):
|
|
494
|
+
new_active_time_list[solve_branch] = active_time[0:]
|
|
495
|
+
solve_branch__time_branch_lists[solve].append(
|
|
496
|
+
(solve_branch, branch)
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
period__branch_lists[solve].append((period, solve_branch))
|
|
500
|
+
|
|
501
|
+
# Track branched timesteps for downstream
|
|
502
|
+
# disambiguation. Mutates state.timeline.
|
|
503
|
+
for i in active_time[0:]:
|
|
504
|
+
self.state.timeline.stochastic_timesteps[solve].append(
|
|
505
|
+
(solve_branch, i.timestep)
|
|
506
|
+
)
|
|
507
|
+
break
|
|
508
|
+
else:
|
|
509
|
+
# Continuation-after-branching: extend each branch
|
|
510
|
+
# into the next period using the same branch-name
|
|
511
|
+
# prefix.
|
|
512
|
+
for branch in branches:
|
|
513
|
+
solve_branch = period + "_" + branch
|
|
514
|
+
period__branch_lists[solve].append((period, solve_branch))
|
|
515
|
+
solve_branch__time_branch_lists[solve].append(
|
|
516
|
+
(solve_branch, branch)
|
|
517
|
+
)
|
|
518
|
+
for i in active_time_list[period]:
|
|
519
|
+
self.state.timeline.stochastic_timesteps[solve].append(
|
|
520
|
+
(solve_branch, i.timestep)
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
# Periods before any branching: copy as-is.
|
|
524
|
+
if not branched:
|
|
525
|
+
new_active_time_list[period] = active_time_list[period]
|
|
526
|
+
if period in realized_time_list:
|
|
527
|
+
new_realized_time_list[period] = realized_time_list[period]
|
|
528
|
+
if period in fix_storage_time_list:
|
|
529
|
+
new_fix_storage_time_list[period] = fix_storage_time_list[period]
|
|
530
|
+
|
|
531
|
+
# Find the realized branch for each period. Each period
|
|
532
|
+
# must have exactly one realized: yes row (or zero before
|
|
533
|
+
# any branching has occurred).
|
|
534
|
+
for period, active_time in active_time_list.items():
|
|
535
|
+
found = 0
|
|
536
|
+
# Before branching: row[0]==period, row[2]==first step,
|
|
537
|
+
# row[3]=='yes'.
|
|
538
|
+
for row in info:
|
|
539
|
+
if (
|
|
540
|
+
row[0] == period
|
|
541
|
+
and row[2] == active_time[0].timestep
|
|
542
|
+
and row[3] == "yes"
|
|
543
|
+
):
|
|
544
|
+
found += 1
|
|
545
|
+
solve_branch__time_branch_lists[solve].append((period, row[1]))
|
|
546
|
+
# After branching: lookup against the branch-start row.
|
|
547
|
+
if found == 0 and branch_start_time_lists[solve] is not None:
|
|
548
|
+
for row in info:
|
|
549
|
+
if (
|
|
550
|
+
row[0] == branch_start_time_lists[solve][0]
|
|
551
|
+
and row[2] == branch_start_time_lists[solve][1]
|
|
552
|
+
and row[3] == "yes"
|
|
553
|
+
):
|
|
554
|
+
found += 1
|
|
555
|
+
solve_branch__time_branch_lists[solve].append(
|
|
556
|
+
(period, row[1])
|
|
557
|
+
)
|
|
558
|
+
if (branch_start_time_lists[solve] is not None and found == 0) or found > 1:
|
|
559
|
+
message = (
|
|
560
|
+
"Each period should have one and only one realized branch. "
|
|
561
|
+
"Found: " + str(found) + "\n"
|
|
562
|
+
)
|
|
563
|
+
self.logger.error(message)
|
|
564
|
+
raise FlexToolConfigError(message)
|
|
565
|
+
|
|
566
|
+
# Update the time lists for this solve.
|
|
567
|
+
realized_time_lists[solve] = new_realized_time_list
|
|
568
|
+
fix_storage_time_lists[solve] = new_fix_storage_time_list
|
|
569
|
+
active_time_lists[solve] = new_active_time_list
|
|
570
|
+
jump_lists[solve] = make_step_jump(
|
|
571
|
+
new_active_time_list,
|
|
572
|
+
period__branch_lists[solve],
|
|
573
|
+
solve_branch__time_branch_lists[solve],
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
return (
|
|
577
|
+
period__branch_lists,
|
|
578
|
+
solve_branch__time_branch_lists,
|
|
579
|
+
active_time_lists,
|
|
580
|
+
jump_lists,
|
|
581
|
+
fix_storage_time_lists,
|
|
582
|
+
realized_time_lists,
|
|
583
|
+
branch_start_time_lists,
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
__all__ = [
|
|
588
|
+
"StochasticSolver",
|
|
589
|
+
"connect_two_timelines",
|
|
590
|
+
"find_previous_timestep",
|
|
591
|
+
"find_next_timestep",
|
|
592
|
+
"write_timeline_matching_map",
|
|
593
|
+
]
|