Skip to content

Commit

Permalink
Add option to specify base portals for a flipfield
Browse files Browse the repository at this point in the history
pwiecz committed Feb 11, 2022

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent d773b6b commit ddd7686
Showing 8 changed files with 141 additions and 16 deletions.
2 changes: 1 addition & 1 deletion cmdline/double_herringbone_cmd.go
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@ func (d *doubleHerringboneCmd) Run(args []string, output io.Writer, numWorkers i
}
fmt.Printf("Read %d portals\n", len(portals))
if len(*d.basePortals) > 2 {
log.Fatalf("double_herringbone command accepts at most two corner portals - %d specified", len(*d.basePortals))
log.Fatalf("double_herringbone command accepts at most two base portals - %d specified", len(*d.basePortals))
}
basePortalIndices := portalsToIndices(*d.basePortals, portals)

11 changes: 10 additions & 1 deletion cmdline/flip_field_cmd.go
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ type flipFieldCmd struct {
numBackbonePortals *numberLimitValue
maxFlipPortals *int
simpleBackbone *bool
basePortals *portalsValue
}

func NewFlipFieldCmd() flipFieldCmd {
@@ -27,13 +28,15 @@ func NewFlipFieldCmd() flipFieldCmd {
},
maxFlipPortals: flags.Int("max_flip_portals", 0, "if >0 don't try to optimize for number of flip portals above this value"),
simpleBackbone: flags.Bool("simple_backbone", false, "make all backbone portals linkable from the first backbone portal"),
basePortals: &portalsValue{},
}
flags.Var(cmd.numBackbonePortals, "num_backbone_portals", "limit of number of portals in the \"backbone\" of the field. May be a number of have a format of \"<=number\"")
flags.Var(cmd.basePortals, "base_portal", "fix a base portal of the flip field")
return cmd
}

func (f *flipFieldCmd) Usage(fileBase string) {
fmt.Fprintf(flag.CommandLine.Output(), "%s flip_field [-num_backbone_portals=[<=]<number>] [--max_flip_portals=<number>] [--simple_backbone]\n", fileBase)
fmt.Fprintf(flag.CommandLine.Output(), "%s flip_field [-num_backbone_portals=[<=]<number>] [--max_flip_portals=<number>] [--simple_backbone] [-base_portal=<lat>,<lng>]... <portals_file>\n", fileBase)
f.flags.PrintDefaults()
}

@@ -51,6 +54,11 @@ func (f *flipFieldCmd) Run(args []string, numWorkers int, output io.Writer, prog
log.Fatalf("Could not parse file %s : %v\n", fileArgs[0], err)
}
fmt.Printf("Read %d portals\n", len(portals))
if len(*f.basePortals) > 2 {
log.Fatalf("flip_field command accepts at most two base portals - %d specified", len(*f.basePortals))
}
basePortalIndices := portalsToIndices(*f.basePortals, portals)

