WUT_Computer_Science/NotProgramming/MOM/report_three/model.py
Krzysztof kuhy Rudnicki cc06bc0cd8 EARIN: zero findings, verified against byte-identical program output
749 findings across six labs and two projects. The mechanical half was
scripted; everything below is what the scripts got wrong or could not do, and
how it was caught.

Three regressions I introduced and then found by RUNNING the code, not by
reading it:

- Converting print() to logger.info() made the programs SILENT. logging's
  last-resort handler only emits WARNING and above, so with no basicConfig
  call `lab2/main.py -t 1 1` printed nothing at all. Fifteen files across the
  repo now configure logging where they log.
- The same conversion dropped `end=""`. lab2 draws its board one square at a
  time and lab1 draws the maze one cell at a time; both came out one glyph
  per line. Those thirteen call sites are sys.stdout.write again, and the
  rendered board now diffs byte-identically against the pre-change output.
- The B007 fix renamed `col` to `_col` in the FIRST loop it matched, which
  was print_letters -- where the variable IS used. That would have been a
  NameError on the first call.

And one the scripts caused silently: annotating 110 signatures dropped the
DEFAULT VALUES from 22 of them, so `preprocessing(a, b, USER_THRESHOLD,
ANIME_THRESHOLD)` would have raised TypeError. Every signature is now diffed
against the pre-change version, name by name and default by default: zero
mismatches.

Real defects the lint surfaced:

- `alpha_beta` ended with an unreachable `return None` that violated its own
  return type. Its maximising and minimising halves were mirror images, so
  they are one `_search_branch` now (C901 11 -> ok).
- `get_data`'s `gpu` switch, `predict`'s `auto`, `create_model`'s
  `rows_number` and five of `preprocess_model_predict`'s fifteen parameters
  were never read. Gone, with their call sites.
- np.random.* -> Generator throughout (NPY002), `.values` -> `.to_numpy()`,
  `datetime.now()` with a tzinfo, `inplace=True` removed, `except:` given a
  type, `random.*` -> `SystemRandom` (S311).
- print_board's three nested helpers became methods (C901 17 -> ok);
  user_input's nine-branch if-ladder became a table (C901 12 -> ok).

Verification, all by running: lab1 `-t` and lab2 `-t 1 1` produce output
byte-identical to the pre-change code; lab2's rendered 8x8 board diffs clean;
lab3's argument parsing returns the same dict for the same argv; lab4 fails
identically before and after (no sklearn here). Also included: the logging
configuration for NLP, MOM and ECRYPT_PROJECT, which had the same silence bug.

Repo-wide: 2,775 findings -> 1,883.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172dj34FDpao6wsix8ZuCzE
2026-08-28 20:39:53 +02:00

169 lines
5.0 KiB
Python
Executable File

