From f877d007bc363f965d46b71d9999bcaeeb3eeacf Mon Sep 17 00:00:00 2001 From: Mike Amaral Date: Tue, 21 Apr 2015 21:28:05 -0400 Subject: [PATCH] Initial commit. --- .gitignore | 8 + .travis.yml | 3 + Demo/AppDelegate.h | 18 + Demo/AppDelegate.m | 30 + Demo/Other/Base.lproj/LaunchScreen.xib | 41 + .../AppIcon.appiconset/Contents.json | 68 + Demo/Other/Supporting Files/Info.plist | 45 + Demo/Other/Supporting Files/main.m | 16 + Demo/WeaselProgramViewController.h | 19 + Demo/WeaselProgramViewController.m | 123 ++ Evolve.podspec | 15 + Evolve.xcodeproj/project.pbxproj | 1097 +++++++++++++++++ Evolve/UIColor_Hex.h | 13 + EvolveTests/ChromosomeTests.m | 111 ++ EvolveTests/EvolutionManagerTests.m | 217 ++++ EvolveTests/OrganismTests.m | 175 +++ EvolveTests/PopulationTests.m | 111 ++ EvolveTests/RandomTests.m | 98 ++ EvolveTests/Supporting Files/Info.plist | 24 + LICENSE.txt | 21 + README.md | 26 + Source/Chromosome.h | 21 + Source/Chromosome.m | 76 ++ Source/EvolutionManager.h | 36 + Source/EvolutionManager.m | 208 ++++ Source/Organism.h | 23 + Source/Organism.m | 75 ++ Source/Population.h | 21 + Source/Population.m | 50 + Source/Random.h | 17 + Source/Random.m | 31 + 31 files changed, 2837 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Demo/AppDelegate.h create mode 100644 Demo/AppDelegate.m create mode 100644 Demo/Other/Base.lproj/LaunchScreen.xib create mode 100644 Demo/Other/Images.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Demo/Other/Supporting Files/Info.plist create mode 100644 Demo/Other/Supporting Files/main.m create mode 100644 Demo/WeaselProgramViewController.h create mode 100644 Demo/WeaselProgramViewController.m create mode 100644 Evolve.podspec create mode 100644 Evolve.xcodeproj/project.pbxproj create mode 100644 Evolve/UIColor_Hex.h create mode 100644 EvolveTests/ChromosomeTests.m create mode 100644 EvolveTests/EvolutionManagerTests.m create mode 100644 EvolveTests/OrganismTests.m create mode 100644 EvolveTests/PopulationTests.m create mode 100644 EvolveTests/RandomTests.m create mode 100644 EvolveTests/Supporting Files/Info.plist create mode 100755 LICENSE.txt create mode 100644 README.md create mode 100644 Source/Chromosome.h create mode 100644 Source/Chromosome.m create mode 100644 Source/EvolutionManager.h create mode 100644 Source/EvolutionManager.m create mode 100644 Source/Organism.h create mode 100644 Source/Organism.m create mode 100644 Source/Population.h create mode 100644 Source/Population.m create mode 100644 Source/Random.h create mode 100644 Source/Random.m diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..443a64b --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +#Xcode +*.pbuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 +*.xcuserstate +project.xcworkspace/ +xcuserdata/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..5c21bab --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: objective-c +script: + - xctool -project Evolve.xcodeproj -sdk iphonesimulator -scheme Evolve build diff --git a/Demo/AppDelegate.h b/Demo/AppDelegate.h new file mode 100644 index 0000000..a3e8674 --- /dev/null +++ b/Demo/AppDelegate.h @@ -0,0 +1,18 @@ +// +// AppDelegate.h +// Evolve +// +// Created by Mike on 3/23/15. +// Copyright (c) 2015 Mike Amaral. All rights reserved. +// + +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + + + +@end + diff --git a/Demo/AppDelegate.m b/Demo/AppDelegate.m new file mode 100644 index 0000000..516fce5 --- /dev/null +++ b/Demo/AppDelegate.m @@ -0,0 +1,30 @@ +// +// AppDelegate.m +// Evolve +// +// Created by Mike on 3/23/15. +// Copyright (c) 2015 Mike Amaral. All rights reserved. +// + +#import "AppDelegate.h" +#import "WeaselProgramViewController.h" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window.backgroundColor = [UIColor whiteColor]; + + self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[WeaselProgramViewController new]]; + + [self.window makeKeyAndVisible]; + + return YES; +} + +@end diff --git a/Demo/Other/Base.lproj/LaunchScreen.xib b/Demo/Other/Base.lproj/LaunchScreen.xib new file mode 100644 index 0000000..37d4c3c --- /dev/null +++ b/Demo/Other/Base.lproj/LaunchScreen.xib @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Demo/Other/Images.xcassets/AppIcon.appiconset/Contents.json b/Demo/Other/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..36d2c80 --- /dev/null +++ b/Demo/Other/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Demo/Other/Supporting Files/Info.plist b/Demo/Other/Supporting Files/Info.plist new file mode 100644 index 0000000..06b9c1d --- /dev/null +++ b/Demo/Other/Supporting Files/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.MikeAmaral.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Demo/Other/Supporting Files/main.m b/Demo/Other/Supporting Files/main.m new file mode 100644 index 0000000..94daa7d --- /dev/null +++ b/Demo/Other/Supporting Files/main.m @@ -0,0 +1,16 @@ +// +// main.m +// Evolve +// +// Created by Mike on 3/23/15. +// Copyright (c) 2015 Mike Amaral. All rights reserved. +// + +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/Demo/WeaselProgramViewController.h b/Demo/WeaselProgramViewController.h new file mode 100644 index 0000000..7558148 --- /dev/null +++ b/Demo/WeaselProgramViewController.h @@ -0,0 +1,19 @@ +// +// WeaselProgramViewController.h +// Evolve +// +// Created by Mike on 4/18/15. +// Copyright (c) 2015 Mike Amaral. All rights reserved. +// + +#import +#import "EvolutionManager.h" + +@interface WeaselProgramViewController : UIViewController + +@property (nonatomic, strong) NSTimer *timer; +@property (nonatomic, strong) UILabel *fittestOrganismLabel; + +@property (nonatomic, strong) EvolutionManager *evolutionManager; + +@end diff --git a/Demo/WeaselProgramViewController.m b/Demo/WeaselProgramViewController.m new file mode 100644 index 0000000..34cdaad --- /dev/null +++ b/Demo/WeaselProgramViewController.m @@ -0,0 +1,123 @@ +// +// WeaselProgramViewController.m +// Evolve +// +// Created by Mike on 4/18/15. +// Copyright (c) 2015 Mike Amaral. All rights reserved. +// + +#import "WeaselProgramViewController.h" + +static NSString * const kTargetString = @"METHINKS IT IS LIKE A WEASEL"; +static NSString * const kTargetDomain = @"ABCDEFGHIJKLMNOPQRSTUVWXYZ "; +static NSTimeInterval const kTimeIntervalPerGeneration = 0.05; + +@interface WeaselProgramViewController () + +@end + +@implementation WeaselProgramViewController + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + // Set up the demo UI. + self.title = @"The Weasel Program"; + + self.fittestOrganismLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 50)]; + self.fittestOrganismLabel.center = self.view.center; + self.fittestOrganismLabel.textAlignment = NSTextAlignmentCenter; + self.fittestOrganismLabel.numberOfLines = 2; + [self.view addSubview:self.fittestOrganismLabel]; + + // Add a nav button to trigger starting the simulation. + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Start" style:UIBarButtonItemStylePlain target:self action:@selector(startSimulation)]; +} + + +#pragma mark - Starting the simulation + +- (void)startSimulation { + // Stop the timer if it's already running. + if (self.timer.isValid) { + [self.timer invalidate]; + } + + // Reset the UI for the start of the simulation. + self.fittestOrganismLabel.textColor = [UIColor blackColor]; + + // Reset our population and start. + [self configurePopulationAndStart]; +} + + +#pragma mark - Population initialization + +- (void)configurePopulationAndStart { + // Create our population - which will be 50 organisms in size, will have a chromosome the length of our target string, + // and will have the domain defined above, which includes all capital letters and the space character. + Population *startingPopulation = [[Population alloc] initRandomPopulationWithSize:50 geneSequenceLength:kTargetString.length chromosomeDomain:kTargetDomain]; + + // Create our evolution manager with the starting population and set ourself as the delegate to recieve the appropriate callbacks. + self.evolutionManager = [[EvolutionManager alloc] initWithPopulation:startingPopulation]; + self.evolutionManager.delegate = self; + + // Start the simulation timer. + self.timer = [NSTimer scheduledTimerWithTimeInterval:kTimeIntervalPerGeneration target:self selector:@selector(continueWithNextGeneration) userInfo:nil repeats:YES]; +} + +- (void)continueWithNextGeneration { + [self evaluateFitnessForPopulation:self.evolutionManager.population]; + [self.evolutionManager processNextGeneration]; +} + +- (void)evaluateFitnessForPopulation:(Population *)population { + // Pass all the organisms through our fitness function. + for (Organism *organism in population.organisms) { + organism.fitness = [self fitnessFunctionForOrganism:organism]; + } +} + +- (NSInteger)fitnessFunctionForOrganism:(Organism *)organism { + // Get the chromosome string from this organism. + NSString *chromosomeString = organism.chromosome.geneSequence; + + // We're going to keep track of the number of characters in the chromosome that + // match our target string at the correct index. Each match increases the "fitness" + // of this organism. + NSInteger correctCharacters = 0; + + for (NSInteger charIndex = 0; charIndex < organism.chromosome.geneSequence.length; charIndex++) { + if ([chromosomeString characterAtIndex:charIndex] == [kTargetString characterAtIndex:charIndex]) { + correctCharacters++; + } + } + + return correctCharacters; +} + + +#pragma mark - Evolution delegate + +- (void)population:(Population *)population didCompetedGeneration:(NSUInteger)generation fittestOrganisms:(NSArray *)fittestOrganisms offspring:(NSArray *)offspring completeNextGeneration:(NSArray *)nextGeneration { + // Get the fittest organism for this generation, which will be the first object in the fittest organisms array. + Organism *fittestOrganism = [fittestOrganisms firstObject]; + + // Get the string representation of the chromosome. + NSString *chromosomeString = fittestOrganism.chromosome.geneSequence; + + // If it equals our target string, we can end the simulation. + if ([chromosomeString isEqualToString:kTargetString]) { + self.fittestOrganismLabel.text = [NSString stringWithFormat:@"Target organism acheived in generation %ld:\n%@", generation, chromosomeString]; + self.fittestOrganismLabel.textColor = [UIColor redColor]; + + [self.timer invalidate]; + } + + // Otherwise just print out the generation number and the chromosome string for a visual representation of this generation. + else { + self.fittestOrganismLabel.text = [NSString stringWithFormat:@"Fittest organism for generation %ld:\n%@", generation, chromosomeString]; + } +} + +@end diff --git a/Evolve.podspec b/Evolve.podspec new file mode 100644 index 0000000..80fb707 --- /dev/null +++ b/Evolve.podspec @@ -0,0 +1,15 @@ +Pod::Spec.new do |s| + + s.name = "Evolve" + s.version = "0.1" + s.summary = "An Objective-C evolution simulation engine." + s.homepage = "https://github.com/mamaral/Evolve" + s.license = "MIT" + s.author = { "Mike Amaral" => "mike.amaral36@gmail.com" } + s.social_media_url = "http://twitter.com/MikeAmaral" + s.platform = :ios + s.source = { :git => "https://github.com/mamaral/Evolve.git", :tag => "v0.1" } + s.source_files = "Evolve/Source/EvolutionManager.{h,m}", "Evolve/Source/Population.{h,m}", "Evolve/Source/Organism.{h,m}", "Evolve/Source/Chromosome.{h,m}", "Evolve/Source/Random.{h,m}" + s.requires_arc = true + +end diff --git a/Evolve.xcodeproj/project.pbxproj b/Evolve.xcodeproj/project.pbxproj new file mode 100644 index 0000000..717250c --- /dev/null +++ b/Evolve.xcodeproj/project.pbxproj @@ -0,0 +1,1097 @@ + + + + + archiveVersion + 1 + classes + + objectVersion + 46 + objects + + 276B673E1AC0CBE700F9A222 + + children + + 276B676A1AC0CBEE00F9A222 + 276B675D1AC0CBE700F9A222 + 276B67491AC0CBE700F9A222 + + isa + PBXGroup + sourceTree + <group> + + 276B673F1AC0CBE700F9A222 + + attributes + + LastUpgradeCheck + 0600 + ORGANIZATIONNAME + Mike Amaral + TargetAttributes + + 276B67461AC0CBE700F9A222 + + CreatedOnToolsVersion + 6.0 + + 276B67591AC0CBE700F9A222 + + CreatedOnToolsVersion + 6.0 + TestTargetID + 276B67461AC0CBE700F9A222 + + + + buildConfigurationList + 276B67421AC0CBE700F9A222 + compatibilityVersion + Xcode 3.2 + developmentRegion + English + hasScannedForEncodings + 0 + isa + PBXProject + knownRegions + + en + Base + + mainGroup + 276B673E1AC0CBE700F9A222 + productRefGroup + 276B67481AC0CBE700F9A222 + projectDirPath + + projectReferences + + projectRoot + + targets + + 276B67461AC0CBE700F9A222 + 276B67591AC0CBE700F9A222 + + + 276B67421AC0CBE700F9A222 + + buildConfigurations + + 276B67621AC0CBE700F9A222 + 276B67631AC0CBE700F9A222 + + defaultConfigurationIsVisible + 0 + defaultConfigurationName + Release + isa + XCConfigurationList + + 276B67431AC0CBE700F9A222 + + buildActionMask + 2147483647 + files + + 276B67501AC0CBE700F9A222 + 276B67771AC0CC5500F9A222 + 27CC1D521AE2F2D100CAA20E + 276B674D1AC0CBE700F9A222 + 276B67711AC0CC2200F9A222 + 27DE3BBA1AE5C9CD00499D8D + 276B676E1AC0CC1B00F9A222 + 276B67741AC0CC3200F9A222 + + isa + PBXSourcesBuildPhase + runOnlyForDeploymentPostprocessing + 0 + + 276B67441AC0CBE700F9A222 + + buildActionMask + 2147483647 + files + + isa + PBXFrameworksBuildPhase + runOnlyForDeploymentPostprocessing + 0 + + 276B67451AC0CBE700F9A222 + + buildActionMask + 2147483647 + files + + 276B67551AC0CBE700F9A222 + 276B67521AC0CBE700F9A222 + + isa + PBXResourcesBuildPhase + runOnlyForDeploymentPostprocessing + 0 + + 276B67461AC0CBE700F9A222 + + buildConfigurationList + 276B67641AC0CBE700F9A222 + buildPhases + + 276B67431AC0CBE700F9A222 + 276B67441AC0CBE700F9A222 + 276B67451AC0CBE700F9A222 + + buildRules + + dependencies + + isa + PBXNativeTarget + name + Evolve + productName + Evolve + productReference + 276B67471AC0CBE700F9A222 + productType + com.apple.product-type.application + + 276B67471AC0CBE700F9A222 + + explicitFileType + wrapper.application + includeInIndex + 0 + isa + PBXFileReference + path + Evolve.app + sourceTree + BUILT_PRODUCTS_DIR + + 276B67481AC0CBE700F9A222 + + children + + 276B67471AC0CBE700F9A222 + 276B675A1AC0CBE700F9A222 + + isa + PBXGroup + name + Products + path + Products + sourceTree + <group> + + 276B67491AC0CBE700F9A222 + + children + + 276B674E1AC0CBE700F9A222 + 276B674F1AC0CBE700F9A222 + 27CC1D501AE2F2D100CAA20E + 27CC1D511AE2F2D100CAA20E + 276B676B1AC0CC0900F9A222 + + isa + PBXGroup + name + Demo + path + Demo + sourceTree + <group> + + 276B674A1AC0CBE700F9A222 + + children + + 276B674B1AC0CBE700F9A222 + 276B674C1AC0CBE700F9A222 + + isa + PBXGroup + name + Supporting Files + path + Supporting Files + sourceTree + <group> + + 276B674B1AC0CBE700F9A222 + + isa + PBXFileReference + lastKnownFileType + text.plist.xml + path + Info.plist + sourceTree + <group> + + 276B674C1AC0CBE700F9A222 + + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + main.m + sourceTree + <group> + + 276B674D1AC0CBE700F9A222 + + fileRef + 276B674C1AC0CBE700F9A222 + isa + PBXBuildFile + + 276B674E1AC0CBE700F9A222 + + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + AppDelegate.h + sourceTree + <group> + + 276B674F1AC0CBE700F9A222 + + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + AppDelegate.m + sourceTree + <group> + + 276B67501AC0CBE700F9A222 + + fileRef + 276B674F1AC0CBE700F9A222 + isa + PBXBuildFile + + 276B67511AC0CBE700F9A222 + + isa + PBXFileReference + lastKnownFileType + folder.assetcatalog + path + Images.xcassets + sourceTree + <group> + + 276B67521AC0CBE700F9A222 + + fileRef + 276B67511AC0CBE700F9A222 + isa + PBXBuildFile + + 276B67531AC0CBE700F9A222 + + children + + 276B67541AC0CBE700F9A222 + + isa + PBXVariantGroup + name + LaunchScreen.xib + path + . + sourceTree + <group> + + 276B67541AC0CBE700F9A222 + + isa + PBXFileReference + lastKnownFileType + file.xib + name + Base + path + Base.lproj/LaunchScreen.xib + sourceTree + <group> + + 276B67551AC0CBE700F9A222 + + fileRef + 276B67531AC0CBE700F9A222 + isa + PBXBuildFile + + 276B67561AC0CBE700F9A222 + + buildActionMask + 2147483647 + files + + 27DE3BBC1AE5E5AE00499D8D + 27DE3BC01AE5F6DE00499D8D + 27DE3BC21AE5FFB700499D8D + 27DE3BBE1AE5F29F00499D8D + 27DE3BC41AE602F900499D8D + + isa + PBXSourcesBuildPhase + runOnlyForDeploymentPostprocessing + 0 + + 276B67571AC0CBE700F9A222 + + buildActionMask + 2147483647 + files + + isa + PBXFrameworksBuildPhase + runOnlyForDeploymentPostprocessing + 0 + + 276B67581AC0CBE700F9A222 + + buildActionMask + 2147483647 + files + + isa + PBXResourcesBuildPhase + runOnlyForDeploymentPostprocessing + 0 + + 276B67591AC0CBE700F9A222 + + buildConfigurationList + 276B67671AC0CBE700F9A222 + buildPhases + + 276B67561AC0CBE700F9A222 + 276B67571AC0CBE700F9A222 + 276B67581AC0CBE700F9A222 + + buildRules + + dependencies + + 276B675C1AC0CBE700F9A222 + + isa + PBXNativeTarget + name + EvolveTests + productName + EvolveTests + productReference + 276B675A1AC0CBE700F9A222 + productType + com.apple.product-type.bundle.unit-test + + 276B675A1AC0CBE700F9A222 + + explicitFileType + wrapper.cfbundle + includeInIndex + 0 + isa + PBXFileReference + path + EvolveTests.xctest + sourceTree + BUILT_PRODUCTS_DIR + + 276B675B1AC0CBE700F9A222 + + containerPortal + 276B673F1AC0CBE700F9A222 + isa + PBXContainerItemProxy + proxyType + 1 + remoteGlobalIDString + 276B67461AC0CBE700F9A222 + remoteInfo + Evolve + + 276B675C1AC0CBE700F9A222 + + isa + PBXTargetDependency + target + 276B67461AC0CBE700F9A222 + targetProxy + 276B675B1AC0CBE700F9A222 + + 276B675D1AC0CBE700F9A222 + + children + + 27DE3BC31AE602F900499D8D + 27DE3BC11AE5FFB700499D8D + 27DE3BBF1AE5F6DE00499D8D + 27DE3BBD1AE5F29F00499D8D + 27DE3BBB1AE5E5AE00499D8D + 276B675E1AC0CBE700F9A222 + + isa + PBXGroup + path + EvolveTests + sourceTree + <group> + + 276B675E1AC0CBE700F9A222 + + children + + 276B675F1AC0CBE700F9A222 + + isa + PBXGroup + name + Supporting Files + path + Supporting Files + sourceTree + <group> + + 276B675F1AC0CBE700F9A222 + + isa + PBXFileReference + lastKnownFileType + text.plist.xml + path + Info.plist + sourceTree + <group> + + 276B67621AC0CBE700F9A222 + + buildSettings + + ALWAYS_SEARCH_USER_PATHS + NO + CLANG_CXX_LANGUAGE_STANDARD + gnu++0x + CLANG_CXX_LIBRARY + libc++ + CLANG_ENABLE_MODULES + YES + CLANG_ENABLE_OBJC_ARC + YES + CLANG_WARN_BOOL_CONVERSION + YES + CLANG_WARN_CONSTANT_CONVERSION + YES + CLANG_WARN_DIRECT_OBJC_ISA_USAGE + YES_ERROR + CLANG_WARN_EMPTY_BODY + YES + CLANG_WARN_ENUM_CONVERSION + YES + CLANG_WARN_INT_CONVERSION + YES + CLANG_WARN_OBJC_ROOT_CLASS + YES_ERROR + CLANG_WARN_UNREACHABLE_CODE + YES + CLANG_WARN__DUPLICATE_METHOD_MATCH + YES + CODE_SIGN_IDENTITY[sdk=iphoneos*] + iPhone Developer + COPY_PHASE_STRIP + NO + ENABLE_STRICT_OBJC_MSGSEND + YES + GCC_C_LANGUAGE_STANDARD + gnu99 + GCC_DYNAMIC_NO_PIC + NO + GCC_OPTIMIZATION_LEVEL + 0 + GCC_PREPROCESSOR_DEFINITIONS + + DEBUG=1 + $(inherited) + + GCC_SYMBOLS_PRIVATE_EXTERN + NO + GCC_WARN_64_TO_32_BIT_CONVERSION + YES + GCC_WARN_ABOUT_RETURN_TYPE + YES_ERROR + GCC_WARN_UNDECLARED_SELECTOR + YES + GCC_WARN_UNINITIALIZED_AUTOS + YES_AGGRESSIVE + GCC_WARN_UNUSED_FUNCTION + YES + GCC_WARN_UNUSED_VARIABLE + YES + IPHONEOS_DEPLOYMENT_TARGET + 8.0 + MTL_ENABLE_DEBUG_INFO + YES + ONLY_ACTIVE_ARCH + YES + SDKROOT + iphoneos + TARGETED_DEVICE_FAMILY + 1,2 + + isa + XCBuildConfiguration + name + Debug + + 276B67631AC0CBE700F9A222 + + buildSettings + + ALWAYS_SEARCH_USER_PATHS + NO + CLANG_CXX_LANGUAGE_STANDARD + gnu++0x + CLANG_CXX_LIBRARY + libc++ + CLANG_ENABLE_MODULES + YES + CLANG_ENABLE_OBJC_ARC + YES + CLANG_WARN_BOOL_CONVERSION + YES + CLANG_WARN_CONSTANT_CONVERSION + YES + CLANG_WARN_DIRECT_OBJC_ISA_USAGE + YES_ERROR + CLANG_WARN_EMPTY_BODY + YES + CLANG_WARN_ENUM_CONVERSION + YES + CLANG_WARN_INT_CONVERSION + YES + CLANG_WARN_OBJC_ROOT_CLASS + YES_ERROR + CLANG_WARN_UNREACHABLE_CODE + YES + CLANG_WARN__DUPLICATE_METHOD_MATCH + YES + CODE_SIGN_IDENTITY[sdk=iphoneos*] + iPhone Developer + COPY_PHASE_STRIP + YES + ENABLE_NS_ASSERTIONS + NO + ENABLE_STRICT_OBJC_MSGSEND + YES + GCC_C_LANGUAGE_STANDARD + gnu99 + GCC_WARN_64_TO_32_BIT_CONVERSION + YES + GCC_WARN_ABOUT_RETURN_TYPE + YES_ERROR + GCC_WARN_UNDECLARED_SELECTOR + YES + GCC_WARN_UNINITIALIZED_AUTOS + YES_AGGRESSIVE + GCC_WARN_UNUSED_FUNCTION + YES + GCC_WARN_UNUSED_VARIABLE + YES + IPHONEOS_DEPLOYMENT_TARGET + 8.0 + MTL_ENABLE_DEBUG_INFO + NO + SDKROOT + iphoneos + TARGETED_DEVICE_FAMILY + 1,2 + VALIDATE_PRODUCT + YES + + isa + XCBuildConfiguration + name + Release + + 276B67641AC0CBE700F9A222 + + buildConfigurations + + 276B67651AC0CBE700F9A222 + 276B67661AC0CBE700F9A222 + + defaultConfigurationIsVisible + 0 + defaultConfigurationName + Release + isa + XCConfigurationList + + 276B67651AC0CBE700F9A222 + + buildSettings + + ASSETCATALOG_COMPILER_APPICON_NAME + AppIcon + INFOPLIST_FILE + Demo/Other/Supporting Files/Info.plist + LD_RUNPATH_SEARCH_PATHS + $(inherited) @executable_path/Frameworks + PRODUCT_NAME + $(TARGET_NAME) + + isa + XCBuildConfiguration + name + Debug + + 276B67661AC0CBE700F9A222 + + buildSettings + + ASSETCATALOG_COMPILER_APPICON_NAME + AppIcon + INFOPLIST_FILE + Demo/Other/Supporting Files/Info.plist + LD_RUNPATH_SEARCH_PATHS + $(inherited) @executable_path/Frameworks + PRODUCT_NAME + $(TARGET_NAME) + + isa + XCBuildConfiguration + name + Release + + 276B67671AC0CBE700F9A222 + + buildConfigurations + + 276B67681AC0CBE700F9A222 + 276B67691AC0CBE700F9A222 + + defaultConfigurationIsVisible + 0 + defaultConfigurationName + Release + isa + XCConfigurationList + + 276B67681AC0CBE700F9A222 + + buildSettings + + BUNDLE_LOADER + $(TEST_HOST) + FRAMEWORK_SEARCH_PATHS + + $(SDKROOT)/Developer/Library/Frameworks + $(inherited) + + GCC_PREPROCESSOR_DEFINITIONS + + DEBUG=1 + $(inherited) + + INFOPLIST_FILE + EvolveTests/Supporting Files/Info.plist + LD_RUNPATH_SEARCH_PATHS + $(inherited) @executable_path/Frameworks @loader_path/Frameworks + PRODUCT_NAME + $(TARGET_NAME) + TEST_HOST + $(BUILT_PRODUCTS_DIR)/Evolve.app/Evolve + + isa + XCBuildConfiguration + name + Debug + + 276B67691AC0CBE700F9A222 + + buildSettings + + BUNDLE_LOADER + $(TEST_HOST) + FRAMEWORK_SEARCH_PATHS + + $(SDKROOT)/Developer/Library/Frameworks + $(inherited) + + INFOPLIST_FILE + EvolveTests/Supporting Files/Info.plist + LD_RUNPATH_SEARCH_PATHS + $(inherited) @executable_path/Frameworks @loader_path/Frameworks + PRODUCT_NAME + $(TARGET_NAME) + TEST_HOST + $(BUILT_PRODUCTS_DIR)/Evolve.app/Evolve + + isa + XCBuildConfiguration + name + Release + + 276B676A1AC0CBEE00F9A222 + + children + + 27DE3BB81AE5C9CD00499D8D + 27DE3BB91AE5C9CD00499D8D + 276B676F1AC0CC2200F9A222 + 276B67701AC0CC2200F9A222 + 276B67751AC0CC5500F9A222 + 276B67761AC0CC5500F9A222 + 276B676C1AC0CC1B00F9A222 + 276B676D1AC0CC1B00F9A222 + 276B67721AC0CC3200F9A222 + 276B67731AC0CC3200F9A222 + + isa + PBXGroup + name + Source + path + Source + sourceTree + <group> + + 276B676B1AC0CC0900F9A222 + + children + + 276B67511AC0CBE700F9A222 + 276B67531AC0CBE700F9A222 + 276B674A1AC0CBE700F9A222 + 276B67481AC0CBE700F9A222 + + isa + PBXGroup + name + Other + path + Other + sourceTree + <group> + + 276B676C1AC0CC1B00F9A222 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + Chromosome.h + sourceTree + <group> + + 276B676D1AC0CC1B00F9A222 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + Chromosome.m + sourceTree + <group> + + 276B676E1AC0CC1B00F9A222 + + fileRef + 276B676D1AC0CC1B00F9A222 + isa + PBXBuildFile + + 276B676F1AC0CC2200F9A222 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + Population.h + sourceTree + <group> + + 276B67701AC0CC2200F9A222 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + Population.m + sourceTree + <group> + + 276B67711AC0CC2200F9A222 + + fileRef + 276B67701AC0CC2200F9A222 + isa + PBXBuildFile + + 276B67721AC0CC3200F9A222 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + Random.h + sourceTree + <group> + + 276B67731AC0CC3200F9A222 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + Random.m + sourceTree + <group> + + 276B67741AC0CC3200F9A222 + + fileRef + 276B67731AC0CC3200F9A222 + isa + PBXBuildFile + + 276B67751AC0CC5500F9A222 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + Organism.h + sourceTree + <group> + + 276B67761AC0CC5500F9A222 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + Organism.m + sourceTree + <group> + + 276B67771AC0CC5500F9A222 + + fileRef + 276B67761AC0CC5500F9A222 + isa + PBXBuildFile + + 27CC1D501AE2F2D100CAA20E + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + WeaselProgramViewController.h + sourceTree + <group> + + 27CC1D511AE2F2D100CAA20E + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + WeaselProgramViewController.m + sourceTree + <group> + + 27CC1D521AE2F2D100CAA20E + + fileRef + 27CC1D511AE2F2D100CAA20E + isa + PBXBuildFile + + 27DE3BB81AE5C9CD00499D8D + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + EvolutionManager.h + sourceTree + <group> + + 27DE3BB91AE5C9CD00499D8D + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + EvolutionManager.m + sourceTree + <group> + + 27DE3BBA1AE5C9CD00499D8D + + fileRef + 27DE3BB91AE5C9CD00499D8D + isa + PBXBuildFile + + 27DE3BBB1AE5E5AE00499D8D + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + RandomTests.m + sourceTree + <group> + + 27DE3BBC1AE5E5AE00499D8D + + fileRef + 27DE3BBB1AE5E5AE00499D8D + isa + PBXBuildFile + + 27DE3BBD1AE5F29F00499D8D + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + ChromosomeTests.m + sourceTree + <group> + + 27DE3BBE1AE5F29F00499D8D + + fileRef + 27DE3BBD1AE5F29F00499D8D + isa + PBXBuildFile + + 27DE3BBF1AE5F6DE00499D8D + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + OrganismTests.m + sourceTree + <group> + + 27DE3BC01AE5F6DE00499D8D + + fileRef + 27DE3BBF1AE5F6DE00499D8D + isa + PBXBuildFile + + 27DE3BC11AE5FFB700499D8D + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + PopulationTests.m + sourceTree + <group> + + 27DE3BC21AE5FFB700499D8D + + fileRef + 27DE3BC11AE5FFB700499D8D + isa + PBXBuildFile + + 27DE3BC31AE602F900499D8D + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + EvolutionManagerTests.m + sourceTree + <group> + + 27DE3BC41AE602F900499D8D + + fileRef + 27DE3BC31AE602F900499D8D + isa + PBXBuildFile + + + rootObject + 276B673F1AC0CBE700F9A222 + + diff --git a/Evolve/UIColor_Hex.h b/Evolve/UIColor_Hex.h new file mode 100644 index 0000000..3ef9776 --- /dev/null +++ b/Evolve/UIColor_Hex.h @@ -0,0 +1,13 @@ +// +// UIColor_Hex.h +// Evolve +// +// Created by Mike on 3/28/15. +// Copyright (c) 2015 Mike Amaral. All rights reserved. +// + +#import + +@interface UIColor () + +@end diff --git a/EvolveTests/ChromosomeTests.m b/EvolveTests/ChromosomeTests.m new file mode 100644 index 0000000..64c8d94 --- /dev/null +++ b/EvolveTests/ChromosomeTests.m @@ -0,0 +1,111 @@ +// +// ChromosomeTests.m +// Evolve +// +// Created by Mike on 4/20/15. +// Copyright (c) 2015 Mike Amaral. All rights reserved. +// + +#import +#import +#import "Chromosome.h" +#import "Random.h" + +static NSInteger const kChromosomeTestIterations = 10000; + +@interface ChromosomeTests : XCTestCase + +@end + +@implementation ChromosomeTests + +- (void)testInitWithGeneSequenceAndDomain { + NSString *entireDomain = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789"; + + for (NSInteger i = 0; i < kChromosomeTestIterations; i++) { + NSInteger randomSequenceLength = [Random randomIntegerFromMin:1 toMax:50]; + NSInteger randomDomainLength = [Random randomIntegerFromMin:1 toMax:25]; + NSString *randomDomain = [Random randomGeneSequenceWithLength:randomDomainLength domain:entireDomain]; + NSString *randomGeneSequence = [Random randomGeneSequenceWithLength:randomSequenceLength domain:randomDomain]; + + Chromosome *chromosome = [[Chromosome alloc] initWithGeneSequence:randomGeneSequence domain:randomDomain]; + + XCTAssertNotNil(chromosome); + XCTAssert([chromosome.geneSequence isEqualToString:randomGeneSequence]); + XCTAssert([chromosome.domain isEqualToString:randomDomain]); + } +} + +- (void)testInitRandomWithLengthAndDomain { + NSString *entireDomain = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789"; + + for (NSInteger i = 0; i < kChromosomeTestIterations; i++) { + NSInteger randomSequenceLength = [Random randomIntegerFromMin:1 toMax:50]; + NSInteger randomDomainLength = [Random randomIntegerFromMin:1 toMax:25]; + NSString *randomDomain = [Random randomGeneSequenceWithLength:randomDomainLength domain:entireDomain]; + + Chromosome *chromosome = [[Chromosome alloc] initRandomChromosomeWithLength:randomSequenceLength domain:randomDomain]; + + XCTAssertNotNil(chromosome); + XCTAssertEqual(chromosome.geneSequence.length, randomSequenceLength); + XCTAssert([chromosome.domain isEqualToString:randomDomain]); + } +} + +- (void)testInitWithNilGeneSequence { + void (^expressionBlock)() = ^{ + __unused Chromosome *testChromosome = [[Chromosome alloc] initWithGeneSequence:nil domain:@"abcd"]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testInitWithInvalidGeneSequence { + void (^expressionBlock)() = ^{ + __unused Chromosome *testChromosome = [[Chromosome alloc] initWithGeneSequence:@"" domain:@"abcd"]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testInitWithNilDomain { + void (^expressionBlock)() = ^{ + __unused Chromosome *testChromosome = [[Chromosome alloc] initWithGeneSequence:@"abcd" domain:nil]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testInitWithInvalidDomain { + void (^expressionBlock)() = ^{ + __unused Chromosome *testChromosome = [[Chromosome alloc] initWithGeneSequence:@"abcd" domain:@""]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testInitRandomWithInvalidLength { + void (^expressionBlock)() = ^{ + __unused Chromosome *testChromosome = [[Chromosome alloc] initRandomChromosomeWithLength:0 domain:@"abcd"]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testInitRandomWithNilDomain { + void (^expressionBlock)() = ^{ + __unused Chromosome *testChromosome = [[Chromosome alloc] initRandomChromosomeWithLength:5 domain:nil]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testInitRandomWithInvalidDomain { + void (^expressionBlock)() = ^{ + __unused Chromosome *testChromosome = [[Chromosome alloc] initRandomChromosomeWithLength:5 domain:@""]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +@end diff --git a/EvolveTests/EvolutionManagerTests.m b/EvolveTests/EvolutionManagerTests.m new file mode 100644 index 0000000..3821d7b --- /dev/null +++ b/EvolveTests/EvolutionManagerTests.m @@ -0,0 +1,217 @@ +// +// EvolutionManagerTests.m +// Evolve +// +// Created by Mike on 4/21/15. +// Copyright (c) 2015 Mike Amaral. All rights reserved. +// + +#import +#import +#import "EvolutionManager.h" + +static NSInteger const kEvolutionManagerTestIterations = 250; + + +@interface EvolutionManager (Testing) + +- (NSArray *)sortOrganismsByFitness:(NSArray *)organisms; +- (NSInteger)calculateNumberOfMates; +- (NSInteger)calculateNumberOfOrganismsSurviving; +- (NSInteger)calculateNumberOfOffspringFromSurvivors:(NSInteger)survivorCount; +- (NSArray *)generateOffspringFromOrganisms:(NSArray *)parents count:(NSInteger)offspringCount; +- (NSArray *)survivorsToNextGenerationWithCandidates:(NSArray *)candidates count:(NSInteger)count; + +@end + + +@interface EvolutionManagerTests : XCTestCase { + EvolutionManager *_testManager; +} + +@end + +@implementation EvolutionManagerTests + +- (void)setUp { + [super setUp]; + + NSInteger randomNumberOfOrganisms = [Random randomIntegerFromMin:100 toMax:500]; + NSMutableArray *organisms = [NSMutableArray arrayWithCapacity:randomNumberOfOrganisms]; + + for (NSInteger i = 0; i < randomNumberOfOrganisms; i++) { + Chromosome *chromosome = [[Chromosome alloc] initRandomChromosomeWithLength:5 domain:@"abcd"]; + [organisms addObject:[[Organism alloc] initWithChomosome:chromosome]]; + } + + Population *testPopulation = [[Population alloc] initWithOrganisms:organisms]; + + _testManager = [[EvolutionManager alloc] initWithPopulation:testPopulation]; + _testManager.delegate = self; +} + +- (void)tearDown { + _testManager = nil; + + [super tearDown]; +} + +- (void)testInitWithPopulation { + for (NSInteger i = 0; i < kEvolutionManagerTestIterations; i++) { + NSInteger randomSize = [Random randomIntegerFromMin:2 toMax:100]; + NSInteger randomLength = [Random randomIntegerFromMin:1 toMax:10]; + NSString *domain = @"abcd"; + Population *population = [[Population alloc] initRandomPopulationWithSize:randomSize geneSequenceLength:randomLength chromosomeDomain:domain]; + + EvolutionManager *manager = [[EvolutionManager alloc] initWithPopulation:population]; + + XCTAssertNotNil(manager); + XCTAssertEqualObjects(manager.population, population); + } +} + +- (void)testInitWithInvalidPopulation { + void (^expressionBlock)() = ^{ + __unused EvolutionManager *manager = [[EvolutionManager alloc] initWithPopulation:nil]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testSortOrganismsByFitness { + NSInteger minFitness = 0; + NSInteger maxFitness = 100; + for (Organism *organism in _testManager.population.organisms) { + organism.fitness = [Random randomIntegerFromMin:minFitness toMax:maxFitness]; + } + + NSArray *sortedOrganisms = [_testManager sortOrganismsByFitness:_testManager.population.organisms]; + + NSInteger previousFitness = maxFitness + 1; + for (Organism *organism in sortedOrganisms) { + XCTAssert(organism.fitness <= maxFitness); + XCTAssert(organism.fitness <= previousFitness); + XCTAssert(organism.fitness >= minFitness); + + previousFitness = organism.fitness; + } +} + +- (void)testCalcuateNumberOfMates { + for (NSInteger i = 0; i < kEvolutionManagerTestIterations; i++) { + CGFloat reproductionRate = [Random randomIntegerFromMin:1 toMax:100] / 100.0; + _testManager.percentageOfOrganismsThatReproduce = reproductionRate; + + NSInteger numberOfMates = [_testManager calculateNumberOfMates]; + + XCTAssertEqual(numberOfMates, round(_testManager.population.organisms.count * reproductionRate)); + } +} + +- (void)testCalcuateNumberOfSurvivors { + for (NSInteger i = 0; i < kEvolutionManagerTestIterations; i++) { + CGFloat survivalRate = [Random randomIntegerFromMin:1 toMax:99] / 100.0; + _testManager.percentageOfOrganismsThatSurvive = survivalRate; + + NSInteger numberOfSurvivors = [_testManager calculateNumberOfOrganismsSurviving]; + + XCTAssertEqual(numberOfSurvivors, round(_testManager.population.organisms.count * survivalRate)); + } +} + +- (void)testCalculateNumberOfOffspring { + for (NSInteger i = 0; i < kEvolutionManagerTestIterations; i++) { + NSInteger randomSurvivorCount = [Random randomIntegerFromMin:0 toMax:_testManager.population.organisms.count - 1]; + NSInteger numberOfOffspring = [_testManager calculateNumberOfOffspringFromSurvivors:randomSurvivorCount]; + + XCTAssertEqual(numberOfOffspring, _testManager.population.organisms.count - randomSurvivorCount); + } +} + +- (void)testProcessNextGeneration { + for (NSInteger i = 1; i < kEvolutionManagerTestIterations; i++) { + NSInteger beforeOrganismCount = _testManager.population.organisms.count; + + [_testManager processNextGeneration]; + + NSInteger afterOrganismCount = _testManager.population.organisms.count; + + XCTAssertEqual(beforeOrganismCount, afterOrganismCount); + XCTAssertEqual(_testManager.currentGeneration, i); + } +} + +- (void)testProcessNextGenerationWithoutDelegate { + void (^expressionBlock)() = ^{ + _testManager.delegate = nil; + [_testManager processNextGeneration]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)population:(Population *)population didCompetedGeneration:(NSUInteger)generation fittestOrganisms:(NSArray *)fittestOrganisms offspring:(NSArray *)offspring completeNextGeneration:(NSArray *)nextGeneration { + +} + +- (void)testSetters { + for (NSInteger i = 0; i < kEvolutionManagerTestIterations; i++) { + CGFloat randomReproductionRate = [Random randomIntegerFromMin:1 toMax:99] / 100.0; + CGFloat randomSurvivalRate = [Random randomIntegerFromMin:1 toMax:99] / 100.0; + CGFloat randomMutationRate = [Random randomIntegerFromMin:1 toMax:99] / 100.0; + + _testManager.percentageOfOrganismsThatReproduce = randomReproductionRate; + _testManager.percentageOfOrganismsThatSurvive = randomSurvivalRate; + _testManager.mutationRate = randomMutationRate; + + XCTAssertEqual(_testManager.percentageOfOrganismsThatReproduce, randomReproductionRate); + XCTAssertEqual(_testManager.percentageOfOrganismsThatSurvive, randomSurvivalRate); + XCTAssertEqual(_testManager.mutationRate, randomMutationRate); + } +} + +- (void)testInvalidReproductionRate { + void (^expressionBlock)() = ^{ + _testManager.percentageOfOrganismsThatReproduce = 1.1; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testInvalidSurvivalRate { + void (^expressionBlock)() = ^{ + _testManager.percentageOfOrganismsThatSurvive = 1.0; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testInvalidMutationRate { + void (^expressionBlock)() = ^{ + _testManager.mutationRate = 1.1; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testGenerateOffspring { + for (NSInteger i = 0; i < kEvolutionManagerTestIterations; i++) { + NSInteger randomCount = [Random randomIntegerFromMin:2 toMax:100]; + NSArray *offspring = [_testManager generateOffspringFromOrganisms:_testManager.population.organisms count:randomCount]; + + XCTAssertNotNil(offspring); + XCTAssertEqual(offspring.count, randomCount); + } +} + +- (void)testSurvivorsToNextGeneration { + for (NSInteger i = 0; i < kEvolutionManagerTestIterations; i++) { + NSInteger randomCount = [Random randomIntegerFromMin:2 toMax:100]; + NSArray *survivors = [_testManager survivorsToNextGenerationWithCandidates:_testManager.population.organisms count:randomCount]; + + XCTAssertNotNil(survivors); + XCTAssertEqual(survivors.count, randomCount); + } +} + +@end diff --git a/EvolveTests/OrganismTests.m b/EvolveTests/OrganismTests.m new file mode 100644 index 0000000..ddda9bc --- /dev/null +++ b/EvolveTests/OrganismTests.m @@ -0,0 +1,175 @@ +// +// OrganismTests.m +// Evolve +// +// Created by Mike on 4/20/15. +// Copyright (c) 2015 Mike Amaral. All rights reserved. +// + +#import +#import +#import "Organism.h" +#import "Random.h" + +static NSInteger const kOrganismTestIterations = 10000; + +@interface OrganismTests : XCTestCase + +@end + +@implementation OrganismTests + +- (void)testInitWithChromosome { + for (NSInteger i = 0; i < kOrganismTestIterations; i++) { + Chromosome *chromosome = [[Chromosome alloc] initRandomChromosomeWithLength:5 domain:@"abcd"]; + + Organism *organism = [[Organism alloc] initWithChomosome:chromosome]; + + XCTAssertNotNil(organism); + XCTAssertNotNil(organism.chromosome); + XCTAssertEqualObjects(organism.chromosome, chromosome); + XCTAssertEqual(organism.fitness, 0); + } +} + +- (void)testInitRandom { + NSString *testDomain = @"abcdefg 123456"; + + for (NSInteger i = 0; i < kOrganismTestIterations; i++) { + NSInteger randomLength = [Random randomIntegerFromMin:1 toMax:25]; + Organism *organism = [[Organism alloc] initRandomWithGeneSequenceLength:randomLength domain:testDomain]; + + XCTAssertNotNil(organism); + XCTAssertNotNil(organism.chromosome); + XCTAssertEqual(organism.chromosome.geneSequence.length, randomLength); + XCTAssert([organism.chromosome.domain isEqualToString:testDomain]); + XCTAssertEqual(organism.fitness, 0); + } +} + +- (void)testOrganismFitness { + for (NSInteger i = 0; i < kOrganismTestIterations; i++) { + Organism *organism = [[Organism alloc] initRandomWithGeneSequenceLength:4 domain:@"abcd"]; + + NSInteger randomFitness = [Random randomIntegerFromMin:-10000 toMax:10000]; + organism.fitness = randomFitness; + + XCTAssertEqual(organism.fitness, randomFitness); + } +} + +- (void)testInitWithInvalidChromosome { + void (^expressionBlock)() = ^{ + __unused Organism *organism = [[Organism alloc] initWithChomosome:nil]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testInitRandomWithInvalidLength { + void (^expressionBlock)() = ^{ + __unused Organism *organism = [[Organism alloc] initRandomWithGeneSequenceLength:0 domain:@"abcd"]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testInitRandomWithNilDomain { + void (^expressionBlock)() = ^{ + __unused Organism *organism = [[Organism alloc] initRandomWithGeneSequenceLength:5 domain:nil]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testInitRandomWithInvalidDomain { + void (^expressionBlock)() = ^{ + __unused Organism *organism = [[Organism alloc] initRandomWithGeneSequenceLength:5 domain:@""]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testGenerateOffspring { + NSInteger testGeneSequenceLength = 20; + NSString *testDomain = @"abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789"; + + for (NSInteger i = 0; i < kOrganismTestIterations; i++) { + Organism *parent1 = [[Organism alloc] initRandomWithGeneSequenceLength:testGeneSequenceLength domain:testDomain]; + Organism *parent2 = [[Organism alloc] initRandomWithGeneSequenceLength:testGeneSequenceLength domain:testDomain]; + Organism *offspring = [Organism offspringFromParent1:parent1 parent2:parent2 mutationRate:0.0]; + + XCTAssertNotNil(offspring); + XCTAssertNotNil(offspring.chromosome); + XCTAssert([offspring.chromosome.domain isEqualToString:parent1.chromosome.domain]); + XCTAssert([offspring.chromosome.domain isEqualToString:parent2.chromosome.domain]); + XCTAssertEqual(offspring.chromosome.geneSequence.length, parent1.chromosome.geneSequence.length); + XCTAssertEqual(offspring.chromosome.geneSequence.length, parent2.chromosome.geneSequence.length); + + NSInteger correctGenes = 0; + + NSString *parent1GeneSequence = parent1.chromosome.geneSequence; + NSString *parent2GeneSequence = parent2.chromosome.geneSequence; + NSString *offspringGeneSequence = offspring.chromosome.geneSequence; + + for (NSInteger geneIndex = 0; geneIndex < testGeneSequenceLength; geneIndex++) { + if ([offspringGeneSequence characterAtIndex:geneIndex] == [parent1GeneSequence characterAtIndex:geneIndex] || [offspringGeneSequence characterAtIndex:geneIndex] == [parent2GeneSequence characterAtIndex:geneIndex]) { + correctGenes++; + } + } + + XCTAssertEqual(correctGenes, testGeneSequenceLength); + } +} + +- (void)testGenerateOffspringWithInvalidParent1 { + void (^expressionBlock)() = ^{ + __unused Organism *offspring = [Organism offspringFromParent1:nil parent2:[Organism new] mutationRate:0.0]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testGenerateOffspringWithInvalidParent2 { + void (^expressionBlock)() = ^{ + __unused Organism *offspring = [Organism offspringFromParent1:[Organism new] parent2:nil mutationRate:0.0]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testGenerateOffspringWithInvalidMutationRate { + void (^expressionBlock)() = ^{ + NSString *testDomain = @"abcd"; + NSInteger testLength = 4; + Organism *parent1 = [[Organism alloc] initRandomWithGeneSequenceLength:testLength domain:testDomain]; + Organism *parent2 = [[Organism alloc] initRandomWithGeneSequenceLength:testLength domain:testDomain]; + __unused Organism *offspring = [Organism offspringFromParent1:parent1 parent2:parent2 mutationRate:1.5]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testGenerateOffspringWithMismatchingParentLength { + void (^expressionBlock)() = ^{ + NSString *testDomain = @"abcd"; + Organism *parent1 = [[Organism alloc] initRandomWithGeneSequenceLength:3 domain:testDomain]; + Organism *parent2 = [[Organism alloc] initRandomWithGeneSequenceLength:4 domain:testDomain]; + __unused Organism *offspring = [Organism offspringFromParent1:parent1 parent2:parent2 mutationRate:0.0]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testGenerateOffspringWithMismatchingParentDomain { + void (^expressionBlock)() = ^{ + NSInteger testLength = 4; + Organism *parent1 = [[Organism alloc] initRandomWithGeneSequenceLength:testLength domain:@"abc"]; + Organism *parent2 = [[Organism alloc] initRandomWithGeneSequenceLength:testLength domain:@"123"]; + __unused Organism *offspring = [Organism offspringFromParent1:parent1 parent2:parent2 mutationRate:0.0]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +@end diff --git a/EvolveTests/PopulationTests.m b/EvolveTests/PopulationTests.m new file mode 100644 index 0000000..0654b4d --- /dev/null +++ b/EvolveTests/PopulationTests.m @@ -0,0 +1,111 @@ +// +// PopulationTests.m +// Evolve +// +// Created by Mike on 4/20/15. +// Copyright (c) 2015 Mike Amaral. All rights reserved. +// + +#import +#import +#import "Random.h" +#import "Population.h" + +static NSInteger const kPopulationTestIterations = 10000; + +@interface PopulationTests : XCTestCase + +@end + +@implementation PopulationTests + +- (void)testInitWithOrganisms { + for (NSInteger i = 0; i < kPopulationTestIterations; i++) { + NSInteger numberOfOrganisms = [Random randomIntegerFromMin:2 toMax:20]; + NSMutableArray *organisms = [NSMutableArray arrayWithCapacity:numberOfOrganisms]; + + for (NSInteger i = 0; i < numberOfOrganisms; i++) { + [organisms addObject:[[Organism alloc] initRandomWithGeneSequenceLength:1 domain:@"abcd"]]; + } + + Population *population = [[Population alloc] initWithOrganisms:organisms]; + + XCTAssertNotNil(population); + XCTAssertNotNil(population.organisms); + XCTAssertEqualObjects(population.organisms, organisms); + XCTAssertEqual(population.organisms.count, numberOfOrganisms); + } +} + +- (void)testInitRandom { + for (NSInteger i = 0; i < kPopulationTestIterations; i++) { + NSInteger randomSize = [Random randomIntegerFromMin:2 toMax:20]; + NSInteger randomGeneSequenceLength = [Random randomIntegerFromMin:2 toMax:30]; + NSString *domain = @"abcdefg1234567"; + + Population *population = [[Population alloc] initRandomPopulationWithSize:randomSize geneSequenceLength:randomGeneSequenceLength chromosomeDomain:domain]; + + XCTAssertNotNil(population); + XCTAssertNotNil(population.organisms); + XCTAssertEqual(population.organisms.count, randomSize); + + for (Organism *organism in population.organisms) { + XCTAssertNotNil(organism.chromosome); + XCTAssertNotNil(organism.chromosome.geneSequence); + XCTAssertEqual(organism.chromosome.geneSequence.length, randomGeneSequenceLength); + XCTAssertNotNil(organism.chromosome.domain); + XCTAssert([organism.chromosome.domain isEqualToString:domain]); + } + } +} + +- (void)testInitWithInvalidOrganisms { + void (^expressionBlock)() = ^{ + __unused Population *population = [[Population alloc] initWithOrganisms:nil]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testInitWithInsufficientOrganisms { + void (^expressionBlock)() = ^{ + Organism *theOnlyOne = [[Organism alloc] initRandomWithGeneSequenceLength:3 domain:@"abcd"]; + __unused Population *population = [[Population alloc] initWithOrganisms:@[theOnlyOne]]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testInitRandomWithInsufficientCount { + void (^expressionBlock)() = ^{ + __unused Population *population = [[Population alloc] initRandomPopulationWithSize:1 geneSequenceLength:4 chromosomeDomain:@"abcd"]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testInitRandomWithInvalidLength { + void (^expressionBlock)() = ^{ + __unused Population *population = [[Population alloc] initRandomPopulationWithSize:10 geneSequenceLength:0 chromosomeDomain:@"abcd"]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testInitRandomWithNilDomain { + void (^expressionBlock)() = ^{ + __unused Population *population = [[Population alloc] initRandomPopulationWithSize:10 geneSequenceLength:5 chromosomeDomain:nil]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testInitRandomWithInvalidDomain { + void (^expressionBlock)() = ^{ + __unused Population *population = [[Population alloc] initRandomPopulationWithSize:10 geneSequenceLength:5 chromosomeDomain:@""]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +@end diff --git a/EvolveTests/RandomTests.m b/EvolveTests/RandomTests.m new file mode 100644 index 0000000..c599065 --- /dev/null +++ b/EvolveTests/RandomTests.m @@ -0,0 +1,98 @@ +// +// RandomTests.m +// Evolve +// +// Created by Mike on 4/20/15. +// Copyright (c) 2015 Mike Amaral. All rights reserved. +// + +#import +#import +#import "Random.h" + +static NSInteger const kRandomTestIterations = 10000; + +@interface RandomTests : XCTestCase + +@end + +@implementation RandomTests + +- (void)testRandomInteger { + NSInteger min = -1234; + NSInteger max = 1234; + + for (size_t i = 0; i < kRandomTestIterations; i++) { + NSInteger random = [Random randomIntegerFromMin:min toMax:max]; + + XCTAssert(random >= min); + XCTAssert(random <= max); + } +} + +- (void)testRandomIntegerPositiveRange { + NSInteger min = 1234; + NSInteger max = 5678; + + for (size_t i = 0; i < kRandomTestIterations; i++) { + NSInteger random = [Random randomIntegerFromMin:min toMax:max]; + + XCTAssert(random >= min); + XCTAssert(random <= max); + } +} + +- (void)testRandomIntegerNegativeRange { + NSInteger min = -5678; + NSInteger max = -1234; + + for (size_t i = 0; i < kRandomTestIterations; i++) { + NSInteger random = [Random randomIntegerFromMin:min toMax:max]; + + XCTAssert(random >= min); + XCTAssert(random <= max); + } +} + +- (void)testRandomGeneSequence { + for (NSInteger i = 0; i < kRandomTestIterations; i++) { + NSInteger randomLength = [Random randomIntegerFromMin:1 toMax:100]; + NSString *testDomain = @"abcdefgHIJKLMNOP 67890"; + NSString *randomGeneSequence = [Random randomGeneSequenceWithLength:randomLength domain:testDomain]; + + XCTAssertEqual(randomGeneSequence.length, randomLength); + + for (NSInteger index = 0; index < randomGeneSequence.length; index++) { + NSString *character = [randomGeneSequence substringWithRange:NSMakeRange(index, 1)]; + BOOL randomSequenceContainsCharacter = [randomGeneSequence rangeOfString:character].location != NSNotFound; + + XCTAssert(randomSequenceContainsCharacter); + } + } +} + +- (void)testRandomGeneSequenceInvalidLength { + void (^expressionBlock)() = ^{ + [Random randomGeneSequenceWithLength:0 domain:@"1234"]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testRandomGeneSequenceNilDomain { + void (^expressionBlock)() = ^{ + [Random randomGeneSequenceWithLength:5 domain:nil]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +- (void)testRandomGeneSequenceInvalidDomain { + void (^expressionBlock)() = ^{ + [Random randomGeneSequenceWithLength:5 domain:@""]; + }; + + XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException); +} + +@end diff --git a/EvolveTests/Supporting Files/Info.plist b/EvolveTests/Supporting Files/Info.plist new file mode 100644 index 0000000..928dd44 --- /dev/null +++ b/EvolveTests/Supporting Files/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.MikeAmaral.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100755 index 0000000..7a3dceb --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +Copyright (c) 2015 Michael Amaral + +MIT LICENSE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3a119ff --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +[![License](https://img.shields.io/cocoapods/l/Evolve.svg)](http://doge.mit-license.org) [![Build Status](https://img.shields.io/travis/mamaral/Evolve.svg)](https://travis-ci.org/mamaral/Evolve/) ![Badge w/ Version](https://img.shields.io/cocoapods/v/Evolve.svg) + + +Installation +=== + +Available via [CocoaPods](http://cocoapods.org/?q=Evolve) + +```ruby +pod ‘Evolve’ +``` + +Get Started +==== + + +Community +==== + +Questions, comments, issues, and pull requests welcomed!! If you want to contribute, please do your best to keep the style/formatting consistent with the current project and MAKE SURE TO ADD TESTS! + + +License +==== + +This project is made available under the MIT license. See LICENSE.txt for details. diff --git a/Source/Chromosome.h b/Source/Chromosome.h new file mode 100644 index 0000000..0443015 --- /dev/null +++ b/Source/Chromosome.h @@ -0,0 +1,21 @@ +// +// Chromosome.h +// Evolve +// +// Created by Mike on 3/23/15. +// Copyright (c) 2015 Mike Amaral. All rights reserved. +// + +#import + +@interface Chromosome : NSObject + +@property (nonatomic, strong) NSString *domain; +@property (nonatomic, strong) NSString *geneSequence; + +- (instancetype)initWithGeneSequence:(NSString *)geneSequence domain:(NSString *)domain; +- (instancetype)initRandomChromosomeWithLength:(NSUInteger)length domain:(NSString *)domain; + +- (void)handleMutationWithRate:(CGFloat)mutationRate; + +@end diff --git a/Source/Chromosome.m b/Source/Chromosome.m new file mode 100644 index 0000000..59dfc43 --- /dev/null +++ b/Source/Chromosome.m @@ -0,0 +1,76 @@ +// +// Chromosome.m +// Evolve +// +// Created by Mike on 3/23/15. +// Copyright (c) 2015 Mike Amaral. All rights reserved. +// + +#import "Chromosome.h" +#import "Random.h" + +@implementation Chromosome + +#pragma mark - Initializers + +- (instancetype)initRandomChromosomeWithLength:(NSUInteger)length domain:(NSString *)domain { + return [self initWithGeneSequence:[Random randomGeneSequenceWithLength:length domain:domain] domain:domain]; +} + +- (instancetype)initWithGeneSequence:(NSString *)geneSequence domain:(NSString *)domain { + self = [super init]; + + if (!self) { + return nil; + } + + NSParameterAssert(geneSequence); + NSParameterAssert(geneSequence.length > 0); + NSParameterAssert(domain); + NSParameterAssert(domain.length > 0); + + self.geneSequence = geneSequence; + self.domain = domain; + + return self; +} + + +#pragma mark - Mutation + +- (void)handleMutationWithRate:(CGFloat)mutationRate { + // Take the decimal value and convert it to an integer - which we expect to be between + // 0 and 100, for easy probability calculation. + NSInteger convertedMutationRate = (NSInteger)round(mutationRate * 100); + + // Each gene in the chromosome has some random predetermined chance to mutate to some other gene. + // Here is where we apply this concept... First we iterate through all the genes in the sequence. + for (NSInteger geneIndex = 0; geneIndex < self.geneSequence.length; geneIndex++) { + + // Determine if we should mutate this or not. + NSInteger random = [Random randomIntegerFromMin:1 toMax:100]; + BOOL shouldMutateChar = random < convertedMutationRate; + + // If we should, get a random character from the set of possible characters for this genome and + // replace the current character with it. NOTE: There is a chance the newly selected random character + // happens to be the same as the current character, which we are currently leaving as a possibility. + if (shouldMutateChar) { + NSInteger randomIndex = [Random randomIntegerFromMin:0 toMax:self.domain.length - 1]; + unichar randomCharInSet = [self.domain characterAtIndex:randomIndex]; + NSString *replacementString = [NSString stringWithFormat:@"%C", randomCharInSet]; + + self.geneSequence = [self.geneSequence stringByReplacingCharactersInRange:NSMakeRange(geneIndex, 1) withString:replacementString]; + } + } +} + +#pragma mark - Debugging + +- (NSString *)debugDescription { + return @{ + @"domain": self.domain, + @"geneSequence": self.geneSequence + }.description; +} + +@end diff --git a/Source/EvolutionManager.h b/Source/EvolutionManager.h new file mode 100644 index 0000000..892c4c4 --- /dev/null +++ b/Source/EvolutionManager.h @@ -0,0 +1,36 @@ +// +// EvolutionManager.h +// Evolve +// +// Created by Mike on 4/20/15. +// Copyright (c) 2015 Mike Amaral. All rights reserved. +// + +#import +#import "Population.h" +#import "Random.h" + +@protocol EvolutionDelegate + +@required +- (void)population:(Population *)population didCompetedGeneration:(NSUInteger)generation fittestOrganisms:(NSArray *)fittestOrganisms offspring:(NSArray *)offspring completeNextGeneration:(NSArray *)nextGeneration; + +@end + +@interface EvolutionManager : NSObject + +@property (nonatomic) id delegate; + +@property (nonatomic, strong) Population *population; + +@property (nonatomic) NSUInteger currentGeneration; + +@property (nonatomic) CGFloat percentageOfOrganismsThatReproduce; +@property (nonatomic) CGFloat percentageOfOrganismsThatSurvive; +@property (nonatomic) CGFloat mutationRate; + +- (instancetype)initWithPopulation:(Population *)population; + +- (void)processNextGeneration; + +@end diff --git a/Source/EvolutionManager.m b/Source/EvolutionManager.m new file mode 100644 index 0000000..9eb2034 --- /dev/null +++ b/Source/EvolutionManager.m @@ -0,0 +1,208 @@ +// +// EvolutionManager.m +// Evolve +// +// Created by Mike on 4/20/15. +// Copyright (c) 2015 Mike Amaral. All rights reserved. +// + +#import "EvolutionManager.h" + +static CGFloat const kDefaultPercentageOfOrganismsThatReproduce = 0.33; +static CGFloat const kDefaultMutationRate = 0.05; +static CGFloat const kDefaultPercentageOfOrganismsThatSurvive = 0.10; + +@implementation EvolutionManager + + +#pragma mark - Initialization + +- (instancetype)initWithPopulation:(Population *)population { + self = [super init]; + + if (!self) { + return nil; + } + + NSParameterAssert(population); + + self.population = population; + self.mutationRate = kDefaultMutationRate; + self.percentageOfOrganismsThatReproduce = kDefaultPercentageOfOrganismsThatReproduce; + self.percentageOfOrganismsThatSurvive = kDefaultPercentageOfOrganismsThatSurvive; + + return self; +} + + +#pragma mark - Generation cycle + +- (void)processNextGeneration { + NSParameterAssert(self.delegate); + + // By now our delegate should have evaluated the fitness for our population, so let's sort + // it by fitness - the fittest first. + NSArray *sortedOrganisms = [self sortOrganismsByFitness:self.population.organisms]; + + // The number of mates is the percentage of our total organisms that will get a chance to reproduce. + NSInteger numberOfMates = [self calculateNumberOfMates]; + + // Some of these organisms will get a chance to live on to the next generation and have another chance + // at reproducing. + NSInteger numberOfOrganismsSurviving = [self calculateNumberOfOrganismsSurviving]; + + // The number of children that are created is the difference between our population count and the + // previously calculated number of organisms that will survive until the next generation. + NSInteger numberOfChildren = [self calculateNumberOfOffspringFromSurvivors:numberOfOrganismsSurviving]; + + // TODO: The organisms selected to breed and survive to the next population should be randomly selected from + // the entire population, with a stong bias towards fitness. Currently only the fittest x% are selected for breeding + // and survival, which is a more optimal but less realistic approach. Sometimes very fit organisms are just unlucky, + // and sometimes the nerd gets the girl... + + // Get the most fit organisms from our population that we just sorted. + NSArray *fittestOrganisms = [sortedOrganisms subarrayWithRange:NSMakeRange(0, numberOfMates)]; + + // Pass these organisms to our function that generates a given number of children randomly from these parents. + NSArray *offspring = [self generateOffspringFromOrganisms:fittestOrganisms count:numberOfChildren]; + + // From the pool of the fittest parents, get the lucky ones that will live on to the next generation. + NSArray *survivors = [self survivorsToNextGenerationWithCandidates:fittestOrganisms count:numberOfOrganismsSurviving]; + + // Build our complete next generation of organisms, including the parents that will live on to the next + // generation, as well as their children - then shuffle the list to avoid any ordering bias. + NSArray *nextGeneration = [self shuffleOrganisms:[survivors arrayByAddingObjectsFromArray:offspring]]; + + // Pass the information back to our delegate now that the population has completed a generation. + if ([self.delegate respondsToSelector:@selector(population:didCompetedGeneration:fittestOrganisms:offspring:completeNextGeneration:)]) { + [self.delegate population:self.population didCompetedGeneration:self.currentGeneration fittestOrganisms:fittestOrganisms offspring:offspring completeNextGeneration:nextGeneration]; + } + + // Create a new population from the next generation. + self.population = [[Population alloc] initWithOrganisms:nextGeneration]; + + // Increment the current generation count. + self.currentGeneration++; +} + +- (NSArray *)sortOrganismsByFitness:(NSArray *)organisms { + return [organisms sortedArrayUsingComparator:^NSComparisonResult(Organism *orgA, Organism *orgB) { + if (orgA.fitness < orgA.fitness) { + return NSOrderedAscending; + } + + else if (orgB.fitness > orgA.fitness) { + return NSOrderedDescending; + } + + else { + return NSOrderedSame; + } + }]; +} + +- (NSInteger)calculateNumberOfMates { + return (NSInteger)round(self.population.organisms.count * self.percentageOfOrganismsThatReproduce); +} + +- (NSInteger)calculateNumberOfOrganismsSurviving { + return (NSInteger)round(self.population.organisms.count * self.percentageOfOrganismsThatSurvive); +} + +- (NSInteger)calculateNumberOfOffspringFromSurvivors:(NSInteger)survivorCount { + NSParameterAssert(survivorCount < self.population.organisms.count); + + return self.population.organisms.count - survivorCount; +} + +- (NSArray *)generateOffspringFromOrganisms:(NSArray *)parents count:(NSInteger)offspringCount { + NSParameterAssert(parents.count > 1); + + // Create our initially-empty offspring array. + NSMutableArray *offspring = [NSMutableArray arrayWithCapacity:offspringCount]; + + // We want to continue adding children until our predetermined count is met. + while (offspring.count < offspringCount) { + + // Choose two random organisms to be the parents, ensuring we don't choose the same + // organism twice as this simulates sexual reproduction, rather than asexual reproduction. + NSInteger randomOrganismIndexA = [Random randomIntegerFromMin:0 toMax:parents.count - 1]; + NSInteger randomOrganismIndexB = [Random randomIntegerFromMin:0 toMax:parents.count - 1]; + + // If the two random indexes are the same, skip this iteration. + if (randomOrganismIndexA == randomOrganismIndexB) { + continue; + } + + // Take these two parents and... well son... you see... When a mommy and + // a daddy love eachother very much... + Organism *parent1 = parents[randomOrganismIndexA]; + Organism *parent2 = parents[randomOrganismIndexB]; + Organism *child = [Organism offspringFromParent1:parent1 parent2:parent2 mutationRate:self.mutationRate]; + + // Add this child to our array of offspring. + [offspring addObject:child]; + } + + return offspring; +} + +- (NSArray *)survivorsToNextGenerationWithCandidates:(NSArray *)candidates count:(NSInteger)count { + // Randomly choose the defined count of parents to return. + NSMutableArray *potentialSurvivors = [NSMutableArray arrayWithArray:candidates]; + NSMutableArray *survivors = [NSMutableArray arrayWithCapacity:count]; + + for (NSInteger i = 0; i < count; i++) { + NSInteger randomIndex = [Random randomIntegerFromMin:0 toMax:potentialSurvivors.count - 1]; + Organism *survivor = potentialSurvivors[randomIndex]; + + [survivors addObject:survivor]; + [potentialSurvivors removeObjectAtIndex:randomIndex]; + } + + return survivors; +} + + +#pragma mark - Setters for customizable / optional simulation params + +- (void)setPercentageOfOrganismsThatReproduce:(CGFloat)percentageOfOrganismsThatReproduce { + NSParameterAssert(percentageOfOrganismsThatReproduce >= 0.0 && percentageOfOrganismsThatReproduce <= 1.0); + + _percentageOfOrganismsThatReproduce = percentageOfOrganismsThatReproduce; +} + +- (void)setPercentageOfOrganismsThatSurvive:(CGFloat)percentageOfOrganismsThatSurvive { + NSParameterAssert(percentageOfOrganismsThatSurvive >= 0.0 && percentageOfOrganismsThatSurvive < 1.0); + + _percentageOfOrganismsThatSurvive = percentageOfOrganismsThatSurvive; +} + +- (void)setMutationRate:(CGFloat)mutationRate { + NSParameterAssert(mutationRate > 0.0 && mutationRate < 1.0); + + _mutationRate = mutationRate; +} + + + +#pragma mark - Utils + +- (NSArray *)shuffleOrganisms:(NSArray *)organisms { + NSMutableArray *sourceOrganisms = [NSMutableArray arrayWithArray:organisms]; + NSMutableArray *shuffledOrganisms = [NSMutableArray arrayWithCapacity:organisms.count]; + + NSUInteger count = organisms.count; + for (NSUInteger i = 0; i < count; i++) { + NSInteger randomIndex = [Random randomIntegerFromMin:0 toMax:sourceOrganisms.count - 1]; + + Organism *randomOrganism = sourceOrganisms[randomIndex]; + + [shuffledOrganisms addObject:randomOrganism]; + [sourceOrganisms removeObjectAtIndex:randomIndex]; + } + + return shuffledOrganisms; +} + +@end diff --git a/Source/Organism.h b/Source/Organism.h new file mode 100644 index 0000000..e8f34ad --- /dev/null +++ b/Source/Organism.h @@ -0,0 +1,23 @@ +// +// Organism.h +// Evolve +// +// Created by Mike on 3/23/15. +// Copyright (c) 2015 Mike Amaral. All rights reserved. +// + +#import +#import "Chromosome.h" + +@interface Organism : NSObject + +@property (nonatomic, strong) Chromosome *chromosome; + +@property (nonatomic) NSInteger fitness; + +- (instancetype)initWithChomosome:(Chromosome *)chromosome; +- (instancetype)initRandomWithGeneSequenceLength:(NSUInteger)length domain:(NSString *)domain; + ++ (instancetype)offspringFromParent1:(Organism *)parent1 parent2:(Organism *)parent2 mutationRate:(CGFloat)mutationRate; + +@end diff --git a/Source/Organism.m b/Source/Organism.m new file mode 100644 index 0000000..41078cf --- /dev/null +++ b/Source/Organism.m @@ -0,0 +1,75 @@ +// +// Organism.m +// Evolve +// +// Created by Mike on 3/23/15. +// Copyright (c) 2015 Mike Amaral. All rights reserved. +// + +#import "Organism.h" +#import "Random.h" + +@implementation Organism + + +#pragma mark - Initializers + +- (instancetype)initRandomWithGeneSequenceLength:(NSUInteger)length domain:(NSString *)domain { + return [self initWithChomosome:[[Chromosome alloc] initRandomChromosomeWithLength:length domain:domain]]; +} + + +- (instancetype)initWithChomosome:(Chromosome *)chromosome { + self = [super init]; + + if (!self) { + return nil; + } + + NSParameterAssert(chromosome); + + self.chromosome = chromosome; + + return self; +} + + +#pragma mark - Reproduction + ++ (instancetype)offspringFromParent1:(Organism *)parent1 parent2:(Organism *)parent2 mutationRate:(CGFloat)mutationRate { + NSParameterAssert(parent1); + NSParameterAssert(parent2); + NSParameterAssert([parent1.chromosome.domain isEqualToString:parent2.chromosome.domain]); + NSParameterAssert(parent1.chromosome.geneSequence.length == parent2.chromosome.geneSequence.length); + NSParameterAssert(mutationRate >= 0 && mutationRate <= 1); + + // Randomly generate a crossover point and combine the parent's chromosomes there - which will + // be the child's starting gene sequence. + NSInteger crossoverPoint = [Random randomIntegerFromMin:0 toMax:parent1.chromosome.geneSequence.length - 1]; + NSString *parent1Contribution = [parent1.chromosome.geneSequence substringToIndex:crossoverPoint]; + NSString *parent2Contribution = [parent2.chromosome.geneSequence substringFromIndex:crossoverPoint]; + NSString *offspringGeneSequence = [parent1Contribution stringByAppendingString:parent2Contribution]; + + // Create the child's chromosome with the parent's same configuration and the crossed over genetic pattern. + Chromosome *childsChromosome = [[Chromosome alloc] initWithGeneSequence:offspringGeneSequence domain:parent1.chromosome.domain]; + + // Tell the chromosome to handle mutation given the provided mutation rate. + [childsChromosome handleMutationWithRate:mutationRate]; + + // Create the child with this chromosome. + Organism *child = [[Organism alloc] initWithChomosome:childsChromosome]; + + return child; +} + + +#pragma mark - Debugging + +- (NSString *)debugDescription { + return @{ + @"geneSequence": self.chromosome.geneSequence, + @"fitness": @(self.fitness) + }.description; +} + +@end diff --git a/Source/Population.h b/Source/Population.h new file mode 100644 index 0000000..2f893c5 --- /dev/null +++ b/Source/Population.h @@ -0,0 +1,21 @@ +// +// Population.h +// Evolve +// +// Created by Mike on 3/23/15. +// Copyright (c) 2015 Mike Amaral. All rights reserved. +// + +#import +#import "Organism.h" +#import "Chromosome.h" + +@interface Population : NSObject + +@property (nonatomic, strong) NSArray *organisms; + +- (instancetype)initRandomPopulationWithSize:(NSUInteger)size geneSequenceLength:(NSUInteger)geneSequenceLength chromosomeDomain:(NSString *)domain; +- (instancetype)initWithOrganisms:(NSArray *)organisms; + +@end + diff --git a/Source/Population.m b/Source/Population.m new file mode 100644 index 0000000..bf86683 --- /dev/null +++ b/Source/Population.m @@ -0,0 +1,50 @@ +// +// Population.m +// Evolve +// +// Created by Mike on 3/23/15. +// Copyright (c) 2015 Mike Amaral. All rights reserved. +// + +#import "Population.h" +#import "Random.h" + +@implementation Population + + +#pragma mark - Initialization + +- (instancetype)initRandomPopulationWithSize:(NSUInteger)size geneSequenceLength:(NSUInteger)geneSequenceLength chromosomeDomain:(NSString *)domain { + return [self initWithOrganisms:[self generateRandomPopulationWithSize:size geneSequenceLength:geneSequenceLength chromosomeDomain:domain]]; +} + +- (instancetype)initWithOrganisms:(NSArray *)organisms { + self = [super init]; + + if (!self) { + return nil; + } + + NSParameterAssert(organisms); + NSParameterAssert(organisms.count >= 2); + + self.organisms = organisms; + + return self; +} + + +#pragma mark - Randomization + +- (NSArray *)generateRandomPopulationWithSize:(NSUInteger)size geneSequenceLength:(NSUInteger)geneSequenceLength chromosomeDomain:(NSString *)domain { + NSMutableArray *startingOrganisms = [NSMutableArray arrayWithCapacity:size]; + + for (NSInteger i = 0; i < size; i++) { + [startingOrganisms addObject:[[Organism alloc] initRandomWithGeneSequenceLength:geneSequenceLength domain:domain]]; + } + + return startingOrganisms; +} + + +@end diff --git a/Source/Random.h b/Source/Random.h new file mode 100644 index 0000000..05b9b9d --- /dev/null +++ b/Source/Random.h @@ -0,0 +1,17 @@ +// +// Random.h +// Evolve +// +// Created by Mike on 3/23/15. +// Copyright (c) 2015 Mike Amaral. All rights reserved. +// + +#import + +@interface Random : NSObject + ++ (NSInteger)randomIntegerFromMin:(NSInteger)min toMax:(NSInteger)max; + ++ (NSString *)randomGeneSequenceWithLength:(NSUInteger)length domain:(NSString *)domain; + +@end diff --git a/Source/Random.m b/Source/Random.m new file mode 100644 index 0000000..d56f037 --- /dev/null +++ b/Source/Random.m @@ -0,0 +1,31 @@ +// +// Random.m +// Evolve +// +// Created by Mike on 3/23/15. +// Copyright (c) 2015 Mike Amaral. All rights reserved. +// + +#import "Random.h" + +@implementation Random + ++ (NSInteger)randomIntegerFromMin:(NSInteger)min toMax:(NSInteger)max { + return (min + arc4random_uniform((u_int32_t)max - (u_int32_t)min + 1)); +} + ++ (NSString *)randomGeneSequenceWithLength:(NSUInteger)length domain:(NSString *)domain { + NSParameterAssert(length > 0); + NSParameterAssert(domain); + NSParameterAssert(domain.length > 0); + + NSMutableString *randomString = [NSMutableString stringWithCapacity:length]; + + for (int i = 0; i < length; i++) { + [randomString appendFormat:@"%C", [domain characterAtIndex:[[self class] randomIntegerFromMin:0 toMax:domain.length - 1]]]; + } + + return randomString; +} + +@end