Skip to content

Commit aab159a

Browse files
committed
added fix for issue #16, thanks to Romina Filippini and Simona Toti
1 parent 456269a commit aab159a

18 files changed

+232
-14
lines changed

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ S3method(print,mip_rule)
99
export(detect_boundary_cat)
1010
export(detect_boundary_num)
1111
export(detect_fixed_variables)
12+
export(detect_infeasible_if_rules)
1213
export(detect_infeasible_rules)
1314
export(detect_redundancy)
1415
export(is_categorical)

R/contradicted.R

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,18 @@
1010
#' @return `character` with conflicting rules.
1111
is_contradicted_by <- function(x, rule_name){
1212
rn <- rule_name %in% names(x)
13-
13+
1414
if (any(!rn)){
1515
nms <- paste0('"',rule_name[!rn], '"', collapse = ", ")
1616
warning("Rule(s) ", nms, " not found in rule set 'x'.", call. = FALSE)
1717
}
1818

1919
N <- length(x)
20-
weight <- rep(N, length(rule_name))
20+
#weight <- rep(N, length(rule_name))
21+
weight <- rep(Inf, length(rule_name))
2122
names(weight) <- rule_name
2223

24+
2325
res <- character()
2426
contra <- detect_infeasible_rules(x, weight = weight)
2527
while (length(contra) && !any(contra %in% names(weight))){

R/detect_infeasible_if_rules.R

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#' Detect infeasible if clauses
2+
#'
3+
#' Detect if clauses that contradict each other. This is useful to detect if clauses that are not satistifable.
4+
#'
5+
#' @param x A validator object.
6+
#' @param ... Additional arguments passed to `detect_if_clauses`.
7+
#' @param verbose Logical. If `TRUE`, print the results.
8+
#' @return A list of contradictions found in the if clauses, or `NULL` if none are found.
9+
#' @family feasibility
10+
#' @example ./examples/detect_infeasible_if_rules.R
11+
#' @export
12+
detect_infeasible_if_rules <- function(x, ..., verbose = TRUE){
13+
res <- detect_if_clauses(x, ...)
14+
if (length(res) == 0){
15+
if (verbose){
16+
message("No contradictory if clauses found.")
17+
}
18+
return(NULL)
19+
}
20+
21+
if (verbose){
22+
message(
23+
length(res),
24+
" contradiction(s) with if clauses found:"
25+
)
26+
for (i in seq_along(res)){
27+
cat(sprintf("When %s:\n", names(res)[i]))
28+
x_c <- x[res[[i]]]
29+
30+
expr <- sapply(x_c$rules, function(r){
31+
deparse(r@expr)
32+
})
33+
cat(paste0(" ", res[[i]], ": ", expr, collapse = "\n"))
34+
cat("\n")
35+
}
36+
}
37+
38+
invisible(res)
39+
}
40+
41+
# Detect if clauses that contradict each other. This is useful to detect if clauses that are not satistifable
42+
detect_if_clauses <- function(x, ...){
43+
x <- check_validator(x)
44+
is_cond <- is_conditional(x) | is_categorical(x)
45+
vals <- to_exprs(x)
46+
47+
l <- list()
48+
for (i in which(is_cond)){
49+
cond <- vals[[i]]
50+
r <- check_condition(cond, x)
51+
l[names(r)] <- r
52+
}
53+
54+
l
55+
}
56+
57+
check_condition <- function(cond_expr, x){
58+
clauses <- as_dnf(cond_expr)
59+
60+
# test whether it is an if statement
61+
if (length(clauses) <= 1){
62+
return(NULL)
63+
}
64+
# to do for %in statement and replace with multiple "=="
65+
neg_clauses <- lapply(clauses, invert_or_negate)
66+
67+
l <- list()
68+
for (neg in neg_clauses){
69+
v <- x + do.call(validate::validator, list(.test = neg))
70+
if (is_feasible(v)){
71+
next
72+
}
73+
v1 <- is_contradicted_by(v, ".test")
74+
l[[deparse(neg)]] <- v1
75+
# op <- op_to_s(neg)
76+
# if (op == "=="){
77+
# .values <- list(neg[[3]]) |> setNames(as.character(neg[[2]]))
78+
# test_rules <- substitute_values(x, .values = .values)
79+
# v <- detect_infeasible_rules(test_rules)
80+
# if (is.null(v)){
81+
# next
82+
# }
83+
# v1 <- is_contradicted_by(test_rules, v)
84+
# v <- c(v,v1)
85+
# l[[deparse(neg)]] <- v
86+
# }
87+
88+
# TODO expand %in% statement here
89+
}
90+
l
91+
}

R/feasible.R

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,19 @@ make_feasible <- function(x, ...){
6161
#' @param ... not used
6262
#' @return `character` with the names of the rules that are causing infeasibility.
6363
detect_infeasible_rules <- function(x, weight = numeric(), ...){
64+
# browser()
6465
if (!is_infeasible(x)){
6566
return(character())
6667
}
67-
68+
# browser()
69+
6870
mr <- to_miprules(x)
6971
mr <- fix_cat_domain(mr)
7072

73+
nms <- mr |> sapply(\(x) x$rule)
74+
w_inf <- nms[grepl("^\\.domain.", nms)] |> sapply(\(x) Inf)
75+
weight <- c(weight, w_inf)
76+
7177
is_equality <- sapply(mr, function(m){
7278
m$op == "==" && all(m$type == "double")
7379
})
@@ -87,8 +93,15 @@ detect_infeasible_rules <- function(x, weight = numeric(), ...){
8793
}
8894

8995
# make all rules soft rules
96+
wl <- as.list(weight)
9097
objective <- numeric()
9198
mr <- lapply(mr , function(r){
99+
w <- wl[[r$rule]]
100+
exclude <- (is.numeric(w) && w == Inf)
101+
if (exclude){
102+
return(r)
103+
}
104+
92105
is_lin <- all(r$type == "double")
93106
is_cat <- all(r$type == "binary")
94107
if (is_lin){
@@ -102,17 +115,18 @@ detect_infeasible_rules <- function(x, weight = numeric(), ...){
102115
objective[[paste0(".delta_", r$rule)]] <<- r$weight
103116
r
104117
})
105-
106118
# set the weights to the weights supplied by the user
107-
if (!is.null(names(weight))){
108-
names(weight) <- paste0(".delta_", names(weight))
109-
objective[names(weight)] <- weight
119+
if (length(weight) && !is.null(names(weight))){
120+
weight <- weight[sapply(weight, is.finite)]
121+
if (length(weight)){
122+
names(weight) <- paste0(".delta_", names(weight))
123+
objective[names(weight)] <- weight
124+
}
110125
}
111-
112126
lp <- translate_mip_lp(mr, objective = objective) #TODO figure out "eps" param
113127
lpSolveAPI::lp.control( lp
114128
#, verbose="full"
115-
, presolve="none"
129+
, presolve="rows"
116130
)
117131
res <- solve(lp)
118132

R/soft-rule.R

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
#TODO rename to mip_*
22

33
# convert statements of A == '1' into A + .delta_A == '1'
4+
# convert statements of a:a - b:b <= 0 into a:a - b:b - delta <= 0
45
soft_cat_rule <- function(x, prefix=".delta_", name = x$rule, ...){
56
stopifnot(inherits(x, "mip_rule"))
67
nm <- paste0(prefix, name, collapse = "")
7-
delta <- setNames(1L, nm)
8+
neg <- min(sum(x$a[x$a < 0]), -1L)
9+
delta <- ifelse(x$op == "==", 1L, neg)
10+
delta <- setNames(delta, nm)
811
x$a <- c(x$a, delta)
912
x$type <- c(x$type, setNames("binary", nm))
1013
x

R/utils.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ get_variables_num <- function(x){
2626
}
2727

2828
get_variables_cat <- function(x){
29-
var_cat <- sapply(to_miprules(x), function(mr){
29+
var_cat <- lapply(to_miprules(x), function(mr){
3030
nms <- names(mr$type)
3131
nms[mr$type == "binary" & grepl(":", nms)]
3232
})
33-
var_cat <- unique(unlist(var_cat))
33+
var_cat <- unique(var_cat |> unlist())
3434
if (length(var_cat) == 0){
3535
return(
3636
data.frame( bin_variable = character(0)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
rules <- validator(
2+
if (nace == "a") export == "y",
3+
if (nace == "a") export == "n"
4+
)
5+
6+
detect_infeasible_if_rules(rules)

issues/cat_no_domain.R

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ is_feasible(rules)
1010

1111

1212
conflicts <- detect_infeasible_rules(rules)
13-
conflict
13+
conflicts
1414
conflict2 <- is_contradicted_by(rules, conflicts)
1515
make_feasible(rules)
16+
17+

man/detect_boundary_cat.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/detect_boundary_num.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)