Package: pycsp3
PyCSP3 v2.2 (December 10, 2023)This is Version 2.2 of PyCSP3, a library in Python 3 (version 3.8 or later) for modeling combinatorial constrained problems;
seewww.pycsp.org.
PyCSP3 is inspired from bothJvCSP3(a Java-based API)
andNumberjack.
With PyCSP3, it is possible to generate instances of:CSP (Constraint Satisfaction Problem)COP (Constraint Optimization Problem)in format XCSP3; seewww.xcsp.org.
Currently, PyCSP3 is targeted toXCSP3-core, which allows us to use integer variables (with finite domains) and popular
constraints.Note that:a dedicatedwebsitewith more than 60 Jupyter notebooks is availablea well-documentedguideis availablePyCSP3 is available as aPyPi packagea GitHub repository with more than 300 models is available atpycsp3-modelsAt this stage, one can run two embedded solvers:the constraint solverACE(AbsCon Essence), with the option -solve or the option -solver=acethe constraint solverChoco, with the option -solver=chocoInformation about how piloting these embedded solvers can be found inthis document.Of course, it is possible to launch on generated XCSP3 instances (files) any solver that recognizes the XCSP3 format.
For example, see the solvers involved in the2022 and 2023 Competitions.
It is also immediate to run ACE or Choco on XCSP3 instances (files) as the respective executables (jar files) are
present in directoriespycsp3/solvers/aceandpycsp3/solvers/choco.
For example, for running ACE on the XCSP3 instance 'zebra.xml', just execute:java -jar ACE-YY-MM.jar zebra.xmlwhile replacing YY and MM with the current values that are present in the name of the jar file.Note that it is also possible to pilot solvers with Python; seePyCSP3 Solving Process.1) Installation from PyPiThis is the easiest way of installing PyCSP3.Note that you need first Python 3 (version 3.8, or later) to be installed.
You can do it, for example, frompython.orgInstalling PyCSP3 (Linux)Check if 'pip3' is installed. If it is not the case, execute:sudo apt install python3-pipThen, install PyCSP3 with the command 'pip3':sudo pip3 install pycsp3For using the -solve or -solver options, you need to have Java (at least, version 11) installed:sudo apt-get install openjdk-11-jdkInstalling PyCSP3 (Mac OS)If Python 3 is installed on your system, the command 'pip3' should already be present.Install PyCSP3 with the command 'pip3':sudo pip3 install pycsp3For using the -solve or -solver options, you need to have Java (at least, version 11) installed.Installing PyCSP3 (Windows)You may need to upgrade 'pip'. Open the console and type:python -m pip install --upgrade pipThen, for installing pycsp3, type:python -m pip install pycsp3For using the -solve or -solver options, you need to install (at least) Java version 11:https://www.oracle.com/java/technologies/javase-downloads.htmlAnd add in the PATH the java command, for example, temporally, with the command:set path=%path%;C:/Program Files/Java/jdk-14.0.1/bin/You can check the java command by typing in your console:java --versionUpdating the Version of PyCSP3 (for PyPi)For updating your version of PyCSP3, simply execute:For linux/Mac:sudo pip3 install --upgrade pycsp3For Windows:python -m pip install --upgrade pycsp3Copying a Pool of ModelsThe procedure, just below, will be removed at the next version because a GitHub repository is now available with more than 300 models
atpycsp3-models.PyCSP3 is accompanied by more than 100 models.
To get them in a subdirectoryproblemsof your current directory, execute:python3 -m pycsp3 (For linux/Mac)python -m pycsp3 (For Windows)And you can test the compilation of one of the models, for example:python3 problems/csp/single/Zebra.py (For Linux/Mac)python problems\csp\single\Zebra.py (For Windows)2) Installation (alternative) by Cloning from GitHubAn alternative to PyPi is to clone the code from GitHub.
Here is an illustration for MAC OS.
We assume that Python 3 is installed (otherwise, typeport install python38for example), and consequently 'pip3' is also installed.
In a console, type:git clone https://github.com/xcsp3team/pycsp3.gitpip3 install lxmlYou may need to update the environment variable 'PYTHONPATH', by typing for example:export PYTHONPATH=$PYTHONPATH:.3) Compilation and ExamplesWe succinctly introduce a few PyCSP3 models, showing how to compile them with different options.
However, note that many illustrations are available onwww.pycsp.org, notably manymodelswith
Jupyter notebooks.First, we give some general information about compilation.Compiling PyCSP3 ModelsFor generating an XCSP3 instance from a PyCSP3 model, you have to execute:python3 <file> [options]with:<file>: a Python file to be executed, describing a model in PyCSP3[options]: possible options to be used when compilingAmong the options, we find:-data=<data_value>: allows us to specify the data to be used by the model. It can be:elementary: -data=5a simple list: -data=[9,0,0,3,9]a JSON file: -data=Bibd-3-4-6.jsonData can then be directly used in the PyCSP3 model by means of a predefined variabledata.-dataparser=<file>: a Python file for reading/parsing data given under any arbitrary form (e.g., by a text file).
See Example Nonogram below, for an illustration.-dataexport: exports (saves) the data in JSON format.
See Example Nonogram below, for an illustration.-variant=<variant_name>: the name of a variant, to be used with functionvariant().
See Example AllInterval below, for an illustration.-solve: attempts to solve the instance with the embedded solver 'Ace'. It requires that Java version 8 (at least) is installed.-solver=<solver_name>: attempts to solve the instance with the solver whose name is given. Currently, it can be 'ace' or 'choco'.
Important: it requires that Java version 8 (at least) is installed.
Information about how piloting these embedded solvers can be found
inthis document.-output=<file_name>: sets the filename of the generated XCSP3 instance (think about the extension .xml)By default, a file containing the XCSP3 instance is generated, unless you use the option:-display: displays the XCSP3 instance in the system standard output, instead of generating an XCSP3 fileExample 1: in console modeOur first example shows how you can build basic models in console mode.
In this example, we just post two variable and two simple binary constraints.$python3Python 3.5.2>>> from pycsp3 import *>>> x = Var(range(10))>>> y = Var(range(10))>>> satisfy(x < y,x + y > 15)>>> compile()Note that to get an XCSP3 file, we callcompile().Example 2: Send+More=MoneyThis example shows how you can define a model when no data is required from the user.
This is the classical crypto-arithmetic puzzle 'Send+More=Money'.FileSendMore.pyfrompycsp3import*# letters[i] is the digit of the ith letter involved in the equations,e,n,d,m,o,r,y=letters=VarArray(size=8,dom=range(10))satisfy(# letters are given different valuesAllDifferent(letters),# words cannot start with 0[s>0,m>0],# respecting the mathematical equation[s,e,n,d]*[1000,100,10,1]+[m,o,r,e]*[1000,100,10,1]==[m,o,n,e,y]*[10000,1000,100,10,1])To generate the XCSP3 instance (file), the command is:python3 SendMore.pyTo generate and solve (with ACE) the XCSP3 instance, the command is:python3 SendMore.py -solveTo generate and solve with Choco the XCSP3 instance, the command is:python3 SendMore.py -solver=chocoExample 3: All-Interval SeriesThis example shows how you can simply specify an integer (as unique data) for a model.
For our illustration, we consider the problemAll-Interval Series.A classical model is:FileAllInterval.py(version 1)frompycsp3import*n=data# x[i] is the ith note of the seriesx=VarArray(size=n,dom=range(n))satisfy(# notes must occur once, and so form a permutationAllDifferent(x),# intervals between neighbouring notes must form a permutationAllDifferent(abs(x[i]-x[i+1])foriinrange(n-1)),# tag(symmetry-breaking)x[0]<x[n-1])Note the presence of a tagsymmetry-breakingthat will be directly integrated into the XCSP3 file generated by the following command:python3 AllInterval.py -data=5Suppose that you would prefer to declare a second array of variables for representing successive distances.
This would give:FileAllInterval.py(version 2)frompycsp3import*n=data# x[i] is the ith note of the seriesx=VarArray(size=n,dom=range(n))# y[i] is the distance between x[i] and x[i+1]y=VarArray(size=n-1,dom=range(1,n))satisfy(# notes must occur once, and so form a permutationAllDifferent(x),# intervals between neighbouring notes must form a permutationAllDifferent(y),# computing distances[y[i]==abs(x[i]-x[i+1])foriinrange(n-1)],# tag(symmetry-breaking)[x[0]<x[n-1],y[0]<y[1]])However, sometimes, it may be relevant to combine different variants of a model in the same file.
In our example, this would give:FileAllInterval.py(version 3)frompycsp3import*n=data# x[i] is the ith note of the seriesx=VarArray(size=n,dom=range(n))ifnotvariant():satisfy(# notes must occur once, and so form a permutationAllDifferent(x),# intervals between neighbouring notes must form a permutationAllDifferent(abs(x[i]-x[i+1])foriinrange(n-1)),# tag(symmetry-breaking)x[0]<x[n-1])elifvariant("aux"):# y[i] is the distance between x[i] and x[i+1]y=VarArray(size=n-1,dom=range(1,n))satisfy(# notes must occur once, and so form a permutationAllDifferent(x),# intervals between neighbouring notes must form a permutationAllDifferent(y),# computing distances[y[i]==abs(x[i]-x[i+1])foriinrange(n-1)],# tag(symmetry-breaking)[x[0]<x[n-1],y[0]<y[1]])For compiling the main model (variant), the command is:python3 AllInterval.py -data=5For compiling the second model variant, using the option-variant, the command is:python3 AllInterval.py -data=5 -variant=auxTo generate and solve (with ACE) the instance of order 10 and variant 'aux', the command is:python3 AllInterval.py -data=10 -variant=aux -solveExample 4: BIBDThis example shows how you can specify a list of integers to be used as data for a model.
For our illustration, we consider the problemBIBD.
We need five integersv, b, r, k, lfor specifying a unique instance (possibly,bandrcan be set to 0, so that these values are automatically computed
according to a template for this problem).
The model is:FileBibd.pyfrompycsp3import*v,b,r,k,l=datab=(l*v*(v-1))//(k*(k-1))ifb==0elsebr=(l*(v-1))//(k-1)ifr==0elser# x[i][j] is the value of the matrix at row i and column jx=VarArray(size=[v,b],dom={0,1})satisfy(# constraints on rows[Sum(row)==rforrowinx],# constraints on columns[Sum(col)==kforcolincolumns(x)],# scalar constraints with respect to lambda[row1*row2==lfor(row1,row2)incombinations(x,2)])To generate an XCSP3 instance (file), we can for example execute a command like:python3 Bibd.py -data=[9,0,0,3,9]With some command interpreters (shells), you may have to escape the characters '[' and ']', which gives:python3 Bibd.py -data=\[9,0,0,3,9\]Example 5: Rack ConfigurationThis example shows how you can specify a JSON file to be used as data for a model.
For our illustration, we consider the problemRack Configuration.
The data (for a specific instance) are then initially given in a JSON file, as for example:FileRack_r2.json{"nRacks":10,"models":[[150,8,150],[200,16,200]],"cardTypes":[[20,20],[40,8],[50,4],[75,2]]}In the following model, we directly unpack the components of the variabledata(because it is automatically given under the form of a named tuple) whose
fields are exactly those of the main object in the JSON file.FileRack.pyfrompycsp3import*nRacks,models,cardTypes=datamodels.append([0,0,0])# we add first a dummy model (0,0,0)powers,sizes,costs=zip(*models)cardPowers,cardDemands=zip(*cardTypes)nModels,nTypes=len(models),len(cardTypes)table={(i,powers[i],sizes[i],costs[i])foriinrange(nModels)}# m[i] is the model used for the ith rackm=VarArray(size=nRacks,dom=range(nModels))# p[i] is the power of the model used for the ith rackp=VarArray(size=nRacks,dom=powers)# s[i] is the size (number of connectors) of the model used for the ith racks=VarArray(size=nRacks,dom=sizes)# c[i] is the cost (price) of the model used for the ith rackc=VarArray(size=nRacks,dom=costs)# nc[i][j] is the number of cards of type j put in the ith racknc=VarArray(size=[nRacks,nTypes],dom=lambdai,j:range(min(max(sizes),cardDemands[j])+1))satisfy(# linking rack models with powers, sizes and costs[(m[i],p[i],s[i],c[i])intableforiinrange(nRacks)],# connector-capacity constraints[Sum(nc[i])<=s[i]foriinrange(nRacks)],# power-capacity constraints[nc[i]*cardPowers<=p[i]foriinrange(nRacks)],# demand constraints[Sum(nc[:,j])==cardDemands[j]forjinrange(nTypes)],# tag(symmetry-breaking)[Decreasing(m),imply(m[0]==m[1],nc[0][0]>=nc[1][0])])minimize(# minimizing the total cost being paid for all racksSum(c))To generate an XCSP3 instance (file), we execute the command:python3 Rack.py -data=Rack_r2.jsonOne might want to have the data in the JSON file with another structure, as for example:FileRack_r2b.json{"nRacks":10,"rackModels":[{"power":150,"nConnectors":8,"price":150},{"power":200,"nConnectors":16,"price":200}],"cardTypes":[{"power":20,"demand":20},{"power":40,"demand":8},{"power":50,"demand":4},{"power":75,"demand":2}]}We only need to modify one line from the previous model:FileRack2.pymodels.append(models[0].__class__(0,0,0))# we add first a dummy model (0,0,0) ; we get the class of the used named tuples to build a new oneTo generate an XCSP3 instance (file), we execute the command:python3 Rack2.py -data=Rack_r2b.jsonExample 6: NonogramThis example shows how you can use an auxiliary Python file for parsing data that are not initially given under JSON format.
For our illustration, we consider the problemNonogram.
The data (for a specific Nonogram puzzle) are initially given in a text file as follows:a line stating the numbers of rows and columns,then, for each row a line stating the number of blocks followed by the sizes of all these blocks (on the same line),then, for each column a line stating the number of blocks followed by the sizes of all these blocks (on the same line).Below, here is an example of such a text file.FileNonogram_example.txt24 24
0
1	5
2	3 3
2	1 2
2	2 1
2	1 1
2	3 3
3	1 5 1
3	1 1 1
3	2 1 1
3	1 1 2
3	3 1 3
3	1 3 1
3	1 1 1
3	2 1 2
3	1 1 1
1	5
3	1 1 1
3	1 1 1
3	1 1 1
3	5 1 1
2	1 2
3	2 2 4
2	4 9

