Skip to content

Instantly share code, notes, and snippets.

@CapacitorSet
Created August 17, 2024 19:21
Show Gist options
  • Save CapacitorSet/c1337c582c9af8704eeaef6ce0822ad2 to your computer and use it in GitHub Desktop.
Save CapacitorSet/c1337c582c9af8704eeaef6ce0822ad2 to your computer and use it in GitHub Desktop.

Revisions

  1. CapacitorSet created this gist Aug 17, 2024.
    173 changes: 173 additions & 0 deletions scheduler.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,173 @@
    import itertools
    from ortools.sat.python import cp_model

    settimana = ("lun", "mar", "mer", "gio", "ven", "sab", "dom")
    slots = ("7:00", "9:00", "12:00", "16:00", "19:30")
    categorie = (
    "news",
    "trucchi",
    "accadde",
    "proverbi",
    "barzellette",
    "sondaggi",
    "ricette",
    "parole",
    "petizioni",
    )


    model = cp_model.CpModel()

    vars = []
    combi: dict[tuple[str, str, str], cp_model.IntVar] = {}
    for day in settimana:
    day_vars = []
    for time in slots:
    time_vars = []
    for cat in categorie:
    var = model.new_bool_var(day + "_" + time + "_" + cat)
    combi[day, time, cat] = var
    time_vars.append(var)

    # In ogni slot è postato un solo messaggio
    model.add_exactly_one(time_vars)

    # Ogni categoria è postata almeno una volta
    for cat in categorie:
    model.add_at_least_one(combi[day, time, cat] for day in settimana for time in slots)

    for day in settimana:
    # Ogni giorno ci sono 3 news
    model.add_abs_equality(3, sum(combi[day, time, "news"] for time in slots))

    # Al più uno di ciascuna categoria eccetto le news
    non_news = categorie[1:]
    for cat in non_news:
    model.add_at_most_one(combi[day, time, cat] for time in slots)

    # Accadde, proverbi e parole sono solo alle 7
    model.add_abs_equality(
    0,
    sum(
    combi[day, time, cat]
    for day in settimana
    for time in slots
    for cat in ("accadde", "proverbi", "parole")
    if time != "7:00"
    ),
    )
    # Accadde, proverbi e parole sono bilanciati:
    # c'è al più una differenza di 1 tra le occorrenze di ciascuno
    for pair in itertools.combinations(("accadde", "proverbi", "parole"), 2):
    cat1, cat2 = pair
    model.add_linear_constraint(
    sum(combi[day, "7:00", cat1] for day in settimana)
    - sum(combi[day, "7:00", cat2] for day in settimana),
    -1,
    +1,
    )
    # Ricette sono solo domenica alle 12
    model.add_abs_equality(
    0,
    sum(
    combi[day, time, "ricette"]
    for day in settimana
    for time in slots
    if day != "dom" or time != "12:00"
    ),
    )
    # Barzellette sono solo alle 16
    model.add_abs_equality(
    0,
    sum(
    combi[day, time, "barzellette"]
    for day in settimana
    for time in slots
    if time != "16:00"
    ),
    )
    # Trucchi e sondaggi sono solo alle 19
    model.add_abs_equality(
    0,
    sum(
    combi[day, time, cat]
    for day in settimana
    for time in slots
    for cat in ("trucchi", "sondaggi")
    if time != "19:30"
    ),
    )
    # Petizioni sono solo alle 16 o alle 19:30
    model.add_abs_equality(
    0,
    sum(
    combi[day, time, "petizioni"]
    for day in settimana
    for time in slots
    if time != "16:00" and time != "19:30"
    ),
    )
    # Trucchi, sondaggi e petizioni (rubriche "serali") sono bilanciati:
    # c'è al più una differenza di 1 tra le occorrenze di ciascuno
    for pair in itertools.combinations(("trucchi", "sondaggi", "petizioni"), 2):
    cat1, cat2 = pair
    model.add_linear_constraint(
    sum(combi[day, time, cat1] for day in settimana for time in slots)
    - sum(combi[day, time, cat2] for day in settimana for time in slots),
    -1,
    +1,
    )

    # Non ci sono mai 3 news di fila la mattina
    for day in settimana:
    model.add_linear_constraint(
    sum(combi[day, time, "news"] for time in slots[0:3]), 0, 2
    )
    # Commented out: se attivato rende la schedule impossibile
    # model.add_linear_constraint(
    # sum(combi[day, time, "news"] for time in slots[1:4]), 0, 2
    # )
    model.add_linear_constraint(
    sum(combi[day, time, "news"] for time in slots[2:5]), 0, 2
    )

    # Non ci sono mai due categorie alla stessa ora in due giorni adiacenti,
    # eccetto le news
    for cat in categorie[1:]:
    for time in slots:
    for idx in range(0, 6):
    model.add_at_most_one(
    combi[day, time, cat] for day in settimana[idx : idx + 2]
    )


    cost_fn = 0
    for key, value in combi.items():
    day, time, cat = key
    # Penalizziamo le news per favorire le altre categorie
    if cat == "news":
    cost_fn -= 1 * value
    else:
    cost_fn -= 2 * value

    model.minimize(cost_fn)
    solver = cp_model.CpSolver()


    status = solver.solve(model)

    if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
    # print(f"Total cost = {solver.objective_value}\n")
    # print(solver._solution)
    # print(f"{len(combi)} variables.")
    for idx, var in enumerate(combi.keys()):
    if solver._solution.solution[idx]:
    day, time, cat = var
    print(f'"{day} {time}": "{cat}",')
    # print(f"{day.capitalize()} alle {time}: {cat}")
    # if time == slots[-1]:
    # print()
    elif status == cp_model.INFEASIBLE:
    print("No solution found")
    else:
    print("Something is wrong, check the status and the log of the solve")