Git Product home page Git Product logo

rulp's Introduction

Rulp Gem Version Downloads Inline docs Codeship Status for wouterken/rulp

Table of Contents generated with DocToc

Rulp

Rulp is an easy to use Ruby DSL for generating linear programming and mixed integer programming problem descriptions in the LP file format.

The [.lp] file format can be read and executed by most LP solvers including coin-Cbc, Scip, GLPK, CPLEX and Gurobi.

Rulp will execute and parse the results generated by Cbc, Scip, fscip(ug) and GLPK.

Rulp is inspired by the ruby wrapper for the GLPK toolkit and the python LP library "Pulp".

Sample Code

	# maximize
	#   z = 10 * x + 6 * y + 4 * z
	#
	# subject to
	#   p:      x +     y +     z <= 100
	#   q: 10 * x + 4 * y + 5 * z <= 600
	#   r:  2 * x + 2 * y + 6 * z <= 300
	#
	# where all variables are non-negative integers
	#   x >= 0, y >= 0, z >= 0
	#


	given[

	X_i >= 0,
	Y_i >= 0,
	Z_i >= 0

	]

	result = Rulp::Max( 10 * X_i + 6 * Y_i + 4 * Z_i ) [
	                X_i +     Y_i +     Z_i <= 100,
	           10 * X_i + 4 * Y_i + 5 * Z_i <= 600,
	           2 *  X_i + 2 * Y_i + 6 * Z_i <= 300
	].solve

	##
	# 'result' is the result of the objective function.
	# You can retrieve the values of variables by using the 'value' method
	# E.g
	#   X_i.value == 32
	#   Y_i.value == 67
	#   Z_i.value == 0
	##

Installation

To use Rulp as a complete solver toolkit you will need one of either Glpsol(GLPK), Scip, Coin-Cbc or HiGHS installed. Here are some sample instructions of how you install each of these solvers. Be sure to read the license terms for each of these solvers before using them.

Scip:

Go to install directory

cd /usr/local

Download Scip source (Be sure to visit the SCIP website and check the license terms first.)

curl -O http://scip.zib.de/download/release/scipoptsuite-3.1.1.tgz

Extract Scip source

gunzip -c scipoptsuite-3.1.1.tgz  | tar xvf -

Scip relies on a number of libraries including ZLIB, GMP and READLINE. Please read the INSTALL directory in the application directory for help on getting these installed.

Build Scip

cd scipoptsuite-3.1.1
make

Add scip bin directory to your path E.g

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/scip-3.1.1/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/scip-3.1.1/bin"' >> ~/.zshrc

You should now have scip installed.

UG (Scip parallel)

To run scip using multiple cores you will need to use the UG library for scip. This is bundled in the scipoptsuite directory. To install this simply run

make ug

From the base directory and then add the fscip binary to your path. E.g

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/ug-0.7.5/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/scipoptsuite-3.1.1/ug-0.7.5/bin"' >> ~/.zshrc

Coin Cbc:

Navigate to install location

cd /usr/local

Follow CBC installation instructions

svn co https://projects.coin-or.org/svn/Cbc/stable/2.8 coin-Cbc
cd coin-Cbc
./configure -C  #Optionally add --enable-cbc-parallel here to enable multiple threads for cbc
make
make install

Add Coin-cbc to path

E.g

echo '\nexport PATH="$PATH:/usr/local/coin-Cbc/bin"' >> ~/.bash_profile

Or

echo '\nexport PATH="$PATH:/usr/local/coin-Cbc/bin"' >> ~/.zshrc

You should now have coin-Cbc installed.

GLPK:

Download the latest version of GLPK from http://www.gnu.org/software/glpk/#downloading

From the download directory

tar -xzf glpk-4.55.tar.gz
cd glpk-4.55
./configure --prefix=/usr/local
make
sudo make install

At this point, you should have GLPK installed. Verify it:

which glpsol
=> /usr/local/bin/glpsol

HiGHS:

Install HiGHS following the installation instructions at https://ergo-code.github.io/HiGHS/dev/interfaces/cpp/

