def ncsimulate():
    """Run a simulation"""

    start_time = time()

    # Print a status message when SIGINFO (ctrl-T) is received on BSD or
    # OS X systems or SIGUSR1 is received on POSIX systems
    def handle_siginfo(signum, frame):
        try:
            print("Cycle {c}: Size {ps}, {pc:.0%} cooperators".format(c=cycle, ps=metapop.shape[0], pc=metapop.Coop.mean()))
        except NameError:
            print("Simulation has not yet begun")

    signal.signal(signal.SIGUSR1, handle_siginfo)
    if hasattr(signal, 'SIGINFO'):
        signal.signal(signal.SIGINFO, handle_siginfo)

    # Some scheduling systems send SIGTERM before killing a job. If SIGTERM
    # is received, flush all of the log files
    def handle_sigterm(signum, frame):
        if log_metapopulation:
            try:
                outfilemp.flush()
            except NameError:
                pass
        if log_population:
            try:
                outfilep.flush()
            except NameError:
                pass
        if log_genotypes:
            try:
                outfileg.flush()
            except NameError:
                pass
    signal.signal(signal.SIGTERM, handle_sigterm)


    # Get the command line arguments
    args = parse_arguments()

    # Read the configuration file
    try:
        config = ConfigObj(infile=args.configfile, configspec='configspec.ini',
                           file_error=True)
    except (ConfigObjError, OSError) as e:
        print("Error: {e}".format(e=e))
        sys.exit(1)

    # Add any parameters specified on the command line to the configuration
    if args.param:
        for param in args.param:
            config[param[0]][param[1]] = param[2]

    # Validate the configuration
    validation = config.validate(Validator(), copy=True)

    if validation != True:
        errors = flatten_errors(config, validation)
        print("Found {n} error(s) in configuration:".format(n=len(errors)))
        for (section_list, key, _) in errors:
            if key is not None:
                print("\t* Invalid value for '{k}' in Section '{s}'".format(k=key, s=section_list[0]))
            else:
                print("\t* Missing required section '{s}'".format(s=section_list[0]))

        sys.exit(2)

    if args.checkconfig:
        print("No errors found in configuration file {f}".format(f=args.configfile))
        sys.exit(0)

    # If the random number generator seed specified, add it to the config,
    # overwriting any previous value. Otherwise, if it wasn't in the
    # supplied configuration file, create one.
    if args.seed:
        config['Simulation']['seed'] = args.seed
    elif 'seed' not in config['Simulation'] or config['Simulation']['seed']==None:
        seed = np.random.randint(low=0, high=np.iinfo(np.uint32).max)           
        config['Simulation']['seed'] = seed

    np.random.seed(seed=config['Simulation']['seed'])

    # Generate a universally unique identifier (UUID) for this run
    config['Simulation']['UUID'] = str(uuid.uuid4())

    # If the data directory is specified, add it to the config, overwriting any
    # previous value
    if args.data_dir:
        config['Simulation']['data_dir'] = args.data_dir


    # If the data_dir already exists, append the current date and time to
    # data_dir, and use that. Afterwards, create the directory.
    if os.path.exists(config['Simulation']['data_dir']):
        newname = '{o}-{d}'.format(o=config['Simulation']['data_dir'],
                                   d=datetime.datetime.now().strftime("%Y%m%d%H%M%S"))
        msg = '{d} already exists. Using {new} instead.'.format(d=config['Simulation']['data_dir'],
                                                                new=newname)
        warn(msg)
        config['Simulation']['data_dir'] = newname

    os.mkdir(config['Simulation']['data_dir'])


    # Write information about the run
    infofile = os.path.join(config['Simulation']['data_dir'], 'run_info.txt')
    write_run_information(filename=infofile, config=config)


    # Write the configuration file
    config.filename = os.path.join(config['Simulation']['data_dir'],
                                   'configuration.cfg')
    config.write()
    config.num_births = 0


    # Create the log file of metapopulation-level data if enabled
    log_metapopulation = config['MetapopulationLog']['enabled']
    if log_metapopulation:
        log_metapopulation_freq = config['MetapopulationLog']['frequency']

        # Config options for logging metapopulation. Name, frequency, etc.
        fieldnames = ['Time', 'Births', 'PopulationSize', 'CooperatorProportion',
                      'MinCooperatorFitness', 'MaxCooperatorFitness',
                      'MeanCooperatorFitness', 'MinDefectorFitness',
                      'MaxDefectorFitness', 'MeanDefectorFitness',
                      'ShannonIndex', 'SimpsonIndex']

        outfilemp = open(os.path.join(config['Simulation']['data_dir'],
                         config['MetapopulationLog']['filename']), 'w')
        writermp = csv.DictWriter(outfilemp, fieldnames=fieldnames)
        writermp.writeheader()


    # Create the log file of population-level data if enabled
    log_population = config['PopulationLog']['enabled']
    if log_population:
        log_population_freq = config['PopulationLog']['frequency']

        # Config options for logging population. Name, frequency, etc.
        fieldnames = ['Time', 'Population', 'X', 'Y', 'PopulationSize',
                      'CooperatorProportion', 'MinCooperatorFitness',
                      'MaxCooperatorFitness', 'MeanCooperatorFitness',
                      'MinDefectorFitness', 'MaxDefectorFitness',
                      'MeanDefectorFitness', 'ShannonIndex', 'SimpsonIndex']

        outfilep = open(os.path.join(config['Simulation']['data_dir'],
                        config['PopulationLog']['filename']), 'w')
        writerp = csv.DictWriter(outfilep, fieldnames=fieldnames)
        writerp.writeheader()

    log_genotypes = config['GenotypeLog']['enabled']
    if log_genotypes:
        log_genotypes_freq = config['GenotypeLog']['frequency']
        fieldnames = ['Time', 'Population', 'X', 'Y', 'Genotype']

        outfileg = open(os.path.join(config['Simulation']['data_dir'],
                        config['GenotypeLog']['filename']), 'w')
        writerg = csv.DictWriter(outfileg, fieldnames=fieldnames)
        writerg.writeheader()


    # Create the migration topology. This is a graph where each population is a
    # node, and the edges between nodes represent potential paths for migration
    topology = build_topology(config=config)

    if config['Simulation']['export_topology']:
        fn = os.path.join(config['Simulation']['data_dir'], 'topology.gml')
        export_topology(topology=topology, filename=fn)


    # Create the metapopulation and apply the initial stress bottleneck
    metapop = create_metapopulation(config=config, topology=topology,
                                    initial_state=config['Metapopulation']['initial_state'])

    stress_tolerance = config['Population']['mutation_rate_tolerance']
    if stress_tolerance < 1:
        metapop = bottleneck(population=metapop,
                             survival_pct=config['Population']['mutation_rate_tolerance'])
    else:
        metapop = bottleneck(population=metapop,
                             survival_pct=config['Population']['dilution_factor'])


    # Keep track of how often the metapopulation should be mixed
    mix_frequency = config['Metapopulation']['mix_frequency']

    adaptive_columns = adaptive_colnames(L=config['Population']['genome_length'])

    # Iterate through each cycle of the simulation
    for cycle in range(config['Simulation']['num_cycles']):
        if not args.quiet:
            if len(adaptive_columns) > 0:
                c1 = (metapop.loc[metapop.Coop==1, adaptive_columns] > 0).sum(axis=1).max()
                d1 = (metapop.loc[metapop.Coop==0, adaptive_columns] > 0).sum(axis=1).max()
            else:
                c1 = d1 = 'NA'

            print("Cycle {c}: Size {ps}, Populations {pops}, {pc:.0%} cooperators, Fitness: {f:.02}, C1: {c1}, D1: {d1} ]".format(c=cycle, ps=metapop.shape[0], pops=metapop.Population.unique().shape[0], pc=metapop.Coop.mean(), f=metapop.Fitness.mean(), c1=c1, d1=d1))

        if log_metapopulation and cycle % log_metapopulation_freq == 0:
            write_metapop_data(writer=writermp, metapop=metapop,
                               topology=topology, cycle=cycle, config=config)

        if log_population and cycle % log_population_freq == 0:
            write_population_data(writer=writerp, metapop=metapop,
                                  topology=topology, cycle=cycle, config=config)

        if log_genotypes and cycle % log_genotypes_freq == 0:
            write_population_genotypes(writer=writerg, metapop=metapop,
                                       topology=topology, cycle=cycle,
                                       config=config)


        # Grow the population to carrying capacity, potentially mutating
        # offspring
        metapop = grow(M=metapop, config=config)
        #print("----- Num births: {nb}".format(nb=config.num_births))

        # Migrate individuals among subpopulations
        metapop = migrate(M=metapop, topology=topology,
                          rate=config['Metapopulation']['migration_rate'])

        # Mix the metapopulation (if configured)
        if mix_frequency > 0 and cycle > 0 and (cycle % mix_frequency == 0):
            metapop = mix(M=metapop, topology=topology)

        # Dilution
        metapop = bottleneck(population=metapop,
                             survival_pct=config['Population']['dilution_factor'])

        if config['Simulation']['stop_when_empty'] and \
                metapop.shape[0] == 0:
            break
        elif config['Simulation']['num_births'] and config.num_births > config['Simulation']['num_births']:
            break


    if log_metapopulation:
        write_metapop_data(writer=writermp, metapop=metapop, topology=topology,
                           cycle=cycle+1, config=config)
    if log_population:
        write_population_data(writer=writerp, metapop=metapop,
                              topology=topology, cycle=cycle, config=config)

    rt_string = 'Run Time: {t}\n'.format(t=datetime.timedelta(seconds=time()-start_time))
    append_run_information(filename=infofile, string=rt_string)