irace/0000755000176200001440000000000014753147342011347 5ustar liggesusersirace/tests/0000755000176200001440000000000014752430310012476 5ustar liggesusersirace/tests/testthat/0000755000176200001440000000000014752430311014337 5ustar liggesusersirace/tests/testthat/good_scenario.txt0000644000176200001440000000004114736526233017721 0ustar liggesusersparameterFile = "parameters.txt" irace/tests/testthat/test-path.R0000644000176200001440000001657014736526233016417 0ustar liggesuserstest_path_rel2abs <- function(testcases) { for (i in 1:nrow(testcases)) { orig <- testcases[i,1L] cwd <- testcases[i,2L] res <- path_rel2abs(testcases[i,1L], cwd) if (testcases[i,3L] == "Sys.which") { exp <- fs::path_abs(Sys.which(testcases[i,1L])) } else { exp <- gsub("\\", "/", fs::path_expand(testcases[i,3L]), fixed = TRUE) } if (res == exp) { #cat("[OK] ", i, ": path_rel2abs(\"", orig, "\", \"", cwd, "\") -> ", res, "\n", sep="") } else { cat(sep="", "[FAILED] ", i, ": path_rel2abs(\"", orig, "\", \"", cwd, "\") -> ", res, " but expected: ", exp, "\n") } expect_match(res, exp, fixed = TRUE) } } test_that("test path_rel2abs", { # Try to set wd; otherwise fail silently. old.cwd <- getwd() skip_if(is.null(old.cwd)) withr::defer(setwd(old.cwd)) tryCatch(setwd("/tmp"), error = function(e) { skip(e) }) testcases <- read.table(text=' "." "/tmp" "/tmp" ".." "/tmp" "/" "../" "/tmp" "/" "../." "/tmp" "/" "../.." "/tmp" "/" "../../" "/tmp" "/" "../../x.r" "/tmp" "/x.r" "../leslie/" "/tmp" "/leslie" "../leslie/x.r" "/tmp" "/leslie/x.r" "../x.r" "/tmp" "/x.r" "..irace" "/tmp" "/tmp/..irace" "./" "/tmp" "/tmp" "./." "/tmp" "/tmp" "./" "/tmp/" "/tmp" "./." "/tmp/" "/tmp" "././x.r" "/tmp" "/tmp/x.r" "./irace/../x.r" "/tmp" "/tmp/x.r" "./x.r" "/tmp" "/tmp/x.r" ".x.R" "/tmp" "/tmp/.x.R" "/./x.r" "/tmp" "/x.r" "/home" "/tmp" "/home" "/home/leslie/././x.r" "/tmp" "/home/leslie/x.r" "/home/leslie/~/x.r" "/tmp" "/home/leslie/~/x.r" "/~/x.r" "/tmp" "/~/x.r" "leslie/leslie/../../irace" "/tmp" "/tmp/irace" "x.r" "/tmp" "/tmp/x.r" "~/irace/../x.r" "/tmp" "~/x.r" "~/x.r" "/tmp" "~/x.r" "../../../data" "./" "/data" "../../../data" "/tmp/a/b/c/" "/tmp/data" "..//a" ".//" "/a" ', stringsAsFactors=FALSE) test_path_rel2abs(testcases) }) test_that("test path_rel2abs without /tmp", { testcases <- read.table(text=' . N:\\\\tmp N:/tmp .. N:\\\\tmp N:/ ..\\\\ N:\\\\tmp N:/ ..\\\\. N:\\\\tmp N:/ ..\\\\.. N:\\\\tmp N:/ ..\\\\..\\\\ N:\\\\tmp N:/ ..\\\\..\\\\x.r N:\\\\tmp N:/x.r ..\\\\leslie\\\\ N:\\\\tmp N:/leslie ..\\\\leslie\\\\x.r N:\\\\tmp N:/leslie/x.r ..\\\\x.r N:\\\\tmp N:/x.r ..irace N:\\\\tmp N:/tmp/..irace .\\\\ N:\\\\tmp N:/tmp .\\\\. N:\\\\tmp N:/tmp .\\\\ N:\\\\tmp\\\\ N:/tmp .\\\\. N:\\\\tmp\\\\ N:/tmp .\\\\.\\\\x.r N:\\\\tmp N:/tmp/x.r .\\\\x.r N:\\\\tmp N:/tmp/x.r .\\\\irace\\\\..\\\\x.r N:\\\\tmp N:/tmp/x.r .x.R N:\\\\tmp N:/tmp/.x.R . N:\\tmp N:/tmp .. N:\\tmp N:/ ..\\ N:\\tmp N:/ ..\\. N:\\tmp N:/ ..\\.. N:\\tmp N:/ ..\\..\\ N:\\tmp N:/ ..\\..\\x.r N:\\tmp N:/x.r ..\\leslie\\ N:\\tmp N:/leslie ..\\leslie\\x.r N:\\tmp N:/leslie/x.r ..\\x.r N:\\tmp N:/x.r ..irace N:\\tmp N:/tmp/..irace .\\ N:\\tmp N:/tmp .\\. N:\\tmp N:/tmp .\\ N:\\tmp\\ N:/tmp .\\. N:\\tmp\\ N:/tmp .\\.\\x.r N:\\tmp N:/tmp/x.r .\\irace\\..\\x.r N:\\tmp N:/tmp/x.r .\\x.r N:\\tmp N:/tmp/x.r .x.R N:\\tmp N:/tmp/.x.R . N: N:/ .. N: N:/ ..\\\\ N: N:/ ..\\\\. N: N:/ ..\\\\.. N: N:/ ..\\\\..\\\\ N: N:/ ..\\\\..\\\\x.r N: N:/x.r ..\\\\leslie\\\\ N: N:/leslie ..\\\\leslie\\\\x.r N: N:/leslie/x.r ..\\\\x.r N: N:/x.r ..\\ N: N:/ ..\\. N: N:/ ..\\.. N: N:/ ..\\..\\ N: N:/ ..\\..\\x.r N: N:/x.r ..\\leslie\\ N: N:/leslie ..\\leslie\\x.r N: N:/leslie/x.r ..\\x.r N: N:/x.r ..irace N: N:/..irace .\\\\ N: N:/ .\\\\. N: N:/ .\\\\ N:\\\\ N:/ .\\\\. N:\\\\ N:/ .\\\\.\\\\x.r N: N:/x.r .\\\\irace\\\\..\\\\x.r N: N:/x.r .\\\\x.r N: N:/x.r .\\ N: N:/ .\\. N: N:/ .\\ N:\\ N:/ .\\. N:\\ N:/ .\\.\\x.r N: N:/x.r .\\irace\\..\\x.r N: N:/x.r .\\x.r N: N:/x.r .x.R N: N:/.x.R . N:/tmp N:/tmp .. N:/tmp N:/ ../ N:/tmp N:/ ../. N:/tmp N:/ ../.. N:/tmp N:/ ../../ N:/tmp N:/ ../../x.r N:/tmp N:/x.r ../leslie/ N:/tmp N:/leslie ../leslie/x.r N:/tmp N:/leslie/x.r ../x.r N:/tmp N:/x.r ..irace N:/tmp N:/tmp/..irace ./ N:/tmp N:/tmp ./. N:/tmp N:/tmp ./ N:/tmp/ N:/tmp ./. N:/tmp/ N:/tmp ././x.r N:/tmp N:/tmp/x.r ./irace/../x.r N:/tmp N:/tmp/x.r ./x.r N:/tmp N:/tmp/x.r .x.R N:/tmp N:/tmp/.x.R D:/./x.r N:/tmp D:/x.r D:\\\\.\\\\x.r N:/tmp D:/x.r D:\\.\\x.r N:/tmp D:/x.r D: N:/tmp D:/ D:\\\\ N:/tmp D:/ D:/ N:/tmp D:/ D:/leslie/././x.r N:/tmp D:/leslie/x.r D:/leslie/~/x.r N:/tmp D:/leslie/~/x.r e:/home/leslie/x.r /tmp E:/home/leslie/x.r leslie/leslie/../../irace N:/tmp N:/tmp/irace x.r N:/tmp N:/tmp/x.r ~/irace/../x.r N:/tmp ~/x.r ~/x.r N:/tmp ~/x.r "R" "/tmp/" "Sys.which" ', stringsAsFactors=FALSE) test_path_rel2abs(testcases) }) test_that("test path_rel2abs with symlink", { # Try to set wd; otherwise fail silently. old.cwd <- getwd() skip_if(is.null(old.cwd)) withr::defer(setwd(old.cwd)) tryCatch({ tmp <- withr::local_tempdir() setwd(tmp) fs::dir_create("a") fs::file_create("a/b") fs::link_create(fs::path_abs("a"), "c") }, error = function(e) { skip(e) }) testcases <- data.frame(p = "c/b", wd = ".", res = file.path(tmp, "c/b"), stringsAsFactors=FALSE) test_path_rel2abs(testcases) }) irace/tests/testthat/test-similar.R0000644000176200001440000000237114736526233017115 0ustar liggesuserstest_that("similarConfigurations", { parameters <- readParameters(text = ' n1 "" r (0,1) n2 "" r (0,1) n3 "" r (0,1) c1 "" c ("a","b") c2 "" c ("a","b") c3 "" c ("a","b") ') confs <- read.table(header = TRUE, stringsAsFactors = FALSE, text = ' .ID. n1 n2 n3 c1 c2 c3 1 0.5 0.5 0.5 "a" "a" "a" 2 NA 0.5 0.5 "a" "a" "a" 3 0.5 0.5 0.5 "a" "a" "b" 4 0.5 0.501 0.5 "a" "a" "a" 5 0.5 0.5 0.499 "a" "a" "a" 6 0.5 0.5 0.5 "a" "a" "a" 7 0.5 0.5 0.5 "a" NA "a" 8 0.5 0.1 0.5 "a" "a" "a" 9 0.5 0.49 0.5 "a" "a" "a" 10 0.5 0.5 0.5 "a" "a" NA 11 0.5 0.5 0.5 "a" "a" NA 12 NA 0.5 0.5 "a" "a" "a" ') expect_identical( irace:::similarConfigurations(confs[1:10,], parameters, threshold = 0.001), as.integer(c(1,6))) expect_identical( irace:::similarConfigurations(confs[1:10,], parameters, threshold = 0.01), as.integer(c(1,4,5,6))) expect_identical( irace:::similarConfigurations(confs[1:10,], parameters, threshold = 0.1), as.integer(c(1,4,5,6,9))) expect_identical( # FIXME: The output should be already sorted sort(irace:::similarConfigurations(confs, parameters, threshold = 0.001)), as.integer(c(1,2,6,10,11,12))) }) irace/tests/testthat/test-bug-10.R0000644000176200001440000000415014736526233016445 0ustar liggesuserswithr::with_output_sink("test-bug-10.Rout", { parameters_txt <- ' algorithm "--" c (as,mmas,eas,ras,acs) localsearch "--localsearch " c (0, 1, 2, 3) alpha "--alpha " r (0.00, 5.00) beta "--beta " r (0.00, 10.00) rho "--rho " r (0.01, 1.00) ants "--ants " i (5, 100) q0 "--q0 " r (0.0, 1.0) | algorithm == "acs" rasrank "--rasranks " i (1, 100) | algorithm == "ras" elitistants "--elitistants " i (1, 750) | algorithm == "eas" nnls "--nnls " i (5, 50) | localsearch %in% c(1,2,3) dlb "--dlb " c (0, 1) | localsearch %in% c(1,2,3) ' parameters <- readParameters(text=parameters_txt) test_that("bug blocksize", { skip_on_cran() target_runner <- function(experiment, scenario) list(cost = 100 + rnorm(1, 0, 0.1)) withr::with_options(list(warning=2), { scenario <- list(targetRunner = target_runner, instances=1:5, firstTest=5*5, eachTest=5, sampleInstances=FALSE, maxExperiments = 5000, logFile = "", elitistNewInstances = 1, elitist = TRUE, parameters = parameters) scenario <- checkScenario (scenario) confs <- irace(scenario = scenario) expect_false(is.null(confs)) }) }) # https://github.com/MLopez-Ibanez/irace/issues/10 test_that("bug 10", { skip_on_cran() target_runner <- function(experiment, scenario) list(cost = 100, call = toString(experiment)) withr::with_options(list(warning=2), { scenario <- list(targetRunner = target_runner, instances=1:10, maxExperiments = 5000, logFile = "", deterministic = TRUE, elitistNewInstances = 0, elitistLimit = 0, elitist = 0, parameters = parameters) scenario <- checkScenario (scenario) confs <- irace(scenario = scenario) expect_false(is.null(confs)) }) }) }) # withr::with_output_sink irace/tests/testthat/test-forbidden.R0000644000176200001440000000340414736526233017407 0ustar liggesuserswithr::with_output_sink("test-forbidden.Rout", { test_that("checkForbidden", { test.checkForbidden <- function(param.file) { params <- readParameters(param.file) confs <- readConfigurationsFile("configurations.txt", params) exp.confs <- readConfigurationsFile(text=' param1 param2 mode real mutation 5 NA "x2" 4.0 "low" 1 NA "x2" 4.0 "low" 5 6 "x1" 3.5 "low" NA NA "x3" 4.5 "low" ', parameters = params) confs <- irace:::checkForbidden(confs, params$forbidden) rownames(confs) <- rownames(exp.confs) <- NULL expect_equal(confs, exp.confs) } test.checkForbidden(test_path("parameters.txt")) test.checkForbidden(test_path("logparameters.txt")) }) test_that("filter_forbidden", { test_filter_forbidden <- function(param_file) { params <- readParameters(param_file) confs <- readConfigurationsFile("configurations.txt", params) exp_confs <- data.table::as.data.table(readConfigurationsFile(text=' param1 param2 mode real mutation 5 NA "x2" 4.0 "low" 1 NA "x2" 4.0 "low" 5 6 "x1" 3.5 "low" NA NA "x3" 4.5 "low" ', parameters = params)) confs <- irace:::filter_forbidden(data.table::as.data.table(confs), params$forbidden) expect_equal(confs, exp_confs) } test_filter_forbidden(test_path("parameters.txt")) test_filter_forbidden(test_path("logparameters.txt")) }) test_that("retries", { params <- readParameters(text=' p0 "" r (0,1) [forbidden] p0 > 0.9 ') confs <- irace:::sampleUniform(params, 50) expect_equal(nrow(confs), 50L) }) test_that("max retries", { params <- readParameters(text=' p0 "" r (0,1) [forbidden] p0 <= 0.5 p0 > 0.5 ') expect_error(irace:::sampleUniform(params, 1), "perhaps your constraints are too strict") }) }) irace/tests/testthat/test-raceconfs.R0000644000176200001440000000241414736526233017416 0ustar liggesuserswithr::with_output_sink("test-raceconfs.Rout", { parameters_txt <- ' launch_method "--launch_method=" c (L-BFGS-B,SLSQP) visit_method "--visit_method=" c (DA, DE) global_method "--global_method=" c (CMAES,DE) ' confs_txt <- ' launch_method visit_method global_method L-BFGS-B DA CMAES SLSQP DA CMAES L-BFGS-B DE CMAES SLSQP DE CMAES L-BFGS-B DA DE SLSQP DA DE L-BFGS-B DE DE SLSQP DE DE ' target_runner <- function(experiment, scenario) list(cost = 100, call = toString(experiment)) withr::with_options(list(warning=2), { parameters <- readParameters(text=parameters_txt) initconfs <- readConfigurationsFile(text=confs_txt, parameters=parameters) scenario <- list(targetRunner = target_runner, instances = 1:10, nbConfigurations = 8, maxExperiments = 96, logFile = "", initConfigurations = initconfs, parameters = parameters) scenario <- checkScenario (scenario) confs <- irace(scenario = scenario) confs <- data.table::as.data.table(removeConfigurationsMetaData(confs)) initconfs <- data.table::as.data.table(initconfs) expect_equal(nrow(data.table::fsetdiff(confs, initconfs)), 0L) }) }) # withr::with_output_sink() irace/tests/testthat/test-get-functions.R0000644000176200001440000000060214737241452020233 0ustar liggesuserswithr::with_output_sink("test-get-functions.Rout", { test_that("getConfigurationById()", { log <- read_logfile(system.file(package="irace", "exdata", "irace-acotsp.Rdata", mustWork=TRUE)) ids <- sample(log$allConfigurations[[".ID."]], size=3) ids <- c(ids, rev(ids)) sel_ids <- getConfigurationById(log, ids = ids)[[".ID."]] expect_identical(sel_ids, ids) }) }) irace/tests/testthat/teardown.R0000644000176200001440000000002214736526233016312 0ustar liggesusersoptions(old_opts) irace/tests/testthat/test-psrace.R0000644000176200001440000000307714736526233016736 0ustar liggesuserswithr::with_output_sink("test-psrace.Rout", { test_that("max_experiments=0.1", { skip_on_cran() generate_set_seed() irace_log <- read_logfile(system.file(package="irace", "exdata", "sann.rda")) # Use a temporary file to not change the original "sann.rda". psrace_logFile <- withr::local_tempfile(fileext = ".Rdata") # Execute the post-selection after the execution of irace. Use 10% of the total budget. psRace(irace_log, max_experiments=0.1, psrace_logFile = psrace_logFile) irace_log <- read_logfile(psrace_logFile) budget <- nrow(irace_log$state$experiment_log[iteration == max(iteration)]) expect_gt(budget, 50L) # It should be equal but elitist_race sometimes fails to consume all the budget. expect_lte(budget, irace_log$psrace_log$max_experiments) }) test_that("max_experiments=101", { skip_on_cran() generate_set_seed() irace_log <- read_logfile(system.file(package="irace", "exdata", "sann.rda")) conf_ids <- unlist(tail(irace_log$allElites, n=1L)) # Use a temporary file to not change the original "sann.rda". psrace_logFile <- withr::local_tempfile(fileext = ".Rdata") # Execute the post-selection after the execution of irace. psRace(irace_log, max_experiments=101, conf_ids = conf_ids, psrace_logFile = psrace_logFile) irace_log <- read_logfile(psrace_logFile) budget <- nrow(irace_log$state$experiment_log[iteration == max(iteration)]) expect_gt(budget, 10L) # It should be equal but elitist_race sometimes fails to consume all the budget. expect_lte(budget, irace_log$psrace_log$max_experiments) }) }) # withr::with_output_sink() irace/tests/testthat/dependencies2.txt0000644000176200001440000000070714736526233017627 0ustar liggesusersparam1 "--param1 " i (1, "real") | mode %in% c("x1", "x2") param2 "--param2 " i ("real", 10) | mode %in% c("x1", "x3") & real > 2.5 & real <= 3.5 mode "--" c ("x1" ,"x2", "x3") real "--paramreal=" r (1.5, 7.5) mutation "--mutation=" o ("none", "very low", "low", "medium", "high", "very high", "all") #unused "-u " c (1, 2, 10, 20) irace/tests/testthat/configurations.txt0000644000176200001440000000056714556723674020165 0ustar liggesusersparam1 param2 mode real mutation 5 NA "x2" 4.0 "low" 1 NA "x1" 4.0 "low" 1 NA "x2" 4.0 "low" 5 7 "x1" 3.5 "low" 5 6 "x1" 3.5 "low" NA 7 "x3" 3.5 "low" NA 6 "x3" 3.5 "low" 5 NA "x2" 2.0 "low" NA NA "x3" 1.5 "low" NA NA "x3" 4.5 "low" irace/tests/testthat/test-readParameters.R0000644000176200001440000001536314736526233020421 0ustar liggesuserswithr::with_output_sink("test-readParameters.Rout", { test_that("optimize conditions", { p <- readParameters(text=' ROOT "" c ("S") ROOT_T "" c ("FIC") | ROOT %in% c("S") ROOT_T_FIC.sum "" r (-50.0, 50.0) | ROOT_T %in% c("FIC") ROOT_T.FFI "" c ("true") | ROOT_T == "FIC" ROOT_T.im "" c ("FFI", "FIC") | ROOT == "S" & ROOT_T.FFI == "true" ROOT_E.FFI "" c ("false") | ROOT_T_FIC.sum < 0 ROOT_E.FIC "" i (0,100) | ROOT_E.FFI == "false" ') expect_identical(p$conditions, list(ROOT=TRUE, ROOT_T=TRUE, ROOT_T_FIC.sum=TRUE, ROOT_T.FFI=TRUE, ROOT_T.im=TRUE, ROOT_E.FFI=expression(ROOT_T_FIC.sum < 0), ROOT_E.FIC=expression(ROOT_E.FFI == "false"))) }) test_that("error checking", { ref <- parametersNew(param_real(name = "tmp", lower = 0, upper=1, label = "", digits = 5), forbidden = "tmp == 0") expect_identical(ref, readParameters(text=' tmp "" r (0, 1) [global] digits = 5 [forbidden] tmp == 0 ')) expect_identical(ref, readParameters(text=' tmp "" r (0, 1) [forbidden] tmp == 0 [global] digits = 5 ')) expect_identical(ref, readParameters(text=' tmp "" r (0, 1) [forbidden] tmp == 0 [global] digits = 5 ')) expect_error(readParameters(text = ' rest_t "--restart_temp_ratio=" r,log (0.00001, 0.9) [global] digits = 4 '), "Domain bounds") expect_no_error(readParameters(text = ' rest_t "--restart_temp_ratio=" r,log (0.00001, 0.9) [global] digits = 5 ')) expect_error(readParameters(text = ' temp "" r (0, 10) t-max "" i (1, 50) '), "name must be alphanumeric") expect_error(readParameters(text = ' temp "" r (0, 10) [forbidden] tmp == 0 '), "contains unknown parameter(s): tmp", fixed = TRUE) expect_error(readParameters(text = ' temp "" r (0, 10) temp "" i (1, 50) '), "Duplicated parameter name") expect_error(readParameters(text = ' temp "" c ("a", "b", "a") '), "duplicated values") expect_error(readParameters(text = ' temp --temp i (0, 10) '), "Parameter label (switch) must be a double-quoted string", fixed = TRUE) expect_error(readParameters(text = ' temp "--temp" i,lag (0, 10) '), "Parameter type must be a single character in .'c','i','r','o'.") expect_error(readParameters(text = ' temp "--temp" i c(0, 10) '), "Allowed values must be a list within parenthesis at line 2") expect_error(readParameters(text = 'param1 "--param1 " c "a,b,c"'), "Allowed values must be a list within parenthesis at line 1") expect_error(readParameters(text = 'param1 "--param1 " i (1,2,3 )'), "Incorrect numeric range") expect_error(readParameters(text = 'param1 "--param1 " i ( 1,10) |'), "Expected condition before '|'") expect_error(readParameters(text = 'param1 "--param1 " i ( 1, 10) param1 < param2'), "Expected condition after '|'") expect_error(readParameters(text = ' param1 "--param1 " i (0,2) param2 "--param2 " r (0,1) | param1 <> 1'), "Invalid condition 'param1 <> 1': ") expect_error(readParameters(text = ' param1 "--param1 " i (0,2) param2 "--param2 " r (0,2) | param1 < param2'), "Cycle detected.+param2") expect_error(readParameters(text = ' param1 "--param1 " i (0,2) param2 "--param2 " r (0,param2)'), "Cycle detected.+param2") expect_error(readParameters(text = ' param1 "--param1 " i (0,2) param2 "--param2 " r (0,2) | param1 < param3'), "Parameter 'param2' depends on 'param3' which is not defined") expect_error(readParameters(text = ' param1 "--param1 " i (0,2) param2 "--param2 " r (0,param3)'), "'param3' cannot be found in the scenario parameters") expect_error(readParameters(text = ' param1 "--param1 " c (a,b) param2 "--param2 " r (0,param1)'), "Domain of parameter 'param2' depends on non-numerical parameters: param1") expect_error(readParameters(text = 'param1 "--param1 " i (1.1,2.0)'), "For parameter 'param1' of type 'i' values must be integers") expect_error(readParameters(text = 'param1 "--param1 " r (0.01, 0.001)'), "Lower bound must be smaller than upper bound in numeric range") expect_error(readParameters(text = 'param1 "--param1 " i (1, 1)'), "Lower bound must be smaller than upper bound in numeric range") expect_error(readParameters(text = ' # # '), "No parameter definition found") test_that("ordinal out of order", { expect_error(readParameters(text = ' param "" o (0, 2, 1) '), "the values are not in increasing order") }) expect_error(readParameters(text = 'param1 "--param1 " r,log (0, 100)'), "of parameter of type 'log' contains non-positive values") expect_error(readParameters(text = 'param1 "--param1 " r,log (0.0001, 0.99999)', digits=3), "must be representable within the given 'digits=3'; you would need at least 'digits=5'") expect_error(param_cat(name = "algorithm", values = c("as", "mmas", "eas", "ras", "acs"), label = "--", condition = "a == 0 && b == 2"), "Please use '&' and '|' instead of '&&' and '|' in: a == 0 && b == 2", fixed = TRUE) expect_error(param_cat(name = "algorithm", values = c("as", "mmas", "eas", "ras", "acs"), label = "--", condition = quote(a == 0 || b == 2)), "Please use '&' and '|' instead of '&&' and '|' in: a == 0 || b == 2", fixed = TRUE) expect_error(param_cat(name = "algorithm", values = c("as", "mmas", "eas", "ras", "acs"), label = "--", condition = 0), "Invalid condition", fixed = TRUE) expect_error(parametersNew(param_real(name = "alpha", lower = 0.0, upper=5.0), param_real(name = "beta", lower = 0.0, upper = 10.0), forbidden = expression((alpha == 0) || (beta == 0))), "Please use '&' and '|' instead of '&&' and '|' in: (alpha == 0) || (beta == 0)", fixed = TRUE) expect_error(parametersNew(param_real(name = "alpha", lower = 0.0, upper=5.0), param_real(name = "beta", lower = 0.0, upper = 10.0), forbidden = quote((alpha == 0) && (beta == 0))), "Please use '&' and '|' instead of '&&' and '|' in: (alpha == 0) && (beta == 0)", fixed = TRUE) expect_error(parametersNew(param_real(name = "alpha", lower = 0.0, upper=5.0), param_real(name = "beta", lower = 0.0, upper = 10.0), forbidden = TRUE), "Invalid forbidden expression", fixed = TRUE) expect_identical(param_real(name = "p", lower = "-1", upper = "10")$domain, c(-1,10)) expect_identical(param_int(name = "p", lower = "-1", upper = "10")$domain, c(-1L,10L)) expect_identical(param_int(name = "p", lower = -1, upper = 10)$domain, c(-1L,10L)) expect_identical(param_int(name = "p", lower = "-1", upper = expression(a))$domain[[1L]], -1L) expect_identical( param_int(name = "p", lower = "-1", upper= "a"), param_int(name = "p", lower = "-1", upper= expression(a))) expect_identical( param_int(name = "p", lower = -1, upper= "a"), param_int(name = "p", lower = "-1", upper= "a")) }) }) irace/tests/testthat/test-repair.R0000644000176200001440000000440514736526233016737 0ustar liggesuserswithr::with_output_sink("test-repair.Rout", { repair_irace <- function(targetRunner, repair) { weights <- rnorm(200, mean = 0.9, sd = 0.02) parameters <- readParameters(text = ' p1 "" r (0,1) p2 "" r (0,1) p3 "" r (0,1) dummy "" c ("d1", "d2") ') scenario <- list(targetRunner = targetRunner, repairConfiguration = repair, instances = weights, maxExperiments=180, seed = 1234567, parameters = parameters) scenario <- checkScenario (scenario) expect_true(irace:::checkTargetFiles(scenario = scenario)) confs <- irace(scenario = scenario) expect_gt(nrow(confs), 0L) confs } target_sum2one <- function(experiment, scenario) { configuration <- experiment$configuration p1 <- configuration[["p1"]] p2 <- configuration[["p2"]] p3 <- configuration[["p3"]] stopifnot(isTRUE(all.equal(p1+p2+p3, 1.0))) list(cost = -p1, call = toString(experiment)) } repair_sum2one <- function(configuration, parameters) { isreal <- parameters$names[parameters$types == "r"] digits <- sapply(isreal, function(x) parameters$get(x)[["digits"]]) c_real <- unlist(configuration[isreal]) c_real <- c_real / sum(c_real) c_real[-1] <- round(c_real[-1], digits[-1]) c_real[1L] <- 1 - sum(c_real[-1]) configuration[isreal] <- c_real configuration } target_order <- function(experiment, scenario) { configuration <- experiment$configuration p1 <- configuration[["p1"]] p2 <- configuration[["p2"]] p3 <- configuration[["p3"]] stopifnot(p1 <= p2 && p2 <= p3) list(cost = -p1, call = toString(experiment)) } repair_order <- function(configuration, parameters) { columns <- c("p1","p2","p3") #cat("Before"); print(configuration) configuration[columns] <- sort(unlist(configuration[columns], use.names=FALSE)) #cat("After"); print(configuration) configuration } test_that("repair: sum to one", { generate_set_seed() confs <- repair_irace(target_sum2one, repair_sum2one) expect_equal(unique(apply(confs[, c("p1", "p2", "p3")], 1L, sum)), 1) }) test_that("repair: increasing order", { generate_set_seed() confs <- repair_irace(target_order, repair_order) expect_true(all(apply(confs[, c("p1", "p2", "p3")], 1L, diff) >= 0)) }) }) # withr::with_output_sink() irace/tests/testthat/test-bug-55.R0000644000176200001440000000101614736526233016454 0ustar liggesusers# https://github.com/MLopez-Ibanez/irace/issues/55 withr::with_output_sink("test-bug-55.Rout", { test_that("bug-55", { parameters_txt <- ' foo "--foo " i (0, 1) foo2 "--foo2 " c (true, false) | foo == 0 ' params <- readParameters(text=parameters_txt) configurations_txt <- ' foo foo2 1 0 false 2 1 ' confs <- readConfigurationsFile(text=configurations_txt, parameters = params) expect_equal(confs, data.frame(foo=c(0L,1L), foo2=c("false",NA), row.names=c("1", "2"), stringsAsFactors=FALSE)) }) }) irace/tests/testthat/test-sann-irace.R0000644000176200001440000000653114736526233017477 0ustar liggesuserswithr::with_output_sink("test-sann-irace.Rout", { ## Functions ########################################################## f_rosenbrock <- function (x) { d <- length(x) z <- x + 1 hz <- z[1:(d - 1)] tz <- z[2:d] sum(100 * (hz^2 - tz)^2 + (hz - 1)^2) } f_rastrigin <- function (x) sum(x * x - 10 * cos(2 * pi * x) + 10) ## target runner ########################################################### target_runner <- function(experiment, scenario) { debugLevel <- scenario$debugLevel configuration_id <- experiment$id_configuration instance_id <- experiment$id_instance seed <- experiment$seed configuration <- experiment$configuration instance <- experiment$instance D <- 3 par <- runif(D, min = -1, max = 1) fn <- function(x) (instance * f_rastrigin(x) + (1 - instance) * f_rosenbrock(x)) tmax = 1 + configuration[["tmax"]] temp = 11.0 + configuration[["temp"]] stopifnot(tmax > 0) stopifnot(temp > 0) res <- withr::with_seed(seed, optim(par, fn, method = "SANN", control = list(maxit = 10, tmax = tmax, temp = temp)) ) list(cost = res$value, call = toString(experiment)) } ## target runner ########################################################### target_runner_reject <- function(experiment, scenario) { if (runif(1) <= 0.05) return (list(cost = -Inf, call = toString(experiment))) target_runner(experiment, scenario) } ## Run function ######################################################## sann_irace <- function(log.param=FALSE, ...) { args <- list(...) # tmax and temp must be > 0 if (log.param) parameters_table <- ' tmax "" i,log (1, 5000) temp "" r,log (1, 100) ' else parameters_table <- ' tmax "" i (1, 5000) temp "" r (1, 100) ' parameters <- readParameters(text = parameters_table) scenario <- list(targetRunner = target_runner, maxExperiments = 1000, seed = 1234567, parameters = parameters) scenario <- modifyList(scenario, args) scenario <- checkScenario (scenario) confs <- irace(scenario = scenario) best.conf <- getFinalElites(scenario$logFile, n = 1, drop.metadata = TRUE) expect_identical(removeConfigurationsMetaData(confs[1, , drop = FALSE]), best.conf) } test_that("parallel", { skip_on_cran() # Reproducible results generate_set_seed() weights <- rnorm(200, mean = 0.9, sd = 0.02) sann_irace(instances = weights, parallel = test_irace_detectCores()) }) test_that("parallel reject", { # Reproducible results generate_set_seed() weights <- rnorm(200, mean = 0.9, sd = 0.02) sann_irace(instances = weights, parallel = test_irace_detectCores(), targetRunner = target_runner_reject) }) test_that("deterministic", { skip_on_cran() # Reproducible results generate_set_seed() weights <- rnorm(200, mean = 0.9, sd = 0.02) sann_irace(deterministic = TRUE, instances = weights[1:7]) }) test_that("log", { skip_on_cran() # Reproducible results generate_set_seed() weights <- rnorm(200, mean = 0.9, sd = 0.02) sann_irace(log.param=TRUE, instances = weights) }) test_that("large newInstances", { skip_on_cran() # Reproducible results generate_set_seed() weights <- rnorm(200, mean = 0.9, sd = 0.02) sann_irace(instances = weights, elitistNewInstances = 6, elitistLimit = 2) }) }) # withr::with_output_sink() irace/tests/testthat/logparameters.txt0000644000176200001440000000161314736526233017761 0ustar liggesusersparam1 "--param1 " i,log (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,log (1.5, 4.5) mutation "--mutation=" o ("none", "very low", "low", "medium", "high", "very high", "all") #unused "-u " c (1, 2, 10, 20) [forbidden] ## 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 == "x3") real < 4 & mode %in% c("x2", "x3") irace/tests/testthat/test-argparser.R0000644000176200001440000000046614736526233017446 0ustar liggesuserswithr::with_output_sink("test-argparser.Rout", { test_that("argparse", { params_def <- data.frame(name =".param", type ="s", short = "-p", long=NA, default=NA, domain=NA, description="") parser <- irace:::CommandArgsParser$new("-p 'something something'", params_def) expect_length(parser$argv, 2L) }) }) irace/tests/testthat/test-sobol.R0000644000176200001440000000755414745735066016611 0ustar liggesuserswithr::with_output_sink("test-sobol.Rout", { test_that("bug with conditional dependent", { parameters <- parametersNew(param_cat(name = "algorithm", values = c("as", "mmas", "ras", "acs")), param_real(name = "alpha", lower = 0.0, upper=5.0), param_real(name = "beta", lower = 0.0, upper = 10.0), param_int(name = "ants", lower = 2, upper = 100), param_real(name = "q0", lower=0.0, upper=1.0, condition = expression(algorithm == "acs")), param_int(name = "rasrank", lower=1, upper=quote(min(ants, 10)), condition = 'algorithm == "ras"'), param_int(name = "eants", lower=0, upper=expression(rasrank)), param_cat(name = "dlb", values = c(0,1), condition = "localsearch == 1"), param_int(name = "nnls", lower = 5, upper = 50, condition = expression(dlb == 1)), param_ord(name = "localsearch", values = c("0", "1")), param_cat(name = "fixed", values = "0")) confs <- irace:::sampleSobol(parameters, 1000L) expect_equal(nrow(confs), 1000L) expect_valid_configurations(confs, parameters) }) test_that("bug with dependent fixed", { params <- readParameters(text=' ROOT "ROOT=" c ("SimpleAlgorithm") ROOT_SimpleAlgorithm.constructive "ROOT_SimpleAlgorithm.constructive=" c ("FasterInvertedConstructive", "SlowConstructive") | ROOT %in% c("SimpleAlgorithm") ROOT_SimpleAlgorithm.constructive_FasterInvertedConstructive.sumThis "ROOT_SimpleAlgorithm.constructive_FasterInvertedConstructive.sumThis=" r (-50.0, 50.0) | ROOT_SimpleAlgorithm.constructive %in% c("FasterInvertedConstructive") ROOT_SimpleAlgorithm.constructive_SlowConstructive.sumThis "ROOT_SimpleAlgorithm.constructive_SlowConstructive.sumThis=" i (-10, 10) | ROOT_SimpleAlgorithm.constructive %in% c("SlowConstructive") ROOT_SimpleAlgorithm.improver "ROOT_SimpleAlgorithm.improver=" c ("FlippyFlopImprover") | ROOT %in% c("SimpleAlgorithm") ROOT_SimpleAlgorithm.improver_FlippyFlopImprover.enabled "ROOT_SimpleAlgorithm.improver_FlippyFlopImprover.enabled=" c ("true", "false") | ROOT_SimpleAlgorithm.improver %in% c("FlippyFlopImprover") ROOT_SimpleAlgorithm.improver_FlippyFlopImprover.sleepy "ROOT_SimpleAlgorithm.improver_FlippyFlopImprover.sleepy=" c ("8", "6", "12", "11", "7", "5", "4", "10", "1", "9", "2", "3", "13") | ROOT_SimpleAlgorithm.improver %in% c("FlippyFlopImprover") ') confs <- irace:::sampleSobol(params, 10L) expect_equal(nrow(confs), 10L) expect_valid_configurations(confs, params) }) test_that("bug with dependent fixed #2", { params <- readParameters(text=' ROOT "ROOT=" c ("SimpleAlgorithm", "ComplexAlgorithms") ROOT_SimpleAlgorithm.constructive "ROOT_SimpleAlgorithm.constructive=" c ("FasterInvertedConstructive", "SlowConstructive") | ROOT %in% c("SimpleAlgorithm") ROOT_SimpleAlgorithm.constructive_FasterInvertedConstructive.sumThis "ROOT_SimpleAlgorithm.constructive_FasterInvertedConstructive.sumThis=" r (-50.0, 50.0) | ROOT_SimpleAlgorithm.constructive %in% c("FasterInvertedConstructive") ROOT_SimpleAlgorithm.constructive_SlowConstructive.sumThis "ROOT_SimpleAlgorithm.constructive_SlowConstructive.sumThis=" i (-10, 10) | ROOT_SimpleAlgorithm.constructive %in% c("SlowConstructive") ROOT_SimpleAlgorithm.improver "ROOT_SimpleAlgorithm.improver=" c ("FlippyFlopImprover") | ROOT %in% c("SimpleAlgorithm") ROOT_SimpleAlgorithm.improver_FlippyFlopImprover.enabled "ROOT_SimpleAlgorithm.improver_FlippyFlopImprover.enabled=" c ("true", "false") | ROOT_SimpleAlgorithm.improver %in% c("FlippyFlopImprover") ROOT_SimpleAlgorithm.improver_FlippyFlopImprover.sleepy "ROOT_SimpleAlgorithm.improver_FlippyFlopImprover.sleepy=" c ("8", "6", "12", "11", "7", "5", "4", "10", "1", "9", "2", "3", "13") | ROOT_SimpleAlgorithm.improver %in% c("FlippyFlopImprover") ') confs <- irace:::sampleSobol(params, 10) expect_equal(nrow(confs), 10) expect_valid_configurations(confs, params) }) }) irace/tests/testthat/test-target-runner-dummy.R0000644000176200001440000001526014745735066021412 0ustar liggesuserswithr::with_output_sink("test-target-runner-dummy.Rout", { skip_on_cran() get_executable <- function(filename) { filename <- paste0(filename, if (system_os_is_windows()) ".exe" else "") p <- if (.Platform$r_arch == "") file.path( "bin", filename) else file.path("bin", .Platform$r_arch, filename) system.file(p, package="irace", mustWork = FALSE) } runexe <- function(exe, args) { err <- NULL output <- withCallingHandlers( tryCatch(system2(exe, args, stdout = TRUE, stderr = TRUE), error = function(e) { err <<- c(err, paste(conditionMessage(e), collapse="\n")) NULL }), warning = function(w) { err <<- c(err, paste(conditionMessage(w), collapse="\n")) invokeRestart("muffleWarning") }) if (!is.null(err)) { err <- paste(err, collapse = "\n") if (!is.null(attr(output, "errmsg"))) err <- paste(sep = "\n", err, attr(output, "errmsg")) stop(err) } if (is.null(output)) output <- "" return(output) } test_that("irace exe works", { iraceexe <- get_executable("irace") skip_if_not(nzchar(iraceexe), "Not run because 'irace' is not installed") expect_true(file.exists(iraceexe)) # FIXME: For some reason, this does not generate any output on Windows output <- expect_silent(runexe(iraceexe, "--help")) ## cat("irace --help\n") ## print(output) expected_output <- if (system_os_is_windows()) "^$" else "irace: An implementation in R.*called with: --help" expect_match(paste(collapse="", output), expected_output) }) test_that("ablation exe works", { ablationexe <- get_executable("ablation") skip_if_not(nzchar(ablationexe), "Not run because 'ablation' is not installed") expect_true(file.exists(ablationexe)) # FIXME: For some reason, this does not generate any output on Windows output <- expect_silent(runexe(ablationexe, "--help")) ## cat("ablation --help\n") ## print(output) expected_output <- if (system_os_is_windows()) "^$" else "ablation: An implementation in R of Ablation Analysis.*called with: --help" expect_match(paste(collapse="", output), expected_output) }) run_cmdline <- function(parameters, args) { parameters_file <- tempfile("dummy-parameters", fileext = ".txt") withr::local_file(parameters_file) cat(parameters, file=parameters_file) train_instances_file <- tempfile("dummy-train-instances", fileext = ".txt") withr::local_file(train_instances_file) cat("1\n", file=train_instances_file) irace_cmdline(paste0(args, ' --debug-level 3 --parameter-file=', parameters_file, ' --train-instances-dir= --train-instances-file=', train_instances_file, ' --target-runner=', target_runner_dummy)) } target_runner_dummy <- get_executable("target-runner-dummy") skip_if_not(nzchar(target_runner_dummy), "Not run because 'target-runner-dummy' is not installed") expect_true(file.exists(target_runner_dummy)) test_that("--check", { expect_warning( run_cmdline(paste0('p_int "--p_int " i (1, 10)\n', 'p_real "--p_real " r (1, 10)\n'), '--check --max-experiments 500'), "No scenario file given") }) test_that("--max-time", { expect_warning( run_cmdline(paste0('p_int "--p_int " c (1)\n', 'p_real "--p_real " r (0, 1)\n', 'dummy1 "--dummy1 " r (0, 1)\n', 'time "--time " c (1)\n'), '--max-time 2500'), "No scenario file given") }) test_that("boundMax is too large", { expect_warning( expect_warning( run_cmdline(paste0('p_int "--p_int " i (1, 10)\n', 'p_real "--p_real " r (0, 1)\n', 'time "--time " c (1)\n', 'capping "--opt-time " c (1)\n'), '--max-time 1000 --bound-max 100 --capping 1'), "is too large"), "No scenario file given") }) test_that("--capping", { expect_warning( expect_warning( run_cmdline(paste0('p_int "--p_int " i (1, 10)\n', 'p_real "--p_real " r (1, 10)\n', 'dummy1 "--dummy1 " r (1, 10)\n', 'dummy2 "--dummy2 " r (1, 10)\n', 'dummy3 "--dummy3 " r (1, 10)\n', 'dummy4 "--dummy4 " r (1, 10)\n', 'dummy5 "--dummy5 " r (1, 10)\n', 'dummy6 "--dummy6 " r (1, 10)\n', 'dummy7 "--dummy7 " r (1, 10)\n', 'dummy8 "--dummy8 " r (1, 10)\n', 'dummy9 "--dummy9 " r (1, 10)\n', 'dummy11 "--dummy11 " r (1, 10)\n', 'dummy12 "--dummy12 " r (1, 10)\n', 'dummy13 "--dummy13 " r (1, 10)\n', 'dummy14 "--dummy14 " r (1, 10)\n', 'dummy15 "--dummy15 " r (1, 10)\n', 'dummy16 "--dummy16 " r (1, 10)\n', 'dummy17 "--dummy17 " r (1, 10)\n', 'dummy18 "--dummy18 " r (1, 10)\n', 'dummy19 "--dummy19 " r (1, 10)\n', 'time "--time " c (1)\n', 'capping "--opt-time " c (1)\n'), '--max-time 500 --bound-max 1 --capping 1 '), "With the current settings and estimated time per run"), "No scenario file given") }) test_that("Error cost time", { expect_error( expect_warning( run_cmdline(paste0('p_int "--p_int " i (1, 10)\n', 'p_real "--p_real " r (1, 10)\n'), '--max-time 100 '), "No scenario file given"), "The output of targetRunner must be two numbers 'cost time'", fixed = TRUE) }) test_that("low --max-experiments", { expect_warning( expect_error( run_cmdline(paste0('p_int "--p_int " i (1, 10)\n', 'p_real "--p_real " r (1, 10)\n', 'time "--time " c (1)\n'), '--max-experiments 50'), "With the current settings"), "No scenario file given") }) test_that("--min-experiments", { expect_no_warning( expect_warning( run_cmdline(paste0('p_int "--p_int " i (1, 10)\n', 'p_real "--p_real " r (1, 10)\n', 'time "--time " c (1)\n'), '--min-experiments 50'), "No scenario file given")) }) }) # withr::with_output_sink() irace/tests/testthat/test-ablation.R0000644000176200001440000002415514745735066017260 0ustar liggesuserswithr::with_output_sink("test-ablation.Rout", { skip_on_cran() withr::local_options(warn=2) test_that("generateAblation", { parameters <- parametersNew(param_cat(name = "algorithm", values = c("as", "mmas", "ras", "acs")), param_real(name = "alpha", lower = 0.0, upper=5.0), param_real(name = "beta", lower = 0.0, upper = 10.0), param_int(name = "ants", lower = 2, upper = 100), param_real(name = "q0", lower=0.0, upper=1.0, condition = expression(algorithm == "acs")), param_int(name = "rasrank", lower=1, upper=quote(min(ants, 10)), condition = 'algorithm == "ras"'), param_int(name = "eants", lower=0, upper=expression(rasrank)), param_cat(name = "dlb", values = c(0,1), condition = "localsearch == 1"), param_int(name = "nnls", lower = 5, upper = 50, condition = expression(dlb == 1)), param_ord(name = "localsearch", values = c("0", "1")), param_cat(name = "fixed", values = "0"), forbidden = "(alpha == 0) & (beta == 0)") confs <- readConfigurationsFile(parameters = parameters, text =' algorithm alpha beta ants q0 rasrank eants dlb nnls localsearch ras 1 0 5 NA 5 5 1 5 1 ras 0.5 0 5 NA 2 2 1 5 1 ras 0.5 0 4 NA 2 2 1 5 1 acs 0 1 2 0.5 NA NA NA NA 0 mmas 1 1 6 NA NA NA 0 NA 1 ') confs[[".ID."]] <- seq_len(nrow(confs)) colClasses <- c(localsearch="character", q0="numeric", rasrank="integer", eants="integer", dlb="character", nnls="integer") check_generate_ablation <- function(src, target, configurations_table, changed) { aux <- irace:::generate_ablation(confs[src, , drop=FALSE], confs[target, , drop=FALSE], parameters, param_names = parameters$names_variable) expect_valid_configurations(aux$configurations, parameters) configurations <- read.table(header=TRUE, colClasses=colClasses, text=configurations_table) configurations[["fixed"]] <- "0" configurations[[".ID."]] <- src configurations[[".PARENT."]] <- src expect_equal(aux, list(configurations=configurations, changed_params = changed)) } check_generate_ablation(1L, 2L, ' algorithm alpha beta ants q0 rasrank eants dlb nnls localsearch ras 0.5 0 5 NA 5 5 1 5 1 ras 1.0 0 5 NA 2 2 1 5 1 ras 1.0 0 5 NA 5 2 1 5 1 ', changed = list("alpha", c("rasrank", "eants"), "eants")) check_generate_ablation(1L, 3L, ' algorithm alpha beta ants q0 rasrank eants dlb nnls localsearch ras 0.5 0 5 NA 5 5 1 5 1 ras 1.0 0 4 NA 2 2 1 5 1 ras 1.0 0 5 NA 2 2 1 5 1 ras 1.0 0 5 NA 5 2 1 5 1 ', changed = list("alpha", c("ants", "rasrank", "eants"), c("rasrank", "eants"),"eants")) check_generate_ablation(1L, 4L, ' algorithm alpha beta ants q0 rasrank eants dlb nnls localsearch acs 1.0 0 5 0.5 NA NA 1 5 1 ras 1.0 1.0 5 NA 5 5 1 5 1 ras 1.0 0 5 NA 5 5 NA NA 0 ', changed = list(c("algorithm", "q0", "rasrank", "eants"), "beta", c("localsearch", "dlb", "nnls"))) check_generate_ablation(1L, 5L, ' algorithm alpha beta ants q0 rasrank eants dlb nnls localsearch mmas 1.0 0 5 NA NA NA 1 5 1 ras 1.0 1.0 5 NA 5 5 1 5 1 ras 1 0 6 NA 5 5 1 5 1 ras 1.0 0 5 NA 5 5 0 NA 1 ', changed = list(c("algorithm", "rasrank", "eants"), "beta", "ants", c("dlb", "nnls"))) check_generate_ablation(2L, 1L, ' algorithm alpha beta ants q0 rasrank eants dlb nnls localsearch ras 1.0 0 5 NA 2 2 1 5 1 ras 0.5 0 5 NA 5 2 1 5 1 ', changed = list("alpha", c("rasrank"))) check_generate_ablation(3L, 1L, ' algorithm alpha beta ants q0 rasrank eants dlb nnls localsearch ras 1.0 0 4 NA 2 2 1 5 1 ras 0.5 0 5 NA 2 2 1 5 1 ', changed = list("alpha", c("ants"))) check_generate_ablation(4L, 1L, ' algorithm alpha beta ants q0 rasrank eants dlb nnls localsearch acs 1 1 2 0.5 NA NA NA NA 0 acs 0 1 5 0.5 NA NA NA NA 0 acs 0 1 2 0.5 NA NA 1 5 1 ', changed = list("alpha", "ants", c("localsearch", "dlb", "nnls"))) check_generate_ablation(5L, 1L, ' algorithm alpha beta ants q0 rasrank eants dlb nnls localsearch ras 1 1 6 NA 5 5 0 NA 1 mmas 1 0 6 NA NA NA 0 NA 1 mmas 1 1 5 NA NA NA 0 NA 1 mmas 1 1 6 NA NA NA 1 5 1 ', changed = list(c("algorithm", "rasrank", "eants"), "beta", "ants", c("dlb", "nnls"))) }) test_that("--help", { expect_output(ablation_cmdline("--help")) }) outfile <- withr::local_tempfile(pattern = "log-ablation", fileext = ".Rdata") logfile <- withr::local_tempfile(pattern = "irace", fileext = ".Rdata") parameters <- parametersNew( param_cat("cat", values = c("0", "1", "2", "3", "4")), param_real("real", lower = 0.0, upper=1.0), param_int("int", lower = 100, upper = 500), param_cat("bool", values = c("0", "1"))) default <- data.frame(cat="4", real=1.0, int=500L, bool = "1") target_runner <- function(experiment, scenario) { conf <- experiment$configuration instance <- experiment$instance seed <- experiment$seed k <- if (as.logical(as.integer(conf[["bool"]]))) 1000 else 100 list(cost = instance + 1/seed + k * (conf[["int"]] + as.integer(conf[["cat"]]) + (conf[["real"]]-0.5)^2)) } check_log <- function(log) { instances_log <- log$state$instances_log instances_log[, instance:=.I] experiment_log <- log$state$experiment_log[instances_log, on="instance"] experiment_log[["instance_value"]] <- log$scenario$instances[experiment_log[["instanceID"]]] experiment_log <- experiment_log[log$allConfigurations, on = c(configuration=".ID.")] experiments <- log$experiments experiments = data.table( instance = rep(seq_len(nrow(experiments)), ncol(experiments)), configuration = rep(seq_len(ncol(experiments)), each = nrow(experiments)), cost3 = c(experiments) ) experiments <- experiments[!is.na(experiments$cost3),] experiment_log <- experiment_log[experiments, on=.NATURAL] experiment_log[, cost2:=instance_value + 1/seed + fifelse(as.logical(as.integer(bool)), 1000, 100) * (int + as.integer(cat) + (real - 0.5)^2)] if ("bound" %in% colnames(experiment_log)) { experiment_log[, cost2 := pmin.int(cost2, bound)] experiment_log[, cost3 := pmin.int(cost3, bound)] } expect_equal(experiment_log[["cost"]], experiment_log[["cost2"]]) expect_equal(experiment_log[["cost"]], experiment_log[["cost3"]]) } src_file <- withr::local_tempfile(pattern="src", fileext=".txt", lines=c("cat real int bool", "4 1.0 500 1")) target_file <- withr::local_tempfile(pattern="target", fileext=".txt", lines=c("cat real int bool", "0 0.0 100 0")) test_that("ablation maxTime", { target_runner_time <- function(experiment, scenario) list(cost = target_runner(experiment, scenario)$cost, time = runif(1, min=0.1, max=1)) scenario <- list(targetRunner = target_runner_time, instances = seq(1000, 10000, 1000), seed = 42, maxTime = 1000, initConfigurations = default, logFile = logfile, parameters = parameters) scenario <- checkScenario (scenario) irace(scenario = scenario) check_log(read_logfile(logfile)) res <- ablation(logfile, ablationLogFile = outfile) check_log(res) expect_true(res$complete) res <- ablation(logfile, ablationLogFile = outfile, type = "racing") check_log(res) expect_true(res$complete) res <- ablation(logfile, ablationLogFile = outfile, src = src_file, target = target_file) check_log(res) expect_true(res$complete) }) test_that("ablation capping", { target_runner_capping <- function(experiment, scenario) { cost <- min(experiment$bound, target_runner(experiment, scenario)$cost) list(cost = cost, time = cost) } boundMax <- 1000 + (1000 * 505) scenario <- list(targetRunner = target_runner_capping, instances = seq(1000, 10000, 1000), seed = 42, maxTime = 100 * boundMax, boundMax = boundMax, initConfigurations = default, logFile = logfile, parameters = parameters) scenario <- checkScenario(scenario) expect_warning(irace(scenario = scenario), "is too large") check_log(read_logfile(logfile)) res <- ablation(logfile, ablationLogFile = outfile) check_log(res) expect_true(res$complete) res <- ablation(logfile, ablationLogFile = outfile, type = "racing") check_log(res) expect_true(res$complete) }) test_that("ablation maxExperiments", { scenario <- list(targetRunner = target_runner, instances = seq(1000, 10000, 1000), maxExperiments = 1000, seed = 42, initConfigurations = default, logFile = logfile, parameters = parameters) scenario <- checkScenario (scenario) irace(scenario = scenario) check_log(read_logfile(logfile)) res <- ablation(logfile, ablationLogFile = outfile) check_log(res) expect_true(res$complete) res <- ablation(logfile, ablationLogFile = outfile, type = "racing") check_log(res) expect_true(res$complete) res <- ablation(logfile, ablationLogFile = outfile, src = src_file, target = target_file) check_log(res) expect_true(res$complete) plotfile <- withr::local_tempfile(pattern = "ablation", fileext = ".pdf") res <- ablation_cmdline(paste0("--log-file=", logfile, " -o ", outfile, " -p ", plotfile)) check_log(res) expect_true(res$complete) }) }) # withr::with_output_sink() irace/tests/testthat/dependencies.txt0000644000176200001440000000057114735305474017545 0ustar liggesusersparam1 "--param1 " i (1, "real") param2 "--param2 " i ("real", 10) mode "--" c ("x1" ,"x2", "x3") real "--paramreal=" r (1.5, 7.5) mutation "--mutation=" o ("none", "very low", "low", "medium", "high", "very high", "all") #unused "-u " c (1, 2, 10, 20) irace/tests/testthat/test-targeteval.R0000644000176200001440000000720614736526233017615 0ustar liggesuserswithr::with_output_sink("test-targeteval.Rout", { target_evaluator <- function(experiment, num_configurations, all_conf_id, scenario, target_runner_call) { withr::local_seed(experiment$seed) list(cost = runif(1), call = deparse1(experiment)) } parameters <- readParameters(text = ' algorithm "--" c (as,mmas,eas,ras,acs) ') test_that("target_evaluator", { target_runner <- function(experiment, scenario) { list(call = deparse1(experiment)) } seed <- sample.int(min(2147483647L, .Machine$integer.max), size = 1L, replace = TRUE) instances <- 1:10 limit <- 58L maxExperiments <- 200 logFile <- withr::local_tempfile(pattern = "irace", fileext = ".Rdata") scenario <- checkScenario(list( targetRunner = target_runner, targetEvaluator = target_evaluator, maxExperiments = maxExperiments, instances = instances, logFile = logFile, seed = seed, quiet = TRUE, parameters = parameters)) expect_true(irace:::checkTargetFiles(scenario = scenario)) scenario <- checkScenario(list( targetRunner = target_runner, targetEvaluator = target_evaluator, maxExperiments = maxExperiments, instances = instances, logFile = logFile, seed = seed, quiet = TRUE, parameters = parameters)) expect_silent(confs <- irace(scenario = scenario)) expect_gt(nrow(confs), 0L) scenario$targetRunner <- wrap_target_runner_error(target_runner, limit) # Otherwise, the tests are too fast. with_mocked_bindings({ expect_error(irace(scenario = scenario), "== irace == The cost returned by targetRunner is not numeric") }, .irace_minimum_saving_time = 0 ) logFile_new <- withr::local_tempfile(pattern = "irace", fileext = ".Rdata") scenario <- modifyList(scenario, list( targetRunner = wrap_target_runner_counter(target_runner), recoveryFile = logFile, seed = NA, quiet = FALSE, logFile = logFile_new)) recover_confs <- irace(scenario = scenario) expect_identical(confs, recover_confs) expect_equal(environment(scenario$targetRunner)$counter, maxExperiments - limit + 1L) }) test_that("target_evaluator maxTime", { target_runner <- function(experiment, scenario) { withr::local_seed(experiment$seed) list(time = min(experiment$bound, as.integer(1 + 10*runif(1)))) } seed <- sample.int(min(2147483647L, .Machine$integer.max), size = 1L, replace = TRUE) instances <- 1:10 limit <- 100L logFile <- withr::local_tempfile(pattern = "irace", fileext = ".Rdata") scenario <- checkScenario(list( targetRunner = target_runner, targetEvaluator = target_evaluator, maxTime = 2000, boundMax = 10, instances = instances, logFile = logFile, seed = seed, quiet = TRUE, parameters = parameters)) expect_true(scenario$capping) expect_silent(confs <- irace(scenario = scenario)) expect_gt(nrow(confs), 0L) scenario$targetRunner <- wrap_target_runner_error(target_runner, limit) # Otherwise, the tests are too fast. with_mocked_bindings({ expect_error(irace(scenario = scenario), "== irace == The cost returned by targetRunner is not numeric") }, .irace_minimum_saving_time = 0 ) logFile_new <- withr::local_tempfile(pattern = "irace", fileext = ".Rdata") scenario <- modifyList(scenario, list( targetRunner = wrap_target_runner_counter(target_runner), recoveryFile = logFile, seed = NA, quiet = FALSE, logFile = logFile_new)) recover_confs <- irace(scenario = scenario) expect_identical(confs, recover_confs) expect_lt(environment(scenario$targetRunner)$counter, sum(!is.na(read_logfile(logFile_new)$experiments)) - 10L) }) }) # withr::with_output_sink() irace/tests/testthat/dummy_wrapper.py0000755000176200001440000000435314735305474017630 0ustar liggesusers#!/usr/bin/env python3 # encoding: utf-8 ''' DummyWrapper -- Dummy wrapper for unit testing This requires the installation of https://github.com/automl/GenericWrapper4AC @author: Manuel López-Ibáñez @copyright: 2018 @license: BSD @contact: manuel.lopez-ibanez@manchester.ac.uk ''' import json import traceback from genericWrapper4AC.generic_wrapper import AbstractWrapper class DummyWrapper(AbstractWrapper): ''' Dummy wrapper for unit testing ''' def __init__(self): AbstractWrapper.__init__(self) self._return_value = None def get_command_line_args(self, runargs, config): ''' ''' runargs.update(config) return "echo '" + json.dumps(runargs) + "'" def process_results(self, filepointer, exit_code): ''' Parse a results file to extract the run's status (SUCCESS/CRASHED/etc) and other optional results. Args: filepointer: a pointer to the file containing the solver execution standard out. exit_code : exit code of target algorithm ''' statuses = ['SUCCESS', 'TIMEOUT', 'CRASHED', 'ABORT'] # If something fails, we a serious problem output = dict(status='ABORT') for line in filepointer: try: argmap = json.loads(str(line.decode('UTF-8')).replace("\n","")) ins = argmap["instance"] seed = argmap["seed"] cost = float(argmap["-cost"]) runtime = float(argmap["-runtime"]) status = statuses[seed % len(statuses)] if ins == "cost": output = dict(status = status, cost = cost) elif ins == "time": cutoff = float(argmap["cutoff"]) output = dict(status=status, runtime=min(cutoff,runtime)) elif ins == "cost+time": cutoff = float(argmap["cutoff"]) output = dict(status=status, cost=cost, runtime=min(cutoff,runtime)) except ValueError: traceback.print_exc() pass return output if __name__ == "__main__": wrapper = DummyWrapper() wrapper.main() irace/tests/testthat/bad_scenario.txt0000644000176200001440000000004214736526233017520 0ustar liggesusersparametersFile = "parameters.txt" irace/tests/testthat/test-multi_irace.R0000644000176200001440000001167514736526233017761 0ustar liggesuserswithr::with_output_sink("test-multi_irace.Rout", { # FIXME: Use temporary files and directories. make.target.runner <- function(parameters) { function(experiment, scenario) { cost <- max(1, abs(rnorm(1, mean=sum(unlist(experiment$configuration[parameters]))))) list(cost = cost, call = toString(experiment)) } } make.parameters.table <- function(parameters) readParameters(text = sprintf('%s "" r (0, 10)', parameters)) make.scenario <- function(targetRunner, maxExperiments = 1000) { list(targetRunner = targetRunner, maxExperiments = maxExperiments, instances = rnorm(200, mean = 0.9, sd = 0.02)) } target.runner.1 <- make.target.runner(c("x1", "x2")) target.runner.2 <- make.target.runner(c("x2", "x3")) target.runner.3 <- make.target.runner(c("x3", "x1")) parameters.table.1 <- make.parameters.table(c("x1", "x2")) parameters.table.2 <- make.parameters.table(c("x2", "x3")) parameters.table.3 <- make.parameters.table(c("x3", "x1")) check.default.logFiles <- function(n) { dir <- sprintf("run_%02d", 1:n) expect_equal(dir.exists(dir), rep_len(TRUE, n)) expect_equal(file.exists(file.path(dir, "irace.Rdata")), rep_len(TRUE, n)) } test_that("multiple scenarios, multiple parameters", { skip_on_cran() # Reproducible results generate_set_seed() scenarios <- lapply(list(target.runner.1, target.runner.2, target.runner.3), make.scenario) parameters <- list(parameters.table.1, parameters.table.2, parameters.table.3) runs <- multi_irace(scenarios, parameters) expect_length(runs, 3) check.default.logFiles(3) }) test_that("one scenario, multiple parameters", { skip_on_cran() # Reproducible results generate_set_seed() scenarios <- list(make.scenario(target.runner.1)) dummy_parameters_names <- c("dummy1", "dummy2", "dummy3") parameter_names <- lapply(dummy_parameters_names, function(dummy) c("x1", "x2", dummy)) parameters <- lapply(parameter_names, make.parameters.table) runs <- multi_irace(scenarios, parameters) expect_length(runs, 3) check.default.logFiles(3) }) test_that("multiple scenarios, one parameters", { skip_on_cran() # Reproducible results generate_set_seed() scenarios <- lapply(list(100, 500, 1000), function(maxExperiments) make.scenario(target.runner.1, maxExperiments) ) parameters <- list(parameters.table.1) runs <- multi_irace(scenarios, parameters) expect_length(runs, 3) check.default.logFiles(3) }) test_that("one scenario, one parameters, multiple n", { skip_on_cran() # Reproducible results generate_set_seed() scenarios <- list(make.scenario(target.runner.1)) parameters <- list(parameters.table.1) runs <- multi_irace(scenarios, parameters, n = 3) expect_length(runs, 3) check.default.logFiles(3) }) test_that("multiple scenarios, multiple parameters, multiple n", { skip_on_cran() # Reproducible results generate_set_seed() scenarios <- lapply(list(target.runner.1, target.runner.2, target.runner.3), make.scenario) parameters <- list(parameters.table.1, parameters.table.2, parameters.table.3) runs <- multi_irace(scenarios, parameters, n = 3) expect_length(runs, 9) check.default.logFiles(9) }) test_that("logFile not in execDir", { skip_on_cran() # Reproducible results generate_set_seed() execDir <- path_rel2abs("./multi_irace_execDir") logFileDir <- path_rel2abs("./multi_irace_logFileDir") dir.create(execDir, showWarnings = FALSE) dir.create(logFileDir, showWarnings = FALSE) scenarios <- lapply(list(target.runner.1, target.runner.2, target.runner.3), make.scenario) for (i in seq_along(scenarios)) { scenarios[[i]]$logFile <- file.path(logFileDir, "irace.Rdata") scenarios[[i]]$execDir <- execDir } parameters <- list(parameters.table.1, parameters.table.2, parameters.table.3) expect_error(multi_irace(scenarios, parameters)) # expect_length(runs, 3) # for (i in 1:3) { # logFile <- file.path(logFileDir, sprintf("irace_%02d.Rdata", i)) # expect_true(file.exists(logFile)) # } }) test_that("global seed", { skip_on_cran() scenarios <- lapply(list(target.runner.1, target.runner.2, target.runner.3), make.scenario) parameters <- list(parameters.table.1, parameters.table.2, parameters.table.3) runs.1 <- multi_irace(scenarios, parameters, global_seed = 42) runs.2 <- multi_irace(scenarios, parameters, global_seed = 42) expect_equal(runs.1, runs.2) }) test_that("sequential and parallel identical", { skip_on_cran() skip_on_os("windows") ncores <- test_irace_detectCores() skip_if(ncores <= 1L, message = "This test only makes sense if multiple cores are available") scenarios <- lapply(list(target.runner.1, target.runner.2, target.runner.3), make.scenario) parameters <- list(parameters.table.1, parameters.table.2, parameters.table.3) runs.sequential <- multi_irace(scenarios, parameters, global_seed = 42) runs.parallel <- multi_irace(scenarios, parameters, global_seed = 42, parallel = ncores) expect_equal(runs.sequential, runs.parallel) }) }) # withr::with_output_sink() irace/tests/testthat/helper-common.R0000644000176200001440000001355714745735066017263 0ustar liggesusers# This file is loaded automatically by testthat. generate_set_seed <- function() { seed <- sample.int(min(2147483647L, .Machine$integer.max), size = 1L, replace = TRUE) cat("Seed: ", seed, "\n") set.seed(seed) } test_irace_detectCores <- function() { if (!identical(Sys.getenv("NOT_CRAN"), "true")) return(1L) # FIXME: covr takes a very long time on github actions otherwise. if (identical(Sys.getenv("COVR_COVERAGE"), "true")) return(1L) x <- Sys.getenv("_R_CHECK_LIMIT_CORES_", "") if (nzchar(x) && x == "TRUE") return(2L) parallel::detectCores() } skip_on_coverage <- function() { if (identical(Sys.getenv("COVR_COVERAGE"), "true")) skip("Not run during coverage.") else invisible() } system_os_is_windows <- function() .Platform$OS.type == "windows" expect_valid_configurations <- function(configurations, parameters) { configurations <- irace:::removeConfigurationsMetaData(as.data.frame(configurations)) output <- capture.output(print(configurations, width=5000L, row.names = FALSE)) output <- paste0(output, "\n", collapse="") confs2 <- readConfigurationsFile(text=output, parameters=parameters) expect_equal(configurations, confs2) } ## Functions ########################################################## f_ackley <- function (x,y, nsize = 0.01) { # Transformation of parameter values # from [0,1] to [vmin,vmax] vmin <- -5 vmax <- 5 x <- (x*(vmax-vmin)) + vmin y <- (y*(vmax-vmin)) + vmin a <- -20 * exp (-0.2 * sqrt(0.5 * (x^2 + y^2))) b <- exp(0.5 * (cos(2*pi*x) + cos(2*pi*y))) f <- a - b + exp(1) + 20 # Simulating stochasticity noise <- runif(1, min = (1-nsize), max = (1+nsize)) f <- f*noise # Transform result from [fmin,fmax] # to [0,100] fmin <- 0 fmax <- 15*(1+nsize) ((f - fmin) / (fmax-fmin)) * (100-0) + 0 } f_goldestein_price <- function (x,y, nsize = 0.01) { # Transformation of parameter values # from [0,1] to [vmin,vmax] vmin <- -2 vmax <- 2 x <- (x*(vmax-vmin)) + vmin y <- (y*(vmax-vmin)) + vmin a <- 1 + ((x + y + 1)^2) * (19 - 14*x + 3*x^2 - 14*y + 6*x*y + 3*y^2) b <- 30 + ((2*x - 3*y)^2) * (18 - 32*x + 12*x^2 + 48*y - 36*x*y + 27*y^2) f <- a*b # Simulating stochasticity noise <- runif(1, min = (1-nsize), max = (1+nsize)) f <- f*noise # Transform result from [fmin,fmax] # to [0,100] fmin <- 0 fmax <- 1000000*(1+nsize) ((f - fmin) / (fmax-fmin)) * (100-0) + 0 } f_matyas <- function (x,y, nsize = 0.01) { # Trasfomation of parameter values # from [0,1] to [vmin,vmax] vmin <- -10 vmax <- 10 x <- (x*(vmax-vmin)) + vmin y <- (y*(vmax-vmin)) + vmin f <- 0.26 * (x^2 + y^2) - (0.48*x*y) # Simulating stochasticity noise <- runif(1, min = (1-nsize), max = (1+nsize)) f <- f*noise # Transform result from [fmin,fmax] # to [0,100] fmin <- 0 fmax <- 100*(1+nsize) ((f - fmin) / (fmax-fmin)) * (100-0) + 0 } f_himmelblau <- function (x,y, nsize = 0.01) { # Trasfomation of parameter values # from [0,1] to [vmin,vmax] vmin <- -5 vmax <- 5 x <- (x*(vmax-vmin)) + vmin y <- (y*(vmax-vmin)) + vmin f <- (x^2 + y - 11)^2 + (x + y^2 - 7)^2 # Simulating stochasticity noise <- runif(1, min = (1-nsize), max = (1+nsize)) f <- f*noise # Transform result from [fmin,fmax] # to [0,100] fmin <- 0 fmax <- 2000*(1+nsize) ((f - fmin) / (fmax-fmin)) * (100-0) + 0 } target_runner_capping_xy <- function(experiment, scenario) { configuration <- experiment$configuration instance <- experiment$instance bound <- experiment$bound x <- configuration[["x"]] y <- configuration[["y"]] value <- switch(instance, ackley = f_ackley(x, y), goldestein = f_goldestein_price(x, y), matyas = f_matyas(x, y), himmelblau = f_himmelblau(x, y)) # Simulate execution bound list(cost = value, time=min(value + 0.1, bound)) } irace_capping_xy <- function(..., targetRunner = force(target_runner_capping_xy), parallel = test_irace_detectCores()) { # Silence Error in `save(iraceResults, file = logfile, version = 3L)`: (converted from warning) 'package:irace' may not be available when loading # See https://github.com/r-lib/testthat/issues/2044 if (!is.null(attr(environment(targetRunner), "name", exact=TRUE))) { environment(targetRunner) <- globalenv() } args <- list(...) parameters_table <- ' x "" r (0, 1.00) y "" r (0, 1.00) reject "" c (0,1)' parameters <- readParameters(text = parameters_table) logFile <- withr::local_tempfile(fileext=".Rdata") scenario <- list(instances = c("ackley", "goldestein", "matyas", "himmelblau"), targetRunner = targetRunner, capping = TRUE, boundMax = 80, testType = "t-test", logFile = logFile, parallel = parallel, parameters = parameters) scenario <- modifyList(scenario, args) scenario <- checkScenario (scenario) expect_true(irace:::checkTargetFiles(scenario = scenario)) confs <- irace(scenario = scenario) best_conf <- getFinalElites(scenario$logFile, n = 1L, drop.metadata = TRUE) expect_identical(removeConfigurationsMetaData(confs[1L, , drop = FALSE]), best_conf) } # Useful for testing recovery. wrap_target_runner_error <- function(target_runner, limit) { counter <- force(limit) target_runner <- force(target_runner) fun <- function(experiment, scenario) { counter <<- counter - 1L if (counter <= 0L) return(list(cost=NA)) target_runner(experiment, scenario) } parent.env(environment(fun)) <- globalenv() fun } wrap_target_runner_counter <- function(target_runner) { counter <- 0L target_runner <- force(target_runner) fun <- function(experiment, scenario) { counter <<- counter + 1L target_runner(experiment, scenario) } parent.env(environment(fun)) <- globalenv() fun } irace/tests/testthat/test-dependencies.R0000644000176200001440000001071714736526233020106 0ustar liggesuserswithr::with_output_sink("test-dependencies.Rout", { test_that("param depend error checking", { expect_error_readParameters <- function(text, error) expect_error(readParameters(text=text), error) expect_error_readParameters(' p1 "" r (0, 1) p2 "" r (p3, 1) ', "parameter 'p2' is not valid: 'p3' cannot be found") expect_error_readParameters(' p1 "" c (0, 1) p2 "" r (1, p1) ', "parameter 'p2' depends on non-numerical parameters") expect_error_readParameters(' p1 "" r (0, 1) p2 "" r (1, foobar(p1)) ', "parameter 'p2' uses function") expect_error_readParameters(' p1 "" r (0, p3) p2 "" r (1, p1) p3 "" r (p2, 1) ', "Cycle detected") expect_error_readParameters(' p1 "" r (0, 1) p2 "" i (0.1, p1) ', "values must be integers") }) checkConditionalAndDependency <- function(configuration, parameters) { for (param in parameters$get_ordered()) { p <- param[["name"]] if (!irace:::conditionsSatisfied(param[["condition"]], configuration)) { expect(is.na(configuration[[p]]), paste0("Conditional parameter '", p, "' is not active but it has a value '", configuration[[p]], "' assigned.")) } else if (param[["is_dependent"]]) { bounds <- irace:::getDependentBound(param, configuration) if (anyNA(bounds)) { expect(is.na(configuration[[p]]), paste0("Dependent parameter '", p, "' has a value '", configuration[[p]], "' but it should be inactive.")) expect_true(anyNA(configuration[parameters$depends[[p]]])) } else { expect (configuration[[p]] >= bounds[1], paste0("Parameter '", p, "=", configuration[[p]], "' does not comply with dependency: ", parameters$depends[[p]], " and lower bound: ", bounds[1])) expect (configuration[[p]] <= bounds[2], paste0("Parameter '", p, " = ", configuration[[p]], "' does not comply with dependency: ", parameters$depends[[p]], " and upper bound: ", bounds[2])) } } } } test_that("test inactive dependent", { parameters <- readParameters(text=' p1 "" r (0,1) p2 "" r (0, p1) | p1 < 0.5 p3 "" r (0, p2) ', digits = 2) confs <- irace:::sampleUniform(parameters, 50) confs <- as.data.frame(confs) for (i in seq_len(nrow(confs))) { checkConditionalAndDependency(confs[i,], parameters) } expect_error(readConfigurationsFile(parameters = parameters, text = ' p1 p2 p3 0.4 0.4 0.4 0.4 0.5 0.4 '), "Configuration number 2 is invalid because the value") expect_error(readConfigurationsFile(parameters = parameters, text = ' p1 p2 p3 0.4 0.3 0.2 0.4 0.3 0.4 '), "Configuration number 2 is invalid because the value") readConfigurationsFile(parameters = parameters, text = ' p1 p2 p3 0.4 0.3 0.2 0.5 NA NA 0.4 0.4 0.4 ') }) test_checkDependencies <- function(parameterFile, ...) { args <- list(...) target_runner <- function(experiment, scenario) { configuration <- experiment$configuration tmax <- configuration[["real"]] stopifnot(is.numeric(tmax)) if (configuration[["mode"]] %in% c("x1", "x2")) temp <- configuration[["param1"]] else temp <- 1 stopifnot(is.numeric(temp)) time <- max(1, abs(rnorm(1, mean=(tmax+temp)/10))) list(cost = time, time = time, call = toString(experiment)) } weights <- rnorm(200, mean = 0.9, sd = 0.02) parameters <- readParameters(parameterFile) scenario <- list(targetRunner = target_runner, instances = weights, seed = 1234567, maxExperiments = 200, parameters = parameters) scenario <- modifyList(scenario, args) scenario <- checkScenario (scenario) nconf <- 100 conf <- irace:::sampleUniform(parameters, nconf) expect_equal(nconf, nrow(conf)) conf$.ID. <- seq_len(nconf) conf <- as.data.frame(conf) for (i in seq_len(nconf)) checkConditionalAndDependency(conf[i,], parameters) model <- irace:::initialiseModel(parameters, conf) conf2 <- irace:::sampleModel(parameters, conf, model, nconf) conf2 <- as.data.frame(conf2) for (i in seq_len(nconf)) checkConditionalAndDependency(conf2[i,], parameters) confs <- irace(scenario = scenario) for (i in seq_len(nrow(confs))) { checkConditionalAndDependency(confs[i,], parameters) } } test_that("checkDependencies", { test_checkDependencies(parameterFile="dependencies.txt") }) test_that("checkDependencies2", { test_checkDependencies(parameterFile="dependencies2.txt") }) }) # withr::with_output_sink() irace/tests/testthat/test-bad_scenario.R0000644000176200001440000000067714736526233020075 0ustar liggesuserswithr::with_output_sink("test-bad_scenario.Rout", { test_that("bad scenario: unknown variables", { skip_on_cran() bad_scenario <- test_path("bad_scenario.txt") expect_error(irace_cmdline(paste0("--scenario ", bad_scenario)), "unknown variables") good_scenario <- test_path("good_scenario.txt") expect_error(irace_cmdline(paste0("--scenario ", good_scenario, " --unknown")), "Unknown command-line options: --unknown") }) }) irace/tests/testthat/test-maxTime.R0000644000176200001440000000326314736526233017062 0ustar liggesuserswithr::with_output_sink("test-maxTime.Rout", { target_runner <- function(experiment, scenario) { configuration <- experiment$configuration tmax <- configuration[["tmax"]] temp <- configuration[["temp"]] time <- max(1, abs(rnorm(1, mean=(tmax+temp)/10))) list(cost = time, time = time) } time_irace <- function(...) { args <- list(...) parameters <- readParameters(text = ' tmax "" i (1, 50) temp "" r (0, 10) dummy "" c ("dummy") ') scenario <- list(targetRunner = target_runner, instances = 1:10, testInstances = 11:20, seed = 1234567, parameters = parameters) scenario <- modifyList(scenario, args) scenario <- checkScenario (scenario) expect_true(irace:::checkTargetFiles(scenario = scenario)) confs <- irace(scenario = scenario) final_ids <- sort(as.character(confs$.ID.[1:scenario$testNbElites])) expect_gt(nrow(confs), 0L) testing_fromlog(scenario$logFile) iraceResults <- read_logfile(scenario$logFile) if (scenario$testIterationElites) { # FIXME: We could test here that the correct configurations are tested. expect_gte(ncol(iraceResults$testing$experiments), scenario$testNbElites) } else { test_ids <- sort(colnames(iraceResults$testing$experiments)) expect_equal(final_ids, test_ids) } confs } test_that("maxTime=500 testNbElites=2 testIterationElites=FALSE", { generate_set_seed() time_irace(maxTime = 500, testNbElites=2) }) test_that("maxTime=1111 testNbElites=3 testIterationElites=TRUE", { skip_on_cran() generate_set_seed() time_irace(maxTime = 1111, testNbElites=3, testIterationElites=TRUE) }) }) # withr::with_output_sink() irace/tests/testthat/test-bug-44.R0000644000176200001440000000064614736526233016462 0ustar liggesusers# https://github.com/MLopez-Ibanez/irace/issues/44 withr::with_output_sink("test-bug-44.Rout", { test_that("bug 44", { readParameters(text='a "a" r (0.01, 0.99)', digits=2) readParameters(text='a "a" r (0, 1)', digits=1) expect_error(readParameters(text='a "a" r (0.01, 0.99)', digits=1), "must be representable") expect_error(readParameters(text='a "a" r,log (1e-8, 1)', digits=4), "must be representable") }) }) irace/tests/testthat/test-read_pcs_file.R0000644000176200001440000000251514736526233020234 0ustar liggesuserswithr::with_output_sink("test-read_pcs_file.Rout", { test_that("read_pcs_file", { pcs_table <- ' # name domain algorithm {as,mmas,eas,ras,acs}[as] localsearch {0, 1, 2, 3}[0] alpha [0.00, 5.00][1] beta [0.00, 10.00][1] rho [0.01, 1.00][0.95]l ants [1, 100][10]il q0 [0.0, 1.0][0] rasrank [1, 100][1]i elitistants [1, 750][1]i nnls [5, 50][5]i dlb {0, 1}[1] Conditionals: q0 | algorithm in {acs} rasrank | algorithm in {ras} elitistants | algorithm in {eas} nnls | localsearch in {1,2,3} dlb | localsearch in {1,2,3} {alpha=0, beta= 0.0} {ants=1,algorithm=eas} {ants=1,algorithm="ras"}' parameters_table <- ' # name domain algorithm "algorithm" c (as,mmas,eas,ras,acs) localsearch "localsearch" c (0, 1, 2, 3) alpha "alpha" r (0.00, 5.00) beta "beta" r (0.00, 10.00) rho "rho" r,log (0.01, 1.00) ants "ants" i,log (1, 100) q0 "q0" r (0.0, 1.0) | algorithm == "acs" rasrank "rasrank" i (1, 100) | algorithm == "ras" elitistants "elitistants" i (1, 750) | algorithm == "eas" nnls "nnls" i (5, 50) | localsearch %in% c("1","2","3") dlb "dlb" c (0, 1) | localsearch %in% c("1","2","3") [forbidden] (alpha == 0) & (beta == 0.0) (ants == 1) & (algorithm == "eas") (ants == 1) & (algorithm == "ras") ' expect_equal(parameters_table, read_pcs_file(text=pcs_table)) }) }) irace/tests/testthat/test-GenericWrapper4AC.R0000644000176200001440000001467214736526233020671 0ustar liggesusers# This requires the installation of https://github.com/automl/GenericWrapper4AC test_that("GenericWrapper4AC", { skip_on_cran() skip_on_coverage() skip_on_ci() skip_on_travis() skip_on_os("mac") # FIXME: how to check that a python package is installed? parameters <- readParameters(text = ' cost "cost" r (0.1, 1.00) runtime "runtime" r (0.1, 1.00)', digits = 15L) configurations <- data.frame(.ID. = 1L, cost = 0.5, runtime = 0.8) instances = c("time", "cost", "cost+time") names(instances) = instances dummy_wrapper <- test_path("dummy_wrapper.py") scenario <- list(targetRunner = dummy_wrapper, instances = instances, maxExperiments = 1000, aclib = TRUE, parameters = parameters) scenario <- checkScenario (scenario) experiments <- irace:::createExperimentList(configurations, parameters = parameters, instances = scenario$instances, instances_ID = rep("cost", 2), seeds = 0:1, bounds = NULL) race_state <- irace:::RaceState$new(scenario) output <- irace:::execute_experiments(race_state, experiments, scenario) expect_equal(output[[1]]$status, "SUCCESS") expect_equal(output[[1]]$cost, 0.5) expect_true(is.null(output[[1]]$error)) expect_equal(output[[2]]$status, "TIMEOUT") expect_equal(output[[2]]$cost, 0.5) expect_true(is.null(output[[2]]$error)) experiments <- irace:::createExperimentList(configurations, parameters = parameters, instances = scenario$instances, instances_ID = rep("cost", 2), seeds = 2, bounds = NULL) expect_error(irace:::execute_experiments(race_state, experiments, scenario), "CRASHED") experiments <- irace:::createExperimentList(configurations, parameters = parameters, instances = scenario$instances, instances_ID = rep("cost", 2), seeds = 3, bounds = NULL) expect_error(irace:::execute_experiments(race_state, experiments, scenario), "ABORT") scenario <- modifyList(scenario, list(capping = TRUE, boundMax = 1, maxTime = 10, maxExperiments = NULL)) scenario <- checkScenario (scenario) experiments <- irace:::createExperimentList(configurations, parameters = parameters, instances = scenario$instances, instances_ID = rep("time", 2), seeds = 0:1, bounds = scenario$boundMax) output <- irace:::execute_experiments(race_state, experiments, scenario) expect_equal(output[[1]]$status, "SUCCESS") expect_equal(output[[1]]$cost, 0.8) expect_equal(output[[1]]$time, 0.8) expect_match(output[[1]]$call, "--cutoff") expect_true(is.null(output[[1]]$error)) expect_equal(output[[2]]$status, "TIMEOUT") expect_equal(output[[2]]$cost, 0.8) expect_equal(output[[2]]$time, 0.8) expect_match(output[[2]]$call, "--cutoff") expect_true(is.null(output[[2]]$error)) experiments <- irace:::createExperimentList(configurations, parameters = parameters, instances = scenario$instances, instances_ID = rep("time", 2), seeds = 2, bounds = scenario$boundMax) expect_error(irace:::execute_experiments(race_state, experiments, scenario), "CRASHED") experiments <- irace:::createExperimentList(configurations, parameters = parameters, instances = scenario$instances, instances_ID = rep("time", 2), seeds = 3, bounds = scenario$boundMax) expect_error(irace:::execute_experiments(race_state, experiments, scenario), "ABORT") experiments <- irace:::createExperimentList(configurations, parameters = parameters, instances = scenario$instances, instances_ID = rep("cost+time", 2), seeds = 0:1, bounds = scenario$boundMax) output <- irace:::execute_experiments(race_state, experiments, scenario) expect_equal(output[[1]]$status, "SUCCESS") expect_equal(output[[1]]$cost, 0.5) expect_equal(output[[1]]$time, 0.8) expect_match(output[[1]]$call, "--cutoff") expect_true(is.null(output[[1]]$error)) expect_equal(output[[2]]$status, "TIMEOUT") expect_equal(output[[2]]$cost, 0.5) expect_equal(output[[2]]$time, 0.8) expect_match(output[[2]]$call, "--cutoff") expect_true(is.null(output[[2]]$error)) experiments <- irace:::createExperimentList(configurations, parameters = parameters, instances = scenario$instances, instances_ID = rep("cost+time", 2), seeds = 2, bounds = scenario$boundMax) expect_error(irace:::execute_experiments(race_state, experiments, scenario), "CRASHED") experiments <- irace:::createExperimentList(configurations, parameters = parameters, instances = scenario$instances, instances_ID = rep("cost+time", 2), seeds = 3, bounds = scenario$boundMax) expect_error(irace:::execute_experiments(race_state, experiments, scenario), "ABORT") }) irace/tests/testthat/test-bug71.R0000644000176200001440000000113614736526233016400 0ustar liggesuserswithr::with_output_sink("test-bug71.Rout", { test_that("bug71.evaluator", { target.runner <- function(experiment, scenario) list(cost = runif(1), call = toString(experiment)) parameters <- readParameters(text = ' algorithm "--" c (as,mmas,eas,ras,acs) ') scenario <- checkScenario(list( targetRunner = target.runner, maxExperiments = 200, instances = runif(10), initConfigurations = data.frame(algorithm="as"), parameters = parameters)) expect_true(irace:::checkTargetFiles(scenario = scenario)) }) }) # withr::with_output_sink() irace/tests/testthat/parameters.txt0000644000176200001440000000166514736526233017266 0ustar liggesusersparam1 "--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") fixed "--fixed " c ("fixed") #unused "-u " c (1, 2, 10, 20) [forbidden] ## 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 == "x3") real < 4 & mode %in% c("x2", "x3") irace/tests/testthat/test-recovery.R0000644000176200001440000000652714736526233017322 0ustar liggesuserswithr::with_output_sink("test-recovery.Rout", { test_that("recovery works", { target_runner_xy <- function(experiment, scenario) { configuration <- experiment$configuration instance <- experiment$instance x <- configuration[["x"]] y <- configuration[["y"]] value <- switch(instance, ackley = f_ackley(x, y), goldestein = f_goldestein_price(x, y), matyas = f_matyas(x, y), himmelblau = f_himmelblau(x, y)) list(cost = value) } parameters_table <- ' x "" r (0, 1.00) y "" r (0, 1.00) ' parameters <- readParameters(text = parameters_table) logFile <- withr::local_tempfile(pattern = "irace", fileext = ".Rdata") seed <- sample.int(min(2147483647L, .Machine$integer.max), size = 1, replace = TRUE) maxExperiments <- 500L limit <- 100L scenario <- list( instances = c("ackley", "goldestein", "matyas", "himmelblau"), parameters = parameters, targetRunner = target_runner_xy, logFile = logFile, seed = seed, quiet = TRUE, maxExperiments = maxExperiments) expect_silent(confs <- irace(scenario = scenario)) scenario$targetRunner <- wrap_target_runner_error(target_runner_xy, limit) # Otherwise, the tests are too fast. with_mocked_bindings({ expect_error(irace(scenario = scenario), "== irace == The cost returned by targetRunner is not numeric") }, .irace_minimum_saving_time = 0 ) logFile_new <- withr::local_tempfile(pattern = "irace", fileext = ".Rdata") scenario <- modifyList(scenario, list( targetRunner = wrap_target_runner_counter(target_runner_xy), recoveryFile = logFile, seed = NA, quiet = FALSE, logFile = logFile_new)) recover_confs <- irace(scenario = scenario) expect_identical(confs, recover_confs) expect_lt(environment(scenario$targetRunner)$counter, maxExperiments - 50) }) test_that("recovery maxTime", { target_runner <- function(experiment, scenario) { configuration <- experiment$configuration tmax <- configuration[["tmax"]] temp <- configuration[["temp"]] time <- max(1, abs(rnorm(1, mean=(tmax+temp)/10))) list(cost = time, time = time) } parameters <- readParameters(text = ' tmax "" i (1, 50) temp "" r (0, 10) ') logFile <- withr::local_tempfile(pattern = "irace", fileext = ".Rdata") seed <- 1234567 scenario <- list(targetRunner = target_runner, instances = 1:10, seed = seed, maxTime = 500, logFile = logFile, quiet = TRUE, parameters = parameters) expect_silent(confs <- irace(scenario = scenario)) scenario$targetRunner <- wrap_target_runner_error(target_runner, 200L) # Otherwise, the tests are too fast. with_mocked_bindings({ expect_error(irace(scenario = scenario), "== irace == The cost returned by targetRunner is not numeric") }, .irace_minimum_saving_time = 0 ) logFile_new <- withr::local_tempfile(pattern = "irace", fileext = ".Rdata") scenario <- modifyList(scenario, list( targetRunner = wrap_target_runner_counter(target_runner), recoveryFile = logFile, seed = NA, quiet = FALSE, logFile = logFile_new)) recover_confs <- irace(scenario = scenario) expect_identical(confs, recover_confs) expect_lt(environment(scenario$targetRunner)$counter, nrow(read_logfile(logFile_new)$state$experiment_log) - 150L) }) }) # withr::with_output_sink() irace/tests/testthat/test-blocksize.R0000644000176200001440000000660414736526233017445 0ustar liggesuserswithr::with_output_sink("test-blocksize.Rout", { cap_irace <- function(..., targetRunner = force(target_runner_capping_xy)) { # Silence Error in `save(iraceResults, file = logfile, version = 3L)`: (converted from warning) 'package:irace' may not be available when loading # See https://github.com/r-lib/testthat/issues/2044 if (!is.null(attr(environment(targetRunner), "name", exact=TRUE))) { environment(targetRunner) <- globalenv() } args <- list(...) parameters_table <- ' x "" r (0, 1.00) y "" r (0, 1.00)' parameters <- readParameters(text = parameters_table) logFile <- withr::local_tempfile(fileext=".Rdata") scenario <- list(instances = c("ackley", "goldestein", "matyas", "himmelblau"), targetRunner = targetRunner, capping = TRUE, blockSize = 4, boundMax = 80, logFile = logFile, testType = "t-test", parameters = parameters) scenario <- modifyList(scenario, args) scenario <- checkScenario (scenario) confs <- irace(scenario = scenario) best_conf <- getFinalElites(scenario$logFile, n = 1L, drop.metadata = TRUE) expect_identical(removeConfigurationsMetaData(confs[1L, , drop = FALSE]), best_conf) invisible(read_logfile(scenario$logFile)) } target_runner_time <- function(experiment, scenario) { configuration <- experiment$configuration tmax <- configuration[["tmax"]] temp <- configuration[["temp"]] time <- max(1, abs(rnorm(1, mean=(tmax+temp)/10))) list(cost = time, time = time) } time_irace <- function(...) { args <- list(...) parameters <- readParameters(text = ' tmax "" i (-10, 10) temp "" r (0, 10) ') scenario <- list(targetRunner = target_runner_time, instances = c("ackley", "goldestein", "matyas"), blockSize=3, parameters = parameters) scenario <- modifyList(scenario, args) scenario <- checkScenario (scenario) confs <- irace(scenario = scenario) best_conf <- getFinalElites(scenario$logFile, n = 1L, drop.metadata = TRUE) expect_identical(removeConfigurationsMetaData(confs[1L, , drop = FALSE]), best_conf) invisible(read_logfile(scenario$logFile)) } check_blocksize <- function(results) { expect_equal(rowMeans(matrix(get_instanceID_seed_pairs(results)[["instanceID"]],nrow=results$scenario$blockSize)), rowMeans(matrix(seq_along(results$scenario$instances), nrow=results$scenario$blockSize))) expect_equal(sum(colSums(!is.na(results$experiments)) %% results$scenario$blockSize), 0) } test_that("blockSize error", { expect_error(cap_irace(maxExperiments = 1000, blockSize=3), "must be a multiple of 'blockSize") }) test_that("blockSize cap_irace maxExperiments = 1000", { generate_set_seed() expect_warning(check_blocksize(cap_irace(maxExperiments = 1000, debugLevel = 3)), "Assuming 'mu = firstTest * blockSize' because 'mu' cannot be smaller", fixed = TRUE) }) test_that("blockSize maxTime=1000", { generate_set_seed() check_blocksize(time_irace(maxTime = 1000)) }) test_that("blockSize maxTime=1000 elitistNewInstances", { skip_on_cran() generate_set_seed() check_blocksize(time_irace(maxTime = 1000, instances = letters[1:9], elitistNewInstances = 2, elitistLimit = 2)) }) }) # withr::with_output_sink() irace/tests/testthat/test-readconfs.R0000644000176200001440000000513214736526233017417 0ustar liggesuserswithr::with_output_sink("test-readconfs.Rout", { params <- readParameters("parameters.txt") expect_error_readconfs <- function(table, exp) expect_error(irace::readConfigurationsFile(text=table, parameters = params), exp) test_that("no error", { x <- irace::readConfigurationsFile(text=' param1 param2 mode real mutation 5 NA "x2" 4.0 "low" 1 NA "x2" 4.0 "low" NA NA "x3" 4.5 "low"', parameters=params) expect_equal(nrow(x), 3L) expect_equal(ncol(x), 6L) # It adds the fixed column }) test_that("wrong fixed", { expect_error_readconfs(' param1 param2 mode real mutation fixed 5 NA "x2" 4.0 "low" fixed 1 NA "x2" 4.0 "low" fixed NA NA "x3" 4.5 "low" wrong', "is not among the valid values") }) test_that("checkDuplicates", { expect_error_readconfs(' param1 param2 mode real mutation 5 NA "x2" 4.0 "low" 1 NA "x2" 4.0 "low" 5 NA "x2" 4.0 "low" NA NA "x3" 4.5 "low" ', "Duplicated") expect_error_readconfs(' param1 param2 mode real mutation 5 NA "x2" 4.0 "low" 1 NA "x2" 4.0 "low" 5 NA "x2" 4.0 "low" 1 NA "x2" 4.0 "low" ', "Duplicated") }) test_that("parameter values", { expect_error_readconfs(' param1 param2 mode real mutation 5 NA "x2" 4.0 "low" 1 NA "x2" 4.0 "low" 11 NA "x2" 4.0 "low" ', "is not within the valid range") expect_error_readconfs(' param1 param2 mode real mutation 5 NA "x2" 4.0 "low" 1 NA "x2" 4.5001 "low" ', "is not within the valid range") expect_error_readconfs(' param1 param2 mode real mutation 5 NA "x2" 4.0 "low" 1.1 NA "x2" 4.5 "low" ', "is not an integer") expect_error_readconfs(' param1 param2 mode real mutation 5 NA "x2" 4.0 "low" 1 NA "x2" 4.5 "lower" ', "is not among the valid values") expect_error_readconfs(' param1 param2 mode real mutation 5 NA "x3" 4.0 "low" 1 NA "x2" 4.5 "low" ', "is not enabled") }) test_that("parameter names", { expect_error_readconfs(' param1 param0 mode real mutation 5 NA "x2" 4.0 "low" 1 NA "x2" 4.0 "low" ', "do not match") expect_error_readconfs(' param1 mode real mutation 5 "x2" 4.0 "low" 1 "x2" 4.0 "low" ', "are missing") expect_error_readconfs(' param1 param2 mode real mutation param3 5 NA "x2" 4.0 "low" NA 1 NA "x2" 4.0 "low" NA ', "do not match") }) }) # withr::with_output_sink irace/tests/testthat/test-targetRunnerParallel.R0000644000176200001440000000412014736526233021604 0ustar liggesuserswithr::with_output_sink("test-targetRunnerParallel.Rout", { test_that("targetRunnerParallel", { setClasses <- function(x, classes) { class(x) = classes x } # test that targetRunnerParallel works with arbitrary instance objects. targetRunnerParallel <- function(experiment, exec_target_runner, scenario, target_runner) { # get our param settings that irace should try cands = lapply(experiment, "[[", "configuration") # the instance is always the same for all different param setting theinst = experiment[[1L]]$instance # we check that we have instances of correct class expect_s3_class(theinst, "foo", exact = TRUE) # fabricate some random fitness vals ys = rnorm(length(cands)) lapply(ys, function(y) list(cost = y, time = NA_real_)) } n.inst = 7L instances = replicate(n.inst, setClasses(list(x=123), classes = "foo"), simplify = FALSE) parameters = readParameters(text='x1 "" r (0,1)') log.file = tempfile() tuner_config = list(maxExperiments = 40L, nbIterations = 1L, minNbSurvival = 1L, targetRunnerParallel = targetRunnerParallel, instances = instances, logFile = log.file) confs <- irace(scenario = tuner_config) expect_gt(nrow(confs), 0L) }) test_that("targetRunnerData", { targetRunnerParallel <- function(experiments, exec_target_runner, scenario, target_runner) { cat("a = ", scenario$targetRunnerData$a, ", b = ", scenario$targetRunnerData$b, "\n", sep = "") # get our param settings that irace should try cands = lapply(experiments, "[[", "configuration") # fabricate some random fitness vals ys = rnorm(length(cands)) ys = lapply(ys, function(y) list(cost = y, time = NA_real_)) return(ys) } parameters = readParameters(text=' x "x" r (1,2) ') expect_output( irace(scenario = list(targetRunnerParallel = targetRunnerParallel, instances = replicate(5, list(10)), targetRunnerData = list(a=1, b=2), maxExperiments = 42L, parameters = parameters)), "a = 1, b = 2") }) }) # with_output_sink() irace/tests/testthat/test-capping.R0000644000176200001440000000354314745735066017106 0ustar liggesuserswithr::with_output_sink("test-capping.Rout", { ## target runner ########################################################### target_runner_reject <- function(experiment, scenario) { if (experiment$configuration[["reject"]] == "1" && runif(1) <= 0.01) return(list(cost = -Inf, time = experiment$bound)) target_runner_capping_xy(experiment, scenario) } test_that("irace_capping_xy maxExperiments = 1000", { generate_set_seed() irace_capping_xy(maxExperiments = 1000) }) test_that("irace_capping_xy maxExperiments = 1000 cappingAfterFirstTest", { generate_set_seed() irace_capping_xy(maxExperiments = 1000, cappingAfterFirstTest=1) }) test_that("irace_capping_xy maxTime = 1000", { generate_set_seed() expect_warning(irace_capping_xy(maxTime = 1000), "boundMax=80 is too large, using 5 instead") }) test_that("irace_capping_xy targetRunner = target_runner_reject, maxTime = 1000", { skip_on_cran() skip_on_coverage() # This test sometimes fails randomly generate_set_seed() irace_capping_xy(targetRunner = target_runner_reject, maxTime = 1000, boundMax = 5, debugLevel = 3, # FIXME: target_runner_reject does not work in parallel on Windows. parallel = if (system_os_is_windows()) 1L else test_irace_detectCores()) }) test_that("capping default value", { parameters_table <- ' x "" r (0, 1.00) y "" r (0, 1.00) reject "" c (0,1)' parameters <- readParameters(text = parameters_table) def_scenario <- list(instances = c("ackley", "goldestein", "matyas", "himmelblau"), targetRunner = target_runner_reject, maxTime = 1200, parameters = parameters) scenario <- checkScenario(def_scenario) expect_false(scenario$capping) def_scenario$boundMax <- 80 scenario <- checkScenario(def_scenario) expect_true(scenario$capping) }) }) # withr::with_output_sink() irace/tests/testthat/setup.R0000644000176200001440000000027414736526233015640 0ustar liggesusersold_opts = options( warnPartialMatchArgs = TRUE, warnPartialMatchAttr = TRUE, warnPartialMatchDollar = TRUE ) old_opts = lapply(old_opts, function(x) if (is.null(x)) FALSE else x) irace/tests/testthat.R0000644000176200001440000000056214736526233014500 0ustar liggesusers# This file is part of the standard setup for testthat. # It is recommended that you do not modify it. # # Where should you do additional test configuration? # Learn more about the roles of various files in: # * https://r-pkgs.org/tests.html # * https://testthat.r-lib.org/reference/test_package.html#special-files library(testthat) library(irace) test_check("irace") irace/.Rinstignore0000644000176200001440000000010714736526233013652 0ustar liggesusers\.github ^GenericWrapper4AC _snaps vignettes/section/irace-options.tex irace/MD50000644000176200001440000003067014753147342011665 0ustar liggesusers2264fa9d8c4f3cb265a9b251aa08b45a *DESCRIPTION b7738f5a508f22779b74733abc034786 *NAMESPACE f560e965aa8e26290a2d926067c4c30c *NEWS.md 15ce628ab5e26316a27df0370a9c9b44 *R/aaa.R 99a995cd4706bf4ad4ee5c27dd4971ab *R/ablation.R 5e7738e78b65ea41cfcb43fbf5a08d34 *R/argparser.R 5b5d4e56ecd05ddc094d46d027c482dc *R/cluster.R 70d588290f1097835f9ee893c1a4247e *R/configurations.R 066f7cabe55ed255e2f2c3aa8ef5645c *R/generation.R 945bff43ed3e28b764e137e20ac45349 *R/irace-options.R 1f98f6158b4d713cd233c8bf0752cfb8 *R/irace-package.R 8f3e4255357b4ffdd68e022a2ab45af3 *R/irace.R 1cf273fe37c5662ad9c6ded6c1690a2b *R/irace_summarise.R 720de1df587d31c2f26f0dccb3644b85 *R/main.R d0e49cf3a5f60810630ddb55ee0f135b *R/model.R 0e5b0a80321f5d9c849378bb5f9a332a *R/multi_irace.R 0d9c21eb7bd2f6ec40df8fa05636619e *R/parameterAnalysis.R 2cb51079a8480518e50476c13322ed76 *R/parameters.R fc9f3c7970c55df0c8b521fba54ae104 *R/path_rel2abs.R 77729498466b297a7f5ca73550ecba84 *R/psRace.R f713e602990990c330e69bd4068cb9e8 *R/race-wrapper.R e06155a1b4a6bfac5f115943976c2eb7 *R/race.R 6e4a6d5ac23110057664a8c800081d32 *R/race_state.R 1d4a29f5811864c1ee0956f4619d5062 *R/random_seed.R 79f206e2ad3c4df9134819d4c21cb944 *R/readConfiguration.R f7023e2dc6bb25d63f33c354d541eefb *R/readParameters.R 279c83de4cdf701daf375a1a3a249da1 *R/scenario.R 1d227a1312b5c1e2e74ac2cd5c456305 *R/testing.R e7db059f3f4a16184a579ee1aedb5733 *R/timer.R 3d9278311be3b74d2efae99f9663e250 *R/tnorm.R 611025ddf1c4accd57794e1be06bfd42 *R/utils.R 95a5f261c73423140bdcee8b95f9a531 *R/version.R 2a118b7354fdedff5c74246d8423e5ad *R/zzz.R 4f15c2c849d23741209e77fd154f0dcf *README.md a82fa3ba81d743f1cab0e69f7490eab5 *build/partial.rdb b07a6562e9646fd61d149097d55d559d *build/vignette.rds 90f388627b81fe6ba8219fb8d4b06a3c *cleanup c62ed27a7d824faa54b9bade601f0086 *inst/CITATION 4fafc43ccb5f8f46ea49dba2cb5d93e7 *inst/bin/parallel-irace 3e8f7689974b059d5a3fa356c7d8a231 *inst/bin/parallel-irace-mpi bd8592671bd7acc42ea91a49a5fc7304 *inst/bin/parallel-irace-qsub 3484f8d53fb8b7eb78ba2ad56171a525 *inst/bin/parallel-irace-slurm 6650dfedb36361c7921aa0701dc53ed9 *inst/doc/irace-package.R 5937f2532fea2909748ecd942dc14b6b *inst/doc/irace-package.Rnw 6bf85ec71efd17e517fb498e29ecc383 *inst/doc/irace-package.pdf 6f5adad387e00e68a7802e8d543b18ce *inst/examples/README.md 067fe2821c16cf8262dece7a4c36144e *inst/examples/Spear/README 4a9044d0341a321c352ec901cb97a8f0 *inst/examples/Spear/parameters-cat.txt cb2da2a31c96ffc3a4274e9a8ef3832a *inst/examples/Spear/parameters-mixed.txt deda433ece24d324cbc1eef067cb43d7 *inst/examples/Spear/scenario.txt 039051117ef71079e1d2ff106d6c4e7a *inst/examples/Spear/target-runner 7d36a29ebc23a8f813ec3a0c46eb1e84 *inst/examples/acotsp/README d14e5bb16ddb374e204cabfc0be4545d *inst/examples/acotsp/default.txt ec439a0bc50219bd37b71c7fb6e67dd0 *inst/examples/acotsp/parameters-acotsp.txt 296613c78543f277ad03f13dd45632b6 *inst/examples/acotsp/scenario.txt 3b2e0404fedaf8c5771da68553626557 *inst/examples/acotsp/target-runner b7d067aba94e5b7fc1e92237a6f0a1b4 *inst/examples/batchmode-cluster/README d960f69eeadff7405e4452f8f50c710a *inst/examples/batchmode-cluster/irace-sge-cluster 0b3da55eb761846dcf247a23060ffb0a *inst/examples/batchmode-cluster/target-evaluator cb7e8b282835fd66d0745aa16dfdecbf *inst/examples/batchmode-cluster/target-runner-htcondor 682e5b6a084e3e94e0ae045b60de8f9f *inst/examples/batchmode-cluster/target-runner-pbs 3cd7828292c3927406d34868ad9266b0 *inst/examples/batchmode-cluster/target-runner-sge 40fbfe617cda99828251f4b2128a4076 *inst/examples/batchmode-cluster/target-runner-slurm 3523d53e5d616ae1343cbb787b703b5c *inst/examples/hypervolume/README 5d16974368bcb97b8dcf9594b19152f2 *inst/examples/hypervolume/target-evaluator 74406c35bc1a89a6e202f9aa27f48817 *inst/examples/hypervolume/target-runner 7a908b35eff27e6ed9fe12d5936f8018 *inst/examples/matlab/Main.m 3b0332e02daabf31651a5a0d81ba830a *inst/examples/matlab/instances.txt 570fbc605de77856756be963e4fb8c91 *inst/examples/matlab/parameters.txt 4250496b1d892f47536b8a70e0bd7506 *inst/examples/matlab/scenario.txt 117ed65e989d5b1d901431e35c392b00 *inst/examples/moaco/README c1f850010a57a1ef001062ccb67a5195 *inst/examples/moaco/parameters.txt f852be06460223000aa582c4e749bb1f *inst/examples/moaco/scenario.txt 5157ef3fc2efb9230f76aef1cfe73291 *inst/examples/moaco/target-evaluator e250b38c64ed8030bc71ee66ec4f8941 *inst/examples/moaco/target-runner 8652813c19e9266c3e1849a06fd04907 *inst/examples/slurm/job.sh 388f9371ca0d455e51e200879b1772f0 *inst/examples/slurm/run_irace.R 03229b37f1f15e8e94a0c7444436f438 *inst/examples/target-runner-c/instances.txt 50d6243d8eb29e9a104461aa9657e2e3 *inst/examples/target-runner-c/parameters.txt 795af3a128d0d1266b90dde627d43b9c *inst/examples/target-runner-c/scenario.txt bb9dabb68e2467077c87d796db8d33a1 *inst/examples/target-runner-c/target-runner.c 3a66bdf4ccc99e46369ac61adcd68a71 *inst/examples/target-runner-python/target-runner-acotsp.py 3181689c55fc627ccc96e5f9980484f0 *inst/examples/target-runner-python/target-runner-advanced.py fc5b988b87d11f4e54a72ced66b72fb2 *inst/examples/target-runner-python/target-runner-python-win.bat 2286cd7e0e526fa1526ea38a612d476b *inst/examples/target-runner-python/trivial/instances.txt e3f5b6aee118bd70096440b0d648e812 *inst/examples/target-runner-python/trivial/parameters.txt e35dda63b5e6a98c1c15b392693ac633 *inst/examples/target-runner-python/trivial/scenario.txt b23ae5fbdad504a0373d8a4147b379e9 *inst/examples/target-runner-python/trivial/target-runner.py c868589f82ab78c538bb73b63ba5ed59 *inst/exdata/irace-acotsp.Rdata 86bac7b38049082b3a0f73634144fd1f *inst/exdata/log-ablation.Rdata 52feab4bf6777dc9bdd387df9703a628 *inst/exdata/sann.rda a523dd89b6ec7ba2adc26e2ed3a3a179 *inst/irace.sindef 5718f32d8359ec715f2d8bc1a0ec02b3 *inst/templates/configurations.txt.tmpl af9376428b2c2d911710fd6e51ecf784 *inst/templates/instances-list.txt.tmpl 5223b5c5b935d24826f19a63540b59c7 *inst/templates/parameters.txt.tmpl f58aa6abf5e9d2755a26435b57c5ae0e *inst/templates/scenario.txt.tmpl 0185c79ae7b6f5994f89da433acd7b94 *inst/templates/target-evaluator.tmpl ce1e0647e76140eac038f0a1e0f30d2a *inst/templates/target-runner.tmpl 4850d7fbd68fbc7c06af53d3c510734f *inst/templates/windows/target-runner.bat 0815ed0bfdfac44a709fdf37e0c8da70 *man/ablation.Rd ad88cc23f0dd138ede5ecbbd4348ef39 *man/ablation_cmdline.Rd 97df995452432aedab6501c447222bee *man/buildCommandLine.Rd 22c55e3778d336a7c65dcc0fad4b8be0 *man/checkIraceScenario.Rd ca4dea9aebb234d9db7afd9742bf2b39 *man/checkParameters.Rd 78d5c354d12f0b0d166cbbc69f6590f0 *man/checkScenario.Rd 08eaf87dd5077a7a2e51debba1327160 *man/check_output_target_runner.Rd 84bffa7e4008c648aa6093ac6b521a0e *man/configurations_print.Rd 20974d74befdc45e32057cc236b250af *man/configurations_print_command.Rd 2a8819d79b9f7e304915eb84773c2052 *man/defaultScenario.Rd b872bbcb3cab0c61fea1051a491dcafd *man/getConfigurationById.Rd e0516ae2bbf6a95946468342474980ca *man/getConfigurationByIteration.Rd 4379d0202d10764f6582d4c77816e516 *man/getFinalElites.Rd 8962b34f3d99b7400b943a72483b7584 *man/get_instanceID_seed_pairs.Rd 14a6c3069e36e86aab218d5f3881620f *man/has_testing_data.Rd 1c89a853ee2bfa26f82518f61e642a12 *man/irace-package.Rd 8a01d51299214920726f2cca2d8dde83 *man/irace.Rd f363573d352dfcf4202eec90c8616c74 *man/irace_cmdline.Rd c5028f112fcba7a0357272df300bfeef *man/irace_license.Rd 798efa16aa50ce4056ba58c2338c93b3 *man/irace_main.Rd c53417b5fbb84a928125a2a63217e2a7 *man/irace_summarise.Rd 1cb0277b653693a2d927d4799e6c82f3 *man/irace_version.Rd b19078bd26ec7ffd9821e44d369afe18 *man/multi_irace.Rd 28b6c3cc7b4bfbad36a9cdeaeda2f148 *man/parameters.Rd 98f45d9d1092dc87591febe99be3b2a7 *man/path_rel2abs.Rd 34ca000f9e09e1e33e569af65355be75 *man/plotAblation.Rd b94901cc7ab0506875410d2684b7bd50 *man/printParameters.Rd 6e4645d0710c8013dd8d1970bf629fb9 *man/printScenario.Rd 6ed47d873d3e4a37f389477163ccf417 *man/psRace.Rd 83683f19856a4b7bdbce1345dab2c539 *man/random_seed.Rd 44ec2759fb9575dd6cd000fe8a94b107 *man/readConfigurationsFile.Rd e0989af0e7d48c44d0574ac8d51bce58 *man/readParameters.Rd 9806771a8e8e3bfcf551cd52902c3ea5 *man/readScenario.Rd 3b1091c12c43f96f4d7622ee269b739a *man/read_ablogfile.Rd 06e4d5d2b1eeddcc2400726e6aa3d62f *man/read_logfile.Rd f700572809f0a7f8ee398e7f18358e13 *man/read_pcs_file.Rd 6ca7619bd07a92f1cca2e228ab184e18 *man/removeConfigurationsMetaData.Rd 7f7e1e4184d1996f3ca4dc3ea153e002 *man/save_irace_logfile.Rd 874142b614625461f91ab9bb1c89c543 *man/scenario_update_paths.Rd 71e04de7d540160a730e85ceb0d5130f *man/target_evaluator_default.Rd 43929367039d9a6095051329fd262831 *man/target_runner_default.Rd 3815d6b4c28da8acb256ee0ff00a251a *man/testConfigurations.Rd fa1eb0e5d9f8e82e066aefab0a50ef0e *man/testing_fromfile.Rd 05fdbf65834ad9d1f1e82cf6cc64b1e7 *man/testing_fromlog.Rd a886f8cb75354603c3204d2cebc07888 *src/Makevars a226bdfce980e2c61c21c03a6442f322 *src/Makevars-common ca5fd252fe6221d98e26e1eab7b8d9c6 *src/Makevars.win 1398b2fd4ff03df145e7be99716fb5c3 *src/dummy/target-runner-dummy.c 70c3abdb60157b72947499b5110cb964 *src/install.libs.R aa3c94c075de8e74d82fa9dbac430ae0 *src/iracebin/ablation.c 7d0d72752d80088203df170808753c12 *src/iracebin/irace.c 21a6f2c3b1b9d8f6f577ed0350f3be5d *src/iracebin/irace.h dd5bdc751bb1cbdd2dd55e82e89a7ee8 *src/iracebin/whereami.c 6c3b99e57a7cd7611fb4c079fafb2cd6 *src/iracebin/whereami.h 5a48d8cf11a02c33827a139b0a225fff *tests/testthat.R b735d8cdd247647949863dc0db5242c5 *tests/testthat/bad_scenario.txt 0c70d9f2df1bdaae7a5427383deae61f *tests/testthat/configurations.txt 5392765ea169c97660c1a8f5e38962d5 *tests/testthat/dependencies.txt cb3f44bc6c27b7761045e0c433c2797d *tests/testthat/dependencies2.txt a96f6d23df9bca94fd33c47b8fe54835 *tests/testthat/dummy_wrapper.py 802b1377a53b451f13bc190dd692eb9e *tests/testthat/good_scenario.txt 0f895dc1a70ef3e070f5f3a3fce3b727 *tests/testthat/helper-common.R 6fda56b55b2ddcd5255cc1f0c38d6081 *tests/testthat/logparameters.txt b5281655464b518a9905e360f72172d0 *tests/testthat/parameters.txt fa5811cf38687ea0ba22857d07e43708 *tests/testthat/setup.R 390e7a8b3437a82b11f614fd1c1976f1 *tests/testthat/teardown.R cc1537221892ea4f0c8169bb85a3b5a3 *tests/testthat/test-GenericWrapper4AC.R 9d28323afacc3d6342da7e21d5f9e2eb *tests/testthat/test-ablation.R dffb23cb04b2410a861012138443c85d *tests/testthat/test-argparser.R 130d5905ba8b3e11af3759d64b826dbb *tests/testthat/test-bad_scenario.R 1d63bd03609be7dfffc15bea8e1900b1 *tests/testthat/test-blocksize.R a17f8f4a5337eac7a4792ca4c657cee6 *tests/testthat/test-bug-10.R c23c303b507d81f3446f93f786838217 *tests/testthat/test-bug-44.R 700aa77339ccdda457068bef5c348ff2 *tests/testthat/test-bug-55.R ffdf4325c439bc2cfc01dc7dfd9902d4 *tests/testthat/test-bug71.R de0cd2d1ade2d1cfe4fc38e4ecfe3f35 *tests/testthat/test-capping.R 882d8851c39a2da5e124c8d4b21999dc *tests/testthat/test-dependencies.R efd1eef47896bc45b8bd9c5f0a98439b *tests/testthat/test-forbidden.R a1da29cd4b5fdad9f96e202a8479e278 *tests/testthat/test-get-functions.R f1e8529b31b7da07bee6238d6b830598 *tests/testthat/test-maxTime.R c56de868281cbf7d393875cade6271d0 *tests/testthat/test-multi_irace.R a8a04422d027bbfb1834ba727612e56d *tests/testthat/test-path.R 103ee22c9b050575083a8320ee3c552d *tests/testthat/test-psrace.R 1102ec28db5067837c544b3f3ab527fd *tests/testthat/test-raceconfs.R d976745248ffdf56fc99c36853735a22 *tests/testthat/test-readParameters.R 105d18d04d2c1b8f7781f4b6991c291c *tests/testthat/test-read_pcs_file.R 17f2f5d1fb1534553b2d20fe0ec3b415 *tests/testthat/test-readconfs.R 89be9519d27923908d6dac9895a2a4ff *tests/testthat/test-recovery.R 26a45177ed6f9aaff18f2e33a65b740f *tests/testthat/test-repair.R dd476a54bd1c9dadec0789c184ac36fd *tests/testthat/test-sann-irace.R 6b5366dcf14f8a98e8c30f923fb06a95 *tests/testthat/test-similar.R 8782bffc2d9c5ab48419fed065d1bf25 *tests/testthat/test-sobol.R 6ed927e415685e16a702edc699c6096f *tests/testthat/test-target-runner-dummy.R 4439f315ad740b55aa598777f5753ad0 *tests/testthat/test-targetRunnerParallel.R 6d84c855508aad6ff5fa1190b2b591e2 *tests/testthat/test-targeteval.R 289ab2a15fa9dca53bb4a94dff1817e3 *vignettes/Warning-icon.png 92cb3ab29da2cefc7dfc3a5cc5f1ec46 *vignettes/examples.Rdata 4784f9bce400ae4624ca874739cd7c4b *vignettes/fig1u-acotsp-instances.pdf 14eff35115c1805af0e8a9bb74298b19 *vignettes/irace-acotsp-stdout.txt 8130717f1dab1c71475aed5f4dca45cb *vignettes/irace-acotsp-testing.txt 5937f2532fea2909748ecd942dc14b6b *vignettes/irace-package.Rnw 94237e6e090105a0aab22b0559917ff2 *vignettes/irace-package.bib f44025a8b672c55f7c68873d309336ad *vignettes/irace-scheme.pdf 0b5a399b2478c44749ab63304194f6a7 *vignettes/light-bulb-icon.png ec6c5ddc5ac0c8741ebe189759ed2a01 *vignettes/section/irace-options.Rnw d41d8cd98f00b204e9800998ecf8427e *vignettes/section/irace-options.tex irace/R/0000755000176200001440000000000014752430310011535 5ustar liggesusersirace/R/psRace.R0000644000176200001440000003627114736526233013122 0ustar liggesusers#' Post-selection race #' #' \code{psRace} performs a post-selection race of a set of configurations. #' #' @inheritParams has_testing_data #' #' @param max_experiments `numeric(1)`\cr Number of experiments for the #' post-selection race. If it is equal to or smaller than 1, then it is a #' fraction of the total budget given by #' `iraceResults$scenario$maxExperiments` or `iraceResults$scenario$maxTime / #' iraceResults$state$boundEstimate`. #' @param conf_ids IDs of the configurations in `iraceResults$allConfigurations` to be used for the post-selection. #' If `NULL`, then the configurations are automatically selected. #' @param iteration_elites If `FALSE`, give priority to selecting the configurations that were elite in the last iteration. #' If `TRUE`, select from all elite configurations of all iterations. This parameter only has an effect when `conf_ids` is not `NULL`. #' @param psrace_logFile `character(1)`\cr Log file to save the post-selection race log. If `NULL`, the log is saved in `iraceResults$scenario$logFile`. #' #' @return The elite configurations after the post-selection. In addition, if `iraceResults$scenario$logFile` is defined, #' it saves an updated copy of `iraceResults` in that file, where `iraceResults$psrace_log` is a list with the following elements: #' \describe{ #' \item{configurations}{Configurations used in the post-selection race.} #' \item{instances}{Data frame with the instances used in the post-selection race. First column has the #' instances IDs from `iraceResults$scenario$instances`, second column the seed assigned to the instance.} #' \item{max_experiments}{Configuration budget assigned to the post-selection race.} #' \item{experiments}{Matrix of results generated by the post-selection race, in the same format as the matrix \code{iraceResults$experiments}. Column names are the configuration IDs and row names are the instance IDs.} #' \item{elites}{Best configurations found in the experiments.} #' } #' #' @examples #' \donttest{ #' irace_log <- read_logfile(system.file(package="irace", "exdata", "sann.rda")) #' # Use a temporary file to not change the original "sann.rda". #' psrace_logFile <- withr::local_tempfile(fileext = ".Rdata") #' # Execute the post-selection after the execution of irace. Use 10% of the total budget. #' psRace(irace_log, max_experiments=0.1, psrace_logFile = psrace_logFile) #' # Print psrace_log #' irace_log <- read_logfile(psrace_logFile) #' str(irace_log$psrace_log) #' } #' @author Leslie Pérez Cáceres and Manuel López-Ibáñez #' @export psRace <- function(iraceResults, max_experiments, conf_ids = NULL, iteration_elites = FALSE, psrace_logFile = NULL) { irace_note("Starting post-selection:\n") if (missing(iraceResults)) stop("argument 'iraceResults' is missing.") iraceResults <- read_logfile(iraceResults) scenario <- iraceResults$scenario if (!is.null(psrace_logFile)) scenario$logFile <- psrace_logFile race_state <- iraceResults$state race_state$initialize(scenario, new = FALSE) # Restores the random seed if (max_experiments <= 0) stop("'max_experiments' must be positive.") if (max_experiments <= 1) { budget <- if (scenario$maxTime == 0L) scenario$maxExperiments else scenario$maxTime / race_state$boundEstimate max_experiments <- as.integer(ceiling(max_experiments * budget)) if (scenario$maxTime == 0L) cat(sep="", "# scenario maxExperiments:", scenario$maxExperiments, "\n") else cat(sep="", "# scenario maxTime:", scenario$maxTime, "\n") } # Get selected configurations. if (is.null(conf_ids)) { # FIXME: Handle scenario$maxTime > 0 irace_assert(scenario$maxTime == 0) which_max_last <- function(x) 1L + length(x) - which.max(rev(x)) # We want to race at least two configurations, so we generate all # integers between 3 and 2^max_confs - 1L that are not powers of 2, then convert # it to a bit-string then to a logical vector. generate_combs_2 <- function(max_confs) { max_pow <- as.integer(2^max_confs) pow_2 <- as.integer(2L^(0L:(max_confs-1L))) s <- seq.int(3L, max_pow - 1L, 2L) if (max_pow >= 8L) s <- c(s, setdiff(seq.int(6L, max_pow - 2L, 2L), pow_2[-1L:-3L])) lapply(s, function(z) as.logical((z %/% pow_2) %% 2L)) } # A version of the above that is not limited to two configurations. generate_combs_1 <- function(max_confs) { max_pow <- as.integer(2^max_confs) pow_2 <- as.integer(2L^(0L:(max_confs-1L))) lapply(seq.int(1L, max_pow - 1L, 1L), function(z) as.logical((z %/% pow_2) %% 2L)) } # FIXME: Is this really faster than head(x, n=max_len) ? truncate_conf_needs <- function(x, max_len) { if (length(x) <= max_len) return(x) x[seq_len(max_len)] } get_confs_for_psrace <- function(iraceResults, iteration_elites, max_experiments, conf_ids, rejected_ids) { scenario <- iraceResults$scenario report_selected <- if (scenario$quiet) do_nothing else function(conf_ids, conf_needs) cat(sep="", "# Configurations selected: ", paste0(collapse=", ", conf_ids), ".\n# Pending instances: ", paste0(collapse=", ", conf_needs), ".\n") allElites <- iraceResults$allElites experiments <- iraceResults$experiments conf_ids <- if (iteration_elites) unlist(rev(allElites)) else allElites[[length(allElites)]] conf_ids <- unique(c(conf_ids, iraceResults$allConfigurations[[".ID."]])) # NA may be generated if we skipped iterations. if (anyNA(conf_ids)) conf_ids <- conf_ids[!is.na(conf_ids)] # Remove rejected configurations. if (length(rejected_ids)) conf_ids <- setdiff(conf_ids, rejected_ids) experiments <- experiments[, conf_ids, drop = FALSE] conf_needs <- matrixStats::colCounts(experiments, value = NA, useNames = TRUE) n_done <- nrow(experiments) - min(conf_needs) # Remove any configuration that needs more than max_experiments. conf_needs <- conf_needs[conf_needs <= max_experiments] if (length(conf_needs) == 1L) { if (!scenario$quiet) cat("# Insufficient budget to race more than one configuration!\n") return(names(conf_needs)) } if (!scenario$deterministic || n_done < length(scenario$instances)) { # We want to evaluate in at least n_new instances more. n_new <- scenario$eachTest * scenario$blockSize n_confs <- floor(max_experiments / n_new) # If we have n_confs that have been evaluated in all instances, select those. if (n_confs > 1L && sum(conf_needs == 0L) >= n_confs) { conf_needs <- conf_needs[conf_needs == 0L][1:n_confs] conf_ids <- names(conf_needs) report_selected(conf_ids, conf_needs) irace_assert(length(conf_ids) > 1L, eval_after = { cat("n_confs: ", n_confs, "\nn_new:", n_new, "\n") print(conf_needs) save(iraceResults, file="bug-conf_ids.Rdata") }) return(conf_ids) } # Let's try first to evaluate on new instances. conf_needs_new <- truncate_conf_needs(conf_needs + n_new, max_len = 16L) irace_assert(length(conf_needs_new) > 1L, eval_after={ cat("max_experiments: ", max_experiments, "\nn_new: ", n_new, "\nconf_needs:") print(conf_needs) save(iraceResults, file="bug-conf_ids.Rdata") }) combs <- generate_combs_2(length(conf_needs_new)) left <- sapply(combs, function(x) max_experiments - sum(conf_needs_new[x]), USE.NAMES=FALSE) irace_assert(!is.null(left) && length(left) > 0L && !anyNA(left), eval_after= { cat("max_experiments: ", max_experiments, "\n") cat("length(left): ", length(left), "\n") cat("sum(is.na(left)): ", sum(is.na(left)), "\n") save(iraceResults, file="bug-conf_ids.Rdata") }) if (any(left >= 0L)) { # We have enough budget to evaluate on a new instance. # Select the combination that will allow to evaluate the most configurations. combs <- combs[left >= 0] n <- sapply(combs, sum, USE.NAMES=FALSE) winner <- which.max(n) conf_needs_new <- conf_needs_new[combs[[winner]]] conf_ids <- names(conf_needs_new) report_selected(conf_ids, conf_needs_new) irace_assert(length(conf_ids) > 1L, eval_after = { cat("max_experiments: ", max_experiments, "\n") cat("length(left): ", length(left), "\n") cat("sum(is.na(left)): ", sum(is.na(left)), "\n") cat("winner: ", winner, "\n") cat("combs[[winner]]: ", paste0(collapse=",", combs[[winner]]), "\n") save(iraceResults, file="bug-conf_ids.Rdata") }) return(conf_ids) } } # We do not have enough budget to see new instances, so we need consider # at least 1 configuration that has NA values, but we still include the # configurations that have been evaluated on all instances. conf_needs_zero <- conf_needs[conf_needs == 0L] conf_needs <- truncate_conf_needs(conf_needs[conf_needs > 0L], 16L) combs <- generate_combs_1(length(conf_needs)) left <- sapply(combs, function(x) max_experiments - sum(conf_needs[x]), USE.NAMES=FALSE) irace_assert(any(left >= 0), eval_after=save(iraceResults, file="bug-conf_ids.Rdata")) # Select the combination that will allow us to evaluate the most configurations. combs <- combs[left >= 0] n <- sapply(combs, sum, USE.NAMES=FALSE) winner <- which.max(n) conf_needs <- c(conf_needs_zero, conf_needs[combs[[winner]]]) conf_ids <- names(conf_needs) report_selected(conf_ids, conf_needs) irace_assert(length(conf_ids) > 1L, eval_after = { print(conf_needs) cat("winner: ", winner, "\n") cat("combs[[winner]]: ", paste0(collapse=",", combs[[winner]]), "\n") save(iraceResults, file="bug-conf_ids.Rdata") }) return(conf_ids) } conf_ids <- get_confs_for_psrace(iraceResults, iteration_elites, max_experiments, conf_ids = conf_ids, rejected_ids = race_state$rejected_ids) irace_assert(length(conf_ids) >= 1L, eval_after = { # Debug what happened if the assert failed. rejected_ids <- race_state$rejected_ids cat("blockSize:", scenario$blockSize, "\niteration_elites: ", as.character(iteration_elites), "\nrejected: ", paste0(collapse=", ", rejected_ids), "\n") allElites <- iraceResults$allElites experiments <- iraceResults$experiments conf_ids <- if (iteration_elites) unlist(rev(allElites)) else allElites[[length(allElites)]] cat("conf_ids1:", paste0(collapse=", ", conf_ids), "\n") conf_ids <- unique(c(conf_ids, iraceResults$allConfigurations[[".ID."]])) cat("conf_ids2:", paste0(collapse=", ", conf_ids), "\n") # NA may be generated if we skipped iterations. if (anyNA(conf_ids)) conf_ids <- conf_ids[!is.na(conf_ids)] cat("conf_ids3:", paste0(collapse=", ", conf_ids), "\n") # Remove rejected configurations. if (length(rejected_ids)) conf_ids <- setdiff(conf_ids, rejected_ids) cat("conf_ids4:", paste0(collapse=", ", conf_ids), "\n") conf_needs <- matrixStats::colCounts(experiments[, conf_ids, drop = FALSE], value = NA) cat("conf_needs:", paste0(collapse=", ", conf_needs), "\n") }) if (length(conf_ids) == 1L) { # If we cannot run post-selection, we just return the elite configurations. allElites <- iraceResults$allElites return(allElites[[length(allElites)]]) } } else if (length(conf_ids) <= 1L) { irace_error ("The number configurations provided should be larger than 1.") } else if (length(race_state$rejected_ids) && any(conf_ids %in% race_state$rejected_ids)) { irace_error ("Some configuration IDs provided were rejected in the previous run: ", paste0(collapse=", ", intersect(conf_ids, race_state$rejected_ids)), ".") } if (!all(conf_ids %in% iraceResults$allConfigurations[[".ID."]])) { irace_error("Some configuration IDs provided cannot be found in the configurations: ", paste0(collapse=", ", setdiff(conf_ids, iraceResults$allConfigurations[[".ID."]])), ".") } elite_configurations <- iraceResults$allConfigurations[iraceResults$allConfigurations[[".ID."]] %in% conf_ids, , drop=FALSE] # Generate new instances. generateInstances(race_state, scenario, max_experiments / nrow(elite_configurations), update = TRUE) elite_data <- iraceResults$experiments[, as.character(elite_configurations[[".ID."]]), drop=FALSE] race_state$next_instance <- nrow(elite_data) + 1L irace_note("seed: ", race_state$seed, "\n# Configurations: ", nrow(elite_configurations), "\n# Available experiments: ", max_experiments, "\n# minSurvival: 1\n") # We do not want to stop because of this limit. scenario$elitistLimit <- 0L # FIXME: elitist_race should not require setting this, but it currently does. scenario$elitist <- TRUE raceResults <- elitist_race(race_state, maxExp = max_experiments, minSurvival = 1L, configurations = elite_configurations, scenario = scenario, elite_data = elite_data, elitist_new_instances = 0L, # FIXME: This should be nrow(elite_data) + 1L if we have budget to evaluate on new instances. firstTest = nrow(elite_data) / scenario$blockSize) elite_configurations <- extractElites(raceResults$configurations, nbElites = race_state$minSurvival, debugLevel = scenario$debugLevel) irace_note("Elite configurations (first number is the configuration ID;", " listed from best to worst according to the ", test.type.order.str(scenario$testType), "):\n") if (!scenario$quiet) configurations_print(elite_configurations, metadata = scenario$debugLevel >= 1L) if (!is.null(scenario$logFile)) { elapsed <- race_state$time_elapsed() if (!scenario$quiet) cat("# Total CPU user time: ", elapsed["user"], ", CPU sys time: ", elapsed["system"], ", Wall-clock time: ", elapsed["wallclock"], "\n", sep="") indexIteration <- 1L + max(race_state$experiment_log[["iteration"]]) # We add indexIteration as an additional column. set(raceResults$experiment_log, j = "iteration", value = indexIteration) race_state$experiment_log <- rbindlist(list(race_state$experiment_log, raceResults$experiment_log), fill=TRUE, use.names=TRUE) # Merge new results. iraceResults$experiments <- merge_matrix(iraceResults$experiments, raceResults$experiments) iraceResults$iterationElites[indexIteration] <- elite_configurations[[".ID."]][1L] iraceResults$allElites[[indexIteration]] <- elite_configurations[[".ID."]] iraceResults$scenario <- scenario iraceResults$state <- race_state # FIXME: This log should contain only information of what was done in the # psRace and avoid duplicating info from iraceResults. iraceResults$psrace_log <- list(configurations = elite_configurations, instances = race_state$instances_log[seq_len(nrow(raceResults$experiments)), , drop = FALSE], max_experiments = max_experiments, experiments = raceResults$experiments, elites = elite_configurations[[".ID."]]) save_irace_logfile(iraceResults, logfile = scenario$logFile) } elite_configurations } irace/R/random_seed.R0000644000176200001440000000404614736526233014160 0ustar liggesusers#' Get, set and restore the state of the random number generator state. #' #' @details These functions originate from the [`withr`][withr::with_seed()] package. #' #' @param seed (`list()`|`integer(1)`)\cr Either an integer or the list returned gy [get_random_seed()]. #' @return [get_random_seed()] returns a list with two components `random_seed` and `rng_kind` or NULL if no seed was set; [set_random_seed()] and [restore_random_seed()] do not return anything. #' @name random_seed #' @examples #' old_seed <- get_random_seed() #' on.exit(restore_random_seed(old_seed)) #' set_random_seed(42) #' value1 <- runif(1) #' set_random_seed(42) #' value2 <- runif(1) #' stopifnot(all.equal(value1,value2)) #' #' @export get_random_seed <- function() { if (has_random_seed()) { return(list(random_seed = get(".Random.seed", globalenv(), mode = "integer", inherits = FALSE), rng_kind = RNGkind())) } NULL } #' @rdname random_seed #' @export set_random_seed <- function(seed) { if (is.list(seed)) { RNGkind(seed$rng_kind[[1L]], normal.kind = seed$rng_kind[[2L]], sample.kind = seed$rng_kind[[3L]]) assign(".Random.seed", seed$random_seed, globalenv()) } else { set.seed(seed) } } #' @rdname random_seed #' @export restore_random_seed <- function(seed) { if (is.null(seed)) { rm_random_seed() } else { set_random_seed(seed) } } rm_random_seed <- function () { if (has_random_seed()) { set.seed(seed = NULL) rm(".Random.seed", envir = globalenv()) } } has_random_seed <- function() { exists(".Random.seed", globalenv(), mode = "integer", inherits = FALSE) } # @return [gen_random_seeds()] returns a list of `n` seeds that were generated from the `global_seed`. # The generated seeds can then e.g. be used to seed thread-local RNGs. gen_random_seeds <- function(n, global_seed = NULL) { # Use a random global seed if not set. if (!is.null(global_seed)) set_random_seed(global_seed) # Generate 'n' seeds using the 'global_seed'. trunc(runif(n, 1, .Machine$integer.max)) } irace/R/generation.R0000644000176200001440000002745714745735066014054 0ustar liggesusers####################################### ## GENERATE CONFIGURATIONS ####################################### ## When called with an unconditional parameter, it must return TRUE. conditionsSatisfied <- function(condition, partialConfiguration) { # If there is no condition, do not waste time evaluating it. if (isTRUE(condition)) return(TRUE) v <- eval(condition, as.list(partialConfiguration)) # Return TRUE if TRUE, FALSE if FALSE or NA ## FIXME: If we byte-compile the condition, then we should incorporate the ## following into the condition directly. !is.na(v) && v } repairConfigurations <- function(x, parameters, repair) { if (!is.null(repair)) { # FIXME: Pass the whole newConfigurations to repair and let it handle each row. j <- parameters$names df <- as.data.frame(x) for (i in seq_nrow(x)) set(x, i = i, j = j, value = repair(df[i, parameters$names], parameters)) } x } is_within_dependent_bound <- function(param, configuration, value) { domain <- unlist(lapply(param[["domain"]], eval, configuration)) # Value gets truncated (defined from robotics initial requirements) if (param[["type"]] == "i") domain <- as.integer(domain) domain[[1L]] <= value && value <= domain[[2L]] } ## Calculates the parameter bounds when parameters domain is dependent getDependentBound <- function(param, configuration) { domain <- param[["domain"]] if (is.expression(domain)) { deps <- all.vars(domain) # If it depends on a parameter that is disabled, then this is disabled. if (anyNA(configuration[deps])) return(NA) domain <- sapply(domain, eval, configuration) irace_assert(all(is.finite(domain))) # Value gets truncated (defined from robotics initial requirements) if (param[["type"]] == "i") domain <- as.integer(domain) if (domain[[1L]] > domain[[2L]]) { irace_error ("Invalid domain (", paste0(domain, collapse=", "), ") generated for parameter '", param[["name"]], "' that depends on parameters (", paste0(deps, collapse=", "), "). This is NOT a bug in irace. Check the definition of these parameters.") } } domain } ## Calculates the parameter bounds when the parameter is dependent. get_dependent_domain <- function(param, configuration) { # FIXME: Make this function handle a data.table and return a list of domains. configuration <- as.list(configuration) domain <- param[["domain"]] deps <- all.vars(domain) # FIXME: This function should not be called if the parent is disabled. # If it depends on a parameter that is disabled, then this is disabled. if (anyNA(configuration[deps])) return(NA) irace_assert(is.expression(domain)) domain <- sapply(domain, eval, configuration, USE.NAMES=FALSE) irace_assert(all(is.finite(domain))) # Value gets truncated (defined from robotics initial requirements) if (param[["type"]] == "i") domain <- as.integer(domain) if (domain[[1L]] > domain[[2L]]) { # FIXME: Add test for this error. irace_error ("Invalid domain (", paste0(domain, collapse=", "), ") generated for parameter '", param[["name"]], "' that depends on parameters (", paste0(deps, collapse=", "), "). This is NOT a bug in irace. Check the definition of these parameters.") } domain } generate_sobol <- function(parameters, n, repair = NULL) { confs <- spacefillr::generate_sobol_set(n, dim = parameters$nbVariable, seed = runif_integer(size = 1L)) confs <- data.table(confs) setnames(confs, parameters$names_variable) hierarchy <- parameters$hierarchy nodep_names <- names(hierarchy)[(hierarchy == 1L) & !parameters$isFixed] # FIXME: How to do this faster using data.table? for (x in nodep_names) set(confs, j = x, value = param_quantile(parameters$get(x), confs[[x]])) for (x in parameters$names_fixed) set(confs, j = x, value = parameters$domains[[x]]) setcolorder(confs, parameters$names) max_level <- max(hierarchy) if (max_level > 1L) { .NEWVALUE <- .DOMAIN <- NULL # To silence CRAN warnings. for (k in seq_len(max_level - 1L)) { prev_names <- names(hierarchy)[hierarchy <= k] dep_names <- names(hierarchy)[hierarchy == k+1L] for (p in dep_names) { param <- parameters$get(p) if (param$isFixed) { if (!isTRUE(param[["condition"]])) # If somehow this fixed parameter was not satisfied sometimes, just set its value to NA. set(confs, which(!eval(param[["condition"]], confs)), j = p, value = NA_character_) next } idx_satisfied <- which_satisfied(confs, param[["condition"]]) if (length(idx_satisfied)) { if (param[["is_dependent"]]) { confs[idx_satisfied, let(.DOMAIN = list(get_dependent_domain(param, .SD))), by=.I, .SDcols=prev_names] confs[idx_satisfied, .NEWVALUE := param_quantile(param, .SD, domain = unlist(.DOMAIN)), by=.I, .SDcols=p] set(confs, j = ".DOMAIN", value = NULL) } else { confs[idx_satisfied, .NEWVALUE := param_quantile(param, unlist(.SD)), .SDcols=p] } set(confs, j = c(p, ".NEWVALUE"), value = list(confs[[".NEWVALUE"]], NULL)) } else { set(confs, j = p, value = .param_na_value_type[[ param[["type"]] ]]) } } } } repairConfigurations(confs, parameters, repair) confs } sampleSobol <- function(parameters, n, repair = NULL) { newConfigurations <- generate_sobol(parameters, n, repair) newConfigurations <- unique(newConfigurations) forbidden <- parameters$forbidden newConfigurations <- filter_forbidden(newConfigurations, forbidden) have <- nrow(newConfigurations) if (have < n) { needed <- max(ceiling(n + (n - have) * (2 - have / n)), min(parameters$nbVariable * 5L, 100L)) newConfigurations <- generate_sobol(parameters, needed, repair) newConfigurations <- unique(newConfigurations) newConfigurations <- filter_forbidden(newConfigurations, forbidden) if (nrow(newConfigurations) == 0L) { irace_error("irace tried to sample a configuration not forbidden without success, perhaps your constraints are too strict?") } newConfigurations <- truncate_rows(newConfigurations, n) } set(newConfigurations, j = ".PARENT.", value = NA_integer_) newConfigurations } generate_uniform <- function(parameters, nbConfigurations, repair = NULL) { newConfigurations <- configurations_alloc(parameters[["names"]], nrow = nbConfigurations, parameters = parameters) # We sample in the order of the conditions. for (param in parameters$get_ordered()) { pname <- param[["name"]] idx <- which_satisfied(newConfigurations, param[["condition"]]) if (length(idx) == 0L) next if (param[["isFixed"]]) { # We don't need to sample, there is only one value. set(newConfigurations, i = idx, j = pname, value = param[["domain"]]) next } if (param[["is_dependent"]]) { sample_dep_unif <- function(x) sample_unif(param, n = 1L, domain = get_dependent_domain(param, x)) newConfigurations[idx, c(pname) := sample_dep_unif(.SD), by=.I, .SDcols=parameters$depends[[pname]]] ## newVals <- sapply(idx, function(i) { ## domain <- get_dependent_domain(param, newConfigurations[i,]) ## runif(param, n = 1L, domain = domain) ## }) } else { set(newConfigurations, i = idx, j = pname, value = sample_unif(param, n = length(idx))) } } repairConfigurations(newConfigurations, parameters, repair) newConfigurations } ### Uniform sampling for the initial generation sampleUniform <- function(parameters, nbConfigurations, repair = NULL) { newConfigurations <- generate_uniform(parameters, nbConfigurations, repair) forbidden <- parameters$forbidden if (!is.null(forbidden)) { retries <- 100L repeat { newConfigurations <- filter_forbidden(newConfigurations, forbidden) needed <- nbConfigurations - nrow(newConfigurations) if (needed == 0L) break newConfigurations <- rbindlist(list(newConfigurations, generate_uniform(parameters, needed, repair = repair))) retries <- retries - 1L if (retries == 0L) { irace_error("irace tried 100 times to sample uniformly a configuration not forbidden without success, perhaps your constraints are too strict?") } } } set(newConfigurations, j = ".PARENT.", value = NA_integer_) newConfigurations } sample_from_model <- function(parameters, eliteConfigurations, model, nbNewConfigurations, repair = NULL) { # FIXME: We only need .WEIGHT. from eliteConfigurations. ids_elites <- names(model[[1L]]) irace_assert(identical(as.integer(ids_elites), as.integer(eliteConfigurations[[".ID."]])), { print(utils::str(ids_elites)) print(utils::str(eliteConfigurations[[".ID."]])) }) newConfigurations <- configurations_alloc(parameters$names, nrow = nbNewConfigurations, parameters) idx_elites <- sample.int(n = length(ids_elites), size = nbNewConfigurations, prob = eliteConfigurations[[".WEIGHT."]], replace = TRUE) .PARENT. <- NULL # Silence CRAN warning. set(newConfigurations, j = ".PARENT.", value = ids_elites[idx_elites]) # Sample a value for every parameter of the new configuration. for (param in parameters$get_ordered()) { idx_satisfied <- which_satisfied(newConfigurations, param[["condition"]]) if (length(idx_satisfied) == 0L) next if (param[["isFixed"]]) { # We don't need to sample, there is only one value. set(newConfigurations, i = idx_satisfied, j = param[["name"]], value = param[["domain"]]) next } pname <- param[["name"]] this_model <- model[[pname]] if (param[["is_dependent"]]) { sample_model <- if (param[["type"]] == "i") sample_model.ParamInt else sample_model.ParamReal dep_rmodel <- function(x, sd_mean) { domain <- get_dependent_domain(param, x) if (is.na(domain[[1L]])) return(NA) # If parameters are dependent standard deviation must be computed # based on the current domain sd <- (domain[[2L]] - domain[[1L]]) * sd_mean[[1L]] if (sd < .Machine$double.eps) return(domain[[1L]]) sample_model(param, n = 1L, model = c(sd, sd_mean[[2L]]), domain = domain) } newConfigurations[idx_satisfied, c(pname) := dep_rmodel(.SD, this_model[[.PARENT.]]), by=.I, .SDcols=parameters$depends[[pname]]] next # We are done with this parameter. } # .BY is a list, so take the first argument. newConfigurations[idx_satisfied, c(pname) := list(sample_model(param, .N, this_model[[ .BY[[1L]] ]])), by = .PARENT.] } set(newConfigurations, j = ".PARENT.", value = as.integer(newConfigurations[[".PARENT."]])) repairConfigurations(newConfigurations, parameters, repair) newConfigurations } sampleModel <- function(parameters, eliteConfigurations, model, nbNewConfigurations, repair = NULL) { if (nbNewConfigurations <= 0) irace_error ("The number of configurations to generate appears to be negative or zero.") newConfigurations <- sample_from_model(parameters, eliteConfigurations, model, nbNewConfigurations, repair) forbidden <- parameters$forbidden if (!is.null(forbidden)) { retries <- 100L repeat { newConfigurations <- filter_forbidden(newConfigurations, forbidden) needed <- nbNewConfigurations - nrow(newConfigurations) if (needed == 0L) break tmp <- sample_from_model(parameters, eliteConfigurations, model, needed, repair = repair) newConfigurations <- rbindlist(list(newConfigurations, tmp)) retries <- retries - 1L if (retries == 0L) { irace_error("irace tried 100 times to sample from the model a configuration not forbidden without success, perhaps your constraints are too strict?") } } } newConfigurations } irace/R/irace.R0000644000176200001440000015412714745735066012777 0ustar liggesusers# FIXME: Restoring occurs after reading the command-line/scenario file. At # least for the irace command-line parameters (scenario), it should occur # before. We would need to: # # 1) Read recovery file settings from command-line/scenario file # # 2) if set, then recover irace scenario # 3) then read other settings from command-line/scenario file being # careful to not override with defaults whatever the recovery has set. # # 4) checkScenario() # # A work-around is to modify the recovery file (you can load it in R, # modify scenario then save it again). recoverFromFile <- function(filename, scenario = list()) { iraceResults <- read_logfile(filename) if (iraceResults$irace_version != irace::irace_version) irace_error("Recovery file '", filename, "' was generated by a version of irace (", iraceResults$irace_version, ") different from this version of irace (", irace::irace_version, ").") # Restore part of scenario but not all. for (name in .irace.params.recover) scenario[[name]] <- iraceResults$scenario[[name]] # We call checkScenario() again to fix any inconsistencies in the recovered data. # FIXME: Do not call checkScenario earlier and instead do the minimum to check recoveryFile. scenario <- checkScenario(scenario) race_state <- iraceResults$state$clone() race_state$initialize(scenario, recover = TRUE) race_state } ## ## Numerical configurations similarity function ## # FIXME: This function is too slow and it shows up in profiles. numeric.configurations.equal <- function(x, configurations, parameters, threshold, param.names) { d <- numeric(nrow(configurations)) isSimilar <- matrix(TRUE, nrow = nrow(configurations), ncol = length(param.names)) selected <- seq_nrow(configurations) for (i in seq_along(param.names)) { pname <- param.names[i] param <- parameters$get(pname) is_dep <- param[["is_dependent"]] x_domain <- if (is_dep) getDependentBound(param, x) else param[["domain"]] x_range <- x_domain[[2L]] - x_domain[[1L]] X <- x[[pname]] # FIXME: Since at the end we select a subset of configurations, we could use selected here. y <- configurations[[pname]] ## FIXME: This can probably done much faster by doing a matrix operation that updates ## isSimilar[, i] in one step instead of the for-loop. ## We would need to handle the NAs first. for (j in seq_len(nrow(isSimilar))) { # Configurations loop Y <- y[selected[j]] if (is.na (X) && is.na(Y)) { # Both NA, just ignore this pname next } else if (xor(is.na (X), is.na(Y))) { # Distance is 1.0, so not equal isSimilar[j,i] <- FALSE } else { # FIXME: Why is this updating d[j]? It seems that if the difference is # large for one configuration, then it will be assumed to be large for # the rest. if (is_dep) { # Compare dependent domains by normalising their values to their own ranges first # and calculating the difference. (When possible) y_domain <- getDependentBound(param, configurations[selected[j],]) y_range <- y_domain[[2L]] - y_domain[[1L]] dx <- if (x_range == 0) 0 else (as.numeric(X) - x_domain[[1L]]) / x_range dy <- if (y_range == 0) 0 else (as.numeric(Y) - y_domain[[1L]]) / y_range d[j] <- max(d[j], abs(dx - dy)) } else { # FIXME: We should calculate (X - x.domain[1]) / x.range once for all configurations # and all parameters, then calculate the differences using vectorization. d[j] <- max(d[j], abs((as.numeric(X) - as.numeric(Y)) / x_range)) } if (d[j] > threshold) isSimilar[j,i] <- FALSE } } index <- which(rowAlls(isSimilar)) isSimilar <- isSimilar[index, , drop=FALSE] d <- d[index] selected <- selected[index] if (nrow(isSimilar) == 0L) break } if (length(selected) == 0L) return(NULL) c(x[[".ID."]], configurations[selected,".ID."]) } ## ## Identify which configurations are similar. ## # FIXME: It would be nice to print the minimum similarity found to the user. similarConfigurations <- function(configurations, parameters, threshold) { # FIXME: Use data.table debug.level <- getOption(".irace.debug.level", 0) if (debug.level >= 1) irace_note ("Computing similarity of configurations .") # Create vectors of categorical and numerical p <- parameters$types %in% c("c","o") vecCat <- parameters$names[p & !parameters$isFixed] vecNum <- parameters$names[!p & !parameters$isFixed] irace_assert(all(parameters$types[vecCat] %in% c("c","o"))) irace_assert(all(parameters$types[vecNum] %not_in% c("c","o"))) irace_assert(length(intersect(vecCat, vecNum)) == 0) nbCater <- length(vecCat) nbNumer <- length(vecNum) ### Categorical/Ordinal filtering #### if (nbCater > 0) { ## Build a vector with the categorical appended together in a string strings <- do.call(paste, c(configurations[, vecCat, drop=FALSE], sep = " ; ")) if (nbNumer != 0) configurations <- configurations[, c(".ID.", vecNum)] ord.strings <- order(strings) configurations <- configurations[ord.strings, ] strings <- strings[ord.strings] ## keep similar (index i == true means is the same as i + 1) similarIdx <- strings[-length(strings)] == strings[-1] ## Now let's get just a FALSE if we remove it, TRUE otherwise: keepIdx <- c(similarIdx[1], (similarIdx[-1] | similarIdx[-length(similarIdx)]), similarIdx[length(similarIdx)]) ## filtering them out: configurations <- configurations [keepIdx, , drop=FALSE] ## filtering their strings out (to use them to define blocks): strings <- strings[keepIdx] ## if everything is already filtered out, return if (nrow(configurations) == 0) { if (debug.level >= 1) cat(" DONE\n") return(NULL) } } ### Numerical parameters within blocks of the same string ### if (nbNumer > 0) { similar <- c() if (nbCater > 0) { ## In this case the object "string" is available to define blocks ## Loop over blocks: beginBlock <- 1L while (beginBlock < nrow(configurations)) { ## The current block is made of all configurations that have the same ## categorical string as the one of configuration[beginBlock, ] blockIds <- which(strings == strings[beginBlock]) endBlock <- blockIds[length(blockIds)] irace_assert (endBlock > beginBlock) ## Loop inside blocks: for (i in seq(beginBlock, endBlock - 1L)) { ## Compare configuration i with all the ones that are after in the block similar <- c(similar, numeric.configurations.equal(configurations[i, ], configurations[(i+1L):endBlock,], parameters, threshold = threshold, param.names = vecNum)) if (debug.level >= 1) cat(".") } beginBlock <- endBlock + 1L # Next block starts after the end of the current one } } else { ## No categorical, so no blocks, just do the basic check without blocks for (i in seq_len(nrow(configurations) - 1L)) { similar <- c(similar, numeric.configurations.equal(configurations[i, ], configurations[(i+1L):nrow(configurations),], parameters, threshold = threshold, param.names = vecNum)) if (debug.level >= 1) cat(".") } } # FIXME: We have to use unique because we return the same configuration # more than once in different calls to numeric.configurations.equal. # Currently, we compare each configuration k=1...n with every configuration # k+1...n. Instead, we should compare k=1...n with ((k+1...n) notin # similar). It may happen that A ~ B and A ~ C and B /= C, but this is OK # because we still return A, B and C. It may also happen that A ~ B, B ~ C # and A /= C, but this is also OK because we will compare A with B,C then B # with C. similar <- unique(similar) configurations <- configurations[configurations[[".ID."]] %in% similar, ] } if (debug.level >= 1) cat(" DONE\n") if (nrow(configurations) == 0L) return(NULL) configurations[[".ID."]] } ## Number of iterations. computeNbIterations <- function(nbParameters) (2 + log2(nbParameters)) ## Computational budget at each iteration. computeComputationalBudget <- function(remainingBudget, indexIteration, nbIterations) floor (remainingBudget / (nbIterations - indexIteration + 1L)) ## The number of configurations computeNbConfigurations <- function(currentBudget, indexIteration, mu, eachTest, blockSize, nElites = 0L, nOldInstances = 0L, newInstances = 0L, maxConfigurations = 1024L) { # FIXME: This is slightly incorrect, because we may have elites that have not # been executed on all nOldInstances. Thus, we need to pass explicitly the # budget that we save (that is, number of entries that are not NA). savedBudget <- nElites * nOldInstances eachTest <- eachTest * blockSize n <- max (mu + eachTest * min(5L, indexIteration), round_to_next_multiple(nOldInstances + newInstances, eachTest)) min (floor ((currentBudget + savedBudget) / n), maxConfigurations) } ## Termination of a race at each iteration. The race will stop if the ## number of surviving configurations is equal or less than this number. computeTerminationOfRace <- function(nbParameters) (2 + log2(nbParameters)) ## Compute the minimum budget required, and exit early in case the ## budget given by the user is insufficient. computeMinimumBudget <- function(scenario, minSurvival, nbIterations, elitist_new_instances) { blockSize <- scenario$blockSize eachTest <- blockSize * scenario$eachTest Tnew <- elitist_new_instances # This is already multiplied by blockSize. mu <- scenario$mu # This is computed from the default formulas as follows: # B_1 = B / I # B_2 = B - (B/I) / (I - 1) = B / I # B_3 = B - 2(B/I) / (I - 2) = B / I # thus # B_i = B / I # and # C_i = B_i / T_i = B / (I * T_i). # # We want to enforce that C_i >= min_surv + 1, thus # B / (I * T_i) >= min_surv + 1 (1) # becomes # B >= (min_surv + 1) * I * T_i # # This is an over-estimation, since actually B_1 = floor(B/I) and if # floor(B/I) < B/I, then B_i < B/I, and we could still satisfy Eq. (1) # with a smaller budget. However, the exact formula requires computing B_i # taking into account the floor() function, which is not obvious. minimumBudget <- (minSurvival + 1L) * nbIterations # We need to compute T_i: if (scenario$elitist) { # T_i = max(mu + Teach * min (5, i), # ceiling((T_{i-1} + Tnew) / Teach) * Teach) # T_1 = mu + Teach # T_2 ~ mu + Teach + max (Teach, Tnew) # T_3 ~ max(mu + 3 * Teach, # mu + Teach + max(Teach, Tnew) + T_new) # = mu + Teach + max(Teach + max(Teach, Tnew), 2 * Tnew) # if Teach > Tnew then 2*Teach > 2*Tnew then max = 2*Teach # if Teach < Tnew then Teach + Tnew < 2*Tnew then max = 2*Tnew # hence: T_3 = mu + Teach + 2 * max(Teach, Tnew) # T_4 = max(mu + 4 * Teach, # ceiling((mu + Teach + 2 * max(Teach, Tnew)) + Tnew) / Teach) * Teach) # ~ mu + Teach + max(2 * Teach + max(Teach, Tnew), 3 * Tnew) # = mu + Teach + 3 * max(Teach, Tnew) # T_i = mu + Teach + (i - 1) * max(Teach, Tnew) # T_6 = max (mu + 5*Teach, # mu + Teach + 5 * max(Teach, Tnew) + Tnew) # = mu + Teach + Tnew + 5 * max (Teach, Tnew) # T_i = mu + Teach + max(I-5, 0) * Tnew + 5 * max (Teach, Tnew) if (nbIterations > 5L) { minimumBudget <- minimumBudget * (mu + eachTest + (nbIterations - 5L) * Tnew + 5L * max(eachTest, Tnew)) } else { minimumBudget <- minimumBudget * (mu + eachTest + (nbIterations - 1L) * max(eachTest, Tnew)) } } else { # T_i = mu + T_each * min (5, i) # and the most strict value is for i >= 5, thus # B >= (min_surv + 1) * I * (mu + 5 * T_each) minimumBudget <- minimumBudget * (mu + 5L * eachTest) } minimumBudget } checkMinimumBudget <- function(scenario, remainingBudget, minSurvival, nbIterations, boundEstimate, timeUsed, elitist_new_instances) { minimumBudget <- computeMinimumBudget(scenario, minSurvival, nbIterations, elitist_new_instances) if (remainingBudget >= minimumBudget) return(TRUE) if (scenario$maxTime == 0) { irace_error("Insufficient budget: ", "With the current settings, irace will require a value of ", "'maxExperiments' of at least '", minimumBudget, "'.") } else if (nbIterations == 1L) { irace_error("Insufficient budget: ", "With the current settings and estimated time per run (", boundEstimate, ") irace will require a value of 'maxTime' of at least '", ceiling((minimumBudget * boundEstimate) + timeUsed), "'.") } FALSE } ## Generate instances + seed. generateInstances <- function(race_state, scenario, n, update = FALSE) { if (!update) race_state$instances_log <- NULL # If we are adding and the scenario is deterministic, we have already added all instances. else if (scenario$deterministic) return(race_state$instances_log) instances <- scenario$instances # Number of times that we need to repeat the set of instances given by the user. ntimes <- if (scenario$deterministic) 1L else # "Upper bound"" of instances needed # FIXME: We could bound it even further if maxExperiments >> nInstances ceiling(n / length(instances)) # Get instances order if (scenario$sampleInstances) { blockSize <- scenario$blockSize n_blocks <- length(instances) / blockSize # Sample instances index in groups (ntimes) selected_blocks <- unlist(lapply(rep.int(n_blocks, ntimes), sample.int, replace = FALSE)) sindex <- c(outer(seq_len(blockSize), (selected_blocks - 1L) * blockSize, "+")) } else { sindex <- rep.int(seq_along(instances), ntimes) } # Sample seeds. race_state$instances_log <- rbindlist(use.names = TRUE, list( race_state$instances_log, data.table(instanceID = sindex, seed = runif_integer(length(sindex))))) race_state$instances_log } do_experiments <- function(race_state, configurations, ninstances, scenario, iteration) { instances <- seq_len(ninstances) output <- race_wrapper_helper(race_state = race_state, configurations = configurations, instance_idx = instances, bounds = rep(scenario$boundMax, nrow(configurations)), is_exe = rep_len(TRUE, nrow(configurations) * ninstances), scenario = scenario) set(output, j = "iteration", value = iteration) Results <- race_state$update_experiment_log(output, instances = instances, scenario = scenario) rejected_ids <- configurations[[".ID."]][colAnys(is.infinite(Results))] scenario$parameters$forbid_configurations( race_state$update_rejected(rejected_ids, configurations) ) Results } ## Initialize allConfigurations with any initial configurations provided. allConfigurationsInit <- function(scenario) { initConfigurations <- scenario$initConfigurations confs_from_file <- NULL if (!is.null.or.empty(scenario$configurationsFile)) { confs_from_file <- readConfigurationsFile(scenario$configurationsFile, scenario$parameters, scenario$debugLevel) } if (is.null.or.empty(initConfigurations)) { initConfigurations <- confs_from_file } else { if (!is.null.or.empty(scenario$configurationsFile) && !identical(initConfigurations, confs_from_file)) irace_warning("'initConfigurations' provided in 'scenario',", " thus ignoring configurations from file '", scenario$configurationsFile, "'.") cat("# Adding", nrow(initConfigurations), "initial configuration(s)\n") initConfigurations <- fix_configurations(initConfigurations, scenario$parameters, debugLevel = scenario$debugLevel) } if (is.null.or.empty(initConfigurations)) { allConfigurations <- configurations_alloc(c(".ID.", scenario$parameters$names, ".PARENT."), nrow = 0L, parameters = scenario$parameters) setDF(allConfigurations) } else { allConfigurations <- cbind(.ID. = seq_nrow(initConfigurations), initConfigurations, .PARENT. = NA_integer_) rownames(allConfigurations) <- allConfigurations[[".ID."]] num <- nrow(allConfigurations) allConfigurations <- checkForbidden(allConfigurations, scenario$parameters$forbidden) if (nrow(allConfigurations) < num) { irace_warning(num - nrow(allConfigurations), " of the ", num, " initial configurations were forbidden", " and, thus, discarded.") } } allConfigurations } ## extractElites # Input: the configurations with the .RANK. field filled. # the number of elites wished # Output: nbElites elites, sorted by ranks, with the weights assigned. extractElites <- function(configurations, nbElites, debugLevel) { irace_assert(nbElites > 0L) # Keep only alive configurations. elites <- as.data.table(configurations) before <- nrow(elites) # Remove duplicated. Duplicated configurations may be generated, however, it # is too slow to check at generation time. Nevertheless, we can check now # since we typically have very few elites. elites <- unique(elites, by=which(!startsWith(colnames(elites), "."))) after <- nrow(elites) if (debugLevel >= 2L && after < before) irace_note("Dropped ", before - after, " duplicated elites.\n") after <- min(after, nbElites) setorderv(elites, cols=".RANK.") selected <- seq_len(after) elites <- elites[selected, ] set(elites, j = ".WEIGHT.", value = ((after + 1L) - selected) / (after * (after + 1L) / 2)) setDF(elites) rownames(elites) <- elites[[".ID."]] elites } #' Execute one run of the Iterated Racing algorithm. #' #' The function `irace` implements the Iterated Racing procedure for parameter #' tuning. It receives a configuration scenario and a parameter space to be #' tuned, and returns the best configurations found, namely, the elite #' configurations obtained from the last iterations. As a first step, it checks #' the correctness of `scenario` using [checkScenario()] and recovers a #' previous execution if `scenario$recoveryFile` is set. A R data file log of #' the execution is created in `scenario$logFile`. #' #' The execution of this function is reproducible under some conditions. See #' the FAQ section in the [User #' Guide](https://cran.r-project.org/package=irace/vignettes/irace-package.pdf). #' #' @inheritParams defaultScenario #' #' @template return_irace #' #' @examples #' \dontrun{ #' # In general, there are three steps: #' scenario <- readScenario(filename = "scenario.txt") #' irace(scenario = scenario) #' } #' ####################################################################### #' # This example illustrates how to tune the parameters of the simulated #' # annealing algorithm (SANN) provided by the optim() function in the #' # R base package. The goal in this example is to optimize instances of #' # the following family: #' # f(x) = lambda * f_rastrigin(x) + (1 - lambda) * f_rosenbrock(x) #' # where lambda follows a normal distribution whose mean is 0.9 and #' # standard deviation is 0.02. f_rastrigin and f_rosenbrock are the #' # well-known Rastrigin and Rosenbrock benchmark functions (taken from #' # the cmaes package). In this scenario, different instances are given #' # by different values of lambda. #' ####################################################################### #' ## First we provide an implementation of the functions to be optimized: #' f_rosenbrock <- function (x) { #' d <- length(x) #' z <- x + 1 #' hz <- z[1L:(d - 1L)] #' tz <- z[2L:d] #' sum(100 * (hz^2 - tz)^2 + (hz - 1)^2) #' } #' f_rastrigin <- function (x) { #' sum(x * x - 10 * cos(2 * pi * x) + 10) #' } #' #' ## We generate 20 instances (in this case, weights): #' weights <- rnorm(20, mean = 0.9, sd = 0.02) #' #' ## On this set of instances, we are interested in optimizing two #' ## parameters of the SANN algorithm: tmax and temp. We setup the #' ## parameter space as follows: #' parameters_table <- ' #' tmax "" i,log (1, 5000) #' temp "" r (0, 100) #' ' #' ## We use the irace function readParameters to read this table: #' parameters <- readParameters(text = parameters_table) #' #' ## Next, we define the function that will evaluate each candidate #' ## configuration on a single instance. For simplicity, we restrict to #' ## three-dimensional functions and we set the maximum number of #' ## iterations of SANN to 1000. #' target_runner <- function(experiment, scenario) #' { #' instance <- experiment$instance #' configuration <- experiment$configuration #' #' D <- 3 #' par <- runif(D, min=-1, max=1) #' fn <- function(x) { #' weight <- instance #' return(weight * f_rastrigin(x) + (1 - weight) * f_rosenbrock(x)) #' } #' # For reproducible results, we should use the random seed given by #' # experiment$seed to set the random seed of the target algorithm. #' res <- withr::with_seed(experiment$seed, #' stats::optim(par,fn, method="SANN", #' control=list(maxit=1000 #' , tmax = as.numeric(configuration[["tmax"]]) #' , temp = as.numeric(configuration[["temp"]]) #' ))) #' ## This list may also contain: #' ## - 'time' if irace is called with 'maxTime' #' ## - 'error' is a string used to report an error #' ## - 'outputRaw' is a string used to report the raw output of calls to #' ## an external program or function. #' ## - 'call' is a string used to report how target_runner called the #' ## external program or function. #' return(list(cost = res$value)) #' } #' #' ## We define a configuration scenario by setting targetRunner to the #' ## function define above, instances to the first 10 random weights, and #' ## a maximum budget of 'maxExperiments' calls to targetRunner. #' scenario <- list(targetRunner = target_runner, #' instances = weights[1:10], #' maxExperiments = 500, #' # Do not create a logFile #' logFile = "", #' parameters = parameters) #' #' ## We check that the scenario is valid. This will also try to execute #' ## target_runner. #' checkIraceScenario(scenario) #' #' \donttest{ #' ## We are now ready to launch irace. We do it by means of the irace #' ## function. The function will print information about its #' ## progress. This may require a few minutes, so it is not run by default. #' tuned_confs <- irace(scenario = scenario) #' #' ## We can print the best configurations found by irace as follows: #' configurations_print(tuned_confs) #' #' ## We can evaluate the quality of the best configuration found by #' ## irace versus the default configuration of the SANN algorithm on #' ## the other 10 instances previously generated. #' test_index <- 11:20 #' test_seeds <- sample.int(2147483647L, size = length(test_index), replace = TRUE) #' test <- function(configuration) #' { #' res <- lapply(seq_along(test_index), #' function(x) target_runner( #' experiment = list(instance = weights[test_index[x]], #' seed = test_seeds[x], #' configuration = configuration), #' scenario = scenario)) #' return (sapply(res, getElement, name = "cost")) #' } #' ## To do so, first we apply the default configuration of the SANN #' ## algorithm to these instances: #' default <- test(data.frame(tmax=10, temp=10)) #' #' ## We extract and apply the winning configuration found by irace #' ## to these instances: #' tuned <- test(removeConfigurationsMetaData(tuned_confs[1,])) #' #' ## Finally, we can compare using a boxplot the quality obtained with the #' ## default parametrization of SANN and the quality obtained with the #' ## best configuration found by irace. #' boxplot(list(default = default, tuned = tuned)) #' } #' @seealso #' \describe{ #' \item{[irace_main()]}{a higher-level interface to [irace()].} #' \item{[irace_cmdline()]}{a command-line interface to [irace()].} #' \item{[readScenario()]}{for reading a configuration scenario from a file.} #' \item{[readParameters()]}{read the target algorithm parameters from a file.} #' \item{[defaultScenario()]}{returns the default scenario settings of \pkg{irace}.} #' \item{[checkScenario()]}{to check that the scenario is valid.} #' } #' @author Manuel López-Ibáñez and Jérémie Dubois-Lacoste #' @concept running #' @export irace <- function(scenario) irace_common(scenario, simple = TRUE) irace_common <- function(scenario, simple, output.width = 9999L) { if (!simple) { withr::local_options(width = output.width) # Do not wrap the output. } scenario <- checkScenario(scenario) debugLevel <- scenario$debugLevel if (debugLevel >= 1L) { op <- list(warning.length = 8170L) if (!base::interactive()) op <- c(op, list(error = irace_dump_frames)) withr::local_options(op) printScenario (scenario) } elite_configurations <- irace_run(scenario = scenario) if (simple) return(elite_configurations) if (!scenario$quiet) { order_str <- test.type.order.str(scenario$testType) cat("# Best configurations (first number is the configuration ID;", " listed from best to worst according to the ", order_str, "):\n", sep = "") configurations_print(elite_configurations) cat("# Best configurations as commandlines (first number is the configuration ID;", " listed from best to worst according to the ", order_str, "):\n", sep = "") configurations_print_command (elite_configurations, scenario$parameters) } testing_fromlog(logFile = scenario$logFile) invisible(elite_configurations) } irace_run <- function(scenario) { # Recover state from file? if (is.null.or.empty(scenario$recoveryFile)) { race_state <- RaceState$new(scenario) } else { irace_note ("Recovering from file: '", scenario$recoveryFile,"'\n") race_state <- recoverFromFile(scenario$recoveryFile, scenario = scenario) } quiet <- scenario$quiet catInfo <- if (quiet) do_nothing else function(..., verbose = TRUE) { irace_note (..., "\n") if (verbose) { cat ("# Iteration: ", indexIteration, "\n", "# nbIterations: ", nbIterations, "\n", "# experimentsUsed: ", experimentsUsed, "\n", "# timeUsed: ", timeUsed, "\n", "# remainingBudget: ", remainingBudget, "\n", "# currentBudget: ", currentBudget, "\n", "# number of elites: ", nrow(elite_configurations), "\n", "# nbConfigurations: ", nbConfigurations, "\n", sep = "") } } # FIXME: use this from psrace? irace_finish <- function(iraceResults, scenario, reason) { elapsed <- race_state$time_elapsed() if (!scenario$quiet) cat("# Total CPU user time: ", elapsed["user"], ", CPU sys time: ", elapsed["system"], ", Wall-clock time: ", elapsed["wallclock"], "\n", sep="") # FIXME: Do we need to clone? race_state$completed <- reason iraceResults$state <- race_state save_irace_logfile(iraceResults, logfile = scenario$logFile) # FIXME: Handle scenario$maxTime > 0 if (scenario$postselection && scenario$maxTime == 0 && floor(remainingBudget / max(scenario$blockSize, scenario$eachTest)) > 1L) psRace(iraceResults, max_experiments = remainingBudget, iteration_elites = TRUE) else elite_configurations } debugLevel <- scenario$debugLevel # Set options controlling debug level. # FIXME: This should be the other way around, the options set the debugLevel. options(.race.debug.level = debugLevel) options(.irace.debug.level = debugLevel) firstRace <- TRUE # Create a data frame of all configurations ever generated. allConfigurations <- allConfigurationsInit(scenario) irace_assert(is.integer(allConfigurations[[".ID."]])) nbUserConfigurations <- nrow(allConfigurations) # To save the logs iraceResults <- list( scenario = scenario, irace_version = irace_version, iterationElites = c(), allElites = list(), experiments = matrix(nrow = 0L, ncol = 0L)) model <- NULL nbConfigurations <- 0L elite_configurations <- data.frame(stringsAsFactors=FALSE) nbIterations <- if (scenario$nbIterations == 0) computeNbIterations(scenario$parameters$nbVariable) else scenario$nbIterations nbIterations <- floor(nbIterations) minSurvival <- if (scenario$minNbSurvival == 0) computeTerminationOfRace(scenario$parameters$nbVariable) else scenario$minNbSurvival minSurvival <- floor(minSurvival) # FIXME: Do this initialization within race_state. race_state$minSurvival <- minSurvival # Generate initial instance + seed list generateInstances(race_state, scenario, n = if (scenario$maxExperiments != 0) ceiling(scenario$maxExperiments / minSurvival) else max(scenario$firstTest, length(scenario$instances))) indexIteration <- 1L experimentsUsed <- 0L timeUsed <- 0 boundEstimate <- NA race_state$start_parallel(scenario) on.exit(race_state$stop_parallel(), add = TRUE) if (scenario$maxTime == 0) { if (is.na(scenario$minExperiments)) { remainingBudget <- scenario$maxExperiments } else { remainingBudget <- max(scenario$minExperiments, computeMinimumBudget(scenario, minSurvival, nbIterations, race_state$elitist_new_instances)) } } else { ## Estimate time when maxTime is defined. ## IMPORTANT: This is firstTest because these configurations will be ## considered elite later, thus preserved up to firstTest, which is ## fine. If a larger number of instances is used, it would prevent ## discarding these configurations. # Get the number of instances to be used. ninstances <- scenario$firstTest * scenario$blockSize estimationTime <- ceiling(scenario$maxTime * scenario$budgetEstimation) irace_note("Estimating execution time using ", 100 * scenario$budgetEstimation, "% of ", scenario$maxTime, " = ", estimationTime, "\n") # Estimate the number of configurations to be used nconfigurations <- max(2L, floor(scenario$parallel / ninstances)) next_configuration <- 1L nruns <- nconfigurations * ninstances boundEstimate <- if (is.null(scenario$boundMax)) 1.0 else scenario$boundMax if (estimationTime < boundEstimate * nruns) { boundEstimate <- max(ceiling_digits(estimationTime / nruns, scenario$boundDigits), scenario$minMeasurableTime) if (!is.null(scenario$boundMax)) { irace_warning("boundMax=", scenario$boundMax, " is too large, using ", boundEstimate, " instead.\n") # FIXME: We should not modify the scenario scenario$boundMax <- boundEstimate } } repeat { # Sample new configurations if needed if (nrow(allConfigurations) < nconfigurations) { newConfigurations <- sampleSobol(scenario$parameters, nconfigurations - nrow(allConfigurations), repair = scenario$repairConfiguration) set(newConfigurations, j = ".ID.", value = max(0L, vlast(allConfigurations[[".ID."]])) + seq_nrow(newConfigurations)) setcolorder(newConfigurations, ".ID.", before=1L) setDF(newConfigurations) # FIXME: use rbindlist(use.names=TRUE) allConfigurations <- rbind(allConfigurations, newConfigurations) rownames(allConfigurations) <- allConfigurations[[".ID."]] # We may have generated less than the number requested if there were duplicates. nconfigurations <- nrow(allConfigurations) } # Estimate the mean execution time. # FIXME: Shouldn't we pass the bounds? experiments <- do_experiments(race_state, configurations = allConfigurations[next_configuration:nconfigurations, ], ninstances = ninstances, scenario = scenario, # These experiments are assigned iteration 0. iteration = 0L) # FIXME: Here we should check if everything timed out and increase the bound dynamically. iraceResults$experiments <- merge_matrix(iraceResults$experiments, experiments) rownames(iraceResults$experiments) <- seq_nrow(iraceResults$experiments) # For the used time, we count the time reported in all configurations # including rejected ones. timeUsed <- sum(race_state$experiment_log[["time"]], na.rm = TRUE) experimentsUsed <- nrow(race_state$experiment_log) # User should return time zero for rejected_ids. boundEstimate <- timeUsed / experimentsUsed boundEstimate <- max(ceiling_digits(boundEstimate, scenario$boundDigits), scenario$minMeasurableTime) next_configuration <- nconfigurations + 1L # Calculate how many new configurations: # 1. We do not want to overrun estimationTime new_conf <- floor(((estimationTime - timeUsed) / boundEstimate) / ninstances) # 2. But there is no point in executing more configurations than those # that we can execute in parallel. new_conf <- min(new_conf, max(1L, floor(scenario$parallel / ninstances))) if (timeUsed >= estimationTime || new_conf == 0L || nconfigurations == 1024L) break else nconfigurations <- min(1024L, nconfigurations + new_conf) } # end of repeat if (length(race_state$rejected_ids)) irace_note ("Immediately rejected configurations: ", paste0(race_state$rejected_ids, collapse = ", ") , "\n") # Update budget remainingBudget <- round((scenario$maxTime - timeUsed) / boundEstimate) elite_configurations <- allConfigurations[allConfigurations[[".ID."]] %not_in% race_state$rejected_ids, , drop = FALSE] irace_assert(is.integer(elite_configurations[[".ID."]])) # Without elitist, the racing does not re-use the results computed during # the estimation. This means that the time used during estimation needs # to be spent again during racing, thus leaving less time for racing. We # want to avoid having less time for racing, and this is an # implementation detail, thus we assume that the time was not actually # wasted. if (!scenario$elitist) timeUsed <- 0 irace_note("Estimated execution time is ", boundEstimate, " based on ", next_configuration - 1L, " configurations and ", ninstances," instances. Used time: ", timeUsed, ", remaining time: ", (scenario$maxTime - timeUsed), ", remaining budget (experiments): ", remainingBudget, "\n") if (!is.null(scenario$boundMax) && 2 * boundEstimate < scenario$boundMax) { irace_warning("boundMax=", scenario$boundMax, " is much larger than estimated execution time, using ", 2 * boundEstimate, " instead.\n") scenario$boundMax <- 2 * boundEstimate } } # end of time estimation # Compute the total initial budget, that is, the maximum number of # experiments that we can perform. currentBudget <- if (scenario$nbExperimentsPerIteration == 0L) computeComputationalBudget(remainingBudget, indexIteration, nbIterations) else scenario$nbExperimentsPerIteration # Check that the budget is enough. For the time estimation case we reduce # the number of iterations. warn_msg <- NULL while (!checkMinimumBudget(scenario, remainingBudget, minSurvival, nbIterations, boundEstimate, timeUsed, race_state$elitist_new_instances)) { if (is.null(warn_msg)) warn_msg <- paste0("With the current settings and estimated time per run (", boundEstimate, ") irace will not have enough budget to execute the minimum", " number of iterations (", nbIterations, "). ", "Execution will continue by assuming that the estimated time", " is too high and reducing the minimum number of iterations,", " however, if the estimation was correct or too low,", " results might not be better than random sampling.\n") nbIterations <- nbIterations - 1L scenario$nbConfigurations <- if (scenario$nbConfigurations > 0L) min(minSurvival * 2L, scenario$nbConfigurations) else minSurvival * 2L } if (!is.null(warn_msg)) irace_warning(warn_msg) catInfo("Initialization\n", if (scenario$elitist) paste0("# Elitist race\n", "# Elitist new instances: ", race_state$elitist_new_instances, "\n", "# Elitist limit: ", scenario$elitistLimit, "\n") else paste0("# Non-elitist race\n"), "# nbIterations: ", nbIterations, "\n", "# minNbSurvival: ", minSurvival, "\n", "# nbParameters: ", scenario$parameters$nbVariable, "\n", "# seed: ", race_state$seed, "\n", "# confidence level: ", scenario$confidence, "\n", "# budget: ", remainingBudget, "\n", if (scenario$maxTime == 0) "" else paste0("# time budget: ", scenario$maxTime - timeUsed, "\n"), "# mu: ", scenario$mu, "\n", "# deterministic: ", scenario$deterministic, "\n", if (scenario$capping) paste0("# capping: ", scenario$cappingType, "\n", "# type bound: ", scenario$boundType, "\n", "# boundMax: ", scenario$boundMax, "\n", "# par bound: ", scenario$boundPar, "\n", "# bound digits: ", scenario$boundDigits, "\n") else if (!is.null(scenario$boundMax)) paste0("# boundMax: ", scenario$boundMax, "\n"), verbose = FALSE) blockSize <- scenario$blockSize repeat { # FIXME: We could directly use race_state$timeUsed everywhere. race_state$timeUsed <- timeUsed ## Save to the log file. iraceResults$allConfigurations <- allConfigurations race_state$save_recovery(iraceResults, logfile = scenario$logFile) # With elitist=TRUE and without targetEvaluator we should never re-run the # same configuration on the same (instance,seed) pair. if (scenario$elitist) { irace_assert(sum(!is.na(iraceResults$experiments)) == experimentsUsed) if (is.null(scenario$targetEvaluator)) irace_assert(experimentsUsed == nrow(race_state$experiment_log)) } if (remainingBudget <= 0) { catInfo("Stopped because budget is exhausted") return(irace_finish(iraceResults, scenario, reason = "Budget exhausted")) } if (scenario$maxTime > 0 && timeUsed >= scenario$maxTime) { catInfo("Stopped because time budget is exhausted") return(irace_finish(iraceResults, scenario, reason = "Time budget exhausted")) } if (indexIteration > nbIterations) { if (scenario$nbIterations == 0L) { nbIterations <- indexIteration } else { if (debugLevel >= 1L) catInfo("Limit of iterations reached", verbose = FALSE) return(irace_finish(iraceResults, scenario, reason = "Limit of iterations reached")) } } # Compute the current budget (nb of experiments for this iteration), # or take the value given as parameter. currentBudget <- if (scenario$nbExperimentsPerIteration == 0L) computeComputationalBudget(remainingBudget, indexIteration, nbIterations) else scenario$nbExperimentsPerIteration # Compute the number of configurations for this race. if (scenario$elitist && !firstRace) { nbConfigurations <- computeNbConfigurations(currentBudget, indexIteration, mu = scenario$mu, eachTest = scenario$eachTest, blockSize = blockSize, nElites = nrow(elite_configurations), nOldInstances = nrow(iraceResults$experiments), newInstances = race_state$elitist_new_instances) # If we don't have enough budget, do not evaluate new instances. if (nbConfigurations <= minSurvival) { race_state$elitist_new_instances <- 0L nbConfigurations <- computeNbConfigurations(currentBudget, indexIteration, mu = scenario$mu, eachTest = scenario$eachTest, blockSize = blockSize, nElites = nrow(elite_configurations), nOldInstances = nrow(iraceResults$experiments), newInstances = race_state$elitist_new_instances) } # If still not enough budget, then try to do at least one test. if (nbConfigurations <= minSurvival) { nbConfigurations <- computeNbConfigurations(currentBudget, indexIteration = 1L, mu = 1L, eachTest = scenario$eachTest, blockSize = blockSize, nElites = nrow(elite_configurations), nOldInstances = nrow(iraceResults$experiments), newInstances = 0L) } } else { nbConfigurations <- computeNbConfigurations(currentBudget, indexIteration, mu = scenario$mu, eachTest = scenario$eachTest, blockSize = blockSize, nElites = 0L, nOldInstances = 0L, newInstances = 0L) } # If a value was given as a parameter, then this value limits the maximum, # but if we have budget only for less than this, then we have run out of # budget. if (scenario$nbConfigurations > 0L) { if (scenario$nbConfigurations <= nbConfigurations) { nbConfigurations <- scenario$nbConfigurations } else if (currentBudget < remainingBudget) { # We skip one iteration catInfo("Not enough budget for this iteration, skipping to the next one.") indexIteration <- indexIteration + 1L next } else { catInfo("Stopped because ", "there is not enough budget to enforce the value of nbConfigurations.") return(irace_finish(iraceResults, scenario, reason = "Not enough budget to enforce the value of nbConfigurations")) } } # Stop if the number of configurations to test is NOT larger than the minimum. if (nbConfigurations <= minSurvival) { catInfo("Stopped because there is not enough budget left to race more than ", "the minimum (", minSurvival,").\n", "# You may either increase the budget or set 'minNbSurvival' to a lower value.") return(irace_finish(iraceResults, scenario, reason = "Not enough budget to race more than the minimum configurations")) } # If we have too many elite_configurations, reduce their number. This can # happen before the first race due to the initial budget estimation. if (firstRace) { if (nbConfigurations < nrow(elite_configurations)) { eliteRanks <- overall_ranks(iraceResults$experiments, test = scenario$testType) elite_configurations <- elite_configurations[order(eliteRanks), ] elite_configurations <- elite_configurations[seq_len(nbConfigurations), ] } } else if (nbConfigurations <= nrow(elite_configurations)) { # Stop if the number of configurations to produce is not greater than # the number of elites. catInfo("Stopped because ", "there is not enough budget left to race newly sampled configurations.") #(number of elites + 1) * (mu + min(5, indexIteration)) > remainingBudget" return(irace_finish(iraceResults, scenario, reason = "Not enough budget left to race newly sampled configurations")) } if (scenario$elitist) { # The non-elite have to run up to the first test. The elites consume # budget at most up to the new instances. if ((nbConfigurations - nrow(elite_configurations)) * scenario$mu + nrow(elite_configurations) * min(race_state$elitist_new_instances, scenario$mu) > currentBudget) { catInfo("Stopped because there is not enough budget left to race all configurations up to the first test (or mu).") return(irace_finish(iraceResults, scenario, reason = "Not enough budget to race all configurations up to the first test (or mu)")) } } else if (nbConfigurations * scenario$mu > currentBudget) { catInfo("Stopped because there is not enough budget left to race all configurations up to the first test (or mu).") return(irace_finish(iraceResults, scenario, reason = "Not enough budget to race all configurations up to the first test (or mu)")) } catInfo("Iteration ", indexIteration, " of ", nbIterations, "\n", "# experimentsUsed: ", experimentsUsed, "\n", if (scenario$maxTime == 0) "" else paste0("# timeUsed: ", timeUsed, "\n", "# boundEstimate: ", boundEstimate, "\n"), "# remainingBudget: ", remainingBudget, "\n", "# currentBudget: ", currentBudget, "\n", "# nbConfigurations: ", nbConfigurations, verbose = FALSE) iraceResults$softRestart[indexIteration] <- FALSE # Sample for the first time. if (firstRace) { # If we need more configurations, sample uniformly. nbNewConfigurations <- nbConfigurations - sum(allConfigurations[[".ID."]] %not_in% race_state$rejected_ids) if (nbNewConfigurations > 0L) { # Sample new configurations. if (debugLevel >= 1L) { catInfo("Sample ", nbNewConfigurations, " configurations from Sobol distribution", verbose = FALSE) } newConfigurations <- sampleSobol(scenario$parameters, nbNewConfigurations, repair = scenario$repairConfiguration) # We could get fewer than we asked for due to removing duplicates and forbidden. nbNewConfigurations <- nrow(newConfigurations) set(newConfigurations, j= ".ID.", value = max(0L, vlast(allConfigurations[[".ID."]])) + seq_nrow(newConfigurations)) setcolorder(newConfigurations, ".ID.", before=1L) setDF(newConfigurations) allConfigurations <- rbind(allConfigurations, newConfigurations) rownames(allConfigurations) <- allConfigurations[[".ID."]] raceConfigurations <- allConfigurations[allConfigurations[[".ID."]] %not_in% race_state$rejected_ids, , drop = FALSE] } else if (nbNewConfigurations <= 0L) { # We let the user know that not all configurations will be used. if (nbUserConfigurations > nbConfigurations) { catInfo("Only ", nbConfigurations, " from the initial configurations will be used", verbose = FALSE) } # This is made only in case that the number of configurations used in # the time estimation is more than needed. if (nrow(elite_configurations) == nbConfigurations) { raceConfigurations <- elite_configurations } else { raceConfigurations <- allConfigurations[allConfigurations[[".ID."]] %not_in% race_state$rejected_ids, , drop = FALSE] raceConfigurations <- raceConfigurations[seq_len(nbConfigurations), , drop = FALSE] } } # end of indexIteration == 1 } else { # How many new configurations should be sampled? nbNewConfigurations <- nbConfigurations - nrow(elite_configurations) # Update the model based on elites configurations if (debugLevel >= 1L) irace_note("Update model\n") model <- updateModel(scenario$parameters, elite_configurations, model, indexIteration, nbIterations, nbNewConfigurations, elitist = scenario$elitist) if (debugLevel >= 2L) printModel (model) if (debugLevel >= 1L) irace_note("Sample ", nbNewConfigurations, " configurations from model\n") irace_assert(is.integer(elite_configurations[[".ID."]])) newConfigurations <- sampleModel(scenario$parameters, elite_configurations, model, nbNewConfigurations, repair = scenario$repairConfiguration) # Set ID of the new configurations. set(newConfigurations, j = ".ID.", value = vlast(allConfigurations[[".ID."]]) + seq_nrow(newConfigurations)) setcolorder(newConfigurations, ".ID.", before=1L) setDF(newConfigurations) raceConfigurations <- rbind(elite_configurations[, colnames(newConfigurations)], newConfigurations) rownames(raceConfigurations) <- raceConfigurations[[".ID."]] if (scenario[["softRestart"]]) { # Rprof("profile.out") restart_ids <- similarConfigurations (raceConfigurations, scenario$parameters, threshold = scenario$softRestartThreshold) # Rprof(NULL) if (!is.null(restart_ids)) { if (debugLevel >= 1L) irace_note("Soft restart: ", paste(collapse = " ", restart_ids), " !\n") model <- restartModel(model, raceConfigurations, restart_ids, scenario$parameters, nbNewConfigurations) iraceResults$softRestart[indexIteration] <- TRUE if (debugLevel >= 2L) { printModel (model) } # Re-sample after restart like above #cat("# ", format(Sys.time(), usetz=TRUE), " sampleModel()\n") newConfigurations <- sampleModel(scenario$parameters, elite_configurations, model, nbNewConfigurations, repair = scenario$repairConfiguration) #cat("# ", format(Sys.time(), usetz=TRUE), " sampleModel() DONE\n") # Set ID of the new configurations. # Set ID of the new configurations. set(newConfigurations, j = ".ID.", value = vlast(allConfigurations[[".ID."]]) + seq_nrow(newConfigurations)) setcolorder(newConfigurations, ".ID.", before=1L) setDF(newConfigurations) raceConfigurations <- rbind(elite_configurations[, colnames(newConfigurations)], newConfigurations) rownames(raceConfigurations) <- raceConfigurations[[".ID."]] } } # Append these configurations to the global table. allConfigurations <- rbind(allConfigurations, newConfigurations) rownames(allConfigurations) <- allConfigurations[[".ID."]] } if (debugLevel >= 2L) { irace_note("Configurations for the race n ", indexIteration, " (elite configurations listed first, then new configurations):\n") configurations_print(raceConfigurations, metadata = TRUE) } # Get data from previous races. elite_data <- if (scenario$elitist && nrow(elite_configurations)) iraceResults$experiments[, as.character(elite_configurations[[".ID."]]), drop=FALSE] else NULL race_state$next_instance <- nrow(iraceResults$experiments) + 1L # Add instances if needed. # Calculate budget needed for old instances assuming non elitist irace. if ((nrow(race_state$instances_log) - (race_state$next_instance - 1L)) < ceiling(remainingBudget / minSurvival)) { generateInstances(race_state, scenario, n = ceiling(remainingBudget / minSurvival), update = TRUE) } if (debugLevel >= 1L) irace_note("Launch race\n") raceResults <- elitist_race (race_state, scenario = scenario, configurations = raceConfigurations, maxExp = currentBudget, minSurvival = minSurvival, elite_data = elite_data, elitist_new_instances = if (firstRace) 0L else race_state$elitist_new_instances) # We add indexIteration as an additional column. set(raceResults$experiment_log, j = "iteration", value = indexIteration) # FIXME: There is a chance that the process stops after we remove # race_experiment_log in elitist_race(), but before we update # race_state$experiment_log here. Doing the two steps in a different order # would be more robust but would need a smarter recovery routine that # checks for duplicates. race_state$experiment_log <- rbindlist(list(race_state$experiment_log, raceResults$experiment_log), use.names=TRUE) # Merge new results. iraceResults$experiments <- merge_matrix(iraceResults$experiments, raceResults$experiments) # Update remaining budget. experimentsUsed <- experimentsUsed + raceResults$experimentsUsed if (scenario$maxTime > 0L) { timeUsed <- timeUsed + sum(raceResults$experiment_log[["time"]], na.rm = TRUE) boundEstimate <- timeUsed / experimentsUsed remainingBudget <- round((scenario$maxTime - timeUsed) / boundEstimate) } else { remainingBudget <- remainingBudget - raceResults$experimentsUsed } if (debugLevel >= 3L) { irace_note("Results for the race of iteration ", indexIteration, " (from best to worst, according to the ", test.type.order.str(scenario$testType), "):\n") configurations_print(raceResults$configurations, metadata = TRUE) } if (debugLevel >= 1L) irace_note("Extracting elites\n") elite_configurations <- extractElites(raceResults$configurations, nbElites = minSurvival, debugLevel = scenario$debugLevel) irace_note("Elite configurations (first number is the configuration ID;", " listed from best to worst according to the ", test.type.order.str(scenario$testType), "):\n") if (!quiet) configurations_print(elite_configurations, metadata = debugLevel >= 1L) iraceResults$iterationElites[indexIteration] <- elite_configurations[[".ID."]][1L] iraceResults$allElites[[indexIteration]] <- elite_configurations[[".ID."]] if (firstRace) { if (debugLevel >= 1L) irace_note("Initialise model\n") model <- initialiseModel(scenario$parameters, elite_configurations) if (debugLevel >= 2L) printModel (model) firstRace <- FALSE } if (debugLevel >= 1L) { irace_note("End of iteration ", indexIteration, "\n") if (debugLevel >= 3L) { irace_note("All configurations (sampling order):\n") configurations_print(allConfigurations, metadata = TRUE) irace_note("Memory used in irace():\n") race_state$print_mem_used() } } indexIteration <- indexIteration + 1L } # end of repeat } irace/R/version.R0000644000176200001440000000016714752430256013362 0ustar liggesusers#' A character string containing the version of `irace` including git SHA. #' @export irace_version <- '4.2.0.b50b134' irace/R/timer.R0000644000176200001440000000076714736526233013026 0ustar liggesusersTimer <- R6::R6Class("Timer", cloneable = TRUE, lock_class = TRUE, lock_objects = TRUE, public = list( start = NULL, initialize = function() { self$start <- proc.time() }, elapsed = function() { x <- proc.time() - self$start if (!is.na(x[[4L]])) x[[1L]] <- x[[1L]] + x[[4L]] if (!is.na(x[[5L]])) x[[2L]] <- x[[2L]] + x[[5L]] c(user=x[[1L]], system=x[[2L]], wallclock=x[[3L]]) }, wallclock = function() { proc.time()[[3L]] - self$start[[3L]] }) ) irace/R/tnorm.R0000644000176200001440000001040014736526233013026 0ustar liggesusers## The naive method would be: ## ## rtnorm.naive <- function(n, mean = 0, sd = 1, lower, upper) ## { ## lower <- (lower - mean) / sd ## Algorithm works on mean 0, sd 1 scale ## upper <- (upper - mean) / sd ## x <- rnorm(n) ## repeat { ## w <- which( x > upper | x < lower) ## if (length(w) == 0) break ## x[w] <- rnorm(length(w)) ## } ## return(x * sd + mean) ## } ## ## There is also https://cran.r-project.org/web/packages/truncnorm/ ## Function to sample according to a truncated normal distribution ## This function comes from the R package 'msm' maintained by ## Christopher Jackson # # Package: msm # Version: 1.7.1 # Date: 2023-03-23 # Title: Multi-State Markov and Hidden Markov Models in Continuous Time # Author: Christopher Jackson # Maintainer: Christopher Jackson # Description: Functions for fitting continuous-time Markov and hidden # Markov multi-state models to longitudinal data. Designed for # processes observed at arbitrary times in continuous time (panel data) # but some other observation schemes are supported. Both Markov # transition rates and the hidden Markov output process can be modelled # in terms of covariates, which may be constant or piecewise-constant # in time. # License: GPL (>= 2) # Imports: # survival,mvtnorm,expm # Suggests: # mstate,minqa,doParallel,foreach,numDeriv,testthat,flexsurv # URL: https://github.com/chjackson/msm # # Repository: CRAN ## Rejection sampling algorithm by Robert (Stat. Comp (1995), 5, 121-5) ## for simulating from the truncated normal distribution. rtnorm <- function (n, mean = 0, sd = 1, lower, upper) { stopifnot(length(n) == 1L && length(mean) == 1L && length(sd) == 1L && length(lower) == 1L && length(upper) == 1L) stopifnot(is.finite(lower) && is.finite(upper)) lower <- (lower - mean) / sd # Algorithm works on mean 0, sd 1 scale upper <- (upper - mean) / sd sdzero <- (sd < .Machine$double.eps) nas <- is.na(mean) || is.na(sd) || (sdzero && (lower > mean || mean > upper)) ret <- rep_len(NaN, n) if (nas) { warning("NAs produced") return(ret) # return NaN } else if (lower > upper) { return(ret) # return NaN } else if (sdzero) { return(rep_len(mean, n)) # SD zero, so set the sampled value to the mean. } else if ((lower < 0) && (upper > 0) && (upper - lower > sqrt(2*pi))) { # standard "simulate from normal and reject if outside limits" method. Use if bounds are wide. ind.no <- seq_len(n) while (length(ind.no) > 0) { y <- rnorm(length(ind.no)) done <- (y >= lower & y <= upper) ret[ind.no[done]] <- y[done] ind.no <- ind.no[!done] } } else if (lower >= 0 && (upper > lower + 2*sqrt(exp(1)) / (lower + sqrt(lower^2 + 4)) * exp((lower*2 - lower*sqrt(lower^2 + 4)) / 4))) { # rejection sampling with exponential proposal. Use if lower >> mean ind.expl <- seq_len(n) a <- (sqrt(lower^2 + 4) + lower) / 2 while (length(ind.expl) > 0) { z <- rexp(length(ind.expl), a) + lower u <- runif(length(ind.expl)) done <- (u <= exp(-(z - a)^2 / 2)) & (z <= upper) ret[ind.expl[done]] <- z[done] ind.expl <- ind.expl[!done] } } else if (upper <= 0 && (-lower > -upper + 2*sqrt(exp(1)) / (-upper + sqrt(upper^2 + 4)) * exp((upper*2 - -upper*sqrt(upper^2 + 4)) / 4))) { # rejection sampling with exponential proposal. Use if upper << mean. ind.expu <- seq_len(n) a <- (sqrt(upper^2 +4) - upper) / 2 while (length(ind.expu) > 0) { z <- rexp(length(ind.expu), a) - upper u <- runif(length(ind.expu)) done <- (u <= exp(-(z - a)^2 / 2)) & (z <= -lower) ret[ind.expu[done]] <- -z[done] ind.expu <- ind.expu[!done] } } else { # rejection sampling with uniform proposal. Use if bounds are narrow and central. ind.u <- seq_len(n) K <- if (lower > 0) lower^2 else if (upper < 0) upper^2 else 0 while (length(ind.u) > 0) { z <- runif(length(ind.u), lower, upper) rho <- exp((K - z^2) / 2) u <- runif(length(ind.u)) done <- (u <= rho) ret[ind.u[done]] <- z[done] ind.u <- ind.u[!done] } } ret*sd + mean } irace/R/path_rel2abs.R0000644000176200001440000000274214736603236014246 0ustar liggesusers#' Converts a relative path to an absolute path. #' #' If the path passed corresponds to an executable, it tries to find its path #' using [Sys.which()]. Expansion of `'~'` in Windows follows the definition #' of [fs::path_expand()] rather than [base::path.expand()]. This function #' tries really hard to create canonical paths. #' #' @param path (`character(1)`) Character string representing a relative path. #' @param cwd (`character(1)`) Current working directory. #' #' @return (`character(1)`) Character string representing the absolute path #' #' @examples #' path_rel2abs("..") #' @export path_rel2abs <- function (path, cwd = getwd()) { if (path == "") return("") # FIXME: split this function into two, one for executable files than handles # finding executables in the PATH with Sys.which() and another for everything # else that doesn't try to be that smart. # We need to do this even with the path returned by `getwd()`. cwd <- fs::path_norm(fs::path_expand(cwd)) if (fs::is_absolute_path(path)) { # Possibly expand ~/path to /home/user/path. path <- fs::path_expand(path) } else if (!startsWith(path, ".")) { # it may be a command in the path. sys_path <- suppressWarnings(Sys.which(path)) if (sys_path != "" && fs::file_access(sys_path, "execute") # fs::is_dir() is broken: https://github.com/r-lib/fs/issues/440 && !file.info(sys_path)$isdir) { path <- as.vector(sys_path) } } as.character(fs::path_abs(path, start = cwd)) } irace/R/aaa.R0000644000176200001440000000212214736603236012412 0ustar liggesusersupdate_package_version <- function() { dcf <- read.dcf(file = "DESCRIPTION", fields=c("Version", "RemoteSha")) if (NROW(dcf) < 1L) return(invisible()) dcf <- as.list(dcf[1L, ]) git_rev <- dcf$RemoteSha if (is.na(git_rev)) git_rev <- "unknown" git <- Sys.which("git") if (git != "" && fs::file_exists(".git") && grepl("[0-9a-z]+$", system2(git, "describe --first-parent --always", stdout = TRUE), perl=TRUE)) { git_rev <- system2(git, "describe --dirty --first-parent --always --exclude '*'", stdout = TRUE) } if (git_rev != "unknown") { realversion <- paste0(dcf$Version, ".", git_rev) cat(file='./R/version.R', sep='', "#' A character string containing the version of `irace` including git SHA.\n#' @export\nirace_version <- '", realversion, "'\n") } invisible() } update_package_version() # We define this tentatively to avoid: undefined exports: irace_version irace_version <- "unknown" .irace_tolerance <- sqrt(.Machine$double.eps) .irace_minimum_saving_time <- 60 # seconds # Prefix for printing messages to the user. .irace_msg_prefix <- "== irace == " irace/R/ablation.R0000644000176200001440000007663614745735066013515 0ustar liggesusers.ablation.params.def <- utils::read.table(header=TRUE, stringsAsFactors = FALSE, text=" name ab type short long default description iraceResults 0 p -l --log-file NA 'Path to the (.Rdata) file created by irace from which the \"iraceResults\" object will be loaded.' src 1 i -S --src 1 'Source configuration ID or the path to a file containing the configuration.' target 1 i -T --target NA 'Target configuration ID (by default the best configuration found by irace) or the path to a file containing the configuration.' ab_params 1 s -P --params '' 'Specific parameter names to be used for the ablation (separated with commas). By default use all' type 1 s -t --type 'full' 'Type of ablation to perform: \"full\" will execute each configuration on all \"--n-instances\" to determine the best-performing one; \"racing\" will apply racing to find the best configurations.' nrep 1 i -n --nrep 1 'Number of replications per instance used in \"full\" ablation.' seed 1 i '' --seed 1234567 'Integer value to use as seed for the random number generation.' ablationLogFile 1 p -o --output-file 'log-ablation.Rdata' 'Log file to save the ablation log. If \"\", the results are not saved to a file.' instancesFile 1 s '' --instances-file 'train' 'Instances file used for ablation: \"train\", \"test\" or a filename containing the list of instances.' plot 0 s -p --plot '' 'Output filename (.pdf) for the plot. If not given, no plot is created.' plot_type 0 s -O --plot-type 'mean' 'Type of plot. Supported values are \"mean\", \"boxplot\", \"rank\" or \"rank,boxplot\".' old_path 0 p '' --old-path NA 'Old path found in the log-file (.Rdata) given as input to be replaced by --new-path.' new_path 0 p '' --new-path NA 'New path to replace the path found in the log-file (.Rdata) given as input.' execDir 0 p -e --exec-dir NA 'Directory where the target runner will be run.' scenarioFile 0 p -s --scenario NA 'Scenario file to override the scenario given in the log-file (.Rdata)' parallel 0 i '' --parallel NA 'Number of calls to targetRunner to execute in parallel. Values 0 or 1 mean no parallelization.' ") ## __VERSION__ below will be replaced by the version defined in R/version.R ## This avoids constant conflicts within this file. cat_ablation_license <- function() { ablation_license <- '#------------------------------------------------------------------------------ # ablation: An implementation in R of Ablation Analysis # Version: __VERSION__ # Copyright (C) 2020--2025 # Manuel Lopez-Ibanez # Leslie Perez Caceres # # This is free software, and you are welcome to redistribute it under certain # conditions. See the GNU General Public License for details. There is NO # WARRANTY; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. #------------------------------------------------------------------------------ ' cat(sub("__VERSION__", irace_version, ablation_license, fixed=TRUE)) } #' Launch ablation with command-line options. #' #' Launch [ablation()] with the same command-line options as the command-line #' executable (`ablation.exe` in Windows). #' #' @param argv `character()`\cr The arguments #' provided on the R command line as a character vector, e.g., #' `c("-i", "irace.Rdata", "--src", 1)`. #' #' @details The function reads the parameters given on the command line #' used to invoke R, launches [ablation()] and possibly [plotAblation()]. #' #' List of command-line options: #' ```{r echo=FALSE,comment=NA} #' cmdline_usage(.ablation.params.def) #' ``` #' @return A list containing the following elements: #' \describe{ #' \item{allConfigurations}{Configurations tested in the ablation.} #' \item{state}{State of the ablation process.} #' \item{experiments}{A matrix with the results of the experiments (columns are configurations, rows are instances).} #' \item{scenario}{Scenario object with the settings used for the experiments.} #' \item{trajectory}{IDs of the best configurations at each step of the ablation.} #' \item{best}{Best configuration found in the experiments.} #' \item{complete}{`TRUE` if the ablation process was completed.} #' } #' #' @seealso [plotAblation()] [ablation()] #' @examples #' ablation_cmdline("--help") #' # Find the ablation command-line executable: #' Sys.glob(file.path(system.file(package="irace", "bin"), "ablation*")) #' @author Manuel López-Ibáñez #' @concept ablation #' @export ablation_cmdline <- function(argv = commandArgs(trailingOnly = TRUE)) { withr::local_options(list(width = 9999L)) # Do not wrap the output. cat_ablation_license() cat ("# installed at: ", system.file(package="irace"), "\n", "# called with: ", paste(argv, collapse = " "), "\n", sep = "") parser <- CommandArgsParser$new(argv = argv, argsdef = .ablation.params.def) if (!is.null(parser$readArg (short = "-h", long = "--help"))) { parser$cmdline_usage() return(invisible(NULL)) } if (!is.null(parser$readArg (short = "-v", long = "--version"))) { print(utils::citation(package="irace")) return(invisible(NULL)) } params <- parser$readAll() # TODO: Send the other options to the irace command-line parser so the user # can override options in scenario via the command-line. if (length(parser$argv) > 0L) stop("Unknown command-line options: ", paste(parser$argv, collapse = " ")) if (is.null(params$iraceResults)) { irace_error("You must provide the path to the '.Rdata' file that contains the 'iraceResults' object generated by irace.") return(invisible(NULL)) } iraceResults <- read_logfile(params$iraceResults) if (is.null(params$old_path) != is.null(params$new_path)) { irace_error("To update paths you must provide both --old-path and --new-path.") return(invisible(NULL)) } else if (!is.null(params$old_path)) { iraceResults$scenario <- scenario_update_paths(iraceResults$scenario, params$old_path, params$new_path) } scenario <- list() if (!is.null(params$scenarioFile)) { scenario <- readScenario(params$scenarioFile) # We do not want this seed value to override the the command-line. scenario$seed <- NULL } for (p in c("execDir", "parallel")) { if (!is.null(params[[p]])) scenario[[p]] <- params[[p]] } if (is_null_or_empty_or_na(trim(params$ablationLogFile))) { params$ablationLogFile <- NULL } else { params$ablationLogFile <- path_rel2abs(params$ablationLogFile) } if (!is.null(params$ab_params)) params$ab_params <- trimws(strsplit(params$ab_params, ",", fixed=TRUE)[[1L]]) # The shell may introduce extra quotes, remove them. params$plot_type <- trimws(gsub("[\"']", "", params$plot_type)) # We want to select elements that actually appear in params, otherwise we get NA names. ablation_params <- intersect(.ablation.params.def[.ablation.params.def$ab == 1, "name", drop=TRUE], names(params)) ablog <- do.call(ablation, args = c(list(iraceResults = iraceResults), params[ablation_params], scenario)) if (!is.null(params[["plot"]]) || base::interactive()) { plotAblation(ablog, pdf_file = params[["plot"]], type = params$plot_type) } invisible(ablog) } fix_dependents <- function(parameters, dep_names, new_conf, final_conf, changed) { # ... check every dependent parameter... for (dep in dep_names) { # If the conditions are satisfied and it doesn't have a value but it # should, then change it. If the conditions are not satisfied and it # has a value but it shouldn't, then change it. ok <- conditionsSatisfied(parameters$conditions[[dep]], new_conf) change <- ok == is.na(new_conf[[dep]]) dep_param <- parameters$get(dep) if (change) { # If it doesn't have a value but it should, take it from final. if (ok) { if (dep_param[["is_dependent"]]) { # If the parameter is domain-dependent, then: if (anyNA(new_conf[, .SD, .SDcols=all.vars(dep_param$domain)])) { # If it depends on a parameter that is disabled, then this is disabled, so no need to change. next } else if (is.na(final_conf[[dep]]) # If the final value is NA, we cannot fix it, so skip it. # If parameter X enables dep but dep has domain that depends on # parameter Y, then Y in new_conf may have a value that makes # final_conf[[dep]] invalid. We cannot fix this, so skip. || !is_within_dependent_bound(dep_param, new_conf, value = final_conf[[dep]])) return(NULL) } set(new_conf, j = dep, value = final_conf[[dep]]) } else { set(new_conf, j = dep, value = NA) } changed <- c(changed, dep) } else if (dep_param[["is_dependent"]] && !is.na(new_conf[[dep]])) { # The condition may not have changed but the domain may depend on a parameter that has been changed. if (anyNA(new_conf[, .SD, .SDcols=all.vars(dep_param$domain)])) { set(new_conf, j = dep, value = NA) changed <- c(changed, dep) } else if (!is_within_dependent_bound(dep_param, new_conf, value = new_conf[[dep]])) { # If the current value is not within bound and the final value is NA, we cannot fix it, so skip it. if (is.na(final_conf[[dep]]) # If parameter X enables dep but dep has domain that depends on # parameter Y, then Y in new_conf may have a value that makes # final_conf[[dep]] invalid. We cannot fix this, so skip. || !is_within_dependent_bound(dep_param, new_conf, value = final_conf[[dep]])) return(NULL) set(new_conf, j = dep, value = final_conf[[dep]]) changed <- c(changed, dep) } } } changed } ## Function that generates the configurations of the ablation path ## between initial_configuration and final_configuration. ## parameters can be selected by specifying them in para.names. generate_ablation <- function(initial_configuration, final_configuration, parameters, param_names) { # Only change variable parameters param_names <- setdiff(param_names, parameters$names_fixed) # Follow the hierarchy order. hierarchy <- sort(parameters$hierarchy) param_names <- intersect(names(parameters$hierarchy), param_names) configurations <- list() changed_params <- list() # skip if the value is the same or NA (not active) skip <- (initial_configuration[param_names] == final_configuration[param_names]) skip <- skip[1L, ] # Drop to a vector with names skip <- skip | is.na(skip) for (pname in param_names) { # If parameter is not active, it will only change if its condition becomes true. if (skip[[pname]]) next param <- parameters$get(pname) # If the parameter is domain-dependent, then we can only change its # value if the new value is within the domain. if (param[["is_dependent"]] && !is_within_dependent_bound(param, initial_configuration, value = final_configuration[[pname]])) next new_configuration <- as.data.table(initial_configuration) set(new_configuration, j = pname, value = final_configuration[[pname]]) changed <- pname # If other parameters depend on this one then... if (any(sapply(parameters$depends, function(x) any(pname %in% x)))) { changed <- fix_dependents(parameters, dep_names = names(hierarchy)[hierarchy > hierarchy[[pname]]], new_conf = new_configuration, final_conf = final_configuration, changed = changed) if (is.null(changed)) next } new_configuration <- filter_forbidden(new_configuration, parameters$forbidden) if (nrow(new_configuration) == 0L) next changed_params <- c(changed_params, list(changed)) configurations <- c(configurations, list(new_configuration)) } configurations <- rbindlist(configurations) set(configurations, j = ".PARENT.", value = configurations[[".ID."]]) setDF(configurations) rownames(configurations) <- NULL list(configurations=configurations, changed_params=changed_params) } report_duplicated_results <- function(experiments, configurations) { x <- t(experiments) x <- x[duplicated(x) | duplicated(x, fromLast = TRUE), , drop=FALSE] if (nrow(x) == 0L) return(NULL) # No duplicates dups <- split(rownames(x), apply(x, 1L, paste0, collapse="")) names(dups) <- NULL for (g in dups) { cat("Warning: The following configurations are different but produced the same results:\n") df <- configurations[configurations[[".ID."]] %in% g, , drop=FALSE] print(df) cat("Parameters with different values from the above configurations:\n") df <- removeConfigurationsMetaData(df) print(df[, vapply(df, function(x) length(unique(x)) > 1L, logical(1L)), drop=FALSE]) } dups } ab_generate_instances <- function(race_state, scenario, nrep, type, instancesFile) { nrep <- suppressWarnings(as.integer(nrep)) if (is.na(nrep) || length(nrep) == 0L || nrep <= 0L) stop("'nrep' must be an integer larger than zero") if (nrep != 1L && type == "racing") stop("'nrep' has no effect when type == 'racing'") if (nrep > 1L && scenario$deterministic) stop("'nrep > 1' does not make sense with a deterministic scenario") if (instancesFile == "test") { scenario$instances <- scenario$testInstances } else if (instancesFile != "train") { scenario$instances <- readInstances(instancesFile = path_rel2abs(instancesFile)) } n_inst <- length(scenario$instances) if (type == "full" && n_inst * nrep == 1L) stop("'nrep' must be larger than 1 when type == 'full' and a single instance") generateInstances(race_state, scenario, n_inst * nrep) msg <- if (instancesFile %in% c("train", "test")) paste0("'", instancesFile, "' instances") else paste0("instances from '", instancesFile, "'") if (n_inst > 100L) { n_inst <- 100L msg <- paste0(msg, " (only showing first 100)") } cat(sep="", "# Using ", msg, ":\n", paste0(collapse="\n", scenario$instances[1L:n_inst]), "\n") scenario$instances } #' Performs ablation between two configurations (from source to target). #' #' @description Ablation is a method for analyzing the differences between two configurations. #' #' @inheritParams has_testing_data #' @param src,target `integer(1)|character(1)`\cr Source and target configuration IDs. By default, the first configuration ever evaluated (ID 1) is used as `src` and the best configuration found by irace is used as target. If the argument is a string, it is interpreted as the path to a file, with the format specified by [readConfigurationsFile()], that contains the configuration. #' @param ab_params `character()`\cr Specific parameter names to be used for the ablation. They must be in `parameters$names`. By default, use all parameters. #' @param type `"full"|"racing"`\cr Type of ablation to perform: `"full"` will execute each configuration on all `n_instances` to determine the best-performing one; `"racing"` will apply racing to find the best configurations. #' @param nrep `integer(1)`\cr Number of replications per instance used in `"full"` ablation. When `nrep > 1`, each configuration will be executed `nrep` times on each instance with different random seeds. #' @param seed `integer(1)`\cr Integer value to use as seed for the random number generation. #' @param ablationLogFile `character(1)`\cr Log file to save the ablation log. If `NULL`, the results are not saved to a file. #' @param instancesFile `character(1)`\cr Instances file used for ablation: `'train'`, `'test'` or a filename containing the list of instances. #' @param ... Further arguments to override scenario settings, e.g., `debugLevel`, `parallel`, etc. #' #' @references #' C. Fawcett and H. H. Hoos. Analysing differences between algorithm #' configurations through ablation. Journal of Heuristics, 22(4):431–458, 2016. #' #' @inherit ablation_cmdline return #' @seealso [plotAblation()] [ablation_cmdline()] #' @examples #' \donttest{ #' logfile <- system.file(package="irace", "exdata", "sann.rda") #' # Execute ablation between the first and the best configuration found by irace. #' ablog <- ablation(logfile, ablationLogFile = NULL) #' plotAblation(ablog) #' # Execute ablation between two selected configurations, and selecting only a #' # subset of parameters, directly reading the setup from the irace log file. #' ablog <- ablation(logfile, src = 1, target = 10, #' ab_params = c("temp"), ablationLogFile = NULL) #' plotAblation(ablog) #' } #' #' @author Leslie Pérez Cáceres and Manuel López-Ibáñez #' @concept ablation #' @export ablation <- function(iraceResults, src = 1L, target = NULL, ab_params = NULL, type = c("full", "racing"), nrep = 1L, seed = 1234567L, ablationLogFile = "log-ablation.Rdata", instancesFile="train", ...) { # Input check if (missing(iraceResults) || is.null(iraceResults)) stop("You must provide an 'iraceResults' object generated by irace or the path to the '.Rdata' file that contains this object.") type <- match.arg(type) if (!is.null(ablationLogFile)) { file.check(ablationLogFile, writeable = TRUE, text = 'ablationLogFile') } save_ablog <- function(complete) { ablog <- list(changes = changes, # This name must match the one used in the irace log. allConfigurations = as.data.frame(all_configurations), experiments = experiments, state = race_state, scenario = scenario, trajectory = trajectory, best = best_configuration, complete = complete) if (!is.null(ablationLogFile)) save(ablog, file = ablationLogFile, version = 3L) invisible(ablog) } # Load the data of the log file. iraceResults <- read_logfile(iraceResults) log_version <- get_log_clean_version(iraceResults) if (log_version < "3.9.0") irace_error("The version of the logfile (", log_version, ") is too old for this version of ablation") if (is.null(iraceResults$state$completed) || length(iraceResults$state$completed) != 1L || iraceResults$state$completed == "Incomplete") stop("The 'iraceResults' logfile seems to belong to an incomplete run of irace.") scenario <- update_scenario(scenario = iraceResults$scenario, ...) scenario$logFile <- "" old_seed <- get_random_seed() on.exit(restore_random_seed(old_seed)) # FIXME: We need to overwrite the seed because RaceState takes it from the scenario and calls set_random_seed(). scenario$seed <- seed scenario <- checkScenario(scenario) race_state <- RaceState$new(scenario) # Generate instances scenario$instances <- ab_generate_instances(race_state, scenario, nrep, type, instancesFile) parameters <- scenario$parameters # Process src and target. if (is.character(src) && is.na(suppressWarnings(as.integer(src)))) { src_configuration <- readConfigurationsFile(src, parameters) if (nrow(src_configuration) != 1L) { stop("Argument of src=\"", src, "\" contains more than one configuration!") } src <- 1L + max(iraceResults$allConfigurations$.ID.) src_configuration$.ID. <- src src_configuration$.PARENT. <- NA_integer_ iraceResults$allConfigurations <- rbind.data.frame(iraceResults$allConfigurations, src_configuration) # FIXME: Check for duplicates } else if (src %not_in% iraceResults$allConfigurations[[".ID."]]) stop("Source configuration ID (", src, ") cannot be found!") if (is.null(target)) target <- iraceResults$iterationElites[length(iraceResults$iterationElites)] else if (is.character(target) && is.na(suppressWarnings(as.integer(target)))) { target_configuration <- readConfigurationsFile(target, parameters) if (nrow(target_configuration) != 1L) { stop("Argument of target=\"", target, "\" contains more than one configuration!") } target <- 1L + max(iraceResults$allConfigurations$.ID.) target_configuration$.ID. <- target target_configuration$.PARENT. <- NA_integer_ iraceResults$allConfigurations <- rbind.data.frame(iraceResults$allConfigurations, target_configuration) # FIXME: Check for duplicates } else if (target %not_in% iraceResults$allConfigurations[[".ID."]]) stop("Target configuration ID (", target, ") cannot be found!") if (src == target) stop("Source and target configuration IDs must be different!") irace_note ("Starting ablation from ", src, " to ", target, "\n# Seed: ", race_state$seed, "\n") cat("# Source configuration (row number is ID):\n") src_configuration <- iraceResults$allConfigurations[src, , drop = FALSE] configurations_print(src_configuration) cat("# Target configuration (row number is ID):\n") target_configuration <- iraceResults$allConfigurations[target, , drop = FALSE] configurations_print(target_configuration) # Select the parameters used for ablation if (is.null(ab_params)) { ab_params <- parameters$names } else if (!all(ab_params %in% parameters$names)) { irace_error("Some of the parameters provided (", paste0(setdiff(ab_params, parameters$names), collapse=", "), ") are not defined in the parameter space.") } # Select parameters that are different in both configurations neq_params <- which(src_configuration[,ab_params] != target_configuration[,ab_params]) if (length(neq_params) == 0L) irace_error("src and target configurations are equal considering the parameters selected.\n") param_names <- colnames(src_configuration[,ab_params])[neq_params] # FIXME: Do we really need to override the ID? src_configuration[[".ID."]] <- 1L target_configuration[[".ID."]] <- 2L all_configurations <- rbindlist(list(src_configuration, target_configuration), use.names=TRUE) race_state$start_parallel(scenario) on.exit(race_state$stop_parallel(), add = TRUE) # Execute source and target configurations. ## FIXME: We may already have these experiments in the logFile! irace_note("Executing source and target configurations on the given instances * nrep (", nrow(race_state$instances_log), ")...\n") experiments <- do_experiments(race_state, configurations = rbind.data.frame(src_configuration, target_configuration), ninstances = nrow(race_state$instances_log), scenario = scenario, iteration = 0L) irace_assert(ncol(experiments) == 2L) step <- 1L # Define variables needed trajectory <- 1L names(trajectory) <- "source" # FIXME: changes should only store the changed parameters. changes <- list() best_configuration <- src_configuration save_ablog(complete = FALSE) while (length(param_names) > 1L) { # Generate ablation configurations cat("# Generating configurations (row number is ID):", param_names, "\n") ab_aux <- generate_ablation(best_configuration, target_configuration, parameters, param_names) aconfigurations <- ab_aux$configurations if (nrow(aconfigurations) == 0L) { cat("# Stopping ablation, no parameter change possible.\n") break } ## FIXME: We may already have these configurations in the logFile! # New configurations IDs aconfigurations[[".ID."]] <- max(all_configurations[[".ID."]]) + seq_nrow(aconfigurations) configurations_print(aconfigurations, metadata = FALSE) all_configurations <- rbindlist(list(all_configurations, aconfigurations), use.names=TRUE) irace_note("Ablation (", type, ") of ", nrow(aconfigurations), " configurations on ", nrow(race_state$instances_log), " instances (this may take a while ...).\n") if (type == "full") { step_experiments <- do_experiments(race_state, configurations = aconfigurations, ninstances = nrow(race_state$instances_log), scenario = scenario, iteration = step) } else { # Set variables for the racing procedure # FIXME: what about blockSize? race_state$next_instance <- 1L race_output <- elitist_race(race_state, maxExp = nrow(aconfigurations) * nrow(race_state$instances_log), minSurvival = 1L, elite_data = NULL, configurations = aconfigurations, scenario = scenario, elitist_new_instances = 0L) set(race_output$experiment_log, j = "iteration", value = step) race_state$experiment_log <- rbindlist(list(race_state$experiment_log, race_output$experiment_log), use.names=TRUE) step_experiments <- race_output$experiments } experiments <- merge_matrix(experiments, step_experiments) save_ablog(complete = FALSE) # Get the best configuration based on the criterion of irace cranks <- overall_ranks(experiments[, aconfigurations[[".ID."]], drop=FALSE], test = scenario$testType) best_id <- which.min(cranks)[1L] changes[[step]] <- ab_aux$changed_params best_change <- changes[[step]][[best_id]] trajectory <- c(trajectory, aconfigurations[[".ID."]][best_id]) # Report best. cat("# Best changed parameters:\n") for (i in seq_along(best_change)) { cat("#", best_change[i], ":", best_configuration[,best_change[i]], "->", aconfigurations[best_id, best_change[i]], "\n") } best_configuration <- aconfigurations[best_id,,drop=FALSE] best_id <- best_configuration[[".ID."]] param_names <- param_names[param_names %not_in% best_change] step <- step + 1L } trajectory <- c(trajectory, target_configuration[[".ID."]]) # Get the overall best cranks <- overall_ranks(experiments[,trajectory, drop=FALSE], test = scenario$testType) best_id <- which.min(cranks)[1L] setDF(all_configurations, rownames = all_configurations[[".ID."]]) best_configuration <- all_configurations[trajectory[best_id], , drop=FALSE] irace_note("Final best configuration:\n") configurations_print(best_configuration) # Check for duplicated results: report_duplicated_results(experiments, all_configurations) save_ablog(complete = TRUE) } ablation_labels <- function(trajectory, configurations) { configurations <- removeConfigurationsMetaData(configurations[trajectory, , drop = FALSE]) labels <- names(trajectory) last <- configurations[1L, , drop = FALSE] param_names <- colnames(last) for (i in 2L:length(trajectory)) { current <- configurations[i, , drop = FALSE] # Select everything that is NOT NA now and was different or NA before. select <- !is.na(current) & (is.na(last) | (current != last)) irace_assert(!anyNA(select)) labels[i] <- paste0(param_names[select], "=", current[, select], collapse = "\n") last <- current } labels } #' Create plot from an ablation log #' #' @param ablog (`list()`|`character(1)`) Ablation log object returned by [ablation()]. Alternatively, the path to an `.Rdata` file, e.g., `"log-ablation.Rdata"`, from which the object will be loaded. #' @param pdf_file Output filename. #' @param width Width provided to create the PDF file. #' @param height Height provided to create the PDF file. #' @param type Type of plot. Supported values are `"mean"` and `"boxplot"`. Adding `"rank"` will plot rank per instance instead of raw cost value. #' @param n `integer(1)`\cr Number of parameters included in the plot. By default all parameters are included. #' @param mar Vector with the margins for the ablation plot. #' @param ylab Label of y-axis. #' @param ylim Numeric vector of length 2 giving the y-axis range. #' @param rename_labels `character()`\cr Renaming table for nicer labels. For example, `c("No value"="NA", "LongParameterName"="LPN")`. #' @param ... Further graphical parameters may also be supplied as #' arguments. See [graphics::plot.default()]. #' #' @author Leslie Pérez Cáceres and Manuel López-Ibáñez #' @seealso [ablation()] [ablation_cmdline()] #' @examples #' logfile <- file.path(system.file(package="irace"), "exdata", "log-ablation.Rdata") #' plotAblation(ablog = logfile) #' plotAblation(ablog = logfile, type = "mean") #' plotAblation(ablog = logfile, type = c("rank","boxplot"), rename_labels = c( #' "localsearch"="ls", algorithm="algo", source="default")) #' @concept ablation #' @export plotAblation <- function (ablog, pdf_file = NULL, width = 20, height = 7, type = c("mean", "boxplot", "rank"), n = 0L, mar = NULL, ylab = "Mean configuration cost", ylim = NULL, rename_labels = NULL, ...) { type <- trimws(unlist(strsplit(type, ",", fixed=TRUE))) type <- match.arg(type, several.ok = TRUE) if (missing(ylab) && ("rank" %in% type)) ylab <- "Rank per instance" if (missing(ablog) || is.null(ablog)) { irace_error("You must provide an 'ablog' object generated by ablation() or the path to the '.Rdata' file that contains this object.") } ablog <- read_ablogfile(ablog) if (!ablog$complete) stop("The ablog shows that the ablation procedure did not complete cleanly and only contains partial information") if (!is.null(pdf_file)) { if (!is.file.extension(pdf_file, ".pdf")) pdf_file <- paste0(pdf_file, ".pdf") cat("Creating PDF file '", pdf_file, "'\n", sep="") local_cairo_pdf(pdf_file, width = width, height = height, onefile= TRUE) } configurations <- ablog$allConfigurations # Support irace < 4.2 if (is.null(configurations)) configurations <- ablog$configurations trajectory <- ablog$trajectory if (n > 0L) trajectory <- trajectory[seq_len(n+1L)] # Generate labels labels <- ablation_labels(trajectory, configurations) if (!is.null(rename_labels)) # stringr::str_replace_all() would be better but it has so many dependencies! for (i in seq_along(rename_labels)) labels <- gsub(names(rename_labels)[i], rename_labels[i], labels) experiments <- ablog$experiments if ("rank" %in% type) { experiments <- rowRanks(experiments, ties.method = "average") if (is.null(ylim)) ylim <- c(1L, ncol(experiments)) } costs_avg <- colMeans2(experiments, cols = trajectory) if (is.null(mar)) mar <- par("mar") inches_to_lines <- (mar / par("mai"))[1L] lab_width <- max(strwidth(labels, units = "inches")) * inches_to_lines with_par(list(mar = c(lab_width + 2.1, 4.1, 0.1, 0.1), cex.axis = 1), { # FIXME: We could also show the other alternatives at each step not just the # one selected. See Leonardo Bezerra's thesis. if ("boxplot" %in% type) { bx <- boxplot(experiments[, trajectory], plot=FALSE) if (is.null(ylim)) { ylim <- range(bx$stats[is.finite(bx$stats)], bx$out[is.finite(bx$out)], bx$conf[is.finite(bx$conf)]) } } plot(costs_avg, xaxt = "n", xlab = NA, ylab = ylab, ylim = ylim, type = "b", pch = 19, ..., panel.first = { grid(nx = NA, ny = NULL, lwd = 2); abline(h = c(costs_avg[1], utils::tail(costs_avg, n = 1L)), col = "lightgray", lty = "dotted", lwd = 2) }) axis(1, at = seq_along(costs_avg), labels = labels, las = 3) if ("boxplot" %in% type) { bxp(bx, show.names = FALSE, add = TRUE) } }) invisible() } #' Read the log file (`log-ablation.Rdata`) produced by [irace::ablation()]. #' #' @param filename `character(1)`\cr Filename that contains the log file saved by [ablation()]. Example: `log-ablation.Rdata`. #' #' @return `list()` #' @concept ablation #' @export read_ablogfile <- function(filename) read_logfile(filename, name = "ablog") irace/R/readConfiguration.R0000644000176200001440000002346714745735066015361 0ustar liggesusers#' Read parameter configurations from a file #' #' Reads a set of target-algorithm configurations from a file and puts them in #' \pkg{irace} format. The configurations are checked to match the parameters #' description provided. #' #' @param filename `character(1)`\cr Filename from which the configurations should be read. #' The contents should be readable by `read.table( , header=TRUE)`. #' @param text `character(1)`\cr If \code{file} is not supplied and this is, #' then configurations are read from the value of \code{text} via a text connection. #' @inheritParams readParameters #' @inheritParams printParameters #' #' @return A data frame containing the obtained configurations. #' Each row of the data frame is a candidate configuration, #' the columns correspond to the parameter names in `parameters`. #' #' @details #' Example of an input file: #' ``` #' # This is a comment line #' param_1 param_2 #' 0.5 "value_1" #' 1.0 NA #' 1.2 "value_3" #' ``` #' The order of the columns does not necessarily have to be the same #' as in the file containing the definition of the parameters. #' #' @seealso #' [readParameters()] to obtain a valid parameter structure from a parameters file. #' #' @author Manuel López-Ibáñez and Jérémie Dubois-Lacoste #' @export readConfigurationsFile <- function(filename, parameters, debugLevel = 0L, text) { if (missing(filename) && !missing(text)) { filename <- NULL configurationTable <- utils::read.table(text = text, header = TRUE, na.strings = c("NA", ""), colClasses = "character", stringsAsFactors = FALSE) } else { # Read the file. configurationTable <- utils::read.table(filename, header = TRUE, na.strings = c("NA", ""), colClasses = "character", stringsAsFactors = FALSE) } irace_assert(is.data.frame(configurationTable)) irace_note("Read ", nrow(configurationTable), " configuration(s)", if (is.null(filename)) "\n" else paste0(" from file '", filename, "'\n")) fix_configurations(configurationTable, parameters, debugLevel = debugLevel, filename = filename) } fix_configurations <- function(configurations, parameters, debugLevel = 0L, filename = NULL) { conf_error <- function(k, ...) irace_error("Configuration number ", k, if (is.null(filename)) "" else paste0(" from file '", filename, "'"), ...) if (debugLevel >= 2L) print(configurations, digits=15L) nbConfigurations <- nrow(configurations) namesParameters <- parameters[["names"]] # This ignores fixed parameters unless they are given with a different value. if (ncol(configurations) != length(namesParameters) || !setequal(colnames(configurations), namesParameters)) { # Column names must match a parameter, including fixed ones. missing <- setdiff(colnames(configurations), namesParameters) if (length(missing) > 0L) { if (is.null(filename)) { irace_error("The parameter names (", strlimit(paste(missing, collapse=", ")), ") do not match the parameter names: ", paste(namesParameters, collapse=", ")) } else { irace_error("The parameter names (", strlimit(paste(missing, collapse=", ")), ") given in the first row of file ", filename, " do not match the parameter names: ", paste(namesParameters, collapse=", ")) } return(NULL) } # All non-fixed parameters must appear in column names. varParameters <- parameters$names_variable missing <- setdiff (varParameters, colnames(configurations)) if (length(missing) > 0) { if (is.null(filename)) { irace_error("The parameter names (", strlimit(paste(missing, collapse=", ")), ") are missing from the configurations provided.") } else { irace_error("The parameter names (", strlimit(paste(missing, collapse=", ")), ") are missing from the first row of file ", filename) } return(NULL) } # Add any missing fixed parameters. missing <- setdiff (namesParameters, colnames(configurations)) if (length(missing) > 0L) { irace_assert (all(parameters$isFixed[missing])) configurations <- cbind.data.frame(configurations, parameters$domains[missing], stringsAsFactors = FALSE) } } # Reorder columns. configurations <- configurations[, namesParameters, drop = FALSE] # Loop over all parameters. for (param in parameters$get()) { pname <- param[["name"]] type <- param[["type"]] domain <- param[["domain"]] is_dep_param <- param[["is_dependent"]] condition <- param[["condition"]] # Fix up numeric columns. if (type == "i") { # For integers, only accept an integer. configurations[[pname]] <- suppressWarnings(as.numeric(configurations[[pname]])) # Remove NAs for this check. values <- configurations[[pname]] values[is.na(values)] <- 0L if (any(as.integer(values) != values)) { k <- which(as.integer(values) != values)[1L] conf_error (k, " is invalid because parameter ", pname, " is of type integer but its value ", values[k], " is not an integer") return(NULL) } } else if (type == "r") { configurations[[pname]] <- round( suppressWarnings(as.numeric(configurations[[pname]])), digits = parameters$get(pname)[["digits"]]) } # Loop over all configurations. # FIXME: Vectorize this loop values <- configurations[[pname]] for (k in seq_len(nbConfigurations)) { currentValue <- values[k] # Check the status of the conditions for this parameter to know whether # it must be enabled. if (conditionsSatisfied(condition, configurations[k, ])) { # Check that the value is among the valid ones. if (is_dep_param) { dep_domain <- getDependentBound(param, configurations[k, ]) if (is.na(dep_domain[1L])) { # Dependencies are not satisfied, so skip if (is.na(currentValue)) next conf_error (k, " is invalid because parameter \"", pname, "\" is not enabled, because its domain ", sub("expression", "", deparse(domain)), " depends on parameters that are not enabled, but its value is \"", currentValue, "\" instead of NA") return(NULL) } lower <- dep_domain[1L] upper <- dep_domain[2L] if (is.na(currentValue) || currentValue < lower || currentValue > upper) { conf_error (k, " is invalid because the value \"", configurations[k, pname], "\" for the parameter ", pname, " is not within the valid range ", sub("expression", "", deparse(domain)), ", that is, [", lower,", ", upper,"]") return(NULL) } } else if (type == "i" || type == "r") { lower <- domain[[1L]] upper <- domain[[2L]] if (is.na(currentValue) || currentValue < lower || currentValue > upper) { conf_error (k, " is invalid because the value \"", configurations[k, pname], "\" for the parameter ", pname, " is not within the valid range [", lower,", ", upper,"]") return(NULL) } # type == "o" or "c" } else if (currentValue %not_in% domain) { conf_error (k, " is invalid because the value \"", currentValue, "\" for the parameter \"", pname, "\" is not among the valid values: (\"", paste0(domain, collapse="\", \""), "\")") return(NULL) } } else if (!is.na(currentValue)) { conf_error (k, " is invalid because parameter \"", pname, "\" is not enabled because of condition \"", param[["condition"]], "\" but its value is \"", currentValue, "\" instead of NA") return(NULL) } } } if (anyDuplicated(configurations)) { irace_error("Duplicated configurations", if (is.null(filename)) "" else paste0(" in file '", filename, "'"), ":\n", paste0(utils::capture.output( configurations[duplicated(configurations), , drop=FALSE]), "\n")) } configurations } compile_forbidden <- function(x) { if (is.null(x) || is.bytecode(x)) return(x) # If we are given an expression, it must be a single one. irace_assert(is.language(x) && (!is.expression(x) || length(x) == 1L)) if (is.expression(x)) x <- x[[1L]] # When a is NA and we check a == 5, we would get NA, which is # always FALSE, when we actually want to be TRUE, so we test # is.na() first below. # We expect that there will be undefined variables, since the expressions # will be evaluated within a data.frame later. expr <- compiler::compile(substitute(is.na(x) | !(x), list(x = x)), options = list(suppressUndefined=TRUE)) attr(expr, "source") <- as.character(as.expression(x)) expr } buildForbiddenExp <- function(configurations) { if (is.null(configurations) || nrow(configurations) == 0L) return(NULL) pnames <- colnames(configurations) lines <- c() # We cannot use apply() because it converts numeric to character. for (k in seq_nrow(configurations)) { values <- as.list(configurations[k, ]) has.value <- !is.na(values) values <- lapply(values[has.value], function(x) deparse(substitute(x, list(x=x)))) lines <- c(lines, paste0("(", pnames[has.value]," == ", values, ")", collapse = "&")) } exps <- parse(text = lines) lapply(exps, compile_forbidden) } irace/R/cluster.R0000644000176200001440000001157314736526233013364 0ustar liggesusers### Submit/wait for jobs in batch clusters. sge.job.finished <- function(jobid) system (paste0("qstat -j ", jobid), ignore.stdout = TRUE, ignore.stderr = TRUE, intern = FALSE, wait = TRUE) pbs.job.finished <- function(jobid) system (paste0("qstat ", jobid), ignore.stdout = TRUE, ignore.stderr = TRUE, intern = FALSE, wait = TRUE) torque.job.finished <- function(jobid) { output <- suppressWarnings(system2("qstat", jobid, stdout = TRUE, stderr = TRUE)) ## 1. If the return code of qstat in not 0, then no such job is in the queue ## anymore. That means that the job was (assumption) successfully submitted, ## executed and completed. Then, for some time the job id was marked as ## completed in the jobs queue and the job status removed from the queue list ## after some time. No job completed status of successfully submitted and ## completed jobs in the queue list happens if a small task was executed ## first and a task of some hours execution time finishes much later. The job ## status of the small task will not be in the queue anymore after some ## minutes. So: If no job id is in the queue list anymore and torq's qstat ## returns an error (return code > 0), then the job has been successfully ## executed (if it has been started successfully before). if (!is.null(attr(output, "status"))) return(TRUE) # 2. If qstat returns OK (return code ==0), then one has to parse qstat's # output. If the 5th token in the last line is a 'C', then the job has # terminated and its output files can be processed. Otherwise the job is not # completed (queued, running, exiting...) any(grepl(paste0(jobid, ".*\\sC\\s"), output)) } slurm.job.finished <- function(jobid) { output <- suppressWarnings(system2("squeue", c("-j", jobid, "--noheader"), stdout = TRUE, stderr = TRUE)) # If the above returns non-zero, either the job terminated or it never # existed. if (!is.null(attr(output, "status"))) return(TRUE) # If may return zero, but the job is not in the system anymore because it # completed. This is different from the Torque case. !any(grepl(paste0("\\s", jobid, "\\s"), output)) } htcondor.job.finished <- function(jobid) { output <- suppressWarnings(system2("condor_q", jobid, stdout = TRUE, stderr = TRUE)) # Check if job is still in the queue, otherwise it is considered finished !any(grepl(paste0("ID:\\s", jobid), output)) } ## Launch a job with qsub and return its jobID. This function does not ## call qsub directly, but instead targetRunner should be a script that ## invokes qsub and returns a jobID. target_runner_qsub <- function(experiment, scenario) { debugLevel <- scenario$debugLevel res <- run_target_runner(experiment, scenario) cmd <- res$cmd output <- res$output args <- res$args jobID <- NULL outputRaw <- output$output err.msg <- output$error if (is.null(err.msg)) { # We cannot use parse.output because that tries to convert to numeric. if (debugLevel >= 2) { cat (outputRaw, sep = "\n") } # Initialize output as raw. If it is empty stays like this. # strsplit crashes if outputRaw == character(0) if (length(outputRaw) > 0) { jobID <- strsplit(trim(outputRaw), "[[:space:]]+")[[1]] } if (length(jobID) != 1) { err.msg <- paste0("The output of targetRunner should be only the jobID!") jobID <- NULL } } list(jobID = jobID, error = err.msg, outputRaw = outputRaw, call = paste(cmd, args)) } cluster_lapply <- function(X, scenario, poll.time = 2) { debugLevel <- scenario$debugLevel cluster.job.finished <- switch(scenario$batchmode, sge = sge.job.finished, pbs = pbs.job.finished, torque = torque.job.finished, slurm = slurm.job.finished, htcondor = htcondor.job.finished, irace_error ("Invalid value of scenario$batchmode = ", scenario$batchmode)) # Parallel controls how many jobs we send at once. Some clusters have low # limits. ## FIXME: It would be better to submit up to the limit, then one by one as jobs finish. chunksize <- scenario$parallel if (chunksize < 0L) { chunksize <- length(X) } chunks <- split(X, ceiling(seq_along(X) / chunksize)) for (chunk in chunks) { if (debugLevel >= 1) { irace_note ("Sending ", length(chunk), " / ", length(X), " jobs\n") } output <- lapply(chunk, exec_target_runner, scenario = scenario, target_runner = target_runner_qsub) jobIDs <- sapply(output, "[[", "jobID") ## Wait for cluster jobs to finish. if (length(jobIDs) > 0L && debugLevel >= 1L) { irace_note("Waiting for jobs ('.' == ", poll.time, " s) ") } for (jobID in jobIDs) { while (!cluster.job.finished(jobID)) { if (debugLevel >= 1) { cat(".") } Sys.sleep(poll.time) } if (debugLevel >= 1) { cat("\n") irace_note ("DONE (", jobID, ")\n") } } } output } irace/R/model.R0000644000176200001440000001531014741742510012766 0ustar liggesusers#################################################### ## INITIALISE AND UPDATE THE MODEL #################################################### # Initial standard deviation for numerical sampling model. init_sd_numeric <- function(param) { # Dependent parameters define the standard deviation as # a portion of the size of the domain interval. In this case, # 0.5 indicates half of the interval, equivalent to # (domain[2] - domain[1]) * 0.5 if (param[["is_dependent"]] || param[["transform"]] == "log") return(0.5) domain <- param[["domain"]] (domain[[2L]] - domain[[1L]]) * 0.5 } ## Initialisation of the model after the first iteration ## # IN: configurations matrix, parameters datastructure ## # OUTPUT: A list of list of vectors. The higher-level list contains # one element per categorical parameter. Each categorical parameter # contains a list of vector. This list contains elements which are the # .ID. of the configuration. initialiseModel <- function (parameters, configurations) { nbConfigurations <- nrow(configurations) ids <- as.character(configurations[[".ID."]]) param_names <- parameters$names_variable model <- setNames(vector("list", length(param_names)), param_names) for (currentParameter in param_names) { param <- parameters$get(currentParameter) type <- param[["type"]] if (type == "c") { nbValues <- length(param[["domain"]]) value <- rep_len(1. / nbValues, nbValues) param <- rep_len(list(value), nbConfigurations) } else { if (type == "r" || type == "i") { sd <- init_sd_numeric(param) values <- configurations[[currentParameter]] } else if (type == "o") { domain <- param[["domain"]] sd <- (length(domain) - 1L) * 0.5 values <- match(configurations[[currentParameter]], domain) } else { irace_internal_error("Unknown parameter type '", type, "'") } # Assign current parameter value to model. param <- mapply(c, sd, values, SIMPLIFY=FALSE, USE.NAMES=FALSE) } names(param) <- ids model[[currentParameter]] <- param } model } updateModel <- function(parameters, eliteConfigurations, oldModel, indexIteration, nbIterations, nbNewConfigurations, elitist) { dec_factor <- (1 - ((indexIteration - 1) / nbIterations)) add_factor <- ((indexIteration - 1) / nbIterations) num_factor <- ((1 / nbNewConfigurations)^(1 / parameters$nbVariable)) prob_max <- 0.2^(1. / parameters$nbVariable) update_prob <- if (elitist) function(p, idx) { # Decrease first all values in the vector: p <- p * dec_factor p[idx] <- p[idx] + add_factor # Normalize probabilities. p <- p / sum(p) # Prevent probabilities from growing too much. p <- pmin.int(p, prob_max) p <- p / sum(p) p / sum(p) } else function(p, idx) { # Decrease first all values in the vector: p <- p * dec_factor p[idx] <- p[idx] + add_factor # Normalize probabilities. p <- p / sum(p) } param_names <- parameters$names_variable model_ids <- names(oldModel[[1L]]) # If the elite is older than the current iteration, it has its own model # that has evolved with time. If the elite is new (generated in the current # iteration), it does not have any, and we have to copy the one from its # parent. The condition of the IF statement is for checking whether the # configuration already has its model or not. elite_ids <- as.character(eliteConfigurations[[".ID."]]) not_in <- elite_ids %not_in% model_ids ids_in_model <- elite_ids # If a configuration does not have any entry, copy the parent one. ids_in_model[not_in] <- as.character(eliteConfigurations[[".PARENT."]][not_in]) newModel <- setNames(vector("list", length(param_names)), param_names) for (currentParameter in param_names) { param <- parameters$get(currentParameter) irace_assert(all(ids_in_model %in% names(oldModel[[currentParameter]]))) this_model <- oldModel[[currentParameter]][ids_in_model] values <- eliteConfigurations[[currentParameter]] values_not_na <- !is.na(values) values <- values[values_not_na] type <- param[["type"]] if (type == "c") { # Find the value that has been "chosen" to increase its probability. values <- match(values, param[["domain"]]) this_model[values_not_na] <- mapply(update_prob, this_model[values_not_na], values, SIMPLIFY=FALSE) } else { irace_assert(type %in% c("i", "r", "o")) if (type == "o") values <- match(values, param[["domain"]]) this_model[values_not_na] <- mapply(function(p, value) c(p[[1L]] * num_factor, value), this_model[values_not_na], values, SIMPLIFY=FALSE) } names(this_model) <- elite_ids newModel[[currentParameter]] <- this_model } newModel } printModel <- function (model) { cat("# Model:\n") print(model) } restartModel <- function(model, configurations, restart_ids, parameters, nbConfigurations) { back_factor <- nbConfigurations^(2 / parameters$nbVariable) second_factor <- (1 / nbConfigurations)^(1 / parameters$nbVariable) model_ids <- names(model[[1L]]) restart_ids <- as.character(sort.int(as.integer(restart_ids))) not_in <- restart_ids %not_in% model_ids configurations <- configurations[configurations[[".ID."]] %in% restart_ids, c(".ID.", ".PARENT.")] restart_ids[not_in] <- configurations[[".PARENT."]][order(as.integer(configurations[[".ID."]]))][not_in] restart_ids <- as.character(unique(restart_ids)) restart_ids <- restart_ids[!is.na(restart_ids)] for (pname in parameters$names_variable) { model_param <- model[[pname]] irace_assert (all(restart_ids %in% names(model_param)), { cat("Param:", pname, "\n") print(restart_ids) print(model) print(configurations[, c(".ID.", ".PARENT.")]) }) param <- parameters$get(pname) type <- param[["type"]] if (type == "c") { model[[pname]][restart_ids] <- sapply(model_param[restart_ids], function(p) { p <- 0.9 * p + 0.1 * max(p) p / sum(p) }, simplify=FALSE) } else { if (type == "i" || type == "r") { value <- init_sd_numeric(param) } else { irace_assert(type == "o") value <- (length(param[["domain"]]) - 1L) * 0.5 } # Bring back the value 2 iterations or to the second iteration value. value <- value * second_factor model[[pname]][restart_ids] <- sapply( model_param[restart_ids], function(x) c(min(x[[1L]] * back_factor, value), x[[2L]]), simplify=FALSE) } } model } irace/R/irace_summarise.R0000644000176200001440000000473014736526233015050 0ustar liggesusers#' Summarise the results of a run of irace #' #' @inheritParams has_testing_data #' #' @return `list()` #' #' @examples #' irace_results <- read_logfile(system.file("exdata/irace-acotsp.Rdata", #' package="irace", mustWork=TRUE)) #' irace_summarise(irace_results) #' #' @author Manuel López-Ibáñez #' @concept analysis #' @export irace_summarise <- function(iraceResults) { if (missing(iraceResults)) stop("argument 'iraceResults' is missing") iraceResults <- read_logfile(iraceResults) if (is.null(iraceResults$state$elapsed)) { time_cpu_user <- time_cpu_sys <- time_cpu_total <- time_wallclock <- NA } else { time_cpu_user <- iraceResults$state$elapsed[["user"]] time_cpu_sys <- iraceResults$state$elapsed[["system"]] time_cpu_total <- time_cpu_user + time_cpu_sys time_wallclock <- iraceResults$state$elapsed[["wallclock"]] } n_iterations <- length(iraceResults$allElites) experiment_log <- iraceResults$state$experiment_log if (is.null(experiment_log)) { experiment_log <- iraceResults$experimentLog } else if (is.null(experiment_log)) stop("Experiment log is NULL") version <- iraceResults$irace_version if (is.null(version)) version <- iraceResults$irace.version # Here to support older versions of irace. time_targetrunner <- iraceResults$state$recovery_info$timeUsed if (is.null(time_targetrunner)) time_targetrunner <- iraceResults$state$timeUsed rejected_ids <- iraceResults$state$rejected_ids if (is.null(rejected_ids)) rejected_ids <- iraceResults$state$rejectedIDs list( version = version, n_iterations = n_iterations, n_configurations = nrow(iraceResults$allConfigurations), n_initial_configurations = if (is.null(iraceResults$scenario$initConfigurations)) 0L else nrow(iraceResults$scenario$initConfigurations), n_instances = nrow(iraceResults$experiments), n_experiments = if (is.null(iraceResults$scenario$targetEvaluator)) nrow(experiment_log) else sum(!is.na(iraceResults$experiments)), n_elites = length(iraceResults$allElites[[n_iterations]]), n_soft_restarts = sum(iraceResults$softRestart), n_rejected = length(rejected_ids), time_targetrunner = time_targetrunner, time_cpu_user = time_cpu_user, time_cpu_sys = time_cpu_sys, time_cpu_total = time_cpu_total, time_wallclock = time_wallclock, termination_reason = if (is.null(iraceResults$state$completed)) "Missing" else iraceResults$state$completed ) } irace/R/testing.R0000644000176200001440000000721414736526233013355 0ustar liggesusers#' Execute the given configurations on the testing instances specified in the #' scenario #' #' @inheritParams removeConfigurationsMetaData #' @inheritParams defaultScenario #' #' @return A list with the following elements: #' \describe{ #' \item{\code{experiments}}{Experiments results.} #' \item{\code{seeds}}{Array of the instance seeds used in the experiments.} #' } #' #' @details A test instance set must be provided through `scenario[["testInstances"]]`. #' #' @seealso #' [testing_fromlog()] #' #' @author Manuel López-Ibáñez #' @export testConfigurations <- function(configurations, scenario) { # We need to set up a default scenario (and repeat all checks) in case # we are called directly instead of being called after executing irace. scenario <- checkScenario(scenario) testInstances <- scenario[["testInstances"]] instances_id <- names(testInstances) if (length(testInstances) == 0L) irace_error("No test instances given") if (is.null(instances_id)) irace_error("testInstances must have names") # 2147483647 is the maximum value for a 32-bit signed integer. # We use replace = TRUE, because replace = FALSE allocates memory for each possible number. ## FIXME: scenario[["testInstances"]] and scenario$instances behave differently, ## we should unify them so that the seeds are also saved in scenario. instanceSeed <- runif_integer(length(testInstances)) names(instanceSeed) <- instances_id # If there is no ID (e.g., after using readConfigurations), then add it. if (".ID." %not_in% colnames(configurations)) configurations[[".ID."]] <- seq_nrow(configurations) # Create experiment list experiments <- createExperimentList(configurations, parameters = scenario$parameters, instances = testInstances, instances_ID = instances_id, seeds = instanceSeed, # We cannot use rep.int because scenario$boundMax may be NULL. bounds = rep(scenario$boundMax, nrow(configurations))) race_state <- RaceState$new(scenario) if (scenario$debugLevel >= 3L) { irace_note ("Memory used before execute_experiments() in testConfigurations():\n") race_state$print_mem_used() } race_state$start_parallel(scenario) on.exit(race_state$stop_parallel()) # We cannot let targetRunner or targetEvaluator modify our random seed, so we save it. withr::local_preserve_seed() target_output <- execute_experiments(race_state, experiments, scenario) # targetEvaluator may be NULL. If so, target_output must contain the right # output already. if (!is.null(scenario$targetEvaluator)) target_output <- execute_evaluator(race_state$target_evaluator, experiments, scenario, target_output) # FIXME: It would be much faster to convert target_output to a data.table like we do in race_wrapper(), # then dcast() to a matrix like we do elsewhere. testResults <- matrix(NA, ncol = nrow(configurations), nrow = length(testInstances), # dimnames = list(rownames, colnames) dimnames = list(instances_id, configurations$.ID.)) cost <- unlist_element(target_output, "cost") if (scenario$capping) cost <- applyPAR(cost, boundMax = scenario$boundMax, boundPar = scenario$boundPar) # FIXME: Vectorize this loop for (i in seq_along(experiments)) { testResults[rownames(testResults) == experiments[[i]]$id_instance, colnames(testResults) == experiments[[i]]$id_configuration] <- cost[i] } if (scenario$debugLevel >= 3L) { irace_note ("Memory used at the end of testConfigurations():\n") race_state$print_mem_used() } ## FIXME: Shouldn't we record these experiments in experiment_log ? list(experiments = testResults, seeds = instanceSeed) } irace/R/zzz.R0000644000176200001440000000066114736526233012534 0ustar liggesusers# Uses 10 decimal places at most so that there is space for '-', '.', and # 'e-NN' within the 16 spaces. .irace.format.perf <- "%#16.10g" .onLoad <- function(libname, pkgname) { # FIXME: We would like to use %#16.10g but this causes problems with # https://github.com/oracle/fastr/issues/191 R_engine <- R.version$engine if (!is.null(R_engine) && R_engine == "FastR") .irace.format.perf <<- "%16.10g" invisible() } irace/R/readParameters.R0000644000176200001440000005257114736603236014644 0ustar liggesusers#' Reads the parameters to be tuned by \pkg{irace} from a file or from a #' character string. #' #' @param file `character(1)`\cr Filename containing the definitions of #' the parameters to be tuned. #' @param digits `integer(1)`\cr The number of decimal places to be considered for real-valued parameters. #' @param debugLevel `integer(1)`\cr Larger values produce more verbose output. #' @param text `character(1)`\cr If \code{file} is not supplied and this is, #' then parameters are read from the value of \code{text} via a text connection. #' #' @return A list containing the definitions of the parameters read. The list is #' structured as follows: #' \describe{ #' \item{`names`}{Vector that contains the names of the parameters.} #' \item{`types`}{Vector that contains the type of each parameter 'i', 'c', 'r', 'o'. #' Numerical parameters can be sampled in a log-scale with 'i,log' and 'r,log' #' (no spaces).} #' \item{`switches`}{Vector that contains the switches to be used for the #' parameters on the command line.} #' \item{`domain`}{List of vectors, where each vector may contain two #' values (minimum, maximum) for real and integer parameters, or #' possibly more for categorical parameters.} #' \item{`conditions`}{List of R logical expressions, with variables #' corresponding to parameter names.} #' \item{`isFixed`}{Logical vector that specifies which parameter is fixed #' and, thus, it does not need to be tuned.} #' \item{`nbParameters`}{An integer, the total number of parameters.} #' \item{`nbFixed`}{An integer, the number of parameters with a fixed value.} #' \item{`nbVariable`}{Number of variable (to be tuned) parameters.} #' \item{`depends`}{List of character vectors, each vector specifies #' which parameters depend on this one.} #' \item{`is_dependent`}{Logical vector that specifies which parameter has #' a dependent domain.} #' \item{`digits`}{Integer vector that specifies the number of digits per parameter.} #' \item{`forbidden`}{List of expressions that define which parameter configurations are forbidden.} #' } #' #' @details Either `file` or `text` must be given. If `file` is given, the #' parameters are read from the file `file`. If `text` is given instead, #' the parameters are read directly from the `text` character string. #' In both cases, the parameters must be given (in `text` or in the file #' whose name is `file`) in the expected form. See the documentation #' for details. If none of these parameters is given, \pkg{irace} #' will stop with an error. #' #' A fixed parameter is a parameter that should not be sampled but #' instead should be always set to the only value of its domain. In this #' function we set `isFixed` to TRUE only if the parameter is a categorical #' and has only one possible value. If it is an integer and the minimum #' and maximum are equal, or it is a real and the minimum and maximum #' values satisfy `round(minimum, digits) == round(maximum, digits)`, #' then the parameter description is rejected as invalid to identify #' potential user errors. #' #' The order of the parameters determines the order in which parameters are #' given to `targetRunner`. Changing the order may also change the results #' produced by `irace`, even with the same random seed. #' #' @examples #' ## Read the parameters directly from text #' parameters_table <- ' #' # name switch type values [conditions (using R syntax)] #' algorithm "--" c (as,mmas,eas,ras,acs) #' localsearch "--localsearch " o (0, 1, 2, 3) #' alpha "--alpha " r (0.00, 5.00) #' beta "--beta " r (0.00, 10.00) #' rho "--rho " r (0.01, 1.00) #' ants "--ants " i,log (5, 100) #' q0 "--q0 " r (0.0, 1.0) | algorithm == "acs" #' rasrank "--rasranks " i (1, "min(ants, 10)") | algorithm == "ras" #' elitistants "--elitistants " i (1, ants) | algorithm == "eas" #' nnls "--nnls " i (5, 50) | localsearch %in% c(1,2,3) #' dlb "--dlb " c (0, 1) | localsearch %in% c(1,2,3) #' #' [forbidden] #' (alpha == 0.0) & (beta == 0.0) #' [global] #' digits = 4 #' ' #' parameters <- readParameters(text=parameters_table) #' str(parameters) #' #' @author Manuel López-Ibáñez and Jérémie Dubois-Lacoste #' @export readParameters <- function (file, digits = 4L, debugLevel = 0L, text) { if (missing(file) && !missing(text)) { filename <- NA_character_ file <- textConnection(text) on.exit(close(file)) } else if (is.character(file)) { filename <- file file.check (file, readable = TRUE, text = "readParameter: parameter file") } else { irace_error("'file' must be a character string") } digits <- as.integer(digits) field.match <- function (line, pattern, delimited = FALSE, sep = "[[:space:]]") { #cat ("pattern:", pattern, "\n") positions <- lapply(seq_along(pattern), function(x) regexpr (paste0("^", pattern[x], sep), line)) if (all(sapply(positions, "[[", 1L) == -1L)) { #cat("no match: NULL\n") return (list(match = NULL, line = line)) } pos_matched <- lapply(seq_along(pattern), function(x) regexpr (paste0("^", pattern[x]), line)) #cat("pos.matched:", pos.matched, "\n") if (all(sapply(pos_matched, "[[", 1L) == -1L)) { #cat(line) return (list(match = NULL, line = line)) } position <- which(sapply(pos_matched, `[[`,1L) != -1L) if (length(position) > 1L) { position <- position[1L] } pos_matched <- pos_matched[[position]] delimited <- as.integer(delimited) match <- substr(line, pos_matched[1L] + delimited, attr(pos_matched, "match.length") - delimited) #cat("match:",match, "\n") line <- substr(line, pos_matched[1L] + attr(pos_matched, "match.length"), nchar(line)) line <- trim_leading (line) #cat(line) list(match = match, line = line) } string2vector <- function(str) { v <- c() str <- trim(str) #cat("string2vector:", str, "\n") while (str != "") { result <- field.match (str, "\"[^\"]*\"", delimited = TRUE, sep="") #cat("result.match: ", result$match,"\n") if (is.null(result$match)) { result <- field.match (str, "[^,]+", sep="") #cat("result.match: ", result$match,"\n") } v <- c(v, result$match) #print(v) str <- sub(",[[:space:]]*", "", result$line) #print(str) } v } errReadParameters <- function(filename, line, context, ...) { context <- if (is.null (context)) "" else paste0(" when reading: \"", context, "\"") fileloc <- if (is.na(filename)) "" else paste0("'", filename, "'," ) irace_error(paste0(..., collapse = ""), " at ", fileloc, "line ", line, context) } warnReadParameters <- function(filename, line, context, ...) { context <- if (is.null (context)) "" else paste0(" when reading: \"", context, "\"") fileloc <- if (is.na(filename)) "" else paste0("'", filename, "'," ) irace_warning(paste0(..., collapse = ""), " at ", fileloc, "line ", line, context) } parse_condition <- function(s, filename, nbLines, line, context) { if (grepl("||", s, fixed=TRUE) || grepl("&&", s, fixed=TRUE)) { warnReadParameters (filename, nbLines, line, paste0("Please use '&' and '|' instead of '&&' and '|'", context)) s <- gsub("||", "|", fixed=TRUE, gsub("&&", "&", fixed=TRUE, s)) } str2expression(s) } params <- list() pnames <- c() lines <- readLines(con = file) # Delete comments lines <- trim(sub("#.*$", "", lines)) within_global <- FALSE nbLines <- 0L # Parse [global] first. for (line in lines) { nbLines <- nbLines + 1L if (line == "") next if (grepl("^[[:space:]]*\\[forbidden\\]", line)) { if (within_global) break next } if (within_global) { if (grepl("^[[:space:]]*digits[[:space:]]*=[[:space:]]*[0-9]+[[:space:]]*$", line)) { eval(parse(text=line)) if (!is.wholenumber(digits) || digits > 15 || digits < 1) errReadParameters(filename, nbLines, line, "'digits' must be an integer within [1, 15]") digits <- as.integer(digits) } else errReadParameters(filename, nbLines, line, "Unknown global option") lines[nbLines] <- "" # Do not parse it again. next } if (grepl("^[[:space:]]*\\[global\\]", line)) { within_global <- TRUE lines[nbLines] <- "" # Do not parse it again. next } } forbidden <- NULL within_forbidden <- FALSE nbLines <- 0L for (line in lines) { nbLines <- nbLines + 1L if (line == "") next if (within_forbidden) { # FIXME: Better error reporting. exp <- parse_condition(line, filename, nbLines, line, " for forbidden expressions") forbidden <- c(forbidden, exp) next } if (grepl("^[[:space:]]*\\[forbidden\\]", line)) { within_forbidden <- TRUE next } ## Match name (unquoted alphanumeric string) result <- field.match (line, "[._[:alnum:]]+") name <- result$match line <- result$line if (is.null(result$match)) { errReadParameters (filename, nbLines, line, "Parameter name must be alphanumeric") } if (name %in% pnames) { errReadParameters (filename, nbLines, NULL, "Duplicated parameter name '", name, "'") } ## Match p_switch (quoted string) result <- field.match (line, "\"[^\"]*\"", delimited = TRUE) p_label <- result$match line <- result$line if (is.null(p_label)) { errReadParameters (filename, nbLines, line, "Parameter label (switch) must be a double-quoted string") } ## Match param.type (longer matches must precede shorter ones) result <- field.match (line, c("i,log", "r,log", "c", "i", "r", "o")) param.type <- result$match line <- result$line if (is.null (param.type)) { errReadParameters(filename, nbLines, line, "Parameter type must be a single character in {'c','i','r','o'}, ", "with 'i', 'r' optionally followed by ',log' (no spaces in between) ", "to sample using a logarithmic scale") } else if (param.type == "i,log") { param.type <- "i" param.transform <- "log" } else if (param.type == "r,log") { param.type <- "r" param.transform <- "log" } else { param.transform <- "" } ## Match domain (delimited by parenthesis) # Regexp to detect dependent domains of the type ("min(p1)", 100) result <- field.match (line, "\\([^|]+\\)", delimited = TRUE, sep = "") domain_str <- result$match line <- result$line if (is.null (domain_str)) { errReadParameters (filename, nbLines, line, "Allowed values must be a list within parenthesis") } # For numerical parameters domains could be dependent # thus, we keep the string values in a variable # for example (10, param1+2) if (param.type %in% c("r","i")) { domain <- eval(parse(text=paste0("expression(", domain_str, ")"), keep.source=FALSE)) # Some expressions like -10 may need to be evaluated again. domain <- sapply(domain, function(x) if (!is.expression(x) || length(all.vars(x, unique=FALSE))) x else eval(x), USE.NAMES=FALSE) if (is.list(domain)) domain <- as.expression(domain) # For dependent domains domain will be NA (we will parse it later) if (length(suppressWarnings(as.numeric(sapply(domain, function(x) if(is.language(x)) NA else x)))) != 2L) { errReadParameters (filename, nbLines, NULL, "Incorrect numeric range (", result$match, ") for parameter '", name, "'") } } else { # type %in% c("c", "o") domain <- string2vector(domain_str) } ## Match start of conditions result <- field.match (line, "\\|", sep="") line <- result$line if (!is.null(result$match) && result$match != "") { result <- field.match (line, ".*$", sep="") condition <- result$match if (is.null(result$match) || result$match == "") errReadParameters (filename, nbLines, line, "Expected condition after '|'") line <- result$line } else if (!is.null(result$line) && result$line != "") { errReadParameters (filename, nbLines, line, "Expected '|' before condition") } else { condition <- TRUE } # ***************************************************************** p <- tryCatch( Parameter(name = name, type = param.type, domain = domain, label = p_label, condition = condition, transf = param.transform, digits = digits), invalid_domain = function(c) structure(paste0("For parameter '", name, "' of type 'i' values must be integers (", domain_str, ")"), class = "try-error"), invalid_range = function(c) structure(paste0("Lower bound must be smaller than upper bound in numeric range (", domain_str, ") for parameter '", name, "'"), class="try-error"), error = function(c) structure(conditionMessage(c), class="try-error") ) if (inherits(p, "try-error")) errReadParameters(filename, nbLines, NULL, p) params <- c(params, list(p)) pnames <- c(pnames, name) } # end loop on lines # Check that we have read at least one parameter if (length(params) == 0) { if (is.na(filename)) irace_error("No parameter definition found in the input text") else irace_error("No parameter definition found, check that the parameter file '", filename, "' is not empty.") } if (length(forbidden)) { irace_note(length(forbidden), " expression(s) specifying forbidden configurations read.\n") check_forbidden_params(forbidden, pnames, filename = filename) } parameters <- do.call(parametersNew, c(params, list(forbidden=forbidden, debugLevel = debugLevel))) if (debugLevel >= 2) { print(parameters, digits = 15L) irace_note("Parameters have been read\n") } parameters } #' Read parameters in PCS (AClib) format and write them in irace format. #' #' @inheritParams readParameters #' #' @return A string representing the parameters in irace format. #' #' @details Either `file` or `text` must be given. If `file` is given, the #' parameters are read from the file `file`. If `text` is given instead, #' the parameters are read directly from the `text` character string. #' In both cases, the parameters must be given (in `text` or in the file #' whose name is `file`) in the expected form. See the documentation #' for details. If none of these parameters is given, \pkg{irace} #' will stop with an error. #' #' **FIXME:** Multiple conditions and default configuration are currently ignored. See #' #' @references #' Frank Hutter, Manuel López-Ibáñez, Chris Fawcett, Marius Thomas Lindauer, Holger H. Hoos, Kevin Leyton-Brown, and Thomas Stützle. **AClib: A Benchmark Library for Algorithm Configuration**. In P. M. Pardalos, M. G. C. Resende, C. Vogiatzis, and J. L. Walteros, editors, _Learning and Intelligent Optimization, 8th International Conference, LION 8_, volume 8426 of Lecture Notes in Computer Science, pages 36–40. Springer, Heidelberg, 2014. #' #' @seealso [readParameters()] #' @examples #' ## Read the parameters directly from text #' pcs_table <- ' #' # name domain #' algorithm {as,mmas,eas,ras,acs}[as] #' localsearch {0, 1, 2, 3}[0] #' alpha [0.00, 5.00][1] #' beta [0.00, 10.00][1] #' rho [0.01, 1.00][0.95] #' ants [1, 100][10]il #' q0 [0.0, 1.0][0] #' rasrank [1, 100][1]i #' elitistants [1, 750][1]i #' nnls [5, 50][5]i #' dlb {0, 1}[1] #' Conditionals: #' q0 | algorithm in {acs} #' rasrank | algorithm in {ras} #' elitistants | algorithm in {eas} #' nnls | localsearch in {1,2,3} #' dlb | localsearch in {1,2,3} #' {alpha=0, beta=0}' #' parameters_table <- read_pcs_file(text=pcs_table) #' cat(parameters_table) #' parameters <- readParameters(text=parameters_table) #' str(parameters) #' #' @author Manuel López-Ibáñez #' @export read_pcs_file <- function(file, digits = 4L, debugLevel = 0L, text) { if (missing(file) && !missing(text)) { filename <- paste0("text=", deparse(substitute(text))) file <- textConnection(text) on.exit(close(file)) } else if (is.character(file)) { filename <- file file.check (file, readable = TRUE, text = "read_pcs_file: parameter file") } else { irace_error("'file' must be a character string") } lines <- readLines(con = file) lines <- trim(lines) # Remove leading and trailing whitespace lines <- lines[!grepl("Conditionals:", lines, fixed=TRUE)] # useless line conditions <- list() forbidden <- NULL regex_cond <- "^([^[:space:]]+)[[:space:]]+\\|[[:space:]]+(.+)$" regex_forbidden <- "^{(.+)}$" for (k in seq_along(lines)) { if (grepl(regex_cond, lines[k], perl=TRUE)) { matches <- regmatches(lines[k], regexec(regex_cond, lines[k], perl=TRUE))[[1L]] stopifnot(length(matches) > 0) lines[k] <- NA_character_ conditions[[matches[[2L]]]] <- matches[[3L]] } else if (grepl(regex_forbidden, lines[k], perl=TRUE)) { forbidden <- c(forbidden, sub(regex_forbidden, "\\1", lines[k], perl=TRUE)) lines[k] <- NA_character_ } } parse_pcs_condition <- function(x, types) { if (is.null(x)) return ("") matches <- regmatches(x, regexec("([^[:space:]]+)[[:space:]]+in[[:space:]]+\\{([^}]+)\\}$", x, perl=TRUE))[[1L]] if (length(matches) == 0L) irace_error("unknown condition ", x) param <- matches[[2L]] type <- types[[param]] if (is.null(type)) irace_error("unknown type for ", param, " in condition: ", x) cond <- matches[[3L]] if (type == "c" || type == "o") { cond <- strsplit(cond, ",[[:space:]]*")[[1L]] equal <- (length(cond) == 1L) cond <- paste0('"', cond, '"', collapse=',') } else { equal <- grepl(",", cond, fixed=TRUE) } if (equal) return(paste0(" | ", param, ' == ', cond)) return(paste0(" | ", param, " %in% c(", cond, ")")) } param_types <- list() param_domains <- list() param_comments <- list() lines <- lines[!is.na(lines)] for (line in lines) { if (startsWith(line, "#") || line == "") next # match a parameter matches <- regmatches(line, regexec("^([^[:space:]]+)[[:space:]]+\\[([^,]+),[[:space:]]*([^]]+)\\][[:space:]]*\\[[^]]+\\](i?l?i?)(.*)$", line, perl=TRUE))[[1]] if (length(matches) > 0L) { param_name <- matches[[2L]] param_type <- paste0(if(grepl("i", matches[5L], fixed=TRUE)) "i" else "r", if(grepl("l", matches[5L], fixed=TRUE)) ",log" else "") param_types[[param_name]] <- param_type param_domains[[param_name]] <- paste0("(", matches[3L], ", ", matches[4L], ")") param_comments[[param_name]] <- matches[6L] next } matches <- regmatches(line, regexec("^([^[:space:]]+)[[:space:]]+\\{([^}]+)\\}[[:space:]]*\\[[^]]+\\](.*)$", line, perl=TRUE))[[1L]] if (length(matches) > 0L) { param_name <- matches[[2L]] param_type <- "c" param_types[[param_name]] <- param_type param_types[[param_name]] <- param_type param_domains[[param_name]] <- paste0("(", matches[3L], ")") param_comments[[param_name]] <- matches[4L] next } } output <- "" for (line in lines) { if (startsWith(line, "#") || line == "") { output <- paste0(output, line, "\n") next } # match a parameter matches <- regmatches(line, regexec("^([^[:space:]]+)[[:space:]]+", line, perl=TRUE))[[1L]] if (length(matches) > 0L) { param_name <- matches[[2L]] cond <- parse_pcs_condition(conditions[[param_name]], param_types) output <- paste0(output, sprintf('%s "%s" %s %s%s%s\n', param_name, param_name, param_types[[param_name]], param_domains[[param_name]], cond, param_comments[[param_name]])) next } irace_error("unrecognized line: ", line) } if (length(forbidden) > 0L) { exp <- sapply(forbidden, function(x) { # FIXME: this will break if there are "," within the values. x <- strsplit(x, ",[[:space:]]*")[[1L]] paste0(collapse=" & ", sapply(regmatches(x, regexec("^([^=]+)=(.+)$", x, perl=TRUE)), function(matches) { rhs <- trim(matches[[3L]]) if (!any(startsWith(rhs, c("'", "\""))) && suppressWarnings(is.na(as.numeric(rhs)))) rhs <- paste0('"', rhs, '"') paste0("(", trim(matches[[2L]]), " == ", rhs, ")") }, USE.NAMES=FALSE)) }, USE.NAMES=FALSE) output <- paste0(output, "\n[forbidden]\n", paste0(collapse="\n", exp), "\n") } output } #' checkParameters #' #' FIXME: This is incomplete, for now we only repair inputs from previous irace #' versions. #' #' @inheritParams printParameters #' @export checkParameters <- function(parameters) { ## if (is.null(parameters$isDependent)) { ## parameters$isDependent <- sapply(parameters$domains, is.expression) ## names(parameters$isDependent) <- parameters$names ## } if (!inherits(parameters, "ParameterSpace")) { irace_error("parameters must be an object of class 'ParameterSpace'") } parameters } irace/R/configurations.R0000644000176200001440000001172214745735066014737 0ustar liggesusers.param_na_value_type <- list(i = NA_integer_, r = NA_real_, c = NA_character_, o = NA_character_) # Returns a data.table configurations_alloc <- function(colnames, nrow, parameters) { column_type <- function(x, n, types) { what <- switch(x, .ID. = NA_integer_, .PARENT. = NA_integer_, .WEIGHT. = NA_real_, .param_na_value_type[[ types[x] ]]) rep_len(what, n) } x <- sapply(colnames, column_type, n=nrow, types = parameters[["types"]], simplify=FALSE, USE.NAMES=TRUE) setDT(x) x } # FIXME: It may be faster to create a single expression that concatenates all # the elements of forbidden using '|' checkForbidden <- function(configurations, forbidden) { # We have to use a variable name that will never appear in # configurations, so .FORBIDDEN . for (.FORBIDDEN in forbidden) { #print(.FORBIDDEN) configurations <- subset(configurations, eval(.FORBIDDEN)) #print(configurations) #print(str(configurations)) ## FIXME: This is normally called with a single configuration. Thus, it ## would be faster to break as soon as nrow(configurations) < 1 } #print(nrow(configurations)) configurations } # FIXME: It may be faster to create a single expression that concatenates all # the elements of forbidden using '|' filter_forbidden <- function(configurations, forbidden) { # We have to use a variable name that will never appear in # configurations, so .FORBIDDEN for (.FORBIDDEN in forbidden) { configurations <- configurations[eval(.FORBIDDEN)] #print(configurations) #print(str(configurations)) if (nrow(configurations) == 0L) return(configurations) } #print(nrow(configurations)) configurations } which_satisfied <- function(configurations, condition) { # If there is no condition, do not waste time evaluating it. if (isTRUE(condition)) return(seq_nrow(configurations)) r <- eval(condition, configurations) # Return TRUE if TRUE, FALSE if FALSE or NA ## FIXME: If we byte-compile the condition, then we should incorporate the ## following into the condition directly. # r & !is.na(r) # Return indexes where r is TRUE. which(r) } #' removeConfigurationsMetaData #' #' Remove the columns with "metadata" of a data frame containing #' configurations. Currently, metadata corresponds to column names starting #' with a period. This function should be used before printing the #' configurations to output only the values for the parameters of the #' configuration without metadata possibly useless to the user. #' #' @param configurations `data.frame`\cr Parameter configurations of the #' target algorithm (one per row). #' #' @return The same data frame without "metadata". #' #' @seealso #' [configurations_print_command()] to print the configurations as command lines. #' [configurations_print()] to print the configurations as a data frame. #' #' @author Manuel López-Ibáñez and Jérémie Dubois-Lacoste #' @export removeConfigurationsMetaData <- function(configurations) configurations[, !startsWith(colnames(configurations), "."), drop = FALSE] #' Print configurations as a data frame #' #' @inheritParams removeConfigurationsMetaData #' #' @param metadata `logical(1)`\cr whether to print the metadata or #' not. The metadata are data for the configurations (additionally to the #' value of each parameter) used by \pkg{irace}. #' #' @return None. #' #' @seealso #' [configurations_print_command()] to print the configurations as command-line strings. #' #' @author Manuel López-Ibáñez and Jérémie Dubois-Lacoste #' @export configurations_print <- function(configurations, metadata = FALSE) { if (is.data.table(configurations) || !is.data.frame(configurations)) configurations <- as.data.frame(configurations, stringsAsFactors = FALSE) rownames(configurations) <- configurations[[".ID."]] if (!metadata) configurations <- removeConfigurationsMetaData(configurations) print.data.frame(configurations, digits = 15L) } #' Print configurations as command-line strings. #' #' Prints configurations after converting them into a representation for the #' command-line. #' #' @inheritParams removeConfigurationsMetaData #' @inheritParams printParameters #' #' @return None. #' #' @seealso #' [configurations_print()] to print the configurations as a data frame. #' #' @author Manuel López-Ibáñez and Jérémie Dubois-Lacoste #' @export configurations_print_command <- function(configurations, parameters) { if (nrow(configurations) <= 0L) return(invisible()) ids <- as.numeric(configurations$.ID.) configurations <- removeConfigurationsMetaData(configurations) # Re-sort the columns configurations <- configurations[, parameters$names, drop = FALSE] # A better way to do this? We cannot use apply() because that coerces # to a character matrix thus messing up numerical values. len <- nchar(max(ids)) for (i in seq_nrow(configurations)) { cat(sprintf("%-*d %s\n", len, ids[i], buildCommandLine(configurations[i, , drop=FALSE], parameters$switches))) } } irace/R/race-wrapper.R0000644000176200001440000006460114736603236014272 0ustar liggesusers#' Generate a command-line representation of a configuration #' #' @description `buildCommandLine` receives two vectors, one containing #' the values of the parameters, the other containing the switches of the #' parameters. It builds a string with the switches and the values that can #' be used as a command line to call the program to be tuned, thus generating #' one candidate configuration. #' #' #' @param values A vector containing the value of each parameter for the #' candidate configuration. #' @param switches A vector containing the switches of each paramter (in an #' order that corresponds to the values vector). #' #' @return A string concatenating each element of `switches` and #' `values` for all parameters with a space between each pair of #' parameters (but none between the switches and the corresponding values). #' #' @examples #' switches <- c("--switch1 ", "--switch2-", "--switch3=") #' values <- list("value_1", 1L, sqrt(2)) #' buildCommandLine (values, switches) #' ## Build a command-line from the results produced by a previous run of irace. #' # First, load the data produced by irace. #' logfile <- file.path(system.file(package="irace"), "exdata", "irace-acotsp.Rdata") #' iraceResults <- read_logfile(logfile) #' allConfigurations <- iraceResults$allConfigurations #' parameters <- iraceResults$scenario$parameters #' apply(allConfigurations[1:10, unlist(parameters$names)], 1, buildCommandLine, #' unlist(parameters$switches)) #' #' @author Manuel López-Ibáñez and Jérémie Dubois-Lacoste #' @export buildCommandLine <- function(values, switches) { irace_assert(length(values) == length(switches)) values <- as.list(values) sel <- !is.na(values) switches <- switches[sel] values <- format.default(values[sel], digits=15L, scientific=FALSE) paste0(switches, values, collapse=" ") } # This function tries to convert a, possibly empty, character vector into a # numeric vector. parse_output <- function(outputRaw, verbose) { if (verbose) cat(outputRaw, sep = "\n") # Initialize output as raw. If it is empty stays like this. output <- outputRaw # strsplit crashes if outputRaw == character(0) if (length(outputRaw)) { outputRaw <- paste0(outputRaw, collapse = "\n") output <- strsplit(trim(outputRaw), "[[:space:]]+")[[1L]] } # suppressWarnings to avoid messages about NAs introduced by coercion suppressWarnings(as.numeric(output)) } target_error <- function(err_msg, output, scenario, target_runner_call, target_evaluator_call = NULL) { if (!is.null(target_evaluator_call)) { err_msg <- paste0(err_msg, "\n", .irace_msg_prefix, "The call to targetEvaluator was:\n", target_evaluator_call) } if (!is.null(target_runner_call)) { err_msg <- paste0(err_msg, "\n", .irace_msg_prefix, "The call to targetRunner was:\n", target_runner_call) } if (is.null(output$outputRaw)) { # Message for a function call. output$outputRaw <- deparse1(output) advice_txt <- paste0( "This is not a bug in irace, but means that something failed in ", "a call to the targetRunner or targetEvaluator functions provided by the user.", " Please check those functions carefully.") } else { # Message for an external script. advice_txt <- paste0( "This is not a bug in irace, but means that something failed when", " running the command(s) above or they were terminated before completion.", " Try to run the command(s) above from the execution directory '", scenario$execDir, "' to investigate the issue. See also Appendix B (targetRunner troubleshooting checklist) of the User Guide (https://cran.r-project.org/package=irace/vignettes/irace-package.pdf).") } irace_error(err_msg, "\n", .irace_msg_prefix, "The output was:\n", paste(output$outputRaw, collapse = "\n"), "\n", .irace_msg_prefix, advice_txt) } check_output_target_evaluator <- function (output, scenario, target_runner_time, target_runner_call, bound) { if (!is.list(output)) { output <- list() target_error ("The output of targetEvaluator must be a list", output, scenario, target_runner_call = target_runner_call) return(output) } err_msg <- output$error if (is.null(err_msg)) { if (is.null(output$cost)) { err_msg <- "The output of targetEvaluator must contain 'cost'!" } else if (is_na_nowarn(output$cost)) { err_msg <- "The output of targetEvaluator is not numeric!" } if (scenario$maxTime > 0 || scenario$capping) { if (scenario$batchmode != 0) { if (is.null (output$time)) err_msg <- "When batchmode != 0 and maxTime > 0, the output of targetEvaluator must be two numbers 'cost time'!" # With scenario$capping == TRUE, we may have pre-executed targetRunner # (which_elite_exe) that already have recorded the time, so when we # reach this point, we may not have 'time'. } else if (!scenario$capping && is.null(target_runner_time) && is.null(output$time)) { err_msg <- "Either targetRunner or targetEvaluator must return 'time' !" } } if (is.null(output$time)) { output$time <- target_runner_time } else { if (!is.null(target_runner_time)) { err_msg <- "Both targetRunner and targetEvaluator cannot return 'time' !" } else if (is_na_nowarn(output$time)) { err_msg <- "The time returned by targetEvaluator is not numeric!" } else if (is.infinite(output$time)) { err_msg <- "The time returned by targetEvaluator is not finite!" } else if (output$time <= 0) { err_msg <- paste0("The value of time (", output$time, ") returned by targetEvaluator must be strictly positive!") } else { # Fix time. output$time <- max(output$time, scenario$minMeasurableTime) if (!is.null(bound) && !is.na(bound) && bound > 0 && bound + scenario$minMeasurableTime < output$time) { err_msg <- paste0("The time returned by targetEvaluator (", output$time, ") does not respect the given bound of ", bound, "!") } } } } if (is.null(err_msg)) { output$error <- NULL } else { target_error (err_msg, output, scenario, target_runner_call = target_runner_call, target_evaluator_call = output$call) } output } #' target_evaluator_default #' #' `target_evaluator_default` is the default `targetEvaluator` function that is #' invoked if `targetEvaluator` is a string (by default #' `targetEvaluator` is `NULL` and this function is not invoked). You can use it as #' an advanced example of how to create your own `targetEvaluator` function. #' #' @param experiment A list describing the experiment. It contains at least: #' \describe{ #' \item{`id_configuration`}{An alphanumeric string that uniquely identifies a configuration;} #' \item{`id_instance`}{An alphanumeric string that uniquely identifies an instance;} #' \item{`seed`}{Seed for the random number generator to be used for #' this evaluation, ignore the seed for deterministic algorithms;} #' \item{`instance`}{String giving the instance to be used for this evaluation;} #' \item{`bound`}{(only when `capping` is enabled) Time bound for the execution;} #' \item{`configuration`}{1-row data frame with a column per parameter #' name;} #' } #' @param num_configurations Number of configurations alive in the race. #' @param all_conf_id Vector of configuration IDs of the alive configurations. #' @inheritParams defaultScenario #' @param target_runner_call String describing the call to `targetRunner` that #' corresponds to this call to `targetEvaluator`. This is used for #' providing extra information to the user, for example, in case #' `targetEvaluator` fails. #' #' @return The function `targetEvaluator` must return a list with one element #' `"cost"`, the numerical value corresponding to the cost measure of the #' given configuration on the given instance. #' #' The return list may also contain the following optional elements that are used #' by \pkg{irace} for reporting errors in `targetEvaluator`: #' \describe{ #' \item{`error`}{is a string used to report an error;} #' \item{`outputRaw`}{is a string used to report the raw output of calls to #' an external program or function;} #' \item{`call`}{is a string used to report how `targetRunner` called #' an external program or function.} #' } #' #' #' @author Manuel López-Ibáñez and Jérémie Dubois-Lacoste #' @export target_evaluator_default <- function(experiment, num_configurations, all_conf_id, scenario, target_runner_call) { configuration_id <- experiment$id_configuration instance_id <- experiment$id_instance seed <- experiment$seed instance <- experiment$instance debugLevel <- scenario$debugLevel targetEvaluator <- scenario$targetEvaluator if (as.logical(file.access(targetEvaluator, mode = 1))) { irace_error ("targetEvaluator", shQuote(targetEvaluator), "cannot be found or is not executable!\n") } all_conf_id <- paste0(all_conf_id, collapse = " ") args <- c(configuration_id, instance_id, seed, instance, num_configurations, all_conf_id) withr::with_dir(scenario$execDir, { output <- runcommand(targetEvaluator, args, configuration_id, debugLevel, timeout = scenario$targetRunnerTimeout) }) cost <- time <- NULL err_msg <- output$error if (is.null(err_msg)) { v_output <- parse_output(output$output, verbose = (scenario$debugLevel >= 2L)) if (length(v_output) == 1L) { cost <- v_output[1L] } else if (length(v_output) == 2L) { cost <- v_output[1L] time <- v_output[2L] } else if (length(v_output) == 0L) { err_msg <- paste0("The output of targetEvaluator must be at least one number 'cost'!") } else { err_msg <- paste0("The output of targetEvaluator should not be more than two numbers!") } } list(cost = cost, time = time, error = err_msg, outputRaw = output$output, call = paste(targetEvaluator, args, collapse=" ")) } #' Check the output of the target runner and repair it if possible. If the #' output is incorrect, this function will throw an error. #' #' @param output The output from target runner. #' @inheritParams defaultScenario #' @param bound Optional time bound that the target runner should have respected. #' #' @return The output with its contents repaired. #' #' @export check_output_target_runner <- function(output, scenario, bound = NULL) { if (!is.list(output)) { output <- list() target_error ("The output of targetRunner must be a list", output, scenario, target_runner_call = NULL) return(output) } err_msg <- output$error if (is.null(err_msg)) { if (is.null(output$cost)) { output$cost <- NULL # make sure to delete it. } else if (is_na_or_empty(output$cost)) { err_msg <- "The cost returned by targetRunner is not numeric!" } if (is.null(output$time)) { output$time <- NULL # make sure to delete it. } else if (is.na(output$time)) { err_msg <- paste0("The time returned by targetRunner is not numeric!") } else if (is.infinite(output$time)) { err_msg <- paste0("The time returned by targetRunner is not finite!") } else if (output$time <= 0) { err_msg <- paste0("The value of time (", output$time, ") returned by targetRunner must be strictly positive!") } else { # Fix time. output$time <- max(output$time, scenario$minMeasurableTime) if (!is.null(bound) && !is.na(bound) && bound > 0 && bound + scenario$minMeasurableTime < output$time) { err_msg <- paste0("The time returned by targetRunner (", output$time, ") does not respect the given bound of ", bound, "!") } } if (is.null(err_msg)) { # When targetEvaluator is provided, targetRunner must return only the time. if (!is.null(scenario$targetEvaluator)) { # unless using batchmode, in that case targetRunner returns neither the # time nor the cost. if (scenario$batchmode != 0) { if (!is.null(output$time) || !is.null(output$cost)) { err_msg <- "When batchmode != 0, the output of targetRunner must not contain a cost nor a time!" } } else if (scenario$maxTime > 0 && is.null(output$time)) { err_msg <- "The output of targetRunner must be one number 'time'!" } else if (!is.null(output$cost)) { err_msg <- "The output of targetRunner must be empty or just one number 'time'!" } } else if (scenario$maxTime > 0 && (is.null(output$cost) || is.null(output$time))) { err_msg <- "The output of targetRunner must be two numbers 'cost time'!" } else if (scenario$maxExperiments > 0 && is.null(output$cost)) { err_msg <- "The output of targetRunner must be one number 'cost'!" } } } if (is.null(err_msg)) { output$error <- NULL } else { target_error (err_msg, output, scenario, target_runner_call = output$call) } output } # This function invokes target_runner. When used on a remote node by Rmpi, # environments do not seem to be shared and the default value is evaluated too # late, thus we have to pass race_state$target_runner explicitly. exec_target_runner <- function(experiment, scenario, target_runner) { doit <- function(experiment, scenario) { x <- target_runner(experiment, scenario) check_output_target_runner(x, scenario, bound = experiment$bound) } retries <- scenario$targetRunnerRetries while (retries > 0L) { output <- try (doit(experiment, scenario)) if (!inherits(output, "try-error") && is.null(output$error)) return (output) irace_note("Retrying (", retries, " left).\n") retries <- retries - 1L } doit(experiment, scenario) } parse.aclib.output <- function(outputRaw) { outputRaw <- paste0(outputRaw, collapse = "\n") text <- regmatches(outputRaw, regexec("Result of this algorithm run:\\s*\\{(.+)\\}\\s*\n", outputRaw))[[1]][2] aclib.match <- function(text, key, value) { pattern <- paste0('"', key, '":\\s*', value) return(regmatches(text, regexec(pattern, text))[[1]][2]) } cost <- runtime <- error <- NULL # AClib wrappers print: # Result of this algorithm run: {"status": "SUCCESS", "cost": cost, "runtime": time } # FIXME: This is not very robust. If we are going to be using jsonlite, then we can simply do: # jsonlite::fromJSON('{\"misc\": \"\", \"runtime\": 164.14, \"status\": \"SUCCESS\", \"cost\": \"0.121340\"}') status <- aclib.match(text, "status", '"([^"]+)"') if (!is.character(status)) { error <- paste0("Not valid AClib output") } else if (status %in% c("SUCCESS", "TIMEOUT")) { cost <- aclib.match(text, "cost", "([^[:space:],}]+)") cost <- suppressWarnings(as.numeric(cost)) runtime <- aclib.match(text, "runtime", "([^[:space:],}]+)") runtime <- suppressWarnings(as.numeric(runtime)) if (is.null.or.na(cost) && is.null.or.na(runtime)) error <- paste0("Not valid cost or runtime in AClib output") } else if (status %in% c("CRASHED", "ABORT")) { # FIXME: Implement ABORT semantics of fatal error error <- paste0("targetRunner returned status (", status, ")") } else { error <- paste0("Not valid AClib output status (", status, ")") } list(status = status, cost = cost, time = runtime, error = error) } target_runner_aclib <- function(experiment, scenario) { debugLevel <- scenario$debugLevel res <- run_target_runner(experiment, scenario) cmd <- res$cmd output <- res$output args <- res$args err_msg <- output$error if (is.null(err_msg)) { return(c(parse.aclib.output(output$output), list(outputRaw = output$output, call = paste(cmd, args)))) } list(cost = NULL, time = NULL, error = err_msg, outputRaw = output$output, call = paste(cmd, args)) } check_target_cmdline <- function(target_cmdline, launcher, capping) { required <- c("seed", "instance", "targetRunnerArgs") if (capping) required <- c(required, "bound") for (x in required) { if (!grepl(paste0("{", x, "}"), target_cmdline, fixed=TRUE)) irace_error("targetCmdline '", target_cmdline, "' must contain '{", x, "}'") } } expand_target_cmdline <- function(target_cmdline, experiment, targetRunner, targetRunnerArgs) { vars <- list(configurationID = experiment$id_configuration, instanceID = experiment$id_instance, seed = experiment$seed, instance = experiment$instance, bound = experiment$bound, targetRunner = targetRunner, targetRunnerArgs = targetRunnerArgs) for (x in names(vars)) { value <- vars[[x]] if (is.null(value)) value <- "" if (x == "targetRunner") value <- shQuote(value) target_cmdline <- gsub(paste0("{", x, "}"), value, target_cmdline, fixed=TRUE) } target_cmdline } run_target_runner <- function(experiment, scenario) { configuration_id <- experiment$id_configuration instance_id <- experiment$id_instance seed <- experiment$seed configuration <- experiment$configuration instance <- experiment$instance bound <- experiment$bound switches <- scenario$parameters$switches[names(configuration)] targetRunner <- scenario[["targetRunner"]] debugLevel <- scenario$debugLevel if (scenario$aclib) { # FIXME: Use targetCmdline for this has_value <- !is.na(configuration) # [] [] ... [--cutoff ] [--instance ] # [--seed ] --config [-param_name_1 value_1] [-param_name_2 value_2] ... args <- paste("--instance", instance, "--seed", seed, "--config", paste0("-", switches[has_value], " ", configuration[has_value], collapse = " ")) if (!is.null.or.na(bound)) args <- paste("--cutoff", bound, args) } else { args <- expand_target_cmdline(scenario$targetCmdline, experiment, targetRunner, targetRunnerArgs=buildCommandLine(configuration, switches)) } targetRunnerLauncher <- scenario$targetRunnerLauncher if (!is.null.or.empty(targetRunnerLauncher)) targetRunner <- targetRunnerLauncher output <- runcommand(targetRunner, args, configuration_id, debugLevel, timeout = scenario$targetRunnerTimeout) list(cmd=targetRunner, output=output, args=args) } #' Default `targetRunner` function. #' #' Use it as an advanced example of how to create your own `targetRunner` function. #' #' @param experiment A list describing the experiment. It contains at least: #' \describe{ #' \item{`id_configuration`}{An alphanumeric string that uniquely identifies a configuration;} #' \item{`id_instance`}{An alphanumeric string that uniquely identifies an instance;} #' \item{`seed`}{Seed for the random number generator to be used for #' this evaluation, ignore the seed for deterministic algorithms;} #' \item{`instance`}{String giving the instance to be used for this evaluation;} #' \item{`bound`}{(only when `capping` is enabled) Time bound for the execution;} #' \item{`configuration`}{1-row data frame with a column per parameter #' name;} #' } #' @inheritParams defaultScenario #' #' @return If `targetEvaluator` is `NULL`, then the `targetRunner` #' function must return a list with at least one element `"cost"`, #' the numerical value corresponding to the evaluation of the given #' configuration on the given instance. #' #' If the scenario option `maxTime` is non-zero or if `capping` is enabled #' then the list must contain at least another element `"time"` that reports the #' execution time for this call to `targetRunner`. #' The return list may also contain the following optional elements that are used #' by \pkg{irace} for reporting errors in `targetRunner`: #' \describe{ #' \item{`error`}{is a string used to report an error;} #' \item{`outputRaw`}{is a string used to report the raw output of calls to #' an external program or function;} #' \item{`call`}{is a string used to report how `targetRunner` called #' an external program or function.} #' } #' #' #' @author Manuel López-Ibáñez and Jérémie Dubois-Lacoste #' @export target_runner_default <- function(experiment, scenario) { res <- run_target_runner(experiment, scenario) cmd <- res$cmd output <- res$output args <- res$args debugLevel <- scenario$debugLevel cost <- time <- NULL err_msg <- output$error if (is.null(err_msg)) { v_output <- parse_output(output$output, verbose = (debugLevel >= 2L)) if (length(v_output) == 1L) { if (is.null(scenario$targetEvaluator)) { cost <- v_output[1L] } else { time <- v_output[1L] } } else if (length(v_output) == 2L) { cost <- v_output[1L] time <- v_output[2L] } else { err_msg <- "The output of targetRunner should not be more than two numbers!" } } list(cost = cost, time = time, error = err_msg, outputRaw = output$output, call = paste(cmd, args, collapse = " ")) } execute_experiments <- function(race_state, experiments, scenario) { parallel <- scenario$parallel mpi <- scenario$mpi target_runner <- race_state$target_runner execDir <- scenario$execDir if (!fs::dir_exists(execDir)) irace_error ("Execution directory '", execDir, "' is not found or not a directory\n") withr::local_dir(execDir) if (!is.null(scenario$targetRunnerParallel)) { # FIXME: We should remove the exec_target_runner parameter from # targetRunnerParallel and do lapply(target_output, # check_output_target_runner, scenario=scenario) after it to make sure the output is valid. # User-defined parallelization target_output <- scenario$targetRunnerParallel(experiments, exec_target_runner, scenario = scenario, target_runner = target_runner) if (length(target_output) != length(experiments)) { irace_error("Stopping because the output of targetRunnerParallel is missing elements. The output was:\n", paste0(utils::capture.output(utils::str(target_output)), collapse="\n")) } } else if (scenario$batchmode != 0L) { target_output <- cluster_lapply(experiments, scenario = scenario) } else if (parallel > 1L) { if (mpi) { if (scenario$loadBalancing) { target_output <- Rmpi::mpi.applyLB(experiments, exec_target_runner, scenario = scenario, target_runner = target_runner) } else { # Without load-balancing, we need to split the experiments into chunks # of size parallel. target_output <- unlist(use.names = FALSE, tapply(experiments, ceiling(seq_along(experiments) / parallel), Rmpi::mpi.apply, exec_target_runner, scenario = scenario, target_runner = target_runner)) } # FIXME: if stop() is called from mpi.applyLB, it does not # terminate the execution of the parent process, so it will # continue and give more errors later. We have to terminate # here, but is there a nicer way to detect this and terminate? if (any(sapply(target_output, inherits, "try-error"))) { # FIXME: mclapply has some bugs in case of error. In that # case, each element of the list does not keep the output of # each configuration and repetitions may occur. cat(unique(unlist(target_output[sapply( target_output, inherits, "try-error")])), file = stderr(), sep = "") irace_error("A slave process terminated with a fatal error") } } else { if (.Platform$OS.type == 'windows') { irace_assert(!is.null(race_state$cluster)) if (scenario$loadBalancing) { target_output <- parallel::parLapplyLB(race_state$cluster, experiments, exec_target_runner, scenario = scenario, target_runner = target_runner) } else { target_output <- parallel::parLapply(race_state$cluster, experiments, exec_target_runner, scenario = scenario, target_runner = target_runner) } # FIXME: if stop() is called from parLapply, then the parent # process also terminates, and we cannot give further errors. } else { target_output <- parallel::mclapply(experiments, exec_target_runner, # FALSE means load-balancing. mc.preschedule = !scenario$loadBalancing, mc.cores = parallel, scenario = scenario, target_runner = target_runner) # FIXME: if stop() is called from mclapply, it does not # terminate the execution of the parent process, so it will # continue and give more errors later. We have to terminate # here, but is there a nicer way to detect this and terminate? if (any(sapply(target_output, inherits, "try-error")) || any(sapply(target_output, is.null))) { # FIXME: mclapply has some bugs in case of error. In that # case, each element of the list does not keep the output of # each configuration and repetitions may occur. cat(unique(unlist( target_output[sapply( target_output, inherits, "try-error")])), file = stderr()) irace_error("A child process triggered a fatal error") } } } } else { # One process, all sequential target_output <- lapply(experiments, exec_target_runner, scenario = scenario, target_runner = target_runner) } target_output } execute_evaluator <- function(target_evaluator, experiments, scenario, target_output) { configurations_id <- unique(unlist_element(experiments, "id_configuration")) nconfs <- length(configurations_id) # Evaluate configurations sequentially. for (k in seq_along(experiments)) { experiment <- experiments[[k]] target_runner_call <- target_output[[k]]$call output <- target_evaluator(experiment = experiment, num_configurations = nconfs, all_conf_id = configurations_id, scenario = scenario, target_runner_call = target_runner_call) output <- check_output_target_evaluator(output, scenario, target_runner_time = target_output[[k]]$time, target_runner_call = target_runner_call, bound = experiment$bound) target_output[[k]]$cost <- output$cost # targetEvaluator may return time, for example for batchmode != 0. target_output[[k]]$time <- output$time if (is.null(target_output[[k]]$call)) target_output[[k]]$call <- output$call } target_output } irace/R/main.R0000644000176200001440000004440614745735066012636 0ustar liggesusers# ========================================================================= # irace: An implementation in R of Iterated Race. # ------------------------------------------------------------------------- # # Copyright (C) 2010-2025 # Manuel López-Ibáñez # Jérémie Dubois-Lacoste # Leslie Perez Caceres # # ------------------------------------------------------------------------- # This program is free software (software libre); you can redistribute # it and/or modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, you can obtain a copy of the GNU # General Public License at: # http://www.gnu.org/copyleft/gpl.html # or by writing to the Free Software Foundation, Inc., 59 Temple Place, # Suite 330, Boston, MA 02111-1307 USA # ------------------------------------------------------------------------- # $Revision$ # ========================================================================= #' irace_license #' #' A character string containing the license information of \pkg{irace}. #' #' @export ## __VERSION__ below will be replaced by the version defined in R/version.R ## This avoids constant conflicts within this file. irace_license <- '#------------------------------------------------------------------------------ # irace: An implementation in R of (Elitist) Iterated Racing # Version: __VERSION__ # Copyright (C) 2010-2025 # Manuel Lopez-Ibanez # Jeremie Dubois-Lacoste # Leslie Perez Caceres # # This is free software, and you are welcome to redistribute it under certain # conditions. See the GNU General Public License for details. There is NO # WARRANTY; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # # irace builds upon previous code from the race package: # race: Racing methods for the selection of the best # Copyright (C) 2003 Mauro Birattari #------------------------------------------------------------------------------ ' cat_irace_license <- function() cat(sub("__VERSION__", irace_version, irace_license, fixed=TRUE)) #' Higher-level interface to launch irace. #' #' @inheritParams defaultScenario #' #' @param output.width `integer(1)`\cr The width used for the screen #' output. #' #' @details This function checks the correctness of the scenario, reads the #' parameter space from \code{scenario$parameterFile}, invokes [irace()], #' prints its results in various formatted ways, (optionally) calls #' [psRace()] and, finally, evaluates the best configurations on the test #' instances (if provided). If you want a lower-level interface that just #' runs irace, please see function [irace()]. #' #' @templateVar return_invisible TRUE #' @template return_irace #' @seealso #' \describe{ #' \item{[irace_cmdline()]}{a command-line interface to [irace()].} #' \item{[readScenario()]}{for reading a configuration scenario from a file.} #' \item{[readParameters()]}{read the target algorithm parameters from a file.} #' \item{[defaultScenario()]}{returns the default scenario settings of \pkg{irace}.} #' } #' @author Manuel López-Ibáñez and Jérémie Dubois-Lacoste #' @concept running #' @export irace_main <- function(scenario, output.width = 9999L) irace_common(scenario = scenario, simple=FALSE, output.width = output.width) #' Test configurations given in the logfile (typically `irace.Rdata`) produced by \pkg{irace}. #' #' `testing_fromlog` executes the testing of the target algorithm configurations #' found by an \pkg{irace} execution. #' #' @param logFile `character(1)`\cr Path to the logfile (typically `irace.Rdata`) produced by \pkg{irace}. #' #' @param testNbElites Number of (final) elite configurations to test. Overrides #' the value found in `logFile`. #' #' @param testIterationElites `logical(1)`\cr If `FALSE`, only the final #' `testNbElites` configurations are tested; otherwise, also test the best #' configurations of each iteration. Overrides the value found in `logFile`. #' #' @param testInstancesDir Directory where testing instances are located, either absolute or relative to current directory. #' #' @param testInstancesFile File containing a list of test instances and optionally additional parameters for them. #' #' @param testInstances Character vector of the instances to be used in the `targetRunner` when executing the testing. #' #' @return `logical(1)`\cr `TRUE` if the testing ended successfully otherwise, `FALSE`. #' #' @details The function `testing_fromlog` loads the `logFile` and obtains the #' testing setup and configurations to be tested. Within the `logFile`, the #' variable `scenario$testNbElites` specifies how many final elite #' configurations to test and `scenario$testIterationElites` indicates #' whether test the best configuration of each iteration. The values may be #' overridden by setting the corresponding arguments in this function. The #' set of testing instances must appear in `scenario[["testInstances"]]`. #' #' @seealso [defaultScenario()] to provide a default scenario for \pkg{irace}. #' [testing_fromfile()] provides a different interface for testing. #' #' @author Manuel López-Ibáñez and Leslie Pérez Cáceres #' @concept running #' @export testing_fromlog <- function(logFile, testNbElites, testIterationElites, testInstancesDir, testInstancesFile, testInstances) { if (is.null.or.empty(logFile)) { irace_note("No logFile provided to perform the testing of configurations. Skipping testing.\n") return(FALSE) } iraceResults <- read_logfile(logFile) scenario <- iraceResults[["scenario"]] instances_changed <- FALSE if (!missing(testNbElites)) scenario[["testNbElites"]] <- testNbElites if (!missing(testIterationElites)) scenario$testIterationElites <- testIterationElites if (!missing(testInstances)) scenario[["testInstances"]] <- testInstances if (!missing(testInstancesDir)) { scenario$testInstancesDir <- testInstancesDir instances_changed <- TRUE } if (!missing(testInstancesFile)) { scenario$testInstancesFile <- testInstancesFile instances_changed <- TRUE } cat("\n\n# Testing of elite configurations:", scenario$testNbElites, "\n# Testing iteration configurations:", scenario$testIterationElites,"\n") if (scenario$testNbElites <= 0) return (FALSE) # If they are already setup, don't change them. if (instances_changed || is.null.or.empty(scenario[["testInstances"]])) { scenario <- setup_test_instances(scenario) if (is.null.or.empty(scenario[["testInstances"]])) { irace_note("No test instances, skip testing\n") return(FALSE) } } # Get configurations that will be tested if (scenario$testIterationElites) testing_id <- sapply(iraceResults$allElites, function(x) x[seq_len(min(length(x), scenario$testNbElites))]) else { tmp <- iraceResults$allElites[[length(iraceResults$allElites)]] testing_id <- tmp[seq_len(min(length(tmp), scenario$testNbElites))] } testing_id <- unique.default(unlist(testing_id)) configurations <- iraceResults$allConfigurations[testing_id, , drop=FALSE] irace_note ("Testing configurations (in no particular order): ", paste(testing_id, collapse=" "), "\n") testing_common(configurations, scenario, iraceResults) return(TRUE) } #' Test configurations given an explicit table of configurations and a scenario file #' #' Executes the testing of an explicit list of configurations given in #' `filename` (same format as in [readConfigurationsFile()]). A `logFile` is #' created unless disabled in `scenario`. This may overwrite an existing one! #' #' @param filename `character(1)`\cr Path to a file containing configurations: one configuration #' per line, one parameter per column, parameter names in header. #' #' @inheritParams defaultScenario #' #' @return iraceResults #' #' @seealso [testing_fromlog()] provides a different interface for testing. #' #' @author Manuel López-Ibáñez #' @concept running #' @export testing_fromfile <- function(filename, scenario) { irace_note ("Checking scenario.\n") scenario <- checkScenario(scenario) if (!scenario$quiet) printScenario(scenario) configurations <- readConfigurationsFile(filename, scenario$parameters) configurations <- cbind(.ID. = seq_nrow(configurations), configurations, .PARENT. = NA_integer_) rownames(configurations) <- configurations[[".ID."]] num <- nrow(configurations) configurations <- checkForbidden(configurations, scenario$parameters$forbidden) if (nrow(configurations) < num) { irace_warning("Some of the configurations in the configurations file were forbidden", "and, thus, discarded.") } # To save the logs iraceResults <- list(scenario = scenario, irace_version = irace_version, allConfigurations = configurations) irace_note ("Testing configurations (in the order given as input): \n") testing_common(configurations, scenario, iraceResults) } testing_common <- function(configurations, scenario, iraceResults) { verbose <- !scenario$quiet if (verbose) configurations_print(configurations) iraceResults$testing <- testConfigurations(configurations, scenario) save_irace_logfile(iraceResults, logfile = scenario$logFile) irace_note ("Testing results (column number is configuration ID in no particular order):\n") if (verbose) print(cbind(seeds = iraceResults$testing$seeds, as.data.frame(iraceResults$testing$experiments))) irace_note ("Finished testing\n") iraceResults } #' Test that the given irace scenario can be run. #' #' Test that the given irace scenario can be run by checking the scenario #' settings provided and trying to run the target-algorithm. #' #' @inheritParams defaultScenario #' #' @return returns `TRUE` if successful and gives an error and returns `FALSE` #' otherwise. #' #' @details If the `parameters` argument is missing, then the parameters #' will be read from the file `parameterFile` given by `scenario`. If #' `parameters` is provided, then `parameterFile` will not be read. This function will #' try to execute the target-algorithm. #' #' @seealso #' \describe{ #' \item{\code{\link{readScenario}}}{for reading a configuration scenario from a file.} #' \item{\code{\link{printScenario}}}{prints the given scenario.} #' \item{\code{\link{defaultScenario}}}{returns the default scenario settings of \pkg{irace}.} #' \item{\code{\link{checkScenario}}}{to check that the scenario is valid.} #' } #' @author Manuel López-Ibáñez and Jérémie Dubois-Lacoste #' @export checkIraceScenario <- function(scenario) { irace_note ("Checking scenario\n") scenario$debugLevel <- 2L scenario <- checkScenario(scenario) if (!scenario$quiet) printScenario(scenario) irace_note("Checking target runner.\n") if (checkTargetFiles(scenario = scenario)) { irace_note("Check successful.\n") return(TRUE) } irace_error("Check unsuccessful.\n") return(FALSE) } init <- function() { irace_note("Initializing working directory...\n") libPath <- system.file(package = "irace") tmplFiles <- list.files(file.path(libPath, "templates")) for (file in tmplFiles) { if (grepl(".tmpl", file) && (file != "target-evaluator.tmpl")) { newFile <- gsub(".tmpl", "", file) if (file == "target-runner.tmpl" && .Platform$OS.type == 'windows') { file.copy(file.path(libPath, "templates", "windows", "target-runner.bat"), file.path(getwd(), "target-runner.bat"), overwrite = FALSE) } else { file.copy(file.path(libPath, "templates", file), file.path(getwd(), newFile), overwrite = FALSE) } } } } #' Launch `irace` with command-line options. #' #' Calls [irace_main()] using command-line options, maybe parsed from the #' command line used to invoke R. #' #' @param argv `character()`\cr The arguments #' provided on the R command line as a character vector, e.g., #' `c("--scenario", "scenario.txt", "-p", "parameters.txt")`. #' Using the default value (not providing the parameter) is the #' easiest way to call [irace_cmdline()]. #' #' @details The function reads the parameters given on the command line #' used to invoke R, finds the name of the scenario file, #' initializes the scenario from the file (with the function #' [readScenario()]) and possibly from parameters passed in #' the command line. It finally starts \pkg{irace} by calling #' [irace_main()]. #' #' List of command-line options: #' ```{r echo=FALSE,comment=NA} #' cmdline_usage(.irace.params.def) #' ``` #' #' @templateVar return_invisible TRUE #' @template return_irace #' #' @seealso #' [irace_main()] to start \pkg{irace} with a given scenario. #' @examples #' irace_cmdline("--version") #' @author Manuel López-Ibáñez and Jérémie Dubois-Lacoste #' @concept running #' @export irace_cmdline <- function(argv = commandArgs(trailingOnly = TRUE)) { parser <- CommandArgsParser$new(argv = argv, argsdef = .irace.params.def) quiet <- !is.null(parser$readArg (short = "-q", long = "--quiet")) if (quiet) { op <- options(.irace.quiet = TRUE) on.exit(options(op)) } else { cat_irace_license() cat("# installed at: ", system.file(package="irace"), "\n", "# called with: ", paste(argv, collapse = " "), "\n", sep = "") } if (!is.null(parser$readArg(short = "-h", long = "--help"))) { parser$cmdline_usage() return(invisible(NULL)) } if (!is.null(parser$readArg(short = "-v", long = "--version"))) { print(utils::citation(package="irace")) return(invisible(NULL)) } if (!is.null(parser$readArg(short = "-i", long = "--init"))) { init() return(invisible(NULL)) } # Read the scenario file and the command line scenarioFile <- parser$readCmdLineParameter ("scenarioFile", default = "") scenario <- readScenario(scenarioFile) for (param in .irace.params.names) { scenario[[param]] <- parser$readCmdLineParameter(paramName = param, default = scenario[[param]]) } if (quiet) scenario$quiet <- TRUE # Check scenario if (!is.null(parser$readArg (short = "-c", long = "--check"))) { checkIraceScenario(scenario) return(invisible(NULL)) } # Only do testing testFile <- parser$readArg (long = "--only-test") if (!is.null(testFile)) { return(invisible(testing_fromfile(testFile, scenario))) } if (length(parser$argv)) irace_error ("Unknown command-line options: ", paste(parser$argv, collapse = " ")) irace_common(scenario = scenario, simple=FALSE) } #' @rdname irace_cmdline #' @export irace.cmdline <- function(argv = commandArgs(trailingOnly = TRUE)) { .Deprecated("irace.cmdline") irace_cmdline(argv = argv) } ## Check targetRunner execution checkTargetFiles <- function(scenario) { ## Create two random configurations configurations <- sampleUniform(scenario$parameters, 2L, repair = scenario$repairConfiguration) set(configurations, j = ".ID.", value = seq_nrow(configurations)) setcolorder(configurations, ".ID.", before = 1L) # Read initial configurations provided by the user. initConfigurations <- allConfigurationsInit(scenario) setDT(initConfigurations) if (nrow(initConfigurations) > 0L) { irace_assert(all(colnames(configurations) == colnames(initConfigurations))) configurations <- rbindlist(list(initConfigurations, configurations)) set(configurations, j = ".ID.", value = seq_nrow(configurations)) } bounds <- rep(scenario$boundMax, nrow(configurations)) instances_ID <- if (scenario$sampleInstances) sample.int(length(scenario$instances), 1L) else 1L setDF(configurations) experiments <- createExperimentList( configurations, scenario$parameters, instances = scenario$instances, instances_ID = instances_ID, seeds = 1234567L, bounds = bounds) race_state <- RaceState$new(scenario) race_state$start_parallel(scenario) on.exit(race_state$stop_parallel(), add = TRUE) # FIXME: Create a function try.call(err.msg,warn.msg, fun, ...) # Executing targetRunner irace_note("Executing targetRunner (", nrow(configurations), " times)...\n") result <- TRUE # We cannot let targetRunner or targetEvaluator modify our random seed, so we save it. withr::local_preserve_seed() output <- withCallingHandlers( tryCatch(execute_experiments(race_state, experiments, scenario), error = function(e) { cat(sep = "\n", "\n# Error occurred while executing targetRunner:", paste0(conditionMessage(e), collapse="\n")) result <<- FALSE NULL }), warning = function(w) { cat(sep = "\n", "\n# Warning occurred while executing targetRunner:", paste0(conditionMessage(w), collapse="\n")) invokeRestart("muffleWarning")}) if (scenario$debugLevel >= 1L) { cat("# targetRunner returned:\n") print(output, digits = 15L) } irace_assert(is.null(scenario$targetEvaluator) == is.null(race_state$target_evaluator)) if (!result) return(FALSE) if (!is.null(scenario$targetEvaluator)) { irace_note("Executing targetEvaluator...\n") output <- withCallingHandlers( tryCatch(execute_evaluator(race_state$target_evaluator, experiments, scenario, output), error = function(e) { cat(sep = "\n", "\n# Error ocurred while executing targetEvaluator:", paste0(conditionMessage(e), collapse="\n")) result <<- FALSE NULL }), warning = function(w) { cat(sep = "\n", "\n# Warning ocurred while executing targetEvaluator:", paste0(conditionMessage(w), collapse="\n")) invokeRestart("muffleWarning")}) if (scenario$debugLevel >= 1L) { cat("# targetEvaluator returned:\n") print(output, digits = 15L) } } result } irace/R/parameterAnalysis.R0000644000176200001440000001434514737241771015371 0ustar liggesusers#' Return the elite configurations of the final iteration. #' #' @inheritParams has_testing_data #' @param n `integer(1)`\cr Number of elite configurations to return, if \code{n} is larger than the #' number of configurations, then only the existing ones are returned. The default (\code{n=0}) returns all of them. #' @param drop.metadata `logical(1)`\cr Remove metadata, such as the #' configuration ID and the ID of the parent, from the returned #' configurations. See [removeConfigurationsMetaData()]. #' #' @return A data frame containing the elite configurations required. #' #' @examples #' log_file <- system.file("exdata/irace-acotsp.Rdata", package="irace", mustWork=TRUE) #' print(removeConfigurationsMetaData(getFinalElites(log_file, n=1))) #' #' @author Manuel López-Ibáñez and Leslie Pérez Cáceres #' @concept analysis #' @export getFinalElites <- function(iraceResults, n = 0L, drop.metadata = FALSE) { if (missing(iraceResults)) stop("argument 'iraceResults' is missing") iraceResults <- read_logfile(iraceResults) last_elites <- iraceResults$allElites[[length(iraceResults$allElites)]] if (n == 0L) n <- length(last_elites) if (length(last_elites) < n) { cat("Only", length(last_elites), "configurations available, reducing n,") n <- length(last_elites) } last_elites <- last_elites[seq_len(n)] configurations <- subset(iraceResults$allConfigurations, get(".ID.") %in% as.character(last_elites), drop = FALSE) if (drop.metadata) configurations <- removeConfigurationsMetaData(configurations) configurations } #' Returns the configurations selected by ID. #' #' @param ids `integer()`\cr The id or a vector of ids of the candidates configurations to obtain. #' @inheritParams getFinalElites #' #' @return A data frame containing the elite configurations required, in the #' order and with the repetitions given by `ids`. #' @examples #' log_file <- system.file("exdata/irace-acotsp.Rdata", package="irace", mustWork=TRUE) #' getConfigurationById(log_file, ids = c(2,1), drop.metadata = TRUE) #' #' @author Manuel López-Ibáñez and Leslie Pérez Cáceres #' @concept analysis #' @export getConfigurationById <- function(iraceResults, ids, drop.metadata = FALSE) { if (missing(iraceResults)) stop("argument 'iraceResults' is missing") iraceResults <- read_logfile(iraceResults) if (length(ids) < 1L) stop("You must provide at least one configuration id.") get_configuration_by_id_helper(iraceResults$allConfigurations, ids, drop_metadata = drop.metadata) } #' Returns the configurations by the iteration in which they were executed. #' #' @param iterations `integer()`\cr The iteration number or a vector of iteration numbers from where #' the configurations should be obtained. Negative values start counting from the last iteration. #' @inheritParams getFinalElites #' #' @return A data frame containing the elite configurations required. #' #' @examples #' log_file <- system.file("exdata/irace-acotsp.Rdata", package="irace", mustWork=TRUE) #' getConfigurationByIteration(log_file, iterations = c(-2, -1), drop.metadata = TRUE) #' #' @author Manuel López-Ibáñez and Leslie Pérez Cáceres #' @concept analysis #' @export getConfigurationByIteration <- function(iraceResults, iterations, drop.metadata = FALSE) { if (missing(iraceResults)) stop("argument 'iraceResults' is missing") iraceResults <- read_logfile(iraceResults) if (length(iterations) < 1L) stop("You must provide at least one iteration number.") n_iterations <- length(iraceResults$iterationElites) iterations <- as.integer(iterations) iterations <- ifelse(iterations >= 0L, iterations, n_iterations + 1L + iterations) # To silence warning. iteration <- NULL if (is.null(iraceResults$state$experiment_log) || nrow(iraceResults$state$experiment_log) == 0L) stop("'iraceResults' does not contain experiment_log, maybe the wrong file, an incomplete run or the wrong version of irace?") ids <- unique(subset(as.data.frame(iraceResults$state$experiment_log), iteration %in% iterations, select="configuration", drop=TRUE)) get_configuration_by_id_helper(iraceResults$allConfigurations, ids, drop_metadata = drop.metadata) } get_configuration_by_id_helper <- function(allConfigurations, ids, drop_metadata) { configurations <- allConfigurations[match(ids, allConfigurations[[".ID."]]), , drop=FALSE] if (nrow(configurations) == 0L) stop("No configuration found with ID:", ids, ".") if (drop_metadata) configurations <- removeConfigurationsMetaData(configurations) configurations } #' Returns the pairs of instance IDs and seeds used as instances in the race #' (and optionally the actual instances). #' #' @inheritParams getFinalElites #' @param index `integer()`\cr Indexes of the (instanceID,seed) pairs to be returned. The default returns everything. #' @param instances `logical(1)`\cr Whether to add the actual instances as an additional column (only if the instances are of atomic type). #' #' @return `data.table()`\cr With default arguments, a `data.table` containing two columns #' `"instanceID"` and `"seed"`. With `instances=TRUE` and if the instances #' are of atomic type (see [is.atomic()]) type, another column `instance` is #' added that contains the actual instance. #' #' @examples #' log_file <- system.file("exdata/irace-acotsp.Rdata", package="irace", mustWork=TRUE) #' head(get_instanceID_seed_pairs(log_file)) #' # Add the instance names #' get_instanceID_seed_pairs(log_file, index=1:10, instances=TRUE) #' @author Manuel López-Ibáñez #' @concept analysis #' @export get_instanceID_seed_pairs <- function(iraceResults, index, instances = FALSE) { if (missing(iraceResults)) stop("argument 'iraceResults' is missing") iraceResults <- read_logfile(iraceResults) instances_log <- iraceResults$state$instances_log if (!missing(index)) instances_log <- instances_log[index, , drop = FALSE] if (!instances) return(instances_log) instances <- iraceResults$scenario$instances if (!is.atomic(instances)) { warning("instances=TRUE requested, but instances are not of atomic type") return(instances_log) } instanceID <- instances_log[["instanceID"]] cbind(instances_log, instance = instances[instanceID]) } irace/R/utils.R0000644000176200001440000005426114745735066013052 0ustar liggesusers# Print a user-level warning message, when the calling context # cannot help the user to understand why the program failed. irace_warning <- function(...) { if (getOption(".irace.quiet", default=FALSE)) return() warning(paste0(.irace_msg_prefix, ..., collapse=""), call. = FALSE, immediate. = TRUE) } # Print a user-level fatal error message, when the calling context # cannot help the user to understand why the program failed. irace_error <- function(...) { # The default is only 1000, which is too small. 8170 is the maximum # value allowed up to R 3.0.2 withr::local_options(list(warning.length = 8170)) stop (.irace_msg_prefix, ..., call. = FALSE) } ## When irace crashes, it generates a file "iracedump.rda". To debug the crash use: ## R> load("iracedump.rda") ## R> debugger(iracedump) ## ## See help(dump.frames) for more details. irace_dump_frames <- function() { execDir <- getOption(".irace.execdir") if (!is.null(execDir)) cwd <- setwd(execDir) utils::dump.frames(dumpto = "iracedump", to.file = TRUE, include.GlobalEnv = TRUE) # FIXME: We want to use on.exit(setwd(cwd)) but q() does not run on.exit. if (!is.null(execDir)) setwd(cwd) # We need this to signal an error in R CMD check. See help(dump.frames) if (!interactive()) quit("no", status = 1) } # Print an internal fatal error message that signals a bug in irace. irace_internal_error <- function(...) { .irace.bug.report <- paste0(.irace_msg_prefix, "An unexpected condition occurred. ", "Please report this bug to the authors of the irace package ") op <- list(warning.length = 8170L) if (!base::interactive()) op <- c(op, list(error = irace_dump_frames)) withr::local_options(op) # 6 to not show anything below irace_assert() bt <- utils::capture.output(traceback(5)) warnings() stop (.irace_msg_prefix, paste0(..., collapse = "\n"), "\n", paste0(bt, collapse= "\n"), "\n", .irace.bug.report, call. = FALSE) invisible() } irace_assert <- function(exp, eval_after = NULL) { # FIXME: It would be great if we could save into a file the state of # the function that called this one. if (isTRUE(as.logical(exp))) return(invisible()) mc <- sys.call()[[2L]] msg <- paste0("'", deparse(mc), "' is not TRUE") if (!is.null(eval_after)) { msg_after <- eval.parent(utils::capture.output(eval_after)) msg <- paste0(msg, "\n", paste0(msg_after, collapse="\n")) } irace_internal_error(msg) invisible() } irace_note <- function(...) { # FIXME: If this was a function within an irace object, we could replace it # when using quiet. if (getOption(".irace.quiet", default=FALSE)) return() cat ("# ", format(Sys.time(), usetz=TRUE), ": ", paste0(..., collapse = ""), sep = "") } file.check <- function (file, executable = FALSE, readable = executable, writeable = FALSE, isdir = FALSE, notempty = FALSE, text = NULL) { EXEC <- 1 # See documentation of the function file.access() WRITE <- 2 READ <- 4 if (!is.character(file) || is.null.or.empty(file)) { irace_error (text, " ", shQuote(file), " is not a vaild filename") } file <- path_rel2abs(file) ## The above should remove the trailing separator if present for windows OS ## compatibility, except when we have just C:/, where the trailing separator ## must remain. if (!file.exists(file)) { if (writeable) { if (tryCatch({ suppressWarnings(file.create(file) && file.remove(file)) }, error=function(e) FALSE)) return(TRUE) irace_error("cannot create ", text, " ", shQuote(file)) return (FALSE) } irace_error (text, " '", file, "' does not exist") return(FALSE) } if (writeable && (file.access(file, mode = WRITE) != 0)) { irace_error(text, " '", file, "' cannot be written into") return(FALSE) } if (readable && (file.access(file, mode = READ) != 0)) { irace_error(text, " '", file, "' is not readable") return (FALSE) } if (executable && file.access(file, mode = EXEC) != 0) { irace_error(text, " '", file, "' is not executable") return (FALSE) } if (isdir) { if (!file.info(file)$isdir) { irace_error(text, " '", file, "' is not a directory") return (FALSE) } if (notempty && length(list.files (file, recursive=TRUE)) == 0) { irace_error(text, " '", file, "' does not contain any file") return (FALSE) } } else if (file.info(file)$isdir) { irace_error(text, " '", file, "' is a directory, not a file") return (FALSE) } return (TRUE) } # Returns the smallest multiple of d that is higher than or equal to x. round_to_next_multiple <- function(x, d) (x + d - 1L - (x - 1L) %% d) # This returns FALSE for Inf/-Inf/NA is.wholenumber <- function(x, tol = .Machine$double.eps^0.5) is.finite(x) & (abs(x - round(x)) < tol) is_na_nowarn <- function(x) length(x) == 1L && suppressWarnings(is.na(x)) is_na_or_empty <- function(x) (length(x) == 0L) || is_na_nowarn(x) is.null.or.na <- function(x) is.null(x) || is_na_nowarn(x) is.null.or.empty <- function(x) (length(x) == 0L) || (length(x) == 1L && !suppressWarnings(is.na(x)) && is.character(x) && x == "") is_null_or_empty_or_na <- function(x) (length(x) == 0L) || is_na_nowarn(x) || (length(x) == 1L && !suppressWarnings(is.na(x)) && is.character(x) && x == "") is.function.name <- function(FUN) { # FIXME: Is there a simpler way to do this check? is.function(FUN) || (!is.null(FUN) && !is.na(FUN) && as.character(FUN) != "" && !is.null(get.function(FUN))) } get.function <- function(FUN) { if (is.function(FUN)) return(FUN) FUN <- dynGet(as.character(FUN), ifnotfound = NULL, inherits = TRUE) if (is.function(FUN)) return(FUN) NULL } is.bytecode <- function(x) typeof(x) == "bytecode" bytecompile <- function(x) { if (is.bytecode(x)) return(x) compiler::cmpfun(x) } # This function is used to trim potentially large strings for printing, since # the maximum error/warning length is 8170 characters (R 3.0.2) strlimit <- function(s, limit = 5000L) { if (nchar(s) <= limit) return(s) paste0(substr(s, 1L, limit - 3L), "...") } #' Update filesystem paths of a scenario consistently. #' #' This function should be used to change the filesystem paths stored in a #' scenario object. Useful when moving a scenario from one computer to another. #' #' @inheritParams defaultScenario #' @param from `character(1)`\cr Character string containing a regular expression (or character #' string for `fixed = TRUE`) to be matched. #' @param to `character(1)`\cr The replacement string.character string. For `fixed = FALSE` #' this can include backreferences `"\1"` to `"\9"` to #' parenthesized subexpressions of `from`. #' @param fixed `logical(1)`\cr If `TRUE`, `from` is a string to be matched #' as is. #' @return The updated scenario #' @examples #' \dontrun{ #' scenario <- readScenario(filename = "scenario.txt") #' scenario <- scenario_update_paths(scenario, from = "/home/manuel/", to = "/home/leslie") #' } #' @seealso [base::grep()] #' @export scenario_update_paths <- function(scenario, from, to, fixed = TRUE) { pathParams <- .irace.params.def[.irace.params.def[, "type"] == "p", "name"] # Only consider the ones that actually appear in scenario. pathParams <- intersect(pathParams, names(scenario)) scenario[pathParams] <- lapply(scenario[pathParams], sub, pattern = from, replacement = to, fixed = fixed) scenario } test.type.order.str <- function(test.type) switch(test.type, friedman = "sum of ranks", t.none =, # Fall-throught t.holm =, # Fall-throught t.bonferroni = "mean value", irace_internal_error ("test.type.order.str() Invalid value '", test.type, "' of test.type")) trim_leading <- function(str) sub('^[[:space:]]+', '', str, perl = TRUE) ## white space, POSIX-style trim_trailing <- function(str) sub('[[:space:]]+$', '', str, perl = TRUE) ## white space, POSIX-style # remove leading and trailing white space characters trim <- function(str) trim_trailing(trim_leading(str)) ## This function takes two matrices x and y and merges them such that the ## resulting matrix z has: # rownames(z) <- setunion(rownames(x), rownames(y)) and # rownames(z) <- setunion(rownames(x), rownames(y)) and # z[rownames(x), colnames(x)] <- x and z[rownames(y), colnames(y)] <- y, and # z[i, j] <- NA for all i,j not in x nor y. merge_matrix <- function(x, y) { rownames_x <- rownames(x) colnames_x <- colnames(x) rownames_y <- rownames(y) colnames_y <- colnames(y) if (is.null(rownames_x) || is.null(colnames_x)) return(y) if (is.null(rownames_y) || is.null(colnames_y)) return(x) row_union <- union(rownames_x, rownames_y) col_union <- union(colnames_x, colnames_y) z <- matrix(NA_real_, nrow = length(row_union), ncol = length(col_union), dimnames = list(row_union, col_union)) # Map row and column names to indices for efficient assignment. row_idx_x <- chmatch(rownames_x, row_union) col_idx_x <- chmatch(colnames_x, col_union) row_idx_y <- chmatch(rownames_y, row_union) col_idx_y <- chmatch(colnames_y, col_union) z[row_idx_x, col_idx_x] <- x z[row_idx_y, col_idx_y] <- y # There must be a non-NA entry for each instance. irace_assert(all(rowAnyNotNAs(z))) return(z) } # FIXME: This may not work when working interactively. For example, # one cannot change the number of slaves. A more robust function # would try to close any open slaves, and then re-spawn a different # number. ## # FIXME2: Slaves will load the irace namespace independently of how the master # loaded it. This can be seen by doing in the slaves: # ## print(loadedNamespaces()) ## try(print(as.list(get(".__NAMESPACE__.", envir = asNamespace("irace", base.OK = FALSE), ## inherits = FALSE))$path)) ## try(print(path.package("irace"))) # # That is, neither R_LIBS, .libPaths or whether library was called with lib.loc # will affect the slaves. It also happens before we can set those variables on # the slaves. Thus, one may end up running a different version of irace on the # slaves than on the master. I wasted more than 12 hours trying to find a # work-around but nothing seems to work. mpiInit <- function(nslaves, debugLevel = 0) { # Load the Rmpi package if it is not already loaded. if ("Rmpi" %not_in% loadedNamespaces()) { if (!suppressPackageStartupMessages(requireNamespace("Rmpi", quietly = TRUE))) irace_error("The 'Rmpi' package is required for using MPI") # When R exits, finalize MPI. reg.finalizer(environment(Rmpi::mpi.exit), function(e) { # Rmpi already prints a message, so we don't need this. # cat("# Finalize MPI...\n") if (Rmpi::mpi.comm.size(1) > 0L) # FIXME: dellog == TRUE tries to delete log files, but it does # not take into account that we may have changed directory and # it does not fail gracefully but produces an annoying: # Warning message: running command 'ls *.30577+1.*.log 2>/dev/null' had status 2 Rmpi::mpi.close.Rslaves(dellog = FALSE) # We would like to use .Call("mpi_finalize", PACKAGE = "Rmpi"), which is # what mpi.finalize does, minus the annoying message: "Exiting Rmpi. Rmpi # cannot be used unless relaunching R", which we do not care about # because this finalizer should only be called when exiting R. utils::capture.output(Rmpi::mpi.finalize(), file = if (.Platform$OS.type == 'windows') 'NUL' else '/dev/null') }, onexit = TRUE) # Create slaves # needlog: a logical. If TRUE, R BATCH outputs will be saved in log # files. If FALSE, the outputs will send to /dev/null. # quiet: a logical. If TRUE, do not print anything unless an error occurs. # If FALSE, prints to stdio how many slaves are successfully # spawned and where they are running. Rmpi::mpi.spawn.Rslaves(nslaves = nslaves, quiet = (debugLevel < 2), needlog = (debugLevel > 0)) } } ## FIXME: Move this to the manual page. ## FIXME: Export this function. # Computes: # * Kendall's W (also known as Kendall's coefficient of concordance) # If 1, all configurations have ranked in the same order in all instances. # If 0, the ranking of each configuration on each instance is essentially random. # W = Friedman / (m * (k-1)) # # * Spearman's rho: average (Spearman) correlation coefficient computed on the # ranks of all pairs of raters. If there are no repeated data values, a # perfect Spearman correlation of +1 or −1 occurs when each of the variables # is a perfect monotone function of the other. # # data: matrix with the data, instances in rows (judges), configurations in # columns. concordance <- function(data) { irace_assert (is.matrix(data) && is.numeric(data)) n <- nrow(data) #judges k <- ncol(data) #objects if (n <= 1L || k <= 1L) return(list(kendall.w = NA_real_, spearman.rho = NA_real_)) # Get rankings by rows (per instance) r <- rowRanks(data, ties.method = "average", useNames=FALSE) TIES <- c(table(r,row(r))) # If everything is tied, then W=1, perfect homogeneity. if (all(TIES == k)) { W <- 1 } else { # FIXME: This formula seems slightly different from the one in # friedman.test. Why? TIES <- sum(TIES^3L - TIES) R <- colSums2(r, useNames=FALSE) W <- ((12 * sum((R - n * (k + 1) / 2)^2L)) / ((n^2L * (k^3L - k)) - (n * TIES))) } # Spearman's rho rho <- (n * W - 1) / (n - 1L) ## Same as in friedman test #STATISTIC <- n * (k - 1) * W #PARAMETER <- k - 1 #pvalue <- pchisq(PARAMETER, df = PARAMETER, lower.tail = FALSE) list(kendall.w = W, spearman.rho = rho) } ## FIXME: Move this to the manual page. ## FIXME: Reference! Explain a bit what is computed! # Calculates Performance similarity of instances # data: matrix with the data, instances in rows (judges), configurations # in columns. # Returns: variance value [0,1], where 0 is a homogeneous set of instances and # 1 is a heterogeneous set. # FIXME: How to handle missing values? dataVariance <- function(data) { irace_assert (is.matrix(data) && is.numeric(data)) # LESLIE: should we rank data?? # MANUEL: We should add the option. if (nrow(data) <= 1L || ncol(data) <= 1L) return(NA_real_) # Normalize #datamin <- apply(data,1,min,na.rm=TRUE) #datamax <- apply(data,1,max,na.rm=TRUE) #normdata <- (data - datamin) / (datamax-datamin) #standardize meandata <- rowMeans2(data, useNames=FALSE) stddata <- rowSds(data, useNames=FALSE) # If stddata == 0, then data is constant and it doesn't matter as long as it # is non-zero. stddata[stddata == 0] <- 1 zscoredata <- (data - meandata) / stddata # FIXME: We could log-tranform if needed # Variance of configurations mean(colVars(zscoredata)) } runcommand <- function(command, args, id, debugLevel, timeout = 0) { if (debugLevel >= 2L) { irace_note (command, " ", args, "\n") elapsed <- proc.time()["elapsed"] } err <- NULL output <- withCallingHandlers( tryCatch(system2(command, args, stdout = TRUE, stderr = TRUE, timeout = timeout), error = function(e) { err <<- c(err, paste(conditionMessage(e), collapse="\n")) NULL }), warning = function(w) { err <<- c(err, paste(conditionMessage(w), collapse="\n")) invokeRestart("muffleWarning") }) if (is.null(output)) output <- "" # If the command could not be run an R error is generated. If ‘command’ # runs but gives a non-zero exit status this will be reported with a # warning and in the attribute ‘"status"’ of the result: an attribute # ‘"errmsg"’ may also be available. if (!is.null(err)) { err <- paste(err, collapse = "\n") if (!is.null(attr(output, "errmsg"))) err <- paste(sep = "\n", err, attr(output, "errmsg")) if (debugLevel >= 2L) irace_note ("ERROR (", id, "): ", err, "\n") return(list(output = output, error = err)) } if (debugLevel >= 2L) { irace_note ("DONE (", id, ") Elapsed wall-clock seconds: ", formatC(proc.time()["elapsed"] - elapsed, format = "f", digits = 2), "\n") } # TODO: Return elapsed time so that we can report at the end the total # elapsed time taken by irace vs the time taken by the target-runner. list(output = output, error = NULL) } # Safe sampling of vector: resample <- function(x, ...) x[sample.int(length(x), ...)] # Rounds up the number x to the specified number of decimal places 'digits'. ceiling_digits <- function(x, digits) { multiple <- 10^-digits div <- x / multiple int_div <- trunc(div) int_div * multiple + ceiling(div - int_div) * multiple } # ceil.decimal <- function(x, d) { # # get the significant digits in the integer part. # ssd <- x * 10^(d) # # get the non significant digits # nsd <- ssd - floor(ssd) # ssd <- trunc(ssd) # sel <- nsd > 0 | ssd==0 # ssd[sel] <- ssd[sel] + 1 # x2 <- ssd/10^(d) # return(x2) # } is.file.extension <- function(filename, ext) substring(filename, nchar(filename) + 1L - nchar(ext)) == ext is.sub.path <- function(x, dir, n = nchar(dir)) substr(x, 1L, n) == dir # Same as !(x %in% table). Package data.table has %notin%. "%not_in%" <- function(x, table) is.na(match(x, table)) #' Save the log generated by \pkg{{irace}} to a file (by default `irace.Rdata`). #' #' This function may be useful if you are manually editing the log data generated by a run of \pkg{irace}. #' #' @param iraceResults `list()`\cr Object created by \pkg{irace} and typically saved in the log file `irace.Rdata`. #' #' @param logfile `character(1)`\cr Filename to save `iraceResults`. Usually, this is given by `scenario$logFile`. If `NULL` or `""`, no data is saved. #' #' @seealso [read_logfile()] #' @concept analysis save_irace_logfile <- function(iraceResults, logfile) { # FIXME: Raul Santa Maria proposed to only save if sufficient time (>= 1 # minute) has passed since the last save. We would need an option to force # saving the last one. if (is.null.or.empty(logfile)) return(invisible()) # Files produced by `saveRDS` (or `serialize` to a file connection) are not # suitable as an interchange format between machines, for example to download # from a website. The files produced by `save` have a header identifying the # file type and so are better protected against erroneous use. save(iraceResults, file = logfile, version = 3L) } valid_iracelog <- function(x) { is.list(x) && ("scenario" %in% names(x)) } #' Read the log file produced by irace (`irace.Rdata`). #' #' @param filename Filename that contains the log file saved by irace. Example: `irace.Rdata`. #' #' @param name Optional argument that allows overriding the default name of the object in the file. #' #' @return (`list()`) #' @examples #' irace_results <- read_logfile(system.file("exdata/irace-acotsp.Rdata", package="irace", #' mustWork=TRUE)) #' str(irace_results) #' @concept analysis #' @export read_logfile <- function(filename, name = "iraceResults") { if (is_na_or_empty(filename)) irace_error("read_logfile: 'filename' is NULL or NA.") # If filename is already the iraceResults object, just return it. if (valid_iracelog(filename)) return(filename) if (file.access(filename, mode = 4) != 0) irace_error("read_logfile: Cannot read file '", filename, "'.") load(filename) iraceResults <- get0(name, inherits=FALSE) if (!valid_iracelog(iraceResults)) irace_error("read_logfile: The file '", filename, "' does not contain the '", name, "' object.") # data.table recommends doing this after loading a data.table from a file. setDT(iraceResults$state$experiment_log) setDT(iraceResults$state$instances_log) iraceResults } get_log_clean_version <- function(iraceResults) { log_version <- iraceResults$irace_version if (is.null(log_version)) log_version <- iraceResults$irace.version if (is.null(log_version)) return(package_version("0.0.0")) if (log_version == "unknown") # This may happen during development. return(package_version("1000.0.0")) if (length(gregexpr("\\.", log_version)[[1L]]) > 3L || grepl("[a-z]", log_version)) log_version <- sub("\\.[^.]*$", "", log_version) package_version(log_version) } #' Check if the results object generated by irace has data about the testing phase. #' #' @param iraceResults `list()`|`character(1)`\cr Object created by \pkg{irace} and typically saved in the log file `irace.Rdata`. If a character string is given, then it is interpreted as the path to the log file from which the `iraceResults` object will be loaded. #' #' @return `logical(1)` #' @examples #' irace_results <- read_logfile(system.file("exdata/irace-acotsp.Rdata", package="irace", #' mustWork=TRUE)) #' print(has_testing_data(irace_results)) #' @export has_testing_data <- function(iraceResults) { ins <- iraceResults$scenario$testInstances exp <- iraceResults$testing$experiments !(length(ins) == 0L || (length(ins) == 1L && (is.na(ins) || ins == "")) || length(exp) == 0L || !(is.matrix(exp) || is.data.frame(exp))) } do_nothing <- function(...) invisible() seq_nrow <- function(x) seq_len(nrow(x)) clamp <- function(x, lower, upper) pmax.int(lower, pmin.int(x, upper)) truncate_rows <- function(x, n) { nx <- nrow(x) if (nx <= n) return(x) x[-seq.int(n + 1L, nx), ] } vlast <- function(x) { stopifnot(is.null(dim(x))) lx <- length(x) if (!lx) x else x[[lx]] } # 2147483647 is the maximum value for a 32-bit signed integer. # We use replace = TRUE, because replace = FALSE allocates memory for each possible number. # Do not use .Machine$integer.max unless it is smaller, to minimize differences # between machines. runif_integer <- function(size) sample.int(min(2147483647L, .Machine$integer.max), size = size, replace = TRUE) unlist_element <- function(x, element) unlist(lapply(x, "[[", element, exact=TRUE), recursive=FALSE, use.names=FALSE) # Extensions of matrixStats rowAnyNotNAs <- function(x, rows = NULL, cols = NULL, ..., useNames = FALSE) !rowAlls(x, rows = rows, cols = cols, value = NA, ..., useNames = useNames) colAnyNotNAs <- function(x, rows = NULL, cols = NULL, ..., useNames = FALSE) !colAlls(x, rows = rows, cols = cols, value = NA, ..., useNames = useNames) irace/R/parameters.R0000644000176200001440000007152014745735066014052 0ustar liggesusers# Checks that variables in the expressions are within the parameters names. check_domain_dependencies <- function (params, depends, types) { allnames <- names(params) allowed_fx <- c("+", "-", "*", "/", "%%", "min", "max", "round", "floor", "ceiling", "trunc") depends <- Filter(length, depends) for (p in names(depends)) { parameter <- params[[p]] domain <- parameter[["domain"]] vars <- depends[[p]] flag <- vars %in% allnames if (!all(flag)) { irace_error ("Domain (", paste0(domain, collapse=", "), ") of parameter '", p, "' is not valid: '", paste0(vars[!flag], collapse=", "), "' cannot be found in the scenario parameters: ", paste0(allnames, collapse=", ")," .") } flag <- types[vars] %in% c("i", "r") if (!all(flag)) { irace_error ("Domain of parameter '", p, "' depends on non-numerical", " parameters: ", paste0(vars[!flag], collapse=", "), " .") } # Supported operations for dependent domains fx <- setdiff(all.names(domain, unique=TRUE), all.vars(domain, unique=TRUE)) flag <- fx %in% allowed_fx if (!all(flag)) { irace_error ("Domain of parameter '", p, "' uses function(s) ", "not yet supported by irace: ", paste0(fx[!flag], collapse=", "), " .") } } invisible(TRUE) } check_forbidden_params <- function(x, pnames, filename = NULL) { if (length(x) == 0L) return(invisible()) if (any(unique(unlist(lapply(x, all.names))) %in% c("&&", "||"))) { for (ex in x) { if (any(all.names(ex) %in% c("&&", "||"))) irace_error("Please use '&' and '|' instead of '&&' and '|' in: ", deparse(ex), " .\n") } } if (all(all.vars(x) %in% pnames)) return(invisible()) for (ex in x) { v <- setdiff(all.vars(ex), pnames) if (length(v)) { v <- paste0(v, collapse=", ") if (is.null(filename)) { irace_error("Forbidden expression '", deparse(ex), "' contains unknown parameter(s): ", v) } else if (is.na(filename)) { irace_error("Expression '", deparse(ex), "' after [forbidden] contains unknown parameter(s): ", v) } else { irace_error("Expression '", deparse(ex), "' after [forbidden] in '", filename, "' contains unknown parameter(s): ", v) } } } } # --------------------------------------------------------------------------- # Ordering of the parameters according to conditions hierarchy # * The conditions hierarchy is an acyclic directed graph. # Function treeLevel() computes an order on vertex s.t: # level(A) > level(B) <=> There is an arc A ---> B # (A depends on B to be activated) # * If a cycle is detected, execution is stopped # * If a parameter depends on another one not defined, execution is stopped param_level <- function(paramName, varsTree, rootParam = paramName) { # The last parameter is used to record the root parameter of the # recursive call in order to detect the presence of cycles. vars <- varsTree[[paramName]] if (length(vars) == 0L) return(1L) # This parameter does not have conditions # This parameter has some conditions # Recursive call: level <- MAX( level(m) : m in children ) maxChildLevel <- 0L for (child in vars) { # The following line detects cycles if (child == rootParam) irace_error("Cycle detected in subordinate parameters! ", "Check definition of conditions and/or dependent domains.\n", "One parameter of this cycle is '", rootParam, "'") # The following line detects a missing definition if (child %not_in% names(varsTree)) irace_error("A parameter definition is missing! ", "Check definition of parameters.\n", "Parameter '", paramName, "' depends on '", child, "' which is not defined.") level <- param_level(child, varsTree, rootParam) if (level > maxChildLevel) maxChildLevel <- level } maxChildLevel + 1L } transform_domain <- function(transf, domain, type) { # We do not support transformation of dependent parameters, yet # TODO: think about dependent domain transformation if (is.expression(domain)) stop("Parameter domain transformations are not yet available for", " dependent parameter domains.") lower <- domain[1L] upper <- domain[2L] if (transf == "log") { # Reject log if domain contains zero or negative values if (any(domain <= 0)) stop("Domain (", lower, ", ", upper, ") of parameter of type 'log' contains non-positive values") trLower <- log(lower) # +1 to adjust before floor() trUpper <- if (type == "i") log(upper + 1) else log(upper) irace_assert(is.finite(trLower)) irace_assert(is.finite(trUpper)) attr(transf, "lower") <- trLower attr(transf, "upper") <- trUpper return(transf) } stop("unrecognized transformation type '", transf, "'") } named_stop <- function(name, ...) stop(errorCondition(paste0(..., collapse=""), class = name, call = sys.call(-1))) param_parse_condition <- function(x, type) { if (is.language(x)) x <- as.expression(x) else if (is.character(x)) { x <- tryCatch(str2expression(x), error = function(e) { msg <- sub(":[0-9]+:[0-9]+: ", "", conditionMessage(e), perl=TRUE) msg <- sub("\n1:", "\n ", msg, perl=TRUE) stop("Invalid condition '", x, "': ", msg, "\n") }) } if (!is.expression(x)) stop("Invalid condition '", x, "'") if (any(all.names(x) %in% c("&&", "||"))) stop("Please use '&' and '|' instead of '&&' and '|' in: ", deparse(x[[1L]]), " .\n") x } valid_real_bound <- function(x, digits) { if (is.na(x) || x == 0) return(TRUE) rx <- round(x, digits = digits) ((abs(rx - x) <= .irace_tolerance * max(1, abs(x))) && digits >= -log10(abs(x))) } parse_exp_domain <- function(domain) { # Convert to expression if string domain <- sapply(domain, USE.NAMES=FALSE, function(x) if (is.character(x)) str2expression(x) else x) # If is an expression or language without variables, then evaluate to convert it to numeric. domain <- sapply(domain, USE.NAMES=FALSE, function(x) if (is.numeric(x) || length(all.vars(x, unique=FALSE))) x else eval(x)) domain } Parameter <- function(name, type, domain, label, condition, transf, digits = if (type=='r') 15L else NULL) { cls <- switch(type, i="ParamInt", r="ParamReal", c="ParamCat", o="ParamOrd", stop("Unknown parameter type '", type, "'.")) if (!isTRUE(condition)) { condition <- param_parse_condition(condition) } if (type %in% c("i", "r")) { exp_domain <- parse_exp_domain(domain) domain <- sapply(exp_domain, USE.NAMES=FALSE, function(x) if (is.numeric(x)) x else NA) if (type == "r") { # digits >= 15 is almost infinite-precision so we do not complain. digits <- as.integer(digits) if (digits < 15L && (!valid_real_bound(domain[1L], digits) || !valid_real_bound(domain[2L], digits))) { for (i in seq.int(digits+1L, 15L)) { if (valid_real_bound(domain[1L], i) && valid_real_bound(domain[2L], i)) break } stop("Domain bounds (", domain[1L], ", ", domain[2L], ") of parameter '", name, "' of type 'real' must be representable within the given 'digits=", digits, "'; you would need at least 'digits=", i, "' or adjust the domain") } if (anyNA(domain)) domain <- as.expression(exp_domain) } else { # type == "i" if (any(!is.wholenumber(domain[!is.na(domain)]))) named_stop("invalid_domain", "for parameter '", name, "' of type 'integer', values must be integers") if (anyNA(domain)) domain <- as.expression(sapply(exp_domain, USE.NAMES=FALSE, function(x) if (is.numeric(x)) as.integer(x) else x)) else domain <- as.integer(domain) } if (!is.expression(domain) && domain[1L] >= domain[2L]) named_stop("invalid_range", "lower bound must be smaller than upper bound in numeric range") } else { # type %in% c("c", "o") if (anyDuplicated(domain)) { dups <- duplicated(domain) stop("duplicated values (", paste0('\"', domain[dups], "\"", collapse = ', '), ") for parameter '", name, "'") } if (type == "o") { tmp <- suppressWarnings(as.numeric(domain)) if (!anyNA(tmp) && !identical(order(tmp), seq_along(tmp))) stop("the domain of parameter '", name, "' appears to be a discretization of a numerical range, but the values are not in increasing order: ", paste0(domain, collapse = ', ')) } } if (transf != "") transf <- transform_domain(transf, domain, type) isFixed <- (type == "c" || type == "o") && (length(domain) == 1L) param <- list(name = name, type = type, domain = domain, label = label, is_dependent = is.expression(domain), isFixed = isFixed, transform = transf, condition = condition) if (type == "r") param$digits <- digits class(param) <- c(cls, "Parameter", class(param)) param } #' Create a parameter space to be tuned. #' #' @description #' - `param_cat()` creates a categorical parameter. #' - `param_ord()` creates an ordinal parameter. #' - `param_real()` creates a real-valued parameter. #' - `param_int()` creates an integer parameter. #' #' @param ... one or more parameters created by `param_int()`, `param_real()`, `param_ord()`, or `param_cat()`. #' @param forbidden `expression()|character(1)`\cr String or list of expressions that define forbidden parameter configurations. #' @inheritParams readParameters #' #' @return `ParameterSpace` #' @name parameters #' @examples #' digits <- 4L #' parametersNew( #' param_cat(name = "algorithm", values = c("as", "mmas", "eas", "ras", "acs"), label = "--"), #' param_ord(name = "localsearch", values = c("0", "1", "2", "3"), label = "--localsearch "), #' param_real(name = "alpha", lower = 0.0, upper=5.0, label = "--alpha ", digits = digits), #' param_real(name = "beta", lower = 0.0, upper = 10.0, label = "--beta ", digits = digits), #' param_real(name = "rho", lower = 0.01, upper = 1.00, label = "--rho ", digits = digits), #' param_int(name = "ants", lower = 5, upper = 100, transf = "log", label = "--ants "), #' param_real(name = "q0", label = "--q0 ", lower=0.0, upper=1.0, #' condition = expression(algorithm == "acs")), #' param_int(name = "rasrank", label = "--rasranks ", lower=1, upper=quote(min(ants, 10)), #' condition = 'algorithm == "ras"'), #' param_int(name = "elitistants", label = "--elitistants ", lower=1, upper=expression(ants), #' condition = 'algorithm == "eas"'), #' param_int(name = "nnls", label = "--nnls ", lower = 5, upper = 50, #' condition = expression(localsearch %in% c(1,2,3))), #' param_cat(name = "dlb", label = "--dlb ", values = c(0,1), #' condition = "localsearch %in% c(1,2,3)"), #' forbidden = "(alpha == 0) & (beta == 0)") #' #' @export parametersNew <- function(..., forbidden = NULL, debugLevel = 0L) ParameterSpace$new(..., forbidden = forbidden, verbose = debugLevel) ParameterSpace <- R6::R6Class("ParameterSpace", cloneable = TRUE, lock_class = TRUE, lock_objects = TRUE, public = list( .params = NULL, conditions = NULL, depends = NULL, domains = NULL, forbidden = NULL, hierarchy = NULL, isFixed = NULL, names = NULL, names_fixed = NULL, names_variable = NULL, names_numeric = NULL, nbParameters = NULL, nbFixed = NULL, nbVariable = NULL, switches = NULL, types = NULL, initialize = function(..., forbidden = NULL, verbose = 0L) { .params <- list(...) names(.params) <- sapply(.params, "[[", "name") self$names <- names(.params) self$types <- sapply(.params, "[[", "type") self$switches <- sapply(.params, "[[", "label") # This has to be a list because each element is a vector. self$domains <- lapply(.params, "[[", "domain") self$conditions <- lapply(.params, "[[", "condition") self$isFixed <- sapply(.params, "[[", "isFixed") self$names_fixed <- self$names[self$isFixed] self$names_variable <- self$names[!self$isFixed] self$names_numeric <- self$names[self$types %in% c("r", "i")] self$nbParameters <- length(.params) self$nbFixed <- sum(self$isFixed) self$nbVariable <- sum(!self$isFixed) # Obtain the variables in each condition self$depends <- lapply(self$domains, all.vars) # Check that domain dependencies are OK. check_domain_dependencies(.params, self$depends, self$types) # Merge dependencies and conditions self$depends <- Map(union, self$depends, lapply(self$conditions, all.vars)) # Sort parameters in 'conditions' in the proper order according to conditions hierarchyLevel <- sapply(names(.params), param_level, varsTree = self$depends) self$hierarchy <- hierarchyLevel self$conditions <- self$conditions[order(hierarchyLevel)] names(self$hierarchy) <- names(.params) stopifnot(length(self$conditions) == length(.params)) if (!is.null(forbidden)) { if (is.language(forbidden)) forbidden <- as.expression(forbidden) else if (is.character(forbidden)) forbidden <- str2expression(forbidden) if (!is.expression(forbidden)) stop ("Invalid forbidden expression.") check_forbidden_params(forbidden, names(.params)) # FIXME: Instead of a list, we should generate a single expression that is # the logical-OR of all elements of the list. # First we would need to handle the "is.na(x) | !(x)" case here. # Maybe: sapply(forbiddenExps, function(x) substitute(is.na(x) | !(x), list(x=x))) # x <- parse(text=paste0("(", paste0(forbiddenExps,collapse=")||("), ")")) self$forbidden <- sapply(forbidden, compile_forbidden) } # Optimize always TRUE conditions. cond_names <- names(which(!unlist(lapply(self$conditions, is.logical)))) for (p in cond_names) { deps <- self$depends[[p]] # p depends on deps and they are both fixed and always active. if (all(self$isFixed[deps]) && all(sapply(self$conditions[deps], isTRUE, USE.NAMES=FALSE))) { self$conditions[[p]] <- eval(self$conditions[[p]], envir = self$domains[deps], enclos = NULL) } } # Print the hierarchy vector: if (verbose >= 1L) { cat ("# --- Parameters Hierarchy ---\n") print(data.frame(Parameter = paste0(names(self$hierarchy)), Level = self$hierarchy, "Depends on" = sapply(names(self$hierarchy), function(x) paste0("", x, collapse=", ")), row.names=NULL)) cat("# ------------------------\n") } self$.params <- .params }, get = function(x) if (missing(x)) self$.params else self$.params[[x]], get_ordered = function() self$.params[names(self$conditions)], forbid_configurations = function(x) { # FIXME: This copies the whole list. Avoid the copy and just append. self$forbidden <- c(self$forbidden, buildForbiddenExp(configurations = x[, self$names, drop=FALSE])) }, as_character = function() { names_len <- max(nchar(names(self$.params))) label_len <- max(nchar(sapply(self$.params, "[[", "label"))) + 2L output <- "" digits <- c() for (p in self$.params) { type <- p[["type"]] domain <- p[["domain"]] if (is.expression(domain)) { domain <- sapply(domain, USE.NAMES=FALSE, function(x) { if (is.numeric(x)) { if (type == "r") x <- formatC(x, digits = p[["digits"]], format="f", drop0trailing=TRUE) } else { x <- paste0("\"", deparse(x, width.cutoff = 500L), "\"") } x }) } domain <- paste0('(', paste0(domain, collapse=","), ')') condition <- p[["condition"]] condition <- if (isTRUE(condition)) "" else paste0(" | ", condition) transf <- p[["transform"]] if (!is.null(transf) && transf != "") type <- paste0(type, ",", transf) label <- paste0('"', p[["label"]], '"') output <- paste0(output, sprintf('%*s %*s %s %-15s%s\n', -names_len, p[["name"]], -label_len, label, type, domain, condition)) if (type == "r") digits <- c(digits, p[["digits"]]) } if (!is.null(self$forbidden)) { output <- paste0(output, "\n[forbidden]\n", paste0(collapse="\n", sapply(self$forbidden, attr, "source", USE.NAMES=FALSE)), "\n") } digits <- unique(digits) n <- length(digits) if (n > 1L) stop("Different digits per parameter is not supported yet") if (n == 1L) output <- paste0(output, "\n[global]\ndigits = ", digits[[1L]], "\n") output }), ) #' @param name `character(1)`\cr Parameter name (must be alphanumeric). #' @param values `character()`\cr Domain as a vector of strings. #' @param label `character(1)`\cr Label associated to the parameter. Often used to encode a command-line switch that activates the parameter. #' @param condition `expression(1)|character(1)`\cr Expression that defines when the parameter is active according to the value of other parameters. #' @rdname parameters #' @export param_cat <- function(name = name, values, label = "", condition = TRUE) Parameter(name = name, type = "c", domain = as.character(values), label = label, condition = condition, transf = "") #' @rdname parameters #' @export param_ord <- function(name, values, label = "", condition = TRUE) Parameter(name = name, type = "o", domain = as.character(values), label = label, condition = condition, transf = "") #' @param lower,upper Lower and upper limits of the valid domain. #' @param transf `character(1)`\cr If `"log"`, then the parameter is sampled in a logarithmic scale. #' @inheritParams readParameters #' @rdname parameters #' @export param_real <- function(name, lower, upper, label = "", condition = TRUE, transf = "", digits = 15L) Parameter(name = name, type = "r", domain = c(lower, upper), label = label, condition = condition, transf = transf, digits = digits) #' @rdname parameters #' @export param_int <- function(name, lower, upper, label = "", condition = TRUE, transf = "") Parameter(name = name, type = "i", domain = c(lower, upper), label = label, condition = condition, transf = transf) transform_from_log <- function(x, transf, lower, upper) { trLower <- attr(transf, "lower") trUpper <- attr(transf, "upper") x <- exp(trLower + (trUpper - trLower) * x) clamp(x, lower, upper) } transform_to_log <- function(x, transf) { trLower <- attr(transf, "lower") trUpper <- attr(transf, "upper") (log(x) - trLower)/(trUpper - trLower) } ## How to sample integer values? # # The problem: If we have an integer with domain [1,3] and we sample a real value # and round, then there are more chances of getting 2 than 1 or 3: # [1, 1,5) -> 1 # [1.5, 2,5) -> 2 # [2.5, 3) -> 3 # # The solution: Sample in [lowerbound, upperbound + 1], that is, [1, 4], then floor(): # [1, 2) -> 1 # [2, 3) -> 2 # [3, 4) -> 3 # # Why floor() and not trunc()? # Because trunc(-1.5) -> -1, while floor(-1.5) -> -2, so for a domain [-3,-1]: # # [-3, -2) -> -3 # [-2, -1) -> -2 # [-1, 0) -> -1 # # Issue 1: We can sample 4 (upperbound + 1). In that case, we return 3. # # Issue 2: When sampling from a truncated normal distribution, the extremes are # not symmetric. # # nsamples <- 100000 # table(floor(rtnorm(nsamples, mean=1, sd=1, lower=1,upper=4)))/nsamples # table(floor(rtnorm(nsamples, mean=3, sd=1, lower=1,upper=4)))/nsamples # # To make them symmetric, we translate by 0.5, so that the mean is at the # actual center of the interval that will produce the same value after # truncation, e.g., given an integer value of 1, then mean=1.5, which is at the # center of [1,2). # # nsamples <- 100000 # table(floor(rtnorm(nsamples, mean=1.5, sd=1, lower=1,upper=4)))/nsamples # table(floor(rtnorm(nsamples, mean=3.5, sd=1, lower=1,upper=4)))/nsamples # # The above reasoning also works for log-transformed domains, because # floor() happens in the original domain, not in the log-transformed one, # except for the case of log-transformed negative domains, where we have to # translate by -0.5. # integer_round <- function(x, lower, upper) { x <- floor(x) # The probability of this happening is very small, but it happens. x <- pmin.int(upper, x) irace_assert(all(x >= lower)) as.integer(x) } sample_numeric_unif <- function(n, lower, upper, transf) { if (transf == "log") { value <- runif(n, min = 0, max = 1) return(transform_from_log(value, transf, lower, upper)) } runif(n, min = lower, max = upper) } sample_numeric_norm <- function(n, mean, sd, lower, upper, transf) { if (transf == "log") { x <- transform_to_log(mean, transf) x <- rtnorm(n, x, sd, lower = 0, upper = 1) return(transform_from_log(x, transf, lower, upper)) } rtnorm(n, mean, sd, lower, upper) } sample_model <- function(param, n, model, domain) UseMethod("sample_model") sample_unif <- function(param, n, domain) UseMethod("sample_unif") param_quantile <- function(param, probs, domain) UseMethod("param_quantile") #' @exportS3Method sample_model.ParamCat <- function(param, n, model, domain = param[["domain"]]) sample(domain, size = n, replace = TRUE, prob = model) #' @exportS3Method sample_unif.ParamCat <- function(param, n, domain = param[["domain"]]) sample(domain, n, replace=TRUE) #' @exportS3Method param_quantile.ParamCat <- function(param, probs, domain = param[["domain"]]) { n <- length(domain) z <- integer_round(as.numeric(probs) * n + 1L, 1L, n) domain[z] } #' @exportS3Method sample_model.ParamOrd <- function(param, n, model, domain = param[["domain"]]) domain[floor(sample_numeric_norm(n, mean = model[[2L]], sd = model[[1L]], lower = 1L, upper = length(domain), transf = ""))] #' @exportS3Method sample_unif.ParamOrd <- function(param, n, domain = param[["domain"]]) sample(domain, n, replace=TRUE) #' @exportS3Method param_quantile.ParamOrd <- param_quantile.ParamCat #' @exportS3Method sample_model.ParamInt <- function(param, n, model, domain = param[["domain"]]) { # Dependent domains could be not available because of inactivity of parameters # on which they are depedent. In this case, the dependent parameter becomes # not active and we return NA. if (anyNA(domain)) return(NA_integer_) lower <- domain[1L] upper <- domain[2L] transf <- param[["transform"]] mean <- model[[2L]] if (is.na(mean)) # +1 for correct rounding before floor() value <- sample_numeric_unif(n, lower, 1L + upper, transf) else # + 0.5 because negative domains are log-transformed to positive domains. value <- sample_numeric_norm(n, mean + 0.5, sd = model[[1L]], lower = lower, # +1 for correct rounding before floor() upper = 1L + upper, transf = transf) # We use original upper, not the +1L for 'i'. integer_round(value, lower, upper) } #' @exportS3Method sample_unif.ParamInt <- function(param, n, domain = param[["domain"]]) { # Dependent domains could be not available because of inactivity of parameters # on which they are depedent. In this case, the dependent parameter becomes # not active and we return NA. if (anyNA(domain)) return(NA_integer_) lower <- domain[1L] upper <- domain[2L] # +1 for correct rounding before floor() value <- sample_numeric_unif(n, lower, 1L + upper, transf = param[["transform"]]) # We use original upperBound, not the +1L for 'i'. integer_round(value, lower, upper) } #' @exportS3Method param_quantile.ParamInt <- function(param, probs, domain = param[["domain"]]) { # Dependent domains could be not available because of inactivity of parameters # on which they are depedent. In this case, the dependent parameter becomes # not active and we return NA. if (anyNA(domain)) return(NA_integer_) lower <- domain[1L] upper <- domain[2L] probs <- as.numeric(probs) transf <- param[["transform"]] if (transf == "log") { # +1 for correct rounding before floor() probs <- transform_from_log(probs, transf, lower, upper + 1L) } else { probs <- probs * (upper + 1L - lower) + lower } integer_round(probs, lower, upper) } #' @exportS3Method sample_model.ParamReal <- function(param, n, model, domain = param[["domain"]]) { # Dependent domains could be not available because of inactivity of parameters # on which they are depedent. In this case, the dependent parameter becomes # not active and we return NA. if (anyNA(domain)) return(NA_real_) lower <- domain[[1L]] upper <- domain[[2L]] transf <- param[["transform"]] mean <- model[[2L]] if (is.na(mean)) x <- sample_numeric_unif(n, lower, upper, transf) else x <- sample_numeric_norm(n, mean, sd = model[[1L]], lower, upper, transf) x <- round(x, digits = param[["digits"]]) irace_assert(all(x >= lower) && all(x <= upper)) x } #' @exportS3Method sample_unif.ParamReal <- function(param, n, domain = param[["domain"]]) { # Dependent domains could be not available because of inactivity of parameters # on which they are depedent. In this case, the dependent parameter becomes # not active and we return NA. if (anyNA(domain)) return(NA_real_) lower <- domain[[1L]] upper <- domain[[2L]] x <- sample_numeric_unif(n, lower, upper, transf = param[["transform"]]) x <- round(x, digits = param[["digits"]]) irace_assert(all(x >= lower) && all(x <= upper)) x } #' @exportS3Method param_quantile.ParamReal <- function(param, probs, domain = param[["domain"]]) { # Dependent domains could be not available because of inactivity of parameters # on which they are depedent. In this case, the dependent parameter becomes # not active and we return NA. if (anyNA(domain)) return(NA_real_) lower <- domain[1L] upper <- domain[2L] probs <- as.numeric(probs) digits <- param[["digits"]] transf <- param[["transform"]] if (transf == "log") return(round(transform_from_log(probs, transf, lower, upper), digits)) probs <- probs * (upper - lower) + lower clamp(round(probs, digits), lower, upper) } #' Print parameter space in the textual format accepted by irace. #' #' @param parameters `ParameterSpace`\cr Data structure containing the parameter #' space definition. The data structure has to similar to the one returned by the #' function [`readParameters`]. #' #' #' @return `character()` #' #' @seealso [readParameters()] #' @examples #' parameters_table <- ' #' # name switch type values [conditions (using R syntax)] #' algorithm "--" c (as,mmas,eas,ras,acs) #' localsearch "--localsearch " c (0, 1, 2, 3) #' alpha "--alpha " r (0.00, 5.00) #' beta "--beta " r (0.00, 10.00) #' rho "--rho " r (0.01, 1.00) #' ants "--ants " i,log (5, 100) #' q0 "--q0 " r (0.0, 1.0) | algorithm == "acs" #' q0dep "--q0 " r (0.0, q0) | algorithm != "acs" #' rasrank "--rasranks " i (1, "min(ants, 10)") | algorithm == "ras" #' elitistants "--elitistants " i (1, ants) | algorithm == "eas" #' nnls "--nnls " i (5, 50) | localsearch %in% c(1,2,3) #' dlb "--dlb " c (0, 1) | localsearch %in% c(1,2,3) #' #' [forbidden] #' (alpha == 0.0) & (beta == 0.0) #' ' #' parameters <- readParameters(text=parameters_table) #' printParameters(parameters) #' @export printParameters <- function(parameters) cat(parameters$as_character()) #' @export print.ParameterSpace <- function(x, digits = 15L, ...) { utils::str(x$.params, digits.d = digits) cat("Forbidden:\n") print(x$forbidden, digits = 15L) } ## digits <- 4L ## x <- parametersNew(param_cat(name = "algorithm", values = c("as", "mmas", "eas", "ras", "acs"), label = "--"), ## param_ord(name = "localsearch", values = c("0", "1", "2", "3"), label = "--localsearch "), ## param_real(name = "alpha", lower = 0.0, upper=5.0, label = "--alpha ", digits = digits), ## param_real(name = "beta", lower = 0.0, upper = 10.0, label = "--beta ", digits = digits), ## param_real(name = "rho", lower = 0.01, upper = 1.00, label = "--rho ", digits = digits), ## param_int(name = "ants", lower = 5, upper = 100, transf = "log", label = "--ants "), ## param_real(name = "q0", label = "--q0 ", lower=0.0, upper=1.0, condition = expression(algorithm == "acs")), ## param_int(name = "rasrank", label = "--rasranks ", lower=1, upper=quote(min(ants, 10)), condition = 'algorithm == "ras"'), ## param_int(name = "elitistants", label = "--elitistants ", lower=1, upper=expression(ants), condition = 'algorithm == "eas"'), ## param_int(name = "nnls", label = "--nnls ", lower = 5, upper = 50, condition = expression(localsearch %in% c(1,2,3))), ## param_cat(name = "dlb", label = "--dlb ", values = c(0,1), condition = "localsearch %in% c(1,2,3)"), ## param_cat(name = "fixed1", label = "--fixed1 ", values = "1"), ## param_cat(name = "fixed2", label = "--fixed2 ", values = "0"), ## forbidden = "(alpha == 0) & (beta == 0)") ## printParameters(x) ## print(x) irace/R/irace-package.R0000644000176200001440000000317614736526233014357 0ustar liggesusers#' The irace package: \packageTitle{irace} #' #' \packageDescription{irace} #' #' @name irace-package #' @import stats matrixStats withr data.table #' @importFrom utils str #' @importFrom R6 R6Class #' @importFrom graphics abline axis boxplot par plot points strwidth bxp grid #' @importFrom spacefillr generate_sobol_set #' #' @details License: GPL (>= 2) #' #' @author Maintainers: Manuel López-Ibáñez and Leslie Pérez Cáceres #' \email{irace-package@googlegroups.com} #' #' @keywords package optimize tuning automatic configuration #' #' @references #' Manuel López-Ibáñez, Jérémie Dubois-Lacoste, Leslie Pérez Cáceres, #' Thomas Stützle, and Mauro Birattari. The irace package: Iterated #' Racing for Automatic Algorithm Configuration. \emph{Operations Research #' Perspectives}, 2016. \doi{10.1016/j.orp.2016.09.002} #' #' Manuel López-Ibáñez, Jérémie Dubois-Lacoste, Thomas Stützle, and Mauro #' Birattari. \emph{The irace package, Iterated Race for Automatic #' Algorithm Configuration}. Technical Report TR/IRIDIA/2011-004, IRIDIA, #' Université Libre de Bruxelles, Belgium, 2011. #' #' Manuel López-Ibáñez and Thomas Stützle. The Automatic Design of #' Multi-Objective Ant Colony Optimization Algorithms. \emph{IEEE Transactions #' on Evolutionary Computation}, 2012. #' @seealso #' [irace()] for examples and `vignette(package = "irace")` for the user-guide. "_PACKAGE" # This silences CRAN NOTE: # Namespace in Imports field not imported from: 'codetools' # All declared Imports should be used. .ignore_unused_imports <- function() { # nocov start codetools::findGlobals } # nocov end irace/R/scenario.R0000644000176200001440000011233214736567422013506 0ustar liggesusers#' Reads from a file the scenario settings to be used by \pkg{irace}. #' #' The scenario argument is an initial scenario that is overwritten for every #' setting specified in the file to be read. #' #' @param filename `character(1)`\cr Filename from which the scenario will #' be read. If empty, the default `scenarioFile` is used. An example #' scenario file is provided in `system.file(package="irace", "templates/scenario.txt.tmpl")`. #' @inheritParams defaultScenario #' #' @return The scenario list read from the file. The scenario settings not #' present in the file are not present in the list, i.e., they are `NULL`. #' #' @seealso #' \describe{ #' \item{[printScenario()]}{prints the given scenario.} #' \item{[defaultScenario()]}{returns the default scenario settings of \pkg{irace}.} #' \item{[checkScenario()]}{to check that the scenario is valid.} #' } #' #' @author Manuel López-Ibáñez and Jérémie Dubois-Lacoste #' @export readScenario <- function(filename = "", scenario = list(), params_def = .irace.params.def) { # This function allows recursively including scenario files. scenario_env <- new.env() include.scenario <- function(rfilename, topfile = filename, envir. = scenario_env) { if (!file.exists (rfilename)) { irace_error ("The scenario file ", shQuote(rfilename), " included from ", shQuote(topfile), " does not exist.") } handle.source.error <- function(e) { irace_error("Reading scenario file ", shQuote(rfilename), " included from ", shQuote(topfile), " produced the following errors or warnings:\n", paste0(conditionMessage(e), collapse="\n")) return(NULL) } withCallingHandlers( tryCatch(source(rfilename, local = envir., chdir = TRUE), error = handle.source.error, warning = handle.source.error)) } # First find out which file... filename_given <- filename != "" if (!filename_given) { filename <- path_rel2abs(params_def["scenarioFile","default"]) if (file.exists(filename)) { irace_warning("A default scenario file ", shQuote(filename), " has been found and will be read.") } else { irace_warning ("No scenario file given (use ", params_def["scenarioFile", "short"], " or ", params_def["scenarioFile", "long"], ") and no default scenario file ", shQuote(filename), " has been found.") scenario[["scenarioFile"]] <- filename return(scenario) } } else { filename <- path_rel2abs(filename) } if (file.exists (filename)) { debug.level <- getOption(".irace.debug.level", default = 0) if (debug.level >= 1) cat("# Reading scenario file", shQuote(filename), ".......") # chdir = TRUE to allow recursive sourcing. handle.source.error <- function(e) { irace_error("Reading scenario file ", shQuote(filename), " produced the following errors or warnings:\n", paste0(conditionMessage(e), collapse="\n")) return(NULL) } withCallingHandlers( tryCatch(source(filename, local = scenario_env, chdir = TRUE), error = handle.source.error, warning = handle.source.error)) if (debug.level >= 1) cat (" done!\n") } else if (filename_given) { irace_error ("The scenario file ", shQuote(filename), " does not exist.") } ## Read scenario file variables. scenario[["scenarioFile"]] <- filename # If these are given and relative, they should be relative to the # scenario file (except logFile, which is relative to execDir). pathParams <- setdiff(params_def[params_def[, "type"] == "p", "name"], "logFile") params_names <- params_def[!startsWith(params_def[,"name"], "."), "name"] for (param in params_names) { if (exists (param, envir = scenario_env, inherits = FALSE)) { value <- get(param, envir = scenario_env, inherits = FALSE) if (filename_given && !is.null.or.empty(value) && is.character(value) && (param %in% pathParams)) { value <- path_rel2abs(value, cwd = dirname(filename)) } scenario[[param]] <- value } } unknown_scenario_vars <- setdiff(ls(scenario_env), params_names) if (length(unknown_scenario_vars)) { # We only accept variables that match irace.params.names and if the user # wants to define their own, they should use names starting with ".", which # are ignored by ls() irace_error("Scenario file ", shQuote(filename), " contains unknown variables: ", paste0(unknown_scenario_vars, collapse=", "), "\nMAKE SURE NO VARIABLE NAME IS MISSPELL (for example, 'parameterFile' is correct, while 'parametersFile' is not)", "\nIf you wish to use your own variables in the scenario file, use names beginning with a dot `.'") } scenario } setup_test_instances <- function(scenario) { testInstances <- scenario[["testInstances"]] if (is.null.or.empty(testInstances)) { if (!is.null.or.empty(scenario$testInstancesDir) || !is.null.or.empty(scenario$testInstancesFile)) { scenario$testInstancesDir <- path_rel2abs(scenario$testInstancesDir) if (!is.null.or.empty(scenario$testInstancesFile)) { scenario$testInstancesFile <- path_rel2abs(scenario$testInstancesFile) } testInstances <- readInstances(instancesDir = scenario$testInstancesDir, instancesFile = scenario$testInstancesFile) } else { testInstances <- NULL } } if (!is.null(testInstances)) { if (!is.null(dim(testInstances))) { if (length(dim(testInstances)) == 1L || (length(dim(testInstances)) == 2L && dim(testInstances)[1L] == 1L)) { # Remove useless dimensions testInstances <- c(testInstances) } else { irace_error("testInstances must be a one-dimensional vector or a list. If your instances are matrices or datasets in R, you can use scenario(testInstances=list(data1, data2, data3))") } } if (is.null(names(testInstances))) { # Create unique IDs for testInstances names(testInstances) <- paste0(seq_along(testInstances), "t") } } scenario[["testInstances"]] <- testInstances scenario } #' Check and correct the given scenario #' #' Checks for errors a (possibly incomplete) scenario setup of #' \pkg{irace} and transforms it into a valid scenario. #' #' @inheritParams defaultScenario #' #' @return The scenario received as a parameter, possibly corrected. Unset #' scenario settings are set to their default values. #' #' @details This function checks that the directories and the file names #' provided and required by the \pkg{irace} exist. It also checks that the #' settings are of the proper type, e.g. that settings expected to be integers #' are really integers. Finally, it also checks that there is no inconsistency #' between settings. If an error is found that prevents \pkg{irace} from #' running properly, it will stop with an error. #' #' @seealso #' \describe{ #' \item{[readScenario()]}{for reading a configuration scenario from a file.} #' \item{[printScenario()]}{prints the given scenario.} #' \item{[defaultScenario()]}{returns the default scenario settings of \pkg{irace}.} #' \item{[checkScenario()]}{to check that the scenario is valid.} #' } #' #' @author Manuel López-Ibáñez and Jérémie Dubois-Lacoste #' @export ## FIXME: This function should only do checks and return TRUE/FALSE. There ## should be other function that does the various transformations. checkScenario <- function(scenario = defaultScenario()) { quote.param <- function(name) { if (.irace.params.def[name, "long"] != "") { return(paste0("'", name, "' (", .irace.params.def[name, "long"], ")")) } paste0("'", name, "'") } as.boolean.param <- function(x, name) { if (is.logical(x)) return(x) if (is.na(x)) return(as.logical(NA)) # We handle "0" and "1" but not "TRUE" and "FALSE". x <- suppressWarnings(as.integer(x)) if (is.na(x) || (x != 0L && x != 1L)) { irace_error (quote.param(name), " must be either 0 or 1.") } as.logical(x) } check.valid.param <- function(x) { valid <- trimws(strsplit(.irace.params.def[x, "domain"],",",fixed=TRUE)[[1L]]) if (scenario[[x]] %not_in% valid) { irace_error ("Invalid value '", scenario[[x]], "' of ", quote.param(x), ", valid values are: ", paste0(valid, collapse = ", ")) } } # Fill possible unset (NULL) with default settings. scenario <- defaultScenario(scenario) # Duplicated entries will cause confusion. dups <- anyDuplicated(names(scenario)) if (dups) irace_error("scenario contains duplicated entries: ", names(scenario)[dups]) # We have characters everywhere, set to the right types to avoid problems # later. # Boolean control parameters. boolParams <- .irace.params.def[.irace.params.def[, "type"] == "b", "name"] for (p in boolParams) { scenario[[p]] <- as.boolean.param(scenario[[p]], p) } # Real [0, 1] control parameters. realParams <- .irace.params.def[.irace.params.def[, "type"] == "r", "name"] for (param in realParams) { if (is.null.or.empty(scenario[[param]])) scenario[[param]] <- NA_real_ if (is.na(scenario[[param]])) next # Allow NA default values scenario[[param]] <- suppressWarnings(as.numeric(scenario[[param]])) if (is.null(scenario[[param]]) || is.na (scenario[[param]]) || scenario[[param]] < 0.0 || scenario[[param]] > 1.0) irace_error (quote.param(param), " must be a real value within [0, 1].") } # Integer control parameters intParams <- .irace.params.def[.irace.params.def[, "type"] == "i", "name"] for (param in intParams) { p <- scenario[[param]] if (is.null.or.empty(p)) scenario[[param]] <- NA_integer_ if (is.na(scenario[[param]])) next # Allow NA default values p <- suppressWarnings(as.numeric(p)) if (is.null(p) || is.na (p) || !is.wholenumber(p) || p < 0) irace_error (quote.param (param), " must be a non-negative integer.") scenario[[param]] <- as.integer(p) } check_positive <- function(scenario, what) { if (any(scenario[what] <= 0L)) { for (op in what) { if (scenario[[op]] <= 0L) irace_error(quote.param (op), " = ", scenario[[op]], " must be larger than 0.") } } } check_positive(scenario, c("firstTest", "eachTest", "blockSize")) options(.irace.quiet = scenario$quiet) ## Check that everything is fine with external parameters # Check that the files exist and are readable. scenario$parameterFile <- path_rel2abs(scenario$parameterFile) if (is.null.or.empty(scenario$parameters)) { irace_note("Reading parameter file '", scenario$parameterFile, "'.\n") scenario$parameters <- readParameters(file = scenario$parameterFile, # AClib benchmarks use 15 digits digits = if (scenario$aclib) 15L else 4L, debugLevel = scenario$debugLevel) } scenario$parameters <- checkParameters(scenario$parameters) scenario$execDir <- path_rel2abs(scenario$execDir) file.check (scenario$execDir, isdir = TRUE, text = paste0("execution directory ", quote.param("execDir"))) options(.irace.execdir = scenario$execDir) if (is.null.or.empty(scenario$logFile)) { # We cannot use NULL because defaultScenario() would override it. scenario$logFile <- "" } else { scenario$logFile <- path_rel2abs(scenario$logFile, cwd = scenario$execDir) file.check(scenario$logFile, writeable = TRUE, text = quote.param('logFile')) } if (is.null.or.empty(scenario$recoveryFile)) { # We cannot use NULL because defaultScenario() would override it. scenario$recoveryFile <- "" } else { scenario$recoveryFile <- path_rel2abs(scenario$recoveryFile) file.check(scenario$recoveryFile, readable = TRUE, text = paste0("recovery file ", quote.param("recoveryFile"))) if (!is.null.or.empty(scenario$logFile) # Must have been set "" above. && scenario$recoveryFile == scenario$logFile) { irace_error("log file and recovery file should be different '", scenario$logFile, "'") } } if (is.null.or.empty(scenario$targetRunnerParallel)) { scenario$targetRunnerParallel <- NULL } else if (is.function.name(scenario$targetRunnerParallel)) { scenario$targetRunnerParallel <- get.function(scenario$targetRunnerParallel) } else { irace_error("'targetRunnerParallel' must be a function") } if (is.null.or.empty(scenario$repairConfiguration)) scenario$repairConfiguration <- NULL else if (is.function.name(scenario$repairConfiguration)) # Byte-compile it. scenario$repairConfiguration <- bytecompile(get.function(scenario$repairConfiguration)) else irace_error("'repairConfiguration' must be a function") if (is.na(scenario$capping)) scenario$capping <- (scenario$elitist && scenario$maxTime > 0 && !is.na(scenario$boundMax) && scenario$boundMax > 0) if (scenario$capping) { if (!scenario$elitist) irace_error("When capping == TRUE, elitist must be enabled.") if (scenario$boundMax <= 0) irace_error("When capping == TRUE, boundMax (", scenario$boundMax, ") must be > 0") check.valid.param("cappingType") check.valid.param("boundType") if (scenario$boundPar < 1) irace_error("Invalid value (", scenario$boundPar, ") ", quote.param("boundPar"), " must be >= 1") } else if (scenario$boundMax <= 0 || is.na(scenario$boundMax)) { # no capping scenario$boundMax <- NULL } if (is.function.name(scenario$targetRunner)) { scenario$targetRunner <- get.function(scenario$targetRunner) irace_assert(is.function(scenario$targetRunner)) } else if (is.null(scenario$targetRunnerParallel)) { if (is.character(scenario$targetRunner)) { scenario$targetRunner <- path_rel2abs(scenario$targetRunner) if (is.null.or.empty(scenario$targetRunnerLauncher)) { file.check (scenario$targetRunner, executable = TRUE, text = paste0("target runner ", quote.param("targetRunner"))) } else { scenario$targetRunnerLauncher <- path_rel2abs(scenario$targetRunnerLauncher) file.check (scenario$targetRunnerLauncher, executable = TRUE, text = paste0("target runner launcher ", quote.param("targetRunnerLauncher"))) file.check (scenario$targetRunner, readable = TRUE, text = paste0("target runner ", quote.param("targetRunner"))) if (!grepl("{targetRunner}", scenario$targetCmdline, fixed=TRUE)) { # If missing, we add it to the beginning for compatibility with irace 3.5. scenario$targetCmdline <- paste0("{targetRunner} ", scenario$targetCmdline) } } check_target_cmdline(scenario$targetCmdline, capping = scenario$capping) } else { irace_error(quote.param ('targetRunner'), " must be a function or an executable program") } } if (is.null.or.empty(scenario$targetEvaluator)) { scenario$targetEvaluator <- NULL } else if (is.function.name(scenario$targetEvaluator)) { scenario$targetEvaluator <- get.function(scenario$targetEvaluator) irace_assert(is.function(scenario$targetEvaluator)) } else if (is.character(scenario$targetEvaluator)) { scenario$targetEvaluator <- path_rel2abs(scenario$targetEvaluator) file.check (scenario$targetEvaluator, executable = TRUE, text = "target evaluator") } else { irace_error(quote.param('targetEvaluator'), " must be a function or an executable program") } # Training instances if (is.null.or.empty(scenario$instances)) { scenario$trainInstancesDir <- path_rel2abs(scenario$trainInstancesDir) if (!is.null.or.empty(scenario$trainInstancesFile)) { scenario$trainInstancesFile <- path_rel2abs(scenario$trainInstancesFile) } if (is.null.or.empty(scenario$trainInstancesDir) && is.null.or.empty(scenario$trainInstancesFile)) irace_error("Both ", quote.param ("trainInstancesDir"), " and ", quote.param ("trainInstancesFile"), " are empty: No instances provided") scenario$instances <- readInstances(instancesDir = scenario$trainInstancesDir, instancesFile = scenario$trainInstancesFile) } if (length(scenario$instances) == 0L) { irace_error("No instances found in `scenario$instances`") } else if (!is.null(dim(scenario$instances))) { if (length(dim(scenario$instances)) == 1L || (length(dim(scenario$instances)) == 2L && dim(scenario$instances)[1] == 1L)) { # Remove useless dimensions scenario$instances <- c(scenario$instances) } else { irace_error("Instances must be a one-dimensional vector or a list. If your instances are matrices or datasets in R, you can use scenario(instances=list(data1, data2, data3))") } } # Testing instances scenario <- setup_test_instances(scenario) # Configurations file if (!is.null.or.empty(scenario$configurationsFile)) { scenario$configurationsFile <- path_rel2abs(scenario$configurationsFile) file.check (scenario$configurationsFile, readable = TRUE, text = "configurations file") # We cannot read the configurations here because we need the parameters. # FIXME: We should have the parameters inside scenario. } if (is.null.or.empty(scenario$initConfigurations)) { scenario$initConfigurations <- NULL } else if (!is.data.frame(scenario$initConfigurations) && !is.matrix(scenario$initConfigurations)) { irace_error("if given, 'initConfigurations' must be a matrix or data.frame.") } if (length(scenario$instances) %% scenario$blockSize != 0) { irace_error("The number of instances (", length(scenario$instances), ") must be a multiple of ", quote.param("blockSize"), ".") } if (scenario$firstTest %% scenario$eachTest != 0) { irace_error(quote.param("firstTest"), " must be a multiple of ", quote.param("eachTest"), ".") } if (scenario$mu < scenario$firstTest * scenario$blockSize) { if (scenario$debugLevel >= 1) { irace_warning("Assuming 'mu = firstTest * blockSize' because 'mu' cannot be smaller.\n") } scenario$mu <- scenario$firstTest * scenario$blockSize } else if (scenario$mu %% scenario$blockSize != 0) { irace_error(quote.param("mu"), " must be a multiple of ", quote.param("blockSize"), ".") } if (!is.na(scenario$minExperiments)) scenario$maxExperiments <- max(scenario$maxExperiments, scenario$minExperiments) ## Only maxExperiments or maxTime should be set. Negative values are not ## allowed. if (scenario$maxExperiments == 0 && scenario$maxTime == 0) { irace_error("Tuning budget was not provided. Set ", quote.param("maxExperiments"), " or ", quote.param("maxTime"), ".") } else if (scenario$maxExperiments > 0 && scenario$maxTime > 0) { irace_error("Two different tuning budgets provided, please set only ", quote.param("maxExperiments"), " or only ", quote.param ("maxTime"), ".") } else if (scenario$maxExperiments < 0 ) { irace_error("Negative budget provided, ", quote.param("maxExperiments"), "must be >= 0." ) } else if (scenario$maxTime < 0) { irace_error("Negative budget provided, ", quote.param("maxTime"), " must be >= 0.") } if (scenario$maxTime > 0 && (scenario$budgetEstimation <= 0 || scenario$budgetEstimation >= 1)) irace_error(quote.param("budgetEstimation"), " must be within (0,1).") if (scenario$maxTime > 0 && !scenario$elitist) irace_error(quote.param("maxTime"), " requires using 'elitist=1'") if (scenario$deterministic && scenario$firstTest * scenario$blockSize > length(scenario$instances)) { irace_error("When deterministic == TRUE, the number of instances (", length(scenario$instances), ") cannot be smaller than firstTest (", scenario$firstTest, ") * blockSize (", scenario$blockSize, ")") } if (scenario$mpi && scenario$parallel < 2) irace_error (quote.param("parallel"), " must be larger than 1 when mpi is enabled.") if (is.null.or.empty(scenario$batchmode)) scenario$batchmode <- 0 if (scenario$batchmode != 0) { scenario$batchmode <- tolower(scenario$batchmode) check.valid.param("batchmode") } # Currently batchmode requires a targetEvaluator if (scenario$batchmode != 0 && is.null(scenario$targetEvaluator)) { irace_error(quote.param("batchmode"), " requires using ", quote.param("targetEvaluator"), ".") } if (scenario$batchmode != 0 && scenario$mpi) { irace_error(quote.param("mpi"), " and ", quote.param("batchmode"), " cannot be enabled at the same time.") } if (is_null_or_empty_or_na(scenario$testType)) { if (scenario$capping) scenario$testType <- "t-test" else scenario$testType <- "f-test" } scenario$testType <- switch(tolower(scenario$testType), "f-test" =, # Fall-through "friedman" = "friedman", "t-test" =, # Fall-through "t.none" = "t.none", "t-test-holm" =, # Fall-through, "t.holm" = "t.holm", "t-test-bonferroni" =, # Fall-through, "t.bonferroni" = "t.bonferroni", check.valid.param("testType")) scenario } #' Prints the given scenario #' #' @inheritParams defaultScenario #' #' @seealso #' \describe{ #' \item{[readScenario()]}{for reading a configuration scenario from a file.} #' \item{[printScenario()]}{prints the given scenario.} #' \item{[defaultScenario()]}{returns the default scenario settings of \pkg{irace}.} #' \item{[checkScenario()]}{to check that the scenario is valid.} #' } #' #' @author Manuel López-Ibáñez and Jérémie Dubois-Lacoste #' @export printScenario <- function(scenario) { scenario_names <- .irace.params.names cat("## irace scenario:\n") for (name in scenario_names) { cat(name, " = ", deparse(scenario[[name]]), "\n", sep = "") } cat("## end of irace scenario\n") } #' Default scenario settings #' #' Return scenario object with default values. #' #' @param scenario `list()`\cr Data structure containing \pkg{irace} #' settings. The data structure has to be the one returned by the function #' [defaultScenario()] or [readScenario()]. #' #' @param params_def `data.frame()`\cr Definition of the options accepted by #' the scenario. This should only be modified by packages that wish to extend #' \pkg{irace}. #' #' #' @return A list indexed by the \pkg{irace} parameter names, #' containing the default values for each parameter, except for those #' already present in the scenario passed as argument. #' The scenario list contains the following elements: # __IRACE_OPTIONS__BEGIN__ #' \itemize{ #' \item General options: #' \describe{ #' \item{`scenarioFile`}{Path of the file that describes the configuration scenario setup and other irace settings. (Default: `"./scenario.txt"`)} #' \item{`execDir`}{Directory where the programs will be run. (Default: `"./"`)} #' \item{`logFile`}{File to save tuning results as an R dataset, either absolute path or relative to execDir. (Default: `"./irace.Rdata"`)} #' \item{`quiet`}{Reduce the output generated by irace to a minimum. (Default: `0`)} #' \item{`debugLevel`}{Debug level of the output of `irace`. Set this to 0 to silence all debug messages. Higher values provide more verbose debug messages. (Default: `0`)} #' \item{`seed`}{Seed of the random number generator (by default, generate a random seed). (Default: `NA`)} #' \item{`repairConfiguration`}{User-defined R function that takes a configuration generated by irace and repairs it. (Default: `""`)} #' \item{`postselection`}{Perform a postselection race after the execution of irace to consume all remaining budget. Value 0 disables the postselection race. (Default: `1`)} #' \item{`aclib`}{Enable/disable AClib mode. This option enables compatibility with GenericWrapper4AC as targetRunner script. (Default: `0`)} #' } #' \item Elitist `irace`: #' \describe{ #' \item{`elitist`}{Enable/disable elitist irace. (Default: `1`)} #' \item{`elitistNewInstances`}{Number of instances added to the execution list before previous instances in elitist irace. (Default: `1`)} #' \item{`elitistLimit`}{In elitist irace, maximum number per race of elimination tests that do not eliminate a configuration. Use 0 for no limit. (Default: `2`)} #' } #' \item Internal `irace` options: #' \describe{ #' \item{`sampleInstances`}{Randomly sample the training instances or use them in the order given. (Default: `1`)} #' \item{`softRestart`}{Enable/disable the soft restart strategy that avoids premature convergence of the probabilistic model. (Default: `1`)} #' \item{`softRestartThreshold`}{Soft restart threshold value for numerical parameters. (Default: `1e-04`)} #' \item{`nbIterations`}{Maximum number of iterations. (Default: `0`)} #' \item{`nbExperimentsPerIteration`}{Number of runs of the target algorithm per iteration. (Default: `0`)} #' \item{`minNbSurvival`}{Minimum number of configurations needed to continue the execution of each race (iteration). (Default: `0`)} #' \item{`nbConfigurations`}{Number of configurations to be sampled and evaluated at each iteration. (Default: `0`)} #' \item{`mu`}{Parameter used to define the number of configurations sampled and evaluated at each iteration. (Default: `5`)} #' } #' \item Target algorithm parameters: #' \describe{ #' \item{`parameterFile`}{File that contains the description of the parameters of the target algorithm. (Default: `"./parameters.txt"`)} #' \item{`parameters`}{Parameters space object (usually read from a file using \code{readParameters}). (Default: `""`)} #' } #' \item Target algorithm execution: #' \describe{ #' \item{`targetRunner`}{Executable called for each configuration that executes the target algorithm to be tuned. See the templates and examples provided. (Default: `"./target-runner"`)} #' \item{`targetRunnerLauncher`}{Executable that will be used to launch the target runner, when `targetRunner` cannot be executed directly (e.g., a Python script in Windows). (Default: `""`)} #' \item{`targetCmdline`}{Command-line arguments provided to `targetRunner` (or `targetRunnerLauncher` if defined). The substrings `\{configurationID\}`, `\{instanceID\}`, `\{seed\}`, `\{instance\}`, and `\{bound\}` will be replaced by their corresponding values. The substring `\{targetRunnerArgs\}` will be replaced by the concatenation of the switch and value of all active parameters of the particular configuration being evaluated. The substring `\{targetRunner\}`, if present, will be replaced by the value of `targetRunner` (useful when using `targetRunnerLauncher`). (Default: `"{configurationID} {instanceID} {seed} {instance} {bound} {targetRunnerArgs}"`)} #' \item{`targetRunnerRetries`}{Number of times to retry a call to `targetRunner` if the call failed. (Default: `0`)} #' \item{`targetRunnerTimeout`}{Timeout in seconds of any `targetRunner` call (only applies to `target-runner` executables not to R functions), ignored if 0. (Default: `0`)} #' \item{`targetRunnerData`}{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. (Default: `""`)} #' \item{`targetRunnerParallel`}{Optional R function to provide custom parallelization of `targetRunner`. (Default: `""`)} #' \item{`targetEvaluator`}{Optional script or R function that provides a numeric value for each configuration. See templates/target-evaluator.tmpl (Default: `""`)} #' \item{`deterministic`}{If the target algorithm is deterministic, configurations will be evaluated only once per instance. (Default: `0`)} #' \item{`parallel`}{Number of calls to `targetRunner` to execute in parallel. Values `0` or `1` mean no parallelization. (Default: `0`)} #' \item{`loadBalancing`}{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. (Default: `1`)} #' \item{`mpi`}{Enable/disable MPI. Use `Rmpi` to execute `targetRunner` in parallel (parameter `parallel` is the number of slaves). (Default: `0`)} #' \item{`batchmode`}{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`. (Default: `0`)} #' } #' \item Initial configurations: #' \describe{ #' \item{`initConfigurations`}{Data frame describing initial configurations (usually read from a file using \code{readConfigurations}). (Default: `""`)} #' \item{`configurationsFile`}{File that contains a table of initial configurations. If empty or `NULL`, all initial configurations are randomly generated. (Default: `""`)} #' } #' \item Training instances: #' \describe{ #' \item{`instances`}{Character vector of the instances to be used in the \code{targetRunner}. (Default: `""`)} #' \item{`trainInstancesDir`}{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. (Default: `""`)} #' \item{`trainInstancesFile`}{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. (Default: `""`)} #' \item{`blockSize`}{Number of training instances, that make up a 'block' in `trainInstancesFile`. Elimination of configurations will only be performed after evaluating a complete block and never in the middle of a block. Each block typically contains one instance from each instance class (type or family) and the block size is the number of classes. The value of `blockSize` will multiply `firstTest`, `eachTest` and `elitistNewInstances`. (Default: `1`)} #' } #' \item Tuning budget: #' \describe{ #' \item{`maxExperiments`}{Maximum number of runs (invocations of `targetRunner`) that will be performed. It determines the maximum budget of experiments for the tuning. (Default: `0`)} #' \item{`minExperiments`}{Minimum number of runs (invocations of `targetRunner`) that will be performed. It determines the minimum budget of experiments for the tuning. The actual budget depends on the number of parameters and `minSurvival`. (Default: `NA`)} #' \item{`maxTime`}{Maximum total execution time for the executions of `targetRunner`. `targetRunner` must return two values: cost and time. This value and the one returned by `targetRunner` must use the same units (seconds, minutes, iterations, evaluations, ...). (Default: `0`)} #' \item{`budgetEstimation`}{Fraction (smaller than 1) of the budget used to estimate the mean computation time of a configuration. Only used when `maxTime` > 0 (Default: `0.05`)} #' \item{`minMeasurableTime`}{Minimum time unit that is still (significantly) measureable. (Default: `0.01`)} #' } #' \item Statistical test: #' \describe{ #' \item{`testType`}{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). (Default: `""`)} #' \item{`firstTest`}{Number of instances evaluated before the first elimination test. It must be a multiple of `eachTest`. (Default: `5`)} #' \item{`eachTest`}{Number of instances evaluated between elimination tests. (Default: `1`)} #' \item{`confidence`}{Confidence level for the elimination test. (Default: `0.95`)} #' } #' \item Adaptive capping: #' \describe{ #' \item{`capping`}{Enable the use of adaptive capping, a technique designed for minimizing the computation time of configurations. Capping is enabled by default if `elitist` is active, `maxTime > 0` and `boundMax > 0`. (Default: `NA`)} #' \item{`cappingAfterFirstTest`}{If set to 1, elimination due to capping only happens after `firstTest` instances are seen. (Default: `0`)} #' \item{`cappingType`}{Measure used to obtain the execution bound from the performance of the elite configurations.\itemize{\item median: Median performance of the elite configurations.\item mean: Mean performance of the elite configurations.\item best: Best performance of the elite configurations.\item worst: Worst performance of the elite configurations.} (Default: `"median"`)} #' \item{`boundType`}{Method to calculate the mean performance of elite configurations.\itemize{\item candidate: Mean execution times across the executed instances and the current one.\item instance: Execution time of the current instance.} (Default: `"candidate"`)} #' \item{`boundMax`}{Maximum execution bound for `targetRunner`. It must be specified when capping is enabled. (Default: `0`)} #' \item{`boundDigits`}{Precision used for calculating the execution time. It must be specified when capping is enabled. (Default: `0`)} #' \item{`boundPar`}{Penalization constant for timed out executions (executions that reach `boundMax` execution time). (Default: `1`)} #' \item{`boundAsTimeout`}{Replace the configuration cost of bounded executions with `boundMax`. (Default: `1`)} #' } #' \item Recovery: #' \describe{ #' \item{`recoveryFile`}{Previously saved log file to recover the execution of `irace`, either absolute path or relative to the current directory. If empty or `NULL`, recovery is not performed. (Default: `""`)} #' } #' \item Testing: #' \describe{ #' \item{`testInstancesDir`}{Directory where testing instances are located, either absolute or relative to current directory. (Default: `""`)} #' \item{`testInstancesFile`}{File containing a list of test instances and optionally additional parameters for them. (Default: `""`)} #' \item{`testInstances`}{Character vector of the instances to be used in the \code{targetRunner} when executing the testing. (Default: `""`)} #' \item{`testNbElites`}{Number of elite configurations returned by irace that will be tested if test instances are provided. (Default: `1`)} #' \item{`testIterationElites`}{Enable/disable testing the elite configurations found at each iteration. (Default: `0`)} #' } #' } # __IRACE_OPTIONS__END__ #' #' @seealso #' \describe{ #' \item{[readScenario()]}{for reading a configuration scenario from a file.} #' \item{[printScenario()]}{prints the given scenario.} #' \item{[defaultScenario()]}{returns the default scenario settings of \pkg{irace}.} #' \item{[checkScenario()]}{to check that the scenario is valid.} #' } #' #' @author Manuel López-Ibáñez and Jérémie Dubois-Lacoste #' @export defaultScenario <- function(scenario = list(), params_def = .irace.params.def) { params_names <- params_def[!startsWith(params_def[,"name"], "."), "name"] if (is.null(names(scenario))) { scenario <- setNames(as.list(params_def[params_names,"default"]), params_names) } else if (!all(names(scenario) %in% params_names)) { irace_error("Unknown scenario settings: ", paste(names(scenario)[!(names(scenario) %in% params_names)], collapse = ", ")) } else { for (k in params_names) { if (is.null.or.na(scenario[[k]])) { scenario[[k]] <- params_def[k, "default"] } } } scenario } readInstances <- function(instancesDir = NULL, instancesFile = NULL) { if (is.null.or.empty(instancesDir) && is.null.or.empty(instancesFile)) irace_error("Both instancesDir and instancesFile are empty: No instances provided") instances <- NULL if (is.null.or.empty(instancesFile)) { file.check (instancesDir, isdir = TRUE, notempty = TRUE, text = "instances directory") # The files are sorted in alphabetical order, on the full path if # 'full.names = TRUE'. instances <- list.files (path = instancesDir, full.names = TRUE, recursive = TRUE) if (length (instances) == 0) irace_error("No instances found in `", instancesDir, "'") } else { file.check (instancesFile, readable = TRUE, text = "instance file") # We do not warn if the last line does not finish with a newline. instances <- readLines (instancesFile, warn = FALSE) instances <- sub("#.*$", "", instances) # Remove comments instances <- sub("^[[:space:]]+", "", instances) # Remove leading whitespace instances <- instances[instances != ""] # Delete empty lines if (is.null.or.empty(instances)) irace_error("No instances found in '", instancesFile, "' (whitespace and comments starting with '#' are ignored)") if (!is.null.or.empty(instancesDir)) instances <- paste0 (instancesDir, "/", instances) } instances } update_scenario <- function(scenario, ...) { scenario_args <- list(...) if (length(scenario_args) == 0L) return(scenario) unknown_scenario_args <- setdiff(names(scenario_args), names(scenario)) if (length(unknown_scenario_args)) irace_error("Unknown scenario settings given: ", paste0(unknown_scenario_args, collapse=", ")) utils::modifyList(scenario, scenario_args) } irace/R/race.R0000644000176200001440000015741714745735066012633 0ustar liggesusers# ---------------------------------------- -*- mode: r; mode: font-lock -*- # # race.R Racing methods for the selection of the best # # ------------------------------------------------------------------------- # # ========================================================================= # # Racing methods for the selection of the best # # ------------------------------------------------------------------------- # # Copyright (C) 2003 Mauro Birattari # # ========================================================================= # # This program is free software; you can redistribute it and/or modify it # # under the terms of the GNU General Public License as published by the # # Free Software Foundation; either version 2 of the License, or (at your # # option) any later version. # # # # This program is distributed in the hope that it will be useful, but # # WITHOUT ANY WARRANTY; without even the implied warranty of # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # # General Public License for more details. # # # # You should have received a copy of the GNU General Public License along # # with this program; if not, write to the Free Software Foundation, Inc., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # ========================================================================= # # ========================================================================= # # Mauro BIRATTARI # # IRIDIA - ULB, CP 194/6 # # Av. F. D. Roosevelt 50 mbiro@ulb.ac.be # # 1050 Brussels, Belgium http://iridia.ulb.ac.be/~mbiro # # ========================================================================= # # $Id: race.R,v 1.54 2005/03/30 12:40:42 mbiro Exp $ # createExperimentList <- function(configurations, parameters, instances, instances_ID, seeds, bounds) { n_configurations <- nrow(configurations) configurations_id <- configurations[[".ID."]] pnames <- parameters$names configurations <- as.list(configurations[, pnames, drop=FALSE]) # FIXME: How to do this faster? # - maybe purrr::transpose() ? # - mlr3misc::transpose_list() uses .mapply(list, configurations, list(), which is slower. configurations <- lapply(seq_len(n_configurations), function(i) lapply(configurations, `[`, i)) dots <- list(id_configuration = configurations_id, configuration = configurations) if (!is.null(bounds)) { # There must be a bound for each configuration. # FIXME: There must be a bound for each configuration AND each instance. irace_assert(length(bounds) == n_configurations) dots$bound <- bounds } configurations <- .mapply(list, dots = dots, MoreArgs = NULL) instances <- instances[instances_ID] n_instances <- length(instances_ID) instances <- .mapply(list, dots = list(id_instance = instances_ID, seed = seeds, instance = instances), MoreArgs = NULL) .mapply(c, dots = list( rep(instances, each = n_configurations), rep(configurations, times = n_instances)), MoreArgs = NULL) } race_wrapper_helper <- function(race_state, configurations, instance_idx, bounds, is_exe, scenario) { # Experiment list to execute experiments <- createExperimentList(configurations, parameters = scenario$parameters, instances = scenario$instances, instances_ID = race_state$instances_log[["instanceID"]][instance_idx], seeds = race_state$instances_log[["seed"]][instance_idx], bounds = bounds) irace_assert(length(is_exe) == length(experiments)) instance_idx <- rep(instance_idx, each = nrow(configurations)) if (race_state$recovery_mode) { # With targetEvaluator or if everything is executed, we recover everything. if (!is.null(scenario$targetEvaluator) || all(is_exe)) { configuration_id <- unlist_element(experiments, "id_configuration") target_output <- race_state$recover_output(instance_idx, configuration_id) } else { irace_assert(any(is_exe)) configuration_id <- unlist_element(experiments[is_exe], "id_configuration") target_output <- race_state$recover_output(instance_idx[is_exe], configuration_id) } } else { # !recovery_mode # We cannot let targetRunner or targetEvaluator modify our random seed, so we save it. withr::local_preserve_seed() target_output <- vector("list", length(experiments)) # Execute experiments for which is_exe is TRUE: if (any(is_exe)) target_output[is_exe] <- execute_experiments(race_state, experiments[is_exe], scenario) # If targetEvaluator is NULL, then target_output must contain the right # output already. Otherwise, targetEvaluator considers all experiments. if (!is.null(scenario$targetEvaluator)) { target_output <- execute_evaluator(race_state$target_evaluator, experiments, scenario, target_output) } else if (any(!is_exe)) { experiments <- experiments[is_exe] instance_idx <- instance_idx[is_exe] } target_output <- rbindlist(target_output, fill=TRUE, use.names=TRUE) set(target_output, j = setdiff(colnames(target_output), c("cost", "time")), value = NULL) if ("time" %notin% colnames(target_output)) set(target_output, j = "time", value = NA) set(target_output, j = "configuration", value = unlist_element(experiments, "id_configuration")) set(target_output, j = "instance", value = instance_idx) if (!is.null(bounds)) set(target_output, j = "bound", value = unlist_element(experiments, "bound")) } target_output } ## Executes a list of configurations in a particular instance ## configurations: description having the id of the configuration ## instance.idx: index of the instance,seed pair in race_state$instances_log ## bounds: execution bounds (NULL if not needed). ## is_exe: Boolean vector that determines which experiments need to executed. race_wrapper <- function(race_state, configurations, instance_idx, bounds, is_exe, scenario) { target_output <- race_wrapper_helper(race_state, configurations, instance_idx, bounds, is_exe, scenario) race_state$update_race_experiment_log(target_output, scenario) target_output } experiments_output_to_matrix <- function(output, scenario) { if (scenario$capping) output[["cost"]] <- applyPAR(output[["cost"]], boundMax = scenario$boundMax, boundPar = scenario$boundPar) as.matrix(dcast(output[, c("instance", "configuration", "cost")], instance ~ configuration, value.var = "cost"), rownames = "instance") } aux2.friedman <- function(y, I, alive, conf.level = 0.95) { dropped.any <- FALSE n <- nrow(y) k <- length(I) r <- rowRanks(y, cols = I, ties.method = "average", useNames = FALSE) R <- colSums2(r, useNames = FALSE) o <- order(R) best <- I[o[1L]] TIES <- tapply(c(r), row(r), table) STATISTIC <- ((12 * sum((R - n * (k + 1L) / 2)^2)) / (n * k * (k + 1L) - (sum(unlist(lapply(TIES, function (u) {u^3 - u}))) / (k - 1L)))) PARAMETER <- k - 1L PVAL <- pchisq(STATISTIC, PARAMETER, lower.tail = FALSE) #names(STATISTIC) <- "Friedman chi-squared" #names(PARAMETER) <- "df" alpha <- 1 - conf.level if (!is.nan(PVAL) && PVAL < alpha) { # This formula for multiple comparisons comes from Conover, "Practical # Nonparametric Statistics", 1999, pages 369-371. A <- sum(as.vector(r)^2) t <- qt(1 - alpha / 2, df = (n - 1L) * (k - 1L)) * (2 * (n * A - sum(R^2)) / ((n - 1L) * (k - 1L)))^(1 / 2) J <- best for (j in 2L:k) { if (abs(R[o[j]] - R[o[1L]]) > t) { break } else { J <- c(J, I[o[j]]) } } alive[-J] <- FALSE dropped.any <- TRUE } irace_assert(I[which.min(R)] == best) list(ranks = R, alive = alive, dropped.any = dropped.any, p.value = PVAL) } aux_friedman <- function(results, alive, which_alive, conf.level) { no.alive <- length(which_alive) if (no.alive > 2L) { # If more then 2 configurations are left, use Friedman return(aux2.friedman(results, which_alive, alive, conf.level = conf.level)) } ranks <- NULL dropped.any <- TRUE PVAL <- 0 # If only 2 configurations are left, switch to Wilcoxon V1 <- results[, which_alive[1L]] V2 <- results[, which_alive[2L]] diffs <- V1 - V2 # Avoid the test if the answer is obvious if (all(diffs <= 0)) { ranks <- c(1L,2L) } else if (all(diffs >= 0)) { ranks <- c(2L,1L) } else { ZEROES <- any(diffs == 0) if (ZEROES) diffs <- diffs[diffs != 0] r <- rank(abs(diffs)) TIES <- length(r) != length(unique(r)) diffs <- outer(diffs, diffs, "+") diffs <- sort(diffs[!lower.tri(diffs)])/2 PVAL <- wilcox.test(V1, V2, paired = TRUE, exact = if (ZEROES || TIES) FALSE else NULL)$p.value irace_assert(!is.nan(PVAL) & !is.na(PVAL)) if (PVAL >= 1 - conf.level) dropped.any <- FALSE # We use the pseudo median (see wilcox.test.default) ranks <- if (median(diffs) <= 0) c(1L,2L) else c(2L,1L) } if (dropped.any) alive[which_alive[ranks[2L]]] <- FALSE list(ranks = ranks, alive = alive, dropped.any = dropped.any, p.value = PVAL) } aux.one_ttest <- function(results, alive, which_alive, conf.level, adjust = c("none","bonferroni","holm")) { adjust <- match.arg(adjust) irace_assert(sum(alive) == length(which_alive)) results <- results[, which_alive] means <- colMeans2(results) best <- which.min(means) mean_best <- means[best] pvals <- sapply(means, function(x) as.numeric(isTRUE( all.equal.numeric(mean_best[[1L]], x[[1L]], check.attributes = FALSE)))) results_best <- results[, best] var_best <- var(results_best) which_test <- which(pvals < 1.0) for (j in which_test) { PVAL <- pvals[j] if (PVAL == 1.0) next results_j <- results[, j] # t.test may fail if the data in each group is almost constant. Hence, we # surround the call in a try() and we initialize p with 1 if the means are # equal or zero if they are different if (min(var(results_best), var(results_j)) < 10 * .Machine$double.eps) next # The t.test may fail if the data are not normal despite one configuration # clearly dominating the other. if (all(results_best <= results_j)) next try(PVAL <- t.test(results_best, results_j, alternative = "less", paired = TRUE)$p.value) irace_assert(!is.nan(PVAL) & !is.na(PVAL)) pvals[j] <- PVAL } pvals <- p.adjust(pvals, method = adjust) dropj <- which_alive[pvals < 1.0 - conf.level] dropped_any <- length(dropj) > 0 irace_assert(all(alive[dropj])) alive[dropj] <- FALSE list(ranks = means, alive = alive, dropped.any = dropped_any, p.value = min(pvals)) } aux.ttest <- function(results, alive, which_alive, conf.level, adjust = c("none","bonferroni","holm")) { adjust <- match.arg(adjust) irace_assert(sum(alive) == length(which_alive)) means <- colMeans2(results, cols = which_alive, useNames = FALSE) # FIXME: break ties using median or ranks? best <- which.min(means) mean_best <- means[best] pvals <- sapply(means, function(x) as.numeric(isTRUE( all.equal.numeric(mean_best[[1L]], x[[1L]], check.attributes = FALSE)))) results <- results[, which_alive] results_best <- results[, best] var_best <- var(results_best) which_test <- which(pvals < 1.0) for (j in which_test) { PVAL <- pvals[j] if (PVAL == 1.0) next results_j <- results[, j] # t.test may fail if the data in each group is almost constant. Hence, we # surround the call in a try() and we initialize p with 1 if the means are # equal or 0 if they are different. if (min(var(results_best), var(results_j)) < 10 * .Machine$double.eps) next # The t.test may fail if the data are not normal despite one configuration # clearly dominating the other. if (all(results_best <= results_j)) next try(PVAL <- t.test(results_best, results_j, paired = TRUE)$p.value) irace_assert(!is.nan(PVAL) & !is.na(PVAL)) pvals[j] <- PVAL } pvals <- p.adjust(pvals, method = adjust) dropj <- which_alive[pvals < 1.0 - conf.level] dropped_any <- length(dropj) > 0L irace_assert(all(alive[dropj])) alive[dropj] <- FALSE list(ranks = means, alive = alive, dropped.any = dropped_any, p.value = min(pvals)) } table_hline <- function(widths) paste0(collapse="+", c("",strrep("-", widths), "\n")) table_sprintf <- function(text, widths) paste0(collapse="|", c("", sprintf("%*s",widths, text), "\n")) .nocap_table_fields_width <- as.integer(c(1, 11, 11, 11, 16, 11, 8, 5, 4, 6)) .nocap_colum_names <- c(" ", "Instance", "Alive", "Best", "Mean best", "Exp so far", "W time", "rho", "KenW", "Qvar") .capping_table_fields_width <- as.integer(c(1, 11, 8, 11, 11, 16, 11, 8, 5, 4, 6)) .capping_colum_names <- c(" ", "Instance", "Bound", "Alive", "Best", "Mean best", "Exp so far", "W time", "rho", "KenW", "Qvar") .capping_hline <- table_hline(.capping_table_fields_width) .nocap_hline <- table_hline(.nocap_table_fields_width) .race_common_header <- "# Markers: x No test is performed. c Configurations are discarded only due to capping. - The test is performed and some configurations are discarded. = The test is performed but no configuration is discarded. ! The test is performed and configurations could be discarded but elite configurations are preserved. . All alive configurations are elite and nothing is discarded.\n\n" .nocap_header <- paste0(.race_common_header, .nocap_hline, table_sprintf(.nocap_colum_names, .nocap_table_fields_width), .nocap_hline) .capping_header <- paste0(.race_common_header, .capping_hline, table_sprintf(.capping_colum_names, .capping_table_fields_width), .capping_hline) race_print_header_nocap <- function() cat(sep = "", .nocap_header) race_print_header_cap <- function() cat(sep = "", .capping_header) elapsed_wctime_str <- function(now, start) { if (now <= start) return("00:00:00") elapsed <- difftime(now, start, units = "secs") # FIXME: Maybe better and faster if we only print seconds? format(.POSIXct(elapsed, tz="GMT"), "%H:%M:%S") } race_print_task <- function(res.symb, Results, instance, current_task, which_alive, id_best, best, experiments_used, start_time, bound, capping) { cat(sprintf("|%s|%11d|", res.symb, instance)) if (capping) { if (is.null(bound)) cat(" NA|") else cat(sprintf("%8.2f|", bound)) } # FIXME: This is the mean of the best, but perhaps it should # be the sum of ranks in the case of test == friedman? mean_best <- mean(Results[, best]) time_str <- elapsed_wctime_str(Sys.time(), start_time) cat(sprintf(paste0("%11d|%11d|", .irace.format.perf, "|%11d|%s"), length(which_alive), id_best, mean_best, experiments_used, time_str)) if (current_task > 1L && length(which_alive) > 1L) { res <- Results[, which_alive, drop = FALSE] conc <- concordance(res) qvar <- dataVariance(res) # FIXME: We would like to use %+#4.2f but this causes problems with # https://github.com/oracle/fastr/issues/191 cat(sprintf("|%+4.2f|%.2f|%.4f|\n", conc$spearman.rho, conc$kendall.w, qvar)) } else { cat("| NA| NA| NA|\n") } } race_print_footer <- function(bestconf, mean_best, break_msg, debug_level, capping = FALSE, old_best_id) { cat(sep = "", if (capping) .capping_hline else .nocap_hline, if (debug_level >= 1L) paste0("# Stopped because ", break_msg, "\n"), if (!is.null(old_best_id)) paste0("Best configuration for the instances in this race: ", old_best_id, "\n"), sprintf("Best-so-far configuration: %11d", bestconf[[".ID."]][1L]), " mean value: ", sprintf(.irace.format.perf, mean_best), "\n", "Description of the best-so-far configuration:\n") configurations_print(bestconf, metadata = TRUE) cat("\n") } ## This function calculates an execution bound ## data: matrix columns as configurations and rows instances ## type: ## median: bound based on the configuration's mean median ## mean: bound based on the configuration's mean mean ## worst: bound based on the worst configuration's mean ## best: bound based on the best configurations's mean executionBound <- function(data, type = "median") { irace_assert (ncol(data) >= 1L) if (ncol(data) == 1L) { return (mean(data[,1L], na.rm = TRUE)) } # This should never happen because the data used to obtain the execution # bound should be complete, that is, the bounding configurations should have # been executed on all previous instances. irace_assert (!anyNA(data)) colmeans <- colMeans2(data) bound <- switch (type, median = median(colmeans), mean = mean(colmeans), worst = max(colmeans), # default: min(colmeans)) bound } ## This function calculates an execution bound per instance ## data: array of the execution times on the current instance ## type: ## median: bound based on the median time ## mean: bound based on the mean time ## worst: bound based on the worst time ## best: bound based on the best time instanceBound <- function(data, type="median") { irace_assert (!anyNA(data)) switch (type, median = median(data), mean = mean(data), worst = max(data), # default: min(data)) } ## This function returns survivors obtained after applying the dominance elimination ## criterion. ## results: matrix of experiments results (all configurations) ## elites: index of elite configurations ## alive: bolean array of alive configurations ## eps: constant added to the bound to account for the measuring precision dom_elim <- function(results, elites, alive, scenario, minSurvival, eps = 1e-5) { which_alive <- which(alive) irace_assert(length(which_alive) >= minSurvival) cmeans <- colMeans2(results, cols = which_alive, useNames = FALSE) irace_assert(!all(is.na(cmeans))) # Only NA values when calculating mean for dominance. # When there are no protected elites left, select the best configurations to # calculate the bounds. This is quite aggressive and another alternative # would be to disable dom_elim when elites == 0. if (length(elites) == 0L) { # In the case we have only two alive configurations only one can be elite. if (length(which_alive) <= 2L) elites <- which_alive[which.min(cmeans)] else elites <- which_alive[order(cmeans, na.last = TRUE, decreasing = FALSE)[seq_len(minSurvival)]] } bound <- executionBound(results[, elites, drop = FALSE], type = scenario$cappingType) alive[which_alive] <- ((bound + eps) >= cmeans) alive } ## This function applies PARX (X=boundPar) to all experiments ## that exceed the maximum execution time (boundMax) applyPAR <- function(results, boundMax, boundPar) { # We do not want to change Inf or -Inf because those represent rejection. if (boundPar != 1) results[is.finite(results) & results >= boundMax] <- boundMax * boundPar results } ## This function calculates the execution time allowed for the executions ## of configurations based on previous execution times. ## It returns a list with two elements: ## * elite_bound : value (mean execution time or per instance execution time) ## used to calculate the maximum execution time (final_bounds) for each configuration. ## * final_bounds[i] : maximum execution time for candidate i on the current instance. final_execution_bound <- function(experimentsTime, elites, current_task, which_alive, which_exe, scenario) { boundMax <- scenario$boundMax final_bounds <- rep(boundMax, length(which_alive)) # Elite candidates can have NA values due to the rejection. if (length(elites)) elites <- elites[!is.na(experimentsTime[current_task,elites])] # Only apply bounds when there is previous data if (length(elites) == 0L || length(which_exe) == 0L) { # FIXME: should we use an adjusted boundMax ? return(list(final_bounds = final_bounds, elite_bound = boundMax)) } minMeasurableTime <- scenario$minMeasurableTime # The elite membership is updated before calling this function to know # the configurations used for calculating the bounds we need to do this. # Note that some of these configurations could be not elite anymore for the # elimination phase, given that all their evaluations have been used. if (scenario$boundType == "instance") { elite_bound <- instanceBound(experimentsTime[current_task, elites], type = scenario$cappingType) final_bounds_exe <- min(elite_bound + minMeasurableTime, boundMax) final_bounds_exe <- ceiling_digits(final_bounds_exe, scenario$boundDigits) } else { elite_bound <- executionBound(experimentsTime[seq_len(current_task), elites, drop = FALSE], type = scenario$cappingType) elite_bound <- min(elite_bound, boundMax) total_time <- (current_task * elite_bound) + minMeasurableTime final_bounds_exe <- total_time - colSums2(experimentsTime, rows = seq_len(current_task), cols = which_exe, na.rm = TRUE, useNames = FALSE) # There are cases in which a small negative budget is used. For example: # assuming candidates 1 and 2 are elite: # maxBound <- 80 # executionTime <- matrix(c(0.010,0.0170,0.010, 24,28,27, 0.010,0.017,NA), # byrow=TRUE, ncol=3, nrow=3, dimnames=list(c(1,2,3),c(1,2,3))) # current_task <- 3; boundDigits = 0 # elite_bound <- irace:::executionBound(executionTime[1:current_task,1:2], "median") # total_time <- elite_bound * current_task + 0.01 # final_bounds_exe <- total_time - colSums2(executionTime[1:current_task,3,drop=FALSE], na.rm=TRUE) # # We set the execution time to the elite_bound, this should be enough # to eliminate a bad candidate for the next task. final_bounds_exe[final_bounds_exe <= 0] <- elite_bound # We round up the bounds up to the specified number of digits. This may # be necessary if the target-algorithm does not support higher precision. final_bounds_exe <- ceiling_digits(final_bounds_exe, scenario$boundDigits) final_bounds_exe[final_bounds_exe > boundMax] <- boundMax irace_assert(all(final_bounds_exe > 0)) } final_bounds[which(which_alive %in% which_exe)] <- final_bounds_exe list(final_bounds = final_bounds, elite_bound = elite_bound) } get_ranks <- function(x, test) if (test == "friedman") colSums2(rowRanks(x, ties.method="average")) else rank(colMeans2(x)) # Recompute the best as follows. Given two configurations, the one evaluated # on more instances is ranked better. Otherwise, break ties according to the # criteria of the stat test. overall_ranks <- function(x, test) { if (ncol(x) == 1L) return(1L) ninstances <- colSums2(!is.na(x)) uniq_ninstances <- sort(unique(ninstances), decreasing = TRUE) last_r <- 0L ranks <- rep_len(Inf, ncol(x)) # Iterate from the largest to the lowest number of instances. for (k in uniq_ninstances) { confs <- which(ninstances == k) irace_assert(all(is.infinite(ranks[confs]))) r <- 1L if (length(confs) > 1L) { # Select only non-NA rows y <- x[, confs, drop = FALSE] y <- y[complete.cases(y), , drop = FALSE] r <- get_ranks(y, test = test) } r <- r + last_r last_r <- max(r) ranks[confs] <- r } ranks } # Remove one elite count from every configuration not executed. update_is_elite <- function(is_elite, which_exe) { which_notexecuted <- setdiff(which(is_elite > 0L), which_exe) is_elite[which_notexecuted] <- is_elite[which_notexecuted] - 1L irace_assert (all(is_elite >= 0L)) is_elite } update_elite_safe <- function(Results, is_elite) { elites <- is_elite > 0L if (!any(elites)) return(0L) # All elites rejected. max(which(rowAnyNotNAs(Results, cols = elites))) } generateTimeMatrix <- function(elite_ids, experiment_log) { # Silence CRAN warnings configuration <- bound <- NULL # Remove everything that we don't need. experiment_log <- experiment_log[(configuration %in% elite_ids) & !is.na(time), c("configuration", "instance", "time", "bound")] experiment_log[, time := pmin.int(time, bound)] experiment_log[, bound := NULL] time_matrix <- dcast(experiment_log, instance ~ configuration, value.var = "time") setcolorder(time_matrix, neworder = as.character(elite_ids)) as.matrix(time_matrix, rownames = "instance") } race <- function(race_state, maxExp, minSurvival = 1L, configurations, scenario) elitist_race(race_state = race_state, maxExp = maxExp, minSurvival = minSurvival, elite_data = NULL, configurations = configurations, scenario = scenario, elitist_new_instances = 0L) elitist_race <- function(race_state, maxExp, minSurvival = 1L, elite_data = NULL, configurations, scenario, elitist_new_instances, firstTest = scenario$firstTest) { blockSize <- scenario$blockSize conf.level <- scenario$confidence firstTest <- blockSize * firstTest eachTest <- blockSize * scenario$eachTest elitist <- scenario$elitist capping <- scenario$capping n_configurations <- nrow(configurations) alive <- rep_len(TRUE, n_configurations) is_rejected <- logical(n_configurations) ## FIXME: Remove argument checking. This must have been done by the caller. irace_assert(maxExp > 0L) if (n_configurations <= minSurvival) { irace_error("Not enough configurations (", n_configurations, ") for a race (minSurvival=", minSurvival, ")") } # Check argument: conf.level if (!missing(conf.level) && (!is.numeric(conf.level) || length(conf.level)!=1 || !is.finite(conf.level) || conf.level < 0 || conf.level > 1)) stop("conf.level must be a single number between 0 and 1") if (scenario$quiet) { print_header <- print_task <- print_footer <- do_nothing } else { print_header <- if (capping) race_print_header_cap else race_print_header_nocap print_task <- race_print_task print_footer <- race_print_footer } stat_test <- scenario$testType do_test <- switch(stat_test, friedman = function(results, alive, which_alive, conf.level = scenario$confidence) aux_friedman(results, alive, which_alive, conf.level = conf.level), t.none = function(results, alive, which_alive, conf.level = scenario$confidence) aux.ttest(results, alive, which_alive, conf.level = conf.level, adjust = "none"), t.holm = function(results, alive, which_alive, conf.level = scenario$confidence) aux.ttest(results, alive, which_alive, conf.level = conf.level, adjust = "holm"), t.bonferroni = function(results, alive, which_alive, conf.level = scenario$confidence) aux.ttest(results, alive, which_alive, conf.level = conf.level, adjust = "bonferroni")) do_test <- bytecompile(do_test) # Create the instance list according to the algorithm selected. if (elitist) { # FIXME: we should sample taking into account the block-size, so we sample blocks, not instances. irace_assert((race_state$next_instance - 1L) %% blockSize == 0, eval_after={cat("next_instance:", race_state$next_instance, ", block_size:", blockSize, "\n")}) race_instances <- elitist_init_instances(race_state, deterministic = scenario$deterministic, sampleInstances = scenario$sampleInstances, elitist_new_instances = elitist_new_instances) # It may be reduced further by elitist_init_instances() elitist_new_instances <- min(elitist_new_instances, race_state$elitist_new_instances) all_elite_instances_evaluated <- function() { if (race_state$next_instance == 1L) return(TRUE) evaluated <- !is.na(Results[, alive, drop=FALSE]) # All instances that have been previously seen have been evaluated by at # least one configuration all(rowAnys(evaluated)) && # and the number of instances evaluated per configuration is a multiple # of eachTest (which is scenario$blockSize * scenario$eachTest). all(colSums2(evaluated) %% eachTest == 0L) } } else { race_instances <- no_elitist_init_instances(race_state, deterministic = scenario$deterministic) all_elite_instances_evaluated <- function() TRUE } irace_assert(!anyDuplicated(race_instances)) irace_assert(identical(sort(race_instances), seq_along(race_instances))) nb_tasks <- length(race_instances) # Initialize some variables... experiments_used <- 0L ## FIXME: Probably, instead of this, we should keep elite_safe in the race_state. if (is.null(elite_data)) { elite_safe <- 0L elite_instances_ID <- NULL } else { irace_assert(race_state$next_instance - 1L == nrow(elite_data)) # There must be a non-NA entry for each instance. irace_assert(all(rowAnyNotNAs(elite_data)), eval_after = { print(elite_data)}) # There must be a non-NA entry for each configuration. irace_assert(all(colAnyNotNAs(elite_data)), eval_after = { cat("elite_data:\n") print(elite_data) cat("experiment_log:\n") print(race_state$experiment_log) }) # elite_safe: maximum instance number for which any configuration may be # considered elite. After evaluating this instance, no configuration is # elite. elite_safe <- elitist_new_instances + nrow(elite_data) elite_instances_ID <- as.character(race_instances[seq_len(elite_safe)]) } configurations_ID <- as.character(configurations[[".ID."]]) Results <- matrix(NA_real_, nrow = elite_safe, ncol = n_configurations, dimnames = list(elite_instances_ID, configurations_ID)) if (capping) experimentsTime <- matrix(NA_real_, nrow = elite_safe, ncol = n_configurations, dimnames = list(elite_instances_ID, configurations_ID)) if (is.null(elite_data)) { # is_elite[i] : number of instances to be seen in this race on which i has # been previously evaluated. is_elite <- integer(n_configurations) } else { Results[rownames(elite_data), colnames(elite_data)] <- elite_data irace_assert(all(colnames(elite_data) %chin% configurations_ID)) if (capping) { # Temporarily use only 0 or 1, we will calculate the real value below. is_elite <- as.integer(configurations_ID %chin% colnames(elite_data)) tmp <- generateTimeMatrix(elite_ids = colnames(elite_data), experiment_log = race_state$experiment_log) experimentsTime[rownames(tmp), colnames(tmp)] <- tmp # Preliminary execution of elite configurations to calculate # the execution bound of initial configurations (capping only). if (elitist_new_instances > 0L) { irace_assert(elitist_new_instances %% blockSize == 0L) # FIXME: This should go into its own function. n_elite <- ncol(elite_data) which_elites <- which(is_elite > 0L, useNames=FALSE) irace_assert(identical(which_elites, seq_len(n_elite))) irace_note("Preliminary execution of ", n_elite, " elite configuration(s) over ", elitist_new_instances, " instance(s).\n") # FIXME: Launch all seq_len(elitist_new_instances) experiments in parallel. for (k in seq_len(elitist_new_instances)) { output <- race_wrapper (race_state, configurations = configurations[which_elites, , drop = FALSE], instance_idx = race_instances[k], bounds = rep(scenario$boundMax, n_elite), # which_exe values are within 1:nbConfigurations, whereas experiments # indices are within 1:length(which_alive). The following line converts # from one to the other. is_exe = rep_len(TRUE, n_elite), scenario = scenario) # Extract results: irace_assert(length(output[["cost"]]) == n_elite) Results[k, which_elites] <- applyPAR(output[["cost"]], boundMax = scenario$boundMax, boundPar = scenario$boundPar) irace_assert(!anyNA(output[["time"]])) experimentsTime[k, which_elites] <- output[["time"]] # capping is enabled irace_assert(all.equal(configurations[[".ID."]][which_elites], output[["configuration"]])) irace_assert(all.equal(output[["bound"]], rep(scenario$boundMax, n_elite))) irace_assert(all.equal(unique(output[["instance"]]), race_instances[k])) experiments_used <- experiments_used + n_elite # We remove elite configurations that are rejected given that # is not possible to calculate the bounds. rejected <- is.infinite(output[["cost"]]) irace_assert(all.equal(as.vector(is.infinite(Results[k, which_elites])), rejected), eval_after={ cat("rejected:\n") print(output[["cost"]]) print(rejected) cat("Results:\n") print(Results[k, which_elites]) print(is.infinite(Results[k, which_elites])) }) is_rejected[which_elites] <- rejected is_elite[rejected] <- 0L which_elites <- which_elites[!rejected] n_elite <- length(which_elites) # If all elite are eliminated we stop execution of initial instances. if (n_elite == 0L) { irace_note ("All elite configurations are rejected. Execution of non-elites will be not bounded.\n") break } } if (any(is_rejected)) { irace_note ("Immediately rejected configurations: ", paste0(configurations[[".ID."]][is_rejected], collapse = ", ") , "\n") alive[is_rejected] <- FALSE # Calculate the maximum instance that has any non-NA value. # FIXME: Use update_elite_safe() if (n_elite > 0L) elite_safe <- max(which(rowAnys(!is.na(Results[, which_elites, drop=FALSE])))) else elite_safe <- 0L irace_assert(identical(update_elite_safe(Results, is_elite), elite_safe)) # FIXME: If sum(alive) <= minSurvival, we stop later but we will have # elites that have not been evaluated in any instance, which is # bad. Ideally we would sample new configurations here but that is # too hard to do in iterated-racing. } } } # end if(capping) # Compute the elite membership. is_elite <- colSums2(!is.na(Results)) # Remove rejected configurations. is_elite[is_rejected] <- 0L } no_elimination <- 0L # number of tasks without elimination. print_header() # Start main loop. break_msg <- NULL best <- NA_integer_ which_alive <- which(alive) nb_alive <- length(which_alive) for (current_task in seq_len(nb_tasks)) { which_exe <- which_alive if (elitist && any(is_elite > 0L)) { # Filter configurations that do not need to be executed (elites). # This is valid only for previous iteration instances. irace_assert(current_task <= elite_safe) # Execute everything that is alive and not yet executed. which_exe <- which(alive & is.na(Results[current_task, ])) if (length(which_exe) == 0L) { is_elite <- update_is_elite(is_elite, which_exe) # LESLIE: This is the case in which there are only elite configurations alive # and we are still in the previous instances execution, but we can still # continue with the race. (This is only possible because the early termination # criterion is disabled) ## MANUEL: So what is the reason to not immediately terminate here? Is ## there a reason to continue? if (current_task == 1L) { # We may reach this point in the first iteration so we need to calculate best. if (nb_alive == 1L) { best <- which_alive } else { tmpResults <- Results[1L, which_alive, drop = FALSE] irace_assert(!anyNA(tmpResults)) # which.min returns only the first minimum. best <- which_alive[which.min(get_ranks(tmpResults, test = stat_test))] } } if (is.na(best)) { utils::dump.frames(dumpto = "best_crash", to.file = TRUE, include.GlobalEnv = TRUE) irace_assert(!is.na(best)) } id_best <- configurations[[".ID."]][best] print_task(".", Results[seq_len(current_task), , drop = FALSE], race_instances[current_task], current_task, which_alive = which_alive, id_best = id_best, best = best, experiments_used, start_time = Sys.time(), # FIXME: Why do we pass NA as bound? Why not pass the actual bound if any? bound = NA_real_, capping = capping) next } } if (all_elite_instances_evaluated()) { # We stop when we have less configurations than required. if (nb_alive <= minSurvival) { # Stop race if we have less or equal than the minimum number of # configurations. break_msg <- paste0("number of alive configurations (", nb_alive, ") <= minimum number of configurations (", minSurvival, ")") break } ## We continue running if (1) we have not reached the firstTest or (2) ## there are instances previously seen that have not been evaluated on any ## alive configuration. If we just did a test, check that we have enough ## budget to reach the next test. # FIXME: In post-selection racing, we want to consume all budget, so we # should discard configurations until we have 2. if (current_task > firstTest && ( (current_task - 1L) %% eachTest) == 0L && experiments_used + length(which_exe) * eachTest > maxExp) { break_msg <- paste0("experiments for next test (", experiments_used + length(which_exe) * eachTest, ") > max experiments (", maxExp, ")") break } if (elitist && scenario$elitistLimit != 0L && no_elimination >= scenario$elitistLimit) { break_msg <- paste0("tests without elimination (", no_elimination, ") >= elitistLimit (", scenario$elitistLimit, ")") break } } if (nrow(Results) < current_task) { Results <- rbind(Results, rep_len(NA_real_, ncol(Results))) rownames(Results) <- race_instances[seq_nrow(Results)] if (capping) { experimentsTime <- rbind(experimentsTime, rep_len(NA_real_, ncol(experimentsTime))) rownames(experimentsTime) <- race_instances[seq_nrow(experimentsTime)] } } start_time <- Sys.time() # Calculate bounds for executing if needed. which_elite_exe <- intersect(which_exe, which(is_elite > 0L)) irace_assert(setequal(which_elite_exe, which(is_elite & is.na(Results[current_task,])))) if (capping) { # Pre-execute elite configurations that are not yet executed in the current instance. if (length(which_elite_exe)) { # FIXME: This should go into its own function output <- race_wrapper(race_state, configurations = configurations[which_elite_exe, , drop = FALSE], instance_idx = race_instances[current_task], # FIXME: What if we already have a bound for this instance? bounds = rep(scenario$boundMax, length(which_elite_exe)), is_exe = rep_len(TRUE, length(which_elite_exe)), scenario = scenario) # Extract results irace_assert(length(output[["cost"]]) == length(which_elite_exe)) Results[current_task, which_elite_exe] <- applyPAR(output[["cost"]], boundMax = scenario$boundMax, boundPar = scenario$boundPar) irace_assert(!anyNA(output[["time"]])) irace_assert(all.equal(configurations[which_elite_exe, ".ID."], output[["configuration"]])) experimentsTime[current_task, which_elite_exe] <- output[["time"]] irace_assert(all.equal(unique(output[["instance"]]), race_instances[current_task])) experiments_used <- experiments_used + length(which_elite_exe) # We remove elite configurations that are rejected given that # is not possible to calculate the bounds rejected <- is.infinite(output[["cost"]]) if (any(rejected)) { irace_note("Immediately rejected configurations: ", paste0(configurations[[".ID."]][which_elite_exe[rejected]], collapse = ", ") , "\n") is_rejected[which_elite_exe] <- rejected is_elite[is_rejected] <- 0L alive[which_elite_exe] <- !rejected if (!any(alive)) { # FIXME: Only report this error if (all(is_rejected)); otherwise # restore non-rejected non-alive ones. Restoring a non-alive # configuration is difficult. We need to evaluate it in all the # instances that it has missed. irace_error("All configurations have been immediately rejected (all of them returned Inf) !") } which_alive <- which(alive) nb_alive <- length(which_alive) elite_safe <- update_elite_safe(Results, is_elite) } which_exe <- setdiff(which_exe, which_elite_exe) # FIXME: There is similar code above. Can we merge these code paths? if (length(which_exe) == 0L) { is_elite <- update_is_elite(is_elite, which_elite_exe) if (current_task == 1L) { # We may reach this point in the first iteration so we need to calculate best. if (nb_alive == 1L) { best <- which_alive } else { tmpResults <- Results[1L, which_alive, drop = FALSE] irace_assert(!anyNA(tmpResults)) # which.min returns only the first minimum. best <- which_alive[which.min(get_ranks(tmpResults, test = stat_test))] } } if (is.na(best)) { utils::dump.frames(dumpto = "best_crash", to.file = TRUE, include.GlobalEnv = TRUE) irace_assert(!is.na(best)) } id_best <- configurations[[".ID."]][best] print_task(".", Results[seq_len(current_task), , drop = FALSE], race_instances[current_task], current_task, which_alive = which_alive, id_best = id_best, best = best, experiments_used, start_time = start_time, # FIXME: Why do we pass NA as bound? Why not pass the actual bound if any? bound = if (is.null(scenario$boundMax)) NA_real_ else scenario$boundMax, capping) next } } all_bounds <- final_execution_bound(experimentsTime, elites = which(is_elite > 0L), current_task, which_alive, which_exe, scenario) final_bounds <- all_bounds$final_bounds elite_bound <- all_bounds$elite_bound } else { final_bounds <- rep(scenario$boundMax, length(which_alive)) elite_bound <- NULL } # Execute experiments. output <- race_wrapper(race_state, configurations = configurations[which_alive, , drop = FALSE], instance_idx = race_instances[current_task], bounds = final_bounds, is_exe = which_alive %in% which_exe, scenario = scenario) # Extract results # Set max execution bound to timed out executions which have execution # times smaller than boundMax and implement parX if required. vcost <- output[["cost"]] # Output is not indexed in the same way as configurations. which_has_time <- which(which_alive %in% which_exe) # With !is.null(scenario$targetEvaluator) we will have duplicated (instance, configuration) in output. irace_assert(all.equal(output[["bound"]], if (is.null(scenario$targetEvaluator)) final_bounds[which_has_time] else final_bounds)) if (capping) { vcost <- applyPAR(vcost, boundMax = scenario$boundMax, boundPar = scenario$boundPar) if (scenario$boundAsTimeout) { timeout_bounds <- if (is.null(scenario$targetEvaluator)) final_bounds[which_has_time] else final_bounds irace_assert(all.equal(output[["bound"]], timeout_bounds)) # We do not want to change Inf or -Inf because those represent rejection. vcost[is.finite(vcost) & (vcost >= timeout_bounds) & (vcost < scenario$boundMax)] <- scenario$boundMax } # If targetEvaluator was used, we do not update the times because no # evaluation actually happened, only the cost values possibly changed. vtimes <- if (is.null(scenario$targetEvaluator)) output[["time"]] else output[["time"]][which_has_time] # Correct higher execution times. irace_assert(all.equal(if (is.null(scenario$targetEvaluator)) output[["bound"]] else output[["bound"]][which_has_time], final_bounds[which_has_time])) experimentsTime[current_task, which_has_time] <- pmin(vtimes, final_bounds[which_has_time]) } ## Currently, targetEvaluator always re-evaluates, which implies that the ## value may change without counting as an evaluation. We do this to allow online normalization. which_has_cost <- if (is.null(scenario$targetEvaluator)) which_exe else which_alive irace_assert(length(output[["cost"]]) == length(which_has_cost)) Results[current_task, which_has_cost] <- vcost experiments_used <- experiments_used + length(which_exe) # We update the elites that have been executed. is_elite <- update_is_elite(is_elite, which_elite_exe) ## Drop bad configurations. ## Infinite values denote immediate rejection of a configuration. # FIXME: Should this be which_has_cost? rejected <- is.infinite(Results[current_task, which_exe]) if (any(rejected)) { irace_note ("Immediately rejected configurations: ", paste0(configurations[which_exe[rejected], ".ID."], collapse = ", ") , "\n") is_rejected[which_exe] <- rejected is_elite[is_rejected] <- 0L alive[is_rejected] <- FALSE if (!any(alive)) { # FIXME: Only report this error if (all(is_rejected)); otherwise # restore non-rejected non-alive ones. Restoring a non-alive # configuration is difficult. We need to evaluate it in all the # instances that it has missed. irace_error("All configurations have been immediately rejected (all of them returned Inf) !") } which_alive <- which(alive) nb_alive <- length(which_alive) # FIXME: Should we stop if (nbAlive <= minSurvival) ??? elite_safe <- update_elite_safe(Results, is_elite) } irace_assert(!anyNA(Results[seq_len(current_task), alive, drop=FALSE])) irace_assert(!any(is.infinite(Results[, alive, drop=FALSE]))) # Variables required to produce output of elimination test. cap_done <- FALSE # if dominance elimination was performed test.done <- FALSE # if statistical test elimination was performed cap_dropped <- FALSE # if capping has drop any configuration test.dropped <- FALSE # if any candidates has been eliminated by testing cap_alive <- test.alive <- alive ## Dominance elimination (Capping only). # The second condition can be false if we eliminated via immediate # rejection. The third condition ensures that we see the block before capping. if (capping && nb_alive > minSurvival && (current_task %% blockSize) == 0L && (!scenario$cappingAfterFirstTest || current_task >= firstTest)) { irace_assert(!any(is_elite > 0L) == (current_task >= elite_safe)) cap_alive <- dom_elim(Results[seq_len(current_task), , drop = FALSE], # Get current elite configurations. elites = which(is_elite > 0L), alive, scenario, minSurvival) cap_dropped <- nb_alive > sum(cap_alive) cap_done <- TRUE } # We assume that firstTest is a multiple of eachTest. In any # case, this will only do the first test after the first multiple # of eachTest that is larger than firstTest. if (current_task >= firstTest && (current_task %% eachTest) == 0L && nb_alive > 1L) { irace_assert(sum(alive) == nb_alive) test_res <- do_test(Results[seq_len(current_task), ], alive, which_alive) # FIXME: This race_ranks is unused. We should check if it matches the one computed below. race_ranks <- test_res$ranks test.alive <- test_res$alive test.dropped <- nb_alive > sum(test.alive) test.done <- TRUE } # Merge the result of both eliminations. prev_nb_alive <- nb_alive prev_which_alive <- which_alive alive <- cap_alive & test.alive # Handle elites when elimination is performed. The elite configurations # can be removed only when they have no more previously-executed instances. irace_assert(!any(is_elite > 0L) == (current_task >= elite_safe)) if (!is.null(elite_data) && any(is_elite > 0L)) { irace_assert (length(alive) == length(is_elite)) alive <- alive | (is_elite > 0L) } # It may happen that the capping and the test eliminate together all # configurations. In that case, we only trust the capping elimination. if (capping && !any(alive)) { if (scenario$debugLevel >= 2L) { irace_warning("Elimination tests have eliminated all configurations, keeping the capping results.\n") irace_note("Alive according to capping:", which(cap_alive), "\n") irace_note("Alive according to test:", which(test.alive), "\n") } alive <- cap_alive } which_alive <- which(alive) nb_alive <- length(which_alive) # Output the result of the elimination test. res.symb <- if (cap_dropped && !test.dropped && prev_nb_alive != nb_alive) { "c" # Removed just by capping. } else if (cap_dropped || test.dropped) { if (prev_nb_alive != nb_alive) "-" else "!" } else if (cap_done || test.done) "=" else "x" # Rank alive configurations: order all configurations (eliminated or not) # LESLIE: we have to make the ranking outside: we can have configurations eliminated by capping # that are not eliminated by the test. # MANUEL: I don't understand the above comment. if (length(prev_which_alive) == 1L) { race_ranks <- 1L best <- prev_which_alive } else { tmpResults <- Results[seq_len(current_task), prev_which_alive, drop = FALSE] irace_assert(!anyNA(tmpResults)) race_ranks <- get_ranks(tmpResults, test = stat_test) # which.min() returns only the first minimum. best <- prev_which_alive[which.min(race_ranks)] } irace_assert(best == prev_which_alive[order(race_ranks)][1L]) irace_assert(length(race_ranks) == length(prev_which_alive)) # Remove the ranks of those that are not alive anymore race_ranks <- race_ranks[which_alive] irace_assert(length(race_ranks) == nb_alive) id_best <- configurations[[".ID."]][best] print_task(res.symb, Results[seq_len(current_task), , drop = FALSE], race_instances[current_task], current_task, which_alive = which_alive, id_best = id_best, best = best, experiments_used, start_time = start_time, bound = elite_bound, capping) if (elitist) { # Compute number of statistical tests without eliminations. irace_assert(!any(is_elite > 0L) == (current_task >= elite_safe)) if (!any(is_elite > 0L) && current_task > firstTest && (current_task %% eachTest) == 0L) { no_elimination <- if (nb_alive == prev_nb_alive) no_elimination + 1L else 0L } } } if (is.null(break_msg)) break_msg <- paste0("all instances (", nb_tasks, ") evaluated") # Adding this given that when ncandidates = minsurvival+1 # and there one elite configuration that gets discarded in the new instances # execution the race is finished with no executions. # FIXME: we should handle this better, maybe allowing irace to handle no elite # in irace() # MANUEL: Leslie, how can we reach this error in normal circumstances? # Can we handle this better? if (current_task == 1L && all(is_elite == 0L)) irace_error ("Maximum number configurations immediately rejected reached!") # All instances that are not new in this race must have been evaluated by at # least one configuration. irace_assert(all_elite_instances_evaluated(), eval_after = { print(Results[,alive, drop=FALSE])}) # If we stop the loop before we see all new instances, there may be new # instances that have not been executed by any configuration. Results <- Results[rowAnyNotNAs(Results), , drop = FALSE] # If we reject configurations so that sum(alive) <= minSurvival, we may stop # before we evaluate some configurations in any instance. if (any(is_rejected)) { alive <- alive & colAnyNotNAs(Results) which_alive <- which(alive) if (!any(alive)) { # FIXME: Only report this error if (all(is_rejected)); otherwise # restore non-rejected non-alive ones. Restoring a non-alive # configuration is difficult. We need to evaluate it in all the # instances that it has missed. irace_error("All configurations have been immediately rejected (all of them returned Inf) !") } } race_ranks <- overall_ranks(Results[, alive, drop = FALSE], test = stat_test) if (!scenario$quiet) { old_best <- best # old_best could be NA. best <- which_alive[which.min(race_ranks)] mean_best <- mean(Results[, best]) print_footer(bestconf = configurations[best, , drop = FALSE], # FIXME: This is the mean of the best, but perhaps it # should be the sum of ranks in the case of test == friedman? mean_best = mean_best, break_msg = break_msg, debug_level = scenario$debugLevel, capping = capping, old_best_id = if (old_best == best || is.na(old_best)) NULL else id_best) } rejected_ids <- configurations[is_rejected, ".ID."] scenario$parameters$forbid_configurations( race_state$update_rejected(rejected_ids, configurations) ) # Only return alive ones. configurations <- configurations[alive, , drop=FALSE] irace_assert(all(configurations[[".ID."]] %not_in% rejected_ids)) # No rejected is alive. # Assign the proper ranks in the configurations data.frame. configurations[[".RANK."]] <- race_ranks # Now we can sort the data.frame by the rank. configurations <- configurations[order(configurations[[".RANK."]]), , drop=FALSE] if (scenario$debugLevel >= 3L) { irace_note ("Memory used in race():\n") race_state$print_mem_used() } local_experiment_log <- race_state$reset_race_experiment_log() # nrow(Results) may be smaller, equal or larger than current_task. if (is.null(scenario$targetEvaluator)) { # With targetEvaluator, we may have the recorded a new cost value but not # counted it as an experiment used if targetRunner was not called. irace_assert(anyDuplicated(local_experiment_log[, c("instance", "configuration")]) == 0L, eval_after = { print(local_experiment_log) print(mget(ls())) }) irace_assert(nrow(local_experiment_log) == experiments_used) } list(experiments = Results, experiment_log = local_experiment_log, experimentsUsed = experiments_used, configurations = configurations) } irace/R/multi_irace.R0000644000176200001440000001266714736573012014202 0ustar liggesusers#' Execute [irace()] multiple times with the same or different scenarios and parameter space definitions. #' #' There are three modes of operation: #' \itemize{ #' \item One `scenarios` and `k` `parameters`: `k` runs with the same scenario and each parameter space definition. #' \item One `parameters` and `k` `scenarios`: `k` runs with the same parameter space definition and each scenario. #' \item `k` `parameters` and `k` scenarios: `k` runs with each scenario and parameter space definition. #' } #' Each of the `k` runs can be repeated `n` times by supplying a value for `n`. #' #' @param scenarios `list()`\cr A list of scenarios. #' If only a single scenario is supplied, it is used for all parameters. #' @param parameters `list()`\cr A list of parameter space definitions. #' If only a single definition is supplied, it is used for all scenarios. #' @param n `integer(1)`\cr The number of repetitions. #' @param parallel `integer(1)`\cr The number of workers to use. #' A value of `1` means sequential execution. Note that `parallel > 1` is not supported on Windows. #' @param split_output `logical(1)`\cr If `TRUE`, the output of [irace()] is written to `{execDir}/run_{i}/irace.out` #' instead of the standard output. #' @param global_seed `integer(1)`\cr The global seed used to seed the individual runs. #' #' @return A list of the outputs of [irace()]. #' #' @seealso #' \describe{ #' \item{[irace()]}{the main interface for single irace runs.} #' } #' #' @concept running #' @export multi_irace <- function(scenarios, parameters, n = 1L, parallel = 1L, split_output = parallel > 1L, global_seed = NULL) { # Parallel execution is not available on Windows. if (.Platform$OS.type == 'windows' && parallel > 1L) { irace_error("multi_irace() does not yet support parallel > 1 on Windows") } # Allow either the same number of scenarios and parameters, or a single scenario or parameter space definition. if (length(scenarios) != length(parameters)) { if (length(scenarios) == 1L) { scenarios <- rep(scenarios, each = length(parameters)) } else if (length(parameters) == 1L) { parameters <- rep(parameters, each = length(scenarios)) } else { irace_error("Invalid arguments: ", "Cannot execute 'irace' with", length(scenarios), "scenarios and", length(parameters), "parameters.", "Either supply the same number of scenarios and parameters, or one scenario or one parameter space definition.") } } # Repeat the existing scenarios and parameters 'n' times. if (n > 1L) { scenarios <- rep(scenarios, each = n) parameters <- rep(parameters, each = n) } # Overwrite scenario seeds. seeds <- gen_random_seeds(length(scenarios), global_seed = global_seed) for (i in seq_along(scenarios)) { # FIXME: We should store this seed in state not in scenario. We should not modify scenario. scenarios[[i]]$seed <- seeds[[i]] scenarios[[i]]$parameters <- parameters[[i]] # Check each scenario. scenarios[[i]] <- checkScenario(scenarios[[i]]) # Modify 'logFile' and 'execDir' with the index of the run. # Paths are guaranteed to be absolute because of 'checkScenario'. logFile_old <- scenarios[[i]]$logFile execDir_old <- scenarios[[i]]$execDir # 'path/to/execDir' -> 'path/to/execDir/run_{i}'. execDir <- file.path(execDir_old, sprintf("run_%02d", i)) scenarios[[i]]$execDir <- execDir fs::dir_create(execDir) if (logFile_old != "") { logFile <- if (is.sub.path(logFile_old, execDir_old)) { # 'logFile' is located in the old 'execDir', so move it into the new 'execDir'. # 'path/to/execDir/logFile.rdata' -> 'path/to/execDir/run_{i}/logFile.rdata'. sub(execDir_old, execDir, logFile_old) } else { # 'logFile' is located outside, so adapt the file name. # 'path/to/logFile.rdata' -> 'path/to/logFile_{i}.rdata'. # pathWithoutExt <- tools::file_path_sans_ext(logFile_old) # pathExt <- tools::file_ext(logFile_old) # pathWithoutExtWithIndex <- sprintf("%s_%02d", pathWithoutExt, i) # paste(pathWithoutExtWithIndex, pathExt, sep = ".") irace_error("Invalid 'logFile' path (", logFile_old, "): ", "The 'logFile' must be located inside the 'execDir' (", execDir_old, ").") } scenarios[[i]]$logFile <- logFile } } irace_run <- function(scenario) { if (scenario$quiet || !split_output) { irace(scenario) } else { withr::with_output_sink(file.path(scenario$execDir, "irace.out"), { irace(scenario) }) } } if (parallel > 1L) { runs <- parallel::mcmapply(irace_run, scenarios, mc.cores = parallel, SIMPLIFY = FALSE) # FIXME: if stop() is called from mcmapply, it does not # terminate the execution of the parent process, so it will # continue and give more errors later. We have to terminate # here, but is there a nicer way to detect this and terminate? if (any(sapply(runs, inherits, "try-error")) || any(sapply(runs, is.null))) { # FIXME: mcmapply has some bugs in case of error. In that # case, each element of the list does not keep the output of # each configuration and repetitions may occur. errors <- unique(unlist(runs[sapply(runs, inherits, "try-error")])) cat(errors, file = stderr()) irace_error("A child process triggered a fatal error") } } else { runs <- mapply(irace_run, scenarios, SIMPLIFY = FALSE) } runs } irace/R/irace-options.R0000644000176200001440000003466314736537750014472 0ustar liggesusers## This file was generated by scripts/generate-options.R # Non-variable options (such as --help and --version) have names starting with "." # Variables that do not have a command-line option have description == "" # Types are b(oolean), i(nteger), s(tring), r(eal), p(ath), x (R object or no value) # FIXME: Add special type for R functions. # FIXME: For i and r add their domain. .irace.params.def <- structure(list(name = c(".help", ".version", ".check", ".init", ".onlytest", "scenarioFile", "execDir", "parameterFile", "parameters", "initConfigurations", "configurationsFile", "logFile", "recoveryFile", "instances", "trainInstancesDir", "trainInstancesFile", "sampleInstances", "testInstancesDir", "testInstancesFile", "testInstances", "testNbElites", "testIterationElites", "testType", "firstTest", "blockSize", "eachTest", "targetRunner", "targetRunnerLauncher", "targetCmdline", "targetRunnerRetries", "targetRunnerTimeout", "targetRunnerData", "targetRunnerParallel", "targetEvaluator", "deterministic", "maxExperiments", "minExperiments", "maxTime", "budgetEstimation", "minMeasurableTime", "parallel", "loadBalancing", "mpi", "batchmode", "quiet", "debugLevel", "seed", "softRestart", "softRestartThreshold", "elitist", "elitistNewInstances", "elitistLimit", "repairConfiguration", "capping", "cappingAfterFirstTest", "cappingType", "boundType", "boundMax", "boundDigits", "boundPar", "boundAsTimeout", "postselection", "aclib", "nbIterations", "nbExperimentsPerIteration", "minNbSurvival", "nbConfigurations", "mu", "confidence"), type = c("x", "x", "x", "x", "p", "p", "p", "p", "x", "x", "p", "p", "p", "s", "p", "p", "b", "p", "p", "x", "i", "b", "s", "i", "i", "i", "p", "p", "s", "i", "i", "x", "x", "p", "b", "i", "i", "i", "r", "r", "i", "b", "b", "s", "b", "i", "i", "b", "r", "b", "i", "i", "x", "b", "b", "s", "s", "i", "i", "i", "b", "b", "b", "i", "i", "i", "i", "i", "r"), short = c("-h", "-v", "-c", "-i", "", "-s", "", "-p", "", "", "", "-l", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "-q", "", "", "", "", "-e", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ), long = c("--help", "--version", "--check", "--init", "--only-test", "--scenario", "--exec-dir", "--parameter-file", "", "", "--configurations-file", "--log-file", "--recovery-file", "", "--train-instances-dir", "--train-instances-file", "--sample-instances", "--test-instances-dir", "--test-instances-file", "", "--test-num-elites", "--test-iteration-elites", "--test-type", "--first-test", "--block-size", "--each-test", "--target-runner", "--target-runner-launcher", "--target-cmdline", "--target-runner-retries", "--target-runner-timeout", "", "", "--target-evaluator", "--deterministic", "--max-experiments", "--min-experiments", "--max-time", "--budget-estimation", "--min-measurable-time", "--parallel", "--load-balancing", "--mpi", "--batchmode", "--quiet", "--debug-level", "--seed", "--soft-restart", "--soft-restart-threshold", "--elitist", "--elitist-new-instances", "--elitist-limit", "", "--capping", "--capping-after-first-test", "--capping-type", "--bound-type", "--bound-max", "--bound-digits", "--bound-par", "--bound-as-timeout", "--postselection", "--aclib", "--iterations", "--experiments-per-iteration", "--min-survival", "--num-configurations", "--mu", "--confidence"), default = c(NA, NA, NA, "", "", "./scenario.txt", "./", "./parameters.txt", "", "", "", "./irace.Rdata", "", "", "", "", "1", "", "", "", "1", "0", "", "5", "1", "1", "./target-runner", "", "{configurationID} {instanceID} {seed} {instance} {bound} {targetRunnerArgs}", "0", "0", "", "", "", "0", "0", NA, "0", "0.05", "0.01", "0", "1", "0", "0", "0", "0", NA, "1", "1e-04", "1", "1", "2", "", NA, "0", "median", "candidate", "0", "0", "1", "1", "1", "0", "0", "0", "0", "0", "5", "0.95"), domain = c(NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "F-test,t-test,t-test-holm,t-test-bonferroni", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "sge,pbs,torque,slurm,htcondor", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "median,mean,worst,best", "instance,candidate", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA), description = c("Show this help.", "Show irace package version.", "Check scenario.", "Initialize the working directory with template config files.", "Only test the configurations given in the file passed as argument.", "File that describes the configuration scenario setup and other irace settings.", "Directory where the programs will be run.", "File that contains the description of the parameters of the target algorithm.", "", "", "File that contains a table of initial configurations. If empty or `NULL`, all initial configurations are randomly generated.", "File to save tuning results as an R dataset, either absolute path or relative to execDir.", "Previously saved log file to recover the execution of `irace`, either absolute path or relative to the current directory. If empty or `NULL`, recovery is not performed.", "", "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.", "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.", "Randomly sample the training instances or use them in the order given.", "Directory where testing instances are located, either absolute or relative to current directory.", "File containing a list of test instances and optionally additional parameters for them.", "", "Number of elite configurations returned by irace that will be tested if test instances are provided.", "Enable/disable testing the elite configurations found at each iteration.", "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).", "Number of instances evaluated before the first elimination test. It must be a multiple of `eachTest`.", "Number of training instances, that make up a 'block' in `trainInstancesFile`. Elimination of configurations will only be performed after evaluating a complete block and never in the middle of a block. Each block typically contains one instance from each instance class (type or family) and the block size is the number of classes. The value of `blockSize` will multiply `firstTest`, `eachTest` and `elitistNewInstances`.", "Number of instances evaluated between elimination tests.", "Executable called for each configuration that executes the target algorithm to be tuned. See the templates and examples provided.", "Executable that will be used to launch the target runner, when `targetRunner` cannot be executed directly (e.g., a Python script in Windows).", "Command-line arguments provided to `targetRunner` (or `targetRunnerLauncher` if defined). The substrings `\\{configurationID\\}`, `\\{instanceID\\}`, `\\{seed\\}`, `\\{instance\\}`, and `\\{bound\\}` will be replaced by their corresponding values. The substring `\\{targetRunnerArgs\\}` will be replaced by the concatenation of the switch and value of all active parameters of the particular configuration being evaluated. The substring `\\{targetRunner\\}`, if present, will be replaced by the value of `targetRunner` (useful when using `targetRunnerLauncher`).", "Number of times to retry a call to `targetRunner` if the call failed.", "Timeout in seconds of any `targetRunner` call (only applies to `target-runner` executables not to R functions), ignored if 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.", "Optional R function to provide custom parallelization of `targetRunner`.", "Optional script or R function that provides a numeric value for each configuration. See templates/target-evaluator.tmpl", "If the target algorithm is deterministic, configurations will be evaluated only once per instance.", "Maximum number of runs (invocations of `targetRunner`) that will be performed. It determines the maximum budget of experiments for the tuning.", "Minimum number of runs (invocations of `targetRunner`) that will be performed. It determines the minimum budget of experiments for the tuning. The actual budget depends on the number of parameters and `minSurvival`.", "Maximum total execution time for the executions of `targetRunner`. `targetRunner` must return two values: cost and time. This value and the one returned by `targetRunner` must use the same units (seconds, minutes, iterations, evaluations, ...).", "Fraction (smaller than 1) of the budget used to estimate the mean computation time of a configuration. Only used when `maxTime` > 0", "Minimum time unit that is still (significantly) measureable.", "Number of calls to `targetRunner` to execute in parallel. Values `0` or `1` mean no parallelization.", "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.", "Enable/disable MPI. Use `Rmpi` to execute `targetRunner` in parallel (parameter `parallel` is the number of slaves).", "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`.", "Reduce the output generated by irace to a minimum.", "Debug level of the output of `irace`. Set this to 0 to silence all debug messages. Higher values provide more verbose debug messages.", "Seed of the random number generator (by default, generate a random seed).", "Enable/disable the soft restart strategy that avoids premature convergence of the probabilistic model.", "Soft restart threshold value for numerical parameters.", "Enable/disable elitist irace.", "Number of instances added to the execution list before previous instances in elitist irace.", "In elitist irace, maximum number per race of elimination tests that do not eliminate a configuration. Use 0 for no limit.", "User-defined R function that takes a configuration generated by irace and repairs it.", "Enable the use of adaptive capping, a technique designed for minimizing the computation time of configurations. Capping is enabled by default if `elitist` is active, `maxTime > 0` and `boundMax > 0`.", "If set to 1, elimination due to capping only happens after `firstTest` instances are seen.", "Measure used to obtain the execution bound from the performance of the elite configurations: median, mean, worst, best.", "Method to calculate the mean performance of elite configurations: candidate or instance.", "Maximum execution bound for `targetRunner`. It must be specified when capping is enabled.", "Precision used for calculating the execution time. It must be specified when capping is enabled.", "Penalization constant for timed out executions (executions that reach `boundMax` execution time).", "Replace the configuration cost of bounded executions with `boundMax`.", "Perform a postselection race after the execution of irace to consume all remaining budget. Value 0 disables the postselection race.", "Enable/disable AClib mode. This option enables compatibility with GenericWrapper4AC as targetRunner script.", "Maximum number of iterations.", "Number of runs of the target algorithm per iteration.", "Minimum number of configurations needed to continue the execution of each race (iteration).", "Number of configurations to be sampled and evaluated at each iteration.", "Parameter used to define the number of configurations sampled and evaluated at each iteration.", "Confidence level for the elimination test.")), row.names = c(".help", ".version", ".check", ".init", ".onlytest", "scenarioFile", "execDir", "parameterFile", "parameters", "initConfigurations", "configurationsFile", "logFile", "recoveryFile", "instances", "trainInstancesDir", "trainInstancesFile", "sampleInstances", "testInstancesDir", "testInstancesFile", "testInstances", "testNbElites", "testIterationElites", "testType", "firstTest", "blockSize", "eachTest", "targetRunner", "targetRunnerLauncher", "targetCmdline", "targetRunnerRetries", "targetRunnerTimeout", "targetRunnerData", "targetRunnerParallel", "targetEvaluator", "deterministic", "maxExperiments", "minExperiments", "maxTime", "budgetEstimation", "minMeasurableTime", "parallel", "loadBalancing", "mpi", "batchmode", "quiet", "debugLevel", "seed", "softRestart", "softRestartThreshold", "elitist", "elitistNewInstances", "elitistLimit", "repairConfiguration", "capping", "cappingAfterFirstTest", "cappingType", "boundType", "boundMax", "boundDigits", "boundPar", "boundAsTimeout", "postselection", "aclib", "nbIterations", "nbExperimentsPerIteration", "minNbSurvival", "nbConfigurations", "mu", "confidence"), class = "data.frame") .irace.params.names <- c("scenarioFile", "execDir", "parameterFile", "parameters", "initConfigurations", "configurationsFile", "logFile", "recoveryFile", "instances", "trainInstancesDir", "trainInstancesFile", "sampleInstances", "testInstancesDir", "testInstancesFile", "testInstances", "testNbElites", "testIterationElites", "testType", "firstTest", "blockSize", "eachTest", "targetRunner", "targetRunnerLauncher", "targetCmdline", "targetRunnerRetries", "targetRunnerTimeout", "targetRunnerData", "targetRunnerParallel", "targetEvaluator", "deterministic", "maxExperiments", "minExperiments", "maxTime", "budgetEstimation", "minMeasurableTime", "parallel", "loadBalancing", "mpi", "batchmode", "quiet", "debugLevel", "seed", "softRestart", "softRestartThreshold", "elitist", "elitistNewInstances", "elitistLimit", "repairConfiguration", "capping", "cappingAfterFirstTest", "cappingType", "boundType", "boundMax", "boundDigits", "boundPar", "boundAsTimeout", "postselection", "aclib", "nbIterations", "nbExperimentsPerIteration", "minNbSurvival", "nbConfigurations", "mu", "confidence") ## FIXME: If these values are special perhaps they should be saved in $state ? .irace.params.recover <- c("instances", "seed", "testInstances", # We need this because this data may mutate "targetRunnerData", "elitist", "deterministic") irace/R/race_state.R0000644000176200001440000002561314745735066014023 0ustar liggesusersRaceState <- R6Class("RaceState", lock_class = TRUE, public = list( # This may be dynamically adjusted cluster = NULL, completed = "Incomplete", elapsed = 0L, elapsed_recovered = 0L, elitist_new_instances = 0L, experiment_log = NULL, instances_log = NULL, minSurvival = NULL, next_instance = -1L, race_experiment_log = NULL, recovery_info = NULL, recovery_mode = FALSE, rejected_ids = NULL, rng = NULL, seed = NULL, session_info = NULL, target_evaluator = NULL, target_runner = NULL, timeUsed = 0, time_next_save = 0, timer = NULL, # Methods. initialize = function(scenario, new = TRUE, recover = FALSE) { self$timer <- Timer$new() self$target_runner <- if (is.function(scenario$targetRunner)) bytecompile(scenario$targetRunner) else if (scenario$aclib) target_runner_aclib else target_runner_default if (!is.null(scenario$targetEvaluator)) { self$target_evaluator <- if (is.function(scenario$targetEvaluator)) bytecompile(scenario$targetEvaluator) else target_evaluator_default } irace_assert(new || !recover) if (new) { # elitist_new_instances must be a multiple of blockSize. self$elitist_new_instances <- scenario$elitistNewInstances * scenario$blockSize # We cannot recover if we did not get to initialize self$rng. if (recover && !is.null(self$rng)) { restore_random_seed(self$rng) self$recovery_mode <- TRUE set(self$experiment_log, j = "iteration", value = NULL) self$recovery_info <- rbindlist(c(list(self$experiment_log), self$race_experiment_log), use.names = TRUE) # Reinitialize some state. self$completed = "Incomplete" self$elapsed = 0L self$elapsed_recovered = 0L self$experiment_log = NULL self$next_instance = -1L self$race_experiment_log = NULL self$rejected_ids = NULL self$timeUsed = 0 self$time_next_save = 0 # Just in case anything is still running. self$stop_parallel() } else { seed <- scenario$seed if (is.na(seed)) seed <- trunc(runif(1, 1, .Machine$integer.max)) set_random_seed(seed) self$seed <- seed self$rng <- get_random_seed() } } else { # !new self$elapsed_recovered <- self$elapsed restore_random_seed(self$rng) } if (is.null(self$experiment_log)) { self$experiment_log <- data.table(iteration=integer(0), instance=integer(0), configuration=integer(0), cost = numeric(0), time = numeric(0), bound = if (is.null(scenario$boundMax)) NULL else numeric(0)) } if (scenario$debugLevel >= 3L) { irace_note("RNGkind: ", paste0(self$rng$rng_kind, collapse = " "), "\n", "# .Random.seed: ", paste0(self$rng$random_seed, collapse = ", "), "\n") } # We do this here, so it is available even if we crash. self$session_info <- utils::sessionInfo() invisible(self) }, update_experiment_log = function(output, instances, scenario) { # FIXME: The instances parameter is not needed. irace_assert(all.equal(rep(instances, each = length(unique(output[["configuration"]]))), output$instance)) # Extract results self$experiment_log <- rbindlist(list(self$experiment_log, output), use.names=TRUE) experiments_output_to_matrix(output, scenario) }, save_recovery = function(iraceResults, logfile) { now <- self$timer$wallclock() # Do not save to disk too frequently. if (now >= self$time_next_save) { # irace_note("Saving recovery info.\n") iraceResults$state <- self save_irace_logfile(iraceResults, logfile) self$time_next_save <- now + .irace_minimum_saving_time } }, update_race_experiment_log = function(experiment_log, scenario) { self$race_experiment_log <- c(self$race_experiment_log, list(experiment_log)) now <- self$timer$wallclock() # Do not save to disk too frequently. if (now >= self$time_next_save) { # irace_note("Saving recovery info.\n") iraceResults <- list( scenario = scenario, irace_version = irace_version, state = self) save_irace_logfile(iraceResults, logfile = scenario$logFile) self$time_next_save <- now + .irace_minimum_saving_time } invisible() }, reset_race_experiment_log = function() { res <- rbindlist(self$race_experiment_log, use.names=TRUE) self$race_experiment_log <- NULL res }, recover_output = function(instance_idx, configuration_id) { search <- data.table(instance = instance_idx, configuration = configuration_id) res <- self$recovery_info[search, on = .(instance,configuration), mult="first", nomatch=NULL, which=TRUE] irace_assert(length(res) == 0L || length(res) == nrow(search)) if (length(res) == 0L) { irace_note("Cannot find the following in recovery info:") print(search[!self$recovery_info, on = .(instance,configuration)]) irace_error("Recovery terminated.") } # Get the rows. output <- self$recovery_info[res] # Delete those rows. self$recovery_info <- self$recovery_info[-res] if (nrow(self$recovery_info) == 0L) { irace_note("Recovery completed.\n") self$recovery_mode <- FALSE self$recovery_info <- NULL } output }, update_rejected = function(rejected_ids, configurations) { if (length(rejected_ids) == 0L) return(NULL) self$rejected_ids <- c(self$rejected_ids, rejected_ids) configurations[configurations[[".ID."]] %in% rejected_ids, , drop = FALSE] }, time_elapsed = function() { self$elapsed <- self$timer$elapsed() + self$elapsed_recovered self$elapsed }, start_parallel = function(scenario) { parallel <- scenario$parallel data.table::setDTthreads(if (parallel <= 1L) 1L else min(4L, parallel)) if (!is.null(scenario$targetRunnerParallel) || parallel <= 1L) return(invisible(self)) # Starting the parallel environment may set some logs to the current # directory, so switch to execDir momentarily. withr::local_dir(scenario$execDir) # setwd() if (scenario$mpi) { mpiInit(parallel, scenario$debugLevel) } else { requireNamespace("parallel", quietly = TRUE) if (.Platform$OS.type == 'windows' && is.null(self$cluster)) { # FIXME: makeCluster does not print the output generated by the workers # on Windows. We need to use the future package for that: # https://stackoverflow.com/questions/56501937/how-to-print-from-clusterapply self$cluster <- parallel::makeCluster(parallel) if (scenario$debugLevel >= 1L) irace_note("makeCluster initialized for ", parallel, " jobs.\n") # We export the global environment because the user may have defined # stuff there. There must be a better way to do this, but I cannot # figure it out. R sucks sometimes. parallel::clusterExport(self$cluster, ls(envir=.GlobalEnv)) # In Windows, this needs to be exported, or we get: ## Error in checkForRemoteErrors(val) : ## 2 nodes produced errors; first error: could not find function "target_runner" parallel::clusterExport(self$cluster, list("target_runner"), envir=self) if (is.function(scenario$targetRunner) && !identical(environment(scenario$targetRunner), globalenv())) { env_target_runner <- environment(scenario$targetRunner) funglobs <- codetools::findGlobals(self$target_runner, merge=TRUE) common <- intersect(funglobs, ls(envir=env_target_runner)) if (length(common)) parallel::clusterExport(self$cluster, common, envir=env_target_runner) } } } invisible(self) }, stop_parallel = function() { if (!is.null(self$cluster)) { try(parallel::stopCluster(self$cluster), silent=TRUE) self$cluster <- NULL } invisible(self) }, print_mem_used = function(objects) { object_size_kb <- function (name, envir) utils::object.size(get(name, envir = envir)) / 1024 envir <- parent.frame() if (missing(objects)) objects <- ls(envir = envir, all.names = TRUE) x <- sapply(objects, object_size_kb, envir = envir) y <- sapply(names(get(class(self)[1L])$public_fields), object_size_kb, envir = self) names(y) <- paste0("RaceState$", names(y)) x <- c(x, y) # Do not print anything that is smaller than 32 Kb x <- x[x > 32] cat(sep="", sprintf("%30s : %17.1f Kb\n", names(x), x), sprintf("%30s : %17.1f Mb\n", "Total", sum(x) / 1024), # This does garbage collection and also prints memory used by R. sprintf("%30s : %17.1f Mb\n", "gc", sum(gc()[,2L]))) invisible(self) } )) no_elitist_init_instances <- function(self, deterministic) { max_instances <- nrow(self$instances_log) # if next.instance == 1 then this is the first iteration. # If deterministic consider all (do not resample). if (self$next_instance == 1L || deterministic) return(seq_len(max_instances)) irace_assert(self$next_instance < max_instances) self$next_instance : max_instances } elitist_init_instances <- function(self, deterministic, sampleInstances, elitist_new_instances) { max_instances <- nrow(self$instances_log) # if next_instance == 1 then this is the first iteration. next_instance <- self$next_instance if (next_instance == 1L) return(seq_len(max_instances)) # Consider all new_instances <- NULL last_new <- next_instance - 1L + elitist_new_instances # Do we need to add new instances? if (elitist_new_instances > 0L) { if (last_new > max_instances) { # This may happen if the scenario is deterministic and we would need # more instances than what we have. irace_assert(deterministic) if (next_instance <= max_instances) { # Add all instances that we have not seen yet as new ones. last_new <- max_instances new_instances <- next_instance : last_new } # else new_instances remains NULL and last_new remains > number of instances. # We need to update this because the value is used below and now there # may be fewer than expected, even zero. self$elitist_new_instances <- length(new_instances) } else { new_instances <- next_instance : last_new } } past_instances <- if (sampleInstances) sample.int(next_instance - 1L) else seq_len(next_instance - 1L) # new_instances + past_instances + future_instances if (last_new + 1L <= max_instances) { future_instances <- (last_new + 1L) : max_instances return(c(new_instances, past_instances, future_instances)) } c(new_instances, past_instances) } irace/R/argparser.R0000644000176200001440000000741314736526233013667 0ustar liggesusers# R6 Class for parsing command-line arguments CommandArgsParser <- R6::R6Class("CommandArgsParser", cloneable = FALSE, lock_class = TRUE, list( argv = NULL, argsdef = NULL, initialize = function(argv, argsdef) { # Handle the case where we are given a single character string like a # command-line. if (!missing(argv) && length(argv) == 1) { # strsplit does not respect quoted strings. argv <- scan(text=argv, what='character', quiet=TRUE) } self$argv <- argv required_colnames <- c("name", "short", "long", "type", "default") if (any(required_colnames %not_in% colnames(argsdef))) { stop("argsdef must contain the column names: ", paste0(required_colnames, collapse=", ")) } self$argsdef <- argsdef rownames(self$argsdef) <- argsdef$name self }, readCmdLineParameter = function (paramName, default = NULL) { short <- self$argsdef[paramName, "short"] long <- self$argsdef[paramName,"long"] value <- self$readArg(short = short, long = long) if (is.null(value)) { value <- if (is.null(default)) self$argsdef[paramName, "default"] else default } else if (is.na(value) && self$argsdef[paramName,"type"] != 'x') { stop("option '", long, "' requires an argument", call. = FALSE) } return(value) }, # Function to read command-line arguments. ## FIXME: This function always consumes two arguments. This is problematic ## for flags that have no arguments, like --check. readArg = function(short = "", long = "") { if (length(short) == 0) short <- "" if (length(long) == 0) long <- "" if (short == "" && long == "") return(NULL) argv <- self$argv pos <- c() pattern <- "" if (short != "") { pattern_equal <- paste0("^", short, "=") pattern <- paste0("^", short, "$|", pattern_equal) } if (long != "") { pattern_long_equal <- paste0("^", long, "=") pattern_long <- paste0("^", long, "$|", pattern_long_equal) if (short != "") { pattern <- paste0(pattern, "|", pattern_long) pattern_equal <- paste0(pattern_equal, "|", pattern_long_equal) } else { pattern <- pattern_long pattern_equal <- pattern_long_equal } } pos <- grep(pattern, argv) if (length(pos) == 0) { return(NULL) # Not found } else if (length(pos) > 0) { # Allow repeated parameters pos <- max(pos) } if (grepl(pattern_equal, argv[pos])) { value <- unlist(strsplit(argv[pos], '=', fixed = TRUE))[2] if (is.null (value) || is.na(value)) value <- "" } else { value <- argv[pos + 1] self$argv <- argv[-(pos + 1)] } self$argv <- self$argv[-pos] return (value) }, readAll = function() { params <- list() for (param in self$argsdef$name[self$argsdef$type != 'x']) { value <- self$readCmdLineParameter(paramName = param) if (is.na(value) || (length(value) > 0 && value == "")) value <- NULL params[[param]] <- value } params }, cmdline_usage = function() { cmdline_usage(self$argsdef) }) ) # `cmdline_usage()` prints the output of `--help` # # @param cmdline_args Definition of the command-line arguments. # # @export cmdline_usage <- function(cmdline_args) { for (i in seq_len(nrow(cmdline_args))) { short <- cmdline_args[i,"short"] long <- cmdline_args[i,"long"] desc <- cmdline_args[i,"description"] if (desc == "" || (short == "" && long == "")) next if (short != "") short <- paste0(short,",") default <- cmdline_args[i,"default"] if (!is_null_or_empty_or_na(default)) { desc <- paste0(desc, " Default: ", default, ".") } cat(sep = "\n", strwrap(desc, width = 80, initial = sprintf("%3s%-20s ", short, long), exdent = 25)) } } irace/cleanup0000755000176200001440000000154514752430310012716 0ustar liggesusers#!/bin/sh rm -f irace.Rdata irace-Ex.R config.* confdefs.h \ src/*.so src/*.o src/*/*.o src/*.gcno src/*/*.gcno src/*.gcov src/config.h src/symbols.rds \ inst/doc/*.blg inst/doc/*.bbl \ tests/testthat/*.log tests/testthat/*.Rout tests/testthat/Rplots.pdf tests/testthat/iracedump.rda tests/testthat/irace.Rdata \ examples/vignette-example/Rplots.pdf \ devel-examples/vignette-example/*.stderr \ devel-examples/vignette-example/*.stdout \ devel-examples/vignette-example/irace-acotsp.Rdata \ devel-examples/vignette-example/examples.Rdata \ devel-examples/vignette-example/irace-acotsp-stdout.txt \ devel-examples/vignette-example/mean.pdf \ devel-examples/vignette-example/sann.rda rm -fr tests/testthat/run_* tests/testthat/multi_irace* \ autom4te.cache find . -name '*.orig' -o -name '.Rhistory' -o -name '*.rej' | xargs rm -f irace/vignettes/0000755000176200001440000000000014752430310013344 5ustar liggesusersirace/vignettes/irace-acotsp-stdout.txt0000644000176200001440000010651614746202625020021 0ustar liggesusers#------------------------------------------------------------------------------ # irace: An implementation in R of (Elitist) Iterated Racing # Version: 4.2.0.ee928b9 # Copyright (C) 2010-2025 # Manuel Lopez-Ibanez # Jeremie Dubois-Lacoste # Leslie Perez Caceres # # This is free software, and you are welcome to redistribute it under certain # conditions. See the GNU General Public License for details. There is NO # WARRANTY; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # # irace builds upon previous code from the race package: # race: Racing methods for the selection of the best # Copyright (C) 2003 Mauro Birattari #------------------------------------------------------------------------------ # installed at: /home/manu/R/x86_64-pc-linux-gnu-library/4.1/irace # called with: --parallel 2 # 2025-01-28 13:47:31 GMT: Reading parameter file '/home/manu/work/irace/git/devel-examples/vignette-example/parameters.txt'. # 2025-01-28 13:47:31 GMT: 1 expression(s) specifying forbidden configurations read. # 2025-01-28 13:47:31 GMT: Read 1 configuration(s) from file '/home/manu/work/irace/git/devel-examples/vignette-example/default.txt' # 2025-01-28 13:47:31 GMT: Initialization # Elitist race # Elitist new instances: 1 # Elitist limit: 2 # nbIterations: 5 # minNbSurvival: 5 # nbParameters: 11 # seed: 687542627 # confidence level: 0.95 # budget: 1000 # mu: 5 # deterministic: FALSE # 2025-01-28 13:47:31 GMT: Iteration 1 of 5 # experimentsUsed: 0 # remainingBudget: 1000 # currentBudget: 200 # nbConfigurations: 33 # Markers: x No test is performed. c Configurations are discarded only due to capping. - The test is performed and some configurations are discarded. = The test is performed but no configuration is discarded. ! The test is performed and configurations could be discarded but elite configurations are preserved. . All alive configurations are elite and nothing is discarded. +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ | | Instance| Alive| Best| Mean best| Exp so far| W time| rho|KenW| Qvar| +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ |x| 1| 33| 22| 33693816.00| 33|00:01:41| NA| NA| NA| |x| 2| 33| 31| 33233161.00| 66|00:01:41|+0.96|0.98|0.0060| |x| 3| 33| 31| 33285969.67| 99|00:01:35|+0.97|0.98|0.0052| |x| 4| 33| 31| 33264133.00| 132|00:01:37|+0.97|0.98|0.0046| |-| 5| 3| 31| 33251469.40| 165|00:01:36|-0.05|0.16|0.4985| +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ Best-so-far configuration: 31 mean value: 33251469.40 Description of the best-so-far configuration: .ID. algorithm localsearch alpha beta rho ants nnls q0 dlb rasrank elitistants time .PARENT. 31 31 acs 3 4.2717 0.2871 0.9362 6 17 0.918 1 NA NA 5 NA # 2025-01-28 13:55:43 GMT: Elite configurations (first number is the configuration ID; listed from best to worst according to the sum of ranks): algorithm localsearch alpha beta rho ants nnls q0 dlb rasrank elitistants time 31 acs 3 4.2717 0.2871 0.9362 6 17 0.918 1 NA NA 5 9 mmas 3 3.4904 4.9746 0.5959 13 33 NA 0 NA NA 5 22 as 3 2.0842 2.1621 0.7506 25 26 NA 1 NA NA 5 # 2025-01-28 13:55:43 GMT: Iteration 2 of 5 # experimentsUsed: 165 # remainingBudget: 835 # currentBudget: 208 # nbConfigurations: 31 # Markers: x No test is performed. c Configurations are discarded only due to capping. - The test is performed and some configurations are discarded. = The test is performed but no configuration is discarded. ! The test is performed and configurations could be discarded but elite configurations are preserved. . All alive configurations are elite and nothing is discarded. +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ | | Instance| Alive| Best| Mean best| Exp so far| W time| rho|KenW| Qvar| +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ |x| 6| 31| 31| 32987639.00| 31|00:01:31| NA| NA| NA| |x| 1| 31| 61| 33317599.00| 59|00:01:19|+0.95|0.97|0.0029| |x| 4| 31| 61| 33270043.67| 87|00:01:19|+0.94|0.96|0.0026| |x| 3| 31| 61| 33282925.75| 115|00:01:20|+0.94|0.96|0.0025| |-| 2| 7| 61| 33187955.60| 143|00:01:19|+0.20|0.36|0.7082| |=| 5| 7| 31| 33207497.67| 147|00:00:12|+0.09|0.24|0.7586| |=| 7| 7| 31| 33197039.29| 154|00:00:21|+0.16|0.28|0.7095| +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ Best-so-far configuration: 31 mean value: 33197039.29 Description of the best-so-far configuration: .ID. algorithm localsearch alpha beta rho ants nnls q0 dlb rasrank elitistants time .PARENT. 31 31 acs 3 4.2717 0.2871 0.9362 6 17 0.918 1 NA NA 5 NA # 2025-01-28 14:03:09 GMT: Elite configurations (first number is the configuration ID; listed from best to worst according to the sum of ranks): algorithm localsearch alpha beta rho ants nnls q0 dlb rasrank elitistants time 31 acs 3 4.2717 0.2871 0.9362 6 17 0.9180 1 NA NA 5 61 ras 3 3.6680 2.4269 0.8717 96 14 NA 1 89 NA 5 37 acs 3 3.8927 1.6181 0.6188 6 35 0.8587 1 NA NA 5 41 ras 3 3.3674 5.9837 0.7399 12 40 NA 1 95 NA 5 9 mmas 3 3.4904 4.9746 0.5959 13 33 NA 0 NA NA 5 # 2025-01-28 14:03:09 GMT: Iteration 3 of 5 # experimentsUsed: 319 # remainingBudget: 681 # currentBudget: 227 # nbConfigurations: 32 # Markers: x No test is performed. c Configurations are discarded only due to capping. - The test is performed and some configurations are discarded. = The test is performed but no configuration is discarded. ! The test is performed and configurations could be discarded but elite configurations are preserved. . All alive configurations are elite and nothing is discarded. +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ | | Instance| Alive| Best| Mean best| Exp so far| W time| rho|KenW| Qvar| +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ |x| 8| 32| 65| 32811562.00| 32|00:01:27| NA| NA| NA| |x| 2| 32| 75| 32779249.00| 59|00:01:16|+0.92|0.96|0.0031| |x| 6| 32| 75| 32799039.00| 86|00:01:16|+0.92|0.95|0.0036| |x| 7| 32| 75| 32842164.00| 113|00:01:16|+0.89|0.92|0.0044| |-| 5| 8| 75| 32875613.60| 140|00:01:16|+0.64|0.71|0.2166| |-| 1| 7| 75| 32974384.00| 143|00:00:10|+0.66|0.71|0.2031| |-| 4| 6| 75| 32972595.86| 145|00:00:05|+0.53|0.60|0.2424| |-| 3| 1| 75| 32995307.50| 146|00:00:05| NA| NA| NA| +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ Best-so-far configuration: 75 mean value: 32995307.50 Description of the best-so-far configuration: .ID. algorithm localsearch alpha beta rho ants nnls q0 dlb rasrank elitistants time .PARENT. 75 75 ras 3 4.5827 1.6918 0.818 13 10 NA 1 15 NA 5 37 # 2025-01-28 14:10:04 GMT: Elite configurations (first number is the configuration ID; listed from best to worst according to the sum of ranks): algorithm localsearch alpha beta rho ants nnls q0 dlb rasrank elitistants time 75 ras 3 4.5827 1.6918 0.818 13 10 NA 1 15 NA 5 # 2025-01-28 14:10:04 GMT: Iteration 4 of 5 # experimentsUsed: 465 # remainingBudget: 535 # currentBudget: 267 # nbConfigurations: 30 # Markers: x No test is performed. c Configurations are discarded only due to capping. - The test is performed and some configurations are discarded. = The test is performed but no configuration is discarded. ! The test is performed and configurations could be discarded but elite configurations are preserved. . All alive configurations are elite and nothing is discarded. +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ | | Instance| Alive| Best| Mean best| Exp so far| W time| rho|KenW| Qvar| +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ |x| 9| 30| 113| 32734055.00| 30|00:01:21| NA| NA| NA| |x| 6| 30| 91| 32806654.50| 59|00:01:21|+0.83|0.91|0.0624| |x| 2| 30| 91| 32738738.33| 88|00:01:21|+0.75|0.83|0.1103| |x| 5| 30| 117| 32780373.75| 117|00:01:21|+0.72|0.79|0.1597| |-| 1| 12| 117| 32893747.40| 146|00:01:21|-0.01|0.19|0.9196| |=| 4| 12| 113| 32874137.17| 157|00:00:32|+0.05|0.20|0.8612| |=| 3| 12| 113| 32910758.71| 168|00:00:32|+0.12|0.25|0.8001| |-| 8| 6| 113| 32892247.62| 179|00:00:32|+0.08|0.19|0.7116| |=| 7| 6| 113| 32894494.00| 184|00:00:16|+0.09|0.19|0.6930| |=| 10| 6| 113| 32912596.60| 190|00:00:16|+0.01|0.11|0.7806| +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ Best-so-far configuration: 113 mean value: 32912596.60 Description of the best-so-far configuration: .ID. algorithm localsearch alpha beta rho ants nnls q0 dlb rasrank elitistants time .PARENT. 113 113 ras 3 4.1042 1.531 0.8104 16 12 NA 1 24 NA 5 75 # 2025-01-28 14:19:01 GMT: Elite configurations (first number is the configuration ID; listed from best to worst according to the sum of ranks): algorithm localsearch alpha beta rho ants nnls q0 dlb rasrank elitistants time 113 ras 3 4.1042 1.5310 0.8104 16 12 NA 1 24 NA 5 117 ras 3 4.1817 0.5324 0.8893 13 18 NA 1 23 NA 5 92 ras 3 4.9632 4.1134 0.8790 7 9 NA 1 26 NA 5 102 ras 3 4.3582 2.8458 0.7508 9 19 NA 1 86 NA 5 93 ras 3 4.1500 2.0082 0.8067 10 14 NA 1 32 NA 5 # 2025-01-28 14:19:01 GMT: Iteration 5 of 5 # experimentsUsed: 655 # remainingBudget: 345 # currentBudget: 345 # nbConfigurations: 35 # Markers: x No test is performed. c Configurations are discarded only due to capping. - The test is performed and some configurations are discarded. = The test is performed but no configuration is discarded. ! The test is performed and configurations could be discarded but elite configurations are preserved. . All alive configurations are elite and nothing is discarded. +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ | | Instance| Alive| Best| Mean best| Exp so far| W time| rho|KenW| Qvar| +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ |x| 11| 35| 124| 32571228.00| 35|00:01:37| NA| NA| NA| |x| 2| 35| 147| 32589096.50| 65|00:01:21|+0.28|0.64|0.1066| |x| 1| 35| 102| 32841930.67| 95|00:01:21|+0.42|0.61|0.0852| |x| 3| 35| 102| 32917142.50| 125|00:01:21|+0.48|0.61|0.0727| |-| 4| 19| 102| 32911207.20| 155|00:01:23|-0.06|0.15|0.9874| |=| 7| 19| 113| 32914603.83| 169|00:00:38|-0.07|0.10|0.9949| |=| 9| 19| 113| 32888811.14| 183|00:00:38|-0.02|0.12|0.9456| |=| 8| 19| 113| 32873043.50| 197|00:00:37|+0.02|0.14|0.9046| |=| 5| 19| 113| 32870705.56| 211|00:00:38|+0.05|0.15|0.8924| |=| 10| 19| 113| 32891187.00| 225|00:00:37|+0.03|0.13|0.9091| |=| 6| 19| 118| 32883264.36| 239|00:00:37|+0.03|0.12|0.9045| |=| 12| 19| 118| 32868008.67| 258|00:00:54|+0.05|0.13|0.8860| +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ Best-so-far configuration: 118 mean value: 32868008.67 Description of the best-so-far configuration: .ID. algorithm localsearch alpha beta rho ants nnls q0 dlb rasrank elitistants time .PARENT. 118 118 ras 3 4.0526 2.5427 0.7522 17 20 NA 1 9 NA 5 113 # 2025-01-28 14:30:48 GMT: Elite configurations (first number is the configuration ID; listed from best to worst according to the sum of ranks): algorithm localsearch alpha beta rho ants nnls q0 dlb rasrank elitistants time 118 ras 3 4.0526 2.5427 0.7522 17 20 NA 1 9 NA 5 124 ras 3 3.1663 0.6971 0.8396 24 26 NA 1 19 NA 5 113 ras 3 4.1042 1.5310 0.8104 16 12 NA 1 24 NA 5 117 ras 3 4.1817 0.5324 0.8893 13 18 NA 1 23 NA 5 133 ras 3 3.1718 0.9297 0.8386 9 24 NA 1 23 NA 5 # 2025-01-28 14:30:48 GMT: Iteration 6 of 6 # experimentsUsed: 913 # remainingBudget: 87 # currentBudget: 87 # nbConfigurations: 11 # Markers: x No test is performed. c Configurations are discarded only due to capping. - The test is performed and some configurations are discarded. = The test is performed but no configuration is discarded. ! The test is performed and configurations could be discarded but elite configurations are preserved. . All alive configurations are elite and nothing is discarded. +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ | | Instance| Alive| Best| Mean best| Exp so far| W time| rho|KenW| Qvar| +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ |x| 13| 11| 113| 32697505.00| 11|00:00:32| NA| NA| NA| |x| 4| 11| 113| 32789761.00| 17|00:00:16|+0.55|0.77|0.4924| |x| 2| 11| 113| 32743042.67| 23|00:00:16|+0.62|0.75|0.4061| |x| 5| 11| 113| 32770282.50| 29|00:00:16|+0.62|0.72|0.4564| |-| 1| 6| 113| 32872017.00| 35|00:00:16|+0.24|0.39|0.6408| |!| 9| 6| 113| 32849023.33| 36|00:00:05|+0.31|0.42|0.6091| |=| 3| 6| 113| 32889232.57| 37|00:00:05|+0.18|0.29|0.6964| |=| 12| 6| 113| 32872605.38| 38|00:00:05|+0.04|0.16|0.7812| |=| 7| 6| 113| 32877034.22| 39|00:00:05|-0.05|0.07|0.8518| |=| 8| 6| 113| 32865597.80| 40|00:00:05|-0.06|0.05|0.8491| |=| 11| 6| 113| 32844551.82| 41|00:00:05|-0.04|0.06|0.8388| |=| 6| 6| 113| 32844854.83| 42|00:00:05|-0.06|0.03|0.8736| |=| 10| 6| 118| 32867735.77| 43|00:00:05|-0.05|0.03|0.8810| |=| 14| 6| 118| 32851875.71| 49|00:00:16|-0.05|0.03|0.8818| +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ Best-so-far configuration: 118 mean value: 32851875.71 Description of the best-so-far configuration: .ID. algorithm localsearch alpha beta rho ants nnls q0 dlb rasrank elitistants time .PARENT. 118 118 ras 3 4.0526 2.5427 0.7522 17 20 NA 1 9 NA 5 113 # 2025-01-28 14:33:27 GMT: Elite configurations (first number is the configuration ID; listed from best to worst according to the sum of ranks): algorithm localsearch alpha beta rho ants nnls q0 dlb rasrank elitistants time 118 ras 3 4.0526 2.5427 0.7522 17 20 NA 1 9 NA 5 113 ras 3 4.1042 1.5310 0.8104 16 12 NA 1 24 NA 5 149 ras 3 3.7080 0.4873 0.9452 8 11 NA 1 50 NA 5 117 ras 3 4.1817 0.5324 0.8893 13 18 NA 1 23 NA 5 124 ras 3 3.1663 0.6971 0.8396 24 26 NA 1 19 NA 5 # 2025-01-28 14:33:27 GMT: Iteration 7 of 7 # experimentsUsed: 962 # remainingBudget: 38 # currentBudget: 38 # nbConfigurations: 7 # Markers: x No test is performed. c Configurations are discarded only due to capping. - The test is performed and some configurations are discarded. = The test is performed but no configuration is discarded. ! The test is performed and configurations could be discarded but elite configurations are preserved. . All alive configurations are elite and nothing is discarded. +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ | | Instance| Alive| Best| Mean best| Exp so far| W time| rho|KenW| Qvar| +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ |x| 15| 7| 149| 32886640.00| 7|00:00:21| NA| NA| NA| |x| 10| 7| 149| 32935718.00| 9|00:00:05|+0.21|0.61|0.8607| |x| 14| 7| 118| 32827495.67| 11|00:00:05|+0.11|0.40|0.7721| |x| 13| 7| 149| 32825055.00| 13|00:00:05|+0.26|0.44|0.7264| |=| 11| 7| 113| 32781621.00| 15|00:00:05|+0.20|0.36|0.6939| |=| 2| 7| 149| 32759707.67| 17|00:00:05|+0.06|0.21|0.8239| |=| 12| 7| 149| 32751285.86| 19|00:00:05|+0.10|0.23|0.7552| |=| 7| 7| 149| 32771017.12| 21|00:00:05|+0.02|0.15|0.8085| |=| 5| 7| 149| 32787355.11| 23|00:00:05|+0.06|0.17|0.7630| |=| 3| 7| 149| 32819324.50| 25|00:00:05|+0.04|0.14|0.8019| |=| 4| 7| 149| 32831015.73| 27|00:00:05|+0.05|0.13|0.7921| |=| 1| 7| 149| 32874709.83| 29|00:00:05|+0.05|0.13|0.7786| |=| 9| 7| 113| 32854146.77| 31|00:00:05|+0.03|0.11|0.8026| |=| 6| 7| 149| 32859769.21| 33|00:00:05|+0.05|0.12|0.7833| |=| 8| 7| 149| 32855221.33| 35|00:00:05|+0.06|0.13|0.7587| +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ Best-so-far configuration: 149 mean value: 32855221.33 Description of the best-so-far configuration: .ID. algorithm localsearch alpha beta rho ants nnls q0 dlb rasrank elitistants time .PARENT. 149 149 ras 3 3.708 0.4873 0.9452 8 11 NA 1 50 NA 5 117 # 2025-01-28 14:35:06 GMT: Elite configurations (first number is the configuration ID; listed from best to worst according to the sum of ranks): algorithm localsearch alpha beta rho ants nnls q0 dlb rasrank elitistants time 149 ras 3 3.7080 0.4873 0.9452 8 11 NA 1 50 NA 5 113 ras 3 4.1042 1.5310 0.8104 16 12 NA 1 24 NA 5 118 ras 3 4.0526 2.5427 0.7522 17 20 NA 1 9 NA 5 117 ras 3 4.1817 0.5324 0.8893 13 18 NA 1 23 NA 5 124 ras 3 3.1663 0.6971 0.8396 24 26 NA 1 19 NA 5 # 2025-01-28 14:35:06 GMT: Stopped because there is not enough budget left to race more than the minimum (5). # You may either increase the budget or set 'minNbSurvival' to a lower value. # Iteration: 8 # nbIterations: 8 # experimentsUsed: 997 # timeUsed: 0 # remainingBudget: 3 # currentBudget: 3 # number of elites: 5 # nbConfigurations: 5 # Total CPU user time: 5397.903, CPU sys time: 78.97, Wall-clock time: 2855.561 # 2025-01-28 14:35:06 GMT: Starting post-selection: # Configurations selected: 149, 113, 118. # Pending instances: 0, 0, 0. # 2025-01-28 14:35:07 GMT: seed: 687542627 # Configurations: 3 # Available experiments: 3 # minSurvival: 1 # Markers: x No test is performed. c Configurations are discarded only due to capping. - The test is performed and some configurations are discarded. = The test is performed but no configuration is discarded. ! The test is performed and configurations could be discarded but elite configurations are preserved. . All alive configurations are elite and nothing is discarded. +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ | | Instance| Alive| Best| Mean best| Exp so far| W time| rho|KenW| Qvar| +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ |.| 8| 3| 118| 32752987.00| 0|00:00:00| NA| NA| NA| |.| 7| 3| 118| 32831948.00| 0|00:00:00|-0.50|0.25|1.1636| |.| 12| 3| 118| 32788030.67| 0|00:00:00|+0.17|0.44|0.7047| |.| 15| 3| 118| 32818929.25| 0|00:00:00|-0.08|0.19|0.7650| |.| 14| 3| 118| 32784282.40| 0|00:00:00|-0.20|0.04|0.8230| |.| 2| 3| 118| 32753987.33| 0|00:00:00|-0.10|0.08|0.7476| |.| 6| 3| 118| 32749294.86| 0|00:00:00|+0.00|0.14|0.6861| |.| 3| 3| 118| 32795767.12| 0|00:00:00|+0.07|0.19|0.6370| |.| 9| 3| 118| 32798232.11| 0|00:00:00|-0.03|0.09|0.6834| |.| 10| 3| 118| 32810925.60| 0|00:00:00|+0.02|0.12|0.6534| |.| 13| 3| 118| 32815792.45| 0|00:00:00|-0.04|0.06|0.6765| |.| 1| 3| 118| 32856770.08| 0|00:00:00|-0.07|0.02|0.7054| |.| 11| 3| 118| 32843955.00| 0|00:00:00|-0.08|0.01|0.7140| |.| 5| 3| 118| 32849549.21| 0|00:00:00|-0.07|0.01|0.7135| |.| 4| 3| 118| 32855859.00| 0|00:00:00|-0.07|0.00|0.7072| |=| 16| 3| 113| 32847622.06| 3|00:00:10|-0.06|0.00|0.7069| +-+-----------+-----------+-----------+----------------+-----------+--------+-----+----+------+ Best-so-far configuration: 113 mean value: 32847622.06 Description of the best-so-far configuration: .ID. algorithm localsearch alpha beta rho ants nnls q0 dlb rasrank elitistants time .PARENT. 113 113 ras 3 4.1042 1.531 0.8104 16 12 NA 1 24 NA 5 75 # 2025-01-28 14:35:17 GMT: Elite configurations (first number is the configuration ID; listed from best to worst according to the sum of ranks): algorithm localsearch alpha beta rho ants nnls q0 dlb rasrank elitistants time 113 ras 3 4.1042 1.5310 0.8104 16 12 NA 1 24 NA 5 118 ras 3 4.0526 2.5427 0.7522 17 20 NA 1 9 NA 5 149 ras 3 3.7080 0.4873 0.9452 8 11 NA 1 50 NA 5 # Total CPU user time: 5414.035, CPU sys time: 79.259, Wall-clock time: 2866.547 # Best configurations (first number is the configuration ID; listed from best to worst according to the sum of ranks): algorithm localsearch alpha beta rho ants nnls q0 dlb rasrank elitistants time 113 ras 3 4.1042 1.5310 0.8104 16 12 NA 1 24 NA 5 118 ras 3 4.0526 2.5427 0.7522 17 20 NA 1 9 NA 5 149 ras 3 3.7080 0.4873 0.9452 8 11 NA 1 50 NA 5 # Best configurations as commandlines (first number is the configuration ID; listed from best to worst according to the sum of ranks): 113 --ras --localsearch 3 --alpha 4.1042 --beta 1.531 --rho 0.8104 --ants 16 --nnls 12 --dlb 1 --rasranks 24 --time 5 118 --ras --localsearch 3 --alpha 4.0526 --beta 2.5427 --rho 0.7522 --ants 17 --nnls 20 --dlb 1 --rasranks 9 --time 5 149 --ras --localsearch 3 --alpha 3.708 --beta 0.4873 --rho 0.9452 --ants 8 --nnls 11 --dlb 1 --rasranks 50 --time 5 # Testing of elite configurations: 5 # Testing iteration configurations: TRUE # 2025-01-28 14:35:18 GMT: Testing configurations (in no particular order): 31 9 22 61 37 41 75 113 117 92 102 93 118 124 133 149 algorithm localsearch alpha beta rho ants nnls q0 dlb rasrank elitistants time 31 acs 3 4.2717 0.2871 0.9362 6 17 0.9180 1 NA NA 5 9 mmas 3 3.4904 4.9746 0.5959 13 33 NA 0 NA NA 5 22 as 3 2.0842 2.1621 0.7506 25 26 NA 1 NA NA 5 61 ras 3 3.6680 2.4269 0.8717 96 14 NA 1 89 NA 5 37 acs 3 3.8927 1.6181 0.6188 6 35 0.8587 1 NA NA 5 41 ras 3 3.3674 5.9837 0.7399 12 40 NA 1 95 NA 5 75 ras 3 4.5827 1.6918 0.8180 13 10 NA 1 15 NA 5 113 ras 3 4.1042 1.5310 0.8104 16 12 NA 1 24 NA 5 117 ras 3 4.1817 0.5324 0.8893 13 18 NA 1 23 NA 5 92 ras 3 4.9632 4.1134 0.8790 7 9 NA 1 26 NA 5 102 ras 3 4.3582 2.8458 0.7508 9 19 NA 1 86 NA 5 93 ras 3 4.1500 2.0082 0.8067 10 14 NA 1 32 NA 5 118 ras 3 4.0526 2.5427 0.7522 17 20 NA 1 9 NA 5 124 ras 3 3.1663 0.6971 0.8396 24 26 NA 1 19 NA 5 133 ras 3 3.1718 0.9297 0.8386 9 24 NA 1 23 NA 5 149 ras 3 3.7080 0.4873 0.9452 8 11 NA 1 50 NA 5 # 2025-01-28 15:14:38 GMT: Testing results (column number is configuration ID in no particular order): seeds 31 9 22 61 37 41 75 113 117 92 102 93 118 124 133 149 1t 2046302398 33098140 33239149 33129540 33109131 33201943 32976999 32852645 32833572 32784419 32875049 32777346 32820444 32780508 32923537 32871318 32805333 2t 827626108 32954263 32987443 33039177 33056915 32887007 32780271 32567740 32607386 32661338 32605363 32590997 32514163 32595732 32744276 32605256 32534045 3t 978077451 33313400 33446626 33376846 33415690 33387989 33266730 33183722 33097013 33077258 33090479 33197150 33032552 33062375 33147446 33042611 33083261 4t 1348269770 33021447 32897210 33165710 33045317 32904477 33068992 32790813 32824418 32867503 32755126 32926804 32786122 32782807 32923497 32806207 32739172 5t 243391689 32944849 33053706 33204377 33003552 32978293 33010655 32819909 32919999 32726458 32790603 32821804 32783965 32760191 32934499 32771715 32921237 6t 1588668262 32690232 32914113 32953951 32875620 32928834 32963678 32717001 32651566 32595506 32617190 32749972 32626934 32573375 32602356 32606513 32692338 7t 423372130 33026281 33211159 33194496 33148754 33213814 33170418 32939764 32989898 32895022 32943900 32836359 32892448 32884365 33044123 32877345 32974480 8t 652122407 33071578 33255488 33244533 33055455 33040474 33118029 32937849 32891239 32898150 32855158 32925102 32884699 33028371 32911267 32946823 33040194 9t 317806051 33033130 33463764 33244304 33208355 33143082 33190011 32874390 32849625 32913245 32933978 32994331 32925936 32887713 33015638 32747034 32812335 10t 747706567 32823698 32971836 32989937 32984342 32843959 32913020 32706959 32756032 32756123 32765789 32614143 32719902 32728343 32857316 32700242 32868859 11t 50933476 33002325 33221790 33224313 33062546 32955083 33021896 32967532 32857920 32912572 32883301 32937237 32842348 33001929 32989392 32982455 32855097 12t 1739405996 32929209 32941102 32856706 33027699 32825183 32820039 32587056 32598397 32561786 32506831 32581913 32643712 32519887 32636143 32595651 32504224 13t 1957203093 32788760 32959449 33065012 32989738 32823546 33016071 32661707 32638211 32607545 32684776 32619844 32841206 32575148 32759115 32739988 32687250 14t 2032826826 32831117 33062008 32923055 32885600 32905397 32973393 32562020 32627456 32606172 32803159 32630108 32648439 32832893 32796466 32593158 32662798 15t 1206236755 32893521 33041201 33083686 33068523 33049312 32984492 32699498 32633428 32733892 32745855 32724253 32731723 32677060 32928235 32739696 32665586 16t 763851782 33159005 33378520 33316334 33228824 33111096 33182851 32900293 33104319 32979011 32964066 32928362 33018843 32927669 33144143 32968303 32984175 17t 349953428 33536691 33491757 33602689 33536763 33359766 33523909 33238122 33127750 33073259 33175404 33148729 33110551 33272981 33264666 33129590 33190112 18t 897599183 32616720 32867845 32930559 32859355 32788206 32793763 32566177 32556947 32550547 32516828 32705214 32629259 32429610 32573661 32525739 32593153 19t 1400970198 33203949 33268680 33373564 33183534 33177561 33272717 33039458 32908001 32889287 32964453 33028715 32959378 32917060 32981568 33004692 33082049 20t 1782793253 32920981 33086354 33144045 32971140 33023459 33035694 32692608 32735331 32742981 32743665 32755353 32721542 32768699 32951370 32864674 32829495 21t 1660845956 33060600 32970450 33071257 32906030 33161795 33015612 32682340 32780886 32764194 32784629 32666204 32614331 32811076 32868182 32716060 32684395 22t 290410408 32969734 33240958 33158577 33077389 33089633 33099623 32791908 32898456 32846216 32885236 32964063 32795078 32902746 32869519 32718033 32851606 23t 1495337927 33058071 33043020 33172331 33129424 32900129 33131786 32825696 32716477 32857494 32876402 32902149 32739830 32773697 32750528 32780460 32807391 24t 1364123825 32936519 33175700 33216248 33127283 33119860 33094549 32862215 32855988 32975309 32893142 32914054 32846935 32862968 33047339 32841995 33002341 25t 915618954 33221437 33340356 33324125 33267849 33350663 33061580 32938672 32895342 32896788 32923194 33025517 32940073 32916386 33044500 33003287 32811036 26t 1066725944 33309234 33413626 33378515 33377976 33100601 33284699 33064354 32973237 32962348 33040082 32977859 32956226 33102636 33196297 33077908 33060265 27t 1012167492 33116237 33212972 33144225 33187194 33123043 33089640 32775498 32747432 32728200 32870284 32843344 32744397 32722944 33026740 32873759 32812007 28t 405072419 32944552 33183445 33260768 33190870 33030890 33070083 32809618 32824751 32825054 32804746 32709328 32709832 32788312 32880987 32854797 32763291 29t 343646063 33287500 33435506 33446599 33374038 33291798 33367811 33077554 33075585 33179998 33127231 33178973 33074167 33081719 33222527 32937251 33268605 30t 2062824562 32892958 33144194 33079207 33019337 33016877 32994210 32758852 32730821 32705369 32776245 32768151 32684416 32675816 32801298 32757259 32647672 31t 1315404608 33362159 33420071 33375785 33466081 33364889 33308878 33035136 33050152 32977172 33184455 33100022 33086417 33007821 33060061 33201336 33111088 32t 718135261 33080581 33148836 33178597 33209213 33075259 33090111 32808179 33073806 32824862 32862792 32910569 32809320 32832661 32916787 33013878 32868270 33t 1756600186 32912578 33140256 33172058 33122171 33082595 32980922 32945569 32778835 32836996 32805414 32804116 32776818 32789844 32877954 32783432 32825277 34t 1084471193 33058926 33089505 33039589 33036017 32921606 32947257 32721148 32709831 32685964 32693570 32687319 32690564 32707820 32743104 32705370 32621660 35t 1163321042 32483594 32752995 32803194 32828186 32771137 32690789 32506559 32396349 32366269 32479742 32416846 32376150 32458495 32567191 32361580 32418296 36t 1318942249 32670682 32971766 32936095 32913150 32717950 32698178 32527927 32575185 32547154 32572291 32474010 32532734 32627421 32750209 32653173 32612411 37t 424612740 32701106 33002602 32819010 32800764 32830202 32743558 32373724 32380917 32391978 32440573 32431067 32539230 32508876 32564875 32388837 32421787 38t 507331628 33045967 33268562 33103741 33098088 32963127 32987509 32829137 32679588 32726309 32747132 32804395 32698469 32753361 32922700 32763021 32692158 39t 1640400685 33006811 33199652 33170436 33106016 33188579 33118545 32864846 32896914 32870078 32916441 32911873 32885323 32959799 33071726 32922589 32905922 40t 1364449617 32623835 32874869 32761751 32828312 32583441 32723534 32457904 32452281 32404861 32344336 32366792 32410717 32465983 32524448 32533048 32335468 41t 752897771 33218038 33453910 33489170 33450105 33482242 33400984 33084008 33033849 33008163 33011304 32990212 32965457 33100836 33246586 33109029 33052769 42t 1025182031 33308181 33416507 33348551 33353589 33078035 33194525 32937731 33096404 32873261 32856868 32900612 32859590 32917712 33107581 32950684 32954981 43t 696942227 32794732 33077276 32835350 33004223 32905747 32813582 32543692 32458725 32599183 32662034 32659345 32519506 32665713 32802829 32620304 32619233 44t 1699723808 32859196 33014447 33005264 33098533 32851130 32863953 32647020 32620048 32544882 32629933 32675907 32701896 32691564 32808682 32804464 32680084 45t 410855221 32973357 33262500 33202569 33189160 33055825 33044003 32646518 32844211 32823867 32790803 32658075 32812383 32899920 32935307 32971815 32763179 46t 1502438571 32936173 33260372 33144182 33085343 33088220 33037123 32752761 32735861 32749653 32834556 32824232 32788860 32760354 32856958 32786384 32726020 47t 1580656301 32814152 33179790 32882078 32970050 32863158 32909574 32521834 32706354 32582668 32607947 32648027 32492769 32721354 32846821 32790095 32548698 48t 1099243655 32864590 33060995 33010305 32994239 32896624 32941142 32602521 32629683 32612903 32561942 32651405 32725952 32587778 32799409 32722572 32540867 49t 1611639012 33389112 33558167 33487694 33554983 33454508 33472490 33183976 33256869 33202243 33186646 33124595 33205308 33311120 33235674 33293977 33179711 50t 1429022466 32683888 32708498 32842868 32898058 32851506 32778780 32610345 32425351 32506621 32515430 32592616 32513821 32588071 32672271 32437732 32531407 # 2025-01-28 15:14:38 GMT: Finished testing irace/vignettes/section/0000755000176200001440000000000014736527040015021 5ustar liggesusersirace/vignettes/section/irace-options.tex0000644000176200001440000000000014736537750020316 0ustar liggesusersirace/vignettes/section/irace-options.Rnw0000644000176200001440000005106614736537750020306 0ustar liggesusers \subsection[General options]{General options} \begin{description} \defparameter[h]{-{}-help}{help}{}% Show the list of command-line options of \irace. \defparameter[v]{-{}-version}{version}{}% Show the version of \irace. \defparameter[c]{-{}-check}{check}{}% Check that the scenario and parameter definitions are correct and test the execution of the target algorithm. See \autoref{sec:execution}. \defparameter[i]{-{}-init}{init}{}% Initialize the working directory with the template config files. This copies the files in \code{\$IRACE_HOME/templates} to the working directory without overwriting the files with the same names as those of the template files. \defparameter[s]{scenarioFile}{scenario}{./scenario.txt}% File that contains the scenario setup and other irace options. All options listed in this section can be included in this file. See \IRACEHOME{/templates/} for an example. Relative file-system paths specified in the scenario file are relative to the scenario file itself. \defparameter{execDir}{exec-dir}{./}% Directory where the target runner will be run. The default execution directory is the current directory. \begin{xwarningbox} The execution directory must exist before executing \irace, it will not be created automatically. \end{xwarningbox} \defparameter[l]{logFile}{log-file}{./irace.Rdata}% File to save tuning results as an \aR dataset. The provided path must be either an absolute path or relative to \parameter{execDir}. See \autoref{sec:output r} for details on the format of the \aR dataset. \defparameter[q]{quiet}{quiet}{0}% Reduce the output generated by irace to a minimum. \defparameter{debugLevel}{debug-level}{0}% Level of information to display in the text output of \irace. A value of 0 silences all debug messages. Higher values provide more verbose debug messages. Details about the text output of \irace are given in \autoref{sec:output text}. \defparameter{seed}{seed}{}% Seed to initiallize the random number generator. The seed must be a positive integer. If the seed is \code{""} or \code{NULL}, a random seed will be generated. \defparameter{repairConfiguration}{}{}% User-defined \aR function that takes a configuration generated by \irace and repairs it. See \autoref{sec:repairconf} for details. \defparameter{postselection}{postselection}{1}% Perform a postselection race after the execution of \irace to consume all remaining budget. Value 0 disables the postselection race. See \autoref{sec:postselection}. \defparameter{aclib}{aclib}{0}% Enable/disable AClib mode. This option enables compatibility with \texttt{GenericWrapper4AC} (\url{https://github.com/automl/GenericWrapper4AC/}) as \parameter{targetRunner} script. \end{description} \subsection[Elitist irace]{Elitist \irace} \begin{description} \defparameter[e]{elitist}{elitist}{1}% Enable/disable elitist \irace. In the \textbf{elitist} version of \code{irace}~\citep{LopDubPerStuBir2016irace}, elite configurations are not discarded from the race until non-elite configurations have been executed on the same instances as the elite configurations. Each race begins by evaluating all configurations on a number of new instances. This number is defined by the option \parameter{elitistNewInstances}. After the new instances have been evaluated, configurations are evaluated on instances seen in the previous race. Elite configurations already have results for most of these previous instances and, therefore, do not need to be re-evaluated. Finally, after configurations have been evaluated on all these instances, the race continues by evaluating additional new instances. The statistical tests can be performed at any moment during the race according to the setting of the options \parameter{firstTest} and \parameter{eachTest}. The elitist rule forbids discarding elite configurations, even if the show poor performance, until the last of the previous instances is seen in the race. The \textbf{non-elitist} version of \irace can discard elite configurations at any point of the race, instances are not re-used from one race to the next, and new instances are sampled for each race. \defparameter{elitistNewInstances}{elitist-new-instances}{1}% Number of new instances added to each race before evaluating instances from previous races (only for elitist \irace). \begin{xwarningbox} If \parameter{deterministic} is \code{TRUE} then the number of \parameter{elitistNewInstances} will be reduced or set to \code{0} once all instances have been evaluated. \end{xwarningbox} \defparameter{elitistLimit}{elitist-limit}{2}% Maximum number of statistical tests performed without successful elimination after all instances from the previous race have been evaluated. If the limit is reached, the current race is stopped. Only valid for elitist \irace. Use \code{0} to disable the limit. \end{description} \subsection[Internal irace options]{Internal \irace options} \begin{description} \defparameter{sampleInstances}{sample-instances}{1}% Enable/disable the sampling of the training instances. If the option \parameter{sampleInstances} is disabled, the instances are used in the order provided in the \parameter{trainInstancesFile} or in the order they are read from the \parameter{trainInstancesDir} when\parameter{trainInstancesFile} is not provided. For more information about training instances see \autoref{sec:training}. \defparameter{softRestart}{soft-restart}{1}% Enable/disable the soft-restart strategy that avoids premature convergence of the probabilistic model. When a sampled configuration is \emph{similar} to its parent configuration, the probabilistic model of these configurations is soft restarted. The soft-restart mechanism is explained in the \irace paper~\citep{LopDubPerStuBir2016irace}. The similarity of categorical and ordinal parameters is given by the hamming distance, and the option \parameter{softRestartThreshold} defines the similarity of numerical parameters. \defparameter{softRestartThreshold}{soft-restart-threshold}{1e-04}% Soft restart threshold value for numerical parameters. \defparameter{nbIterations}{iterations}{0}% Maximum number of iterations to be executed. Each iteration involves the generation of new configurations and the use of racing to select the best configurations. By default (with 0), \irace calculates a \emph{minimum} number of iterations as $\Niter = \lfloor 2 + \log_{2}\Nparam \rfloor$, where $\Nparam$ is the number of non-fixed parameters to be tuned. Setting this parameter may make \irace stop sooner than it should without using all the available budget. We recommend to use the default value. \defparameter{nbExperimentsPerIteration}{experiments-per-iteration}{0}% Number of runs of the target algorithm per iteration. By default (when equal to 0), this value changes for each iteration and depends on the iteration index and the remaining budget. Further details are provided in the \irace paper~\citep{LopDubPerStuBir2016irace}. We recommend to use the default value. \defparameter{minNbSurvival}{min-survival}{0}% Minimum number of configurations needed to continue the execution of each race (iteration). If the number of configurations alive in the race is not larger than this value, the current iteration will stop and a new iteration will start, even if there is budget left to continue the current race. By default (when equal to 0), the value is calculated automatically as $\lfloor 2 + \log_{2}\Nparam \rfloor$, where $\Nparam$ is the number of non-fixed parameters to be tuned. \defparameter{nbConfigurations}{num-configurations}{0}% The number of configurations that will be raced at each iteration. By default (when equal to 0), this value changes for each iteration and depends on \parameter{nbExperimentsPerIteration}, the iteration index and \parameter{mu}. The precise details are given in the \irace paper~\citep{LopDubPerStuBir2016irace}. We recommend to use the default value. \defparameter{mu}{mu}{5}% Parameter used to define the number of configurations to be sampled and evaluated at each iteration. The number of configurations will be calculated such that there is enough budget in each race to evaluate all configurations on at least $\mu + \min(5,j)$ training instances, where $j$ is the index of the current iteration. The value of $\mu$ will be adjusted to never be lower than the value of \parameter{firstTest}. We recommend to use the default value and, if needed, adjust \parameter{firstTest}and \parameter{eachTest}, instead. \end{description} \subsection[Target algorithm parameters]{Target algorithm parameters} \begin{description} \defparameter[p]{parameterFile}{parameter-file}{./parameters.txt}% File that contains the description of the parameters of the target algorithm. See \autoref{sec:target parameters}. \end{description} \subsection[Target algorithm execution]{Target algorithm execution} \begin{description} \defparameter{targetRunner}{target-runner}{./target-runner}% Executable or \aR function that evaluates a configuration of the target algorithm on a particular instance. See \autoref{sec:runner} for details. \defparameter{targetRunnerLauncher}{target-runner-launcher}{}% Executable that will be used to launch the target runner, when \parameter{targetRunner} cannot be executed directly (e.g., a Python script in Windows). \defparameter{targetCmdline}{target-cmdline}{\{configurationID\} \{instanceID\} \{seed\} \{instance\} \{bound\} \{targetRunnerArgs\}}% Command-line arguments provided to \parameter{targetRunner} (or \parameter{targetRunnerLauncher} if defined). The substrings \code{\{configurationID\}}, \code{\{instanceID\}}, \code{\{seed\}}, \code{\{instance\}}, and \code{\{bound\}} will be replaced by their corresponding values. The substring \code{\{targetRunnerArgs\}} will be replaced by the concatenation of the switch and value of all active parameters of the particular configuration being evaluated. The substring \code{\{targetRunner\}}, if present, will be replaced by the value of \parameter{targetRunner} (useful when using \parameter{targetRunnerLauncher}). Example: <>= targetRunner="./real_target_runner.py" targetRunnerLauncher="python" targetCmdLine="-m {targetRunner} {configurationID} {instanceID}\ --seed {seed} -i {instance} --cutoff {bound} {targetRunnerArgs}" @ \defparameter{targetRunnerRetries}{target-runner-retries}{0}% Number of times to retry a call to \parameter{targetRunner} if the call failed. \defparameter{targetRunnerTimeout}{target-runner-timeout}{0}% Timeout in seconds of any \parameter{targetRunner} call (only applies to \code{target-runner} executables not to R functions), ignored if 0. \defparameter{targetRunnerData}{}{}% Optional data passed to \parameter{targetRunner}. This is ignored by the default \parameter{targetRunner} function, but it may be used by custom \parameter{targetRunner} functions to pass persistent data around. \defparameter{targetRunnerParallel}{}{}% Optional \aR function to provide custom parallelization of \parameter{targetRunner}. See \autoref{sec:parallel} for more information. \defparameter{targetEvaluator}{target-evaluator}{}% Optional script or \aR function that returns a numerical value for an experiment after all configurations have been executed on a given instance using \parameter{targetRunner}. See \autoref{sec:evaluator} for details. \defparameter{deterministic}{deterministic}{0}% Enable/disable deterministic target algorithm mode. If the target algorithm is deterministic, configurations will be evaluated only once per instance. See \autoref{sec:training} for more information. \begin{xwarningbox} If the number of instances provided is less than the value specified for the option \parameter{firstTest}, no statistical test will be performed. \end{xwarningbox} \defparameter{parallel}{parallel}{0}% Number of calls of the \parameter{targetRunner} to execute in parallel. Values 0 or 1 mean no parallelization. For more information on parallelization, see \autoref{sec:parallel}. \defparameter{loadBalancing}{load-balancing}{1}% 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. See \autoref{sec:parallel}. \defparameter{mpi}{mpi}{0}% Enable/disable use of \pkg{Rmpi} to execute the \parameter{targetRunner} in parallel using MPI protocol. When \parameter{mpi} is enabled, the option \parameter{parallel} is the number of slave nodes. See \autoref{sec:parallel}. \defparameter{batchmode}{batchmode}{0}% Specify how irace waits for jobs to finish when \parameter{targetRunner} submits jobs to a batch cluster: \code{sge}, \code{pbs}, \code{torque}, \code{slurm} or \code{htcondor} (\parameter{targetRunner} must submit jobs to the cluster using. for example, \code{qsub}). See \autoref{sec:parallel}. \end{description} \subsection[Initial configurations]{Initial configurations} \begin{description} \defparameter{configurationsFile}{configurations-file}{}% File containing a table of initial configurations. If empty or \code{NULL}, \irace will not use initial configurations. See \autoref{sec:initial}. \begin{xwarningbox} The provided configurations must not violate the constraints described in \parameter{parameterFile} and \parameter{forbiddenFile}. \end{xwarningbox} \end{description} \subsection[Training instances]{Training instances} \begin{description} \defparameter{trainInstancesDir}{train-instances-dir}{}% Directory where training instances are located; either absolute path or relative to current directory. See \autoref{sec:training}. \defparameter{trainInstancesFile}{train-instances-file}{}% File that contains a list of instances and optionally additional parameters for them. See \autoref{sec:training}. \begin{xwarningbox} The list of instances in \parameter{trainInstancesFile} is interpreted as file-system paths relative to \parameter{trainInstancesDir}. When using an absolute path or instances that are not files, set \code{trainInstancesDir=""}. \end{xwarningbox} \defparameter{blockSize}{block-size}{1}% Number of training instances, that make up a 'block' in \parameter{trainInstancesFile}. Elimination of configurations will only be performed after evaluating a complete block and never in the middle of a block. Each block typically contains one instance from each instance class (type or family) and the block size is the number of classes. The value of \parameter{blockSize} will multiply \parameter{firstTest}, \parameter{eachTest} and \parameter{elitistNewInstances}. \end{description} \subsection[Tuning budget]{Tuning budget} \begin{description} \defparameter{maxExperiments}{max-experiments}{0}% The maximum number of runs (invocations of \parameter{targetRunner}) that will be performed. It determines the maximum budget of experiments for the tuning. See \autoref{sec:budget}. \defparameter{minExperiments}{min-experiments}{}% The minimum number of runs (invocations of \parameter{targetRunner}) that will be performed. If this option is set, then \parameter{maxExperiments} is ignored and the actual budget will depend on the number of parameters and \parameter{minSurvival}, but it will not be smaller than this value. See \autoref{sec:budget}. \defparameter{maxTime}{max-time}{0}% The maximum total time for the runs of \parameter{targetRunner} that will be performed. The mean execution time of each run is estimated in order to calculate the maximum number of experiments (see option \parameter{budgetEstimation}). When \parameter{maxTime} is positive, then \parameter{targetRunner} \textbf{must} return the execution time as its second output. This value and the one returned by \parameter{targetRunner} must use the same units (seconds, minutes, iterations, evaluations, \ldots). See \autoref{sec:budget}. \defparameter{budgetEstimation}{budget-estimation}{0.05}% Fraction (smaller than 1) of the budget used to estimate the mean execution time of a configuration. Only used when \parameter{maxTime} $> 0$. See \autoref{sec:budget}. \defparameter{minMeasurableTime}{min-measurable-time}{0.01}% Minimum time unit that is still (significantly) measureable. \end{description} \subsection[Statistical test]{Statistical test} \begin{description} \defparameter{testType}{test-type}{}% Specifies the statistical test used for elimination: \begin{itemize} \item[] \code{F-test} (Friedman test) \item[] \code{t-test} (pairwise t-tests with no correction) \item[] \code{t-test-bonferroni} (t-test with Bonferroni's correction for multiple comparisons) \item[] \code{t-test-holm} (t-test with Holm's correction for multiple comparisons). \end{itemize} We recommend to not use corrections for multiple comparisons because the test typically becomes too strict and the search stagnates. See \autoref{sec:stat test} for details about choosing the statistical test most appropriate for your scenario. \begin{xwarningbox} The default setting of \parameter{testType} is \code{F-test} unless the \parameter{capping} option is enabled in which case, the default setting is \code{t-test}. \end{xwarningbox} \defparameter{firstTest}{first-test}{5}% Specifies how many instances are evaluated before the first elimination test. \begin{xwarningbox} The value of \parameter{firstTest} must be a multiple of \parameter{eachTest}. \end{xwarningbox} \defparameter{eachTest}{each-test}{1}% Specifies how many instances are evaluated between elimination tests. \defparameter{confidence}{confidence}{0.95}% Confidence level for the elimination test. \end{description} \subsection[Adaptive capping]{Adaptive capping} \begin{description} \defparameter{capping}{capping}{}% Enable the use of adaptive capping. Capping is enabled by default if \parameter{elitist} is active, $\parameter{maxTime} > 0$ and $\parameter{boundMax} > 0$. When using this option, \irace provides an execution bound to each target algorithm execution (See \autoref{sec:runner}). For more details about this option See \autoref{sec:capping}. \defparameter{cappingAfterFirstTest}{capping-after-first-test}{0}% If set to 1, elimination due to capping only happens after \parameter{firstTest} instances are seen. \defparameter{cappingType}{capping-type}{median}% Specifies the measure used to define the execution bound: \begin{itemize} \item[] \code{median} (the median of the performance of the elite configurations) \item[] \code{mean} (the mean of the performance of the elite configurations) \item[] \code{best} (the best performance of the elite configurations) \item[] \code{worst} (the worst performance of the elite configurations). \end{itemize} \defparameter{boundType}{bound-type}{candidate}% Specifies how to calculate the performance of elite configurations for the execution bound: \begin{itemize} \item[] \code{candidate} (performance of candidates is aggregated across the instances already executed) \item[] \code{instance} (performance of candidates on each instance). \end{itemize} \defparameter{boundMax}{bound-max}{0}% Maximum execution bound for \parameter{targetRunner}. It must be specified when capping is enabled. \defparameter{boundDigits}{bound-digits}{0}% Precision used for calculating the execution time. It must be specified when capping is enabled. \defparameter{boundPar}{bound-par}{1}% Penalty used for PARX. This value is used to penalize timed out executions, see \autoref{sec:capping}. \defparameter{boundAsTimeout}{bound-as-timeout}{1}% Replace the configuration cost of bounded executions with \parameter{boundMax}. See \autoref{sec:capping}. \end{description} \subsection[Recovery]{Recovery} \begin{description} \defparameter{recoveryFile}{recovery-file}{}% Previously saved \irace log file that should be used to recover the execution of \irace; either absolute path or relative to the current directory. If empty or \code{NULL}, recovery is not performed. For more details about recovery, see \autoref{sec:recovery}. \end{description} \subsection[Testing]{Testing} \begin{description} \defparameter{-{}-only-test}{only-test}{}% Run the configurations contained in the file provided as argument on the test instances. See \autoref{sec:testing}. \defparameter{testInstancesDir}{test-instances-dir}{}% Directory where testing instances are located, either absolute or relative to current directory. \defparameter{testInstancesFile}{test-instances-file}{}% File containing a list of test instances and optionally additional parameters for them. \defparameter{testNbElites}{test-num-elites}{1}% Number of elite configurations returned by irace that will be tested if test instances are provided. For more information about the testing, see \autoref{sec:testing}. \defparameter{testIterationElites}{test-iteration-elites}{0}% Enable/disable testing the elite configurations found at each iteration. \end{description} irace/vignettes/fig1u-acotsp-instances.pdf0000644000176200001440000010420514736526233020344 0ustar liggesusers%PDF-1.4 % 1 0 obj << /Pages 2 0 R /Type /Catalog >> endobj 8 0 obj << /ExtGState 4 0 R /Font 3 0 R /Pattern 5 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Shading 6 0 R /XObject 7 0 R >> endobj 10 0 obj << /Annots [ ] /Contents 9 0 R /Group << /CS /DeviceRGB /S /Transparency /Type /Group >> /MediaBox [ 0 0 720 468 ] /Parent 2 0 R /Resources 8 0 R /Type /Page >> endobj 9 0 obj << /Filter /FlateDecode /Length 11 0 R >> stream xKslǑ;ǯa~ K]ݲѕv{P^HMIDE;O!fՒ(ȌɝO?|pLJߩ4_ܚ~Wb99GmI}5kq_/Iov_2=;TVcn%>ߒJLώ%7m\O-2ow\\j/;JGĘ"_r߱ͅ{hn o~-п/gWW7W &Og#sO~߿wNr}ϟo?9}.z۱4u_fӫ~{?/V_$Ϛڿ",ϙ<_N_/ cz9~:}O/F-k˟OߚwKw7ҫ{-*|˯߉)Ur;Q<-'Bv"y/%N"/+/_QV}^'u/%cǁne箫ۤ$)ԴZ~eL˼/ I$AŧCtsjZ6Z|lsk[kx΅|mQJ3=m1iܹcԣ 0w;%E]6WeW?ä{ٝnTỪ?_ͻ}t75-gM}w' O?u:D9r$O.3S|dߊhAWNI1WɆ8^ASY<j>x7םnZzR([2ăf}s%X?3rl#8C??}_?w jK>apBnI:"[Vϥ=/"D[Pd[-KEEH g_$E]yvO.B75_yK;-5`rB()w'cOթ\]i}RFa'a˰0s~nE4_] vopysu> :rv}r~7'Q'a=\]$ݙvOV.Ovô=a'a2eGwOvê;t}ݰ7뛎uk/>Ϸp9GAg'h gCm,ECD@Ϳ:}'j!iFvN?ìꋥ~Aؿ'+폧n|(cȧteHvtj.R.Q F{Rt:>:kѲDeh3/?Zgl2|Y*MBOqE]@-,TF4\$YNzr9̜]_RzXwZG|.Э7QvEpiztyX[^%}=;]zkt;#>ΒΑFZ.!ߪ`gIίW5g.wEb ]sԕrVOUy,v׋Betqmι>JWK}ΚnHp%$eGVüepIR!绹^X6ӟR,km9k /}^ ":S=tr{e+l/1v"MKZD ãzpջ`p6E5't$yeAdj +z5N']GIJ.m w5e#t1hf kmn B-rt$%C^Gi}Wu(Joy|ꓽl]ʛĘ[o+W1N.`Xzǁ J@?GX(1rpAMRO7K yՃ>L}=YW$^˂۽G 5R>Zh FA% BVC_}|邮Z5 ij,# PK m*{l}$;֦X5]H6vM4 h:H~vk5BsxRjZoDKk:9hרeՒ$i^\Mr9Y$Pl(8[׳ppz2s6MYp"KvYo®Ye]#q "HS;@&zy:>),S]ta}5/^"ܤ%#@JK灾s4zHRIF\/hz\Qïo&җ's 5>W2"lm(},Hžˣ'aSrO2S 2rYGD7m[ NFvI /벇52AeVT7A7i$.? <0a93NL%ϗJ H a ;(QZu)ȸHŰuO X0cZ,CɲR%a V$8~ϸɘ絗`|zY!A*e$@45`ٲ<V7k|}_"%a^ugm҄dȭP: &k5-DHBHɴXaYWbAu $V:0B2$׺lŗĭBеn?ⲵn9И,o:< QCD-,:A'#:ȇ֚2Fu|5tҕ]m)uQ1;NZN%87k2j~0:7X!cdNvX{"Idw =9)EzxdkCKNHtK yTZ!ޙ8|:N7}y>_o !? Z з<6p.xݮ`!p{ܭĐdrT q*Ro9 A6d.Z64 LqK_ۍ`{|s[]J;?/q=V&@nAk-(ItQh, ^2!Ă! D(BrG#(6`¤K^'qݲ`[c\ϰ^uhkAى_K?|8$ē[ JS@A+p bkA9,-5&.aYeUc_&"C4 Yo9.hSrI&1ޕe= lIC%cۨH /DgS;uRB7B XĮo`B ) Qtc6xIY"GlRu,I!v1< k!FF\g4I6fAg =  v?K I,ņdњkZ1LMs7O8hd 0t-u(  w؈BD * q#%&M:OlGg_zk]},B78CT7 E2 |QZΥ N!ƍaTqFP!VT:s)K'Hdx}qϸPhQ݄A5TS(Wjx< *  :QgL̆Ò.iZG G4!8$g hzɸLeŔugo"Nsg`CU5 (0PE$,ڄ*%j:UŲAJe`2#$ϵlHNZDJt8(Hk GIr;8l2$3Z pޘ/89F":aj$2`&d+&dEpĔ -0+kvkiFɼpѓNUH5`%Ĕ R. | Hq 57#~I܁0<[9:rM#uPbq_GA3 am(u!rB+h{ņJ}@Mgmv,DY/:)Tц2:AAض-o^xZjd?Jޏa `snhСÛAK$cDO"C(!RWStT1m)7 .un!8ґH[ dI$ShHtilI]]ƞc@JP&ALdh(m}29O xI6(Lj&ԸipxynSx^kt1 fXxse꺘 vgk#%Yai3D yMߏk>"7?уށ 1? N1Ξ{Hyj4ҥ5D TS,V\_LcS?Bh?3T%K/`S@?b}QMH,NHpxNI诟o龡UFP juZ-I 8zhr)^L$g4X=A^ PlH4Ɔsb! Ap{ڣ * z{TZwA%PF6# P<%a-/=Qǂ! !NV+h+ԉߵOg*m 5{I4rCT"\:x:7'Z6IH F$Ee$ ~rI= 0lxѰza^@ ng]Ѥ?V  EJQ+d1W6jLBuqdpt~Q="pGse7tg GG,POEpw`!ɧᲶxCܜp]=Rt7#/ ^bAԝHhHIBƝK!\Bp,4 b,$=#fו<0dKGC@hVY8Sj =- -DJPn5`Pu!`ܗ *6LBL/[R*ꛁFG:YzӒ!57dE  jvf:Fw=dYOv~l\~tܴ{8ױJWhBf.AqdxÊ?lwYdd]J{fH 6<־ |L)֜c*iPp2YHJs#кgG|Z:b-ljs_؅cn#P$&^ k` #mX5Lsb5%Y׬I>746u54MC $g(hŒ."1e12cTde7&lBgЂ2)7 u>5H p% H:9ZHmx:+(Iz 63/VА5F^,'>I[t!kTLZ-OLR*؟GJ%):] lk _YV†"r!$a*XU`@Rβ0jJC07dIJG;$$oiaDb(V#`md;8/(R ϣsS4'ɾA@yHy`֎JO䵻L"&' #hG,)Q[N@¦ dž!-Q,JkACS:!$cޠ:6=V'E Y7Mυ:a Qu`>E 6K }M )jբ]r{@UhC|F"qۼ_2ibI*Q3!  yC_F*Cд*Ep_O3 #RcxY:CӠ@|&+hʼnZsBU4JG@40_YZZZG0`:"bg_ Vɚ70}$|<"dEh) ݩ))1XCX7 a];浡Ux)f} K{q[nKBe4S4!t<auc^Ew`0Qpu ]FCǍJz.t qmOX[ 76 /oKT7Ǵy jQhNL@ْEd'ENLgg dLB[]hJ+?:bQy96D (tDH́n!ĢiuψװtZGneK8W+l4>)>X fNwib"*o/ђ,A"`ʮ7kMGZX26]fA&F;jdj?;MR- ZsU3hH rGkRC[}i]" Gg mA .uu~$壁u|d .eEhѰoI ;Bמ֜$Xb;-;gyȖ0;G ^%w;e>M/\pPQwݐŖ'Wj =!9Rߢ:6tDПb$)u!eF,xZ&= ;:j)2 +;?ժt{a!` KLA- Sހf( n%TO[vMR'9HiZ`Ѓ`p7Adt{EKV U[Ueܕ6"φ LS"i2c ;~Agn\{&M^9&>\HtJ4傁:p܉ Yu$KbuCo͂s.?%KUg5Vt=X/dæ Cuk0TxB{]9& HQV!7BHA]Be\}neYY2CT.}7B r+2,E7aDmL#/\4 *㊥k4qҀQi`GPf(!!י2\X2nh($j5C@48VFz2$;DC΍OEܷ]Z V/"LUײ)8 >_3]xa,;t66?uR_iC IdÆiSΓR:jI o AM@*ѻ\'Jv.lM2RY?eׯmxzRr% ־6~Z ; ›#ʊFe4hCgRfzY&tD)IB֮7b*f+M Nl]^ MRJ3DgzHڮ *HDÍܼ`ХL5zB2mJd` 0.::Lfs;dh+Ƙ!=YL&B]c#O#a>%T3U(6Xd^(R^k $5'[vs%Ԙ\4(L{Jqʈ4D^mMbζNIY,y<҆A8ۨǣIy 5Y)> x=~}Q uXOu \@c/C4Gja6I^R!8H]s _ `H*%DM0N#ݒ j+&#Bi)*:|:L3"TՆt0Z%֩u1>z(`~zdjX,M]TI<10Y, M[9HH:(Grm(~PwҚ wq3pC * ;d9Y>)OE( D5΋ޛdRa dWOYAOӿq6HB߆{ 4Ap &}o|::Q҃$VMvw\y⷏ŢNƹ!R u8s5M rjAʵb('G_Tѓ76꺔b`iR(Ӱݕ0s_Bp̖lEw#1cB6KM}P=Q'RS8 %=eg J! KO@ 2mKSZFK] ,DDǒ g$33g+880"Uz@6\g6((fN%x8Eh=b2!{)= ( [1M"Z2-imܿ%Z AÐʏ/Ov:*AY!w\9vAI2tR,w`d ͏#!paB!O7Ёvb]ap P#C9tZҬ[܊TK_#ߦ&dWEGbiS46̙0ɔ[&Ĺ tX Q0QJh\ sn ? jmft/ M#&b#[Z-jgY5#XMzm-Jk[Av/z>c'O6Y8Iښ:Qe"WVG}`r >UOi6`FiRhXeh&@{JB " H [?gnz V+yIk$<ѥJ7Ln42R1"krZmj0凗R0,G=a78bՂJ lP:z:baCnS}Ig|2B%"6"ԖYrKؤsvQi3 Mڐ$s&zYp YsuzM<?j(d !*E{Rg*gi_lh#-pa6CFU hioy9y1i]ILL{ ~l7#˹LrL Slel8՚B3TLdgB?FGdBjzΓL/ Rg&+,']f D-}H._K,@͒eG6M(dZ) p A w R!F9RՑIDYRƉY@YbQ)5*7/v𱐨WWQƔu|iNT1y)犷ɣ h|;son~2 GnQA7rS_d<Չr st}eF8 7sMiIx.ăo̍90S5~>ցyNsӠb liR<]= !r A\z=DRWIBX-ՙFhPYO˗-8[K+׹j0jzvZZ|Tuq㫠La1Cѻn%#p??yɫb؞w(ެ598gURRE`%~ޫۚ~3+9b'iꨱ}U TQzgn?9~$W;ϙT(l@ fE~DJ˒7|F$F޳u>G>L#gޕ\A jWrڃX 6/oBr:0<3/3 iYg"p#RvmPtǺj : QRw!O.$˰ Y#ة,w]llxꮃ4h/.T n+DC8+g؍BN ؊Nk4 :AR;ǀkDֵRB׶9-V J%gGDţ4<#$[Hj :ß<2xF,E_2 pKRkaxG< WCa,̢G92q&#;դCR WK {P0l+չxaCRa3˭#pIp#[fFq4%V=X'(#(!(2!Sq9ҽsL]Z썲oh<}+aKE‡9wȏe.ft7-iU1Ajp`^Q2\N?1MoBSBޑoaί)Ȼ |_yuԘAlsfᛁ%y&S+7qS9e֟t8W>+$rx.Q'S!%Ipc kF‚S'ݖsxC?MtGJ|2WԶ_s'IU|*›*V)H \YJp@*yQTZՖ>W=/#`Z=vwW*bkaL}t{%aLCro,i@+hY3[&xrEb;:XwcGBmq^HFS O1X e OK:UT)IQ>eHf_Cemz.szN)={rޜ.E< >̹N+!i GYOYny-! \Zgl鞻AfKdzܞ]=:Yv9Iy'e3zH$Ȩ>,MrՋ.tf S;d Dɥ`Od̽9sMP^f#m͖P]/c#| PL5Q)|=9 ]PyM Nߤ1feDqZsz#zT\C0`a$DPH~b1 CIHej -̥t76OtZ3Zzϓ,|S8ZzrVG,Kf/B /Ya1ܿcG],#Ѳr7@9 [Cky1rx{d^z^b}3:kKvm}I?.,Nk6M⛓P&@ FFPʗ8l`f9awmi2g`} MDZn}lontFD{ތ=cVnoo Pwo6#OA+eWyK 3:]/69 F屋4HMWɧdhJẀntwΙ'ϙKS@; |ҽ~TTt_7kn4I lۥYH8Nǻ+F ,!rd(+m 5{tNtVwmE h0YKKN)ڕ <8%.v-8a%{7_6_>rP`d~{]CU iꩪ_-à}AS誋kw9 N9@T`ak="lLbh{Cӥ.VL,x{k|<68%g$/m>_$Rk9o[2RWWuLpί֯BudDq[gLCK2RUbu'0mTS %*Tb3`R 1%v &̟[Z>ؗQB{  fu;t%>y%^r~N'<y}|-nQR)K?] l5 P 6R Y#qB׋/'/S];Fy PR2؞宽[)(9~7ݵܧ_`9 si]r,x;~ ywNjMƩVl-z+-V !3%I /!<:Ez`9W( ;h!ܓ )<#W,>x纭2ZJTڿ=e(g/PK 9  Fb[S;mK/tz7h}c*xs.YDFK+@U~Z-.r5OUVˡh@'WJٙ"bK3Ch7{%CwW;nV'}f$IO&XALu:{' =o9*)q7BHnw/pfnS0{76fHE(']zp +`&@)]%tUr2'5*t+d-i8Yk_Jw} #hvȋ)K.Ěd^e}9Zdƽ %} U?܍Htfrdn2nY|-Y;~{DjPmݼT(3{fe'^ ?2t,{&Y };ڦ-(Mb0- ޅiaɞ_틝6z-D~.#QF4p:2\*دB{*ok9!OLu}nFfx*k)Ppikgjo]e4o6 4n>(֙Cjz-0] .ա[fs5ZNCfUHN]WfcAJ.*=" M+6)Cɉ|mW'5T{Ox١bæ?e Z2e(Q%%117 *ܚt;#\xk;wQ+}ϓ};A8M+ddG;<>io\klݲ6.t6/@Kʠ\ 0%!a׼.f[BUn|ūW&eU <̩fe\iBF&qqaw|<^~%|ky5YsEPʍzKx=A0tR)tȥ7g5"VrS[NQ:݁Ht,n\$,%TmpO4_7&>hnAF`fFnƸ;`B>W¦y֞RuQP)il"G |*!OK1.ޡw =* G&-LR Pl5:lع mKrҽ}# po8* ivt!iQP Uro |%~i7` efUnp?ӆSPxqA[znC̈P9XJ\ArW`[&Awlh?4(\NLT&9ׇu# 6`9f'P7V;2Ub֎VR!_$3!.c$꩞>kHz-KW;)0ұgN֣$=qSUXBX7/c$xaeɾW1 > [!5wiMFnSpz,9r;IvSD>(S75[ܾi_\/Q-m͡y-GB mof@CC?񭍖8xu ^ޖBnS)\7Qcz&Q 7r6L31߼7\ypW7˶r&4m~Ɇ з 9,F7$%?5ɮJZX{IķF7暯/w?\YwIk yG fG8*-m"{h{ߝrSo:IBAN ?| B{85!{?~os 9N?>6g*ǃ Q5\eޖ\t~|ւPGcVf7+_Y]3hª?>.%퟿ : ^/fuy 툴R~^zz>EŚBg?E.)z37^i1\Cei~1tb*D>7>EʼdpMov*rܶvzvKjTVlteF_S+ ?_N姇[7xfY䍊{J7]i$(%%oov#O?'O `]?M;\ǒ4iLױO_{xطaz|yݗ]~~tm~!fvhj*^>.Rg{cߞ>3˜hI 1nu%l_> stream x5Q;r1} ] 3og3JFfa =؈ėoYf~'Y)QTEX!Yjs#Sr&>'b8Ύf01h9f=!#n4U i[ZSEȺ)Z[=- c _ĜE'~3尒4#15όO}>hw/̈́LHœƘ1T$?г>0TG endstream endobj 21 0 obj << /Filter /FlateDecode /Length 49 >> stream x34U0P F )\@> 4XiLEWD endstream endobj 22 0 obj << /Filter /FlateDecode /Length 227 >> stream xEK!CGp;.:l%(3{Lnd;0sJlLN\#T޼頉{DΣY jg.7ɭFiwyHT9λi Yմlk?*MlVWw'K~0%?Q|]7g'W&kyI_ʣq)$_SS{ginV9 endstream endobj 23 0 obj << /Filter /FlateDecode /Length 304 >> stream x=;0 C{Ȍd'>2VI(/u< i& b;w؞D/)ϡ+E:Ū0[M*K õ}74uK hY pu;Gw5<TQ!OJ|<(!\{0FS@\^BAjI'> stream xEQIr! ++Nͩk,3IN`k1i-x!́/|M4|72:pO.ܗ˂g–WTw/]H}w~fd{H͐:B4&!=#2V,s)R([.ꕶN;#o#JvMl:˶.:$vaqj!"uc5N"v0Q$gq&M]9yV*9>^Ƅtc׶i_>}?\" endstream endobj 25 0 obj << /Filter /FlateDecode /Length 230 >> stream x5QIn0 .AO ``hKxA[֌]< 'Q^{ .o8|^Z9O2 D`@ai'ώH5YN_KK(O~ J.kO8'O [WLcD.A|!]'@;綟Wuu)O645I"%Ҹ[{TS endstream endobj 26 0 obj << /Filter /FlateDecode /Length 227 >> stream x5O;! 9.m`ϳT/od HDG&^S=Ä=Ba$5븛ſ]3 b0řvX'qNcDlc]L(!0%)}9:Nb.a}1WOdP=&ՍI4^2`a$YNkGQ"1'2Ҝb :; *s>h][M endstream endobj 27 0 obj << /Filter /FlateDecode /Length 245 >> stream xEPC1 =`,{wHۿ=JFp!Z?ZK oGFA= 3A΄@xFnvpμ39Zpә\'mBITqTqLύׁlӑ!KI%&~S*)[*EH䁓M4,?Cb̠Q0qGuٜ9-L|X&Q)2>'\N}䢥Uޑ"ۡW%Qէ<Y> endstream endobj 28 0 obj << /Filter /FlateDecode /Length 392 >> stream x=RKn1)@Mr[T /1 %?ꒈ3L~r]Qljg!.6Xr_rњbO/ȴTXVݣC(-װr{d`Jn@CHYAaPl( WԬtb ) ٠[]aP[[xfޑ3qYk?=Q2QMg|2RCgB'`$Ip#A 1qOl)V;ޒ{,\L'ib?lK\+E(~Aq|XdDw#h% 0xyDhDԎ=(ͱ&{ǫvzcw. endstream endobj 29 0 obj << /Filter /FlateDecode /Length 133 >> stream xMA0~tzJ-6팀03 endstream endobj 30 0 obj << /Filter /FlateDecode /Length 247 >> stream xMQmD1 \ky R]oC /)%K [UC?13,=?TPbht/"+ߏe s`&4`oI&ռ3d‰ATwM,3V7: lx%D`r Z`Q+ tĺv7C/਺x} K{,|BL;wI#fR:=b}@e+ (\* endstream endobj 31 0 obj << /Filter /FlateDecode /Length 90 >> stream xMA "OPDtz_NE5jK02kP)U0\ 2IL{qIqzz"X endstream endobj 32 0 obj << /Filter /FlateDecode /Length 338 >> stream xERKr0\ 3gNWp:<  2=eH6dWdՐFD)򹼖\nJ?72ͮЪG6F5+# CzVQdv!:Sp,Cu)mA#o<rLn[ :[m@ s` )(UI­\';PЪt79`Òho>F, f1H'N=q:őpI8@/ u:eMžBRq"n]ElO ?*3b Ԓ枾?9 endstream endobj 33 0 obj << /Filter /FlateDecode /Length 68 >> stream x32P0P4& f )\@B.H  %[B4AXf&fI8"ɴ endstream endobj 34 0 obj << /Filter /FlateDecode /Length 45 >> stream x32P0P4& f )\V.L,іp "} endstream endobj 35 0 obj << /Filter /FlateDecode /Length 255 >> stream xEK D#> stream x240S065U276r,#s# $`Ad_ endstream endobj 37 0 obj << /Filter /FlateDecode /Length 161 >> stream xEK CBGG|tJ■!M@w'/mK >[ x6n5uVhR}ith6s+ fz:rGp_Gdf)|Q]dcnk]3s: endstream endobj 38 0 obj << /Filter /FlateDecode /Length 320 >> stream x5Qq0 54sۿ @;a@dJ\UGM>`!S֖{&UF!}W2j]* UYFp&I8d Rӿccz endstream endobj 39 0 obj << /Filter /FlateDecode /Length 214 >> stream x=PC1= |7˥m$B6BLɔ:ʒ)O>Kbnd6%*E/% }ՖC4h9~ 3*K6p*3 mtV[ Ф`׶ r " JMrR=ot-N=Dkq: DpFjtaŲC5=kz7hGt4CָR endstream endobj 40 0 obj << /Filter /FlateDecode /Length 80 >> stream xE 0D{`~&f( JpO{:2Sa ,S`5FR죰n_uzS*Ovvq= endstream endobj 41 0 obj << /Filter /FlateDecode /Length 49 >> stream x36P0P040F@B!H Y@8&+ & endstream endobj 42 0 obj << /Filter /FlateDecode /Length 157 >> stream xEC1DsUA wJo-%S'"h0yM%V,&rAJ1xN1븨ufihW3=5'M<[ }@8IP1}bv">G)#qbn fW7y endstream endobj 43 0 obj << /Filter /FlateDecode /Length 332 >> stream x-R9$1 ~`LtIUls#h/#xE=f۴[iGiK,W ;BjW0wy.2meDkag؏]e8*Jl !2J'Qw\I2[E™w2;yNE{ kF9+%|6vzrYɩHHӺ NKؖߗ3| endstream endobj 44 0 obj << /Filter /FlateDecode /Length 68 >> stream x336S0P0 F )\@>,́,# .C c0mbl`fbdY 1 r endstream endobj 45 0 obj << /Filter /FlateDecode /Length 317 >> stream x5RKrC1ۿSpΘ}tj'+-@B./YK~%ۥW%B>R-G- Q=2'":xa>N)x_xN;2$KMH=I+4t~&+s{rj X+)$=Hr7VސWg%&&MܕBXtLX㰄*aՃM5fcdxLP} #GMv²[6!D3,($Nc$ Ұ9 9e, mh%zМaמE[{ endstream endobj 46 0 obj << /Filter /FlateDecode /Length 17 >> stream x36P0C. endstream endobj 47 0 obj << /Filter /FlateDecode /Length 131 >> stream xE ! CT>՞0ABA";06Ѣ76իc,zRV鐇Pi0QąYLCaΘȖ2MlTv<e~ma, U^ ?KwUBS0 endstream endobj 48 0 obj << /Filter /FlateDecode /Length 338 >> stream x5R9@ } ] v͜~߆_ CVie!U-.Im W%ڥ Pt,6˯JH+kLwIi"Eo7o}=@.^ AS(i|Ъc(ew 4<3}(~_K&(? _osџa`Ś}@*z`yT endstream endobj 49 0 obj << /Filter /FlateDecode /Length 248 >> stream x-Q9AzBsˑ C :-qPO+Uwu9HTM]vf5,?c 7zqxLu5{kOfP2+qSușO \ ȹeƌ#M!RH&3AQ~#aU#j \Ks4;<9GW +ET<pC7ҹ^s0XM7/=[ endstream endobj 50 0 obj << /Filter /FlateDecode /Length 171 >> stream xMMB!0h\vK!CGFGx1 2&^$ m;,1',#`kܛG ADko5u~~]ԥu# ȎP p=&T)8T bSUhV=^; endstream endobj 51 0 obj << /Filter /FlateDecode /Length 72 >> stream x50{6X`KEoC|N/Z #pu#])Ec΂q_H1F=#O_p} endstream endobj 52 0 obj << /Filter /FlateDecode /Length 210 >> stream x5P C1g dVukm;aBXȔy)K>:L." u%ʚ +`p&^7`i5tႦ.B%|u{OxjrvC` jMX> /FirstChar 0 /FontBBox [ -1021 -463 1794 1233 ] /FontDescriptor 17 0 R /FontMatrix [ 0.001 0 0 0.001 0 0 ] /LastChar 255 /Name /DejaVuSans /Subtype /Type3 /Type /Font /Widths 16 0 R >> endobj 17 0 obj << /Ascent 929 /CapHeight 0 /Descent -236 /Flags 32 /FontBBox [ -1021 -463 1794 1233 ] /FontName /DejaVuSans /ItalicAngle 0 /MaxWidth 1342 /StemV 0 /Type /FontDescriptor /XHeight 0 >> endobj 16 0 obj [ 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 318 401 460 838 636 950 780 275 390 390 500 838 318 361 318 337 636 636 636 636 636 636 636 636 636 636 337 337 838 838 838 531 1000 684 686 698 770 632 575 775 752 295 295 656 557 863 748 787 603 787 695 635 611 732 684 989 685 611 685 390 337 390 838 500 500 613 635 550 635 615 352 635 634 278 278 579 278 974 634 612 635 635 411 521 392 634 592 818 592 592 525 636 337 636 838 600 636 600 318 352 518 1000 500 500 500 1342 635 400 1070 600 685 600 600 318 318 518 518 590 500 1000 500 1000 521 400 1023 600 525 611 318 401 636 636 636 636 337 500 500 1000 471 612 838 361 1000 500 500 838 401 401 500 636 636 318 500 401 471 612 969 969 969 531 684 684 684 684 684 684 974 698 632 632 632 632 295 295 295 295 775 748 787 787 787 787 787 838 787 732 732 732 732 611 605 630 613 613 613 613 613 613 982 550 615 615 615 615 278 278 278 278 612 634 612 612 612 612 612 838 612 634 634 634 634 592 635 592 ] endobj 19 0 obj << /C 20 0 R /I 21 0 R /R 22 0 R /a 23 0 R /b 24 0 R /c 25 0 R /d 26 0 R /e 27 0 R /eight 28 0 R /f 29 0 R /five 30 0 R /four 31 0 R /g 32 0 R /i 33 0 R /l 34 0 R /m 35 0 R /n 37 0 R /nine 38 0 R /o 39 0 R /one 40 0 R /period 41 0 R /r 42 0 R /s 43 0 R /seven 44 0 R /six 45 0 R /space 46 0 R /t 47 0 R /three 48 0 R /two 49 0 R /u 50 0 R /v 51 0 R /zero 52 0 R >> endobj 3 0 obj << /F1 18 0 R >> endobj 4 0 obj << /A1 << /CA 0 /Type /ExtGState /ca 1 >> /A2 << /CA 1 /Type /ExtGState /ca 1 >> /A3 << /CA 0.8 /Type /ExtGState /ca 0.8 >> >> endobj 5 0 obj << >> endobj 6 0 obj << >> endobj 7 0 obj << /F1-DejaVuSans-minus 36 0 R /P0 12 0 R /P1 13 0 R /P2 14 0 R /P3 15 0 R >> endobj 12 0 obj << /BBox [ -2.5 -2.5 2.5 2.5 ] /Filter /FlateDecode /Length 36 /Subtype /Form /Type /XObject >> stream x3T2P5R\.##񸜸 endstream endobj 13 0 obj << /BBox [ -3.1494897428 -3.1494897428 3.1494897428 3.1494897428 ] /Filter /FlateDecode /Length 138 /Subtype /Form /Type /XObject >> stream xmA!E |հטͤIQ4!y7qzZjVIYKSDvK(N.lb"`ԩ`ԫi0>7<>p;cOY`ʡV endstream endobj 14 0 obj << /BBox [ -4.0166247904 -4.0166247904 4.0166247904 4.0166247904 ] /Filter /FlateDecode /Length 49 /Subtype /Form /Type /XObject >> stream x3T2P5P5364332U3 rpn.T4 endstream endobj 15 0 obj << /BBox [ -4.6785548508 -4.0843608999 4.6785548508 4.8833001327 ] /Filter /FlateDecode /Length 105 /Subtype /Form /Type /XObject >> stream xm10w^kǾ%*eua%8TQjJKdg60d+SeB"BB]6xxԹduՇ'/R() endstream endobj 2 0 obj << /Count 1 /Kids [ 10 0 R ] /Type /Pages >> endobj 53 0 obj << /CreationDate (D:20210416181907-03'00') /Creator (Matplotlib v3.3.3, https://matplotlib.org) /Producer (Matplotlib pdf backend v3.3.3) >> endobj xref 0 54 0000000000 65535 f 0000000016 00000 n 0000033572 00000 n 0000032305 00000 n 0000032337 00000 n 0000032479 00000 n 0000032500 00000 n 0000032521 00000 n 0000000065 00000 n 0000000385 00000 n 0000000208 00000 n 0000021487 00000 n 0000032614 00000 n 0000032780 00000 n 0000033085 00000 n 0000033300 00000 n 0000030871 00000 n 0000030671 00000 n 0000030210 00000 n 0000031924 00000 n 0000021509 00000 n 0000021814 00000 n 0000021935 00000 n 0000022235 00000 n 0000022612 00000 n 0000022922 00000 n 0000023225 00000 n 0000023525 00000 n 0000023843 00000 n 0000024308 00000 n 0000024514 00000 n 0000024834 00000 n 0000024996 00000 n 0000025407 00000 n 0000025547 00000 n 0000025664 00000 n 0000025992 00000 n 0000026162 00000 n 0000026396 00000 n 0000026789 00000 n 0000027076 00000 n 0000027228 00000 n 0000027349 00000 n 0000027579 00000 n 0000027984 00000 n 0000028124 00000 n 0000028514 00000 n 0000028603 00000 n 0000028807 00000 n 0000029218 00000 n 0000029539 00000 n 0000029783 00000 n 0000029927 00000 n 0000033632 00000 n trailer << /Info 53 0 R /Root 1 0 R /Size 54 >> startxref 33789 %%EOF irace/vignettes/irace-scheme.pdf0000644000176200001440000005432714157104332016400 0ustar liggesusers%PDF-1.4 % 4 0 obj << /Length 39 /Filter /FlateDecode >> stream x+2T0BC]C#S]SC\.}\C|@.l( endstream endobj 3 0 obj << /Type /Page /Contents 4 0 R /Resources 2 0 R /MediaBox [0 0 380 239] /Parent 6 0 R /Group 5 0 R >> endobj 1 0 obj << /Type /XObject /Subtype /Form /FormType 1 /PTEX.FileName (./irace-scheme-simple.pdf) /PTEX.PageNumber 1 /PTEX.InfoDict 7 0 R /BBox [0 0 595 842] /Group 5 0 R /Resources << /Font << /F1 8 0 R/F2 9 0 R>> /ProcSet [ /PDF /Text ] >> /Length 4301 /Filter /FlateDecode >> stream x[ˎ% W:t  ĉ;'dd5y!o!aս31>XEQɛÿt$C:C8~q!{Hgr|C|:}qFu ͝Թ0vF/K˅;υsw΅*(^qD}O}.5ޥ;bzn1ljsG2o٬ͫ3Gݴ |? m_Um3K%di>.ӛ쎚ӛn> +tkK{ޔvr_-fn1 +odYcoMtZ((7KRo :P^\U30"^)66q6~^'N2v2˅{L_׌1߈afTΨDz8>\׌R4/#PQ! F-V'jwB%Top dy7Ob;Ur7 XX4b ZilmVnA#}$[aaS$` LK%G&%L)|ӎm9+d:XB!M5RF)(ӘH*ׄȭ`7 =o&fST,|K11P:1 6iރشa0jսlTePB*MFqүbr4||xW]'#X2؅%#v#Dyuf'D;QB}@'u%8S9l`o!;r $ewG1-e,H4)a=eƦ^ F7PX;Ɯ!'8~>֝+vs= e=MjГs(0_ NQs-V PxE*=|e!'-櫖M`4`FOFCJXN &w\p7pw>_5nlpg[ V<5\^3r `wʣyBr N7 ݱ{L}]F P*n\qmn`&@0M =Rᣖ92(ȓcȈh]KS8%#P:@p:32ox^2QԣCAdo24q'8K653`ub酉g{Κn߿Bt$t^4d+i qhVW桛1(G,.RӠ3cV܅ƗB;wwqL1{NyS;C_pRi8ə OQg G$.t_*&׽}zy3>)X9^M}bGKVDJ( N }nPwca߿~~w7A¤0 ؝`'O$c+B]㑌Se"08J둎x#QG)'ɳc:S<[1쎠7;ocNh #!iR n?hDs×ry̶/ 6=jA\oϗ$ X;OwI{iyq=_25nKSNIɽ*t7Hjf9Ӝc&𬖫%Y`']^2b씆C*yP|l-Kr Bpc#ǖrz ))t P߹R(`ڕ⹿RH9V'b༿z'+!^ t0hjPgӢ A=.K6\`쨊uq3.+㞊 D#i+gM6E7f"4iu.vx]!XEf'5̮I8͠Шe7LMF3RdB{Ǻ Qsnj8꧔Wa`*#4vzZӸ48S&1b[ F T@AǺȭEV+' F#q&>҈3:>#^zj!i<ϋX0=xDltF_Ŋ!((r<" +((R^n=hFy46`HCX ciup݁:}X7k#/H2/e_5(~&+W1 Ò ̪00',̀yA(;М0l),1H͆ 0*t+wL8&V'ذwϠ&Q&Ԝ8*SrρA YFN&d`uӝ6hG}AAS8qݴd f,>nH?136΄ACYZ:șj"F"*,*jܘ8{z3 L ;rTT{G{diFG< xE>6n!7({;R;8$j2Nް`{ FxmX2Jh0 &*0w[h "iㆥ> $#Vcn&l0Qatm083dX׊ESޑ2n2d̢M t( } >8-ae0%t\.IiSK!ARqf\6ZѩcVSǚ-"]?%P#TQQv8s 5tnQ./JK-eSMQG;Gɜinjٟ9.=r_Fl4(!&Q\(ͅDIc]Po|Z)K)q(tTP8=n0Jjt;id\Дb;2O^&YL[+.dVlʈoA)%Ju#07q (QȠp/P (A&ٕ.K+F;< V:Y댫mxGPi.F+rj;,Qdy`Dệ>/r=Gj{o&܈&D?Z0-Z7ODSr\p*IɎ?)Tsl)F` endstream endobj 7 0 obj << /Creator /Producer /CreationDate (D:20160720164806+01'00') >> endobj 8 0 obj << /Type /Font /Subtype /TrueType /BaseFont /BAAAAA+LiberationSans /FirstChar 0 /LastChar 18 /Widths [ 365 610 333 556 222 556 556 500 277 500 556 666 833 556 722 556 277 556 722] /FontDescriptor 10 0 R /ToUnicode 11 0 R >> endobj 9 0 obj << /Type /Font /Subtype /TrueType /BaseFont /CAAAAA+LiberationSans-Italic /FirstChar 0 /LastChar 17 /Widths [ 365 500 556 222 500 277 722 222 277 556 542 277 333 556 556 556 333 333] /FontDescriptor 12 0 R /ToUnicode 13 0 R >> endobj 10 0 obj << /Type /FontDescriptor /FontName /BAAAAA+LiberationSans /Flags 4 /FontBBox [ -203 -303 1049 910] /ItalicAngle 0 /Ascent 905 /Descent -211 /CapHeight 910 /StemV 80 /FontFile2 14 0 R >> endobj 11 0 obj << /Length 305 /Filter /FlateDecode >> stream x]n0~ CMBH$f8tQiCj8޾OJ=Y,١96քՏ{ciyWc̹6*ܭWCX<ۏUŲb > endobj 13 0 obj << /Length 302 /Filter /FlateDecode >> stream x]Mk0 >v; !Ц-8!~mC#}%eusl ٫U jx 2smTG鯆α,zy 04˒eo6?^x`ًGƸ9*}: %ײѱl¼?)5j\w \1_MdQ*TKqN\$&q./ )FQy:$>PԿ&}|$Dgʟ"KA^#. { ƻqxi=cntJ7nL endstream endobj 14 0 obj << /Length 16 0 R /Filter /FlateDecode /Length1 11508 >> stream xzkxS׵#%ɖ:?u,ǀe6l [,$$D^GـcAqG ߍN-h+P#VC;Ip } 9h/c_Eo( L$or[<3Qǧ;Z7nhq7_FWvjQ#VWUVZ|Yͺ ?/לmJ3uڄ^Vq,C0*7;9[f.R@B 9onEL]RmwHJIiQjTD7 uf2ㅶLc"=}r^PMi%7#w;:?Cp:74I-[e@Ü*3[߱ [lwOP XK{POY$,2馜Nr-pwa66l^c||+@< ҩ0?gfBC> shukd\ Um-7l`/ ԙӒys_D]w¸ܴq*7G"QF4ULelvzaÊV$ѵ<@oܼZ rW"jMk@2[3eZ$|`cgTZLjtL{B%J(B5mDg^SBP)4 $B-MRhYJC10AcGelk0Q1f9ּU)xiɞq]t$n}\TjdV}Vjf˦T/2@4P2Gg*곘l}^>suP@V^K.<~+\q#y<Γ?Yg;o~z="ZD HdgrNt q^JKԩP;l-mevr+)g1^l_ $K"064f &aMejjUMfKQͼqzxVV祲QHFRǻb Xx[e9%+Xo*fU혡!zېĕh01)%'jq-eYfWvKaJE gػ f0AYn`Z0a}bm1alXf3te̖ٖz, x5ātzClȜdC ߝlyZRjykM1 _Y 9Fk2߽˼i[?gvke֮\[X&dlYJx.` L-GBR<.cKYFR'XVG e~'yxbR5Sq}NYYSMF!ph^ܑBV}lYiR=_uNx/g?ZG0yh3?#c]yNɍq43t\gR*s:x]|iLxNrtIxeD-el}t֒aY@b*R9#Ldltv>Z@^m\3sjK\IEXr}7lin3smBP=HrY6V?&#$YD\ԃʘbm|6TS%\I c*'G98&Ch 5 rҀpsk8@ rH\]NN** xf%PJꉦ `Y)ZVpa+] rvteФ`J;ޠJZ!Pa&%OsCU[-=ai+%+{WLRO݊ esz3ZrjQS~:7fe![ |hmw)}7w3oܬDoImaVHޗ4fĊ ( hi{<ȃ oH [f&+S|u O7Ⱥ㩓c^gu.~w_pKڬ{–u;V[:ۚVec{Z,FɿִEWtؔ/߶ȱ+ڻJ* tmM{ķk+W\ x?b1+@J>Ǿ̲Ź)RHJ\| s #N ıjn, kU8G In&k4`]vljݖi3Σ\o.'ۓѴLV.'?;25_=?>tmC"]Kh2ã72XpL ZSN23L>aB"4A͘fF!Nӭ@Pk4;!T=(B-0oN2t^"Y2uq=45*,Nʔkx nD!aĿ_$9kv޷b߻S6K?StM.,]vͤ3k01>2y>A2$wXZJVfT[+ 84KQ} [>Zv[;HJ"F\̍#]W/n{vǵk ~|liok%WDVҥ[m8 g-ΊlD&ȵ~bpT ܔ\~KOͿiN [0m;~>ry߾i߾z}󁾊㎿c.W˶8Z#RըpP %/!SM!Ѹ6GbLra5DפksJsqr؅J.No0ieEK#eX:^,em|{WO8k[M;JkI's;)uN ==ߣq` {KLLR=6 dPp.]"86#'Ǎj%U 1FiS(cȜQɈ$0NWi1N3aeF@N3VÅJ #'c-+Ws QʑJJ.V@ p0z[)IqHp툒Wjj$c֮Xr8hz ØW~ya!YWy( L9 6d`.#F*A>tP'D s ~ 3c c;y3f}cP71>YĬ;ccҗ%}~RBK¤pB`ҙN2jVGl]t6=uq V㉜y$V[wgMO6^В d⠅sO5^"O{5n)iw\~ýHsG>cV_\=|?}ԥ";x7:۬2s uG]5sQ͟y6| +y `Ch31:O ob R%Ly&*"0c4@eo#\%d0wos˹*Mc\1UHBΨ 1PMv/ڍ!0Aj @0u49ȓǣ WApyQn±`]Xq( ǣ~> '16MGa(LPS, Ea-aGa/Fa5?Ga *dGa-agp,Z~( >Pm 8: EBYiJaOpy%BPUXc_7g6߰y ^!@Շ@6_ zHv-?ojk 9%h ?G(TzQ)K7`A8u{z $Oۅae?YȌ[;!F=4;Ю[ߊ2򌬘3K۔~"^ =~)޶*~e~C?@Gg?JӚ[n˾H0_xm4+㘕71ױp.4Yhb5uuU|3WO?2NX3郙+3Wgiƾ9L3J?L̴7iaQnһi5]mO|.4_cZϟO,s95 w3'gY<7F㵫1cLdyZ,333++d˯ˎx/STԥSp(tuMHiߑ#G''ؓ8816A&' |p0stCLMCrSNkKL .\f2u3,eL[Z--Dj)_Z HnL.3N|yIƜ8,Mm2m #l2i.>-ڴڀvB{E;U;vU@89|ONnXΫ74j6һ)ȨsKOt$ 3N5Px^8 QS) bp.8zo8 Y"A( @>Ci[!7Bǟ endstream endobj 15 0 obj << /Length 17 0 R /Filter /FlateDecode /Length1 11236 >> stream xyxSו=~Zz- ۲-Y-Ho [&e,ABh&BB ]%M@"C8@'o4v2[N~|{}5Igfgog9s9sKL 2EX" E&B!97!vcaJ&v|#!\!J~辡5FOeF+O KOQ*)f%̇#8ƓhQ%؊أ$!c}$Jb(yy?ODʭ'}GFbh;!ɴGAv"N#3@^N> 5}-HuF%S/=,T3EKV,a|F3~vTX%"cOo|īT#cU=6߷?.[{yEҺ-K{{e]{斮Zrl|Wспe ׹RD:10U8*XfZ2=}k=_tqM _@ <(-kM&;hjlX6˄ x]r ҚmUV5fk|";w.&у}Yu}?u\=SUˋd8XlBʎU6-e|ԱI@amXqO>_)y宾&|Vў jp~6uVڲKJ6?JP7њ]UK3u䖙nMK66X7;0  /Ybjq6Cφ0'&^a3W0+2]mk"րm,_'dE\Q)C|) 2pBnaW$SfvpCUNO >. AR\QON辭cu [4f5lƭݏֻpKHyAE,(0ukl5n73^4*I%+5djk9`O9,MNtzNkcͩ͋F \e&n10H.A\jтZ!x7^9^/ފr!^C|J¥pywD?Pށgq*wy^iĖ\W?7VeJl2Bl7;yIP3j#ՠ){3C!ބFQü:h՝]q:]a.)# s(bϷ,h)'zuy6#} cI#5;L^kU饭o_?߽ z Bo-K폭\m psHqJfҢd'Uh@C2\ 5͎ c<2w.~W9(6ɧzzBhJY#|yϾ~7I*~$OZ_36~" +M}#;kX,/;b0([ĆQQfxtrotL\hji(ň F"$Fbbgd<ޒ 4IT]L1|1uSt2NIeeoP_P 9OD'82.vumDt=!C[F[;G-Hv#ۀr#L"W/%»I@]//(_1c)KIȲS#n蔹_ yq Fld}wpȲ-)1Ӟ߅R`Phf_0O4F'1JcxjWsLO1Gۆel]Kʐnie24&d?F9߉/e|O{h\^C{1/ǀNѻЙowQxZ!y&CGeoԝ#8#ݩiڽ,r=/ƚd7x;"i%n^y8kp&\ H\O.'7؍7ݸ|' ץm'O]WֿE*_ֿ^vktͷ"p-cokv| }a | Տk~Vdk⭩XivbGclfe@xuf2Ͽj΂ 5Vi._qU''3$;30/|9]xl,=;gZϜ>s }*VM<pY%5nÛ@.}ov!*:<~?»>ar 85~}$`-?"a@-FKt4c#c )=Wx5N2 z&UxKE%q|!6wtr>["3c0P?vߎwA *h ~ @DP]XgA%U}[*֭-ΪqzJ;Ƃֳ̉Hv\AgBGtvFjZ ]m n,T nՆ?r;J9 /gBjkK@јx 3sevnCVP/l xV!& i7+)30 tuz> endobj 2 0 obj << /XObject << /Im1 1 0 R >> /ProcSet [ /PDF ] >> endobj 6 0 obj << /Type /Pages /Count 1 /Kids [3 0 R] >> endobj 18 0 obj << /Type /Catalog /Pages 6 0 R >> endobj 19 0 obj << /Producer (pdfTeX-1.40.16) /Creator (TeX) /CreationDate (D:20160720164946+01'00') /ModDate (D:20160720164946+01'00') /Trapped /False /PTEX.Fullbanner (This is pdfTeX, Version 3.14159265-2.6-1.40.16 (TeX Live 2015/Debian) kpathsea version 6.2.1) >> endobj xref 0 20 0000000000 65535 f 0000000250 00000 n 0000021747 00000 n 0000000133 00000 n 0000000015 00000 n 0000021686 00000 n 0000021812 00000 n 0000004853 00000 n 0000005023 00000 n 0000005262 00000 n 0000005504 00000 n 0000005706 00000 n 0000006084 00000 n 0000006298 00000 n 0000006673 00000 n 0000014287 00000 n 0000021644 00000 n 0000021665 00000 n 0000021869 00000 n 0000021919 00000 n trailer << /Size 20 /Root 18 0 R /Info 19 0 R /ID [ ] >> startxref 22186 %%EOF irace/vignettes/examples.Rdata0000644000176200001440000000073014746200372016145 0ustar liggesusersTN18!H\z R8l Pc@'d6k.#zOCqP ^<`LovyǛԡQ#PoZG?6@XPt\)ZOp,W+^.o `$LGZr1~T<)vxwOp+R)ٔc}PdSXʋ3fePJۡ qRU\zTCؤ"AТjh<m $ (']ҫ[@OBNL1# T֏mD*ˋt})LU'fV13̾'pf˥({)6o+~\0]'*irace/vignettes/irace-package.bib0000644000176200001440000004253714745735066016533 0ustar liggesusers @preamble{{\providecommand{\MaxMinAntSystem}{{$\cal MAX$--$\cal MIN$} {Ant} {System}} } # {\providecommand{\rpackage}[1]{{#1}} } # {\providecommand{\softwarepackage}[1]{{#1}} } # {\providecommand{\proglang}[1]{{#1}} }} @string{and = { and }} @string{lncs = {Lecture Notes in Computer Science}} @string{add-berlin = { Berlin, Germany }} @string{add-cham = { Cham, Switzerland }} @string{add-heidelberg = { Heidelberg }} @string{add-ny = { New York, NY }} @string{aaaip-pub = {{AAAI} Press}} @string{acm-pub = {ACM Press}} @string{ieeep = {IEEE Press}} @string{ieeep-ad = {Piscataway, NJ}} @string{springer = {Springer}} @string{iridia = {IRIDIA, Universit{\'e} Libre de Bruxelles, Belgium}} @string{proc_of = {Proceedings of }} @string{proc_of_the = proc_of # { the }} @string{aaai = proc_of_the # {{AAAI} Conference on Artificial Intelligence}} @string{cec = { Congress on Evolutionary Computation (CEC}} @string{cec2006 = proc_of_the # {2006} # cec # { 2006)}} @string{gecco = proc_of_the # {Genetic and Evolutionary Computation Conference, GECCO }} @string{gecco2013 = gecco # {2013}} @string{icml = { International Conference on Machine Learning, {ICML} }} @string{icml2014 = proc_of_the # {31st} # icml # {2014}} @string{acm-cs = {{ACM} Computing Surveys}} @string{cor = {Computers \& Operations Research}} @string{jair = {Journal of Artificial Intelligence Research}} @string{joh = {Journal of Heuristics}} @string{orp = {Operations Research Perspectives}} @string{telo = {ACM Transactions on Evolutionary Learning and Optimization}} @string{alba_e = { Alba, Enrique }} @string{balaprakash = { Prasanna Balaprakash }} @string{bartz-beielstein = { Thomas Bartz-Beielstein }} @string{battiti = { Roberto Battiti }} @string{biedenkapp = { Biedenkapp, Andr{\'e} }} @string{birattari = { Mauro Birattari }} @string{blum = { Christian Blum }} @string{branke = { J{\"u}rgen Branke }} @string{chiarandini = { Marco Chiarandini }} @string{desouza = { Marcelo {De Souza} }} @string{dubois-lacoste = { J{\'e}r{\'e}mie Dubois-Lacoste }} @string{fawcett = { Chris Fawcett }} @string{fonseca = { Carlos M. Fonseca }} @string{hamadi = { Youssef Hamadi }} @string{hoos = { Holger H. Hoos }} @string{hutter = { Frank Hutter }} @string{lau_hc = { Hoong Chuin Lau }} @string{leyton-brown = { Kevin Leyton-Brown }} @string{lindauer_m = { Marius Thomas Lindauer }} @string{lopez-ibanez = { Manuel L{\'o}pez-Ib{\'a}{\~n}ez }} @string{mcgeoch_cc = { Catherine C. McGeoch }} @string{montesdeoca = { Marco A. {Montes de Oca} }} @string{paquete = { Lu{\'i}s Paquete }} @string{pardalos = { Panos M. Pardalos }} @string{perez_l = { P{\'e}rez C{\'a}ceres, Leslie}} @string{preuss_m = { Mike Preuss }} @string{ritt = { Marcus Ritt}} @string{schneider_m = { Marius Schneider }} @string{schoenauer = { Marc Schoenauer }} @string{stuetzle = { Thomas St{\"u}tzle }} @string{yuan_z = { Zhi Yuan }} @article{DesRitLopPer2021acviz, author = desouza # and # ritt # and # lopez-ibanez # and # perez_l, title = {{\softwarepackage{ACVIZ}}: A Tool for the Visual Analysis of the Configuration of Algorithms with {\rpackage{irace}}}, journal = orp, year = 2021, doi = {10.1016/j.orp.2021.100186}, supplement = {https://zenodo.org/record/4714582}, abstract = {This paper introduces acviz, a tool that helps to analyze the automatic configuration of algorithms with irace. It provides a visual representation of the configuration process, allowing users to extract useful information, e.g. how the configurations evolve over time. When test data is available, acviz also shows the performance of each configuration on the test instances. Using this visualization, users can analyze and compare the quality of the resulting configurations and observe the performance differences on training and test instances.}, volume = 8, pages = 100186 } @article{FawHoos2015ablation, title = {Analysing Differences Between Algorithm Configurations through Ablation}, author = fawcett # and # hoos, journal = joh, pages = {431--458}, volume = 22, number = 4, year = 2016 } @article{HutHooLeyStu2009jair, author = hutter # and # hoos # and # leyton-brown # and # stuetzle, title = {{\softwarepackage{ParamILS}:} An Automatic Algorithm Configuration Framework}, journal = jair, year = 2009, volume = 36, pages = {267--306}, month = oct, doi = {10.1613/jair.2861} } @article{LopBraPaq2021telo, author = lopez-ibanez # and # branke # and # paquete, title = {Reproducibility in Evolutionary Computation}, journal = telo, year = 2021, volume = 1, number = 4, pages = {1--21}, doi = {10.1145/3466624}, epub = {https://arxiv.org/abs/2102.03380}, abstract = {Experimental studies are prevalent in Evolutionary Computation (EC), and concerns about the reproducibility and replicability of such studies have increased in recent times, reflecting similar concerns in other scientific fields. In this article, we suggest a classification of different types of reproducibility that refines the badge system of the Association of Computing Machinery (ACM) adopted by TELO. We discuss, within the context of EC, the different types of reproducibility as well as the concepts of artifact and measurement, which are crucial for claiming reproducibility. We identify cultural and technical obstacles to reproducibility in the EC field. Finally, we provide guidelines and suggest tools that may help to overcome some of these reproducibility obstacles.}, keywords = {Evolutionary Computation, Reproducibility, Empirical study, Benchmarking} } @article{LopDubPerStuBir2016irace, author = lopez-ibanez # and # dubois-lacoste # and # perez_l # and # stuetzle # and # birattari, title = {The {\rpackage{irace}} Package: Iterated Racing for Automatic Algorithm Configuration}, journal = orp, year = 2016, supplement = {http://iridia.ulb.ac.be/supp/IridiaSupp2016-003/}, doi = {10.1016/j.orp.2016.09.002}, volume = 3, pages = {43--58} } @article{McG1992vrt, author = mcgeoch_cc, title = {Analyzing Algorithms by Simulation: Variance Reduction Techniques and Simulation Speedups}, abstract = {Although experimental studies have been widely applied to the investigation of algorithm performance, very little attention has been given to experimental method in this area. This is unfortunate, since much can be done to improve the quality of the data obtained; often, much improvement may be needed for the data to be useful. This paper gives a tutorial discussion of two aspects of good experimental technique: the use of variance reduction techniques and simulation speedups in algorithm studies. In an illustrative study, application of variance reduction techniques produces a decrease in variance by a factor 1000 in one case, giving a dramatic improvement in the precision of experimental results. Furthermore, the complexity of the simulation program is improved from $\Theta(m n/H_n)$ to $\Theta(m + n \log n)$ (where $m$ is typically much larger than $n$), giving a much faster simulation program and therefore more data per unit of computation time. The general application of variance reduction techniques is also discussed for a variety of algorithm problem domains.}, volume = 24, doi = {10.1145/130844.130853}, number = 2, journal = acm-cs, year = 1992, keywords = {experimental analysis of algorithms, move-to-front rule, self-organizing sequential search, statistical analysis of algorithms, transpose rule, variance reduction techniques}, pages = {195--212} } @article{SouRitLop2021cap, author = desouza # and # ritt # and # lopez-ibanez, title = {Capping Methods for the Automatic Configuration of Optimization Algorithms}, journal = cor, doi = {10.1016/j.cor.2021.105615}, year = 2022, volume = 139, pages = 105615, supplement = {https://github.com/souzamarcelo/supp-cor-capopt}, abstract = {Automatic configuration techniques are widely and successfully used to find good parameter settings for optimization algorithms. Configuration is costly, because it is necessary to evaluate many configurations on different instances. For decision problems, when the objective is to minimize the running time of the algorithm, many configurators implement capping methods to discard poor configurations early. Such methods are not directly applicable to optimization problems, when the objective is to optimize the cost of the best solution found, given a predefined running time limit. We propose new capping methods for the automatic configuration of optimization algorithms. They use the previous executions to determine a performance envelope, which is used to evaluate new executions and cap those that do not satisfy the envelope conditions. We integrate the capping methods into the irace configurator and evaluate them on different optimization scenarios. Our results show that the proposed methods can save from about 5\% to 78\% of the configuration effort, while finding configurations of the same quality. Based on the computational analysis, we identify two conservative and two aggressive methods, that save an average of about 20\% and 45\% of the configuration effort, respectively. We also provide evidence that capping can help to better use the available budget in scenarios with a configuration time limit.} } @inproceedings{BieLinEggFraFawHoo2017, author = biedenkapp # and # lindauer_m # and # {Eggensperger, Katharina} # and # hutter # and # fawcett # and # hoos, title = {Efficient Parameter Importance Analysis via Ablation with Surrogates}, crossref = {AAAI2017}, doi = {10.1609/aaai.v31i1.10657} } @incollection{BirYuaBal2010:emaoa, author = birattari # and # yuan_z # and # balaprakash # and # stuetzle, title = {{F}-Race and Iterated {F}-Race: An Overview}, pages = {311--336}, crossref = {BarChiPaqPre2010emaoa}, keywords = {F-race, iterated F-race, irace, tuning}, doi = {10.1007/978-3-642-02538-9_13} } @incollection{FonPaqLop06:hypervolume, author = fonseca # and # paquete # and # lopez-ibanez, title = {An improved dimension-\hspace{0pt}sweep algorithm for the hypervolume indicator}, crossref = {CEC2006}, pages = {1157--1163}, doi = {10.1109/CEC.2006.1688440}, pdf = {FonPaqLop06-hypervolume.pdf}, abstract = {This paper presents a recursive, dimension-sweep algorithm for computing the hypervolume indicator of the quality of a set of $n$ non-dominated points in $d>2$ dimensions. It improves upon the existing HSO (Hypervolume by Slicing Objectives) algorithm by pruning the recursion tree to avoid repeated dominance checks and the recalculation of partial hypervolumes. Additionally, it incorporates a recent result for the three-dimensional special case. The proposed algorithm achieves $O(n^{d-2} \log n)$ time and linear space complexity in the worst-case, but experimental results show that the pruning techniques used may reduce the time complexity exponent even further.} } @incollection{HutHooLey2013lion, author = hutter # and # hoos # and # leyton-brown, title = {Identifying Key Algorithm Parameters and Instance Features using Forward Selection}, crossref = {LION2013}, pages = {364--381}, doi = {10.1007/978-3-642-44973-4_40}, keywords = {parameter importance} } @inproceedings{HutHooLey2014icml, author = hutter # and # hoos # and # leyton-brown, title = {An Efficient Approach for Assessing Hyperparameter Importance}, crossref = {ICML2014}, pages = {754--762}, url = {https://proceedings.mlr.press/v32/hutter14.html}, keywords = {fANOVA, parameter importance} } @techreport{IRIDIA-2004-001, author = birattari, title = {On the Estimation of the Expected Performance of a Metaheuristic on a Class of Instances. How Many Instances, How Many Runs?}, institution = iridia, year = 2004, number = {TR/IRIDIA/2004-001} } @techreport{LopDubStu2011irace, author = lopez-ibanez # and # dubois-lacoste # and # stuetzle # and # birattari, title = {The {\rpackage{irace}} package, Iterated Race for Automatic Algorithm Configuration}, institution = iridia, year = 2011, number = {TR/IRIDIA/2011-004}, url = {http://iridia.ulb.ac.be/IridiaTrSeries/link/IridiaTr2011-004.pdf}, note = {Published in } # orp # {~\cite{LopDubPerStuBir2016irace}} } @incollection{PerLopHooStu2017:lion, author = perez_l # and # lopez-ibanez # and # hoos # and # stuetzle, title = {An Experimental Study of Adaptive Capping in {\rpackage{irace}}}, crossref = {LION2017}, pages = {235--250}, pdf = {PerLopHooStu2017lion.pdf}, doi = {10.1007/978-3-319-69404-7_17}, supplement = {http://iridia.ulb.ac.be/supp/IridiaSupp2016-007/} } @incollection{SchHoo2012quanti, title = {Quantifying Homogeneity of Instance Sets for Algorithm Configuration}, crossref = {LION2012}, keywords = {Quantifying Homogeneity; Empirical Analysis; Parameter Optimization; Algorithm Configuration}, author = schneider_m # and # hoos, pages = {190--204}, doi = {10.1007/978-3-642-34413-8_14} } @incollection{YuaStuMonLauBir13, author = yuan_z # and # montesdeoca # and # stuetzle # and # lau_hc # and # birattari, title = {An Analysis of Post-selection in Automatic Configuration}, pages = {1557--1564}, crossref = {GECCO2013} } @book{AAAI2017, booktitle = aaai, editor = {Satinder P. Singh and Shaul Markovitch}, title = {Proceedings of the Thirty-First {AAAI} Conference on Artificial Intelligence, February 4-9, 2017, San Francisco, California, {USA}}, year = 2017, month = feb, publisher = aaaip-pub } @book{BarChiPaqPre2010emaoa, title = {Experimental Methods for the Analysis of Optimization Algorithms}, booktitle = {Experimental Methods for the Analysis of Optimization Algorithms}, publisher = springer, address = add-berlin, year = 2010, editor = bartz-beielstein # and # chiarandini # and # paquete # and # preuss_m } @proceedings{CEC2006, key = {IEEE CEC}, title = {Proceedings of the 2006 Congress on Evolutionary Computation (CEC 2006)}, booktitle = cec2006, year = 2006, month = jul, publisher = ieeep, address = ieeep-ad } @book{GECCO2013, title = {Genetic and Evolutionary Computation Conference, GECCO 2013, Proceedings, Amsterdam, The Netherlands, July 6-10, 2013}, booktitle = gecco2013, editor = blum # and # alba_e, year = 2013, publisher = acm-pub, address = add-ny, isbn = {978-1-4503-1963-8} } @proceedings{ICML2014, editor = {Xing, Eric P. and Jebara, Tony}, title = {Proceedings of the 31st International Conference on Machine Learning, {ICML} 2014, Beijing, China, 21-26 June 2014}, booktitle = icml2014, volume = 32, year = 2014, url = {http://jmlr.org/proceedings/papers/v32/} } @book{LION2012, title = {6th International Conference, LION 6, Paris, France, January 16-20, 2012. Selected Papers}, booktitle = {Learning and Intelligent Optimization, 6th International Conference, LION 6}, year = 2012, series = lncs, publisher = springer, address = add-heidelberg, editor = hamadi # and # schoenauer, volume = 7219 } @book{LION2013, title = {7th International Conference, LION 7, Catania, Italy, January 7-11, 2013. Selected Papers}, booktitle = {Learning and Intelligent Optimization, 7th International Conference, LION 7}, year = 2013, series = lncs, editor = pardalos # and # {G. Nicosia}, volume = 7997, publisher = springer, address = add-heidelberg } @book{LION2017, title = {11th International Conference, LION 11, Nizhny Novgorod, Russia, June 19-21, 2017, Revised Selected Papers}, booktitle = {Learning and Intelligent Optimization, 11th International Conference, LION 11}, year = 2017, editor = battiti # and # {Dmitri E. Kvasov} # and # {Yaroslav D. Sergeyev}, volume = 10556, series = lncs, publisher = springer, address = add-cham } irace/vignettes/Warning-icon.png0000644000176200001440000012750114157104332016414 0ustar liggesusersPNG  IHDRXPStIME""NbKGDIDATxx4z/Rދ ҋޥ "vD+Ҥ(* (H]AEQHoIH=g7ݙI{}J ٙ7!H,n$ʶ&[J -LW2DH5L4z+rS0?N?6^Ex^DbMAX5BZ^HQ|GQ "yUfRī A,ED jZ-hf*"x~Nϼ1DLa9.':*;7= =DiE|aSc=QέD}, o[BL"bh\@T-2@ZH 3b+Q+ïrq dG> 0 o F46^dgVWҍD-Ƌ,c5 7'@#?Ǖ__%-&L25Dq)DYYH2D!=U*t1uc>߻#>ݓ/̧:ߪWo3gTWRU}@d$  ARBKڰ #j:3Od+[qe1Ѭ0y$o(3K&>ףǰ{"&{dG&Uy$ "^rdي(["A[f.#eXl8g WW;ҤOuLp\ICyVWqXJ#2MՑ+s:`%q%]Kt$Q *#⪂Uqùĕo5*%86>ψ>%,8"cE}*򙮭V3kf%q0DmMK޺U#hʭ_*VWi-A3!*oY@ACDUqMt|y`D , )U,~5*+L#LիM%Z\I/[SxVF\mp _@T$-QPW?$QpjJpW{c[vo%:l=(ڥWvIнvȦ$B$1ψ(rkPB>6U|k6aY:=oΕD'`Yyq-ۈ( SWf輍8ss#-|g,>?.$ #t8r`/U^h9W1w-^wiR+VQaD UվBmm F|4 ,G^h b!"#j[HmanjbO8|]RWq:F1 IT^UP{7<սy-,-yS`-uc9*DV~Ip;jW,:5k29mǸ2|)%qe8h2ofzVɶM̱؟x+xktJJ(%Qc*NxR|\VW?/lo}ٗ0+tHo>ҨzP\.v~O*C8t"NSž-ՍqZl7UZ9)\yVWo 1 2T8 *W|իrw&<ޱ]W֙nmxcP-A3WGT$ R^\UW u+C9$i`Jjm&L8(ڮa|ӓ3j(;~UY`!+/<1v㭙2(DOV a 8 ʭel\\Y\XKY h &~I*Vnq ,*vM ո w+c_W̧$ &z$ ST^1[1{ÇFCW`=`%q%]ItQ *#ДmDDŨ=䲵5D+%5{իFE%q&ιDVaf! QW`{` `+ϱ5Xvlk0~`]ԝgɬ$E%joX%A8J4H/Zڰ6C+WN[6JPXW_BT$hz"o2˃[Kby_~_W{PW 8" Q4Oձ?8cЧ<{^ycp%ѵjDuL"H1CU\?; %y\,>xoc\zyDK|=LOA,eAA|G;quA qc>KxϑMI\ Q7*MxD#Tn Vի[7%[\FX1mWU08B?cQET̙b5nM_3O?q؄*.?,$ C[p~T^Xl1c'JvqqzexwGޘ&uBJVQaVLXUXߋ,L\NW׫$uG,'J-/Uq5$?r\x륄?9c޽|g,VwΩ$t K!8r^*תb}>v+?Q+WvZ}qt)DL"+?\GJTHQڕ=ngMٸ~p6-ӭ%_2F4̷ 3|GTŕdfGf/}:۷q}=ƕBN)}:%qRN &&U0"ڪ + /~X%q\޽w>1 hWaeĕ|4LW‘ ;2H:*Nu>>5{Wg+-V7ɭ$ _'jXepDz5QU\IWs}9Wq]/6xN-yMP-A3&:Id $3[JP*{(m9nϝwqŻw3|o`XFI\"C[pdPFU\m ӝWF=ÎCe\Icn+rC\]^ۙ7e̠$ޣ:=iX%8d@DQaes#׬۸]wO渍+#6yD{ܬb$1;`*Mq~WN+]*WI\MI`Ƒ$!"fMqiT=㘽{/6+Z&W3]Z`[+L\JtQM*#H"DU*v?`giW7;v1X]Z^I\!O[Yp@M?́|[ c#Jز 1{JJLr/&UJ#HDD=զxTUƵŕ9rG[KzE}j ~At Id$rn%*vdHոxߏ+V +޶G]r"ĕD|=-@^}`|6{`8vZo1΁n:ۭ MJI\-w _JT$(f+QPUq<|qt=yիK\RZDK|= ": >׳帺 єJذ 1[rdSWq>M$J  "jZի6iG~oՈWxW\DGz &Kv!u-Aod=-խKqqexQĕ$ dU D0-UWYl1c"7neK]Jz{[^&uBW\7 b;ʸH[[KZ+#4+ްyz?ρշCu)+ֹYʁw`DiDLvpKHr\x%WDฒ:֮+z+mr*83 2SzSU\I]ճ}8)>[Qrb`h۔W$(+N lYN,o wfʡCzY۳"׮ub?-$ M% x^U\X7>`vrPm\ɰ5k8vtOӸXzu&dK.݂I`' Q^e\/Y`{%Wvzg$Wtrĕ%DQZVIa Q9{+eKl_qe1xre8b$quNlTGI\~J*xGH "b겷VҥKYcW6ҥ|!q/8)VUŕhۇ}? /^1s?KrC\IOwjS(+"UDVz\CU\}_ ;_|rJU/Z7?uWzWp6d><ڲ(DojcPkqu}HWHy+V+[q%u̟ϡr)q%gUWq:&iXxuj zE[se*⊿yBǸ2<ڢ`{KD'`Y" oyjW;d=-֭iC\%r\9]|R`]՞w/$_ %H[N2Uq%=Ѻq}$+iGs\Jzs+^&Wae՗uV1aލt* ,Z`{W WIW<r4 =/(+9Dݬb;߳(cjs>ߧ帺KB\%Y\3)_+WrPWÈVaޙ}-[=V`{>sVԔI|wGq%=޲hkŮU1GqUTT&G6r`ݞ5%W7F>g).l{JSWVaFC%"6+[C!WW>wGq؋<^l{˕TW>&xY(@.Uq1 Owne}g! qq%꾸9s8v ۸rֹxu jkn&URw0d3SJlZ̿JqexghӭA#voˇRWfa"*K˗vWaA\x\2ϚWWm(+7^&4x')D\}*G׵0a8滝+_ٳ=mB umS^uX' -AT$ V cUzwys tOJeqN%ZD[Y,! gEj\]u;d<+#W>Wc7/ʁw u}3^*DI\I/5AH'BͼU|gkk΂Oĕ +Uĕi֋=ƕ8? ZANd' !uXU\m s:XlWL!>xTvL̗uWs][YUDL0xITl Z9ݸqI\)N#njXE`i8/V+BT$ B_mUq=kfr`ݚnƕ ++V>WN'OkC~[2O?2*Lw>^Z*'4>>bqa\s̄wwxWiR+;߮G$$b\v*{5x>JӸ2@J^ !ngAF\}NXZ'-Ur\?a2x$/f)ʡ$ !hXe!8P&Uŕ:l Goل={i=eW维͟E pDUL"Q (CQ1WjGl:=s2*ĕ\*.JQW Œ8#P[ZXk#CNz0#IJ;~_c\МK-Ώ3D3f&U0[6uVWJ5dƕVJ*?cb#׮*qw9|pqexJpN7Xpf`Vet-AiR]-Uظ\ak\ɏMṄV4+0yxK\Jad08CH"&+^=חco3;} M;8k\V|EC^hkj?%:_Idm6*vikk0|t=JY)r5K=#Jr:~<}\tOJDVaf)XD@}>-._z2t+s8Qu}DZcnV\[%xnO$J q` J}Uŕl^E-_bW2uENTu kWN~0C\KzlCʛD8cV"B4ܒ>_`oW;!K\Q\9}-U|7{DI\L [DHQW#Z~c~q%J*k q%JE\4՗X\|e#^l+>w۽帺0vȈ-Ǔvq%u{W:=x_%uۆDM0g^=*zuw2JHT(&G<܃^qYmĕVIW[y`b#_;J(>ߥǸݪ1/Ng&#Qq*Jx4DO\JދJ>ƒ)(:s`utcJp* 7XqfT"vjm냃\_)J~;^}/1ulg;4ٲ(\~DM0gHzEU\IYճ}UW2+髯rLux~@WaWNIGT$ )G vKp[|yp/ˁukʻŕ?'q5Q\9}&=.)$ G[pUJzM#q쮝ŕ}C}W" cFzNI\4էzD MaRՓ*bl G,TZeiw5.XU\/3O*؝2UO#f+'8XCND o 7 };[oOXS\ɰrW<]\Q/ȗW.ٕĕ3DOVa<9"ƫX2nu[WDD :UQ5WRWS9D֣8l *.*RU\mϑCX?_\IeB>G~7Fq%WxHާǸ:7 ++D3|0@VUq. Oոv|K~fq%}~8_tk0~`jӄJ6A[KS&U0Z^uv*?.fk=rǕVWR?D~x(c\YYI\O'f+7DU&9Uq)Mп {Jk*?svq#F8ܭǸ2\C^W=oXeqF}k:Eq%=\իrŕQQEC[M %q%|&Ug$MѪվӼ+VWK27k|/FqÇ;Twqu]3JSW#,83D|K+/=Pŕ8tJ|SKX4%ۚr⋚DmL0g(իJJFSҶV./~:! RkWǪ)+7Gp?d9QDWTզŕA֒  ĕV>WNCW>$>%W|= TWsUŕ&[~usǕ +emfq%~z kR`k>/rJ68S!(7q&=bkkܙŕi⊟yyPc\Iϴmߕ-$ G%g D"AA|G;quavJ~)C'dX%S\I [z+̙% v#bXA8s#"j-AUZ|~q%Y>/pVqC8)qexJpo!DL"+/\ì"zDTڒ)_~/,q(&,,+>&'O'KXoppsphVqTVdJzZ^6X"~\HjK,ɗ彮_ŕӧtX ٔĕDM0tĈzGU\Iխno}jJ<>dw3dWqeXzu;/_#_+#W}8+gw q ?ywN3N&jYp]*!)JE\n˙CX_\!pJcXY_ƕa6m̍W:zeXA%qe,2*̄3$"Xq& Ov|r\]{q;١_\?Fl|~WU,,woكϵm1r}a ?$lNI`:nJC#}_\\w~X7%ݷo wxa!W/=v[UcZE%qe8;7XqDd=Q&QTib*l+)?hkg ʰ\r|]/,xo>[5Us&hKFVYaj 5ynիr̺5WFX%e\ɟ+gE*m _S{]}uc-swzW -YDI\#̷ L C("{o 򩮭z~_\ɏ&C^.Xދ!Y\qnۭ+oc\IO>ـȐ>AqMݖI`15PU\IW(mk*/+lٲVFqB|U\9ڕ;X[65+++éD?7GpFH!ꊪژ>_r\|goĕ +Uq%gq%wXiӦ(hNΜgKrڥ _nc\lkNЖ#^#83Wx;o?ֳ0a8vzJz69yqeG9;wY\XGOlLkʼnjDV ֋*ڙ?;s>/)Ngap|FWFXq\qNNomqpWJ|=ΐv: >ۣ帺0vȠ򗸒ae%ǜ@٣}`LkWRGǎ|uq%=d^)c3+QW*Mx#z^U\IT-ok/+7o&8 34cVq"CӮ:dCvU( @E\9J[z &gJ<"T9tpO_\-\/ v>ʐI(.f4+6<ƕyĕ+D|0Θ`}KaexT\,7WC` oGwq%۷mY\+uy^j?7=aXE8kj2+gWs[RB`iƻ)2y۵sֺի]¶*V9 RUq6$h9n>VF\aq%Q?ռys_+nۖ">Ϸll5?_fͬ$tWZ6W SW\X2*̀3)wj[ymS#Cݸ2WJyyy8{, ?j1-OzSMOӤ*fsQDML8aWmT՞ ~q`K0ܦ]l<ƕbi%qe8hUpF&"NuS񅾝~aK\͉5LDnjWN|/X ,ٔĕ@&UF gV@Gһ[kl_L?/""Q+SLZ/E#[,թx_ 5oDsU58R4kJ*=CXNLaq5>#XɓGzwԿhׯQ\9mޜh>KH%q7ţ>tU gX@D\DLvKpu@帺6r0;l/!H(QBյQC!CJg1x`k׮]",Q#mؐZ+ɦ 鼎1 Q{*) -"+Jel^E-qe,oDDX?S⿈<ǎ*Xĕ4qqes {D?7Gphz"z;oe!Cz4帺9uJ?'%Ǐ:~WplkKrڠ_jj!s\ Do{Z:^}*9DZkVWN>}ցuԩ{1Pn|B\Iz VWiDJ1"8b c"n ~?;W}2)c ğs`]IpFq'z',REĕXd> gd Zg{W_~2%ǟ X2z.z\Sq%=q^)a.DL0gfW#TŕTYx~q%֭d u99>noǕVE\Iԯ1 ֨3Tĕ'`Yyqf{\uy+Í3qֻcO?e>x^۸ʘ1cgϺ4+~qurzd=$ _$f0%JzU#p캵ŕs풉~Mʙ3gcB:u8|ʕWҿR؊nJtQ}*& Yw%jbKh~q%=w]vVp c\au_\կk!wLq%qe8y*2]ʁ35AF_jMH{帺WPWLXXUL}qAc|=ƕY2+68z%~ѪJK*6܇,/Op3!00PVZ'jW\ӈn XjVq{W>DL"+?`QaT[nΞ/e}}Ҹĉ+##͓O+d˖M/!x݄ŕZJ:WN6vѼJen 3 իuJh`AXNnlVB 2L|y,W"֬1kgHU:֠6M:![t ѝ:DML8aˈpjbKЈ% lCO/]b_RJZVw|ݻÆiWNkujy\:ٸ=J*qC*C8|%DDXVWkR Xl+t]א[i:}E>p@՝oPc\.ΞUI\'jXep&k&+z5 $+q%}XFz$⊫UZ5\1e!~]gŕtDUM"BUiQw2Pr`ݞ٩帺6r0;6/&S烌1B2e,J?eWDI4 |S4 @zSŖ޲% Ǖ +;q%\QƎe`͝;^lXõ+[{\:٨*VWq%Yʍ3; YAE%Uq6]Z8 {eJL>]R~.}w`iW\vTP1qeJp(ы&UVgx@!"i'Qz/ǬX_\C~3_]t UMWFXx\IVX3-'/4B8SU1[w{ܶç_\}1co!w+Vtz.-ժ&f0 $e QUZIWnmz2t+1;w2{O2,BT4|4x>3QG*% WUŕt_2V/俻~/СCZ1__=U:bq%Gi|ZeW\VU\dDBʛDVIz"A-qWc̙3ZEe_Q6Fq1-tW*2F4HիW)+?=a}}X?]Bwt֭[ ۷o'`,J]۶iW\4;ďg6X',c[VD$J q%$jm^]4_M "EhXOx( T1 T-Sĕ D'`Yp%(eQ^OTՆ,8tpOquk›G1mPE\Hu %K'An-Y,KbK{+yeJ8'f0=ex۰2JWFۇU+ƕ +gm`5pDWIWFX%b\IK j{N[g$8޿nW"oXń*FQ3qH[[Ws؟СVU_F?rsZ(\8ߨZc\9_-YTI\!Zf+' AFDQUqJ|yO'quVĕPsU`W#~4+Cx?ϒ)A[f oXe!BYU|>VUŕy~X#G*3U\F+c)VS;LT$ HPWg!,_\͙#3:0n8k [ WFX)+КULm-^%q5)ΑDlfĕ`RͬyvJ~<5`ƌZ(y?!MJ#~Ω'j]q%=V:J*a?&#51 ℁w͸2,jkk0ŕqBXoVTsϞZŕ?e++ñqTU\b,(3jM|_otEƕVvJv8Nu`XSL?n\ɰJ@\9?\nWd\IOȖEI\ŭbq_gaЩpXYzyWճ}8EŕZݻ q%eǛoWqF--s\Iը +#slnj&UW[DQuWU\mɕC>;ՇaׅOߵ /qI`^z|FqexzeKu≚@^%qe8h̸햠ኀ>ٹ帺6r0;V2ʈ+9/zֹs x%,%Kz5=ƕha4 |#jD$J q%UDUŕtoۣ>P?^e`kX:5 (vU\ުX?q:!Bl8+ñD{ݬb=+ _%*벪Z.-_r\yI=cE;&&Ʃ*U*m?x%9w2K2P+WsfWW^"4^RVյ~ϫr̒/+D߹u`=CֱcǴ+itt4OhWҨ2^2O 5oeEtbQM*+ j"bUնmm O_\͚1G8/p:VbŴ /jXѡMjWתVXҍUW-tUWR8KD0t ƍkW HvU\._c\9cRJm!DM"+0PVuU+#eLϗW7Ǿ_\ɭ={qeX:EVN̙3kXƱxW>d\`iWkUqX" uX1d>WR "櫊+ϱ_/.s'|zw,FEEq̄ ŕn ( G<58XI\M+H-IT$J@ C"jGL]\Is^RJ`KZVI) \a{7^ǸrZo-RPI\<gR{;oe"8`9+4u\X'N"~,a|iWRXGG&UZ+)ckpT9+#)ϑ)*>o~)&"#"8N2W\V*I*D#hYp@s1u-AY2q̙θJiD|>t֭[;F#vbiWjTWeJЖÉ&*L+#Bim`tvqfDxx -[hX={LQѓ'kWR%( g*<-$DI\D L0W!4d%QsqhA[3jW.F\XRPҘ+Èe<^*ĕt,9D֣" "vK87ơCzYᆬ]\9wJ%?qa)?)4 ŕ=ƕʼ+ q f0#LhMJz}ss1˾.b~vKʚ5y ,1>)e!ԪSJpXŏ+;DV ;~WTREmm F@9yyaUB }`Ǩq ~c5* *WNE,YTI\&*V.\Sety+UiR]+t>໛7۷XqT\kǎ)vJ*۵+ó5~5sM? :T!"WWCOԲu+u׮%J`kd5lKn"4紋+idRq!ez*ZO TWqDsU3Rg *'*-A͹sr]\I#w1Xbu/9q2n?0Jq ,yU,xVaf\#@.Uq, Ovie}}``2ʏ*F|lnbmB' x񢶫WvKߑY\IcK]2Ve6WqοېI`l QW˕55gvq%ӉXYFÔ=h֭78' ,e=ƕUywbhݬb=+>l".{;oe:}Z8 =J>o7o.@"+*ֻq% ߵKU,L%hf|= `ՇJzq]s~/,Z&dXH\Ŋֵk+%m?VlٴY&c[һ#GjXQJ:J[<)(PI\I& -LT$ Xo }`+_wѹ?M(?K: (b͓'QuRyqe@>%q%/|h¬{[A"~RWWmW_SUX&͛7u`w0Ka`Ŗ(佱<V>V2J֫2j -:V)ah+QW+7>CJO;/4,6}:{1l!~`]1kY˖W{˔PWoBT$^Auy+õ2{Xc_/Mo7nX,ۄYǎj޼ۃ 2yyG5 *z c\.~(2|htZbJTgۇ_hWq劥JۄW^ҥKX^*ŋ eZU,ŕ`[V|zYM(a$/"aPW 泵5(s0XHSoݻl>1s` 80EoZ gpl͚z ?HG5? WWXq C"갪Z&:;ӫrڪ_xT~WQ˗םYV+mf͚o^J̻?(|/UDBk-syX^rY dHu\GG^&UZ+ɳ5VKSJj@gZ* _-F=eƕCgMX`A z/W=hwʈ+鵫W9[7x#KླྀeX+W`I+Tĕ1D#hYp YB_DTZ53i?/|/ []sU\)|g.vJmBJ*m`Mwk{^`E?3.7quGxwT-KJ#{j>VǭA#ϕ.ɝ3A[LB@Z`|ib?5o\jt|ghwQ "bğ{ʕVRo*V 6>Ǔ^ݞ$|XssU? ?qk0~\IWCB8D MaAT qmBjh/Z!i\ЪG +#+i3.$IM*V6=>ֵ9n] "]sVFXX[d@Ji[Kh%qeF7X9q ӊ:*V }:ͫX^x'*rjg\%v`:޿ 7p{RlJMy;_z`|ᬼ#kn~p&˫W.+9)a3 *# Dd98o{N5W:qZe%7|&_;wr`%6?~X{n*ʕ+էOUL|"O6ko?,ĕX҃ Z%q%Mt2QzW@/TWrdС^~^\X7ZTI$k\Io `_&zw6:䗫W=h5+;ʕK ,V{ +wqB\IgῪWzuRWpUWBgj[=ޡHI\ݳh{c&OL?C/_v^0GVro*ևsᯁu)-ۓj{xH'MJ:?r/WYŕW#*ԩln+M$JPWUwܓdkm\yIZ4'Y\I9¡ &ꫯ6Uի,yi0ꕝ~X`oRWN_!f+(b!Q&VgU4BWVHܕ۸ V58zۉW'k wۄbm޼o^W;Wɵ=hV]Lm|RXNeɔ{ 7 Ը2fj2aekW+ClU*HxG3Q*Vгg#Ke`麊?e\e̘oVs]`P!="vUbĕ[`۲yzuuBJa>Cxs!*%W+OQG㗫{o7Tʫ\q, ψ^g?}'YQv7k׮ϴ+vAE_g} |:5v*r_rELؗ&WNuUSFDfx!2ULh~}+gl/WS]ȕk~Kv3KkLh)M'Xsέ+$X?>Թe-\m(Wv\|!rU-X^Pdh0E1Qԑե7Du+;??(~-l0di_ʗ`%ޝ%oKFlO #ԔY$>MN"WBTwR8pڊ+vQE_g{Ӵ+g6U;5MߊP k?V !p8W+ʱv-j|(2+JQ^IDĄ"(4kΝ,:+t, LG$ʋWKgD݋G|Bd$+pYPi%F V\C/2_rE2˟u }H^}^ܻ J 12QlMOXJbi$Mb%ɕT^V  N"LkE½w-ջrWc|wLYv52BTHvf,}6b4:ңܮ`QN̞iRgBv4Lf% !rfuw8u-Rv7ᗫͲxS.u+IrlMk.E^Pd]HQ!l6~- j^*k ]&6rFaЅree-'S\IFh+p"Wߊ+ʪ!ճuJgwULh)$+%%4rmJxYk' LۻF;S"r(OV32UJ;.:u>G9QrE)؆_& 7 Ct]]u,_8-XbB#LF4h`v|2>ا){Wr1L$W%»zұR"Bd$+ pR4XH?jcrAwX VLk,[//re䲻) ]t1`5ip+Orez%ܮ5t»R#Lٻr+ʖ |Ec镝MUr zK!*;/Rl&B_OA /WYwE^-K\KPm]QM#X:u2U4JxP,ʉ'fv(\Ik%Wfx~H~B=ew_8V^ %W%jH|IezWWjRb }52d}21ZnWJ|w[kFPkE|fؑ%D$~^)V*܁X|P F\ ?M/XݛZc43.W,eIJ)KT+ɚ?ik̘1}UnRjO g5+g؞n(:ߢ1~?6o03Y2՘ wb8V^&J(痫q͂`k,JU,51ewQS,w_ ֲeL#X>AQv+_ăkÇ JH^:\QK oJGd2"R?-i)rEiSDb7j ]02KTxO?mXb-53t;ӣMٻ$XclLr X7ƫS%Vr%1 QaܑX搫yZVO;/W:. ܂勲;oS,Q$~m"XrbjA5K9oMٻzžx+ʙfaB䪚vE`lpgchp((+ᗫ#(k)w{3 Pm])oT+ɢNpp)룏>2\)rzG@\Q*w◫1}F6!XUYsqGL)OHEKAvmޕ+=jA{E|h.+;N+`l\g "WO8692 wh8=Dn#*J6ު%L|h SXKN,ew5(TEFe :uJD+r;s<$X;2gR\Q6D&E^AI4+2҄ȕ}-U('NTk[m6rկuzW]#|` 9UTKzM!X^rg4h镨xPz-uLۻr/;ScAI(Jar<܍0*;3N%WíՔafq%ڣH|qYv7WxbUhhcdYWzEă`}xLٻr+,Ѡ`QjSSx(WvAt0Be2wl89!Tߗ 1Qե%,sa'b Uv{5*KN8ax*++ӥЮ\izWGũ\٧XJx!r%1gs13QrE9۫\iR;R<3kdi 22dя)((ȒQ+WzF^Mke… %*&Yv:#*dM2Űr]p+GF^8}xU*@R'U=%K(g P >_"4NFJAp'cCqQrpr5i0YjeT/w,_ƄFbE> V˖-.WA +]y1lY1xqrfrfB(DQpGc4B\Q._meT.I.{挋`2&˗Q+Z뜚7Q3\QlkzW<( #v(\I :Sٸ?,D("%#X@u48\D$(_7Xb= 6gKiQvc~g!dN prG4h長x"]/Z`ޕ\Ilg8U`Qމ[=a=,#X p+zC\}F8uӫq KӫVi񣲂-bSx׳"Y"EK-]BCCʕ+u+_ȕh+Qv%нzwI(yrctHVތ5$W+:jfR;»/E'&u]Kt]/bfi-5K;J/ٻҺAJm'Ug725ZU>5X+K1˕U}Ɓ

iJiVZ 60KX+ޕhPJjA ^>u ooQdR`Q2,]9BJbBkd\%q)JVEFkSF Ŗ]w^͝nX: KTLȻKk],=D]v܉333*88>[nP+HP\}1R[nJE9t1hR\Q6ƆTJ,q1\rF!B=Ci(Tݙ_F7,LԾ]C\Y~b):&4KT睅"%KIxdB?ΥKqᲲ2"S񸰰Ϟ=^*UZV+bW5cȁxZѠ3_5Ha^I9 "QG/9.#Yy`pDU{¯ZVlL痫cV,ӻ[' `i݉e)Q/$KhʖHԈHr-T3RA+D8S< ֚@|8Y$ԯ'DXx=-'|v"D%Wl6|yd~%~g ژWewXF,ih-[,V\"1ҳ"XC?cޕ\IlMf*-/jG\nV መ^ Jn]/W~]( (|fvJ=Fb%B}劵Ԯg4h镚xӧve]˕r%.@:Ss1feσE.Wdž.["5&4KDT+Y-Qg?JD4Slu7*[SHv9Ta.JT!rU-Xx:BXI` pFB0%W˃񕱃WZ[cTlЗ"6&4KKT(RLo)WF^,{Mۻr+;aoI+ _$V\UkO+ރFO%֒z5i0QjX|f^拤jX y%KmdE|-[,XL WzYW~)˰K[SVɕġr%?7 ? B<75L08^("!_>_:XbOr] b EOލ(YjDUDKϟZʕ޽+3NƃΜظoL12Q •%'BJbB)DQ`pM֋+ʅ=jXw˔)W:Z}1ZIxEKo}S+HP\ {z%>sajvsY"dEy sWsV(i[I,!,嗫8M]}Ϯ]髲X zKhU}LZŊej#WV^:8}(ðDYYV,Ѡ`Q6e +mWbIu>F(%S+*W+BC{4L^l1L\]v7_U.?[$W^]nđw2|ez%1.yzj!#X0 8 R޹-\Mo[wu, W>i ަX" ZB032\Y`(tvOr%q 3K(܀!rE,0 8`1%WSc] /jb .YDKlZxVzʕAQ+#=qrV!1̰Dy䊲*†+L%I "92G]0کo%)^ݫwXcTl|0嫲)?BQOp~ be bzr'9{Wnf{r,\QNgl^2UHÀhp(--䗫i#&іX&(3 8e.2*dy-R+^j~_\+4J`>qo.62QZx^h`ٚY_\ٙ~)V*F>#"uUk$(2< 4i84xW˔)_ϙ(TxZXz }%Y-OH@{{{yJJJxЙwƒ` )oӫ|ތR%Vr%12՘ QwWo+ʩnWA,[Uǎ8Qv7r]; J*\c޶m~'p޽qII 4AAA~M6xxѢE1Py+rG4镞OɕľѠ`sldžk0YloAgQr~=~ӫi)7G].Fb)YY%A+V~Z2C&MycQbe ^],ĩݻ(.呫 ӫ3E$dt$Ɓqԡ4BDi[I,%? |7/`hy5nFֲѦXzE,-M駟ɚxȑ#^JXIDF^B(kR\IE2)MHCCU b ̣DSEeR~2(LrA8>}FewXQ?$'2T-^٢3<ㅋ;䇟Cڿ"JRSju4(zzjSxsB.+9^AIٙnGVz:N^sMe$>G݈TEk$(**_2?Zdev'h)»RTOSv؁ u+wbbbo(U"VzɕU]^I[hr)6:SO1Ѫa CI{8R٣ jto|#fg>0i%"*Qz,!hz!DrL =V"ʗѠW,{}@S؝5tyq[z%2bC_E՗ j|E$X>ygEֲS,EZ$KihѯMn*V ;MĊ%4\,ky栿 Ǜw$WdlfXU/IU$(V(D,xv"Dm66DȕQwh)P; "YjE~ #WʌrG4hā?ce,̉M \rEBU뗻!#X% 0Mf+er5y(QnR;}sQڽŻ_S,#Jhђd~ 'W鸢BXDJޕk2imaȕġ9+Ξ^Xre~* 0kӉ+RŽѠ\QNe'l6!rEy UFNL~u)WTG-L>ʱ+>EQ)BJ7ђdk٦+ +O]HUq^Uϔ+w, y O \Y"D$ ^ab0"PF\- W -ӻ|3/fbMX#C5k.l#?M#zhU*W< EFV^I|e9{W!!6)t0?CK V K$+2J*+{v5Bj劲44d+;yYxUr D!*+#j ~7^U]bΦ+S,B,d,-!h+, ~ѠWket H+ʱ»Qxk6Be$+Ƿ`G"Qj%deՌ1zzW"']rCOS,Gf,ڹs%+==]XYEZ]SkLUjOˉ0Ven^JJb>8"LD(ZbGL^lrB= "XzJOdFf9jٲ(rW4Slk+~;C/e+/b"R?"DFV ! Hhp(lӌz5y(^f\gg,)/B4KN:wl95jp\ ֕U O aޕ'_OV< V%]i»3 HV0 }w^5 2}+Uq1ƴQӫNyX&*qYB»K; IȐGx K/UL|!WzDui-7YWFLٻrO‚i"N,+*WDk+CzA!*ozR\Qz5eRZ*NYb(5*%Y"#CEٱckBʗrw օ LMٻr+q\rEGm6!ruV#Bؐ""A/ }4,26|f:q!4rTh$R+ZeeedM4y4\A]5 LG(aUNN"Wڨ0J#x"FkQr4$_7?D%2\yXBHH$[ekرJMߊG*C4W^62Q9$X5Qa\rE9Mx>,[S;Be f$n-ӢrVӫ.5IU%bU%Yk ZjX +=zW0̦N &{+'{i= @\~12H\bEYoL?jee5s~:y!T[xU]d-ٲcsk>W^\+ӫ xETJJY "2r% .F\I Gu0 I{}(\p\ :+ZloSg (YZD~,999 &Vf+(u`Sʎ M1 r*X'ǂljf53ws(k[0p@U N7L>z:}!4bT(ZX#CѢh"UII rѠ'o+Mٻr+'# {]SȕDv)L)8ZQr,,8a(fX&*u#_Q ,4EeW^ eU li{Wr%yx(>*WՂEy9!wn?_olR];t !ˌ,-%B(soذkΜ9LR%'VF+}Þ~c +%k4,W 4P@C"Be$+)jLZJ/%3OfXb$W N B_IPOڶm 1\i5X+w B4;,DYWYOdex%8>V\ID}0̉+^OE_҇_vLYν\E޵H^&[o& ?#UQQ{FDNDʕ/A,WNc8>6)tDʞR%VrUA`GcED{GFQ~+ ~Y>z.~ }-Y"YZDa%+;;*Vf+ W9M>w%B< Pz*r:T/YʝRbA)U<(ZN?zW.-j,>%Khy{pմiS|I!bW$\A]G;fޕ';DA$.D$"B= LJ^ :SqWG~_ϷYw"X|.> )YzlQ~mnݻk*Ube4hЇvDʝّbzg!di l9KCB'Z[oe})VF+}ñ5 g5+w|+n,,QBJbB 0BDŽʞ]Wzk vmT\y\M.YYZEKlQ<Νucbb*==s=_ħN$TJRX\Y=[Ԯ$X ٚ<8Q?D~!W)52WHQg"V-W3k͓-LTI4 a_XZ%Kddhi-g۷/Y3gδ3~xhV)\P0JQri _=]Zw&WosٕQ|!Y"Ci7-s!V+v+vNI[4,Xw"j!BTYFΜ{merUCa>KpѬhKx?6oH(rѕ+Fٖ\&#W⢙W5(D9 ,B`5"ԅb{Or5?[E}4^`8ȕ`IBCDPdFz%_ZLϋW W0Rɽ{eFk-I,W4#t6)L--W  #(Z Ӳd䪆,po{|qաpQթ+5l k2x |?/+bBmj7auze'L` ³!jw!4YFJ Vȏj@| DUɕDo׷ChrFUޘJ N I>4YʇpڍSk-fO#2Usע93TrRRS!)frp㳦pNGOŇd8xL$3j;mG0nbdvv0m0_/f!CLG$v%%0˕nZx"WS޿Ed5(G\!G VлR,IdZ \9/9g2\9+TPN}]p48)ÉTW8.Qϕc Hq = IUbmɰW"= 얈we;݉sC히uM |6KDfS-QlP`;P`;ʰFᮬRs0WVʰB"6eXEZC¢@|\)VE5GG=107h+ ֗Z#Ag. >e F\"Wʣ`NNYdrIxjj*F\m\\) \Y!μ+wX4pلe2BiP*T`r8_֍ejAruA\U!WU"AN ^q?!Uvg&@+5ӫ*KN ~""DyJPZhBJb BX,!W"MD(Z8? J,+?9?iUv:g: @i r&(W_ +F ~+/+v镝4;/;Xy+{z@a!V^oLyWck2QJU*߽]%]MK4D"䪮>s./IJM) ?&"ZFM-WK'A꿢j]j2\I3qUmR_zW"JлޕRzWKP]( m&oѠ$W cUEr iQa) #@jHPba꬟KPj7m6]E]ADYY(k"AgNǂdmP_*"1'J({ZI"M[jޕvij o=ҿv]w.WvȽ{JhНI X-i*#YiE%y5,p|uh[ ,һBϥv]i/oRiKлLG((W^S%WeУ 03M%WSwu^`(,,eL\j$W [bI& jVLDF}n\S6D~ Jr|ۯJeл,5E}KлeZKjQ:V"='BJbwxCrlDOl6݈~rua(,eL2QX&R'X%WSABj_"4XF 6#G3E嫖MNGCwD-Rjrq*W{VXN#T&#Y醔־Ċ(|mHb{MѠL4ſv]A BT媢"…ȕĽ=F{X\Qz/W#{:л8CwD!л[ʝ&WNu85?(D(kBd+P^P"Sj%6e7/O5Ov w%Bw+.ozWuw.Wȵ j)V!](%WˣOzeL2QX& Da*ɕ`QV2-:ImHXkGFV !)B+v58q0VD!g(CwD}ܻ+] ^AINU=)Aq!Wv"t.f2`C"Uڷ<>_6z!Aqeuqe~-sF|dNZ=fdjW #9Z#J(/W8C CwD!]ɱ>*k4,W@!r5QxgBd_ Dz%6ךC zWL`}~.eLڍػ:}z&bgE\HPmQb%A(Z7zգ,eLzWL2Q,e+BBAg:IG"z@h`}YQrE9ҡ \5 ,`(<zWY& 2O$vUEGR,B-e$S@?"Ċ|0qETe2QX& Da(,eL`DyYH)reP-Vr%17h_L6EӫA!g]2Qx3weR"ýFrE9^/ ?d "W|P;EG%SGk1Pj +]AʯD[j`ǁf:lgM|( CZ0Ū`*WU$, 8q4xWSX& Da(a(,e_&`hY(υh+IVcBrI\Qwm?{/lR;`_K-C zW~]&R;Ա56yz%Dȕ-Qbe!Xh%֑/\UL2QX& Da(,@f2\!Q"AOE5W8ar0"S!W _l zWL +x3<zW]ɱ:<]9(OI³m6r%qB3(jz_\Q4-^MGA ^R;`h].CDհ/1Y$6 +P*%M`o%42_ ol :s^~1,oq1]"X&b>rEхz57geл,5d}Kлef.+9:yzEr"% O&W Ew"D_4ղH|}hL2QX& Da(,`]@ɉ^YNCXX8:%*W}-G}C zWLzWлe:лʰP+XrE+G;˴ĩ58y(Zs8CL4ſv]2QX& q6\I!*W:A$#`5Q"E\Q.5rH0|}L& ve'9@c\a-ƸhFS^όOpwz;P??3<vxbݮJ?g頯d*O/.zVsKtv-ɕe[::ˍ;;x\C+]%nMv.|Y-mtr-:p{KWKsޙ$N+m9Ɲ2izV4s -ӼT;g$yA;EzAw 4vPNiڔ8,LQ<..39vJ3Y24LNf-NIdK4p%K"6 %]8Df}Wx"Avݩ_O4WR%w )IvXAgjq?/bTUE䨧VZHqP.ahWIreSGf;#Lvg-&9V0`ㇸpUb`yre;4ڝN#`-~tg!->v~pgDoW;˕A=\=(пGwUWrr镜\ݫBLjɕ镌\)NJ+9^W䪕 0!W\+r镜\+镜\^JNLO+߻`Rf܋9+AM+]M$NbhA!hA!+V%VrE3-XO#1B"WODT hA!hA)Ok+JGsVwn"W ײ D B4 D B4Ѡh9.V\~IA`-F-'7;!hA!hAͦo(.rT6ZJbqX>7D B4 D B4Ѡh@R<~i5XѰS(GHdo$(G0 (D B4 D B4ѠhpkB,~z8r%v"JC::I.׈A4 D B4 D }+xR@POqUc$\P3"V+g> _=ſ\jh@_3)[CSC!6nnr1B2Eyx3+6Ր렀rc5FDG-rYWCkYȖhP5rdIdPՄU.;dX)~5PA},H4~G걐?s&H,HJSJbI!dzs8$b刖'ƕjegD4 vHDEF?tDX3?P1*FZZCF>`} .bV-d F yYȋ x #yx232WyG #sd <,dFg>Ff0 Lcd S$&22A#V1+ N*&[ [(WqFzՋ:.WOk'\=rI$W-Wtz 4@ZFVw4[u\^$Wρ\i'\(WqQqQ W*+I`I ruE\}rIިrFz4ռ:.Ws5գu\fi\M(W\q.f+Jq>B"OMԷ2\oeF2RʌreHHHX,Pf2;١ev(Cf[I@|"Bm4V:y05"Sev(CPf2;(WPW rEDrltu١ev(CPf2Nru5U#!7P:ɂ2;١ev(CPf7z2KQ(1HC$7D~2;١ev(CPf7b2 ) WWHVw"So}+o}+[A VF[s/BoxΠܳ#/("ZI,or}+[A Vзgj8B V_S3J/ Wзo}+[Aבޅ+ <{Ԟ* зo}+[A V:/:ŊRDAF:EI.S t-?PX Cay(,,塂"Pr"X7!M"t}?XIn+Mx 'qP  + A /32_yA 3{cY<#VFNR%QBG?6B_BI-Շo&z0dC77QJHB&?Ӭ d}cz57_Ґ,|; S? @/hM)L%vSjZwY:}M8SslhB!Z%xxC]zUp8p8peIENDB`irace/vignettes/light-bulb-icon.png0000644000176200001440000011267314157104332017044 0ustar liggesusersPNG  IHDR\rfIDATx$gu/z8qg6gII$"/߻~|f{g 8 clYHͻ:wW}gvfΑzCuOuu XUY VR_b7nFG?SyJ=ޯUCKW*,SAE߉z(?6/eF@qo AaRUY2++{A);)Uv?yJ=O?矿8Q@!Uyc۫>g2XPӖ*ں /~l^#bu|#_>-26B?,; OZs*@Pw@[zVnT@+>Q (W 3 a:{XRvڗl@*V~M 1}*,ܫ=)qSJOOBLccHzz@0%_{Rxّ;0V| (E :m7liPc7%UX\~n`E7Uzvw]CI=J7d+2r\@ o(v:N$%'P0E?F$O_BUxrdoR\Aon +&$B[@ .~RnO e,ϟÀ@Jތ l)@.(e z] rK'kIII]'Yo-}9O"Pby1np 6Y* υnDi1+:Աb_B xhI @VEsZ'kmThPe t=@^p?7@0P,̠FB aK H~ A`R+QVȥ8`Mx{ *E% ?;!y[w~~ޔ2szAI:=G3~- ! 4Mv(l(f@@j*7M~-gVKσyo:Y`xblX %_wUю1N͐TEj@xtx OI} @@قv:3 (64 3h598lU6hYE*NPOwc>r2oGz-UzR]2:hkbP:S4gkV@[h+@lKWU)b/*g@LyE.hA B<%GQ4d:~Rsmk8n1UH1 JU`A oYKZ *G@P4Կ|.  A/ @&W:Z9Eg :'>@=rTfB6 Vf {/*pr/j>o$edB~@Yz ~2OgޮhH^n•KEwi*"ȳ@E@%o l:NШG3T,'Yy *?G:u9UC|ң@ ]gOXch\^$Lv _@*4brlTqV~ :]Iۖ4,YyVˊқ 0v{K}*' dshFUhU/0UbVY݆@CV`cr)%%gʏ@PΡGEAl5ΝK;OPF$-h`z sualz N7=﭂@Y>͏]zn>A}󖬽f@L'R~1^/хb/.H tΠ[w ?.Q*t@\[f g ׭@OYV~ (|~a(@4!Zkm n% J!7ԧ`hBu 2R4NB7݆M]5ب\\+> F+\›E&iD-0x/нfOb@bAOrg 12[yL]iVAd9]U/|/h/P+r/Q~Q* b1,x;|~v_/ qi>/r*$ 5v1@C)UpeRAX@XohymS{ԥx涂M~'f5nq1t+hxwl7U@Zt:SP2ׯUТ? >*BMl!X"1TR{axSZʧK^tQA]h)GrH9}ٞPFQ NS"lԡ>9@Y 䌁J"E:INVX)LYHYGKe}=P vOi&Tҁע4GBVBu\ W泟L܏AE)IN" wB Dl|AE .R~*Z~<X7Shѳo6D!Ev1?'y,cb޻w -L)5Om*feVb&_[3 g%CbusHQŅn)>/HYTfDxA4y|ɹZ^2DN<}k_H.݋.ej5TL>s g. E.+R.h@1-l.)TyT"|cV)?BS|Z@Z#sF|'-os| W ,Q%TeI pS'ZMhIit jPzU SP0$uF8Xr*?G)O%To{#`W_G<G,gCE=6;;x~^:@T<,2(8 D9Jev'e-$;eնK߀A~ ű1*a7DiE]8Ԅ][YwΟH'zYGPfHvW9ZԸz;ix@;#B @@|A^ g=eT?(`p<~?]k~B4=}z&3x>zs:Fȿr Is:dZrN3_JAnj6z4A'(x-`<.ߧA.9ݗtTߍ?" XW\"_wf> z(tf0磡]M`BT*Z*YE0{[| *3И9@FMI4ht-U$tA*?ꏔB0X>'шx55|Xs=~|2Y A,s v"q{X@#fL)1 &C^s V+pA\`)+ AqF|M6X ~XE o^[k~̧(}/[{Qz*Nf39@!@g_FP9y50.AS_ynbYw%s~$?b=/b,@n^R^SH[RWU .#w`\$!r^3w{Cj #iGVQÝs+r|BtBs@^*2." Viq^s`  F+QB B+@QxȍO/'}˧O(u\1yȜ9,JnRukk<_6ׁ8vt{9R UH'U,@&rye-%{q󡒶@iV{_ b=.0"7S&Nz+dO/={hj9)3ɲޓЯUL)t"K9GaAimO{@"ބ DU 9xe4 n×'Kfz ڀZ-,\O.ه _y@-Q( _ q("ou@5~_FC2Yg4NER>.~{*n|F2L#a-h@|ڙݨ2  0=aeW4WRx APmWbpR1eI(׷dUo6~Ԭz} jX[ ][">» ÍwJOSJޥe$עy V8_|n>A}M+0*t! Zf2n;F X]xVYX)̠ҧ2 ?=k}5H+n?@ u\ E`#:s0 rv+О*OfE3Za;kVTJ<#o(a@o$eâ2)AVO󽨼ZR.." YDWҏ3|~`elj ^pib[@kנpJtn%|-~:TN>)T`ɊT~g}70aF  bg^RB}7jfV<[{>T~H1A$s? |ieN+_!2 $ -b7 l#"} `:ԗuktZD @FXQW| 7hQ`70_oO I[ FeS3~_?{YxCS܅AWe`,p—Ou)$z^݁73:h0 >4!j\q0PtGT%`ejwtU`-R}?hJ5v`0:QnFܹ(hԯDhKQ|zن|!~9JmӮc^$XA[6;.ؑ~X@iAo`U{8{W yq;h}ch qo+fjЊE7KRL%6 c0@=1CA~]@N$%Ck!h`Xf\ry!Rp$xcvQaoCJ/ܼP[i>$,R1N fI?m&A8H+3<$  +]OT` K1 `'`7 xQVƱEFgYD3At&?Hu) yN$2sA3s,7Mے4(ys@n@Nyo8ip'G5ah!=b+7඗:1dp|X7k.r5ț;<ݤ筲! x{J[)KUYk s>tJ'.LF2k DBM? v}TD!CᏂ?nI:'|gA\ûq! O| jOBs(ԧOpCP[-(7\ګ_`^`?Y/=~Z %wcgYS` wm|100 m^3-i'‚M2:;)40Xew^ :&;]tg`m]`92V\6q v ЮMCԳPwVdҁku`%m``ax# ︅~72ZyEdQ~cܘ#Ho!TZc:*wRn5'{ agMI*x_B2oGשAS[ CtsNyjFuN;Pg!&sЃ ֿDqg8HNEl ɖM }}Vju^S~sy7|x'ue}Ry4\Rk ?@ ::}}-^*/ d,tN}1E`N=@=NŰ )qiˆk3+xL!G` _'K`^0K߃_|O'`{(]> <B:QCLm}}2ٖd д ^a$PF1T hƕ%G{S',uJQGXAvx#cVD0(oo:ȭ}'tEU }aK8}P0WКd:H(~ @2j/9>"9])_wn*΋Kn$1t:'{ی0aWv&6-h 𢰉Yßa|޸"J@jR}օʲ/~xC/ fgP/&]`3@`K h+b@&:& Q>Yd<4LAϟ (H 7f݁E(v%r? [Q24d{t^;=P.o H:ͨ 0`b41->DrX8LWXS1v4;PW`I@x\CK87AqR(nx=_*)4Q~iThat- _{ߣ!(Q#%XA'HBn1P?ٺ=r9 ?7>}9 Gu1HD[!P~.2Q 0,ȡ| o@)"~fSOA>- *,M.[@ 8]8_!dJe봶&N*>Ad)1 89/dzTIk=n[_Y<V6W<~8ԣڳkQvYTI J\n 1p3q vnF@ 4&Fl5 `RBe~5d4Cq}:mm.5ʯ@J]}kگ΢=eMv-=/~]@2>D{@jŎgJd~&lz3$jRMA17$}/V:[[^ _AW!? [~\믢m~hn/e\ t<(;Y/W Rd{qjP$: {DWWy(8esƊy3 !cs:쬵MT)0)r4_?O7qcׁ(dud\ 4 p>yBYVKUR6eG{oҁvPXv|&5ps*V fp@kQG4-OS95ʋ{م笈ӕ24K-Pt3;8VWyÖd9n*0U z@`0~:R)&myxS A_B䷠z6 -! Jҫ>iumW?"%?7dcwiPAꘀ. i7 Ewxs@s k2dJslp@@nAb?P8ӀjZ$ ڀWu>DM[5;mUzXy($l])s>G*g/Rϔ*ې3~R`eG ؽS vV~ x"*и QG `!1w,@7 mNp4p҂'\'CM̀F6@M@}VwP T@_ A2"cـTwT=>}\CIE0S2-L]Ǐ^.Mo X7OK5Y'W{wb+l[ݱBivJ#@EMՈ,^&YAop޶BRS or:J11Ų̌]-X9718.@۸i^rέ%x0(I(_peB£L*}GY0[@̾ p>.-uXRxjH)s]bJ+Jkz9HwITCP(!Q엳ŷqyԿES&$cxT( u[ Vq<A!ʡډ@an W=ut:x'6.h7. 6VXZkr8 (,2^Ed$Lj*9K|ӖgaQvO)H+1 Un@ + P&u'@ :^Mu9@7tAA7@Q@j@8AhQS*,ycQi@ӁW`)$5jXw9SL16DHp~ O6/vAA k'S"7h-*gҬA -LB>_"v_GaG0ߏW% _v?*0ՊKݭO#@Dte (p4u~7HtP}ܘ kA;B0u.TRN~``{l|FĴj1Ha꩔ϭ,0QI^tJix4LEƋY B/% {>Q"sbwQ8D3Th7@Fc XB Jf$W`o9&t}T?O $J- 4XRT]PZ@7@BaWYi@4[žևr5Xm.0z 7>8 hۉ~{XwY L$z5 ]p8h}ArS}̂C9ڟW3L04 ȣT£VhV4~ᨐ|?JZ}G&.+COĖMrIu.tٰHbW4_:+ P62#ٳFiOBXtԝ `~* T~oXT!1_V}*ՎV`A@ ) 0v 6,NDA@kFNǭ*tx hDLP>T1U5#ܘReJ PAs^*˭0M NiƝ3YS d0PngBUDR}Tm@Gd9 P3 ӳ ~Tvd Dmp3qf}ph "JL?l7@ ~iua`  fbCG7ȥ[ ^eil n:im pjLY~Z;Tee܎2mP9[(AC?oWT(*:}(@ⱆ"Qs4oU@[Sa+2TZZ5RxIؔ_ClRMex͐f'!{;:5?p)68AT2A}uy/{ӑ_ wua~g/ ^.ǭ ]=*(eTPo'omK ] D:?7s9=hR_if(G)=X`zT*F)_7]7%5֤I;ݟȹGmd,YO1(e8JWRBi+-0~m[°ή] h@ ]}HDW}'{LΟb 3)Xe_rzǬo[P/[j]X,oc۝y Z}^\r ]06M@&4>u)l JԸ~NLj.p|t1t"_G Jxqui<5 WjU3@J[r4[1ǭc 6vdjPeQ+bA '!5h2 -LB˰ 46CQՂ7bc-*0hˆ!;`dK pNA(lS"ff`TO@ZPS/.,T;͇he`=T]o8 ԟ[/8I@ZI-A.`ՙ +JTFNeEQVv%($Z#abnE!@ϋf+?]r]3v]Yx)XHt,?cABd~ ocv%r?N[7]@7>@T>t|x?*jq +@nbm. Akn$Ko[`Hθ2k h<| L#Pr |ШNlGht`1swlUA&ĩ-%N'@T?.DAPܽE1- {MTx:DQyLS*a4݋[QywV3u:VZvA2C9 V<>L b7EDFd(`@Gy.(%/|{D_0LUį1v] ̄qڌg{xcqٯ^S͘4cJa5B 6N? Kh+@Af˔S:g`1s{JRzZ'F7ؕqQv1%oZ4]Zl|B)H4??|V6T *(nB|Q2Gk z}ЖE4!a g@ Q )ca˼e9\$EYb͛EȍBPŏ|.0n~Uw&1d۴MӈDQT<7VpL~"(HT*~ oRN{e+-+A* lrSPp#c=48$L_74*\jҁ il# \nJTn*ikk 7\C{uR"^-G׾]Tюӂ(T׮kS{F"ct|m:)Uk֬:}>T<*վ*QF;3zކ H.CȦjqPf\00>?,SMZ:5>L=?`*ߎ8oҁ(#[jXP :FSV`Kދbx!uТp 06e违 Y@_gre:`Pԅ@y<Ux.Cu*~=fզ grz20-=рy?q1L#ܣ\x$ 0: uK3n*hL66\T[9%7p,u ;6V~}ta6&"xOXHxw?el0`ZZe_;|lJ}@ Y{ejG㠟tY3زgY,g˨Qibd (q'P?FS tc`QĪB A?z!$I*xV@^ak\ Sթ94fhW%{y.w쇶EB 0p޿6"QV[2:玣;?Rgp=@[^Vzg`g+d Sֆ++fln49q:%f[= (i )W w [` B L@O ,E sPmృ<2 RsGO6y&ݟV͋XW +3 ` :Jq"؄V_0| TMB۵`o`V~C\30"ke]S1SCyp@aM9 @{s!+ n'Hp0 \ 3HM!3PCO7o>āA՛Y 1[x6B"QƋU׎[%i=@|>L 09Eվzxw\Pus>_..v1p4B O<;> cW7pğo} |^/&"bw(d6T47p,'TX@CP}W 05lg ?/4U~<h%OA@S*{8{r+`tMfEƒ+L^cD@)zD <=Ձ6w=3؀*:x_yQ=)~dW'W~CZAZLO^3;ȯS;X {&ޯ'D ʠBAl@ ``C[S1LCXn@)@T .U}1 ا2q }Jֆee3BLAH<߀֍ij 8Yαf x`txR͌DfZpf+ xG!G!/L( @jQ/迗w85PCIVV+_uȝi7z>aÍm,ڄNy[| :w27;3|-=EMj88AfNLpp™ZGILmBx*tP5JK\?U'@ 8>VjPkzMPQ1D* ݭ uڕ3p컟Q~[c߻? ;Ӗ)#T)A ln(KwɸM~;W:ݣ˺/-+?mVAee$Xnq|z Ϭbv(̢c= ۂ 3n6M7<ւG|`3qx7C`dЇhl<9݀V2`tg a ?3$RVf !iwEJnR2͐l P=;'SJM5 CK|WYto@;[n\  9S,b||+Pt3s v~ Gozai:=1цgFzϓ]S=ofʮ}<}nw%dSq$ȏ,N9ppoYO5w9JV  mƯ}/0hq& px$X[P-R I$?~ϴaۆ<\%ӟo7zTSg>?LӛǞEMW; ݚ}M&PovB"c \w@ɔ`Җ.Ohkwƺ^w&B$k/ƩP]ȃ8yW־{v $[b z% io=< x]gf[™ڪXO0p81/)_4%Yp[OQ15ޱ}턾d%mp #G3@AX=Zg*p=0@;x7`-/+gZ5t7`+9 x>gL;)UsYR73g^]Qf3 M=ЖKfܨZ3]} ~XNԟ++H (Xmy ;a균! /®@{$l ~xYmSB5MZNt@p.k2 go@kweY$|p-MD3;{md-zDz/!^}YB'T sjT'w ='e)+n' kw37 rku@0v'( E2}Q3 e ;Ѐc[p RK ȺKB'7@ϟg$^8~űw~4Rp?7^U5A6[ѴS_8Ӏ^F5 Ľd1g-?,L<{z';DX %+H?nzhp%)= 0 P| lXQ, rs.OA(gK5b `}O֠pݟfOwO^5 ]Tn~ŠlRC]=;݆{rg^}]w@EADWd}Pׂ} Z}5|ߢN\.؃b<:\Wusu ,@X੿(r ` Rv[7|Y+`l8^iv߯R= *]2gp YÕ;cc纨c 1OEÉa >=%lMϿF߹ oȊJwt>MxްF y~P{ h!fX?ߌ=}}L1}zn_ARRYv} N>M\U͸}+. x 159YCe7R YAQsY@^ێY@O߿? 8o~0= Rؼ>$Zy\30tȩ<\5d||@hL3P7xu2ɳ&gC}?_`AZ9?j15{݂6w@DzqJ Dt08Ў/v9Uu @FMgK0Y+A;aX3.aSm8<|'iy`oe \7G ^i\-ŮG)'h3A}zu9EyN!ش_ƩA5 ы.Mxj5 Fup 6 tk ϛU~PW,;!ȣ{n)$}OVdW_yT[ӫ+WQiVIM<_|>qT7WOP@2Qu93/ BQ'ڦz-Z_ꤶѪS>z.NM4[f L~<Sjղ(9p9i=0KQ>mج ?gYpOV~mN+4 (0P޸lؒ/GL8:J'%HApʭŋsɧB~l-˶GkpBUX6 +6|$ӌ0-;W!z?_G߯mo7*"W.h ܸ70 @Pٺ7_HȐк 0H7Ehsa޿7#Cܐ|, Zp\uhEp˵Ĺ$$ ̛GЬסvL?GJO>sR_R=?' HTf z.  ahfխ.O>9z؂rTtSc}+zua  ~=Ǟ?ي 8i:+\7vI_LˁW\é>> MhLO\ǑF}:r;*U0S+h޺ A`>EyhHmPaQ,\,W >لW_1WpEDjsa~o< +>ڡQ4f΢bK7UO*hp@@/,bF 6ByA@\(D@+ *jt[" {oY/>0 C>\!x ~㰮\ Zf|O[H4q?'Vl*8ظ7K0i-inL L/6 tـp"+t\wga @/@\#'pS54CưC6Pz%h|Ѷb?{,V?#xⶅʓUHg~cCF)Eذ l6 ܃%Cͽ]sRtU{*!.D ށpkJC 6`Q-e }s}\ݏV``ז ^{U!ʧ3e7/)}}PinAZLj3uYʿYy1kʆs<@* (o@, tKXGtK,/4z#pXo a| JSNYC^7.V^ O1Zhn` !(bӢDQP?<8޻5lBxf8y ?R}PR=UiЪWQ[Hj[ӯ/LVS@o#&@D<_bpx SX&#@W^Jy1' @;oIV}iVkm9rzgvۏWVS@zv(P d!nK9(V(N%Y@l=z H yΡˡ[B`|I*jTچl}B |>c|_q [6LA3e8p r"9 /+pUΣ^Ao?Ya.41wd{M dP/@W)WЋy.jm -TC>;7L@+NV` 7A,ZVEˁ4JSKW` @FS`=܀^M+9R+t_4$DϹ'x|6B%ŏB+ ZMeOYUXկ߃Ն ;u ny]9hP7]}AjJ MO!O5Kt+_=6KwZ["A U f%1^H>x57A89֯h|2iSa:бY96ڗjW(4tQ~V|A?~4aFuk GCQRʽ-O"d΃ @(U0kHr6lhCo#hgQx{58|UnCf;k8hY.d'6]w_b r q^gWK|Qb*+=YPjv*_ŖU8@AC(b:K@7# daD+Q|{)W P aT<,\hlOCT?P 5i@Zm)lTyL߱U78~QW{σy OtL*""^oc쾮~޾Ͼr!E)dQR-K1R-ɊvYq?Q@ĖEĖbIEoEm#q3Eq'Erַמﹿ;3p޳{o|9sm`O @P-CaF$Ыs4<ߺ}Dm O[q[EsCza מ~-8j _g7{nZ-y|./v3Ky3 zq-U߾0GU}gY6y0 `ҭGx}~j~xJ췓Í$`2kw"lW`x@/)ґq>t4lBRb$ e 5hd9w`w#ȳ?ۯ}3Z@+8?er  =tI VY0 9)P1X[R" 9=ŕS[\YM/%x= c1fZJ<ٜH%^/3(ƥPUւ (s~~P:ʼ෬+)XK fg~(j*+TNBSժ)<z?nLJo}+9*J4.ٿ`EWC\G,+ Yp GɃX2] n`uY@`vf>=)} &t6*i@P %3zඈX9BOS.BHy/Uj6߿|w0H9do $Onc3 y|Xb8.MLQcw-(ylf_lP/#áUk=,OoEt=yH薭aekV \:J|SqUfZCV`!!%.RGX6I]*Țī*@՜TI:uf >y(9!TNn`8w2^ߡ5%i :6꣝[X *igc\Dpy! g}փS;:1Jr7FSVZR @p~8Cu)yx.fN% qOO1:JetcCG\U!4ߺeqrXp i 3|I2ْ-'V@iZ/bFuB}RPG$O8@^Y@+ʱVf )Ў-EiH!)gZ^gX+2CAnc?u@7m_yJM?{R,9_E P{jP¦A!ax]ԿMV ـ0 V*P1_\GC nIr7f s@\V@v>=rW޷LT:s+xTR}UҙjVӏ}-jZ}Lvߣ$k,~_v<AaN÷YT=`H;E(P @%X J }fD;6ٽmΊՇVBllto3t^܎ꢟ-y l/ebT.'1XcaH0(N ̏DGo wXK@eR>A3%Vhm VٲGi| =E?[BJ[oRrvJd7b7 `lYtR܂k>[f~m0=3+饲eiz]nH߿Ȏ@0"z9DJ;*% Y}j/}֛Au 65п#z'I('%)rcIDBnѻT)._#W!#IZdn HB^-hR TE)y5@}~lNtz6wrɯ\7=NAo xd,@Nӡ4B:oIqrmTQL3NOeː<(b2[ 0I& T?,?Hʨ?Mp JrSFN+8beBj (˒05y\a؜4ZUv fyI[%Сyog2 hB7mpb# vʦ쎔$ο˃ }o֛AOel$#+. ![iX X[H6rjs,D4Qeϧ;}'M.pu:G2wy2eI!&3'~B< $ M ,?@ IQ'"XkU Xjt*MC]N||>~װvߗ(m + H@ Q潲f ` AJ/^| uů=r:uj4.@^\h(R{[y,'V$Ff=Հ$MSPٲT`z6@Vg`X VV"b'`lv PzsO> :xQ!/G1h=v9TpJ@ȯ܁c!wdCj{pX9Љ)~\^tybT$X-nj xU ] ?)IXY' 2`O4s@C˃3hIO34.V.rwm"1%/X5f.i 7Z^ɝ+fOTbF<@/CV"dY.y=յu_b,xL[ynb6Dm6#ƛ4޸s*Z Wj5E:d2*! `Z|#vߋ*m@ 𪀠{l"ds # VgT/;ݻ)IHǟfrD\[W̻\'m_9 TpY T@xb@対x  !Gl zkTՀ,E:t:&A+rqffK{fr|?W7Xqk F Kp+j^JKJK6KU ( D/w#)u |fzFj*tl xˇݺ'`*(UhU,3؅dž3K x6 {l^8¦a3O/a#.Pz%Ȇs>g1h0vns+ v; ٖ`0 @ kۮ\M񍬋bGgw PͪmK {R~'miO5m'mÓ|U*I z#U_2Z~'qER~ejC>/:VBt @" v@((zji!φܥJF@"\.Wi WKp1␓5RcS>Lb]uA cX(Z~3lF :J{ί3|E-+"n y*;9k`g'E0\S C2פب.mC C5?̈́I10ywN|֡ ˲}=^)P|vZ]N\ֹ"% 3,X,(Hos(Q砍 Pjȹ7m\Bɜ ,`FG1rT\2?0EabnHɾ} E5Hgf>w100Mp98[N:zD/7wCY\.&}w'Yѽn^e Bg@uS0`$H"=!m΀T\)RD[n;>> /\iوk`ƍrYL^)_L,xISX^)U)/R"b6IŹ85*QeʖAg͕o**+5>飍t48}}]y `M|ҽ`zer` ~8p_mccY??2AaB6oތ mڴo߾&FfuJcpm@@w?<, J799)@Bb@t /sVHR2JHY ;\I+>pWpxЦ??яwtٳ痬%9 S {7cڇJ #XxDGs8b1J*g{(<@k7(@EuwX1?200 > fAڱc(B~€4;ǀ2Y~ Y>a@qx|P~XQ ˗/K> ˛oi:L aVyi .0ܹSnWtm#2u8 81 0_ϟgJN {017C䡇廬;1œyY-"̺P> 7mk@h Y aa"@qD+?iV?l:L 0?%V?oPv,2@@N= AznL}(}v RڙQx)p:& vڽ8p{017d޽ PjXw#sϛ ,,W H`YE@P~">a/Šg:L :L+O5` <ڴ Ŷ+Vz:PbJtk+@[,$oQt1Љ'ɓ2Bb] 5 ^ەU+25V6vhAelllY@ǎrxU(ף?L1PLj@ d*Lm{1 ˁ:Ctl@[zv݂++-Œb d\ (nb'xl?EA ,)@~|7 +$h$~zmci/kPD(@kY @qgŸ׹E55|›bf@b (4CfL}VXײpDRNY>@@+}@y7C :@O1N@u6 x805mY p-@(H7K a 5V")L@Pl(:8<ׁch ^`~ܐ{>3?b {޽.B-(ʮv8h`h7߯ktHw3r ܐ~wXu+-XKiiѹPJ(;@;Y^ZA5gHA:M 0yG+YKapv4(?Z@Z@8Lb <~[!&wuX*\(8ogs`kVX_ &Jo/°@-v%pn:O 0ٻwه-U WR sb ]2$!(µb m؋0p N>]>uꔷݿ_#{ܹ7Pб=g]{B@`:W @:.x"@_x:qС[5X :PS̏Vbi?q j{b/ih A?5+;4v B gzկ~%{ FUW/5X :K0eͮ|>8o׮]2h3^ s:?U K >W1cǞC _~|9Նab>Wv۰?v۷?>pr`,aMCL}qESM>|Wls}FVC Vvo|| 6m4{/}/Ϥn }`Qya.˵ۛǘ Ph  aA`-?77>.7<՗k5^VrOy#@0 ?.AoثK` hW?,f`^Mgk=u@w遰PfƳC8rVk_94ԖR<)#=6Sk ̜Kʍ }AH쯻-ue<&xĬZ:a?J[vd1XUVG|R o?2#Ha,}t#`],IxYZ5-'ȩ_G1XJLV^Ga򸋔;ХХ:OuEixV5۾Or"t[pl1پ$بK-+~2ઈ7SVA񁥔QQMalGNj,E2&EJٗR5Nl4vʿ5KTV.RKZ+df"k':=_Jeչ7ƍD}-h6n4>/Q5VZhvE?nf7SYܮ̭}+kѝh8V 2`FP :Gg~/5 LpL; h6cv5F,lt6 ,{NB[\ #",i Z֊[mx 1D Y{H"IENDB`irace/vignettes/irace-package.Rnw0000644000176200001440000052354114752430247016533 0ustar liggesusers% !Rnw weave = knitr %%% DO NOT EDIT the .tex file directly since it is generated from the .Rnw %%% sources. %\VignetteEngine{knitr::knitr} %\VignetteIndexEntry{irace package: User Guide} %\VignetteDepends{knitr} %\VignetteCompiler{knitr} \synctex=1 \RequirePackage{xparse} \RequirePackage[dvipsnames]{xcolor} \documentclass[a4paper,english]{article} \usepackage[utf8]{inputenc} \usepackage[T1]{fontenc} \usepackage{lmodern} \usepackage[a4paper]{geometry} % It saves some pages \usepackage[english]{babel} \usepackage{ifthen} \newboolean{Release} \setboolean{Release}{true} \usepackage{calc} \usepackage{afterpage} \usepackage{algorithm,algorithmic} \usepackage{booktabs} \usepackage{tabularx} \usepackage{xspace} \usepackage{amsmath,amssymb} \usepackage{relsize} \usepackage{fancyvrb} \usepackage{underscore} \usepackage{microtype} % \texttt{test -- test} keeps the "--" as "--" (and does not convert it to an en dash) \DisableLigatures{encoding = T1, family = tt* } \usepackage[hyphens]{url} \usepackage{hyperref} \usepackage[numbers]{natbib} \usepackage[nottoc]{tocbibind} %% For autoref \hypersetup{ colorlinks, linkcolor={red!50!black}, citecolor={blue!50!black}, urlcolor={blue!70!black} } \addto\extrasenglish{% \def\sectionautorefname{Section} \let\subsectionautorefname\sectionautorefname \let\subsubsectionautorefname\sectionautorefname } \usepackage[titletoc, title]{appendix} % Fix use with \autoref \newcommand*{\Appendixautorefname}{Appendix} \usepackage{tocloft} \setlength{\cftsubsecnumwidth}{3em}% Set length of number width in ToC for \subsection \usepackage[inline]{enumitem} \setlist[enumerate]{leftmargin=*,widest=00} \setlist[itemize]{leftmargin=1.5em} %% FIXME: listing is very limited, we should use 'minted' \usepackage{listings} \lstdefinestyle{BashInputStyle}{ language=bash,% basicstyle=\ttfamily,% numbers=none,% frame=tb,% rulecolor=\color{lightgray}, % framesep=1ex, framexleftmargin=1ex, columns=fullflexible,% backgroundcolor=\color{yellow!05},% linewidth=\linewidth,% % xleftmargin=1\linewidth,% identifierstyle=\color{darkgray},% keywordstyle=\color{darkgray},% keywordstyle={[2]\color{Cyan}},% keywordstyle={[3]\color{olive}},% stringstyle=\color{MidnightBlue},% commentstyle=\color{RedOrange},% morestring=[b]',% showstringspaces=false } \DefineVerbatimEnvironment{Code}{Verbatim}{} \DefineVerbatimEnvironment{CodeInput}{Verbatim}{fontshape=rm} \DefineVerbatimEnvironment{CodeOutput}{Verbatim}{} \newenvironment{CodeChunk}{}{} \newcommand{\IRACEHOME}[1]{\hyperlink{irace_home}{\path{$IRACE_HOME}}\path{#1}} \providecommand{\keywords}[1]{\textbf{\textit{Index terms---}} #1} % Simple font selection is not good enough. For example, |\texttt{--}| % gives `\texttt{--}', i.e., an endash in typewriter font. Hence, we % need to turn off ligatures, which currently only happens for commands % |\code| and |\samp| and the ones derived from them. Hyphenation is % another issue; it should really be turned off inside |\samp|. And % most importantly, \LaTeX{} special characters are a nightmare. E.g., % one needs |\~{}| to produce a tilde in a file name marked by |\file|. % Perhaps a few years ago, most users would have agreed that this may be % unfortunate but should not be changed to ensure consistency. But with % the advent of the WWW and the need for getting `|~|' and `|#|' into % URLs, commands which only treat the escape and grouping characters % specially have gained acceptance \makeatletter \DeclareRobustCommand\code{\bgroup\@makeother\_\@makeother\~\@makeother\$\@noligs\@codex} \def\@codex#1{\texorpdfstring% {{\text{\normalfont\ttfamily\hyphenchar\font=-1 #1}}}% {#1}\egroup} \makeatother \let\proglang=\textsf \newcommand{\pkg}[1]{{\fontseries{b}\selectfont #1}} \newcommand{\aR}{\proglang{R}\xspace} \newcommand{\MATLAB}{\proglang{MATLAB}\xspace} \newcommand{\eg}{e.g.,\xspace} \newcommand{\SoftwarePackage}{\pkg} \newcommand{\ACOTSP}{\SoftwarePackage{ACOTSP}\xspace} %% How to use this command: % Parameter with one short switch: \defparameter[short]{paramName}{long}{default} % Parameter without short switch: \defparameter{paramName}{long}{default} % Parameter without switch: \defparameter{paramName}{}{default} \newcommand{\defparameter}[4][]{% \item[\code{#2}]\hypertarget{opt:#2}{} ~~ % \ifthenelse{\equal{#3}{}}{}{% \emph{flag:} % \ifthenelse{\equal{#1}{}}{}{% \code{-#1}~~~\emph{or}~~~}% \code{--#3} ~~ }% \emph{default:}~\texttt{#4} \\ } \newcommand{\parameter}[1]{\hyperlink{opt:#1}{\code{#1}}} \newcommand{\iracefun}[1]{\href{https://mlopez-ibanez.github.io/irace/reference/#1.html}{\code{#1()}}} %\usepackage{showlabels} %\showlabels{hypertarget} \newcommand{\irace}{\pkg{irace}\xspace} \newcommand{\Irace}{\pkg{Irace}\xspace} \newcommand{\race}{\pkg{race}\xspace} \newcommand{\FRACE}{\text{F-Race}\xspace} \newcommand{\IFRACE}{\text{I/F-Race}\xspace} \newcommand{\PyImp}{\pkg{PyImp}\xspace} \newcommand{\iraceversion}{\Sexpr{packageVersion("irace")}} \newcommand{\Niter}{\ensuremath{N^\text{iter}}\xspace} \newcommand{\Nparam}{\ensuremath{{N^\text{param}}}\xspace} \newcommand{\iter}{\ensuremath{j}\xspace} \newcommand{\Budget}{\ensuremath{B}\xspace} \newcommand{\Budgetj}{\ensuremath{\Budget_{\iter}}\xspace} \newcommand{\Bused}{\ensuremath{\Budget_\text{used}}\xspace} \newcommand{\Ncand}[1][]{\ensuremath{N_{#1}}\xspace} \newcommand{\Mui}{\ensuremath{\mu_{\iter}}\xspace} \newcommand{\Nmin}{\ensuremath{N^\text{min}}\xspace} \newcommand{\Nsurv}{\ensuremath{N^\text{surv}}\xspace} \newcommand{\Nelite}{\ensuremath{N^\text{elite}}\xspace} \newcommand{\Nnew}{\ensuremath{N^\text{new}}\xspace} \newcommand{\bmax}{\ensuremath{b^\text{max}}\xspace} \newcommand{\bmin}{\ensuremath{b^\text{min}}\xspace} \newcommand{\Celite}{\ensuremath{\Theta^\text{elite}}\xspace} \ifthenelse {\boolean{Release}}{% \newcommand{\MANUEL}[1]{} \newcommand{\LESLIE}[1]{} \newcommand{\THOMAS}[1]{} }{% \newcommand{\MANUEL}[1]{{\footnotesize\noindent\textbf{\color{red}[~MANUEL: #1~]}}} \newcommand{\LESLIE}[1]{\footnote{\noindent\textbf{[ LESLIE: #1 ]}}} \newcommand{\THOMAS}[1]{\footnote{\noindent\textbf{[ THOMAS: #1 ]}}} } \newcommand{\hide}[1]{} \usepackage{tcolorbox} \newcommand{\infoicon}{% \parbox[c]{0.75cm}{\includegraphics[keepaspectratio=true,width=0.75cm]{light-bulb-icon}}% \hspace{1em}} \newcommand{\warningicon}{% \parbox[c]{0.75cm}{\includegraphics[keepaspectratio=true,width=0.75cm]{Warning-icon}}% \hspace{1em}} \definecolor{LightGray}{RGB}{193,193,193} \definecolor{LightYellow}{RGB}{253,247,172} \newlength\macroiconwidth \newenvironment{xwarningbox}{% \setlength{\fboxrule}{3.0\fboxrule}% \setlength{\fboxsep}{0\fboxsep}% \begin{tcolorbox}[colback=LightYellow,colframe=LightGray,boxrule=\fboxrule,boxsep=\fboxsep]% \infoicon% \settowidth{\macroiconwidth}{\infoicon}% \begin{minipage}[c]{\columnwidth - \macroiconwidth - 2.0\fboxrule - 2.0\fboxsep} \raggedright\footnotesize % }{% \end{minipage} \end{tcolorbox} % } % Workaround for broken knitr: https://github.com/yihui/knitr-examples/blob/master/036-latex-if.tex \providecommand{\hldef}[1]{\textcolor[rgb]{0.345,0.345,0.345}{#1}}% \providecommand{\hlsng}[1]{\textcolor[rgb]{0.192,0.494,0.8}{#1}}% <>= library(knitr) knit_hooks$set(inline = function(x) { if (is.numeric(x)) return(knitr:::format_sci(x, 'latex')) highr::hi_latex(x) }) @ \begin{document} <>= library(knitr) @ \author{Manuel L\'opez-Ib\'a\~nez, Leslie P\'erez C\'aceres, J\'er\'emie Dubois-Lacoste,\\ Thomas St\"utzle and Mauro Birattari \\IRIDIA, CoDE, Universit\'e Libre de Bruxelles, Brussels, Belgium} \title{The \irace Package: User Guide} \date{Version \iraceversion, \today} %\keywords{automatic % algorithm configuration, racing, parameter tuning, \aR} \maketitle \tableofcontents %Load files needed for examples <>= library("irace") load("examples.Rdata") # loads "experiment" and "output" iraceResults <- irace::read_logfile(system.file(package="irace", "exdata", "irace-acotsp.Rdata", mustWork=TRUE)) log_ablation_file <- system.file(package="irace", "exdata", "log-ablation.Rdata", mustWork = TRUE) load(log_ablation_file) options(width = 70) @ \newpage %% %% %% %% General info %% %% %% \section{General information} \MANUEL{Some things could be taken from the intro of the irace paper and reformulated.} \MANUEL{It would be good to mention that not only opt algorithms can be configured with irace, we say this in the paper.} \subsection{Background} \MANUEL{I would add a paragraph defining what is irace (a bit longer than the abstract above) and references to the literature so people can find more info. The first reference should be the irace TR.} \LESLIE{Here i guess we should say why tune an algorithm is a good idea, and why using irace is a better one.} The \irace package implements an \emph{iterated racing} procedure, which is an extension of Iterated F-race (\IFRACE)~\cite{BirYuaBal2010:emaoa}. The main use of \irace is the automatic configuration of optimization and decision algorithms, that is, finding the most appropriate settings of an algorithm given a set of instances of a problem. However, it may also be useful for configuring other types of algorithms when performance depends on the used parameter settings. It builds upon the \pkg{race} package by Birattari and it is implemented in \aR. The \irace package is available from CRAN: % \begin{center} \url{https://cran.r-project.org/package=irace} \end{center} % More information about \irace is available at \url{https://mlopez-ibanez.github.io/irace}. \subsection{Version} The current version of the \irace package is \iraceversion. Previous versions of the package can also be found in the \href{https://cran.r-project.org/package=irace}{CRAN website}. The algorithm underlying the current version of \irace and its motivation are described by \citet{LopDubPerStuBir2016irace}. The \textbf{adaptive capping mechanism} available from version $3.0$ is described by \citet{PerLopHooStu2017:lion}. Details of the implementation before version 2.0 can be found in a previous technical report~\cite{LopDubStu2011irace}. % \begin{xwarningbox} Versions of \irace before 2.0 are not compatible with the file formats detailed in this document. \end{xwarningbox} \subsection{License} The \irace package is Copyright \copyright{} \the\year\ and distributed under the GNU General Public License version 3.0 (\url{http://www.gnu.org/licenses/gpl-3.0.en.html}). The \irace package is free software (software libre): You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. The \irace package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Please be aware that the fact that this program is released as Free Software does not excuse you from scientific propriety, which obligates you to give appropriate credit! If you write a scientific paper describing research that made substantive use of this program, it is your obligation as a scientist to (a) mention the fashion in which this software was used in the Methods section; (b) mention the algorithm in the References section. The appropriate citation is: \begin{itemize}[leftmargin=3em] \item[] Manuel López-Ibáñez, Jérémie Dubois-Lacoste, Leslie Pérez Cáceres, Thomas Stützle, and Mauro Birattari. The \irace package: Iterated Racing for Automatic Algorithm Configuration. \emph{Operations Research Perspectives}, 3:43--58, 2016. doi:~\href{http://dx.doi.org/10.1016/j.orp.2016.09.002}{10.1016/j.orp.2016.09.002} \end{itemize} \section{Before starting} \MANUEL{I think this could be a bit more detailed by defining what is a parameter, a configuration, an instance, etc. but ok for now.} The \irace package provides an automatic configuration tool for tuning optimization algorithms, that is, automatically finding good configurations for the parameters values of a (target) algorithm saving the effort that normally requires manual tuning. \begin{figure}[t] \centering \includegraphics[width=0.6\textwidth]{irace-scheme} \caption{Scheme of \irace flow of information.} \label{fig:irace-scheme} \end{figure} Figure~\ref{fig:irace-scheme} gives a general scheme of how \irace works. \Irace receives as input a \emph{parameter space definition} corresponding to the parameters of the target algorithm that will be tuned, a set of \emph{instances} for which the parameters must be tuned for and a set of options for \irace that define the \emph{configuration scenario}. Then, \irace searches in the parameter search space for good performing algorithm configurations by executing the target algorithm on different instances and with different parameter configurations. A \parameter{targetRunner} must be provided to execute the target algorithm with a specific parameter configuration ($\theta$) and instance ($i$). The \parameter{targetRunner} function (or program) acts as an interface between the execution of the target algorithm and \irace: It receives the instance and configuration as arguments and must return the evaluation of the execution of the target algorithm. The following user guide contains guidelines for installing \irace, defining configuration scenarios, and using \irace to automatically configure your algorithms. %% %% %% %% Installation %% %% %% \section{Installation} \subsection{System requirements} \begin{itemize} \item \aR ($\text{version} \geq 3.2.0$) is required for running irace, but you don't need to know the \aR language to use it. \aR is freely available and you can download it from the \aR project website (\url{https://www.r-project.org}). See \autoref{sec:installation} for a quick installation guide of \aR. \item For GNU/Linux and OS X, the command-line executable \code{parallel-irace} requires GNU Bash. Individual examples may require additional software. \end{itemize} \subsection{\irace installation} \label{sec:irace install} The \irace package can be installed automatically within \aR or by manual download and installation. We advise to use the automatic installation unless particular circumstances do not allow it. The instructions to install \irace with the two mentioned methods are the following: \subsubsection[Install automatically within R]{Install automatically within \aR{}} Execute the following line in the \aR console to install the package: <>= install.packages("irace") @ Select a mirror close to your location, and test the installation in the \aR console with: <>= library("irace") q() # To exit R @ Alternatively, within the \aR graphical interface, you may use the \code{Packages and data->Package installer} menu on OS X or the \code{Packages} menu on Windows. \subsubsection{Manual download and installation} From the \irace package CRAN website (\url{https://cran.r-project.org/package=irace}), download one of the three versions available depending on your operating system: \begin{itemize} \item \code{irace_\iraceversion.tar.gz} (Unix/BSD/GNU/Linux) \item \code{irace_\iraceversion.tgz} (OS X) \item \code{irace_\iraceversion.zip} (Windows) \end{itemize} To install the package on GNU/Linux and OS X, you must execute the following command at the shell (replace \code{} with the path to the downloaded file, either \code{irace_\iraceversion.tar.gz} or \code{irace_\iraceversion.zip}): % \begin{lstlisting}[style=BashInputStyle] R CMD INSTALL \end{lstlisting} To install the package on Windows, open \aR and execute the following line on the \aR console (replace \code{} with the path to the downloaded file \code{irace_\iraceversion.zip}): %\LESLIE{Check that this actually works on internet says that this: \code{Rscript -e "install.packages('foo.zip', repos = NULL)"} also works} % <>= install.packages("", repos = NULL) @ If the previous installation instructions fail because of insufficient permissions and you do not have sufficient admin rights to install \irace system-wide, then you need to force a local installation. \subsubsection{Local installation} Let's assume you wish to install \irace on a path denoted by \code{}, which is a filesystem path for which you have sufficient rights. This directory \textbf{must} exist before attempting the installation. Moreover, you must provide to \aR the path to this library when loading the package. However, the latter can be avoided by adding the path to the system variable \code{R_LIBS} or to the \aR internal variable \code{.libPaths}, as we will see below.\footnote{% On Windows, see also \url{https://cran.r-project.org/bin/windows/base/rw-FAQ.html\#I-don_0027t-have-permission-to-write-to-the-R_002d3_002e3_002e1_005clibrary-directory}.} On GNU/Linux or OS X, execute the following commands to install the package on a local directory: \begin{lstlisting}[style=BashInputStyle] export R_LIBS_USER="" # Create R_LIBS_USER if it doesn't exist mkdir $R_LIBS_USER # Replace with the path to the downloaded file. R CMD INSTALL --library=$R_LIBS_USER # Tell R where to find R_LIBS_USER export R_LIBS=${R_LIBS_USER}:${R_LIBS} \end{lstlisting} On Windows, you can install the package on a local directory by executing the following lines in the \aR console: <>= # Replace with the path to the downloaded file. # Replace with the path used for installation. install.packages("", repos = NULL, lib = "") # Tell R where to find R_LIBS_USER. # This must be executed for every new session. .libPaths(c("", .libPaths())) @ \subsubsection{Testing the installation and invoking irace} Once \irace has been installed, load the package and test that the installation was successful by opening an \aR console and executing: <>= # Load the package library("irace") # Obtain the installation path system.file(package = "irace") @ The last command must print out the filesystem path where \irace is installed. In the remainder of this guide, the variable \code{\$IRACE_HOME} is used to denote this path. When executing any provided command that includes the \code{\$IRACE_HOME} variable do not forget to replace this variable with the installation path of \irace. On GNU/Linux or OS X, you can let the operating system know where to find \irace by defining the \code{\$IRACE_HOME} variable and adding it to the system \code{PATH}. Append the following commands to \path{~/.bash_profile}, \path{~/.bashrc} or \path{~/.profile}: % %<>= \begin{lstlisting}[style=BashInputStyle] # Replace with the irace installation path export IRACE_HOME= export PATH=${IRACE_HOME}/bin/:$PATH # Tell R where to find R_LIBS_USER # Use the following line only if local installation was forced export R_LIBS=${R_LIBS_USER}:${R_LIBS} \end{lstlisting} %@ Then, open a new terminal and launch \irace as follows: %<>= \begin{lstlisting}[style=BashInputStyle] irace --help \end{lstlisting} %@ On Windows, you need to add both \aR and the installation path of \irace to the environment variable \code{PATH}. To edit the \code{PATH}, search for ``Environment variables'' in the control panel, edit \code{PATH} and add a string similar to \path{C:\R_PATH\bin;C:\IRACE_HOME\bin\x64\} where \code{R_PATH} is the installation path of \aR and \code{IRACE_HOME} is the installation path of \irace. If \irace was installed locally, you also need to edit the environment variable \code{R_LIBS} to add \code{R_LIBS_USER}. Then, open a new terminal (run program \code{cmd.exe}) and launch \irace as: % %<>= \begin{lstlisting}[style=BashInputStyle] irace.exe --help \end{lstlisting} %@ Alternatively, you may directly invoke \irace from within the \aR console by executing: <>= library("irace") irace_cmdline("--help") @ \section{Running irace}\label{sec:execution} Before performing the tuning of your algorithm, it is necessary to define a tuning scenario that will give \irace all the necessary information to optimize the parameters of the algorithm. The tuning scenario is composed of the following elements: \begin{enumerate} \item Target algorithm parameter description (see \autoref{sec:target parameters}). \item Target algorithm runner (see \autoref{sec:runner}). \item Training instances list (see \autoref{sec:training}) \item \irace options (see \autoref{sec:irace options}). \item \textit{Optional:} Initial configurations (see \autoref{sec:initial}). \item \textit{Optional:} Target algorithm evaluator (see \autoref{sec:evaluator}). \end{enumerate} These scenario elements can be provided as plain text files or as \aR objects. This user guide provides examples of both types, but we advise the use of plain text files, which we consider the simpler option. For a step-by-step guide to create the scenario elements for your target algorithm continue to \autoref{sec:step}. For an example execution of \irace using the \ACOTSP scenario go to \autoref{sec:example}. \subsection{Step-by-step setup guide}\label{sec:step} This section provides a guide to setup a basic execution of \irace. The template files provided in the package (\IRACEHOME{/templates}) will be used as basis for creating your new scenario. Please follow carefully the indications provided in each step and in the template files used; if you have doubts check the the sections that describe each option in detail. \begin{enumerate}[leftmargin=*] \item Create a directory (\eg~\path{./tuning/}) for the scenario setup. This directory will contain all the files that describe the scenario. On GNU/Linux or OS X, you can do this as follows: %<>= \begin{lstlisting}[style=BashInputStyle] mkdir ./tuning cd ./tuning \end{lstlisting} %@ \item Initialize the tuning directory with template config files. On GNU/Linux or OS X, you can do this as follows: %<>= \begin{lstlisting}[style=BashInputStyle] irace --init \end{lstlisting} %@ \item Define the target algorithm parameters to be tuned by following the instructions in \code{parameters.txt}. Available parameter types and other guidelines can be found in \autoref{sec:target parameters}. \item \textit{Optional}: Define the initial parameter configuration(s) of your algorithm, which allows you to provide good starting configurations (if you know some) for the tuning. Follow the instructions in \code{configurations.txt} and set \parameter{configurationsFile}\code{="configurations.txt"} in \code{scenario.txt}. More information in \autoref{sec:initial}. If you do not need to define initial configurations remove this file from the directory. \item Place the instances you would like to use for the tuning of your algorithm in the folder \path{./tuning/Instances/}. In addition, you can create a file (\eg~\code{instances-list.txt}) that specifies which instances from that directory should be run and which instance-specific parameters to use. To use such an instance file, set the appropriate option in \code{scenario.txt}, e.g., \parameter{trainInstancesFile} \code{= "instances-list.txt"}. See \autoref{sec:training} for guidelines. \item Uncomment and assign in \code{scenario.txt} only the options for which you need a value different from the default. Some common options that you might want to adjust are: \begin{description} \item[\parameter{execDir}] (\code{--exec-dir}): the directory in which \irace will execute the target algorithm; the default value is the current directory. %\item[\parameter{logFile}] (\code{--log-file}): a file the name of the results \aR data file that produces \irace. \item[\parameter{maxExperiments}] (\code{--max-experiments}): the maximum number of executions of the target algorithm that \irace will perform. \item[\parameter{maxTime}] (\code{--max-time}): maximum total execution time in seconds for the executions of \code{targetRunner}. In this case, \code{targetRunner} must return two values: cost and time. Note that you must provide either \parameter{maxTime} or \parameter{maxExperiments}. \item[\parameter{trainInstancesDir}] (\code{--train-instances-dir}): set to \path{./Instances} if you put the training instances in that folder as instructed above. \end{description} For setting the tuning budget, see \autoref{sec:budget}. For more information on \irace options and their default values, see \autoref{sec:irace options}. \item Modify the \code{target-runner} script to run your algorithm. This script must execute your algorithm with the parameters and instance specified by \irace and return the evaluation of the execution and \textit{optionally} the execution time (\code{cost [time]}). When the \parameter{maxTime} option is used, returning \code{time} is mandatory. The \code{target-runner} template is written in \proglang{GNU Bash} scripting language, which can be executed easily in GNU/Linux and OS X systems. However, you may use any other programming language. We provide examples written in \proglang{Python}, \proglang{MATLAB} and other languages in \IRACEHOME{/examples/}. An example using Julia is available at \url{https://github.com/sbomsdorf/An-example-of-irace-using-Julia-code}. % Follow these instructions to adjust the given \code{target-runner} template to your algorithm: \begin{enumerate} \item Set the \code{EXE} variable with the path to the executable of the target algorithm. \item Set the \code{FIXED_PARAMS} if you need extra arguments in the execution line of your algorithm. An example could be the time that your algorithm is required to run (\code{FIXED_PARAMS}\hspace{0pt}\code{=}\hspace{0pt}\code{"--time 60"}) or the number of evaluations required (\code{FIXED_PARAMS}\hspace{0pt}\code{=}\hspace{0pt}\code{"--evaluations 10000"}). \item The line provided in the template executes the executable described in the \code{EXE} variable. % \begin{center} \code{\$EXE \$\{FIXED_PARAMS\} -i \$\{INSTANCE\} --seed \$\{SEED\} \$\{CONFIG_PARAMS\}} \end{center} % You must change this line according to the way your algorithm is executed. In this example, the algorithm receives the instance to solve with the flag \code{-i} and the seed of the random number generator with the flag \code{--seed}. The variable \code{CONFIG_PARAMS} adds to the command line the parameters that \irace has given for the execution. You must set the command line execution as needed. For example, the instance might not need a flag and might need to be the first argument: \begin{center} \code{\$EXE \$\{INSTANCE\} \$\{FIXED_PARAMS\} --seed \$\{SEED\} \$\{CONFIG_PARAMS\}} \end{center} The output of your algorithm is saved to the file defined in the \code{\$STDOUT} variable, and error output is saved in the file given by \code{\$STDERR}. The line: \begin{center} \code{if [ -s "\${STDOUT}" ]; then} \end{center} checks if the file containing the output of your algorithm is not empty. The example provided in the template assumes that your algorithm prints in the last output line the best result found (only a number). The line: \begin{center} \code{COST=\$(cat \$\{STDOUT\} | grep -e '\^{}[[:space:]]*[+-]\textbackslash{}?[0-9]' | cut -f1)} \end{center} parses the output of your algorithm to obtain the result from the last line. The \code{target-runner} script must print \textbf{only} one number. In the template example, the result is printed with \code{echo "\$COST"} (assuming \parameter{maxExperiments} is used) and the generated files are deleted (you may remove that line if you wish to keep them). \begin{xwarningbox} The \code{target-runner} script must be an executable file, unless you specify \parameter{targetRunnerLauncher}. \end{xwarningbox} You can test the target runner from the \aR console by checking the scenario as explained earlier in \autoref{sec:execution}. If you have problems related to the \code{target-runner} script when executing \irace, see \autoref{sec:check list} for a check list to help diagnose common problems. For more information about the \parameter{targetRunner}, please see \autoref{sec:runner}, \end{enumerate} \item \textit{Optional}: Modify the \code{target-evaluator} file. This is rarely needed and the \code{target-runner} template does not use it. \autoref{sec:evaluator} explains when a \parameter{targetEvaluator} is needed and how to define it. \item The \irace executable provides an option (\parameter{--check}) to check that the scenario is correctly defined. We recommend to perform a check every time you create a new scenario. When performing the check, \irace will verify that the scenario and parameter definitions are correct and will test the execution of the target algorithm. To check your scenario execute the following commands: \begin{itemize} \item From the command-line (on Windows, execute \code{irace.bat}): %<>= \begin{lstlisting}[style=BashInputStyle] # $IRACE_HOME is the installation directory of irace. $IRACE_HOME/bin/irace --scenario scenario.txt --check \end{lstlisting} %@ \item Or from the \aR console: <>= library("irace") scenario <- readScenario(filename = "scenario.txt", scenario = defaultScenario()) checkIraceScenario(scenario = scenario) @ \end{itemize} \item Once all the scenario elements are prepared you can execute \irace, either using the command-line wrappers provided by the package or directly from the \aR console: \begin{itemize} \item \textbf{From the command-line console}, call the command (on Windows, you should execute \code{irace.exe}): \begin{lstlisting}[style=BashInputStyle] cd ./tuning/ # $IRACE_HOME is the installation directory of irace # By default, irace reads scenario.txt, you can specify a different file # with --scenario. $IRACE_HOME/bin/irace \end{lstlisting} For this example, we assume that the needed scenario files have been set properly in the \code{scenario.txt} file using the options described in \autoref{sec:irace options}. Most \irace options can be specified in the command line or directly in the \code{scenario.txt} file. \item \textbf{From the \aR console}, evaluate: <>= library("irace") # Go to the directory containing the scenario files setwd("./tuning") scenario <- readScenario(filename = "scenario.txt", scenario = defaultScenario()) irace_main(scenario = scenario) @ \end{itemize} This will perform one run of \irace. See the output of \code{irace --help} in the command-line or \code{irace_cmdline("--help")} in \aR for quick information on additional \irace options. For more information about \irace options, see \autoref{sec:irace options}. \end{enumerate} \begin{xwarningbox} Command-line options override the same options specified in the \code{scenario.txt} file. \end{xwarningbox} \subsection{Setup example for ACOTSP}\label{sec:example} The \ACOTSP tuning example can be found in the package installation in the folder \IRACEHOME{/examples/acotsp}. % Other example scenarios can be found in the same folder. More examples of tuning scenarios can be found in the Algorithm Configuration Library (AClib, \url{http://www.aclib.net/}). In this section, we describe how to execute the \ACOTSP scenario. If you wish to start setting up your own scenario, continue to the next section. For this example, we assume a GNU/Linux system such as Ubuntu with a working \proglang{C} compiler such as \code{gcc}. To execute this scenario follow these steps: \begin{enumerate} \item Create a directory for the tuning (\eg~\path{./tuning/}) and copy the example scenario files located in the \code{examples} folder to the created directory: %<>= \begin{lstlisting}[style=BashInputStyle] mkdir ./tuning cd ./tuning # $IRACE_HOME is the installation directory of irace. cp $IRACE_HOME/examples/acotsp/* ./ ls ./ # Make sure that target-runner is executable chmod u+x target-runner \end{lstlisting} %@ \item Download the training instances from \url{https://iridia.ulb.ac.be/supp/IridiaSupp2016-003/scenarios/acotsp/instances.tar.gz} to the \path{./tuning/} directory and decompress it, which creates create a folder \path{instances}: %<>= \begin{lstlisting}[style=BashInputStyle] tar -xvf instances.tar.gz ls instances/ \end{lstlisting} %@ If the above gives an error or does not show any files, then the files were not extract correctly. Maybe the \path{instances.tar.gz} file did not download correctly or maybe it is not in the correct place. It should be within the folder \path{tuning}.correctly. \item Download the \ACOTSP software from \url{https://github.com/MLopez-Ibanez/ACOTSPQAP/archive/refs/heads/master.zip} to the \path{./tuning/} directory and compile the \path{acotsp} executable using \code{make}. %<>= \begin{lstlisting}[style=BashInputStyle] unzip master.zip make -C ACOTSPQAP-master acotsp ./ACOTSPQAP-master/acotsp --version \end{lstlisting} %@ If the above gives an error, then the \path{acotsp} executable failed to compile for some reason. Maybe you are missing the C compiler or some files did not extract correctly. \item Create a directory for executing the experiments and execute \irace: %<>= \begin{lstlisting}[style=BashInputStyle] mkdir ./acotsp-arena/ # $IRACE_HOME is the installation directory of irace. $IRACE_HOME/bin/irace \end{lstlisting} %@ Or you can also execute \irace from the \aR console using: <>= library("irace") setwd("./tuning/") irace_cmdline() @ \end{enumerate} The most usual sources of error when running the above commands are: \begin{itemize} \item The \irace package is not correctly installed. Please make sure that installing \irace did not give any errors. \item The location of the files is not correct. Please make sure that you have: \begin{itemize} \item The folder \path{tuning} and that it contains the files \path{scenario.txt}, \path{parameters-acotsp.txt}, \path{target-runner}, and the folders \path{instances}, \path{ACOTSPQAP-master} and \path{acotsp-arena}. \item The folder \path{instances} should contain the TSP instance files (\path{*.tsp}). \item The folder \path{ACOTSPQAP-master} should contain the executable \path{acotsp}. You should be able to invoke \code{./ACOTSPQAP-master/acotsp --version} without an error. \item The folder \path{acotsp-arena} should be empty. \end{itemize} \end{itemize} %% %% %% %% Scenario settings %% %% %% \section{Defining a configuration scenario}\label{sec:scenario} \subsection{Target algorithm parameters} \label{sec:target parameters} The parameters of the target algorithm are defined by a parameter file as described in \autoref{sec:parameters file}. Optionally, when executing \irace from the \aR console, the parameters can be specified directly as an \aR object (see \autoref{sec:parameters object}). For defining your parameters follow the guidelines provided in the following sections. \subsubsection{Parameter types} Each target parameter has an associated type that defines its domain and the way \irace handles them internally. Understanding the nature of the domains of the target parameters is important to select appropriate types. The four basic types supported by \irace are the following: \begin{itemize} \item \textit{Real} parameters are numerical parameters that can take floating-point values within a given range. The range is specified as an interval `\code{(,)}'. This interval is closed, that is, the parameter value may eventually be one of the bounds. The possible values are rounded to a number of \emph{decimal places} specified by the global option \code{digits} (Section~\ref{sec:globaloptions}). For example, given the default number of digits of $4$, the values $0.12345$ and $0.12341$ are both rounded to $0.1234$. Selected real-valued parameters can be optionally sampled on a logarithmic scale (base $e$). \item \textit{Integer} parameters are numerical parameters that can take only integer values within the given range. Their range is specified as the range of real parameters and they can also be optionally sampled on a logarithmic scale (base $e$). \item \textit{Categorical} parameters are defined by a set of possible values specified as `\code{(,} \code{...,} \code{)}'. The values are quoted or unquoted character strings. Empty strings and strings containing commas or spaces must be quoted. \item \emph{Ordinal} parameters are defined by an \emph{ordered} set of possible values in the same format as for categorical parameters. They are handled internally as integer parameters, where the integers correspond to the indexes of the values. \end{itemize} \begin{xwarningbox} Boolean (or logical) parameters are best encoded as categorical ones with just two values rather than integer ones with domain $(0,1)$. Some boolean parameters take an explicit value (0/1 or true/false) such as: \begin{CodeInput} dlb "--dlb " c (0, 1) \end{CodeInput} Others are switches whose presence activates the parameter: \begin{CodeInput} dlb "" c ("", "--dlb") \end{CodeInput} \end{xwarningbox} \subsubsection{Parameter domains} For each target parameter, an interval or a set of values must be defined according to its type, as described above. There is no limit for the size of the set or the length of the interval, but keep in mind that larger domains could increase the difficulty of the tuning task. Choose always values that you consider relevant for the tuning. In case of doubt, we recommend to choose larger intervals, as occasionally best parameter settings may be not intuitive a priori. All intervals are considered as closed intervals. It is possible to define parameters that will have always the same value. Such ``\emph{fixed}'' parameters will not be tuned but their values are used when executing the target algorithm and they are affected by constraints defined on them. All fixed parameters must be defined as categorical parameters and have a domain of one element. \subsubsection{Parameter dependent domains}\label{sec:paramdependant} Domains that are dependent on the values of other parameters can be specified only for numerical parameters (both integer and real). To do so, the dependent domain must be expressed in function of another parameter, which must be a numerical parameter. The expression that defines a dependency must be written between quotes: \code{(value, "expression")} or \code{("expression", value)} or \code{("expression", "expression")}. The expressions can only use the following operators and \aR functions: \code{+}, \code{-}, \code{*}, \code{/}, \code{\%\%}, \code{min}, \code{max}, \code{round}, \code{floor}, \code{ceiling}, \code{trunc}. If you need to use an operator or function not listed here, please contact us. \begin{xwarningbox} The user must ensure that the defined domain is valid at all times since \irace currently is not able to detect possible invalid domains based on the expressions provided. \end{xwarningbox} If you have a parameter \code{p2} that is just a transformation of another \code{p1}, then instead of using a dependent domain (left-hand side of the following example), it will be better to create a dummy parameter that controls the transformation (right-hand side) and do the transformation within \code{target-runner}. For example: % \begin{center} \begin{minipage}{0.4\linewidth} \small\centering% \begin{CodeInput}[frame=single] # With dependent domains p1 "" r (0, 100) p2 "" r ("p1", "p1 + 10") \end{CodeInput} \end{minipage} \hspace{\stretch{1}} should be \hspace{\stretch{1}} \begin{minipage}{0.4\linewidth} \small\centering% \begin{CodeInput}[frame=single] # With a dummy parameter p1 "" r (0, 100) p2dum "" r (0, 10) \end{CodeInput} \end{minipage} \end{center} % and \code{target-runner} will compute $\code{p2} = \code{p2dum} \cdot \code{p1}$. \subsubsection{Conditional parameters}\label{sec:conditional} Conditional parameters are active only when others have certain values. These dependencies define a hierarchical relation between parameters. For example, the target algorithm may have a parameter \code{localsearch} that takes values \code{(sa, ts)} and another parameter \code{ts-length} that only needs to be set if the first parameter takes precisely the value \code{ts}. Thus, parameter \code{ts-length} is conditional on \code{localsearch == "ts"}. \subsubsection{Forbidden parameter configurations}\label{sec:forbidden} A line containing just \texttt{[forbidden]} ends the list of parameters and starts the list of forbidden expressions. Each line is a logical expression (in \aR syntax) containing parameter names as defined by the \parameter{parameterFile} (\autoref{sec:target parameters}), values and logical operators. For a list of \aR logical operators see: \begin{center} \url{https://stat.ethz.ch/R-manual/R-devel/library/base/html/Syntax.html} \end{center} If \irace generates a parameter configuration that makes any of the logical expressions evaluate to \code{TRUE}, then the configuration is considered forbidden and it is never evaluated. This is useful when some combination of parameter values could cause the target algorithm to crash, consume excessive CPU time or memory, or when it is known that they do no produce satisfactory results. \begin{xwarningbox} Initial configuration (\autoref{sec:initial}) that are forbidden will be discarded with a warning. \end{xwarningbox} If the forbidden constraints provided are too strict, \irace may produce the following error: \begin{CodeInput} irace tried 100 times to sample from the model a configuration not forbidden without success, perhaps your constraints are too strict? \end{CodeInput} % In that case, it may be a good idea to reformulate the forbidden constraints as conditional parameters (\autoref{sec:conditional}), parameter-dependent domains (\autoref{sec:paramdependant}), repairing the configurations (\autoref{sec:repairconf}) or post-processing within the target-algorithm (\autoref{sec:complex_domains}). \subsubsection{Global options}\label{sec:globaloptions} A line containing just \texttt{[global]} starts the definition of global options. The only global option currently implemented is \code{digits}, which controls the number of decimal digits for real valued parameters. Its default value is 4. \subsubsection{Parameter file format}\label{sec:parameters file} For simplicity, the description of the parameters space is given as a table. Each line of the table defines a configurable parameter % \begin{center} \code{\

CGekmzX#ulFޔ$l v]a_19sXD]ʬ-G >Z 5X0_Z6cζ%. (pAD?x!lb YOz'!T\h?j6 :xYWR.A+惯2\*ڻl?$ln ]<‚C9MC%]5!)}Cvu(я!JpcU)YjɐWOrM-efpkN|궼rmK.`w1n%:NXp>,,&4g'&?~XGhXGn _;GG:k1o4DR8 R)#"6x ԧnLW P{ұn ?db_z1̗tq<8o'<+y΁*|~g/gy; O~#âjcCr8 >!J1ڜ.=꣈ G6r,ɐ6>muZtuҙ4]. Ds#R\W% l1,3r3mc6Gfd[BQ hendstream endobj 1167 0 obj << /Filter /FlateDecode /Length 2978 >> stream x\rf%*D~RLԸb+J6,Z-dͦm(JԠH9S^)v788`zJܿ`/듏'y7]xqs*ͩV1uz$sqLEEߋtq> 7m%ioMbd0`1B Ba{yvkzIR6ˀEEf$VŝJ|'bHRnM!k# ? !STŲX^7 Xe!(wX7𓜔u' {Z)+1iI ͈Wˢ.y52?#+,gKG8)fm;ӯ90}xJ\9W[EAjQQniA&R =?/U[=Nj,e@bx% ɀ+T93l}*r4&}fXaQL .~K5. tAA6Dٺ.] {Y֘DXE\+̿N*_uu=v U~"Y?Ӄ|6-=5ͪsa,APD_Tt3XAN7.C`!^m궃˫BlS'f>l bq}|H !{걆}q 7B-8jCGt ^>9 &T]G gi# D>+lu耈[{Y>˱ &"% xP1pWa)0 Y"}ЛT ttvkκ\EbrDK(8] 1H9l7.7W1 %R= ӊtN9֬[Y^1^20㐧oRA OpZ=XG`Fzh|wRTx DJdއEG݊E{*hljeo],fi .?2!JxgN[~jG]&ʣDu|G,W6:`8[4]>͘5B(W-vCb-7%\DW {>P)^klhf2Tn͌ ;( ^ [uqtv9To(Fl4YXM7GF+/d ƅdCI7sy|[8Փu,f(6t5Bֺ$}*~t@Ɲ:4ގrJh 0e&݅{$^B1i[mPYu]PN oZL& "o+1׏x/,eU&…(D6065H!3- AkM]CӠŠtBuaeMao|dYCkp1eO[5o;5ap tLd"bL9U,*Nm`^:c4/X̖ScQ[Sxt-&nV7 O*z8:$^ #Sf40D#wv`=*\#:t]lہ,Snv2n=阀ww;w LN<\^Z& < /s¥kj;`]Bczth|4*$Ldʄۛ+xlr$¶;X&㱻HrFQGV%ڽ]ǭEu_<<M…&π#{|oYqGL OB"} 8˽pgyѸ|(ͨ \>sFή6ʷ]|o.K49<`&Uh3[*4GumNP?M{[nBo1 尧n%Qt{20j0t"wɴ]\w3:CC5CboaivW$E|-/ݽM/quĶBmmh7ŠC[j ^!TP_R:TԲjVl4j@2lːnkZS3Ԋ 5] }vaH|<ƔCqu*F 뽸wBp'x=fNuSuOL<&;5lngks]m}`l%{x}5/g?_Ȏ:b1T3‡qvERR(h>p֏z魯F_k$;|a@h{WjVRzSד!Ls=2 $O=]z-Zu75E mM4E~0a@Wq/v;jhs\CKp7\:v r6@˪;Q+#qD> K"c $k,\'="endstream endobj 1168 0 obj << /Filter /FlateDecode /Length 264 >> stream x]n y &M*_Km/@T9 xa/c5-?iSqIc}Ojے:=]˫_ߙT=@Qݩ#OK_=T\QwsQ J(s܏j`؁::X `,E 0P3(05U U05Y0U kUy-?묺; -=J[[U[/`IͼfRB endstream endobj 1169 0 obj << /Filter /FlateDecode /Length1 11688 /Length 7588 >> stream xz T[׵9+_ ]Gڀ6pd kc'ωv9m'؎_jym&OLV2nt6M絎aҼYfY]й>Fţq wkػ[zwk 'RrGFހ۷W{"TB,DCJ3#;_͚^ot:h vxw+Y B1!_O/"P*G(q҃#-? {{h/ѨHCh ڻmBOU:zA' GBKi:KQ0:|astv= 3G߇6u[1zE_wE|Xy=zGC1C8IF'fg ]B'zp>h~A~4|ۈ17dܾ 7 -<O0Pŧ;Z7oq7[FWv*QcXbyR ?/7ǔeLi5^Tp,C0*79)[bL.W 훼ކ@9䑄nMSέwqNqkjT]R,ԛ:pwDCfex yr':YY0BO$-K]u o:6T))F1$Ӹ)&HOzoni()nLu2 "%EE ~::(LL:A=>Sv ~bqIk MuR}R^2SMi5%\IrL_܉F1\͗N0Ą$8''{L417 #w;:? yCtb뛤u ^Ô2#K۱kdsMQtH_@=gh5wHRf(6J_,6n6mh>S=W[aH 2M$j k+V}~A,0)tȄF$)̀ B P9ﮁT K.sd[%ݣR+v퓬3^OVC<$:LJ7:Jљ T_^&dfCPGeNʫh*3m3$6렎* e3Ji}eTcscjψT*d0ZI .cqZ5BW;rԝj]  rj]YYYN #TԨ p(jTBg0 nk摭5l^ѻX`&54gn\A/v]w„Դa 7E"мQBԅŕ lrzÉĴ(ҳ<@턩o´Z7~:W"jMK!6-"޿ʇg &ݫ;s~A\!c R$C%Jϸ "4.SY!{c$T 8zϓN#c#8Qv)ul ^@Dwq _,aS XT3"N1VKxGx%xN%OhM_ e:T{+ߕdLr!Hw)v ޸8{QFj(%̠t+ 39GaDa% pyQn±æ(,8Q?P 7CEa2(LPS, Ea-aEa/Fa%#󟢰 0Wp,Z~нܲ( Pm 8: BYi aOpyBPEX} 3_7g4_yAvxG w_x;Z߹;*%] wu7FB^j)]aYvx뿢h}# m  ‚wOh]ؼu'#{}#a/0Kg Yկ Q¾]>a7Cpj,(s/Zz;D }}nؾ ;AM l !OC T }aK`zCD@AGG(TzQ!P)h=T}v!/pԈ*~$>xR50j5iP+h`2>a"n7οPzC G;A?*o`z3$1|%V$ᛩeJh4@u#YR²_d.s8n/; Yd4]#k׺M^;sL~4_:4~pkWm3?Wδ]L'̼Qsω܈f8?{L?>>Zmh6K݆&h huG8i(l6B´gdC@P8N28|o8 #A GDB-> NY1VUٲiIY(RU}SMfa.?;7t\w[~ۇg %%<Ʊ<~f?ȟ/7xe+X3_E<⛀AI3œRy')vg8x6.<6_0@+W\<౛V2x~'^Eͳ֥Vݤtjwbsd★ k+Y d[f]Z1{ʗ.CЕ+eC赺d[Ȕ;V&0z]2ߞlyMXRjYkMbVz?U3klTFawi㾶\}_gy#$lU u~M+n[g8=gqLPXѲ|Yk'\bmYgc{7x;GBbV|/gR 8s&xM|i*>M6jk"'MZKKYəb*R9Ƃ#L¤׬tv>ԚO^m\QҶ̆R%W\Q)7TMk$~o\c>P>]/XJv)S_ nőJ,gH^".|JZhʁVaH62WDN$IILRxlc銉)v5 D0`CwL/r9*'snੜ9$'G$`,űDy4WbEx4|4=(”/[Nͯn(S29ͪ<36xxיoYӹj_>\Һ6a̝mM+y}-f_cԢ_UQdLo]{؃uE&=MMlɺ=b[uRCD#n*Hф LA<Ǐg og`c_fYŹc$9.>8y$NX%7 w#$7C}^om6%d@ {si-ךm+lz ˤmHH_jPI~Ͷu"E̻NeTGSpo.e4U*D $dr1aR"TAոꪊC*[BRqdc!HZ`VkkNt_!Y"e<44Y'eocge9G*7?/_H3Hᚁqlw|o-lӮA[s8En*u Dm4h"A рsaZ-2#$H6wn,ͣ +6R~Nឱy}/B-Ի (vs)iNkH 1 ˍcPN0ꔝtDRtuV]K7Ժt:FI];ncvDO/ـ<>2E;U RnA:~Bu}tTUG:c;2yڎ+.9aa:$6&2 h')3Eﱳ n}vlc$&ٱ*]?H#@+­an {nrG*/E*h :tAP_w[%Y&gr Z߷&7i=nj>sn>⑹M]P%:,j+GWhy,mzڒ1Fc4*;1(BԧKHєjkXFƎj&j<_gV%lMZZ"b_*\zh9M`Y*缾XCh0p3c4q<)x;fK^vgSoU*sH[{J s"UUp[eF۾2=ʡ46 rG _"6tOv?O%yC9d0.mJ"àz8hs3/<:xꟋ[w߽64<[1pdKCin>|!nqos'gb\w2B2?q'*[b\1#"W1S(U|Ll\|ZM?}Xm8,yh1m-C D*ģPx"[mRd #w7̮endstream endobj 1170 0 obj << /Filter /FlateDecode /Length 256 >> stream x]n y H/ݥMӶ T9 xaۈr}eS{Y'm*.i.X%趤N5/a]y;=htuGO7x;J#c{Fj `V|D Z9VؑuDգpN(iGVB\ӈXkLœu}n5c hOUxBikӲėDߗ]J3endstream endobj 1171 0 obj << /Filter /FlateDecode /Length1 11412 /Length 7334 >> stream xz xSו=Gd¶ld[l˶0?22`caX5I &PBid6My&Idn53& i[N~|{}ҙg9s={ϕEb ӄ%m $k%r$H'J%ͼHHwWgG{[릍낁nm횚U*|%‚|sÞe1Aj*c g_ e PEBB,")x;,+lr)%9`kImIpNqG>yި\q8nerpLuMΦYpׁ0@,C4VfA=h9=%-٬4&EjՊHqNf3޴A`t{Dfw ^,=q<&;J wd>g>#8)EoP0 :LLtnqzS49gff&a^so'{dS0Ԥ"2HOӱ0,}Q3AG;Ч9DDn$q총##3RKf7m^jѽ߉\~ˠ3>>wbOUm-B76/!ԙrkչ:hnYjg4p L $hyLhK6e FqV̬$ѵO4F0J'nsBO(1Gۆl)?+E%*rK TS|&T9!qe]PeI|Q<wre&,}ӾKS)z^zS]Pw)c9AVJfɖS7$RY] x,pWu8La >ks? N_c|&vkT>\8z蕣SG#fL i8?WhIFsP)a{4h?0hF_^{yﵽ쵽Ks``*Xi߃E,(ebXr ;۟խ*1(Q,-h6, 954 s <r6tV|gTw8\=ֳ̉;.t\3jG tvFj\m n*5+ڍƟn?6JY /m`*{KPp0발 3kEfnSvP/ VaB8*\u=Ү ,fVazsŎn&!9RVYuH&[EfyҘ+:#r^OXD@4Y+iI{$@qW^8Pi$)Ai$~&?D/Q{x(U' qE9ho_Cb}o< '/'|7$jR:[E951_1^{e$,%|NP0;̎fF\pɅao7s+wūoMMKpxeaD)Xjf2:cQ4Z*B! @"WODNK&I'Lm9"beyҦyi|y3B{//%ʓ_QT-3LUUub9L]#6B \Om\ԧZ#c=*Y6׿<0.S{eyޮ[}%/5v]]r{뱑D.^JǤ^R*g@nK{pu wȒ۠:Ԛ*XTQJݲ"2CvmTt n$ |E^szw'SzӫӑZ^#8WP8S3uP@qNaKiQYQUU6匟:5m7ms|j^o:8zjg\Yϫ =ݸwݛ;ë [^U7Nce{EhuiK-۶M]닊76ط.4?/* ax" /nav(Ig %WԣW왪&򃃼CEF]{Ğ<0YC^JK` W*:Xo=y XJ ̊=&mZ(Ӟisx[&tguOWkձ:It|IKN0pł(4oRzSX ª,ʕ Lj˖M>kxVvKl6\wm84ԘvU }{Wvۚz54wW{32+;xȑ6n \5EEUGFzٺX!BT\ qG4cVFn WC$Y3CC( lW~q ҇ЪD%@9EHS?S}[lFYep) הڤ)}؈~؊NyhKP?x?{?,5c5J3Dۛn$'K{AIᨾ⁋ݱpP{gҴ.79nBp}e~\޾^\^oowI!N_V3`{ûZ: xN Ux3xB K WWƅw/m?a`CYV0 If޲lr%빚뎴sy2_^5B9ޯ&3Ok/i-] ݾPZvQbJ 0[87a`19/L OpE pA Bx.ZOgʟ<P0?7߻ǻUWCV,lk͋WHz2|1#K]ByyfAg7̭fY_3zSUhNYjnC:u`Wښk7Yh49m5yx3a`Hcw ܭ3u^S-k M7.@ׇy{3qpVq`~B~;~(g}8,$ C]bK6s˟~Zj̞RZ BL7'yQJf2Z3h[t> :E3@ᲁ3I^ْ9y9sm\[Ɇ5G[^.6oȱi6޴\˪KNɫ 2SI#hndUR#g͔u5jؿ"eW ?ulo0{SJ0oCe33c[#/_oaGc[ 9d)''!h`G m4҄[QTY8PL&äefg:C)i+/aK -oI,'TR/ʵLϰ_힢"bՎ=JbjIɊ $pK) IWD;4CtиXua~kߟxo4+nrp_rA(X;ɔ \^sk{g`L|?}U{?uG- ϼɓ0`P~ LsH2ݟSG4NfyazK"'O.|pq៨DQ8lgsAǪq՟5!U=O  n*Fӧ|?iTjŐX甚Aq'*5DKtD'16La}4Oo#~\%h@'.q 3XK(p/ඐIfJGfFϾCؓ\<%4U (\"endstream endobj 1172 0 obj << /BBox [ 903.865 2539.86 4818.61 8079.72 ] /Filter /FlateDecode /FormType 1 /Group 489 0 R /Matrix [ 1 0 0 1 0 0 ] /Resources << /Font << /R304 148 0 R /R306 150 0 R >> >> /Subtype /Form /Type /XObject /Length 5760 >> stream x[ˎ%q9Rl/sրa; xazն,čqΉ̪~H ÈM]Ӽԑ5K!\cӵOy^~̐}^{<ܔuj ך z9oo⸶8{}.|z!kIG1{72!knϸqc|L1Lg?4kVW{0IOaF.סv5i1pGHȸ )ry?s&;`Nڻ}*alć8_FRҵ R^Ĕ S5:1U$`ƵK8gU/qmsvf6;^h'D\UDnOC!G w^l8c*-ݽROwє(=>31|?á l.?>r17ņ`ϘjՔthnwټ)7_}gV2,bwܼ7{oջxFoZ>R4C&x7&O2n];mLHmr$v @;mj. n`0EїZt23{>rckal-Y?~sd٨l~zy{ok4,ad06۴Kf?z`}m5VSh|)YH3[9+|#+͖J-gȡݻ .qŻzw4V9\3]{*ӷ%'Җ>1e|0yA 1oȷvZjqHS"rshS/|ѻWz*f_ħ?_(\gDI|/2=w(5۷o 1[=|]mX6Qw{"gX|>>Z3/ ٚH3쯀#tw9yb$~}<0S9Diiv9埜0oetoŘM]Vڟln?x,~E-0Y)x7-_c>Ö j%%;,*PQxˌ3l{JłIu*KQ_ba h$qhG^$~hG8i#TڼBJrܚ݇3qlXDd xu0\f5fE%I@8PAYw<2 `!,"fCH%iaR^rƹ И8L"寙T*La1?`:k=[X 0)bDz1[Z7،:nXTP enD%y$kwO&V9xh瞈>`pewXD*{ b  '^,Y25j<9KHav*d7$ &8{E9AiNW nT"ET(HQj=XNL:K7.0.M)N)fA\r\/.cڈ)B V_l  SugO)I9搓2xYlAn(3KH&h|!6JН7LeݴO: = N5Tf )Y"%2YadJ8o 8>d2, 'ZD b0o" k SCt#K+c3 D@uE,Υ.aN8Kg1ɿ^0K28ʻ}w:[3&z)GDP1UΓ\BKFb m6N<h7iyj?)h 6X'Ȳ8!S|wPyR]/ua8$@cVl"T|)㚰YQ`9+LjYH@o!hy4hag"p4Ia 8B~G\hd"EFQ9+KoWI#:%فJC-}Iy7PAҍ؍m'_oKv[ ٧ŕCn]ef%D"U߫A=GK&}tzd~a#@(; ^3X OT'5ՉG Ryp,Y4}&[DgI"3BIf NIOpˋ3&"p sr*!&* !Z6m.*%bRNR1ۅy7wLJRVuN TT%-v}Ji) 0 퉈`mAv*`7OEdZ%:h4߈3FyS@6 c6 a,?SpgD1\x4IkB UFQNV* a~1YI!h;~& ]8  e2|l_`ZV- öAEm0Vd3O89J},KI|*!\°T!؛. Ij\DH 4R'?3nػu넛.D$f^'feâevs!I!Űag^KC@61l=z6/ut Qh޵aS#*E1.5O˕&cf-֍1FĶf:wW\eϘe;/YB&eRJh6hB!(:G1t8盤lg:! J[3̳?QGrJܽJ5x:Ehb{ e7_~4 5j= i8JƛuD8nP%3(=ɡű3]I.P;ST?F~C:O0Κ8}x g Q_R+q Uǣ{kF}qki Lb89[?hO f\g*ܦ31=apx&OJ/0LyXqO$B#bQc ̋y"R{#Y1OϚ$,*\T"s6K c@:8Q;3`Gد&S2VO !1('n`Vީ&lZewE%7a+ތSVBYy՞@'NjWY<Tm8,b^cj!ՃʲU62a *\ *ˎB&0Dr(%KdpUܹ6NuY%b 0 Gq{ګB̽&Ufqzm^C4pžТ)nFwY(ٌgg]U,zpMcpUZ;%Y2Ld? Ae0g}Gu]_9Mh=ґrRm"r$(gMFZ]<ѽ[4P"h:{0O7ѽ^ C,h:ۃOF1!*fAcjM&+"' e0'n--?ΌSՃY1YE 1VlWyl 9Xe.OȾ{t;<0JX\TNZV$O0\40iL"<Syt!e͈Sf\6d<l <72W![ujfXB0AI}I?lBغS55vE"0*A2˽B ڢ쪫c\H\kBXpNtR\ Z NAm7`6Nm;c ڌ$L4`A f3ţYb(WЇħaư.VA_CED.'%O bk4.5{L`SC=X0ƖX!ۓ:dLЀ AWN5E9Y\ ޱ(pϬ+fX03˟4 +%/q X{(\Wrּ}Y^!#]:07Ɣv|D> >> /Subtype /Form /Type /XObject /Length 60 >> stream x+24723T00634Q025034W04573P(JUW26Pp  endstream endobj 1174 0 obj << /Filter /FlateDecode /Length 163 >> stream x]O1 y?V],钡UĘ!2tp'w'E?\rGĭ#qk#Njq2:0txl@w=x:םR .AFMVʮcHOݝͥ2sJQkHVUJG&PR<}kSdendstream endobj 1175 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 335 >> stream xcd`ab`dd74 JM/I, f!CL<<,o$={ #cxz~s~AeQfzF.THTpSJL//THKQS/ f*h)$f$))F(+k꡻ /,/I-KI+)6d```Tg`b`bdd ˾*p?nv.WVUUY!> stream x^Pè0|0G', ȯWf)Q 匠n43иȦpl(-SUc 37}5 ֭Tu8)E3\ Qo<+ޠp|1=("ѹGk6sd/|9'0ԵF"ć#*cj&VK!@|ay8z>`<:ܸ9%K'x!>hn{qS]:8QWoF-kH8V'qU|7R8°7Q=A,!|]ú$ vrJxzI~=yA1'Y|Gӥ`C)KA&(H*_G%J֥~p>'X)(Gfc$+YҐ s_y]zkrD)g(] }P,s ׋u+{$``A5`Gw?1Nmo1S0\eԯ#s(͉9MQoI~+L`$Zz!Ltz;  [MWמI<$qvԹac;PhVvXka^)gxiַL孏'k-ANPs>U60k޲nh "VJ0毺,ryT'qn ?Zrcq[|]aHM u"j4 E]f%ż^Mx=& UI^|׋]|!MCHIVSOH , b]ه˴ϻ2\/`ZrR]qWY ]~ HdFz+!qm4SHpI7d,gŰ̯rԷ5p*. Mh;e db}Ɖ'큂ZZ:iZz.xu q3 Ϟ2BAEff%- OAK>4hOMEZPU+縴_ކa2)Mz&~Ju@8*RgK~,C*n_|? HF0VW=$ ʅ`$UekK]͸5Qm8GR[`rd坯@%ጬ< 8G -,'+nij r:&.XS+pcfӭH懇Et= Bp/V Ww<w-ģݲD5br*ve橤۔\Rٚ?9v^,'+U(yMb.Jzt\SslpbI⍶?۸0:Omk|6y4s >șTLHsZ]r |/N6Zt^b53; JJLc%;iɆ)vKʐؐmrW3JoMHec.qb]ƥpV5mqn $nyӰ@xm[PsOdD1A .."|=%YH`(#YI9& JHC g“'~ b.ھþOg8Q\ʗc4! W2j C:#Bb.*s&9`ZYHQ|^uvwaO~ WǫxN &^sO5{c;ە Rrŕ-y!j|#55q͠!߀7,!ƒAJض3@I]Q1Ū@ohoi[֫>BjQaLxrPaVfu J4V%Dp}U-:&"=?L3k/Hl 3yeWR&>RQhIzN1IO9Y=7J ,E 75JC"撄lX@,Ó$zcG4FwIAj'D8YRI[_B -+`”IH%Wm]<^#w`5]"g$W4p-TEnXvGL#մ8r [BA}8:jMa^^Dj(%I`?GyԐEy4:G' 3 sX{?ХLzKʊp͓o[1hnGO<NlҴc%8ϟ2GNZt}V^֭QBjK;Y_'L𦤬XϞx㰝jUtb FboK[,$DU2zOƣŲ%VqޯVHP1_!A&߫Q*|ko1e)F| KۇTdLyfqֽ#Mc!K_:)%0Ҍ唈PLLC.ܭ-yŎۊǡ9^2%2ho?֫^Tmb8Wq4;}8 뼂8KO8lC:@;,< o402zEfba \UxcBh)ҙ`t5tJЭR`D~yn=X-T^8Xlhlr*{%K4>{W$Ep[!vW+]l> ㊦W÷KyU|E,EidiZNRܟgS2ОJx&bxP^I4TR·+RU;K˱j6x@TVR9WIOs9~MXc1 4k*;ЬsaK.uX'Ketlp!_mϴޕU&4/ 2֚Yv `QEumvmM s2G$/yMUBۻTgd9ٽrf{u!fU(;NjL݁C݅u+$LTf KfqsFt7*\@f3xP5][R{oilzޡ??~^.7_nⅷu l5X3+ u`ͱ-=庹}l?N :On5nwLt &%P+r࿏T+URҤendstream endobj 1177 0 obj << /Filter /FlateDecode /Length 5142 >> stream x[IsHvduP M[P=aB=@7'8`ZH"̗oR--*_Ϊog.g?3%V):s߰EmKn9_Q/WqUVx=˕0.O˪TU ΋[_m)vv~IL"Lq׍7a]tChzbp!bs\w[xpiT;Nڊ1s [Z5.E]Ż|_r#,'DL!Kn4SJJL\oW2.e @)f&_(݊~׺'6:{tcЂO sYUUrzaU[Qɩ5Ժ5ON ?ཝ7~t&u1 } ÊbT1}×2ɽHUE].WDW]'i%G4/]tl˿cZX3&nD8de-E*go.pLS-L2S帬,5cٲL$~Jt KTK*S kdx͜4OJ̈́>t OAkAR: 톥7vPxt ^nKnGXUэS@0SX9xc5)tV*>h?5m DRXԁ qg7CYP R`9uoְ-T;9O*};<-<s*ݡu$:lsB_$9S΂>X!ui~ ԑ' "'mtעAZA()'eL@P h57~OۂB' _UJ,m*ߞ<2%7,F$Obpi5C|YPkK '$߻rlB6l(ct'N\4ɱV̰iX1p ^(sLi$O1GeR6}:HYR:c-ze]Xן'6:BF1!*QJnu^|2[JkL_M]3dW{"bVeȰBPXڊ@pPGf_AqKs4{ouFXlIPɶV7~Aqed-qs.8xnhSq}΁.O*t𱖕jMxqVQEYC`L>z_\8:#U8ɋI"k@h I6zps0 P0c3\=F¯=9U[2`8G5'=Sf{X&sG yOR"߽yH̉Bn2ٳ 'T96?cvIyް LghFrR LegMO3 eUlhJLZlW )&dr&;b:(2] r"*p0D.$'&?$lBIЧ`%דg#8;f% knبj܃}$Ó4ݡY {:0f17oMTe\ *pcx@YPJq;N,(e3@f՘Y[ x 9pE jv˯ǾaNG̴%[̤:\99oi*xHm*nZm7l49&!O(5{WhSS/LRAmzcaF[K`0V\\%6)] nGXMveط'An~} V|_}}.sȻ|dfcH9m 3l|k{jo}n}'CΌLUmUב8NU 2'0=c&e+c?^97)X|:vwM_wCiRY]бDWnۍ~!Gg L%λu%,0V[A<^k?Afl~%Ix`:…/ **]8x@z幅 ,-|>ހG ǁŊֿEiWu8poYC~:j,þ/;"t:ՑcdTkoM6&"fP˿|iPDU(8(^(Q-_z0( }Qaz4F|`h%YޕG>~>>Le AC9SKm|Jxi5%1*-fg |OB5f#&ߟ[ CXglq0k*V6!@It0ǃFRsm\\r݇$A02]+Y ! "6%ɂihV%Cԓ0,SHu!vTN.BS1`6:Pi{[_<Q?RrrFob9 Ԭ*#!@{ˈx:"2P݉s'Vu:oIM*7fdjXA XuAd,BW"rMC<I)K5Gj}~u.v-sckF-q5j]ST&Tf) 5( `эUݒ6~c؊@j4q:[TpI=7D!8cK,3x)֩R~cKl.̶/16<2f3d]61@`,Ј& />R(@g9ew͠!\J+/sVL)m8Y4yc ћHI;M ")LE `2Qyd@Su_ ԭ UtZN R#I-!+h׽]4׹ נ}p,y|DoWPEggC槊T T) rx.  6XC4@h`1;-X[WXV1T^JX(,ՌS^RQkle#e;DlOK?޽H_4;Ӭx7*Jhσr)?~zE2BܪRs9of6?~D"i26fTsY]#FVSctg'!p@,,r\W/6z+dy9)qHf@HT.d H]G䐰8w Uo=:`C|@N;P^"8~8?_CZ+0"e os^fkNkGz[N;4OlWOLpAsq^d>cz5Uה}Bg~6mUTO(ֹ܂n}]}61en?c Fx)iTP2I+^U*KN|*94ua8g:.M{P? VFSM_m6SOk !Ǵ`c(CJI&Dfĸ3`ttK%vl&NeY~28x l\ۤ͋|Q\@pW[\C׌!_]YwzVD'DeN$"\iśmɸ"o?#6eДgY FjHD!g=G!B1endstream endobj 1178 0 obj << /Filter /FlateDecode /Length 5309 >> stream xysĺ;'F4unr׫v6 c\ZZעqTݴ4[?/~1XvET۬f`i`<,l$9 R,d]D-A#y5M?l|70gvn=۽H[TՆ,̪?J>+S7~r |VO&?m?a 츉kZ <#b( NsM;HvV: a+0Ӹᦖ!nf0e݀ .lTb]h0FR82  88h:Ӯ/8a e+( X孩b܌V*ż|u븶#Nڶ}D!$HzW ͍bFhςZ7}d1}nk٪!+R !'`A;/D=4fb8(RW R"/ RkUƨH(M]9L*N*fŦx܍ /yl^-@PaYd8\"t99Or@ؖvo'$F0;G?(9`šZ6naY,$ě&+szpIWDy`/^ ^.2Y=2dÖ_Efؿ) 6NUh4R?cZ! "gm:*y`"9 /u㾐Kk&w}fwUhq = `j Bk] M@ c yƂSWJ+7]"$q):6DdHN؄^ L 4iwy F% \Ϩm4}\ !G_q#7j 1r/ZnDfy8S삊p~&W"{B 7l7wd.Es, :S,AtqcDPo?10Zb[?g|'@& /v vFa(m 4(eM7|̟J5CA-,$`|6ͥr3o/@0 TFsJecic?VaC?EBsLZLq1R^n_C& Hi)$˖BAAa_c}0=5:HH@#J^R">RAD_$LkHS@*`l(KsB0aXt,!@mChDF(4 tŋ j ̠(B h)2U`|éi4SҴ1ǝ12;{A0R,?x2.~r4lL0͹/hcSH,&-$ GTDG٬|BFP0-9fJ‡2jݦW{N ኄձ>039Q!KZ4lQAʀ02#ѫpǜ˔d+`i p)uV wΪ0ś #"BI lV;%p٤I>sx$Q.yڀT6:bW^|s0ffChr[?ܭI|Jm6!Pӷa>W;/L0 9g^LXA˿wcNMAh5pX)Xmu`A! VݲCFhV9;sǪ͸N!*%5oTԓr%ș-FOtLrj@LN6i'8Kbl|M]j#v/r&B68T!"Ba/ImkNRB ;Bq1?m|$`! pQ~H_7wtNb,Ke:"2\=q̗ ½lڷSxjraF BVC"q JcECfƔalC2Pc<cZacp<ٴ >p?/S&CjLHBjѮ]*^V355R>9Z<, aK`G$$C\[:" 29-& X(jA5Ynq4+jy[N^nLe$.8bTv!"wɔNf4<"o|XJGs,<ݐ3Xh]24E$!~U$=|":Tu&`_P09?^w!HaEm44M4hܤ_&aUa|p 8׿!@2·$1\#s"YBJ<ߙHҪb[\;_ chݶ}Zv|:+!J75ơ/_o~E8׈|ﳍUJ"@AQBIZb'ּNs^M}|3nbl I%LZϩ ~skoE? _lY)<0YQ_(xeW.+9x_Iˋf;1XKʈS"Ӌj\0! jVQ}V~?+uR:Ha>#0 ^+<-҂*؟׏4y~;Et |_I< YRl+c5}Ï9f1hYb+l5[}Cͫ=$r*aRC/1s˾[_G !dKۭb d-u(\욺P 8eb&fzn©@Vx˲c_T7sR?t ӔPr?=HLXԥf=8Yt1}@Ô*]lr5|#EER6}rH=)>UM<ù3x#$BY"8}K^(Ng=]u-+%lubɎ3#y# ѡqF]vC&|8^h2l {LMC<O^jJ0 nWQ`e W+kK23!G ͘WAkϛxEk dNO%wpIY{ dghMXvheI?3xh=rOz&)4VWLd|vV1 ^9YgthD-⡫oeڏDz4`q+ڰ @{$x օ4Fwxpl2nJxܶ&x1.-QC! LQz?Z/~h`"Bm=6q^ZLUA`ލ5խG !pq5ݸ,}L_ ^ ˫)IJ3CVz!ꃠfOxo{!-<4v?H/uZ(}%65x9iE6"ec}w\qqC{Ɓ`wb_\k`x[pFRa,VrnWs9uA}=>JvHDnl5(ރ}6hi#*~-ܭ7rV_y@5=-!vqa62K];zT7e/9\BἩyё)r*bQBy~Ҡ JbZP*i P`ҊͅVK ЪdZ' J& 8p}BC=\8o_mECRoro [:_KOg 1ES"!$iͫ'׽ܡPh"z5.8_wE+@q!A(eNHN<#5zUHɏxGg@xv&>Vt1e9P mmbu`q`*ut,ȣ#<F7X27fHQB ݼ;Ö YnXp w{l;Y&N@<0Qf$.XQ ʁVq]~QE PJ7$dܹ97]ḱU52r3> stream x[K}os<4 qfp;h,1HOUȖ5ei |>YW_W׫:$^M~Ѽ,>VCsRw,Ji Luw&b0.y{ "7+h9/JeHkk%ea RmEQR޿OvaL_><()3py5dݵ0L ׯ4=7#"IR ^mrz|yY99Di Оbz!,wqE;hGc 1s sQ{Np]OYUx%ԟ"ejzOK@ 0;taeԍSO)(,' Iݜ7u'.[7kO6q1%YKm#D),ne_a!Ĭm՝!ݵr 3LMr^d$U@4p$d9xQfHw}t}Wʢɵupm0 Uq"J!%s݌__YG&O[؉uKAiɀ &D2`YG2ΨH6a.Iu\H`ЌRu;Gnd=>Tr3nbBVh lKUf@xE嘈qz Dܨ$ hH& M!-Λvhf*K(^a)@Q7) \E@\Bz4#=ZH)=Zӂ2.m`1 L X 6f BIxd]eR*Y m`'~Xj1|M|Ak+IUb: zQw,"I_m1Q[s ] \c67(tȐDq ~j2Pg3z$JJ (=>@լ+'}^{AM&r|"{QrYS\>uio -2jm^N$J !Vwt)/Au3F&.Zj̒/W}u>c#˺I$dZf6ݕOrd71C6r6;ǖ{Xݮ* !0e1Σ2.tjt(-.#@f9oaK? ޢD‰qc`l =nKSP!*.ޗTLt]|ڪ7a+@K] k%tM)d4 5G$Y(?`0*( Cӂp> !9p;yFMF~y^mW"(Or9 w5')5"*bg)DS\'f;>'?7: YPeu,*JM5!e&T:duh`6M+>UTJ҉*6J?4l{H\tcUXEĵXrTsK?aFbj&@xj=U u?j;~ڒ &vH˼3#Q=}oGI !x+•&l7`ã qGcދ^ҔiNR)<FdkD+%fvf*@U(CU3ؿq&?qrXHBOR( d!KnCñJi$/s\r R 2#nHG|^x֑s6^VP~4fYinuq: RQsmQ\,'Ow6)`,iץE֨V_m$ %e ^X| /w=v!d% B(q$yzi zT@5.'3V~v+H >. 7 ZG GͱVdobRbSq`kwUr12$* 1oܱ~@3߬n xBOk "~kwN*b ^xs9eltԶ!Y;^Qۆhf~^G̲GN !B_> stream xcd`ab`ddM,p(I+34 JM/I,ɪf!C[ǻISYyyXx(}ߧ 0324vL9(3=DXWHZ*$U*8)x%&ggg*$(x)34R3sBR#B]܃C5p:C"(195L夦$)pvIcc7cc/+08Xj^2_1}iWO7[AWWy=]N-Y*Qgszr9yrF#N(qbqwٖ,GX'+~|^]v\@qìٻ+]]5iijuqa:"E]=Erlst$.Ќ==s9PC~j_f r\,!<< gN7i}'̙Se`endstream endobj 1181 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceGray /DecodeParms << /Columns 44 /Predictor 15 >> /Filter /FlateDecode /Height 44 /Subtype /Image /Width 44 /Length 292 >> stream xcd 0bUXӳ(O6*PE1(*cUIe߈bQ`$5Wp)E)}[1o4 p+⋦xQ,ngMŭbBSL#M1Ii4$gaW,SAlv3tv: *Py U"A1,$(6=`l$_HP.Ibj 9|3 L^H+ +[au.zbfyO΀?^?xl2Ғܟ>ddx'IM-endstream endobj 1182 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1183 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1184 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1185 0 obj << /Filter /FlateDecode /Length 4092 >> stream x[[oGv~oCiuݫ'p66ѮDP_pE8]suu_{f7v\mf?^^|-6Xig/D8jC+3և0\w]/͡_j/m'lsn)4wl#.U._XҐ~tVمֹP?뿽'/*qVEӕny{ndHgmg2]n"-@r0]y aZ-U0ń)R\/4QƟ^ qѤ}{/@^`W74S_J ;}aI,mx!;-B@#}zVvsg@o>̍M~߭ YWd\I۪Nk^_~|3%%Օ USi$rAҙ˛'շ_C@[m$[ۊdjTt#IT:'i2p @ ~TՖUBh3ݼmm~w7Ԃp*Ͳt=EȜ2dz;OC6Žٺ?ҡJ6۫9 hI=7DzxVϥ Qr?//L&=uo.`V&gN':Q|+ls9jxx3ڂy]!Bkv4xiΒb{.h;>%d .l݂^IZ&j♧$ՅO)׼^ ؽ֛Iy)hSHHzC0OA(Qu>[xl=~( ݏ0H?>zH0 uj} OzX瑞\i\빇6,t66+xmFKqu]m]T@c ͘n8euݶ=u:;!Ui h>WۛžPӿ$l,8 ~0id6AvkQG0z?/ e 9)zn?ZEG;gb?:S)A@4`1$VJ4{Y V߀kAW8j{mG"P[q_bA6pNFV7Fac+c<<[}=_sI#[Xg GT0e0Y-Ǹn6 oK%mSc&/i`k Nqdm'C{zlo^ALYŦq| CU~_4IcZ@QyǑ0h(p|E+v1(R0(‚ȓ:z1*W\i"V)pj|e'tΧ;}\8E`Bk,\1:krȜS*6Oo!\d1-!uc*ҕc\;M7N} W>ijLT`GPnj)L5yzNgVAH<)iQü_*fZgZO}&kba@\x1rLX9Y0[~K6}'R5oi~}+}^, .4Qi޴SbFL\y)L ">V5C|eWjzFQ6Dvȡx~C:JEPpFc|S$O^!9,],՜O4,Px(秒"eןzrNQcNS7T=~|9EXތ?LЊɆ q QOШ*2J'׹'F)J:eBu鎳Pf.E̦xtFϏZ l^ArŁ0+bTEo`[j)n @Hr:ތa}r'8)~ \p9TN0z޸!}'7A@(*w#昩gzwi׺p-~ Yl`9YBRѝJRJQ*UzRy1s<^8{*J==|) u٘Ye5$݌U*]}Z)~H2^]'yS ?M#E˜OOʩֶLD1|VAjm0k kszLDRO4ow˗Ts}D%ׇ'{yq㹺U3/WW1أ-.:O1X$m06]~TZp%Ps\:>(1wk~`3iB!uHE~ȱ;NWR5u궏 uD~;; U$ۡGD-6鲶fI= IT%N^?x.J*d,mTLЯbnhL 9I8W?eQK#dd>zmۛţR8jڗf6ӜP_)LMrC,Z)[eqTc%C>&i_n0֤R Eו \V};Wz04f5)qJA#AƌnK5|LONւx!E\Ͽ#7;6OYK]xf_/Fb3a5bZR=.DBdP~&J!̞S'/F ¡uI +TWuWg{9JAوs4ZY]Fb :.s5 Y)9WCJVa"lSZ6h%h%J&$G(Jo'=mCbb54D)&EA6|RWzendstream endobj 1186 0 obj << /Filter /FlateDecode /Length 4179 >> stream xro[pe385'cM`\,\di{UT` = oix9hjJ`j#+n+aem6 k83K0?Ve寜$#C+hm4Sr@7vqxW8.մ: pLcऩʡ& wHa4im⵶4xig6"6V# ګɀlU3\Bc驫ۺapu* ̟F{#jMYj3m'/w%ZHi~Jilfdntj6_ZgNRHG'UxpV"o΀Ma c' \P}Padxld)P ,L߻EdtR~׋[9.+[ 8֗KJaYt zTSi '8 v.o̅[In}8G$)ib[NVEFֹdK)Dˤ4]y\e&~G~@P%YyWZ]WTHŠ}PmU#||K5plku@}WCR@ e_y r@("50P ?8Z >0Lb`3G1Z`غӧj0ʹݻ!b뀳O7e:r~= ѸuAnx,XGV 72n:`UqD/MB(4d*( G2JK` 1\*MѠ w|-wGј n>fA&Lqb)Rucd ;v}" s,Yڮ$ x@ST VclURd5}2iuCJt?4u.GGEP<1垔t163yiw;9` 0%6 PK8pb2>Se̾`e|Rz2~eCsx-=o_P^%}2~eCsP`[1ב1/ȷNcLY4y̶ĵ1㲿$N&s. 7@|}d>MY %P2p ]k f1.\ǔE0GZj- \7A],at{O 1ˤ\@~"ex"|G/ N 2>95ctTx}(=J)=D7t5qhF qm?wqJ~IGs .6qmgޗlovE|gUq>(NK#:B0+I{ 6tPO9<2 `S.ޮ7z1jmEx? u0DgܓkY@~ŦuF%(]mݎXv_\=Gj-Vr}XRk 1shjPBf:`%j:G,cMj; 1phPwޙ<̊&JR7iXE> stream x\Yo$Gr~_<4}6w?h-Ba1 c0&٫f7qY잃3YYll./>Xl텷9)\oLKi{fˆ~~[^NY%=`֛,gys_4+I;{ss3zmEwX?.~zs񟰬:81{3JgO,+z%h p<+9 +z%8_Wr0)t泌j!(qy 矲4ڍexCΧ"7O?~Zs8/aMuNxCPzeV F+?H7 f +zࣀJ4ARٙDDo7wRƫt}Bt۹IacPKOZ߭W>ktLw ('A˺E=Xփ͸[mA77`O\j<8O2bo~F=Dkߞpw'֌`S`p.bX] v8l18q&-ow xmH+'zaϛt SS-ufu/:l`@k~.٭6w"-u-F!X\_țB n]&Za;5Tŭ/=rO1,OuTtׅV9VkƬ` #5р0IN.P{^&Ѻ|Y{u}bεz/V]EEsjߞЯƵd &CͫnD#E8˱A* qs}@n@B>w FÈ3N Xg", XMJO'ih"\޾>)`z,~ 2!Ƭy$KiRVȱ6Su:zᄛ}Ni}$Td``{ "d/ ~#]T,*._!#=JdF?[SB H.@JToݼVRuHsN=!XJHvr,Pkϭ6 ъ`__4=;FF4=VձyDw`vibϠCFPy@;xMh bƇ|xɗbH< 7 l<,yL4O/{$(ݍUq$"3U28_/eQT%It)7IR͸I).d*kE@]$:Ww7\|Z%=ʃx&Plݸ}mBR5p`(g¿@-ZNoKWjxC($Q*cTߌE+nUoQy;~A_X80FNhĢN "\,0Ģc7$W("OXJdz*dh jJ!D(1lbR2r AXuYU!&)>@}CN$7H3aqQ }TU,LJnF2|Q ⑷/vJ*W, ܝgf>k]676Z1nO6;#uiҀ͎"0/!p`q PI/3vTk>Dukn^|gEH!#pLy.by.エ-#6Zs3mT.Ԅ ѷ%XԢmY~nT|t>%iZ|Xh0< w9sцR[nҴVJD{$h[+E߰#ZfzeI[?Bg+4K9{4NOD;Nsio"+"U]+h՚Pnkt d3<uUo0Norm 4D|e/iT󻦙sJ}g^-ɛcV5^~urj ؆z*n^^ߎ[gRy:€dTJfa7 f2xƦwWk2cYڀl͹=*yg0qᝆLyE;[W3R2MI5Ly8^\řE{)gS~,.D)'4)/I18Wg2ˌE7-:@ <2y7,8S10p4y\3gqqiN }&!5kQ7$d`@Ik_܃=Yf?n&p M4GVt}Cዔ茨F-oJ'mny c(PP.S9_ӱn g=E# 8̇!]LXZ^3^>)%W@x?!CT#oj 8+Anq]l vgLjB)Q4_F&' wr3w'}s?]@_ Pn"/۵|+JQׁ _bO^'?у@/[OƼ$ަfS61lb,2k +;n]'xMB^MVդMy*62ONZ,jGi|ธ7~e.&L k]4!:ߒGR pmuR 'ebt3 ZUرn{|rssk[=@ WIJ\%!oW T*diq\સu5a-vIiAQ  DTqM8Ո*s.C̹<i$PW!=-q9U(sq+^TU BsL` =nVv|( Oݍ5(CCĿ_q.2!q`ew0/Hq<~yd^7*UE̊Sv?W\-yPyQFG YaNš/_O hs;/}6ݐR"`xى2?22\@wy9VF:4Hl1rqΈnq\;ⰭNq2ib@bCwgr%a" i #KaB$j9R9i3.j'K9\^s~F ⎴ EBчlj,?؟)7guJ-ϫ@sdS۸dkK=;i# rZv"_lGG0eߴ |%'vOn0*+lv͓j+& ,!7v Uۂ6Խ1HW2}Ѿ;e i:}@mTOd ` nh`3rr؂]&r!Zk$;3};|E]:%o|v T ^,VvBQ2[ TzUi\0Z)6di1Z۸ !Th7 MLAiiN730dqNf XY+ q@) 7diqa`f R5y\3g M3Lsʏ`F(WmE/s'2z3<4s2vHupNs&|zF-DߴJF=AqLJ^v !#^7#>'\>8>W܋ 얿SR"jKޱ[!{.Q-z\qR: {>lc_&_&8n8V=="fYu!bSgKvi7j]%v75*ZS8G-Se,5CYoSuU3( Y-?.?v(C 7M\@7>:F $x/ܭri[A1^.7I?G˾q~Js%fɅGE{YHy)a 3lc!e)M-UPIqK}0gI"U4ZO_nEfK1l\ΥZ0^ W /xeUxUhB/D9|ݍYpʰ0Ǜmv3Vc7K/}bS0m%\Gdž9TS4G]Qid^*]%_ ʥh>#So6ܟqvi}/_aXYlZ_`=U ~endstream endobj 1188 0 obj << /Filter /FlateDecode /Length 2080 >> stream xZKs67I{Z i3NGhvTBM_@$R,A$~vCA0-.n&|PZt?9[)Y1wha,fBsfY̧3JD?5)DC^Og\ lFZ)V~Rn7%J$gXLaa/S &TE8ĉBS% `o+,& =8+-3'%Z5OnrSu_phcD۶ 0k%Zzab4& }F$EmHT}f1:T &f2&Cmz;֪\@ lEpL:΄[½[*.0`v:q{?1v=ZvЌ@uݱ``)e{ D^Ureusz8eDg9Ё̲ \7eo828xܸ;=W;E–FhȮw7v7zuG3kwt i9 TX>`Ψ.f@rU((G"+Yg("%GYdVNQg*![Y:?B57"zt &[tyz28tfT > R9Y -:-gy9FPg^gmטǂvδH@ ӑ&:P‚sEaJ_1 `o&DcF,uWыZ4h܅2R } Bp P 5g}X}r-J2GeVntaDNXkeԿX5#'] \Pt*1@x(&UcT\UXd{eWog @E,;n(jno.6VI5y[C84_( E~"-tiǑ GTfLtYoRacC 3!. 30F+_!ĉ^e`0♺GI]F`&ਦ1 3&fz?p'@pZǦ+)? >7a2##FksUS-6v!bYuVO28Yr ''cv Fs)K `ҴTK\y;?Q@Б*;h&#x.\.ߍc"rWƺ( klK4i׉UQ 9np-Ԅ﷿w7qe[7H ]oD='u} '#ڲ;>}7o;8֛ۋU?σ(lr25ZtF$%asT6?22 V/z|%`gܤv\㎫fQ|LTq {IL.B"[ l MS`y[B, qDApp%f;>G6Q1>ĕ/Req+#ePjRW>#]]L ENi.CRW4&$ DQ`vlȔ<v) |R7|Bqq":S)^ICU?vw;>#{ ̻Z}Aڬ kqaX3y<Ni3:; w> stream xZے6}oȃ*OԖ!R'񦲕3nI(C Ey|v7 HǞrG$эF/sRlRw&?/?䋫+zr“cQֹ `ʦe dɅNi~+~3E1ΪRjfrS:d ~c?Ls̜)U:3Ɇ_-Yoe_n߼CP.E)]LС dSEݾUnlmܢ\^crQ;OIT`T0@SeG "lO%k UnWo]`M`BѮ`M k ~Pb;~20fbW#͌Ij'3ӦM>kcpm|rgCdnٿZq$~z'$LR(&3%Wp` {KQOa!bɩ`o?m ePb>jd[0Q!'ZҰ`W7kna*(Ҟ KrŦf'~Ki@&ƛ?pE=O=jXehV2c:qb.+3mMl>.Qj-mJЕ+U &DXr'w|X"Q.< ʄ &!(J+g0?6qqR[J]-}=t"x % BF J\ipB\ 1@ 2(C `&hJl f128.FqR ^xH ptJ\ESb*1>ȯ!:UqXKs#p yC ]Diq > 9# HhM4 n)DuH+(}q*R Y eiEMKItUd'=aI@'z-'ƺ2u KjY1`ߓ4 2-7ˆx *( f>K1}IJ<#N"ۅaRko2^D$O1JaF> ȫ8:g{hMj}#hd c :S&Jބy22KӓRa ˶+v}}` -uHfT;vuxzWtzsE3έ#&ƬS -P38Eex$" C#M :cC \d #.w"H-XmPm8(- B]~B6T*8. ;UAdEqX5z_9ⲥz,8x {fՑe) ~]Bč ih~[;=}[m[cilDJzE{pb T?ʃ[h.^fRBm6]{X řͨnOF&bQBȢߐ/]KfAOZSTC ߯WY>nE _z'F4}WX:]ܝWpT&TƍqlJWH*\Z\t%4a"AyvѳHyzR cMrA0~݆O^*{d4j̩Rp 120훗 VzopUv ?q( MBmFFKK;6c2/fwe*^H>Ad:H {a %_i$~vhbPP?&'1+":? I1*h cҭVIg1!q$ғyjn)('(֙CJ7t+>96c0 6^,é t͸ŜcyFVP fS$ŠA z!$$'db +Dd#(  aa2?L@|%( ӼеwAWQ,t}Ά.0(?^7b((O 9µtMg@7nQބ ȞnI_ 𓥫&=oF覒CS(9oסIeu&qdxO5e4Ѯ\u896KY%3<4q{J(OB!^pvR=C"˦^7qkz=@Z};?ƨs|ΏtFehkoof,_| JOZJZP4endstream endobj 1190 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 1338 >> stream x{PTeϲpvR9f@i:%@MQ tw,(»lbJjbheY:;5m[e3T{֏;B ՜3}y>Q>H$ X.E+mJ~;db~P?7btJ,mޖB_Z6EEDDgUYHU\(7GmS%ET7 9':UluFר6nOSڐzq4(J.^oX[.9{FCT*zD*ZLM(_!sG\) |/Tg F{uPci!Z L}*H:8Q>hxsvqB/Ўn5 I0TK3FAUA*F *q)*C u> ̝</[nE[CkMnWm*=Ah9aŁ i,8] B.D~ل;s< K1䀚%JJC _L@!K)qZ#^ʒh Z{ 9 1 m舀J1ߊ -!dɲEd1h8(": )y x$j|2 7)gW޿d/V ےf'8Z(2 g4:;Dy̫<^{zNQt#LtTH>~ ' 'n(2)$Hi٬3_hzO!T+j,T} 5LTȒ`,`~(ԝ1 yI̒]bo9C)W~]031n]7\.Cl6:M&,.FQLendstream endobj 1191 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1192 0 obj << /Filter /FlateDecode /Length 177 >> stream x]OA  ~PXIå^zCPzj0ݝl޻L[ ZM9,I#ptpaIEtt=@񫚰sq:T:ҘZIЛ, I+ֶd;V]WK=KJs}-1Ģ+6=Ymendstream endobj 1193 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 439 >> stream xcd`ab`ddM,M)6 JM/I,If!CWYyyXp^!{+#cxnuSs~AeQfzF.THTpSJL//THKQS/ f*h)$f$))F(+kawhjNjnj^IAQfn*32.nFF)G\s{ƟK.)鮕󎭶xq f,^,Tx~ S3+o߂jrHIc];PV7wWo +w0{=c#`qvrcV]o?mU'O0_{gJ5NwjϤ &y3M8P endstream endobj 1194 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 3328 >> stream xW TW֮DJ N51(1.31!q@d v7͎,JA@AEvIMT,gLʐM⒨ƨ_5sz}^$!H&Wݒ1~.JVo8GW ";SodrDzO$lOw;gμٳߋݢs|bTYiI nQn^A^n+UY n*[tl|Tr*-\bFr;en"}R"fW6gY<6!^]v{g?lYEr©ہ6+|Q6V;-<,&GND;قz$8s[ߛؖůySamh]Ys?g8C{Q[3Q& )[ZRI} < ~an ~e?^ki׿CbL|yVlfĤ§`tfi4YYM[Vgg[[8KEuF,֥dV=^xN˰v ,&A SZF;FPbB&I`ks"7_FYi~ްvNw3nWhMRk*9ɗ<Xk&hxĒo8  C2G;ξwQhB<ϰð;X<_.Ч28'j1^:Jf\֎z^|85[Ҝ:%+bD9)qWeI7reɛO_ZY#R6m)@ξn/dIQjY:yo{Swkqmit!Q\;IHpzVmEeep ]ofϙ: <$8jmqmӥvHץMA lb>Ңxрuxz ۧKֿKz$Lf (zQ cOdTGdAȾKCdLek5Q> ]\ w`Bw`: 5YvSJ[4o dÖ,z _qy~Duxj}7>MATzInQҽcm+J<O((Җ6WyY_M|aLP9/2mR;zkw*kصEy(*hmwRlZF^mґ-?WM@v{*u:]Πʆz;{Gendstream endobj 1195 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 801 >> stream xm_HSanVU.DK-?Y:İryssWϦnWZكT?=|Wp;2 bkaQO٤;]m4Ӷ!2%6)em THBhKCq#dQnvf5TnNl1Q OGC-5THCbbDb,TH csTY鱒RDIq,mYӥ4  E G$t4\d䀫)HR*ETpi!t gFp<:p-jhCmBz_EO5" 2zt%=$yJ ٛRa9Euez'|87EGkE#֧h?P= 8~?< 7V㊭H]v;=Е^y_,"W5~wz' DА;bXksO&I/ ?)nYӅO|"kݩ{3az|=p߸8;9:s.S"DB߂ 䉗мUtHw88u.k E(Xr;Vpyd'!6մRժLe+*(׶81tWt yPYGߦeZF*Ry+r >ߴW^Ulrendstream endobj 1196 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 388 >> stream xcd`ab`ddM,,IL6 JM/I,ɨf!Cǡ~<<,(={3#cxNs~AeQfzF.THTpSJL//THKQS/ f*h)$f$))F(+kau c1C##K5|04Y5k^qdžO3 2-jrU_*ؖQa-SY6[%=}AZ+4jԥ'շoUɳWt/X^643 NAsm+]yڴi-ýgڼI3yxn;ab_/➰endstream endobj 1197 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 560 >> stream xcd`ab`dd M3 JM/I, f!C箟<<,~L'={ #cxz~s~AeQfzF.THTpSJL//THKQS/ f*h)$f$))F(+k꡻ H-Id```64bb`bf{a5|, X{r +>]{ؗuyt%usHg_}r?سRپBMzO}}u߽D3:J[k9~}yn]W]8;"M#M]b-V]:pW7V&n[756uwvK0`X߷/g֞zsgc~[|;o1#6;Gȩn7oPw TQ]/"]+3d+yS泭-ýwR+xxO?r$^(endstream endobj 1198 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 629 >> stream x]oHqrSAInXRTŚ)b%4NtO7g:) X04՛(#$GEE"<ϗB: (*Q VÍ:J^C~S% Ui\HKQ^P ^_3ޖgw}6lj֦?n+Cbf1=>!P:\9+\EN,(seBf?P*FvTR/ULKꘀ?8ZͰu^H͓dM.eZzA("pЬOw[do2EeP?5r^p*v6u"1/peVh:Aζl?É{\$6T{<$q|9/d*<{U L n_YeF27 _I %"| `2Ӗwex.N<ٻΎ7$v(:@Nڹ[?MF5"uOṽe<׋!?V*jU$O´ $x>Vv/ӟߝT$ITu' pFRꢑKՕU1&zUh>$Xo4Y_Aendstream endobj 1199 0 obj << /Filter /FlateDecode /Length 163 >> stream x]O10 vXЪj8(Nߗ,N>ˮGֱ4%"@cQ`Bv7ޟ@n'.eWm)树DTX b'̨թ.C8 HJR%pLo9+i?SYendstream endobj 1200 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 309 >> stream xcd`ab`ddM,M)6 JM/I,If!Cۏì<<,%={ #cxnus~AeQfzF.THTpSJL//THKQS/ f*h)$f$))F(+kawhAQfn*c##5|wd:StMvzj;%M?mFO-8uB7-d;u[9\Ğ{=&Ogľ=s&Oe`1wLendstream endobj 1201 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1202 0 obj << /Filter /FlateDecode /Length 5377 >> stream x\IFv_0}3{"QO8  kiHl-Lt5@/yתggvy_M^3Σmy0]S:ܞ5kW?tSn;n/Յ1: +TL.6w S>3֧FC_mpHh;n/˳_x>%g6l\?p79VtM%8ҫMb'm]kvZ@lZ+e͡oxk:e~sVc&6b/+Ν -s/< xݍwRK&RMy|4k׶oݚ7u谑7)5մzk޳lhZ% QFN\t\ӅFWC{*w؏y {٨RͻUQUlRz-g-o EZz&zv`rȽiyJ@ܭA9.OJNb3G폇Ӱ֧N):2LU"q4Xx`u˗Щ)UDsI*,Ǵ;THXT/rv// 9ytAkJ?TC5 yH$I8N{AeS$p! 6./I xWK:+6Mܽ&[.&GJHm0^Aε׾&^ $O9`b1 `ץy{#*/C'o\ 1y(yez| z|@{;*v跫FՋ;a4z5;XN]`Y X2S*55+g1+?+W!/p3߃Rkjge<_6[E6wq2,~1}8je03xBv(mq}T_>xUD&.JxJԼ]9c ^}ۿae ؉˱9Ȁj|1Jŵ؅\ )ŷx@ly$SKSu]7X-]YJs@ ^l0^i'zЂ|>;80eA*UDI`v{r7WƩE^lW%i 7b8ŏiJ] zĕV83-`\gUOWS5eƠ X؆Sx1$.->r] 1M#RE_re_i/u\݊G9; & T{C\⊴ :8lQs%칝kJt?훬ɣ:fl}A9 -hc<0DMX*9Wmن$J7BT ;風_GKN,Bp~,ò 2DnL.}GA,yn?Qyu {ŽˉAbh^c)W "Ow({r spea-r7o`J a pr܀[=_x:Cg[TsOp5S3=}S(2|2H(~vZW NJP4os< "3Ӱ 9Iװ폻p)b-, sQ;wOʒ+IcKO--C@r4TER*|LUjW^5Um8m kUiZ(o#SPV}yS"*PuN hѡTmR]$ hMkcaԂXE%e+o˅UDmpb׳bU֭reQU^vmT/srP LwȠ^< ˂Jp[j8f:;[愐M3ϚJ+}nZDns|l2|CrzUvê)ʳM& ,2 $[$_f;OJ؞oi?PoEr~g@jwni2t޶=j7TsP\$9o%jOzE][ JAQ̲UcWk&rOkBee]?Pkjá%XViOP҉.M_^iGa 75c߈wc|m,t|(&)Aa\UnB?0;Ux@暎;j730EK?ItG͌0WPi1=d$d:ƚ/)i.'].*%'wCQ̉k*.,G05WҾ-$?!H8 8J(hK"FϷ‘4#on̛r@oZ/HŽUyNH.jB41}0cwU3'~@;WP܇Gmus:yfAݼ7Hk&ux&m|Ao+2lj .o+?T}TŰɾSg57Ր]՚V!_#zYakEO zR19f?'oўz$2U? ,!;]kbÜq6`2ٰ_W|a0&6x_oA;cj^28,Y`>-GMY+Vy M QR(y(!8FgSDcfaybJ)8GJMڕTsgW.g|()̭0F].ˎl#K#B#ya?KoGĥQ!U[~#IS:Aj}85fpp-fTt刜KEL5P4I}r|Җendstream endobj 1203 0 obj << /Filter /FlateDecode /Length 5393 >> stream x<ێ  <VN*|,' ر6+F;{dgf=]ԅd=ֱk,u~;Zq~!H۫I }T˩lm8UNj?pҵΜ;t:1 Wɛa:"F;ܵҜ^ g?KgOCg' \uY(qz:ibvg';9 O[kgC7} [?AGaA)jjE ]"MbЯyv;\%gI4oPj׫8\vbXLuB7tݥ5~6/P8K(Kb` aH0SB4Lxt>M z#%|; }Y'(hZ#Uuv?8$u>I:Alk0OL3$Y7w7M4,x)x/(x'Pqgt6n Obѝm%L!i ibI4W~Yד us^^bXٹ&xll آB0DJ(@)ԤS!^6ӊ/q ;{ř1`*N砝}lyM:Yk;9V ̏fF/g^1u+ ίZMu pK,a x&$2-O >ӗu47Wq*ª J2 @ )s@4s+ uVԚy֒Wv`"UP9n `4oޓh(:뚫 k0 gQd]IsP{`u$mzLL]C/ q%gA7071" 8fgA@_Bg!dPTk$ ٧r2Q6gєEJt(!-/Ƞ-h7>3Ta -|{yWOT |.LiJOKmt@ZR؝_~s`D+(ڹ Gh^# /i |z 83$=z7M%jxX1_ew$s~>7u~ÿш/Al_p٦FDZ1pueGZ|6+ڰ/,aAi."}ݬSg#OP>lf봴-D{"Β7 ޕN⼴D=z/-sUf-a>/":l, /$8~Q!ݰSP4͍t|O'h\B'S8Na&]Sg&ʇ!bMCb.Dq0Qfji.1zsKݠ͉RY{,j𼊮ߣlʩ5 -3 Q (߁U)=}"]9Z^ɝUJ>Kr\ֱf}|eL)+"vx?3~`Bp- m5;%N pb8>+ v8^}CF^=ǐ)y0>F %a<{pۢYWعY>$%I'n(X33E<=h$Xxq},y 28>Z ̀Ȗ3sfdt[OvZ];rrH?5IGH,XGƮ:es-U:Rp]b}1i'H)mI:p4xQ:/wpاYF2!9Czsi@"j} K}B| q]RGrynrŚs-#,8: O$8ւarĬǗo/b} y.\JCj|8UHݗrUBTW~Eya:gC'!V\߮xqȩrKv I h6Ft g},d+0%c8~ɾ a$-3W>V0π]I?Cf}>E/&Gεܝ htIiXV>;4nD(2D9U ecF ;,XŹ9F-jGbԗ ʵݛKr*Hb?I\zAbvN "_ّ,>*Ѻy+i$R+QWZa*Wȉ"mũ8m-wȭ }?g!] U.fBS;l1{v1w _Up!毛}iR *%(2%-)\2 r^Mn+T-k#cYi7 v•{zvKPpn w~=F.E *qNBV;,Li /r$EHhq.S1T\)?6) hjja ZڣPQpLeΎ:E9db`}=[S C W4ɪ\ g˫z8%Ce;U&TaGuR(lαF&8|&W%4d>BwBv:q۱5N*REdrX`&Z JXJֆYO8Y/枥`llóxT+׎]xZܱ<$gbVuBZ N( umD)Ft=Ꞗ R«+hxIT\7 ;N۴0DAbyga{=H ͫV˕DWeAu幇vuuQ(?<>n,XeӶ@k}r)>DwئlI8^5Adg! "%;.ΝStgwzv>}1S#\Ynó0Ε Pߠjf ;*ų_΍Qv7-vSZz: ;Ef:,rtV2Ǚ)P Tgi{ X%ʫo7¢=%M`/]6M#_ZS$P5#Ŏt"||ŒMnX5W+k|?2s#W{ɤV ,kj}W tbb[S:vc#B^b8? g( \Wj&Y[#BơQ!hÀ@Έۄ~ެ'E o؉X3v1loJ ҿnK/5Ԩ|\ۄ5 'lA†4篻hOQ#ueG2UU(~~/_|L?Dm*.ԧ[X C99;ѽX3EgW[?jg9aMtJ^0P(cbFJx˫D4-D gWP޵ճݻjl/XD;hx\ a:R3x>,/ygtB=Ht"]%0$Gq΁<ŋʞ=j%*2T0uUWF"tlivkh)}@rb5֡hh@$(I˼mts.pbέjnf&]jtPBeVm_\au2֊!.4!6>VOu+>(m[9Ҏֱ+yd`lTaPB[Ǔ C3W_?7 %+ `bhװ.@j&$tc:`>#&/p[)<61s ś1{Ueӭr"./њ=q;%ilfKM -'X tj0|i5S]}ELT^1S8VvwiNFL9y? \hu?a[<Vl9niHtfq5X<#%%/&>*fD>kLm,OX0# d"(` rlbGg> stream x[IrqNj\cuL,vgϜ cYNMO3G#"5 hW3+EDίe g3F|=s'u3?MnbDZ&볗Ë?_OWmD~|O?fğu<哟;L*0iv|1-O@)Sx$V0;1gsZgn0j 9ϻIk9ݙf^bf(uZn7-|(uk*`}jakDYM3#/n }f:MX4|XQ;qW-܁qhn"i^CNOLs?븶l[U=`qH<@:mCwa] _wXT_m֘R y`0AXUyx$m^`]gݼ8/Viת.HI%Nj5Q{G %J {Wg]^Ɛ[cMPnˍp`94m(B@෎UUa2R <0ֻ{cW H!e"HAXɍ \uw+hH*nVy Ky\+j vxKFa̘ɖ|'4Ei!4ZEb# z&ܔg3YJV4%FD@SfCd2]U2pql^`>F6zõ1@RHɲ ¸cX\ځ71k-^\Wo ~l߲Ml}.i frVh\ Clx?We7u갹Rd G:jo٫6 Z;0be!4$0Y K6LFNQ AIr,d('.po:jr.jO5q-~'Y+Hn“~v(ĥ= h".ʒQ&t/roM8nHꔇДmn͒`D95g[&Tb~f컇h ;e 1zWfXn .{qy Vk@0rfʍŇݢ_0~{O&m6/XkQ (pBn4bE 1A'#$@p١6xH#ZjS8rdF~qUU+xnTW#t } R.CQEobJ)wwB]́"ak ۝ p!p!׹\.ɪĔ t2D\ &"Zm%zY&0;I,Qz DxhrC[j# &։"bOf]p35 (%ߠ|N :9z c 1O1.9{@ogkyLu\Ar\ @>c&?l?a縕8{0 :x |1Ŵ340TAxgury("㞺%UUлcȥ|Ky4ۄ`Qp`b}#Y-CSB)ʵavl1_;-jbZ2h츙n~҃,W!}adPo*lcSd1SFcj?$'q0IF K@{.#[» Dculi~d V*H,h@xyβ]ŪDQ )!oV9^k|:H6/[ŒLVr3̆Yt>T WK/XQPHHZHr֯u읋opt&E5uX~~o#;{*U:G4^I`fЍtqk$<]0i 4]$!`:|vdʩ,ȲhDUy%:WXFb0:؎dEY.AaA9R@h0dJ1+wrcv_RL\6svQAȗͶZc0pL:[ьT+t^d!XBXXmXK;tD *h,=0֊9PS%0`_23 7 `T0gU7;axYMm`,mi<׸ +?BOB<ఴ ö1+f0#&B l4VR1KŹJ1=DOېS\ȕ itx$,:H/NIQ\pV% 45 ˣ[?IxU`GȦxvR(hegd8F;9k~M^9s#p]N0Ye\0*KLQq%ŚVe)^TFT`HJ42gj RXjsp,}$\ !|%Ͼ`X~ 7V \?]s vyQpY#=_ګ6)˗o8hM*@N*lg)Ǥr4O[Fu ].WT%UZ,4l*RRH/u(7Zf ڡTH~)wWN7/`r(Ӎ؜b z.1L\|b:nuX"edqX/S, , _RxD"i&=eg/L"#|[(y~V~WVIUt* P]xK\}+ *0 .^}z(}O8\>"XpĈ}L}U0N֌ ƻa11!a#xLG㑫irl$rĆ'w\|WFOhM T iҳ }D\e|q51$IYQՔbT|sW+}=Y=,$+I*)FLa)ʭ|xr|"[d/r X4Ep )6GGZX*#S GBkG/Ko|?otm 0.rrﻸ+]0ha䌭ݗ9m'HVkB89FǬjߋnқf,bڼzє qA@똡WAB{4)aҢ|.IrKGRY7~Yn?WW涊E.?Tj'X9Cir4M HK'|&TdX&[(ȷI8-Z'S87u^Գ:{Go%O 3,ˁ礪;"u-]P>çϟ|{E~VWN v #K\`/#|̶̯{W 1sWkV(Y=_zzO2ayρYeʿ}V(Ro@H|Z%|C"yZ-D$]אWkN_I'4՚X]sEWϫXGB\nz!Yy+x 1.uv I {z_Wծ΁Tvו?=>; O=lJi|DֵTs['=`G SByϺt@8=endstream endobj 1205 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1206 0 obj << /Filter /FlateDecode /Length 3700 >> stream x[Ys~o1;ŕ(eeoRʵ@rc = !.Xrz1. _w#?oT%V15O 47cKb_F%JbMxH_z_jhuQiIJ Sb^TYRQOv4(I;utjI( a}LG^We|&E-YRfb]5Əed {W2b]Νaz+^O.8g`V1m(qQ&n-$Y{ȃK>U)iǡL#SD=J`;= 'xHGx`6Og n|4^#Z2n TU$^kf 7eJgTҁwL*É-((,>˄4%2 Xrm}*Hͪt;zZP_CsLpF]ì\02 Q(o"`jlQpqf݆.A 4 *P /ز46I[Xovx x},>qR׎u@MV|'|K`'Eir7u"<T'o~461`.9=MXv/,4='=quu P)bAă%͏>4u$gBL*wWd pscނ[wb>C7;~Yջ{[DȤky %S\8l !߅[i]g+%".ֳC5 %ܩtӸN&Z5V*hmxEA2x7qAgo'QP.H9;'*Wh\rJ * P:)2ȏmV7?m_/ -F%//ќU6vQͷU->wȴUF=RZ-E O:A([PÑc%0tKǴ[jU/[Bo:[Tw.#$0dldut]c[t%ӋDT]Å#}Vp٤@?`¬9cBVy A}A5$ErFJ~;O8Yڦ@@(C9~at? z=Hs"sZŚ|_Zmymu39rvȶ  ]wȗ6i \f) pRqZ'"Ch\3C7BO$9|cO8 shKNCqlqVdx^Rt wqk>I)݅X/@%% k7GLwߡmxGJ#D~b = =_Q}0U¦&8o8QNsd|ήfE aŦy4ŃP:$9?/e\tm25Eb\Dݛ@~~mrZD4Ul]uŪ6awLUU38=q\=$gĶWB]`TO>4؀o8%MN}&_k2|CU#OUS'{i૞7Ea%竐M<; ^.gn=恫WuIs||Om4X936ǁJQ\o ulZ{|q.`;)[|v9zYYz֣sKe^TPGez6u5P f2OnP";ADkqX6`IjpTJ:yOE^A3'c=D}5^r\<NL@Pf]?([_uxַe6@O_Pg54~ǭqc2-nuItMX-pm;B9~k;* I㎩Fsz3NQ myGАBJ8 %?)_ `f}TPֺÅr4 s8ԗH.^e4_ӡs1-̴I23D Mݧ駣=W:Kݬx&bf3+Qto|Yc_h]~L|u`+q9 ruْ+ŊfC9źkP ewTx& z a88l(c-J ntm}HWJqȤ!x#m5aբHGxI{$6\`|2[zqMiZDM 3g/l?[HQGp$m]tzmGp$sxA<&hq_nN=tBySۼ#"ոau4яƤ"";_cN6kJ$.#\6*9jEt/m̗8dHGZc.Jc51B |M{N_D<?mQ6Ky0u}|Y AY[Ncendstream endobj 1207 0 obj << /Filter /FlateDecode /Length 2870 >> stream xZKs7`3d6ܽ)/DB6$(-ק{^@N9U)D3=_w=EUEZܞ?#ۅ-:+”FRXݜ-dAXUV-tڝ]ka$mٛⷥfeE)%Z{Pȋa//u-z'ZxN7%U1Cv /+e鶰N/ٰ'?'$\s#_]_^2.>ziXR *.>//qvj2geVk@ZyyD|ޅVrqޞ_8wKB%w်8 7 iiNAR0>n>s a UђP8 )]n|h0, : :ݟFB" 9͵nhoO"!I 4DDW./9Q QҴȶ'm9p0A!~j}p/a*#`1<&@S&:BQ+V__TcRuĮ^g[}&*$Qǻl[˅ |n&OcA`„Ck^U ~2!lcfKbvEL@d:[MrBQ[%nڎr#pYJ4kDqk3r}DOΑyB{ƀbK7`a Y7b֯ХP}zr}߮m]Ѕj\ao.8[7iᒗTzmڡw IijIP\KE<`,/(^jJ) am:m$$=l*)!]^؃L\!yF!.8v۩t{P%#` טK@EwW@ʶ9 6dO,(%.:#Qt TYץ:GH{}h6ʺ1z0EZي:&K%WDց* MYOk5˂ O(W-AS?6v![B_#`CvWHxEl1[W:kȏ .0aL*F8n8ᵧ:e19T!PT 8M$4d*:Vr#܃CD#S#A켐:RU8(VxǔAgmP5V ָNU4"E fYm%2VcËc&"Ƚ'p 5Ab[r[Ps # ^q,tI%:,K>5J$l A??L)&xK`GOR>n.x0nfqfo>k9Ť|dy={,p pkӐe$xD'(9˯xxL`~M~,-%qS8|Kb,F}eсC П]ln~!@' k@Gls<ث|8VLKgx iƻ~O0,OzMbk`jnm#,CC9eݒ-ٸ ?*'B,p{voM {:z@Dk^jHg-C’<>[NnHqL 6ٗϺJP=f]R&v2?x ]n=y97Gy~_)=00@czt1BkW.)_HGIj%<0B#]wHendx5봭Ӆu,HLьʠAG;+[P l4U2Jz9SRBQȽwe_WR.hDZ}OY=R% mroqt28#T2Wepj/oޙw]gzwu6a5 RxkM $w+ULA٘ 4'j8O' IUzb_F_$bgK0f"Fݨh x6/ ~Eދ9eoj(( @ĝn|pƙ~{9/*vSfeұ|AF[s(UG[Y`#@U?3ARFAqgbO[i/Wg`uJoϴ1Hd! BEߜaub0Aqx_,cmJHyK`Q`@4lĨdL#'ݩ%<6 VƠ 2)Wo^+LC.cSR*J5${p KFAf@^45 "T\UMU!dqI[ۺb[K(iR"u>W9u3'tpg%V~ 6C?r))&_|y 48^os\D\WT:<_^ jgbû,^Z qs+1Lkj1|4~f{?|1'`yOZCkKOv\hi]#=$.\?A?zu6C͞7ql|7MlTD&~kwHp?M?hlہm&qʲ:DuxW2 4#YftqJ\H"g!VO I91A9CnsUɂWkz<=/g gzlge\O/e y?IM^(FZU\ 3䗳>endstream endobj 1208 0 obj << /Filter /FlateDecode /Length 5594 >> stream x\Y9~~z!7~L ,yr崎dA2ɔʮƠ" Ɉ/g]+f/{f~;lgWa,J;-ΜZz{[J07rsvo@a"nE+D5{sݬ4Z4])Yu\0M;>08ans c9:~}|K5˹>| ;aͤ7Hsq/`#oP0ws(nk뀗qcvmEuIpy,A8=ypA 2ՇV r Z\3^Z! .pec4esfp6acl:kaPFšI`e3T6#-ͽ i #4PD2cT=(y5tjn$ǃ,4Wj0$1/8tx9.]{w8r»VYUͱܭFe^v&|g*?~~?reC;Q %;$xSS %{<#;J3 Yߥ ױ `@okދ~}ۭc1UΗ4I]wӆ41g#k~t+dkz#Z[mT뭓a}u{#_7jm ֫ܳ8): ZcZi|0~|!v!:]]| 3!![#\IM#@}KM'@?zli/3#%'Q-'낗Z!k/[mæh 辀]Vͩl_ȧeTx @ j._xX2=wq2I-iVV)m-aAC߽;-jh͘엀2m苄f?]e_Qow"ڷ0iW5"=ѕB7^0,ojVKaov"S5#.s~l>ՔQZьH`fm')cC@95%X 5ܥMPͦ%Q ժu{ʎPP"WyJ)C lW5}K^J!RO:CnS^YSe #Ȣi(cBu^Cc^w} {sͪXk Pz~6)?+-HmoWܞ򭦑-G1t8p Qlǣ5fDDؑXaij J3dep*_J;šl$冢FEݖvꝁI :䰪:_ڒv)91?&'>iJP{7 bZyHsp+KnaEt-7kaY:d2H$Lif(p ]1$fc=k\

:++4Nq KJ6mA]-Zp-qtn4}b?~w9phtsMh/@*sWREVa0+MS"EcFIo$o\ia{7o~p0 ~}Xl !tB w@5Dh>f 2nr%lh 1e߆ݶs߆{wn6]W=^QC5å[ZVi}οPkSbMtoc]DY_- !jhfbBq.LL XUlٳ?r;W!ۉFޘZ>V ?n>%hpS'o) ]Һp u0!X;,C|:%=u<@h7P/Á$ FJ7)PC.\67YdG_m^G"o6H2:?,t(MgMvLV`i6vCx#ܵ%Nl/,`x|~!$YJph8Y{8TT**;z?amNdTU}-lL9XHSRp/%5,HÕV310<^ϙJhpA4ߝx]yL43@L^[x\$i"Q\$#Z$D:O8o&Eio-D ޞIE?]*DKʋu?#txRx9]2<. uL|<:n|jh!r] 88zNzǜ @csOTlc F#Rzے@Mv'Mn}Ţ @`Ka y~^^H|I#!\+&}ħD_owf <*b TN*r (51#{9OWOC}!BAO5q1_ZӑXUq( j'> jTMa D,<V. -TNpe8(P(>+|Gn,Q:\jnW-Ti.!9Ca_v-*q~m'O.A)<ׂMjY ~!X~P,s*Z-0zbáiTS#iZTX  lhZwAj(U'v?m >9P9I&BECpn)H3j 抨UR"A3bF&@>z47o}@w 2h_H*TNNId]_sJ0; i]OQg)I/!\mU9;EoPA<ΗbN5Pvx07)sr쀠p/5t8pA~Zzӣlj Lp4c:6EQc2"^ rMY P$ ,fMcsuC 2Đ8qr,=3_u8./?:1+ ϓYtyye&3JUh߈=/4)"a +;[M2$pҰ{S:Tޔ$lĚ W?s?7q0{. f~)FHD0 2Z YaDQF)b5#BQcQf@6 ưsEqUkɋVp{?  CJ/ު.,\cۄ#TpQH,YzxpMb`#*d/|`nrIwi|TQ 8tZ[Ӄbejn56-x$JtTo0?G?}ol <5]Y , U1n%ݿQK]P?JL1_h#ĺ|V8 oK_38)?¢NJ!Σ?]aVw1{w85I(`mFPr:CD+J[%-{R^dL ,jc?*71WuIϥz:F)Uߤ1…o bFj'>l>l|R$Kcj1q<ڙZ܊EMa cka3D' V=W1鵪!D|ԓt3nwQiR&s|k`?z{6yӦ豰7 8'7W5hܳ-0z?dcg=MnL{p}s9={4j,{+0k*EZ _B.=x8RX_MSӉ:ep< ?<DJo <(1әvT0c 9G pQr"jX mG%v0bQ8 Ő{~m;:I͍!ljd9LVKώ(5#͉`B<0ج=%.ã5[w#?v7[8@aЎj gKwBg #9d ql!bMqrǓr_=m+q`ypwڋ75[HU%>>jոE‘mCE|vop4qcQZ[O5,sehZSiSNb2Pq}_2:ҏ<\i:WQ+J+U(-R Wu`XS泘P8NpfkB`ySſ]rV<4U=UoGj3jTk#.7{~p]񢚁+,u&{X0W4ks*roFk~c+/h۪}ΣN >yEkxx PncvOEh/4_ؠ> 8H 3ljU7X|_ gLS R{~gDΩ'Dsl#rNm݈( R{~gDΩg\?Ӝy`{,`9M$YۥgDΩ'Dsl=S+vD+"FA1`f3ѲBMh?-Qz G?yO" ي`l5=oKK - CPZJjD)(KICd"K›.z,W#x%5rڍN8> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1210 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1211 0 obj << /Filter /FlateDecode /Length 3554 >> stream xn#oȁX=/vrHqC4Qka4;JSA;DVB)O|X14O|}@6 o624@ka5 ٳQb+sc !7G5Y8ky1̏xLm}nWI`"m[ cЮ*6Dd`Ȉem*ۡv:~SS ͠8fT q;f;(уwW N4z0'EuYϛ|Z=-Ŗ(q29YH~cJ1EP:Sm ܒA%%R akY&+ku˄ h#ݱP>Ld7%E72.z'Bj.f%^6~'Trơ8< źvV:S9]IoڴWT1ވ>tܹVj)N3{T=ľ\1|5nQwX% *LFKZY@d <[,BWlaOR Y1x#E|<–~|ۋ\=rE1:N+M e9.9,P.#X]K5_!'jJ`\@Nt5⍵Fݯ?R)p|,]߆s ۏ0F'&t{ 6oL:B +b{@KyXi+9F.f-Ml. \TGpP)k?(U7ݗ)Vy\HP (VJ{>1"M:Nm)Ҕ2=to> ,FkQT_n^dG(w>ŎYM9! 51^peI[u(Nʸmwx/l>)ES+S†e5U:y/A{U+$ԁ8gjHGE;=T5xRK#F5HȪtӲzJ]d(L>QNӲ菪Z\ꅁeu-d. :MlH+5X b_N`KP'ێnC9%ָ>ƢGgyKLTtPLS)9N ңkMU_pK[~]Pt.7,6"dd7qc?ק)@ɱivk7htKzZX0N TrbpkDewqc q !b  %JzWP oi*9_ro㑃@p뱒C_/Qy5uv_'՗A5R\Qt?.mlHO_J0^HUEmJM@_dsx* 746Ӝ4J AՇ~%mܛo'{.(P'ͤMHȃkPQj5~%^c)wnocDQnzLK DkA#R izqtyy.䣨$E_/h\UF4E* 0} 1*׌ώ=˘z(+?U+!A /[>X lrCǴ8UExUD{G[ ei3ȍMr4W JɥR8҉CV%d猧J, e`A1 2qu#YsqRIQRrs#x}kNѷ4x6endstream endobj 1212 0 obj << /Filter /FlateDecode /Length 5183 >> stream x[I#9vݷ6 šAeTp'ǍflppT[j~ U̪60CC-#U׊UǛnu덠q? cW Vݻ"#jrk}ǛwkO]oelv[t9߼#j+( mqaB [:f?Ϳlַ]ٮki~Vp]h{(~ڽi%\/D aic5# x{*}![yՓ.l3bh J$$,5Q`~ٽZB}=[([#i{O{P璽94އo[b)q6WT!aׁmͼ0<>Mڣ̂+A[I# A4Y L7 ltAi6Oq3 O9 H0ZcPƿ&mOܭ̇aSxLlAupR:! VPzn1ex ~2e,lCZ .v ? 068UlBZ0Fa4lFKPcIA# ,*G[ ZcIre7)>f9爞`\0ŮeZP! 8>\Q~&->[$ XeXƳ9UV _C& y!2 /'\J8|BI& "Vh2hK?V( e1 +pce#l_rfIލ#/)G4s=]^:OKZgir+Cbd:pӾqn*#Q5#gHNRUESIeS~fW6Ћ S UYcJJ @Sms23}KZiT\@&v &>ϟd3üQ !}/W 7 ] =ij9IL|Jdk)~ 6pN}ftjq-0KDFuy",Hdapo9b$$F<&Vyϓݷil H-)\1]XF˚彮sio !2Jq]DB.bǵB &1 X<҂ciVI#'A6@{K9u6RwpBw37tÀ>uDx|!q"b p"'UOSؠ$b2j 4ť )5ݔl&GN`vS@C\Bvq1,hu "+׸ (n)~*g3z 2݇=j2Cr@z.: ] kȻBANTCҎ5@ r@OX}?/%:4!l!8Uzf*n ӎuIiZwRq}L1F'IzZ,՘?ˑcJ9+'ԧ2{eOAU:msjNT.SPJD1,NM\ti&XsSbW'B`3B6NUߔIxaH+2dsJ]6dǜʺ.‹#bp)]um *{\fİlgnj6x@,r61J]ܘlrppne;IzѬAaIƔ|T\&YyL3X4:j8& ôNq x6(dF/fS =׃mLOCA}cB MtmNC>[[.k4(9ϧPiXSNR#Ef#Y\῟J :Zגx|w> 1:oqWߝ&$o/+iZ+M=?D>=:S/5^谎y.XP?iXcˬ{ aE2["t@N`.OxdTM#NU M qm? ؓI{_{Y4ױ ?"4ɻ2Qr~*j$ܢ&WjkrmsٚڨGhIʅ18V"G+p܌ꏌd>#rMbtK#'@Tx2 QXXt-Jiar2].2ú8h6G[5)YI 镴:H.ؤc@6&-? mħ(5(HF7-EC&~kߵ^F+/E\S@Z%[UjhN[IO9-KQ`^ A,yVx,)ķazﻈ\y eUlz50 E &KvBj_{ia5^E£ f,ԁLRԛM`gb RL?օ (`ʢC"+}p1& `(U ak=Sdx+NJ\:yj,k Bᾧ㴜溋a\6;1wm0ur2*Y/7;.2Nޯ4 jƹ@h^/D i_BsgK( հǢ;sؿe#H{@/RbHqtx:´Wt0-s8KyΖ bE+g !DS Ps${F 3˞rT&RkϿGF'1EQɏ;_ aP+m8"lu*)TrUtq0B $z(H/aĩC q.^h.!Uq [pk$'l=_URXӱgR9 m@\.ښ0w")mz]2#͇k⻨@TU 1#cKG!eϱk9-*"pE,᝹{*iYhS6r^fgtмPcr^ZoJSIUrմmj)v.s _Ptn^Buۣ\"w `s.v,+5_T~q<9KP|u{|ǥ?Nx_I7a-1T`WUIp)W>L/KQ]QP~اs %@W@H[ֈk`trݭ vG| dT'^z_ KN& },2=p7"il=@j! ~QxMb>_!.PbWA4_k/ X#l~HmqLX|ETnZ4S4]sH5.@x[W$K/S3lSR--4+O޵s­蘡.r,?$/x#?9#ځz6tcqo'h.!s| ?NxF(OiBEBY_0,qUүWWiN7 }J"zz3 9f.]R\#紦:.n@?ל?.gqyGp/2gʽF[dpWaA"5xzHzW h*ʤT) %8 -Z k}miM / HZaViܤE7f8 y) JKs:E\wқ,խ1 8kq㮕K9 l,N1v(҈qz7 ۥ W̛!/eu%!BU,gVy;?>2Pᗠo؆_W-v$=iDx/M!P@ǻDv S4 ^)B.|,1ʷt D; G BB/Em ~r4i^ARrYbZRgr1a"({@|K7=p3ƒ0a,) %&Н:!?{sG  鲾6Wu`71ڤ-5xر$ *;r9`t jfsGUH@ #L@Йy-f ^ؔP APnQoi<^lɐV7At1j?<gJUAo ;v=k@ͷE[6⣠mGX+lIiAZ_8hٻ*W;C@pͧqwR7Ӟ:#K:s+03R\kI*I be핤m99NA( endstream endobj 1213 0 obj << /Filter /FlateDecode /Length 5799 >> stream x ]'BY X 9‘b"8.%16wO谊`u"ցFLf]14Hx$VWG:T?6kakbxڝUyi8aUYMb8>1`*FݬQiY)֖N oP$AQgoom<$2]at@ORl׻e|y0ot!RѮ<`۠$qðPiA6U04\/D-qq]TD݂Q<45`Oh$5{q">-Ҥ>m@tr`T5jVY8xLwVh$>8$JkVdZ;UHPDt C,}WEDv,3j<3&9JP5;#n2~$I`}SV~ ̐/xhښE &6_&dpޅ*wCVM?MPt9z@m.H؇f9/n !4-eMӫ͆#h0MEK@sF cex/ic%(C%X  ۞,fӔ7Nt<Ǩ~g<&94MLZ:w(4Dx~FNx٭JqFzL1rth"rƏ62(J4/0@|dTLU6:wQHvS= ϥ0f!`*!Dh= "@Qͱ!/u:nَ\q_%oEe"RaDH߇c*RY#dO L44)HTwYRW4Ou 6GPq~q`: i}+5?:F4W#w~P>/yN~"cyn~Inv{*A*ZוZ:rEͰ~|F9lY66A95U} Ur>7 zCsni;kx #o!EڭN&~&5h_; هk8)(GEZ9gZ G\*ӀS) SW̙F̂+UEzixT A(5Xk2'n%G d35gb8]0«~#'8FFdE0qxt7݈Ls^Ɍ`W f4D x̼}ooxq"s&I>3 sK>7oWb'aGH5x yC5VS )T,~ MJEtW~ DE6ܫ|=!v Dz9VCD6{j<.)FeGn~㖕 C};,oM|,`$H =S;/t~͟µ(A6C9^)z pEQJ;'xCyR~8[X@PjJ&K 5.٦r6̪$W"}qx a &Ȧ&B ĶZ|BI!;$w(D2,sv$WL+ݤ!m>Bk$퍨Ӓ盛!$#H\P|P>F@*QLDl[~@+dqQ!`++Eټ ,>:Fi{EҨdpOn#jjٜ|^X3:V US>v녈*lKp'@M*߂z.WJǙo.y;ޞB =; B0rg8~p GW;׹]-^b-a{xy`# )[!:xfb9$S$Y,Dl>;! Ad'_3* -SXnp%׆k)нԇĠ-)b @hUsc],o xI?iMi*|6bXp2d?K*<jeh%!ʣa0כj#曮ӥh-.h;fqZ^ts:֜hW2Y ۗ]f?S_omŚB_D ~>^)a(j4>xpnMK0_u.aj{G)(Zib1iO8xt ]GgkWu`^&LY]c=/Vy!7(~ k=iL? ҇6w?xe&u$_'\HLO}3[@idY R?d8AvJ?#N,{"Csع 5Y)\rc)Ҧ6e)j=EY(e8H02xendstream endobj 1214 0 obj << /Filter /FlateDecode /Length 5033 >> stream x[Yo$7~oCa^w0óbmk6UU*]^̏8H&*IY@'Y</"M- ]i/oW~WoJgVO !E\8ԭջaO߼ysތ[޼YhX[[ùQiꦱZoovov u͛q{^꿁%*;V?/oYM6㙼Yk8O?t j6D|+k'm7Ch𿯆n,~=-ouceՅFWyc m[p$Jo6W݈[u)|j]+ V݁xk>4 ]Ut:/'N3[1>PT] Z͢ls'eFD18j|CMlqyxv,~Ŋ78T8匫Vy͖p 7Tjx!c%=p*! `EJ=@1헴(yG"Lf<*ö62Vq hF*5|R8 ,7)z"<9\705?ѣ=QD@~$Ǹv5I (epk9(u zUY p.ڏAG) `8^(x^+٭dSƾrQp W%%C~AC'Bg+(u%:%@(soLe=eelWtI z5!46>?䛑><&Sj$bV(6:>TTE=f(H z@E)wYBLF ADDikUȨi(@0 Mi sDѲ8]{#'"8 \SMhރ0 :gW惧k(0or`  녴/bװ4>߾z~~&H;ZRgg0*qC4!b:nyyKy#EfTr _uaeȓ˙-汎?)qLi+LzYkq,$Cf@)03U:_l8 (eۃ.KQ&!Z*]pND;#.]0kŔ2q0K+"CW}͖3S"x\M5`KŸK6a3f;*$ٶ-xaF8}u(4?U1x@no6_‹mj j|{9S}64Z|7e MO߮~SŒS$1$-8A>&pERޭ'; RK4Rb9͙g w93 ₛۇ}_ͷ" P]LI||2"dw\B DVl$xئ,%WojUE*!^vMRE}ĸk匟r7,p^ܛkڅUKImᙁ$ݒR.\-5[/fOU |JOy`Zx*N~,ZJFÂXW49:Rt5c)O9锟oSjm@4ơrb LᇶCՅO~?KI :L hSCıh6X͠J2[A9nǷPI+|J$QZ0yGD/)5%/1*M KԆe=%/?8>1s+!;mú rl]#$)Ex]k͗C6 W;6&&_0*TNϢe$Ha,1>(?(Y~f".meSNHES P.uC3i˜~7x@Xq$`ot0? :j"$:ݜ(i-d(>@7Lcp̘9j\mdY6! adc>hMrl0M;|/4y.gL@A/1|/]MyVxpO|P桨̓&6K{7TLC|E?U wi‡r&YɅyW@ν*Sr. =Zy>!å<0)AKrqBz0RX 7GfÑ=l>uIL C;0%Ʋ~{`;0m!dz90 .(:~e+qĂY,oTcj%E >v0:n2H"|I3t/z5칸 S B:'ƫ j*ZYi^mk-dI!1Td1n04JBx1;\`q!eTO[g+OCSEn{.u\F  ȀB@N~لZ;`wG,Q>3b hp [NKz3Pf揞BGXMVGn.{* K.`Qw'w/ƹ -էq|?> stream xZ[s䶱~oDpRE܁-ۧkԩZo~fF9 eZ5.$0\k ntݘM vgOg.n볫腫zq}{e2-hjzwV},KҦv6fRԦwշ_KV7 o~yNK˫.XvK36JՍ3|נZ*RWIYΦU];&itnHF7 ՘v(&duEx3 /ҬovWېBqf*XM)0njo:hiOICLIg.`>r[0N`n H+U=d]v {ܪ֍J!~}dѶ5uvɅI^>.=i7WcDmEqbsp3&7S)߳߿DirXBυ(M$H`=-}nz&6"_v$#qU$hCܐKKY%yqq]R-3ZZ a~y=U)rko|ZҺ}$}פ((gH ^90l$v>r_ MњUtp}T-lw "V]/&ak!߳x&&de}r>*QyړD DaS!!.0Ľ/$Zx q09>>̯)"1Kٮł} Ԧ\St0IhUeGк oSJI& =_^Dǔ!7qr$ѻ5f}l%"X @ )=z;(^U-='4` v=y3+Cޞ)jLH?l/3-(/Dn Ml~(Ƈ|U1H v1vq m#p ˀ '{L>pzːOXLc Ncov42 C 6ȭC2wdL۰Pt}8#6}%E{3PdrcOk U)$Pahẏ$Ǹ 񚖢!41iB/t|"g(8UWB:>Y#1)Ub͉ 6MvhDESK>n.dD)zc1*h)d\ G=n2yzp?~LJS!7du*Jya(pP> b7hNl3(#Ь?8i:w5f:HV#x2bhNȕh$Y0?D^M!|,If${hcucC:KA5*@ȚfYӌh,*`(Jq ~8W`ͣrqDsg 3 FLy=+03R`=4A i3;q؁Rz+\Tat0G,.&I.Dv! ؇~خ|V2nި>6q:} @#|8  QA"CuP8vEDT6~$gffrGH171TCrJ0sJp>߳0T'55u1 $x~ SivPi8ԃqhS׀*O Հp,%oXsR.|J= GTk+bf#?)a`)}@ KgJ rhG}EBߨSdvleVa^\wp!`=fҺ`?_tV %C1lqj 4'#|ZRGCы转h5秔dԔL~OSRPv$aTm6br7^q, ?+A!6MQGRip lu~}1o|j^&I0&e6f2hPs]=p 聕ľ!ƭ~PQf-G9|ɨD͔C DiIj_-*{Ɖb # 2L(EPOS^bOGj*cU-Jk겣NI0F:Һl8#gޕ.cqZ"~TjN%~5!ZPvI*jXg=cSKuoG8tk^֓{^f}I;K0Bz^w l(Ny¿AᡟN&)Tx X` ԑP8w94gmbƫ~l?_T#38痗HFH/vϛ Nb67_:3GiH Ԏg`ZpfX`{=iQ8A8 \,q\ 6g4o6c0 h|ȚWIAPgiْ0F'UB }Q PSDlΦ?';;}4~j0J|={X6SwxR"1戀zjvh ^k;*2,G+KƾYY'V:0R)p·H}yTSl3񥟳$mqG5=/ՕnBC)@t _% vnԧn~7fQ jH .:tBZKӽ\xo+(/Ě~&jN?s ě](#p\`v~7S׶Koڞȅ-wv1l|WHMwes=Pj8)HXjLb*gezu Vd0f Væ]wj>rj,g98wy=+I-ٗ65sf_gPZG,@ŃEh15(sNeZ&7l(b7L*]~R Ќdձw"y[#a)+\s5;tkLύozv&N$EB|JP)ȳ)=S0r24 eS:~g2f2ڊgo soufץh]=%hsy=Wx(`k[ub?ka>v~z[MZG"n69HM#zcOVlIӑ"ݍvXoƻԫ <`yh0Ep77U=lbaAx]}{5=xuQM0TZ1=`>ma endstream endobj 1216 0 obj << /Filter /FlateDecode /Length 4746 >> stream x;KoHzws w"{,v $c A@KŌDy(j܍|WVѲV3@dU}wESEWfqjכ)`vJݕq0s1nZY;Ǫաsڱ;eS75Jo`؅A4 xqB֍qyGńV-L4LV};tܤ0vK?/?\+;< ntid3vex"w]"OnEn? |hwÒۺidx \Q򚖸%|Iw~tNW&_~/R՛&Tm>xj`n,UmG4>Ј DqVTF{7# X^I5k`N`?Va+%~c;  mQ[?'/~+53$NTtC[Yd&mRJw McJ{TN{ptI9i3V]S8m)l5$Aî[5od͸ 7/A9y;UM.vNnI +O#.0Dcs Ak !?S7V;D>U-<ܤMD 18=Kucaз  ·22mv^vM; :Ute @J>k3 Zں<4q Wt@Vg-`q ^SK:ƵQ>~&w,Gk 6>xޮy`zhvt[gx# t),"A/psEQ6%Kp Am%!N~KZ@#xJ,dGNznԌxt^В1m~C*9N82 Qr),4fF0nZ2:D}GropC87,e7cv~O`Kʦ6&ؒg.21c% V`^3Ǭ!My32Jo\Ӈcwx 'dJ6%g5ͮnIȧ : dzItri. D שo.˼|*-G;,%ڹckݥ/IU!,>*qǐzrcbG*(&](88M/Gmd-{Lyb:+儘0?^3O&j1c EôNY/)I*4ϭũC[%\,SD l'6VA78?L[ȕ Rg;ư:Ht_4ID($D).}6q|($yzl8zne |8@=l۝}iRү*l}GX +gczc\o;f){)Է1Peb,!iu"dynbQ_05L^xe L+ؤW8Hmqr?w ҇ XtŇ>7$d:|GJq/qWm`LF|%9c'Ӫu!N͇&<]hdY Ԑ8 b'̯O)R +q\\ m NbױY @X۱̜@8ݸfnaN7t SLͦztRt*ߐҌqMҲ(D378lt1x&X>ޙU.S1kda_:A} qZ#ܕ4WP~il8sׂF k ioP~&iXFjj"MX-RKC]C@l͸Bg{ABBBoj'od99!)eO婄|9߅̦8a]=8蛄ZMrȴk6W?>P1^QTtpϸL 438L7:<MᇲVsYÌ)e(@8M47 q6zFpG ކ$A}{AJAv YJo %BT^ҭJc#^+v[|æ]tdPcj)G_3PFh:o=eόqv^0+vJPnShlF9] kS33?/Cas"bJ[vSA! n㗊 v-]i/s鄅Pe` zp>loo.;Um͌yK>45c8PaJAQ3`àB /pI;?U\}1ȊH}kjCwߍ("H~ҫ39m@Qe+|]:dN=TceM!qkeS޿"3"3Ps>iH{`t"QrVz1t#e9]ѝ!ZWdx=1'E+Nc.cG$# Kʬ ۗ\ WаR}TA3ǻCXOӟ6r0<4q[ޯM޴[I> stream x[Ks7AGjo 9e_U!r*8l3%K^{ Ӎ@<Ú"4 ohW]*~ş~Zj2buusAcFx!VV~uu K΅nZξ?q})->r7lmjji7{Xխ|0aQ im\oag=x)Ah]_N8StM^RoIZcA ,u9v$PPK@ĵ }^JaP{ͺwKa`VT↍w80vDZڰc)_^Lͤ8|$O{R!>"?fݰ ogF26\H2!DTʇ 5s67vk58}sdoĂu7c5 _Y7ٴ-W :րKđVsp#kvhDaUz5+2"i}gve;oe nm `<¡J4{jUMچg W?up>z5fy ,vz6H +\uleR 6?v}thxS x 7Vʮ3vcZ ^[j؎ lM@I?뀒4y/a#!Lᄐj%m%leQs04 swi0a9m+^'4kBtvw T=3M+3t´(@Dj79P!5啱,P ❬G1)A SNaޱᴟ(fF^ti.esVU4{xr&I+Q'50n%19 5c.LW S sa+(? rR$>Y"Z@mIj۝}qXwgN! 8Vs@%LނYZޥ8N~Fn\&v'Z0ꌽ} A{ 3>ѥ0B4A.hnefůpk.x8tg0hjE~/Z,t exX -D m4n[) 逋&at$JUʪ#E{>CĠ\ȶ cBE$Tz![[?3kaHX@RECXDa|[PZnuL9RXY)JRڠc9iގ*^tK/t!sO 8 JIoY@x:R2j(a?'薨9VɆr|-4(7QtL=>9;5a :O$%Ⱈ)=DJ+$DMa~i| ĩlUpe]IE/'k27ӫ4tc BUyYoME(v$id dYg7Z}C0< qKK8T}% # g7LaQEXpƒ ^wCHZ|8Kl&mUuE0j1ei(\laHVE䢱V~IJ#!q0~> ׃LP`iTL_LV֤*g`&<` a׭g- jcA=2 8K<HuևO *ZL{wv\KПm%!F\ܹ|ɟ~׽%k>B*m3t౿SD.iu{=(0Էii{6oR3aHD$m6&؋ڴN?4>'pHh1am.AvV-pʬF>5LM&MTK*:,42&ҙsqLW\2-֖KшQFc?c/eEKk?@$}#Bg@يL))n%:±0сnC?eD`%Rw$sej櫭MDz:ЉCPd@, iy#'z6" {Dj %ٲj.N(̚"$ԔE׏MYJa?0/;t9x[ZXys+P|vňnwd{y>l$xq 7Ƹ0JƷ5tBZGDxA;Zp,؝#Lu|C>ơ}* pHs 6Kj)i Tm*bw]G\@Tn%ThK ƭ]7)\&hu-UP7%*lI$p@%(J+ϖ}j t]>}>I!=䣬-F{7_}m ~>>@z#{ߔZfv24@ek@WJ 7.9U͚A- x^xevO:[:XRsr ՜|p_X%"Yg.d2'`9gĭ:) S3{kxOAocALA(E#c9Ig #x3LIT#6E>;ߟʇ T.)G< 2NBAN(mVc:ď]&X[;UAPFx: {9Dg#^PIؑJF>o9Gi8o3ukV+Q ei[ 2U373 .hwڤ x~jIpe}SL6BiLP͠&FUūަ$2M}Hܝrnȥ6/v}ojkE'?V|NIH7 nR33Dx.endstream endobj 1218 0 obj << /Filter /FlateDecode /Length 3749 >> stream x[Yo~oB00'}@I A`2 c]A.mg>f&q ζ/.0,M%BR)77EyewsU4}˲MidYu֓ڴָ|_ L4dŏ|*l&w'l8?4PAJVJ7gGDAZx%":>Q0H" ;l`]v7tQ[ȧu< N濃 rxM̥}}VHGJ }2ߦ"/DϧN~k%)#錱vJ՞WO$)=w|Yy`.ƬJ ŤhT'4Iκ~wnwG|1n)ovoD4MU@AgքxȔ8 xR&^ׅ^=\($ 4\, e pLXcS% EE7v9Nj Y} }-Y$T=AeOqU7`IjPEq'_ؗ0sJ :LlV7]mUL?hvy^# ]Jزp>m7؟+l/3[.V"JJТitnwZ@P` <+]najycޛ:"&Tp 2M.hU>ж<ښԸ4>#rݼyݡН `,:hjhncwm_5A-[I*fLCRDɣOjϹٖĢ>{/WQ Ѱju~LV!GH~b9 ޲v½K<8fU)&+4[{`nC0N}ʹ= Kĉ.uFHmvƜ4x^{zZWOS!iNһR3'i H _+Ybj LFa`ih`XiX `y(w&RsQCnI߫qLմi~l leX I ֣Kf.17Po2mr80 'P / z?@zǯrCd[PltX yc-):Np@](t4/KAn(If!8RRG楬GhDC Ğ 9lpu7x ͥ:8K~hM; MC*cWG^B|dEUy: =KػBϣHQ`\0JDRє\3Hd88ST!>eC|VfPLuDK{[U[C"O9@iU.8;G*c$I%`Bz Xg]֢-bbq_=jG/ʑ UJOiNjByP|8N;/8!1=G&Ot(M$ )]уUk|\P"S~y& N`>'Bö_UKE9B3wU5pM%K?i!)QoLxoM'жs+8My=4MElq8^)ďȳdM:ApɣJ0>ܬ9Rfڼ9ͳt{Mӭ2 26OWc^Pʉ8r nɑK;Ҁ7$|`+pnEd?&СB,!5A+ dn >c\6!X6X,d}~ z?!8Q*<61$ehGNrOXubxpÔQH?^q5ʴU\݌GSDaw8c xeoiO }g͆U@M~4^foh4 ~t+jL$=~L4jY-!pGBk½Q|lzБjV%PK:/SS0׼@i|D>e|{8J.&#kk}qR!) uR9Wߜ$yn$߮s旫e X|pVCUyX)sQ4r@HH.y(u<_̿\j mJj_mYh9#*i|T4,qRӂf6|U@i܌*¢[BJI|@D (B"ʔ ЩF{%X)/8rf<AFˌ/elU)iE﷬#J<#좤0AV }h8X ڛH5>r;6j4;:5)!T=agH[⛒<W*)]-̰|P*bo\4{E(<<ut ΧdZ M|2$VB',yj{ѭv@L4뮯8ܷy $)8cMG݆&fDHyt2gx1F:RAg<㸺mK11,)ᐄ7T=o5V8|Y逢8uzo[6W1<=͚dpΗ$x+._aS$ 1[Z{iLoV]/-ƺAc[|bۖ>r_٧y-Qx&+dW)K.ʻcȭ * ҭQ7Ã#+nh@xbeT"vƒ5iT~2$)IRJc5UX Ï}I^Nr ?Д*#+ǟ=A[c<#LSLѕrWFCcRSˤNV6~=8s!I.M8:aT7)޴fg>ιP)Taendstream endobj 1219 0 obj << /Filter /FlateDecode /Length 5989 >> stream x<Ɏ7wfc4H@\Ks4@ 5ZʥY%[ X H>=ϫ t7?uݭͿ]n[oY]߿m'yVz)WVu~u{c3?ޭt߷~-mu6Z{+}] 癶>Wc{ߌ4rv}sXK?ЯN'Zv; m<k0~nO no(7'06tY '+i~i B0}W| zOx p>\a? y/$y\Mj)NruM\WfFߖnWndK "jQ:)ʄ%rb$)Ԋdۢ47cŒ2풇i@'e HO/KL)@ʸ͑>OaϑW|F)"XXۥtj 쳏3 iL8t`M1%QM;_^ⱁKQDX$N $o w@E<_Z֢ifxiX,.\H=W I.5 2}P#1~ *l  ^Q0Tn%k($XG^8 GWӕR]!1o 'j{'t}2Mh홾?SDly,y}WT&#js& Ӟ_j<5j~T IaA챸z/9.8y$C+y^G qy|,7J\H>ȀdHWxxerl?RVqIjƔ$=ؔα (3eL)ڴEnmALrN. \!kȉoEmJx>$:32W`nV_^3w$;n[&TPLQS߀?=>^[(zB (:"v@GH*Hpel~q2gEMi-*o3v4U{7>~ u noYuX՘=%Pw ;W]4Y02OQ ~ѕM"W]5ySn(K$W?gm߳ox)L]`DXV4j9@{{q@()aj/`v[FXT﹕ 'M˿ [^c16X9L@n:Kioxisi.](6.U05|Q!&[N)I%3ږxK]r Њ?ㇻzfV8G,$zu}zAFo=ډ,to^ )@wV`ḿi0 Mx+%xsE3k)=#E>@a@Y`c6v!֟a=8uG`6 .^6. (S-QJг v@L:vʅm٤hhROGb ɾ P5]7ZK@3"/*,:t1)yr/Rfǰs9*cMA% l L@;}@.|)g9COప#VI+N%C5:=X-$~5$=*22a?l? @.hX^g"ypL8P`lܖ+z/K9l-T+Sa?9Ih^) :-_8jѱq(4 ӝqf{1/IiG%x5/l^/r_s̎CL6az)ۥMd.#r:tjсd'\UؓOlS]>CdQ5!Z>:UV}ГJTCmb);(ZXD[C58hh[Ta&EnI邧ѳ|ߘ:LG,уMTƒss藁\deHlu "[ƛS irF[ڇSdI`@l7rwʵT7t425^kZ>dŏjsuI7t1ʿnc[gO9賈* 'm.^TQ{Ψip=@ocae""L"/"ڕ|Hf|@=:QĝiNƝkUcL$[!(ǕU?'A*0&O(PߒkίO;?VAtF嫽s>E;@UXvB+ jcAzU;+9[ e]7uꣂ4BITxJ겒d]3 qy"^= ؘn;ժbJ! ]olmwRd+",Vd*_V32Ij[_ ,#sXzݖ*㐢B2 ⮊P>ƘlW(FDݕI_ΌgjÒc)h%Gfa 9 /pc|9[pdmh*XZ}xnBmgpo'~{_+ϼ1Bwb$#P;n)tDG0Y͑rF䘅|ޡX(N J28w_;z.К] L^HAГHKZ;H; vx'7~<y. g Zj񔸑E%1F"*VF+ ~܍Ie`H8<U 8ا9)+.4mEIઐ5 [C&\~ $0 "(^vBTƥ%aŊR4>jb}ZVˆO ČM6+R0_2  pmyf_e!sL|9uMh"پ.`,KlaYRb0PKIq9߻W1ǡ ~>1naDS ۛrz|VJ1WKkPX\\Qs{ߏ ?a|__?b&{UPZM,.E}K-[jm|ڦzm_.x˺S5E}m%ׇME粔 o`1OJaqPKyV=.Յd>ſ,uX5)bUУٰr#.D侯CÇ'cToP#K*o$0IplYǥXd( /|Ov~>6U)P'82$7cUqj"?ҀP:y%/7cru%yaz}*)TD2EO3ev.x%ô,ekc 3uD7xegwLa ] 6aI32D VF̓WR5 ha(^.qABȶEn-hb\]/)sm&8g Qnm[lET\/ZXzU*'(nȇV瞯3a4W  2LV7}kL}%V ڦKx on}Z `%1\a. Hpǘ8R؀۰@KFZSWxNW0D@uOj::֪ÿš_NoXn?%y߲xsQmjEZ÷ZW AOBn 0J2n0 r=߇AxOȞllBR;-_їKҏ1EEOӎck4ꪷſ1u4ۛ݌endstream endobj 1220 0 obj << /Filter /FlateDecode /Length 4034 >> stream x[Ks#\ SGR>$ʩ*uu(hZqNwCR=,gh4yj>a/b8n7?__;Մk-&Wa8_ /JV;'כg9fjٯsiUfY&rziδvvo3y6v ky/Uf~+q&Fb9;V6HX c"9RpK[SYmSaї^1_gQumu# ÅQz2YsXqͫͩoOC=+)I,뗩 JFK=$WĝB,%_lǻą#%Y(@UZ="xpj6%#iPf RhP Ta*靎*>Ha׬,M춤M=H TgVzGs|?-[зYSS9 gsNVǮf" 3U i&Ƙ8 {R`sQU sk]K[P#aanMӚ>/Hn F1Lf2ը1F)Vv5ʿٌL–$ӵ0-{ 2$PɈA`nuYPֶ'0C|@MWu)aQSNX0N{@VVBlH۹ҪY`Sz6Ln2!3ƶ7zA wj]#n,zEbGm^p9 0sL&u]ܬ֫jQϗl̹:5%ChqJ$ CAKwdq4|X'bMâ 7*lofzffvf~%ST``oZ@}~\ۑcL {[0Z|.l/f3hډfplJM5H,a]2ua(ẑYdXm..ÔnݢnHk.p50x%^3!D p| =YNqc'=A,pFV7eZBswSS ԬnHNgCôxj"Ӿ\hYࡣК-Ķ([~0JiWw q$R<T>wXqΘ CIr ziV6D(p;FA ~R@՟)ҳL1 q9!+6>iM!!H.E5HjV(4f J‚48בݦ8(q@r!pn$NZ(.8czqAegC{Ga$w+8t”E4Ѥco"[$E ROsGwɈ0?iw~e 1 {/)>r,asbߌ>TWѷ@t~N96KIwRxI `&nrL +KOR! E<O LΎ>b5L> ՇQWqlRRKx_4H[u.|jy ImW,+d}\R ߗ.`]DU)/n](5PH9JƴQ{VDZ\V&G o\ adddճE0{ IeBv_Eqq<,bLXlR hI:M:AU8hUBER/ ڦ䅗SoE8P:Ĵ' 똩- j}N|R+kݭ ? 02cI_Qcx1a *ỴW_Ӯ T_{ G*EŻsђ-2ǔ$U"@% aT @D"TRWܡ˔G@|Y;BbrB= J\&&Q]Mǟ= a:CJT_b9Z9zS8v6.H%\WDSVt*XVtV:{Nglnޖ]Zw5%iL KIH{5Q*ꔏb 0@JC<e0;%TD!EZU!/|RهS"}$N|[QOTzg[oY:WِZ\^9?%IC6$X3j:ɗG}(?uy=PM5 !6X[ڄ牡OBL(sV!C<NVاxe l|В ‡eg_>>t ېb!t$R⌐]Mr2VI3t "Xd8mB2BIκO?)&9;w)`J0U7}?"IDӫ<0<' !P@"!4ID0q}VN?r|s/w;6vJC(: 6|Aq -FvԔb7)39AϺB!kPxNynٶt2(\"}l,~ jt1 ʝ$xDZr7B v3l˕jD|%PNTعht Χa1`RΔ@d;Rx場Jn Zŀijr2`ȀTq: Z0-W`:}iv-`DG\4U4ݱi>hb^jRON"3&! ,zxfQ$ _l<ЄIy(E!IQ)iV˒1N XNgTԲFsU FM\ZIjc0IaDj]'TeCl2Db\6R Hul$RQ2,cOw^Eh~ܭ9~YyWeTCj}1p3oP[QuCaILŰ%DN|F+wbm3"q8N} nnp F˜Xj`B@G&3](Ƞ+Բs~ ƵSņCHYwXI!.AxHS龣|W b11*Ulg#d\Dp#X o (ЯNa$zx+#=6+!,*SleWS[C/ƠQ&E5QzB hMtFi Yn\vdUh5F8MQc.t q~ޛ ;tp%D{19vgc:tz 8VUmg%1sF1@}d!tix;b7s&.0QT(ȟ5DoNNV*ٿX<(V9idIHfeXn~fhC4p%(]˶ln ^I&BN 2TVX+!@VߵΛ>nJt5޾)/ڮl> stream x;˒ȑo"xF^C݃dwc J#3$APWu-u/tQ21qxaLֹk;57BV٦xVck;93ly]z^ިVfo{T\k3OEMlS15'씅+ҩyy0 o.&<(g᜶k[O@%Z*7 [$n&0< oB<˛։3?g7#fg@&XA@\п!zÛ|H38$7ڴaN۷oF< -"LoK:`L7|!#sྲྀsެqlV|ڎwjYk&?| is:Ûq$m8\u6C=:h<+k=q?={:7[4á_wwIcP(ZyPt3Dt8k>:qi\ɋv1L'naWR^ d&pyZ%Aqao6 6܊=sI\# WA9m6~k:?,-)Hk%6&t-2 xX"WAS7wR5-q}ޟ@%r䚍"dZiv6#smsDs馟s`LLa 1IՌʠҚU@N6Y1l{.+ `feN]еF[ՙ6c;_HAdPv<_kð6p 6uպ:%Nݘadi_"7T߇3$7܆c|%| ]dH܀"s](V)J^Rδܘ)0fi+^s=B倢gW%&#xH#`LS>|͍ 1O[@BIGƵW__t LHEٴ??$Hc;`ukN+|v& i?=YXOv-_% \i;o`-+g!\myeכ@U`WoN1] Nc5K0ϛ)&}Q!B8n*ǵ%Pxk!Cq6Z!ɥ0# ٜ2]\隊?R)c *9rA#{L5muؽMJY.pDѝ&6qx|3$:@M/h!]JDx(b R9 =A1#Ip>&Kc%n.2/|lOHwg&ߒf/$ad"`XA͔--,^FL~0/[]S<ǫ `55j5Ih _M̈́`I**;֪_qg1=b 3Q^C&t8D"0ۛ7S%sBW>7caxQ&/=?Yt~~Dߤɿ`S@p0z};Ap7U fJwNBiI 2FgU2c_Hk[g7.sd%sT@>!ק<+h9s>:J}q<75Mp#a>E4eY ^0pe7 "mLQUނ?}ӎ1 %75Pfqb]raV(OzAū5/{AE/NOXײE%\ "60%A2j/7U\\o+T1߸(e161=[g|?'nMſCxGR= d@cXbܘw]6&q3D'ՠf=p 0>M0G2PA8wðC>XȪ).8qj}C_e:;oT4noRvRws{ ?BlGk>}bBVV]uizU$Z]8aB}0<Sr9=%V3v XX k#);Ʒ,(VX;on5Au:oG]+ ؂a9V7[*EϠ2 h&Mv~K%^hl/jRwӿ/xO$] цO @|.\=۠TL)26/+Pfs8RblVJGW,\W>ljY~NLB#]Qu^C|Bo_}SKC6M&vŽ4W #:(e|IďPf@LFd IjSaeĜZnAO"9!7]>D+΃<&Wq0L퓟)Zm*k&l1%8qTXi1 lo@v ؟8LHU+UP 3,YvDH<YO/3LDx~~'VM/*ܦòو;p1S=Mj G/J0A:Oa\O BbT]cZ_k1CHЏ2| P]JQlar2uHTn S-a.o,ea YWw(kmd3REнw7n7FU|zZ VZq@_dlԬ{t/#I6W+'Q0Al'?1~Lq_ 7Ŕ/\S0УcS"TĞOf@lKS RכCBțY7q A4E7VJ82MړB@ bG (_k2!<  8/A|0ڗ=xDmq6+~r!+)Uh*w,9E2#Yt8m.c`7ɥKh؋#/EvƱd YWy *P`~C ?8<,s,h&%d5@}Hפ.(-*1m91\Yu? KBx4tS,^9Wk7MF7Cg2(W>rJE> stream x[Ko#7Lݶ:7]$ArHvrd[^+nղ-9s.V},V}Er~454/oWNfōi'7W6aLMm\o_fqz|Ŕ;6oWU_^V鬩e-Zlt&V}8~N}giSҐ˪k5 4ZNEm/]h9+~ɬq۾٧fy8춫10ŝM8{# ~rm\U|!`^qFW7CBLT)74.~Sٷrvۢÿ́GS6CTq~E4t}./ $lgSV7|HU[ h -4X44 d5fP>}K c!͹WzyY7}fZ1aHi 6:69MäwX.}#TppǓF1!aU;0m}'O(z a}\3U!MLD=II6C.:OySe2I9R^ 9/kc(*zګH>?q57?>aE̱]*]5LFS.1w8aV-}(},2Jee *e me::ܬKc#'z"sHƓ=ʻQ EKъp[(EF<^;t=+ج&ǨZp@)D-4ڠ]ufpԳFd aTzfGZ7ABxQS{~4K=dK=CC015X%Aϻ'njӰrPFh,w̯}'B,iSu<CQF_hDg8S(T_{2 ԦddQE`9*·zyQjOGK#Ԟ# a5ְ*'}hzmr]}C'~@{3 S\-y{|Ă^&<:V(r KY( laa%JYv0X՜e(crA8F|k6sE F#U)e)%b1X4cªCRɏa/NꚐW9+#0 "Kʸ]2Xj;ۑlW;(0ˬ,4<@qЖc:c #ܓH/R2ջ8WѥjMߓ% %TF3\"2$8 %djy24#t!aԌfD2P$Iڼϔeؓьdx>=lG3tl2C;0L#t!='0JHK =ߑьћ xd(H{-P#$Ku~)/SM(Eke\>|_)0]qY_㪇,QP"u p3(~#&,H߽eit1g+7%\-Sh0LJ 딱M)f$|jmj$s+ p[{n=N Ƞ_|B6g_VnK[͒Fk:s:moVv%yQ\舏| 90H~m- -i]{~ܤ͝n1z>cނ$yT1)A3!dzĥX3\U?L#lhQ|MeF늨1SwJ=+,ݢNܬEO `NXD>}TQ.No~jfQ)Tmѯ5N~TS,3%=~)FQlH t4%-`°P[/gQ 4ery0qmVfX!7՛/dŤ6m1+|u\GiiX$wŲY/Xnr <_~&*GBju$m#>%٫|XetvcS?ڞv|= / c;NŷۇCo Y-%xD6ΟV9{ަDZB}D~e!@O\LFW(?u8B? 'YR.-{2t9A:<{mtwp+,@ovƼ?e,xc{ \(=EZpa sIE^ٔvX-h Í4m]fu͡2EZ(;_W_G ^u^S%d ͘=( m5_)bRee\ ߙЉG+Ոnh/X_eҍdt!t>~cDEn^to&k}as_ԒS|JŷGv>$$M*:L?Pf$*1>Iptm K7]a@ sendstream endobj 1223 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 6783 >> stream xY \Ƒ3Rd̾-M+=RyJRp!EMd_daf}WvvAPDPP\JmE٩{yro{t?~ߞg81Ù-QiO,.+*%1l1 ȅc_)?&#=(^lNf&'d/\`ѼyUG$''Gq~t1y01xNzZpmvo ٲ5x͖sX?MQԓ+W C2Z+Z^bnƼMB$nMږ'.ZdS̝8EͤB0aj35JmP\%Qj;ZEEPyNyj> @@-POPku"j=@=Im6Qidj HGT5OERwSKHmTeΑ1|ǽ>v5vz}ŜwsV]k1h*9aP䳑L"W] /hCK 2 Q&rx.Tx4R&LMAm+V7Ty}< @ηp%y83\G!SBhP8w3Ғ:g:ՉB?6Bs HqV~jNL&rf,T\<dM>>SՠUr[9[g?|aG^G3#L!>DNM۶cNco#ni}x!O4 cIA.(wp<9o0`AdE̋+#l.XxF ڎ8(8yZ݁Wt4|z}4=gA2ͣ/y#= }볡CH3GULg1oh"E $mV-y-:zs{ͶE\f/͌ fM`2chW@ܒ%60 sޏhs~Yj|YEf$NjƓx)`YMNn.J}"B+ue2VW|@*&S@?@z9dR YZ_ZZs\adjP^ !U(Ph XZQHۡ~C<$kB2k^my':<>$iT1GrO?,l9ϜQr=@vI=䳹%$HKQJHcZQb JY;j]А榋d鑱ۤ~|kH_}Ea3iob=.jrG[5FQ]D I _V?m"ߨIA`rw<h!ԕ٠Ⱦzn]5E>kGs%`:"ϔgьŋ@}j|XDq O(ب5V?Ob Jon.e{~p)}Zec  |* Fnº;TC}::Mp= Z6wf}:\^W @Nzly1 h(h5X$p)Jũ4Yչ+*䤧zO>ګ-%PI&y#'9Ǩe?.6c\(e3B2VvzqS͵LExڡ[>7Bpf *n7`iLB PMLh4EM3,.MBӇ:IE .\Mg;|l'tiB%f'"G`  'MIiYYY6Uff<.\{VÛZ;kD ΢gz3|>sE|ʤHB"vH2 h7t? lH0=ܖm uNHhGwxZ01o~3Rl6{7J]{?8@(>ss;W.13Eۓ_:"P:AX=DIS je>pOxt[+O B=~KzZdiaY8G:h|͇SOח>o2~$+z&+,#߅dz\;xaֵ=PkrOmzls8 vU= J/uvQ᜺zix|5#|2"6 hW겷WuP&>w,fwڶ9rFtn=GԬ26|wຩ2+ftgQ }ӡ ~+i;ꠔ϶fff'9~rBV7'ѴpиWh_bvr"s~jzezȜPrܨk` z< YƣGI4iLvքxUM;Z;yٿtTr"=TQ2 #1sɏMu:ڬ@qhD0L1Gs<I#wPr& Gys M3@ujWEZ>`5R@)bB@Uomq1/_7] 6뵸w2ijW$=أnr} - By 5y@gvjkq޶n"4ݺDkj='"7܉sήs;ڙ wdc}<hA:ׯ@Xϛrߐ[utNsZ[s $O ibwnAn1L`5!1ƘRfurxЅ_8h!2ApwAn"l/j܈] |ؑZ,5A PmF4 =t; URP wKjꬌ'.nma힦v G~D9(+eximV@)wNofJ;vYEf4Z'8Ldc˯ cE.N^oƅ^aC̵|EʦdڡF_wAhԆOi_ .I Մ2bbPE:N m¡rU@<HNV+gqPd[\>wJm)0J@@F.15hP>9=MHǻteopvCfxtz \whLl%_,3rHby]h7d&_^JT?|w; eSpr\(Yr1was@/!SI۟u 0Z+^/^y'^c0]=7ߟdub RJUCѪl"p{9ߦR-55BTkgM5P g@3^oן'3W xty_C%T(ޑmd++ۭ@عr 8>4S"=G^ekmF@_RmA}7w+SE7~ޡ4cC @q;{=l2^W7/U(4e,ndT[wz7dQYvSKT 9he Iٙђ镚o̱8Ҽ?|׬+5"F/ qЇb~DP"fן7F߃9߸힗UQjNH \%xzG>ok, &Y":I}:^ Zʢ Fq[ &ihʏb 7P9o`2AXj9/Gwrp秦8-/aI\VIj{Dˇzpw̗oFk f2w̐'&9IӐd6X@j@T쁧 %يr N}pZ{h\r(` xbBImbuh-ȏPnlrl$<ɆQ/Ta"77pnxU@ey!9Q _ 7Z.Dصk!bx{#d$@o}vFV^,{֋׏Em*5+h :*5NI*4^Фcexs˜=¨QCʞeh$no8 -(˫׃V'3j>qNo̗Rֳm߶7ןX'̞$2RO5L*쒂4gj?:_r6OP6*uOʦ'" YvJ2YY]h7GL+x렕hsԷK+[TD53L(z!h8֙m*#ODVw9q<tPG5ԏ%U4UP${1 oaF&A>ڭnqPA6٬8J)DžD@RmC BةؼxodzD<#Ud'Azls d f}^}$)<,L]ePj.OuI9!x#(ۇ\iryd M|I @tęxe^{ӓR2*[{t&0,1%9%LmEZ>/DpNԛ+^43 }NƤhwO %o΢e_@35No{Mq_)dvF5hUAWd>#H/*j3c]@+2G&87bzjf3CM hY8D%%!McR ץm a+"vm.!l;"pBnoU_lrr<=lhVHD-4k.44Ơu^ F$ݟC7 _W Vҥ<+x_TD Ǵu{ fti踆LL3L9 x0ό—d@a/hNۧGl49^@m\6 C 墬c̄yuUla awܑDkR*=n4$V~c殱۶8&NH'?։;mW=noJendstream endobj 1224 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 2720 >> stream x TWi@ܺbND4FH-;B@MB@vtۀ6"7\O1ND5Wd 8&39gNSwW05!d{|l`I1cor6n 0;20# !XLJPd5M"ƈR##Ο7a\hblg.08:^-:60.vr~3vF|mPhD`Lm|g&[/]6x̴zAe\|ȘXy^%3fnM™&=b2!!hBJL!`3œx x) I& SII͢0q?&4f!fͣ*jl#'2*td2B,=\#AB$cL{FmT8O@ Rfn !E*\2qF4ȿ$ƒVq5$Ш>R+W(qⶡ0LZ~PY;%ꩠz/[** rrFM_К'A}>K/A_XҤKfHkZByA lݪUB ɲ(P55:<dJ[Fff7D%@_[?[UwCC;:};?TV+ J̪)+3x&NO Pߤi6sR}s0L.C&^+gΎ9%Lal/s@$cKڀڂeO`1IߕKBⲛmVOmM[yrMc# {7_$w)^~.wdE]SC3${Yz:Ku fO~3+oVnJ'.I">X5.zUX NEK?^tI eC-C;ߨw*q`H;p ҁ:4]h )u3<`)s c\>r I{Uy;Iv}SM=H#5ZMq; ]3 mg.߻V=yd=n[O9/,M*ӕk! (F< Ck$ Oeo11"*F~$P?^(bw=]'R(Q)c|Qu=2t @vw9_T _yZOyB~s(ݎ=zݞ>Hg*ہ>dU_%RUo+TBZlJQLZxڪ8S|:m[h?2ϨF.z;Jӹ-<d2xp4R& ˘q7{wpU1\VX.&WX%F*LVdruCWޏoNֱrI߃y7-W&}hCLl HgF[-6h⧷+U;2ڌPAJY"|K|r~9#rdLܪ-[\Ĕt \#`-M ӆ7hme~4E;9YV]nZkؽ; V f!SDxm|-d [)#VR\ JG A:[YQR|*Bblc(,7!8Xp=V:Ps w]lh!k {CArV-4%(GIܜm' Ov&2=}ञ&qkn^} LOZꤴ؜5A )P;w"g90#/ĜM>ᆎ68~ME}>w1:!V7?s؀A>rtdH> stream x]=n0 FwB70%.ɒEL" 3%CGY">jO%մu _\> stream xYXڞeugbʢf+jXb%Ʈ1V, }KgoYXz 6{-&&xɍcƨ7x{x`g|+o9HnVD"^l+]wyX~9B(YoIwmݡzu;9S~T"YC5W=~Ǝ_o 3~6/UN/{-g\Ba?Rk^f~ͪV_5+Vaa}sA E!.[ö- ߾c;Wz\gq0q 6rAc2cf*3Y c>a3+UɬaF1k:Ƒìg2y8f#3,`&0 E$f1.,e1ṠL/ӏfly2 Ɗȼ`f0>LoIfM<ӍHzH.Z9Yΐ~m^'ub%#-7Q{ֻooO<;oS)#x|O+g.mU(&]6p z{Pܠ;oYUVЈ>΀͢c ٥cT&Q9c8&.1l 28Ov$n:!Ő}X!;CFԐ`T'Cg3jH ȰVDnlfvԐT ?dxtwOJF%g7a }T B%Gg^e@^{,ceXR"!U$(p$:캮 ̬f zC,?M\|,Gf(`\IMJ]vQ~̒< [䏛IAMKf@0yP .X"ߘW |8DA ; ؖLơc`S; K/ls1|=廵-%rMbPRv+ZnB{?Rh7e{!릍Px'%/Hd񲺔f2ңW(+ɷi ?.{6Ng?1񳘘ZqIRӊMRQr Q0kb(|=DkaQN*mÜi]*hҼ󵍕 h (srѭmpV̒sO:i:g1"Z,đqGVZ,ŅB2RWEJT+8ޱoѷ8d23 gZ3mZ|lA5m98zӝI:G" ,!Eo;v*/w<|4h:af:<絕aG V)|ֳ|Ozr !uz*K ' /b O搩J].fUCrrllŖE'l"tF|ۯ);_YZlS,?'ZMI$ F6]{O7^vs]w4(3ɥ,-Ku8 bRF޸,V_?gKN=g&zO~jPlCJu,#Yv 'W1_}A/ވYxdi48$]*qG%c9uKgMNj<&]\D%΅q=_ nf-,ޤsNv&sec{7r CC/goc\-jbf;ipc:,1Kzq¬ZJyux[^k p&&R0(H,Oȁ *'*@e-MJs#xY<M)wQ`υ C6_)4sٶȎe=[ :_B ݟ&A?(aeb%TݺrvZ O)Ek!ɩIYΪPf^T_˝ 3)K3E.}l>@j!7SF Q`YV £|dS ^kM,fݕ^nYng96^|J*MEU BC)*͸3gOUzhj> xr|_4_*] f~,<.FO߶ιxY8Z| -K _K0W՘t%SZ ET5AQjuيչ`ń 4 #_˟kx֙Z.n _FDSAY7hU|38jx;iH&(wFلQG){W'e {Dذ%,\/Nj=<^)_¼vlIq[>uQ'?c)y Kl]Ss^77KJ`+sImtD=IT(cڕJimlr N@'̓}>lPeA5g vnt;zT+6duTGJOx{Է{(.^('&h` MbFZBSi[:ޓDZL Sz؂Sx\k]Ԧa b!Sڲy՝hf5=Zn][m.kgM\c/nS=!-^w~̤/vg7?tH1)59,618ʪ¢J e@ OW=řTi9-n%Äv?}i* "p1}gʡ@LNUT3H6w9;R /[Ni65έyfͺsXF 7&8  67r6U(`'l-. En4`U@k:~AaE*> @y1x0`_b9pZ7|Jen.I\XNTanL -WH+> @]8PW,(3eW#2F ~3OF maB}yJ?RLrZ&$}RH8/)z2x31Ꮂ㠧WRc OHONhC>l̗ĪFg(|7x@8\#^ @*㪽YKaA7?x&z(I8^Nw hɠRU7vC1pwQRm 6M-hO0pqaRdX~;aIg t>ȃ8R|$7z-gTfѳ߫/I*\&W/'nŒMŧe=WKa.ⴚx}KVS9i.E=;q Ә3+< vX:/L{"_u)C.M/uR_DSGRv/kqC+!Nr1%,Cv,4bTCR&*XxFuz[?䇵 PKY'ퟻlYmyu#BH%3?-/2EI Xw*z@#ŮP X(TO1VC@ erSͨzaNh@Xd8P|w :;3SI9XTdrj$@pHx\F8׎>)-=`C{~]^p~H~HnH?C5jS.^Pe:[K=h Xbv_{cSu7X|`K=Gnj,S'*K$F+ɆP*2TU f# P oGEFh&j[}FZ.Ɖ7U_щKq5j[hWY"yio0Gq՛G/]r\wy~ &%OwdKKNU%61JI&o%3pjR@oW{-4T/OYK~Yuex0 tyWډ͟HP}S*ovgh?Viu~mkwAŅU\{&Q=ew^͋}<{h9lOϰO| PׄĭW&̈́p ;hϵxiÞ׉ fa_|nIm^%yb=/*RApdƔsfؐtu4hJYW6kZP{E)T\ç~-#L9|қsWǏ5Pit-.n O_RQbsZܰ|]"#>ށIqTE}8&y!JC繭]=̬OX 6e(C"٣Qaݜ}^|m#7 ~Sk؜1-tyYDj@cw9вoΎ1XN(Sf6  wp;R`;%Q9l^ aɁ2Z0TZXe*y{{S*W?_H]1Knh;!Po[\9v%͚2$| |tQR ޤm>GDNS}u{nlǑs'0g=vW`/j݋7jB_`xSwߗ'f)K. e]jQy<J)yY_fdL aD_ 22f'8c@ 8)[8+G/hܠ0fQ\+^"`$ &F|}4) <].G&v@gʻ"Vx.t}8b50d9)킡'Cj.*r\7t` I1c2ثB ' q\F6XN^y/j/^?Elpom*c4IV+ў&K$!Kg;npڛp!`s97b_Qb~$Hi)y Klc)$i@٣.ćV5nWeDinuGEo 2%,x-8=vQ!:v0;R{*?rbgO}in^/P0u> stream x]n! w78 wC%]24ڼǙ!"!oۗTUw?|J^twjsE\"/(295TB=?*j*1\ή]qVCإ cmӟJ9Š9߳ ށ6&n@ Y=g݂<`xFN5,D2s\wmuܥ +iendstream endobj 1228 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 910 >> stream x}]h[eߓ䴋U-s#ځC~4D.ICͲ.mr4Is44hkMXB[ED t7╂޸1yxxx_5iAmlQk3s3Oww۝nݓF>YjC`Ђ=v?~ HOp2{y]}S!xm̘yӖ._PC>/=lA؇VWOf}5vovi4wF@CÉGYI,@5oPį~ h>%#|-”^zLR.WV9V>AA4|~B#a=lNRw㼄[Ep J+++tJ CJs03dNRJ:azhEOSA*oup:nz{%LST*=s3oms(Dqٜ@FIE͟jzDiq# ? 0' 8Ȉ_vnqV[ͦDS]m_5vT.|U(*)ߍgh m3$$ld6LrIIqiXI #Q..1 &5UD`y~6S/T9QX .H" UX)FI0+.Rlb@JdRxP)|uo@U|Th\ rżwDWՠ S<m Wc Ux{G8zRH \ [tAZb5]ۿuTQ- `W39Sb0x~I^0<Пm3Lendstream endobj 1229 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 5034 >> stream xXytS?pSD +*\Ee(2Sd*-tcҤf4MӤm:ҴL T" wOExkiW{mWw>TuSo{F |YavfVq_,yt"ǯݗ+fvRbv|q^|jz־۶ܲ5~6m_yG {^¢% u32doMvESu&lbC%|b2N 'v/]ċJbXM!GR1b8@,#1XC'&?sHreټ&11ĔFނZf'>,|aMŸi,  ^_ j k!a9<O0xp凈BL4^mb<ů s [FbaȰ!3?IzŤB1]|&٪}P(V*_(VOE;k8,S3IU^0BKr0TL2n1w?(Y5 LmL] horr[Vkþ.BdDlNf˼(K"R4e̋r!^ $*_(ƅf2ĕQ g^'9! (ؤ>n ϣ>NuFG#]v[:Jج۩t.i)|d" %T0QCଯ8 좠T #lh!c$6\uc,D z^6oUsړZXmr2=5Ab= f5n7*8rFϴwZ8jn,Z(ʚC^ 5VᓹI*8UB_/ ФL堡܍us2+.hRA͖e@j}|ꖃ 9dWd0z4¼ougA'TRhu@l졖 dvuvkŽp Z۽9pR#)w0%>7v{Gbn^8=ocKC! ledeR]C۴%)Yy0Q:lr 3Oexx;t0YA7S[WTuP|K{=*~93ﱭ? k^C僁tVHW@H”L85޶CS XkN뫻Fy޲bNlSANWꙶwj:~}Ϊ=P&KIvU+!rM^ED_J Ruޙsn8;F:k| 51F}PKK[@I+.+˼ÌjRuY=chY2 l2IqSih>myρЃ+Q/>anQ4Хwf[U[}p:y>q:S+T2(cK$K P;Y1/5vkgNC@ sً_PW)T.km;XXX$ӵjIwetۀBhl=">WQ1Wyz!˳3E9 au3* x\?T9׹&t>/x̪"h쵣mFu>Mj@AE&b3qk[1J:-rc'>N] C:Cp]~-B.}>Oij =w'C p]wk@rI)d2)wC2ȥPNKz|ک A 7/ ­u%L64RPGBiCYNo2RYh ~DoTzA"*Qy}?bfs }5NnImъƱɣ3}d:^G ##z~'K"ս誺Ž4.z1#|AcA_Y>y *r[-6KxDfKt _pc-"?xs}okhщE Ur8FCRx9?[fWm X=WwoK-T9ezwO;0zq6sm/ m@$PΒd|:8ee]W<7]~r"̡UggZ 6OURT᭷x [ @f^ϙ usqܑģ#9gQC2F Jn|VZ;]*6F~e Ocj}(U#H7&6EUc+t_4ȶ9[޵:u>#PXQibTʤ徬׿D:iA| ӸRoI0^A+2eTt%yq)8K]|S/+3AG*QA`zmsAҷ.89EgQ|"++ U+V,}iueZCi;Zt ZKsZdsOB{m'[*Ns?Y]{@TI8ןjg]棸hN7銑f\;Щ/fz%`TƬ;Gv٠oyeɤ(A6<=_htIU޳D} MWR_B;̷$ E3ן>u=wk;q(G G h"/_v Xz#g_7JN8֏|} Bwo`~Ar.NH%ҕ{u9b2dwVx?#0}%d۠k%CBVxVE-&_ùKpJ K(Σ ғrԦ7HMi80W$atiZt˗1PvYINF%S*rF{gT9yhvG8΋љzO7Jd [. (2 <+ǫRJ-H Y1&Lo<'?. -ޞ"ͥzP~4)5l-,dxDzD0ib%cԸ&V⯚4  Dqendstream endobj 1230 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 2464 >> stream xU Pga`x gIDѸA<@Ot8fA "#*üFN 0J(*Fd͚D#, Zo[mְnnuWW{yflSHb%osx\3u>/[/}5F3ѦY )q岈{..nsOq%boIhB?J.Ćl\"ޤPrr"V!R6qֵ~[6lٺpӚz'O$c\\s[}%b;&6 k&l!aKHpE愊724',ۂ8AFPis-{%=%~6ЄdGװcF["@{)#=[ȯ^x32g[Ԛ|f $]/ocpo|QZ,oGS]-D3~I!L?IEȃǏID/CҞ!j ߝ<~;p(d,&-rOnJ6R{?! 'fɓT%,֙.ljص?F\@ L{׭&Z@=gHOڴՕ% +8U#kiD>Jk4$o7A; Y {(i:-cP5r1bCr5YM)HJG.3W]_*<*CB++4XwdM.f*{TNEEhҴd PlmL+/+ͨk@Ph?5q eOol`Bu]s`Exs.rv ޶\цIM2Ј,F{k P_G.}F~Cs[sIȊ͊dS2T %5eUw(46:\#vkc'wwA-ƠM<==TPteAW; ЮiPs@t),,*)m?t2 ਃ2A)n4)N2 Ԣ^}T1g#PUuTfW|*]\*C *r;bLL#[9JZ(B4ﻪV@3AEI8W)~H˷!-; qBk^\39ko6j mNk ب}MUzNێ>wAqF@`E5w'8_෍0V^Pޅ>-BCB@fi݃]WSzy.G(PȥHaue}b(Muʑ}07e"|K^zFF7wN6N."*}\4!@2CzKFB&\&@F={`]{6!o\NTmISe2<}l}oO0Nnb<}:Tחˎ41n|] VXq]Lê\*X,5">g@O{?^BxU=mmɥ1S\g(+uLg-@u^K\*9{_(2\]4C`rIDݺPA5V(RB?ل#?0S"#|%Y?reAG_nRV0q9GMrQK5DZ? fDsǫhGczY,{g J _'J&EgGA&3A:q5g:T"Ÿ)p$_;Qt5Uy<,2'|9'^w 5PGuH`;"+LG6|Yxsg"9 rDFro7]yeg%@,_PV^Er `{Ocm$qa#wp'}FS2_u|t֝S5LuVv6^#'Q8=^erե\#-%y9wc3V0÷ÞlqHݟCgN l[%RSs_:/^q˾MíazE]2%SA5[;܈~r;7/ S?}Ŋkߴ ~9[|2~Fn~1tZt9J]9)]]B0O?C\#BfcؒGºc~[呡aJabdybg$ ߜ;ǜg7A>URs@ #4HzILB ^ͧ+.@4DhQ'^8CYz؂r545s%XMtl].ʐkk<}t;/d,endstream endobj 1231 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 1516 >> stream x]mPTe%iYhw7E0eP)4V`Eyi].;""ۺ b4t-FM{a4rug:EgbJ$)^OJ64ٺI2*⧉g$P_{'? &c@4% ˊru7gώ|uV:!RB!߸8?W)ЪWD&EW\,c:+{F6fSS&WIy>EM=':fKS dPTLP(j#EWPLrgYLK!*u?ExzT!2#a ; hbÛX 3.ّHl+)? <|>ݫ]p./nyӒo[D%SLy(4{T1v0;" 8Vye33a01?!u !MI.\˷W]@2G;î`$\QAIaF}VG;N?^Y; KWH51TWՁ>OQ~iO@$WrLt]wx=}#b6ǡ֮%6ة8RAZ3ki孪Bs꺪:N}Y0"^PzgymY(NA%uWv2*8^ES#~̼%cI](hemh+σ=8!N:/x=5]Nhܺе|iHsuiWX]EZ xR﫶&+\EN˷AekcmO{9 4y!_+%/K^W `ّ-9x>TڡѼu;O{xOpA*L:MUvRH宱tz5਩ØS[ M%.Y5~p݉0ETKL( Y~gNe ņ YxVwOJ;TN-7gDTWm:MZ} Z*h 7L\ͬS5AM`'7q›߇}'"loښԵ]!S3z2>-b{Kˋ"Sf_T4J,c Rz̖4ѐ/ _ˌg;3/GeŲʀȗyFߙ6-8cqewy^` !P(^&"L& HPSӭc{3?tnñ~;wVi3_͐Zot)wCcGYU7lݪ_6F ՘ faLRa{9 2?qvzZO YA5/iAc02 ~bmX }lf5Xl&(o7xendstream endobj 1232 0 obj << /Filter /FlateDecode /Length 678 >> stream x]=n@^ DـݸH$E- Y.ř)fD}xjni4>ulߦ?>}ߧ׾|oii}雇ql6}=a~הqĉo ՚jTgͦ&]SPXz:5ub=4:7uf=6ڛY`2(2hL"Ƞ1"$2,FUSPV5XM^`5y^5z V5XM^`5y^t8 t\@'Ёs@t8 t\@'Ёs@t8 t\@'Ёs@t8 t\@t-ԹЀ5 z֐7 XCޠ7` yހ5 z֐7 XCޠ7` yހ5 z֐7 B 0 . ((J"$ S$!1~$$O)B?EHIBb!IH"$ uV\YB%E MJ%4)QRXWjeɕ%p)`XXWieŕ%o[EoZk[%o[EoZk[%o[EoZk[p%`1~֮7wy8y~]ry{]~+^W8/ \>5 9xendstream endobj 1233 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 7942 >> stream xZTT׺>9(9ŨXM,v.VDz30޻#11hL$xO&= 0wz .\={o e6211!(0hM^.{1&[8Bh}9l;m<5)SmA>^aֳfΜ=}:=5nj7 %z d䦏@kWoO O-ۭn^iMnΌ璢]K^,tdEU25k#y{f-~[9M3ߋ5;v?7o6Nhą.]SwOS @mަ6Q$j 5JC9RSmTj;eGMvPtj'AQR˩ =j%5ZEͦVSs5\j->@}@Q0sʖNvSRoRΔI,eJݣFSfʜKE(B@j5Z@ RC(-D L045=o6,\lEt#~fS7xwW3ӡACo [?p#F2l<&v=rG3eb8&G{V3jLk3-pb~Ÿ^hG xO66'oO8qbͤLe&N 4-4O5$Ih*5C e m(Y(wY _FT.jD(xj芙61K綞8ӥY!YALСYI fZJ(spr)n:Z@=*WYۘhliC,aM "#P1SGB @rNFc dBr:k24/h{س AX etrjt_?34਱x0B4,ؗ\O6׆8pa4{r@0!FҬ/EBzLH5-TjM `3'x@9gg:,wWo޼vgOzTPGU>K~Vitbc'^/Zȗe4ϛRQ ue /d7/3aFا8 a%rw? G_>A m2ut3bZQM9oQtKm[lM[x9^[0'س,T \ItߐY?cݮQA^H8Kல?M􁀶ӗ?/9sŚeK<}o~r̕/.P@0{.Lc9NG,P% D)4X=&^CƢMx0Iw3.Na-TF}T'rTƒzjGxw~xlF] ꣅ x%k>ɭkڻmWK aVOyLX_OE"/$kŌ{La N;A@;\ $W< 漜gC?NZ\yt[CCF{{Rlb(@-XlT L7(KS[Y߰\5hi=Ȗ!(q# 7vIfO7]+kF5g"yc:x$Bwzim kF1XQ~Fe+CLEM'jHW{ʦj’R827FaQ3?p/d<8U~*K"FDlb j+S7qg}/ ~#y1EH|Wmsg>#:HLy1x%^Wl /i(PAo?U}'YW0LfӴwL #QB">4cT`1KЅj0GT@D󠧂B#D_SZ/Hh'0F K@4CN*H* Zf%vgO$ROkןm{^uI^"hIf* źHp1RM+6pżPyד)AȪՠV0o,i]` H$ט\{v^uޡ ɂQ_\҃2 V^ꛧ~"#gP"$ =輡lh'jFt7ŜO.Dh_Tй)L Oц\ɋT_Ə=Sk1QP|;UAz9nlJ1 UeuCTcqZ RBUruF<GvO@ s(G*LW|us oan-$)_ٞrzL}>aIQ{FzŎr hFep lXvNHNa*l0oIݞ L.^m墢^7f F)OI& yLDwճ`#H1MHL5m&MzT|oҾ1di%L !xQ/ ^t0јt ,{FHC2߰׸ 4B\wJ$耼U鈃y \v$WĻ0L7^12oQM(UDNEh;b*)d~JQR+Pae.չxağh7u8Y\rPPh$O~ԀB1rDV?s]eA=#1@/ j t%301˳xw`c\"90O6orZ`rgo1f:aigtwAjcLWQ"-,GP8b:2gSEWE6FwܪPbW6y!R5&羄O d{v!;dw:KN5aS&7 o_oq]5 ^1^&tTO.`|WzXWUGŹbP}$qgWt]?]x` 7AVD8zPy.ӹS b u t #tz:SSYWdP; (,%,daz>@Hntgbe4mPJ8lgo󮝾[Y4^ Ү #P X6lޫ <#fʒ%s+Cxv<  bEs@P~ .@$50amliTNKGΝZbĕӳ$erԔZّco^GĮ.8vbcG=/߃wo \ChIGBدԯԿGրBUfty-[%wyZRUSVAS q'rF)NIF])ʔi*)b\ %T>?z1a s Cy A'Et3Tʊ'T.g7Z2L2N'Q_G5!fPZ]mvXbm-Ծ @Je #BHLle_]%-a't ^O jKnrד[ݥ pKWZRATêȻ޻ڿ9dg'xB"vr\oO797@Y7P.=gb<'Or⟢)#4&CRrAa*҄ z@ _n~*Nn>=6A?m:@0ϽNzT`Phy?Z8 (PF, ?Du "}̰P?Q5"?IO^dEGN (Q|/&7 Am*뜢 궊vŝʍ).V)w`M:**=%I!eᵩM)uɭU_`{]jZڊyEg L}h݌gppV\VRYT9|T4[LӚgnWZ0+pqS;M*ё 00[Dṭ QQҙ}Ed:9"p#L@Lt !^2;1% ƽTCe<5KȥS$'s3bbqY yq;dLᒯ|Ab1_@$& k,νإ {B1q}n VC|ټRk1I?\>{0c/i.H.JKm8>Xiúe/i<~ǚQ~ՑuW_YY_/s_TwK }|`hP_}Qi ]rKwn03tʃOWg<+6[T ]HXϬ0~= ;qiLiD|y$ %,E~T‡Po,?*80|⢺jQPڊ}i~cIM'}k_ >?bX˄Xfl+o7" h΋7? Լx]WFބdms;٪GO:FȧOfa-{l9vˉ_8y|1>$^in`'kMwNeesx™3>䍤 -,n97s[vӧw8 L0J)̾=MN ;.d7nt3H "盦cu΀W(̛;qݣ -tjTtFU5g ?wo/b4SMc0@^ ٟ=_,./F ~`'Fe`r/)9A$Lp\3<;ԭ|QcM]f=u¤`vWǶfg.*P*8 l9O67b;wTa%lo(pnA'PwX5L\82s.w mc LH|).@WR8аؤĞ_"0=޹%d=4U(q X]*#J"%$t,-T%}fYNKSYT# pů_uO Bbyݬ4"/2wƢ7sJ_ G& {e9T9pvvȎ=Cu\i]sv{Cdvl`jp""YUsZvylb81*9RzU"(NqqF{' ڂJhݍ=6߻{ϤUi*(67\oEyG3x9 e0 ;DV{/{p|czȎe*]@z 'zM]f ~b29_YJb)$+3<|]"'YUڪ4_q;ko_陪 u*uJU BQ d0endstream endobj 1234 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 4647 >> stream xW XS׶>18!& z{óNתuNEЊ2Q@y 2*b"mZցmzZk[m_o{;>H} koa6vzz)]MfS?Fo PV 8Q,O& p!xy+.Xh<-f\cam;nv _%vvYeֻ#,1 yOCy?#͛,œ =y -uz]{G/_v 698-h=Cfήތictz+-O@ nBl6xd.^͋Gx oYd@((DΘ$h yS,?d~ -}VnpV3isЩcq)W0f#ʃr({&P^< t"'Ƒ ۷qY>LhR=F![ӧ ^,)UyA8^G]r}m!ZZ1d67k oфp,)~v>$4Yu|Kh%YNInr03Рÿ6Xp׌-94IWL@IMb9NS=x8 [CrGL:8Qs^T190*vX  ՙ3bE- * c[# JKa+}tU0 .1 -O Fg2qd 31Rnz߸7A3~x{Q~_)G)XjPS2_БnhM?H@[CXʋ9A(M"4h%9q!'1#CQAp 6Q[|Ou>&S%VE~w\}>P@:rG,91J!)#'Ձ P^@e8UVm -֠ͧd.CYd}2o[VWz%s9Y#TbzSlPc+`m@YxEamY:d3Y2Y:w(i̚kvOso. OE  ]EI#ĩnN@ Pq x NpݝDPZɂ*Y|Ec_nMEx]?T4P];4H.\ov Rw% y /sp'¿tSj鞯NW;8AD6L9`IF`Qlz2؇f[ߋ9¹Qu]Wt\.HK cnz;),W߮o,ɺh+Jd}erCpv8a.׻.ݯa;"P S2SR7A]RFGWGnԒMN*O:֐Mj|b Ue0Thv4u2i\0ļ&%'ƗzykJ|.,q/ߍp4ƣ fCsDb+-Kiik79|!w=җxNp+}K{ n+*ڴ[A P=`;i(͐| L,fIpJ v Nl-Z%8NmqKˁz 2Ns;S5sap]τ]p`Fϐ=k3i*+ W|pe gdQg*enš#/Լu6dy~!&26Ryjf$[nPGp 3DHbLy@rӳԵZ@`NSQɖk3J +)`-Ў..{uu;x}wlN5՝{Fhct6`%Or@0 ^`Qe`(w?ɔF3hp S p dH ˰qb?J_IMdKU_h)>a?=߷:j| sޥn@rr"#u!t ,4/rB!+˫!!n!l$wL0/vUOh<-ì>ۧ? 9݃c%3uqCpD M~ВNS#Fl!i!gN+.la~$8 s&Q§- ,{+ 7Ni5E1(B +#8sCNN' ,w`#cnJpIgB&_:w@,ǿ 1c֤3.XrKaWjj}9i&`u@vETN>F ]\fVZ|ϵ,e܋8j^t_l1NEGmɗ >yoxn# JxgeU24' Jp? _MU~@?Ym`]SUm]М+དop<Xcbp 2Ch nJ۟7J N̺ͰCWct8or6>P ,û8R<.r}p$m]3x d<=\~X2+)稞@nlPDxsGʄ"xƽv:Fw֛MMV@;* Q}N|d00WWFZÝѾnG. cF7 &ΓEybg*Polƭ79,wSئdZ}655&?(yCySO;0#R?;'*H-z؃p^M*۳TLI[(^/]tq݌nֆV-Ҟ5N]r^P6f0A7EDyi1y958H92\mwBJC^V/\QTьYyMİ^d>|A_Jګ_p9"eS r2 B #t:&'я3Ph L g^f]S+pACwW+gtŽ{6mt8p#~ ƒT1 s}rBRXErXFVJxuc66Dw0E&<38/>ƅ>f( u~X=j- A>?ZS橱p8TV@< "?K}RV T|8J8ӯY?C㋒ِ-ȝeݪzn?>l{ޮm.LT+:27xSikc]s³p;~06/JOKtQCWiy|P{4<#pdQ+jZUAr/@y|RߚY~ <`"({WDD1)Ոy2D9fM^ A<bO"8*+.=~i-.\V4,l@䐘¬ˢ SS3p_c: AalF,Anre:N~TO 0N0*X!{YB|?܎_ C$q ux&d˓}7WuSQe\dv|V -D ք*p4h(MFhU >e!A hLb*x՛Xד8-bL Oz=SHBC/to|иg K9"V<<{ GcB񺿀g /YGa_YU`^զ2G{#4I @I K~=~}\%1Ʊ]qͅT(5sܸԴt]j6 r2(9endstream endobj 1235 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1236 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 321 >> stream xcd`ab`dd N+ JM/I,f!C'O/nnW }S1<-9(3=DXWHZ*$U*8)x%&ggg*$(x)34R3sBR#B]܃C52000103012~_i}Â~|/1TwuoU߳z~%X+~􋺺}wWC=rlgqqpvp 3xxI^endstream endobj 1237 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1238 0 obj << /Filter /FlateDecode /Length 162 >> stream x]O1 yDDY%C1C "dHҡÝdߝ|pgIghKB&B7`=}o&>`5y|vzKa4G O$ZֹN?inw6BPJ8.)ZV)<bI j Saendstream endobj 1239 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 320 >> stream xcd`ab`dd74,ILf!CGO&nnW }O=J1<-9(3=DXWHZ*$U*8)x%&ggg*$(x)34R3sBR#B]܃C5旤g200030v1012|_cew3qEt"깥rmlٝ%-Y%:;8j&,YٿcD9f{orߔ)| ~xNn;n9.|nz@g <<{'}=s&20$xendstream endobj 1240 0 obj << /Filter /FlateDecode /Length 4926 >> stream x[K#9r_tL]`aXl>t/lIUzT*zǃd*UO|01%f`0__g]+f.7]wfvwV3o;7Rֆ q}sW?Av7$3!V =sҵAn7ݗǡY~0Ҵ]5c79< @tk;HBtk;Ǘi`02ѐ0?M7{{q& Zxys7x>!'7q8>l#یnͼd$6(=Zn@Gm'Sen ͩ?tTxAO?= iVs6ê?cGRG~Әf@kl !]*m>;Mɦ:. 0s@zKםvÍwջ`$j'J2o4<? \}*VD޷dTs Y#!PQ=,b'h)_&^X2@{TO= y:`B5ͳ$b/к?9gq:͢5/)悃%Rxd%fOYYWp*p0) Hyg=]煸5H{Q]u @?D:)PbT u0R&ڦLL J`-Th2Ίj5eUK`tXűz 5&e0DHzy:_6u RV h!֮&fC%i'+y<+-BQU< T'\bl,KNɗe #zFRMCNf!8di1$$ rSge(\,!*A5diV솯)CA/*_rZ/ rPKU#kU= 0O x W$~5XRe2V\@ X"~w7RAX;H;0X^r/ .e nk#?͑YHJpػX20 7= :8,x!;*o,HsI1m nzZNe KE\[NC@T5'8QpYr6ڃX*mE]ښKRKcJ-")3F4X[G>I,A-$6p^2PvBHVJo'ws *yWrP܈VÞV-%$B +UydU"<(W[ܧA7jĔ g5=8/HV\TPC$YFF7:ChwXM%oe#XPë7U pwf߰4 ZiX řTңZ~Q>J9  "9>Wi\~uJehҩCP.Xù.W@SV%H*:=.X6(/DM*D@PQP*|;rz X(V_ vnrT9izC@zq](b7Ȟ)B]RG+@ջTvMގ~]u ~5ȦZww_WT(gY^m}RchGj d~i9tPQn6}Xl4"b{IP E:}q6EsuLH#a3t qHu0̍sAz䓱4Sv5~al6B e }&HDRLHı(%S.Nj:)HqB5jUüj$A3,!`Vĭ< ±;;3qy{8l+,1yBR5% 2.c%7ҔkunXB7%ނ鎅lXfm6t4AJ_N$b`tl8V pOg{AV9u*dٰSv $ 0*E-`bم|W-CH0{?m

˾,),On1-ik=}浪hn~sXn-ZDSMtWvCRNk(V5xJVfC){x1e8j,xoB*xg?ؠSǸpa üEZa_P'Ҩ\r(*LMLMVLy!~ey<mG\֏s4~rA:tl+LgFķ۰t?:?81hSKwrieʲ 5*˜[ͅ xmRS4v\ģy|#"R_f,욯uAҚz#ji:8IqL3^I?JFYlLkɠ-l>hbe a= ѰKP93;qW>1݄tX \dOx D=ycNrVe}4&r:~/Smu7Tu_!I RɠSW{]1Zo֥lKܬMn܆ l)Kh*HQkr=|hLTV5AO7fզ_ _. &eӅR2xv΂+Te 9ڜ< 5QVoQc@x-3TN^/2OZ#,!xoMp ojQN:Huwk x{a:qh}~cGbt9\2h%k$Q 8@UItQ'i~/^al<-S@LxSv=Bn:jR.C' %=Lq6:?+_.Y?~fgt%PB2F.Ha?7 keO4+ю"muKq=13VK۩jrDko[!eJ__Fszh!-钑}6% Kbnp<#MrHY@Y+#25x .{U+ݤ4Dp|n.&Z0vRhWhq v20.|EG=T#bJTnN)V1_|9-\[%He` 3vWQ??tendstream endobj 1241 0 obj << /Filter /FlateDecode /Length 5202 >> stream x\[o7~oC#O T 63&Z `PjE=EV€dzxxw.YSY¿Y3xLP,X~' ˖I;WKQRu; wqmk\ ]\|Uخ/a{|~nhپzm\nZm.~X.~XrXuWrm=< 4ōx&kMq 0Ҕ# 7؈' 5|1PuD1>.աq?||_~E=<٘ZryNq6x2 9JW,&_CZJN,JJSY-nPt]0xa$ݫ&oe/aR1RU@$A$V>oapR.}VDH}.U|m'- (2OK뢴KQmO^-V^m_L8U` gne+\ J3v"+u+[ & 5xPnwsA %lA'1 imBU6nrx! _t"+ޜIT{"@ W0뷛7^1Sԭrf[4 ]k- x+TwTbϿ ݦPET_k`ط;pz]NwC:/\|v32 "qԔTLtD 3eyp]V6PAXߕ pMXo*RJwos*(­EǣXO h#鄰x%ǛBZa@ۻ$QA7 V~|n AWRB/vsG%2~O[E˪'*Rˠ vJ(%j-@>*o~Q%'@ydgh|sg/HRц/\P.^S I2iR9P@ h٬ H8o%^+Q}LD:GstAc-*}ˡe(Ho]v!Уnu4%\F^d$Z{<G?/ȌɐZM D(֩y6nr$ѷ_t@?,$J|64a/ B(b('妕Ǫ@7ef>Vl[*1I_LKz/b˝wCu@}V6*%φ}Vu>uctut)RYqKNV%;FGf$LO(f?ZB4Z5xVYE1 5P&3t ,8_áǚ`]F ce5aeE0f35qP^YZ{cN8a!1vmL&Z"=  jsm-nOF?* 9:u0X#BMFXZ2q$sq /;Ԍ{1@}I!a`YKɷs1n~AH6^'GVF1|v!xأ~T;M01@2M79?%ZN|KNo<܀ >nwQ!˩s c%4 !e"G%C~ V )iLׅy%ȼтIr_s``)}IĹ$| ~x_^{exyFl1'*H`<)7\.;geR a(1\X! QKR:؜I_߉KL*mt@- i|2tL|ǚWi+0nڦDw0Yqq BXFzd93 QPqӭ{ 9駾fr 8ZQ5䭇>e-r2nxx`i|50/1:ci.=S [Mu|Uզ@S(G:/sƨ,l^ބ#%bdW,Oc!=x _+Ot"b JyCbì|K 1&siޔ(:Oxl ٌ=I 'LA&vnzǼkI(3Q }j]ŷ}yjwh,:){ϊ]j-oS񳃛ie.{Rus=gR6S}ӞL'\ՏqzR kE7Ww6?SJxf{$- =vsdˆ>Kv%dI W/Ksᓵ 0!ETq"p/' VIUڗ}V(r2$1#-Z_w18 ?",6įm!SOA"LD|D7NwF^qu3LftNȰi@=1b)zNCj9d Gg_΂ԭgyUf3 mJ#ćv ^ C7N,&:/MvWEeg\vm[VP/--%zey^8X(7BJr Y(|De]Dj1")8a؇ Lj9F 𓟧FcFδЖQOdLk*d-*a> 4MzP;-̷Mz_s1os# Ԓy_.chhɇnsXCzN*F`Nq}V&q.p?0=?sendstream endobj 1242 0 obj << /Filter /FlateDecode /Length 3329 >> stream xZKs/ć=ΦqRN%YNU$%9>,iקX.Q:34nY׊YWg>6H9ӮzmP3:ߚ~5l{g/ϻu&H,w g V"f" Z nv9{8B)hL㿝/6m'lp?_t{\s xn"wmҺ hj۲Dsҵ]'}<\CW*˨P\ |7__;-7.z FT Z'EiF/%.϶,֒ չVv% oT4X6˱81iIOOo=Gjef ylQUVf<<7n:Jjqcڑ)wm>f $k:8^;ܳ j;[BZ#FǴO\m4@tGUO xtv*C+)DtuV1^xUa[dBwEX%+zOs5 8]b T:VyR } I BiJрhCOhq$v ,N^C68?X_'܌z"2PG'l廴Ig͘/c,OnL& g'ePb03bƓN*;!'WakD4pPBm c$aƵ1o T 6,vhgJ٬aښOX9<Oc){&k<>Ռ8  !>q`'v5%=Gk ~aVbVdZ?:6`OCԩ&'FòD:ޞ^HNH^h$2XG&Z0~LPp9`zl֟2M/*CNBڱڑ;Eƛ)LtƩڋ VlJ@oe!AcCh$}(MGKx^)ǴV*>g:4O9`%T.Fl8]T~r:amɐ~ fzp el$:= ImS}lo81"vFL솖HYC`y lK m&4'9Gc TYRyŹb3윮F[G';3 Tښhfd1l™Vc; t1)_5!IjVBl>8t+I_L~:ui&IV"Eq7,OO׆SX ,q;='CjhZ i \=E(ZBv߀J܆zMq ۩*A{W&vt"22T݈a[7B-WFʚ|yeyMX oק]vVq'e>g9xWbQOaɴzH)iC|6zJ+HzTԼ#xrh FCrUO~ i\EOpz ;26x;s+v&-euDQϫ#(7&%(eD ʳx O(ͱ\zGkK@X(rs񕶂wSThU 5߉a9!VNp_niL> stream x\KHr>Pfj󝹰|h- Gi;"b% T$"2] WjՏ0euk;G-k|aDZ7۫fNcV 2Z5uvZl3,#40MZgy:ՒqG:۬TFN5HxI7X. 79uil}Cf) j[7.l3> zKTs_~vդq b@[oR41<~@cQi|1|Wl̶Nʰ]-&Lfl>C5rF&_r[]gp7+VӎX^Kd&:rӨJYZ\$I"`GE( aۏ~o:J2t;<x5] cyvaN7=Q(<\65ntij!nVtd~sG`װr!1!mk%`G1!+f_eJUO咫b`GT+ i $-XS<2))iV$k0}%ΚDz@v1z5L7[}th4ޕtU^G h)7A*R 70>npPM %f&~1oN4Kǹ#i3o oWߟvkB#StHwѴx? 8W3x9: "5΢5-S&Y_tיoF?p*pNs<J6 ^N)cgP,.couysdҡoN+% X >x}@.a;o* : {9(ʅF #ߏOIldB$Ta&aPD%6EI=p~^/ZS}2td\4#.9,JtאTڃupRth;rBo?= V~2whԀPH︝ڙjy[//l$ZZ(H2hL)l1mg CyO)pC L "ྣZmYJF)IQm_4.khݲ{hӉ s*=$ΦTa7|JQ ծ1+:<#ĵa)Bns"| XUZ _Z6=mxiב.qeCsq0O_aOh:W^y<ՂKphB21SKֲs|T "9-؆jq Cr &O$ anU/xxf誅Jf)Y՝;x!N#[L5B5}og++VJn݅J.2S3.O~L:r҉VW{}6Z)8 O;xuT9kLpZwcE-jgC]|x ĘPB**8mTG"'#Xg5?X1V@uvZ,YEsyhBU'D6G6EiBm3r!k%$I H?mc1 p5gK^ǑmNd+e+c: ˂~D7Mc2*4BO^e@nakB ^h5S\P%5 u7q ˿l0NyN$k&cSkQIѾP54& ?{A.ҪVvqW'A,M?DӤ(4eZ*eY?j+4ycoDk@ ƨ.P ZeI>Bả~rӜ]tʺrrckNΰELMoͯb*!:HUQPkYrx ɃV *6SY$3tpj-i B 5$k 4M_PȪ[Q=cXT5UNtg2H>!>$ʿ\̾Ӳ8 uOK)IhTrg\'?@iL {jV YaHn/P+K}V7i"pZ&4 b<™vVYaإ،@<51!sds/Lw8jZ)Inأk*-grN}BP?N.ic|Ö tvϟ5U> R !>j!ue҇?V+G͝9s9 *R/TA[iBT!9`T 5noZA= 엩Qf82.Plۆ\:R0x(J_ cޖmF`!ZdѶ3Uo"㬩+pKYRJJ┅SwL~(ᜤT].IN5}SyjX<9}6ƂӜxe/Gf7 'rO]0mE2DU6 fQ5ǁkẖYQ*}̩0OUL Xׁsh墉u9l/#[|W-fD|Oوd̲o%BQPߋ`*!`+8.8j5Dm_ wkX8p).iY8 CWW`l$ZI hAگs;Mq?wœ1EZ5\e*dSy|gbJr>~{YN`c bF0 iTkX!@] AKq`"͙ÝB8"iN,1aV.N9 \(t7҂jKX/y穰/1p20XZe4QR]<6e)Q tG~![DǻW‚ޯjśIP(* pGs(PǽC{b~Ơ3^4B8)Ix?F^ڰ`O) V+ !\Xwctu;vB}۬.VCnO"l&sؚHZ6w)f?4F۟VS LQB9r:|5` ",r$$L**y6O0ȫ~=UKziw }8>әNnL չIVuԁ'W+4y;K-1b?;NIӁߊhLB8,a:\,>z~2_CRKWG)羓NV;W/U0@Y8X9=Vt*S 5qG> stream x<ˎ8:ׇ͜\V Ti79=tc`9șYUr:Si7$Ee7lD"#PZ_]u]?7W|-^c#>2Hpk}7!i;clvQN!fwǦw0NqM_C|67Nk;#:dYa"8"J`vm RN=xgUa%ɦl;rF(via<)>ײ|l>/dhR:44qr>i@4/,d Z5t-^4!^{n>UyL5 h'HU?Kء]h#}Lf)x+ ޔs6Vx@KYKG̗4N5Xu7/Xr]:ζVG>z8fMRSD}5m$%+#Liz58oG:7q7ĮJ*-v ))u&(m7€6%cq'=t-xxĨ_&-ai m`'2'mSӰK̓jisHf͏&Za ,kN |M9kh(e+N'2h]:&=IxB&Tb^ܳz|OX@r@<p?NC >ƥaz2DX*Gߥm[n ˕KT9 Ha6>cIbXO^1]S9"HsB@txΗ9gAO\כ8lN/А." E (zX%}m8ub Q`<9f$=3q095D[A hJ}_HK@h[ӲS5H|&4*rގ[;)Y) Ag'd Ū󝳘ᩃNƒ8\9:o#"Bvc3Āir>'#bb!~ZׯI{p~6VDf84*divLؚ?%.:ߞlÑ܈ 'yO<x0ē%(c;۟PF0+TGMp*.h^Y'?Y%Z!Ԑ"m;N;dWwcJ}AI*\Mʚ+;8%.yCGr#L+uNVdAELD'1N(}7t |`X`_4y'~7yp6Iu, T%/ 0pS~o() &2h̄@}zM`zlGźbR*K >ݘ2Wij+%H/ P#2uVeDR>( 5gXP1qhNLYȍǤV㊝g+X+[)> "YB8X !Y+xM䗡סCn@_H 5g +Z_ԑtF= -嬟K**KEYpm(O>9(n'=O,@x)3!S f1X|aw~H1wO"qTɎm &'C-sK? v!ڹ m! ^\z25}k ""f:oK;x;lyy ^HKj+Eq2y<\,蓞#Ϧ}ݗ%4.0OENSQ33#zD%g&N9.M)xב@Lv]̱8 $G}|=8c50_Jlz55Ӎ7foq0LXWiCMa]Q z~T!0 .MXLҞ3hP(vwى叙c]W? ɂ1IxPTYZq/< [Vѹ8Q3rě9u1Pk%.gŕCaiʓvlsNXE'ˆ|\"Icy7d_ 9^TrU#sR^-5Kli 6d[OŠY4Q|\%&__E:{5e#A#`Fk/N42 Fvse=ȁ+1ov7ӯ /Sj5=9ovy|fB3; BSuyYD>LB7%Z=WHw#pp0_;$ 0ZK6OlG!# o= ܌>8| 6\$s̙AlUMoLR]NI^݁+.߀HQ(t=aIgRPt=AW[fW!nԈ 'J8V󚅉 ҷGFhn2?PAc@"g}4s#X*3Y݁U+8]7Vo3t79Sh~om¢G|﷏ SsP ~]ծ)ZH8LI(j -a] {u6}eBbi:/_/%'T{s46쳙TPvzkzpQ ؎KJ٩~0N'+b(w >{q܅+r'{CoTJ9ROib5 iSU bMRf⍀ .AT~jAF$; R 8gg/Uli_PDŽj"E m?oJ/9ZrI.q\W/EM/QYY_Q[ OKq@dZ.ojj܃k/4P@iXj鿶S,&*v5K>l/UD+f vgSE e]il9|(L^&E}-UYՙa8'dkߤo򬻪Vq}Lr2MxRI,[59y >y$**m|;wGВjdu~n|Rumim&֢)5. TZpMh^1|Ĭj f~*ˬSQ&"֊\ە9\5a?}/TulO_;~)Yijtu3kIKiIwWRj 0zL,"?W_f<<XC!ST5*Ywÿ.A] هcyuk?rT)'J7 L e֋ jҬ9 AçS%?sVpSS[> 4f%5N!ԇt{Na?X"fb5U-~URq. ]oU `SW0i 갏$VIPMi0;{<*0牽^7hG"֞'\Ę?a&#R eOetRfZYJ Gf)2PO;?b/3^)t*У+*nYgQ [m Wx@/ڀ7pwendstream endobj 1245 0 obj << /Filter /FlateDecode /Length 3584 >> stream xZmop@""%mk..Ed{[߯̐%۹"@,.!9 Wb_{:㓫}?ɟϞNꪶN/1Vxzr:cz5?0OӦVOΤ8W5[Lg{^n+2Uԭm.Z|V+(p i(Wk.U]ePy;PBZػi5ߎ5hi? (Ńz]1H:0r޺"4*+U%MfDp@6D~9ŃvϺGLeNk1qp|+`L#) Xh_%{ȏE"oPhk[ Y!h +C>Tv@۫Ev^/Ȃ=rCJ*9TMgՍ[b Pu}I=+F߄!LdF# ̵~'-pƭ- %Pjnn v޶ ҳugy.e͖E|f8|]ZʉupZ8Hѿin7=x'z[^ #*.ǺJ(ͶR~7]"fkr 1$$,cYA MG=P=xTE>c*KiU1Xpܓ఺v,:Px(PIXa!TԴ+hi[f&BIl$́6 eo 8PUt`ezOLb~r3NDvxR |JjF%.z)fo4_0 rd 7S<`^g9)n=KWXInV%PapͲ%k'`Np;1o\#Se0h ܻr獇DbD)5h2* ׆Jb}R#s8Lp~W/v Xp񀈽Q6YwdMs25`*Y7, 2lhS#JDE){A~d c*L@u}h/4ԢҠ3ĤjjpFlZS$/ ]?\uU rbE"XS׮1(ΜT=!5k儙zx꠸0B)(4-mip-.6S/nȌXRohŒW-OW,}{O4gHtӦjws )*kXay7WфXm0RԦOdi?{/{W&:1CN 2C2Kq dKCJldX9Xi,1VK-PjS!lN XX]I;#E%ּ/fǚ`i=iS5m @i p0VA#@v`$K1BRA[/F/-5{} P.[ Y]:& 0?g"~ djt-==\g :+@oRHۻ%Ql"LXhS@!9̠ '4`gA m5$8².`PRNLu.HʉX!MymWŒxzP.OA9gH,O D 3͖>4pGb1|Zf0TT6ݬ[캱Z@)'@GޒraXأ"1 hJV%@; u6T`iɫ9-[ELlm&>\ q 9v9BJ٤Aleah=M.LA&ECW. C4qG藣X=ݢU5-x-x`) x˗φgʂqU)4ZhM;ZK}ee@sD³tᴛI QhH-GqsAa'DWC'."ue? :=*'hA~U& v t_K SR8J0/|`0RLc3e6  yd6o1qas<p kંOS9iB>X)4dj0:U Yyl+1(!ݳN)떞֐ K[mn5ːkf>oMAmVn6.^FK}y|_.[1S YWI(яUMW;+r<^H٪k36c6fB=/+@8co `2Go4؀ b T].c }yְ[u/0 J,t]^&5,7 0L ѽ]iHgcN$Zz⩍oWMVEр~ѰΛt-Ξ >!ܔLIM@RlMAvu`X=Ja/5c2 x>/]c\mJZL£p|F$#K0 R!UgM!@W=|,q23)75_Pq\;~p;;CC7TvqŮk_"i6'u|E>(e nb(2y&p{}X}[К<"-Ϫ%^pl|oEChh|'A ,bOIUd]UvZ^mW OT>@Ħ OH4X- pNݳҰϹ0GT &36=ڣf[n`x`KX&vA;Dr -0~X 5J)Q"΃LvԇcXUm*U)wA;u0Xb%y^Hx$uXPe`&%ٽа,=rLJ dzͫwLUuJ}VY@D,惄L>rd}Edi'3 YNLl>R:i4/ͳAa#o܀#zG1&: ,__tЙ^k.D E9 Sh o!.{dHW$ބGI aS&>ςP0,Ԗ9|{G uϫ=gCv4H.,z_/ ux{M16/xj(^bagQue,q"27˙g~2jR~8[endstream endobj 1246 0 obj << /Filter /FlateDecode /Length 3438 >> stream xZ]oF}oȃ"!gf66u R%E츿~ϝrF.8"9s?=w(dY4Y}%J(uH$4\mW)ϬD`ٴViG/O+ĒK? A,8pef&"j^hE jUmcQD%w!2#. l-.Rȣql1CACAb9jhm)lo:!qc>#;[6q-oI;U] 0v„;]0IGBL 7trWOj23Qӹ0ڇr5y90#gDz.Lft] T슶={ݸ hXyQb dw{Fkؗs''} BW<(BbpkjǜUAymr邤q=ƃd ƕugbl|״^6U^y]Gzo?GB"ufdo JY*^fEE.oP<*c*̼W;ڈ(J15_J;w̍qAeD>PO44}{DH.vy̠~.cն~= O,$6Vb,֮A>W =4 KMds=BSB(#0cI2X'6?C;~ڮ"jBwdD2&{0P$LΧ -Pm6U.6`Ciq1S]aS| kceK,89馳F85TnBP AUwYt? ě?aGkL*v~-3'ƞhp}1`knP*un&jil©o3ٛ Q9XQ?ٞ@}MimO)F/_?zu BS`L&3I(xEԋyNs(LP}e9ov|-1)af'4§KLSOG@>\<`/E7Ҭ,ch"E_nn=гiE7r۫`ljIb4]Q1`pa3L!:\07/ۦѸc|Zga*eKs E%CL`v;-F%^B{ t] *8 BC&!im]Wr2Th?tHS?q47' N݃BVz/gC #=sf愠oj>"J hU׿)o^ I*y6fͺu:tfYSn}@:'@I$ƉdayK[!HJ0B\?,S9bcQIL%،yjkToI{ bQ3{T܋,ḋѤ-;y=j,[BVupT-?F;=Hf/6]JqZYgʳӅ,}a4?[ݻv մe'L}F:uU7+e_su,Fr!:0w +bG_ D ʌ6c-+E> Ƨq|Ї _Gel{Xrpuyh9)oS!1Ð&hNaq(#*{W*@|H?e=O4bSX!zv_ψ K%ۦt-9jqˁ&>Hˑ=]}]q_nR p1zz'V$4s]S નWstב#~}7a 8PEfgM1: ~VtZw5[t'K7!o`] D<x4r?4uFN]|sͬ{=ZwNEA79 4 &n?nThr bZ/d0J~W]E 'G(mbjK"ܨ=-,.Iiv~̐!~턻+@uJ͜񫮜9Wֹؗ2mYR~XEݚNfDŽs+*_"X]Ҫa'fY{n_ۯ@y7m鴵gS|IIzL_Љe]V};ʅoo_/jYt qp-AC$C2-ǣO*=lC6_We M ?0endstream endobj 1247 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 2368 >> stream xuVyp_E@]14q Cb#9pdKr,_d-yo%iيlE86─@@24- 3$}JSr2 LfXL tG~~ZU]vTQȆBYuѲS?]OXùo,n^ YB8wmP.[2jیyuFE\- rtz鶲:mSBZn[^N5u*iL^vJZW%++LsYǧaU-0 KUTTSPlzܧ6°]nl/==möc;؝X#;Nl. 3K-y:󏷉n{ز{obX*'&@7o S"(PvBrO1V_`cd4ZwJ)ޮn%쯗CRiҵ): 7|4Oʨ͵Q65%9z 8>{b|U+g*e~1RҬvr=MV``Xp]' B5 /(Prd[\8 Zs SRN As $2L9Ќw@drŧ9qU''mYY#ԏsYO8i 4jmDw2=tjZpjyal{Wйi騷X it7>f]C

3m$w,1f~$N]6p0&ZXmu;X^$Mzq&@/1GTCcW>@Qaj˿Efw8,6"w 'O? yr63 W\4ļ !eًrBqyo~Vt-@ h5噓h?ӀJE[չhRQG]_Pj""MCVyB7$| Iw )1S3q Geoz/@ƪ4Vjj+AS)M˩+r/Υ.SuDԵZm?8 ʸELBϗ.Քn">ov4h²K c<QWߨTWL5_KpOF/tnQE?!">x{Grwd+/r4t\Np% x%q<R;dٜG(d6aJe/\$ꨝ ـ`Qj`N08ҡ_0Uqv(3#/<ʢ9 >&D{gEɦIhJ&Ċ47ڵTuofV+eoɷ 1fxՍx0|GƸ4,㏿Z GcS aɮg] N{ɆM 94z.(9d$s(tȱ@Et'(\əAD0=# Hc6VY!Z◀ˀ_=Xx ;$`) Z]ϲ~ cd^C! pl43L8z:vt$Rv?@@r(Wm ZtqRm6]p\4^dR*I9-KJCZїG`"(L(E"9[ύs$' ݏ֡)&r8Yfթg&^g{ai ii6ɊKWj_L{85Xfl{d-cf]=!:Lge1~rqe݁ai-endstream endobj 1248 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 452 >> stream xcd`ab`dd M34 JM/I,f!CO{nn?G~/Q_PYQ`d`` $-*ˋ3R|ˁ y I9i i ! A Az 0000``XPrˎgZнG %=w@߁~[B+]wp$.[_tBwKkwG\m@7Go-[~Hi=-ݝ9&wN.>].?|^nI#,ٞ|zriy̜}+W:ccecs֭3gn.<{޴ Vq]b ^3ϒ-}{'MD=Rendstream endobj 1249 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1250 0 obj << /Filter /FlateDecode /Length 173 >> stream x]A EbnFctS7]hz Câtںp2 kO6^Xg0hU5h6Ww7h.'p9jm)qRa4"khcZVZ̖mYsJ,!?eR,,Xq܂$(Vuendstream endobj 1251 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 411 >> stream xcd`ab`dd˷ JM/I,f!C̬<<, }=Y1<-9(3=DXWHZ*$U*8)x%&ggg*$(x)34R3sBR#B]܃C5\旤g% L ] ̌,S$C=߫.1?d%eK;@honk-wt~t}/_P.$EK&M1]#Q7vrM7J 7I˞>if5N<ݬ\׹XBy8y7. =&l>ojOϼ?xxĤendstream endobj 1252 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 2688 >> stream xV}TM߻{kRjےrMRQԐ>T*BTR' ͘˔BURd S RRszu>5ֽ]k><04 H4OɊL+R6%H c d,AƆ֟]7f3a Q# Iym ]:q$ykrғ3Scr"HJHۊi̠$e ԌB1>umZzF I)JG)S]\M͘A `"XD,&ˆ%R‹Gxp">/GDHaNH#FI&,1 0%r $US ג~C TRiT(/)/t.#a\< 9;gdegM:T(>H,4W{n J(tE=pfUm ڿV:{peC;Y:NesXa~'#B<CہV{!w nA\W[M7p*'ʰĀHpi`yuFqcu&hgAv)|ƛ@0pWy܂>0zfo޾+0ƒa_ qmj/p|~Mfϼ#|GOjkϒm9Ȯrƅ[B`tOێWƝݿ{qTnY}L˽e&p6`B>A4?E'RuLEu܍I*;z~Mݐ=͂F E3p V)\/ޕ ڴ7ݎOU2^z!bD Ey`!.!@W$Apn'/pD۶w7څ/6S9Ϡq {2=+Nw ѥz:7!g6l)gwu)ȇqh^{s]7a'3oc҃ ݾ1=3)%?[wT{Vp403(ŅB`xZQ/7Ge=X dZxNy҂x8M-.g|*Y}} N{qƆ {h`f`d>Fmv[m0,nt1E[m>u1 [q_{dj_eމC /UDLe9)qw21 "NyᱍO6 # 9NA:(Pw| s:jn uǣyo3!8pgą,tAHڢutǽy w|c(cRX.kCIX!Tw ?p &RGGyg lQ+ *龜S`9%f-Oot%;#z;n$~f6Q iHuX {yI] `)Bԁ acy_ /X fê2c3X:8A}7+&6} $BmyIcrmע,;gEJug[7Pi zzn=b~ -TL(6z=FMjX 0i9!VX-a佚U|.]dgѪU s]w;N]:}I ]uO@ME gwN Cob$G=1〱qwpITчendstream endobj 1253 0 obj << /Filter /FlateDecode /Length 273 >> stream xMRKn0>Oj■*f`G7/={Cxk?iҳ1-}^MwKLJWq%8(ş\b,[FcOʠT*+wtz{Lz[`Rd Gm`@wք-Y"_kܛBo|x FyK g )GI2X\k9E#ɜ]ꎬ A9,2n?v Cw͐~iendstream endobj 1254 0 obj << /Filter /FlateDecode /Length 57 >> stream x34U0P F )\z@ 1s,- @,KK T$RҸl endstream endobj 1255 0 obj << /Filter /FlateDecode /Length 262 >> stream xUQ]n0{)r$tSLu$K;v޿G/q OcfxF loM<$;Un`5Ol0Ke>3Ooԯh)6?jzY{l? }[endstream endobj 1256 0 obj << /Filter /FlateDecode /Length 345 >> stream xUR[ \`V<W8ReCH'[}90jTO#秈JkSx͘{?,5K7ۘeTm n8JwBqóm"p̀H!* }cTu~vÇdqoh •Bɴ KqFn3dD#p|74$53C|5&| 4B2%Jvw,OvĝRdX?k7Ju 1mcc;*Je CFB-5Yld9T ^C- efNG/3CM5b5eF[nendstream endobj 1257 0 obj << /Filter /FlateDecode /Length 273 >> stream xUQK s .9O:owmh:qdeKT\|d%ZRy[O[)̸21ƙÉz>㙔Ϥnѧ{ࢡ{ cYv E014^ioLX7j[4=1iZIq4g@W$_6%cq&gVJL[H0%fc89XkKd*U%|[OAa[ as(gW!);?l+J{L_endstream endobj 1258 0 obj << /Filter /FlateDecode /Length 267 >> stream xMR; 9> stream xePA0B4ݤV!lzmսDcҾC| bnUW%zeȺ z gxqu uIbHLqt s8ܰD{l 8 pKAġZ:|)yhsk&ôsk̓d :փM l{.Z (/|*ёpcHmPqZf4[h[xn,?!endstream endobj 1260 0 obj << /Filter /FlateDecode /Length 284 >> stream xUQq0 7x;ymr2S MGq4zԯ*\?%:[m仈ϿVX] 5 WQvͷzc dpgJwxk  &fssVz[ҽ"DuN1[O!'@1y@p!UX`Xt,Np8ӯ"+F)W3LmXdk7]Hx[_])m.?S?ZFۇxpG.[mendstream endobj 1261 0 obj << /Filter /FlateDecode /Length 435 >> stream xUSKn0@+<{ 6q)aU]/)?tB`>x>P/Qz,궈ۥ]<,QV{Z^6OTDV>*a»G1m&a YXef^*l_Ö5[$@559XvJTζ"-m\'! x{44%tT4-(w)lL=1JYEl%, N`Uk(XNȈ7VtGz=h;endstream endobj 1262 0 obj << /Filter /FlateDecode /Length 156 >> stream x]A!E4zi[~P .ըby O|-s}#Hokzx U Fؕc{3P|~RI3yR:> stream xeRKn! s .*rza*U#LkcI5:7hקPbOQޛu`>8؈> stream xeA "4 =mz_KZc2 $ $ O;8 x݉P7-8174LsAQ 9sw[ G.dd1zendstream endobj 1265 0 obj << /Filter /FlateDecode /Length 377 >> stream xURKr0@:?yokIid|hkJTo^K::od58V<=bu~uΈGdYmBxqIES" +mټ?:EQ.c dseKCf8] Ј̇ ;jJIZyоe F9򯓞J`"LwfAY9T)DkIipXZ{ )Š6=cgW٠ZHhX@a/, _X PX A$/1soq4p?5 ]M7Kn\hxC aʃ(12q$ Wme%k#,~*endstream endobj 1266 0 obj << /Filter /FlateDecode /Length 86 >> stream x32P0P4& f )\z@ 1s,M LḾd.P1X( <4I+jfbPK endstream endobj 1267 0 obj << /Filter /FlateDecode /Length 57 >> stream x32P0P4& f )\z@ 1s,M @,K T$RҸ tendstream endobj 1268 0 obj << /Filter /FlateDecode /Length 290 >> stream xUA!EL@@<5U#J%RPk:y cj/XYG\U50y"$U:fX} 6#6Uq#w92jDƌQIQpTSI,yp3d2ԇʼ驵~t7?bB!tVq3&ݚfFS}ȷyE.sr7->mq-@C΃֛<]·0M:9nrg-fPߴ>B|wح)!;O ;Dا|M:y**endstream endobj 1269 0 obj << /Filter /FlateDecode /Length 188 >> stream xUQ[0)r Iy2/UrK{Cj젰 (n9PT}&@ $9Q/o!9ȰSrуKtAt #p.'[TDrS!E(Nrv8-3%4s6}^8 -tis9HO,LUendstream endobj 1270 0 obj << /Filter /FlateDecode /Length 367 >> stream xMR[ \ ;!Ig],%jŶd¢V#p/)J\"ZױAKW|ΙXff\U Cס#4^`T{2H=8خj*&3 zHԑL}mkp*|71TgQOxr(,&4bFF n, kp,;N(;Cp6b3vlAE"ºa"p8ܔcȼ1Vp 4z2  HAl%Z_/kUwkyam sߤ`.X\!|T tlrצx{DcwhѷꌟnA'z> stream xUPA! ?0S {BAB0.j~_j?M<&!#ʵh?U)|Kq$6K'/MBE>&ѷbUYz@աJpH@װijG+_PD'&zAp.臓xEUOE&Fc[;.Qq,*!±OWqc#Tlg D[W `٫v!c<4_(|Vendstream endobj 1272 0 obj << /Filter /FlateDecode /Length 96 >> stream x]K0D~y4RF<2 THL^qg#¦^H+7 U{M"{StW4"M* \&endstream endobj 1273 0 obj << /Filter /FlateDecode /Length 57 >> stream x36P0P040F@B!!Pa\.*jcJ Yʕ endstream endobj 1274 0 obj << /Filter /FlateDecode /Length 184 >> stream xUPA! _AfO.Y3Z =w+'ǷO2 EKyȘx1;̬䓋܊.̜\VyGfCE|Ոճhw?U7^2boҪ¨ft|̯]M> stream xMS[!^`6"<=ٯUh*DKv/lfo%%c~Ic:ap^I62}JUƌWg._ tEKGǕpN*\J%\q$k⤊R1IK2HPAQaVfrʂѻ6E~ydfO"QlέXāNн.E&$5D1}1̈́K|[VBgN0a\xӜZ)uSl|MzgeRy\zĐsGٜ"]ƞx9{TKERΓm|gNe&&&wz.Cendstream endobj 1276 0 obj << /Filter /FlateDecode /Length 88 >> stream xe;0CwN *> i)Z.lěms p!@IFͶ ^zendstream endobj 1277 0 obj << /Filter /FlateDecode /Length 364 >> stream xMS[n! sVA3U-]b8 ^^_bWvuGJ2?W1^^}wMi[O1i"AVlW.Y!6ԁW!fWA"뾁# >i<pgaƻ(A-E(iȧUOi,] &+&w;UbUցh97TfF2,66lD9lT=t/*}C٪vѠ\fqG?9W1caB$̇;TfY 5_y΄㠽xXl%7B8lt4 0z(L2H8om%r^#ֱH+endstream endobj 1278 0 obj << /Filter /FlateDecode /Length 28 >> stream x36P0C.=CHendstream endobj 1279 0 obj << /Filter /FlateDecode /Length 153 >> stream xeP[!ኜǦ_6&A Vi(ry+~6o.p8K-UNJY 6GFr0)L=*,Y6;>$eۢ%_y ֻÛZټ,p >Gendstream endobj 1280 0 obj << /Filter /FlateDecode /Length 389 >> stream xMSIn1@"(1g4f(yCm|ځ1,r[2S;oi,ILkf2W,|cB[NͲ3jQIß>qVz1*ȘCIϨ):Qlj49ϩ\z&M/еPf ի>`HTj3 / =ձ RQL-'"}(@x:}X" z@jU>1vWZw>Ёp{O#xG/f :Nws.XuD`\$b!ĕ%uo='1DF5W^`)ݬ@ 6N>XbS5 obؾendstream endobj 1281 0 obj << /Filter /FlateDecode /Length 288 >> stream xMRKC1ۿS7y:jz`VU%j;$iy{Z(8]_ϐR7x.:ƽ*;; дop"E1YVEڮte ,d\d80F,hs:sլΒ\76xTmE*g{V=O.kȒ4E&`\w$ Rcr,(M)u5fඓHz]g}>OZ.46y2n5ζ,n^܃Es+nl}㞊-Y>:endstream endobj 1282 0 obj << /Filter /FlateDecode /Length 202 >> stream xeQI1 (A_S Hh4ݸMtG:[׆ԻF" gMo0"E?30iRc$xģ vGq`<5x TyMjSpͳXdݰLj&R=z1c0KajNJӞ>nJTX* l~L-(/yY]endstream endobj 1283 0 obj << /Filter /FlateDecode /Length 86 >> stream xM; C"'LBU'ڀ*bck`EO"\H> stream xMQA1@WɼgV{ Jn6uf碾t_nC;~7MѬ^m*6Xs6jNKbJ6Z,Ђ `TQZ[)s..c a1%uփ x?CTy8bj(43̙D'%P*-&?ve%g x UfJٍrV<-> /Font << /R784 623 0 R >> >> /Subtype /Form /Type /XObject /Length 92288 >> stream x̽M,Kr,K`@vi`E8OEg=nh>?淬2#<-ës/>/G~p^%>a5r/!W~?׿|?-}Wp?O*÷8)'%ǿ/??O?B?2ʩ>m]x G?1i}}d,?2j1>҇)z|fݟgվ>oYƓWn%zܞJ|;<0o/gnR ^>s6Frx}u#Ǐײ~dLm{!ߔv[|z[}l*OZ~cxsٍU]oMcB~nɚN}s{1>˷{ ܝ{[~6ss9|ns?[VU_?7?ZPu)7,yL:^8H8Խj mzOcU|5tsF7m|G#?`?`X?<jL۪yŜ==i1"-5_q㫶FdL//+y)?~m*>{ ތ o˿Xّ-"=}q 275G- ^:%.^ri =,g KcM;,X^v0Xu50Z:>b,nFcr5ZWwߔllKnxi9#PXv"W=,vxeqw+-Z4ji9֚6޾ÇҲa#n8hZhTKr5mv0Xv{% ,Wء԰i)t`+n#}?!!9®ǟoi# 0T]J17R9wW}O?J884^U#9ob1_%Oz̓> fy(F0#qB`uL7nc> 0AU'D K\g2bYMɄ3>#2Jb1h%cMV|VWiEy5,4e>!jX}DcyXd):*ˆUB&Obev XYL=I\2f /H5~a(29!|,=̻&ȑ^ `1OoX,^yǾx/$*Вl 1'^))!7f}E>**س~ ,/˼Q+G52GW"!0X 1,E}y7$8ʹ<=`bIGZ޷mDOIzd<RKI%%+iE51 6x 7%yFdzƭ5EX,&מw6wn}5eX.3F2%d̉begj>_{lp%sm(.g]Bh0:Y#Oۡ~Rj.Wi9*+nL&N ,/Koh-c3iy rBh18c{/m!xԸUȧ>a>RsFXGi5 \؟髷 lXI֞"{o~AE¡yăcg{ R/܆\RYQQ$WeHxb1u 46qu|^>k*Iغ naƄ|F[-}pBg%1'tD[CsK#Nf́QAs@i#|nZ ^zWBrY 3z,(ͤFр '㈬e i%^5.wpwoGLvwP!D$ %O|܍ѰiT>V]b12ԋ2iZ V:z b0UV}y w+mb1Գ&yoYao$6/]=Lk]&!H*Aw Nsb0UzT{銱˩qD!Me.Cb02ԫ|AAGՓHiNunX,^yx5(1>triskǦM! NwRB.T *qDCp0G -C/K 4aizfh&44;rizjlO$p"fnM,>nr+wZymAH  s1!5S1 8AO$X"Z ^z\$-0GtESW51q] AW[pLp]OC15<> %ޒn*__IFO1!-lGd :n(=1iƑ /K=hd.b$q,s*1ҷQ8gvb`xYG')_MIgfFv|5'&+% /Kj9.H5 If0erPq%n|^RKN֧b15d/ bEtOVǹۆbя~{z~WYnƱd|/ ,/K_f"wُ=yNؼ׆¤p$髧H<7h%Ăz"8y%e%_UPd.zˠ%.)X,^~eG*/V1Bu7*>z \'n5X|,>g^W"uX,^~":٪>@_5.z) CW1WN ,/K_SJHO,A0/XhIz:;!|,y.QyЏhE>71cP<9˰CUޓy`2\`VGo~,N|' {Ym<;r:Ce Z@=F'%sj}[B9T"hZ ^~S@./~^\MU j Џځ©[Fw /By:$㥉8 Me[YC"WX4O' BK>Ae0&|"|zXR4i8\X,^~8ѿg;uZ( n ]r/ Ԁ$%JT?!X:cǥD(1opѢ1Eqt ])PScW\4u&be' ~D:z71$ԌB##}`h1zYE/eN4_"0X ,)]Ph%d@@u / C2ycGUƣ D+|k'&˲=Rw:}heD6O6(I~Uqq2 u8_:k -:YRXY'6f$h/XM./82rHUk&oJ$b1hkjT/U2HãΨD,D/D1M'X3 / FȖ @dGH(+-oM(B X:Y)FA<=l ү*Aв]OUi W|DSNýbJEoK=HaŞ!poa-,|eplEX/=9~ݡr "cGHZ<է^G@ E 7N{Ob0jy(##}RLH)NEeG"IT9v`)u/' eޡFk(D!![r8!k),,UbGƾj^^ w_ zy%h:ָ͐{@`0XMC{QBLX O>>jbїQEp#jSfInS4[6@u6gP>@=zkRhC'UZhx~\bpN\ i b1 c={B;'1KT1=:T/ߩ[X,Ɨv.vv%"ˁcu.bE B'}mPZd:{M[c=uCR=1X ,R ڕxoG"*f%)B7亖4X,^+"'^_{ _ ; '3̔e%J O,˽i/uFٶ[ȏl[Ji-%?!jP꺎|LJg0[91P+.7SjmC5nx ck4:;څw?̿ySܰfDWbek?Rc>\F,E?ͥ*Ggga`xYbpZZ:"C!ZV(0X Jou;I^G?陯h+GRQXia`xY!9'suRs,C熡`eg=usxnM:t,-S*yy}ϫL1gyA`1XUy|=N) M;}b`xS2<3)13r,J, -C/K?":ZHK.UCQ?Cd宝nQ|i/z*+{L 'K".G줢eCE79/z"] X:Y]\;S^GCǑ5LE.LׅѫКU !@&}y: o'P,kΧ[Ybh1zYE u퉩>kR1# , ,/KA[CLuD=m8- [@ 'U҉ 6'j?P015R Я8g=Բ\@wX,^~yi0֍GWy$/!gs~}?Bd7.ry?YQ]>Z ^~ӘS/&~U!cy)}ND]ױO(&=@L۵",-']3E!/kl@  og8=5%Q%{ol<,LY&$)֢$BD+uRrÇ~b`xYzuߪC?hbA:!Ka; nX,F yY>#ZU:%~|r .NL^}D^O^Gs}>b13u u%}Ӹ]CeQ˲*Yǂw<h.+$6@#4n_Z ^~G1NK\G?7  F<;~ O&H"2<89Tܕ,L) Y_4jA7XFtbh1zYY '7ګmPɞX >|Etڛ2ޡԑ"*-*I&exvn<-}|*D5R٣ha`x#b! 0qX]%FRN= 0},zdhuRG:5Sa>߰qkO5!plK;L_GB!e^ />ACό* dGY}Z 1Xg@jQ3=ZM}gNYjl!c7Er"TJUWΉV]` t2ܥCWm1\#-M:eX`e ύ-97hi0X ,bURaoqճ)熉/z]۪x6"b5 @c7[A;)s>×ByX0aAǐ>_I] h| >UO};"ck[>5Yb72 eJ;k.uy.VDcbɒ)YڑWJTg Ac0X }Ed\ yjSƕ5Tg >6)r}M~0m1~ c>ݢ z# ;be g -%f )3m6t]aD'o7Enq躉,"Z2!!"CGtV#;t6UGM`e w{8|py>%%15'!\=>g?W~pB%7މW.m&)&& /K8JZx{ WUd^|$IH鍊jms?7rبCӠgA7E#h Dq֘ &!ܜfkkEkآ ߄`!pljyRM;V 1z^來x dmHoEjؤ+Je̳-`Äe, ,9}nZŊü[<4)x-v!ZlnZ ~l{&4N8 | ^z\-Ͳkq!gRxb`xYc=3nbE:5 xz c&wgRTӢY ԕ8[\\Km\¶lU$M '˶1`Ҋ<7ЕU܆#'JHwA`u):喽fq{cax HZ(%GZ[lfGp<^!C#剡`e '="7f:BTDf,"+/,6#.:0qՋF[kS0K0GzqUCIR~&A@cKO}@gah1zY]ekn_]E8hʽM| bе}[lR4#WhY0/,,[-9VT*ZH>s):\1!4lcfԧk6ڣ V'UTCz(MkN}O(<9\ n>b0;!vǘR "`e=IozIK4G1Z ^:÷pAd-B,,BFe Uw2mf6WتXqNo4L9RgNg.,ێ3Q23ZBb02C oW'Tm~F]GLbN3C/KXk6B-;%k2<ipTN$ߢZQ ;!S H xae wkgռE#a]=녖Q&Npxbe 7,Kc{$Аv<+E#beKs*O|pSO qBHL= |,ݠao-@u>@ eCM(N;9~pEލ9 gƅqLaω.[;B_፫ڗV7(0X ,ᦡ9;l|.xc[VYָE B'C7k[DxO6U&Sz0X ,ǕR"h4y pC+8b19G8![mFXYz Qѧsb1nǻ$$!pIs8%K!E.z{fT}ױWq; NnqHy=kp+yR-b1dVӆ𷌮k dB!e8NL^;" U` "YR y bgAKfPR oQm(+$ʽ!{, ,/KReZPt|BQU٩@\X,^pG[Tvဌs* ?6粉bem~Z"PvL?wElY\sBh tXErb<^2:ߎk CΨK\E!_T70mYxY]rߢUG@ ˪s%nj6Bd6ԷbrtǣwsIȮ;OX`>TqW ΐbX,F,e*j/_p=j㪃X: b1_ *[ywӰ2PFsb`x/'+o"n͍~bg!h9O[N^I=~-xbP0vA`u2P+:N4wO!jXQe'W_pvgF8}@ru D6rܾ@Du)~vJU3  r- 2]o6cL!DiT g'^ 4$ix5U9ql¤:. /8|s WXCYr&Œv^CJ*85PLhNL\U4'&hyaz,l!ZM 3?!jߐ;w߅$6h37H&>܅@g| %v1 ,Qĝ/YcŒzV2[;`O@)o'1ԫN*j2b1_h,_K^0'>$xG"T.XM]xQ3㯚;㯉`e9Pά6w DV̧M)|b1_ TY7QjTD*'ea;쵋;wgتGLYA t=Bi4,˹͒R䉁be@C^b<_5Ҥ+1 utN~xY'g_:΂:s[fJb12_ z4RT'֮2ERړ0BdG{u>vIi,@5XҽN2!g UKҭ%foaL- h֑CVY܃`y- ,통heDו_ /utij0xɫ5-գ"ckbHjU⹒~ӈum[.K4rx1u#i7 g"!O' eQSr } ,/[ͧ 217~%P0X Pmp(Q`Ju#ea`x/Y-N 4h^$s"`fi_7Yy6h$/|@h18Yޫ sV4BEuQ6 ymX,^ +Y3/zqݸ,+su[JwAJ<|Ԯh%vd'zҨ4~'jLz'%+|r[A]hB/O?ԕ{Ey.㞴08,Y? n2. kKNxx`xs$iyA&67Mo obscw6&Zzh[xc 81Y&l 5X1H;MZ`hsJ&7ZfǛÛ=ﱂ"[לHz'?ۓ=;7N!{{m.to6s] j6ZtǛÛ&ɥxݧc 8wY ؝v!}7ޚ%&&;,HghJ/vjEB ˲Ws^%F=8Fm'0E66! 0.цăB 0rOۧi%S⍨uN+=Td - ,/K?{gA0|̰%=ԗy9nr7r`] %f&YB&&^[Yo! _;$ ?Y> x 'PnΎug HÓtC.Ev g'Jv1}`.Ḿw2U΢ kB?.8cѶl`v `]@tjKMḾw"*V Mj%=U~ObeWI?6cN*}7ۨrC.NFCRɻ#MḾwd!ޅIu"&&;k)s'^QbXWp%nLH9ވ F=h <~,F^.HO܈YX,;DqU- +;G7Ck|[ϛmB! VrWOb1P@")6%=/*׋u|5%;4Z4RMH8o*DWCeP$8Q~b15T~+ t SGd_iN  m?`XN0^XؗQ//b12_ W}ڛ_=OOK B'>hV,x7{]6( ?1 HzAȥ<`f| М n"0X;RH+NnJ(3nvd=o.,N6"\hF6p =1 )7qꙊĆ+R|;[jVr -i_ѢMu+u <E!0uǾ 0 h{Z\ Yꨢb12_`Rȷva/aY2v͖`e.}>Uݏ$T]$ڽBz? .6.NgV2oGpΡC -C/?Qg\/ e^VrA/EɁ`C -C/=U<@H OA.^u ~T* =?TP숲!{,һC՜ ^=AX_u0,c@\a`2-$z(,BB~f_`e"TMO-G0xBb1_@0i'gu-M= ~UTkBdwK|}L(%Z 1 2gjH#\1*L)r ~EOjT>1vXrsh%՗0M ,/2g(C1q?B_lTj2xT5Db0p;f j$YwpSɊvƭ AE =J#:0PKC~<>O_ 'k`/Oit3DE"D5 ca=zɠN,skGF T##`EV06 hyɞUl%&#džb"!/>ݻ&җ~헦UGU bOfÞVԧ{b1_iCGƿkL,g4&vpڟ07fKxb%j3IzRg{B`8\Z" OLؕzah1z/4h߾ sby 4 31X HlUV+{@U[vn,N~ae$n^z}-!r3-fz/ (xmih,B`1f]%^wя)"NUĴ>MŠMȬcV;,#ho "v){\&D Qa9B3\SKM94&%1%8HU*JMژ0i /K#զ_!^L 9~&'8! BdhI돌*;Et,ŨTT?Rń31X ,xYVޓ$+rC;DyImVͨu۩~W5= eY) [ 8% heGv7ˎ^3aC { @ciuk>m+@5=Ϻ  - 3PSF/bxX!jXZQSJCѓ}Uj#!,NUF8cuڋ!WD" 6XR{c5& 3*Cz!:h6 ,/KWg˜g9\YikU"ϵoKAܚCmܙBUQxS1ƯH >Oc:U =@da`xYbMr$pV;UNXVd M ,/Cr魡;֨+Gj"+" @h0:YVASըWhe=kZ xƇBlـgQjUĜ~Xe_g96iHd- -C/Ka$;o+:CCbb1L*cly8bK#lb`vW-MQ7-@G&q|&S!ƝVFn](z2˘s;1azw-<%ՠ^h^K G3!*u@Ȣm]Ц[:ڵ@{Gޣ4X,^zN{d1ccom~ NO5YWE=i~`}ҥ:,2 6T$"S_!_&/EQ>y)P8[~KM2Ts?1X ,~(cݰR|}2v`4㕳oӒk*N!7 ,㎩AH#VN cyf/& ~h6VO"2ASZv"%y^)o:Gh#"2xŨj-|fDUtV$ +(Uk9 )k]xX%)3XcL=<݄qb`xYbiײAEȁzpxK)뱀GJ -5bQDĦnIX >FL] *KXsiBRk Y4e7 s$9ԕx\kf2X,^ؘ ECKχ~OO ,/K,_B9G&kw ' xCh{3MNW騮{%V}x~,NELPj(hɰa`xYbpNrdA,NkĘeb12ĢCZF}樓OdGti0X ,1!h~Wcys1??ɕ|yq4~H *יKW(NhdKh%a`xYꅽQᔟ8~ ߠ Bvϯ_[^վN$e 'a}$n~ڜys>r/&, ,򇻩A`Rԑ =@lb`xY"M7.Kt&h䱈lX0/CRRG׭ZPϼҚFE*5T %4 {gt)0ڱ@B"Qh31 ,†IމSTl-(/|Z:%펺pZ273/nqғ{zCC|nZ ^y8Fw3Ob< gCC S9ϗPoz$cy]SWhbeW#|zuH a4 V/Jt̊1 bJŸ~d)3峮ȗPO8u#wN ,/K=QZݳKQՎ@0 X:YE}Kx7HzrM=ɞXoB`𱼫VM+0Fg㎶5{-dR6}KH񕤱5=v> Sb12ԛq~'@Ui! .ݰ3R8+zCiuE/2'D K\,zzu q]LJ"h:k4X,^zE(eC=oA(*z߅bewMj痮zZH]O>B QEGU;"p3qy1i^D^Ȕ;c S@Z3g>?(7 ,/K=n\¼22c gah1zYZC#xVrEs0SK*@j$L-`!psueS{@el,I^:zDλO8@ʷEP'), Q`<(x u-YD(%P `e'Lzɝ<xY':@c9JU-ԑJB$Em QEyw%2 ICZŅ)n*R۟QV$E5 NVϳ%Ki\,!d3,vջS5;R>(7AQQ#y[Msr}%J]T2/, ,/Cq#I"W~Y5,B*GDвAbi{޳uJ.Tu'p=IAQP``xYZ|+KuxR2pUЁ:1XL{dGgk7MKiP\XнObh1zYRJsGᬜ̥|Tc%dJBsnB̻Ϸd Gy6Ϸ2!Gb12ԣW5"gbAZp=# P|AΛ*Q -.ղHj1mfF=#-[Qo(XX:Nd w)qAziH?# >1%crt- ,/˾ȴ&\D;x $"rvхbe'<| TOXfPiԂY~jBʡl#2uǚp/2J JEL˲ϸ>2 wY|{JԹ:51X ,[c?=r~vHKI҇bDft;. ,/K`bl7;pVjz X SsYY[z%T)`&R6"Jb0t9qʑ*TC˲O#CMFĔC -C/K:|gcqX[ [*cgA`1׎ɟ#8]WQ:%1 ,ѥ.bgDCH" .,e9Y_;W_F4 0/ -C/KiYV gWLЫ2/L&DZmԒNeu:"į^G⎽|%nŷcկie#2l!S& Aǒ&0Y'CÜ^3gF̴le'Nܬ839fհY:pA`1Xp29hc`eUTz 'j#_:ա E rgdƃOsR`-vf^[xejE#uP1dbڃLL>Ablh'PCX !,3.w#4깅jrSmh&%ۉ[lE8u!kоYba 0PBbq[u۫Oc}Z8,}b1|pM;7*GRmFb1ACY|p H9GI:VR\7UF2y'݄E:ʜ)nFϞ)0'A >nb_Y_.ÜĥWVyaVyaf:<- ʽYLJ` tt+R'"Źe,+n^nSdo6YL ,aL- Y^~V2if01X aQQ2M(=pVZ/ ,/7|\BVk/ [XU%5Ӊ[B`YX5\lmrA(d@ -C/˷ܧ"’K$$pN ,/K#YuoȖyR^݂\oa¼0률-MI=t,[W::/ 'K6] TzTmR_ && !z̽( 3;Wpdb1Ev;!Yyb 1tMbVߢp>'y{d>NM%Qypl<=/墖`a(e "rE <|gAE"XDU#!S+Ԃx&%_]Y1?<C˲mH$Nk.l5NS <18K] e4")Sh#]@h18YeC;|~gT٬yVk^<5/LfU``O!O{)VdTl1=1=1sn];ꄼSr.w٩aG~aW9tyt{*N'ư'杽߷V:}^ ;ļo^$鎽v1yg%={-c"{b {bkkdޫaǞļGқr/ ²f" ;6ƾK4z:ء'Čygdy}{Mb==B_zԳwb6N^dy|% @UO„_xYM|qqTu1OࣣS3,>bGÝwړ乑ϒ.ߑoxa 8{?<Ǿ:z'杽W|ث(cǞÞwM؏>$" K~B y@޸U㮕av!(^C<*r\D/!*NرǨxbٗc\y/grG}y7I<6C{$mOaO;hTľt/߱'fc?1c^!ļGQ5X~̾Q/(EDP~B`[_R|ߒ"=wI,]<$Y6},>|ICC$ocX<1Okˍ=hO?7x{F^u&;ļѰx+~?Wc>$!ҁ}?c/b)tTSؓ'd#O;z;Mq'p'|W!T O#TUejK1~B`u2C± T9R( Tr>!X:Yށ^3 VV-˭ØqwB`8Yzc[$JY-wU) b0()wk=bD{¥4="X,^zuu];\B=OxxVJKcj% RG&]B]/Ʊ GPtYwbh1zYꅽyKw\BuTzo()ݵϺ]A\6hAG霍b`: jUVM&xNxPB:0 ,uG]xzB;& V Ɯ]2-NxFYxCXƥԥb1xDz")"䀆(V3|B`0X]$\,?x rn=!9 ZX,}U7Ϲ,ZS`UQSZ4f0'Q'K\~ċCfTiRJ'5X|,q{-/!xg)tD>A5X|,qEYӮ+{I٘ީT^X >8ڋգx+J^m,1ɺԑ21X uq'n^?,>1-aQe _wPO[?n1# .~,h}Jx -C/K=k{Ks/[%yVIlG B'KPd3w :_KRAnua0u zvLk+O%)qX,}ޖ\0<-9ʪO -C/C=kTyĽZ%xDOQgB`8YF~cF>6D]AX,6cv粎kD%†xԉ)kޜt80;eW"R 9-b1UQImBVrmf3c+ٕxBh 1I\Fw?-RC8@+RsE 2Gۖ*sX~V\=%҆ɳRO(y˪KW4J$. ϥ@' RI|,bX3!h%W]U"G)91XL%5w-٣,.*´1b12Գycz ].U Y6Dՠb0p5xԡ0#ui0X ,"]Y{@+-[Ԏh00Ӳa>OzC@xcD!U DP.<9p<__^W?ܿ*^JW 01q3m׎>Lg[+dHkpwi0t3JK'7=t~32 9 IkgԒ$]ՙ,HGwb`xճi8v]G?i혷SDҮbɒciȫm|^b![Vۨ0X ,}lc?I]GB)7_<r@hK|z:Mn``8[,;k(۩4G= dGQEǐGH-9"*FAxj&eOIuܳȳ;wuSa!0XH>4.-ў4byBbre!bj&'haɒzƥrO^GaR)0cJbh1zYh$1vO+}e fNjcgfb`xȐ<XLJĺ)zR-m9XNLkY:,<̠d( bɒOi ɫz|^1L Α, ,/K2]5reuXѓ`赱=[~+qk(=Ȃ= 0},TN*{s?0aҞţp͚..QUEg9@c.._<ZHVo foOX{?HCJ\?OYGt4iq"* QEǒyܩ^: _$ Gl0PL|,yiNC_ȼh&ݻY*ZJk70KE/LJEWPߐM QP.,N|E[n>},*5E򪳊YEzat/NKφ5Q;O/ ٗ}{T?~!K<#cC{bh1zY>kxAao-<J8"cɣUSRFOLculUR!B8! |,.R)f|F1S&)y%wvb`x=D uzR(5fI&5,Oz\HhZ7R0B[c<#. ,}mI%{r!b+l$j2y>9 @dWd^ՃF^kGv/ ,/Kkh,AOJ%R"<Q'C>o\''O:A+ID7O&XStr#:Yފ&J bZ0um]&^~Ҍ='[/+sN`B敊9gb`xYEc9= ~DEMjb'Wu2#jwE51Ff]F\mĚ-pGֳZ:HfTOȩ%b`xY#dB8PX,^>cɑUy"B:Tn:be]j;ιr526/Ԧ's |` t}K"ǽp|EN Z ^~VZ3v~Lr3Voˎ]OD_AA?jQ.%ҧëbexV~F/ 4JX:%ϫ /KH{cwH ZZ ^=ZYT/>PO B΋䧁b1dW/#yh$vWBBa`xYAd|}YTd)pK׫%u&xU!ﷹ` 0X ,ί-Nk in\> _Q n1 μCsw̼Bcޡ%8s{C]F2)eǐog[ {HǑZY!Aa`xY#,_\Go'MMKg嫉`eg\x=7tmiC" qCҗ\L]ccߝ^4PTda$lrP-xC?b=Sq ^Q6^H%yB`8^/= ˚_Œ`",RR/=1X ,9G# sk%sk0X ,ze5ruemr-90X ,}=һOlPژ3/nr ik{L˂LK^OqTy0RC-Q iхpy!眐"X-ƒ31X ,2aşWѸ~Ӓ(/jV)/,N|Aت*uhZO -C/K`{rƢy7Ƒ-B<0 ۯJя.YMeYҬ iV\%Y+;[+}u38!)|AjT" %QMswԮR0AV̲AS/]HQo( )Xҹ w3o( J]|,-P\cc`xyz]W-׎?L4$S7 -C/O~iq\>W]Q=T }<NJx _4j*5f)TeX<^~F_f?^%ܵIXR ӯ oesXp,)r0J/A3L&)nQӸ!eWI|?.^!62C1ycIo c߭_PT X;Yd]0x Π2㹘ODf &[2g}u22&XW7T띒h!a$*u>=!QܹvIu~cYDlzd0/ESB ,/OEI_ ӖX2DUX>d\#fhfF#CQPS&e"1 4 1x <,.}Oo4ܪz~ ΢l8AX<&cČw6}$ MYyy*]'?Zd47u,ܓQ]@Yyy*SnUc')7TTʑi$3Nx`7^u5'ZYo.Е@@W_ϋzσ^gA(8ia`xy&G)E{}Ce`>^W~X<^J2qU~q^0Tz2g*"E4Sr%V|"2Gm\zw<)zC#YVoDi"b`;Mg0O&RuZ>xfCbQXwƤA22:V7T*K*1rTzda~=(ɀ񄯁zIC?7D O$#37D%"2&ATms?2Ⱥkx:0L:BG&2͛'ց8uL-P"NȠV?[m7�1x k.)UmK@&ẻ>E׌& + Ĝ<0@ ia"YFХ)v 0 ,/Oxq28y4Tb`xy*Tn3B[Q|JU5BYEQGCY&sOdfDOsv3 ,/OEG5jI U G-uYrCs<($ɲg[lpbXkAJ_Ay moO])[x1{צ| #ѥU C&YN=]z@zaHHJO"({EnW'&QG,wܳzFZS-GRVT*úKM*p` %8/!KfCKߒ~CX[^^$2'`\qZ>,IG 4X<^p([|Fu$==M2癠r``xyK|oG/2QyƗX<&g42S7#f be+!rV Њfûg:8)ѫ&H`HUDSZH=*X<^0DSG.T3'Ɵ**xmab`x̒tӪn)+ԂsT_g mr8"~k9xOr\]?ݪCu{EQ p PA-Im= l 0~`ɳmZ&jǁliZ/'Crc yU/Q0&Ukԁi:ng^)ty+Z(.Wwlc kiC+1JȌ*P,J;g W._|dMV#T HfX<^oá73Y_t%Y *601x -l6#3&pE'728(qClNy=Gt$4p@@b Qqza=J&jp1<zq%I16)Q+t,fdE1GO^h{0^/ZdsřVEhKco3ȤGa=Tn7#2B/>KɪN3LyNU5-&/r8|'ꩍ'j 7<#'$*rC1t:yL n ?IT~j@CBW/N{g@2{(`nFp0x <@弍pɶM"|>+X<^ 4";4 ɢ$%ev '\5'sH*z/w ?+T3rj0NԗF,KXP! :S;0x ^԰1bxcO]GB+[ dds1 /"=;bhȼ 24Q$q``xy B^ҵ9S/?)UX%3:X<^; ^G_kqDA.(1reتNq [^D2̴`!B!`quԡ!%z'ZQ?e"9- bWm/htujRLx1ˁ1/Oe5Ur^HbC*^sZLY׹1xL*uAM)qE3H>*'e,g=L"2k'7 >N. fē`*K‡82Ǵ𠮭EqBUj:H~; ,/OhY"]dtuZFfdE4Pm%ـvs+Y>%[IGD؜ ilNV%Yȥ]H>iOYCQv`*ULO^&&wu|}#Mn(7 XT}2.OR 'ϽiySeLU,<ցZ iA8;&$o]vshn?јUt .d(V-5*dQlFY ֧(T# <|{.S 7fRos!uYBE'ױo;y%,$0p+GR ?`'9dX1;Gк2nhSK7D!4 'Ĭ5Cq9?䳑ב/>{``:{1[;R+g&VsuBKjC-qxBD$9zsuubbN|cu'¼Q!R2iX<&?|giϟ8˨1-XL| A1-qH"hIgOho5ިІsa`xy 3#$w^FWdt֢6;Py@`qxX:n-bn1=<źe31Q8P:XU7k>0,wUM/"ID5V7HfFǑϢfP(/dh?0xx s $Ԫy'[dHkLZfiύ_?^_MB]b,*k }]4{#ǿo8=Jf+k¯%ܣ Hjݾ!%VdQT#tZbX>y95hu =+fi疄r}  AOlEB GRԤȞX<^~$:/CP!P}@hq8yϵT|Bh6rl,}#{-|H558eԫH:X<^J5FL1&f%08 < YN^GU_ М mT]oc cc^4Uk։vZ ThSRP󴕉h7&YRjcSRJUkK'̽>~,&BK5x|1aLU..\DVn "Co7 2% k$)1驃/egQzVԚd5Z愨#Of~&s qɟꔍ S:hHg!F6{y„)h!{gGnX<^zƸQYd~V/ Qb |<\^PUVn'q u0M?0z7^z㐴t>B%:S"18 p~]71 W:=fU_K/;(,l,W&I}{z)8m刊Hi0x ޕ7Yp^W#R?ߍcgT tvj lʑ"I7,c&}fp^sN\;"0)똧:F?%Yey$'`m;$RܪM_ U-MoYn ,S2eʸgw/2oPh=Ǩ'G_vVu vEjhNB[ ӲBjN)(u?|!pds=S.v废'?$۱$.Qrm뷇Wd3J>Qfo ,/O=!K+ $? g§cVv)M?)HY! _Hp֠cUK >~ z)d2> 39},|u4,: b Ѱ8׬]@.rEIif)O# D~/P4)j6بjG]zAhp:y]&'-W,?-2 =pT<0x Hש@ 5ri`C+ j?0_`BBOԫ m:ңnw썀Aő/ѿt8/%_͟޹r$2e_ i/А֖f#C*:Pghp {za@ D/b7}u{6{F3 ƣX.xQp_4x.e%1xujj99T=4CgEL\yB1LK5?þ(/,*cɳH&O]JH, GfnX>~WQ3lBbS@uL |6voc=j~>!Xm;$?XR,$1e BHc#ki9Xjһ: e-J%lG1pj)cIXQ/еgj4+_u'j6}c/]R{:~{ggz{ S0%J_@Lu(*X,vf0r_`yB7`=WE+W0x Hp*!r^Xޞ``a`x/P_w}nWX"͑ tr`:/0Q+dNE: BO|i[kʅO4<*M1LgCmLx:TK{'1УY| Wto̰.iO]2S'q㙤_b>:YzZxVjvpI x96fx򍽋lt'2p(vx/0ҭ/ŕ7|aF( H~cџ}Mk2>])/ɿQQ2xu9~A'YVLF<lN'ӿQ̹Al'( ,mY}Yn ,Sce.5?r 6H +43 ͚|1_/Y(ނUIZDDJX ,/$ #94A$+x B`_BD0:1/h0W:!0x Ȫ!Ӯ @cۦ"!E.pahqz/PlGNrvVeJFNW  {/U7DWC$J_lA 'ԑ/ &E+.k_KqZȑvu_1ڦVԐhLpX<^ . k*ݫQ +{&?08L|(EQG% RPߣzX<^ ȅ@o < d. *_0xLy>()Qrc>+st-Q&Ҍڍc-PM:fwE$@U}y->i쁽V<'Ʊ'} s&أ$Ƴ'Ʊ'敽{?Ecߵ=ɱZr'䕻~OmSܓ"¾~qr̵ 3''xǸ7=yy6x OcO+ QS}^Үk+ʈ,tIYyyby^0ZqCԼq9QG!fqZh! I-7PV0!cK 5:PjHι(7'V!:K??[m@?ӌPXο!1xLUUE'(K 'ܿ%KDQ"~33aXΦQ]P=dT`55VGvBÁ0O႞˹e'6)UcBv X<^gKeYPc, Xa<NZO$Qug 99y#2ӞyS|Cǰe6 yXyy٣~Y~G0A˚\Z!,Ƽ<}I%~y 'ItPKn,80J36^Ĵ'Y&Uяz^-Y/1x <0wO1`$wc Fқ AgfQiukQ'[̴"]ZpvSh2hYGoZ^~D k̞SK l٘=Sgcg8w},~-L DU]!prS,tu ehV:b0x <m@{4V rP(^6,1'y:[̵dF28CHU:SJgcSNAC/3Z]ec.?/pѬd #&L1maa+'͢ڐ-) ɖulzo7tK@d]Ekl8hX<^ yDybVQ$_cc^ڑTAo8'"ݖ֩lVf.+rxV YH37n ,lk-2|Yۮz VQF_CՌZ^p='_kxz=Q.3ο1x 2Y^.kF #njGI ?71 22R'\;R-;nm,>X<^hj0!UbQ-#?uSؾI?0|6%ΓoWb͖Y'8L wʬ7Y/jAUؑ5t5"//0r;#_T1/3r2kLֻTd1Ogo} d RR9u Xҷ4ړ&GK ,/OxjGCY[@xmL&PҞ1m{.:i)Z%-1Aƴaqp+Y2ֲRm""S-3!x:ys6u䵸}0Y7 O8,}Ih99.ϓ<N|N(u] G"FӆbG;`r`;Vq}Y"N QGǓP'-y}k,ƛ*660P1: < C"`m(\,s;bBǹ$WvqDU#LPП6 1@L}b`xyaoe%=6G-[#moD2N^W$Փ$ڈIA8ԞnWD:7ZDB]bc`/MWd=.џ8ȫE+L,6'? S6OtюY*y#)g\esRAX8y,=䛖{jAZ 9dC:GlD} "ƙuE.{SVm2Ŀ1x < @O82!Xs@:#C\s~σo(sD=YcT7 < Kyo>[G>&٣S|c`VeAuaXx2-CLƃ3E C/O 0[ޝ!`)} #eXw}tGޢ:ցJm6$Z@c'4J(K:Uu+5(U|( &׍ctVY}~6*F덁c鷯Y'}iױdf h~ab[^QyxC0QO}bhqz9Äֵwgd(V1[].P iA:p ǓO(x*Lqc{a`/+{w֝FրT*ـjZ%GIdGO3YRFȚbY`r`hcRԊًW1: ڸKGE anD!`qTS>ϊ:RY#+\hKa`xy6?tU֔`=LS68YIF?@F=fzbJDm**srs/ǕpիpB$ 0x+C$4χySJ{s-SsŹ1nĘ!]өu웖Ժx#:k7.ׯϊS>PG 6CEAZ^d<vpÄ-F",ƼV)Ke[>&30O=NwOx_]g ֵf=}WfZlM{F7]&3ȁe$$0f90k\;Prw.Y7X+bR㍁cNp>2`:zm>T'D cw_oX#+vڱC&ڀ^{OG>XA4 [fy/xoiv1,R9 ?&''}K8ξ>X2(s`Il }9DB ^ƒ9X3CHx~ӀiBcrig~c`xy?RD5*Q8zC8y͔6]E4]x03GNmX<&ǩMM@ehOmo,uhX ZIe syC`u3Dnc|`לő6$5!+Tx:eoEZK6L1;4~(׿Ȟu"3mC`uG?O<\G>QJG<[ )]5btxdXcmDր,j?9g*c$qZ017>)VN'{ӯTnˈ)i$(V8RU'zx\堢 H 9م0x <{Wї%7LjJ%Plc`x̡ LoQv(W6NY; -C/O?aV /7{/ eFBT=Fh<N|a=l؍Iw.r\)?CcVؘ1x <ʌ ɣ_GzsGQ#ɨWX<&GA kg/y[^qXb !0x }jN9շ*1)h17ja=G?0]rޘJ2elPWΖ!|dP!3.6 sRcO#"kiu fِi:D| ӬR}cʓhI0I;5%IOWb4Sm* "tGչ1Q3jcgc`EFHr.<{FkB!6AecX"~lTB@(LhZ;~cg պ#d/Af?NuP0I4 aF6!p+Gҥ[)ɜX<^zG¼c\-.;au#7sFԧCVL2I[[|ZkBQ;}wAln==Ƙgr}!.k6XC&]!Q'O\%[mγ%ć$geeڞ 3LcSY{y:7ƣl,{ڄ#%#rKJ! cѮ:@EIT(YeFG]GpCSO[?7=B_ԭ  W)׳1xL~69j"[rSPŝh!pBe~=Ɇ&YF&6uØ#av-oZǡR'.gwKgDݲ *;L<QgĬvL0׎>Y6|<5^C\ܚefujUސw_({Ĕ&nu ї%w,֓5r,1r;?4\<񪃞9pBDcKh*ڔ%+U}EbZ$*61 ˼w4H%FwR'@ɞVn\B}jPSyUBpV$'> /*}S~yd$<˓T}毥&l5T%O(Đ$ tj6$ƨ'u=+xZ(auRag\bhqzyy>z'RhѨ`jX<^zR8/:2sCOy@1O:2^;!b-!%Aބ 9>"¬LJI9:G^|i&l`N ,njd>(&ey'Sl /Y0n-y rH2@Jt._X<^Jԯ&?SY4QH Rr "nύcLDd4 DE"}51ˁ㱞ffex濾j 2"e-HC9y5p7,D[h]gX<^NPjTTsg,5841T:[aN~1[N*20ׄ6f m̳!Ul|CEΌZ#@=pq4ɫQ~ L)hvy=d;A`u<8nHo4L$̭U⍁cTdC٢?$QPhu6OTHd߆X<&G'OE+}:@?&k h= 8|<ȓYo9";tȬoc2MG7T ;"l5Gprcژ4K@1œoTwFJ`+ml ,/O?F? ^5rCY47fǿ-LՃLN*|<5dht*ҫ19kabA!2ž>yRdJƹ&YUS"eX<^ GvvyC5ab -=6C~; ,/O *ɾqds @1T2G8#JF&;݂w0T5bfQJ}1L&`u3LgqH[T$qN 0LCp} ?dCPVT!Q)y?u eW1T N(>TIā8[ 9@eE0x ehg?SYP} ʙLl -C/Oej@mqսKbCSFt 1:ߩԿc#X ߒi%ҏG@46;<.vLC7L}Y,qzCe Ɛ Uo'<=vb4uO8b%7@3CSP|:( ĉt 1UpVLA㈤ ڿ);D~ [R"0x < i/*Me_sAQ}5¾4w: h0㝕V]R iѿiHGJΙȻՑ oJW"QGҵ7wb8ˡ\+DB'1-=k s|nXPS*2Cn_jМJoPÓhKi׷ET G]I0x fҮ3֣EIDfkX<^jƗ3)_P?%v1 y5"ԉ ,/O0ԟO9~#,;)@=Y=Kڦd½T.7;7;RY]''M7$ejj76#},Ƽv['Vuc t``x9rSOQt=uRw˽c52_Tuꇎgk0C+)x0Ll5ڈa whʧCxBy8DM~Z^0׻J"eXkn"|!B_Et+錃P#ir_/hW^2F7u.f`=P+g2RHZbl,쉁c:~>tߛ^!$ ^>|Z]'xX^A>o[P(\.7f߈aS_|+V4~q08  )_JXӶa`xf^%kBC:1Z!,K MHz׆LKm<ݮ)$*Wn zwOW2j9d7'˩̹wo'-N-q(ez-2#z  6$ȧi/%4m \dpcje?ǔq$}'ֿ v⠟0fMEs^k5HX<^6yU.z27ZB*Zd"6#nBe/,в9<D1^LQt3X>-h-'I2޹ ,AC#&ז3[ra SF4SS=/ç"$z /gcs@R J蔇I*ݔ) MbCDu|rlkkϖWYO-TɏcV5(|fW[%kośu6+HY(=V;Z #D*!8|Y8>n8e~ZQ>s(}pPx`-_%Ϣp69-;Hh#8y&cxM ')#`]Fi݅_@tf2@тQA 08 ˳IW~b 7g,ƼHݯW~0( c@}Q%W~ư>\J\lǁiǂV`'5*I,C<[w¿1Ů/:Y }M ~TMoPJJ.nUTR5cx˪FϪʯQ{)T27 -C/2t*D&/XXC˄pw!X<^ T{pZ}u&BW\Dkwni y񿐾 ("WkFbM1i>버%Ϝdd@=!2Vh,ƼJ z nzIv(d'QGQüE^U}U+,u~&2(!vV~%:"#"`a`x/09ꞛ71iTVF.u/ X#V;;ͳNMO!"[nBSc/}S73/ѹ#3ZEƁcS\a HZF>" ]^Jjpao`D:+W& R=OQ0x <.^1*pҢ5XeK&qҁ @߱hQԻYJ#H.p1 ,c)ɃzB ,73*〵\I%a-> 0xeX<<2 ww$n嘒fe]zsQO[/hGi=zIqtKP5S6zQM 1%VDOmL3e܍.9כO/:tQ 62-\ oax MTbt&K8l:c)wfxك eE46%*#=0/Oy"@gQNFE@X<^􆤫Iw :{JB &监mћʀdN@if*M˖,ƼtZE=/Sz~j{NvnhግcH = 8KZJ_ߘd OR2Rx?kM~ANȜ1EIik4a?4YGK&s\N՗LnAk{ar`ӟ^S]kAk)V1.AZp,1 ,/GF$(s~?t^F ]{4inLQTPLoo49xM,AFZٶ:k0NtՈdGC1wάyQO *˰<{Ícjx-ubJH%|i O-U\Z% dx>l8^kc˨{7tF;R0>(|`F/1+w8]ǾC#F{o ,c-@uG?O(RL˼Wl ,/O0V7d)RsFG{Tl ,/G_W%'WGa`xyMH<\k i0x <{EҶ菈h!c߁ t llz)C"BFgP!M+*9 ɓ/˔듇h)?r ]3A䫖Xu7@C<,_u6o=oobxj~ ${pyyMzOd.Qu5im@X' }uCf4MMoom߼r7oȻhuoobxrpwF&&vUk~-H;8x摷~y[N r͞771O1cu{>7?e3 YwrG6!O>|$$jze'1reRZdQz#Rj 3n1x OE߾9W~PPSh08 hh:s(mu$0X<^ Tv{)dO`0+k 0 xڪ>Q_'D Gyb jcDU(m ,/2/i/p9&cy7 y0) @OXKPcdtq:;]MVNIe}ɍZ^ T_,MVeDy&5lEKahqzySʰ2`UDa> \ Ab~lB]kzi%Jv H'޷ 3m"kԸ4g~_n0(rp[OUl'\٪O~CxPZqZb.z<&'1 3@;%D-";;LKvʓlZͥ-KeE*HBk'!8 <[1&Yo_Wk! RfrӬcc t!ʨӱY1Ŀ[BM:*ı18 K޶{OOq r: [Fq}`hqz92>FLTe> (g ,/G%E^ge !GQzӡ#)+OrI>AENݳcby>kiBA$+]>r未#S[Gi躗{\l.1re?Kx=Ԉ]4K^kqvچdmL|Rq}O: Vϫ;Qhm=: x:9ZJ-3vkfȟs`Z@q9ilLM`=sB~M0PR^/ RL_ .gmaӳn ZwME'Fn'̉X<^

.PZ_غU-@V1NE4be*^D?o ,/O~MܢNw؏-`GLӔn ,x&4^V[(d_>#{7Q'O~0(5qO$IQBq_TT<\|]&1Jم2 MpqB"6% Uh6󴊸MOf7?0[&}cu_Kx]'%= V2~#`qu9Ub=ݿZPIz`wb`xyU~ڌu[{yMވE<'gߩ?-SB*PG3C{@&: ;jzL\]hCoc06^ ᫼=3f H^lc6q^{ؠW֮Im"9m ,/OKqN?Y9f.ۍc(qSeSFSNK T7d ƴSNZZ0RO#Y\3l_b3l1x <}xuIu5Y;JMvٚQ']Zp#e[_) c|4(_\D/s(zUU"A70xL .=އ8g~S!>6{B'f1ʞ|/J UYLYyyuϚϓZb-}*B?\e_L 30bҡx'MױϬ <@JH: 7Q'Ͻ 3',_GItJZ#,_O%uR~^XH%t ,/O~l׀- 5+m ,/O~2ģϛƲcjh]jEeXlˑq|5RF2PzN,ϼ"94rcOHEk`Rc5x|23YS4 &Omp:yMNj<Ɉ_I2bRH '; ,/oeռ4+*EGc1r_@UGƞvChLD Yy_@"zzttY R*TȖ?C y,ya4^CFf1 k燐rz?A2|!prī /=uV)C![2Z8ˎhgc0 OQ/wg ]8.VL~'Xv-i'%2 ,On O ,/G^K3KGf^C^$8VH!&jhz4 -C/O3ekꔥNF7d Co B1?_~prw`L,NK5ls7'iv#=(8U s=Ew]݌?U/') 7hpUdX?|e2B|w~\joj B:fx}/-p}|gkTsG'dɀi,3_n/)/-'0sD^r3  ]m9N=lAixo6('Ogl 27fS7#2<8KF5{p}ID߃|E_޾u g OD潓l W@@u`1\yW-Zq=LwM%}˥F>)9/==쥳{|(Ya?(s덴]ߋ)y%*M}yBo&hʂEa5F6L ,3{2i( !!$ 0ҧ/~\{>~+\_x 6׻(/ɣWۗX \vTxo.x=Y rlno#//M=Q8Bð S׷_xRaP^}2c<"@j$jځvK]LltC`pz?in;׉=R*z4s/wKxHGԼS{ڱ͡mo{To~8N + 'w7B3IۯGao6xƆT aС9%G+ ;EX(̧v[<\K~vVMmKt[(7nŎ[/i<՛sgBˏ3~] DOvP z@ +63"JEGE(J]'ȒB dHC LY}/K8|&bD`O?-s4綎>0Vl˷_`VffR_м^Hl*+?V $X^g"e^1XTܽa :)ɫe &Pe ۧB>}~;w?J7ʸ{9%<#mC0H2PL{EVT)-f482O{ XQ$um]|̶}}7 Ve`P,AɁ12R 4/؝n)zEY>eJaϟ8%t#Fmi17S~䬹X(i_ O(Sğ_ANy[h&n~'uc]`&VÓ"^7Q Μ2P#9P4F!BF*PИ2l}xcw׽kޟ*[~-@Wnb NrYw{;_lSj9@>tJ_h /nl7; [M OA@v@Ȗ>}h+iE]/۬87TҲzGW,WV-\aAc5n-xZҶ$@n@k*U@H n3j+n/\ƒswF+G]7^ 1CG\&蛑(?H|a$ӓ)ssag$-[6QKKNp@<>([IYWuTs3a7+nx[Y_RCm?xڞm""$E`U%ȠC qF!$#MaŒ@ZcĶ ?z5=}n z5BV7~LV~D8{9nޗH^A"wJ<[T+{T{x @Is(Gp{͢t( T2AfDe OS?#Y V„+*}VT~͓6zڄǯ B3b5J@U\ɠ6qlj//_ȉ!pM 厪wTW>$2̧#ͽ+qM[@tf=i/>v[ԍo]J eYxF7}/1}"pq(n9!I~xjZN]]=kv[xdlC9B26#AXʧN#ްV7ڧ7:Y'*>_ _Tͷ4Ghzu9x{)+=.!9hHĻbz|;Y}hmأ AT Ev4@Ȕ)S8|_} F+}{˛6S ^\n{{rdV p=|M:X~[|Oz8=G)" %{ 8Vew}o;Vg)^YTz &H2Ia4qdV KD:F :kE~󹟨)M{ÝnP- nrg"P!S L}pxcU[<} P_dp@/pt<9x ?x'JHIdS0o]J?%O~vқő9  "Na߭@n^v[vUy'? !*ٌnQfL&mW1~x%o~ 4)1HhJSPM@#PBIdFAOG~ kw޸wIr$d `uD1<"0QZ;!=@A`Fx!N `RPzjYB2VB!k }7 e|hU K/fy3PPы;<9Si *i_`#FxBx>DJ͕sk]=>X8%V:ZjYA2N!Cnv ^4/pضf}w*wa[CB}Gbyr! 5'J6W70p赸ͬ W7}㨼qTJ6Ĉ)hb,-_qw0 uqFAPKi!I N6>p8s lʻtK>FL8*vԤ,{tűƙ^%F(2,Q jw?(Nn4vm1P"8TS# ҧ/vOYǙ}ƭaVay9USzssw_-|ƃn4_~wͫl4ϭ7o=eBC? l1@߸L572_΅N7[6Q\8 :54DBH>8_ٰ\PPʡMI&Ac7tc_?p.Gd[BI7n?R::yIDG(j"@׽&GmW zŘl"PE(! zD(2T C:r@㧕V@fvDzê{^ܪlhfoJk^P*s1oOa1 H ˛S@qLPd(!ڹJ%@ ܟ,sC}hb'g J\6 Ȏ(;_|#+?~]@,5FpJAaF1 y;N.4l+>iI?czODD ȯ $(%HՈKܶntdk*SRt dHK l)p2+23Bؗ3N)-qʪhq[+ %YkpU_wy0Bs 7kg(ǰ3F#fʰHgCa?φ2Cid H[ )XbSn'cL$?ÆXsh+a7a7EL emJ%@m]_`L&G6 vD Rt*dK L)2պYs=[D 2:f:qiV22D|4DJ?/%X;p-3|s$I hҷHQOF{,6@+nK Wq,Dp&3Pf(Sdz}^*Ǘ|ƢNJKpz B^2p%[N+(~ ,yf_ P}_&Eԅ|o2A6$j+\_J T u2FO#I(|2ߑq^ 7EՆ2D{'B]^٪j㻋 lM nn l-dJ|h!N#TTJ3Cj 1ΑAKDG d@}~?4=bR\O8C 2FF{7D&je}UBNߨXd,<`ڒ6lB}p-P-N~>H}ב w༴&N{ƅ@n3\[Y-ֳ.FYc)b1/AzΞ DXHho/Pk\| ]R yf*"# ̑9-Ҋ"yGBhE}EHMR_ 0C*% 6 1B ى ’>=q(!˪jпޯٔZ? b"ȥudX e$$!BVF@lX)Z*9](aѨFR?$2̧#41^_py~(t'vg?8Wf agE_>\87oȮ 1<#n?÷h^)3R35DFK"QIZ M!"\" ҥB8/ӹ_{n\cܜ,&ΛLpӝIzwhH; ,)a{}{EVHHm0+bN^(odQkK5ܞ@Ηt@y)Jtip3'UDĢж5w"P!S,}p8UN@Ϫ kMoQ\҃7Y,o0?gKY0AC=WFS[JÃV ui?bq99r_2':Zre"Haן 璢r,[6>މ݃[(ӳ-UIVd` y9&64MBC P+AZcIհ=L..S;†!3,4#ĤDrf@3@Ȑ(C>N'̨;_#'GeN+;Îfph^;{ԸE8K}A^x$@aޔ`%]`4amv3 #R vE_XPX3e||E!ȠCPQ CHFZJ(dKOo})^M29h*J(+8j &%X܀m#W:%cѮnI yN$ l+\4}q~x[l/:(C?V(YZGj}KBύhax1:7rxR*'n_?џ0sbw]D\n./`d(3X:6=EirѲ!j;0X6WB^5m>T]J(|'mJ;З)뤻NH'd2VOKastľt:ö^WBb ;ڗ]>j͎ r!Vso2 Ϥ+Qj(]anwW͔0 <(:dB$DYUP_rE[_ ϵ-Kmp˪m2W϶Jz0*%8B?J0kKJ=:IӋX튫LGPFΨf"ѣ(Aj$4DHK l)vN}N+d8),~ۉF pX_5`w EO qQ0 d€h#{~_]v6`>$~11ZzjA2RB![ L}p~ghYt>[ X)hX<ܽx9rYit5BR@,Ԭ(uB:mX# oٳ˰M 7"C2$ 4|ZIdFOGn~0~ оP SMjSi\ܰgsvQc9CDe">+p'l-Sm ¨Q!N%C55 a'!Q>]qy f|ޗ1u*bEc*9;wh/v7 o3-4k/""uhJK3͹b:ͮbL ȠCPM +PDIhGA 38<}*Ky͚R^F}xN8Pho0:^yq:H!~>0xPHƖWi{;b )7% ,L -l7C /ѶLud6ܦ b=t ZZjjA"P=5f}J 6 Vr[q]}aSo=Rvr KDg(K0Maz" BQP{/o`z]Dě { -cZ!D,)2V^N_?dgþ{)>u\lXsW$my9.q1|,).(a2 ΎGNJTS75T'1Q0@pMAJ 1d\Ma@ 3 |:t{Vl_'8z{?Ulk<[x {1tYQt!y_by=8 pޟ3EK%?'$Z,,Dbya&.úacСEr q@;T-dwo<9U;D๲qr%] 4r܋H]~VfjnT.߹ YrF Pݾda*(䵥˧9ªh2,#Dan|fL@v@Ȗ)[Ȃs ~MDWBKg ;S6 2?Uы2XJk.~]8I Ņ RF{|}L%Hs}XnC1,@2 1ii#!", hKOW>ͧ.P1)68tOe'd1qlH"*vT<',"knT0oK1bxouavJXNC|nFA$D_R 80F @ _}u|0*yGkn9$v"Xb+x@/Yw8[DMlG:iYaW;FhM⁢=eaX[nL8{n# WzL :H4 2THoG i'==<^~ƣ$KJ=b;QyXݵ cSzQ:DYPK@ZWx1 `{U"S YbR"9P3BD*@\%2Lt=HX&ߣdD^[zS{Ql^#_ K{B4o#tGޘ}(}pyъ]E53sY.ꁓSH1B0=8VĤ&Aʐ(ȇN"d`;]nq[.|v/XbfWc?T~Lzrbx8c0)eёO^7CeE¬]MInxb`@ѱ#`vRi g_m4>Wf>7oW#Kg0K/wMi|1XLKύ5!^ x=q܂?Osso!둛^/h zڟ=J׭}tc};gu}_..0wbư}֙槄/zO\A =_y'eY yho0EMp7bhۋ2 =8MHuJl'xaߙn(!Zŭ_:7MS-F@k `uk8s()}6N7DooF#:r@ud P916b`{F!r"2P#8TT# S(և7v[aa)@kU=8՛/Wժn?{JBwT8#xjX@] Ødv h7c5}3*D\, J d"1!J#55 i()1,}=fmu|]OlyirgCd酀j%:+n^X|雡^!9÷BDP|s\N|: [?QVߓ !faK đ@V0!0|z{E%q\S⛑j{OoQsU-F߉ۿ/tmtLW_`V':bh0`HxX9 0% T\<;_LE#E˨=7#TB jiV"0#OGol[cd]o[iރ,T='3ҡ_.v8j̯l7bwӂӽE~&.DFID5]~b}Fp3 EܝT"8TS R eop5"gƫ]~yh%[6\1+nqxWNke ws[<Z ay6XB_Bj:H .y9j|q8u<(.9t_:}Tl[Νd>/fzZ堭9>'JZҳ2#&9ؤK + b JqqGHJA~V1kX"z`]eth!DBHp!y ^~l+!;!a[jH+tk6K>YXhqcx!H Øh~'Sq j2 DyOAA4PЖ)K8߲:g7~v!=ҹUD QTkص:vM [N ߯M)cMJ(˘J vWǨ{," @ "AjzjA"P!S dt]#V+E]-8 .&0ݬT9qr'bF7F:%A1BÊ2zUD}%=@> 02Wh`JA-yL C }n! _}.Ι;`q^ڻ'،BA}ڣW-zT`/,їy@יz<¥Yy"h(U' ~1+>`% uFѩBpFYP _Oneh{J=1qF$!$ 2$!̧N3~B@cX=}suJew.>7FXU&8v&&,rz0֊0i'n^/|.<>kaZDn&3: C@˸V a K ,}𽽧9οg .{v,dEx[XĺǰX2)hKkĻ;8^7G"%ȁ-wt\9.2  ȠC qF!$#M%@ԇ/N?(S<9MF:bY< ˉG9,je^+͔Ok-d~%V1mA2P{ul]#IdL#}+I¥!N%C55 i()Rv>=Ya|Li/D[zoѮ$w{TnUDuĎ㵳$q]D0405>GLb1BEQ'EEFq)=2y5ݣ$eCHѩFp($!-% 0֧7?l^7?fzc N^۫m)N=K}Al3Pk3ɛIf  ;ZSDh~{-: n͍_i Nw >łt*dH# (2BH۞_7{095UV9kgi!XIs! o-rl"j S֊w/rC}:<ĠC0$"tߛOV>p:;j>l|P \9|b9=}Ng:Uy,~$CdxMU]A<| 5@[cm{p'dܕ[aj\͝aN}9@[ho|rRgWCXrV1@p" E@f4@Ȕ)S8|V^ ͪ<|5F8o+(x_ __(/`(cvB՞H`yIx.fOHB ?q 5F( `)*L%ʠCqF!$"-%@֧7%G-տQIģZzU{-@ -f2j&6LͬD(n.%CQktdzߟ~POn ;}Ke T 8TQ#FQe }xs#;U,]w `Ta?pmlph:q8m<٬q5`DŽQQ@ڮ({2b3G(톟_?v7 ^С9" pFV,'.G}/O C﹜!pv;53/pepuW?lU=_/͚p1춟_5F)5;l%p 1?DȨיDs T!8Q#Pe LԤ?|ڿVY˺vTE|ԁ{"X7۱ : czzT4=cʟ!b+ӯ /%X:Q%"1@ET&"3@nZjjA"P K ,}ciZ>~ N[ևXȩVx0%~:^6ؑfgke]ٲrX' =1CB-@{c/~5PK PTAEK y D/# |asz b>lQx}ܱmvK29=&7eJGwi=gR/=);I?[O$r쨅 d+7P< Dߨ @ڏN?^Vv,6HOlAzqFY„ė&>pGlՃ<(rrjكէ_tT]h4kQҾݙrNjW>_}7e4C ~+\1ThpZ"*B' ZZlF2El"0%A_ɛog\{w<.f9/qbxt Ϸm3yzՖ &,D ]LϤETOܩm_Kym#mɨ iD:)2DBȰJ,Ӝ쓗'|A+9_bw6yie/;\0 JvNheeq [ K-[k&gh#ꏱڮnSz V:j@"J@F$}=z1 ̏3~,}q8%'A. qLwo1LH*`w i 8&W$1fS*.h: $Bq6|2hi!2DB0Su$ZWzKkv(Oҡ~`oQϯ!F0',X^bayĸRxj-/ 4BCP&jl! ȐZ6i%0|8p'9b9&_{|b{| 愜2{=/rv: [¬\ZwF)kINJd5*4_7jxhpU> jSb8+t~ bRA d? $2'F{'ݗf7*+ `xKw_ UݸW+hk 8^;,FXMCƺ*7|* z/:z6 +.,EdСFp(GCPf*cey& Ӧj@o ӼwzrK#L6};VfJ~~|)̓Fs6kGR M݃FS ~)ZQ0B#Lġm2w2A--/9B@) Ȓ(K89uˇ_{ݐǃyiM|U49iXoRҟmߛNa]m}9 X0?U+yqO\njP/=yN~63z"j^>^K O h+2/֐#eP2(̓BeC> FlJv /8\嶴rRMEa¾DVR/Ojy7WM7אf7/0<}L%@_\\0jDL 6`7jcT"2(!$2 O {Np=kx{(d/ޢvмs"{ȇ|)kʉk/ѓcpgmM} qy_UUcF{]V7p]e=&GDѩDpF$" E 47y)R]ϔߏ4S&OY[*ed6z_c?zR%\Vry_>ʗTWGXU  =ZreHj'ap'~ZO^g VU`XbȋKU()hK3@ r^!<<@X>nAī8qæԶo [{Qpc.oY1Ͱ~q.޹ƭc[_wxniqO WU=U(>^5G!B"Rt*dHK l)nFU};| VqO~O^q]/z!a+vob7g%)⼆e;*AiER ' {G 8]@xB(w?!N%C55 a'!Q>]q:ݷM']zt߾~C^ 31[rBݣ_r;]nxxBʨ,(x$ʪ[zԮGuvxYwV9p 2PC)dIHK l)a'WCTWk\{i #6|Ǯw8(ƞr/ ^z] O9{$9BA\XȰY^'z71_蟸:v/]%J nt(!2DB$Fpž~? O42.2f0yD%<{VEdN3}W̛y짖 N@OVh1ٲm?u&2C *>1PBdHC L)2W~,qO>Wؕb½ȽXW0Sʎ\ k7l7v ˑӑ}X/@'\/xCD#AT[!ȠnucquL i$AQV>p:LvDyw6.Rjp^ڿn~}-*pv4e:􎍳0gX)þ]ovvWCzYEMG{!h(*ZʠCPӪIZt2lEaMLk8=&/2>waC>{Z#hcv/Yb&l]*Hl< DA~W(@a ( BdrEybByY~xtRFぎPXPzjYB2VB!k %}8bs=XoB8nvv$|GԲ }8g\CɃ;Nqɶ)z]|͛ =R_ܱ}sqM5p5&T!8Zp)"# 20 Nl|с^pޓ}ap%d]hgbp&ay]*.` LpM}I5[-o+`С8RR#fefo=jPʇhrqF?0O)yoStpWy(;dXH "N&bTC:&<4H@kqj1BGM@Gᡀ" JC-5 i&!RV>pUo;ws}Zxڊ=fv&O?OE?ߍ~LQzf">sxD?,Qq̔AM9XWMyB Ҝ)nm1l7 !J#55@!,IaJ8*"zOx ;+!ߞZ $]ɥz6VA sԊ\}Z2Ћ(ӃgIf|"-1 EA4PȘD>q1V~??.~n |b})THW[3roRcC,! :tdBvư^HW,[=<&ڐkyb-20~d%+Mm/ 0&?҉kvXJn@]0dd $S1$ 4zHeD #n8pw49j@ZhTg:9G&c .P`EA!+ d8xNwTnG[S~3 ~P+ltXMp=S}2O56^}ۥo{v}IoG>El'>AoG_>=޿yvϛN*9}}rdW ާ'>ؼp/uG}Dݥ>Aާ@ 2x@]~?D_uM >x=jzcdv}.rV. #$ܾ1B҂s rvpTZK[^to4UpL#X!bLl ZȠRRFQ̇#NWЮ_p|v!:G' 5=OaP0%NEey oXNVf#QJҊ ͭ`Y0$1 E`_, v>GK>_ 81F BFXIdGAL;8άZ~|Q Ŋ2\9n!gtrt(|s:iT+69B7W#Z`  ;R4g~O~9 @`.wLZ*Ju)xZ1ʿ8K7GnH=kןK@G*IAo%*6r)2.f9axLxUΧϋOb#S:OHu  $4%!MN?8Mi wg0jsl*D]z_Ez󍆚 ,4(>*nJМ׏;]u7)E>a"93FBFXJ hJAUo?kBwHXfDt2WQ VW#ф5wc 4,]Q}HҚ%'ˣ6ݨCnՁBS3crOC`Ϛ 4F ¤c{DjJaAK0orٻ=sw ?hDg+lҧA=8k!u@@4lB>?σ{kpx䵾0zo^cIX^1u3w]NGp5W>H۞?=Y6O:Zf,ݭlBwg<)_|ֳu3ur,jdzN ;AO;hwL5XP?ȗ D”gwk!", dKOoXoټtg ޷({훽nD7s%nz0w<8RP#/ؑ2*>$jN؏ҝŸp8:طi ;{;pc_Ն]܎6?"ZaUW _;OG}qb-WƷ7K @sCȞ   E\c4t%Ӟ?l#l}_jc75cIʳЃÿw3<֋_zAVҙ(}^/3z[4y.ŴؖA#<ݕ2/W0B !AúX zn_ݓs#tĠCPM 3HF ”>}q:0FBos7{^y*_V#S'N``u?hP2FGpWZC!m(l+AG+Et/.RqT)b&0\,#SPO ;HFZJ dKOo[k2x8/͜R?`VPiϡ4>Vqn?PzAe e6?FyD_H TqKJL_mE AޯPJiL:"AV40Ȑ=qz/>~;c-90jGy湉oY~閝GrK;b䠾_'v1.Rl+!w' ^aWpа+P@ Ȑ\&4Ȉ'#n8Pjybw9~ H.9/V 5}iX\ggKqj2{4x$b.XuS)[)5@2~ѱC#tlm2 `,S@O^/+H@IdIA K8v~b;NVIEIMteTh~JE4[Z3%^dXLgɱNMĺ( /JOaHb-I.ql 1"R /8-5qf KcYbkVXN0*.GdQ ayb蟸z_6%jk 2ii# f 4d` C8μw^|Yi?xW0^7T=|JՁ <[>/jb۟AW8b[KMuGNaSڮ׽KC12RBJV`vg_ϵZ7ދ{Cr*9/vOw.r7KՈmՂ\ 1@Erhj%H6N 5z@q^ZʰeCQ eRC dHK l)a㹑u~o7-8 T{n?f cc{e 5ʅ[:f6+j9BPKv:vD JJ{f1Nh` ĠZ \t8-(xוWAݝ%17>pr;V2n/n7S;jv"4߼An_AHs+OV;hh%>J %HX9~a렺`E.50I "8S#Y –,S8>|s\q曡my;MCli}Y E|L_˴ǫe&YU/9X-,]Z流ˆ/]a1m+XZ=DL'+e6APO @+HBIdHBOO7[ gyt{ԩ9/=~H#+aگU[VVkɬSy.T27> V}=_韸USQQ#L4*DX0{@fmBLZjjB2TB\jL[88~f. }ӝcǗ6~iޜ^7Ps!*3w 2G,  wS ~6k+6f#Q41 91. B@X) ЎvE=9##xsu25Y|}3ϯ[!iI+08L5D JD>d/G~K&c 8r Dq^jjA"N!K#W_FM7;t$DB;cv1vNGrHD T#_`|#kuv+̾{>PQ@3H|B%1p>:Aw_n~ u{KND" ve 2 3Aכ]}#T+": C|Ʉ<& 6"'{1PBdHC L)=V9QNp^zT9ymFv}2q5aJl 'ͯǿ9%MZE7~!ʁChrBDl]M2PC)dHSl) Ew? wS+b߆W,n=n_R(dӅ~i {xpA4~RO/,cmY N%C55 i()>q8}m>Jho?jPf<E58bhٟHb^_9ۇVA0뚕{T+Ŭ8@NEʰ @[6"$Ay,F/# _p=f_Oϖl /p^a zS><=b #Y/wmӅ[G7K]>۶)NCnbw0}/o1|//Q 6dĠC q&G3Pf%;e{w{E-l'pKZ~T{?rq61.kj 9D1+ @*fbOխnYG"y1(u-Q=McZ"dBJYq$XX7fzw*NO?ƙLuNc0)1jT9Em->>KR(eҟ!E#G{h"a7U1P"9d H@(ЄP&>pzEV#smZ^T\57ڐIP/ɉcP8K<~^ew:\3RjJ./Q=af.h ~?H뻕Shs3YPM @#HBZŒV>p8ܚވ\[>~YV$ +9@"Oyˣ-~;Y>g㝐 (m &p2>a| r>F~QT&{Ѹfrr\U3WMAvRD$0y #ʞdUaq6={d0U.gXN5:$VTTr[#t4 Z;rW75`n^5ݍ6n3;V$⒇+j E2 z =Pߊwuܚ%P]:?uy{ h-"`L4}Hl+f I8y ,b!{ӰCzWd8Txق|p4,/ 8$p6y}a$I5xF D/|J"pҊsbWi=D ߚx6!YFCq_q-ZjxnK? sÑaG0ll! 9E[.1zG5`|.& TSvQ9d Mt;:Qs=[1 -D;KS .)=lk15a}\3eBLwH"Yr.?f N 0^AX Z06XQXi \1/Lk+$$1ҲZVeKSa^]|رs22`.ϼ+L"({6p~{[ȕMU=ABj52 !,b4 u2Y> 0cu1E Ihbp'u{?JEۇ:+b,b3\-:,3a}Kۣ.J@ـhbGaH2@L<ǀ2o/1yW0w:}A!0aZqk8H1zl2 Hfb`s&g?s3 ?,lٞ샲]z#/r5j5|+"&ը e=%х8MP>w/8A"Z#?x,1xVtVo}ly6qw>P68vy7RQŴ9FС刳 %! oClW+>Vԭ\56 K0C舶Fy@^ؘ-fއ,DDepր|2IibU21tQ|*.nhZ$gWP /wxcqȘ  S ?H-$/y IC6ŹAeVGvmʐ:"Jy CT8 EXs^ÑZ cP7 q{}J>ޜ-M 00[E.K_Ȳ,bSOK+R4A\R"QTbV ,v#M\a7sQ󨶅"A-$U/] !]s> 9|gZOc43O8WYgG- !yYKLkX(gP5[ZKղ94jyxw;.uGH^0v>V2Tb᳨*|r7Y.޳020`ZFQ>-8o-l}GT<)oHyoMOYB\If]|U^7ޝ€ҽQ֕~9ˍݩS)x,g`duξd}T (` 8QV}q̇`* EZ pBإ4|(p.Xi1ye(ҹ>H^GB8~~X,+X݊ל<Lly; kMM+[Ya Hje!5_B!؊JTZa_2E͡gj8A$$p,/ 8)b0VBNsO~s@w@\ "]R )9ٞtPiTlSב,NUPrm?7ɶu0eA !D xnRk!Fe"'Xv}Vcô b0# 1z}ibgeljHt5#4Ȗ MA4}gjȭ^ ۴gUf cxT:$Yz`)g]!ºZ`N%x {y@1k uPBRkA Ĉjs(6FQ2X b.υ 29H+ax j-#*Ca4m6.k~F4`i*,[n7'jZFź]mx'ĞUacFǿ5_.::-gE+lsu,"3s/ yE8|m|Clc"ktb8RU/#`I:2-Ո6B[ufNK7ҧ5Aj2^h/k?7wAA(ʂ/}|^ag|"g$1:V5 M_iv6TvrrTI|i2MVKVA٢~_g11xnz8khVgV^A^C jۺ}bC1cDl6<[\)>ePTQfvb} Ak~r ټO>j)SC1Woa9 zql!UJ>  7$wXyDrQ$ Q=#(1D1./1u;R ,-TX(1QO @cX8EKfc"ٽ*?v6a_q5,M1֮AJ 3u 8?NYW#2+8 y yfuQU6ACt*lր"g%0:go ]ίoĸJBr8=(f]xLR2)3:." rVȒُx b@Y jov9); xf`  iodzd@Thp^Qx bA8/ y6zxs!jW؞k:I% }@]l5xJlRBY&6Dk-'c@>쉱Du(PPNl4{vQiM Њs^A d! y}a)y{3+c4F q-hdĴ QvUn<ϔ/v(T?Yjh` kDHb+bȝKW EԤɥIG", m)p^zm9%IVޝ}ܗ( 7 \Dƒ58#e RJt(8b+9G.66Rb(8]%e)Y6[hGgM?e]Yݬkhn-އQx>jgzO ws4噙ńhXD> s{lfLlfj8p)Z,y9r !k{G ]~n[?]P3q{kv@k_t\o>I6$/];BYsm'5ee]Ǘ9`KC6ORt:7Y =~nk2QG km FH6}iW!uwu}hW,18QSq 1ְ&BH|7{ggWfe5~JFwb0sMΑSz+0Hjbe$ϢY{Mg7P.:}lN(//:Ŭ}6 ىժ+22Ø5И[dwrd$X^˪l:L9DCRQvNRTCI:[MQ0YFWkC:˺eU#fb5:-d.N/kǟ @_g}{dN < Z-zp(h>L2Eh,'aqŖ7^koN3'IGedڭjFQˠ&S/g&Ҷ|kϟ-)иjR]fY^ȖAzH0 s^aZ v0 89>_^[|wM `')rer}߽"Yz >N6|` yI}6c^\A/n`;1)SD8TݤO;j0"TCr i4T*̈́)7MT%o湶~wHNaz"Ս1id/\ɂ2dz!wa&2 ("{VL88򶫱 O CK7-|m2%`mX(JIXq5d[Z>(,4!-IP!t=h#kUa4 \6<:yxnmuAQT\/FZ a4 B-c`6f?;7Ƭ=Q4*fYi,".`M*ܫ6ɸ+tE ǤєC8WhYTW!ӒE=& ⸒8-~]K.Fr(Μ*|,4CôR7E(c{8X'2P=)Ј ̦c4+N/Q6_/M]?!UWXaM2Κ,h:j*>M[UW? N$+{\at[- ^x U#EW ,pϓdEGKޠ}p]3AD;@a$EcohN<@.6 Ĕ A)fH=tҸhVLPRd }uMWHfXhZ9+%͂i@Ď+MQ!mv#\cHK4=;f` ٝ: F =iV<;dTCiߑ_qlFc,U#(v[ Wެ 3]wFKWg1i$dv4fʼnMlLs99 \#9 81inf ~&7Ĺ0%%rPi@ǤÞ̨FΏϾNsW;p yRþT6pM7DK y h7) *eEթscz'*q*@MOeqEYt梑,Ab<&QO c4H28B,&.E}hjIW*$cO"CʇH-1( qEAHç+wɅUt|W s4RS^O4+>~9~؍W= RΆFHrs7F?v[ן*КDa$qj)D#O̿"UT0jP:<'&xMiv4 #a22X 6x0?,lnbwgts\zIݗuZ͊+&JsL\dB. t j+H,!J3驻]/%fRKi&I1=f56(&hTD9QSш,O?IS-?N&f)*8;¤ Gp`+4ӗɞH|nŒT %/+;@ 5`ȥt!ۻHl(K;S45ЬhtE_Oa麀)jO,3qBg o+G>z5|SW:(ٓќ)\Ld4+cx<[7vԗlL;MDW`c,Uժ]+Ybfp`H; -9B$϶03-T 78F#}-]F-@~[X <8Ed{ [iX58`+JV<6;~H`_gp[ɿgV,d`܌[ HQr3N hfi;Ԉf,4+F+z-(++?ߥx٥ KkXe0_܏K%V:z?B=\{ Vњ|lT\APC-f0T) _@F B"ېy7P-\ NZf!g[cc"8cͩ 0l4+j PɆc0)RB4{R) ?DِT#AػvXFS47 0d4+LnWxj͊ i @#k94Ue܎R]hV& LxcOdT 3&kOblŕfE}jg*&ۨ/ǡhA;qxfU2],U4ÒpLljRXhYʊ{: [}v$`%A`ЊWRBSA3e7D[y[&s QX="gޞD\3XPڮ%OD%xNO\EW3T \NI3b]fHrߛr\hVH)1ܞ!nr>htO0IS̞vtDӶq<=}TK~NJ+.#G f\\ad@#AvAPGEٰӈB*7?)ȩ/ݚ B4-QIϚJiC ;qKwyN쳑?ǥbWYx{ 7;1Rgy+$"R }>u{s i J^3L,΅f$޽U~//̚7*95`R2iVu;f+.PhDOf/h^:ôvlXLFe%5Ry!h {Ff4~T4ǹ]7Jct<7Stӫ7^h:ɰUM'ԼQ h1texNt>g)0A]<ߩhDRvDÄ֗lqص^sYվj'Q-|=^ L lz}  x+/4l!׮A9kh:w|}V %$3:2iɥVgMeXsidxL-^Bi؍'Mxvr@7*Dfrlt>=NV#p\ 5@:<ꦾDXjXhycfA`6Bjuwo͔̼UrjdXrqTNywep``?֚HlbDh :P>J'Mfَd֗47,9*b ER`՛1I*{9FFdRd\h{iU֔IRJ1uʞ2X*YmijXy$}9(PFjN6 <̾NN$rPu"zּQ-SS-/wu"ʋc3x M+2Vyw>hH*e+<օa`v8U634E*Yv7!Ai$&5>ϑB좽\Lk93iC%z[_kbSI,;^8'SNp`t/4ʣfNhٕ%zJpzܗTi(3v=A^faI\Ax5xYf'h^lGEd}JWլq =ǑR\& d`a7l5'ND`06,mo3I{I9+Goj4S{ rkkK:nUP\I$]^_z-'oev5<ۯ:$$+Ir߉i~`y!~_mռlPˣW"Ҡy $WVnN*ͩFkYcԼ)RPa<<}1krviWuJ).;M#Kּۙ~]+;]\.7Wt<. çF_,rDJId(+_~t޳ϗӹm܇Nf"eeMjR3 59E-g6d5vԭ֨۔I{NQ2qqV[ K p{[^0: }~$υAlyGtCIn$1ҰfҟM|y}N$M6 45?Ǐ~~}grUxrZ|&r U$~7$1̉B%kѴo`QEoAه<5템WRc B>hZpI~'`o?>^nN1e~\!AT$Qk#CG?r2Xs%Ÿ'n^,Z__~~W,Mqni!8o/s6OlOj;>:Wj丹1jI.i> stream xY[~oT(bm 1eY/qvHe6>,,)P>}n}nS6j~1c\c]%7m8~6aU/~4Uߗ3׈ UKYX./2?%m3$̞g*ݶ|J0*%uuI1%i]^P%̔J UTXմĄ!  -YZ0~- {971 rrM#L%\mr5!!A 81PüT J0&*_2O%t\J:(GT 0 kƑ[CTNAF˱hN+ ˅^J'/՛hE#UxŬ"X%2vd*(`~G~V kӆDHJFS+V3u8}[b!q4 2F>g 򈰰GTi4zda'EV02eB0m+c3 Fka65Ґ##bSA[CgeDvD-2E2s !F/> 2`*dCIRX\ˆbgT=A:U9H(*y~yԂ`Ҿ*"}rGBS xL;2 \eO&GhCdl+eQLVӜDAX_"U8'}`x٢rYuc-Uaڑ0HS ţòDd[&xd15-a",@s mVq]Jq{ڐ0FQ F5磄ӎBH*&S#eР )1(p4b@0d W7 cO̓ѩvמt߂JxՇgL!QzQ׃Tşնzp>wX跶: RV-hbP)B5Qog/k5_n(ښmy͔vЪ/gߙ͠J90 @Qecu պǦS4`t=Ɲ"IKI _Oz.cQJ ,HRv'CX$و +!F%!xxXB48՛jwauu" j%z6S7i٠O/7',@qr^D:AŢmZfQ tK Sp=)[M0wsZPokb'\]ʰA!Ni ZH`EwX0s,&Ё=tKD.a9a|[T,AWp񕱼/F&WBb`Mǁy)T󍧠vڗunTo1wmfW+aIЬ8!12Nf̝}an,JG?3b gK)]9 d c)8Fc3"0*'1:R=TQ ܵ*n34EeL8ͨۂl5V^t/yFgJOLxРִQ8/]P1wvMó %zĨ}X?r o#+T w$a#3o9DдQV:nsw}4$aʆZ9)WS60qN*xF9Ԩ 9G: 4Uq܈+ %Fb s 1sxJT"1K7'ThDg*3Z8"G+:ɕ3Q(5 g;bt I"&n<ۘl,& gS$g*yfS6G/1o>S1Cr*zKڻ,(1M(QHL~nޤE]^'QqaU 4,21Ŝӻf{gRq!W@yTIfyXW9ͫME8ð S 04bؓb!Z6E~01kLV5F^D/pt%2[?l}jh-~3QO$-WskbJ{UHNWh%F6Gbĭώ:f:G5IGydz÷9@U]RЧo0Xn?T_GsC`UI$IllmaYi$1m"Ҫ1 dv)1 'rrL K(^}j*VW}J 9n hѺΩQ|R!R\I5?6f8;YM-tYE9qT~@ƔL:4_ o-r\~^S//h~sO(S!hxv!!ú@m-%ikiܼ߯- Bi ^ mmy~?\F_ygxcZ7&)'eӞdɚ_]O[]kvC\57?#y#~7nHFXV tֶ'O\Q kl.HEW Ax~E5,2ԳtWׇˡ[ڪC&Ѝnm3BSSg?taX_0[I9Vw(6#Ǵk˨֬U[? U|]LO-0mJ>جWI ]?Y~_Xz7/;(!)(ϯ_>i;"~<='>xw ?|}6r83>9=ٳ? ՟^r>@>^^Y;l?wwFکaГ=m]Q- 5WM]1۔3KXx*endstream endobj 1287 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1288 0 obj << /Filter /FlateDecode /Length 6173 >> stream x<ɒuwR)= a[ҨPhKzpzK\ gȡ#<U2[WӨ&bz^>\_}\Jc_ݾ{1): fcJW/noIdF1x;;;<avyIüdh* c rv-PaP7}7}o9)a9@x75nkq 3F p]kگm4r㵂N1neݐSjyknx#m0v^p$ҦLژF 6`-vŨ t!1 }}=Y tGBQ~v;o Y=CJQe2#dsHԊXFM^Vb= @ |{ٹ_OK=y9..&01Fyo*)h}>H_m+N8B%&O#"!.tM~'mX(xC4I>NfP^[90AiRvb2vX 㝬"H:)siEg)wikz >] Yb-t |7(9a>q2YRE->ʴӶkd, QF <$kD`у {jZa;Ti=*NZ ^."j ;9.hm;I7C[П47<# ;Z@c}'֕y1 )g e풲ѠgEw( ljЈ5+Hz=AXL|1϶ix؋ÎpCX]qVՆ`.b8U Gk7hQe^'k:\0t0v>!#GVЇ@*+*‘GTx6LgAP>_lMl)CxӫCgv, T |RMA#2a$y棱heъu,p!dlvnC FyGF`0h@ 20pNj%̛WlDFxCl:'҈n!Qy}C A*WOA)򅀉48Np/h 7pDH'TЄn}ZϨ/B2~I ˲kasTʦoo@x\va8.TrN]׋ُM}.gIH PpV֑ӈ_1Nqhǣ,y =)jHAkQ5q,W"?ꡧA 3kp/g=UWܓ+oqpVixF9+6^%OKBc*;B嬻 FSel@e'kHPu- է% tGKr^x4g4Xy`;Dp Jfx$[ [ʛ0Ƀ]9i[LL%JK<2PFf&\]($&U9YL4RdmF,r~[i^b) ]$.I,D/|?M,ö;Л湲}fP6?хzU8}5\G#/O9`ܒ/Α+Ajf^9Vb<]y,nւA$,yjd`&i&+DE}'>r ˄`IgʠAOxk%?]hxNJє'd |FTQ  9̔3[@tw&S5h-?C5YtGXt쓌u:o: :~J(hlͦHjDP숀Ɏ&,&MfUQt4,JYa)AurM*)AhjT.lwy|%dP}KdF?nX[eW ) E$S5nI xcMLt (S3m]Wouc8Wex֚;oFHqLmtJsӡٛkLǛZ7 8%:Y7G]#Xw߃l4)Ϟm&c앲x D5F)MuJId&=i'9HT^{7k3o` H~}usyceN_yj9YNF}=GHd[˂=ePnט;PϡB*eRWʳ%T%q?0a,'s]p#"}.{+.&I3˵lxF\mc_e84('DbIErնC$I0, *Zig\gj~?^לa/YW8CA `jJDmRhxPTeA5贈_tj >ojh%Zjy331*:06z#2gీV##2VAMbȇq^Cw39kހF~_3ӰGT:"WuL!r}KAГ}rvzc~5g-h18NՎe)=Ɣ)>k`Ji_of4H t/^uSX6gƬYԤEAP?6d h_ Jx9\5Jjn?e3I$Ilx;HRz^P{(֣b2gME"kic@TZ,`7*>/Qxj!.{P:`1WpInψ!3[@Lr4E;QcQ^|Σ6Rk lWjD +Яō|]?vO%@g?pro'!h(nۻ ?H@kgFuhl:)?w^̞bK Z#k\+Z;Ji}}q?BzJP wEFj;l8q{#8gZ=XPw{̀<\m(N rj,jE6j({sݐӀ7+ݽ76Ew d8ncK*h& 'C&')]ph ظ}^61u"NZZI]n2iO2<?wE[ 5w8ɽj'u{ gK ;ЁMS~Vz2" ~Om1?,zAJafRUfoOC쨑e>j\+KUQEgc+$8÷󇩜o+eݗTJ}1L@`zdE_TEl7f?tU#Kk`*7! 8Qvv˵iq$ u>D MjT^ZJkWbu rщ'պaMm8nnPPR^7Z{X=$ bIn+L֤(B+7R 7Є'ټNI$ %y q/M)y\{z ޥt9Me cS*,) "&+Zߤ. NY%0^޵vBCB2E{pvS[L75oL(Vqar[IUZQ :&j#t35FJPc9 ԗغL%88NgnX$' oF,RȧR}d#2^atFeJ&?uF<~TÔXeIS*DKA<~eF_ OTG7zz(im~sv:>mZͳHI〜4Ƿƻ/0a;DQl|KZrOnbX8 @]c!M"NJk*Wth/H E/DD/%άG5m -3;?7# .;i {7Fcc.>Xѐ7[ /:#8n }J‚a}ЗmK1} R]VPF]ݔ{kZ'. b8|]IZ0MHf듻GF洘7<da͜^*UT'r~9#vWW817t0__#{YEm^Kզe!*ƎR Iť{nGM|T .!{mzA ~Py|6giYxA~R-=:7a*l/_ޔ@z}>| SԊ*`ZrT~kJuT#&jr5imd%(ZO.PmnqRd1suHu>uS7#U}/Y7,_tsƒ=&a_v ]2%-Q+PoTd$=΂LM~D͛VOcC[qyp$` ~Jx%J}/H,,gRg8D"-z~*X M񤝑AOmPKq=!?r r$xĘS 2;-᫺Wx‹}0Rb5Q7 SKώ7=ŝZgnN?[2STgFYN_ "Bаj)t94gug-Ŗvjs\zΕ ;v\jS+Uo{7DSo,CY>}Y g=U{EE ]@zR$Bx8;,sE_FPpK?O0$ѲBNX iH 3㤜`8y05x7Kendstream endobj 1289 0 obj << /Filter /FlateDecode /Length 2934 >> stream xYI7L M9QR˒%V. r M~}뽹U.Fw{[o$EYxp`q-cYKYt96B9л$~[Po߯lY'-׻.2f%c˜qɖgU2#۪Z˔<(zj32(4u 1aڮYE+A|'$ d 荄$XWsyeD2WcSJB͋Վն1fd7G>I)1+E$y>6ө _le`sm'G7TuSN!k;};MvE> JCkۡ7J[goxܔTU$🫶o]LceͮqfW` s50l΅Z5d ^:_N _BNU |#_WLn#Xar&tl:fn:O=|(l|v A}T2~&S.2y`8*v" fQ{<3" Z aXU<ތ ݩ4_inMu5cV1EQ4WY X凶(:@bDZ/ FIb蛿9=@PS-̕ؽ8괳 cRx<[LW~~l? `n_å@;Q͝~!r;6 bg;Y3 AFwRu'0MIŪm(a/~VuYٲWUIo"Tk*3.,_nppY7("WaD [_e.6(v<'?<%Qw=f0 U_YiݩKbf%۰W@dS%BIZ+D`7/ׇu=×fe tA >F$k:{w?|+1)|vW=o;@3y=_*M7>si,o?0T, ˊXB=1$ !XV0L$NR?cdJ 4 ^vSA>G-ypL!}*qF. d9a9jE vEc_?Η TT|ed$e9^J͕(X`c窖;91ZRVMJ7|H4RjgMm w]!]Ʋ|TW ŰU=s)Ev< !v QYgcܭNU&\$J1OLsmWTiPP1&(1FY@zx)7IF~鄾N(DǙ(jDMaU@0LlAE"d) ,Ln@;nԢ 6 fV6ufZS~˒L+UHPa?CBHcC_(sҲl:Sx֢7* cL^%;#<+;=m!6čxF(PdQ m ?Ƚ (0S: 9\ {[Kw/rj:p U_Z!d\uYJ&"uJgیܓyD$M8j4] bʕ;/TIee.{M<p^a$a` {<_yι:sHKvh8mj}ʄtQM@jnF"`VhV{|# zkǺVxxLdX+8K2U+)wB4 %肕q MJZ;yNj7mc"$#ut9[ks '}0GWi샆D&cfe4H4Ov2v[ 4U'-;&!g(mPt`Zd΁RՙX)(c;PMV9 ]T* BVBj&p\ -L6^l+j(@HZ16kX)&:0 ~4C(Y 0`)IO"^endstream endobj 1290 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 636 >> stream x]]HSa;n'KcA~̗ QrkM7fdi4݇esjgfJdۅ v]DXB؅PT7ItQ/~g41 xJ/uˍpˆ1b c']7DbIta*|!l-m8WS0q .56+65]ĥhhř|1_uUp\o,_!6'3 DFk )B/ ~GЖ ZU10l32>;wӯ"H MOcOą*s)Bm|A"Dž04, |C~(endstream endobj 1291 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 307 >> stream xcd`ab`ddM,,IL JM/I,ɨf!Cw̬<<,/$={3#cxNs~AeQfzF.THTpSJL//THKQS/ f*h)$f$))F(+kau 5/##5|U33~_=OtyϚU+> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1293 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1294 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1295 0 obj << /Filter /FlateDecode /Length 2585 >> stream xYK Wȥ'b=|"@ @i<=hlI燬z{#emfX2O/+H忻oh)7Wy C1"9ȭ7io+9ZXow7LfqN>lMOwMy<0=>?=oň8Cv͛ݰbrôy{S L)8̭$Y 42˦Xmf5I&bL&5 QL:T {~HwoM|:~НEiw|wn3anشSشMdnmbpm/o8@  vddR7l``1o+>~csKXxON@9Ȋ[yHY~" φS;v{87q:  C# q{:nn,.:MVWOt^-CN q0z.ۉf.6ڙYV@.][jj 3ĔWLrSп!F~QBYQW3K5#yv!sD߳t$RX N Neo6QmI4{j"5B+7brjAu"4Lˏޣ֓-`BlLۉ&SbsLͪEaE`%t1ޭ̦wkXg)fgfBvE543b^ X}h@AMIEgg5{SQrsʚ 3[QtpĮB؄|ԅ |jlJoZX nN +3 kxNƿG3V ,P$}VU|.z#z8\XЗ#!uZ#iؤOK?ly^&`/W ^Z!7B2RT"Pz*M;Bhң)uLL[ݳQH _$6*HΦ`WڊZ>v6 []7kT ٛybor rp\CBÀp$xm{UcWu;V2NC9|BQ6Z֟K gBBWę@YΠ2 HXq_pIGC(l D>`$DO-h>w&Z֚?! f b^L4%ե1ci$O/I{6&T@? ~jGoozۥ ~$2Cs$agPɰr60a/\_"Jh=[ hHP6 Cˆ2bCh47®L#dD?:ېP_̝[!bh)s.k(Ho@/Υ I.N0=0>aʬYoFF}$v*%KiOVCssIFk 9JMnI+KvL GN 1Mw@lE| zB#&z|(8 Y֚8MiQR"nV>k" 6tcj6MdAH.t_#CS! B3t CM LP}+_4P-4޽k w .S|"4VBY$fOX&y r,'.]VÛkFͪQP,b_^9n^BXmVeڻ0&V|@V8սaD`?A8"KڷsEI0r[Lp21I`@gG)D߄:7[Jޙ MmǢ[gUHhZT(RY$T A+!(5rWohA]tЂoZzHN,5>fgmswҿI&IJtQ7焦C$Q> stream xcd`ab`dd M̳uIf!CO~nn? ~&_PYQ`d`` $-*ˋ3R|ˁ y I9i i ! A Az(.qKBg`a`8(} ߻{V|@O\O9ٿ/5j]Z/MxofpcS g?ط{c;8Zbم|g~EUrg+ڙвVƵߥ/.~%[zIo߲ݟ]l;)@þ=\;wy _t_p[juI`ׁerߗ|7{p0{Fz'G"𕇿)xp[r|3L?wL\XBy8y7M3lW,8cro^Ifendstream endobj 1297 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1298 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1299 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1300 0 obj << /Filter /FlateDecode /Length 2671 >> stream xYkSίT>Fݭ&0;ZRMFŏnnh0:@R}{oa:dt玮>uJףxNh4[%d)4p\Fޯ֟mOԳe짽Љ8/dbeUIza+[orE4Y)h ߍ9SʥGR YͻnZ /4IZB/^pDH,ϡphkBƏ;.7zt_$,צ.yR'oZNsR> )cy?h5ii}@xPE;/>KXq\-P /aOZ:#Wz! GI|9W8[eggˊK$`*Bwr&2eR,˶[A# غyZ%H2j煀{Ue2?o7ji[,xyDa)ceDξM@8GxyuF Xx! Ǒo=X;ϳ8,EmXuYV2 $8=qGD\tzx1{ lP$~ 4vC LkNNgj )9p:;=~svAoNĝA/ 垷YQ-4T&+[0B7üFJ500MD`l ;y*5iK[0jTMx7WʼG d_zY UwUޛ&QM*(ɒfrW嚏y(O"goR;ϊξ.ZxWIr9d:Ab~V7MOС˞mAA h8\-3*rVkI$O79C/!0@yyxvu")GbCo* bF@7H-MG>5WcU{miTBg-=Y=mz/L9r|?Hw@D:*օ_8H{?ӤZ`5E_DF^Fh6Iiـ1qx^dRn(F)M!0x_=KV˅-A'%&^ LՎC͵+ݧe\ư)0B7]'lYBT11euPj:fq`oP|XjOď=]2ODh{Hb~d4u'}JV E2Ec6@jH~^[7b珦;)=ag/$j K齾#A"-O!bN\yf2H&8knb2h# 5՗u(;~n@ !#9AOaVK !{=8cE^,' Bhϕ`|,RG|,4/.9;{΃{q!Asaץ jFgW|=Z[kx{7& H,Eu:/xVȱ↰/5!UqhA朾p0'uYej8/$9#<+ XI*<R(DMEZ.Ɖ\#<‹úJpՆ\NO.f:.(L>@VJRg1ihLCC=Bb,@iEFLOidP9SJDf`~ vo4q|ⱡdwia܊"RETѮqd΍x>ٺ_Ƨ_|r{ 8JVq87Lп-;ʲI/6go_)k|1@Bmwpa&U]DJ3!j/G#BB]?zp:1aD_NA8 oDD`k%sEE]ӭ͕mi;rh0 \z5wPE-~;PƏM/bBNf{?endstream endobj 1301 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1302 0 obj << /Filter /FlateDecode /Length 4676 >> stream x[Ko$IgVȜE;" Xajm[=ODm̮Ю4Ȍ?\v-){Z`mDŽe˅4ks׻nUvWW;ჀͰiCx;;z"tӇ7v(쉘Va0>NЬyySڗH)휪I<T>TO7~YnpuYJJܘkoVܴN)ܯuu6u-w_f['?oYټ_]q'UClsls'O ppK#1R҈ Ȧ}8Wpq]Un8u<<'OPK3V\FH4v|k9U` h%9߀_StS /Fs;f@IzҒaݏLKyZLVt7BF{αSny~AG89B e{L;lVa͚Q:UI'Cz/quKW^K@V0>Ӡz@)zKh6iȼbMSOݑ2$ji> A5I!,SHD rq@Z2:15:)f=bqAͺ"Ğ7qEnU-WbIpirceO)A%T1Z Xp=wQK&GfVAAY )K(el4J&mL s~02Ifb3/NKE+S6g5#ٜ5{]b,{Qm5t/Fk0u 7ҼK`9 J֥>Ư]=ys17CS آ:!q?4h y)jB} }G'cZ,i])j»7_=Ms_KC`X yu?,ۏ1/ {FM>S~)67_ ueʱVBbC" * |?xihU!)"ӸSҔjsvv8,q+O0W [cDH~<8S쐊l H́B=/H?LC2,ez]RFpFyVDtjˮ咃0dOG,F zQaoWTNV`4j*AMMXD˥$R:H@\k#HA0x6eJZ@U+>B/SQƀ*SI%$*ۣZqO  hxJV`9~sEu,ñcއ->9[4%RtKq\+*ִ  t|Fkecb0+ک{NKC6Տp%N"j6&an*9yҚkzu^v+`Q])N 1K* ,qҦWDݺnATd1.7&& CU.2Ȯ~Pʅ0M#|8]3VGaħ`8uoWW9J_ >xZ-༒rb٬߬kJjaF.lJ Sr {ˈd2OY3!E˵<^T^;=Ptzծ`hk\0t0I^{g>ʃq\&Ap~vv!Iq0)ƽS D5*AVܺX)xg[kXy q7JZ-TE ZYUVtE9Yn")?G̺6c|tS  t p8¼ޘW^Mvj3*?T)#dg+|ܗ*"&$P m#G ',>US L+@Td)zH_t Η 뢮"~"bni L+̿]׸ML|ašt0F :&ڝ-q >xg{XHѫ> V.@VT%F rxΗ :iKtRP.<ݟtǣ4 ^1\ƝJrrY_\;+b)M,˚l*QJ*01~Pr+~c^&͋&Vf4h B[MdnI3/Jیyj9-[H2 X@CsgCV h+yU:?fw8ՄLH[1"<$E 1 Svy5}ެ>n+$' X37sWqGoÝs 'V0,'U<As>.U\HRo}0OUϑ(-zs+*KG|ESHo$ZnbW_fQIxKNo#*k4_p@|;R/*"oHY,ۑ~'N`Ph>IW!y Ni!ۻin`哃kz` @O!$OV'#硒E1׹/syW,N/ G#U 4)K?4AnR%uJ$ၜ9x$`']8K=J>u勳nez_UBՈxqkϩ%qnEF Ν kKl[3'[A~zvZ]NUiTVXWNfb!`SN{ ^Xe= u9U2[;E\(e[g c0Q1Uz+E ,Wܖ$+\Xߩ8hqe&-epKp`kzU}A--mSUAyt]Cߍ‘ )N̰59R5~A&Fc}/ƹHpmu Y"A@D]Cjå's ̩0K 㪣HLl<}w%s}q.Mw̰itsV^ϠI݌ d+ݹjqgz7e'U+H%zMyV`s"s/& !td< +6 ٔ* ϕF萌U3%)A1So"rUV߈h&,6F;/; wS~{?0WX7!`<@7sh'5[cZ-/i髧\lYvA^MQQ_jpSv>ċ[0wU7] "710:&z9px=uL~\rJ_ 9dƅ6?#/P xSZONRdVSҜxx8Rͧ()C&qʿ\*G6f|,=@Lod8˹'fDa[d.6I* D%dǣŠ$ 1dIN@(f,^f9$?4gC'qdnz*v#FIeƃ>bp7_ݔoBegqVpGt8cԯ y׵k)>Q!\;d 0) Zq=1RyT> stream xYێ}7샱:X3_d 2lAzAcGR/!EJ`Q]UYdJ9 w}1ꍳz~[8*;WR *\1ksɄJR)oa#)?f=f!E4s W~j#4R`_ ^{o{+jP=ֻc[^)F%;f5bվV!r@3FPVǶ,~'; l&(GQNcq E df `Ma3 7b Û}xVDz{澄 TJؒB@AE> H57,ǸP`U֜\^Hp lqp횶ԉ.s9j?ܼaE_ ɻLͤ8X&\;k֮11]8݉fYfuiϑ5Pû}?; j!;!St4.F82Drjq:ބ bPsep!a:۸Bt/$ =>2߆HH~mFܔf׵8,TmauLcht~yz<#(CJJMZLCPv6_Z"zC"MYƷ`Ȩ=p昈]QB)yOq~F%Ji~8*Ύg|N gv';G4N~"r*J-endstream endobj 1304 0 obj << /Filter /FlateDecode /Length 2598 >> stream xZ[o~o0sM'vA.$#X+i]j)/W{;CRjB̹̹|~L_>ΨuߜJ3*'$Ws 6Og臶^,9(V%DCFU_}2_UBƍF{l/.oom!]_\?F%HGzX Zc&HfԳ X΅`$8 σHTr@éˮa=~ b !Tut,8*'P &7`?GA @10cKJ!?.C]+<{(iP|ڼ/fxjT^;=-Zs﹇k*\ X% ~# >Y*TPJjbYK\(p(IN ɠIiPRl2Z[k&JWC0P2:hw`{9jWWXo_w dIYADm6rӴm*bKj[ZQy5jئA^!ޞ%$ns&Git }^ʙq==CSzkq]cp rⴲjňyb}۾gҘS<% M y+L}j_&?qe,vs3W˺~u ]ןľ ~صԥpbBHhp<=R$G9>QPUPo#PXET c90BuzEZ 16I Q=g6 Ŕ&9c`P'Z@Z`-R1ZjI_QD+ "&>ʖԄ.s>:NL..dpU8q7Ԙ:7椦HfǜR2.;t2RR@AXnu 6bPIK4bmKG MHl ,Õ# sreB8o0j=1E{z- -f@mO*)wc@4& r o0K*>BrnKc0s]alww}7;)e'DbDaR[iBq jē6=jĉ.H54endstream endobj 1305 0 obj << /Filter /FlateDecode /Length 2557 >> stream xZ[o~o胚(UX X aA64%GCr8$u݌Ƀg4;heL0;lrwyD eXba;Dk-M=\AH2 nƠ*nV\V[ usiQa6u"҆iT-wuҿ%;J$n-,e/; TpT}TeKt_M\Pk,cD C}La7!:sOKR@42^.TYpwVpa`OX^{;vѣWmڤ[]b}tn Q"n -nUQ E덻4 1oMs;cR:" e]5o͢XrB0ƀA~C E 1qP۔4\hc4j rWVx|e_B<k ׺mbI ?D ͹vi_+giJ)bOn.}쇋r\qGQHlt 0D)T&L<O=Wm }k~@bE;Y6EL1]M>نKWa纽fg+t@ ayo-eRgRBLS1dcώQ'j<*5܇Y]M"vi6ǩ ʇrÃ3BOZMӇgUXm%[DhHTOXT=S[5/Sشw*}D.!n@Da-CdࢯTi 3Há eAi@49cX雑:>.b:ծJBbEц?wkAڅ f̍ 0 @S3 ?էzM f^6qJ0 s9 R'׻">e"[?В6iV*}kwYѲm0lኄThXm9suxX!Jh~>6c9Csřy"shk  Qh$-Ю c`mJau8?y0R%'R<@jxU c-CjZryejrN ߥAZ)۴G)@ALHqBj#R +Sy;͉kL[ 6ݦNƹȜ/@2Oyu2І& d~`3yt2/u.2@&?r2_.O{u9<3!ss6g_?|Fiw J9ƚTdv9bX.9e0GWEnu"W mKZpvpcD%3{Dj6ռXowv{52hjq/k g&⺶WCCBo r^V<,ⵃr徵׸IvRoLJ)cfwLh \3п̡߸. ܑDpL.![;6zT@0%-L@L)"*tLxEdq kY-A"5,|T0HL(`԰"1iVRSBFZ)5=T).١HArbO$g_~ 2ur:@9DjN%R'h+¡{p&rPx=eICռUP`~yG{n2Sa_IN~J^Uf{N 31`RDi(XkӼoo!d ɅÛx>#dPOgXxIٞwUS g2 27TIUޯ4c2ċ8= =&e5|V&fyOcbYTc( 2Gӵendstream endobj 1306 0 obj << /Filter /FlateDecode /Length 2798 >> stream xZn3p tTH9@@ @cE.-$W%W}~ %qɾfwČ|s«`3ÿ' Yi7xP ;ܹJ^TVv޽ /DNßf/Jgg],OzaJ*;sV.6''^|F94 덁G)b$teabt|znp$1犭Λ}ZkY޷Fݬ޲MZ){Z]ϖV7-l{ªòvwſ&$rB͜ 3/9ϯ]=]zؾӳ 2UKPfw]3C܄{:e(hp |8M>9wxozߙ$tRW85 ׼mUsZlO>R>|WEs,Z*+Ku3YRZ:,*k(%_yz>NXYyY0xϢᢒRzJΕPXΑWbvH:=W֑hxa$Qc"_!~ax+rs-*^`/OϵC~FB#JkF_Hy7(Λ]29 Q%5(0%J˘P9Z(=0aNy7{LF/$tOI>)2P)mF-` )<"\a),'A] ˂KIYzC$#ZR)QܞNJ*F ΃=4;7Rac>P&# Z" S҉Z8nQJ$3JgI4G{,%5%3j\@F-6DB粹, 8t wA\IdԄȃ>h0Ǩ,"UL A' x *Cy\k^y(]]pF4զvora|ӏO-FDZJ(h4MC>7#C ߻8Lx岸VY73Y޿a-7nxKOGErWoKmGzrn[}=\fsw4ZSa 5joO؄sՋ=7RNƼnz zoV:yqWG"A/`0zqC7ǣn^T,HIꙒ1%wݘ5=l1"~9 7}6]h)8w:>:ޒЊhsJ^2?JrJ靗I(rfج)u+Dd@ f0,B4Fv@16H+U::V.bhջRF7xF5yi"3,_uŢWvDM~q"H quBZ OEexlcCHٜs(c/88cZ>q{_zwĝ֖сטL$؎Ƿ\8$\D2vr -:' ,赆I{}4/F}VNT~|U$4L}5c(8?`;t1Rf^W)zT#C6m- a z8IR∲Kiˈ@*H?>=XT1K%IRU'qh-{˺+Lq-ܥ8lap6`(QjΚGasƩ8f?}#ܬthlد)tYnEBGOVӹ@YODqK*g ^z7Nz{vSӷUϬ㕶_OBǥy}SCV>g,ic\(8mSTܣXR QpqsA@IJVjV*lpX{y9FF 4OKl@G?yCogo(V Q@ Eb!~asӜGܼM]짰Hn+ =㴟7zj/ZHj>,8`d=6/9-MϞD㚗p)d1>\Cf\_e7"z_YYcы eG\??3endstream endobj 1307 0 obj << /Filter /FlateDecode /Length 2622 >> stream xZM0Ȏ 9+Jˀ+sHy=3kqrUkw+~q%zsW%j\߾]2NDsd}Xnw;/>ޅa#ɩ;J=/g?m4cyNG{_vz*%t໧4/@Oфw]ޛ:m6؟g[~Żi8-ξSpt\;BSs=ny~?j]RX߫u~[cԂL{daxJ#So{.HsWꕦ`gwxwaq=| 2@5BܞvwO=8[ymS|?MuVHˠ~?L#;y~vom2 vzZg(%urxCM&|V 3aRן˚e}Y5o߬Ul=FRvϧkG2N|}wx-a 5ZOpQv41Dyz] ߍs>ͅ>5q8K4z;OMS9^sN c5}:&M8[::ԁf˦*_Oŗ@G-Gc>өOw\'ERB1byD, 5t܈wQ ݧy AC11 ǯWqb8Zd |܇!vEDF%#)wW /~)Ĺ|aHpUF\@ 3tPY|K_ϗa m☋6\65ꄈ0dߐM@GqA8kFq8lʴyaݪv}Ig?ixo*@cJ<Ģ6s>ڑk2N_onKOS}seǑ|ueyEL>&J3`)fEuY. $^vZ !IhNPCxFo h²ezݙKR<:~wQϖ2.] C>D`dA):x7n_&+R\rR-Fr$tHc`芵%F~aN0PS4's4er$@RpZeubΔyGY>K1{ pS7[Bɇ*c*sI*Fm4Uh|Έ[RuTðl,e(e-\yAuc\sȿO(Nn-k;uPNMߒc$ Ϡqƚlzf_]#k2KPNC-bbsV[C$? + 5DGBK榧m•ό2=v$A3F6& OBX\|?i̗<Ҕ_Ec)00[j/8`#έiE.?~ckQq߭ |;endstream endobj 1308 0 obj << /Filter /FlateDecode /Length 1895 >> stream x}Y;9+^Q|*]%{ph_?vbf:X%~dXEc_z~$rYr}1d SiX|XbdII.ft&Z"KE t9 K0'KV-xăs b*%RYtcF)rD( '98)lp0wF>@cb.1<)a%!a^cPrؒc{t<87Me]]&Bپ+yXl1׮0•*cU(f+&~Fxd72!d썌KOA9UY #r܍ⱍf+>.c vY$q@^d^_q~ӺǍː, Y@5OPcNesk]{љ*"Be*0Ѯ#%gyMN|X:b@9cϰ&ZL : Tx.%,RɍIqj2C5ZBfbA~7j'qbm,RI+Lb+LȰy@/e0?9p( `$[rAlXI\[٨j?cVԘϐe`.T?:7#])[dLHPA,i*pk0@gLX#ē][Zep\bq4i .K82@ glu=gԝ)}@7(T BUEG3Fmr&?2G0.Az xݦ,7`xQF:Bz*xOFkI!s$ȚVsCNpLن5pv96GX;*#QżHT &B!ƍ+|!IfJ;9-v8(|BJPԳ.fhL旴=抦-==C{sQ\'St7iT-p1Y m'*`$va 2 y#} -x],Wl9c4.EZ(5݀^=fOg ZPZKFK[ۻĐͩj`,ɦųzGxBDND#O6jIm=c-1g2N+8z=loHelJ9A4-,vufM䄣'd|+>MhHWgjี_}[Mi7L4`ٳaCfklMeF֜>YFi'a)wmM׃`{),P O6F q7 osӶ3'VQZ\pXSe{У7ܘ&"mܵ Nԏ?NC1%<2{AwNswendstream endobj 1309 0 obj << /Filter /FlateDecode /Length 1211 >> stream x}XG +& ܚl6_'`@ ?\j hNWYE~䪛OvT>zmկsl#հ˟9;W|iv5NAW-qL;W{g#()'MsTJ֠2v L\]ϗw:l-p3Z9h P'kW&E*$pd*8) ΡH08pV3i %Q߭L0\IY{(m(XF?SdyYE,rd 3A JB&;] #g5a_!L0/OQ0&q@i4_5DR<EYU +QI.4^6+${KpXd]L$[sqcr)U`B;hmeoEƼXt-2ЗHAMJ(6R1_g/vTEPmp6 Z-Q CnUh0\e ֙3:[$#<|+?oa`|;O{b?z 50fp;1(Y(p o<cxA:{W,wW=M|}̥iM*1`-wG+M aΎ.}wUG*?<7wN~cz?Qk'2,cv7eĘ:LkDY~c?[)ypikumFH1PLܚ}ERB`v`\Js@<]׬.; ޟPCA|Oi1-B\QhJjȔ!ɴ x|'=P<~}|dendstream endobj 1310 0 obj << /Filter /FlateDecode /Length 1709 >> stream xXMo$ =:HWH 䲋 r0<=;l;??O=Nv |(U5%~GDD<-_8 3SJ{쎿VmS'K>ҴuV$*Ǹ2JAppa&;}Hq?荲0G!Y;#, jNIɷ:M6Asb= ca&ßHzDU8:%tQ"IJDMiτr5hD @6^mܧjԍ.U#<|Mz"ا خ4q펆7k Q㠥T5,7b()Gkw{;p?8RG2PH>N7^rA#/P1ay.4n×Og ~~x< 'awi5aV$5 .WF@o>ϋPͥbz.=cɖ0#QIV Z#/wO}W=S\:%Z1SAߟ&3F ~.+K4^.﯉ u˧SrF fEr~29s+q׎P_[zߵ(S~jN!hp y"[@'Sn5s-5G_OMIZ_暰 ~-U2gbvz<<}ߔ_5Ri&P旪DS8WyLɷ\~ErӴƱeN'nۭTC,+@=XZuί Y.º$,iiKI4oȏ)Phxa"CD5+_̱hBKnM,mn1%CmskY<,i]A9O!^=c+x F6_ gYLs<{_:-F"q{L=Ēh1phlfYQ&[xWYZWb]͕_:YʐQ~K_V\~E5w8jņj8uUzӡnh(G61qPRܰ+2)_ $i *5rʇ7_Q@hul >_m[d6g6bxQ(b'8`dYMoweTX"|FKPf 1+)"ʆ*LT'HE+/@[PWky@1uoiep0V(Ʊ @؆|,N_J+,Za VBj\[YŚ|B!yxZU;Aq4R o3-^)@o 2R۱-}?OKmfY{h4Ԧk=~*u:2+yVLPbwޭ1,-m5с˧[-xᮧ9`H}#T Br?=\>..%&0r݈Y/Rm%n`yD2L#)kQ[Fendstream endobj 1311 0 obj << /Filter /FlateDecode /Length 3892 >> stream x[Yo#~ofl},1؉@U`(jɘ<$# ȏOU3ѱ)+r꺻˘Q>f/Gl~ˈgq>:AX3aף8 Ac+uޏףwmn7zς,f6O_]jߴݐ?K%孠 p~5"MBMdΤ!l,7"D;;"5e=F3 q;r5la }Bϵ+Tq XF1ʴs;lv:K FR ؐ% `a*ϙի [js@08Eha:6I<&)5lz*'خ5H _V`CUs`,V-"Ir^XA jM_*UˌҒT8@%-|Udj>a g̑RK.'8p60yrzH) oSAE31z ρΡBnShEm~K0N@ad{:I +IV~xPTJB{M=Bp9-˛} ag|@qݼ?+_&(I|dD lɂv%j6 6ؠ P='- D䉉|E!FTBܮ*6V5ⰟI _)t/vEʓ[d[ǏЂTp (7s^n˘9i8Z~޿F2I`a8Z89;Ez>}3"v( KF:ʳ2pJod=Tg}`}tR{*@wkbCA{4$G|࢏0_6rX+z"|x7f'/R< ۤ0^*kx)u :D^a(c#m4&u]EC(m`y/,( ,B}&A^+[f B"P#/$/:D3[C_z%r׬o*x ;yjj+}\/&!!}&ae8/14zP}D!;hz*!$1(<֘C,E: /, o5~o c9qRI<{Rll^#3Tiy5E}5Fsdp$=L`}l;㌱)7tɥv2M΃YM;[ǎ~Y,Vʾr?^M_ j1FpZęfBd w)K?f+WZ];a%dcl~f:u?S|P(jhU$+:\y(\.9za 0o(JOQ_7վ}A< >%8˫ɛM<.nrX܊IZGrƇG\L E153 KNuQ}~(&}GC*o{w/i,yJI_.7gl su}z{\±m! rs{gG'Tqi7ANRctCW)fHt ]Rww>aQ#%Pjdu" /Fsٶ7^j{ IGS; VMSI@Pv_QH=9ұF`E0]_qo uB=f孤rPTx `gH~Exc q]VCl`p@*u8 })x~l(`-{?T?,g2VE65ccةRU7)~ܕGECT?j)p6N,Kx~q}/=r%4̣`Ld<'eJ2ZՋ U4.r5+6 }"z*TpZb7k+'lԲQ%%T|:5(¼`K,XO+"xu/";o[bS:O5ڞY]G*mqeZG:*`)f%ŋC%'} D5B~Vqn!29Va+%wut ϙ z̽N0\RW>N ҉]%6!kǩVUB"$`4脶5T|-$F` 5:?R|!?3+FqfOBn>]x2~ZsV)؅"J"ʉWLl2'`.$3<ՐGs pLK'/ *N󘠅|]kG}f(M]O^a02nRj4L@m)5tƫ 7Fj6t <9=]ɣ el*~'EjQIlں>}Ju,˥swJ Tձݬc1Z08#r].>4t]҇u, D?- _B<е/ V/ d ~ Bh  .\+c]dJS㪁dU4 щ|w_l7N^wLPU7%I~* -uނ$!{B _䋬X'Pj & +-zkER\h:>}M ¦{`vڛ\NsR6\p)|שM9alθױy;:bEkm~Mё_\JZ?~?zp_U/c^ZMD)8e9rXF 5T\W6}KҸخZ+j޶CXvɠ];jtljq3y9xA-`O_;]ٞ4 fFzl$xm5>Wozs?EИqTT:G6c g]nunyޞ n$U#s+s'GG\hZ[P&,P @SO>n@Y6Pez=2k`DJ ϭ|xcW} Li,`uXN.+K.W&gqusAH+Q2Gf0*Q[UlƩع@,BGxendstream endobj 1312 0 obj << /Filter /FlateDecode /Length 4206 >> stream x[oO|` ]y &JMZ[E 8Eq(5yT8j3Eˑ"ý}7hf?ߜN:;~v57z]. r>36ֹ-9mrP\6)ߥߜ|ZbT%ȪKU[uWA5jrlbϳ?lLB >r /Njg>0Tt y ц*k0| -y?_p2\tC's@$)vGyѕ |dhVQR qw5 bAS$W7CEy#mlطmjݖUeXPt^5N+̹A%%H}`$vf?4 *svl4)XCYW_^yd`8<^&QT2!$/0є,h {\.ա[Ҽ6}0l, Wi~FVLc]08dWnh.@&b6Xzlٖ, i8Ch1;o '꯷i)*n؍ WzŢEeݸZJ }i9C8RSp7z+QoWa`I:>%~hw/G]]ٮj%$Rg8Rs0^> 9@%Sh:0"BƟܭGNF0Yj`_N\X);qٸ5qӒ~qtv!e%[˧ Qf~(HCZp4U!B~@vfm=OdMTeEg hDQx判N,9zQ40LKHE>g6R H*1i,KOXRg |6~?[/wcKy(Ճj?DX ~k(+\١<޸ ml$ $T(cǡ!Ѫ}2ߞ)پh6xdpbd9̽쥤#ٗc7w.,'$-\.@,%lGz\c)vb/χmq mc[xQph4G23BA&&Sp g6F1m,Z=``ҒvSn6,Kq@U0w1U0EB?}(Mظ߬v|;\*WyWndR48֨p3൳NlS)>jWyD{(E[.c`! 'WUW~yJk׵lJDeQǩr@!^uva׫ͪK1چM2)C0+*VR#R=O`'=X5o@I671wuR$\_qta L捚 zwu{HX?͉j O_'oqBڒeJqiNd sLDAD7#4r8ϒsňT¿' ǡߒ_?W_'ׯ6rd 7hibcz7;{q8&xwBѯ"I'eym /32!c])C.ܘĴ+ b>S{n~u\a 9,Vd~\ s9 Tu{ Uyxz2y0=qo8Mq *C*uXj]nRL8LRm41dRN}$7qGdH}!b c`%,3'@,$d>m):' P[h v&a.T/!1*cRIwcf3aF2  J Mdւs'05 w9$ {M4Vޙ|&CN"ӑq78$'@:GY8mqiS єb_fVH `N>prmu1C] AD2/{8pWҌR{sΧH|5 H509;Y'f.|R2\0WdTםsЗey3& 7U1eJ "ss$FA zd96.bC㪻XR2 :TyPb[ZeKM~;Ծjy0zw$ei?5iuՌ%Z`1pc#WҤ?̟`䳄R|zdp(LZn!wa`ݺ;1Vr7oĉǼ PلDgxdPR :RV+Qdʦ+eZW Hugw|;xPS/.bCDWL ҫ97}$ $ё'i.?I5cU0 pw9W 'tz|%GHX.C6ږ)id>? wemUjIPu")@ź?\z "{|Ww|i}Vfeu OP"?=U #{ZCY`°($F49y3LR E:}#i߭SDPT#۫nCBD)}t2*,Ƨ~E U5sWi|\<ѴdƔ (Lv-5}.S3ST>NGv"RQLy^T` հ&ktwM.crɟvۀ4(Vi1RZc5y0>_q+PVgw勂>6?pVũz.c\dvZ+g$'E M>k6`oj(z^iՏU۸ (b4 YF 'טܴXwb&O wkNz2ᴐP\a1sendstream endobj 1313 0 obj << /Filter /FlateDecode /Length 2588 >> stream xZm۶_3(S|4yڎ&geq! hw(:>?He 6wlwY9so3We/[籝Ǝ;_ngr \;My;ve9C+WYmjBߵ1R&Z_Mm8Rm!U + %rGn:R-p")X^5-2f^UJ07hA}&<I 2kcvav.NHD }tFڱճǪh+#;RZLգ-ePSjFwL 9yHhxL=4Mmׯ` SXXaC}!o0|R(I"J+Jc$Xcx@1mK Ņh3ZEqR"Ep \VDօ6̀WB̶ڪ;b8a㺆.-G"H rNWNtjlDPQ ( N}^kgm6A{DYG-q'f֮716{~/Ծua !*)IeFnq:QS3V݋J>z?$|Z0REx(g|<}MnF#ҍ䩬#P1x҉vׄ2FxGʍC^/sB(]ӁuGʼzױs*K'W8@wtMEXS`~rqկ.|{#RV٧O YRi$qu3/}D݃:vVŠa1^b@(+ F%ə(DFGgQBJ}1G ;q/H_#ejD׌~$W0錨qPMat{R@jrNY}!HzkVT8vp ` 䶫*33ď;(p]ݤLؐӇ<ϱ89+OuI{![dJcO³'; 5klDDAĚ+CQ3ۃ M88=️4ng# pOVwE\[Fg|;=)b`!i|;(V"R\Aj@'>$vDIϖcWsʁA[cǾViKvȁApqFXg2I1Z[Y>DՕ$eQ!AȏbyFFd%7'N0"ˇ>Q@WsާB㖺k0}(Xr+(R^3ޡcx#̳g౱&;ܪC3UWNoj0]c4V}/iZvҼO&_#3q˕1 sqy$ކL$LbeNieMs!;OQv5m+xGs\@ӲX$Ɠ.ުs9ɟRZ%2R7FE%ڿ I2]^E_Ms,͢z'υS݇hX}4jl%_j@s\ T~SZ ^w&٪j\NԭhECLA}ށ5$5iTr$垼׫J~E (AqL>nJeǚ9{ia]-'П5- e޶G Hx:7E9[VnZI~">h6{Nl5u (> stream xZY~_!x5ǰl4g 8g 3"[my̱?y R}Rǎ0lVWUmf#=xXȞ}" c~7.{H(rKMB趼azln;keyuw,R8UB=[iAҢAsl.%wرg%emE?H!bT;zGlQ%;(43kLܑliC+u:Fa$[ޓ- ˍ ʨw<;`g>?4,^?s;C7AB/8--Ė#3Gy]}hMuԣ#OA Qyd 1# ; A<¼P:면h744ٔƇc/-0e>%yEĄ2Q.T``Q8!^qt )8~fcإ\5SN5H2**")%f x$0q2[F%Z&"R}tԧp?@,9]F"T`Q1 @z47ypSWi/_lZ^#em%VR0@/ZzhJ 4Sa>AY p#պ1,؎w\ʅg/sӦEMn99?M~R.-%DfCQ@V4˵ wA;/]!{9'HZRC Y!Q2j&@2$(Z4,9up"X5dА+n 'unznVe+J( xwNDJG2\)?F1 2OG_" :Jc|x|ހڰƚD*i3~dtG MQ;c1r8I40ϳB}PAҤm2nL1ݡE٠;-F y7(w!E+!lQzF==*@@~24ma`$)GV] tC~+/@MJ;Y.aRH򕬍aL 8LfT|F6l5լmaێ4ݮ 1_G .%¬HEˇxPG<3bˮ{1[t .:|;% .IM_Fyg0t숦x̅E2ɦL݂5ҹ(P(tWKWyҎ.F,l ;%TMMk\x9n)5'i1^z&jZ x.@^DCɏ}4ȊLc6==M [jZ`^4ɴ#Ɥ7<2pޒE52O&Qod+eec I EF[n"@3V@xm; h{@yi|vqr >NeyT4Hc+o #$Љ53 Hk`/Z;|! ߅/5~"EKst?wMY]Z- =+>H&Eno'2m=}|/1J8;s^]N3+Sz|MNSiUo L͖ȶ:_ UN3ItX7Qu|!96cnrXu|J㓩qhe&/%5:7Қ+!dbrz}6zʴ̓u鴡q#@L/|FQՃh'+y%Oʃ7{#GO.V'L_hc荓0] m/]6yKr淝d3= x,ovֳWsRWfFvB T6s"W{~cde 8tW z([*#FٵHet r=iRÚ `+2t]j#{:\RY0xst 6 sl&۝pFVFMUx43 x8Q<DPphu!LՇT4/P(v=ٟUUU;A=6eׯ_w=nEG4?X!bN 5sv?g ^\]wendstream endobj 1315 0 obj << /Filter /FlateDecode /Length 3454 >> stream x[Yo~ l܇ql Q`D#r;c=)!=tWgD.w{ꫪ/=׿?Q]x_K_Q]0 /S7O=z{!?_Ipuuj=7bhʡ~,>Z.]/ 3Xzs_A[`uד_EF27D-fEN{cIɸ u riXwےJwif4{x|Bxa0Iest\6tv3[S:.gNKkxƼofɮ}g|n4bj: XܽZ^34s'r%^hˊ?tf(|Yn'e4uP8BR : XrZZpQ:z\:k 0rdcv2t3bޛ_C]ϙ b0tX:Msts';ap0Rw[VaQ\[9XFίogn ^*U($4>wGDALZܳ~7bqhzonL ܲ;*Ѭn.)i嫴mcK!~GjTfuy,{p.]wIlt@oR U[~[X 'd'iieǹ\q0"qOjCUCgLMB(k6Vҿa(ew8EQy:AIҘaX*{4ZA!K}fQh-}6j}f/wBzJ !;s0>9uñ #CgkII??;gW)jV!~ٶ1TVmhoPuӟ !Q.N&-nv TCnd4mQuG x%)^yu.~ W;|bsHi2>[v;Oan4b8&5nk"fmFXܯk (d4J!*̖pJ|e؁*dɟȖEЀ\4d3nM5$9 QVGTSB:f,s;r|dݞIdͧvlb1o A.wĈ&C`Cg.r=>v(ٻbPc讱ifr[zǚy&h|2;zO9{}cbv#sN;F]Νe *uG]Gf[(HͪuRc@ؙU,lzK7ϼKTb_oA-;ErCFj*.+Q"rκloٿgIH6K'?+It%uT?Uce+Z%y]S#7+̚U,g()ZWޜ\jzh"ww-*ٔt:z.M->;5xUlyLb$~֠w{(Um:iq7!  WzE 4>jD^oaJtYBs: i5s㚔jFUOKז%t٧CS_siE->T[<=T=w @wQL5ֳGfq^5 PϏu_.<No< Cpԑ:"2$Y L?ſ7&|// Y]Ffendstream endobj 1316 0 obj << /Filter /FlateDecode /Length 3022 >> stream xZے6}PItN[WVU<[ZFy3/L6AıH\O煉d?82wWpgVW{gGoj{_FY ȱ*`|{tmLxKok1ɣ'geC?^ ``LJ^>>Z,G>MTݑ]}F)F;G`"99aph MǨI\I-,(Юf}0f.!r<ײk6s& ؎a8JaqkyoDeR")[rY9!gܓmM΀hy68wݬKrh1=>Զcz4-*kx(K&uϦ؛Ku'  uN&\! g.uPufhR?i S挅h[WfƩ~0:N)&5!6y!B8J!y XyaVz@alGW,(`ۦEkO/`)627*qrޢwr2gI,1y;5 8b ۚCKEYH/5*Z4y L@|0mO(b_V ^.w;L%1OA1MIRc0y;.!=2׳c!{V1|$$aBw2',va3#w&[#!1D}~qEhېJ-=ˑ( pDJ=4p!{N"Q~!e:+ʤnF^ۇ!b~u"R*dA@ۏ<1O6gn_J8Ƃvf_0-* ɛA8ƬLWJb[x{p3N;5c=GY)ˤw-M&rs,pi\9|+@e2eIMW]sI$43*ՖHϜX 9wPs/Ƥ$6W}V}A:[SMۈF"AW͗?u߳]{8,ja4˸^|,g:vTuF[“/Z74IH.'ga>" -Z02u VJI1K _(dPyЉ(I(W"m|`,#Ge,ğ;ɛ˪7E{:"ۛDvt8iL>OhW5 >墳9fwJ)ǫ_) m_ʗT2VeQˡOѽ nb 7RU4zDe]օp̀!@C'I%vg8eפi1&q]MxU,}^9Gk|V{Z>o>i 0htnH]}'6mksk&7bDD%YZEZbJvg3);De𧆵-jT( j Gw9_m)hi|־F?AQ'kƦist]9WGZ&ȃR(L\(fERT#[$ڗ<,A?"tF:uޱ=]$҆^"H0J:ʊ$m,EF _nnī fmwx` 9$uy,ml#:DBi"gf pӺa.s@`+.R¦[(o%mf2Mk{\sD]pR`츇C=H]! H'g|wOZP,873޾xX?O>˲xB7kEt3:TKhua~ʧs6^C\TdӪu/Ҿ_ț(|TGBz Ib_ ?Fendstream endobj 1317 0 obj << /Filter /FlateDecode /Length 2859 >> stream xZnH}Wl&xYd!^db+qvq?&MRY`XRY]uT!?؜,f'Ov+6,O^^A0KP,l:YȏY,Pet-|^,aJS6yAGfSX IBV nuirziX#+u&ֈmrZϾǜ ɫM^T f^syz') 4f d,ۼ7#Mݲ*:i{+ɖ8NB,f=haZ3%bFy=;]~3n%jMIiu".BoC왷t oYwmQkvcoZVEQx>^`Bk($ `Ǿ,&C!Slu=?[u~v;O4 q|}jD"Ȗ5Dv >1g0Lblel}É2Aڑ]\kM^Ү'7 Q{o2a赤{y.X}M' %rb$A)T3 ՝jѼ_\ 62$ msDWoH)76`X@~kI:Aܓ+"!- DO?8D:w @nL%%0~}EVz[ԃ2D-u/Q(v^uaYdUnmu Y=!}&r0p&X28DLIk'@;<.U5E8L2($UUC vCKT}o'`yxL;MDIz +MroUmOZUᘾoפ P+ukV#r+bص?q.pd8g{Җ6T$N Z9"a:'Α`TAHʵl֚ u~DPuH i+cN!s{C}||Y '- ~kevv F Uű?j.!'1`EJH9qmNR(fV6M9|bwC̦Я[LxdqC9XC\?=8d0S%QLmA7rÃķkA _G?# ƂʯW_%ua&I|&&WX~Ң IrxL< @izKGRtaR]Hd]5 @k7P\@"Di0ҖS!wOE8^j;WήFyI՝ߐZO(kϽzcDNGB OtH _*װ6~p$7vx9zz$;! 0  {'d͈.gq c>s^kWߏ8"YW]ӂ\--$dЂQh5VrǶ  >rNʤuRgH!X GC@Mn(OsATG!ѳu|!EV!1t~;qO7|o_BNڴCQƊ?( l4%@Oߑ+ll yuZjҍ}UZœƨy}y.iϟ_) 'Jk *2]kPh'#H#IV"[4"?jyk~Ey2"?#UT Tp4J)6W$&.7tcFBYdzђeIɢ=#I-YxJCbq,I;z8?:.Iawf@@H `@Ę?tS ;&)F革s^ʾ2DLurX;R|)|ţq^B]morZv]G流;ee$p/<SZjM~$w9A|&y*LT |6+X|`ila"o̊54hxg_]QLա Hbzy IXp;1Nɗ&)_vy7_oiendstream endobj 1318 0 obj << /Filter /FlateDecode /Length 2509 >> stream xZn8}W4yud0Ivg ȃ=ͱD)>HJ"Ֆ, $&NU\}9_qx::8"@Q`ZH~Z8Da-Vхqi)-dߣ(4ҪL _hi۰-V";#㧓՟Yږ!r4+ r8Qؾ\ߎeGw"kZ| +ӄJ Nc܅jފsVVW |-m ^8d|5iJ\ D 2h*ȶ}΋YNR6jȳ]Ns$D  0(;1(G" )| |-·pWs[BW~iYCRuzXS܆+9/FQeMzm/t7-) 2ܕ8#Ut$rt$ͱ-0\>C}#/q䄞+j\ސS\qRn bTHn[>?8X78(i!ߊvtayW6$-Pĸ"2{04|Yr5Uݭפ{wv)Zcyybu~¨u}Kex˚M-/4pC87I#!M&ި'X{0]z3DXظ6rbH+LYW'Hڑp;gBfIEΑsP:g,'3~'Vٜ& ,*^u}3iu~B퍖a,sT[x k!0,wshcNJ@X+T4S5qӞTSixD-x-s&71ntao[Fhw!j*9RZ >aA m) OSƀoX^~PM02/n}PNf4k;=Tl #DwsߵGѪV3 G.rH,Ʀn|.SG@4׈gTO8Yt0>0PT% 4ڹSܫ[h=t@]4(X1$6m=[Bᦿ~jµ#-˄2 ~C3,~ł=;;U_h;2{fhE^){!R[K>?NALkR_]}f1T8+R`uKՃ)' Qf~vڀ#wTsR*?%* o1wթBCtv.R EAt˳k5}^hMCޔ܄Tw[EDsk &K s'뺝0ǫ(墀po[3o(0M .YSH%SR Iu,{*۲PNS}Sd>Nx,fȐRTq%./gi׋‰|(m;52$9U Z?se~FU^Djө#`"M/3Pc`Z4JfV{g6ԡO'ɲ7VR+ += ۴I 0:e&3nM6Mqa q0WuG465Iwmu"ߵ;#)vdd(p}sJVE9YO7f *c|ֽDpJHAV.ڮpM$T)-WQlp.es jR ̋;=@,[#\6*gE{4pAՕ69ϔ E9Sh p:q.}Ëscj GASW_s6ցܣ_ A۔eESyB{o6}=CP,xY1*a|GE<K#VuW4ARՓʀ^a7P w=u7.]DƯ% źHЊ RN o{8r||5ҊL9_] %\oA[LkXVs@q\$۫1!LS"tnŷT~,kaޖy]5^A/Smʠ(a n`Ayw$|k"Дrz12Sčriϩu_%߈ݚ\+L5v?x٤IS4Q1_Aԫ'V/.;yxⲚ{ ]]}P۾88бCs2mq }u/"endstream endobj 1319 0 obj << /Filter /FlateDecode /Length 2604 >> stream xZr8}W&KOTa2c:ejem̓=5 $4$hEmڤ,-rt,d,N3k9nκ_I>{_~z&׍vɽ!88,z0GOBz}EdK ++tSXH; X:,os젡,H6]C  u;͟7:T۞ dD!_0DܸE9a VyC?pmbuX1weŜ7Q>ksch2i}a+e[OwiCebǸ!T>2tEh7xK);>C8+Zw/٤*2H~6 з;H{0Tƞ2:@I 2'BIxwƖ].+ ycT%[)JX`9hj[$fidu2^`&ʹ7ۑ?v|8 0)pE*"U/H\;kGyE͜hŽ߱R~B\l.89[,x՗# |~_+|UײdM[s# HJLOVҥZj,$idtlȗ26HIS9)ZGe0^߆nTljh0>I!그LHV)Qx_  YQ8DR*bBJǵ6餲"$pۜ8IC/댫xo Wp<a7J\E,Okup B"\ 16DSywQ4jv\W (*SXlO`4MKYYQuj=wNys 9xCZ2>ݵuaDRH |#aG,fAT^}0ބo;jMo)ִ1=z (NND+՗.nB'\{M>7ȃqlNW%-1h[;;ӗ7xgMγ^3ʁV ;^WF(r=I30V;hl]5t$n{4n,W*r_{,~6퐾Be9bf8Ȟ7p6+otSQ_Zl<-v /4seޢW( @,JJ\JR D@>A@IVp9Jҷryr°Z @J|%&,v-1FdqE3OO8;{\5#" `0Dvbʼ-d]B> stream xX[o6~𺢣I]u@o2d]xC<\dI%ob`,J8m1$o|;o`7_zF#ŷ__Nz1c`1Hr+!} l3yɪwW" -FA:7w٧=3>`ސ39z"p_&&!1l=6bdd5Q31esCb2&CG!ش]ǂ0Ԓvc咷oޟw"62r<\r(I&**Ȉ,⅀W Uoi5u=1FI2[k7t: G"j]^TTRAYf;' y 4OӊvcrQ@L`hRP;Lљ(APȚc9tIĝU<;u*h } -BPz9<|ݣ(\d)40!T P L}2Mgt :-a'a;1YvPja`{,U~;*xr üJ5txZɃ!o9zfhkV4A 2L3(6Eѥ6%@Չ3[P\<&웦$Klu96ט{ڐ1:lKUPLyWg) [jC0F9 lc=e\jegSuStbX0&BZ}C[d2*AG`8ۨ"t7S/t|7K]MPJUx%CWݍdļWv"Waysg- P@5YKrs2*Z-!qpc=ڻp7@n1e۾dV>Q 2㝴A0E<5̲M'|'S) V2-hNEP9'!9iz:6jyekZiuK=:>(Q(\޽ze&ܭ)7m> stream xZ]۸}0ЇʋY`NYL)PZʒ#R}\KQ)ȓiQalY/=s/y ^Yo.Zk$F~GNFjbq"_?a Wx#!,-+vz c%W\[7Vh6 jk n7F؉ƿfMQ\cZߓo;žXR)!/7zg hj%nh]/$X3,26n@܉a {`YT=yxxdģ$j#.!^TB;,%orQr/)O}OI_7UIqkp1q>" zq l޻XIRee醦?%ՓFܳ]l&4u$-VTb;%fB'6T_k${ L~h8UJSMKuAB n|L||enZH emGLPօ¶74d`<][ٕ<0ƉxG<]FXSDZ(pȊXL%vMV"9 /HyK]Vy*eQRC!_G{dtEг*"a+E~xh"BDI3+-zGsS1^ ~0(Jcf|Z EҤt˕{^%kL)8пuh~kAƏ(†+mY%˲.^Zs]ծ-m{Q{*)^h-%ݐ;V sӼ*(;[m#gz;M='QHդnf khlj@\ FnIm&/nMA(wv@8ȉs#=9.كWHΒ[V\KmӻMf6v#p_&A/,4ƊR/ZDښ-JI?J3H :3#5,.vu, I3|bC 8i5TۮҾƃ[8:4'b` dSD@ZOd4\ w*8K--7zk#Ďi>6nS-QjTfsUs;C}ui8MBTe+f`sί.ޟ8D !?:EgXdлzAqN?i=%@}^G;-;jT,=׍Q\|"1>H oMh#I]Sb !fVeCUCI=KuK/Xk3 )vյC<-gGё޶M"DIYyJ f>Cܠ/G٣lź0U =`w|{۳˟΁b'5(Ga h~t=2C9޲rxrDIH*o2ki_a>gV;* y̘ fjO]UP[@W'4d(Rp_uTtmOPޤĀEF8WT߷?W4J߿!w핁,˴u2yguu U˷50;j[1ºӃB{ TVZ#AȟᢙDȋt-HL8nքe esv32l֣)0* 2|.;\cQq V&=ovC`w6"G1qMRWtWBH OR?];Jx:n>,_ӭBֲ_ؑ\h*E4LUzf_i~ kLkCRE3j}6eQ=)s5I4F㝲% 5v3Fhd23{Wz̍[WR٪Я~GK3}'[  ,,C7 Cjʱ vQ܉.p|ʶ:ܘ vcDʙQC5b_TxP:J=k}5Poj3֜_h4)S*J+3U:K, 6M6;|rzCl#ӈ1+S($HƮ\|1ckendstream endobj 1322 0 obj << /Filter /FlateDecode /Length 2411 >> stream xYMo6W49h>N3YLnW(1`)%;ldիWVWQ啷_v˧+k[__AJPxj Ux(ͲպwqF<0NTȻv}/ǻO(Kn]*D8t)p wsVCY9m45Z$ct|:[4ګ찭˦Hj v:Vq}씬b%{{a8r1v($Zz`@9ݑ]z WKbۻ8m{J{;\=aIGnat @i^FI K8h[0j -崽1TUyu` &΋q a,ZE ~!{˘m]*<~|yiz>~X: /Юv~+qQσ7>8+gi( 8kHJ1cMQJWXfr*KG#ʲ!%yGlTcMw)nv>]?)עY34 gI7Tx7HA׃cXT궵#\::qd)dԽ cPkn]jU \CeWFsUIi+}|]0;طoOĀfki"wI}hRKٖfޡ΅%T K$9ueǏQD%k"?@4OQJ8g .N"Z-EpiL?Oűj-H۲}EG9# W'Tј?|퉴s5mAT՝9`ɝʡ|iLxmHRwЬ7\)"1#foWJRJn)[4#=*Jxo| UbO+h$lO&;_6~ F#9Csz XuBuA*Bε@ޫ7fn)3eVE71["ĦM~50Cq^pW$rDn|zܑ@&/9zn89/!οj7[1JWedr>ĻviwWFf6<[6v6% t뽈?N8AI_mE15Cq {eG3IceIsdknQ2+ԩ꫗+P;91'#s]xTφwf]KJ8<냮B[7]_&繭pRԜLf_V Woft 5jGD1'O.:-[]*Tdz$b)iIc¢(vt9~pG'l@O.Öx%^Oov]; agwFʻڠ7CU#"z2 r- g\.xL8ڐnDHCM~ҟYO2gt[bj|B]ߕsݿ +H'd |ZP>C9klچ<(tԌArciYj@ Ϟui7#gjbkG8EqϞE|C3q_1=jSPYQeM8s'3/ںJ.n0L'MYsU&SGe?u'S0I,Bp0L֧GAGCXOˆ׏V'GKڶJWxUmND혼>۳G$LMpE,Ȩ D1Иx͉Y j|klfw_K7tvv A/ark=\Ĩh䕤0_,ðLOb,^?׺l~OI¿p3C$B\jro>PRoՆt85)bK [;hV%GZ\I$W-Qendstream endobj 1323 0 obj << /Filter /FlateDecode /Length 2731 >> stream xZnF}W 2wî8 2b-N m^MRm>!ȻЃE쮮:uTџ [l/>_`qwɫߗ7A,%^,7HYXVGx!ΰB8M"xip>Gk,0JozSl0$ɜhw0qvl'nL%NupFp,i!QⓏtv)>F8GW:0m_ѺV6SvE!en^7Q2gL+u Z9vbf}m5s &vS==jPߪ"Vp}19hg?4UPsKP:T^E5CfZ:bMmnzNzǥ~WE@i>L>^?˨!;}YQ 7ͰA[W .`n;ɜ>7 vRKիW*蔵-h#A@7->\sAo_l bSnnpIV-VV/Ψ#1 OD5ٙM!/r3-:/ygSbp՚U7 hF1[]]^C.\ bz8ܯK ѡ.۲g%X8Uq)S㲮,ЗA[K]qQC \+1EӁk?oX|fVjU?Cu_(&ՠR"V׸S2qp9,{/ɗZ;wu>zJ=0,>va\H{GI;4I> Ͷ:D#%5zˇ7jr?VWyf:Լ<@b1;yzZ8Eqϒ;k`w#[BPZŐblcLt b_[s,4X˘L!f? N|7[#uk#tgpMrzF /KI2߭ uo^V͎&'( (ۻd Aa_rhmC=R"ooc5cJ=OqO䣁p-NOTS gc?ȼ#_7+nnq.2~,bRtN48y @endstream endobj 1324 0 obj << /Filter /FlateDecode /Length 2202 >> stream xYێ6}b;hqDQy$l/:]>L/ZLtHT_yE~ EI)ggmYɪ:uTdᪿ_ ~Rpd^tEBq"iXЗN@ v=xR]b꣇֗p D>jvݓILgĨl?i#Fי}LMbë^]\Q*kɊ߫_(X8 |Ie/-0dli(j_h+2Mks5:U. &QO'x):!61C?q{pƳseѺKޝhx bgyIH9 k/WHa{&qx^y "!E" ꣯4ibMV8&8p!NUO.>?7JâafA .96r*Nl]6O{Xkr^@Rm2c3钫;˚$V&A p*.MQ  gjűZZY%ʶ2$ ?⵲rmx笏 )ݞЧ=z8Xp]Btεpqo fY0$C^r` ;mBVM1JqH}f11͑~(OWPٳlruplkbA$م+%x[?@髨ShlMDzekXRhMJ5PAA@ԙBPT!lYY]BʐUd=fCǝXc~|n42j+ 1ٳY`R09EM! &h ҘjD>THNơMMЦ:*4`)`.yo1FUA4R90Pw%vЗ7BlmdY#8"͜ 3y=|:Q)eT` _b#(:۩@m>~Cvs|(G`hԷȧ̢#aAՌyLlg =VaH 5776fn83%]Fyq1i ;w^ӏ`W~<ҙj3Oϟ: rH?N6cPjҺ@V=<Ȅ x.ƓzLHQ,9h Nw R|\6ٹ԰jAvƎlnhmۇ/4p"9GAYip7q@_rABdpü.FHfMz_!vpAFz$AHpPsT1VN0 W?Kpendstream endobj 1325 0 obj << /Filter /FlateDecode /Length 2277 >> stream xY[o6~I-Z]&):k5uRmXHX㌳ ,`Y"yn9o3/:gߎHv0 g)S.Gj !$Q.܋CN>8bhX Mg?ю lVVhrK{uzkw.~-Z N4XO?2$AZyނ]mW^nJ$DTjYIA `A%^h8J T:\B@t=xﮤ3p9U2؛#΃,Wmut˫M ycVApIgp߆ Z^M-i<6i]|ӵ5F?Rۖ?hmW/9*y$1 UH"?94ݶf^Ey 3LfGd'Ŷ횊?z5DPU}@ 5ܓ :B7ЧOy^GŊupx]3 I~H`AMyr<[N:^iDAqA['~aB4O'3e7{Ap7"2y#xP b DqAA7Tpz%'϶CYp̪f5s2 }42`Xc0I8b$P jZZ>nYoXB=ͭ5WlQ7( ~d?2StcTtJd Vy?<)>HgpBffVDY;ûAs\kX'PeLGqA!E!bӓ@ %&%DpSB!(},D.Fn_ਙ)aHr#;PڝUorޭ+Cg *it8^fEfm b^^5['ӣ{sy%trJiϷ{I'@wǹg/WJ2QIsdoS?fNu=:e, % 䁻lO݈F-^fIeQ"Xj 羵_6W'%$4ҝZdA{:ϔg^/ A,w-i$&k^Qg%lEst[\m>oDD0UOVCy<,^&[G~Z%/ G^qn! i\j ݒpƙ[\jtHP2g]6N){Mфr5t4Qgh¶K;kG]%_A\VT2gySvMv n.dwf{][Y`Yx%7zz(P G| q0=*6b|2<6?8A@fjE۵\տ vti'vSĤ.SM ayORS ͕择zhɞ3=['=aȏñmnyYZ&jaUY>ABzdt+x#:XXGe>{(2=ϹɘIǮ3~ 6>0?BM Ӷ B#,$Yx+UH͠MwT$F02JY"8~rendstream endobj 1326 0 obj << /Filter /FlateDecode /Length 2411 >> stream xZKoW C mq H٣dS=?&ُ1g,r6|fꫯy!Ŀ:V7g?aj/V/g^AJPxxޞ K* P* k<pڼA.B`{aߑ@7πkkGK WSi6l]/n( 4g[fy`2 < Y51aG^8f-6{ ?}]^I<}\~ĨGAt:J!CpP0JZwcIyӴ*4HE5r520_8d=^|Gfri$>A8 4ljÇvͽip~waCHx箝w+Q- ?:ӖvWR2qN9=UF~pn~~ (d) 2p*$ D.|,&rT-~7uq]K5\u^Z~u?4~jZCq^|}θ9,07[bdTm__66ä#HF(LtGD r<Ԗ:nud j~mSi|N-sA[jVVvHop~ۆ4{Tdxe Ba/`[ib1~I42' 7f&( /53}?l^L'W 1e`=}/ZRu$\؝z(\gdx:x&(Z00v,%œr,V\Dsd) q-.+]2.p0){”'ԧi3&eA$2GޓȄFj_꜌t8NEY=78[oaU;I)\msiKKM.4'Yo:14nsܒWRC\X6u_mhkFZð@ZL3 ӫ5iCﶔdS݄tm+n: }}I>;=}gbmKy fPq [%O $#mŚtAyA3 r<*8Je9͘\ L*]4 oj( IǾ'-Kvd: lhAʺ~8ǭiM=c0X$-RR+:fhڙ9)| FT"#6.gXN,XԠ]*4FHL1ڭf4eH?n8WdY-R˛j`PYӷz@ӉEH9xI1+{4 ES־fAhvmߚNVH3'\)9HQRt\9mu:l*oLd&D@DSތ,,Ѥ<ca[ͣo<F1>}M~;nMA:mΖլYqOǑpaN[ hFMd? }Xa!A Ne|qjpzijw7\h Ɛ~`|[􆔿߉QVl;NweP6b:t4".Ցc]4q Ie%>p#âYv&P1 `v= #y|d#`\L;H oNDD0P,CdžV?J@|OE81x8 qVu&Lwz5^[,nZg5;aF,_vIݘsA{7fEۼPW@$UPI,3B @KrV(Nヿܘ1,!Z+=08z NҊg؋S@,%F/CtLlEO[_irʝaY& :3Oi)=Wslϫ e{̓vD'"+LHaz.-endstream endobj 1327 0 obj << /Filter /FlateDecode /Length 2309 >> stream xY[o8~ϯ0 jF]t]tg!YDۜʒJJΤvq1`)ٵ")^;ߡLfg grAmg[\\y7p9dX]tK,p,r `^ܣG[V #NMy& 'fC90죲ꞓG vH PZѲ1.|M1lX]x[/x'=fs?/bq <7[dَ2AkCZ$_R8_J7'ntZUZx}&C`bl>]-~}* S:iQjx;ŞO&OOyP&fj3/ĉm[ H$) a7{G$BƔ܅j˜?̫G  -xJz1~lҲ;h2o+Ae]qO^q0he^B_ b іx~[jGaQΫ8I^M m(_wv {϶F9Xs >#HE((yheϲʩ a4HUimJ8II"{ fR]r+(,+pZW1V50R1%H"k؄%ޛBT}h^WsVx}`[/-5:#-8mZ^Ps2Z)-DVVk+#H㖣&pb *dyNK㴬*Wl[+ׇR A(>F4c+F(9ȇЇA2phx*'"8Rk:e4a}Ң]QsX?uyH)ZZcTJ`PU" i3!s.r3_mSR"ABx7.7PoVZA]d #7c %OFD5SFGwT Г .N rN3J3*w8ҳfPxc >I4 d|'0ppL mP fn2娧;q=nLwjNwj ժ-AtCE q3I5 /b v 3}]{\^G  ȑl@Ըx<ۗe jʠMݒk_}L'hYa`: lj_v!6ۘxV_Z5}*xsGӥ|]F"9@2[-FhDųN֒}=wΆQnA)%U'R-ـ8Rzg%UZ(HTSdeVq)ɖ,Ys~$_r*2$|);u+XZQjo)=*~ι0 }4 6%&ֹj4YD>r^p$Cy%Fl`<0vFhkMnB78S„mjq"R= &xs}c_X:\ucrfz~yh~6t+c#E,>`evom&Odn"&=2B4/K Flu򶆆f==}*@Z\Gh=պzT繹j&>N-l7HLQ|:Xp`;`f 3O7!z7~j6UyoؗtĤGnx?wf()CD fP@e5YBˌ:-=~TOHpˆ }z ѭ˽E+\01]`[+op cLS++Qٕj=ͭb; ;_7hdXM}Jp%g?DjgͷZ_*kkN!:z=n%P*0iqHsvڒ0;|}8|#ǿVHfBN0U ]Rj_%8 \K{9rf˩o?gZendstream endobj 1328 0 obj << /Filter /FlateDecode /Length 1028 >> stream xV]o6}ׯ I"uPdÚC3`DRqb0I!@,J{ț8$NWQ_G7O^m78*KHE+94()>׫5g'}[9Jr?)L/CWή';f u\8P}>"+X6[wa+gnM#ah_y$gX4>p7a:lSOQTʖ ;.l;FSƔdM|ul[1C?_iMH#o:04}ٛA2i0ͥPdQیl3/Xm>}V~1M1w!8a,`z.)I)8D`qf4ָ*ˈ`ȗ\\aI%'V>".cΔƹP+~Q)΄ "sƸGpk<'N;(}<14\1}N;hJ.mjmcB @ОDL-0&5v簫Z4eCqo@ӷ[u|.%BRA]_Ҹ,%55Ҵ2uwHB _Tzʦ}S֔KL%R34SDyUDlz5耧{S^wY- hnJbom\@Ay N"4/ti"~Zw("%pؙ;뺅*:4m9ـv}wSipe %Pa[w͉cPɏ@G=?[m$OW4cSD- M.2f9c3PnFtz9E<]3Crc= e(K]<2-mڦ:[R?l1J08\发Tq*(H^U8)%;)_0.cbyaSp0 #C/|Tendstream endobj 1329 0 obj << /Filter /FlateDecode /Length 4258 >> stream x\o(ѶN|@S$Eڴh;l+]mZ.}gR{yK+78?ͪ*듪4R+1;_hʊ"Rl)43Z9s{O'ĒR%gQ9;:qc!3mJj()JJ|}rQs~&(/m7VnX7fTnneU\5}m|y~jȈʊə44DxeŋeUVBIYbW+V,3%EIoe~qCJ*`"!6fZ-$h),3?)PJy*sgz sa,;pE`j!|>p+ƋAɁlM(.QNNЌn0U* dYQic9٘1BJh^*#ggtV2;_Mm 4,nꝿqx D׉V M|׷a>v*rexG 7\Jl^u/ZP P8t3S|A+r| ۹(53-зqݤ5@BBV}_m! O OE"].0++'+0Is߶RtݖO[%P̤nY%C?X$CC/ЅWxt-8pQR񈧕"40gE74l+g{7ݺN&su{>q{ϣz{ %jfE q]^@bg5/y(g\`LTWne$s v^Co1KʥE\&KCl}IF:RW~;T]clYGӨ$C 3VG1Iza[O'S*U.kv u'HpUck"KXo5F'mJƄmS =QEqMdMv:@#TK."svmQML|6x`GQ\0ZJAhIMɀ'tUQ,`#~Av{0Ya_#I]M@$0tZ_# 0J)ơ DR v `` 70K3L^l_ôӝr]_&1nXIh 1?H Oh,p*e n"]C!@ɦ0 ڣ"פd#ٯ/>E.w hvhrC^};I( ';"eHŰ\hIy!|jP|CV✼@o#|Bo,j> ^4=.Cb^^,&l"\ ;WMf#Rq^uȽ-p&i)HJj1˜֥\2vTZpRdJ%)Iub7Cb4n"O1}""L 6["hPUs2x_5,-޽F5#@NttH@v0`BHYf9_%A8Xመ&.06W:ivr)qk r>~Dr*@w*T#unSȵBS^FmҪ_``&`L~3|J$91Jdn%bcRnwLL"QLPȤAAxR:;l⟂ KJq8ܸsJx*2Z6}չ2 pDVZXŕ\\cRW†\vI&c =L_UB}4Vp4|T)~<JH5}|&):S`粤;`}ʟ|_ E-~[x.!jDٚOt7_}l~K#-,WK5Gz!-PY;qHwO89=i'#,39(\;Ǫ'7 T&qVX{ʂ[ ,?U+{^RJ@N_%Rp,\~Ѭe Ic~27TRQhGwA-nJ G(s4Q&ݕp\Rlp4y@㋚j~F` yGh;\b!twmAaOzؠ 'V:'oYrĝӇs0ƇKvgk4:W\xn91B#X(+r^dX\j]<0OǺո V\\Z0-![ҟ6˝X P;K2*uj׾P]Ru?POendstream endobj 1330 0 obj << /Filter /FlateDecode /Length 5616 >> stream x\Ks$q$ƞTx؋d),Z3Ì5lYR[]=/v>,vϮ]@HDbZq߻r{ㅠ_/?wO˾B>Mt'ZĥDQn ~<:#(SRgq3uR#KE>Ô:"eOJP =1^C@%V6*n߇Q"pWE*׊ޔ[l%w3F.&` gJլB+$8ߗ(@v{Pʰ^f 4-$P֊%`v}>˂)(a~1Jo>%6z4J0yE*c?hF' [gZ6Ȋ tX!Vo?)Ї;7ʖ#X_`"i{5향7`*z4 &vδcƞxXƻ'h'Fy 93Neg(loՀaGhi;y2DAV9HhEl Y''sO;Z( dYtLɗIi1CZ{RkTGkaF0xj/^IE>ꨌsCIܠ֒XeHAiÉvtb&9s&tW}?]_~KA8_*(ѵ|v7#HYu]I+1$ZgN6_6Op.t;aL)ϟ+-Dq]M>.9$AV-"yiasnwh:P%cYv!7]C0AK6~F`S[w ;X<>Eo1^ﺟ[Ԫ􎸇15iFKk8V*0Ɖ1 $< 7[c^6)aWca9Pײ1l˅<,e1?]~s[LO;ZJoQטN5|u MJxqNirF&l!vQ0זL{Ɇ~~yP Ym1v~r fpDBV}:g}#hZ .$Fhz{Wx V)[c:7e j1pB$,bKRDG}LٯhiS/'`船se+}-U:pۼ<º#ρI:~H@3 XtZ+^V6 Vl75Y/-{8FyC)WaZ#X8h/Ub#Z<hPZMV_-):isDJ$oeSE֓xYx94q(y+0ǎ^_7#xqړxJ}TX~nsp1rw]K _bBIA洁0t|Ou=/Z7DԈ="ōu 5oP1o b] yiVɯ#`RGeV1ĞKElO0 zdKi)(st5LS ިĎ*gZ :/ Eoqq) w%i轿:”>Y̭vKY8;ioIrqQ e91(!Y;g5RΨ&?q2 m |_[ OmjozqL&$zG԰xk2L55`FҔsi4p1t@YƼ 8*'+3XT D{5'E0l-4-҄is K@bZ {SG*Ai>:'kg1VXeJCK)) X|gpˏZq4@+T"qfhاlOgVV^Lʀ]9a+ry[0G'΋%.a]=/'.j#͎!֞Moq$17^C54t*_M)&xQ"" 'x>1"x8yb:~Ip|j|9>t;\ώΐBMhl/e!l:vrgшZ NZ2dPb %LrVHh{LZŷ\MJFg}V\Oi;\D ]ʶ3UlEL37p~2\UsʡM08)z)buVݜ9lkWWdwElS5@ mݵjM9B&%O) dJ)E<.O9|L}Ti_3j-#J,Q6kԷ1jR%No6h;̛rr:4&XYכGŠ[]_< CUAiPʲk"LX^K;k>RB21PXcJ^ O ͇h|IJstGMu#`LİUj"v"/)l?tDC Ib*7v,=Ӈ] >ag^w*ާe9>!2>pi iNJl(:5XLjFZH΍`k0k ݠ- kS( L 9(3)ozީ.SR=Ux>SY_TN2,.VF 84@Iљ;2M=|ZT5c )2 Gw!(I|34Xx<ܙDW/"&ah1e`X[$a*j)f@VĻ\{]5: tr`m0 ;7IyTE#mML.A}[Dՠh\짭Ťo|Ru^=HX&g#Ye|đ9HsRz³+(.?l><}د{1ã囨ϡ2ͳ$&Vzu,XHedgcI ʄc6|w&aڍ_naHPn鮜v jwR}iSzc3d!}Jpʛ: PK⦣}mF,vI.UN aE"ZwLAIÓUGû %ʊ^*7SduGbSR@|jbWE"ԫ5P|KC sR׮\"OCaSZidgObHbb$-I3#(%Pw(UKdjΖT/V6݉+/jgRfdJ+KsKGF6K&3]狷E+)_QAwTFL`Q\.)7=~Gg]:A[•IJ}3V)`hzێ*=VBNF6{.`!g r %ٯ(ޖhw2Ǟ}(-W[%Y&/YrξrU"A o~,nLw hלS\9-!̋tȱ"Gќ-J${$#lN 稕hy]b˟/endstream endobj 1331 0 obj << /Filter /FlateDecode /Length 4745 >> stream x[Y$7r~` MZmH>H~鳄Vs/wu4nB#e'1_{^ބAa'gg|ݰ>lzRzj_ϋyOB*' iw:4R FNNH9 K¤<l\ur ,DKy ,$/H# I!U5Yc.6XE2yU ޙK`YX`)1I@Z,n iNymoប7?W?ȅpu)jq|qiBNF[7z8V(f/o`ls.땚dfauCtX֋ild\!+A:kpUj&_lH ?Iqb(<)Hx DoV(R;y$own؁ Y37{nrw{[B =k*J6u.7Ç N5|# 6{9bC_ui|)*D*D Bb[E8 ͩjbR6jqL32#s)r\/@ʠ`ea& 5dѤ h yXkd*#1 tBDrn[7p7`U()CN :c(HVh@UT(;֨y o+<_!qT q5\de•af?É,Ab(Tob,\y  鰠(!A~ ,8̢"Fiv黴 rmX f뼆V|*a8P5 D@"V2 *Jq:$kNX f뼆VVtX!r+̜ X浴FU7y/A9"aa ayE_3$b%X:Z)ѼZQuXh=}g6̳ZJ/fk)̧XK<j] 15Bbt煦#s{/=B7$vkc! #K)vZAD9.CHD"B! _Jtv T ΡlҲ$=76Kqw{qnԌkƎN_W 7 @&1R& 5g?  ewGsBznfw ΎA1:ܷ߶w]&kf?v/n K_~N_+bD\Ȃa\X Dur+cKWnj"[fnXC) EE\QjoVZY!SA] 0KM8`6&:;!B}K{͸YVwF=,3M09LG;-#/fB?i[kړ2[iX mnhO;@jk muu}2+VIVpտ86Yh5. ѹ1=lOn<Å!tUn!yyqC? ]v`жc@4PK8c>v(h7"Z>VVZ:")2=k2B2㒓&v+l 9>!m}).;>~̭Օd.oz -0'@ uF Wvy?~K Fvf_}|wsGV8xS%M̴a)!a-'}L[(cl:J:b\(}KD\rbtPriLx'/~Ss`~e7#l(H'GVef, 5rmШ_nwFP.ߜ9v^ũ@V>N&[;S4ƒv%/~?"(= _̞ vxrpS" m+*.ڲ?wt;-uI1"fG&*:q@MtuytsU.1<VN(>} Z.k k[ѧ£kSƯI{sm:v Ͽ"Mw D?vDJU/j)b.qZ}&kupys}@: 4ȩ>&#\xJ+ P 7I?jǠZ٢;)WM[b`(8O؁_r8Y]%(&4#NDڢpF7S7J՛69Xi Ku*mRnE.yN$dh vL ߜMF;9/ |$v,=&Ei^M`5H$*@͐G@; 1CYqU [Ec&i4^qQxO*yo awN)sB (BP ]XZa3& $u@xG4*0Af(B6(0[F)0 UHT_$0Yx LY|?m#FIJNՐ4t.)vX\9-'QbcczAmi \oOCO6'9^T'(|Ih>_-vvUJ:'i2PڈP_Z S*V(9 4&-6eIA/r|E)U8G;;8/T0Ĺ3Q2t%S21SCtx`MۈGKEΤŵos&#wy-ꂖ3(?Ki}T(p8i5rWlO"^{}[gz3!ʫ"1Dz;194㉠94"?s/J֠&րMN&%H^fD(C2g DJi<;Cjː*yxU%VC/jCa>4.JJ*/BZe=`P%ۉ 4=ʋVyR]T#S]vC[d;AOLMxck;è_4o? 0{MS|JO%ecɞ=ŽFoq" *[Zx0%+_nytmyuj;nkCWsZG/Ti F=.tzLP-71O"*b kX(({0?)owք(Ȣ{K3r-Cx2EUb /jU8&%o9tn+[R!:`,aOU.R?Gik{jaF2>\ͰJR=[E#/RRq."B :am'W"$a\3.tI+%wF |`=~IYendstream endobj 1332 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1333 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1334 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1335 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1336 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1337 0 obj << /Filter /FlateDecode /Length 5011 >> stream x\[r~߿#;i$mKV`vgwx.{=?;_nr4 =t7YdY?F]+FKggߎҟϗg/+,v[9յ>Qg:ZζVկj{7wmg2Y D&޸M~zL+|K'D_jf檤3g];XN,x: AN=jah }r=[O˱V(oon֫E~j&WA* 5e1mdRzX̖$Ff>Y&4vdyͼmL$|m𺳂:=<= [?3:#0/d9Lor&#|:V\zk. n7ۻ6?BpeXۄUy2PYmR`5,՛'P!kSQ}ޭ$}&jEz5{A[ՠ0kmRKO|~}>B֐(\L#KAslʄ::DXM+ D?, %rJV =ZE5#[,YRY$NVfAI[/N:;Nܑ$a~If-:;?Iapӹt}S^E?:5,l3nKξjOs{ۦ64yBy2}}itzJ f\M[R<ߤq1Gz;lc"i5RƳ?ˆͮY5d$Aolg)=|0B?YpNC$tv L*ELV찝XknG@&](ZPt'zW%|{]1mGIzwU|Ɍ`_nyxZ\$= JRݣkj ,Zʑgz(X15[E'hv>cF" ST0(zrt>#,\^   nja,t6/4"0]EW#HR k; -Ao'X#TsIH Ò(&^OKgF5G ?]naH6ege%tZMŢ؄Q5F mp^6Dp5XDHe'V #i%ƀ&&K^ yanD ByT_[O$^dЕgXDf Q8=^%a*~IHwDaOFK.ZRZ߾{E*Q]Dz͓jv3U*ҹa3OhYŸdNh݇{?^gQpl㷧" (!c dSzF)OTEniم-Xb1ZOπ)r<{oH4g轅og/VXmZ 7&Z$SLX<ceD*w?c!@R{AߎJ6i5ǒ7I!;s29R-9`"=o}/m (QR=7RsT>/m3f.v5Y/ pGb<%ӕ*Xy`5S V[N*Xޅ}>{yB[!Xx2#EDZU ȧCZ@V%( D,Lp*p|RAiĒ5ޘwg #Zόz<_0RM4L\ժ3җD5z8Mttd4̶W+G:IY/ԟĉCAx;>}(iII88.h}0cq Hs:9fuB9zzQ<W`%* K:Q?WI5VA"$O5}T̯^}L R,hZC;{%cPp*lMT^@g]eGz y(B'hsJJmjGz "{D'p(p:N*X/38=43WtlT3Jg8ɧiX޶}_$凡t  `>srNlL;زt):jW%͗ʿO^t콩yIYgŒ>)7DMBKÄpF͡z2 ةx 5*' vOj!%Au| IڒBLPF$2QX|FW:q0h[iϛl ryA=tt@R"؜q-Q^z)K T'}txh+u;|ϧ| ΆVWQKC,m5:zkUruT1Mc9H?1 *N?hW!oxR]lj7Ls?.Vx90A3-n~XN{gPeshj901!W6"ҹ0-ڤs׵nt;E<5sAgڲl3;E_r!P:M;7CMZO!pLTI] %*TLJ6'(R'R=N| 0xq(KEITD~G[.Hhڱ.,; H4=z%z^ =#'_|V.w%i0* 2GT7uy<XݧDS&>enOJh #ȞCӗQl6(ғb"?R5 ۷^>dpS{^.^.Z4_rǃ"EתNGa)ᐂi* |\#z !{DѵІ/ѕ$-w %RT[R\Z6oy_tP] G E/fJ5QY?7(_dTE;=a27\~m ٬m/6tr螜pl1\3tz؜n3_mb6nif\RY0$bغ#ӆ9]}yCG/"r~K6xOAK:ڽv]Çg؊pZad%@)6,loӲthndpBcOFe+I tOIO5GULOixP#iYs! g3~ܿBhi;(  dLh߇EJ"K-+=_I> stream x[msߐC?P3gx5Mtfrq'ԝ-l(QﻻH@lٝ,}yYUYjvsogz˳L.?w̺;gFTunv>wn^P1Wlsx,OI.*%d1mlnp*+Ɗsxj5Vo$9xkQ [:!grBϹ8GɔQ Zha}U@;*Q49l_م1FuX}sԪ8"s#c( 6x]wl2QIuh7UHˆz2Uk¡wsFM_n"#O\r tdJUۢ(+'q8XÐ\DZaY ;f軘I%į'\,߄*`1[53*J &Y8ᬔ~|T %!$QU\-WU*gBf?87}|$ !!45fLU eumȣM_/ҐƐ8Q$ `.Fs =`ࣜ"DHG`ҁD17S"Y $/%/Dɟ I-TZ\xf:oE$X@^r0FPdUʍH==P5LvEN"" a,X8-,~N0(O Tٜ-~4X7}&A81¦%*H 28{.鰘gxU%l R8p*Ap"fC,J`;F%< 'Uq,Ũ(89J*wc@ wt~,F:{B;iS<QhߛE}T.Oa),QKHI515M1#mik@[5_s0Fv_.ݏ\lVcC0y p^ 6x9ūgq>~ws'n+liNy-$ o(*gj*L шTUv}-0ۈt]s8I:H)縶T"絙) oV{2~Ya$nݮc4BS{McbǤmSe`~l`1†>Ur;se % F yż0 he 8eoSВiw6nax@XnÜX(Ws8OXC+NC<[/9:T{4r8b=~meNC0Lhj `T.SC> Y"&чVT)C˲;iK6Ը eSl­Y|A6K\6vhj]eN-A%븄'˞1)SG4;`c hW>̈0@滰T(Ew΢ .p4'4EŋX tw;i ,{sPV&WHKɀ^dϰX͜ 7ho1~]TziM|{s *UK\dUeJNg6tP1>pF6=զ}C"@T+#B;.?{܆-£~šlK9qsi|lC1.w^p?Ǿ/!L&RCԌbŜ46jx묙l<"N*Se`MpH{9Zo uB0ttlt̲ѥy6ΌDXEe;`c\ƍҫzPFt}19>caXvsH73],E! X#tBNp hE/ac7)}.rYf~PD"*9'g FS3!\>\k[pkBl\-o/){ dNBy@ۃlhORЎ{ i1<@k>b&-|ũXuX,Ռ7lceS'^ȅ{ +,3;xQQ=  D%=?돔ۘ%j= !`aLT@hhv w.j鮊Qw&{&" Jzb֨ܛ};/( QVAħśUM|.L7daWrfL.ԇ4sgo<`{oϬ;0[A w7reti?qag^fR2D(3&P{a( a ~"v V_Et<Ď @n4p+mR:E=JnYt)|m8 MhrZc"Ye c76çfw0VҊUmW}Yo,n} 1jxXv^QW1'Y0ힹROnݸMMxendstream endobj 1339 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1340 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1341 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1342 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1343 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1344 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 44 /SMask 1181 0 R /Subtype /Image /Width 44 /Length 1084 >> stream AdobedC    %,'..+'+*17F;14B4*+=S>BHJNON/;V\UL[FMNKC $$K2+2KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,," }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?| Iq v?Ӓ/nG,EP0B>kW{B7~cmF7֚ׯp;pn5C`K1G^kz ^Ssp6*ͪw|9n=GUGh9V_z)]Ggv,rX}i+iST࢏>Rw ($'PgB۹ʎ7J|yw-͜oˎ-Ɂ`cmo>!l'ȪWsH$*0AXƅ8˝-Ku$+zEendstream endobj 1345 0 obj << /Type /XRef /Length 731 /Filter /FlateDecode /DecodeParms << /Columns 5 /Predictor 12 >> /W [ 1 3 1 ] /Info 3 0 R /Root 2 0 R /Size 1346 /ID [] >> stream xKHTQ98gԙ<{`IR((TR3%T VJ[60\TeIZXP =l]apݟYssgW[ʲK g)˞Lgbߐg7T3y8ЛsjF9s 98gq74 41朦E✳+s 9`8{q7t,/71朮fqNCozsN6yl'uaÙ:˙82$ T 2T*K& FZWÍVX.,?'L=VF7$v lG >RRPtp? I)~+z2K UQm<0\7%TrJ6QIhw~uw([fOqǖ 2JW/C.pu%&xhcka|fgIbb+,{,5 V>Α>Iι;nU/kOUŹ۝aY n0 y'0>mW.`,gYosA%Y4QU=8O0 ۄ~ٍ@8]/tf~#DM^P endstream endobj startxref 662947 %%EOF irace/inst/doc/irace-package.R0000644000176200001440000002715514752430276015702 0ustar liggesusers## ----setup, include=FALSE----------------------------------------------------- library(knitr) knit_hooks$set(inline = function(x) { if (is.numeric(x)) return(knitr:::format_sci(x, 'latex')) highr::hi_latex(x) }) ## ----include=FALSE------------------------------------------------------------ library(knitr) ## ----exampleload,eval=TRUE,include=FALSE---------------------------- library("irace") load("examples.Rdata") # loads "experiment" and "output" iraceResults <- irace::read_logfile(system.file(package="irace", "exdata", "irace-acotsp.Rdata", mustWork=TRUE)) log_ablation_file <- system.file(package="irace", "exdata", "log-ablation.Rdata", mustWork = TRUE) load(log_ablation_file) options(width = 70) ## ----R_irace_install, prompt=FALSE, eval=FALSE---------------------- # install.packages("irace") ## ----R_irace_launch,eval=FALSE, prompt=FALSE------------------------ # library("irace") # q() # To exit R ## ----install_win1,eval=FALSE, prompt=FALSE-------------------------- # install.packages("", repos = NULL) ## ----install_win2,eval=FALSE, prompt=FALSE-------------------------- # # Replace with the path to the downloaded file. # # Replace with the path used for installation. # install.packages("", repos = NULL, lib = "") # # Tell R where to find R_LIBS_USER. # # This must be executed for every new session. # .libPaths(c("", .libPaths())) ## ----R_irace_test1, prompt=FALSE, eval=FALSE------------------------ # # Load the package # library("irace") # # Obtain the installation path # system.file(package = "irace") ## ----windows_irace_help ,eval=FALSE, prompt=FALSE------------------- # library("irace") # irace_cmdline("--help") ## ----irace_R_check, eval=FALSE, prompt=FALSE------------------------ # library("irace") # scenario <- readScenario(filename = "scenario.txt", # scenario = defaultScenario()) # checkIraceScenario(scenario = scenario) ## ----irace_R_exe, eval=FALSE, prompt=FALSE-------------------------- # library("irace") # # Go to the directory containing the scenario files # setwd("./tuning") # scenario <- readScenario(filename = "scenario.txt", # scenario = defaultScenario()) # irace_main(scenario = scenario) ## ----runexample2,prompt=FALSE,eval=FALSE---------------------------- # library("irace") # setwd("./tuning/") # irace_cmdline() ## ----readParameters,prompt=FALSE, eval=FALSE------------------------ # parameters <- readParameters(file = "parameters.txt") ## ----parametersetup,eval=TRUE,include=FALSE------------------------- # Setup example parameters <- readParameters(text=' algorithm "--" c (as,mmas,eas,ras,acs) ants "--ants " i (5, 100) q0 "--q0 " r (0.0, 1.0) | algorithm %in% c("acs") ') ## ----parameterlist,eval=TRUE, prompt=TRUE, size='normalsize', comment=""---- str(parameters, vec.len = 10) ## ----targetRunner,prompt=FALSE, eval=FALSE-------------------------- # targetRunner(experiment, scenario) ## ----experimentlist,eval=TRUE,size='normalsize', prompt=TRUE, comment=""---- print(experiment) ## ----targetEvaluator, eval=FALSE------------------------------------ # targetEvaluator(experiment, num_configurations, all_conf_id, scenario, # target_runner_call) ## ----instance1, prompt=FALSE, eval=FALSE---------------------------- # scenario$instances <- c("rosenbrock_20 --function=12 --nvar 20", # "rosenbrock_40 --function=12 --nvar 30", # "rastrigin_20 --function=15 --nvar 20", # "rastrigin_40 --function=15 --nvar 30") ## ----repairEx,prompt=FALSE, eval=FALSE------------------------------ # repairConfiguration = function(configuration, parameters) # { # isreal <- names(which(parameters$types[colnames(configuration)] == "r")) # # This ignores 'digits' # c_real <- unlist(configuration[isreal]) # c_real <- c_real / sum(c_real) # configuration[isreal] <- c_real # return(configuration) # } ## ----repairEx2,prompt=FALSE, eval=FALSE----------------------------- # repairConfiguration = function(configuration, parameters) # { # columns <- c("p1","p2","p3") # # cat("Before"); print(configuration) # configuration[columns] <- sort(unlist(configuration[columns], use.names=FALSE)) # # cat("After"); print(configuration) # return(configuration) # } ## ----targetRunnerParallel,prompt=FALSE, eval=FALSE------------------ # targetRunnerParallel(experiments, exec_target_runner, scenario, target_runner) ## ----targetRunnerParallel2,prompt=FALSE, eval=FALSE----------------- # targetRunnerParallel <- function(experiments, exec_target_runner, scenario, # target_runner) # { # lapply(experiments, exec_target_runner, scenario = scenario, # target_runner = target_runner) # } ## ----targetRunnerParallel3,prompt=FALSE, eval=TRUE------------------ print(output) ## ----testing_r, prompt=FALSE, eval=FALSE---------------------------- # testing_fromlog(logFile = "./irace.Rdata", testNbElites = 1) ## ----change_recover, prompt=TRUE, eval=FALSE------------------------ # iraceResults <- read_logfile("./tuning/irace.Rdata") # new_path <- "./experiments/tuning/instances/" # iraceResults$scenario$instances <- # paste0(new_path, basename(iraceResults$scenario$instances)) # save(iraceResults, file="./tuning/irace.Rdata") ## ----load_rdata, prompt=FALSE, eval=FALSE--------------------------- # logfile <- system.file(package="irace", "exdata", "irace-acotsp.Rdata", mustWork=TRUE) # iraceResults <- read_logfile(logfile) ## ----show_version, prompt=TRUE, eval=TRUE, comment=""--------------- iraceResults$irace_version ## ----show_configurations, prompt=TRUE, eval=TRUE, comment=""-------- head(iraceResults$allConfigurations) ## ----show_idelites, prompt=TRUE, eval=TRUE, comment=""-------------- print(iraceResults$allElites) ## ----get_elites, prompt=TRUE, eval=TRUE, comment=""----------------- logfile <- system.file(package="irace", "exdata", "irace-acotsp.Rdata", mustWork=TRUE) getFinalElites(logfile, n = 0) ## ----show_iditelites, prompt=TRUE, eval=TRUE, comment=""------------ print(iraceResults$iterationElites) ## ----get_elite, prompt=TRUE, eval=TRUE, comment=""------------------ last <- length(iraceResults$iterationElites) id <- iraceResults$iterationElites[last] getConfigurationById(iraceResults, ids = id) ## ----get_experiments, prompt=TRUE, eval=TRUE, comment=""------------ # As an example, we use the best configuration found best_config <- getFinalElites(iraceResults, n = 1) best_id <- as.character(best_config$.ID.) # Obtain the results of the best configuration all_exp <- iraceResults$experiments[, best_id] # all_exp is a vector and names(all_exp) is the (instance,seed) index. all_exp # Obtain the results of the first and best configurations all_exp <- iraceResults$experiments[, c("1", best_id)] # all_exp is a matrix: colnames(all_exp) is configurationID and # rownames(all_exp) is the (instance,seed) index. all_exp ## ----get_instance_seed, prompt=TRUE, eval=TRUE, comment=""---------- # As an example, we get instanceID, seeds and instances of the experiments # of the best configuration. # We could get the indexes of the instances on which at least one # configuration was executed: pair_index <- which(apply(!is.na(all_exp), 1L, any)) # or the instances on which all configurations were executed: pair_index <- which(apply(!is.na(all_exp), 1L, all)) # but in this example we get the indexes of the instances executed for # the best configuration. pair_index <- which(!is.na(all_exp[, best_id])) instanceID <- get_instanceID_seed_pairs(iraceResults)[["instanceID"]][pair_index] # or get the seeds get_instanceID_seed_pairs(iraceResults)[["seed"]][pair_index] # or obtain the actual instances. iraceResults$scenario$instances[instanceID] # If the instances are of atomic type (integers, floating-point numbers or # character strings), the above is similar to: get_instanceID_seed_pairs(iraceResults, index = pair_index, instances=TRUE) ## ----get_model, prompt=TRUE, eval=TRUE, comment=""------------------ # As an example, we get the model probabilities for the # localsearch parameter. iraceResults$state$model["localsearch"] # The order of the probabilities corresponds to: iraceResults$scenario$parameters$domains$localsearch ## ----get_test_exp, prompt=TRUE, eval=TRUE, comment=""--------------- # Get the results of the testing iraceResults$testing$experiments ## ----get_test_seeds, prompt=TRUE, eval=TRUE, comment=""------------- # Get the seeds used for testing iraceResults$testing$seeds ## ----wilcox_test,prompt=TRUE, eval=TRUE, comment=""----------------- results <- iraceResults$testing$experiments # Wilcoxon paired test conf <- gl(ncol(results), # number of configurations nrow(results), # number of instances labels = colnames(results)) pairwise.wilcox.test (as.vector(results), conf, paired = TRUE, p.adj = "bonf") ## ----conc, prompt=TRUE, eval=TRUE, comment=""----------------------- irace:::concordance(iraceResults$testing$experiments) ## ----testEvo, fig.pos="tb", fig.align="center", out.width='0.7\\textwidth', fig.cap="Testing set performance of the best-so-far configuration over number of experiments. Label of each point is the configuration ID.", prompt=FALSE, eval=TRUE, comment=""---- # Get summary data from the logfile. irs <- irace_summarise(iraceResults) # Get number of iterations iters <- irs$n_iterations # Get number of experiments (runs of target-runner) up to each iteration fes <- cumsum(table(iraceResults$state$experiment_log[["iteration"]])) # Get the mean value of all experiments executed up to each iteration # for the best configuration of that iteration. elites <- as.character(iraceResults$iterationElites) values <- colMeans(iraceResults$testing$experiments[, elites]) stderr <- function(x) sqrt(var(x)/length(x)) err <- apply(iraceResults$testing$experiments[, elites], 2L, stderr) plot(fes, values, type = "s", xlab = "Number of runs of the target algorithm", ylab = "Mean value over testing set", ylim=c(min(values-err),max(values+err))) points(fes, values, pch=19) arrows(fes, values - err, fes, values + err, length=0.05, angle=90, code=3) text(fes, values, elites, pos = 1) ## ----ablation, prompt=FALSE, eval=FALSE----------------------------- # ablog <- ablation("irace.Rdata", src = 1) # plotAblation(ablog) ## ----testAb, fig.pos="htb!", fig.align="center", out.width="0.75\\textwidth", fig.cap="Example of plot generated by \\code{plotAblation()}.", prompt=FALSE, eval=TRUE, echo=FALSE---- logfile <- system.file(package="irace", "exdata", "log-ablation.Rdata", mustWork=TRUE) plotAblation(logfile) ## ----ablation_cmdline, prompt=FALSE, eval=TRUE,echo=FALSE, comment=""---- ablation_cmdline("--help") ## ----postsel, prompt=FALSE, eval=FALSE------------------------------ # # Execute all elite configurations in the iterations # psRace("irace.Rdata", max_experiments = 0.5, iteration_elites=TRUE) # # Execute a set of configurations IDs providing budget # psRace("irace.Rdata", conf_ids = c(34, 87, 102, 172, 293), max_experiments = 500) ## ----targetCmdline, prompt=FALSE, eval=FALSE------------------------ # targetRunner="./real_target_runner.py" # targetRunnerLauncher="python" # targetCmdLine="-m {targetRunner} {configurationID} {instanceID}\ # --seed {seed} -i {instance} --cutoff {bound} {targetRunnerArgs}" ## ----faq3, eval=FALSE----------------------------------------------- # library(Rmpi) # mpi.spawn.Rslaves(nslaves = 10) # paths <- mpi.applyLB(1:10, function(x) { # library(irace); return(path.package("irace")) }) # print(paths) ## ----R_irace_home2, prompt=FALSE, eval=FALSE------------------------ # system.file(package = "irace") irace/inst/doc/irace-package.Rnw0000644000176200001440000052354114752430247016245 0ustar liggesusers% !Rnw weave = knitr %%% DO NOT EDIT the .tex file directly since it is generated from the .Rnw %%% sources. %\VignetteEngine{knitr::knitr} %\VignetteIndexEntry{irace package: User Guide} %\VignetteDepends{knitr} %\VignetteCompiler{knitr} \synctex=1 \RequirePackage{xparse} \RequirePackage[dvipsnames]{xcolor} \documentclass[a4paper,english]{article} \usepackage[utf8]{inputenc} \usepackage[T1]{fontenc} \usepackage{lmodern} \usepackage[a4paper]{geometry} % It saves some pages \usepackage[english]{babel} \usepackage{ifthen} \newboolean{Release} \setboolean{Release}{true} \usepackage{calc} \usepackage{afterpage} \usepackage{algorithm,algorithmic} \usepackage{booktabs} \usepackage{tabularx} \usepackage{xspace} \usepackage{amsmath,amssymb} \usepackage{relsize} \usepackage{fancyvrb} \usepackage{underscore} \usepackage{microtype} % \texttt{test -- test} keeps the "--" as "--" (and does not convert it to an en dash) \DisableLigatures{encoding = T1, family = tt* } \usepackage[hyphens]{url} \usepackage{hyperref} \usepackage[numbers]{natbib} \usepackage[nottoc]{tocbibind} %% For autoref \hypersetup{ colorlinks, linkcolor={red!50!black}, citecolor={blue!50!black}, urlcolor={blue!70!black} } \addto\extrasenglish{% \def\sectionautorefname{Section} \let\subsectionautorefname\sectionautorefname \let\subsubsectionautorefname\sectionautorefname } \usepackage[titletoc, title]{appendix} % Fix use with \autoref \newcommand*{\Appendixautorefname}{Appendix} \usepackage{tocloft} \setlength{\cftsubsecnumwidth}{3em}% Set length of number width in ToC for \subsection \usepackage[inline]{enumitem} \setlist[enumerate]{leftmargin=*,widest=00} \setlist[itemize]{leftmargin=1.5em} %% FIXME: listing is very limited, we should use 'minted' \usepackage{listings} \lstdefinestyle{BashInputStyle}{ language=bash,% basicstyle=\ttfamily,% numbers=none,% frame=tb,% rulecolor=\color{lightgray}, % framesep=1ex, framexleftmargin=1ex, columns=fullflexible,% backgroundcolor=\color{yellow!05},% linewidth=\linewidth,% % xleftmargin=1\linewidth,% identifierstyle=\color{darkgray},% keywordstyle=\color{darkgray},% keywordstyle={[2]\color{Cyan}},% keywordstyle={[3]\color{olive}},% stringstyle=\color{MidnightBlue},% commentstyle=\color{RedOrange},% morestring=[b]',% showstringspaces=false } \DefineVerbatimEnvironment{Code}{Verbatim}{} \DefineVerbatimEnvironment{CodeInput}{Verbatim}{fontshape=rm} \DefineVerbatimEnvironment{CodeOutput}{Verbatim}{} \newenvironment{CodeChunk}{}{} \newcommand{\IRACEHOME}[1]{\hyperlink{irace_home}{\path{$IRACE_HOME}}\path{#1}} \providecommand{\keywords}[1]{\textbf{\textit{Index terms---}} #1} % Simple font selection is not good enough. For example, |\texttt{--}| % gives `\texttt{--}', i.e., an endash in typewriter font. Hence, we % need to turn off ligatures, which currently only happens for commands % |\code| and |\samp| and the ones derived from them. Hyphenation is % another issue; it should really be turned off inside |\samp|. And % most importantly, \LaTeX{} special characters are a nightmare. E.g., % one needs |\~{}| to produce a tilde in a file name marked by |\file|. % Perhaps a few years ago, most users would have agreed that this may be % unfortunate but should not be changed to ensure consistency. But with % the advent of the WWW and the need for getting `|~|' and `|#|' into % URLs, commands which only treat the escape and grouping characters % specially have gained acceptance \makeatletter \DeclareRobustCommand\code{\bgroup\@makeother\_\@makeother\~\@makeother\$\@noligs\@codex} \def\@codex#1{\texorpdfstring% {{\text{\normalfont\ttfamily\hyphenchar\font=-1 #1}}}% {#1}\egroup} \makeatother \let\proglang=\textsf \newcommand{\pkg}[1]{{\fontseries{b}\selectfont #1}} \newcommand{\aR}{\proglang{R}\xspace} \newcommand{\MATLAB}{\proglang{MATLAB}\xspace} \newcommand{\eg}{e.g.,\xspace} \newcommand{\SoftwarePackage}{\pkg} \newcommand{\ACOTSP}{\SoftwarePackage{ACOTSP}\xspace} %% How to use this command: % Parameter with one short switch: \defparameter[short]{paramName}{long}{default} % Parameter without short switch: \defparameter{paramName}{long}{default} % Parameter without switch: \defparameter{paramName}{}{default} \newcommand{\defparameter}[4][]{% \item[\code{#2}]\hypertarget{opt:#2}{} ~~ % \ifthenelse{\equal{#3}{}}{}{% \emph{flag:} % \ifthenelse{\equal{#1}{}}{}{% \code{-#1}~~~\emph{or}~~~}% \code{--#3} ~~ }% \emph{default:}~\texttt{#4} \\ } \newcommand{\parameter}[1]{\hyperlink{opt:#1}{\code{#1}}} \newcommand{\iracefun}[1]{\href{https://mlopez-ibanez.github.io/irace/reference/#1.html}{\code{#1()}}} %\usepackage{showlabels} %\showlabels{hypertarget} \newcommand{\irace}{\pkg{irace}\xspace} \newcommand{\Irace}{\pkg{Irace}\xspace} \newcommand{\race}{\pkg{race}\xspace} \newcommand{\FRACE}{\text{F-Race}\xspace} \newcommand{\IFRACE}{\text{I/F-Race}\xspace} \newcommand{\PyImp}{\pkg{PyImp}\xspace} \newcommand{\iraceversion}{\Sexpr{packageVersion("irace")}} \newcommand{\Niter}{\ensuremath{N^\text{iter}}\xspace} \newcommand{\Nparam}{\ensuremath{{N^\text{param}}}\xspace} \newcommand{\iter}{\ensuremath{j}\xspace} \newcommand{\Budget}{\ensuremath{B}\xspace} \newcommand{\Budgetj}{\ensuremath{\Budget_{\iter}}\xspace} \newcommand{\Bused}{\ensuremath{\Budget_\text{used}}\xspace} \newcommand{\Ncand}[1][]{\ensuremath{N_{#1}}\xspace} \newcommand{\Mui}{\ensuremath{\mu_{\iter}}\xspace} \newcommand{\Nmin}{\ensuremath{N^\text{min}}\xspace} \newcommand{\Nsurv}{\ensuremath{N^\text{surv}}\xspace} \newcommand{\Nelite}{\ensuremath{N^\text{elite}}\xspace} \newcommand{\Nnew}{\ensuremath{N^\text{new}}\xspace} \newcommand{\bmax}{\ensuremath{b^\text{max}}\xspace} \newcommand{\bmin}{\ensuremath{b^\text{min}}\xspace} \newcommand{\Celite}{\ensuremath{\Theta^\text{elite}}\xspace} \ifthenelse {\boolean{Release}}{% \newcommand{\MANUEL}[1]{} \newcommand{\LESLIE}[1]{} \newcommand{\THOMAS}[1]{} }{% \newcommand{\MANUEL}[1]{{\footnotesize\noindent\textbf{\color{red}[~MANUEL: #1~]}}} \newcommand{\LESLIE}[1]{\footnote{\noindent\textbf{[ LESLIE: #1 ]}}} \newcommand{\THOMAS}[1]{\footnote{\noindent\textbf{[ THOMAS: #1 ]}}} } \newcommand{\hide}[1]{} \usepackage{tcolorbox} \newcommand{\infoicon}{% \parbox[c]{0.75cm}{\includegraphics[keepaspectratio=true,width=0.75cm]{light-bulb-icon}}% \hspace{1em}} \newcommand{\warningicon}{% \parbox[c]{0.75cm}{\includegraphics[keepaspectratio=true,width=0.75cm]{Warning-icon}}% \hspace{1em}} \definecolor{LightGray}{RGB}{193,193,193} \definecolor{LightYellow}{RGB}{253,247,172} \newlength\macroiconwidth \newenvironment{xwarningbox}{% \setlength{\fboxrule}{3.0\fboxrule}% \setlength{\fboxsep}{0\fboxsep}% \begin{tcolorbox}[colback=LightYellow,colframe=LightGray,boxrule=\fboxrule,boxsep=\fboxsep]% \infoicon% \settowidth{\macroiconwidth}{\infoicon}% \begin{minipage}[c]{\columnwidth - \macroiconwidth - 2.0\fboxrule - 2.0\fboxsep} \raggedright\footnotesize % }{% \end{minipage} \end{tcolorbox} % } % Workaround for broken knitr: https://github.com/yihui/knitr-examples/blob/master/036-latex-if.tex \providecommand{\hldef}[1]{\textcolor[rgb]{0.345,0.345,0.345}{#1}}% \providecommand{\hlsng}[1]{\textcolor[rgb]{0.192,0.494,0.8}{#1}}% <>= library(knitr) knit_hooks$set(inline = function(x) { if (is.numeric(x)) return(knitr:::format_sci(x, 'latex')) highr::hi_latex(x) }) @ \begin{document} <>= library(knitr) @ \author{Manuel L\'opez-Ib\'a\~nez, Leslie P\'erez C\'aceres, J\'er\'emie Dubois-Lacoste,\\ Thomas St\"utzle and Mauro Birattari \\IRIDIA, CoDE, Universit\'e Libre de Bruxelles, Brussels, Belgium} \title{The \irace Package: User Guide} \date{Version \iraceversion, \today} %\keywords{automatic % algorithm configuration, racing, parameter tuning, \aR} \maketitle \tableofcontents %Load files needed for examples <>= library("irace") load("examples.Rdata") # loads "experiment" and "output" iraceResults <- irace::read_logfile(system.file(package="irace", "exdata", "irace-acotsp.Rdata", mustWork=TRUE)) log_ablation_file <- system.file(package="irace", "exdata", "log-ablation.Rdata", mustWork = TRUE) load(log_ablation_file) options(width = 70) @ \newpage %% %% %% %% General info %% %% %% \section{General information} \MANUEL{Some things could be taken from the intro of the irace paper and reformulated.} \MANUEL{It would be good to mention that not only opt algorithms can be configured with irace, we say this in the paper.} \subsection{Background} \MANUEL{I would add a paragraph defining what is irace (a bit longer than the abstract above) and references to the literature so people can find more info. The first reference should be the irace TR.} \LESLIE{Here i guess we should say why tune an algorithm is a good idea, and why using irace is a better one.} The \irace package implements an \emph{iterated racing} procedure, which is an extension of Iterated F-race (\IFRACE)~\cite{BirYuaBal2010:emaoa}. The main use of \irace is the automatic configuration of optimization and decision algorithms, that is, finding the most appropriate settings of an algorithm given a set of instances of a problem. However, it may also be useful for configuring other types of algorithms when performance depends on the used parameter settings. It builds upon the \pkg{race} package by Birattari and it is implemented in \aR. The \irace package is available from CRAN: % \begin{center} \url{https://cran.r-project.org/package=irace} \end{center} % More information about \irace is available at \url{https://mlopez-ibanez.github.io/irace}. \subsection{Version} The current version of the \irace package is \iraceversion. Previous versions of the package can also be found in the \href{https://cran.r-project.org/package=irace}{CRAN website}. The algorithm underlying the current version of \irace and its motivation are described by \citet{LopDubPerStuBir2016irace}. The \textbf{adaptive capping mechanism} available from version $3.0$ is described by \citet{PerLopHooStu2017:lion}. Details of the implementation before version 2.0 can be found in a previous technical report~\cite{LopDubStu2011irace}. % \begin{xwarningbox} Versions of \irace before 2.0 are not compatible with the file formats detailed in this document. \end{xwarningbox} \subsection{License} The \irace package is Copyright \copyright{} \the\year\ and distributed under the GNU General Public License version 3.0 (\url{http://www.gnu.org/licenses/gpl-3.0.en.html}). The \irace package is free software (software libre): You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. The \irace package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Please be aware that the fact that this program is released as Free Software does not excuse you from scientific propriety, which obligates you to give appropriate credit! If you write a scientific paper describing research that made substantive use of this program, it is your obligation as a scientist to (a) mention the fashion in which this software was used in the Methods section; (b) mention the algorithm in the References section. The appropriate citation is: \begin{itemize}[leftmargin=3em] \item[] Manuel López-Ibáñez, Jérémie Dubois-Lacoste, Leslie Pérez Cáceres, Thomas Stützle, and Mauro Birattari. The \irace package: Iterated Racing for Automatic Algorithm Configuration. \emph{Operations Research Perspectives}, 3:43--58, 2016. doi:~\href{http://dx.doi.org/10.1016/j.orp.2016.09.002}{10.1016/j.orp.2016.09.002} \end{itemize} \section{Before starting} \MANUEL{I think this could be a bit more detailed by defining what is a parameter, a configuration, an instance, etc. but ok for now.} The \irace package provides an automatic configuration tool for tuning optimization algorithms, that is, automatically finding good configurations for the parameters values of a (target) algorithm saving the effort that normally requires manual tuning. \begin{figure}[t] \centering \includegraphics[width=0.6\textwidth]{irace-scheme} \caption{Scheme of \irace flow of information.} \label{fig:irace-scheme} \end{figure} Figure~\ref{fig:irace-scheme} gives a general scheme of how \irace works. \Irace receives as input a \emph{parameter space definition} corresponding to the parameters of the target algorithm that will be tuned, a set of \emph{instances} for which the parameters must be tuned for and a set of options for \irace that define the \emph{configuration scenario}. Then, \irace searches in the parameter search space for good performing algorithm configurations by executing the target algorithm on different instances and with different parameter configurations. A \parameter{targetRunner} must be provided to execute the target algorithm with a specific parameter configuration ($\theta$) and instance ($i$). The \parameter{targetRunner} function (or program) acts as an interface between the execution of the target algorithm and \irace: It receives the instance and configuration as arguments and must return the evaluation of the execution of the target algorithm. The following user guide contains guidelines for installing \irace, defining configuration scenarios, and using \irace to automatically configure your algorithms. %% %% %% %% Installation %% %% %% \section{Installation} \subsection{System requirements} \begin{itemize} \item \aR ($\text{version} \geq 3.2.0$) is required for running irace, but you don't need to know the \aR language to use it. \aR is freely available and you can download it from the \aR project website (\url{https://www.r-project.org}). See \autoref{sec:installation} for a quick installation guide of \aR. \item For GNU/Linux and OS X, the command-line executable \code{parallel-irace} requires GNU Bash. Individual examples may require additional software. \end{itemize} \subsection{\irace installation} \label{sec:irace install} The \irace package can be installed automatically within \aR or by manual download and installation. We advise to use the automatic installation unless particular circumstances do not allow it. The instructions to install \irace with the two mentioned methods are the following: \subsubsection[Install automatically within R]{Install automatically within \aR{}} Execute the following line in the \aR console to install the package: <>= install.packages("irace") @ Select a mirror close to your location, and test the installation in the \aR console with: <>= library("irace") q() # To exit R @ Alternatively, within the \aR graphical interface, you may use the \code{Packages and data->Package installer} menu on OS X or the \code{Packages} menu on Windows. \subsubsection{Manual download and installation} From the \irace package CRAN website (\url{https://cran.r-project.org/package=irace}), download one of the three versions available depending on your operating system: \begin{itemize} \item \code{irace_\iraceversion.tar.gz} (Unix/BSD/GNU/Linux) \item \code{irace_\iraceversion.tgz} (OS X) \item \code{irace_\iraceversion.zip} (Windows) \end{itemize} To install the package on GNU/Linux and OS X, you must execute the following command at the shell (replace \code{} with the path to the downloaded file, either \code{irace_\iraceversion.tar.gz} or \code{irace_\iraceversion.zip}): % \begin{lstlisting}[style=BashInputStyle] R CMD INSTALL \end{lstlisting} To install the package on Windows, open \aR and execute the following line on the \aR console (replace \code{} with the path to the downloaded file \code{irace_\iraceversion.zip}): %\LESLIE{Check that this actually works on internet says that this: \code{Rscript -e "install.packages('foo.zip', repos = NULL)"} also works} % <>= install.packages("", repos = NULL) @ If the previous installation instructions fail because of insufficient permissions and you do not have sufficient admin rights to install \irace system-wide, then you need to force a local installation. \subsubsection{Local installation} Let's assume you wish to install \irace on a path denoted by \code{}, which is a filesystem path for which you have sufficient rights. This directory \textbf{must} exist before attempting the installation. Moreover, you must provide to \aR the path to this library when loading the package. However, the latter can be avoided by adding the path to the system variable \code{R_LIBS} or to the \aR internal variable \code{.libPaths}, as we will see below.\footnote{% On Windows, see also \url{https://cran.r-project.org/bin/windows/base/rw-FAQ.html\#I-don_0027t-have-permission-to-write-to-the-R_002d3_002e3_002e1_005clibrary-directory}.} On GNU/Linux or OS X, execute the following commands to install the package on a local directory: \begin{lstlisting}[style=BashInputStyle] export R_LIBS_USER="" # Create R_LIBS_USER if it doesn't exist mkdir $R_LIBS_USER # Replace with the path to the downloaded file. R CMD INSTALL --library=$R_LIBS_USER # Tell R where to find R_LIBS_USER export R_LIBS=${R_LIBS_USER}:${R_LIBS} \end{lstlisting} On Windows, you can install the package on a local directory by executing the following lines in the \aR console: <>= # Replace with the path to the downloaded file. # Replace with the path used for installation. install.packages("", repos = NULL, lib = "") # Tell R where to find R_LIBS_USER. # This must be executed for every new session. .libPaths(c("", .libPaths())) @ \subsubsection{Testing the installation and invoking irace} Once \irace has been installed, load the package and test that the installation was successful by opening an \aR console and executing: <>= # Load the package library("irace") # Obtain the installation path system.file(package = "irace") @ The last command must print out the filesystem path where \irace is installed. In the remainder of this guide, the variable \code{\$IRACE_HOME} is used to denote this path. When executing any provided command that includes the \code{\$IRACE_HOME} variable do not forget to replace this variable with the installation path of \irace. On GNU/Linux or OS X, you can let the operating system know where to find \irace by defining the \code{\$IRACE_HOME} variable and adding it to the system \code{PATH}. Append the following commands to \path{~/.bash_profile}, \path{~/.bashrc} or \path{~/.profile}: % %<>= \begin{lstlisting}[style=BashInputStyle] # Replace with the irace installation path export IRACE_HOME= export PATH=${IRACE_HOME}/bin/:$PATH # Tell R where to find R_LIBS_USER # Use the following line only if local installation was forced export R_LIBS=${R_LIBS_USER}:${R_LIBS} \end{lstlisting} %@ Then, open a new terminal and launch \irace as follows: %<>= \begin{lstlisting}[style=BashInputStyle] irace --help \end{lstlisting} %@ On Windows, you need to add both \aR and the installation path of \irace to the environment variable \code{PATH}. To edit the \code{PATH}, search for ``Environment variables'' in the control panel, edit \code{PATH} and add a string similar to \path{C:\R_PATH\bin;C:\IRACE_HOME\bin\x64\} where \code{R_PATH} is the installation path of \aR and \code{IRACE_HOME} is the installation path of \irace. If \irace was installed locally, you also need to edit the environment variable \code{R_LIBS} to add \code{R_LIBS_USER}. Then, open a new terminal (run program \code{cmd.exe}) and launch \irace as: % %<>= \begin{lstlisting}[style=BashInputStyle] irace.exe --help \end{lstlisting} %@ Alternatively, you may directly invoke \irace from within the \aR console by executing: <>= library("irace") irace_cmdline("--help") @ \section{Running irace}\label{sec:execution} Before performing the tuning of your algorithm, it is necessary to define a tuning scenario that will give \irace all the necessary information to optimize the parameters of the algorithm. The tuning scenario is composed of the following elements: \begin{enumerate} \item Target algorithm parameter description (see \autoref{sec:target parameters}). \item Target algorithm runner (see \autoref{sec:runner}). \item Training instances list (see \autoref{sec:training}) \item \irace options (see \autoref{sec:irace options}). \item \textit{Optional:} Initial configurations (see \autoref{sec:initial}). \item \textit{Optional:} Target algorithm evaluator (see \autoref{sec:evaluator}). \end{enumerate} These scenario elements can be provided as plain text files or as \aR objects. This user guide provides examples of both types, but we advise the use of plain text files, which we consider the simpler option. For a step-by-step guide to create the scenario elements for your target algorithm continue to \autoref{sec:step}. For an example execution of \irace using the \ACOTSP scenario go to \autoref{sec:example}. \subsection{Step-by-step setup guide}\label{sec:step} This section provides a guide to setup a basic execution of \irace. The template files provided in the package (\IRACEHOME{/templates}) will be used as basis for creating your new scenario. Please follow carefully the indications provided in each step and in the template files used; if you have doubts check the the sections that describe each option in detail. \begin{enumerate}[leftmargin=*] \item Create a directory (\eg~\path{./tuning/}) for the scenario setup. This directory will contain all the files that describe the scenario. On GNU/Linux or OS X, you can do this as follows: %<>= \begin{lstlisting}[style=BashInputStyle] mkdir ./tuning cd ./tuning \end{lstlisting} %@ \item Initialize the tuning directory with template config files. On GNU/Linux or OS X, you can do this as follows: %<>= \begin{lstlisting}[style=BashInputStyle] irace --init \end{lstlisting} %@ \item Define the target algorithm parameters to be tuned by following the instructions in \code{parameters.txt}. Available parameter types and other guidelines can be found in \autoref{sec:target parameters}. \item \textit{Optional}: Define the initial parameter configuration(s) of your algorithm, which allows you to provide good starting configurations (if you know some) for the tuning. Follow the instructions in \code{configurations.txt} and set \parameter{configurationsFile}\code{="configurations.txt"} in \code{scenario.txt}. More information in \autoref{sec:initial}. If you do not need to define initial configurations remove this file from the directory. \item Place the instances you would like to use for the tuning of your algorithm in the folder \path{./tuning/Instances/}. In addition, you can create a file (\eg~\code{instances-list.txt}) that specifies which instances from that directory should be run and which instance-specific parameters to use. To use such an instance file, set the appropriate option in \code{scenario.txt}, e.g., \parameter{trainInstancesFile} \code{= "instances-list.txt"}. See \autoref{sec:training} for guidelines. \item Uncomment and assign in \code{scenario.txt} only the options for which you need a value different from the default. Some common options that you might want to adjust are: \begin{description} \item[\parameter{execDir}] (\code{--exec-dir}): the directory in which \irace will execute the target algorithm; the default value is the current directory. %\item[\parameter{logFile}] (\code{--log-file}): a file the name of the results \aR data file that produces \irace. \item[\parameter{maxExperiments}] (\code{--max-experiments}): the maximum number of executions of the target algorithm that \irace will perform. \item[\parameter{maxTime}] (\code{--max-time}): maximum total execution time in seconds for the executions of \code{targetRunner}. In this case, \code{targetRunner} must return two values: cost and time. Note that you must provide either \parameter{maxTime} or \parameter{maxExperiments}. \item[\parameter{trainInstancesDir}] (\code{--train-instances-dir}): set to \path{./Instances} if you put the training instances in that folder as instructed above. \end{description} For setting the tuning budget, see \autoref{sec:budget}. For more information on \irace options and their default values, see \autoref{sec:irace options}. \item Modify the \code{target-runner} script to run your algorithm. This script must execute your algorithm with the parameters and instance specified by \irace and return the evaluation of the execution and \textit{optionally} the execution time (\code{cost [time]}). When the \parameter{maxTime} option is used, returning \code{time} is mandatory. The \code{target-runner} template is written in \proglang{GNU Bash} scripting language, which can be executed easily in GNU/Linux and OS X systems. However, you may use any other programming language. We provide examples written in \proglang{Python}, \proglang{MATLAB} and other languages in \IRACEHOME{/examples/}. An example using Julia is available at \url{https://github.com/sbomsdorf/An-example-of-irace-using-Julia-code}. % Follow these instructions to adjust the given \code{target-runner} template to your algorithm: \begin{enumerate} \item Set the \code{EXE} variable with the path to the executable of the target algorithm. \item Set the \code{FIXED_PARAMS} if you need extra arguments in the execution line of your algorithm. An example could be the time that your algorithm is required to run (\code{FIXED_PARAMS}\hspace{0pt}\code{=}\hspace{0pt}\code{"--time 60"}) or the number of evaluations required (\code{FIXED_PARAMS}\hspace{0pt}\code{=}\hspace{0pt}\code{"--evaluations 10000"}). \item The line provided in the template executes the executable described in the \code{EXE} variable. % \begin{center} \code{\$EXE \$\{FIXED_PARAMS\} -i \$\{INSTANCE\} --seed \$\{SEED\} \$\{CONFIG_PARAMS\}} \end{center} % You must change this line according to the way your algorithm is executed. In this example, the algorithm receives the instance to solve with the flag \code{-i} and the seed of the random number generator with the flag \code{--seed}. The variable \code{CONFIG_PARAMS} adds to the command line the parameters that \irace has given for the execution. You must set the command line execution as needed. For example, the instance might not need a flag and might need to be the first argument: \begin{center} \code{\$EXE \$\{INSTANCE\} \$\{FIXED_PARAMS\} --seed \$\{SEED\} \$\{CONFIG_PARAMS\}} \end{center} The output of your algorithm is saved to the file defined in the \code{\$STDOUT} variable, and error output is saved in the file given by \code{\$STDERR}. The line: \begin{center} \code{if [ -s "\${STDOUT}" ]; then} \end{center} checks if the file containing the output of your algorithm is not empty. The example provided in the template assumes that your algorithm prints in the last output line the best result found (only a number). The line: \begin{center} \code{COST=\$(cat \$\{STDOUT\} | grep -e '\^{}[[:space:]]*[+-]\textbackslash{}?[0-9]' | cut -f1)} \end{center} parses the output of your algorithm to obtain the result from the last line. The \code{target-runner} script must print \textbf{only} one number. In the template example, the result is printed with \code{echo "\$COST"} (assuming \parameter{maxExperiments} is used) and the generated files are deleted (you may remove that line if you wish to keep them). \begin{xwarningbox} The \code{target-runner} script must be an executable file, unless you specify \parameter{targetRunnerLauncher}. \end{xwarningbox} You can test the target runner from the \aR console by checking the scenario as explained earlier in \autoref{sec:execution}. If you have problems related to the \code{target-runner} script when executing \irace, see \autoref{sec:check list} for a check list to help diagnose common problems. For more information about the \parameter{targetRunner}, please see \autoref{sec:runner}, \end{enumerate} \item \textit{Optional}: Modify the \code{target-evaluator} file. This is rarely needed and the \code{target-runner} template does not use it. \autoref{sec:evaluator} explains when a \parameter{targetEvaluator} is needed and how to define it. \item The \irace executable provides an option (\parameter{--check}) to check that the scenario is correctly defined. We recommend to perform a check every time you create a new scenario. When performing the check, \irace will verify that the scenario and parameter definitions are correct and will test the execution of the target algorithm. To check your scenario execute the following commands: \begin{itemize} \item From the command-line (on Windows, execute \code{irace.bat}): %<>= \begin{lstlisting}[style=BashInputStyle] # $IRACE_HOME is the installation directory of irace. $IRACE_HOME/bin/irace --scenario scenario.txt --check \end{lstlisting} %@ \item Or from the \aR console: <>= library("irace") scenario <- readScenario(filename = "scenario.txt", scenario = defaultScenario()) checkIraceScenario(scenario = scenario) @ \end{itemize} \item Once all the scenario elements are prepared you can execute \irace, either using the command-line wrappers provided by the package or directly from the \aR console: \begin{itemize} \item \textbf{From the command-line console}, call the command (on Windows, you should execute \code{irace.exe}): \begin{lstlisting}[style=BashInputStyle] cd ./tuning/ # $IRACE_HOME is the installation directory of irace # By default, irace reads scenario.txt, you can specify a different file # with --scenario. $IRACE_HOME/bin/irace \end{lstlisting} For this example, we assume that the needed scenario files have been set properly in the \code{scenario.txt} file using the options described in \autoref{sec:irace options}. Most \irace options can be specified in the command line or directly in the \code{scenario.txt} file. \item \textbf{From the \aR console}, evaluate: <>= library("irace") # Go to the directory containing the scenario files setwd("./tuning") scenario <- readScenario(filename = "scenario.txt", scenario = defaultScenario()) irace_main(scenario = scenario) @ \end{itemize} This will perform one run of \irace. See the output of \code{irace --help} in the command-line or \code{irace_cmdline("--help")} in \aR for quick information on additional \irace options. For more information about \irace options, see \autoref{sec:irace options}. \end{enumerate} \begin{xwarningbox} Command-line options override the same options specified in the \code{scenario.txt} file. \end{xwarningbox} \subsection{Setup example for ACOTSP}\label{sec:example} The \ACOTSP tuning example can be found in the package installation in the folder \IRACEHOME{/examples/acotsp}. % Other example scenarios can be found in the same folder. More examples of tuning scenarios can be found in the Algorithm Configuration Library (AClib, \url{http://www.aclib.net/}). In this section, we describe how to execute the \ACOTSP scenario. If you wish to start setting up your own scenario, continue to the next section. For this example, we assume a GNU/Linux system such as Ubuntu with a working \proglang{C} compiler such as \code{gcc}. To execute this scenario follow these steps: \begin{enumerate} \item Create a directory for the tuning (\eg~\path{./tuning/}) and copy the example scenario files located in the \code{examples} folder to the created directory: %<>= \begin{lstlisting}[style=BashInputStyle] mkdir ./tuning cd ./tuning # $IRACE_HOME is the installation directory of irace. cp $IRACE_HOME/examples/acotsp/* ./ ls ./ # Make sure that target-runner is executable chmod u+x target-runner \end{lstlisting} %@ \item Download the training instances from \url{https://iridia.ulb.ac.be/supp/IridiaSupp2016-003/scenarios/acotsp/instances.tar.gz} to the \path{./tuning/} directory and decompress it, which creates create a folder \path{instances}: %<>= \begin{lstlisting}[style=BashInputStyle] tar -xvf instances.tar.gz ls instances/ \end{lstlisting} %@ If the above gives an error or does not show any files, then the files were not extract correctly. Maybe the \path{instances.tar.gz} file did not download correctly or maybe it is not in the correct place. It should be within the folder \path{tuning}.correctly. \item Download the \ACOTSP software from \url{https://github.com/MLopez-Ibanez/ACOTSPQAP/archive/refs/heads/master.zip} to the \path{./tuning/} directory and compile the \path{acotsp} executable using \code{make}. %<>= \begin{lstlisting}[style=BashInputStyle] unzip master.zip make -C ACOTSPQAP-master acotsp ./ACOTSPQAP-master/acotsp --version \end{lstlisting} %@ If the above gives an error, then the \path{acotsp} executable failed to compile for some reason. Maybe you are missing the C compiler or some files did not extract correctly. \item Create a directory for executing the experiments and execute \irace: %<>= \begin{lstlisting}[style=BashInputStyle] mkdir ./acotsp-arena/ # $IRACE_HOME is the installation directory of irace. $IRACE_HOME/bin/irace \end{lstlisting} %@ Or you can also execute \irace from the \aR console using: <>= library("irace") setwd("./tuning/") irace_cmdline() @ \end{enumerate} The most usual sources of error when running the above commands are: \begin{itemize} \item The \irace package is not correctly installed. Please make sure that installing \irace did not give any errors. \item The location of the files is not correct. Please make sure that you have: \begin{itemize} \item The folder \path{tuning} and that it contains the files \path{scenario.txt}, \path{parameters-acotsp.txt}, \path{target-runner}, and the folders \path{instances}, \path{ACOTSPQAP-master} and \path{acotsp-arena}. \item The folder \path{instances} should contain the TSP instance files (\path{*.tsp}). \item The folder \path{ACOTSPQAP-master} should contain the executable \path{acotsp}. You should be able to invoke \code{./ACOTSPQAP-master/acotsp --version} without an error. \item The folder \path{acotsp-arena} should be empty. \end{itemize} \end{itemize} %% %% %% %% Scenario settings %% %% %% \section{Defining a configuration scenario}\label{sec:scenario} \subsection{Target algorithm parameters} \label{sec:target parameters} The parameters of the target algorithm are defined by a parameter file as described in \autoref{sec:parameters file}. Optionally, when executing \irace from the \aR console, the parameters can be specified directly as an \aR object (see \autoref{sec:parameters object}). For defining your parameters follow the guidelines provided in the following sections. \subsubsection{Parameter types} Each target parameter has an associated type that defines its domain and the way \irace handles them internally. Understanding the nature of the domains of the target parameters is important to select appropriate types. The four basic types supported by \irace are the following: \begin{itemize} \item \textit{Real} parameters are numerical parameters that can take floating-point values within a given range. The range is specified as an interval `\code{(,)}'. This interval is closed, that is, the parameter value may eventually be one of the bounds. The possible values are rounded to a number of \emph{decimal places} specified by the global option \code{digits} (Section~\ref{sec:globaloptions}). For example, given the default number of digits of $4$, the values $0.12345$ and $0.12341$ are both rounded to $0.1234$. Selected real-valued parameters can be optionally sampled on a logarithmic scale (base $e$). \item \textit{Integer} parameters are numerical parameters that can take only integer values within the given range. Their range is specified as the range of real parameters and they can also be optionally sampled on a logarithmic scale (base $e$). \item \textit{Categorical} parameters are defined by a set of possible values specified as `\code{(,} \code{...,} \code{)}'. The values are quoted or unquoted character strings. Empty strings and strings containing commas or spaces must be quoted. \item \emph{Ordinal} parameters are defined by an \emph{ordered} set of possible values in the same format as for categorical parameters. They are handled internally as integer parameters, where the integers correspond to the indexes of the values. \end{itemize} \begin{xwarningbox} Boolean (or logical) parameters are best encoded as categorical ones with just two values rather than integer ones with domain $(0,1)$. Some boolean parameters take an explicit value (0/1 or true/false) such as: \begin{CodeInput} dlb "--dlb " c (0, 1) \end{CodeInput} Others are switches whose presence activates the parameter: \begin{CodeInput} dlb "" c ("", "--dlb") \end{CodeInput} \end{xwarningbox} \subsubsection{Parameter domains} For each target parameter, an interval or a set of values must be defined according to its type, as described above. There is no limit for the size of the set or the length of the interval, but keep in mind that larger domains could increase the difficulty of the tuning task. Choose always values that you consider relevant for the tuning. In case of doubt, we recommend to choose larger intervals, as occasionally best parameter settings may be not intuitive a priori. All intervals are considered as closed intervals. It is possible to define parameters that will have always the same value. Such ``\emph{fixed}'' parameters will not be tuned but their values are used when executing the target algorithm and they are affected by constraints defined on them. All fixed parameters must be defined as categorical parameters and have a domain of one element. \subsubsection{Parameter dependent domains}\label{sec:paramdependant} Domains that are dependent on the values of other parameters can be specified only for numerical parameters (both integer and real). To do so, the dependent domain must be expressed in function of another parameter, which must be a numerical parameter. The expression that defines a dependency must be written between quotes: \code{(value, "expression")} or \code{("expression", value)} or \code{("expression", "expression")}. The expressions can only use the following operators and \aR functions: \code{+}, \code{-}, \code{*}, \code{/}, \code{\%\%}, \code{min}, \code{max}, \code{round}, \code{floor}, \code{ceiling}, \code{trunc}. If you need to use an operator or function not listed here, please contact us. \begin{xwarningbox} The user must ensure that the defined domain is valid at all times since \irace currently is not able to detect possible invalid domains based on the expressions provided. \end{xwarningbox} If you have a parameter \code{p2} that is just a transformation of another \code{p1}, then instead of using a dependent domain (left-hand side of the following example), it will be better to create a dummy parameter that controls the transformation (right-hand side) and do the transformation within \code{target-runner}. For example: % \begin{center} \begin{minipage}{0.4\linewidth} \small\centering% \begin{CodeInput}[frame=single] # With dependent domains p1 "" r (0, 100) p2 "" r ("p1", "p1 + 10") \end{CodeInput} \end{minipage} \hspace{\stretch{1}} should be \hspace{\stretch{1}} \begin{minipage}{0.4\linewidth} \small\centering% \begin{CodeInput}[frame=single] # With a dummy parameter p1 "" r (0, 100) p2dum "" r (0, 10) \end{CodeInput} \end{minipage} \end{center} % and \code{target-runner} will compute $\code{p2} = \code{p2dum} \cdot \code{p1}$. \subsubsection{Conditional parameters}\label{sec:conditional} Conditional parameters are active only when others have certain values. These dependencies define a hierarchical relation between parameters. For example, the target algorithm may have a parameter \code{localsearch} that takes values \code{(sa, ts)} and another parameter \code{ts-length} that only needs to be set if the first parameter takes precisely the value \code{ts}. Thus, parameter \code{ts-length} is conditional on \code{localsearch == "ts"}. \subsubsection{Forbidden parameter configurations}\label{sec:forbidden} A line containing just \texttt{[forbidden]} ends the list of parameters and starts the list of forbidden expressions. Each line is a logical expression (in \aR syntax) containing parameter names as defined by the \parameter{parameterFile} (\autoref{sec:target parameters}), values and logical operators. For a list of \aR logical operators see: \begin{center} \url{https://stat.ethz.ch/R-manual/R-devel/library/base/html/Syntax.html} \end{center} If \irace generates a parameter configuration that makes any of the logical expressions evaluate to \code{TRUE}, then the configuration is considered forbidden and it is never evaluated. This is useful when some combination of parameter values could cause the target algorithm to crash, consume excessive CPU time or memory, or when it is known that they do no produce satisfactory results. \begin{xwarningbox} Initial configuration (\autoref{sec:initial}) that are forbidden will be discarded with a warning. \end{xwarningbox} If the forbidden constraints provided are too strict, \irace may produce the following error: \begin{CodeInput} irace tried 100 times to sample from the model a configuration not forbidden without success, perhaps your constraints are too strict? \end{CodeInput} % In that case, it may be a good idea to reformulate the forbidden constraints as conditional parameters (\autoref{sec:conditional}), parameter-dependent domains (\autoref{sec:paramdependant}), repairing the configurations (\autoref{sec:repairconf}) or post-processing within the target-algorithm (\autoref{sec:complex_domains}). \subsubsection{Global options}\label{sec:globaloptions} A line containing just \texttt{[global]} starts the definition of global options. The only global option currently implemented is \code{digits}, which controls the number of decimal digits for real valued parameters. Its default value is 4. \subsubsection{Parameter file format}\label{sec:parameters file} For simplicity, the description of the parameters space is given as a table. Each line of the table defines a configurable parameter % \begin{center} \code{\