From 435daebe2ac9394061a50d94424f59ad4861c92f Mon Sep 17 00:00:00 2001 From: Martin Lange <44003176+mlange-42@users.noreply.github.com> Date: Thu, 4 Jul 2024 21:03:33 +0200 Subject: [PATCH] Write info/into text for all example networks (#67) * add network info texts * update README --- .github/workflows/tests.yml | 3 +- CHANGELOG.md | 3 +- README.md | 8 ++- _examples/asia.yml | 13 ++++ _examples/disease-control.yml | 40 ++++++++--- _examples/earthquake.yml | 15 ++++ _examples/fruits-untrained.yml | 1 - _examples/logic/friends.yml | 3 + _examples/logic/weekday.yml | 3 + .../{medical-treatment.yml => medical.yml} | 27 +++++-- _examples/mendel.yml | 35 ++++++--- _examples/monty-hall.yml | 17 +++++ _examples/native-fish.yml | 71 ------------------- _examples/oil.yml | 19 +++-- _examples/robot.yml | 12 ++++ _examples/sprinkler.yml | 6 ++ _examples/umbrella.yml | 14 ++++ internal/tui/app.go | 13 ++-- internal/tui/input.go | 48 ++++++------- 19 files changed, 216 insertions(+), 135 deletions(-) rename _examples/{medical-treatment.yml => medical.yml} (60%) delete mode 100644 _examples/native-fish.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2b496e2..ec2a4e6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -80,10 +80,9 @@ jobs: ./bbn inference _examples/dog-problem.xml ./bbn inference _examples/earthquake.yml ./bbn train _examples/fruits-untrained.yml _examples/fruits.csv - ./bbn inference _examples/medical-treatment.yml + ./bbn inference _examples/medical.yml ./bbn inference _examples/mendel.yml ./bbn inference _examples/monty-hall.yml - ./bbn inference _examples/native-fish.yml ./bbn inference _examples/oil.yml ./bbn inference _examples/robot.yml ./bbn inference _examples/sprinkler.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 390e0a6..ee9a18b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ * Optional total utility node for weighting individual utilities (#59) * Adds support for custom node colors in YAML files (#64) * Adds shortcuts for logic nodes like, and, or, if-then, etc. (#65) -* Networks can have a longer info text, also shown in TUI app (#65) +* Networks can have a longer info text, also shown in TUI app (#66) ### Documentation @@ -23,6 +23,7 @@ * Adds medical treatment decision example (#60) * Adds disease control decision example (#61) * Adds several examples for logic deduction (#65, #66) +* All examples have a detailed description that shows up in the TUI app (#66, #67) ## [[v0.4.0]](https://github.com/mlange-42/bbn/compare/v0.3.0...v0.4.0) diff --git a/README.md b/README.md index 80b90ba..51b82a5 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,12 @@ Bayesian Belief Network library and CLI/TUI tool for [Go](https://go.dev). ## Features * Minimal, fast API for usage as a library. -* Decision networks with utility and decision nodes. -* Human-readable YAML format, as well as BIF-XML. -* Train and query networks from the command line with `bbn`. * Visualize, query and explore networks in the interactive TUI app `bbni`. +* Supports decision networks (aka influence diagrams) for multi-stage decisions. +* Provides logic nodes for logic inference in addition to probabilistic inference. +* Train and query networks from the command line with `bbn`. +* Human-readable YAML format for networks, as well as BIF-XML. +* Plenty of [examples](https://github.com/mlange-42/bbn/tree/main/_examples) with introductory text, shown in-app. ## Installation diff --git a/_examples/asia.yml b/_examples/asia.yml index 5df4a57..f7f926b 100644 --- a/_examples/asia.yml +++ b/_examples/asia.yml @@ -1,14 +1,24 @@ name: Asia +info: >- + The good old Asia example about lung cancer diagnosis. + + + Yellow are typical evidence nodes. Set/unset their values by clicking the bars. + + + Observe the probabilities for the three diseases (white nodes). variables: - variable: Visit to Asia position: [1, 0] + color: yellow outcomes: [yes, no] table: - [1, 99] - variable: Smoker position: [49, 0] + color: yellow outcomes: [yes, no] table: - [50, 50] @@ -39,12 +49,14 @@ variables: - variable: Tuberculosis or Cancer position: [18, 14] + color: gray given: [Has Tuberculosis, Has Lung Cancer] outcomes: [yes, no] logic: OR - variable: XRay Result position: [1, 21] + color: yellow given: [Tuberculosis or Cancer] outcomes: [abnormal, normal] table: @@ -53,6 +65,7 @@ variables: - variable: Dyspnea position: [49, 21] + color: yellow given: [Tuberculosis or Cancer, Has Bronchitis] outcomes: [yes, no] table: diff --git a/_examples/disease-control.yml b/_examples/disease-control.yml index 3d8258d..cbfc3c9 100644 --- a/_examples/disease-control.yml +++ b/_examples/disease-control.yml @@ -1,4 +1,26 @@ name: Disease Control Decision +info: >- + A decision network for disease control decisions in livestock herds. + + + Three decisions are taken sequentially: + + (1) Whether to vaccinate, preventively, depending on herd size. + Vaccination reduces infection and spread risk. + + (2) Whether to perform a test, depending on the herd size, + previous vaccination, and whether an animal has symptoms. + + (3) Whether to treat (or cull) the animal, depending on the herd size, + previous vaccination, the test and its result. + Treatment reduces the spread risk. + + + Get definitive decisions by setting the values of "Herd size", + "Symptoms" and "Test result" by clicking on the resp. bars. + + + Particularly inspect the differences between the policies for the different herd sizes. variables: - variable: Herd size @@ -89,7 +111,7 @@ variables: - [-10] # treatment+ - [ 0] # treatment- -- variable: Outbreak +- variable: Spread position: [79, 6] color: gray outcomes: [yes, no] @@ -107,15 +129,15 @@ variables: - [0, 100] # inf- treat- vacc+ - [0, 100] # inf- treat- vacc- -- variable: Outbreak cost - given: [Outbreak, Herd size] +- variable: Spread cost + given: [Spread, Herd size] outcomes: [euros] position: [78, 0] type: utility table: - - [-100] # outbreak+ small - - [-500] # outbreak+ medium - - [-5000] # outbreak+ large - - [0] # outbreak- small - - [0] # outbreak- medium - - [0] # outbreak- large + - [-100] # spread+ small + - [-500] # spread+ medium + - [-5000] # spread+ large + - [0] # spread- small + - [0] # spread- medium + - [0] # spread- large diff --git a/_examples/earthquake.yml b/_examples/earthquake.yml index 77689d8..10a6560 100644 --- a/_examples/earthquake.yml +++ b/_examples/earthquake.yml @@ -1,8 +1,23 @@ name: Earthquake Decision Network +info: >- + A network for the decision on evacuating a city, + based on warnings from a seismic sensor. + + + The actor can observe the sensor warning, as well as the quality of sensor maintenance. + To trigger a definitive decision, set these two to some value by clicking on the bars. + + + In this example, multiple utility nodes (green) are used. + "Material damage" depends on the actual earthquake, + "Human damage" depends on the earthquake and the decision, + and "Evacuation cost" only depends on the decision. + The sum of all three is optimized. variables: - variable: Earthquake next week position: [1, 0] + color: gray outcomes: [strong, slight, none] table: - [1, 5, 94] diff --git a/_examples/fruits-untrained.yml b/_examples/fruits-untrained.yml index 1de9083..055dcfb 100644 --- a/_examples/fruits-untrained.yml +++ b/_examples/fruits-untrained.yml @@ -1,7 +1,6 @@ # An untrained network. # Train with: # bbn train _examples/fruits.yml _examples/fruits.csv - name: Fruits variables: diff --git a/_examples/logic/friends.yml b/_examples/logic/friends.yml index 06ffa58..35afc2b 100644 --- a/_examples/logic/friends.yml +++ b/_examples/logic/friends.yml @@ -1,3 +1,6 @@ +# Source of the puzzle: +# https://www.logisch-gedacht.de/logikraetsel/wochentag/ + name: Who is right? info: >- Four friends A, B, C and D meet and state the quoted facts below. diff --git a/_examples/logic/weekday.yml b/_examples/logic/weekday.yml index 20c32fe..05dff8a 100644 --- a/_examples/logic/weekday.yml +++ b/_examples/logic/weekday.yml @@ -1,3 +1,6 @@ +# Source of the puzzle: +# https://www.logisch-gedacht.de/logikraetsel/wochentag/ + name: Weekday 7 friends puzzle info: >- Seven friends meet, and are not sure which day of the week it is. diff --git a/_examples/medical-treatment.yml b/_examples/medical.yml similarity index 60% rename from _examples/medical-treatment.yml rename to _examples/medical.yml index be7b6f0..216d477 100644 --- a/_examples/medical-treatment.yml +++ b/_examples/medical.yml @@ -1,5 +1,20 @@ name: Medical Treatment Decision +info: >- + A decision network for the sequential decisions on whether to test a patient, + and whether to treat them. + + The decision for testing is based on whether the patient exhibits symptoms. + Set the "Symptoms" node to a value by clicking a bar. + + + The decision for treatment is based on symptoms, whether a test was performed, and the test result. + Set the test result to get a definitive decision. + + + Note that the actual infection state of the patient is not known for the decisions. + Also note that it is a severe disease, which is very cheap to treat. + Therefore, the optimal policy might be unexpected. variables: - variable: Infected @@ -12,7 +27,7 @@ variables: - variable: Symptoms outcomes: [yes, no] given: [Infected] - position: [1, 8] + position: [1, 10] table: - [0.8, 0.2] # infected+ - [0.05, 0.95] # infected- @@ -24,7 +39,7 @@ variables: type: utility table: - [ -100] # infected+ treatment+ - - [-1000] # infected+ treatment- + - [-5000] # infected+ treatment- - [ 0] # infected- treatment+ - [ 0] # infected- treatment- @@ -37,7 +52,7 @@ variables: - variable: Test result outcomes: [pos, neg] given: [Test, Infected] - position: [31, 8] + position: [31, 7] table: - [0.95, 0.05] # test+ infected+ - [0.05, 0.95] # test+ infected- @@ -50,12 +65,12 @@ variables: position: [1, 24] type: utility table: - - [-5] # test+ + - [-10] # test+ - [ 0] # test- - variable: Treatment outcomes: [yes, no] - given: [Test, Test result] + given: [Symptoms, Test, Test result] position: [46, 17] type: decision @@ -66,5 +81,5 @@ variables: type: utility table: - [-100] # treatment+ - - [ 0] # treatment- + - [ 0] # treatment- \ No newline at end of file diff --git a/_examples/mendel.yml b/_examples/mendel.yml index c42f02d..4420d11 100644 --- a/_examples/mendel.yml +++ b/_examples/mendel.yml @@ -4,17 +4,33 @@ # - https://www.norsys.com/netlibrary/nets/tut/Mendel%20Genetics_tut.htm name: Mendel Genetics +info: >- + Mendel's experiment of breeding red and white flowered peas. + + + White nodes show observable colors of parents and offspring, + while gray nodes are unobservable genetics. + + + 'R' is the dominant allele for red flowers, + 'w' is the recessive allele for white flowers. + + + Set evidence on some nodes by clicking the bars. + Observe the behavior of other nodes. variables: - variable: Genes Parent 1 outcomes: [RR, Rw, ww] position: [17, 0] + color: gray table: - [25, 50, 25] - variable: Genes Parent 2 outcomes: [RR, Rw, ww] position: [49, 0] + color: gray table: - [25, 50, 25] @@ -27,10 +43,20 @@ variables: - [1, 0] # Rw - [0, 1] # ww +- variable: Color Parent 2 + given: [Genes Parent 2] + outcomes: [red, white] + position: [65, 8] + table: + - [1, 0] # RR + - [1, 0] # Rw + - [0, 1] # ww + - variable: Genes Child given: [Genes Parent 1, Genes Parent 2] outcomes: [RR, Rw, ww] position: [33, 8] + color: gray table: # RR Rw ww P1 P2 - [100, 0, 0] # RR RR @@ -43,15 +69,6 @@ variables: - [ 0, 50, 50] # ww Rw - [ 0, 0, 100] # ww ww -- variable: Color Parent 2 - given: [Genes Parent 2] - outcomes: [red, white] - position: [65, 8] - table: - - [1, 0] # RR - - [1, 0] # Rw - - [0, 1] # ww - - variable: Color Child given: [Genes Child] outcomes: [red, white] diff --git a/_examples/monty-hall.yml b/_examples/monty-hall.yml index 35a750d..fdceaee 100644 --- a/_examples/monty-hall.yml +++ b/_examples/monty-hall.yml @@ -1,4 +1,21 @@ name: Monty-Hall Problem +info: + The good old Monty Hall Problem. + + + It is a game with three doors, behind one of which is the price (a car), + and nothing is behind the two other doors. + First, the player chooses a door. + Next, the game host opens one of the two doors that were not selected by the player. + Finally, the player may choose to change their decision to the other close door. + + + Should the player change the decision? + This question is solved by the network, already without any interaction. + + + Explore the network by setting some evidence (by clicking bars), + and try to understand why the player should change. variables: - variable: Player diff --git a/_examples/native-fish.yml b/_examples/native-fish.yml deleted file mode 100644 index af22317..0000000 --- a/_examples/native-fish.yml +++ /dev/null @@ -1,71 +0,0 @@ -# Native Fish V1 from https://www.abnms.org/bn/107 -name: Native Fish -variables: - -- variable: Pesticide Use - outcomes: [High, Low] - position: [1, 0] - table: - - [90, 10] - -- variable: Drought Conditions - outcomes: [Yes, No] - position: [32, 0] - table: - - [25, 75] - -- variable: Annual Rainfall - outcomes: [BelowAvg, Avg, AboveAvg] - position: [66, 0] - table: - - [10, 70, 20] - -- variable: Pesticide in River - given: [Pesticide Use, Annual Rainfall] - outcomes: [High, Low] - position: [1, 8] - table: - # high low PesticideUse AnnualRainfall - - [30, 70] # High BelowAvg - - [60, 40] # High Avg - - [80, 20] # High AboveAvg - - [10, 90] # Low BelowAvg - - [20, 80] # Low Avg - - [30, 70] # Low AboveAvg - -- variable: River Flow - given: [Drought Conditions, Annual Rainfall] - outcomes: [Good, Poor] - position: [32, 8] - table: - # good poor DroughtCond AnnualRainfall - - [ 5, 95] # Yes BelowAvg - - [15, 85] # Yes Avg - - [80, 20] # Yes AboveAvg - - [40, 60] # No BelowAvg - - [60, 40] # No Avg - - [99, 1] # No AboveAvg - -- variable: Tree Condition - given: [Drought Conditions, Annual Rainfall] - outcomes: [Good, Damaged, Dead] - position: [64, 8] - table: - # good damaged dead DroughtCond AnnualRainfall - - [ 20, 60, 20] # Yes BelowAvg - - [ 25, 60, 15] # Yes Avg - - [ 30, 60, 10] # Yes AboveAvg - - [ 70, 25, 5] # No BelowAvg - - [ 80, 18, 2] # No Avg - - [ 90, 9, 1] # No AboveAvg - -- variable: Native Fish Abundance - given: [Pesticide in River, River Flow] - outcomes: [High, Medium, Low] - position: [16, 16] - table: - # high medium low PestInRiver RiverFlow - - [ 20, 40, 40] # Yes Good - - [ 1, 10, 89] # Yes Poor - - [ 80, 15, 5] # No Good - - [ 5, 15, 80] # No Poor diff --git a/_examples/oil.yml b/_examples/oil.yml index 9814121..dc19566 100644 --- a/_examples/oil.yml +++ b/_examples/oil.yml @@ -1,5 +1,16 @@ name: Oil Drilling Decision +info: >- + A decision network for sequential decisions on oil test drilling and actual drilling. + + First, a decision is taken whether to do test drilling. + Then, based on the result of the decision and a potential test result, + the actual drilling decision is made. + + + The decision for testing was already in favor, as it does not depend on any observations. + To get a definitive drilling decision, set "Test result" (white) to a value by clicking a bar. + Also observe the expected test and drill utilities. variables: - variable: Oil @@ -12,7 +23,7 @@ variables: - variable: Test result position: [1, 8] outcomes: ["closed", "open", "diffuse"] - given: [Oil, Do test] + given: [Oil, Do test drill] table: # closed, open, diffuse - [0.1, 0.3, 0.6] # dry, test+ @@ -22,7 +33,7 @@ variables: - [0.5, 0.4, 0.1] # soaking, test+ - [0.333, 0.333, 0.333] # soaking, test- -- variable: Do test +- variable: Do test drill position: [38, 15] outcomes: ["yes", "no"] type: decision @@ -31,7 +42,7 @@ variables: position: [38, 8] outcomes: ["yes", "no"] type: decision - given: [Do test, Test result] + given: [Do test drill, Test result] - variable: Drill utility position: [36, 0] @@ -50,7 +61,7 @@ variables: position: [0, 17] outcomes: ["expected"] type: utility - given: [Do test] + given: [Do test drill] table: - [-10] # test+ - [ 0] # test- diff --git a/_examples/robot.yml b/_examples/robot.yml index d053468..b574adb 100644 --- a/_examples/robot.yml +++ b/_examples/robot.yml @@ -1,5 +1,17 @@ name: Robot Decision +info: + The famous robot decision network example. + + the robot has to choose between a short and a long route, + where the short route harbours the risk of an accident. + Further, the robot decides whether to wear protective pads + to reduce damage in case of an accident. + + + The decision is already taken, as it does not depend on any information. + You can set the value of "Accident" by clicking a bar, + and observe the expected utility. variables: - variable: Short path diff --git a/_examples/sprinkler.yml b/_examples/sprinkler.yml index bfb241c..b13de8a 100644 --- a/_examples/sprinkler.yml +++ b/_examples/sprinkler.yml @@ -1,4 +1,10 @@ name: Sprinkler +info: >- + The famous sprinkler example. + + + Experiment with setting/unsetting the evidence values of some nodes by clicking the bars. + Observe how the remaining nodes behave. variables: - variable: Rain diff --git a/_examples/umbrella.yml b/_examples/umbrella.yml index 1bf1aab..71d63c5 100644 --- a/_examples/umbrella.yml +++ b/_examples/umbrella.yml @@ -1,4 +1,18 @@ name: Umbrella Decision Network +info: >- + Decision network for whether to take an umbrella. + The agent knows the weather forecast, but weather itself is not observable. + + + The umbrella decision (blue) is taken to maximize the utility (green), + which depends on the actual weather and the decision. + + + To trigger a decision, set the forecast (white) to a value by clicking a bar. + To view the complete decision policy, right-click the Umbrella node. + + + To inspect utilities, right-click the green utility node. variables: - variable: Weather diff --git a/internal/tui/app.go b/internal/tui/app.go index 619cb5f..4e33395 100644 --- a/internal/tui/app.go +++ b/internal/tui/app.go @@ -120,7 +120,10 @@ func (a *App) Run() error { a.info.SetMouseCapture(a.mouseInputInfo) rooted := a.app.SetRoot(a.pages, true) - a.showInfo() + + if a.network.Info() != "" { + a.showInfo() + } if err := rooted.Run(); err != nil { return err @@ -168,7 +171,7 @@ func (a *App) createWidgets() { Navigate nodes Tab/BackTab Navigate bars Space/Numbers Toggle evidence Enter left click - Show table T right click + Show node table T right click Ignore policies P Move node W/A/S/D Save network Ctrl+S @@ -202,7 +205,7 @@ func (a *App) createMainPanel() *tview.Grid { func (a *App) createTablePanel() *tview.Grid { grid := tview.NewGrid(). SetColumns(0, 72, 0). - SetRows(0, 17, 0) + SetRows(0, 20, 0) subGrid := tview.NewGrid(). SetColumns(0). @@ -224,7 +227,7 @@ func (a *App) createTablePanel() *tview.Grid { func (a *App) createHelpPanel() *tview.Grid { grid := tview.NewGrid(). SetColumns(0, 72, 0). - SetRows(0, 17, 0) + SetRows(0, 20, 0) subGrid := tview.NewGrid(). SetColumns(0). @@ -247,7 +250,7 @@ func (a *App) createHelpPanel() *tview.Grid { func (a *App) createInfoPanel() *tview.Grid { grid := tview.NewGrid(). SetColumns(0, 72, 0). - SetRows(0, 17, 0) + SetRows(0, 20, 0) subGrid := tview.NewGrid(). SetColumns(0). diff --git a/internal/tui/input.go b/internal/tui/input.go index de83147..a4edbe3 100644 --- a/internal/tui/input.go +++ b/internal/tui/input.go @@ -132,30 +132,6 @@ func (a *App) moveNode(event *tcell.EventKey) *tcell.EventKey { return event } -func (a *App) inputTable(event *tcell.EventKey) *tcell.EventKey { - if event.Key() == tcell.KeyEsc { - a.pages.HidePage("Table") - return nil - } - return event -} - -func (a *App) inputHelp(event *tcell.EventKey) *tcell.EventKey { - if event.Key() == tcell.KeyEsc { - a.pages.HidePage("Help") - return nil - } - return event -} - -func (a *App) inputInfo(event *tcell.EventKey) *tcell.EventKey { - if event.Key() == tcell.KeyEsc { - a.pages.HidePage("Info") - return nil - } - return event -} - // inputEnter adds the currently selected node and state to the evidence. func (a *App) inputEnter() error { node := a.nodes[a.selectedNode] @@ -282,6 +258,30 @@ func (a *App) mouseInputInfo(action tview.MouseAction, event *tcell.EventMouse) return action, event } +func (a *App) inputTable(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyEsc || event.Key() == tcell.KeyEnter { + a.pages.HidePage("Table") + return nil + } + return event +} + +func (a *App) inputHelp(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyEsc || event.Key() == tcell.KeyEnter { + a.pages.HidePage("Help") + return nil + } + return event +} + +func (a *App) inputInfo(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyEsc || event.Key() == tcell.KeyEnter { + a.pages.HidePage("Info") + return nil + } + return event +} + func (a *App) mousePosInGraph(x, y int) (int, int) { boxX, boxY, _, _ := a.graph.GetInnerRect() scrollY, scrollX := a.graph.GetScrollOffset()