Usage

Variables

	# Rulp variables are initialized as soon as they are needed so there is no
	# need to initialize them.
	# They follow a naming convention that defines their type.
	# A variable is declared as a constant with one of three different suffixes.
	# 'f' or '_f' indicates a general variable (No constraints)
	# 'i' or '_i' indicates a integer variable
	# 'b' or '_b' indicates a binary/boolean variable


	An_Integer_i
	=> #<IV:0x007ffc4b651b80 @name="An_Integer">

	Generalf
	=> #<LV:0x007ffc4b651b80 @name="General">

	Bool_Val_b
	=> #<BV:0x007ffc4b67b6b0 @name="Bool_Val">

	# In some cases it is implausible to generate a unique name for every possible variable
	# as an LP problem description may contain many hundreds of variables.
	# To handle these scenarios variable definitions can
	# accept index parameters to create large ranges of unique variables.
	# Examples of how indexed variables can be declared are as follows:

	Item_i(4,5)
	#<IV:0x007ffc4b3ea518 @name="Item4_5">

	Item_i("store_3", "table_2")
	#<IV:0x007ffc4b3a3cd0 @name="Itemstore_3_table_2">

	[*0..10].map(&Unit_f)
	=> [#<LV:0x007ffc4cc25768 @name="Unit0">,
	 #<LV:0x007ffc4cc24cf0 @name="Unit1">,
	 #<LV:0x007ffc4cc0fc88 @name="Unit2">,
	 #<LV:0x007ffc4cc0f260 @name="Unit3">,
	 #<LV:0x007ffc4cc0ecc0 @name="Unit4">,
	 #<LV:0x007ffc4cc0e748 @name="Unit5">,
	 #<LV:0x007ffc4cc0df50 @name="Unit6">,
	 #<LV:0x007ffc4cc0d9d8 @name="Unit7">,
	 #<LV:0x007ffc4cc0d460 @name="Unit8">,
	 #<LV:0x007ffc4cc0cee8 @name="Unit9">,
	 #<LV:0x007ffc4cc0c970 @name="Unit10">]

Variable Constraints

Add variable constraints to a variable using the <,>,<=,>=,== operators. Be careful to use '==' and not '=' when expressing equality. Constraints on a variable can only use numeric literals and not other variables. Inter-variable constraints should be expressed as problem constrants. (Explained below.)

	X_i < 5
	X_i.bounds
	=> "X <= 5"

	3 <= X_i < 15
	X_i.bounds
	=> "3 <= X <= 15"

	Y_f == 10
	Y_f.bounds
	=> "y = 10"

Problem constraints

Constraints are added to a problem using the :[] syntax.

	problem = Rulp::Max( 10 * X_i + 6 * Y_i + 4 * Z_i )

	problem[
		X_i +     Y_i +     Z_i <= 100
	]

	problem[
		10 * X_i + 4 * Y_i + 5 * Z_i <= 600
	]
	...
	problem.solve

You can add multiple constraints at once by comma separating them as seen in the earlier examples:

	Rulp::Max( 10 * X_i + 6 * Y_i + 4 * Z_i ) [
		                X_i +     Y_i +     Z_i <= 100,
		           10 * X_i + 4 * Y_i + 5 * Z_i <= 600,
		           2 *  X_i + 2 * Y_i + 6 * Z_i <= 300
		          ]

Solving or saving 'lp' files

There are multiple ways to solve the problem or output the problem to an 'lp' file. Currently the Rulp library supports calling installed executables for Scip, Cbc and Glpk. For each of these solvers it requires the solver command line binaries to be installed such that the command which [exec_name] returns a path. (I.e they must be on your PATH. See Installation).

Given a problem there are multiple ways to initiate a solver.

	@problem = Rulp::Max( 10 * X_i + 6 * Y_i + 4 * Z_i ) [
	                 X_i +     Y_i +     Z_i <= 100,
	           	10 * X_i + 4 * Y_i + 5 * Z_i <= 600,
	           	2 *  X_i + 2 * Y_i + 6 * Z_i <= 300
						]

Default solver:

	@problem.solve
	# this will use the solver specified in the environment variable 'SOLVER' by default.
	# This can be 'scip', 'cbc', 'glpk' or 'gurobi'.
	# You can also try and run scip or cbc in parallel using by passing an options argument containing { parallel: true }
	# If no variable is given it uses 'scip' as a default.

If you had a linear equation in a file named 'problem.rb' from the command line you could specify an alternate solver by executing:

	SOLVER=cbc ruby problem.rb

Explicit solver:

	@problem.scip
	# 	Or
	@problem.cbc
	#   Or
	@problem.glpk
	# 	Or
	@problem.cbc(parallel: true)
	#   Or
	@problem.scip(parallel: true)

Or

	Rulp::Scip(@problem)
	Rulp::Glpk(@problem)
	Rulp::Cbc(@problem)
	Rulp::scip(@problem, parallel: true)
	Rulp::cbc(@problem, parallel: true)

For debugging purposes you may wish to see the input and output files generated and consumed by Rulp. To do this you can pass open_definition and open_solution as options with the following extended syntax:

	Rulp::Cbc(@problem, open_definition: true, open_solution: true)

The optional booleans will optionally call the 'open' utility to open the problem definition or the solution. (This utility is installed by default on a mac and will not work if the utility is not on your PATH)

Additional Options

Mip tolerance.

You can provide a MIP tolerance to any of the solvers to allow it to return a sub-optimal result within this tolerance from the dual bound.

# Various ways to invoke this option
@problem.cbc(gap: 0.05)
Rulp::Cbc(@problem, gap: 0.05)
@problem.solve(gap: 0.05)
Node Limit.

You can limit the number of nodes the solver is able to explore before it must return it's current most optimal solution. If it has not discovered a solution by the time it hits the node limit it will return nil. Glpk does not accept a node limit option.

# Various ways to invoke this option
@problem.cbc(node_limit: 10_000)
Rulp::Cbc(@problem, node_limit: 10_000)
@problem.solve(node_limit: 10_000)

For Scip you can also specify these limits in a settings file called scip.set in the current directory. Options passed to Rulp will overwrite these values.

# E.g inside ./scip.set
limits/gap   = 0.15 # optimality within 0.1%.
limits/nodes = 200000 # No more than 200000 nodes explored

Saving LP files.

You may not wish to use one of the RULP compatible solvers but instead another solver that is able to read .lp files. (E.g CPLEX) but still want to use Rulp to generate your LP file. In this case you should use Rulp to output your lp problem description to a file of your choice. To do this simply use the following call

	@problem.save("/Users/johndoe/Desktop/myproblem.lp")

OR

	@problem.output("/Users/johndoe/Desktop/myproblem.lp")

You should also be able to call

	@problem.save

Without parameters to be prompted for a save location.

Examples.

Take a look at some basic examples in the ./examples directory in the source code.

Rulp Executable

Rulp comes bundled with a 'rulp' executable which by default loads the rulp environment and either the Pry or Irb REPL. (Preference for Pry). Once installed you should be able to simply execute 'rulp' to launch this rulp enabled REPL. You can then play with and attempt LP and MIP problems straight from the command line.

	[1] pry(main)> 13 <= X_i <= 45 # Declare integer variable
	=> X(i)[undefined]

	[2] pry(main)> -15 <= Y_f <= 15 # Declare float variable
	=> Y(f)[undefined]

	[3] pry(main)> @problem = Rulp::Min(X_i - Y_f) # Create min problem
	[info] Creating minimization problem
	=>
	Minimize
	 obj: X -Y
	Subject to
	0 X = 0
	Bounds
	 13 <= X <= 45
	 -15 <= Y <= 15
	General
	 X
	End

	[4] @problem[ X_i - 2 * Y_f < 40] #Adding a problem constraint
	=>
	Minimize
	 obj: X -Y
	Subject to
	  c0: X -2 Y <= 40
	Bounds
	 13 <= X <= 45
	 -15 <= Y <= 15
	General
	 X
	End


	[5] pry(main)> @problem.solve # Solve
	...
	[info] Solver took 0.12337
	[info] Parsing result
	=> -2.0 #(Minimal result)

	[6] pry(main)> Y_f # See value calculated for Y now that solver has run
	=> Y(f)[15.0]

	[8] pry(main)> X_i
	=> X(i)[13.0]

	# The result of the objective function (-2.0) was returned by the call to .solve
	# Now the solver has run and calculated values for our variables we can also test the
	# objective function (or any function) by calling evaluate on it.
	# E.g

	[9] (X_i - Y_f).evaluate
	=> -2.0

	[10] pry(main)> (2 * X_i + 15 * Y_f).evaluate
	=> 251.0

A larger example

Here is a basic example of how Rulp can help you model problems with a large number of variables. Suppose we are playing an IOS app which contains in-app purchases. Each of these in-app purchases costs a variable amount and gives us a certain number of in-game points. Suppose our mother gave us $55 to spend. We want to find the maximal number of in-game points we can buy using this money. Here is a simple example of how we could use Rulp to formulate this problem.

We decide to model each of these possible purchases as a binary variable (as we either purchase them or we don't. We can't partially purchase one.)

# Generate the data randomly for this example.
costs, points = [*0..1000].map do |i|
	[Purchase_b(i) * Random.rand(1.0..3.0), Purchase_b(i) * Random.rand(5.0..10.0)]
end.transpose.map(&:sum) #We sum the array of points and array of costs to create a Rulp expression

# And this is where the magic happens!. We ask rulp to maximise the number of points given
# the constraint that costs must be less than $55

Rulp::Max(points)[
	costs < 55
].solve
=> 538.2125623353652 (# You will get a different value as data was generated randomly)

# Now how do we check which purchases were selected?
selected_purchases = [*0..1000].map(&Purchase_b).select(&:selected?)

=> [Purchase27(b)[true],
 Purchase86(b)[true],
 Purchase120(b)[true],
 Purchase141(b)[true],
 Purchase154(b)[true],
 ...

MIP Minimization Example

Here is another example of using array style variables. Let's use the example of making a cup of coffee. Personally, I like my coffee with 2 creams and 1 sugar. There are many types of products that can add cream, or sugar to taste. Each has a different associated cost. As a price sensitive consumer what combination of products will allow me to flavor my coffee as desired with the lowest total cost?

  desired = [
    { ingredient: :cream, amount: 2 },
    { ingredient: :sugar, amount: 1 },
  ]

  products = [
    { name: 'Milk',                cost: 0.50, supplies: { cream: 1 } },
    { name: 'Baileys_Irish_Cream', cost: 2.99, supplies: { cream: 1, sugar: 1 } },
    { name: 'Non_Dairy_Creamer',   cost: 0.10, supplies: { cream: 1 } },
    { name: 'Sugar',               cost: 0.10, supplies: { sugar: 1 } },
  ]

  variables = products.map do |product|
    Products_i(product[:name])
  end
  # => [ ProductMilk_i, ProductBaileys_Irish_Cream_i, ProductNon_Dairy_Creamer_i, ProductSugar_i ]

  given[
    variables.map { |i| i >= 0 }
  ]
  # given [
  #   ProductMilk_i >= 0,
  #   ProductBaileys_Irish_Cream_i >= 0,
  #   ProductNon_Dairy_Creamer_i >= 0,
  #   ProductSugar_i >= 0,
  # ]

  constraints = desired.map do |minimum|
    ingredient = minimum[:ingredient]
    desired_amount = minimum[:amount]

    products.map.with_index do |p, i|
      coefficient = p[:supplies][ingredient] || 0
      coefficient * variables[i]
    end.inject(:+) >= desired_amount
  end
  # => [
  #    1 * ProductMilk_i + 1 * ProductBaileys_Irish_Cream_i + 1 * ProductNon_Dairy_Creamer_i + 0 * ProductSugar_i >= 2
  #    0 * ProductMilk_i + 1 * ProductBaileys_Irish_Cream_i + 0 * ProductNon_Dairy_Creamer_i + 1 * ProductSugar_i >= 1
  # ]

  objective = products.map.with_index { |p, i| p[:cost] * variables[i] }.inject(:+)
  # => 0.50 * ProductMilk_i + 2.99 * ProductBaileys_Irish_Cream_i + 0.10 * ProductNon_Dairy_Creamer_i + 0.10 * ProductSugar_i

  problem = Rulp::Min(objective)
  problem[constraints]

  Rulp::Glpk(problem)
  # DEBUG -- : Objective: 0.30000000000000004
  # Products = 0.0
  # Products = 0.0
  # Products = 2.0
  # Products = 1.0

  variables.map(&:value)
  # => [ 0, 0, 2, 1 ]

rulp's People

Contributors

dependabot[bot] avatar jonahgeorge avatar joseglego avatar kluzny avatar tjozwik avatar wouterken avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

rulp's Issues

Question regarding NoMethodError and given[]

Hey there,
I've been encountering this error while constructing some unit tests and I'm curious if you've seen this before (very likely is a logic error on my part but wanted to double-check).

Explanation
It appears to only happen when the length of @sections is 1, any value higher and this error disappears. The length of @students can be anything and it still works.

NoMethodError:
  undefined method `variables' for VAR56-26(b)[undefined]:BV
# /Users/jonahgeorge/.gem/ruby/2.3.0/gems/rulp-0.0.26/lib/extensions/kernel_extensions.rb:35:in `method_missing'
# /Users/jonahgeorge/.gem/ruby/2.3.0/gems/rulp-0.0.26/lib/rulp/rulp.rb:103:in `each'
# /Users/jonahgeorge/.gem/ruby/2.3.0/gems/rulp-0.0.26/lib/rulp/rulp.rb:103:in `flat_map'
# /Users/jonahgeorge/.gem/ruby/2.3.0/gems/rulp-0.0.26/lib/rulp/rulp.rb:103:in `[]'
# ./lib/integer_linear_program.rb:79:in `block in fte_contraint'
# /Users/jonahgeorge/.gem/ruby/2.3.0/gems/activerecord-4.2.0/lib/active_record/relation/delegation.rb:46:in `each'
# /Users/jonahgeorge/.gem/ruby/2.3.0/gems/activerecord-4.2.0/lib/active_record/relation/delegation.rb:46:in `each'
# ./lib/integer_linear_program.rb:76:in `fte_contraint'
# ./lib/integer_linear_program.rb:16:in `initialize'
# ./spec/lib/integer_linear_program_spec.rb:74:in `new'
# ./spec/lib/integer_linear_program_spec.rb:74:in `block (2 levels) in <top (required)>'
def fte_contraint
  @students.each do |student|
    variables = @sections.map { |section| VAR_b(student.id, section.id) }
    constraint = (variables.sum <= (student.fte / @fte_per_section))
    @problem[ constraint ]
  end
end

What I Think Is Happening
When the length of @sections is 1, the resulting length of variables is also 1. Because of this, the constraint turns into the form:

@problem[ VAR_b(int, int) <= int ]

Its my understanding that this library expects constraints with only one variable to be defined via the given[] method. I was able to get around this with the following code, but I'm curious if there's a way to achieve this using a standard constraint instead of given[].

def fte_contraint
  @students.each do |student|
    variables = @sections.map { |section| VAR_b(student.id, section.id) }
    constraint = (variables.sum <= (student.fte / @fte_per_section))
    if variables.length > 1
      @problem[ constraint ]
    else
      given[ constraint ]
    end
  end
end

Invalid parentheses expansion when minus sign is used

Let's have a simple optimization problem with integer variables - X2_i and X1_i.

# frozen_string_literal: true

require 'rulp'

ENV['SOLVER'] = 'cbc'
Rulp::log_level = Logger::DEBUG

problem = Rulp::Max(- X1_i - 2 * X2_i)
problem[
  X1_i + X2_i == 10,
  X1_i >= 0,
  X2_i >= 0
]

problem.solve

It may be rewritten as minimization, of course. But it's not the case of the issue.

Optimal solution is achieved for X1_i = 10 and X2_i = 0.

When adding additional parentheses into my objective, I'm getting invalid results:

# Before
problem = Rulp::Max(- X1_i - 2 * X2_i)

# After
problem = Rulp::Max(- (X1_i + 2 * X2_i))

It ends up with X1_i = 0 and X2_i = 10 as a solution.

I found out that the new objective is improperly converted to LP format. Rulp generates the following LP model:

Maximize
 obj:  - X1 + 2 X2
Subject to
 c0: X1 + X2 = 10

Bounds
 0 <= X1
 0 <= X2
General
 X1 X2
End

When I use minus sign before the parenthesed expression, it reverses the first component's sign only. Shouldn't it reverse the signs of all the parenthesed components?

rulp gem version is not latest

gemserver is showing 0.44 from 2017. the latest changes from master (2021) are not released. Is there a disconnect?

image

Would it be possible to push latest version to gemserver?

I found as I couldn't use 0.44 with Rails and noticed there is a fix (#5) to use ENV already in place for custom suffix.

No such file or directory @ rb_sysopen - /tmp/rulp-812.sol (Errno::ENOENT)

I haven't gotten a chance to look further into this but I'll be digging into it over the weekend.

Figured I would just start a conversation to see if you already knew anything about this. There appears to be an interesting edge case where calling #solve fails due to the .sol file not being found. Unfortunately, I have not been able to reduce this to a reproducible test. My initial thoughts are to the two separate definitions of #get_output_filename in Problem and Solver.

Below is the full dump if it helps.

/Users/jonahgeorge/.gem/ruby/2.3.0/bundler/gems/rulp-89f4502d8c28/lib/solvers/cbc.rb:21:in `read': No such file or directory @ rb_sysopen - /tmp/rulp-736.sol (Errno::ENOENT)
    from /Users/jonahgeorge/.gem/ruby/2.3.0/bundler/gems/rulp-89f4502d8c28/lib/solvers/cbc.rb:21:in `store_results'
    from /Users/jonahgeorge/.gem/ruby/2.3.0/bundler/gems/rulp-89f4502d8c28/lib/rulp/rulp.rb:183:in `solve_with'
    from /Users/jonahgeorge/.gem/ruby/2.3.0/bundler/gems/rulp-89f4502d8c28/lib/rulp/rulp.rb:46:in `Cbc'
    from /Users/jonahgeorge/.gem/ruby/2.3.0/bundler/gems/rulp-89f4502d8c28/lib/rulp/rulp.rb:122:in `call'
    from /Users/jonahgeorge/.gem/ruby/2.3.0/bundler/gems/rulp-89f4502d8c28/lib/rulp/rulp.rb:113:in `method_missing'
    from /Users/jonahgeorge/Workspace/gta-assignment/lib/integer_linear_program.rb:29:in `solve'
    from lib/spring_ilp.rb:28:in `run'
    from lib/spring_ilp.rb:390:in `<top (required)>'
    from /Users/jonahgeorge/.gem/ruby/2.3.0/gems/railties-4.2.0/lib/rails/commands/runner.rb:60:in `load'
    from /Users/jonahgeorge/.gem/ruby/2.3.0/gems/railties-4.2.0/lib/rails/commands/runner.rb:60:in `<top (required)>'
    from /Users/jonahgeorge/.gem/ruby/2.3.0/gems/railties-4.2.0/lib/rails/commands/commands_tasks.rb:123:in `require'
    from /Users/jonahgeorge/.gem/ruby/2.3.0/gems/railties-4.2.0/lib/rails/commands/commands_tasks.rb:123:in `require_command!'
    from /Users/jonahgeorge/.gem/ruby/2.3.0/gems/railties-4.2.0/lib/rails/commands/commands_tasks.rb:90:in `runner'
    from /Users/jonahgeorge/.gem/ruby/2.3.0/gems/railties-4.2.0/lib/rails/commands/commands_tasks.rb:39:in `run_command!'
    from /Users/jonahgeorge/.gem/ruby/2.3.0/gems/railties-4.2.0/lib/rails/commands.rb:17:in `<top (required)>'
    from bin/rails:4:in `require'
    from bin/rails:4:in `<main>'

[Question] Constructing complex constraints

tl;dr I'm curious if there is an undocumented way to better deal with constructing constraints, or if this is an area you would be interested in making the API more flexible.

This question is primarily about constructing constraints but it can also be applied to objective functions. I'm currently in the process of translating a Pulp LP into Rulp to be embedded into a Rails application. In Pulp there is the ability to define a variable as None and slowly build the constraint via loops:

def _students_per_course_constraint(self):
  '''
  This constraint restricts the number of TAs per course. 
  It uses the current_enrollment numbers of the section to determine
  the number of TAs to assign.
  '''
  for section in SECTIONS:
    c = None
    for student in STUDENTS:
      c += self.variables[student["id"]][section["id"]]
    self.problem += (c <= (section["current_enrollment"] // STUDENTS_PER_TEACHING_ASSISTANT))

Naively translating this to Ruby you might end up with the code below; however, this results in method_missing errors when adding Rulp::LVs to the nil constraint.

def students_per_course_contraint
  SECTIONS.each do |section|
    constraint = nil
    STUDENTS.each do |student|
      constraint += VAR_b(student["id"], section["id"]) 
    end
    @problem[ constraint <= (section["current_enrollment"] / STUDENTS_PER_TA) ]
  end
end

As far as I can tell there is no way to do this in Rulp and instead you must construct a complex constraint directly in the @problem[] call. For obvious reasons, this code is not nearly as clear about its intentions.

def students_per_course_contraint
  SECTIONS.each do |section|
    @problem[ STUDENTS.map { |student| VAR_b(student["id"], section["id"]) }.inject(:+) <= (section["current_enrollment"] / STUDENTS_PER_TA) ]
  end
end

Working examples of these snippets can be found here.

Rails & Rulp Integration error

I'm working with rails and I'm trying to build a RESTful API which solved LP problems using rulp

When I try to define my Api module I can't because rulp defined it as a variable of a LP problem. Based on it finished on i. Something like this:

module Api
  module v1
    class LinearProgramingSolversController
      ...
    end
  end
end

I think, we can rewrite the suffixes as lpi, lpf and lpb or just use _i, _b y _f. So, it'll be less problematic to work in a general context with other gems and applications.

Or even better, the user can define which suffixes use

What do you think?

Array extensions interfere with rails Enumerable#sum

The use of sum when require 'rulp' is being bolted on to Array and causes issues when blocks are given

https://apidock.com/rails/v6.1.3.1/Enumerable/sum

# File activesupport/lib/active_support/core_ext/enumerable.rb, line 37
  def sum(identity = nil, &block)
    if identity
      _original_sum_with_required_identity(identity, &block)
    elsif block_given?
      map(&block).sum(identity)
    else
      inject(:+) || 0
    end
  end

For Example:

Failure/Error: some_has_many_association.sum(&:some_field)
     
     ArgumentError:
       wrong number of arguments (given 1, expected 0)

I am not sure if you are open to pull requests. I'd be interested to hear which direction you would like to go, either deprecating the use of sum, or using a array like class, instead of array with a custom defined sum method. I'd be happy to write a PR if you could offer some direction on how you would like the gem to move forward.

It is worth noting, that newer versions of ruby natively support the sum method since 2.4.6:

https://apidock.com/ruby/v2_6_3/Array/sum

So we might simply need to remove the custom sum method ( this would change supported versions in the gemspec).

"No such file or directory",

So I am trying to find a set of combinations of foods that are cheap and meet some nutrition requirements for different types of diets (like keto for example).

I have a set of food nutritional info:

foods = {
  Lettuce: {
    serving: 100,
    carbs: 2.9,
    protein: 1.4,
    fat: 0.2,
    fiber: 1.3,
    sugar: 0.8,
    sodium: 28,
    potassium: 194,
    package: 360,
    cost: 1.0,
  },
  Tomato: {
    serving: 100,
    carbs: 3.9,
    protein: 0.9,
    fat: 0.2,
    fiber: 1.2,
    sugar: 2.6,
    sodium: 5,
    potassium: 237,
    package: 1000,
    cost: 3.0,
  },
  ...
}

I add some extra fields:

foods.each do |key, food|
  ['carbs', 'protein', 'fat', 'potassium', 'sodium'].each do |sym|
    food["#{sym}_per_unit".to_sym] = (food[sym.to_sym]/food[:serving]).round(3)
  end

  food[:carbs_cal] = (food[:carbs] * 4).round(3)
  food[:protein_cal] = (food[:protein] * 4).round(3)
  food[:fat_cal] = (food[:fat] * 9).round(3)

  food[:carbs_cal_per_unit] = (food[:carbs_cal]/food[:serving]).round(3)
  food[:protein_cal_per_unit] = (food[:protein]/food[:serving]).round(3)
  food[:fat_cal_per_unit] = (food[:fat]/food[:serving]).round(3)

  food[:net_carbs] = (food[:carbs] - food[:fiber]).round(2)
  food[:cost_per_unit] = (food[:cost]/food[:package]).round(3)

end

And I have these expressions:

expressions = foods.keys.map(&C_i)
objective = expressions.map {|i| get_food(i)[:cost_per_unit] * i}.inject(:+)

carbs_cal = expressions.map {|i| get_food(i)[:carbs_cal_per_unit] * i}.inject(:+)
protein_cal = expressions.map {|i| get_food(i)[:protein_cal_per_unit] * i}.inject(:+)
fat_cal = expressions.map {|i| get_food(i)[:fat_cal_per_unit] * i}.inject(:+)
total_cal = carbs_cal + protein_cal + fat_cal

total_sodium = expressions.map {|i| get_food(i)[:sodium_per_unit] * i}.inject(:+)
total_potassium = expressions.map {|i| get_food(i)[:potassium_per_unit] * i}.inject(:+)

Rulp::Min(objective) [
  carbs_cal <= 80,
  protein_cal <= 200,
  fat_cal >= 1120,
 
  # carbs_cal * 0.95 - protein_cal * 0.05 - fat_cal * 0.05 <= 0,

  # carbs_cal + protein_cal + fat_cal >= 1500,

  total_potassium >= 2000,
].glpk

objective.evaluate

So this works, but If I use something like this:

Rulp::Min(objective) [
  carbs_cal + protein_cal + fat_cal >= 1500,
  total_potassium >= 2000,
].glpk

I get an error: "No such file or directory @ rb_sysopen - /tmp/rulp-899.sol". From the log info I have the lines:

D, [2019-03-06T13:39:08.937336 #31024] DEBUG -- :  --lp /tmp/rulp-499.sol --cuts --output /tmp/rulp-899.sol

D, [2019-03-06T13:39:08.937351 #31024] DEBUG -- : Reading problem data from '/tmp/rulp-499.sol'...

D, [2019-03-06T13:39:08.937368 #31024] DEBUG -- : /tmp/rulp-499.sol:5: multiple use of variable 'CLettuce' not allowed

Looking at the source code, I can see that 2 different /tmp/rulp-xxx.sol files are created, but like I said, if I use different expressions I get no errors. I tried adding these to lib/solvers/glpk.rb at the beginning of the store_results method:

`touch #{@filename}`
`touch #{@outfile}`

but I get another error "lib/extensions/kernel_extensions.rb:35:in 'method_missing': undefined method 'split' for nil:NilClass (NoMethodError)".

If I use these:

Rulp::Min(objective) [
  carbs_cal * 0.95 - protein_cal * 0.05 - fat_cal * 0.05 <= 0,
  total_potassium >= 2000,
].glpk

I get another error "lib/extensions/kernel_extensions.rb:35:in 'method_missing': undefined method '*' for #Expressions:0x00005652feec0070"
This time, the Log doesn't show anything, so I think GLPK isn't even initialized, which means I am probably using wrong syntax.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.