Make quick first prototype

This commit is contained in:
Jip J. Dekker 2016-12-05 16:35:06 +01:00
parent c1301d30cb
commit ac7b103305
10 changed files with 372 additions and 19 deletions

View File

@ -1,2 +1,6 @@
# ChronoZinc
Small wrapper for `mzn-fzn` to benchmark MiniZinc models
# TODO
- Remove all usage of `viper` from parsing and runtime packages
- Provide separate the `run` and `parse` commands

View File

@ -1,11 +0,0 @@
package main
import "github.com/spf13/viper"
func setDefaults() {
viper.SetDefault("Processes", 1)
viper.SetDefault("RawDir", "raw")
viper.SetDefault("Solvers", map[string]interface{}{})
}

33
main.go
View File

@ -3,7 +3,11 @@ package main
import (
"fmt"
"os"
"path/filepath"
"github.com/jjdekker/chronozinc/parsing"
"github.com/jjdekker/chronozinc/runtime"
"github.com/jjdekker/chronozinc/settings"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@ -18,11 +22,26 @@ var rootCmd = &cobra.Command{
a sub-set of declared solvers and report on the statistics gathered. The data
gathered is saved in every stage.`,
Run: func(cmd *cobra.Command, args []string) {
file, err := os.Open(path)
if err != nil {
return nil, err
for _, file := range args {
switch filepath.Ext(file) {
case ".mzn":
viper.Set("models", append(viper.GetStringSlice("models"), file))
case ".dzn":
viper.Set("data", append(viper.GetStringSlice("data"), file))
default:
viper.SetConfigFile(args[0])
fmt.Println("Using config file:", args[0])
err := viper.MergeInConfig()
if err != nil {
panic(err)
}
}
}
viper.MergeConfig(file)
solvers := settings.SolversFromViper()
instances := settings.InstancesFromViper()
runtime.RunAll(solvers, instances)
parsing.ParseAll(solvers, instances)
},
}
@ -32,9 +51,9 @@ func initConfig() {
viper.SetConfigFile(cfgFile)
}
setDefaults()
settings.SetViperDefaults()
viper.SetConfigName("config.czn") // name of config file (without extension)
viper.SetConfigName("settings") // name of config file (without extension)
viper.AddConfigPath("$HOME/.config/chronozinc") // add home directory as first search path
viper.AddConfigPath("/etc/chronozinc") // adds global machine configuration
viper.SetEnvPrefix("czn") // set environment prefix
@ -43,8 +62,6 @@ func initConfig() {
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
} else {
fmt.Println("No config file found; using ENV and defaults")
}
}

35
parsing/extract.go Normal file
View File

@ -0,0 +1,35 @@
package parsing
import "regexp"
func Extract(file []byte, reg *regexp.Regexp) string {
submatches := reg.FindSubmatch(file)
if submatches == nil {
return ""
}
names := reg.SubexpNames()
for i, name := range names {
if name == "result" {
return string(submatches[i])
}
}
return ""
}
func ExtractLast(file []byte, reg *regexp.Regexp) string {
submatches := reg.FindAllSubmatch(file, -1)
if submatches == nil {
return ""
}
names := reg.SubexpNames()
for i, name := range names {
if name == "result" {
return string(submatches[len(submatches)-1][i])
}
}
return ""
}

16
parsing/match.go Normal file
View File

@ -0,0 +1,16 @@
package parsing
import (
"fmt"
"regexp"
)
func Match(file []byte, dict map[string]*regexp.Regexp) string {
for key, reg := range dict {
fmt.Printf("test %s", key)
if reg.Match(file) {
return key
}
}
return ""
}

73
parsing/parser.go Normal file
View File

@ -0,0 +1,73 @@
package parsing
import (
"encoding/csv"
"fmt"
"io/ioutil"
"log"
"os"
"github.com/jjdekker/chronozinc/settings"
"github.com/spf13/viper"
)
func ParseAll(solvers []settings.Solver, instances []settings.Instance) {
params := viper.GetStringSlice("parameters")
if len(params) > 0 {
f, err := os.Create(viper.GetString("output"))
if err != nil {
log.Panicf("Unable to create file %s", viper.GetString("output"))
}
defer f.Close()
w := csv.NewWriter(f)
defer w.Flush()
headers := append(persistantHeaders(), params...)
w.Write(headers)
for i := range solvers {
for j := range instances {
record := make([]string, 0, len(headers))
record = append(record,
[]string{solvers[i].Name, instances[j].Model}...)
if instances[j].Data != "" {
record = append(record, instances[j].Data)
}
for k := range params {
record = append(record,
ParseParameter(&solvers[i], &instances[j], params[k]))
}
w.Write(record)
}
}
}
}
func persistantHeaders() []string {
headers := []string{"solver", "model"}
if len(viper.GetStringSlice("data")) > 0 {
headers = append(headers, "data")
}
return headers
}
func ParseParameter(solver *settings.Solver, instance *settings.Instance,
parameter string) string {
fmt.Println(solver.Matchers)
var res string
if f, err := ioutil.ReadFile(instance.OutPath(solver.Name)); err != nil {
log.Printf("Unable to open file %s", instance.OutPath(solver.Name))
} else {
switch {
case (solver.Extractors[parameter] != nil):
res = Extract(f, solver.Extractors[parameter])
case (solver.LastExtractors[parameter] != nil):
res = ExtractLast(f, solver.LastExtractors[parameter])
case (solver.Matchers[parameter] != nil):
fmt.Println("do you see me?")
res = Match(f, solver.Matchers[parameter])
}
}
return res
}

75
runtime/run.go Normal file
View File

@ -0,0 +1,75 @@
package runtime
import (
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"github.com/jjdekker/chronozinc/settings"
"github.com/spf13/viper"
)
// RunAll runs every instance on every solver
func RunAll(solvers []settings.Solver, instances []settings.Instance) {
work := make(chan func())
wait := govern(work)
for i := range solvers {
for j := range instances {
work <- func() { RunInstance(&solvers[i], &instances[j]) }
}
}
close(work)
wait.Wait()
}
func govern(work <-chan func()) *sync.WaitGroup {
var wg sync.WaitGroup
procs := viper.GetInt("processes")
wg.Add(procs)
for i := 0; i < procs; i++ {
go func() {
for f := range work {
f()
}
wg.Done()
}()
}
return &wg
}
// RunInstance runs an instance on a Solver using mzn-fzn
func RunInstance(solver *settings.Solver, instance *settings.Instance) {
args := []string{
"--solver", solver.Binary,
"--flatzinc-flag", solver.Flags,
instance.Model,
}
if solver.Globals != "" {
args = append(args, "--globals-dir", solver.Globals)
}
if instance.Data != "" {
args = append(args, "--data", instance.Data)
}
args = append(args, strings.Split(viper.GetString("flags"), " ")...)
proc := exec.Command(viper.GetString("mznfzn"), args...)
if out, err := proc.CombinedOutput(); err != nil {
log.Printf("Instance %s ended with error %s", instance, err)
} else {
path := instance.OutPath(solver.Name)
os.MkdirAll(filepath.Dir(path), os.ModePerm)
err := ioutil.WriteFile(path, out, 0644)
if err != nil {
log.Printf("Saving results for instance %s ended with error %s",
instance, err)
}
}
}

16
settings/defaults.go Normal file
View File

@ -0,0 +1,16 @@
package settings
import "github.com/spf13/viper"
// SetViperDefaults set the default settings for the viper handler
func SetViperDefaults() {
viper.SetDefault("processes", 1)
viper.SetDefault("mznfzn", "mzn-fzn")
viper.SetDefault("raw_dir", "raw")
viper.SetDefault("flags", "--verbose --statistics")
viper.SetDefault("output", "benchmark.csv")
viper.SetDefault("comma", ",")
viper.SetDefault("crlf", false)
}

45
settings/instance.go Normal file
View File

@ -0,0 +1,45 @@
package settings
import (
"path/filepath"
"strings"
"github.com/spf13/viper"
)
// Instance contains the information to run one particular instance of a model
type Instance struct {
Model string
Data string
}
// InstancesFromViper extracts all instance information from Viper
func InstancesFromViper() []Instance {
var instances []Instance
data := viper.GetStringSlice("data")
for _, model := range viper.GetStringSlice("models") {
if len(data) > 0 {
for _, dat := range data {
instances = append(instances, Instance{Model: model, Data: dat})
}
} else {
instances = append(instances, Instance{Model: model})
}
}
return instances
}
// OutPath returns the intended output location of the instance given the solver
func (i *Instance) OutPath(solver string) string {
path := viper.GetString("raw_dir")
path = filepath.Join(path, solver)
cleanModel := strings.TrimSuffix(i.Model, filepath.Ext(i.Model))
path = filepath.Join(path, cleanModel)
if i.Data != "" {
cleanData := strings.TrimSuffix(i.Data, filepath.Ext(i.Data))
path = filepath.Join(path, cleanData)
}
return path + ".dat"
}

83
settings/solver.go Normal file
View File

@ -0,0 +1,83 @@
package settings
import (
"fmt"
"log"
"regexp"
"github.com/spf13/viper"
)
// Solver contains all information regarding a FlatZinc solver and its output
type Solver struct {
Name string // Solver name
Binary string // Binary location
Globals string // Globals directory
Flags string // FZN solver flags
Extractors map[string]*regexp.Regexp
LastExtractors map[string]*regexp.Regexp
Matchers map[string]map[string]*regexp.Regexp
}
// SolversFromViper extracts all solver information from Viper
func SolversFromViper() []Solver {
var solvers []Solver
for key := range viper.GetStringMap("solvers") {
options := viper.GetStringMapString("solvers." + key)
solver := Solver{
Name: key,
Flags: options["flags"],
Globals: options["globals"],
}
if bin, exists := options["binary"]; exists {
solver.Binary = bin
} else {
solver.Binary = solver.Name
}
solver.Extractors = make(map[string]*regexp.Regexp)
for i, str := range viper.GetStringMapString("solvers." + key + ".extractors") {
reg, err := regexp.Compile(str)
if err != nil {
log.Panicf("Error compiling extractor `%s:%s`: %s",
key, i, err)
} else {
solver.Extractors[i] = reg
}
}
solver.LastExtractors = make(map[string]*regexp.Regexp)
for i, str := range viper.GetStringMapString("solvers." + key + ".last_extractors") {
reg, err := regexp.Compile(str)
if err != nil {
log.Panicf("Error compiling extractor `%s:%s`: %s",
key, i, err)
} else {
solver.LastExtractors[i] = reg
}
}
solver.Matchers = make(map[string]map[string]*regexp.Regexp)
for i := range viper.GetStringMap("solvers." + key + ".matchers") {
matcher := make(map[string]*regexp.Regexp)
for j, str := range viper.GetStringMapString("solvers." + key + ".matchers." + i) {
reg, err := regexp.Compile(str)
if err != nil {
log.Panicf("Error compiling match case `%s:%s:`: %s",
key, i, err)
} else {
matcher[j] = reg
}
}
fmt.Println(matcher)
solver.Matchers[i] = matcher
}
solvers = append(solvers, solver)
}
return solvers
}