From 4ef702fddef52723228aa543ac01413883a97642 Mon Sep 17 00:00:00 2001 From: Igor Machado Date: Tue, 19 Dec 2023 01:21:15 +0000 Subject: [PATCH] example of irace for optframe on linux --- demo/05_QuickstartTSP_IRACE/.gitignore | 5 + .../InstancesTest/tsp-example.txt | 6 + .../InstancesTraining/tsp-example.txt | 6 + demo/05_QuickstartTSP_IRACE/README.md | 15 ++ demo/05_QuickstartTSP_IRACE/TSP-fcore.py | 158 ++++++++++++ .../configurations.txt.tmpl | 15 ++ .../05_QuickstartTSP_IRACE/forbidden.txt.tmpl | 14 + .../instances-list.txt.tmpl | 12 + .../mainTSP-fcore-ils.py | 244 ++++++++++++++++++ demo/05_QuickstartTSP_IRACE/makefile | 12 + demo/05_QuickstartTSP_IRACE/parameters.txt | 32 +++ .../parameters.txt.tmpl | 34 +++ demo/05_QuickstartTSP_IRACE/run_irace.r | 6 + demo/05_QuickstartTSP_IRACE/scenario.txt | 234 +++++++++++++++++ demo/05_QuickstartTSP_IRACE/scenario.txt.tmpl | 234 +++++++++++++++++ .../target-evaluator.tmpl | 58 +++++ demo/05_QuickstartTSP_IRACE/target-runner.sh | 80 ++++++ .../05_QuickstartTSP_IRACE/target-runner.tmpl | 80 ++++++ 18 files changed, 1245 insertions(+) create mode 100644 demo/05_QuickstartTSP_IRACE/.gitignore create mode 100644 demo/05_QuickstartTSP_IRACE/InstancesTest/tsp-example.txt create mode 100644 demo/05_QuickstartTSP_IRACE/InstancesTraining/tsp-example.txt create mode 100644 demo/05_QuickstartTSP_IRACE/README.md create mode 100644 demo/05_QuickstartTSP_IRACE/TSP-fcore.py create mode 100644 demo/05_QuickstartTSP_IRACE/configurations.txt.tmpl create mode 100644 demo/05_QuickstartTSP_IRACE/forbidden.txt.tmpl create mode 100644 demo/05_QuickstartTSP_IRACE/instances-list.txt.tmpl create mode 100644 demo/05_QuickstartTSP_IRACE/mainTSP-fcore-ils.py create mode 100644 demo/05_QuickstartTSP_IRACE/makefile create mode 100644 demo/05_QuickstartTSP_IRACE/parameters.txt create mode 100644 demo/05_QuickstartTSP_IRACE/parameters.txt.tmpl create mode 100644 demo/05_QuickstartTSP_IRACE/run_irace.r create mode 100644 demo/05_QuickstartTSP_IRACE/scenario.txt create mode 100644 demo/05_QuickstartTSP_IRACE/scenario.txt.tmpl create mode 100755 demo/05_QuickstartTSP_IRACE/target-evaluator.tmpl create mode 100755 demo/05_QuickstartTSP_IRACE/target-runner.sh create mode 100755 demo/05_QuickstartTSP_IRACE/target-runner.tmpl diff --git a/demo/05_QuickstartTSP_IRACE/.gitignore b/demo/05_QuickstartTSP_IRACE/.gitignore new file mode 100644 index 0000000..bd18a51 --- /dev/null +++ b/demo/05_QuickstartTSP_IRACE/.gitignore @@ -0,0 +1,5 @@ +demo_* +*.stdout +*.stderr +*.Rdata + diff --git a/demo/05_QuickstartTSP_IRACE/InstancesTest/tsp-example.txt b/demo/05_QuickstartTSP_IRACE/InstancesTest/tsp-example.txt new file mode 100644 index 0000000..7b82ebf --- /dev/null +++ b/demo/05_QuickstartTSP_IRACE/InstancesTest/tsp-example.txt @@ -0,0 +1,6 @@ +5 +1 10 10 +2 20 20 +3 30 30 +4 40 40 +5 50 50 diff --git a/demo/05_QuickstartTSP_IRACE/InstancesTraining/tsp-example.txt b/demo/05_QuickstartTSP_IRACE/InstancesTraining/tsp-example.txt new file mode 100644 index 0000000..7b82ebf --- /dev/null +++ b/demo/05_QuickstartTSP_IRACE/InstancesTraining/tsp-example.txt @@ -0,0 +1,6 @@ +5 +1 10 10 +2 20 20 +3 30 30 +4 40 40 +5 50 50 diff --git a/demo/05_QuickstartTSP_IRACE/README.md b/demo/05_QuickstartTSP_IRACE/README.md new file mode 100644 index 0000000..c2d6f58 --- /dev/null +++ b/demo/05_QuickstartTSP_IRACE/README.md @@ -0,0 +1,15 @@ +## 05_QuickstartTSP_IRACE + +This example demonstrates how to integrate irace with optframe python (in linux). + +Just run: + +``` +make vendor +make run +``` + + +### Acknowledgements + +Thanks Henrique @henriqueborgesv for the advices on irace for optframe. diff --git a/demo/05_QuickstartTSP_IRACE/TSP-fcore.py b/demo/05_QuickstartTSP_IRACE/TSP-fcore.py new file mode 100644 index 0000000..f19863e --- /dev/null +++ b/demo/05_QuickstartTSP_IRACE/TSP-fcore.py @@ -0,0 +1,158 @@ +# OptFrame Python Demo TSP - Traveling Salesman Problem + +from typing import List +import random + +from optframe import * +from optframe.protocols import * + + +class SolutionTSP(object): + def __init__(self): + # number of cities in solution + self.n : int = 0 + # visited cities as a list + self.cities : List[int] = [] + + # MUST provide some printing mechanism + def __str__(self): + return f"SolutionTSP(n={self.n};cities={self.cities})" + +class ProblemContextTSP(object): + def __init__(self): + # float engine for OptFrame + self.engine = Engine(APILevel.API1d) + # number of cities + self.n = 0 + # x coordinates + self.vx = [] + # y coordinates + self.vy = [] + # distance matrix + self.dist = [] + + # Example: "3\n1 10 10\n2 20 20\n3 30 30\n" + + def load(self, filename: str): + with open(filename, 'r') as f: + lines = f.readlines() + self.n = int(lines[0]) + for i in range(self.n): + id_x_y = lines[i+1].split() + # ignore id_x_y[0] + self.vx.append(int(id_x_y[1])) + self.vy.append(int(id_x_y[2])) + # + self.dist = [[0 for col in range(self.n)] for row in range(self.n)] + for i in range(self.n): + for j in range(self.n): + self.dist[i][j] = round(self.euclidean(self.vx[i], self.vy[i], self.vx[j], self.vy[j])) + + def euclidean(self, x1, y1, x2, y2): + import math + return math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) + + def __str__(self): + return f"ProblemContextTSP(n={self.n};vx={self.vx};vy={self.vy};dist={self.dist})" +# continuation of ProblemContextTSP class... + @staticmethod + def minimize(pTSP: 'ProblemContextTSP', s: SolutionTSP) -> float: + assert (s.n == pTSP.n) + assert (len(s.cities) == s.n) + # remember this is an API1d method + f = 0.0 + for i in range(pTSP.n-1): + f += pTSP.dist[s.cities[i]][s.cities[i + 1]]; + f += pTSP.dist[s.cities[int(pTSP.n) - 1]][s.cities[0]]; + return f +# continuation of ProblemContextTSP class... + @staticmethod + def generateSolution(problemCtx: 'ProblemContextTSP') -> SolutionTSP: + sol = SolutionTSP() + for i in range(problemCtx.n): + sol.cities.append(i) + random.shuffle(sol.cities) + sol.n = problemCtx.n + return sol + +# optional tests... +assert isinstance(SolutionTSP, XSolution) # composition tests +assert isinstance(ProblemContextTSP, XProblem) # composition tests +assert isinstance(ProblemContextTSP, XConstructive) # composition tests +assert isinstance(ProblemContextTSP, XMinimize) # composition tests + +from optframe.components import Move + +class MoveSwapClass(Move): + def __init__(self, _i: int = 0, _j: int = 0): + self.i = _i + self.j = _j + def __str__(self): + return "MoveSwapClass(i="+str(self.i)+";j="+str(self.j)+")" + def apply(self, problemCtx, sol: SolutionTSP) -> 'MoveSwapClass': + aux = sol.cities[self.j] + sol.cities[self.j] = sol.cities[self.i] + sol.cities[self.i] = aux + # must create reverse move (j,i) + return MoveSwapClass(self.j, self.i) + def canBeApplied(self, problemCtx, sol: SolutionTSP) -> bool: + return True + def eq(self, problemCtx, m2: 'MoveSwapClass') -> bool: + return (self.i == m2.i) and (self.j == m2.j) + +assert isinstance(MoveSwapClass, XMove) # composition tests +assert MoveSwapClass in Move.__subclasses__() # classmethod style + +#from optframe.components import NS + +class NSSwap(object): + @staticmethod + def randomMove(pTSP, sol: SolutionTSP) -> MoveSwapClass: + import random + n = sol.n + i = random.randint(0, n - 1) + j = i + while j <= i: + i = random.randint(0, n - 1) + j = random.randint(0, n - 1) + # return MoveSwap(i, j) + return MoveSwapClass(i, j) + +#assert NSSwap in NS.__subclasses__() # optional test + +#from optframe.components import NSSeq +from optframe.components import NSIterator + +# For NSSeq, one must provide a Move Iterator +# A Move Iterator has five actions: Init, First, Next, IsDone and Current + +class IteratorSwap(NSIterator): + def __init__(self, _i: int, _j: int): + self.i = _i + self.j = _j + def first(self, pTSP: ProblemContextTSP): + self.i = 0 + self.j = 1 + def next(self, pTSP: ProblemContextTSP): + if self.j < pTSP.n - 1: + self.j = self.j+1 + else: + self.i = self.i + 1 + self.j = self.i + 1 + def isDone(self, pTSP: ProblemContextTSP): + return self.i >= pTSP.n - 1 + def current(self, pTSP: ProblemContextTSP): + return MoveSwapClass(self.i, self.j) + +assert IteratorSwap in NSIterator.__subclasses__() # optional test + +class NSSeqSwap(object): + @staticmethod + def randomMove(pTSP: ProblemContextTSP, sol: SolutionTSP) -> MoveSwapClass: + return NSSwap.randomMove(pTSP, sol) # composition + + @staticmethod + def getIterator(pTSP: ProblemContextTSP, sol: SolutionTSP) -> IteratorSwap: + return IteratorSwap(-1, -1) + +#assert NSSeqSwap in NSSeq.__subclasses__() # optional test diff --git a/demo/05_QuickstartTSP_IRACE/configurations.txt.tmpl b/demo/05_QuickstartTSP_IRACE/configurations.txt.tmpl new file mode 100644 index 0000000..01b3ff3 --- /dev/null +++ b/demo/05_QuickstartTSP_IRACE/configurations.txt.tmpl @@ -0,0 +1,15 @@ +## Template for specifying initial parameter configurations, for +## example, the default configuration, in irace. +## +## This filename must be specified via the --candidates-file command-line option +## (or candidatesFile in scenario.txt). +## +## The format is one parameter configuration per line, and one parameter per +## column. The first line gives the parameter name corresponding to +## each column (names must match those given in the parameters +## file). Each configuration must satisfy the +## parameter conditions (NA should be used for those parameters that +## are not enabled for a given configuration) and, if given, the +## constraints that describe forbidden configurations. +param1 param2 mode real mutation +5 NA "x2" 2.0 "low" diff --git a/demo/05_QuickstartTSP_IRACE/forbidden.txt.tmpl b/demo/05_QuickstartTSP_IRACE/forbidden.txt.tmpl new file mode 100644 index 0000000..4ae8070 --- /dev/null +++ b/demo/05_QuickstartTSP_IRACE/forbidden.txt.tmpl @@ -0,0 +1,14 @@ +## Template for specifying forbidden parameter configurations in irace. +## +## This filename must be specified via the --forbidden-file command-line option +## (or forbiddenFile in scenario.txt). +## +## The format is one constraint per line. Each constraint is a logical +## expression (in R syntax). If a parameter configuration +## is generated that makes the logical expression evaluate to TRUE, +## then the configuration is discarded. +## +## Examples of valid logical operators are: == != >= <= > < & | ! %in% +param1 < 5 & mode == "x1" +(param2 > 6 & mode == "x1") | (param2 <= 6 & mode == "x2") +real < 4 & mode %in% c("x2", "x3") diff --git a/demo/05_QuickstartTSP_IRACE/instances-list.txt.tmpl b/demo/05_QuickstartTSP_IRACE/instances-list.txt.tmpl new file mode 100644 index 0000000..70b89b5 --- /dev/null +++ b/demo/05_QuickstartTSP_IRACE/instances-list.txt.tmpl @@ -0,0 +1,12 @@ +## This is an example of specifying a list of instances. + +# Each line is an instance relative to trainInstancesDir/testInstancesdir +# unless set to "" (see scenario.txt.tmpl) and an optional sequence of +# instance-specific parameters that will be passed to target-runner when +# invoked on that instance. + +# Empty lines and comments are ignored. + +100/100-1_100-2.tsp --time 1 +100/100-1_100-3.tsp --time 2 +100/100-1_100-4.tsp --time 3 diff --git a/demo/05_QuickstartTSP_IRACE/mainTSP-fcore-ils.py b/demo/05_QuickstartTSP_IRACE/mainTSP-fcore-ils.py new file mode 100644 index 0000000..78a7f8f --- /dev/null +++ b/demo/05_QuickstartTSP_IRACE/mainTSP-fcore-ils.py @@ -0,0 +1,244 @@ +# OptFrame Python Demo TSP - Traveling Salesman Problem + +from typing import List +import random + +from optframe import * +from optframe.protocols import * + + +class SolutionTSP(object): + def __init__(self): + # number of cities in solution + self.n : int = 0 + # visited cities as a list + self.cities : List[int] = [] + + # MUST provide some printing mechanism + def __str__(self): + return f"SolutionTSP(n={self.n};cities={self.cities})" + +class ProblemContextTSP(object): + def __init__(self): + # float engine for OptFrame + self.engine = Engine(APILevel.API1d) + # number of cities + self.n = 0 + # x coordinates + self.vx = [] + # y coordinates + self.vy = [] + # distance matrix + self.dist = [] + + # Example: "3\n1 10 10\n2 20 20\n3 30 30\n" + + def load(self, filename: str): + with open(filename, 'r') as f: + lines = f.readlines() + self.n = int(lines[0]) + for i in range(self.n): + id_x_y = lines[i+1].split() + # ignore id_x_y[0] + self.vx.append(int(id_x_y[1])) + self.vy.append(int(id_x_y[2])) + # + self.dist = [[0 for col in range(self.n)] for row in range(self.n)] + for i in range(self.n): + for j in range(self.n): + self.dist[i][j] = round(self.euclidean(self.vx[i], self.vy[i], self.vx[j], self.vy[j])) + + def euclidean(self, x1, y1, x2, y2): + import math + return math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) + + def __str__(self): + return f"ProblemContextTSP(n={self.n};vx={self.vx};vy={self.vy};dist={self.dist})" +# continuation of ProblemContextTSP class... + @staticmethod + def minimize(pTSP: 'ProblemContextTSP', s: SolutionTSP) -> float: + assert (s.n == pTSP.n) + assert (len(s.cities) == s.n) + # remember this is an API1d method + f = 0.0 + for i in range(pTSP.n-1): + f += pTSP.dist[s.cities[i]][s.cities[i + 1]]; + f += pTSP.dist[s.cities[int(pTSP.n) - 1]][s.cities[0]]; + return f +# continuation of ProblemContextTSP class... + @staticmethod + def generateSolution(problemCtx: 'ProblemContextTSP') -> SolutionTSP: + sol = SolutionTSP() + for i in range(problemCtx.n): + sol.cities.append(i) + random.shuffle(sol.cities) + sol.n = problemCtx.n + return sol + +# optional tests... +assert isinstance(SolutionTSP, XSolution) # composition tests +assert isinstance(ProblemContextTSP, XProblem) # composition tests +assert isinstance(ProblemContextTSP, XConstructive) # composition tests +assert isinstance(ProblemContextTSP, XMinimize) # composition tests + +from optframe.components import Move + +class MoveSwapClass(Move): + def __init__(self, _i: int = 0, _j: int = 0): + self.i = _i + self.j = _j + def __str__(self): + return "MoveSwapClass(i="+str(self.i)+";j="+str(self.j)+")" + def apply(self, problemCtx, sol: SolutionTSP) -> 'MoveSwapClass': + aux = sol.cities[self.j] + sol.cities[self.j] = sol.cities[self.i] + sol.cities[self.i] = aux + # must create reverse move (j,i) + return MoveSwapClass(self.j, self.i) + def canBeApplied(self, problemCtx, sol: SolutionTSP) -> bool: + return True + def eq(self, problemCtx, m2: 'MoveSwapClass') -> bool: + return (self.i == m2.i) and (self.j == m2.j) + +assert isinstance(MoveSwapClass, XMove) # composition tests +assert MoveSwapClass in Move.__subclasses__() # classmethod style + +#from optframe.components import NS + +class NSSwap(object): + @staticmethod + def randomMove(pTSP, sol: SolutionTSP) -> MoveSwapClass: + import random + n = sol.n + i = random.randint(0, n - 1) + j = i + while j <= i: + i = random.randint(0, n - 1) + j = random.randint(0, n - 1) + # return MoveSwap(i, j) + return MoveSwapClass(i, j) + +#assert NSSwap in NS.__subclasses__() # optional test + +#from optframe.components import NSSeq +from optframe.components import NSIterator + +# For NSSeq, one must provide a Move Iterator +# A Move Iterator has five actions: Init, First, Next, IsDone and Current + +class IteratorSwap(NSIterator): + def __init__(self, _i: int, _j: int): + self.i = _i + self.j = _j + def first(self, pTSP: ProblemContextTSP): + self.i = 0 + self.j = 1 + def next(self, pTSP: ProblemContextTSP): + if self.j < pTSP.n - 1: + self.j = self.j+1 + else: + self.i = self.i + 1 + self.j = self.i + 1 + def isDone(self, pTSP: ProblemContextTSP): + return self.i >= pTSP.n - 1 + def current(self, pTSP: ProblemContextTSP): + return MoveSwapClass(self.i, self.j) + +assert IteratorSwap in NSIterator.__subclasses__() # optional test + +class NSSeqSwap(object): + @staticmethod + def randomMove(pTSP: ProblemContextTSP, sol: SolutionTSP) -> MoveSwapClass: + return NSSwap.randomMove(pTSP, sol) # composition + + @staticmethod + def getIterator(pTSP: ProblemContextTSP, sol: SolutionTSP) -> IteratorSwap: + return IteratorSwap(-1, -1) + +#assert NSSeqSwap in NSSeq.__subclasses__() # optional test +# =========================================== +# begins main() python script for TSP ILS/VNS +# =========================================== + +import sys + +# Adaptations for irace. +# Main() must receive params from irace. +#instancia = sys.argv[1] +instancia = "InstancesTraining/tsp-example.txt" +time = 3 +localSearch = 0 # TODO +iterMax = 10 +maxPert = 5 +for i in range(len(sys.argv)): + if (sys.argv[i] == "--time"): + time = int(sys.argv[i + 1]) + if (sys.argv[i] == "-i"): + instancia = str(sys.argv[i + 1]) + if (sys.argv[i] == "--seed"): + random.seed(int(sys.argv[i + 1])) + # begin CONFIG_PARAMS + if (sys.argv[i] == "--localSearch"): + localSearch = int(sys.argv[i + 1]) + if (sys.argv[i] == "--iterMax"): + iterMax = int(sys.argv[i + 1]) + if (sys.argv[i] == "--maxPert"): + maxPert = float(sys.argv[i + 1]) + +# import ILSLevels and BestImprovement +from optframe.heuristics import * + +# loads problem from filesystem +pTSP = ProblemContextTSP() + +# set SILENT +pTSP.engine.experimental_set_parameter("ENGINE_LOG_LEVEL", "0") +pTSP.engine.experimental_set_parameter("COMPONENT_LOG_LEVEL", "0") + +# load +pTSP.load(instancia) + +# Register Basic Components +comp_list = pTSP.engine.setup(pTSP) + +# get index of new NS +ns_idx = pTSP.engine.add_ns_class(pTSP, NSSwap) + +# get index of new NSSeq +nsseq_idx = pTSP.engine.add_nsseq_class(pTSP, NSSeqSwap) + + +# pack NS into a NS list +list_idx = pTSP.engine.create_component_list( + "[ OptFrame:NS 0 ]", "OptFrame:NS[]") +# print("list_idx=", list_idx) + +# print("Listing registered components:") +# pTSP.engine.list_components("OptFrame:") + +# list the required parameters for OptFrame ComponentBuilder +# print("engine will list builders for OptFrame: ") +# print(pTSP.engine.list_builders("OptFrame:")) +# print() + + +# TODO: add IF for FirstImprovement... +bi = BestImprovement(pTSP.engine, 0, 0) +ls_idx = bi.get_id() + +list_vnd_idx = pTSP.engine.create_component_list( + "[ OptFrame:LocalSearch 0 ]", "OptFrame:LocalSearch[]") + +vnd = VariableNeighborhoodDescent(pTSP.engine, 0, 0) +vnd_idx = vnd.get_id() + + +##### +#pTSP.engine.list_components("OptFrame:") + +ilsl_pert = ILSLevelPertLPlus2(pTSP.engine, 0, 0) +pert_idx = ilsl_pert.get_id() + +ilsl = ILSLevels(pTSP.engine, 0, 0, 1, 0, iterMax, maxPert) +lout = ilsl.search(time) +print(lout.best_e) diff --git a/demo/05_QuickstartTSP_IRACE/makefile b/demo/05_QuickstartTSP_IRACE/makefile new file mode 100644 index 0000000..f60a735 --- /dev/null +++ b/demo/05_QuickstartTSP_IRACE/makefile @@ -0,0 +1,12 @@ +all: + +run: + Rscript run_irace.r + +vendor: + sudo apt-get install -y r-base + Rscript -e "install.packages(c('irace'), repos='https://cran.rstudio.com')" + cp /usr/local/lib/R/site-library/irace/templates/*.tmpl . + +# templates must be on: /usr/local/lib/R/site-library/irace/templates + \ No newline at end of file diff --git a/demo/05_QuickstartTSP_IRACE/parameters.txt b/demo/05_QuickstartTSP_IRACE/parameters.txt new file mode 100644 index 0000000..fefd04d --- /dev/null +++ b/demo/05_QuickstartTSP_IRACE/parameters.txt @@ -0,0 +1,32 @@ +## Template for parameter description file for Iterated Race. +## +## The format is one parameter per line. Each line contains: +## +## 1: Name of the parameter. An unquoted alphanumeric string, +## example: ants + +## 2: Switch to pass the parameter. A quoted (possibly empty) string, +## if the value and the switch must be separated, add a space at +## the end of the string. Example : "--version1 --ants " + +## 3: Type. An unquoted single letter, among +## i: Integer, c: categorical, o: ordinal, r: real. + +## 4: For c and o: All possible values, that is, a variable number of +## quoted or unquoted strings separated by commas within +## parenthesis. Empty strings and strings containing commas or +## spaces must be quoted. +## For i,r: a pair of unquoted numbers representing minimum and +## maximum values. + +## 5: A conditional parameter can be defined according to the values of +## one or several other parameters. This is done by adding a +## character '|' followed by an R expression involving the names of +## other parameters. This expression must return TRUE if the +## condition is satisfied, FALSE otherwise. + +# 1: 2: 3: 4: 5: +iterMax "--iterMax " i (10, 20) +maxPert "--maxPert " i (3, 7) +localSearch "--localSearch " c (0) +#unused "-u " c (1, 2, 10, 20) diff --git a/demo/05_QuickstartTSP_IRACE/parameters.txt.tmpl b/demo/05_QuickstartTSP_IRACE/parameters.txt.tmpl new file mode 100644 index 0000000..c0734f6 --- /dev/null +++ b/demo/05_QuickstartTSP_IRACE/parameters.txt.tmpl @@ -0,0 +1,34 @@ +## Template for parameter description file for Iterated Race. +## +## The format is one parameter per line. Each line contains: +## +## 1: Name of the parameter. An unquoted alphanumeric string, +## example: ants + +## 2: Switch to pass the parameter. A quoted (possibly empty) string, +## if the value and the switch must be separated, add a space at +## the end of the string. Example : "--version1 --ants " + +## 3: Type. An unquoted single letter, among +## i: Integer, c: categorical, o: ordinal, r: real. + +## 4: For c and o: All possible values, that is, a variable number of +## quoted or unquoted strings separated by commas within +## parenthesis. Empty strings and strings containing commas or +## spaces must be quoted. +## For i,r: a pair of unquoted numbers representing minimum and +## maximum values. + +## 5: A conditional parameter can be defined according to the values of +## one or several other parameters. This is done by adding a +## character '|' followed by an R expression involving the names of +## other parameters. This expression must return TRUE if the +## condition is satisfied, FALSE otherwise. + +# 1: 2: 3: 4: 5: +param1 "--param1 " i (1, 10) | mode %in% c("x1", "x2") +param2 "--param2 " i (1, 10) | mode %in% c("x1", "x3") && real > 2.5 && real <= 3.5 +mode "--" c ("x1" ,"x2", "x3") +real "--paramreal=" r (1.5, 4.5) +mutation "--mutation=" o ("none", "very low", "low", "medium", "high", "very high", "all") +#unused "-u " c (1, 2, 10, 20) diff --git a/demo/05_QuickstartTSP_IRACE/run_irace.r b/demo/05_QuickstartTSP_IRACE/run_irace.r new file mode 100644 index 0000000..e29a51b --- /dev/null +++ b/demo/05_QuickstartTSP_IRACE/run_irace.r @@ -0,0 +1,6 @@ +library("irace") +# Go to the directory containing the scenario files +# setwd("./tuning") +scenario <- readScenario(filename = "scenario.txt", +scenario = defaultScenario()) +irace.main(scenario = scenario) \ No newline at end of file diff --git a/demo/05_QuickstartTSP_IRACE/scenario.txt b/demo/05_QuickstartTSP_IRACE/scenario.txt new file mode 100644 index 0000000..e26f4c0 --- /dev/null +++ b/demo/05_QuickstartTSP_IRACE/scenario.txt @@ -0,0 +1,234 @@ +###################################################### -*- mode: r -*- ##### +## Scenario setup for Iterated Race (irace). +############################################################################ + +## To use the default value of a parameter of iRace, simply do not set +## the parameter (comment it out in this file, and do not give any +## value on the command line). + +## Directory where the programs will be run. +execDir = "./" + +## File that contains the description of the parameters of the target +## algorithm. +parameterFile = "./parameters.txt" + +## File that contains a list of logical expressions that cannot be TRUE for +## any evaluated configuration. If empty or NULL, do not use forbidden +## expressions. +# forbiddenFile = "" + +## File that contains a table of initial configurations. If empty or NULL, all +## initial configurations are randomly generated. +# configurationsFile = "" + +## File to save tuning results as an R dataset, either absolute path or +## relative to execDir. +# logFile = "./irace.Rdata" + +## Directory where training instances are located; either absolute path or +## relative to current directory. If no trainInstancesFiles is provided, all +## the files in trainInstancesDir will be listed as instances. +trainInstancesDir = "./InstancesTraining" + +## File that contains a list of training instances and optionally additional +## parameters for them. If trainInstancesDir is provided, irace will search +## for the files in this folder. +# trainInstancesFile = "" + +## Randomly sample the training instances or use them in the order given. +# sampleInstances = 1 + +## Directory where testing instances are located, either absolute or relative +## to current directory. +testInstancesDir = "./InstancesTest" + +## File containing a list of test instances and optionally additional +## parameters for them. +# testInstancesFile = "" + +## Number of elite configurations returned by irace that will be tested if +## test instances are provided. +# testNbElites = 1 + +## Enable/disable testing the elite configurations found at each iteration. +# testIterationElites = 0 + +## Statistical test used for elimination. The default value selects t-test if +## capping is enabled or F-test, otherwise. Valid values are: F-test (Friedman +## test), t-test (pairwise t-tests with no correction), t-test-bonferroni +## (t-test with Bonferroni's correction for multiple comparisons), t-test-holm +## (t-test with Holm's correction for multiple comparisons). +# testType = "" + +## Number of instances evaluated before the first elimination test. It must be +## a multiple of eachTest. +# firstTest = 5 + +## Number of instances evaluated between elimination tests. +# eachTest = 1 + +## Executable called for each configuration that executes the target algorithm +## to be tuned. See the templates and examples provided. +targetRunner = "./target-runner.sh" + +## Executable that will be used to launch the target runner, when targetRunner +## cannot be executed directly (.e.g, a Python script in Windows). +# targetRunnerLauncher = "" + +## Command-line arguments provided to targetRunnerLauncher. The substrings +## \{targetRunner\} and \{targetRunnerArgs\} will be replaced by the value of +## the option targetRunner and by the arguments usually passed when calling +## targetRunner, respectively. Example: "-m {targetRunner --args +## {targetRunnerArgs}"}. +# targetRunnerLauncherArgs = "{targetRunner} {targetRunnerArgs}" + +## Number of times to retry a call to targetRunner if the call failed. +# targetRunnerRetries = 0 + +## Optional data passed to targetRunner. This is ignored by the default +## targetRunner function, but it may be used by custom targetRunner functions +## to pass persistent data around. +# targetRunnerData = "" + +## Optional R function to provide custom parallelization of targetRunner. +# targetRunnerParallel = "" + +## Optional script or R function that provides a numeric value for each +## configuration. See templates/target-evaluator.tmpl +# targetEvaluator = "" + +## If the target algorithm is deterministic, configurations will be evaluated +## only once per instance. +# deterministic = 0 + +## Maximum number of runs (invocations of targetRunner) that will be +## performed. It determines the maximum budget of experiments for the tuning. +maxExperiments = 100 # min 96... + +## Maximum total execution time in seconds for the executions of targetRunner. +## targetRunner must return two values: cost and time. +# maxTime = 0 + +## Fraction (smaller than 1) of the budget used to estimate the mean +## computation time of a configuration. Only used when maxTime > 0 +# budgetEstimation = 0.02 + +## Minimum time unit that is still (significantly) measureable. +# minMeasurableTime = 0.01 + +## Number of calls to targetRunner to execute in parallel. Values 0 or 1 mean +## no parallelization. +# parallel = 0 + +## Enable/disable load-balancing when executing experiments in parallel. +## Load-balancing makes better use of computing resources, but increases +## communication overhead. If this overhead is large, disabling load-balancing +## may be faster. +# loadBalancing = 1 + +## Enable/disable MPI. Use Rmpi to execute targetRunner in parallel (parameter +## parallel is the number of slaves). +# mpi = 0 + +## Specify how irace waits for jobs to finish when targetRunner submits jobs +## to a batch cluster: sge, pbs, torque, slurm or htcondor. targetRunner must +## submit jobs to the cluster using, for example, qsub. +# batchmode = 0 + +## Maximum number of decimal places that are significant for numerical (real) +## parameters. +# digits = 4 + +## Reduce the output generated by irace to a minimum. +# quiet = 0 + +## Debug level of the output of irace. Set this to 0 to silence all debug +## messages. Higher values provide more verbose debug messages. +# debugLevel = 0 + +## Seed of the random number generator (by default, generate a random seed). +# seed = NA + +## Enable/disable the soft restart strategy that avoids premature convergence +## of the probabilistic model. +# softRestart = 1 + +## Soft restart threshold value for numerical parameters. If NA, NULL or "", +## it is computed as 10^-digits. +# softRestartThreshold = "" + +## Enable/disable elitist irace. +# elitist = 1 + +## Number of instances added to the execution list before previous instances +## in elitist irace. +# elitistNewInstances = 1 + +## In elitist irace, maximum number per race of elimination tests that do not +## eliminate a configuration. Use 0 for no limit. +# elitistLimit = 2 + +## User-defined R function that takes a configuration generated by irace and +## repairs it. +# repairConfiguration = "" + +## Enable the use of adaptive capping, a technique designed for minimizing the +## computation time of configurations. This is only available when elitist is +## active. +# capping = 0 + +## Measure used to obtain the execution bound from the performance of the +## elite configurations: median, mean, worst, best. +# cappingType = "median" + +## Method to calculate the mean performance of elite configurations: candidate +## or instance. +# boundType = "candidate" + +## Maximum execution bound for targetRunner. It must be specified when capping +## is enabled. +# boundMax = 0 + +## Precision used for calculating the execution time. It must be specified +## when capping is enabled. +# boundDigits = 0 + +## Penalization constant for timed out executions (executions that reach +## boundMax execution time). +# boundPar = 1 + +## Replace the configuration cost of bounded executions with boundMax. +# boundAsTimeout = 1 + +## Percentage of the configuration budget used to perform a postselection race +## of the best configurations of each iteration after the execution of irace. +# postselection = 0 + +## Enable/disable AClib mode. This option enables compatibility with +## GenericWrapper4AC as targetRunner script. +# aclib = 0 + +## Maximum number of iterations. +# nbIterations = 0 + +## Number of runs of the target algorithm per iteration. +# nbExperimentsPerIteration = 0 + +## Minimum number of configurations needed to continue the execution of each +## race (iteration). +# minNbSurvival = 0 + +## Number of configurations to be sampled and evaluated at each iteration. +# nbConfigurations = 0 + +## Parameter used to define the number of configurations sampled and evaluated +## at each iteration. +# mu = 5 + +## Confidence level for the elimination test. +# confidence = 0.95 + +## END of scenario file +############################################################################ + diff --git a/demo/05_QuickstartTSP_IRACE/scenario.txt.tmpl b/demo/05_QuickstartTSP_IRACE/scenario.txt.tmpl new file mode 100644 index 0000000..5f3635f --- /dev/null +++ b/demo/05_QuickstartTSP_IRACE/scenario.txt.tmpl @@ -0,0 +1,234 @@ +###################################################### -*- mode: r -*- ##### +## Scenario setup for Iterated Race (irace). +############################################################################ + +## To use the default value of a parameter of iRace, simply do not set +## the parameter (comment it out in this file, and do not give any +## value on the command line). + +## Directory where the programs will be run. +# execDir = "./" + +## File that contains the description of the parameters of the target +## algorithm. +# parameterFile = "./parameters.txt" + +## File that contains a list of logical expressions that cannot be TRUE for +## any evaluated configuration. If empty or NULL, do not use forbidden +## expressions. +# forbiddenFile = "" + +## File that contains a table of initial configurations. If empty or NULL, all +## initial configurations are randomly generated. +# configurationsFile = "" + +## File to save tuning results as an R dataset, either absolute path or +## relative to execDir. +# logFile = "./irace.Rdata" + +## Directory where training instances are located; either absolute path or +## relative to current directory. If no trainInstancesFiles is provided, all +## the files in trainInstancesDir will be listed as instances. +# trainInstancesDir = "./Instances" + +## File that contains a list of training instances and optionally additional +## parameters for them. If trainInstancesDir is provided, irace will search +## for the files in this folder. +# trainInstancesFile = "" + +## Randomly sample the training instances or use them in the order given. +# sampleInstances = 1 + +## Directory where testing instances are located, either absolute or relative +## to current directory. +# testInstancesDir = "" + +## File containing a list of test instances and optionally additional +## parameters for them. +# testInstancesFile = "" + +## Number of elite configurations returned by irace that will be tested if +## test instances are provided. +# testNbElites = 1 + +## Enable/disable testing the elite configurations found at each iteration. +# testIterationElites = 0 + +## Statistical test used for elimination. The default value selects t-test if +## capping is enabled or F-test, otherwise. Valid values are: F-test (Friedman +## test), t-test (pairwise t-tests with no correction), t-test-bonferroni +## (t-test with Bonferroni's correction for multiple comparisons), t-test-holm +## (t-test with Holm's correction for multiple comparisons). +# testType = "" + +## Number of instances evaluated before the first elimination test. It must be +## a multiple of eachTest. +# firstTest = 5 + +## Number of instances evaluated between elimination tests. +# eachTest = 1 + +## Executable called for each configuration that executes the target algorithm +## to be tuned. See the templates and examples provided. +# targetRunner = "./target-runner" + +## Executable that will be used to launch the target runner, when targetRunner +## cannot be executed directly (.e.g, a Python script in Windows). +# targetRunnerLauncher = "" + +## Command-line arguments provided to targetRunnerLauncher. The substrings +## \{targetRunner\} and \{targetRunnerArgs\} will be replaced by the value of +## the option targetRunner and by the arguments usually passed when calling +## targetRunner, respectively. Example: "-m {targetRunner --args +## {targetRunnerArgs}"}. +# targetRunnerLauncherArgs = "{targetRunner} {targetRunnerArgs}" + +## Number of times to retry a call to targetRunner if the call failed. +# targetRunnerRetries = 0 + +## Optional data passed to targetRunner. This is ignored by the default +## targetRunner function, but it may be used by custom targetRunner functions +## to pass persistent data around. +# targetRunnerData = "" + +## Optional R function to provide custom parallelization of targetRunner. +# targetRunnerParallel = "" + +## Optional script or R function that provides a numeric value for each +## configuration. See templates/target-evaluator.tmpl +# targetEvaluator = "" + +## If the target algorithm is deterministic, configurations will be evaluated +## only once per instance. +# deterministic = 0 + +## Maximum number of runs (invocations of targetRunner) that will be +## performed. It determines the maximum budget of experiments for the tuning. +# maxExperiments = 0 + +## Maximum total execution time in seconds for the executions of targetRunner. +## targetRunner must return two values: cost and time. +# maxTime = 0 + +## Fraction (smaller than 1) of the budget used to estimate the mean +## computation time of a configuration. Only used when maxTime > 0 +# budgetEstimation = 0.02 + +## Minimum time unit that is still (significantly) measureable. +# minMeasurableTime = 0.01 + +## Number of calls to targetRunner to execute in parallel. Values 0 or 1 mean +## no parallelization. +# parallel = 0 + +## Enable/disable load-balancing when executing experiments in parallel. +## Load-balancing makes better use of computing resources, but increases +## communication overhead. If this overhead is large, disabling load-balancing +## may be faster. +# loadBalancing = 1 + +## Enable/disable MPI. Use Rmpi to execute targetRunner in parallel (parameter +## parallel is the number of slaves). +# mpi = 0 + +## Specify how irace waits for jobs to finish when targetRunner submits jobs +## to a batch cluster: sge, pbs, torque, slurm or htcondor. targetRunner must +## submit jobs to the cluster using, for example, qsub. +# batchmode = 0 + +## Maximum number of decimal places that are significant for numerical (real) +## parameters. +# digits = 4 + +## Reduce the output generated by irace to a minimum. +# quiet = 0 + +## Debug level of the output of irace. Set this to 0 to silence all debug +## messages. Higher values provide more verbose debug messages. +# debugLevel = 0 + +## Seed of the random number generator (by default, generate a random seed). +# seed = NA + +## Enable/disable the soft restart strategy that avoids premature convergence +## of the probabilistic model. +# softRestart = 1 + +## Soft restart threshold value for numerical parameters. If NA, NULL or "", +## it is computed as 10^-digits. +# softRestartThreshold = "" + +## Enable/disable elitist irace. +# elitist = 1 + +## Number of instances added to the execution list before previous instances +## in elitist irace. +# elitistNewInstances = 1 + +## In elitist irace, maximum number per race of elimination tests that do not +## eliminate a configuration. Use 0 for no limit. +# elitistLimit = 2 + +## User-defined R function that takes a configuration generated by irace and +## repairs it. +# repairConfiguration = "" + +## Enable the use of adaptive capping, a technique designed for minimizing the +## computation time of configurations. This is only available when elitist is +## active. +# capping = 0 + +## Measure used to obtain the execution bound from the performance of the +## elite configurations: median, mean, worst, best. +# cappingType = "median" + +## Method to calculate the mean performance of elite configurations: candidate +## or instance. +# boundType = "candidate" + +## Maximum execution bound for targetRunner. It must be specified when capping +## is enabled. +# boundMax = 0 + +## Precision used for calculating the execution time. It must be specified +## when capping is enabled. +# boundDigits = 0 + +## Penalization constant for timed out executions (executions that reach +## boundMax execution time). +# boundPar = 1 + +## Replace the configuration cost of bounded executions with boundMax. +# boundAsTimeout = 1 + +## Percentage of the configuration budget used to perform a postselection race +## of the best configurations of each iteration after the execution of irace. +# postselection = 0 + +## Enable/disable AClib mode. This option enables compatibility with +## GenericWrapper4AC as targetRunner script. +# aclib = 0 + +## Maximum number of iterations. +# nbIterations = 0 + +## Number of runs of the target algorithm per iteration. +# nbExperimentsPerIteration = 0 + +## Minimum number of configurations needed to continue the execution of each +## race (iteration). +# minNbSurvival = 0 + +## Number of configurations to be sampled and evaluated at each iteration. +# nbConfigurations = 0 + +## Parameter used to define the number of configurations sampled and evaluated +## at each iteration. +# mu = 5 + +## Confidence level for the elimination test. +# confidence = 0.95 + +## END of scenario file +############################################################################ + diff --git a/demo/05_QuickstartTSP_IRACE/target-evaluator.tmpl b/demo/05_QuickstartTSP_IRACE/target-evaluator.tmpl new file mode 100755 index 0000000..4dd80f1 --- /dev/null +++ b/demo/05_QuickstartTSP_IRACE/target-evaluator.tmpl @@ -0,0 +1,58 @@ +#!/bin/bash +############################################################################### +# This script is run for each candidate to evaluate it after all +# candidate configurations have been run on a single instance. +# +# Check the examples in examples/ +# +# PARAMETERS: +# $1 is the candidate configuration number +# $2 is the instance id +# $3 is the seed +# $4 is the instance name +# $5 is the number of candidates alive in this iteration +# +# ONLY FOR ELITIST RACE: The rest ($* after `shift 5') are the ids of the +# candidates alive in this iteration. This list can be used to calculate the +# hypervolume using previous execution results. +# +# RETURN VALUE: +# This script should print one numerical value: the cost that must be minimized. +# Exit with 0 if no error, with 1 in case of error +############################################################################### +error() { + echo "`TZ=UTC date`: $0: error: $@" + exit 1 +} + +CANDIDATE="$1" +INSTANCEID="$2" +SEED="$3" +INSTANCE="$4" +TOTALCANDIDATES="$5" +shift 5 || error "Not enough parameters" +ALLIDS=$* + +STDOUT=c${CANDIDATE}-${INSTANCEID}-${SEED}.stdout +STDERR=c${CANDIDATE}-${INSTANCEID}-${SEED}.stderr + + +# # This may be used to introduce a delay if there are filesystem +# # issues. +# SLEEPTIME=1 +# while [ ! -s "${STDOUT}" ]; do +# sleep $SLEEPTIME +# let "SLEEPTIME += 1" +# done + +# This is an example of reading a number from the output of +# target-runner. It assumes that the objective value is the first number in +# the first column of the only line starting with a digit. +if [ -s "${STDOUT}" ]; then + COST=$(cat ${STDOUT} | grep -e '^[[:space:]]*[+-]\?[0-9]' | cut -f1) + echo "$COST" + rm -f "${STDOUT}" "${STDERR}" + exit 0 +else + error "${STDOUT}: No such file or directory" +fi diff --git a/demo/05_QuickstartTSP_IRACE/target-runner.sh b/demo/05_QuickstartTSP_IRACE/target-runner.sh new file mode 100755 index 0000000..9be9761 --- /dev/null +++ b/demo/05_QuickstartTSP_IRACE/target-runner.sh @@ -0,0 +1,80 @@ +#!/bin/bash +############################################################################### +# This script is the command that is executed every run. +# Check the examples in examples/ +# +# This script is run in the execution directory (execDir, --exec-dir). +# +# PARAMETERS: +# $1 is the candidate configuration number +# $2 is the instance ID +# $3 is the seed +# $4 is the instance name +# The rest ($* after `shift 4') are parameters to the run +# +# RETURN VALUE: +# This script should print one numerical value: the cost that must be minimized. +# Exit with 0 if no error, with 1 in case of error +############################################################################### +error() { + echo "`TZ=UTC date`: $0: error: $@" + exit 1 +} + +# This parses the arguments given by irace. Do not touch it! +CONFIG_ID=$1 +INSTANCE_ID=$2 +SEED=$3 +INSTANCE=$4 +shift 4 || error "Not enough parameters" +CONFIG_PARAMS=$* +# End of parsing + +## Find our own location. +# BINDIR=$(dirname "$(readlink -f "$(type -P $0 || echo $0)")") + +# EDIT THIS: Path to your executable +EXE=/usr/bin/python3 +# EDIT THIS: Specify how parameters are given to your executable +EXE_PARAMS="mainTSP-fcore-ils.py --time 1 -i $INSTANCE --seed ${SEED} ${CONFIG_PARAMS}" + +if [ ! -x "$(command -v ${EXE})" ]; then + error "${EXE}: not found or not executable (pwd: $(pwd))" +fi + +# # If the program just prints a number, we can use 'exec' to avoid creating +# # another process, but there can be no other commands after exec, only exit. +# exec $EXE ${EXE_PARAMS} +# exit 1 + +# These files are saved in execDir. Saving them in /tmp may be faster. +STDOUT=c${CONFIG_ID}-${INSTANCE_ID}-${SEED}.stdout +STDERR=c${CONFIG_ID}-${INSTANCE_ID}-${SEED}.stderr + +# # Otherwise, save the output to a file, and parse the result from it. (If +# # you wish to ignore segmentation faults you can use '{}' around the +# # command.) +# # If the command below fails, but you are not sure why, it may be useful to +# # print it before executing using 'echo', as in: +# echo "$EXE ${EXE_PARAMS} 1> ${STDOUT} 2> ${STDERR}" +$EXE ${EXE_PARAMS} 1> ${STDOUT} 2> ${STDERR} + +# # This may be used to introduce a delay if there are filesystem issues. +# SLEEPTIME=1 +# while [ ! -s "${STDOUT}" ]; do +# sleep $SLEEPTIME +# let "SLEEPTIME += 1" +# done + +if [ ! -s "${STDOUT}" ]; then + error "${STDOUT}: No such file or directory" +fi +# This is an example of reading a number from the output. It assumes that the +# objective value is the first number in the first column of the last line of +# the output. +COST=$(tail -n 1 ${STDOUT} | grep -e '^[[:space:]]*[+-]\?[0-9]' | cut -f1) +echo "$COST" +# Comment the following line if you wish to preserve temporary files +rm -f "${STDOUT}" "${STDERR}" +exit 0 + diff --git a/demo/05_QuickstartTSP_IRACE/target-runner.tmpl b/demo/05_QuickstartTSP_IRACE/target-runner.tmpl new file mode 100755 index 0000000..f1174d1 --- /dev/null +++ b/demo/05_QuickstartTSP_IRACE/target-runner.tmpl @@ -0,0 +1,80 @@ +#!/bin/bash +############################################################################### +# This script is the command that is executed every run. +# Check the examples in examples/ +# +# This script is run in the execution directory (execDir, --exec-dir). +# +# PARAMETERS: +# $1 is the candidate configuration number +# $2 is the instance ID +# $3 is the seed +# $4 is the instance name +# The rest ($* after `shift 4') are parameters to the run +# +# RETURN VALUE: +# This script should print one numerical value: the cost that must be minimized. +# Exit with 0 if no error, with 1 in case of error +############################################################################### +error() { + echo "`TZ=UTC date`: $0: error: $@" + exit 1 +} + +# This parses the arguments given by irace. Do not touch it! +CONFIG_ID=$1 +INSTANCE_ID=$2 +SEED=$3 +INSTANCE=$4 +shift 4 || error "Not enough parameters" +CONFIG_PARAMS=$* +# End of parsing + +## Find our own location. +# BINDIR=$(dirname "$(readlink -f "$(type -P $0 || echo $0)")") + +# EDIT THIS: Path to your executable +EXE=~/bin/program +# EDIT THIS: Specify how parameters are given to your executable +EXE_PARAMS="--time 5 -i $INSTANCE --seed ${SEED} ${CONFIG_PARAMS}" + +if [ ! -x "$(command -v ${EXE})" ]; then + error "${EXE}: not found or not executable (pwd: $(pwd))" +fi + +# # If the program just prints a number, we can use 'exec' to avoid creating +# # another process, but there can be no other commands after exec, only exit. +# exec $EXE ${EXE_PARAMS} +# exit 1 + +# These files are saved in execDir. Saving them in /tmp may be faster. +STDOUT=c${CONFIG_ID}-${INSTANCE_ID}-${SEED}.stdout +STDERR=c${CONFIG_ID}-${INSTANCE_ID}-${SEED}.stderr + +# # Otherwise, save the output to a file, and parse the result from it. (If +# # you wish to ignore segmentation faults you can use '{}' around the +# # command.) +# # If the command below fails, but you are not sure why, it may be useful to +# # print it before executing using 'echo', as in: +# echo "$EXE ${EXE_PARAMS} 1> ${STDOUT} 2> ${STDERR}" +$EXE ${EXE_PARAMS} 1> ${STDOUT} 2> ${STDERR} + +# # This may be used to introduce a delay if there are filesystem issues. +# SLEEPTIME=1 +# while [ ! -s "${STDOUT}" ]; do +# sleep $SLEEPTIME +# let "SLEEPTIME += 1" +# done + +if [ ! -s "${STDOUT}" ]; then + error "${STDOUT}: No such file or directory" +fi +# This is an example of reading a number from the output. It assumes that the +# objective value is the first number in the first column of the last line of +# the output. +COST=$(tail -n 1 ${STDOUT} | grep -e '^[[:space:]]*[+-]\?[0-9]' | cut -f1) +echo "$COST" +# Comment the following line if you wish to preserve temporary files +rm -f "${STDOUT}" "${STDERR}" +exit 0 +