- added gui for decisions
- fixed problems with net
- added unrolled net (cause a bug of smile)
This commit is contained in:
2024-01-10 11:01:33 +01:00
parent af41eae58c
commit af825cc174
16 changed files with 1305 additions and 55 deletions

View File

@@ -0,0 +1,108 @@
package net.berack.upo.ai.gui;
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import net.berack.upo.ai.decision.PrototypeGUI;
import net.berack.upo.ai.decision.VehicleGUI;
import net.berack.upo.ai.problem1.Puzzle8GUI;
import net.berack.upo.ai.problem2.TrisGUI;
import net.berack.upo.ai.problem3.LikelihoodWeightingGUI;
/**
* Classe che rappresenta il main di tutto il progetto
* In essa si può navigare in tutti gli esercizi e testarli
* @author Berack
*/
public class MainGUI extends JFrame {
public static void main(String[] args) {
new MainGUI();
}
public final Puzzle8GUI Puzzle8GUI = new Puzzle8GUI();
public final TrisGUI TrisGUI = new TrisGUI();
public final LikelihoodWeightingGUI LikelihoodWeightingGUI = new LikelihoodWeightingGUI();
public final PrototypeGUI PrototypeGUI = new PrototypeGUI();
public final VehicleGUI VehicleGUI = new VehicleGUI();
private final JMenuBar menuBar = new JMenuBar();
/**
* Crea una finestra con nulla da mostrare, ma si può visualizzare uno degli esercizi tramite
* la barre dei menù che permette di cambiare da un esercizio all'altro
*/
private MainGUI() {
super("Progetto per AI");
this.buildMenu();
this.setJMenuBar(menuBar);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(500, 400);
this.setResizable(false);
this.setLocationRelativeTo(null);
this.setVisible(true);
}
/**
* Crea la barra dei menu aggiungendo tutti i menù passati in input
* @param menus una lista di menù da aggiungere
*/
private void buildMenu(JMenu...menus) {
menuBar.removeAll();
var menu = new JMenu("View");
var puzzle = new JMenuItem("Puzzle8 Game");
puzzle.addActionListener(action -> this.setPanel(Puzzle8GUI));
var tris = new JMenuItem("Tris Game");
tris.addActionListener(action -> this.setPanel(TrisGUI));
var lw = new JMenuItem("Likelihood Weighting");
lw.addActionListener(action -> this.setPanel(LikelihoodWeightingGUI));
var prototype = new JMenuItem("Prototype net");
prototype.addActionListener(action -> this.setPanel(PrototypeGUI));
var vehicle = new JMenuItem("Vehicle net");
vehicle.addActionListener(action -> this.setPanel(VehicleGUI));
menu.add(puzzle);
menu.add(tris);
menu.add(lw);
menu.add(new JSeparator());
menu.add(prototype);
menu.add(vehicle);
menuBar.add(menu);
for(var m : menus) if(m != null) menuBar.add(m);
}
/**
* Cambia il pannello principale con quello passato in input
* Nel caso sia un pannello MyDecision allora mette anche uno scroll panel per far si che si
* possano vedere tutti i nodi necessari
* @param panel il pannello da mostrare
*/
private void setPanel(MyPanel panel) {
if(panel instanceof MyDecisionPanel) {
var scroll = new JScrollPane(panel);
scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scroll.getVerticalScrollBar().setUnitIncrement(20);
scroll.setPreferredSize(new Dimension(500, 400));
this.setContentPane(scroll);
}
else this.setContentPane(panel);
this.buildMenu(panel.getMenu());
panel.updateAll();
this.pack();
this.invalidate();
this.validate();
this.repaint();
}
}

View File

