def altpw(self, username: str, row: str, col: str, val: float, createUnknownUser=True) -> None: ''' Does a pairwise comparison of the alts. If there is not a pairwise comparison object being used to prioritize the children, we create one first. :param username: The user to perform the comparison on. :param row: The name of the row alt of the comparison :param col: The name of the column alt of the comparison :param val: The comparison value :param createUnknownUser: If True, and username did not exist, it will be created and then the vote set. Otherwise if the user did not exist, will raise an exception. :return: Nothing :raises ValueError: If the user did not exist and createUnknownUser is False. ''' if not isinstance(self.alt_prioritizer, Pairwise): self.alt_prioritizer = Pairwise(self.alt_names) self.alt_prioritizer.vote(username, row, col, val, createUnknownUser=createUnknownUser)
def test_sim_pw(self): pw = np.array([[1, 2, 4], [1 / 2, 1, 2], [1 / 4, 1 / 3, 1]]) mc = MCAnp() pwobj = Pairwise(alts=['alt ' + str(i) for i in range(3)]) pwobj.vote_matrix(user_name='u1', val=pw) s1 = mc.sim(pwobj) #print(s1) err = 0.5 npt.assert_allclose([4 / 7, 2 / 7, 1 / 7], s1, rtol=err) # Next I need to test with a full sim with multiple count sim1 = mc.sim(pw, count=20) self.assertEqual(20, len(sim1.df)) print(sim1)
def sim_pw_fill(self, pwsrc: Pairwise, pwdest: Pairwise = None) -> Pairwise: """ Fills in the pairwise comparison structure of pwdest with noisy pairwise data from pwsrc. If pwdest is None, we create one first, then fill in. In either case, we return the pwdest object with new noisy data in it. """ if pwdest is None: pwdest = deepcopy(pwsrc) for user in pwsrc.usernames(): srcmat = pwsrc.matrix(user) destmat = pwdest.matrix(user) self.sim_pwmat_fill(srcmat, destmat) return pwdest
def sim_pw(self, pwsrc: Pairwise, pwdest: Pairwise, pritype: PriorityType = None) -> np.ndarray: """ Performs a simulation on a pairwise comparison matrix object and returns the resulting priorities """ pwdest = self.sim_pw_fill(pwsrc, pwdest) return pwdest.priority(self.username, pritype)
def __init__(self, parent, name:str, alt_names): ''' Initial a new AHPTreeNode :param parent: The parent AHPTree this AHPTreeNode is in. :param name: The string name of this node. It should be unique in its parent tree. :param alt_names: The alternatives we are comparing in the AHPTree. As currently implemented the parent tree has the list of alternatives and we pass that object to the nodes. This allows us to add new alternatives once in the parent tree and the addition cascades down. ''' self.parent = parent self.children = [] nalts = len(alt_names) self.alt_scores = pd.Series(data=[0]*nalts, index=alt_names) self.child_prioritizer = Direct() self.alt_prioritizer = Direct(alt_names) self.alt_names = alt_names self.alt_scores_manually_set=False self.name = name
class AHPTreeNode: ''' Represents a node in an AHPTree class ''' def __init__(self, parent, name:str, alt_names): ''' Initial a new AHPTreeNode :param parent: The parent AHPTree this AHPTreeNode is in. :param name: The string name of this node. It should be unique in its parent tree. :param alt_names: The alternatives we are comparing in the AHPTree. As currently implemented the parent tree has the list of alternatives and we pass that object to the nodes. This allows us to add new alternatives once in the parent tree and the addition cascades down. ''' self.parent = parent self.children = [] nalts = len(alt_names) self.alt_scores = pd.Series(data=[0]*nalts, index=alt_names) self.child_prioritizer = Direct() self.alt_prioritizer = Direct(alt_names) self.alt_names = alt_names self.alt_scores_manually_set=False self.name = name def has_child(self, name:str)->bool: ''' Returns a boolean telling if this node has a child with the given name. :param name: The name of the child to check for. :return: True/False if the node has the child by the given name or not. ''' return name in [kid.name for kid in self.children] def add_child(self, childname:str)->None: ''' Adds a child to this node. :param childname: The string name of the child to add. :return: Nothing :raises ValueError: If a child by the given name already existed ''' if self.has_child(childname): raise ValueError("Cannot duplicate children names") kidnode = AHPTreeNode(self.parent, childname, self.alt_names) self.children.append(kidnode) self.child_prioritizer.add_alt(childname) def childnames(self): ''' Get the names of the children of this node :return: A list of str's of the names of this nodes children. If it has no children we return the empty list. ''' return [child.name for child in self.children] def add_alt(self, alt_name:str)->None: ''' Adds an alternative to the alternatives under this node. :param alt_name: The new alternative to add :return: Nothing :raises ValueError: If the alternative already existed ''' self.alt_prioritizer.add_alt(alt_name) self.alt_scores[alt_name]=0.0 for kid in self.children: kid.add_alt(alt_name) def nalts(self)->int: ''' Gets the number of alternatives under this node. ''' return len(self.alt_names) def has_children(self)->int: ''' :return: A boolean telling if this node has children ''' return len(self.children) > 0 def synthesize(self, username=None)->None: ''' Synthesizes up the alternative scores below this alternative and stores the result in the alt_scores. However if the node has no children and has had it's alternative scores manually set via AHPTreeNode.set_alt_scores, then this does nothing. Otherwise it synthesizes upward. :param username: The name of the user (or list of names of the users) to synthesize for. :return: Nothing ''' if self.has_children(): nalts = self.nalts() rval = pd.Series(data=[0]*nalts, index=self.alt_names) kidpris = self.child_prioritizer.priority(username, PriorityType.NORMALIZE) if np.sum(np.abs(kidpris)) == 0: nkids = len(kidpris) for key, value in kidpris.iteritems(): kidpris[key]=1.0 / nkids for child, childpri in zip(self.children, kidpris): child.synthesize(username) rval += childpri * child.alt_scores self.alt_scores = rval else: if self.alt_scores_manually_set: # Do nothing here, alt scores are already setup pass else: self.alt_scores = self.alt_prioritizer.priority(username) def set_alt_scores(self, new_scores:dict): ''' Used to manually set (or unset) alternative scores. If new_scores is None, it unsets the manually set values, so that the next call to AHPTreeNode.synthesize() will actually synthesize the scores and not use the manually set values. :param new_scores: If None, it means undo the manual setting of the scores, otherwise it loops over each key, value pair and sets the score in AHPTreeNode.alt_scores :return: ''' if new_scores is None: self.alt_scores_manually_set = False else: self.alt_scores_manually_set=True #self.alt_scores = pd.Series([0]*self.nalts(), index=self.alt_names, dtype=float) if isinstance(new_scores, dict): for key, value in new_scores.items(): if key not in self.alt_scores.index: raise ValueError("Tried to score alt "+key+" that did not exist.") self.alt_scores[key] = value else: raise ValueError("Do not know how to set alt scores from type "+type(new_scores)) def get_nodes_under_hash(self, rval:dict = None)->dict: ''' Returns a dictionary of nodeName:AHPTreeNode of the nodes under this node. It includes this node as well. :param rval: If passed in, we add the dictionary items to this dictionary :return: The dictionary of name:AHPTreeNode objects ''' if rval is None: rval = {} rval[self.name] = self for child in self.children: child.get_nodes_under_hash(rval) return rval def nodepw(self, username:str, row:str, col:str, val:float, createUnknownUser=True)->None: ''' Does a pairwise comparison of the children. If there is not a pairwise comparison object being used to prioritize the children, we create one first. :param username: The user to perform the comparison on. :param row: The name of the row node of the comparison :param col: The name of the column node of the comparison :param val: The comparison value :param createUnknownUser: If True, and username did not exist, it will be created and then the vote set. Otherwise if the user did not exist, will raise an exception. :return: Nothing :raises ValueError: If the user did not exist and createUnknownUser is False. ''' if not isinstance(self.child_prioritizer, Pairwise): self.child_prioritizer = Pairwise(self.childnames()) self.child_prioritizer.vote(username, row, col, val, createUnknownUser=createUnknownUser) def altpw(self, username:str, row:str, col:str, val:float, createUnknownUser=True)->None: ''' Does a pairwise comparison of the alts. If there is not a pairwise comparison object being used to prioritize the children, we create one first. :param username: The user to perform the comparison on. :param row: The name of the row alt of the comparison :param col: The name of the column alt of the comparison :param val: The comparison value :param createUnknownUser: If True, and username did not exist, it will be created and then the vote set. Otherwise if the user did not exist, will raise an exception. :return: Nothing :raises ValueError: If the user did not exist and createUnknownUser is False. ''' if not isinstance(self.alt_prioritizer, Pairwise): self.alt_prioritizer = Pairwise(self.alt_names) self.alt_prioritizer.vote(username, row, col, val, createUnknownUser=createUnknownUser) def add_user(self, user:str)->None: ''' Adds a user to the prioritizers below this :param user: The name of the user to add :return: Nothing :raises ValueError: If the user already existed ''' self.child_prioritizer.add_user(user) self.alt_prioritizer.add_user(user) def alt_direct(self, node, val): ''' Manually sets the alternative score. See AHPTreeNode.set_alt_scores() for more info. :param node: :param val: :return: ''' self.set_alt_scores({node:val}) def _repr_html(self, tab=""): ''' Used by Jupyter to pretty print an instance of AHPTreeNode :param tab: How many tabs should we indent? :return: The html string pretty print version of this ''' rval = tab+"<li><b>Node:</b>"+self.name+"\n" if self.has_children(): # Append child prioritizer info rval += self.child_prioritizer._repr_html(tab+"\t") if self.has_children(): rval += tab+"<ul>\n" for child in self.children: rval += child._repr_html(tab+"\t") rval += "</ul>\n" else: # Should connect to alternatives, let's just report scores altscoresstr = tab+"\t\t"+str(self.alt_scores)+"\n" altscoresstr = re.sub("\n", "\n"+tab+"\t\t", altscoresstr) altscoresstr = altscoresstr.rstrip() rval += tab+"\t"+"<ul><li>AltScores=\n"+altscoresstr+"\n" rval += tab+"\t"+"</ul>\n" return rval def usernames(self, rval:list=None)->list: ''' Returns the names of all users involved in this AHPTreeNode :param rval: If not None, we add the names to this list :return: List of str user names. ''' if rval is None: rval = [] if self.child_prioritizer is not None: users = self.child_prioritizer.usernames() for user in users: if user not in rval: rval.append(user) if self.alt_prioritizer is not None: for user in self.alt_prioritizer.usernames(): if user not in rval: rval.append(user) return rval
def test_crud(self): alts = ['alt1', 'alt2', 'alt3'] a1, a2, a3 = alts pw = Pairwise(alts=alts) u1 = 'Bill' pw.add_user(u1) self.assertTrue(pw.is_user(u1)) mat = pw.matrix(u1) assert_array_equal(mat, np.identity(3)) pw.vote('Bill', a1, a2, 3) assert_array_equal(mat, [[1, 3, 0], [1. / 3, 1, 0], [0, 0, 1]]) pw.unvote('Bill', a1, a2) assert_array_equal(mat, np.identity(3)) u2 = 'Leanne' pw.add_user(u2) pw.vote(u2, a1, a2, 5) pw.vote(u1, a1, a2, 3) group = pw.matrix(None) assert_allclose( group, [[1, np.sqrt(15), 0], [1 / np.sqrt(15), 1, 0], [0, 0, 1]]) pw.vote(u1, a2, a3, 2) pw.vote(u1, a1, a3, 6) pri = pw.priority(u1) assert_allclose(pri, [6 / 9, 2 / 9, 1 / 9])
def test_addalt(self): pw = Pairwise() a1, a2, a3 = ["alt1", "a2", "a3"] u1, u2 = ["Bill", "Leanne"] pw.add_alt(a1) pw.add_user(u1) pw.add_alt(a2) m = pw.matrix(u1) assert_array_equal(m, np.identity(2)) pw.vote(u1, a1, a2, 5) assert_allclose(m, [[1, 5.0], [1 / 5, 1]])