0
0
0
1	1
1	2
1	2
2	6 1
3	3 1 3
3	1 1 4
4	2 1 1 7
5	1 1 1 1 1
3	1 12 1
5	1 1 1 1 1
4	2 1 1 7
4	1 1 4 1
4	2 1 2 2
2	8 3
2	1 1
2	1 2
1	4
1	3
1	2
1	1
0First, we need to write a piece of code in Python for building a dictionarydatathat will be then used in our model (after having been automatically
converted to a named tuple).
We have first to import everything (*) frompycsp3.problems.data.parsing.
We can then add any new arbitrary item to the dictionarydata(which is initially empty).
This is what we do below with two items whose keys are calledrowPatternsandcolPatterns.
The values associated with these two keys are defined as two-dimensional arrays (lists) of integers, defining the sizes of blocks.
The functionnext_int()can be called for reading the next integer in a text file, which will be specified on the command line (see later).FileNonogram_Parser.pyfrompycsp3.problems.data.parsingimport*nRows,nCols=next_int(),next_int()data["rowPatterns"]=[[next_int()for_inrange(next_int())]for_inrange(nRows)]data["colPatterns"]=[[next_int()for_inrange(next_int())]for_inrange(nRows)]Then, we just write the model by getting data from the variabledata.
The model is totally independent of the way data were initially given (from a text file or a JSON file, for example).
In the code below, note how an objectAutomatonis defined from a specified pattern (list of blocks).
Also, for aregularconstraint, we just write something likescope in automaton.
Finally,x[:, j]denotes the jth column ofx.FileNonogram.pyfrompycsp3import*rows,cols=data# patterns for row and columnsnRows,nCols=len(rows),len(cols)defautomaton(pattern):q=Automaton.q# for building state namestransitions=[]iflen(pattern)==0:n_states=1transitions.append((q(0),0,q(0)))else:n_states=sum(pattern)+len(pattern)num=0fori,sizeinenumerate(pattern):transitions.append((q(num),0,q(num)))transitions.extend((q(num+j),1,q(num+j+1))forjinrange(size))transitions.append((q(num+size),0,q(num+size+(1ifi<len(pattern)-1else0))))num+=size+1returnAutomaton(start=q(0),final=q(n_states-1),transitions=transitions)# x[i][j] is 1 iff the cell at row i and col j is colored in blackx=VarArray(size=[nRows,nCols],dom={0,1})satisfy([x[i]inautomaton(rows[i])foriinrange(nRows)],[x[:,j]inautomaton(cols[j])forjinrange(nCols)])To generate the XCSP3 instance (file), we just need to specify the name of the text file (option-data) and the name of the Python parser (
option-dataparser).python3 Nonogram.py -data=Nonogram_example.txt -dataparser=Nonogram_Parser.pyMaybe, you think that it would be simpler to have directly the data in JSON file.
You can generate such a file with the option-dataexport.
The command is as follows:python3 Nonogram.py -data=Nonogram_example.txt -dataparser=Nonogram_Parser.py -dataexportA fileNonogram_example.jsonis generated, whose content is:{"colPatterns":[[],[],[],[1],[2],[2],[6,1],[3,1,3],[1,1,4],[2,1,1,7],[1,1,1,1,1],[1,12,1],[1,1,1,1,1],[2,1,1,7],[1,1,4,1],[2,1,2,2],[8,3],[1,1],[1,2],[4],[3],[2],[1],[]],"rowPatterns":[[],[5],[3,3],[1,2],[2,1],[1,1],[3,3],[1,5,1],[1,1,1],[2,1,1],[1,1,2],[3,1,3],[1,3,1],[1,1,1],[2,1,2],[1,1,1],[5],[1,1,1],[1,1,1],[1,1,1],[5,1,1],[1,2],[2,2,4],[4,9]]}With this new file, you can directly generate the XCSP3 file with:python3 Nonogram.py -data=Nonogram_example.json