Skip to content

Commit

Permalink
#6: Cyclic representation of AP. (#8)
Browse files Browse the repository at this point in the history
* #6: Cyclic representation of AP.

* #6: Updates readme.
  • Loading branch information
ryanjoneil authored Aug 11, 2019
1 parent 74c66d9 commit e59ec2d
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 36 deletions.
26 changes: 16 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@

## v0.2.0

* `ap.Assignment` is renamed to `ap.Permutation`.
* `ap.Inverse(ap.Assignment)` is now `Permutation.Inverse()`.
* `ap.ToMatrix(ap.Assignment)` is now `Permutation.Matrix()`.
* `ap.ToPermutation(ap.Matrix)` is now `Matrix.Permutation()`.
* #6: Provide Cycle struct and method
* Adds `ap.Cycle` and `ap.Cycles`.
* `Permutation`, `Cycles,` and `Matrix` structs all convert to each other.
* All assignment problem representations provide `Inverse` methods.
* #5: Rename "Assignment" to "Permutation"
* `ap.Assignment` renamed to `ap.Permutation`.
* `ap.Inverse(ap.Assignment)` is now `Permutation.Inverse()`.
* `ap.ToMatrix(ap.Assignment)` is now `Permutation.Matrix()`.
* `ap.ToPermutation(ap.Matrix)` is now `Matrix.Permutation()`.

## v0.1.0

* New API supports multiple forms of assignment problems.
* Split code into `ap` and `lsap` packages.
* Added benchmarks and examples.
* Adopted Go modules and versioning.
* Added `lsap` binary under `cmd` subfolder.
* Licensed library under APL v2.
* #1: AP code and module refactor
* New API supports multiple forms of assignment problems.
* Splits code into `ap` and `lsap` packages.
* Adds benchmarks and examples.
* Adopts Go modules and versioning.
* Adds `lsap` binary under `cmd` subfolder.
* Licenses library under APL v2.
48 changes: 37 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# `ap`: assignment problems
# `ap`: assignment problem solvers for go

