Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
SAT4J
sat4j
Commits
1fc1c0dd
Commit
1fc1c0dd
authored
Apr 26, 2017
by
Anne Parrain
Browse files
Add a solver with the "Divide by 2" feature. Correctthe previous version
and remove it from the standard process.
parent
c5a99b7a
Pipeline
#138
failed with stage
Changes
6
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
org.sat4j.pb/src/main/java/org/sat4j/pb/SolverFactory.java
View file @
1fc1c0dd
...
...
@@ -68,6 +68,7 @@ import org.sat4j.pb.core.PBSolverCP;
import
org.sat4j.pb.core.PBSolverCPCardLearning
;
import
org.sat4j.pb.core.PBSolverCPClauseLearning
;
import
org.sat4j.pb.core.PBSolverCPLong
;
import
org.sat4j.pb.core.PBSolverCPLongDivideBy2
;
import
org.sat4j.pb.core.PBSolverCPLongReduceToCard
;
import
org.sat4j.pb.core.PBSolverCPLongRounding
;
//import org.sat4j.pb.core.PBSolverCard;
...
...
@@ -632,6 +633,18 @@ public final class SolverFactory extends ASolverFactory<IPBSolver> {
return
solver
;
}
private
static
PBSolverCP
newPBCPStarDivideBy2
(
PBDataStructureFactory
dsf
,
IOrder
order
,
boolean
noRemove
)
{
MiniSATLearning
<
PBDataStructureFactory
>
learning
=
new
MiniSATLearning
<
PBDataStructureFactory
>();
PBSolverCP
solver
=
new
PBSolverCPLongDivideBy2
(
learning
,
dsf
,
order
,
noRemove
);
learning
.
setDataStructureFactory
(
solver
.
getDSFactory
());
learning
.
setVarActivityListener
(
solver
);
solver
.
setRestartStrategy
(
new
ArminRestarts
());
solver
.
setLearnedConstraintsDeletionStrategy
(
solver
.
lbd_based
);
return
solver
;
}
private
static
PBSolverCP
newPBCPStarRounding
(
PBDataStructureFactory
dsf
,
IOrder
order
,
boolean
noRemove
)
{
MiniSATLearning
<
PBDataStructureFactory
>
learning
=
new
MiniSATLearning
<
PBDataStructureFactory
>();
...
...
@@ -704,6 +717,11 @@ public final class SolverFactory extends ASolverFactory<IPBSolver> {
new
VarOrderHeapObjective
(),
true
);
}
public
static
IPBSolver
newCuttingPlanesStarDivideBy2
()
{
return
newPBCPStarDivideBy2
(
new
PBMaxClauseCardConstrDataStructure
(),
new
VarOrderHeapObjective
(),
true
);
}
/**
* Cutting Planes based solver. The inference during conflict analysis is
* based on cutting planes instead of resolution as in a SAT solver.
...
...
org.sat4j.pb/src/main/java/org/sat4j/pb/constraints/pb/ConflictMap.java
View file @
1fc1c0dd
...
...
@@ -35,6 +35,7 @@ import org.sat4j.core.VecInt;
import
org.sat4j.minisat.constraints.cnf.Lits
;
import
org.sat4j.minisat.core.ILits
;
import
org.sat4j.minisat.core.VarActivityListener
;
import
org.sat4j.pb.core.PBSolverStats
;
import
org.sat4j.specs.IVecInt
;
import
org.sat4j.specs.IteratorInt
;
...
...
@@ -49,6 +50,9 @@ public class ConflictMap extends MapPb implements IConflict {
public
static
final
int
NOPOSTPROCESS
=
0
;
public
static
final
int
POSTPROCESSTOCLAUSE
=
1
;
public
static
final
int
POSTPROCESSTOCARD
=
2
;
public
static
final
int
POSTPROCESSDIVIDEBY2
=
3
;
private
static
final
int
NOTCOMPUTED
=
-
2
;
protected
boolean
hasBeenReduced
=
false
;
protected
long
numberOfReductions
=
0
;
...
...
@@ -59,6 +63,9 @@ public class ConflictMap extends MapPb implements IConflict {
protected
BigInteger
currentSlack
;
protected
int
currentLevel
;
private
int
backtrackLevel
=
NOTCOMPUTED
;
private
final
PBSolverStats
stats
;
/**
* allows to access directly to all variables belonging to a particular
...
...
@@ -82,24 +89,31 @@ public class ConflictMap extends MapPb implements IConflict {
public
static
IConflict
createConflict
(
PBConstr
cpb
,
int
level
,
boolean
noRemove
)
{
return
new
ConflictMap
(
cpb
,
level
,
noRemove
,
NOPOSTPROCESS
);
return
new
ConflictMap
(
cpb
,
level
,
noRemove
,
NOPOSTPROCESS
,
null
);
}
public
static
IConflict
createConflict
(
PBConstr
cpb
,
int
level
,
boolean
noRemove
,
int
postProcessing
)
{
return
new
ConflictMap
(
cpb
,
level
,
noRemove
,
postProcessing
);
return
new
ConflictMap
(
cpb
,
level
,
noRemove
,
postProcessing
,
null
);
}
public
static
IConflict
createConflict
(
PBConstr
cpb
,
int
level
,
boolean
noRemove
,
int
postProcessing
,
PBSolverStats
stats
)
{
return
new
ConflictMap
(
cpb
,
level
,
noRemove
,
postProcessing
,
stats
);
}
ConflictMap
(
PBConstr
cpb
,
int
level
)
{
this
(
cpb
,
level
,
false
,
NOPOSTPROCESS
);
this
(
cpb
,
level
,
false
,
NOPOSTPROCESS
,
null
);
}
ConflictMap
(
PBConstr
cpb
,
int
level
,
boolean
noRemove
)
{
this
(
cpb
,
level
,
noRemove
,
NOPOSTPROCESS
);
this
(
cpb
,
level
,
noRemove
,
NOPOSTPROCESS
,
null
);
}
ConflictMap
(
PBConstr
cpb
,
int
level
,
boolean
noRemove
,
int
postProcessing
)
{
ConflictMap
(
PBConstr
cpb
,
int
level
,
boolean
noRemove
,
int
postProcessing
,
PBSolverStats
stats
)
{
super
(
cpb
,
level
,
noRemove
);
this
.
stats
=
stats
;
this
.
voc
=
cpb
.
getVocabulary
();
this
.
currentLevel
=
level
;
initStructures
();
...
...
@@ -110,10 +124,14 @@ public class ConflictMap extends MapPb implements IConflict {
case
POSTPROCESSTOCARD:
this
.
postProcess
=
new
PostProcessToCard
();
break
;
case
POSTPROCESSDIVIDEBY2:
this
.
postProcess
=
new
PostProcessDivideBy2
();
break
;
default
:
this
.
postProcess
=
new
NoPostProcess
();
break
;
}
if
(
noRemove
)
this
.
rmSatLit
=
new
NoRemoveSatisfied
();
else
...
...
@@ -224,18 +242,6 @@ public class ConflictMap extends MapPb implements IConflict {
*/
private
interface
IPostProcess
{
void
postProcess
(
int
dl
);
/**
* retourne le niveau de backtrack : c'est-?-dire le niveau le plus haut
* pour lequel la contrainte est assertive
*
* @param maxLevel
* le plus bas niveau pour lequel la contrainte est assertive
* @return the highest level (smaller int) for which the constraint is
* assertive.
*/
int
getBacktrackLevel
(
int
maxLevel
);
}
private
final
IPostProcess
postProcess
;
...
...
@@ -245,56 +251,6 @@ public class ConflictMap extends MapPb implements IConflict {
}
/**
* computes the level for the backtrack : the highest decision level for
* which the conflict is assertive.
*
* @param maxLevel
* the lowest level for which the conflict is assertive
* @return the highest level (smaller int) for which the constraint is
* assertive.
*/
public
int
getBacktrackLevel
(
int
maxLevel
)
{
// we are looking for a level higher than maxLevel
// where the constraint is still assertive
VecInt
lits
;
int
level
;
int
indStop
=
levelToIndex
(
maxLevel
)
-
1
;
int
indStart
=
levelToIndex
(
0
);
BigInteger
slack
=
computeSlack
(
0
)
.
subtract
(
ConflictMap
.
this
.
degree
);
int
previous
=
0
;
for
(
int
indLevel
=
indStart
;
indLevel
<=
indStop
;
indLevel
++)
{
if
(
ConflictMap
.
this
.
byLevel
[
indLevel
]
!=
null
)
{
level
=
indexToLevel
(
indLevel
);
assert
ConflictMap
.
this
.
computeSlack
(
level
)
.
subtract
(
ConflictMap
.
this
.
degree
).
equals
(
slack
);
if
(
ConflictMap
.
this
.
isImplyingLiteralOrdered
(
level
,
slack
))
{
break
;
}
// updating the new slack
lits
=
ConflictMap
.
this
.
byLevel
[
indLevel
];
int
lit
;
for
(
IteratorInt
iterator
=
lits
.
iterator
();
iterator
.
hasNext
();)
{
lit
=
iterator
.
next
();
if
(
ConflictMap
.
this
.
voc
.
isFalsified
(
lit
)
&&
ConflictMap
.
this
.
voc
.
getLevel
(
lit
)
==
indexToLevel
(
indLevel
))
{
slack
=
slack
.
subtract
(
ConflictMap
.
this
.
weightedLits
.
get
(
lit
));
}
}
if
(!
lits
.
isEmpty
())
{
previous
=
level
;
}
}
}
assert
previous
==
oldGetBacktrackLevel
(
maxLevel
);
return
previous
;
}
}
private
class
PostProcessToClause
implements
IPostProcess
{
...
...
@@ -303,7 +259,8 @@ public class ConflictMap extends MapPb implements IConflict {
&&
(!
ConflictMap
.
this
.
degree
.
equals
(
BigInteger
.
ONE
)))
{
int
litLevel
,
ilit
;
if
(
ConflictMap
.
this
.
assertiveLiteral
!=
-
1
)
{
this
.
chooseAssertiveLiteral
(
dl
);
ConflictMap
.
this
.
assertiveLiteral
=
this
.
chooseAssertiveLiteral
(
dl
);
int
lit
=
ConflictMap
.
this
.
weightedLits
.
getLit
(
ConflictMap
.
this
.
assertiveLiteral
);
...
...
@@ -330,23 +287,21 @@ public class ConflictMap extends MapPb implements IConflict {
ConflictMap
.
this
.
degree
=
BigInteger
.
ONE
;
ConflictMap
.
this
.
assertiveLiteral
=
ConflictMap
.
this
.
weightedLits
.
getFromAllLits
(
lit
);
// ConflictMap.this.currentSlack = ConflictMap.this
// .computeSlack(this.assertiveLevel);
// assert ConflictMap.this.isAssertive(this.backtrackLevel);
assert
this
.
backtrackLevel
==
oldGetBacktrackLevel
(
dl
);
assert
ConflictMap
.
this
.
backtrackLevel
==
oldGetBacktrackLevel
(
dl
);
}
}
}
private
int
assertiveLevel
;
private
int
backtrackLevel
;
public
void
chooseAssertiveLiteral
(
int
maxLevel
)
{
public
int
chooseAssertiveLiteral
(
int
maxLevel
)
{
// we are looking for a level higher than maxLevel
// where the constraint is still assertive
// update ConflictMap.this.assertiveLiteral
VecInt
lits
;
int
level
;
int
indStop
=
levelToIndex
(
maxLevel
);
// ou maxLevel - 1 ???
int
indStart
=
levelToIndex
(
0
);
BigInteger
slack
=
ConflictMap
.
this
.
computeSlack
(
0
)
...
...
@@ -359,8 +314,8 @@ public class ConflictMap extends MapPb implements IConflict {
.
subtract
(
ConflictMap
.
this
.
degree
).
equals
(
slack
);
if
(
ConflictMap
.
this
.
isImplyingLiteralOrdered
(
level
,
slack
))
{
this
.
backtrackLevel
=
previous
;
this
.
assertiveLevel
=
level
;
ConflictMap
.
this
.
backtrackLevel
=
previous
;
assertiveLevel
=
level
;
break
;
}
// updating the new slack
...
...
@@ -382,11 +337,9 @@ public class ConflictMap extends MapPb implements IConflict {
}
}
assert
this
.
backtrackLevel
==
oldGetBacktrackLevel
(
maxLevel
);
}
public
int
getBacktrackLevel
(
int
maxLevel
)
{
return
this
.
backtrackLevel
;
assert
ConflictMap
.
this
.
backtrackLevel
==
oldGetBacktrackLevel
(
maxLevel
);
return
assertiveLiteral
;
}
}
...
...
@@ -469,13 +422,13 @@ public class ConflictMap extends MapPb implements IConflict {
ConflictMap
.
this
.
assertiveLiteral
=
ConflictMap
.
this
.
weightedLits
.
getFromAllLits
(
lit
);
assert
this
.
backtrackLevel
==
oldGetBacktrackLevel
(
dl
);
assert
ConflictMap
.
this
.
backtrackLevel
==
oldGetBacktrackLevel
(
dl
);
}
}
}
private
int
assertiveLevel
;
private
int
backtrackLevel
;
public
int
chooseAssertiveLiteral
(
int
maxLevel
)
{
// we are looking for a level higher than maxLevel
...
...
@@ -493,10 +446,10 @@ public class ConflictMap extends MapPb implements IConflict {
level
=
indexToLevel
(
indLevel
);
assert
ConflictMap
.
this
.
computeSlack
(
level
)
.
subtract
(
ConflictMap
.
this
.
degree
).
equals
(
slack
);
if
(
ConflictMap
.
this
.
isImplyingLiteralOrdered
Indexes
(
level
,
slack
,
literals
))
{
if
(
ConflictMap
.
this
.
isImplyingLiteralOrdered
(
level
,
slack
,
literals
))
{
this
.
assertiveLevel
=
level
;
this
.
backtrackLevel
=
previous
;
ConflictMap
.
this
.
backtrackLevel
=
previous
;
break
;
}
// updating the new slack
...
...
@@ -530,13 +483,23 @@ public class ConflictMap extends MapPb implements IConflict {
}
}
assert
this
.
backtrackLevel
==
oldGetBacktrackLevel
(
maxLevel
);
assert
ConflictMap
.
this
.
backtrackLevel
==
oldGetBacktrackLevel
(
maxLevel
);
assert
literals
.
size
()
>
0
;
return
maxLit
;
}
public
int
getBacktrackLevel
(
int
maxLevel
)
{
return
this
.
backtrackLevel
;
}
private
class
PostProcessDivideBy2
implements
IPostProcess
{
public
void
postProcess
(
int
dl
)
{
int
nbBits
=
ConflictMap
.
this
.
reduceCoeffsByPower2
();
if
(
nbBits
>
0
)
{
stats
.
numberOfReductionsByPower2
++;
stats
.
numberOfRightShiftsForCoeffs
=
stats
.
numberOfRightShiftsForCoeffs
+
nbBits
;
}
}
}
...
...
@@ -905,21 +868,6 @@ public class ConflictMap extends MapPb implements IConflict {
// a particular level
// uses the coefs data structure (where coefficients are decreasing ordered)
// to parse each literal
private
boolean
isImplyingLiteralOrderedIndexes
(
int
dl
,
BigInteger
slack
,
IVecInt
literals
)
{
assert
literals
.
size
()
==
0
;
int
ilit
,
litLevel
;
for
(
int
i
=
0
;
i
<
size
();
i
++)
{
ilit
=
this
.
weightedLits
.
getLit
(
i
);
litLevel
=
this
.
voc
.
getLevel
(
ilit
);
if
((
litLevel
>=
dl
||
this
.
voc
.
isUnassigned
(
ilit
))
&&
slack
.
compareTo
(
this
.
weightedLits
.
getCoef
(
i
))
<
0
)
{
literals
.
push
(
i
);
}
}
return
literals
.
size
()
>
0
;
}
private
boolean
isImplyingLiteralOrdered
(
int
dl
,
BigInteger
slack
,
IVecInt
literals
)
{
assert
literals
.
size
()
==
0
;
...
...
@@ -929,7 +877,7 @@ public class ConflictMap extends MapPb implements IConflict {
litLevel
=
this
.
voc
.
getLevel
(
ilit
);
if
((
litLevel
>=
dl
||
this
.
voc
.
isUnassigned
(
ilit
))
&&
slack
.
compareTo
(
this
.
weightedLits
.
getCoef
(
i
))
<
0
)
{
literals
.
push
(
i
lit
);
literals
.
push
(
i
);
}
}
return
literals
.
size
()
>
0
;
...
...
@@ -1068,7 +1016,46 @@ public class ConflictMap extends MapPb implements IConflict {
* assertive.
*/
public
int
getBacktrackLevel
(
int
maxLevel
)
{
return
this
.
postProcess
.
getBacktrackLevel
(
maxLevel
);
if
(
this
.
backtrackLevel
==
NOTCOMPUTED
)
{
// we are looking for a level higher than maxLevel
// where the constraint is still assertive
VecInt
lits
;
int
level
;
int
indStop
=
levelToIndex
(
maxLevel
)
-
1
;
int
indStart
=
levelToIndex
(
0
);
BigInteger
slack
=
computeSlack
(
0
)
.
subtract
(
ConflictMap
.
this
.
degree
);
int
previous
=
0
;
for
(
int
indLevel
=
indStart
;
indLevel
<=
indStop
;
indLevel
++)
{
if
(
ConflictMap
.
this
.
byLevel
[
indLevel
]
!=
null
)
{
level
=
indexToLevel
(
indLevel
);
assert
ConflictMap
.
this
.
computeSlack
(
level
)
.
subtract
(
ConflictMap
.
this
.
degree
).
equals
(
slack
);
if
(
ConflictMap
.
this
.
isImplyingLiteralOrdered
(
level
,
slack
))
break
;
// updating the new slack
lits
=
ConflictMap
.
this
.
byLevel
[
indLevel
];
int
lit
;
for
(
IteratorInt
iterator
=
lits
.
iterator
();
iterator
.
hasNext
();)
{
lit
=
iterator
.
next
();
if
(
ConflictMap
.
this
.
voc
.
isFalsified
(
lit
)
&&
ConflictMap
.
this
.
voc
.
getLevel
(
lit
)
==
indexToLevel
(
indLevel
))
slack
=
slack
.
subtract
(
ConflictMap
.
this
.
weightedLits
.
get
(
lit
));
}
if
(!
lits
.
isEmpty
())
previous
=
level
;
}
}
assert
previous
==
oldGetBacktrackLevel
(
maxLevel
);
return
previous
;
}
else
return
this
.
backtrackLevel
;
}
public
int
oldGetBacktrackLevel
(
int
maxLevel
)
{
...
...
org.sat4j.pb/src/main/java/org/sat4j/pb/constraints/pb/MapPb.java
View file @
1fc1c0dd
...
...
@@ -75,10 +75,14 @@ public class MapPb implements IDataStructurePB {
}
public
int
reduceCoeffsByPower2
()
{
int
nbBits
=
1
;
for
(
int
i
=
0
;
i
<
this
.
weightedLits
.
size
()
&&
nbBits
>
0
;
i
++)
assert
this
.
weightedLits
.
size
()
>
0
;
int
nbBits
=
this
.
weightedLits
.
getCoef
(
0
).
bitLength
();
for
(
int
i
=
0
;
i
<
this
.
weightedLits
.
size
()
&&
nbBits
>
0
;
i
++)
{
nbBits
=
Math
.
min
(
nbBits
,
this
.
weightedLits
.
getCoef
(
i
).
getLowestSetBit
());
if
(
nbBits
==
0
)
break
;
}
if
(
nbBits
>
0
)
{
for
(
int
i
=
0
;
i
<
this
.
weightedLits
.
size
();
i
++)
{
this
.
weightedLits
.
changeCoef
(
i
,
...
...
org.sat4j.pb/src/main/java/org/sat4j/pb/core/PBSolverCP.java
View file @
1fc1c0dd
...
...
@@ -164,12 +164,6 @@ public class PBSolverCP extends PBSolver {
}
// assertive PB-constraint is build and referenced
int
nbBits
=
confl
.
reduceCoeffsByPower2
();
if
(
nbBits
>
0
)
{
stats
.
numberOfReductionsByPower2
++;
stats
.
numberOfRightShiftsForCoeffs
=
stats
.
numberOfRightShiftsForCoeffs
+
nbBits
;
}
confl
.
postProcess
(
currentLevel
);
PBConstr
resConstr
=
(
PBConstr
)
this
.
dsfactory
.
createUnregisteredPseudoBooleanConstraint
(
confl
);
...
...
org.sat4j.pb/src/main/java/org/sat4j/pb/core/PBSolverCPLongDivideBy2.java
0 → 100644
View file @
1fc1c0dd
package
org.sat4j.pb.core
;
import
org.sat4j.minisat.core.IOrder
;
import
org.sat4j.minisat.core.LearningStrategy
;
import
org.sat4j.minisat.core.RestartStrategy
;
import
org.sat4j.minisat.core.SearchParams
;
import
org.sat4j.pb.constraints.pb.ConflictMap
;
import
org.sat4j.pb.constraints.pb.ConflictMapReduceToClause
;
import
org.sat4j.pb.constraints.pb.IConflict
;
import
org.sat4j.pb.constraints.pb.PBConstr
;
public
class
PBSolverCPLongDivideBy2
extends
PBSolverCPLong
{
public
PBSolverCPLongDivideBy2
(
LearningStrategy
<
PBDataStructureFactory
>
learner
,
PBDataStructureFactory
dsf
,
IOrder
order
)
{
super
(
learner
,
dsf
,
order
);
// TODO Auto-generated constructor stub
}
public
PBSolverCPLongDivideBy2
(
LearningStrategy
<
PBDataStructureFactory
>
learner
,
PBDataStructureFactory
dsf
,
SearchParams
params
,
IOrder
order
,
RestartStrategy
restarter
)
{
super
(
learner
,
dsf
,
params
,
order
,
restarter
);
// TODO Auto-generated constructor stub
}
public
PBSolverCPLongDivideBy2
(
LearningStrategy
<
PBDataStructureFactory
>
learner
,
PBDataStructureFactory
dsf
,
SearchParams
params
,
IOrder
order
)
{
super
(
learner
,
dsf
,
params
,
order
);
// TODO Auto-generated constructor stub
}
public
PBSolverCPLongDivideBy2
(
LearningStrategy
<
PBDataStructureFactory
>
learner
,
PBDataStructureFactory
dsf
,
IOrder
order
,
boolean
noRemove
)
{
super
(
learner
,
dsf
,
order
,
noRemove
);
// TODO Auto-generated constructor stub
}
public
PBSolverCPLongDivideBy2
(
LearningStrategy
<
PBDataStructureFactory
>
learner
,
PBDataStructureFactory
dsf
,
SearchParams
params
,
IOrder
order
,
RestartStrategy
restarter
,
boolean
noRemove
)
{
super
(
learner
,
dsf
,
params
,
order
,
restarter
,
noRemove
);
// TODO Auto-generated constructor stub
}
public
PBSolverCPLongDivideBy2
(
LearningStrategy
<
PBDataStructureFactory
>
learner
,
PBDataStructureFactory
dsf
,
SearchParams
params
,
IOrder
order
,
boolean
noRemove
)
{
super
(
learner
,
dsf
,
params
,
order
,
noRemove
);
// TODO Auto-generated constructor stub
}
@Override
protected
IConflict
chooseConflict
(
PBConstr
myconfl
,
int
level
)
{
return
ConflictMapReduceToClause
.
createConflict
(
myconfl
,
level
,
noRemove
,
ConflictMap
.
POSTPROCESSDIVIDEBY2
,
stats
);
}
@Override
public
String
toString
(
String
prefix
)
{
return
super
.
toString
(
prefix
)
+
"\n"
+
prefix
+
"Performs a post-processing after conflict analysis in order to divide by 2 as much as possible coefficients of learned constraints"
;
}
}
org.sat4j.pb/src/test/java/org/sat4j/pb/constraints/PBCPMaxClauseCardConstrLearningDivideBy2Test.java
0 → 100644
View file @
1fc1c0dd
package
org.sat4j.pb.constraints
;
import
org.sat4j.pb.IPBSolver
;
import
org.sat4j.pb.SolverFactory
;
public
class
PBCPMaxClauseCardConstrLearningDivideBy2Test
extends
AbstractPseudoBooleanAndPigeonHoleTest
{
public
PBCPMaxClauseCardConstrLearningDivideBy2Test
(
String
arg
)
{
super
(
arg
);
// TODO Auto-generated constructor stub
}
@Override
protected
IPBSolver
createSolver
()
{
return
SolverFactory
.
newCuttingPlanesStarDivideBy2
();
}
}
Write
Preview
Supports
Markdown
0%
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!
Cancel
Please
register
or
sign in
to comment