From 474d2dffbd17c991885d0d505ae1ec322f8f814d Mon Sep 17 00:00:00 2001 From: Jean-Christophe Dubacq Date: Thu, 24 Oct 2024 20:11:39 +0200 Subject: [PATCH] Refactorize debug and warning functions add switches to control optimization --- get.py | 219 +++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 142 insertions(+), 77 deletions(-) diff --git a/get.py b/get.py index 0c926fb..08f3e50 100755 --- a/get.py +++ b/get.py @@ -6,6 +6,7 @@ import csv import json import os import pdb # used for debugging +import inspect # used for debugging import random import sys @@ -26,6 +27,33 @@ def die(msg: str, status=3): sys.exit(status) +def debug(*args): + if not hasattr(debug, "counter"): + debug.counter = 1 # Initialize the counter if it doesn't exist + else: + debug.counter += 1 + caller_frame = inspect.currentframe().f_back + where = str(caller_frame.f_lineno) + "@" + caller_frame.f_code.co_name + if len(args) > 0: + print(f"[DEBUG {debug.counter}:{where}] " + str(args[0]), args[1:]) + else: + print(f"[DEBUG {debug.counter}:{where}] (no reason given)") + + +def warning(*args): + if len(args) > 0: + print("[WARNING] " + str(args[0]), args[1:]) + else: + print("[WARNING] (no reason given)") + + +def info(*args): + if len(args) > 0: + print("[INFO] " + str(args[0]), args[1:]) + else: + print("[INFO] (no reason given)") + + load_dotenv(".env") SCODOC_SERVER = os.environ.get("SCODOC_SERVER") or "http://localhost:5000" @@ -39,7 +67,7 @@ SCODOC_PASSWORD = os.environ.get("SCODOC_PASSWORD") or die( API_URL = f"{SCODOC_SERVER}/ScoDoc/api" # TODO : refactor globals -debug = True # Not used +DEBUG = True # Not used BLOCKING = True # Die if csv is incorrect # TODO : refactor / put globals in a class, eg Config @@ -47,7 +75,11 @@ depts = [] orderkey = "" -def blockordie(status=2): +def blockordie(reason: str = "", status: int = 2): + if reason: + print(reason) + else: + print("Blocking, no reason given") if BLOCKING: sys.exit(status) @@ -66,17 +98,52 @@ def cli_check(): parser = argparse.ArgumentParser(description="Process some departments.") parser.add_argument("--techno", action="store_true", help="Enable TECHNO mode") - parser.add_argument("depts", nargs="+", help="List of departments") + parser.add_argument("depts", nargs="*", help="List of departments") parser.add_argument( "--base", "-b", type=int, - choices=range(2000, 2667), + choices=range(2000, 2067), default=2021, help="base year for the cohort (integer between 2000 and 2666)", ) - + optimize_group = parser.add_mutually_exclusive_group() + optimize_group.add_argument( + "--reuse", action="store_true", help="Reuse mode, sets value to 0" + ) + optimize_group.add_argument( + "--optimize", + type=str, + nargs="?", + const="100", # Default value if --optimize is used without specifying n + help="Optimize mode, takes an optional integer (default is 100, or 300 if no optimization option specified)", + ) + optimize_group.add_argument( + "--restart", + type=str, + nargs="?", + const="300", # Default value if --restart is used without specifying n + help="Restart & Optimize mode, takes an optional integer (default is 300)", + ) args = parser.parse_args() + Options.restart = False + if args.reuse: + Options.optimize = 0 + elif args.restart is not None: + Options.restart = True + try: + Options.optimize = -int(args.restart) + except (TypeError, ValueError): + Options.optimize = -300 + if args.restart: + args.depts.insert(0, args.restart) + else: + try: + Options.optimize = int(args.optimize) + except (TypeError, ValueError): + Options.optimize = 300 + if args.optimize: + args.depts.insert(0, args.optimize) Options.base_year = args.base Options.techno = args.techno @@ -129,6 +196,8 @@ def conf_value(xkey: str): "year_separator": " ", "rank_separator": "", "diplome_separator": "", + "reuse": "yes", + "optimize": "yes", } if xkey in conf: return conf[xkey] @@ -176,8 +245,7 @@ def read_theme(): if len(row) == 0: continue elif len(row[0]) == 0: - print("Wrong line in theme : " + str(row)) - blockordie() + blockordie("Wrong line in theme : " + str(row)) elif row[0][0] == "#": continue else: @@ -210,8 +278,7 @@ def read_redirects(): if len(row) == 0: continue elif len(row[0]) == 0: - print("Wrong line in redirect : " + str(row)) - blockordie() + blockordie("Wrong line in redirect : " + str(row)) elif row[0][0] == "#": continue else: @@ -227,7 +294,7 @@ token = None def get_json(url: str, params=None): - print(f"Requesting {url}") + debug(f"Requesting {url}") global token if token == None: url_token = f"{API_URL}/tokens" @@ -237,18 +304,19 @@ def get_json(url: str, params=None): if response.status_code == 200: token = response.json().get("token") else: - print( - f"Erreur de récupération de token: {response.status_code} - {response.text}" + blockordie( + f"Erreur de récupération de token: {response.status_code} - {response.text}", + status=1, ) - sys.exit(1) headers = {"Authorization": f"Bearer {token}"} response = requests.get(url, headers=headers, params=params) if response.status_code == 200: # Afficher la réponse JSON return response.json() else: - print(f"Erreur avec {url}: {response.status_code} - {response.text}") - sys.exit(1) + blockordie( + f"Erreur avec {url}: {response.status_code} - {response.text}", status=1 + ) formsem_dept = {} @@ -337,6 +405,8 @@ def get_override(sem, xkey, default=None): def nick_replace( department, diplome, rank, modalite, parcours, nick, year=Options.base_year ): + if type(rank) != int: + rank = 0 if len(department) > 0: nick = nick.replace( "{department}", conf_value("department_separator") + department @@ -357,7 +427,7 @@ def nick_replace( nick = nick.replace("{diplomenobut}", conf_value("diplome_separator") + diplome) else: nick = nick.replace("{diplomenobut}", "") - if len(str(rank)) > 0: + if rank > 0: nick = nick.replace("{rank}", conf_value("rank_separator") + str(rank)) else: nick = nick.replace("{rank}", "") @@ -424,7 +494,6 @@ def analyse_student(semobj, etud, univ_year=None): modalite = None for g in goal: gg = g.split("=") - # print(f"Looking for {gg[0]} yielding {gg[1]} out of {groupes}") if gg[0] in groups: modalite = gg[1] nick = conf_value("nick") @@ -523,11 +592,11 @@ def analyse_depts(): continue if bucket in studentsummary["cursus"]: semestreerreur = int(bucket) + 1 - print( + warning( f"// Élève {etudid} dans deux semestres à la fois : S{semestreerreur}, semestres {studentsummary['cursus'][bucket]} et {semid}" ) if "dept" in studentsummary and studentsummary["dept"] != dept: - print( + warning( f"// Élève ayant changé de département {dept},{studentsummary['dept']}" ) # department, diplome, rank, modalite, parcours, nick = analyse_student( @@ -1044,7 +1113,8 @@ def crossweight(node_position, node_layer, edges): return w -def genetic_optimize(node_position, node_layer, edges): +def genetic_optimize(node_position, node_layer, edges, loops=1): + debug(f"Begin genetic optimization with {loops} loops") oldcandidates = [] l_indices = list(range(5)) lays = [] @@ -1055,6 +1125,7 @@ def genetic_optimize(node_position, node_layer, edges): for i in lays[index]: randomness_l.append(index) w = crossweight(node_position, node_layer, edges) + for i in range(20): oldcandidates.append([node_position.copy(), w]) w = crossweight(node_position, node_layer, edges) @@ -1070,7 +1141,7 @@ def genetic_optimize(node_position, node_layer, edges): k += 1 oldcandidates.append([n, w]) candidates = oldcandidates - for i in range(300): + for i in range(loops): oldcandidates = candidates oldcandidates.sort(key=lambda x: x[1]) candidates = oldcandidates[:30] @@ -1119,11 +1190,37 @@ def genetic_optimize(node_position, node_layer, edges): b = lays[i].copy() b.sort(key=lambda x: best[x]) orders.append(b) - print(orders) - print(candidates[0][1]) + debug(orders) + debug(candidates[0][1]) return orders +def ordernodes(layers, orders, edges): + node_position = {} + node_layer = {} + newls = [[], [], [], [], []] + if orders != {}: + for i in range(len(newls)): + ls = newls[i] + for node in orders[i]: + if node in layers[i]: + ls.append(node) + for node in layers[i]: + if node not in ls: + ls.append(node) + for layer, layernodes in enumerate(newls): + for j, n in enumerate(layernodes): + node_position[n] = j + node_layer[n] = layer + else: + for layer, layernodes in enumerate(layers): + for j, n in enumerate(layernodes): + node_position[n] = j + node_layer[n] = layer + debug(crossweight(node_position, node_layer, edges)) + return node_position, node_layer, newls + + def printsvg(): padding = 4 unit_ratio = 96 / 72 @@ -1164,64 +1261,31 @@ def printsvg(): else: node_structure[startnode]["next"].append([endnode, weight]) edges.append([startnode, endnode, weight]) - node_position = {} - node_layer = {} - layer_structure = [ - {"olayer": []}, - {"olayer": []}, - {"olayer": []}, - {"olayer": []}, - {"olayer": []}, - ] - - lastorders = read_conf("best-" + orderkey) - if lastorders != {}: - for i in range(5): - ls = layer_structure[i] - ord = lastorders[i] - for node in lastorders[i]: - if node in layers[i]: - ls["olayer"].append(node) - for node in layers[i]: - if node not in ls["olayer"]: - ls["olayer"].append(node) - for layer, layernodes in enumerate(layer_structure): - for j, n in enumerate(layernodes["olayer"]): - node_position[n] = j - node_layer[n] = layer - print(crossweight(node_position, node_layer, edges)) + filename = "best-" + orderkey + if Options.restart: + try: + os.remove(filename) + except OSError: + pass + if Options.optimize >= 0: + lastorders = read_conf(filename) else: - for layer, layernodes in enumerate(layers): - for j, n in enumerate(layernodes): - node_position[n] = j - node_layer[n] = layer - orders = genetic_optimize(node_position, node_layer, edges) + lastorders = {} + node_position, node_layer, newls = ordernodes(layers, lastorders, edges) + if Options.optimize != 0: + orders = genetic_optimize( + node_position, node_layer, edges, loops=abs(Options.optimize) + ) + else: + orders = newls write_conf("best-" + orderkey, orders) - layer_structure = [ - {"olayer": []}, - {"olayer": []}, - {"olayer": []}, - {"olayer": []}, - {"olayer": []}, - ] - for i in range(5): - ls = layer_structure[i] - ord = orders[i] - for node in orders[i]: - if node in layers[i]: - ls["olayer"].append(node) - for node in layers[i]: - if node not in ls["olayer"]: - ls["olayer"].append(node) - for layer, layernodes in enumerate(layer_structure): - for j, n in enumerate(layernodes["olayer"]): - node_position[n] = j - node_layer[n] = layer - print(crossweight(node_position, node_layer, edges)) + node_position, node_layer, newls = ordernodes(layers, orders, edges) + layer_structure = [] density = [] for i in range(5): - ls = layer_structure[i] - ls["num"] = len(ls["olayer"]) + ls = {} + ls["olayer"] = newls[i] + ls["num"] = len(newls[i]) ls["inout"] = 0 for j in ls["olayer"]: lhi = 0 @@ -1239,6 +1303,7 @@ def printsvg(): print(json.dumps(layer_structure, indent=2)) print(json.dumps(node_structure, indent=2)) ls["inout"] += k["size"] + layer_structure.append(ls) if height == 0: minheight = 0 for i in range(5):