# ParamILS wrapper for gurobi solver

def float_regexp()
        return '[+-]?\d+(?:\.\d+)?(?:[eE][+-]\d+)?';
end

#puts "Inside call to gurobi_wrapper.rb ..."

# Deal with inputs.
if ARGV.length < 5
    puts "gurobi.rb is a wrapper for gurobi."
    puts "Usage: ruby gurobi.rb <instance_relname> <instance_specifics (string in \"quotes\"> <cutoff_time> <cutoff_length> <seed> <params to be passed on>."
    exit -1
end

input_file = ARGV[0]
#=== Here instance_specifics are not used - but you can put any information into this string you wish ...
instance_specifics = ARGV[1]
timeout = ARGV[2].to_f
cutoff_length = ARGV[3].to_i
seed = ARGV[4].to_i

#=== Go through all the parameters, and transform them to the appropriate gurobi input command.
params = ARGV[5...ARGV.length]
paramstring = ""
i=0
cuts = ["-CliqueCuts", "-CoverCuts", "-FlowCoverCuts", "-FlowPathCuts", "-GUBCoverCuts", "-ImpliedCuts", "-MIPSepCuts", "-MIRCuts", "-ZeroHalfCuts"]
unless params[0] == "-param_string" and params[1] == "default-params"
	while i<params.length
		unless (cuts.include?(params[i]) and params[i+1] == "-2")
			puts "#{params[i]} #{params[i+1]}"
			paramstring = paramstring + params[i].sub(/^-/, "") + "=" + params[i+1] + " "
		end
		i = i+2
	end
end
p paramstring

# Build algorithm command and execute it.
#cmd = "gurobi_cl #{paramstring} TimeLimit=#{timeout.ceil} Threads=1 MIPGap=0" 
machine_name = File.popen("hostname"){|file| $work_dir = file.gets.chomp}.sub(/.cs.ubc.ca/,"").sub(/.cs.ubc.ca/,"")
license_file = "/.autofs/csother/ubccsprojectarrow/hutter/mip_configuration/gurobi/gurobi201/gurobi_#{machine_name}.lic"

#cmd = "setenv GRB_LICENSE_FILE #{license_file}; gurobi_cl #{paramstring} TimeLimit=#{timeout} Threads=1 MIPGap=0" 
#cmd = "gurobi_cl #{paramstring} TimeLimit=#{timeout} Threads=1 MIPGap=0" 
allowed_mipgap = 0.0001
cmd = "gurobi_cl #{paramstring} TimeLimit=#{timeout} Threads=1 MIPGap=#{allowed_mipgap}"  # leaving the (relative) MIPGap at its default of 0.01%, same as for CPLEX


path, filename = File.split input_file

if input_file =~ /\.lp$/
	unless input_file =~ /MIP_data\/LP/
		input_file = path + "/FIXED-" + filename.sub(/.lp/, ".mps")
		unless File.file?(input_file)
			input_file = input_file = path + "/CATSmps-" + filename.sub(/.lp/, ".mps")
		end
	end
end
cmd += " #{input_file} "


tmp_file = "gurobi_out#{rand}.txt"
exec_cmd = "#{cmd} > #{tmp_file}"

STDERR.puts "Calling: #{exec_cmd}"
system exec_cmd

#=== Parse algorithm output to extract relevant information for ParamILS.
solved = nil
runtime = nil
best_sol = 1e6 # don't use anything higher to avoid numerical problems when breaking ties based on runtimes to solve other instances.
obj = 0

solved = "CRASHED"
runtime = timeout + 0.001
optimality_flag = false
slack_in_my_assertions = 1.0 # multiplicative, so 1.0 is no slack.

timed_out = false
File.open(tmp_file){|file|
	while line = file.gets
		if line =~ /Unknown parameter: /
			raise line
		end
		
		if line =~ /in (#{float_regexp}) seconds/  # different strings ending in this: Smth. like: Explored ... nodes , Solved in ... nodes and in ..., etc
			runtime = $1.to_f
		end
		
#		if line =~ /Best objective (#{float_regexp}), best bound (#{float_regexp}), gap (#{float_regexp})%/

		if line =~ /Optimal solution found/
			optimality_flag  = true
		end
		
		if line =~ /Best objective -, best bound.*, gap (#{float_regexp})%/
			best_sol = $1.to_f/100
			if best_sol <= slack_in_my_assertions * allowed_mipgap
				raise "No objective fct. value, but gap is small enough? Parsing error."
				#solved = "SAT"
			else 
				solved = "TIMEOUT" 
			end
		end

		if line =~ /Best objective (#{float_regexp}), best bound.*, gap (#{float_regexp})%/
			obj = $1.to_f
			best_sol = $2.to_f/100
			if best_sol <= slack_in_my_assertions * allowed_mipgap
				solved = "SAT"

				#=== Check correctness.
				unless instance_specifics == "0" || instance_specifics == "instance_specific" # for backwards compatibility with my previous runs
					maxi = [obj.abs, instance_specifics.to_f.abs].max
					if (obj.abs - instance_specifics.to_f.abs).abs/maxi > slack_in_my_assertions * allowed_mipgap  && (obj.abs - instance_specifics.to_f.abs).abs > 1e-8
						solved = "WRONG ANSWER" 
						#raise "Gurobi claims to have solved the instance, but its result (#{obj.abs}) differs from the actual one (#{instance_specifics.to_f.abs}) by more than a relative error of 0.01%."
					end
				end
			else 
				if optimality_flag
					solved = "WRONG ANSWER" 
					#raise "Gurobi claims optimality even though the MIP gap is bigger than allowed."
				else
					solved = "TIMEOUT" 
				end
			end
		end
		
		if line =~/^Model is infeasible$/
			solved = "UNSAT"
			solved = "WRONG ANSWER" # we KNOW that all input models are feasible.
			#raise "Params lead to numerical issues: gurobi thinks the instance is infeasible"
		end
		
		if line =~ /Current MIP best bound is infinite./ 
			solved = "TIMEOUT"
			if line = file.gets
				if line =~/Solution time =  #{float_regexp} sec\./
					runtime = timeout + 0.01 # override that output, it's not accurate.
				end
			end
		end
		
		if line =~ /Best objective.*best bound.*gap -/
			solved = "TIMEOUT" 
		end
		
		if line =~ /Time limit exceeded/
			solved = "TIMEOUT"
		end

		if line =~ /Solve interrupted/
			solved ="CRASHED"
			break
		end
		
		if line =~/Best objective (#{float_regexp}), best bound -, gap -/
			solved = "TIMEOUT" 
		end
		
		if line =~ /Optimal objective  #{float_regexp}/
			solved = "SAT"			
		end
	end
}
#raise "did not parse runtime correctly in gurobi_wrapper.rb: solved but time>timeout"  if (solved == "SAT" or solved =="UNSAT") and runtime > timeout
if (solved == "SAT" or solved =="UNSAT") and runtime > timeout
	solved = "TIMEOUT"
end
File.delete(tmp_file)
puts "Result for ParamILS: #{solved}, #{runtime}, #{obj }, #{best_sol}, #{seed}"  # mis-using the runlength field to store objective reached (best_sol is MIP gap here).