def test_mutate(self): # vars for the test and defining programs progs = 100 muts = 100 prog_len = 100 ops = 5 dests = 8 inputs = 10000 # needed for mutation and initialization mutateParams = { 'pProgMut': 0.5, 'nOperations': ops, 'nDestinations': dests, 'inputSize': inputs, 'pInstDel': 0.5, 'pInstMut': 0.5, 'pInstSwp': 0.5, 'pInstAdd': 0.5, 'idCountProgram': 0 } # mutate all the progs many times for i in range(progs): p = Program(maxProgramLength=100, nOperations=ops, nDestinations=dests, inputSize=inputs, initParams=mutateParams) for i in range(muts): p.mutate(mutateParams) # check for program validity # atleast 1 instruction self.assertGreaterEqual(len(p.instructions), 1) # make sure each value in each intex within proper range for inst in p.instructions: # mode self.assertGreaterEqual(inst[0], 0) self.assertLessEqual(inst[0], 1) # op self.assertGreaterEqual(inst[1], 0) self.assertLessEqual(inst[1], ops - 1) # dest self.assertGreaterEqual(inst[2], 0) self.assertLessEqual(inst[2], dests - 1) # input self.assertGreaterEqual(inst[3], 0) self.assertLessEqual(inst[3], inputs - 1)
class ConfActionObject: def init_def(self, initParams=None, action=None): ''' Defer importing the Team class to avoid circular dependency. This may require refactoring to fix properly ''' from tpg.team import Team # The action is a team if isinstance(action, Team): self.teamAction = action self.actionCode = None #print("chose team action") return # The action is another action object if isinstance(action, ActionObject): self.actionCode = action.actionCode self.teamAction = action.teamAction return # An int means the action is an index into the action codes in initParams if isinstance(action, int): if "actionCodes" not in initParams: raise Exception('action codes not found in init params', initParams) try: self.actionCode = initParams["actionCodes"][action] self.teamAction = None except IndexError as err: ''' TODO log index error ''' print("Index error") return def init_real(self, initParams=None, action=None): ''' Defer importing the Team class to avoid circular dependency. This may require refactoring to fix properly ''' from tpg.team import Team if isinstance(action, Team): # The action is a team self.actionCode = None self.actionLength = None self.teamAction = action self.program = Program( initParams=initParams, maxProgramLength=initParams["initMaxActProgSize"], nOperations=initParams["nOperations"], nDestinations=initParams["nDestinations"], inputSize=initParams["inputSize"]) elif isinstance(action, ActionObject): # The action is another action object self.actionCode = action.actionCode self.actionLength = action.actionLength self.teamAction = action.teamAction self.program = Program(instructions=action.program.instructions, initParams=initParams) elif isinstance(action, int): # An int means the action is an index into the action codes in initParams if "actionCodes" not in initParams: raise Exception('action codes not found in init params', initParams) try: self.actionCode = initParams["actionCodes"][action] self.actionLength = initParams["actionLengths"][action] self.teamAction = None self.program = Program( initParams=initParams, maxProgramLength=initParams["initMaxActProgSize"], nOperations=initParams["nOperations"], nDestinations=initParams["nDestinations"], inputSize=initParams["inputSize"]) except IndexError as err: ''' TODO log index error ''' print("Index error") self.registers = np.zeros( max(initParams["nActRegisters"], initParams["nDestinations"])) """ Returns the action code, and if applicable corresponding real action. """ def getAction_def(self, state, visited, actVars=None, path_trace=None): if self.teamAction is not None: # action from team return self.teamAction.act(state, visited, actVars=actVars, path_trace=path_trace) else: # atomic action return self.actionCode """ Returns the action code, and if applicable corresponding real action(s). """ def getAction_real(self, state, visited, actVars=None, path_trace=None): if self.teamAction is not None: # action from team return self.teamAction.act(state, visited, actVars=actVars, path_trace=path_trace) else: # atomic action if self.actionLength == 0: return self.actionCode, None else: return self.actionCode, self.getRealAction(state, actVars=actVars) """ Gets the real action from a register. """ def getRealAction_real(self, state, actVars=None): Program.execute(state, self.registers, self.program.instructions[:, 0], self.program.instructions[:, 1], self.program.instructions[:, 2], self.program.instructions[:, 3]) return self.registers[:self.actionLength] """ Gets the real action from a register. With memory. """ def getRealAction_real_mem(self, state, actVars=None): Program.execute(state, self.registers, self.program.instructions[:, 0], self.program.instructions[:, 1], self.program.instructions[:, 2], self.program.instructions[:, 3], actVars["memMatrix"], actVars["memMatrix"].shape[0], actVars["memMatrix"].shape[1], Program.memWriteProbFunc) return self.registers[:self.actionLength] """ Returns true if the action is atomic, otherwise the action is a team. """ def isAtomic_def(self): return self.teamAction is None """ Change action to team or atomic action. """ def mutate_def(self, mutateParams, parentTeam, teams, pActAtom, learner_id): # mutate action if flip(pActAtom): # atomic ''' If we already have an action code make sure not to pick the same one. TODO handle case where there is only 1 action code. ''' if self.actionCode is not None: options = list( filter(lambda code: code != self.actionCode, mutateParams["actionCodes"])) else: options = mutateParams["actionCodes"] # let our current team know we won't be pointing to them anymore if not self.isAtomic(): #print("Learner {} switching from Team {} to atomic action".format(learner_id, self.teamAction.id)) self.teamAction.inLearners.remove(str(learner_id)) self.actionCode = random.choice(options) self.teamAction = None else: # team action selection_pool = [ t for t in teams if t is not self.teamAction and t is not parentTeam ] # If we have a valid set of options choose from them if len(selection_pool) > 0: # let our current team know we won't be pointing to them anymore oldTeam = None if not self.isAtomic(): oldTeam = self.teamAction self.teamAction.inLearners.remove(str(learner_id)) self.teamAction = random.choice(selection_pool) # Let the new team know we're pointing to them self.teamAction.inLearners.append(str(learner_id)) #if oldTeam != None: # print("Learner {} switched from Team {} to Team {}".format(learner_id, oldTeam.id, self.teamAction.id)) return self """ Change action to team or atomic action. """ def mutate_real(self, mutateParams, parentTeam, teams, pActAtom, learner_id): # first maybe mutate just program if self.actionLength > 0 and flip(0.5): self.program.mutate(mutateParams) # mutate action if flip(pActAtom): # atomic ''' If we already have an action code make sure not to pick the same one. TODO handle case where there is only 1 action code. ''' if self.actionCode is not None: options = list( filter(lambda code: code != self.actionCode, mutateParams["actionCodes"])) else: options = mutateParams["actionCodes"] # let our current team know we won't be pointing to them anymore if not self.isAtomic(): #print("Learner {} switching from Team {} to atomic action".format(learner_id, self.teamAction.id)) self.teamAction.inLearners.remove(str(learner_id)) self.actionCode = random.choice(options) self.actionLength = mutateParams["actionLengths"][self.actionCode] self.teamAction = None else: # team action selection_pool = [ t for t in teams if t is not self.teamAction and t is not parentTeam ] # If we have a valid set of options choose from them if len(selection_pool) > 0: # let our current team know we won't be pointing to them anymore oldTeam = None if not self.isAtomic(): oldTeam = self.teamAction self.teamAction.inLearners.remove(str(learner_id)) self.teamAction = random.choice(selection_pool) # Let the new team know we're pointing to them self.teamAction.inLearners.append(str(learner_id)) #if oldTeam != None: # print("Learner {} switched from Team {} to Team {}".format(learner_id, oldTeam.id, self.teamAction.id)) return self
class Learner: idCount = 0 # unique learner id """ Create a new learner, either copied from the original or from a program or action. Either requires a learner, or a program/action pair. """ def __init__(self, learner=None, program=None, action=None, numRegisters=8): if learner is not None: self.program = Program(instructions=learner.program.instructions) self.action = learner.action self.registers = np.zeros(len(learner.registers), dtype=float) elif program is not None and action is not None: self.program = program self.action = action self.registers = np.zeros(numRegisters, dtype=float) if not self.isActionAtomic(): self.action.numLearnersReferencing += 1 self.states = [] self.numTeamsReferencing = 0 # amount of teams with references to this self.id = Learner.idCount """ Get the bid value, highest gets its action selected. """ def bid(self, state): Program.execute(state, self.registers, self.program.modes, self.program.operations, self.program.destinations, self.program.sources) return self.registers[0] """ Returns the action of this learner, either atomic, or requests the action from the action team. """ def getAction(self, state, visited): if self.isActionAtomic(): return self.action else: return self.action.act(state, visited) """ Returns true if the action is atomic, otherwise the action is a team. """ def isActionAtomic(self): return isinstance(self.action, (int, list)) """ Mutates either the program or the action or both. """ def mutate(self, pMutProg, pMutAct, pActAtom, atomics, parentTeam, allTeams, pDelInst, pAddInst, pSwpInst, pMutInst, multiActs, pSwapMultiAct, pChangeMultiAct, uniqueProgThresh, inputs=None, outputs=None, update=True): changed = False while not changed: # mutate the program if flip(pMutProg): changed = True self.program.mutate(pMutProg, pDelInst, pAddInst, pSwpInst, pMutInst, len(self.registers), uniqueProgThresh, inputs=inputs, outputs=outputs, update=update) # mutate the action if flip(pMutAct): changed = True self.mutateAction(pActAtom, atomics, allTeams, parentTeam, multiActs, pSwapMultiAct, pChangeMultiAct) """ Changes the action, into an atomic or team. """ def mutateAction(self, pActAtom, atomics, allTeams, parentTeam, multiActs, pSwapMultiAct, pChangeMultiAct): if not self.isActionAtomic(): # dereference old team action self.action.numLearnersReferencing -= 1 if flip(pActAtom): # atomic action if multiActs is None: self.action = random.choice( [a for a in atomics if a is not self.action]) else: swap = flip(pSwapMultiAct) if swap or not self.isActionAtomic( ): # totally swap action for another self.action = list(random.choice(multiActs)) # change some value in action if not swap or flip(pChangeMultiAct): changed = False while not changed or flip(pChangeMultiAct): index = random.randint(0, len(self.action) - 1) self.action[index] += random.gauss(0, .15) self.action = list(np.clip(self.action, 0, 1)) changed = True else: # Team action self.action = random.choice([ t for t in allTeams if t is not self.action and t is not parentTeam ]) if not self.isActionAtomic(): # add reference for new team action self.action.numLearnersReferencing += 1 """ Saves visited states for mutation uniqueness purposes. """ def saveState(self, state, numStates=50): self.states.append(state) self.states = self.states[-numStates:]
class ConfLearner: """ Create a new learner, either copied from the original or from a program or action. Either requires a learner, or a program/action pair. """ def init_def(self, initParams, program, actionObj, numRegisters, learner_id=None): self.program = Program( instructions=program.instructions ) #Each learner should have their own copy of the program self.actionObj = ActionObject(action=actionObj, initParams=initParams) #Each learner should have their own copy of the action object self.registers = np.zeros(numRegisters, dtype=float) self.ancestor = None #By default no ancestor ''' TODO What's self.states? ''' self.states = [] self.inTeams = [] # Store a list of teams that reference this learner, incoming edges self.genCreate = initParams["generation"] # Store the generation that this learner was created on ''' TODO should this be -1 before it sees any frames? ''' self.frameNum = 0 # Last seen frame is 0 # Assign id from initParams counter self.id = uuid.uuid4() if not self.isActionAtomic(): self.actionObj.teamAction.inLearners.append(str(self.id)) #print("Creating a brand new learner" if learner_id == None else "Creating a learner from {}".format(str(learner_id))) #print("Created learner {} [{}] -> {}".format(self.id, "atomic" if self.isActionAtomic() else "Team", self.actionObj.actionCode if self.isActionAtomic() else self.actionObj.teamAction.id)) """ Get the bid value, highest gets its action selected. """ def bid_def(self, state, actVars=None): # exit early if we already got bidded this frame if self.frameNum == actVars["frameNum"]: return self.registers[0] self.frameNum = actVars["frameNum"] Program.execute(state, self.registers, self.program.instructions[:,0], self.program.instructions[:,1], self.program.instructions[:,2], self.program.instructions[:,3]) return self.registers[0] """ Get the bid value, highest gets its action selected. Passes memory args to program. """ def bid_mem(self, state, actVars=None): # exit early if we already got bidded this frame if self.frameNum == actVars["frameNum"]: return self.registers[0] self.frameNum = actVars["frameNum"] Program.execute(state, self.registers, self.program.instructions[:,0], self.program.instructions[:,1], self.program.instructions[:,2], self.program.instructions[:,3], actVars["memMatrix"], actVars["memMatrix"].shape[0], actVars["memMatrix"].shape[1], Program.memWriteProbFunc) return self.registers[0] """ Returns the action of this learner, either atomic, or requests the action from the action team. """ def getAction_def(self, state, visited, actVars=None, path_trace=None): return self.actionObj.getAction(state, visited, actVars=actVars, path_trace=path_trace) """ Gets the team that is the action of the learners action object. """ def getActionTeam_def(self): return self.actionObj.teamAction """ Returns true if the action is atomic, otherwise the action is a team. """ def isActionAtomic_def(self): return self.actionObj.isAtomic() """ Mutates either the program or the action or both. """ def mutate_def(self, mutateParams, parentTeam, teams, pActAtom): changed = False while not changed: # mutate the program if flip(mutateParams["pProgMut"]): changed = True self.program.mutate(mutateParams) # mutate the action if flip(mutateParams["pActMut"]): changed = True self.actionObj.mutate(mutateParams, parentTeam, teams, pActAtom, learner_id=self.id) return self
class Learner: def __init__(self, initParams, program, actionObj, numRegisters, learner_id=None): self.program = Program( instructions=program.instructions ) #Each learner should have their own copy of the program self.actionObj = ActionObject( action=actionObj, initParams=initParams ) #Each learner should have their own copy of the action object self.registers = np.zeros(numRegisters, dtype=float) self.ancestor = None #By default no ancestor ''' TODO What's self.states? ''' self.states = [] self.inTeams = [ ] # Store a list of teams that reference this learner, incoming edges self.genCreate = initParams[ "generation"] # Store the generation that this learner was created on ''' TODO should this be -1 before it sees any frames? ''' self.frameNum = 0 # Last seen frame is 0 # Assign id from initParams counter self.id = uuid.uuid4() if not self.isActionAtomic(): self.actionObj.teamAction.inLearners.append(str(self.id)) #print("Creating a brand new learner" if learner_id == None else "Creating a learner from {}".format(str(learner_id))) #print("Created learner {} [{}] -> {}".format(self.id, "atomic" if self.isActionAtomic() else "Team", self.actionObj.actionCode if self.isActionAtomic() else self.actionObj.teamAction.id)) def zeroRegisters(self): self.registers = np.zeros(len(self.registers), dtype=float) self.actionObj.zeroRegisters() def numTeamsReferencing(self): return len(self.inTeams) ''' A learner is equal to another object if that object: - is an instance of the learner class - was created on the same generation - has an identical program - has the same action object - has the same inTeams - has the same id ''' def __eq__(self, o: object) -> bool: # Object must be an instance of Learner if not isinstance(o, Learner): return False # The object must have been created the same generation as us if self.genCreate != o.genCreate: return False # The object's program must be equal to ours if self.program != o.program: return False # The object's action object must be equal to ours if self.actionObj != o.actionObj: return False ''' The other object's inTeams must match our own, therefore: - len(inTeams) must be equal - every id that appears in our inTeams must appear in theirs (order doesn't matter) ''' if len(self.inTeams) != len(o.inTeams): return False ''' Collection comparison via collection counters https://www.journaldev.com/37089/how-to-compare-two-lists-in-python ''' if collections.Counter(self.inTeams) != collections.Counter(o.inTeams): return False # The other object's id must be equal to ours if self.id != o.id: return False return True def debugEq(self, o: object) -> bool: # Object must be an instance of Learner if not isinstance(o, Learner): print("other object is not instance of Learner") return False # The object must have been created the same generation as us if self.genCreate != o.genCreate: print("other object has different genCreate") return False # The object's program must be equal to ours if self.program != o.program: print("other object has a different program") return False # The object's action object must be equal to ours if self.actionObj != o.actionObj: print("other object has a different action object") return False ''' The other object's inTeams must match our own, therefore: - len(inTeams) must be equal - every id that appears in our inTeams must appear in theirs (order doesn't matter) ''' if len(self.inTeams) != len(o.inTeams): print("other object has different number of inTeams") print("us:") print(self) print("other learner:") print(o) return False ''' Collection comparison via collection counters https://www.journaldev.com/37089/how-to-compare-two-lists-in-python ''' if collections.Counter(self.inTeams) != collections.Counter(o.inTeams): print("other object has different inTeams") return False # The other object's id must be equal to ours if self.id != o.id: print("other object has different id") return False return True ''' Negation of __eq__ ''' def __ne__(self, o: object) -> bool: return not self.__eq__(o) ''' String representation of a learner ''' def __str__(self): result = """id: {} created_at_gen: {} program_id: {} type: {} action: {} numTeamsReferencing: {} inTeams:\n""".format( self.id, self.genCreate, self.program.id, "actionCode" if self.isActionAtomic() else "teamAction", self.actionObj.actionCode if self.isActionAtomic() else self.actionObj.teamAction.id, self.numTeamsReferencing()) for cursor in self.inTeams: result += "\t{}\n".format(cursor) return result """ Create a new learner, either copied from the original or from a program or action. Either requires a learner, or a program/action pair. """ """ Get the bid value, highest gets its action selected. """ def bid(self, state, actVars=None): # exit early if we already got bidded this frame if self.frameNum == actVars["frameNum"]: return self.registers[0] self.frameNum = actVars["frameNum"] Program.execute(state, self.registers, self.program.instructions[:, 0], self.program.instructions[:, 1], self.program.instructions[:, 2], self.program.instructions[:, 3]) return self.registers[0] """ Returns the action of this learner, either atomic, or requests the action from the action team. """ def getAction(self, state, visited, actVars=None, path_trace=None): return self.actionObj.getAction(state, visited, actVars=actVars, path_trace=path_trace) """ Gets the team that is the action of the learners action object. """ def getActionTeam(self): return self.actionObj.teamAction """ Returns true if the action is atomic, otherwise the action is a team. """ def isActionAtomic(self): return self.actionObj.isAtomic() """ Mutates either the program or the action or both. A mutation creates a new instance of the learner, removes it's anscestor and adds itself to the team. """ def mutate(self, mutateParams, parentTeam, teams, pActAtom): changed = False while not changed: # mutate the program if flip(mutateParams["pProgMut"]): changed = True self.program.mutate(mutateParams) # mutate the action if flip(mutateParams["pActMut"]): changed = True self.actionObj.mutate(mutateParams, parentTeam, teams, pActAtom, learner_id=self.id) return self """ Ensures proper functions are used in this class as set up by configurer. """ @classmethod def configFunctions(cls, functionsDict): from tpg.configuration.conf_learner import ConfLearner if functionsDict["init"] == "def": cls.__init__ = ConfLearner.init_def if functionsDict["bid"] == "def": cls.bid = ConfLearner.bid_def elif functionsDict["bid"] == "mem": cls.bid = ConfLearner.bid_mem if functionsDict["getAction"] == "def": cls.getAction = ConfLearner.getAction_def if functionsDict["getActionTeam"] == "def": cls.getActionTeam = ConfLearner.getActionTeam_def if functionsDict["isActionAtomic"] == "def": cls.isActionAtomic = ConfLearner.isActionAtomic_def if functionsDict["mutate"] == "def": cls.mutate = ConfLearner.mutate_def