├── .gitattributes ├── .gitignore ├── .gitmodules ├── Attributions.md ├── END USER AGREEMENT.txt ├── Examples ├── Cuban.gmcr ├── Elmira.gmcr ├── Garrison.gmcr ├── MilkRiver.gmcr ├── Prisoners.gmcr ├── SI_misp.gmcr └── SyriaIraq.gmcr ├── GMCR+handout.pdf ├── README.md ├── a_Main_Window.py ├── a_Main_Window.spec ├── cmdLineGuide.py ├── data_01_conflictModel.py ├── data_02_conflictSolvers.py ├── data_03_gmcrUtilities.py ├── data_04_spSolvers.py ├── frame_00_frameTemplate.py ├── frame_01_decisionMakers.py ├── frame_02_infeasibles.py ├── frame_02a_misperceptions.py ├── frame_03_irreversibles.py ├── frame_04_preferencePrioritization.py ├── frame_05_preferenceRanking.py ├── frame_06_equilibria.py ├── frame_07_inverseGMCR.py ├── frame_08_stabilityAnalysis.py ├── gmcr.ico ├── gmcr.py ├── icons ├── DMs_and_Options_OFF.gif ├── DMs_and_Options_ON.gif ├── Equilibria_Results_OFF.gif ├── Equilibria_Results_ON.gif ├── Infeasible_States_OFF.gif ├── Infeasible_States_ON.gif ├── Inverse_GMCR_OFF.gif ├── Inverse_GMCR_ON.gif ├── Irreversible_Moves_OFF.gif ├── Irreversible_Moves_ON.gif ├── Post_Analysis_OFF.gif ├── Post_Analysis_ON.gif ├── Preference_Ranking_OFF.gif ├── Preference_Ranking_ON.gif ├── Prioritization_OFF.gif ├── Prioritization_ON.gif ├── Thumbs.db ├── backArrow.gif ├── bothArrow.gif └── fwdArrow.gif ├── mp_test.py ├── savefile-schema.json ├── sp.py ├── test_data ├── Garrison_0_invSol.txt ├── Garrison_1_invSol.txt ├── Garrison_2_invSol.txt ├── Garrison_3_invSol.txt ├── Garrison_4_invSol.txt ├── Garrison_logSol.txt ├── Garrison_narr.txt ├── MilkRiver_0_invSol.txt ├── MilkRiver_1_invSol.txt ├── MilkRiver_2_invSol.txt ├── MilkRiver_3_invSol.txt ├── MilkRiver_4_invSol.txt ├── MilkRiver_logSol.txt ├── MilkRiver_narr.txt ├── Prisoners_0_invSol.txt ├── Prisoners_1_invSol.txt ├── Prisoners_2_invSol.txt ├── Prisoners_3_invSol.txt ├── Prisoners_logSol.txt ├── Prisoners_narr.txt ├── SyriaIraq_0_invSol.txt ├── SyriaIraq_1_invSol.txt ├── SyriaIraq_2_invSol.txt ├── SyriaIraq_3_invSol.txt ├── SyriaIraq_4_invSol.txt ├── SyriaIraq_logSol.txt └── SyriaIraq_narr.txt ├── tests.py ├── version.py ├── visualizerLauncher.py ├── widgets_f01_01_dmOptElements.py ├── widgets_f02_01_radioButtonEntry.py ├── widgets_f02_02_infeasTreeview.py ├── widgets_f02_03_feasDisp.py ├── widgets_f02a_01_radioButtonEntry.py ├── widgets_f02a_02_infeasTreeview.py ├── widgets_f02a_03_feasDisp.py ├── widgets_f03_01_irreversibleToggles.py ├── widgets_f04_01_prefRadioButton.py ├── widgets_f04_02_prefElements.py ├── widgets_f04_03_optionForm.py ├── widgets_f05_01_PrefRank.py ├── widgets_f06_01_logResultDisp.py ├── widgets_f07_01_inverseContent.py └── widgets_f08_01_stabilityAnalysis.py /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | build 3 | builds 4 | dist 5 | __pycache__ 6 | "*.pyc" 7 | license.txt 8 | nppWkspc 9 | tradeConflict 10 | Thumbs.db -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "gmcr-vis"] 2 | path = gmcr-vis 3 | url = https://github.com/onp/gmcr-vis.git 4 | branch = embedded 5 | -------------------------------------------------------------------------------- /Attributions.md: -------------------------------------------------------------------------------- 1 | #CC and free attributions 2 | 3 | ## Software packages 4 | 5 | [Python](https://www.python.org/) 6 | 7 | 8 | [Numpy](http://www.numpy.org/) 9 | 10 | 11 | [cx_Freeze](http://cx-freeze.sourceforge.net/) 12 | 13 | 14 | [jquery](http://jquery.com/) 15 | 16 | 17 | [d3.js](http://d3js.org/) 18 | 19 | 20 | 21 | 22 | ## CC Icons 23 | 24 | "Man of business standing on a circle with two possible options to choose" 25 | from [www.freepik.com](www.freepik.com) via [flaticon.com](www.flaticon.com/free-icon/man-of-business-standing-on-a-circle-with-two-possible-options-to-choose_47888) 26 | 27 | 28 | "Two circular arrows" from [www.freepik.com](www.freepik.com) via [flaticon.com](www.flaticon.com/free-icon/two-circular-arrows_761) 29 | 30 | 31 | "Rankings" from [www.freepik.com](www.freepik.com) via [flaticon.com](http://www.flaticon.com/free-icon/rankings_14265) 32 | 33 | 34 | "Data analysis symbol" from [www.freepik.com](www.freepik.com) via [flaticon.com](http://www.flaticon.com/free-icon/data-analysis-symbol_38811) 35 | 36 | 37 | "Stop" from [www.freepik.com](www.freepik.com) via [flaticon.com](http://www.flaticon.com/free-icon/stop_1718) 38 | 39 | 40 | "Data wave" from [www.freepik.com](www.freepik.com) via [flaticon.com](http://www.flaticon.com/free-icon/data-wave_38860) 41 | 42 | 43 | "Smart rank symbol of eyeglasses with bars graphic" from [www.freepik.com](www.freepik.com) via [flaticon.com](http://www.flaticon.com/free-icon/smart-rank-symbol-of-eyeglasses-with-bars-graphic_36314) -------------------------------------------------------------------------------- /END USER AGREEMENT.txt: -------------------------------------------------------------------------------- 1 | END USER AGREEMENT FOR GMCR+ 1. I hereby agree to maintain, at any given time, no more installations of this GMCR+ software than the number of licenses purchased. I will not make copies of the software, other than a single copy for archival purposes. Unless enforcement is prohibited by applicable law, I may not modify, decompile, or reverse engineer the software. I agree not to transfer any copy of the software to any other party. 2. I acknowledge that the copyright for GMCR+ is owned by Rami A. Kinsara, Oskar Petersons, Keith W. Hipel, and D. Marc Kilgour (referred to as the Vendors). I further acknowledge that the title, ownership rights, and all intellectual property rights in and to the GMCR+ software shall remain the sole and exclusive property of the Vendors. 3. I understand that this version of GMCR+ was designed as a research and consulting tool for the Vendors and is provided as is. 4. To the greatest extent consistent with law, in no event will the Vendors be liable for any lost revenue, profit, or data, or for any special, indirect, consequential, incidental, or punitive damages, however caused, regardless of the theory of liability, arising out of or related to the use or inability to use the software, even if the Vendors have been advised of the possibility of such damages. 5. While the Vendors may be willing to make reasonable effort to address any usability or functionality issues with the software reported by me, I understand that it is by no means the Vendors’ obligation to do so and that no warranty is granted in this regard. -------------------------------------------------------------------------------- /Examples/Cuban.gmcr: -------------------------------------------------------------------------------- 1 | {"useManualPreferenceVectors": false, "version": "0.3.1", "decisionMakers": [{"preferences": [[[2, "Y"]], [[3, "N"]], {"members": [[[0, "N"], [2, "Y"]]], "compound": true}, [[1, "N"], [2, "Y"]], [[0, "Y"], [3, "Y"]], [[1, "Y"], [3, "Y"]], {"members": [[[0, "Y"], [2, "N"]], [[1, "Y"], [3, "N"]], [[0, "Y"], [3, "N"]], [[1, "Y"], [2, "N"]]], "compound": true}, [[0, "N"], [2, "N"], [3, "N"]], [[1, "N"], [2, "N"], [3, "N"]]], "name": "US", "options": [0, 1]}, {"preferences": [[[3, "N"]], {"members": [[[0, "Y"], [3, "Y"]], [[0, "N"], [3, "N"]], [[0, "Y"], [3, "N"]]], "compound": true}, {"members": [[[1, "Y"], [3, "Y"]], [[1, "N"], [3, "N"]], [[1, "Y"], [3, "N"]]], "compound": true}, [[0, "N"], [3, "N"]], [[1, "N"], [3, "N"]], {"members": [[[1, "Y"], [2, "Y"]], [[1, "N"], [2, "N"]], [[0, "Y"], [2, "Y"]]], "compound": true}, [[1, "Y"], [2, "N"], [3, "N"]], [[0, "Y"], [1, "N"], [2, "Y"], [3, "N"]]], "name": "USSR", "options": [2, 3]}], "program": "gmcr-py", "infeasibles": [[[2, "Y"], [3, "Y"]]], "options": [{"name": "Air Strike", "permittedDirection": "fwd"}, {"name": "Blockade", "permittedDirection": "both"}, {"name": "Withdraw", "permittedDirection": "both"}, {"name": "Escalate", "permittedDirection": "both"}]} -------------------------------------------------------------------------------- /Examples/Elmira.gmcr: -------------------------------------------------------------------------------- 1 | {"options": [{"permittedDirection": "fwd", "name": "Modify"}, {"permittedDirection": "back", "name": "Delay"}, {"permittedDirection": "both", "name": "Accept"}, {"permittedDirection": "fwd", "name": "Abandon"}, {"permittedDirection": "both", "name": "Insist"}], "version": "0.3.6", "coalitionsFull": [{"options": [0], "name": "MoE"}, {"options": [1, 2, 3], "name": "Uniroil"}, {"options": [4], "name": "Local Government"}], "decisionMakers": [{"options": [0], "preferenceRanking": [9, 3, 4, 10, 7, 1, 2, 8, [11, 5, 6, 12]], "preferences": [[[2, "Y"]], [[3, "N"]], [[0, "N"]], {"compound": true, "members": [[[0, "N"], [4, "Y"]], [[0, "Y"], [4, "N"]]]}], "name": "MoE"}, {"options": [1, 2, 3], "preferenceRanking": [1, 4, 10, 7, [5, 11, 6, 12], 2, 3, 9, 8], "preferences": [{"compound": true, "members": [[[0, "Y"], [2, "Y"]], [[0, "N"], [2, "N"]]]}, [[3, "N"]], [[4, "N"]], {"compound": true, "members": [[[1, "Y"], [4, "N"]], [[1, "N"], [4, "Y"]]]}], "name": "Uniroyal"}, {"options": [4], "preferenceRanking": [9, 3, 7, 1, 10, 8, 4, 2, [11, 5, 12, 6]], "preferences": [[[3, "N"]], [[0, "N"]], {"compound": true, "members": [[[0, "N"], [2, "Y"]], [[0, "Y"], [2, "N"]], [[0, "Y"], [2, "Y"]]]}, {"compound": true, "members": [[[0, "N"], [4, "N"]], [[0, "N"], [4, "Y"]], [[0, "Y"], [4, "Y"]]]}, [[1, "N"]], [[4, "Y"]]], "name": "Local Government"}], "infeasibles": [[[1, "Y"], [2, "Y"]], [[1, "Y"], [3, "Y"]], [[2, "Y"], [3, "Y"]], [[1, "N"], [2, "N"], [3, "N"]]], "program": "gmcr-py", "useManualPreferenceRanking": true, "coalitions": [0, 1, 2]} -------------------------------------------------------------------------------- /Examples/Garrison.gmcr: -------------------------------------------------------------------------------- 1 | {"useManualPreferenceVectors": true, "infeasibles": [[[0, "Y"], [1, "Y"]], [[0, "Y"], [2, "Y"]], [[1, "Y"], [2, "Y"]], [[5, "Y"], [6, "Y"]], [[5, "Y"], [7, "Y"]], [[5, "Y"], [8, "Y"]], [[6, "Y"], [7, "Y"]], [[6, "Y"], [8, "Y"]], [[7, "Y"], [8, "Y"]], [[0, "N"], [1, "N"], [2, "N"]], [[5, "N"], [6, "N"], [7, "N"], [8, "N"]], [[0, "Y"], [3, "N"]], [[2, "Y"], [3, "Y"]], [[0, "Y"], [4, "N"], [5, "N"]], [[1, "Y"], [4, "N"], [7, "Y"]], [[1, "Y"], [4, "N"], [8, "Y"]], [[2, "Y"], [4, "N"], [7, "Y"]], [[2, "Y"], [4, "N"], [8, "Y"]]], "program": "gmcr-py", "options": [{"name": "Full", "permittedDirection": "both"}, {"name": "Reduced", "permittedDirection": "both"}, {"name": "Appease", "permittedDirection": "both"}, {"name": "Legal Challenge", "permittedDirection": "both"}, {"name": "Treaty Challenge", "permittedDirection": "both"}, {"name": "Full", "permittedDirection": "both"}, {"name": "Reduced", "permittedDirection": "both"}, {"name": "Lonetree", "permittedDirection": "both"}, {"name": "Suspend", "permittedDirection": "both"}], "decisionMakers": [{"preferences": [], "name": "US Support", "preferenceVector": [3, 7, [1, 2], 4, [5, 6], 8, 9, 12, 11, 15, 10, 13, 14, 17, 16, 18, 19, 21, 20, 22, 23], "options": [0, 1, 2]}, {"preferences": [], "name": "US Opposition", "preferenceVector": [[21, 17, 13, 10, 6, 2], [23, 19], 15, 11, 8, 4, 22, 18, 14, 7, 3, 20, 16, 12, 9, 5, 1], "options": [3]}, {"preferences": [], "name": "Canadian Opposition", "preferenceVector": [23, [20, 21], 19, [16, 17], 11, [9, 10], 15, [12, 13], 2, 4, 1, 8, [5, 6], 22, 18, 14, 3, 7], "options": [4]}, {"preferences": [], "name": "IJC", "preferenceVector": [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]], "options": [5, 6, 7, 8]}]} -------------------------------------------------------------------------------- /Examples/MilkRiver.gmcr: -------------------------------------------------------------------------------- 1 | {"useManualPreferenceVectors": false, "infeasibles": [[[4, "Y"], [5, "Y"]], [[4, "Y"], [6, "Y"]], [[5, "Y"], [6, "Y"]], [[2, "Y"], [3, "Y"]], [[1, "Y"], [5, "Y"]], [[1, "Y"], [6, "Y"]]], "program": "gmcr-py", "options": [{"name": "Petition Review", "permittedDirection": "both"}, {"name": "Object", "permittedDirection": "both"}, {"name": "Reject Petition", "permittedDirection": "both"}, {"name": "Form Task Force", "permittedDirection": "both"}, {"name": "Favor US", "permittedDirection": "both"}, {"name": "Favor CA", "permittedDirection": "both"}, {"name": "No Agreement", "permittedDirection": "both"}], "decisionMakers": [{"preferences": [[[4, "Y"]], [[3, "Y"], [4, "Y"]]], "name": "US", "options": [0]}, {"preferences": [[[0, "N"], [1, "N"], [2, "N"], [3, "N"], [4, "N"], [5, "N"], [6, "N"]], [[0, "Y"], [5, "Y"]], [[6, "N"]], [[2, "Y"]]], "name": "CA", "options": [1]}, {"preferences": [[[0, "N"], [1, "N"]]], "name": "IJC", "options": [2, 3]}, {"preferences": [[[0, "N"], [1, "N"]]], "name": "TF", "options": [4, 5, 6]}]} -------------------------------------------------------------------------------- /Examples/Prisoners.gmcr: -------------------------------------------------------------------------------- 1 | {"useManualPreferenceVectors": false, 2 | "infeasibles": [], 3 | "program": "gmcr-py", 4 | "options": [ 5 | {"name": "Cooperate with 2", "permittedDirection": "both"}, 6 | {"name": "Cooperate with One", "permittedDirection": "both"} 7 | ], 8 | "decisionMakers": [ 9 | {"preferences": [[[1, "Y"]], [[0, "N"]]], "name": "Prisoner One", "options": [0]}, 10 | {"preferences": [[[0, "Y"]], [[1, "N"]]], "name": "prisoner 2", "options": [1]} 11 | ] 12 | } -------------------------------------------------------------------------------- /Examples/SI_misp.gmcr: -------------------------------------------------------------------------------- 1 | {"infeasibles": [[[0, "Y"], [1, "Y"]]], "useManualPreferenceRanking": false, "options": [{"permittedDirection": "both", "name": "Release Water"}, {"permittedDirection": "both", "name": "Escalate"}, {"permittedDirection": "both", "name": "Attack"}], "decisionMakers": [{"name": "Syria", "payoffs": [6, 3, 5, 2, 1, 4], "options": [0, 1], "misperceptions": [[[0, "Y"], [1, "N"]]], "preferences": [[[0, "N"], [1, "N"], [2, "N"]], [[1, "Y"]], [[2, "N"]], [[0, "N"]]]}, {"name": "Iraq", "payoffs": [3, 6, 1, 5, 2, 4], "options": [2], "misperceptions": [[[0, "N"], [1, "N"]]], "preferences": [[[0, "Y"], [1, "N"], [2, "N"]], [[0, "N"], [1, "N"], [2, "Y"]], [[0, "N"], [1, "Y"], [2, "Y"]], [[0, "N"], [1, "N"], [2, "N"]], [[0, "Y"], [1, "N"], [2, "Y"]]]}], "program": "gmcr-py", "coalitionsFull": [{"name": "Syria", "payoffs": [6, 3, 5, 2, 1, 4], "options": [0, 1], "misperceptions": [[[0, "Y"], [1, "N"]]], "preferences": [[[0, "N"], [1, "N"], [2, "N"]], [[1, "Y"]], [[2, "N"]], [[0, "N"]]]}, {"name": "Iraq", "payoffs": [3, 6, 1, 5, 2, 4], "options": [2], "misperceptions": [[[0, "N"], [1, "N"]]], "preferences": [[[0, "Y"], [1, "N"], [2, "N"]], [[0, "N"], [1, "N"], [2, "Y"]], [[0, "N"], [1, "Y"], [2, "Y"]], [[0, "N"], [1, "N"], [2, "N"]], [[0, "Y"], [1, "N"], [2, "Y"]]]}], "version": "0.3.12", "coalitions": [0, 1]} -------------------------------------------------------------------------------- /Examples/SyriaIraq.gmcr: -------------------------------------------------------------------------------- 1 | {"useManualPreferenceVectors": false, "infeasibles": [[[0, "Y"], [1, "Y"]]], "program": "gmcr-py", "options": [{"name": "Release Water", "permittedDirection": "both"}, {"name": "Escalate", "permittedDirection": "both"}, {"name": "Attack", "permittedDirection": "both"}], "decisionMakers": [{"preferences": [[[0, "N"], [1, "N"], [2, "N"]], [[1, "Y"]], [[2, "N"]], [[0, "N"]]], "name": "Syria", "options": [0, 1]}, {"preferences": [[[0, "Y"], [1, "N"], [2, "N"]], [[0, "N"], [1, "N"], [2, "Y"]], [[0, "N"], [1, "Y"], [2, "Y"]], [[0, "N"], [1, "N"], [2, "N"]], [[0, "Y"], [1, "N"], [2, "Y"]]], "name": "Iraq", "options": [2]}]} -------------------------------------------------------------------------------- /GMCR+handout.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/GMCR+handout.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gmcr-py 2 | ======= 3 | 4 | A Decision Support System (DSS) based on the Graph Model for Conflict Resolution (GMCR). 5 | 6 | Model implemented in python/numpy, with a tkinter GUI. 7 | 8 | _compatible with Python 3.2+, numpy 1.7+_ 9 | 10 | Developed with support from Rami Kinsara, Keith Hipel, and Marc Kilgour -------------------------------------------------------------------------------- /a_Main_Window.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Launches the GMCR-py Decision Support System.""" 4 | 5 | from version import __version__ 6 | from tkinter import Tk, VERTICAL, HORIZONTAL, N, S, E, W 7 | from tkinter import ttk 8 | from tkinter import filedialog 9 | from data_01_conflictModel import ConflictModel 10 | from frame_01_decisionMakers import DMInpFrame 11 | from frame_02_infeasibles import InfeasInpFrame 12 | from frame_02a_misperceptions import MisperceptionFrame 13 | from frame_03_irreversibles import IrrevInpFrame 14 | from frame_04_preferencePrioritization import PreferencesFrame 15 | from frame_05_preferenceRanking import PreferenceRankingFrame 16 | from frame_06_equilibria import ResultFrame 17 | from frame_07_inverseGMCR import InverseFrame 18 | from frame_08_stabilityAnalysis import StabilityFrame 19 | 20 | from multiprocessing import freeze_support 21 | import os 22 | import sys 23 | 24 | NSEW = (N, S, E, W) 25 | 26 | 27 | class MainAppWindow: 28 | """Top Level Window for the GMCR-py application.""" 29 | 30 | def __init__(self, file=None): 31 | """Initialize the window.""" 32 | self.file = file # file reference used for saving the conflict. 33 | 34 | self.root = Tk() 35 | self.root.iconbitmap('gmcr.ico') 36 | self.root.wm_title('GMCR+ v{} | New Model'.format(__version__)) 37 | self.root.columnconfigure(0, weight=1) 38 | self.root.rowconfigure(0, weight=1) 39 | 40 | self.frameList = [] 41 | self.frameBtnCmds = [] 42 | self.frameBtnList = [] 43 | 44 | self.topFrame = ttk.Frame(self.root) 45 | self.fileFrame = ttk.Frame(self.topFrame, border=3, relief='raised') 46 | self.pageSelectFrame = ttk.Frame(self.topFrame, border=3, 47 | relief='raised') 48 | self.contentFrame = ttk.Frame(self.topFrame) 49 | self.topVSep = ttk.Separator(self.topFrame, orient=HORIZONTAL) 50 | self.bottomHSep = ttk.Separator(self.contentFrame, orient=VERTICAL) 51 | 52 | self.topFrame.grid(column=0, row=0, sticky=NSEW) 53 | self.topFrame.columnconfigure(2, weight=1) 54 | self.topFrame.rowconfigure(3, weight=1) 55 | self.fileFrame.grid(column=0, row=1, sticky=NSEW) 56 | self.pageSelectFrame.grid(column=1, row=1, columnspan=4, 57 | sticky=(N, S, W)) 58 | self.pageSelectFrame.rowconfigure(0, weight=1) 59 | self.topVSep.grid(column=0, row=2, columnspan=10, sticky=NSEW) 60 | self.bottomHSep.grid(column=1, row=0, rowspan=10, sticky=NSEW) 61 | self.contentFrame.grid(column=0, row=3, columnspan=5, sticky=NSEW) 62 | self.contentFrame.columnconfigure(0, weight=1) 63 | self.contentFrame.rowconfigure(1, weight=1) 64 | 65 | self.saveButton = ttk.Button(self.fileFrame, text='Save Conflict', 66 | command=self.saveConflict, width=20) 67 | self.saveAsButton = ttk.Button(self.fileFrame, text='Save Conflict As', 68 | command=self.saveAs, width=20) 69 | self.loadButton = ttk.Button(self.fileFrame, text='Load Conflict', 70 | command=self.loadConflict, width=20) 71 | self.newButton = ttk.Button(self.fileFrame, text='New Conflict', 72 | command=self.newConflict, width=20) 73 | 74 | self.saveButton.grid(column=0, row=0, sticky=(E, W)) 75 | self.saveAsButton.grid(column=0, row=1, sticky=(E, W)) 76 | self.loadButton.grid(column=0, row=2, sticky=(E, W)) 77 | self.newButton.grid(column=0, row=3, sticky=(E, W)) 78 | 79 | self.activeConflict = ConflictModel() 80 | 81 | self.contentFrame.currFrame = None 82 | 83 | self.addMod(DMInpFrame) 84 | self.addMod(InfeasInpFrame) 85 | self.addMod(MisperceptionFrame) 86 | self.addMod(IrrevInpFrame) 87 | self.addMod(PreferencesFrame) 88 | self.addMod(PreferenceRankingFrame) 89 | self.addMod(ResultFrame) 90 | self.addMod(InverseFrame) 91 | self.addMod(StabilityFrame) 92 | 93 | self.refreshActiveFrames() 94 | 95 | self.root.bind_all("<>", self.checkFramesHaveData) 96 | 97 | if self.file is not None: 98 | self.loadConflict(self.file) 99 | 100 | self.root.mainloop() 101 | 102 | def addMod(self, newMod): 103 | """Add a new input frame and Module to the Conflict.""" 104 | newFrame = newMod(self.contentFrame, self.activeConflict) 105 | self.frameList.append(newFrame) 106 | newButton = newFrame.makeButton(self.pageSelectFrame, self) 107 | self.frameBtnList.append(newButton) 108 | newButton.grid(column=len(self.frameBtnList), row=0, sticky=NSEW) 109 | 110 | def checkFramesHaveData(self, event=None): 111 | """Check if required data exists for each frame then de/activate.""" 112 | for idx, frame in enumerate(self.frameList): 113 | if frame.hasRequiredData(): 114 | self.frameBtnList[idx].config(state="normal") 115 | else: 116 | frame.clearFrame() 117 | self.frameBtnList[idx].config(state="disabled") 118 | if frame != self.contentFrame.currFrame: 119 | frame.built = False 120 | 121 | def refreshActiveFrames(self, event=None): 122 | """Unload all frames and attempt to reload the current one.""" 123 | self.unloadAllFrames() 124 | self.checkFramesHaveData() 125 | 126 | try: 127 | self.contentFrame.currFrame.built = False 128 | except AttributeError: 129 | pass 130 | 131 | try: 132 | if self.contentFrame.currFrame.hasRequiredData(): 133 | self.contentFrame.currFrame.enter() 134 | else: 135 | self.frameBtnCmds[0](self) 136 | except AttributeError: 137 | self.frameBtnCmds[0](self) 138 | 139 | def unloadAllFrames(self, event=None): 140 | """Remove all frames (conflict screens) from the window.""" 141 | for idx, frame in enumerate(self.frameList): 142 | frame.clearFrame() 143 | self.frameBtnList[idx].config(state="disabled") 144 | 145 | def frameLeave(self): 146 | """Ungrid the current frame and performs other exit tasks.""" 147 | try: 148 | self.contentFrame.currFrame.leave() 149 | except AttributeError: 150 | pass 151 | 152 | def saveConflict(self): 153 | """Save all information to the currently active file.""" 154 | if not self.file: 155 | self.saveAs() 156 | return 157 | try: 158 | self.contentFrame.currFrame.leave() 159 | except AttributeError: 160 | pass 161 | self.contentFrame.currFrame.enter() 162 | self.activeConflict.save_to_file(self.file) 163 | print('Saved') 164 | 165 | def saveAs(self): 166 | """Open a file dialog to save the conflict to file.""" 167 | print('running saveAs') 168 | fileName = filedialog.asksaveasfilename( 169 | defaultextension='.gmcr', 170 | filetypes=(("GMCR+ Save Files", "*.gmcr"), ("All files", "*.*")), 171 | parent=self.root 172 | ) 173 | if fileName: 174 | self.file = fileName 175 | self.root.wm_title('GMCR+ v{} | {}'.format(__version__, self.file)) 176 | self.saveConflict() 177 | 178 | def loadConflict(self, fileName=None): 179 | """Open a file dialog that prompts for a save file to open.""" 180 | if not fileName: 181 | fileName = filedialog.askopenfilename( 182 | filetypes=(("GMCR+ Save Files", "*.gmcr"), 183 | ("All files", "*.*")), 184 | initialdir='{}/Examples'.format(os.getcwd()), 185 | parent=self.root 186 | ) 187 | if fileName: 188 | self.file = fileName 189 | self.frameBtnCmds[0](self) 190 | self.frameLeave() 191 | self.unloadAllFrames() 192 | print('loading: {}'.format(fileName)) 193 | self.root.wm_title('GMCR+ v{} | {}'.format(__version__, self.file)) 194 | self.activeConflict.load_from_file(fileName) 195 | self.refreshActiveFrames() 196 | 197 | def newConflict(self): 198 | """Clear all data, creating a new conflict.""" 199 | print("Initializing new conflict...") 200 | self.unloadAllFrames() 201 | self.activeConflict.__init__() 202 | self.file = None 203 | self.refreshActiveFrames() 204 | self.root.wm_title('GMCR+ v{} | New Model'.format(__version__)) 205 | 206 | 207 | if __name__ == '__main__': 208 | freeze_support() 209 | # Set working directory to program folder if started from executable. 210 | # This allows easy access to example files. 211 | 212 | if getattr(sys, 'frozen', False): 213 | path = getattr(sys, '_MEIPASS', os.getcwd()) 214 | os.chdir(path) 215 | else: 216 | try: 217 | os.chdir(sys.argv[0].rpartition('\\')[0]) 218 | except OSError: 219 | pass 220 | launchFile = None 221 | try: 222 | launchFile = sys.argv[1] 223 | except IndexError: 224 | pass 225 | a = MainAppWindow(launchFile) 226 | -------------------------------------------------------------------------------- /a_Main_Window.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python -*- 2 | 3 | block_cipher = None 4 | 5 | 6 | a = Analysis(['a_Main_Window.py'], 7 | pathex=['C:\\Users\\oskar\\Documents\\GitHub\\gmcr-py'], 8 | binaries=[], 9 | datas=[('Examples', 'Examples'), 10 | ('icons', 'icons'), 11 | ('gmcr-vis', 'gmcr-vis'), 12 | ('gmcr.ico', '.'), 13 | ('GMCR+handout.pdf', '.'), 14 | ('END USER AGREEMENT.txt', '.')], 15 | hiddenimports=[], 16 | hookspath=[], 17 | runtime_hooks=[], 18 | excludes=[], 19 | win_no_prefer_redirects=False, 20 | win_private_assemblies=False, 21 | cipher=block_cipher) 22 | pyz = PYZ(a.pure, a.zipped_data, 23 | cipher=block_cipher) 24 | exe = EXE(pyz, 25 | a.scripts, 26 | a.binaries, 27 | a.zipfiles, 28 | a.datas, 29 | name='GMCRplus', 30 | debug=False, 31 | strip=False, 32 | upx=True, 33 | runtime_tmpdir=None, 34 | console=False, 35 | icon='gmcr.ico' ) 36 | 37 | # call pyinstaller a_Main_Window.spec to build executable 38 | -------------------------------------------------------------------------------- /cmdLineGuide.py: -------------------------------------------------------------------------------- 1 | # To use gmcr-py from the command line: 2 | 3 | # First navigate to the folder where the program lives. 4 | # Then type: 5 | 6 | import gmcr 7 | 8 | # this makes the the conflict model classes available as gmcr.model 9 | # and the conflict solvers under as gmcr.solvers 10 | 11 | # To initialize a fresh conflict, next type: 12 | 13 | myConflict = gmcr.model.ConflictModel() 14 | 15 | # A decision maker is created by giving a reference to the parent conflict, and a name for the dm: 16 | 17 | syria = gmcr.model.DecisionMaker(myConflict, "syria") 18 | 19 | # the DM must then be added to the game: 20 | 21 | myConflict.decisionMakers.append(syria) 22 | 23 | #you can also a decision maker to the conflict more directly by typing: 24 | 25 | myConflict.decisionMakers.append("iraq") 26 | 27 | # grab a reference to this decisionMaker: 28 | 29 | iraq = myConflict.decisionMakers[-1] 30 | 31 | #The list of decision makers can be shown with 32 | 33 | print(myConflict.decisionMakers) 34 | 35 | #or: 36 | 37 | [dm.name for dm in myConflict.decisionMakers] 38 | 39 | #Now, lets give the decision makers some options: 40 | 41 | syria.addOption("Release Water") 42 | syria.addOption("Escalate") 43 | iraq.addOption("Attack") 44 | 45 | rw = syria.options[0] 46 | esc = myConflict.options[1] 47 | att = iraq.options[0] 48 | 49 | #View the options: 50 | 51 | [opt.name for opt in syria.options] 52 | [opt.name for opt in iraq.options] 53 | [opt.name for opt in myConflict.options] 54 | 55 | # remove infeasible states 56 | 57 | myConflict.infeasibles.append([[rw,"Y"],[esc,"Y"]]) 58 | myConflict.recalculateFeasibleStates() 59 | 60 | #add preferences 61 | 62 | syria.preferences.append([[rw,"N"],[esc,"N"],[att,"N"]]) 63 | syria.preferences.append([[esc,"Y"]]) 64 | syria.preferences.append([[att,"N"]]) 65 | syria.preferences.append([[rw,"N"]]) 66 | syria.calculatePreferences() 67 | 68 | iraq.preferences.append([[rw,"Y"],[esc,"N"],[att,"N"]]) 69 | iraq.preferences.append([[rw,"N"],[esc,"N"],[att,"Y"]]) 70 | iraq.preferences.append([[rw,"N"],[esc,"Y"],[att,"Y"]]) 71 | iraq.preferences.append([[rw,"N"],[esc,"N"],[att,"N"]]) 72 | iraq.preferences.append([[rw,"Y"],[esc,"N"],[att,"Y"]]) 73 | iraq.calculatePreferences() 74 | 75 | # Run the conflict! 76 | 77 | lSolver = gmcr.solvers.LogicalSolver(myConflict) 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /data_03_gmcrUtilities.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Assorted utility functions required by GMCR-py modules.""" 4 | 5 | import itertools 6 | import numpy 7 | 8 | bitFlip = {'N': 'Y', 'Y': 'N'} 9 | 10 | 11 | def reducePatterns(patterns): 12 | """Reduce patterns into compact dash notation. 13 | 14 | Effectively a partial implementation of the Quine-McCluskey Algorithm? 15 | """ 16 | for p in patterns: 17 | if len(p) != len(patterns[0]): 18 | raise ValueError("Patterns have different lengths.") 19 | 20 | if type(patterns) is not list: 21 | raise TypeError("Patterns must be provided as a list.") 22 | 23 | newPatterns = [] 24 | matched = [] 25 | for x, p1 in enumerate(patterns): 26 | if x in matched: 27 | continue 28 | for y, p2 in enumerate(patterns[x + 1:], 1): 29 | if x + y in matched: 30 | continue 31 | diffs = 0 32 | for idx, bit in enumerate(zip(p1, p2)): 33 | if bit[0] != bit[1]: 34 | diffs += 1 35 | dbit = idx 36 | if diffs > 1: 37 | break 38 | if diffs == 1: 39 | newPatterns.append(p1[:dbit] + '-' + p1[dbit + 1:]) 40 | matched += [x, x + y] 41 | break 42 | if x not in matched: 43 | newPatterns.append(p1) 44 | if matched: 45 | newPatterns = reducePatterns(newPatterns) 46 | return newPatterns 47 | 48 | 49 | def expandPatterns(patterns): 50 | """Expand patterns so that they contain no dashes.""" 51 | newPatterns = [] 52 | for pat in patterns: 53 | if '-' in pat: 54 | adding = [pat.replace('-', 'Y', 1), pat.replace('-', 'N', 1)] 55 | newPatterns += expandPatterns(adding) 56 | else: 57 | newPatterns += [pat] 58 | return newPatterns 59 | 60 | 61 | def yn2dec(ynState): 62 | """Convert a binary YN string into a decimal number.""" 63 | bit = 0 64 | output = 0 65 | for m in ynState: 66 | if m == 'Y': 67 | output += 2**bit 68 | bit += 1 69 | return output 70 | 71 | 72 | def dec2yn(decState, numOpts): 73 | """Convert a decimal number into a binary string of appropriate length.""" 74 | output = bin(decState).lstrip("0b").zfill( 75 | numOpts)[::-1].replace('1', 'Y').replace('0', 'N') 76 | return output 77 | 78 | 79 | def _subtractPattern(feas, sub): 80 | """Remove infeasible condition 'sub' from feasible condition 'feas'.""" 81 | if len(feas) != len(sub): 82 | raise ValueError("Patterns have different lengths.") 83 | sub = [x for x in enumerate(sub) if x[1] != '-'] 84 | # check if targ overlaps with state: 85 | for x in sub: 86 | idx, val = x 87 | if feas[idx] == bitFlip[val]: # if no overlap then no change, 88 | return [feas] # and return the same feasible condition 89 | # subtract overlap if it exists 90 | remainingStates = [] 91 | curr = feas 92 | for x in sub: 93 | idx, val = x 94 | if curr[idx] == '-': 95 | remainingStates.append(curr[:idx] + bitFlip[val] + curr[idx + 1:]) 96 | curr = curr[:idx] + val + curr[idx + 1:] 97 | return remainingStates 98 | 99 | 100 | def rmvSt(feas, rmv): 101 | """Subtract YND 'rmv' from states list of states 'feas'. 102 | 103 | feas: list of YND states. 104 | rmv: a single YND state. 105 | returns: list feas - rmv, and the number of states removed. 106 | """ 107 | orig = sum([2**x.count('-') for x in feas]) # Original number of states 108 | newfeas = [] 109 | for pattern in feas: 110 | newfeas += _subtractPattern(pattern, rmv) 111 | newfeas = reducePatterns(newfeas) 112 | # number of states removed 113 | numRmvd = orig - sum([2**x.count('-') for x in newfeas]) 114 | return newfeas, numRmvd 115 | 116 | 117 | def subtractStateSets(originalStates, statesToRemove): 118 | """Return the originalStates minus the statesToRemove. 119 | 120 | originalStates: list of YND states. 121 | statesToRemove: list of YND states. 122 | """ 123 | newStates = reducePatterns(originalStates) 124 | for rmv in reducePatterns(statesToRemove): 125 | newStates = rmvSt(newStates, rmv)[0] 126 | 127 | return newStates 128 | 129 | 130 | def mutuallyExclusive(mutEx): 131 | """Return the equivalent set of infeasible states.""" 132 | return list(itertools.combinations(mutEx, 2)) 133 | 134 | 135 | def orderedNumbers(decimalList): 136 | """Create translation dictionaries for using ordered numbers. 137 | 138 | Generates the decimal->ordered and ordered->decimal translation 139 | dictionaries for a list of decimal values. 140 | """ 141 | toOrdered = {} # decimal -> ordered dictionary 142 | toDecimal = {} # ordered -> decimal dictionary 143 | for i, x in enumerate(decimalList, 1): 144 | toOrdered[x] = i 145 | toDecimal[i] = x 146 | return toOrdered, toDecimal 147 | 148 | 149 | def validatePreferenceRanking(prefRank, feasibles): 150 | """Check that the preference ranking given is valid.""" 151 | alreadySeen = [] 152 | if not isinstance(prefRank, list): 153 | return "Invalid format." 154 | for state in prefRank: 155 | if state in feasibles.ordered: 156 | if state in alreadySeen: 157 | return "State {} cannot appear more than once.".format(state) 158 | alreadySeen.append(state) 159 | else: 160 | try: 161 | for subSt in state: 162 | if subSt in feasibles.ordered: 163 | if subSt in alreadySeen: 164 | return ("State {} cannot appear more than" 165 | " once.").format(subSt) 166 | alreadySeen.append(subSt) 167 | else: 168 | return ("State {} is not a feasible " 169 | "state.").format(subSt) 170 | except TypeError: 171 | return "State {} is not a feasible state".format(state) 172 | 173 | for state in feasibles.ordered: 174 | if state not in alreadySeen: 175 | return "State {} is missing.".format(state) 176 | 177 | return None 178 | 179 | 180 | def mapPrefRank2Payoffs(preferenceRanking, feasibles): 181 | """Map the preference rankings into payoff values for each state.""" 182 | payoffs = numpy.zeros(len(feasibles), numpy.int_) # clean payoffs array 183 | 184 | # use position in preference ranking to give a payoff value. 185 | for idx, state in enumerate(preferenceRanking): 186 | try: 187 | for subState in state: 188 | payoffs[subState - 1] = len(feasibles) - idx 189 | except TypeError: 190 | payoffs[state - 1] = len(feasibles) - idx 191 | 192 | if 0 in payoffs: 193 | state = feasibles.ordered[payoffs.index(0)] 194 | raise Exception(("Feasible state '%s' for DM was not included in the " 195 | "preference ranking").format(state)) 196 | 197 | return payoffs 198 | 199 | 200 | def prefPriorities2payoffs(preferences, feasibles): 201 | """Rank the states for a DM, generating payoff values. 202 | 203 | Ranking is based on Preference Prioritization, and output payoff values 204 | are sequential. 205 | """ 206 | # generate initial payoffs 207 | payoffsRaw = numpy.zeros(len(feasibles), numpy.int_) 208 | for preference in preferences: 209 | for state in feasibles.decimal: 210 | if preference.test(state): 211 | payoffsRaw[feasibles.toOrdered[state] - 1] += preference.weight 212 | 213 | # Reduce magnitude of payoffs. 214 | # Do not do this if weights had special meaning. 215 | uniquePayoffs = numpy.unique(payoffsRaw) 216 | preferenceRanking = [] 217 | payoffs = payoffsRaw.copy() # creates a copy 218 | 219 | for idx, value in enumerate(uniquePayoffs): 220 | for jdx, pay in enumerate(payoffsRaw): 221 | if pay == value: 222 | payoffs[jdx] = idx + 1 223 | stateSet = [idx + 1 for idx, pay in enumerate(payoffsRaw) 224 | if pay == value] 225 | if len(stateSet) > 1: 226 | preferenceRanking.append(stateSet) 227 | else: 228 | preferenceRanking.append(stateSet[0]) 229 | 230 | # necessary to put most preferred states at beginning instead of end 231 | preferenceRanking.reverse() 232 | 233 | return payoffs, preferenceRanking 234 | -------------------------------------------------------------------------------- /frame_00_frameTemplate.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Base class for frames to be loaded into the main window.""" 4 | 5 | from tkinter import Frame, PhotoImage, StringVar, N, S, E, W 6 | from tkinter import ttk 7 | 8 | NSEW = (N, S, E, W) 9 | 10 | 11 | class FrameTemplate(Frame): 12 | """Base class for frames to be loaded into the main window.""" 13 | 14 | # ######################## INITIALIZATION ################################ 15 | def __init__(self, master, conflict, buttonLabel, activeIcon, 16 | inactiveIcon, helpText): 17 | """Initialize the Frame. Does not build widgets.""" 18 | ttk.Frame.__init__(self, master) 19 | 20 | self.infoFrame = ttk.Frame(master, relief='sunken', borderwidth='3') 21 | self.helpFrame = ttk.Frame(master, relief='sunken', borderwidth='3') 22 | 23 | # Connect to active conflict module 24 | self.conflict = conflict 25 | 26 | # Label used for button to select frame in the main program. 27 | self.buttonLabel = buttonLabel 28 | # Image used on button to select frame, when frame is active. 29 | self.activeIcon = PhotoImage(file=activeIcon) 30 | # Image used on button to select frame, when frame is inactive. 31 | self.inactiveIcon = PhotoImage(file=inactiveIcon) 32 | # Help text string variable. 33 | self.helpVar = StringVar(value=self.helpText) 34 | 35 | self.built = False 36 | self.button = None 37 | 38 | def makeButton(self, buttonMaster, mainWindow): 39 | """Create a button to be used ot enter the frame.""" 40 | def onClick(*args): 41 | print('Loading {} frame...'.format(self.buttonLabel)) 42 | mainWindow.frameLeave() 43 | self.enter() 44 | mainWindow.contentFrame.currFrame = self 45 | 46 | mainWindow.frameBtnCmds.append(onClick) 47 | 48 | self.button = ttk.Button(buttonMaster, text=self.buttonLabel, 49 | image=self.inactiveIcon, compound="top", 50 | width=20, command=lambda: onClick()) 51 | self.button.grid(column=len(mainWindow.frameBtnList), row=0, 52 | sticky=NSEW) 53 | return self.button 54 | 55 | def hasRequiredData(self): 56 | """Check that minimum data required to render the frame exists. 57 | 58 | Always returns True. Should be re-implmented in subclass if an actual 59 | test is needed. 60 | """ 61 | return True 62 | 63 | def dataChanged(self): 64 | """Check if data has changed since the last build of the Frame. 65 | 66 | Always returns False. Should be re-implmented in subclass if an actual 67 | test is needed. 68 | """ 69 | return False 70 | 71 | def clearFrame(self): 72 | """Destroy all child widgets and mark frame as unbuilt.""" 73 | self.built = False 74 | for child in self.winfo_children(): 75 | child.destroy() 76 | self.infoFrame.grid_forget() 77 | self.helpFrame.grid_forget() 78 | 79 | def enter(self): 80 | """Re-grid the screen into the master. Perform required updates.""" 81 | if not self.built: 82 | self.buildFrame() 83 | self.refresh() 84 | self.grid() 85 | self.infoFrame.grid() 86 | self.helpFrame.grid() 87 | if self.button: 88 | self.button['image'] = self.activeIcon 89 | 90 | def leave(self): 91 | """Un-grid the screen. Perform exit clean-up tasks.""" 92 | self.grid_remove() 93 | self.infoFrame.grid_remove() 94 | self.helpFrame.grid_remove() 95 | if self.button: 96 | self.button['image'] = self.inactiveIcon 97 | -------------------------------------------------------------------------------- /frame_01_decisionMakers.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Frame for creating and editing decision makers and options. 4 | 5 | Loaded by the a_Main_Window module, and implements all of its required 6 | interfaces. 7 | """ 8 | 9 | from tkinter import Tk, StringVar, VERTICAL, N, S, E, W, END 10 | from tkinter import ttk 11 | from frame_00_frameTemplate import FrameTemplate 12 | from widgets_f01_01_dmOptElements import DMselector, DMeditor 13 | from data_01_conflictModel import ConflictModel 14 | 15 | NSEW = (N, S, E, W) 16 | 17 | 18 | class DMInpFrame(FrameTemplate): 19 | """Frame for input of DMs and options.""" 20 | 21 | # Label used for button to select frame in the main program. 22 | buttonLabel = 'DMs & Options' 23 | # Image used on button to select frame, when frame is active. 24 | activeIcon = 'icons/DMs_and_Options_ON.gif' 25 | # Image used on button to select frame, when frame is inactive. 26 | inactiveIcon = 'icons/DMs_and_Options_OFF.gif' 27 | # Help text to be displayed when screen is active. 28 | helpText = ("Click a decision maker in the left panel to view their " 29 | "associated options in the right panel. Double clicking an " 30 | "entry or hitting 'Enter' with it selected allows you to edit " 31 | "it. Pressing 'Delete' with an entry selected will remove it.") 32 | 33 | # ######################## INITIALIZATION ################################ 34 | def __init__(self, master, conflict): 35 | """Initialize DMinput Frame.""" 36 | FrameTemplate.__init__(self, master, conflict, self.buttonLabel, 37 | self.activeIcon, self.inactiveIcon, 38 | self.helpText) 39 | 40 | # ############################ METHODS ################################### 41 | 42 | def buildFrame(self): 43 | """Contruct frame widgets and initialize data.""" 44 | if self.built: 45 | return 46 | # Define variables that will display in the infoFrame 47 | self.dmCount = StringVar(value='Number of Decision Makers: ' + 'init') 48 | self.optCount = StringVar(value='Number of Options: ' + 'init') 49 | self.stateCount = StringVar(value='Total States: ' + 'init') 50 | 51 | # Define frame-specific variables 52 | 53 | # infoFrame: frame and label definitions (with master 'self.infoFrame') 54 | self.dmCountLabel = ttk.Label(self.infoFrame, 55 | textvariable=self.dmCount) 56 | self.optCountLabel = ttk.Label(self.infoFrame, 57 | textvariable=self.optCount) 58 | self.stateCountLabel = ttk.Label(self.infoFrame, 59 | textvariable=self.stateCount) 60 | 61 | # helpFrame: frame and label definitions (with master 'self.helpFrame') 62 | self.helpLabel = ttk.Label(self.helpFrame, textvariable=self.helpVar, 63 | wraplength=150) 64 | 65 | # Define frame-specific input widgets 66 | # (with master 'self' or a child thereof) 67 | self.dmSelector = DMselector(self, self.conflict) 68 | self.editor = DMeditor(self, self.conflict) 69 | 70 | # ######## preliminary griding and option configuration 71 | 72 | # configuring the input frame 73 | self.grid(column=0, row=0, rowspan=5, sticky=NSEW) 74 | self.grid_remove() 75 | self.columnconfigure(0, weight=1) 76 | self.columnconfigure(2, weight=1) 77 | self.rowconfigure(0, weight=1) 78 | 79 | # configuring infoFrame & infoFrame widgets 80 | self.infoFrame.grid(column=2, row=0, sticky=NSEW, padx=3, pady=3) 81 | self.infoFrame.grid_remove() 82 | self.dmCountLabel.grid(column=0, row=1, sticky=NSEW) 83 | self.optCountLabel.grid(column=0, row=2, sticky=NSEW) 84 | self.stateCountLabel.grid(column=0, row=3, sticky=NSEW) 85 | 86 | # configuring helpFrame & helpFrame widgets 87 | self.helpFrame.grid(column=2, row=1, sticky=NSEW, padx=3, pady=3) 88 | self.helpFrame.grid_remove() 89 | self.helpLabel.grid(column=0, row=0, sticky=NSEW) 90 | 91 | # configuring frame-specific options 92 | self.dmSelector.grid(row=0, column=0, sticky=NSEW) 93 | ttk.Separator(self, orient=VERTICAL).grid(row=0, column=1, 94 | sticky=NSEW, padx=3) 95 | self.editor.grid(row=0, column=2, sticky=NSEW) 96 | 97 | # bindings 98 | self.dmSelector.bind('<>', self.dmChange) 99 | self.dmSelector.bind('<>', self.dmEdit) 100 | self.dmSelector.bind('<>', self.handleDMOptChange) 101 | self.editor.bind('<>', self.handleDMOptChange) 102 | self.editor.bind('<>', self.updateDMnames) 103 | 104 | self.built = True 105 | 106 | def refresh(self, *args): 107 | """Refresh data in all active display widgets.""" 108 | self.dmSelector.refresh() 109 | self.dmSelector.reselect() 110 | self.updateTotals() 111 | 112 | def dmChange(self, event=None): 113 | """Change the selected decision maker.""" 114 | self.editor.loadDM(self.dmSelector.selectedDM) 115 | 116 | def dmEdit(self, event=None): 117 | """Push focus to the DM's name editing box. Create new DM if needed.""" 118 | if self.dmSelector.selectedDM is None: 119 | self.conflict.decisionMakers.append("New Decision Maker") 120 | self.dmSelector.refresh() 121 | self.dmSelector.reselect() 122 | self.dmSelector.dmListDisp.selection_set(self.dmSelector.selIdx) 123 | self.editor.dmNameEditor.focus() 124 | self.editor.dmNameEditor.select_range(0, END) 125 | 126 | def handleDMOptChange(self, event=None): 127 | """Update data when a DM or option is changed.""" 128 | self.conflict.useManualPreferenceRanking = False 129 | self.conflict.clearPreferences() 130 | self.event_generate('<>') 131 | 132 | self.updateTotals() 133 | 134 | def updateDMnames(self, event=None): 135 | """Update screen when a DM name is changed.""" 136 | self.dmSelector.updateList() 137 | 138 | def updateTotals(self, event=None): 139 | """Update data shown in the infobox.""" 140 | numD = len(self.conflict.decisionMakers) 141 | numO = sum([len(dm.options) for dm in self.conflict.decisionMakers]) 142 | self.dmCount.set('Number of Decision Makers: {}'.format(numD)) 143 | self.optCount.set('Number of Options: {}'.format(numO)) 144 | self.stateCount.set('Total States: {}'.format(2**numO)) 145 | 146 | # ############################################################################# 147 | # ############### TESTING ########### 148 | # ############################################################################# 149 | 150 | # Code in this section is only run when this module is run by itself. It serves 151 | # as a test of module functionality. 152 | 153 | 154 | def main(): 155 | """Run screen in test window.""" 156 | root = Tk() 157 | root.columnconfigure(0, weight=1) 158 | root.rowconfigure(0, weight=1) 159 | 160 | cFrame = ttk.Frame(root) 161 | cFrame.columnconfigure(0, weight=1) 162 | cFrame.rowconfigure(1, weight=1) 163 | cFrame.grid(column=0, row=0, sticky=NSEW) 164 | 165 | hSep = ttk.Separator(cFrame, orient=VERTICAL) 166 | hSep.grid(column=1, row=0, rowspan=10, sticky=NSEW) 167 | 168 | g1 = ConflictModel('AmRv2.gmcr') 169 | 170 | testFrame = DMInpFrame(cFrame, g1) 171 | testFrame.enter() 172 | 173 | root.mainloop() 174 | 175 | if __name__ == '__main__': 176 | main() 177 | -------------------------------------------------------------------------------- /frame_02_infeasibles.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Frame for editing infeasible states in the conflict. 4 | 5 | Loaded by the a_Main_Window module, and implements all of its required 6 | interfaces. 7 | """ 8 | 9 | from tkinter import Tk, StringVar, N, S, E, W, VERTICAL, HORIZONTAL 10 | from tkinter import ttk 11 | from frame_00_frameTemplate import FrameTemplate 12 | from widgets_f02_01_radioButtonEntry import RadiobuttonEntry 13 | from widgets_f02_02_infeasTreeview import TreeInfeas 14 | from widgets_f02_03_feasDisp import FeasDisp 15 | from data_01_conflictModel import ConflictModel 16 | import data_03_gmcrUtilities as gmcrUtil 17 | 18 | NSEW = (N, S, E, W) 19 | 20 | 21 | class InfeasInpFrame(FrameTemplate): 22 | """Frame for input of infeasible states.""" 23 | 24 | # Label used for button to select frame in the main program. 25 | buttonLabel = 'Infeasible States' 26 | # Image used on button to select frame, when frame is active. 27 | activeIcon = 'icons/Infeasible_States_ON.gif' 28 | # Image used on button to select frame, when frame is inactive. 29 | inactiveIcon = 'icons/Infeasible_States_OFF.gif' 30 | # Help text to be displayed when screen is active. 31 | helpText = ("Enter infeasible states using the box at left. Removing as " 32 | "infeasible state will remove all states that match the " 33 | "pattern from the conflict. Removing as mutually exclusive " 34 | "will remove all states where ANY TWO OR MORE of the specified" 35 | " options occur together.") 36 | 37 | # ######################## INITIALIZATION ################################ 38 | def __init__(self, master, conflict, *args): 39 | """Initialize the Frame. Does not build widgets.""" 40 | FrameTemplate.__init__(self, master, conflict, self.buttonLabel, 41 | self.activeIcon, self.inactiveIcon, 42 | self.helpText) 43 | 44 | self.lastBuildDMs = None 45 | self.lastBuildOptions = None 46 | self.lastBuildInfeasibles = None 47 | 48 | # ############################ METHODS ################################### 49 | 50 | def hasRequiredData(self): 51 | """Check that minimum data required to render the frame exists.""" 52 | if len(self.conflict.decisionMakers) < 1: 53 | return False 54 | if len(self.conflict.options) < 1: 55 | return False 56 | else: 57 | return True 58 | 59 | def dataChanged(self): 60 | """Check if data has changed since the last build of the Frame.""" 61 | if self.lastBuildDMs != self.conflict.decisionMakers.export_rep(): 62 | return True 63 | if self.lastBuildOptions != self.conflict.options.export_rep(): 64 | return True 65 | if self.lastBuildInfeasibles != self.conflict.infeasibles.export_rep(): 66 | return True 67 | else: 68 | return False 69 | 70 | def buildFrame(self): 71 | """Contruct frame widgets and initialize data.""" 72 | if self.built: 73 | return 74 | 75 | # Ensure all required parts of the conflict model are properly set-up. 76 | self.conflict.reorderOptionsByDM() 77 | self.conflict.options.set_indexes() 78 | self.conflict.infeasibles.validate() 79 | self.conflict.recalculateFeasibleStates() 80 | 81 | self.lastBuildDMs = self.conflict.decisionMakers.export_rep() 82 | self.lastBuildOptions = self.conflict.options.export_rep() 83 | self.lastBuildInfeasibles = self.conflict.infeasibles.export_rep() 84 | 85 | # Define variables that will display in the infoFrame 86 | self.originalStatesText = StringVar(value='Original States: init') 87 | self.removedStatesText = StringVar(value='States Removed: init') 88 | self.feasStatesText = StringVar(value='States Remaining: init') 89 | 90 | # Define frame-specific variables 91 | self.warnText = StringVar(value='') 92 | 93 | # infoFrame: frame and label definitions (with master 'self.infoFrame') 94 | self.originalStatesLabel = ttk.Label( 95 | self.infoFrame, textvariable=self.originalStatesText) 96 | self.removedStatesLabel = ttk.Label( 97 | self.infoFrame, textvariable=self.removedStatesText) 98 | self.feasStatesLabel = ttk.Label( 99 | self.infoFrame, textvariable=self.feasStatesText) 100 | 101 | # helpFrame: frame and label definitions (with master 'self.helpFrame') 102 | self.helpLabel = ttk.Label(self.helpFrame, textvariable=self.helpVar, 103 | wraplength=150) 104 | 105 | # Define frame-specific input widgets (with 'self' as master) 106 | self.optsFrame = ttk.Frame(self) 107 | self.hSep = ttk.Separator(self, orient=VERTICAL) 108 | self.infeasFrame = ttk.Panedwindow(self, orient=HORIZONTAL) 109 | 110 | self.optsInp = RadiobuttonEntry(self.optsFrame, self.conflict) 111 | self.infeasDisp = TreeInfeas(self.infeasFrame, self.conflict) 112 | self.feasList = FeasDisp(self.infeasFrame, self.conflict) 113 | self.infeasFrame.add(self.infeasDisp) 114 | self.infeasFrame.add(self.feasList) 115 | 116 | # ######## preliminary gridding and option configuration 117 | 118 | # configuring the input frame 119 | self.grid(column=0, row=0, rowspan=5, sticky=NSEW) 120 | self.grid_remove() 121 | 122 | self.columnconfigure(2, weight=3) 123 | self.rowconfigure(0, weight=1) 124 | 125 | # configuring infoFrame & infoFrame widgets 126 | self.infoFrame.grid(column=2, row=0, sticky=NSEW, padx=3, pady=3) 127 | self.infoFrame.grid_remove() 128 | self.originalStatesLabel.grid(column=0, row=1, sticky=NSEW) 129 | self.removedStatesLabel.grid(column=0, row=2, sticky=NSEW) 130 | self.feasStatesLabel.grid(column=0, row=3, sticky=NSEW) 131 | 132 | # configuring helpFrame & helpFrame widgets 133 | self.helpFrame.grid(column=2, row=1, sticky=NSEW, padx=3, pady=3) 134 | self.helpFrame.grid_remove() 135 | self.helpLabel.grid(column=0, row=0, sticky=NSEW) 136 | 137 | # configuring frame-specific options 138 | self.optsFrame.columnconfigure(0, weight=1) 139 | self.optsFrame.rowconfigure(0, weight=1) 140 | self.optsFrame.grid(column=0, row=0, sticky=NSEW) 141 | 142 | self.infeasFrame.grid(column=2, row=0, sticky=NSEW) 143 | 144 | self.optsInp.grid(column=0, row=0, columnspan=2, sticky=NSEW) 145 | self.optsInp.bind('<>', self.addInfeas) 146 | self.optsInp.bind('<>', self.addMutEx) 147 | 148 | self.infeasDisp.bind('<>', self.selChg) 149 | self.infeasDisp.bind('<>', self.refresh) 150 | 151 | self.hSep.grid(column=1, row=0, rowspan=10, sticky=NSEW) 152 | 153 | self.refresh() 154 | 155 | self.built = True 156 | 157 | def refresh(self, *args): 158 | """Refresh data in all active display widgets.""" 159 | self.infeasDisp.refreshView() 160 | self.feasList.refreshList() 161 | self.updateTotals() 162 | 163 | def updateTotals(self, event=None): 164 | """Update data shown in the infobox.""" 165 | numO = len(self.conflict.options) 166 | numF = len(self.conflict.feasibles) 167 | self.originalStatesText.set('Original States: {}'.format(2**numO)) 168 | self.feasStatesText.set('Feasible States: {}'.format(numF)) 169 | self.removedStatesText.set('States Removed: {}'.format(2**numO - numF)) 170 | 171 | def addInfeas(self, *args): 172 | """Remove an infeasible state from the conflict.""" 173 | infeas = self.optsInp.getStates() 174 | self.conflict.infeasibles.append(infeas) 175 | self.conflict.recalculateFeasibleStates() 176 | self.refresh() 177 | 178 | def addMutEx(self, *args): 179 | """Remove a set of Mutually Exclusive States from the conflict.""" 180 | mutEx = self.optsInp.getStates() 181 | mutEx = gmcrUtil.mutuallyExclusive(mutEx) 182 | for infeas in mutEx: 183 | self.conflict.infeasibles.append(list(infeas)) 184 | self.refresh() 185 | 186 | def selChg(self, event): 187 | """Triggered when the selection changes in the treeview.""" 188 | state = self.conflict.infeasibles[event.x].name 189 | self.optsInp.setStates(state) 190 | 191 | def enter(self): 192 | """Run when entering the Infeasible States screen.""" 193 | if self.dataChanged(): 194 | self.clearFrame() 195 | 196 | FrameTemplate.enter(self) 197 | 198 | self.optsInp.reloadOpts() 199 | 200 | # ############################################################################# 201 | # ############### TESTING ########### 202 | # ############################################################################# 203 | 204 | # Code in this section is only run when this module is run by itself. It serves 205 | # as a test of module functionality. 206 | 207 | 208 | def main(): 209 | """Run screen in test window.""" 210 | root = Tk() 211 | root.columnconfigure(0, weight=1) 212 | root.rowconfigure(0, weight=1) 213 | 214 | cFrame = ttk.Frame(root) 215 | cFrame.columnconfigure(0, weight=1) 216 | cFrame.rowconfigure(1, weight=1) 217 | cFrame.grid(column=0, row=0, sticky=NSEW) 218 | 219 | hSep = ttk.Separator(cFrame, orient=VERTICAL) 220 | hSep.grid(column=1, row=0, rowspan=10, sticky=NSEW) 221 | 222 | g1 = ConflictModel('Prisoners.gmcr') 223 | 224 | testFrame = InfeasInpFrame(cFrame, g1) 225 | testFrame.enter() 226 | 227 | root.mainloop() 228 | 229 | 230 | if __name__ == '__main__': 231 | main() 232 | -------------------------------------------------------------------------------- /frame_02a_misperceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Frame for editing misperceived states in the conflict. 4 | 5 | Loaded by the a_Main_Window module, and implements all of its required 6 | interfaces. 7 | 8 | This frame is a repurposed "Infeasibles" frame and reuses most of the same 9 | elements. 10 | """ 11 | 12 | from tkinter import Tk, StringVar, N, S, E, W, VERTICAL, HORIZONTAL 13 | from tkinter import ttk 14 | from frame_00_frameTemplate import FrameTemplate 15 | from widgets_f02a_01_radioButtonEntry import RadiobuttonEntry 16 | from widgets_f02a_02_infeasTreeview import TreeInfeas 17 | from widgets_f02a_03_feasDisp import FeasDisp 18 | from data_01_conflictModel import ConflictModel 19 | import data_03_gmcrUtilities as gmcrUtil 20 | 21 | NSEW = (N, S, E, W) 22 | 23 | 24 | class MisperceptionFrame(FrameTemplate): 25 | """Frame for editing misperceived states in the conflict.""" 26 | 27 | # Label used for button to select frame in the main program. 28 | buttonLabel = 'Misperceptions' 29 | # Image used on button to select frame, when frame is active. 30 | activeIcon = 'icons/Infeasible_States_ON.gif' 31 | # Image used on button to select frame, when frame is inactive. 32 | inactiveIcon = 'icons/Infeasible_States_OFF.gif' 33 | # Help text to be displayed when screen is active. 34 | helpText = ("Enter misperceived states using the box at left. Removing as " 35 | "misperceived state will remove all states that match the " 36 | "pattern from the conflict. Removing as mutually exclusive " 37 | "will remove all states where ANY TWO OR MORE of the specified" 38 | " options occur together.") 39 | 40 | # ######################## INITIALIZATION ################################ 41 | def __init__(self, master, conflict, *args): 42 | """Initialize the Frame. Does not build widgets.""" 43 | FrameTemplate.__init__(self, master, conflict, self.buttonLabel, 44 | self.activeIcon, self.inactiveIcon, 45 | self.helpText) 46 | 47 | self.lastBuildDMs = None 48 | self.lastBuildOptions = None 49 | self.lastBuildInfeasibles = None 50 | 51 | # ############################ METHODS ################################### 52 | 53 | def hasRequiredData(self): 54 | """Check that minimum data required to render the frame exists.""" 55 | if len(self.conflict.decisionMakers) < 1: 56 | return False 57 | if len(self.conflict.options) < 1: 58 | return False 59 | else: 60 | return True 61 | 62 | def dataChanged(self): 63 | """Check if data has changed since the last build of the Frame.""" 64 | if self.lastBuildDMs != self.conflict.decisionMakers.export_rep(): 65 | return True 66 | if self.lastBuildOptions != self.conflict.options.export_rep(): 67 | return True 68 | if self.lastBuildInfeasibles != self.conflict.infeasibles.export_rep(): 69 | return True 70 | else: 71 | return False 72 | 73 | def buildFrame(self): 74 | """Contruct frame widgets and initialize data.""" 75 | if self.built: 76 | return 77 | 78 | # Ensure all required parts of the conflict model are properly set-up. 79 | self.conflict.reorderOptionsByDM() 80 | self.conflict.options.set_indexes() 81 | self.conflict.infeasibles.validate() 82 | self.conflict.recalculateFeasibleStates() 83 | 84 | self.lastBuildDMs = self.conflict.decisionMakers.export_rep() 85 | self.lastBuildOptions = self.conflict.options.export_rep() 86 | self.lastBuildInfeasibles = self.conflict.infeasibles.export_rep() 87 | 88 | # Define variables that will display in the infoFrame 89 | self.originalStatesText = StringVar(value='Original States: init') 90 | self.removedStatesText = StringVar(value='States Removed: init') 91 | self.feasStatesText = StringVar(value='States Remaining: init') 92 | 93 | # Define frame-specific variables 94 | self.warnText = StringVar(value='') 95 | self.activeDM = None 96 | 97 | # infoFrame: frame and label definitions (with master 'self.infoFrame') 98 | self.originalStatesLabel = ttk.Label( 99 | self.infoFrame, textvariable=self.originalStatesText) 100 | self.removedStatesLabel = ttk.Label( 101 | self.infoFrame, textvariable=self.removedStatesText) 102 | self.feasStatesLabel = ttk.Label( 103 | self.infoFrame, textvariable=self.feasStatesText) 104 | 105 | # helpFrame: frame and label definitions (with master 'self.helpFrame') 106 | self.helpLabel = ttk.Label(self.helpFrame, textvariable=self.helpVar, 107 | wraplength=150) 108 | 109 | # Define frame-specific input widgets (with 'self' as master) 110 | self.optsFrame = ttk.Frame(self) 111 | self.hSep = ttk.Separator(self, orient=VERTICAL) 112 | self.infeasFrame = ttk.Panedwindow(self, orient=HORIZONTAL) 113 | 114 | self.optsInp = RadiobuttonEntry(self.optsFrame, self.conflict) 115 | self.infeasDisp = TreeInfeas(self.infeasFrame, self.conflict) 116 | self.feasList = FeasDisp(self.infeasFrame, self.conflict) 117 | self.infeasFrame.add(self.infeasDisp) 118 | self.infeasFrame.add(self.feasList) 119 | 120 | # ######## preliminary gridding and option configuration 121 | 122 | # configuring the input frame 123 | self.grid(column=0, row=0, rowspan=5, sticky=NSEW) 124 | self.grid_remove() 125 | 126 | self.columnconfigure(2, weight=3) 127 | self.rowconfigure(0, weight=1) 128 | 129 | # configuring infoFrame & infoFrame widgets 130 | self.infoFrame.grid(column=2, row=0, sticky=NSEW, padx=3, pady=3) 131 | self.infoFrame.grid_remove() 132 | self.originalStatesLabel.grid(column=0, row=1, sticky=NSEW) 133 | self.removedStatesLabel.grid(column=0, row=2, sticky=NSEW) 134 | self.feasStatesLabel.grid(column=0, row=3, sticky=NSEW) 135 | 136 | # configuring helpFrame & helpFrame widgets 137 | self.helpFrame.grid(column=2, row=1, sticky=NSEW, padx=3, pady=3) 138 | self.helpFrame.grid_remove() 139 | self.helpLabel.grid(column=0, row=0, sticky=NSEW) 140 | 141 | # configuring frame-specific options 142 | self.optsFrame.columnconfigure(0, weight=1) 143 | self.optsFrame.rowconfigure(0, weight=1) 144 | self.optsFrame.grid(column=0, row=0, sticky=NSEW) 145 | 146 | self.infeasFrame.grid(column=2, row=0, sticky=NSEW) 147 | 148 | self.optsInp.grid(column=0, row=0, columnspan=2, sticky=NSEW) 149 | self.optsInp.bind('<>', self.addMisperceived) 150 | self.optsInp.bind('<>', self.addMutEx) 151 | self.optsInp.bind('<>', self.refresh) 152 | 153 | self.infeasDisp.bind('<>', self.selChg) 154 | self.infeasDisp.bind('<>', self.refresh) 155 | 156 | self.hSep.grid(column=1, row=0, rowspan=10, sticky=NSEW) 157 | 158 | self.refresh() 159 | 160 | self.built = True 161 | 162 | def refresh(self, *args): 163 | """Refresh data in all active display widgets.""" 164 | self.activeDM = self.optsInp.activeDM 165 | self.infeasDisp.activeDM = self.activeDM 166 | self.feasList.activeDM = self.activeDM 167 | self.activeDM.calculatePerceived() 168 | self.infeasDisp.refresh() 169 | self.feasList.refresh() 170 | self.updateTotals() 171 | 172 | def updateTotals(self, event=None): 173 | """Update data shown in the infobox.""" 174 | numO = len(self.conflict.options) 175 | numF = len(self.conflict.feasibles) 176 | self.originalStatesText.set('Original States: {}'.format(2**numO)) 177 | self.feasStatesText.set('Feasible States: {}'.format(numF)) 178 | self.removedStatesText.set('States Removed: {}'.format(2**numO - numF)) 179 | 180 | def addMisperceived(self, *args): 181 | """Remove an infeasible state from the conflict.""" 182 | misperceived = self.optsInp.getStates() 183 | self.activeDM.misperceptions.append(misperceived) 184 | self.refresh() 185 | 186 | def addMutEx(self, *args): 187 | """Remove a set of Mutually Exclusive States from the conflict.""" 188 | mutEx = self.optsInp.getStates() 189 | mutEx = gmcrUtil.mutuallyExclusive(mutEx) 190 | for misperceived in mutEx: 191 | self.activeDM.misperceptions.append(list(misperceived)) 192 | self.refresh() 193 | 194 | def selChg(self, event): 195 | """Triggered when the selection changes in the treeview.""" 196 | state = self.activeDM.misperceptions[event.x].name 197 | self.optsInp.setStates(state) 198 | 199 | def enter(self): 200 | """Run when entering the Misperceived States screen.""" 201 | if self.dataChanged(): 202 | self.clearFrame() 203 | FrameTemplate.enter(self) 204 | self.optsInp.reloadOpts() 205 | 206 | # ############################################################################# 207 | # ############### TESTING ########### 208 | # ############################################################################# 209 | 210 | # Code in this section is only run when this module is run by itself. It serves 211 | # as a test of module functionality. 212 | 213 | 214 | def main(): 215 | """Run screen in test window.""" 216 | root = Tk() 217 | root.columnconfigure(0, weight=1) 218 | root.rowconfigure(0, weight=1) 219 | 220 | cFrame = ttk.Frame(root) 221 | cFrame.columnconfigure(0, weight=1) 222 | cFrame.rowconfigure(1, weight=1) 223 | cFrame.grid(column=0, row=0, sticky=NSEW) 224 | 225 | hSep = ttk.Separator(cFrame, orient=VERTICAL) 226 | hSep.grid(column=1, row=0, rowspan=10, sticky=NSEW) 227 | 228 | g1 = ConflictModel() 229 | g1.load_from_file('Examples/SyriaIraq.gmcr') 230 | 231 | testFrame = MisperceptionFrame(cFrame, g1) 232 | testFrame.enter() 233 | 234 | root.mainloop() 235 | 236 | 237 | if __name__ == '__main__': 238 | main() 239 | -------------------------------------------------------------------------------- /frame_03_irreversibles.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Frame allowing control over option reversibility. 4 | 5 | Loaded by the a_Main_Window module, and implements all of its required 6 | interfaces. 7 | """ 8 | 9 | from tkinter import Tk, N, S, E, W, VERTICAL 10 | from tkinter import ttk 11 | from frame_00_frameTemplate import FrameTemplate 12 | from data_01_conflictModel import ConflictModel 13 | from widgets_f03_01_irreversibleToggles import IrreversibleSetter 14 | 15 | NSEW = (N, S, E, W) 16 | 17 | 18 | class IrrevInpFrame(FrameTemplate): 19 | """Frame allowing control over option reversibility.""" 20 | 21 | # Label used for button to select frame in the main program. 22 | buttonLabel = 'Irreversible Moves' 23 | # Image used on button to select frame, when frame is active. 24 | activeIcon = 'icons/Irreversible_Moves_ON.gif' 25 | # Image used on button to select frame, when frame is inactive. 26 | inactiveIcon = 'icons/Irreversible_Moves_OFF.gif' 27 | # Help text to be displayed when screen is active. 28 | helpText = ("Specify the reversibility of moves by clicking the arrow " 29 | "until it correctly shows the direction(s) in which a move " 30 | "may be made.") 31 | 32 | # ######################## INITIALIZATION ################################ 33 | def __init__(self, master, conflict, *args): 34 | """Initialize the Frame. Does not build widgets.""" 35 | FrameTemplate.__init__(self, master, conflict, self.buttonLabel, 36 | self.activeIcon, self.inactiveIcon, 37 | self.helpText) 38 | 39 | self.lastBuildDMs = None 40 | 41 | # ############################ METHODS ################################### 42 | 43 | def hasRequiredData(self): 44 | """Check that minimum data required to render the frame exists.""" 45 | if len(self.conflict.decisionMakers) < 1: 46 | return False 47 | if len(self.conflict.options) < 1: 48 | return False 49 | else: 50 | return True 51 | 52 | def dataChanged(self): 53 | """Check if data has changed since the last build of the Frame.""" 54 | if self.lastBuildDMs != self.conflict.decisionMakers.export_rep(): 55 | return True 56 | else: 57 | return False 58 | 59 | def buildFrame(self): 60 | """Contruct frame widgets and initialize data.""" 61 | if self.built: 62 | return 63 | 64 | # Ensure all required parts of the conflict model are properly set-up. 65 | self.lastBuildDMs = self.conflict.decisionMakers.export_rep() 66 | 67 | # infoFrame: frame and label definitions (with master 'self.infoFrame') 68 | self.infoLabel = ttk.Label(self.infoFrame, text='') 69 | 70 | # helpFrame: frame and label definitions (with master 'self.helpFrame') 71 | self.helpLabel = ttk.Label(self.helpFrame, textvariable=self.helpVar, 72 | wraplength=150) 73 | 74 | # Define frame-specific input widgets (with 'self' as master) 75 | self.irrevEntry = IrreversibleSetter(self, self.conflict) 76 | 77 | # ######## preliminary gridding and option configuration 78 | # configuring the input frame 79 | self.grid(column=0, row=0, rowspan=5, sticky=NSEW) 80 | self.grid_remove() 81 | self.columnconfigure(0, weight=1) 82 | 83 | # configuring infoFrame & infoFrame widgets 84 | self.infoFrame.grid(column=2, row=0, sticky=NSEW, padx=3, pady=3) 85 | self.infoFrame.grid_remove() 86 | self.infoLabel.grid(column=0, row=1, sticky=NSEW) 87 | 88 | # configuring helpFrame & helpFrame widgets 89 | self.helpFrame.grid(column=2, row=1, sticky=NSEW, padx=3, pady=3) 90 | self.helpFrame.grid_remove() 91 | self.helpLabel.grid(column=0, row=0, sticky=NSEW) 92 | 93 | # configuring frame-specific options 94 | self.irrevEntry.grid(column=0, row=0, sticky=NSEW) 95 | 96 | self.rowconfigure(0, weight=1) 97 | 98 | # bindings 99 | # None 100 | 101 | self.built = True 102 | 103 | def refresh(self, *args): 104 | """Refresh data in all active display widgets.""" 105 | self.irrevEntry.refreshDisplay() 106 | 107 | def enter(self, *args): 108 | """Re-grid the screen into the master. Perform required updates.""" 109 | if self.dataChanged(): 110 | self.clearFrame() 111 | 112 | FrameTemplate.enter(self) 113 | 114 | 115 | # ############################################################################# 116 | # ############### TESTING ########### 117 | # ############################################################################# 118 | 119 | # Code in this section is only run when this module is run by itself. It serves 120 | # as a test of module funcitonality. 121 | 122 | 123 | def main(): 124 | """Run screen in test window.""" 125 | root = Tk() 126 | root.columnconfigure(0, weight=1) 127 | root.rowconfigure(0, weight=1) 128 | 129 | cFrame = ttk.Frame(root) 130 | cFrame.columnconfigure(0, weight=1) 131 | cFrame.rowconfigure(1, weight=1) 132 | cFrame.grid(column=0, row=0, sticky=NSEW) 133 | 134 | hSep = ttk.Separator(cFrame, orient=VERTICAL) 135 | hSep.grid(column=1, row=0, rowspan=10, sticky=NSEW) 136 | 137 | testConflict = ConflictModel('Prisoners.gmcr') 138 | 139 | testFrame = IrrevInpFrame(cFrame, testConflict) 140 | testFrame.enter() 141 | 142 | root.mainloop() 143 | 144 | if __name__ == '__main__': 145 | main() 146 | -------------------------------------------------------------------------------- /frame_04_preferencePrioritization.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Frame used to set decision maker's preferences for use in prioritization. 4 | 5 | Loaded by the a_Main_Window module, and implements all of its required 6 | interfaces. 7 | """ 8 | 9 | from tkinter import (Tk, StringVar, N, S, E, W, VERTICAL, HORIZONTAL, 10 | PanedWindow) 11 | from tkinter import ttk 12 | from frame_00_frameTemplate import FrameTemplate 13 | from widgets_f04_01_prefRadioButton import RadiobuttonEntry 14 | from widgets_f04_02_prefElements import (PreferenceRankingMaster, 15 | PreferenceStaging, 16 | PreferenceListDisplay) 17 | from widgets_f04_03_optionForm import OptionFormTable 18 | 19 | NSEW = (N, S, E, W) 20 | 21 | 22 | class PreferencesFrame(FrameTemplate): 23 | """Used to set decision maker's preferences for use in prioritization.""" 24 | 25 | # Label used for button to select frame in the main program. 26 | buttonLabel = 'Prioritization' 27 | # Image used on button to select frame, when frame is active. 28 | activeIcon = 'icons/Prioritization_ON.gif' 29 | # Image used on button to select frame, when frame is inactive. 30 | inactiveIcon = 'icons/Prioritization_OFF.gif' 31 | # Help text to be displayed when screen is active. 32 | helpText = ("Select a decision maker from the column at left by clicking " 33 | "its 'Edit' button. Enter preferred conditions using the " 34 | "inputs to the right. Preferred conditions for the selected " 35 | "decision maker are shown at the far right, from most " 36 | "important at the top, to least important at the bottom.") 37 | 38 | # ######################## INITIALIZATION ################################ 39 | def __init__(self, master, conflict, *args): 40 | """Initialize the Frame. Does not build widgets.""" 41 | FrameTemplate.__init__(self, master, conflict, self.buttonLabel, 42 | self.activeIcon, self.inactiveIcon, 43 | self.helpText) 44 | 45 | self.lastBuildDMs = None 46 | self.lastBuildOptions = None 47 | self.lastBuildInfeasibles = None 48 | self.lastBuildRanking = None 49 | 50 | 51 | # ############################ METHODS ################################### 52 | 53 | def hasRequiredData(self): 54 | """Check that minimum data required to render the frame exists.""" 55 | if len(self.conflict.decisionMakers) < 1: 56 | return False 57 | if len(self.conflict.options) < 1: 58 | return False 59 | if len(self.conflict.feasibles) < 1: 60 | self.conflict.recalculateFeasibleStates() 61 | if len(self.conflict.feasibles) < 1: 62 | return False 63 | else: 64 | return True 65 | 66 | def dataChanged(self): 67 | """Check if data has changed since the last build of the Frame.""" 68 | if self.lastBuildDMs != self.conflict.decisionMakers.export_rep(): 69 | return True 70 | if self.lastBuildOptions != self.conflict.options.export_rep(): 71 | return True 72 | if self.lastBuildInfeasibles != self.conflict.infeasibles.export_rep(): 73 | return True 74 | if self.lastBuildRanking != self.conflict.useManualPreferenceRanking: 75 | return True 76 | else: 77 | return False 78 | 79 | def buildFrame(self): 80 | """Contruct frame widgets and initialize data.""" 81 | if self.built: 82 | return 83 | 84 | # Ensure all required parts of the conflict model are properly set-up. 85 | self.conflict.reorderOptionsByDM() 86 | self.conflict.options.set_indexes() 87 | self.conflict.infeasibles.validate() 88 | self.conflict.recalculateFeasibleStates() 89 | 90 | for dm in self.conflict.decisionMakers: 91 | dm.calculatePerceived() 92 | dm.calculatePreferences() 93 | 94 | self.lastBuildDMs = self.conflict.decisionMakers.export_rep() 95 | self.lastBuildOptions = self.conflict.options.export_rep() 96 | self.lastBuildInfeasibles = self.conflict.infeasibles.export_rep() 97 | self.lastBuildRanking = self.conflict.useManualPreferenceRanking 98 | 99 | # Define variables that will display in the infoFrame 100 | numD = len(self.conflict.decisionMakers) 101 | self.infoText = StringVar( 102 | value='Valid Preferences set for {0}/{0} DMs.'.format(numD)) 103 | 104 | # Define frame-specific variables 105 | self.dm = None 106 | 107 | # infoFrame: frame and label definitions (with master 'self.infoFrame') 108 | self.infoLabel = ttk.Label(self.infoFrame, textvariable=self.infoText) 109 | 110 | # helpFrame: frame and label definitions (with master 'self.helpFrame') 111 | self.helpLabel = ttk.Label(self.helpFrame, textvariable=self.helpVar, 112 | wraplength=150) 113 | 114 | # Define frame-specific input widgets (with 'self' as master) 115 | self.paneMaster = PanedWindow(self, orient=VERTICAL, sashwidth=5, 116 | sashrelief="raised", sashpad=2, 117 | relief="sunken") 118 | 119 | self.paneTop = ttk.Frame(self.paneMaster) 120 | self.rankings = PreferenceRankingMaster(self.paneTop, self.conflict) 121 | self.editor = RadiobuttonEntry(self.paneTop, self.conflict) 122 | self.paneTopRightMaster = PanedWindow(self.paneTop, orient=HORIZONTAL, 123 | sashwidth=5, sashrelief="raised", 124 | sashpad=2, relief="sunken") 125 | self.staging = PreferenceStaging(self.paneTopRightMaster, 126 | self.conflict) 127 | self.preferenceDisp = PreferenceListDisplay(self.paneTopRightMaster, 128 | self.conflict) 129 | 130 | self.paneBottom = ttk.Frame(self.paneMaster) 131 | self.optionTable = OptionFormTable(self.paneBottom, self.conflict) 132 | 133 | self.usePrioritizationButton = ttk.Button( 134 | self, 135 | text=("Use preference prioritization. Any manually set " 136 | "preference rankings will be lost."), 137 | command=self.usePrioritization) 138 | 139 | # ######## preliminary gridding and option configuration 140 | 141 | # configuring the input frame 142 | self.grid(column=0, row=0, rowspan=5, sticky=NSEW) 143 | self.columnconfigure(0, weight=1) 144 | self.rowconfigure(1, weight=1) 145 | self.grid_remove() 146 | 147 | # configuring infoFrame & infoFrame widgets 148 | self.infoFrame.grid(column=2, row=0, sticky=NSEW, padx=3, pady=3) 149 | self.infoFrame.grid_remove() 150 | self.infoLabel.grid(column=0, row=1, sticky=NSEW) 151 | 152 | # configuring helpFrame & helpFrame widgets 153 | self.helpFrame.grid(column=2, row=1, sticky=NSEW, padx=3, pady=3) 154 | self.helpFrame.grid_remove() 155 | self.helpLabel.grid(column=0, row=0, sticky=NSEW) 156 | 157 | # configuring frame-specific options 158 | self.paneMaster.grid(column=0, row=1, sticky=NSEW) 159 | self.paneMaster.add(self.paneTop, minsize=200) 160 | self.rankings.grid(column=0, row=1, sticky=NSEW) 161 | ttk.Separator(self.paneTop, orient=VERTICAL).grid( 162 | column=1, row=1, sticky=NSEW, padx=3) 163 | self.editor.grid(column=2, row=1, sticky=NSEW) 164 | ttk.Separator(self.paneTop, orient=VERTICAL).grid( 165 | column=3, row=1, sticky=NSEW, padx=3) 166 | self.paneTopRightMaster.grid(column=4, row=1, sticky=NSEW) 167 | self.paneTop.columnconfigure(0, weight=0) 168 | self.paneTop.columnconfigure(2, weight=0) 169 | self.paneTop.columnconfigure(4, weight=1) 170 | self.paneTop.rowconfigure(1, weight=1) 171 | 172 | self.usePrioritizationButton.grid(column=0, row=0, columnspan=5, 173 | sticky=NSEW) 174 | 175 | self.paneTopRightMaster.add(self.staging) 176 | self.paneTopRightMaster.add(self.preferenceDisp) 177 | 178 | self.paneMaster.add(self.paneBottom) 179 | self.optionTable.grid(column=0, row=0, sticky=NSEW) 180 | self.paneBottom.columnconfigure(0, weight=1) 181 | self.paneBottom.rowconfigure(0, weight=1) 182 | 183 | # bindings 184 | self.rankings.bind('<>', self.dmChgHandler) 185 | self.editor.bind('<>', self.addPref) 186 | self.editor.bind('<>', self.stagePref) 187 | self.staging.bind('<>', self.selCondChg) 188 | self.staging.bind('<>', self.pullFromStage) 189 | self.preferenceDisp.bind('<>', self.selPrefChg) 190 | self.preferenceDisp.bind('<>', self.refresh) 191 | 192 | self.dmChgHandler() 193 | 194 | self.built = True 195 | 196 | def enter(self, *args): 197 | """Re-grid the screen into the master. Perform required updates.""" 198 | if self.dataChanged(): 199 | self.clearFrame() 200 | 201 | FrameTemplate.enter(self) 202 | 203 | def refresh(self, *args): 204 | """Refresh data in all active display widgets.""" 205 | for dm in self.conflict.decisionMakers: 206 | dm.calculatePreferences() 207 | self.editor.reloadOpts() 208 | self.rankings.refresh() 209 | self.preferenceDisp.refresh() 210 | self.optionTable.buildTable(self.dm) 211 | 212 | self.checkIfUsingRankings() 213 | 214 | def checkIfUsingRankings(self, event=None): 215 | """Disable screen if Manual Ranking has been assigned.""" 216 | if self.conflict.useManualPreferenceRanking: 217 | self.usePrioritizationButton.grid() 218 | self.rankings.disable() 219 | self.editor.disable() 220 | self.staging.disable() 221 | self.preferenceDisp.disable() 222 | else: 223 | self.usePrioritizationButton.grid_remove() 224 | 225 | def usePrioritization(self): 226 | """Reactivate the screen if user decides to use prioritization.""" 227 | self.conflict.useManualPreferenceRanking = False 228 | self.conflict.preferenceErrors = None 229 | self.refresh() 230 | 231 | def dmChgHandler(self, event=None): 232 | """Bound to <>.""" 233 | self.dm = self.rankings.dm 234 | self.preferenceDisp.changeDM(self.dm) 235 | self.optionTable.buildTable(self.dm) 236 | self.staging.clear() 237 | self.editor.setStates('clear') 238 | if self.dm is None: 239 | self.preferenceDisp.disable() 240 | self.editor.disable() 241 | self.staging.disable() 242 | else: 243 | self.preferenceDisp.enable() 244 | self.editor.enable() 245 | 246 | def addPref(self, event=None): 247 | """Add a preference for the active decision maker.""" 248 | pref = self.editor.getStates() 249 | self.dm.preferences.append(pref) 250 | self.refresh() 251 | 252 | def stagePref(self, event=None): 253 | """Send a condition to the staging area.""" 254 | if self.editor.hasValidIf: 255 | for cond in self.editor.ifCond: 256 | self.staging.addCondition(cond) 257 | else: 258 | condData = self.editor.getStates() 259 | newCond = self.conflict.newCondition(condData) 260 | self.staging.addCondition(newCond) 261 | 262 | self.editor.setStates('clear') 263 | 264 | def pullFromStage(self, event=None): 265 | """Move a compound condition from Staging to Preferences.""" 266 | newPref = self.staging.conditionList 267 | self.staging.clear() 268 | self.dm.preferences.append(newPref) 269 | self.refresh() 270 | 271 | def selCondChg(self, event=None): 272 | """Triggered when a condition is selected in staging.""" 273 | condition = self.staging.conditionList[event.x] 274 | self.editor.setStates(condition.ynd()) 275 | 276 | def selPrefChg(self, event=None): 277 | """Triggered when a preference is select from preferences.""" 278 | condition = self.dm.preferences[event.x] 279 | self.staging.setList(condition) 280 | 281 | 282 | # ############################################################################# 283 | # ############### TESTING ########### 284 | # ############################################################################# 285 | 286 | # Code in this section is only run when this module is run by itself. It serves 287 | # as a test of module functionality. 288 | 289 | 290 | def main(): 291 | """Run screen in test window.""" 292 | from data_01_conflictModel import ConflictModel 293 | 294 | root = Tk() 295 | root.columnconfigure(0, weight=1) 296 | root.rowconfigure(0, weight=1) 297 | 298 | cFrame = ttk.Frame(root) 299 | cFrame.columnconfigure(0, weight=1) 300 | cFrame.rowconfigure(1, weight=1) 301 | cFrame.grid(column=0, row=0, sticky=NSEW) 302 | 303 | hSep = ttk.Separator(cFrame, orient=VERTICAL) 304 | hSep.grid(column=1, row=0, rowspan=10, sticky=NSEW) 305 | 306 | conf = ConflictModel() 307 | conf.load_from_file("save_files/Garrison.gmcr") 308 | 309 | testFrame = PreferencesFrame(cFrame, conf) 310 | if testFrame.hasRequiredData(): 311 | testFrame.buildFrame() 312 | else: 313 | print("data missing") 314 | return 315 | testFrame.enter() 316 | 317 | root.mainloop() 318 | 319 | if __name__ == '__main__': 320 | main() 321 | -------------------------------------------------------------------------------- /frame_05_preferenceRanking.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Frame used to manually set decision maker's preferences. 4 | 5 | Loaded by the a_Main_Window module, and implements all of its required 6 | interfaces. 7 | """ 8 | 9 | from tkinter import Tk, StringVar, N, S, E, W, VERTICAL 10 | from tkinter import ttk 11 | from frame_00_frameTemplate import FrameTemplate 12 | from data_01_conflictModel import ConflictModel 13 | from widgets_f05_01_PrefRank import PRankEditMaster 14 | from widgets_f04_03_optionForm import OptionFormTable 15 | 16 | NSEW = (N, S, E, W) 17 | 18 | 19 | class PreferenceRankingFrame(FrameTemplate): 20 | """Frame used to manually set decision maker's preferences.""" 21 | 22 | # Label used for button to select frame in the main program. 23 | buttonLabel = 'Preference Ranking' 24 | # Image used on button to select frame, when frame is active. 25 | activeIcon = 'icons/Preference_Ranking_ON.gif' 26 | # Image used on button to select frame, when frame is inactive. 27 | inactiveIcon = 'icons/Preference_Ranking_OFF.gif' 28 | # Help text to be displayed when screen is active. 29 | helpText = ("Use this screen to manually make small adjustments to " 30 | "preference rankings. Any Changes made on this screen " 31 | "override preference prioritization inputs. Preference " 32 | "priorities will not be lost, in case you wish to revert " 33 | "to them later.") 34 | 35 | # ######################## INITIALIZATION ################################ 36 | def __init__(self, master, conflict, *args): 37 | """Initialize the Frame. Does not build widgets.""" 38 | FrameTemplate.__init__(self, master, conflict, self.buttonLabel, 39 | self.activeIcon, self.inactiveIcon, 40 | self.helpText) 41 | 42 | self.lastBuildDMs = None 43 | self.lastBuildOptions = None 44 | self.lastBuildInfeasibles = None 45 | self.lastBuildRanking = None 46 | 47 | # ############################ METHODS ################################### 48 | 49 | def hasRequiredData(self): 50 | """Check that minimum data required to render the frame exists.""" 51 | if len(self.conflict.decisionMakers) < 1: 52 | return False 53 | if len(self.conflict.options) < 1: 54 | return False 55 | if self.lastBuildRanking != self.conflict.useManualPreferenceRanking: 56 | return True 57 | if len(self.conflict.feasibles) < 1: 58 | self.conflict.recalculateFeasibleStates() 59 | if len(self.conflict.feasibles) < 1: 60 | return False 61 | else: 62 | return True 63 | 64 | def dataChanged(self): 65 | """Check if data has changed since the last build of the Frame.""" 66 | if self.lastBuildDMs != self.conflict.decisionMakers.export_rep(): 67 | return True 68 | if self.lastBuildOptions != self.conflict.options.export_rep(): 69 | return True 70 | if self.lastBuildInfeasibles != self.conflict.infeasibles.export_rep(): 71 | return True 72 | else: 73 | return False 74 | 75 | def buildFrame(self): 76 | """Contruct frame widgets and initialize data.""" 77 | if self.built: 78 | return 79 | 80 | # Ensure all required parts of the conflict model are properly set-up. 81 | self.conflict.reorderOptionsByDM() 82 | self.conflict.options.set_indexes() 83 | self.conflict.infeasibles.validate() 84 | self.conflict.recalculateFeasibleStates() 85 | 86 | for dm in self.conflict.decisionMakers: 87 | dm.calculatePerceived() 88 | dm.calculatePreferences() 89 | 90 | self.lastBuildDMs = self.conflict.decisionMakers.export_rep() 91 | self.lastBuildOptions = self.conflict.options.export_rep() 92 | self.lastBuildInfeasibles = self.conflict.infeasibles.export_rep() 93 | self.lastBuildRanking = self.conflict.useManualPreferenceRanking 94 | 95 | # Define variables that will display in the infoFrame 96 | self.infoText = StringVar(value='') 97 | 98 | # Define frame-specific variables 99 | 100 | # infoFrame: frame and label definitions (with master 'self.infoFrame') 101 | self.infoLabel = ttk.Label(self.infoFrame, textvariable=self.infoText) 102 | 103 | # helpFrame: frame and label definitions (with master 'self.helpFrame') 104 | self.helpLabel = ttk.Label(self.helpFrame, textvariable=self.helpVar, 105 | wraplength=150) 106 | 107 | # Define frame-specific input widgets (with 'self' as master) 108 | self.prefEditor = PRankEditMaster(self, self.conflict) 109 | self.stateTable = OptionFormTable(self, self.conflict) 110 | 111 | # ######## preliminary gridding and option configuration 112 | 113 | # configuring the input frame 114 | self.grid(column=0, row=0, rowspan=5, sticky=NSEW) 115 | self.grid_remove() 116 | 117 | # configuring infoFrame & infoFrame widgets 118 | self.infoFrame.grid(column=2, row=0, sticky=NSEW, padx=3, pady=3) 119 | self.infoFrame.grid_remove() 120 | self.infoLabel.grid(column=0, row=1, sticky=NSEW) 121 | 122 | # configuring helpFrame & helpFrame widgets 123 | self.helpFrame.grid(column=2, row=1, sticky=NSEW, padx=3, pady=3) 124 | self.helpFrame.grid_remove() 125 | self.helpLabel.grid(column=0, row=0, sticky=NSEW) 126 | 127 | # configuring frame-specific options 128 | self.prefEditor.grid(row=0, column=0, sticky=NSEW) 129 | self.stateTable.grid(row=1, column=0, sticky=NSEW) 130 | self.columnconfigure(0, weight=1) 131 | self.rowconfigure(1, weight=1) 132 | 133 | # bindings 134 | self.prefEditor.bind("<>", 135 | self.onPreferenceChange) 136 | 137 | self.built = True 138 | 139 | def enter(self, *args): 140 | """Re-grid the screen into the master. Perform required updates.""" 141 | if self.dataChanged(): 142 | self.clearFrame() 143 | 144 | FrameTemplate.enter(self) 145 | 146 | def refresh(self, *args): 147 | """Refresh data in all active display widgets.""" 148 | self.prefEditor.refresh() 149 | self.stateTable.buildTable() 150 | 151 | def onPreferenceChange(self, event=None): 152 | """Rebuild state table if preferences change.""" 153 | self.stateTable.buildTable() 154 | 155 | 156 | # ############################################################################# 157 | # ############### TESTING ########### 158 | # ############################################################################# 159 | 160 | # Code in this section is only run when this module is run by itself. It serves 161 | # as a test of module functionality. 162 | 163 | def main(): 164 | """Run screen in test window.""" 165 | root = Tk() 166 | root.columnconfigure(0, weight=1) 167 | root.rowconfigure(0, weight=1) 168 | 169 | cFrame = ttk.Frame(root) 170 | cFrame.columnconfigure(0, weight=1) 171 | cFrame.rowconfigure(1, weight=1) 172 | cFrame.grid(column=0, row=0, sticky=NSEW) 173 | 174 | hSep = ttk.Separator(cFrame, orient=VERTICAL) 175 | hSep.grid(column=1, row=0, rowspan=10, sticky=NSEW) 176 | 177 | testConflict = ConflictModel('SyriaIraq.gmcr') 178 | 179 | testFrame = PreferenceRankingFrame(cFrame, testConflict) 180 | testFrame.enter() 181 | 182 | root.mainloop() 183 | 184 | if __name__ == '__main__': 185 | main() 186 | -------------------------------------------------------------------------------- /frame_06_equilibria.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Frame for display of equilibria in the conflict. 4 | 5 | Loaded by the a_Main_Window module, and implements all of its required 6 | interfaces. 7 | 8 | Also includes functionality for exporting reachability data. 9 | """ 10 | 11 | from tkinter import (Tk, StringVar, N, S, E, W, VERTICAL, HORIZONTAL, 12 | PanedWindow) 13 | from tkinter import ttk 14 | from frame_00_frameTemplate import FrameTemplate 15 | from data_02_conflictSolvers import LogicalSolver 16 | from widgets_f06_01_logResultDisp import (CoalitionSelector, 17 | OptionFormSolutionTable, 18 | Exporter, 19 | LogNarrator) 20 | 21 | NSEW = (N, S, E, W) 22 | 23 | 24 | class ResultFrame(FrameTemplate): 25 | """Frame for display of equilibria in the conflict.""" 26 | 27 | # Label used for button to select frame in the main program. 28 | buttonLabel = 'Equilibria Results' 29 | # Image used on button to select frame, when frame is active. 30 | activeIcon = 'icons/Equilibria_Results_ON.gif' 31 | # Image used on button to select frame, when frame is inactive. 32 | inactiveIcon = 'icons/Equilibria_Results_OFF.gif' 33 | # Help text to be displayed when screen is active. 34 | helpText = ("The stability of each state in the conflict is shown in the " 35 | "table on the left, giving results under a number of different" 36 | " stability criterion. The display on the right allows the " 37 | "logic which defines the stability or instability of each " 38 | "option to be examined.") 39 | 40 | # ######################## INITIALIZATION ################################ 41 | def __init__(self, master, conflict, *args): 42 | """Initialize the Frame. Does not build widgets.""" 43 | FrameTemplate.__init__(self, master, conflict, self.buttonLabel, 44 | self.activeIcon, self.inactiveIcon, 45 | self.helpText) 46 | 47 | self.lastBuildConflict = None 48 | 49 | # ############################ METHODS ################################### 50 | 51 | def hasRequiredData(self): 52 | """Check that minimum data required to render the frame exists.""" 53 | if len(self.conflict.decisionMakers) < 1: 54 | return False 55 | if len(self.conflict.options) < 1: 56 | return False 57 | if len(self.conflict.feasibles) < 1: 58 | return False 59 | if self.conflict.preferenceErrors: 60 | return False 61 | else: 62 | return True 63 | 64 | def dataChanged(self): 65 | """Check if data has changed since the last build of the Frame.""" 66 | if self.lastBuildConflict != self.conflict.export_rep(): 67 | return True 68 | else: 69 | return False 70 | 71 | def buildFrame(self): 72 | """Contruct frame widgets and initialize data.""" 73 | if self.built: 74 | return 75 | 76 | # Ensure all required parts of the conflict model are properly set-up. 77 | self.conflict.reorderOptionsByDM() 78 | self.conflict.options.set_indexes() 79 | self.conflict.infeasibles.validate() 80 | self.conflict.recalculateFeasibleStates() 81 | self.conflict.coalitions.validate() 82 | 83 | for dm in self.conflict.decisionMakers: 84 | dm.calculatePerceived() 85 | dm.calculatePreferences() 86 | 87 | self.lastBuildConflict = self.conflict.export_rep() 88 | 89 | # Define variables that will display in the infoFrame 90 | self.infoText = StringVar(value='') 91 | 92 | # Define frame-specific variables 93 | self.sol = LogicalSolver(self.conflict) 94 | self.sol.findEquilibria() 95 | 96 | # infoFrame: frame and label definitions (with master 'self.infoFrame') 97 | self.infoLabel = ttk.Label(self.infoFrame, textvariable=self.infoText) 98 | 99 | # helpFrame: frame and label definitions (with master 'self.helpFrame') 100 | self.helpLabel = ttk.Label(self.helpFrame, textvariable=self.helpVar, 101 | wraplength=150) 102 | 103 | # Define frame-specific input widgets (with 'self' as master) 104 | self.paneMaster = PanedWindow(self, orient=HORIZONTAL, sashwidth=10, 105 | sashrelief="raised", sashpad=3, 106 | relief="sunken") 107 | 108 | self.pane1 = ttk.Frame(self.paneMaster) 109 | self.coalitionSelector = CoalitionSelector(self.pane1, self.conflict, 110 | self) 111 | self.solutionTable = OptionFormSolutionTable(self.pane1, self.conflict, 112 | self) 113 | self.exporter = Exporter(self.pane1, self.conflict, self) 114 | 115 | self.pane2 = ttk.Frame(self.paneMaster) 116 | self.narrator = LogNarrator(self.pane2, self.conflict, self) 117 | 118 | # ######## preliminary gridding and option configuration 119 | 120 | # configuring the input frame 121 | self.grid(column=0, row=0, rowspan=5, sticky=NSEW) 122 | self.grid_remove() 123 | self.columnconfigure(0, weight=1) 124 | self.rowconfigure(1, weight=1) 125 | 126 | # configuring infoFrame & infoFrame widgets 127 | self.infoFrame.grid(column=2, row=0, sticky=NSEW, padx=3, pady=3) 128 | self.infoFrame.grid_remove() 129 | self.infoLabel.grid(column=0, row=1, sticky=NSEW) 130 | 131 | # configuring helpFrame & helpFrame widgets 132 | self.helpFrame.grid(column=2, row=1, sticky=NSEW, padx=3, pady=3) 133 | self.helpFrame.grid_remove() 134 | self.helpLabel.grid(column=0, row=0, sticky=NSEW) 135 | 136 | # configuring frame-specific options 137 | self.paneMaster.grid(column=0, row=1, sticky=NSEW) 138 | self.paneMaster.add(self.pane1, width=600, stretch='always') 139 | self.pane1.rowconfigure(1, weight=1) 140 | self.pane1.columnconfigure(0, weight=1) 141 | self.coalitionSelector.grid(row=0, column=0, sticky=NSEW) 142 | self.solutionTable.grid(row=1, column=0, sticky=NSEW) 143 | self.exporter.grid(row=2, column=0, sticky=NSEW) 144 | 145 | self.paneMaster.add(self.pane2, width=250, stretch='always') 146 | self.pane2.rowconfigure(0, weight=1) 147 | self.pane2.columnconfigure(0, weight=1) 148 | self.narrator.grid(row=0, column=0, sticky=NSEW) 149 | 150 | # bindings 151 | self.coalitionSelector.bind("<>", 152 | self.refresh) 153 | 154 | self.built = True 155 | 156 | def refresh(self, *args): 157 | """Refresh data in all active display widgets.""" 158 | self.sol = LogicalSolver(self.conflict) 159 | self.sol.findEquilibria() 160 | self.coalitionSelector.refresh() 161 | self.solutionTable.refresh() 162 | self.narrator.refresh() 163 | 164 | 165 | # ############################################################################# 166 | # ############### TESTING ########### 167 | # ############################################################################# 168 | 169 | # Code in this section is only run when this module is run by itself. It serves 170 | # as a test of module functionality. 171 | 172 | 173 | def main(): 174 | """Run screen in test window.""" 175 | from data_01_conflictModel import ConflictModel 176 | 177 | root = Tk() 178 | root.columnconfigure(0, weight=1) 179 | root.rowconfigure(0, weight=1) 180 | 181 | cFrame = ttk.Frame(root) 182 | cFrame.columnconfigure(0, weight=1) 183 | cFrame.rowconfigure(1, weight=1) 184 | cFrame.grid(column=0, row=0, sticky=NSEW) 185 | 186 | hSep = ttk.Separator(cFrame, orient=VERTICAL) 187 | hSep.grid(column=1, row=0, rowspan=10, sticky=NSEW) 188 | 189 | conf = ConflictModel() 190 | conf.load_from_file("save_files/Garrison.gmcr") 191 | 192 | testFrame = ResultFrame(cFrame, conf) 193 | if testFrame.hasRequiredData(): 194 | testFrame.buildFrame() 195 | else: 196 | print("data missing") 197 | return 198 | testFrame.enter() 199 | 200 | root.mainloop() 201 | 202 | if __name__ == '__main__': 203 | main() 204 | -------------------------------------------------------------------------------- /frame_07_inverseGMCR.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Frame used to create and display results of Inverse Approach solutions. 4 | 5 | Loaded by the a_Main_Window module, and implements all of its required 6 | interfaces. 7 | """ 8 | 9 | from tkinter import Tk, N, S, E, W, VERTICAL 10 | from tkinter import ttk 11 | from frame_00_frameTemplate import FrameTemplate 12 | from data_01_conflictModel import ConflictModel 13 | from widgets_f07_01_inverseContent import InverseContent 14 | 15 | NSEW = (N, S, E, W) 16 | 17 | 18 | class InverseFrame(FrameTemplate): 19 | """Calculate and display results of Inverse Approach solutions.""" 20 | 21 | # Label used for button to select frame in the main program. 22 | buttonLabel = 'Inverse GMCR' 23 | # Image used on button to select frame, when frame is active. 24 | activeIcon = 'icons/Inverse_GMCR_ON.gif' 25 | # Image used on button to select frame, when frame is inactive. 26 | inactiveIcon = 'icons/Inverse_GMCR_OFF.gif' 27 | # Help text to be displayed when screen is active. 28 | helpText = ("Inverse GMCR calculations.") 29 | 30 | # ######################## INITIALIZATION ################################ 31 | def __init__(self, master, conflict, *args): 32 | """Initialize the Frame. Does not build widgets.""" 33 | FrameTemplate.__init__(self, master, conflict, self.buttonLabel, 34 | self.activeIcon, self.inactiveIcon, 35 | self.helpText) 36 | 37 | self.lastBuildConflict = None 38 | 39 | # ############################ METHODS ################################### 40 | 41 | def hasRequiredData(self): 42 | """Check that minimum data required to render the frame exists.""" 43 | if len(self.conflict.decisionMakers) < 1: 44 | return False 45 | if len(self.conflict.options) < 1: 46 | return False 47 | if len(self.conflict.feasibles) < 1: 48 | return False 49 | if self.conflict.preferenceErrors: 50 | return False 51 | else: 52 | return True 53 | 54 | def dataChanged(self): 55 | """Check if data has changed since the last build of the Frame.""" 56 | if self.lastBuildConflict != self.conflict.export_rep(): 57 | return True 58 | else: 59 | return False 60 | 61 | def buildFrame(self): 62 | """Contruct frame widgets and initialize data.""" 63 | if self.built: 64 | return 65 | 66 | # Ensure all required parts of the conflict model are properly set-up. 67 | self.conflict.reorderOptionsByDM() 68 | self.conflict.options.set_indexes() 69 | self.conflict.infeasibles.validate() 70 | self.conflict.recalculateFeasibleStates() 71 | 72 | for dm in self.conflict.decisionMakers: 73 | dm.calculatePerceived() 74 | dm.calculatePreferences() 75 | 76 | self.lastBuildConflict = self.conflict.export_rep() 77 | 78 | # Define frame-specific variables 79 | 80 | # infoFrame: frame and label definitions (with master 'self.infoFrame') 81 | self.infoLabel = ttk.Label(self.infoFrame, text="") 82 | 83 | # helpFrame: frame and label definitions (with master 'self.helpFrame') 84 | self.helpLabel = ttk.Label(self.helpFrame, textvariable=self.helpVar, 85 | wraplength=150) 86 | 87 | # Define frame-specific input widgets (with 'self' as master) 88 | self.invDisp = InverseContent(self, self.conflict) 89 | 90 | # ######## preliminary gridding and option configuration 91 | 92 | # configuring the input frame 93 | self.grid(column=0, row=0, rowspan=5, sticky=NSEW) 94 | self.grid_remove() 95 | self.columnconfigure(0, weight=1) 96 | self.rowconfigure(1, weight=1) 97 | 98 | # configuring infoFrame & infoFrame widgets 99 | self.infoFrame.grid(column=2, row=0, sticky=NSEW, padx=3, pady=3) 100 | self.infoFrame.grid_remove() 101 | self.infoLabel.grid(column=0, row=1, sticky=NSEW) 102 | 103 | # configuring helpFrame & helpFrame widgets 104 | self.helpFrame.grid(column=2, row=1, sticky=NSEW, padx=3, pady=3) 105 | self.helpFrame.grid_remove() 106 | self.helpLabel.grid(column=0, row=0, sticky=NSEW) 107 | 108 | # configuring frame-specific options 109 | self.invDisp.grid(column=0, row=1, sticky=NSEW) 110 | 111 | # bindings 112 | 113 | self.built = True 114 | 115 | def refresh(self, *args): 116 | """Refresh data in all active display widgets.""" 117 | self.invDisp.refreshDisplay() 118 | self.invDisp.refreshSolution() 119 | 120 | def leave(self, *args): 121 | """Un-grid the screen. Perform exit clean-up tasks.""" 122 | FrameTemplate.leave(self) 123 | del self.invDisp.sol 124 | 125 | 126 | # ############################################################################# 127 | # ############### TESTING ########### 128 | # ############################################################################# 129 | 130 | # Code in this section is only run when this module is run by itself. It serves 131 | # as a test of module functionality. 132 | 133 | 134 | def main(): 135 | """Run screen in test window.""" 136 | root = Tk() 137 | root.columnconfigure(0, weight=1) 138 | root.rowconfigure(0, weight=1) 139 | 140 | cFrame = ttk.Frame(root) 141 | cFrame.columnconfigure(0, weight=1) 142 | cFrame.rowconfigure(1, weight=1) 143 | cFrame.grid(column=0, row=0, sticky=NSEW) 144 | 145 | hSep = ttk.Separator(cFrame, orient=VERTICAL) 146 | hSep.grid(column=1, row=0, rowspan=10, sticky=NSEW) 147 | 148 | testConflict = ConflictModel('pris.gmcr') 149 | 150 | testFrame = InverseFrame(cFrame, testConflict) 151 | testFrame.enter() 152 | 153 | root.mainloop() 154 | 155 | 156 | if __name__ == '__main__': 157 | main() 158 | -------------------------------------------------------------------------------- /frame_08_stabilityAnalysis.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Frame used to for Stability Analysis. 4 | 5 | Loaded by the a_Main_Window module, and implements all of its required 6 | interfaces. 7 | """ 8 | 9 | from tkinter import Tk, N, S, E, W, PanedWindow, HORIZONTAL, VERTICAL 10 | from tkinter import ttk 11 | from frame_00_frameTemplate import FrameTemplate 12 | from data_02_conflictSolvers import GoalSeeker 13 | from widgets_f06_01_logResultDisp import CoalitionSelector 14 | from widgets_f04_03_optionForm import OptionFormTable 15 | from widgets_f08_01_stabilityAnalysis import (StatusQuoAndGoals, 16 | ReachableTreeViewer, 17 | PatternNarrator) 18 | 19 | NSEW = (N, S, E, W) 20 | 21 | 22 | class StabilityFrame(FrameTemplate): 23 | """Frame used to for Stability Analysis.""" 24 | 25 | # Label used for button to select frame in the main program. 26 | buttonLabel = 'Post Analysis' 27 | # Image used on button to select frame, when frame is active. 28 | activeIcon = 'icons/Post_Analysis_ON.gif' 29 | # Image used on button to select frame, when frame is inactive. 30 | inactiveIcon = 'icons/Post_Analysis_OFF.gif' 31 | # Help text to be displayed when screen is active. 32 | helpText = ("Selecting coalitions, status quo and goals at the top left" 33 | "will generate a reachability tree below the status quo, and " 34 | "detail the patterns that preferences must match for the goal " 35 | "state to be reached.") 36 | 37 | # ######################## INITIALIZATION ################################ 38 | def __init__(self, master, conflict, *args): 39 | """Initialize the Frame. Does not build widgets.""" 40 | FrameTemplate.__init__(self, master, conflict, self.buttonLabel, 41 | self.activeIcon, self.inactiveIcon, 42 | self.helpText) 43 | 44 | self.lastBuildConflict = None 45 | 46 | 47 | # ############################ METHODS ################################### 48 | 49 | def hasRequiredData(self): 50 | """Check that minimum data required to render the frame exists.""" 51 | if len(self.conflict.decisionMakers) < 1: 52 | return False 53 | if len(self.conflict.options) < 1: 54 | return False 55 | if len(self.conflict.feasibles) < 1: 56 | return False 57 | if self.conflict.preferenceErrors: 58 | return False 59 | else: 60 | return True 61 | 62 | def dataChanged(self): 63 | """Check if data has changed since the last build of the Frame.""" 64 | if self.lastBuildConflict != self.conflict.export_rep(): 65 | return True 66 | else: 67 | return False 68 | 69 | def buildFrame(self): 70 | """Contruct frame widgets and initialize data.""" 71 | if self.built: 72 | return 73 | 74 | # Ensure all required parts of the conflict model are properly set-up. 75 | self.conflict.reorderOptionsByDM() 76 | self.conflict.options.set_indexes() 77 | self.conflict.infeasibles.validate() 78 | self.conflict.recalculateFeasibleStates() 79 | 80 | for dm in self.conflict.decisionMakers: 81 | dm.calculatePerceived() 82 | dm.calculatePreferences() 83 | 84 | self.lastBuildConflict = self.conflict.export_rep() 85 | 86 | # Define frame-specific variables 87 | self.sol = GoalSeeker(self.conflict) 88 | 89 | # infoFrame: frame and label definitions (with master 'self.infoFrame') 90 | self.infoLabel = ttk.Label(self.infoFrame, text="") 91 | 92 | # helpFrame: frame and label definitions (with master 'self.helpFrame') 93 | self.helpLabel = ttk.Label(self.helpFrame, textvariable=self.helpVar, 94 | wraplength=150) 95 | 96 | # Define frame-specific input widgets (with 'self' as master) 97 | self.paneMaster = PanedWindow(self, orient=HORIZONTAL, sashwidth=10, 98 | sashrelief="raised", sashpad=3, 99 | relief="sunken") 100 | 101 | self.paneLeft = PanedWindow(self.paneMaster, orient=VERTICAL, 102 | sashwidth=10, sashrelief="raised", 103 | sashpad=3, relief="sunken") 104 | 105 | self.paneLeftTop = ttk.Frame(self.paneLeft) 106 | self.coalitionSelector = CoalitionSelector(self.paneLeftTop, 107 | self.conflict, self) 108 | self.statusQuoAndGoals = StatusQuoAndGoals(self.paneLeftTop, 109 | self.conflict) 110 | self.reachableTree = ReachableTreeViewer(self.paneLeftTop, 111 | self.conflict, self) 112 | 113 | self.paneLeftBottom = ttk.Frame(self.paneLeft) 114 | self.optionFormTable = OptionFormTable(self.paneLeftBottom, 115 | self.conflict) 116 | 117 | self.paneRight = ttk.Frame(self.paneMaster) 118 | self.patternNarrator = PatternNarrator(self.paneRight, self.conflict, 119 | self) 120 | 121 | # ######## preliminary gridding and option configuration 122 | 123 | # configuring the input frame 124 | self.grid(column=0, row=0, rowspan=5, sticky=NSEW) 125 | self.grid_remove() 126 | self.columnconfigure(0, weight=1) 127 | self.rowconfigure(0, weight=1) 128 | 129 | # configuring infoFrame & infoFrame widgets 130 | self.infoFrame.grid(column=2, row=0, sticky=NSEW, padx=3, pady=3) 131 | self.infoFrame.grid_remove() 132 | self.infoLabel.grid(column=0, row=1, sticky=NSEW) 133 | 134 | # configuring helpFrame & helpFrame widgets 135 | self.helpFrame.grid(column=2, row=1, sticky=NSEW, padx=3, pady=3) 136 | self.helpFrame.grid_remove() 137 | self.helpLabel.grid(column=0, row=0, sticky=NSEW) 138 | 139 | # configuring frame-specific options 140 | self.paneMaster.grid(row=0, column=0, sticky=NSEW) 141 | 142 | self.paneMaster.add(self.paneLeft) 143 | 144 | self.paneLeft.add(self.paneLeftTop) 145 | self.paneLeftTop.columnconfigure(1, weight=1) 146 | self.paneLeftTop.rowconfigure(2, weight=1) 147 | self.coalitionSelector.grid(row=0, column=0, sticky=NSEW) 148 | ttk.Separator(self, orient=HORIZONTAL).grid(row=1, column=0, 149 | sticky=NSEW, pady=3) 150 | self.statusQuoAndGoals.grid(row=2, column=0, sticky=NSEW) 151 | self.reachableTree.grid(row=0, column=1, rowspan=3, sticky=NSEW) 152 | 153 | self.paneLeft.add(self.paneLeftBottom) 154 | self.paneLeftBottom.rowconfigure(0, weight=1) 155 | self.paneLeftBottom.columnconfigure(0, weight=1) 156 | self.optionFormTable.grid(row=0, column=0, sticky=NSEW) 157 | 158 | self.paneMaster.add(self.paneRight) 159 | self.paneRight.rowconfigure(0, weight=1) 160 | self.paneRight.columnconfigure(0, weight=1) 161 | self.patternNarrator.grid(row=0, column=0, sticky=NSEW) 162 | 163 | # bindings 164 | self.statusQuoAndGoals.bind("<>", 165 | self.refresh) 166 | self.statusQuoAndGoals.bind("<>", 167 | self.refresh) 168 | self.coalitionSelector.bind("<>", 169 | self.refresh) 170 | 171 | self.built = True 172 | 173 | def refresh(self, *args): 174 | """Refresh data in all active display widgets.""" 175 | sq = self.statusQuoAndGoals.statusQuoSelector.current() 176 | goals = self.statusQuoAndGoals.getGoals() 177 | if len(goals) > 0: 178 | self.sol = GoalSeeker(self.conflict, goals) 179 | else: 180 | self.sol = GoalSeeker(self.conflict) 181 | self.reachableTree.buildTree(sq, watchFor=[x[0] for x in goals]) 182 | self.patternNarrator.updateNarration( 183 | goalInfo=self.reachableTree.goalInfo()) 184 | 185 | 186 | # ############################################################################# 187 | # ############### TESTING ########### 188 | # ############################################################################# 189 | 190 | # Code in this section is only run when this module is run by itself. It serves 191 | # as a test of module functionality. 192 | 193 | 194 | def main(): 195 | """Run screen in test window.""" 196 | from data_01_conflictModel import ConflictModel 197 | 198 | root = Tk() 199 | root.columnconfigure(0, weight=1) 200 | root.rowconfigure(0, weight=1) 201 | 202 | cFrame = ttk.Frame(root) 203 | cFrame.columnconfigure(0, weight=1) 204 | cFrame.rowconfigure(1, weight=1) 205 | cFrame.grid(column=0, row=0, sticky=NSEW) 206 | 207 | hSep = ttk.Separator(cFrame, orient=VERTICAL) 208 | hSep.grid(column=1, row=0, rowspan=10, sticky=NSEW) 209 | 210 | conf = ConflictModel() 211 | conf.load_from_file("save_files/Garrison.gmcr") 212 | 213 | testFrame = StabilityFrame(cFrame, conf) 214 | if testFrame.hasRequiredData(): 215 | testFrame.buildFrame() 216 | else: 217 | print("data missing") 218 | return 219 | testFrame.enter() 220 | 221 | root.mainloop() 222 | 223 | if __name__ == '__main__': 224 | main() 225 | -------------------------------------------------------------------------------- /gmcr.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/gmcr.ico -------------------------------------------------------------------------------- /gmcr.py: -------------------------------------------------------------------------------- 1 | import data_01_conflictModel as model 2 | import data_02_conflictSolvers as solvers 3 | from version import __version__ 4 | -------------------------------------------------------------------------------- /icons/DMs_and_Options_OFF.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/icons/DMs_and_Options_OFF.gif -------------------------------------------------------------------------------- /icons/DMs_and_Options_ON.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/icons/DMs_and_Options_ON.gif -------------------------------------------------------------------------------- /icons/Equilibria_Results_OFF.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/icons/Equilibria_Results_OFF.gif -------------------------------------------------------------------------------- /icons/Equilibria_Results_ON.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/icons/Equilibria_Results_ON.gif -------------------------------------------------------------------------------- /icons/Infeasible_States_OFF.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/icons/Infeasible_States_OFF.gif -------------------------------------------------------------------------------- /icons/Infeasible_States_ON.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/icons/Infeasible_States_ON.gif -------------------------------------------------------------------------------- /icons/Inverse_GMCR_OFF.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/icons/Inverse_GMCR_OFF.gif -------------------------------------------------------------------------------- /icons/Inverse_GMCR_ON.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/icons/Inverse_GMCR_ON.gif -------------------------------------------------------------------------------- /icons/Irreversible_Moves_OFF.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/icons/Irreversible_Moves_OFF.gif -------------------------------------------------------------------------------- /icons/Irreversible_Moves_ON.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/icons/Irreversible_Moves_ON.gif -------------------------------------------------------------------------------- /icons/Post_Analysis_OFF.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/icons/Post_Analysis_OFF.gif -------------------------------------------------------------------------------- /icons/Post_Analysis_ON.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/icons/Post_Analysis_ON.gif -------------------------------------------------------------------------------- /icons/Preference_Ranking_OFF.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/icons/Preference_Ranking_OFF.gif -------------------------------------------------------------------------------- /icons/Preference_Ranking_ON.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/icons/Preference_Ranking_ON.gif -------------------------------------------------------------------------------- /icons/Prioritization_OFF.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/icons/Prioritization_OFF.gif -------------------------------------------------------------------------------- /icons/Prioritization_ON.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/icons/Prioritization_ON.gif -------------------------------------------------------------------------------- /icons/Thumbs.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/icons/Thumbs.db -------------------------------------------------------------------------------- /icons/backArrow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/icons/backArrow.gif -------------------------------------------------------------------------------- /icons/bothArrow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/icons/bothArrow.gif -------------------------------------------------------------------------------- /icons/fwdArrow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onp/gmcr-py/502a12c9100962aaa0551763b74d303864967b80/icons/fwdArrow.gif -------------------------------------------------------------------------------- /mp_test.py: -------------------------------------------------------------------------------- 1 | from tkinter import Tk, StringVar 2 | from tkinter import ttk as ttk 3 | from subprocess import Popen, PIPE 4 | import sys 5 | import multiprocessing as mp 6 | 7 | 8 | class MainWindow: 9 | 10 | def __init__(self): 11 | 12 | self.root = Tk() 13 | self.labelVar = StringVar() 14 | self.labelVar.set("start") 15 | self.label = ttk.Label(self.root, textvariable=self.labelVar) 16 | 17 | self.label.grid(column=0, row=0) 18 | 19 | self.root.mainloop() 20 | 21 | 22 | def initApp(): 23 | app = MainWindow() 24 | 25 | 26 | def runProc(): 27 | p = mp.Process(target=initApp) 28 | p.start() 29 | 30 | 31 | if __name__ == '__main__': 32 | runProc() 33 | self.process = Popen([sys.executable], stdout=PIPE) 34 | -------------------------------------------------------------------------------- /savefile-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema" : "http://json-schema.org/schema#", 3 | "type" : "object", 4 | "properties" : { 5 | "decisionMakers":{ 6 | "type" : "array", 7 | "items" : { 8 | "type":"object", 9 | "properties" : { 10 | "name": {"type" : "string"} 11 | "options" : {"type" : "array", "items" : {"type" : "number"}} 12 | "preferences" : { 13 | "type" : "array", 14 | "items" : { 15 | "type" : "array", 16 | "items" : { 17 | "type" : "array", 18 | "items": [ 19 | {"type":"number"}, 20 | {"type":"string"} 21 | ], 22 | "minItems": 2, 23 | "additionalItems":false 24 | } 25 | } 26 | } 27 | }, 28 | "required": [ "name", "options", "preferences"] 29 | }, 30 | }, 31 | "options":{ 32 | "type" : "array", 33 | "items" : { 34 | "type" : "object", 35 | "properties" : { 36 | "name" : {"type" : "string"}, 37 | "permittedDirection" : { 38 | "type" : "string" 39 | } 40 | }, 41 | "required" : ["name", "permittedDirection"] 42 | }, 43 | "infeasibles":{ 44 | "type" : "array", 45 | "items" : { 46 | "type" : "array", 47 | "items" : { 48 | "type" : "array", 49 | "items": [ 50 | {"type":"number"}, 51 | {"type":"string"} 52 | ], 53 | "minItems": 2, 54 | "additionalItems":false 55 | } 56 | } 57 | }, 58 | "program" 59 | "version" 60 | "useManualPreferenceVectors" : { "type":"boolean" } 61 | }, 62 | "required" : ["decisionMakers", "options", "infeasibles"] 63 | } -------------------------------------------------------------------------------- /sp.py: -------------------------------------------------------------------------------- 1 | import data_01_conflictModel as mod 2 | import data_02_conflictSolvers as slv 3 | import data_04_spSolvers as spslv 4 | import time 5 | 6 | 7 | conf2 = mod.ConflictModel() 8 | conf2.load_from_file('save_files/garrison.gmcr') 9 | 10 | t3 = time.perf_counter() 11 | spsol = spslv.LogicalSolver(conf2) 12 | spsol.findEquilibria() 13 | t4 = time.perf_counter() 14 | 15 | print(t4-t3) 16 | 17 | 18 | 19 | conf = mod.ConflictModel() 20 | conf.load_from_file('save_files/garrison.gmcr') 21 | 22 | t1 = time.perf_counter() 23 | sol = slv.LogicalSolver(conf) 24 | sol.findEquilibria() 25 | t2 = time.perf_counter() 26 | 27 | print(t2-t1) -------------------------------------------------------------------------------- /test_data/Garrison_logSol.txt: -------------------------------------------------------------------------------- 1 | 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 2 | 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 3 | 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 4 | 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 5 | 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 6 | 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 7 | -------------------------------------------------------------------------------- /test_data/MilkRiver_logSol.txt: -------------------------------------------------------------------------------- 1 | 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 2 | 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 3 | 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 4 | 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 5 | 1.000000000000000000e+00 1.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 6 | 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 7 | -------------------------------------------------------------------------------- /test_data/Prisoners_logSol.txt: -------------------------------------------------------------------------------- 1 | 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 2 | 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 3 | 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 4 | 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 5 | 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 6 | 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 7 | -------------------------------------------------------------------------------- /test_data/Prisoners_narr.txt: -------------------------------------------------------------------------------- 1 | 2 | #DM Prisoner One state 0 NASH 3 | state 1 (decimal 0, payoff 2) is Nash stable for DM Prisoner One since they have no UIs from this state. 4 | 5 | #DM Prisoner One state 0 SEQ 6 | state 1 (decimal 0, payoff 2) is SEQ stable for DM Prisoner One since they have no UIs from this state. 7 | 8 | #DM Prisoner One state 0 SIM 9 | state 1 (decimal 0, payoff 2) is SIM stable since focal dm Prisoner One has no UIs available. 10 | 11 | #DM Prisoner One state 0 GMR 12 | state 1 (decimal 0, payoff 2) is GMR stable since focal DM Prisoner One has no UIs available. 13 | 14 | #DM Prisoner One state 0 SMR 15 | state 1 (decimal 0, payoff 2) is SMR stable since focal DM Prisoner One has no UIs available. 16 | 17 | #DM Prisoner One state 1 NASH 18 | state 2 (decimal 1, payoff 1) is NOT Nash stable for DM Prisoner One since they have UIs available to: state 1 (decimal 0, payoff 2) 19 | 20 | #DM Prisoner One state 1 SEQ 21 | From state 2 (decimal 1, payoff 1) Prisoner One has UIs available to: state 1 (decimal 0, payoff 2) . Check for sanctioning... 22 | 23 | state 2 (decimal 1, payoff 1) is unstable by SEQ for focal DM Prisoner One, since their opponents have no UIs from state 1 (decimal 0, payoff 2) 24 | 25 | 26 | #DM Prisoner One state 1 SIM 27 | From state 2 (decimal 1, payoff 1) Prisoner One has UIs available to: state 1 (decimal 0, payoff 2) . Check for sanctioning... 28 | 29 | state 2 (decimal 1, payoff 1) is unstable by SIM for focal dm Prisoner One, since their opponents have no UIs from state 2 (decimal 1, payoff 1). 30 | 31 | 32 | #DM Prisoner One state 1 GMR 33 | From state 2 (decimal 1, payoff 1) Prisoner One has UIs available to: state 1 (decimal 0, payoff 2). Check for sanctioning... 34 | 35 | state 2 (decimal 1, payoff 1)) is unstable by GMR for focal dm Prisoner One, since their opponents have no less preferred sanctioning UIs from state 1 (decimal 0, payoff 2). 36 | 37 | 38 | #DM Prisoner One state 1 SMR 39 | From state 2 (decimal 1, payoff 1) Prisoner One has UIs available to: state 1 (decimal 0, payoff 2) . Check for sanctioning... 40 | 41 | state 2 (decimal 1, payoff 1)) is unstable by SMR for focal dm Prisoner One, since their opponents have no less preferred sanctioning UIs from state 1 (decimal 0, payoff 2) that cannot be effectively countermoved by the focal dm. 42 | 43 | 44 | #DM Prisoner One state 2 NASH 45 | state 3 (decimal 2, payoff 4) is Nash stable for DM Prisoner One since they have no UIs from this state. 46 | 47 | #DM Prisoner One state 2 SEQ 48 | state 3 (decimal 2, payoff 4) is SEQ stable for DM Prisoner One since they have no UIs from this state. 49 | 50 | #DM Prisoner One state 2 SIM 51 | state 3 (decimal 2, payoff 4) is SIM stable since focal dm Prisoner One has no UIs available. 52 | 53 | #DM Prisoner One state 2 GMR 54 | state 3 (decimal 2, payoff 4) is GMR stable since focal DM Prisoner One has no UIs available. 55 | 56 | #DM Prisoner One state 2 SMR 57 | state 3 (decimal 2, payoff 4) is SMR stable since focal DM Prisoner One has no UIs available. 58 | 59 | #DM Prisoner One state 3 NASH 60 | state 4 (decimal 3, payoff 3) is NOT Nash stable for DM Prisoner One since they have UIs available to: state 3 (decimal 2, payoff 4) 61 | 62 | #DM Prisoner One state 3 SEQ 63 | From state 4 (decimal 3, payoff 3) Prisoner One has UIs available to: state 3 (decimal 2, payoff 4) . Check for sanctioning... 64 | 65 | A move to state 3 (decimal 2, payoff 4) is SEQ sanctioned for focal DM Prisoner One by a move to state 1 (decimal 0, payoff 2) by other dms. Check other focal DM UIs for sanctioning... 66 | 67 | state 4 (decimal 3, payoff 3) is stable by SEQ for focal dm Prisoner One, since all available UIs ['state 3 (decimal 2, payoff 4)'] are sanctioned by other players. 68 | 69 | 70 | #DM Prisoner One state 3 SIM 71 | From state 4 (decimal 3, payoff 3) Prisoner One has UIs available to: state 3 (decimal 2, payoff 4) . Check for sanctioning... 72 | 73 | A move to state 3 (decimal 2, payoff 4) is SIM sanctioned for focal DM Prisoner One by a move to state 2 (decimal 1, payoff 1) by other DMs, which would give a final state of state 1 (decimal 0, payoff 2). Check other focal DM UIs for sanctioning... 74 | 75 | state 4 (decimal 3, payoff 3) is stable by SIM for focal DM Prisoner One, since all available UIs ['state 3 (decimal 2, payoff 4)'] are sanctioned by other players. 76 | 77 | 78 | #DM Prisoner One state 3 GMR 79 | From state 4 (decimal 3, payoff 3) Prisoner One has UIs available to: state 3 (decimal 2, payoff 4). Check for sanctioning... 80 | 81 | A move to state 3 (decimal 2, payoff 4) is GMR sanctioned for focal DM Prisoner One by a move to state 1 (decimal 0, payoff 2) by other DMs. 82 | 83 | state 4 (decimal 3, payoff 3) is stable by GMR for focal DM Prisoner One, since all available UIs ['state 3 (decimal 2, payoff 4)']are sanctioned by other players. 84 | 85 | 86 | #DM Prisoner One state 3 SMR 87 | From state 4 (decimal 3, payoff 3) Prisoner One has UIs available to: state 3 (decimal 2, payoff 4) . Check for sanctioning... 88 | 89 | A move to state 3 (decimal 2, payoff 4) is SMR sanctioned for focal DM Prisoner One by a move to state 1 (decimal 0, payoff 2) by other dms. Check for possible countermoves... 90 | 91 | state 3 (decimal 2, payoff 4) remains sanctioned under SMR for focal DM Prisoner One, since they cannot countermove their opponent's sanction to state 1 (decimal 0, payoff 2). 92 | 93 | state 4 (decimal 3, payoff 3) is stable by SMR for focal dm Prisoner One, since all available UIs ['state 3 (decimal 2, payoff 4)'] are sanctioned by other players and cannot be countermoved. 94 | 95 | 96 | #DM prisoner 2 state 0 NASH 97 | state 1 (decimal 0, payoff 2) is Nash stable for DM prisoner 2 since they have no UIs from this state. 98 | 99 | #DM prisoner 2 state 0 SEQ 100 | state 1 (decimal 0, payoff 2) is SEQ stable for DM prisoner 2 since they have no UIs from this state. 101 | 102 | #DM prisoner 2 state 0 SIM 103 | state 1 (decimal 0, payoff 2) is SIM stable since focal dm prisoner 2 has no UIs available. 104 | 105 | #DM prisoner 2 state 0 GMR 106 | state 1 (decimal 0, payoff 2) is GMR stable since focal DM prisoner 2 has no UIs available. 107 | 108 | #DM prisoner 2 state 0 SMR 109 | state 1 (decimal 0, payoff 2) is SMR stable since focal DM prisoner 2 has no UIs available. 110 | 111 | #DM prisoner 2 state 1 NASH 112 | state 2 (decimal 1, payoff 4) is Nash stable for DM prisoner 2 since they have no UIs from this state. 113 | 114 | #DM prisoner 2 state 1 SEQ 115 | state 2 (decimal 1, payoff 4) is SEQ stable for DM prisoner 2 since they have no UIs from this state. 116 | 117 | #DM prisoner 2 state 1 SIM 118 | state 2 (decimal 1, payoff 4) is SIM stable since focal dm prisoner 2 has no UIs available. 119 | 120 | #DM prisoner 2 state 1 GMR 121 | state 2 (decimal 1, payoff 4) is GMR stable since focal DM prisoner 2 has no UIs available. 122 | 123 | #DM prisoner 2 state 1 SMR 124 | state 2 (decimal 1, payoff 4) is SMR stable since focal DM prisoner 2 has no UIs available. 125 | 126 | #DM prisoner 2 state 2 NASH 127 | state 3 (decimal 2, payoff 1) is NOT Nash stable for DM prisoner 2 since they have UIs available to: state 1 (decimal 0, payoff 2) 128 | 129 | #DM prisoner 2 state 2 SEQ 130 | From state 3 (decimal 2, payoff 1) prisoner 2 has UIs available to: state 1 (decimal 0, payoff 2) . Check for sanctioning... 131 | 132 | state 3 (decimal 2, payoff 1) is unstable by SEQ for focal DM prisoner 2, since their opponents have no UIs from state 1 (decimal 0, payoff 2) 133 | 134 | 135 | #DM prisoner 2 state 2 SIM 136 | From state 3 (decimal 2, payoff 1) prisoner 2 has UIs available to: state 1 (decimal 0, payoff 2) . Check for sanctioning... 137 | 138 | state 3 (decimal 2, payoff 1) is unstable by SIM for focal dm prisoner 2, since their opponents have no UIs from state 3 (decimal 2, payoff 1). 139 | 140 | 141 | #DM prisoner 2 state 2 GMR 142 | From state 3 (decimal 2, payoff 1) prisoner 2 has UIs available to: state 1 (decimal 0, payoff 2). Check for sanctioning... 143 | 144 | state 3 (decimal 2, payoff 1)) is unstable by GMR for focal dm prisoner 2, since their opponents have no less preferred sanctioning UIs from state 1 (decimal 0, payoff 2). 145 | 146 | 147 | #DM prisoner 2 state 2 SMR 148 | From state 3 (decimal 2, payoff 1) prisoner 2 has UIs available to: state 1 (decimal 0, payoff 2) . Check for sanctioning... 149 | 150 | state 3 (decimal 2, payoff 1)) is unstable by SMR for focal dm prisoner 2, since their opponents have no less preferred sanctioning UIs from state 1 (decimal 0, payoff 2) that cannot be effectively countermoved by the focal dm. 151 | 152 | 153 | #DM prisoner 2 state 3 NASH 154 | state 4 (decimal 3, payoff 3) is NOT Nash stable for DM prisoner 2 since they have UIs available to: state 2 (decimal 1, payoff 4) 155 | 156 | #DM prisoner 2 state 3 SEQ 157 | From state 4 (decimal 3, payoff 3) prisoner 2 has UIs available to: state 2 (decimal 1, payoff 4) . Check for sanctioning... 158 | 159 | A move to state 2 (decimal 1, payoff 4) is SEQ sanctioned for focal DM prisoner 2 by a move to state 1 (decimal 0, payoff 2) by other dms. Check other focal DM UIs for sanctioning... 160 | 161 | state 4 (decimal 3, payoff 3) is stable by SEQ for focal dm prisoner 2, since all available UIs ['state 2 (decimal 1, payoff 4)'] are sanctioned by other players. 162 | 163 | 164 | #DM prisoner 2 state 3 SIM 165 | From state 4 (decimal 3, payoff 3) prisoner 2 has UIs available to: state 2 (decimal 1, payoff 4) . Check for sanctioning... 166 | 167 | A move to state 2 (decimal 1, payoff 4) is SIM sanctioned for focal DM prisoner 2 by a move to state 3 (decimal 2, payoff 1) by other DMs, which would give a final state of state 1 (decimal 0, payoff 2). Check other focal DM UIs for sanctioning... 168 | 169 | state 4 (decimal 3, payoff 3) is stable by SIM for focal DM prisoner 2, since all available UIs ['state 2 (decimal 1, payoff 4)'] are sanctioned by other players. 170 | 171 | 172 | #DM prisoner 2 state 3 GMR 173 | From state 4 (decimal 3, payoff 3) prisoner 2 has UIs available to: state 2 (decimal 1, payoff 4). Check for sanctioning... 174 | 175 | A move to state 2 (decimal 1, payoff 4) is GMR sanctioned for focal DM prisoner 2 by a move to state 1 (decimal 0, payoff 2) by other DMs. 176 | 177 | state 4 (decimal 3, payoff 3) is stable by GMR for focal DM prisoner 2, since all available UIs ['state 2 (decimal 1, payoff 4)']are sanctioned by other players. 178 | 179 | 180 | #DM prisoner 2 state 3 SMR 181 | From state 4 (decimal 3, payoff 3) prisoner 2 has UIs available to: state 2 (decimal 1, payoff 4) . Check for sanctioning... 182 | 183 | A move to state 2 (decimal 1, payoff 4) is SMR sanctioned for focal DM prisoner 2 by a move to state 1 (decimal 0, payoff 2) by other dms. Check for possible countermoves... 184 | 185 | state 2 (decimal 1, payoff 4) remains sanctioned under SMR for focal DM prisoner 2, since they cannot countermove their opponent's sanction to state 1 (decimal 0, payoff 2). 186 | 187 | state 4 (decimal 3, payoff 3) is stable by SMR for focal dm prisoner 2, since all available UIs ['state 2 (decimal 1, payoff 4)'] are sanctioned by other players and cannot be countermoved. 188 | 189 | -------------------------------------------------------------------------------- /test_data/SyriaIraq_logSol.txt: -------------------------------------------------------------------------------- 1 | 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 2 | 1.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 3 | 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 4 | 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 5 | 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 6 | 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 7 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import data_01_conflictModel 3 | import data_02_conflictSolvers 4 | import data_03_gmcrUtilities as util 5 | import data_04_spSolvers 6 | import numpy 7 | 8 | files = ["Garrison", 9 | "MilkRiver", 10 | "Prisoners", 11 | "SyriaIraq"] 12 | 13 | 14 | class TestStateSetMath(unittest.TestCase): 15 | """Tests on math to combine/subtract state sets.""" 16 | 17 | def test_reduce(self): 18 | """Test ability to reduce sets of states.""" 19 | # non-overlapping sets should not change. 20 | a1 = util.reducePatterns(["Y---", "---Y"]) 21 | self.assertEqual(a1, ["Y---", "---Y"]) 22 | 23 | # subsets should be removed. 24 | a2 = util.reducePatterns(["Y---", "YY--"]) 25 | self.assertEqual(a2, ["Y---"]) 26 | 27 | # larger example 28 | a3 = util.reducePatterns(["-Y---", "YY---", "---NY", "NNNY-"]) 29 | self.assertEqual(a3, ["-Y---", "---NY", "NNNY-"]) 30 | 31 | # If elements have different lengths, an exception should be raised. 32 | with self.assertRaises(ValueError): 33 | a4 = util.reducePatterns(["N-NN", "Y-N"]) 34 | 35 | # If something other than a list is provided, an exception should be raised. 36 | with self.assertRaises(TypeError): 37 | a5 = util.reducePatterns("N--,Y-N") 38 | 39 | def test_subtract(self): 40 | # basic subtraction example. 41 | a1 = util._subtractPattern("---", "NYN") 42 | self.assertEqual(a1, ['Y--', 'NN-', 'NYY']) 43 | 44 | # subtracting itself should give empty set. 45 | a2 = util._subtractPattern("Y--", "Y--") 46 | self.assertEqual(a2, []) 47 | 48 | # subtracting a superset should give an empty set. 49 | a3 = util._subtractPattern("-Y-", "---") 50 | self.assertEqual(a3, []) 51 | 52 | # If elements have different lengths, an exception should be raised. 53 | with self.assertRaises(ValueError): 54 | a4 = util._subtractPattern("-Y-", "Y---") 55 | with self.assertRaises(ValueError): 56 | a5 = util._subtractPattern("-Y--", "Y--") 57 | 58 | def test_subtractSingleFromGroup(self): 59 | # basic examples 60 | a1 = util.rmvSt(['-N-Y-', '-N-NN'], 'NNNY-') 61 | self.assertEqual(a1[0], ["YN-Y-", 'NNYY-', '-N-NN']) 62 | self.assertEqual(a1[1], 2) 63 | 64 | # Results must be presented in minimal form. 65 | a2 = util.rmvSt(['N----', 'YN---'], '-Y---') 66 | self.assertEqual(a2[0], ['-N---']) 67 | self.assertEqual(a2[1], 8) 68 | 69 | def test_groupSubtract(self): 70 | # basic examples 71 | a1 = util.subtractStateSets(['-----'], ["-Y---", "YY---", "---NY", "NNNY-"]) 72 | self.assertEqual(a1, ["YN-Y-", 'NNYY-', '-N-NN']) 73 | a2 = util.subtractStateSets(['N----', 'YN---'], ["-Y---", "---NY", "NNNY-"]) 74 | 75 | 76 | class TestSolvers(unittest.TestCase): 77 | 78 | def setUp(self): 79 | self.conf = data_01_conflictModel.ConflictModel() 80 | 81 | def test_fileLoading(self): 82 | self.assertTrue(len(self.conf.decisionMakers) == 0) 83 | self.assertTrue(len(self.conf.options) == 0) 84 | 85 | for file in files: 86 | self.conf.load_from_file("Examples/" + file + ".gmcr") 87 | self.assertTrue(len(self.conf.decisionMakers) > 0) 88 | self.assertTrue(len(self.conf.options) > 0) 89 | 90 | self.conf.__init__() 91 | self.assertTrue(len(self.conf.decisionMakers) == 0) 92 | self.assertTrue(len(self.conf.options) == 0) 93 | 94 | def test_logSol(self): 95 | for file in files: 96 | self.conf.load_from_file("Examples/" + file + ".gmcr") 97 | solver = data_02_conflictSolvers.LogicalSolver(self.conf) 98 | solver.findEquilibria() 99 | expected = numpy.loadtxt("test_data/" + file + "_logSol.txt") 100 | numpy.testing.assert_array_equal(expected, solver.allEquilibria, "Incorrect logical solution for " + file) 101 | 102 | def test_splogSol(self): 103 | for file in files: 104 | self.conf.load_from_file("Examples/" + file + ".gmcr") 105 | solver = data_04_spSolvers.LogicalSolver(self.conf) 106 | solver.findEquilibria() 107 | expected = numpy.loadtxt("test_data/" + file + "_logSol.txt") 108 | numpy.testing.assert_array_equal(expected, solver.allEquilibria, "Incorrect logical solution for " + file) 109 | 110 | # def test_narration(self): 111 | # for file in files: 112 | # self.conf.load_from_file("Examples/" + file + ".gmcr") 113 | # solver = data_02_conflictSolvers.LogicalSolver(self.conf) 114 | # narrOut = "" 115 | # for dm in self.conf.decisionMakers: 116 | # for state in self.conf.feasibles: 117 | # narrOut += "\n#DM %s state %s NASH\n"%(dm.name, state) 118 | # narrOut += solver.nash(dm, state)[1] 119 | # narrOut += "\n#DM %s state %s SEQ\n"%(dm.name, state) 120 | # narrOut += solver.seq(dm, state)[1] 121 | # narrOut += "\n#DM %s state %s SIM\n"%(dm.name, state) 122 | # narrOut += solver.sim(dm, state)[1] 123 | # narrOut += "\n#DM %s state %s GMR\n"%(dm.name, state) 124 | # narrOut += solver.gmr(dm, state)[1] 125 | # narrOut += "\n#DM %s state %s SMR\n"%(dm.name, state) 126 | # narrOut += solver.smr(dm, state)[1] 127 | # with open("test_data/" + file + "_narr.txt", "r") as f: 128 | # expected = f.read().splitlines() 129 | # generated = narrOut.splitlines() 130 | # self.assertEqual(expected, generated) # used for testing 131 | # # f.write(narrOut) #used to update expected test results 132 | 133 | def test_inverseSol(self): 134 | for file in files: 135 | self.conf.load_from_file("Examples/" + file + ".gmcr") 136 | varyRanges = [([0, min(len(dm.preferenceRanking) + 1, 4)] if len(dm.preferenceRanking) > 1 else [0, 0]) for dm in self.conf.decisionMakers] 137 | desEqs = range(min(5, len(self.conf.feasibles))) 138 | for desEq in desEqs: 139 | solver = data_02_conflictSolvers.InverseSolver(self.conf, varyRanges, desEq) 140 | solver.findEquilibria() 141 | expected = numpy.loadtxt("test_data/%s_%s_invSol.txt"%(file, desEq)) 142 | numpy.testing.assert_array_equal(expected, solver.equilibriums, "Incorrect inverse results for %s_%s"%(file, desEq)) 143 | 144 | 145 | if __name__ == "__main__": 146 | unittest.main() 147 | -------------------------------------------------------------------------------- /version.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.4.1' 2 | -------------------------------------------------------------------------------- /visualizerLauncher.py: -------------------------------------------------------------------------------- 1 | import webbrowser 2 | import sys 3 | from multiprocessing import Process 4 | import http.server 5 | import os 6 | 7 | 8 | class Fakestd(object): 9 | def write(self, string): 10 | pass 11 | 12 | def flush(self): 13 | pass 14 | 15 | 16 | def serve(): 17 | # required to successfully freeze multiprocessing to a win32gui application 18 | sys.stderr = Fakestd() 19 | sys.stdout = Fakestd() 20 | 21 | # move to the temp folder where the visualizer files were copied 22 | os.chdir(os.environ['TEMP'] + '/gmcr-vis') 23 | 24 | PORT = 8000 25 | Handler = http.server.SimpleHTTPRequestHandler 26 | httpd = http.server.HTTPServer(("", PORT), Handler) 27 | print("now serving on port ", PORT) 28 | httpd.serve_forever() 29 | 30 | 31 | def launchVis(data=None): 32 | serverThread = Process(target=serve, daemon=True) 33 | 34 | serverThread.start() 35 | 36 | webbrowser.open("http://127.0.0.1:8000", 2, True) 37 | -------------------------------------------------------------------------------- /widgets_f01_01_dmOptElements.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Widgets used for editing DMs and Options. 4 | 5 | Select DMs from a listbox, then edit name and options 6 | 7 | Loaded by the frame_01_decisionMakers module. 8 | """ 9 | 10 | from tkinter import N, S, E, W, VERTICAL, HORIZONTAL, END, StringVar, Listbox 11 | from tkinter import ttk 12 | 13 | NSEW = (N, S, E, W) 14 | 15 | 16 | class DMselector(ttk.Frame): 17 | """Listbox for DM Creation, Selection, and Sorting.""" 18 | 19 | def __init__(self, master, conflict): 20 | """Initialize DM selection/creation Widget.""" 21 | ttk.Frame.__init__(self, master) 22 | self.conflict = conflict 23 | self.dms = conflict.decisionMakers 24 | # variables 25 | self.listVariable = StringVar(value=tuple( 26 | ['Double Click to Add Item'])) 27 | self.selIdx = None 28 | self.selectedDM = None 29 | 30 | # widgets 31 | self.label = ttk.Label(self, text="Decision Makers") 32 | self.dmListDisp = Listbox(self, listvariable=self.listVariable) 33 | self.scrollY = ttk.Scrollbar(self, orient=VERTICAL, 34 | command=self.dmListDisp.yview) 35 | self.upBtn = ttk.Button(self, width=10, text='Up', command=self.upCmd) 36 | self.downBtn = ttk.Button(self, width=10, text='Down', 37 | command=self.downCmd) 38 | self.delBtn = ttk.Button(self, width=10, text='Delete', 39 | command=self.delCmd) 40 | 41 | # configuration 42 | self.dmListDisp.configure(yscrollcommand=self.scrollY.set) 43 | self.columnconfigure(0, weight=1) 44 | self.rowconfigure(1, weight=1) 45 | 46 | self.label.grid(column=0, row=0, columnspan=5, sticky=NSEW) 47 | self.dmListDisp.grid(column=0, row=1, columnspan=5, sticky=NSEW) 48 | self.scrollY.grid(column=5, row=1, sticky=NSEW) 49 | self.upBtn.grid(column=2, row=2, sticky=NSEW) 50 | self.downBtn.grid(column=3, row=2, sticky=NSEW) 51 | self.delBtn.grid(column=4, row=2, sticky=NSEW) 52 | 53 | self.dmListDisp.bind('<>', self.selChgCmd) 54 | self.dmListDisp.bind('', self.editCmd) 55 | self.dmListDisp.bind('', self.delCmd) 56 | self.dmListDisp.bind('', self.editCmd) 57 | 58 | def refresh(self, event=None): 59 | """Refresh widget contents.""" 60 | self.updateList() 61 | for idx in range(len(self.dms)): 62 | self.dmListDisp.itemconfigure(idx, foreground='black') 63 | self.dmListDisp.itemconfigure(len(self.dms), foreground='#A0A0A0') 64 | 65 | def updateList(self, event=None): 66 | """Update the value in the displayed listVariable.""" 67 | self.listVariable.set(tuple( 68 | self.dms.names() + ['Double Click to Add Item'])) 69 | 70 | def moveEntry(self, idx, idx2): 71 | """Move an item from idx to idx2.""" 72 | self.dms.insert(idx2, self.dms.pop(idx)) 73 | self.updateList() 74 | self.dmListDisp.selection_clear(idx) 75 | self.dmListDisp.selection_set(idx2) 76 | self.selChgCmd() 77 | self.event_generate("<>") 78 | 79 | def upCmd(self, event=None): 80 | """Move the selected element up one space in the list.""" 81 | idx = self.selIdx 82 | # check that there is an entry selected, and it isn't the first entry. 83 | if idx not in [-1, 0, len(self.dms)]: 84 | self.moveEntry(idx, idx - 1) 85 | 86 | def downCmd(self, event=None): 87 | """Move the selected element down one space in the list.""" 88 | idx = self.selIdx 89 | # check that there is an entry selected, and it isn't the last entry 90 | if idx not in [-1, len(self.dms) - 1, len(self.dms)]: 91 | self.moveEntry(idx, idx + 1) 92 | 93 | def delCmd(self, *args): 94 | """Delete the selected element from the list.""" 95 | idx = self.selIdx 96 | if idx != len(self.dms): # check that a valid entry is selected 97 | del self.dms[idx] 98 | self.refresh() 99 | self.event_generate("<>") 100 | 101 | def selChgCmd(self, *args): 102 | """Called when the selection changes.""" 103 | if len(self.dmListDisp.curselection()) == 0: 104 | self.selectedDM = self.conflict.decisionMakers[self.selIdx] 105 | else: 106 | self.selIdx = int(self.dmListDisp.curselection()[0]) 107 | if self.selIdx != len(self.conflict.decisionMakers): 108 | self.selectedDM = self.conflict.decisionMakers[self.selIdx] 109 | else: 110 | self.selectedDM = None 111 | self.event_generate('<>') 112 | 113 | def editCmd(self, *args): 114 | """Called when a list entry is selected for editing.""" 115 | self.event_generate('<>') 116 | 117 | def reselect(self, event=None): 118 | """Return selection focus to a dm after an action is completed.""" 119 | if self.selIdx is not None: 120 | self.dmListDisp.selection_set(self.selIdx) 121 | self.dmListDisp.event_generate('<>') 122 | 123 | 124 | class DMeditor(ttk.Frame): 125 | """Widget to change a DM's name and options.""" 126 | 127 | def __init__(self, master, conflict): 128 | """DM editor widget initialization.""" 129 | ttk.Frame.__init__(self, master) 130 | self.conflict = conflict 131 | 132 | # variables 133 | self.labelVar = StringVar() 134 | self.dmNameVar = StringVar() 135 | self.optionEditors = [] 136 | self.dm = None 137 | 138 | # widgets 139 | self.label = ttk.Label(self, textvariable=self.labelVar) 140 | self.dmFieldLabel = ttk.Label(self, text="DM Name:") 141 | self.dmNameEditor = ttk.Entry(self, textvariable=self.dmNameVar, 142 | validate='key') 143 | vcmd = self.dmNameEditor.register(self.dmNameMod) 144 | self.dmNameEditor.configure(validatecommand=(vcmd, '%P')) 145 | self.optionListFrame = ttk.Frame(self) 146 | self.newOptionBtn = ttk.Button(self, text="New Option", 147 | command=self.newOption) 148 | 149 | # configuration 150 | self.columnconfigure(1, weight=1) 151 | self.label.grid(column=0, row=0, columnspan=2, sticky=NSEW) 152 | ttk.Separator(self, orient=HORIZONTAL).grid( 153 | column=0, row=1, columnspan=2, sticky=NSEW, pady=3) 154 | self.dmFieldLabel.grid(column=0, row=2, sticky=NSEW) 155 | self.dmNameEditor.grid(column=1, row=2, sticky=NSEW) 156 | ttk.Separator(self, orient=HORIZONTAL).grid( 157 | column=0, row=3, columnspan=2, sticky=NSEW, pady=3) 158 | self.optionListFrame.grid(column=0, row=4, columnspan=2, sticky=NSEW) 159 | self.newOptionBtn.grid(column=0, row=5, sticky=NSEW) 160 | 161 | self.loadDM() 162 | 163 | def loadDM(self, dm=None): 164 | """Load a decision maker's data into the widget.""" 165 | self.dm = dm 166 | 167 | print('loading ' + str(self.dm)) 168 | 169 | for child in self.optionListFrame.winfo_children(): 170 | child.destroy() 171 | 172 | ttk.Frame(self.optionListFrame).grid(column=0, row=0) 173 | 174 | if dm is None: 175 | self.labelVar.set("Select a Decision Maker") 176 | self.dmNameVar.set("No DM Selected") 177 | self.dmNameEditor.configure(state='disabled') 178 | self.newOptionBtn.configure(state='disabled') 179 | return 180 | 181 | self.labelVar.set("Editing DM " + self.dm.name) 182 | self.dmNameVar.set(self.dm.name) 183 | self.dmNameEditor.configure(state='normal') 184 | self.newOptionBtn.configure(state='normal') 185 | 186 | self.optionVars = [] 187 | self.optionEditors = [] 188 | 189 | for idx, opt in enumerate(self.dm.options): 190 | self.addOption(opt) 191 | 192 | def dmNameMod(self, newName): 193 | """Change the decisionmaker's name.""" 194 | self.labelVar.set("Editing DM " + newName) 195 | self.dm.name = newName 196 | self.event_generate('<>') 197 | return True 198 | 199 | def addOption(self, opt): 200 | """Create an editing widget for the option.""" 201 | def optNameMod(newName): 202 | opt.name = newName 203 | return True 204 | 205 | def deleteOption(event=None): 206 | self.dm.options.remove(opt) 207 | self.loadDM(self.dm) 208 | self.event_generate('<>') 209 | 210 | newOptionVar = StringVar(value=opt.name) 211 | self.optionVars.append(newOptionVar) 212 | newOptionEditor = ttk.Entry(self.optionListFrame, 213 | textvariable=newOptionVar, validate='key') 214 | self.optionEditors.append(newOptionEditor) 215 | ovcmd = newOptionEditor.register(optNameMod) 216 | newOptionEditor.configure(validatecommand=(ovcmd, '%P')) 217 | newOptionEditor.grid(row=len(self.optionVars), column=0, sticky=NSEW) 218 | newOptionDelBtn = ttk.Button(self.optionListFrame, text="X", 219 | command=deleteOption) 220 | newOptionDelBtn.grid(row=len(self.optionVars), column=1, sticky=NSEW) 221 | 222 | def newOption(self): 223 | """Add an option to the decisionmaker's list.""" 224 | self.dm.options.append('New Option') 225 | self.loadDM(self.dm) 226 | self.event_generate('<>') 227 | self.optionEditors[-1].focus() 228 | self.optionEditors[-1].select_range(0, END) 229 | -------------------------------------------------------------------------------- /widgets_f02_01_radioButtonEntry.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Widgets for selecting a state using radio buttons for each option. 4 | 5 | Loaded by the frame_02_infeasibles module. 6 | """ 7 | 8 | from tkinter import Tk, Canvas, N, S, E, W, VERTICAL, StringVar 9 | from tkinter import ttk 10 | from data_01_conflictModel import ConflictModel 11 | 12 | NSEW = (N, S, E, W) 13 | 14 | 15 | class RadiobuttonSeries(ttk.Labelframe): 16 | """State entry for a single decision maker.""" 17 | 18 | def __init__(self, master=None, text=None, width=None, *args): 19 | """Initialize the widget.""" 20 | ttk.Labelframe.__init__(self, master, text=text, width=width, *args) 21 | self.columnconfigure(0, weight=1) 22 | 23 | self.options = [] 24 | self.stringVarList = [] 25 | 26 | self.yLabel = ttk.Label(self, text='Y ', anchor="w") 27 | self.nLabel = ttk.Label(self, text='N ', anchor="w") 28 | self.oLabel = ttk.Label(self, text='Open', anchor="w") 29 | 30 | self.yLabel.grid(column=1, row=0) 31 | self.nLabel.grid(column=2, row=0) 32 | self.oLabel.grid(column=3, row=0) 33 | 34 | self.placeholder = False 35 | 36 | self.setOpts(self.options) 37 | 38 | def setOpts(self, options, *args): 39 | """Create Radiobutton inputs for each Option for the target DM.""" 40 | if not options: 41 | self.placeholder = ttk.Label(self, text="This DM has no Options.") 42 | self.placeholder.grid(column=0, row=1, columnspan=4, sticky=NSEW) 43 | return None 44 | if self.placeholder: 45 | self.placeholder.grid_forget() 46 | self.options = options 47 | self.stringVarList = [] 48 | 49 | for idx, opt in enumerate(self.options): 50 | self.stringVarList.append(StringVar(value='-')) 51 | yb = ttk.Radiobutton(self, variable=self.stringVarList[idx], 52 | value='Y', command=self.chgEvent) 53 | nb = ttk.Radiobutton(self, variable=self.stringVarList[idx], 54 | value='N', command=self.chgEvent) 55 | ob = ttk.Radiobutton(self, variable=self.stringVarList[idx], 56 | value='-', command=self.chgEvent) 57 | name = ttk.Label(self, text=opt.name) 58 | 59 | yb.grid(column=1, row=int(idx + 1), padx=(15, 10), pady=5) 60 | nb.grid(column=2, row=int(idx + 1), padx=(15, 10)) 61 | ob.grid(column=3, row=int(idx + 1), padx=(15, 10)) 62 | name.grid(column=0, row=int(idx + 1)) 63 | 64 | def getStates(self, *args): 65 | """Get the input option selection in (idx,YN) format.""" 66 | states = [] 67 | for idx, bit in enumerate([x.get() for x in self.stringVarList]): 68 | if bit != '-': 69 | states.append((self.options[idx], bit)) 70 | return states 71 | 72 | def chgEvent(self): 73 | """Trigger an event in the master frame if a change is made.""" 74 | self.master.event_generate('<>') 75 | 76 | 77 | class RadiobuttonEntry(ttk.Frame): 78 | """State entry for the entire conflict. 79 | 80 | Uses a set of RadioButtonSeries elements. 81 | """ 82 | 83 | def __init__(self, master, conflict): 84 | """Initialize the widget.""" 85 | ttk.Frame.__init__(self, master) 86 | 87 | self.conflict = conflict 88 | 89 | self.rbeCanvas = Canvas(self) 90 | self.rdBtnFrame = ttk.Frame(self.rbeCanvas) 91 | self.scrollY = ttk.Scrollbar(self, orient=VERTICAL, 92 | command=self.rbeCanvas.yview) 93 | 94 | self.rbeCanvas.grid(column=0, row=0, columnspan=2, sticky=NSEW) 95 | self.scrollY.grid(column=2, row=0, sticky=NSEW) 96 | self.rbeCanvas.configure(yscrollcommand=self.scrollY.set) 97 | self.canvWindow = self.rbeCanvas.create_window( 98 | (0, 0), window=self.rdBtnFrame, anchor='nw') 99 | 100 | self.rowconfigure(0, weight=1) 101 | 102 | self.entryText = StringVar(value='') 103 | 104 | vcmd = self.register(self.onValidate) 105 | self.entryBx = ttk.Entry(self, textvariable=self.entryText, 106 | validate="key", 107 | validatecommand=(vcmd, '%S', '%P')) 108 | self.entryBx.grid(column=0, row=1, columnspan=2, sticky=NSEW) 109 | self.entryBx.bind('', self.generateAdd) 110 | 111 | self.warnText = StringVar(value='') 112 | 113 | self.addBtn = ttk.Button(self, text='Remove as Infeasible Condition', 114 | command=self.generateAdd) 115 | self.mutExBtn = ttk.Button(self, 116 | text='Remove as Mutually Exclusive Options', 117 | command=self.generateMutEx) 118 | self.warnLab = ttk.Label(self, textvariable=self.warnText) 119 | self.warnLab.grid(column=0, row=2, sticky=NSEW) 120 | self.addBtn.grid(column=0, row=3, columnspan=2, sticky=NSEW) 121 | self.mutExBtn.grid(column=0, row=4, columnspan=2, sticky=NSEW) 122 | 123 | self.reloadOpts() 124 | 125 | def resize(self, event=None): 126 | """Resize the scroll region of the main canvas element.""" 127 | self.rbeCanvas.configure(scrollregion=self.rbeCanvas.bbox("all")) 128 | self.rbeCanvas["width"] = self.rbeCanvas.bbox("all")[2] 129 | 130 | def generateAdd(self, *args): 131 | """Prompt response to addition of an infeasible state.""" 132 | self.event_generate('<>') 133 | 134 | def generateMutEx(self, *args): 135 | """Prompt response to addition of a mutually exclusive set.""" 136 | self.event_generate('<>') 137 | 138 | def reloadOpts(self): 139 | """Reload all options for all DMs.""" 140 | self.rbeCanvas.delete(self.canvWindow) 141 | self.rdBtnFrame.destroy() 142 | self.rdBtnFrame = ttk.Frame(self.rbeCanvas) 143 | self.canvWindow = self.rbeCanvas.create_window( 144 | (0, 0), window=self.rdBtnFrame, anchor='nw') 145 | self.rdBtnFrame.bind('<>', self.rdBtnChgCmd) 146 | self.rdBtnFrame.bind("", self.resize) 147 | 148 | self.rdBtnSrs = [] 149 | self.stringVarList = [] 150 | 151 | for x, dm in enumerate(self.conflict.decisionMakers): 152 | a = RadiobuttonSeries(self.rdBtnFrame, dm) 153 | self.rdBtnSrs.append(a) 154 | a.setOpts(dm.options) 155 | a.grid(column=0, row=int(x), sticky=NSEW) 156 | self.stringVarList += a.stringVarList 157 | 158 | self.rdBtnChgCmd() 159 | 160 | def setStates(self, dashOne): 161 | """Change the condition selected on the radio buttons.""" 162 | if len(dashOne) != len(self.stringVarList): 163 | raise Exception('string length does not match number ' 164 | 'of options: {}'.format(dashOne)) 165 | for x, y in enumerate(dashOne): 166 | self.stringVarList[x].set(y) 167 | self.entryText.set(dashOne) 168 | 169 | def getStates(self): 170 | """Get the condition selected on the radio buttons.""" 171 | states = [] 172 | for srs in self.rdBtnSrs: 173 | states.extend(srs.getStates()) 174 | return states 175 | 176 | def onValidate(self, chg, res): 177 | """Validate manually entered condition characters and length.""" 178 | if chg in ['Y', 'N', 'y', 'n', '-']: 179 | if len(res) < len(self.stringVarList): 180 | self.warnText.set('Entry too short') 181 | return True 182 | if len(res) == len(self.stringVarList): 183 | self.setStates(res.upper()) 184 | self.warnText.set('') 185 | return True 186 | return False 187 | 188 | def rdBtnChgCmd(self, *args): 189 | """Set the entry box value to match the radiobuttons.""" 190 | val = ''.join([x.get() for x in self.stringVarList]) 191 | self.entryText.set(val) 192 | 193 | # ###################### 194 | 195 | 196 | def main(): 197 | """Run widget in test window.""" 198 | root = Tk() 199 | root.columnconfigure(0, weight=1) 200 | root.rowconfigure(0, weight=1) 201 | 202 | g1 = ConflictModel('Prisoners.gmcr') 203 | 204 | radFrame = RadiobuttonEntry(root, g1) 205 | radFrame.grid(column=0, row=0, sticky=(N, W)) 206 | 207 | root.mainloop() 208 | 209 | print(radFrame.getStates()) 210 | 211 | 212 | if __name__ == '__main__': 213 | main() 214 | -------------------------------------------------------------------------------- /widgets_f02_02_infeasTreeview.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Treeview widget for displaying infeasible states removed. 4 | 5 | Loaded by the frame_02_infeasibles module. 6 | """ 7 | 8 | from tkinter import Tk, N, S, E, W, VERTICAL 9 | from tkinter import ttk 10 | from data_01_conflictModel import ConflictModel 11 | 12 | NSEW = (N, S, E, W) 13 | 14 | 15 | class TreeInfeas(ttk.Frame): 16 | """Treeview widget for displaying infeasible states removed.""" 17 | 18 | def __init__(self, master, conflict=None, *args): 19 | """Initialize the widget.""" 20 | ttk.Frame.__init__(self, master, padding=(5)) 21 | 22 | self.conflict = conflict 23 | 24 | self.tDisp = ttk.Treeview(self, columns=('state', 'stDes', 'stRem'), 25 | selectmode='browse') 26 | self.scrl = ttk.Scrollbar(self, orient=VERTICAL, 27 | command=self.tDisp.yview) 28 | 29 | self.upBtn = ttk.Button(self, width=10, text='Up', command=self.upCmd) 30 | self.downBtn = ttk.Button(self, width=10, text='Down', 31 | command=self.downCmd) 32 | self.delBtn = ttk.Button(self, width=10, text='Delete', 33 | command=self.delCmd) 34 | 35 | # ########## 36 | 37 | self.columnconfigure(0, weight=1) 38 | self.rowconfigure(0, weight=1) 39 | 40 | self.tDisp.heading('state', text='Infeasible State') 41 | self.tDisp.heading('stDes', text='# of States Described') 42 | self.tDisp.heading('stRem', text='# of States Removed') 43 | self.tDisp['show'] = 'headings' 44 | 45 | self.tDisp.grid(column=0, row=0, columnspan=5, sticky=NSEW) 46 | self.scrl.grid(column=5, row=0, sticky=NSEW) 47 | self.tDisp.configure(yscrollcommand=self.scrl.set) 48 | 49 | self.upBtn.grid(column=2, row=2, sticky=NSEW) 50 | self.downBtn.grid(column=3, row=2, sticky=NSEW) 51 | self.delBtn.grid(column=4, row=2, sticky=NSEW) 52 | 53 | self.tDisp.bind('<>', self.selChgCmd) 54 | 55 | self.refreshView() 56 | 57 | def refreshView(self): 58 | """Fully refreshes the list displayed.""" 59 | chldn = self.tDisp.get_children() 60 | for chld in chldn: 61 | self.tDisp.delete(chld) 62 | if len(self.conflict.infeasibles) > 0: 63 | self.conflict.recalculateFeasibleStates() 64 | for infeas in self.conflict.infeasibles: 65 | key = infeas.name 66 | self.tDisp.insert('', 'end', key, text=key) 67 | self.tDisp.set(key, 'state', key) 68 | self.tDisp.set(key, 'stDes', str(2**(key.count('-')))) 69 | self.tDisp.set(key, 'stRem', str(infeas.statesRemoved)) 70 | 71 | def selChgCmd(self, *args): 72 | """Called whenever the selection changes.""" 73 | self.tDisp.selId = self.tDisp.selection() 74 | self.tDisp.selIdx = self.tDisp.index(self.tDisp.selId) 75 | self.event_generate('<>', x=self.tDisp.selIdx) 76 | 77 | def upCmd(self, *args): 78 | """Called whenever an item is moved upwards.""" 79 | idx = self.tDisp.selIdx 80 | if idx != 0: 81 | self.conflict.infeasibles.moveCondition(idx, idx - 1) 82 | self.conflict.recalculateFeasibleStates() 83 | self.event_generate('<>') 84 | self.tDisp.selection_set(self.tDisp.selId) 85 | self.selChgCmd() 86 | 87 | def downCmd(self, *args): 88 | """Called whenever an item is moved downwards.""" 89 | idx = self.tDisp.selIdx 90 | if idx != len(self.conflict.infeasibles) - 1: 91 | self.conflict.infeasibles.moveCondition(idx, idx + 1) 92 | self.conflict.recalculateFeasibleStates() 93 | self.event_generate('<>') 94 | self.tDisp.selection_set(self.tDisp.selId) 95 | self.selChgCmd() 96 | 97 | def delCmd(self, *args): 98 | """Called when an item is deleted.""" 99 | idx = self.tDisp.selIdx 100 | self.conflict.infeasibles.removeCondition(idx) 101 | self.conflict.recalculateFeasibleStates() 102 | self.event_generate('<>') 103 | if len(self.conflict.infeasibles) > 0: 104 | try: 105 | self.tDisp.selection_set(self.conflict.infeasibles[idx].name) 106 | except IndexError: 107 | self.tDisp.selection_set( 108 | self.conflict.infeasibles[idx - 1].name) 109 | 110 | 111 | def main(): 112 | """Run widget in test window.""" 113 | root = Tk() 114 | root.columnconfigure(0, weight=1) 115 | root.rowconfigure(0, weight=1) 116 | 117 | g1 = ConflictModel('AmRv2.gmcr') 118 | 119 | theTree = TreeInfeas(root, g1) 120 | theTree.grid(column=0, row=0, sticky=NSEW) 121 | 122 | root.mainloop() 123 | 124 | if __name__ == '__main__': 125 | main() 126 | -------------------------------------------------------------------------------- /widgets_f02_03_feasDisp.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Widget for displaying all of the feasible states in the conflict. 4 | 5 | Loaded by the frame_02_infeasibles module. 6 | """ 7 | 8 | from tkinter import Tk, N, S, E, W, VERTICAL, StringVar, Listbox 9 | from tkinter import ttk 10 | from data_01_conflictModel import ConflictModel 11 | 12 | NSEW = (N, S, E, W) 13 | 14 | 15 | class FeasDisp(ttk.Frame): 16 | """Widget for displaying all of the feasible states in the conflict.""" 17 | 18 | def __init__(self, master=None, conflict=None, *args): 19 | """Initialize the widget.""" 20 | ttk.Frame.__init__(self, master, padding=5) 21 | self.columnconfigure(1, weight=1) 22 | self.rowconfigure(2, weight=1) 23 | 24 | self.conflict = conflict 25 | 26 | self.dispFormat = StringVar(value='pattern') 27 | self.dispList = StringVar() 28 | self.feasList = [] 29 | 30 | self.fmts = {'Pattern': 'YN-', 'List (YN)': 'YN', 31 | 'List (ordered and [decimal])': 'ord_dec'} 32 | cBoxOpts = ('Pattern', 'List (YN)', 'List (ordered and [decimal])') 33 | self.feasText = ttk.Label(self, text='Feasible States') 34 | self.feasText.grid(row=0, column=0, columnspan=3) 35 | self.cBox = ttk.Combobox(self, textvariable=self.dispFormat, 36 | values=cBoxOpts, state='readonly') 37 | self.cBoxLb = ttk.Label(self, text='Format:') 38 | self.feasLBx = Listbox(self, listvariable=self.dispList) 39 | self.scrl = ttk.Scrollbar(self, orient=VERTICAL, 40 | command=self.feasLBx.yview) 41 | 42 | # ########### 43 | self.cBoxLb.grid(column=0, row=1, sticky=NSEW, pady=3) 44 | self.cBox.grid(column=1, row=1, columnspan=2, sticky=NSEW, pady=3) 45 | self.feasLBx.grid(column=0, row=2, columnspan=2, sticky=NSEW) 46 | self.scrl.grid(column=2, row=2, sticky=NSEW) 47 | 48 | self.cBox.bind('<>', self.fmtSel) 49 | self.feasLBx.configure(yscrollcommand=self.scrl.set) 50 | 51 | self.dispFormat.set('Pattern') 52 | self.fmtSel() 53 | 54 | def fmtSel(self, *args): 55 | """Action on selection of a new format.""" 56 | self.refreshList() 57 | 58 | def setFeas(self, feasList): 59 | """Change the list of feasible states to be displayed.""" 60 | self.feasList = feasList 61 | self.refreshList() 62 | 63 | def refreshList(self): 64 | """Update the list of feasible states displayed and the format.""" 65 | fmt = self.fmts[self.dispFormat.get()] 66 | if fmt == "YN-": 67 | feas = self.conflict.feasibles.dash 68 | if fmt == "YN": 69 | feas = self.conflict.feasibles.yn 70 | if fmt == "ord_dec": 71 | feas = self.conflict.feasibles.ordDec 72 | self.dispList.set(tuple(feas)) 73 | 74 | 75 | def main(): 76 | """Run widget in test window.""" 77 | root = Tk() 78 | root.columnconfigure(0, weight=1) 79 | root.rowconfigure(0, weight=1) 80 | 81 | g1 = ConflictModel('Prisoners.gmcr') 82 | 83 | FeasView = FeasDisp(root, g1) 84 | FeasView.grid(column=0, row=0, sticky=NSEW) 85 | 86 | root.mainloop() 87 | 88 | if __name__ == '__main__': 89 | main() 90 | -------------------------------------------------------------------------------- /widgets_f02a_01_radioButtonEntry.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Widgets for selecting a state using radio buttons for each option. 4 | 5 | Loaded by the frame_02a_misperceptions module. 6 | Copied from a very similar widget used on the infeasibles screen. 7 | """ 8 | 9 | from tkinter import Tk, Canvas, N, S, E, W, VERTICAL, StringVar 10 | from tkinter import ttk 11 | from data_01_conflictModel import ConflictModel 12 | 13 | NSEW = (N, S, E, W) 14 | 15 | 16 | class RadiobuttonSeries(ttk.Labelframe): 17 | """State entry for a single decision maker.""" 18 | 19 | def __init__(self, master=None, text=None, width=None, *args): 20 | """Initialize the widget.""" 21 | ttk.Labelframe.__init__(self, master, text=text, width=width, *args) 22 | self.columnconfigure(0, weight=1) 23 | 24 | self.options = [] 25 | self.stringVarList = [] 26 | 27 | self.yLabel = ttk.Label(self, text='Y ', anchor="w") 28 | self.nLabel = ttk.Label(self, text='N ', anchor="w") 29 | self.oLabel = ttk.Label(self, text='Open', anchor="w") 30 | 31 | self.yLabel.grid(column=1, row=0) 32 | self.nLabel.grid(column=2, row=0) 33 | self.oLabel.grid(column=3, row=0) 34 | 35 | self.placeholder = False 36 | 37 | self.setOpts(self.options) 38 | 39 | def setOpts(self, options, *args): 40 | """Create Radiobutton inputs for each Option for the target DM.""" 41 | if not options: 42 | self.placeholder = ttk.Label(self, text="This DM has no Options.") 43 | self.placeholder.grid(column=0, row=1, columnspan=4, sticky=NSEW) 44 | return None 45 | if self.placeholder: 46 | self.placeholder.grid_forget() 47 | self.options = options 48 | self.stringVarList = [] 49 | 50 | for idx, opt in enumerate(self.options): 51 | self.stringVarList.append(StringVar(value='-')) 52 | yb = ttk.Radiobutton(self, variable=self.stringVarList[idx], 53 | value='Y', command=self.chgEvent) 54 | nb = ttk.Radiobutton(self, variable=self.stringVarList[idx], 55 | value='N', command=self.chgEvent) 56 | ob = ttk.Radiobutton(self, variable=self.stringVarList[idx], 57 | value='-', command=self.chgEvent) 58 | name = ttk.Label(self, text=opt.name) 59 | 60 | yb.grid(column=1, row=int(idx + 1), padx=(15, 10), pady=5) 61 | nb.grid(column=2, row=int(idx + 1), padx=(15, 10)) 62 | ob.grid(column=3, row=int(idx + 1), padx=(15, 10)) 63 | name.grid(column=0, row=int(idx + 1)) 64 | 65 | def getStates(self, *args): 66 | """Get the input option selection in (idx,YN) format.""" 67 | states = [] 68 | for idx, bit in enumerate([x.get() for x in self.stringVarList]): 69 | if bit != '-': 70 | states.append((self.options[idx], bit)) 71 | return states 72 | 73 | def chgEvent(self): 74 | """Trigger an event in the master frame if a change is made.""" 75 | self.master.event_generate('<>') 76 | 77 | 78 | class RadiobuttonEntry(ttk.Frame): 79 | """State entry for all DMs, and controls for adding the infeasibles. 80 | 81 | Uses a set of RadioButtonSeries elements. 82 | """ 83 | 84 | def __init__(self, master, conflict): 85 | """Initialize the widget.""" 86 | ttk.Frame.__init__(self, master) 87 | 88 | self.conflict = conflict 89 | 90 | self.dmLookup = {dm.name: dm for dm in self.conflict.decisionMakers} 91 | dmNames = tuple(self.dmLookup.keys()) 92 | self.activeDMname = StringVar(value=dmNames[0]) 93 | self.activeDM = self.dmLookup[dmNames[0]] 94 | 95 | dmSelLabel = ttk.Label(self, text="Decision Maker") 96 | dmSelLabel.grid(column=0, row=0) 97 | 98 | self.dmSelector = ttk.Combobox(self, textvariable=self.activeDMname, 99 | values=dmNames, state='readonly') 100 | self.dmSelector.grid(column=1, row=0, sticky=NSEW) 101 | self.dmSelector.bind('<>', self.dmSel) 102 | 103 | self.rbeCanvas = Canvas(self) 104 | self.rdBtnFrame = ttk.Frame(self.rbeCanvas) 105 | self.scrollY = ttk.Scrollbar(self, orient=VERTICAL, 106 | command=self.rbeCanvas.yview) 107 | 108 | self.rbeCanvas.grid(column=0, row=1, columnspan=2, sticky=NSEW) 109 | self.scrollY.grid(column=2, row=1, sticky=NSEW) 110 | self.rbeCanvas.configure(yscrollcommand=self.scrollY.set) 111 | self.canvWindow = self.rbeCanvas.create_window( 112 | (0, 0), window=self.rdBtnFrame, anchor='nw') 113 | 114 | self.rowconfigure(1, weight=1) 115 | 116 | self.entryText = StringVar(value='') 117 | 118 | vcmd = self.register(self.onValidate) 119 | self.entryBx = ttk.Entry(self, textvariable=self.entryText, 120 | validate="key", 121 | validatecommand=(vcmd, '%S', '%P')) 122 | self.entryBx.grid(column=0, row=2, columnspan=2, sticky=NSEW) 123 | self.entryBx.bind('', self.generateAdd) 124 | 125 | self.warnText = StringVar(value='') 126 | 127 | self.addBtn = ttk.Button(self, text='Remove as Misperceived Condition', 128 | command=self.generateAdd) 129 | self.mutExBtn = ttk.Button(self, 130 | text='Perceived as Mutually Exclusive', 131 | command=self.generateMutEx) 132 | self.warnLab = ttk.Label(self, textvariable=self.warnText) 133 | self.warnLab.grid(column=0, row=3, sticky=NSEW) 134 | self.addBtn.grid(column=0, row=4, columnspan=2, sticky=NSEW) 135 | self.mutExBtn.grid(column=0, row=5, columnspan=2, sticky=NSEW) 136 | 137 | self.reloadOpts() 138 | 139 | def resize(self, event=None): 140 | """Resize the scroll region of the main canvas element.""" 141 | self.rbeCanvas.configure(scrollregion=self.rbeCanvas.bbox("all")) 142 | self.rbeCanvas["width"] = self.rbeCanvas.bbox("all")[2] 143 | 144 | def generateAdd(self, *args): 145 | """Prompt response to addition of an infeasible state.""" 146 | self.event_generate('<>') 147 | 148 | def generateMutEx(self, *args): 149 | """Prompt response to addition of a mutually exclusive set.""" 150 | self.event_generate('<>') 151 | 152 | def reloadOpts(self): 153 | """Reload all options for all DMs.""" 154 | self.rbeCanvas.delete(self.canvWindow) 155 | self.rdBtnFrame.destroy() 156 | self.rdBtnFrame = ttk.Frame(self.rbeCanvas) 157 | self.canvWindow = self.rbeCanvas.create_window( 158 | (0, 0), window=self.rdBtnFrame, anchor='nw') 159 | self.rdBtnFrame.bind('<>', self.rdBtnChgCmd) 160 | self.rdBtnFrame.bind("", self.resize) 161 | 162 | self.rdBtnSrs = [] 163 | self.stringVarList = [] 164 | 165 | for x, dm in enumerate(self.conflict.decisionMakers): 166 | a = RadiobuttonSeries(self.rdBtnFrame, dm) 167 | self.rdBtnSrs.append(a) 168 | a.setOpts(dm.options) 169 | a.grid(column=0, row=int(x), sticky=NSEW) 170 | self.stringVarList += a.stringVarList 171 | 172 | self.rdBtnChgCmd() 173 | 174 | def setStates(self, dashOne): 175 | """Change the condition selected on the radio buttons.""" 176 | if len(dashOne) != len(self.stringVarList): 177 | raise Exception('string length does not match number ' 178 | 'of options: {}'.format(dashOne)) 179 | for x, y in enumerate(dashOne): 180 | self.stringVarList[x].set(y) 181 | self.entryText.set(dashOne) 182 | 183 | def getStates(self): 184 | """Get the condition selected on the radio buttons.""" 185 | states = [] 186 | for srs in self.rdBtnSrs: 187 | states.extend(srs.getStates()) 188 | return states 189 | 190 | def onValidate(self, chg, res): 191 | """Validate manually entered condition characters and length.""" 192 | if chg in ['Y', 'N', 'y', 'n', '-']: 193 | if len(res) < len(self.stringVarList): 194 | self.warnText.set('Entry too short') 195 | return True 196 | if len(res) == len(self.stringVarList): 197 | self.setStates(res.upper()) 198 | self.warnText.set('') 199 | return True 200 | return False 201 | 202 | def rdBtnChgCmd(self, *args): 203 | """Set the entry box value to match the radiobuttons.""" 204 | val = ''.join([x.get() for x in self.stringVarList]) 205 | self.entryText.set(val) 206 | 207 | def dmSel(self, *args): 208 | """Prompt response to a different DM being selected.""" 209 | dmName = self.activeDMname.get() 210 | self.activeDM = self.dmLookup[dmName] 211 | self.event_generate('<>') 212 | 213 | # ###################### 214 | 215 | 216 | def main(): 217 | """Run widget in test window.""" 218 | root = Tk() 219 | root.columnconfigure(0, weight=1) 220 | root.rowconfigure(0, weight=1) 221 | 222 | g1 = ConflictModel() 223 | g1.load_from_file('Examples/SyriaIraq.gmcr') 224 | 225 | radFrame = RadiobuttonEntry(root, g1) 226 | radFrame.grid(column=0, row=0, sticky=(N, W)) 227 | 228 | root.mainloop() 229 | 230 | print(radFrame.getStates()) 231 | 232 | 233 | if __name__ == '__main__': 234 | main() 235 | -------------------------------------------------------------------------------- /widgets_f02a_02_infeasTreeview.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Treeview widget for displaying misperceived states removed. 4 | 5 | Loaded by the frame_02_infeasibles module. 6 | Copied from a very similar widget used on the infeasibles screen. 7 | """ 8 | 9 | from tkinter import Tk, N, S, E, W, VERTICAL 10 | from tkinter import ttk 11 | from data_01_conflictModel import ConflictModel 12 | 13 | NSEW = (N, S, E, W) 14 | 15 | 16 | class TreeInfeas(ttk.Frame): 17 | """Treeview widget for displaying infeasible states removed.""" 18 | 19 | def __init__(self, master, conflict=None, *args): 20 | """Initialize the widget.""" 21 | ttk.Frame.__init__(self, master, padding=(5)) 22 | 23 | self.conflict = conflict 24 | 25 | self.tDisp = ttk.Treeview(self, columns=('state', 'stDes', 'stRem'), 26 | selectmode='browse') 27 | self.scrl = ttk.Scrollbar(self, orient=VERTICAL, 28 | command=self.tDisp.yview) 29 | 30 | self.upBtn = ttk.Button(self, width=10, text='Up', command=self.upCmd) 31 | self.downBtn = ttk.Button(self, width=10, text='Down', 32 | command=self.downCmd) 33 | self.delBtn = ttk.Button(self, width=10, text='Delete', 34 | command=self.delCmd) 35 | 36 | # ########## 37 | 38 | self.columnconfigure(0, weight=1) 39 | self.rowconfigure(0, weight=1) 40 | 41 | self.tDisp.heading('state', text='Misperceived State') 42 | self.tDisp.heading('stDes', text='# of States Described') 43 | self.tDisp.heading('stRem', text='# of States Removed') 44 | self.tDisp['show'] = 'headings' 45 | 46 | self.tDisp.grid(column=0, row=0, columnspan=5, sticky=NSEW) 47 | self.scrl.grid(column=5, row=0, sticky=NSEW) 48 | self.tDisp.configure(yscrollcommand=self.scrl.set) 49 | 50 | self.upBtn.grid(column=2, row=2, sticky=NSEW) 51 | self.downBtn.grid(column=3, row=2, sticky=NSEW) 52 | self.delBtn.grid(column=4, row=2, sticky=NSEW) 53 | 54 | self.tDisp.bind('<>', self.selChgCmd) 55 | 56 | def refresh(self): 57 | """Fully refreshes the list displayed.""" 58 | chldn = self.tDisp.get_children() 59 | for chld in chldn: 60 | self.tDisp.delete(chld) 61 | for misp in self.activeDM.misperceptions: 62 | key = misp.name 63 | self.tDisp.insert('', 'end', key, text=key) 64 | self.tDisp.set(key, 'state', key) 65 | self.tDisp.set(key, 'stDes', str(2**(key.count('-')))) 66 | self.tDisp.set(key, 'stRem', str(misp.statesRemoved)) 67 | 68 | def selChgCmd(self, *args): 69 | """Called whenever the selection changes.""" 70 | self.tDisp.selId = self.tDisp.selection() 71 | self.tDisp.selIdx = self.tDisp.index(self.tDisp.selId) 72 | self.event_generate('<>', x=self.tDisp.selIdx) 73 | 74 | def upCmd(self, *args): 75 | """Called whenever an item is moved upwards.""" 76 | idx = self.tDisp.selIdx 77 | if idx != 0: 78 | self.activeDM.misperceptions.moveCondition(idx, idx - 1) 79 | self.activeDM.calculatePerceived() 80 | self.event_generate('<>') 81 | self.tDisp.selection_set(self.tDisp.selId) 82 | self.selChgCmd() 83 | 84 | def downCmd(self, *args): 85 | """Called whenever an item is moved downwards.""" 86 | idx = self.tDisp.selIdx 87 | if idx != len(self.conflict.infeasibles) - 1: 88 | self.activeDM.misperceptions.moveCondition(idx, idx + 1) 89 | self.activeDM.calculatePerceived() 90 | self.event_generate('<>') 91 | self.tDisp.selection_set(self.tDisp.selId) 92 | self.selChgCmd() 93 | 94 | def delCmd(self, *args): 95 | """Called when an item is deleted.""" 96 | idx = self.tDisp.selIdx 97 | self.activeDM.misperceptions.removeCondition(idx) 98 | self.activeDM.calculatePerceived() 99 | self.event_generate('<>') 100 | if len(self.activeDM.misperceptions) > 0: 101 | try: 102 | self.tDisp.selection_set( 103 | self.activeDM.misperceptions[idx].name) 104 | except IndexError: 105 | self.tDisp.selection_set( 106 | self.activeDM.misperceptions[idx - 1].name) 107 | 108 | 109 | def main(): 110 | """Run widget in test window.""" 111 | root = Tk() 112 | root.columnconfigure(0, weight=1) 113 | root.rowconfigure(0, weight=1) 114 | 115 | g1 = ConflictModel() 116 | g1.load_from_file('Examples/SyriaIraq.gmcr') 117 | 118 | theTree = TreeInfeas(root, g1) 119 | theTree.grid(column=0, row=0, sticky=NSEW) 120 | 121 | root.mainloop() 122 | 123 | if __name__ == '__main__': 124 | main() 125 | -------------------------------------------------------------------------------- /widgets_f02a_03_feasDisp.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Widget for displaying all of the perceived states for a DM. 4 | 5 | Loaded by the frame_02a_misperceptions module. 6 | Copied from a very similar widget used on the infeasibles screen. 7 | """ 8 | 9 | from tkinter import Tk, N, S, E, W, VERTICAL, StringVar, Listbox 10 | from tkinter import ttk 11 | from data_01_conflictModel import ConflictModel 12 | 13 | NSEW = (N, S, E, W) 14 | 15 | 16 | class FeasDisp(ttk.Frame): 17 | """Widget for displaying all of the feasible states in the conflict.""" 18 | 19 | def __init__(self, master=None, conflict=None, *args): 20 | """Initialize the widget.""" 21 | ttk.Frame.__init__(self, master, padding=5) 22 | self.columnconfigure(1, weight=1) 23 | self.rowconfigure(2, weight=1) 24 | 25 | self.conflict = conflict 26 | 27 | self.dispFormat = StringVar(value='pattern') 28 | self.dispList = StringVar() 29 | self.feasList = [] 30 | 31 | self.fmts = {'Pattern': 'YN-', 'List (YN)': 'YN', 32 | 'List (ordered and [decimal])': 'ord_dec'} 33 | cBoxOpts = ('Pattern', 'List (YN)', 'List (ordered and [decimal])') 34 | self.feasText = ttk.Label(self, text='Perceived States') 35 | self.feasText.grid(row=0, column=0, columnspan=3) 36 | self.cBox = ttk.Combobox(self, textvariable=self.dispFormat, 37 | values=cBoxOpts, state='readonly') 38 | self.cBoxLb = ttk.Label(self, text='Format:') 39 | self.feasLBx = Listbox(self, listvariable=self.dispList) 40 | self.scrl = ttk.Scrollbar(self, orient=VERTICAL, 41 | command=self.feasLBx.yview) 42 | 43 | # ########### 44 | self.cBoxLb.grid(column=0, row=1, sticky=NSEW, pady=3) 45 | self.cBox.grid(column=1, row=1, columnspan=2, sticky=NSEW, pady=3) 46 | self.feasLBx.grid(column=0, row=2, columnspan=2, sticky=NSEW) 47 | self.scrl.grid(column=2, row=2, sticky=NSEW) 48 | 49 | self.cBox.bind('<>', self.fmtSel) 50 | self.feasLBx.configure(yscrollcommand=self.scrl.set) 51 | 52 | self.dispFormat.set('Pattern') 53 | 54 | def fmtSel(self, *args): 55 | """Action on selection of a new format.""" 56 | self.refresh() 57 | 58 | def setFeas(self, feasList): 59 | """Change the list of feasible states to be displayed.""" 60 | self.feasList = feasList 61 | self.refresh() 62 | 63 | def refresh(self): 64 | """Update the list of perceived states displayed and the format.""" 65 | self.activeDM.calculatePerceived() 66 | fmt = self.fmts[self.dispFormat.get()] 67 | if fmt == "YN-": 68 | perc = self.activeDM.perceived.dash 69 | if fmt == "YN": 70 | perc = self.activeDM.perceived.yn 71 | if fmt == "ord_dec": 72 | perc = self.activeDM.perceived.ordDec 73 | self.dispList.set(tuple(perc)) 74 | 75 | 76 | def main(): 77 | """Run widget in test window.""" 78 | root = Tk() 79 | root.columnconfigure(0, weight=1) 80 | root.rowconfigure(0, weight=1) 81 | 82 | g1 = ConflictModel('Prisoners.gmcr') 83 | 84 | FeasView = FeasDisp(root, g1) 85 | FeasView.grid(column=0, row=0, sticky=NSEW) 86 | 87 | root.mainloop() 88 | 89 | if __name__ == '__main__': 90 | main() 91 | -------------------------------------------------------------------------------- /widgets_f03_01_irreversibleToggles.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Widgets for editing the reversibility of options in the conflict. 4 | 5 | Loaded by the frame_03_irreversibles module. 6 | """ 7 | 8 | from tkinter import * 9 | from tkinter import ttk 10 | from data_01_conflictModel import ConflictModel 11 | 12 | 13 | class ToggleButton(ttk.Labelframe): 14 | def __init__(self,master,option,*args): 15 | ttk.Frame.__init__(self,master,*args) 16 | self.columnconfigure(0,weight=1) 17 | 18 | self.option = option 19 | 20 | self.fwdIcon=PhotoImage(file='icons/fwdArrow.gif') 21 | self.backIcon=PhotoImage(file='icons/backArrow.gif') 22 | self.bothIcon=PhotoImage(file='icons/bothArrow.gif') 23 | 24 | ttk.Label(self,text=option.name).grid(column=0,row=0,sticky=(N,S,E,W)) 25 | ttk.Label(self,text=' N ').grid(column=1,row=0,sticky=(N,S,E,W)) 26 | ttk.Label(self,text=' Y ').grid(column=3,row=0,sticky=(N,S,E,W)) 27 | 28 | self.fwd = ttk.Button(self,image=self.fwdIcon, command=lambda: self.chg('back')) 29 | self.back = ttk.Button(self,image=self.backIcon,command=lambda: self.chg('both')) 30 | self.both = ttk.Button(self,image=self.bothIcon,command=lambda: self.chg('fwd')) 31 | 32 | self.both.grid(column=2,row=0,sticky=(N,S,E,W)) 33 | self.curr = self.both 34 | 35 | self.chg(option.permittedDirection) 36 | 37 | def chg(self,new): 38 | self.curr.grid_remove() 39 | if new == 'back': 40 | self.back.grid(column=2,row=0,sticky=(N,S,E,W)) 41 | self.curr= self.back 42 | self.option.permittedDirection = 'back' 43 | elif new == 'fwd': 44 | self.fwd.grid(column=2,row=0,sticky=(N,S,E,W)) 45 | self.curr = self.fwd 46 | self.option.permittedDirection = 'fwd' 47 | elif new == 'both': 48 | self.both.grid(column=2,row=0,sticky=(N,S,E,W)) 49 | self.curr= self.both 50 | self.option.permittedDirection = 'both' 51 | 52 | 53 | class IrreversibleSetter(Frame): 54 | def __init__(self,master,conflict): 55 | ttk.Frame.__init__(self,master) 56 | 57 | self.opts= [] 58 | self.conflict = conflict 59 | 60 | self.canv = Canvas(self) 61 | 62 | self.mainFrame = ttk.Frame(self.canv) 63 | self.scrollY = ttk.Scrollbar(self, orient=VERTICAL,command = self.canv.yview) 64 | self.canvWindow = self.canv.create_window((0,0),window=self.mainFrame,anchor='nw') 65 | 66 | self.canv.grid(column=0,row=0,sticky=(N,S,E,W)) 67 | self.scrollY.grid(column=1,row=0,sticky=(N,S,E,W)) 68 | self.canv.configure(yscrollcommand=self.scrollY.set) 69 | 70 | self.rowconfigure(0, weight=1) 71 | self.mainFrame.bind("",self.resize) 72 | 73 | self.refreshDisplay() 74 | 75 | def resize(self,event=None): 76 | self.canv.configure(scrollregion=self.canv.bbox("all")) 77 | self.canv["width"] = self.canv.bbox("all")[2] 78 | 79 | def refreshDisplay(self): 80 | self.opts=[] 81 | for chld in self.mainFrame.winfo_children(): 82 | chld.destroy() 83 | for dmIdx,dm in enumerate(self.conflict.decisionMakers): 84 | currframe = ttk.LabelFrame(self.mainFrame,text=dm.name) 85 | currframe.grid(column=0,row=dmIdx,sticky=(N,S,E,W)) 86 | currframe.columnconfigure(0,weight=1) 87 | for optIdx,opt in enumerate(dm.options): 88 | newtog = ToggleButton(currframe,opt) 89 | newtog.grid(column=0,row=optIdx,sticky=(N,S,E,W)) 90 | self.opts.append(newtog) 91 | 92 | 93 | 94 | 95 | 96 | 97 | # ###################### 98 | 99 | def main(): 100 | root = Tk() 101 | root.columnconfigure(0,weight=1) 102 | root.rowconfigure(0,weight=1) 103 | 104 | g1 = ConflictModel('Prisoners.gmcr') 105 | 106 | f1 = IrreversibleSetter(root,g1) 107 | f1.grid(column=0,row=0,sticky=(N,S,E,W)) 108 | 109 | root.mainloop() 110 | 111 | 112 | if __name__ == '__main__': 113 | main() -------------------------------------------------------------------------------- /widgets_f04_03_optionForm.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Various widgets used in editing and displaying prioritization-based preferences. 4 | 5 | Loaded by the frame_04_preferencePrioritization module. 6 | """ 7 | 8 | from tkinter import * 9 | from tkinter import ttk 10 | import numpy 11 | 12 | class OptionFormTable(ttk.Frame): 13 | def __init__(self,master,conflict): 14 | ttk.Frame.__init__(self,master) 15 | 16 | self.conflict = conflict 17 | 18 | #widgets 19 | self.tableCanvas = Canvas(self) 20 | self.table = ttk.Frame(self.tableCanvas) 21 | self.scrollY = ttk.Scrollbar(self, orient=VERTICAL,command = self.tableCanvas.yview) 22 | self.scrollX = ttk.Scrollbar(self, orient=HORIZONTAL,command = self.tableCanvas.xview) 23 | 24 | #configuration 25 | self.columnconfigure(0,weight=1) 26 | self.rowconfigure(0,weight=1) 27 | 28 | def resize(event): 29 | self.tableCanvas.configure(scrollregion=self.tableCanvas.bbox("all")) 30 | 31 | self.tableCanvas.grid(column=0,row=0,sticky=(N,S,E,W)) 32 | self.scrollY.grid(column=1,row=0,sticky=(N,S,E,W)) 33 | self.scrollX.grid(column=0,row=1,sticky=(N,S,E,W)) 34 | self.tableCanvas.configure(yscrollcommand=self.scrollY.set) 35 | self.tableCanvas.configure(xscrollcommand=self.scrollX.set) 36 | self.tableCanvas.create_window((0,0),window=self.table,anchor='nw') 37 | self.table.bind("",resize) 38 | 39 | self.style = ttk.Style() 40 | # styles for cells in the Option form table. CHANGE COLOURS HERE. 41 | self.style.configure('states.TLabel',background="grey80") 42 | self.style.configure('yn.TLabel',background="white") 43 | self.style.configure('payoffs.TLabel',background="grey80") 44 | self.style.configure('hover.TLabel',background="lightpink") 45 | 46 | self.buildTable() 47 | 48 | def buildTable(self,focusDM=None): 49 | if len(self.conflict.feasibles)>1000: 50 | note = ttk.Label(self.table,text="More than 1000 states. No table will be drawn") 51 | note.grid(row=1,column=1,sticky=(N,S,E,W)) 52 | return 53 | 54 | 55 | if focusDM is not None: 56 | rowCount = len(self.conflict.options)+3 57 | else: 58 | rowCount = len(self.conflict.options)+len(self.conflict.decisionMakers)+2 59 | 60 | columnCount = len(self.conflict.feasibles)+2 61 | tableData = numpy.zeros((rowCount,columnCount),dtype="=2): 137 | columns[col].append([newEntry,tag]) 138 | newEntry.bind("", enterCell) 139 | newEntry.bind("", exitCell) 140 | elif (row >= 2) and (col == 1): 141 | rows[row].append([newEntry,tag]) 142 | newEntry.bind("", enterCell) 143 | newEntry.bind("", exitCell) 144 | elif (col >= 2) and (row >= 2): 145 | rows[row].append([newEntry,tag]) 146 | columns[col].append([newEntry,tag]) 147 | newEntry.bind("", enterCell) 148 | newEntry.bind("", exitCell) 149 | -------------------------------------------------------------------------------- /widgets_f05_01_PrefRank.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Widgets used for validating and manually entering state rankings. 4 | 5 | Loaded by the frame_05_preferenceRanking module. 6 | """ 7 | 8 | from tkinter import StringVar, N, S, E, W, Text 9 | from tkinter import ttk 10 | from ast import literal_eval 11 | import data_03_gmcrUtilities as gmcrUtil 12 | 13 | NSEW = (N, S, E, W) 14 | 15 | 16 | class RankingEditor(ttk.Frame): 17 | """Display and edit the state ranking for a single Decision Maker.""" 18 | 19 | def __init__(self, master, conflict, dm): 20 | """Initialize a RankingEditor widget for a decision maker.""" 21 | ttk.Frame.__init__(self, master, borderwidth=2) 22 | 23 | self.master = master 24 | self.conflict = conflict 25 | self.dm = dm 26 | 27 | self.columnconfigure(1, weight=1) 28 | 29 | self.dmText = StringVar(value=dm.name + ': ') 30 | self.dmLabel = ttk.Label(self, textvariable=self.dmText, width=20) 31 | self.dmLabel.grid(row=0, column=0, sticky=NSEW) 32 | 33 | self.prefRankVar = StringVar(value=str(dm.perceivedRanking)) 34 | self.prefRankEntry = ttk.Entry(self, textvariable=self.prefRankVar) 35 | self.prefRankEntry.grid(row=0, column=1, sticky=NSEW) 36 | 37 | self.errorDetails = None 38 | 39 | self.prefRankEntry.bind("", self.onFocusOut) 40 | 41 | def onFocusOut(self, event): 42 | """Validate the preference ranking when focus leaves the widget.""" 43 | try: 44 | perceivedRank = literal_eval(self.prefRankVar.get()) 45 | except SyntaxError: 46 | self.errorDetails = ("DM {}'s preference ranking is " 47 | "invalid.").format(self.dm.name) 48 | self.master.event_generate("<>") 49 | return 50 | except NameError: 51 | self.errorDetails = ("DM {}'s preference ranking is " 52 | "invalid.").format(self.dm.name) 53 | self.master.event_generate("<>") 54 | return 55 | prefRank = perceivedRank + self.dm.misperceived 56 | self.errorDetails = gmcrUtil.validatePreferenceRanking( 57 | prefRank, self.conflict.feasibles) 58 | if self.errorDetails: 59 | self.errorDetails += (" Check DM {}'s preference " 60 | "ranking.").format(self.dm.name) 61 | self.master.event_generate("<>") 62 | return 63 | self.dm.preferenceRanking = prefRank 64 | self.dm.calculatePreferences() 65 | self.master.event_generate("<>") 66 | 67 | def enableWidget(self): 68 | """Enable editting of the ranking.""" 69 | self.prefRankEntry['state'] = 'normal' 70 | 71 | def disableWidget(self): 72 | """Disable editting of the ranking.""" 73 | self.prefRankEntry['state'] = 'disabled' 74 | 75 | 76 | class PRankEditMaster(ttk.Frame): 77 | """Contains a RankingEditor for each DM, plus an error box.""" 78 | 79 | def __init__(self, master, conflict): 80 | """Initialize a preference ranking master widget for the conflict.""" 81 | ttk.Frame.__init__(self, master, borderwidth=2) 82 | 83 | self.conflict = conflict 84 | 85 | self.columnconfigure(0, weight=1) 86 | 87 | self.activateButton = ttk.Button( 88 | self, 89 | text="Press to enable manual preference ranking changes", 90 | command=self.enableEditing) 91 | self.activateButton.grid(row=0, column=0, sticky=NSEW) 92 | 93 | self.editorFrame = ttk.Frame(self) 94 | self.editorFrame.grid(row=2, column=0, sticky=NSEW) 95 | self.editorFrame.columnconfigure(0, weight=1) 96 | 97 | self.errorDisplay = Text(self, height=6) 98 | self.errorDisplay['state'] = 'disabled' 99 | self.errorDisplay.grid(row=3, column=0, sticky=(N, E, W)) 100 | 101 | self.editorFrame.bind('<>', self.updateErrors) 102 | 103 | def refresh(self): 104 | """Refresh the widget and all children.""" 105 | for child in self.editorFrame.winfo_children(): 106 | child.destroy() 107 | 108 | self.rankingEditors = [] 109 | 110 | for idx, dm in enumerate(self.conflict.decisionMakers): 111 | newEditor = RankingEditor(self.editorFrame, self.conflict, dm) 112 | self.rankingEditors.append(newEditor) 113 | newEditor.grid(row=idx, column=0, sticky=NSEW) 114 | 115 | self.updateErrors() 116 | 117 | if not self.conflict.useManualPreferenceRanking: 118 | self.activateButton['text'] = ("Click to enable manual preference " 119 | "ranking changes") 120 | self.activateButton['state'] = 'normal' 121 | for editor in self.rankingEditors: 122 | editor.disableWidget() 123 | else: 124 | self.activateButton['text'] = ("Preference rankings entered below " 125 | "will be used in analysis.") 126 | self.activateButton['state'] = 'disabled' 127 | 128 | def enableEditing(self): 129 | """Switch on manual editing of the preference rankings.""" 130 | self.activateButton['text'] = ("Preference rankings entered below " 131 | "will be used in analysis.") 132 | self.activateButton['state'] = 'disabled' 133 | for editor in self.rankingEditors: 134 | editor.enableWidget() 135 | self.conflict.useManualPreferenceRanking = True 136 | 137 | def updateErrors(self, event=None): 138 | """Update the messages in the error box.""" 139 | messages = [editor.errorDetails for editor in self.rankingEditors 140 | if editor.errorDetails] 141 | self.conflict.preferenceErrors = len(messages) 142 | 143 | self.errorDisplay['state'] = 'normal' 144 | self.errorDisplay.delete('1.0', 'end') 145 | if len(messages) > 0: 146 | text = '\n'.join(messages) 147 | self.errorDisplay.insert('1.0', text) 148 | self.errorDisplay['foreground'] = 'red' 149 | else: 150 | self.errorDisplay.insert( 151 | '1.0', "No Errors. Preference rankings are valid.") 152 | self.errorDisplay['foreground'] = 'black' 153 | self.event_generate("<>") 154 | self.errorDisplay['state'] = 'disabled' 155 | -------------------------------------------------------------------------------- /widgets_f08_01_stabilityAnalysis.py: -------------------------------------------------------------------------------- 1 | # Copyright: (c) Oskar Petersons 2013 2 | 3 | """Compound widgets for Stability Analysis screen. 4 | 5 | Loaded by the frame_08_stabilityAnalysis module. 6 | """ 7 | 8 | from tkinter import * 9 | from tkinter import ttk 10 | 11 | class StatusQuoAndGoals(ttk.Frame): 12 | def __init__(self,master,conflict): 13 | ttk.Frame.__init__(self,master) 14 | 15 | self.conflict = conflict 16 | 17 | self.statusQuoVar = StringVar() 18 | self.goalGetters = [] 19 | self.removeCommands = [] 20 | self.cleanRow = 2 21 | 22 | self.listFrame = ttk.Frame(self) 23 | self.listFrame.grid(row=0,column=0,sticky=(N,S,E,W)) 24 | self.listFrame.columnconfigure(2,weight=1) 25 | self.columnconfigure(0,weight=1) 26 | 27 | self.statusQuoLabel = ttk.Label(self.listFrame,text="Status Quo:") 28 | self.statusQuoLabel.grid(row=0,column=0,columnspan=2,sticky=(N,S,E,W)) 29 | self.statusQuoSelector = ttk.Combobox(self.listFrame,textvariable=self.statusQuoVar,state='readonly') 30 | self.statusQuoSelector.grid(row=0,column=2,columnspan=2,sticky=(N,S,E,W)) 31 | ttk.Separator(self.listFrame,orient=HORIZONTAL).grid(row=1,column=0,columnspan=3,sticky=(N,S,E,W)) 32 | 33 | self.addGoalButton = ttk.Button(self,text="Add Goal",command = self.addGoal) 34 | self.addGoalButton.grid(row=1,column=0,sticky=(N,S,E,W)) 35 | 36 | self.statusQuoSelector.bind('<>',self.sqChange) 37 | self.refresh() 38 | 39 | def refresh(self): 40 | self.statusQuoSelector['values'] = tuple(self.conflict.feasibles.ordered) 41 | self.statusQuoSelector.current(0) 42 | for rmv in self.removeCommands: 43 | rmv() 44 | self.cleanRow = 2 45 | 46 | def addGoal(self,event=None): 47 | goalVar = StringVar() 48 | stabilityVar = StringVar() 49 | 50 | rmvBtn = ttk.Button(self.listFrame,text="X",width=5) 51 | label = ttk.Label(self.listFrame,text="Goal:") 52 | stateSelector = ttk.Combobox(self.listFrame,textvariable=goalVar,state='readonly',width=5) 53 | stateSelector['values'] = tuple(self.conflict.feasibles.ordered) 54 | desiredStable = ttk.Combobox(self.listFrame,textvariable=stabilityVar,state='readonly',width=10) 55 | desiredStable['values'] = tuple(['Stable','Unstable']) 56 | 57 | def getGoal(): 58 | return [stateSelector.current(), desiredStable.current()==0] 59 | 60 | def removeGoal(event=None): 61 | self.removeCommands.remove(removeGoal) 62 | self.goalGetters.remove(getGoal) 63 | label.destroy() 64 | stateSelector.destroy() 65 | desiredStable.destroy() 66 | rmvBtn.destroy() 67 | self.goalChange() 68 | 69 | 70 | rmvBtn.configure(command=removeGoal) 71 | self.removeCommands.append(removeGoal) 72 | self.goalGetters.append(getGoal) 73 | 74 | stateSelector.bind("<>",self.goalChange) 75 | desiredStable.bind("<>",self.goalChange) 76 | 77 | rmvBtn.grid(row=self.cleanRow,column=0,sticky=(N,S,E,W)) 78 | label.grid(row=self.cleanRow,column=1,sticky=(N,S,E,W)) 79 | stateSelector.grid(row=self.cleanRow,column=2,sticky=(N,S,E,W)) 80 | desiredStable.grid(row=self.cleanRow,column=3,sticky=(N,S,E,W)) 81 | desiredStable.current(1) 82 | self.cleanRow += 1 83 | 84 | def sqChange(self,event=None): 85 | self.event_generate("<>") 86 | 87 | def goalChange(self,event=None): 88 | self.event_generate("<>") 89 | 90 | def getGoals(self,event=None): 91 | goals = [] 92 | for gt in self.goalGetters: 93 | goals.append(gt()) 94 | return goals 95 | 96 | class ReachableTreeViewer(ttk.Frame): 97 | def __init__(self,master,conflict,solOwner): 98 | ttk.Frame.__init__(self,master) 99 | 100 | self.conflict = conflict 101 | self.owner = solOwner 102 | self.found = [] 103 | self.notFound = [] 104 | self.foundUI = [] 105 | self.notFoundUI = [] 106 | self.statusQuo = None 107 | 108 | self.reachableTree = ttk.Treeview(self,selectmode='browse') 109 | self.reachableTree.grid(row=0,column=0,sticky=(N,S,E,W)) 110 | self.scrollX = ttk.Scrollbar(self, orient=HORIZONTAL,command = self.reachableTree.xview) 111 | self.scrollX.grid(row=1,column=0,sticky=(N,S,E,W)) 112 | self.scrollY = ttk.Scrollbar(self, orient=VERTICAL,command = self.reachableTree.yview) 113 | self.scrollY.grid(row=0,column=1,sticky=(N,S,E,W)) 114 | self.reachableTree.configure(xscrollcommand=self.scrollX.set) 115 | self.reachableTree.configure(yscrollcommand=self.scrollY.set) 116 | 117 | self.rowconfigure(0,weight=1) 118 | self.columnconfigure(0,weight=1) 119 | 120 | self.reachableTree.column("#0",stretch=True,minwidth=100,width=120) 121 | self.reachableTree.heading("#0",text="State Number") 122 | 123 | columnNames = ("DM","YN","Decimal","Payoff","UI") 124 | self.reachableTree.configure(columns=columnNames) 125 | 126 | for col in columnNames: 127 | self.reachableTree.column(col,stretch=True,minwidth=60,width=80,anchor="center") 128 | self.reachableTree.heading(col,text=col) 129 | 130 | self.reachableTree.tag_configure("Y",background="green") 131 | self.buildTree(0,watchFor=[]) 132 | 133 | def buildTree(self,statusQuo,depth=5,lastDM=None,parent="",wasUI="N",onUIpath=True,watchFor=None): 134 | sol = self.owner.sol 135 | if lastDM is None: 136 | self.statusQuo = statusQuo 137 | self.notFound = list(set(watchFor)) 138 | self.found = [] 139 | self.notFoundUI = list(set(watchFor)) 140 | self.foundUI = [] 141 | for child in self.reachableTree.get_children(): 142 | self.reachableTree.delete(child) 143 | newNode = self.reachableTree.insert(parent,'end',text=str(statusQuo+1)) 144 | else: 145 | vals = (lastDM.name, 146 | self.conflict.feasibles.yn[statusQuo], 147 | self.conflict.feasibles.decimal[statusQuo], 148 | lastDM.payoffs[statusQuo], 149 | wasUI) 150 | newNode = self.reachableTree.insert(parent,'end',text=str(statusQuo+1),values=vals,tags=(wasUI,)) 151 | 152 | if statusQuo in self.notFound: 153 | self.notFound.remove(statusQuo) 154 | self.found.append(statusQuo) 155 | if onUIpath: 156 | if statusQuo in self.notFoundUI: 157 | self.notFoundUI.remove(statusQuo) 158 | self.foundUI.append(statusQuo) 159 | 160 | if depth>0: 161 | for co in [dm for dm in sol.conflict.coalitions if dm is not lastDM]: 162 | for reachable in sol.reachable(co,statusQuo): 163 | ui = "Y" if co.payoffMatrix[statusQuo,reachable]>0 else "N" 164 | uiPath = onUIpath and (ui=="Y") 165 | self.buildTree(reachable,depth-1,co,newNode,ui,uiPath) 166 | 167 | def goalInfo(self,event=None): 168 | message = "" 169 | if len(self.found)>0: 170 | message += "Goal states " +str([x+1 for x in self.found])[1:-1] + " are reachable from %s.\n"%(self.statusQuo+1) 171 | if len(self.notFound)>0: 172 | message += "States " +str([x+1 for x in self.notFound])[1:-1] + " are NOT reachable from %s.\n"%(self.statusQuo+1) 173 | if message != "": 174 | message += "\n" 175 | if len(self.foundUI)>0: 176 | message += "Goal states " +str([x+1 for x in self.foundUI])[1:-1] + " are reachable from %s solely by UIs.\n"%(self.statusQuo+1) 177 | if len(self.notFoundUI)>0: 178 | message += "States " +str([x+1 for x in self.notFoundUI])[1:-1] + " are NOT reachable from %s solely by UIs.\n"%(self.statusQuo+1) 179 | message += "\n" 180 | return message 181 | 182 | 183 | class PatternNarrator(ttk.Frame): 184 | def __init__(self,master,conflict,solOwner): 185 | ttk.Frame.__init__(self,master) 186 | 187 | self.conflict = conflict 188 | self.owner = solOwner 189 | 190 | self.label = ttk.Label(self,text="Requirements for goals to be met:") 191 | self.label.grid(row=0,column=0,sticky=(N,S,E,W)) 192 | 193 | self.textBox = Text(self,wrap="word") 194 | self.textBox.grid(row=1,column=0,sticky=(N,S,E,W)) 195 | self.scrollY = ttk.Scrollbar(self,orient=VERTICAL,command=self.textBox.yview) 196 | self.textBox.configure(yscrollcommand=self.scrollY.set) 197 | self.scrollY.grid(row=1,column=1,sticky=(N,S,E,W)) 198 | 199 | self.rowconfigure(1,weight=1) 200 | self.columnconfigure(0,weight=1) 201 | 202 | self.updateNarration() 203 | 204 | def updateNarration(self,event=None,goalInfo=""): 205 | message = goalInfo 206 | if not self.owner.sol.validGoals(): 207 | message += "No valid goals set." 208 | else: 209 | message += "\n" + self.owner.sol.nash().asString() 210 | message += "\n" + self.owner.sol.seq().asString() 211 | 212 | self.textBox.delete('1.0','end') 213 | self.textBox.insert('1.0',message) 214 | 215 | 216 | --------------------------------------------------------------------------------