├── Rules.pyc ├── INSTRUCTIONS ├── Rules.py ├── README ├── MU └── MultiMU /Rules.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffbr13/MU/master/Rules.pyc -------------------------------------------------------------------------------- /INSTRUCTIONS: -------------------------------------------------------------------------------- 1 | 2 | 3 | MU finds whether you can derive one string from another, using the 4 | rules printed in Hofstadter's 'Godel, Escher, Bach:..' using a 5 | breadth-first, brute-force method (now with added cores!). 6 | 7 | Arguments: 8 | 1st argument is the number of derivation cycles to compute. 9 | 2nd argument is the starting string. 10 | 3rd argument is the string you want to achieve. 11 | 4th argument is the number of processors to use. Defaults to 1 if blank. 12 | 13 | i.e. To run 50 cycles with 2 cores and see if it produces 'MI' from 'MU', 14 | (hint - it doesn't) then at the command line, while in the right 15 | directory, type: 16 | 17 | ./MultiMU 50 'MU' 'MI' 2 18 | 19 | -------------------------------------------------------------------------------- /Rules.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright (C) 2011 Ben Jeffrey. All Rights Reserved. 3 | 4 | def Instruct(): 5 | return 'You are in the wrong script. Be instructed by this.' 6 | 7 | def Transform(Word): 8 | """ 9 | Given a string, returns a list of all possible 10 | derivations according to the MU puzzle rules. 11 | """ 12 | Word = str(Word) # Ensure input is a string 13 | Transformed = [] 14 | 15 | if Word[-1] == 'I': # Rule 1: xxI -> xxIU 16 | Transformed.append(Word + 'U') 17 | if Word[0] == 'M': # Rule 2: Mxx -> Mxxxx 18 | Transformed.append(Word+Word[1:]) 19 | if 'III' in Word: # Rule 3: xxIIIxx -> xxUxx 20 | Transformed.append(Word.replace('III', 'U')) 21 | if 'UU' in Word: # Rule 4: xxUUxx -> xxxx 22 | Transformed.append(Word.replace('UU', '')) 23 | 24 | return Transformed # Returns list of all applied transformations. 25 | 26 | if __name__ == '__main__': 27 | print Instruct() 28 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | MU finds whether you can derive one string from another, primarily using the 2 | rules printed in Hofstadter's 'Godel, Escher, Bach: An Eternal Golden Braid, 3 | but other rules can be implemented by changing the 'Transform' method 4 | in 'Rules.py'. 5 | 6 | MU uses a breadth-first, brute-force method, and MultiMU (the main version) 7 | has multi-core support. The Parallel Python library (pp) must be installed. 8 | 9 | MultiMU(.py) is the main version. 10 | MU is the reference single core implementation, and is now deprecated. 11 | 12 | Arguments: 13 | 1st argument is the number of derivation cycles to compute. 14 | 2nd argument is the starting string. 15 | 3rd argument is the string you want to achieve. 16 | 4th argument is the number of processors to use. Defaults to 1 if blank. 17 | 18 | i.e. To run 50 cycles with 2 cores and see if it produces 'MI' from 'MU', 19 | (hint - it doesn't) then at the command line, while in the right 20 | directory, type: 21 | 22 | ./MultiMU 50 'MU' 'MI' 2" 23 | 24 | -------------------------------------------------------------------------------- /MU: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright (C) 2011 Ben Jeffrey. All Rights Reserved. 3 | import Rules 4 | import sys 5 | 6 | Transform = Rules.Transform 7 | 8 | def CycleThrough (WordList): 9 | Derivations = [] 10 | for Word in WordList: # For every word in the list, 11 | Derivatives = Transform(Word) # get it's transformations, 12 | Derivations.extend(Derivatives) # and put them in a big list. 13 | return Derivations # Return the list for the next cycle. 14 | 15 | def Run (MaxCycles=10, StartWord='MI', EndWord='MU'): 16 | 17 | Derivatives = [StartWord] # List of words 18 | Cycles = 0 19 | 20 | while EndWord not in Derivatives: 21 | 22 | Cycles += 1 23 | print 'Cycle:', Cycles, ' Derivations:', str(len(Derivatives)) 24 | if Cycles > MaxCycles: # Stop if we can't find it in given number of cycles. 25 | return 'Could not derive in %s cycles' % MaxCycles 26 | else: 27 | Derivatives.extend(CycleThrough(Derivatives)) 28 | 29 | return (Cycles, StartWord, EndWord) # Returns a tuple at the end, if found. 30 | 31 | 32 | if __name__ == '__main__': 33 | 34 | if len(sys.argv)<2: #Instructions! 35 | InstructionsFile = open('Instructions', 'r') 36 | Instructions = InstructionsFile.read() 37 | InstructionsFile.close() 38 | print Instructions 39 | 40 | else: 41 | print Run(int(sys.argv[1]), str(sys.argv[2]), str(sys.argv[3])) 42 | -------------------------------------------------------------------------------- /MultiMU: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright (C) 2011 Ben Jeffrey. All Rights Reserved. 3 | 4 | import Rules 5 | import pp 6 | import sys 7 | import time 8 | # Import some stuff (including the rules to mutate the strings). 9 | Transform = Rules.Transform 10 | 11 | def ApplyRules (WordList): 12 | """ 13 | Takes the word list and returns all the derivations 14 | according to the rules file. 15 | """ 16 | Derivations = [] 17 | for Word in WordList: 18 | WordDerivatives = Transform(Word) 19 | Derivations.extend(WordDerivatives) 20 | return Derivations 21 | 22 | 23 | def DoleOutJobs (WordList, JobServer, NumberOfWorkers): 24 | """ 25 | Takes the word list, the job server object, and the 26 | number of workers to use; distributes words to workers, 27 | collects the derivations, and returns them at the end. 28 | """ 29 | 30 | EachWorkerGets = len(WordList)/NumberOfWorkers 31 | Derivations = [] 32 | ListOfWorkers = [] 33 | # Sort out how the word list is distributed, then 34 | # set up worker processes to be run, in a list. 35 | for Worker in range(0,NumberOfWorkers): 36 | StartMarker = (Worker*EachWorkerGets) 37 | EndMarker = ((Worker+1)*EachWorkerGets) 38 | WorkerWords = WordList[StartMarker:EndMarker] 39 | ActualWorker = JobServer.submit(ApplyRules, (WorkerWords,), (Transform, Rules.Transform,), ("Rules",)) 40 | ListOfWorkers.append(ActualWorker) 41 | 42 | # Run all the worker processes in the list, 43 | # and collect their results. 44 | for Worker in ListOfWorkers: 45 | WhatWorkerFinds = Worker() 46 | Derivations.extend(WhatWorkerFinds) 47 | 48 | return Derivations 49 | 50 | 51 | def Run (MaxCycles=10, StartWord='MI', EndWord='MU', Workers=1): 52 | 53 | # Create list to hold strings, with a copy for each processor. 54 | WordList = [] 55 | for Process in range(0,Workers): 56 | WordList.append(StartWord) 57 | # Set up the environment with: a job server for multiple 58 | # worker processes, a cycle counter, and a timer. 59 | ppservers = () 60 | JobServer = pp.Server(Workers, ppservers=ppservers) 61 | #print 'Job server started with %s worker processes.' % str(JobServer.get_ncpus()) 62 | Cycles = 0 63 | InitialiseTime = time.time() 64 | 65 | while EndWord not in WordList: 66 | 67 | if Cycles >= MaxCycles: 68 | return "Sorry. '%s' wasn't found in %s cycle(s); took %.3f seconds on %s core(s)." %(EndWord, Cycles, (time.time()-InitialiseTime), Workers) 69 | 70 | else: 71 | StartTime = time.time() 72 | # If we haven't found the word, and haven't reached 73 | # the max no. of cycles yet, set the word list to all 74 | # the derivations of the previous one. 75 | WordList = DoleOutJobs(WordList, JobServer, Workers) 76 | Cycles += 1 77 | #print "Cycle %s completed: %s words in WordList after %s seconds." %(Cycles, len(WordList), (time.time()-StartTime)) 78 | 79 | # If the string ends up in the word list, the loop ends, 80 | # and we get to this return statement. 81 | return "Success! '%s' found in %s cycle(s); took %.3f seconds on %s core(s)." %(EndWord, Cycles, (time.time()-InitialiseTime), Workers) 82 | 83 | # If this script is run alone (ie, not imported by 84 | # another) then this bit is run. 85 | if __name__ == '__main__': 86 | 87 | # These IF statements just use the command line 88 | # arguments given to figure out what to do next. 89 | 90 | if len(sys.argv)<2: 91 | # If only the script name is given, prints the contents 92 | # of the INSTRUCTIONS file. 93 | InstructionsFile = open('INSTRUCTIONS', 'r') 94 | Instructions = InstructionsFile.read() 95 | InstructionsFile.close() 96 | print Instructions 97 | 98 | elif sys.argv[1] == 'a': 99 | print Run(20, 'MI', 'MU') 100 | 101 | elif len(sys.argv)<5: 102 | print Run(int(sys.argv[1]), str(sys.argv[2]), str(sys.argv[3])) 103 | 104 | else: 105 | print Run(int(sys.argv[1]), str(sys.argv[2]), str(sys.argv[3]), int(sys.argv[4])) 106 | 107 | --------------------------------------------------------------------------------