From bd288e7666cca83b06921ee32ddb30b64dadcb89 Mon Sep 17 00:00:00 2001 From: Nostalghia Date: Sat, 22 Aug 2009 16:14:38 +0000 Subject: initial commit --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b675269 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# no backup files +*~ +# no binaries +*.pyc +# no archives +*.xo +*.tar.bz2 +*.zip +# no test data +*.[Ss][Vv][Gg] +*.[Pp][Nn][Gg] +*.[Pp][Dd][Ff] +*.[Jj][Pp][Gg] +*.[Jj][Pp][Ee][Gg] +testoutput_* +# no covarage datas +coverage.lst +.coverage +# no project files +.project +.pydevproject +.classpath +.directory +.project +*.asc +*.gpg +# allow logo +!activity-kandid.svg + diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..9a6140d --- /dev/null +++ b/AUTHORS @@ -0,0 +1,2 @@ +Thomas Jourdan + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..10926e8 --- /dev/null +++ b/COPYING @@ -0,0 +1,675 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..95f9f38 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,49 @@ +activity/activity.info +activity/activity-kandid.svg +locale/en/activity.linfo +locale/en/LC_MESSAGES/net.sourceforge.kandid.mo +po/Kandid.pot +po/en.po +activity.py +ep_buzzwordconstraint_sugar.py +ep_colorconstraint_bw.py +ep_colorconstraint_gray.py +ep_colorconstraint_none.py +ep_directionconstraint_none.py +ep_formater_html.py +ep_layer_image.py +ep_layer_letterpress.py +ep_layer_markovchain.py +ep_layer_referencepattern.py +ep_merger_alphaimage.py +ep_merger_border.py +ep_merger_flip.py +ep_merger_layermask.py +ep_merger_mask.py +ep_merger_rectangulartile.py +ep_merger_straight.py +ep_positionconstraint_centered.py +ep_positionconstraint_none.py +exon_buzzword.py +exon_color.py +exon_direction.py +exon_position.py +ka_canvas.py +ka_controller.py +ka_debug.py +ka_extensionpoint.py +ka_factory.py +ka_importer.py +ka_incoming.py +ka_random.py +ka_task.py +kandid.glade +kandid.py +kandidtube.py +model_allele.py +model_constraintpool.py +model_layer.py +model_locus.py +model_population.py +model_protozoon.py +setup.py diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..bdfd335 --- /dev/null +++ b/NEWS @@ -0,0 +1,2 @@ ++ + diff --git a/activity/activity-kandid.svg b/activity/activity-kandid.svg new file mode 100644 index 0000000..c1c4e09 --- /dev/null +++ b/activity/activity-kandid.svg @@ -0,0 +1,89 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/activity/activity.info b/activity/activity.info new file mode 100644 index 0000000..47eb4fe --- /dev/null +++ b/activity/activity.info @@ -0,0 +1,7 @@ +[Activity] +name = Kandid +service_name = net.sourceforge.kandid +class = activity.KandidActivity +icon = activity-kandid +activity_version = 1 +show_launcher = yes diff --git a/ep_buzzwordconstraint_sugar.py b/ep_buzzwordconstraint_sugar.py new file mode 100644 index 0000000..0bec310 --- /dev/null +++ b/ep_buzzwordconstraint_sugar.py @@ -0,0 +1,41 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import model_locus + +class SugarBuzzwordConstraint(model_locus.Locus): + """SugarBuzzwordConstraint + """ + + def __init__(self, trunk): + """Buzzword constraint constructor + """ + super(SugarBuzzwordConstraint, self).__init__(trunk) + + def get_wordlist(self): + """Returns a list of predefined words. + """ + return (u'Sugar Labs', \ + u'Sugar on a Stick', \ + u'learning platform', \ + u'activity', \ + u'education', \ + u'open source', \ + u'collaboration', \ + u'participation', \ + u'independence', \ + ) diff --git a/ep_colorconstraint_bw.py b/ep_colorconstraint_bw.py new file mode 100644 index 0000000..a55c511 --- /dev/null +++ b/ep_colorconstraint_bw.py @@ -0,0 +1,62 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import random +import model_locus + +EPSILON = 0.00001 + +class BwColorConstraint(model_locus.Locus): + """BwColorConstraint + """ + + def __init__(self, trunk): + """Color constraint constructor + """ + super(BwColorConstraint, self).__init__(trunk) + + def filter(self, rgba): + """Set constraint to red, green, blue and alpha values. + post: len(__return__) == 4 + """ + gray = round((rgba[0] + rgba[1] + rgba[2]) / 3.0) + alpha = round(rgba[3]) + return (gray, gray, gray, alpha) + + def randomize(self): + """Set black or white and alpha to random values. + post: len(__return__) == 4 + post: __return__[0] == 0 or __return__[0] == 1 + post: __return__[3] == 0 or __return__[3] == 1 + """ + gray = 1.0*random.randint(0, 1) + alpha = 1.0*random.randint(0, 1) + return (gray, gray, gray, alpha) + + def mutate(self, rgba): + """Make random switch between black and white. + post: len(__return__) == 4 + """ + return self.randomize() + + def explain(self, rgba, alfa=True): + """ + pre: len(rgba) == 4 + """ + part1 = 'black' if abs(rgba[0]) < EPSILON else 'white' + part2 = ', %d%% opaque' % (100*rgba[3]) if alfa else '' + return part1 + part2 diff --git a/ep_colorconstraint_gray.py b/ep_colorconstraint_gray.py new file mode 100644 index 0000000..c9ec8c6 --- /dev/null +++ b/ep_colorconstraint_gray.py @@ -0,0 +1,62 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import random +import ka_random +import model_locus + +class GrayColorConstraint(model_locus.Locus): + """GrayColorConstraint + """ + + def __init__(self, trunk): + """Color constraint constructor + """ + super(GrayColorConstraint, self).__init__(trunk) + + def filter(self, rgba): + """Set constraint to red, green, blue and alpha values. + post: len(__return__) == 4 + """ + gray = (rgba[0] + rgba[1] + rgba[2]) / 3.0 + alpha = rgba[3] + return (gray, gray, gray, alpha) + + def randomize(self): + """Set lightness and alpha to random values. + post: len(__return__) == 4 + """ + gray = random.random() + return (gray, gray, gray, random.random()) + + def mutate(self, rgba): + """Make small random changes in hue, lightness, saturation. + post: len(__return__) == 4 + """ + gray = (rgba[0] + rgba[1] + rgba[2]) / 3.0 + gray = ka_random.limitate(gray + 0.1 * (random.random() - 0.5)) + alpha = ka_random.limitate(rgba[3] + 0.1 * (random.random() - 0.5)) + return (gray, gray, gray, alpha) + + def explain(self, rgba, alfa=True): + """ + pre: len(rgba) == 4 + """ + if alfa: + return '%d%% gray, %d%% opaque' % (100*rgba[0], 100*rgba[3]) + else: + return '%d%% gray' % (100*rgba[0]) diff --git a/ep_colorconstraint_none.py b/ep_colorconstraint_none.py new file mode 100644 index 0000000..053a67d --- /dev/null +++ b/ep_colorconstraint_none.py @@ -0,0 +1,67 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import random +import colorsys +import ka_random +import model_locus + +class NoneColorConstraint(model_locus.Locus): + """NoneColorConstraint + """ + + def __init__(self, trunk): + """Color constraint constructor + """ + super(NoneColorConstraint, self).__init__(trunk) + + def filter(self, rgba): + """Set constraint to red, green, blue and alpha values. + post: len(__return__) == 4 + """ + return (rgba[0], rgba[1], rgba[2], rgba[3]) + + def randomize(self): + """Set red, green, blue and alpha to random values. + post: len(__return__) == 4 + """ + return (random.random(), random.random(), random.random(), \ + random.random()) + + def mutate(self, rgba): + """Make small random changes in hue, lightness, saturation. + post: len(__return__) == 4 + """ + hue, lightness, saturation = colorsys.rgb_to_hls( \ + rgba[0], rgba[1], rgba[2]) + hue = ka_random.cyclic_limitate(hue + 0.1 * (random.random() - 0.5)) + lightness = ka_random.limitate(lightness + 0.1 * (random.random() - 0.5)) + saturation = ka_random.limitate(saturation + 0.1 * (random.random() - 0.5)) + alpha = ka_random.limitate(rgba[3] + 0.1 * (random.random() - 0.5)) + rgb = colorsys.hls_to_rgb(hue, lightness, saturation) + return (rgb[0], rgb[1], rgb[2], alpha) + + def explain(self, rgba, alfa=True): + """ + pre: len(rgba) == 4 + """ + if alfa: + return '%d%% red, %d%% green, %d%% blue, %d%% opaque' \ + % (100*rgba[0], 100*rgba[1], 100*rgba[2], 100*rgba[3]) + else: + return '%d%% red, %d%% green, %d%% blue' \ + % (100*rgba[0], 100*rgba[1], 100*rgba[2]) diff --git a/ep_directionconstraint_none.py b/ep_directionconstraint_none.py new file mode 100644 index 0000000..35fb6c1 --- /dev/null +++ b/ep_directionconstraint_none.py @@ -0,0 +1,76 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import math +import ka_random +import model_constraintpool +import model_locus + +DISTANCE_CONSTRAINT = 'distance' +RADIAN_CONSTRAINT = 'radian' + +class NoneDirectionConstraint(model_locus.Locus): + """NoneDirectionConstraint + """ + + cdef = [{'bind' : DISTANCE_CONSTRAINT, + 'name' : 'Distance per hop', + 'domain': model_constraintpool.FLOAT_RANGE, + 'min' : 0.0, 'max': 0.5, + }, + {'bind' : RADIAN_CONSTRAINT, + 'name' : 'Rotation in radians', + 'domain': model_constraintpool.FLOAT_RANGE, + 'min' : -1.0*math.pi, 'max': math.pi, + }, + ] + + def __init__(self, trunk): + """Direction constraint constructor + """ + super(NoneDirectionConstraint, self).__init__(trunk) + + def filter(self, radian, distance): + """No constraints for radian and distance. + post: (-1.0*math.pi) <= __return__[0] <= math.pi + post: __return__[1] >= 0.0 + """ + return ka_random.radian_limitate(radian), math.fabs(distance) + + def randomize(self): + """Set radian and distance to random values. + post: (-1.0*math.pi) <= __return__[0] <= math.pi + post: __return__[1] >= 0.0 + """ + cpool = model_constraintpool.ConstraintPool.get_pool() + radian_constraint = cpool.get(self, RADIAN_CONSTRAINT) + distance_constraint = cpool.get(self, DISTANCE_CONSTRAINT) + radian = ka_random.uniform_constrained(radian_constraint) + distance = ka_random.uniform_constrained(distance_constraint) + return ka_random.radian_limitate(radian), math.fabs(distance) + + def mutate(self, radian, distance): + """Make small random changes in radian and distance. + post: (-1.0*math.pi) <= __return__[0] <= math.pi + post: __return__[1] >= 0.0 + """ + cpool = model_constraintpool.ConstraintPool.get_pool() + radian_constraint = cpool.get(self, RADIAN_CONSTRAINT) + distance_constraint = cpool.get(self, DISTANCE_CONSTRAINT) + radian += ka_random.jitter_constrained(radian_constraint) + distance += ka_random.jitter_constrained(distance_constraint) + return ka_random.radian_limitate(radian), math.fabs(distance) diff --git a/ep_formater_html.py b/ep_formater_html.py new file mode 100644 index 0000000..8db6de7 --- /dev/null +++ b/ep_formater_html.py @@ -0,0 +1,345 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import sys +import ka_debug +import exon_color +import exon_position +import exon_direction + +ICON_WIDTH = ICON_HEIGHT = 48 + +class HtmlFormater(object): + """HtmlFormater + inv: self._indent >= 0 + """ + + generator = u'Minimal Kandid' + + def __init__(self, base_name, unique_id, base_folder): + """Constructor for HTML formater.""" + self._base_name = base_name.replace(' ', '_') + self.unique_id = unique_id + self._base_folder = base_folder.replace(' ', '_') + self._indent = 0 + self._page = u'' + self.id_count = 0 +# self.strategy = {} + self._header_occured = False # debugging only + self._footer_occured = False # debugging only + file_path = os.path.join(base_folder, unique_id) + try: + os.mkdir(file_path) + except: + ka_debug.err('creating directory [%s] [%s] [%s]' % \ + (file_path, sys.exc_info()[0], sys.exc_info()[1])) + + + def _escape(self, text): + utext = unicode(text) + quoted = u'' + for uchar in utext: + if uchar in [u'<']: + quoted += u'<' + elif uchar in [u'&']: + quoted += u'&' + else: + quoted += uchar + return quoted + + def _append(self, lines): + indenting = u' '*(2*self._indent) + for line in lines: + self._page += indenting + line + u'\n' + + def _append_escaped(self, text): + self._page += self._escape(text) + + def _get_id(self): + self.id_count += 1 + return u'id_' + unicode(self.id_count) + + def get_pathname(self, extension, postfix=None): + return os.path.join(self._base_folder, + self.unique_id, + self.get_filename(extension, postfix)) + + def get_filename(self, extension, postfix=None): + postfix = '' if postfix is None else '_' + postfix + return self._base_name + postfix + '.' + extension + + def header(self, title): + """ + pre: not self._header_occured + pre: not self._footer_occured + """ + self._append( ( \ + u'', + u'', + u'', + u'', + u'', + u'', + u' ', + u'', + u' ' + title + u'', + u' ', + u' ', + u'', + u'', + u'', + u'
', + u' Expand -', + u' Collapse', + u'
', + u'', + u'
', + u'', + u'
    ', + ) ) + self._header_occured = True + + def footer(self): + """ + pre: self._indent == 0 + pre: self._header_occured + pre: not self._footer_occured + """ + self._append( ( \ + u'
