Introduzione
Immagina un’azienda che produce turbine eoliche su commessa. Ogni turbina richiede migliaia di componenti, alcuni dei quali sono unici e realizzati su misura. Le commesse durano anni e la manutenzione delle turbine installate è essenziale per garantire la produzione di energia. Un guasto a un componente critico può fermare la turbina, causando perdite significative.
In questo scenario, la gestione efficiente delle scorte di ricambio è vitale. L’azienda deve prevedere la domanda futura, ottimizzare la produzione e lo stoccaggio, e garantire che i componenti giusti siano disponibili al momento giusto. L’ottimizzazione diventa cruciale per bilanciare i costi di produzione e stoccaggio con il rischio di fermi macchina.
Lo scenario nel dettaglio
Una multinazionale di ingegneria elettrica produce su commessa per pochi clienti, con progetti multiannuali. La gestione delle parti di ricambio è fondamentale per evitare rotture di stock, dando priorità alle commesse più vecchie. L’azienda deve ottimizzare la produzione e lo stoccaggio delle parti di ricambio considerando: capacità produttiva, capacità di magazzino, costo di produzione e di stoccaggio, domanda specifica per ogni commessa, turnover delle scorte. L’obiettivo del modello è minimizzare i costi mantenendo un equilibrio tra produzione, domanda e capacità logistiche.
2. Definizione del Problema
2.1 Obiettivi
Il problema è un modello di programmazione lineare intera con i seguenti obiettivi:
- Minimizzare il costo totale (produzione + stoccaggio).
- Soddisfare la domanda prevista senza rotture di stock.
- Dare priorità alle commesse più vecchie.
- Limitare la produzione in base alla capacità produttiva.
- Limitare lo spazio occupato in base alla capacità del magazzino.
- Mantenere un turnover delle scorte, evitando accumuli eccessivi.
2.2 Variabili Decisionali
- Produzione: [math]x_{c,p}[/math] (quantità prodotta della parte [math]p[/math] per la commessa [math]c[/math]).
- Scorte finali: [math]S_p[/math] (quantità della parte [math]p[/math] in magazzino dopo la domanda).
Entrambe le variabili sono intere non negative.
3. Formulazione Matematica
3.1 Notazione
| Simbolo | Descrizione |
|---|---|
| [math]C[/math] | Insieme delle commesse |
| [math]P[/math] | Insieme delle parti di ricambio |
| [math]D_{c,p}[/math] | Domanda della parte [math]p[/math] per la commessa [math]c[/math] |
| [math]x_{c,p}[/math] | Quantità prodotta della parte [math]p[/math] per la commessa [math]c[/math] |
| [math]S_p[/math] | Scorte finali della parte [math]p[/math] in magazzino |
| [math]C_{prod,p}[/math] | Costo unitario di produzione della parte [math]p[/math] |
| [math]C_{stocc,p}[/math] | Costo unitario di stoccaggio della parte [math]p[/math] |
| [math]V_p[/math] | Volume unitario della parte [math]p[/math] |
| [math]S_{att,p}[/math] | Scorte iniziali disponibili della parte [math]p[/math] |
| [math]C_{tot}[/math] | Capacità massima di produzione nel periodo |
| [math]C_{p,p}[/math] | Capacità massima di produzione per singola parte |
| [math]C_{mag}[/math] | Capacità massima del magazzino |
3.2 Funzione Obiettivo
Minimizziamo il costo totale:
[math]\min \sum_{c \in C} \sum_{p \in P} C_{prod,p} \cdot x_{c,p} + \sum_{p \in P} C_{stocc,p} \cdot S_p[/math]
3.3 Vincoli
- Soddisfazione della domanda:[math]\sum_{c \in C} x_{c,p} + S_{att,p} = \sum_{c \in C} D_{c,p} + S_p, \quad \forall p \in P[/math]
- Priorità alle commesse più vecchie:[math]\sum x_{c_i, p} \geq \sum x_{c_{i+1}, p}, \quad \forall p \in P, \forall i = 1, \dots, |C|-1[/math]
- Capacità produttiva totale:[math]\sum_{c \in C} \sum_{p \in P} x_{c,p} \leq C_{tot}[/math]
- Capacità produttiva per singola parte:[math]\sum_{c \in C} x_{c,p} \leq C_{p,p}, \quad \forall p \in P[/math]
- Capacità del magazzino:[math]\sum_{p \in P} V_p \cdot S_p \leq C_{mag}[/math]
- Turnover delle scorte:[math]S_p \leq 0.3 \cdot \sum_{c \in C} x_{c,p}, \quad \forall p \in P[/math]
Implementazione in Python
L’implementazione di un modello di ottimizzazione come il seguente offre numerosi vantaggi:
- Riduzione dei costi: Minimizzando i costi di produzione e stoccaggio, l’azienda può aumentare la sua competitività.
- Aumento dell’efficienza: Ottimizzando la produzione e la gestione delle scorte, l’azienda può ridurre i tempi di consegna e migliorare la sua capacità di risposta alla domanda.
- Maggiore soddisfazione del cliente: Garantendo la disponibilità dei componenti di ricambio, l’azienda può ridurre i tempi di fermo macchina e aumentare la soddisfazione del cliente.
- Migliore pianificazione: Il modello fornisce una base solida per la pianificazione della produzione e la gestione delle scorte, consentendo all’azienda di prendere decisioni più informate.
- Riduzione degli sprechi: Un’ottimizzazione efficace riduce la probabilità di sovrapproduzione o obsolescenza delle scorte.
L’implementazione usa PuLP, una libreria per la programmazione lineare in Python.
Il codice segue la formulazione sopra e include:
Dati di input e definizione delle variabili per l’ottimizzazione della produzione di parti di ricambio
1. Dati di Input
Commesse
C1, C2, C3 (ordinate dalla più vecchia alla più recente).
Parti di ricambio
P1, P2, P3, P4.
Domanda
La quantità richiesta di ciascuna parte per ogni commessa è definita come segue:
| Commessa | P1 | P2 | P3 | P4 |
|---|---|---|---|---|
| C1 | 10 | 5 | 7 | 3 |
| C2 | 8 | 6 | 5 | 2 |
| C3 | 6 | 4 | 3 | 1 |
Costi di produzione
Il costo per produrre una unità di ciascuna parte è:
| Parte | Costo di produzione |
|---|---|
| P1 | 2.0 |
| P2 | 3.0 |
| P3 | 1.5 |
| P4 | 4.0 |
Costi di stoccaggio
Il costo per mantenere in magazzino una unità di ciascuna parte è:
| Parte | Costo di stoccaggio |
|---|---|
| P1 | 0.5 |
| P2 | 0.3 |
| P3 | 0.2 |
| P4 | 0.7 |
Scorte attuali
La quantità di ciascuna parte attualmente disponibile in magazzino è:
| Parte | Scorte attuali |
|---|---|
| P1 | 5 |
| P2 | 3 |
| P3 | 2 |
| P4 | 1 |
Capacità produttiva
-
- Capacità totale di produzione: 50 unità.
- Capacità per singola parte:
| Parte | Capacità produttiva |
|---|---|
| P1 | 20 |
| P2 | 15 |
| P3 | 10 |
| P4 | 5 |
Capacità del magazzino
-
- Capacità totale del magazzino: 100 unità di volume.
- Volume occupato da ciascuna parte:
| Parte | Volume per unità |
|---|---|
| P1 | 3 |
| P2 | 2 |
| P3 | 1 |
| P4 | 4 |
2. Definizione delle Variabili
produzione[c, p]: Quantità di parte [math]p[/math] prodotta per la commessa [math]c[/math] (variabile intera).
Esempio:produzione["C1", "P1"]rappresenta la quantità di P1 prodotta per C1.scorte_finali[p]: Quantità di parte [math]p[/math] rimanente in magazzino alla fine del periodo (variabile intera).
Esempio:scorte_finali["P1"]rappresenta la quantità di P1 in magazzino alla fine del periodo.
3. Funzione Obiettivo
Minimizzare i costi totali, che includono:
Costi di produzione:
[math]\sum_{c \in \text{commesse}} \sum_{p \in \text{parti}} \text{costo_produzione}[p] \cdot \text{produzione}[c, p][/math]
Costi di stoccaggio:
[math]\sum_{p \in \text{parti}} \text{costo_stoccaggio}[p] \cdot \text{scorte_finali}[p][/math]
La funzione obiettivo è:
Minimizzare
[math]2.0 \cdot \text{produzione}[C1, P1] + 3.0 \cdot \text{produzione}[C1, P2] + 1.5 \cdot \text{produzione}[C1, P3] + 4.0 \cdot \text{produzione}[C1, P4] +[/math]
[math]2.0 \cdot \text{produzione}[C2, P1] + 3.0 \cdot \text{produzione}[C2, P2] + 1.5 \cdot \text{produzione}[C2, P3] + 4.0 \cdot \text{produzione}[C2, P4] +[/math]
[math]2.0 \cdot \text{produzione}[C3, P1] + 3.0 \cdot \text{produzione}[C3, P2] + 1.5 \cdot \text{produzione}[C3, P3] + 4.0 \cdot \text{produzione}[C3, P4] +[/math]
[math]0.5 \cdot \text{scorte_finali}[P1] + 0.3 \cdot \text{scorte_finali}[P2] + 0.2 \cdot \text{scorte_finali}[P3] + 0.7 \cdot \text{scorte_finali}[P4][/math]
Questa funzione rappresenta la somma dei costi di produzione e dei costi di stoccaggio per tutte le parti e le commesse.
4. Vincoli
Vincolo 1: Bilancio delle scorte
Per ogni parte [math]p[/math], la somma della produzione e delle scorte attuali deve soddisfare la domanda e le scorte finali:
[math]\sum_{c \in \text{commesse}} \text{produzione}[c, p] + \text{scorte_attuali}[p] = \sum_{c \in \text{commesse}} \text{domanda}[c][p] + \text{scorte_finali}[p][/math]
Vincolo 2: Priorità delle commesse
Per ogni parte [math]p[/math], la produzione per le commesse più vecchie deve essere maggiore o uguale a quella per le commesse più recenti:
[math]\text{produzione}[C1, p] \geq \text{produzione}[C2, p] \geq \text{produzione}[C3, p][/math]
Vincolo 3: Capacità produttiva totale
La produzione totale non deve superare la capacità produttiva totale:
[math]\sum_{c \in \text{commesse}} \sum_{p \in \text{parti}} \text{produzione}[c, p] \leq 50[/math]
Vincolo 4: Capacità produttiva per singola parte
Per ogni parte [math]p[/math], la produzione totale non deve superare la capacità produttiva della parte:
[math]\sum_{c \in \text{commesse}} \text{produzione}[c, p] \leq \text{capacita_per_parte}[p][/math]
Vincolo 5: Capacità del magazzino
Le scorte finali non devono superare la capacità del magazzino:
[math]\sum_{p \in \text{parti}} \text{volume_per_parte}[p] \cdot \text{scorte_finali}[p] \leq 100[/math]
Vincolo 6: Turnover delle scorte
Per ogni parte [math]p[/math], le scorte finali non devono superare il 30% della produzione totale:
[math]\text{scorte_finali}[p] \leq 0.3 \cdot \sum_{c \in \text{commesse}} \text{produzione}[c, p][/math]
5. Risoluzione del Problema
Il modello viene risolto utilizzando il risolutore CBC (incluso in PuLP).
Il codice completo:
from pulp import LpMaximize, LpProblem, LpVariable, lpSum, PULP_CBC_CMD
# ===========================================
# 1. Dati di input
# ===========================================
# Commesse ordinate dalla più vecchia alla più recente
commesse = ["C1", "C2", "C3"]
# Parti di ricambio
parti = ["P1", "P2", "P3", "P4"]
# Domanda di ciascuna parte per ogni commessa
domanda = {
"C1": {"P1": 10, "P2": 5, "P3": 7, "P4": 3},
"C2": {"P1": 8, "P2": 6, "P3": 5, "P4": 2},
"C3": {"P1": 6, "P2": 4, "P3": 3, "P4": 1},
}
# Costi di produzione per ciascuna parte
costi_produzione = {"P1": 2, "P2": 3, "P3": 1.5, "P4": 4}
# Costi di stoccaggio per ciascuna parte
costi_stoccaggio = {"P1": 0.5, "P2": 0.3, "P3": 0.2, "P4": 0.7}
# Scorte attuali di ciascuna parte
scorte_attuali = {"P1": 5, "P2": 3, "P3": 2, "P4": 1}
# Capacità produttiva totale e per singola parte
capacita_totale = 50
capacita_per_parte = {"P1": 20, "P2": 15, "P3": 10, "P4": 5}
# Capacità del magazzino e volumi occupati da ciascuna parte
capacita_magazzino = 100
volume_per_parte = {"P1": 3, "P2": 2, "P3": 1, "P4": 4}
# ===========================================
# 2. Definizione del modello e delle variabili
# ===========================================
# Creazione del modello di ottimizzazione
model = LpProblem("Ottimizzazione_Magazzino", LpMaximize)
# Variabili decisionali:
# - produzione[c, p]: quantità di parte p prodotta per la commessa c
# - scorte_finali[p]: quantità di parte p rimanente in magazzino alla fine del periodo
produzione = {
(c, p): LpVariable(f"Produzione_{c}_{p}", lowBound=0, cat="Integer")
for c in commesse for p in parti
}
scorte_finali = {
p: LpVariable(f"Scorte_Finali_{p}", lowBound=0, cat="Integer")
for p in parti
}
# ===========================================
# 3. Definizione della funzione obiettivo
# ===========================================
# Obiettivo: minimizzare i costi totali di produzione e stoccaggio
model += (
lpSum(costi_produzione[p] * produzione[c, p] for c in commesse for p in parti) +
lpSum(costi_stoccaggio[p] * scorte_finali[p] for p in parti),
"Minimizzazione_Costi_Totali"
)
# ===========================================
# 4. Definizione dei vincoli
# ===========================================
# Vincolo 1: soddisfare la domanda con scorte disponibili + nuova produzione
for p in parti:
model += (
lpSum(produzione[c, p] for c in commesse) + scorte_attuali.get(p, 0) ==
lpSum(domanda[c][p] for c in commesse) + scorte_finali[p],
f"Bilancio_Scorte_{p}"
)
# Vincolo 2: priorità alle commesse più vecchie
for i in range(len(commesse) - 1):
c1, c2 = commesse[i], commesse[i + 1]
for p in parti:
model += (
lpSum(produzione[c1, p]) >= lpSum(produzione[c2, p]),
f"Priorità_{p}_{c1}_{c2}"
)
# Vincolo 3: capacità produttiva totale
model += (
lpSum(produzione[c, p] for c in commesse for p in parti) <= capacita_totale,
"Capacità_Totale"
)
# Vincolo 4: capacità produttiva per singola parte
for p in parti:
model += (
lpSum(produzione[c, p] for c in commesse) <= capacita_per_parte[p],
f"Capacità_{p}"
)
# Vincolo 5: capacità del magazzino
model += (
lpSum(volume_per_parte[p] * scorte_finali[p] for p in parti) <= capacita_magazzino,
"Capacità_Magazzino"
)
# Vincolo 6: turnover delle scorte (massimo il 30% della produzione totale può restare in stock)
for p in parti:
model += (
scorte_finali[p] <= 0.3 * lpSum(produzione[c, p] for c in commesse),
f"Turnover_{p}"
)
# ===========================================
# 5. Risoluzione del problema
# ===========================================
# Risoluzione del modello utilizzando il risolutore CBC
model.solve(PULP_CBC_CMD(msg=True))
# ===========================================
# 6. Stampa dei risultati
# ===========================================
# Stampa della produzione per ciascuna commessa e parte
print("Produzione:")
for c in commesse:
for p in parti:
print(f"Produzione {p} per {c}: {produzione[c, p].varValue}")
# Stampa delle scorte finali per ciascuna parte
print("\nScorte finali:")
for p in parti:
print(f"Scorte finali {p}: {scorte_finali[p].varValue}")
# Stampa del costo totale
print(f"\nCosto totale: {model.objective.value()}")
6. Risultati
Produzione
| Commessa | P1 | P2 | P3 | P4 |
|---|---|---|---|---|
| C1 | 19 | 13 | 13 | 2 |
| C2 | 0 | 0 | 0 | 2 |
| C3 | 0 | 0 | 0 | 2 |
Scorte finali
| Parte | Scorte finali |
|---|---|
| P1 | 0 |
| P2 | 1 |
| P3 | 0 |
| P4 | 0 |
Costo totale
Costo totale = 123.5
Spiegazione dei Risultati
- Produzione:
- La produzione è concentrata principalmente sulla commessa più vecchia (
C1), rispettando il vincolo di priorità. - Per le commesse
C2eC3, la produzione è minima o nulla, poiché la capacità produttiva è stata quasi interamente utilizzata perC1.
- La produzione è concentrata principalmente sulla commessa più vecchia (
- Scorte finali:
- Le scorte finali sono quasi tutte a zero, tranne per
P2che ha una scorte finale di 1 unità.
- Le scorte finali sono quasi tutte a zero, tranne per
- Costo totale:
- Il costo totale minimizzato è 123.5, che include sia i costi di produzione che i costi di stoccaggio.
Il modello presentato può essere esteso in diversi modi per affrontare ulteriori complessità:
- Vincoli di qualità: Includere vincoli di qualità per garantire che i componenti prodotti soddisfino gli standard richiesti.
- Gestione dei tempi di consegna: Considerare i tempi di consegna dei fornitori e i tempi di produzione per ottimizzare la pianificazione.
- Ottimizzazione della logistica: Includere i costi di trasporto e i vincoli di capacità logistica nel modello.
- Modelli di previsione della domanda: Utilizzare modelli di previsione più sofisticati per prevedere la domanda futura in modo più accurato.
- Considerazione dell’obsolescenza: Includere la probabilità di obsolescenza dei componenti nel modello per evitare accumuli eccessivi di scorte.





