Skip to content

Commit

Permalink
v0.3 release (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanjoneil authored Jan 18, 2024
1 parent efa3b03 commit 52b73ed
Show file tree
Hide file tree
Showing 28 changed files with 502 additions and 359 deletions.
48 changes: 17 additions & 31 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -1,41 +1,27 @@
name: Go
name: go
on: [push]

jobs:

build:
name: Build
runs-on: ubuntu-latest
steps:

- name: Set up Go 1.12
uses: actions/setup-go@v1
with:
go-version: 1.12
id: go
name: lint, build & test

- name: go get golint
run: go get -u golang.org/x/lint/golint

- name: go get staticcheck
run: go get -u honnef.co/go/tools/cmd/staticcheck

- name: git clone
uses: actions/checkout@v1
runs-on: ubuntu-latest

- name: go fmt
run: test -z $(go fmt ./...)
steps:
- name: setup go
uses: actions/setup-go@v3
with:
go-version: '1.21'

- name: go get
run: go get -v -t -d ./...
- name: checkout
uses: actions/checkout@v3

- name: golint
run: $(go env GOPATH)/bin/golint -set_exit_status

- name: staticcheck
run: $(go env GOPATH)/bin/staticcheck ./...
- name: lint
uses: golangci/golangci-lint-action@v3

- name: go build
run: go build -race -v ./...
- name: build
run: go build -race -v ./...

- name: go test
run: go test -cover -race ./...
- name: test
run: go test -cover -race ./...
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# ap: Change Log

## v0.3.0

* LSAP solver auto-computes big-M.
* LSAP solver provides `M` and `SetM` methods.
* Update Go to v1.21.
* Update linting and GitHub Actions.
* Assigners operate on generic signed `int` types.
* Remove `Int64*` interface prefixes.
* Improve docs and examples.

## v0.2.0

* #12: Add cycle and matrix output to lsap command
Expand Down
4 changes: 2 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright 2019 Ryan J. O'Neil
Copyright 2023-2024 Ryan O'Neil

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -198,4 +198,4 @@
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
limitations under the License.
34 changes: 20 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
# `ap`: assignment problem solvers for go

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

This package provides interfaces and data structures common to formulating and solving [assignment problems](https://en.wikipedia.org/wiki/Assignment_problem), as well as production-ready codes for solving particular variants. More details about these can be found in:
This package provides interfaces and data structures common to formulating and
solving [assignment problems][ap], as well as codes for solving particular
variants. More details about these can be found in:

```text
Rainer Burkard, Mauro Dell'Amico, and Silvano Martello.
"Assignment Problems - Revised Reprint."
Society for Industrial and Applied Mathematics (2012).
```

At this time, `ap` only provides an incremental code to solve the Linear Sum Assignment Problem. Additional forms are planned for future milestones.
At this time, `ap` only provides an incremental code to solve the Linear Sum
Assignment Problem. Additional forms are planned for future milestones.

LSAPs take the following form:

Expand All @@ -29,7 +30,8 @@ To solve LSAPs from the command line, first install the `lsap` binary using Go.
go install github.com/ryanjoneil/ap/cmd/lsap
```

`lsap` reads JSON input data in the form of a square cost matrix from standard input and writes an optimal permutation and cost to standard output.
`lsap` reads JSON input data in the form of a square cost matrix from standard
input and writes an optimal permutation and cost to standard output.

```bash
cat <<EOF | lsap | jq
Expand All @@ -56,28 +58,28 @@ EOF

## Quick Start: Packages

Extensive examples are available in the module docs.

```bash
godoc -http=localhost:6060
```
Examples are available in the [package docs][docs].

### `ap`: assignment representations & interfaces

Package `ap` provides solution representations and interfaces for working with assignment problems and solvers.
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`.
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.
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}
Expand All @@ -88,7 +90,8 @@ p.Matrix() // p[u] == v -> m[u][v] == true

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

Package `ap/lsap` provides a efficient, iterative implementation of a primal-dual 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 Down Expand Up @@ -135,3 +138,6 @@ Flags:
-rc
output reduced cost matrix
```

[ap]: https://en.wikipedia.org/wiki/Assignment_problem
[docs]: https://pkg.go.dev/github.com/ryanjoneil/ap
2 changes: 1 addition & 1 deletion assigner.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package ap

// An Assigner creates a mapping between two sets U = V = {0,...,n-1}.
// An Assigner creates a mapping between two sets U = V = {0, ..., n-1}.
type Assigner interface {
Assign() Permutation
}
26 changes: 14 additions & 12 deletions cmd/lsap/lsap.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,19 @@ import (

func usage() {
out := flag.CommandLine.Output()
fmt.Fprintf(out, "%s solves linear sum assignment problems, given a square cost matrix\n", os.Args[0])
fmt.Fprintf(out, "Usage:\n")
fmt.Fprintf(out, "\t%s < input.json -dual -rc > output.json\n", os.Args[0])
fmt.Fprintf(out, "\tcat <<EOF | %s | jq\n", os.Args[0])
fmt.Fprintf(out, "\t[\n")
fmt.Fprintf(out, "\t\t[ 90, 76, 75, 70 ],\n")
fmt.Fprintf(out, "\t\t[ 35, 85, 55, 65 ],\n")
fmt.Fprintf(out, "\t\t[ 125, 95, 90, 105 ],\n")
fmt.Fprintf(out, "\t\t[ 45, 110, 95, 115 ]\n")
fmt.Fprintf(out, "\t]\n")
fmt.Fprintf(out, "\tEOF\n")
msg := " solves linear sum assignment problems, given a square cost matrix."
fmt.Fprint(out, os.Args[0])
fmt.Fprintln(out, msg)
fmt.Fprint(out, "\nUsage:\n\n")
fmt.Fprintf(out, "%s < input.json -dual -rc > output.json\n\n", os.Args[0])
fmt.Fprintf(out, "cat <<EOF | %s | jq\n", os.Args[0])
fmt.Fprintf(out, "[\n")
fmt.Fprintf(out, "\t[ 90, 76, 75, 70 ],\n")
fmt.Fprintf(out, "\t[ 35, 85, 55, 65 ],\n")
fmt.Fprintf(out, "\t[ 125, 95, 90, 105 ],\n")
fmt.Fprintf(out, "\t[ 45, 110, 95, 115 ]\n")
fmt.Fprintf(out, "]\n")
fmt.Fprintf(out, "EOF\n\n")
fmt.Fprintf(out, "Flags:\n")
flag.PrintDefaults()
}
Expand All @@ -41,7 +43,7 @@ func main() {

a := lsap.New(c)
p := a.Assign()
out := map[string]interface{}{
out := map[string]any{
"permutation": p,
"cost": a.Cost(),
}
Expand Down
12 changes: 6 additions & 6 deletions coster.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package ap

// An Int64Coster provides an int64 cost value. This is usually the objective of
// A Coster provides an Integer cost value. This is usually the objective of
// value of a particular assignment.
type Int64Coster interface {
Cost() int64
type Coster[T Integer] interface {
Cost() T
}

// An Int64ReducedCoster provides a method for computing the reduced cost of
// A ReducedCoster provides a method for computing the reduced cost of
// assigning u ∈ U to v ∈ V, where u and v are both integers from 0 to n-1. The
// reduced cost of a basic edge (already part of an assignment) is zero, since
// it does not change the solution. Introducing a nonbasic edge (not in the
// assignment) may change the resulting assignment's overall cost.
type Int64ReducedCoster interface {
ReducedCost(u, v int) int64
type ReducedCoster[T Integer] interface {
ReducedCost(u, v int) T
}
22 changes: 8 additions & 14 deletions cycles.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package ap

// A Cycle is an individual cycle in an assignment, such as {1,2,4}. The cycle
// 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}.
// 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 {
Expand All @@ -14,7 +14,7 @@ func (c Cycle) inverse() Cycle {
c2 = append(c2, c[0])
}

// Procede through the remaining nodes in reverse order.
// Proceed through the remaining nodes in reverse order.
for i := len(c) - 1; i > 0; i-- {
c2 = append(c2, c[i])
}
Expand All @@ -23,15 +23,12 @@ func (c Cycle) inverse() Cycle {
}

// 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
// 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 {
Expand All @@ -45,7 +42,7 @@ func (c Cycles) Inverse() Cycles {
func (c Cycles) Matrix() Matrix {
m := make(Matrix, c.len())
for u := range m {
m[u] = make([]int8, len(m))
m[u] = make([]bool, len(m))
}

for _, cycle := range c {
Expand All @@ -54,7 +51,7 @@ func (c Cycles) Matrix() Matrix {
if i < len(cycle)-1 {
v = cycle[i+1]
}
m[u][v] = 1
m[u][v] = true
}
}

Expand All @@ -63,9 +60,6 @@ func (c Cycles) Matrix() Matrix {

// 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 {
Expand Down
29 changes: 22 additions & 7 deletions cycles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,31 @@ import (
"github.com/ryanjoneil/ap"
)

func ExampleCycles() {
func ExampleCycles_Inverse() {
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]]
// [[0 1 0 0 0 0 0] [1 0 0 0 0 0 0] [0 0 1 0 0 0 0] [0 0 0 0 0 0 1] [0 0 0 0 0 1 0] [0 0 0 1 0 0 0] [0 0 0 0 1 0 0]]
}

func ExampleCycles_Matrix() {
c := ap.Cycles{{0, 1}, {2}, {3, 6, 4, 5}}
for _, row := range c.Matrix() {
fmt.Println(row)
}
// Output:
// [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]
}

func ExampleCycles_Permutation() {
c := ap.Cycles{{0, 1}, {2}, {3, 6, 4, 5}}
fmt.Println(c.Permutation())
// Output:
// [1 0 2 6 5 3 4]
}
6 changes: 3 additions & 3 deletions doc.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Package ap defines interfaces and data structures common to formulating and
// solving assignment problems. More details about these can be found in:
//
// Rainer Burkard, Mauro Dell'Amico, and Silvano Martello.
// "Assignment Problems - Revised Reprint."
// Society for Industrial and Applied Mathematics (2012).
// Rainer Burkard, Mauro Dell'Amico, and Silvano Martello.
// "Assignment Problems - Revised Reprint."
// Society for Industrial and Applied Mathematics (2012).
package ap
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/ryanjoneil/ap

go 1.12
go 1.21
22 changes: 22 additions & 0 deletions integer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package ap

import "unsafe"

// Integer is any native signed int type.
type Integer interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}

// MaxOf returns the maximum value of any signed integer type.
func MaxOf[T Integer]() T {
return ^MinOf[T]()
}

// MinOf returns the minimum value of any signed integer type.
func MinOf[T Integer]() T {
// See: https://github.com/golang/go/issues/50019#issuecomment-1327464505
var zero T
minusone := ^zero
bits := 8 * unsafe.Sizeof(zero)
return minusone << (bits - 1)
}
Loading

0 comments on commit 52b73ed

Please sign in to comment.