#!/usr/bin/env python3
"""Bi-criteria distribution model for MOM project 3 (data set 1.6).
A PuLP transport problem over two factories, four warehouses and six
customers, minimising distribution cost while maximising customer
satisfaction. It is solved three ways: once on cost alone, then swept over
alpha in 0.0..1.0 to trace the efficient frontier, then at the five
weightings the report tabulates.
"""
import logging
import sys
import pulp
logger = logging.getLogger(__name__)
logging.basicConfig(format="%(message)s", level=logging.INFO)
# alpha runs over 0.0, 0.1 ... 1.0, so eleven steps.
ALPHA_STEPS = 11
# Create a problem variable:
model = pulp.LpProblem("Optimal_Distribution", pulp.LpMinimize)
# Zdefiniowanie dostawcy (fabryki and magazyny)
fabryki = ["F1", "F2"]
magazyny = ["M1", "M2", "M3", "M4"]
dostawcy = fabryki + magazyny # Combining both lists
# Zdefiniowanie klientów
klienci = ["K1", "K2", "K3", "K4", "K5", "K6"]
koszt = {
"F1": {
"M1": 0.3,
"M2": 0.5,
"M3": 1.2,
"M4": 0.8,
"K1": 1.2,
"K2": 999,
"K3": 1.2,
"K4": 2.0,
"K5": 999,
"K6": 1.1,
},
"F2": {
"M1": 999,
"M2": 0.4,
"M3": 0.5,
"M4": 0.3,
"K1": 1.8,
"K2": 999,
"K3": 999,
"K4": 999,
"K5": 999,
"K6": 999,
},
"M1": {"K1": 999, "K2": 1.2, "K3": 0.2, "K4": 1.7, "K5": 999, "K6": 2.0},
"M2": {"K1": 1.4, "K2": 0.3, "K3": 1.8, "K4": 1.3, "K5": 0.5, "K6": 999},
"M3": {"K1": 999, "K2": 1.3, "K3": 2.0, "K4": 999, "K5": 0.3, "K6": 1.4},
"M4": {"K1": 999, "K2": 999, "K3": 0.4, "K4": 2.0, "K5": 0.5, "K6": 1.6},
}
P = pulp.LpVariable.dicts(
"P", [(i, j) for i in dostawcy for j in klienci], cat="Binary"
)
maksymalne_zamowienie = 60
poziom_satysfakcji = {
"K1": 50 / maksymalne_zamowienie,
"K2": 10 / maksymalne_zamowienie,
"K3": 40 / maksymalne_zamowienie,
"K4": 35 / maksymalne_zamowienie,
"K5": 60 / maksymalne_zamowienie,
"K6": 20 / maksymalne_zamowienie,
}
preferencja_klienta = {
"K1": ["F2"],
"K2": ["M1"],
"K3": ["M2", "M3"],
"K4": ["F1"],
"K5": [],
"K6": ["M3", "M4"],
}
suma_satysfakcji = pulp.lpSum(
[poziom_satysfakcji[j] * P[(i, j)] for i in dostawcy for j in klienci]
)
# Zmienne decyzyjne
x = pulp.LpVariable.dicts(
"x", [(i, j) for i in dostawcy for j in klienci], lowBound=0, cat="Integer"
)
y = pulp.LpVariable.dicts(
"y", [(i, k) for i in fabryki for k in magazyny], lowBound=0, cat="Integer"
)
# Funkcje Celu
koszt_dystrybucji = pulp.lpSum(
[koszt[i][j] * x[(i, j)] for i in dostawcy for j in klienci]
)
koszt_magazynowania = pulp.lpSum(
[koszt[i][k] * y[(i, k)] for i in fabryki for k in magazyny]
)
alpha = 0.5
beta = 0.5
model += alpha * (koszt_dystrybucji + koszt_magazynowania) - beta * suma_satysfakcji
mozliwosci_fabryki = {"F1": 150, "F2": 200}
pojemnosc_magazynu = {"M1": 70, "M2": 50, "M3": 100, "M4": 40}
zamowienia_klientow = {"K1": 50, "K2": 10, "K3": 40, "K4": 35, "K5": 60, "K6": 20}
for i in fabryki:
model += (
pulp.lpSum([x[(i, j)] for j in klienci] + [y[(i, k)] for k in magazyny])
<= mozliwosci_fabryki[i]
)
for k in magazyny:
model += pulp.lpSum([x[(k, j)] for j in klienci]) <= pojemnosc_magazynu[k]
for j in klienci:
model += pulp.lpSum([x[(i, j)] for i in dostawcy]) == zamowienia_klientow[j]
model.solve()
for v in model.variables():
logger.info("%s = %s", v.name, v.varValue)
koszt_wyniki = []
zadowolenie_wyniki = []
wyniki_funkcji = []
maksymalny_wynik = 0
for step in range(ALPHA_STEPS):
alpha = step / 10.0
beta = (ALPHA_STEPS - 1 - step + sys.float_info.epsilon) / 10.0
# Update objective function
model.objective = alpha * koszt_dystrybucji - beta * suma_satysfakcji
# Solve the model
model.solve()
logger.info("%s", alpha)
# Record the wyniki
calkowity_koszt = pulp.value(koszt_dystrybucji)
calkowite_zadowolenie = pulp.value(suma_satysfakcji)
wynik_funkcji = pulp.value(alpha * koszt_dystrybucji - beta * suma_satysfakcji)
koszt_wyniki.append(calkowity_koszt)
zadowolenie_wyniki.append(calkowite_zadowolenie)
maksymalny_wynik = max(maksymalny_wynik, wynik_funkcji)
wyniki_funkcji.append(wynik_funkcji)
logger.info("maksymalny_wynik %s %s", maksymalny_wynik, wyniki_funkcji)
# Pseudo-code, assuming model setup as previously discussed
scemariusze = [
(1.0, sys.float_info.epsilon),
(0.8, 0.2),
(0.5, 0.5),
(0.2, 0.8),
(sys.float_info.epsilon, 1.0),
]
wyniki = []
for alpha, beta in scemariusze:
model.objective = alpha * koszt_dystrybucji - beta * suma_satysfakcji
model.solve()
calkowity_koszt = pulp.value(koszt_dystrybucji)
calkowite_zadowolenie = pulp.value(suma_satysfakcji)
wyniki.append((alpha, beta, calkowity_koszt, calkowite_zadowolenie))
for idx, (alpha, beta, koszt, zadowolenie) in enumerate(wyniki):
logger.info("Krok %d:", idx + 1)
logger.info(" koszt: %s, zadowolenie: %s", alpha, beta)
logger.info(" Calkowity koszt: %s, zadowolenie klienta: %s\n", koszt, zadowolenie)