@@ -0,0 +1,94 @@
package net.berack.upo.ai.gui;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.GroupLayout;
import javax.swing.JTextArea;
import net.berack.upo.ai.gui.nodes.NodeGUI;
import net.berack.upo.ai.gui.nodes.NodeGUIFactory;
import smile.Network;
/**
* Classe creata per espandere la classe MyPanel aggiungendo la possibilità di mostrare
* a schermo una rete e dei suoi nodi passati in input.
* Il costruttore costruisce automaticamente i nodi indicati ma non ne mostra i valori.
* Per farlo bisognerà utilizzare il metodo updateAll() appena creato il pannello.
* NOTA: updateAll() richiama il metodo della rete updateBeliefs() che può essere lento con molti nodi.
* @author Berack
*/
public abstract class MyDecisionPanel extends MyPanel {
public final Network net;
private final List<NodeGUI> update = new ArrayList<>();
/**
* Costruisce il dinamicamente da una rete e da una lista di nodi da mostrare.
* Il risultato sarà già inserito nel pannello e per vedere i valori bisognerà utilizzare il metodo updateAll()
* @param net la rete da mostrare
* @param nodes il sottoinsieme di nodi da mostrare
*/
protected MyDecisionPanel(Network net, String...nodes) {
this.net = net;
var layout = new GroupLayout(this);
var size = new Dimension(500, 400);
this.setSize(size);
this.setLayout(layout);
layout.setAutoCreateGaps(true);
var factory = new NodeGUIFactory(net, () -> this.updateAll());
var gLabel = layout.createParallelGroup();
var gBarch = layout.createParallelGroup();
var vGroup = layout.createSequentialGroup();
for(var node: nodes) {
var handle = net.getNode(node);
var panel = factory.create(handle);
update.add(panel);
var label = new JTextArea(net.getNodeName(handle));
label.setEditable(false);
label.setLineWrap(true);
label.setWrapStyleWord(true);
label.setOpaque(false);
label.setBorder(BorderFactory.createEmptyBorder());
this.setFont(label);
gLabel.addComponent(label);
gBarch.addComponent(panel);
vGroup.addGroup(layout.createParallelGroup()
.addComponent(label)
.addComponent(panel));
}
var hGroup = layout.createSequentialGroup();
hGroup.addGroup(gLabel).addGroup(gBarch);
layout.setVerticalGroup(vGroup);
layout.setHorizontalGroup(hGroup);
}
/**
* Utile per cambiare il font in BOLD e aumentarne la grandezza
* @param component il componente da cambiare il font
*/
private void setFont(Component component) {
var font = component.getFont();
font = new Font(font.getName(), Font.BOLD, font.getSize() + 2);
component.setFont(font);
}
@Override
public void updateAll() {
net.updateBeliefs();
for (var node : update)
node.updateNode();
}
}

View File

@@ -0,0 +1,27 @@
package net.berack.upo.ai.gui;
import javax.swing.JMenu;
import javax.swing.JPanel;
/**
* Classe utilizzata per far si che tutti i frame della finestra abbiano lo stesso metodo
* per la generazione dei menu.
* @author Berack
*/
public abstract class MyPanel extends JPanel {
/**
* Crea un menu da aggiungere alla lista, in modo che appaia solamente quando
* la finestra venga utilizzata.
* Questo metodo viene usato solo dalla finestra MainGUI
* @return il menu contestuale da aggiungere
*/
abstract public JMenu getMenu();
/**
* Permette di fare l'update di qualunque componente interno del pannello.
* Questo verrà utilizzato quando il pannello verrà mostrato in modo da
* disegnare correttamente il pannello.
*/
abstract public void updateAll();
}

View File

