Ejemplo n.º 1
0
def main():
    description = ('Ingress FieldPlan - Maximize the number of links '
                   'and fields, and thus AP, for a collection of '
                   'portals in the game Ingress and create a convenient plan '
                   'in Google Spreadsheets. Spin-off from Maxfield.')

    parser = argparse.ArgumentParser(
        description=description,
        prog='makePlan.py',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument(
        '-i',
        '--iterations',
        type=int,
        default=5000,
        help='Number of iterations to perform. More iterations may improve '
        'results, but will take longer to process.')
    parser.add_argument(
        '-m',
        '--travelmode',
        default='walking',
        help='Travel mode (walking, bicycling, driving, transit).')
    parser.add_argument(
        '-s',
        '--sheetid',
        default=None,
        required=True,
        help='The Google Spreadsheet ID with portal definitions.')
    parser.add_argument(
        '-n',
        '--nosave',
        action='store_true',
        default=False,
        help='Do not attempt to save the spreadsheet, just calculate the plan.'
    )
    parser.add_argument(
        '-p',
        '--plots',
        default=None,
        help='Save step-by-step PNGs of the workplan into this directory.')
    parser.add_argument(
        '--plotdpi',
        default=96,
        type=int,
        help='DPI to use for generating plots (try 144 for high-dpi screens)')
    parser.add_argument('-g',
                        '--gmapskey',
                        default=None,
                        help='Google Maps API key (for better distances)')
    parser.add_argument('-f',
                        '--faction',
                        default='enl',
                        help='Set to "res" to use resistance colours')
    parser.add_argument(
        '-c',
        '--cooling',
        default='rhs',
        help='What kind of heatsinks to assume (hs, rhs, vrhs, none, idkfa)')
    parser.add_argument(
        '-u',
        '--maxmu',
        action='store_true',
        default=False,
        help='Find a plan with highest MU coverage instead of best AP')
    parser.add_argument(
        '-t',
        '--maxtime',
        default=None,
        type=int,
        help='Ignore plans that would take longer than this (in minutes)')
    parser.add_argument(
        '--minap',
        default=None,
        type=int,
        help=
        'Ignore plans that result in less AP than specified (used with --maxtime)'
    )
    parser.add_argument('--maxcpus',
                        default=mp.cpu_count(),
                        type=int,
                        help='Maximum number of cpus to use')
    parser.add_argument('-l',
                        '--log',
                        default=None,
                        help='Log file where to log processing info')
    parser.add_argument('-d',
                        '--debug',
                        action='store_true',
                        default=False,
                        help='Add debug information to the logfile')
    parser.add_argument('-q',
                        '--quiet',
                        action='store_true',
                        default=False,
                        help='Only output errors to the stdout')
    parser.add_argument('--no-plan-cache',
                        dest='nocache',
                        action='store_true',
                        default=False,
                        help='Do not load or save plan cache')
    parser.add_argument('-j',
                        '--jsonmap',
                        default=None,
                        help='Save the resulting map as IITC DrawTools json')
    # Obsolete options
    parser.add_argument('-b',
                        '--beginfirst',
                        action='store_true',
                        default=False,
                        help='(Obsolete, use waypoints instead)')
    parser.add_argument('-r',
                        '--roundtrip',
                        action='store_true',
                        default=False,
                        help='(Obsolete, use waypoints instead)')
    parser.add_argument('-k',
                        '--maxkeys',
                        type=int,
                        default=None,
                        help='(Obsolete, use --cooling options instead)')
    args = parser.parse_args()

    if args.beginfirst or args.roundtrip:
        parser.error(
            'Options -b and -r are obsolete. Use waypoints instead (see README).'
        )
    if args.maxkeys:
        parser.error('Option -k is obsolete. Use --cooling instead.')

    if args.iterations < 0:
        parser.error('Number of extra samples should be positive')

    if args.plotdpi < 1:
        parser.error('%s is not a valid screen dpi' % args.plotdpi)

    if args.faction not in ('enl', 'res'):
        parser.error('Sorry, I do not know about faction "%s".' % args.faction)

    logger.setLevel(logging.DEBUG)

    if args.log:
        ch = logging.FileHandler(args.log)
        formatter = logging.Formatter(
            '{%(module)s:%(funcName)s:%(lineno)s} %(message)s')
        ch.setFormatter(formatter)

        if args.debug:
            ch.setLevel(logging.DEBUG)
        else:
            ch.setLevel(logging.INFO)

        logger.addHandler(ch)

    ch = logging.StreamHandler()
    formatter = logging.Formatter('%(message)s')
    ch.setFormatter(formatter)

    if args.quiet:
        ch.setLevel(logging.CRITICAL)
    else:
        ch.setLevel(logging.INFO)

    logger.addHandler(ch)

    gs = gsheets.setup()

    portals, waypoints = gsheets.get_portals_from_sheet(gs, args.sheetid)
    logger.info('Considering %d portals and %s waypoints', len(portals),
                len(waypoints))

    if len(portals) < 3:
        logger.critical('Must have more than 2 portals!')
        sys.exit(1)

    maxfield.populate_graphs(portals, waypoints)

    maxfield.gen_distance_matrix(args.gmapskey, args.travelmode)

    if args.maxtime or args.nocache:
        bestgraph = None
        bestplan = None
    else:
        (bestgraph, bestplan) = maxfield.load_cache(args.travelmode,
                                                    args.maxmu, args.cooling,
                                                    args.maxtime)

    if args.maxmu:
        beststr = 'm2/min'
    else:
        beststr = 'AP/min'

    beststats = None
    bestkm = '-.--'
    bestsqkm = '-.--'
    bestap = '-----'
    nicetime = '-:--'
    bestportals = '-'
    best = 0

    if bestgraph is not None:
        beststats = maxfield.get_workplan_stats(bestplan)
        if args.maxmu:
            best = beststats['sqmpmin']
        else:
            best = beststats['appmin']

    logger.info('Finding an efficient plan that maximizes %s', beststr)

    failcount = 0
    seenplans = list()

    s_best = mp.Value('I', best)
    s_counter = mp.Value('I', 0)

    push_maxfield_data(args)

    ready_queue = mp.Queue(maxsize=10)
    processes = list()
    for i in range(args.maxcpus):
        logger.debug('Starting process %s', i)
        p = mp.Process(target=queue_job,
                       args=(args, s_best, s_counter, ready_queue))
        processes.append(p)
        p.start()
    logger.info('Started %s worker processes', len(processes))

    logger.info('Ctrl-C to exit and use the latest best plan')

    try:
        while True:
            if failcount > 1000:
                logger.info('Too many consecutive failures, exiting early.')
                break

            if beststats is not None:
                bestdist = beststats['dist']
                bestarea = beststats['area']
                bestap = beststats['ap']
                bestkm = '%0.2f' % (bestdist / float(1000))
                bestsqkm = '%0.2f' % (bestarea / float(1000000))
                nicetime = beststats['nicetime']
                if args.maxmu:
                    best = beststats['sqmpmin']
                else:
                    best = beststats['appmin']
                bestportals = bestgraph.order()
                if maxfield.waypoint_graph is not None:
                    bestportals -= maxfield.waypoint_graph.order()

            if not args.quiet:
                sys.stdout.write(
                    '\r(Best: %s km, %s km2, %s portals, %s AP, %s %s, %s): %s/%s     '
                    % (bestkm, bestsqkm, bestportals, bestap, best, beststr,
                       nicetime, s_counter.value, args.iterations))
                sys.stdout.flush()

            if s_counter.value >= args.iterations:
                break

            try:
                success, b, workplan, stats = ready_queue.get_nowait()
            except queue.Empty:
                time.sleep(0.1)
                continue

            if not success:
                failcount += 1
                continue
            if workplan in seenplans:
                # Already seen this plan, so don't consider it again. This is done
                # to avoid pingpoings for best plan.
                continue

            if not args.quiet:
                if bestkm is not None:
                    sys.stdout.write(
                        '\r(      %s km, %s km2, %s portals, %s AP, %s %s, %s)              \n'
                        % (bestkm, bestsqkm, bestportals, bestap, best,
                           beststr, nicetime))

            beststats = stats
            bestgraph = b
            bestplan = workplan

            if args.maxmu:
                best = stats['sqmpmin']
            else:
                best = stats['appmin']

            failcount = 0

    except KeyboardInterrupt:
        if not args.quiet:
            print()
            print('Exiting loop')
    finally:
        for p in processes:
            p.terminate()

    if not args.quiet:
        print()

    if bestplan is None:
        logger.critical('Could not find a solution for this list of portals.')
        sys.exit(1)

    maxfield.active_graph = bestgraph

    if not args.nocache:
        maxfield.save_cache(bestgraph, bestplan, args.travelmode, args.maxmu,
                            args.cooling, args.maxtime)

    if args.plots:
        animate.make_png_steps(bestgraph, bestplan, args.plots, args.faction,
                               args.plotdpi)

    if args.jsonmap:
        animate.make_json(bestgraph, args.jsonmap, args.faction)

    gsheets.write_workplan(gs, args.sheetid, bestgraph, bestplan, beststats,
                           args.faction, args.travelmode, args.nosave)
Ejemplo n.º 2
0
def main():
    description = ('Ingress FieldPlan - Maximize the number of links '
                   'and fields, and thus AP, for a collection of '
                   'portals in the game Ingress and create a convenient plan '
                   'in Google Spreadsheets. Spin-off from Maxfield.')

    parser = argparse.ArgumentParser(
        description=description,
        prog='makePlan.py',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument(
        '-i',
        '--iterations',
        type=int,
        default=10000,
        help='Number of iterations to perform. More iterations may improve '
        'results, but will take longer to process.')
    parser.add_argument('-k',
                        '--maxkeys',
                        type=int,
                        default=None,
                        help='Limit number of keys required per portal '
                        '(may result in less efficient plans)')
    parser.add_argument(
        '-m',
        '--travelmode',
        default='walking',
        help='Travel mode (walking, bicycling, driving, transit).')
    parser.add_argument(
        '-s',
        '--sheetid',
        default=None,
        required=True,
        help='The Google Spreadsheet ID with portal definitions.')
    parser.add_argument(
        '-n',
        '--nosave',
        action='store_true',
        default=False,
        help='Do not attempt to save the spreadsheet, just calculate the plan.'
    )
    parser.add_argument(
        '-p',
        '--plots',
        default=None,
        help='Save step-by-step PNGs of the workplan into this directory.')
    parser.add_argument(
        '--plotdpi',
        default=96,
        type=int,
        help='DPI to use for generating plots (try 144 for high-dpi screens)')
    parser.add_argument('-g',
                        '--gmapskey',
                        default=None,
                        help='Google Maps API key (for better distances)')
    parser.add_argument('-f',
                        '--faction',
                        default='enl',
                        help='Set to "res" to use resistance colours')
    parser.add_argument('-l',
                        '--log',
                        default=None,
                        help='Log file where to log processing info')
    parser.add_argument('-d',
                        '--debug',
                        action='store_true',
                        default=False,
                        help='Add debug information to the logfile')
    parser.add_argument('-q',
                        '--quiet',
                        action='store_true',
                        default=False,
                        help='Only output errors to the stdout')
    args = parser.parse_args()

    if args.iterations < 0:
        parser.error('Number of extra samples should be positive')

    if args.plotdpi < 1:
        parser.error('%s is not a valid screen dpi' % args.plotdpi)

    if args.faction not in ('enl', 'res'):
        parser.error('Sorry, I do not know about faction "%s".' % args.faction)

    logger.setLevel(logging.DEBUG)

    if args.log:
        ch = logging.FileHandler(args.log)
        formatter = logging.Formatter(
            '{%(module)s:%(funcName)s:%(lineno)s} %(message)s')
        ch.setFormatter(formatter)

        if args.debug:
            ch.setLevel(logging.DEBUG)
        else:
            ch.setLevel(logging.INFO)

        logger.addHandler(ch)

    ch = logging.StreamHandler()
    formatter = logging.Formatter('%(message)s')
    ch.setFormatter(formatter)

    if args.quiet:
        ch.setLevel(logging.CRITICAL)
    else:
        ch.setLevel(logging.INFO)

    logger.addHandler(ch)

    gs = gsheets.setup()

    portals, blockers = gsheets.get_portals_from_sheet(gs, args.sheetid)
    logger.info('Considering %d portals', len(portals))

    if len(portals) < 3:
        logger.critical('Must have more than 2 portals!')
        sys.exit(1)

    if len(portals) > _MAX_PORTALS_:
        logger.critical('Portal limit is %d', _MAX_PORTALS_)

    a = maxfield.populateGraph(portals)

    ab = None
    if blockers:
        ab = maxfield.populateGraph(blockers)

    (bestgraph, bestplan, bestdist) = maxfield.loadCache(a, ab)
    if bestgraph is None:
        # Use a copy, because we concat ab to a for blockers distances
        maxfield.genDistanceMatrix(a.copy(), ab, args.gmapskey,
                                   args.travelmode)
        bestkm = None
    else:
        bestkm = bestdist / float(1000)
        logger.info('Best distance of the plan loaded from cache: %0.2f km',
                    bestkm)

    counter = 0

    if args.maxkeys:
        logger.info('Finding an efficient plan with max %s keys', args.maxkeys)
    else:
        logger.info('Finding an efficient plan')

    logger.info('Ctrl-C to exit and use the latest best plan')

    failcount = 0

    try:
        while counter < args.iterations:
            if failcount >= 100:
                logger.info('Too many consecutive failures, exiting early.')
                break

            b = a.copy()
            counter += 1

            if not args.quiet:
                if bestkm is not None:
                    sys.stdout.write('\r(%0.2f km best): %s/%s      ' %
                                     (bestkm, counter, args.iterations))
                    sys.stdout.flush()

            failcount += 1
            if not maxfield.maxFields(b):
                logger.debug('Could not find a triangulation')
                failcount += 1
                continue

            for t in b.triangulation:
                t.markEdgesWithFields()

            maxfield.improveEdgeOrder(b)
            workplan = maxfield.makeWorkPlan(b, ab)

            if args.maxkeys:
                # do any of the portals require more than maxkeys
                sane_key_reqs = True
                for i in range(len(b.node)):
                    if b.in_degree(i) > args.maxkeys:
                        sane_key_reqs = False
                        break

                if not sane_key_reqs:
                    failcount += 1
                    logger.debug('Too many keys required, ignoring plan')
                    continue

            sane_out_links = True
            for i in range(len(b.node)):
                if b.out_degree(i) > 8:
                    sane_out_links = False
                    break

            if not sane_out_links:
                failcount += 1
                logger.debug('Too many outgoing links, ignoring plan')
                continue

            failcount = 0

            totaldist = maxfield.getWorkplanDist(b, workplan)

            if totaldist < bestdist:
                counter = 0
                bestplan = workplan
                bestgraph = b
                bestdist = totaldist
                bestkm = bestdist / float(1000)

    except KeyboardInterrupt:
        if not args.quiet:
            print()
            print('Exiting loop')

    if not args.quiet:
        print()

    if bestplan is None:
        logger.critical('Could not find a solution for this list of portals.')
        sys.exit(1)

    maxfield.saveCache(bestgraph, ab, bestplan, bestdist)

    if args.plots:
        animate.make_png_steps(bestgraph, bestplan, args.plots, args.faction,
                               args.plotdpi)

    gs = gsheets.setup()
    gsheets.write_workplan(gs, args.sheetid, bestgraph, bestplan, args.faction,
                           args.travelmode, args.nosave)
Ejemplo n.º 3
0
def main():
    description = ('Ingress FieldPlan - Maximize the number of links '
                   'and fields, and thus AP, for a collection of '
                   'portals in the game Ingress and create a convenient plan '
                   'in Google Spreadsheets. Spin-off from Maxfield.')

    parser = argparse.ArgumentParser(
        description=description,
        prog='makePlan.py',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument(
        '-i',
        '--iterations',
        type=int,
        default=10000,
        help='Number of iterations to perform. More iterations may improve '
        'results, but will take longer to process.')
    parser.add_argument('-k',
                        '--maxkeys',
                        type=int,
                        default=None,
                        help='Limit number of keys required per portal '
                        '(may result in less efficient plans).')
    parser.add_argument(
        '-m',
        '--travelmode',
        default='walking',
        help='Travel mode (walking, bicycling, driving, transit).')
    parser.add_argument(
        '-s',
        '--sheetid',
        default=None,
        required=True,
        help='The Google Spreadsheet ID with portal definitions.')
    parser.add_argument(
        '-n',
        '--nosave',
        action='store_true',
        default=False,
        help='Do not attempt to save the spreadsheet, just calculate the plan.'
    )
    parser.add_argument(
        '-r',
        '--roundtrip',
        action='store_true',
        default=False,
        help=
        'Make sure the plan starts and ends at the same portal (may be less efficient).'
    )
    parser.add_argument(
        '-b',
        '--beginfirst',
        action='store_true',
        default=False,
        help=
        'Begin capture with the first portal in the spreadsheet (may be less efficient).'
    )
    parser.add_argument(
        '-p',
        '--plots',
        default=None,
        help='Save step-by-step PNGs of the workplan into this directory.')
    parser.add_argument(
        '--plotdpi',
        default=96,
        type=int,
        help='DPI to use for generating plots (try 144 for high-dpi screens)')
    parser.add_argument('-g',
                        '--gmapskey',
                        default=None,
                        help='Google Maps API key (for better distances)')
    parser.add_argument('-f',
                        '--faction',
                        default='enl',
                        help='Set to "res" to use resistance colours')
    parser.add_argument(
        '-u',
        '--maxmu',
        action='store_true',
        default=False,
        help='Find a plan with best MU per distance travelled ratio')
    parser.add_argument('--maxcpus',
                        default=mp.cpu_count(),
                        type=int,
                        help='Maximum number of cpus to use')
    parser.add_argument('-l',
                        '--log',
                        default=None,
                        help='Log file where to log processing info')
    parser.add_argument('-d',
                        '--debug',
                        action='store_true',
                        default=False,
                        help='Add debug information to the logfile')
    parser.add_argument('-q',
                        '--quiet',
                        action='store_true',
                        default=False,
                        help='Only output errors to the stdout')
    args = parser.parse_args()

    if args.iterations < 0:
        parser.error('Number of extra samples should be positive')

    if args.plotdpi < 1:
        parser.error('%s is not a valid screen dpi' % args.plotdpi)

    if args.faction not in ('enl', 'res'):
        parser.error('Sorry, I do not know about faction "%s".' % args.faction)

    logger.setLevel(logging.DEBUG)

    if args.log:
        ch = logging.FileHandler(args.log)
        formatter = logging.Formatter(
            '{%(module)s:%(funcName)s:%(lineno)s} %(message)s')
        ch.setFormatter(formatter)

        if args.debug:
            ch.setLevel(logging.DEBUG)
        else:
            ch.setLevel(logging.INFO)

        logger.addHandler(ch)

    ch = logging.StreamHandler()
    formatter = logging.Formatter('%(message)s')
    ch.setFormatter(formatter)

    if args.quiet:
        ch.setLevel(logging.CRITICAL)
    else:
        ch.setLevel(logging.INFO)

    logger.addHandler(ch)

    gs = gsheets.setup()

    portals, blockers = gsheets.get_portals_from_sheet(gs, args.sheetid)
    logger.info('Considering %d portals', len(portals))

    if len(portals) < 3:
        logger.critical('Must have more than 2 portals!')
        sys.exit(1)

    if len(portals) > _MAX_PORTALS_:
        logger.critical('Portal limit is %d', _MAX_PORTALS_)

    a = maxfield.populateGraph(portals)

    ab = None
    if blockers:
        ab = maxfield.populateGraph(blockers)

    # Use a copy, because we concat ab to a for blockers distances
    maxfield.genDistanceMatrix(a.copy(), ab, args.gmapskey, args.travelmode)

    (bestgraph, bestplan) = maxfield.loadCache(a, ab, args.travelmode,
                                               args.beginfirst, args.roundtrip,
                                               args.maxmu)
    if bestgraph is not None:
        bestdist = maxfield.getWorkplanDist(bestgraph, bestplan)
        bestarea = maxfield.getWorkplanArea(bestgraph, bestplan)
        bestmudist = int(bestarea / bestdist)
        bestkm = bestdist / float(1000)
        bestsqkm = bestarea / float(1000000)
        logger.info('Best distance of the plan loaded from cache: %0.2f km',
                    bestkm)
        logger.info('Best coverage of the plan loaded from cache: %0.2f sqkm',
                    bestsqkm)

    else:
        bestkm = None
        bestsqkm = None
        bestdist = np.inf
        bestarea = 0
        bestmudist = 0

    counter = 0

    if args.maxkeys:
        logger.info('Finding an efficient plan with max %s keys', args.maxkeys)
    else:
        logger.info('Finding an efficient plan')

    failcount = 0
    seenplans = list()

    # set up multiprocessing
    ready_queue = mp.Queue()
    processes = list()
    for i in range(args.maxcpus):
        logger.debug('Starting process %s', i)
        p = mp.Process(target=queue_job, args=(a, ready_queue))
        processes.append(p)
        p.start()
    logger.info('Started %s worker processes', len(processes))

    logger.info('Ctrl-C to exit and use the latest best plan')

    try:
        while counter < args.iterations:
            if failcount >= 100:
                logger.info('Too many consecutive failures, exiting early.')
                break

            success, b = ready_queue.get()
            counter += 1

            if not args.quiet:
                if bestkm is not None:
                    sys.stdout.write(
                        '\r(Best: %0.2f km, %0.2f sqkm, %s sqm/m, %s actions): %s/%s      '
                        % (bestkm, bestsqkm, bestmudist, len(bestplan),
                           counter, args.iterations))
                    sys.stdout.flush()

            if not success:
                failcount += 1
                continue

            workplan = maxfield.makeWorkPlan(b, ab, args.roundtrip,
                                             args.beginfirst)

            if args.maxkeys:
                # do any of the portals require more than maxkeys
                sane_key_reqs = True
                for i in range(len(b.node)):
                    if b.in_degree(i) > args.maxkeys:
                        sane_key_reqs = False
                        break

                if not sane_key_reqs:
                    failcount += 1
                    logger.debug('Too many keys required, ignoring plan')
                    continue

            sane_out_links = True
            for i in range(len(b.node)):
                if b.out_degree(i) > 8:
                    sane_out_links = False
                    break

            if not sane_out_links:
                failcount += 1
                logger.debug('Too many outgoing links, ignoring plan')
                continue

            failcount = 0

            totaldist = maxfield.getWorkplanDist(b, workplan)
            totalarea = maxfield.getWorkplanArea(b, workplan)
            mudist = int(totalarea / totaldist)

            newbest = False
            if args.maxmu:
                # choose a plan that gives us most MU captured per distance of travel
                if mudist > bestmudist:
                    newbest = True
            else:
                # We want:
                # - the shorter plan, or
                # - the plan with a similar length that requires fewer actions, or
                # - the plan with a similar length that has higher mu per distance of travel
                # - we have not yet considered this plan
                if ((bestdist - totaldist > 80 or
                     (len(workplan) < len(bestplan)
                      and totaldist - bestdist <= 80) or
                     (mudist > bestmudist and totaldist - bestdist <= 80))
                        and workplan not in seenplans):
                    newbest = True

            if newbest:
                counter = 0
                bestplan = workplan
                seenplans.append(workplan)
                bestgraph = b
                bestdist = totaldist
                bestarea = totalarea
                bestkm = bestdist / float(1000)
                bestsqkm = bestarea / float(1000000)
                bestmudist = mudist

    except KeyboardInterrupt:
        if not args.quiet:
            print()
            print('Exiting loop')
    finally:
        for p in processes:
            p.terminate()

    if not args.quiet:
        print()

    if bestplan is None:
        logger.critical('Could not find a solution for this list of portals.')
        sys.exit(1)

    maxfield.saveCache(bestgraph, ab, bestplan, args.travelmode,
                       args.beginfirst, args.roundtrip, args.maxmu)

    if args.plots:
        animate.make_png_steps(bestgraph, bestplan, args.plots, args.faction,
                               args.plotdpi)

    gsheets.write_workplan(gs, args.sheetid, bestgraph, bestplan, args.faction,
                           args.travelmode, args.nosave, args.roundtrip,
                           args.maxmu)