[![Build Status](https://semaphoreci.com/api/v1/ryanjoneil/ap/branches/master/badge.svg)](https://semaphoreci.com/ryanjoneil/ap)

Expand Down Expand Up @@ -54,9 +54,41 @@ EOF
}
```

## `lsap` Package
## Quick Start: Packages

In order to embed an LSAP solver in other Go code, `go get` the library.
Extensive examples are available in the module docs.

```bash
godoc -http=localhost:6060
```

### `ap`: assignment representations & interfaces

Package `ap` provides solution representations and interfaces for working with assignment problems and solvers.

```bash
go get github.com/ryanjoneil/ap
```

The default representation of an assignment produced by an `Assigner` is a `Permutation`.

```go
a := SomeAssigner{} // implements ap.Assign
p := a.Assign() // p is an ap.Permutation
```

Permutations can be converted to cyclic and matrix representations of assignments, and vice versa. All representations provide `Inverse` methods reverse the direction of assignment.

```go
p := ap.Permutation{1, 0, 2, 6, 5, 3, 4}
p.Cycles() // {{0, 1}, {2}, {3, 6, 4, 5}}
p.Inverse() // {1, 0, 2, 5, 6, 4, 3}
p.Matrix() // // p[u] == v -> m[u][v] == true
```

### `lsap`: linear sum assignment problem solver

Package `ap/lsap` provides a efficient, iterative implementation of a primal-dual linear sum assignment problem solver.

```bash
go get github.com/ryanjoneil/ap/lsap
Expand All @@ -71,12 +103,6 @@ a := lsap.New([][]int64{
{11, 91, 10},
})

assignment := a.Assign() // [1 2 0]
cost := a.Cost() // 49
```

Extensive examples are available in the module docs.

```bash
godoc -http=localhost:6060
permutation := a.Assign() // [1 2 0]
cost := a.Cost() // 49
```
89 changes: 89 additions & 0 deletions cycles.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package ap

// A Cycle is an individual cycle in an assignment, such as {1,2,4}. The cycle
// is assumed to contain an arc from the final element to the initial element,
// so this cycle has the arcs (1,2), (2,4) and (4,1). A cycle can contain a
// a single element with an arc to itself, such as {3}.
type Cycle []int

func (c Cycle) inverse() Cycle {
c2 := make(Cycle, 0, len(c))

// Start at the same node.
if len(c) > 0 {
c2 = append(c2, c[0])
}

// Procede through the remaining nodes in reverse order.
for i := len(c) - 1; i > 0; i-- {
c2 = append(c2, c[i])
}

return c2
}

// Cycles represents multiple individual cycle structures corresponding to an
// assignment. For example, the permutation {1,0,2,6,5,3,4} is equivalent to the
// set of cycles {{0,1}, {2}, {3,6,4,5}}. If a permutation corresponds to a
// single cycle, then that cycle is a Hamiltonian cycle.
type Cycles []Cycle

// Inverse changes the direction of a set of cycles representing an assignment.
//
// c := ap.Cycles{{0, 1}, {2}, {3, 6, 4, 5}}
// c.Inverse() // {{0, 1}, {2}, {3, 5, 4, 6}}
func (c Cycles) Inverse() Cycles {
c2 := make(Cycles, len(c))
for i, cycle := range c {
c2[i] = cycle.inverse()
}
return c2
}

// Matrix converts a cyclic representation of an assignment into a permutation
// matrix.
func (c Cycles) Matrix() Matrix {
m := make(Matrix, c.len())
for u := range m {
m[u] = make([]bool, len(m))
}

for _, cycle := range c {
for i, u := range cycle {
v := cycle[0]
if i < len(cycle)-1 {
v = cycle[i+1]
}
m[u][v] = true
}
}

return m
}

// Permutation converts a cyclic representation of an assignment into a
// permutation representation.
//
// c := ap.Cycles{{0, 1}, {2}, {3, 6, 4, 5}}
// c.Permutation() // {1, 0, 2, 6, 5, 3, 4}
func (c Cycles) Permutation() Permutation {
p := make(Permutation, c.len())
for _, cycle := range c {
for i, u := range cycle {
v := cycle[0]
if i < len(cycle)-1 {
v = cycle[i+1]
}
p[u] = v
}
}
return p
}

func (c Cycles) len() int {
l := 0
for _, cycle := range c {
l += len(cycle)
}
return l
}
27 changes: 27 additions & 0 deletions cycles_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package ap_test

import (
"fmt"

"github.com/ryanjoneil/ap"
)

func ExampleCycles() {
c := ap.Cycles{{0, 1}, {2}, {3, 6, 4, 5}}
fmt.Println(c)
fmt.Println(c.Inverse())
fmt.Println(c.Matrix())
fmt.Println(c.Permutation())

// Output:
// [[0 1] [2] [3 6 4 5]]
// [[0 1] [2] [3 5 4 6]]
// - X - - - - -
// X - - - - - -
// - - X - - - -
// - - - - - - X
// - - - - - X -
// - - - X - - -
// - - - - X - -
// [1 0 2 6 5 3 4]
}
10 changes: 10 additions & 0 deletions matrix.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ import "strings"
// is true. Each row and each column has exactly one true element.
type Matrix [][]bool

// Cycles converts a permutation matrix to a cyclic representation.
func (m Matrix) Cycles() Cycles {
return m.Permutation().Cycles()
}

// Inverse inverts a permutation matrix, changing its direction.
func (m Matrix) Inverse() Matrix {
return m.Permutation().Inverse().Matrix()
}

// Permutation converts a matrix assignment representation into a permutation.
func (m Matrix) Permutation() Permutation {
p := make(Permutation, len(m))
Expand Down
32 changes: 25 additions & 7 deletions matrix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,34 @@ import (

func ExampleMatrix() {
m := ap.Matrix{
{false, true, false},
{true, false, false},
{false, false, true},
{false, true, false, false, false, false, false},
{true, false, false, false, false, false, false},
{false, false, true, false, false, false, false},
{false, false, false, false, false, false, true},
{false, false, false, false, false, true, false},
{false, false, false, true, false, false, false},
{false, false, false, false, true, false, false},
}
fmt.Println(m)
fmt.Println(m.Cycles())
fmt.Println(m.Inverse())
fmt.Println(m.Permutation())

// Output:
// - X -
// X - -
// - - X
// [1 0 2]
// - X - - - - -
// X - - - - - -
// - - X - - - -
// - - - - - - X
// - - - - - X -
// - - - X - - -
// - - - - X - -
// [[0 1] [2] [3 6 4 5]]
// - X - - - - -
// X - - - - - -
// - - X - - - -
// - - - - - X -
// - - - - - - X
// - - - - X - -
// - - - X - - -
// [1 0 2 6 5 3 4]
}
27 changes: 26 additions & 1 deletion permutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,34 @@ package ap
// A Permutation P maps elements from one set U = {0,...,n-1} to another set
// V = {0,...,n-1}, where P[u] = v means that u ∈ U is assigned to v ∈ V.
//
// a := ap.Permutation{1, 0, 2} // Assign 0 to 1, 1 to 0, and 2 to itself.
// p := ap.Permutation{1, 0, 2} // Assign 0 to 1, 1 to 0, and 2 to itself.
type Permutation []int

// Cycles convert a permutation representation of an assignment to a cyclical
// representation of that assignment. If the result contains a single cycle,
// then that cycle is a Hamiltonian cycle.
//
// p := ap.Permutation{1, 0, 2, 6, 5, 3, 4}
// p.Cycles() // {{0, 1}, {2}, {3, 6, 4, 5}}
func (p Permutation) Cycles() Cycles {
cycles := Cycles{}
seen := make([]bool, len(p))

c := Cycle{}
for start := range p {
for i := start; !seen[i]; i = p[i] {
seen[i] = true
c = append(c, i)
}
if len(c) > 0 {
cycles = append(cycles, c)
c = Cycle{}
}
}

return cycles
}

// Inverse converts an assignment from a set U to a set V to an assignment from
// V to U. If, for example, U is the left hand side of a bipartite matching and
// V is the right hand side, this function essentially swaps their sides.
Expand Down
19 changes: 12 additions & 7 deletions permutation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,21 @@ import (
)

func ExamplePermutation() {
p := ap.Permutation{1, 3, 2, 0}
p := ap.Permutation{1, 0, 2, 6, 5, 3, 4}
fmt.Println(p)
fmt.Println(p.Cycles())
fmt.Println(p.Inverse())
fmt.Println(p.Matrix())

// Output:
// [1 3 2 0]
// [3 0 2 1]
// - X - -
// - - - X
// - - X -
// X - - -
// [1 0 2 6 5 3 4]
// [[0 1] [2] [3 6 4 5]]
// [1 0 2 5 6 4 3]
// - X - - - - -
// X - - - - - -
// - - X - - - -
// - - - - - - X
// - - - - - X -
// - - - X - - -
// - - - - X - -
}

0 comments on commit e59ec2d

Please sign in to comment.