In SSC un problema di LP può risolto utilizzando diversi formati. Vogliamo risolvere questo problema usando un
primo formato di rappresentazione denominato formato testuale.
In questo formato la funzione obiettivo (f.o.) e i vincoli possono essere espressi mediante equazioni/disequazioni
rappresentate all'interno di un testo [righi 12-16]. Le variabili possono avere qualsiasi nome (devono però
iniziare con un carattere alfabetico seguito da caratteri alfanumerici) e risultano non essere case-sensitive
(ovvero x3 e X3 rappresentano la stessa variabile). In questo formato la f.o e ciascun vincolo devono
essere scritti su righi differenti.
Uno dei vantaggi di questo formato è che se una variabile non è presente in un vincolo questa può essere
tralasciata, a differenza del formato matriciale o a coefficienti dove deve essere rappresentata con un
coefficiente uguale a zero. Si ricorda che in SSC, di default, le variabili di un problema di LP sono
considerate, se non diversamente specificato, non negative (≥ 0).
Una volta rappresentato il problema, si esegue l'algoritmo del
Simplesso mediante la creazione di un oggetto della classe LP e l'invocazione del
metodo resolve() [righi 18-19]. Una volta eseguito il Simplesso, il
metodo resolve() ritorna il tipo di soluzione trovata; se la
soluzione è ottima è possibile ricavare i valori delle
variabili ed il valore ottimo della funzione obiettivo.
import org.ssclab.log.SscLogger;
import org.ssclab.pl.milp.*;
public class Esempio {
public static void main(String[] args) throws Exception {
//Questo esempio utilizza i Text Blocks """, presenti da java 15.
//Se si dispone di una versione di java precedente usare le stringhe standard.
String pl_string = """
min: 3Y +2x2 +4x3 +7x4 +8X5
5Y +2x2 >= 9 -3X4
3Y + X2 + X3 +5X5 = 12
6Y +3x2 +4X3 <= 124 -5X4
y + 3x2 +6X5 <= 854 -3X4 """;
LP lp = new LP(pl_string);
SolutionType solution_type=lp.resolve();
if(solution_type==SolutionType.OPTIMUM) {
Solution soluzione=lp.getSolution();
for(Variable var:soluzione.getVariables()) {
SscLogger.log("Nome variabile :"+var.getName() + " valore :"+var.getValue());
}
SscLogger.log("Valore ottimo:"+soluzione.getOptimumValue());
}
else SscLogger.log("Soluzione non ottima:"+solution_type);
}
}
Vogliamo risolvere questo problema usando un secondo formato di rappresentazione. In questo secondo caso possiamo utilizzare
dei dati strutturati dove ciascun rigo rappresenta o la funzione obiettivo o un vincolo, mentre le colonne rappresentano i coefficienti
delle variabili, le relazioni, i valori rhs [righi 10-13 del codice sottostante]. Questa rappresentazione
è denominata a coefficienti.
Formulato il problema occorre specificare il suo tracciato record (o formato di input).
Per formato di input intendiamo una dichiarazione che descrive come vanno lette ed interpretate le colonne
presenti nella struttura dati nella quale si è formulato il problema [righi 10-13].
La definizione del formato di input si effettua attraverso una stringa da passare
come argomento al metodo setInputFormat() [rigo 15];
in questa stringa si susseguono le notazioni "nome variabile : tipologia variabile" per definire, in sequenza,
i nomi delle variabili da associare alla colonne ed il tipo di dato presente nelle colonne.
In definitiva si dichiarano: i nomi delle variabili da assegnare alle prime due colonne
che rappresentano le variabili del problema di LP
(in questo caso X1 e X2, di tipo numerico double), la variabile relativa alla colonna delle
relazioni (chiamata TYPE e di tipo stringa con lunghezza di 3 caratteri), la variabile relativa alla colonna dei valori rhs
(chiamata RHS e di tipo double).
I nomi da assegnare alle variabili del problema di LP devono essere scelti dall'analista
(devono però iniziare con un carattere alfabetico seguito da caratteri alfanumerici),
mentre il nomi delle variabili da associare alla colonna delle relazioni e a quella
dei valori rhs devono necessariamente essere TYPE e RHS [rigo 15].
Di norma possiamo chiamare le variabili anche con nomi non necessariamente seguiti da un numero, ad esempio :
"PRICE:double, AMOUNT:double, WEIGHT:double", etc, etc. Di solito però è preferibile
dichiarare le n variabili di un problema con una diversa notazione, chiamata ad intervalli, che risulta
particolarmente utile se le variabili del problema sono decine o centinaia. In questo caso la dichiarazione
"X1:double, X2:double, X3:double, .... , Xn:double" di n variabili,
può essere sostituita con la notazione "X1-Xn:double". Questa seconda notazione, certamente più compatta,
permette di dichiarare tutte le variabili comprese nell'intervallo che va da X1 a Xn.
Possiamo notare come sulla riga relativa alla definizione della f.o. sia presente, nella colonna dei valori rhs, un punto (".")
che rappresenta un valore indefinito (simile ad un null). Ciò è dovuto al fatto che nel formato a coefficienti ogni variabile
dichiarata deve avere, su ogni rigo, associato un valore; in questo caso inseriamo un valore indefinito,
da associare alla colonna relativa ai valori rhs, in quanto irrilevante nella definizione della funzione obiettivo.
Una volta rappresentato il problema
e assegnati i nomi alle variabili, si esegue l'algoritmo del
Simplesso mediante la creazione di un oggetto della classe LP e l'invocazione del
metodo resolve() [righi 17-18]. Una volta eseguito il Simplesso, il
metodo resolve() ritorna il tipo di soluzione trovata; se la
soluzione è ottima è possibile ricavare i valori delle
variabili ed il valore ottimo della funzione obiettivo. Si
ricorda che in SSC, di default, le variabili di un problema di LP sono
considerate, se non diversamente specificato, non negative (
≥ 0).
import org.ssclab.log.SscLogger;
import org.ssclab.pl.milp.*;
import org.ssclab.ref.*;
public class Esempio {
public static void main(String[] args) throws Exception {
String lp_string =
" 1 3 max . \n" +
" 1 1 ge -1 \n" +
" 1 1.4 le 6 \n" +
"-5 3 eq 5";
Input lp_input = new InputString(lp_string).setInputFormat("X1-X2:double, TYPE:varstring(3), RHS:double");
LP lp = new LP(lp_input);
SolutionType solution_type=lp.resolve();
if(solution_type==SolutionType.OPTIMUM) {
Solution solution=lp.getSolution();
for(Variable var:solution.getVariables()) {
SscLogger.log("Nome variabile :"+var.getName() + " valore:"+var.getValue());
}
SscLogger.log("Valore ottimo:"+solution.getOptimumValue());
}
else SscLogger.log("Soluzione non ottima:"+solution_type);
}
}
Il problema dell'esempio precedente può essere
espresso anche attraverso l'uso di vettori e matrici.
Il formato di rappresentazione utilizzato è
simile a quello di Apache simplex solver. In questo caso
occorre definire la matrice A[][] dei coefficienti , il vettore c[] dei coefficienti
della f.o. ed il vettore b[] dei valori RHS.
Definite tali entità [righi 9-14] si crea un oggetto
LinearObjectiveFunction [rigo 18] che rappresenta la funzione
obiettivo (il GoalType rappresenta il tipo di ottimizzazione : MAX o MIN) e gli oggetti Constraint [rigo 22] che rappresentano i
vincoli. Il tipo di vincolo (LE='minore o uguale', GE='maggiore o uguale', EQ='uguale') viene specificato attraverso
gli elementi dell'array delle relazioni rel[], array contenente oggetti di tipo ConsType [rigo 16].
Infine, la lista di vincoli e la funzione obiettivo sono parametri
sufficienti per istanziare un oggetto della classe LP [rigo 25].
In questo esempio la variabile X1
risulta delimitata sia inferiormente (non dallo zero) che
superiormente. Nella rappresentazione del problema con il formato a coefficienti,
per poter definire una variabile con tali limiti,
basta aggiungere un rigo per gli upper bound e uno per i lower
bound [righi 13-14]. Nel caso della variabile X2,
cioè di una variabile senza limiti superiore ed inferiore, basta specificare
un lower bound e un upper bound indefinito. Se si dichiara un valore indefinito
su un lower bound o su un upper bound, SSC trasforma tali valori in -/+ infinito.
In questo formato il valore indefinito si rappresenta con il punto (.).
È importante sottolineare che per poter impostare delle variabili libere o variabili che
possono assumere valori anche in un range negativo,
basta valorizzare un lower bound indefinito (.) o negativo, oppure un upper bound negativo.
Ad esempio, se consideriamo la variabile X1
questa deve assumere valori maggiori di -1; ebbene introdurre un vincolo del tipo
" 1 0 ge -1 \n" nella formulazione del problema
(ovvero il vincolo che la variabile X1 sia maggiore di -1) non determina che la variabile X1
possa assumere valori fino a -1, ma si vincola solo il problema a soddisfare
tale condizione, condizione che non potrà essere vagliata pienamente se non
si rende la variabile capace di assumere anche valori negativi.
Per rendere una variabile libera di variare in range negativi, si deve usare esclusivamente la notazione degli upper e lower bound.
In questo esempio sia X1 che X2 sono variabili che possono assumere valori negativi
(tramite le dichiarazioni ai righi 13-14).
Infine in fase di stampa della soluzione
è possibile recuperare per ogni variabile gli upper ed i lower precedentemente valorizzati
in fase di formulazione del problema [rigo 24].
Risolviamo l'esempio
precedente utilizzando il formato matriciale. Per poter rappresentare la
variabile X1, che risulta delimitata sia inferiormente che
superiormente, occorre aggiungere alla matrice A un rigo
per gli upper bound e uno per i lower bound [righi 14-15]. Nel caso della variabile X2, ovvero di una
variabile libera senza limiti , basta specificare un lower bound e un upper bound
indefinito con il valore NaN.
Ricordiamo che per poter impostare delle variabili libere, ovvero delle variabili che possono assumere anche valori negativi,
basta valorizzare un lower bound indefinito o negativo,
oppure un upper bound negativo. In questo esempio sia X1 che X2 sono libere di assumere anche valori negativi,
rispettando il lower e l'upper definiti.
Occorre poi aggiungere nel
vettore delle relazioni [rigo 20] le costanti che dichiarano
che gli ultimi due righi della matrice A sono relativi agli upper e
lower bound (si aggiunge ConsType.UPPER e ConsType.LOWER). Infine, va
aggiornata anche la dimensione del vettore b (aggiungendo due valori NaN) che deve essere
uguale al numero di righi della matrice A.
import org.ssclab.log.SscLogger;
import static org.ssclab.pl.milp.ConsType.*;
import org.ssclab.pl.milp.*;
import static org.ssclab.pl.milp.LP.NaN;
public class Esempio {
public static void main(String[] args) throws Exception {
double A[][]={
{ 1.0 , 1.0 },
{ 1.0 , 1.4 },
{-5.0 , 3.0 },
{ 1.0 , NaN },
{-1.0 , NaN }} ;
double b[]= { 1.0, 6.0 ,5.0, NaN, NaN };
double c[]= { 1.0, 3.0 };
ConsType rel[]= {GE, LE, EQ, UPPER, LOWER};
LinearObjectiveFunction f = new LinearObjectiveFunction(c, GoalType.MAX);
ListConstraints constraints = new ListConstraints();
for(int i=0; i < A.length; i++) {
constraints.add(new Constraint(A[i], rel[i], b[i]));
}
LP lp = new LP(f,constraints);
SolutionType solution_type=lp.resolve();
if(solution_type==SolutionType.OPTIMUM) {
Solution solution=lp.getSolution();
for(Variable var:solution.getVariables()) {
SscLogger.log("Nome variabile :"+var.getName() + " valore:"+var.getValue());
}
SscLogger.log("Valore ottimo:"+solution.getOptimumValue());
}
else SscLogger.log("Soluzione non ottima:"+solution_type);
}
}
min 3Y + 2X2 + 4Z + 7X4 + 8X5
5Y + 2X2 + 3X4 ≥ 9
3Y + X2 + Z + 5X5 = 12
6Y + 3X2 + 4Z + 5X4 ≤ 124
Y + 3X2 + 3X4 + 6X5 ≤ 854
con Y, X4, X5 ≥ 0
-1 ≤ X2 ≤ +6
-∞ ≤ Z ≤ +∞
Vogliamo risolvere questo problema usando ancora il formato testuale. Questo problema è analogo al problema 1.1,
con la sola aggiunta di upper e lower bound. Nel formato testo per definire gli upper u e lower bound l occorre aggiunge
rispettivamente un rigo per ogni variabile da limitare [righi 14-15] con dichiarazioni di questo tipo :
" u ≤ x ≤ l " , " x ≤ l " , " x ≥ u ".
Ricordiamo che per rendere una variabile libera o una variabile che può assumere valori in un range negativo, occorre valorizzare un lower
bound a -infinito (il +infinito e il -infinito si rapresentano con "." nel formato testo) o negativo, oppure un upper bound negativo
(vedi esempio 1.4). Nel formato testo dichiarazioni di upper e lower bound come quelle dei righi [14-15]
permettono di far assumere alla variabile valori negativi.
Infine in fase di stampa della soluzione è possibile recuperare per ogni variabile [rigo 23] gli upper ed i lower precedentemente valorizzati
in fase di formulazione del problema.
L'esempio 1.4 può essere rappresentato con un quarto formato denominato "sparso" [righi 14-37].
Quando il motore di elaborazione analizza una riga con TYPE valorizzato (diverso da '.'), esso inizia a definire una nuova
entità e le assegna un marcatore, specificato nella colonna ROW_. Questo marcatore rappresenta il nome dell'entità,
che può essere un vincolo o la funzione obiettivo. In particolare, se il motore rileva che il TYPE assume il valore 'MAX'
o 'MIN', inizia la definizione della funzione obiettivo; se invece il valore di TYPE è 'EQ' (=), 'LE' (≤)
o 'GE' (≥), inizia la definizione di un vincolo ed asssegna a queste entità un nome (il marcatore specificato
in ROW_). La colonna TYPE può assumere uno dei seguenti valori: EQ, LE, GE, UPPER, LOWER, MAX, MIN.
Successivamente, le variabili del problema vengono specificate valorizzando la colonna COL_, che identifica la variabile stessa,
e indicando il coefficiente corrispondente nella colonna COEF.
Se una riga contiene un valore in ROW_ ma ha TYPE indefinito ('.') non si sta definendo una nuova entità ma si sta
specificando il coefficiente che una variabile assume su quella entità. In questo caso il valore COEF indica il
coefficiente che la variabile, specificata in COL_, assume sull'entità corrispondente al marcatore presente in ROW_.
Oltre al nome della variabili (scelte dallo sviluppatore), nella colonna COL_ può essere presente anche il
valore "RHS", utilizzato per definire i termini noti (il lato destro) dei vincoli. Se i nomi delle variabili possono essere
scelti liberamente, il nome per i valori rhs deve necessariamente essere indicato con la notazione "RHS".
Definito il problema occorre dichiarare nel formato di lettura [rigo 40] il nome e il tipo (con la relativa lunghezza)
delle quattro colonne richieste da questo formato :
TYPE, COL_, ROW_ e COEF. I nomi delle variabili da assegnare alle corrispettive colonne devono essere
necessariamente TYPE, COL_, ROW_ e COEF.
Per poter istanziare con questo formato un oggetto LP occorre passare come argomento
al costruttore la costante FormatType.SPARSE per informare l'oggetto LP del tipo di formato utilizzato [rigo 42].
Questo formato è
particolarmente indicato per prelevare problemi presenti in tabelle
di database, in quanto la struttura della tabella richiesta è
sempre la stessa: basta che questa definisca le colonne TYPE, COL_,
ROW_ e COEF.
Occorre infine ricordare che i
valori della colonna TYPE e COL_ possono essere espressi sia in
maiuscolo che in minuscolo; in entrambi i casi essi verranno
ricondotti al maiuscolo; mentre per la colonna ROW_ esprimere
il nome di un rigo contemporaneamente in minuscolo e
in maiuscolo, nell'ambito di una stessa formulazione, significa dichiarare
due nomi (e quindi due righi ) distinti.
In questo caso vogliamo risolvere un problema di LP la cui
formulazione si trova in un file di testo (.txt). Chiamiamo
tale file pl_problem.txt e
utilizziamo, per la rappresentazione del problema, il formato
a coefficienti. Nel file pl_problem.txt avremo il seguente contenuto:
3 1 4 7 8 min .
5 2 0 3 0 ge 9
3 1 1 0 5 ge 12
6 3 4 5 0 le 124
1 3 0 3 6 le 854
. 6 . . . upper .
0 0 1 0 0 lower .
Definito tale file di testo, occorre informare SSC che il problema
è contenuto nel file pl_problem.txt [rigo 9] creando una istanza della classe InputFile. Se si hanno un numero
significativo di variabili (in questo caso solo 5, ma possono essere
molte di più) queste si possono dichiarare mediante notazione
ad intervallo [rigo 10], dove con la sintassi "Y1-Y5:double" si
dichiarano 5 variabili distinte (Y1, Y2, Y3, Y4, Y5) di tipo double
(naturalmente possono essere assegnati altri nomi come Z1-Z5 etc,etc).
import org.ssclab.log.SscLogger;
import org.ssclab.pl.milp.*;
import org.ssclab.ref.InputFile;
public class Esempio {
public static void main(String[] args) throws Exception {
InputFile input = new InputFile("c:/dati_pl/pl_problem.txt");
input.setInputFormat("Y1-Y5:double, TYPE:varstring(10), RHS:double");
LP lp=new LP(input);
SolutionType solution_type=lp.resolve();
if(solution_type==SolutionType.OPTIMUM) {
Solution soluzione=lp.getSolution();
for(Variable var:soluzione.getVariables()) {
SscLogger.log("Nome variabile :"+var.getName() + " valore :"+var.getValue());
}
SscLogger.log("Valore ottimo:"+soluzione.getOptimumValue());
}
else SscLogger.log("Soluzione non ottima:"+solution_type);
}
}
In questo esempio vogliamo recuperare il problema, formulato nel formato sparso, da una
tabella di un database (RDBMS) Oracle. Ricordiamo che nel
formato sparso l'input necessario per la risoluzione di un problema
di LP richiede la presenza delle colonne TYPE, ROW_, COL_ e COEF.
Nella tabella inseriamo anche il campo ID_PROBLEM in modo da poter inserire in tale
tabella una moltitudine di problemi di LP identificabili tramite tale campo.
Chiamiamo PL_PROBLEM la tabella Oracle che dovrà contenere tali campi; questa tabella può essere creata
con la seguente istruzione DDL (Data Definition Language):
Per poter recuperare la formulazione del problema da una tabella
presente in un database, occorre scaricare il driver JDBC per il
database in questione e istanziare un oggetto Connection [righi
40-47]. Una volta ottenuta una connessione al DB, tramite l'oggetto
Connection è possibile, da SSC, accedere al DB attraverso il concetto di
libreria. SSC per poter allocare librerie necessita che venga creata una sessione di lavoro
SSC [riga 17]. Una sessione SSC rappresenta l'ambiente nel quale SSC viene
eseguito. Negli esempi precedenti la creazione di una sessione non
era necessaria in quanto l'oggetto LP crea e usa una sua sessione
SSC implicita.
L'invocazione di addLibrary()
[rigo 18] permette di aggiunge alla sessione SSC corrente una
libreria (di nome "DB_ORACLE") che rappresenta una connessione al DB
Oracle. Dopo tale invocazione il metodo addLibrary() ritorna un
oggetto di tipo Library che rappresenta una interfaccia al database. Da
questa interfaccia possiamo [rigo 20] ottenere, tramite una query SQL,
un oggetto di tipo Input che fa riferimento ai record relativi al problema inserito nella tabella
e precisamente quello con identificativo 13.
Una volta ottenuto tale input possiamo passarlo al
costruttore LP [rigo 24] ed eseguire il Simplesso.
Naturalmente, le colonne presenti nel database possono anche non chiamarsi TYPE, ROW_, COL_ e COEF;
se hanno nomi diversi in fase di estrazione dei dati, tramite select SQL, è possibile rinominare le colonne
dei campi (ad esempio: SELECT TIPO as TYPE, RIGA as ROW_, COLONNA as COL_, COEFFICIENTI AS COEF from MyTable)
cambiandoli nei nomi richiesti dal'oggetto LP.
È da notare che nel costruttore della classe LP oltre all'oggetto di tipo Input che contiene un riferimento
ai record relativi alla formulazione del problema, viene anche passato come
secondo argomento [rigo 24] la sessione SSC precedentemente creata, questo per non far istanziare
all'oggetto LP una seconda sessione del tutto inutile. Infine, come terzo argomento, viene passata una costante (FormatType.SPARSE) che
esplicita con quale formato (sparso) è stato formulato il problema.
Naturalmente, al posto del database Oracle puo essere usato qualsiasi altro RDBMS del quale si dispone di un driver JDBC.
Nell'esempio sottostante si riporta un semplice problema di LP che
vuole evidenziare quali sono i valori che il
metodo resolve() può restituire a seconda che il problema di
LP :
a) restituisca una soluzione ottima
b) non ammetta soluzioni ammissibili
c) abbia ottimo illimitato
d) raggiunga il numero massimo di iterazioni possibili
e) venga restituita una soluzione ammissibile al posto di una soluzione ottima
Se l'esigenza è quella di ottenere una soluzione
ammissibile non necessariamente ottima (caso e), basta invocare il metodo
setJustTakeFeasibleSolution() passandogli il valore "true" (rigo 28). Questa opzione
permette di far eseguire solo la prima fase del simplesso e di ottenere una prima soluzione
(basica) ammissibile. In questo caso il valore restituito dal metodo resolve()
(nel caso esista una soluzione ammissibile)
sarà SolutionType.FEASIBLE.
Infine ricordiamo che il numero massimo di iterazioni possibili può
essere modificato dall'analista [rigo 27], il numero massimo di default è 100.000.000.
import org.ssclab.log.SscLogger;
import org.ssclab.pl.milp.LP;
import org.ssclab.pl.milp.Solution;
import org.ssclab.pl.milp.SolutionType;
import org.ssclab.pl.milp.Variable;
import org.ssclab.ref.InputString;
public class Esempio {
public static void main(String[] args) throws Exception {
String lp_string =
"5 4 1 3 max . \n" +
"4 3 1 1 ge 2 \n" +
"1 -2 1 -1 le 2 \n" +
"3 2 1 1.4 le 6 \n" +
"9 8 4 1.7 le 7 \n" +
"5 3 -1 2.4 le 9 \n" +
"3 -2 -5 3 le 5 ";
InputString lp_input = new InputString(lp_string);
lp_input.setInputFormat("V1-V4:double, TYPE:varstring(8), RHS:double");
LP lp = new LP(lp_input);
SscLogger.log("Numero di iterazioni di default:"+lp.getNumMaxIteration());
lp.setNumMaxIteration(5);
lp.setJustTakeFeasibleSolution(true); //imposto la ricerca di una soluzione ammissibile ,
SolutionType solution_type=lp.resolve();
if(solution_type==SolutionType.FEASIBLE) {
Solution solution=lp.getSolution();
for(Variable var:solution.getVariables()) {
SscLogger.log("Nome variabile :"+var.getName() + " valore:"+var.getValue());
}
SscLogger.log("Valore assunto dalla soluzione ammissibile sulla f.o.:"+solution.getValue());
}
else if(solution_type==SolutionType.VUOTUM) {
SscLogger.log("La fase 1 del simplesso non ha trovato soluzioni ammissibili:("+solution_type+")");
}
else if(solution_type==SolutionType.ILLIMITATUM) {
SscLogger.log("Ottimo illimitato:("+solution_type+")");
}
else if(solution_type==SolutionType.MAX_ITERATIUM) {
SscLogger.log("Raggiunto il numero massimo di iterazioni:("+solution_type+")");
}
else if(solution_type==SolutionType.OPTIMUM) {
//questa sezione non verrà mai raggiunta in quanto avendo impostato
//setJustTakeFeasibleSolution(true), il simplesso può solo restituire soluzioni ammissibili
}
}
}
Nell'esempio sottostante si vuole risolvere il problema già affrontato nell'esercizio 1.6, con la particolarità
che la formulazione del problema (utilizzando il formato testuale) è memorizzata in un file. Chiamiamo questo file
pl_problem.txt. Il file menzionato conterrà il seguente contenuto:
A differenza della formulazione dell'esempio 1.6 in questo caso ad ogni vincolo abbiamo anche associato una etichetta
(nome del vincolo) che si inserisce anteponendo ad ogni vincolo un nome seguito da due punti.
Nel codice sottostante sono stati aggiunti delle istruzioni per recuperare le informazioni relative ai vincoli, come il nome
associato al vincolo e il tipo di vincolo. Va però evidenziato che è possibile recuperare
anche il valore che la parte LHS di ogni vincolo (la parte alla sinistra della relazione) assume valorizzando le variabili
incognite con la soluzione ottima. Il valore
LHS viene poi confrontato con il corrispettivo valore RHS [righi 18-19]. Specifichiamo che le quantià LHS e RHS
di cui si parla in questo specifico caso, dato un problema di programmazione lineare AX <=> b, sono date da AX (parte LHS)
e b (RHS). Per cui se nella definizione del problema inseriamo delle variabili sulla destra del segno di
disuguaglianza/uguaglianza, queste non saranno considerate come parte RHS, ma raggruppate nella forma: AX (parte LHS)
e b (RHS).
import java.nio.file.Paths;
import org.ssclab.log.SscLogger;
import org.ssclab.pl.milp.*;
public class Esempio {
public static void main(String[] args) throws Exception {
LP lp = new LP(Paths.get("C:\\pl_problem.txt"));
SolutionType solution_type=lp.resolve();
if(solution_type==SolutionType.OPTIMUM) {
Solution soluzione=lp.getSolution();
for(Variable var:soluzione.getVariables()) {
SscLogger.log("Nome variabile :"+var.getName() + " valore :"+var.getValue());
}
for(SolutionConstraint sol_constraint: soluzione.getSolutionConstraint()) {
SscLogger.log("Vincolo "+sol_constraint.getName()+" : valore="+sol_constraint.getValue() +
"[ "+sol_constraint.getRel()+" "+sol_constraint.getRhs()+" ]" );
}
SscLogger.log("Valore ottimo:"+soluzione.getOptimumValue());
}
else SscLogger.log("Soluzione non ottima:"+solution_type);
}
}
In questo esempio vogliamo recuperare il problema, formulato nel formato sparso, da un
file di testo chiamato problem.txt contenete il seguente listato:
E' da notare che nel costruttore della classe LP viene passato come
argomento un oggetto di tipo InputFile che fa riferimento al file contenente il problema.
import org.ssclab.log.SscLogger;
import org.ssclab.pl.milp.FormatTypeInput.FormatType;
import org.ssclab.pl.milp.*;
import org.ssclab.ref.InputFile;
public class Esempio {
public static void main(String[] args) throws Exception {
InputFile input_sparse = new InputFile("C:/ssc_project/problem.txt");
input_sparse.setInputFormat("TYPE:varstring(5), COL_:varstring(3) , ROW_:varstring(7), COEF:double");
LP lp = new LP(input_sparse,FormatType.SPARSE);
SolutionType solution_type=lp.resolve();
if(solution_type==SolutionType.OPTIMUM) {
Solution solution=lp.getSolution();
for(Variable var:solution.getVariables()) {
SscLogger.log("Nome variabile :"+var.getName() + " valore:"+var.getValue());
}
SscLogger.log("Valore ottimo:"+solution.getOptimumValue());
}
else SscLogger.log("Soluzione non ottima:"+solution_type);
}
}
Nell'esempio sottostante si riporta un problema di LP generato con
numeri pseudocausali, con la particolarità che al vettore b vengono fatti assumere valori dell'ordine di
decine di milioni. La conseguenza di lavorare con numeri di elevata grandezza potrebbe non far convergere
la fase 1 del simplesso.
In questo particolare caso se non si modifica
la tolleranza ε del valore ottimo z della f.o relativo alla fase 1 la risoluzione del problema potrebbe non dare come risultato soluzioni ammissibili.
In altre parole, alla fine della fase 1, se | z | > ε allora il problema non ammette soluzioni.
Per ovviare a questo, nell'esempio riportato, la tolleranza viene aumentata da 1E-8 (valore di default) a 1E-5 [rigo 31] per far si che , a causa
della manipolazione di numeri di grandezza elevata, possa avvenire che | z | < ε.
import org.ssclab.log.SscLogger;
import org.ssclab.pl.milp.*;
import java.util.ArrayList;
import java.util.Random;
public class Esempio {
public static void main(String arg[]) throws Exception {
final int M = 445; // rows
final int N = 345; // cols
Random random = new Random();
double[] c = new double[N];
double[] b = new double[M];
double[][] A = new double[M][N];
for (int j = 0; j < N; j++) c[j] = (double) (random.nextInt(20));
for (int i = 0; i < M; i++) b[i] = (double) random.nextInt(100000000);
for (int i = 0; i < M; i++)
for (int j = 0; j < N; j++) A[i][j] = (double) random.nextInt(10);
LinearObjectiveFunction f = new LinearObjectiveFunction(c, GoalType.MAX);
ArrayList< Constraint > constraints = new ArrayList< Constraint >();
for(int i=0; i < A.length; i++) {
constraints.add(new Constraint(A[i], ConsType.LE, b[i]));
}
LP lp = new LP(f,constraints);
lp.setCEpsilon(EPSILON._1E_M5);
SolutionType solution_type=lp.resolve();
if(solution_type==SolutionType.OPTIMUM) {
Solution solution=lp.getSolution();
for(Variable var:solution.getVariables()) {
SscLogger.log("Nome variabile :"+var.getName() + " value:"+var.getValue());
}
SscLogger.log("Valore f.o. :"+solution.getOptimumValue());
}
else SscLogger.log("Soluzione non ottima. Tipo di soluzione:"+solution_type);
}
}
Per risolvere questo problema (identico al problema 1.1) con una implementazione del simplesso parallelo,
occorre semplicemente aggiungere l'istruzione presente nel rigo 24. Con questa istruzione specifichiamo il numero di thread
da utilizare per eseguire una istanza del simplesso parallelo (in questo caso 4). Tra i valori selezionabili vi è anche il valore
LPThreadsNumber.AUTO, con il quale si delega al sistema la scelta del numero di thread da utilizzare per l'esecuzione del simplesso parallelo.
Occorre ricordare che vantaggi sulla durata
del metodo si ottengono con almeno 4 o più core fisici e che ha senso utilizzare questa tecnica
solo se il problema presenta un numero di variabili/vincoli dell'ordine dei migliaia.
import org.ssclab.log.SscLogger;
import org.ssclab.pl.milp.LP;
import org.ssclab.pl.milp.Solution;
import org.ssclab.pl.milp.SolutionType;
import org.ssclab.pl.milp.Variable;
import org.ssclab.ref.InputString;
import org.ssclab.pl.milp.util.LPThreadsNumber;
public class Esempio {
public static void main(String[] args) throws Exception {
String lp_string =
" 1 3 max . \n" +
" 1 1 ge -1 \n" +
" 1 1.4 le 6 \n" +
"-5 3 eq 5";
InputString lp_input = new InputString(lp_string);
lp_input.setInputFormat("X1:double, X2:double, TYPE:varstring(3), RHS:double");
LP lp = new LP(lp_input);
lp.setThreadsNumber(LPThreadsNumber.N_4);
SolutionType solution_type=lp.resolve();
if(solution_type==SolutionType.OPTIMUM) {
Solution solution=lp.getSolution();
for(Variable var:solution.getVariables()) {
SscLogger.log("Nome variabile :"+var.getName() + " valore:"+var.getValue());
}
SscLogger.log("Valore ottimo:"+solution.getOptimumValue());
}
else SscLogger.log("Soluzione non ottima:"+solution_type);
}
}
Vogliamo risolvere questo problema usando un nuovo formato
di rappresentazione che permette di formulare il problema tramite un oggetto JSON.
In questo formato la funzione obiettivo deve essere espressa come un oggetto
avente chiave "objective" [riga 12], mentre i vincoli vanno inseriti come array di oggetti nella chiave json
'constraints' [riga 22]. Infine gli upper e i lower bounds vanno inseriti nell'oggetto avente chiave "bounds".
Possiamo notare come sia possibile dare dei nomi ai vincoli tramite la chiave "name" [riga 31].
Ricordiamo che in questo formato di rappresentazione di un problema di LP occorre scrivere sia le chiavi
("objective","coefficients","constraints", etc) che i valori ("max","min","eq", etc) in minuscolo.
Al contrario i nomi da dare alle variabili non sono case-sensitive, per cui "X3" e "x3" rappresentano la stessa variabile.
Naturalmente, i nomi dei vincoli possono esssere scritti in maiuscolo o munuscolo o un mix, ricordiamo però
che questi sono case-sensitive.
Secondo le specifiche JSON, le chiavi devono essere univoche. Ad esempio nell'oggetto
che definisce la funzione obiettivo (quello avente sezione "objective"), una variabile può comparire solo una volta
affinchè siano soddisfatte le regole legate alla corretta compilazione di un JSON.
È importante precisare che se si definisce un lower bound negativo, come in questo caso,
la variabile interessata (la Y) non è più soggetta ai vincoli di non negatività.
Inoltre, per poter dichiarare variabili libere, ovvero variabili senza limite inferiore
(il limite inferiore non sarà più lo zero ma -infinito), basta specificare un lower bound indefinito.
Se si dichiara un valore indefinito su un lower bound o su un upper bound, SSC trasforma tali valori in -infinito nel
primo caso ed in +infinito nel secondo. Nel formato JSON il valore indefinito si rappresenta con il null (senza apici).
Uno dei vantaggi di questo formato è che se una variabile non è presente in un vincolo o
nella funzione obiettivo, questa può essere
tralasciata, a differenza del formato matriciale o a coefficienti dove deve essere rappresentata con un
coefficiente uguale a zero. Si ricorda che in SSC, di default, le variabili di un problema di LP sono
considerate, se non diversamente specificato, non negative (≥ 0).
Una volta rappresentato il problema, occorre creare un oggetto delle classe JsonProblem, passandogli come argomento
la stringa contenente il JSON: questo oggetto è sufficiente per istanziare un oggetto della classe LP
ed eseguire in Simplesso. Una volta eseguito il Simplesso, il metodo resolve() ritorna il tipo di soluzione trovata;
se la soluzione è ottima è possibile ricavare i valori delle variabili ed il valore ottimo della funzione
obiettivo.
import org.ssclab.log.*;
import org.ssclab.pl.milp.*;
public class Esempio {
public static void main(String[] args) throws Exception {
//Questo esempio utilizza i Text Blocks """, presenti da java 15.
//Se si dispone di una versione di java precedente usare le stringhe standard.
String pl_json = """
{
"objective": {
"type": "min",
"coefficients": {
"Y": 3,
"X2": 2,
"X3": 4,
"X4": 7,
"X5": 8
}
},
"constraints": [
{
"coefficients": {
"Y": 5,
"X2": 2,
"X4":-3
},
"relation": "ge",
"rhs": 9,
"name":"VINCOLO1"
},
{
"coefficients": {
"Y": 3,
"X2": 1,
"X3": 1,
"X5": 5
},
"relation": "eq",
"rhs": 12,
"name":"VINCOLO2"
},
{
"coefficients": {
"Y": 6,
"X2": 3,
"X3": 4,
"X4":-5
},
"relation": "le",
"rhs": 124,
"name":"VINCOLO3"
}
],
"bounds": {
"Y": {
"lower": -1,
"upper": 4
},
"X2": {
"lower": null,
"upper": 5
}
}
}""";
LP lp = new LP(new JsonProblem(pl_json));
SolutionType solution_type=lp.resolve();
if(solution_type==SolutionType.OPTIMUM) {
Solution soluzione=lp.getSolution();
for(Variable var:soluzione.getVariables()) {
SscLogger.log("Nome variabile :"+var.getName() + " valore :"+var.getValue());
}
SscLogger.log("Valore ottimo:"+soluzione.getOptimumValue());
}
else SscLogger.log("Soluzione non ottima:"+solution_type);
}
}
In questo caso vogliamo risolvere un problema di LP la cui formulazione si trova in un file esterno JSON.
Chiamiamo tale file pl_problem.json. Nel file avremo il seguente contenuto:
Per poter referenziare il file json occorre semplicemente creare una istanza della classe JsonProblem,
passandogli come argomento un oggetto Path contenente il percorso di dove è situato il file [riga 9].
import java.nio.file.Paths;
import org.ssclab.log.SscLogger;
import org.ssclab.pl.milp.*;
public class Esempio {
public static void main(String[] args) throws Exception {
LP lp = new LP(new JsonProblem(Paths.get("C:/appo/pl_problem.json")));
SolutionType solution_type = lp.resolve();
if (solution_type == SolutionType.OPTIMUM) {
Solution soluzione = lp.getSolution();
for (Variable var : soluzione.getVariables()) {
SscLogger.log("Nome variabile :" + var.getName() + " valore :" + var.getValue());
}
SscLogger.log("Valore ottimo:" + soluzione.getOptimumValue());
}
else SscLogger.log("Soluzione non ottima:" + solution_type);
}
}
Questo esempio è identico all'esempio 1.1, con la particolarità che i risultati relativi
alla soluzione ottima individuata vengono memorizzati in un oggetto della classe JsonSolution, che permette
di salvare i risultati o in un file JSON esterno o in una stringa contenente il JSON o infine come oggetto JSON (JsonObject) della libreria
jakarta.json. In questo esempio salveremo la soluzione in un file esterno JSON ed otterremo il seguente contenuto:
È importante sottolineare che le diverse informazioni relative alla soluzione individuata possono essere inserite o meno
nel JSON aggiungendo le relative costanti di tipo SolutionDetail al metodo getSolutionAsJson() della classe LP (o MILP).
In questo caso aggiungendo INCLUDE_BOUNDS e INCLUDE_META, vengono aggiunte, alle informazioni di base, anche quelle relative
agli upper e lower bound e le metainformazioni relative all'elaborazione, quali i tempi di esecuzione, il numero di thread, etc.
Se non vengono passati argomenti al metodo getSolutionAsJson(), vengono memorizzate sole le informazioni di base.
Questo esempio è identico all'esempio 1.1, con la particolarità che i rusultati, normalmente
visualizzati sulla console standard, vengono salvati in un file di testo esterno (result_lp.txt) tramite la ridirezione
del log [rigo 16].
Questo problema presenta le variabili X2, X3,
X4, X5 intere, mentre la variabile X1
non lo è. In SSC per risolvere un problema di MILP occorre
semplicemente introdurre nel formato a coefficienti un rigo aggiuntivo
[rigo 20] per dichiarare le variabili intere. Ponendo 1 sul rigo degli "integer"
si dichiara la relativa variabile come intera.
Altra differenza è che in questo caso va
instanziato l'oggetto MILP [rigo 25] e non l'oggetto LP.
Vogliamo risolvere questo problema di programmazione
lineare intera utilizzando la notazione matriciale. Per far ciò
basta aggiungere alla matrice A dei coefficienti un rigo
per esprimere quali sono le variabili intere [rigo 21]. Con il valore 1
si dichiara la variabile intera, con lo 0 la si considera non
intera. Occorre anche dichiarare a SSC che l'ulteriore rigo
inserito in A non è relativo ai vincoli di tipo GE, LE ,EQ ,
ma alla dichiarazione delle variabili intere; ciò si realizza
aggiungendo al vettore che esprime il tipo di vincolo
(array rel[]) il valore ConsType.INT [rigo 26]. Infine occorre aggiungere al
vettore b un valore NaN [rigo 23], per adeguare la dimensione di A
con quella di b in quanto la dimensione di b deve coincidere con il
numero di righi della matrice A.
Vogliamo risolvere questo problema di programmazione
lineare misto intera utilizzando la notazione sparsa. Per indicare
che la variabile Y2 è intera basta aggiungere
nella colonna dei ROW_ un valore per indicare quale sarà il
marcatore che le definisce. Questo marcatore (da noi chiamato
"var_int", ma può avere qualsiasi altro nome) deve avere
associato un TYPE di tipo INTEGER [rigo 23].
Una volta definito il marcatore che definisce le variabili
intere, basta dichiarare [rigo 38] che la variabile Y2
è di tipo var_int (ovvero INTEGER) ponendo nella colona dei
COEF il valore 1. Se al contrario una variabile non è intera
si deve valorizzare la colonna COEF con lo 0 o, più brevemente,
tralasciare l'intera dichiarazione. Difatti, nella
formulazione sottostante, la variabile Y1 non è intera e
quindi non è necessario esprimere la mancata associazione tra la
variabile e il marcatore var_int ponendo il valore 0 in COEF.
Questo problema presenta variabili intere e
binarie, mentre la variabile K3 non è soggetta a nessun
vincolo di interezza. Per dichiarare una variabile binaria basta
aggiungere un rigo avente TYPE "binary" [rigo 22]. Se la i-esima variabile
è binaria basta porre 1 nel corrispondente rigo dei binary.
Si rammenta che una variabile non può essere definita
contemporaneamente sia binaria che intera. Nel nostro caso le
variabili K1 e K4 sono binarie mentre le
variabili K2 e K5 sono intere.
Vogliamo risolvere questo problema di programmazione
lineare intera, che presenta anche variabili binarie, utilizzando
la notazione matriciale.
In questa notazione, per rappresentare le variabili binarie,
basta aggiungere alla matrice A dei coefficienti un rigo
per dichiararle [rigo 15]. Con
il valore 1 si dichiara la variabile binaria, con lo 0 la si
considera non binaria. Va anche aggiunto il valore ConsType.BIN
[rigo 20] per dichiarare che l'ultimo rigo della matrice A è
relativo alla definizione delle variabili binarie.
Vogliamo risolvere questo problema di programmazione
lineare misto intera, che presenta anche variabili binarie, utilizzando la
notazione sparsa. Per indicare che la variabile Y1
è binaria basta aggiungere nella colonna dei ROW_ il
marcatore che le definisce. Questo marcatore (da noi chiamato
"var_bin" , ma che può avere qualsiasi altro nome)
deve avere associato un TYPE di tipo BINARY [rigo 24].
Una volta definito il marcatore delle variabili binarie,
basta dichiarare [rigo 32] che la variabile Y1 è
di tipo var_bin (ovvero BINARY) ponendo nella colonna dei COEF il
valore 1.
Vogliamo risolvere questo problema di programmazione lineare
misto intera usando il formato testuale. In questo formato per aggiungere il vincolo
di interezza sulle variabili X2 e X3 basta aggiungere
un rigo [rigo 15] con la dichiarazione delle variabili da considerare intere. Nel caso in cui si vogliano
definire variabili binare o semicontinue, la sintassi
è identica a quella della dichiarazione degli interi, ovvero si antepone invece di "int" la parola chiave "bin" o "sec" e si
fa seguire una lista di variabili separate da virgola. Se il problema richiede tutte le variabili intere (o binarie o semicontinue)
basta far seguire al tipo di variabile di cui si necessita la parola ALL ("INT ALL"). Attenzione ! Se si è già definito
una istruzione "int", altre istruzioni "int" o "bin" o "sec" vanno poste ognuna su righi differenti.
Si consideri il problema di programmazione
lineare intera riportato nella sezione sottostante.
In questo esempio
si confronta la soluzione ottima finale intera con quella
ottenuta dal suo rilassamento (i valori del problema rilassato sono riportati
tra parentesi quadre [righi 46-49])
Questo problema è simile al problema 2.7. Nel codice per la sua risoluzione sono state aggiunti
dei righi per recuperare il valore che la parte LHS di ogni vincolo (la parte alla sinistra della relazione)
assume valorizzando le variabili incognite con la soluzione ottima. Il valore LHS viene poi
confrontato con il corrispettivo valore RHS [righi 24-27]. Specifichiamo che le quantià LHS e RHS di cui si parla
in questo specifico caso, dato un problema di programmazione lineare AX <=> b,
sono date da AX (parte LHS) e b (RHS). Per cui se nella definizione del problema inseriamo delle variabili sulla destra del segno di
disuguaglianza/uguaglianza, queste non saranno considerate come parte RHS, ma raggruppate nella forma: AX (parte LHS) e b (RHS). Ai vincoli si può dare un nome,
basta anteporre un nome al vincolo e facendolo seguire dai due punti esempio 1.11).
In questo esempio si sono rese intere tutte le variabili. Per dichiararle tutte intere è possibile utilizzare la notazione
"int all", oppure utilizzare la notazione che utilizza il catattere jolly *, che indica che tutte le variabili che iniziano
per un determinato prefisso siano di un determinato tipo (come nell'esempio). Utilizzando questa notazione è possibile ad esempio utilizzare una notazione
come questa: "int XI* \n bin XB*". In questo modo dichiariamo che le variabili che iniziano per "XI" siano intere,
quelle che iniziano per "XB" siano binarie e le rimanenti reali.
Questo problema è identico al problema 2.4 con l'aggiunta della
condizione che la variabile K2 è semicontinua, ovvero essa non è strettamente vincolata
ad essere compresa tra i valori di upper e lower, ma può anche assumere il valore 0.
Vogliamo risolvere questo problema di programmazione
lineare intera, che presenta anche una variabile semicontinua intera, utilizzando
la notazione matriciale.
Si consideri il problema di programmazione
lineare intera riportato nella sezione sottostante .
Vogliamo risolvere questo problema utilizzando un'implementazione del B&B parallelo.
Per far questo basta aggiugere l'istruzione al rigo [39], con il quale si dichiara il numero
di thread (in questo caso 4) da utilizzare per eseguire un B&B parallelo.
Se l'esigenza è quella di ottenere una soluzione
ammissibile non necessariamente ottima , basta invocare il metodo
setJustTakeFeasibleSolution() passandogli il valore "true" (rigo 24). Questa opzione
permette di far eseguire il B&B im modo di ottenere una soluzione
ammissibile . In questo caso il valore restituito da resolve() (se esiste una soluzione ammissibile)
sarà SolutionType.FEASIBLE. Nel nostro esempio la soluzione ammissibile intera ottenuta (X1=2,X2=2)
ha come valore sulla f.o. -6 , mentre la soluzione ottima (X1=3,X2=1) ha come valore -7.
import org.ssclab.log.SscLogger;
import org.ssclab.pl.milp.MILP;
import org.ssclab.pl.milp.Solution;
import org.ssclab.pl.milp.SolutionType;
import org.ssclab.pl.milp.Variable;
import org.ssclab.ref.InputString;
public class Esempio {
public static void main(String[] args) throws Exception {
String milp_string=
"-2 -1 min ." +"\n"+
"-1 -1 ge -5" +"\n"+
"1 -1 ge 0" +"\n"+
"-6 -2 ge -21" +"\n"+
"4 3 upper ." +"\n"+
"1 1 integer ." +"\n" ;
InputString milp_input = new InputString(milp_string);
milp_input.setInputFormat("X1-X2:double, TYPE:varstring(20), RHS:double");
MILP milp=new MILP(milp_input);
milp.setJustTakeFeasibleSolution(true);
SolutionType solution_type= milp.resolve();
if(solution_type==SolutionType.FEASIBLE) {
Solution solution=milp.getSolution();
for(Variable var:solution.getVariables()) {
SscLogger.log("Nome variabile :"+var.getName() + " valore:"+var.getValue());
}
SscLogger.log("Valore soluzione sulla f.o.:"+solution.getValue());
}
else SscLogger.log("Non esiste una soluzione ammissibile :"+solution_type);
}
}
Vogliamo risolvere questo problema usando il formato json.
Per poter indicare quali varibili debbono essere intere occorre aggiungere la sezione "variables",
nella quale si indica il tipo di variabili. A seconda che siano intere, binarie, semicontinue o
semicontinue intere, si utilizzano rispettivamente i valori "integer","binary" ,"semicont" e "integer-semicont".
Ricordiamo che nel formato json occorre scrivere sia le chiavi (ad esempio "objective","coefficients","constraints",etc)
che i valori (ad esempio "max","min","eq",etc) in minuscolo.
Al contrario i nomi dati alle variabili non sono case-sensitive, per cui "X3" e "x3" rappresentano la stessa variabile.
Infine, i nomi dei vincoli possono esssere scritti in maiuscolo o munuscolo o un mix, ricordando che però
questi sono case-sensitive.
Vogliamo risolvere questo problema di programmazione lineare
misto intera e memorizzare la soluzione in un file JSON esterno.
In questo caso includiamo, tramite le dichiariazioni "INCLUDE_XXX", tutte le ulteriori informazioni disponibili
da memorizzare sulla soluzione in formato JSON. Possiamo notare come al metodo resolve() venga passato il valore
null per distinguerlo da quello standard che ritorna il tipo di soluzione trovata, a differenza di questo che permette di
costruire una catena di chiamate sull'oggetto MILP.
Vogliamo risolvere questo problema di programmazione lineare
che presenta un insieme di variabili di tipo SOS1, ovvero tra le variabili che fanno parte di questo insieme,
solo una può essere maggiore di zero.
Vogliamo risolvere questo problema di programmazione lineare
che presenta un insieme di variabili di tipo SOS2 intere, ovvero tra le variabili che fanno parte di questo insieme,
solo due possono essere maggiori di zero e devono essere consecutive, ovvero o (X2, X3) obiettivo
(X3, X5) o nessuna.
Vogliamo risolvere questo problema di programmazione lineare
che presenta un insieme di variabili di tipo F-SOS1 binarie e SOS2. Le prime devono essere binarie
ed almeno una deve essere uguale a uno (F = Forced), due consecutive delle seconde possono essere maggiori di zero,
ovvero o (X4, X2) o (X2, X3) o una o nessuna.
import org.ssclab.log.SscLogger;
import org.ssclab.pl.milp.*;
public class Esempio {
public static void main(String[] args) throws Exception {
//Questo esempio utilizza i Text Blocks """, presenti da java 15.
//Se si dispone di una versione di java precedente usare le stringhe standard.
String lp_problem = """
min: 3x1 +X2 +4x3 +7x4 +8X5
5x1 +2x2 +3X4 >= 9
3x1 + X2 +X3 +5X5 >= 12.5
6X1+3.1x2 +4X3 +5X4 >= 24
1 <= x2 <= 6
sos1:bin:force x1, X5
sos2 x4,x2,X3 """;
MILP milp = new MILP(lp_problem);
SolutionType solution_type = milp.resolve();
if (solution_type == SolutionType.OPTIMUM) {
Solution solution = milp.getSolution();
for (Variable var : solution.getVariables()) {
SscLogger.log("Nome variabile :" + var.getName() + " valore:" + var.getValue());
}
SscLogger.log("Valore ottimo f.o. :" + solution.getOptimumValue());
}
else SscLogger.log("Soluzione non ottima:" + solution_type);
}
}
In SSC un problema di LP può risolto utilizzando diversi formati. Vogliamo risolvere questo problema usando un primo formato di rappresentazione denominato formato testuale. In questo formato la funzione obiettivo (f.o.) e i vincoli possono essere espressi mediante equazioni/disequazioni rappresentate all'interno di un testo [righi 12-16]. Le variabili possono avere qualsiasi nome (devono però iniziare con un carattere alfabetico seguito da caratteri alfanumerici) e risultano non essere case-sensitive (ovvero x3 e X3 rappresentano la stessa variabile). In questo formato la f.o e ciascun vincolo devono essere scritti su righi differenti.
Uno dei vantaggi di questo formato è che se una variabile non è presente in un vincolo questa può essere tralasciata, a differenza del formato matriciale o a coefficienti dove deve essere rappresentata con un coefficiente uguale a zero. Si ricorda che in SSC, di default, le variabili di un problema di LP sono considerate, se non diversamente specificato, non negative (≥ 0).
Una volta rappresentato il problema, si esegue l'algoritmo del Simplesso mediante la creazione di un oggetto della classe LP e l'invocazione del metodo resolve() [righi 18-19]. Una volta eseguito il Simplesso, il metodo resolve() ritorna il tipo di soluzione trovata; se la soluzione è ottima è possibile ricavare i valori delle variabili ed il valore ottimo della funzione obiettivo.