Meet AMoR, the Modeling Language I Created in Only One Day

I have created a modeling language for linear and integer programming. You may wonder why I did that, because there are already some out there (AIMMS, AMPL, GAMS and ZIMPL to name a few). The first answer is: because I could. Seriously - if you knew you could pull something like that off in a single day, wouldn't you be tempted to just do it?

But there is another answer. All modeling languages I encountered so far suffer from a common issue. While they are usually easy to learn in the beginning, they quickly start to become a burden once you try to do more complex things with them. Want to pull data from a database, website or some fancy document format? That might be supported. But it might also not be - the more likely case. It would be far fetched to expect the maintainers of the modeling language to include adapters for all possible forms of data sources. But that leaves us with doing dirty hacks to get the data into the model - resulting in code that is anything but maintainable and future proof. Another common issue is, that implementing more or less complex logic into the model can be difficult and also slow. These problems usually start as soon as one needs to do something other than iterating or summing over an existing set and become worse when there are formulas and conditions involved that can't be properly expressed in closed form.

All this is bad if you want to learn operations research. You learn how models work, then you learn how the modeling language works. Everything is fine, the learning curve was steep, you had some successes solving example problems. But as soon as you want to solve real problems, the bad things start to happen and it takes you incredible amounts of time to discover how to overcome even simple obstacles (like getting your data into the model). Thats when people start to suggest to just use the Java / Python / etc. interface of some solver directly. While some of those interfaces are very well designed (I particularly like the Python interface of Gurobi), it still means that your learning curve gets quite a dent. The work you spent for learning the modeling language is of no use anymore. On top of that you lose advantages the modeling language had - like being able to exchange the solver in the background.

So how to overcome these issues. The main problem is that modeling languages start with the algebraic functionality and later add features of a scripting language on top to become more flexible. But that is where they are doing a bad job. So my idea was to take an existing scripting language and put the algebraic functionality on top. This way you can get the best from both worlds, the ease of use of any other modeling language together with the flexibility of the original scripting language. The article that gave me the inspiration for this was on Ruby Inside and explains how to build a domain specific language (DSL) in Ruby. The great thing here is, that Ruby has a very versatile syntax that can easily be extended with custom functionality. This makes it easy to add the look and feel of a modeling language right into Ruby. For example a knapsack model in AMoR (thats how I named the language: Amazing Modeling with Ruby) can look this way:

# The data
fruits = ['Pepper', 'Pumpkin', 'Melon', 'Garlic', 'Apple', 'Peach']
value = {
  'Pepper' => 6,
  'Pumpkin' => 12,
  'Melon' => 11,
  'Garlic' => 5,
  'Apple' => 5,
  'Peach' => 7
}
size = {
  'Pepper' => 3,
  'Pumpkin' => 6,
  'Melon' => 5,
  'Garlic' => 2,
  'Apple' => 3,
  'Peach' => 3
}

# Maximize total value
max sum(fruits) {|f| value[f] * x(f)}

# Keeping in mind our knapsack capacity
st sum(fruits) {|f| size[f] * x(f) } <= 15

# Either pack fruit or leave it at home
forall(fruits) {|f| binary x(f)}

# Run SCIP
scip

# Output
puts "You should pack: #{fruits.select{|f| x(f) > 0}.join(', ')}."
puts "That is a total value of #{objective}."

Sufficiently simple isn't it. And now expressing complex logic or pulling in data is no issue anymore, because you can easily define functions for that and include already existing packages for most needs (there seriously is a Ruby Gem for almost everything).

Extending the already existing language turned out to have a lot of advantages. First of all I didn't have to write my own parser, because that is handled by the Ruby interpreter (which by the way does this job better than I could ever have implemented it). And as I could lend from the existing functionality there was only a ridiculously small amount of code I had to write to get the necessary algebraic functionality (I will write an article about how those things work some time in the near future). In fact there are currently only 422 lines of code (check out the repo) to make this work - alongside 727 lines of tests (I am becoming a TDD fanatic). No wonder this could be easily done in a single day.

Up till now I have included an export to the LP file format as well as a simple connection for the SCIP solver. Also there is currently only support for linear and integer programs. So there is obviously more work to be done. But I believe that in the long run AMoR could be an excellent tool to make optimization more convenient and thereby used more widely.

If you want to know more, checkout the small documentation website I created: amor-gem.com.

Finally, if you like the idea, feel free to contact me. There is still space in the development team ;-)


comments powered by Disqus