|
| 1 | +import os |
| 2 | +import jijmodeling as jm |
| 3 | +import glob |
| 4 | +from ommx.artifact import ArtifactBuilder |
| 5 | +from model import build_ip_formulation |
| 6 | +from sol_reader import parse_solution_zfx |
| 7 | + |
| 8 | + |
| 9 | +def _pick_solution_file(sol_dir: str, base: str) -> str | None: |
| 10 | + """Pick first existing solution file for a basename.""" |
| 11 | + candidates = [ |
| 12 | + os.path.join(sol_dir, f"{base}.opt.sol"), |
| 13 | + os.path.join(sol_dir, f"{base}.best.sol"), |
| 14 | + os.path.join(sol_dir, f"{base}.bst.sol"), |
| 15 | + os.path.join(sol_dir, f"{base}.sol"), |
| 16 | + ] |
| 17 | + for p in candidates: |
| 18 | + if os.path.exists(p): |
| 19 | + return p |
| 20 | + return None |
| 21 | + |
| 22 | + |
| 23 | +def batch_process( |
| 24 | + sol_root: str = "../../solutions", |
| 25 | + output_directory: str = "./ommx_output", |
| 26 | +): |
| 27 | + """ |
| 28 | + Process instances from a QBench JSON file and corresponding solution files, |
| 29 | + convert them into OMMX artifacts, and save them to the output directory. |
| 30 | +
|
| 31 | + Parameters: |
| 32 | + sol_root (str): Path to the root solutions directory. |
| 33 | + output_directory (str): Path to save the generated .ommx files. |
| 34 | + """ |
| 35 | + os.makedirs(output_directory, exist_ok=True) |
| 36 | + |
| 37 | + problem = build_ip_formulation() |
| 38 | + |
| 39 | + processed_count = 0 |
| 40 | + error_count = 0 |
| 41 | + |
| 42 | + # scan all sol_root's .sol file and get the base name. |
| 43 | + sol_files = glob.glob(os.path.join(sol_root, "*.sol")) |
| 44 | + bases = set() |
| 45 | + for path in sol_files: |
| 46 | + name = os.path.basename(path) |
| 47 | + for suffix in [".opt.sol", ".best.sol", ".bst.sol", ".sol"]: |
| 48 | + if name.endswith(suffix): |
| 49 | + bases.add(name[: -len(suffix)]) |
| 50 | + break |
| 51 | + |
| 52 | + print(f"Found {len(bases)} bases: {sorted(bases)}") |
| 53 | + |
| 54 | + def cut_matrix(t: list[list[int]], n: int) -> list[list[int]]: |
| 55 | + if not (5 <= n <= 24): |
| 56 | + raise ValueError("n must be between 5 and 24.") |
| 57 | + return [row[:n] for row in t[:n]] |
| 58 | + |
| 59 | + your_t_0based = [ |
| 60 | + [0, 24, 43, 23, 21, 41, 61, 21, 20, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 24, 19, 23, 0], |
| 61 | + [0, 0, 0, 0, 21, 18, 39, 23, 0, 0, 40, 19, 64, 19, 17, 64, 80, 0, 0, 22, 24, 18, 19, 0], |
| 62 | + [16, 0, 0, 20, 44, 0, 42, 22, 20, 0, 0, 23, 21, 0, 40, 21, 38, 97, 22, 17, 20, 37, 17, 20], |
| 63 | + [42, 20, 18, 0, 40, 57, 43, 42, 0, 0, 19, 17, 0, 39, 0, 0, 22, 41, 0, 0, 17, 42, 40, 43], |
| 64 | + [0, 0, 0, 40, 0, 83, 60, 0, 44, 0, 37, 60, 0, 40, 0, 0, 40, 19, 0, 39, 41, 17, 0, 0], |
| 65 | + [60, 18, 0, 37, 18, 0, 21, 41, 23, 39, 0, 63, 60, 39, 0, 19, 0, 16, 16, 0, 0, 40, 0, 16], |
| 66 | + [22, 0, 0, 0, 0, 0, 0, 61, 36, 80, 96, 19, 19, 41, 16, 0, 0, 0, 22, 0, 43, 0, 44, 22], |
| 67 | + [0, 0, 20, 19, 17, 20, 40, 0, 0, 60, 61, 0, 20, 62, 20, 0, 0, 38, 0, 0, 0, 0, 24, 22], |
| 68 | + [21, 22, 38, 0, 44, 20, 40, 39, 0, 36, 22, 21, 19, 39, 19, 0, 0, 21, 24, 16, 23, 21, 37, 0], |
| 69 | + [0, 24, 23, 39, 20, 0, 0, 41, 0, 0, 0, 22, 0, 0, 44, 42, 22, 42, 22, 19, 20, 58, 18, 0], |
| 70 | + [60, 57, 0, 0, 16, 0, 16, 37, 0, 0, 0, 44, 63, 0, 18, 0, 17, 18, 0, 0, 0, 100, 24, 23], |
| 71 | + [0, 44, 44, 0, 23, 17, 39, 21, 0, 17, 40, 0, 24, 78, 17, 24, 20, 18, 0, 24, 24, 0, 20, 0], |
| 72 | + [23, 16, 0, 0, 0, 23, 0, 0, 0, 0, 43, 58, 0, 0, 24, 60, 0, 0, 19, 0, 21, 0, 20, 0], |
| 73 | + [44, 20, 0, 19, 21, 0, 39, 19, 0, 0, 0, 39, 22, 0, 0, 64, 24, 22, 0, 39, 0, 43, 42, 16], |
| 74 | + [0, 60, 37, 18, 0, 0, 0, 20, 0, 41, 43, 16, 43, 24, 0, 0, 18, 18, 0, 44, 20, 0, 21, 37], |
| 75 | + [0, 0, 23, 39, 0, 24, 40, 0, 37, 0, 40, 20, 44, 43, 0, 0, 0, 0, 0, 16, 0, 59, 0, 0], |
| 76 | + [0, 42, 0, 0, 23, 24, 38, 19, 36, 0, 20, 60, 57, 0, 23, 40, 0, 0, 0, 16, 42, 0, 23, 0], |
| 77 | + [0, 41, 36, 43, 23, 41, 17, 0, 38, 0, 0, 21, 21, 17, 16, 16, 39, 0, 22, 0, 21, 23, 16, 23], |
| 78 | + [17, 0, 23, 23, 20, 0, 17, 58, 17, 0, 20, 0, 17, 24, 0, 0, 17, 42, 0, 58, 19, 22, 0, 24], |
| 79 | + [42, 0, 16, 0, 43, 0, 24, 36, 0, 16, 24, 41, 41, 0, 24, 0, 0, 0, 0, 0, 56, 38, 63, 19], |
| 80 | + [37, 23, 23, 0, 42, 16, 23, 76, 23, 0, 0, 24, 20, 41, 20, 24, 40, 23, 0, 0, 0, 39, 20, 0], |
| 81 | + [43, 20, 17, 17, 0, 20, 19, 0, 80, 0, 0, 0, 40, 0, 40, 16, 19, 0, 0, 0, 18, 0, 17, 0], |
| 82 | + [18, 20, 44, 40, 21, 18, 0, 20, 0, 0, 16, 24, 0, 0, 19, 18, 0, 17, 23, 0, 23, 44, 0, 42], |
| 83 | + [44, 0, 0, 0, 62, 0, 17, 41, 0, 0, 0, 63, 0, 37, 22, 0, 20, 0, 0, 19, 57, 18, 38, 0], |
| 84 | + ] |
| 85 | + |
| 86 | + for base in sorted(bases): |
| 87 | + # from base to obtain n, such like network05 → n=5 |
| 88 | + try: |
| 89 | + n_str = "".join(ch for ch in base if ch.isdigit()) |
| 90 | + n_val = int(n_str) if n_str else 5 |
| 91 | + except Exception: |
| 92 | + print(f"[{base}] Cannot parse n from base, skip.") |
| 93 | + continue |
| 94 | + |
| 95 | + try: |
| 96 | + instance_data = { |
| 97 | + "n": n_val, |
| 98 | + "t": cut_matrix(your_t_0based, n_val), |
| 99 | + "M": 1000, |
| 100 | + "intscale": 1000, |
| 101 | + } |
| 102 | + |
| 103 | + interpreter = jm.Interpreter(instance_data) |
| 104 | + ommx_instance = interpreter.eval_problem(problem) |
| 105 | + |
| 106 | + sol_path = _pick_solution_file(sol_root, base) |
| 107 | + solution = None |
| 108 | + if sol_path: |
| 109 | + try: |
| 110 | + print(f" → Evaluating solution: {sol_path}") |
| 111 | + solution_dict = parse_solution_zfx(sol_path, n_val) |
| 112 | + solution = ommx_instance.evaluate(solution_dict) |
| 113 | + if ( |
| 114 | + solution.feasible |
| 115 | + and abs(solution.objective - solution_dict[0]) < 1e-6 |
| 116 | + ): |
| 117 | + print( |
| 118 | + f" objective={solution.objective}, feasible={solution.feasible}" |
| 119 | + ) |
| 120 | + else: |
| 121 | + print( |
| 122 | + " ! Objective mismatch or infeasible; will save instance only." |
| 123 | + ) |
| 124 | + solution = None |
| 125 | + except Exception as sol_err: |
| 126 | + print(f" ! Solution evaluation failed: {sol_err}") |
| 127 | + solution = None |
| 128 | + |
| 129 | + out_path = os.path.join(output_directory, f"{base}.ommx") |
| 130 | + if os.path.exists(out_path): |
| 131 | + os.remove(out_path) |
| 132 | + |
| 133 | + builder = ArtifactBuilder.new_archive_unnamed(out_path) |
| 134 | + builder.add_instance(ommx_instance) |
| 135 | + if solution is not None: |
| 136 | + builder.add_solution(solution) |
| 137 | + builder.build() |
| 138 | + |
| 139 | + print(f" ✓ Created: {out_path}") |
| 140 | + print("-" * 50) |
| 141 | + processed_count += 1 |
| 142 | + |
| 143 | + except Exception as e: |
| 144 | + print(f"[{base}] Error: {e}") |
| 145 | + print("-" * 50) |
| 146 | + error_count += 1 |
| 147 | + |
| 148 | + print(f"Batch complete — processed: {processed_count}, errors: {error_count}") |
| 149 | + |
| 150 | + |
| 151 | +if __name__ == "__main__": |
| 152 | + batch_process( |
| 153 | + sol_root="../../solutions", |
| 154 | + output_directory="./ommx_output", |
| 155 | + ) |
0 commit comments