1- # Copyright 2016-2023 Blue Marble Analytics LLC.
1+ # Copyright 2016-2025 Blue Marble Analytics LLC.
2+ # Copyright 2026 Sylvan Energy Analytics LLC.
23#
34# Licensed under the Apache License, Version 2.0 (the "License");
45# you may not use this file except in compliance with the License.
@@ -320,6 +321,11 @@ def run_optimization_for_subproblem_stage(
320321 prob_sol_files_directory = prob_sol_files_directory ,
321322 solution_filename = "gurobi_solution.json" ,
322323 )
324+ if parsed_arguments .load_highs_solution :
325+ solved_instance , results , dynamic_components = load_highs_xml_solution (
326+ prob_sol_files_directory = prob_sol_files_directory ,
327+ solution_filename = "highs_solution.sol" ,
328+ )
323329 else :
324330 dynamic_components , instance = create_problem (
325331 scenario_directory = scenario_directory ,
@@ -357,7 +363,7 @@ def run_optimization_for_subproblem_stage(
357363 symbol_map = instance .solutions .symbol_map [smap_id ]
358364
359365 symbol_cuid_pairs = tuple (
360- (symbol , ComponentUID (var_weakref () , cuid_buffer = {}))
366+ (symbol , ComponentUID (var_weakref , cuid_buffer = {}))
361367 for symbol , var_weakref in symbol_map .bySymbol .items ()
362368 )
363369
@@ -1638,8 +1644,9 @@ def load_cplex_xml_solution(
16381644 type_tag .get ("index" ),
16391645 type_tag .get ("value" ),
16401646 )
1641- if not var_id == "ONE_VAR_CONSTANT" :
1642- symbol_map .bySymbol [var_id ]().value = float (value )
1647+ # "x2" with value None added for CPLEXSolution version 1.2
1648+ if not var_id in ["ONE_VAR_CONSTANT" , "x2" ]:
1649+ symbol_map .bySymbol [var_id ].value = float (value )
16431650
16441651 # Constraints
16451652 for type_tag in root .findall ("linearConstraints/constraint" ):
@@ -1649,8 +1656,9 @@ def load_cplex_xml_solution(
16491656 type_tag .get ("dual" ),
16501657 )
16511658 if not constraint_id_w_extra_symbols == "c_e_ONE_VAR_CONSTANT" :
1652- constraint_id = constraint_id_w_extra_symbols [4 :- 1 ]
1653- instance .dual [symbol_map .bySymbol [constraint_id ]()] = float (dual )
1659+ # constraint_id = constraint_id_w_extra_symbols[4:-1]
1660+ constraint_id = constraint_id_w_extra_symbols
1661+ instance .dual [symbol_map .bySymbol [constraint_id ]] = float (dual )
16541662
16551663 # Solver status
16561664 header = root .findall ("header" )[0 ] # Need a check that there is only one element
@@ -1715,6 +1723,95 @@ def load_gurobi_json_solution(
17151723 return instance , results , dynamic_components
17161724
17171725
1726+ def load_highs_xml_solution (
1727+ prob_sol_files_directory , solution_filename = "highs_solution.sol"
1728+ ):
1729+ """
1730+ :param prob_sol_files_directory:
1731+ :param solution_filename:
1732+ :return:
1733+ """
1734+ print (
1735+ "Loading results from solution file {}..." .format (
1736+ os .path .join (prob_sol_files_directory , solution_filename )
1737+ )
1738+ )
1739+ instance , dynamic_components , symbol_map = load_problem_info (
1740+ prob_sol_files_directory = prob_sol_files_directory
1741+ )
1742+
1743+ # Read HiGHS solution file
1744+ with open (os .path .join (prob_sol_files_directory , solution_filename ), "r" ) as f :
1745+ lines = f .readlines ()
1746+
1747+ # Model status is th second line
1748+ model_status = lines [1 ].strip () if len (lines ) > 1 else "Unknown"
1749+ termination_condition = model_status .lower ()
1750+ solver_status = "ok" if termination_condition == "optimal" else "unknown"
1751+
1752+ # Parse the HiGHS solution file
1753+ section = None
1754+
1755+ for line in lines :
1756+ line = line .strip ()
1757+
1758+ # Skip empty lines and comments
1759+ if not line or line .startswith ("#" ):
1760+ # Check for section headers in comments
1761+ if line == "# Primal solution values" :
1762+ section = "primal_start"
1763+ elif "# Columns" in line and section == "primal_start" :
1764+ section = "primal_columns"
1765+ elif "# Rows" in line and section == "primal_columns" :
1766+ section = "primal_rows"
1767+ elif line == "# Dual solution values" :
1768+ section = "dual_start"
1769+ elif "# Columns" in line and section == "dual_start" :
1770+ section = "dual_columns"
1771+ elif "# Rows" in line and section in ["dual_start" , "dual_columns" ]:
1772+ section = "dual_rows"
1773+ elif line == "# Basis" :
1774+ # Stop parsing once we reach basis section
1775+ break
1776+ continue
1777+
1778+ # Skip non-data lines
1779+ if line in ["Model status" , "Feasible" , "Valid" ]:
1780+ continue
1781+ if line .startswith ("Objective " ):
1782+ continue
1783+
1784+ # Parse primal variable values (x variables only)
1785+ if section == "primal_columns" :
1786+ parts = line .split ()
1787+ if len (parts ) == 2 :
1788+ var_id , value = parts [0 ], parts [1 ]
1789+ if (
1790+ var_id .startswith ("x" )
1791+ and var_id in symbol_map .bySymbol
1792+ and var_id not in ["ONE_VAR_CONSTANT" , "x2" ]
1793+ ):
1794+ symbol_map .bySymbol [var_id ].value = float (value )
1795+
1796+ # Parse constraint dual values (c_ constraints only) from dual rows
1797+ elif section == "dual_rows" :
1798+ parts = line .split ()
1799+ if len (parts ) == 2 :
1800+ constraint_id , dual = parts [0 ], parts [1 ]
1801+ if (
1802+ constraint_id .startswith ("c_" )
1803+ and constraint_id in symbol_map .bySymbol
1804+ and constraint_id != "c_e_ONE_VAR_CONSTANT"
1805+ ):
1806+ instance .dual [symbol_map .bySymbol [constraint_id ]] = float (dual )
1807+
1808+ results = Results (
1809+ solver_status = solver_status , termination_condition = termination_condition
1810+ )
1811+
1812+ return instance , results , dynamic_components
1813+
1814+
17181815def load_problem_info (prob_sol_files_directory ):
17191816 with open (
17201817 os .path .join (prob_sol_files_directory , "instance.pickle" ), "rb"
0 commit comments