@hackage minizinc-process0.1.4.0

A set of helpers to call minizinc models.

minizinc-process

MiniZinc is a language and a toolchain to solve discrete optimization problems. This package offers wrappers around the minizinc executable to pass inputs and outputs.

Assume that a primitive MiniZinc model is available at the path models/example001.mzn.

0..100: x;
var int: y;
constraint x < y;

This model expects x as an Int and decides y as an Int if a solution is found. Ideally we would like to use minizinc and this model like a function of type Int -> IO (Maybe Int) function in Haskell. This package provides building blocks to create such a mapping.

Implementation

This package relies on JSON support for MiniZinc by using JSON as an intermediary representation. On the Haskell side we picked the popular aeson package for serializing values.

MiniZinc input binds names to variables, hence the Int -> IO (Maybe Int) example above is insufficient: inputs and outputs need to translate to JSON Object constructor of Aeson's Value type.

Example Use

The runLastMinizincJSON function requires some configuration object to provide parameters like the solver backing MiniZinc, the timeout, where to store MiniZinc data files. The simpleMiniZinc function provides a smart constructor for building such an environment.

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TypeApplications #-}

module Main where

import Data.Aeson
import Data.Hashable
import GHC.Generics
import Process.Minizinc
import Process.Minizinc.Inspect

data Input = Input {x :: Int}
  deriving (Generic)

instance ToJSON Input -- required by `runLastMinizincJSON` for serialization of input

instance Hashable Input -- required by `simpleMiniZinc` to create a somewhat unique filepath

data Output = Output {y :: Int}
  deriving (Show, Generic)

instance FromJSON Output -- required by `runLastMinizincJSON` for deserialization of output

main :: IO ()
main = do
  inspect "models/example001.mzn" >>= print
  let mzn = simpleMiniZinc @Input @Output "models/example001.mzn" 1000 Gecode
  let problem = Input 10
  runLastMinizincJSON mzn problem >>= print

The @Input and @Output syntax allow to pass type parameters to simpleMiniZinc, this style is optional but helps the GHC compiler inference (in our example, this type application is the only indication needed to tell the compiler to deserialize Output objects).

A more-general function named runMinizincJSON allows to stream results iteratively has the solver progresses. This function is more complicated than runLastMinizincJSON has it takes a callback to consume an output and control whether to continue reading inputs or not (i.e., a coroutine) plus some initial state that is carried over (a bit like a fold). Luckily, we provide two coroutines keepLast and collectResults for some typical use cases.

Usage in a project

MiniZinc model files

In a typical project, you will have fixed models and varying inputs. That is, you would like to carry the models along with the code (e.g., a web application or gRPC server using minizinc in the background) in a same repository as your Haskell code. One option is to leverage the support of cabal data-files.

Serialization and DeSerialization

You will still need some mapping functions to translate between domain objects like User into the JSON values that MiniZinc requires: objects do not map well with relations. We may consider compile-time helpers like TemplateHaskell, but at this time it would not be immediately feasible. Be at peace with this.

A module named Process.Minizinc.TH has TemplateHaskell functions to generate. As of today, you'll still need to activate some extensions and import some libraries so that the TemplateHaskell-generated code compiles: as in the following example.

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE TemplateHaskell #-}

import Data.Aeson
import Data.Hashable
import Process.Minizinc.TH
import GHC.Generics

genModelData "MyModel" "models/mymodel.mzn"
int: x;
array[1..2] of int: y;
var int: z;

...

Will generate the following haskell codes

data MyModelOutput = MyModelOutput {
  z :: Int
} deriving (Show, Eq, Ord, Generic, Hashable, ToJSON, FromJSON)

data MyModelInput = MyModelInput {
  x :: Int,
  y :: [[Int]]
} deriving (Show, Eq, Ord, Generic, Hashable, ToJSON, FromJSON)

See examples/Main.hs for an example usage of TemplateHaskell.

Temporary data files

For now, the implementation leverages file-system to pass the JSON object to MiniZinc, this design means you should pay attention to disk usage and maybe clean the clutter. A function named cleanTmpFile will remove the .json disk file for a given pair of MiniZinc and input objects.

Development

Testing

We use Hedgehog to test the overall system at once rather than having individual tests for the internal parser and other files.

Test cases are in .hs files under the test directory whereas the .mzn models are in the models directory. We use a naming nomenclature to help organize what files require what input/output types: test{inputtype}_{testnum}.mzn where inputtype pertains to the haskell Input/Output types and testnum pertains to the test number. Thus: all testnum are unique and are groupable by inputtype.

Misc.

The author of this package is not affiliated with MiniZinc. See also: [https://www.minizinc.org/](https://www.minizinc.org/).