diff --git a/src/main/java/net/berack/upo/ai/problem3/LikelyhoodWeighting.java b/src/main/java/net/berack/upo/ai/problem3/LikelyhoodWeighting.java index 817cb75..53f76e4 100644 --- a/src/main/java/net/berack/upo/ai/problem3/LikelyhoodWeighting.java +++ b/src/main/java/net/berack/upo/ai/problem3/LikelyhoodWeighting.java @@ -1,34 +1,66 @@ package net.berack.upo.ai.problem3; import java.security.SecureRandom; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import smile.Network; +/** + * Calcolo dei valori tramite l'algoritmo del Likelyhood Weighting + * @author Berack + */ public class LikelyhoodWeighting { public final Network net; + private Map values = new HashMap<>(); + /** + * Inizializza un nuovo oggetto che calcolerà i valori per la rete inserita + * @param net la rete a cui calcolare i valori + */ public LikelyhoodWeighting(Network net) { this.net = Objects.requireNonNull(net); } - public void calculate(int totalRuns) { + /** + * Recupera i valori del nodo dopo averli calcolati + * Nel caso in cui non si abbia ancora fatto {@link #updateNetwork(int)} allora restituirà + * una eccezione di tiop UnsupportedOperationException + * @param node il nodo da vedere + * @return l'array di valori da restituire + */ + public double[] getNodeValue(int node) { + if(values.size() == 0) throw new UnsupportedOperationException("You should run first updateNetwork method"); + return values.get(node); + } + + /** + * Calcola i valori possibili per la rete. + * Per poterli vedere utilizzare il metodo {@link #getNodeValue(int)} + * @param totalRuns + */ + public void updateNetwork(int totalRuns) { totalRuns = Math.max(1, totalRuns); - var nodes = NetworkNode.buildSetFrom(net, totalRuns); + var nodes = SmileLib.buildListFrom(net); var rand = new SecureRandom(); var prob = new double[totalRuns]; var sum = 0.0d; + for(var node : nodes) + node.samples = new int[totalRuns]; + for(var run = 0; run < totalRuns; run++) { - prob[run] = 1; + var probRun = 1.0d; for(var node: nodes) { if(!node.isEvidence()) node.setSample(rand.nextDouble(), run); - else prob[run] *= node.getProbSampleEvidence(run); + else probRun *= node.getProbSampleEvidence(run); } - sum += prob[run]; + prob[run] = probRun; + sum += probRun; } for(var node : nodes) if(!node.isEvidence()) { @@ -39,7 +71,7 @@ public class LikelyhoodWeighting { for(var i = 0; i < values.length; i++) values[i] /= sum; - net.setPointValues(node.handle, values); + this.values.put(node.handle, values); } } } diff --git a/src/main/java/net/berack/upo/ai/problem3/NetworkNode.java b/src/main/java/net/berack/upo/ai/problem3/NetworkNode.java index 5130cfb..1af7814 100644 --- a/src/main/java/net/berack/upo/ai/problem3/NetworkNode.java +++ b/src/main/java/net/berack/upo/ai/problem3/NetworkNode.java @@ -1,58 +1,59 @@ package net.berack.upo.ai.problem3; import java.util.Arrays; -import java.util.HashMap; -import java.util.Set; - import smile.Network; +/** + * Una classe di appoggio per i nodi di un network. + * Questa classe contiene anche un array di samples in modo da facilitare il + * calcolo di valori. + * + * @see SmileLib#buildListFrom(NetWork) + * @author Berack + */ public class NetworkNode { - public static Set buildSetFrom(Network net, int totRounds) { - var nodes = new HashMap(); - - for(var handle : net.getAllNodes()) nodes.put(handle, new NetworkNode(net, handle)); - - var retSet = Set.copyOf(nodes.values()); - for(var node : retSet) { - var parentsHandle = net.getParents(node.handle); - node.parents = new NetworkNode[parentsHandle.length]; - - for(var i = 0; i < parentsHandle.length; i++) - node.parents[i] = nodes.get(parentsHandle[i]); - - if(!node.isEvidence()) { - node.samples = new int[totRounds]; - Arrays.fill(node.samples, -1); - } - } - - return retSet; - } - - public static NetworkNode[] topologicalSort(Set nodes) { - throw new UnsupportedOperationException("TODO implement this function"); - } - final int handle; final String[] outcomes; final double[] definition; final int evidence; + final Network net; NetworkNode[] parents; - int[] samples; + public int[] samples; - private NetworkNode(Network net, int handle) { + /** + * Questo costruttore crea un nodo e gli assegna i valori essenziali. + * @apiNote Non usare questo costruttore se non si sa che si sa quello che si sta facendo + * @param net la rete + * @param handle l'handle del nodo + */ + NetworkNode(Network net, int handle) { this.handle = handle; + this.net = net; + this.definition = net.getNodeDefinition(handle); this.outcomes = net.getOutcomeIds(handle); this.evidence = net.isEvidence(handle)? net.getEvidence(handle) : -1; } + /** + * Indica se il nodo è evidenza o meno + * @return vero se lo è + */ public boolean isEvidence() { return this.evidence > 0 ; } + /** + * Per utilizzare questo metodo il nodo deve essere una evidenza. + * Dato un roud di sample permette di ricevere il valore della + * probabilità che, dati i sample dei genitori, il nodo abbia + * il valore dell'evidenza impostata. + * + * @param round il numero del round che si stà controllando + * @return il valore della probabilità della evidenza + */ public double getProbSampleEvidence(int round) { if(!this.isEvidence()) throw new IllegalArgumentException("Evidence"); @@ -60,28 +61,44 @@ public class NetworkNode { return this.definition[init + this.evidence]; } + /** + * Mette un sample al nodo nel round selezionato. + * Il valore di rand deve essere un numero casuale tra 0 e 1 ed + * esso permetterà di impostare un valore in base ai valori dei genitori. + * + * @param rand un valore casuale tra 0 e 1 + * @param round il numero del round + */ public void setSample(double rand, int round) { var init = getStartingIndex(round); var end = init + this.outcomes.length; - var prob = 0; + var prob = 0.0d; for(var i = init; i < end; i++) { prob += this.definition[i]; - if(rand <= prob) { + if(prob >= rand) { this.samples[round] = i - init; break; } } } + /** + * Dato un round permette di ricavare l'indice di partenza della CPT. + * Questo metodo serve perchè i genitori del nodo nel sample hanno + * dei valori e io devo generarli in accordo con la CPT di questo nodo. + * + * @param round il roundo corrente + * @return l'indice iniziale per gli output del nodo in base ai valori dei genitori + */ private int getStartingIndex(int round) { var init = 0; var tot = this.definition.length; for(var p : this.parents) { var pIndex = p.isEvidence()? p.evidence : p.samples[round]; - if(pIndex == -1) throw new IllegalArgumentException("Parent"); + if(pIndex < 0) throw new IllegalArgumentException("Parent"); // in theory impossible since Topological sorted tot /= p.outcomes.length; init += tot * pIndex; @@ -89,4 +106,16 @@ public class NetworkNode { return init; } + + @Override + public boolean equals(Object obj) { + if(!obj.getClass().isInstance(this)) return false; + + var other = (NetworkNode) obj; + if(this.handle != other.handle) return false; + if(this.evidence != other.evidence) return false; + if(!Arrays.equals(this.definition, other.definition)) return false; + + return true; + } } \ No newline at end of file diff --git a/src/main/java/net/berack/upo/ai/problem3/SmileLib.java b/src/main/java/net/berack/upo/ai/problem3/SmileLib.java index af8b279..a07caab 100644 --- a/src/main/java/net/berack/upo/ai/problem3/SmileLib.java +++ b/src/main/java/net/berack/upo/ai/problem3/SmileLib.java @@ -1,13 +1,24 @@ package net.berack.upo.ai.problem3; import java.net.URLDecoder; - +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import smile.Network; +/** + * Classe che permette l'utilizzo della libreria SMILE di BAYESFUSION. + * La classe carica staticamente la libreria.dll creando la proprietà di + * sistema jsmile.native.library includendo la resource path di jsmile. + * In questo modo per utilizzare SMILE basta chiamare un metodo di questa classe + * per far si che la chiave di attivazione venga correttamente controllata. + * + * @apiNote Scadenza chiave 2024-06-16 + * @author Berack + */ public class SmileLib { public static final String RESOURCE_PATH; - static { var loader = SmileLib.class.getClassLoader(); var wrongPath = loader.getResource("").getFile(); @@ -39,30 +50,51 @@ public class SmileLib { ); } + /** + * Crea un Network dal file indicato + * Il file deve essere una risorsa del jar o un file esterno + * + * @param file il file da cercare + * @return il network creato + */ public static Network getNetworkFrom(String file) { var net = new Network(); - net.readFile(RESOURCE_PATH + file); + try { + net.readFile(RESOURCE_PATH + file); + } catch (smile.SMILEException e) { + net.readFile(file); + } + return net; } - public static void main(String[] args) throws Exception { - var net = new Network(); + /** + * Crea una lista di nodi dal network indicato. + * I nodi usati sono un po' più comodi rispetto al network. + * La lista è ordinata in modo che il nodo 'k' sia un discendente + * dei nodi '0...k-1' e non di 'k+1...n' + * + * @param net il network da cui prendere i dati + * @return una lista ordinata di nodi + */ + public static List buildListFrom(Network net) { + var nodes = new HashMap(); + var list = new ArrayList(); - net.readFile(RESOURCE_PATH + "VentureBN.xdsl"); + for(var handle : net.getAllNodes()) { + var node = new NetworkNode(net, handle); + list.add(node); + nodes.put(handle, node); + } - var nodes = net.getAllNodes(); - for (var i = 0; i < nodes.length; i++) { - System.out.println(nodes[i] + " -> " + net.getNodeId(nodes[i])); + for(var node : nodes.values()) { + var parentsHandle = net.getParents(node.handle); + node.parents = new NetworkNode[parentsHandle.length]; + + for(var i = 0; i < parentsHandle.length; i++) + node.parents[i] = nodes.get(parentsHandle[i]); + } + + return list; } - - net.setEvidence("Forecast", "Moderate"); - net.updateBeliefs(); - - var beliefs = net.getNodeValue("Success"); - for (var i = 0; i < beliefs.length; i++) { - System.out.println(net.getOutcomeId("Success", i) + " = " + beliefs[i]); - } - - net.close(); - } } diff --git a/src/test/java/net/berack/upo/ai/problem3/LWTest.java b/src/test/java/net/berack/upo/ai/problem3/LWTest.java new file mode 100644 index 0000000..a3fc876 --- /dev/null +++ b/src/test/java/net/berack/upo/ai/problem3/LWTest.java @@ -0,0 +1,46 @@ +package net.berack.upo.ai.problem3; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +public class LWTest { + + @Test + public void testSmile() { + var net = SmileLib.getNetworkFrom("VentureBN.xdsl"); + + var nodes = net.getAllNodes(); + for (var i = 0; i < nodes.length; i++) { + System.out.println(nodes[i] + " -> " + net.getNodeId(nodes[i])); + } + + net.setEvidence("Forecast", "Moderate"); + net.updateBeliefs(); + + var beliefs = net.getNodeValue("Success"); + var arr = new double[] {0.25, 0.75}; + assertArrayEquals(arr, beliefs, 0.001); + + net.close(); + } + + @Test + public void testSimpleNetwork() { + var net = SmileLib.getNetworkFrom("VentureBN.xdsl"); + net.updateBeliefs(); + + var lw = new LikelyhoodWeighting(net); + lw.updateNetwork(1000); + + for(var node : net.getAllNodes()) { + var arr1 = net.getNodeValue(node); + var arr2 = lw.getNodeValue(node); + + System.out.println(Arrays.toString(arr1) + " " + Arrays.toString(arr2)); + assertArrayEquals(arr1, arr2, 0.05); // 5% difference max (it is a lot but is fair) + } + } +} diff --git a/src/test/resources/Airport.xdsl b/src/test/resources/Airport.xdsl new file mode 100644 index 0000000..2c33554 --- /dev/null +++ b/src/test/resources/Airport.xdsl @@ -0,0 +1,146 @@ + + + + + + + + + + + + + 0.3 0.7 + + + + + 0.2 0.8 + + + + + 0.4 0.6 + + + + + AirTraffic AirportSite + 0.9 0.09999999999999998 0.7 0.3 0.7 0.3 0.8 0.2 0.6 0.4 0.4 0.6 + + + + + AirTraffic AirportSite + 0.9 0.09999999999999998 0.8 0.2 0.7 0.3 0.7 0.3 0.4 0.6 0.2 0.8 + + + + + Litigation Construction AirportSite + 0.9 0.09999999999999998 0.8 0.2 0.75 0.25 0.3 0.7 0.25 0.75 0.2 0.8 0.4 0.6 0.5 0.5 0.45 0.55 0.3 0.7 0.2 0.8 0.09999999999999998 0.9 + + + Deaths + 1 0 + + + Noise + 1 0 + + + Cost + 1 0 + + + UC UD UN + 2 10 1 + + + + + + AirportSite + + + + 440 289 503 315 + + + AirTraffic + + + + 375 62 436 100 + + + Litigation + + + + 375 138 432 173 + + + Construction + + + + 365 211 440 257 + + + Deaths + + + + 558 62 609 93 + + + Noise + + + + 564 138 609 166 + + + Cost + + + + 567 221 609 247 + + + Per l'utilita' data:\nsi suppone di considerare le seguenti\npriorita' o preferenze:\nSafety, Tollerabilita' rumore, Costo\nCio' implica che tutte le utilita' con Deaths=Safe devono essere maggiori di quelle con Deaths=Unsafe, che, specificato Deaths, quelle con Noise=tolerable devono essere maggiori di quelle con Noise=untolerable e in subordine quelle con Cost=low maggiori di quelle con Cost=high + + 28 32 221 200 + + + UD + + + + 716 72 750 104 + + + UN + + + + 723 153 757 185 + + + UC + + + + 741 231 775 263 + + + U + + + + 886 168 913 200 + + + + diff --git a/src/test/resources/AppleTree.xdsl b/src/test/resources/AppleTree.xdsl new file mode 100644 index 0000000..2416252 --- /dev/null +++ b/src/test/resources/AppleTree.xdsl @@ -0,0 +1,92 @@ + + + + + + + + 0.1 0.9 + + + + + 0.1 0.9 + + + + + Malato Secco + 0.95 0.05000000000000004 0.9 0.09999999999999998 0.85 0.15 0.02 0.98 + + + + + PerditaFoglie + + + Trattamento + -8000 0 + + + Malato + 3000 20000 + + + + + Malato Trattamento + 0.2 0.8 0.99 0.01 0.01000000000000001 0.99 0.02000000000000002 0.98 + + + Secco + 0.6 0.4 0.05000000000000004 0.95 + + + + + 20 30 819 509 + + Malato + + + + 228 205 302 251 + + + Secco + + + + 226 87 300 133 + + + PerditaFoglie + + + + 382 143 508 221 + + + Trattamento + + + + 311 304 424 338 + + + CostoT + + + + 327 382 418 472 + + + U + + + + 584 295 625 343 + + + + diff --git a/src/test/resources/Cloudy.xdsl b/src/test/resources/Cloudy.xdsl new file mode 100644 index 0000000..cd6334d --- /dev/null +++ b/src/test/resources/Cloudy.xdsl @@ -0,0 +1,65 @@ + + + + + + + + 0.5 0.5 + + + + + Cloudy + 0.1 0.9 0.5 0.5 + + + + + Cloudy + 0.8 0.2 0.2 0.8 + + + + + Rain Sprinkler + 0.99 0.01000000000000001 0.9 0.09999999999999998 0.9 0.09999999999999998 0.01 0.99 + + + + + + Cloudy + + + + 498 107 576 155 + + + + Sprinkler + + + + 371 233 462 289 + + + + Rain + + + + 646 236 708 274 + + + + WetGrass + + + + 498 329 600 392 + + + + + diff --git a/src/test/resources/Malaria.xdsl b/src/test/resources/Malaria.xdsl new file mode 100644 index 0000000..b2b55da --- /dev/null +++ b/src/test/resources/Malaria.xdsl @@ -0,0 +1,56 @@ + + + + + + + + + 0.3333333333333333 0.3333333333333333 0.3333333333333334 + -1 + + + + + 0.5 0.5 + -1 + + + + + + Infliuenza Malaria + 0 1 2 0 1 + 0.99 0.01 0 0.4 0.55 0.04999999999999993 0 0 1 0.999 0.001 0 0 0 1 0.01000000000000001 0.09999999999999998 0.89 + -1 + + + + + + Infliuenza + + + + 338 118 434 178 + + + + Malaria + + + + 645 111 723 159 + + + + Febbre + + + + 524 310 605 360 + + + + + diff --git a/src/test/resources/Micromorti.xdsl b/src/test/resources/Micromorti.xdsl new file mode 100644 index 0000000..2bb8e7f --- /dev/null +++ b/src/test/resources/Micromorti.xdsl @@ -0,0 +1,89 @@ + + + + + + + + 0.5 0.5 + + + + + Malattia + 0.99 0.01000000000000001 0.15 0.85 + + + + + Sintomo + + + + + + Test Malattia + 0.95 0.05000000000000004 0 0.01000000000000001 0.99 0 0 0 1 0 0 1 + + + + + RisultatoTest + + + Malattia Intervento + 0.9871992685296302 0 0.9992856734670552 1 + + + + + + Malattia + + + + 283 79 335 111 + + + Sintomo + + + + 285 185 339 218 + + + Test + + + + 416 189 449 215 + + + RisultatoTest + + + + 443 60 518 106 + + + Intervento + + + + 571 188 631 214 + + + U + + + + 448 306 475 338 + + + (MM) Micromorte=1 probabilita' su un millione di morire entro l'anno\nStima: 1 MM=$20\n\nUna probabilita' p e' quindi = p*10^6 MM\n(facendo una proporzione p:x=10^-6:1)\n\nAvere la malattia e non intervenire comporta un rischio pari ad una probabilita' del 35% di morire entro l'anno.\nCio' e' pari a 350.000 MM ossia $7Millioni\n\nIntervenire sulla malattia abbassa tale probabilita' per il nostro paziente a 0.0002=200MM=$4000\n\nSe il ns paziente non e' malato la sua aspettativa di vita e' tale che la prob. che muoia entro l'anno e' 1/50000=20MM=$400\n\nL'intervento non e' rischioso (quindi si tralascia di stimare quante micromorti vale l'intervento in assenza di malattia)\n\nSupponiamo che l'intervento costi $5000\n\nLa funzione di utilita' si puo' calcolare usando questi costi\nU(no M, no I)=-400\nU(no M, I)=-400-5000\nU(M, no I)=-7M\nU(M, I)=-4000-5000\n\nRinormalizzata a 1 e' come appare nell'ID\n + + 3 21 230 497 + + + + diff --git a/src/main/resources/VentureBN.xdsl b/src/test/resources/VentureBN.xdsl similarity index 100% rename from src/main/resources/VentureBN.xdsl rename to src/test/resources/VentureBN.xdsl diff --git a/src/test/resources/WetGrass.xdsl b/src/test/resources/WetGrass.xdsl new file mode 100644 index 0000000..37b5fc2 --- /dev/null +++ b/src/test/resources/WetGrass.xdsl @@ -0,0 +1,65 @@ + + + + + + + + 0.4 0.6 + + + + + Cloudy + 0.7000000000000001 0.3 0.4 0.6 + + + + + Cloudy + 0.1 0.9 0.5 0.5 + + + + + Sprinkler Rain + 0.99 0.01000000000000001 0.85 0.15 0.9 0.09999999999999998 0.1 0.9 + + + + + + Cloudy + + + + 360 82 442 143 + + + + Rain + + + + 508 217 583 273 + + + + Sprinkler + + + + 214 216 305 282 + + + + Wet grass + + + + 357 337 455 408 + + + + + diff --git a/src/test/resources/lucas96simp.xdsl b/src/test/resources/lucas96simp.xdsl new file mode 100644 index 0000000..b1cdccc --- /dev/null +++ b/src/test/resources/lucas96simp.xdsl @@ -0,0 +1,160 @@ + + + + + + + + + 0.3333333333333333 0.3333333333333333 0.3333333333333334 + + + + + Age + 0.05 0.95 0.5 0.5 0.8 0.2 + -1 + + + + + Heart_failiure + 0.95 0.05000000000000004 0.1 0.9 + + + + + dyspnea + 0.98 0.02000000000000002 0.2 0.8 + + + + + dyspnea + 0.95 0.05000000000000004 0.1 0.9 + + + + + Heart_failiure + 0.7 0.3 0.15 0.85 + + + + + Age tachycpnea tachicardia PulmonaryCrepitations + -1 + + + + + treatment Heart_failiure Age + 0.6 0.4 0.45 0.55 0.65 0.35 0.1 0.9 0.15 0.85 0.18 0.8200000000000001 0.6 0.4 0.8 0.2 0.95 0.05000000000000004 0.1 0.9 0.15 0.85 0.18 0.8200000000000001 + -1 + + + + + intermdiate_result treatment + 0.5 0.5 0.95 0.05000000000000004 0.01 0.99 0.1 0.9 + + + treatment intermdiate_result Late_complications + 0 0.6 0.4 0.8 0.3 0.7 0.5 1 + + + + + + Age + + + + 599 419 640 444 + + + Heart_failiure + + + + 386 329 462 376 + + + dyspnea + + + + 387 197 480 221 + + + tachycpnea + + + + 385 102 451 143 + + + tachiycardia + + + + 543 203 609 244 + + + pulmonary crepitations + + + + 532 278 610 326 + + + aumento velocita' respirazione + + 333 86 479 100 + + + aumento velocita' cardiaca + + 513 187 641 201 + + + difficolta' a respirare + + 285 198 384 212 + + + treatment + + + + 713 177 770 203 + + + intermdiate result + + + + 619 643 712 701 + + + Late complications + + + + 863 432 962 493 + + + U + + + + 775 429 802 461 + + + Utilita' graduata considerando LateComplications piu' importante di IntermideiaResult piu' importante di costo trattamento + + 85 74 279 130 + + + +