', + u'
', + u'', + u'', + ) ) + self._footer_occured = True + + def _begin_list_item(self, identification): + """ + pre: self._header_occured + pre: not self._footer_occured + """ + self._append( ( \ + u'
  • ', + u' ', + ) ) + + def _end_list_item(self): + """ + pre: self._header_occured + pre: not self._footer_occured + """ + self._append( ( \ + u' ', + u'
  • ', + ) ) + + def _image_item(self, filename, width, height, description, newline=True): + if newline: + self._append( ( \ + u'
    ', + ) ) + self._append( ( \ + u' ' + description + u'', + ) ) + + def begin_list(self, text): + """ + pre: self._header_occured + pre: not self._footer_occured + """ + self._indent += 1 + self._append( ( \ + u'
  • ', + self._escape(text), + u'
      ', + ) ) + + def end_list(self): + """ + pre: self._header_occured + pre: not self._footer_occured + """ + self._append( ( \ + u'
    ', + u'
  • ', + ) ) + self._indent -= 1 + + def text_item(self, text): + """ + pre: self._header_occured + pre: not self._footer_occured + """ + self._begin_list_item(self._get_id()) + self._append_escaped(text) + self._end_list_item() + + def text_list(self, title, text_list): + """ + pre: self._header_occured + pre: not self._footer_occured + """ + self._begin_list_item(self._get_id()) + self._append_escaped(title) + for text in text_list: + self._append_escaped(text + u', ') + self._end_list_item() + + def color_item(self, color, text, alfa=True): + """ + pre: self._header_occured + pre: not self._footer_occured + """ + identification = self._get_id() + pathname = self.get_pathname('png', postfix=identification) + filename = self.get_filename('png', postfix=identification) + surface = exon_color.Color.make_icon(color.rgba, alfa, ICON_WIDTH, ICON_HEIGHT) + surface.write_to_png(pathname) + + description = color.explain(alfa) + self._begin_list_item(identification) + self._append_escaped(text) + self._image_item(filename, ICON_WIDTH, ICON_HEIGHT, description) + self._end_list_item() + + def alfa_item(self, alfa, text): + """ + pre: self._header_occured + pre: not self._footer_occured + """ + identification = self._get_id() + pathname = self.get_pathname('png', postfix=identification) + filename = self.get_filename('png', postfix=identification) + surface = exon_color.Color.make_icon((1.0, 1.0, 1.0, alfa), True, ICON_WIDTH, ICON_HEIGHT) + surface.write_to_png(pathname) + + description = '%d%% opaque' % (100*alfa) + self._begin_list_item(self._get_id()) + self._append_escaped(text) + self._image_item(filename, ICON_WIDTH, ICON_HEIGHT, description) + self._end_list_item() + + def color_array(self, color_list, text, alfa=True): + """ + pre: self._header_occured + pre: not self._footer_occured + """ + self._begin_list_item(self._get_id()) + self._append_escaped(text) + self._append( (u'
    ',) ) + for color in color_list: + identification = self._get_id() + pathname = self.get_pathname('png', postfix=identification) + filename = self.get_filename('png', postfix=identification) + surface = color.make_icon(color.rgba, alfa, ICON_WIDTH, ICON_HEIGHT) + surface.write_to_png(pathname) + description = color.explain(alfa) + self._image_item(filename, ICON_WIDTH, ICON_HEIGHT, \ + description, newline=False) + self._end_list_item() + + def position_item(self, position, text): + self.position_array([position], text) + + def position_array(self, position_list, text): + """ + pre: self._header_occured + pre: not self._footer_occured + pre: len(position_list) >= 0 + """ + description = u'[' + for position in position_list: + description += position.explain() + u'; ' + description += u']' + + self._begin_list_item(self._get_id()) + self._append_escaped(text + u' ' + description) + identification = self._get_id() + pathname = self.get_pathname('png', postfix=identification) + filename = self.get_filename('png', postfix=identification) + surface = exon_position.Position.make_icon(position_list, ICON_WIDTH, ICON_HEIGHT) + surface.write_to_png(pathname) + self._image_item(filename, ICON_WIDTH, ICON_HEIGHT, description) + self._end_list_item() + + def direction_item(self, direction, text): + self.direction_array([direction], text) + + def direction_array(self, direction_list, text): + """ + pre: self._header_occured + pre: not self._footer_occured + pre: len(direction_list) >= 0 + """ + description = u'[' + for direction in direction_list: + description += direction.explain() + u'; ' + description += u']' + + self._begin_list_item(self._get_id()) + self._append_escaped(text + u' ' + description) + identification = self._get_id() + pathname = self.get_pathname('png', postfix=identification) + filename = self.get_filename('png', postfix=identification) + surface = exon_direction.Direction.make_icon(direction_list, ICON_WIDTH, ICON_HEIGHT) + surface.write_to_png(pathname) + self._image_item(filename, ICON_WIDTH, ICON_HEIGHT, description) + self._end_list_item() + + def surface_item(self, surface, text, description): + """ + pre: self._header_occured + pre: not self._footer_occured + """ + identification = self._get_id() + pathname = self.get_pathname('png', postfix=identification) + filename = self.get_filename('png', postfix=identification) + surface.write_to_png(pathname) + width = surface.get_width() + height = surface.get_height() + + self._begin_list_item(self._get_id()) + self._append_escaped(text) + self._image_item(filename, width, height, description) + self._end_list_item() + + def write_html_file(self, file_path): + """ + pre: self._header_occured + pre: self._footer_occured + pre: self._indent == 0 + """ + out_file = None + try: + out_file = open(file_path, 'w') + out_file.write(self._page.encode('utf-8')) + except: + ka_debug.err('failed writing [%s] [%s] [%s]' % \ + (file_path, sys.exc_info()[0], sys.exc_info()[1])) + finally: + if out_file: + out_file.close() diff --git a/ep_layer_image.py b/ep_layer_image.py new file mode 100644 index 0000000..683fe28 --- /dev/null +++ b/ep_layer_image.py @@ -0,0 +1,128 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import random +import os +import cairo +import model_layer +import ka_random +import ka_importer + +class ImageLayer(model_layer.Layer): + """ImageLayer + """ + + def __init__(self, trunk): + """Constructor for bitmap image layer.""" + super(ImageLayer, self).__init__(trunk) + self.selected_image = '' + self.alpha_blending = 1.0 + + def __eq__(self, other): + """Equality based on the cells color components.""" + equal = isinstance(other, ImageLayer) \ + and super(ImageLayer, self).__eq__(other) \ + and self.selected_image == other.selected_image \ + and self.alpha_blending == other.alpha_blending + return equal + + def randomize(self): + """Randomize the layers components.""" + super(ImageLayer, self).randomize() + image_list = ka_importer.get_rgb_image_list() + if len(image_list) > 0: + self.selected_image = \ + image_list[random.randint(0, len(image_list)-1)] + self.alpha_blending = random.random() + + def mutate(self): + """Make small random changes to the layers components.""" + super(ImageLayer, self).mutate() + if ka_random.is_mutating(): + image_list = ka_importer.get_rgb_image_list() + if len(image_list) > 0: + self.selected_image = \ + image_list[random.randint(0, len(image_list)-1)] + if ka_random.is_mutating(): + self.alpha_blending += ka_random.jitter(0.2) + self.alpha_blending = ka_random.limitate(self.alpha_blending) + + def shuffle(self): + """Shuffle similar componets.""" + super(ImageLayer, self).shuffle() + + def crossingover(self, other): + """ + pre: isinstance(other, ImageLayer) + pre: isinstance(self, ImageLayer) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + post: __return__ is not other + """ + new_one = ImageLayer(self.get_trunk()) + cross_sequence = self.crossingover_base(new_one, other, 2) + new_one.selected_image = self.selected_image if cross_sequence[0] \ + else other.selected_image + new_one.alpha_blending = self.alpha_blending if cross_sequence[0] \ + else other.alpha_blending + return new_one + + def draw(self, ctx, width, height): + """ + pre: ctx is not None + pre: width > 0 + pre: height > 0 + pre: width == height + """ + self.begin_draw(ctx, width, height) + + if os.path.isfile(self.selected_image): + surface = cairo.ImageSurface.create_from_png(self.selected_image) + ctx.translate(-0.5, -0.5) + ctx.scale(1.0/surface.get_width(), 1.0/surface.get_height()) + ctx.set_source_surface(surface) + ctx.paint_with_alpha(self.alpha_blending) + + def explain(self, formater): + """ + pre: formater is not None + """ + formater.alfa_item(self.alpha_blending, 'alfa blendig: %d%%' \ + % (100*self.alpha_blending)) + if os.path.isfile(self.selected_image): + png_surface = cairo.ImageSurface.create_from_png(self.selected_image) + width = height = 48 + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) + ctx = cairo.Context(surface) + ctx.scale(width, height) + ctx.scale(1.0/png_surface.get_width(), 1.0/png_surface.get_height()) + ctx.set_source_surface(png_surface) + ctx.paint() + formater.surface_item(surface, + 'image file:' + self.selected_image, + self.selected_image) + + def copy(self): + """The Voronoi diagram layers copy constructor. + # check for distinct references, needs to copy content, not references + post: __return__ is not self + """ + new_one = ImageLayer(self.get_trunk()) + self.copy_base(new_one) + new_one.selected_image = self.selected_image + new_one.alpha_blending = self.alpha_blending + return new_one diff --git a/ep_layer_letterpress.py b/ep_layer_letterpress.py new file mode 100644 index 0000000..7ce19d9 --- /dev/null +++ b/ep_layer_letterpress.py @@ -0,0 +1,216 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import math +import random +import pango +import pangocairo +import model_layer +import ka_random +import model_constraintpool +import exon_color +import exon_position +import exon_direction +import exon_buzzword + +class LetterPress(model_layer.Layer): + """LetterPress + inv: len(self.direction_steps) > 0 + """ + + cdef = [{'bind' : 'family', + 'name' : 'Font family', + 'domain': model_constraintpool.STRING_1_OF_N, + 'enum' : ['Sans', + 'Nimbus Sans L', + 'Monospace',]}, + {'bind' : 'style', + 'name' : 'Font style', + 'domain': model_constraintpool.INT_1_OF_N, + 'enum' : [('Normal', pango.STYLE_NORMAL), + ('Oblique', pango.STYLE_OBLIQUE), + ('Italic', pango.STYLE_ITALIC),]}, + {'bind' : 'size', + 'name' : 'Font size', + 'domain': model_constraintpool.INT_RANGE, + 'min' : 4, 'max': 64}, + ] + + font_style = {pango.STYLE_NORMAL: 'Normal', + pango.STYLE_OBLIQUE: 'Oblique', + pango.STYLE_ITALIC: 'Italic', + } + + def __init__(self, trunk): + """LetterPress diagram layer constructor""" + super(LetterPress, self).__init__(trunk) + cpool = model_constraintpool.ConstraintPool.get_pool() + self.textcolor = exon_color.Color(self.path, 0, 0, 0, 1) + self.family = cpool.get(self, 'family')[0] + self.style = cpool.get(self, 'style')[0] + self.size = cpool.get(self, 'size')[0] + self.center = exon_position.Position(self.path, 0.0, 0.0) + self.direction_steps = [exon_direction.Direction(self.path, 0.0, 0.0)] + self.buzzwords = exon_buzzword.Buzzword(self.path, ['']) + + def __eq__(self, other): + """Equality based on the objects components.""" + equal = isinstance(other, LetterPress) \ + and super(LetterPress, self).__eq__(other) \ + and self.textcolor == other.textcolor \ + and self.family == other.family \ + and self.style == other.style \ + and self.size == other.size \ + and self.center == other.center \ + and len(self.direction_steps) == len(other.direction_steps) \ + and self.buzzwords == other.buzzwords + if equal: + for index, direction in enumerate(self.direction_steps): + equal = equal and direction == other.direction_steps[index] + return equal + + def randomize(self): + """Randomize the layers components.""" + super(LetterPress, self).randomize() + cpool = model_constraintpool.ConstraintPool.get_pool() + family_constraint = cpool.get(self, 'family') + style_constraint = cpool.get(self, 'style') + size_constraint = cpool.get(self, 'size') + self.textcolor.randomize() + self.family = random.choice(family_constraint) + self.style = random.choice(style_constraint) + self.size = random.randint(size_constraint[0], size_constraint[1]) + self.center.randomize() + self.buzzwords.randomize() + self.direction_steps = [] + for i in range(random.randint(1, 6)): + direction = exon_direction.Direction(self.path, 0.0, 0.0) + direction.randomize() + self.direction_steps.append(direction) + + def mutate(self): + """Make small random changes to the layers components.""" + super(LetterPress, self).mutate() + cpool = model_constraintpool.ConstraintPool.get_pool() + family_constraint = cpool.get(self, 'family') + style_constraint = cpool.get(self, 'style') + size_constraint = cpool.get(self, 'size') + self.textcolor.mutate() + if ka_random.is_mutating(): + self.family = random.choice(family_constraint) + if ka_random.is_mutating(): + self.style = random.choice(style_constraint) + if ka_random.is_mutating(): + self.size += random.randint(-12, 12) + self.size = ka_random.limitate_range(self.size, \ + size_constraint[0], \ + size_constraint[1]) + self.center.mutate() + self.buzzwords.mutate() + for direction in self.direction_steps: + direction.mutate() + + def shuffle(self): + """Shuffle similar componets.""" + super(LetterPress, self).shuffle() + self.buzzwords.shuffle() +# self.direction_steps.shuffle() + + def crossingover(self, other): + """ + pre: isinstance(other, LetterPress) + pre: isinstance(self, LetterPress) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + post: __return__ is not other + """ + new_one = LetterPress(self.get_trunk()) + crossing = self.crossingover_base(new_one, other, 5) + new_one.textcolor = self.textcolor.crossingover(other.textcolor) + new_one.center = self.center if crossing[0] else other.center + new_one.buzzwords = self.buzzwords if crossing[1] else other.buzzwords + new_one.family = self.family if crossing[2] else other.family + new_one.style = self.style if crossing[3] else other.style + new_one.size = self.size if crossing[4] else other.size + new_one.direction_steps = ka_random.crossingover(self.direction_steps, \ + other.direction_steps) + return new_one + + def draw(self, ctx, width, height): + """ + pre: ctx is not None + pre: width > 0 + pre: height > 0 + pre: width == height + """ +# self.operator = cairo.OPERATOR_OVER + self.begin_draw(ctx, width, height) + + ctx.scale(1.0/width, 1.0/height) + pango_ctx = pangocairo.CairoContext(ctx) + px, py = self.center.x_pos, self.center.y_pos + fi = di = -1 + for word in self.buzzwords.wordlist: + di = (di+1) % len(self.direction_steps) + step = self.direction_steps[di] + px += step.offset * math.cos(step.radian) + py += step.offset * math.sin(step.radian) + + layout = pango_ctx.create_layout() + fi = (fi+1) % len(self.family) + desc = pango.FontDescription(self.family[fi]) + desc.set_size(self.size * pango.SCALE) + desc.set_style(self.style) + # desc.set_weight(self.font["weight"]) + layout.set_text(word.encode('utf-8')) + layout.set_font_description(desc) + layout.set_alignment(pango.ALIGN_CENTER) + rgba = self.textcolor.rgba + pango_ctx.set_source_rgba(rgba[0], rgba[1], rgba[2], rgba[3]) + pango_ctx.update_layout(layout) + pixel_extents = layout.get_pixel_extents() +# extents = layout.get_extents() + dx = pixel_extents[1][2] / 2 + dy = pixel_extents[1][3] / 2 + pango_ctx.move_to((width * px) - dx, (height * py) - dy) + pango_ctx.show_layout(layout) + + def explain(self, formater): +# super(LetterPress, self).explain(formater) + formater.text_list('buzzwords: ', self.buzzwords.wordlist) + formater.color_item(self.textcolor, 'text color:') + formater.text_item(self.family \ + + ', ' + LetterPress.font_style[self.style] \ + + ', ' + str(self.size)) + formater.position_item(self.center, 'center:') + formater.direction_array(self.direction_steps, 'direction:') + + def copy(self): + """The LetterPress diagram layers copy constructor. + # check for distinct references, needs to copy content, not references + post: __return__ is not self + """ + new_one = LetterPress(self.get_trunk()) + self.copy_base(new_one) + new_one.textcolor = self.textcolor.copy() + new_one.family = self.family + new_one.style = self.style + new_one.size = self.size + new_one.center = self.center.copy() + new_one.buzzwords = self.buzzwords.copy() + new_one.direction_steps = ka_random.copy_list(self.direction_steps) + return new_one diff --git a/ep_layer_markovchain.py b/ep_layer_markovchain.py new file mode 100644 index 0000000..066a5ac --- /dev/null +++ b/ep_layer_markovchain.py @@ -0,0 +1,157 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import random +import exon_color +import model_layer +import ka_random + +class MarkovChainLayer(model_layer.Layer): + """Markov chain layer + inv: self.cell_colors is not None and len(self.cell_colors) == self.states + inv: self.probability is not None and len(self.probability) == self.states + inv: 1 <= self.square_base <= 7 + """ + + def __init__(self, trunk): + """Markov chain layer constructor""" + super(MarkovChainLayer, self).__init__(trunk) + self.states = 4 + self.square_base = 3 + self.cell_colors = [None] * self.states + for cix in range(len(self.cell_colors)): + self.cell_colors[cix] = exon_color.Color(self.path, 0, 0, 0, 1) + self.probability = [[1.0 / self.states] * self.states for i in range(self.states)] +# for row, row_probabilities in enumerate(other.probability): +# for col, cell_probability in enumerate(row_probabilities): +# self.probability[row][col] = 1.0 / self.states + + def __eq__(self, other): + """Equality based on the cells color components.""" + equal = isinstance(other, MarkovChainLayer) \ + and super(MarkovChainLayer, self).__eq__(other) \ + and self.states == other.states \ + and self.square_base == other.square_base + if equal: + for cix, cell_color in enumerate(self.cell_colors): + equal = equal and cell_color == other.cell_colors[cix] + if equal: + for row, row_probabilities in enumerate(self.probability): + for col, cell_probability in enumerate(row_probabilities): + equal = equal and cell_probability == other.probability[row][col] + return equal + + def randomize(self): + """Randomize the layers components.""" + super(MarkovChainLayer, self).randomize() + for cix in range(len(self.cell_colors)): + self.cell_colors[cix].randomize() + for row, row_probabilities in enumerate(self.probability): + for col in range(len(row_probabilities)): + self.probability[row][col] = random.random() / self.states + self.square_base = random.randint(1, 7) + + def mutate(self): + """Make small random changes to the layers components.""" + super(MarkovChainLayer, self).mutate() + for cix in range(len(self.cell_colors)): + self.cell_colors[cix].mutate() + for row, row_probabilities in enumerate(self.probability): + for col, cell_probability in enumerate(row_probabilities): + val = ka_random.limitate(cell_probability \ + + ka_random.jitter(1.0 / self.states)) + self.probability[row][col] = val + if ka_random.is_mutating(): + self.square_base += random.randint(-1, 1) + self.square_base = ka_random.limitate_range(self.square_base, 1, 7) + + def shuffle(self): + """Shuffle similar componets. + For example exchange foreground and background color.""" + super(MarkovChainLayer, self).shuffle() + if ka_random.is_shuffling(): + random.shuffle(self.cell_colors) + if ka_random.is_shuffling(): + random.shuffle(self.probability) + for row_probabilities in self.probability: + if ka_random.is_shuffling(): + random.shuffle(row_probabilities) + + def crossingover(self, other): + """ + pre: isinstance(other, MarkovChainLayer) + pre: isinstance(self, MarkovChainLayer) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + post: __return__ is not other + """ + new_one = MarkovChainLayer(self.get_trunk()) + cross_sequence = self.crossingover_base(new_one, other, \ + 1 + len(self.cell_colors)) + if cross_sequence[0]: + new_one.square_base = self.square_base + else: + new_one.square_base = other.square_base + for cix in range(len(self.cell_colors)): + if cross_sequence[1 + cix]: + new_one.cell_colors[cix] = self.cell_colors[cix].copy() + else: + new_one.cell_colors[cix] = other.cell_colors[cix].copy() + return new_one + + def draw(self, ctx, width, height): + """ + pre: ctx is not None + pre: width > 0 + pre: height > 0 + pre: width == height + """ + self.begin_draw(ctx, width, height) + cell_state = 0 + dim = self.square_base**2 + delta_x, delta_y = 1.0 / dim, 1.0 / dim + for y_index in range(dim): + for x_index in range(dim): + rgba = self.cell_colors[cell_state].rgba + ctx.set_source_rgba(rgba[0], rgba[1], rgba[2], rgba[3]) + ctx.rectangle((x_index-0.5*dim)*delta_x, \ + (y_index-0.5*dim)*delta_y, \ + delta_x, delta_y) + ctx.fill() + cell_state = (cell_state+1) % self.states + + def explain(self, formater): +# super(MarkovChainLayer, self).explain(formater) + formater.color_array(self.cell_colors, 'cell colors:') + + def copy(self): + """The Markov chain layer copy constructor + # check for distinct references, needs to copy content, not references + post: __return__ is not self + """ + new_one = MarkovChainLayer(self.get_trunk()) + self.copy_base(new_one) + new_one.states = self.states + new_one.square_base = self.square_base + new_one.cell_colors = [None] * self.states + for cix in range(len(self.cell_colors)): + new_one.cell_colors[cix] = self.cell_colors[cix].copy() + new_one.probability = [[0.0] * self.states for i in range(self.states)] + for row, row_probabilities in enumerate(self.probability): + for col, cell_probability in enumerate(row_probabilities): + new_one.probability[row][col] = cell_probability + return new_one diff --git a/ep_layer_referencepattern.py b/ep_layer_referencepattern.py new file mode 100644 index 0000000..0487d2c --- /dev/null +++ b/ep_layer_referencepattern.py @@ -0,0 +1,123 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import cairo +import model_layer + +image_list = [] + +class ReferencePattern(model_layer.Layer): + """ReferencePattern + """ + + def __init__(self, trunk): + """Constructor for bitmap image layer.""" + super(ReferencePattern, self).__init__(trunk) + + def __eq__(self, other): + """Equality based on the cells color components.""" + equal = isinstance(other, ReferencePattern) \ + and super(ReferencePattern, self).__eq__(other) + return equal + + def randomize(self): + """Randomize the layers components.""" + super(ReferencePattern, self).randomize() + + def mutate(self): + """Make small random changes to the layers components.""" + super(ReferencePattern, self).mutate() + + def shuffle(self): + """Shuffle similar componets.""" + super(ReferencePattern, self).shuffle() + + def crossingover(self, other): + """ + pre: isinstance(other, ReferencePattern) + pre: isinstance(self, ReferencePattern) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + post: __return__ is not other + """ + new_one = ReferencePattern(self.get_trunk()) + return new_one + + def draw(self, ctx, width, height): + """ + pre: ctx is not None + pre: width > 0 + pre: height > 0 + pre: width == height + """ + self.begin_draw(ctx, width, height) + + ctx.set_source_rgba(1.0, 1.0, 1.0, 0.5) + ctx.rectangle(-0.5, -0.5, 1.0, 1.0) + ctx.fill() + + linear = cairo.LinearGradient(-0.5, -0.5, 1.0, 1.0) + linear.add_color_stop_rgba(0.00, 1.0, 0.0, 0.0, 1.0) + linear.add_color_stop_rgba(1.00, 1.0, 1.0, 0.0, 0.0) + + ctx.rectangle(-0.5, -0.5, 1.0, 1.0) + ctx.set_source(linear) + ctx.fill() + + ctx.set_line_width(0.05) + ctx.set_source_rgba(0.0, 0.0, 0.0, 0.5) + ctx.rectangle(-0.5, -0.5, 1.0, 1.0) + ctx.stroke() + + ctx.set_source_rgba(1.0, 1.0, 1.0, 0.25) + ctx.select_font_face("Sans", + cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) + ctx.set_font_size(0.2) + text = 'L' + ctx.move_to(0.0, 0.0) + ctx.show_text(text) + ctx.set_font_size(0.03) + for ix in range(11): + for iy in range(11): + ctx.move_to(ix/10.0-0.5, iy/10.0-0.5) + ctx.show_text(str(ix)+'&'+str(iy)) + + + ctx.set_line_width(0.01) + ctx.set_source_rgba(0.75, 0.75, 0.75, 0.75) + diagonal = 0.05 + ctx.move_to(-diagonal, -diagonal) + ctx.line_to( diagonal, diagonal) + ctx.move_to( diagonal, -diagonal) + ctx.line_to(-diagonal, diagonal) + ctx.stroke() + + def explain(self, formater): + """ + pre: formater is not None + """ +# super(ReferencePattern, self).explain(formater) + pass + + def copy(self): + """The Voronoi diagram layers copy constructor. + # check for distinct references, needs to copy content, not references + post: __return__ is not self + """ + new_one = ReferencePattern(self.get_trunk()) + self.copy_base(new_one) + return new_one diff --git a/ep_merger_alphaimage.py b/ep_merger_alphaimage.py new file mode 100644 index 0000000..7e0c052 --- /dev/null +++ b/ep_merger_alphaimage.py @@ -0,0 +1,116 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import random +import cairo +import model_allele +import ka_random +import ka_importer + +class AlphaImageMerger(model_allele.Allele): + """AlphaImageMerger + """ + constraint_wieght = (0.0, 0.5) + + def __init__(self, trunk): + """Constructor for a simple merger.""" + super(AlphaImageMerger, self).__init__(trunk) + self.selected_image = '' + + def __eq__(self, other): + """Equality based on same instance.""" + equal = isinstance(other, AlphaImageMerger) \ + and self.selected_image == other.selected_image + return equal + + def randomize(self): + """No member variables. Nothing to do.""" + image_list = ka_importer.get_alpha_image_list() + self.selected_image = image_list[random.randint(0, len(image_list)-1)] + + def mutate(self): + """No member variables. Nothing to do.""" + if ka_random.is_mutating(): + image_list = ka_importer.get_alpha_image_list() + self.selected_image = image_list[random.randint(0, len(image_list)-1)] + + def shuffle(self): + """No member variables. Nothing to do.""" + pass + + def crossingover(self, other): + """ + pre: isinstance(other, AlphaImageMerger) + pre: isinstance(self, AlphaImageMerger) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + post: __return__ is not other + """ + new_one = AlphaImageMerger(self.get_trunk()) + cross_sequence = ka_random.crossing_sequence(1) + new_one.selected_image = self.selected_image if cross_sequence[0] \ + else other.selected_image + return new_one + + def draw_single_layer(self, single_layer, mask_layer, ctx, width, height): + """ + pre: single_layer is not None + pre: ctx is not None + pre: width > 0 + pre: height > 0 + pre: width == height + """ + # paint one layer + ctx.save() + + msk_surface = cairo.ImageSurface.create_from_png(self.selected_image) + ctx.save() + ctx.translate(-0.5, -0.5) + ctx.scale(1.0/msk_surface.get_width(), 1.0/msk_surface.get_height()) + ctx.mask_surface(msk_surface, 0.0, 0.0) + ctx.restore() + + single_layer.draw(ctx, width, height) + ctx.restore() + + def explain(self): + """ + post len(__return__) == 3 + """ + png_surface = cairo.ImageSurface.create_from_png(self.selected_image) + width = height = 48 + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) + ctx = cairo.Context(surface) + ctx.scale(width, height) + ctx.scale(1.0/png_surface.get_width(), 1.0/png_surface.get_height()) + ctx.set_source_surface(png_surface) + ctx.paint() + + return u'Transparent image merger, image ' \ + + unicode(self.selected_image), \ + surface, \ + self.selected_image + + def copy(self): + """A copy constructor. + post: isinstance(__return__, AlphaImageMerger) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + """ + new_one = AlphaImageMerger(self.get_trunk()) + new_one.selected_image = self.selected_image + return new_one diff --git a/ep_merger_border.py b/ep_merger_border.py new file mode 100644 index 0000000..548cb31 --- /dev/null +++ b/ep_merger_border.py @@ -0,0 +1,132 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import random +import cairo +import model_allele +import ka_random + +class BorderMerger(model_allele.Allele): + """BorderMerger + inv: 0.0 <= self.border_weight <= 0.5 + inv: self.border_alfa == 0.0 or self.border_alfa == 1.0 + """ + constraint_wieght = (0.0, 0.5) + + def __init__(self, trunk): + """Constructor for a simple merger.""" + super(BorderMerger, self).__init__(trunk) + self.border_weight = 0.25 + self.border_alfa = 1.0 + self.constraint_weight = (0.0, 0.5) + + def __eq__(self, other): + """Equality based on same instance.""" + equal = isinstance(other, BorderMerger) \ + and self.border_weight == other.border_weight \ + and self.border_alfa == other.border_alfa + return equal + + def randomize(self): + """No member variables. Nothing to do.""" + self.border_weight = random.uniform(self.constraint_weight[0], self.constraint_weight[1]) + self.border_alfa = 1.0 if random.randint(0, 1) > 0 else 0.0 + + def mutate(self): + """No member variables. Nothing to do.""" + if ka_random.is_mutating(): + self.border_weight += ka_random.jitter_constrained(self.constraint_weight) + self.border_weight = ka_random.limitate_range(self.border_weight, self.constraint_weight[0], self.constraint_weight[1]) + if ka_random.is_mutating(): + self.border_alfa = 1.0 if random.randint(0, 1) > 0 else 0.0 + + def shuffle(self): + """No member variables. Nothing to do.""" + pass + + def crossingover(self, other): + """ + pre: isinstance(other, BorderMerger) + pre: isinstance(self, BorderMerger) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + post: __return__ is not other + """ + new_one = BorderMerger(self.get_trunk()) + cross_sequence = ka_random.crossing_sequence(2) + new_one.border_weight = self.border_weight if cross_sequence[0] else other.border_weight + new_one.border_alfa = self.border_alfa if cross_sequence[1] else other.border_alfa + return new_one + + def draw_single_layer(self, single_layer, mask_layer, ctx, width, height): + """ + pre: single_layer is not None + pre: ctx is not None + pre: width > 0 + pre: height > 0 + pre: width == height + """ + # paint one layer + ctx.save() + msk_surface = ctx.get_target().create_similar(cairo.CONTENT_ALPHA, + width, height) + msk_ctx = cairo.Context(msk_surface) + msk_width = msk_surface.get_width() + msk_height = msk_surface.get_height() + + # fill the whole background with an alpha value. + msk_ctx.set_operator(cairo.OPERATOR_SOURCE) + msk_ctx.set_source_rgba(1.0, 1.0, 1.0, self.border_alfa) + msk_ctx.paint() + + # fill the interior with an alpha value. + msk_ctx.set_operator(cairo.OPERATOR_SOURCE) + msk_ctx.set_source_rgba(1.0, 1.0, 1.0, 1.0-self.border_alfa) + msk_ctx.rectangle(self.border_weight*msk_width, + self.border_weight*msk_height, + (1.0-2.0*self.border_weight)*msk_height, + (1.0-2.0*self.border_weight)*msk_width) + msk_ctx.fill() + + ctx.save() + ctx.translate(-0.5, -0.5) + ctx.scale(1.0/msk_width, 1.0/msk_height) + ctx.mask_surface(msk_surface, 0.0, 0.0) + ctx.restore() + + single_layer.draw(ctx, width, height) + ctx.restore() + + def explain(self): + """ + post len(__return__) == 3 + """ + return u'Border merger, border weight=' + unicode(self.border_weight) \ + + u' alfa=' + unicode(self.border_alfa), \ + None, \ + None + + def copy(self): + """A copy constructor. + post: isinstance(__return__, BorderMerger) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + """ + new_one = BorderMerger(self.get_trunk()) + new_one.border_weight = self.border_weight + new_one.border_alfa = self.border_alfa + return new_one diff --git a/ep_merger_flip.py b/ep_merger_flip.py new file mode 100644 index 0000000..19c5bb9 --- /dev/null +++ b/ep_merger_flip.py @@ -0,0 +1,108 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import random +import ka_random +import model_allele + +class FlipMerger(model_allele.Allele): + """FlipMerger: Reverse the layer horizontally or vertically. + inv: self.xFlip == -1.0 or self.xFlip == 1.0 + inv: self.yFlip == -1.0 or self.yFlip == 1.0 + """ + + def __init__(self, trunk): + """Constructor for a flip merger.""" + super(FlipMerger, self).__init__(trunk) + self.xFlip = 1.0 + self.yFlip = 1.0 + + def __eq__(self, other): + """Equality based on fliping horizontally or vertically.""" + equal = isinstance(other, FlipMerger) \ + and self.xFlip == other.xFlip \ + and self.yFlip == other.yFlip + return equal + + def randomize(self): + """Randomize the layers components.""" + self.xFlip = 1.0 if random.randint(0, 1) > 0 else -1.0 + self.yFlip = 1.0 if random.randint(0, 1) > 0 else -1.0 + + def mutate(self): + """Make small random changes to the layers components.""" + if ka_random.is_mutating(): + self.xFlip = 1.0 if random.randint(0, 1) > 0 else -1.0 + if ka_random.is_mutating(): + self.yFlip = 1.0 if random.randint(0, 1) > 0 else -1.0 + + def shuffle(self): + """Shuffle similar componets.""" + pass + + def crossingover(self, other): + """ + pre: isinstance(other, FlipMerger) + pre: isinstance(self, FlipMerger) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + post: __return__ is not other + """ + new_one = FlipMerger(self.get_trunk()) + cross_sequence = ka_random.crossing_sequence(2) + new_one.xFlip = self.xFlip if cross_sequence[0] else other.xFlip + new_one.yFlip = self.yFlip if cross_sequence[1] else other.yFlip + return new_one + + def draw_single_layer(self, single_layer, mask_layer, ctx, width, height): + """ + pre: single_layer is not None + pre: ctx is not None + pre: width > 0 + pre: height > 0 + pre: width == height + """ + # paint one layer + ctx.scale(self.xFlip, self.yFlip) + ctx.save() + single_layer.draw(ctx, width, height) + ctx.restore() + + def explain(self): + """ + post len(__return__) == 3 + """ + if self.xFlip and self.yFlip: + text = u'Flip merger: flip horizontally and vertically.' + elif self.xFlip: + text = u'Flip merger: flip horizontally.' + elif self.yFlip: + text = u'Flip merger: flip vertically.' + else: + text = u'Flip merger: did not flip.' + return text, None, None + + def copy(self): + """A copy constructor. + post: isinstance(__return__, FlipMerger) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + """ + new_one = FlipMerger(self.get_trunk()) + new_one.xFlip = self.xFlip + new_one.yFlip = self.yFlip + return new_one diff --git a/ep_merger_layermask.py b/ep_merger_layermask.py new file mode 100644 index 0000000..98272e5 --- /dev/null +++ b/ep_merger_layermask.py @@ -0,0 +1,134 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import random +import cairo +import model_allele +import ka_random + +class LayermaskMerger(model_allele.Allele): + """LayermaskMerger + inv: self.scale_x >= 1.0 + inv: self.scale_y >= 1.0 + """ + + def __init__(self, trunk): + """Constructor for a simple merger.""" + super(LayermaskMerger, self).__init__(trunk) + self.scale_x = 1.0 + self.scale_y = 1.0 + self.constraint_scale = (1.0, 2.5) + + def __eq__(self, other): + """Equality based on same instance.""" + equal = isinstance(other, LayermaskMerger) \ + and self.scale_x == other.scale_x \ + and self.scale_y == other.scale_y + return equal + + def randomize(self): + """No member variables. Nothing to do.""" + self.scale_x = random.uniform(self.constraint_scale[0], self.constraint_scale[1]) + self.scale_y = random.uniform(self.constraint_scale[0], self.constraint_scale[1]) + + def mutate(self): + """No member variables. Nothing to do.""" + if ka_random.is_mutating(): + self.scale_x += ka_random.jitter_constrained(self.constraint_scale) + self.scale_x = ka_random.limitate_range(self.scale_x, self.constraint_scale[0], self.constraint_scale[1]) + if ka_random.is_mutating(): + self.scale_y += ka_random.jitter_constrained(self.constraint_scale) + self.scale_y = ka_random.limitate_range(self.scale_y, self.constraint_scale[0], self.constraint_scale[1]) + + def shuffle(self): + """No member variables. Nothing to do.""" + pass + + def crossingover(self, other): + """ + pre: isinstance(other, LayermaskMerger) + pre: isinstance(self, LayermaskMerger) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + post: __return__ is not other + """ + new_one = LayermaskMerger(self.get_trunk()) + cross_sequence = ka_random.crossing_sequence(2) + new_one.scale_x = self.scale_x if cross_sequence[0] else other.scale_x + new_one.scale_y = self.scale_y if cross_sequence[1] else other.scale_y + return new_one + + def draw_single_layer(self, single_layer, mask_layer, ctx, width, height): + """ + pre: single_layer is not None + pre: ctx is not None + pre: width > 0 + pre: height > 0 + pre: width == height + """ + # paint one layer + ctx.save() + msk_surface = ctx.get_target().create_similar(cairo.CONTENT_ALPHA, + width, height) + # draw mask. + msk_ctx = cairo.Context(msk_surface) + sx, sy = self.scale_x**2, self.scale_y**2 +# sx, sy = 2.5**2, 2.5**2 + msk_ctx.scale(sx*width, sy*height) +# msk_ctx.translate(0.5-0.5/sx, 0.5-0.5/sy) + msk_ctx.translate(0.5/sx, 0.5/sy) + + # draw mask background + msk_ctx.set_operator(cairo.OPERATOR_SOURCE) + msk_ctx.set_source_rgba(0.0, 0.0, 0.0, 0.5) + msk_ctx.paint() + # draw mask layer + msk_ctx.set_operator(cairo.OPERATOR_SOURCE) + mask_layer.draw(msk_ctx, width, height) +# dummy = ep_layer_referencepattern.ReferencePattern(self.path+'/TODO') +# dummy.operator = cairo.OPERATOR_SOURCE +# dummy.draw(msk_ctx, width, height) +# msk_surface.write_to_png('/dev/shm/TODO.png') + + ctx.save() + ctx.translate(-0.5, -0.5) + ctx.scale(1.0/msk_surface.get_width(), 1.0/msk_surface.get_height()) + ctx.mask_surface(msk_surface, 0, 0) + ctx.restore() + + single_layer.draw(ctx, width, height) + ctx.restore() + + def explain(self): + """ + post len(__return__) == 3 + """ + return 'Layermask merger, scale_x**2=' + unicode(self.scale_x**2) \ + + u' scale_y**2=' + unicode(self.scale_y**2), \ + None, \ + None + + def copy(self): + """A copy constructor. + post: isinstance(__return__, LayermaskMerger) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + """ + new_one = LayermaskMerger(self.get_trunk()) + new_one.scale_x = self.scale_x + new_one.scale_y = self.scale_y + return new_one diff --git a/ep_merger_mask.py b/ep_merger_mask.py new file mode 100644 index 0000000..910ab4d --- /dev/null +++ b/ep_merger_mask.py @@ -0,0 +1,131 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import math +import random +import cairo +import ka_random +import model_allele +import exon_position +import exon_direction + +class MaskMerger(model_allele.Allele): + """MaskMerger + """ + + def __init__(self, trunk): + """Constructor for a simple merger.""" + super(MaskMerger, self).__init__(trunk) + self.alfa1 = 1.0 + self.alfa2 = 0.0 + self.center = exon_position.Position(self.path, 0.0, 0.0) + self.direction = exon_direction.Direction(self.path, 0.0, 0.0) + self.constraint_alfa = (0.0, 1.0) + + def __eq__(self, other): + """Equality based on same instance.""" + equal = isinstance(other, MaskMerger) \ + and self.alfa1 == other.alfa1 \ + and self.alfa2 == other.alfa2 \ + and self.center == other.center \ + and self.direction == other.direction + return equal + + def randomize(self): + """ + """ + self.alfa1 = random.uniform(self.constraint_alfa[0], self.constraint_alfa[1]) + self.alfa2 = random.uniform(self.constraint_alfa[0], self.constraint_alfa[1]) + self.center.randomize() + self.direction.randomize() + + def mutate(self): + """ + """ + if ka_random.is_mutating(): + self.alfa1 += ka_random.jitter_constrained(self.constraint_alfa) + self.alfa1 = ka_random.limitate_range(self.alfa1, self.constraint_alfa[0], self.constraint_alfa[1]) + if ka_random.is_mutating(): + self.alfa2 += ka_random.jitter_constrained(self.constraint_alfa) + self.alfa2 = ka_random.limitate_range(self.alfa2, self.constraint_alfa[0], self.constraint_alfa[1]) + self.center.mutate() + self.direction.mutate() + + def shuffle(self): + """ + """ + pass + + def crossingover(self, other): + """ + pre: isinstance(other, MaskMerger) + pre: isinstance(self, MaskMerger) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + post: __return__ is not other + """ + new_one = MaskMerger(self.get_trunk()) + crossing = ka_random.crossing_sequence(2) + new_one.alfa1 = self.alfa1 if crossing[0] else other.alfa1 + new_one.alfa2 = self.alfa2 if crossing[0] else other.alfa2 + new_one.center = self.center if crossing[0] else other.center + new_one.direction = self.direction if crossing[1] else other.direction + return new_one + + def draw_single_layer(self, single_layer, mask_layer, ctx, width, height): + """ + pre: single_layer is not None + pre: ctx is not None + pre: width > 0 + pre: height > 0 + pre: width == height + """ + # paint one layer masked by an linear gradient + ctx.save() + + dx = self.direction.offset * math.cos(self.direction.radian) + dy = self.direction.offset * math.sin(self.direction.radian) + linear = cairo.LinearGradient(self.center.x_pos + dx, + self.center.y_pos + dy, + self.center.x_pos - dx, + self.center.y_pos - dy) + linear.add_color_stop_rgba(1.0, 1.0, 1.0, 1.0, self.alfa1) + linear.add_color_stop_rgba(0.0, 0.0, 0.0, 0.0, self.alfa2) + ctx.mask(linear) + + single_layer.draw(ctx, width, height) + ctx.restore() + + def explain(self): + """ + post len(__return__) == 3 + """ + return u'Mask merger, center=' + self.center.explain() \ + + u' direction=' + self.direction.explain(), \ + None, \ + None + + def copy(self): + """A copy constructor. + post: isinstance(__return__, MaskMerger) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + """ + new_one = MaskMerger(self.get_trunk()) + new_one.center = self.center.copy() + new_one.direction = self.direction.copy() + return new_one diff --git a/ep_merger_rectangulartile.py b/ep_merger_rectangulartile.py new file mode 100644 index 0000000..1db1736 --- /dev/null +++ b/ep_merger_rectangulartile.py @@ -0,0 +1,112 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import random +import ka_random +import model_allele + +class RectangularTileMerger(model_allele.Allele): + """RectangularTileMerger: Reverse the layer horizontally or vertically. + inv: self.xTiles > 0 + inv: self.yTiles > 0 + """ + + def __init__(self, trunk): + """Constructor for a flip merger.""" + super(RectangularTileMerger, self).__init__(trunk) + self.xTiles = 1 + self.yTiles = 1 + + def __eq__(self, other): + """Equality based on fliping horizontally or vertically.""" + equal = isinstance(other, RectangularTileMerger) \ + and self.xTiles == other.xTiles \ + and self.yTiles == other.yTiles + return equal + + def randomize(self): + """Randomize the layers components.""" + self.xTiles = random.randint(2, 4) + self.yTiles = random.randint(2, 4) + + def mutate(self): + """Make small random changes to the layers components.""" + if ka_random.is_mutating(): + self.xTiles += random.randint(-1, 1) + self.xTiles = 1 if self.xTiles < 1 else self.xTiles + if ka_random.is_mutating(): + self.yTiles += random.randint(-1, 1) + self.yTiles = 1 if self.yTiles < 1 else self.yTiles + + def shuffle(self): + """Shuffle similar componets.""" + pass + + def crossingover(self, other): + """ + pre: isinstance(other, RectangularTileMerger) + pre: isinstance(self, RectangularTileMerger) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + post: __return__ is not other + """ + new_one = RectangularTileMerger(self.get_trunk()) + cross_sequence = ka_random.crossing_sequence(2) + new_one.xTiles = self.xTiles if cross_sequence[0] else other.xTiles + new_one.yTiles = self.yTiles if cross_sequence[1] else other.yTiles + return new_one + + def draw_single_layer(self, single_layer, mask_layer, ctx, width, height): + """ + pre: single_layer is not None + pre: ctx is not None + pre: width > 0 + pre: height > 0 + pre: width == height + """ + # repeat painting layer + dx, dy = 1.0 / self.xTiles, 1.0 / self.yTiles + for tx in range(self.xTiles): + for ty in range(self.yTiles): + ctx.save() + ctx.translate((tx-0.5*self.xTiles)*dx+0.5*dx, \ + (ty-0.5*self.yTiles)*dy+0.5*dy) + ctx.scale(dx, dy) + ctx.save() + single_layer.draw(ctx, width, height) + ctx.restore() + ctx.restore() + + def explain(self): + """ + post len(__return__) == 3 + """ + return u'Rectangular tile merger: %d*x, %d*y' \ + % (self.xTiles, self.yTiles), \ + None, \ + None + + def copy(self): + """A copy constructor. + post: isinstance(__return__, RectangularTileMerger) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + """ + new_one = RectangularTileMerger(self.get_trunk()) + new_one.xTiles = self.xTiles + new_one.yTiles = self.yTiles + return new_one diff --git a/ep_merger_straight.py b/ep_merger_straight.py new file mode 100644 index 0000000..77ee0e1 --- /dev/null +++ b/ep_merger_straight.py @@ -0,0 +1,81 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import model_allele + +class StraightMerger(model_allele.Allele): + """StraightMerger + """ + + def __init__(self, trunk): + """Constructor for a simple merger.""" + super(StraightMerger, self).__init__(trunk) + + def __eq__(self, other): + """Equality based on same instance.""" + equal = isinstance(other, StraightMerger) + return equal + + def randomize(self): + """No member variables. Nothing to do.""" + pass + + def mutate(self): + """No member variables. Nothing to do.""" + pass + + def shuffle(self): + """No member variables. Nothing to do.""" + pass + + def crossingover(self, other): + """ + pre: isinstance(other, StraightMerger) + pre: isinstance(self, StraightMerger) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + post: __return__ is not other + """ + new_one = StraightMerger(self.get_trunk()) + return new_one + + def draw_single_layer(self, single_layer, mask_layer, ctx, width, height): + """ + pre: single_layer is not None + pre: ctx is not None + pre: width > 0 + pre: height > 0 + pre: width == height + """ + # paint one layer + ctx.save() + single_layer.draw(ctx, width, height) + ctx.restore() + + def explain(self): + return 'Straight merger', \ + None, \ + None + + def copy(self): + """A copy constructor. + post: isinstance(__return__, StraightMerger) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + """ + new_one = StraightMerger(self.get_trunk()) + return new_one diff --git a/ep_positionconstraint_centered.py b/ep_positionconstraint_centered.py new file mode 100644 index 0000000..12fd99c --- /dev/null +++ b/ep_positionconstraint_centered.py @@ -0,0 +1,53 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import random +import ka_random +import model_locus + +class CenteredPositionConstraint(model_locus.Locus): + """CenteredPositionConstraint + """ + + def __init__(self, trunk): + """Position constraint constructor + """ + super(CenteredPositionConstraint, self).__init__(trunk) + + def filter(self, x_pos, y_pos): + """Set constraint x- and y-position. + post: len(__return__) == 2 + """ + xp = (x_pos + 0.5) / 2.0 + yp = (y_pos + 0.5) / 2.0 + return xp, yp + + def randomize(self): + """Set x- and y-position to random values. + post: len(__return__) == 2 + """ + return random.gauss(0.5, 0.1), random.gauss(0.5, 0.1) + + def mutate(self, x_pos, y_pos): + """Make small random changes in x- and y-position. + post: len(__return__) == 2 + """ + xp = x_pos + ka_random.jitter(0.2) + xp = (2.0*xp + random.gauss(0.5, 0.1)) / 3.0 + yp = y_pos + ka_random.jitter(0.2) + yp = (2.0*yp + random.gauss(0.5, 0.1)) / 3.0 + return xp, yp diff --git a/ep_positionconstraint_none.py b/ep_positionconstraint_none.py new file mode 100644 index 0000000..31dd0ff --- /dev/null +++ b/ep_positionconstraint_none.py @@ -0,0 +1,49 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import random +import ka_random +import model_locus + +class NonePositionConstraint(model_locus.Locus): + """NonePositionConstraint + """ + + def __init__(self, trunk): + """Position constraint constructor + """ + super(NonePositionConstraint, self).__init__(trunk) + + def filter(self, x_pos, y_pos): + """Set constraint x- and y-position. + post: len(__return__) == 2 + """ + return x_pos, y_pos + + def randomize(self): + """Set x- and y-position to random values. + post: len(__return__) == 2 + """ + return random.random(), random.random() + + def mutate(self, x_pos, y_pos): + """Make small random changes in x- and y-position. + post: len(__return__) == 2 + """ + xp = x_pos + ka_random.jitter(0.2) + yp = y_pos + ka_random.jitter(0.2) + return xp, yp diff --git a/exon_buzzword.py b/exon_buzzword.py new file mode 100644 index 0000000..523d1d6 --- /dev/null +++ b/exon_buzzword.py @@ -0,0 +1,129 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import random +import ka_random +import ka_extensionpoint +import model_constraintpool +import model_allele + +BUZZWORD_CONSTRAINTS = 'buzzwordconstraint' + +class Buzzword(model_allele.Allele): + """Buzzword + inv: len(self.wordlist) > 0 + """ + + cdef = [{'bind' : BUZZWORD_CONSTRAINTS, + 'name' : 'Buzzwords', + 'domain': model_constraintpool.STRING_1_OF_N, + 'enum' : ka_extensionpoint.list_extensions(BUZZWORD_CONSTRAINTS) + }, + ] + + def __init__(self, trunk, words): + """Buzzword constructor + pre: len(words) > 0 + """ + super(Buzzword, self).__init__(trunk) + cpool = model_constraintpool.ConstraintPool.get_pool() + constraint_name = cpool.get(self, BUZZWORD_CONSTRAINTS)[0] + self.constraint = ka_extensionpoint.create(constraint_name, self.path) + self.wordlist = words + + def __eq__(self, other): + """Equality based on list of strings.""" + equal = isinstance(other, Buzzword) \ + and len(self.wordlist) == len(other.wordlist) + if equal: + for index, word in enumerate(self.wordlist): + equal = equal and word == other.wordlist[index] + return equal + +# def __ne__(self, other): +# return not self.__eq__(other) +# +# def __hash__(self): +# raise TypeError("Genome objects are unhashable") + + def randomize(self): + """Create a random word list. + Result is a subset of the word list from + extension point 'ep_buzzwordconstraint'. + """ + cpool = model_constraintpool.ConstraintPool.get_pool() + constraints = cpool.get(self, BUZZWORD_CONSTRAINTS) + self.constraint = ka_extensionpoint.create(random.choice(constraints), self.path) + + full_wordlist = self.constraint.get_wordlist() + approximate_count = random.randint(1, len(full_wordlist)-1) + self.wordlist = [] + self.wordlist.append(random.choice(full_wordlist)) + for i in range(approximate_count): + candidate = random.choice(full_wordlist) + if candidate not in self.wordlist: + self.wordlist.append(candidate) + + def mutate(self): + """Mutate word list. + """ + full_wordlist = self.constraint.get_wordlist() + + # change a word + if ka_random.is_mutating(): + candidate = random.choice(full_wordlist) + if candidate not in self.wordlist: + self.wordlist[random.randint(0, len(self.wordlist)-1)] = candidate + + # change number of words + if ka_random.is_mutating(): + new_count = len(self.wordlist) + random.randint(-1, 1) + if new_count > len(self.wordlist): + # append one + self.wordlist.insert(random.randint(0, len(self.wordlist)-1), \ + random.choice(full_wordlist)) + elif new_count < len(self.wordlist) and len(self.wordlist) >= 2: + #remove one + del self.wordlist[random.randint(0, len(self.wordlist)-1)] + + def shuffle(self): + """Exchange position of words in word list""" + random.shuffle(self.wordlist) + + def crossingover(self, other): + """Merges both word lists and returns a copy of Buzzword. + pre: isinstance(other, Buzzword) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + post: __return__ is not other + """ + new_one = Buzzword(self.get_trunk(), ['']) + new_one.wordlist = ka_random.crossingover(self.wordlist, other.wordlist) + return new_one.copy() + + def copy(self): + """A word list copy constructor. + post: isinstance(__return__, Buzzword) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + """ + new_one = Buzzword(self.get_trunk(), ['']) + new_one.wordlist = [] + for word in self.wordlist: + new_one.wordlist.append(word) + new_one.constraint = self.constraint + return new_one diff --git a/exon_color.py b/exon_color.py new file mode 100644 index 0000000..cf3150d --- /dev/null +++ b/exon_color.py @@ -0,0 +1,135 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import random +import cairo +import ka_extensionpoint +import ka_random +import model_constraintpool +import model_allele + +EPSILON = 0.00001 +COLOR_CONSTRAINT = 'colorconstraint' + +class Color(model_allele.Allele): + """Color + inv: len(self.rgba) == 4 + inv: 0.0 <= self.rgba[0] <= 1.0 + inv: 0.0 <= self.rgba[1] <= 1.0 + inv: 0.0 <= self.rgba[2] <= 1.0 + inv: 0.0 <= self.rgba[3] <= 1.0 + """ + + cdef = [{'bind' : COLOR_CONSTRAINT, + 'name' : 'Color constraint', + 'domain': model_constraintpool.STRING_1_OF_N, + 'enum' : ka_extensionpoint.list_extensions(COLOR_CONSTRAINT) + }, + ] + + def __init__(self, trunk, red, green, blue, alpha): + """Color constructor + pre: 0.0 <= red <= 1.0 + pre: 0.0 <= green <= 1.0 + pre: 0.0 <= blue <= 1.0 + pre: 0.0 <= alpha <= 1.0 + """ + super(Color, self).__init__(trunk) + cpool = model_constraintpool.ConstraintPool.get_pool() + constraint_name = cpool.get(self, COLOR_CONSTRAINT)[0] + self.constraint = ka_extensionpoint.create(constraint_name, self.path) + self.rgba = self.constraint.filter((red, green, blue, alpha)) + + def __eq__(self, other): + """Equality based on color components.""" + return isinstance(other, Color) \ + and abs(self.rgba[0] - other.rgba[0]) < EPSILON \ + and abs(self.rgba[1] - other.rgba[1]) < EPSILON \ + and abs(self.rgba[2] - other.rgba[2]) < EPSILON \ + and abs(self.rgba[3] - other.rgba[3]) < EPSILON + + def randomize(self): + """Set red, green, blue and alpha to random values.""" + cpool = model_constraintpool.ConstraintPool.get_pool() + constraints = cpool.get(self, COLOR_CONSTRAINT) + self.constraint = ka_extensionpoint.create(random.choice(constraints), self.path) + self.rgba = self.constraint.randomize() + + def mutate(self): + """Make small random changes in hue, lightness, saturation.""" + if ka_random.is_mutating(): + if ka_random.is_mutating(): + cpool = model_constraintpool.ConstraintPool.get_pool() + constraints = cpool.get(self, COLOR_CONSTRAINT) + self.constraint = ka_extensionpoint.create(random.choice(constraints), self.path) + self.rgba = self.constraint.mutate(self.rgba) + + def shuffle(self): + """Not implemented.""" + pass + + def crossingover(self, other): + """Returns either a copy of self or a copy of other. + pre: isinstance(other, Color) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + post: __return__ is not other + """ + randseq = ka_random.crossing_sequence(1) + return self.copy() if randseq[0] else other.copy() + + def copy(self): + """A color copy constructor + post: isinstance(__return__, Color) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + """ + new_one = Color(self.get_trunk(), 0.0, 0.0, 0.0, 0.0) + new_one.rgba = (self.rgba[0], self.rgba[1], self.rgba[2], self.rgba[3]) + new_one.constraint = self.constraint + return new_one + + def explain(self, alfa=True): + return self.constraint.explain(self.rgba, alfa) + + @staticmethod + def make_icon(rgba, alfa, width, height): + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) + ctx = cairo.Context(surface) + if alfa: + # paint checker board background + steps = 4 + delta = (1.0 * width) / (1.0 * steps) + ctx.set_operator(cairo.OPERATOR_SOURCE) + for row in range(steps): + for col in range(steps): + ctx.rectangle(col * delta, row * delta, delta, delta) + if (col + row) % 2 == 0: + ctx.set_source_rgb(0.4, 0.4, 0.4) + else: + ctx.set_source_rgb(0.6, 0.6, 0.6) + ctx.fill() + # paint color and alfa + ctx.set_operator(cairo.OPERATOR_OVER) + ctx.set_source_rgba(rgba[0], rgba[1], rgba[2], rgba[3]) + else: + # paint color + ctx.set_operator(cairo.OPERATOR_OVER) + ctx.set_source_rgb(rgba[0], rgba[1], rgba[2]) + ctx.paint() + return surface + diff --git a/exon_direction.py b/exon_direction.py new file mode 100644 index 0000000..efd435f --- /dev/null +++ b/exon_direction.py @@ -0,0 +1,123 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import random +import math +import cairo +import ka_extensionpoint +import ka_random +import model_constraintpool +import model_allele + +EPSILON = 0.00001 +DIRECTION_CONSTRAINT = 'directionconstraint' + +class Direction(model_allele.Allele): + """Direction + """ + + cdef = [{'bind' : DIRECTION_CONSTRAINT, + 'name' : 'Direction constraint', + 'domain': model_constraintpool.STRING_1_OF_N, + 'enum' : ka_extensionpoint.list_extensions(DIRECTION_CONSTRAINT) + }, + ] + + def __init__(self, trunk, radian, offset): + """Direction constructor + """ + super(Direction, self).__init__(trunk) + cpool = model_constraintpool.ConstraintPool.get_pool() + constraint_name = cpool.get(self, DIRECTION_CONSTRAINT)[0] + self.constraint = ka_extensionpoint.create(constraint_name, self.path) + self.radian, self.offset = self.constraint.filter(radian, offset) + + def __eq__(self, other): + """Equality based on radian and offset.""" + return isinstance(other, Direction) \ + and abs(self.radian - other.radian) < EPSILON \ + and abs(self.offset - other.offset) < EPSILON + +# def __ne__(self, other): +# return not self.__eq__(other) +# +# def __hash__(self): +# raise TypeError("Genome objects are unhashable") + + def randomize(self): + """Set radian and offset to random values.""" + cpool = model_constraintpool.ConstraintPool.get_pool() + constraints = cpool.get(self, DIRECTION_CONSTRAINT) + self.constraint = ka_extensionpoint.create(random.choice(constraints), self.path) + self.radian, self.offset = self.constraint.randomize() + + def mutate(self): + """Make small random changes in radian and offset.""" + if ka_random.is_mutating(): + if ka_random.is_mutating(): + cpool = model_constraintpool.ConstraintPool.get_pool() + constraints = cpool.get(self, DIRECTION_CONSTRAINT) + self.constraint = ka_extensionpoint.create(random.choice(constraints), self.path) + self.radian, self.offset = self.constraint.mutate(self.radian, self.offset) + + def shuffle(self): + """Not implemented.""" + pass + + def crossingover(self, other): + """Returns either a copy of self or a copy of other. + pre: isinstance(other, Direction) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + post: __return__ is not other + """ + randseq = ka_random.crossing_sequence(1) + return self.copy() if randseq[0] else other.copy() + + def copy(self): + """A direction copy constructor + post: isinstance(__return__, Direction) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + """ + new_one = Direction(self.get_trunk(), 0.0, 0.0) + new_one.radian, new_one.offset = self.radian, self.offset + new_one.constraint = self.constraint + return new_one + + def explain(self): + return u'%4.3f, %4.3f' % (self.radian, self.offset) + + @staticmethod + def make_icon(direction_list, width, height): + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) + ctx = cairo.Context(surface) + ctx.scale(width, height) + # paint background + ctx.set_operator(cairo.OPERATOR_OVER) + ctx.set_source_rgb(1.0, 1.0, 1.0) + ctx.paint() + ctx.set_line_width(0.02) + for direction in direction_list: + # paint a arrow for each direction + dx = direction.offset * math.cos(direction.radian) + dy = direction.offset * math.sin(direction.radian) + ctx.set_source_rgb(0.0, 0.0, 0.0) + ctx.move_to(0.5, 0.5) + ctx.line_to(0.5+dx, 0.5+dy) + ctx.stroke() + return surface diff --git a/exon_position.py b/exon_position.py new file mode 100644 index 0000000..efe560a --- /dev/null +++ b/exon_position.py @@ -0,0 +1,118 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import random +import cairo +import ka_extensionpoint +import ka_random +import model_constraintpool +import model_allele + +EPSILON = 0.00001 +POSITION_CONSTRAINT = 'positionconstraint' + +class Position(model_allele.Allele): + """Position + """ + + cdef = [{'bind' : POSITION_CONSTRAINT, + 'name' : 'Position', + 'domain': model_constraintpool.STRING_1_OF_N, + 'enum' : ka_extensionpoint.list_extensions(POSITION_CONSTRAINT) + }, + ] + + def __init__(self, trunk, x_pos, y_pos): + """Position constructor + """ + super(Position, self).__init__(trunk) + cpool = model_constraintpool.ConstraintPool.get_pool() + constraint_name = cpool.get(self, POSITION_CONSTRAINT)[0] + self.constraint = ka_extensionpoint.create(constraint_name, self.path) + self.x_pos, self.y_pos = self.constraint.filter(x_pos, y_pos) + + def __eq__(self, other): + """Equality based on color components.""" + return isinstance(other, Position) \ + and abs(self.x_pos - other.x_pos) < EPSILON \ + and abs(self.y_pos - other.y_pos) < EPSILON + + def randomize(self): + """Set x_pos, y_pos to random values.""" + cpool = model_constraintpool.ConstraintPool.get_pool() + constraints = cpool.get(self, POSITION_CONSTRAINT) + self.constraint = ka_extensionpoint.create(random.choice(constraints), self.path) + self.x_pos, self.y_pos = self.constraint.randomize() + + def mutate(self): + """Make small random changes in position.""" + if ka_random.is_mutating(): + if ka_random.is_mutating(): + cpool = model_constraintpool.ConstraintPool.get_pool() + constraints = cpool.get(self, POSITION_CONSTRAINT) + self.constraint = ka_extensionpoint.create(random.choice(constraints), self.path) + self.x_pos, self.y_pos = self.constraint.mutate(self.x_pos, self.y_pos) + + def shuffle(self): + """Exchange x- and y-coordinate.""" + temp0, temp1 = self.x_pos, self.y_pos + self.x_pos, self.y_pos = temp1, temp0 + + def crossingover(self, other): + """Returns either a copy of self or a copy of other. + pre: isinstance(other, Position) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + post: __return__ is not other + """ + randseq = ka_random.crossing_sequence(1) + return self.copy() if randseq[0] else other.copy() + + def copy(self): + """A position copy constructor + post: isinstance(__return__, Position) + # check for distinct references, needs to copy content, not references + post: __return__ is not self + """ + new_one = Position(self.get_trunk(), 0.0, 0.0) + new_one.x_pos, new_one.y_pos = self.x_pos, self.y_pos + new_one.constraint = self.constraint + return new_one + + def explain(self): + return u'%4.3f, %4.3f' % (self.x_pos, self.y_pos) + + @staticmethod + def make_icon(position_list, width, height): + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) + ctx = cairo.Context(surface) + ctx.scale(width, height) + # paint background + ctx.set_operator(cairo.OPERATOR_OVER) + ctx.set_source_rgb(1.0, 1.0, 1.0) + ctx.paint() + radius = 0.1 + ctx.set_line_width(0.02) + for position in position_list: + # paint a cross for each position + ctx.set_source_rgb(0.0, 0.0, 0.0) + ctx.move_to(position.x_pos-radius, position.y_pos-radius) + ctx.line_to(position.x_pos+radius, position.y_pos+radius) + ctx.move_to(position.x_pos+radius, position.y_pos-radius) + ctx.line_to(position.x_pos-radius, position.y_pos+radius) + ctx.stroke() + return surface diff --git a/ka_canvas.py b/ka_canvas.py new file mode 100644 index 0000000..ab31380 --- /dev/null +++ b/ka_canvas.py @@ -0,0 +1,42 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gtk +import gtk.glade + +class KandidCanvas(object): + """ + inv: self.widgetTree is not None + inv: self.widget is not None + """ + + def __init__(self): + # Load Glade XML + self.widgetTree = gtk.glade.XML("kandid.glade") + + # Get Window + self._window = self.widgetTree.get_widget('window1') + # Get Windows child + self._window_child = self._window.get_child() + + # Remove the widget's parent + if self._window_child.parent: + self._window_child.parent.remove(self._window_child) + + # self.widget will be attached to the Activity + # This can be any GTK widget except a window + self.widget = self._window_child diff --git a/ka_controller.py b/ka_controller.py new file mode 100644 index 0000000..05ef161 --- /dev/null +++ b/ka_controller.py @@ -0,0 +1,357 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import hashlib +import gtk +import gtk.glade +import cairo +import ka_debug +import model_population +import ka_incoming +import kandidtube +import ka_random +import ka_task + +class KandidController(object): + """ + inv: self.model is not None + inv: self._canvas is not None + """ + + def __init__(self, init_canvas): + """ + pre: init_canvas is not None + """ + self._canvas = init_canvas + self._tube = None + + # create to data model + self.model = model_population.KandidModel(12) + self.model.randomize() + self.incoming = ka_incoming.KandidIncoming(3) + + self.surface_cache = {} + + #Create a dictionary to connect events + events = { + 'on_breed_generation' : self.on_breed_generation, + 'on_random_generation' : self.on_random_generation, + 'on_flurry_value_changed' : self.on_flurry_value_changed, + 'on_decline_incoming' : self.on_decline_incoming, + 'on_accept_incoming' : self.on_accept_incoming, + 'delete_event' : gtk.main_quit } + for cell_index in range(self.model.size): + strix = str(cell_index) + key = 'on_drawingarea_#_expose'.replace('#', strix) + events[key] = self.on_drawingarea_expose + key = 'on_drawingarea_#_size_allocate'.replace('#', strix) + events[key] = self.on_drawingarea_size_allocate + key = 'on_fitness_#_value_changed'.replace('#', strix) + events[key] = self.on_fitness_value_changed + key = 'on_protozoon_popup_#'.replace('#', strix) + events[key] = self.on_protozoon_popup + key = 'on_publishprotozoon_activate_#'.replace('#', strix) + events[key] = self.on_publishprotozoon_activate + events['on_favorite_activate_' + strix] = self.on_favorite_activate + events['on_awfull_activate_' + strix] = self.on_awfull_activate + for cell_index in range(3): + strix = str(cell_index) + key = 'on_incomingarea_#_expose'.replace('#', strix) + events[key] = self.on_incomingarea_expose + key = 'on_incoming_#_popup'.replace('#', strix) + events[key] = self.on_incoming_popup + self._canvas.widgetTree.signal_autoconnect(events) + self._update_gui() + + def _update_model(self, in_model): + if in_model: + self.model = in_model + self.model._state = model_population.STATE_EVOLVED + self._update_gui() + + def _update_gui(self): + for cell_index in range(self.model.size): + strix = str(cell_index) + key = 'fitness_#'.replace('#', strix) + self._canvas.widgetTree.get_widget(key). \ + set_value(self.model.fitness[cell_index]) + self._canvas.widgetTree.get_widget('flurrySpinButton'). \ + set_value(self.model.flurry_rate) + + def _draw_from_cache(self, widget, cell_index): + if self.surface_cache.has_key(cell_index): + ka_debug.info('_draw_from_cache: ' + widget.name + ' ' + str(cell_index)) + ctx = self._create_context(widget) + ctx.set_operator(cairo.OPERATOR_SOURCE) + ctx.set_source_surface(self.surface_cache[cell_index]) + ctx.paint() + + def on_drawingarea_expose(self, widget, event): + """ Repaint image of a single protozoon inside population area. + pre: widget is not None + """ + # draw precalculated protozoon stored in the surface cache. + ka_debug.info('on_drawingarea_expose: ' + widget.name + ' ' + + str(widget.allocation.width) + + 'x' + str(widget.allocation.height)) + self._draw_from_cache(widget, _name_to_index(widget.name)) + + def on_drawingarea_size_allocate(self, widget, event): + """ New size for drawing area available. + pre: widget is not None + """ + ka_debug.info('on_drawingarea_size_allocate: ' + widget.name + ' ' + + str(widget.allocation.width) + + 'x' + str(widget.allocation.height)) + self._start_calculation([_name_to_index(widget.name)]) + + def _create_context(self, widget): + """ Create cairo context. + pre: widget is not None + """ + ctx = widget.window.cairo_create() + ctx.rectangle(0, 0, \ + widget.allocation.width, widget.allocation.height) + ctx.clip() + return ctx + + def on_fitness_value_changed(self, *args): + """ + pre: len(args) >= 1 + """ + ka_debug.info('on_fitness_value_changed %f [%s]' % + (args[0].get_value(), args[0].get_name())) + self.model.fitness[_name_to_index(args[0].get_name())] \ + = args[0].get_value() + + def on_breed_generation(self, *args): + if ka_task.is_completed(): + ka_debug.info('on_breed_generation entry') + ka_task.GeneratorTask(self.task_breed_generation, + self.on_model_completed).start() + ka_debug.info('on_breed_generation exit') + else: + ka_debug.info('on_breed_generation ignored') + + def on_random_generation(self, *args): + if ka_task.is_completed(): + ka_debug.info('on_random_generation entry') + ka_task.GeneratorTask(self.task_random_generation, + self.on_model_completed).start() + ka_debug.info('on_random_generation exit') + else: + ka_debug.info('on_random_generation ignored') + + def on_model_completed(self, *args): + """ + pre: len(args) == 1 + """ + ka_debug.info('on_model_completed entry') + for cell_index in args[0]: + self._canvas.widgetTree.get_widget('vbox_' + str(cell_index)). \ + set_sensitive(False) + self._start_calculation(args[0]) + ka_debug.info('on_model_completed exit') + + def _start_calculation(self, concerned): + """ + pre: len(concerned) > 0 + pre: forall(concerned, lambda x: 0 <= x <= self.model.size) + """ + for cell_index in concerned: + widget = self._canvas.widgetTree.get_widget('drawingarea_' + + str(cell_index)) + task = ka_task.GeneratorTask(self.task_draw, + self.on_image_completed) + task.start(self.model.protozoans[cell_index], cell_index, + widget.allocation.width, widget.allocation.height) + + def task_breed_generation(self, *args, **kwargs): + """ + pre: len(args) == 0 + """ + ka_debug.info('task_breed_generation entry') + new_indices = self.model.breed() + ka_debug.info('task_breed_generation exit') + return new_indices + + def task_random_generation(self, *args, **kwargs): + """ + pre: len(args) == 0 + """ + ka_debug.info('task_random_generation entry') + new_indices = self.model.random() + ka_debug.info('task_random_generation exit') + return new_indices + + def task_draw(self, *args, **kwargs): + """ + pre: len(args) == 4 + """ + protozoon, cell_index, width, height = args[0], args[1], args[2], args[3] + ka_debug.info('task_draw entry: ' + str(cell_index)) + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) + ctx = cairo.Context(surface) + protozoon.draw(ctx, width, height) + self.surface_cache[cell_index] = surface + ka_debug.info('task_draw exit: ' + str(cell_index)) + return cell_index + + def on_image_completed(self, *args): + ka_debug.info('on_image_completed: ' + str(args[0])) + cell_index = args[0] + widget = self._canvas.widgetTree.get_widget('drawingarea_' + + str(cell_index)) + self._draw_from_cache(widget, cell_index) + self._canvas.widgetTree.get_widget('vbox_' + str(cell_index)). \ + set_sensitive(True) + self._canvas.widgetTree.get_widget('fitness_' + str(cell_index)). \ + set_value(self.model.fitness[cell_index]) + + def on_flurry_value_changed(self, *args): + """ + pre: len(args) >= 1 + pre: 0 <= args[0].get_value() <= 9 + """ + ka_debug.info('on_flurry_value_changed [%s]' % args[0].get_value()) + ka_random.set_flurry(args[0].get_value()) + + def on_protozoon_popup(self, widget, event): + ka_debug.info('on_protozoon_popup: ' + widget.name) + self._show_popup(widget, event, \ + 'protozoon_menu_%u' % _name_to_index(widget.name)) + + def on_publishprotozoon_activate(self, *args): + """Publish single protozoon to all other buddies. + pre: len(args) >= 1 + """ + ka_debug.info('on_publishprotozoon_activate [%s]' % args[0].get_name()) + if self._tube: + proto = self.model.protozoans[_name_to_index(args[0].get_name())] + self._tube.publish_protozoon(model_population.to_buffer(proto)) + + def on_favorite_activate(self, *args): + """Set best ranking for this protozoon. + pre: len(args) >= 1 + """ + ka_debug.info('on_favorite_activate [%s]' % args[0].get_name()) + self.model.raise_fitness(_name_to_index(args[0].get_name())) + self._update_gui() + + def on_awfull_activate(self, *args): + """Set last ranking for this protozoon. + pre: len(args) >= 1 + """ + ka_debug.info('on_awfull_activate [%s]' % args[0].get_name()) + self.model.reduce_fitness(_name_to_index(args[0].get_name())) + self._update_gui() + + def on_received(self, code_type, code_element, code_md5): + """Update population or protozoon preview when received from others.""" + ka_debug.info('on_received: Received %u bytes, type: [%s] md5: [%s]' % \ + (len(code_element), code_type, code_md5)) + if hashlib.md5(code_element).hexdigest() == code_md5: + inbox_widget = self._canvas.widgetTree.get_widget('incomingBox') + if code_type == kandidtube.SEND_POPULATION: + if self.is_overwrite_allowed: + self._update_model(model_population.from_buffer(code_element)) + self.model._state = model_population.STATE_EVOLVED + inbox_widget.queue_draw() + else: + ka_debug.info("I've already an evolved population, doing nothing") + elif code_type == kandidtube.SEND_PROTOZOON: + self.incoming.append_protozoon(model_population.from_buffer(code_element)) + inbox_widget.queue_draw() + else: + ka_debug.info('Somebody called me using an illegal type [%s]' \ + % code_type) + else: + ka_debug.info('Somebody called me with an corrupt data model') + + def on_new_tube(self, tube, is_initiator, get_buddy): + """Creates communication object and sends population + pre: tube > 0 + pre: get_buddy is not None + """ + self._tube = kandidtube.KandidTube(self, + tube, + is_initiator, + get_buddy) + +# Event handling for incoming protozoans + def on_incoming_popup(self, widget, event): + ka_debug.info('on_incoming_popup: ' + widget.name) + index = _name_to_index(widget.name) + self._show_popup(widget, event, 'incoming_menu_'+ str(index)) + + def _show_popup(self, widget, event, menu): + ka_debug.info('%s [%s]' % (menu, widget.name)) + popup_menu = self._canvas.widgetTree.get_widget(menu) + popup_menu.popup(None, None, None, event.button, event.time) + + def on_accept_incoming(self, menu_item): + ka_debug.info('on_accept_incoming [%s]' % menu_item.parent.name) + new_at = self.incoming.accept_protozoon(self.model, \ + _name_to_index(menu_item.parent.name)) + self._start_calculation([new_at]) +# self._update_gui() + inbox_widget = self._canvas.widgetTree.get_widget('incomingBox') + inbox_widget.queue_draw() + + def on_decline_incoming(self, menu_item): + ka_debug.info('on_decline_incoming [%s]' % menu_item.parent.name) + self.incoming.decline_protozoon(_name_to_index(menu_item.parent.name)) + inbox_widget = self._canvas.widgetTree.get_widget('incomingBox') + inbox_widget.queue_draw() + + def on_incomingarea_expose(self, widget, event): + """ Repaint image of a single protozoon inside incoming area. + pre: widget is not None + """ + ka_debug.info('on_incomingarea_expose: ' + widget.name) + self.incoming.draw(_name_to_index(widget.name), \ + self._create_context(widget), \ + widget.allocation.width, widget.allocation.height) + +# data model and persistence + def is_overwrite_allowed(self): + """Preserve an already evolved population from over writing.""" + return self.model.is_overwrite_allowed() + + def serialize_model(self): + """Serialize population to a string buffer.""" + return model_population.to_buffer(self.model) + + def read_file(self, file_path): + """Delegate reading from journal to data model + pre: (file_path is not None) and (len(file_path) >= 1) + """ + self._update_model(model_population.read_file(file_path)) + + def write_file(self, file_path): + """Delegate writing data model to journal + pre: (file_path is not None) and (len(file_path) >= 1) + """ + model_population.write_file(file_path, self.model) + +def _name_to_index(name): + """Extract index from last part of a name. + Parts are separated by _. + pre: name[-1].isdigit() + post: __return__ >= 0 + """ + return int(name.rsplit('_')[-1]) diff --git a/ka_debug.py b/ka_debug.py new file mode 100644 index 0000000..a29acb5 --- /dev/null +++ b/ka_debug.py @@ -0,0 +1,75 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import logging +import os +import time + +# default path for testing on local machine +DEBUG_PATH = '/home/strom/minimal/activities/Kandid.activity' +DBC_BLACK_LIST = ['activity', 'ka_debug', 'kandidtube', 'setup', + 'test_suite', 'test_enumerator'] +_logger = None +_start_time = time.time() +_last_clock = 0.0 + +def _avtivate_logger(): + global _logger + if not _logger: + _logger = logging.getLogger('Kandid') + _logger.setLevel(logging.DEBUG) + +def info(msg): + global _last_clock + clock_now = time.clock() + print 'debug', int((time.time()-_start_time)*1000), int((clock_now-_last_clock)*1000), ':', msg + _last_clock = clock_now + _avtivate_logger() + _logger.debug(msg) + +def err(msg): + global _last_clock + clock_now = time.clock() + print 'error', int((time.time()-_start_time)*1000), int((clock_now-_last_clock)*1000), ':', msg + _last_clock = clock_now + _avtivate_logger() + _logger.error(msg) + +# Add support for design by contract. +if True: +#if False: + try: + import contract, doctest + def enable_contact(module_name): + if module_name not in DBC_BLACK_LIST: + a_modul = __import__(module_name) + contract.checkmod(module_name) + doctest.testmod(a_modul) + # print 'enable_contact', a_modul + + bundle_path = DEBUG_PATH + if 'SUGAR_BUNDLE_PATH' in os.environ: + from sugar.activity import activity + bundle_path = activity.get_bundle_path() + for element in os.listdir(bundle_path): + if element.endswith('.py') \ + and os.path.isfile(os.path.join(bundle_path, element)): + name_parts = element.split('.') + if len(name_parts) == 2: + enable_contact(name_parts[0]) + except ImportError: + print "unsupported design by contract" diff --git a/ka_extensionpoint.py b/ka_extensionpoint.py new file mode 100644 index 0000000..1f6067f --- /dev/null +++ b/ka_extensionpoint.py @@ -0,0 +1,81 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +#import types +import ka_debug + +_PREFIX = 'ep_' +_PYEXT = '.py' +_SEP = '_' + +_extension_types = [] +_extensions = [] + +def list_extensions(extension_type): + """ + pre: extension_type in _extension_types + """ + e_list = [] + for extension_class in _extensions: + if extension_class.startswith(extension_type + _SEP): + e_list.append(extension_class) + e_list.sort() + return e_list + +def list_extension_types(): + return _extension_types + +def create(extension_key, *params): + """ + pre: extension_key in _extensions + post: __return__ is not None + """ + a_module = __import__('ep_' + extension_key) + for key, value in a_module.__dict__.iteritems(): +# print '-- ', str(type(value)), key + if str(type(value)) == "": + a_class = getattr(a_module, key) + return a_class(*params) +# if len(params) == 0: +# return a_class() +# elif len(params) == 1: +# return a_class(params[0]) + +def _add(extension_type, extension_class): + if extension_type not in _extension_types: + _extension_types.append(extension_type) + e_key = extension_type + _SEP + extension_class + if e_key not in _extensions: + _extensions.append(e_key) + +def scann(): + bundle_path = ka_debug.DEBUG_PATH + if 'SUGAR_BUNDLE_PATH' in os.environ: + from sugar.activity import activity + bundle_path = activity.get_bundle_path() + ka_debug.info('searching for extensions in ' + bundle_path) + for element in os.listdir(bundle_path): + if element.startswith(_PREFIX) and element.endswith(_PYEXT) \ + and os.path.isfile(os.path.join(bundle_path, element)): + name_parts = element.split(_SEP) + if len(name_parts) == 3: + _add(name_parts[1], name_parts[2].replace(_PYEXT, '')) + _extension_types.sort() + _extensions.sort() + +scann() \ No newline at end of file diff --git a/ka_factory.py b/ka_factory.py new file mode 100644 index 0000000..21f50d4 --- /dev/null +++ b/ka_factory.py @@ -0,0 +1,73 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import random +import ka_extensionpoint + +class LayerFactory(object): + """ + inv: self.factory_keys is not None + """ + def __init__(self, category): + """ LayerFactory is a singleton. + Please call ka_factory.get_factory(category) to retrieve the instance. + """ + self.category = category + self.factory_keys = ka_extensionpoint.list_extensions(self.category) + self.factory_keys = map(lambda x: x.replace(self.category + '_', ''), \ + self.factory_keys) + + def count(self): + """ + post: __return__ > 0 + """ + return len(self.factory_keys) + + def keys(self): + """ + """ + return self.factory_keys + + def create(self, factory_key, trunk): + """ + pre: factory_key in self.factory_keys + post: __return__ is not None + """ + return ka_extensionpoint.create(self.category + '_' + factory_key, trunk) + + def create_random(self, key_filter, trunk): + """ + pre: key_filter is not None + pre: len(key_filter) > 0 + post: __return__ is not None + """ + permitted = [x for x in self.factory_keys if x in key_filter] + return self.create(random.choice(permitted), trunk) + +_factory_index = {} + +def get_factory(category): + """ + pre: category is not None + post: __return__ is not None + """ +# global _factory_index +# if _factory_index is None: +# _factory_index = {} + if not _factory_index.has_key(category): + _factory_index[category] = LayerFactory(category) + return _factory_index[category] diff --git a/ka_importer.py b/ka_importer.py new file mode 100644 index 0000000..ece269f --- /dev/null +++ b/ka_importer.py @@ -0,0 +1,46 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os + +_rgb_image_list = [] +_alpha_image_list = [] + +def _populate_image_list(): + #TODO use correct folder for user files + image_path = '/home/strom/.sugar/1/net.sourceforge.kandid/instance' + if 'SUGAR_BUNDLE_PATH' in os.environ: + from sugar.activity import activity + image_path = os.path.join(activity.get_activity_root(), 'instance') + for element in os.listdir(image_path): + if element.endswith('.png') or element.endswith('.PNG'): + abs_name = os.path.join(image_path, element) + if os.path.isfile(abs_name): + if element.find('.alpha.') == -1: + _rgb_image_list.append(abs_name) + else: + _alpha_image_list.append(abs_name) + +def get_rgb_image_list(): + if len(_rgb_image_list) == 0: + _populate_image_list() + return _rgb_image_list + +def get_alpha_image_list(): + if len(_alpha_image_list) == 0: + _populate_image_list() + return _alpha_image_list diff --git a/ka_incoming.py b/ka_incoming.py new file mode 100644 index 0000000..a97dbc4 --- /dev/null +++ b/ka_incoming.py @@ -0,0 +1,68 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import ka_debug + +class KandidIncoming(object): + """ + inv: 0 <= len(self.incoming) <= self.capacity + inv: self.capacity > 0 + """ + + def __init__(self, capacity): + self.capacity = capacity + self.incoming = [] + + def append_protozoon(self, incoming_protozoon): + """ Append protozoon from incoming list and manage capacity. + pre: incoming_protozoon is not None + """ + ka_debug.info('Append protozoon') + while len(self.incoming) >= self.capacity: + self.incoming[0:1] = [] + self.incoming.append(incoming_protozoon) + + def accept_protozoon(self, model, index): + """ Move protozoon from incoming list to image population. + pre: 0 <= index < self.capacity + """ + new_at = -1 + if index < len(self.incoming): + ka_debug.info('accept incoming protozoon %u' % index) + new_at = model.replace(self.incoming[index]) + self.incoming[index:1] = [] + return new_at + + def decline_protozoon(self, index): + """ Remove protozoon from incoming list + pre: 0 <= index < self.capacity + """ + if index < len(self.incoming): + ka_debug.info('decline incoming protozoon %u' % index) + self.incoming[index:1] = [] + + def draw(self, index, ctx, width, height): + """ Repaint all protozoon images inside incoming area. + pre: 0 <= index < self.capacity + pre: ctx is not None + pre: width > 0 + pre: height > 0 + """ + if index < len(self.incoming): + # draw protozoon + self.incoming[index].draw(ctx, width, height) + diff --git a/ka_random.py b/ka_random.py new file mode 100644 index 0000000..fde952a --- /dev/null +++ b/ka_random.py @@ -0,0 +1,163 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import random +import math + +crossing_probability = 0.75 +shuffling_probability = 0.10 +mutating_probability = 0.25 + +_flurry = 0.5 + +def set_flurry(flurry_rate): + """ + pre: 0 <= flurry_rate <= 9 + post: 0.0 <= _flurry <= 1.0 + """ + global _flurry + _flurry = flurry_rate / 10.0 + +def get_flurry(): + """ + post: 0 <= __return__ <= 9 + """ + return _flurry * 10.0 + +def jitter(sigma): + """ + """ + return 0.03 * _flurry * random.gauss(0.0, sigma) + +def jitter_constrained(constraint): + """ + pre: len(constraint) == 2 + pre: constraint[0] <= constraint[1] + """ +# mean = 0.5 * (constraint[0] + constraint[1]) + sigma = 0.03 * _flurry * (constraint[1] - constraint[0]) + return random.gauss(0.0, sigma) + +def uniform_constrained(constraint): + """ + pre: len(constraint) == 2 + pre: constraint[0] <= constraint[1] + """ + return random.uniform(constraint[0], constraint[1]) + +def is_shuffling(): + return random.random() < shuffling_probability * _flurry + +def is_mutating(): + return random.random() < mutating_probability * _flurry + +def is_crossing(): + return random.random() < crossing_probability * _flurry + +def crossing_sequence(size): + """Produces a sequence filled with True or False elements. + With a low crossing_probability there will be lesser change overs from + True series to False series. + pre: size >= 1 + post: len(__return__) == size + post: forall(__return__, lambda x: x is True or x is False) + """ + sample = [random.choice([False, True])] + for i in range(size-1): + if is_crossing(): + sample.append(not sample[-1]) + else: + sample.append(sample[-1]) + return sample + +def crossingover(first, second): + """ + pre: len(first) >= 1 + pre: len(second) >= 1 + post: min([len(first), len(second)]) <= len(__return__) <= max([len(first), len(second)]) + post: forall(__return__, lambda x: x in first or x in second) + """ + seq = [] + len_first, len_second = len(first), len(second) + len_max = max([len_first, len_second]) + randseq = crossing_sequence(len_max) + for index in range(len_max): + if randseq[index]: + if index < len_first: + seq.append(first[index]) + else: + if index < len_second: + seq.append(second[index]) + return seq + +def limitate(value): + """ + post: 0.0 <= __return__ <= 1.0 + """ + if value < 0.0: + value = 0.0 + elif value > 1.0: + value = 1.0 + return value + +def cyclic_limitate(value): + """ + post: 0.0 <= __return__ <= 1.0 + """ + return _cyclic_limitate(value, 0.0, 1.0) + +def radian_limitate(value): + """ + post: (-1.0*math.pi) <= __return__ <= math.pi + """ + return _cyclic_limitate(value, -1.0*math.pi, math.pi) + +def _cyclic_limitate(value, lower_bound, upper_bound): + """ + post: lower_bound <= __return__ <= upper_bound + """ + while value < lower_bound: + value += upper_bound-lower_bound + while value > upper_bound: + value -= upper_bound-lower_bound + return value + +def limitate_range(value, min_value, max_value): + """ + pre: min_value <= max_value + post: min_value <= __return__ <= max_value + """ + if value < min_value: + value = min_value + elif value > max_value: + value = max_value + return value + +def copy_list(other_list): + """Make a deep copy of a list and its elements. + pre: other_list is not None + post: __return__ is not other_list + post: len(__return__) == len(other_list) + post: forall([other_list[i] is not __return__[i] for i in range(len(other_list))]) + post: forall([other_list[i] == __return__[i] for i in range(len(other_list))]) + """ + new_list = [] + for element in other_list: + new_list.append(element.copy()) +# for i in range(len(other_list)): +# print other_list[i] == new_list[i], other_list[i], new_list[i] + return new_list diff --git a/ka_task.py b/ka_task.py new file mode 100644 index 0000000..b18212b --- /dev/null +++ b/ka_task.py @@ -0,0 +1,78 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import sys +import threading +import gobject +import ka_debug + +#gtk.gdk.threads_init() + +_internal_lock = threading.Lock() +_internal_task_count = 0 + +def _enter(): + """ + pre: _internal_task_count >= 0 + post: _internal_task_count >= 1 + """ + _internal_lock.acquire() + global _internal_task_count + _internal_task_count += 1 + _internal_lock.release() + +def _leave(): + """ + pre: _internal_task_count >= 1 + post: _internal_task_count >= 0 + """ + _internal_lock.acquire() + global _internal_task_count + _internal_task_count -= 1 + _internal_lock.release() + +def is_completed(): + """ + pre: _internal_task_count >= 0 + """ + global _internal_task_count + return _internal_task_count < 1 + +class GeneratorTask(object): + + def __init__(self, task_function, on_task_completed): + """ + pre: task_function is not None and callable(task_function) + pre: on_task_completed is not None and callable(on_task_completed) + """ + self._on_task_completed = on_task_completed + self._task_function = task_function + + def _start(self, *args, **kwargs): + try: + _enter() + result = self._task_function(*args) + print 'result', result + gobject.idle_add(self._on_task_completed, result) + except: + ka_debug.err('failed calculating [%s] [%s] [%s]' % \ + (self._task_function, sys.exc_info()[0], sys.exc_info()[1])) + finally: + _leave() + + def start(self, *args, **kwargs): + threading.Thread(target=self._start, args=args, kwargs=kwargs).start() diff --git a/kandid.glade b/kandid.glade new file mode 100644 index 0000000..127a0f5 --- /dev/null +++ b/kandid.glade @@ -0,0 +1,1531 @@ + + + + + + + + True + 8 + 0 + GTK_SHADOW_NONE + + + True + + + True + True + + + True + + + True + + + True + 5 + 5 + GTK_BUTTONBOX_START + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Breed + 0 + + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Random + 0 + + + + 1 + + + + + True + 0 + 1 + Flurry rate: + + + 2 + + + + + True + True + 0 0 9 1 3 0 + + + + 3 + + + + + True + + + 4 + + + + + False + 5 + + + + + True + + + True + + + True + True + True + 0 + + + + True + GTK_ARROW_DOWN + + + + + False + False + + + + + True + + + 1 + + + + + False + False + + + + + True + 0 + GTK_SHADOW_NONE + + + True + + + + + + 1 + + + + + True + + + True + True + True + 0 + + + + True + GTK_ARROW_DOWN + + + + + False + False + + + + + True + + + 1 + + + + + False + False + 2 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + + + + + + 3 + + + + + True + + + True + True + True + 0 + + + + True + GTK_ARROW_DOWN + + + + + False + False + + + + + True + + + 1 + + + + + False + False + 4 + + + + + True + 0 + GTK_SHADOW_NONE + + + True + + + + + + 5 + + + + + GTK_PACK_END + 1 + + + + + False + 5 + + + + + True + 5 + 3 + 4 + 5 + 5 + True + + + True + + + True + + + True + True + True + 0 + + + + True + GTK_ARROW_DOWN + + + + + False + + + + + True + True + GTK_UPDATE_DISCONTINUOUS + 1 0 10 1 1 1 + 0 + GTK_POS_RIGHT + + + + 5 + 1 + + + + + False + + + + + True + 0 + GTK_SHADOW_NONE + + + True + + + + + + + 1 + + + + + 1 + 2 + + + + + True + + + True + + + True + True + True + 0 + + + + True + GTK_ARROW_DOWN + + + + + False + + + + + True + True + GTK_UPDATE_DISCONTINUOUS + 1 0 10 1 1 1 + 0 + GTK_POS_RIGHT + + + + 5 + 1 + + + + + False + + + + + True + 0 + GTK_SHADOW_NONE + + + True + + + + + + + 1 + + + + + 2 + 3 + + + + + True + + + True + + + True + True + True + 0 + + + + True + GTK_ARROW_DOWN + + + + + False + + + + + True + True + GTK_UPDATE_DISCONTINUOUS + 1 0 10 1 1 1 + 0 + GTK_POS_RIGHT + + + + 5 + 1 + + + + + False + + + + + True + 0 + GTK_SHADOW_NONE + + + True + + + + + + + 1 + + + + + 1 + 2 + + + + + True + + + True + + + True + True + True + 0 + + + + True + GTK_ARROW_DOWN + + + + + False + + + + + True + True + GTK_UPDATE_DISCONTINUOUS + 1 0 10 1 1 1 + 0 + GTK_POS_RIGHT + + + + 5 + 1 + + + + + False + + + + + True + 0 + GTK_SHADOW_NONE + + + True + + + + + + + 1 + + + + + 1 + 2 + 1 + 2 + + + + + True + + + True + + + True + True + True + 0 + + + + True + GTK_ARROW_DOWN + + + + + False + + + + + True + True + GTK_UPDATE_DISCONTINUOUS + 1 0 10 1 1 1 + 0 + GTK_POS_RIGHT + + + + 5 + 1 + + + + + False + + + + + True + 0 + GTK_SHADOW_NONE + + + True + + + + + + + 1 + + + + + 1 + 2 + 2 + 3 + + + + + True + + + True + + + True + True + True + 0 + + + + True + GTK_ARROW_DOWN + + + + + False + + + + + True + True + GTK_UPDATE_DISCONTINUOUS + 1 0 10 1 1 1 + 0 + GTK_POS_RIGHT + + + + 5 + 1 + + + + + False + + + + + True + 0 + GTK_SHADOW_NONE + + + True + + + + + + + 1 + + + + + 2 + 3 + + + + + True + + + True + + + True + True + True + 0 + + + + True + GTK_ARROW_DOWN + + + + + False + + + + + True + True + GTK_UPDATE_DISCONTINUOUS + 1 0 10 1 1 1 + 0 + GTK_POS_RIGHT + + + + 5 + 1 + + + + + False + + + + + True + 0 + GTK_SHADOW_NONE + + + True + + + + + + + 1 + + + + + 2 + 3 + 1 + 2 + + + + + True + + + True + + + True + True + True + 0 + + + + True + GTK_ARROW_DOWN + + + + + False + + + + + True + True + GTK_UPDATE_DISCONTINUOUS + 1 0 10 1 1 1 + 0 + GTK_POS_RIGHT + + + + 5 + 1 + + + + + False + + + + + True + 0 + GTK_SHADOW_NONE + + + True + + + + + + + 1 + + + + + 2 + 3 + 2 + 3 + + + + + True + + + True + + + True + True + True + 0 + + + + True + GTK_ARROW_DOWN + + + + + False + + + + + True + True + GTK_UPDATE_DISCONTINUOUS + 1 0 10 1 1 1 + 0 + GTK_POS_RIGHT + + + + 5 + 1 + + + + + False + + + + + True + 0 + GTK_SHADOW_NONE + + + True + + + + + + + 1 + + + + + 3 + 4 + + + + + True + + + True + + + True + True + True + 0 + + + + True + GTK_ARROW_DOWN + + + + + False + + + + + True + True + GTK_UPDATE_DISCONTINUOUS + 1 0 10 1 1 1 + 0 + GTK_POS_RIGHT + + + + 5 + 1 + + + + + False + + + + + True + 0 + GTK_SHADOW_NONE + + + True + + + + + + + 1 + + + + + 3 + 4 + 1 + 2 + + + + + True + + + True + + + True + True + True + 0 + + + + True + GTK_ARROW_DOWN + + + + + False + + + + + True + True + GTK_UPDATE_DISCONTINUOUS + 1 0 10 1 1 1 + 0 + GTK_POS_RIGHT + + + + 5 + 1 + + + + + False + + + + + True + 0 + GTK_SHADOW_NONE + + + True + + + + + + + 1 + + + + + 3 + 4 + 2 + 3 + + + + + True + + + True + + + True + True + True + 0 + + + + True + GTK_ARROW_DOWN + + + + + False + + + + + True + True + GTK_UPDATE_DISCONTINUOUS + 1 0 10 1 1 1 + 0 + GTK_POS_RIGHT + + + + 5 + 1 + + + + + False + + + + + True + 0 + GTK_SHADOW_NONE + + + True + + + + + + + 1 + + + + + + + 1 + + + + + + + True + Popolation + + + tab + False + + + + + True + 4 + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + + + True + True + False + False + + + + + + + 1 + + + + + True + Introduction + + + tab + 1 + False + + + + + + + + + + + True + + + True + My favorite + True + + + + + + True + Awful bore + True + + + + + + True + Publish to a friend + True + + + + + + True + + + True + My favorite + True + + + + + + True + Awful bore + True + + + + + + True + Publish to a friend + True + + + + + + True + + + True + My favorite + True + + + + + + True + Awful bore + True + + + + + + True + Publish to a friend + True + + + + + + True + + + True + My favorite + True + + + + + + True + Awful bore + True + + + + + + True + Publish to a friend + True + + + + + + True + + + True + My favorite + True + + + + + + True + Awful bore + True + + + + + + True + Publish to a friend + True + + + + + + True + + + True + My favorite + True + + + + + + True + Awful bore + True + + + + + + True + Publish to a friend + True + + + + + + True + + + True + My favorite + True + + + + + + True + Awful bore + True + + + + + + True + Publish to a friend + True + + + + + + True + + + True + My favorite + True + + + + + + True + Awful bore + True + + + + + + True + Publish to a friend + True + + + + + + True + + + True + My favorite + True + + + + + + True + Awful bore + True + + + + + + True + Publish to a friend + True + + + + + + True + + + True + My favorite + True + + + + + + True + Awful bore + True + + + + + + True + Publish to a friend + True + + + + + + True + + + True + My favorite + True + + + + + + True + Awful bore + True + + + + + + True + Publish to a friend + True + + + + + + True + + + True + My favorite + True + + + + + + True + Awful bore + True + + + + + + True + Publish to a friend + True + + + + + + True + incomingbutton_0 + + + True + Accept protozoon + True + + + + + + True + Decline protozoon + True + + + + + + True + incomingbutton_1 + + + True + Accept protozoon + True + + + + + + True + Decline protozoon + True + + + + + + True + incomingbutton_2 + + + True + Accept protozoon + True + + + + + + True + Decline protozoon + True + + + + + diff --git a/kandid.py b/kandid.py new file mode 100644 index 0000000..46151bd --- /dev/null +++ b/kandid.py @@ -0,0 +1,80 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# This source code is based on the helloworld.py example from +# PyGTK 2.0 Tutorial. see: http://www.pygtk.org/pygtk2tutorial/ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import pygtk +pygtk.require('2.0') +import gtk +gtk.gdk.threads_init() +import ka_debug +import ka_canvas +import ka_controller + +TESTMODEL = '/home/strom/tmp/kmodel' + +class KandidApplication(object): + + def delete_event(self, widget, event, data=None): + # If you return FALSE in the "delete_event" signal handler, + # GTK will emit the "destroy" signal. Returning TRUE means + # you don't want the window to be destroyed. + # This is useful for popping up 'are you sure you want to quit?' + # type dialogs. + ka_debug.info('Kandid test application: delete event occurred') + # Change FALSE to TRUE and the main window will not be destroyed + # with a "delete_event". + return False + + def destroy(self, widget, data=None): + ka_debug.info('Kandid test application: destroy signal occurred') + self._controller.write_file(TESTMODEL) + gtk.main_quit() + + def __init__(self): + # create a new window + self._window = gtk.Window(gtk.WINDOW_TOPLEVEL) + self._window.connect('destroy', self.destroy) + + # Create GUI + self._canvas = ka_canvas.KandidCanvas() + self._window.add(self._canvas.widget) + # Create a controller to connect view and model + self._controller = ka_controller.KandidController(self._canvas) + self._controller.read_file(TESTMODEL) + + # Display everything + self._canvas.widget.show() + self._window.set_default_size(1200, 800) + self._window.show() + + def main(self): + """ + All PyGTK applications must have a gtk.main(). Control ends here + and waits for an event to occur (like a key press or mouse event). + """ + gtk.main() + + +# If the program is run directly or passed as an argument to the python +# interpreter then create a Kandid instance and show it +if __name__ == "__main__": + ka_debug.info('starting Minimal Kandid') + application = KandidApplication() + application.main() + + diff --git a/kandidtube.py b/kandidtube.py new file mode 100644 index 0000000..67c0dba --- /dev/null +++ b/kandidtube.py @@ -0,0 +1,132 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import hashlib +import base64 + +from dbus.service import method, signal +from dbus.gobject_service import ExportedGObject +import ka_debug + +SERVICE = 'net.sourceforge.kandid' +IFACE = SERVICE +PATH = '/net/sourceforge/kandid/minimal' + +SEND_POPULATION = 'P' +SEND_PROTOZOON = 'I' + +class KandidTube(ExportedGObject): + """The bit that talks over the tubes""" + + def __init__(self, controller, tube, is_initiator, get_buddy): + super(KandidTube, self).__init__(tube, PATH) + self._controller = controller + self._tube = tube + self._is_initiator = is_initiator + self._get_buddy = get_buddy # Converts handle to Buddy object + self._entered = False # Have we set up the tube? + self._tube.watch_participants(self.on_participant_change) + + def on_participant_change(self, added, removed): + if added: + ka_debug.info('Tube: Added participants: %r' % added) + if removed: + ka_debug.info('Tube: Removed participants: %r' % removed) + for handle, bus_name in added: + buddy = self._get_buddy(handle) + if buddy is not None: + ka_debug.info('Tube: Handle %u (Buddy [%s]) was added' % \ + (handle, buddy.props.nick)) + for handle in removed: + buddy = self._get_buddy(handle) + if buddy is not None: + ka_debug.info('Buddy [%s] was removed' % buddy.props.nick) + if not self._entered: + if self._is_initiator: + ka_debug.info("I'm initiating the tube, will watch for hellos.") + self.add_hello_handler() + self.add_publish_handler() + else: + ka_debug.info('Hello, everyone! What did I miss?') + self.Hello() + self.add_publish_handler() + self._entered = True + + def add_hello_handler(self): + ka_debug.info('I am initiating. Adding hello handler.') + self._tube.add_signal_receiver(self.on_hello, 'Hello', + IFACE, path=PATH, sender_keyword='sender') + + def add_publish_handler(self): + ka_debug.info('I am entered. Adding input handler.') + self._tube.add_signal_receiver(self.on_publish_protozoon, 'PublishProtozoon', + IFACE, path=PATH, sender_keyword='sender') + + @signal(dbus_interface=IFACE, signature='') + def Hello(self): + """Say Hello to whoever else is in the tube.""" + ka_debug.info('I said Hello.') + + def on_hello(self, sender=None): + """Somebody helloed me. Send them my population.""" + if sender == self._tube.get_unique_name(): + # sender is my bus name, so ignore my own signal + return + ka_debug.info('on_hello: Newcomer [%s] has joined. World them.' % sender) + self.send_population(self._controller.serialize_model(), sender) + + def publish_protozoon(self, code_element): + ka_debug.info('publish_protozoon: %s %s' % (type(code_element), code_element)) + code_element_base64 = base64.b64encode(code_element) + code_md5 = hashlib.md5(code_element).hexdigest() + ka_debug.info('publish_protozoon: Sent %u bytes, type: [%s] md5: [%s]' % \ + (len(code_element), SEND_PROTOZOON, code_md5)) + self.PublishProtozoon(SEND_PROTOZOON, code_element_base64, code_md5) + + @signal(dbus_interface=IFACE, signature='sss') + def PublishProtozoon(self, code_type, code_element, code_md5): + """Send protozoon.""" + ka_debug.info('PublishProtozoon: I published %u bytes, type: [%s] md5: [%s]' \ + % (len(code_element), code_type, code_md5)) + + def on_publish_protozoon(self, code_type, code_element, code_md5, sender=None): + """Somebody published. Process received parameters.""" + if sender == self._tube.get_unique_name(): + # sender is my bus name, so ignore my own signal + return + ka_debug.info('on_publish: I got %u bytes, type: [%s] md5: [%s]' \ + % (len(code_element), code_type, code_md5)) + dec = base64.b64decode(code_element) + self._controller.on_received(code_type, dec, code_md5) + + def send_population(self, code_element, sender=None): + code_element_base64 = base64.b64encode(code_element) + code_md5 = hashlib.md5(code_element).hexdigest() + ka_debug.info('send_population: Sent %u bytes, type: [%s] md5: [%s]' % \ + (len(code_element), SEND_POPULATION, code_md5)) + self._tube.get_object(sender, PATH).SendPopulation(SEND_POPULATION, + code_element_base64, + code_md5) + + #@signal(dbus_interface=IFACE, signature='sss') + @method(dbus_interface=IFACE, in_signature='sss', out_signature='') + def SendPopulation(self, code_type, code_element, code_md5): + """Send to all participants.""" + ka_debug.info('SendPopulation: Sent %u bytes, type: [%s] md5: [%s]' \ + % (len(code_element), code_type, code_md5)) + dec = base64.b64decode(code_element) + self._controller.on_received(code_type, dec, code_md5) diff --git a/locale/en/LC_MESSAGES/net.sourceforge.kandid.mo b/locale/en/LC_MESSAGES/net.sourceforge.kandid.mo new file mode 100644 index 0000000..5bfb918 --- /dev/null +++ b/locale/en/LC_MESSAGES/net.sourceforge.kandid.mo Binary files differ diff --git a/locale/en/activity.linfo b/locale/en/activity.linfo new file mode 100644 index 0000000..4b628b7 --- /dev/null +++ b/locale/en/activity.linfo @@ -0,0 +1,2 @@ +[Activity] +name = Kandid diff --git a/model_allele.py b/model_allele.py new file mode 100644 index 0000000..ffe3cb2 --- /dev/null +++ b/model_allele.py @@ -0,0 +1,32 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import model_locus + +class Allele(model_locus.Locus): + + def __init__(self, root): + super(Allele, self).__init__(root) + + def __eq__(self, other): + raise TypeError("Use only __eq__ from derived subclasses.") + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + raise TypeError("Genome objects are unhashable") diff --git a/model_constraintpool.py b/model_constraintpool.py new file mode 100644 index 0000000..12eca65 --- /dev/null +++ b/model_constraintpool.py @@ -0,0 +1,143 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import model_population + +#_CONSTRAINT = 'constraint' + +INT_1_OF_N = 11 +INT_M_OF_N = 12 +INT_RANGE = 13 +FLOAT_RANGE = 22 +STRING_1_OF_N = 31 +STRING_M_OF_N = 32 + +class ConstraintPool(object): + """ConstraintPool is a singleton. + Use ConstraintPool.get_pool() to get an instance. + """ + + _constraintpool = None + _known_keys = None + + def __init__(self): + self._overwrite = {} + self._my_defaults() + + @staticmethod + def get_pool(): + if ConstraintPool._constraintpool == None: + ConstraintPool._constraintpool = ConstraintPool() + return ConstraintPool._constraintpool + + def _generate_key(self, class_key, attribute_key): + """ + pre: class_key is not None + pre: attribute_key is not None + """ + return class_key + '/' + attribute_key + + def get(self, caller, attribute_key): + """ + #pre: 'cdef' in dir(caller) or 'base_cdef' in dir(caller) + pre: True if 'cdef' not in dir(caller) else caller.cdef is not None and len(caller.cdef) > 0 + pre: True if 'base_cdef' not in dir(caller) else caller.base_cdef is not None and len(caller.base_cdef) > 0 + pre: caller.path is not None + pre: attribute_key is not None + post: __return__ is not None + """ + primary_key = self._generate_key(caller.path, attribute_key) + result = None + if self._overwrite.has_key(primary_key): + result = self._overwrite[primary_key] +# print '>> over', primary_key, result + else: + if 'cdef' in dir(caller): + result = self._search_constraint(caller.cdef, attribute_key) + if result is None and 'base_cdef' in dir(caller): + result = self._search_constraint(caller.base_cdef, attribute_key) +# print '>> cdef', primary_key, result + if result is not None: + self._add_key(primary_key) + return result + + def _search_constraint(self, caller, attribute_key): + result = None + for definition in caller: + if definition['bind'] == attribute_key: + if definition['domain'] == INT_1_OF_N: + result = [x[1] for x in definition['enum']] + if definition['domain'] == INT_M_OF_N: + result = [x[1] for x in definition['enum']] + if definition['domain'] == INT_RANGE: + result = (definition['min'], definition['max']) + if definition['domain'] == FLOAT_RANGE: + result = (definition['min'], definition['max']) + if definition['domain'] == STRING_1_OF_N: + result = definition['enum'] + if definition['domain'] == STRING_M_OF_N: + result = definition['enum'] + return result + + def is_overwritten(self, caller, attribute_key): + return False + + def set(self, class_key, attribute_key, constraint): + """ + pre: class_key is not None + pre: class_key.startswith('/') + pre: attribute_key is not None + pre: constraint is not None + """ + primary_key = self._generate_key(class_key, attribute_key) + self._overwrite[primary_key] = constraint + + def clear_all(self): + """ + """ + self._overwrite = {} + + def listknown_keys(self): + """ + """ + return [] if ConstraintPool._known_keys is None \ + else ConstraintPool._known_keys + + def _add_key(self, key): + """ + """ + file_path = '/dev/shm/minmal_known_keys.mkey' + if ConstraintPool._known_keys is None: + ConstraintPool._known_keys = model_population.read_file(file_path) + if ConstraintPool._known_keys is None: + ConstraintPool._known_keys = [] + if key not in ConstraintPool._known_keys: + ConstraintPool._known_keys.append(key) + model_population.write_file(file_path, ConstraintPool._known_keys) + + def _my_defaults(self): + self.set('/Protozoon', 'layertypeconstraint', ['image', 'letterpress', 'markovchain', ]) + self.set('/Protozoon', 'mergertypeconstraint', ['layermask', ]) + #TODO read from persistence, provide an constraint editor +# self.set('/Protozoon', 'layertypeconstraint', ['referencepattern', 'image', 'letterpress', 'markovchain', ]) +# self.set('/Protozoon', 'layertypeconstraint', ['referencepattern']) +# self.set('/Protozoon', 'layertypeconstraint', ['image', ]) + +# self.set('/Protozoon', 'mergertypeconstraint', ['flip', ]) +# self.set('/Protozoon', 'mergertypeconstraint', ['flip', 'rectangulartile', 'mask', 'straight', 'border', ]) +# self.set('/Protozoon', 'mergertypeconstraint', ['alphaimage', ]) + pass diff --git a/model_layer.py b/model_layer.py new file mode 100644 index 0000000..5d2eb25 --- /dev/null +++ b/model_layer.py @@ -0,0 +1,167 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import time +import random +import cairo +import ka_random +import model_allele +import model_constraintpool + +# Cairo's compositing operators +# OPERATOR_ADD = 12 +# OPERATOR_ATOP = 5 +# OPERATOR_CLEAR = 0 +# OPERATOR_DEST = 6 +# OPERATOR_DEST_ATOP = 10 +# OPERATOR_DEST_IN = 8 +# OPERATOR_DEST_OUT = 9 +# OPERATOR_DEST_OVER = 7 +# OPERATOR_IN = 3 +# OPERATOR_OUT = 4 +# OPERATOR_OVER = 2 +# OPERATOR_SATURATE = 13 +# OPERATOR_SOURCE = 1 +# OPERATOR_XOR = 11 +#OPERATORS = [operator for operator in range(cairo.OPERATOR_SATURATE+1)] +# + +OPERATOR_CONSTRAINT = 'operatorconstraint' + +class Layer(model_allele.Allele): + """Layer + inv: cairo.OPERATOR_CLEAR <= self.operator <= cairo.OPERATOR_SATURATE + """ + + base_cdef = [{'bind' : OPERATOR_CONSTRAINT, + 'name' : 'Permitted operators for combining two layers', + 'domain': model_constraintpool.INT_M_OF_N, + 'enum' : [('add', cairo.OPERATOR_ADD), + ('atop', cairo.OPERATOR_ATOP), +# ('clear', cairo.OPERATOR_CLEAR), +# ('destination', cairo.OPERATOR_DEST), + ('destination atop', cairo.OPERATOR_DEST_ATOP), + ('destination in', cairo.OPERATOR_DEST_IN), + ('destination out', cairo.OPERATOR_DEST_OUT), +# ('destination over', cairo.OPERATOR_DEST_OVER), +# ('in', cairo.OPERATOR_IN), + ('out', cairo.OPERATOR_OUT), + ('over', cairo.OPERATOR_OVER), +# ('saturate', cairo.OPERATOR_SATURATE), + ('source', cairo.OPERATOR_SOURCE), + ('xor', cairo.OPERATOR_XOR),],}, + ] + OPERATORS = [o[1] for o in base_cdef[0]['enum']] +# OPERATOR_NAMES = [o[0] for o in base_cdef[0]['enum']] + OPERATOR_NAMES = { + cairo.OPERATOR_ADD: 'ADD', + cairo.OPERATOR_ATOP: 'ATOP', + cairo.OPERATOR_CLEAR: 'CLEAR', + cairo.OPERATOR_DEST: 'DEST', + cairo.OPERATOR_DEST_ATOP: 'DEST_ATOP', + cairo.OPERATOR_DEST_IN: 'DEST_IN', + cairo.OPERATOR_DEST_OUT: 'DEST_OUT', + cairo.OPERATOR_DEST_OVER: 'DEST_OVER', + cairo.OPERATOR_IN: 'IN', + cairo.OPERATOR_OUT: 'OUT', + cairo.OPERATOR_OVER: 'OVER', + cairo.OPERATOR_SATURATE: 'SATURATE', + cairo.OPERATOR_SOURCE: 'SOURCE', + cairo.OPERATOR_XOR: 'XOR', + } + + def __init__(self, trunk): + """Layer constructor""" + super(Layer, self).__init__(trunk) + self.seed = 1512 + self.operator = cairo.OPERATOR_OVER + + def __eq__(self, other): + """Equality based on the cells color components.""" + equal = isinstance(other, Layer) \ + and self.seed == other.seed \ + and self.operator == other.operator + return equal + + def randomize(self): + """Randomize the layers components.""" + cpool = model_constraintpool.ConstraintPool.get_pool() + self.seed = random.randint(1, 65535) + operator_constraint = cpool.get(self, OPERATOR_CONSTRAINT) + self.operator = random.choice(operator_constraint) + + def mutate(self): + """Make small random changes to the layers components.""" + if ka_random.is_mutating(): + cpool = model_constraintpool.ConstraintPool.get_pool() + operator_constraint = cpool.get(self, OPERATOR_CONSTRAINT) + self.operator = random.choice(operator_constraint) + + def shuffle(self): + """Shuffle similar componets. + For example exchange foreground and background color.""" + + def crossingover(self, other): + """ + """ + raise TypeError("Layer is an abstract class. Call crossingover() on sub classes only.") + + def crossingover_base(self, new_one, other, cross_lenght): + """ + pre: isinstance(new_one, Layer) + pre: isinstance(other, Layer) + pre: cross_lenght >= 0 + """ + cross_sequence = ka_random.crossing_sequence(cross_lenght + 2) + if cross_sequence[cross_lenght]: + new_one.seed = self.seed + else: + new_one.seed = other.seed + if cross_sequence[cross_lenght+1]: + new_one.operator = self.operator + else: + new_one.operator = other.operator + return cross_sequence + + def draw(self, ctx, width, height): + raise TypeError("Layer is an abstract class. Call draw() on sub classes only.") + + def begin_draw(self, ctx, width, height): + """ + pre: ctx is not None + pre: width > 0 + pre: height > 0 + pre: width == height + """ + time.sleep(0.001) # Python can not set thread priorities. + # Workaround: regularly released the global inter- + # preter lock GIL to give the GUI Thread a chance. + ctx.set_operator(self.operator) + + def explain(self, formater): + formater.text_item('Compositing drawing operator: ' \ + + Layer.OPERATOR_NAMES[self.operator]) + + def copy(self): + raise TypeError("Layer is an abstract class. Call copy() on sub classes only.") + + def copy_base(self, new_one): + """The layers copy constructor + pre: isinstance(new_one, Layer) + """ + new_one.seed = self.seed + new_one.operator = self.operator diff --git a/model_locus.py b/model_locus.py new file mode 100644 index 0000000..7db01dd --- /dev/null +++ b/model_locus.py @@ -0,0 +1,69 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import random + +#@staticmethod +def _fix_pattern(): + p = '_' + for i in range(6): + # 12345678901234567890123456 + p += random.choice('abcdefghijklmnopqrstuvwxyz') + p += '_' + return p + +class Locus(object): + +# @staticmethod +# def _fix_pattern(): +# p = '' +# p += random.choice('abcd') +# return p +# + _modulo_count = 0 + _pattern = _fix_pattern() + + def __init__(self, trunk): + """ + pre: isinstance(trunk, str) + pre: not (trunk == '') + """ + Locus._modulo_count = Locus._modulo_count + 1 \ + if Locus._modulo_count < 999 else 0 + self._unique_id = Locus._pattern + str(Locus._modulo_count) + if trunk == '/': + self.path = '/' + self.__class__.__name__ + else: + self.path = trunk + '/' + self.__class__.__name__ +# print '>>', self.path, self._unique_id + + def get_trunk(self): + parts = self.path.split('/') + if len(parts) > 2: + trunk = '' + for part in parts[1:-1]: + if len(part) > 0: + trunk += '/' + part + return trunk + else: + return '/' + + def get_unique_id(self): + """ unique identifikation for this protozoons copy constructor. + """ + return self._unique_id + diff --git a/model_population.py b/model_population.py new file mode 100644 index 0000000..93ae43e --- /dev/null +++ b/model_population.py @@ -0,0 +1,243 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import pickle +import sys +import os.path +import random +import ka_debug +import ka_random +import model_protozoon + +STATE_INIT = 'I' +STATE_RANDOMIZED = 'R' +STATE_EVOLVED = 'E' + +class KandidModel(object): + """ + inv: self.size >= 2 + inv: 1 <= self.fade_away <= self.size + #inv: 0.0 <= self._flurry_rate <= 9.0 + inv: len(self.fitness) == self.size + inv: forall(self.fitness, lambda f: 0.0 <= f <= 9.0) + inv: len(self.protozoans) == self.size + inv: forall(self.protozoans, lambda p: p is not None) + # all protozoans must be distinct objects, maybe with equal content + inv: all_uniqe_reference(self.protozoans) + """ + + def __init__(self, init_size): + self._state = STATE_INIT + self.size = init_size + self.fade_away = init_size / 2 + #self._flurry_rate = 5.0 + self.protozoans = [model_protozoon.Protozoon() for i in range(self.size)] + self.fitness = [4.0 for i in range(self.size)] + ka_debug.info('initializing model with population size %u' % init_size) + + def get_flurry_rate(self): + return ka_random.get_flurry() +# return self._flurry_rate + + def set_flurry_rate(self, value): + """ + pre: 0 <= value <= 9 + """ + ka_random.set_flurry(value) +# self._flurry_rate = value + + def is_overwrite_allowed(self): + """Preserve an already evolved population from over writing.""" + return not self._state == STATE_EVOLVED + + def classify(self): + """ + # __return__[0] -> good + # __return__[1] -> moderate + # __return__[2] -> poor + post: len(__return__[0])+len(__return__[1])+len(__return__[2]) == self.size + post: len(__return__[0]) >= 1 + post: len(__return__[2]) >= 1 + + # mutual exclusive + post: forall(__return__[0], lambda x: not contains_reference(x, __return__[1])) + post: forall(__return__[0], lambda x: not contains_reference(x, __return__[2])) + post: forall(__return__[2], lambda x: not contains_reference(x, __return__[0])) + post: forall(__return__[2], lambda x: not contains_reference(x, __return__[1])) + """ + good, moderate, poor = [], [], [] + sorted_fitness = sorted(self.fitness) + poor_level = sorted_fitness[self.fade_away-1] + good_level = sorted_fitness[-1] + for protoz, fit in zip(self.protozoans, self.fitness): + if fit >= good_level and len(good) < 1: + good.append(protoz) + elif fit <= poor_level: + if len(poor) >= self.fade_away: + index = random.randint(0, len(poor)-1) + moderate.append(poor[index]) + del poor[index] + poor.append(protoz) + else: + moderate.append(protoz) + return good, moderate, poor + + def reduce_fitness(self, index): + """ + pre: 0 <= index < len(self.fitness) + post: self.fitness.count(0.0) == 1 + """ + for raise_at, fit in enumerate(self.fitness): + if fit < 1.0: + self.fitness[raise_at] = 1.0 + self.fitness[index] = 0.0 + + def raise_fitness(self, index): + """ + pre: 0 <= index < len(self.fitness) + post: self.fitness.count(9.0) == 1 + """ + for lower_at, fit in enumerate(self.fitness): + if 5.0 < fit: + self.fitness[lower_at] = round(self.fitness[lower_at] - 1.0) + self.fitness[index] = 9.0 + + def randomize(self): + self._state = STATE_RANDOMIZED + for protoz in self.protozoans: + protoz.randomize() + + def random(self): + """ + post: len(__return__) > 0 + post: forall(__return__, lambda x: 0 <= x < self.size) + """ + new_indices = [] + self._state = STATE_EVOLVED + good, moderate, poor = self.classify() + for new_at, protoz in enumerate(self.protozoans): + if protoz in poor: + self.protozoans[new_at].randomize() + self.fitness[new_at] = 4.0 + new_indices.append(new_at) + print 'new_indices', new_indices + return new_indices + + def breed(self): + """ + post: len(__return__) > 0 + post: forall(__return__, lambda x: 0 <= x < self.size) + """ + new_indices = [] + self._state = STATE_EVOLVED + good, moderate, poor = self.classify() + index = 0 + for new_at, protoz in enumerate(self.protozoans): + if protoz in poor: + new_one = good[0].crossingover(moderate[index % len(moderate)]) + new_one.shuffle() + new_one.mutate() + self.protozoans[new_at] = new_one + self.fitness[new_at] = 4.0 + new_indices.append(new_at) + index += 1 + print 'new_indices', new_indices + return new_indices + + def replace(self, new_one): + """Replace protozoon with lowest fitness. + pre: isinstance(new_one, model_protozoon.Protozoon) + """ + poor_level = 999999.9 + for protoz, fit in zip(self.protozoans, self.fitness): + if fit < poor_level: + poor_level = fit + poor = protoz + for new_at, protoz in enumerate(self.protozoans): + if protoz is poor: + self.protozoans[new_at] = new_one + self.fitness[new_at] = 5.0 + return new_at + return -1 + + flurry_rate = property(get_flurry_rate, set_flurry_rate) + + +def from_buffer(str_buffer): + ka_debug.info('read from_buffer') + obj = None + try: + obj = pickle.loads(str_buffer) + except: + ka_debug.err('failed reading buffer [%s] [%s]' % \ + (sys.exc_info()[0], sys.exc_info()[1])) + ka_debug.info('[%s]' % str_buffer) + return obj + +def to_buffer(obj): + ka_debug.info('write %s to_buffer' % type(obj)) + try: + return pickle.dumps(obj) + except: + ka_debug.err('failed writing buffer [%s] [%s]' % \ + (sys.exc_info()[0], sys.exc_info()[1])) + +def read_file(file_path): + model = None + if os.path.isfile(file_path): + in_file = None + try: + in_file = open(file_path, 'r') + ka_debug.info('in_file [%s]' % in_file.name) + model = pickle.load(in_file) + except: + ka_debug.err('failed reading [%s] [%s] [%s]' % \ + (in_file.name, sys.exc_info()[0], sys.exc_info()[1])) + finally: + if in_file: + in_file.close() + return model + +def write_file(file_path, model): + out_file = None + try: + out_file = open(file_path, 'w') + ka_debug.info('write out_file [%s]' % out_file.name) + pickle.dump(model, out_file) + except: + ka_debug.err('failed writing [%s] [%s] [%s]' % \ + (out_file.name, sys.exc_info()[0], sys.exc_info()[1])) + finally: + if out_file: + out_file.close() + +def all_uniqe_reference(sequ): + # Brute force is all that's left. + unique = [] + for elem in sequ: + if contains_reference(elem, unique): + return False + else: + unique.append(elem) + return len(unique) == len(sequ) + +def contains_reference(find_elem, sequ): + for elem in sequ: + if id(find_elem) == id(elem): + return True + return False + diff --git a/model_protozoon.py b/model_protozoon.py new file mode 100644 index 0000000..2e389f0 --- /dev/null +++ b/model_protozoon.py @@ -0,0 +1,315 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import random +import cairo +import ka_factory +import ka_random +import model_constraintpool +import exon_color +import model_allele + +TRUNK = '/' + +LAYERTYPE_CONSTRAINT = 'layertypeconstraint' +NUMBER_OF_LAYERS_CONSTRAINT = 'layernumberofconstraint' +MERGER_CONSTRAINT = 'mergertypeconstraint' + +class Protozoon(model_allele.Allele): + """ + inv: self.layers is not None + inv: self.mergers is not None + inv: self.background is not None + inv: isinstance(self.background, exon_color.Color) + """ + + cdef = [{'bind' : LAYERTYPE_CONSTRAINT, + 'name' : 'Permitted layer types', + 'domain': model_constraintpool.STRING_M_OF_N, + 'enum' : ka_factory.get_factory('layer').keys() + }, + {'bind' : NUMBER_OF_LAYERS_CONSTRAINT, + 'name' : 'Number of layers', + 'domain': model_constraintpool.INT_RANGE, + 'min' : 2, 'max': 5, + }, + {'bind' : MERGER_CONSTRAINT, + 'name' : 'Permitted merging strategies for layers', + 'domain': model_constraintpool.STRING_M_OF_N, + 'enum' : ka_factory.get_factory('merger').keys() + }, + ] + + def __init__(self): + super(Protozoon, self).__init__(TRUNK) + self.layers = [] + self.mergers = [] + self.background = exon_color.Color(self.path, 0, 0, 0, 1) + + def __eq__(self, other): + """Equality based on layers quantity and content.""" + equal = isinstance(other, Protozoon) \ + and len(self.layers) == len(other.layers) \ + and len(self.mergers) == len(other.mergers) \ + and self.background == other.background + if equal: + for index in range(len(self.layers)): + equal = equal and self.layers[index] == other.layers[index] + if equal: + for index in range(len(self.mergers)): + equal = equal and self.mergers[index] == other.mergers[index] + return equal + + def randomize(self): + """Randomize the protozoons components. + post: len(self.layers) >= 1 + post: len(self.mergers) >= 1 + """ + cpool = model_constraintpool.ConstraintPool.get_pool() + + # create layers + layer_factory = ka_factory.get_factory('layer') + layertype_constraint = cpool.get(self, LAYERTYPE_CONSTRAINT) + number_of_constraint = cpool.get(self, NUMBER_OF_LAYERS_CONSTRAINT) + + number_of_layers = random.randint(number_of_constraint[0], \ + number_of_constraint[1]) + self.layers = [] + for i in range(number_of_layers): + single_layer = layer_factory.create_random(layertype_constraint, self.path) + single_layer.randomize() + self.layers.append(single_layer) + + # create strategy for merging layers + merger_factory = ka_factory.get_factory('merger') + mergertype_constraint = cpool.get(self, MERGER_CONSTRAINT) + number_of_mergers = 1 if number_of_layers < 3 else int(number_of_layers / 2) + self.mergers = [] + for i in range(number_of_mergers): + single_merger = merger_factory.create_random(mergertype_constraint, self.path) + single_merger.randomize() + self.mergers.append(single_merger) + + # create background color + self.background.randomize() + + + def mutate(self): + """Make small random changes to the protozoon. + """ + cpool = model_constraintpool.ConstraintPool.get_pool() + layertype_constraint = cpool.get(self, LAYERTYPE_CONSTRAINT) + number_of_constraint = cpool.get(self, NUMBER_OF_LAYERS_CONSTRAINT) + + self._mutate_list(self.layers, + layertype_constraint, number_of_constraint, + ka_factory.get_factory('layer'), self.path) + mergertype_constraint = cpool.get(self, MERGER_CONSTRAINT) + self._mutate_list(self.mergers, + mergertype_constraint, number_of_constraint, + ka_factory.get_factory('merger'), self.path) + + # mutate background color + self.background.mutate() + + def shuffle(self): + """Shuffle layer list and delegate shuffling to the protozoons componets.""" + # delegate shuffling to the layers child components + for single_layer in self.layers: + single_layer.shuffle() + # shuffle layers + if ka_random.is_shuffling(): + random.shuffle(self.layers) + # shuffle mergers + if ka_random.is_shuffling(): + random.shuffle(self.mergers) + + def crossingover(self, other): + """Returns a deep copy mixed from my protozoon and the other protozoon. + pre: isinstance(other, Protozoon) + """ + # deep copy + new_one = Protozoon() + # crossing over the layers + for single_layer in self._crossingover_list(self.layers, other.layers): + new_one.layers.append(single_layer.copy()) + + # crossing over the layer mergers + new_one.mergers = [m.copy() for m in self._crossingover_list(self.mergers, + other.mergers)] + # crossing over the background color + new_one.background = self.background.crossingover(other.background) + return new_one + + def _crossingover_list(self, this_list, other_list): + # crossing over common part of this list and other list + len_this, len_other = len(this_list), len(other_list) + min_elements = min([len_this, len_other]) + max_elements = max([len_this, len_other]) + + new_list = [] + cross_sequence = ka_random.crossing_sequence(min_elements) + for index in range(min_elements): + this_element, other_element = this_list[index], other_list[index] + if this_element.__class__ == other_element.__class__: + # delegate crossing over to the elements components + new_list.append(this_element.crossingover(other_element)) + else: + # crossing over whole elements + if cross_sequence[index]: + new_list.append(this_element) + else: + new_list.append(other_element) + + # appending elements from protozoon of greater size + if len_this > len_other: + for index in range(min_elements, max_elements): + new_list.append(this_list[index]) + if len_this < len_other: + for index in range(min_elements, max_elements): + new_list.append(other_list[index]) + return new_list + + def _mutate_list(self, this_list, type_constraint, number_of_constraint, + factory, path): + # maybe remove one element + len_this = len(this_list) + if len_this > number_of_constraint[0] and ka_random.is_mutating(): + del this_list[random.randint(0, len_this-1)] + len_this -= 1 + # maybe duplicate one of the elements + if len_this < number_of_constraint[1] and ka_random.is_mutating(): + random_element = this_list[random.randint(0, len_this-1)] + dupli_element = random_element.copy() + this_list.insert(random.randint(0, len_this-1), dupli_element) + len_this += 1 + # maybe insert a new element + if len_this < number_of_constraint[1] and ka_random.is_mutating(): + single_element = factory.create_random(type_constraint, path) + single_element.randomize() + this_list.insert(random.randint(0, len_this-1), single_element) + len_this += 1 + + # delegate mutation to the elements child components + for single_element in this_list: + single_element.mutate() + + def draw(self, ctx, width, height): + """ + pre: ctx is not None + pre: width > 0 + pre: height > 0 + pre: width == height + """ + ctx.save() + ctx.scale(width, height) + ctx.translate(0.5, 0.5) + + # paint background + ctx.set_operator(cairo.OPERATOR_SOURCE) + ctx.set_source_rgb(self.background.rgba[0], + self.background.rgba[1], + self.background.rgba[2]) + ctx.paint() + + # merge all layers + for index, single_layer in enumerate(self.layers): + self.mergers[index % len(self.mergers)].draw_single_layer( + single_layer, + self.layers[-1-index], + ctx, width, height) + ctx.restore() + + def explain(self, formater): + width = height = 256 + # begin with header + titel = 'protozoon ' + str(id(self)) + formater.header(titel) + + # display combined layers + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) + ctx = cairo.Context(surface) + self.draw(ctx, width, height) + formater.surface_item(surface, 'combined layers', titel) + + # explain all layers from top to bottom + formater.begin_list('protozoon has %d layers' % (len(self.layers))) + reverse_list = self.layers[:] + reverse_list.reverse() + for index, single_layer in enumerate(reverse_list): + title = 'top level layer' if index == 0 else 'layer ' + str(index) + formater.begin_list(title) + # explain operator to merge layer + super(single_layer.__class__, single_layer).explain(formater) + + # explain layer merging strategy + text, surface, descr = self.mergers[index % len(self.mergers)].explain() + if surface is not None: + formater.surface_item(surface, 'merging strategy:' + text, descr) + else: + formater.text_item(text) + + # preview of a single layer + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) + ctx = cairo.Context(surface) + ctx.scale(width, height) + ctx.translate(0.5, 0.5) + + # paint checker board background + steps = 16 +# delta = (1.0 * width) / (1.0 * steps) + delta = (1.0) / (1.0 * steps) + ctx.set_operator(cairo.OPERATOR_SOURCE) + for row in range(steps): + for col in range(steps): + ctx.rectangle(col * delta - 0.5, row * delta - 0.5, + delta, delta) + if (col + row) % 2 == 0: + ctx.set_source_rgb(0.4, 0.4, 0.4) + else: + ctx.set_source_rgb(0.6, 0.6, 0.6) + ctx.fill() + + ctx.save() + single_layer.draw(ctx, width, height) + formater.surface_item(surface, single_layer.__class__.__name__, \ + single_layer.__class__.__name__) + ctx.restore() + + # explain the layers details + formater.begin_list('Details for ' + single_layer.__class__.__name__) + single_layer.explain(formater) + formater.end_list() + formater.end_list() + formater.end_list() + + #explain background color + formater.color_item(self.background, 'background color:', alfa=False) + + # stop with footer + formater.footer() + + def copy(self): + """ The protozoons copy constructor. + """ + new_one = Protozoon() + new_one.layers = [] + for single_layer in self.layers: + new_one.layers.append(single_layer.copy()) + new_one.mergers = [m.copy() for m in self.mergers] + new_one.background = self.background.copy() + return new_one diff --git a/po/Kandid.pot b/po/Kandid.pot new file mode 100644 index 0000000..4cfcba0 --- /dev/null +++ b/po/Kandid.pot @@ -0,0 +1,49 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-10-31 13:04+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: activity/activity.info:2 +msgid "Connect" +msgstr "" + +#: activity.py:63 +msgid "To play, share or invite someone." +msgstr "" + +#: activity.py:133 +msgid "Waiting for another player to join." +msgstr "" + +#: activity.py:164 +msgid "Joined a game. Waiting for my turn..." +msgstr "" + +#: game.py:183 +msgid "The other player wins!" +msgstr "" + +#: game.py:200 +msgid "Your turn" +msgstr "" + +#: game.py:257 +msgid "You win!" +msgstr "" + +#: game.py:259 +msgid "Other player's turn" +msgstr "" diff --git a/po/en.po b/po/en.po new file mode 100644 index 0000000..2c8ed1f --- /dev/null +++ b/po/en.po @@ -0,0 +1,49 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-10-31 13:04+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Translate Toolkit 1.0.1\n" + +#: activity/activity.info:2 +msgid "Connect" +msgstr "" + +#: activity.py:63 +msgid "To play, share or invite someone." +msgstr "" + +#: activity.py:133 +msgid "Waiting for another player to join." +msgstr "" + +#: activity.py:164 +msgid "Joined a game. Waiting for my turn..." +msgstr "" + +#: game.py:183 +msgid "The other player wins!" +msgstr "" + +#: game.py:200 +msgid "Your turn" +msgstr "" + +#: game.py:257 +msgid "You win!" +msgstr "" + +#: game.py:259 +msgid "Other player's turn" +msgstr "" diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..231385d --- /dev/null +++ b/setup.py @@ -0,0 +1,20 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +from sugar.activity import bundlebuilder + +bundlebuilder.start('Kandid') diff --git a/test_enumerator.py b/test_enumerator.py new file mode 100644 index 0000000..e4af604 --- /dev/null +++ b/test_enumerator.py @@ -0,0 +1,97 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import sys +import cairo +import ka_debug +import ka_factory +import ka_random +import ka_extensionpoint +import model_protozoon +import model_constraintpool +import model_layer + +class VariantEnumerator(object): + + def explain_permutation(self): + ka_random.set_flurry(9) + cpool = model_constraintpool.ConstraintPool.get_pool() + + for operator_key in model_layer.Layer.OPERATORS: + for merger_key in ka_factory.get_factory('merger').keys(): + cpool.clear_all() + cpool.set('/Protozoon', 'layernumberofconstraint', [4, 4]) + cpool.set('/Protozoon', 'layertypeconstraint', + ['image', 'markovchain']) + cpool.set('/Protozoon/ImageLayer', 'operatorconstraint', + [operator_key]) + cpool.set('/Protozoon/MarkovChainLayer', 'operatorconstraint', + [operator_key]) + cpool.set('/Protozoon', 'mergertypeconstraint', merger_key) + self._explain_protozon( + model_layer.Layer.OPERATOR_NAMES[operator_key], merger_key) + + for layer_key in ka_factory.get_factory('layer').keys(): + for merger_key in ka_factory.get_factory('merger').keys(): + cpool.clear_all() + cpool.set('/Protozoon', 'layernumberofconstraint', [3, 3]) + cpool.set('/Protozoon', 'layertypeconstraint', layer_key) + cpool.set('/Protozoon', 'mergertypeconstraint', merger_key) + cpool.set('/Protozoon/ImageLayer', 'operatorconstraint', + [cairo.OPERATOR_OVER]) + cpool.set('/Protozoon/MarkovChainLayer', 'operatorconstraint', + [cairo.OPERATOR_OVER]) + cpool.set('/Protozoon/ReferencePattern', 'operatorconstraint', + [cairo.OPERATOR_OVER]) + cpool.set('/Protozoon/LetterPress', 'operatorconstraint', + [cairo.OPERATOR_OVER]) + self._explain_protozon(layer_key, merger_key) + + def _explain_protozon(self, key1, key2): + a_protozoon = model_protozoon.Protozoon() + a_protozoon.randomize() + + base_name = 'index' + base_folder = '/dev/shm/' + explain_id = '_' + key1 + '_' + key2 + ep_list = ka_extensionpoint.list_extensions('formater') + formater = ka_extensionpoint.create(ep_list[0], + base_name, + explain_id, + base_folder) + a_protozoon.explain(formater) + file_path = formater.get_pathname('html') + formater.write_html_file(file_path) + + def _write_file(self, file_path, page): + out_file = None + try: + out_file = open(file_path, 'w') + out_file.write(page) + except: + print 'failed writing [%s] [%s] [%s]' % \ + (out_file.name, sys.exc_info()[0], sys.exc_info()[1]) + finally: + if out_file: + out_file.close() + +def explain(): + ve = VariantEnumerator() + ve.explain_permutation() + +if __name__ == '__main__': + explain() \ No newline at end of file diff --git a/test_suite.py b/test_suite.py new file mode 100644 index 0000000..d78b3f0 --- /dev/null +++ b/test_suite.py @@ -0,0 +1,385 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import sys +import unittest +import cairo +import ka_debug +import ka_factory +import ka_random +import ka_extensionpoint +import model_protozoon +import model_constraintpool +import model_population +import model_locus +import exon_color +import exon_buzzword +import exon_direction +import exon_position + +EPSILON = 0.00001 + +class TestKandidModel(unittest.TestCase): + + def setUp(self): + pass + + def tearDown(self): + pass + + def _neutral(self): + cpool = model_constraintpool.ConstraintPool.get_pool() + permitted = [x for x in ka_extensionpoint.list_extensions(exon_position.POSITION_CONSTRAINT) if x not in ['positionconstraint_centered']] + cpool.set('/Position', exon_position.POSITION_CONSTRAINT, permitted) + cpool.set('/LetterPress/Position', exon_position.POSITION_CONSTRAINT, permitted) + cpool.set('/Protozoon/LetterPress/Position', exon_position.POSITION_CONSTRAINT, permitted) + permitted = ['colorconstraint_none'] + cpool.set('/Color', exon_color.COLOR_CONSTRAINT, permitted) + cpool.set('/Protozoon/Color/', exon_color.COLOR_CONSTRAINT, permitted) + cpool.set('/MarkovChainLayer/Color', exon_color.COLOR_CONSTRAINT, permitted) + cpool.set('/LetterPress/Color', exon_color.COLOR_CONSTRAINT, permitted) + cpool.set('/Protozoon/LetterPress/Color', exon_color.COLOR_CONSTRAINT, permitted) + cpool.set('/Protozoon/MarkovChainLayer/Color', exon_color.COLOR_CONSTRAINT, permitted) + + #/Protozoon/mergertypeconstraint ['flip', 'mask', 'rectangulartile', 'straight'] + permitted = [x for x in ka_factory.get_factory('merger').keys() if x not in ['straight', ]] + cpool.set('/Protozoon', model_protozoon.MERGER_CONSTRAINT, permitted) + + def test_locus(self): + self.assertEqual('/Locus', model_locus.Locus('/').path) + self.assertEqual('/A/Locus', model_locus.Locus('/A').path) + self.assertEqual('/A', model_locus.Locus('/A').get_trunk()) + self.assertEqual('/A/B', model_locus.Locus('/A/B').get_trunk()) + + def test_html_formater(self): + base_name = 'test_html_formater' + base_folder = '/dev/shm/' + formater = ka_extensionpoint.create('formater_html', base_name, 'test', base_folder) + formater.header('Test title') + formater.begin_list('l1') + a_text = '&<>' + formater.text_item(a_text) + + formater.begin_list('l2') + cpool = model_constraintpool.ConstraintPool.get_pool() + key = 'color' + for cc in ka_extensionpoint.list_extensions(key+'constraint'): + cpool.set('/'+key.capitalize(), key+'constraint', [cc]) + a_color = exon_color.Color('/', 0.5, 0.6, 0.7, 0.8) + formater.color_item(a_color, 'Color ' + str(cc)) + formater.end_list() + + formater.end_list() + formater.footer() + file_path = formater.get_pathname('html') + formater.write_html_file(file_path) + exit_code = os.spawnvp(os.P_WAIT, 'tidy', ['tidy', '-i', file_path]) + self.assertEqual(0, exit_code) + + def test_protozon_formater(self): + ka_random.set_flurry(9) + cpool = model_constraintpool.ConstraintPool.get_pool() + cpool.clear_all() + + ep_list = ka_extensionpoint.list_extensions('formater') + self.assertTrue(len(ep_list) > 0) + for ep_key in ep_list: + a_protozoon = model_protozoon.Protozoon() + a_protozoon.randomize() + + base_name = 'index' + base_folder = '/dev/shm/' + formater = ka_extensionpoint.create(ep_key, base_name, a_protozoon.get_unique_id(), base_folder) + a_protozoon.explain(formater) + + file_path = formater.get_pathname('html') + formater.write_html_file(file_path) + exit_code = os.spawnvp(os.P_WAIT, 'tidy', ['tidy', '-i', file_path]) + self.assertEqual(0, exit_code) + + def _write_file(self, file_path, page): + out_file = None + try: + out_file = open(file_path, 'w') + out_file.write(page) + except: + self.fail('failed writing [%s] [%s] [%s]' % \ + (out_file.name, sys.exc_info()[0], sys.exc_info()[1])) + finally: + if out_file: + out_file.close() + + def test_constrain_pool(self): + position = exon_position.Position('/', 0.1, 0.2) + cpool = model_constraintpool.ConstraintPool.get_pool() + cpool.get(position, exon_position.POSITION_CONSTRAINT) + print cpool + + def test_limit(self): + self.assertTrue(ka_random.limitate(-0.01) == 0.0) + self.assertTrue(ka_random.limitate(0.01) == 0.01) + self.assertTrue(ka_random.limitate(1.01) == 1.0) + self.assertTrue(ka_random.limitate_range(-0.02, -0.01, 0.01) == -0.01) + self.assertTrue(ka_random.limitate_range(0.01, -0.01, 0.01) == 0.01) + self.assertTrue(ka_random.limitate_range(0.011, -0.01, 0.01) == 0.01) + self.assertTrue(abs(ka_random.cyclic_limitate(-0.01) - 0.99) < EPSILON) + self.assertTrue(abs(ka_random.cyclic_limitate(0.01) - 0.01) < EPSILON) + self.assertTrue(abs(ka_random.cyclic_limitate(1.01) - 0.01) < EPSILON) + for value in range(-20, 20): + self.assertTrue(0.0 <= ka_random.limitate(0.1 * value) <= 1.0) + self.assertTrue(0.0 <= ka_random.cyclic_limitate(0.1 * value) <= 1.0) + + def test_extensionpoint(self): +# ka_extensionpoint.scann() + ep_types = ka_extensionpoint.list_extension_types() + self.assertTrue(len(ep_types) > 0) + + ep_list = ka_extensionpoint.list_extensions('colorconstraint') + self.assertTrue(len(ep_list) > 0) + for ep_key in ep_list: + colorconstraint = ka_extensionpoint.create(ep_key, '/') + self.assertEqual(4, len(colorconstraint.filter((.5, .5, .5, 1.)))) + + def test_deepcopy(self): + self._neutral() + list1 = [exon_buzzword.Buzzword('/', [u'sugar', u'kandid', u'']), \ + exon_color.Color('/', 0.1, 0.2, 0.3, 1.0), \ + exon_direction.Direction('/', 0.1, 0.2), \ + exon_position.Position('/', 0.1, 0.2), \ + ] + list2 = ka_random.copy_list(list1) + self.assertEqual(len(list1), len(list2)) + for index, i in enumerate(list1): + self.assertEqual(list1[index], list2[index]) + self.assertTrue(list1[index] is not list2[index]) + + def test_random(self): + ka_random.set_flurry(0) + seq = ka_random.crossing_sequence(10) + for index in range(1,len(seq)): + self.assertEqual(seq[0], seq[index]) + ka_random.set_flurry(9) + seq = ka_random.crossingover([11], [21, 22, 23, 24, 25]) + print seq + seq = ka_random.crossingover([11, 12, 13], [21, 22, 23, 24, 25]) + print seq + seq = ka_random.crossingover([11, 12, 13], [21]) + print seq + + def test_positionconstraint(self): + self._run_ep_test('position') + + def test_directionconstraint(self): + self._run_ep_test('direction') + + def _run_ep_test(self, key): + for flavor in ka_extensionpoint.list_extensions(key+'constraint'): + constraint = ka_extensionpoint.create(flavor, '/') + x_pos, y_pos = 0.1, 0.2 + x_pos, y_pos = constraint.filter(x_pos, y_pos) + x_pos, y_pos = constraint.randomize() + x_pos, y_pos = constraint.mutate(x_pos, y_pos) + self.assertTrue(isinstance(x_pos, float)) + self.assertTrue(isinstance(y_pos, float)) + + def test_classification(self): + population_size = 12 + model = model_population.KandidModel(population_size) + self.assertEqual(population_size, len(model.fitness)) + for fa in [1, 2, population_size-1]: + model.fade_away = fa + for i in range(population_size): + model.fitness[i] = (population_size-i-1)*(1.0 / population_size) + self._check_classification(population_size, model) + for ft in [0.0, 5.0, 9.0]: + for i in range(population_size): + model.fitness[i] = ft + self._check_classification(population_size, model) + + def _check_classification(self, population_size, model): + good, moderate, poor = model.classify() + self.assertEqual(model.fade_away, len(poor)) + self.assertEqual(population_size, len(good) + len(moderate) + len(poor)) + self.assertTrue(good[0] in model.protozoans) + self.assertFalse(model_population.contains_reference(good[0], moderate)) + self.assertFalse(model_population.contains_reference(good[0], poor)) + for mo in moderate: + self.assertFalse(model_population.contains_reference(mo, poor)) + self.assertFalse(model_population.contains_reference(mo, good)) + for po in poor: + self.assertFalse(model_population.contains_reference(po, moderate)) + self.assertFalse(model_population.contains_reference(po, good)) + + def test_exon_position(self): + self._run_exon_test('position', exon_position.Position, 0.5, 0.5) + + def test_exon_direction(self): + self._run_exon_test('direction', exon_direction.Direction, 0.3, 0.4) + + def test_exon_buzzword(self): + self._run_exon_test('buzzword', exon_buzzword.Buzzword, [u'a', u'b']) + + def _run_exon_test(self, key, constructor, *params): + ka_random.set_flurry(9) + self._neutral() + + cpool = model_constraintpool.ConstraintPool.get_pool() + for cc in ka_extensionpoint.list_extensions(key+'constraint'): + cpool.set('/'+key.capitalize(), key+'constraint', [cc]) + element1 = constructor('/', *params) + element2 = element1.copy() + for i in range(32): + element3 = element1.crossingover(element2) + self.assertEqual(element1, element2) + self.assertEqual(element1, element3) + equal = True + for i in range(32): + element2.randomize() + element2.mutate() + element2.shuffle() + equal = equal and element1 == element2 + self.assertFalse(equal) + self.assertEqual(element1, element3) + + def test_exon_color(self): + ka_random.set_flurry(9) + cpool = model_constraintpool.ConstraintPool.get_pool() + cpool.clear_all() + + key = 'color' + cpool = model_constraintpool.ConstraintPool.get_pool() + + for cc in ka_extensionpoint.list_extensions(key+'constraint'): + cpool.set('/'+key.capitalize(), key+'constraint', [cc]) + a_color = exon_color.Color('/', 0.5, 0.5, 0.5, 1.0) + self.assertFalse(a_color is a_color.crossingover( \ + exon_color.Color('/', 0.5, 0.5, 0.5, 1.0))) + self.assertEqual(4, len(a_color.copy().rgba)) + a_color.randomize() + self.assertEqual(4, len(a_color.rgba)) + for i in range(32): + a_color.mutate() + self.assertEqual(4, len(a_color.rgba)) + + self._neutral() + color1 = exon_color.Color('/', 1.0, 1.0, 1.0, 1.0) + color2 = color1.copy() + self.assertTrue(color2 is not color1) + self.assertTrue(color2 == color1) + color1.randomize() + self.assertFalse(color2 == color1) + for i in range(32): + color3 = color1.crossingover(color2) + self.assertTrue(color3 is not color1) + self.assertTrue(color3 is not color2) + self.assertTrue(color3 == color1 or color3 == color2) + color4 = color3.copy() + eq = True + for i in range(32): + color4.mutate() + color4.shuffle() + eq = eq and color4 == color3 + self.assertFalse(eq) + + def test_layer_factory(self): + self._neutral() + layer_factory = ka_factory.get_factory('layer') + keys = layer_factory.keys() + self.assertTrue(layer_factory.count() >= 1) + self.assertEqual(len(keys), layer_factory.count()) + for layer_key in keys: + self.assertTrue(layer_factory.create(layer_key, '/') is not None) + self.assertTrue(layer_factory.create_random([keys[0]], '/') is not None) + + def test_layer(self): + ka_random.set_flurry(9) + self._neutral() + + layer_factory = ka_factory.get_factory('layer') + for layer_key in layer_factory.keys(): + layer1 = layer_factory.create(layer_key, '/') + layer2 = layer1.copy() + self.assertTrue(layer2 is not layer1) + self.assertTrue(layer2 == layer1) + layer1.randomize() + layer1.shuffle() + if layer2 == layer1: + pass + self.assertFalse(layer2 == layer1) + layer3 = layer1.crossingover(layer2) + self.assertTrue(layer3 is not layer1) + self.assertTrue(layer3 is not layer2) + layer4 = layer3.copy() + layer4.mutate() + for i in range(32): + layer4.shuffle() +# self.assertFalse(layer4 == layer3) + + def test_protozoon(self): + ka_random.set_flurry(9) + self._neutral() + + protozoon1 = model_protozoon.Protozoon() + protozoon1.randomize() + protozoon2 = protozoon1.copy() + self.assertTrue(protozoon2 is not protozoon1) + self.assertTrue(protozoon2 == protozoon1) + protozoon1.randomize() + protozoon1.shuffle() + self.assertFalse(protozoon2 == protozoon1) + protozoon3 = protozoon1.crossingover(protozoon2) + self.assertTrue(protozoon3 is not protozoon1) + self.assertTrue(protozoon3 is not protozoon2) + protozoon4 = protozoon3.copy() + protozoon4.mutate() + for i in range(32): + protozoon4.shuffle() +# self.assertFalse(protozoon4 == protozoon3) + + def test_draw(self): + self._neutral() + + width, height = 200, 200 + surface = cairo.SVGSurface('testoutput_p.svg', width, height) + ctx = cairo.Context(surface) + protozoon1 = model_protozoon.Protozoon() + protozoon1.randomize() + protozoon1.draw(ctx, width, height) + + layer_factory = ka_factory.get_factory('layer') + for strategy in layer_factory.keys(): + layer = layer_factory.create(strategy, '/') + layer.randomize() + outname = 'testoutput_' + strategy +# surface = cairo.SVGSurface(outname + '.svg', width, height) + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) + ctx = cairo.Context(surface) + ctx.scale(width, height) + # paint background + ctx.set_operator(cairo.OPERATOR_SOURCE) + ctx.set_source_rgb(0.0, 0.0, 0.0) + ctx.paint() + layer.draw(ctx, width, height) + surface.write_to_png(outname + '.png') + +ka_debug.info('starting TestSuite') +ka_debug.err('testing error message channel.') +alltests = unittest.TestSuite((\ + unittest.makeSuite(TestKandidModel), \ + )) +unittest.TextTestRunner(verbosity=2).run(alltests) -- cgit v0.9.1