@@ -0,0 +1,86 @@
package net.berack.upo.ai.gui.nodes;
import java.awt.Font;
import java.awt.GridLayout;
import java.util.function.Consumer;
import javax.swing.AbstractButton;
import javax.swing.ButtonGroup;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import smile.Network;
/**
* La rappresentazione grafica di un nodo a decisione.
* Esso mostrerà gli outcome e i valori associati ad essi, e permetterà di sceglierne uno o l'altro.
* Nel caso in cui ci siano più valori per outcome allora il nodo non sarà abilitato
* @author Berack
*/
public class DecisionNodeGUI extends NodeGUI {
private final AbstractButton[] buttons;
private final JLabel[] values;
private final ButtonGroup group;
/**
* Costruisce il nodo decisionale in modo che si possa interagire con esso
* @param net la rete del nodo
* @param node il nodo
* @param action la azione da fare quando si preme su una decisione
*/
public DecisionNodeGUI(Network net, int node, Consumer<Integer> action) {
super(net, node);
var outcomes = net.getOutcomeCount(node);
this.buttons = new AbstractButton[outcomes];
this.values = new JLabel[outcomes];
this.group = new ButtonGroup();
var grid = new GridLayout(outcomes, 2);
this.setLayout(grid);
for(var i = 0; i < outcomes; i++) {
final var index = i;
var label = net.getOutcomeId(node, i);
var value = new JLabel();
var button = new JCheckBox(label);
button.setContentAreaFilled(false);
button.setFocusable(false);
button.addActionListener(a -> action.accept(index));
this.buttons[i] = button;
this.values[i] = value;
this.add(button);
this.add(value);
this.group.add(button);
}
}
@Override
public void updateNode() {
var selected = this.net.isEvidence(this.node)? this.net.getEvidence(this.node) : -1;
var values = this.net.getNodeValue(this.node);
var max = -1;
if(values.length == this.buttons.length) {
max = 0;
for(int i = 1; i < values.length; i++)
if(values[max] < values[i])
max = i;
}
this.group.clearSelection();
for(var i = 0; i < this.buttons.length; i++) {
var font = buttons[i].getFont();
var style = (i == max)? (Font.BOLD + Font.ITALIC) : Font.PLAIN;
var newFont = new Font(font.getName(), style, font.getSize());
var value = max < 0? "" : String.format("% 5.2f", values[i]);
this.buttons[i].setFont(newFont);
this.buttons[i].setSelected(i == selected);
this.buttons[i].setEnabled(max != -1);
this.values[i].setText(value);
}
}
}

View File

@@ -0,0 +1,35 @@
package net.berack.upo.ai.gui.nodes;
import javax.swing.JPanel;
import smile.Network;
/**
* Classe astratta che implementa un nodo base data la rete in input.
* @author Berack
*/
public abstract class NodeGUI extends JPanel {
public final Network net;
public final int node;
/**
* Salva informazioni essenziali del nodo
* I costruttori delle sottoclassi dovranno creare il contenuto del pannello
* ed utilizzare questo costruttore.
* I valori passati saranno poi disponibili alle proprietà pubbliche net e node.
*
* @param net la rete a cui appartiene
* @param node il valore dell'handle
*/
protected NodeGUI(Network net, int node) {
this.net = net;
this.node = node;
}
/**
* In questo metodo si deve fare un refresh dei valori mostrati nel pannello.
* I valori potranno essere presi direttamente dalla rete utilizzando le proprietà pubbliche net e node.
*/
abstract public void updateNode();
}

View File

@@ -0,0 +1,54 @@
package net.berack.upo.ai.gui.nodes;
import java.util.function.Consumer;
import smile.Network;
import smile.Network.NodeType;
/**
* Classe che raggruppa tutte le rappresentazioni grafiche dei nodi.
* È una classe comoda per la creazione dei nodi dato che crea automaticamente
* anche la funzione di update e scelta della evidenza per ogni nodo.
* @author Berack
*/
public class NodeGUIFactory {
private final Network net;
private final Runnable updateAll;
/**
* Crea una factory pronta per la creazione dei nodi
* @param net la rete
* @param updateAll una funzione richiesta per l'update di tutti gli altri nodi
*/
public NodeGUIFactory(Network net, Runnable updateAll) {
this.net = net;
this.updateAll = updateAll;
}
/**
* Crea una nuova rappresentazione grafica di un nodo passato in input.
*
* @throws IllegalArgumentException nel caso in cui il nodo non sia ancora supportato
* @param handle il nodo
* @return la rappresentazione grafica del nodo
*/
public NodeGUI create(int handle) {
Consumer<Integer> action = outcome -> {
if(net.isEvidence(handle) && net.getEvidence(handle) == outcome) this.net.clearEvidence(handle);
else this.net.setEvidence(handle, outcome);
this.updateAll.run();
};
return switch(net.getNodeType(handle)) {
case NodeType.DECISION -> new DecisionNodeGUI(net, handle, action);
case NodeType.UTILITY -> new UtilityNodeGUI(net, handle);
case NodeType.MAU -> new UtilityNodeGUI(net, handle);
case NodeType.NOISY_MAX -> new ProbabilityNodeGUI(net, handle, action);
case NodeType.CPT -> new ProbabilityNodeGUI(net, handle, action);
default -> throw new IllegalArgumentException("Node type not supported! ");
};
}
}

View File

