The relation matrix $M(R)$ is the binary indicator of the relation: in this case, the entry in $M(\lt)$ for variables $x,y$ is $1$ (true) when $x\lt y$ and $0$ (false) otherwise.
These individual relationships imply other relationships through the transitivity property $a\lt b$ and $b\lt c$ implies $a\lt c.$ The resulting transitive closure of the relation matrix must be consistent with antisymmetry: $M(R)[x,y]=1$ implies $M(R)[y,x]=0.$ The transitive closure is readily (if not entirely efficiently) found by taking powers of $M(R)$ until they stabilize, treating all values of $0$ as false and all nonzero values as true.
Thus, you can systematically check such a relation by (1) generating its matrix, (2) computing its transitive closure, and (3) checking that for (partial) antisymmetry. This is a general solution for any transitive, partially asymmetric relation, although it needs an (obvious) modification for reflexive relationships like $\le$ (where $M(R)[x,x]=1$ for all $x$).
Below is working R
code to illustrate. It checks five relations defined by sets of inequalities (given as character strings involving alpha variables). Its output is
[1] "Invalid: FALSE"
[1] "Valid: TRUE"
[1] "Invalid.2: FALSE"
[1] "Valid.2: TRUE"
[1] "Invalid.3: FALSE"
The first term is my indication of whether the relation is valid and the second TRUE/FALSE is the code's determination. It and I agree. The first line of output corresponds to the example in the question.
constraint.to.matrix <- function(x) {
ab <- sapply(x, function(s) strsplit(s, "[^[:alpha:]]+"))
vars <- sort(unique(unlist(ab)))
n <- length(vars)
r <- matrix(0, n, n, dimnames=list(vars, vars))
for (rel in ab) {
r[rel[1], rel[2]] <- 1
}
r
}
is.antisymmetric <- function(r) all(c(t(r) + r + diag(nrow(r))) <= 1)
transitive.closure <- function(r) {
s <- r + diag(nrow(r)) >= 1
while(TRUE) {
r1 <- r %*% s > 0
if (all(r1 - r == 0)) break
r <- r1
}
r
}
check <- function(constraints)
is.antisymmetric(transitive.closure(constraint.to.matrix(constraints)))
trials <- list(Invalid=list("e < b", "b < e", "c < d", "e < d"),
Valid=list("a<b", "b<c", "c<d"),
Invalid.2=list("a<b", "b<c", "c<d", "d<a"),
Valid.2=list(),
Valid.3=list("a<a"))
for (s in names(trials)) print(paste0(s, ": ", check(trials[[s]])))