Make quick first prototype
This commit is contained in:
parent
c1301d30cb
commit
ac7b103305
@ -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
|
||||
|
11
defaults.go
11
defaults.go
@ -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
33
main.go
@ -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
35
parsing/extract.go
Normal 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
16
parsing/match.go
Normal 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
73
parsing/parser.go
Normal 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
75
runtime/run.go
Normal 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
16
settings/defaults.go
Normal 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
45
settings/instance.go
Normal 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
83
settings/solver.go
Normal 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
|
||||
}
|
Reference in New Issue
Block a user