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,165 @@
|
|
|
1
|
+
"""Greedy Convex Hull Clustering for representative period selection.
|
|
2
|
+
|
|
3
|
+
Implements Algorithm 2 from:
|
|
4
|
+
"Hull Clustering with Blended Representative Periods"
|
|
5
|
+
Neustroev et al., 2025.
|
|
6
|
+
|
|
7
|
+
The algorithm greedily selects representative periods by iteratively
|
|
8
|
+
picking the base period whose feature vector is furthest from the
|
|
9
|
+
convex hull of already-selected representatives. A caching optimisation
|
|
10
|
+
avoids recomputing hull distances when the new representative cannot
|
|
11
|
+
have improved the approximation for a given candidate.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
from .weights import distance_to_hull
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def greedy_convex_hull_clustering(
|
|
20
|
+
C: np.ndarray,
|
|
21
|
+
n_rp: int,
|
|
22
|
+
seed_indices: list[int] | None = None,
|
|
23
|
+
) -> list[int]:
|
|
24
|
+
"""Select representative periods using greedy convex hull clustering.
|
|
25
|
+
|
|
26
|
+
Algorithm:
|
|
27
|
+
1. Compute the mean of all columns of C.
|
|
28
|
+
2. Select the column furthest from the mean as the first RP.
|
|
29
|
+
3. Greedily add the column furthest from the current convex hull.
|
|
30
|
+
4. Use caching: if dist(c_d, c_{r_new}) >= cached_dist[d],
|
|
31
|
+
the cached hull distance is still valid (the hull only gets
|
|
32
|
+
closer or stays the same when a point is added, never farther).
|
|
33
|
+
|
|
34
|
+
When ``seed_indices`` is supplied, steps 1–2 are skipped: those columns
|
|
35
|
+
form the *initial* hull (force-included periods), and the greedy loop
|
|
36
|
+
fills the remaining ``n_rp - len(seed)`` slots so the clustered picks
|
|
37
|
+
COMPLEMENT the seed rather than duplicating what it already represents.
|
|
38
|
+
The total is always pinned at ``n_rp`` (a seed longer than ``n_rp`` is
|
|
39
|
+
truncated). With no seed the behaviour is byte-identical to the original.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
C: Clustering matrix, shape (n_features, n_base_periods).
|
|
43
|
+
Each column is one base period's feature vector.
|
|
44
|
+
n_rp: Number of representative periods to select.
|
|
45
|
+
seed_indices: Optional base-period indices to force into the set as
|
|
46
|
+
the initial hull. ``None``/empty → the unseeded default path.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
List of selected column indices (representative period indices).
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
ValueError: If C is not a 2-D array or n_rp < 1.
|
|
53
|
+
"""
|
|
54
|
+
if C.ndim != 2:
|
|
55
|
+
raise ValueError(f"C must be a 2-D array, got {C.ndim}-D")
|
|
56
|
+
n_features, n_base = C.shape
|
|
57
|
+
|
|
58
|
+
if n_rp <= 0:
|
|
59
|
+
raise ValueError(f"n_rp must be >= 1, got {n_rp}")
|
|
60
|
+
|
|
61
|
+
# Edge case: requesting as many or more RPs than base periods
|
|
62
|
+
if n_rp >= n_base:
|
|
63
|
+
return list(range(n_base))
|
|
64
|
+
|
|
65
|
+
if seed_indices:
|
|
66
|
+
# ------------------------------------------------------------------
|
|
67
|
+
# Seeded start: the forced periods form the initial hull.
|
|
68
|
+
# ------------------------------------------------------------------
|
|
69
|
+
# De-duplicate while preserving order, drop out-of-range indices, and
|
|
70
|
+
# cap at n_rp (forced periods can never exceed the target budget).
|
|
71
|
+
rep_indices = []
|
|
72
|
+
_seen: set = set()
|
|
73
|
+
for _raw in seed_indices:
|
|
74
|
+
_idx = int(_raw)
|
|
75
|
+
if 0 <= _idx < n_base and _idx not in _seen:
|
|
76
|
+
_seen.add(_idx)
|
|
77
|
+
rep_indices.append(_idx)
|
|
78
|
+
if len(rep_indices) >= n_rp:
|
|
79
|
+
break
|
|
80
|
+
if len(rep_indices) >= n_rp:
|
|
81
|
+
# Seed already fills (or overfills) the budget — nothing to add.
|
|
82
|
+
return rep_indices[:n_rp]
|
|
83
|
+
print(
|
|
84
|
+
f"Seeded hull with {len(rep_indices)} forced period(s): "
|
|
85
|
+
f"{rep_indices}"
|
|
86
|
+
)
|
|
87
|
+
# Exact distance of every base period to the (multi-point) seed hull.
|
|
88
|
+
# This is the cached-distance invariant the greedy loop relies on: it
|
|
89
|
+
# only ever decreases as points are added, never increases.
|
|
90
|
+
R_seed = C[:, rep_indices]
|
|
91
|
+
cached_dist = np.array(
|
|
92
|
+
[distance_to_hull(R_seed, C[:, d])[0] for d in range(n_base)]
|
|
93
|
+
)
|
|
94
|
+
for _r in rep_indices:
|
|
95
|
+
cached_dist[_r] = -1.0
|
|
96
|
+
first_k = len(rep_indices) + 1
|
|
97
|
+
else:
|
|
98
|
+
# ------------------------------------------------------------------
|
|
99
|
+
# Step 1: compute mean column vector
|
|
100
|
+
# ------------------------------------------------------------------
|
|
101
|
+
mean_col = C.mean(axis=1) # shape (n_features,)
|
|
102
|
+
|
|
103
|
+
# ------------------------------------------------------------------
|
|
104
|
+
# Step 2: first RP = column furthest from the mean
|
|
105
|
+
# ------------------------------------------------------------------
|
|
106
|
+
dists_to_mean = np.linalg.norm(C - mean_col[:, np.newaxis], axis=0)
|
|
107
|
+
first_rp = int(np.argmax(dists_to_mean))
|
|
108
|
+
rep_indices: list[int] = [first_rp]
|
|
109
|
+
print(
|
|
110
|
+
f"Selected RP 1/{n_rp}, max distance to mean: "
|
|
111
|
+
f"{dists_to_mean[first_rp]:.6f}"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# ------------------------------------------------------------------
|
|
115
|
+
# Initialise cached hull distances for step 3
|
|
116
|
+
# ------------------------------------------------------------------
|
|
117
|
+
# After picking the first RP, the "hull" is a single point.
|
|
118
|
+
# dist(c_d, hull) = ||c_d - c_{first_rp}||
|
|
119
|
+
cached_dist = np.linalg.norm(
|
|
120
|
+
C - C[:, first_rp : first_rp + 1], axis=0
|
|
121
|
+
) # shape (n_base,)
|
|
122
|
+
# Mark selected indices so they are never picked again
|
|
123
|
+
cached_dist[first_rp] = -1.0
|
|
124
|
+
first_k = 2
|
|
125
|
+
|
|
126
|
+
# ------------------------------------------------------------------
|
|
127
|
+
# Step 3: greedily add the column furthest from current hull
|
|
128
|
+
# ------------------------------------------------------------------
|
|
129
|
+
for k in range(first_k, n_rp + 1):
|
|
130
|
+
# The previous iteration chose rep_indices[-1] as r_new.
|
|
131
|
+
r_new = rep_indices[-1]
|
|
132
|
+
c_new = C[:, r_new]
|
|
133
|
+
|
|
134
|
+
# Build the representative sub-matrix for hull distance queries
|
|
135
|
+
R = C[:, rep_indices] # shape (n_features, len(rep_indices))
|
|
136
|
+
|
|
137
|
+
for d in range(n_base):
|
|
138
|
+
if cached_dist[d] < 0.0:
|
|
139
|
+
# Already selected as an RP — skip
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
# Caching check: if the new RP is at least as far from c_d
|
|
143
|
+
# as the cached hull distance, adding it cannot help.
|
|
144
|
+
dist_to_new = float(np.linalg.norm(C[:, d] - c_new))
|
|
145
|
+
if dist_to_new >= cached_dist[d]:
|
|
146
|
+
# Cached value is still a valid upper bound and the hull
|
|
147
|
+
# distance cannot have decreased — keep cached value.
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
# Need to recompute hull distance with the updated hull
|
|
151
|
+
hull_dist, _ = distance_to_hull(R, C[:, d])
|
|
152
|
+
cached_dist[d] = hull_dist
|
|
153
|
+
|
|
154
|
+
# Pick the candidate with the largest hull distance
|
|
155
|
+
best_idx = int(np.argmax(cached_dist))
|
|
156
|
+
best_dist = cached_dist[best_idx]
|
|
157
|
+
rep_indices.append(best_idx)
|
|
158
|
+
cached_dist[best_idx] = -1.0 # mark as selected
|
|
159
|
+
|
|
160
|
+
print(
|
|
161
|
+
f"Selected RP {k}/{n_rp}, max hull distance: "
|
|
162
|
+
f"{best_dist:.6f}"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return rep_indices
|