@@ -0,0 +1,114 @@
package net.berack.upo.ai.gui.nodes;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.Label;
import java.util.function.Consumer;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import smile.Network;
/**
* La rappresentazione grafica di un nodo probabilistico.
* Esso mostrerà gli outcome ele probabilità associate ad essi, e permetterà di sceglierne uno o l'altro.
* Nel caso in cui ci siano più valori per outcome allora il nodo non sarà abilitato
* @author Berack
*/
public class ProbabilityNodeGUI extends NodeGUI {
public static final Color[] COLORS = {
new Color(255,127,0),
new Color(0,127,255),
new Color(0,255,127),
new Color(255,0,127),
new Color(127,0,255),
new Color(127,255,0),
};
private final JButton[] outcomes;
private final Label[] valuesChart;
private final JLabel[] valuesPercent;
/**
* Costruisce il nodo probabilistico in modo che si possa interagire con esso
* @param net la rete del nodo
* @param node il nodo
* @param action la azione da fare quando si preme su un outcome
*/
public ProbabilityNodeGUI(Network net, int node, Consumer<Integer> action) {
super(net, node);
var labels = net.getOutcomeIds(node);
var layout = new GridLayout(labels.length, 2);
this.setLayout(layout);
this.setBorder(BorderFactory.createCompoundBorder(new EmptyBorder(1, 0, 0, 0), BorderFactory.createLineBorder(Color.GRAY)));
this.outcomes = new JButton[labels.length];
this.valuesChart = new Label[labels.length];
this.valuesPercent = new JLabel[labels.length];
for(var i = 0; i < labels.length; i++) {
var lName = new JButton(labels[i]);
var lValue = new JLabel();
var barchart = new Label();
this.outcomes[i] = lName;
this.valuesChart[i] = barchart;
this.valuesPercent[i] = lValue;
final var index = i;
lName.setContentAreaFilled(false);
lName.setFocusable(false);
if(action == null) lName.setBorder(null);
else lName.addActionListener(a -> action.accept(index));
var size = barchart.getPreferredSize();
size.width = 100;
size.height = 10;
barchart.setPreferredSize(size);
barchart.setBackground(COLORS[i % COLORS.length]);
var panel1 = new JPanel();
panel1.setLayout(new GridLayout(1, 2));
panel1.add(lName);
panel1.add(lValue);
var panel2 = new JPanel();
panel2.setLayout(new BorderLayout());
panel2.add(barchart, BorderLayout.LINE_START);
this.add(panel1);
this.add(panel2);
}
}
@Override
public void updateNode() {
var values = this.net.getNodeValue(this.node);
var evidence = this.net.isEvidence(this.node)? this.net.getEvidence(node) : -1;
var enable = (values.length == this.outcomes.length);
for(var i = 0; i < this.outcomes.length; i++) {
var value = values[i] * 100;
var barchart = this.valuesChart[i];
var size = barchart.getPreferredSize();
size.width = enable? (int) (value * 1.5) : 0;
barchart.setSize(size);
barchart.setPreferredSize(size);
this.valuesPercent[i].setText(enable? String.format("% 4.2f%%", value) : " ");
if(evidence == i) this.outcomes[i].setForeground(Color.RED);
else this.outcomes[i].setForeground(Color.BLACK);
this.outcomes[i].setEnabled(enable);
}
}
}

View File

@@ -0,0 +1,47 @@
package net.berack.upo.ai.gui.nodes;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import javax.swing.JLabel;
import smile.Network;
/**
* La rappresentazione grafica di un nodo utilità.
* Esso mostrerà il valore solamente nel caso in cui esso sia l'unico.
* @author Berack
*/
public class UtilityNodeGUI extends NodeGUI {
private final JLabel utility;
/**
* Costruisce il nodo utilità che mostra il valore corrente
* @param net la rete del nodo
* @param node il nodo
*/
UtilityNodeGUI(Network net, int node) {
super(net, node);
this.utility = new JLabel();
var font = this.utility.getFont();
font = new Font(font.getName(), Font.BOLD, font.getSize() + 5);
this.setLayout(new FlowLayout());
this.utility.setFont(font);
this.utility.setForeground(Color.RED);
this.add(utility);
}
@Override
public void updateNode() {
var values = this.net.getNodeValue(this.node);
var val = (values.length == 1) ? String.format("% 5.2f", values[0]) : " ";
this.utility.setText(val);
}
}