var numPortalLimit lib.PortalLimit
if f.numBackbonePortals.Exactly {
numPortalLimit = lib.EQUAL
@@ -67,6 +75,7 @@ func (f *flipFieldCmd) Run(args []string, numWorkers int, output io.Writer, prog
lib.FlipFieldBackbonePortalLimit{Value: f.numBackbonePortals.Value, LimitType: numPortalLimit},
lib.FlipFieldMaxFlipPortals(*f.maxFlipPortals),
lib.FlipFieldSimpleBackbone(*f.simpleBackbone),
lib.FlipFieldFixedBaseIndices(basePortalIndices),
}
backbone, rest := lib.LargestFlipField(portals, options...)
fmt.Fprintf(output, "\nNum backbone portals: %d, num flip portals: %d, num fields: %d\nBackbone:\n",
2 changes: 1 addition & 1 deletion cmdline/herringbone_cmd.go
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@ func (h *herringboneCmd) Run(args []string, output io.Writer, numWorkers int, pr
}
fmt.Printf("Read %d portals\n", len(portals))
if len(*h.basePortals) > 2 {
log.Fatalf("herringbone command accepts at most two corner portals - %d specified", len(*h.basePortals))
log.Fatalf("herringbone command accepts at most two base portals - %d specified", len(*h.basePortals))
}
basePortalIndices := portalsToIndices(*h.basePortals, portals)

73 changes: 71 additions & 2 deletions gui/flip_field.go
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ package main

import (
"fmt"
"image/color"
"runtime"

"github.com/golang/geo/s2"
@@ -18,14 +19,15 @@ type flipFieldTab struct {
backbone []lib.Portal
flipPortals []lib.Portal
solutionText string
basePortals map[string]struct{}
}

var _ pattern = (*flipFieldTab)(nil)

func newFlipFieldTab(portals *Portals) *flipFieldTab {
t := &flipFieldTab{}
t.baseTab = newBaseTab("Flip Field", portals, t)

t.basePortals = make(map[string]struct{})
warningLabel := fltk.NewBox(fltk.NO_BOX, 0, 0, 700, 30)
warningLabel.SetAlign(fltk.ALIGN_INSIDE | fltk.ALIGN_LEFT)
warningLabel.SetLabel("* WARNING: a greedy algorithm which finds suboptimal solutions *")
@@ -66,6 +68,7 @@ func newFlipFieldTab(portals *Portals) *flipFieldTab {
}

func (t *flipFieldTab) onReset() {
t.basePortals = make(map[string]struct{})
t.backbone = nil
t.flipPortals = nil
t.solutionText = ""
@@ -75,14 +78,21 @@ func (t *flipFieldTab) onSearch(progressFunc func(int, int), onSearchDone func()
if t.exactly.Value() {
numPortalLimit = lib.EQUAL
}
portals := t.enabledPortals()
base := []int{}
for i, portal := range portals {
if _, ok := t.basePortals[portal.Guid]; ok {
base = append(base, i)
}
}
options := []lib.FlipFieldOption{
lib.FlipFieldProgressFunc(progressFunc),
lib.FlipFieldBackbonePortalLimit{Value: int(t.numBackbonePortals.Value()), LimitType: numPortalLimit},
lib.FlipFieldFixedBaseIndices(base),
lib.FlipFieldMaxFlipPortals(int(t.maxFlipPortals.Value())),
lib.FlipFieldSimpleBackbone(t.simpleBackbone.Value()),
lib.FlipFieldNumWorkers(runtime.GOMAXPROCS(0)),
}
portals := t.enabledPortals()
go func() {
backbone, flipPortals := lib.LargestFlipField(portals, options...)
fltk.Awake(func() {
@@ -122,6 +132,20 @@ func (t *flipFieldTab) solutionPaths() [][]s2.Point {
return lines
}

func (t *flipFieldTab) portalLabel(guid string) string {
if _, ok := t.basePortals[guid]; ok {
return "Base"
}
return t.baseTab.portalLabel(guid)
}

func (t *flipFieldTab) portalColor(guid string) (color.Color, color.Color) {
if _, ok := t.basePortals[guid]; ok {
return color.NRGBA{0, 128, 0, 128}, t.baseTab.strokeColor(guid)
}
return t.baseTab.portalColor(guid)
}

func (t *flipFieldTab) enableSelectedPortals() {
for guid := range t.portals.selectedPortals {
delete(t.portals.disabledPortals, guid)
@@ -131,20 +155,40 @@ func (t *flipFieldTab) enableSelectedPortals() {
func (t *flipFieldTab) disableSelectedPortals() {
for guid := range t.portals.selectedPortals {
t.portals.disabledPortals[guid] = struct{}{}
delete(t.basePortals, guid)
}
}

func (t *flipFieldTab) makeSelectedPortalsBase() {
for guid := range t.portals.selectedPortals {
delete(t.portals.disabledPortals, guid)
t.basePortals[guid] = struct{}{}
}
}
func (t *flipFieldTab) unmakeSelectedPortalsBase() {
for guid := range t.portals.selectedPortals {
delete(t.basePortals, guid)
}
}

func (t *flipFieldTab) contextMenu() *menu {
var aSelectedGUID string
numSelectedEnabled := 0
numSelectedDisabled := 0
numSelectedBase := 0
numSelectedNotBase := 0
for guid := range t.portals.selectedPortals {
aSelectedGUID = guid
if _, ok := t.portals.disabledPortals[guid]; ok {
numSelectedDisabled++
} else {
numSelectedEnabled++
}
if _, ok := t.basePortals[guid]; ok {
numSelectedBase++
} else {
numSelectedNotBase++
}
}
menu := &menu{}
if len(t.portals.selectedPortals) > 1 {
@@ -166,10 +210,25 @@ func (t *flipFieldTab) contextMenu() *menu {
menu.items = append(menu.items, menuItem{"Disable All", t.disableSelectedPortals})
}
}
if numSelectedBase > 0 {
if len(t.portals.selectedPortals) == 1 {
menu.items = append(menu.items, menuItem{"Unmake base", t.unmakeSelectedPortalsBase})
} else {
menu.items = append(menu.items, menuItem{"Unmake all base", t.unmakeSelectedPortalsBase})
}
}
if numSelectedNotBase > 0 && numSelectedNotBase+len(t.basePortals) <= 2 {
if len(t.portals.selectedPortals) == 1 {
menu.items = append(menu.items, menuItem{"Make base", t.makeSelectedPortalsBase})
} else {
menu.items = append(menu.items, menuItem{"Make all base", t.makeSelectedPortalsBase})
}
}
return menu
}

type flipFieldState struct {
BasePortals []string `json:"basePortals"`
NumBackbonePortals int `json:"numBackbonePortals"`
Exactly bool `json:"exactly"`
MaxFlipPortals int `json:"maxFlipPortals"`
@@ -187,6 +246,9 @@ func (t *flipFieldTab) state() flipFieldState {
SimpleBackbone: t.simpleBackbone.Value(),
SolutionText: t.solutionText,
}
for baseGUID := range t.basePortals {
state.BasePortals = append(state.BasePortals, baseGUID)
}
for _, backbonePortal := range t.backbone {
state.Backbone = append(state.Backbone, backbonePortal.Guid)
}
@@ -197,6 +259,13 @@ func (t *flipFieldTab) state() flipFieldState {
}

func (t *flipFieldTab) load(state flipFieldState) error {
t.basePortals = make(map[string]struct{})
for _, baseGUID := range state.BasePortals {
if _, ok := t.portals.portalMap[baseGUID]; !ok {
return fmt.Errorf("unknown herringbone base portal \"%s\"", baseGUID)
}
t.basePortals[baseGUID] = struct{}{}
}
if state.NumBackbonePortals <= 0 {
return fmt.Errorf("non-positive flipField.numBackbonePortals value %d", state.NumBackbonePortals)
}
8 changes: 8 additions & 0 deletions lib/common.go
Original file line number Diff line number Diff line change
@@ -107,6 +107,14 @@ func hasAllIndicesInTheTriple(indices []int, a, b, c int) bool {
}
return true
}
func hasAllPortalIndicesInThePair(indices []portalIndex, a, b portalIndex) bool {
for _, index := range indices {
if a != index && b != index {
return false
}
}
return true
}

func hasAllIndicesInThePair(indices []int, a, b int) bool {
for _, index := range indices {
32 changes: 27 additions & 5 deletions lib/flip_field.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package lib

import "github.com/golang/geo/s2"
import (
"github.com/golang/geo/s2"
)

// LargestFlipField -
func LargestFlipField(portals []Portal, options ...FlipFieldOption) ([]Portal, []Portal) {
@@ -32,14 +34,16 @@ type bestFlipFieldQuery struct {
numPortalLimit PortalLimit
maxFlipPortals int
simpleBackbone bool
fixedBaseIndices []portalIndex
}

func newBestFlipFieldQuery(portals []portalData, maxBackbonePortals int, numPortalLimit PortalLimit, maxFlipPortals int, simpleBackbone bool) bestFlipFieldQuery {
func newBestFlipFieldQuery(portals []portalData, fixedBaseIndices []portalIndex, maxBackbonePortals int, numPortalLimit PortalLimit, maxFlipPortals int, simpleBackbone bool) bestFlipFieldQuery {
return bestFlipFieldQuery{
maxBackbonePortals: maxBackbonePortals,
numPortalLimit: numPortalLimit,
maxFlipPortals: maxFlipPortals,
simpleBackbone: simpleBackbone,
fixedBaseIndices: fixedBaseIndices,
portals: portals,
backbone: make([]portalData, 0, maxBackbonePortals),
candidates: make([]portalData, 0, len(portals)),
@@ -106,6 +110,14 @@ func numFlipFields(numFlipPortals, numBackbonePortals int) int {
return numFlipPortals * (2*numBackbonePortals - 3)
}

func sliceContains(slice []portalIndex, ix portalIndex) bool {
for _, val := range slice {
if val == ix {
return true
}
}
return false
}
func (f *bestFlipFieldQuery) findBestFlipField(p0, p1 portalData, ccw bool) ([]portalData, []portalData, float64) {
if ccw {
f.candidates = portalsLeftOfLine(f.portals, p0, p1, f.candidates[:0])
@@ -137,6 +149,9 @@ func (f *bestFlipFieldQuery) findBestFlipField(p0, p1 portalData, ccw bool) ([]p
segCCW := newCCWQuery(f.backbone[pos-1].LatLng, f.backbone[pos].LatLng)
tripleIndexBase := (uint64(f.backbone[pos-1].Index)*numAllPortals + uint64(f.backbone[pos].Index)) * numAllPortals
for i, candidate := range f.candidates {
if sliceContains(f.fixedBaseIndices, candidate.Index) {
continue
}
tripleIndex := tripleIndexBase + uint64(candidate.Index)
if _, ok := nonBeneficialTriples[tripleIndex]; ok {
continue
@@ -178,7 +193,7 @@ func (f *bestFlipFieldQuery) findBestFlipField(p0, p1 portalData, ccw bool) ([]p
}
}
// Check if appending a new backbone portal would improve the solution.
{
if !sliceContains(f.fixedBaseIndices, f.backbone[len(f.backbone)-1].Index) {
pos := len(f.backbone) - 1
zeroLast := newCCWQuery(f.backbone[0].LatLng, f.backbone[pos].LatLng)
for i, candidate := range f.portals {
@@ -211,7 +226,7 @@ func (f *bestFlipFieldQuery) findBestFlipField(p0, p1 portalData, ccw bool) ([]p
}
}
// Check if prepending a new backbone portal would improve the solution.
{
if !sliceContains(f.fixedBaseIndices, f.backbone[0].Index) {
zeroLast := newCCWQuery(f.backbone[0].LatLng, f.backbone[len(f.backbone)-1].LatLng)
for i, candidate := range f.portals {
if f.backbone[len(f.backbone)-1].Index == candidate.Index || f.backbone[0].Index == candidate.Index {
@@ -333,6 +348,10 @@ func LargestFlipFieldST(portals []Portal, params flipFieldParams) ([]Portal, []P
panic("Too short portal list")
}
portalsData := portalsToPortalData(portals)
fixedBaseIndices := []portalIndex{}
for _, i := range params.fixedBaseIndices {
fixedBaseIndices = append(fixedBaseIndices, portalsData[i].Index)
}

numPairs := len(portals) * (len(portals) - 1)
everyNth := numPairs / 1000
@@ -346,14 +365,17 @@ func LargestFlipFieldST(portals []Portal, params flipFieldParams) ([]Portal, []P
var bestNumFields int
bestBackbone, bestFlipPortals := []portalData(nil), []portalData(nil)
var bestBackboneLength float64
q := newBestFlipFieldQuery(portalsData, params.maxBackbonePortals, params.backbonePortalLimit, params.maxFlipPortals, params.simpleBackbone)
q := newBestFlipFieldQuery(portalsData, fixedBaseIndices, params.maxBackbonePortals, params.backbonePortalLimit, params.maxFlipPortals, params.simpleBackbone)
for _, p0 := range portalsData {
for _, p1 := range portalsData {
if p0.Index == p1.Index {
continue
}
for _, ccw := range []bool{true, false} {
b, f, bl := q.findBestFlipField(p0, p1, ccw)
if len(b) <= 2 || !hasAllPortalIndicesInThePair(fixedBaseIndices, b[0].Index, b[len(b)-1].Index) {
continue
}
if params.backbonePortalLimit != EQUAL || len(b) == params.maxBackbonePortals {
numFlipPortals := len(f)
if params.maxFlipPortals > 0 && numFlipPortals > params.maxFlipPortals {
21 changes: 15 additions & 6 deletions lib/flip_field_mt.go
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ import (

type bestFlipFieldMtQuery struct {
portals []portalData
fixedBaseIndices []portalIndex
maxBackbonePortals int
maxFlipPortals int
numPortalLimit PortalLimit
@@ -21,6 +22,7 @@ func (f *bestFlipFieldMtQuery) findBestFlipField(p0, p1 portalData, ccw bool, ba
simpleBackbone: f.simpleBackbone,
bestSolution: bestSolution,
portals: f.portals,
fixedBaseIndices: f.fixedBaseIndices,
backbone: backbone,
candidates: candidates,
flipPortals: flipPortals,
@@ -71,6 +73,10 @@ func LargestFlipFieldMT(portals []Portal, params flipFieldParams) ([]Portal, []P
panic(fmt.Errorf("too short portal list: %d", len(portals)))
}
portalsData := portalsToPortalData(portals)
fixedBaseIndices := []portalIndex{}
for _, i := range params.fixedBaseIndices {
fixedBaseIndices = append(fixedBaseIndices, portalsData[i].Index)
}

backboneCache := sync.Pool{
New: func() interface{} {
@@ -88,11 +94,12 @@ func LargestFlipFieldMT(portals []Portal, params flipFieldParams) ([]Portal, []P
var wg sync.WaitGroup
wg.Add(params.numWorkers)
q := &bestFlipFieldMtQuery{
maxBackbonePortals: params.maxBackbonePortals,
numPortalLimit: params.backbonePortalLimit,
maxFlipPortals: params.maxFlipPortals,
simpleBackbone: params.simpleBackbone,
portals: portalsData}
maxBackbonePortals: params.maxBackbonePortals,
numPortalLimit: params.backbonePortalLimit,
maxFlipPortals: params.maxFlipPortals,
simpleBackbone: params.simpleBackbone,
portals: portalsData,
fixedBaseIndices: fixedBaseIndices}
for i := 0; i < params.numWorkers; i++ {
go bestFlipFieldWorker(q, requestChannel, responseChannel, &wg)
}
@@ -132,7 +139,9 @@ func LargestFlipFieldMT(portals []Portal, params flipFieldParams) ([]Portal, []P
bestBackbone, bestFlipPortals := []portalData(nil), []portalData(nil)
var bestBackboneLength float64
for resp := range responseChannel {
if params.backbonePortalLimit != EQUAL || len(resp.backbone) == params.maxBackbonePortals {
if len(resp.backbone) >= 2 &&
hasAllPortalIndicesInThePair(fixedBaseIndices, resp.backbone[0].Index, resp.backbone[len(resp.backbone)-1].Index) &&
(params.backbonePortalLimit != EQUAL || len(resp.backbone) == params.maxBackbonePortals) {
numFlipPortals := len(resp.flipPortals)
if params.maxFlipPortals > 0 && numFlipPortals > params.maxFlipPortals {
numFlipPortals = params.maxFlipPortals
8 changes: 8 additions & 0 deletions lib/flip_field_options.go
Original file line number Diff line number Diff line change
@@ -39,10 +39,17 @@ func (f FlipFieldSimpleBackbone) apply(params *flipFieldParams) {
params.simpleBackbone = bool(f)
}

type FlipFieldFixedBaseIndices []int

func (f FlipFieldFixedBaseIndices) apply(params *flipFieldParams) {
params.fixedBaseIndices = []int(f)
}

type flipFieldParams struct {
progressFunc func(int, int)
maxBackbonePortals int
backbonePortalLimit PortalLimit
fixedBaseIndices []int
maxFlipPortals int
numWorkers int
simpleBackbone bool
@@ -52,6 +59,7 @@ func defaultFlipFieldParams() flipFieldParams {
return flipFieldParams{
maxBackbonePortals: 16,
backbonePortalLimit: EQUAL,
fixedBaseIndices: nil,
maxFlipPortals: 0,
simpleBackbone: false,
numWorkers: runtime.GOMAXPROCS(0),

0 comments on commit ddd7686

Please sign in to comment.