Imported Upstream version 0.4.0
SVN-Git Migration
8 years ago
0 | GNU GENERAL PUBLIC LICENSE | |
1 | Version 2, June 1991 | |
2 | ||
3 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. | |
4 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
5 | Everyone is permitted to copy and distribute verbatim copies | |
6 | of this license document, but changing it is not allowed. | |
7 | ||
8 | Preamble | |
9 | ||
10 | The licenses for most software are designed to take away your | |
11 | freedom to share and change it. By contrast, the GNU General Public | |
12 | License is intended to guarantee your freedom to share and change free | |
13 | software--to make sure the software is free for all its users. This | |
14 | General Public License applies to most of the Free Software | |
15 | Foundation's software and to any other program whose authors commit to | |
16 | using it. (Some other Free Software Foundation software is covered by | |
17 | the GNU Library General Public License instead.) You can apply it to | |
18 | your programs, too. | |
19 | ||
20 | When we speak of free software, we are referring to freedom, not | |
21 | price. Our General Public Licenses are designed to make sure that you | |
22 | have the freedom to distribute copies of free software (and charge for | |
23 | this service if you wish), that you receive source code or can get it | |
24 | if you want it, that you can change the software or use pieces of it | |
25 | in new free programs; and that you know you can do these things. | |
26 | ||
27 | To protect your rights, we need to make restrictions that forbid | |
28 | anyone to deny you these rights or to ask you to surrender the rights. | |
29 | These restrictions translate to certain responsibilities for you if you | |
30 | distribute copies of the software, or if you modify it. | |
31 | ||
32 | For example, if you distribute copies of such a program, whether | |
33 | gratis or for a fee, you must give the recipients all the rights that | |
34 | you have. You must make sure that they, too, receive or can get the | |
35 | source code. And you must show them these terms so they know their | |
36 | rights. | |
37 | ||
38 | We protect your rights with two steps: (1) copyright the software, and | |
39 | (2) offer you this license which gives you legal permission to copy, | |
40 | distribute and/or modify the software. | |
41 | ||
42 | Also, for each author's protection and ours, we want to make certain | |
43 | that everyone understands that there is no warranty for this free | |
44 | software. If the software is modified by someone else and passed on, we | |
45 | want its recipients to know that what they have is not the original, so | |
46 | that any problems introduced by others will not reflect on the original | |
47 | authors' reputations. | |
48 | ||
49 | Finally, any free program is threatened constantly by software | |
50 | patents. We wish to avoid the danger that redistributors of a free | |
51 | program will individually obtain patent licenses, in effect making the | |
52 | program proprietary. To prevent this, we have made it clear that any | |
53 | patent must be licensed for everyone's free use or not licensed at all. | |
54 | ||
55 | The precise terms and conditions for copying, distribution and | |
56 | modification follow. | |
57 | ||
58 | GNU GENERAL PUBLIC LICENSE | |
59 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | |
60 | ||
61 | 0. This License applies to any program or other work which contains | |
62 | a notice placed by the copyright holder saying it may be distributed | |
63 | under the terms of this General Public License. The "Program", below, | |
64 | refers to any such program or work, and a "work based on the Program" | |
65 | means either the Program or any derivative work under copyright law: | |
66 | that is to say, a work containing the Program or a portion of it, | |
67 | either verbatim or with modifications and/or translated into another | |
68 | language. (Hereinafter, translation is included without limitation in | |
69 | the term "modification".) Each licensee is addressed as "you". | |
70 | ||
71 | Activities other than copying, distribution and modification are not | |
72 | covered by this License; they are outside its scope. The act of | |
73 | running the Program is not restricted, and the output from the Program | |
74 | is covered only if its contents constitute a work based on the | |
75 | Program (independent of having been made by running the Program). | |
76 | Whether that is true depends on what the Program does. | |
77 | ||
78 | 1. You may copy and distribute verbatim copies of the Program's | |
79 | source code as you receive it, in any medium, provided that you | |
80 | conspicuously and appropriately publish on each copy an appropriate | |
81 | copyright notice and disclaimer of warranty; keep intact all the | |
82 | notices that refer to this License and to the absence of any warranty; | |
83 | and give any other recipients of the Program a copy of this License | |
84 | along with the Program. | |
85 | ||
86 | You may charge a fee for the physical act of transferring a copy, and | |
87 | you may at your option offer warranty protection in exchange for a fee. | |
88 | ||
89 | 2. You may modify your copy or copies of the Program or any portion | |
90 | of it, thus forming a work based on the Program, and copy and | |
91 | distribute such modifications or work under the terms of Section 1 | |
92 | above, provided that you also meet all of these conditions: | |
93 | ||
94 | a) You must cause the modified files to carry prominent notices | |
95 | stating that you changed the files and the date of any change. | |
96 | ||
97 | b) You must cause any work that you distribute or publish, that in | |
98 | whole or in part contains or is derived from the Program or any | |
99 | part thereof, to be licensed as a whole at no charge to all third | |
100 | parties under the terms of this License. | |
101 | ||
102 | c) If the modified program normally reads commands interactively | |
103 | when run, you must cause it, when started running for such | |
104 | interactive use in the most ordinary way, to print or display an | |
105 | announcement including an appropriate copyright notice and a | |
106 | notice that there is no warranty (or else, saying that you provide | |
107 | a warranty) and that users may redistribute the program under | |
108 | these conditions, and telling the user how to view a copy of this | |
109 | License. (Exception: if the Program itself is interactive but | |
110 | does not normally print such an announcement, your work based on | |
111 | the Program is not required to print an announcement.) | |
112 | ||
113 | These requirements apply to the modified work as a whole. If | |
114 | identifiable sections of that work are not derived from the Program, | |
115 | and can be reasonably considered independent and separate works in | |
116 | themselves, then this License, and its terms, do not apply to those | |
117 | sections when you distribute them as separate works. But when you | |
118 | distribute the same sections as part of a whole which is a work based | |
119 | on the Program, the distribution of the whole must be on the terms of | |
120 | this License, whose permissions for other licensees extend to the | |
121 | entire whole, and thus to each and every part regardless of who wrote it. | |
122 | ||
123 | Thus, it is not the intent of this section to claim rights or contest | |
124 | your rights to work written entirely by you; rather, the intent is to | |
125 | exercise the right to control the distribution of derivative or | |
126 | collective works based on the Program. | |
127 | ||
128 | In addition, mere aggregation of another work not based on the Program | |
129 | with the Program (or with a work based on the Program) on a volume of | |
130 | a storage or distribution medium does not bring the other work under | |
131 | the scope of this License. | |
132 | ||
133 | 3. You may copy and distribute the Program (or a work based on it, | |
134 | under Section 2) in object code or executable form under the terms of | |
135 | Sections 1 and 2 above provided that you also do one of the following: | |
136 | ||
137 | a) Accompany it with the complete corresponding machine-readable | |
138 | source code, which must be distributed under the terms of Sections | |
139 | 1 and 2 above on a medium customarily used for software interchange; or, | |
140 | ||
141 | b) Accompany it with a written offer, valid for at least three | |
142 | years, to give any third party, for a charge no more than your | |
143 | cost of physically performing source distribution, a complete | |
144 | machine-readable copy of the corresponding source code, to be | |
145 | distributed under the terms of Sections 1 and 2 above on a medium | |
146 | customarily used for software interchange; or, | |
147 | ||
148 | c) Accompany it with the information you received as to the offer | |
149 | to distribute corresponding source code. (This alternative is | |
150 | allowed only for noncommercial distribution and only if you | |
151 | received the program in object code or executable form with such | |
152 | an offer, in accord with Subsection b above.) | |
153 | ||
154 | The source code for a work means the preferred form of the work for | |
155 | making modifications to it. For an executable work, complete source | |
156 | code means all the source code for all modules it contains, plus any | |
157 | associated interface definition files, plus the scripts used to | |
158 | control compilation and installation of the executable. However, as a | |
159 | special exception, the source code distributed need not include | |
160 | anything that is normally distributed (in either source or binary | |
161 | form) with the major components (compiler, kernel, and so on) of the | |
162 | operating system on which the executable runs, unless that component | |
163 | itself accompanies the executable. | |
164 | ||
165 | If distribution of executable or object code is made by offering | |
166 | access to copy from a designated place, then offering equivalent | |
167 | access to copy the source code from the same place counts as | |
168 | distribution of the source code, even though third parties are not | |
169 | compelled to copy the source along with the object code. | |
170 | ||
171 | 4. You may not copy, modify, sublicense, or distribute the Program | |
172 | except as expressly provided under this License. Any attempt | |
173 | otherwise to copy, modify, sublicense or distribute the Program is | |
174 | void, and will automatically terminate your rights under this License. | |
175 | However, parties who have received copies, or rights, from you under | |
176 | this License will not have their licenses terminated so long as such | |
177 | parties remain in full compliance. | |
178 | ||
179 | 5. You are not required to accept this License, since you have not | |
180 | signed it. However, nothing else grants you permission to modify or | |
181 | distribute the Program or its derivative works. These actions are | |
182 | prohibited by law if you do not accept this License. Therefore, by | |
183 | modifying or distributing the Program (or any work based on the | |
184 | Program), you indicate your acceptance of this License to do so, and | |
185 | all its terms and conditions for copying, distributing or modifying | |
186 | the Program or works based on it. | |
187 | ||
188 | 6. Each time you redistribute the Program (or any work based on the | |
189 | Program), the recipient automatically receives a license from the | |
190 | original licensor to copy, distribute or modify the Program subject to | |
191 | these terms and conditions. You may not impose any further | |
192 | restrictions on the recipients' exercise of the rights granted herein. | |
193 | You are not responsible for enforcing compliance by third parties to | |
194 | this License. | |
195 | ||
196 | 7. If, as a consequence of a court judgment or allegation of patent | |
197 | infringement or for any other reason (not limited to patent issues), | |
198 | conditions are imposed on you (whether by court order, agreement or | |
199 | otherwise) that contradict the conditions of this License, they do not | |
200 | excuse you from the conditions of this License. If you cannot | |
201 | distribute so as to satisfy simultaneously your obligations under this | |
202 | License and any other pertinent obligations, then as a consequence you | |
203 | may not distribute the Program at all. For example, if a patent | |
204 | license would not permit royalty-free redistribution of the Program by | |
205 | all those who receive copies directly or indirectly through you, then | |
206 | the only way you could satisfy both it and this License would be to | |
207 | refrain entirely from distribution of the Program. | |
208 | ||
209 | If any portion of this section is held invalid or unenforceable under | |
210 | any particular circumstance, the balance of the section is intended to | |
211 | apply and the section as a whole is intended to apply in other | |
212 | circumstances. | |
213 | ||
214 | It is not the purpose of this section to induce you to infringe any | |
215 | patents or other property right claims or to contest validity of any | |
216 | such claims; this section has the sole purpose of protecting the | |
217 | integrity of the free software distribution system, which is | |
218 | implemented by public license practices. Many people have made | |
219 | generous contributions to the wide range of software distributed | |
220 | through that system in reliance on consistent application of that | |
221 | system; it is up to the author/donor to decide if he or she is willing | |
222 | to distribute software through any other system and a licensee cannot | |
223 | impose that choice. | |
224 | ||
225 | This section is intended to make thoroughly clear what is believed to | |
226 | be a consequence of the rest of this License. | |
227 | ||
228 | 8. If the distribution and/or use of the Program is restricted in | |
229 | certain countries either by patents or by copyrighted interfaces, the | |
230 | original copyright holder who places the Program under this License | |
231 | may add an explicit geographical distribution limitation excluding | |
232 | those countries, so that distribution is permitted only in or among | |
233 | countries not thus excluded. In such case, this License incorporates | |
234 | the limitation as if written in the body of this License. | |
235 | ||
236 | 9. The Free Software Foundation may publish revised and/or new versions | |
237 | of the General Public License from time to time. Such new versions will | |
238 | be similar in spirit to the present version, but may differ in detail to | |
239 | address new problems or concerns. | |
240 | ||
241 | Each version is given a distinguishing version number. If the Program | |
242 | specifies a version number of this License which applies to it and "any | |
243 | later version", you have the option of following the terms and conditions | |
244 | either of that version or of any later version published by the Free | |
245 | Software Foundation. If the Program does not specify a version number of | |
246 | this License, you may choose any version ever published by the Free Software | |
247 | Foundation. | |
248 | ||
249 | 10. If you wish to incorporate parts of the Program into other free | |
250 | programs whose distribution conditions are different, write to the author | |
251 | to ask for permission. For software which is copyrighted by the Free | |
252 | Software Foundation, write to the Free Software Foundation; we sometimes | |
253 | make exceptions for this. Our decision will be guided by the two goals | |
254 | of preserving the free status of all derivatives of our free software and | |
255 | of promoting the sharing and reuse of software generally. | |
256 | ||
257 | NO WARRANTY | |
258 | ||
259 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY | |
260 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN | |
261 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES | |
262 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED | |
263 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
264 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS | |
265 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE | |
266 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, | |
267 | REPAIR OR CORRECTION. | |
268 | ||
269 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING | |
270 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR | |
271 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, | |
272 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING | |
273 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED | |
274 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY | |
275 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER | |
276 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE | |
277 | POSSIBILITY OF SUCH DAMAGES. | |
278 | ||
279 | END OF TERMS AND CONDITIONS | |
280 | ||
281 | How to Apply These Terms to Your New Programs | |
282 | ||
283 | If you develop a new program, and you want it to be of the greatest | |
284 | possible use to the public, the best way to achieve this is to make it | |
285 | free software which everyone can redistribute and change under these terms. | |
286 | ||
287 | To do so, attach the following notices to the program. It is safest | |
288 | to attach them to the start of each source file to most effectively | |
289 | convey the exclusion of warranty; and each file should have at least | |
290 | the "copyright" line and a pointer to where the full notice is found. | |
291 | ||
292 | <one line to give the program's name and a brief idea of what it does.> | |
293 | Copyright (C) <year> <name of author> | |
294 | ||
295 | This program is free software; you can redistribute it and/or modify | |
296 | it under the terms of the GNU General Public License as published by | |
297 | the Free Software Foundation; either version 2 of the License, or | |
298 | (at your option) any later version. | |
299 | ||
300 | This program is distributed in the hope that it will be useful, | |
301 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
302 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
303 | GNU General Public License for more details. | |
304 | ||
305 | You should have received a copy of the GNU General Public License | |
306 | along with this program; if not, write to the Free Software | |
307 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
308 | ||
309 | ||
310 | Also add information on how to contact you by electronic and paper mail. | |
311 | ||
312 | If the program is interactive, make it output a short notice like this | |
313 | when it starts in an interactive mode: | |
314 | ||
315 | Gnomovision version 69, Copyright (C) year name of author | |
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | |
317 | This is free software, and you are welcome to redistribute it | |
318 | under certain conditions; type `show c' for details. | |
319 | ||
320 | The hypothetical commands `show w' and `show c' should show the appropriate | |
321 | parts of the General Public License. Of course, the commands you use may | |
322 | be called something other than `show w' and `show c'; they could even be | |
323 | mouse-clicks or menu items--whatever suits your program. | |
324 | ||
325 | You should also get your employer (if you work as a programmer) or your | |
326 | school, if any, to sign a "copyright disclaimer" for the program, if | |
327 | necessary. Here is a sample; alter the names: | |
328 | ||
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program | |
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. | |
331 | ||
332 | <signature of Ty Coon>, 1 April 1989 | |
333 | Ty Coon, President of Vice | |
334 | ||
335 | This General Public License does not permit incorporating your program into | |
336 | proprietary programs. If your program is a subroutine library, you may | |
337 | consider it more useful to permit linking proprietary applications with the | |
338 | library. If this is what you want to do, use the GNU Library General | |
339 | Public License instead of this License. |
0 | ChangeLog for logilab.constraint | |
1 | ================================ | |
2 | ||
3 | 2008-08-07 -- 0.4.0 | |
4 | * allow capture of solver output | |
5 | ||
6 | ||
7 | ||
8 | 2005-09-05 -- 0.3.0 | |
9 | * Added finite interval domains and constraints in module | |
10 | constraint.fi | |
11 | ||
12 | * constraint now depends on logilab.common to support a larger number | |
13 | of python versions | |
14 | ||
15 | * Optimisation of finite domains by using copy on write | |
16 | ||
17 | * Better heuristics for constraint queuing | |
18 | ||
19 | ||
20 | ||
21 | 2005-06-17 -- 0.2.7 | |
22 | * Added fd.InSet special constraint that tests for set inclusion | |
23 | ||
24 | * bumped copyright | |
25 | ||
26 | * pylint fixes | |
27 | ||
28 | ||
29 | ||
30 | 2004-12-22 -- 0.2.6 | |
31 | * Dropped support for python2.2, added support for python2.4 | |
32 | ||
33 | * Optimized version of Expression and BinaryExpression, as well as | |
34 | several other optimizations lead to increased performance (money2 runs | |
35 | almost 40% faster with the new release, queens about 20%) | |
36 | ||
37 | * Optimizations in the search algorithm leads to important speedups | |
38 | when only looking for a single solution (instead of performing an | |
39 | exhaustive search of all solutions) | |
40 | ||
41 | * Added a warning when psyco cannot be loaded | |
42 | ||
43 | ||
44 | ||
45 | 2004-06-13 -- 0.2.5 | |
46 | * logilab/debian packaging | |
47 | ||
48 | * New constraint AllDistinct with linear narrowing time | |
49 | ||
50 | * updated unit tests | |
51 | ||
52 | * updated send+more=money example | |
53 | ||
54 | * added RandomizingDistributor | |
55 | ||
56 | ||
57 | ||
58 | 2002-10-16 -- 0.2.3 | |
59 | * New distributors SplitDistributor and EnumeratorDistributor | |
60 | ||
61 | * fast solution in examples/menza2.py | |
62 | ||
63 | * Dichotomy distributors can now split domains in more than two parts. | |
64 | ||
65 | * added timestamps to log messages | |
66 | ||
67 | * updated queens.py to use the new EnumeratorDistributor (25% speed increase) | |
68 | ||
69 | ||
70 |
0 | Metadata-Version: 1.0 | |
1 | Name: logilab-constraint | |
2 | Version: 0.4.0 | |
3 | Summary: constraints satisfaction solver in Python | |
4 | Home-page: http://www.logilab.org/projects/constraint | |
5 | Author: Alexandre Fayolle | |
6 | Author-email: alexandre.fayolle@logilab.fr | |
7 | License: GPL | |
8 | Description: Extensible constraint satisfaction problem solver written in pure | |
9 | Python, using constraint propagation algorithms. The | |
10 | logilab.constraint module provides finite domains with arbitrary | |
11 | values, finite interval domains, and constraints which can be applied | |
12 | to variables linked to these domains. | |
13 | ||
14 | Platform: UNKNOWN |
0 | This package implements constraint propagation algorithm to solve | |
1 | constraint satisfaction problem. It requires python 2.3 or later to | |
2 | work. Having psyco (http://psyco.sf.net) installed will definitely | |
3 | speed up things, but is not a requirement. | |
4 | ||
5 | It is released under the GNU Public License. | |
6 | ||
7 | The documentation is in the doc/ directory. Examples are in the | |
8 | examples/ directory. | |
9 | ||
10 | Discussion about constraint should take place on the python-logic | |
11 | mailing list. Information on subscription and mailing list archives | |
12 | can be accessed at | |
13 | http://lists.logilab.org/mailman/listinfo/python-logic/ | |
14 | ||
15 | Your feedback is very valuable to me. Please share tour experience | |
16 | with other users of the package on the mailing list. | |
17 | ||
18 | -- | |
19 | Alexandre Fayolle | |
20 |
0 | """ | |
1 | Constraint Satisfaction Problem (CSP) Solver in Python. | |
2 | ||
3 | :copyright: | |
4 | 2000-2008 `LOGILAB S.A. <http://www.logilab.fr>`_ (Paris, FRANCE), | |
5 | all rights reserved. | |
6 | ||
7 | :contact: | |
8 | http://www.logilab.org/project/logilab-constraint -- | |
9 | mailto:python-projects@logilab.org | |
10 | ||
11 | :license: | |
12 | `General Public License version 2 | |
13 | <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>`_ | |
14 | """ | |
15 | ||
16 | from logilab.constraint.__pkginfo__ import version as __version__ | |
17 | from logilab.constraint.propagation import Repository, Solver | |
18 | from logilab.constraint.distributors import DefaultDistributor | |
19 | from logilab.constraint import fd | |
20 | from logilab.constraint import fi | |
21 | __all__ = ['Repository', 'Solver', 'DefaultDistributor', 'fd', 'fi'] |
0 | # pylint: disable-msg=W0622 | |
1 | # This program is free software; you can redistribute it and/or modify it under | |
2 | # the terms of the GNU General Public License as published by the Free Software | |
3 | # Foundation; either version 2 of the License, or (at your option) any later | |
4 | # version. | |
5 | # | |
6 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
7 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
8 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details | |
9 | # | |
10 | # You should have received a copy of the GNU General Public License along with | |
11 | # this program; if not, write to the Free Software Foundation, Inc., | |
12 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
13 | """Copyright (c) 2001-2006 LOGILAB S.A. (Paris, FRANCE). | |
14 | http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
15 | """ | |
16 | ||
17 | modname = 'constraint' | |
18 | distname = 'logilab-constraint' | |
19 | ||
20 | numversion = (0, 4, 0) | |
21 | version = '.'.join(map(str, numversion)) | |
22 | pyversions = ('2.3', '2.4') | |
23 | ||
24 | license = 'GPL' | |
25 | copyright = '''Copyright (c) 2001-2006 LOGILAB S.A. (Paris, FRANCE). | |
26 | http://www.logilab.fr/ -- mailto:contact@logilab.fr''' | |
27 | ||
28 | short_desc = "constraints satisfaction solver in Python" | |
29 | ||
30 | long_desc = """Extensible constraint satisfaction problem solver written in pure | |
31 | Python, using constraint propagation algorithms. The | |
32 | logilab.constraint module provides finite domains with arbitrary | |
33 | values, finite interval domains, and constraints which can be applied | |
34 | to variables linked to these domains. | |
35 | """ | |
36 | ||
37 | author = "Alexandre Fayolle" | |
38 | author_email = "alexandre.fayolle@logilab.fr" | |
39 | ||
40 | web = "http://www.logilab.org/projects/%s" % modname | |
41 | ftp = "ftp://ftp.logilab.org/pub/%s/" % modname | |
42 | mailinglist = "http://lists.logilab.org/mailman/listinfo/python-logic/" | |
43 | ||
44 | subpackage_of = 'logilab' | |
45 | debian_name = 'constraint' | |
46 | debian_maintainer_email = 'afayolle@debian.org' |
0 | # (c) 2000-2001 LOGILAB S.A. (Paris, FRANCE). | |
1 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
2 | # | |
3 | # This program is free software; you can redistribute it and/or modify it under | |
4 | # the terms of the GNU General Public License as published by the Free Software | |
5 | # Foundation; either version 2 of the License, or (at your option) any later | |
6 | # version. | |
7 | # | |
8 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
9 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
10 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
11 | # | |
12 | # You should have received a copy of the GNU General Public License along with | |
13 | # this program; if not, write to the Free Software Foundation, Inc., | |
14 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
15 | # USA. | |
16 | ||
17 | """ | |
18 | distributors - part of Logilab's constraint satisfaction solver. | |
19 | """ | |
20 | ||
21 | from logilab.constraint.psyco_wrapper import Psyobj | |
22 | from logilab.constraint.interfaces import DistributorInterface | |
23 | import math, random | |
24 | ||
25 | def make_new_domains(domains): | |
26 | """return a shallow copy of dict of domains passed in argument""" | |
27 | domain = {} | |
28 | for key, value in domains.items(): | |
29 | domain[key] = value.copy() | |
30 | return domain | |
31 | ||
32 | class AbstractDistributor(Psyobj): | |
33 | """Implements DistributorInterface but abstract because | |
34 | _distribute is left unimplemented.""" | |
35 | ||
36 | __implements__ = DistributorInterface | |
37 | ||
38 | def __init__(self, nb_subspaces=2): | |
39 | self.nb_subspaces = nb_subspaces | |
40 | self.verbose = 0 | |
41 | ||
42 | def findSmallestDomain(self, domains): | |
43 | """returns the variable having the smallest domain. | |
44 | (or one of such varibles if there is a tie) | |
45 | """ | |
46 | domlist = [(dom.size(), variable ) for variable, dom in domains.items() | |
47 | if dom.size() > 1] | |
48 | domlist.sort() | |
49 | return domlist[0][1] | |
50 | ||
51 | def findLargestDomain(self, domains): | |
52 | """returns the variable having the largest domain. | |
53 | (or one of such variables if there is a tie) | |
54 | """ | |
55 | domlist = [(dom.size(), variable) for variable, dom in domains.items() | |
56 | if dom.size() > 1] | |
57 | domlist.sort() | |
58 | return domlist[-1][1] | |
59 | ||
60 | def nb_subdomains(self, domains): | |
61 | """return number of sub domains to explore""" | |
62 | return self.nb_subspaces | |
63 | ||
64 | def distribute(self, domains, verbose=0): | |
65 | """do the minimal job and let concrete class distribute variables | |
66 | """ | |
67 | self.verbose = verbose | |
68 | replicas = [] | |
69 | for i in range(self.nb_subdomains(domains)): | |
70 | replicas.append(make_new_domains(domains)) | |
71 | modified_domains = self._distribute(*replicas) | |
72 | for domain in modified_domains: | |
73 | domain.resetFlags() | |
74 | return replicas | |
75 | ||
76 | def _distribute(self, *args): | |
77 | """ method to implement in concrete class | |
78 | ||
79 | take self.nb_subspaces copy of the original domains as argument | |
80 | distribute the domains and return each modified domain | |
81 | """ | |
82 | raise NotImplementedError("Use a concrete implementation of " | |
83 | "the Distributor interface") | |
84 | ||
85 | class NaiveDistributor(AbstractDistributor): | |
86 | """distributes domains by splitting the smallest domain in 2 new domains | |
87 | The first new domain has a size of one, | |
88 | and the second has all the other values""" | |
89 | ||
90 | def __init__(self): | |
91 | AbstractDistributor.__init__(self) | |
92 | ||
93 | def _distribute(self, dom1, dom2): | |
94 | """See AbstractDistributor""" | |
95 | variable = self.findSmallestDomain(dom1) | |
96 | values = dom1[variable].getValues() | |
97 | if self.verbose: | |
98 | print 'Distributing domain for variable', variable, \ | |
99 | 'at value', values[0] | |
100 | dom1[variable].removeValues(values[1:]) | |
101 | dom2[variable].removeValue(values[0]) | |
102 | return (dom1[variable], dom2[variable]) | |
103 | ||
104 | ||
105 | class RandomizingDistributor(AbstractDistributor): | |
106 | """distributes domains as the NaiveDistrutor, except that the unique | |
107 | value of the first domain is picked at random.""" | |
108 | ||
109 | def __init__(self): | |
110 | AbstractDistributor.__init__(self) | |
111 | ||
112 | def _distribute(self, dom1, dom2): | |
113 | """See AbstractDistributor""" | |
114 | variable = self.findSmallestDomain(dom1) | |
115 | values = dom1[variable].getValues() | |
116 | distval = random.choice(values) | |
117 | values.remove(distval) | |
118 | if self.verbose: | |
119 | print 'Distributing domain for variable', variable, \ | |
120 | 'at value', distval | |
121 | dom1[variable].removeValues(values) | |
122 | dom2[variable].removeValue(distval) | |
123 | return (dom1[variable], dom2[variable]) | |
124 | ||
125 | ||
126 | class SplitDistributor(AbstractDistributor): | |
127 | """distributes domains by splitting the smallest domain in | |
128 | nb_subspaces equal parts or as equal as possible. | |
129 | If nb_subspaces is 0, then the smallest domain is split in | |
130 | domains of size 1""" | |
131 | ||
132 | def __init__(self, nb_subspaces=3): | |
133 | AbstractDistributor.__init__(self, nb_subspaces) | |
134 | self.__to_split = None | |
135 | def nb_subdomains(self, domains): | |
136 | """See AbstractDistributor""" | |
137 | self.__to_split = self.findSmallestDomain(domains) | |
138 | if self.nb_subspaces: | |
139 | return min(self.nb_subspaces, domains[self.__to_split].size()) | |
140 | else: | |
141 | return domains[self.__to_split].size() | |
142 | ||
143 | def _distribute(self, *args): | |
144 | """See AbstractDistributor""" | |
145 | variable = self.__to_split | |
146 | nb_subspaces = len(args) | |
147 | values = args[0][variable].getValues() | |
148 | nb_elts = max(1, len(values)*1./nb_subspaces) | |
149 | slices = [(int(math.floor(index * nb_elts)), | |
150 | int(math.floor((index + 1) * nb_elts))) | |
151 | for index in range(nb_subspaces)] | |
152 | if self.verbose: | |
153 | print 'Distributing domain for variable', variable | |
154 | modified = [] | |
155 | for (dom, (end, start)) in zip(args, slices) : | |
156 | dom[variable].removeValues(values[:end]) | |
157 | dom[variable].removeValues(values[start:]) | |
158 | modified.append(dom[variable]) | |
159 | return modified | |
160 | ||
161 | class DichotomyDistributor(SplitDistributor): | |
162 | """distributes domains by splitting the smallest domain in | |
163 | two equal parts or as equal as possible""" | |
164 | def __init__(self): | |
165 | SplitDistributor.__init__(self, 2) | |
166 | ||
167 | ||
168 | class EnumeratorDistributor(SplitDistributor): | |
169 | """distributes domains by splitting the smallest domain | |
170 | in domains of size 1.""" | |
171 | def __init__(self): | |
172 | SplitDistributor.__init__(self, 0) | |
173 | ||
174 | DefaultDistributor = DichotomyDistributor |
0 | Alexandre Fayolle: core coding | |
1 | Herb Schilling: documentation proof reading | |
2 | Terry Reedy: documentation proof reading |
0 | <html><head><meta content="text/html; charset=ISO-8859-1" http-equiv="Content-Type"><title>Constraint Propagation in Python</title><meta name="generator" content="LOGILAB adaptation of DocBook XSL Stylesheets V1.29"><meta name="language" content="en"><meta name="organization" content="Logilab S.A."></head><body bgcolor="#FFFFFF" text="#000000"><table border="0" width="100%" cellspacing="2" cellpadding="0"><tr><td width="20%"><img src="file:/home/logilab/public/logos/logilab.gif" width="150"></td><td width="80%"><h3 class="template"><center>Constraint Propagation in Python</center></h3><hr noshade="yes" size="5" width="100%"></td></tr></table><table width="100%" cellspacing="0" cellpadding="8"><tr><td width="5%" bgcolor="#FFAA1B" valign="top" align="left"> </td><td width="95%" valign="top" align="left"><div id="constraint-propagation-manual" class="article"><div class="article"><p> </p><h1 class="title0"><center>Constraint Propagation in Python</center></h1><h2 class="subtitle0"><center><i></i></center></h2><p> </p><table border="0" width="100%"><tr><td width="50%"><p align="left"><b>Alexandre Fayolle</b></p></td><td width="50%"></td></tr></table><hr width="100%"><p> </p><h3 class="titlepage-title">Copyright © 2002 by Logilab</h3>This manual presents how to use the constraint package to solve | |
1 | constraint propagation problems in Python. It also presents the | |
2 | the extension mechanisms available in the package</div><div class="sect1"><a name="using"></a><div class="sect1"><p>Â </p><h1 class="title2"><a name="using"></a>1. Using the constraint package<br><hr width="100%"></h1></div><div class="sect2"><a name="id2709498"></a><div class="sect2"><h2 class="title3"><a name="id2709498"></a>1.1. Sample problem</h2></div><p>Let's take a real-life problem to get started. You're organising a | |
3 | an international Python Programming event, and you have to schedule | |
4 | conferences. There are 10 different conferences, you have 3 conference | |
5 | rooms available during 2 days. Each conference is 4 hours long, so you | |
6 | can organize at most 2 conferences per day in a given room.</p><p>Conferences 3, 4, 5 and 6 have to take place in room C, because | |
7 | it's the only one with Internet access.</p><p>Some of the speakers are not available on both days, so | |
8 | conferences 1, 5 and 10 have to take place on day 1, and conferences 2, | |
9 | 3 4 and 9 on day 2.</p><p>You have made a quick poll over the python mailing list, and it | |
10 | turns out that people attending some of the conferences are likely to be | |
11 | attending some other conferences too, so you want to make sure that such | |
12 | conferences are not scheduled at the same time. A careful statistical | |
13 | study has found 4 groups of potential attendees. The first group want to | |
14 | attend conferences 1, 2, 3 and 10, the second conferences 2, 6, 8 and 9 | |
15 | the third group conferences 3, 5, 6 and 7, and the last group | |
16 | conferences 1, 3, 7 and 8.</p><p>You've tried to put this on a whiteboard, but this quickly proved | |
17 | to be tedious, so you thought about using the constraint solving | |
18 | package.</p></div><div class="sect2"><a name="id2709551"></a><div class="sect2"><h2 class="title3"><a name="id2709551"></a>1.2. Variables, Domains and Constraints</h2></div><p>The first thing to find out in order to use the constraint package | |
19 | is what the variables are, what their domains are and what the | |
20 | constraints between variables are.</p><p>If we look at our problem, the variables are the conferences' room | |
21 | and time slot, and for each conference, the domain is the cross product | |
22 | of the set of available rooms with the set of available time slots. We | |
23 | could say that the conferences which require Internet access have a | |
24 | different domain, because they need to be in room C. This is perfectly | |
25 | valid. However, we will model this as a constraint.</p><p>Variables are manipulated as names, stored in character | |
26 | strings. Domains are instances of the fd.FiniteDomain class, which is | |
27 | instantiated with a list of values. Domains are manipulated through a | |
28 | dictionnary mapping a variable to its domain. Do not use the same domain | |
29 | instance for several variables, because in the current implementation, | |
30 | this is guaranteed to break. The code looks like this: | |
31 | <pre class="programlisting"> | |
32 | <font color="#008000"># import Repository class and fd module, </font> | |
33 | <font color="#C00000">from</font> logilab.constraint <font color="#C00000">import</font> * | |
34 | variables = (<font color="#004080">'c01'</font>,<font color="#004080">'c02'</font>,<font color="#004080">'c03'</font>,<font color="#004080">'c04'</font>,<font color="#004080">'c05'</font>,<font color="#004080">'c06'</font>,<font color="#004080">'c07'</font>,<font color="#004080">'c08'</font>,<font color="#004080">'c09'</font>,<font color="#004080">'c10'</font>) | |
35 | values = [(room,slot) | |
36 | <font color="#C00000">for</font> room <font color="#C00000">in</font> (<font color="#004080">'room A'</font>,<font color="#004080">'room B'</font>,<font color="#004080">'room C'</font>) | |
37 | <font color="#C00000">for</font> slot <font color="#C00000">in</font> (<font color="#004080">'day 1 AM'</font>,<font color="#004080">'day 1 PM'</font>,<font color="#004080">'day 2 AM'</font>,<font color="#004080">'day 2 PM'</font>)] | |
38 | domains = {} | |
39 | <font color="#C00000">for</font> v <font color="#C00000">in</font> variables: | |
40 | domains[v]=fd.FiniteDomain(values) | |
41 | </pre> | |
42 | </p><p>Constraints, like domains are objects. So far the only class that | |
43 | can be used is fd.Expression and fd.BinaryExpression. We use the | |
44 | fd.make_expression factory function to build an instance of the right | |
45 | class, depending on the number of variables that is passed. This | |
46 | function takes a list of affected variables and a python expression that | |
47 | evaluates to true if the constraint is satisfied. </p><p>We have several constraints on our variables. First some | |
48 | conferences need to take place in room C: | |
49 | <pre class="programlisting"> | |
50 | constraints = [] | |
51 | <font color="#C00000">for</font> conf <font color="#C00000">in</font> (<font color="#004080">'c03'</font>,<font color="#004080">'c04'</font>,<font color="#004080">'c05'</font>,<font color="#004080">'c06'</font>): | |
52 | constraints.append(fd.make_expression((conf,), | |
53 | "%s[0] == 'room C'"%conf)) | |
54 | </pre> | |
55 | </p><p>Availability of the speakers impose some more constraints: | |
56 | <pre class="programlisting"> | |
57 | <font color="#C00000">for</font> conf <font color="#C00000">in</font> (<font color="#004080">'c01'</font>,<font color="#004080">'c05'</font>,<font color="#004080">'c10'</font>): | |
58 | constraints.append(fd.make_expression((conf,), | |
59 | "%s[1].startswith(<font color="#004080">'day 1'</font>)"%conf)) | |
60 | <font color="#C00000">for</font> conf <font color="#C00000">in</font> (<font color="#004080">'c02'</font>,<font color="#004080">'c03'</font>,<font color="#004080">'c04'</font>,<font color="#004080">'c09'</font>): | |
61 | constraints.append(fd.make_expression((conf,), | |
62 | "%s[1].startswith(<font color="#004080">'day 2'</font>)"%conf)) | |
63 | </pre> | |
64 | </p><p>Then we want to say that some of the conferences should not be | |
65 | scheduled at the same time: | |
66 | <pre class="programlisting"> | |
67 | groups = ((<font color="#004080">'c01'</font>,<font color="#004080">'c02'</font>,<font color="#004080">'c03'</font>,<font color="#004080">'c10'</font>), | |
68 | (<font color="#004080">'c02'</font>,<font color="#004080">'c06'</font>,<font color="#004080">'c08'</font>,<font color="#004080">'c09'</font>), | |
69 | (<font color="#004080">'c03'</font>,<font color="#004080">'c05'</font>,<font color="#004080">'c06'</font>,<font color="#004080">'c07'</font>), | |
70 | (<font color="#004080">'c01'</font>,<font color="#004080">'c03'</font>,<font color="#004080">'c07'</font>,<font color="#004080">'c08'</font>)) | |
71 | <font color="#C00000">for</font> g <font color="#C00000">in</font> groups: | |
72 | <font color="#C00000">for</font> conf1 <font color="#C00000">in</font> g: | |
73 | <font color="#C00000">for</font> conf2 <font color="#C00000">in</font> g: | |
74 | <font color="#C00000">if</font> conf2 > conf1: | |
75 | constraints.append(fd.make_expression((conf1,conf2), | |
76 | <font color="#004080">'%s[1] != %s[1]'</font>%\ | |
77 | (conf1,conf2))) | |
78 | </pre> | |
79 | </p><p>Finally, no two conferences can be scheduled in the same room at | |
80 | the same time: | |
81 | <pre class="programlisting"> | |
82 | <font color="#C00000">for</font> conf1 <font color="#C00000">in</font> variables: | |
83 | <font color="#C00000">for</font> conf2 <font color="#C00000">in</font> variables: | |
84 | <font color="#C00000">if</font> conf2 > conf1: | |
85 | constraints.append(fd.make_expression((conf1,conf2), | |
86 | <font color="#004080">'%s != %s'</font>%(conf1,conf2))) | |
87 | </pre> | |
88 | </p></div><div class="sect2"><a name="id2706665"></a><div class="sect2"><h2 class="title3"><a name="id2706665"></a>1.3. The Repository class</h2></div><p>Repository objects are used to hold the variables, domains and | |
89 | constraints describing the problem. A Solver object can solve the | |
90 | problem described by a Repository.</p><p>Here's the final touch: | |
91 | <pre class="programlisting"> | |
92 | r = Repository(variables,domains,constraints) | |
93 | solutions = Solver().solve(r) | |
94 | <font color="#C00000">print</font> solutions | |
95 | </pre> | |
96 | </p></div><p>The code is available in the file conferences.py in the examples | |
97 | directory of the distribution. It finds 64 possible schedules in a | |
98 | couple of seconds on my machine.</p><div class="sect2"><a name="id2706710"></a><div class="sect2"><h2 class="title3"><a name="id2706710"></a>1.4. Performance considerations</h2></div><p>There is still a lot of things to be worked on with this | |
99 | package. Here are a few tips that can help you to use it in its | |
100 | current state: | |
101 | <div class="itemizedlist"><ul><li><a name="id2706724"></a><p class="in-li"><a name="id2706724"></a>Try to avoid constraints with a lot of variables. It | |
102 | is better to have several binary constraints than one big N-ary | |
103 | constraint with several 'and' conditions</p></li><li><a name="id2706733"></a><p class="in-li"><a name="id2706733"></a>If you cannot avoid constraint with lots of | |
104 | variable, put the constraints with less variables first in the list, | |
105 | because they will get evaluated before, will take less time to | |
106 | process, and will hopefully reduce the domains of the variables | |
107 | playing a role in your big constraints</p></li></ul></div> | |
108 | </p></div></div><div class="sect1"><a name="extending"></a><div class="sect1"><p>Â </p><h1 class="title2"><a name="extending"></a>2. Extending the constraint package<br><hr width="100%"></h1></div><p>WRITE ME!</p></div></div></td></tr></table></body></html> |
0 | <?xml version="1.0" encoding="ISO-8859-1"?> | |
1 | ||
2 | <!DOCTYPE article SYSTEM "/home/logilab/docbook/dtd/docbookx.dtd" > | |
3 | <article id='constraint-propagation-manual' lang='en'> | |
4 | <artheader> | |
5 | <title>Constraint Propagation in Python</title> | |
6 | <author> | |
7 | <firstname>Alexandre</firstname> | |
8 | <surname>Fayolle</surname> | |
9 | </author> | |
10 | <abstract> | |
11 | <simpara>This manual presents how to use the constraint package to solve | |
12 | constraint propagation problems in Python. It also presents the | |
13 | the extension mechanisms available in the package</simpara> | |
14 | </abstract> | |
15 | <copyright><year>2002-2005</year><holder>Logilab</holder> | |
16 | </copyright> | |
17 | <revhistory> | |
18 | <revision> | |
19 | <revnumber>$Revision: 1.6 $</revnumber> | |
20 | <date>$Date: 2005-09-05 15:56:26 $</date> | |
21 | <authorinitials>$Author: alf $</authorinitials> | |
22 | </revision> | |
23 | </revhistory> | |
24 | </artheader> | |
25 | ||
26 | ||
27 | <sect1 id='using'><title>Using the constraint package</title> | |
28 | ||
29 | <sect2><title>Sample problem</title> | |
30 | ||
31 | <para>Let's take a real-life problem to get started. You're organising a | |
32 | an international Python Programming event, and you have to schedule | |
33 | conferences. There are 10 different conferences, you have 3 conference | |
34 | rooms available during 2 days. Each conference is 4 hours long, so you | |
35 | can organize at most 2 conferences per day in a given room.</para> | |
36 | ||
37 | <para>Conferences 3, 4, 5 and 6 have to take place in room C, because | |
38 | it's the only one with Internet access.</para> | |
39 | ||
40 | <para>Some of the speakers are not available on both days, so | |
41 | conferences 1, 5 and 10 have to take place on day 1, and conferences 2, | |
42 | 3 4 and 9 on day 2.</para> | |
43 | ||
44 | <para>You have made a quick poll over the python mailing list, and it | |
45 | turns out that people attending some of the conferences are likely to be | |
46 | attending some other conferences too, so you want to make sure that such | |
47 | conferences are not scheduled at the same time. A careful statistical | |
48 | study has found 4 groups of potential attendees. The first group want to | |
49 | attend conferences 1, 2, 3 and 10, the second conferences 2, 6, 8 and 9 | |
50 | the third group conferences 3, 5, 6 and 7, and the last group | |
51 | conferences 1, 3, 7 and 8.</para> | |
52 | ||
53 | <para>You've tried to put this on a whiteboard, but this quickly proved | |
54 | to be tedious, so you thought about using the constraint solving | |
55 | package.</para> | |
56 | ||
57 | </sect2> | |
58 | ||
59 | <sect2><title>Variables, Domains and Constraints</title> | |
60 | ||
61 | <para>The first thing to find out in order to use the constraint package | |
62 | is what the variables are, what their domains are and what the | |
63 | constraints between variables are.</para> | |
64 | ||
65 | <para>If we look at our problem, the variables are the conferences' room | |
66 | and time slot, and for each conference, the domain is the cross product | |
67 | of the set of available rooms with the set of available time slots. We | |
68 | could say that the conferences which require Internet access have a | |
69 | different domain, because they need to be in room C. This is perfectly | |
70 | valid. However, we will model this as a constraint.</para> | |
71 | ||
72 | <para>Variables are manipulated as names, stored in character | |
73 | strings. Domains are instances of the fd.FiniteDomain class, which is | |
74 | instantiated with a list of values. Domains are manipulated through a | |
75 | dictionnary mapping a variable to its domain. Do not use the same domain | |
76 | instance for several variables, because in the current implementation, | |
77 | this is guaranteed to break. The code looks like this: | |
78 | <programlisting role='python' > | |
79 | <emphasis role='comment'># import Repository class and fd module, </emphasis> | |
80 | <emphasis role='keyword'>from</emphasis> logilab.constraint <emphasis role='keyword'>import</emphasis> * | |
81 | variables = (<emphasis role='string'>'c01'</emphasis>,<emphasis role='string'>'c02'</emphasis>,<emphasis role='string'>'c03'</emphasis>,<emphasis role='string'>'c04'</emphasis>,<emphasis role='string'>'c05'</emphasis>,<emphasis role='string'>'c06'</emphasis>,<emphasis role='string'>'c07'</emphasis>,<emphasis role='string'>'c08'</emphasis>,<emphasis role='string'>'c09'</emphasis>,<emphasis role='string'>'c10'</emphasis>) | |
82 | values = [(room,slot) | |
83 | <emphasis role='keyword'>for</emphasis> room <emphasis role='keyword'>in</emphasis> (<emphasis role='string'>'room A'</emphasis>,<emphasis role='string'>'room B'</emphasis>,<emphasis role='string'>'room C'</emphasis>) | |
84 | <emphasis role='keyword'>for</emphasis> slot <emphasis role='keyword'>in</emphasis> (<emphasis role='string'>'day 1 AM'</emphasis>,<emphasis role='string'>'day 1 PM'</emphasis>,<emphasis role='string'>'day 2 AM'</emphasis>,<emphasis role='string'>'day 2 PM'</emphasis>)] | |
85 | domains = {} | |
86 | <emphasis role='keyword'>for</emphasis> v <emphasis role='keyword'>in</emphasis> variables: | |
87 | domains[v]=fd.FiniteDomain(values) | |
88 | </programlisting> | |
89 | </para> | |
90 | ||
91 | <para>Constraints, like domains are objects. So far the only class that | |
92 | can be used is fd.Expression and fd.BinaryExpression. We use the | |
93 | fd.make_expression factory function to build an instance of the right | |
94 | class, depending on the number of variables that is passed. This | |
95 | function takes a list of affected variables and a python expression that | |
96 | evaluates to true if the constraint is satisfied. </para> | |
97 | ||
98 | <para>We have several constraints on our variables. First some | |
99 | conferences need to take place in room C: | |
100 | <programlisting role='python' > | |
101 | constraints = [] | |
102 | <emphasis role='keyword'>for</emphasis> conf <emphasis role='keyword'>in</emphasis> (<emphasis role='string'>'c03'</emphasis>,<emphasis role='string'>'c04'</emphasis>,<emphasis role='string'>'c05'</emphasis>,<emphasis role='string'>'c06'</emphasis>): | |
103 | constraints.append(fd.make_expression((conf,), | |
104 | "%s[0] == 'room C'"%conf)) | |
105 | </programlisting > | |
106 | </para> | |
107 | ||
108 | <para>Availability of the speakers impose some more constraints: | |
109 | <programlisting role='python' > | |
110 | <emphasis role='keyword'>for</emphasis> conf <emphasis role='keyword'>in</emphasis> (<emphasis role='string'>'c01'</emphasis>,<emphasis role='string'>'c05'</emphasis>,<emphasis role='string'>'c10'</emphasis>): | |
111 | constraints.append(fd.make_expression((conf,), | |
112 | "%s[1].startswith(<emphasis role='string'>'day 1'</emphasis>)"%conf)) | |
113 | <emphasis role='keyword'>for</emphasis> conf <emphasis role='keyword'>in</emphasis> (<emphasis role='string'>'c02'</emphasis>,<emphasis role='string'>'c03'</emphasis>,<emphasis role='string'>'c04'</emphasis>,<emphasis role='string'>'c09'</emphasis>): | |
114 | constraints.append(fd.make_expression((conf,), | |
115 | "%s[1].startswith(<emphasis role='string'>'day 2'</emphasis>)"%conf)) | |
116 | </programlisting> | |
117 | </para> | |
118 | ||
119 | <para>Then we want to say that some of the conferences should not be | |
120 | scheduled at the same time: | |
121 | <programlisting role='python'> | |
122 | groups = ((<emphasis role='string'>'c01'</emphasis>,<emphasis role='string'>'c02'</emphasis>,<emphasis role='string'>'c03'</emphasis>,<emphasis role='string'>'c10'</emphasis>), | |
123 | (<emphasis role='string'>'c02'</emphasis>,<emphasis role='string'>'c06'</emphasis>,<emphasis role='string'>'c08'</emphasis>,<emphasis role='string'>'c09'</emphasis>), | |
124 | (<emphasis role='string'>'c03'</emphasis>,<emphasis role='string'>'c05'</emphasis>,<emphasis role='string'>'c06'</emphasis>,<emphasis role='string'>'c07'</emphasis>), | |
125 | (<emphasis role='string'>'c01'</emphasis>,<emphasis role='string'>'c03'</emphasis>,<emphasis role='string'>'c07'</emphasis>,<emphasis role='string'>'c08'</emphasis>)) | |
126 | <emphasis role='keyword'>for</emphasis> g <emphasis role='keyword'>in</emphasis> groups: | |
127 | <emphasis role='keyword'>for</emphasis> conf1 <emphasis role='keyword'>in</emphasis> g: | |
128 | <emphasis role='keyword'>for</emphasis> conf2 <emphasis role='keyword'>in</emphasis> g: | |
129 | <emphasis role='keyword'>if</emphasis> conf2 > conf1: | |
130 | constraints.append(fd.make_expression((conf1,conf2), | |
131 | <emphasis role='string'>'%s[1] != %s[1]'</emphasis>%\ | |
132 | (conf1,conf2))) | |
133 | </programlisting> | |
134 | </para> | |
135 | ||
136 | <para>Finally, no two conferences can be scheduled in the same room at | |
137 | the same time: | |
138 | <programlisting role='python'> | |
139 | <emphasis role='keyword'>for</emphasis> conf1 <emphasis role='keyword'>in</emphasis> variables: | |
140 | <emphasis role='keyword'>for</emphasis> conf2 <emphasis role='keyword'>in</emphasis> variables: | |
141 | <emphasis role='keyword'>if</emphasis> conf2 > conf1: | |
142 | constraints.append(fd.make_expression((conf1,conf2), | |
143 | <emphasis role='string'>'%s != %s'</emphasis>%(conf1,conf2))) | |
144 | </programlisting> | |
145 | </para> | |
146 | ||
147 | </sect2> | |
148 | ||
149 | <sect2><title>The Repository class</title> | |
150 | <para>Repository objects are used to hold the variables, domains and | |
151 | constraints describing the problem. A Solver object can solve the | |
152 | problem described by a Repository.</para> | |
153 | ||
154 | <para>Here's the final touch: | |
155 | <programlisting role='python'> | |
156 | r = Repository(variables,domains,constraints) | |
157 | solutions = Solver().solve(r) | |
158 | <emphasis role='keyword'>print</emphasis> solutions | |
159 | </programlisting> | |
160 | </para> | |
161 | </sect2> | |
162 | ||
163 | <para>The code is available in the file conferences.py in the examples | |
164 | directory of the distribution. It finds 64 possible schedules in a | |
165 | couple of seconds on my machine.</para> | |
166 | ||
167 | <sect2><title>Performance considerations</title> | |
168 | <para>There is still a lot of things to be worked on with this | |
169 | package. Here are a few tips that can help you to use it in its | |
170 | current state: | |
171 | <itemizedlist> | |
172 | <listitem><para>Try to avoid constraints with a lot of variables. It | |
173 | is better to have several binary constraints than one big N-ary | |
174 | constraint with several 'and' conditions</para></listitem> | |
175 | ||
176 | <listitem><para>If you cannot avoid constraint with lots of | |
177 | variable, put the constraints with less variables first in the list, | |
178 | because they will get evaluated before, will take less time to | |
179 | process, and will hopefully reduce the domains of the variables | |
180 | playing a role in your big constraints</para> | |
181 | </listitem> | |
182 | </itemizedlist> | |
183 | </para> | |
184 | </sect2> | |
185 | ||
186 | </sect1> | |
187 | <sect1 id='extending'><title>Extending the constraint package</title> | |
188 | ||
189 | <para>WRITE ME!</para> | |
190 | <!-- | |
191 | <sect2><title>The interfaces</title> | |
192 | <para></para> | |
193 | </sect2> | |
194 | ||
195 | <sect2><title>The abstract implementations</title> | |
196 | <para></para> | |
197 | </sect2> | |
198 | ||
199 | <sect2><title>AbstractConstraint Narrowing algorithm</title> | |
200 | <para></para> | |
201 | </sect2> | |
202 | --> | |
203 | ||
204 | </sect1> | |
205 | </article> |
0 | """Chess constraints and domains""" | |
1 | ||
2 | ||
3 | from logilab.constraint import fd | |
4 | from logilab.constraint.propagation import AbstractConstraint, ConsistencyFailure | |
5 | ||
6 | class ChessDomain(fd.FiniteDomain): | |
7 | def __init__(self, size): | |
8 | values = [(i,j) for i in range(size) for j in range(size)] | |
9 | fd.FiniteDomain.__init__(self, values) | |
10 | ||
11 | def __repr__(self): | |
12 | vals = self.getValues() | |
13 | vals.sort() | |
14 | return '<ChessDomain %s>' % str(vals) | |
15 | ||
16 | class QueensConstraint(AbstractConstraint): | |
17 | def __init__(self, variables): | |
18 | AbstractConstraint.__init__(self, variables) | |
19 | ||
20 | def __repr__(self): | |
21 | return '<QueensConstraint %s>' % str(self._variables) | |
22 | ||
23 | def narrow(self, domains): | |
24 | maybe_entailed = 1 | |
25 | var1 = self._variables[0] | |
26 | dom1 = domains[var1] | |
27 | values1 = dom1.getValues() | |
28 | var2 = self._variables[1] | |
29 | dom2 = domains[var2] | |
30 | values2 = dom2.getValues() | |
31 | ||
32 | keep1 = {} | |
33 | keep2 = {} | |
34 | maybe_entailed = 1 | |
35 | for val1 in values1: | |
36 | val1_0 = val1[0] | |
37 | val1_1 = val1[1] | |
38 | for val2 in values2: | |
39 | if val1 in keep1 and val2 in keep2 and maybe_entailed == 0: | |
40 | continue | |
41 | val2_0 = val2[0] | |
42 | val2_1 = val2[1] | |
43 | if val1_0 < val2_0 and \ | |
44 | val1_1 != val2_1 and \ | |
45 | abs(val1_0-val2_0) != abs(val1_1-val2_1): | |
46 | keep1[val1] = 1 | |
47 | keep2[val2] = 1 | |
48 | else: | |
49 | maybe_entailed = 0 | |
50 | ||
51 | try: | |
52 | dom1.removeValues([val for val in values1 if val not in keep1]) | |
53 | dom2.removeValues([val for val in values2 if val not in keep2]) | |
54 | except ConsistencyFailure: | |
55 | raise ConsistencyFailure('Inconsistency while applying %s' % \ | |
56 | repr(self)) | |
57 | except Exception: | |
58 | print self, kwargs | |
59 | raise | |
60 | return maybe_entailed | |
61 |
0 | # (c) 2000-2001 LOGILAB S.A. (Paris, FRANCE). | |
1 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
2 | # | |
3 | # This program is free software; you can redistribute it and/or modify it under | |
4 | # the terms of the GNU General Public License as published by the Free Software | |
5 | # Foundation; either version 2 of the License, or (at your option) any later | |
6 | # version. | |
7 | # | |
8 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
9 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
10 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
11 | # | |
12 | # You should have received a copy of the GNU General Public License along with | |
13 | # this program; if not, write to the Free Software Foundation, Inc., | |
14 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
15 | # USA. | |
16 | ||
17 | ||
18 | # import Repository, ListDomain and MathematicConstraint | |
19 | from logilab.constraint import * | |
20 | variables = ('c01','c02','c03','c04','c05','c06','c07','c08','c09','c10') | |
21 | values = [(room,slot) | |
22 | for room in ('room A','room B','room C') | |
23 | for slot in ('day 1 AM','day 1 PM','day 2 AM','day 2 PM')] | |
24 | domains = {} | |
25 | for v in variables: | |
26 | domains[v]=fd.FiniteDomain(values) | |
27 | constraints = [] | |
28 | ||
29 | # Internet access is in room C only | |
30 | for conf in ('c03','c04','c05','c06'): | |
31 | constraints.append(fd.make_expression((conf,), | |
32 | "%s[0] == 'room C'"%conf)) | |
33 | ||
34 | # Speakers only available on day 1 | |
35 | for conf in ('c01','c05','c10'): | |
36 | constraints.append(fd.make_expression((conf,), | |
37 | "%s[1].startswith('day 1')"%conf)) | |
38 | # Speakers only available on day 2 | |
39 | for conf in ('c02','c03','c04','c09'): | |
40 | constraints.append(fd.make_expression((conf,), | |
41 | "%s[1].startswith('day 2')"%conf)) | |
42 | ||
43 | # try to satisfy people willing to attend several conferences | |
44 | groups = (('c01','c02','c03','c10'), | |
45 | ('c02','c06','c08','c09'), | |
46 | ('c03','c05','c06','c07'), | |
47 | ('c01','c03','c07','c08')) | |
48 | for g in groups: | |
49 | for conf1 in g: | |
50 | for conf2 in g: | |
51 | if conf2 > conf1: | |
52 | print '%s[1] != %s[1]'%(conf1,conf2) | |
53 | constraints.append(fd.make_expression((conf1,conf2), | |
54 | '%s[1] != %s[1]'%\ | |
55 | (conf1,conf2))) | |
56 | ||
57 | ||
58 | constraints.append(fd.AllDistinct(variables)) | |
59 | ||
60 | r = Repository(variables,domains,constraints) | |
61 | solutions = Solver().solve(r) | |
62 | print solutions | |
63 | print len(solutions) |
0 | #!/usr/bin/python | |
1 | # -*- coding: ISO-8859-1 -*- | |
2 | # (c) 2000-2001 LOGILAB S.A. (Paris, FRANCE). | |
3 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
4 | # | |
5 | # This program is free software; you can redistribute it and/or modify it under | |
6 | # the terms of the GNU General Public License as published by the Free Software | |
7 | # Foundation; either version 2 of the License, or (at your option) any later | |
8 | # version. | |
9 | # | |
10 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
12 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
13 | # | |
14 | # You should have received a copy of the GNU General Public License along with | |
15 | # this program; if not, write to the Free Software Foundation, Inc., | |
16 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
17 | # USA. | |
18 | ||
19 | ||
20 | """ | |
21 | Example problem with intervals | |
22 | """ | |
23 | ||
24 | from __future__ import generators | |
25 | ||
26 | from logilab.constraint import * | |
27 | from logilab.constraint.distributors import * | |
28 | ||
29 | def intervals(size=5,verbose=0): | |
30 | variables = [] | |
31 | domains = {} | |
32 | constraints = [] | |
33 | for i in range(size): | |
34 | name = 'v%02d'%i | |
35 | variables.append(name) | |
36 | domains[name] = fi.FiniteIntervalDomain(0, size*5, 5) | |
37 | ||
38 | for i, q1 in enumerate(variables): | |
39 | if i+1 == len(variables): | |
40 | continue | |
41 | q2 = variables[i+1] | |
42 | c = fi.StartsAfterEnd(q1, q2) | |
43 | constraints.append(c) | |
44 | ||
45 | ## print domains | |
46 | ## print constraints | |
47 | r = Repository(variables,domains,constraints) | |
48 | for sol in Solver(fi.FiniteIntervalDistributor()).solve_all(r,verbose): | |
49 | yield sol | |
50 | ||
51 | def main(args = None): | |
52 | import sys,getopt | |
53 | if args is None: | |
54 | args = sys.argv[1:] | |
55 | opts,args = getopt.getopt(args,'dvf') | |
56 | display = 0 | |
57 | verbose = 0 | |
58 | first = 0 | |
59 | if args: | |
60 | size = int(args[0]) | |
61 | else: | |
62 | size = 8 | |
63 | for o,v in opts: | |
64 | if o == '-d': | |
65 | display = 1 | |
66 | elif o == '-v': | |
67 | verbose += 1 | |
68 | elif o == "-f": | |
69 | first = 1 | |
70 | count = 0 | |
71 | for sol in intervals(size,verbose): | |
72 | count += 1 | |
73 | if display: | |
74 | print sol | |
75 | print '*'*80 | |
76 | if first: | |
77 | break | |
78 | if not display: | |
79 | print 'Use -d option to display solutions' | |
80 | print count,'solutions found.' | |
81 | ||
82 | if __name__ == '__main__': | |
83 | ## import hotshot | |
84 | ## p = hotshot.Profile('/tmp/queens.prof') | |
85 | ## p.runcall(main) | |
86 | ## p.close() | |
87 | main() |
0 | #!/usr/bin/env python2.3 | |
1 | # -*- coding: ISO-8859-1 -*- | |
2 | """Knight tour problem: | |
3 | Place n*n values on a checker so | |
4 | that consecutive values are a knight's | |
5 | move away from each other""" | |
6 | ||
7 | ||
8 | # This program is free software; you can redistribute it and/or modify it under | |
9 | # the terms of the GNU General Public License as published by the Free Software | |
10 | # Foundation; either version 2 of the License, or (at your option) any later | |
11 | # version. | |
12 | # | |
13 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
14 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
15 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
16 | # | |
17 | # You should have received a copy of the GNU General Public License along with | |
18 | # this program; if not, write to the Free Software Foundation, Inc., | |
19 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
20 | # USA. | |
21 | ||
22 | ||
23 | from logilab.constraint import * | |
24 | from logilab.constraint.distributors import * | |
25 | ||
26 | def knight_tour(size=6,verbose=0): | |
27 | variables = [] | |
28 | domains = {} | |
29 | constraints = [] | |
30 | black_checker=[] | |
31 | # the black tiles | |
32 | white_checker=[] | |
33 | #the white tiles: one less if n is odd | |
34 | for row in range(size): | |
35 | for column in range(size): | |
36 | if (row+column)%2==0: | |
37 | black_checker.append((row,column)) | |
38 | else: | |
39 | white_checker.append((row, column)) | |
40 | ||
41 | # One variable for each step in the tour | |
42 | for i in range(size*size): | |
43 | name = 'x%02d'%i | |
44 | variables.append(name) | |
45 | # The knight's move jumps from black to white | |
46 | # and vice versa, so we make all the even steps black | |
47 | # and all the odd ones white. | |
48 | if i%2==0: | |
49 | domains[name] = fd.FiniteDomain(black_checker) | |
50 | else: | |
51 | domains[name] = fd.FiniteDomain(white_checker) | |
52 | if i > 0: | |
53 | j = i -1 | |
54 | k1 = 'x%02d'%j | |
55 | k2 = 'x%02d'%i | |
56 | # the knight's move constraint | |
57 | c = fd.make_expression((k1,k2), | |
58 | 'abs(%(v1)s[0]-%(v2)s[0]) + abs(%(v1)s[1]-%(v2)s[1]) == 3'%\ | |
59 | {'v1':k1,'v2':k2}) | |
60 | constraints.append(c) | |
61 | c = fd.make_expression((k1,k2), | |
62 | 'abs(abs(%(v1)s[0]-%(v2)s[0]) - abs(%(v1)s[1]-%(v2)s[1])) == 1'%\ | |
63 | {'v1':k1,'v2':k2}) | |
64 | constraints.append(c) | |
65 | constraints.append(fd.AllDistinct(variables)) | |
66 | half = size/2 | |
67 | r = Repository(variables,domains,constraints) | |
68 | sol = Solver(EnumeratorDistributor()).solve_one(r,verbose) | |
69 | return sol | |
70 | ||
71 | def draw_solution(sol, size): | |
72 | # change the keys into elements, elements into keys | |
73 | # to display the results. | |
74 | # I'm sure there's a better way to do this, but I'm | |
75 | # new to python | |
76 | board = '' | |
77 | board += '_'*(size*3+1)+'\n' | |
78 | squares = {} | |
79 | for t in sol.items(): | |
80 | squares[(t[1][0]*size)+t[1][1]]=t[0] | |
81 | for i in range(size): | |
82 | for j in range(size): | |
83 | # find the variable whose value is (i,j) | |
84 | square = squares[i*size+j] | |
85 | # numbering should start from 1 ,not 0 | |
86 | intsquare = int(square[1:4]) + 1 | |
87 | board+='|%02s'%intsquare | |
88 | board+='|\n' | |
89 | board += '¯'*(size*3+1)+'\n' | |
90 | print board | |
91 | ||
92 | ||
93 | if __name__ == '__main__': | |
94 | import sys,getopt | |
95 | opts,args = getopt.getopt(sys.argv[1:],'dv') | |
96 | display = 0 | |
97 | verbose = 0 | |
98 | if args: | |
99 | size = int(args[0]) | |
100 | else: | |
101 | size = 6 | |
102 | for o,v in opts: | |
103 | if o == '-d': | |
104 | display = 1 | |
105 | elif o == '-v': | |
106 | verbose += 2 | |
107 | count = 0 | |
108 | sol = knight_tour(size,verbose) | |
109 | if display: | |
110 | print 'Solution found:' | |
111 | draw_solution(sol,size) |
0 | """ | |
1 | Solve a puzzle that got discussed on c.l.p. on october 2002 | |
2 | ||
3 | ABC*DE=FGHIJ with all letters different and in domain [0,9] | |
4 | """ | |
5 | from __future__ import generators | |
6 | ||
7 | from logilab.constraint import * | |
8 | from logilab.constraint.psyco_wrapper import psyobj | |
9 | from logilab.constraint.propagation import BasicConstraint, ConsistencyFailure | |
10 | ||
11 | class DistinctDigits(BasicConstraint,psyobj): | |
12 | def __init__(self,variable): | |
13 | BasicConstraint.__init__(self,variable,None,None) | |
14 | ||
15 | def narrow(self,domains): | |
16 | domain = domains[self._variable] | |
17 | for v in domain.getValues(): | |
18 | s = str(v) | |
19 | for d in ('0','1','2','3','4','5','6','7','8','9'): | |
20 | if s.count(d) not in (0,1): | |
21 | domain.removeValue(v) | |
22 | break | |
23 | return 1 | |
24 | ||
25 | def __repr__(self): | |
26 | ||
27 | return '<DistinctDigits on variable %s>'%self._variable | |
28 | ||
29 | ||
30 | def menza() : | |
31 | """ | |
32 | """ | |
33 | ||
34 | VARS='ab' | |
35 | variables = list(VARS) | |
36 | domains = {} | |
37 | constraints = [] | |
38 | ||
39 | domains['a'] = fd.FiniteDomain(range(0,1000)) | |
40 | domains['b'] = fd.FiniteDomain(range(0,100)) | |
41 | ||
42 | me = fd.make_expression | |
43 | ||
44 | for v in variables: | |
45 | constraints.append(DistinctDigits(v)) | |
46 | dist = ['10000 < a*b '] | |
47 | for digit in range(10): | |
48 | dist.append('("%%.3d%%.2d%%.5d" %% (a,b,a*b)).count("%d")==1'%digit) | |
49 | constraints.append(me(('a','b'),' and '.join(dist))) | |
50 | r = Repository(variables, domains, constraints) | |
51 | return r | |
52 | ||
53 | if __name__ == '__main__' : | |
54 | import sys,getopt | |
55 | opts,args = getopt.getopt(sys.argv[1:],'dv') | |
56 | verbose = 0 | |
57 | display = 0 | |
58 | create_problem=menza | |
59 | for o,v in opts: | |
60 | if o == '-v': | |
61 | verbose += 1 | |
62 | elif o == '-d': | |
63 | display = 1 | |
64 | ||
65 | ||
66 | r = create_problem() | |
67 | print 'problem created. let us solve it.' | |
68 | s = [] | |
69 | for sol in Solver().solve_all(r,verbose): | |
70 | s.append(sol) | |
71 | if display: | |
72 | sol['c'] = sol['a']*sol['b'] | |
73 | print "%(a)s x %(b)s = %(c)s" % sol | |
74 | if not display: | |
75 | print 'Found %d solutions'%len(s) |
0 | """ | |
1 | Solve a puzzle that got discussed on c.l.p. on october 2002 | |
2 | ||
3 | ABC*DE=FGHIJ with all letters different and in domain [0,9] | |
4 | """ | |
5 | from __future__ import generators | |
6 | ||
7 | from logilab.constraint import * | |
8 | from logilab.constraint.distributors import DichotomyDistributor | |
9 | from logilab.constraint.propagation import BasicConstraint, ConsistencyFailure | |
10 | ||
11 | class DistinctDigits(BasicConstraint): | |
12 | def __init__(self,variable): | |
13 | BasicConstraint.__init__(self,variable,None,None) | |
14 | ||
15 | def narrow(self,domains): | |
16 | domain = domains[self._variable] | |
17 | try: | |
18 | for v in domain.getValues(): | |
19 | s = str(v) | |
20 | for d in ('0','1','2','3','4','5','6','7','8','9'): | |
21 | if s.count(d) not in (0,1): | |
22 | domain.removeValue(v) | |
23 | break | |
24 | except ConsistencyFailure, e: | |
25 | raise ConsistencyFailure('inconsistency while applying %s'%repr(self)) | |
26 | return 1 | |
27 | ||
28 | def __repr__(self): | |
29 | return '<DistinctDigits>' | |
30 | ||
31 | ||
32 | ||
33 | def mensa() : | |
34 | """ | |
35 | ABC*DE=FGHIJ with all letters different and in domain [0,9] | |
36 | """ | |
37 | ||
38 | VARS='xy' | |
39 | variables = list(VARS) | |
40 | domains = {} | |
41 | constraints = [] | |
42 | ||
43 | # x = ABC and y = DE, x*y = FGHIJ | |
44 | domains['x'] = fd.FiniteDomain(range(0,1000)) | |
45 | domains['y'] = fd.FiniteDomain(range(0,100)) | |
46 | ||
47 | # x and y *must* have distinct digits themselves | |
48 | # (for example this will remove 232 from x's domain) | |
49 | for v in variables: | |
50 | constraints.append(DistinctDigits(v)) | |
51 | ||
52 | # x,y and x*y must have distinct digits | |
53 | dist = [] | |
54 | for digit in range(10): | |
55 | dist.append('("%%.3d%%.2d%%.5d" %% (x,y,x*y)).count("%d")==1'%digit) | |
56 | c = ' and '.join(dist) | |
57 | constraints.append(fd.make_expression(('x','y'),c)) | |
58 | r = Repository(variables, domains, constraints) | |
59 | return r | |
60 | ||
61 | if __name__ == '__main__' : | |
62 | import sys,getopt | |
63 | opts,args = getopt.getopt(sys.argv[1:],'dv') | |
64 | verbose = 0 | |
65 | display = 0 | |
66 | for o,v in opts: | |
67 | if o == '-v': | |
68 | verbose += 1 | |
69 | elif o == '-d': | |
70 | display = 1 | |
71 | ||
72 | r = mensa() | |
73 | print 'problem created. let us solve it.' | |
74 | s = [] | |
75 | for sol in Solver().solve_all(r,verbose): | |
76 | s.append(sol) | |
77 | if display: | |
78 | sol['c'] = sol['x']*sol['y'] | |
79 | print "%(x)s x %(y)s = %(c)s" % sol | |
80 | if not display: | |
81 | print 'Found %d solutions'%len(s) |
0 | try: | |
1 | import psyco | |
2 | psyco.full() | |
3 | except ImportError: | |
4 | print 'Psyco not available' | |
5 | ||
6 | def menza(): | |
7 | sol = [] | |
8 | all_digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] | |
9 | for a in range(1000) : | |
10 | for b in range(100) : | |
11 | c = a*b | |
12 | if c > 9999: | |
13 | digits = list("%.3d%.2d%.5d" % (a, b, c)) | |
14 | digits.sort() | |
15 | if digits == all_digits : | |
16 | sol.append({'a': a, 'b': b}) | |
17 | print "%.3d x %.2d = %.5d" % (a, b, c) | |
18 | return sol | |
19 | ||
20 | if __name__ == '__main__': | |
21 | sol = menza() | |
22 | print len(sol) |
0 | #!/usr/bin/env python2.2 | |
1 | ||
2 | # (c) 2000-2001 LOGILAB S.A. (Paris, FRANCE). | |
3 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
4 | # | |
5 | # This program is free software; you can redistribute it and/or modify it under | |
6 | # the terms of the GNU General Public License as published by the Free Software | |
7 | # Foundation; either version 2 of the License, or (at your option) any later | |
8 | # version. | |
9 | # | |
10 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
12 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
13 | # | |
14 | # You should have received a copy of the GNU General Public License along with | |
15 | # this program; if not, write to the Free Software Foundation, Inc., | |
16 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
17 | # USA. | |
18 | ||
19 | ||
20 | from logilab.constraint import * | |
21 | ||
22 | def money(verbose=0): | |
23 | """ SEND | |
24 | +MORE | |
25 | ------- | |
26 | MONEY | |
27 | """ | |
28 | digits = range(10) | |
29 | variables = list('sendmory') | |
30 | domains = {} | |
31 | constraints = [] | |
32 | for v in variables: | |
33 | domains[v] = fd.FiniteDomain(digits) | |
34 | ||
35 | constraints.append(fd.AllDistinct(variables)) | |
36 | constraints.append(fd.NotEquals('m', 0)) | |
37 | constraints.append(fd.NotEquals('s', 0)) | |
38 | constraints.append(fd.make_expression(('s', 'm', 'o'), | |
39 | '(s+m) in (10*m+o,10*m+o-1)')) | |
40 | constraints.append(fd.make_expression(('d', 'e', 'y'), | |
41 | '(d+e)%10 == y')) | |
42 | constraints.append(fd.make_expression(('n', 'r', 'e'), | |
43 | '(n+r)%10 in (e,e-1)')) | |
44 | constraints.append(fd.make_expression(('o', 'e', 'n'), | |
45 | '(o+e)%10 in (n,n-1)')) | |
46 | constraints.append(fd.make_expression(variables, | |
47 | 'm*10000+(o-m-s)*1000+(n-o-e)*100+(e-r-n)*10+y-e-d == 0')) | |
48 | r = Repository(variables, domains, constraints) | |
49 | s = Solver().solve_one(r, verbose) | |
50 | return s | |
51 | ||
52 | def display_solution(d): | |
53 | for s in d: | |
54 | print ' SEND\t \t',' %(s)d%(e)d%(n)d%(d)d'%s | |
55 | print '+ MORE\t \t','+ %(m)d%(o)d%(r)d%(e)d'%s | |
56 | print '------\t-->\t','------' | |
57 | print ' MONEY\t \t',' %(m)d%(o)d%(n)d%(e)d%(y)d'%s | |
58 | ||
59 | ||
60 | if __name__ == '__main__': | |
61 | import sys, getopt | |
62 | opts, args = getopt.getopt(sys.argv[1:], 'dv') | |
63 | verbose = 0 | |
64 | display = 0 | |
65 | for o,v in opts: | |
66 | if o == '-v': | |
67 | verbose += 1 | |
68 | if o == '-d': | |
69 | display = 1 | |
70 | sol = money(verbose) | |
71 | if display: | |
72 | display_solution([sol]) | |
73 | else: | |
74 | print sol |
0 | #!/usr/bin/env python2.2 | |
1 | ||
2 | # (c) 2000-2001 LOGILAB S.A. (Paris, FRANCE). | |
3 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
4 | # | |
5 | # This program is free software; you can redistribute it and/or modify it under | |
6 | # the terms of the GNU General Public License as published by the Free Software | |
7 | # Foundation; either version 2 of the License, or (at your option) any later | |
8 | # version. | |
9 | # | |
10 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
12 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
13 | # | |
14 | # You should have received a copy of the GNU General Public License along with | |
15 | # this program; if not, write to the Free Software Foundation, Inc., | |
16 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
17 | # USA. | |
18 | ||
19 | ||
20 | from logilab.constraint import * | |
21 | from logilab.constraint.distributors import SplitDistributor | |
22 | ||
23 | def money(verbose=0): | |
24 | """ SEND | |
25 | +MORE | |
26 | ------- | |
27 | MONEY | |
28 | """ | |
29 | digits = range(10) | |
30 | variables = list('sendmory') | |
31 | domains = {} | |
32 | constraints = [] | |
33 | for v in variables: | |
34 | domains[v] = fd.FiniteDomain(digits) | |
35 | ||
36 | constraints.append(fd.AllDistinct(variables)) | |
37 | constraints.append(fd.NotEquals('m', 0)) | |
38 | constraints.append(fd.NotEquals('s', 0)) | |
39 | for v1 in variables: | |
40 | for v2 in variables: | |
41 | if v1 < v2: | |
42 | constraints.append(fd.make_expression((v1, v2), | |
43 | '%s != %s'%(v1, v2))) | |
44 | constraints.append(fd.make_expression(variables, | |
45 | 'm*10000+(o-m-s)*1000+(n-o-e)*100+(e-r-n)*10+y-e-d == 0')) | |
46 | r = Repository(variables, domains, constraints) | |
47 | s = Solver(distributor=SplitDistributor(10)).solve_one(r, verbose) | |
48 | return s | |
49 | ||
50 | def display_solution(d): | |
51 | print ' SEND\t \t',' %(s)d%(e)d%(n)d%(d)d'%d | |
52 | print '+ MORE\t \t','+ %(m)d%(o)d%(r)d%(e)d'%d | |
53 | print '------\t-->\t','------' | |
54 | print ' MONEY\t \t',' %(m)d%(o)d%(n)d%(e)d%(y)d'%d | |
55 | ||
56 | if __name__ == '__main__': | |
57 | print 'WARNING!' | |
58 | print 'This example takes looooooooooooooots of CPU to complete.' | |
59 | print 'try money.py for a faster version.' | |
60 | import sys, getopt | |
61 | opts, args = getopt.getopt(sys.argv[1:], 'dv') | |
62 | verbose = 0 | |
63 | display = 0 | |
64 | for o,v in opts: | |
65 | if o == '-v': | |
66 | verbose += 1 | |
67 | if o == '-d': | |
68 | display = 1 | |
69 | sol = money(verbose) | |
70 | if display: | |
71 | display_solution(sol) | |
72 | else: | |
73 | print sol |
0 | #!/usr/bin/env python | |
1 | # -*- coding: ISO-8859-1 -*- | |
2 | """N queens problem | |
3 | The problem is solved with a EnumeratorDistributor splitting | |
4 | the smallest domain in at most N subdomains.""" | |
5 | ||
6 | # (c) 2000-2001 LOGILAB S.A. (Paris, FRANCE). | |
7 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
8 | # | |
9 | # This program is free software; you can redistribute it and/or modify it under | |
10 | # the terms of the GNU General Public License as published by the Free Software | |
11 | # Foundation; either version 2 of the License, or (at your option) any later | |
12 | # version. | |
13 | # | |
14 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
15 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
16 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
17 | # | |
18 | # You should have received a copy of the GNU General Public License along with | |
19 | # this program; if not, write to the Free Software Foundation, Inc., | |
20 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
21 | # USA. | |
22 | ||
23 | from __future__ import generators | |
24 | ||
25 | from logilab.constraint import * | |
26 | from logilab.constraint.distributors import * | |
27 | ||
28 | ||
29 | distributors = { | |
30 | "enum" : EnumeratorDistributor, | |
31 | "dicho" : DichotomyDistributor, | |
32 | "naive" : NaiveDistributor, | |
33 | } | |
34 | ||
35 | def queens(size=8,verbose=0,distrib="enum"): | |
36 | variables = [] | |
37 | domains = {} | |
38 | constraints = [] | |
39 | for i in range(size): | |
40 | name = 'Q%02d'%i | |
41 | variables.append(name) | |
42 | domains[name] = fd.FiniteDomain([(i,j) for j in range(size)]) | |
43 | ||
44 | for q1 in variables: | |
45 | for q2 in variables: | |
46 | if q1 < q2: | |
47 | c = fd.make_expression((q1,q2), | |
48 | '%(q1)s[0] < %(q2)s[0] and ' | |
49 | '%(q1)s[1] != %(q2)s[1] and ' | |
50 | 'abs(%(q1)s[0]-%(q2)s[0]) != ' | |
51 | 'abs(%(q1)s[1]-%(q2)s[1])'%\ | |
52 | {'q1':q1,'q2':q2}) | |
53 | constraints.append(c) | |
54 | r = Repository(variables,domains,constraints) | |
55 | Distrib = distributors[distrib] | |
56 | for sol in Solver(Distrib()).solve_all(r,verbose): | |
57 | yield sol | |
58 | ||
59 | ||
60 | ||
61 | def draw_solution(s): | |
62 | size = len(s) | |
63 | queens = {} | |
64 | board = '' | |
65 | for q,p in s.items(): | |
66 | queens[p]=q | |
67 | board += '_'*(size*2+1)+'\n' | |
68 | for i in range(size): | |
69 | # | |
70 | for j in range(size): | |
71 | q = queens.get((i,j)) | |
72 | if q is None: | |
73 | board+='|'+'·-'[(i+j)%2] | |
74 | else: | |
75 | board+='|Q' | |
76 | board+='|\n' | |
77 | board += '¯'*(size*2+1) | |
78 | print board | |
79 | ||
80 | ||
81 | def main(args = None): | |
82 | import sys,getopt | |
83 | if args is None: | |
84 | args = sys.argv[1:] | |
85 | opts,args = getopt.getopt(args,'dvfD:') | |
86 | display = 0 | |
87 | verbose = 0 | |
88 | first = 0 | |
89 | distrib = "enum" | |
90 | if args: | |
91 | size = int(args[0]) | |
92 | else: | |
93 | size = 8 | |
94 | for o,v in opts: | |
95 | if o == '-d': | |
96 | display = 1 | |
97 | elif o == '-v': | |
98 | verbose += 1 | |
99 | elif o == "-f": | |
100 | first = 1 | |
101 | elif o == "-D": | |
102 | if v in distributors: | |
103 | distrib = v | |
104 | else: | |
105 | raise RuntimeError("Distributor must be one of %s" % ", ".join(distributors.keys()) ) | |
106 | ||
107 | count = 0 | |
108 | for sol in queens(size,verbose,distrib): | |
109 | count += 1 | |
110 | if display: | |
111 | print 'solution #%d'%count | |
112 | draw_solution(sol) | |
113 | print '*'*80 | |
114 | if first: | |
115 | break | |
116 | if not display: | |
117 | print 'Use -d option to display solutions' | |
118 | print count,'solutions found.' | |
119 | ||
120 | if __name__ == '__main__': | |
121 | ## import hotshot | |
122 | ## p = hotshot.Profile('/tmp/queens.prof') | |
123 | ## p.runcall(main) | |
124 | ## p.close() | |
125 | main() |
0 | #!/usr/bin/env python | |
1 | # -*- coding: ISO-8859-1 -*- | |
2 | """N queens problem | |
3 | The problem is solved with a EnumeratorDistributor splitting | |
4 | the smallest domain in at most N subdomains.""" | |
5 | ||
6 | # (c) 2000-2001 LOGILAB S.A. (Paris, FRANCE). | |
7 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
8 | # | |
9 | # This program is free software; you can redistribute it and/or modify it under | |
10 | # the terms of the GNU General Public License as published by the Free Software | |
11 | # Foundation; either version 2 of the License, or (at your option) any later | |
12 | # version. | |
13 | # | |
14 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
15 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
16 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
17 | # | |
18 | # You should have received a copy of the GNU General Public License along with | |
19 | # this program; if not, write to the Free Software Foundation, Inc., | |
20 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
21 | # USA. | |
22 | ||
23 | ||
24 | from __future__ import generators | |
25 | ||
26 | from logilab.constraint import * | |
27 | from logilab.constraint.distributors import * | |
28 | ||
29 | distributors = { | |
30 | "enum" : EnumeratorDistributor, | |
31 | "dicho" : DichotomyDistributor, | |
32 | "naive" : NaiveDistributor, | |
33 | "random" : RandomizingDistributor, | |
34 | } | |
35 | ||
36 | def queens(size=8,verbose=0,distrib="enum"): | |
37 | variables = [] | |
38 | domains = {} | |
39 | constraints = [] | |
40 | for i in range(size): | |
41 | name = 'Q%02d'%i | |
42 | variables.append(name) | |
43 | domains[name] = fd.FiniteDomain( range(size) ) | |
44 | ||
45 | for r1 in range(size): | |
46 | q1 = 'Q%02d' % r1 | |
47 | for r2 in range(r1+1, size): | |
48 | q2 = 'Q%02d' % r2 | |
49 | D = {'q1':q1,'q2':q2, 'diag':(r2-r1) } | |
50 | c = fd.make_expression((q1,q2), | |
51 | '%(q1)s != %(q2)s and ' | |
52 | '%(diag)s != abs(%(q1)s-%(q2)s)'% D ) | |
53 | constraints.append(c) | |
54 | ||
55 | r = Repository(variables,domains,constraints) | |
56 | Distrib = distributors[distrib] | |
57 | for sol in Solver(Distrib()).solve_all(r,verbose): | |
58 | yield sol | |
59 | ||
60 | ||
61 | ||
62 | def draw_solution(s): | |
63 | size = len(s) | |
64 | board = '' | |
65 | queens = s.items() | |
66 | queens.sort() | |
67 | board += '_'*(size*2+1)+'\n' | |
68 | for i in range(size): | |
69 | qj = queens[i][1] | |
70 | for j in range(size): | |
71 | if j != qj: | |
72 | board+='|'+'·-'[(i+j)%2] | |
73 | else: | |
74 | board+='|Q' | |
75 | board+='|\n' | |
76 | board += '¯'*(size*2+1) | |
77 | print board | |
78 | ||
79 | ||
80 | def main(args = None): | |
81 | import sys,getopt | |
82 | if args is None: | |
83 | args = sys.argv[1:] | |
84 | opts,args = getopt.getopt(args,'dvfD:') | |
85 | display = 0 | |
86 | verbose = 0 | |
87 | first = 0 | |
88 | distrib = "enum" | |
89 | if args: | |
90 | size = int(args[0]) | |
91 | else: | |
92 | size = 8 | |
93 | for o,v in opts: | |
94 | if o == '-d': | |
95 | display = 1 | |
96 | elif o == '-v': | |
97 | verbose += 1 | |
98 | elif o == "-f": | |
99 | first = 1 | |
100 | elif o == "-D": | |
101 | if v in distributors: | |
102 | distrib = v | |
103 | else: | |
104 | raise RuntimeError("Distributor must be one of %s" % ", ".join(distributors.keys()) ) | |
105 | count = 0 | |
106 | for sol in queens(size,verbose,distrib): | |
107 | count += 1 | |
108 | if display: | |
109 | print 'solution #%d'%count | |
110 | draw_solution(sol) | |
111 | print '*'*80 | |
112 | if first: | |
113 | break | |
114 | if not display: | |
115 | print 'Use -d option to display solutions' | |
116 | print count,'solutions found.' | |
117 | ||
118 | print "Domains copy:", fd.FiniteDomain._copy_count | |
119 | print "Domains writes:", fd.FiniteDomain._write_count | |
120 | ||
121 | if __name__ == '__main__': | |
122 | ## import hotshot | |
123 | ## p = hotshot.Profile('/tmp/queens.prof') | |
124 | ## p.runcall(main) | |
125 | ## p.close() | |
126 | main() |
0 | #!/usr/bin/env python | |
1 | # -*- coding: ISO-8859-1 -*- | |
2 | """N queens problem | |
3 | The problem is solved with a EnumeratorDistributor splitting | |
4 | the smallest domain in at most N subdomains.""" | |
5 | ||
6 | # (c) 2000-2001 LOGILAB S.A. (Paris, FRANCE). | |
7 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
8 | # | |
9 | # This program is free software; you can redistribute it and/or modify it under | |
10 | # the terms of the GNU General Public License as published by the Free Software | |
11 | # Foundation; either version 2 of the License, or (at your option) any later | |
12 | # version. | |
13 | # | |
14 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
15 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
16 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
17 | # | |
18 | # You should have received a copy of the GNU General Public License along with | |
19 | # this program; if not, write to the Free Software Foundation, Inc., | |
20 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
21 | # USA. | |
22 | ||
23 | ||
24 | from __future__ import generators | |
25 | ||
26 | from logilab.constraint import * | |
27 | from logilab.constraint.distributors import * | |
28 | from chess import ChessDomain, QueensConstraint | |
29 | ||
30 | def queens(size=8,verbose=0): | |
31 | variables = [] | |
32 | domains = {} | |
33 | constraints = [] | |
34 | for i in range(size): | |
35 | name = 'Q%02d'%i | |
36 | variables.append(name) | |
37 | domains[name] = ChessDomain(size) | |
38 | ||
39 | for q1 in variables: | |
40 | for q2 in variables: | |
41 | if q1 < q2: | |
42 | c = QueensConstraint((q1,q2)) | |
43 | constraints.append(c) | |
44 | r = Repository(variables,domains,constraints) | |
45 | for sol in Solver(EnumeratorDistributor()).solve_all(r,verbose): | |
46 | yield sol | |
47 | ||
48 | ||
49 | ||
50 | def draw_solution(s): | |
51 | size = len(s) | |
52 | queens = {} | |
53 | board = '' | |
54 | for q,p in s.items(): | |
55 | queens[p]=q | |
56 | board += '_'*(size*2+1)+'\n' | |
57 | for i in range(size): | |
58 | # | |
59 | for j in range(size): | |
60 | q = queens.get((i,j)) | |
61 | if q is None: | |
62 | board+='|'+'·-'[(i+j)%2] | |
63 | else: | |
64 | board+='|Q' | |
65 | board+='|\n' | |
66 | board += '¯'*(size*2+1) | |
67 | print board | |
68 | ||
69 | ||
70 | def main(args = None): | |
71 | import sys,getopt | |
72 | if args is None: | |
73 | args = sys.argv[1:] | |
74 | opts,args = getopt.getopt(args,'dvf') | |
75 | display = 0 | |
76 | verbose = 0 | |
77 | first = 0 | |
78 | if args: | |
79 | size = int(args[0]) | |
80 | else: | |
81 | size = 8 | |
82 | for o,v in opts: | |
83 | if o == '-d': | |
84 | display = 1 | |
85 | elif o == '-v': | |
86 | verbose += 1 | |
87 | elif o == "-f": | |
88 | first = 1 | |
89 | count = 0 | |
90 | for sol in queens(size,verbose): | |
91 | count += 1 | |
92 | if display: | |
93 | print 'solution #%d'%count | |
94 | draw_solution(sol) | |
95 | print '*'*80 | |
96 | if first: | |
97 | break | |
98 | if not display: | |
99 | print 'Use -d option to display solutions' | |
100 | print count,'solutions found.' | |
101 | ||
102 | if __name__ == '__main__': | |
103 | ## import hotshot | |
104 | ## p = hotshot.Profile('/tmp/queens.prof') | |
105 | ## p.runcall(main) | |
106 | ## p.close() | |
107 | main() |
0 | #!/usr/bin/env python | |
1 | # -*- coding: ISO-8859-1 -*- | |
2 | """N queens problem | |
3 | The problem is solved with a EnumeratorDistributor splitting | |
4 | the smallest domain in at most N subdomains.""" | |
5 | ||
6 | # (c) 2000-2001 LOGILAB S.A. (Paris, FRANCE). | |
7 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
8 | # | |
9 | # This program is free software; you can redistribute it and/or modify it under | |
10 | # the terms of the GNU General Public License as published by the Free Software | |
11 | # Foundation; either version 2 of the License, or (at your option) any later | |
12 | # version. | |
13 | # | |
14 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
15 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
16 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
17 | # | |
18 | # You should have received a copy of the GNU General Public License along with | |
19 | # this program; if not, write to the Free Software Foundation, Inc., | |
20 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
21 | # USA. | |
22 | ||
23 | ||
24 | from __future__ import generators | |
25 | ||
26 | from logilab.constraint import * | |
27 | from logilab.constraint.distributors import * | |
28 | ||
29 | def rooks(size=8,verbose=0): | |
30 | variables = [] | |
31 | domains = {} | |
32 | constraints = [] | |
33 | for i in range(size): | |
34 | name = 'R%02d'%i | |
35 | variables.append(name) | |
36 | domains[name] = fd.FiniteDomain( range(size) ) | |
37 | ||
38 | for r1 in range(size): | |
39 | for r2 in range(size): | |
40 | q1 = 'R%02d' % r1 | |
41 | q2 = 'R%02d' % r2 | |
42 | if r1 < r2: | |
43 | D = {'q1':q1,'q2':q2, 'r1' : r1, 'r2' : r2 } | |
44 | c = fd.make_expression((q1,q2), '%(q1)s != %(q2)s' % D ) | |
45 | constraints.append(c) | |
46 | ||
47 | r = Repository(variables,domains,constraints) | |
48 | for sol in Solver(EnumeratorDistributor()).solve_all(r,verbose): | |
49 | yield sol | |
50 | ||
51 | ||
52 | ||
53 | def draw_solution(s): | |
54 | size = len(s) | |
55 | board = '' | |
56 | queens = s.items() | |
57 | queens.sort() | |
58 | board += '_'*(size*2+1)+'\n' | |
59 | for i in range(size): | |
60 | qj = queens[i][1] | |
61 | for j in range(size): | |
62 | if j != qj: | |
63 | board+='|'+'·-'[(i+j)%2] | |
64 | else: | |
65 | board+='|R' | |
66 | board+='|\n' | |
67 | board += '¯'*(size*2+1) | |
68 | print board | |
69 | ||
70 | ||
71 | def main(args = None): | |
72 | import sys,getopt | |
73 | if args is None: | |
74 | args = sys.argv[1:] | |
75 | opts,args = getopt.getopt(args,'dvf') | |
76 | display = 0 | |
77 | verbose = 0 | |
78 | first = 0 | |
79 | if args: | |
80 | size = int(args[0]) | |
81 | else: | |
82 | size = 8 | |
83 | for o,v in opts: | |
84 | if o == '-d': | |
85 | display = 1 | |
86 | elif o == '-v': | |
87 | verbose += 1 | |
88 | elif o == "-f": | |
89 | first = 1 | |
90 | count = 0 | |
91 | for sol in rooks(size,verbose): | |
92 | count += 1 | |
93 | if display: | |
94 | print 'solution #%d'%count | |
95 | draw_solution(sol) | |
96 | print '*'*80 | |
97 | if first: | |
98 | break | |
99 | if not display: | |
100 | print 'Use -d option to display solutions' | |
101 | print count,'solutions found.' | |
102 | ||
103 | if __name__ == '__main__': | |
104 | ## import hotshot | |
105 | ## p = hotshot.Profile('/tmp/queens.prof') | |
106 | ## p.runcall(main) | |
107 | ## p.close() | |
108 | main() |
0 | # (c) 2006 LOGILAB S.A. (Paris, FRANCE). | |
1 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
2 | # | |
3 | # This program is free software; you can redistribute it and/or modify it under | |
4 | # the terms of the GNU General Public License as published by the Free Software | |
5 | # Foundation; either version 2 of the License, or (at your option) any later | |
6 | # version. | |
7 | # | |
8 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
9 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
10 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
11 | # | |
12 | # You should have received a copy of the GNU General Public License along with | |
13 | # this program; if not, write to the Free Software Foundation, Inc., | |
14 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
15 | # USA. | |
16 | ||
17 | ||
18 | from logilab.constraint import * | |
19 | ||
20 | ||
21 | # games found on http://www.websudoku.com/ | |
22 | # I'm not sure how they rate the difficulty of their problems. | |
23 | easy = [" 5 27 ", | |
24 | " 4 79 ", | |
25 | "1 6 8 35", | |
26 | "4 32 16 9", | |
27 | " 5 8 ", | |
28 | "8 76 95 3", | |
29 | "73 2 1 6", | |
30 | " 41 2 ", | |
31 | " 12 8 "] | |
32 | ||
33 | medium = [" 9 85 ", | |
34 | " 3 1 5 ", | |
35 | " 283 1 ", | |
36 | " 2 4 7", | |
37 | " 3 5 ", | |
38 | "4 7 5 ", | |
39 | " 4 362 ", | |
40 | " 2 7 1 ", | |
41 | " 26 3 "] | |
42 | ||
43 | hard = [" 19 73 4", | |
44 | " 98 72 ", | |
45 | " 5", | |
46 | " 4 6", | |
47 | "93 72", | |
48 | "4 6 ", | |
49 | "8 ", | |
50 | " 92 36 ", | |
51 | "5 42 31 "] | |
52 | ||
53 | evil = [" 1 9 ", | |
54 | " 5 4 ", | |
55 | "2 1 365", | |
56 | " 327 ", | |
57 | "9 8", | |
58 | " 821 ", | |
59 | "473 5 1", | |
60 | " 6 4 ", | |
61 | " 3 8 "] | |
62 | ||
63 | def sudoku(problem, verbose=0): | |
64 | assert len(problem) == 9 # more sizes later | |
65 | variables = ['v%02d_%02d'%(i,j) for i in range(9) for j in range(9)] | |
66 | domains = {} | |
67 | constraints = [] | |
68 | values = list('123456789') | |
69 | for v in variables: | |
70 | domains[v] = fd.FiniteDomain(values) | |
71 | ||
72 | # line and column constraints | |
73 | for i in range(9): | |
74 | constraints.append(fd.AllDistinct(['v%02d_%02d'%(i,j) for j in range(9)])) | |
75 | constraints.append(fd.AllDistinct(['v%02d_%02d'%(j,i) for j in range(9)])) | |
76 | ||
77 | # square constraints: | |
78 | for i in (0, 3, 6): | |
79 | for j in (0, 3, 6): | |
80 | constraints.append(fd.AllDistinct(['v%02d_%02d'%(i+ii,j+jj) | |
81 | for ii in (0, 1, 2) | |
82 | for jj in (0, 1, 2)])) | |
83 | ||
84 | # fixed values: | |
85 | for i, line in enumerate(problem): | |
86 | for j, value in enumerate(line): | |
87 | if value != ' ': | |
88 | constraints.append(fd.Equals('v%02d_%02d'%(i,j), value)) | |
89 | ||
90 | r = Repository(variables, domains, constraints) | |
91 | s = Solver().solve_one(r, verbose) | |
92 | return s | |
93 | ||
94 | def display_solution(d): | |
95 | for i in range(9): | |
96 | for j in range(9): | |
97 | print d['v%02d_%02d'%(i,j)], | |
98 | ||
99 | ||
100 | if __name__ == '__main__': | |
101 | import sys, getopt | |
102 | opts, args = getopt.getopt(sys.argv[1:], 'dv') | |
103 | verbose = 0 | |
104 | display = 0 | |
105 | for o,v in opts: | |
106 | if o == '-v': | |
107 | verbose += 1 | |
108 | if o == '-d': | |
109 | display = 1 | |
110 | sol = sudoku(evil, verbose) | |
111 | if display: | |
112 | display_solution(sol) | |
113 | else: | |
114 | print sol |
0 | # pylint: disable-msg=W0142 | |
1 | # (c) 2002 LOGILAB S.A. (Paris, FRANCE). | |
2 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
3 | # | |
4 | # This program is free software; you can redistribute it and/or modify it under | |
5 | # the terms of the GNU General Public License as published by the Free Software | |
6 | # Foundation; either version 2 of the License, or (at your option) any later | |
7 | # version. | |
8 | # | |
9 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
11 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
12 | # | |
13 | # You should have received a copy of the GNU General Public License along with | |
14 | # this program; if not, write to the Free Software Foundation, Inc., | |
15 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
16 | # USA. | |
17 | """Tools to work with finite domain variables and constraints | |
18 | ||
19 | This module provides the following usable classes: | |
20 | * FiniteDomain: a class for storing FiniteDomains | |
21 | * Expression: a constraint represented as an expression | |
22 | * BinaryExpression: a binary constraint represented as an expression | |
23 | * various BasicConstraint classes | |
24 | ||
25 | The Expression and BinaryExpression classes can be constructed using the | |
26 | make_expression factory function. """ | |
27 | ||
28 | from __future__ import generators | |
29 | ||
30 | import operator | |
31 | ||
32 | from logilab.constraint.propagation import AbstractDomain, BasicConstraint, \ | |
33 | ConsistencyFailure, \ | |
34 | AbstractConstraint | |
35 | ||
36 | ||
37 | class FiniteDomain(AbstractDomain): | |
38 | """ | |
39 | Variable Domain with a finite set of possible values | |
40 | """ | |
41 | ||
42 | _copy_count = 0 | |
43 | _write_count = 0 | |
44 | ||
45 | def __init__(self, values): | |
46 | """values is a list of values in the domain | |
47 | This class uses a dictionnary to make sure that there are | |
48 | no duplicate values""" | |
49 | AbstractDomain.__init__(self) | |
50 | if isinstance(values, FiniteDomain): | |
51 | # do a copy on write | |
52 | self._cow = True | |
53 | values._cow = True | |
54 | FiniteDomain._copy_count += 1 | |
55 | self._values = values._values | |
56 | else: | |
57 | assert len(values) > 0 | |
58 | self.setValues(values) | |
59 | ||
60 | ##self.getValues = self._values.keys | |
61 | ||
62 | def setValues(self, values): | |
63 | self._cow = False | |
64 | FiniteDomain._write_count += 1 | |
65 | self._values = {} | |
66 | for val in values: | |
67 | self._values[val] = 0 | |
68 | ||
69 | def removeValue(self, value): | |
70 | """Remove value of domain and check for consistency""" | |
71 | ## print "removing", value, "from", self._values.keys() | |
72 | if self._cow: | |
73 | self.setValues(self._values) | |
74 | del self._values[value] | |
75 | self._valueRemoved() | |
76 | ||
77 | def removeValues(self, values): | |
78 | """Remove values of domain and check for consistency""" | |
79 | if self._cow: | |
80 | self.setValues(self._values) | |
81 | if values: | |
82 | ## print "removing", values, "from", self._values.keys() | |
83 | for val in values : | |
84 | del self._values[val] | |
85 | self._valueRemoved() | |
86 | __delitem__ = removeValue | |
87 | ||
88 | def size(self): | |
89 | """computes the size of a finite domain""" | |
90 | return len(self._values) | |
91 | __len__ = size | |
92 | ||
93 | def getValues(self): | |
94 | """return all the values in the domain""" | |
95 | return self._values.keys() | |
96 | ||
97 | def __iter__(self): | |
98 | return iter(self._values) | |
99 | ||
100 | def copy(self): | |
101 | """clone the domain""" | |
102 | return FiniteDomain(self) | |
103 | ||
104 | def __repr__(self): | |
105 | return '<FiniteDomain %s>' % str(self.getValues()) | |
106 | ||
107 | ## | |
108 | ## Constraints | |
109 | ## | |
110 | class AllDistinct(AbstractConstraint): | |
111 | """Contraint: all values must be distinct""" | |
112 | ||
113 | def __init__(self, variables): | |
114 | assert len(variables)>1 | |
115 | AbstractConstraint.__init__(self, variables) | |
116 | # worst case complexity | |
117 | self.__cost = len(variables) * (len(variables) - 1) / 2 | |
118 | ||
119 | def __repr__(self): | |
120 | return '<AllDistinct %s>' % str(self._variables) | |
121 | ||
122 | def estimateCost(self, domains): | |
123 | """return cost""" | |
124 | return self.__cost | |
125 | ||
126 | def narrow(self, domains): | |
127 | """narrowing algorithm for the constraint""" | |
128 | variables = [(domains[variable].size(), variable, domains[variable]) | |
129 | for variable in self._variables] | |
130 | ||
131 | variables.sort() | |
132 | # if a domain has a size of 1, | |
133 | # then the value must be removed from the other domains | |
134 | for size, var, dom in variables: | |
135 | if dom.size() == 1: | |
136 | for _siz, _var, _dom in variables: | |
137 | if _var != var: | |
138 | try: | |
139 | _dom.removeValue(dom.getValues()[0]) | |
140 | except KeyError: | |
141 | # we ignore errors caused by the removal of | |
142 | # non existing values | |
143 | pass | |
144 | ||
145 | # if there are less values than variables, the constraint fails | |
146 | values = {} | |
147 | for size, var, dom in variables: | |
148 | for val in dom: | |
149 | values[val] = 0 | |
150 | if len(values) < len(variables): | |
151 | raise ConsistencyFailure() | |
152 | ||
153 | # the constraint is entailed if all domains have a size of 1 | |
154 | for variable in variables: | |
155 | if variable[2].size() != 1: | |
156 | return 0 | |
157 | return 1 | |
158 | ||
159 | ||
160 | ||
161 | ||
162 | class Expression(AbstractConstraint): | |
163 | """A constraint represented as a python expression.""" | |
164 | _FILTER_CACHE = {} | |
165 | ||
166 | def __init__(self, variables, formula, type='fd.Expression'): | |
167 | """variables is a list of variables which appear in the formula | |
168 | formula is a python expression that will be evaluated as a boolean""" | |
169 | AbstractConstraint.__init__(self, variables) | |
170 | self.formula = formula | |
171 | self.type = type | |
172 | try: | |
173 | self.filterFunc = Expression._FILTER_CACHE[formula] | |
174 | except KeyError: | |
175 | self.filterFunc = eval('lambda %s: %s' % \ | |
176 | (','.join(variables), formula), {}, {}) | |
177 | Expression._FILTER_CACHE[formula] = self.filterFunc | |
178 | ||
179 | def _init_result_cache(self): | |
180 | """key = (variable,value), value = [has_success,has_failure]""" | |
181 | result_cache = {} | |
182 | for var_name in self._variables: | |
183 | result_cache[var_name] = {} | |
184 | return result_cache | |
185 | ||
186 | ||
187 | def _assign_values(self, domains): | |
188 | variables = [] | |
189 | kwargs = {} | |
190 | for variable in self._variables: | |
191 | domain = domains[variable] | |
192 | values = domain.getValues() | |
193 | variables.append((domain.size(), [variable, values, 0, len(values)])) | |
194 | kwargs[variable] = values[0] | |
195 | # sort variables to instanciate those with fewer possible values first | |
196 | variables.sort() | |
197 | ||
198 | go_on = 1 | |
199 | while go_on: | |
200 | yield kwargs | |
201 | # try to instanciate the next variable | |
202 | for size, curr in variables: | |
203 | if (curr[2] + 1) < curr[-1]: | |
204 | curr[2] += 1 | |
205 | kwargs[curr[0]] = curr[1][curr[2]] | |
206 | break | |
207 | else: | |
208 | curr[2] = 0 | |
209 | kwargs[curr[0]] = curr[1][0] | |
210 | else: | |
211 | # it's over | |
212 | go_on = 0 | |
213 | ||
214 | ||
215 | def narrow(self, domains): | |
216 | """generic narrowing algorithm for n-ary expressions""" | |
217 | maybe_entailed = 1 | |
218 | ffunc = self.filterFunc | |
219 | result_cache = self._init_result_cache() | |
220 | for kwargs in self._assign_values(domains): | |
221 | if maybe_entailed: | |
222 | for var, val in kwargs.iteritems(): | |
223 | if val not in result_cache[var]: | |
224 | break | |
225 | else: | |
226 | continue | |
227 | if ffunc(**kwargs): | |
228 | for var, val in kwargs.items(): | |
229 | result_cache[var][val] = 1 | |
230 | else: | |
231 | maybe_entailed = 0 | |
232 | ||
233 | try: | |
234 | for var, keep in result_cache.iteritems(): | |
235 | domain = domains[var] | |
236 | domain.removeValues([val for val in domain if val not in keep]) | |
237 | ||
238 | except ConsistencyFailure: | |
239 | raise ConsistencyFailure('Inconsistency while applying %s' % \ | |
240 | repr(self)) | |
241 | except KeyError: | |
242 | # There are no more value in result_cache | |
243 | pass | |
244 | ||
245 | return maybe_entailed | |
246 | ||
247 | def __repr__(self): | |
248 | return '<%s "%s">' % (self.type, self.formula) | |
249 | ||
250 | class BinaryExpression(Expression): | |
251 | """A binary constraint represented as a python expression | |
252 | ||
253 | This implementation uses a narrowing algorithm optimized for | |
254 | binary constraints.""" | |
255 | ||
256 | def __init__(self, variables, formula, type = 'fd.BinaryExpression'): | |
257 | assert len(variables) == 2 | |
258 | Expression.__init__(self, variables, formula, type) | |
259 | ||
260 | def narrow(self, domains): | |
261 | """specialized narrowing algorithm for binary expressions | |
262 | Runs much faster than the generic version""" | |
263 | maybe_entailed = 1 | |
264 | var1 = self._variables[0] | |
265 | dom1 = domains[var1] | |
266 | values1 = dom1.getValues() | |
267 | var2 = self._variables[1] | |
268 | dom2 = domains[var2] | |
269 | values2 = dom2.getValues() | |
270 | ffunc = self.filterFunc | |
271 | if dom2.size() < dom1.size(): | |
272 | var1, var2 = var2, var1 | |
273 | dom1, dom2 = dom2, dom1 | |
274 | values1, values2 = values2, values1 | |
275 | ||
276 | kwargs = {} | |
277 | keep1 = {} | |
278 | keep2 = {} | |
279 | maybe_entailed = 1 | |
280 | try: | |
281 | # iterate for all values | |
282 | for val1 in values1: | |
283 | kwargs[var1] = val1 | |
284 | for val2 in values2: | |
285 | kwargs[var2] = val2 | |
286 | if val1 in keep1 and val2 in keep2 and maybe_entailed == 0: | |
287 | continue | |
288 | if ffunc(**kwargs): | |
289 | keep1[val1] = 1 | |
290 | keep2[val2] = 1 | |
291 | else: | |
292 | maybe_entailed = 0 | |
293 | ||
294 | dom1.removeValues([val for val in values1 if val not in keep1]) | |
295 | dom2.removeValues([val for val in values2 if val not in keep2]) | |
296 | ||
297 | except ConsistencyFailure: | |
298 | raise ConsistencyFailure('Inconsistency while applying %s' % \ | |
299 | repr(self)) | |
300 | except Exception: | |
301 | print self, kwargs | |
302 | raise | |
303 | return maybe_entailed | |
304 | ||
305 | ||
306 | def make_expression(variables, formula, constraint_type=None): | |
307 | """create a new constraint of type Expression or BinaryExpression | |
308 | The chosen class depends on the number of variables in the constraint""" | |
309 | # encode unicode | |
310 | vars = [] | |
311 | for var in variables: | |
312 | if type(var) == type(u''): | |
313 | vars.append(var.encode()) | |
314 | else: | |
315 | vars.append(var) | |
316 | if len(vars) == 2: | |
317 | if constraint_type is not None: | |
318 | return BinaryExpression(vars, formula, constraint_type) | |
319 | else: | |
320 | return BinaryExpression(vars, formula) | |
321 | ||
322 | else: | |
323 | if constraint_type is not None: | |
324 | return Expression(vars, formula, constraint_type) | |
325 | else: | |
326 | return Expression(vars, formula) | |
327 | ||
328 | ||
329 | class Equals(BasicConstraint): | |
330 | """A basic constraint variable == constant value""" | |
331 | def __init__(self, variable, reference): | |
332 | BasicConstraint.__init__(self, variable, reference, operator.eq) | |
333 | ||
334 | class NotEquals(BasicConstraint): | |
335 | """A basic constraint variable != constant value""" | |
336 | def __init__(self, variable, reference): | |
337 | BasicConstraint.__init__(self, variable, reference, operator.ne) | |
338 | ||
339 | class LesserThan(BasicConstraint): | |
340 | """A basic constraint variable < constant value""" | |
341 | def __init__(self, variable, reference): | |
342 | BasicConstraint.__init__(self, variable, reference, operator.lt) | |
343 | ||
344 | class LesserOrEqual(BasicConstraint): | |
345 | """A basic constraint variable <= constant value""" | |
346 | def __init__(self, variable, reference): | |
347 | BasicConstraint.__init__(self, variable, reference, operator.le) | |
348 | ||
349 | class GreaterThan(BasicConstraint): | |
350 | """A basic constraint variable > constant value""" | |
351 | def __init__(self, variable, reference): | |
352 | BasicConstraint.__init__(self, variable, reference, operator.gt) | |
353 | ||
354 | class GreaterOrEqual(BasicConstraint): | |
355 | """A basic constraint variable >= constant value""" | |
356 | def __init__(self, variable, reference): | |
357 | BasicConstraint.__init__(self, variable, reference, operator.ge) | |
358 | ||
359 | def _in(v, set): | |
360 | """test presence of v in set""" | |
361 | return v in set | |
362 | ||
363 | class InSet(BasicConstraint): | |
364 | """A basic contraint variable in set value""" | |
365 | def __init__(self, variable, set): | |
366 | BasicConstraint.__init__(self, variable, set, _in ) | |
367 | ||
368 | ||
369 | ||
370 |
0 | # pylint: disable-msg=W0142 | |
1 | # (c) 2002 LOGILAB S.A. (Paris, FRANCE). | |
2 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
3 | # | |
4 | # This program is free software; you can redistribute it and/or modify it under | |
5 | # the terms of the GNU General Public License as published by the Free Software | |
6 | # Foundation; either version 2 of the License, or (at your option) any later | |
7 | # version. | |
8 | # | |
9 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
11 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
12 | # | |
13 | # You should have received a copy of the GNU General Public License along with | |
14 | # this program; if not, write to the Free Software Foundation, Inc., | |
15 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
16 | # USA. | |
17 | """Tools to work with finite interval domain interval and constraints | |
18 | ||
19 | """ | |
20 | ||
21 | from __future__ import generators, division | |
22 | ||
23 | from logilab.common.compat import set, sorted | |
24 | ||
25 | from logilab.constraint.distributors import AbstractDistributor | |
26 | from logilab.constraint.propagation import (AbstractDomain, ConsistencyFailure, | |
27 | AbstractConstraint) | |
28 | ||
29 | class Interval: | |
30 | """representation of an interval | |
31 | This class is used to give back results from a FiniteIntervalDomain | |
32 | """ | |
33 | def __init__(self, start, end): | |
34 | self._start = start | |
35 | self._end = end | |
36 | ||
37 | def __repr__(self): | |
38 | return "<Interval [%.2f %.2f[>" % (self._start, self._end) | |
39 | ||
40 | def __eq__(self, other): | |
41 | return self._start == other._start and \ | |
42 | self._end == other._end | |
43 | ||
44 | class FiniteIntervalDomain(AbstractDomain): | |
45 | """ | |
46 | Domain for a variable with interval values. | |
47 | """ | |
48 | ||
49 | def __init__(self, lowestMin, highestMax, | |
50 | min_length, max_length=None, resolution=1): | |
51 | """ | |
52 | lowestMin is the lowest value of a low boundary for a variable (inclusive). | |
53 | highestMax is the highest value of a high boundary for a variable (exclusive). | |
54 | min_length is the minimum width of the interval. | |
55 | max_length is the maximum width of the interval. | |
56 | Use None to have max = min. | |
57 | resolution is the precision to use for constraint satisfaction. Defaults to 1 | |
58 | """ | |
59 | assert highestMax >= lowestMin | |
60 | if max_length is None: | |
61 | max_length = min_length | |
62 | assert 0 <= min_length <= max_length | |
63 | assert min_length <= highestMax - lowestMin | |
64 | assert resolution > 0 | |
65 | AbstractDomain.__init__(self) | |
66 | self.lowestMin = lowestMin | |
67 | self.highestMax = highestMax | |
68 | self._min_length = min_length | |
69 | max_length = min(max_length, highestMax - lowestMin) | |
70 | self._max_length = max_length | |
71 | self._resolution = resolution | |
72 | ||
73 | def __eq__(self, other): | |
74 | ||
75 | return self.lowestMin == other.lowestMin and \ | |
76 | self.highestMax == other.highestMax and \ | |
77 | self._min_length == other._min_length and \ | |
78 | self._max_length == other._max_length and \ | |
79 | self._resolution == other._resolution | |
80 | ||
81 | def getValues(self): | |
82 | return list(self.iter_values()) | |
83 | ||
84 | def iter_values(self): | |
85 | length = self._min_length | |
86 | while length <= self._max_length: | |
87 | start = self.lowestMin | |
88 | while start + length <= self.highestMax: | |
89 | yield Interval(start, start+length) | |
90 | start += self._resolution | |
91 | length += self._resolution | |
92 | ||
93 | ||
94 | def size(self): | |
95 | """computes the size of a finite interval""" | |
96 | size = 0 | |
97 | length = self._min_length | |
98 | while length <= self._max_length : | |
99 | size += ((self.highestMax - length) - self.lowestMin) / self._resolution + 1 | |
100 | length += self._resolution | |
101 | return size | |
102 | ||
103 | def _highestMin(self): | |
104 | return self.highestMax - self._min_length | |
105 | ||
106 | def _lowestMax(self): | |
107 | return self.lowestMin + self._min_length | |
108 | ||
109 | lowestMax = property(_lowestMax, None, None, "") | |
110 | ||
111 | highestMin = property(_highestMin, None, None, "") | |
112 | ||
113 | def copy(self): | |
114 | """clone the domain""" | |
115 | return FiniteIntervalDomain(self.lowestMin, self.highestMax, | |
116 | self._min_length, self._max_length, | |
117 | self._resolution) | |
118 | ||
119 | def setLowestMin(self, new_lowestMin): | |
120 | self.lowestMin = new_lowestMin | |
121 | self._valueRemoved() | |
122 | ||
123 | def setHighestMax(self, new_highestMax): | |
124 | self.highestMax = new_highestMax | |
125 | self._valueRemoved() | |
126 | ||
127 | def setMinLength(self, new_min): | |
128 | self._min_length = new_min | |
129 | self._valueRemoved() | |
130 | ||
131 | def setMaxLength(self, new_max): | |
132 | self._max_length = new_max | |
133 | self._valueRemoved() | |
134 | ||
135 | def overlap(self, other): | |
136 | return other.highestMax > self.lowestMin and \ | |
137 | other.lowestMin < self.highestMax | |
138 | ||
139 | def no_overlap_impossible(self, other): | |
140 | return self.lowestMax > other.highestMin and \ | |
141 | other.lowestMax > self.highestMin | |
142 | ||
143 | def hasSingleLength(self): | |
144 | return self._max_length == self._min_length | |
145 | ||
146 | def _valueRemoved(self): | |
147 | if self.lowestMin >= self.highestMax: | |
148 | raise ConsistencyFailure("earliest start [%.2f] higher than latest end [%.2f]" % | |
149 | (self.lowestMin, self.highestMax)) | |
150 | if self._min_length > self._max_length: | |
151 | raise ConsistencyFailure("min length [%.2f] greater than max length [%.2f]" % | |
152 | (self._min_length, self._max_length)) | |
153 | ||
154 | self._max_length = min(self._max_length, self.highestMax - self.lowestMin) | |
155 | AbstractDomain._valueRemoved(self) | |
156 | ||
157 | def __repr__(self): | |
158 | return '<FiniteIntervalDomain % 3d from [%.2f, %.2f[ to [%.2f, %.2f[>' % (self.size(), | |
159 | self.lowestMin, | |
160 | self.lowestMax, | |
161 | self.highestMin, | |
162 | self.highestMax,) | |
163 | ||
164 | ## | |
165 | ## Distributors | |
166 | ## | |
167 | ||
168 | class FiniteIntervalDistributor(AbstractDistributor): | |
169 | """Distributes a set of FiniteIntervalDomain | |
170 | The distribution strategy is the following: | |
171 | - the smallest domain of size > 1 is picked | |
172 | - if its max_length is greater than its min_length, a subdomain if size | |
173 | min_length is distributed, with the same boundaries | |
174 | - otherwise, a subdomain [lowestMin, lowestMax[ is distributed | |
175 | """ | |
176 | def __init__(self): | |
177 | AbstractDistributor.__init__(self) | |
178 | ||
179 | def _split_values(self, copy1, copy2): | |
180 | lm = copy1.lowestMin | |
181 | hM = copy1.highestMax | |
182 | if copy1.hasSingleLength(): | |
183 | r = copy1._resolution | |
184 | L = copy1._min_length | |
185 | m = (hM-L+lm)//(2*r)*r | |
186 | ## copy1.highestMax = copy1.lowestMin + copy1._min_length | |
187 | ## copy2.lowestMin += copy2._resolution | |
188 | copy1.highestMax = m+L | |
189 | copy2.lowestMin = m+r | |
190 | else: | |
191 | copy1._max_length = copy1._min_length | |
192 | copy2._min_length += copy2._resolution | |
193 | ||
194 | ||
195 | def _distribute(self, dom1, dom2): | |
196 | variable = self.findSmallestDomain(dom1) | |
197 | if self.verbose: | |
198 | print 'Distributing domain for variable', variable | |
199 | splitted = dom1[variable] | |
200 | cpy1 = splitted.copy() | |
201 | cpy2 = splitted.copy() | |
202 | ||
203 | self._split_values(cpy1, cpy2) | |
204 | ||
205 | dom1[variable] = cpy1 | |
206 | dom2[variable] = cpy2 | |
207 | ||
208 | return cpy1, cpy2 | |
209 | ||
210 | ||
211 | ||
212 | ## | |
213 | ## Constraints | |
214 | ## | |
215 | ||
216 | class AbstractFIConstraint(AbstractConstraint): | |
217 | def __init__(self, var1, var2): | |
218 | AbstractConstraint.__init__(self, (var1, var2)) | |
219 | ||
220 | def estimateCost(self, domains): | |
221 | return 1 | |
222 | ||
223 | def __repr__(self): | |
224 | return '<%s %s>' % (self.__class__.__name__, str(self._variables)) | |
225 | ||
226 | def __eq__(self, other): | |
227 | return repr(self) == repr(other) | |
228 | ||
229 | def __hash__(self): | |
230 | # FIXME: to be able to add constraints in Sets (and compare them) | |
231 | # FIXME: improve implementation | |
232 | variables = tuple(sorted(self._variables)) | |
233 | return hash((self.__class__.__name__, variables)) | |
234 | ||
235 | def narrow(self, domains): | |
236 | """narrowing algorithm for the constraint""" | |
237 | dom1 = domains[self._variables[0]] | |
238 | dom2 = domains[self._variables[1]] | |
239 | ||
240 | return self._doNarrow(dom1, dom2) | |
241 | ||
242 | def _doNarrow(self, dom1, dom2): | |
243 | """virtual method which does the real work""" | |
244 | raise NotImplementedError | |
245 | ||
246 | ||
247 | ||
248 | # FIXME: deal with more than 2 domains at once ? | |
249 | class NoOverlap(AbstractFIConstraint): | |
250 | ||
251 | def __eq__(self, other): | |
252 | return isinstance(other, NoOverlap) and \ | |
253 | set(self._variables) == set(other._variables) | |
254 | ||
255 | def _doNarrow(self, dom1, dom2): | |
256 | if not dom1.overlap(dom2): | |
257 | return 1 | |
258 | elif dom1.no_overlap_impossible(dom2) : | |
259 | raise ConsistencyFailure | |
260 | elif dom1.lowestMax == dom2.highestMin and dom2.lowestMax > dom1.highestMin : | |
261 | dom1.setHighestMax(dom2.highestMin) | |
262 | dom2.setLowestMin(dom1.lowestMax) | |
263 | return 1 | |
264 | elif dom1.lowestMax > dom2.highestMin and dom2.lowestMax == dom1.highestMin : | |
265 | dom2.setHighestMax(dom1.highestMin) | |
266 | dom1.setLowestMin(dom2.lowestMax) | |
267 | return 1 | |
268 | return 0 | |
269 | ||
270 | class StartsBeforeStart(AbstractFIConstraint): | |
271 | ||
272 | def _doNarrow(self, dom1, dom2): | |
273 | ||
274 | if dom1.lowestMin > dom2.highestMin: | |
275 | raise ConsistencyFailure | |
276 | if dom1.highestMin < dom2.lowestMin: | |
277 | return 1 | |
278 | return 0 | |
279 | ||
280 | class StartsBeforeEnd(AbstractFIConstraint): | |
281 | ||
282 | def _doNarrow(self, dom1, dom2): | |
283 | if dom1.lowestMin > dom2.highestMax: | |
284 | raise ConsistencyFailure | |
285 | if dom1.highestMin < dom2.lowestMax: | |
286 | return 1 | |
287 | return 0 | |
288 | ||
289 | class EndsBeforeStart(AbstractFIConstraint): | |
290 | ||
291 | def _doNarrow(self, dom1, dom2): | |
292 | if dom1.lowestMax > dom2.highestMin: | |
293 | raise ConsistencyFailure | |
294 | if dom1.highestMax < dom2.lowestMin: | |
295 | return 1 | |
296 | if dom1.highestMax > dom2.highestMin: | |
297 | dom1.setHighestMax(dom2.highestMin) | |
298 | return 0 | |
299 | ||
300 | class EndsBeforeEnd(AbstractFIConstraint): | |
301 | ||
302 | def _doNarrow(self, dom1, dom2): | |
303 | if dom1.lowestMax > dom2.highestMax: | |
304 | raise ConsistencyFailure | |
305 | if dom1.highestMax < dom2.lowestMax: | |
306 | return 1 | |
307 | if dom1.highestMax > dom2.highestMax: | |
308 | dom1.setHighestMax(dom2.highestMax) | |
309 | return 0 | |
310 | ||
311 | class StartsAfterStart(AbstractFIConstraint): | |
312 | ||
313 | def _doNarrow(self, dom1, dom2): | |
314 | if dom1.highestMin < dom2.lowestMin: | |
315 | raise ConsistencyFailure | |
316 | if dom1.lowestMin > dom2.highestMin: | |
317 | return 1 | |
318 | if dom1.lowestMin < dom2.lowestMin: | |
319 | dom1.setLowestMin(dom2.lowestMin) | |
320 | return 0 | |
321 | ||
322 | class StartsAfterEnd(AbstractFIConstraint): | |
323 | ||
324 | def _doNarrow(self, dom1, dom2): | |
325 | if dom1.highestMin < dom2.lowestMax: | |
326 | raise ConsistencyFailure | |
327 | if dom1.lowestMin > dom2.highestMax: | |
328 | return 1 | |
329 | if dom1.lowestMin < dom2.lowestMax: | |
330 | dom1.setLowestMin(dom2.lowestMax) | |
331 | if dom2.highestMax > dom1.highestMin: | |
332 | dom2.setHighestMax(dom1.highestMin) | |
333 | return 0 | |
334 | ||
335 | class EndsAfterStart(AbstractFIConstraint): | |
336 | ||
337 | def _doNarrow(self, dom1, dom2): | |
338 | if dom1.highestMax < dom2.lowestMin: | |
339 | raise ConsistencyFailure | |
340 | if dom1.lowestMax > dom2.highestMin: | |
341 | return 1 | |
342 | return 0 | |
343 | ||
344 | class EndsAfterEnd(AbstractFIConstraint): | |
345 | ||
346 | def _doNarrow(self, dom1, dom2): | |
347 | if dom1.highestMax < dom2.lowestMax: | |
348 | raise ConsistencyFailure | |
349 | if dom1.lowestMax > dom2.highestMax: | |
350 | return 1 | |
351 | return 0 | |
352 |
0 | # (c) 2000-2001 LOGILAB S.A. (Paris, FRANCE). | |
1 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
2 | # | |
3 | # This program is free software; you can redistribute it and/or modify it under | |
4 | # the terms of the GNU General Public License as published by the Free Software | |
5 | # Foundation; either version 2 of the License, or (at your option) any later | |
6 | # version. | |
7 | # | |
8 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
9 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
10 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
11 | # | |
12 | # You should have received a copy of the GNU General Public License along with | |
13 | # this program; if not, write to the Free Software Foundation, Inc., | |
14 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
15 | # USA. | |
16 | """Definition of interfaces""" | |
17 | ||
18 | class ConstraintInterface: | |
19 | """The interface that all constraints should implement""" | |
20 | def isVariableRelevant(self, variable): | |
21 | """Returns true if changes in the domaine of the variable | |
22 | should trigger an evaluation of the constraint""" | |
23 | raise NotImplementedError | |
24 | ||
25 | def affectedVariables(self): | |
26 | """ Return a list of all variables affected by this constraint """ | |
27 | raise NotImplementedError | |
28 | ||
29 | def estimateCost(self, domains): | |
30 | """ Return an estimate of the cost of the narrowing of the constraint""" | |
31 | raise NotImplementedError | |
32 | ||
33 | def narrow(self, domains): | |
34 | """ensures that the domains are consistent with the constraint | |
35 | Calls domain.removeValue to remove values from a domain | |
36 | raises ConsistencyFailure if the narrowing fails | |
37 | Returns 1 if the constraint is entailed, and 0 otherwise""" | |
38 | raise NotImplementedError | |
39 | ||
40 | ||
41 | class DomainInterface: | |
42 | """The interface that all domains should implement""" | |
43 | ||
44 | def resetFlags(self): | |
45 | """resets the hasChanged flag""" | |
46 | raise NotImplementedError | |
47 | ||
48 | def hasChanged(self): | |
49 | """returns true if values have been removed from the domain | |
50 | since the last call to resetFlags""" | |
51 | raise NotImplementedError | |
52 | ||
53 | def removeValue(self, value): | |
54 | """Removes a value from the domain""" | |
55 | raise NotImplementedError | |
56 | ||
57 | def size(self): | |
58 | """returns the number of values in the domain""" | |
59 | raise NotImplementedError | |
60 | ||
61 | def getValues(self): | |
62 | """returns a tuple containing all the values in the domain | |
63 | These values should not be modified!""" | |
64 | raise NotImplementedError | |
65 | ||
66 | class DistributorInterface: | |
67 | """The interface that all distributors should implement""" | |
68 | def distribute(self, domains, verbose=0): | |
69 | """domains is a dictionnary of variable -> Domain objects | |
70 | This method returns a list of dictionnaries similar to the domain argument | |
71 | This list should be a partition of the initial domains""" | |
72 | raise NotImplementedError | |
73 | ||
74 | ## class VariableInterface: | |
75 | ## """The interface that all variables should implement""" | |
76 | ## def getDomain(self): | |
77 | ## """returns the domain of the variable""" | |
78 | ## raise NotImplementedError | |
79 | ## def setDomain(self, domain): | |
80 | ## """sets a new domain to the variable""" | |
81 | ## raise NotImplementedError | |
82 | ||
83 | ## # Magic methods for various operations | |
84 | ## def __add__(self, other): | |
85 | ## raise NotImplementedError | |
86 | ||
87 | ## def __sub__(self, other): | |
88 | ## raise NotImplementedError | |
89 | ||
90 | ## def __mul__(self, other): | |
91 | ## raise NotImplementedError | |
92 | ||
93 | ## def __div__(self, other): | |
94 | ## raise NotImplementedError | |
95 | ||
96 | ## def __radd__(self, other): | |
97 | ## raise NotImplementedError | |
98 | ||
99 | ## def __rsub__(self, other): | |
100 | ## raise NotImplementedError | |
101 | ||
102 | ## def __rmul__(self, other): | |
103 | ## raise NotImplementedError | |
104 | ||
105 | ## def __rdiv__(self, other): | |
106 | ## raise NotImplementedError | |
107 | ||
108 | ## def __abs__(self): | |
109 | ## raise NotImplementedError | |
110 | ||
111 | ## def __neg__(self): | |
112 | ## raise NotImplementedError | |
113 | ||
114 | ## def __pos__(self): | |
115 | ## raise NotImplementedError | |
116 | ||
117 | ## def __lt__(self, other): | |
118 | ## raise NotImplementedError | |
119 | ||
120 | ## def __le__(self, other): | |
121 | ## raise NotImplementedError | |
122 | ||
123 | ## def __gt__(self, other): | |
124 | ## raise NotImplementedError | |
125 | ||
126 | ## def __ge__(self, other): | |
127 | ## raise NotImplementedError | |
128 | ||
129 | ## def __eq__(self, other): | |
130 | ## raise NotImplementedError | |
131 | ||
132 | ## def __ne__(self, other): | |
133 | ## raise NotImplementedError | |
134 | ||
135 | ## def __len__(self): | |
136 | ## raise NotImplementedError | |
137 | ||
138 | ## def __getitem__(self, size): | |
139 | ## raise NotImplementedError |
0 | # (c) 2002 LOGILAB S.A. (Paris, FRANCE). | |
1 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
2 | # | |
3 | # This program is free software; you can redistribute it and/or modify it under | |
4 | # the terms of the GNU General Public License as published by the Free Software | |
5 | # Foundation; either version 2 of the License, or (at your option) any later | |
6 | # version. | |
7 | # | |
8 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
9 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
10 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
11 | # | |
12 | # You should have received a copy of the GNU General Public License along with | |
13 | # this program; if not, write to the Free Software Foundation, Inc., | |
14 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
15 | # USA. | |
16 | """The code of the constraint propagation algorithms""" | |
17 | ||
18 | from __future__ import generators | |
19 | from operator import mul as MUL | |
20 | from time import strftime | |
21 | from logilab.constraint.interfaces import DomainInterface, ConstraintInterface | |
22 | from logilab.constraint.psyco_wrapper import Psyobj | |
23 | from logilab.common.compat import enumerate | |
24 | ||
25 | def _default_printer(*msgs): | |
26 | for msg in msgs[:-1]: | |
27 | print msg, | |
28 | print msgs[-1] | |
29 | class ConsistencyFailure(Exception): | |
30 | """The repository is not in a consistent state""" | |
31 | pass | |
32 | ||
33 | class Repository(Psyobj): | |
34 | """Stores variables, domains and constraints | |
35 | Propagates domain changes to constraints | |
36 | Manages the constraint evaluation queue""" | |
37 | ||
38 | def __init__(self, variables, domains, constraints = None, printer=_default_printer): | |
39 | # encode unicode | |
40 | self._printer = printer | |
41 | ||
42 | for i, var in enumerate(variables): | |
43 | if type(var) == type(u''): | |
44 | variables[i] = var.encode() | |
45 | ||
46 | self._variables = variables # list of variable names | |
47 | self._domains = domains # maps variable name to domain object | |
48 | self._constraints = [] # list of constraint objects | |
49 | # self._queue = [] # queue of constraints waiting to be processed | |
50 | self._variableListeners = {} | |
51 | for var in self._variables: | |
52 | self._variableListeners[var] = [] | |
53 | assert self._domains.has_key(var) | |
54 | for constr in constraints or (): | |
55 | self.addConstraint(constr) | |
56 | ||
57 | ||
58 | def display(self): | |
59 | self._printer( "VARIABLES") | |
60 | self._printer( "---------") | |
61 | for v in sorted(self._variables): | |
62 | self._printer( "%s = %s" % (v, self._domains[v])) | |
63 | self._printer( "CONSTRAINTS") | |
64 | self._printer( "-----------") | |
65 | for c in self._constraints: | |
66 | self._printer( c) | |
67 | ||
68 | def display_vars(self): | |
69 | self._printer( "%d constraints with:" % len(self._constraints)) | |
70 | for v in sorted(self._variables): | |
71 | self._printer( "%15s = %s" % (v, self._domains[v])) | |
72 | ||
73 | ||
74 | def __repr__(self): | |
75 | return '<Repository nb_constraints=%d domains=%s>' % \ | |
76 | (len(self._constraints), self._domains) | |
77 | ||
78 | def vcg_draw(self, filename, title='Constraints graph'): | |
79 | """ draw a constraints graph readable by vcg | |
80 | """ | |
81 | from logilab.common.vcgutils import VCGPrinter, EDGE_ATTRS | |
82 | stream = open(filename, 'w') | |
83 | printer = VCGPrinter(stream) | |
84 | printer.open_graph(title=title, textcolor='black' | |
85 | # layoutalgorithm='dfs', | |
86 | # manhattan_edges='yes' | |
87 | # port_sharing='no' | |
88 | # late_edge_labels='yes' | |
89 | ) | |
90 | ||
91 | for var in self._variables: | |
92 | printer.node(var, shape='ellipse') | |
93 | ||
94 | type_colors = {} | |
95 | color_index = 2 | |
96 | i = 0 | |
97 | for constraint in self._constraints: | |
98 | key = constraint.type | |
99 | if not type_colors.has_key(key): | |
100 | type_colors[key] = color_index | |
101 | color_index += 1 | |
102 | affected_vars = constraint.affectedVariables() | |
103 | if len(affected_vars) <= 1: | |
104 | continue | |
105 | if len(affected_vars) == 2: | |
106 | var1 = affected_vars[0] | |
107 | var2 = affected_vars[1] | |
108 | printer.edge(var1, var2, arrowstyle='none', | |
109 | color=EDGE_ATTRS['color'][type_colors[key]]) | |
110 | continue | |
111 | n_id = 'N_aire%i' % i | |
112 | i += 1 | |
113 | printer.node(n_id, shape='triangle') | |
114 | for var1 in affected_vars: | |
115 | printer.edge(var1, n_id, arrowstyle='none', | |
116 | color=EDGE_ATTRS['color'][type_colors[key]]) | |
117 | # self._printer( legend) | |
118 | for node_type, color in type_colors.items(): | |
119 | printer.node(node_type, shape='box', | |
120 | color=EDGE_ATTRS['color'][color]) | |
121 | printer.close_graph() | |
122 | stream.close() | |
123 | ||
124 | def addConstraint(self, constraint): | |
125 | if isinstance(constraint, BasicConstraint): | |
126 | # Basic constraints are processed just once | |
127 | # because they are straight away entailed | |
128 | var = constraint.getVariable() | |
129 | constraint.narrow({var: self._domains[var]}) | |
130 | else: | |
131 | self._constraints.append(constraint) | |
132 | for var in constraint.affectedVariables(): | |
133 | self._variableListeners[var].append(constraint) | |
134 | ||
135 | def _removeConstraint(self, constraint): | |
136 | self._constraints.remove(constraint) | |
137 | for var in constraint.affectedVariables(): | |
138 | try: | |
139 | self._variableListeners[var].remove(constraint) | |
140 | except ValueError: | |
141 | raise ValueError('Error removing constraint from listener', | |
142 | var, | |
143 | self._variableListeners[var], | |
144 | constraint) | |
145 | ||
146 | def getDomains(self): | |
147 | return self._domains | |
148 | ||
149 | def distribute(self, distributor, verbose=0): | |
150 | """Create new repository using the distributor and self """ | |
151 | for domains in distributor.distribute(self._domains, verbose): | |
152 | yield Repository(self._variables, domains, self._constraints) | |
153 | ||
154 | # alf 20041216 -- I tried the following to avoid the cost of the | |
155 | # creation of new Repository objects. It resulted in functional, but | |
156 | # slightly slower code. If you want to try to improve it, I keep the | |
157 | # commented out version in the source, but the version above stays | |
158 | # active as it is both simpler and faster. | |
159 | ## backup_constraints = self._constraints[:] | |
160 | ## for domains in distributor.distribute(self._domains, verbose): | |
161 | ## self._domains = domains | |
162 | ## self._constraints = [] | |
163 | ## self._queue = [] | |
164 | ## for var in self._variables: | |
165 | ## self._variableListeners[var] = [] | |
166 | ## for constraint in backup_constraints: | |
167 | ## self.addConstraint(constraint) | |
168 | ## yield self | |
169 | ||
170 | def consistency(self, verbose=0, custom_printer=None): | |
171 | """Prunes the domains of the variables | |
172 | This method calls constraint.narrow() and queues constraints | |
173 | that are affected by recent changes in the domains. | |
174 | Returns True if a solution was found""" | |
175 | if custom_printer is None: | |
176 | printer = self._printer | |
177 | else: | |
178 | printer = custom_printer | |
179 | if verbose: | |
180 | printer( strftime('%H:%M:%S'), '** Consistency **') | |
181 | ||
182 | _queue = [ (constr.estimateCost(self._domains), | |
183 | constr) for constr in self._constraints ] | |
184 | _queue.sort() | |
185 | _affected_constraints = {} | |
186 | while True: | |
187 | if not _queue: | |
188 | # refill the queue if some constraints have been affected | |
189 | _queue = [(constr.estimateCost(self._domains), | |
190 | constr) for constr in _affected_constraints] | |
191 | if not _queue: | |
192 | break | |
193 | _queue.sort() | |
194 | _affected_constraints.clear() | |
195 | if verbose > 2: | |
196 | printer( strftime('%H:%M:%S'), 'Queue', _queue) | |
197 | cost, constraint = _queue.pop(0) | |
198 | if verbose > 1: | |
199 | printer( strftime('%H:%M:%S'), | |
200 | 'Trying to entail constraint', constraint, '[cost:%d]' % cost) | |
201 | entailed = constraint.narrow(self._domains) | |
202 | for var in constraint.affectedVariables(): | |
203 | # affected constraints are listeners of | |
204 | # affected variables of this constraint | |
205 | dom = self._domains[var] | |
206 | if not dom.hasChanged(): | |
207 | continue | |
208 | if verbose > 1 : | |
209 | printer( strftime('%H:%M:%S'), | |
210 | ' -> New domain for variable', var, 'is', dom) | |
211 | for constr in self._variableListeners[var]: | |
212 | if constr is not constraint: | |
213 | _affected_constraints[constr] = True | |
214 | dom.resetFlags() | |
215 | if entailed: | |
216 | if verbose: | |
217 | printer( strftime('%H:%M:%S'), | |
218 | "--> Entailed constraint", constraint) | |
219 | self._removeConstraint(constraint) | |
220 | if constraint in _affected_constraints: | |
221 | del _affected_constraints[constraint] | |
222 | ||
223 | for domain in self._domains.itervalues(): | |
224 | if domain.size() != 1: | |
225 | return 0 | |
226 | return 1 | |
227 | ||
228 | class Solver(Psyobj): | |
229 | """Top-level object used to manage the search""" | |
230 | ||
231 | def __init__(self, distributor=None, printer=_default_printer): | |
232 | """if no distributer given, will use the default one""" | |
233 | self.printer = printer | |
234 | if distributor is None: | |
235 | from logilab.constraint.distributors import DefaultDistributor | |
236 | distributor = DefaultDistributor() | |
237 | self.verbose = True | |
238 | self._distributor = distributor | |
239 | self.max_depth = 0 | |
240 | ||
241 | def solve_one(self, repository, verbose=0): | |
242 | """Generates only one solution""" | |
243 | self.verbose = verbose | |
244 | self.max_depth = 0 | |
245 | self.distrib_cnt = 0 | |
246 | try: | |
247 | # XXX FIXME: this is a workaround a bug in psyco-1.4 | |
248 | ## return self._solve(repository).next() | |
249 | return self._solve(repository, 0).next() | |
250 | except StopIteration: | |
251 | return | |
252 | ||
253 | def solve_best(self, repository, cost_func, verbose=0): | |
254 | """Generates solution with an improving cost""" | |
255 | self.verbose = verbose | |
256 | self.max_depth = 0 | |
257 | self.distrib_cnt = 0 | |
258 | best_cost = None | |
259 | # XXX FIXME: this is a workaround a bug in psyco-1.4 | |
260 | ## for solution in self._solve(repository): | |
261 | for solution in self._solve(repository, 0): | |
262 | cost = cost_func(**solution) | |
263 | if best_cost is None or cost <= best_cost: | |
264 | best_cost = cost | |
265 | yield solution, cost | |
266 | ||
267 | def solve_all(self, repository, verbose=0): | |
268 | """Generates all solutions""" | |
269 | self.verbose = verbose | |
270 | self.max_depth = 0 | |
271 | self.distrib_cnt = 0 | |
272 | for solution in self._solve(repository): | |
273 | yield solution | |
274 | ||
275 | def solve(self, repository, verbose=0): | |
276 | """return list of all solutions""" | |
277 | self.max_depth = 0 | |
278 | self.distrib_cnt = 0 | |
279 | solutions = [] | |
280 | for solution in self.solve_all(repository, verbose): | |
281 | solutions.append(solution) | |
282 | return solutions | |
283 | ||
284 | def _solve(self, repository, recursion_level=0): | |
285 | """main generator""" | |
286 | _solve = self._solve | |
287 | verbose = self.verbose | |
288 | if recursion_level > self.max_depth: | |
289 | self.max_depth = recursion_level | |
290 | if verbose >= 2: | |
291 | self.printer( strftime('%H:%M:%S'),) | |
292 | self.printer( '*** [%d] Solve called with repository' % recursion_level,) | |
293 | repository.display_vars() | |
294 | try: | |
295 | foundSolution = repository.consistency(verbose, custom_printer=self.printer) | |
296 | except ConsistencyFailure, exc: | |
297 | if verbose: | |
298 | self.printer( strftime('%H:%M:%S'), exc) | |
299 | else: | |
300 | if foundSolution: | |
301 | solution = {} | |
302 | for variable, domain in repository.getDomains().items(): | |
303 | solution[variable] = domain.getValues()[0] | |
304 | if verbose: | |
305 | self.printer( strftime('%H:%M:%S'), '### Found Solution', solution) | |
306 | self.printer( '-'*80) | |
307 | yield solution | |
308 | else: | |
309 | self.distrib_cnt += 1 | |
310 | for repo in repository.distribute(self._distributor, | |
311 | verbose>=2): | |
312 | for solution in _solve(repo, recursion_level+1): | |
313 | if solution is not None: | |
314 | yield solution | |
315 | ||
316 | if recursion_level == 0 and self.verbose: | |
317 | self.printer( strftime('%H:%M:%S'),'Finished search') | |
318 | self.printer( strftime('%H:%M:%S'), 'Maximum recursion depth = ', | |
319 | self.max_depth) | |
320 | self.printer( 'Nb distributions = ', self.distrib_cnt) | |
321 | ||
322 | ||
323 | ||
324 | ||
325 | ||
326 | ||
327 | class BasicConstraint(Psyobj): | |
328 | """A BasicConstraint, which is never queued by the Repository | |
329 | A BasicConstraint affects only one variable, and will be entailed | |
330 | on the first call to narrow()""" | |
331 | ||
332 | __implements__ = ConstraintInterface | |
333 | ||
334 | def __init__(self, variable, reference, operator): | |
335 | """variables is a list of variables on which | |
336 | the constraint is applied""" | |
337 | self._variable = variable | |
338 | self._reference = reference | |
339 | self._operator = operator | |
340 | ||
341 | def __repr__(self): | |
342 | return '<%s %s %s>'% (self.__class__, self._variable, self._reference) | |
343 | ||
344 | def isVariableRelevant(self, variable): | |
345 | return variable == self._variable | |
346 | ||
347 | def estimateCost(self, domains): | |
348 | return 0 # get in the first place in the queue | |
349 | ||
350 | def affectedVariables(self): | |
351 | return [self._variable] | |
352 | ||
353 | def getVariable(self): | |
354 | return self._variable | |
355 | ||
356 | def narrow(self, domains): | |
357 | domain = domains[self._variable] | |
358 | operator = self._operator | |
359 | ref = self._reference | |
360 | try: | |
361 | for val in domain.getValues() : | |
362 | if not operator(val, ref) : | |
363 | domain.removeValue(val) | |
364 | except ConsistencyFailure: | |
365 | raise ConsistencyFailure('inconsistency while applying %s' % \ | |
366 | repr(self)) | |
367 | return 1 | |
368 | ||
369 | ||
370 | class AbstractDomain(Psyobj): | |
371 | """Implements the functionnality related to the changed flag. | |
372 | Can be used as a starting point for concrete domains""" | |
373 | ||
374 | __implements__ = DomainInterface | |
375 | def __init__(self): | |
376 | self.__changed = 0 | |
377 | ||
378 | def resetFlags(self): | |
379 | self.__changed = 0 | |
380 | ||
381 | def hasChanged(self): | |
382 | return self.__changed | |
383 | ||
384 | def _valueRemoved(self): | |
385 | """The implementation of removeValue should call this method""" | |
386 | self.__changed = 1 | |
387 | if self.size() == 0: | |
388 | raise ConsistencyFailure() | |
389 | ||
390 | class AbstractConstraint(Psyobj): | |
391 | __implements__ = ConstraintInterface | |
392 | ||
393 | def __init__(self, variables): | |
394 | """variables is a list of variables which appear in the formula""" | |
395 | self._variables = variables | |
396 | ||
397 | def affectedVariables(self): | |
398 | """ Return a list of all variables affected by this constraint """ | |
399 | return self._variables | |
400 | ||
401 | def isVariableRelevant(self, variable): | |
402 | return variable in self._variables | |
403 | ||
404 | def estimateCost(self, domains): | |
405 | """Return an estimate of the cost of the narrowing of the constraint""" | |
406 | return reduce(MUL, [domains[var].size() for var in self._variables]) | |
407 | ||
408 |
0 | # pylint: disable-msg=W0232 | |
1 | # (c) 2002-2004 LOGILAB S.A. (Paris, FRANCE). | |
2 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
3 | # | |
4 | # This program is free software; you can redistribute it and/or modify it under | |
5 | # the terms of the GNU General Public License as published by the Free Software | |
6 | # Foundation; either version 2 of the License, or (at your option) any later | |
7 | # version. | |
8 | # | |
9 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
11 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
12 | # | |
13 | # You should have received a copy of the GNU General Public License along with | |
14 | # this program; if not, write to the Free Software Foundation, Inc., | |
15 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
16 | # USA. | |
17 | ||
18 | """Provides an psyobj class regardless of the availability of psyco""" | |
19 | ||
20 | import os | |
21 | ||
22 | try: | |
23 | if "NO_PSYCO" in os.environ: | |
24 | raise ImportError() | |
25 | from psyco.classes import psyobj as Psyobj # pylint: disable-msg=W0611 | |
26 | except ImportError: | |
27 | ||
28 | class Psyobj: | |
29 | pass | |
30 | ||
31 | if hasattr(os,'uname') and os.uname()[-1] == 'x86_64': | |
32 | pass # psyco only available for 32bits platforms | |
33 | else: | |
34 | from warnings import warn | |
35 | warn("Psyco could not be loaded." | |
36 | " Psyco is a Python just in time compiler available at http://psyco.sf.net" | |
37 | " Installing it will enhance the performance of logilab.constraint", | |
38 | stacklevel=2) | |
39 | ||
40 |
0 | #!/usr/bin/env python | |
1 | # pylint: disable-msg=W0142, W0403,W0404, W0613,W0622,W0622, W0704, R0904 | |
2 | # | |
3 | # Copyright (c) 2003 LOGILAB S.A. (Paris, FRANCE). | |
4 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
5 | # | |
6 | # This program is free software; you can redistribute it and/or modify it under | |
7 | # the terms of the GNU General Public License as published by the Free Software | |
8 | # Foundation; either version 2 of the License, or (at your option) any later | |
9 | # version. | |
10 | # | |
11 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
13 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
14 | # | |
15 | # You should have received a copy of the GNU General Public License along with | |
16 | # this program; if not, write to the Free Software Foundation, Inc., | |
17 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
18 | """ Generic Setup script, takes package info from __pkginfo__.py file """ | |
19 | ||
20 | import os | |
21 | import sys | |
22 | import shutil | |
23 | from distutils.core import setup | |
24 | from distutils.command import install_lib | |
25 | from os.path import isdir, exists, join, walk | |
26 | ||
27 | # import required features | |
28 | from __pkginfo__ import modname, version, license, short_desc, long_desc, \ | |
29 | web, author, author_email | |
30 | # import optional features | |
31 | try: | |
32 | from __pkginfo__ import distname | |
33 | except ImportError: | |
34 | distname = modname | |
35 | try: | |
36 | from __pkginfo__ import scripts | |
37 | except ImportError: | |
38 | scripts = [] | |
39 | try: | |
40 | from __pkginfo__ import data_files | |
41 | except ImportError: | |
42 | data_files = None | |
43 | try: | |
44 | from __pkginfo__ import subpackage_of | |
45 | except ImportError: | |
46 | subpackage_of = None | |
47 | try: | |
48 | from __pkginfo__ import include_dirs | |
49 | except ImportError: | |
50 | include_dirs = [] | |
51 | try: | |
52 | from __pkginfo__ import ext_modules | |
53 | except ImportError: | |
54 | ext_modules = None | |
55 | ||
56 | BASE_BLACKLIST = ('CVS', 'debian', 'dist', 'build', '__buildlog') | |
57 | IGNORED_EXTENSIONS = ('.pyc', '.pyo', '.elc') | |
58 | ||
59 | ||
60 | def ensure_scripts(linux_scripts): | |
61 | """ | |
62 | Creates the proper script names required for each platform | |
63 | (taken from 4Suite) | |
64 | """ | |
65 | from distutils import util | |
66 | if util.get_platform()[:3] == 'win': | |
67 | scripts_ = [script + '.bat' for script in linux_scripts] | |
68 | else: | |
69 | scripts_ = linux_scripts | |
70 | return scripts_ | |
71 | ||
72 | ||
73 | def get_packages(directory, prefix): | |
74 | """return a list of subpackages for the given directory | |
75 | """ | |
76 | result = [] | |
77 | for package in os.listdir(directory): | |
78 | absfile = join(directory, package) | |
79 | if isdir(absfile): | |
80 | if exists(join(absfile, '__init__.py')) or \ | |
81 | package in ('test', 'tests'): | |
82 | if prefix: | |
83 | result.append('%s.%s' % (prefix, package)) | |
84 | else: | |
85 | result.append(package) | |
86 | result += get_packages(absfile, result[-1]) | |
87 | return result | |
88 | ||
89 | def export(from_dir, to_dir, | |
90 | blacklist=BASE_BLACKLIST, | |
91 | ignore_ext=IGNORED_EXTENSIONS): | |
92 | """make a mirror of from_dir in to_dir, omitting directories and files | |
93 | listed in the black list | |
94 | """ | |
95 | def make_mirror(arg, directory, fnames): | |
96 | """walk handler""" | |
97 | for norecurs in blacklist: | |
98 | try: | |
99 | fnames.remove(norecurs) | |
100 | except ValueError: | |
101 | pass | |
102 | for filename in fnames: | |
103 | # don't include binary files | |
104 | if filename[-4:] in ignore_ext: | |
105 | continue | |
106 | if filename[-1] == '~': | |
107 | continue | |
108 | src = '%s/%s' % (directory, filename) | |
109 | dest = to_dir + src[len(from_dir):] | |
110 | print >> sys.stderr, src, '->', dest | |
111 | if os.path.isdir(src): | |
112 | if not exists(dest): | |
113 | os.mkdir(dest) | |
114 | else: | |
115 | if exists(dest): | |
116 | os.remove(dest) | |
117 | shutil.copy2(src, dest) | |
118 | try: | |
119 | os.mkdir(to_dir) | |
120 | except OSError, ex: | |
121 | # file exists ? | |
122 | import errno | |
123 | if ex.errno != errno.EEXIST: | |
124 | raise | |
125 | walk(from_dir, make_mirror, None) | |
126 | ||
127 | ||
128 | EMPTY_FILE = '"""generated file, don\'t modify or your data will be lost"""\n' | |
129 | ||
130 | class MyInstallLib(install_lib.install_lib): | |
131 | """extend install_lib command to handle package __init__.py and | |
132 | include_dirs variable if necessary | |
133 | """ | |
134 | def run(self): | |
135 | """overridden from install_lib class""" | |
136 | install_lib.install_lib.run(self) | |
137 | # create Products.__init__.py if needed | |
138 | if subpackage_of: | |
139 | product_init = join(self.install_dir, subpackage_of, '__init__.py') | |
140 | if not exists(product_init): | |
141 | self.announce('creating %s' % product_init) | |
142 | stream = open(product_init, 'w') | |
143 | stream.write(EMPTY_FILE) | |
144 | stream.close() | |
145 | # manually install included directories if any | |
146 | if include_dirs: | |
147 | if subpackage_of: | |
148 | base = join(subpackage_of, modname) | |
149 | else: | |
150 | base = modname | |
151 | for directory in include_dirs: | |
152 | dest = join(self.install_dir, base, directory) | |
153 | export(directory, dest) | |
154 | ||
155 | def install(**kwargs): | |
156 | """setup entry point""" | |
157 | if subpackage_of: | |
158 | package = subpackage_of + '.' + modname | |
159 | kwargs['package_dir'] = {package : '.'} | |
160 | packages = [package] + get_packages(os.getcwd(), package) | |
161 | else: | |
162 | kwargs['package_dir'] = {modname : '.'} | |
163 | packages = [modname] + get_packages(os.getcwd(), modname) | |
164 | kwargs['packages'] = packages | |
165 | return setup(name = distname, | |
166 | version = version, | |
167 | license =license, | |
168 | description = short_desc, | |
169 | long_description = long_desc, | |
170 | author = author, | |
171 | author_email = author_email, | |
172 | url = web, | |
173 | scripts = ensure_scripts(scripts), | |
174 | data_files=data_files, | |
175 | ext_modules=ext_modules, | |
176 | cmdclass={'install_lib': MyInstallLib}, | |
177 | **kwargs | |
178 | ) | |
179 | ||
180 | if __name__ == '__main__' : | |
181 | install() |
0 | # (c) 2000-2001 LOGILAB S.A. (Paris, FRANCE). | |
1 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
2 | # | |
3 | # This program is free software; you can redistribute it and/or modify it under | |
4 | # the terms of the GNU General Public License as published by the Free Software | |
5 | # Foundation; either version 2 of the License, or (at your option) any later | |
6 | # version. | |
7 | # | |
8 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
9 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
10 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
11 | # | |
12 | # You should have received a copy of the GNU General Public License along with | |
13 | # this program; if not, write to the Free Software Foundation, Inc., | |
14 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
15 | # USA. | |
16 | ||
17 | from logilab.constraint.propagation import * | |
18 | from logilab.constraint import fd | |
19 | ||
20 | ||
21 | def queens(size=8,verbose=0): | |
22 | possible_positions = [(i,j) for i in range(size) for j in range(size)] | |
23 | variables = [] | |
24 | domains = {} | |
25 | constraints = [] | |
26 | for i in range(size): | |
27 | name = 'Q%d'%i | |
28 | variables.append(name) | |
29 | domains[name] = fd.FiniteDomain(possible_positions) | |
30 | for q1 in variables: | |
31 | for q2 in variables: | |
32 | if q1 < q2: | |
33 | constraints.append(fd.make_expression((q1,q2), | |
34 | '%(q1)s[0] < %(q2)s[0] and ' | |
35 | '%(q1)s[1] != %(q2)s[1] and ' | |
36 | 'abs(%(q1)s[0]-%(q2)s[0]) != ' | |
37 | 'abs(%(q1)s[1]-%(q2)s[1])'%\ | |
38 | {'q1':q1,'q2':q2})) | |
39 | r = Repository(variables,domains,constraints) | |
40 | s = Solver().solve(r,verbose) | |
41 | print 'Number of solutions:',len(s) | |
42 | ||
43 | if __name__ == '__main__': | |
44 | import profile | |
45 | profile.run('queens()','csp.prof') | |
46 | import pstats | |
47 | ||
48 | p = pstats.Stats('csp.prof') | |
49 | p.sort_stats('time','calls').print_stats(.25) | |
50 | p.sort_stats('cum','calls').print_stats(.25) | |
51 | p.strip_dirs().sort_stats('cum','calls').print_callers(.25) | |
52 | p.strip_dirs().sort_stats('cum','calls').print_callees() |
0 | from logilab.common.testlib import main | |
1 | ||
2 | if __name__ == '__main__': | |
3 | import sys, os | |
4 | main(os.path.dirname(sys.argv[0]) or '.') |
0 | """Unit testing for constraint propagation module""" | |
1 | ||
2 | # (c) 2000-2001 LOGILAB S.A. (Paris, FRANCE). | |
3 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
4 | # | |
5 | # This program is free software; you can redistribute it and/or modify it under | |
6 | # the terms of the GNU General Public License as published by the Free Software | |
7 | # Foundation; either version 2 of the License, or (at your option) any later | |
8 | # version. | |
9 | # | |
10 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
12 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
13 | # | |
14 | # You should have received a copy of the GNU General Public License along with | |
15 | # this program; if not, write to the Free Software Foundation, Inc., | |
16 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
17 | # USA. | |
18 | ||
19 | import unittest | |
20 | from logilab.constraint import fd | |
21 | from logilab.constraint import propagation | |
22 | from logilab.constraint import distributors | |
23 | ||
24 | ||
25 | class AbstractConstraintTC(unittest.TestCase): | |
26 | """override the following methods: | |
27 | * setUp to initialize variables | |
28 | * narrowingAssertions to check that narrowing was ok | |
29 | """ | |
30 | def setUp(self): | |
31 | self.relevant_variables = [] | |
32 | self.irrelevant_variable = 'tagada' | |
33 | self.constraint = None #AbstractConstraint(self.relevant_variables) | |
34 | self.domains = {} | |
35 | self.entailed_domains = {} | |
36 | #raise NotImplementedError | |
37 | ||
38 | def testRelevance(self): | |
39 | """tests that relevant variables are relevant""" | |
40 | for v in self.relevant_variables: | |
41 | self.assert_(self.constraint.isVariableRelevant(v)) | |
42 | self.failIf(self.constraint.isVariableRelevant(self.irrelevant_variable)) | |
43 | ||
44 | ||
45 | def testNarrowing(self): | |
46 | """tests that narrowing is performed correctly""" | |
47 | entailed = self.constraint.narrow(self.domains) | |
48 | self.narrowingAssertions() | |
49 | ||
50 | def testEntailment(self): | |
51 | """tests that narrowing is performed correctly""" | |
52 | entailed = self.constraint.narrow(self.entailed_domains) | |
53 | self.assert_(entailed) | |
54 | ||
55 | class AllDistinctTC(AbstractConstraintTC): | |
56 | def setUp(self): | |
57 | self.relevant_variables = ['x','y','z'] | |
58 | self.irrelevant_variable = 'tagada' | |
59 | self.constraint = fd.AllDistinct(self.relevant_variables) | |
60 | self.domains = {'x':fd.FiniteDomain((1,2)), | |
61 | 'y':fd.FiniteDomain((1,3)), | |
62 | 'z':fd.FiniteDomain((1,4)),} | |
63 | ||
64 | self.entailed_domains = {'x':fd.FiniteDomain((1,)), | |
65 | 'y':fd.FiniteDomain((1,2)), | |
66 | 'z':fd.FiniteDomain((1,2,3)),} | |
67 | ||
68 | def narrowingAssertions(self): | |
69 | vx = self.domains['x'].getValues() | |
70 | vy = self.domains['y'].getValues() | |
71 | vz = self.domains['z'].getValues() | |
72 | self.assert_( 1 in vx and 2 in vx) | |
73 | self.assert_( 1 in vy and 3 in vy) | |
74 | self.assert_( 1 in vz and 4 in vz) | |
75 | ||
76 | def testNarrowing2(self): | |
77 | domains = {'x':fd.FiniteDomain((1,2)), | |
78 | 'y':fd.FiniteDomain((1,)), | |
79 | 'z':fd.FiniteDomain((1,4)),} | |
80 | entailed = self.constraint.narrow(domains) | |
81 | vx = domains['x'].getValues() | |
82 | vy = domains['y'].getValues() | |
83 | vz = domains['z'].getValues() | |
84 | self.assert_(entailed) | |
85 | self.assert_(2 in vx) | |
86 | self.assert_(1 in vy) | |
87 | self.assert_(4 in vz) | |
88 | ||
89 | def testNarrowing3(self): | |
90 | domains = {'x':fd.FiniteDomain((1,)), | |
91 | 'y':fd.FiniteDomain((2,)), | |
92 | 'z':fd.FiniteDomain((1,2,3,4)),} | |
93 | entailed = self.constraint.narrow(domains) | |
94 | vx = domains['x'].getValues() | |
95 | vy = domains['y'].getValues() | |
96 | vz = domains['z'].getValues() | |
97 | self.assert_(not entailed) | |
98 | self.assert_(1 in vx, str(vx)) | |
99 | self.assert_(2 in vy, str(vy)) | |
100 | self.assert_(4 in vz and 3 in vz, str(vz)) | |
101 | ||
102 | def testNarrowing4(self): | |
103 | domains = {'x':fd.FiniteDomain((1,)), | |
104 | 'y':fd.FiniteDomain((2,)), | |
105 | 'z':fd.FiniteDomain((1,3,4)), | |
106 | 't':fd.FiniteDomain((2,5,4)), | |
107 | 'u':fd.FiniteDomain((1,2,4)), | |
108 | } | |
109 | constraint = fd.AllDistinct(domains.keys()) | |
110 | entailed = constraint.narrow(domains) | |
111 | vx = domains['x'].getValues() | |
112 | vy = domains['y'].getValues() | |
113 | vz = domains['z'].getValues() | |
114 | vt = domains['t'].getValues() | |
115 | vu = domains['u'].getValues() | |
116 | self.failUnless(entailed) | |
117 | self.assertEquals([1], vx) | |
118 | self.assertEquals([2], vy) | |
119 | self.assertEquals([3], vz) | |
120 | self.assertEquals([5], vt) | |
121 | self.assertEquals([4], vu) | |
122 | ||
123 | def testFailure1(self): | |
124 | domains = {'x':fd.FiniteDomain((1,2)), | |
125 | 'y':fd.FiniteDomain((2,1)), | |
126 | 'z':fd.FiniteDomain((1,2)),} | |
127 | exception = 0 | |
128 | try: | |
129 | entailed = self.constraint.narrow(domains) | |
130 | except propagation.ConsistencyFailure: | |
131 | exception = 1 | |
132 | assert exception | |
133 | ||
134 | def testFailure2(self): | |
135 | domains = {'x':fd.FiniteDomain((1,)), | |
136 | 'y':fd.FiniteDomain((2,)), | |
137 | 'z':fd.FiniteDomain((1,2)),} | |
138 | exception = 0 | |
139 | try: | |
140 | entailed = self.constraint.narrow(domains) | |
141 | except propagation.ConsistencyFailure: | |
142 | exception = 1 | |
143 | assert exception | |
144 | ||
145 | def testFailure3(self): | |
146 | domains = {'x':fd.FiniteDomain((1,)), | |
147 | 'y':fd.FiniteDomain((1,)), | |
148 | 'z':fd.FiniteDomain((2,3)),} | |
149 | exception = 0 | |
150 | try: | |
151 | entailed = self.constraint.narrow(domains) | |
152 | except propagation.ConsistencyFailure: | |
153 | exception = 1 | |
154 | assert exception | |
155 | ||
156 | ||
157 | ||
158 | class UnaryMathConstrTC(AbstractConstraintTC): | |
159 | def setUp(self): | |
160 | self.relevant_variables = ['x'] | |
161 | self.irrelevant_variable = 'tagada' | |
162 | self.constraint = fd.make_expression(self.relevant_variables, | |
163 | 'x==2') | |
164 | self.domains = {'x':fd.FiniteDomain(range(4))} | |
165 | self.entailed_domains = {'x':fd.FiniteDomain([2])} | |
166 | def narrowingAssertions(self): | |
167 | v = list(self.domains['x'].getValues()) | |
168 | v.sort() | |
169 | assert v == [2],str(v) | |
170 | ||
171 | class BinaryMathConstrTC(AbstractConstraintTC): | |
172 | def setUp(self): | |
173 | self.relevant_variables = ['x','y'] | |
174 | self.irrelevant_variable = 'tagada' | |
175 | self.constraint = fd.make_expression(self.relevant_variables, | |
176 | 'x+y==2') | |
177 | self.domains = {'x':fd.FiniteDomain(range(4)), | |
178 | 'y':fd.FiniteDomain(range(2))} | |
179 | self.entailed_domains = {'x':fd.FiniteDomain([2]), | |
180 | 'y':fd.FiniteDomain([0])} | |
181 | def narrowingAssertions(self): | |
182 | v = list(self.domains['x'].getValues()) | |
183 | v.sort() | |
184 | assert v == [1,2],str(v) | |
185 | v = list(self.domains['y'].getValues()) | |
186 | v.sort() | |
187 | assert v == [0,1],str(v) | |
188 | ||
189 | class TernaryMathConstrTC(AbstractConstraintTC): | |
190 | def setUp(self): | |
191 | self.relevant_variables = ['x','y','z'] | |
192 | self.irrelevant_variable = 'tagada' | |
193 | self.constraint = fd.make_expression(self.relevant_variables, | |
194 | 'x+y==2 and z>1') | |
195 | self.domains = {'x':fd.FiniteDomain(range(4)), | |
196 | 'y':fd.FiniteDomain(range(3)), | |
197 | 'z':fd.FiniteDomain(range(4))} | |
198 | self.entailed_domains = {'x':fd.FiniteDomain([2]), | |
199 | 'y':fd.FiniteDomain([0]), | |
200 | 'z':fd.FiniteDomain([2,3]),} | |
201 | ||
202 | ||
203 | def narrowingAssertions(self): | |
204 | v = list(self.domains['x'].getValues()) | |
205 | v.sort() | |
206 | assert v == [0,1,2],str(v) | |
207 | v = list(self.domains['y'].getValues()) | |
208 | v.sort() | |
209 | assert v == [0,1,2],str(v) | |
210 | v = list(self.domains['z'].getValues()) | |
211 | v.sort() | |
212 | assert v == [2,3],str(v) | |
213 | ||
214 | class AbstractBasicConstraintTC(unittest.TestCase): | |
215 | """override the following methods: | |
216 | * setUp to initialize variables | |
217 | * narrowingAssertions to check that narrowing was ok | |
218 | """ | |
219 | def setUp(self): | |
220 | self.constraint = None #AbstractConstraint(self.relevant_variables) | |
221 | self.domains = {} | |
222 | self.entailed_domains = {} | |
223 | #raise NotImplementedError | |
224 | ||
225 | def testRelevance(self): | |
226 | """tests that relevant variables are relevant""" | |
227 | assert self.constraint.isVariableRelevant('x') | |
228 | assert not self.constraint.isVariableRelevant('tagada') | |
229 | ||
230 | def testGetVariable(self): | |
231 | """test that getVariable returns the right variable""" | |
232 | assert self.constraint.getVariable() == 'x' | |
233 | ||
234 | def testNarrowing(self): | |
235 | """tests that narrowing is performed correctly""" | |
236 | entailed = self.constraint.narrow(self.domains) | |
237 | self.narrowingAssertions() | |
238 | ||
239 | def testEntailment(self): | |
240 | """tests that narrowing is performed correctly""" | |
241 | entailed = self.constraint.narrow(self.domains) | |
242 | assert entailed | |
243 | ||
244 | ||
245 | class EqualsConstrTC(AbstractBasicConstraintTC): | |
246 | def setUp(self): | |
247 | self.constraint = fd.Equals('x',1) | |
248 | self.domains = {'x':fd.FiniteDomain(range(3))} | |
249 | ||
250 | def narrowingAssertions(self): | |
251 | v = list(self.domains['x'].getValues()) | |
252 | v.sort() | |
253 | assert v == [1],str(v) | |
254 | ||
255 | class NotEqualsConstrTC(AbstractBasicConstraintTC): | |
256 | def setUp(self): | |
257 | self.constraint = fd.NotEquals('x',1) | |
258 | self.domains = {'x':fd.FiniteDomain(range(3))} | |
259 | ||
260 | def narrowingAssertions(self): | |
261 | v = list(self.domains['x'].getValues()) | |
262 | v.sort() | |
263 | assert v == [0,2],str(v) | |
264 | ||
265 | class LesserThanConstrTC(AbstractBasicConstraintTC): | |
266 | def setUp(self): | |
267 | self.constraint = fd.LesserThan('x',1) | |
268 | self.domains = {'x':fd.FiniteDomain(range(3))} | |
269 | ||
270 | def narrowingAssertions(self): | |
271 | v = list(self.domains['x'].getValues()) | |
272 | v.sort() | |
273 | assert v == [0],str(v) | |
274 | ||
275 | class LesserOrEqualConstrTC(AbstractBasicConstraintTC): | |
276 | def setUp(self): | |
277 | self.constraint = fd.LesserOrEqual('x',1) | |
278 | self.domains = {'x':fd.FiniteDomain(range(3))} | |
279 | ||
280 | def narrowingAssertions(self): | |
281 | v = list(self.domains['x'].getValues()) | |
282 | v.sort() | |
283 | assert v == [0,1],str(v) | |
284 | ||
285 | class GreaterThanConstrTC(AbstractBasicConstraintTC): | |
286 | def setUp(self): | |
287 | self.constraint = fd.GreaterThan('x',1) | |
288 | self.domains = {'x':fd.FiniteDomain(range(3))} | |
289 | ||
290 | def narrowingAssertions(self): | |
291 | v = list(self.domains['x'].getValues()) | |
292 | v.sort() | |
293 | assert v == [2],str(v) | |
294 | ||
295 | class GreaterOrEqualConstrTC(AbstractBasicConstraintTC): | |
296 | def setUp(self): | |
297 | self.constraint = fd.GreaterOrEqual('x',1) | |
298 | self.domains = {'x':fd.FiniteDomain(range(3))} | |
299 | ||
300 | def narrowingAssertions(self): | |
301 | v = list(self.domains['x'].getValues()) | |
302 | v.sort() | |
303 | assert v == [1,2],str(v) | |
304 | ||
305 | ||
306 | ||
307 | def get_all_cases(module): | |
308 | import types | |
309 | all_cases = [] | |
310 | for name in dir(module): | |
311 | obj = getattr(module, name) | |
312 | if type(obj) in (types.ClassType, types.TypeType) and \ | |
313 | issubclass(obj, unittest.TestCase) and \ | |
314 | not name.startswith('Abstract'): | |
315 | all_cases.append(obj) | |
316 | all_cases.sort() | |
317 | return all_cases | |
318 | ||
319 | def suite(cases = None): | |
320 | import test_constraints | |
321 | cases = cases or get_all_cases(test_constraints) | |
322 | loader = unittest.defaultTestLoader | |
323 | loader.testMethodPrefix = 'test' | |
324 | loader.sortTestMethodsUsing = None # disable sorting | |
325 | suites = [loader.loadTestsFromTestCase(tc) for tc in cases] | |
326 | return unittest.TestSuite(suites) | |
327 | ||
328 | ||
329 | if __name__ == '__main__': | |
330 | unittest.main(defaultTest="suite") |
0 | """Unit testing for constraint propagation module""" | |
1 | ||
2 | # (c) 2000-2001 LOGILAB S.A. (Paris, FRANCE). | |
3 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
4 | # | |
5 | # This program is free software; you can redistribute it and/or modify it under | |
6 | # the terms of the GNU General Public License as published by the Free Software | |
7 | # Foundation; either version 2 of the License, or (at your option) any later | |
8 | # version. | |
9 | # | |
10 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
12 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
13 | # | |
14 | # You should have received a copy of the GNU General Public License along with | |
15 | # this program; if not, write to the Free Software Foundation, Inc., | |
16 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
17 | # USA. | |
18 | ||
19 | import unittest | |
20 | from logilab.constraint import distributors,fd | |
21 | ||
22 | ||
23 | class AbstractDistributorTC(unittest.TestCase): | |
24 | """override the following methods: | |
25 | * buildDistributor to create the distributor | |
26 | * distributionAssertions to check that distribution was OK | |
27 | """ | |
28 | def setUp(self): | |
29 | self.variables = ('v1','v2','v3') | |
30 | self.domains1 = {'v1':fd.FiniteDomain([1]), | |
31 | 'v2':fd.FiniteDomain([2,3]), | |
32 | 'v3':fd.FiniteDomain([4,5,6,7]), | |
33 | } | |
34 | self.domains2 = {'v1':fd.FiniteDomain([1]), | |
35 | 'v2':fd.FiniteDomain([2,3,4,5,6]), | |
36 | 'v3':fd.FiniteDomain([7,8,9,10,11,12,13]), | |
37 | } | |
38 | self.distributor = self.buildDistributor() | |
39 | ||
40 | def buildDistributor(self): | |
41 | """returns a distributor""" | |
42 | raise NotImplementedError | |
43 | ||
44 | def distributionAssertions(self): | |
45 | """checks the distribution""" | |
46 | raise NotImplementedError | |
47 | ||
48 | def testFindSmallestDomain(self): | |
49 | """tests that the smallest domain is indeed the smallest one | |
50 | with at least 2 values inside""" | |
51 | dist = self.buildDistributor() | |
52 | self.assertEquals('v2', dist.findSmallestDomain(self.domains1)) | |
53 | self.assertEquals('v2', dist.findSmallestDomain(self.domains2)) | |
54 | ||
55 | def testFindLargestDomain(self): | |
56 | """tests that the largest domain is indeed the largest one""" | |
57 | dist = self.buildDistributor() | |
58 | self.assertEquals('v3', dist.findLargestDomain(self.domains1)) | |
59 | self.assertEquals('v3', dist.findLargestDomain(self.domains2)) | |
60 | ||
61 | def testSingleValueDomainNotDistributed(self): | |
62 | """tests that a domain of size 1 is not distributed""" | |
63 | for initial_domain in (self.domains1,self.domains2): | |
64 | distributed_domains = self.distributor.distribute(self.domains1) | |
65 | for d in distributed_domains: | |
66 | assert d['v1'].size() == initial_domain['v1'].size() | |
67 | ||
68 | ||
69 | def testDistribution(self): | |
70 | """tests that the right domain is correctly distributed""" | |
71 | for initial_domain in (self.domains1,self.domains2): | |
72 | distributed_domains = self.distributor.distribute(initial_domain) | |
73 | self.distributionAssertions(initial_domain,distributed_domains) | |
74 | ||
75 | class NaiveDistributorTC(AbstractDistributorTC): | |
76 | def buildDistributor(self): | |
77 | return distributors.NaiveDistributor() | |
78 | def distributionAssertions(self,initial,distributed): | |
79 | assert len(distributed) == 2 | |
80 | for d in distributed: | |
81 | for v in ('v1','v3'): | |
82 | assert d[v].getValues() == initial[v].getValues() | |
83 | assert distributed[0]['v2'].size() == 1 | |
84 | assert distributed[1]['v2'].size() == initial['v2'].size()-1 | |
85 | ||
86 | class RandomizingDistributorTC(NaiveDistributorTC): | |
87 | def buildDistributor(self): | |
88 | return distributors.RandomizingDistributor() | |
89 | ||
90 | ||
91 | class DichotomyDistributorTC(AbstractDistributorTC): | |
92 | def buildDistributor(self): | |
93 | return distributors.DichotomyDistributor() | |
94 | def distributionAssertions(self,initial,distributed): | |
95 | assert len(distributed) == 2 | |
96 | for d in distributed: | |
97 | for v in ('v1','v3'): | |
98 | assert d[v].getValues() == initial[v].getValues() | |
99 | assert distributed[0]['v2'].size() + distributed[1]['v2'].size() == initial['v2'].size() | |
100 | ||
101 | class SplitDistributorTC(AbstractDistributorTC): | |
102 | def buildDistributor(self): | |
103 | return distributors.SplitDistributor(4) | |
104 | def distributionAssertions(self,initial,distributed): | |
105 | assert len(distributed) == min(4,initial['v2'].size()) | |
106 | for d in distributed: | |
107 | for v in ('v1','v3'): | |
108 | assert d[v].getValues() == initial[v].getValues() | |
109 | sizes = [d['v2'].size() for d in distributed] | |
110 | import operator | |
111 | tot_size = reduce(operator.add,sizes) | |
112 | assert tot_size == initial['v2'].size() | |
113 | ||
114 | class EnumeratorDistributorTC(AbstractDistributorTC): | |
115 | def buildDistributor(self): | |
116 | return distributors.EnumeratorDistributor() | |
117 | def distributionAssertions(self,initial,distributed): | |
118 | assert len(distributed) == initial['v2'].size() | |
119 | for d in distributed: | |
120 | for v in ('v1','v3'): | |
121 | assert d[v].getValues() == initial[v].getValues() | |
122 | assert d['v2'].size() == 1 | |
123 | ||
124 | ||
125 | def get_all_cases(module): | |
126 | import types | |
127 | all_cases = [] | |
128 | for name in dir(module): | |
129 | obj = getattr(module, name) | |
130 | if type(obj) in (types.ClassType, types.TypeType) and \ | |
131 | issubclass(obj, unittest.TestCase) and \ | |
132 | not name.startswith('Abstract'): | |
133 | all_cases.append(obj) | |
134 | all_cases.sort() | |
135 | return all_cases | |
136 | ||
137 | def suite(cases = None): | |
138 | import test_distributors | |
139 | cases = cases or get_all_cases(test_distributors) | |
140 | loader = unittest.defaultTestLoader | |
141 | loader.testMethodPrefix = 'test' | |
142 | loader.sortTestMethodsUsing = None # disable sorting | |
143 | suites = [loader.loadTestsFromTestCase(tc) for tc in cases] | |
144 | return unittest.TestSuite(suites) | |
145 | ||
146 | ||
147 | if __name__ == '__main__': | |
148 | unittest.main(defaultTest="suite") |
0 | """Unit testing for constraint propagation module""" | |
1 | ||
2 | # (c) 2000-2001 LOGILAB S.A. (Paris, FRANCE). | |
3 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
4 | # | |
5 | # This program is free software; you can redistribute it and/or modify it under | |
6 | # the terms of the GNU General Public License as published by the Free Software | |
7 | # Foundation; either version 2 of the License, or (at your option) any later | |
8 | # version. | |
9 | # | |
10 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
12 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
13 | # | |
14 | # You should have received a copy of the GNU General Public License along with | |
15 | # this program; if not, write to the Free Software Foundation, Inc., | |
16 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
17 | # USA. | |
18 | ||
19 | import unittest | |
20 | from logilab.constraint import fd | |
21 | from logilab.constraint import propagation | |
22 | from logilab.constraint import distributors | |
23 | ||
24 | class AbstractDomainTC(unittest.TestCase): | |
25 | """override the following methods: | |
26 | * setUp to initialize variables | |
27 | """ | |
28 | def setUp(self): | |
29 | self.values = [] | |
30 | self.domain = None | |
31 | raise NotImplementedError | |
32 | ||
33 | def testGetValues(self): | |
34 | """tests the getValues() method""" | |
35 | v1 = list(self.domain.getValues()) | |
36 | v1.sort() | |
37 | v2 = self.values[:] | |
38 | v2.sort() | |
39 | assert v1 == v2 | |
40 | ||
41 | def testSize(self): | |
42 | """tests the size() method""" | |
43 | assert self.domain.size() == len(self.values) | |
44 | self.domain.removeValue(self.values[0]) | |
45 | assert self.domain.size() == len(self.values) - 1 | |
46 | ||
47 | def testRemove(self): | |
48 | """tests the removeValue() method""" | |
49 | self.domain.removeValue(self.values[0]) | |
50 | assert self.values[0] not in self.domain.getValues() | |
51 | ||
52 | def testEmptyDomain(self): | |
53 | """tests that a ConsistencyFailure exception is raised | |
54 | when the last value of a domain is removed""" | |
55 | exception = 0 | |
56 | for v in self.values[1:]: | |
57 | self.domain.removeValue(v) | |
58 | try: | |
59 | self.domain.removeValue(self.values[0]) | |
60 | except propagation.ConsistencyFailure: | |
61 | exception = 1 | |
62 | assert exception | |
63 | ||
64 | class SuiteDomainTC(AbstractDomainTC): | |
65 | def setUp(self): | |
66 | self.values = range(3) | |
67 | self.domain = fd.FiniteDomain(self.values) | |
68 | ||
69 | ||
70 | ||
71 | def get_all_cases(module): | |
72 | import types | |
73 | all_cases = [] | |
74 | for name in dir(module): | |
75 | obj = getattr(module, name) | |
76 | if type(obj) in (types.ClassType, types.TypeType) and \ | |
77 | issubclass(obj, unittest.TestCase) and \ | |
78 | not name.startswith('Abstract'): | |
79 | all_cases.append(obj) | |
80 | all_cases.sort() | |
81 | return all_cases | |
82 | ||
83 | def suite(cases = None): | |
84 | import test_domains | |
85 | cases = cases or get_all_cases(test_domains) | |
86 | loader = unittest.defaultTestLoader | |
87 | loader.testMethodPrefix = 'test' | |
88 | loader.sortTestMethodsUsing = None # disable sorting | |
89 | suites = [loader.loadTestsFromTestCase(tc) for tc in cases] | |
90 | return unittest.TestSuite(suites) | |
91 | ||
92 | ||
93 | if __name__ == '__main__': | |
94 | unittest.main(defaultTest="suite") |
0 | """Unit testing for constraint propagation module""" | |
1 | ||
2 | # (c) 2005 LOGILAB S.A. (Paris, FRANCE). | |
3 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
4 | # | |
5 | # This program is free software; you can redistribute it and/or modify it under | |
6 | # the terms of the GNU General Public License as published by the Free Software | |
7 | # Foundation; either version 2 of the License, or (at your option) any later | |
8 | # version. | |
9 | # | |
10 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
12 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
13 | # | |
14 | # You should have received a copy of the GNU General Public License along with | |
15 | # this program; if not, write to the Free Software Foundation, Inc., | |
16 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
17 | # USA. | |
18 | ||
19 | import unittest | |
20 | from logilab.constraint.fi import * | |
21 | from logilab.constraint import Repository, Solver | |
22 | ||
23 | class FiniteIntervalTC(unittest.TestCase): | |
24 | ||
25 | def setUp(self): | |
26 | self.dom1 = FiniteIntervalDomain(0, 10, 2, 4, 1) | |
27 | self.dom2 = FiniteIntervalDomain(2, 5, 3) | |
28 | self.dom3 = FiniteIntervalDomain(2, 6, 2, 3, .5) | |
29 | ||
30 | def testConstructorExceptions(self): | |
31 | try: | |
32 | d = FiniteIntervalDomain(5,1,3) | |
33 | self.fail("Should have an assertion error here") | |
34 | except AssertionError: | |
35 | pass | |
36 | try: | |
37 | d = FiniteIntervalDomain(1,5,3,1) | |
38 | self.fail("Should have an assertion error here") | |
39 | except AssertionError: | |
40 | pass | |
41 | try: | |
42 | d = FiniteIntervalDomain(1,3,-2) | |
43 | self.fail("Should have an assertion error here") | |
44 | except AssertionError: | |
45 | pass | |
46 | try: | |
47 | d = FiniteIntervalDomain(1,3,5) | |
48 | self.fail("Should have an assertion error here") | |
49 | except AssertionError: | |
50 | pass | |
51 | ||
52 | def test_ConstructorDefaults(self): | |
53 | d = FiniteIntervalDomain(1,3,2) | |
54 | self.assertEquals(d._max_length, 2) | |
55 | self.assertEquals(d._resolution, 1) | |
56 | ||
57 | def test_ConstructorAjustMaxLength(self): | |
58 | d = FiniteIntervalDomain(0, 5, 2, 8) | |
59 | self.assertEquals(d._max_length, 5) | |
60 | ||
61 | def test_getValues(self): | |
62 | self.assertEquals(len(self.dom1.getValues()), self.dom1.size()) | |
63 | self.assertEquals(len(self.dom2.getValues()), self.dom2.size()) | |
64 | self.assertEquals(len(self.dom3.getValues()), self.dom3.size()) | |
65 | ||
66 | def test_Size(self): | |
67 | self.assertEquals(self.dom1.size(), 9 + 8 + 7) | |
68 | self.assertEquals(self.dom2.size(), 1) | |
69 | self.assertEquals(self.dom3.size(), 12) | |
70 | ||
71 | def test_overlap(self): | |
72 | self.failUnless(self.dom1.overlap(self.dom2)) | |
73 | self.failUnless(self.dom1.overlap(FiniteIntervalDomain(-5, 5, 1))) | |
74 | self.failUnless(self.dom1.overlap(FiniteIntervalDomain( 5, 15, 1))) | |
75 | self.failUnless(self.dom1.overlap(FiniteIntervalDomain(-5, 15, 1))) | |
76 | self.failIf(self.dom1.overlap(FiniteIntervalDomain(-15, 0, 1))) | |
77 | self.failIf(self.dom1.overlap(FiniteIntervalDomain(10, 25, 1))) | |
78 | ||
79 | def test_SetLow(self): | |
80 | self.dom1.setLowestMin(2) | |
81 | self.assertEquals(self.dom1.lowestMin, 2) | |
82 | self.assertRaises(ConsistencyFailure, self.dom1.setLowestMin, 10) | |
83 | ||
84 | def test_SetHigh(self): | |
85 | self.dom1.setHighestMax(9) | |
86 | self.assertEquals(self.dom1.highestMax, 9) | |
87 | self.assertRaises(ConsistencyFailure, self.dom1.setHighestMax, -10) | |
88 | ||
89 | def test_SetMinLength(self): | |
90 | self.dom1.setMinLength(3) | |
91 | self.assertEquals(self.dom1._min_length, 3) | |
92 | self.dom1.setMinLength(4) | |
93 | self.assertEquals(self.dom1._min_length, 4) | |
94 | self.assertRaises(ConsistencyFailure, self.dom2.setMinLength, 5) | |
95 | ||
96 | def test_SetMaxLength(self): | |
97 | self.dom1.setMaxLength(3) | |
98 | self.assertEquals(self.dom1._max_length, 3) | |
99 | self.dom1.setMaxLength(2) | |
100 | self.assertEquals(self.dom1._max_length, 2) | |
101 | self.assertRaises(ConsistencyFailure, self.dom2.setMaxLength, 1) | |
102 | ||
103 | def test_FailureIfSizeEqualsZero(self): | |
104 | self.assertRaises(ConsistencyFailure, self.dom2.setHighestMax, 4) | |
105 | ||
106 | def test_LatestStart(self): | |
107 | self.assertEquals(self.dom1.highestMin, 8) | |
108 | self.assertEquals(self.dom2.highestMin, 2) | |
109 | self.assertEquals(self.dom3.highestMin, 4) | |
110 | ||
111 | def test_EarliestEnd(self): | |
112 | self.assertEquals(self.dom1.lowestMax, 2) | |
113 | self.assertEquals(self.dom2.lowestMax, 5) | |
114 | self.assertEquals(self.dom3.lowestMax, 4) | |
115 | ||
116 | # FIXME check all possible cases are handled | |
117 | class ConstraintOverlapTC(unittest.TestCase): | |
118 | def setUp(self): | |
119 | self.d1 = FiniteIntervalDomain(0, 5, 2) | |
120 | self.d2 = FiniteIntervalDomain(0, 5, 3) | |
121 | self.d3 = FiniteIntervalDomain(1, 5, 3) | |
122 | self.d4 = FiniteIntervalDomain(0, 4, 2) | |
123 | self.d5 = FiniteIntervalDomain(1, 4, 2) | |
124 | self.d6 = FiniteIntervalDomain(4, 7, 2) | |
125 | self.d7 = FiniteIntervalDomain(0, 5, 4) | |
126 | self.d8 = FiniteIntervalDomain(3, 8, 4) | |
127 | self.d9 = FiniteIntervalDomain(3, 8, 1) | |
128 | self.d10 = FiniteIntervalDomain(0, 6, 2) | |
129 | self.d11 = FiniteIntervalDomain(1, 5, 2) | |
130 | self.d12 = FiniteIntervalDomain(0, 6, 3) | |
131 | self.d13 = FiniteIntervalDomain(1, 6, 3) | |
132 | self.d14 = FiniteIntervalDomain(0, 6, 3) | |
133 | self.d15 = FiniteIntervalDomain(0, 2, 2) | |
134 | self.d16 = FiniteIntervalDomain(0, 2, 2) | |
135 | self.domains = {'v1': self.d1, | |
136 | 'v2': self.d2, | |
137 | 'v3': self.d3, | |
138 | 'v4': self.d4, | |
139 | 'v5': self.d5, | |
140 | 'v6': self.d6, | |
141 | 'v7': self.d7, | |
142 | 'v8': self.d8, | |
143 | 'v9': self.d9, | |
144 | 'v10': self.d10, | |
145 | 'v11': self.d11, | |
146 | 'v12': self.d12, | |
147 | 'v13': self.d13, | |
148 | 'v14': self.d14, | |
149 | 'v15': self.d15, | |
150 | 'v16': self.d16, | |
151 | } | |
152 | ||
153 | # consistency failure | |
154 | ||
155 | def test_NoOverlap_ConsistencyFailure(self): | |
156 | c = NoOverlap('v2', 'v3') | |
157 | self.assertRaises(ConsistencyFailure, c.narrow, self.domains) | |
158 | c = NoOverlap('v3', 'v2') | |
159 | self.assertRaises(ConsistencyFailure, c.narrow, self.domains) | |
160 | ||
161 | def test_NoOverlap_ConsistencyFailure1(self): | |
162 | c = NoOverlap('v5', 'v2') | |
163 | self.assertRaises(ConsistencyFailure, c.narrow, self.domains) | |
164 | c = NoOverlap('v2', 'v5') | |
165 | self.assertRaises(ConsistencyFailure, c.narrow, self.domains) | |
166 | ||
167 | def test_NoOverlap_ConsistencyFailure2(self): | |
168 | c = NoOverlap('v15', 'v16') | |
169 | self.assertRaises(ConsistencyFailure, c.narrow, self.domains) | |
170 | c = NoOverlap('v16', 'v15') | |
171 | self.assertRaises(ConsistencyFailure, c.narrow, self.domains) | |
172 | ||
173 | # entailed | |
174 | ||
175 | def test_NoOverlap_Entailed(self): | |
176 | c = NoOverlap('v6', 'v4') | |
177 | self.assertEquals(c.narrow(self.domains), 1) | |
178 | c = NoOverlap('v4', 'v6') | |
179 | self.assertEquals(c.narrow(self.domains), 1) | |
180 | ||
181 | def test_NoOverlap_Entailed1(self): | |
182 | c = NoOverlap('v1', 'v3') | |
183 | self.assertEquals(c.narrow(self.domains), 1) | |
184 | c = NoOverlap('v3', 'v1') | |
185 | self.assertEquals(c.narrow(self.domains), 1) | |
186 | ||
187 | def test_NoOverlap_Entailed2(self): | |
188 | c = NoOverlap('v7', 'v8') | |
189 | self.assertEquals(c.narrow(self.domains), 1) | |
190 | self.assertEquals(self.d7, FiniteIntervalDomain(0,4,4)) | |
191 | self.assertEquals(self.d8, FiniteIntervalDomain(4,8,4)) | |
192 | ||
193 | def test_NoOverlap_Entailed2bis(self): | |
194 | c = NoOverlap('v8', 'v7') | |
195 | self.assertEquals(c.narrow(self.domains), 1) | |
196 | self.assertEquals(self.d7, FiniteIntervalDomain(0,4,4)) | |
197 | self.assertEquals(self.d8, FiniteIntervalDomain(4,8,4)) | |
198 | ||
199 | def test_NoOverlap_Entailed3(self): | |
200 | c = NoOverlap('v7', 'v10') | |
201 | self.assertEquals(c.narrow(self.domains), 1) | |
202 | self.assertEquals(self.d7, FiniteIntervalDomain(0,4,4)) | |
203 | self.assertEquals(self.d10, FiniteIntervalDomain(4,6,2)) | |
204 | ||
205 | def test_NoOverlap_Entailed3bis(self): | |
206 | c = NoOverlap('v10', 'v7') | |
207 | self.assertEquals(c.narrow(self.domains), 1) | |
208 | self.assertEquals(self.d7, FiniteIntervalDomain(0,4,4)) | |
209 | self.assertEquals(self.d10, FiniteIntervalDomain(4,6,2)) | |
210 | ||
211 | def test_NoOverlap_Entailed4(self): | |
212 | c = NoOverlap('v12', 'v13') | |
213 | self.assertEquals(c.narrow(self.domains), 1) | |
214 | self.assertEquals(self.d12, FiniteIntervalDomain(0,3,3)) | |
215 | self.assertEquals(self.d13, FiniteIntervalDomain(3,6,3)) | |
216 | ||
217 | def test_NoOverlap_Entailed4bis(self): | |
218 | c = NoOverlap('v13', 'v12') | |
219 | self.assertEquals(c.narrow(self.domains), 1) | |
220 | self.assertEquals(self.d12, FiniteIntervalDomain(0,3,3)) | |
221 | self.assertEquals(self.d13, FiniteIntervalDomain(3,6,3)) | |
222 | ||
223 | # not entailed | |
224 | ||
225 | def test_NoOverlap_NotEntailed(self): | |
226 | c = NoOverlap('v4', 'v1') | |
227 | self.assertEquals(c.narrow(self.domains), 0) | |
228 | ||
229 | def test_NoOverlap_NotEntailed2(self): | |
230 | c = NoOverlap('v8', 'v9') | |
231 | self.assertEquals(c.narrow(self.domains), 0) | |
232 | c = NoOverlap('v9', 'v8') | |
233 | self.assertEquals(c.narrow(self.domains), 0) | |
234 | ||
235 | def test_NoOverlap_NotEntailed3(self): | |
236 | c = NoOverlap('v11', 'v12') | |
237 | self.assertEquals(c.narrow(self.domains), 0) | |
238 | c = NoOverlap('v12', 'v11') | |
239 | self.assertEquals(c.narrow(self.domains), 0) | |
240 | ||
241 | def test_NoOverlap_NotEntailed4(self): | |
242 | c = NoOverlap('v12', 'v14') | |
243 | self.assertEquals(c.narrow(self.domains), 0) | |
244 | c = NoOverlap('v14', 'v12') | |
245 | self.assertEquals(c.narrow(self.domains), 0) | |
246 | ||
247 | ||
248 | def test_equality(self): | |
249 | c1 = NoOverlap('v12', 'v14') | |
250 | c2 = NoOverlap('v14', 'v12') | |
251 | c3 = NoOverlap('v15', 'v12') | |
252 | self.assertEquals(c1, c2) | |
253 | self.assertNotEquals(c1, c3) | |
254 | self.assertNotEquals(c2, c3) | |
255 | self.assertEquals(c3, c3) | |
256 | ||
257 | def test_hash(self): | |
258 | c1 = NoOverlap('v12', 'v14') | |
259 | c2 = NoOverlap('v14', 'v12') | |
260 | c3 = NoOverlap('v15', 'v12') | |
261 | d = {c1 : 'hello', c2 : 'hello', c3 : 'hello'} | |
262 | self.assertEquals(len(d), 2) | |
263 | ||
264 | ||
265 | class ConstraintTC(unittest.TestCase): | |
266 | def setUp(self): | |
267 | self.d1 = FiniteIntervalDomain(5, 10, 1, 1) | |
268 | self.d2 = FiniteIntervalDomain(2, 7, 1, 1) | |
269 | self.d3 = FiniteIntervalDomain(8, 10, 1, 1) | |
270 | self.d4 = FiniteIntervalDomain(3, 10, 5, 6) | |
271 | self.d5 = FiniteIntervalDomain(4, 10, 5) | |
272 | self.d6 = FiniteIntervalDomain(0, 3, 2) | |
273 | self.d7 = FiniteIntervalDomain(0, 5, 4) | |
274 | self.d8 = FiniteIntervalDomain(3, 8, 4) | |
275 | self.d9 = FiniteIntervalDomain(3, 8, 1) | |
276 | self.d10 = FiniteIntervalDomain(0, 6, 2) | |
277 | self.domains = {'v1': self.d1, | |
278 | 'v2': self.d2, | |
279 | 'v3': self.d3, | |
280 | 'v4': self.d4, | |
281 | 'v5': self.d5, | |
282 | 'v6': self.d6, | |
283 | 'v7': self.d7, | |
284 | 'v8': self.d8, | |
285 | 'v9': self.d9, | |
286 | 'v10': self.d10, | |
287 | } | |
288 | ## | |
289 | ## StartsBeforeStart | |
290 | ## | |
291 | def test_StartsBeforeStart_NotEntailed(self): | |
292 | c = StartsBeforeStart('v2','v1') | |
293 | ret = c.narrow(self.domains) | |
294 | self.assertEquals(ret, 0) | |
295 | ||
296 | def test_StartsBeforeStart_ConsistencyFailure(self): | |
297 | c = StartsBeforeStart('v3','v2') | |
298 | self.assertRaises(ConsistencyFailure, c.narrow, self.domains) | |
299 | ||
300 | def test_StartsBeforeStart_Entailed(self): | |
301 | c = StartsBeforeStart('v2','v3') | |
302 | ret = c.narrow(self.domains) | |
303 | self.assertEquals(ret, 1) | |
304 | ||
305 | ## | |
306 | ## StartsBeforeEnd | |
307 | ## | |
308 | def test_StartsBeforeEnd_NotEntailed(self): | |
309 | c = StartsBeforeEnd('v2','v1') | |
310 | ret = c.narrow(self.domains) | |
311 | self.assertEquals(ret, 0) | |
312 | ||
313 | def test_StartsBeforeEnd_ConsistencyFailure(self): | |
314 | c = StartsBeforeEnd('v3','v2') | |
315 | self.assertRaises(ConsistencyFailure, c.narrow, self.domains) | |
316 | ||
317 | def test_StartsBeforeEnd_Entailed(self): | |
318 | c = StartsBeforeEnd('v4', 'v1') | |
319 | ret = c.narrow(self.domains) | |
320 | self.assertEquals(ret, 1) | |
321 | ||
322 | ## | |
323 | ## EndsBeforeStart | |
324 | ## | |
325 | def test_EndsBeforeStart_Entailed(self): | |
326 | c = EndsBeforeStart('v2','v3') | |
327 | ret = c.narrow(self.domains) | |
328 | self.assertEquals(ret, 1) | |
329 | ||
330 | def test_EndsBeforeStart_NotEntailed(self): | |
331 | c = EndsBeforeStart('v3','v1') | |
332 | ret = c.narrow(self.domains) | |
333 | self.assertEquals(ret, 0) | |
334 | ||
335 | def test_EndsBeforeStart_NotEntailed_withRemoval(self): | |
336 | c = EndsBeforeStart('v1','v3') | |
337 | ret = c.narrow(self.domains) | |
338 | self.assertEquals(ret, 0) | |
339 | self.assertEquals(self.d1.highestMax, self.d3.highestMin) | |
340 | ||
341 | def test_EndsBeforeStart_ConsistencyFailure(self): | |
342 | c = EndsBeforeStart('v3','v2') | |
343 | self.assertRaises(ConsistencyFailure, c.narrow, self.domains) | |
344 | ||
345 | ## | |
346 | ## EndsBeforeEnd | |
347 | ## | |
348 | def test_EndsBeforeEnd_Entailed(self): | |
349 | c = EndsBeforeEnd('v2','v3') | |
350 | ret = c.narrow(self.domains) | |
351 | self.assertEquals(ret, 1) | |
352 | ||
353 | def test_EndsBeforeEnd_NotEntailed(self): | |
354 | c = EndsBeforeEnd('v2','v1') | |
355 | ret = c.narrow(self.domains) | |
356 | self.assertEquals(ret, 0) | |
357 | self.assertEquals(self.d2.highestMax, 7) | |
358 | ||
359 | def test_EndsBeforeEnd_NotEntailed_withRemoval(self): | |
360 | c = EndsBeforeEnd('v1','v2') | |
361 | ret = c.narrow(self.domains) | |
362 | self.assertEquals(ret, 0) | |
363 | self.assertEquals(self.d1.highestMax, self.d2.highestMax) | |
364 | ||
365 | def test_EndsBeforeEnd_ConsistencyFailure(self): | |
366 | c = EndsBeforeEnd('v3','v2') | |
367 | self.assertRaises(ConsistencyFailure, c.narrow, self.domains) | |
368 | ||
369 | ## | |
370 | ## StartsAfterStart | |
371 | ## | |
372 | def test_StartsAfterStart_Entailed(self): | |
373 | c = StartsAfterStart('v3','v2') | |
374 | ret = c.narrow(self.domains) | |
375 | self.assertEquals(ret, 1) | |
376 | ||
377 | def test_StartsAfterStart_NotEntailed(self): | |
378 | c = StartsAfterStart('v1','v2') | |
379 | ret = c.narrow(self.domains) | |
380 | self.assertEquals(ret, 0) | |
381 | self.assertEquals(self.d1.lowestMin, 5) | |
382 | ||
383 | def test_StartsAfterStart_NotEntailed_withRemoval(self): | |
384 | c = StartsAfterStart('v2','v1') | |
385 | ret = c.narrow(self.domains) | |
386 | self.assertEquals(ret, 0) | |
387 | self.assertEquals(self.d2.lowestMin, self.d1.lowestMin) | |
388 | ||
389 | def test_StartsAfterStart_ConsistencyFailure(self): | |
390 | c = StartsAfterStart('v2','v3') | |
391 | self.assertRaises(ConsistencyFailure, c.narrow, self.domains) | |
392 | ||
393 | ## | |
394 | ## StartsAfterEnd | |
395 | ## | |
396 | def test_StartsAfterEnd_Entailed(self): | |
397 | c = StartsAfterEnd('v3','v2') | |
398 | ret = c.narrow(self.domains) | |
399 | self.assertEquals(ret, 1) | |
400 | ||
401 | def test_StartsAfterEnd_NotEntailed_withRemoval(self): | |
402 | c = StartsAfterEnd('v1','v4') | |
403 | ret = c.narrow(self.domains) | |
404 | self.assertEquals(ret, 0) | |
405 | self.assertEquals(self.d1.lowestMin, self.d4.lowestMax) | |
406 | ||
407 | def test_StartsAfterEnd_ConsistencyFailure(self): | |
408 | c = StartsAfterEnd('v2','v3') | |
409 | self.assertRaises(ConsistencyFailure, c.narrow, self.domains) | |
410 | ||
411 | ## | |
412 | ## EndsAfterStart | |
413 | ## | |
414 | def test_EndsAfterStart_Entailed(self): | |
415 | c = EndsAfterStart('v4','v2') | |
416 | ret = c.narrow(self.domains) | |
417 | self.assertEquals(ret, 1) | |
418 | ||
419 | def test_EndsAfterStart_NotEntailed(self): | |
420 | c = EndsAfterStart('v4','v3') | |
421 | ret = c.narrow(self.domains) | |
422 | self.assertEquals(ret, 0) | |
423 | ||
424 | def test_EndsAfterStart_ConsistencyFailure(self): | |
425 | c = EndsAfterStart('v2','v3') | |
426 | self.assertRaises(ConsistencyFailure, c.narrow, self.domains) | |
427 | ||
428 | ||
429 | ## | |
430 | ## EndsAfterEnd | |
431 | ## | |
432 | def test_EndsAfterEnd_Entailed(self): | |
433 | c = EndsAfterEnd('v4','v2') | |
434 | ret = c.narrow(self.domains) | |
435 | self.assertEquals(ret, 1) | |
436 | ||
437 | def test_EndsAfterEnd_NotEntailed(self): | |
438 | c = EndsAfterEnd('v4','v3') | |
439 | ret = c.narrow(self.domains) | |
440 | self.assertEquals(ret, 0) | |
441 | ||
442 | def test_EndsAfterEnd_ConsistencyFailure(self): | |
443 | c = EndsAfterEnd('v2','v3') | |
444 | self.assertRaises(ConsistencyFailure, c.narrow, self.domains) | |
445 | ||
446 | ||
447 | class DistributorTC(unittest.TestCase): | |
448 | def setUp(self): | |
449 | self.d = FiniteIntervalDistributor() | |
450 | ||
451 | def test_DistributeDifferentLengths(self): | |
452 | d1 = FiniteIntervalDomain(0, 5, 3, 5) | |
453 | d2 = FiniteIntervalDomain(0, 20, 1) | |
454 | domains = {'v1': d1, | |
455 | 'v2': d2, | |
456 | } | |
457 | dom1, dom2 = self.d.distribute(domains) | |
458 | self.failUnless(dom1['v2'] == dom2['v2']) | |
459 | self.failUnless(dom1['v2'] == d2) | |
460 | self.failIf(dom1['v1'] == d1) | |
461 | self.failIf(dom2['v1'] == d1) | |
462 | self.assertEquals(dom1['v1']._max_length, d1._min_length) | |
463 | self.assertEquals(dom2["v1"]._min_length, d1._min_length+d1._resolution) | |
464 | self.assertEquals(d1.size(), dom1['v1'].size() + dom2['v1'].size()) | |
465 | ||
466 | def test_DistributeSameLengths(self): | |
467 | d1 = FiniteIntervalDomain(lowestMin=0, highestMax=5, min_length=4) | |
468 | d2 = FiniteIntervalDomain(lowestMin=0, highestMax=20, min_length=1) | |
469 | domains = {'v1': d1, | |
470 | 'v2': d2, | |
471 | } | |
472 | dom1, dom2 = self.d.distribute(domains) | |
473 | self.failUnless(dom1['v2'] == dom2['v2']) | |
474 | self.failUnless(dom1['v2'] == d2) | |
475 | self.failIf(dom1['v1'] == d1) | |
476 | self.failIf(dom2['v1'] == d1) | |
477 | self.assertEquals(dom1['v1'].size(), 1) | |
478 | self.assertEquals(dom1["v1"].highestMax, d1._min_length + d1.lowestMin) | |
479 | self.assertEquals(dom2["v1"].lowestMin, d1._resolution + d1.lowestMin) | |
480 | self.assertEquals(d1.size(), dom1['v1'].size() + dom2['v1'].size()) | |
481 | ||
482 | ||
483 | class PlannerTC(unittest.TestCase): | |
484 | def setUp(self): | |
485 | self.d = FiniteIntervalDistributor() | |
486 | self.verbose = 1 | |
487 | ||
488 | def solve_repo1(self, constraints): | |
489 | dom1 = FiniteIntervalDomain(0, 15, 5) | |
490 | dom2 = FiniteIntervalDomain(0, 15, 5) | |
491 | dom3 = FiniteIntervalDomain(0, 15, 5) | |
492 | repo = Repository( ['A','B','C'], { 'A': dom1, | |
493 | 'B': dom2, | |
494 | 'C': dom3 }, | |
495 | constraints ) | |
496 | s = Solver( self.d ) | |
497 | answers = list(s.solve_all(repo,verbose=self.verbose)) | |
498 | self.assertEquals(len(answers), 2) | |
499 | import pprint | |
500 | pprint.pprint( list(answers) ) | |
501 | ||
502 | def test_pb1(self): | |
503 | constraints = [ StartsAfterEnd('B','A'), | |
504 | StartsAfterEnd('C','A'), | |
505 | NoOverlap('B','C' ) ] | |
506 | self.solve_repo1( constraints ) | |
507 | ||
508 | def test_pb2(self): | |
509 | constraints = [ EndsBeforeStart('A','B'), | |
510 | EndsBeforeStart('A','C'), | |
511 | NoOverlap('B','C' ) ] | |
512 | self.solve_repo1( constraints ) | |
513 | ||
514 | ||
515 | def solve_repo2(self, constraints): | |
516 | dom1 = FiniteIntervalDomain(0, 20, 5) | |
517 | dom2 = FiniteIntervalDomain(0, 20, 5) | |
518 | dom3 = FiniteIntervalDomain(0, 20, 10) | |
519 | dom4 = FiniteIntervalDomain(0, 20, 5) | |
520 | repo = Repository( ['A','B','C', 'D'], { 'A': dom1, | |
521 | 'B': dom2, | |
522 | 'C': dom3, | |
523 | 'D': dom4 }, | |
524 | constraints ) | |
525 | s = Solver( self.d ) | |
526 | answers = list(s.solve_all(repo,verbose=1)) | |
527 | import pprint | |
528 | pprint.pprint( list(answers) ) | |
529 | ||
530 | def test_pb3(self): | |
531 | constraints = [ StartsAfterEnd('B','A'), | |
532 | StartsAfterEnd('C','A'), | |
533 | StartsAfterEnd('D','B'), | |
534 | StartsAfterEnd('D','C'), | |
535 | ] | |
536 | self.solve_repo2( constraints ) | |
537 | ||
538 | def test_pb4(self): | |
539 | constraints = [ StartsAfterEnd('B','A'), | |
540 | StartsAfterEnd('C','A'), | |
541 | StartsAfterEnd('D','B'), | |
542 | StartsAfterEnd('D','C'), | |
543 | StartsAfterEnd('D','A'), | |
544 | ] | |
545 | self.solve_repo2( constraints ) | |
546 | ||
547 | ||
548 | if __name__ == '__main__': | |
549 | unittest.main() |
0 | """Unit testing for constraint propagation module""" | |
1 | ||
2 | # (c) 2000-2001 LOGILAB S.A. (Paris, FRANCE). | |
3 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
4 | # | |
5 | # This program is free software; you can redistribute it and/or modify it under | |
6 | # the terms of the GNU General Public License as published by the Free Software | |
7 | # Foundation; either version 2 of the License, or (at your option) any later | |
8 | # version. | |
9 | # | |
10 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
12 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
13 | # | |
14 | # You should have received a copy of the GNU General Public License along with | |
15 | # this program; if not, write to the Free Software Foundation, Inc., | |
16 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
17 | # USA. | |
18 | ||
19 | import unittest | |
20 | import os | |
21 | from logilab.constraint.propagation import * | |
22 | from logilab.constraint import fd | |
23 | from logilab.constraint.distributors import DefaultDistributor | |
24 | ||
25 | class Repository_TC(unittest.TestCase): | |
26 | def setUp(self): | |
27 | self.domains = {} | |
28 | self.variables = list('abcdef') | |
29 | for v in self.variables: | |
30 | self.domains[v] = fd.FiniteDomain(range(6)) | |
31 | ||
32 | self.repo = Repository(self.variables, self.domains) | |
33 | ||
34 | def testVCGDraw(self): | |
35 | for v1 in self.variables: | |
36 | for v2 in self.variables: | |
37 | if v1 < v2: | |
38 | self.repo.addConstraint(fd.make_expression((v1, v2), | |
39 | '%s < %s'%(v1, v2))) | |
40 | try: | |
41 | try: | |
42 | self.repo.vcg_draw('toto.vcg') | |
43 | except IOError, exc: | |
44 | self.fail('This test cannot run in the testing environment' | |
45 | 'because I cannot write the file.\n' | |
46 | 'The error message was: \n%s' % exc) | |
47 | finally: | |
48 | os.unlink('toto.vcg') | |
49 | ||
50 | ||
51 | ||
52 | def testGetDomains(self): | |
53 | doms = self.repo.getDomains() | |
54 | self.assertEqual(doms, self.domains) | |
55 | ||
56 | def testDistribute(self): | |
57 | d = [] | |
58 | for distributed in self.repo.distribute(DefaultDistributor()): | |
59 | d.append(distributed) | |
60 | self.assertEqual(len(d),2) | |
61 | ||
62 | def testConsistencyNoConstraint(self): | |
63 | self.repo.consistency() | |
64 | for v, dom in self.repo.getDomains().items(): | |
65 | self.assertEqual(dom.size(),6) | |
66 | ||
67 | def testConsistency(self): | |
68 | for v1 in self.variables: | |
69 | for v2 in self.variables: | |
70 | if v1 < v2: | |
71 | self.repo.addConstraint(fd.make_expression((v1, v2), | |
72 | '%s < %s'%(v1, v2))) | |
73 | ||
74 | self.repo.consistency() | |
75 | for v, dom in self.repo.getDomains().items(): | |
76 | self.assertEqual(dom.size(),1) | |
77 | ||
78 | def testInconsistency(self): | |
79 | self.repo.addConstraint(fd.make_expression(('a', 'b'), | |
80 | 'a < b')) | |
81 | self.repo.addConstraint(fd.make_expression(('a', 'b'), | |
82 | 'a > b')) | |
83 | ||
84 | try: | |
85 | self.repo.consistency() | |
86 | self.fail('No ConsistencyFailure raised') | |
87 | except ConsistencyFailure: | |
88 | pass | |
89 | ||
90 | ||
91 | class Sover_TC(unittest.TestCase): | |
92 | def setUp(self): | |
93 | self.solver = Solver() | |
94 | self.domains = {} | |
95 | self.variables = list('abcdef') | |
96 | for v in self.variables: | |
97 | self.domains[v] = fd.FiniteDomain(range(6)) | |
98 | ||
99 | self.repo = Repository(self.variables, self.domains) | |
100 | for v1 in self.variables: | |
101 | for v2 in self.variables: | |
102 | if v1 < v2: | |
103 | self.repo.addConstraint(fd.make_expression((v1, v2), | |
104 | '%s < %s'%(v1, v2))) | |
105 | ||
106 | ||
107 | def testSolveOne(self): | |
108 | solution = self.solver.solve_one(self.repo) | |
109 | self.assertEqual(solution,{'a':0, 'b':1, 'c':2, 'd':3, 'e':4, 'f':5}) | |
110 | ||
111 | def testSolve(self): | |
112 | solutions = self.solver.solve(self.repo) | |
113 | self.assertEqual(solutions, | |
114 | [{'a':0, 'b':1, 'c':2, 'd':3, 'e':4, 'f':5}]) | |
115 | ||
116 | def testSolveAll(self): | |
117 | solutions = [] | |
118 | for s in self.solver.solve_all(self.repo): | |
119 | solutions.append(s) | |
120 | self.assertEqual(solutions, | |
121 | [{'a':0, 'b':1, 'c':2, 'd':3, 'e':4, 'f':5}]) | |
122 | ||
123 | def testNolutionSolve(self): | |
124 | self.repo.addConstraint(fd.make_expression(('a', 'b'), | |
125 | 'b < a')) | |
126 | ||
127 | solutions = self.solver.solve(self.repo) | |
128 | self.assertEqual(solutions, | |
129 | []) | |
130 | ||
131 | ||
132 | class SolverBest_TC(unittest.TestCase): | |
133 | def setUp(self): | |
134 | self.solver = Solver() | |
135 | self.domains = {} | |
136 | self.variables = list('abc') | |
137 | for v in self.variables: | |
138 | self.domains[v] = fd.FiniteDomain(range(6)) | |
139 | ||
140 | self.repo = Repository(self.variables, self.domains) | |
141 | for v1 in self.variables: | |
142 | for v2 in self.variables: | |
143 | if v1 < v2: | |
144 | self.repo.addConstraint(fd.make_expression((v1, v2), | |
145 | '%s < %s'%(v1, v2))) | |
146 | ||
147 | def costFunc(self, a,b,c): | |
148 | return -(a*a+b*b+c*c) | |
149 | ||
150 | def testSolveBest(self): | |
151 | solutions = [] | |
152 | for s in self.solver.solve_best(self.repo, self.costFunc): | |
153 | solutions.append(s) | |
154 | ||
155 | costs = [self.costFunc(**sol[0]) for sol in solutions] | |
156 | sorted_costs = costs[:] | |
157 | sorted_costs.sort() | |
158 | sorted_costs.reverse() | |
159 | self.assertEquals(costs, sorted_costs) | |
160 | self.assertEquals(costs, [s[1] for s in solutions]) | |
161 | ||
162 | ||
163 | ||
164 | if __name__ == '__main__': | |
165 | unittest.main() |
0 | """Validation testing for constraint propagation module""" | |
1 | ||
2 | # (c) 2000-2001 LOGILAB S.A. (Paris, FRANCE). | |
3 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
4 | # | |
5 | # This program is free software; you can redistribute it and/or modify it under | |
6 | # the terms of the GNU General Public License as published by the Free Software | |
7 | # Foundation; either version 2 of the License, or (at your option) any later | |
8 | # version. | |
9 | # | |
10 | # This program is distributed in the hope that it will be useful, but WITHOUT | |
11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
12 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
13 | # | |
14 | # You should have received a copy of the GNU General Public License along with | |
15 | # this program; if not, write to the Free Software Foundation, Inc., | |
16 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307 | |
17 | # USA. | |
18 | ||
19 | from logilab.common.testlib import TestCase | |
20 | ||
21 | from logilab.constraint import * | |
22 | from logilab.constraint.distributors import EnumeratorDistributor | |
23 | ||
24 | import os, sys | |
25 | from cStringIO import StringIO | |
26 | ||
27 | class Queens8_TC(TestCase): | |
28 | size = 8 | |
29 | nb_sols = 92 | |
30 | verbose=0 | |
31 | def setUp(self): | |
32 | variables = [] | |
33 | domains = {} | |
34 | constraints = [] | |
35 | for i in range(self.size): | |
36 | name = 'Q%d'%i | |
37 | variables.append(name) | |
38 | domains[name] = fd.FiniteDomain([(i,j) for j in range(self.size)]) | |
39 | ||
40 | for q1 in variables: | |
41 | for q2 in variables: | |
42 | if q1 < q2: | |
43 | c = fd.make_expression((q1,q2), | |
44 | '%(q1)s[0] < %(q2)s[0] and ' | |
45 | '%(q1)s[1] != %(q2)s[1] and ' | |
46 | 'abs(%(q1)s[0]-%(q2)s[0]) != ' | |
47 | 'abs(%(q1)s[1]-%(q2)s[1])'%\ | |
48 | {'q1':q1,'q2':q2}) | |
49 | constraints.append(c) | |
50 | self.repo = Repository(variables,domains,constraints) | |
51 | sys.stdout = StringIO() | |
52 | ||
53 | def tearDown(self): | |
54 | sys.stdout = sys.__stdout__ | |
55 | ||
56 | def testQueensWithEnumerator(self): | |
57 | self.skip("to long") | |
58 | solver = Solver(EnumeratorDistributor()) | |
59 | solutions = solver.solve(self.repo, verbose=self.verbose) | |
60 | self.assertEqual(len(solutions), self.nb_sols) | |
61 | ||
62 | def testQueensWithDefaultDistributor(self): | |
63 | self.skip("to long") | |
64 | solver = Solver() | |
65 | solutions = solver.solve(self.repo, verbose=self.verbose) | |
66 | self.assertEqual(len(solutions), self.nb_sols) | |
67 | ||
68 | ||
69 | ||
70 | class Queens4_TC(Queens8_TC): | |
71 | size=4 | |
72 | nb_sols=2 | |
73 | ||
74 | class Queens5_TC(Queens8_TC): | |
75 | size=5 | |
76 | nb_sols=10 | |
77 | ||
78 | class Queens6_TC(Queens8_TC): | |
79 | size=6 | |
80 | nb_sols=4 | |
81 | ||
82 | class Queens7_TC(Queens8_TC): | |
83 | size=7 | |
84 | nb_sols=40 | |
85 | ||
86 | class Queens6Verbose_TC(Queens6_TC): | |
87 | verbose = 3 | |
88 | ||
89 | ||
90 | # remove if we are running with pylint, 'cos this gets too long without psyco | |
91 | if os.environ.get('PYLINT_IMPORT') != '1': | |
92 | class Queens9_TC(Queens8_TC): | |
93 | size=9 | |
94 | nb_sols=352 | |
95 | ||
96 | class Queens10_TC(Queens8_TC): | |
97 | size=10 | |
98 | nb_sols=724 | |
99 |