Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
S
Set Game
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Iterations
Requirements
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Test cases
Artifacts
Deploy
Releases
Package Registry
Container Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Issue analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
OROBWorld
Set Game
Commits
ebffca83
Commit
ebffca83
authored
1 year ago
by
Paul A. Rubin
Browse files
Options
Downloads
Patches
Plain Diff
Initial commit.
parent
1f8d0e14
No related branches found
No related tags found
No related merge requests found
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
src/setgame/CP.java
+88
-0
88 additions, 0 deletions
src/setgame/CP.java
src/setgame/IP.java
+182
-0
182 additions, 0 deletions
src/setgame/IP.java
src/setgame/Set.java
+149
-0
149 additions, 0 deletions
src/setgame/Set.java
src/setgame/SetGame.java
+71
-0
71 additions, 0 deletions
src/setgame/SetGame.java
with
490 additions
and
0 deletions
src/setgame/CP.java
0 → 100644
+
88
−
0
View file @
ebffca83
package
setgame
;
import
ilog.concert.IloAnd
;
import
ilog.concert.IloConstraint
;
import
ilog.concert.IloException
;
import
ilog.concert.IloIntVar
;
import
ilog.cp.IloCP
;
/**
* CP implements a constraint programming model to find all set.
* @author Paul A. Rubin (rubin@msu.edu)
*/
public
final
class
CP
implements
AutoCloseable
{
private
final
IloCP
model
;
// the CP model
private
final
IloIntVar
[]
cards
;
// the index of the card in each slot
private
final
IloIntVar
[][]
values
;
// the value of each feature (row) in
// each slot (column)
/**
* Constructs the CP model.
* @param game the game to solve
* @throws IloException if CPO objects to something
*/
public
CP
(
final
Set
game
)
throws
IloException
{
model
=
new
IloCP
();
cards
=
new
IloIntVar
[
Set
.
SETSIZE
];
values
=
new
IloIntVar
[
Set
.
NFEATURES
][
Set
.
SETSIZE
];
// Define the variables.
for
(
int
slot
=
0
;
slot
<
Set
.
SETSIZE
;
slot
++)
{
cards
[
slot
]
=
model
.
intVar
(
0
,
Set
.
NCARDS
-
1
,
"card_in_slot_"
+
slot
);
for
(
int
feature
=
0
;
feature
<
Set
.
NFEATURES
;
feature
++)
{
values
[
feature
][
slot
]
=
model
.
intVar
(
0
,
Set
.
NVALUES
,
"slot_"
+
slot
+
"_feature_"
+
feature
);
}
}
// Constraint: No card can be used twice.
model
.
add
(
model
.
allDiff
(
cards
));
// Constraint: To avoid finding all possible permutations of all possible
// hands, the slots must be filled in ascending card order.
for
(
int
slot
=
1
;
slot
<
Set
.
SETSIZE
;
slot
++)
{
model
.
add
(
model
.
lt
(
cards
[
slot
-
1
],
cards
[
slot
]));
}
// Constraint: The value of a feature in a slot is inherited from the
// card in that slot.
for
(
int
feature
=
0
;
feature
<
Set
.
NFEATURES
;
feature
++)
{
int
[]
v
=
game
.
getValues
(
feature
);
for
(
int
slot
=
0
;
slot
<
Set
.
SETSIZE
;
slot
++)
{
model
.
add
(
model
.
eq
(
values
[
feature
][
slot
],
model
.
element
(
v
,
cards
[
slot
])));
}
}
// Constraint: For each feature, either all slots have the same value or
// all slots are different.
for
(
int
feature
=
0
;
feature
<
Set
.
NFEATURES
;
feature
++)
{
IloConstraint
different
=
model
.
allDiff
(
values
[
feature
]);
IloAnd
same
=
model
.
and
();
for
(
int
slot
=
0
;
slot
<
Set
.
SETSIZE
-
1
;
slot
++)
{
same
.
add
(
model
.
eq
(
values
[
feature
][
slot
],
values
[
feature
][
slot
+
1
]));
}
model
.
add
(
model
.
or
(
different
,
same
));
}
// Suppress output.
model
.
setOut
(
null
);
}
/**
* Solves the model and returns the number of solutions found.
* @return the solution count
* @throws IloException if CPO pitches a fit
*/
public
int
solve
()
throws
IloException
{
int
found
=
0
;
model
.
startNewSearch
();
while
(
model
.
next
())
{
found
+=
1
;
}
return
found
;
}
/**
* Closes the model.
*/
@Override
public
void
close
()
{
model
.
end
();
}
}
This diff is collapsed.
Click to expand it.
src/setgame/IP.java
0 → 100644
+
182
−
0
View file @
ebffca83
package
setgame
;
import
ilog.concert.IloException
;
import
ilog.concert.IloLinearNumExpr
;
import
ilog.concert.IloNumVar
;
import
ilog.concert.IloRange
;
import
ilog.cplex.IloCplex
;
import
java.util.HashSet
;
/**
* IP implements an integer programming model to find all sets.
* @author Paul A. Rubin (rubin@msu.edu)
*/
public
final
class
IP
implements
AutoCloseable
{
private
static
final
double
HALF
=
0.5
;
// used for rounding
private
final
IloCplex
model
;
// the MIP model
private
final
IloNumVar
[]
include
;
// indicator to include a card
private
final
IloNumVar
[][]
appears
;
// appears[i][j] = 1 if value j for
// feature i appears in the set
private
final
IloNumVar
[]
different
;
// 1 if feature values all differ,
// 0 if they are all the same
private
final
HashSet
<
HashSet
<
Integer
>>
found
;
// solutions found
/**
* Constructor.
* @param game the game to solve
* @throws IloException if CPLEX fails to build the model
*/
public
IP
(
final
Set
game
)
throws
IloException
{
model
=
new
IloCplex
();
found
=
new
HashSet
<>();
// Define the variables.
include
=
new
IloNumVar
[
Set
.
NCARDS
];
appears
=
new
IloNumVar
[
Set
.
NFEATURES
][
Set
.
NVALUES
];
different
=
new
IloNumVar
[
Set
.
NFEATURES
];
for
(
int
c
=
0
;
c
<
Set
.
NCARDS
;
c
++)
{
include
[
c
]
=
model
.
boolVar
(
"include_"
+
c
);
}
for
(
int
f
=
0
;
f
<
Set
.
NFEATURES
;
f
++)
{
different
[
f
]
=
model
.
boolVar
(
"all_different_"
+
f
);
for
(
int
v
=
0
;
v
<
Set
.
NVALUES
;
v
++)
{
appears
[
f
][
v
]
=
model
.
boolVar
(
"feature_"
+
f
+
"_value_"
+
v
);
}
}
// We use the trivial objective (minimize 0).
// Constraint: the set must have the correct size.
model
.
addEq
(
model
.
sum
(
include
),
Set
.
SETSIZE
);
// Constraint: the number of values for any feature appearing in the set
// must be either 1 (all the same) or the set size (all different).
for
(
int
f
=
0
;
f
<
Set
.
NFEATURES
;
f
++)
{
model
.
addEq
(
model
.
sum
(
appears
[
f
]),
model
.
sum
(
1.0
,
model
.
prod
(
2.0
,
different
[
f
])));
}
// Constraint: a value of a feature appears in the set if and only if
// any card with that value is selected.
for
(
int
f
=
0
;
f
<
Set
.
NFEATURES
;
f
++)
{
for
(
int
v
=
0
;
v
<
Set
.
NVALUES
;
v
++)
{
IloLinearNumExpr
expr
=
model
.
linearNumExpr
();
for
(
int
c
:
game
.
findCards
(
f
,
v
))
{
model
.
addGe
(
appears
[
f
][
v
],
include
[
c
]);
expr
.
addTerm
(
1.0
,
include
[
c
]);
}
model
.
addLe
(
appears
[
f
][
v
],
expr
);
}
}
// Suppress output.
model
.
setOut
(
null
);
// Avoid warm starts (which do not work).
model
.
setParam
(
IloCplex
.
Param
.
Advance
,
0
);
}
/**
* Solves the model.
* @return true if the model is feasible (hence automatically optimal)
* @throws IloException if CPLEX blows up
*/
public
boolean
solve
()
throws
IloException
{
// Allow parallel threads.
model
.
setParam
(
IloCplex
.
Param
.
Threads
,
0
);
model
.
solve
();
if
(
model
.
getStatus
()
==
IloCplex
.
Status
.
Optimal
)
{
// A solution was found. Recover it.
HashSet
<
Integer
>
set
=
toSet
(
model
.
getValues
(
include
));
// Add a constraint to rule the solution out.
model
.
add
(
exclude
(
set
));
return
true
;
}
else
{
return
false
;
}
}
/**
* Converts the values of "include" in a solution to a card set.
* @param x the solution
* @return the corresponding set
*/
private
HashSet
<
Integer
>
toSet
(
final
double
[]
x
)
{
HashSet
<
Integer
>
s
=
new
HashSet
<>();
for
(
int
c
=
0
;
c
<
Set
.
NCARDS
;
c
++)
{
if
(
x
[
c
]
>
HALF
)
{
s
.
add
(
c
);
}
}
return
s
;
}
/**
* Solves a single model using a callback to exclude previous solutions.
* Note: To avoid having to synchronize methods (and test whether multiple
* threads found the same solution) we throttle the solver to a single
* thread.
* @return the number of solutions found.
* @throws IloException if CPLEX blows up
*/
public
int
solve2
()
throws
IloException
{
// Clear the solution accumulator.
found
.
clear
();
// Attach a callback to reject solutions.
model
.
use
(
new
Callback
(),
IloCplex
.
Callback
.
Context
.
Id
.
Candidate
);
// Force single threading.
model
.
setParam
(
IloCplex
.
Param
.
Threads
,
1
);
// Solve the model (stopping due to infeasibility).
model
.
solve
();
// Return the solution count.
return
found
.
size
();
}
/**
* Creates a constraint to exclude an identified set.
* @param set the set to exclude
* @return a range constraint that excludes the set
* @throws IloException if CPLEX gets snippy
*/
private
IloRange
exclude
(
final
HashSet
<
Integer
>
set
)
throws
IloException
{
IloLinearNumExpr
expr
=
model
.
linearNumExpr
();
for
(
int
c
:
set
)
{
expr
.
addTerm
(
1.0
,
include
[
c
]);
}
return
model
.
le
(
expr
,
Set
.
SETSIZE
-
1
);
}
/**
* Closes the model.
*/
@Override
public
void
close
()
{
model
.
close
();
}
/**
* Callback implements a generic callback to add no good constraints whenever
* a set is found.
*/
private
final
class
Callback
implements
IloCplex
.
Callback
.
Function
{
/**
* Constructor (which does nothing).
*/
Callback
()
{
};
/**
* Rejects the current candidate solution using a no-good constraint.
* @param context the context information
* @throws IloException if CPLEX gets snippy
*/
@Override
public
void
invoke
(
final
IloCplex
.
Callback
.
Context
context
)
throws
IloException
{
// Get the candidate set.
double
[]
x
=
context
.
getCandidatePoint
(
include
);
// Convert it to a set.
HashSet
<
Integer
>
s
=
toSet
(
x
);
// Add the set to the pool of solutions.
found
.
add
(
s
);
// Turn the set into a range constraint.
IloRange
r
=
exclude
(
s
);
// Reject the solution.
context
.
rejectCandidate
(
r
);
}
}
}
This diff is collapsed.
Click to expand it.
src/setgame/Set.java
0 → 100644
+
149
−
0
View file @
ebffca83
package
setgame
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collection
;
import
java.util.Collections
;
import
java.util.HashMap
;
import
java.util.HashSet
;
/**
* Set is the container for a game instance.
* @author Paul A. Rubin (rubin@msu.edu)
*/
public
final
class
Set
{
/** NCARDS is the number of cards in the game. */
public
static
final
int
NCARDS
=
81
;
/** NFEATURES is the number of features on a card. */
public
static
final
int
NFEATURES
=
4
;
/** NVALUES is the number of values a feature can have. */
public
static
final
int
NVALUES
=
3
;
/** SETSIZE is the number of cards in a set. */
public
static
final
int
SETSIZE
=
3
;
private
final
int
[][]
cards
;
// the card deck
private
final
HashMap
<
Integer
,
HashMap
<
Integer
,
HashSet
<
Integer
>>>
hasFeature
;
private
int
found
;
// number of valid sets found
/**
* Constructor.
*/
public
Set
()
{
cards
=
new
int
[
NCARDS
][
NFEATURES
];
int
[]
divisors
=
new
int
[
NFEATURES
+
1
];
divisors
[
0
]
=
1
;
for
(
int
i
=
1
;
i
<=
NFEATURES
;
i
++)
{
divisors
[
i
]
=
NVALUES
*
divisors
[
i
-
1
];
}
// Fill in the cards.
for
(
int
c
=
0
;
c
<
NCARDS
;
c
++)
{
cards
[
c
][
0
]
=
c
%
divisors
[
1
];
for
(
int
f
=
1
;
f
<
NFEATURES
;
f
++)
{
cards
[
c
][
f
]
=
(
c
%
divisors
[
f
+
1
])
/
divisors
[
f
];
}
}
// Fill in the feature possession map.
hasFeature
=
new
HashMap
<>();
for
(
int
f
=
0
;
f
<
NFEATURES
;
f
++)
{
HashMap
<
Integer
,
HashSet
<
Integer
>>
m
=
new
HashMap
<>();
for
(
int
v
=
0
;
v
<
NVALUES
;
v
++)
{
HashSet
<
Integer
>
s
=
new
HashSet
<>();
for
(
int
c
=
0
;
c
<
NCARDS
;
c
++)
{
if
(
cards
[
c
][
f
]
==
v
)
{
s
.
add
(
c
);
}
}
m
.
put
(
v
,
s
);
}
hasFeature
.
put
(
f
,
m
);
}
}
/**
* Gets the set of cards with a particular value of a particular feature.
* @param feature the feature
* @param value the target value
* @return the set of cards with that value
*/
public
Collection
<
Integer
>
findCards
(
final
int
feature
,
final
int
value
)
{
return
new
HashSet
<>(
hasFeature
.
get
(
feature
).
get
(
value
));
}
/**
* Prints a set.
* @param set the set to print
*/
public
void
printSet
(
final
Collection
<
Integer
>
set
)
{
ArrayList
<
Integer
>
list
=
new
ArrayList
<>(
set
);
Collections
.
sort
(
list
);
for
(
int
c
:
list
)
{
System
.
out
.
println
(
Arrays
.
toString
(
cards
[
c
])
+
" ("
+
c
+
")"
);
}
}
/**
* Computes all valid sets by brute force (using recursion).
* @return the number of sets found
*/
public
int
findAllSets
()
{
found
=
0
;
HashMap
<
Integer
,
HashSet
<
Integer
>>
map
=
new
HashMap
<>();
for
(
int
f
=
0
;
f
<
NFEATURES
;
f
++)
{
map
.
put
(
f
,
new
HashSet
<>());
}
enumerate
(
1
,
0
,
map
);
return
found
;
}
/**
* Recursively enumerates all valid sets.
* @param level the level (1 ... SETSIZE) at which the function is called
* @param start the initial card index
* @param values the accumulated values of all features
*/
private
void
enumerate
(
final
int
level
,
final
int
start
,
final
HashMap
<
Integer
,
HashSet
<
Integer
>>
values
)
{
// Check for vacuous cases (level or start index too high).
if
(
level
>
SETSIZE
||
start
==
NCARDS
)
{
return
;
}
for
(
int
c
=
start
;
c
<
NCARDS
;
c
++)
{
// Clone the values map, adding the value of each feature in card c.
HashMap
<
Integer
,
HashSet
<
Integer
>>
v
=
new
HashMap
<>();
for
(
int
f
=
0
;
f
<
NFEATURES
;
f
++)
{
HashSet
<
Integer
>
s
=
new
HashSet
<>(
values
.
get
(
f
));
s
.
add
(
cards
[
c
][
f
]);
v
.
put
(
f
,
s
);
}
if
(
level
==
SETSIZE
)
{
// Final level: check for a valid set and if valid count it.
boolean
valid
=
true
;
for
(
int
f
=
0
;
f
<
NFEATURES
;
f
++)
{
if
(
v
.
get
(
f
).
size
()
==
2
)
{
valid
=
false
;
break
;
}
}
if
(
valid
)
{
found
+=
1
;
}
}
else
{
// Call enumerate at the next level.
enumerate
(
level
+
1
,
c
+
1
,
v
);
}
}
}
/**
* Gets the value of a particular feature for every card.
* @param feature the feature
* @return the vector of feature values
*/
public
int
[]
getValues
(
final
int
feature
)
{
int
[]
v
=
new
int
[
NCARDS
];
for
(
int
c
=
0
;
c
<
NCARDS
;
c
++)
{
v
[
c
]
=
cards
[
c
][
feature
];
}
return
v
;
}
}
This diff is collapsed.
Click to expand it.
src/setgame/SetGame.java
0 → 100644
+
71
−
0
View file @
ebffca83
package
setgame
;
import
ilog.concert.IloException
;
/**
* SetGame attempts to find all possible "sets" from the game Set.
*
* This addresses a question on OR Stack Exchange:
* https://or.stackexchange.com/questions/10820/how-to-generate-feasible
* -sets-with-a-mip
*
* @author Paul A. Rubin (rubin@msu.edu)
*/
public
final
class
SetGame
{
/** Dummy constructor. */
private
SetGame
()
{
}
/**
* Solves for all possible sets in the game.
* @param args the command line arguments (ignored)
*/
@SuppressWarnings
({
"checkstyle:magicnumber"
})
public
static
void
main
(
final
String
[]
args
)
{
// Generate the card deck.
Set
deck
=
new
Set
();
// Try brute force.
long
clock
=
System
.
currentTimeMillis
();
int
count
=
deck
.
findAllSets
();
clock
=
System
.
currentTimeMillis
()
-
clock
;
System
.
out
.
println
(
"Brute force found "
+
count
+
" sets in "
+
(
0.001
*
clock
)
+
" seconds."
);
// Try the IP model using a callback.
System
.
out
.
println
(
"Trying the IP model with callback ..."
);
clock
=
System
.
currentTimeMillis
();
try
(
IP
ip
=
new
IP
(
deck
))
{
int
found
=
ip
.
solve2
();
clock
=
System
.
currentTimeMillis
()
-
clock
;
System
.
out
.
println
(
"Number of sets found = "
+
found
);
System
.
out
.
println
(
"Time required = "
+
(
0.001
*
clock
)
+
" seconds."
);
}
catch
(
IloException
ex
)
{
System
.
out
.
println
(
ex
.
getMessage
());
}
// Try the basic IP model.
System
.
out
.
println
(
"Trying the basic IP model ..."
);
clock
=
System
.
currentTimeMillis
();
try
(
IP
ip
=
new
IP
(
deck
))
{
int
found
=
0
;
while
(
ip
.
solve
())
{
found
+=
1
;
}
clock
=
System
.
currentTimeMillis
()
-
clock
;
System
.
out
.
println
(
"Number of sets found = "
+
found
);
System
.
out
.
println
(
"Time required = "
+
(
0.001
*
clock
)
+
" seconds."
);
}
catch
(
IloException
ex
)
{
System
.
out
.
println
(
ex
.
getMessage
());
}
// Try the CP model.
System
.
out
.
println
(
"Trying the CP model ..."
);
clock
=
System
.
currentTimeMillis
();
try
(
CP
cp
=
new
CP
(
deck
))
{
int
found
=
cp
.
solve
();
clock
=
System
.
currentTimeMillis
()
-
clock
;
System
.
out
.
println
(
"Number of sets found = "
+
found
);
System
.
out
.
println
(
"Time required = "
+
(
0.001
*
clock
)
+
" seconds."
);
}
catch
(
IloException
ex
)
{
System
.
out
.
println
(
ex
.
getMessage
());
}
}
}
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment