pax_global_header00006660000000000000000000000064125556740210014521gustar00rootroot0000000000000052 comment=21bb60b877153fa12b3260b1f3c5cee125e4107f ufl-1.6.0/000077500000000000000000000000001255567402100123135ustar00rootroot00000000000000ufl-1.6.0/.bzrignore000066400000000000000000000000001255567402100143030ustar00rootroot00000000000000ufl-1.6.0/.gitignore000066400000000000000000000000601255567402100142770ustar00rootroot00000000000000*.pyc *.py~ build/ test/*_debug.py test/UFL.log ufl-1.6.0/COPYING000066400000000000000000001045131255567402100133520ustar00rootroot00000000000000 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 . ufl-1.6.0/COPYING.LESSER000066400000000000000000000167271255567402100143570ustar00rootroot00000000000000 GNU LESSER 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. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser 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 Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ufl-1.6.0/ChangeLog000066400000000000000000000260501255567402100140700ustar00rootroot000000000000001.6.0 [2015-07-28] - Change approach to attaching __hash__ implementation to accomodate python 3 - Implement new non-recursive traversal based hash computation - Allow derivative(M, ListTensor(), ...) just like list/tuple works - Add traits is_in_reference_frame, is_restriction, is_evaluation, is_differential - Add missing linear operators to ArgumentDependencyExtractor - Add _ufl_is_literal_ type trait - Add _ufl_is_terminal_modifier_ type trait and Expr._ufl_terminal_modifiers_ list - Add new types ReferenceDiv and ReferenceCurl - Outer product element support in degree estimation - Add TraceElement, InteriorElement, FacetElement, BrokenElement - Add OuterProductCell to valid Real elements - Add _cache member to form for use by external frameworks - Add Sobolev space HEin - Add measures dI,dO,dC for interface, overlap, cutcell - Remove Measure constants - Remove cell2D and cell3D - Implement reference_value in apply_restrictions - Rename point integral to vertex integral and kept *dP syntax - Replace lambda functions in ufl_type with named functions for nicer stack traces - Minor bugfixes, removal of unused code and cleanups 1.5.0 [2015-01-12] - Require Python 2.7 - Python 3 support - Change to py.test - Rewrite parts of expression representation core, providing significant optimizations in speed and memory use, as well as a more elaborate type metadata system for internal use - Use expr.ufl_shape instead of ufl.shape() - Use expr.ufl_indices instead of ufl.indices(), returns tuple of free index ids, not Index objects - Use expr.ufl_index_dimensions instead of ufl.index_dimensions(), returns tuple of dimensions ordered corresponding to expr.ufl_indices, not a dict - Rewrite core algorithms for expression traversal - Add new core algorithms map_expr_dag(), map_integrand_dag(), similar to python map() but applying a callable MultiFunction recursively to each Expr node, without Python recursion - Highly recommend rewriting algorithms based on Transformer using map_expr_dag and MultiFunction, avoiding Python recursion overhead - Rewrite core algorithms apply_derivatives, apply_restrictions - Form signature is now computed without applying derivatives first, introducing smaller overhead on jit cache hits - Use form.signature() to compute form signature - Use form.arguments() instead of extract_arguments(form) - Use form.coefficients() instead of extract_coefficients(form) - Small improvement to str and latex output of expressions - Allow diff(expr, coefficient) without wrapping coefficient in variable - Add keywords to measures: dx(..., degree=3, rule="canonical") - Introduce notation from the Periodic Table of the Finite Elements - Introduce notation for FEEC families of elements: P-, P, Q-, S - Experimental support for high-order geometric domains - Algorithms for symbolic rewriting of geometric quantities (used by uflacs) - Remove the *Constant* classes, using Coefficient with a Real element instead - Add types for MinValue and MaxValue - Disable automatic rewriting a+a->2*a, a*a->a**2, a/a->1, these are costly and the compiler should handle them instead - Fix signature stability w.r.t. metadata dicts - Minor bugfixes, removal of unused code and cleanups 1.4.0 [2014-06-02] - New integral type custom_integral (*dc) - Add analysis of which coefficients each integral actually uses to optimize assembly - Improved svg rendering of cells and sobolevspaces in ipython notebook - Add sobolev spaces, use notation "element in HCurl" (HCurl, HDiv, H1, H2, L2) - Improved error checking of facet geometry in non-facet integrals - Improved restriction handling, restricting continuous coefficients and constants is now optional - Introduce notation from the Periodic Table of the Finite Elements (draft) - Remove alias "Q" for quadrature element, use "Quadrature" - New derivative type ReferenceGrad - New discontinuous RT element - New geometry types Jacobian, JacobianInverse, JacobianDeterminant - New geometry types FacetJacobian, FacetJacobianInverse, FacetJacobianDeterminant - New geometry types CellFacetJacobian, CellFacetJacobianInverse, CellFacetJacobianDeterminant - New geometry types FacetOrigin, CellOrigin - New geometry types CellCoordinate, FacetCoordinate - New geometry types CellNormal, CellOrientation, QuadratureWeight - Argument (and TestFunction, TrialFunction) now use absolute numbering f.number() instead of relative f.count() - New syntax: integrand*dx(domain) - New syntax: integrand*dx(1, domain=domain) - New syntax: integrand*dx(1, subdomain_data=domain_data) - Using domain instead of cell in many places. - Deprecated notation 'cell.n', 'cell.x' etc. - Recommended new notation: FacetNormal(domain) - Experimental: Argument (and TestFunction, TrialFunction) now can have a specified part index for representing block systems - Experimental: Domains can now be created with a Coefficient providing coordinates: Domain(Coefficient(VectorElement("CG", domain, 2))) - Experimental: New concept Domain: domain = Domain(triangle, geometric_dimension=3, label="MyDomain") - Various general optimizations - Various minor bugfixes - Various docstring improvements 1.3.0 [2014-01-07] - Add cell_avg and facet_avg operators, can be applied to a Coefficient or Argument or restrictions thereof - Fix bug in cofactor: now it is transposed the correct way. - Add cell.min_facet_edge_length - Add cell.max_facet_edge_length - Simplify 0^f -> 0 if f is a non-negative scalar value - Add atan2 function - Allow form+0 -> form 1.2.0 [2013-03-24] - NB! Using shapes such as (1,) and (1,1) instead of () for 1D tensor quantities I, x, grad(f) - Add cell.facet_diameter - Add new concept Domain - Add new concept Region, which is the union of numbered subdomains - Add integration over regions (which may be overlapping by sharing subdomains) - Add integration over everywhere - Add functions cosh, sinh, tanh, Max, Min - Generalize jump(v,n) for rank(v) > 2 - Fix some minor bugs 1.1.0 [2013-01-07] - Add support for pickling of expressions (thanks to Graham Markall) - Add shorthand notation A**2 == inner(A, A), special cased for power 2. - Add support for measure sum notation f*(dx(0) + dx(3)) == f*dx(0) + f*dx(3) - Supporting code for bugfix in PyDOLFIN when comparing test/trial functions - Remove support for tuple form notation as this was ambiguous - Bugfix in quadrature degree estimation, never returning <0 now - Remove use of cmp to accomodate removal from python 3 1.1-alpha-prerelease [2012-11-18] (Not released, snapshot archived with submission of UFL journal paper) - Support adding 0 to forms, allowing sum([a]) - Major memory savings and optimizations. - Some bugfixes. - Add perp operator. - Support nested tuple syntax like MixedElement((U,V),W) - Allow outer(a, b, c, ...) by recursive application from left. - Add simplification f/f -> 1 - Add operators <,>,<=,>= in place of lt,gt,le,ge 1.0.0 [2011-12-07] - No changes since rc1. 1.0-rc1 [2011-11-22] - Added tests covering snippets from UFL chapter in FEniCS book - Added more unit tests - Added operators diag and diag_vector - Added geometric quantities cell.surface_area and cell.facet_area - Fixed rtruediv bug - Fixed bug with derivatives of elements of type Real with unspecified cell 1.0-beta3 [2011-10-26] - Added nabla_grad and nabla_div operators - Added error function erf(x) - Added bessel functions of first and second kind, normal and modified, bessel_J(nu, x), bessel_Y(nu, x), bessel_I(nu, x), bessel_K(nu, x) - Extended derivative() to allow indexed coefficient(s) as differentiation variable - Made *Constant use the 'Real' space instead of 'DG0' - Bugfix in adjoint where test and trial functions were in different spaces - Bugfix in replace where the argument to a grad was replaced with 0 - Bugfix in reconstruction of tensor elements - Some other minor bugfixes 1.0-beta2 [2011-08-11] - Support c*form where c depends on a coefficient in a Real space 1.0-beta [2011-07-08] - Add script ufl-version - Added syntax for associating an arbitrary domain data object with a measure: dss = ds[boundaries]; M = f*dss(1) + g*dss(2) - Added new operators elem_mult, elem_div, elem_pow and elem_op for elementwise application of scalar operators to tensors of equal shape - Added condition operators And(lhs,rhs) and Or(lhs,rhs) and Not(cond) - Fixed support for symmetries in subelements of a mixed element - Add support for specifying derivatives of coefficients to derivative() 0.9.1 [2011-05-16] - Remove set_foo functions in finite element classes - Change license from GPL v3 or later to LGPL v3 or later - Change behavior of preprocess(), form.compute_form_data(), form_data.preprocessed_form - Allowing grad, div, inner, dot, det, inverse on scalars - Simplify Identity(1) -> IntValue(1) automatically - Added Levi-Cevita symbol: e = PermutationSymbol(3); e[i,j,k] - Fix bug with future division behaviour (ufl does not support floor division) - Add subdomain member variables to form class - Allow action on forms of arbitrary rank 0.9.0 [2011-02-23] - Allow jump(Sigma, n) for matrix-valued expression Sigma - Bug fix in scalar curl operator - Bug fix in deviatoric operator 0.5.4 [2010-09-01] - Bug fixes in PartExtracter - Do not import x for coordinate - Add Circumradius to Cell (Cell.circumradius) - Add CellVolume to Cell (Cell.volume) 0.5.3 [2010-07-01] - Rename ElementRestriction --> RestrictedElement - Experimental import of x from tetrahedron - Make lhs/rhs work for resrictions - Redefine operator + for FiniteElements and replace + by * - Rename ElementUnion -> EnrichedElement - Add support for tan() and inverse trigonometric functions 0.5.2 [2010-02-15] - Attach form data to preprocessed form, accessible by form.form_data() 0.5.1 [2010-02-03] - Fix bug in propagate_restriction 0.5.0 [2010-02-01] - Several interface changes in FormData class - Introduce call preprocess(form) to be called at beginning of compilation - Rename BasisFunction --> Argument - Rename Function --> Coefficient 0.4.1 [2009-12-04] - Redefine grad().T --> grad() - New meaning of estimate_max_polynomial_degree - New function estimate_total_polynomial_degree - Allow degree = None and cell = None for elements 0.4.0 [2009-09-23] - Extensions for ElementRestriction (restrict FiniteElement to Cell) - Bug fix for lhs/rhs with list tensor types - Add new log function set_prefix - Add new log function log(level, message) - Added macro cell integral *dE - Added mechanism to add additional integral types - Added LiftingOperator and LiftingFunction - Added ElementRestriction 0.3.0 [2009-05-28] - Some critical bugfixes, in particular in differentiation. - Added form operators "system" and "sensitivity_rhs". - diff can take form as argument, applies to all integrands. - Rudimentary precedence handling for better use of parentheses in str(expression). - Added script ufl2py, mainly for debugging purposes. - Crude implementation of estimate_max_polynomial_degree for quadrature degree estimation. - Improved manual. 0.2.0 [2009-04-07] - Initial release of UFL. 0.1.0 [...] - Unreleased development versions of UFL. ufl-1.6.0/README000066400000000000000000000062701255567402100132000ustar00rootroot00000000000000=========================== UFL - Unified Form Language =========================== The Unified Form Language (UFL) is a domain specific language for declaration of finite element discretizations of variational forms. More precisely, it defines a flexible interface for choosing finite element spaces and defining expressions for weak forms in a notation close to mathematical notation. Authors: | Martin Sandve Alnæs | Anders Logg Contributors: | Kristian B. Ølgaard | Garth N. Wells | Marie E. Rognes | Kent-Andre Mardal | Johan Hake | David Ham | Florian Rathgeber | Andrew McRae | Lawrence Mitchell | Johannes Ring Installation ============ Linux:: sudo python setup.py install Directories =========== - ufl/ All source code for the UFL implementation. - scripts/ Commandline utilities like "ufl-analyse", "ufl-convert" and "form2ufl". - demo/ Several ufl form files which demonstrates the use of the form language. - doc/ The UFL manual resides here. - test/ Unit tests for the UFL implementation. Run all tests by typing "python test.py" inside the test/ directory. - sandbox/ A place for experimental scripts and other unofficial code. Utilities ========= For more information about the utilities, type:: ufl-analyse -h ufl-convert -h form2ufl -h after installation. About the Python modules ======================== The global namespace of the module ufl contains the entire UFL language:: from ufl import * Form compilers may want to import additional implementation details like:: from ufl.classes import * and:: from ufl.algorithms import * Importing a .ufl file can be done easily from Python:: from ufl.algorithms import load_ufl_file filedata = load_ufl_file("filename.ufl") forms = filedata.forms elements = filedata.elements to get lists of forms and elements from the .ufl file, or:: from ufl.algorithms import load_forms forms = load_forms("filename.ufl") to get a list of forms in the .ufl file. Contact ======= Send feature requests and questions to fenics@fenicsproject.org The Git source repository for UFL is located at https://bitbucket.org/fenics-project/ufl and bugs can be registered at https://bitbucket.org/fenics-project/ufl/issues License ======= UFL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. UFL 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with UFL. If not, see . ufl-1.6.0/demo/000077500000000000000000000000001255567402100132375ustar00rootroot00000000000000ufl-1.6.0/demo/Constant.ufl000066400000000000000000000020001255567402100155300ustar00rootroot00000000000000# Copyright (C) 2007 Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Martin Sandve Alnes, 2009 # # Test form for scalar and vector constants. cell = triangle element = FiniteElement("Lagrange", cell, 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) c = Constant(cell) d = VectorConstant(cell) a = c*dot(grad(v), grad(u))*dx L = inner(d, grad(v))*dx ufl-1.6.0/demo/ConvectionJacobi.ufl000066400000000000000000000003531255567402100171670ustar00rootroot00000000000000# # Author: Martin Sandve Alnes # Date: 2008-10-03 # element = VectorElement("Lagrange", triangle, 1) u = TrialFunction(element) v = TestFunction(element) w = Coefficient(element) a = dot(dot(u, grad(w)) + dot(w, grad(u)), v) * dx ufl-1.6.0/demo/ConvectionJacobi2.ufl000066400000000000000000000003541255567402100172520ustar00rootroot00000000000000# # Author: Martin Sandve Alnes # Date: 2008-10-03 # element = VectorElement("Lagrange", triangle, 1) u = TrialFunction(element) v = TestFunction(element) w = Coefficient(element) a = (u[j]*w[i].dx(j) + w[j]*u[i].dx(j)) * v[i] * dx ufl-1.6.0/demo/ConvectionVector.ufl000066400000000000000000000003001255567402100172320ustar00rootroot00000000000000# # Author: Martin Sandve Alnes # Date: 2008-10-03 # element = VectorElement("Lagrange", triangle, 1) v = TestFunction(element) w = Coefficient(element) a = dot( dot(w, grad(w)), v ) * dx ufl-1.6.0/demo/Elasticity.ufl000066400000000000000000000004421255567402100160610ustar00rootroot00000000000000# # Author: Anders Logg # Modified by: Martin Sandve Alnes # Date: 2009-01-12 # element = VectorElement("Lagrange", tetrahedron, 1) v = TestFunction(element) u = TrialFunction(element) def epsilon(v): Dv = grad(v) return 0.5*(Dv + Dv.T) a = inner(epsilon(v), epsilon(u))*dx ufl-1.6.0/demo/EnergyNorm.ufl000066400000000000000000000016521255567402100160400ustar00rootroot00000000000000# Copyright (C) 2005-2007 Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # This example demonstrates how to define a functional, here # the energy norm (squared) for a reaction-diffusion problem. element = FiniteElement("Lagrange", tetrahedron, 1) v = Coefficient(element) a = (v*v + dot(grad(v), grad(v)))*dx ufl-1.6.0/demo/Equation.ufl000066400000000000000000000030451255567402100155360ustar00rootroot00000000000000# Copyright (C) 2007 Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Specification of a system F(v, u) = 0 and extraction of # the bilinear and linear forms a and L for the left- and # right-hand sides: # # F(v, u) = a(v, u) - L(v) = 0 # # The example below demonstrates the specification of the # linear system for a cG(1)/Crank-Nicholson time step for # the heat equation. # # The below formulation is equivalent to writing # # a = v*u*dx + 0.5*k*dot(grad(v), grad(u))*dx # L = v*u0*dx - 0.5*k*dot(grad(v), grad(u0))*dx # # but instead of manually shuffling terms not including # the unknown u to the right-hand side, all terms may # be listed on one line and left- and right-hand sides # extracted by lhs() and rhs(). element = FiniteElement("Lagrange", triangle, 1) k = 0.1 v = TestFunction(element) u = TrialFunction(element) u0 = Coefficient(element) F = v*(u - u0)*dx + k*dot(grad(v), grad(0.5*(u0 + u)))*dx a = lhs(F) L = rhs(F) ufl-1.6.0/demo/ExplicitConvection.ufl000066400000000000000000000003331255567402100175570ustar00rootroot00000000000000# # Author: Martin Sandve Alnes # Date: 2008-10-03 # element = VectorElement("Lagrange", triangle, 1) u = TrialFunction(element) v = TestFunction(element) w = Coefficient(element) a = dot( dot(w, grad(u)), v ) * dx ufl-1.6.0/demo/FEEC.ufl000066400000000000000000000032641255567402100144560ustar00rootroot00000000000000# Copyright (C) 2010 Marie Rognes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . """ This demo illustrates the FEEC notation V = FiniteElement("P Lambda", cell, r, k) V = FiniteElement("P- Lambda", cell, r, k) and their aliases. """ from ufl import exterior_derivative as d set_level(INFO) cells = [interval, triangle, tetrahedron] r = 1 for cell in cells: for family in ["P Lambda", "P- Lambda"]: tdim = cell.topological_dimension() for k in range(0, tdim+1): # Testing exterior derivative V = FiniteElement(family, cell, r, form_degree=k) v = TestFunction(V) u = TrialFunction(V) a = inner(d(u), d(v))*dx # Testing mixed formulation of Hodge Laplace if k > 0 and k < tdim+1: S = FiniteElement(family, cell, r, form_degree=k-1) W = S * V (sigma, u) = TrialFunctions(W) (tau, v) = TestFunctions(W) a = (inner(sigma, tau) - inner(d(tau), u) + inner(d(sigma), v) + inner(d(u), d(v)))*dx ufl-1.6.0/demo/FunctionOperators.ufl000066400000000000000000000017071255567402100174400ustar00rootroot00000000000000# Copyright (C) 2007 Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Test form for operators on Coefficients. element = FiniteElement("Lagrange", triangle, 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) g = Coefficient(element) a = sqrt(1/abs(1/f))*sqrt(g)*dot(grad(v), grad(u))*dx + v*u*sqrt(f*g)*g*dx ufl-1.6.0/demo/H1norm.ufl000066400000000000000000000002541255567402100151140ustar00rootroot00000000000000# # Author: Martin Sandve Alnes # Date: 2008-10-03 # element = FiniteElement("Lagrange", triangle, 1) f = Coefficient(element) a = ( f*f + dot(grad(f), grad(f)) ) * dx ufl-1.6.0/demo/HarmonicMap.ufl000066400000000000000000000005371255567402100161520ustar00rootroot00000000000000# # Harmonic map demo using separate coefficients x and y. # Author: Martin Alnes # Date: 2009-04-09 # cell = triangle X = VectorElement("Lagrange", cell, 1) Y = FiniteElement("Lagrange", cell, 1) x = Coefficient(X) y = Coefficient(Y) L = inner(grad(x), grad(x))*dx + dot(x,x)*y*dx F = derivative(L,(x,y)) J = derivative(F,(x,y)) forms = [L,F,J] ufl-1.6.0/demo/HarmonicMap2.ufl000066400000000000000000000005541255567402100162330ustar00rootroot00000000000000# # Harmonic map demo using one mixed function u to represent x and y. # Author: Martin Alnes # Date: 2009-04-09 # cell = triangle X = VectorElement("Lagrange", cell, 1) Y = FiniteElement("Lagrange", cell, 1) M = X * Y u = Coefficient(M) x, y = split(u) L = inner(grad(x), grad(x))*dx + dot(x,x)*y*dx F = derivative(L, u) J = derivative(F, u) forms = [L,F,J] ufl-1.6.0/demo/Heat.ufl000066400000000000000000000023411255567402100146300ustar00rootroot00000000000000# Copyright (C) 2005-2009 Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Martin Sandve Alnes, 2009 # # The bilinear form a(v, u1) and linear form L(v) for # one backward Euler step with the heat equation. # cell = triangle element = FiniteElement("Lagrange", cell, 1) v = TestFunction(element) # Test function u1 = TrialFunction(element) # Value at t_n u0 = Coefficient(element) # Value at t_n-1 c = Coefficient(element) # Heat conductivity f = Coefficient(element) # Heat source k = Constant(cell) # Time step a = v*u1*dx + k*c*dot(grad(v), grad(u1))*dx L = v*u0*dx + k*v*f*dx ufl-1.6.0/demo/HornSchunck.ufl000066400000000000000000000012311255567402100161710ustar00rootroot00000000000000# # Implemented by imitation of # http://code.google.com/p/debiosee/wiki/DemosOptiocFlowHornSchunck # but not tested so this could contain errors! # # Finite element spaces for scalar and vector fields cell = triangle S = FiniteElement("CG", cell, 1) V = VectorElement("CG", cell, 1) # Optical flow function u = Coefficient(V) # Previous image brightness I0 = Coefficient(S) # Current image brightness I1 = Coefficient(S) # Regularization parameter lamda = Constant(cell) # Coefficiental to minimize M = (dot(u, grad(I1)) + (I1-I0))**2 * dx\ + lamda*inner(grad(u), grad(u)) * dx # Derived linear system L = derivative(M, u) a = derivative(L, u) L = -L ufl-1.6.0/demo/HyperElasticity.ufl000066400000000000000000000035041255567402100170730ustar00rootroot00000000000000# # Author: Martin Sandve Alnes # Date: 2008-12-22 # # Modified by Garth N. Wells, 2009 # Cell and its properties cell = tetrahedron d = cell.geometric_dimension() N = FacetNormal(cell) x = SpatialCoordinate(cell) # Elements u_element = VectorElement("CG", cell, 2) p_element = FiniteElement("CG", cell, 1) A_element = TensorElement("CG", cell, 1) # Test and trial functions v = TestFunction(u_element) w = TrialFunction(u_element) # Displacement at current and two previous timesteps u = Coefficient(u_element) up = Coefficient(u_element) upp = Coefficient(u_element) # Time parameters dt = Constant(cell) # Fiber field A = Coefficient(A_element) # External forces T = Coefficient(u_element) p0 = Coefficient(p_element) # Material parameters FIXME rho = Constant(cell) K = Constant(cell) c00 = Constant(cell) c11 = Constant(cell) c22 = Constant(cell) # Deformation gradient I = Identity(d) F = I + grad(u) F = variable(F) Finv = inv(F) J = det(F) # Left Cauchy-Green deformation tensor B = F*F.T I1_B = tr(B) I2_B = (I1_B**2 - tr(B*B))/2 I3_B = J**2 # Right Cauchy-Green deformation tensor C = F.T*F I1_C = tr(C) I2_C = (I1_C**2 - tr(C*C))/2 I3_C = J**2 # Green strain tensor E = (C-I)/2 # Mapping of strain in fiber directions Ef = A*E*A.T # Strain energy function W(Q(Ef)) Q = c00*Ef[0,0]**2 + c11*Ef[1,1]**2 + c22*Ef[2,2]**2 # FIXME: insert some simple law here W = (K/2)*(exp(Q) - 1) # + p stuff # First Piola-Kirchoff stress tensor P = diff(W, F) # Acceleration term discretized with finite differences k = dt/rho acc = (u - 2*up + upp) # Residual equation # FIXME: Can contain errors, not tested! a_F = inner(acc, v)*dx \ + k*inner(P, grad(v))*dx \ - k*dot(J*Finv*T, v)*ds(0) \ - k*dot(J*Finv*p0*N, v)*ds(1) # Jacobi matrix of residual equation a_J = derivative(a_F, u, w) # Export forms forms = [a_F, a_J] ufl-1.6.0/demo/HyperElasticity1D.ufl000066400000000000000000000005031255567402100172540ustar00rootroot00000000000000# # Author: Martin Sandve Alnes # Date: 2008-10-03 # cell = interval element = FiniteElement("CG", cell, 2) u = Coefficient(element) b = Constant(cell) K = Constant(cell) E = u.dx(0) + u.dx(0)**2 / 2 E = variable(E) Q = b*E**2 psi = K*(exp(Q)-1) f = psi*dx F = derivative(f, u) J = derivative(-F, u) forms = [f, F, J] ufl-1.6.0/demo/L2norm.ufl000066400000000000000000000002171255567402100151200ustar00rootroot00000000000000# # Author: Martin Sandve Alnes # Date: 2008-10-03 # element = FiniteElement("Lagrange", triangle, 1) f = Coefficient(element) a = f**2*dx ufl-1.6.0/demo/Mass.ufl000066400000000000000000000016341255567402100146560ustar00rootroot00000000000000# Copyright (C) 2004-2007 Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Martin Sandve Alnes, 2009 # # Last changed: 2009-03-02 # # The bilinear form for a mass matrix. element = FiniteElement("Lagrange", triangle, 1) u = TrialFunction(element) v = TestFunction(element) a = v*u*dx ufl-1.6.0/demo/MassAD.ufl000066400000000000000000000003431255567402100150570ustar00rootroot00000000000000# # Author: Martin Sandve Alnes # Date: 2008-10-28 # element = FiniteElement("Lagrange", triangle, 1) u = Coefficient(element) # L2 norm M = u**2/2*dx # source vector L = derivative(M, u) # mass matrix a = derivative(L, u) ufl-1.6.0/demo/MixedElasticity.ufl000066400000000000000000000030011255567402100170420ustar00rootroot00000000000000# Copyright (C) 2008-2010 Marie Rognes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # First added: 2008-10-03 # Last changed: 2011-07-22 # Define vectorized skew operator def skw(tau): sk = 2*skew(tau) return as_vector((sk[0,1], sk[0,2], sk[1,2])) cell = tetrahedron n = 3 # Finite element exterior calculus syntax r = 1 S = VectorElement("P Lambda", cell, r, form_degree=n-1) V = VectorElement("P Lambda", cell, r-1, form_degree=n) Q = VectorElement("P Lambda", cell, r-1, form_degree=n) # Alternative syntax: # S = VectorElement("BDM", cell, r) # V = VectorElement("Discontinuous Lagrange", cell, r-1) # Q = VectorElement("Discontinuous Lagrange", cell, r-1) W = MixedElement(S, V, Q) (sigma, u, gamma) = TrialFunctions(W) (tau, v, eta) = TestFunctions(W) a = (inner(sigma, tau) - tr(sigma)*tr(tau) + dot(div(tau), u) - dot(div(sigma), v) + inner(skw(tau), gamma) + inner(skw(sigma), eta))*dx ufl-1.6.0/demo/MixedMixedElement.ufl000066400000000000000000000014471255567402100173240ustar00rootroot00000000000000# Copyright (C) 2007 Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # A mixed element of mixed elements P3 = FiniteElement("Lagrange", triangle, 3) element = (P3 * P3) * (P3 * P3) ufl-1.6.0/demo/MixedPoisson.ufl000066400000000000000000000023301255567402100163660ustar00rootroot00000000000000# Copyright (C) 2006-2009 Anders Logg and Marie Rognes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Martin Sandve Alnes, 2009 # # Last changed: 2009-01-12 # # The bilinear form a(v, u) and linear form L(v) for # a mixed formulation of Poisson's equation with BDM # (Brezzi-Douglas-Marini) elements. # cell = triangle BDM1 = FiniteElement("Brezzi-Douglas-Marini", cell, 1) DG0 = FiniteElement("Discontinuous Lagrange", cell, 0) element = BDM1 * DG0 (tau, w) = TestFunctions(element) (sigma, u) = TrialFunctions(element) f = Coefficient(DG0) a = (dot(tau, sigma) - div(tau)*u + w*div(sigma))*dx L = w*f*dx ufl-1.6.0/demo/MixedPoisson2.ufl000066400000000000000000000006111255567402100164500ustar00rootroot00000000000000# # Author: Marie Rognes # Modified by: Martin Sandve Alnes # Date: 2009-02-12 # cell = tetrahedron RT = FiniteElement("Raviart-Thomas", cell, 1) DG = FiniteElement("DG", cell, 0) MX = RT * DG (u, p) = TrialFunctions(MX) (v, q) = TestFunctions(MX) n = FacetNormal(cell) a0 = (dot(u, v) + div(u)*q + div(v)*p)*dx a1 = (dot(u, v) + div(u)*q + div(v)*p)*dx - p*dot(v, n)*ds forms = [a0, a1] ufl-1.6.0/demo/NavierStokes.ufl000066400000000000000000000020421255567402100163620ustar00rootroot00000000000000# Copyright (C) 2004-2007 Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Martin Sandve Alnes # # Last changed: 2009-03-02 # # The bilinear form for the nonlinear term in the # Navier-Stokes equations with fixed convective velocity. cell = tetrahedron element = VectorElement("Lagrange", cell, 1) v = TestFunction(element) u = TrialFunction(element) w = Coefficient(element) Du = grad(u) a = dot( dot(w, Du), v )*dx ufl-1.6.0/demo/NeumannProblem.ufl000066400000000000000000000020051255567402100166660ustar00rootroot00000000000000# Copyright (C) 2006-2007 Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # The bilinear form a(v, u) and linear form L(v) for # Poisson's equation with Neumann boundary conditions. element = VectorElement("Lagrange", triangle, 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) g = Coefficient(element) a = inner(grad(v), grad(u))*dx L = inner(v, f)*dx + inner(v, g)*ds ufl-1.6.0/demo/NonlinearPoisson.ufl000066400000000000000000000004621255567402100172510ustar00rootroot00000000000000# nonlinear_poisson.ufl element = FiniteElement("Lagrange", triangle, 1) v = TestFunction(element) u = TrialFunction(element) u0 = Coefficient(element) f = Coefficient(element) a = (1+u0**2)*dot(grad(v), grad(u))*dx \ + 2*u0*u*dot(grad(v), grad(u0))*dx L = v*f*dx - (1+u0**2)*dot(grad(v), grad(u0))*dx ufl-1.6.0/demo/P5tet.ufl000066400000000000000000000014511255567402100147510ustar00rootroot00000000000000# Copyright (C) 2006-2007 Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # A fifth degree Lagrange finite element on a tetrahedron element = FiniteElement("Lagrange", tetrahedron, 5) ufl-1.6.0/demo/P5tri.ufl000066400000000000000000000014431255567402100147540ustar00rootroot00000000000000# Copyright (C) 2006-2007 Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # A fifth degree Lagrange finite element on a triangle element = FiniteElement("Lagrange", triangle, 5) ufl-1.6.0/demo/Poisson.ufl000066400000000000000000000021111255567402100153740ustar00rootroot00000000000000# Copyright (C) 2004-2008 Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Martin Sandve Alnes, 2009 # # Last changed: 2009-03-02 # # The bilinear form a(v, u) and linear form L(v) for Poisson's equation. element = FiniteElement("Lagrange", triangle, 1) u = TrialFunction(element) v = TestFunction(element) f = Coefficient(element) dx0_1 = dx(0, {"quadrature_order": 1}) dx0_2 = dx(0, {"quadrature_order": 2}) a = dot(grad(v), grad(u))*dx0_1 L = v*f*dx0_2 ufl-1.6.0/demo/PoissonDG.ufl000066400000000000000000000026151255567402100156200ustar00rootroot00000000000000# Copyright (C) 2006-2007 Kristiand Oelgaard and Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # First added: 2006-12-05 # Last changed: 2007-07-15 # # The bilinear form a(v, u) and linear form L(v) for # Poisson's equation in a discontinuous Galerkin (DG) # formulation. element = FiniteElement("Discontinuous Lagrange", triangle, 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) n = FacetNormal(triangle) h = Constant(triangle) gN = Coefficient(element) alpha = 4.0 gamma = 8.0 a = inner(grad(v), grad(u))*dx \ - inner(avg(grad(v)), jump(u, n))*dS \ - inner(jump(v, n), avg(grad(u)))*dS \ + alpha/h('+')*dot(jump(v, n), jump(u, n))*dS \ - inner(grad(v), u*n)*ds \ - inner(v*n, grad(u))*ds \ + gamma/h*v*u*ds L = v*f*dx + v*gN*ds ufl-1.6.0/demo/PoissonSystem.ufl000066400000000000000000000020561255567402100166110ustar00rootroot00000000000000# Copyright (C) 2005-2007 Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by: Martin Sandve Alnes, 2009 # # Last changed: 2009-03-02 # # The bilinear form a(v, u) and linear form L(v) for # Poisson's equation in system form (vector-valued). cell = triangle element = VectorElement("Lagrange", cell, 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) a = inner(grad(v), grad(u))*dx L = dot(v, f)*dx ufl-1.6.0/demo/PowAD.ufl000066400000000000000000000003321255567402100147170ustar00rootroot00000000000000# # Author: Martin Sandve Alnes # Date: 2008-10-03 # element = FiniteElement("Lagrange", triangle, 1) v = TestFunction(element) u = TrialFunction(element) w = Coefficient(element) L = w**5*v*dx a = derivative(L, w) ufl-1.6.0/demo/ProjectionSystem.ufl000066400000000000000000000002301255567402100172630ustar00rootroot00000000000000 element = FiniteElement("Lagrange", triangle, 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) a = u*v*dx L = f*v*dx ufl-1.6.0/demo/QuadratureElement.ufl000066400000000000000000000023711255567402100174010ustar00rootroot00000000000000# Copyright (C) 2008 Kristian B. Oelgaard # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # First added: 2008-03-31 # Last changed: 2008-03-31 # # The linearised bilinear form a(u,v) and linear form L(v) for # the nonlinear equation - div (1+u) grad u = f (non-linear Poisson) element = FiniteElement("Lagrange", triangle, 2) QE = FiniteElement("Quadrature", triangle, 3) sig = VectorElement("Quadrature", triangle, 3) v = TestFunction(element) u = TrialFunction(element) u0= Coefficient(element) C = Coefficient(QE) sig0 = Coefficient(sig) f = Coefficient(element) a = v.dx(i)*C*u.dx(i)*dx + v.dx(i)*2*u0*u*u0.dx(i)*dx L = v*f*dx - dot(grad(v), sig0)*dx ufl-1.6.0/demo/README000066400000000000000000000003011255567402100141110ustar00rootroot00000000000000To run these demos from within the source tree without needing to install UFL system-wide, update your paths according to export PATH="../scripts:$PATH" export PYTHONPATH="..:$PYTHONPATH" ufl-1.6.0/demo/RestrictedElement.ufl000066400000000000000000000026651255567402100174020ustar00rootroot00000000000000# Copyright (C) 2009 Kristian B. Oelgaard # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Restriction of a finite element. # The below syntax show how one can restrict a higher order Lagrange element # to only take into account those DOFs that live on the facets. # Restricted element CG_R = FiniteElement("Lagrange", triangle, 4)["facet"] u_r = TrialFunction(CG_R) v_r = TestFunction(CG_R) a = avg(v_r)*avg(u_r)*dS + v_r*u_r*ds #CG = FiniteElement("Lagrange", triangle, 4) #CG_R = CG["facet"] #u_r = TrialFunction(CG_R) #v_r = TestFunction(CG_R) #a = v_r('+')*u_r('+')*dS + v_r('-')*u_r('-')*dS + v_r*u_r*ds # Mixed element #CG = FiniteElement("Lagrange", triangle, 4) #CG_R = CG["facet"] #ME = CG * CG_R #u, u_r = TrialFunctions(ME) #v, v_r = TestFunctions(ME) #a = v*u*dx + v_r('+')*u_r('+')*dS + v_r('+')*u_r('+')*dS + v_r*u_r*ds ufl-1.6.0/demo/ShouldFail.ufl000066400000000000000000000003301255567402100157750ustar00rootroot00000000000000 # FIXME: This form passes validation but should fail since it mixes linear and bilinear terms. e = FiniteElement("CG", triangle, 1) f = Coefficient(e) v = TestFunction(e) u = TrialFunction(e) a = f*v*dx + u*v*dx ufl-1.6.0/demo/Source.ufl000066400000000000000000000002501255567402100152040ustar00rootroot00000000000000# # Author: Martin Sandve Alnes # Date: 2008-10-03 # element = FiniteElement("Lagrange", triangle, 1) v = TestFunction(element) f = Coefficient(element) a = f*v*dx ufl-1.6.0/demo/Stiffness.ufl000066400000000000000000000002731255567402100157150ustar00rootroot00000000000000# # Author: Martin Sandve Alnes # Date: 2008-10-03 # element = FiniteElement("Lagrange", triangle, 1) u = TrialFunction(element) v = TestFunction(element) a = dot(grad(u), grad(v))*dx ufl-1.6.0/demo/StiffnessAD.ufl000066400000000000000000000006611255567402100161230ustar00rootroot00000000000000# # Author: Martin Sandve Alnes # Date: 2008-10-30 # element = FiniteElement("Lagrange", triangle, 1) w = Coefficient(element) # H1 semi-norm f = inner(grad(w), grad(w))/2*dx # grad(w) : grad(v) b = derivative(f, w) # stiffness matrix, grad(u) : grad(v) a = derivative(b, w) # adjoint, grad(v) : grad(u) astar = adjoint(a) # action of adjoint, grad(v) : grad(w) astaraction = action(astar) forms = [f, b, a, astar, astaraction] ufl-1.6.0/demo/Stokes.ufl000066400000000000000000000021741255567402100152230ustar00rootroot00000000000000# Copyright (C) 2005-2007 Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by: Martin Sandve Alnes (2009) # Date: 2009-03-02 # # The bilinear form a(v, u) and Linear form L(v) for the Stokes # equations using a mixed formulation (Taylor-Hood elements). cell = triangle P2 = VectorElement("Lagrange", cell, 2) P1 = FiniteElement("Lagrange", cell, 1) TH = P2 * P1 (v, q) = TestFunctions(TH) (u, p) = TrialFunctions(TH) f = Coefficient(P2) a = (inner(grad(v), grad(u)) - div(v)*p + q*div(u))*dx L = dot(v, f)*dx ufl-1.6.0/demo/StokesEquation.ufl000066400000000000000000000023351255567402100167300ustar00rootroot00000000000000# Copyright (C) 2005-2009 Anders Logg and Harish Narayanan # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # The bilinear form a(v, u) and Linear form L(v) for the Stokes # equations using a mixed formulation (Taylor-Hood elements) in # combination with the lhs() and rhs() operators to extract the # bilinear and linear forms from an expression F = 0. cell = triangle P2 = VectorElement("Lagrange", cell, 2) P1 = FiniteElement("Lagrange", cell, 1) TH = P2 * P1 (v, q) = TestFunctions(TH) (u, p) = TrialFunctions(TH) f = Coefficient(P2) F = (inner(grad(v), grad(u)) - div(v)*p + q*div(u))*dx - dot(v, f)*dx a = lhs(F) L = rhs(F) ufl-1.6.0/demo/SubDomain.ufl000066400000000000000000000016721255567402100156360ustar00rootroot00000000000000# Copyright (C) 2008 Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # This example illustrates how to define a form over a # given subdomain of a mesh, in this case a functional. element = FiniteElement("CG", tetrahedron, 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) M = f*dx(2) + f*ds(5) ufl-1.6.0/demo/SubDomains.ufl000066400000000000000000000020451255567402100160140ustar00rootroot00000000000000# Copyright (C) 2008 Anders Logg and Kristian B. Oelgaard # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # This simple example illustrates how forms can be defined on different sub domains. # It is supported for all three integral types. element = FiniteElement("CG", tetrahedron, 1) v = TestFunction(element) u = TrialFunction(element) a = v*u*dx(0) + 10.0*v*u*dx(1) + v*u*ds(0) + 2.0*v*u*ds(1) + v('+')*u('+')*dS(0) + 4.3*v('+')*u('+')*dS(1) ufl-1.6.0/demo/TensorWeightedPoisson.ufl000066400000000000000000000017551255567402100202650ustar00rootroot00000000000000# Copyright (C) 2005-2007 Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # The bilinear form a(v, u) and linear form L(v) for # tensor-weighted Poisson's equation. P1 = FiniteElement("Lagrange", triangle, 1) P0 = TensorElement("Discontinuous Lagrange", triangle, 0, shape=(2, 2)) v = TestFunction(P1) u = TrialFunction(P1) C = Coefficient(P0) a = inner(grad(v), C*grad(u))*dx ufl-1.6.0/demo/VectorLaplaceGradCurl.ufl000066400000000000000000000025631255567402100201250ustar00rootroot00000000000000# Copyright (C) 2007 Marie Rognes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # The bilinear form a(v, u) and linear form L(v) for the Hodge Laplace # problem using 0- and 1-forms. Intended to demonstrate use of Nedelec # elements. def HodgeLaplaceGradCurl(element, felement): tau, v = TestFunctions(element) sigma, u = TrialFunctions(element) f = Coefficient(felement) a = ( inner(tau, sigma) - inner(grad(tau), u) +\ inner(v, grad(sigma)) + inner(curl(v), curl(u)) )*dx L = inner(v, f)*dx return a, L cell = tetrahedron order = 1 GRAD = FiniteElement("Lagrange", cell, order) CURL = FiniteElement("N1curl", cell, order) VectorLagrange = VectorElement("Lagrange", cell, order+1) a, L = HodgeLaplaceGradCurl(GRAD * CURL, VectorLagrange) ufl-1.6.0/demo/_TensorProductElement.ufl000066400000000000000000000017431255567402100202400ustar00rootroot00000000000000# Copyright (C) 2012 Marie E. Rognes (meg@simula.no) # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # First added: 2012-08-16 # Last changed: 2012-08-16 V0 = FiniteElement("CG", triangle, 1) V1 = FiniteElement("DG", interval, 0) V2 = FiniteElement("DG", tetrahedron, 0) V = TensorProductElement(V0, V1, V2) u = TrialFunction(V) v = TestFunction(V) dxxx = dx*dx*dx a = u*v*dxxx ufl-1.6.0/demo/clean.sh000077500000000000000000000003471255567402100146640ustar00rootroot00000000000000#!/bin/bash # ufl-analyse output rm -f *_debug.py rm -f *.analysis rm -f *.repr rm -f *.str rm -f *.tree # python compiled files rm -f *.pyc # latex files rm -f *.aux *.log *.pdf *.tex *.ps *.dvi # dot files rm -f *.dot *.png ufl-1.6.0/doc/000077500000000000000000000000001255567402100130605ustar00rootroot00000000000000ufl-1.6.0/doc/man/000077500000000000000000000000001255567402100136335ustar00rootroot00000000000000ufl-1.6.0/doc/man/man1/000077500000000000000000000000001255567402100144675ustar00rootroot00000000000000ufl-1.6.0/doc/man/man1/form2ufl.1.gz000066400000000000000000000011501255567402100167210ustar00rootroot00000000000000%Mn0|AN-hzK k*ِd>JbA*Iwe;M@{!nT,qXgO %i.j5/jg;X]a!?`\d@`).]o$~Ä2N_,K6ENE^S:/7ud@>0޺AF˻i9ez~?#E-,gq/E^3w yE8xZq4B"8M4F{mZiKx>>4j]xhO T?8ulN<  VF8$F*s-Y ``&)k\>oaI=ٵ#=8{6NtޤIq|Zz# ~M"me3̃Ǽ2{PfAsC|^&G?D{xk踵h.77ǶIr3xh`Y@WEr/u6|PU:R&>*ɊdN)*V7 QPqE5A|9Z4#ؠj GG6Dk *4\zIQRTb[a7YxD3@ vτ6";F 0SyR IM˝|Ra ;}Zޮ6%h mwufl-1.6.0/doc/man/man1/ufl-convert.1.gz000066400000000000000000000016531255567402100174410ustar00rootroot00000000000000*MTn6+{,`+(zK*^'Qa[$75hidH-IGcJhу Cy捂>lWEy;Ƃ6zɺJ ha18JHA T4#BIe,7@*<7sB)Ă (xDMKBz)Ğ?.Ïxʞk Z+gܴ HJRou4uٞ[, P<8]ш *u W֙l̀ӛ݀N%Rۡl(W ~&dY<s[,nQ (RСQ\Q /:Y-G驓0̰*`^ [hmF7q֡4}wi,+?@@Nu@r-Y^ eEU *4XΑ!]f+iJ] Vd.=BC%^V@e8'&S{_{D5@2~5τ6"u‚nOD\t~GR^1kMq59 > )aY:w)i!)Lp~oGѴR,CztHûh;O>7,s!$aG*L!٦I- _Tx%:.wJ2p'K#䩶c\jZ_&=H +KRJbA*]N(Ћ@r9ÙUPl$?,w|a,ȷ>B+FQ\@x(?ThΰyB=Q5Pa@ ~ynѲq8Ka $G ]p&M(#Vhtgl "XݺY{"rጨCjtjМ<:4ݞ6Oi ZK'Th!5*@5YDD+@Au'ï\V@>qH=.$v2^B8{=$ [l\w%.0=KeaZMkx(ђÕI3{-$.M\QfA0+2 3ؗ~G@!UTSs(ҒҒ2@ϏH-QIRdo) ,oiN@n- v}w°w?D1W.se!]V ^@6\y9?¿+2Aoo=l^7ufl-1.6.0/doc/man/man1/ufl2py.1.gz000066400000000000000000000011621255567402100164110ustar00rootroot00000000000000MSK0W {j!u]%aQǶ,z$w$,zHkۭ62kH%F hDNC1>fERP&]@oX' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/DOLFIN.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/DOLFIN.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/DOLFIN" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/DOLFIN" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." ufl-1.6.0/doc/sphinx/README000066400000000000000000000003071255567402100152510ustar00rootroot00000000000000To build the documentation, run `scripts/makedoc` from the top-level UFL directory. Running `make html` directly from within this directory won't work (unless the makedoc script has been run first). ufl-1.6.0/doc/sphinx/scripts/000077500000000000000000000000001255567402100160605ustar00rootroot00000000000000ufl-1.6.0/doc/sphinx/scripts/README_generate_modules.rst000066400000000000000000000021211255567402100231450ustar00rootroot00000000000000 Generate Modules ================ This script parses a directory tree looking for python modules and packages and creates ReST files appropriately to create code documentation with Sphinx. It also creates a modules index. Usage:: Usage: generate_modules.py [options] [exclude paths, ...] Note: By default this script will not overwrite already created files. Options: -h, --help show this help message and exit -n HEADER, --doc-header=HEADER Documentation Header (default=Project) -d DESTDIR, --dest-dir=DESTDIR Output destination directory -s SUFFIX, --suffix=SUFFIX module suffix (default=txt) -m MAXDEPTH, --maxdepth=MAXDEPTH Maximum depth of submodules to show in the TOC (default=4) -r, --dry-run Run the script without creating the files -f, --force Overwrite all the files -t, --no-toc Don't create the table of content file ufl-1.6.0/doc/sphinx/scripts/generate_index.py000077500000000000000000000050251255567402100214200ustar00rootroot00000000000000#!/usr/bin/env python # Copyright (C) 2011 Marie E. Rognes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # First added: 2011-06-09 # Last changed: 2011-06-09 # # This is a naive utility script for adding some labels to generated # .rst and for creating a main level index. It is used by # scripts/makedoc after generating .rst # import os, sys index_template = """ ############################################# Documentation for UFL v%s ############################################# .. UFL Language Reference .. ====================== .. .. * (What is accessible?) UFL Library Reference ===================== * :ref:`UFL Programmer's Reference ` (Packages and Modules. Everything.) .. toctree:: :hidden: :maxdepth: 1 modules """ def insert_labels(directory, filenames): """ Insert labels based on filename for those files defined by the given filenames relative to directory """ for name in filenames: filename = os.path.join(directory, name) file = open(filename) text = file.read() file.close() label = "\n.. _%s_package:\n\n" % "_".join(name.split(".")[:-1]) modded_text = label + text print(("Adding label to %s" % filename)) file = open(filename, "w") file.write(modded_text) file.close() def generate_index_file(output_dir, version): text = index_template % version filename = os.path.join(output_dir, "index.rst") print(("Writing documentation index file to %s" % filename)) file = open(filename, "w") file.write(text) file.close() def main(input_dir, version): files = ["ufl.rst", "ufl.finiteelement.rst", "ufl.algorithms.rst"] insert_labels(input_dir, files) generate_index_file(input_dir, version) if __name__ == '__main__': if len(sys.argv) != 3: print("Usage: python generate_index.py input_directory version") exit() main(sys.argv[1], sys.argv[2]) ufl-1.6.0/doc/sphinx/scripts/generate_modules.py000077500000000000000000000237321255567402100217660ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ sphinx-autopackage-script This script parses a directory tree looking for python modules and packages and creates ReST files appropriately to create code documentation with Sphinx. It also creates a modules index (named modules.). """ # Copyright 2008 Société des arts technologiques (SAT), http://www.sat.qc.ca/ # Copyright 2010 Thomas Waldmann # All rights reserved. # # 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 2 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 . # Modified by Marie E. Rognes (meg@simula.no), 2011 import os import optparse # automodule options OPTIONS = ['members', 'undoc-members', # 'inherited-members', # disabled because there's a bug in sphinx 'show-inheritance', ] INIT = '__init__.py' def makename(package, module): """Join package and module with a dot.""" # Both package and module can be None/empty. if package: name = package if module: name += '.' + module else: name = module return name def write_file(name, text, opts): """Write the output file for module/package .""" if opts.dryrun: return fname = os.path.join(opts.destdir, "%s.%s" % (name, opts.suffix)) if not opts.force and os.path.isfile(fname): print(('File %s already exists, skipping.' % fname)) else: print(('Creating file %s.' % fname)) f = open(fname, 'w') f.write(text) f.close() def format_heading(level, text): """Create a heading of [1, 2 or 3 supported].""" underlining = ['=', '-', '~', ][level-1] * len(text) return '%s\n%s\n\n' % (text, underlining) def format_directive(module, package=None): """Create the automodule directive and add the options.""" directive = '.. automodule:: %s\n' % makename(package, module) # MER: Treat stuff from __init__ a little differently. if "__init__" in module: directive += ' :%s:\n' % "noindex" else: for option in OPTIONS: directive += ' :%s:\n' % option return directive def create_module_file(package, module, opts): """Build the text of the file and write the file.""" text = format_heading(1, '%s Module' % module) text += format_heading(2, ':mod:`%s` Module' % module) text += format_directive(module, package) write_file(makename(package, module), text, opts) def create_package_file(root, master_package, subroot, py_files, opts, subs): """Build the text of the file and write the file.""" package = os.path.split(root)[-1] text = format_heading(1, '%s Package' % package) # add each package's module for py_file in py_files: if shall_skip(os.path.join(root, py_file)): continue is_package = py_file == INIT py_file = os.path.splitext(py_file)[0] py_path = makename(subroot, py_file) if is_package: heading = ':mod:`%s` Package' % package else: heading = ':mod:`%s` Module' % py_file text += format_heading(2, heading) text += format_directive(is_package and subroot or py_path, master_package) text += '\n' # build a list of directories that are packages (they contain an INIT file) subs = [sub for sub in subs if os.path.isfile(os.path.join(root, sub, INIT))] # if there are some package directories, add a TOC for theses subpackages if subs: text += format_heading(2, 'Subpackages') text += '.. toctree::\n\n' for sub in subs: text += ' %s.%s\n' % (makename(master_package, subroot), sub) text += '\n' write_file(makename(master_package, subroot), text, opts) def create_modules_toc_file(master_package, modules, opts, name='modules'): """ Create the module's index. """ text = format_heading(1, '%s Modules' % opts.header) text += '.. toctree::\n' text += ' :maxdepth: %s\n\n' % opts.maxdepth modules.sort() prev_module = '' for module in modules: # look if the module is a subpackage and, if yes, ignore it if module.startswith(prev_module + '.'): continue prev_module = module text += ' %s\n' % module write_file(name, text, opts) def shall_skip(module): """ Check if we want to skip this module. """ # skip it, if there is nothing (or just \n or \r\n) in the file return (os.path.getsize(module) < 3) def recurse_tree(path, excludes, opts): """ Look for every file in the directory tree and create the corresponding ReST files. """ # use absolute path for root, as relative paths like '../../foo' cause # 'if "/." in root ...' to filter out *all* modules otherwise path = os.path.abspath(path) # check if the base directory is a package and get is name if INIT in os.listdir(path): package_name = path.split(os.path.sep)[-1] else: package_name = None toc = [] tree = os.walk(path, False) for root, subs, files in tree: # keep only the Python script files py_files = sorted([f for f in files if os.path.splitext(f)[1] == '.py']) if INIT in py_files: py_files.remove(INIT) py_files.insert(0, INIT) # remove hidden ('.') and private ('_') directories subs = sorted([sub for sub in subs if sub[0] not in ['.', '_']]) # check if there are valid files to process # TODO: could add check for windows hidden files if "/." in root or "/_" in root \ or not py_files \ or is_excluded(root, excludes): continue if INIT in py_files: # we are in package ... if (# ... with subpackage(s) subs or # ... with some module(s) len(py_files) > 1 or # ... with a not-to-be-skipped INIT file not shall_skip(os.path.join(root, INIT)) ): subroot = root[len(path):].lstrip(os.path.sep).replace(os.path.sep, '.') create_package_file(root, package_name, subroot, py_files, opts, subs) toc.append(makename(package_name, subroot)) elif root == path: # if we are at the root level, we don't require it to be a package for py_file in py_files: if not shall_skip(os.path.join(path, py_file)): module = os.path.splitext(py_file)[0] create_module_file(package_name, module, opts) toc.append(makename(package_name, module)) # create the module's index if not opts.notoc: create_modules_toc_file(package_name, toc, opts) def normalize_excludes(rootpath, excludes): """ Normalize the excluded directory list: * must be either an absolute path or start with rootpath, * otherwise it is joined with rootpath * with trailing slash """ sep = os.path.sep f_excludes = [] for exclude in excludes: if not os.path.isabs(exclude) and not exclude.startswith(rootpath): exclude = os.path.join(rootpath, exclude) if not exclude.endswith(sep): exclude += sep f_excludes.append(exclude) return f_excludes def is_excluded(root, excludes): """ Check if the directory is in the exclude list. Note: by having trailing slashes, we avoid common prefix issues, like e.g. an exlude "foo" also accidentally excluding "foobar". """ sep = os.path.sep if not root.endswith(sep): root += sep for exclude in excludes: if root.startswith(exclude): return True return False def main(): """ Parse and check the command line arguments. """ parser = optparse.OptionParser(usage="""usage: %prog [options] [exclude paths, ...] Note: By default this script will not overwrite already created files.""") parser.add_option("-n", "--doc-header", action="store", dest="header", help="Documentation Header (default=Project)", default="Project") parser.add_option("-d", "--dest-dir", action="store", dest="destdir", help="Output destination directory", default="") parser.add_option("-s", "--suffix", action="store", dest="suffix", help="module suffix (default=txt)", default="txt") parser.add_option("-m", "--maxdepth", action="store", dest="maxdepth", help="Maximum depth of submodules to show in the TOC (default=4)", type="int", default=4) parser.add_option("-r", "--dry-run", action="store_true", dest="dryrun", help="Run the script without creating the files") parser.add_option("-f", "--force", action="store_true", dest="force", help="Overwrite all the files") parser.add_option("-t", "--no-toc", action="store_true", dest="notoc", help="Don't create the table of content file") (opts, args) = parser.parse_args() if not args: parser.error("package path is required.") else: rootpath, excludes = args[0], args[1:] if os.path.isdir(rootpath): # check if the output destination is a valid directory if opts.destdir and os.path.isdir(opts.destdir): excludes = normalize_excludes(rootpath, excludes) recurse_tree(rootpath, excludes, opts) else: print(('%s is not a valid output destination directory.' % opts.destdir)) else: print(('%s is not a valid directory.' % rootpath)) if __name__ == '__main__': main() ufl-1.6.0/doc/sphinx/source/000077500000000000000000000000001255567402100156715ustar00rootroot00000000000000ufl-1.6.0/doc/sphinx/source/_themes/000077500000000000000000000000001255567402100173155ustar00rootroot00000000000000ufl-1.6.0/doc/sphinx/source/_themes/fenics/000077500000000000000000000000001255567402100205645ustar00rootroot00000000000000ufl-1.6.0/doc/sphinx/source/_themes/fenics/README000066400000000000000000000001761255567402100214500ustar00rootroot00000000000000This theme is based on the default 'haiku' theme from Sphinx. Modified by Anders Logg for the FEniCS Project web pages, 2011. ufl-1.6.0/doc/sphinx/source/_themes/fenics/layout.html000066400000000000000000000044501255567402100227720ustar00rootroot00000000000000{# fenics/layout.html ~~~~~~~~~~~~~~~~~~ Sphinx layout template for the fenics theme. :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. This theme is based on the default 'haiku' theme from Sphinx. Modified by Anders Logg for the FEniCS Project web pages, 2011. #} {% extends "basic/layout.html" %} {% set script_files = script_files + ['_static/platform_sniff.js'] %} {% set css_files = css_files + ['_static/print.css'] %} {# do not display relbars #} {% block relbar1 %}{% endblock %} {% block relbar2 %}{% endblock %} {% macro nav() %}

{%- block fenicsrel1 %} {%- endblock %} About     Installation     Documentation     Developers     Citing     Contact {%- block fenicsrel2 %} {%- endblock %}

{% endmacro %} {% block content %}
{%- block fenicsheader %} {%- if theme_full_logo != "false" %} {%- else %} {%- if logo -%} {%- endif -%}

{{ shorttitle|e }}

{{ title|striptags|e }}

{%- endif %} {%- endblock %}
{{ nav() }}
{#{%- if display_toc %}

Table Of Contents

{{ toc }}
{%- endif %}#} {% block body %}{% endblock %}
{{ nav() }}
{% endblock %} ufl-1.6.0/doc/sphinx/source/_themes/fenics/static/000077500000000000000000000000001255567402100220535ustar00rootroot00000000000000ufl-1.6.0/doc/sphinx/source/_themes/fenics/static/alert_info_32.png000066400000000000000000000022201255567402100252030ustar00rootroot00000000000000PNG  IHDR szzWIDATXýohUu?"JzћWzqzq ]d܆.d54܄;5TҴ?i4Gd Mm^ݷLKbн=r<| cp'̣@&Vx8At,('"I'{ckꗥ o`0DQ:!:OnzVk%{u՜% [#RqԪ~7;(>`fKe? !H16O & ['U@8d =r3͔G8&/˅-*de `<<,X(lN`u"coEj2 Ijxp p:ְR.pe–maiQrP P!TxH5[rJmSyf|a5 (=*"®k"ג?K\}S=*iHCo,*8[xIM+טPlVn*9.5x;Y,/Kޒ.x?DZ 75+p[V/pS$zCE0%+-U,l("@m٤[bܵ`jƢ<O^YsEMdա=!ux o~\(9OV/_,P^V0S)Uޫje/~MĮOۇ@rD $L/OcbsJ~ӛj]FINZA`?0x!0#sS5;x7l(_.X+NaS\@~-h/5?._z9.NAzS|lbW2`mC.S?+pG 2Æ `.P.*[^⤢s:מT$)܁x #\?0OeEI/ɫVY WYK\Pl o/HtKt˕ HE`7ޏ"rQD:_gCqG &Y%ߐ!$IENDB`ufl-1.6.0/doc/sphinx/source/_themes/fenics/static/alert_warning_32.png000066400000000000000000000020441255567402100257210ustar00rootroot00000000000000PNG  IHDR szzIDATXML\U o((e &`uUvlUcM7mhbDn(u4F4ta1PB(-TZt޻y3yL;'}ssszX)`os;\df7x᜜z] FUqDtNDDԖsO^+239*zKΜ:ϜH24=g )@N$љmWPmGwV e ,X`Hܭ9S_Zt ' (PggPK4@*Bh * ;P@5) A@9 eunjnb..R-Q[ًȗt30'fQQpʍO8 P1avʮ 37 /azM,ýv Ä뭼eCp` ^=_}mBt4 B*cl) `A]Trh?~_Ke:p#dښiFPVt& cn*p  trfrHi`[B vpAg/IDAT u? .r/P5z=,%&;%tEXtdate:create2010-01-07T12:49:06+01:00:y%tEXtdate:modify2010-01-07T12:49:06+01:00KtIENDB`ufl-1.6.0/doc/sphinx/source/_themes/fenics/static/dolfin.png000066400000000000000000000010731255567402100240350ustar00rootroot00000000000000PNG  IHDR*sRGB pHYsnu>tIME  0IDAT(mKTqϽe^9 ,AE-APЦE6B(pc?3RhM38@|m*l|ι b*;vd'*"#\NwvI X6cDilUeʚ/f{YI&"U+qw韹EfR&6KA gf}4;^fV8'x/ߡaA(+5Fmzl·ZJTUBEfݸw@-yf %i؅;+kVU'r1{\5DPZQ2FA(/&Jf׶8q=^}t:M_]ut`1:4<;D4R]7x H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F9IDATxlKHTa?g:ye" jW "0"ADvF("hU *+\t3 8̜˜og/KǠ|P>(Ri( 8@RUb$b3-pz7[zZxu(}7+j %  j}+f]Qnjܰh\?IC킴FSחl5oFu($&|)+Kz`Rgi $M \(Y+- &)WUnlW^";|ǡp JBw`rUtk!1nәm*N/!dJ dl(#b()~=zKUepa“q~maEZ=V \J |#`*lуjݔcG@> dk=1CaL#!EfµF6M]q.4zە>GLNyQ!P'ƜhiӜ1hgrP=IENDB`ufl-1.6.0/doc/sphinx/source/_themes/fenics/static/fenics.css_t000066400000000000000000000157401255567402100243660ustar00rootroot00000000000000/* * fenics.css_t * ~~~~~~~~~~~~ * * Sphinx stylesheet -- fenics theme. * * Adapted from http://haiku-os.org/docs/Haiku-doc.css. * Original copyright message: * * Copyright 2008-2009, Haiku. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Francois Revol * Stephan Assmus * Braden Ewing * Humdinger * * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * * This theme is based on the default 'haiku' theme from Sphinx. * Modified by Anders Logg for the FEniCS Project web pages, 2011. */ @import url("basic.css"); html { margin: 0px; padding: 0px; } body { line-height: 1.5; margin: auto; padding: 0px; font-family: "DejaVu Sans", Arial, Helvetica, sans-serif; min-width: 59em; max-width: 70em; color: {{ theme_textcolor }}; } div.footer { padding: 8px; font-size: 11px; text-align: center; letter-spacing: 0.5px; } /* link colors and text decoration */ a:link { font-weight: bold; text-decoration: none; color: {{ theme_linkcolor }}; } a:visited { font-weight: bold; text-decoration: none; color: {{ theme_visitedlinkcolor }}; } a:hover, a:active { text-decoration: underline; color: {{ theme_hoverlinkcolor }}; } /* Some headers act as anchors, don't give them a hover effect */ h1 a:hover, a:active { text-decoration: none; color: {{ theme_headingcolor }}; } h2 a:hover, a:active { text-decoration: none; color: {{ theme_headingcolor }}; } h3 a:hover, a:active { text-decoration: none; color: {{ theme_headingcolor }}; } h4 a:hover, a:active { text-decoration: none; color: {{ theme_headingcolor }}; } a.headerlink { color: #a7ce38; padding-left: 5px; } a.headerlink:hover { color: #a7ce38; } /* basic text elements */ div.content { margin-top: 20px; margin-left: 40px; margin-right: 40px; margin-bottom: 50px; font-size: 0.9em; } /* heading and navigation */ div.header { position: relative; left: 0px; top: 0px; height: 85px; padding: 0 40px; background: #FFF url(bg-page.png) top left repeat-x; color: #ffffff; } div.header h1 { font-size: 1.6em; font-weight: normal; letter-spacing: 1px; color: {{ theme_headingcolor }}; border: 0; margin: 0; padding-top: 15px; } div.header h1 a { font-weight: normal; color: #ffffff; } div.header h2 { font-size: 1.3em; font-weight: normal; letter-spacing: 1px; text-transform: uppercase; color: #e0e0e0; border: 0; margin-top: -3px; padding: 0; } div.header img.rightlogo { float: right; } div.title { font-size: 1.3em; font-weight: bold; color: {{ theme_headingcolor }}; border-bottom: dotted thin #e0e0e0; margin-bottom: 25px; color: #ffffff; } div.topnav { background: #e0e0e0; } div.topnav p { margin-top: 0; margin-left: 40px; margin-right: 40px; margin-bottom: 0px; text-align: right; font-size: 0.8em; } div.bottomnav { background: #d81e28; } div.bottomnav p { margin-right: 40px; text-align: right; font-size: 0.8em; } a.uplink { font-weight: normal; } /* contents box */ table.index { margin: 0px 0px 30px 30px; padding: 1px; border-width: 1px; border-style: dotted; border-color: #e0e0e0; } table.index tr.heading { background-color: #e0e0e0; text-align: center; font-weight: bold; font-size: 1.1em; } table.index tr.index { background-color: #eeeeee; } table.index td { padding: 5px 20px; } table.index a:link, table.index a:visited { font-weight: normal; text-decoration: none; color: {{ theme_linkcolor }}; } table.index a:hover, table.index a:active { text-decoration: underline; color: {{ theme_hoverlinkcolor }}; } /* FEniCS User Guide styles and layout */ /* Rounded corner boxes */ /* Common declarations */ div.admonition { -webkit-border-radius: 10px; -khtml-border-radius: 10px; -moz-border-radius: 10px; border-radius: 10px; border-style: dotted; border-width: thin; border-color: #dcdcdc; padding: 10px 15px 10px 15px; margin-bottom: 15px; margin-top: 15px; } div.note { padding: 10px 15px 10px 80px; background: #e4ffde url(alert_info_32.png) 15px 15px no-repeat; min-height: 42px; } div.warning { padding: 10px 15px 10px 80px; background: #fffbc6 url(alert_warning_32.png) 15px 15px no-repeat; min-height: 42px; } div.seealso { background: #e4ffde; } /* More layout and styles */ h1 { font-size: 1.3em; font-weight: bold; color: {{ theme_headingcolor }}; border-bottom: dotted thin #e0e0e0; margin-top: 30px; } h2 { font-size: 1.2em; font-weight: normal; color: #000000; border-bottom: dotted thin #e0e0e0; margin-top: 30px; } h3 { font-size: 1.1em; font-weight: normal; color: {{ theme_headingcolor }}; margin-top: 30px; } h4 { font-size: 1.0em; font-weight: normal; color: {{ theme_headingcolor }}; margin-top: 30px; } p { text-align: justify; } p.last { margin-bottom: 0; } ol { padding-left: 20px; } ul { padding-left: 5px; margin-top: 3px; } li { line-height: 1.3; } div.content ul > li { -moz-background-clip:border; -moz-background-inline-policy:continuous; -moz-background-origin:padding; background: transparent url(bullet_orange.png) no-repeat scroll left 0.45em; list-style-image: none; list-style-type: none; padding: 0 0 0 1.666em; margin-bottom: 3px; } td { vertical-align: top; } tt { background-color: #e2e2e2; font-size: 1.0em; font-family: monospace; } pre { border-color: #0c3762; border-style: dotted; border-width: thin; margin: 0 0 12px 0; padding: 0.8em; background-color: #f0f0f0; } hr { border-top: 1px solid #ccc; border-bottom: 0; border-right: 0; border-left: 0; margin-bottom: 10px; margin-top: 20px; } /* printer only pretty stuff */ @media print { .noprint { display: none; } /* for acronyms we want their definitions inlined at print time */ acronym[title]:after { font-size: small; content: " (" attr(title) ")"; font-style: italic; } /* and not have mozilla dotted underline */ acronym { border: none; } div.topnav, div.bottomnav, div.header, table.index { display: none; } div.content { margin: 0px; padding: 0px; } html { background: #FFF; } } .viewcode-back { font-family: "DejaVu Sans", Arial, Helvetica, sans-serif; } div.viewcode-block:target { background-color: #f4debf; border-top: 1px solid #ac9; border-bottom: 1px solid #ac9; margin: -1px -12px; padding: 0 12px; } div.title a:visited { color: #ffffff; } ufl-1.6.0/doc/sphinx/source/_themes/fenics/static/unknown.png000066400000000000000000000053621255567402100242660ustar00rootroot00000000000000PNG  IHDR* pHYs B(x OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATxb_@`b *U E<߿IENDB`ufl-1.6.0/doc/sphinx/source/_themes/fenics/theme.conf000066400000000000000000000003631255567402100225370ustar00rootroot00000000000000[theme] inherit = basic stylesheet = fenics.css pygments_style = autumn [options] full_logo = false textcolor = #333333 headingcolor = #d81e28 linkcolor = #d81e28 visitedlinkcolor = #d81e28 hoverlinkcolor = #d8363f ufl-1.6.0/doc/sphinx/source/conf.py000066400000000000000000000155161255567402100172000ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath( os.path.join('..', '..', '..') )) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. needs_sphinx = '1.1' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.mathjax'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'UFL' copyright = 'FEniCS Project, http://www.fenicsproject.org/' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '1.2' # The full version, including alpha/beta/rc tags. release = '1.2.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. modindex_common_prefix = ["ufl.", "ufl.finiteelement", "ufl.algorithms."] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'nature' #html_theme = 'fenics' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = ["_themes"] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = "FEniCS Project" # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'UFLdoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). #latex_documents = [] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # Parameters affecting the LaTeX PNGs in the HTML files pngmath_latex_preamble = r" \usepackage{stmaryrd} " # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} # Use docstrings both from __init__ and class when using autodoc for # Python classes autoclass_content = "both" ufl-1.6.0/release.conf000066400000000000000000000002421255567402100146000ustar00rootroot00000000000000# Configuration file for fenics-release PACKAGE="ufl" BRANCH="master" FILES="ChangeLog ufl/__init__.py setup.py" POST_FILES="ChangeLog ufl/__init__.py setup.py" ufl-1.6.0/scripts/000077500000000000000000000000001255567402100140025ustar00rootroot00000000000000ufl-1.6.0/scripts/fixdates000077500000000000000000000005131255567402100155360ustar00rootroot00000000000000#!/usr/bin/env python import sys, os, glob, re, commands r = re.compile(r"(20..-..-..)") for fn in glob.glob("*.py"): st, op = commands.getstatusoutput("bzr log -l1 --line " + fn) m = r.search(op) date, = m.groups() st, op = commands.getstatusoutput("sed -i 's/Last changed.*/Last changed: %s/' %s" % (date, fn)) ufl-1.6.0/scripts/form2ufl000077500000000000000000000065301255567402100154700ustar00rootroot00000000000000#!/usr/bin/env python # # Simple conversion script from old FFC .form format to UFL format __authors__ = "Anders Logg" __date__ = "2008-08-01 -- 2009-03-15" import sys import re from os.path import exists def help(): print("Simple conversion script from old FFC .form format to UFL format.") print("") print("Usage: form2ufl .form") def error(message=None): if not message is None: print(message) else: help() sys.exit(1) def split_at_closing_brace(s): num_left = num_right = 0 for (i, c) in enumerate(s): if c == "(": num_left += 1 elif c == ")": num_right += 1 if num_left == num_right: return s[:i + 1], s[i + 1:] return s, "" def replace_transp(match): expr1, expr2 = split_at_closing_brace(match.groups()[0]) return expr1 + ".T" + expr2 def replace_mult(match): start, end = split_at_closing_brace(match.groups()[0]) expr1 = start.split(",")[0][1:].strip() expr2 = start.split(",")[1][:-1].strip() if "+" in expr1: expr1 = "(%s)" % expr1 if "+" in expr2: expr2 = "(%s)" % expr2 return expr1 + "*" + expr2 + end def form2ufl(code): # List of simple replacements simple_replacements = ((r"\.form", ".ufl"), (r"\bdot\b", "inner"), (r"\bD\b", "Dx"), (r"\bmodulus\b", "abs"), (r'"interval"', "interval"), (r'"triangle"', "triangle"), (r'"tetrahedron"', "tetrahedron"), (r'MeshSize', "Constant"), (r'FacetNormal', "VectorConstant"), (r"Nedelec", "N1curl"), (r"VectorQuadratureElement\(", 'VectorElement("Quadrature", '), (r"QuadratureElement\(", 'FiniteElement("Quadrature", ')) # List of complex replacements complex_replacements = ((r"\btransp(.*)", replace_transp), (r"\bmult(.*)", replace_mult)) # Iterate over replacemens for (a, b) in simple_replacements + complex_replacements: code = re.sub(a, b, code) return code def main(args): # Check command-line arguments if not len(args) == 1: error() # Get prefix and filenames words = args[0].split(".form") if not (len(words) == 2 and words[1] == ""): error() prefix = words[0] form_filename = prefix + ".form" ufl_filename = prefix + ".ufl" # Check if outfile exists if exists(ufl_filename): error("File already exists: " + ufl_filename) print("Converting %s --> %s" % (form_filename, ufl_filename)) # Read file try: file = open(form_filename, "r") code = file.read() file.close() except: error("Unable to read file: " + form_filename) # Convert to UFL code = form2ufl(code) # Write file try: file = open(ufl_filename, "w") file.write(code) file.close() except: error("Unable to write to file: " + ufl_filename) if __name__ == "__main__": main(sys.argv[1:]) sys.exit(0) ufl-1.6.0/scripts/makedoc000077500000000000000000000030411255567402100153310ustar00rootroot00000000000000# Copyright (C) 2011 Marie E. Rognes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # First added: 2011-06-09 # Last changed: 2011-06-27 # # This is a utility script for generating .rst and .html # documentation for UFL. # # Run from the top level UFL directory: # # ./scripts/makedoc # echo "" echo "--- Generating UFL html documentation" echo "" SPHINX_DIR=./doc/sphinx SPHINX_SCRIPT_DIR=$SPHINX_DIR/scripts SPHINX_SOURCE_DIR=$SPHINX_DIR/source # Generate .rst files $SPHINX_SCRIPT_DIR/generate_modules.py ufl --dest-dir=$SPHINX_SOURCE_DIR --suffix=rst --force echo "" echo "--- reSTructured text files generated in doc/sphinx/source/" echo "" # Generate index (and add some labels) VERSION=`grep '__version__' ufl/__init__.py | cut -d'"' -f2` $SPHINX_SCRIPT_DIR/generate_index.py $SPHINX_SOURCE_DIR $VERSION # Run sphinx make html cd $SPHINX_DIR make clean make html echo "" echo "--- HTML files generated in $SPHINX_DIR/build/html" echo "" ufl-1.6.0/scripts/ufl-analyse000077500000000000000000000057551255567402100161640ustar00rootroot00000000000000#!/usr/bin/env python __authors__ = "Martin Sandve Alnes" __date__ = "2008-05-09" # Modified by Anders Logg, 2009. # Last changed: 2015-01-05 import sys, optparse from ufl.log import warning from ufl.algorithms import load_ufl_file, validate_form, ufl2latex, tree_format # Get commandline options usage = """Analyse a .ufl file to find errors. Optionally write information about the forms for further inspection. Examples: ufl-analyse --quiet=0 --write=1 mass.ufl""" def opt(long, short, t, default, help): return optparse.make_option("--%s" % long, "-%s" % short, action="store", type=t, dest=long, default=default, help=help) option_list = [ \ opt("quiet", "q", "int", 1, "Do not print form information to screen."), opt("write", "w", "int", 0, "Write form information to file."), ] parser = optparse.OptionParser(usage=usage, option_list=option_list) args = sys.argv[1:] (options, args) = parser.parse_args(args=args) if not args: print("Missing files!") print() parser.print_usage() sys.exit(-1) filenames = args write_file = options.write quiet = options.quiet # Handle each form file separately for filename in filenames: # Check file suffix if not filename.endswith(".ufl"): warning("Filename '%s' does not end with .ufl." % filename) # Load form file, which triggers many consistency # checks while the form is being built print("Loading form file '%s'" % filename) try: # TODO: Forms that fail will usually fail inside this, which doesn't produce any log... Perhaps we should pass a log file to load_forms? data = load_ufl_file(filename) forms = data.forms except: print("Failed to load form file.") raise outputfilename = filename + ".log" if write_file: outputfile = open(outputfilename, "w") def write(*items): text = " ".join(str(s) for s in items) if write_file: outputfile.write(text) outputfile.flush() if not quiet: print(text) # Analyse each form separately for form in forms: # Validate form validate_form(form) # Compute form metadata and extract preprocessed form form_data = form.compute_form_data() preprocessed_form = form_data.preprocessed_form # Print form data write("\nForm data:\n", str(form_data)) # Print different representations write("\n\nForm pretty-print (original):\n", str(form)) write("\n\nForm pretty-print (preprocessed):\n", str(preprocessed_form)) write("\n\nForm representation (original):\n", repr(form)) write("\n\nForm representation (preprocessed):\n", repr(preprocessed_form)) write("\n\nForm tree formatting (original):\n", tree_format(form)) write("\n\nForm tree formatting (preprocessed):\n", tree_format(preprocessed_form)) write("\n\nForm LaTeX code (preprocessed):\n", ufl2latex(form)) if write_file: outputfile.close() ufl-1.6.0/scripts/ufl-convert000077500000000000000000000164401255567402100162010ustar00rootroot00000000000000#!/usr/bin/env python import sys, os, optparse from pprint import pprint from ufl.algorithms import load_ufl_file, ufl2dot, tree_format, forms2latexdocument, preprocess # --- Utilities # Taken from http://ivory.idyll.org/blog/mar-07/replacing-commands-with-subprocess from subprocess import Popen, PIPE, STDOUT def get_status_output(cmd, input=None, cwd=None, env=None): pipe = Popen(cmd, shell=True, cwd=cwd, env=env, stdout=PIPE, stderr=STDOUT) (output, errout) = pipe.communicate(input=input) assert not errout status = pipe.returncode return (status, output) def runcmd(cmd): status, output = get_status_output(cmd) if status != 0: print("*** Error:") print(output) sys.exit(-1) def write_file(filename, text): "Write text to a file and close it." f = open(filename, "w") f.write(text) f.close() print("Wrote file %s" % filename) # --- Option parsing usage = """Convert a .ufl file to some other format. Examples: ufl-convert -omydir -iyourdir -c -f -tpdf -s mass.ufl""" def opt(long, short, t, default, help): return optparse.make_option("--%s" % long, "-%s" % short, action="store", type=t, dest=long, default=default, help=help) option_list = [ \ # Directories: opt("outputdir", "o", "str", "", "Output directory."), opt("inputdir", "i", "str", "", "Input directory."), # Expression transformations: opt("compile", "c", "int", 0, "'Compile' forms: apply expression transformations like in a quadrature based form compilation."), opt("labeling", "l", "str", "repr", "Set to 'repr' or 'compact' for different naming of graph nodes."), # Output formats: opt("format", "f", "str", "", "Rendering format (str, repr, tree, dot, latex)."), opt("filetype", "t", "str", "", "Output file type (txt, py, dot, tex, ps, pdf, png)."), # Additional actions: opt("show", "s", "int", 0, "Open in an external viewer."), ] parser = optparse.OptionParser(usage=usage, option_list=option_list) args = sys.argv[1:] (options, args) = parser.parse_args(args=args) if not args: print("Missing files!") print() parser.print_usage() sys.exit(-1) # --- Handle each file for arg in args: # 0) Get and check filename uflfilename = os.path.join(options.inputdir, arg) path, name = os.path.split(uflfilename) basename, ext = os.path.splitext(name) if ext != ".ufl": print("Expecting a .ufl file, not ", uflfilename) sys.exit(-1) #print "uflfilename =", uflfilename #print "path =", path #print "name =", name #print "basename =", basename #print "ext =", ext # 1) Load forms #forms = load_forms(uflfilename) #formdatas = [f.compute_form_data() for f in forms] ufl_data = load_ufl_file(uflfilename) forms = ufl_data.forms #expressions = ufl_data.expressions # TODO: Allow rendering expressions without form stuff! # Preprocess forms for f in forms: f.compute_form_data(object_names=ufl_data.object_names) # 2) Transform forms compiled_forms = [] if options.compile: pass # TODO #compiled_forms = [compile_form(form) for form in forms] # 3) Render result format = options.format # Make format string conform if format == "latex": format = "tex" if format == "str": if options.compile: print("Warning: compile option not used.") rendered = "\n\n".join("Form %s:\n%s\n" % (f.form_data().name, str(f)) for f in forms) #rendered = "\n\n".join("Form %s:\n%s\n" % (f.form_data().name, str(f.form_data())) for f in forms) elif format == "repr": if options.compile: print("Warning: compile option not used.") rendered = "\n\n".join("Form %s:\n%s\n" % (f.form_data().name, repr(f)) for f in forms) elif format == "tree": if options.compile: print("Warning: compile option not used.") rendered = "\n\n".join("Form %s:\n%s\n" % (f.form_data().name, tree_format(f)) for f in forms) elif format == "dot": if options.compile: print("Warning: compile option not used.") data = [] nodeoffset = 0 for i, f in enumerate(forms): begin = (i == 0) end = (i == len(forms) - 1) dot, nodeoffset = ufl2dot(f, f.form_data().name, nodeoffset, begin, end, options.labeling, ufl_data.object_names) tmp = "/* Form %s: */\n%s\n" % (f.form_data().name, dot) data.append(tmp) rendered = "\n\n".join(data) elif format == "tex": rendered = forms2latexdocument(forms, uflfilename, compile=options.compile) else: print("Unknown rendering format ", format) sys.exit(-1) # 4) Convert file format filetype = options.filetype # Default filetypes: if not filetype: if format == "str": filetype = "str" elif format == "repr": filetype = "repr" elif format == "tree": filetype = "tree" elif format == "dot": filetype = "dot" elif format == "tex": filetype = "tex" # Guess that the filetype is the ext, usually the case ext = filetype if ext and not ext.startswith("."): ext = "." + ext outputfilename = os.path.join(options.outputdir, basename + ext) # Pure text files: if filetype == "txt" or filetype == format: write_file(outputfilename, rendered) # Conversions from tex: elif format == "tex": texfile = os.path.join(options.outputdir, basename + ".tex") # TODO: Use a proper temp file? write_file(texfile, rendered) if filetype == "pdf": flags = "-file-line-error-style -interaction=nonstopmode" cmd = "pdflatex %s '%s'" % (flags, texfile) runcmd(cmd) if options.show: print(outputfilename) runcmd("evince '%s' &" % outputfilename) else: print("Unknown format and filetype combination:", format, filetype) sys.exit(-1) # Conversions from dot: elif format == "dot": tempfile = os.path.join(options.outputdir, basename + ".dot") # TODO: Use a proper temp file? write_file(tempfile, rendered) if filetype in ("png", "ps", "svg", "gif", "dia", "imap", "cmapx"): # taken from "man dot" runcmd("dot -T%s -o'%s' '%s'" % (filetype, outputfilename, tempfile)) if options.show: runcmd("evince '%s' &" % outputfilename) elif filetype == "pdf": psfilename = os.path.join(options.outputdir, basename + ".ps") pdffilename = os.path.join(options.outputdir, basename + ".pdf") runcmd("dot -T%s -o'%s' '%s'" % (filetype, psfilename, tempfile)) runcmd("ps2pdf '%s' '%s'" % (psfilename, pdffilename)) if options.show: print(pdffilename) runcmd("evince '%s' &" % pdffilename) else: print("Unknown format and filetype combination:", format, filetype) sys.exit(-1) # That's all we know! else: print("*** Error: Sorry, don't know how to render format '%s' for file type '%s'." \ % (format, filetype)) print("Please try another combination, perhaps -fdot -tpdf?") sys.exit(-1) ufl-1.6.0/scripts/ufl-version000077500000000000000000000015671255567402100162120ustar00rootroot00000000000000#!/usr/bin/env python # Copyright (C) 2011 Anders Logg. # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # First added: 2011-06-14 # Last changed: 2011-06-14 # This is a simple script that just prints the UFL version number. from ufl import __version__ print(__version__) ufl-1.6.0/scripts/ufl2py000077500000000000000000000027071255567402100151570ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import with_statement import os, sys, optparse from ufl.algorithms import FormData, read_ufl_file, load_forms, validate_form, ufl2latex, tree_format # Get commandline options usage = """Convert a .ufl file to an executable .py file for debugging. Example: ufl2py Poisson.ufl""" def opt(long, short, t, default, help): return optparse.make_option("--%s" % long, "-%s" % short, action="store", type=t, dest=long, default=default, help=help) option_list = [] # opt("quiet", "q", "int", 1, "Do not print form information to screen."), # opt("write", "w", "int", 0, "Write form information to file."), # ] parser = optparse.OptionParser(usage=usage, option_list=option_list) args = sys.argv[1:] (options, args) = parser.parse_args(args=args) if not args: print("Missing files!") print() parser.print_usage() sys.exit(-1) filenames = args header = """#!/usr/bin/env python from ufl import * set_level(DEBUG) """ footer = "" # Handle each form file separately for filename in filenames: if not filename.endswith(".ufl"): print("Warning: Filename '%s' doesn't end with .ufl." % filename) # Read code fcode = read_ufl_file(filename) code = header + fcode + footer # Dump code to python file basename = os.path.splitext(os.path.basename(filename))[0] basename = "%s_debug" % basename pyname = "%s.py" % basename with file(pyname, "w") as f: f.write(code) ufl-1.6.0/setup.py000077500000000000000000000047541255567402100140420ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import print_function from distutils.core import setup from os.path import join as pjoin, split as psplit import re import sys import platform if sys.version_info < (2, 7): print("Python 2.7 or higher required, please upgrade.") sys.exit(1) scripts = [pjoin("scripts", "ufl-analyse"), pjoin("scripts", "ufl-convert"), pjoin("scripts", "ufl-version"), pjoin("scripts", "ufl2py"), pjoin("scripts", "form2ufl")] if platform.system() == "Windows" or "bdist_wininst" in sys.argv: # In the Windows command prompt we can't execute Python scripts # without a .py extension. A solution is to create batch files # that runs the different scripts. batch_files = [] for script in scripts: batch_file = script + ".bat" f = open(batch_file, "w") f.write('python "%%~dp0\%s" %%*' % psplit(script)[1]) f.close() batch_files.append(batch_file) scripts.extend(batch_files) version = re.findall('__version__ = "(.*)"', open('ufl/__init__.py', 'r').read())[0] setup(name="UFL", version = version, description = "Unified Form Language", author = "Martin Sandve Alnes, Anders Logg", author_email = "fenics@fenicsproject.org", url = "http://www.fenicsproject.org", classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Intended Audience :: Science/Research', 'Programming Language :: Python :: 2.5', 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', 'Topic :: Scientific/Engineering :: Mathematics', 'Topic :: Software Development :: Compilers', 'Topic :: Software Development :: Libraries :: Python Modules', ], scripts = scripts, packages = [ "ufl", "ufl.utils", "ufl.finiteelement", "ufl.core", "ufl.corealg", "ufl.algorithms", "ufl.formatting", ], package_dir = {"ufl": "ufl"}, data_files = [(pjoin("share", "man", "man1"), [pjoin("doc", "man", "man1", "ufl-analyse.1.gz"), pjoin("doc", "man", "man1", "ufl-convert.1.gz"), pjoin("doc", "man", "man1", "ufl-version.1.gz"), pjoin("doc", "man", "man1", "ufl2py.1.gz"), pjoin("doc", "man", "man1", "form2ufl.1.gz")])]) ufl-1.6.0/test/000077500000000000000000000000001255567402100132725ustar00rootroot00000000000000ufl-1.6.0/test/README000066400000000000000000000006271255567402100141570ustar00rootroot00000000000000To run tests, you need the py.test module. Just run cd /test py.test Or to run a single test file py.test test_literals.py To run these tests from within the source tree without needing to install the UFL Python module, update your PYTHONPATH and PATH by running source sourceme.sh in a bash shell or equivalent. If on other OSes, you must set the paths whichever way your OS requires. ufl-1.6.0/test/clean.sh000077500000000000000000000002761255567402100147200ustar00rootroot00000000000000#!/bin/bash # ufl-analyse output rm -f *_debug.py # python compiled files rm -f *.pyc # logs rm -f *.log # temp files from emacs rm -f *~ # temp files from py.test rm -rf __pycache__ ufl-1.6.0/test/conftest.py000066400000000000000000000030761255567402100154770ustar00rootroot00000000000000 import pytest import ufl from ufl import as_ufl, inner, dx from ufl.algorithms import compute_form_data class Tester: def assertTrue(self, a): assert a def assertFalse(self, a): assert not a def assertEqual(self, a, b): assert a == b def assertAlmostEqual(self, a, b): assert abs(a-b) < 1e-7 def assertNotEqual(self, a, b): assert a != b def assertIsInstance(self, obj, cls): assert isinstance(obj, cls) def assertNotIsInstance(self, obj, cls): assert not isinstance(obj, cls) def assertRaises(self, e, f): assert pytest.raises(e, f) def assertEqualTotalShape(self, value, expected): self.assertEqual(value.ufl_shape, expected.ufl_shape) self.assertEqual(set(value.free_indices()), set(expected.free_indices())) self.assertEqual(value.index_dimensions(), expected.index_dimensions()) def assertSameIndices(self, expr, free_indices): self.assertEqual(expr.free_indices(), free_indices) def assertEqualAfterPreprocessing(self, a, b): a2 = compute_form_data(a*dx).preprocessed_form b2 = compute_form_data(b*dx).preprocessed_form self.assertEqual(a2, b2) def assertEqualValues(self, A, B): B = as_ufl(B) self.assertEqual(A.ufl_shape, B.ufl_shape) self.assertEqual(inner(A-B, A-B)(None), 0) @pytest.fixture(scope="session") def self(): return Tester() _all_cells = [ufl.interval, ufl.triangle, ufl.tetrahedron] @pytest.fixture(params=_all_cells) def cell(request): return request.param ufl-1.6.0/test/mockobjects.py000066400000000000000000000020251255567402100161460ustar00rootroot00000000000000 from ufl import * class MockMesh: def __init__(self, ufl_id): self._ufl_id = ufl_id def ufl_id(self): return self._ufl_id def ufl_domain(self): return Domain(triangle, label="MockMesh_id_%d" % self.ufl_id(), data=self) def ufl_measure(self, integral_type="dx", subdomain_id="everywhere", metadata=None, subdomain_data=None): return Measure(integral_type, subdomain_id=subdomain_id, metadata=metadata, domain=self, subdomain_data=subdomain_data) class MockMeshFunction: "Mock class for the pydolfin compatibility hack for domain data with [] syntax." def __init__(self, ufl_id, mesh): self._mesh = mesh self._ufl_id = ufl_id def ufl_id(self): return self._ufl_id def mesh(self): return self._mesh def ufl_measure(self, integral_type=None, subdomain_id="everywhere", metadata=None): return Measure( integral_type, subdomain_id=subdomain_id, metadata=metadata, domain=self.mesh(), subdomain_data=self) ufl-1.6.0/test/pytest.ini000066400000000000000000000000651255567402100153240ustar00rootroot00000000000000[pytest] minversion = 2.4 looponfailroots = . ../ufl ufl-1.6.0/test/sourceme.sh000066400000000000000000000003041255567402100154450ustar00rootroot00000000000000# Set python path to find the local uninstalled ufl version export PYTHONPATH="`pwd`/..:$PYTHONPATH" export PATH="`pwd`/../scripts:$PATH" echo PYTHONPATH is now $PYTHONPATH echo PATH is now $PATH ufl-1.6.0/test/test_algorithms.py000077500000000000000000000072701255567402100170650ustar00rootroot00000000000000#!/usr/bin/env py.test __authors__ = "Martin Sandve Alnes" __date__ = "2008-03-12 -- 2009-01-28" # Modified by Anders Logg, 2008 # Modified by Garth N. Wells, 2009 import pytest from pprint import * from ufl import * from ufl.algorithms import * from ufl.classes import Sum, Product from ufl.corealg.traversal import * # TODO: add more tests, covering all utility algorithms @pytest.fixture(scope='module') def element(): return FiniteElement("CG", triangle, 1) @pytest.fixture(scope='module') def arguments(element): v = TestFunction(element) u = TrialFunction(element) return (v, u) @pytest.fixture(scope='module') def coefficients(element): c = Coefficient(element) f = Coefficient(element) return (c, f) @pytest.fixture def forms(arguments, coefficients): v, u = arguments c, f = coefficients n = FacetNormal(triangle) a = u * v * dx L = f * v * dx b = u * v * dx(0) + inner(c * grad(u), grad(v)) * \ dx(1) + dot(n, grad(u)) * v * ds + f * v * dx return (a, L, b) def test_extract_arguments_vs_fixture(arguments, forms): assert arguments == tuple(extract_arguments(forms[0])) assert tuple(arguments[:1]) == tuple(extract_arguments(forms[1])) def test_extract_coefficients_vs_fixture(coefficients, forms): assert coefficients == tuple(extract_coefficients(forms[2])) def test_extract_elements_and_extract_unique_elements(forms): b = forms[2] integrals = b.integrals_by_type("cell") integrand = integrals[0].integrand() element1 = FiniteElement("CG", triangle, 1) element2 = FiniteElement("CG", triangle, 1) v = TestFunction(element1) u = TrialFunction(element2) a = u * v * dx assert extract_elements(a) == (element1, element2) assert extract_unique_elements(a) == (element1,) def test_pre_and_post_traversal(): element = FiniteElement("CG", "triangle", 1) v = TestFunction(element) f = Coefficient(element) g = Coefficient(element) p1 = f * v p2 = g * v s = p1 + p2 # NB! These traversal algorithms are intended to guarantee only # parent before child and vice versa, not this particular ordering: assert list(pre_traversal(s)) == [s, p2, g, v, p1, f, v] assert list(post_traversal(s)) == [g, v, p2, f, v, p1, s] assert list(unique_pre_traversal(s)) == [s, p2, g, v, p1, f] assert list(unique_post_traversal(s)) == [v, f, p1, g, p2, s] def test_expand_indices(): element = FiniteElement("Lagrange", triangle, 2) v = TestFunction(element) u = TrialFunction(element) def evaluate(form): return form.cell_integral()[0].integrand()((), {v: 3, u: 5}) # TODO: How to define values of derivatives? a = div(grad(v)) * u * dx # a1 = evaluate(a) a = expand_derivatives(a) # a2 = evaluate(a) a = expand_indices(a) # a3 = evaluate(a) # TODO: Compare a1, a2, a3 # TODO: Test something more def test_adjoint(): cell = triangle V1 = FiniteElement("CG", cell, 1) V2 = FiniteElement("CG", cell, 2) u = TrialFunction(V1) v = TestFunction(V2) assert u.number() > v.number() u2 = Argument(V1, 2) v2 = Argument(V2, 3) assert u2.number() < v2.number() a = u * v * dx a_arg_degrees = [arg.element().degree() for arg in extract_arguments(a)] assert a_arg_degrees == [2, 1] b = adjoint(a) b_arg_degrees = [arg.element().degree() for arg in extract_arguments(b)] assert b_arg_degrees == [1, 2] c = adjoint(a, (u2, v2)) c_arg_degrees = [arg.element().degree() for arg in extract_arguments(c)] assert c_arg_degrees == [1, 2] d = adjoint(b) d_arg_degrees = [arg.element().degree() for arg in extract_arguments(d)] assert d_arg_degrees == [2, 1] ufl-1.6.0/test/test_analyse_demos.py000077500000000000000000000043451255567402100175370ustar00rootroot00000000000000#!/usr/bin/env py.test __authors__ = "Martin Sandve Alnes" __date__ = "2008-09-28 -- 2008-09-28" import os import pytest from ufl.algorithms import load_ufl_file, validate_form from glob import glob # Taken from # http://ivory.idyll.org/blog/mar-07/replacing-commands-with-subprocess from subprocess import Popen, PIPE, STDOUT def get_status_output(cmd, input=None, cwd=None, env=None): pipe = Popen(cmd, shell=True, cwd=cwd, env=env, stdout=PIPE, stderr=STDOUT) (output, errout) = pipe.communicate(input=input) assert not errout status = pipe.returncode return (status, output) def _test_all_demos(): # Check all at once skip = set(glob("../demo/_*.ufl")) filenames = [f for f in sorted(glob("../demo/*.ufl")) if not f in skip] cmd = "ufl-analyse %s" % " ".join(filenames) status, output = get_status_output(cmd) assert status == 0 def get_demo_filenames(): skiplist = glob("../demo/_*.ufl") # + ["../demo/Hyperelasticity3D.ufl"] filenames = [] for f in sorted(glob("../demo/*.ufl")): if f in skiplist: print(("Skipping demo %s" % f)) else: filenames.append(f) return filenames def xtest_each_demo_with_ufl_analyse(): "Check each file from cmdline with ufl-analyse." for f in get_demo_filenames(): cmd = "ufl-analyse %s" % f status, output = get_status_output(cmd) assert status == 0 if status == 0: print(("Successfully analysed %s without problems" % f)) else: name = "%s.analysis" % f print(("Encountered problems when analysing %s " "(return code %s), see output in file %s" % (f, status, name))) of = open(name, "w") of.write(output) of.close() print() print(output) print() def test_each_demo_with_validate_form(): "Check each form in each file with validate_form." for filename in get_demo_filenames(): print(filename) data = load_ufl_file(filename) for form in data.forms: try: validate_form(form) excepted = 0 except: excepted = 1 assert excepted == 0, filename ufl-1.6.0/test/test_apply_restrictions.py000066400000000000000000000033151255567402100206420ustar00rootroot00000000000000 from pytest import raises from ufl import * from ufl.algorithms.apply_restrictions import apply_restrictions from ufl.algorithms import renumber_indices def test_apply_restrictions(): cell = triangle V0 = FiniteElement("DG", cell, 0) V1 = FiniteElement("Lagrange", cell, 1) V2 = FiniteElement("Lagrange", cell, 2) f0 = Coefficient(V0) f = Coefficient(V1) g = Coefficient(V2) n = FacetNormal(cell) x = SpatialCoordinate(cell) assert raises(UFLException, lambda: apply_restrictions(f0)) assert raises(UFLException, lambda: apply_restrictions(grad(f))) assert raises(UFLException, lambda: apply_restrictions(n)) # Continuous function gets default restriction if none # provided otherwise the user choice is respected assert apply_restrictions(f) == f('+') assert apply_restrictions(f('-')) == f('-') assert apply_restrictions(f('+')) == f('+') # Propagation to terminals assert apply_restrictions((f + f0)('+')) == f('+') + f0('+') # Propagation stops at grad assert apply_restrictions(grad(f)('-')) == grad(f)('-') assert apply_restrictions((grad(f)**2)('+')) == grad(f)('+')**2 assert apply_restrictions((grad(f) + grad(g))('-')) == (grad(f)('-') + grad(g)('-')) # x is the same from both sides but computed from one of them assert apply_restrictions(x) == x('+') # n on a linear mesh is opposite pointing from the other side assert apply_restrictions(n('+')) == n('+') assert renumber_indices(apply_restrictions(n('-'))) == renumber_indices(as_tensor(-1*n('+')[i], i)) # This would be nicer, but -f is translated to -1*f which is translated to as_tensor(-1*f[i], i). #assert apply_restrictions(n('-')) == -n('+') ufl-1.6.0/test/test_arithmetic.py000077500000000000000000000051441255567402100170430ustar00rootroot00000000000000#!/usr/bin/env py.test import pytest from ufl import * from ufl.classes import Division, FloatValue, IntValue def test_scalar_casting(self): f = as_ufl(2.0) r = as_ufl(4) self.assertIsInstance(f, FloatValue) self.assertIsInstance(r, IntValue) assert float(f) == 2.0 assert int(r) == 4 def test_ufl_float_division(self): d = SpatialCoordinate(triangle)[0] / 10.0 # TODO: Use mock instead of x self.assertIsInstance(d, Division) def test_float_ufl_division(self): d = 3.14 / SpatialCoordinate(triangle)[0] # TODO: Use mock instead of x self.assertIsInstance(d, Division) def test_float_division(self): d = as_ufl(20.0) / 10.0 self.assertIsInstance(d, FloatValue) assert float(d) == 2.0 def test_int_division(self): # UFL treats all divisions as true division d = as_ufl(40) / 7 self.assertIsInstance(d, FloatValue) assert float(d) == 40.0 / 7.0 #self.assertAlmostEqual(float(d), 40 / 7.0, 15) def test_float_int_division(self): d = as_ufl(20.0) / 5 self.assertIsInstance(d, FloatValue) assert float(d) == 4.0 def test_floor_division_fails(self): f = as_ufl(2.0) r = as_ufl(4) s = as_ufl(5) self.assertRaises(NotImplementedError, lambda: r // 4) self.assertRaises(NotImplementedError, lambda: r // s) self.assertRaises(NotImplementedError, lambda: f // s) def test_elem_mult(self): self.assertEqual(int(elem_mult(2, 3)), 6) v = as_vector((1, 2, 3)) u = as_vector((4, 5, 6)) self.assertEqual(elem_mult(v, u), as_vector((4, 10, 18))) def test_elem_mult_on_matrices(self): A = as_matrix(((1, 2), (3, 4))) B = as_matrix(((4, 5), (6, 7))) self.assertEqual(elem_mult(A, B), as_matrix(((4, 10), (18, 28)))) x, y = SpatialCoordinate(triangle) A = as_matrix(((x, y), (3, 4))) B = as_matrix(((4, 5), (y, x))) self.assertEqual(elem_mult(A, B), as_matrix(((4*x, 5*y), (3*y, 4*x)))) x, y = SpatialCoordinate(triangle) A = as_matrix(((x, y), (3, 4))) B = Identity(2) self.assertEqual(elem_mult(A, B), as_matrix(((x, 0), (0, 4)))) def test_elem_div(self): x, y, z = SpatialCoordinate(tetrahedron) A = as_matrix(((x, y, z), (3, 4, 5))) B = as_matrix(((7, 8, 9), (z, x, y))) self.assertEqual(elem_div(A, B), as_matrix(((x/7, y/8, z/9), (3/z, 4/x, 5/y)))) def test_elem_op(self): x, y, z = SpatialCoordinate(tetrahedron) A = as_matrix(((x, y, z), (3, 4, 5))) self.assertEqual(elem_op(sin, A), as_matrix(((sin(x), sin(y), sin(z)), (sin(3), sin(4), sin(5))))) self.assertEqual(elem_op(sin, A).dx(0).ufl_shape, (2, 3)) ufl-1.6.0/test/test_automatic_differentiation.py000077500000000000000000000455141255567402100221370ustar00rootroot00000000000000#!/usr/bin/env py.test """ These tests should cover the behaviour of the automatic differentiation algorithm at a technical level, and are thus implementation specific. Other tests check for mathematical correctness of diff and derivative. """ from __future__ import division, absolute_import, print_function, unicode_literals import pytest from itertools import chain import ufl # This imports everything external code will see from ufl from ufl import * import ufl.algorithms from ufl.common import unique_post_traversal from ufl.conditional import Conditional from ufl.algorithms import expand_derivatives class ExpressionCollection(object): def __init__(self, cell): self.cell = cell d = cell.geometric_dimension() x = SpatialCoordinate(cell) n = FacetNormal(cell) c = CellVolume(cell) h = Circumradius(cell) f = FacetArea(cell) #s = CellSurfaceArea(cell) # FIXME: Add all new geometry types here! I = Identity(d) eps = PermutationSymbol(d) U = FiniteElement("U", cell, None) V = VectorElement("U", cell, None) W = TensorElement("U", cell, None) u = Coefficient(U) v = Coefficient(V) w = Coefficient(W) du = Argument(U, 0) dv = Argument(V, 1) dw = Argument(W, 2) class ObjectCollection(object): pass self.shared_objects = ObjectCollection() for key, value in list(locals().items()): setattr(self.shared_objects, key, value) self.literals = list(map(as_ufl, [0, 1, 3.14, I, eps])) self.geometry = [x, n, c, h, f] self.functions = [u, du, v, dv, w, dw] self.terminals = [] self.terminals += self.literals self.terminals += self.geometry self.terminals += self.functions self.algebra = ([ u*2, v*2, w*2, u+2*u, v+2*v, w+2*w, 2/u, u/2, v/2, w/2, u**3, 3**u, ]) self.mathfunctions = ([ abs(u), sqrt(u), exp(u), ln(u), cos(u), sin(u), tan(u), acos(u), asin(u), atan(u), erf(u), bessel_I(1, u), bessel_J(1, u), bessel_K(1, u), bessel_Y(1, u), ]) self.variables = ([ variable(u), variable(v), variable(w), variable(w*u), 3*variable(w*u), ]) if d == 1: w2 = as_matrix(((u**2,),)) if d == 2: w2 = as_matrix(((u**2, u**3), (u**4, u**5))) if d == 3: w2 = as_matrix(((u**2, u**3, u**4), (u**4, u**5, u**6), (u**6, u**7, u**8))) # Indexed, ListTensor, ComponentTensor, IndexSum i, j, k, l = indices(4) self.indexing = ([ v[0], w[d-1, 0], v[i], w[i, j], v[:], w[0,:], w[:, 0], v[...], w[0, ...], w[..., 0], v[i]*v[j], w[i, 0]*v[j], w[d-1, j]*v[i], v[i]*v[i], w[i, 0]*w[0, i], v[i]*w[0, i], v[j]*w[d-1, j], w[i, i], w[i, j]*w[j, i], as_tensor(v[i]*w[k, 0], (k, i)), as_tensor(v[i]*w[k, 0], (k, i))[:, l], as_tensor(w[i, j]*w[k, l], (k, j, l, i)), as_tensor(w[i, j]*w[k, l], (k, j, l, i))[0, 0, 0, 0], as_vector((u, 2, 3)), as_matrix(((u**2, u**3), (u**4, u**5))), as_vector((u, 2, 3))[i], w2[i, j]*w[i, j], ]) self.conditionals = ([ conditional(le(u, 1.0), 1, 0), conditional(eq(3.0, u), 1, 0), conditional(ne(sin(u), cos(u)), 1, 0), conditional(lt(sin(u), cos(u)), 1, 0), conditional(ge(sin(u), cos(u)), 1, 0), conditional(gt(sin(u), cos(u)), 1, 0), conditional(And(lt(u, 3), gt(u, 1)), 1, 0), conditional(Or(lt(u, 3), gt(u, 1)), 1, 0), conditional(Not(ge(u, 0.0)), 1, 0), conditional(le(u, 0.0), 1, 2), conditional(Not(ge(u, 0.0)), 1, 2), conditional(And(Not(ge(u, 0.0)), lt(u, 1.0)), 1, 2), conditional(le(u, 0.0), u**3, ln(u)), ]) self.restrictions = [u('+'), u('-'), v('+'), v('-'), w('+'), w('-')] if d > 1: i, j = indices(2) self.restrictions += ([ v('+')[i]*v('+')[i], v[i]('+')*v[i]('+'), (v[i]*v[i])('+'), (v[i]*v[j])('+')*w[i, j]('+'), ]) self.noncompounds = [] self.noncompounds += self.algebra self.noncompounds += self.mathfunctions self.noncompounds += self.variables self.noncompounds += self.indexing self.noncompounds += self.conditionals self.noncompounds += self.restrictions if d == 1: self.tensorproducts = [] else: self.tensorproducts = ([ dot(v, v), dot(v, w), dot(w, w), inner(v, v), inner(w, w), outer(v, v), outer(w, v), outer(v, w), outer(w, w), ]) if d == 1: self.tensoralgebra = [] else: self.tensoralgebra = ([ w.T, sym(w), skew(w), dev(w), det(w), tr(w), cofac(w), inv(w), ]) if d != 3: self.crossproducts = [] else: self.crossproducts = ([ cross(v, v), cross(v, 2*v), cross(v, w[0,:]), cross(v, w[:, 1]), cross(w[:, 0], v), ]) self.compounds = [] self.compounds += self.tensorproducts self.compounds += self.tensoralgebra self.compounds += self.crossproducts self.all_expressions = [] self.all_expressions += self.terminals self.all_expressions += self.noncompounds self.all_expressions += self.compounds @pytest.fixture(params=(1,2,3)) def d_expr(request): d = request.param cell = { 1: interval, 2: triangle, 3: tetrahedron }[d] expr = ExpressionCollection(cell) return d, expr def ad_algorithm(expr): #alt = 1 #alt = 4 #alt = 6 alt = 0 if alt == 0: return expand_derivatives(expr) elif alt == 1: return expand_derivatives(expr, apply_expand_compounds_before=True, apply_expand_compounds_after=False, use_alternative_wrapper_algorithm=True) elif alt == 2: return expand_derivatives(expr, apply_expand_compounds_before=False, apply_expand_compounds_after=True, use_alternative_wrapper_algorithm=False) elif alt == 3: return expand_derivatives(expr, apply_expand_compounds_before=False, apply_expand_compounds_after=False, use_alternative_wrapper_algorithm=False) elif alt == 4: return expand_derivatives(expr, apply_expand_compounds_before=False, apply_expand_compounds_after=False, use_alternative_wrapper_algorithm=True) elif alt == 5: return expand_derivatives(expr, apply_expand_compounds_before=False, apply_expand_compounds_after=False, use_alternative_wrapper_algorithm=False) def _test_no_derivatives_no_change(self, collection): for expr in collection: before = expr after = ad_algorithm(before) #print '\n', str(before), '\n', str(after), '\n' self.assertEqualTotalShape(before, after) assert before == after def _test_no_derivatives_but_still_changed(self, collection): # Planning to fix these: for expr in collection: before = expr after = ad_algorithm(before) #print '\n', str(before), '\n', str(after), '\n' self.assertEqualTotalShape(before, after) #assert before == after # Without expand_compounds self.assertNotEqual(before, after) # With expand_compounds def test_only_terminals_no_change(self, d_expr): d, ex = d_expr _test_no_derivatives_no_change(self, ex.terminals) def test_no_derivatives_no_change(self, d_expr): d, ex = d_expr _test_no_derivatives_no_change(self, ex.noncompounds) def xtest_compounds_no_derivatives_no_change(self, d_expr): # This test fails with expand_compounds enabled d, ex = d_expr _test_no_derivatives_no_change(self, ex.compounds) def test_zero_derivatives_of_terminals_produce_the_right_types_and_shapes(self, d_expr): d, ex = d_expr _test_zero_derivatives_of_terminals_produce_the_right_types_and_shapes(self, ex) def _test_zero_derivatives_of_terminals_produce_the_right_types_and_shapes(self, collection): c = Constant(collection.shared_objects.cell) u = Coefficient(collection.shared_objects.U) v = Coefficient(collection.shared_objects.V) w = Coefficient(collection.shared_objects.W) for t in collection.terminals: for var in (u, v, w): before = derivative(t, var) # This will often get preliminary simplified to zero after = ad_algorithm(before) expected = 0*t #print '\n', str(expected), '\n', str(after), '\n', str(before), '\n' assert after == expected before = derivative(c*t, var) # This will usually not get simplified to zero after = ad_algorithm(before) expected = 0*t #print '\n', str(expected), '\n', str(after), '\n', str(before), '\n' assert after == expected def test_zero_diffs_of_terminals_produce_the_right_types_and_shapes(self, d_expr): d, ex = d_expr _test_zero_diffs_of_terminals_produce_the_right_types_and_shapes(self, ex) def _test_zero_diffs_of_terminals_produce_the_right_types_and_shapes(self, collection): c = Constant(collection.shared_objects.cell) u = Coefficient(collection.shared_objects.U) v = Coefficient(collection.shared_objects.V) w = Coefficient(collection.shared_objects.W) vu = variable(u) vv = variable(v) vw = variable(w) for t in collection.terminals: for var in (vu, vv, vw): before = diff(t, var) # This will often get preliminary simplified to zero after = ad_algorithm(before) expected = 0*outer(t, var) #print '\n', str(expected), '\n', str(after), '\n', str(before), '\n' assert after == expected before = diff(c*t, var) # This will usually not get simplified to zero after = ad_algorithm(before) expected = 0*outer(t, var) #print '\n', str(expected), '\n', str(after), '\n', str(before), '\n' assert after == expected def test_zero_derivatives_of_noncompounds_produce_the_right_types_and_shapes(self, d_expr): d, ex = d_expr _test_zero_derivatives_of_noncompounds_produce_the_right_types_and_shapes(self, ex) def _test_zero_derivatives_of_noncompounds_produce_the_right_types_and_shapes(self, collection): debug = 0 u = Coefficient(collection.shared_objects.U) v = Coefficient(collection.shared_objects.V) w = Coefficient(collection.shared_objects.W) #for t in chain(collection.noncompounds, collection.compounds): #debug = True for t in collection.noncompounds: for var in (u, v, w): if debug: print('\n', 'shapes: ', t.ufl_shape, var.ufl_shape, '\n') if debug: print('\n', 't: ', str(t), '\n') if debug: print('\n', 't ind: ', str(t.free_indices()), '\n') if debug: print('\n', 'var: ', str(var), '\n') before = derivative(t, var) if debug: print('\n', 'before: ', str(before), '\n') after = ad_algorithm(before) if debug: print('\n', 'after: ', str(after), '\n') expected = 0*t if debug: print('\n', 'expected: ', str(expected), '\n') assert after == expected def test_zero_diffs_of_noncompounds_produce_the_right_types_and_shapes(self, d_expr): d, ex = d_expr _test_zero_diffs_of_noncompounds_produce_the_right_types_and_shapes(self, ex) def _test_zero_diffs_of_noncompounds_produce_the_right_types_and_shapes(self, collection): debug = 0 u = Coefficient(collection.shared_objects.U) v = Coefficient(collection.shared_objects.V) w = Coefficient(collection.shared_objects.W) vu = variable(u) vv = variable(v) vw = variable(w) #for t in chain(collection.noncompounds, collection.compounds): for t in collection.noncompounds: for var in (vu, vv, vw): before = diff(t, var) if debug: print('\n', 'before: ', str(before), '\n') after = ad_algorithm(before) if debug: print('\n', 'after: ', str(after), '\n') expected = 0*outer(t, var) if debug: print('\n', 'expected: ', str(expected), '\n') #print '\n', str(expected), '\n', str(after), '\n', str(before), '\n' assert after == expected def test_nonzero_derivatives_of_noncompounds_produce_the_right_types_and_shapes(self, d_expr): d, ex = d_expr _test_nonzero_derivatives_of_noncompounds_produce_the_right_types_and_shapes(self, ex) def _test_nonzero_derivatives_of_noncompounds_produce_the_right_types_and_shapes(self, collection): debug = 0 u = collection.shared_objects.u v = collection.shared_objects.v w = collection.shared_objects.w #for t in chain(collection.noncompounds, collection.compounds): for t in collection.noncompounds: for var in (u, v, w): # Include d/dx [z ? y: x] but not d/dx [x ? f: z] if isinstance(t, Conditional) and (var in unique_post_traversal(t.ufl_operands[0])): if debug: print(("Depends on %s :: %s" % (str(var), str(t)))) continue if debug: print(('\n', '...: ', t.ufl_shape, var.ufl_shape, '\n')) before = derivative(t, var) if debug: print(('\n', 'before: ', str(before), '\n')) after = ad_algorithm(before) if debug: print(('\n', 'after: ', str(after), '\n')) expected_shape = 0*t if debug: print(('\n', 'expected_shape: ', str(expected_shape), '\n')) #print '\n', str(expected_shape), '\n', str(after), '\n', str(before), '\n' if var in unique_post_traversal(t): self.assertEqualTotalShape(after, expected_shape) self.assertNotEqual(after, expected_shape) else: assert after == expected_shape def test_nonzero_diffs_of_noncompounds_produce_the_right_types_and_shapes(self, d_expr): d, ex = d_expr _test_nonzero_diffs_of_noncompounds_produce_the_right_types_and_shapes(self, ex) def _test_nonzero_diffs_of_noncompounds_produce_the_right_types_and_shapes(self, collection): debug = 0 u = collection.shared_objects.u v = collection.shared_objects.v w = collection.shared_objects.w vu = variable(u) vv = variable(v) vw = variable(w) #for t in chain(collection.noncompounds, collection.compounds): for t in collection.noncompounds: t = replace(t, {u:vu, v:vv, w:vw}) for var in (vu, vv, vw): # Include d/dx [z ? y: x] but not d/dx [x ? f: z] if isinstance(t, Conditional) and (var in unique_post_traversal(t.ufl_operands[0])): if debug: print(("Depends on %s :: %s" % (str(var), str(t)))) continue before = diff(t, var) if debug: print(('\n', 'before: ', str(before), '\n')) after = ad_algorithm(before) if debug: print(('\n', 'after: ', str(after), '\n')) expected_shape = 0*outer(t, var) # expected shape, not necessarily value if debug: print(('\n', 'expected_shape: ', str(expected_shape), '\n')) #print '\n', str(expected_shape), '\n', str(after), '\n', str(before), '\n' if var in unique_post_traversal(t): self.assertEqualTotalShape(after, expected_shape) self.assertNotEqual(after, expected_shape) else: assert after == expected_shape def test_grad_coeff(self, d_expr): d, collection = d_expr u = collection.shared_objects.u v = collection.shared_objects.v w = collection.shared_objects.w for f in (u, v, w): before = grad(f) after = ad_algorithm(before) if before.ufl_shape != after.ufl_shape: print(('\n', 'shapes:', before.ufl_shape, after.ufl_shape)) print(('\n', str(before), '\n', str(after), '\n')) self.assertEqualTotalShape(before, after) if f is u: # Differing by being wrapped in indexing types assert before == after before = grad(grad(f)) after = ad_algorithm(before) self.assertEqualTotalShape(before, after) #assert before == after # Differing by being wrapped in indexing types before = grad(grad(grad(f))) after = ad_algorithm(before) self.assertEqualTotalShape(before, after) #assert before == after # Differing by being wrapped in indexing types def test_derivative_grad_coeff(self, d_expr): d, collection = d_expr u = collection.shared_objects.u v = collection.shared_objects.v w = collection.shared_objects.w for f in (u, v, w): before = derivative(grad(f), f) after = ad_algorithm(before) self.assertEqualTotalShape(before, after) #assert after == expected before = derivative(grad(grad(f)), f) after = ad_algorithm(before) self.assertEqualTotalShape(before, after) #assert after == expected before = derivative(grad(grad(grad(f))), f) after = ad_algorithm(before) self.assertEqualTotalShape(before, after) #assert after == expected if 0: print() print(('B', f, "::", before)) print(('A', f, "::", after)) def xtest_derivative_grad_coeff_with_variation_components(self, d_expr): d, collection = d_expr v = collection.shared_objects.v w = collection.shared_objects.w dv = collection.shared_objects.dv dw = collection.shared_objects.dw for g, dg in ((v, dv), (w, dw)): # Pick a single component ii = (0,)*(g.rank()) f = g[ii] df = dg[ii] before = derivative(grad(g), f, df) after = ad_algorithm(before) self.assertEqualTotalShape(before, after) #assert after == expected before = derivative(grad(grad(g)), f, df) after = ad_algorithm(before) self.assertEqualTotalShape(before, after) #assert after == expected before = derivative(grad(grad(grad(g))), f, df) after = ad_algorithm(before) self.assertEqualTotalShape(before, after) #assert after == expected if 0: print() print(('B', f, "::", before)) print(('A', f, "::", after)) ufl-1.6.0/test/test_book_snippets.py000077500000000000000000000317521255567402100175750ustar00rootroot00000000000000#!/usr/bin/env py.test """ This file contains snippets from the FEniCS book, and allows us to test that these can still run with future versions of UFL. Please don't change these and please do keep UFL compatible with these snippets as long as possible. """ import pytest from ufl import * from ufl.algorithms import * def test_uflcode_269(self): # Finite element spaces cell = tetrahedron element = VectorElement("Lagrange", cell, 1) # Form arguments phi0 = TestFunction(element) phi1 = TrialFunction(element) u = Coefficient(element) c1 = Constant(cell) c2 = Constant(cell) # Deformation gradient Fij = dXi/dxj I = Identity(cell.geometric_dimension()) F = I + grad(u) # Right Cauchy-Green strain tensor C with invariants C = variable(F.T*F) I_C = tr(C) II_C = (I_C**2 - tr(C*C))/2 # Mooney-Rivlin constitutive law W = c1*(I_C-3) + c2*(II_C-3) # Second Piola-Kirchoff stress tensor S = 2*diff(W, C) # Weak forms L = inner(F*S, grad(phi0))*dx a = derivative(L, u, phi1) def test_uflcode_316(self): shapestring = 'triangle' cell = Cell(shapestring) def test_uflcode_323(self): cell = tetrahedron def test_uflcode_356(self): cell = tetrahedron P = FiniteElement("Lagrange", cell, 1) V = VectorElement("Lagrange", cell, 2) T = TensorElement("DG", cell, 0, symmetry=True) TH = V*P ME = MixedElement(T, V, P) def test_uflcode_400(self): V = FiniteElement("CG", triangle, 1) f = Coefficient(V) g = Coefficient(V) h = Coefficient(V) w = Coefficient(V) v = TestFunction(V) u = TrialFunction(V) # ... a = w*dot(grad(u), grad(v))*dx L = f*v*dx + g**2*v*ds(0) + h*v*ds(1) def test_uflcode_469(self): V = FiniteElement("CG", triangle, 1) f = Coefficient(V) g = Coefficient(V) h = Coefficient(V) v = TestFunction(V) # ... dx02 = dx(0, { "integration_order": 2 }) dx14 = dx(1, { "integration_order": 4 }) dx12 = dx(1, { "integration_order": 2 }) L = f*v*dx02 + g*v*dx14 + h*v*dx12 def test_uflcode_552(self): element = FiniteElement("CG", triangle, 1) # ... phi = Argument(element, 2) v = TestFunction(element) u = TrialFunction(element) def test_uflcode_563(self): cell = triangle element = FiniteElement("CG", cell, 1) # ... w = Coefficient(element) c = Constant(cell) v = VectorConstant(cell) M = TensorConstant(cell) def test_uflcode_574(self): V0 = FiniteElement("CG", triangle, 1) V1 = V0 # ... V = V0*V1 u = Coefficient(V) u0, u1 = split(u) def test_uflcode_582(self): V0 = FiniteElement("CG", triangle, 1) V1 = V0 # ... V = V0*V1 u = Coefficient(V) u0, u1 = split(u) # ... v0, v1 = TestFunctions(V) u0, u1 = TrialFunctions(V) f0, f1 = Coefficients(V) def test_uflcode_644(self): V = VectorElement("CG", triangle, 1) u = Coefficient(V) v = Coefficient(V) # ... A = outer(u, v) Aij = A[i, j] def test_uflcode_651(self): V = VectorElement("CG", triangle, 1) u = Coefficient(V) v = Coefficient(V) # ... Aij = v[j]*u[i] A = as_tensor(Aij, (i, j)) def test_uflcode_671(self): i = Index() j, k, l = indices(3) def test_uflcode_684(self): V = VectorElement("CG", triangle, 1) v = Coefficient(V) # ... th = pi/2 A = as_matrix([[ cos(th), -sin(th)], [ sin(th), cos(th)]]) u = A*v def test_uflcode_824(self): V = VectorElement("CG", triangle, 1) f = Coefficient(V) # ... df = Dx(f, i) df = f.dx(i) def test_uflcode_886(self): cell = triangle # ... g = sin(cell.x[0]) v = variable(g) f = exp(v**2) h = diff(f, v) # ... #print v #print h def test_python_894(self): # We don't have to keep the string output compatible, so no test here. pass #>>> print v #var0(sin((x)[0])) #>>> print h #d/d[var0(sin((x)[0]))] (exp((var0(sin((x)[0]))) ** 2)) def test_uflcode_930(self): condition = lt(1, 0) true_value = 1 false_value = 0 # ... f = conditional(condition, true_value, false_value) def test_uflcode_1003(self): # Not testable, but this is tested below anyway "a = derivative(L, w, u)" pass def test_uflcode_1026(self): element = FiniteElement("CG", triangle, 1) # ... v = TestFunction(element) u = TrialFunction(element) w = Coefficient(element) f = 0.5*w**2*dx F = derivative(f, w, v) J = derivative(F, w, u) def test_uflcode_1050(self): Vx = VectorElement("Lagrange", triangle, 1) Vy = FiniteElement("Lagrange", triangle, 1) u = Coefficient(Vx*Vy) x, y = split(u) f = inner(grad(x), grad(x))*dx + y*dot(x, x)*dx F = derivative(f, u) J = derivative(F, u) def test_uflcode_1085(self): cell = triangle # ... V = VectorElement("Lagrange", cell, 1) T = TensorElement("Lagrange", cell, 1) u = TrialFunction(V) v = TestFunction(V) M = Coefficient(T) a = M[i, j]*u[k].dx(j)*v[k].dx(i)*dx astar = adjoint(a) def test_uflcode_1120(self): cell = triangle # ... V = FiniteElement("Lagrange", cell, 1) v = TestFunction(V) f = Coefficient(V) g = Coefficient(V) L = f**2 / (2*g)*v*dx L2 = replace(L, { f: g, g: 3}) L3 = g**2 / 6*v*dx def test_uflcode_1157(self): cell = triangle # ... V = FiniteElement("Lagrange", cell, 1) u = TrialFunction(V) v = TestFunction(V) f = Coefficient(V) pde = u*v*dx - f*v*dx a, L = system(pde) def test_uflcode_1190(self): cell = triangle element = FiniteElement("Lagrange", cell, 1) V = element u = TrialFunction(V) v = TestFunction(V) f = Coefficient(V) c = variable(Coefficient(V)) pde = c*u*v*dx - c*f*v*dx a, L = system(pde) # ... u = Coefficient(element) sL = diff(L, c) - action(diff(a, c), u) def test_uflcode_1195(self): cell = triangle element = FiniteElement("Lagrange", cell, 1) V = element u = TrialFunction(V) v = TestFunction(V) f = Coefficient(V) c = variable(Coefficient(V)) pde = c*u*v*dx - c*f*v*dx a, L = system(pde) u = Coefficient(element) # ... sL = sensitivity_rhs(a, u, L, c) def test_uflcode_1365(self): e = 0 v = variable(e) f = sin(v) g = diff(f, v) def test_python_1426(self): # Covered by the below test pass #from ufl.algorithms import Graph #G = Graph(expression) #V, E = G def test_python_1446(self): cell = triangle V = FiniteElement("Lagrange", cell, 1) u = TrialFunction(V) v = TestFunction(V) c = Constant(cell) f = Coefficient(V) e = c*f**2*u*v # The linearized Graph functionality has been removed from UFL: #from ufl.algorithms import Graph, partition #G = Graph(e) #V, E, = G #print(("str(e) = %s\n" % str(e))) #print(("\n".join("V[%d] = %s" % (i, v) for (i, v) in enumerate(V)), "\n")) #print(("\n".join("E[%d] = %s" % (i, e) for (i, e) in enumerate(E)), "\n")) def test_python_1512(self): cell = triangle V = FiniteElement("Lagrange", cell, 1) u = TrialFunction(V) v = TestFunction(V) c = Constant(cell) f = Coefficient(V) e = c*f**2*u*v # The linearized Graph functionality has been removed from UFL: #from ufl.algorithms import Graph, partition #G = Graph(e) #V, E, = G # ... #Vin = G.Vin() #Vout = G.Vout() def test_python_1557(self): cell = triangle V = FiniteElement("Lagrange", cell, 1) u = TrialFunction(V) v = TestFunction(V) c = Constant(cell) f = Coefficient(V) e = c*f**2*u*v # The linearized Graph functionality has been removed from UFL: #from ufl.algorithms import Graph, partition #G = Graph(e) #V, E, = G # ... #partitions, keys = partition(G) #for deps in sorted(partitions.keys()): # P = partitions[deps] # #print "The following depends on", tuple(deps) # for i in sorted(P): # #print "V[%d] = %s" % (i, V[i]) # # ... # v = V[i] def test_python_1843(self): def apply_ad(e, ad_routine): if e._ufl_is_terminal_: return e ops = [apply_ad(o, ad_routine) for o in e.ufl_operands] e = e.reconstruct(*ops) if isinstance(e, Derivative): e = ad_routine(e) return e def test_uflcode_1901(self): cell = triangle element = FiniteElement("Lagrange", cell, 1) # ... v = Argument(element, 2) w = Coefficient(element) def test_python_1942(self): def walk(expression, pre_action, post_action): pre_action(expression) for o in expression.ufl_operands: walk(o) post_action(expression) def test_python_1955(self): def post_traversal(root): for o in root.ufl_operands: yield post_traversal(o) yield root def test_python_1963(self): def post_action(e): #print str(e) pass cell = triangle V = FiniteElement("Lagrange", cell, 1) u = TrialFunction(V) v = TestFunction(V) c = Constant(cell) f = Coefficient(V) expression = c*f**2*u*v # ... for e in post_traversal(expression): post_action(e) def test_python_1990(self): from ufl.classes import IntValue, Sum expression = as_ufl(3) def int_operation(x): return 7 # ... if isinstance(expression, IntValue): result = int_operation(expression) elif isinstance(expression, Sum): result = sum_operation(expression) # etc. # ... self.assertTrue(result == 7) def test_python_2024(self): class ExampleFunction(MultiFunction): def __init__(self): MultiFunction.__init__(self) def terminal(self, expression): return "Got a Terminal subtype %s." % type(expression) def operator(self, expression): return "Got an Operator subtype %s." % type(expression) def argument(self, expression): return "Got an Argument." def sum(self, expression): return "Got a Sum." m = ExampleFunction() cell = triangle element = FiniteElement("Lagrange", cell, 1) x = cell.x if 0: print((m(Argument(element, 2)))) print((m(x))) print((m(x[0] + x[1]))) print((m(x[0] * x[1]))) def test_python_2066(self): def apply(e, multifunction): ops = [apply(o, multifunction) for o in e.ufl_operands] return multifunction(e, *ops) def test_python_2087(self): class Replacer(Transformer): def __init__(self, mapping): Transformer.__init__(self) self.mapping = mapping def operator(self, e, *ops): return e.reconstruct(*ops) def terminal(self, e): return self.mapping.get(e, e) f = Constant(triangle) r = Replacer({f: f**2}) g = r.visit(2*f) def test_python_2189(self): V = FiniteElement("Lagrange", triangle, 1) u = TestFunction(V) v = TrialFunction(V) f = Coefficient(V) # Note no *dx! This is an expression, not a form. a = dot(grad(f*u), grad(v)) ac = expand_compounds(a) ad = expand_derivatives(ac) ai = expand_indices(ad) af = tree_format(a) acf = tree_format(ac) adf = "\n", tree_format(ad) aif = tree_format(ai) if 0: print(("\na: ", str(a), "\n", tree_format(a))) print(("\nac:", str(ac), "\n", tree_format(ac))) print(("\nad:", str(ad), "\n", tree_format(ad))) print(("\nai:", str(ai), "\n", tree_format(ai))) def test_python_2328(self): cell = triangle x = cell.x e = x[0] + x[1] #print e((0.5, 0.7)) # prints 1.2 # ... self.assertEqual( e((0.5, 0.7)), 1.2 ) def test_python_2338(self): cell = triangle x = cell.x # ... c = Constant(cell) e = c*(x[0] + x[1]) #print e((0.5, 0.7), { c: 10 }) # prints 12.0 # ... self.assertEqual( e((0.5, 0.7), { c: 10 }), 12.0 ) def test_python_2349(self): element = VectorElement("Lagrange", triangle, 1) c = Constant(triangle) f = Coefficient(element) e = c*(f[0] + f[1]) def fh(x): return (x[0], x[1]) #print e((0.5, 0.7), { c: 10, f: fh }) # prints 12.0 # ... self.assertEqual( e((0.5, 0.7), { c: 10, f: fh }), 12.0 ) def test_python_2364(self): element = FiniteElement("Lagrange", triangle, 1) g = Coefficient(element) e = g**2 + g.dx(0)**2 + g.dx(1)**2 def gh(x, der=()): if der == (): return x[0]*x[1] if der == (0,): return x[1] if der == (1,): return x[0] #print e((2, 3), { g: gh }) # prints 49 # ... self.assertEqual( e((2, 3), { g: gh }), 49 ) def test_python_2462(self): cell = triangle element = FiniteElement("Lagrange", cell, 1) V = element u = TrialFunction(V) v = TestFunction(V) f = Coefficient(V) c = variable(Coefficient(V)) pde = c*u*v*dx - c*f*v*dx a, L = system(pde) u = Coefficient(element) myform = a # ... #print repr(preprocess(myform).preprocessed_form) # ... r = repr(compute_form_data(myform).preprocessed_form) ufl-1.6.0/test/test_change_to_local.py000077500000000000000000000045431255567402100200150ustar00rootroot00000000000000#!/usr/bin/env py.test """ Tests of the change to local representaiton algorithms. """ import pytest from ufl import * from ufl.classes import ReferenceGrad, JacobianInverse from ufl.algorithms import tree_format, change_to_reference_grad, renumber_indices def test_change_to_reference_grad(): domain = Domain(triangle) U = FiniteElement("CG", domain, 1) V = VectorElement("CG", domain, 1) u = Coefficient(U) v = Coefficient(V) Jinv = JacobianInverse(domain) i, j, k = indices(3) q, r, s = indices(3) t, = indices(1) # Single grad change on a scalar function expr = grad(u) actual = change_to_reference_grad(expr) expected = as_tensor(Jinv[k, i] * ReferenceGrad(u)[k], (i,)) assert renumber_indices(actual) == renumber_indices(expected) # Single grad change on a vector valued function expr = grad(v) actual = change_to_reference_grad(expr) expected = as_tensor(Jinv[k, j] * ReferenceGrad(v)[i, k], (i, j)) assert renumber_indices(actual) == renumber_indices(expected) # Multiple grads should work fine for affine domains: expr = grad(grad(u)) actual = change_to_reference_grad(expr) expected = as_tensor( Jinv[s, j] * (Jinv[r, i] * ReferenceGrad(ReferenceGrad(u))[r, s]), (i, j)) assert renumber_indices(actual) == renumber_indices(expected) expr = grad(grad(grad(u))) actual = change_to_reference_grad(expr) expected = as_tensor( Jinv[s, k] * (Jinv[r, j] * (Jinv[q, i] * ReferenceGrad(ReferenceGrad(ReferenceGrad(u)))[q, r, s])), (i, j, k)) assert renumber_indices(actual) == renumber_indices(expected) # Multiple grads on a vector valued function expr = grad(grad(v)) actual = change_to_reference_grad(expr) expected = as_tensor( Jinv[s, j] * (Jinv[r, i] * ReferenceGrad(ReferenceGrad(v))[t, r, s]), (t, i, j)) assert renumber_indices(actual) == renumber_indices(expected) expr = grad(grad(grad(v))) actual = change_to_reference_grad(expr) expected = as_tensor( Jinv[s, k] * (Jinv[r, j] * (Jinv[q, i] * ReferenceGrad(ReferenceGrad(ReferenceGrad(v)))[t, q, r, s])), (t, i, j, k)) assert renumber_indices(actual) == renumber_indices(expected) # print tree_format(expected) # print tree_format(actual) # print tree_format(renumber_indices(actual)) # print tree_format(renumber_indices(expected)) ufl-1.6.0/test/test_check_arities.py000066400000000000000000000011171255567402100175000ustar00rootroot00000000000000 import pytest from ufl import * from ufl.algorithms.compute_form_data import compute_form_data def test_check_arities(): # Code from bitbucket issue #49 D = Domain(tetrahedron) V = VectorElement("P", D, 2) dv = TestFunction(V) du = TrialFunction(V) X = SpatialCoordinate(D) N = FacetNormal(D) u = Coefficient(V) x = X + u F = grad(x) n = cofac(F) * N M = inner(x, n) * ds L = derivative(M, u, dv) a = derivative(L, u, du) fd = compute_form_data(M) fd = compute_form_data(L) fd = compute_form_data(a) assert True ufl-1.6.0/test/test_classcoverage.py000077500000000000000000000362321255567402100175350ustar00rootroot00000000000000#!/usr/bin/env py.test from __future__ import print_function __authors__ = "Martin Sandve Alnes" __date__ = "2008-09-06 -- 2009-02-10" import pytest import ufl from ufl import * from ufl.constantvalue import as_ufl from ufl.classes import * from ufl.algorithms import * has_repr = set() has_dict = set() def _test_object(a, shape, free_indices): # Check if instances of this type has certain memory consuming members if hasattr(a, '_repr'): has_repr.add(a.__class__.__name__) if hasattr(a, '__dict__'): has_dict.add(a.__class__.__name__) # Test reproduction via repr string r = repr(a) e = eval(r, globals()) assert hash(a) == hash(e) # Can't really test str more than that it exists s = str(a) # Check that some properties are at least available fi = a.free_indices() sh = a.ufl_shape ce = a.cell() # Compare with provided properties if free_indices is not None: if len(set(fi) ^ set(free_indices)) != 0: print(type(a)) print(a) print(fi) print(free_indices) assert len(set(fi) ^ set(free_indices)) == 0 if shape is not None: if sh != shape: print(("sh:", sh)) print(("shape:", shape)) assert sh == shape def _test_object2(a): # Check if instances of this type has certain memory consuming members if hasattr(a, '_repr'): has_repr.add(a.__class__.__name__) if hasattr(a, '__dict__'): has_dict.add(a.__class__.__name__) # Test reproduction via repr string r = repr(a) e = eval(r, globals()) assert hash(a) == hash(e) # Can't really test str more than that it exists s = str(a) # Check that some properties are at least available ce = a.cell() def _test_form(a): # Test reproduction via repr string r = repr(a) e = eval(r, globals()) assert hash(a) == hash(e) # Can't really test str more than that it exists s = str(a) def testExports(self): "Verify that ufl.classes exports all Expr subclasses." all_expr_classes = [] for m in list(vars(ufl).values()): if isinstance(m, type(ufl)): for c in list(vars(m).values()): if isinstance(c, type) and issubclass(c, Expr): all_expr_classes.append(c) missing_classes = set(c.__name__ for c in all_expr_classes)\ - set(c.__name__ for c in all_ufl_classes) if missing_classes: print("The following subclasses of Expr were not exported from ufl.classes:") print(("\n".join(sorted(missing_classes)))) assert missing_classes == set() def testAll(self): Expr.ufl_enable_profiling() # --- Elements: cell = triangle dim = cell.geometric_dimension() e0 = FiniteElement("CG", cell, 1) e1 = VectorElement("CG", cell, 1) e2 = TensorElement("CG", cell, 1) e3 = MixedElement(e0, e1, e2) e13D = VectorElement("CG", tetrahedron, 1) # --- Terminals: v13D = Argument(e13D, 3) f13D = Coefficient(e13D) v0 = Argument(e0, 4) v1 = Argument(e1, 5) v2 = Argument(e2, 6) v3 = Argument(e3, 7) _test_object(v0, (), ()) _test_object(v1, (dim,), ()) _test_object(v2, (dim, dim), ()) _test_object(v3, (dim*dim+dim+1,), ()) f0 = Coefficient(e0) f1 = Coefficient(e1) f2 = Coefficient(e2) f3 = Coefficient(e3) _test_object(f0, (), ()) _test_object(f1, (dim,), ()) _test_object(f2, (dim, dim), ()) _test_object(f3, (dim*dim+dim+1,), ()) c = Constant(cell) _test_object(c, (), ()) c = VectorConstant(cell) _test_object(c, (dim,), ()) c = TensorConstant(cell) _test_object(c, (dim, dim), ()) a = FloatValue(1.23) _test_object(a, (), ()) a = IntValue(123) _test_object(a, (), ()) I = Identity(1) _test_object(I, (1, 1), ()) I = Identity(2) _test_object(I, (2, 2), ()) I = Identity(3) _test_object(I, (3, 3), ()) e = PermutationSymbol(2) _test_object(e, (2, 2), ()) e = PermutationSymbol(3) _test_object(e, (3, 3, 3), ()) x = SpatialCoordinate(cell) _test_object(x, (dim,), ()) xi = CellCoordinate(cell) _test_object(xi, (dim,), ()) #g = CellBarycenter(cell) #_test_object(g, (dim,), ()) #g = FacetBarycenter(cell) #_test_object(g, (dim,), ()) g = Jacobian(cell) _test_object(g, (dim, dim), ()) g = JacobianDeterminant(cell) _test_object(g, (), ()) g = JacobianInverse(cell) _test_object(g, (dim, dim), ()) g = FacetJacobian(cell) _test_object(g, (dim, dim-1), ()) g = FacetJacobianDeterminant(cell) _test_object(g, (), ()) g = FacetJacobianInverse(cell) _test_object(g, (dim-1, dim), ()) g = FacetNormal(cell) _test_object(g, (dim,), ()) #g = CellNormal(cell) #_test_object(g, (dim,), ()) g = CellVolume(cell) _test_object(g, (), ()) g = Circumradius(cell) _test_object(g, (), ()) #g = CellSurfaceArea(cell) #_test_object(g, (), ()) g = FacetArea(cell) _test_object(g, (), ()) g = MinFacetEdgeLength(cell) _test_object(g, (), ()) g = MaxFacetEdgeLength(cell) _test_object(g, (), ()) #g = FacetDiameter(cell) #_test_object(g, (), ()) a = variable(v0) _test_object(a, (), ()) a = variable(v1) _test_object(a, (dim,), ()) a = variable(v2) _test_object(a, (dim, dim), ()) a = variable(v3) _test_object(a, (dim*dim+dim+1,), ()) a = variable(f0) _test_object(a, (), ()) a = variable(f1) _test_object(a, (dim,), ()) a = variable(f2) _test_object(a, (dim, dim), ()) a = variable(f3) _test_object(a, (dim*dim+dim+1,), ()) #a = MultiIndex() # --- Non-terminals: #a = Indexed() a = v2[i, j] _test_object(a, (), (i, j)) a = v2[0, k] _test_object(a, (), (k,)) a = v2[l, 1] _test_object(a, (), (l,)) a = f2[i, j] _test_object(a, (), (i, j)) a = f2[0, k] _test_object(a, (), (k,)) a = f2[l, 1] _test_object(a, (), (l,)) I = Identity(dim) a = inv(I) _test_object(a, (dim, dim), ()) a = inv(v2) _test_object(a, (dim, dim), ()) a = inv(f2) _test_object(a, (dim, dim), ()) for v in (v0, v1, v2, v3): for f in (f0, f1, f2, f3): a = outer(v, f) _test_object(a, None, None) for v, f in zip((v0, v1, v2, v3), (f0, f1, f2, f3)): a = inner(v, f) _test_object(a, None, None) for v, f in zip((v1, v2, v3), (f1, f2, f3)): a = dot(v, f) sh = v.ufl_shape[:-1] + f.ufl_shape[1:] _test_object(a, sh, None) a = cross(v13D, f13D) _test_object(a, (3,), ()) #a = Sum() a = v0 + f0 + v0 _test_object(a, (), ()) a = v1 + f1 + v1 _test_object(a, (dim,), ()) a = v2 + f2 + v2 _test_object(a, (dim, dim), ()) #a = Product() a = 3*v0*(2.0*v0)*f0*(v0*3.0) _test_object(a, (), ()) #a = Division() a = v0 / 2.0 _test_object(a, (), ()) a = v0 / f0 _test_object(a, (), ()) a = v0 / (f0 + 7) _test_object(a, (), ()) #a = Power() a = f0**3 _test_object(a, (), ()) a = (f0*2)**1.23 _test_object(a, (), ()) #a = ListTensor() a = as_vector([1.0, 2.0*f0, f0**2]) _test_object(a, (3,), ()) a = as_matrix([[1.0, 2.0*f0, f0**2], [1.0, 2.0*f0, f0**2]]) _test_object(a, (2, 3), ()) a = as_tensor([ [[0.00, 0.01, 0.02], [0.10, 0.11, 0.12] ], [ [1.00, 1.01, 1.02], [1.10, 1.11, 1.12]] ]) _test_object(a, (2, 2, 3), ()) #a = ComponentTensor() a = as_vector(v1[i]*f1[j], i) _test_object(a, (dim,), (j,)) a = as_matrix(v1[i]*f1[j], (j, i)) _test_object(a, (dim, dim), ()) a = as_tensor(v1[i]*f1[j], (i, j)) _test_object(a, (dim, dim), ()) a = as_tensor(v2[i, j]*f2[j, k], (i, k)) _test_object(a, (dim, dim), ()) a = dev(v2) _test_object(a, (dim, dim), ()) a = dev(f2) _test_object(a, (dim, dim), ()) a = dev(f2*f0+v2*3) _test_object(a, (dim, dim), ()) a = sym(v2) _test_object(a, (dim, dim), ()) a = sym(f2) _test_object(a, (dim, dim), ()) a = sym(f2*f0+v2*3) _test_object(a, (dim, dim), ()) a = skew(v2) _test_object(a, (dim, dim), ()) a = skew(f2) _test_object(a, (dim, dim), ()) a = skew(f2*f0+v2*3) _test_object(a, (dim, dim), ()) a = v2.T _test_object(a, (dim, dim), ()) a = f2.T _test_object(a, (dim, dim), ()) a = transpose(f2*f0+v2*3) _test_object(a, (dim, dim), ()) a = det(v2) _test_object(a, (), ()) a = det(f2) _test_object(a, (), ()) a = det(f2*f0+v2*3) _test_object(a, (), ()) a = tr(v2) _test_object(a, (), ()) a = tr(f2) _test_object(a, (), ()) a = tr(f2*f0+v2*3) _test_object(a, (), ()) a = cofac(v2) _test_object(a, (dim, dim), ()) a = cofac(f2) _test_object(a, (dim, dim), ()) a = cofac(f2*f0+v2*3) _test_object(a, (dim, dim), ()) cond1 = le(f0, 1.0) cond2 = eq(3.0, f0) cond3 = ne(sin(f0), cos(f0)) cond4 = lt(sin(f0), cos(f0)) cond5 = ge(sin(f0), cos(f0)) cond6 = gt(sin(f0), cos(f0)) cond7 = And(cond1, cond2) cond8 = Or(cond1, cond2) cond9 = Not(cond8) a = conditional(cond1, 1, 2) b = conditional(cond2, f0**3, ln(f0)) _test_object2(cond1) _test_object2(cond2) _test_object2(cond3) _test_object2(cond4) _test_object2(cond5) _test_object2(cond6) _test_object2(cond7) _test_object2(cond8) _test_object2(cond9) _test_object(a, (), ()) _test_object(b, (), ()) a = abs(f0) _test_object(a, (), ()) a = sqrt(f0) _test_object(a, (), ()) a = cos(f0) _test_object(a, (), ()) a = sin(f0) _test_object(a, (), ()) a = tan(f0) _test_object(a, (), ()) a = cosh(f0) _test_object(a, (), ()) a = sinh(f0) _test_object(a, (), ()) a = tanh(f0) _test_object(a, (), ()) a = exp(f0) _test_object(a, (), ()) a = ln(f0) _test_object(a, (), ()) a = asin(f0) _test_object(a, (), ()) a = acos(f0) _test_object(a, (), ()) a = atan(f0) _test_object(a, (), ()) one = as_ufl(1) a = abs(one) _test_object(a, (), ()) a = Sqrt(one) _test_object(a, (), ()) a = Cos(one) _test_object(a, (), ()) a = Sin(one) _test_object(a, (), ()) a = Tan(one) _test_object(a, (), ()) a = Cosh(one) _test_object(a, (), ()) a = Sinh(one) _test_object(a, (), ()) a = Tanh(one) _test_object(a, (), ()) a = Acos(one) _test_object(a, (), ()) a = Asin(one) _test_object(a, (), ()) a = Atan(one) _test_object(a, (), ()) a = Exp(one) _test_object(a, (), ()) a = Ln(one) _test_object(a, (), ()) # TODO: #a = SpatialDerivative() a = f0.dx(0) _test_object(a, (), ()) a = f0.dx(i) _test_object(a, (), (i,)) a = f0.dx(i, j, 1) _test_object(a, (), (i, j)) s0 = variable(f0) s1 = variable(f1) s2 = variable(f2) f = dot(s0*s1, s2) _test_object(s0, (), ()) _test_object(s1, (dim,), ()) _test_object(s2, (dim, dim), ()) _test_object(f, (dim,), ()) a = diff(f, s0) _test_object(a, (dim,), ()) a = diff(f, s1) _test_object(a, (dim, dim,), ()) a = diff(f, s2) _test_object(a, (dim, dim, dim), ()) a = div(v1) _test_object(a, (), ()) a = div(f1) _test_object(a, (), ()) a = div(v2) _test_object(a, (dim,), ()) a = div(f2) _test_object(a, (dim,), ()) a = div(Outer(f1, f1)) _test_object(a, (dim,), ()) a = grad(v0) _test_object(a, (dim,), ()) a = grad(f0) _test_object(a, (dim,), ()) a = grad(v1) _test_object(a, (dim, dim), ()) a = grad(f1) _test_object(a, (dim, dim), ()) a = grad(f0*v0) _test_object(a, (dim,), ()) a = grad(f0*v1) _test_object(a, (dim, dim), ()) a = nabla_div(v1) _test_object(a, (), ()) a = nabla_div(f1) _test_object(a, (), ()) a = nabla_div(v2) _test_object(a, (dim,), ()) a = nabla_div(f2) _test_object(a, (dim,), ()) a = nabla_div(Outer(f1, f1)) _test_object(a, (dim,), ()) a = nabla_grad(v0) _test_object(a, (dim,), ()) a = nabla_grad(f0) _test_object(a, (dim,), ()) a = nabla_grad(v1) _test_object(a, (dim, dim), ()) a = nabla_grad(f1) _test_object(a, (dim, dim), ()) a = nabla_grad(f0*v0) _test_object(a, (dim,), ()) a = nabla_grad(f0*v1) _test_object(a, (dim, dim), ()) a = curl(v13D) _test_object(a, (3,), ()) a = curl(f13D) _test_object(a, (3,), ()) a = rot(v1) _test_object(a, (), ()) a = rot(f1) _test_object(a, (), ()) #a = PositiveRestricted(v0) #_test_object(a, (), ()) a = v0('+') _test_object(a, (), ()) a = v0('+')*f0 _test_object(a, (), ()) #a = NegativeRestricted(v0) #_test_object(a, (), ()) a = v0('-') _test_object(a, (), ()) a = v0('-') + f0 _test_object(a, (), ()) a = cell_avg(v0) _test_object(a, (), ()) a = facet_avg(v0) _test_object(a, (), ()) a = cell_avg(v1) _test_object(a, (dim,), ()) a = facet_avg(v1) _test_object(a, (dim,), ()) a = cell_avg(v1)[i] _test_object(a, (), (i,)) a = facet_avg(v1)[i] _test_object(a, (), (i,)) # --- Integrals: a = v0*dx _test_form(a) a = v0*dx(0) _test_form(a) a = v0*dx(1) _test_form(a) a = v0*ds _test_form(a) a = v0*ds(0) _test_form(a) a = v0*ds(1) _test_form(a) a = v0*dS _test_form(a) a = v0*dS(0) _test_form(a) a = v0*dS(1) _test_form(a) a = v0*dot(v1, f1)*dx _test_form(a) a = v0*dot(v1, f1)*dx(0) _test_form(a) a = v0*dot(v1, f1)*dx(1) _test_form(a) a = v0*dot(v1, f1)*ds _test_form(a) a = v0*dot(v1, f1)*ds(0) _test_form(a) a = v0*dot(v1, f1)*ds(1) _test_form(a) a = v0*dot(v1, f1)*dS _test_form(a) a = v0*dot(v1, f1)*dS(0) _test_form(a) a = v0*dot(v1, f1)*dS(1) _test_form(a) # --- Form transformations: a = f0*v0*dx + f0*v0*dot(f1, v1)*dx #b = lhs(a) # TODO #c = rhs(a) # TODO d = derivative(a, f1, v1) f = action(d) #e = action(b) # --- Check which classes have been created ic, dc = Expr.ufl_disable_profiling() constructed = set() unused = set(Expr._ufl_all_classes_) for cls in Expr._ufl_all_classes_: tc = cls._ufl_typecode_ if ic[tc]: constructed.add(cls) if cls._ufl_is_abstract_: unused.remove(cls) if unused: print() print("The following classes were never instantiated in class coverage test:") print(("\n".join(sorted(map(str, unused))))) print() # --- Check which classes had certain member variables if has_repr: print() print("The following classes contain a _repr member:") print(("\n".join(sorted(map(str, has_repr))))) print() if has_dict: print() print("The following classes contain a __dict__ member:") print(("\n".join(sorted(map(str, has_dict))))) print() # TODO: Add tests for bessel functions: # BesselI # BesselJ # BesselK # BesselY # Erf # TODO: Add tests for: # Label ufl-1.6.0/test/test_conditionals.py000077500000000000000000000107151255567402100174000ustar00rootroot00000000000000#!/usr/bin/env py.test __authors__ = "Martin Sandve Alnes" __date__ = "2008-08-20 -- 2012-11-30" import pytest from ufl import * # from ufl.algorithms import * from ufl.classes import * @pytest.fixture def f(): element = FiniteElement("Lagrange", triangle, 1) return Coefficient(element) @pytest.fixture def g(): element = FiniteElement("Lagrange", triangle, 1) return Coefficient(element) def test_conditional_does_not_allow_bool_condition(f, g): # The reason for this test is that it protects from the case # conditional(a == b, t, f) in which a == b means comparing representations with pytest.raises(UFLException): conditional(True, 1, 0) def test_eq_produces_ufl_expr(f, g): expr1 = eq(f, f) expr2 = eq(f, g) expr3 = eq(f, g) assert isinstance(expr1, EQ) assert isinstance(expr2, EQ) assert not bool(expr1 == expr2) assert bool(expr1 != expr2) assert bool(expr2 == expr3) def test_eq_oper_produces_bool(f, g): expr1 = f == f expr2 = f == g assert isinstance(expr1, bool) assert isinstance(expr2, bool) assert expr1 assert not expr2 def xtest_eq_produces_ufl_expr(f, g): expr1 = f == g expr2 = eq(f, g) assert isinstance(expr1, EQ) assert isinstance(expr2, EQ) assert bool(expr1 == expr2) assert not bool(expr1 != expr2) def test_eq_produces_ufl_expr(f, g): expr1 = eq(f, g) expr2 = eq(f, f) expr3 = f == g expr4 = f == f # Correct types: assert isinstance(expr1, EQ) assert isinstance(expr2, EQ) assert isinstance(expr3, bool) assert isinstance(expr4, bool) # Comparing representations correctly: assert bool(expr1 == eq(f, g)) assert bool(expr1 != eq(g, g)) assert bool(expr2 == eq(f, f)) assert bool(expr2 != eq(g, f)) # Bool evaluation yields actual bools: assert isinstance(bool(expr1), bool) assert isinstance(bool(expr2), bool) assert not expr3 assert expr4 # Allow use in boolean python expression context: # NB! This means comparing representations! Required by dict and set. assert not bool(expr1) assert bool(expr2) assert not bool(expr3) assert bool(expr4) def test_ne_produces_ufl_expr(f, g): expr1 = ne(f, g) expr2 = ne(f, f) expr3 = f != g expr4 = f != f # Correct types: assert isinstance(expr1, NE) assert isinstance(expr2, NE) assert isinstance(expr3, bool) assert isinstance(expr4, bool) # Comparing representations correctly: assert bool(expr1 == ne(f, g)) assert bool(expr1 != ne(g, g)) assert bool(expr2 == ne(f, f)) assert bool(expr2 != ne(g, f)) assert not bool(expr2 == expr3) # Bool evaluation yields actual bools: assert isinstance(bool(expr1), bool) assert isinstance(bool(expr2), bool) # Allow use in boolean python expression context: # NB! This means the opposite of ==, i.e. comparing representations! assert bool(expr1) assert not bool(expr2) assert bool(expr1) assert not bool(expr2) def test_lt_produces_ufl_expr(f, g): expr1 = lt(f, g) expr2 = f < g # Correct types (no bools here!): assert isinstance(expr1, LT) assert isinstance(expr2, LT) # Representations are the same: assert bool(expr1 == expr2) # Protection from misuse in boolean python expression context: with pytest.raises(UFLException): bool(expr1) def test_gt_produces_ufl_expr(f, g): expr1 = gt(f, g) expr2 = f > g # Correct types (no bools here!): assert isinstance(expr1, GT) assert isinstance(expr2, GT) # Representations are the same: assert bool(expr1 == expr2) # Protection from misuse in boolean python expression context: with pytest.raises(UFLException): bool(expr1) def test_le_produces_ufl_expr(f, g): expr1 = le(f, g) expr2 = f <= g # Correct types (no bools here!): assert isinstance(expr1, LE) assert isinstance(expr2, LE) # Representations are the same: assert bool(expr1 == expr2) # Protection from misuse in boolean python expression context: with pytest.raises(UFLException): bool(expr1) def test_ge_produces_ufl_expr(f, g): expr1 = ge(f, g) expr2 = f >= g # Correct types (no bools here!): assert isinstance(expr1, GE) assert isinstance(expr2, GE) # Representations are the same: assert bool(expr1 == expr2) # Protection from misuse in boolean python expression context: with pytest.raises(UFLException): bool(expr1) ufl-1.6.0/test/test_degree_estimation.py000077500000000000000000000115131255567402100203760ustar00rootroot00000000000000#!/usr/bin/env py.test __authors__ = "Martin Sandve Alnes" __date__ = "2008-03-12 -- 2009-01-28" import pytest from pprint import * from ufl import * from ufl.algorithms import * def test_total_degree_estimation(): V1 = FiniteElement("CG", triangle, 1) V2 = FiniteElement("CG", triangle, 2) VV = VectorElement("CG", triangle, 3) VM = V1 * V2 O1 = OuterProductElement(V1, V1) O2 = OuterProductElement(V2, V1) v1 = Argument(V1, 2) v2 = Argument(V2, 3) f1, f2 = Coefficients(VM) u1 = Coefficient(O1) u2 = Coefficient(O2) vv = Argument(VV, 4) vu = Argument(VV, 5) x, y = SpatialCoordinate(triangle) assert estimate_total_polynomial_degree(x) == 1 assert estimate_total_polynomial_degree(x * y) == 2 assert estimate_total_polynomial_degree(x ** 3) == 3 assert estimate_total_polynomial_degree(x ** 3) == 3 assert estimate_total_polynomial_degree((x - 1) ** 4) == 4 assert estimate_total_polynomial_degree(vv[0]) == 3 assert estimate_total_polynomial_degree(v2 * vv[0]) == 5 assert estimate_total_polynomial_degree(vu[0] * vv[0]) == 6 assert estimate_total_polynomial_degree(vu[i] * vv[i]) == 6 assert estimate_total_polynomial_degree(v1) == 1 assert estimate_total_polynomial_degree(v2) == 2 # TODO: This should be 1, but 2 is expected behaviour now # because f1 is part of a mixed element with max degree 2. assert estimate_total_polynomial_degree(f1) == 2 assert estimate_total_polynomial_degree(f2) == 2 assert estimate_total_polynomial_degree(v2 * v1) == 3 # TODO: This should be 2, but 3 is expected behaviour now # because f1 is part of a mixed element with max degree 2. assert estimate_total_polynomial_degree(f1 * v1) == 3 assert estimate_total_polynomial_degree(f2 * v1) == 3 assert estimate_total_polynomial_degree(f2 * v2 * v1) == 5 assert estimate_total_polynomial_degree(f2 + 3) == 2 assert estimate_total_polynomial_degree(f2 * 3) == 2 assert estimate_total_polynomial_degree(f2 ** 3) == 6 assert estimate_total_polynomial_degree(f2 / 3) == 2 assert estimate_total_polynomial_degree(f2 / v2) == 4 assert estimate_total_polynomial_degree(f2 / (x - 1)) == 3 assert estimate_total_polynomial_degree(v1.dx(0)) == 0 assert estimate_total_polynomial_degree(f2.dx(0)) == 1 assert estimate_total_polynomial_degree(f2 * v2.dx(0) * v1.dx(0)) == 2 + 1 assert estimate_total_polynomial_degree(f2) == 2 assert estimate_total_polynomial_degree(f2 ** 2) == 4 assert estimate_total_polynomial_degree(f2 ** 3) == 6 assert estimate_total_polynomial_degree(f2 ** 3 * v1) == 7 assert estimate_total_polynomial_degree(f2 ** 3 * v1 + f1 * v1) == 7 # outer product tuple-degree tests assert estimate_total_polynomial_degree(u1) == (1, 1) assert estimate_total_polynomial_degree(u2) == (2, 1) # derivatives should do nothing (don't know in which direction they act) assert estimate_total_polynomial_degree(grad(u2)) == (2, 1) assert estimate_total_polynomial_degree(u1 * u1) == (2, 2) assert estimate_total_polynomial_degree(u2 * u1) == (3, 2) assert estimate_total_polynomial_degree(u2 * u2) == (4, 2) assert estimate_total_polynomial_degree(u1 ** 3) == (3, 3) assert estimate_total_polynomial_degree(u1 ** 3 + u2 * u2) == (4, 3) assert estimate_total_polynomial_degree(u2 ** 2 * u1) == (5, 3) # Math functions of constant values are constant values nx, ny = FacetNormal(triangle) e = nx ** 2 for f in [sin, cos, tan, abs, lambda z:z**7]: assert estimate_total_polynomial_degree(f(e)) == 0 # Based on the arbitrary chosen math function heuristics... heuristic_add = 2 e = x**3 for f in [sin, cos, tan]: assert estimate_total_polynomial_degree(f(e)) == 3 + heuristic_add def test_some_compound_types(): # NB! Although some compound types are supported here, # some derivatives and compounds must be preprocessed # prior to degree estimation. In generic code, this algorithm # should only be applied after preprocessing. etpd = estimate_total_polynomial_degree P2 = FiniteElement("CG", triangle, 2) V2 = VectorElement("CG", triangle, 2) u = Coefficient(P2) v = Coefficient(V2) assert etpd(u.dx(0)) == 2 - 1 assert etpd(grad(u)) == 2 - 1 assert etpd(nabla_grad(u)) == 2 - 1 assert etpd(div(u)) == 2 - 1 assert etpd(v.dx(0)) == 2 - 1 assert etpd(grad(v)) == 2 - 1 assert etpd(nabla_grad(v)) == 2 - 1 assert etpd(div(v)) == 2 - 1 assert etpd(nabla_div(v)) == 2 - 1 assert etpd(dot(v, v)) == 2 + 2 assert etpd(inner(v, v)) == 2 + 2 assert etpd(dot(grad(u), grad(u))) == 2 - 1 + 2 - 1 assert etpd(inner(grad(u), grad(u))) == 2 - 1 + 2 - 1 assert etpd(dot(grad(v), grad(v))) == 2 - 1 + 2 - 1 assert etpd(inner(grad(v), grad(v))) == 2 - 1 + 2 - 1 ufl-1.6.0/test/test_derivative.py000077500000000000000000000417001255567402100170520ustar00rootroot00000000000000#!/usr/bin/env py.test __authors__ = "Martin Sandve Alnes" __date__ = "2009-02-17 -- 2009-02-17" import pytest import math from itertools import chain from ufl import * from ufl.constantvalue import as_ufl from ufl.algorithms import expand_indices, strip_variables, post_traversal, compute_form_data def assertEqualBySampling(actual, expected): ad = compute_form_data(actual*dx) a = ad.preprocessed_form.integrals_by_type("cell")[0].integrand() bd = compute_form_data(expected*dx) b = bd.preprocessed_form.integrals_by_type("cell")[0].integrand() assert ([ad.function_replace_map[ac] for ac in ad.reduced_coefficients] == [bd.function_replace_map[bc] for bc in bd.reduced_coefficients]) n = ad.num_coefficients def make_value(c): if isinstance(c, Coefficient): z = 0.3 m = c.count() else: z = 0.7 m = c.number() if c.ufl_shape == (): return z * (0.1 + 0.9 * m / n) elif len(c.ufl_shape) == 1: return tuple((z * (j + 0.1 + 0.9 * m / n) for j in range(c.ufl_shape[0]))) else: raise NotImplementedError("Tensor valued expressions not supported here.") amapping = dict((c, make_value(c)) for c in chain(ad.original_form.coefficients(), ad.original_form.arguments())) bmapping = dict((c, make_value(c)) for c in chain(bd.original_form.coefficients(), bd.original_form.arguments())) acell = actual.cell() bcell = expected.cell() assert acell == bcell if acell.geometric_dimension() == 1: x = (0.3,) elif acell.geometric_dimension() == 2: x = (0.3, 0.4) elif acell.geometric_dimension() == 3: x = (0.3, 0.4, 0.5) av = a(x, amapping) bv = b(x, bmapping) if not av == bv: print("Tried to sample expressions to compare but failed:") print() print((str(a))) print(av) print() print((str(b))) print(bv) print() assert av == bv def _test(self, f, df): cell = triangle element = FiniteElement("CG", cell, 1) v = TestFunction(element) u = TrialFunction(element) w = Coefficient(element) xv = (0.3, 0.7) uv = 7.0 vv = 13.0 wv = 11.0 x = xv mapping = { v: vv, u: uv, w: wv } dfv1 = derivative(f(w), w, v) dfv2 = df(w, v) dfv1 = dfv1(x, mapping) dfv2 = dfv2(x, mapping) assert dfv1 == dfv2 dfv1 = derivative(f(7*w), w, v) dfv2 = 7*df(7*w, v) dfv1 = dfv1(x, mapping) dfv2 = dfv2(x, mapping) assert dfv1 == dfv2 # --- Literals def testScalarLiteral(self): def f(w): return as_ufl(1) def df(w, v): return zero() _test(self, f, df) def testIdentityLiteral(self): def f(w): return Identity(2)[i, i] def df(w, v): return zero() _test(self, f, df) # --- Form arguments def testCoefficient(self): def f(w): return w def df(w, v): return v _test(self, f, df) def testArgument(self): def f(w): return TestFunction(FiniteElement("CG", triangle, 1)) def df(w, v): return zero() _test(self, f, df) # --- Geometry def testSpatialCoordinate(self): def f(w): return SpatialCoordinate(triangle)[0] def df(w, v): return zero() _test(self, f, df) def testFacetNormal(self): def f(w): return FacetNormal(triangle)[0] def df(w, v): return zero() _test(self, f, df) #def testCellSurfaceArea(self): # def f(w): return CellSurfaceArea(triangle) # def df(w, v): return zero() # _test(self, f, df) def testFacetArea(self): def f(w): return FacetArea(triangle) def df(w, v): return zero() _test(self, f, df) def testCircumradius(self): def f(w): return Circumradius(triangle) def df(w, v): return zero() _test(self, f, df) def testCellVolume(self): def f(w): return CellVolume(triangle) def df(w, v): return zero() _test(self, f, df) # --- Basic operators def testSum(self): def f(w): return w + 1 def df(w, v): return v _test(self, f, df) def testProduct(self): def f(w): return 3*w def df(w, v): return 3*v _test(self, f, df) def testPower(self): def f(w): return w**3 def df(w, v): return 3*w**2*v _test(self, f, df) def testDivision(self): def f(w): return w / 3.0 def df(w, v): return v / 3.0 _test(self, f, df) def testDivision2(self): def f(w): return 3.0 / w def df(w, v): return -3.0 * v / w**2 _test(self, f, df) def testExp(self): def f(w): return exp(w) def df(w, v): return v*exp(w) _test(self, f, df) def testLn(self): def f(w): return ln(w) def df(w, v): return v / w _test(self, f, df) def testCos(self): def f(w): return cos(w) def df(w, v): return -v*sin(w) _test(self, f, df) def testSin(self): def f(w): return sin(w) def df(w, v): return v*cos(w) _test(self, f, df) def testTan(self): def f(w): return tan(w) def df(w, v): return v*2.0/(cos(2.0*w) + 1.0) _test(self, f, df) def testAcos(self): def f(w): return acos(w/1000) def df(w, v): return -(v/1000)/sqrt(1.0 - (w/1000)**2) _test(self, f, df) def testAsin(self): def f(w): return asin(w/1000) def df(w, v): return (v/1000)/sqrt(1.0 - (w/1000)**2) _test(self, f, df) def testAtan(self): def f(w): return atan(w) def df(w, v): return v/(1.0 + w**2) _test(self, f, df) # FIXME: Add the new erf and bessel_* # --- Abs and conditionals def testAbs(self): def f(w): return abs(w) def df(w, v): return sign(w)*v _test(self, f, df) def testConditional(self): def cond(w): return lt(1.0, 2.0) def f(w): return conditional(cond(w), 2*w, 3*w) def df(w, v): return 2*v _test(self, f, df) def cond(w): return lt(2.0, 1.0) def f(w): return conditional(cond(w), 2*w, 3*w) def df(w, v): return 3*v _test(self, f, df) def testConditional(self): # This will fail without bugfix in derivative def cond(w): return lt(w, 1.0) def f(w): return conditional(cond(w), 2*w, 3*w) def df(w, v): return (conditional(cond(w), 1, 0) * 2*v + conditional(cond(w), 0, 1) * 3*v) _test(self, f, df) # --- Tensor algebra basics def testIndexSum(self): def f(w): # 3*w + 4*w**2 + 5*w**3 a = as_vector((w, w**2, w**3)) b = as_vector((3, 4, 5)) i, = indices(1) return a[i]*b[i] def df(w, v): return 3*v + 4*2*w*v + 5*3*w**2*v _test(self, f, df) def testListTensor(self): v = variable(as_ufl(42)) f = as_tensor(( ( (0, 0), (0, 0) ), ( (v, 2*v), (0, 0) ), ( (v**2, 1), (2, v/2) ), )) assert f.ufl_shape == (3, 2, 2) g = as_tensor(( ( (0, 0), (0, 0) ), ( (1, 2), (0, 0) ), ( (84, 0), (0, 0.5) ), )) assert g.ufl_shape == (3, 2, 2) dfv = diff(f, v) x = None for i in range(3): for j in range(2): for k in range(2): self.assertEqual(dfv[i, j, k](x), g[i, j, k](x)) # --- Coefficient and argument input configurations def test_single_scalar_coefficient_derivative(self): cell = triangle V = FiniteElement("CG", cell, 1) u = Coefficient(V) v = TestFunction(V) a = 3*u**2 b = derivative(a, u, v) self.assertEqualAfterPreprocessing(b, 3*(u*(2*v))) def test_single_vector_coefficient_derivative(self): cell = triangle V = VectorElement("CG", cell, 1) u = Coefficient(V) v = TestFunction(V) a = 3*dot(u, u) actual = derivative(a, u, v) expected = 3*(2*(u[i]*v[i])) assertEqualBySampling(actual, expected) def test_multiple_coefficient_derivative(self): cell = triangle V = FiniteElement("CG", cell, 1) W = VectorElement("CG", cell, 1) M = V*W uv = Coefficient(V) uw = Coefficient(W) v = TestFunction(M) vv, vw = split(v) a = sin(uv)*dot(uw, uw) actual = derivative(a, (uv, uw), split(v)) expected = cos(uv)*vv * (uw[i]*uw[i]) + (uw[j]*vw[j])*2 * sin(uv) assertEqualBySampling(actual, expected) actual = derivative(a, (uv, uw), v) expected = cos(uv)*vv * (uw[i]*uw[i]) + (uw[j]*vw[j])*2 * sin(uv) assertEqualBySampling(actual, expected) def test_indexed_coefficient_derivative(self): cell = triangle I = Identity(cell.geometric_dimension()) V = FiniteElement("CG", cell, 1) W = VectorElement("CG", cell, 1) u = Coefficient(W) v = TestFunction(V) w = dot(u, nabla_grad(u)) #a = dot(w, w) a = (u[i]*u[k].dx(i)) * w[k] actual = derivative(a, u[0], v) dw = v*u[k].dx(0) + u[i]*I[0, k]*v.dx(i) expected = 2 * w[k] * dw assertEqualBySampling(actual, expected) def test_multiple_indexed_coefficient_derivative(self): cell = tetrahedron I = Identity(cell.geometric_dimension()) V = FiniteElement("CG", cell, 1) V2 = V*V W = VectorElement("CG", cell, 1) u = Coefficient(W) w = Coefficient(W) v = TestFunction(V2) vu, vw = split(v) actual = derivative(cos(u[i]*w[i]), (u[2], w[1]), (vu, vw)) expected = -sin(u[i]*w[i])*(vu*w[2] + u[1]*vw) assertEqualBySampling(actual, expected) def test_segregated_derivative_of_convection(self): cell = tetrahedron V = FiniteElement("CG", cell, 1) W = VectorElement("CG", cell, 1) u = Coefficient(W) v = Coefficient(W) du = TrialFunction(V) dv = TestFunction(V) L = dot(dot(u, nabla_grad(u)), v) Lv = {} Lvu = {} for i in range(cell.geometric_dimension()): Lv[i] = derivative(L, v[i], dv) for j in range(cell.geometric_dimension()): Lvu[i, j] = derivative(Lv[i], u[j], du) for i in range(cell.geometric_dimension()): for j in range(cell.geometric_dimension()): form = Lvu[i, j]*dx fd = compute_form_data(form) pf = fd.preprocessed_form a = expand_indices(pf) #print (i,j), str(a) k = Index() for i in range(cell.geometric_dimension()): for j in range(cell.geometric_dimension()): actual = Lvu[i, j] expected = du*u[i].dx(j)*dv + u[k]*du.dx(k)*dv assertEqualBySampling(actual, expected) # --- User provided derivatives of coefficients def test_coefficient_derivatives(self): V = FiniteElement("Lagrange", triangle, 1) dv = TestFunction(V) f = Coefficient(V).reconstruct(count=0) g = Coefficient(V).reconstruct(count=1) df = Coefficient(V).reconstruct(count=2) dg = Coefficient(V).reconstruct(count=3) u = Coefficient(V).reconstruct(count=4) cd = { f: df, g: dg } integrand = inner(f, g) expected = (df*dv)*g + f*(dg*dv) F = integrand*dx J = derivative(F, u, dv, cd) fd = compute_form_data(J) actual = fd.preprocessed_form.integrals()[0].integrand() assert (actual*dx).signature() == (expected*dx).signature() self.assertEqual(replace(actual, fd.function_replace_map), expected) def test_vector_coefficient_derivatives(self): V = VectorElement("Lagrange", triangle, 1) VV = TensorElement("Lagrange", triangle, 1) dv = TestFunction(V) df = Coefficient(VV).reconstruct(count=0) g = Coefficient(V).reconstruct(count=1) f = Coefficient(V).reconstruct(count=2) u = Coefficient(V).reconstruct(count=3) cd = { f: df } integrand = inner(f, g) i0, i1, i2, i3, i4 = [Index(count=c) for c in range(5)] expected = as_tensor(df[i2, i1]*dv[i1], (i2,))[i0]*g[i0] F = integrand*dx J = derivative(F, u, dv, cd) fd = compute_form_data(J) actual = fd.preprocessed_form.integrals()[0].integrand() assert (actual*dx).signature() == (expected*dx).signature() #self.assertEqual(replace(actual, fd.function_replace_map), expected) def test_vector_coefficient_derivatives_of_product(self): V = VectorElement("Lagrange", triangle, 1) VV = TensorElement("Lagrange", triangle, 1) dv = TestFunction(V) df = Coefficient(VV).reconstruct(count=0) g = Coefficient(V).reconstruct(count=1) dg = Coefficient(VV).reconstruct(count=2) f = Coefficient(V).reconstruct(count=3) u = Coefficient(V).reconstruct(count=4) cd = { f: df, g: dg } integrand = f[i]*g[i] i0, i1, i2, i3, i4 = [Index(count=c) for c in range(5)] expected = as_tensor(df[i2, i1]*dv[i1], (i2,))[i0]*g[i0] +\ f[i0]*as_tensor(dg[i4, i3]*dv[i3], (i4,))[i0] F = integrand*dx J = derivative(F, u, dv, cd) fd = compute_form_data(J) actual = fd.preprocessed_form.integrals()[0].integrand() # Keeping this snippet here for a while for debugging purposes if 0: print(('\n', 'str:')) print((str(actual))) print((str(expected))) print(('\n', 'repr:')) print((repr(actual))) print((repr(expected))) from ufl.algorithms import tree_format open('actual.txt', 'w').write(tree_format(actual)) open('expected.txt', 'w').write(tree_format(expected)) print(('\n', 'equal:')) print((str(actual) == str(expected))) print((repr(actual) == repr(expected))) print((actual == expected)) # Tricky case! These are equal in representation except # that the outermost sum/indexsum are swapped. # Sampling the expressions instead of comparing representations. x = (0, 0) funcs = {dv: (13, 14), f: (1, 2), g: (3, 4), df: ((5, 6), (7, 8)), dg: ((9, 10), (11, 12))} self.assertEqual(replace(actual, fd.function_replace_map)(x, funcs), expected(x, funcs)) # TODO: Add tests covering more cases, in particular mixed stuff # --- Some actual forms def testHyperElasticity(self): cell = interval element = FiniteElement("CG", cell, 2) w = Coefficient(element) v = TestFunction(element) u = TrialFunction(element) b = Constant(cell) K = Constant(cell) dw = w.dx(0) dv = v.dx(0) du = v.dx(0) E = dw + dw**2 / 2 E = variable(E) Q = b*E**2 psi = K*(exp(Q)-1) f = psi*dx F = derivative(f, w, v) J = derivative(F, w, u) form_data_f = compute_form_data(f) form_data_F = compute_form_data(F) form_data_J = compute_form_data(J) f = form_data_f.preprocessed_form F = form_data_F.preprocessed_form J = form_data_J.preprocessed_form f_expression = strip_variables(f.integrals_by_type("cell")[0].integrand()) F_expression = strip_variables(F.integrals_by_type("cell")[0].integrand()) J_expression = strip_variables(J.integrals_by_type("cell")[0].integrand()) #classes = set(c.__class__ for c in post_traversal(f_expression)) Kv = .2 bv = .3 dw = .5 dv = .7 du = .11 E = dw + dw**2 / 2. Q = bv*E**2 expQ = float(exp(Q)) psi = Kv*(expQ-1) fv = psi Fv = 2*Kv*bv*E*(1+dw)*expQ*dv Jv = 2*Kv*bv*expQ*dv*du*(E + (1+dw)**2*(2*bv*E**2 + 1)) def Nv(x, derivatives): assert derivatives == (0,) return dv def Nu(x, derivatives): assert derivatives == (0,) return du def Nw(x, derivatives): assert derivatives == (0,) return dw w, b, K = form_data_f.original_form.coefficients() mapping = { K: Kv, b: bv, w: Nw } fv2 = f_expression((0,), mapping) self.assertAlmostEqual(fv, fv2) w, b, K = form_data_F.original_form.coefficients() v, = form_data_F.original_form.arguments() mapping = { K: Kv, b: bv, v: Nv, w: Nw } Fv2 = F_expression((0,), mapping) self.assertAlmostEqual(Fv, Fv2) w, b, K = form_data_J.original_form.coefficients() v, u = form_data_J.original_form.arguments() mapping = { K: Kv, b: bv, v: Nv, u: Nu, w: Nw } Jv2 = J_expression((0,), mapping) self.assertAlmostEqual(Jv, Jv2) def test_mass_derived_from_functional(self): cell = triangle V = FiniteElement("CG", cell, 1) v = TestFunction(V) u = TrialFunction(V) w = Coefficient(V) f = (w**2/2)*dx L = w*v*dx a = u*v*dx F = derivative(f, w, v) J1 = derivative(L, w, u) J2 = derivative(F, w, u) # TODO: assert something # --- Interaction with replace def test_derivative_replace_works_together(self): cell = triangle V = FiniteElement("CG", cell, 1) v = TestFunction(V) u = TrialFunction(V) f = Coefficient(V) g = Coefficient(V) M = cos(f)*sin(g) F = derivative(M, f, v) J = derivative(F, f, u) JR = replace(J, { f: g }) F2 = -sin(f)*v*sin(g) J2 = -cos(f)*u*v*sin(g) JR2 = -cos(g)*u*v*sin(g) assertEqualBySampling(F, F2) assertEqualBySampling(J, J2) assertEqualBySampling(JR, JR2) # --- Scratch space def test_foobar(self): element = VectorElement("Lagrange", triangle, 1) v = TestFunction(element) du = TrialFunction(element) U = Coefficient(element) def planarGrad(u): return as_matrix([[u[0].dx(0), 0, u[0].dx(1)], [ 0, 0, 0 ], [u[1].dx(0), 0, u[1].dx(1)]]) def epsilon(u): return 0.5*(planarGrad(u)+planarGrad(u).T) def NS_a(u, v): return inner(epsilon(u), epsilon(v)) L = NS_a(U, v)*dx a = derivative(L, U, du) # TODO: assert something ufl-1.6.0/test/test_diff.py000077500000000000000000000062601255567402100156220ustar00rootroot00000000000000#!/usr/bin/env py.test __authors__ = "Martin Sandve Alnes" __date__ = "2009-02-17 -- 2014-10-14" import pytest import math from ufl import * from ufl.constantvalue import as_ufl from ufl.algorithms import expand_derivatives def get_variables(): xv = None vv = 5.0 return (xv, variable(vv)) @pytest.fixture def v(): xv, vv = get_variables() return vv def _test(f, df): x, v = get_variables() dfv1 = diff(f(v), v) dfv2 = df(v) dfv1 = dfv1(x) dfv2 = dfv2(x) assert round(dfv1 - dfv2, 7) == 0 dfv1 = diff(f(7 * v), v) dfv2 = 7 * df(7 * v) dfv1 = dfv1(x) dfv2 = dfv2(x) assert round(dfv1 - dfv2, 7) == 0 def testVariable(v): def f(v): return v def df(v): return as_ufl(1) _test(f, df) def testSum(v): def f(v): return v + 1 def df(v): return as_ufl(1) _test(f, df) def testProduct(v): def f(v): return 3 * v def df(v): return as_ufl(3) _test(f, df) def testPower(v): def f(v): return v ** 3 def df(v): return 3 * v ** 2 _test(f, df) def testDivision(v): def f(v): return v / 3.0 def df(v): return as_ufl(1.0 / 3.0) _test(f, df) def testDivision2(v): def f(v): return 3.0 / v def df(v): return -3.0 / v ** 2 _test(f, df) def testExp(v): def f(v): return exp(v) def df(v): return exp(v) _test(f, df) def testLn(v): def f(v): return ln(v) def df(v): return 1.0 / v _test(f, df) def testSin(v): def f(v): return sin(v) def df(v): return cos(v) _test(f, df) def testCos(v): def f(v): return cos(v) def df(v): return -sin(v) _test(f, df) def testTan(v): def f(v): return tan(v) def df(v): return 2.0 / (cos(2.0 * v) + 1.0) _test(f, df) # TODO: Check the following tests. They run into strange math domain errors. # def testAsin(v): # def f(v): return asin(v) # def df(v): return 1/sqrt(1.0 - v**2) # _test(f, df) # def testAcos(v): # def f(v): return acos(v) # def df(v): return -1/sqrt(1.0 - v**2) # _test(f, df) def testAtan(v): def f(v): return atan(v) def df(v): return 1 / (1.0 + v ** 2) _test(f, df) def testIndexSum(v): def f(v): # 3*v + 4*v**2 + 5*v**3 a = as_vector((v, v ** 2, v ** 3)) b = as_vector((3, 4, 5)) i, = indices(1) return a[i] * b[i] def df(v): return 3 + 4 * 2 * v + 5 * 3 * v ** 2 _test(f, df) def testCoefficient(): v = Constant(triangle) assert round(expand_derivatives(diff(v,v))-1.0, 7) == 0 def testDiffX(): cell = triangle x = SpatialCoordinate(cell) f = x[0] ** 2 * x[1] ** 2 i, = indices(1) df1 = diff(f, x) df2 = as_vector(f.dx(i), i) xv = (2, 3) df10 = df1[0](xv) df11 = df1[1](xv) df20 = df2[0](xv) df21 = df2[1](xv) assert round(df10 - df20, 7) == 0 assert round(df11 - df21, 7) == 0 assert round(df10 - 2 * 2 * 9, 7) == 0 assert round(df11 - 2 * 4 * 3, 7) == 0 # TODO: More tests involving wrapper types and indices ufl-1.6.0/test/test_domains.py000077500000000000000000000343601255567402100163460ustar00rootroot00000000000000#!/usr/bin/env py.test """ Tests of domain language and attaching domains to forms. """ import pytest from ufl import * from ufl.geometry import as_domain from ufl.algorithms import compute_form_data all_cells = (interval, triangle, tetrahedron, quadrilateral, hexahedron) from mockobjects import MockMesh, MockMeshFunction def test_construct_domains_from_cells(): for cell in all_cells: D1 = Domain(cell) D2 = as_domain(cell) assert D1 is not D2 if 0: print() for D in (D1, D2): print(('id', id(D))) print(('str', str(D))) print(('repr', repr(D))) print() assert D1 == D2 def test_as_domain_from_cell_is_equal(): for cell in all_cells: D1 = as_domain(cell) D2 = as_domain(cell) assert D1 == D2 def test_construct_domains_with_names(): for cell in all_cells: D2 = Domain(cell, label="D2") D3 = Domain(cell, label="D3") D3b = Domain(cell, label="D3") assert D2 != D3 assert D3 == D3b def test_domains_sort_by_name(): # This ordering is rather arbitrary, but at least this shows sorting is # working domains1 = [Domain(cell, label="D%s" % cell.cellname()) for cell in all_cells] domains2 = [Domain(cell, label="D%s" % cell.cellname()) for cell in sorted(all_cells)] sdomains = sorted(domains1, key=lambda D: (D.geometric_dimension(), D.topological_dimension(), D.cell(), D.label())) assert sdomains != domains1 assert sdomains == domains2 def test_topdomain_creation(): D = Domain(interval) assert D.geometric_dimension() == 1 D = Domain(triangle) assert D.geometric_dimension() == 2 D = Domain(tetrahedron) assert D.geometric_dimension() == 3 def test_cell_legacy_case(): # Passing cell like old code does D = as_domain(triangle) V = FiniteElement("CG", triangle, 1) f = Coefficient(V) assert f.domains() == (D, ) M = f * dx assert M.domains() == (D, ) def test_simple_domain_case(): # Creating domain from just cell with label like new dolfin will do D = Domain(triangle, label="foo") V = FiniteElement("CG", D, 1) f = Coefficient(V) assert f.domains() == (D, ) M = f * dx assert M.domains() == (D, ) def test_creating_domains_with_coordinate_fields(): # P2 field for coordinates D = Domain(triangle) P2 = VectorElement("CG", D, 2) x = Coefficient(P2) assert x.domains() == (D, ) # Definition of higher order domain, element, coefficient, form E = Domain(x) V = FiniteElement("CG", E, 1) f = Coefficient(V) M = f * dx assert f.domains() == (E, ) assert M.domains() == (E, ) # Test the gymnastics that dolfin will have to go through V2 = eval(V.reconstruction_signature()) E2 = V2.domain().reconstruct(coordinates=x) V2 = V2.reconstruct(domain=E2) f2 = f.reconstruct(element=V2) assert f == f2 assert V == V2 assert E == E2 def test_join_domains(): from ufl.geometry import join_domains mesh1 = MockMesh(11) mesh2 = MockMesh(13) triangle3 = Cell("triangle", geometric_dimension=3) xa = Coefficient(VectorElement("CG", Domain(triangle, label="A"), 1)) xb = Coefficient(VectorElement("CG", Domain(triangle, label="B"), 1)) # Equal domains are joined assert 1 == len(join_domains([Domain(triangle), Domain(triangle)])) assert 1 == len(join_domains([Domain(triangle, label="A"), Domain(triangle, label="A")])) assert 1 == len(join_domains([Domain(triangle, label="A", data=mesh1), Domain(triangle, label="A", data=mesh1)])) assert 1 == len(join_domains([Domain(xa), Domain(xa)])) # Different domains are not joined assert 2 == len(join_domains([Domain(triangle, label="A"), Domain(triangle, label="B")])) assert 2 == len(join_domains([Domain(triangle, label="A"), Domain(quadrilateral, label="B")])) assert 2 == len(join_domains([Domain(xa), Domain(xb)])) # Incompatible cells require labeling # self.assertRaises(UFLException, lambda: join_domains([Domain(triangle), Domain(triangle3)])) # FIXME: Figure out # self.assertRaises(UFLException, lambda: join_domains([Domain(triangle), # Domain(quadrilateral)])) # FIXME: Figure out # Incompatible coordinates require labeling xc = Coefficient(VectorElement("CG", Domain(triangle), 1)) xd = Coefficient(VectorElement("CG", Domain(triangle), 1)) with pytest.raises(UFLException): join_domains([Domain(xc), Domain(xd)]) # Incompatible data is checked if and only if the domains are the same assert 2 == len(join_domains([Domain(triangle, label="A", data=mesh1), Domain(triangle, label="B", data=mesh2)])) assert 2 == len(join_domains([Domain(triangle, label="A", data=mesh1), Domain(triangle3, label="B", data=mesh2)])) assert 2 == len(join_domains([Domain(triangle, label="A", data=mesh1), Domain(quadrilateral, label="B", data=mesh2)])) with pytest.raises(UFLException): join_domains([Domain(triangle, label="A", data=mesh1), Domain(triangle, label="A", data=mesh2)]) # Nones are removed assert 1 == len( join_domains([None, Domain(triangle), None, Domain(triangle), None])) assert 2 == len(join_domains([Domain(triangle, label="A"), None, Domain(quadrilateral, label="B")])) assert None not in join_domains([Domain(triangle, label="A"), None, Domain(tetrahedron, label="B")]) def test_everywhere_integrals_with_backwards_compatibility(): D = Domain(triangle) V = FiniteElement("CG", D, 1) f = Coefficient(V) a = f * dx ida, = compute_form_data(a).integral_data # Check some integral data assert ida.integral_type == "cell" assert ida.subdomain_id == "otherwise" assert ida.metadata == {} # Integrands are not equal because of renumbering itg1 = ida.integrals[0].integrand() itg2 = a.integrals()[0].integrand() assert type(itg1) == type(itg2) assert itg1.element() == itg2.element() def xtest_mixed_elements_on_overlapping_regions(): # Create domain and both disjoint and overlapping regions D = Domain(tetrahedron, label='D') DD = Region(D, (0, 4), "DD") DL = Region(D, (1, 2), "DL") DR = Region(D, (2, 3), "DR") # Create function spaces on D V = FiniteElement("CG", D, 1) VD = FiniteElement("DG", DD, 1) VC = FiniteElement("R", DD, 0) VL = VectorElement("DG", DL, 2) VR = FiniteElement("CG", DR, 3) # Create mixed space over all these spaces M = MixedElement(V, VD, VC, VL, VR) # Check that we can get the degree for each value component of the mixed # space assert M.degree(0) == 1 assert M.degree(1) == 1 assert M.degree(2) == 0 assert M.degree(3) == 2 # Vector element assert M.degree(4) == 2 assert M.degree(5) == 2 assert M.degree(6) == 3 assert M.degree() == 3 # Check that we can get the domain for each value component of the mixed # space assert M.domain(0) == D assert M.domain(1) == DD assert M.domain(2) == DD assert M.domain(3) == DL # Vector element assert M.domain(4) == DL assert M.domain(5) == DL assert M.domain(6) == DR # assert M.domain() == None # FIXME: What? # Create a mixed function and fetch components with names for more # readable test code below m = Coefficient(M) md = m[1] mc = m[2] ml = as_vector((m[3], m[4], m[5])) mr = m[6] # These should all work out fine with function and integration domains # perfectly matching a = m[0] ** 2 * dx(D) ad = (md ** 2 + mc ** 2) * dx(DD) al = ml ** 2 * dx(DL) ar = mr ** 2 * dx(DR) # TODO: Check properties of forms, maybe by computing and inspecting form # data. # These should all work out fine with because integration domains are # contained in the function domains ad = m[0] ** 2 * dx(DD) al = m[0] ** 2 * dx(DL) ar = m[0] ** 2 * dx("DR") a0 = m[0] ** 2 * dx(0) a12 = m[0] ** 2 * dx((1, 2)) a3 = m[0] ** 2 * dx(3) # TODO: Check properties of forms, maybe by computing and inspecting form # data. # These should fail because the functions are undefined on the integration domains # self.assertRaises(UFLException, lambda: mr**2*dx(DD)) # FIXME: Make this fail # self.assertRaises(UFLException, lambda: mr**2*dx(DD)) # FIXME: Make this # fail def xtest_form_domain_model(): # Create domains with different celltypes # TODO: Figure out PyDOLFIN integration with Domain DA = Domain(tetrahedron, label='DA') DB = Domain(hexahedron, label='DB') # Check python protocol behaviour assert DA != DB assert {DA, DA} == {DA} assert {DB, DB} == {DB} assert {DA, DB} == {DB, DA} assert sorted((DA, DB, DA, DB)) == sorted((DB, DA, DA, DB)) # Check basic properties assert DA.name() == 'DA' assert DA.geometric_dimension() == 3 assert DA.topological_dimension() == 3 assert DA.cell() == tetrahedron # Check region/domain getters assert DA.top_domain() == DA assert DA.subdomain_ids() == None # assert DA.region_names() == [] # assert DA.regions() == [] # BDA = Boundary(DA) # TODO # IDAB = Intersection(DA,DB) # TODO # ODAB = Overlap(DA,DB) # TODO # Create overlapping regions of each domain DAL = Region(DA, (1, 2), "DAL") DAR = Region(DA, (2, 3), "DAR") DBL = Region(DB, (0, 1), "DBL") DBR = Region(DB, (1, 4), "DBR") # Check that regions are available through domains # assert DA.region_names() == ['DAL', 'DAR'] # assert DA.regions() == [DAL, DAR] # assert DA["DAR"] == DAR # assert DA["DAL"] == DAL # Create function spaces on DA VA = FiniteElement("CG", DA, 1) VAL = FiniteElement("CG", DAL, 1) VAR = FiniteElement("CG", DAR, 1) # Create function spaces on DB VB = FiniteElement("CG", DB, 1) VBL = FiniteElement("CG", DBL, 1) VBR = FiniteElement("CG", DBR, 1) # Check that regions are available through elements assert VA.domain() == DA assert VAL.domain() == DAL assert VAR.domain() == DAR # Create functions in each space on DA fa = Coefficient(VA) fal = Coefficient(VAL) far = Coefficient(VAR) # Create functions in each space on DB fb = Coefficient(VB) fbl = Coefficient(VBL) fbr = Coefficient(VBR) # Checks of coefficient domains is covered well in the mixed element test # Create measure objects directly based on domain and region objects dxa = dx(DA) dxal = dx(DAL) dxar = dx('DAR') # Get Region by name # Create measure proxy objects from strings and ints, requiring # domains and regions to be part of their integrands dxb = dx('DB') # Get Domain by name dxbl = dx(Region(DB, (1, 4), 'DBL2')) # Provide a region with different name but same subdomain ids as # DBL dxbr = dx((1, 4)) # Assume unique Domain and provide subdomain ids explicitly # Not checking measure objects in detail, as long as # they carry information to construct integrals below # they are good to go. # Create integrals on each region with matching spaces and measures Ma = fa * dxa Mar = far * dxar Mal = fal * dxal Mb = fb * dxb Mbr = fbr * dxbr Mbl = fbl * dxbl # TODO: Check forms, by getting and inspecting domains somehow. # TODO: How do we express the distinction between "everywhere" and # "nowhere"? subdomain_ids=None vs []? # Create forms from integrals over overlapping regions on same top domain Marl = Mar + Mal Mbrl = Mbr + Mbl # Create forms from integrals over top domain and regions Mac = Ma + Marl Mbc = Mb + Mbrl # Create forms from separate top domains Mab = Ma + Mb # Create forms from separate top domains with overlapping regions Mabrl = Mac + Mbc # self.assertFalse(True) # Getting here, but it's not bloody likely that # everything above is actually working. Add assertions! def xtest_subdomain_stuff(): # Old sketch, not working D = Domain(triangle) D1 = D[1] D2 = D[2] D3 = D[3] DL = Region(D, (D1, D2), 'DL') DR = Region(D, (D2, D3), 'DR') DM = Overlap(DL, DR) assert DM == D2 VL = VectorElement(DL, "CG", 1) VR = FiniteElement(DR, "CG", 2) V = VL * VR def sub_elements_on_subdomains(W): # Get from W: (already there) subelements = (VL, VR) # Get from W: subdomains = (D1, D2, D3) # Build in W: dom2elm = {D1: (VL,), D2: (VL, VR), D3: (VR,), } # Build in W: elm2dom = {VL: (D1, D2), VR: (D2, D3)} # ElementSwitch represents joining of elements restricted to disjunct # subdomains. # An element restricted to a domain union becomes a switch # of elements restricted to each disjoint subdomain VL_D1 = VectorElement(D1, "CG", 1) VL_D2 = VectorElement(D2, "CG", 1) VLalt = ElementSwitch({D1: VL_D1, D2: VL_D2}) # Ditto VR_D2 = FiniteElement(D2, "CG", 2) VR_D3 = FiniteElement(D3, "CG", 2) VRalt = ElementSwitch({D2: VR_D2, D3: VR_D3}) # A mixed element of ElementSwitches is mixed only on the overlapping # domains: Valt1 = VLalt * VRalt Valt2 = ElementSwitch({D1: VL_D1, D2: VL_D2 * VR_D2, D3: VR_D3}) ul, ur = TrialFunctions(V) vl, vr = TestFunctions(V) # Implemented by user: al = dot(ul, vl) * dx(DL) ar = ur * vr * dx(DR) # Disjunctified by UFL: alonly = dot(ul, vl) * dx(D1) # integral_1 knows that only subelement VL is active am = (dot(ul, vl) + ur * vr) * dx(D2) # integral_2 knows that both subelements are active aronly = ur * vr * \ dx(D3) # integral_3 knows that only subelement VR is active ufl-1.6.0/test/test_elements.py000077500000000000000000000146751255567402100165370ustar00rootroot00000000000000#!/usr/bin/env py.test # Last changed: 2014-02-24 import pytest from ufl import * all_cells = (interval, triangle, tetrahedron, quadrilateral, hexahedron) # TODO: cover all valid element definitions def test_scalar_galerkin(): for cell in all_cells: for p in range(1, 10): for family in ("Lagrange", "CG", "Discontinuous Lagrange", "DG"): element = FiniteElement(family, cell, p) assert element.value_shape() == () assert element == eval(repr(element)) def test_vector_galerkin(): for cell in all_cells: dim = cell.geometric_dimension() # shape = () if dim == 1 else (dim,) shape = (dim,) for p in range(1, 10): for family in ("Lagrange", "CG", "Discontinuous Lagrange", "DG"): element = VectorElement(family, cell, p) assert element.value_shape() == shape assert element == eval(repr(element)) for i in range(dim): c = element.extract_component(i) assert c[0] == () def test_tensor_galerkin(): for cell in all_cells: dim = cell.geometric_dimension() # shape = () if dim == 1 else (dim,dim) shape = (dim, dim) for p in range(1, 10): for family in ("Lagrange", "CG", "Discontinuous Lagrange", "DG"): element = TensorElement(family, cell, p) assert element.value_shape() == shape assert element == eval(repr(element)) for i in range(dim): for j in range(dim): c = element.extract_component((i, j)) assert c[0] == () def test_tensor_symmetry(): for cell in all_cells: dim = cell.geometric_dimension() for p in range(1, 10): for s in (None, True, {(0, 1): (1, 0)}): # Symmetry dict is invalid for interval cell if isinstance(s, dict) and cell == interval: continue for family in ("Lagrange", "CG", "Discontinuous Lagrange", "DG"): if isinstance(s, dict): element = TensorElement( family, cell, p, shape=(dim, dim), symmetry=s) else: element = TensorElement(family, cell, p, symmetry=s) assert element.value_shape(), (dim == dim) assert element == eval(repr(element)) for i in range(dim): for j in range(dim): c = element.extract_component((i, j)) assert c[0] == () def test_mixed_tensor_symmetries(): from ufl.algorithms import expand_indices, expand_compounds S = FiniteElement('CG', triangle, 1) V = VectorElement('CG', triangle, 1) T = TensorElement('CG', triangle, 1, symmetry=True) # M has dimension 4+1, symmetries are 2->1 M = T * S P = Coefficient(M) M = inner(P, P) * dx M2 = expand_indices(expand_compounds(M)) assert '[1]' in str(M2) assert '[2]' not in str(M2) # M has dimension 2+(1+4), symmetries are 5->4 M = V * (S * T) P = Coefficient(M) M = inner(P, P) * dx M2 = expand_indices(expand_compounds(M)) assert '[4]' in str(M2) assert '[5]' not in str(M2) def test_bdm(): for cell in (triangle, tetrahedron): dim = cell.geometric_dimension() element = FiniteElement("BDM", cell, 1) assert element.value_shape() == (dim,) assert element == eval(repr(element)) def test_vector_bdm(): for cell in (triangle, tetrahedron): dim = cell.geometric_dimension() element = VectorElement("BDM", cell, 1) assert element.value_shape(), (dim == dim) assert element == eval(repr(element)) def test_mixed(): for cell in (triangle, tetrahedron): dim = cell.geometric_dimension() velement = VectorElement("CG", cell, 2) pelement = FiniteElement("CG", cell, 1) TH1 = MixedElement(velement, pelement) TH2 = velement * pelement assert TH1.value_shape() == (dim + 1,) assert TH2.value_shape() == (dim + 1,) assert repr(TH1) == repr(TH2) assert TH1 == eval(repr(TH2)) assert TH2 == eval(repr(TH1)) def test_nested_mixed(): for cell in (triangle, tetrahedron): dim = cell.geometric_dimension() velement = VectorElement("CG", cell, 2) pelement = FiniteElement("CG", cell, 1) TH1 = MixedElement((velement, pelement), pelement) TH2 = velement * pelement * pelement assert TH1.value_shape() == (dim + 2,) assert TH2.value_shape() == (dim + 2,) assert repr(TH1) == repr(TH2) assert TH1 == eval(repr(TH2)) assert TH2 == eval(repr(TH1)) def test_quadrature_scheme(): for cell in (triangle, tetrahedron): for q in (None, 1, 2, 3): element = FiniteElement("CG", cell, 1, quad_scheme=q) assert element.quadrature_scheme() == q assert element == eval(repr(element)) def test_missing_cell(): # These special cases are here to allow missing # cell in PyDOLFIN Constant and Expression for cell in (triangle, None): element = FiniteElement("Real", cell, 0) assert element == eval(repr(element)) element = FiniteElement("Undefined", cell, None) assert element == eval(repr(element)) element = VectorElement("Lagrange", cell, 1, dim=2) assert element == eval(repr(element)) element = TensorElement("DG", cell, 1, shape=(2, 2)) assert element == eval(repr(element)) def test_invalid_degree(): cell = triangle for degree in (1, None): element = FiniteElement("CG", cell, degree) assert element == eval(repr(element)) element = VectorElement("CG", cell, degree) assert element == eval(repr(element)) def test_lobatto(): cell = interval for degree in (1, 2, None): element = FiniteElement("Lob", cell, degree) assert element == eval(repr(element)) element = FiniteElement("Lobatto", cell, degree) assert element == eval(repr(element)) def test_radau(): cell = interval for degree in (0, 1, 2, None): element = FiniteElement("Rad", cell, degree) assert element == eval(repr(element)) element = FiniteElement("Radau", cell, degree) assert element == eval(repr(element)) ufl-1.6.0/test/test_equals.py000077500000000000000000000034401255567402100162010ustar00rootroot00000000000000#!/usr/bin/env py.test """ Test of expression comparison. """ import pytest # This imports everything external code will see from ufl from ufl import * def test_comparison_of_coefficients(): V = FiniteElement("CG", triangle, 1) U = FiniteElement("CG", triangle, 2) Ub = FiniteElement("CG", triangle, 2) v1 = Coefficient(V, count=1) v1b = Coefficient(V, count=1) v2 = Coefficient(V, count=2) u1 = Coefficient(U, count=1) u2 = Coefficient(U, count=2) u2b = Coefficient(Ub, count=2) # Itentical objects assert v1 == v1 assert u2 == u2 # Equal but distinct objects assert v1 == v1b assert u2 == u2b # Different objects assert not v1 == v2 assert not u1 == u2 assert not v1 == u1 assert not v2 == u2 def test_comparison_of_products(): V = FiniteElement("CG", triangle, 1) v = Coefficient(V) u = Coefficient(V) a = (v * 2) * u b = (2 * v) * u c = 2 * (v * u) assert a == b assert not a == c assert not b == c def test_comparison_of_sums(): V = FiniteElement("CG", triangle, 1) v = Coefficient(V) u = Coefficient(V) a = (v + 2) + u b = (2 + v) + u c = 2 + (v + u) assert a == b assert not a == c assert not b == c def test_comparison_of_deeply_nested_expression(): V = FiniteElement("CG", triangle, 1) v = Coefficient(V, count=1) u = Coefficient(V, count=1) w = Coefficient(V, count=2) def build_expr(a): for i in range(100): if i % 3 == 0: a = a + i elif i % 3 == 1: a = a * i elif i % 3 == 2: a = a ** i return a a = build_expr(u) b = build_expr(v) c = build_expr(w) assert a == b assert not a == c assert not b == c ufl-1.6.0/test/test_evaluate.py000077500000000000000000000163221255567402100165200ustar00rootroot00000000000000#!/usr/bin/env py.test __authors__ = "Martin Sandve Alnes" __date__ = "2009-02-13 -- 2009-02-13" import pytest import math from ufl import * from ufl.constantvalue import as_ufl def testScalars(): s = as_ufl(123) e = s((5, 7)) v = 123 assert e == v def testZero(): s = as_ufl(0) e = s((5, 7)) v = 0 assert e == v def testIdentity(): cell = triangle I = Identity(cell.geometric_dimension()) s = 123 * I[0, 0] e = s((5, 7)) v = 123 assert e == v s = 123 * I[1, 0] e = s((5, 7)) v = 0 assert e == v def testCoords(): cell = triangle x = SpatialCoordinate(cell) s = x[0] + x[1] e = s((5, 7)) v = 5 + 7 assert e == v def testFunction1(): cell = triangle element = FiniteElement("CG", cell, 1) f = Coefficient(element) s = 3 * f e = s((5, 7), {f: 123}) v = 3 * 123 assert e == v def testFunction2(): cell = triangle element = FiniteElement("CG", cell, 1) f = Coefficient(element) def g(x): return x[0] s = 3 * f e = s((5, 7), {f: g}) v = 3 * 5 assert e == v def testArgument2(): cell = triangle element = FiniteElement("CG", cell, 1) f = Argument(element, 2) def g(x): return x[0] s = 3 * f e = s((5, 7), {f: g}) v = 3 * 5 assert e == v def testAlgebra(): cell = triangle x = SpatialCoordinate(cell) s = 3 * (x[0] + x[1]) - 7 + x[0] ** (x[1] / 2) e = s((5, 7)) v = 3 * (5. + 7.) - 7 + 5. ** (7. / 2) assert e == v def testIndexSum(): cell = triangle x = SpatialCoordinate(cell) i, = indices(1) s = x[i] * x[i] e = s((5, 7)) v = 5 ** 2 + 7 ** 2 assert e == v def testIndexSum2(): cell = triangle x = SpatialCoordinate(cell) I = Identity(cell.geometric_dimension()) i, j = indices(2) s = (x[i] * x[j]) * I[i, j] e = s((5, 7)) # v = sum_i sum_j x_i x_j delta_ij = x_0 x_0 + x_1 x_1 v = 5 ** 2 + 7 ** 2 assert e == v def testMathFunctions(): x = SpatialCoordinate(triangle)[0] s = sin(x) e = s((5, 7)) v = math.sin(5) assert e == v s = cos(x) e = s((5, 7)) v = math.cos(5) assert e == v s = tan(x) e = s((5, 7)) v = math.tan(5) assert e == v s = ln(x) e = s((5, 7)) v = math.log(5) assert e == v s = exp(x) e = s((5, 7)) v = math.exp(5) assert e == v s = sqrt(x) e = s((5, 7)) v = math.sqrt(5) assert e == v def testListTensor(): x, y = SpatialCoordinate(triangle) m = as_matrix([[x, y], [-y, -x]]) s = m[0, 0] + m[1, 0] + m[0, 1] + m[1, 1] e = s((5, 7)) v = 0 assert e == v s = m[0, 0] * m[1, 0] * m[0, 1] * m[1, 1] e = s((5, 7)) v = 5 ** 2 * 7 ** 2 assert e == v def testComponentTensor1(): x = SpatialCoordinate(triangle) m = as_vector(x[i], i) s = m[0] * m[1] e = s((5, 7)) v = 5 * 7 assert e == v def testComponentTensor2(): x = SpatialCoordinate(triangle) xx = outer(x, x) m = as_matrix(xx[i, j], (i, j)) s = m[0, 0] + m[1, 0] + m[0, 1] + m[1, 1] e = s((5, 7)) v = 5 * 5 + 5 * 7 + 5 * 7 + 7 * 7 assert e == v def testComponentTensor3(): x = SpatialCoordinate(triangle) xx = outer(x, x) m = as_matrix(xx[i, j], (i, j)) s = m[0, 0] * m[1, 0] * m[0, 1] * m[1, 1] e = s((5, 7)) v = 5 * 5 * 5 * 7 * 5 * 7 * 7 * 7 assert e == v def testCoefficient(): V = FiniteElement("CG", triangle, 1) f = Coefficient(V) e = f ** 2 def eval_f(x): return x[0] * x[1] ** 2 assert e((3, 7), {f: eval_f}) == (3 * 7 ** 2) ** 2 def testCoefficientDerivative(): V = FiniteElement("CG", triangle, 1) f = Coefficient(V) e = f.dx(0) ** 2 + f.dx(1) ** 2 def eval_f(x, derivatives): if not derivatives: return eval_f.c * x[0] * x[1] ** 2 # assume only first order derivative d, = derivatives if d == 0: return eval_f.c * x[1] ** 2 if d == 1: return eval_f.c * x[0] * 2 * x[1] # shows how to attach data to eval_f eval_f.c = 5 assert e((3, 7), {f: eval_f}) == (5 * 7 ** 2) ** 2 + (5 * 3 * 2 * 7) ** 2 def test_dot(): x = SpatialCoordinate(triangle) s = dot(x, 2 * x) e = s((5, 7)) v = 2 * (5 * 5 + 7 * 7) assert e == v def test_inner(): x = SpatialCoordinate(triangle) xx = as_matrix(((2 * x[0], 3 * x[0]), (2 * x[1], 3 * x[1]))) s = inner(xx, 2 * xx) e = s((5, 7)) v = 2 * ((5 * 2) ** 2 + (5 * 3) ** 2 + (7 * 2) ** 2 + (7 * 3) ** 2) assert e == v def test_outer(): x = SpatialCoordinate(triangle) xx = outer(outer(x, x), as_vector((2, 3))) s = inner(xx, 2 * xx) e = s((5, 7)) v = 2 * (5 ** 2 + 7 ** 2) ** 2 * (2 ** 2 + 3 ** 2) assert e == v def test_cross(): x = SpatialCoordinate(tetrahedron) xv = (3, 5, 7) # Test cross product of triplets of orthogonal # vectors, where |a x b| = |a| |b| ts = [ [as_vector((x[0], 0, 0)), as_vector((0, x[1], 0)), as_vector((0, 0, x[2]))], [as_vector((x[0], x[1], 0)), as_vector((x[1], -x[0], 0)), as_vector((0, 0, x[2]))], [as_vector((0, x[0], x[1])), as_vector((0, x[1], -x[0])), as_vector((x[2], 0, 0))], [as_vector((x[0], 0, x[1])), as_vector((x[1], 0, -x[0])), as_vector((0, x[2], 0))], ] for t in ts: for i in range(3): for j in range(3): cij = cross(t[i], t[j]) dij = dot(cij, cij) eij = dij(xv) tni = dot(t[i], t[i])(xv) tnj = dot(t[j], t[j])(xv) vij = tni * tnj if i != j else 0 assert eij == vij def xtest_dev(): x = SpatialCoordinate(triangle) xv = (5, 7) xx = outer(x, x) s1 = dev(2 * xx) s2 = 2 * (xx - xx.T) # FIXME e = inner(s1, s1)(xv) v = inner(s2, s2)(xv) assert e == v def test_skew(): x = SpatialCoordinate(triangle) xv = (5, 7) xx = outer(x, x) s1 = skew(2 * xx) s2 = (xx - xx.T) e = inner(s1, s1)(xv) v = inner(s2, s2)(xv) assert e == v def test_sym(): x = SpatialCoordinate(triangle) xv = (5, 7) xx = outer(x, x) s1 = sym(2 * xx) s2 = (xx + xx.T) e = inner(s1, s1)(xv) v = inner(s2, s2)(xv) assert e == v def test_tr(): x = SpatialCoordinate(triangle) xv = (5, 7) xx = outer(x, x) s = tr(2 * xx) e = s(xv) v = 2 * sum(xv[i] ** 2 for i in (0, 1)) assert e == v def test_det2D(): x = SpatialCoordinate(triangle) xv = (5, 7) a, b = 6.5, -4 xx = as_matrix(((x[0], x[1]), (a, b))) s = det(2 * xx) e = s(xv) v = 2 ** 2 * (5 * b - 7 * a) assert e == v def xtest_det3D(): # FIXME x = SpatialCoordinate(tetrahedron) xv = (5, 7, 9) a, b, c = 6.5, -4, 3 d, e, f = 2, 3, 4 xx = as_matrix(((x[0], x[1], x[2]), (a, b, c), (d, e, f))) s = det(2 * xx) e = s(xv) v = 2 ** 3 * \ (xv[0] * (b * f - e * c) - xv[1] * (a * f - c * d) + xv[2] * (a * e - b * d)) assert e == v def test_cofac(): pass # TODO def test_inv(): pass # TODO ufl-1.6.0/test/test_expand_indices.py000077500000000000000000000227701255567402100176730ustar00rootroot00000000000000#!/usr/bin/env py.test __authors__ = "Martin Sandve Alnes" __date__ = "2009-03-19 -- 2012-03-20" # Modified by Anders Logg, 2008 # Modified by Garth N. Wells, 2009 import pytest import math from pprint import * from ufl import * from ufl.algorithms import * from ufl.classes import Sum, Product # TODO: Test expand_indices2 throuroughly for correctness, then efficiency: #expand_indices, expand_indices2 = expand_indices2, expand_indices class Fixture: def __init__(self): cell = triangle element = FiniteElement("Lagrange", cell, 1) velement = VectorElement("Lagrange", cell, 1) telement = TensorElement("Lagrange", cell, 1) self.sf = Coefficient(element) self.sf2 = Coefficient(element) self.vf = Coefficient(velement) self.tf = Coefficient(telement) # Note: the derivatives of these functions make no sense, but # their unique constant values are used for validation. def SF(x, derivatives=()): # first order derivatives if derivatives == (0,): return 0.30 elif derivatives == (1,): return 0.31 # second order derivatives elif derivatives == (0, 0): return 0 elif derivatives in ((1, 0), (0, 1)): return 0 elif derivatives == (1, 1): return 0 # function value assert derivatives == () return 3 def SF2(x, derivatives=()): # first order derivatives if derivatives == (0,): return 0.30 elif derivatives == (1,): return 0.31 # second order derivatives elif derivatives == (0, 0): return 3.300 elif derivatives in ((1, 0), (0, 1)): return 3.310 elif derivatives == (1, 1): return 3.311 # function value assert derivatives == () return 3 def VF(x, derivatives=()): # first order derivatives if derivatives == (0,): return (0.50, 0.70) elif derivatives == (1,): return (0.51, 0.71) # second order derivatives elif derivatives == (0, 0): return (0.20, 0.21) elif derivatives in ((1, 0), (0, 1)): return (0.30, 0.31) elif derivatives == (1, 1): return (0.40, 0.41) # function value assert derivatives == () return (5, 7) def TF(x, derivatives=()): # first order derivatives if derivatives == (0,): return ((1.10, 1.30), (1.70, 1.90)) elif derivatives == (1,): return ((1.11, 1.31), (1.71, 1.91)) # second order derivatives elif derivatives == (0, 0): return ((10.00, 10.01), (10.10, 10.11)) elif derivatives in ((1, 0), (0, 1)): return ((12.00, 12.01), (12.10, 12.11)) elif derivatives == (1, 1): return ((11.00, 11.01), (11.10, 11.11)) # function value assert derivatives == () return ((11, 13), (17, 19)) self.x = (1.23, 3.14) self.mapping = { self.sf: SF, self.sf2: SF2, self.vf: VF, self.tf: TF } def compare(self, f, value): debug = 0 if debug: print(('f', f)) g = expand_derivatives(f) if debug: print(('g', g)) gv = g(self.x, self.mapping) assert abs(gv - value) < 1e-7 g = expand_indices(g) if debug: print(('g', g)) gv = g(self.x, self.mapping) assert abs(gv - value) < 1e-7 g = renumber_indices(g) if debug: print(('g', g)) gv = g(self.x, self.mapping) assert abs(gv - value) < 1e-7 @pytest.fixture(scope="module") def fixt(): # Workaround for quick pytest transition return Fixture() def test_basic_expand_indices(self, fixt): sf = fixt.sf vf = fixt.vf tf = fixt.tf compare = fixt.compare # Simple expressions with no indices or derivatives to expand compare(sf, 3) compare(sf + 1, 4) compare(sf - 2.5, 0.5) compare(sf/2, 1.5) compare(sf/0.5, 6) compare(sf**2, 9) compare(sf**0.5, 3**0.5) compare(sf**3, 27) compare(0.5**sf, 0.5**3) compare(sf * (sf/6), 1.5) compare(sin(sf), math.sin(3)) compare(cos(sf), math.cos(3)) compare(exp(sf), math.exp(3)) compare(ln(sf), math.log(3)) # Simple indexing compare(vf[0], 5) compare(vf[0] + 1, 6) compare(vf[0] - 2.5, 2.5) compare(vf[0]/2, 2.5) compare(vf[0]/0.5, 10) compare(vf[0]**2, 25) compare(vf[0]**0.5, 5**0.5) compare(vf[0]**3, 125) compare(0.5**vf[0], 0.5**5) compare(vf[0] * (vf[0]/6), 5*(5./6)) compare(sin(vf[0]), math.sin(5)) compare(cos(vf[0]), math.cos(5)) compare(exp(vf[0]), math.exp(5)) compare(ln(vf[0]), math.log(5)) # Double indexing compare(tf[1, 1], 19) compare(tf[1, 1] + 1, 20) compare(tf[1, 1] - 2.5, 16.5) compare(tf[1, 1]/2, 9.5) compare(tf[1, 1]/0.5, 38) compare(tf[1, 1]**2, 19**2) compare(tf[1, 1]**0.5, 19**0.5) compare(tf[1, 1]**3, 19**3) compare(0.5**tf[1, 1], 0.5**19) compare(tf[1, 1] * (tf[1, 1]/6), 19*(19./6)) compare(sin(tf[1, 1]), math.sin(19)) compare(cos(tf[1, 1]), math.cos(19)) compare(exp(tf[1, 1]), math.exp(19)) compare(ln(tf[1, 1]), math.log(19)) def test_expand_indices_index_sum(self, fixt): sf = fixt.sf vf = fixt.vf tf = fixt.tf compare = fixt.compare # Basic index sums compare(vf[i]*vf[i], 5*5+7*7) compare(vf[j]*tf[i, j]*vf[i], 5*5*11 + 5*7*13 + 5*7*17 + 7*7*19) compare(vf[j]*tf.T[j, i]*vf[i], 5*5*11 + 5*7*13 + 5*7*17 + 7*7*19) compare(tf[i, i], 11 + 19) compare(tf[i, j]*(tf[j, i]+outer(vf, vf)[i, j]), (5*5+11)*11 + (7*5+17)*13 + (7*5+13)*17 + (7*7+19)*19) compare( as_tensor( as_tensor(tf[i, j], (i, j))[k, l], (l, k) )[i, i], 11 + 19 ) def test_expand_indices_derivatives(self, fixt): sf = fixt.sf vf = fixt.vf tf = fixt.tf compare = fixt.compare # Basic derivatives compare(sf.dx(0), 0.3) compare(sf.dx(1), 0.31) compare(sf.dx(i)*vf[i], 0.30*5 + 0.31*7) compare(vf[j].dx(i)*vf[i].dx(j), 0.50*0.50 + 0.51*0.70 + 0.70*0.51 + 0.71*0.71) def test_expand_indices_hyperelasticity(self, fixt): sf = fixt.sf vf = fixt.vf tf = fixt.tf compare = fixt.compare # Deformation gradient I = Identity(2) u = vf F = I + grad(u) # F = (1 + vf[0].dx(0), vf[0].dx(1), vf[1].dx(0), 1 + vf[1].dx(1)) # F = (1 + 0.50, 0.51, 0.70, 1 + 0.71) F00 = 1 + 0.50 F01 = 0.51 F10 = 0.70 F11 = 1 + 0.71 compare(F[0, 0], F00) compare(F[0, 1], F01) compare(F[1, 0], F10) compare(F[1, 1], F11) J = det(F) compare(J, (1 + 0.50)*(1 + 0.71) - 0.70*0.51) # Strain tensors C = F.T*F # Cij = sum_k Fki Fkj C00 = F00*F00 + F10*F10 C01 = F00*F01 + F10*F11 C10 = F01*F00 + F11*F10 C11 = F01*F01 + F11*F11 compare(C[0, 0], C00) compare(C[0, 1], C01) compare(C[1, 0], C10) compare(C[1, 1], C11) E = (C-I)/2 E00 = (C00-1)/2 E01 = (C01 )/2 E10 = (C10 )/2 E11 = (C11-1)/2 compare(E[0, 0], E00) compare(E[0, 1], E01) compare(E[1, 0], E10) compare(E[1, 1], E11) # Strain energy Q = inner(E, E) Qvalue = E00**2 + E01**2 + E10**2 + E11**2 compare(Q, Qvalue) K = 0.5 psi = (K/2)*exp(Q) compare(psi, 0.25*math.exp(Qvalue)) def test_expand_indices_div_grad(self, fixt): sf = fixt.sf sf2 = fixt.sf2 vf = fixt.vf tf = fixt.tf compare = fixt.compare a = div(grad(sf)) compare(a, 0) a = div(grad(sf2)) compare(a, 3.300 + 3.311) if 0: Dvf = grad(vf) Lvf = div(Dvf) Lvf2 = dot(Lvf, Lvf) pp = compute_form_data(Lvf2*dx).preprocessed_form.integrals()[0].integrand() print(('vf', vf.ufl_shape, str(vf))) print(('Dvf', Dvf.ufl_shape, str(Dvf))) print(('Lvf', Lvf.ufl_shape, str(Lvf))) print(('Lvf2', Lvf2.ufl_shape, str(Lvf2))) print(('pp', pp.ufl_shape, str(pp))) a = div(grad(vf)) compare(dot(a, a), (0.20+0.40)**2 + (0.21+0.41)**2) a = div(grad(tf)) compare(inner(a, a), (10.00+11.00)**2 + (10.01+11.01)**2 + (10.10+11.10)**2 + (10.11+11.11)**2) def test_expand_indices_nabla_div_grad(self, fixt): sf = fixt.sf sf2 = fixt.sf2 vf = fixt.vf tf = fixt.tf compare = fixt.compare a = nabla_div(nabla_grad(sf)) compare(a, 0) a = nabla_div(nabla_grad(sf2)) compare(a, 3.300 + 3.311) a = nabla_div(nabla_grad(vf)) compare(dot(a, a), (0.20+0.40)**2 + (0.21+0.41)**2) a = nabla_div(nabla_grad(tf)) compare(inner(a, a), (10.00+11.00)**2 + (10.01+11.01)**2 + (10.10+11.10)**2 + (10.11+11.11)**2) def xtest_expand_indices_list_tensor_problem(self, fixt): print() print(('='*40)) # TODO: This is the case marked in the expand_indices2 implementation #as not working. Fix and then try expand_indices2 on other tests! V = VectorElement("CG", triangle, 1) w = Coefficient(V) v = as_vector([w[0], 0]) a = v[i]*w[i] # TODO: Compare print((type(a), str(a))) A, comp = a.ufl_operands print((type(A), str(A))) print((type(comp), str(comp))) ei1 = expand_indices(a) ei2 = expand_indices2(a) print((str(ei1))) print((str(ei2))) print(('='*40)) print() ufl-1.6.0/test/test_ffcforms.py000077500000000000000000000224451255567402100165220ustar00rootroot00000000000000#!/usr/bin/env py.test """Unit tests including all demo forms from FFC 0.5.0. The forms are modified (with comments) to work with the UFL notation which differs from the FFC notation in some places.""" __author__ = "Anders Logg (logg@simula.no) et al." __date__ = "2008-04-09 -- 2008-09-26" __copyright__ = "Copyright (C) 2008 Anders Logg et al." __license__ = "GNU GPL version 3 or any later version" # Examples copied from the FFC demo directory, examples contributed # by Johan Jansson, Kristian Oelgaard, Marie Rognes, and Garth Wells. import pytest from ufl import * def testConstant(): element = FiniteElement("Lagrange", "triangle", 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) c = Constant("triangle") d = VectorConstant("triangle") a = c * dot(grad(v), grad(u)) * dx # FFC notation: L = dot(d, grad(v))*dx L = inner(d, grad(v)) * dx def testElasticity(): element = VectorElement("Lagrange", "tetrahedron", 1) v = TestFunction(element) u = TrialFunction(element) def eps(v): # FFC notation: return grad(v) + transp(grad(v)) return grad(v) + (grad(v)).T # FFC notation: a = 0.25*dot(eps(v), eps(u))*dx a = 0.25 * inner(eps(v), eps(u)) * dx def testEnergyNorm(): element = FiniteElement("Lagrange", "tetrahedron", 1) v = Coefficient(element) a = (v * v + dot(grad(v), grad(v))) * dx def testEquation(): element = FiniteElement("Lagrange", "triangle", 1) k = 0.1 v = TestFunction(element) u = TrialFunction(element) u0 = Coefficient(element) F = v * (u - u0) * dx + k * dot(grad(v), grad(0.5 * (u0 + u))) * dx a = lhs(F) L = rhs(F) def testFunctionOperators(): element = FiniteElement("Lagrange", "triangle", 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) g = Coefficient(element) # FFC notation: a = sqrt(1/modulus(1/f))*sqrt(g)*dot(grad(v), grad(u))*dx # + v*u*sqrt(f*g)*g*dx a = sqrt(1 / abs(1 / f)) * sqrt(g) * \ dot(grad(v), grad(u)) * dx + v * u * sqrt(f * g) * g * dx def testHeat(): element = FiniteElement("Lagrange", "triangle", 1) v = TestFunction(element) u1 = TrialFunction(element) u0 = Coefficient(element) c = Coefficient(element) f = Coefficient(element) k = Constant("triangle") a = v * u1 * dx + k * c * dot(grad(v), grad(u1)) * dx L = v * u0 * dx + k * v * f * dx def testMass(): element = FiniteElement("Lagrange", "tetrahedron", 3) v = TestFunction(element) u = TrialFunction(element) a = v * u * dx def testMixedMixedElement(): P3 = FiniteElement("Lagrange", "triangle", 3) element = (P3 * P3) * (P3 * P3) def testMixedPoisson(): q = 1 BDM = FiniteElement("Brezzi-Douglas-Marini", "triangle", q) DG = FiniteElement("Discontinuous Lagrange", "triangle", q - 1) mixed_element = BDM * DG (tau, w) = TestFunctions(mixed_element) (sigma, u) = TrialFunctions(mixed_element) f = Coefficient(DG) a = (dot(tau, sigma) - div(tau) * u + w * div(sigma)) * dx L = w * f * dx def testNavierStokes(): element = VectorElement("Lagrange", "tetrahedron", 1) v = TestFunction(element) u = TrialFunction(element) w = Coefficient(element) # FFC notation: a = v[i]*w[j]*D(u[i], j)*dx a = v[i] * w[j] * Dx(u[i], j) * dx def testNeumannProblem(): element = VectorElement("Lagrange", "triangle", 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) g = Coefficient(element) # FFC notation: a = dot(grad(v), grad(u))*dx a = inner(grad(v), grad(u)) * dx # FFC notation: L = dot(v, f)*dx + dot(v, g)*ds L = inner(v, f) * dx + inner(v, g) * ds def testOptimization(): element = FiniteElement("Lagrange", "triangle", 3) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) a = dot(grad(v), grad(u)) * dx L = v * f * dx def testP5tet(): element = FiniteElement("Lagrange", tetrahedron, 5) def testP5tri(): element = FiniteElement("Lagrange", triangle, 5) def testPoissonDG(): element = FiniteElement("Discontinuous Lagrange", triangle, 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) n = FacetNormal(triangle) # FFC notation: h = MeshSize("triangle"), not supported by UFL h = Constant(triangle) gN = Coefficient(element) alpha = 4.0 gamma = 8.0 # FFC notation # a = dot(grad(v), grad(u))*dx \ # - dot(avg(grad(v)), jump(u, n))*dS \ # - dot(jump(v, n), avg(grad(u)))*dS \ # + alpha/h('+')*dot(jump(v, n), jump(u, n))*dS \ # - dot(grad(v), mult(u,n))*ds \ # - dot(mult(v,n), grad(u))*ds \ # + gamma/h*v*u*ds a = inner(grad(v), grad(u)) * dx \ - inner(avg(grad(v)), jump(u, n)) * dS \ - inner(jump(v, n), avg(grad(u))) * dS \ + alpha / h('+') * dot(jump(v, n), jump(u, n)) * dS \ - inner(grad(v), u * n) * ds \ - inner(u * n, grad(u)) * ds \ + gamma / h * v * u * ds L = v * f * dx + v * gN * ds def testPoisson(): element = FiniteElement("Lagrange", "triangle", 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) # Note: inner() also works a = dot(grad(v), grad(u)) * dx L = v * f * dx def testPoissonSystem(): element = VectorElement("Lagrange", "triangle", 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) # FFC notation: a = dot(grad(v), grad(u))*dx a = inner(grad(v), grad(u)) * dx # FFC notation: L = dot(v, f)*dx L = inner(v, f) * dx def testProjection(): # Projections are not supported by UFL and have been broken # in FFC for a while. For DOLFIN, the current (global) L^2 # projection can be extended to handle also local projections. P0 = FiniteElement("Discontinuous Lagrange", "triangle", 0) P1 = FiniteElement("Lagrange", "triangle", 1) P2 = FiniteElement("Lagrange", "triangle", 2) v = TestFunction(P1) f = Coefficient(P1) # pi0 = Projection(P0) # pi1 = Projection(P1) # pi2 = Projection(P2) # # a = v*(pi0(f) + pi1(f) + pi2(f))*dx def testQuadratureElement(): element = FiniteElement("Lagrange", "triangle", 2) # FFC notation: # QE = QuadratureElement("triangle", 3) # sig = VectorQuadratureElement("triangle", 3) QE = FiniteElement("Quadrature", "triangle", 3) sig = VectorElement("Quadrature", "triangle", 3) v = TestFunction(element) u = TrialFunction(element) u0 = Coefficient(element) C = Coefficient(QE) sig0 = Coefficient(sig) f = Coefficient(element) a = v.dx(i) * C * u.dx(i) * dx + v.dx(i) * 2 * u0 * u * u0.dx(i) * dx L = v * f * dx - dot(grad(v), sig0) * dx def testStokes(): # UFLException: Shape mismatch in sum. P2 = VectorElement("Lagrange", "triangle", 2) P1 = FiniteElement("Lagrange", "triangle", 1) TH = P2 * P1 (v, q) = TestFunctions(TH) (u, p) = TrialFunctions(TH) f = Coefficient(P2) # FFC notation: # a = (dot(grad(v), grad(u)) - div(v)*p + q*div(u))*dx a = (inner(grad(v), grad(u)) - div(v) * p + q * div(u)) * dx L = dot(v, f) * dx def testSubDomain(): element = FiniteElement("CG", "tetrahedron", 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) M = f * dx(2) + f * ds(5) def testSubDomains(): element = FiniteElement("CG", "tetrahedron", 1) v = TestFunction(element) u = TrialFunction(element) a = v * u * dx(0) + 10.0 * v * u * dx(1) + v * u * ds(0) + 2.0 * v * u * \ ds(1) + v('+') * u('+') * dS(0) + 4.3 * v('+') * u('+') * dS(1) def testTensorWeightedPoisson(): # FFC notation: # P1 = FiniteElement("Lagrange", "triangle", 1) # P0 = FiniteElement("Discontinuous Lagrange", "triangle", 0) # # v = TestFunction(P1) # u = TrialFunction(P1) # f = Coefficient(P1) # # c00 = Coefficient(P0) # c01 = Coefficient(P0) # c10 = Coefficient(P0) # c11 = Coefficient(P0) # # C = [[c00, c01], [c10, c11]] # # a = dot(grad(v), mult(C, grad(u)))*dx P1 = FiniteElement("Lagrange", "triangle", 1) P0 = TensorElement("Discontinuous Lagrange", "triangle", 0, shape=(2, 2)) v = TestFunction(P1) u = TrialFunction(P1) C = Coefficient(P0) a = inner(grad(v), C * grad(u)) * dx def testVectorLaplaceGradCurl(): def HodgeLaplaceGradCurl(element, felement): (tau, v) = TestFunctions(element) (sigma, u) = TrialFunctions(element) f = Coefficient(felement) # FFC notation: a = (dot(tau, sigma) - dot(grad(tau), u) + dot(v, # grad(sigma)) + dot(curl(v), curl(u)))*dx a = (inner(tau, sigma) - inner(grad(tau), u) + inner(v, grad(sigma)) + inner(curl(v), curl(u))) * dx # FFC notation: L = dot(v, f)*dx L = inner(v, f) * dx return [a, L] shape = "tetrahedron" order = 1 GRAD = FiniteElement("Lagrange", shape, order) # FFC notation: CURL = FiniteElement("Nedelec", shape, order-1) CURL = FiniteElement("N1curl", shape, order) VectorLagrange = VectorElement("Lagrange", shape, order + 1) [a, L] = HodgeLaplaceGradCurl(GRAD * CURL, VectorLagrange) ufl-1.6.0/test/test_form.py000077500000000000000000000063541255567402100156610ustar00rootroot00000000000000import pytest from ufl import * @pytest.fixture def element(): cell = triangle domain = Domain(cell) element = FiniteElement("Lagrange", domain, 1) return element @pytest.fixture def mass(): cell = triangle domain = Domain(cell) element = FiniteElement("Lagrange", domain, 1) v = TestFunction(element) u = TrialFunction(element) return u * v * dx @pytest.fixture def stiffness(): cell = triangle domain = Domain(cell) element = FiniteElement("Lagrange", domain, 1) v = TestFunction(element) u = TrialFunction(element) return inner(grad(u), grad(v)) * dx @pytest.fixture def convection(): cell = triangle domain = Domain(cell) element = VectorElement("Lagrange", domain, 1) v = TestFunction(element) u = TrialFunction(element) w = Coefficient(element) return dot(dot(w, nabla_grad(u)), v) * dx @pytest.fixture def load(): cell = triangle domain = Domain(cell) element = FiniteElement("Lagrange", domain, 1) f = Coefficient(element) v = TestFunction(element) return f * v * dx @pytest.fixture def boundary_load(): cell = triangle domain = Domain(cell) element = FiniteElement("Lagrange", domain, 1) f = Coefficient(element) v = TestFunction(element) return f * v * ds def test_form_arguments(mass, stiffness, convection, load): v, u = mass.arguments() f, = load.coefficients() assert v.number() == 0 assert u.number() == 1 assert stiffness.arguments() == (v, u) assert load.arguments() == (v,) assert (v * dx).arguments() == (v,) assert (v * dx + v * ds).arguments() == (v,) assert (v * dx + f * v * ds).arguments() == (v,) assert (u * v * dx(1) + v * u * dx(2)).arguments() == (v, u) assert ((f * v) * u * dx + (u * 3) * (v / 2) * dx(2)).arguments() == (v, u) def test_form_coefficients(element): v = TestFunction(element) f = Coefficient(element) g = Coefficient(element) assert (g * dx).coefficients() == (g,) assert (g * dx + g * ds).coefficients() == (g,) assert (g * dx + f * ds).coefficients() == (f, g) assert (g * dx(1) + f * dx(2)).coefficients() == (f, g) assert (g * v * dx + f * v * dx(2)).coefficients() == (f, g) def test_form_domains(): cell = triangle domain = Domain(cell) element = FiniteElement("Lagrange", domain, 1) v = TestFunction(element) f = Coefficient(element) x = SpatialCoordinate(domain)[0] assert (x * dx).domains() == (domain,) assert (v * dx).domains() == (domain,) assert (f * dx).domains() == (domain,) assert (x * v * f * dx).domains() == (domain,) assert (1 * dx(domain)).domains() == (domain,) def test_form_empty(mass): assert not mass.empty() assert Form([]).empty() def form_integrals(mass, boundary_load): assert isinstance(mass.integrals(), tuple) assert len(mass.integrals()) == 1 assert mass.integrals()[0].integral_type() == "cell" assert mass.integrals_by_type("cell") == mass.integrals() assert mass.integrals_by_type("exterior_facet") == () assert isinstance(boundary_load.integrals_by_type("cell"), tuple) assert len(boundary_load.integrals_by_type("cell")) == 0 assert len(boundary_load.integrals_by_type("exterior_facet")) == 1 ufl-1.6.0/test/test_future_division.py000077500000000000000000000016131255567402100201250ustar00rootroot00000000000000#!/usr/bin/env py.test # This file must be separate from the other arithmetic # tests to test the effect of this future statment from __future__ import division import pytest from ufl import * from ufl.classes import Division, FloatValue, IntValue def test_future_true_float_division(self): d = as_ufl(20.0) / 10.0 self.assertIsInstance(d, FloatValue) assert float(d) == 2 def test_future_true_int_division(self): # UFL treats all divisions as true division d = as_ufl(40) / 7 self.assertIsInstance(d, FloatValue) assert float(d) == 40.0 / 7.0 #self.assertAlmostEqual(float(d), 40 / 7.0, 15) def test_future_floor_division_fails(self): f = as_ufl(2.0) r = as_ufl(4) s = as_ufl(5) self.assertRaises(NotImplementedError, lambda: r // 4) self.assertRaises(NotImplementedError, lambda: r // s) self.assertRaises(NotImplementedError, lambda: f // s) ufl-1.6.0/test/test_grad.py000077500000000000000000000076771255567402100156440ustar00rootroot00000000000000#!/usr/bin/env py.test """ Test use of grad in various situations. """ import pytest # This imports everything external code will see from ufl from ufl import * #from ufl.classes import ... from ufl.algorithms import compute_form_data def xtest_grad_div_curl_properties_in_1D(self): _test_grad_div_curl_properties(self, interval) def xtest_grad_div_curl_properties_in_2D(self): _test_grad_div_curl_properties(self, triangle) def xtest_grad_div_curl_properties_in_3D(self): _test_grad_div_curl_properties(self, tetrahedron) def _test_grad_div_curl_properties(self, cell): d = cell.geometric_dimension() S = FiniteElement("CG", cell, 1) V = VectorElement("CG", cell, 1) T = TensorElement("CG", cell, 1) cs = Constant(cell) cv = VectorConstant(cell) ct = TensorConstant(cell) s = Coefficient(S) v = Coefficient(V) t = Coefficient(T) def eval_s(x, derivatives=()): return sum(derivatives) def eval_v(x, derivatives=()): return tuple(float(k)+sum(derivatives) for k in range(d)) def eval_t(x, derivatives=()): return tuple(tuple(float(i*j)+sum(derivatives) for i in range(d)) for j in range(d)) mapping = { cs: eval_s, s: eval_s, cv: eval_v, v: eval_v, ct: eval_t, t: eval_t, } x = tuple(1.0+float(k) for k in range(d)) assert s.ufl_shape == () assert v.ufl_shape == (d,) assert t.ufl_shape == (d, d) assert cs.ufl_shape == () assert cv.ufl_shape == (d,) assert ct.ufl_shape == (d, d) self.assertEqual(s(x, mapping=mapping), eval_s(x)) self.assertEqual(v(x, mapping=mapping), eval_v(x)) self.assertEqual(t(x, mapping=mapping), eval_t(x)) assert grad(s).ufl_shape == (d,) assert grad(v).ufl_shape == (d, d) assert grad(t).ufl_shape == (d, d, d) assert grad(cs).ufl_shape == (d,) assert grad(cv).ufl_shape == (d, d) assert grad(ct).ufl_shape == (d, d, d) self.assertEqual(grad(s)[0](x, mapping=mapping), eval_s(x, (0,))) self.assertEqual(grad(v)[d-1, d-1](x, mapping=mapping), eval_v(x, derivatives=(d-1,))[d-1]) self.assertEqual(grad(t)[d-1, d-1, d-1](x, mapping=mapping), eval_t(x, derivatives=(d-1,))[d-1][d-1]) assert div(grad(cs)).ufl_shape == () assert div(grad(cv)).ufl_shape == (d,) assert div(grad(ct)).ufl_shape == (d, d) assert s.dx(0).ufl_shape == () assert v.dx(0).ufl_shape == (d,) assert t.dx(0).ufl_shape == (d, d) assert s.dx(0 == 0).ufl_shape, () assert v.dx(0 == 0).ufl_shape, (d,) assert t.dx(0 == 0).ufl_shape, (d, d) i, j = indices(2) assert s.dx(i).ufl_shape == () assert v.dx(i).ufl_shape == (d,) assert t.dx(i).ufl_shape == (d, d) assert s.dx(i).free_indices() == (i,) assert v.dx(i).free_indices() == (i,) assert t.dx(i).free_indices() == (i,) self.assertEqual(s.dx(i, j).ufl_shape, ()) self.assertEqual(v.dx(i, j).ufl_shape, (d,)) self.assertEqual(t.dx(i, j).ufl_shape, (d, d)) # This comparison is unstable w.r.t. sorting of i,j self.assertTrue(s.dx(i, j).free_indices() in [(i, j), (j, i)]) self.assertTrue(v.dx(i, j).free_indices() in [(i, j), (j, i)]) self.assertTrue(t.dx(i, j).free_indices() in [(i, j), (j, i)]) a0 = s.dx(0)*dx a1 = s.dx(0)**2*dx a2 = v.dx(0)**2*dx a3 = t.dx(0)**2*dx a4 = inner(grad(s), grad(s))*dx a5 = inner(grad(v), grad(v))*dx a6 = inner(grad(t), grad(t))*dx a7 = inner(div(grad(s)), s)*dx a8 = inner(div(grad(v)), v)*dx a9 = inner(div(grad(t)), t)*dx fd0 = compute_form_data(a0) fd1 = compute_form_data(a1) fd2 = compute_form_data(a2) fd3 = compute_form_data(a3) fd4 = compute_form_data(a4) fd5 = compute_form_data(a5) fd6 = compute_form_data(a6) fd7 = compute_form_data(a7) fd8 = compute_form_data(a8) fd9 = compute_form_data(a9) #self.assertTrue(False) # Just to show it runs ufl-1.6.0/test/test_illegal.py000077500000000000000000000024261255567402100163230ustar00rootroot00000000000000#!/usr/bin/env py.test import pytest from ufl import * from ufl.algorithms import * # TODO: Add more illegal expressions to check! def selement(): return FiniteElement("Lagrange", "triangle", 1) def velement(): return VectorElement("Lagrange", "triangle", 1) @pytest.fixture def a(): return Argument(selement(), 2) @pytest.fixture def b(): return Argument(selement(), 3) @pytest.fixture def v(): return Argument(velement(), 4) @pytest.fixture def u(): return Argument(velement(), 5) @pytest.fixture def f(): return Coefficient(selement()) @pytest.fixture def g(): return Coefficient(selement()) @pytest.fixture def vf(): return Coefficient(velement()) @pytest.fixture def vg(): return Coefficient(velement()) def test_mul_v_u(v, u): with pytest.raises(UFLException): v * u def test_mul_vf_u(vf, u): with pytest.raises(UFLException): vf * u def test_mul_vf_vg(vf, vg): with pytest.raises(UFLException): vf * vg def test_add_a_v(a, v): with pytest.raises(UFLException): a + v def test_add_vf_b(vf, b): with pytest.raises(UFLException): vf + b def test_add_vectorexpr_b(vg, v, u, vf, b): tmp = vg + v + u + vf with pytest.raises(UFLException): tmp + b ufl-1.6.0/test/test_indexing.py000077500000000000000000000034171255567402100165200ustar00rootroot00000000000000 import pytest from ufl import * from ufl.classes import * @pytest.fixture def x1(): x = SpatialCoordinate(triangle) return x @pytest.fixture def x2(): x = x1() return outer(x, x) @pytest.fixture def x3(): x = x1() return outer(outer(x, x), x) def test_annotated_literals(): z = Zero(()) assert z.ufl_shape == () assert z.ufl_free_indices == () assert z.ufl_index_dimensions == () assert z.free_indices() == () assert z.index_dimensions() == {} z = Zero((3,)) assert z.ufl_shape == (3,) assert z.ufl_free_indices == () assert z.ufl_index_dimensions == () assert z.free_indices() == () assert z.index_dimensions() == {} i = Index(count=2) j = Index(count=4) # z = Zero((), (2, 4), (3, 5)) z = Zero((), (j, i), {i: 3, j: 5}) assert z.ufl_shape == () assert z.free_indices() == (i, j) assert z.index_dimensions() == {i: 3, j: 5} assert z.ufl_free_indices == (2, 4) assert z.ufl_index_dimensions == (3, 5) def test_fixed_indexing_of_expression(x1, x2, x3): x0 = x1[0] x00 = x2[0, 0] x000 = x3[0, 0, 0] assert isinstance(x0, Indexed) assert isinstance(x00, Indexed) assert isinstance(x000, Indexed) assert isinstance(x0.ufl_operands[0], SpatialCoordinate) assert isinstance(x00.ufl_operands[0], Outer) assert isinstance(x000.ufl_operands[0], Outer) assert isinstance(x0.ufl_operands[1], MultiIndex) assert isinstance(x00.ufl_operands[1], MultiIndex) assert isinstance(x000.ufl_operands[1], MultiIndex) mi = x000.ufl_operands[1] assert len(mi) == 3 assert mi.indices() == (FixedIndex(0),) * 3 def test_indexed(): pass def test_indexsum(): pass def test_componenttensor(): pass def test_tensoralgebra(): pass ufl-1.6.0/test/test_indices.py000077500000000000000000000154161255567402100163330ustar00rootroot00000000000000#!/usr/bin/env py.test import pytest from ufl import * #from ufl.indexutils import * from ufl.algorithms import * from ufl.classes import IndexSum # TODO: add more expressions to test as many possible combinations of index notation as feasible... def xtest_index_utils(self): ii = indices(3) assert ii == unique_indices(ii) assert ii == unique_indices(ii+ii) assert () == repeated_indices(ii) assert ii == repeated_indices(ii+ii) assert ii == shared_indices(ii, ii) assert ii == shared_indices(ii, ii+ii) assert ii == shared_indices(ii+ii, ii) assert ii == shared_indices(ii+ii, ii+ii) assert ii == single_indices(ii) assert () == single_indices(ii+ii) def test_vector_indices(self): element = VectorElement("CG", "triangle", 1) u = Argument(element, 2) f = Coefficient(element) a = u[i]*f[i]*dx b = u[j]*f[j]*dx def test_tensor_indices(self): element = TensorElement("CG", "triangle", 1) u = Argument(element, 2) f = Coefficient(element) a = u[i, j]*f[i, j]*dx b = u[j, i]*f[i, j]*dx c = u[j, i]*f[j, i]*dx with pytest.raises(UFLException): d = (u[i, i]+f[j, i])*dx def test_indexed_sum1(self): element = VectorElement("CG", "triangle", 1) u = Argument(element, 2) f = Coefficient(element) a = u[i]+f[i] with pytest.raises(UFLException): a*dx def test_indexed_sum2(self): element = VectorElement("CG", "triangle", 1) v = Argument(element, 2) u = Argument(element, 3) f = Coefficient(element) a = u[j]+f[j]+v[j]+2*v[j]+exp(u[i]*u[i])/2*f[j] with pytest.raises(UFLException): a*dx def test_indexed_sum3(self): element = VectorElement("CG", "triangle", 1) u = Argument(element, 2) f = Coefficient(element) with pytest.raises(UFLException): a = u[i]+f[j] def test_indexed_function1(self): element = VectorElement("CG", "triangle", 1) v = Argument(element, 2) u = Argument(element, 3) f = Coefficient(element) aarg = (u[i]+f[i])*v[i] a = exp(aarg)*dx def test_indexed_function2(self): element = VectorElement("CG", "triangle", 1) v = Argument(element, 2) u = Argument(element, 3) f = Coefficient(element) bfun = cos(f[0]) left = u[i] + f[i] right = v[i] * bfun assert len(left.free_indices()) == 1 assert left.free_indices()[0] == i assert len(right.free_indices()) == 1 assert right.free_indices()[0] == i b = left * right * dx def test_indexed_function3(self): element = VectorElement("CG", "triangle", 1) v = Argument(element, 2) u = Argument(element, 3) f = Coefficient(element) with pytest.raises(UFLException): c = sin(u[i] + f[i])*dx def test_vector_from_indices(self): element = VectorElement("CG", "triangle", 1) v = TestFunction(element) u = TrialFunction(element) # legal vv = as_vector(u[i], i) uu = as_vector(v[j], j) w = v + u ww = vv + uu assert vv.rank() == 1 assert uu.rank() == 1 assert w.rank() == 1 assert ww.rank() == 1 def test_matrix_from_indices(self): element = VectorElement("CG", "triangle", 1) v = TestFunction(element) u = TrialFunction(element) A = as_matrix(u[i]*v[j], (i, j)) B = as_matrix(v[k]*v[k]*u[i]*v[j], (j, i)) C = A + A C = B + B D = A + B assert A.rank() == 2 assert B.rank() == 2 assert C.rank() == 2 assert D.rank() == 2 def test_vector_from_list(self): element = VectorElement("CG", "triangle", 1) v = TestFunction(element) u = TrialFunction(element) # create vector from list vv = as_vector([u[0], v[0]]) ww = vv + vv assert vv.rank() == 1 assert ww.rank() == 1 def test_matrix_from_list(self): element = VectorElement("CG", "triangle", 1) v = TestFunction(element) u = TrialFunction(element) # create matrix from list A = as_matrix( [ [u[0], u[1]], [v[0], v[1]] ] ) # create matrix from indices B = as_matrix( (v[k]*v[k]) * u[i]*v[j], (j, i) ) # Test addition C = A + A C = B + B D = A + B assert A.rank() == 2 assert B.rank() == 2 assert C.rank() == 2 assert D.rank() == 2 def test_tensor(self): element = VectorElement("CG", "triangle", 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) g = Coefficient(element) # define the components of a fourth order tensor Cijkl = u[i]*v[j]*f[k]*g[l] assert Cijkl.rank() == 0 assert set(Cijkl.free_indices()) == {i, j, k, l} # make it a tensor C = as_tensor(Cijkl, (i, j, k, l)) assert C.rank() == 4 self.assertSameIndices(C, ()) # get sub-matrix A = C[:,:, 0, 0] assert A.rank() == 2 self.assertSameIndices(A, ()) A = C[:,:, i, j] assert A.rank() == 2 assert set(A.free_indices()) == {i, j} # legal? vv = as_vector([u[i], v[i]]) ww = f[i]*vv # this is well defined: ww = sum_i # illegal with pytest.raises(UFLException): vv = as_vector([u[i], v[j]]) # illegal with pytest.raises(UFLException): A = as_matrix( [ [u[0], u[1]], [v[0],] ] ) # ... def test_indexed(self): element = VectorElement("CG", "triangle", 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) i, j, k, l = indices(4) a = v[i] self.assertSameIndices(a, (i,)) a = outer(v, u)[i, j] self.assertSameIndices(a, (i, j)) a = outer(v, u)[i, i] self.assertSameIndices(a, ()) self.assertIsInstance(a, IndexSum) def test_spatial_derivative(self): cell = triangle element = VectorElement("CG", cell, 1) v = TestFunction(element) u = TrialFunction(element) i, j, k, l = indices(4) d = cell.geometric_dimension() a = v[i].dx(i) self.assertSameIndices(a, ()) self.assertIsInstance(a, IndexSum) assert a.ufl_shape == () a = v[i].dx(j) self.assertSameIndices(a, (i, j)) self.assertNotIsInstance(a, IndexSum) assert a.ufl_shape == () a = (v[i]*u[j]).dx(i, j) self.assertSameIndices(a, ()) self.assertIsInstance(a, IndexSum) assert a.ufl_shape == () a = v.dx(i, j) #self.assertSameIndices(a, (i,j)) assert set(a.free_indices()) == {j, i} self.assertNotIsInstance(a, IndexSum) assert a.ufl_shape == (d,) a = v[i].dx(0) self.assertSameIndices(a, (i,)) self.assertNotIsInstance(a, IndexSum) assert a.ufl_shape == () a = (v[i]*u[j]).dx(0, 1) # indices change place because of sorting, I guess this may be ok assert set(a.free_indices()) == {i, j} self.assertNotIsInstance(a, IndexSum) assert a.ufl_shape == () a = v.dx(i)[i] self.assertSameIndices(a, ()) self.assertIsInstance(a, IndexSum) assert a.ufl_shape == () def test_renumbering(self): pass ufl-1.6.0/test/test_lhs_rhs.py000077500000000000000000000031431255567402100163510ustar00rootroot00000000000000#!/usr/bin/env py.test __authors__ = "Marie E. Rognes" # First added: 2011-11-09 # Last changed: 2011-11-09 import pytest from ufl import * def test_lhs_rhs_simple(): V = FiniteElement("CG", interval, 1) v = TestFunction(V) u = TrialFunction(V) w = Argument(V, 2) # This was 0, not sure why f = Coefficient(V) F0 = f * u * v * w * dx a, L = system(F0) assert(len(a.integrals()) == 0) assert(len(L.integrals()) == 0) F1 = derivative(F0, f) a, L = system(F1) assert(len(a.integrals()) == 0) assert(len(L.integrals()) == 0) F2 = action(F0, f) a, L = system(F2) assert(len(a.integrals()) == 1) assert(len(L.integrals()) == 0) F3 = action(F2, f) a, L = system(F3) assert(len(L.integrals()) == 1) def test_lhs_rhs_derivatives(): V = FiniteElement("CG", interval, 1) v = TestFunction(V) u = TrialFunction(V) f = Coefficient(V) F0 = exp(f) * u * v * dx + v * dx + f * v * ds + exp(f)('+') * v * dS a, L = system(F0) assert(len(a.integrals()) == 1) assert(len(L.integrals()) == 3) F1 = derivative(F0, f) a, L = system(F0) def test_lhs_rhs_slightly_obscure(): V = FiniteElement("CG", interval, 1) u = TrialFunction(V) w = Argument(V, 2) f = Constant(interval) # FIXME: # ufl.algorithsm.formtransformations.compute_form_with_arity # is not perfect, e.g. try # F = f*u*w*dx + f*w*dx F = f * u * w * dx a, L = system(F) assert(len(a.integrals()) == 1) assert(len(L.integrals()) == 0) F = f * w * dx a, L = system(F) assert(len(L.integrals()) == 1) ufl-1.6.0/test/test_literals.py000077500000000000000000000067231255567402100165350ustar00rootroot00000000000000#!/usr/bin/env py.test __authors__ = "Martin Sandve Alnes" __date__ = "2011-04-14 -- 2011-04-14" import pytest from ufl import * from ufl.classes import Indexed from ufl.constantvalue import Zero, FloatValue, IntValue, as_ufl def test_zero(self): z1 = Zero(()) z2 = Zero(()) z3 = as_ufl(0) z4 = as_ufl(0.0) z5 = FloatValue(0) z6 = FloatValue(0.0) #self.assertTrue(z1 is z2) #self.assertTrue(z1 is z3) #self.assertTrue(z1 is z4) #self.assertTrue(z1 is z5) #self.assertTrue(z1 is z6) assert z1 == z1 assert int(z1) == 0 assert float(z1) == 0.0 self.assertNotEqual(z1, 1.0) self.assertFalse(z1) # If zero() == 0 is to be allowed, it must not have the same hash or it will collide with 0 as key in dicts... self.assertNotEqual(hash(z1), hash(0.0)) self.assertNotEqual(hash(z1), hash(0)) def test_float(self): f1 = as_ufl(1) f2 = as_ufl(1.0) f3 = FloatValue(1) f4 = FloatValue(1.0) f5 = 3 - FloatValue(1) - 1 f6 = 3 * FloatValue(2) / 6 assert f1 == f1 self.assertNotEqual(f1, f2) # IntValue vs FloatValue, == compares representations! assert f2 == f3 assert f2 == f4 assert f2 == f5 assert f2 == f6 def test_int(self): f1 = as_ufl(1) f2 = as_ufl(1.0) f3 = IntValue(1) f4 = IntValue(1.0) f5 = 3 - IntValue(1) - 1 f6 = 3 * IntValue(2) / 6 assert f1 == f1 self.assertNotEqual(f1, f2) # IntValue vs FloatValue, == compares representations! assert f1 == f3 assert f1 == f4 assert f1 == f5 assert f2 == f6 # Division produces a FloatValue def test_scalar_sums(self): n = 10 s = [as_ufl(i) for i in range(n)] for i in range(n): self.assertNotEqual(s[i], i+1) for i in range(n): assert s[i] == i for i in range(n): assert 0 + s[i] == i for i in range(n): assert s[i] + 0 == i for i in range(n): assert 0 + s[i] + 0 == i for i in range(n): assert 1 + s[i] - 1 == i assert s[1] + s[1] == 2 assert s[1] + s[2] == 3 assert s[1] + s[2] + s[3] == s[6] assert s[5] - s[2] == 3 assert 1*s[5] == 5 assert 2*s[5] == 10 assert s[6]/3 == 2 def test_identity(self): pass # FIXME def test_permutation_symbol_3(self): e = PermutationSymbol(3) assert e.ufl_shape == (3, 3, 3) assert eval(repr(e)) == e for i in range(3): for j in range(3): for k in range(3): value = (j-i)*(k-i)*(k-j)/2 self.assertEqual(e[i, j, k], value) i, j, k = indices(3) self.assertIsInstance(e[i, j, k], Indexed) x = (0, 0, 0) self.assertEqual((e[i, j, k] * e[i, j, k])(x), 6) def test_permutation_symbol_n(self): for n in range(2, 5): # tested with upper limit 7, but evaluation is a bit slow then e = PermutationSymbol(n) assert e.ufl_shape == (n,)*n assert eval(repr(e)) == e ii = indices(n) x = (0,)*n nfac = product(m for m in range(1, n+1)) assert (e[ii] * e[ii])(x) == nfac def test_unit_dyads(self): from ufl.tensors import unit_vectors, unit_matrices ei, ej = unit_vectors(2) self.assertEqual(as_vector((1, 0)), ei) self.assertEqual(as_vector((0, 1)), ej) eii, eij, eji, ejj = unit_matrices(2) self.assertEqual(as_matrix(((1, 0), (0, 0))), eii) self.assertEqual(as_matrix(((0, 1), (0, 0))), eij) self.assertEqual(as_matrix(((0, 0), (1, 0))), eji) self.assertEqual(as_matrix(((0, 0), (0, 1))), ejj) ufl-1.6.0/test/test_measures.py000077500000000000000000000152711255567402100165400ustar00rootroot00000000000000#!/use/bin/env py.test """ Tests of the various ways Measure objects can be created and used. """ import pytest # This imports everything external code will see from ufl from ufl import * from ufl.algorithms import compute_form_data # all_cells = (interval, triangle, tetrahedron, # quadrilateral, hexahedron) from mockobjects import MockMesh, MockMeshFunction def test_construct_forms_from_default_measures(): # Create defaults: dx = Measure("dx") #dE = Measure("dE") ds = Measure("ds") dS = Measure("dS") dP = Measure("dP") #dV = Measure("dV") dc = Measure("dc") #dC = Measure("dC") #dO = Measure("dO") #dI = Measure("dI") ds_b = Measure("ds_b") ds_t = Measure("ds_t") ds_v = Measure("ds_v") dS_h = Measure("dS_h") dS_v = Measure("dS_v") # Check that names are mapped properly assert dx.integral_type() == "cell" #assert dE.integral_type() == "macro_cell" assert ds.integral_type() == "exterior_facet" assert dS.integral_type() == "interior_facet" assert dP.integral_type() == "vertex" # TODO: Change dP to dV: #assert dP.integral_type() == "point" #assert dV.integral_type() == "vertex" assert dc.integral_type() == "custom" assert dC.integral_type() == "cutcell" assert dO.integral_type() == "overlap" assert dI.integral_type() == "interface" # TODO: Remove firedrake hacks: assert ds_b.integral_type() == "exterior_facet_bottom" assert ds_t.integral_type() == "exterior_facet_top" assert ds_v.integral_type() == "exterior_facet_vert" assert dS_h.integral_type() == "interior_facet_horiz" assert dS_v.integral_type() == "interior_facet_vert" # Check that defaults are set properly assert dx.domain() == None assert dx.metadata() == {} # Check that we can create a basic form with default measure one = as_ufl(1) a = one * dx(Domain(triangle)) def test_foo(): # Define a manifold domain, allows checking gdim/tdim mixup errors gdim = 3 tdim = 2 cell = Cell("triangle", gdim) mymesh = MockMesh(9) mydomain = Domain(cell, label="Omega", data=mymesh) assert cell.topological_dimension() == tdim assert cell.geometric_dimension() == gdim assert cell.cellname() == "triangle" assert mydomain.topological_dimension() == tdim assert mydomain.geometric_dimension() == gdim assert mydomain.cell() == cell assert mydomain.label() == "Omega" assert mydomain.data() == mymesh # Define a coefficient for use in tests below V = FiniteElement("CG", mydomain, 1) f = Coefficient(V) # Test definition of a custom measure with explicit parameters metadata = {"opt": True} mydx = Measure("dx", domain=mydomain, subdomain_id=3, metadata=metadata) assert mydx.domain().label() == mydomain.label() assert mydx.metadata() == metadata M = f * mydx # Compatibility: dx = Measure("dx") # domain=None, # subdomain_id="everywhere", # metadata=None) assert dx.domain() == None assert dx.subdomain_id() == "everywhere" # Set subdomain_id to "everywhere", still no domain set dxe = dx() assert dxe.domain() == None assert dxe.subdomain_id() == "everywhere" # Set subdomain_id to 5, still no domain set dx5 = dx(5) assert dx5.domain() == None assert dx5.subdomain_id() == 5 # Check that original dx is untouched assert dx.domain() == None assert dx.subdomain_id() == "everywhere" # Set subdomain_id to (2,3), still no domain set dx23 = dx((2, 3)) assert dx23.domain() == None assert dx23.subdomain_id(), (2 == 3) # Map metadata to metadata, ffc interprets as before dxm = dx(metadata={"dummy": 123}) # assert dxm.metadata() == {"dummy":123} assert dxm.metadata() == {"dummy": 123} # Deprecated, TODO: Remove assert dxm.domain() == None assert dxm.subdomain_id() == "everywhere" # dxm = dx(metadata={"dummy":123}) # assert dxm.metadata() == {"dummy":123} dxm = dx(metadata={"dummy": 123}) assert dxm.metadata() == {"dummy": 123} assert dxm.domain() == None assert dxm.subdomain_id() == "everywhere" dxi = dx(metadata={"quadrature_degree": 3}) # Mock some dolfin data structures dx = Measure("dx") ds = Measure("ds") dS = Measure("dS") mesh = MockMesh(8) cell_domains = MockMeshFunction(1, mesh) exterior_facet_domains = MockMeshFunction(2, mesh) interior_facet_domains = MockMeshFunction(3, mesh) assert dx[cell_domains] == dx(subdomain_data=cell_domains) assert dx[cell_domains] != dx assert dx[cell_domains] != dx[exterior_facet_domains] # Test definition of a custom measure with legacy bracket syntax dxd = dx[cell_domains] dsd = ds[exterior_facet_domains] dSd = dS[interior_facet_domains] # Current behaviour: no domain created, measure domain data is a single # object not a full dict assert dxd.domain() == None assert dsd.domain() == None assert dSd.domain() == None assert dxd.subdomain_data() is cell_domains assert dsd.subdomain_data() is exterior_facet_domains assert dSd.subdomain_data() is interior_facet_domains # Considered behaviour at one point: # assert dxd.domain().label() == "MockMesh" # assert dsd.domain().label() == "MockMesh" # assert dSd.domain().label() == "MockMesh" # assert dxd.domain().data() == { "mesh": mesh, "cell": cell_domains } # assert dsd.domain().data() == { "mesh": mesh, "exterior_facet": exterior_facet_domains } # assert dSd.domain().data() == { "mesh": mesh, "interior_facet": # interior_facet_domains } # Create some forms with these measures (used in checks below): Mx = f * dxd Ms = f ** 2 * dsd MS = f('+') * dSd M = f * dxd + f ** 2 * dsd + f('+') * dSd # Test extracting domain data from a form for each measure: domain, = Mx.domains() assert domain.label() == mydomain.label() assert domain.data() == mymesh assert Mx.subdomain_data()[mydomain]["cell"] == cell_domains domain, = Ms.domains() assert domain.data() == mymesh assert Ms.subdomain_data()[mydomain][ "exterior_facet"] == exterior_facet_domains domain, = MS.domains() assert domain.data() == mymesh assert MS.subdomain_data()[mydomain][ "interior_facet"] == interior_facet_domains # Test joining of these domains in a single form domain, = M.domains() assert domain.data() == mymesh assert M.subdomain_data()[mydomain]["cell"] == cell_domains assert M.subdomain_data()[mydomain][ "exterior_facet"] == exterior_facet_domains assert M.subdomain_data()[mydomain][ "interior_facet"] == interior_facet_domains ufl-1.6.0/test/test_mock_expr.py000077500000000000000000000043561255567402100167050ustar00rootroot00000000000000#!/usr/bin/env py.test import pytest from ufl import * from ufl.core.expr import Expr class MockExpr(Expr): "A mock type for unit testing." def __init__(self, shape=None, free_indices=None, index_dimensions=None, cell=None): Expr.__init__(self) self.fields = [] if not shape is None: self._shape = shape self.fields.append("shape") if not free_indices is None: self._free_indices = free_indices self.fields.append("free_indices") if not index_dimensions is None: self._index_dimensions = index_dimensions self.fields.append("index_dimensions") if not cell is None: self._cell = cell self.fields.append("cell") def shape(self): assert hasattr(self, "_shape") return self._shape def free_indices(self): assert hasattr(self, "_free_indices") return self._free_indices def index_dimensions(self): assert hasattr(self, "_index_dimensions") return self._index_dimensions def cell(self): assert hasattr(self, "_cell") return self._cell def matches(self, other): for field in self.fields: a = getattr(self, field)() b = getattr(other, field)() if not a == b: return False return True def __repr__(self): return "MockExpr(%s)" % ", ".join("%s=%s" % (k, repr(getattr(self, "_%s" % k))) for k in self.fields) def __iter__(self): raise NotImplementedError def test_mock_expr(): a = MockExpr(shape=(1,)) b = MockExpr(shape=(2,)) assert not a.matches(b) i, j = indices(2) c = MockExpr(shape=(1, 2), free_indices=(i, j), index_dimensions={i:2, j:3}, cell=triangle) d = MockExpr(shape=(1, 2), free_indices=(i, j), index_dimensions={i:2, j:3}, cell=triangle) assert c.matches(d) e = FiniteElement("CG", triangle, 1) f = Coefficient(e) g = MockExpr(shape=(), free_indices=(), index_dimensions={}, cell=triangle) assert g.matches(f) h = MockExpr(shape=(1,), free_indices=(), index_dimensions={}, cell=triangle) assert not h.matches(f) ufl-1.6.0/test/test_new_ad.py000077500000000000000000000136501255567402100161500ustar00rootroot00000000000000 import pytest from ufl import * from ufl.tensors import as_tensor from ufl.classes import Grad from ufl.algorithms import tree_format from ufl.algorithms import renumber_indices from ufl.algorithms.apply_derivatives import apply_derivatives, GenericDerivativeRuleset, \ GradRuleset, VariableRuleset, GateauxDerivativeRuleset # Note: the old tests in test_automatic_differentiation.py are a bit messy # but still cover many things that are not in here yet. # FIXME: Write UNIT tests for all terminal derivatives! # FIXME: Write UNIT tests for operator derivatives! def test_apply_derivatives_doesnt_change_expression_without_derivatives(): cell = triangle d = cell.geometric_dimension() V0 = FiniteElement("DG", cell, 0) V1 = FiniteElement("Lagrange", cell, 1) # Literals z = zero((3, 2)) one = as_ufl(1) two = as_ufl(2.0) I = Identity(d) literals = [z, one, two, I] # Geometry x = SpatialCoordinate(cell) n = FacetNormal(cell) volume = CellVolume(cell) geometry = [x, n, volume] # Arguments v0 = TestFunction(V0) v1 = TestFunction(V1) arguments = [v0, v1] # Coefficients f0 = Coefficient(V0) f1 = Coefficient(V1) coefficients = [f0, f1] # Expressions e0 = f0 + f1 e1 = v0 * (f1/3 - f0**2) e2 = exp(sin(cos(tan(ln(x[0]))))) expressions = [e0, e1, e2] # Check that all are unchanged for expr in literals + geometry + arguments + coefficients + expressions: # Note the use of "is" here instead of ==, this property # is important for efficiency and memory usage assert apply_derivatives(expr) is expr def test_literal_derivatives_are_zero(): cell = triangle d = cell.geometric_dimension() # Literals one = as_ufl(1) two = as_ufl(2.0) I = Identity(d) E = PermutationSymbol(d) literals = [one, two, I] # Generic ruleset handles literals directly: for l in literals: for sh in [(), (d,), (d,d+1)]: assert GenericDerivativeRuleset(sh)(l) == zero(l.ufl_shape + sh) # Variables v0 = variable(one) v1 = variable(zero((d,))) v2 = variable(I) variables = [v0, v1, v2] # Test literals via apply_derivatives and variable ruleset: for l in literals: for v in variables: assert apply_derivatives(diff(l, v)) == zero(l.ufl_shape + v.ufl_shape) V0 = FiniteElement("DG", cell, 0) V1 = FiniteElement("Lagrange", cell, 1) u0 = Coefficient(V0) u1 = Coefficient(V1) v0 = TestFunction(V0) v1 = TestFunction(V1) args = [(u0, v0), (u1, v1)] # Test literals via apply_derivatives and variable ruleset: for l in literals: for u, v in args: assert apply_derivatives(derivative(l, u, v)) == zero(l.ufl_shape + v.ufl_shape) # Test grad ruleset directly since grad(literal) is invalid: assert GradRuleset(d)(one) == zero((d,)) assert GradRuleset(d)(one) == zero((d,)) def test_grad_ruleset(): cell = triangle d = cell.geometric_dimension() V0 = FiniteElement("DG", cell, 0) V1 = FiniteElement("Lagrange", cell, 1) V2 = FiniteElement("Lagrange", cell, 2) W0 = VectorElement("DG", cell, 0) W1 = VectorElement("Lagrange", cell, 1) W2 = VectorElement("Lagrange", cell, 2) # Literals one = as_ufl(1) two = as_ufl(2.0) I = Identity(d) literals = [one, two, I] # Geometry x = SpatialCoordinate(cell) n = FacetNormal(cell) volume = CellVolume(cell) geometry = [x, n, volume] # Arguments u0 = TestFunction(V0) u1 = TestFunction(V1) arguments = [u0, u1] # Coefficients r = Constant(cell) vr = VectorConstant(cell) f0 = Coefficient(V0) f1 = Coefficient(V1) f2 = Coefficient(V2) vf0 = Coefficient(W0) vf1 = Coefficient(W1) vf2 = Coefficient(W2) coefficients = [f0, f1, vf0, vf1] # Expressions e0 = f0 + f1 e1 = u0 * (f1/3 - f0**2) e2 = exp(sin(cos(tan(ln(x[0]))))) expressions = [e0, e1, e2] # Variables v0 = variable(one) v1 = variable(f1) v2 = variable(f0*f1) variables = [v0, v1, v2] rules = GradRuleset(d) # Literals assert rules(one) == zero((d,)) assert rules(two) == zero((d,)) assert rules(I) == zero((d,d,d)) # Assumed piecewise constant geometry for g in [n, volume]: assert rules(g) == zero(g.ufl_shape + (d,)) # Non-constant geometry assert rules(x) == I # Arguments for u in arguments: assert rules(u) == grad(u) # Piecewise constant coefficients (Constant) assert rules(r) == zero((d,)) assert rules(vr) == zero((d,d)) assert rules(grad(r)) == zero((d,d)) assert rules(grad(vr)) == zero((d,d,d)) # Piecewise constant coefficients (DG0) assert rules(f0) == zero((d,)) assert rules(vf0) == zero((d,d)) assert rules(grad(f0)) == zero((d,d)) assert rules(grad(vf0)) == zero((d,d,d)) # Piecewise linear coefficients assert rules(f1) == grad(f1) assert rules(vf1) == grad(vf1) #assert rules(grad(f1)) == zero((d,d)) # TODO: Use degree to make this work #assert rules(grad(vf1)) == zero((d,d,d)) # Piecewise quadratic coefficients assert rules(grad(f2)) == grad(grad(f2)) assert rules(grad(vf2)) == grad(grad(vf2)) # Indexed coefficients assert renumber_indices(apply_derivatives(grad(vf2[0]))) == renumber_indices(grad(vf2)[0,:]) assert renumber_indices(apply_derivatives(grad(vf2[1])[0])) == renumber_indices(grad(vf2)[1,0]) # Grad of gradually more complex expressions assert apply_derivatives(grad(2*f0)) == zero((d,)) assert renumber_indices(apply_derivatives(grad(2*f1))) == renumber_indices(2*grad(f1)) assert renumber_indices(apply_derivatives(grad(sin(f1)))) == renumber_indices(cos(f1) * grad(f1)) assert renumber_indices(apply_derivatives(grad(cos(f1)))) == renumber_indices(-sin(f1) * grad(f1)) def test_variable_ruleset(): pass def test_gateaux_ruleset(): pass ufl-1.6.0/test/test_pickle.py000077500000000000000000000327401255567402100161630ustar00rootroot00000000000000#!/usr/bin/env py.test """Pickle all the unit test forms from FFC 0.5.0""" __author__ = "Anders Logg (logg@simula.no) et al." __date__ = "2008-04-09 -- 2008-09-26" __copyright__ = "Copyright (C) 2008 Anders Logg et al." __license__ = "GNU GPL version 3 or any later version" # Examples copied from the FFC demo directory, examples contributed # by Johan Jansson, Kristian Oelgaard, Marie Rognes, and Garth Wells. import pytest from ufl import * from ufl.algorithms import compute_form_data import pickle p = pickle.HIGHEST_PROTOCOL def testConstant(): element = FiniteElement("Lagrange", "triangle", 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) c = Constant("triangle") d = VectorConstant("triangle") a = c * dot(grad(v), grad(u)) * dx # FFC notation: L = dot(d, grad(v))*dx L = inner(d, grad(v)) * dx a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) assert(a.signature() == a_restore.signature()) assert(L.signature() == L_restore.signature()) def testElasticity(): element = VectorElement("Lagrange", "tetrahedron", 1) v = TestFunction(element) u = TrialFunction(element) def eps(v): # FFC notation: return grad(v) + transp(grad(v)) return grad(v) + (grad(v)).T # FFC notation: a = 0.25*dot(eps(v), eps(u))*dx a = 0.25 * inner(eps(v), eps(u)) * dx a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) assert(a.signature() == a_restore.signature()) def testEnergyNorm(): element = FiniteElement("Lagrange", "tetrahedron", 1) v = Coefficient(element) a = (v * v + dot(grad(v), grad(v))) * dx a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) assert(a.signature() == a_restore.signature()) def testEquation(): element = FiniteElement("Lagrange", "triangle", 1) k = 0.1 v = TestFunction(element) u = TrialFunction(element) u0 = Coefficient(element) F = v * (u - u0) * dx + k * dot(grad(v), grad(0.5 * (u0 + u))) * dx a = lhs(F) L = rhs(F) a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) assert(a.signature() == a_restore.signature()) assert(L.signature() == L_restore.signature()) def testFunctionOperators(): element = FiniteElement("Lagrange", "triangle", 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) g = Coefficient(element) # FFC notation: a = sqrt(1/modulus(1/f))*sqrt(g)*dot(grad(v), grad(u))*dx # + v*u*sqrt(f*g)*g*dx a = sqrt(1 / abs(1 / f)) * sqrt(g) * \ dot(grad(v), grad(u)) * dx + v * u * sqrt(f * g) * g * dx a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) assert(a.signature() == a_restore.signature()) def testHeat(): element = FiniteElement("Lagrange", "triangle", 1) v = TestFunction(element) u1 = TrialFunction(element) u0 = Coefficient(element) c = Coefficient(element) f = Coefficient(element) k = Constant("triangle") a = v * u1 * dx + k * c * dot(grad(v), grad(u1)) * dx L = v * u0 * dx + k * v * f * dx a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) assert(a.signature() == a_restore.signature()) assert(L.signature() == L_restore.signature()) def testMass(): element = FiniteElement("Lagrange", "tetrahedron", 3) v = TestFunction(element) u = TrialFunction(element) a = v * u * dx a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) assert(a.signature() == a_restore.signature()) def testMixedMixedElement(): P3 = FiniteElement("Lagrange", "triangle", 3) element = (P3 * P3) * (P3 * P3) element_pickle = pickle.dumps(element, p) element_restore = pickle.loads(element_pickle) assert(element == element_restore) def testMixedPoisson(): q = 1 BDM = FiniteElement("Brezzi-Douglas-Marini", "triangle", q) DG = FiniteElement("Discontinuous Lagrange", "triangle", q - 1) mixed_element = BDM * DG (tau, w) = TestFunctions(mixed_element) (sigma, u) = TrialFunctions(mixed_element) f = Coefficient(DG) a = (dot(tau, sigma) - div(tau) * u + w * div(sigma)) * dx L = w * f * dx a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) assert(a.signature() == a_restore.signature()) assert(L.signature() == L_restore.signature()) def testNavierStokes(): element = VectorElement("Lagrange", "tetrahedron", 1) v = TestFunction(element) u = TrialFunction(element) w = Coefficient(element) # FFC notation: a = v[i]*w[j]*D(u[i], j)*dx a = v[i] * w[j] * Dx(u[i], j) * dx a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) assert(a.signature() == a_restore.signature()) def testNeumannProblem(): element = VectorElement("Lagrange", "triangle", 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) g = Coefficient(element) # FFC notation: a = dot(grad(v), grad(u))*dx a = inner(grad(v), grad(u)) * dx # FFC notation: L = dot(v, f)*dx + dot(v, g)*ds L = inner(v, f) * dx + inner(v, g) * ds a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) assert(a.signature() == a_restore.signature()) assert(L.signature() == L_restore.signature()) def testOptimization(): element = FiniteElement("Lagrange", "triangle", 3) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) a = dot(grad(v), grad(u)) * dx L = v * f * dx a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) assert(a.signature() == a_restore.signature()) assert(L.signature() == L_restore.signature()) def testP5tet(): element = FiniteElement("Lagrange", tetrahedron, 5) element_pickle = pickle.dumps(element, p) element_restore = pickle.loads(element_pickle) assert(element == element_restore) def testP5tri(): element = FiniteElement("Lagrange", triangle, 5) element_pickle = pickle.dumps(element, p) element_restore = pickle.loads(element_pickle) def testPoissonDG(): element = FiniteElement("Discontinuous Lagrange", triangle, 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) n = FacetNormal(triangle) # FFC notation: h = MeshSize("triangle"), not supported by UFL h = Constant(triangle) gN = Coefficient(element) alpha = 4.0 gamma = 8.0 # FFC notation # a = dot(grad(v), grad(u))*dx \ # - dot(avg(grad(v)), jump(u, n))*dS \ # - dot(jump(v, n), avg(grad(u)))*dS \ # + alpha/h('+')*dot(jump(v, n), jump(u, n))*dS \ # - dot(grad(v), mult(u,n))*ds \ # - dot(mult(v,n), grad(u))*ds \ # + gamma/h*v*u*ds a = inner(grad(v), grad(u)) * dx \ - inner(avg(grad(v)), jump(u, n)) * dS \ - inner(jump(v, n), avg(grad(u))) * dS \ + alpha / h('+') * dot(jump(v, n), jump(u, n)) * dS \ - inner(grad(v), u * n) * ds \ - inner(u * n, grad(u)) * ds \ + gamma / h * v * u * ds L = v * f * dx + v * gN * ds a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) assert(a.signature() == a_restore.signature()) assert(L.signature() == L_restore.signature()) def testPoisson(): element = FiniteElement("Lagrange", "triangle", 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) # Note: inner() also works a = dot(grad(v), grad(u)) * dx L = v * f * dx a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) assert(a.signature() == a_restore.signature()) assert(L.signature() == L_restore.signature()) def testPoissonSystem(): element = VectorElement("Lagrange", "triangle", 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) # FFC notation: a = dot(grad(v), grad(u))*dx a = inner(grad(v), grad(u)) * dx # FFC notation: L = dot(v, f)*dx L = inner(v, f) * dx a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) assert(a.signature() == a_restore.signature()) assert(L.signature() == L_restore.signature()) def testQuadratureElement(): element = FiniteElement("Lagrange", "triangle", 2) # FFC notation: # QE = QuadratureElement("triangle", 3) # sig = VectorQuadratureElement("triangle", 3) QE = FiniteElement("Quadrature", "triangle", 3) sig = VectorElement("Quadrature", "triangle", 3) v = TestFunction(element) u = TrialFunction(element) u0 = Coefficient(element) C = Coefficient(QE) sig0 = Coefficient(sig) f = Coefficient(element) a = v.dx(i) * C * u.dx(i) * dx + v.dx(i) * 2 * u0 * u * u0.dx(i) * dx L = v * f * dx - dot(grad(v), sig0) * dx a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) assert(a.signature() == a_restore.signature()) assert(L.signature() == L_restore.signature()) def testStokes(): # UFLException: Shape mismatch in sum. P2 = VectorElement("Lagrange", "triangle", 2) P1 = FiniteElement("Lagrange", "triangle", 1) TH = P2 * P1 (v, q) = TestFunctions(TH) (u, r) = TrialFunctions(TH) f = Coefficient(P2) # FFC notation: # a = (dot(grad(v), grad(u)) - div(v)*p + q*div(u))*dx a = (inner(grad(v), grad(u)) - div(v) * r + q * div(u)) * dx L = dot(v, f) * dx a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) assert(a.signature() == a_restore.signature()) assert(L.signature() == L_restore.signature()) def testSubDomain(): element = FiniteElement("CG", "tetrahedron", 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) M = f * dx(2) + f * ds(5) M_pickle = pickle.dumps(M, p) M_restore = pickle.loads(M_pickle) assert(M.signature() == M_restore.signature()) def testSubDomains(): element = FiniteElement("CG", "tetrahedron", 1) v = TestFunction(element) u = TrialFunction(element) a = v * u * dx(0) + 10.0 * v * u * dx(1) + v * u * ds(0) + 2.0 * v * u * \ ds(1) + v('+') * u('+') * dS(0) + 4.3 * v('+') * u('+') * dS(1) a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) assert(a.signature() == a_restore.signature()) def testTensorWeightedPoisson(): # FFC notation: # P1 = FiniteElement("Lagrange", "triangle", 1) # P0 = FiniteElement("Discontinuous Lagrange", "triangle", 0) # # v = TestFunction(P1) # u = TrialFunction(P1) # f = Coefficient(P1) # # c00 = Coefficient(P0) # c01 = Coefficient(P0) # c10 = Coefficient(P0) # c11 = Coefficient(P0) # # C = [[c00, c01], [c10, c11]] # # a = dot(grad(v), mult(C, grad(u)))*dx P1 = FiniteElement("Lagrange", "triangle", 1) P0 = TensorElement("Discontinuous Lagrange", "triangle", 0, shape=(2, 2)) v = TestFunction(P1) u = TrialFunction(P1) C = Coefficient(P0) a = inner(grad(v), C * grad(u)) * dx a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) assert(a.signature() == a_restore.signature()) def testVectorLaplaceGradCurl(): def HodgeLaplaceGradCurl(element, felement): (tau, v) = TestFunctions(element) (sigma, u) = TrialFunctions(element) f = Coefficient(felement) # FFC notation: a = (dot(tau, sigma) - dot(grad(tau), u) + dot(v, # grad(sigma)) + dot(curl(v), curl(u)))*dx a = (inner(tau, sigma) - inner(grad(tau), u) + inner(v, grad(sigma)) + inner(curl(v), curl(u))) * dx # FFC notation: L = dot(v, f)*dx L = inner(v, f) * dx return [a, L] shape = "tetrahedron" order = 1 GRAD = FiniteElement("Lagrange", shape, order) # FFC notation: CURL = FiniteElement("Nedelec", shape, order-1) CURL = FiniteElement("N1curl", shape, order) VectorLagrange = VectorElement("Lagrange", shape, order + 1) [a, L] = HodgeLaplaceGradCurl(GRAD * CURL, VectorLagrange) a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) assert(a.signature() == a_restore.signature()) assert(L.signature() == L_restore.signature()) def testIdentity(): i = Identity(2) i_pickle = pickle.dumps(i, p) i_restore = pickle.loads(i_pickle) assert(i == i_restore) def testFormData(): element = FiniteElement("Lagrange", "tetrahedron", 3) v = TestFunction(element) u = TrialFunction(element) a = v * u * dx form_data = compute_form_data(a) form_data_pickle = pickle.dumps(form_data, p) form_data_restore = pickle.loads(form_data_pickle) assert(str(form_data) == str(form_data_restore)) ufl-1.6.0/test/test_piecewise_checks.py000077500000000000000000000161151255567402100202070ustar00rootroot00000000000000#!/usr/bin/env py.test """ Test the is_cellwise_constant function on all relevant terminal types. """ import pytest from ufl import * from ufl.classes import * def get_domains(): all_cells = [ #vertex, interval, triangle, quadrilateral, tetrahedron, hexahedron, ] return [Domain(cell) for cell in all_cells] def get_nonlinear(): domains_with_quadratic_coordinates = [] for D in get_domains(): V = VectorElement("CG", D, 2) x = Coefficient(V) E = Domain(x) domains_with_quadratic_coordinates.append(E) return domains_with_quadratic_coordinates @pytest.fixture(params=list(range(5))) def nonlinear_domains(request): return get_nonlinear()[request.param] @pytest.fixture(params=list(range(10))) def domains_not_linear(request): all_domains_not_linear = get_domains() + get_nonlinear() return all_domains_not_linear[request.param] @pytest.fixture(params=list(range(15))) def domains(request): domains = get_domains() domains_with_linear_coordinates = [] for D in domains: V = VectorElement("CG", D, 1) x = Coefficient(V) E = Domain(x) domains_with_linear_coordinates.append(E) all_domains = domains + domains_with_linear_coordinates + get_nonlinear() return all_domains[request.param] @pytest.fixture(params=list(range(6))) def affine_domains(request): affine_cells = [ interval, triangle, tetrahedron, ] affine_domains = [Domain(cell) for cell in affine_cells] affine_domains_with_linear_coordinates = [] for D in affine_domains: V = VectorElement("CG", D, 1) x = Coefficient(V) E = Domain(x) affine_domains_with_linear_coordinates.append(E) all_affine_domains = affine_domains + \ affine_domains_with_linear_coordinates return all_affine_domains[request.param] @pytest.fixture(params=list(range(8))) def affine_facet_domains(request): affine_facet_cells = [ interval, triangle, quadrilateral, tetrahedron, ] affine_facet_domains = [Domain(cell) for cell in affine_facet_cells] affine_facet_domains_with_linear_coordinates = [] for D in affine_facet_domains: V = VectorElement("CG", D, 1) x = Coefficient(V) E = Domain(x) affine_facet_domains_with_linear_coordinates.append(E) all_affine_facet_domains = affine_facet_domains + \ affine_facet_domains_with_linear_coordinates return all_affine_facet_domains[request.param] @pytest.fixture(params=list(range(4))) def nonaffine_domains(request): nonaffine_cells = [ quadrilateral, hexahedron, ] nonaffine_domains = [Domain(cell) for cell in nonaffine_cells] nonaffine_domains_with_linear_coordinates = [] for D in nonaffine_domains: V = VectorElement("CG", D, 1) x = Coefficient(V) E = Domain(x) nonaffine_domains_with_linear_coordinates.append(E) all_nonaffine_domains = nonaffine_domains + \ nonaffine_domains_with_linear_coordinates return all_nonaffine_domains[request.param] @pytest.fixture(params=list(range(2))) def nonaffine_facet_domains(request): nonaffine_facet_cells = [ hexahedron, ] nonaffine_facet_domains = [Domain(cell) for cell in nonaffine_facet_cells] nonaffine_facet_domains_with_linear_coordinates = [] for D in nonaffine_facet_domains: V = VectorElement("CG", D, 1) x = Coefficient(V) E = Domain(x) nonaffine_facet_domains_with_linear_coordinates.append(E) all_nonaffine_facet_domains = nonaffine_facet_domains + \ nonaffine_facet_domains_with_linear_coordinates return all_nonaffine_facet_domains[request.param] def test_always_cellwise_constant_geometric_quantities(domains): "Test geometric quantities that are always constant over a cell." e = CellVolume(domains) assert e.is_cellwise_constant() e = Circumradius(domains) assert e.is_cellwise_constant() e = FacetArea(domains) assert e.is_cellwise_constant() e = MinFacetEdgeLength(domains) assert e.is_cellwise_constant() e = MaxFacetEdgeLength(domains) assert e.is_cellwise_constant() def test_coordinates_never_cellwise_constant(domains): e = SpatialCoordinate(domains) assert not e.is_cellwise_constant() e = CellCoordinate(domains) assert not e.is_cellwise_constant() def test_coordinates_never_cellwise_constant_vertex(): # The only exception here: domains = Domain(Cell("vertex", 3)) assert domains.cell().cellname() == "vertex" e = SpatialCoordinate(domains) assert e.is_cellwise_constant() e = CellCoordinate(domains) assert e.is_cellwise_constant() def mappings_are_cellwise_constant(domain, test): e = Jacobian(domain) assert e.is_cellwise_constant() == test e = JacobianDeterminant(domain) assert e.is_cellwise_constant() == test e = JacobianInverse(domain) assert e.is_cellwise_constant() == test if domain.topological_dimension() != 1: e = FacetJacobian(domain) assert e.is_cellwise_constant() == test e = FacetJacobianDeterminant(domain) assert e.is_cellwise_constant() == test e = FacetJacobianInverse(domain) assert e.is_cellwise_constant() == test def test_mappings_are_cellwise_constant_on_linear_affine_cells(affine_domains): mappings_are_cellwise_constant(affine_domains, True) def test_mappings_are_cellwise_not_constant_on_nonaffine_cells(nonaffine_domains): mappings_are_cellwise_constant(nonaffine_domains, False) def test_mappings_are_cellwise_not_constant_on_nonlinear_cells(nonlinear_domains): mappings_are_cellwise_constant(nonlinear_domains, False) def facetnormal_cellwise_constant(domain, test): e = FacetNormal(domain) assert e.is_cellwise_constant() == test def test_facetnormal_cellwise_constant_affine(affine_facet_domains): facetnormal_cellwise_constant(affine_facet_domains, True) def test_facetnormal_not_cellwise_constant_nonaffine(nonaffine_facet_domains): facetnormal_cellwise_constant(nonaffine_facet_domains, False) def test_facetnormal_not_cellwise_constant_nonlinear(nonlinear_domains): facetnormal_cellwise_constant(nonlinear_domains, False) def test_coefficient_sometimes_cellwise_constant(domains_not_linear): e = Constant(domains_not_linear) assert e.is_cellwise_constant() V = FiniteElement("DG", domains_not_linear, 0) e = Coefficient(V) assert e.is_cellwise_constant() V = FiniteElement("R", domains_not_linear, 0) e = Coefficient(V) assert e.is_cellwise_constant() # This should be true, but that has to wait for a fix of issue #13 # e = TestFunction(V) # assert e.is_cellwise_constant() # V = FiniteElement("R", domains_not_linear, 0) # e = TestFunction(V) # assert e.is_cellwise_constant() def test_coefficient_mostly_not_cellwise_constant(domains_not_linear): V = FiniteElement("DG", domains_not_linear, 1) e = Coefficient(V) assert not e.is_cellwise_constant() e = TestFunction(V) assert not e.is_cellwise_constant() ufl-1.6.0/test/test_reference_shapes.py000066400000000000000000000015141255567402100202050ustar00rootroot00000000000000#!/usr/bin/env py.test import pytest from ufl import * def test_reference_shapes(): #show_elements() cell = Cell("triangle", 3) V = FiniteElement("N1curl", cell, 1) assert V.value_shape() == (3,) assert V.reference_value_shape() == (2,) U = FiniteElement("RT", cell, 1) assert U.value_shape() == (3,) assert U.reference_value_shape() == (2,) W = FiniteElement("CG", cell, 1) assert W.value_shape() == () assert W.reference_value_shape() == () Q = VectorElement("CG", cell, 1) assert Q.value_shape() == (3,) assert Q.reference_value_shape() == (3,) T = TensorElement("CG", cell, 1) assert T.value_shape() == (3, 3) assert T.reference_value_shape() == (9,) M = MixedElement(V, U, W) assert M.value_shape() == (7,) assert M.reference_value_shape() == (5,) ufl-1.6.0/test/test_scratch.py000077500000000000000000000352021255567402100163370ustar00rootroot00000000000000#!/usr/bin/env py.test """ This is a template file you can copy when making a new test case. Begin by copying this file to a filename matching test_*.py. The tests in the file will then automatically be run by ./test.py. Next look at the TODO markers below for places to edit. """ import pytest from six.moves import zip # This imports everything external code will see from ufl from ufl import * from ufl.log import error, warning from ufl.assertions import ufl_assert from ufl.tensors import as_scalar, unit_indexed_tensor, unwrap_list_tensor # TODO: Import only what you need from classes and algorithms: from ufl.classes import Grad, FormArgument, Zero, Indexed, FixedIndex, ListTensor class MockForwardAD: def __init__(self): self._w = () self._v = () class Obj: def __init__(self): self._data = {} self._cd = Obj() def grad(self, g): # If we hit this type, it has already been propagated # to a coefficient (or grad of a coefficient), # FIXME: Assert this! # so we need to take the gradient of the variation or return zero. # Complications occur when dealing with derivatives w.r.t. single components... # Figure out how many gradients are around the inner terminal ngrads = 0 o = g while isinstance(o, Grad): o, = o.ufl_operands ngrads += 1 if not isinstance(o, FormArgument): error("Expecting gradient of a FormArgument, not %r" % (o,)) def apply_grads(f): if not isinstance(f, FormArgument): print((','*60)) print(f) print(o) print(g) print((','*60)) error("What?") for i in range(ngrads): f = Grad(f) return f # Find o among all w without any indexing, which makes this easy for (w, v) in zip(self._w, self._v): if o == w and isinstance(v, FormArgument): # Case: d/dt [w + t v] return (g, apply_grads(v)) # If o is not among coefficient derivatives, return do/dw=0 gprimesum = Zero(g.ufl_shape) def analyse_variation_argument(v): # Analyse variation argument if isinstance(v, FormArgument): # Case: d/dt [w[...] + t v] vval, vcomp = v, () elif isinstance(v, Indexed): # Case: d/dt [w + t v[...]] # Case: d/dt [w[...] + t v[...]] vval, vcomp = v.ufl_operands vcomp = tuple(vcomp) else: error("Expecting argument or component of argument.") ufl_assert(all(isinstance(k, FixedIndex) for k in vcomp), "Expecting only fixed indices in variation.") return vval, vcomp def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): # Apply gradients directly to argument vval, # and get the right indexed scalar component(s) kk = indices(ngrads) Dvkk = apply_grads(vval)[vcomp+kk] # Place scalar component(s) Dvkk into the right tensor positions if wshape: Ejj, jj = unit_indexed_tensor(wshape, wcomp) else: Ejj, jj = 1, () gprimeterm = as_tensor(Ejj*Dvkk, jj+kk) return gprimeterm # Accumulate contributions from variations in different components for (w, v) in zip(self._w, self._v): # Analyse differentiation variable coefficient if isinstance(w, FormArgument): if not w == o: continue wshape = w.ufl_shape if isinstance(v, FormArgument): # Case: d/dt [w + t v] return (g, apply_grads(v)) elif isinstance(v, ListTensor): # Case: d/dt [w + t <...,v,...>] for wcomp, vsub in unwrap_list_tensor(v): if not isinstance(vsub, Zero): vval, vcomp = analyse_variation_argument(vsub) gprimesum = gprimesum + compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp) else: ufl_assert(wshape == (), "Expecting scalar coefficient in this branch.") # Case: d/dt [w + t v[...]] wval, wcomp = w, () vval, vcomp = analyse_variation_argument(v) gprimesum = gprimesum + compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp) elif isinstance(w, Indexed): # This path is tested in unit tests, but not actually used? # Case: d/dt [w[...] + t v[...]] # Case: d/dt [w[...] + t v] wval, wcomp = w.ufl_operands if not wval == o: continue assert isinstance(wval, FormArgument) ufl_assert(all(isinstance(k, FixedIndex) for k in wcomp), "Expecting only fixed indices in differentiation variable.") wshape = wval.ufl_shape vval, vcomp = analyse_variation_argument(v) gprimesum = gprimesum + compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp) else: error("Expecting coefficient or component of coefficient.") # FIXME: Handle other coefficient derivatives: oprimes = self._cd._data.get(o) if 0: oprimes = self._cd._data.get(o) if oprimes is None: if self._cd._data: # TODO: Make it possible to silence this message in particular? # It may be good to have for debugging... warning("Assuming d{%s}/d{%s} = 0." % (o, self._w)) else: # Make sure we have a tuple to match the self._v tuple if not isinstance(oprimes, tuple): oprimes = (oprimes,) ufl_assert(len(oprimes) == len(self._v), "Got a tuple of arguments, "+\ "expecting a matching tuple of coefficient derivatives.") # Compute dg/dw_j = dg/dw_h : v. # Since we may actually have a tuple of oprimes and vs in a # 'mixed' space, sum over them all to get the complete inner # product. Using indices to define a non-compound inner product. for (oprime, v) in zip(oprimes, self._v): error("FIXME: Figure out how to do this with ngrads") so, oi = as_scalar(oprime) rv = len(v.ufl_shape) oi1 = oi[:-rv] oi2 = oi[-rv:] prod = so*v[oi2] if oi1: gprimesum += as_tensor(prod, oi1) else: gprimesum += prod return (g, gprimesum) def test_unit_tensor(self): E2_1, ii = unit_indexed_tensor((2,), (1,)) E3_1, ii = unit_indexed_tensor((3,), (1,)) E22_10, ii = unit_indexed_tensor((2, 2), (1, 0)) # TODO: Evaluate and assert values def test_unwrap_list_tensor(self): lt = as_tensor((1, 2)) expected = [((0,), 1), ((1,), 2),] comp = unwrap_list_tensor(lt) assert comp == expected lt = as_tensor(((1, 2), (3, 4))) expected = [((0, 0), 1), ((0, 1), 2), ((1, 0), 3), ((1, 1), 4),] comp = unwrap_list_tensor(lt) assert comp == expected lt = as_tensor((((1, 2), (3, 4)), ((11, 12), (13, 14)))) expected = [((0, 0, 0), 1), ((0, 0, 1), 2), ((0, 1, 0), 3), ((0, 1, 1), 4), ((1, 0, 0), 11), ((1, 0, 1), 12), ((1, 1, 0), 13), ((1, 1, 1), 14),] comp = unwrap_list_tensor(lt) assert comp == expected def test__forward_coefficient_ad__grad_of_scalar_coefficient(self): U = FiniteElement("CG", triangle, 1) u = Coefficient(U) du = TestFunction(U) mad = MockForwardAD() mad._w = (u,) mad._v = (du,) # Simple grad(coefficient) -> grad(variation) f = grad(u) df = grad(du) g, dg = mad.grad(f) assert g == f assert dg == df # Simple grad(grad(coefficient)) -> grad(grad(variation)) f = grad(grad(u)) df = grad(grad(du)) g, dg = mad.grad(f) assert g == f assert dg == df def test__forward_coefficient_ad__grad_of_vector_coefficient(self): V = VectorElement("CG", triangle, 1) v = Coefficient(V) dv = TestFunction(V) mad = MockForwardAD() mad._w = (v,) mad._v = (dv,) # Simple grad(coefficient) -> grad(variation) f = grad(v) df = grad(dv) g, dg = mad.grad(f) assert g == f assert dg == df # Simple grad(grad(coefficient)) -> grad(grad(variation)) f = grad(grad(v)) df = grad(grad(dv)) g, dg = mad.grad(f) assert g == f assert dg == df def test__forward_coefficient_ad__grad_of_vector_coefficient__with_component_variation(self): V = VectorElement("CG", triangle, 1) v = Coefficient(V) dv = TestFunction(V) mad = MockForwardAD() # Component of variation: # grad(grad(c))[0,...] -> grad(grad(dc))[1,...] mad._w = (v[0],) mad._v = (dv[1],) f = grad(v) df = grad(as_vector((dv[1], 0))) # Mathematically this would be the natural result j, k = indices(2) df = as_tensor(Identity(2)[0, j]*grad(dv)[1, k], (j, k)) # Actual representation should have grad right next to dv g, dg = mad.grad(f) if 0: print(('\nf ', f)) print(('df ', df)) print(('g ', g)) print(('dg ', dg)) assert f.ufl_shape == df.ufl_shape assert g.ufl_shape == f.ufl_shape assert dg.ufl_shape == df.ufl_shape assert g == f self.assertEqual((inner(dg, dg)*dx).signature(), (inner(df, df)*dx).signature()) #assert dg == df # Expected to fail because of different index numbering # Multiple components of variation: # grad(grad(c))[0,1,:,:] -> grad(grad(dc))[1,0,:,:] mad._w = (v[0], v[1]) mad._v = (dv[1], dv[0]) f = grad(v) # Mathematically this would be the natural result: df = grad(as_vector((dv[1], dv[0]))) # Actual representation should have grad right next to dv: j0, k0 = indices(2) j1, k1 = indices(2) # Using j0,k0 for both terms gives different signature df = (as_tensor(Identity(2)[0, j0]*grad(dv)[1, k0], (j0, k0)) + as_tensor(Identity(2)[1, j1]*grad(dv)[0, k1], (j1, k1))) g, dg = mad.grad(f) print(('\nf ', f)) print(('df ', df)) print(('g ', g)) print(('dg ', dg)) assert f.ufl_shape == df.ufl_shape assert g.ufl_shape == f.ufl_shape assert dg.ufl_shape == df.ufl_shape assert g == f self.assertEqual((inner(dg, dg)*dx).signature(), (inner(df, df)*dx).signature()) #assert dg == df # Expected to fail because of different index numbering def test__forward_coefficient_ad__grad_of_vector_coefficient__with_component_variation_in_list(self): V = VectorElement("CG", triangle, 1) v = Coefficient(V) dv = TestFunction(V) mad = MockForwardAD() # Component of variation: # grad(grad(c))[0,...] -> grad(grad(dc))[1,...] mad._w = (v,) mad._v = (as_vector((dv[1], 0)),) f = grad(v) df = grad(as_vector((dv[1], 0))) # Mathematically this would be the natural result j, k = indices(2) df = as_tensor(Identity(2)[0, j]*grad(dv)[1, k], (j, k)) # Actual representation should have grad right next to dv g, dg = mad.grad(f) if 0: print(('\nf ', f)) print(('df ', df)) print(('g ', g)) print(('dg ', dg)) assert f.ufl_shape == df.ufl_shape assert g.ufl_shape == f.ufl_shape assert dg.ufl_shape == df.ufl_shape assert g == f self.assertEqual((inner(dg, dg)*dx).signature(), (inner(df, df)*dx).signature()) #assert dg == df # Expected to fail because of different index numbering # Multiple components of variation: # grad(grad(c))[0,1,:,:] -> grad(grad(dc))[1,0,:,:] mad._w = (v, ) mad._v = (as_vector((dv[1], dv[0])),) f = grad(v) # Mathematically this would be the natural result: df = grad(as_vector((dv[1], dv[0]))) # Actual representation should have grad right next to dv: j0, k0 = indices(2) j1, k1 = indices(2) # Using j0,k0 for both terms gives different signature df = (as_tensor(Identity(2)[0, j0]*grad(dv)[1, k0], (j0, k0)) + as_tensor(Identity(2)[1, j1]*grad(dv)[0, k1], (j1, k1))) g, dg = mad.grad(f) print(('\nf ', f)) print(('df ', df)) print(('g ', g)) print(('dg ', dg)) assert f.ufl_shape == df.ufl_shape assert g.ufl_shape == f.ufl_shape assert dg.ufl_shape == df.ufl_shape assert g == f self.assertEqual((inner(dg, dg)*dx).signature(), (inner(df, df)*dx).signature()) #assert dg == df # Expected to fail because of different index numbering def test__forward_coefficient_ad__grad_of_tensor_coefficient(self): W = TensorElement("CG", triangle, 1) w = Coefficient(W) dw = TestFunction(W) mad = MockForwardAD() mad._w = (w,) mad._v = (dw,) # Simple grad(coefficient) -> grad(variation) f = grad(w) df = grad(dw) g, dg = mad.grad(f) assert g == f assert dg == df # Simple grad(grad(coefficient)) -> grad(grad(variation)) f = grad(grad(w)) df = grad(grad(dw)) g, dg = mad.grad(f) assert g == f assert dg == df def test__forward_coefficient_ad__grad_of_tensor_coefficient__with_component_variation(self): W = TensorElement("CG", triangle, 1) w = Coefficient(W) dw = TestFunction(W) mad = MockForwardAD() # Component of variation: # grad(grad(c))[0,...] -> grad(grad(dc))[1,...] wc = (1, 0) dwc = (0, 1) mad._w = (w[wc],) mad._v = (dw[dwc],) f = grad(w) df = grad(as_matrix(((0, 0), (dw[dwc], 0)))) # Mathematically this is it. i, j, k = indices(3) E = outer(Identity(2)[wc[0], i], Identity(2)[wc[1], j]) Ddw = grad(dw)[dwc + (k,)] df = as_tensor(E*Ddw, (i, j, k)) # Actual representation should have grad next to dv g, dg = mad.grad(f) if 0: print(('\nf ', f)) print(('df ', df)) print(('g ', g)) print(('dg ', dg)) assert f.ufl_shape == df.ufl_shape assert g.ufl_shape == f.ufl_shape assert dg.ufl_shape == df.ufl_shape assert g == f self.assertEqual((inner(dg, dg)*dx).signature(), (inner(df, df)*dx).signature()) #assert dg == df # Expected to fail because of different index numbering ufl-1.6.0/test/test_signature.py000077500000000000000000000412411255567402100167110ustar00rootroot00000000000000#!/usr/bin/env py.test """ Test the computation of form signatures. """ import pytest from ufl import * from ufl.common import EmptyDictType from ufl.classes import MultiIndex, FixedIndex from ufl.algorithms.signature import compute_multiindex_hashdata, \ compute_terminal_hashdata from itertools import chain # TODO: Test compute_terminal_hashdata # TODO: Check that form argument counts only affect the sig by their relative ordering # TODO: Check that all other relevant terminal propeties affect the terminal_hashdata # TODO: Test that operator types affect the sig # TODO: Test that we do not get collisions for some large sets of generated forms # TODO: How do we know that we have tested the signature reliably enough? def domain_numbering(*cells): renumbering = {} for i, cell in enumerate(cells): domain = as_domain(cell) renumbering[domain] = i return renumbering def test_domain_signatures_of_cell2domains(self): all_cells = (interval, quadrilateral, hexahedron, triangle, tetrahedron) for cell in all_cells: # Equality holds when constructing two domains from a cell: assert as_domain(cell) == as_domain(cell) # Hash value holds when constructing two domains from a cell: assert hash(as_domain(cell)) == hash(as_domain(cell)) # Signature data holds when constructing two domains from a cell: D1 = as_domain(cell) D2 = as_domain(cell) self.assertEqual(D1.signature_data({D1:0}), D2.signature_data({D2:0})) def compute_unique_terminal_hashdatas(hashdatas): count = 0 data = set() hashes = set() reprs = set() for d in hashdatas: # Each d is the result of a compute_terminal_hashdatas call, # which is a dict where the keys are non-canonical terminals # and the values are the canonical hashdata. # We want to count unique hashdata values, # ignoring the original terminals. assert isinstance(d, dict) # Sorting values by hash should be stable at least in a single test run: t = tuple(sorted(list(d.values()), key=lambda x: hash(x))) #print t # Add the hashdata values tuple to sets based on itself, its hash, # and its repr (not sure why I included repr anymore?) hashes.add(hash(t)) # This will fail if t is not hashable, which it should be! data.add(t) reprs.add(repr(t)) count += 1 return count, len(data), len(reprs), len(hashes) def test_terminal_hashdata_depends_on_literals(self): reprs = set() hashes = set() def forms(): i, j = indices(2) for d in (2, 3): domain = as_domain({2:triangle,3:tetrahedron}[d]) x = SpatialCoordinate(domain) I = Identity(d) for fv in (1.1, 2.2): for iv in (5, 7): expr = (I[0, j]*(fv*x[j]))**iv reprs.add(repr(expr)) hashes.add(hash(expr)) yield compute_terminal_hashdata(expr, {domain: 0}) c, d, r, h = compute_unique_terminal_hashdatas(forms()) assert c == 8 assert d == c assert r == c assert h == c assert len(reprs) == c assert len(hashes) == c def test_terminal_hashdata_depends_on_geometry(self): reprs = set() hashes = set() def forms(): i, j = indices(2) cells = (triangle, tetrahedron) for cell in cells: d = cell.geometric_dimension() x = SpatialCoordinate(cell) n = FacetNormal(cell) r = Circumradius(cell) a = FacetArea(cell) #s = CellSurfaceArea(cell) v = CellVolume(cell) I = Identity(d) ws = (x, n) qs = (r, a, v) #, s) for w in ws: for q in qs: expr = (I[0, j]*(q*w[j])) reprs.add(repr(expr)) hashes.add(hash(expr)) yield compute_terminal_hashdata(expr, domain_numbering(*cells)) c, d, r, h = compute_unique_terminal_hashdatas(forms()) assert c == 2*3*2 # len(ws)*len(qs)*len(cells) assert d == c assert r == c assert h == c assert len(reprs) == c assert len(hashes) == c def test_terminal_hashdata_depends_on_form_argument_properties(self): reprs = set() hashes = set() nelm = 6 nreps = 2 # Data cells = (triangle, tetrahedron) degrees = (1, 2) families = ("CG", "Lagrange", "DG") def forms(): for rep in range(nreps): for cell in cells: d = cell.geometric_dimension() for degree in degrees: for family in families: V = FiniteElement(family, cell, degree) W = VectorElement(family, cell, degree) W2 = VectorElement(family, cell, degree, dim=d+1) T = TensorElement(family, cell, degree) S = TensorElement(family, cell, degree, symmetry=True) S2 = TensorElement(family, cell, degree, shape=(d, d), symmetry={(0, 0):(1, 1)}) elements = [V, W, W2, T, S, S2] assert len(elements) == nelm for H in elements[:nelm]: # Keep number and count fixed, we're not testing that here a = Argument(H, number=1) c = Coefficient(H, count=1) renumbering = domain_numbering(*cells) renumbering[c] = 0 for f in (a, c): expr = inner(f, f) reprs.add(repr(expr)) hashes.add(hash(expr)) yield compute_terminal_hashdata(expr, renumbering) c, d, r, h = compute_unique_terminal_hashdatas(forms()) c1 = nreps * len(cells) * len(degrees) * len(families) * nelm * 2 # Number of cases with repetitions assert c == c1 c0 = len(cells) * len(degrees) * (len(families)-1) * nelm * 2 # Number of unique cases, "CG" == "Lagrange" #c0 = len(cells) * len(degrees) * (len(families)) * nelm * 2 # Number of unique cases, "CG" != "Lagrange" assert d == c0 assert r == c0 assert h == c0 assert len(reprs) == c0 assert len(hashes) == c0 def test_terminal_hashdata_does_not_depend_on_coefficient_count_values_only_ordering(self): reprs = set() hashes = set() counts = list(range(-3, 4)) cells = (interval, triangle, hexahedron) assert len(counts) == 7 nreps = 1 def forms(): for rep in range(nreps): for cell in cells: for k in counts: V = FiniteElement("CG", cell, 2) f = Coefficient(V, count=k) g = Coefficient(V, count=k+2) expr = inner(f, g) renumbering = domain_numbering(*cells) renumbering[f] = 0 renumbering[g] = 1 reprs.add(repr(expr)) hashes.add(hash(expr)) yield compute_terminal_hashdata(expr, renumbering) c, d, r, h = compute_unique_terminal_hashdatas(forms()) c0 = len(cells) # Number of actually unique cases from a code generation perspective c1 = len(counts) * c0 # Number of unique cases from a symbolic representation perspective assert len(reprs) == c1 assert len(hashes) == c1 assert c == nreps * c1 # number of inner loop executions in forms() above assert d == c0 assert r == c0 assert h == c0 def test_terminal_hashdata_does_depend_on_argument_number_values(self): # TODO: Include part numbers as well reprs = set() hashes = set() counts = list(range(4)) cells = (interval, triangle, hexahedron) nreps = 2 def forms(): for rep in range(nreps): for cell in cells: for k in counts: V = FiniteElement("CG", cell, 2) f = Argument(V, k) g = Argument(V, k+2) expr = inner(f, g) reprs.add(repr(expr)) hashes.add(hash(expr)) yield compute_terminal_hashdata(expr, domain_numbering(*cells)) c, d, r, h = compute_unique_terminal_hashdatas(forms()) c0 = len(cells) * len(counts) # Number of actually unique cases from a code generation perspective c1 = 1 * c0 # Number of unique cases from a symbolic representation perspective assert len(reprs) == c1 assert len(hashes) == c1 self.assertEqual(c, nreps * c1) # number of inner loop executions in forms() above assert d == c0 assert r == c0 assert h == c0 def test_domain_signature_data_does_not_depend_on_domain_label_value(self): cells = [triangle, tetrahedron, hexahedron] s0s = set() s1s = set() s2s = set() for cell in cells: d0 = Domain(cell) d1 = Domain(cell, label="domain1") d2 = Domain(cell, label="domain2") s0 = d0.signature_data({ d0: 0 }) s1 = d1.signature_data({ d1: 0 }) s2 = d2.signature_data({ d2: 0 }) assert s0 == s1 assert s0 == s2 s0s.add(s0) s1s.add(s1) s2s.add(s2) assert len(s0s) == len(cells) assert len(s1s) == len(cells) assert len(s2s) == len(cells) def test_terminal_hashdata_does_not_depend_on_domain_label_value(self): reprs = set() hashes = set() labels = ["domain1", "domain2"] cells = [triangle, quadrilateral] domains = [Domain(cell, label=label) for cell in cells for label in labels] nreps = 2 num_exprs = 2 def forms(): for rep in range(nreps): for domain in domains: V = FiniteElement("CG", domain, 2) f = Coefficient(V, count=0) v = TestFunction(V) x = SpatialCoordinate(domain) n = FacetNormal(domain) exprs = [inner(x, n), inner(f, v)] assert num_exprs == len(exprs) # Assumed in checks below # This numbering needs to be recreated to count 'domain' and 'f' as 0 each time: renumbering = { f: 0, domain: 0 } for expr in exprs: reprs.add(repr(expr)) hashes.add(hash(expr)) yield compute_terminal_hashdata(expr, renumbering) c, d, r, h = compute_unique_terminal_hashdatas(forms()) c0 = num_exprs * len(cells) # Number of actually unique cases from a code generation perspective c1 = num_exprs * len(domains) # Number of unique cases from a symbolic representation perspective assert len(reprs) == c1 assert len(hashes) == c1 self.assertEqual(c, nreps * c1) # number of inner loop executions in forms() above assert d == c0 assert r == c0 assert h == c0 def compute_unique_multiindex_hashdatas(hashdatas): count = 0 data = set() hashes = set() reprs = set() for d in hashdatas: data.add(tuple(d)) hashes.add(hash(tuple(d))) reprs.add(repr(d)) count += 1 return count, len(data), len(reprs), len(hashes) def test_multiindex_hashdata_depends_on_fixed_index_values(self): reprs = set() hashes = set() def hashdatas(): for i in range(3): for ii in ((i,), (i, 0), (1, i)): jj = tuple(FixedIndex(j) for j in ii) expr = MultiIndex(jj) reprs.add(repr(expr)) hashes.add(hash(expr)) yield compute_multiindex_hashdata(expr, {}) c, d, r, h = compute_unique_multiindex_hashdatas(hashdatas()) assert c == 9 assert d == 9-1 # (1,0 is repeated, therefore -1) assert len(reprs) == 9-1 assert len(hashes) == 9-1 def test_multiindex_hashdata_does_not_depend_on_counts(self): reprs = set() hashes = set() def hashdatas(): ijs = [] iind = indices(3) jind = indices(3) for i in iind: ijs.append((i,)) for j in jind: ijs.append((i, j)) ijs.append((j, i)) for ij in ijs: expr = MultiIndex(ij) reprs.add(repr(expr)) hashes.add(hash(expr)) yield compute_multiindex_hashdata(expr, {}) c, d, r, h = compute_unique_multiindex_hashdatas(hashdatas()) assert c == 3+9+9 assert d == 1+1 assert len(reprs) == 3+9+9 assert len(hashes) == 3+9+9 def test_multiindex_hashdata_depends_on_the_order_indices_are_observed(self): reprs = set() hashes = set() nrep = 3 def hashdatas(): for rep in range(nrep): # Resetting index_numbering for each repetition, # resulting in hashdata staying the same for # each repetition but repr and hashes changing # because new indices are created each repetition. index_numbering = {} i, j, k, l = indices(4) for expr in (MultiIndex((i,)), MultiIndex((i,)), # r MultiIndex((i, j)), MultiIndex((j, i)), MultiIndex((i, j)), # r MultiIndex((i, j, k)), MultiIndex((k, j, i)), MultiIndex((j, i))): # r reprs.add(repr(expr)) hashes.add(hash(expr)) yield compute_multiindex_hashdata(expr, index_numbering) c, d, r, h = compute_unique_multiindex_hashdatas(hashdatas()) assert c == nrep*8 assert d == 5 assert len(reprs) == nrep*5 assert len(hashes) == nrep*5 def check_unique_signatures(forms): count = 0 sigs = set() sigs2 = set() hashes = set() reprs = set() for a in forms: sig = a.signature() sig2 = a.signature() sigs.add(sig) sigs2.add(sig2) assert sig hashes.add(hash(a)) reprs.add(repr(a)) count += 1 assert len(sigs) == count assert len(sigs2) == count assert len(reprs) == count assert len(hashes) == count def test_signature_is_affected_by_element_properties(self): def forms(): for family in ("CG", "DG"): for cell in (triangle, tetrahedron, quadrilateral): for degree in (1, 2): V = FiniteElement(family, cell, degree) u = Coefficient(V) v = TestFunction(V) x = SpatialCoordinate(cell) w = as_vector([v]*x.ufl_shape[0]) f = dot(w, u*x) a = f*dx yield a check_unique_signatures(forms()) def test_signature_is_affected_by_domains(self): def forms(): for cell in (triangle, tetrahedron): for di in (1, 2): for dj in (1, 2): for dk in (1, 2): V = FiniteElement("CG", cell, 1) u = Coefficient(V) a = u*dx(di) + 2*u*dx(dj) + 3*u*ds(dk) yield a check_unique_signatures(forms()) def test_signature_of_forms_with_diff(self): def forms(): for cell in (triangle, tetrahedron): for k in (1, 2, 3): V = FiniteElement("CG", cell, 1) W = VectorElement("CG", cell, 1) u = Coefficient(V) w = Coefficient(W) vu = variable(u) vw = variable(w) f = vu*dot(vw, vu**k*vw) g = diff(f, vu) h = dot(diff(f, vw), FacetNormal(cell)) a = f*dx(1) + g*dx(2) + h*ds(0) yield a check_unique_signatures(forms()) def test_signature_of_form_depend_on_coefficient_numbering_across_integrals(self): cell = triangle V = FiniteElement("CG", cell, 1) f = Coefficient(V) g = Coefficient(V) M1 = f*dx(0) + g*dx(1) M2 = g*dx(0) + f*dx(1) M3 = g*dx(0) + g*dx(1) self.assertTrue(M1.signature() != M2.signature()) self.assertTrue(M1.signature() != M3.signature()) self.assertTrue(M2.signature() != M3.signature()) def test_signature_of_forms_change_with_operators(self): def forms(): for cell in (triangle, tetrahedron): V = FiniteElement("CG", cell, 1) u = Coefficient(V) v = Coefficient(V) fs = [(u*v)+(u/v), (u+v)+(u/v), (u+v)*(u/v), (u*v)*(u*v), (u+v)*(u*v), # (!) same #(u*v)*(u+v), # (!) same (u*v)+(u+v), ] for f in fs: a = f*dx yield a check_unique_signatures(forms()) ufl-1.6.0/test/test_simplify.py000077500000000000000000000066261255567402100165540ustar00rootroot00000000000000#!/usr/bin/env py.test import pytest from ufl.classes import Sum, Product import math from ufl import * def xtest_zero_times_argument(self): # FIXME: Allow zero forms element = FiniteElement("CG", triangle, 1) v = TestFunction(element) u = TrialFunction(element) L = 0*v*dx a = 0*(u*v)*dx b = (0*u)*v*dx assert len(compute_form_data(L).arguments) == 1 assert len(compute_form_data(a).arguments) == 2 assert len(compute_form_data(b).arguments) == 2 def test_divisions(self): element = FiniteElement("CG", triangle, 1) f = Coefficient(element) g = Coefficient(element) # Test simplification of division by 1 a = f b = f/1 assert a == b # Test simplification of division by 1.0 a = f b = f/1.0 assert a == b # Test simplification of division by of zero by something a = 0/f b = 0*f assert a == b # Test simplification of division by self (this simplification has been disabled) #a = f/f #b = 1 #assert a == b def test_products(self): element = FiniteElement("CG", triangle, 1) f = Coefficient(element) g = Coefficient(element) # Test simplification of literal multiplication assert f*0 == as_ufl(0) assert 0*f == as_ufl(0) assert 1*f == f assert f*1 == f assert as_ufl(2)*as_ufl(3) == as_ufl(6) assert as_ufl(2.0)*as_ufl(3.0) == as_ufl(6.0) # Test reordering of operands assert f*g == g*f # Test simplification of self-multiplication (this simplification has been disabled) #assert f*f == f**2 def test_sums(self): element = FiniteElement("CG", triangle, 1) f = Coefficient(element) g = Coefficient(element) # Test reordering of operands assert f + g == g + f # Test adding zero assert f + 0 == f assert 0 + f == f # Test collapsing of basic sum (this simplification has been disabled) #assert f + f == 2 * f # Test reordering of operands and collapsing sum a = f + g + f # not collapsed, but ordered b = g + f + f # not collapsed, but ordered c = (g + f) + f # not collapsed, but ordered d = f + (f + g) # not collapsed, but ordered assert a == b assert a == c assert a == d # Test reordering of operands and collapsing sum a = f + f + g # collapsed b = g + (f + f) # collapsed assert a == b def test_mathfunctions(self): for i in (0.1, 0.3, 0.9): assert math.sin(i) == sin(i) assert math.cos(i) == cos(i) assert math.tan(i) == tan(i) assert math.sinh(i) == sinh(i) assert math.cosh(i) == cosh(i) assert math.tanh(i) == tanh(i) assert math.asin(i) == asin(i) assert math.acos(i) == acos(i) assert math.atan(i) == atan(i) assert math.exp(i) == exp(i) assert math.log(i) == ln(i) # TODO: Implement automatic simplification of conditionals? assert i == float(Max(i, i-1)) # TODO: Implement automatic simplification of conditionals? assert i == float(Min(i, i+1)) def test_indexing(self): u = VectorConstant(triangle) v = VectorConstant(triangle) A = outer(u, v) A2 = as_tensor(A[i, j], (i, j)) assert A2 == A Bij = u[i]*v[j] Bij2 = as_tensor(Bij, (i, j))[i, j] Bij3 = as_tensor(Bij, (i, j)) #print "BIJ: ", Bij.free_indices() #print "BIJ2: ", Bij2.free_indices() #print "BIJ: ", Bij #print "BIJ2: ", Bij2 assert Bij2 == Bij ufl-1.6.0/test/test_sobolevspace.py000077500000000000000000000052311255567402100173740ustar00rootroot00000000000000#!/usr/bin/env py.test __authors__ = "David Ham" __date__ = "2014-03-04" import pytest from ufl import FiniteElement, triangle from ufl.sobolevspace import H2, H1, HDiv, HCurl, L2, SobolevSpace # TODO: Add construction of all elements with periodic table notation here. def test_inclusion(): assert H2 < H1 # Inclusion assert not H2 > H1 # Not included assert HDiv <= HDiv # Reflexivity assert H2 < L2 # Transitivity def test_repr(): assert eval(repr(H2)) == H2 def xtest_contains_mixed(): pass # FIXME: How to handle this? def test_contains_l2(): l2_elements = [ FiniteElement("Real", triangle, 0), FiniteElement("DG", triangle, 0), FiniteElement("DG", triangle, 1), FiniteElement("DG", triangle, 2), FiniteElement("CR", triangle, 1), ] for l2_element in l2_elements: assert l2_element in L2 assert l2_element not in H1 assert l2_element not in HCurl assert l2_element not in HDiv assert l2_element not in H2 def test_contains_h1(): h1_elements = [ # Standard Lagrange elements: FiniteElement("CG", triangle, 1), FiniteElement("CG", triangle, 2), # Some special elements: FiniteElement("AW", triangle), FiniteElement("HER", triangle), FiniteElement("MTW", triangle), ] for h1_element in h1_elements: assert h1_element in H1 assert h1_element in HDiv assert h1_element in HCurl assert h1_element in L2 assert h1_element not in H2 def test_contains_h2(): h2_elements = [ FiniteElement("ARG", triangle, 1), FiniteElement("MOR", triangle), ] for h2_element in h2_elements: assert h2_element in H2 assert h2_element in H1 assert h2_element in HDiv assert h2_element in HCurl assert h2_element in L2 def test_contains_hdiv(): hdiv_elements = [ FiniteElement("RT", triangle, 1), FiniteElement("BDM", triangle, 1), FiniteElement("BDFM", triangle, 2), ] for hdiv_element in hdiv_elements: assert hdiv_element in HDiv assert hdiv_element in L2 assert hdiv_element not in H1 assert hdiv_element not in HCurl assert hdiv_element not in H2 def test_contains_hcurl(): hcurl_elements = [ FiniteElement("N1curl", triangle, 1), FiniteElement("N2curl", triangle, 1), ] for hcurl_element in hcurl_elements: assert hcurl_element in HCurl assert hcurl_element in L2 assert hcurl_element not in H1 assert hcurl_element not in HDiv assert hcurl_element not in H2 ufl-1.6.0/test/test_split.py000077500000000000000000000031041255567402100160370ustar00rootroot00000000000000#!/usr/bin/env py.test from ufl import * __authors__ = "Martin Sandve Alnes" __date__ = "2009-03-14 -- 2009-03-14" import pytest from ufl import * def test_split(self): cell = triangle d = cell.geometric_dimension() f = FiniteElement("CG", cell, 1) v = VectorElement("CG", cell, 1) w = VectorElement("CG", cell, 1, dim=d+1) t = TensorElement("CG", cell, 1) s = TensorElement("CG", cell, 1, symmetry=True) r = TensorElement("CG", cell, 1, symmetry={(1, 0): (0, 1)}, shape=(d, d)) m = MixedElement(f, v, w, t, s, r) # Shapes of all these functions are correct: assert () == Coefficient(f).ufl_shape self.assertEqual((d,), Coefficient(v).ufl_shape) self.assertEqual((d+1,), Coefficient(w).ufl_shape) self.assertEqual((d, d), Coefficient(t).ufl_shape) self.assertEqual((d, d), Coefficient(s).ufl_shape) self.assertEqual((d, d), Coefficient(r).ufl_shape) self.assertEqual((3*d*d + 2*d + 2,), Coefficient(m).ufl_shape) # sum of value sizes, not accounting for symmetries # Shapes of subelements are reproduced: g = Coefficient(m) s, = g.ufl_shape for g2 in split(g): s -= product(g2.ufl_shape) assert s == 0 # TODO: Should functions on mixed elements (vector+vector) be able to have tensor shape instead of vector shape? Think Marie wants this for BDM+BDM? v2 = MixedElement(v, v) m2 = MixedElement(t, t) #assert d == 2 #self.assertEqual((2,2), Coefficient(v2).ufl_shape) self.assertEqual((d+d,), Coefficient(v2).ufl_shape) self.assertEqual((2*d*d,), Coefficient(m2).ufl_shape) ufl-1.6.0/test/test_str.py000077500000000000000000000046371255567402100155300ustar00rootroot00000000000000#!/usr/bin/env py.test import pytest from ufl import * from ufl.classes import * def test_str_int_value(self): assert str(as_ufl(3)) == "3" def test_str_float_value(self): assert str(as_ufl(3.14)) == "3.14" def test_str_zero(self): x = SpatialCoordinate(triangle) assert str(as_ufl(0)) == "0" assert str(0*x) == "(0<(2,), ()>)" # TODO: Not very nice... assert str(0*x*x[Index(42)]) == "(0<(2,), (42,)>)" # TODO: Not very nice... def test_str_index(self): assert str(Index(3)) == "i_3" assert str(Index(42)) == "i_{42}" def test_str_coordinate(self): assert str(SpatialCoordinate(triangle)) == "x" assert str(SpatialCoordinate(triangle)[0]) == "x[0]" def test_str_normal(self): assert str(FacetNormal(triangle)) == "n" assert str(FacetNormal(triangle)[0]) == "n[0]" def test_str_circumradius(self): assert str(Circumradius(triangle)) == "circumradius" #def test_str_cellsurfacearea(self): # assert str(CellSurfaceArea(triangle)) == "surfacearea" def test_str_facetarea(self): assert str(FacetArea(triangle)) == "facetarea" def test_str_volume(self): assert str(CellVolume(triangle)) == "volume" def test_str_scalar_argument(self): v = TestFunction(FiniteElement("CG", triangle, 1)) u = TrialFunction(FiniteElement("CG", triangle, 1)) assert str(v) == "v_0" assert str(u) == "v_1" #def test_str_vector_argument(self): # FIXME #def test_str_scalar_coefficient(self): # FIXME #def test_str_vector_coefficient(self): # FIXME def test_str_list_vector(self): x, y, z = SpatialCoordinate(tetrahedron) v = as_vector((x, y, z)) assert str(v) == ("[%s, %s, %s]" % (x, y, z)) def test_str_list_vector_with_zero(self): x, y, z = SpatialCoordinate(tetrahedron) v = as_vector((x, 0, 0)) assert str(v) == ("[%s, 0, 0]" % (x,)) def test_str_list_matrix(self): x, y = SpatialCoordinate(triangle) v = as_matrix(((2*x, 3*y), (4*x, 5*y))) a = str(2*x) b = str(3*y) c = str(4*x) d = str(5*y) assert str(v) == ("[\n [%s, %s],\n [%s, %s]\n]" % (a, b, c, d)) def test_str_list_matrix_with_zero(self): x, y = SpatialCoordinate(triangle) v = as_matrix(((2*x, 3*y), (0, 0))) a = str(2*x) b = str(3*y) c = str(as_vector((0, 0))) assert str(v) == ("[\n [%s, %s],\n%s\n]" % (a, b, c)) # FIXME: Add more tests for tensors collapsing # partly or completely into Zero! ufl-1.6.0/test/test_tensoralgebra.py000077500000000000000000000070441255567402100175430ustar00rootroot00000000000000#!/usr/bin/env py.test """ Test tensor algebra operators. """ import pytest from ufl import * @pytest.fixture(scope="module") def A(): return as_matrix([[2, 3], [4, 5]]) @pytest.fixture(scope="module") def B(): return as_matrix([[6, 7], [8, 9]]) @pytest.fixture(scope="module") def u(): return as_vector([10, 20]) @pytest.fixture(scope="module") def v(): return as_vector([30, 40]) def test_repeated_as_tensor(self, A, B, u, v): A2 = as_tensor(A) B2 = as_matrix(B) u2 = as_tensor(u) v2 = as_vector(v) assert A2 == A assert B2 == B assert u2 == u assert v2 == v def test_outer(self, A, B, u, v): C = outer(u, v) D = as_matrix([[10*30, 10*40], [20*30, 20*40]]) self.assertEqualValues(C, D) C = outer(A, v) A, v = A, v dims = (0, 1) D = as_tensor([[[A[i, j]*v[k] for k in dims] for j in dims] for i in dims]) self.assertEqualValues(C, D) # TODO: Test other ranks def test_inner(self, A, B, u, v): C = inner(A, B) D = 2*6 + 3*7 + 4*8 + 5*9 self.assertEqualValues(C, D) C = inner(u, v) D = 10*30 + 20*40 self.assertEqualValues(C, D) def test_pow2_inner(self, A, u): f = FacetNormal(triangle)[0] f2 = f*f assert f2 == inner(f, f) u2 = u**2 assert u2 == inner(u, u) A2 = A**2 assert A2 == inner(A, A) # Only tensor**2 notation is supported: self.assertRaises(UFLException, lambda: A**3) def test_dot(self, A, B, u, v): C = dot(u, v) D = 10*30 + 20*40 self.assertEqualValues(C, D) C = dot(A, B) dims = (0, 1) D = as_matrix([[sum(A[i, k]*B[k, j] for k in dims) \ for j in dims] for i in dims]) self.assertEqualValues(C, D) def test_cross(self): u = as_vector([3, 3, 3]) v = as_vector([2, 2, 2]) C = cross(u, v) D = zero(3) self.assertEqualValues(C, D) u = as_vector([3, 3, 0]) v = as_vector([-2, 2, 0]) C = cross(u, v) z = det(as_matrix([[3, 3], [-2, 2]])) D = as_vector([0, 0, z]) self.assertEqualValues(C, D) def xtest_dev(self, A): C = dev(A) D = 0*C # FIXME: Add expected value here self.assertEqualValues(C, D) def test_skew(self, A): C = skew(A) A, dims = A, (0, 1) D = 0.5*as_matrix([[A[i, j] - A[j, i] for j in dims] for i in dims]) self.assertEqualValues(C, D) def test_sym(self, A): C = sym(A) A, dims = A, (0, 1) D = 0.5*as_matrix([[A[i, j] + A[j, i] for j in dims] for i in dims]) self.assertEqualValues(C, D) def test_transpose(self, A): C = transpose(A) dims = (0, 1) D = as_matrix([[A[j, i] for j in dims] for i in dims]) self.assertEqualValues(C, D) def test_diag(self, A, u): dims = (0, 1) C = diag(A) D = as_matrix([[(0 if i != j else A[i, i]) for j in dims] for i in dims]) self.assertEqualValues(C, D) C = diag(u) D = as_matrix([[(0 if i != j else u[i]) for j in dims] for i in dims]) self.assertEqualValues(C, D) def test_diag_vector(self, A): dims = (0, 1) C = diag_vector(A) D = as_vector([A[i, i] for i in dims]) self.assertEqualValues(C, D) def test_tr(self, A): C = tr(A) A, dims = A, (0, 1) D = sum(A[i, i] for i in dims) self.assertEqualValues(C, D) def xtest_det(self, A): C = det(A) D = zero() # FIXME: Add expected value here self.assertEqualValues(C, D) def xtest_cofac(self, A): C = cofac(A) D = 0*C # FIXME: Add expected value here self.assertEqualValues(C, D) def xtest_inv(self, A): C = inv(A) D = 0*C # FIXME: Add expected value here self.assertEqualValues(C, D) ufl-1.6.0/test/test_utilities.py000077500000000000000000000113021255567402100167160ustar00rootroot00000000000000#!/usr/bin/env py.test """ Test internal utility functions. """ import pytest from six.moves import xrange as range from ufl.utils.indexflattening import shape_to_strides, flatten_multiindex, unflatten_index def test_shape_to_strides(): assert () == shape_to_strides(()) assert (1,) == shape_to_strides((3,)) assert (2, 1) == shape_to_strides((3, 2)) assert (4, 1) == shape_to_strides((3, 4)) assert (12, 4, 1) == shape_to_strides((6, 3, 4)) def test_flatten_multiindex_to_multiindex(): sh = (2, 3, 5) strides = shape_to_strides(sh) for i in range(sh[2]): for j in range(sh[1]): for k in range(sh[0]): index = (k, j, i) c = flatten_multiindex(index, strides) index2 = unflatten_index(c, strides) assert index == index2 def test_indexing_to_component(): assert 0 == flatten_multiindex( (), shape_to_strides(())) assert 0 == flatten_multiindex((0,), shape_to_strides((2,))) assert 1 == flatten_multiindex((1,), shape_to_strides((2,))) assert 3 == flatten_multiindex((1, 1), shape_to_strides((2, 2))) for i in range(5): for j in range(3): for k in range(2): assert 15*k+5*j+i == flatten_multiindex((k, j, i), shape_to_strides((2, 3, 5))) def test_component_numbering(): from ufl.permutation import build_component_numbering sh = (2, 2) sm = {(1, 0): (0, 1)} v, s = build_component_numbering(sh, sm) assert v == {(0, 1): 1, (1, 0): 1, (0, 0): 0, (1, 1): 2} assert s == [(0, 0), (0, 1), (1, 1)] sh = (3, 3) sm = {(1, 0): (0, 1), (2, 0): (0, 2), (2, 1): (1, 2)} v, s = build_component_numbering(sh, sm) assert v == {(0, 1): 1, (1, 2): 4, (0, 0): 0, (2, 1): 4, (1, 1): 3, (2, 0): 2, (2, 2): 5, (1, 0): 1, (0, 2): 2} assert s == [(0, 0), (0, 1), (0, 2), (1, 1), (1, 2), (2, 2)] def test_index_flattening(): from ufl.utils.indexflattening import shape_to_strides, flatten_multiindex, unflatten_index # Scalar shape s = () st = shape_to_strides(s) assert st == () c = () q = flatten_multiindex(c, st) c2 = unflatten_index(q, st) assert q == 0 assert c2 == () # Vector shape s = (2,) st = shape_to_strides(s) assert st == (1,) for i in range(s[0]): c = (i,) q = flatten_multiindex(c, st) c2 = unflatten_index(q, st) assert c == c2 # Tensor shape s = (2, 3) st = shape_to_strides(s) assert st == (3, 1) for i in range(s[0]): for j in range(s[1]): c = (i, j) q = flatten_multiindex(c, st) c2 = unflatten_index(q, st) assert c == c2 # Rank 3 tensor shape s = (2, 3, 4) st = shape_to_strides(s) assert st == (12, 4, 1) for i in range(s[0]): for j in range(s[1]): for k in range(s[2]): c = (i, j, k) q = flatten_multiindex(c, st) c2 = unflatten_index(q, st) assert c == c2 # Taylor-Hood example: # pressure element is index 3: c = (3,) # get flat index: i = flatten_multiindex(c, shape_to_strides((4,))) # remove offset: i -= 3 # map back to scalar component: c2 = unflatten_index(i, shape_to_strides(())) assert () == c2 # vector element y-component is index 1: c = (1,) # get flat index: i = flatten_multiindex(c, shape_to_strides((4,))) # remove offset: i -= 0 # map back to vector component: c2 = unflatten_index(i, shape_to_strides((3,))) assert (1,) == c2 # Try a tensor/vector element: mixed_shape = (6,) ts = (2, 2) vs = (2,) offset = 4 # product(ts) # vector element y-component is index offset+1: c = (offset + 1,) # get flat index: i = flatten_multiindex(c, shape_to_strides(mixed_shape)) # remove offset: i -= offset # map back to vector component: c2 = unflatten_index(i, shape_to_strides(vs)) assert (1,) == c2 for k in range(4): # tensor element (1,1)-component is index 3: c = (k,) # get flat index: i = flatten_multiindex(c, shape_to_strides(mixed_shape)) # remove offset: i -= 0 # map back to tensor component: c2 = unflatten_index(i, shape_to_strides(ts)) assert (k//2, k%2) == c2 def test_stackdict(): from ufl.common import StackDict d = StackDict(a=1) assert d["a"] == 1 d.push("a", 2) assert d["a"] == 2 d.push("a", 3) d.push("b", 9) assert d["a"] == 3 assert d["b"] == 9 d.pop() assert d["a"] == 3 assert "b" not in d d.pop() assert d["a"] == 2 d.pop() assert d["a"] == 1 ufl-1.6.0/ufl/000077500000000000000000000000001255567402100131015ustar00rootroot00000000000000ufl-1.6.0/ufl/__init__.py000066400000000000000000000250161255567402100152160ustar00rootroot00000000000000"""The Unified Form Language is an embedded domain specific language for definition of variational forms intended for finite element discretization. More precisely, it defines a fixed interface for choosing finite element spaces and defining expressions for weak forms in a notation close to mathematical notation. This Python module contains the language as well as algorithms to work with it. * To import the language, type:: from ufl import * * To import the underlying classes an UFL expression tree is built from, type:: from ufl.classes import * * Various algorithms for working with UFL expression trees can be found in:: from ufl.algorithms import * The classes and algorithms are considered implementation details and should not be used in form definitions. For more details on the language, see http://www.fenicsproject.org and http://arxiv.org/abs/1211.4047 The development version can be found in the repository at https://www.bitbucket.org/fenics-project/ufl A very brief overview of the language contents follows: * Domains:: Domain, ProductDomain * Cells:: Cell, ProductCell, OuterProductCell, interval, triangle, tetrahedron, quadrilateral, hexahedron * Sobolev spaces:: L2, H1, H2, HDiv, HCurl * Elements:: FiniteElement, MixedElement, VectorElement, TensorElement EnrichedElement, RestrictedElement, TensorProductElement, OuterProductElement, OuterProductVectorElement, HDiv, HCurl BrokenElement, TraceElement FacetElement, InteriorElement * Arguments:: Argument, TestFunction, TrialFunction * Coefficients:: Coefficient, Constant, VectorConstant, TensorConstant * Splitting form arguments in mixed spaces:: split * Literal constants:: Identity, PermutationSymbol * Geometric quantities:: SpatialCoordinate, FacetNormal, CellNormal, CellVolume, Circumradius, MinCellEdgeLength, MaxCellEdgeLength, FacetArea, MinFacetEdgeLength, MaxFacetEdgeLength, * Indices:: Index, indices, i, j, k, l, p, q, r, s * Scalar to tensor expression conversion:: as_tensor, as_vector, as_matrix * Unit vectors and matrices:: unit_vector, unit_vectors, unit_matrix, unit_matrices * Tensor algebra operators:: outer, inner, dot, cross, perp, det, inv, cofac, transpose, tr, diag, diag_vector, dev, skew, sym * Elementwise tensor operators:: elem_mult, elem_div, elem_pow, elem_op * Differential operators:: variable, diff, grad, div, nabla_grad, nabla_div, Dx, Dn, curl, rot * Nonlinear functions:: max_value, min_value, abs, sign, sqrt, exp, ln, erf, cos, sin, tan, acos, asin, atan, atan_2, cosh, sinh, tanh, bessel_J, bessel_Y, bessel_I, bessel_K * Discontinuous Galerkin operators: jump, avg, v('+'), v('-'), cell_avg, facet_avg * Conditional operators:: eq, ne, le, ge, lt, gt, <, >, <=, >=, And, Or, Not, conditional * Integral measures:: dx, ds, dS, dP, dc, dC, dO, dI, ds_b, ds_t, ds_tb, ds_v, dS_h, dS_v * Form transformations:: rhs, lhs, system, functional, replace, replace_integral_domains, adjoint, action, energy_norm, sensitivity_rhs, derivative """ # Copyright (C) 2008-2014 Martin Sandve Alnes and Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Kristian B. Oelgaard, 2009, 2011 # Modified by Anders Logg, 2009. # Modified by Johannes Ring, 2014. # Modified by Andrew T. T. McRae, 2014 # Modified by Lawrence Mitchell, 2014 __version__ = "1.6.0" ########## README # Imports here should be what the user sees when doing "from ufl import *", # which means we should _not_ import f.ex. "Grad", but "grad". # This way we expose the language, the operation "grad", but less # of the implementation, the particular class "Grad". ########## # Utility functions (product is the counterpart of the built-in # python function sum, can be useful for users as well?) from ufl.common import product # Output control from ufl.log import get_handler, get_logger, set_handler, set_level, add_logfile, \ UFLException, DEBUG, INFO, WARNING, ERROR, CRITICAL # Types for geometric quantities from ufl.cell import as_cell, Cell, ProductCell, OuterProductCell from ufl.domain import as_domain, Domain, ProductDomain from ufl.geometry import ( SpatialCoordinate, FacetNormal, CellNormal, CellVolume, Circumradius, MinCellEdgeLength, MaxCellEdgeLength, FacetArea, MinFacetEdgeLength, MaxFacetEdgeLength, ) # Sobolev spaces from ufl.sobolevspace import L2, H1, H2, HDiv, HCurl # Finite elements classes from ufl.finiteelement import FiniteElementBase, FiniteElement, \ MixedElement, VectorElement, TensorElement, EnrichedElement, \ RestrictedElement, TensorProductElement, OuterProductElement, \ OuterProductVectorElement, HDiv, HCurl, BrokenElement, TraceElement, \ FacetElement, InteriorElement # Hook to extend predefined element families from ufl.finiteelement.elementlist import register_element, show_elements #, ufl_elements # Arguments from ufl.argument import Argument, TestFunction, TrialFunction, \ Arguments, TestFunctions, TrialFunctions # Coefficients from ufl.coefficient import Coefficient, Coefficients, \ Constant, VectorConstant, TensorConstant # Split function from ufl.split_functions import split # Literal constants from ufl.constantvalue import PermutationSymbol, Identity, zero, as_ufl # Indexing of tensor expressions from ufl.core.multiindex import Index, indices # Special functions for expression base classes # (ensure this is imported, since it attaches operators to Expr) import ufl.exproperators as __exproperators # Containers for expressions with value rank > 0 from ufl.tensors import as_tensor, as_vector, as_matrix, relabel from ufl.tensors import unit_vector, unit_vectors, unit_matrix, unit_matrices # Operators from ufl.operators import rank, shape, \ outer, inner, dot, cross, perp, \ det, inv, cofac, \ transpose, tr, diag, diag_vector, \ dev, skew, sym, \ sqrt, exp, ln, erf, \ cos, sin, tan, \ acos, asin, atan, atan_2, \ cosh, sinh, tanh, \ bessel_J, bessel_Y, bessel_I, bessel_K, \ eq, ne, le, ge, lt, gt, And, Or, Not, \ conditional, sign, max_value, min_value, Max, Min, \ variable, diff, \ Dx, grad, div, curl, rot, nabla_grad, nabla_div, Dn, exterior_derivative, \ jump, avg, cell_avg, facet_avg, \ elem_mult, elem_div, elem_pow, elem_op # Measure classes from ufl.measure import Measure, register_integral_type, integral_types # Form class from ufl.form import Form, replace_integral_domains # Integral classes from ufl.integral import Integral # Special functions for Measure class # (ensure this is imported, since it attaches operators to Measure) import ufl.measureoperators as __measureoperators # Representations of transformed forms from ufl.formoperators import replace, derivative, action, energy_norm, rhs, lhs,\ system, functional, adjoint, sensitivity_rhs #, dirichlet_functional # Predefined convenience objects from ufl.objects import ( vertex, interval, triangle, tetrahedron, quadrilateral, hexahedron, facet, i, j, k, l, p, q, r, s, dx, ds, dS, dP, dc, dC, dO, dI, ds_b, ds_t, ds_tb, ds_v, dS_h, dS_v ) # Useful constants from math import e, pi __all__ = [ 'product', 'get_handler', 'get_logger', 'set_handler', 'set_level', 'add_logfile', 'UFLException', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', 'as_cell', 'Cell', 'ProductCell', 'OuterProductCell', 'as_domain', 'Domain', 'ProductDomain', 'L2', 'H1', 'H2', 'HCurl', 'HDiv', 'SpatialCoordinate', 'CellVolume', 'Circumradius', 'MinCellEdgeLength', 'MaxCellEdgeLength', 'FacetArea', 'MinFacetEdgeLength', 'MaxFacetEdgeLength', 'FacetNormal', 'CellNormal', 'FiniteElementBase', 'FiniteElement', 'MixedElement', 'VectorElement', 'TensorElement', 'EnrichedElement', 'RestrictedElement', 'TensorProductElement', 'OuterProductElement', 'OuterProductVectorElement', 'HDiv', 'HCurl', 'BrokenElement', 'TraceElement', 'FacetElement', 'InteriorElement', 'register_element', 'show_elements', 'Argument', 'TestFunction', 'TrialFunction', 'Arguments', 'TestFunctions', 'TrialFunctions', 'Coefficient', 'Coefficients', 'Constant', 'VectorConstant', 'TensorConstant', 'split', 'PermutationSymbol', 'Identity', 'zero', 'as_ufl', 'Index', 'indices', 'as_tensor', 'as_vector', 'as_matrix', 'relabel', 'unit_vector', 'unit_vectors', 'unit_matrix', 'unit_matrices', 'rank', 'shape', 'outer', 'inner', 'dot', 'cross', 'perp', 'det', 'inv', 'cofac', 'transpose', 'tr', 'diag', 'diag_vector', 'dev', 'skew', 'sym', 'sqrt', 'exp', 'ln', 'erf', 'cos', 'sin', 'tan', 'acos', 'asin', 'atan', 'atan_2', 'cosh', 'sinh', 'tanh', 'bessel_J', 'bessel_Y', 'bessel_I', 'bessel_K', 'eq', 'ne', 'le', 'ge', 'lt', 'gt', 'And', 'Or', 'Not', 'conditional', 'sign', 'max_value', 'min_value', 'Max', 'Min', 'variable', 'diff', 'Dx', 'grad', 'div', 'curl', 'rot', 'nabla_grad', 'nabla_div', 'Dn', 'exterior_derivative', 'jump', 'avg', 'cell_avg', 'facet_avg', 'elem_mult', 'elem_div', 'elem_pow', 'elem_op', 'Form', 'Integral', 'Measure', 'register_integral_type', 'integral_types', 'replace', 'replace_integral_domains', 'derivative', 'action', 'energy_norm', 'rhs', 'lhs', 'system', 'functional', 'adjoint', 'sensitivity_rhs', 'dx', 'ds', 'dS', 'dP', 'dc', 'dC', 'dO', 'dI', 'ds_b', 'ds_t', 'ds_tb', 'ds_v', 'dS_h', 'dS_v', 'vertex', 'interval', 'triangle', 'tetrahedron', 'quadrilateral', 'hexahedron', 'facet', 'i', 'j', 'k', 'l', 'p', 'q', 'r', 's', 'e', 'pi', ] ufl-1.6.0/ufl/algebra.py000066400000000000000000000263301255567402100150540ustar00rootroot00000000000000"Basic algebra operations." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2008 from itertools import chain from six import iteritems from ufl.log import error, warning from ufl.assertions import ufl_assert from ufl.common import product, mergedicts2, subdict, EmptyDict from ufl.core.expr import Expr from ufl.core.operator import Operator from ufl.constantvalue import Zero, zero, ScalarValue, IntValue, as_ufl from ufl.checks import is_ufl_scalar, is_true_ufl_scalar from ufl.index_combination_utils import merge_unique_indices from ufl.sorting import sorted_expr from ufl.precedence import parstr from ufl.core.ufl_type import ufl_type #--- Algebraic operators --- @ufl_type(num_ops=2, inherit_shape_from_operand=0, inherit_indices_from_operand=0, binop="__add__", rbinop="__radd__") class Sum(Operator): __slots__ = () def __new__(cls, a, b): # Make sure everything is an Expr a = as_ufl(a) b = as_ufl(b) # Assert consistent tensor properties sh = a.ufl_shape fi = a.ufl_free_indices fid = a.ufl_index_dimensions if b.ufl_shape != sh: error("Can't add expressions with different shapes.") if b.ufl_free_indices != fi: error("Can't add expressions with different free indices.") if b.ufl_index_dimensions != fid: error("Can't add expressions with different index dimensions.") # Skip adding zero if isinstance(a, Zero): return b elif isinstance(b, Zero): return a # Handle scalars specially and sort operands sa = isinstance(a, ScalarValue) sb = isinstance(b, ScalarValue) if sa and sb: # Apply constant propagation return as_ufl(a._value + b._value) elif sa: # Place scalar first #operands = (a, b) pass #a, b = a, b elif sb: # Place scalar first #operands = (b, a) a, b = b, a #elif a == b: # # Replace a+b with 2*foo # return 2*a else: # Otherwise sort operands in a canonical order #operands = (b, a) a, b = sorted_expr((a, b)) # construct and initialize a new Sum object self = Operator.__new__(cls) self._init(a, b) return self def _init(self, a, b): self.ufl_operands = (a, b) def __init__(self, a, b): Operator.__init__(self) def evaluate(self, x, mapping, component, index_values): return sum(o.evaluate(x, mapping, component, index_values) for o in self.ufl_operands) def __str__(self): ops = [parstr(o, self) for o in self.ufl_operands] if False: # Implementation with line splitting: limit = 70 delimop = " + \\\n + " op = " + " s = ops[0] n = len(s) for o in ops[1:]: m = len(o) if n+m > limit: s += delimop n = m else: s += op n += m s += o return s # Implementation with no line splitting: return "%s" % " + ".join(ops) def __repr__(self): return "Sum(%s)" % ", ".join(repr(o) for o in self.ufl_operands) @ufl_type(num_ops=2, binop="__mul__", rbinop="__rmul__") class Product(Operator): """The product of two or more UFL objects.""" __slots__ = ("ufl_free_indices", "ufl_index_dimensions",) def __new__(cls, a, b): # Conversion a = as_ufl(a) b = as_ufl(b) # Type checking # Make sure everything is scalar if a.ufl_shape or b.ufl_shape: error("Product can only represent products of scalars.") # Simplification if isinstance(a, Zero) or isinstance(b, Zero): # Got any zeros? Return zero. fi, fid = merge_unique_indices(a.ufl_free_indices, a.ufl_index_dimensions, b.ufl_free_indices, b.ufl_index_dimensions) return Zero((), fi, fid) sa = isinstance(a, ScalarValue) sb = isinstance(b, ScalarValue) if sa and sb: # const * const = const # FIXME: Handle free indices like with zero? I think IntValue may be index annotated now? return as_ufl(a._value * b._value) elif sa: # 1 * b = b if a._value == 1: return b # a, b = a, b elif sb: # a * 1 = a if b._value == 1: return a a, b = b, a #elif a == b: # a * a = a**2 # TODO: Why? Maybe just remove this? # if not a.ufl_free_indices: # return a**2 else: # a * b = b * a # Sort operands in a semi-canonical order # (NB! This is fragile! Small changes here can have large effects.) a, b = sorted_expr((a, b)) # Construction self = Operator.__new__(cls) self._init(a, b) return self def _init(self, a, b): "Constructor, called by __new__ with already checked arguments." self.ufl_operands = (a, b) # Extract indices fi, fid = merge_unique_indices(a.ufl_free_indices, a.ufl_index_dimensions, b.ufl_free_indices, b.ufl_index_dimensions) self.ufl_free_indices = fi self.ufl_index_dimensions = fid def __init__(self, a, b): Operator.__init__(self) ufl_shape = () def evaluate(self, x, mapping, component, index_values): ops = self.ufl_operands sh = self.ufl_shape if sh: ufl_assert(sh == ops[-1].ufl_shape, "Expecting nonscalar product operand to be the last by convention.") tmp = ops[-1].evaluate(x, mapping, component, index_values) ops = ops[:-1] else: tmp = 1 for o in ops: tmp *= o.evaluate(x, mapping, (), index_values) return tmp def __str__(self): a, b = self.ufl_operands return " * ".join((parstr(a, self), parstr(b, self))) def __repr__(self): return "Product(%r, %r)" % self.ufl_operands @ufl_type(num_ops=2, inherit_indices_from_operand=0, binop="__div__", rbinop="__rdiv__") class Division(Operator): __slots__ = () def __new__(cls, a, b): # Conversion a = as_ufl(a) b = as_ufl(b) # Type checking # TODO: Enabled workaround for nonscalar division in __div__, # so maybe we can keep this assertion. Some algorithms may need updating. if not is_ufl_scalar(a): error("Expecting scalar nominator in Division.") if not is_true_ufl_scalar(b): error("Division by non-scalar is undefined.") if isinstance(b, Zero): error("Division by zero!") # Simplification # Simplification a/b -> a if isinstance(a, Zero) or (isinstance(b, ScalarValue) and b._value == 1): return a # Simplification "literal a / literal b" -> "literal value of a/b" # Avoiding integer division by casting to float if isinstance(a, ScalarValue) and isinstance(b, ScalarValue): return as_ufl(float(a._value) / float(b._value)) # Simplification "a / a" -> "1" #if not a.ufl_free_indices and not a.ufl_shape and a == b: # return as_ufl(1) # Construction self = Operator.__new__(cls) self._init(a, b) return self def _init(self, a, b): self.ufl_operands = (a, b) def __init__(self, a, b): Operator.__init__(self) ufl_shape = () # self.ufl_operands[0].ufl_shape def evaluate(self, x, mapping, component, index_values): a, b = self.ufl_operands a = a.evaluate(x, mapping, component, index_values) b = b.evaluate(x, mapping, component, index_values) # Avoiding integer division by casting to float return float(a) / float(b) def __str__(self): return "%s / %s" % (parstr(self.ufl_operands[0], self), parstr(self.ufl_operands[1], self)) def __repr__(self): return "Division(%r, %r)" % (self.ufl_operands[0], self.ufl_operands[1]) @ufl_type(num_ops=2, inherit_indices_from_operand=0, binop="__pow__", rbinop="__rpow__") class Power(Operator): __slots__ = () def __new__(cls, a, b): # Conversion a = as_ufl(a) b = as_ufl(b) # Type checking if not is_true_ufl_scalar(a): error("Cannot take the power of a non-scalar expression.") if not is_true_ufl_scalar(b): error("Cannot raise an expression to a non-scalar power.") # Simplification if isinstance(a, ScalarValue) and isinstance(b, ScalarValue): return as_ufl(a._value ** b._value) if isinstance(a, Zero) and isinstance(b, ScalarValue): bf = float(b) if bf < 0: error("Division by zero, cannot raise 0 to a negative power.") else: return zero() if isinstance(b, ScalarValue) and b._value == 1: return a if isinstance(b, Zero): return IntValue(1) # Construction self = Operator.__new__(cls) self._init(a, b) return self def _init(self, a, b): self.ufl_operands = (a, b) def __init__(self, a, b): Operator.__init__(self) ufl_shape = () def evaluate(self, x, mapping, component, index_values): a, b = self.ufl_operands a = a.evaluate(x, mapping, component, index_values) b = b.evaluate(x, mapping, component, index_values) return a**b def __str__(self): a, b = self.ufl_operands return "%s ** %s" % (parstr(a, self), parstr(b, self)) def __repr__(self): return "Power(%r, %r)" % self.ufl_operands @ufl_type(num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0, unop="__abs__") class Abs(Operator): __slots__ = () def __init__(self, a): Operator.__init__(self, (a,)) ufl_assert(isinstance(a, Expr), "Expecting Expr instance.") if not isinstance(a, Expr): error("Expecting Expr instances.") def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) return abs(a) def __str__(self): a, = self.ufl_operands return "|%s|" % (parstr(a, self),) def __repr__(self): return "Abs(%r)" % self.ufl_operands ufl-1.6.0/ufl/algorithms/000077500000000000000000000000001255567402100152525ustar00rootroot00000000000000ufl-1.6.0/ufl/algorithms/__init__.py000066400000000000000000000077121255567402100173720ustar00rootroot00000000000000"This module collects algorithms and utility functions operating on UFL objects." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2008-2009. # Utilities for traversing over expression trees in different ways from ufl.algorithms.traversal import iter_expressions # Keeping these imports here for backwards compatibility, doesn't cost anything. # Prefer importing from ufl.corealg.traversal in future code. from ufl.corealg.traversal import pre_traversal, post_traversal from ufl.corealg.traversal import traverse_terminals, traverse_unique_terminals # Utilities for extracting information from forms and expressions from ufl.algorithms.analysis import ( extract_type, extract_arguments, extract_coefficients, extract_arguments_and_coefficients, extract_elements, extract_unique_elements, extract_sub_elements, sort_elements, ) # Preprocessing a form to extract various meta data from ufl.algorithms.formdata import FormData from ufl.algorithms.compute_form_data import compute_form_data # Utilities for checking properties of forms from ufl.algorithms.predicates import is_multilinear from ufl.algorithms.signature import compute_form_signature # Utilities for error checking of forms from ufl.algorithms.checks import validate_form # Utilites for modifying expressions and forms from ufl.corealg.multifunction import MultiFunction from ufl.algorithms.transformer import Transformer, is_post_handler, \ apply_transformer, \ ReuseTransformer, ufl2ufl, \ CopyTransformer, ufl2uflcopy, \ VariableStripper, strip_variables from ufl.algorithms.replace import Replacer, replace from ufl.algorithms.change_to_reference import change_to_reference_grad from ufl.algorithms.expand_compounds import CompoundExpander, expand_compounds, \ CompoundExpanderPreDiff, expand_compounds_prediff, \ CompoundExpanderPostDiff, expand_compounds_postdiff from ufl.algorithms.estimate_degrees import SumDegreeEstimator, estimate_total_polynomial_degree from ufl.algorithms.argument_dependencies import ArgumentDependencyExtracter, extract_argument_dependencies, NotMultiLinearException from ufl.algorithms.renumbering import renumber_indices from ufl.algorithms.expand_indices import expand_indices, purge_list_tensors from ufl.algorithms.propagate_restrictions import propagate_restrictions # Utilities for transforming complete Forms into other Forms from ufl.algorithms.formtransformations import ( compute_form_adjoint, compute_form_action, compute_energy_norm, compute_form_lhs, compute_form_rhs, compute_form_functional, compute_form_arities) # Utilities for Automatic Functional Differentiation from ufl.algorithms.ad import expand_derivatives #, compute_diff, propagate_spatial_derivatives, compute_form_derivative # Utilities for form file handling from ufl.algorithms.formfiles import read_ufl_file, load_ufl_file, load_forms # Utilities for UFL object printing from ufl.formatting.printing import integral_info, form_info, tree_format from ufl.formatting.ufl2latex import ufl2latex, ufl2tex, ufl2pdf, forms2latexdocument from ufl.formatting.ufl2dot import ufl2dot ufl-1.6.0/ufl/algorithms/ad.py000066400000000000000000000060451255567402100162150ustar00rootroot00000000000000"""Front-end for AD routines.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2009. from ufl.log import debug, error from ufl.assertions import ufl_assert from ufl.classes import Terminal, Derivative from ufl.algorithms.map_integrands import map_integrands from ufl.algorithms.expand_compounds import expand_compounds, expand_compounds_postdiff from ufl.algorithms.forward_ad import apply_nested_forward_ad from ufl.algorithms.apply_derivatives import apply_derivatives def expand_derivatives(form, dim=None, apply_expand_compounds_before=True, apply_expand_compounds_after=False, use_alternative_wrapper_algorithm=False): """Expand all derivatives of expr. In the returned expression g which is mathematically equivalent to expr, there are no VariableDerivative or CoefficientDerivative objects left, and Grad objects have been propagated to Terminal nodes. Note: Passing dim is now unnecessary, and the argument is ignored. """ def _expand_derivatives(expression): #print '_expand_derivatives:', expression # Expand compound expressions or not, in the future this # should be removed from here and applied on the outside. if apply_expand_compounds_before: expression = expand_compounds(expression) #print 'after expand_compounds', expression # Apply recursive forward mode AD #expression = apply_nested_forward_ad(expression) expression = apply_derivatives(expression) # FIXME: Form compilers assume expand_compounds have been applied. # This means quite a bit of work to handle all compounds # through the entire jit chain. For now, just test if we # can apply compounds afterwards, to focus on fixing issues # in the AD algorithm for compounds. Since this is optional, # alternative form compilers can then disable expand_compounds alltogether. if apply_expand_compounds_after: # FIXME: Test expand_compounds_postdiff, it should make this algorithm viable for existing FFC code #expression = expand_compounds(expression) expression = expand_compounds_postdiff(expression) return expression # Apply chosen algorithm to all integrands return map_integrands(_expand_derivatives, form) ufl-1.6.0/ufl/algorithms/analysis.py000066400000000000000000000166621255567402100174620ustar00rootroot00000000000000"""Utility algorithms for inspection of and information extraction from UFL objects in various ways.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2009-2010. # Modified by Johan Hake, 2010. from itertools import chain from six.moves import zip from collections import namedtuple from ufl.log import error, warning, info from ufl.assertions import ufl_assert from ufl.sorting import topological_sorting from ufl.utils.sorting import sorted_by_count from ufl.core.expr import Expr from ufl.core.terminal import Terminal, FormArgument from ufl.finiteelement import MixedElement, RestrictedElement from ufl.argument import Argument from ufl.coefficient import Coefficient from ufl.variable import Variable from ufl.core.multiindex import Index, MultiIndex from ufl.geometry import Domain from ufl.integral import Measure, Integral from ufl.form import Form from ufl.algorithms.traversal import iter_expressions from ufl.corealg.traversal import pre_traversal, traverse_terminals # TODO: Some of these can possibly be optimised by implementing inlined stack based traversal algorithms def _sorted_by_number_and_part(seq): return sorted(seq, key=lambda x: (x.number(), x.part())) def unique_tuple(objects): "Return tuple of unique objects, preserving initial ordering." unique_objects = [] handled = set() for obj in objects: if obj not in handled: handled.add(obj) unique_objects.append(obj) return tuple(unique_objects) #--- Utilities to extract information from an expression --- def __unused__extract_classes(a): """Build a set of all unique Expr subclasses used in a. The argument a can be a Form, Integral or Expr.""" return set(o._ufl_class_ for e in iter_expressions(a) for o in pre_traversal(e)) def extract_type(a, ufl_type): """Build a set of all objects of class ufl_type found in a. The argument a can be a Form, Integral or Expr.""" if issubclass(ufl_type, Terminal): # Optimization return set(o for e in iter_expressions(a) for o in traverse_terminals(e) if isinstance(o, ufl_type)) else: return set(o for e in iter_expressions(a) for o in pre_traversal(e) if isinstance(o, ufl_type)) def has_type(a, ufl_type): """Return if an object of class ufl_type can be found in a. The argument a can be a Form, Integral or Expr.""" if issubclass(ufl_type, Terminal): # Optimization traversal = traverse_terminals else: traversal = pre_traversal return any(isinstance(o, ufl_type) for e in iter_expressions(a) for o in traversal(e)) def has_exact_type(a, ufl_type): """Return if an object of class ufl_type can be found in a. The argument a can be a Form, Integral or Expr.""" tc = ufl_type._ufl_typecode_ if issubclass(ufl_type, Terminal): # Optimization traversal = traverse_terminals else: traversal = pre_traversal return any(o._ufl_typecode_ == tc for e in iter_expressions(a) for o in traversal(e)) def extract_arguments(a): """Build a sorted list of all arguments in a, which can be a Form, Integral or Expr.""" return _sorted_by_number_and_part(extract_type(a, Argument)) def extract_coefficients(a): """Build a sorted list of all coefficients in a, which can be a Form, Integral or Expr.""" return sorted_by_count(extract_type(a, Coefficient)) def extract_arguments_and_coefficients(a): """Build two sorted lists of all arguments and coefficients in a, which can be a Form, Integral or Expr.""" # This function is faster than extract_arguments + extract_coefficients # for large forms, and has more validation built in. # Extract lists of all form argument instances terminals = extract_type(a, FormArgument) arguments = [f for f in terminals if isinstance(f, Argument)] coefficients = [f for f in terminals if isinstance(f, Coefficient)] # Build number,part: instance mappings, should be one to one bfnp = dict((f, (f.number(), f.part())) for f in arguments) if len(bfnp) != len(set(bfnp.values())): msg = """\ Found different Arguments with same number and part. Did you combine test or trial functions from different spaces? The Arguments found are:\n%s""" % "\n".join(" %s" % f for f in arguments) error(msg) # Build count: instance mappings, should be one to one fcounts = dict((f, f.count()) for f in coefficients) if len(fcounts) != len(set(fcounts.values())): msg = """\ Found different coefficients with same counts. The arguments found are:\n%s""" % "\n".join(" %s" % f for f in coefficients) error(msg) # Passed checks, so we can safely sort the instances by count arguments = _sorted_by_number_and_part(arguments) coefficients = sorted_by_count(coefficients) return arguments, coefficients def extract_elements(form): "Build sorted tuple of all elements used in form." args = chain(*extract_arguments_and_coefficients(form)) return tuple(f.element() for f in args) def extract_unique_elements(form): "Build sorted tuple of all unique elements used in form." return unique_tuple(extract_elements(form)) def extract_sub_elements(elements): "Build sorted tuple of all sub elements (including parent element)." sub_elements = tuple(chain(*[e.sub_elements() for e in elements])) if not sub_elements: return tuple(elements) return tuple(elements) + extract_sub_elements(sub_elements) def __unused__extract_unique_sub_elements(elements): "Build sorted tuple of all unique sub elements (including parent element)." return unique_tuple(extract_sub_elements(elements)) def __unused__extract_element_map(elements): "Build map from elements to element index in ordered tuple." element_map = {} unique_elements = unique_tuple(elements) for element in elements: indices = [i for (i, e) in enumerate(unique_elements) if e == element] ufl_assert(len(indices) == 1, "Unable to find unique index for element.") element_map[element] = i return element_map def sort_elements(elements): """ Sort elements so that any sub elements appear before the corresponding mixed elements. This is useful when sub elements need to be defined before the corresponding mixed elements. The ordering is based on sorting a directed acyclic graph. """ # Set nodes nodes = elements # Set edges edges = dict((node, []) for node in nodes) for element in elements: for sub_element in element.sub_elements(): edges[element].append(sub_element) # Sort graph sorted_elements = topological_sorting(nodes, edges) # Reverse list of elements sorted_elements.reverse() return sorted_elements ufl-1.6.0/ufl/algorithms/apply_derivatives.py000066400000000000000000000744671255567402100214000ustar00rootroot00000000000000"""This module contains the apply_derivatives algorithm which computes the derivatives of a form of expression.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from ufl.assertions import ufl_assert from ufl.log import error from ufl.core.terminal import Terminal from ufl.core.multiindex import MultiIndex, Index, FixedIndex, indices from ufl.tensors import as_tensor, as_scalar, as_scalars, unit_indexed_tensor, unwrap_list_tensor from ufl.classes import ConstantValue, Identity, Zero, FloatValue from ufl.classes import Coefficient, FormArgument from ufl.classes import Grad, Variable from ufl.classes import Indexed, ListTensor, ComponentTensor from ufl.classes import ExprList, ExprMapping from ufl.classes import Product, Sum, IndexSum from ufl.constantvalue import is_true_ufl_scalar, is_ufl_scalar from ufl.operators import dot, inner, outer, lt, eq, conditional, sign, \ sqrt, exp, ln, cos, sin, tan, cosh, sinh, tanh, acos, asin, atan, atan_2, \ erf, bessel_J, bessel_Y, bessel_I, bessel_K, \ cell_avg, facet_avg from math import pi from ufl.corealg.multifunction import MultiFunction from ufl.corealg.map_dag import map_expr_dag from ufl.algorithms.map_integrands import map_integrand_dags # TODO: Add more rulesets? # - DivRuleset # - CurlRuleset # - ReferenceGradRuleset # - ReferenceDivRuleset class GenericDerivativeRuleset(MultiFunction): def __init__(self, var_shape): MultiFunction.__init__(self) self._var_shape = var_shape # --- Error checking for missing handlers and unexpected types def expr(self, o): error("Missing differentiation handler for type {0}. Have you added a new type?".format(o._ufl_class_.__name__)) def unexpected(self, o): error("Unexpected type {0} in AD rules.".format(o._ufl_class_.__name__)) def override(self, o): error("Type {0} must be overridden in specialized AD rule set.".format(o._ufl_class_.__name__)) def derivative(self, o): error("Unhandled derivative type {0}, nested differentiation has failed.".format(o._ufl_class_.__name__)) def fixme(self, o): error("FIXME: Unimplemented differentiation handler for type {0}.".format(o._ufl_class_.__name__)) # --- Helper functions for creating zeros with the right shapes def non_differentiable_terminal(self, o): "Labels and indices are not differentiable. It's convenient to return the non-differentiated object." return o def independent_terminal(self, o): "Return a zero with the right shape for terminals independent of differentiation variable." return Zero(o.ufl_shape + self._var_shape) def independent_operator(self, o): "Return a zero with the right shape and indices for operators independent of differentiation variable." return Zero(o.ufl_shape + self._var_shape, o.ufl_free_indices, o.ufl_index_dimensions) # --- All derivatives need to define grad and averaging grad = override cell_avg = override facet_avg = override # --- Default rules for terminals # Some types just don't have any derivative, this is just to make algorithm structure generic label = non_differentiable_terminal multi_index = non_differentiable_terminal # Literals are assumed independent of the differentiation variable by default constant_value = independent_terminal # Rules for form arguments must be specified in specialized rule set form_argument = override # Rules for geometric quantities must be specified in specialized rule set geometric_quantity = override # These types are currently assumed independent, but for non-affine domains # this no longer holds and we want to implement rules for them. facet_normal = independent_terminal spatial_coordinate = independent_terminal cell_coordinate = independent_terminal # Measures of cell entities, assuming independent although # this will not be true for all of these for non-affine domains cell_volume = independent_terminal circumradius = independent_terminal facet_area = independent_terminal #cell_surface_area = independent_terminal min_cell_edge_length = independent_terminal max_cell_edge_length = independent_terminal min_facet_edge_length = independent_terminal max_facet_edge_length = independent_terminal # Other stuff cell_orientation = independent_terminal quadrature_weigth = independent_terminal # These types are currently not expected to show up in AD pass. # To make some of these available to the end-user, they need to be implemented here. facet_coordinate = unexpected cell_origin = unexpected facet_origin = unexpected cell_facet_origin = unexpected jacobian = unexpected jacobian_determinant = unexpected jacobian_inverse = unexpected facet_jacobian = unexpected facet_jacobian_determinant = unexpected facet_jacobian_inverse = unexpected cell_facet_jacobian = unexpected cell_facet_jacobian_determinant = unexpected cell_facet_jacobian_inverse = unexpected cell_edge_vectors = unexpected facet_edge_vectors = unexpected cell_normal = unexpected # TODO: Expecting rename #cell_normals = unexpected #facet_tangents = unexpected #cell_tangents = unexpected #cell_midpoint = unexpected #facet_midpoint = unexpected # --- Default rules for operators def variable(self, o, df, l): return df # --- Indexing and component handling def indexed(self, o, Ap, ii): # TODO: (Partially) duplicated in nesting rules # Propagate zeros if isinstance(Ap, Zero): return self.independent_operator(o) # Untangle as_tensor(C[kk], jj)[ii] -> C[ll] to simplify resulting expression if isinstance(Ap, ComponentTensor): B, jj = Ap.ufl_operands if isinstance(B, Indexed): C, kk = B.ufl_operands kk = list(kk) if all(j in kk for j in jj): Cind = list(kk) for i, j in zip(ii, jj): Cind[kk.index(j)] = i return Indexed(C, MultiIndex(tuple(Cind))) # Otherwise a more generic approach r = Ap.rank() - len(ii) if r: kk = indices(r) op = Indexed(Ap, MultiIndex(ii.indices() + kk)) op = as_tensor(op, kk) else: op = Indexed(Ap, ii) return op def list_tensor(self, o, *dops): return ListTensor(*dops) def component_tensor(self, o, Ap, ii): if isinstance(Ap, Zero): op = self.independent_operator(o) else: Ap, jj = as_scalar(Ap) op = as_tensor(Ap, ii.indices() + jj) return op # --- Algebra operators def index_sum(self, o, Ap, i): return IndexSum(Ap, i) def sum(self, o, da, db): return da + db def product(self, o, da, db): # Even though arguments to o are scalar, da and db may be tensor valued a, b = o.ufl_operands (da, db), ii = as_scalars(da, db) pa = Product(da, b) pb = Product(a, db) s = Sum(pa, pb) if ii: s = as_tensor(s, ii) return s def division(self, o, fp, gp): f, g = o.ufl_operands ufl_assert(is_ufl_scalar(f), "Not expecting nonscalar nominator") ufl_assert(is_true_ufl_scalar(g), "Not expecting nonscalar denominator") #do_df = 1/g #do_dg = -h/g #op = do_df*fp + do_df*gp #op = (fp - o*gp) / g # Get o and gp as scalars, multiply, then wrap as a tensor again so, oi = as_scalar(o) sgp, gi = as_scalar(gp) o_gp = so * sgp if oi or gi: o_gp = as_tensor(o_gp, oi + gi) op = (fp - o_gp) / g return op def power(self, o, fp, gp): f, g = o.ufl_operands ufl_assert(is_true_ufl_scalar(f), "Expecting scalar expression f in f**g.") ufl_assert(is_true_ufl_scalar(g), "Expecting scalar expression g in f**g.") # Derivation of the general case: o = f(x)**g(x) #do/df = g * f**(g-1) = g / f * o #do/dg = ln(f) * f**g = ln(f) * o #do/df * df + do/dg * dg = o * (g / f * df + ln(f) * dg) if isinstance(gp, Zero): # This probably produces better results for the common case of f**constant op = fp * g * f**(g-1) else: # Note: This produces expressions like (1/w)*w**5 instead of w**4 #op = o * (fp * g / f + gp * ln(f)) # This reuses o op = f**(g-1) * (g*fp + f*ln(f)*gp) # This gives better accuracy in dolfin integration test # Example: d/dx[x**(x**3)]: #f = x #g = x**3 #df = 1 #dg = 3*x**2 #op1 = o * (fp * g / f + gp * ln(f)) # = x**(x**3) * (x**3/x + 3*x**2*ln(x)) #op2 = f**(g-1) * (g*fp + f*ln(f)*gp) # = x**(x**3-1) * (x**3 + x*3*x**2*ln(x)) return op def abs(self, o, df): f, = o.ufl_operands #return conditional(eq(f, 0), 0, Product(sign(f), df)) return sign(f) * df # --- Mathfunctions def math_function(self, o, df): # FIXME: Introduce a UserOperator type instead of this hack and define user derivative() function properly if hasattr(o, 'derivative'): f, = o.ufl_operands return df * o.derivative() error("Unknown math function.") def sqrt(self, o, fp): return fp / (2*o) def exp(self, o, fp): return fp * o def ln(self, o, fp): f, = o.ufl_operands ufl_assert(not isinstance(f, Zero), "Division by zero.") return fp / f def cos(self, o, fp): f, = o.ufl_operands return fp * -sin(f) def sin(self, o, fp): f, = o.ufl_operands return fp * cos(f) def tan(self, o, fp): f, = o.ufl_operands return 2.0*fp / (cos(2.0*f) + 1.0) def cosh(self, o, fp): f, = o.ufl_operands return fp * sinh(f) def sinh(self, o, fp): f, = o.ufl_operands return fp * cosh(f) def tanh(self, o, fp): f, = o.ufl_operands def sech(y): return (2.0*cosh(y)) / (cosh(2.0*y) + 1.0) return fp * sech(f)**2 def acos(self, o, fp): f, = o.ufl_operands return -fp / sqrt(1.0 - f**2) def asin(self, o, fp): f, = o.ufl_operands return fp / sqrt(1.0 - f**2) def atan(self, o, fp): f, = o.ufl_operands return fp / (1.0 + f**2) def atan_2(self, o, fp, gp): f, g = o.ufl_operands return (g*fp - f*gp) / (f**2 + g**2) def erf(self, o, fp): f, = o.ufl_operands return fp * (2.0 / sqrt(pi) * exp(-f**2)) # --- Bessel functions def bessel_j(self, o, nup, fp): nu, f = o.ufl_operands if not (nup is None or isinstance(nup, Zero)): error("Differentiation of bessel function w.r.t. nu is not supported.") if isinstance(nu, Zero): op = -bessel_J(1, f) else: op = 0.5 * (bessel_J(nu-1, f) - bessel_J(nu+1, f)) return op * fp def bessel_y(self, o, nup, fp): nu, f = o.ufl_operands if not (nup is None or isinstance(nup, Zero)): error("Differentiation of bessel function w.r.t. nu is not supported.") if isinstance(nu, Zero): op = -bessel_Y(1, f) else: op = 0.5 * (bessel_Y(nu-1, f) - bessel_Y(nu+1, f)) return op * fp def bessel_i(self, o, nup, fp): nu, f = o.ufl_operands if not (nup is None or isinstance(nup, Zero)): error("Differentiation of bessel function w.r.t. nu is not supported.") if isinstance(nu, Zero): op = bessel_I(1, f) else: op = 0.5 * (bessel_I(nu-1, f) + bessel_I(nu+1, f)) return op * fp def bessel_k(self, o, nup, fp): nu, f = o.ufl_operands if not (nup is None or isinstance(nup, Zero)): error("Differentiation of bessel function w.r.t. nu is not supported.") if isinstance(nu, Zero): op = -bessel_K(1, f) else: op = -0.5 * (bessel_K(nu-1, f) + bessel_K(nu+1, f)) return op * fp # --- Restrictions def restricted(self, o, fp): # Restriction and differentiation commutes, at least for the derivatives we support. if isinstance(fp, ConstantValue): return fp # TODO: Necessary? Can't restriction simplify directly instead? else: return fp(o._side) # (f+-)' == (f')+- # --- Conditionals def binary_condition(self, o, dl, dr): # Should not be used anywhere... return None def not_condition(self, o, c): # Should not be used anywhere... return None def conditional(self, o, dc, dt, df): if isinstance(dt, Zero) and isinstance(df, Zero): # Assuming dt and df have the same indices here, which should be the case return dt else: # Placing t[1],f[1] outside here to avoid getting arguments inside conditionals c, t, f = o.ufl_operands dc = conditional(c, 1, 0) return dc*dt + (1.0 - dc)*df def max_value(self, o, df, dg): #d/dx max(f, g) = # f > g: df/dx # f < g: dg/dx # Placing df,dg outside here to avoid getting arguments inside conditionals f, g = o.ufl_operands dc = conditional(f > g, 1, 0) return dc*df + (1.0 - dc)*dg def min_value(self, o, df, dg): #d/dx min(f, g) = # f < g: df/dx # else: dg/dx # Placing df,dg outside here to avoid getting arguments inside conditionals f, g = o.ufl_operands dc = conditional(f < g, 1, 0) return dc*df + (1.0 - dc)*dg class GradRuleset(GenericDerivativeRuleset): def __init__(self, geometric_dimension): GenericDerivativeRuleset.__init__(self, var_shape=(geometric_dimension,)) self._Id = Identity(geometric_dimension) # --- Specialized rules for geometric quantities def geometric_quantity(self, o): "dg/dx = 0 if piecewise constant, otherwise Grad(g)" if o.is_cellwise_constant(): return self.independent_terminal(o) else: # TODO: Which types does this involve? I don't think the form compilers will handle this. return Grad(o) def spatial_coordinate(self, o): "dx/dx = I" return self._Id def cell_coordinate(self, o): "dX/dx = inv(dx/dX) = inv(J) = K" return JacobianInverse(o.domain()) # TODO: Add more geometry types here, with non-affine domains several should be non-zero. # --- Specialized rules for form arguments def coefficient(self, o): if o.is_cellwise_constant(): return self.independent_terminal(o) return Grad(o) def argument(self, o): return Grad(o) def _argument(self, o): # TODO: Enable this after fixing issue#13, unless we move simplification to a separate stage? if o.is_cellwise_constant(): # Collapse gradient of cellwise constant function to zero return AnnotatedZero(o.ufl_shape + self._var_shape, arguments=(o,)) # TODO: Missing this type else: return Grad(o) # --- Nesting of gradients def grad(self, o): "Represent grad(grad(f)) as Grad(Grad(f))." # Check that o is a "differential terminal" ufl_assert(isinstance(o.ufl_operands[0], (Grad, Terminal)), "Expecting only grads applied to a terminal.") return Grad(o) def _grad(self, o): pass # TODO: Not sure how to detect that gradient of f is cellwise constant. # Can we trust element degrees? #if o.is_cellwise_constant(): # return self.terminal(o) # TODO: Maybe we can ask "f.has_derivatives_of_order(n)" to check # if we should make a zero here? # 1) n = count number of Grads, get f # 2) if not f.has_derivatives(n): return zero(...) cell_avg = GenericDerivativeRuleset.independent_operator facet_avg = GenericDerivativeRuleset.independent_operator class VariableRuleset(GenericDerivativeRuleset): def __init__(self, var): GenericDerivativeRuleset.__init__(self, var_shape=var.ufl_shape) ufl_assert(not var.ufl_free_indices, "Differentiation variable cannot have free indices.") self._variable = var self._Id = self._make_identity(self._var_shape) def _make_identity(self, sh): "Create a higher order identity tensor to represent dv/dv." res = None if sh == (): # Scalar dv/dv is scalar return FloatValue(1.0) elif len(sh) == 1: # Vector v makes dv/dv the identity matrix return Identity(sh[0]) else: # TODO: Add a type for this higher order identity? # II[i0,i1,i2,j0,j1,j2] = 1 if all((i0==j0, i1==j1, i2==j2)) else 0 # Tensor v makes dv/dv some kind of higher rank identity tensor ind1 = () ind2 = () for d in sh: i, j = indices(2) dij = Identity(d)[i, j] if res is None: res = dij else: res *= dij ind1 += (i,) ind2 += (j,) fp = as_tensor(res, ind1 + ind2) return fp # Explicitly defining dg/dw == 0 geometric_quantity = GenericDerivativeRuleset.independent_terminal # Explicitly defining da/dw == 0 argument = GenericDerivativeRuleset.independent_terminal def _argument(self, o): return AnnotatedZero(o.ufl_shape + self._var_shape, arguments=(o,)) # TODO: Missing this type def coefficient(self, o): """df/dv = Id if v is f else 0. Note that if v = variable(f), df/dv is still 0, but if v == f, i.e. isinstance(v, Coefficient) == True, then df/dv == df/df = Id. """ v = self._variable if isinstance(v, Coefficient) and o == v: # dv/dv = identity of rank 2*rank(v) return self._Id else: # df/v = 0 return self.independent_terminal(o) def variable(self, o, df, l): v = self._variable if isinstance(v, Variable) and v.label() == l: # dv/dv = identity of rank 2*rank(v) return self._Id else: # df/v = df return df def grad(self, o): "Variable derivative of a gradient of a terminal must be 0." # Check that o is a "differential terminal" ufl_assert(isinstance(o.ufl_operands[0], (Grad, Terminal)), "Expecting only grads applied to a terminal.") return self.independent_terminal(o) cell_avg = GenericDerivativeRuleset.independent_operator facet_avg = GenericDerivativeRuleset.independent_operator class GateauxDerivativeRuleset(GenericDerivativeRuleset): """Apply AFD (Automatic Functional Differentiation) to expression. Implements rules for the Gateaux derivative D_w[v](...) defined as D_w[v](e) = d/dtau e(w+tau v)|tau=0 """ def __init__(self, coefficients, arguments, coefficient_derivatives): GenericDerivativeRuleset.__init__(self, var_shape=()) # Type checking ufl_assert(isinstance(coefficients, ExprList), "Expecting a ExprList of coefficients.") ufl_assert(isinstance(arguments, ExprList), "Expecting a ExprList of arguments.") ufl_assert(isinstance(coefficient_derivatives, ExprMapping), "Expecting a coefficient-coefficient ExprMapping.") # The coefficient(s) to differentiate w.r.t. and the argument(s) s.t. D_w[v](e) = d/dtau e(w+tau v)|tau=0 self._w = coefficients.ufl_operands self._v = arguments.ufl_operands self._w2v = {w: v for w, v in zip(self._w, self._v)} # Build more convenient dict {f: df/dw} for each coefficient f where df/dw is nonzero cd = coefficient_derivatives.ufl_operands self._cd = {cd[2*i]: cd[2*i+1] for i in range(len(cd)//2)} # Explicitly defining dg/dw == 0 geometric_quantity = GenericDerivativeRuleset.independent_terminal # Explicitly defining da/dw == 0 argument = GenericDerivativeRuleset.independent_terminal def coefficient(self, o): # Define dw/dw := d/ds [w + s v] = v # Return corresponding argument if we can find o among w do = self._w2v.get(o) if do is not None: return do # Look for o among coefficient derivatives dos = self._cd.get(o) if dos is None: # If o is not among coefficient derivatives, return do/dw=0 do = Zero(o.ufl_shape) return do else: # Compute do/dw_j = do/dw_h : v. # Since we may actually have a tuple of oprimes and vs in a # 'mixed' space, sum over them all to get the complete inner # product. Using indices to define a non-compound inner product. # Example: # (f:g) -> (dfdu:v):g + f:(dgdu:v) # shape(dfdu) == shape(f) + shape(v) # shape(f) == shape(g) == shape(dfdu : v) # Make sure we have a tuple to match the self._v tuple if not isinstance(dos, tuple): dos = (dos,) ufl_assert(len(dos) == len(self._v), "Got a tuple of arguments, expecting a matching tuple of coefficient derivatives.") dosum = Zero(o.ufl_shape) for do, v in zip(dos, self._v): so, oi = as_scalar(do) rv = len(v.ufl_shape) oi1 = oi[:-rv] oi2 = oi[-rv:] prod = so*v[oi2] if oi1: dosum += as_tensor(prod, oi1) else: dosum += prod return dosum def cell_avg(self, o, fp): # Cell average of a single function and differentiation commutes, D_f[v](cell_avg(f)) = cell_avg(v) return cell_avg(fp) def facet_avg(self, o, fp): # Facet average of a single function and differentiation commutes, D_f[v](facet_avg(f)) = facet_avg(v) return cell_avg(fp) def grad(self, g): # If we hit this type, it has already been propagated # to a coefficient (or grad of a coefficient), # FIXME: Assert this! # so we need to take the gradient of the variation or return zero. # Complications occur when dealing with derivatives w.r.t. single components... # Figure out how many gradients are around the inner terminal ngrads = 0 o = g while isinstance(o, Grad): o, = o.ufl_operands ngrads += 1 if not isinstance(o, FormArgument): error("Expecting gradient of a FormArgument, not %r" % (o,)) def apply_grads(f): for i in range(ngrads): f = Grad(f) return f # Find o among all w without any indexing, which makes this easy for (w, v) in zip(self._w, self._v): if o == w and isinstance(v, FormArgument): # Case: d/dt [w + t v] return apply_grads(v) # If o is not among coefficient derivatives, return do/dw=0 gprimesum = Zero(g.ufl_shape) def analyse_variation_argument(v): # Analyse variation argument if isinstance(v, FormArgument): # Case: d/dt [w[...] + t v] vval, vcomp = v, () elif isinstance(v, Indexed): # Case: d/dt [w + t v[...]] # Case: d/dt [w[...] + t v[...]] vval, vcomp = v.ufl_operands vcomp = tuple(vcomp) else: error("Expecting argument or component of argument.") ufl_assert(all(isinstance(k, FixedIndex) for k in vcomp), "Expecting only fixed indices in variation.") return vval, vcomp def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): # Apply gradients directly to argument vval, # and get the right indexed scalar component(s) kk = indices(ngrads) Dvkk = apply_grads(vval)[vcomp+kk] # Place scalar component(s) Dvkk into the right tensor positions if wshape: Ejj, jj = unit_indexed_tensor(wshape, wcomp) else: Ejj, jj = 1, () gprimeterm = as_tensor(Ejj*Dvkk, jj+kk) return gprimeterm # Accumulate contributions from variations in different components for (w, v) in zip(self._w, self._v): # Analyse differentiation variable coefficient if isinstance(w, FormArgument): if not w == o: continue wshape = w.ufl_shape if isinstance(v, FormArgument): # Case: d/dt [w + t v] return apply_grads(v) elif isinstance(v, ListTensor): # Case: d/dt [w + t <...,v,...>] for wcomp, vsub in unwrap_list_tensor(v): if not isinstance(vsub, Zero): vval, vcomp = analyse_variation_argument(vsub) gprimesum = gprimesum + compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp) else: ufl_assert(wshape == (), "Expecting scalar coefficient in this branch.") # Case: d/dt [w + t v[...]] wval, wcomp = w, () vval, vcomp = analyse_variation_argument(v) gprimesum = gprimesum + compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp) elif isinstance(w, Indexed): # This path is tested in unit tests, but not actually used? # Case: d/dt [w[...] + t v[...]] # Case: d/dt [w[...] + t v] wval, wcomp = w.ufl_operands if not wval == o: continue assert isinstance(wval, FormArgument) ufl_assert(all(isinstance(k, FixedIndex) for k in wcomp), "Expecting only fixed indices in differentiation variable.") wshape = wval.ufl_shape vval, vcomp = analyse_variation_argument(v) gprimesum = gprimesum + compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp) else: error("Expecting coefficient or component of coefficient.") # FIXME: Handle other coefficient derivatives: oprimes = self._cd.get(o) if 0: oprimes = self._cd.get(o) if oprimes is None: if self._cd: # TODO: Make it possible to silence this message in particular? # It may be good to have for debugging... warning("Assuming d{%s}/d{%s} = 0." % (o, self._w)) else: # Make sure we have a tuple to match the self._v tuple if not isinstance(oprimes, tuple): oprimes = (oprimes,) ufl_assert(len(oprimes) == len(self._v), "Got a tuple of arguments, "+\ "expecting a matching tuple of coefficient derivatives.") # Compute dg/dw_j = dg/dw_h : v. # Since we may actually have a tuple of oprimes and vs in a # 'mixed' space, sum over them all to get the complete inner # product. Using indices to define a non-compound inner product. for (oprime, v) in zip(oprimes, self._v): error("FIXME: Figure out how to do this with ngrads") so, oi = as_scalar(oprime) rv = len(v.ufl_shape) oi1 = oi[:-rv] oi2 = oi[-rv:] prod = so*v[oi2] if oi1: gprimesum += as_tensor(prod, oi1) else: gprimesum += prod return gprimesum class DerivativeRuleDispatcher(MultiFunction): def __init__(self): MultiFunction.__init__(self) def terminal(self, o): return o def derivative(self, o): error("Missing derivative handler for {0}.".format(type(o).__name__)) expr = MultiFunction.reuse_if_untouched def grad(self, o, f): rules = GradRuleset(o.ufl_shape[-1]) return map_expr_dag(rules, f) def variable_derivative(self, o, f, dummy_v): rules = VariableRuleset(o.ufl_operands[1]) return map_expr_dag(rules, f) def coefficient_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): dummy, w, v, cd = o.ufl_operands rules = GateauxDerivativeRuleset(w, v, cd) return map_expr_dag(rules, f) def indexed(self, o, Ap, ii): # TODO: (Partially) duplicated in generic rules # Reuse if untouched if Ap is o.ufl_operands[0]: return o # Untangle as_tensor(C[kk], jj)[ii] -> C[ll] to simplify resulting expression if isinstance(Ap, ComponentTensor): B, jj = Ap.ufl_operands if isinstance(B, Indexed): C, kk = B.ufl_operands kk = list(kk) if all(j in kk for j in jj): Cind = list(kk) for i, j in zip(ii, jj): Cind[kk.index(j)] = i return Indexed(C, MultiIndex(tuple(Cind))) # Otherwise a more generic approach r = Ap.rank() - len(ii) if r: kk = indices(r) op = Indexed(Ap, MultiIndex(ii.indices() + kk)) op = as_tensor(op, kk) else: op = Indexed(Ap, ii) return op def apply_derivatives(expression): rules = DerivativeRuleDispatcher() return map_integrand_dags(rules, expression) ufl-1.6.0/ufl/algorithms/apply_restrictions.py000066400000000000000000000212511255567402100215620ustar00rootroot00000000000000"""This module contains the apply_restrictions algorithm which propagates restrictions in a form towards the terminals.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from ufl.assertions import ufl_assert from ufl.log import error from ufl.core.terminal import Terminal from ufl.classes import Coefficient, Argument, GeometricQuantity, Restricted from ufl.corealg.multifunction import MultiFunction from ufl.corealg.map_dag import map_expr_dag from ufl.algorithms.map_integrands import map_integrand_dags from ufl.measure import integral_type_to_measure_name class RestrictionPropagator(MultiFunction): def __init__(self, side=None): MultiFunction.__init__(self) self.current_restriction = side self.default_restriction = "+" if self.current_restriction is None: self._rp = { "+": RestrictionPropagator("+"), "-": RestrictionPropagator("-") } def restricted(self, o): "When hitting a restricted quantity, visit child with a separate restriction algorithm." # Assure that we have only two levels here, inside or outside the Restricted node ufl_assert(self.current_restriction is None, "Cannot restrict an expression twice.") # Configure a propagator for this side and apply to subtree return map_expr_dag(self._rp[o.side()], o.ufl_operands[0]) # FIXME: Reuse cache between these calls! # --- Reusable rules def _ignore_restriction(self, o): "Ignore current restriction, quantity is independent of side also from a computational point of view." return o def _require_restriction(self, o): "Restrict a discontinuous quantity to current side, require a side to be set." ufl_assert(self.current_restriction is not None, "Discontinuous type %s must be restricted." % o._ufl_class_.__name__) return o(self.current_restriction) def _default_restricted(self, o): "Restrict a continuous quantity to default side if no current restriction is set." r = self.current_restriction if r is None: r = self.default_restriction return o(r) def _opposite(self, o): "Restrict a quantity to default side, if the current restriction is different swap the sign, require a side to be set." if self.current_restriction is None: error("Discontinuous type %s must be restricted." % o._ufl_class_.__name__) elif self.current_restriction == self.default_restriction: return o(self.default_restriction) else: return -o(self.default_restriction) def _missing_rule(self, o): error("Missing rule for %s" % o._ufl_class_.__name__) # --- Rules for operators # Default: Operators should reconstruct only if subtrees are not touched operator = MultiFunction.reuse_if_untouched # Assuming apply_derivatives has been called, propagating Grad inside the Restricted nodes. grad = _require_restriction # Considering all grads to be discontinuous, may need something else for facet functions in future # Assuming averages are also applied directly to the terminal or grad nodes cell_avg = _require_restriction facet_avg = _ignore_restriction def variable(self, o, op): "Strip variable." return op def reference_value(self, o): "Reference value of something follows same restriction rule as the underlying object." f, = o.ufl_operands assert f._ufl_is_terminal_ g = self(f) if isinstance(g, Restricted): side = g.side() return o(side) else: return o # --- Rules for terminals # Default: Literals should ignore restriction terminal = _ignore_restriction # TODO: Require handlers to be specified for all terminals? That would be safer. # Even arguments with continuous elements such as Lagrange must be # restricted to associate with the right part of the element matrix argument = _require_restriction def coefficient(self, o): "Allow coefficients to be unrestricted (apply default if so) if the values are fully continuous across the facet." e = o.element() d = e.degree() f = e.family() # TODO: Move this choice to the element class? if (f == "Lagrange" and d > 0) or f == "Real": # If the coefficient _value_ is _fully_ continuous return self._default_restricted(o) # Must still be computed from one of the sides, we just don't care which else: return self._require_restriction(o) def facet_normal(self, o): D = o.domain() e = D.coordinate_element() f = e.family() d = e.degree() gd = D.geometric_dimension() td = D.topological_dimension() if f == "Lagrange" and d == 1 and gd == td: # For meshes with a continuous linear non-manifold coordinate field, # the facet normal from side - points in the opposite direction of the one from side +. # We must still require a side to be chosen by the user but rewrite n- -> n+. # This is an optimization, possibly premature, however it's more difficult to do at a later stage. return self._opposite(o) else: # For other meshes, we require a side to be chosen by the user and respect that return self._require_restriction(o) # Defaults for geometric quantities geometric_cell_quantity = _missing_rule #_require_restriction geometric_facet_quantity = _missing_rule #_ignore_restriction spatial_coordinate = _default_restricted # Continuous but computed from cell data cell_coordinate = _require_restriction # Depends on cell facet_coordinate = _ignore_restriction # Independent of cell cell_origin = _require_restriction # Depends on cell facet_origin = _default_restricted # Depends on cell but only to get to the facet # TODO: Is this valid for quads? cell_facet_origin = _require_restriction # Depends on cell jacobian = _require_restriction # Property of cell jacobian_determinant = _require_restriction # ... jacobian_inverse = _require_restriction # ... facet_jacobian = _default_restricted # Depends on cell only to get to the facet facet_jacobian_determinant = _default_restricted # ... (actually continuous?) facet_jacobian_inverse = _default_restricted # ... cell_facet_jacobian = _require_restriction # Depends on cell cell_facet_jacobian_determinant = _require_restriction # ... cell_facet_jacobian_inverse = _require_restriction # ... cell_normal = _require_restriction # Property of cell #facet_tangents = _default_restricted # Independent of cell #cell_tangents = _require_restriction # Depends on cell #cell_midpoint = _require_restriction # Depends on cell #facet_midpoint = _default_restricted # Depends on cell only to get to the facet cell_volume = _require_restriction # Property of cell circumradius = _require_restriction # Property of cell #cell_surface_area = _require_restriction # Property of cell facet_area = _default_restricted # Depends on cell only to get to the facet #facet_diameter = _default_restricted # Depends on cell only to get to the facet min_facet_edge_length = _default_restricted # Depends on cell only to get to the facet max_facet_edge_length = _default_restricted # Depends on cell only to get to the facet cell_orientation = _require_restriction # Property of cell facet_orientation = _require_restriction # Property of cell (depends on local facet number in cell) quadrature_weight = _ignore_restriction # Independent of cell def apply_restrictions(expression): "Propagate restriction nodes to wrap differential terminals directly." integral_types = [k for k in integral_type_to_measure_name.keys() if k.startswith("interior_facet")] rules = RestrictionPropagator() return map_integrand_dags(rules, expression, only_integral_type=integral_types) ufl-1.6.0/ufl/algorithms/argument_dependencies.py000066400000000000000000000132201255567402100221520ustar00rootroot00000000000000"""Algorithms for analysing argument dependencies in expressions.""" # Copyright (C) 2008-2014 Martin Sandve Alnes and Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2009-2010 # # First added: 2008-05-07 # Last changed: 2012-04-12 from ufl.assertions import ufl_assert from ufl.classes import Expr from ufl.algorithms.transformer import Transformer class NotMultiLinearException(Exception): def __init__(self, *args, **kwargs): Exception.__init__(self, *args, **kwargs) class ArgumentDependencyExtracter(Transformer): def __init__(self): Transformer.__init__(self) self._empty = frozenset() def operator(self, o, *opdeps): "Default for Operators: nonlinear in all operands." for d in opdeps: if d: raise NotMultiLinearException(repr(o)) return self._empty def terminal(self, o): "Default for terminals: no dependency on Arguments." return self._empty def variable(self, o): # Check variable cache to reuse previously transformed variable if possible e, l = o.ufl_operands d = self._variable_cache.get(l) if d is None: # Visit the expression our variable represents d = self.visit(e) self._variable_cache[l] = d return d def argument(self, o): d = frozenset((o,)) return frozenset((d,)) def linear(self, o, a): "Nonterminals that are linear with a single argument." return a nabla_grad = linear nabla_div = linear grad = linear div = linear curl = linear reference_grad = linear reference_div = linear reference_curl = linear transposed = linear trace = linear skew = linear sym = linear positive_restricted = linear negative_restricted = linear cell_avg = linear facet_avg = linear def indexed(self, o, f, i): return f def spatial_derivative(self, o, a, b): return a def variable_derivative(self, o, a, b): if b: raise NotMultiLinearException(repr(o)) return a def component_tensor(self, o, f, i): return f def list_tensor(self, o, *opdeps): "Require same dependencies for all listtensor entries." d = opdeps[0] for d2 in opdeps[1:]: if not d == d2: raise NotMultiLinearException(repr(o)) return d def conditional(self, o, cond, t, f): "Considering EQ, NE, LE, GE, LT, GT nonlinear in this context." if cond or (not t == f): raise NotMultiLinearException(repr(o)) return t def min_value(self, o, l, r): "Considering min, max nonlinear in this context." if l or r: raise NotMultiLinearException(repr(o)) return t max_value = min_value def division(self, o, a, b): "Arguments cannot be in the denominator." if b: raise NotMultiLinearException(repr(o)) return a def index_sum(self, o, f, i): "Index sums inherit the dependencies of their summand." return f def sum(self, o, *opdeps): """Sums can contain both linear and bilinear terms (we could change this to require that all operands have the same dependencies).""" # convert frozenset to a mutable set deps = set(opdeps[0]) for d in opdeps[1:]: # d is a frozenset of frozensets deps.update(d) return frozenset(deps) def product(self, o, *opdeps): # Product operands should not depend on the same Arguments c = [] adeps, bdeps = opdeps # TODO: Generalize to any number of operands using permutations # for each frozenset ad in the frozenset adeps ufl_assert(isinstance(adeps, frozenset), "Type error") ufl_assert(isinstance(bdeps, frozenset), "Type error") ufl_assert(all(isinstance(ad, frozenset) for ad in adeps), "Type error") ufl_assert(all(isinstance(bd, frozenset) for bd in bdeps), "Type error") none = frozenset((None,)) noneset = frozenset((none,)) if not adeps: adeps = noneset if not bdeps: bdeps = noneset for ad in adeps: # for each frozenset bd in the frozenset bdeps for bd in bdeps: # build frozenset cd with the combined Argument dependencies from ad and bd cd = (ad | bd) - none # build frozenset cd with the combined Argument dependencies from ad and bd if not len(cd) == len(ad - none) + len(bd - none): raise NotMultiLinearException(repr(o)) # remember this dependency combination if cd: c.append(cd) return frozenset(c) inner = product outer = product dot = product cross = product def extract_argument_dependencies(e): "Extract a set of sets of Arguments." ufl_assert(isinstance(e, Expr), "Expecting an Expr.") return ArgumentDependencyExtracter().visit(e) ufl-1.6.0/ufl/algorithms/change_to_reference.py000066400000000000000000000772431255567402100216060ustar00rootroot00000000000000"""Algorithm for replacing gradients in an expression with reference gradients and coordinate mappings.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from six.moves import xrange as range from ufl.log import error, warning from ufl.assertions import ufl_assert from ufl.core.multiindex import Index, indices from ufl.corealg.multifunction import MultiFunction from ufl.corealg.map_dag import map_expr_dag from ufl.classes import (FormArgument, GeometricQuantity, Terminal, ReferenceGrad, Grad, Restricted, ReferenceValue, Jacobian, JacobianInverse, JacobianDeterminant, FacetJacobian, FacetJacobianInverse, FacetJacobianDeterminant, CellFacetJacobian, CellEdgeVectors, FacetEdgeVectors, FacetNormal, CellNormal, CellVolume, FacetArea, CellOrientation, FacetOrientation, QuadratureWeight, Indexed, MultiIndex, FixedIndex) from ufl.finiteelement import MixedElement from ufl.constantvalue import as_ufl from ufl.tensors import as_tensor, as_vector from ufl.operators import sqrt, max_value, min_value from ufl.permutation import compute_indices from ufl.algorithms.transformer import ReuseTransformer, apply_transformer from ufl.compound_expressions import determinant_expr, cross_expr, inverse_expr # TODO: Move to ufl.corealg.multifunction? def memoized_handler(handler, cachename="_cache"): def _memoized_handler(self, o): c = getattr(self, cachename) r = c.get(o) if r is None: r = handler(self, o) c[o] = r return r return _memoized_handler """ # Some notes: # Below, let v_i mean physical coordinate of vertex i and V_i mean the reference cell coordinate of the same vertex. # Add a type for CellVertices? Note that vertices must be computed in linear cell cases! triangle_vertices[i,j] = component j of vertex i, following ufc numbering conventions # DONE Add a type for CellEdgeLengths? Note that these are only easy to define in the linear cell case! triangle_edge_lengths = [v1v2, v0v2, v0v1] # shape (3,) tetrahedron_edge_lengths = [v0v1, v0v2, v0v3, v1v2, v1v3, v2v3] # shape (6,) # DONE Here's how to compute edge lengths from the Jacobian: J =[ [dx0/dX0, dx0/dX1], [dx1/dX0, dx1/dX1] ] # First compute the edge vector, which is constant for each edge: the vector from one vertex to the other reference_edge_vector_0 = V2 - V1 # Example! Add a type ReferenceEdgeVectors? # Then apply J to it and take the length of the resulting vector, this is generic for affine cells edge_length_i = || dot(J, reference_edge_vector_i) || e2 = || J[:,0] . < 1, 0> || = || J[:,0] || = || dx/dX0 || = edge length of edge 2 (v0-v1) e1 = || J[:,1] . < 0, 1> || = || J[:,1] || = || dx/dX1 || = edge length of edge 1 (v0-v2) e0 = || J[:,:] . <-1, 1> || = || < J[0,1]-J[0,0], J[1,1]-J[1,0] > || = || dx/dX <-1,1> || = edge length of edge 0 (v1-v2) trev = triangle_reference_edge_vector evec0 = J00 * trev[edge][0] + J01 * trev[edge][1] = J*trev[edge] evec1 = J10 * trev[edge][0] + J11 * trev[edge][1] elen[edge] = sqrt(evec0*evec0 + evec1*evec1) = sqrt((J*trev[edge])**2) trev = triangle_reference_edge_vector evec0 = J00 * trev[edge][0] + J01 * trev[edge][1] = J*trev evec1 = J10 * trev[edge][0] + J11 * trev[edge][1] evec2 = J20 * trev[edge][0] + J21 * trev[edge][1] # Manifold: triangle in 3D elen[edge] = sqrt(evec0*evec0 + evec1*evec1 + evec2*evec2) = sqrt((J*trev[edge])**2) trev = tetrahedron_reference_edge_vector evec0 = sum(J[0,k] * trev[edge][k] for k in range(3)) evec1 = sum(J[1,k] * trev[edge][k] for k in range(3)) evec2 = sum(J[2,k] * trev[edge][k] for k in range(3)) elen[edge] = sqrt(evec0*evec0 + evec1*evec1 + evec2*evec2) = sqrt((J*trev[edge])**2) # DONE Here's how to compute min/max facet edge length: triangle: r = facetarea tetrahedron: min(elen[edge] for edge in range(6)) or min( min(elen[0], min(elen[1], elen[2])), min(elen[3], min(elen[4], elen[5])) ) or min1 = min_value(elen[0], min_value(elen[1], elen[2])) min2 = min_value(elen[3], min_value(elen[4], elen[5])) r = min_value(min1, min2) (want proper Min/Max types for this!) # DONE Here's how to compute circumradius for an interval: circumradius_interval = cellvolume / 2 # DONE Here's how to compute circumradius for a triangle: e0 = elen[0] e1 = elen[1] e2 = elen[2] circumradius_triangle = (e0*e1*e2) / (4*cellvolume) # DONE Here's how to compute circumradius for a tetrahedron: # v1v2 = edge length between vertex 1 and 2 # la,lb,lc = lengths of the sides of an intermediate triangle la = v1v2 * v0v3 lb = v0v2 * v1v3 lc = v0v1 * v2v3 # p = perimeter p = (la + lb + lc) # s = semiperimeter s = p / 2 # area of intermediate triangle with Herons formula tmp_area = sqrt(s * (s - la) * (s - lb) * (s - lc)) circumradius_tetrahedron = tmp_area / (6*cellvolume) """ # FIXME: This implementation semeed to work last year but lead to performance problems. Look through and test again now. class NEWChangeToReferenceGrad(MultiFunction): def __init__(self): MultiFunction.__init__(self) self._ngrads = 0 self._restricted = '' self._avg = '' def expr(self, o, *ops): return o.reconstruct(*ops) def terminal(self, o): return o def coefficient_derivative(self, o, *dummy_ops): error("Coefficient derivatives should be expanded before applying change to reference grad.") def reference_grad(self, o, *dummy_ops): error("Not expecting reference grad while applying change to reference grad.") def restricted(self, o, *dummy_ops): "Store modifier state." ufl_assert(self._restricted == '', "Not expecting nested restrictions.") self._restricted = o.side() f, = o.ufl_operands r = self(f) self._restricted = '' return r def grad(self, o, *dummy_ops): "Store modifier state." self._ngrads += 1 f, = o.ufl_operands r = self(f) self._ngrads -= 1 return r def facet_avg(self, o, *dummy_ops): ufl_assert(self._avg == '', "Not expecting nested averages.") self._avg = "facet" f, = o.ufl_operands r = self(f) self._avg = "" return r def cell_avg(self, o, *dummy_ops): ufl_assert(self._avg == '', "Not expecting nested averages.") self._avg = "cell" f, = o.ufl_operands r = self(f) self._avg = "" return r def form_argument(self, t): return self._mapped(t) def geometric_quantity(self, t): if self._restricted or self._ngrads or self._avg: return self._mapped(t) else: return t def _mapped(self, t): # Check that we have a valid input object ufl_assert(isinstance(t, Terminal), "Expecting a Terminal.") # Get modifiers accumulated by previous handler calls ngrads = self._ngrads restricted = self._restricted avg = self._avg ufl_assert(avg == "", "Averaging not implemented.") # FIXME # These are the global (g) and reference (r) values if isinstance(t, FormArgument): g = t r = ReferenceValue(g) elif isinstance(t, GeometricQuantity): g = t r = g else: error("Unexpected type {0}.".format(type(t).__name__)) # Some geometry mapping objects we may need multiple times below domain = t.domain() J = Jacobian(domain) detJ = JacobianDeterminant(domain) K = JacobianInverse(domain) # Restrict geometry objects if applicable if restricted: J = J(restricted) detJ = detJ(restricted) K = K(restricted) # Create Hdiv mapping from possibly restricted geometry objects Mdiv = (1.0/detJ) * J # Get component indices of global and reference terminal objects gtsh = g.ufl_shape rtsh = r.ufl_shape gtcomponents = compute_indices(gtsh) rtcomponents = compute_indices(rtsh) # Create core modified terminal, with eventual # layers of grad applied directly to the terminal, # then eventual restriction applied last for i in range(ngrads): g = Grad(g) r = ReferenceGrad(r) if restricted: g = g(restricted) r = r(restricted) # Get component indices of global and reference objects with grads applied gsh = g.ufl_shape rsh = r.ufl_shape gcomponents = compute_indices(gsh) rcomponents = compute_indices(rsh) # Get derivative component indices dsh = gsh[len(gtsh):] dcomponents = compute_indices(dsh) # Create nested array to hold expressions for global components mapped from reference values def ndarray(shape): if len(shape) == 0: return [None] elif len(shape) == 1: return [None]*shape[-1] else: return [ndarray(shape[1:]) for i in range(shape[0])] global_components = ndarray(gsh) # Compute mapping from reference values for each global component for gtc in gtcomponents: if isinstance(t, FormArgument): # Find basic subelement and element-local component #ec, element, eoffset = t.element().extract_component2(gtc) # FIXME: Translate this correctly eoffset = 0 ec, element = t.element().extract_reference_component(gtc) # Select mapping M from element, pick row emapping = M[ec,:], or emapping = [] if no mapping ufl_assert(not isinstance(element, MixedElement), "Expecting a basic element here.") mapping = element.mapping() if mapping == "contravariant Piola": #S == HDiv: # Handle HDiv elements with contravariant piola mapping # contravariant_hdiv_mapping = (1/det J) * J * PullbackOf(o) ec, = ec emapping = Mdiv[ec,:] elif mapping == "covariant Piola": #S == HCurl: # Handle HCurl elements with covariant piola mapping # covariant_hcurl_mapping = JinvT * PullbackOf(o) ec, = ec emapping = K[:,ec] # Column of K is row of K.T elif mapping == "identity": emapping = None else: error("Unknown mapping {0}".format(mapping)) elif isinstance(t, GeometricQuantity): eoffset = 0 emapping = None else: error("Unexpected type {0}.".format(type(t).__name__)) # Create indices #if rtsh: # i = Index() ufl_assert(len(dsh) == ngrads, "Mismatch between derivative shape and ngrads.") if ngrads: ii = indices(ngrads) else: ii = () # Apply mapping row to reference object if emapping: # Mapped, always nonscalar terminal # Not using IndexSum for the mapping row dot product to keep it simple, # because we don't have a slice type emapped_ops = [emapping[s] * Indexed(r, MultiIndex((FixedIndex(eoffset + s),) + ii)) for s in range(len(emapping))] emapped = sum(emapped_ops[1:], emapped_ops[0]) elif gtc: # Nonscalar terminal, unmapped emapped = Indexed(r, MultiIndex((FixedIndex(eoffset),) + ii)) elif ngrads: # Scalar terminal, unmapped, with derivatives emapped = Indexed(r, MultiIndex(ii)) else: # Scalar terminal, unmapped, no derivatives emapped = r for di in dcomponents: # Multiply derivative mapping rows, parameterized by free column indices dmapping = as_ufl(1) for j in range(ngrads): dmapping *= K[ii[j], di[j]] # Row of K is column of JinvT # Compute mapping from reference values for this particular global component global_value = dmapping * emapped # Apply index sums #if rtsh: # global_value = IndexSum(global_value, MultiIndex((i,))) #for j in range(ngrads): # Applied implicitly in the dmapping * emapped above # global_value = IndexSum(global_value, MultiIndex((ii[j],))) # This is the component index into the full object with grads applied gc = gtc + di # Insert in nested list comp = global_components for i in gc[:-1]: comp = comp[i] comp[0 if gc == () else gc[-1]] = global_value # Wrap nested list in as_tensor unless we have a scalar expression if gsh: tensor = as_tensor(global_components) else: tensor, = global_components return tensor class OLDChangeToReferenceGrad(MultiFunction): def __init__(self): MultiFunction.__init__(self) expr = MultiFunction.reuse_if_untouched def terminal(self, o): return o def grad(self, o): # FIXME: Handle HDiv elements with contravariant piola mapping specially? # FIXME: Handle HCurl elements with covariant piola mapping specially? # Peel off the Grads and count them, and get restriction if it's between the grad and the terminal ngrads = 0 restricted = '' while not o._ufl_is_terminal_: if isinstance(o, Grad): o, = o.ufl_operands ngrads += 1 elif isinstance(o, Restricted): restricted = o.side() o, = o.ufl_operands f = o # Get domain and create Jacobian inverse object domain = f.domain() Jinv = JacobianInverse(domain) # This is an assumption in the below code TODO: Handle grad(grad(.)) for non-affine domains. ufl_assert(ngrads == 1 or Jinv.is_cellwise_constant(), "Multiple grads for non-affine domains not currently supported in this algorithm.") # Create some new indices ii = indices(f.rank()) # Indices to get to the scalar component of f jj = indices(ngrads) # Indices to sum over the local gradient axes with the inverse Jacobian kk = indices(ngrads) # Indices for the leftover inverse Jacobian axes # Preserve restricted property if restricted: Jinv = Jinv(restricted) f = f(restricted) # Apply the same number of ReferenceGrad without mappings lgrad = f for i in range(ngrads): lgrad = ReferenceGrad(lgrad) # Apply mappings with scalar indexing operations (assumes ReferenceGrad(Jinv) is zero) jinv_lgrad_f = lgrad[ii+jj] for j, k in zip(jj, kk): jinv_lgrad_f = Jinv[j, k]*jinv_lgrad_f # Wrap back in tensor shape, derivative axes at the end jinv_lgrad_f = as_tensor(jinv_lgrad_f, ii+kk) return jinv_lgrad_f def reference_grad(self, o): error("Not expecting reference grad while applying change to reference grad.") def coefficient_derivative(self, o): error("Coefficient derivatives should be expanded before applying change to reference grad.") class ChangeToReferenceGeometry(MultiFunction): def __init__(self, physical_coordinates_known, coordinate_coefficient_mapping): MultiFunction.__init__(self) self.coordinate_coefficient_mapping = coordinate_coefficient_mapping or {} self.physical_coordinates_known = physical_coordinates_known self._cache = {} # Needed by memoized_handler expr = MultiFunction.reuse_if_untouched def terminal(self, o): return o @memoized_handler def jacobian(self, o): domain = o.domain() x = domain.coordinates() if x is None: r = o else: x = self.coordinate_coefficient_mapping[x] r = ReferenceGrad(x) return r @memoized_handler def _future_jacobian(self, o): # If we're not using Coefficient to represent the spatial coordinate, # we can just as well just return o here too unless we add representation # of basis functions and dofs to the ufl layer (which is nice to avoid). return o @memoized_handler def jacobian_inverse(self, o): domain = o.domain() J = self.jacobian(Jacobian(domain)) return inverse_expr(J) @memoized_handler def jacobian_determinant(self, o): domain = o.domain() J = self.jacobian(Jacobian(domain)) return determinant_expr(J) @memoized_handler def facet_jacobian(self, o): domain = o.domain() J = self.jacobian(Jacobian(domain)) RFJ = CellFacetJacobian(domain) i, j, k = indices(3) return as_tensor(J[i, k]*RFJ[k, j], (i, j)) @memoized_handler def facet_jacobian_inverse(self, o): domain = o.domain() FJ = self.facet_jacobian(FacetJacobian(domain)) return inverse_expr(FJ) @memoized_handler def facet_jacobian_determinant(self, o): domain = o.domain() FJ = self.facet_jacobian(FacetJacobian(domain)) return determinant_expr(FJ) @memoized_handler def spatial_coordinate(self, o): "Fall through to coordinate field of domain if it exists." if self.physical_coordinates_known: return o else: domain = o.domain() x = domain.coordinates() if x is None: return o else: x = self.coordinate_coefficient_mapping[x] return x @memoized_handler def _future_spatial_coordinate(self, o): "Fall through to coordinate field of domain if it exists." if self.physical_coordinates_known: return o else: # If we're not using Coefficient to represent the spatial coordinate, # we can just as well just return o here too unless we add representation # of basis functions and dofs to the ufl layer (which is nice to avoid). return o @memoized_handler def cell_coordinate(self, o): "Compute from physical coordinates if they are known, using the appropriate mappings." if self.physical_coordinates_known: K = self.jacobian_inverse(JacobianInverse(domain)) x = self.spatial_coordinate(SpatialCoordinate(domain)) x0 = CellOrigin(domain) i, j = indices(2) X = as_tensor(K[i, j] * (x[j] - x0[j]), (i,)) return X else: return o @memoized_handler def facet_cell_coordinate(self, o): if self.physical_coordinates_known: error("Missing computation of facet reference coordinates from physical coordinates via mappings.") else: return o @memoized_handler def cell_volume(self, o): domain = o.domain() if not domain.is_piecewise_linear_simplex_domain(): error("Only know how to compute the cell volume of an affine cell.") r = self.jacobian_determinant(JacobianDeterminant(domain)) r0 = domain.cell().reference_volume() return abs(r * r0) @memoized_handler def facet_area(self, o): domain = o.domain() if not domain.is_piecewise_linear_simplex_domain(): error("Only know how to compute the facet area of an affine cell.") r = self.facet_jacobian_determinant(FacetJacobianDeterminant(domain)) r0 = domain.cell().reference_facet_volume() return abs(r * r0) @memoized_handler def circumradius(self, o): domain = o.domain() if not domain.is_piecewise_linear_simplex_domain(): error("Only know how to compute the circumradius of an affine cell.") cellname = domain.cell().cellname() cellvolume = self.cell_volume(CellVolume(domain)) if cellname == "interval": r = 0.5 * cellvolume elif cellname == "triangle": J = self.jacobian(Jacobian(domain)) trev = CellEdgeVectors(domain) num_edges = 3 i, j, k = indices(3) elen = [sqrt((J[i, j]*trev[edge, j])*(J[i, k]*trev[edge, k])) for edge in range(num_edges)] r = (elen[0] * elen[1] * elen[2]) / (4.0 * cellvolume) elif cellname == "tetrahedron": J = self.jacobian(Jacobian(domain)) trev = CellEdgeVectors(domain) num_edges = 6 i, j, k = indices(3) elen = [sqrt((J[i, j]*trev[edge, j])*(J[i, k]*trev[edge, k])) for edge in range(num_edges)] # elen[3] = length of edge 3 # la, lb, lc = lengths of the sides of an intermediate triangle la = elen[3] * elen[2] lb = elen[4] * elen[1] lc = elen[5] * elen[0] # p = perimeter p = (la + lb + lc) # s = semiperimeter s = p / 2 # area of intermediate triangle with Herons formula triangle_area = sqrt(s * (s - la) * (s - lb) * (s - lc)) r = triangle_area / (6.0 * cellvolume) else: error("Unhandled cell type %s." % cellname) return r @memoized_handler def min_cell_edge_length(self, o): domain = o.domain() if not domain.is_piecewise_linear_simplex_domain(): error("Only know how to compute the min_cell_edge_length of an affine cell.") cellname = domain.cell().cellname() J = self.jacobian(Jacobian(domain)) trev = CellEdgeVectors(domain) num_edges = trev.ufl_shape[0] i, j, k = indices(3) elen = [sqrt((J[i, j]*trev[edge, j])*(J[i, k]*trev[edge, k])) for edge in range(num_edges)] if cellname == "triangle": return min_value(elen[0], min_value(elen[1], elen[2])) elif cellname == "tetrahedron": min1 = min_value(elen[0], min_value(elen[1], elen[2])) min2 = min_value(elen[3], min_value(elen[4], elen[5])) return min_value(min1, min2) else: error("Unhandled cell type %s." % cellname) @memoized_handler def max_cell_edge_length(self, o): domain = o.domain() if not domain.is_piecewise_linear_simplex_domain(): error("Only know how to compute the max_cell_edge_length of an affine cell.") cellname = domain.cell().cellname() J = self.jacobian(Jacobian(domain)) trev = CellEdgeVectors(domain) num_edges = trev.ufl_shape[0] i, j, k = indices(3) elen = [sqrt((J[i, j]*trev[edge, j])*(J[i, k]*trev[edge, k])) for edge in range(num_edges)] if cellname == "triangle": return max_value(elen[0], max_value(elen[1], elen[2])) elif cellname == "tetrahedron": max1 = max_value(elen[0], max_value(elen[1], elen[2])) max2 = max_value(elen[3], max_value(elen[4], elen[5])) return max_value(max1, max2) else: error("Unhandled cell type %s." % cellname) @memoized_handler def min_facet_edge_length(self, o): domain = o.domain() if not domain.is_piecewise_linear_simplex_domain(): error("Only know how to compute the min_facet_edge_length of an affine cell.") cellname = domain.cell().cellname() if cellname == "triangle": return self.facet_area(FacetArea(domain)) elif cellname == "tetrahedron": J = self.jacobian(Jacobian(domain)) trev = FacetEdgeVectors(domain) num_edges = 3 i, j, k = indices(3) elen = [sqrt((J[i, j]*trev[edge, j])*(J[i, k]*trev[edge, k])) for edge in range(num_edges)] return min_value(elen[0], min_value(elen[1], elen[2])) else: error("Unhandled cell type %s." % cellname) @memoized_handler def max_facet_edge_length(self, o): domain = o.domain() if not domain.is_piecewise_linear_simplex_domain(): error("Only know how to compute the max_facet_edge_length of an affine cell.") cellname = domain.cell().cellname() if cellname == "triangle": return self.facet_area(FacetArea(domain)) elif cellname == "tetrahedron": J = self.jacobian(Jacobian(domain)) trev = FacetEdgeVectors(domain) num_edges = 3 i, j, k = indices(3) elen = [sqrt((J[i, j]*trev[edge, j])*(J[i, k]*trev[edge, k])) for edge in range(num_edges)] return max_value(elen[0], max_value(elen[1], elen[2])) else: error("Unhandled cell type %s." % cellname) @memoized_handler def cell_normal(self, o): warning("Untested complicated code for cell normal. Please report if this works correctly or not.") domain = o.domain() gdim = domain.geometric_dimension() tdim = domain.topological_dimension() if tdim == gdim - 1: if tdim == 2: # Surface in 3D J = self.jacobian(Jacobian(domain)) cell_normal = cross_expr(J[:, 0], J[:, 1]) elif tdim == 1: # Line in 2D # TODO: Document which normal direction this is cell_normal = as_vector((-J[1, 0], J[0, 0])) i = Index() return cell_normal / sqrt(cell_normal[i]*cell_normal[i]) elif tdim == gdim: return as_vector((0.0,)*tdim + (1.0,)) else: error("What do you mean by cell normal in gdim={0}, tdim={1}?".format(gdim, tdim)) @memoized_handler def facet_normal(self, o): domain = o.domain() gdim = domain.geometric_dimension() tdim = domain.topological_dimension() if tdim == 3: FJ = self.facet_jacobian(FacetJacobian(domain)) ufl_assert(gdim == 3, "Inconsistent dimensions.") ufl_assert(FJ.ufl_shape == (3, 2), "Inconsistent dimensions.") # Compute signed scaling factor scale = self.jacobian_determinant(JacobianDeterminant(domain)) # Compute facet normal direction of 3D cell, product of two tangent vectors fo = FacetOrientation(domain) ndir = (fo * scale) * cross_expr(FJ[:, 0], FJ[:, 1]) # Normalise normal vector i = Index() n = ndir / sqrt(ndir[i]*ndir[i]) r = n elif tdim == 2: FJ = self.facet_jacobian(FacetJacobian(domain)) if gdim == 2: # 2D facet normal in 2D space ufl_assert(FJ.ufl_shape == (2, 1), "Inconsistent dimensions.") # Compute facet tangent tangent = as_vector((FJ[0, 0], FJ[1, 0], 0)) # Compute cell normal cell_normal = as_vector((0, 0, 1)) # Compute signed scaling factor scale = self.jacobian_determinant(JacobianDeterminant(domain)) else: # 2D facet normal in 3D space ufl_assert(FJ.ufl_shape == (gdim, 1), "Inconsistent dimensions.") # Compute facet tangent tangent = FJ[:, 0] # Compute cell normal cell_normal = self.cell_normal(CellNormal(domain)) # Compute signed scaling factor (input in the manifold case) scale = CellOrientation(domain) ufl_assert(len(tangent) == 3, "Inconsistent dimensions.") ufl_assert(len(cell_normal) == 3, "Inconsistent dimensions.") # Compute normal direction cr = cross_expr(tangent, cell_normal) if gdim == 2: cr = as_vector((cr[0], cr[1])) fo = FacetOrientation(domain) ndir = (fo * scale) * cr # Normalise normal vector i = Index() n = ndir / sqrt(ndir[i]*ndir[i]) r = n elif tdim == 1: J = self.jacobian(Jacobian(domain)) # dx/dX fo = FacetOrientation(domain) ndir = fo * J[:, 0] if gdim == 1: nlen = abs(ndir[0]) else: i = Index() nlen = sqrt(ndir[i]*ndir[i]) n = ndir / nlen r = n ufl_assert(r.ufl_shape == o.ufl_shape, "Inconsistent dimensions (in=%d, out=%d)." % (o.ufl_shape[0], r.ufl_shape[0])) return r def change_to_reference_grad(e): """Change Grad objects in expression to products of JacobianInverse and ReferenceGrad. Assumes the expression is preprocessed or at least that derivatives have been expanded. @param e: An Expr or Form. """ mf = OLDChangeToReferenceGrad() #mf = NEWChangeToReferenceGrad() return map_expr_dag(mf, e) def change_to_reference_geometry(e, physical_coordinates_known, coordinate_coefficient_mapping=None): """Change GeometricQuantity objects in expression to the lowest level GeometricQuantity objects. Assumes the expression is preprocessed or at least that derivatives have been expanded. @param e: An Expr or Form. """ mf = ChangeToReferenceGeometry(physical_coordinates_known, coordinate_coefficient_mapping) return map_expr_dag(mf, e) def compute_integrand_scaling_factor(domain, integral_type): """Change integrand geometry to the right representations.""" weight = QuadratureWeight(domain) tdim = domain.topological_dimension() if integral_type == "cell": scale = abs(JacobianDeterminant(domain)) * weight elif integral_type.startswith("exterior_facet"): if tdim > 1: # Scaling integral by facet jacobian determinant and quadrature weight scale = FacetJacobianDeterminant(domain) * weight else: # No need to scale 'integral' over a vertex scale = 1 elif integral_type.startswith("interior_facet"): if tdim > 1: # Scaling integral by facet jacobian determinant from one side and quadrature weight scale = FacetJacobianDeterminant(domain)('+') * weight else: # No need to scale 'integral' over a vertex scale = 1 elif integral_type in ("custom", "interface", "overlap", "cutcell"): # Scaling with custom weight, which includes eventual volume scaling scale = weight elif integral_type in ("vertex", "point"): # No need to scale 'integral' over a point scale = 1 else: error("Unknown integral type {}, don't know how to scale.".format(integral_type)) return scale def change_integrand_geometry_representation(integrand, scale, integral_type): """Change integrand geometry to the right representations.""" integrand = change_to_reference_grad(integrand) integrand = integrand * scale if integral_type == "quadrature": physical_coordinates_known = True else: physical_coordinates_known = False integrand = change_to_reference_geometry(integrand, physical_coordinates_known) return integrand ufl-1.6.0/ufl/algorithms/check_arities.py000066400000000000000000000104511255567402100204220ustar00rootroot00000000000000 from itertools import chain from ufl.log import UFLException from ufl.corealg.traversal import traverse_terminals from ufl.corealg.multifunction import MultiFunction from ufl.corealg.map_dag import map_expr_dag from ufl.classes import Argument class ArityMismatch(UFLException): pass class ArityChecker(MultiFunction): def __init__(self, arguments): MultiFunction.__init__(self) self.arguments = arguments self._et = () def terminal(self, o): return self._et def argument(self, o): return (o,) def nonlinear_operator(self, o): # Cutoff traversal by not having *ops in argument list of this handler. # Traverse only the terminals under here the fastest way we know of: for t in traverse_terminals(o): if t._ufl_typecode_ == Argument._ufl_typecode_: raise ArityMismatch("Applying nonlinear operator to expression depending on form argument {0}.".format(t)) return self._et expr = nonlinear_operator def sum(self, o, a, b): if a != b: raise ArityMismatch("Adding expressions with non-matching form arguments {0} vs {1}.".format(a, b)) return a def division(self, o, a, b): if b: raise ArityMismatch("Cannot divide by form argument {0}.".format(b)) return a def product(self, o, a, b): if a and b: # Check that we don't have test*test, trial*trial, even for different parts in a block system anumbers = set(x.number() for x in a) for x in b: if x.number() in anumbers: raise ArityMismatch("Multiplying expressions with overlapping form argument number {0}, argument is {1}.".format(x.number(), x)) # Combine argument lists c = tuple(sorted(set(a + b), key=lambda x: (x.number(), x.part()))) # Check that we don't have any arguments shared between a and b if len(c) != len(a) + len(b): raise ArityMismatch("Multiplying expressions with overlapping form arguments {0} vs {1}.".format(a, b)) # It's fine for argument parts to overlap return c elif a: return a else: return b # inner, outer and dot all behave as product inner = product outer = product dot = product def linear_operator(self, o, a): return a # Positive and negative restrictions behave as linear operators positive_restricted = linear_operator negative_restricted = linear_operator # Cell and facet average are linear operators cell_avg = linear_operator facet_avg = linear_operator # Grad is a linear operator grad = linear_operator # Does it make sense to have a Variable(Argument)? I see no problem. def variable(self, o, f, l): return f def linear_indexed_type(self, o, a, i): return a # All of these indexed thingies behave as a linear_indexed_type indexed = linear_indexed_type index_sum = linear_indexed_type component_tensor = linear_indexed_type def list_tensor(self, o, *ops): args = set(chain(*ops)) if args: # Check that each list tensor component has the same argument numbers (ignoring parts) numbers = set(tuple(sorted(set(arg.number() for arg in op))) for op in ops) if () in numbers: # Allow e.g. but not numbers.remove(()) if len(numbers) > 1: raise ArityMismatch("Listtensor components must depend on the same argument numbers, found {0}.".format(numbers)) # Allow different parts with the same number return tuple(sorted(args, key=lambda x: (x.number(), x.part()))) else: # No argument dependencies return self._et def check_integrand_arity(expr, arguments): arguments = tuple(sorted(set(arguments), key=lambda x: (x.number(), x.part()))) rules = ArityChecker(arguments) args = map_expr_dag(rules, expr, compress=False) if args != arguments: raise ArityMismatch("Integrand arguments {0} differ from form arguments {1}.".format(args, arguments)) def check_form_arity(form, arguments): for itg in form.integrals(): check_integrand_arity(itg.integrand(), arguments) ufl-1.6.0/ufl/algorithms/checks.py000066400000000000000000000111211255567402100170600ustar00rootroot00000000000000"""Functions to check the validity of forms.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2008-2009. # Modified by Mehdi Nikbakht, 2010. from ufl.log import warning, error # UFL classes from ufl.form import Form from ufl.argument import Argument from ufl.coefficient import Coefficient from ufl.constantvalue import is_true_ufl_scalar from ufl.integral import Measure # UFL algorithms from ufl.algorithms.traversal import iter_expressions from ufl.corealg.traversal import traverse_terminals from ufl.algorithms.propagate_restrictions import check_restrictions from ufl.measure import integral_type_to_measure_name def validate_form(form): # TODO: Can we make this return a list of errors instead of raising exception? """Performs all implemented validations on a form. Raises exception if something fails.""" errors = [] warnings = [] if not isinstance(form, Form): msg = "Validation failed, not a Form:\n%s" % repr(form) error(msg) #errors.append(msg) #return errors # FIXME: There's a bunch of other checks we should do here. # FIXME: Add back check for multilinearity # Check that form is multilinear #if not is_multilinear(form): # errors.append("Form is not multilinear in arguments.") # FIXME DOMAIN: Add check for consistency between domains somehow domains = set(t.domain() for e in iter_expressions(form) for t in traverse_terminals(e)) - {None} if not domains: errors.append("Missing domain definition in form.") # Check that cell is the same everywhere cells = set(dom.cell() for dom in domains) - {None} if not cells: errors.append("Missing cell definition in form.") elif len(cells) > 1: errors.append("Multiple cell definitions in form: %s" % str(cells)) # Check that no Coefficient or Argument instance # have the same count unless they are the same coefficients = {} arguments = {} for e in iter_expressions(form): for f in traverse_terminals(e): if isinstance(f, Coefficient): c = f.count() if c in coefficients: g = coefficients[c] if not f is g: errors.append("Found different Coefficients with " + \ "same count: %s and %s." % (repr(f), repr(g))) else: coefficients[c] = f elif isinstance(f, Argument): n = f.number() p = f.part() if (n, p) in arguments: g = arguments[(n, p)] if not f is g: if n == 0: msg = "TestFunctions" elif n == 1: msg = "TrialFunctions" else: msg = "Arguments with same number and part" msg = "Found different %s: %s and %s." % (msg, repr(f), repr(g)) errors.append(msg) else: arguments[(n, p)] = f # Check that all integrands are scalar for expression in iter_expressions(form): if not is_true_ufl_scalar(expression): errors.append("Found non-scalar integrand expression:\n%s\n%s" % \ (str(expression), repr(expression))) # Check that restrictions are permissible for integral in form.integrals(): # Only allow restrictions on interior facet integrals and surface measures if integral.integral_type().startswith("interior_facet"): check_restrictions(integral.integrand(), True) else: check_restrictions(integral.integrand(), False) # Raise exception with all error messages # TODO: Return errors list instead, need to collect messages from all validations above first. if errors: final_msg = 'Found errors in validation of form:\n%s' % '\n\n'.join(errors) error(final_msg) ufl-1.6.0/ufl/algorithms/compute_form_data.py000066400000000000000000000310101255567402100213070ustar00rootroot00000000000000"""This module provides the compute_form_data function which form compilers will typically call prior to code generation to preprocess/simplify a raw input form given by a user.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from collections import defaultdict from itertools import chain from time import time import ufl from ufl.common import lstr, tstr, estr, istr, slice_dict from ufl.common import Timer from ufl.assertions import ufl_assert from ufl.log import error, warning, info from ufl.core.expr import Expr from ufl.corealg.traversal import traverse_terminals from ufl.form import Form from ufl.protocols import id_or_none from ufl.geometry import as_domain from ufl.classes import GeometricFacetQuantity from ufl.algorithms.replace import replace from ufl.algorithms.analysis import extract_coefficients, extract_sub_elements, unique_tuple from ufl.algorithms.domain_analysis import build_integral_data, reconstruct_form_from_integral_data from ufl.algorithms.formdata import FormData, ExprData from ufl.algorithms.ad import expand_derivatives from ufl.algorithms.propagate_restrictions import propagate_restrictions from ufl.algorithms.formtransformations import compute_form_arities from ufl.algorithms.check_arities import check_form_arity def _auto_select_degree(elements): """ Automatically select degree for all elements of the form in cases where this has not been specified by the user. This feature is used by DOLFIN to allow the specification of Expressions with undefined degrees. """ # Use max degree of all elements, at least 1 (to work with Lagrange elements) return max({ e.degree() for e in elements } - { None } | { 1 }) def _compute_element_mapping(form): "Compute element mapping for element replacement" # Extract all elements and include subelements of mixed elements elements = [obj.element() for obj in chain(form.arguments(), form.coefficients())] elements = extract_sub_elements(elements) # Try to find a common degree for elements common_degree = _auto_select_degree(elements) # Compute element map element_mapping = {} for element in elements: # Flag for whether element needs to be reconstructed reconstruct = False # Set domain/cell domain = element.domain() if domain is None: domains = form.domains() ufl_assert(len(domains) == 1, "Cannot replace unknown element domain without unique common domain in form.") domain, = domains info("Adjusting missing element domain to %s." % (domain,)) reconstruct = True # Set degree degree = element.degree() if degree is None: info("Adjusting missing element degree to %d" % (common_degree,)) degree = common_degree reconstruct = True # Reconstruct element and add to map if reconstruct: element_mapping[element] = element.reconstruct(domain=domain, degree=degree) else: element_mapping[element] = element return element_mapping def _compute_num_sub_domains(integral_data): num_sub_domains = {} for itg_data in integral_data: it = itg_data.integral_type si = itg_data.subdomain_id if isinstance(si, int): newmax = si + 1 else: newmax = 0 prevmax = num_sub_domains.get(it, 0) num_sub_domains[it] = max(prevmax, newmax) return num_sub_domains def _compute_form_data_elements(self, arguments, coefficients): self.argument_elements = tuple(f.element() for f in arguments) self.coefficient_elements = tuple(f.element() for f in coefficients) self.elements = self.argument_elements + self.coefficient_elements self.unique_elements = unique_tuple(self.elements) self.sub_elements = extract_sub_elements(self.elements) self.unique_sub_elements = unique_tuple(self.sub_elements) def _check_elements(form_data): for element in chain(form_data.unique_elements, form_data.unique_sub_elements): ufl_assert(element.domain() is not None, "Found element with undefined domain: %s" % repr(element)) ufl_assert(element.family() is not None, "Found element with undefined familty: %s" % repr(element)) def _check_facet_geometry(integral_data): for itg_data in integral_data: for itg in itg_data.integrals: it = itg_data.integral_type # Facet geometry is only valid in facet integrals. # Allowing custom integrals to pass as well, although that's not really strict enough. if "facet" not in it and "custom" not in it: # Not a facet integral for expr in traverse_terminals(itg.integrand()): cls = expr._ufl_class_ if issubclass(cls, GeometricFacetQuantity): error("Integral of type %s cannot contain a %s." % (it, cls.__name__)) def _check_form_arity(preprocessed_form): # Check that we don't have a mixed linear/bilinear form or anything like that # FIXME: This is slooow and should be moved to form compiler and/or replaced with something faster ufl_assert(len(compute_form_arities(preprocessed_form)) == 1, "All terms in form must have same rank.") def _build_coefficient_replace_map(coefficients, element_mapping=None): """Create new Coefficient objects with count starting at 0. Return mapping from old to new objects, and lists of the new objects.""" if element_mapping is None: element_mapping = {} new_coefficients = [] replace_map = {} for i, f in enumerate(coefficients): old_e = f.element() new_e = element_mapping.get(old_e, old_e) new_f = f.reconstruct(element=new_e, count=i) new_coefficients.append(new_f) replace_map[f] = new_f return new_coefficients, replace_map def compute_form_data(form, apply_propagate_restrictions=True): # TODO: Move this to the constructor instead self = FormData() # Store untouched form for reference. # The user of FormData may get original arguments, # original coefficients, and form signature from this object. # But be aware that the set of original coefficients are not # the same as the ones used in the final UFC form. # See 'reduced_coefficients' below. self.original_form = form # Get rank of form from argument list (assuming not a mixed arity form) self.rank = len(form.arguments()) # Extract common geometric dimension (topological is not common!) gdims = set(domain.geometric_dimension() for domain in form.domains()) ufl_assert(len(gdims) == 1, "Expecting all integrals in a form to share geometric dimension, got %s." % str(tuple(sorted(gdims)))) self.geometric_dimension, = gdims # Build mapping from old incomplete element objects to new well defined elements. # This is to support the Expression construct in dolfin which subclasses Coefficient # but doesn't provide an element, and the Constant construct that doesn't provide # the domain that a Coefficient is supposed to have. A future design iteration in # UFL/UFC/FFC/DOLFIN may allow removal of this mapping with the introduction of UFL # types for . self.element_replace_map = _compute_element_mapping(form) # --- Pass form integrands through some symbolic manipulation # Process form the way that is currently expected by FFC preprocessed_form = expand_derivatives(form) if apply_propagate_restrictions: preprocessed_form = propagate_restrictions(preprocessed_form) # --- Group and collect data about integrals # TODO: Refactor this # TODO: Is form.domains() right here? self.integral_data = \ build_integral_data(preprocessed_form.integrals(), form.domains()) # --- Create replacements for arguments and coefficients # Figure out which form coefficients each integral should enable for itg_data in self.integral_data: itg_coeffs = set() # Get all coefficients in integrand for itg in itg_data.integrals: itg_coeffs.update(extract_coefficients(itg.integrand())) # Add coefficient for integration domain if any c = itg_data.domain.coordinates() if c is not None: itg_coeffs.add(c) # Store with IntegralData object itg_data.integral_coefficients = itg_coeffs # Figure out which coefficients from the original form are actually used in any integral # (Differentiation may reduce the set of coefficients w.r.t. the original form) reduced_coefficients_set = set() for itg_data in self.integral_data: reduced_coefficients_set.update(itg_data.integral_coefficients) self.reduced_coefficients = sorted(reduced_coefficients_set, key=lambda c: c.count()) self.num_coefficients = len(self.reduced_coefficients) self.original_coefficient_positions = [i for i, c in enumerate(form.coefficients()) if c in self.reduced_coefficients] # Store back into integral data which form coefficients are used by each integral for itg_data in self.integral_data: itg_data.enabled_coefficients = [bool(coeff in itg_data.integral_coefficients) for coeff in self.reduced_coefficients] """ # Build mappings from coefficients, domains and geometric quantities # that reside in form to objects with canonical numbering as well as # completed elements coordinate_functions = set(domain.coordinates() for domain in form.domains()) - set((None,)) coordinates_replace_map = {} for i, f in enumerate(self.reduced_coefficients): if f in coordinate_functions: new_f = f.reconstruct(count=i) coordinates_replace_map[f] = new_f domains_replace_map = {} for domain in form.domains(): FIXME geometry_replace_map = {} FIXME coefficients_replace_map = {} for i, f in enumerate(self.reduced_coefficients): if f not in coordinate_functions: old_e = f.element() new_e = self.element_replace_map.get(old_e, old_e) new_f = f.reconstruct(element=new_e, count=i) coefficients_replace_map[f] = new_f self.terminals_replace_map = {} self.terminals_replace_map.update(coordinates_replace_map) self.terminals_replace_map.update(domains_replace_map) # Not currently terminals but soon will be self.terminals_replace_map.update(geometry_replace_map) self.terminals_replace_map.update(coefficients_replace_map) renumbered_coefficients = [self.terminals_replace_map[f] for f in self.reduced_coefficients] """ # Mappings from elements and coefficients # that reside in form to objects with canonical numbering as well as # completed cells and elements renumbered_coefficients, function_replace_map = \ _build_coefficient_replace_map(self.reduced_coefficients, self.element_replace_map) self.function_replace_map = function_replace_map # --- Store various lists of elements and sub elements (adds members to self) _compute_form_data_elements(self, form.arguments(), renumbered_coefficients) # --- Store number of domains for integral types # TODO: Group this by domain first. For now keep a backwards compatible data structure. self.num_sub_domains = _compute_num_sub_domains(self.integral_data) # --- Checks _check_elements(self) _check_facet_geometry(self.integral_data) # TODO: This is a very expensive check... Replace with something faster! preprocessed_form = reconstruct_form_from_integral_data(self.integral_data) #_check_form_arity(preprocessed_form) check_form_arity(preprocessed_form, form.arguments()) # Currently testing how fast this is # TODO: This is used by unit tests, change the tests! self.preprocessed_form = preprocessed_form return self ufl-1.6.0/ufl/algorithms/domain_analysis.py000066400000000000000000000257371255567402100210140ustar00rootroot00000000000000"""Algorithms for building canonical data structure for integrals over subdomains.""" # Copyright (C) 2009-2014 Anders Logg and Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from collections import defaultdict from six.moves import zip from six import iteritems import ufl from ufl.utils.sorting import sorted_by_key from ufl.log import error from ufl.assertions import ufl_assert from ufl.geometry import Domain from ufl.measure import Measure from ufl.integral import Integral from ufl.form import Form from ufl.sorting import cmp_expr from ufl.sorting import sorted_expr from ufl.utils.sorting import canonicalize_metadata class IntegralData(object): """Utility class with the members (domain, integral_type, subdomain_id, integrals, metadata) where metadata is an empty dictionary that may be used for associating metadata with each object. """ __slots__ = ('domain', 'integral_type', 'subdomain_id', 'integrals', 'metadata', 'integral_coefficients', 'enabled_coefficients') def __init__(self, domain, integral_type, subdomain_id, integrals, metadata): ufl_assert(all(domain.label() == itg.domain().label() for itg in integrals), "Domain label mismatch in integral data.") ufl_assert(all(integral_type == itg.integral_type() for itg in integrals), "Domain type mismatch in integral data.") ufl_assert(all(subdomain_id == itg.subdomain_id() for itg in integrals), "Domain id mismatch in integral data.") self.domain = domain self.integral_type = integral_type self.subdomain_id = subdomain_id self.integrals = integrals # This is populated in preprocess using data not available at this stage: self.integral_coefficients = None self.enabled_coefficients = None # TODO: I think we can get rid of this with some refactoring in ffc: self.metadata = metadata def __lt__(self, other): # To preserve behaviour of extract_integral_data: return ((self.integral_type, self.subdomain_id, self.integrals, self.metadata) < (other.integral_type, other.subdomain_id, other.integrals, other.metadata)) def __eq__(self, other): # Currently only used for tests: return (self.integral_type == other.integral_type and self.subdomain_id == other.subdomain_id and self.integrals == other.integrals and self.metadata == other.metadata) def __str__(self): return "IntegralData object over domain (%s, %s), with integrals:\n%s\nand metadata:\n%s" % ( self.integral_type, self.subdomain_id, '\n\n'.join(map(str, self.integrals)), self.metadata) def dicts_lt(a, b): na = 0 if a is None else len(a) nb = 0 if b is None else len(b) if na != nb: return len(a) < len(b) for ia, ib in zip(sorted_by_key(a), sorted_by_key(b)): # Assuming keys are sortable (usually str) if ia[0] != ib[0]: return (ia[0].__class__.__name__, ia[0]) < (ib[0].__class__.__name__, ib[0]) # Hack to preserve type sorting in py3 # Assuming values are sortable if ia[1] != ib[1]: return (ia[1].__class__.__name__, ia[1]) < (ib[1].__class__.__name__, ib[1]) # Hack to preserve type sorting in py3 # Tuple comparison helper class ExprTupleKey(object): __slots__ = ('x',) def __init__(self, x): self.x = x def __lt__(self, other): # Comparing expression first c = cmp_expr(self.x[0], other.x[0]) if c < 0: return True elif c > 0: return False else: # Comparing form compiler data mds = canonicalize_metadata(self.x[1]) mdo = canonicalize_metadata(other.x[1]) return mds < mdo def group_integrals_by_domain_and_type(integrals, domains): """ Input: integrals: list of Integral objects domains: list of Domain objects from the parent Form Output: integrals_by_domain_and_type: dict: (domain, integral_type) -> list(Integral) """ integral_data = [] domains_by_label = dict((domain.label(), domain) for domain in domains) integrals_by_domain_and_type = defaultdict(list) for itg in integrals: # Canonicalize domain domain = itg.domain() ufl_assert(domain is not None, "Integrals without a domain is now illegal.") domain = domains_by_label.get(domain.label()) integral_type = itg.integral_type() # Append integral to list of integrals with shared key integrals_by_domain_and_type[(domain, integral_type)].append(itg) return integrals_by_domain_and_type def integral_subdomain_ids(integral): "Get a tuple of integer subdomains or a valid string subdomain from integral." did = integral.subdomain_id() if isinstance(did, int): return (did,) elif isinstance(did, tuple): ufl_assert(all(isinstance(d, int) for d in did), "Expecting only integer subdomains in tuple.") return did elif did in ("everywhere", "otherwise"): # TODO: Define list of valid strings somewhere more central return did else: error("Invalid domain id %s." % did) def rearrange_integrals_by_single_subdomains(integrals): """Rearrange integrals over multiple subdomains to single subdomain integrals. Input: integrals: list(Integral) Output: integrals: dict: subdomain_id -> list(Integral) (reconstructed with single subdomain_id) """ # Split integrals into lists of everywhere and subdomain integrals everywhere_integrals = [] subdomain_integrals = [] for itg in integrals: dids = integral_subdomain_ids(itg) if dids == "otherwise": error("'otherwise' integrals should never occur before preprocessing.") elif dids == "everywhere": everywhere_integrals.append(itg) else: subdomain_integrals.append((dids, itg)) # Fill single_subdomain_integrals with lists of integrals from # subdomain_integrals, but split and restricted to single subdomain ids single_subdomain_integrals = defaultdict(list) for dids, itg in subdomain_integrals: # Region or single subdomain id for did in dids: # Restrict integral to this subdomain! single_subdomain_integrals[did].append(itg.reconstruct(subdomain_id=did)) # Add everywhere integrals to each single subdomain id integral list otherwise_integrals = [] for ev_itg in everywhere_integrals: # Restrict everywhere integral to 'otherwise' otherwise_integrals.append( ev_itg.reconstruct(subdomain_id="otherwise")) # Restrict everywhere integral to each subdomain # and append to each integral list for subdomain_id in sorted(single_subdomain_integrals.keys()): single_subdomain_integrals[subdomain_id].append( ev_itg.reconstruct(subdomain_id=subdomain_id)) if otherwise_integrals: single_subdomain_integrals["otherwise"] = otherwise_integrals return single_subdomain_integrals def accumulate_integrands_with_same_metadata(integrals): """ Taking input on the form: integrals = [integral0, integral1, ...] Return result on the form: integrands_by_id = [(integrand0, metadata0), (integrand1, metadata1), ...] where integrand0 < integrand1 by the canonical ufl expression ordering criteria. """ # Group integrals by compiler data hash by_cdid = {} for itg in integrals: cd = itg.metadata() cdid = hash(canonicalize_metadata(cd)) if cdid not in by_cdid: by_cdid[cdid] = ([], cd) by_cdid[cdid][0].append(itg) # Accumulate integrands separately for each compiler data object id for cdid in by_cdid: integrals, cd = by_cdid[cdid] # Ensure canonical sorting of more than two integrands integrands = sorted_expr((itg.integrand() for itg in integrals)) integrands_sum = sum(integrands[1:], integrands[0]) by_cdid[cdid] = (integrands_sum, cd) # Sort integrands canonically by integrand first then compiler data return sorted(by_cdid.values(), key=ExprTupleKey) def build_integral_data(integrals, domains): integral_data = [] # Group integrals by domain and type integrals_by_domain_and_type = \ group_integrals_by_domain_and_type(integrals, domains) for domain in domains: for integral_type in ufl.measure.integral_types(): # Get integrals with this domain and type ddt_integrals = integrals_by_domain_and_type.get((domain, integral_type)) if ddt_integrals is None: continue # Group integrals by subdomain id, after splitting e.g. # f*dx((1,2)) + g*dx((2,3)) -> f*dx(1) + (f+g)*dx(2) + g*dx(3) # (note: before this call, 'everywhere' is a valid subdomain_id, # and after this call, 'otherwise' is a valid subdomain_id) single_subdomain_integrals = \ rearrange_integrals_by_single_subdomains(ddt_integrals) for subdomain_id, ss_integrals in sorted_by_key(single_subdomain_integrals): # Accumulate integrands of integrals that share the same compiler data integrands_and_cds = \ accumulate_integrands_with_same_metadata(ss_integrals) # Reconstruct integrals with new integrands and the right domain object integrals = [Integral(integrand, integral_type, domain, subdomain_id, metadata, None) for integrand, metadata in integrands_and_cds] # Create new metadata dict for each integral data, # this is filled in by ffc to associate compiler # specific information with this integral data metadata = {} # Finally wrap it all in IntegralData object! ida = IntegralData(domain, integral_type, subdomain_id, integrals, {}) # Store integral data objects in list with canonical ordering integral_data.append(ida) return integral_data def reconstruct_form_from_integral_data(integral_data): integrals = [] for ida in integral_data: integrals.extend(ida.integrals) return Form(integrals) ufl-1.6.0/ufl/algorithms/elementtransformations.py000066400000000000000000000041741255567402100224350ustar00rootroot00000000000000# Copyright (C) 2012 Marie E. Rognes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from six.moves import xrange as range from ufl.assertions import ufl_assert from ufl.finiteelement import FiniteElement, MixedElement def change_regularity(element, family): """ For a given finite element, return the corresponding space specified by 'family'. """ n = element.num_sub_elements() if n > 0: subs = element.sub_elements() return MixedElement([change_regularity(subs[i], family) for i in range(n)]) shape = element.value_shape() if not shape: return FiniteElement(family, element.domain(), element.degree()) ufl_assert(len(shape) == 1, "TODO: Update this code to handle tensor elements.") return MixedElement([FiniteElement(family, element.domain(i), element.degree(i)) for i in range(shape[0])]) def tear(V): "For a finite element, return the corresponding discontinuous element." W = change_regularity(V, "DG") return W def increase_order(element): "Return element of same family, but a polynomial degree higher." ufl_assert(len(element.value_shape()) <= 1, "TODO: Update this code to handle tensor elements.") n = element.num_sub_elements() if n > 0: subs = element.sub_elements() return MixedElement([increase_order(subs[i]) for i in range(n)]) if element.family() == "Real": return element return FiniteElement(element.family(), element.domain(), element.degree()+1) ufl-1.6.0/ufl/algorithms/estimate_degrees.py000066400000000000000000000253631255567402100211460ustar00rootroot00000000000000"""Algorithms for estimating polynomial degrees of expressions.""" # Copyright (C) 2008-2014 Martin Sandve Alnes and Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2009-2010 # Modified by Jan Blechta, 2012 from ufl.assertions import ufl_assert from ufl.log import warning from ufl.form import Form from ufl.integral import Integral from ufl.algorithms.transformer import Transformer class SumDegreeEstimator(Transformer): "This algorithm is exact for a few operators and heuristic for many." def __init__(self, default_degree, element_replace_map): Transformer.__init__(self) self.default_degree = default_degree self.element_replace_map = element_replace_map def constant_value(self, v): "Constant values are constant." return 0 def geometric_quantity(self, v): "Some geometric quantities are cellwise constant. Others are nonpolynomial and thus hard to estimate." if v.is_cellwise_constant(): return 0 else: # As a heuristic, just returning domain degree to bump up degree somewhat return v.domain().coordinate_element().degree() def spatial_coordinate(self, v): "A coordinate provides additional degrees depending on coordinate field of domain." return v.domain().coordinate_element().degree() def cell_coordinate(self, v): "A coordinate provides one additional degree." return 1 def argument(self, v): """A form argument provides a degree depending on the element, or the default degree if the element has no degree.""" return v.element().degree() # FIXME: Use component to improve accuracy for mixed elements def coefficient(self, v): """A form argument provides a degree depending on the element, or the default degree if the element has no degree.""" e = v.element() e = self.element_replace_map.get(e, e) d = e.degree() # FIXME: Use component to improve accuracy for mixed elements if d is None: d = self.default_degree return d def _reduce_degree(self, v, f): """Reduces the estimated degree by one; used when derivatives are taken. Does not reduce the degree when OuterProduct elements are involved.""" if isinstance(f, int): return max(f-1, 0) else: # if tuple, do not reduce return f def _add_degrees(self, v, *ops): if all(isinstance(o, int) for o in ops): return sum(ops) else: # we can add a slight hack here to handle things # like adding 0 to (3, 3) [by expanding # 0 to (0, 0) when making tempops] tempops = [foo if isinstance(foo, tuple) else (foo, foo) for foo in ops] return tuple(map(sum, zip(*tempops))) def _max_degrees(self, v, *ops): if all(isinstance(o, int) for o in ops): return max(ops + (0,)) else: tempops = [foo if isinstance(foo, tuple) else (foo, foo) for foo in ops] return tuple(map(max, zip(*tempops))) def _not_handled(self, v, *args): error("Missing degree handler for type %s" % v._ufl_class_.__name__) def expr(self, v, *ops): "For most operators we take the max degree of its operands." warning("Missing degree estimation handler for type %s" % v._ufl_class_.__name__) return self._add_degrees(v, *ops) # Utility types with no degree concept def multi_index(self, v): return None def label(self, v): return None # Fall-through, indexing and similar types def variable(self, v, e, l): return e def transposed(self, v, A): return A def index_sum(self, v, A, ii): return A def indexed(self, v, A, ii): return A def component_tensor(self, v, A, ii): return A list_tensor = _max_degrees def positive_restricted(self, v, a): return a def negative_restricted(self, v, a): return a # A sum takes the max degree of its operands: sum = _max_degrees # TODO: Need a new algorithm which considers direction of derivatives of form arguments # A spatial derivative reduces the degree with one grad = _reduce_degree # Handling these types although they should not occur... please apply preprocessing before using this algorithm: nabla_grad = _reduce_degree div = _reduce_degree nabla_div = _reduce_degree curl = _reduce_degree def cell_avg(self, v, a): "Cell average of a function is always cellwise constant." return 0 def facet_avg(self, v, a): "Facet average of a function is always cellwise constant." return 0 # A product accumulates the degrees of its operands: product = _add_degrees # Handling these types although they should not occur... please apply preprocessing before using this algorithm: inner = _add_degrees dot = _add_degrees outer = _add_degrees cross = _add_degrees # Explicitly not handling these types, please apply preprocessing before using this algorithm: derivative = _not_handled # base type compound_derivative = _not_handled # base type compound_tensor_operator = _not_handled # base class variable_derivative = _not_handled trace = _not_handled determinant = _not_handled cofactor = _not_handled inverse = _not_handled deviatoric = _not_handled skew = _not_handled sym = _not_handled def abs(self, v, a): "This is a heuristic, correct if there is no " if a == 0: return a else: return a def division(self, v, *ops): "Using the sum here is a heuristic. Consider e.g. (x+1)/(x-1)." return self._add_degrees(v, *ops) def power(self, v, a, b): """If b is an integer: degree(a**b) == degree(a)*b otherwise use the heuristic degree(a**b) == degree(a)*2""" f, g = v.ufl_operands try: gi = abs(int(g)) if isinstance(a, int): return a*gi else: return tuple(foo*gi for foo in a) except: pass # Something to a non-integer power, this is just a heuristic with no background if isinstance(a, int): return a*2 else: return tuple(foo*2 for foo in a) def atan_2(self, v, a, b): """Using the heuristic degree(atan2(const,const)) == 0 degree(atan2(a,b)) == max(degree(a),degree(b))+2 which can be wildly inaccurate but at least gives a somewhat high integration degree. """ #print "estimate",a,b if a or b: return self._add_degrees(v, self._max_degrees(v, a, b), 2) else: return self._max_degrees(v, a, b) def math_function(self, v, a): """Using the heuristic degree(sin(const)) == 0 degree(sin(a)) == degree(a)+2 which can be wildly inaccurate but at least gives a somewhat high integration degree. """ if a: return self._add_degrees(v, a, 2) else: return a def bessel_function(self, v, nu, x): """Using the heuristic degree(bessel_*(const)) == 0 degree(bessel_*(x)) == degree(x)+2 which can be wildly inaccurate but at least gives a somewhat high integration degree. """ if x: return self._add_degrees(v, x, 2) else: return x def condition(self, v, *args): return None def conditional(self, v, c, t, f): """Degree of condition does not influence degree of values which conditional takes. So heuristicaly taking max of true degree and false degree. This will be exact in cells where condition takes single value. For improving accuracy of quadrature near condition transition surface quadrature order must be adjusted manually.""" return self._max_degrees(v, t, f) def min_value(self, v, l, r): """Same as conditional.""" return self._max_degrees(v, l, r) max_value = min_value def estimate_total_polynomial_degree(e, default_degree=1, element_replace_map={}): """Estimate total polynomial degree of integrand. NB! Although some compound types are supported here, some derivatives and compounds must be preprocessed prior to degree estimation. In generic code, this algorithm should only be applied after preprocessing. For coefficients defined on an element with unspecified degree (None), the degree is set to the given default degree. """ de = SumDegreeEstimator(default_degree, element_replace_map) if isinstance(e, Form): ufl_assert(e.integrals(), "Got form with no integrals!") degrees = [de.visit(integral.integrand()) for integral in e.integrals()] elif isinstance(e, Integral): degrees = [de.visit(e.integrand())] else: degrees = [de.visit(e)] degree = max(degrees) if degrees else default_degree return degree # TODO: Do these contain useful ideas or should we just delete them? def __unused__extract_max_quadrature_element_degree(integral): """Extract quadrature integration order from quadrature elements in integral. Returns None if not found.""" quadrature_elements = [e for e in extract_elements(integral) if "Quadrature" in e.family()] degrees = [element.degree() for element in quadrature_elements] degrees = [q for q in degrees if not q is None] if not degrees: return None max_degree = quadrature_elements[0].degree() ufl_assert(all(max_degree == q for q in degrees), "Incompatible quadrature elements specified (orders must be equal).") return max_degree def __unused__estimate_quadrature_degree(integral): "Estimate the necessary quadrature order for integral using the sum of argument degrees." arguments = extract_arguments(integral) degrees = [v.element().degree() for v in arguments] if len(arguments) == 0: return None if len(arguments) == 1: return 2*degrees[0] return sum(degrees) ufl-1.6.0/ufl/algorithms/expand_compounds.py000066400000000000000000000156411255567402100212010ustar00rootroot00000000000000"""Algorithm for expanding compound expressions into equivalent representations using basic operators.""" # Copyright (C) 2008-2014 Martin Sandve Alnes and Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2009-2010 from ufl.log import error, warning from ufl.assertions import ufl_assert from ufl.classes import Product, Index, Zero, FormArgument, Grad from ufl.core.multiindex import indices from ufl.tensors import as_tensor, as_matrix, as_vector from ufl.algorithms.transformer import Transformer, ReuseTransformer, apply_transformer from ufl.compound_expressions import deviatoric_expr, determinant_expr, cofactor_expr, adj_expr, inverse_expr class CompoundExpander(ReuseTransformer): "Expands compound expressions to equivalent representations using basic operators." def __init__(self): ReuseTransformer.__init__(self) # ------------ Compound tensor operators def trace(self, o, A): i = Index() return A[i, i] def transposed(self, o, A): i, j = indices(2) return as_tensor(A[i, j], (j, i)) def _square_matrix_shape(self, A): sh = A.ufl_shape ufl_assert(sh[0] == sh[1], "Expecting square matrix.") ufl_assert(sh[0] is not None, "Unknown dimension.") return sh def deviatoric(self, o, A): return deviatoric_expr(A) def skew(self, o, A): i, j = indices(2) return as_matrix( (A[i, j] - A[j, i]) / 2, (i, j) ) def sym(self, o, A): i, j = indices(2) return as_matrix( (A[i, j] + A[j, i]) / 2, (i, j) ) def cross(self, o, a, b): def c(i, j): return Product(a[i], b[j]) - Product(a[j], b[i]) return as_vector((c(1, 2), c(2, 0), c(0, 1))) def dot(self, o, a, b): ai = indices(a.rank()-1) bi = indices(b.rank()-1) k = indices(1) # Create an IndexSum over a Product s = a[ai+k]*b[k+bi] return as_tensor(s, ai+bi) def inner(self, o, a, b): ufl_assert(a.rank() == b.rank()) ii = indices(a.rank()) # Create multiple IndexSums over a Product s = a[ii]*b[ii] return s def outer(self, o, a, b): ii = indices(a.rank()) jj = indices(b.rank()) # Create a Product with no shared indices s = a[ii]*b[jj] return as_tensor(s, ii+jj) def determinant(self, o, A): return determinant_expr(A) def cofactor(self, o, A): return cofactor_expr(A) def inverse(self, o, A): return inverse_expr(A) # ------------ Compound differential operators def div(self, o, a): i = Index() return a[..., i].dx(i) def grad(self, o, a): return self.reuse_if_possible(o, a) def nabla_div(self, o, a): i = Index() return a[i, ...].dx(i) def nabla_grad(self, o, a): j = Index() if a.rank() > 0: ii = tuple(indices(a.rank())) return as_tensor(a[ii].dx(j), (j,) + ii) else: return as_tensor(a.dx(j), (j,)) def curl(self, o, a): # o = curl a = "[a.dx(1), -a.dx(0)]" if a.ufl_shape == () # o = curl a = "cross(nabla, (a0, a1, 0))[2]" if a.ufl_shape == (2,) # o = curl a = "cross(nabla, a)" if a.ufl_shape == (3,) def c(i, j): return a[j].dx(i) - a[i].dx(j) sh = a.ufl_shape if sh == (): return as_vector((a.dx(1), -a.dx(0))) if sh == (2,): return c(0, 1) if sh == (3,): return as_vector((c(1, 2), c(2, 0), c(0, 1))) error("Invalid shape %s of curl argument." % (sh,)) """ FIXME: Make expand_compounds_prediff skip types that we make work in expand_derivatives, one by one, and optionally use it instead of expand_compounds from expand_derivatives. """ class CompoundExpanderPreDiff(CompoundExpander): def __init__(self): CompoundExpander.__init__(self) #inner = Transformer.reuse_if_possible #dot = Transformer.reuse_if_possible def grad(self, o, a): return self.reuse_if_possible(o, a) def nabla_grad(self, o, a): r = o.rank() ii = indices(r) jj = ii[-1:] + ii[:-1] return as_tensor(Grad(a)[ii], jj) def div(self, o, a): i = Index() return Grad(a)[..., i, i] def nabla_div(self, o, a): i = Index() return Grad(a)[i, ..., i] def curl(self, o, a): # o = curl a = "[a.dx(1), -a.dx(0)]" if a.ufl_shape == () # o = curl a = "cross(nabla, (a0, a1, 0))[2]" if a.ufl_shape == (2,) # o = curl a = "cross(nabla, a)" if a.ufl_shape == (3,) Da = Grad(a) def c(i, j): #return a[j].dx(i) - a[i].dx(j) return Da[j, i] - Da[i, j] sh = a.ufl_shape if sh == (): #return as_vector((a.dx(1), -a.dx(0))) return as_vector((Da[1], -Da[0])) if sh == (2,): return c(0, 1) if sh == (3,): return as_vector((c(1, 2), c(2, 0), c(0, 1))) error("Invalid shape %s of curl argument." % (sh,)) class CompoundExpanderPostDiff(CompoundExpander): def __init__(self): CompoundExpander.__init__(self) def nabla_grad(self, o, a, i): error("This should not happen.") def div(self, o, a, i): error("This should not happen.") def nabla_div(self, o, a, i): error("This should not happen.") def curl(self, o, a, i): error("This should not happen.") def expand_compounds1(e): """Expand compound objects into basic operators. Requires e to have a well defined geometric dimension.""" return apply_transformer(e, CompoundExpander()) def expand_compounds2(e): """Expand compound objects into basic operators. Requires e to have a well defined geometric dimension.""" return expand_compounds_postdiff(expand_compounds_prediff(e)) def expand_compounds_prediff(e): """Expand compound objects into basic operators. Requires e to have a well defined geometric dimension.""" return apply_transformer(e, CompoundExpanderPreDiff()) def expand_compounds_postdiff(e): """Expand compound objects into basic operators. Requires e to have a well defined geometric dimension.""" return apply_transformer(e, CompoundExpanderPostDiff()) expand_compounds = expand_compounds1 ufl-1.6.0/ufl/algorithms/expand_indices.py000066400000000000000000000163221255567402100206050ustar00rootroot00000000000000"""This module defines expression transformation utilities, for expanding free indices in expressions to explicit fixed indices only.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2009. from six.moves import zip from six.moves import xrange as range from ufl.log import error from ufl.common import Stack, StackDict from ufl.assertions import ufl_assert from ufl.finiteelement import TensorElement from ufl.classes import Expr, Terminal, ListTensor, IndexSum, Indexed, FormArgument from ufl.tensors import as_tensor, ComponentTensor from ufl.permutation import compute_indices from ufl.constantvalue import Zero from ufl.core.multiindex import Index, FixedIndex, MultiIndex from ufl.differentiation import Grad from ufl.algorithms.transformer import ReuseTransformer, apply_transformer from ufl.corealg.traversal import pre_traversal class IndexExpander(ReuseTransformer): """...""" def __init__(self): ReuseTransformer.__init__(self) self._components = Stack() self._index2value = StackDict() def component(self): "Return current component tuple." if self._components: return self._components.peek() return () def terminal(self, x): if x.ufl_shape: c = self.component() ufl_assert(len(x.ufl_shape) == len(c), "Component size mismatch.") return x[c] return x def form_argument(self, x): sh = x.ufl_shape if sh == (): return x else: e = x.element() r = len(sh) # Get component c = self.component() ufl_assert(r == len(c), "Component size mismatch.") # Map it through an eventual symmetry mapping s = e.symmetry() c = s.get(c, c) ufl_assert(r == len(c), "Component size mismatch after symmetry mapping.") return x[c] def zero(self, x): ufl_assert(len(x.ufl_shape) == len(self.component()), "Component size mismatch.") s = set(x.ufl_free_indices) - set(i.count() for i in self._index2value.keys()) if s: error("Free index set mismatch, these indices have no value assigned: %s." % str(s)) # There is no index/shape info in this zero because that is asserted above return Zero() def scalar_value(self, x): if len(x.ufl_shape) != len(self.component()): self.print_visit_stack() ufl_assert(len(x.ufl_shape) == len(self.component()), "Component size mismatch.") s = set(x.ufl_free_indices) - set(i.count() for i in self._index2value.keys()) if s: error("Free index set mismatch, these indices have no value assigned: %s." % str(s)) return x._ufl_class_(x.value()) def division(self, x): a, b = x.ufl_operands # Not accepting nonscalars in division anymore ufl_assert(a.ufl_shape == (), "Not expecting tensor in division.") ufl_assert(self.component() == (), "Not expecting component in division.") ufl_assert(b.ufl_shape == (), "Not expecting division by tensor.") a = self.visit(a) #self._components.push(()) b = self.visit(b) #self._components.pop() return self.reuse_if_possible(x, a, b) def index_sum(self, x): ops = [] summand, multiindex = x.ufl_operands index, = multiindex # TODO: For the list tensor purging algorithm, do something like: # if index not in self._to_expand: # return self.expr(x, *[self.visit(o) for o in x.ufl_operands]) for value in range(x.dimension()): self._index2value.push(index, value) ops.append(self.visit(summand)) self._index2value.pop() return sum(ops) def _multi_index_values(self, x): comp = [] for i in x._indices: if isinstance(i, FixedIndex): comp.append(i._value) elif isinstance(i, Index): comp.append(self._index2value[i]) return tuple(comp) def multi_index(self, x): comp = self._multi_index_values(x) return MultiIndex(tuple(FixedIndex(i) for i in comp)) def indexed(self, x): A, ii = x.ufl_operands # Push new component built from index value map self._components.push(self._multi_index_values(ii)) # Hide index values (doing this is not correct behaviour) #for i in ii: # if isinstance(i, Index): # self._index2value.push(i, None) result = self.visit(A) # Un-hide index values #for i in ii: # if isinstance(i, Index): # self._index2value.pop() # Reset component self._components.pop() return result def component_tensor(self, x): # This function evaluates the tensor expression # with indices equal to the current component tuple expression, indices = x.ufl_operands ufl_assert(expression.ufl_shape == (), "Expecting scalar base expression.") # Update index map with component tuple values comp = self.component() ufl_assert(len(indices) == len(comp), "Index/component mismatch.") for i, v in zip(indices.indices(), comp): self._index2value.push(i, v) self._components.push(()) # Evaluate with these indices result = self.visit(expression) # Revert index map for _ in comp: self._index2value.pop() self._components.pop() return result def list_tensor(self, x): # Pick the right subtensor and subcomponent c = self.component() c0, c1 = c[0], c[1:] op = x.ufl_operands[c0] # Evaluate subtensor with this subcomponent self._components.push(c1) r = self.visit(op) self._components.pop() return r def grad(self, x): f, = x.ufl_operands ufl_assert(isinstance(f, (Terminal, Grad)), "Expecting expand_derivatives to have been applied.") # No need to visit child as long as it is on the form [Grad]([Grad](terminal)) return x[self.component()] def expand_indices(e): return apply_transformer(e, IndexExpander()) def purge_list_tensors(expr): """Get rid of all ListTensor instances by expanding expressions to use their components directly. Will usually increase the size of the expression.""" if any(isinstance(subexpr, ListTensor) for subexpr in pre_traversal(expr)): return expand_indices(expr) # TODO: Only expand what's necessary to get rid of list tensors return expr ufl-1.6.0/ufl/algorithms/formdata.py000066400000000000000000000067711255567402100174340ustar00rootroot00000000000000"""FormData class easy for collecting of various data about a form.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2008. from ufl.common import lstr, tstr, estr from ufl.assertions import ufl_assert class FormData(object): """ Class collecting various information extracted from a Form by calling preprocess. """ def __init__(self): "Create empty form data for given form." def __str__(self): "Return formatted summary of form data" types = sorted(self.num_sub_domains.keys()) subdomains = tuple(("Number of %s subdomains" % integral_type, self.num_sub_domains[integral_type]) for integral_type in types) return tstr( (# Geometry ("Geometric dimension", self.geometric_dimension), ) + subdomains + ( # Arguments ("Rank", self.rank), ("Arguments", lstr(self.original_form.arguments())), # Coefficients ("Number of coefficients", self.num_coefficients), ("Coefficients", lstr(self.reduced_coefficients)), # Elements ("Unique elements", estr(self.unique_elements)), ("Unique sub elements", estr(self.unique_sub_elements)), )) class ExprData(object): """ Class collecting various information extracted from a Expr by calling preprocess. """ def __init__(self): "Create empty expr data for given expr." def __str__(self): "Return formatted summary of expr data" return tstr((("Name", self.name), ("Cell", self.cell), ("Topological dimension", self.topological_dimension), ("Geometric dimension", self.geometric_dimension), ("Rank", self.rank), ("Number of coefficients", self.num_coefficients), ("Arguments", lstr(self.arguments)), ("Coefficients", lstr(self.coefficients)), ("Argument names", lstr(self.argument_names)), ("Coefficient names", lstr(self.coefficient_names)), ("Unique elements", estr(self.unique_elements)), ("Unique sub elements", estr(self.unique_sub_elements)), # FIXME DOMAINS what is "the domain(s)" for an expression? ("Domains", self.domains), )) ufl-1.6.0/ufl/algorithms/formfiles.py000066400000000000000000000175151255567402100176230ustar00rootroot00000000000000"""A collection of utility algorithms for handling UFL files.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2008-2009. # Modified by Marie E. Rognes, 2011. import os, re from ufl.log import error, warning from ufl.utils.sorting import sorted_by_key from ufl.assertions import ufl_assert from ufl.form import Form from ufl.finiteelement import FiniteElementBase from ufl.core.expr import Expr from ufl.argument import Argument from ufl.coefficient import Coefficient class FileData(object): def __init__(self): self.elements = [] self.coefficients = [] self.expressions = [] self.forms = [] self.object_names = {} self.object_by_name = {} self.reserved_objects = {} def __bool__(self): return bool(self.elements or self.coefficients or self.forms or self.expressions or\ self.object_names or self.object_by_name or self.reserved_objects) __nonzero__ = __bool__ infostring = """An exception occured during evaluation of form file. To help you find the location of the error, a temporary script '%s' has been created and will now be executed with debug output enabled:""" def replace_include_statements(code): "Replace '#include foo.ufl' statements with contents of foo.ufl." if "#include" in code: lines = code.split("\n") newlines = [] regexp = re.compile(r"^#include (.*)$") for l in lines: m = regexp.search(l) if m: fn = m.groups()[0] newlines.append("# --- begin %s" % fn) newlines.extend(open(fn).readlines()) newlines.append("# --- end %s" % fn) else: newlines.append(l) return "\n".join(l.rstrip() for l in newlines) return code def read_ufl_file(filename): "Read a .ufl file, handling file extension, file existance, and #include replacement." if not os.path.exists(filename) and filename[-4:] != ".ufl": filename = filename + ".ufl" if not os.path.exists(filename): error("File '%s' doesn't exist." % filename) with open(filename) as f: code = replace_include_statements(f.read()) return code def execute_ufl_code(uflcode, filename): # Execute code namespace = {} try: pycode = "from ufl import *\n" + uflcode exec(pycode, namespace) except: # Dump python code for debugging if this fails basename = os.path.splitext(os.path.basename(filename))[0] basename = "%s_debug" % basename pyname = "%s.py" % basename pycode = "#!/usr/bin/env python\nfrom ufl import *\nset_level(DEBUG)\n" + uflcode with open(pyname, "w") as f: f.write(pycode) warning(infostring % pyname) m = __import__(basename) error("An error occured, aborting load_forms.") return namespace def interpret_ufl_namespace(namespace): "Takes a namespace dict from an executed ufl file and converts it to a FileData object." # Object to hold all returned data ufd = FileData() # Extract object names for Form, Coefficient and FiniteElementBase objects # The use of id(obj) as key in object_names is necessary # because we need to distinguish between instances, # and not just between objects with different values. for name, value in sorted_by_key(namespace): # Store objects by reserved name OR instance id reserved_names = ("unknown",) # Currently only one reserved name if name in reserved_names: # Store objects with reserved names ufd.reserved_objects[name] = value # FIXME: Remove after FFC is updated to use reserved_objects: ufd.object_names[name] = value ufd.object_by_name[name] = value elif isinstance(value, (FiniteElementBase, Coefficient, Argument, Form, Expr)): # Store instance <-> name mappings for important objects without a reserved name ufd.object_names[id(value)] = name ufd.object_by_name[name] = value # Get list of exported forms forms = namespace.get("forms") if forms is None: # Get forms from object_by_name, which has already mapped tuple->Form where needed def get_form(name): form = ufd.object_by_name.get(name) return form if isinstance(form, Form) else None a_form = get_form("a") L_form = get_form("L") M_form = get_form("M") forms = [a_form, L_form, M_form] # Add forms F and J if not "a" and "L" are used if a_form is None or L_form is None: F_form = get_form("F") J_form = get_form("J") forms += [F_form, J_form] # Remove Nones forms = [f for f in forms if isinstance(f, Form)] ufd.forms = forms # Validate types ufl_assert(isinstance(ufd.forms, (list, tuple)), "Expecting 'forms' to be a list or tuple, not '%s'." % type(ufd.forms)) ufl_assert(all(isinstance(a, Form) for a in ufd.forms), "Expecting 'forms' to be a list of Form instances.") # Get list of exported elements elements = namespace.get("elements") if elements is None: elements = [ufd.object_by_name.get(name) for name in ("element",)] elements = [e for e in elements if e is not None] ufd.elements = elements # Validate types ufl_assert(isinstance(ufd.elements, (list, tuple)), "Expecting 'elements' to be a list or tuple, not '%s'." % type(ufd.elements)) ufl_assert(all(isinstance(e, FiniteElementBase) for e in ufd.elements), "Expecting 'elements' to be a list of FiniteElementBase instances.") # Get list of exported coefficients # TODO: Temporarily letting 'coefficients' override 'functions', but allow 'functions' for compatibility functions = namespace.get("functions", []) if functions: warning("Deprecation warning: Rename 'functions' to 'coefficients' to export coefficients.") ufd.coefficients = namespace.get("coefficients", functions) #ufd.coefficients = namespace.get("coefficients", []) # Validate types ufl_assert(isinstance(ufd.coefficients, (list, tuple)), "Expecting 'coefficients' to be a list or tuple, not '%s'." % type(ufd.coefficients)) ufl_assert(all(isinstance(e, Coefficient) for e in ufd.coefficients), "Expecting 'coefficients' to be a list of Coefficient instances.") # Get list of exported expressions ufd.expressions = namespace.get("expressions", []) # Validate types ufl_assert(isinstance(ufd.expressions, (list, tuple)), "Expecting 'expressions' to be a list or tuple, not '%s'." % type(ufd.expressions)) ufl_assert(all(isinstance(e, Expr) for e in ufd.expressions), "Expecting 'expressions' to be a list of Expr instances.") # Return file data return ufd def load_ufl_file(filename): "Load a .ufl file with elements, coefficients and forms." # Read code from file and execute it uflcode = read_ufl_file(filename) namespace = execute_ufl_code(uflcode, filename) return interpret_ufl_namespace(namespace) def load_forms(filename): "Return a list of all forms in a file." ufd = load_ufl_file(filename) return ufd.forms ufl-1.6.0/ufl/algorithms/formtransformations.py000066400000000000000000000402351255567402100217450ustar00rootroot00000000000000"""This module defines utilities for transforming complete Forms into new related Forms.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2008-2009. # Modified by Garth N. Wells, 2010. # Modified by Marie E. Rognes, 2010. from six import iteritems from six.moves import xrange as range from ufl.common import product from ufl.log import error, warning, debug from ufl.assertions import ufl_assert # All classes: from ufl.argument import Argument from ufl.coefficient import Coefficient from ufl.constantvalue import Zero from ufl.algebra import Sum # Other algorithms: from ufl.algorithms.map_integrands import map_integrands from ufl.algorithms.transformer import Transformer from ufl.algorithms.replace import replace # FIXME: Don't use this below, it makes partextracter more expensive than necessary def _expr_has_terminal_types(expr, ufl_types): input = [expr] while input: e = input.pop() ops = e.ufl_operands if ops: input.extend(ops) elif isinstance(e, ufl_types): return True return False def zero_expr(e): return Zero(e.ufl_shape, e.ufl_free_indices, e.ufl_index_dimensions) class PartExtracter(Transformer): """ PartExtracter extracts those parts of a form that contain the given argument(s). """ def __init__(self, arguments): Transformer.__init__(self) self._want = set(arguments) def expr(self, x): """The default is a nonlinear operator not accepting any Arguments among its children.""" if _expr_has_terminal_types(x, Argument): error("Found Argument in %s, this is an invalid expression." % repr(x)) return (x, set()) # Terminals that are not Variables or Arguments behave as default # expr-s. terminal = expr def variable(self, x): "Return relevant parts of this variable." # Extract parts/provides from this variable's expression expression, label = x.ufl_operands part, provides = self.visit(expression) # If the extracted part is zero or we provide more than we # want, return zero if isinstance(part, Zero) or (provides - self._want): return (zero_expr(x), set()) # Reuse varible if possible (or reconstruct from part) x = self.reuse_if_possible(x, part, label) return (x, provides) def argument(self, x): "Return itself unless itself provides too much." # An argument provides itself provides = {x} # If we provide more than we want, return zero if provides - self._want: return (zero_expr(x), set()) return (x, provides) def sum(self, x): """ Return the terms that might eventually yield the correct parts(!) The logic required for sums is a bit elaborate: A sum may contain terms providing different arguments. We should return (a sum of) a suitable subset of these terms. Those should all provide the same arguments. For each term in a sum, there are 2 simple possibilities: 1a) The relevant part of the term is zero -> skip. 1b) The term provides more arguments than we want -> skip 2) If all terms fall into the above category, we can just return zero. Any remaining terms may provide exactly the arguments we want, or fewer. This is where things start getting interesting. 3) Bottom-line: if there are terms with providing different arguments -- provide terms that contain the most arguments. If there are terms providing different sets of same size -> throw error (e.g. Argument(-1) + Argument(-2)) """ parts_that_provide = {} # 1. Skip terms that provide too much original_terms = x.ufl_operands assert len(original_terms) == 2 for term in original_terms: # Visit this term in the sum part, term_provides = self.visit(term) # If this part is zero or it provides more than we want, # skip it if isinstance(part, Zero) or (term_provides - self._want): continue # Add the contributions from this part to temporary list term_provides = frozenset(term_provides) if term_provides in parts_that_provide: parts_that_provide[term_provides] += [part] else: parts_that_provide[term_provides] = [part] # 2. If there are no remaining terms, return zero if not parts_that_provide: return (zero_expr(x), set()) # 3. Return the terms that provide the biggest set most_provided = frozenset() for (provideds, parts) in iteritems(parts_that_provide): # TODO: Just sort instead? # Throw error if size of sets are equal (and not zero) if len(provideds) == len(most_provided) and len(most_provided): error("Don't know what to do with sums with different Arguments.") if provideds > most_provided: most_provided = provideds terms = parts_that_provide[most_provided] if len(terms) == 2: x = self.reuse_if_possible(x, *terms) else: x, = terms return (x, most_provided) def product(self, x, *ops): """ Note: Product is a visit-children-first handler. ops are the visited factors.""" provides = set() factors = [] for factor, factor_provides in ops: # If any factor is zero, return if isinstance(factor, Zero): return (zero_expr(x), set()) # Add factor to factors and extend provides factors.append(factor) provides = provides | factor_provides # If we provide more than we want, return zero if provides - self._want: return (zero_expr(x), provides) # Reuse product if possible (or reconstruct from factors) x = self.reuse_if_possible(x, *factors) return (x, provides) # inner, outer and dot all behave as product inner = product outer = product dot = product def division(self, x): "Return parts_of_numerator/denominator." # Get numerator and denominator numerator, denominator = x.ufl_operands # Check for Arguments in the denominator if _expr_has_terminal_types(denominator, Argument): error("Found Argument in denominator of %s , this is an invalid expression." % repr(x)) # Visit numerator numerator_parts, provides = self.visit(numerator) # If numerator is zero, return zero. (No need to check whether # it provides too much, already checked by visit.) if isinstance(numerator_parts, Zero): return (zero_expr(x), set()) # Reuse x if possible, otherwise reconstruct from (parts of) # numerator and denominator x = self.reuse_if_possible(x, numerator_parts, denominator) return (x, provides) def linear_operator(self, x, arg): """A linear operator with a single operand accepting arity > 0, providing whatever Argument its operand does.""" # linear_operator is a visit-children-first handler. Handled # arguments are in arg. part, provides = arg # Discard if part is zero. (No need to check whether we # provide too much, already checked by children.) if isinstance(part, Zero): return (zero_expr(x), set()) x = self.reuse_if_possible(x, part) return (x, provides) # Positive and negative restrictions behave as linear operators positive_restricted = linear_operator negative_restricted = linear_operator # Cell and facet average are linear operators cell_avg = linear_operator facet_avg = linear_operator # Grad is a linear operator grad = linear_operator def linear_indexed_type(self, x): """Return parts of expression belonging to this indexed expression.""" expression, index = x.ufl_operands part, provides = self.visit(expression) # Return zero if extracted part is zero. (The expression # should already have checked if it provides too much.) if isinstance(part, Zero): return (zero_expr(x), set()) # Reuse x if possible (or reconstruct by indexing part) x = self.reuse_if_possible(x, part, index) return (x, provides) # All of these indexed thingies behave as a linear_indexed_type indexed = linear_indexed_type index_sum = linear_indexed_type component_tensor = linear_indexed_type def list_tensor(self, x, *ops): # list_tensor is a visit-children-first handler. ops contains # the visited operands with their provides. (It follows that # none of the visited operands provide more than wanted.) # Extract the most arguments provided by any of the components most_provides = ops[0][1] for (component, provides) in ops: if (provides - most_provides): most_provides = provides # Check that all components either provide the same arguments # or vanish. (This check is here b/c it is not obvious what to # return if the components provide different arguments, at # least with the current transformer design.) for (component, provides) in ops: if (provides != most_provides and not isinstance(component, Zero)): error("PartExtracter does not know how to handle list_tensors with non-zero components providing fewer arguments") # Return components components = [op[0] for op in ops] x = self.reuse_if_possible(x, *components) return (x, most_provides) def compute_form_with_arity(form, arity, arguments=None): """Compute parts of form of given arity.""" # Extract all arguments in form if arguments is None: arguments = form.arguments() parts = [arg.part() for arg in arguments] if set(parts) - {None}: error("compute_form_with_arity cannot handle parts.") if len(arguments) < arity: warning("Form has no parts with arity %d." % arity) return 0*form # Assuming that the form is not a sum of terms # that depend on different arguments, e.g. (u+v)*dx # would result in just v*dx. But that doesn't make # any sense anyway. sub_arguments = set(arguments[:arity]) pe = PartExtracter(sub_arguments) def _transform(e): e, provides = pe.visit(e) if provides == sub_arguments: return e return Zero() return map_integrands(_transform, form) def compute_form_arities(form): """Return set of arities of terms present in form.""" #ufl_assert(form.is_preprocessed(), "Assuming a preprocessed form.") # Extract all arguments present in form arguments = form.arguments() parts = [arg.part() for arg in arguments] if set(parts) - {None}: error("compute_form_arities cannot handle parts.") arities = set() for arity in range(len(arguments)+1): # Compute parts with arity "arity" parts = compute_form_with_arity(form, arity, arguments) # Register arity if "parts" does not vanish if parts and parts.integrals(): arities.add(arity) return arities def compute_form_lhs(form): """Compute the left hand side of a form. Example: a = u*v*dx + f*v*dx a = lhs(a) -> u*v*dx """ return compute_form_with_arity(form, 2) def compute_form_rhs(form): """Compute the right hand side of a form. Example: a = u*v*dx + f*v*dx L = rhs(a) -> -f*v*dx """ return -compute_form_with_arity(form, 1) def compute_form_functional(form): """Compute the functional part of a form, that is the terms independent of Arguments. (Used for testing, not sure if it's useful for anything?)""" return compute_form_with_arity(form, 0) def compute_form_action(form, coefficient): """Compute the action of a form on a Coefficient. This works simply by replacing the last Argument with a Coefficient on the same function space (element). The form returned will thus have one Argument less and one additional Coefficient at the end if no Coefficient has been provided. """ # TODO: Check whatever makes sense for coefficient # Extract all arguments arguments = form.arguments() parts = [arg.part() for arg in arguments] if set(parts) - {None}: error("compute_form_action cannot handle parts.") # Pick last argument (will be replaced) u = arguments[-1] e = u.element() if coefficient is None: coefficient = Coefficient(e) else: #ufl_assert(coefficient.element() == e, \ if coefficient.element() != e: debug("Computing action of form on a coefficient in a different element space.") return replace(form, { u: coefficient }) def compute_energy_norm(form, coefficient): """Compute the a-norm of a Coefficient given a form a. This works simply by replacing the two Arguments with a Coefficient on the same function space (element). The Form returned will thus be a functional with no Arguments, and one additional Coefficient at the end if no coefficient has been provided. """ arguments = form.arguments() parts = [arg.part() for arg in arguments] if set(parts) - {None}: error("compute_energy_norm cannot handle parts.") ufl_assert(len(arguments) == 2, "Expecting bilinear form.") v, u = arguments e = u.element() e2 = v.element() ufl_assert(e == e2, "Expecting equal finite elements for test and trial functions, got '%s' and '%s'." % (str(e), str(e2))) if coefficient is None: coefficient = Coefficient(e) else: ufl_assert(coefficient.element() == e, \ "Trying to compute action of form on a "\ "coefficient in an incompatible element space.") return replace(form, { u: coefficient, v: coefficient }) def compute_form_adjoint(form, reordered_arguments=None): """Compute the adjoint of a bilinear form. This works simply by swapping the number and part of the two arguments, but keeping their elements and places in the integrand expressions. """ arguments = form.arguments() parts = [arg.part() for arg in arguments] if set(parts) - {None}: error("compute_form_adjoint cannot handle parts.") ufl_assert(len(arguments) == 2, "Expecting bilinear form.") v, u = arguments ufl_assert(v.number() < u.number(), "Mistaken assumption in code!") if reordered_arguments is None: reordered_u = u.reconstruct(number=v.number(), part=v.part()) reordered_v = v.reconstruct(number=u.number(), part=u.part()) else: reordered_u, reordered_v = reordered_arguments ufl_assert(reordered_u.number() < reordered_v.number(), "Ordering of new arguments is the same as the old arguments!") ufl_assert(reordered_u.part() == v.part(), "Ordering of new arguments is the same as the old arguments!") ufl_assert(reordered_v.part() == u.part(), "Ordering of new arguments is the same as the old arguments!") ufl_assert(reordered_u.element() == u.element(), "Element mismatch between new and old arguments (trial functions).") ufl_assert(reordered_v.element() == v.element(), "Element mismatch between new and old arguments (test functions).") return replace(form, {v: reordered_v, u: reordered_u}) ufl-1.6.0/ufl/algorithms/forward_ad.py000066400000000000000000001071011255567402100177340ustar00rootroot00000000000000"""Forward mode AD implementation.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2009. # Modified by Garth N. Wells, 2010. # Modified by Kristian B. Oelgaard, 2011 # Modified by Jan Blechta, 2012. from six.moves import xrange as range from six.moves import zip from math import pi from ufl.log import error, warning, debug from ufl.assertions import ufl_assert from ufl.common import unzip, subdict, lstr # All classes: from ufl.core.terminal import Terminal from ufl.constantvalue import ConstantValue, Zero, IntValue, Identity,\ is_true_ufl_scalar, is_ufl_scalar from ufl.variable import Variable from ufl.coefficient import Coefficient, FormArgument from ufl.core.multiindex import MultiIndex, Index, FixedIndex, indices from ufl.indexed import Indexed from ufl.indexsum import IndexSum from ufl.tensors import ListTensor, ComponentTensor, as_tensor, as_scalar, unit_indexed_tensor, unwrap_list_tensor from ufl.algebra import Sum, Product, Division, Power, Abs from ufl.tensoralgebra import Transposed, Outer, Inner, Dot, Cross, Trace, \ Determinant, Inverse, Deviatoric, Cofactor from ufl.mathfunctions import MathFunction, Sqrt, Exp, Ln, Cos, Sin, Tan, Acos, Asin, Atan, Atan2, Erf, BesselJ, BesselY, BesselI, BesselK from ufl.restriction import Restricted, PositiveRestricted, NegativeRestricted from ufl.differentiation import Derivative, CoefficientDerivative,\ VariableDerivative, Grad from ufl.conditional import EQ, NE, LE, GE, LT, GT, Conditional from ufl.exprcontainers import ExprList, ExprMapping from ufl.operators import dot, inner, outer, lt, eq, conditional, sign, \ sqrt, exp, ln, cos, sin, tan, cosh, sinh, tanh, acos, asin, atan, atan_2, \ erf, bessel_J, bessel_Y, bessel_I, bessel_K, \ cell_avg, facet_avg from ufl.algorithms.transformer import Transformer class ForwardAD(Transformer): def __init__(self, var_shape, cache=None): Transformer.__init__(self) self._var_shape = var_shape self._variable_cache = {} if cache is None else cache def _debug_visit(self, o): "Debugging hook, enable this by renaming to 'visit'." r = Transformer.visit(self, o) f, df = r if not f is o: debug("In ForwardAD.visit, didn't get back o:") debug(" o: %s" % str(o)) debug(" f: %s" % str(f)) debug(" df: %s" % str(df)) fi_diff = set(f.ufl_free_indices) ^ set(df.ufl_free_indices) if fi_diff: debug("In ForwardAD.visit, got free indices diff:") debug(" o: %s" % str(o)) debug(" f: %s" % str(f)) debug(" df: %s" % str(df)) debug(" f.fi(): %s" % lstr(f.ufl_free_indices)) debug(" df.fi(): %s" % lstr(df.ufl_free_indices)) debug(" fi_diff: %s" % str(fi_diff)) return r def _make_zero_diff(self, o): # Define a zero with the right shape and indices return Zero(o.ufl_shape + self._var_shape, o.ufl_free_indices, o.ufl_index_dimensions) # --- Default rules def expr(self, o): error("Missing ForwardAD handler for type %s" % str(type(o))) def terminal(self, o): """Terminal objects are assumed independent of the differentiation variable by default, and simply 'lifted' to the pair (o, 0). Depending on the context, override this with custom rules for non-zero derivatives.""" fp = self._make_zero_diff(o) return (o, fp) def variable(self, o): """Variable objects are just 'labels', so by default the derivative of a variable is the derivative of its referenced expression.""" # Check variable cache to reuse previously transformed variable if possible e, l = o.ufl_operands r = self._variable_cache.get(l) # cache contains (v, vp) tuple if r is not None: return r # Visit the expression our variable represents e2, vp = self.visit(e) # Recreate Variable (with same label) only if necessary v = self.reuse_if_possible(o, e2, l) # Cache and return (v, vp) tuple r = (v, vp) self._variable_cache[l] = r return r # --- Indexing and component handling def multi_index(self, o): return (o, None) # oprime here should never be used, this might even not be called? def indexed(self, o): A, jj = o.ufl_operands A2, Ap = self.visit(A) o = self.reuse_if_possible(o, A2, jj) if isinstance(Ap, Zero): op = self._make_zero_diff(o) else: r = Ap.rank() - len(jj) if r: ii = indices(r) op = Indexed(Ap, MultiIndex(jj.indices() + ii)) op = as_tensor(op, ii) else: op = Indexed(Ap, jj) return (o, op) def list_tensor(self, o, *ops): ops, dops = unzip(ops) o = self.reuse_if_possible(o, *ops) op = ListTensor(*dops) return (o, op) def component_tensor(self, o): A, ii = o.ufl_operands A, Ap = self.visit(A) o = self.reuse_if_possible(o, A, ii) if isinstance(Ap, Zero): op = self._make_zero_diff(o) else: Ap, jj = as_scalar(Ap) op = as_tensor(Ap, ii.indices() + jj) return (o, op) # --- Algebra operators def index_sum(self, o): A, i = o.ufl_operands A2, Ap = self.visit(A) o = self.reuse_if_possible(o, A2, i) op = IndexSum(Ap, i) return (o, op) def sum(self, o, *ops): ops, opsp = unzip(ops) o2 = self.reuse_if_possible(o, *ops) op = sum(opsp[1:], opsp[0]) return (o2, op) def product(self, o, *ops): # Start with a zero with the right shape and indices fp = self._make_zero_diff(o) # Get operands and their derivatives ops2, dops2 = unzip(ops) o = self.reuse_if_possible(o, *ops2) for i in range(len(ops)): # Get scalar representation of differentiated value of operand i dop = dops2[i] dop, ii = as_scalar(dop) # Replace operand i with its differentiated value in product fpoperands = ops2[:i] + [dop] + ops2[i+1:] p = Product(*fpoperands) # Wrap product in tensor again if ii: p = as_tensor(p, ii) # Accumulate terms fp += p return (o, fp) def division(self, o, a, b): f, fp = a g, gp = b o = self.reuse_if_possible(o, f, g) ufl_assert(is_ufl_scalar(f), "Not expecting nonscalar nominator") ufl_assert(is_true_ufl_scalar(g), "Not expecting nonscalar denominator") #do_df = 1/g #do_dg = -h/g #op = do_df*fp + do_df*gp #op = (fp - o*gp) / g # Get o and gp as scalars, multiply, then wrap as a tensor again so, oi = as_scalar(o) sgp, gi = as_scalar(gp) o_gp = so*sgp if oi or gi: o_gp = as_tensor(o_gp, oi + gi) op = (fp - o_gp) / g return (o, op) def power(self, o, a, b): f, fp = a g, gp = b # Debugging prints, should never happen: if not is_true_ufl_scalar(f): print(":"*80) print("f =", str(f)) print("g =", str(g)) print(":"*80) ufl_assert(is_true_ufl_scalar(f), "Expecting scalar expression f in f**g.") ufl_assert(is_true_ufl_scalar(g), "Expecting scalar expression g in f**g.") # Derivation of the general case: o = f(x)**g(x) # #do_df = g * f**(g-1) #do_dg = ln(f) * f**g #op = do_df*fp + do_dg*gp # #do_df = o * g / f # f**g * g / f #do_dg = ln(f) * o #op = do_df*fp + do_dg*gp # Got two possible alternatives here: if True: # This version looks better. # Rewriting o as f*f**(g-1) we can do: f_g_m1 = f**(g-1) op = f_g_m1*(fp*g + f*ln(f)*gp) # In this case we can rewrite o using new subexpression o = f*f_g_m1 else: # Pulling o out gives: op = o*(fp*g/f + ln(f)*gp) # This produces expressions like (1/w)*w**5 instead of w**4 # If we do this, we reuse o o = self.reuse_if_possible(o, f, g) return (o, op) def abs(self, o, a): f, fprime = a o = self.reuse_if_possible(o, f) #oprime = conditional(eq(f, 0), 0, Product(sign(f), fprime)) oprime = sign(f)*fprime return (o, oprime) # --- Mathfunctions def math_function(self, o, a): if hasattr(o, 'derivative'): # FIXME: Introduce a UserOperator type instead of this hack f, fp = a o = self.reuse_if_possible(o, f) op = fp * o.derivative() return (o, op) error("Unknown math function.") def sqrt(self, o, a): f, fp = a o = self.reuse_if_possible(o, f) op = fp / (2*o) return (o, op) def exp(self, o, a): f, fp = a o = self.reuse_if_possible(o, f) op = fp*o return (o, op) def ln(self, o, a): f, fp = a o = self.reuse_if_possible(o, f) ufl_assert(not isinstance(f, Zero), "Division by zero.") return (o, fp/f) def cos(self, o, a): f, fp = a o = self.reuse_if_possible(o, f) op = -fp*sin(f) return (o, op) def sin(self, o, a): f, fp = a o = self.reuse_if_possible(o, f) op = fp*cos(f) return (o, op) def tan(self, o, a): f, fp = a o = self.reuse_if_possible(o, f) op = fp*2.0/(cos(2.0*f) + 1.0) return (o, op) def cosh(self, o, a): f, fp = a o = self.reuse_if_possible(o, f) op = fp*sinh(f) return (o, op) def sinh(self, o, a): f, fp = a o = self.reuse_if_possible(o, f) op = fp*cosh(f) return (o, op) def tanh(self, o, a): f, fp = a o = self.reuse_if_possible(o, f) def sech(y): return (2.0*cosh(y)) / (cosh(2.0*y) + 1.0) op = fp*sech(f)**2 return (o, op) def acos(self, o, a): f, fp = a o = self.reuse_if_possible(o, f) op = -fp/sqrt(1.0 - f**2) return (o, op) def asin(self, o, a): f, fp = a o = self.reuse_if_possible(o, f) op = fp/sqrt(1.0 - f**2) return (o, op) def atan(self, o, a): f, fp = a o = self.reuse_if_possible(o, f) op = fp/(1.0 + f**2) return (o, op) def atan_2(self, o, a, b): f, fp = a g, gp = b o = self.reuse_if_possible(o, f, g) op = (g*fp-f*gp)/(f**2+g**2) return (o, op) def erf(self, o, a): f, fp = a o = self.reuse_if_possible(o, f) op = fp*(2.0/sqrt(pi)*exp(-f**2)) return (o, op) def bessel_j(self, o, nu, x): nu, dummy = nu if not (dummy is None or isinstance(dummy, Zero)): error("Differentiation of bessel function w.r.t. nu is not supported.") f, fp = x o = self.reuse_if_possible(o, nu, f) if isinstance(nu, Zero): op = -bessel_J(1, f) else: op = 0.5 * (bessel_J(nu-1, f) - bessel_J(nu+1, f)) return (o, op*fp) def bessel_y(self, o, nu, x): nu, dummy = nu if not (dummy is None or isinstance(dummy, Zero)): error("Differentiation of bessel function w.r.t. nu is not supported.") f, fp = x o = self.reuse_if_possible(o, nu, f) if isinstance(nu, Zero): op = -bessel_Y(1, f) else: op = 0.5 * (bessel_Y(nu-1, f) - bessel_Y(nu+1, f)) return (o, op*fp) def bessel_i(self, o, nu, x): nu, dummy = nu if not (dummy is None or isinstance(dummy, Zero)): error("Differentiation of bessel function w.r.t. nu is not supported.") f, fp = x o = self.reuse_if_possible(o, nu, f) if isinstance(nu, Zero): op = bessel_I(1, f) else: op = 0.5 * (bessel_I(nu-1, f) + bessel_I(nu+1, f)) return (o, op*fp) def bessel_k(self, o, nu, x): nu, dummy = nu if not (dummy is None or isinstance(dummy, Zero)): error("Differentiation of bessel function w.r.t. nu is not supported.") f, fp = x o = self.reuse_if_possible(o, nu, f) if isinstance(nu, Zero): op = -bessel_K(1, f) else: op = -0.5 * (bessel_K(nu-1, f) + bessel_K(nu+1, f)) return (o, op*fp) # --- Restrictions def restricted(self, o, a): # Restriction and differentiation commutes. f, fp = a o = self.reuse_if_possible(o, f) if isinstance(fp, ConstantValue): return (o, fp) # TODO: Necessary? Can't restriction simplify directly instead? else: return (o, fp(o._side)) # (f+-)' == (f')+- def cell_avg(self, o, a): # Cell average of a single function and differentiation commutes. f, fp = a o = self.reuse_if_possible(o, f) if isinstance(fp, ConstantValue): return (o, fp) # TODO: Necessary? Can't CellAvg simplify directly instead? else: return (o, cell_avg(fp)) def facet_avg(self, o, a): # Facet average of a single function and differentiation commutes. f, fp = a o = self.reuse_if_possible(o, f) if isinstance(fp, ConstantValue): return (o, fp) # TODO: Necessary? Can't FacetAvg simplify directly instead? else: return (o, facet_avg(fp)) # --- Conditionals def binary_condition(self, o, l, r): o = self.reuse_if_possible(o, l[0], r[0]) #if any(not (isinstance(op[1], Zero) or op[1] is None) for op in (l, r)): # warning("Differentiating a conditional with a condition "\ # "that depends on the differentiation variable."\ # "Assuming continuity of conditional. The condition "\ # "will not be differentiated.") oprime = None # Shouldn't be used anywhere return (o, oprime) def not_condition(self, o, c): o = self.reuse_if_possible(o, c[0]) #if not (isinstance(c[1], Zero) or c[1] is None): # warning("Differentiating a conditional with a condition "\ # "that depends on the differentiation variable."\ # "Assuming continuity of conditional. The condition "\ # "will not be differentiated.") oprime = None # Shouldn't be used anywhere return (o, oprime) def conditional(self, o, c, t, f): o = self.reuse_if_possible(o, c[0], t[0], f[0]) if isinstance(t[1], Zero) and isinstance(f[1], Zero): # Assuming t[1] and f[1] have the same indices here, which should be the case op = t[1] else: # Placing t[1],f[1] outside here to avoid getting arguments inside conditionals op = conditional(c[0], 1, 0)*t[1] + conditional(c[0], 0, 1)*f[1] return (o, op) def max_value(self, o, x, y): #d/dx max(f, g) = # f > g: df/dx # f < g: dg/dx op = conditional(x[0] > y[0], x[1], y[1]) return (o, op) def min_value(self, o, x, y): #d/dx min(f, g) = # f < g: df/dx # else: dg/dx op = conditional(x[0] < y[0], x[1], y[1]) return (o, op) # --- Other derivatives def derivative(self, o): error("This should never occur.") def grad(self, o): error("FIXME") # TODO: Add a ReferenceGradAD ruleset class GradAD(ForwardAD): def __init__(self, geometric_dimension, cache=None): ForwardAD.__init__(self, var_shape=(geometric_dimension,), cache=cache) def geometric_quantity(self, o): "Represent grad(g) as Grad(g)." # Collapse gradient of cellwise constant function to zero if o.is_cellwise_constant(): return self.terminal(o) return (o, Grad(o)) def spatial_coordinate(self, o): "Gradient of x w.r.t. x is Id." if not hasattr(self, '_Id'): gdim = o.geometric_dimension() self._Id = Identity(gdim) return (o, self._Id) def cell_coordinate(self, o): "Gradient of X w.r.t. x is K. But I'm not sure if we want to allow this." error("This has not been validated. Does it make sense to do this here?") K = JacobianInverse(o.domain()) return (o, K) # TODO: Implement rules for some of these types? def facet_coordinate(self, o): error("Not expecting this low level type in AD.") def jacobian(self, o): error("Not expecting this low level type in AD.") def jacobian_determinant(self, o): error("Not expecting this low level type in AD.") def jacobian_inverse(self, o): error("Not expecting this low level type in AD.") def facet_jacobian(self, o): error("Not expecting this low level type in AD.") def facet_jacobian_determinant(self, o): error("Not expecting this low level type in AD.") def facet_jacobian_inverse(self, o): error("Not expecting this low level type in AD.") def argument(self, o): "Represent grad(f) as Grad(f)." # Collapse gradient of cellwise constant function to zero # FIXME: Enable this after fixing issue#13 #if o.is_cellwise_constant(): # return zero(...) # TODO: zero annotated with argument return (o, Grad(o)) def coefficient(self, o): "Represent grad(f) as Grad(f)." # Collapse gradient of cellwise constant function to zero if o.is_cellwise_constant(): return self.terminal(o) return (o, Grad(o)) def grad(self, o): "Represent grad(grad(f)) as Grad(Grad(f))." # TODO: Not sure how to detect that gradient of f is cellwise constant. # Can we trust element degrees? #if o.is_cellwise_constant(): # return self.terminal(o) # TODO: Maybe we can ask "f.has_derivatives_of_order(n)" to check # if we should make a zero here? # 1) n = count number of Grads, get f # 2) if not f.has_derivatives(n): return zero(...) f, = o.ufl_operands ufl_assert(isinstance(f, (Grad, Terminal)), "Expecting derivatives of child to be already expanded.") return (o, Grad(o)) class VariableAD(ForwardAD): def __init__(self, var, cache=None): ForwardAD.__init__(self, var_shape=var.ufl_shape, cache=cache) ufl_assert(not var.ufl_free_indices, "Differentiation variable cannot have free indices.") self._variable = var def grad(self, o): # If we hit this type, it has already been propagated # to a coefficient, so it cannot depend on the variable. # FIXME: Assert this! return self.terminal(o) def _make_self_diff_identity(self, o): sh = o.ufl_shape res = None if sh == (): # Scalar dv/dv is scalar return IntValue(1) elif len(sh) == 1: # Vector v makes dv/dv the identity matrix return Identity(sh[0]) else: # Tensor v makes dv/dv some kind of higher rank identity tensor ind1 = () ind2 = () for d in sh: i, j = indices(2) dij = Identity(d)[i, j] if res is None: res = dij else: res *= dij ind1 += (i,) ind2 += (j,) fp = as_tensor(res, ind1 + ind2) return fp def variable(self, o): # Check cache e, l = o.ufl_operands c = self._variable_cache.get(l) if c is not None: return c if o.label() == self._variable.label(): # dv/dv = identity of rank 2*rank(v) op = self._make_self_diff_identity(o) else: # differentiate expression behind variable e2, ep = self.visit(e) op = ep if not e2 == e: o = Variable(e2, l) # return variable and derivative of its expression c = (o, op) self._variable_cache[l] = c return c class CoefficientAD(ForwardAD): "Apply AFD (Automatic Functional Differentiation) to expression." def __init__(self, coefficients, arguments, coefficient_derivatives, cache=None): ForwardAD.__init__(self, var_shape=(), cache=cache) ufl_assert(isinstance(coefficients, ExprList), "Expecting a ExprList.") ufl_assert(isinstance(arguments, ExprList), "Expecting a ExprList.") ufl_assert(isinstance(coefficient_derivatives, ExprMapping), "Expecting a ExprList.") self._v = arguments self._w = coefficients cd = coefficient_derivatives.ufl_operands self._cd = dict((cd[2*i], cd[2*i+1]) for i in range(len(cd)//2)) def coefficient(self, o): # Define dw/dw := d/ds [w + s v] = v debug("In CoefficientAD.coefficient:") debug("o = %s" % o) debug("self._w = %s" % self._w) debug("self._v = %s" % self._v) # Find o among w for (w, v) in zip(self._w, self._v): if o == w: return (w, v) # If o is not among coefficient derivatives, return do/dw=0 oprimesum = Zero(o.ufl_shape) oprimes = self._cd.get(o) if oprimes is None: if self._cd: # TODO: Make it possible to silence this message in particular? # It may be good to have for debugging... warning("Assuming d{%s}/d{%s} = 0." % (o, self._w)) else: # Make sure we have a tuple to match the self._v tuple if not isinstance(oprimes, tuple): oprimes = (oprimes,) ufl_assert(len(oprimes) == len(self._v), "Got a tuple of arguments, "+\ "expecting a matching tuple of coefficient derivatives.") # Compute do/dw_j = do/dw_h : v. # Since we may actually have a tuple of oprimes and vs in a # 'mixed' space, sum over them all to get the complete inner # product. Using indices to define a non-compound inner product. for (oprime, v) in zip(oprimes, self._v): so, oi = as_scalar(oprime) rv = len(v.ufl_shape) oi1 = oi[:-rv] oi2 = oi[-rv:] prod = so*v[oi2] if oi1: oprimesum += as_tensor(prod, oi1) else: oprimesum += prod # Example: # (f : g) -> (dfdu : v) : g + ditto # shape(f) == shape(g) == shape(dfdu : v) # shape(dfdu) == shape(f) + shape(v) return (o, oprimesum) def grad(self, g): # If we hit this type, it has already been propagated # to a coefficient (or grad of a coefficient), # FIXME: Assert this! # so we need to take the gradient of the variation or return zero. # Complications occur when dealing with derivatives w.r.t. single components... # Figure out how many gradients are around the inner terminal ngrads = 0 o = g while isinstance(o, Grad): o, = o.ufl_operands ngrads += 1 if not isinstance(o, FormArgument): error("Expecting gradient of a FormArgument, not %r" % (o,)) def apply_grads(f): if not isinstance(f, FormArgument): print(','*60) print(f) print(o) print(g) print(','*60) error("What?") for i in range(ngrads): f = Grad(f) return f # Find o among all w without any indexing, which makes this easy for (w, v) in zip(self._w, self._v): if o == w and isinstance(v, FormArgument): # Case: d/dt [w + t v] return (g, apply_grads(v)) # If o is not among coefficient derivatives, return do/dw=0 gprimesum = Zero(g.ufl_shape) def analyse_variation_argument(v): # Analyse variation argument if isinstance(v, FormArgument): # Case: d/dt [w[...] + t v] vval, vcomp = v, () elif isinstance(v, Indexed): # Case: d/dt [w + t v[...]] # Case: d/dt [w[...] + t v[...]] vval, vcomp = v.ufl_operands vcomp = tuple(vcomp) else: error("Expecting argument or component of argument.") ufl_assert(all(isinstance(k, FixedIndex) for k in vcomp), "Expecting only fixed indices in variation.") return vval, vcomp def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): # Apply gradients directly to argument vval, # and get the right indexed scalar component(s) kk = indices(ngrads) Dvkk = apply_grads(vval)[vcomp+kk] # Place scalar component(s) Dvkk into the right tensor positions if wshape: Ejj, jj = unit_indexed_tensor(wshape, wcomp) else: Ejj, jj = 1, () gprimeterm = as_tensor(Ejj*Dvkk, jj+kk) return gprimeterm # Accumulate contributions from variations in different components for (w, v) in zip(self._w, self._v): # Analyse differentiation variable coefficient if isinstance(w, FormArgument): if not w == o: continue wshape = w.ufl_shape if isinstance(v, FormArgument): # Case: d/dt [w + t v] return (g, apply_grads(v)) elif isinstance(v, ListTensor): # Case: d/dt [w + t <...,v,...>] for wcomp, vsub in unwrap_list_tensor(v): if not isinstance(vsub, Zero): vval, vcomp = analyse_variation_argument(vsub) gprimesum = gprimesum + compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp) else: ufl_assert(wshape == (), "Expecting scalar coefficient in this branch.") # Case: d/dt [w + t v[...]] wval, wcomp = w, () vval, vcomp = analyse_variation_argument(v) gprimesum = gprimesum + compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp) elif isinstance(w, Indexed): # This path is tested in unit tests, but not actually used? # Case: d/dt [w[...] + t v[...]] # Case: d/dt [w[...] + t v] wval, wcomp = w.ufl_operands if not wval == o: continue assert isinstance(wval, FormArgument) ufl_assert(all(isinstance(k, FixedIndex) for k in wcomp), "Expecting only fixed indices in differentiation variable.") wshape = wval.ufl_shape vval, vcomp = analyse_variation_argument(v) gprimesum = gprimesum + compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp) else: error("Expecting coefficient or component of coefficient.") # FIXME: Handle other coefficient derivatives: oprimes = self._cd.get(o) if 0: oprimes = self._cd.get(o) if oprimes is None: if self._cd: # TODO: Make it possible to silence this message in particular? # It may be good to have for debugging... warning("Assuming d{%s}/d{%s} = 0." % (o, self._w)) else: # Make sure we have a tuple to match the self._v tuple if not isinstance(oprimes, tuple): oprimes = (oprimes,) ufl_assert(len(oprimes) == len(self._v), "Got a tuple of arguments, "+\ "expecting a matching tuple of coefficient derivatives.") # Compute dg/dw_j = dg/dw_h : v. # Since we may actually have a tuple of oprimes and vs in a # 'mixed' space, sum over them all to get the complete inner # product. Using indices to define a non-compound inner product. for (oprime, v) in zip(oprimes, self._v): error("FIXME: Figure out how to do this with ngrads") so, oi = as_scalar(oprime) rv = len(v.ufl_shape) oi1 = oi[:-rv] oi2 = oi[-rv:] prod = so*v[oi2] if oi1: gprimesum += as_tensor(prod, oi1) else: gprimesum += prod return (g, gprimesum) def variable(self, o): # Check variable cache to reuse previously transformed variable if possible e, l = o.ufl_operands c = self._variable_cache.get(l) if c is not None: return c # Visit the expression our variable represents e2, ep = self.visit(e) op = ep # If the expression is not the same, reconstruct Variable object o = self.reuse_if_possible(o, e2, l) # Recreate Variable (with same label) and cache it c = (o, op) self._variable_cache[l] = c return c def compute_grad_forward_ad(f, geometric_dimension): alg = GradAD(geometric_dimension) e, ediff = alg.visit(f) return ediff def compute_variable_forward_ad(f, v): alg = VariableAD(v) e, ediff = alg.visit(f) return ediff def compute_coefficient_forward_ad(f, w, v, cd): alg = CoefficientAD(w, v, cd) e, ediff = alg.visit(f) return ediff def apply_nested_forward_ad(expr): if expr._ufl_is_terminal_: # A terminal needs no differentiation applied return expr elif not isinstance(expr, Derivative): # Apply AD recursively to children preops = expr.ufl_operands postops = tuple(apply_nested_forward_ad(o) for o in preops) # Reconstruct if necessary need_reconstruct = not (preops == postops) # FIXME: Is this efficient? O(n)? if need_reconstruct: expr = expr.reconstruct(*postops) return expr elif isinstance(expr, Grad): # Apply AD recursively to children f, = expr.ufl_operands f = apply_nested_forward_ad(f) # Apply Grad-specialized AD to expanded child gdim = expr.ufl_shape[-1] return compute_grad_forward_ad(f, gdim) elif isinstance(expr, VariableDerivative): # Apply AD recursively to children f, v = expr.ufl_operands f = apply_nested_forward_ad(f) # Apply Variable-specialized AD to expanded child return compute_variable_forward_ad(f, v) elif isinstance(expr, CoefficientDerivative): # Apply AD recursively to children f, w, v, cd = expr.ufl_operands f = apply_nested_forward_ad(f) # Apply Coefficient-specialized AD to expanded child return compute_coefficient_forward_ad(f, w, v, cd) else: error("Unknown type.") # TODO: We could expand only the compound objects that have no rule # before differentiating, to allow the AD to work on a coarser graph class UnusedADRules(object): def _variable_derivative(self, o, f, v): f, fp = f v, vp = v ufl_assert(isinstance(vp, Zero), "TODO: What happens if vp != 0, i.e. v depends the differentiation variable?") # Are there any issues with indices here? Not sure, think through it... oprime = o.reconstruct(fp, v) return (o, oprime) # --- Tensor algebra (compound types) def outer(self, o, a, b): a, ap = a b, bp = b return (o, outer(ap, b) + outer(a, bp)) # FIXME: Not valid for derivatives w.r.t. nonscalar variables! def inner(self, o, a, b): a, ap = a b, bp = b # FIXME: Rewrite with index notation (if necessary, depends on shapes) # NB! Using b : ap because derivative axis should be # last, in case of nonscalar differentiation variable! return (o, inner(b, ap) + inner(a, bp)) # FIXME: Not correct, inner requires equal shapes! def dot(self, o, a, b): a, ap = a b, bp = b # FIXME: Rewrite with index notation (if necessary, depends on shapes) # NB! Using b . ap because derivative axis should be # last, in case of nonscalar differentiation variable! return (o, dot(b, ap) + dot(a, bp)) def commute(self, o, a): "This should work for all single argument operators that commute with d/dw with w scalar." aprime = a[1] return (o, o.reconstruct(aprime)) # FIXME: Not true for derivatives w.r.t. nonscalar variables... transposed = commute trace = commute deviatoric = commute # --- Compound differential operators, probably do not want... # FIXME: nabla_div, nabla_grad div = commute curl = commute def grad(self, o, a): a, aprime = a if aprime.domains(): # TODO: Assuming this is equivalent to 'is_constant', which may not be the case... oprime = o.reconstruct(aprime) else: oprime = self._make_zero_diff(o) return (o, oprime) class UnimplementedADRules(object): def cross(self, o, a, b): error("Derivative of cross product not implemented, apply expand_compounds before AD.") u, up = a v, vp = b #oprime = ... return (o, oprime) def determinant(self, o, a): """FIXME: Some possible rules: d detA / dv = detA * tr(inv(A) * dA/dv) or d detA / d row0 = cross(row1, row2) d detA / d row1 = cross(row2, row0) d detA / d row2 = cross(row0, row1) i.e. d detA / d A = [cross(row1, row2), cross(row2, row0), cross(row0, row1)] # or transposed or something """ error("Derivative of determinant not implemented, apply expand_compounds before AD.") A, Ap = a #oprime = ... return (o, oprime) def cofactor(self, o, a): error("Derivative of cofactor not implemented, apply expand_compounds before AD.") A, Ap = a #cofacA_prime = detA_prime*Ainv + detA*Ainv_prime #oprime = ... return (o, oprime) def inverse(self, o, a): """Derivation: 0 = d/dx [Ainv*A] = Ainv' * A + Ainv * A' Ainv' * A = - Ainv * A' Ainv' = - Ainv * A' * Ainv K J = I d/dv[r] (K[i,j] J[j,k]) = d/dv[r] K[i,j] J[j,k] + K[i,j] d/dv[r] J[j,k] = 0 d/dv[r] K[i,j] J[j,k] = -K[i,j] d/dv[r] J[j,k] d/dv[r] K[i,j] J[j,k] K[k,s] = -K[i,j] d/dv[r] J[j,k] K[k,s] d/dv[r] K[i,j] I[j,s] = -K[i,j] (d/dv[r] J[j,k]) K[k,s] d/dv[r] K[i,s] = -K[i,j] (d/dv[r] J[j,k]) K[k,s] """ A, Ap = a return (o, -o*Ap*o) # FIXME: Need reshaping move derivative axis to the end of this expression ufl-1.6.0/ufl/algorithms/map_integrands.py000066400000000000000000000044621255567402100206250ustar00rootroot00000000000000"""Basic algorithms for applying functions to subexpressions.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # NOTE: Placing this under algorithms/ because I want corealg/ to stay clean # as part of a careful refactoring process, and this file depends on ufl.form # which drags in a lot of stuff. from ufl.log import error from ufl.assertions import ufl_assert from ufl.core.expr import Expr from ufl.corealg.map_dag import map_expr_dag from ufl.integral import Integral from ufl.form import Form from ufl.constantvalue import Zero def map_integrands(function, form, only_integral_type=None): """Apply transform(expression) to each integrand expression in form, or to form if it is an Expr. """ if isinstance(form, Form): mapped_integrals = [map_integrands(function, itg, only_integral_type) for itg in form.integrals()] nonzero_integrals = [itg for itg in mapped_integrals if not isinstance(itg.integrand(), Zero)] return Form(nonzero_integrals) elif isinstance(form, Integral): itg = form if (only_integral_type is None) or (itg.integral_type() in only_integral_type): return itg.reconstruct(function(itg.integrand())) else: return itg elif isinstance(form, Expr): #ufl_assert(only_integral_type is None, "Restricting integral type is only valid with Form and Integral.") integrand = form return function(integrand) else: error("Expecting Form, Integral or Expr.") def map_integrand_dags(function, form, only_integral_type=None, compress=True): return map_integrands(lambda expr: map_expr_dag(function, expr, compress), form, only_integral_type) ufl-1.6.0/ufl/algorithms/multifunction.py000066400000000000000000000002061255567402100205220ustar00rootroot00000000000000# Moved here to be usable in ufl.* files without depending on ufl.algorithms.*... from ufl.corealg.multifunction import MultiFunction ufl-1.6.0/ufl/algorithms/pdiffs.py000066400000000000000000000172461255567402100171110ustar00rootroot00000000000000"""This module defines partial differentiation rules for all relevant operands for use with reverse mode AD.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Kristian B. Oelgaard, 2011 from math import pi from ufl.log import error from ufl.assertions import ufl_assert from ufl.classes import Zero, IntValue from ufl.operators import cos, sin, cosh, sinh, exp, ln, sqrt, conditional, sign from ufl.tensors import unit_vectors, ListTensor from ufl.algorithms.multifunction import MultiFunction class PartialDerivativeComputer(MultiFunction): """NB! The main reason for keeping this out of the Expr hierarchy is to avoid user mistakes in the form of mixups with total derivatives, and to allow both reverse and forward mode AD.""" #def __init__(self, spatial_dim): #self._spatial_dim = spatial_dim def __init__(self): MultiFunction.__init__(self) # TODO: Make sure we have implemented partial derivatives of all operators. # At least non-compound ones should be covered, but compound ones # may be a good idea in future versions. def expr(self, o): error("No partial derivative defined for %s" % type(o)) # --- Basic algebra operators def index_sum(self, f): "d/dx sum_j x = TODO" TODO def sum(self, f): "d/dx_i sum_j x_j = 1" _1 = IntValue(1) # TODO: Handle non-scalars return (_1,)*len(f.ufl_operands) def product(self, f): a, b = f.ufl_operands # TODO: Assuming binary operator for now da = b # TODO: Is this right even for non-scalar b? db = a return (da, db) def division(self, f): """f = x/y d/dx x/y = 1/y d/dy x/y = -x/y**2 = -f/y""" x, y = f.ufl_operands # Nonscalar x not supported ufl_assert(x.ufl_shape == (), "Expecting scalars in division.") ufl_assert(y.ufl_shape == (), "Expecting scalars in division.") d = 1 / y return (d, -f*d) def power(self, f): """f = x**y d/dx x**y = y*x**(y-1) = y*f/x d/dy x**y = ln(x)*x**y = ln(x)*f""" x, y = f.ufl_operands dx = y*f/x dy = ln(x)*f return (dx, dy) def abs(self, f): r".. math:: \\frac{d}{dx} f(x) = \\frac{d}{dx} abs(x) = sign(x)" x, = f.ufl_operands dx = sign(x) return (dx,) # --- Mathfunctions def sqrt(self, f): "d/dx sqrt(x) = 1 / (2*sqrt(x))" return (0.5/f,) def exp(self, f): "d/dx exp(x) = exp(x)" return (f,) def ln(self, f): "d/dx ln x = 1 / x" x, = f.ufl_operands return (1/x,) def cos(self, f): "d/dx cos x = -sin(x)" x, = f.ufl_operands return (-sin(x),) def sin(self, f): "d/dx sin x = cos(x)" x, = f.ufl_operands return (cos(x),) def tan(self, f): "d/dx tan x = (sec(x))^2 = 2/(cos(2x) + 1)" x, = f.ufl_operands return (2.0/(cos(2.0*x) + 1.0),) def cosh(self, f): "d/dx cosh x = sinh(x)" x, = f.ufl_operands return (sinh(x),) def sinh(self, f): "d/dx sinh x = cosh(x)" x, = f.ufl_operands return (cosh(x),) def tanh(self, f): "d/dx tanh x = (sech(x))^2 = (2 cosh(x) / (cosh(2x) + 1))^2" x, = f.ufl_operands return (((2.0*cosh(x))/(cosh(2.0*x) + 1.0))**2,) def acos(self, f): r".. math:: \\frac{d}{dx} f(x) = \frac{d}{dx} \arccos(x) = \frac{-1}{\sqrt{1 - x^2}}" x, = f.ufl_operands return (-1.0/sqrt(1.0 - x**2),) def asin(self, f): "d/dx asin x = 1/sqrt(1 - x^2)" x, = f.ufl_operands return (1.0/sqrt(1.0 - x**2),) def atan(self, f): "d/dx atan x = 1/(1 + x^2)" x, = f.ufl_operands return (1.0/(1.0 + x**2),) def atan_2(self, f): """ f = atan2(x,y) d/dx atan2(x,y) = y / (x**2 + y**2 ) d/dy atan2(x,y) = -x / (x**2 + y**2) """ x, y = f.ufl_operands d = x**2 + y**2 return (y/d, -x/d) def erf(self, f): "d/dx erf x = 2/sqrt(pi)*exp(-x^2)" x, = f.ufl_operands return (2.0/sqrt(pi)*exp(-x**2),) def bessel_function(self, nu, x): return NotImplemented # --- Shape and indexing manipulators def indexed(self, f): # TODO: Is this right? Fix for non-scalars too. "d/dx x_i = (1)_i = 1" s = f.ufl_shape ufl_assert(s == (), "TODO: Assuming a scalar expression.") _1 = IntValue(1) # TODO: Non-scalars return (_1, None) def list_tensor(self, f): # TODO: Is this right? Fix for higher order tensors too. "d/dx_i [x_0, ..., x_n-1] = e_i (unit vector)" ops = f.ufl_operands n = len(ops) s = ops[0].ufl_shape ufl_assert(s == (), "TODO: Assuming a vector, i.e. scalar operands.") return unit_vectors(n) # TODO: Non-scalars def component_tensor(self, f): x, i = f.ufl_operands s = f.ufl_shape ufl_assert(len(s) == 1, "TODO: Assuming a vector, i.e. scalar operands.") n, = s d = ListTensor([1]*n) # TODO: Non-scalars return (d, None) # --- Restrictions def positive_restricted(self, f): _1 = IntValue(1) return (_1,) # or _1('+')? TODO: is this right? # Note that _1('+') would become 0 with the current implementation def negative_restricted(self, f): _1 = IntValue(1) return (_1,) # or _1('-')? TODO: is this right? def cell_avg(self, f): error("Not sure how to implement partial derivative of this operator at all actually.") def facet_avg(self, f): error("Not sure how to implement partial derivative of this operator at all actually.") # --- Conditionals def condition(self, f): return (None, None) def conditional(self, f): # TODO: Is this right? What about non-scalars? c, a, b = f.ufl_operands s = f.ufl_shape ufl_assert(s == (), "TODO: Assuming scalar valued expressions.") _0 = Zero() _1 = IntValue(1) da = conditional(c, _1, _0) db = conditional(c, _0, _1) return (None, da, db) # --- Derivatives def spatial_derivative(self, f): error("Partial derivative of spatial_derivative not implemented, "\ "when is this called? apply_ad should make sure it isn't called.") x, i = f.ufl_operands return (None, None) def variable_derivative(self, f): error("Partial derivative of variable_derivative not implemented, "\ "when is this called? apply_ad should make sure it isn't called.") x, v = f.ufl_operands return (None, None) def coefficient_derivative(self, f): error("Partial derivative of coefficient_derivative not implemented, "\ "when is this called? apply_ad should make sure it isn't called.") a, w, v = f.ufl_operands return (None, None, None) # Example usage: def pdiffs(exprs): pd = PartialDerivativeComputer() return [pd(e) for e in exprs] ufl-1.6.0/ufl/algorithms/predicates.py000066400000000000000000000042441255567402100177530ustar00rootroot00000000000000"""Functions to check properties of forms and integrals.""" # Copyright (C) 2008-2014 Martin Sandve Alnes and Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from ufl.log import warning, debug from ufl.algorithms.traversal import iter_expressions from ufl.algorithms.argument_dependencies import extract_argument_dependencies, NotMultiLinearException #--- Utilities for checking properties of forms --- def is_multilinear(form): "Check if form is multilinear in arguments." # An attempt at implementing is_multilinear using extract_argument_dependencies. # TODO: This has some false negatives for "multiple configurations". (Does it still? Needs testing!) # TODO: FFC probably needs a variant of this which checks for some sorts of linearity # in Coefficients as well, this should be a fairly simple extension of the current algorithm. try: for e in iter_expressions(form): deps = extract_argument_dependencies(e) nargs = [len(d) for d in deps] if len(nargs) == 0: debug("This form is a functional.") if len(nargs) == 1: debug("This form is linear in %d arguments." % nargs[0]) if len(nargs) > 1: warning("This form has more than one argument "\ "'configuration', it has terms that are linear in %s "\ "arguments respectively." % str(nargs)) except NotMultiLinearException as msg: warning("Form is not multilinear, the offending term is: %s" % msg) return False return True ufl-1.6.0/ufl/algorithms/preprocess_expression.py000066400000000000000000000000001255567402100222560ustar00rootroot00000000000000ufl-1.6.0/ufl/algorithms/propagate_restrictions.py000066400000000000000000000046001255567402100224160ustar00rootroot00000000000000"Algorithms related to restrictions." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from ufl.core.expr import Expr from ufl.classes import Measure from ufl.assertions import ufl_assert from ufl.algorithms.transformer import Transformer class RestrictionChecker(Transformer): def __init__(self, require_restriction): Transformer.__init__(self) self.current_restriction = None self.require_restriction = require_restriction def expr(self, o): pass def restricted(self, o): ufl_assert(self.current_restriction is None, "Not expecting twice restricted expression.") self.current_restriction = o._side e, = o.ufl_operands self.visit(e) self.current_restriction = None def facet_normal(self, o): if self.require_restriction: ufl_assert(self.current_restriction is not None, "Facet normal must be restricted in interior facet integrals.") else: ufl_assert(self.current_restriction is None, "Restrictions are only allowed for interior facet integrals.") def form_argument(self, o): if self.require_restriction: ufl_assert(self.current_restriction is not None, "Form argument must be restricted in interior facet integrals.") else: ufl_assert(self.current_restriction is None, "Restrictions are only allowed for interior facet integrals.") def check_restrictions(expression, require_restriction): ufl_assert(isinstance(expression, Expr), "Expecting Expr instance.") return RestrictionChecker(require_restriction).visit(expression) def propagate_restrictions(expression): from ufl.algorithms.apply_restrictions import apply_restrictions return apply_restrictions(expression) ufl-1.6.0/ufl/algorithms/renumbering.py000066400000000000000000000053321255567402100201440ustar00rootroot00000000000000"Algorithms for renumbering of counted objects, currently variables and indices." # Copyright (C) 2008-2014 Martin Sandve Alnes and Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from six.moves import zip from ufl.common import Stack, StackDict from ufl.log import error from ufl.core.expr import Expr from ufl.core.multiindex import Index, FixedIndex, MultiIndex from ufl.variable import Label, Variable from ufl.algorithms.transformer import ReuseTransformer, apply_transformer from ufl.assertions import ufl_assert class VariableRenumberingTransformer(ReuseTransformer): def __init__(self): ReuseTransformer.__init__(self) self.variable_map = {} def variable(self, o): e, l = o.ufl_operands v = self.variable_map.get(l) if v is None: e = self.visit(e) l2 = Label(len(self.variable_map)) v = Variable(e, l2) self.variable_map[l] = v return v class IndexRenumberingTransformer(VariableRenumberingTransformer): def __init__(self): VariableRenumberingTransformer.__init__(self) self.index_map = {} def zero(self, o): new_indices = tuple(self.index(Index(count=i) for i in o.ufl_free_indices)) return o.reconstruct(new_indices) def index(self, o): if isinstance(o, FixedIndex): return o else: c = o._count i = self.index_map.get(c) if i is None: i = Index(count=len(self.index_map)) self.index_map[c] = i return i def multi_index(self, o): return MultiIndex(tuple(self.index(i) for i in o.indices())) def renumber_indices(expr): if isinstance(expr, Expr): num_free_indices = len(expr.ufl_free_indices) #error("Not expecting any free indices left in expression.") result = apply_transformer(expr, IndexRenumberingTransformer()) if isinstance(expr, Expr): ufl_assert(num_free_indices == len(result.ufl_free_indices), "The number of free indices left in expression should be invariant w.r.t. renumbering.") return result ufl-1.6.0/ufl/algorithms/replace.py000066400000000000000000000046571255567402100172530ustar00rootroot00000000000000"""Algorithm for replacing terminals in an expression.""" # Copyright (C) 2008-2014 Martin Sandve Alnes and Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2009-2010 from six import iteritems, iterkeys from ufl.log import error from ufl.assertions import ufl_assert from ufl.classes import CoefficientDerivative from ufl.constantvalue import as_ufl from ufl.corealg.multifunction import MultiFunction from ufl.algorithms.map_integrands import map_integrand_dags from ufl.algorithms.analysis import has_exact_type class Replacer(MultiFunction): def __init__(self, mapping): MultiFunction.__init__(self) self._mapping = mapping ufl_assert(all(k._ufl_is_terminal_ for k in iterkeys(mapping)), "This implementation can only replace Terminal objects.") ufl_assert(all(k.ufl_shape == v.ufl_shape for k, v in iteritems(mapping)), "Replacement expressions must have the same shape as what they replace.") expr = MultiFunction.reuse_if_untouched def terminal(self, o): e = self._mapping.get(o) if e is None: return o else: return e def coefficient_derivative(self, o): error("Derivatives should be applied before executing replace.") def replace(e, mapping): """Replace terminal objects in expression. @param e: An Expr or Form. @param mapping: A dict with from:to replacements to perform. """ mapping2 = dict((k, as_ufl(v)) for (k, v) in iteritems(mapping)) # Workaround for problem with delayed derivative evaluation if has_exact_type(e, CoefficientDerivative): # Hack to avoid circular dependencies from ufl.algorithms.ad import expand_derivatives e = expand_derivatives(e) return map_integrand_dags(Replacer(mapping2), e) ufl-1.6.0/ufl/algorithms/signature.py000066400000000000000000000173261255567402100176360ustar00rootroot00000000000000"""Signature computation for forms.""" # Copyright (C) 2012-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . import hashlib from ufl.classes import (Terminal, Label, Index, MultiIndex, Coefficient, Argument, FormArgument, GeometricQuantity, ConstantValue, ExprList, ExprMapping) from ufl.log import error from ufl.corealg.traversal import traverse_unique_terminals from ufl.common import pre_traversal, sorted_by_count from ufl.geometry import join_domains from ufl.algorithms.domain_analysis import canonicalize_metadata def compute_multiindex_hashdata(expr, index_numbering): data = [] for i in expr: if isinstance(i, Index): j = index_numbering.get(i) if j is None: # Use negative ints for Index j = -(len(index_numbering)+1) index_numbering[i] = j data.append(j) else: # Use nonnegative ints for FixedIndex data.append(int(i)) return tuple(data) def compute_terminal_hashdata(expressions, renumbering): if not isinstance(expressions, list): expressions = [expressions] assert renumbering is not None # Extract a unique numbering of free indices, # as well as form arguments, and just take # repr of the rest of the terminals while # we're iterating over them terminal_hashdata = {} labels = {} index_numbering = {} for expression in expressions: for expr in traverse_unique_terminals(expression): if isinstance(expr, MultiIndex): # Indices need a canonical numbering for a stable signature, thus this algorithm data = compute_multiindex_hashdata(expr, index_numbering) elif isinstance(expr, ConstantValue): data = expr.signature_data(renumbering) elif isinstance(expr, Coefficient): data = expr.signature_data(renumbering) elif isinstance(expr, Argument): data = expr.signature_data(renumbering) elif isinstance(expr, GeometricQuantity): data = expr.signature_data(renumbering) elif isinstance(expr, Label): # Numbering labels as we visit them # TODO: Include in renumbering data = labels.get(expr) if data is None: data = "L%d" % len(labels) labels[expr] = data elif isinstance(expr, ExprList): # Not really a terminal but can have 0 operands... data = "[]" elif isinstance(expr, ExprMapping): # Not really a terminal but can have 0 operands... data = "{}" else: error("Unknown terminal type %s" % type(expr)) terminal_hashdata[expr] = data return terminal_hashdata def compute_expression_hashdata(expression, terminal_hashdata): # The hashdata computed here can be interpreted as # prefix operator notation, i.e. we store the equivalent # of '+ * a b * c d' for the expression (a*b)+(c*d) expression_hashdata = [] for expr in pre_traversal(expression): if expr._ufl_is_terminal_: data = terminal_hashdata[expr] else: data = expr._ufl_typecode_ # TODO: Use expr.signature_data()? More extensible, but more overhead. expression_hashdata.append(data) # Oneliner: TODO: Benchmark, maybe use a generator? #expression_hashdata = [(terminal_hashdata[expr] if expr._ufl_is_terminal_ else expr._ufl_typecode_) # for expr in pre_traversal(expression)] return expression_hashdata def build_domain_numbering(domains): # Create canonical numbering of domains for stable signature # (ordering defined by __lt__ implementation in Domain class) assert None not in domains # Collect domain keys items = [] for i, domain in enumerate(domains): key = (domain.cell(), domain.label()) items.append((key, i)) # Build domain numbering, not allowing repeated keys domain_numbering = {} for key, i in items: if key in domain_numbering: error("Domain key %s occured twice!" % (key,)) domain_numbering[key] = i # Build domain numbering extension for None-labeled domains, not allowing ambiguity from collections import defaultdict domain_numbering2 = defaultdict(list) for key, i in items: cell, label = key key2 = (cell, None) domain_numbering2[key2].append(domain_numbering[key]) # Add None-based key only where unambiguous for key, i in items: cell, label = key key2 = (cell, None) if len(domain_numbering2[key2]) == 1: domain_numbering[key2] = domain_numbering[key] else: # Two domains occur with same properties but different label, # so we cannot decide which one to map None-labeled Domains to. pass return domain_numbering def compute_expression_signature(expr, renumbering): # FIXME: Fix callers # FIXME: Rewrite in terms of compute_form_signature? # Build hashdata for all terminals first terminal_hashdata = compute_terminal_hashdata([expr], renumbering) # Build hashdata for full expression expression_hashdata = compute_expression_hashdata(expr, terminal_hashdata) # Pass it through a seriously overkill hashing algorithm :) TODO: How fast is this? Reduce? return hashlib.sha512(str(expression_hashdata).encode('utf-8')).hexdigest() def compute_form_signature(form, renumbering): # FIXME: Fix callers # Extract integrands integrals = form.integrals() integrands = [integral.integrand() for integral in integrals] # Build hashdata for all terminals first, with on-the-fly # replacement of functions and index labels. terminal_hashdata = compute_terminal_hashdata(integrands, renumbering) # Build hashdata for each integral hashdata = [] for integral in integrals: # Compute hash data for expression, this is the expensive part integrand_hashdata = compute_expression_hashdata(integral.integrand(), terminal_hashdata) domain_hashdata = integral.domain().signature_data(renumbering) # Collect all data about integral that should be reflected in signature, # including compiler data but not domain data, because compiler data # affects the way the integral is compiled while domain data is only # carried for convenience in the problem solving environment. integral_hashdata = ( integrand_hashdata, domain_hashdata, integral.integral_type(), integral.subdomain_id(), canonicalize_metadata(integral.metadata()), ) hashdata.append(integral_hashdata) # Pass hashdata through a seriously overkill hashing algorithm :) TODO: How fast is this? Reduce? return hashlib.sha512(str(hashdata).encode('utf-8')).hexdigest() ufl-1.6.0/ufl/algorithms/transformer.py000066400000000000000000000203021255567402100201630ustar00rootroot00000000000000"""This module defines the Transformer base class and some basic specializations to further base other algorithms upon, as well as some utilities for easier application of such algorithms.""" # Copyright (C) 2008-2014 Martin Sandve Alnes and Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2009-2010 from inspect import getargspec from ufl.log import error, debug from ufl.assertions import ufl_assert from ufl.classes import Expr, Terminal, Variable, Zero, all_ufl_classes from ufl.algorithms.map_integrands import map_integrands from ufl.integral import Integral from ufl.form import Form def is_post_handler(function): "Is this a handler that expects transformed children as input?" insp = getargspec(function) num_args = len(insp[0]) + int(insp[1] is not None) visit_children_first = num_args > 2 return visit_children_first class Transformer(object): """Base class for a visitor-like algorithm design pattern used to transform expression trees from one representation to another.""" _handlers_cache = {} def __init__(self, variable_cache=None): if variable_cache is None: variable_cache = {} self._variable_cache = variable_cache # Analyse class properties and cache handler data the # first time this is run for a particular class cache_data = Transformer._handlers_cache.get(type(self)) if not cache_data: cache_data = [None]*len(all_ufl_classes) # For all UFL classes for classobject in all_ufl_classes: # Iterate over the inheritance chain # (NB! This assumes that all UFL classes inherits a single # Expr subclass and that this is the first superclass!) for c in classobject.mro(): # Register classobject with handler for the first encountered superclass name = c._ufl_handler_name_ function = getattr(self, name, None) if function: cache_data[classobject._ufl_typecode_] = name, is_post_handler(function) break Transformer._handlers_cache[type(self)] = cache_data # Build handler list for this particular class (get functions bound to self) self._handlers = [(getattr(self, name), post) for (name, post) in cache_data] # Keep a stack of objects visit is called on, to ease backtracking self._visit_stack = [] def print_visit_stack(self): print("/"*80) print("Visit stack in Transformer:") def sstr(s): ss = str(type(s)) + " ; " n = 160 - len(ss) return ss + str(s)[:n] print("\n".join(sstr(s) for s in self._visit_stack)) print("\\"*80) def visit(self, o): #debug("Visiting object of type %s." % type(o).__name__) # Update stack self._visit_stack.append(o) # Get handler for the UFL class of o (type(o) may be an external subclass of the actual UFL class) h, visit_children_first = self._handlers[o._ufl_typecode_] #if not h: # # Failed to find a handler! Should never happen, but will happen if a non-Expr object is visited. # error("Can't handle objects of type %s" % str(type(o))) # Is this a handler that expects transformed children as input? if visit_children_first: # Yes, visit all children first and then call h. r = h(o, *[self.visit(op) for op in o.ufl_operands]) else: # No, this is a handler that handles its own children # (arguments self and o, where self is already bound) r = h(o) # Update stack and return self._visit_stack.pop() return r def undefined(self, o): "Trigger error." error("No handler defined for %s." % o._ufl_class_.__name__) def reuse(self, o): "Always reuse Expr (ignore children)" return o def reuse_if_untouched(self, o, *ops): """Reuse object if operands are the same objects. Use in your own subclass by setting e.g. expr = MultiFunction.reuse_if_untouched as a default rule. """ if all(a is b for a, b in zip(o.ufl_operands, ops)): return o else: return o.reconstruct(*ops) # It's just so slow to compare all operands, avoiding it now reuse_if_possible = reuse_if_untouched def always_reconstruct(self, o, *operands): "Always reconstruct expr." return o.reconstruct(*operands) # Set default behaviour for any Expr expr = undefined # Set default behaviour for any Terminal terminal = reuse def reuse_variable(self, o): # Check variable cache to reuse previously transformed variable if possible e, l = o.ufl_operands v = self._variable_cache.get(l) if v is not None: return v # Visit the expression our variable represents e2 = self.visit(e) # If the expression is the same, reuse Variable object if e == e2: v = o else: # Recreate Variable (with same label) v = Variable(e2, l) # Cache variable self._variable_cache[l] = v return v def reconstruct_variable(self, o): # Check variable cache to reuse previously transformed variable if possible e, l = o.ufl_operands v = self._variable_cache.get(l) if v is not None: return v # Visit the expression our variable represents e2 = self.visit(e) # Always reconstruct Variable (with same label) v = Variable(e2, l) self._variable_cache[l] = v return v class ReuseTransformer(Transformer): def __init__(self, variable_cache=None): Transformer.__init__(self, variable_cache) # Set default behaviour for any Expr expr = Transformer.reuse_if_untouched # Set default behaviour for any Terminal terminal = Transformer.reuse # Set default behaviour for Variable variable = Transformer.reuse_variable class CopyTransformer(Transformer): def __init__(self, variable_cache=None): Transformer.__init__(self, variable_cache) # Set default behaviour for any Expr expr = Transformer.always_reconstruct # Set default behaviour for any Terminal terminal = Transformer.reuse # Set default behaviour for Variable variable = Transformer.reconstruct_variable class VariableStripper(ReuseTransformer): def __init__(self): ReuseTransformer.__init__(self) def variable(self, o): return self.visit(o.ufl_operands[0]) def apply_transformer(e, transformer, integral_type=None): """Apply transformer.visit(expression) to each integrand expression in form, or to form if it is an Expr.""" return map_integrands(lambda expr: transformer.visit(expr), e, integral_type) def ufl2ufl(e): """Convert an UFL expression to a new UFL expression, with no changes. This is used for testing that objects in the expression behave as expected.""" return apply_transformer(e, ReuseTransformer()) def ufl2uflcopy(e): """Convert an UFL expression to a new UFL expression. All nonterminal object instances are replaced with identical copies, while terminal objects are kept. This is used for testing that objects in the expression behave as expected.""" return apply_transformer(e, CopyTransformer()) def strip_variables(e): "Replace all Variable instances with the expression they represent." return apply_transformer(e, VariableStripper()) ufl-1.6.0/ufl/algorithms/traversal.py000066400000000000000000000032641255567402100176340ustar00rootroot00000000000000"""This module contains algorithms for traversing expression trees in different ways.""" # Copyright (C) 2008-2014 Martin Sandve Alnes and Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2008 from ufl.log import error from ufl.core.expr import Expr from ufl.integral import Integral from ufl.form import Form #--- Traversal utilities --- def iter_expressions(a): """Utility function to handle Form, Integral and any Expr the same way when inspecting expressions. Returns an iterable over Expr instances: - a is an Expr: (a,) - a is an Integral: the integrand expression of a - a is a Form: all integrand expressions of all integrals """ if isinstance(a, Form): return (itg.integrand() for itg in a.integrals()) elif isinstance(a, Integral): return (a.integrand(),) elif isinstance(a, Expr): return (a,) error("Not an UFL type: %s" % str(type(a))) # The rest is moved here: #from ufl.corealg.traversal import pre_traversal, post_traversal, traverse_terminals, traverse_unique_terminals ufl-1.6.0/ufl/argument.py000066400000000000000000000152221255567402100152770ustar00rootroot00000000000000"""This module defines the class Argument and a number of related classes (functions), including TestFunction and TrialFunction.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2008-2009. from ufl.log import deprecate from ufl.assertions import ufl_assert from ufl.core.terminal import Terminal, FormArgument from ufl.split_functions import split from ufl.finiteelement import FiniteElementBase from ufl.core.ufl_type import ufl_type # --- Class representing an argument (basis function) in a form --- @ufl_type() class Argument(FormArgument): """UFL value: Representation of an argument to a form.""" __slots__ = ("_element", "_number", "_part", "_repr") def __init__(self, element, number, part=None): FormArgument.__init__(self) ufl_assert(isinstance(element, FiniteElementBase), "Expecting an element, not %s" % (element,)) ufl_assert(isinstance(number, int), "Expecting an int for number, not %s" % (number,)) ufl_assert(part is None or isinstance(part, int), "Expecting None or an int for part, not %s" % (part,)) self._element = element self._number = number self._part = part self._repr = "Argument(%r, %r, %r)" % (self._element, self._number, self._part) def reconstruct(self, element=None, number=None, part=None): if element is None or (element == self._element): # TODO: Is the == here a workaround for some bug? element = self._element if number is None: number = self._number if part is None: part = self._part if number == self._number and part == self._part and element is self._element: return self ufl_assert(element.value_shape() == self._element.value_shape(), "Cannot reconstruct an Argument with a different value shape.") return Argument(element, number, part) def element(self): return self._element def number(self): return self._number def part(self): return self._part def count(self): deprecate("The count of an Argument has been replaced with number() and part().") ufl_assert(self.part() is None, "Deprecation transition for count() will not work with parts.") return self.number() # I think this will work ok in most cases during the deprecation transition @property def ufl_shape(self): return self._element.value_shape() def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." # TODO: Should in principle do like with Coefficient, # but that may currently simplify away some arguments # we want to keep, or? See issue#13. # When we can annotate zero with arguments, we can change this. return False def domains(self): "Return tuple of domains related to this terminal object." return self._element.domains() def signature_data(self, domain_numbering): "Signature data for form arguments depend on the global numbering of the form arguments and domains." s = self._element.signature_data(domain_numbering=domain_numbering) return ("Argument", self._number, self._part) + s def signature_data(self, renumbering): "Signature data for form arguments depend on the global numbering of the form arguments and domains." edata = self.element().signature_data(renumbering) d = self.domain() ddata = None if d is None else d.signature_data(renumbering) return ("Coefficient", self._number, self._part, edata, ddata) def __str__(self): number = str(self._number) if len(number) == 1: s = "v_%s" % number else: s = "v_{%s}" % number if self._part is not None: part = str(self._part) if len(part) == 1: s = "%s^%s" % (s, part) else: s = "%s^{%s}" % (s, part) return s def __repr__(self): return self._repr def __eq__(self, other): """Deliberately comparing exact type and not using isinstance here, meaning eventual subclasses must reimplement this function to work correctly, and instances of this class will compare not equal to instances of eventual subclasses. The overloading allows subclasses to distinguish between test and trial functions with a different non-ufl payload, such as dolfin FunctionSpace with different mesh. This is necessary because arguments with the same element and argument number are always equal from a pure ufl point of view, e.g. TestFunction(V1) == TestFunction(V2) if V1 and V2 are the same ufl element but different dolfin function spaces. """ return (type(self) == type(other) and self._number == other._number and self._part == other._part and self._element == other._element) # --- Helper functions for pretty syntax --- def TestFunction(element, part=None): """UFL value: Create a test function argument to a form.""" return Argument(element, 0, part) def TrialFunction(element, part=None): """UFL value: Create a trial function argument to a form.""" return Argument(element, 1, part) # --- Helper functions for creating subfunctions on mixed elements --- def Arguments(element, number): """UFL value: Create an Argument in a mixed space, and return a tuple with the function components corresponding to the subelements.""" return split(Argument(element, number)) def TestFunctions(element): """UFL value: Create a TestFunction in a mixed space, and return a tuple with the function components corresponding to the subelements.""" return Arguments(element, 0) def TrialFunctions(element): """UFL value: Create a TrialFunction in a mixed space, and return a tuple with the function components corresponding to the subelements.""" return Arguments(element, 1) ufl-1.6.0/ufl/assertions.py000066400000000000000000000031141255567402100156440ustar00rootroot00000000000000"""This module provides assertion functions used by the UFL implementation.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # First added: 2009-01-28 # Last changed: 2011-06-02 from ufl.log import error #--- Standardized error messages --- expecting_instance = lambda v, c: error("Expecting %s instance, not %s." % (c.__name__, repr(v))) expecting_python_scalar = lambda v: error("Expecting Python scalar, not %s." % repr(v)) expecting_expr = lambda v: error("Expecting Expr instance, not %s." % repr(v)) expecting_terminal = lambda v: error("Expecting Terminal instance, not %s." % repr(v)) expecting_true_ufl_scalar = lambda v: error("Expecting UFL scalar expression with no free indices, not %s." % repr(v)) #--- Standardized assertions --- def ufl_assert(condition, *message): "Assert that condition is true and otherwise issue an error with given message." if not condition: error(*message) ufl-1.6.0/ufl/cell.py000066400000000000000000000306071255567402100144000ustar00rootroot00000000000000"Types for representing a cell." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2009. # Modified by Kristian B. Oelgaard, 2009 # Modified by Marie E. Rognes 2012 # Modified by Andrew T. T. McRae, 2014 from itertools import chain from collections import defaultdict from ufl.log import warning, error, deprecate from ufl.assertions import ufl_assert from ufl.common import istr, EmptyDict from ufl.core.terminal import Terminal from ufl.protocols import id_or_none # --- Basic cell properties # Mapping from cell name to topological dimension cellname2dim = { "vertex": 0, "interval": 1, "triangle": 2, "tetrahedron": 3, "quadrilateral": 2, "hexahedron": 3, } def cell2dim(cell): "Maps from UFL cell or cell name to topological dimension" if isinstance(cell, str): # Backwards compatibility cellname = cell else: cellname = cell.cellname() if cellname == "OuterProductCell": return cell2dim(cell._A) + cell2dim(cell._B) else: return cellname2dim[cellname] # Mapping from cell name to facet name _cellname2facetname = { "interval": "vertex", "triangle": "interval", "quadrilateral": "interval", "tetrahedron": "triangle", "hexahedron": "quadrilateral", } _reference_cell_volume = { "vertex": 0.0, "interval": 1.0, "triangle": 0.5, "tetrahedron": 1.0/6.0, "quadrilateral": 1.0, "hexahedron": 1.0 } num_cell_entities = { "interval": (2, 1), "triangle": (3, 3, 1), "quadrilateral": (4, 4, 1), "tetrahedron": (4, 6, 4, 1), "hexahedron": (8, 12, 6, 1), } affine_cells = {"vertex", "interval", "triangle", "tetrahedron"} # --- Basic cell representation classes class Cell(object): "Representation of a finite element cell." __slots__ = ("_cellname", "_geometric_dimension", "_topological_dimension" ) def __init__(self, cellname, geometric_dimension=None, topological_dimension=None): "Initialize basic cell description." # The topological dimension is defined by the cell type, # so the cellname must be among the known ones, # so we can find the known dimension, unless we have # a product cell, in which the given dimension is used tdim = cellname2dim.get(cellname, topological_dimension) # The geometric dimension defaults to equal the topological # dimension if undefined if geometric_dimension is None: gdim = tdim else: gdim = geometric_dimension # Validate dimensions ufl_assert(isinstance(gdim, int), "Expecting integer dimension, not '%r'" % (gdim,)) ufl_assert(isinstance(tdim, int), "Expecting integer dimension, not '%r'" % (tdim,)) ufl_assert(tdim <= gdim, "Topological dimension cannot be larger than geometric dimension.") # ... Finally store validated data self._cellname = cellname self._topological_dimension = tdim self._geometric_dimension = gdim # --- Fundamental dimensions --- def topological_dimension(self): "Return the dimension of the topology of this cell." return self._topological_dimension def geometric_dimension(self): "Return the dimension of the space this cell is embedded in." return self._geometric_dimension # --- Cell properties --- def cellname(self): "Return the cellname of the cell." return self._cellname def num_entities(self, dim=None): "The number of cell entities of given topological dimension." num = num_cell_entities[self.cellname()] if dim is None: return num else: return num[dim] def num_vertices(self): "The number of cell vertices." return self.num_entities(0) def num_edges(self): "The number of cell edges." return self.num_entities(1) def num_facets(self): "The number of cell facets." tdim = self.topological_dimension() return self.num_entities(tdim-1) def reference_volume(self): "The volume of a reference cell of the same type." return _reference_cell_volume[self.cellname()] # --- Facet properties --- # TODO: The concept of a fixed name and number of entities for a facet does not work with product cells. # Search for 'facet_cellname' and 'num_facet_' to find usage and figure out another way to handle those places. # TODO: Maybe return a facet cell instead of all these accessors #def facet(self): # return Cell(self.facet_cellname(), self.geometric_dimension()) def facet_cellname(self): "Return the cellname of the facet of this cell, or None if not available." return _cellname2facetname.get(self.cellname()) def num_facet_entities(self, dim): "Return the number of cell entities of given topological dimension, or None if not available." num = num_cell_entities.get(self.cellname()) return num[dim] if num else None def num_facet_vertices(self): "The number of cell vertices, or None if not available." return self.num_facet_entities(0) def num_facet_edges(self): "The number of facet edges, or None if not available." return self.num_facet_entities(1) def reference_facet_volume(self): "The volume of a reference cell of the same type." return _reference_cell_volume[self.facet_cellname()] # --- Special functions for proper object behaviour --- def __eq__(self, other): if not isinstance(other, Cell): return False s = (self.geometric_dimension(), self.topological_dimension(), self.cellname()) o = (other.geometric_dimension(), other.topological_dimension(), other.cellname()) return s == o def __ne__(self, other): return not self == other def __lt__(self, other): if not isinstance(other, Cell): return False s = (self.geometric_dimension(), self.topological_dimension(), self.cellname()) o = (other.geometric_dimension(), other.topological_dimension(), other.cellname()) return s < o def __hash__(self): return hash(repr(self)) def __str__(self): return "<%s cell in %sD>" % (istr(self.cellname()), istr(self.geometric_dimension())) def __repr__(self): return "Cell(%r, %r)" % (self.cellname(), self.geometric_dimension()) def _repr_svg_(self): "" name = self.cellname() m = 200 if name == "interval": points = [(0, 0), (m, 0)] elif name == "triangle": points = [(0, m), (m, m), (0, 0), (0, m)] elif name == "quadrilateral": points = [(0, m), (m, m), (m, 0), (0, 0), (0, m)] else: points = None svg = ''' ''' if points: fill = "none" stroke = "black" strokewidth = 3 width = max(p[0] for p in points) - min(p[0] for p in points) height = max(p[1] for p in points) - min(p[1] for p in points) width = max(width, strokewidth) height = max(height, strokewidth) style = "fill:%s; stroke:%s; stroke-width:%s" % (fill, stroke, strokewidth) points = " ".join(','.join(map(str, p)) for p in points) return svg % (width, height, points, style) else: return None class ProductCell(Cell): __slots__ = ("_cells",) def __init__(self, *cells): cells = tuple(as_cell(cell) for cell in cells) gdim = sum(cell.geometric_dimension() for cell in cells) tdim = sum(cell.topological_dimension() for cell in cells) Cell.__init__(self, "product", gdim, tdim) self._cells = tuple(cells) def sub_cells(self): "Return list of cell factors." return self._cells def __eq__(self, other): if not isinstance(other, ProductCell): return False return self._cells == other._cells def __lt__(self, other): if not isinstance(other, ProductCell): return False return self._cells < other._cells def __repr__(self): return "ProductCell(*%r)" % (self._cells,) class OuterProductCell(Cell): """Representation of a cell formed as the Cartesian product of two existing cells""" __slots__ = ("_A", "_B", "facet_horiz", "facet_vert") def __init__(self, A, B, gdim=None): self._A = A self._B = B tdim = A.topological_dimension() + B.topological_dimension() # default gdim -- "only as big as it needs to be, but not smaller than A or B" gdim_temp = max(A.geometric_dimension(), B.geometric_dimension(), A.topological_dimension() + B.topological_dimension()) if gdim is None: # default gdim gdim = gdim_temp else: # otherwise, validate custom gdim if not isinstance(gdim, int): raise TypeError("gdim must be an integer") if gdim < gdim_temp: raise ValueError("gdim must be at least %d" % gdim_temp) Cell.__init__(self, "OuterProductCell", gdim, tdim) # facets for extruded cells if B.cellname() == "interval": self.facet_horiz = A if A.topological_dimension() == 2: self.facet_vert = OuterProductCell(Cell("interval"), Cell("interval")) elif A.topological_dimension() == 1: # Terminate this recursion somewhere! self.facet_vert = Cell("interval") else: # Don't know how to extrude this self.facet_vert = None def num_entities(self, dim): "The number of cell entities of given topological dimension." # Return None unless asked for the number of vertices / volumes templist = [None,] * (self.topological_dimension() + 1) templist[0] = self._A.num_vertices() * self._B.num_vertices() templist[-1] = 1 return templist[dim] def reference_volume(self): "The volume of a reference cell of the same type." return _reference_cell_volume[self._A.cellname()] * _reference_cell_volume[self._B.cellname()] def __eq__(self, other): if not isinstance(other, OuterProductCell): return False # This is quite subtle: my intuition says that the OPCs of # Cell("triangle") with Cell("interval"), and # Cell("triangle", 3) with Cell("interval") # are essentially the same: triangular prisms with gdim = tdim = 3. # For safety, though, we will only compare equal if the # subcells are *identical*, including immersion. return (self._A, self._B) == (other._A, other._B) and self.geometric_dimension() == other.geometric_dimension() def __lt__(self, other): if not isinstance(other, OuterProductCell): return NotImplemented return (self._A, self._B) < (other._A, other._B) def __repr__(self): return "OuterProductCell(*%r)" % list([self._A, self._B]) # --- Utility conversion functions def as_cell(cell): """Convert any valid object to a Cell (in particular, cellname string), or return cell if it is already a Cell.""" if isinstance(cell, Cell): return cell elif hasattr(cell, "ufl_cell"): return cell.ufl_cell() elif isinstance(cell, str): return Cell(cell) else: error("Invalid cell %s." % cell) ufl-1.6.0/ufl/checks.py000066400000000000000000000046021255567402100147150ustar00rootroot00000000000000"""Utility functions for checking properties of expressions.""" # Copyright (C) 2013-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2008-2009 from ufl.core.expr import Expr from ufl.corealg.traversal import traverse_unique_terminals def is_python_scalar(expression): "Return True iff expression is of a Python scalar type." return isinstance(expression, (int, float)) def is_ufl_scalar(expression): """Return True iff expression is scalar-valued, but possibly containing free indices.""" return isinstance(expression, Expr) and not expression.ufl_shape def is_true_ufl_scalar(expression): """Return True iff expression is scalar-valued, with no free indices.""" return isinstance(expression, Expr) and \ not (expression.ufl_shape or expression.ufl_free_indices) def is_globally_constant(expr): """Check if an expression is globally constant, which includes spatially independent constant coefficients that are not known before assembly time.""" # TODO: This does not consider gradients of coefficients, so false negatives are possible. from ufl.argument import Argument from ufl.coefficient import Coefficient for e in traverse_unique_terminals(expr): if isinstance(e, Argument): return False if isinstance(e, Coefficient) and e.element().family() != "Real": return False if not e.is_cellwise_constant(): return False # All terminals passed constant check return True def is_scalar_constant_expression(expr): """Check if an expression is a globally constant scalar expression.""" if is_python_scalar(expr): return True if expr.ufl_shape != (): return False return is_globally_constant(expr) ufl-1.6.0/ufl/classes.py000066400000000000000000000104241255567402100151110ustar00rootroot00000000000000"""This file is useful for external code like tests and form compilers, since it enables the syntax "from ufl.classes import CellFacetooBar" for getting implementation details not exposed through the default ufl namespace. It also contains functionality used by algorithms for dealing with groups of classes, and for mapping types to different handler functions.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2009. # Modified by Kristian B. Oelgaard, 2011 # Modified by Andrew T. T. McRae, 2014 # This will be populated part by part below __all__ = [] # # Import all submodules, triggering execution of the # ufl_type class decorator for each Expr class. # # Base classes of Expr type hierarchy import ufl.core.expr import ufl.core.terminal import ufl.core.operator # Terminal types import ufl.constantvalue import ufl.argument import ufl.coefficient import ufl.geometry import ufl.indexing # Operator types import ufl.indexed import ufl.indexsum import ufl.variable import ufl.tensors import ufl.algebra import ufl.tensoralgebra import ufl.mathfunctions import ufl.differentiation import ufl.conditional import ufl.restriction import ufl.exprcontainers import ufl.referencevalue # Make sure we import exproperators which attaches special functions to Expr from ufl import exproperators as __exproperators # # Make sure to import modules with new Expr subclasses here! # # Collect all classes in sets automatically classified by some properties all_ufl_classes = set(ufl.core.expr.Expr._ufl_all_classes_) abstract_classes = set(c for c in all_ufl_classes if c._ufl_is_abstract_) ufl_classes = set(c for c in all_ufl_classes if not c._ufl_is_abstract_) terminal_classes = set(c for c in all_ufl_classes if c._ufl_is_terminal_) nonterminal_classes = set(c for c in all_ufl_classes if not c._ufl_is_terminal_) __all__ += [ "all_ufl_classes", "abstract_classes", "ufl_classes", "terminal_classes", "nonterminal_classes", ] def populate_namespace_with_expr_classes(namespace): """Export all Expr subclasses into the namespace under their natural name.""" names = [] classes = ufl.core.expr.Expr._ufl_all_classes_ for cls in classes: class_name = cls.__name__ namespace[class_name] = cls names.append(class_name) return names __all__ += populate_namespace_with_expr_classes(locals()) # Domain types from ufl.cell import Cell, ProductCell, OuterProductCell from ufl.domain import Domain, ProductDomain __all__ += [ "Cell", "ProductCell", "OuterProductCell", "Domain", "ProductDomain", ] # Elements from ufl.finiteelement import ( FiniteElementBase, FiniteElement, MixedElement, VectorElement, TensorElement, EnrichedElement, RestrictedElement, TensorProductElement, OuterProductElement, OuterProductVectorElement) __all__ += [ "FiniteElementBase", "FiniteElement", "MixedElement", "VectorElement", "TensorElement", "EnrichedElement", "RestrictedElement", "TensorProductElement", "OuterProductElement", "OuterProductVectorElement", ] # Other non-Expr types from ufl.argument import TestFunction, TrialFunction, TestFunctions, TrialFunctions from ufl.core.multiindex import IndexBase, FixedIndex, Index __all__ += [ "TestFunction", "TrialFunction", "TestFunctions", "TrialFunctions", "IndexBase", "FixedIndex", "Index", ] # Higher level abstractions from ufl.measure import Measure, MeasureSum, MeasureProduct from ufl.integral import Integral from ufl.form import Form from ufl.equation import Equation __all__ += [ "Measure", "MeasureSum", "MeasureProduct", "Integral", "Form", "Equation", ] ufl-1.6.0/ufl/coefficient.py000066400000000000000000000122611255567402100157330ustar00rootroot00000000000000"""This module defines the Coefficient class and a number of related classes, including Constant.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2008-2009. from ufl.log import warning from ufl.assertions import ufl_assert from ufl.core.terminal import Terminal, FormArgument from ufl.finiteelement import FiniteElementBase, FiniteElement, VectorElement, TensorElement from ufl.split_functions import split from ufl.common import counted_init from ufl.core.ufl_type import ufl_type # --- The Coefficient class represents a coefficient in a form --- @ufl_type() class Coefficient(FormArgument): """UFL form argument type: Representation of a form coefficient.""" # Slots are disabled here because they cause trouble in PyDOLFIN multiple inheritance pattern: #__slots__ = ("_count", "_element", "_repr", "_gradient", "_derivatives") _ufl_noslots_ = True _globalcount = 0 def __init__(self, element, count=None): FormArgument.__init__(self) counted_init(self, count, Coefficient) ufl_assert(isinstance(element, FiniteElementBase), "Expecting a FiniteElementBase instance.") self._element = element self._repr = None def count(self): return self._count def reconstruct(self, element=None, count=None): # This code is shared with the FooConstant classes if element is None or element == self._element: element = self._element if count is None or count == self._count: count = self._count if count is self._count and element is self._element: return self ufl_assert(isinstance(element, FiniteElementBase), "Expecting an element, not %s" % element) ufl_assert(isinstance(count, int), "Expecting an int, not %s" % count) ufl_assert(element.value_shape() == self._element.value_shape(), "Cannot reconstruct a Coefficient with a different value shape.") return self._reconstruct(element, count) def _reconstruct(self, element, count): # This code is class specific return Coefficient(element, count) def element(self): return self._element @property def ufl_shape(self): return self._element.value_shape() def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." return self._element.is_cellwise_constant() def domains(self): "Return tuple of domains related to this terminal object." return self._element.domains() def signature_data(self, renumbering): "Signature data for form arguments depend on the global numbering of the form arguments and domains." count = renumbering[self] edata = self.element().signature_data(renumbering) d = self.domain() ddata = None if d is None else d.signature_data(renumbering) return ("Coefficient", count, edata, ddata) def __str__(self): count = str(self._count) if len(count) == 1: return "w_%s" % count else: return "w_{%s}" % count def __repr__(self): if self._repr is None: self._repr = "Coefficient(%r, %r)" % (self._element, self._count) return self._repr def __eq__(self, other): if not isinstance(other, Coefficient): return False if self is other: return True return (self._count == other._count and self._element == other._element) # --- Helper functions for defining constant coefficients without specifying element --- def Constant(domain, count=None): """UFL value: Represents a globally constant scalar valued coefficient.""" e = FiniteElement("Real", domain, 0) return Coefficient(e, count=count) def VectorConstant(domain, dim=None, count=None): """UFL value: Represents a globally constant vector valued coefficient.""" e = VectorElement("Real", domain, 0, dim) return Coefficient(e, count=count) def TensorConstant(domain, shape=None, symmetry=None, count=None): """UFL value: Represents a globally constant tensor valued coefficient.""" e = TensorElement("Real", domain, 0, shape=shape, symmetry=symmetry) return Coefficient(e, count=count) # --- Helper functions for subfunctions on mixed elements --- def Coefficients(element): """UFL value: Create a Coefficient in a mixed space, and return a tuple with the function components corresponding to the subelements.""" return split(Coefficient(element)) ufl-1.6.0/ufl/common.py000066400000000000000000000036071255567402100147510ustar00rootroot00000000000000"This module contains a collection of common utilities." # Copyright (C) 2008-2014 Martin Sandve Alnes and Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Kristian Oelgaard, 2009 # TODO: These things used to reside here, if we import from ufl.utils instead where applicable we can remove common.py from ufl.utils.indexflattening import shape_to_strides, unflatten_index, flatten_multiindex from ufl.utils.sequences import product, unzip, xor, or_tuples, and_tuples, iter_tree, recursive_chain from ufl.corealg.traversal import (pre_traversal, unique_pre_traversal, unique_pre_traversal, unique_post_traversal, post_traversal, unique_post_traversal) from ufl.utils.formatting import lstr, estr, istr, sstr, tstr, dstr, camel2underscore from ufl.utils.dicts import split_dict, slice_dict, mergedicts, mergedicts2, subdict, dict_sum, EmptyDictType, EmptyDict from ufl.utils.counted import counted_init, ExampleCounted from ufl.utils.timer import Timer from ufl.utils.stacks import Stack, StackDict from ufl.utils.ufltypedicts import UFLTypeDict, UFLTypeDefaultDict from ufl.utils.sorting import topological_sorting, sorted_by_count, sorted_by_key from ufl.utils.system import get_status_output, openpdf, pdflatex, write_file ufl-1.6.0/ufl/compound_expressions.py000066400000000000000000000255731255567402100177550ustar00rootroot00000000000000"""Functions implementing compound expressions as equivalent representations using basic operators.""" # Copyright (C) 2008-2014 Martin Sandve Alnes and Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2009-2010 from ufl.log import error, warning from ufl.assertions import ufl_assert from ufl.core.multiindex import indices from ufl.tensors import as_tensor, as_matrix, as_vector from ufl.operators import sqrt # Note: To avoid typing errors, the expressions for cofactor and # deviatoric parts below were created with the script # tensoralgebrastrings.py under sandbox/scripts/ # Note: Avoiding or delaying application of these horrible expressions # would be a major improvement to UFL and the form compiler toolchain. # It could easily be a moderate to major undertaking to get rid of though. def cross_expr(a, b): assert len(a) == 3 assert len(b) == 3 def c(i, j): return a[i]*b[j] - a[j]*b[i] return as_vector((c(1, 2), c(2, 0), c(0, 1))) def pseudo_determinant_expr(A): """Compute the pseudo-determinant of A: sqrt(det(A.T*A)).""" i, j, k = indices(3) ATA = as_tensor(A[k, i]*A[k, j], (i, j)) return sqrt(determinant_expr(ATA)) def pseudo_inverse_expr(A): """Compute the Penrose-Moore pseudo-inverse of A: (A.T*A)^-1 * A.T.""" i, j, k = indices(3) ATA = as_tensor(A[k, i]*A[k, j], (i, j)) ATAinv = inverse_expr(ATA) q, r, s = indices(3) return as_tensor(ATAinv[r, q] * A[s, q], (r, s)) def determinant_expr(A): "Compute the determinant of A." sh = A.ufl_shape if sh == (): return A elif sh[0] == sh[1]: if sh[0] == 1: return A[0, 0] elif sh[0] == 2: return determinant_expr_2x2(A) elif sh[0] == 3: return determinant_expr_3x3(A) else: return pseudo_determinant_expr(A) # TODO: Implement generally for all dimensions? error("determinant_expr not implemented for shape %s." % (sh,)) def _det_2x2(B, i, j, k, l): return B[i, k]*B[j, l] - B[i, l]*B[j, k] def determinant_expr_2x2(B): return _det_2x2(B, 0, 1, 0, 1) def determinant_expr_3x3(A): return (A[0, 0]*_det_2x2(A, 1, 2, 1, 2) + A[0, 1]*_det_2x2(A, 1, 2, 2, 0) + A[0, 2]*_det_2x2(A, 1, 2, 0, 1)) def inverse_expr(A): "Compute the inverse of A." sh = A.ufl_shape if sh == (): return 1.0 / A elif sh[0] == sh[1]: if sh[0] == 1: return as_tensor(((1.0 / A[0, 0],),)) else: return adj_expr(A) / determinant_expr(A) else: return pseudo_inverse_expr(A) def adj_expr(A): sh = A.ufl_shape ufl_assert(sh[0] == sh[1], "Expecting square matrix.") if sh[0] == 2: return adj_expr_2x2(A) elif sh[0] == 3: return adj_expr_3x3(A) elif sh[0] == 4: return adj_expr_4x4(A) error("adj_expr not implemented for dimension %s." % sh[0]) def adj_expr_2x2(A): return as_matrix([[A[1, 1], -A[0, 1]], [-A[1, 0], A[0, 0]]]) def adj_expr_3x3(A): return as_matrix([ [ A[2, 2]*A[1, 1] - A[1, 2]*A[2, 1], -A[0, 1]*A[2, 2] + A[0, 2]*A[2, 1], A[0, 1]*A[1, 2] - A[0, 2]*A[1, 1]], [-A[2, 2]*A[1, 0] + A[1, 2]*A[2, 0], -A[0, 2]*A[2, 0] + A[2, 2]*A[0, 0], A[0, 2]*A[1, 0] - A[1, 2]*A[0, 0]], [ A[1, 0]*A[2, 1] - A[2, 0]*A[1, 1], A[0, 1]*A[2, 0] - A[0, 0]*A[2, 1], A[0, 0]*A[1, 1] - A[0, 1]*A[1, 0]], ]) def adj_expr_4x4(A): return as_matrix([ [-A[3, 3]*A[2, 1]*A[1, 2] + A[1, 2]*A[3, 1]*A[2, 3] + A[1, 1]*A[3, 3]*A[2, 2] - A[3, 1]*A[2, 2]*A[1, 3] + A[2, 1]*A[1, 3]*A[3, 2] - A[1, 1]*A[3, 2]*A[2, 3], -A[3, 1]*A[0, 2]*A[2, 3] + A[0, 1]*A[3, 2]*A[2, 3] - A[0, 3]*A[2, 1]*A[3, 2] + A[3, 3]*A[2, 1]*A[0, 2] - A[3, 3]*A[0, 1]*A[2, 2] + A[0, 3]*A[3, 1]*A[2, 2], A[3, 1]*A[1, 3]*A[0, 2] + A[1, 1]*A[0, 3]*A[3, 2] - A[0, 3]*A[1, 2]*A[3, 1] - A[0, 1]*A[1, 3]*A[3, 2] + A[3, 3]*A[1, 2]*A[0, 1] - A[1, 1]*A[3, 3]*A[0, 2], A[1, 1]*A[0, 2]*A[2, 3] - A[2, 1]*A[1, 3]*A[0, 2] + A[0, 3]*A[2, 1]*A[1, 2] - A[1, 2]*A[0, 1]*A[2, 3] - A[1, 1]*A[0, 3]*A[2, 2] + A[0, 1]*A[2, 2]*A[1, 3]], [ A[3, 3]*A[1, 2]*A[2, 0] - A[3, 0]*A[1, 2]*A[2, 3] + A[1, 0]*A[3, 2]*A[2, 3] - A[3, 3]*A[1, 0]*A[2, 2] - A[1, 3]*A[3, 2]*A[2, 0] + A[3, 0]*A[2, 2]*A[1, 3], A[0, 3]*A[3, 2]*A[2, 0] - A[0, 3]*A[3, 0]*A[2, 2] + A[3, 3]*A[0, 0]*A[2, 2] + A[3, 0]*A[0, 2]*A[2, 3] - A[0, 0]*A[3, 2]*A[2, 3] - A[3, 3]*A[0, 2]*A[2, 0], -A[3, 3]*A[0, 0]*A[1, 2] + A[0, 0]*A[1, 3]*A[3, 2] - A[3, 0]*A[1, 3]*A[0, 2] + A[3, 3]*A[1, 0]*A[0, 2] + A[0, 3]*A[3, 0]*A[1, 2] - A[0, 3]*A[1, 0]*A[3, 2], A[0, 3]*A[1, 0]*A[2, 2] + A[1, 3]*A[0, 2]*A[2, 0] - A[0, 0]*A[2, 2]*A[1, 3] - A[0, 3]*A[1, 2]*A[2, 0] + A[0, 0]*A[1, 2]*A[2, 3] - A[1, 0]*A[0, 2]*A[2, 3]], [ A[3, 1]*A[1, 3]*A[2, 0] + A[3, 3]*A[2, 1]*A[1, 0] + A[1, 1]*A[3, 0]*A[2, 3] - A[1, 0]*A[3, 1]*A[2, 3] - A[3, 0]*A[2, 1]*A[1, 3] - A[1, 1]*A[3, 3]*A[2, 0], A[3, 3]*A[0, 1]*A[2, 0] - A[3, 3]*A[0, 0]*A[2, 1] - A[0, 3]*A[3, 1]*A[2, 0] - A[3, 0]*A[0, 1]*A[2, 3] + A[0, 0]*A[3, 1]*A[2, 3] + A[0, 3]*A[3, 0]*A[2, 1], -A[0, 0]*A[3, 1]*A[1, 3] + A[0, 3]*A[1, 0]*A[3, 1] - A[3, 3]*A[1, 0]*A[0, 1] + A[1, 1]*A[3, 3]*A[0, 0] - A[1, 1]*A[0, 3]*A[3, 0] + A[3, 0]*A[0, 1]*A[1, 3], A[0, 0]*A[2, 1]*A[1, 3] + A[1, 0]*A[0, 1]*A[2, 3] - A[0, 3]*A[2, 1]*A[1, 0] + A[1, 1]*A[0, 3]*A[2, 0] - A[1, 1]*A[0, 0]*A[2, 3] - A[0, 1]*A[1, 3]*A[2, 0]], [-A[1, 2]*A[3, 1]*A[2, 0] - A[2, 1]*A[1, 0]*A[3, 2] + A[3, 0]*A[2, 1]*A[1, 2] - A[1, 1]*A[3, 0]*A[2, 2] + A[1, 0]*A[3, 1]*A[2, 2] + A[1, 1]*A[3, 2]*A[2, 0], -A[3, 0]*A[2, 1]*A[0, 2] - A[0, 1]*A[3, 2]*A[2, 0] + A[3, 1]*A[0, 2]*A[2, 0] - A[0, 0]*A[3, 1]*A[2, 2] + A[3, 0]*A[0, 1]*A[2, 2] + A[0, 0]*A[2, 1]*A[3, 2], A[0, 0]*A[1, 2]*A[3, 1] - A[1, 0]*A[3, 1]*A[0, 2] + A[1, 1]*A[3, 0]*A[0, 2] + A[1, 0]*A[0, 1]*A[3, 2] - A[3, 0]*A[1, 2]*A[0, 1] - A[1, 1]*A[0, 0]*A[3, 2], -A[1, 1]*A[0, 2]*A[2, 0] + A[2, 1]*A[1, 0]*A[0, 2] + A[1, 2]*A[0, 1]*A[2, 0] + A[1, 1]*A[0, 0]*A[2, 2] - A[1, 0]*A[0, 1]*A[2, 2] - A[0, 0]*A[2, 1]*A[1, 2]], ]) def cofactor_expr(A): sh = A.ufl_shape ufl_assert(sh[0] == sh[1], "Expecting square matrix.") if sh[0] == 2: return cofactor_expr_2x2(A) elif sh[0] == 3: return cofactor_expr_3x3(A) elif sh[0] == 4: return cofactor_expr_4x4(A) error("cofactor_expr not implemented for dimension %s." % sh[0]) def cofactor_expr_2x2(A): return as_matrix([[A[1, 1], -A[1, 0]], [-A[0, 1], A[0, 0]]]) def cofactor_expr_3x3(A): return as_matrix([ [A[1, 1]*A[2, 2] - A[2, 1]*A[1, 2], A[2, 0]*A[1, 2] - A[1, 0]*A[2, 2], - A[2, 0]*A[1, 1] + A[1, 0]*A[2, 1]], [A[2, 1]*A[0, 2] - A[0, 1]*A[2, 2], A[0, 0]*A[2, 2] - A[2, 0]*A[0, 2], - A[0, 0]*A[2, 1] + A[2, 0]*A[0, 1]], [A[0, 1]*A[1, 2] - A[1, 1]*A[0, 2], A[1, 0]*A[0, 2] - A[0, 0]*A[1, 2], - A[1, 0]*A[0, 1] + A[0, 0]*A[1, 1]], ]) def cofactor_expr_4x4(A): return as_matrix([ [-A[3, 1]*A[2, 2]*A[1, 3] - A[3, 2]*A[2, 3]*A[1, 1] + A[1, 3]*A[3, 2]*A[2, 1] + A[3, 1]*A[2, 3]*A[1, 2] + A[2, 2]*A[1, 1]*A[3, 3] - A[3, 3]*A[2, 1]*A[1, 2], -A[1, 0]*A[2, 2]*A[3, 3] + A[2, 0]*A[3, 3]*A[1, 2] + A[2, 2]*A[1, 3]*A[3, 0] - A[2, 3]*A[3, 0]*A[1, 2] + A[1, 0]*A[3, 2]*A[2, 3] - A[1, 3]*A[3, 2]*A[2, 0], A[1, 0]*A[3, 3]*A[2, 1] + A[2, 3]*A[1, 1]*A[3, 0] - A[2, 0]*A[1, 1]*A[3, 3] - A[1, 3]*A[3, 0]*A[2, 1] - A[1, 0]*A[3, 1]*A[2, 3] + A[3, 1]*A[1, 3]*A[2, 0], A[3, 0]*A[2, 1]*A[1, 2] + A[1, 0]*A[3, 1]*A[2, 2] + A[3, 2]*A[2, 0]*A[1, 1] - A[2, 2]*A[1, 1]*A[3, 0] - A[3, 1]*A[2, 0]*A[1, 2] - A[1, 0]*A[3, 2]*A[2, 1]], [ A[3, 1]*A[2, 2]*A[0, 3] + A[0, 2]*A[3, 3]*A[2, 1] + A[0, 1]*A[3, 2]*A[2, 3] - A[3, 1]*A[0, 2]*A[2, 3] - A[0, 1]*A[2, 2]*A[3, 3] - A[3, 2]*A[0, 3]*A[2, 1], -A[2, 2]*A[0, 3]*A[3, 0] - A[0, 2]*A[2, 0]*A[3, 3] - A[3, 2]*A[2, 3]*A[0, 0] + A[2, 2]*A[3, 3]*A[0, 0] + A[0, 2]*A[2, 3]*A[3, 0] + A[3, 2]*A[2, 0]*A[0, 3], A[3, 1]*A[2, 3]*A[0, 0] - A[0, 1]*A[2, 3]*A[3, 0] - A[3, 1]*A[2, 0]*A[0, 3] - A[3, 3]*A[0, 0]*A[2, 1] + A[0, 3]*A[3, 0]*A[2, 1] + A[0, 1]*A[2, 0]*A[3, 3], A[3, 2]*A[0, 0]*A[2, 1] - A[0, 2]*A[3, 0]*A[2, 1] + A[0, 1]*A[2, 2]*A[3, 0] + A[3, 1]*A[0, 2]*A[2, 0] - A[0, 1]*A[3, 2]*A[2, 0] - A[3, 1]*A[2, 2]*A[0, 0]], [ A[3, 1]*A[1, 3]*A[0, 2] - A[0, 2]*A[1, 1]*A[3, 3] - A[3, 1]*A[0, 3]*A[1, 2] + A[3, 2]*A[1, 1]*A[0, 3] + A[0, 1]*A[3, 3]*A[1, 2] - A[0, 1]*A[1, 3]*A[3, 2], A[1, 3]*A[3, 2]*A[0, 0] - A[1, 0]*A[3, 2]*A[0, 3] - A[1, 3]*A[0, 2]*A[3, 0] + A[0, 3]*A[3, 0]*A[1, 2] + A[1, 0]*A[0, 2]*A[3, 3] - A[3, 3]*A[0, 0]*A[1, 2], -A[1, 0]*A[0, 1]*A[3, 3] + A[0, 1]*A[1, 3]*A[3, 0] - A[3, 1]*A[1, 3]*A[0, 0] - A[1, 1]*A[0, 3]*A[3, 0] + A[1, 0]*A[3, 1]*A[0, 3] + A[1, 1]*A[3, 3]*A[0, 0], A[0, 2]*A[1, 1]*A[3, 0] - A[3, 2]*A[1, 1]*A[0, 0] - A[0, 1]*A[3, 0]*A[1, 2] - A[1, 0]*A[3, 1]*A[0, 2] + A[3, 1]*A[0, 0]*A[1, 2] + A[1, 0]*A[0, 1]*A[3, 2]], [ A[0, 3]*A[2, 1]*A[1, 2] + A[0, 2]*A[2, 3]*A[1, 1] + A[0, 1]*A[2, 2]*A[1, 3] - A[2, 2]*A[1, 1]*A[0, 3] - A[1, 3]*A[0, 2]*A[2, 1] - A[0, 1]*A[2, 3]*A[1, 2], A[1, 0]*A[2, 2]*A[0, 3] + A[1, 3]*A[0, 2]*A[2, 0] - A[1, 0]*A[0, 2]*A[2, 3] - A[2, 0]*A[0, 3]*A[1, 2] - A[2, 2]*A[1, 3]*A[0, 0] + A[2, 3]*A[0, 0]*A[1, 2], -A[0, 1]*A[1, 3]*A[2, 0] + A[2, 0]*A[1, 1]*A[0, 3] + A[1, 3]*A[0, 0]*A[2, 1] - A[1, 0]*A[0, 3]*A[2, 1] + A[1, 0]*A[0, 1]*A[2, 3] - A[2, 3]*A[1, 1]*A[0, 0], A[1, 0]*A[0, 2]*A[2, 1] - A[0, 2]*A[2, 0]*A[1, 1] + A[0, 1]*A[2, 0]*A[1, 2] + A[2, 2]*A[1, 1]*A[0, 0] - A[1, 0]*A[0, 1]*A[2, 2] - A[0, 0]*A[2, 1]*A[1, 2]] ]) def deviatoric_expr(A): sh = A.ufl_shape ufl_assert(sh[0] == sh[1], "Expecting square matrix.") if sh[0] == 2: return deviatoric_expr_2x2(A) elif sh[0] == 3: return deviatoric_expr_3x3(A) error("deviatoric_expr not implemented for dimension %s." % sh[0]) def deviatoric_expr_2x2(A): return as_matrix([[-1./2*A[1, 1]+1./2*A[0, 0], A[0, 1]], [A[1, 0], 1./2*A[1, 1]-1./2*A[0, 0]]]) def deviatoric_expr_3x3(A): return as_matrix([[-1./3*A[1, 1]-1./3*A[2, 2]+2./3*A[0, 0], A[0, 1], A[0, 2]], [A[1, 0], 2./3*A[1, 1]-1./3*A[2, 2]-1./3*A[0, 0], A[1, 2]], [A[2, 0], A[2, 1], -1./3*A[1, 1]+2./3*A[2, 2]-1./3*A[0, 0]]]) ufl-1.6.0/ufl/conditional.py000066400000000000000000000255531255567402100157700ustar00rootroot00000000000000"""This module defines classes for conditional expressions.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from ufl.log import warning, error from ufl.assertions import ufl_assert from ufl.core.operator import Operator from ufl.constantvalue import as_ufl from ufl.precedence import parstr from ufl.exprequals import expr_equals from ufl.checks import is_true_ufl_scalar from ufl.common import EmptyDict from ufl.core.ufl_type import ufl_type #--- Condition classes --- # TODO: Would be nice with some kind of type system to show that this is a boolean type not a float type @ufl_type(is_abstract=True, is_scalar=True) class Condition(Operator): __slots__ = () def __init__(self, operands): Operator.__init__(self, operands) def __bool__(self): # Showing explicit error here to protect against misuse error("UFL conditions cannot be evaluated as bool in a Python context.") #return NotImplemented __nonzero__ = __bool__ @ufl_type(is_abstract=True, num_ops=2) class BinaryCondition(Condition): __slots__ = ('_name',) def __init__(self, name, left, right): left = as_ufl(left) right = as_ufl(right) Condition.__init__(self, (left, right)) self._name = name if name in ('!=', '=='): # Since equals and not-equals are used for comparing representations, # we have to allow any shape here. The scalar properties must be # checked when used in conditional instead! pass elif name in ('&&', '||'): # Binary operators acting on boolean expressions allow only conditions ufl_assert(isinstance(left, Condition), "Expecting a Condition, not a %s." % left._ufl_class_) ufl_assert(isinstance(right, Condition), "Expecting a Condition, not a %s." % right._ufl_class_) else: # Binary operators acting on non-boolean expressions allow only scalars ufl_assert(left.ufl_shape == () and right.ufl_shape == (), "Expecting scalar arguments.") ufl_assert(left.ufl_free_indices == () and right.ufl_free_indices == (), "Expecting scalar arguments.") def __str__(self): return "%s %s %s" % (parstr(self.ufl_operands[0], self), self._name, parstr(self.ufl_operands[1], self)) def __repr__(self): return "%s(%r, %r)" % (type(self).__name__, self.ufl_operands[0], self.ufl_operands[1]) # Not associating with __eq__, the concept of equality with == is reserved for object equivalence for use in set and dict. @ufl_type() class EQ(BinaryCondition): __slots__ = () def __init__(self, left, right): BinaryCondition.__init__(self, "==", left, right) def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) b = self.ufl_operands[1].evaluate(x, mapping, component, index_values) return bool(a == b) def __bool__(self): return expr_equals(self.ufl_operands[0], self.ufl_operands[1]) __nonzero__ = __bool__ # Not associating with __ne__, the concept of equality with == is reserved for object equivalence for use in set and dict. @ufl_type() class NE(BinaryCondition): __slots__ = () def __init__(self, left, right): BinaryCondition.__init__(self, "!=", left, right) def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) b = self.ufl_operands[1].evaluate(x, mapping, component, index_values) return bool(a != b) def __bool__(self): return not expr_equals(self.ufl_operands[0], self.ufl_operands[1]) __nonzero__ = __bool__ @ufl_type(binop="__le__") class LE(BinaryCondition): __slots__ = () def __init__(self, left, right): BinaryCondition.__init__(self, "<=", left, right) def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) b = self.ufl_operands[1].evaluate(x, mapping, component, index_values) return bool(a <= b) @ufl_type(binop="__ge__") class GE(BinaryCondition): __slots__ = () def __init__(self, left, right): BinaryCondition.__init__(self, ">=", left, right) def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) b = self.ufl_operands[1].evaluate(x, mapping, component, index_values) return bool(a >= b) @ufl_type(binop="__lt__") class LT(BinaryCondition): __slots__ = () def __init__(self, left, right): BinaryCondition.__init__(self, "<", left, right) def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) b = self.ufl_operands[1].evaluate(x, mapping, component, index_values) return bool(a < b) @ufl_type(binop="__gt__") class GT(BinaryCondition): __slots__ = () def __init__(self, left, right): BinaryCondition.__init__(self, ">", left, right) def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) b = self.ufl_operands[1].evaluate(x, mapping, component, index_values) return bool(a > b) @ufl_type() class AndCondition(BinaryCondition): __slots__ = () def __init__(self, left, right): BinaryCondition.__init__(self, "&&", left, right) def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) b = self.ufl_operands[1].evaluate(x, mapping, component, index_values) return bool(a and b) @ufl_type() class OrCondition(BinaryCondition): __slots__ = () def __init__(self, left, right): BinaryCondition.__init__(self, "||", left, right) def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) b = self.ufl_operands[1].evaluate(x, mapping, component, index_values) return bool(a or b) @ufl_type(num_ops=1) class NotCondition(Condition): __slots__ = () def __init__(self, condition): Condition.__init__(self, (condition,)) ufl_assert(isinstance(condition, Condition), "Expecting a condition.") def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) return bool(not a) def __str__(self): return "!(%s)" % (str(self.ufl_operands[0]),) def __repr__(self): return "NotCondition(%r)" % (self.ufl_operands[0],) #--- Conditional expression (condition ? true_value : false_value) --- @ufl_type(num_ops=3, inherit_shape_from_operand=1, inherit_indices_from_operand=1) class Conditional(Operator): __slots__ = () def __init__(self, condition, true_value, false_value): ufl_assert(isinstance(condition, Condition), "Expectiong condition as first argument.") true_value = as_ufl(true_value) false_value = as_ufl(false_value) tsh = true_value.ufl_shape fsh = false_value.ufl_shape ufl_assert(tsh == fsh, "Shape mismatch between conditional branches.") tfi = true_value.ufl_free_indices ffi = false_value.ufl_free_indices ufl_assert(tfi == ffi, "Free index mismatch between conditional branches.") if isinstance(condition, (EQ, NE)): ufl_assert( condition.ufl_operands[0].ufl_shape == () and condition.ufl_operands[0].ufl_free_indices == () and condition.ufl_operands[1].ufl_shape == () and condition.ufl_operands[1].ufl_free_indices == (), "Non-scalar == or != is not allowed.") Operator.__init__(self, (condition, true_value, false_value)) def evaluate(self, x, mapping, component, index_values): c = self.ufl_operands[0].evaluate(x, mapping, component, index_values) if c: a = self.ufl_operands[1] else: a = self.ufl_operands[2] return a.evaluate(x, mapping, component, index_values) def __str__(self): return "%s ? %s : %s" % tuple(parstr(o, self) for o in self.ufl_operands) def __repr__(self): return "Conditional(%r, %r, %r)" % self.ufl_operands #--- Specific functions higher level than a conditional --- @ufl_type(is_scalar=True, num_ops=1) class MinValue(Operator): "UFL operator: Take the minimum of two values." __slots__ = () def __init__(self, left, right): Operator.__init__(self, (left, right)) ufl_assert(is_true_ufl_scalar(left) and is_true_ufl_scalar(right), "Expecting scalar arguments.") def evaluate(self, x, mapping, component, index_values): a, b = self.ufl_operands a = a.evaluate(x, mapping, component, index_values) b = b.evaluate(x, mapping, component, index_values) try: res = min(a, b) except ValueError: warning('Value error in evaluation of min() of %s and %s.' % self.ufl_operands) raise return res def __str__(self): return "min_value(%s, %s)" % self.ufl_operands def __repr__(self): return "MinValue(%r, %r)" % self.ufl_operands @ufl_type(is_scalar=True, num_ops=1) class MaxValue(Operator): "UFL operator: Take the maximum of two values." __slots__ = () def __init__(self, left, right): Operator.__init__(self, (left, right)) ufl_assert(is_true_ufl_scalar(left) and is_true_ufl_scalar(right), "Expecting scalar arguments.") def evaluate(self, x, mapping, component, index_values): a, b = self.ufl_operands a = a.evaluate(x, mapping, component, index_values) b = b.evaluate(x, mapping, component, index_values) try: res = max(a, b) except ValueError: warning('Value error in evaluation of max() of %s and %s.' % self.ufl_operands) raise return res def __str__(self): return "max_value(%s, %s)" % self.ufl_operands def __repr__(self): return "MaxValue(%r, %r)" % self.ufl_operands ufl-1.6.0/ufl/constantvalue.py000066400000000000000000000325571255567402100163550ustar00rootroot00000000000000"This module defines classes representing constant values." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2011. from six.moves import zip from six.moves import xrange as range from six import iteritems from ufl.log import warning, error from ufl.assertions import ufl_assert, expecting_python_scalar from ufl.core.expr import Expr from ufl.core.terminal import Terminal from ufl.core.multiindex import Index, FixedIndex from ufl.common import EmptyDict from ufl.core.ufl_type import ufl_type #--- Helper functions imported here for compatibility--- from ufl.checks import is_python_scalar, is_ufl_scalar, is_true_ufl_scalar # Precision for float formatting precision = None def format_float(x): "Format float value based on global UFL precision." if precision is None: return repr(x) else: return ("%%.%dg" % precision) % x #--- Base classes for constant types --- @ufl_type(is_abstract=True) class ConstantValue(Terminal): __slots__ = () def __init__(self): Terminal.__init__(self) def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." return True def domains(self): "Return tuple of domains related to this terminal object." return () #--- Class for representing abstract constant symbol only for use internally in form compilers #@ufl_type() #class AbstractSymbol(ConstantValue): # "UFL literal type: Representation of a constant valued symbol with unknown properties." # __slots__ = ("_name", "ufl_shape") # def __init__(self, name, shape): # ConstantValue.__init__(self) # self._name = name # self.ufl_shape = shape # # def reconstruct(self, name=None): # if name is None: # name = self._name # return AbstractSymbol(name, self.ufl_shape) # # def __str__(self): # return "" % (self._name, self.ufl_shape) # # def __repr__(self): # return "AbstractSymbol(%r, %r)" % (self._name, self.ufl_shape) # # def __eq__(self, other): # return isinstance(other, AbstractSymbol) and self._name == other._name and self.ufl_shape == other.ufl_shape #--- Class for representing zero tensors of different shapes --- # TODO: Add geometric dimension/domain and Argument dependencies to Zero? @ufl_type(is_literal=True) class Zero(ConstantValue): "UFL literal type: Representation of a zero valued expression." __slots__ = ("ufl_shape", "ufl_free_indices", "ufl_index_dimensions") _cache = {} def __getnewargs__(self): return (self.ufl_shape, self.ufl_free_indices, self.ufl_index_dimensions) def __new__(cls, shape=(), free_indices=(), index_dimensions=None): if free_indices: self = ConstantValue.__new__(cls) else: self = Zero._cache.get(shape) if self is not None: return self self = ConstantValue.__new__(cls) Zero._cache[shape] = self self._init(shape, free_indices, index_dimensions) return self def __init__(self, shape=(), free_indices=(), index_dimensions=None): pass def _init(self, shape=(), free_indices=(), index_dimensions=None): ConstantValue.__init__(self) if not all(isinstance(i, int) for i in shape): error("Expecting tuple of int.") if not isinstance(free_indices, tuple): error("Expecting tuple for free_indices, not %s" % str(free_indices)) self.ufl_shape = shape if not free_indices: self.ufl_free_indices = () self.ufl_index_dimensions = () elif all(isinstance(i, Index) for i in free_indices): # Handle old input format if not (isinstance(index_dimensions, dict) and all(isinstance(i, Index) for i in index_dimensions.keys())): error("Expecting tuple of index dimensions, not %s" % str(index_dimensions)) self.ufl_free_indices = tuple(sorted(i.count() for i in free_indices)) self.ufl_index_dimensions = tuple(d for i, d in sorted(iteritems(index_dimensions), key=lambda x: x[0].count())) else: # Handle new input format if not all(isinstance(i, int) for i in free_indices): error("Expecting tuple of integer free index ids, not %s" % str(free_indices)) if not (isinstance(index_dimensions, tuple) and all(isinstance(i, int) for i in index_dimensions)): error("Expecting tuple of integer index dimensions, not %s" % str(index_dimensions)) # TODO: Assume sorted and avoid this cost. ufl_assert(sorted(free_indices) == list(free_indices), "Expecting sorted input. Remove this check later for efficiency.") self.ufl_free_indices = free_indices self.ufl_index_dimensions = index_dimensions def free_indices(self): "Intermediate helper property getter to transition from .free_indices() to .ufl_free_indices." return tuple(Index(count=i) for i in self.ufl_free_indices) def index_dimensions(self): "Intermediate helper property getter to transition from .index_dimensions() to .ufl_index_dimensions." return { Index(count=i): d for i, d in zip(self.ufl_free_indices, self.ufl_index_dimensions) } def reconstruct(self, free_indices=None): if not free_indices: return self ufl_assert(len(free_indices) == len(self.ufl_free_indices), "Size mismatch between old and new indices.") fid = self.ufl_index_dimensions new_fi, new_fid = zip(*tuple(sorted((free_indices[pos], fid[pos]) for pos, a in enumerate(self.ufl_free_indices)))) return Zero(self.ufl_shape, new_fi, new_fid) def evaluate(self, x, mapping, component, index_values): return 0.0 def __str__(self): if self.ufl_shape == () and self.ufl_free_indices == (): return "0" return "(0<%r, %r>)" % (self.ufl_shape, self.ufl_free_indices) def __repr__(self): return "Zero(%r, %r, %r)" % (self.ufl_shape, self.ufl_free_indices, self.ufl_index_dimensions) def __eq__(self, other): if isinstance(other, Zero): if self is other: return True return (self.ufl_shape == other.ufl_shape and self.ufl_free_indices == other.ufl_free_indices and self.ufl_index_dimensions == other.ufl_index_dimensions) elif isinstance(other, (int, float)): return other == 0 else: return False def __neg__(self): return self def __abs__(self): return self def __bool__(self): return False __nonzero__ = __bool__ def __float__(self): return 0.0 def __int__(self): return 0 def zero(*shape): "UFL literal constant: Return a zero tensor with the given shape." if len(shape) == 1 and isinstance(shape[0], tuple): return Zero(shape[0]) else: return Zero(shape) #--- Scalar value types --- @ufl_type(is_abstract=True, is_scalar=True) class ScalarValue(ConstantValue): "A constant scalar value." __slots__ = ("_value",) def __init__(self, value): ConstantValue.__init__(self) self._value = value def value(self): return self._value def evaluate(self, x, mapping, component, index_values): return self._value def __eq__(self, other): """This is implemented to allow comparison with python scalars. Note that this will make IntValue(1) != FloatValue(1.0), but ufl-python comparisons like IntValue(1) == 1.0 FloatValue(1.0) == 1 can still succeed. These will however not have the same hash value and therefore not collide in a dict. """ if isinstance(other, self._ufl_class_): return self._value == other._value elif isinstance(other, (int, float)): # FIXME: Disallow this, require explicit 'expr == IntValue(3)' instead to avoid ambiguities! return other == self._value else: return False def __str__(self): return str(self._value) def __float__(self): return float(self._value) def __int__(self): return int(self._value) def __neg__(self): return type(self)(-self._value) def __abs__(self): return type(self)(abs(self._value)) @ufl_type(wraps_type=float, is_literal=True) class FloatValue(ScalarValue): "UFL literal type: Representation of a constant scalar floating point value." __slots__ = () def __getnewargs__(self): return (self._value,) def __new__(cls, value): if value == 0.0: # Always represent zero with Zero return Zero() return ConstantValue.__new__(cls) def __init__(self, value): ScalarValue.__init__(self, float(value)) def __repr__(self): return "%s(%s)" % (type(self).__name__, format_float(self._value)) @ufl_type(wraps_type=int, is_literal=True) class IntValue(ScalarValue): "UFL literal type: Representation of a constant scalar integer value." __slots__ = () _cache = {} def __getnewargs__(self): return (self._value,) def __new__(cls, value): if value == 0: # Always represent zero with Zero return Zero() elif abs(value) < 100: # Small numbers are cached to reduce memory usage (fly-weight pattern) self = IntValue._cache.get(value) if self is not None: return self self = ScalarValue.__new__(cls) IntValue._cache[value] = self else: self = ScalarValue.__new__(cls) self._init(value) return self def _init(self, value): ScalarValue.__init__(self, int(value)) def __init__(self, value): pass def __repr__(self): return "%s(%s)" % (type(self).__name__, repr(self._value)) #--- Identity matrix --- @ufl_type() class Identity(ConstantValue): "UFL literal type: Representation of an identity matrix." __slots__ = ("_dim", "ufl_shape") def __init__(self, dim): ConstantValue.__init__(self) self._dim = dim self.ufl_shape = (dim, dim) def evaluate(self, x, mapping, component, index_values): a, b = component return 1 if a == b else 0 def __getitem__(self, key): ufl_assert(len(key) == 2, "Size mismatch for Identity.") if all(isinstance(k, (int, FixedIndex)) for k in key): return IntValue(1) if (int(key[0]) == int(key[1])) else Zero() return Expr.__getitem__(self, key) def __str__(self): return "I" def __repr__(self): return "Identity(%d)" % self._dim def __eq__(self, other): return isinstance(other, Identity) and self._dim == other._dim #--- Permutation symbol --- @ufl_type() class PermutationSymbol(ConstantValue): """UFL literal type: Representation of a permutation symbol. This is also known as the Levi-Civita symbol, antisymmetric symbol, or alternating symbol.""" __slots__ = ("ufl_shape", "_dim") def __init__(self, dim): ConstantValue.__init__(self) self._dim = dim self.ufl_shape = (dim,)*dim def evaluate(self, x, mapping, component, index_values): return self.__eps(component) def __getitem__(self, key): ufl_assert(len(key) == self._dim, "Size mismatch for PermutationSymbol.") if all(isinstance(k, (int, FixedIndex)) for k in key): return self.__eps(key) return Expr.__getitem__(self, key) def __str__(self): return "eps" def __repr__(self): return "PermutationSymbol(%d)" % self._dim def __eq__(self, other): return isinstance(other, PermutationSymbol) and self._dim == other._dim def __eps(self, x): """This function body is taken from http://www.mathkb.com/Uwe/Forum.aspx/math/29865/N-integer-Levi-Civita""" result = IntValue(1) for i, x1 in enumerate(x): for j in range(i + 1, len(x)): x2 = x[j] if x1 > x2: result = -result elif x1 == x2: return Zero() return result def as_ufl(expression): "Converts expression to an Expr if possible." if isinstance(expression, Expr): return expression if isinstance(expression, float): return FloatValue(expression) if isinstance(expression, int): return IntValue(expression) error(("Invalid type conversion: %s can not be converted to any UFL type.\n"+\ "The representation of the object is:\n%r") % (type(expression), expression)) ufl-1.6.0/ufl/core/000077500000000000000000000000001255567402100140315ustar00rootroot00000000000000ufl-1.6.0/ufl/core/__init__.py000066400000000000000000000000001255567402100161300ustar00rootroot00000000000000ufl-1.6.0/ufl/core/compute_expr_hash.py000066400000000000000000000036531255567402100201270ustar00rootroot00000000000000"""Non-recursive traversal based hash computation algorithm. Fast iteration over nodes in an Expr DAG to compute memoized hashes for all unique nodes. """ # Copyright (C) 2015 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # This limits the _depth_ of expression trees _recursion_limit_ = 6400 # should be enough for everyone def compute_expr_hash(expr): """Compute hashes of expr and all its nodes efficiently without using Python recursion.""" if expr._hash is not None: return expr._hash stack = [None]*_recursion_limit_ stacksize = 0 ops = expr.ufl_operands stack[stacksize] = [expr, ops, len(ops)] stacksize += 1 while stacksize > 0: entry = stack[stacksize - 1] e = entry[0] if e._hash is not None: # cutoff: don't need to visit children when hash has previously been computed stacksize -= 1 elif entry[2] == 0: # all children consumed: trigger memoized hash computation e._hash = e._ufl_compute_hash_() stacksize -= 1 else: # add children to stack to hash them first entry[2] -= 1 o = entry[1][entry[2]] oops = o.ufl_operands stack[stacksize] = [o, oops, len(oops)] stacksize += 1 return expr._hash ufl-1.6.0/ufl/core/expr.py000066400000000000000000000404501255567402100153640ustar00rootroot00000000000000"""This module defines the Expr class, the superclass for all expression tree node types in UFL. NB! A note about other operators not implemented here: More operators (special functions) on Exprs are defined in exproperators.py, as well as the transpose "A.T" and spatial derivative "a.dx(i)". This is to avoid circular dependencies between Expr and its subclasses. """ # Copyright (C) 2008-2015 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2008 from six.moves import xrange as range from ufl.log import warning, error #--- The base object for all UFL expression tree nodes --- class Expr(object): """Base class for all UFL expression types. *Instance properties* Every expression instance will have certain properties. Most important are the ``ufl_operands``, ``ufl_shape``, ``ufl_free_indices``, and ``ufl_index_dimensions`` properties. Expressions are immutable and hashable. *Type traits* The Expr API defines a number of type traits that each subclass needs to provide. Most of these are specified indirectly via the arguments to the ``ufl_type`` class decorator, allowing UFL to do some consistency checks and automate most of the traits for most types. The type traits are accessed via a class or instance object on the form obj._ufl_traitname_. See the source code for description of each type trait. *Operators* Some Python special functions are implemented in this class, some are implemented in subclasses, and some are attached to this class in the ``ufl_type`` class decorator. *Defining subclasses* To define a new expression class, inherit from either ``Terminal`` or ``Operator``, and apply the ``ufl_type`` class decorator with suitable arguments. See the docstring of ``ufl_type`` for details on its arguments. Looking at existing classes similar to the one you wish to add is a good idea. Looking through the comments in the ``Expr`` class and ``ufl_type`` to understand all the properties that may need to be specified is also a good idea. Note that many algorithms in UFL and form compilers will need handlers implemented for each new type. .. code-block:: python @ufl_type() class MyOperator(Operator): pass *Type collections* All ``Expr`` subclasses are collected by ``ufl_type`` in global variables available via ``Expr``. *Profiling* Object creation statistics can be collected by doing .. code-block:: python Expr.ufl_enable_profiling() # ... run some code initstats, delstats = Expr.ufl_disable_profiling() Giving a list of creation and deletion counts for each typecode. """ # --- Each Expr subclass must define __slots__ or _ufl_noslots_ at the top --- # This is to freeze member variables for objects of this class and save memory # by skipping the per-instance dict. __slots__ = ("_hash",) #_ufl_noslots_ = True # --- Basic object behaviour --- def __getnewargs__(self): """The tuple returned here is passed to as args to cls.__new__(cls, *args). This implementation passes the operands, which is () for terminals. May be necessary to override if __new__ is implemented in a subclass. """ return self.ufl_operands def __init__(self): self._hash = None def __del__(self): pass # This shows the principal behaviour of the hash function attached in ufl_type: #def __hash__(self): # if self._hash is None: # self._hash = self._ufl_compute_hash_() # return self._hash # --- Type traits are added to subclasses by the ufl_type class decorator --- # Note: Some of these are modified after the Expr class definition # because Expr is not defined yet at this point. # Note: Boolean type traits that categorize types are mostly set to # None for Expr but should be True or False for any non-abstract type. # A reference to the UFL class itself. # This makes it possible to do type(f)._ufl_class_ and be sure you get # the actual UFL class instead of a subclass from another library. _ufl_class_ = None # The handler name. # This is the name of the handler function you implement for this type in a multifunction. _ufl_handler_name_ = "expr" # The integer typecode, a contiguous index different for each type. # This is used for fast lookup into e.g. multifunction handler tables. _ufl_typecode_ = 0 # Number of operands, "varying" for some types, or None if not applicable for abstract types. _ufl_num_ops_ = None # Type trait: If the type is abstract. # An abstract class cannot be instantiated and does not need all properties specified. _ufl_is_abstract_ = True # Type trait: If the type is terminal. _ufl_is_terminal_ = None # Type trait: If the type is a literal. _ufl_is_literal_ = None # Type trait: If the type is classified as a 'terminal modifier', for form compiler use. _ufl_is_terminal_modifier_ = None # Type trait: If the type is a shaping operator. # Shaping operations include indexing, slicing, transposing, # i.e. not introducing computation of a new value. _ufl_is_shaping_ = False # Type trait: If the type is in reference frame. _ufl_is_in_reference_frame_ = None # Type trait: If the type is a restriction to a geometric entity. _ufl_is_restriction_ = None # Type trait: If the type is evaluation in a particular way. _ufl_is_evaluation_ = None # Type trait: If the type is a differential operator. _ufl_is_differential_ = None # Type trait: If the type is purely scalar, having no shape or indices. _ufl_is_scalar_ = None # Type trait: If the type never has free indices. _ufl_is_index_free_ = False # --- All subclasses must define these object attributes --- # FIXME: Enable checks in ufl_type _ufl_required_properties_ = ( # A tuple of operands, all of them Expr instances. "ufl_operands", # A tuple of ints, the value shape of the expression. "ufl_shape", # A tuple of free index counts. "ufl_free_indices", # A tuple providing the int dimension for each free index. "ufl_index_dimensions", ) # FIXME: Add more and enable all _ufl_required_methods_ = ( # To compute the hash on demand, this method is called. "_ufl_compute_hash_", # The == operator must be implemented to compare for identical representation, used by set() and dict(). "__eq__", # To reconstruct an object of the same type with operands or properties changed. "reconstruct", # Return whether this expression is spatially constant over each cell. "domains", "is_cellwise_constant", #"cell", #"domain", #"geometric_dimension", #"__str__", #"__repr__", #"signature_data", #"__repr__", ) # --- Global variables for collecting all types --- # A global counter of the number of typecodes assigned _ufl_num_typecodes_ = 1 # A global set of all handler names added _ufl_all_handler_names_ = set() # A global array of all Expr subclasses, indexed by typecode _ufl_all_classes_ = [] # A global dict mapping language_operator_name to the type it produces _ufl_language_operators_ = {} # List of all terminal modifier types _ufl_terminal_modifiers_ = [] # --- Mechanism for profiling object creation and deletion --- # A global array of the number of initialized objects for each typecode _ufl_obj_init_counts_ = [0] # A global array of the number of deleted objects for each typecode _ufl_obj_del_counts_ = [0] # Backup of default init and del _ufl_regular__init__ = __init__ _ufl_regular__del__ = __del__ def _ufl_profiling__init__(self): "Replacement constructor with object counting." Expr._ufl_regular__init__(self) Expr._ufl_obj_init_counts_[self._ufl_typecode_] += 1 def _ufl_profiling__del__(self): "Replacement destructor with object counting." Expr._ufl_regular__del__(self) Expr._ufl_obj_del_counts_[self._ufl_typecode_] -= 1 @staticmethod def ufl_enable_profiling(): "Turn on object counting mechanism and reset counts to zero." Expr.__init__ = Expr._ufl_profiling__init__ Expr.__del__ = Expr._ufl_profiling__del__ for i in range(len(Expr._ufl_obj_init_counts_)): Expr._ufl_obj_init_counts_[i] = 0 Expr._ufl_obj_del_counts_[i] = 0 @staticmethod def ufl_disable_profiling(): "Turn off object counting mechanism. Returns object init and del counts." Expr.__init__ = Expr._ufl_regular__init__ Expr.__del__ = Expr._ufl_regular__del__ return (Expr._ufl_obj_init_counts_, Expr._ufl_obj_del_counts_) #=== Abstract functions that must be implemented by subclasses === #--- Functions for reconstructing expression --- # All subclasses must implement reconstruct def reconstruct(self, *operands): "Return a new object of the same type with new operands." raise NotImplementedError(self.__class__.reconstruct) #--- Functions for expression tree traversal --- # All subclasses must implement operands def operands(self): "Return a sequence with all subtree nodes in expression tree." raise NotImplementedError(self.__class__.operands) #--- Functions for geometric properties of expression --- # All subclasses must implement domains if it is known def domains(self): # TODO: Is it better to use an external traversal algorithm for this? from ufl.geometry import extract_domains return extract_domains(self) # All subclasses must implement domain if it is known def domain(self): "Return the single unique domain this expression is defined on or throw an error." domains = self.domains() if len(domains) == 1: domain, = domains return domain elif domains: error("Found multiple domains, cannot return just one.") else: return None # All subclasses must implement cell if it is known def cell(self): # TODO: Deprecate this "Return the cell this expression is defined on." domain = self.domain() return domain.cell() if domain is not None else None # This function was introduced to clarify and # eventually reduce direct dependencies on cells. def geometric_dimension(self): "Return the geometric dimension this expression lives in." # TODO: Deprecate this, and use external analysis algorithm? for domain in self.domains(): return domain.geometric_dimension() error("Cannot get geometric dimension from an expression with no domains!") def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." raise NotImplementedError(self.__class__.is_cellwise_constant) #--- Functions for float evaluation --- def evaluate(self, x, mapping, component, index_values): """Evaluate expression at given coordinate with given values for terminals.""" error("Symbolic evaluation of %s not available." % self._ufl_class_.__name__) def _ufl_evaluate_scalar_(self): if self.ufl_shape or self.ufl_free_indices: raise TypeError("Cannot evaluate a nonscalar expression to a scalar value.") return self(()) # No known x def __float__(self): "Try to evaluate as scalar and cast to float." try: v = float(self._ufl_evaluate_scalar_()) except: v = NotImplemented return v def __bool__(self): "By default, all Expr are nonzero/False." return True def __nonzero__(self): "By default, all Expr are nonzero/False." return self.__bool__() @staticmethod def _ufl_coerce_(value): "Convert any value to a UFL type." # Quick skip for most types if isinstance(value, Expr): return value # Conversion from non-ufl types # (the _ufl_from_*_ functions are attached to Expr by ufl_type) ufl_from_type = "_ufl_from_{0}_".format(value.__class__.__name__) return getattr(Expr, ufl_from_type)(value) #if hasattr(Expr, ufl_from_type): # return getattr(Expr, ufl_from_type)(value) ## Fail gracefully if no valid type conversion found #raise TypeError("Cannot convert a {0.__class__.__name__} to UFL type.".format(value)) #--- Functions for shape and index handling --- def shape(self): "Return the tensor shape of the expression." return self.ufl_shape def rank(self): "Return the tensor rank of the expression." return len(self.ufl_shape) # All subclasses that can have indices must implement free_indices def free_indices(self): "Return a tuple with the free indices (unassigned) of the expression." raise NotImplementedError(self.__class__.free_indices) # All subclasses must implement index_dimensions def index_dimensions(self): """Return a dict with the free or repeated indices in the expression as keys and the dimensions of those indices as values.""" raise NotImplementedError(self.__class__.index_dimensions) #--- Special functions for string representations --- # All subclasses must implement signature_data def signature_data(self): "Return data that uniquely identifies form compiler relevant aspects of this object." raise NotImplementedError(self.__class__.signature_data) # All subclasses must implement __repr__ def __repr__(self): "Return string representation this object can be reconstructed from." raise NotImplementedError(self.__class__.__repr__) # All subclasses must implement __str__ def __str__(self): "Return pretty print string representation of this object." raise NotImplementedError(self.__class__.__str__) def _repr_latex_(self): from ufl.algorithms import ufl2latex return "$%s$" % ufl2latex(self) def _repr_png_(self): from IPython.lib.latextools import latex_to_png return latex_to_png(self._repr_latex_()) #--- Special functions used for processing expressions --- def __eq__(self, other): """Checks whether the two expressions are represented the exact same way. This does not check if the expressions are mathematically equal or equivalent! Used by sets and dicts.""" raise NotImplementedError(self.__class__.__eq__) def __len__(self): "Length of expression. Used for iteration over vector expressions." s = self.ufl_shape if len(s) == 1: return s[0] raise NotImplementedError("Cannot take length of non-vector expression.") def __iter__(self): "Iteration over vector expressions." for i in range(len(self)): yield self[i] def __floordiv__(self, other): "UFL does not support integer division." raise NotImplementedError(self.__class__.__floordiv__) def __pos__(self): "Unary + is a no-op." return self def __round__(self, n=None): "Round to nearest integer or to nearest nth decimal." return round(float(self), n) # Initializing traits here because Expr is not defined in the class declaration Expr._ufl_class_ = Expr Expr._ufl_all_handler_names_.add(Expr) Expr._ufl_all_classes_.append(Expr) ufl-1.6.0/ufl/core/multiindex.py000066400000000000000000000152641255567402100165750ustar00rootroot00000000000000"""This module defines the single index types and some internal index utilities.""" # Copyright (C) 2008-2014 Martin Sandve Alnes and Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from six.moves import xrange as range from ufl.log import error from ufl.assertions import ufl_assert from ufl.common import counted_init from ufl.core.ufl_type import ufl_type from ufl.core.terminal import Terminal class IndexBase(object): __slots__ = () def __init__(self): pass class FixedIndex(IndexBase): """UFL value: An index with a specific value assigned.""" __slots__ = ("_value", "_hash") _cache = {} def __getnewargs__(self): return (self._value,) def __new__(cls, value): self = FixedIndex._cache.get(value) if self is None: if not isinstance(value, int): error("Expecting integer value for fixed index.") self = IndexBase.__new__(cls) self._init(value) FixedIndex._cache[value] = self return self def _init(self, value): IndexBase.__init__(self) self._value = value self._hash = hash(("FixedIndex", self._value)) def __init__(self, value): pass def __hash__(self): return self._hash def __eq__(self, other): return isinstance(other, FixedIndex) and (self._value == other._value) def __int__(self): return self._value def __str__(self): return "%d" % self._value def __repr__(self): return "FixedIndex(%d)" % self._value class Index(IndexBase): """UFL value: An index with no value assigned. Used to represent free indices in Einstein indexing notation.""" __slots__ = ("_count",) _globalcount = 0 def __init__(self, count=None): IndexBase.__init__(self) counted_init(self, count, Index) def count(self): return self._count def __hash__(self): return hash(("Index", self._count)) def __eq__(self, other): return isinstance(other, Index) and (self._count == other._count) def __str__(self): c = str(self._count) if len(c) > 1: c = "{%s}" % c return "i_%s" % c def __repr__(self): return "Index(%d)" % self._count @ufl_type() class MultiIndex(Terminal): "Represents a sequence of indices, either fixed or free." __slots__ = ("_indices",) _cache = {} def __getnewargs__(self): return (self._indices,) def __new__(cls, indices): ufl_assert(isinstance(indices, tuple), "Expecting a tuple of indices.") if all(isinstance(ind, FixedIndex) for ind in indices): # Cache multiindices consisting of purely fixed indices (aka flyweight pattern) key = tuple(ind._value for ind in indices) self = MultiIndex._cache.get(key) if self is not None: return self self = Terminal.__new__(cls) MultiIndex._cache[key] = self else: # Create a new object if we have any free indices (too many combinations to cache) ufl_assert(all(isinstance(ind, IndexBase) for ind in indices), "Expecting only Index and FixedIndex objects.") self = Terminal.__new__(cls) # Initialize here instead of in __init__ to avoid overwriting self._indices from cached objects self._init(indices) return self def __init__(self, indices): pass def _init(self, indices): Terminal.__init__(self) self._indices = indices def indices(self): return self._indices def _ufl_compute_hash_(self): return hash(("MultiIndex",) + tuple(hash(ind) for ind in self._indices)) def __eq__(self, other): return isinstance(other, MultiIndex) and \ self._indices == other._indices def evaluate(self, x, mapping, component, index_values): # Build component from index values component = [] for i in self._indices: if isinstance(i, FixedIndex): component.append(i._value) elif isinstance(i, Index): component.append(index_values[i]) return tuple(component) @property def ufl_shape(self): error("Multiindex has no shape (it is not a tensor expression).") @property def ufl_free_indices(self): error("Multiindex has no free indices (it is not a tensor expression).") @property def ufl_index_dimensions(self): error("Multiindex has no free indices (it is not a tensor expression).") def is_cellwise_constant(self): error("Asking if a Multiindex is cellwise constant makes no sense (it is not a tensor expression).") #return True # Could also just return True, after all it doesn't change with the cell def domains(self): "Return tuple of domains related to this terminal object." return () # --- Adding multiindices --- def __add__(self, other): if isinstance(other, tuple): return MultiIndex(self._indices + other) elif isinstance(other, MultiIndex): return MultiIndex(self._indices + other._indices) return NotImplemented def __radd__(self, other): if isinstance(other, tuple): return MultiIndex(other + self._indices) elif isinstance(other, MultiIndex): return MultiIndex(other._indices + self._indices) return NotImplemented # --- String formatting --- def __str__(self): return ", ".join(str(i) for i in self._indices) def __repr__(self): return "MultiIndex(%r)" % (self._indices,) # --- Iteration protocol --- def __len__(self): return len(self._indices) def __getitem__(self, i): return self._indices[i] def __iter__(self): return iter(self._indices) def as_multi_index(ii, shape=None): if isinstance(ii, MultiIndex): return ii elif not isinstance(ii, tuple): ii = (ii,) return MultiIndex(ii) def indices(n): "UFL value: Return a tuple of n new Index objects." return tuple(Index() for i in range(n)) ufl-1.6.0/ufl/core/operator.py000066400000000000000000000051251255567402100162410ustar00rootroot00000000000000"Base class for all operators, i.e. non-terminal expr types." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2008 from six import iteritems from ufl.log import error from ufl.core.expr import Expr from ufl.core.ufl_type import ufl_type from ufl.core.multiindex import Index #--- Base class for operator objects --- @ufl_type(is_abstract=True, is_terminal=False) class Operator(Expr): __slots__ = ("ufl_operands",) def __init__(self, operands=None): Expr.__init__(self) # If operands is None, the type sets this itself. This is to get around # some tricky too-fancy __new__/__init__ design in algebra.py, for now. # It would be nicer to make the classes in algebra.py pass operands here. if operands is not None: self.ufl_operands = operands def reconstruct(self, *operands): "Return a new object of the same type with new operands." return self._ufl_class_(*operands) def signature_data(self): return self._ufl_typecode_ def _ufl_compute_hash_(self): "Compute a hash code for this expression. Used by sets and dicts." return hash((self._ufl_typecode_,) + tuple(hash(o) for o in self.ufl_operands)) def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." return all(o.is_cellwise_constant() for o in self.ufl_operands) # --- Transitional property getters --- def operands(self): return self.ufl_operands def free_indices(self): "Intermediate helper property getter to transition from .free_indices() to .ufl_free_indices." return tuple(Index(count=i) for i in self.ufl_free_indices) def index_dimensions(self): "Intermediate helper property getter to transition from .index_dimensions() to .ufl_index_dimensions." return { Index(count=i): d for i, d in zip(self.ufl_free_indices, self.ufl_index_dimensions) } ufl-1.6.0/ufl/core/terminal.py000066400000000000000000000074041255567402100162230ustar00rootroot00000000000000"""This module defines the Terminal class, the superclass for all types that are terminal nodes in the expression trees.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2008 from ufl.log import error, warning from ufl.assertions import ufl_assert from ufl.common import EmptyDict from ufl.common import counted_init from ufl.core.expr import Expr from ufl.core.ufl_type import ufl_type #--- Base class for terminal objects --- @ufl_type(is_abstract=True, is_terminal=True) class Terminal(Expr): "A terminal node in the UFL expression tree." __slots__ = () def __init__(self): Expr.__init__(self) def reconstruct(self, *operands): "Return self." if operands: error("Got call to reconstruct in a terminal with non-empty operands.") return self ufl_operands = () ufl_free_indices = () ufl_index_dimensions = () def operands(self): "A Terminal object never has operands." return () def free_indices(self): "A Terminal object never has free indices." return () def index_dimensions(self): "A Terminal object never has free indices." return EmptyDict def domains(self): "Return tuple of domains related to this terminal object." raise NotImplementedError("Missing implementation of domains().") def evaluate(self, x, mapping, component, index_values, derivatives=()): "Get self from mapping and return the component asked for." f = mapping.get(self) # No mapping, trying to evaluate self as a constant if f is None: try: f = float(self) if derivatives: f = 0.0 return f except: pass # If it has an ufl_evaluate function, call it if hasattr(self, 'ufl_evaluate'): return self.ufl_evaluate(x, component, derivatives) # Take component if any warning("Couldn't map '%s' to a float, returning ufl object without evaluation." % str(self)) f = self if component: f = f[component] return f # Found a callable in the mapping if callable(f): if derivatives: f = f(x, derivatives) else: f = f(x) else: if derivatives: return 0.0 # Take component if any (expecting nested tuple) for c in component: f = f[c] return f def signature_data(self, renumbering): "Default signature data for of terminals just return the repr string." return repr(self) def _ufl_compute_hash_(self): "Default hash of terminals just hash the repr string." return hash(repr(self)) def __eq__(self, other): "Default comparison of terminals just compare repr strings." return repr(self) == repr(other) #--- Subgroups of terminals --- @ufl_type(is_abstract=True) class FormArgument(Terminal): __slots__ = () def __init__(self): Terminal.__init__(self) ufl-1.6.0/ufl/core/ufl_type.py000066400000000000000000000344511255567402100162410ustar00rootroot00000000000000 from ufl.core.expr import Expr from ufl.core.compute_expr_hash import compute_expr_hash from ufl.common import camel2underscore, EmptyDict # Make UFL type coercion available under the as_ufl name #as_ufl = Expr._ufl_coerce_ def get_base_attr(cls, name): "Return first non-None attribute of given name among base classes." for base in cls.mro(): if hasattr(base, name): attr = getattr(base, name) if attr is not None: return attr return None def set_trait(cls, basename, value, inherit=False): """Assign a trait to class with namespacing _ufl_basename_ applied. If trait value is None, optionally inherit it from the closest base class that has it. """ name = "_ufl_" + basename + "_" if value is None and inherit: value = get_base_attr(cls, name) setattr(cls, name, value) def determine_num_ops(cls, num_ops, unop, binop, rbinop): # Try to determine num_ops from other traits or baseclass, # or require num_ops to be set for non-abstract classes # if it cannot be determined automatically if num_ops is not None: return num_ops elif cls._ufl_is_terminal_: return 0 elif unop: return 1 elif binop or rbinop: return 2 else: # Determine from base class return get_base_attr(cls, "_ufl_num_ops_") def check_is_terminal_consistency(cls): "Check for consistency in is_terminal trait among superclasses." if cls._ufl_is_terminal_ is None: msg = ("Class {0.__name__} has not specified the is_terminal trait." + " Did you forget to inherit from Terminal or Operator?") raise TypeError(msg.format(cls)) base_is_terminal = get_base_attr(cls, "_ufl_is_terminal_") if base_is_terminal is not None and cls._ufl_is_terminal_ != base_is_terminal: msg = ("Conflicting given and automatic 'is_terminal' trait for class {0.__name__}." + " Check if you meant to inherit from Terminal or Operator.") raise TypeError(msg.format(cls)) def check_abstract_trait_consistency(cls): "Check that the first base classes up to Expr are other UFL types." for base in cls.mro(): if base is Expr: break if not issubclass(base, Expr) and base._ufl_is_abstract_: msg = ("Base class {0.__name__} of class {1.__name__} " "is not an abstract subclass of {2.__name__}.") raise TypeError(msg.format(base, cls, Expr)) def check_has_slots(cls): "Check if type has __slots__ unless it is marked as exception with _ufl_noslots_" if "_ufl_noslots_" in cls.__dict__: return if "__slots__" not in cls.__dict__: msg = ("Class {0.__name__} is missing the __slots__ " "attribute and is not marked with _ufl_noslots_.") raise TypeError(msg.format(cls)) # Check base classes for __slots__ as well, skipping object which is the last one for base in cls.mro()[1:-1]: if "__slots__" not in base.__dict__: msg = ("Class {0.__name__} is has a base class " "{1.__name__} with __slots__ missing.") raise TypeError(msg.format(cls, base)) def check_type_traits_consistency(cls): "Execute a variety of consistency checks on the ufl type traits." # Check for consistency in global type collection sizes assert Expr._ufl_num_typecodes_ == len(Expr._ufl_all_handler_names_) assert Expr._ufl_num_typecodes_ == len(Expr._ufl_all_classes_) assert Expr._ufl_num_typecodes_ == len(Expr._ufl_obj_init_counts_) assert Expr._ufl_num_typecodes_ == len(Expr._ufl_obj_del_counts_) # Check that non-abstract types always specify num_ops if not cls._ufl_is_abstract_: if cls._ufl_num_ops_ is None: msg = "Class {0.__name__} has not specified num_ops." raise TypeError(msg.format(cls)) # Check for non-abstract types that num_ops has the right type if not cls._ufl_is_abstract_: if not (isinstance(cls._ufl_num_ops_, int) or cls._ufl_num_ops_ == "varying"): msg = 'Class {0.__name__} has invalid num_ops value {1} (integer or "varying").' raise TypeError(msg.format(cls, cls._ufl_num_ops_)) # Check that num_ops is not set to nonzero for a terminal if cls._ufl_is_terminal_ and cls._ufl_num_ops_ != 0: msg = "Class {0.__name__} has num_ops > 0 but is terminal." raise TypeError(msg.format(cls)) # Check that a non-scalar type doesn't have a scalar base class. if not cls._ufl_is_scalar_: if get_base_attr(cls, "_ufl_is_scalar_"): msg = "Non-scalar class {0.__name__} is has a scalar base class." raise TypeError(msg.format(cls)) def check_implements_required_methods(cls): """Check if type implements the required methods.""" if not cls._ufl_is_abstract_: for attr in Expr._ufl_required_methods_: if not hasattr(cls, attr): msg = "Class {0.__name__} has no {1} method." raise TypeError(msg.format(cls, attr)) elif not callable(getattr(cls, attr)): msg = "Required method {1} of class {0.__name__} is not callable." raise TypeError(msg.format(cls, attr)) def check_implements_required_properties(cls): "Check if type implements the required properties." if not cls._ufl_is_abstract_: for attr in Expr._ufl_required_properties_: if not hasattr(cls, attr): msg = "Class {0.__name__} has no {1} property." raise TypeError(msg.format(cls, attr)) elif callable(getattr(cls, attr)): msg = "Required property {1} of class {0.__name__} is a callable method." raise TypeError(msg.format(cls, attr)) def attach_implementations_of_indexing_interface(cls, inherit_shape_from_operand, inherit_indices_from_operand): # Scalar or index-free? Then we can simplify the implementation of tensor # properties by attaching them here. if cls._ufl_is_scalar_: # New interface cls.ufl_shape = () # Legacy interface def _scalar_shape(self): return () cls.shape = _scalar_shape if cls._ufl_is_scalar_ or cls._ufl_is_index_free_: # New interface cls.ufl_free_indices = () cls.ufl_index_dimensions = () # Legacy interface def _empty_free_indices(self): return () def _empty_index_dimensions(self): return EmptyDict cls.free_indices = _empty_free_indices cls.index_dimensions = _empty_index_dimensions # Automate direct inheriting of shape and indices from one of the operands. # This simplifies refactoring because a lot of types do this. if inherit_shape_from_operand is not None: def _inherited_ufl_shape(self): return self.ufl_operands[inherit_shape_from_operand].ufl_shape # New interface cls.ufl_shape = property(_inherited_ufl_shape) # Legacy interface cls.shape = _inherited_ufl_shape if inherit_indices_from_operand is not None: # New interface def _inherited_ufl_free_indices(self): return self.ufl_operands[inherit_indices_from_operand].ufl_free_indices def _inherited_ufl_index_dimensions(self): return self.ufl_operands[inherit_indices_from_operand].ufl_index_dimensions cls.ufl_free_indices = property(_inherited_ufl_free_indices) cls.ufl_index_dimensions = property(_inherited_ufl_index_dimensions) # Legacy interface def _inherited_legacy_free_indices(self): return self.ufl_operands[inherit_indices_from_operand].free_indices() def _inherited_legacy_index_dimensions(self): return self.ufl_operands[inherit_indices_from_operand].index_dimensions() cls.free_indices = _inherited_legacy_free_indices cls.index_dimensions = _inherited_legacy_index_dimensions def update_global_expr_attributes(cls): "Update global Expr attributes, mainly by adding cls to global collections of ufl types." Expr._ufl_all_classes_.append(cls) Expr._ufl_all_handler_names_.add(cls._ufl_handler_name_) if cls._ufl_is_terminal_modifier_: Expr._ufl_terminal_modifiers_.append(cls) # Add to collection of language operators. # This collection is used later to populate the official language namespace. # TODO: I don't think this functionality is fully completed, check it out later. if not cls._ufl_is_abstract_ and hasattr(cls, "_ufl_function_"): cls._ufl_function_.__func__.__doc__ = cls.__doc__ Expr._ufl_language_operators_[cls._ufl_handler_name_] = cls._ufl_function_ # Append space for counting object creation and destriction of this this type. Expr._ufl_obj_init_counts_.append(0) Expr._ufl_obj_del_counts_.append(0) def ufl_type(is_abstract=False, is_terminal=None, is_scalar=False, is_index_free=False, is_shaping=False, is_literal=False, is_terminal_modifier=False, is_in_reference_frame=False, is_restriction=False, is_evaluation=False, is_differential=None, use_default_hash=True, num_ops=None, inherit_shape_from_operand=None, inherit_indices_from_operand=None, wraps_type=None, unop=None, binop=None, rbinop=None ): """This decorator is to be applied to every subclass in the UFL Expr hierarchy. This decorator contains a number of checks that are intended to enforce uniform behaviour across UFL types. The rationale behind the checks and the meaning of the optional arguments should be sufficiently documented in the source code below. """ def _ufl_type_decorator_(cls): # Determine integer typecode by oncrementally counting all types typecode = Expr._ufl_num_typecodes_ Expr._ufl_num_typecodes_ += 1 # Determine handler name by a mapping from "TypeName" to "type_name" handler_name = camel2underscore(cls.__name__) # is_scalar implies is_index_free if is_scalar: _is_index_free = True else: _is_index_free = is_index_free # Store type traits cls._ufl_class_ = cls set_trait(cls, "handler_name", handler_name, inherit=False) set_trait(cls, "typecode", typecode, inherit=False) set_trait(cls, "is_abstract", is_abstract, inherit=False) set_trait(cls, "is_terminal", is_terminal, inherit=True) set_trait(cls, "is_literal", is_literal, inherit=True) set_trait(cls, "is_terminal_modifier", is_terminal_modifier, inherit=True) set_trait(cls, "is_shaping", is_shaping, inherit=True) set_trait(cls, "is_in_reference_frame", is_in_reference_frame, inherit=True) set_trait(cls, "is_restriction", is_restriction, inherit=True) set_trait(cls, "is_evaluation", is_evaluation, inherit=True) set_trait(cls, "is_differential", is_differential, inherit=True) set_trait(cls, "is_scalar", is_scalar, inherit=True) set_trait(cls, "is_index_free", _is_index_free, inherit=True) # Number of operands can often be determined automatically _num_ops = determine_num_ops(cls, num_ops, unop, binop, rbinop) set_trait(cls, "num_ops", _num_ops) # Attach builtin type wrappers to Expr """# These are currently handled in the as_ufl implementation in constantvalue.py if wraps_type is not None: if not isinstance(wraps_type, type): msg = "Expecting a type, not a {0.__name__} for the wraps_type argument in definition of {1.__name__}." raise TypeError(msg.format(type(wraps_type), cls)) def _ufl_from_type_(value): return cls(value) from_type_name = "_ufl_from_{0}_".format(wraps_type.__name__) setattr(Expr, from_type_name, staticmethod(_ufl_from_type_)) """ # Attach special function to Expr. # Avoids the circular dependency problem of making # Expr.__foo__ return a Foo that is a subclass of Expr. """# These are currently attached in exproperators.py if unop: def _ufl_expr_unop_(self): return cls(self) setattr(Expr, unop, _ufl_expr_unop_) if binop: def _ufl_expr_binop_(self, other): try: other = Expr._ufl_coerce_(other) except: return NotImplemented return cls(self, other) setattr(Expr, binop, _ufl_expr_binop_) if rbinop: def _ufl_expr_rbinop_(self, other): try: other = Expr._ufl_coerce_(other) except: return NotImplemented return cls(other, self) setattr(Expr, rbinop, _ufl_expr_rbinop_) """ # Make sure every non-abstract class has its own __hash__ and __eq__. # Python 3 will set __hash__ to None if cls has __eq__, but we've # implemented it in a separate function and want to inherit/use that # for all types. Allow overriding by setting use_default_hash=False. if use_default_hash: cls.__hash__ = compute_expr_hash # NB! This function conditionally adds some methods to the class! # This approach significantly reduces the amount of small functions to # implement across all the types but of course it's a bit more opaque. attach_implementations_of_indexing_interface(cls, inherit_shape_from_operand, inherit_indices_from_operand) # Update Expr update_global_expr_attributes(cls) # Apply a range of consistency checks to detect bugs in type # implementations that Python doesn't check for us, including # some checks that a static language compiler would do for us check_abstract_trait_consistency(cls) check_has_slots(cls) check_is_terminal_consistency(cls) check_implements_required_methods(cls) check_implements_required_properties(cls) check_type_traits_consistency(cls) return cls return _ufl_type_decorator_ ufl-1.6.0/ufl/corealg/000077500000000000000000000000001255567402100145155ustar00rootroot00000000000000ufl-1.6.0/ufl/corealg/__init__.py000066400000000000000000000000001255567402100166140ustar00rootroot00000000000000ufl-1.6.0/ufl/corealg/map_dag.py000066400000000000000000000055421255567402100164650ustar00rootroot00000000000000"""Basic algorithms for applying functions to subexpressions.""" # Copyright (C) 2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from ufl.core.expr import Expr from ufl.corealg.traversal import post_traversal, cutoff_post_traversal from ufl.corealg.multifunction import MultiFunction def map_expr_dag(function, expression, compress=True): """Apply a function to each subexpression node in expression dag. If compress is True (default), the output object from the function is cached in a dict and reused such that the resulting expression dag does not contain duplicate objects. Returns the result of the final function call. """ # Temporary data structures vcache = {} rcache = {} results = [] # Build mapping typecode:bool, for which types to skip the subtree of if isinstance(function, MultiFunction): cutoff_types = function._is_cutoff_type else: # Regular function: no skipping supported cutoff_types = [False]*Expr._ufl_num_typecodes_ # Pick faster traversal algorithm if we have no cutoffs if any(cutoff_types): traversal = lambda expression: cutoff_post_traversal(expression, cutoff_types) else: traversal = lambda expression: post_traversal(expression) # Iterate over all subexpression nodes, child before parent for v in traversal(expression): # Check if v is in vcache (to be able to skip transformations) i = vcache.get(v) # Cache hit: skip transformation if i is not None: continue # Cache miss: Get transformed operands, then apply transformation if cutoff_types[v._ufl_typecode_]: r = function(v) else: rops = [results[vcache[u]] for u in v.ufl_operands] r = function(v, *rops) # Optionally check if r is in rcache (to be able to keep representation of result compact) i = rcache.get(r) if compress else None if i is None: # Cache miss: Assign result index and store in results list i = len(results) results.append(r) # Store in rcache if compress: rcache[r] = i # Store in vcache vcache[v] = i return results[i] ufl-1.6.0/ufl/corealg/multifunction.py000066400000000000000000000072121255567402100177710ustar00rootroot00000000000000"""Base class for multifunctions with UFL Expr type dispatch.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from inspect import getargspec from ufl.log import error from ufl.core.expr import Expr def get_num_args(function): insp = getargspec(function) return len(insp[0]) + int(insp[1] is not None) class MultiFunction(object): """Base class for collections of nonrecursive expression node handlers. Subclass this (remember to call the __init__ method of this class), and implement handler functions for each Expr type, using the lower case handler name of the type (exprtype._ufl_handler_name_). This class is optimized for efficient type based dispatch in the __call__ operator via typecode based lookup of the handler function bound to the algorithm object. Of course function call overhead of Python still applies. """ _handlers_cache = {} def __init__(self): # Analyse class properties and cache handler data the # first time this is run for a particular class # (cached for each algorithm for performance) algorithm_class = type(self) cache_data = MultiFunction._handlers_cache.get(algorithm_class) if not cache_data: cache_data = [None]*len(Expr._ufl_all_classes_) # Iterate over the inheritance chain for each Expr subclass # (NB! This assumes that all UFL classes inherits from # a single Expr subclass and that the first superclass # is always from the UFL Expr hierarchy!) for classobject in Expr._ufl_all_classes_: for c in classobject.mro(): # Register classobject with handler for the first encountered superclass name = c._ufl_handler_name_ if hasattr(self, name): cache_data[classobject._ufl_typecode_] = name break MultiFunction._handlers_cache[algorithm_class] = cache_data # Build handler list for this particular class # (get functions bound to self, these cannot be cached) self._handlers = [getattr(self, name) for name in cache_data] self._is_cutoff_type = [get_num_args(h) == 2 for h in self._handlers] def __call__(self, o, *args): "Delegate to handler function based on typecode of first argument." return self._handlers[o._ufl_typecode_](o, *args) def undefined(self, o, *args): "Trigger error for types with missing handlers." error("No handler defined for %s." % o._ufl_class_.__name__) def reuse_if_untouched(self, o, *ops): """Reuse object if operands are the same objects. Use in your own subclass by setting e.g. expr = MultiFunction.reuse_if_untouched as a default rule. """ if all(a is b for a, b in zip(o.ufl_operands, ops)): return o else: return o.reconstruct(*ops) # Set default behaviour for any Expr as undefined expr = undefined ufl-1.6.0/ufl/corealg/traversal.py000066400000000000000000000115211255567402100170720ustar00rootroot00000000000000"""Various expression traversal utilities. The algorithms here are non-recursive, which is faster than recursion by a factor 10 or so because of the function call overhead. """ # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # This limits the _depth_ of expression trees _recursion_limit_ = 6400 # should be enough for everyone def pre_traversal(expr): """Yields o for each tree node o in expr, parent before child.""" stack = [None]*_recursion_limit_ stack[0] = expr stacksize = 1 while stacksize > 0: stacksize -= 1 expr = stack[stacksize] yield expr for op in expr.ufl_operands: stack[stacksize] = op stacksize += 1 def post_traversal(expr): """Yields o for each node o in expr, child before parent.""" stack = [None]*_recursion_limit_ stacksize = 0 ops = expr.ufl_operands stack[stacksize] = [expr, ops, len(ops)] stacksize += 1 while stacksize > 0: entry = stack[stacksize - 1] if entry[2] == 0: yield entry[0] stacksize -= 1 else: entry[2] -= 1 o = entry[1][entry[2]] oops = o.ufl_operands stack[stacksize] = [o, oops, len(oops)] stacksize += 1 def cutoff_post_traversal(expr, cutofftypes): """Yields o for each node o in expr, child before parent, but skipping subtrees of the cutofftypes.""" stack = [None]*_recursion_limit_ stacksize = 0 ops = expr.ufl_operands stack[stacksize] = [expr, ops, len(ops)] stacksize += 1 while stacksize > 0: entry = stack[stacksize - 1] expr = entry[0] if entry[2] == 0 or cutofftypes[expr._ufl_typecode_]: yield expr stacksize -= 1 else: entry[2] -= 1 o = entry[1][entry[2]] if cutofftypes[expr._ufl_typecode_]: oops = () else: oops = o.ufl_operands stack[stacksize] = [o, oops, len(oops)] stacksize += 1 def unique_pre_traversal(expr, visited=None): """Yields o for each tree node o in expr, parent before child. This version only visits each node once! """ stack = [None]*_recursion_limit_ stack[0] = expr stacksize = 1 visited = visited or set() while stacksize > 0: stacksize -= 1 expr = stack[stacksize] if expr not in visited: visited.add(expr) yield expr for op in expr.ufl_operands: stack[stacksize] = op stacksize += 1 def unique_post_traversal(expr, visited=None): """Yields o for each node o in expr, child before parent. Never visits a node twice.""" stack = [None]*_recursion_limit_ stack[0] = (expr, list(expr.ufl_operands)) stacksize = 1 visited = visited or set() while stacksize > 0: expr, ops = stack[stacksize - 1] for i, o in enumerate(ops): if o is not None and o not in visited: stack[stacksize] = (o, list(o.ufl_operands)) stacksize += 1 ops[i] = None break else: yield expr visited.add(expr) stacksize -= 1 def traverse_terminals(expr): "Iterate over all terminal objects in expression, including duplicates." stack = [None]*_recursion_limit_ stack[0] = expr stacksize = 1 while stacksize > 0: stacksize -= 1 expr = stack[stacksize] if expr._ufl_is_terminal_: yield expr else: for op in expr.ufl_operands: stack[stacksize] = op stacksize += 1 def traverse_unique_terminals(expr): "Iterate over all terminal objects in expression, not including duplicates." stack = [None]*_recursion_limit_ stack[0] = expr stacksize = 1 visited = set() while stacksize > 0: stacksize -= 1 expr = stack[stacksize] if expr not in visited: visited.add(expr) if expr._ufl_is_terminal_: yield expr else: for op in expr.ufl_operands: stack[stacksize] = op stacksize += 1 ufl-1.6.0/ufl/differentiation.py000066400000000000000000000342571255567402100166400ustar00rootroot00000000000000"Differential operators." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2009. from ufl.log import warning, error from ufl.assertions import ufl_assert from ufl.common import subdict, mergedicts, EmptyDict from ufl.core.expr import Expr from ufl.core.terminal import Terminal from ufl.core.operator import Operator from ufl.exprcontainers import ExprList, ExprMapping from ufl.constantvalue import Zero from ufl.coefficient import Coefficient from ufl.core.multiindex import Index, FixedIndex, MultiIndex from ufl.indexed import Indexed from ufl.variable import Variable from ufl.precedence import parstr from ufl.core.ufl_type import ufl_type #--- Basic differentiation objects --- @ufl_type(is_abstract=True, is_differential=True) class Derivative(Operator): "Base class for all derivative types." __slots__ = () def __init__(self, operands): Operator.__init__(self, operands) @ufl_type(num_ops=4, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class CoefficientDerivative(Derivative): """Derivative of the integrand of a form w.r.t. the degrees of freedom in a discrete Coefficient.""" __slots__ = () def __new__(cls, integrand, coefficients, arguments, coefficient_derivatives): ufl_assert(isinstance(coefficients, ExprList), "Expecting ExprList instance with Coefficients.") ufl_assert(isinstance(arguments, ExprList), "Expecting ExprList instance with Arguments.") ufl_assert(isinstance(coefficient_derivatives, ExprMapping), "Expecting ExprMapping for coefficient derivatives.") if isinstance(integrand, Zero): return integrand return Derivative.__new__(cls) def __init__(self, integrand, coefficients, arguments, coefficient_derivatives): if not isinstance(coefficient_derivatives, ExprMapping): coefficient_derivatives = ExprMapping(coefficient_derivatives) Derivative.__init__(self, (integrand, coefficients, arguments, coefficient_derivatives)) def __str__(self): return "d/dfj { %s }, with fh=%s, dfh/dfj = %s, and coefficient derivatives %s"\ % (self.ufl_operands[0], self.ufl_operands[1], self.ufl_operands[2], self.ufl_operands[3]) def __repr__(self): return "CoefficientDerivative(%r, %r, %r, %r)"\ % (self.ufl_operands[0], self.ufl_operands[1], self.ufl_operands[2], self.ufl_operands[3]) @ufl_type(num_ops=2) class VariableDerivative(Derivative): __slots__ = ("ufl_shape", "ufl_free_indices", "ufl_index_dimensions",) def __new__(cls, f, v): # Checks ufl_assert(isinstance(f, Expr), "Expecting an Expr in VariableDerivative.") ufl_assert(isinstance(v, (Variable, Coefficient)), "Expecting a Variable in VariableDerivative.") ufl_assert(not v.ufl_free_indices, "Differentiation variable cannot have free indices.") # Simplification # Return zero if expression is trivially independent of variable if f._ufl_is_terminal_ and f != v: return Zero(f.ufl_shape + v.ufl_shape, f.ufl_free_indices, f.ufl_index_dimensions) # Construction return Derivative.__new__(cls) def __init__(self, f, v): Derivative.__init__(self, (f, v)) self.ufl_free_indices = f.ufl_free_indices self.ufl_index_dimensions = f.ufl_index_dimensions self.ufl_shape = f.ufl_shape + v.ufl_shape def __str__(self): if isinstance(self.ufl_operands[0], Terminal): return "d%s/d[%s]" % (self.ufl_operands[0], self.ufl_operands[1]) return "d/d[%s] %s" % (self.ufl_operands[1], parstr(self.ufl_operands[0], self)) def __repr__(self): return "VariableDerivative(%r, %r)" % (self.ufl_operands[0], self.ufl_operands[1]) #--- Compound differentiation objects --- @ufl_type(is_abstract=True) class CompoundDerivative(Derivative): "Base class for all compound derivative types." __slots__ = () def __init__(self, operands): Derivative.__init__(self, operands) @ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True) class Grad(CompoundDerivative): __slots__ = ("_dim",) def __new__(cls, f): # Return zero if expression is trivially constant if f.is_cellwise_constant(): dim = f.geometric_dimension() return Zero(f.ufl_shape + (dim,), f.ufl_free_indices, f.ufl_index_dimensions) return CompoundDerivative.__new__(cls) def __init__(self, f): CompoundDerivative.__init__(self, (f,)) self._dim = f.geometric_dimension() def reconstruct(self, op): "Return a new object of the same type with new operands." if op.is_cellwise_constant(): ufl_assert(op.ufl_shape == self.ufl_operands[0].ufl_shape, "Operand shape mismatch in Grad reconstruct.") ufl_assert(self.ufl_operands[0].ufl_free_indices == op.ufl_free_indices, "Free index mismatch in Grad reconstruct.") return Zero(self.ufl_shape, self.ufl_free_indices, self.ufl_index_dimensions) return self._ufl_class_(op) def evaluate(self, x, mapping, component, index_values, derivatives=()): "Get child from mapping and return the component asked for." r = len(component) component, i = component[:-1], component[-1] derivatives = derivatives + (i,) result = self.ufl_operands[0].evaluate(x, mapping, component, index_values, derivatives=derivatives) return result @property def ufl_shape(self): return self.ufl_operands[0].ufl_shape + (self._dim,) def __str__(self): return "grad(%s)" % self.ufl_operands[0] def __repr__(self): return "Grad(%r)" % self.ufl_operands[0] @ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True, is_in_reference_frame=True) class ReferenceGrad(CompoundDerivative): __slots__ = ("_dim",) def __new__(cls, f): # Return zero if expression is trivially constant if f.is_cellwise_constant(): dim = f.domain().topological_dimension() return Zero(f.ufl_shape + (dim,), f.ufl_free_indices, f.ufl_index_dimensions) return CompoundDerivative.__new__(cls) def __init__(self, f): CompoundDerivative.__init__(self, (f,)) domain = f.domain() dim = domain.topological_dimension() self._dim = dim def reconstruct(self, op): "Return a new object of the same type with new operands." if op.is_cellwise_constant(): ufl_assert(op.ufl_shape == self.ufl_operands[0].ufl_shape, "Operand shape mismatch in ReferenceGrad reconstruct.") ufl_assert(self.ufl_operands[0].ufl_free_indices == op.ufl_free_indices, "Free index mismatch in ReferenceGrad reconstruct.") return Zero(self.ufl_shape, self.ufl_free_indices, self.ufl_index_dimensions) return self._ufl_class_(op) def evaluate(self, x, mapping, component, index_values, derivatives=()): "Get child from mapping and return the component asked for." r = len(component) component, i = component[:-1], component[-1] derivatives = derivatives + (i,) result = self.ufl_operands[0].evaluate(x, mapping, component, index_values, derivatives=derivatives) return result @property def ufl_shape(self): return self.ufl_operands[0].ufl_shape + (self._dim,) def __str__(self): return "reference_grad(%s)" % self.ufl_operands[0] def __repr__(self): return "ReferenceGrad(%r)" % self.ufl_operands[0] @ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True) class Div(CompoundDerivative): __slots__ = () def __new__(cls, f): ufl_assert(not f.ufl_free_indices, "Free indices in the divergence argument is not allowed.") # Return zero if expression is trivially constant if f.is_cellwise_constant(): return Zero(f.ufl_shape[:-1]) # No free indices asserted above return CompoundDerivative.__new__(cls) def __init__(self, f): CompoundDerivative.__init__(self, (f,)) @property def ufl_shape(self): return self.ufl_operands[0].ufl_shape[:-1] def __str__(self): return "div(%s)" % self.ufl_operands[0] def __repr__(self): return "Div(%r)" % self.ufl_operands[0] @ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True, is_in_reference_frame=True) class ReferenceDiv(CompoundDerivative): __slots__ = () def __new__(cls, f): ufl_assert(not f.ufl_free_indices, "Free indices in the divergence argument is not allowed.") # Return zero if expression is trivially constant if f.is_cellwise_constant(): return Zero(f.ufl_shape[:-1]) # No free indices asserted above return CompoundDerivative.__new__(cls) def __init__(self, f): CompoundDerivative.__init__(self, (f,)) @property def ufl_shape(self): return self.ufl_operands[0].ufl_shape[:-1] def __str__(self): return "reference_div(%s)" % self.ufl_operands[0] def __repr__(self): return "ReferenceDiv(%r)" % self.ufl_operands[0] @ufl_type(num_ops=1, inherit_indices_from_operand=0) class NablaGrad(CompoundDerivative): __slots__ = ("_dim",) def __new__(cls, f): # Return zero if expression is trivially constant if f.is_cellwise_constant(): dim = f.geometric_dimension() return Zero((dim,) + f.ufl_shape, f.ufl_free_indices, f.ufl_index_dimensions) return CompoundDerivative.__new__(cls) def __init__(self, f): CompoundDerivative.__init__(self, (f,)) self._dim = f.geometric_dimension() def reconstruct(self, op): "Return a new object of the same type with new operands." if op.is_cellwise_constant(): ufl_assert(op.ufl_shape == self.ufl_operands[0].ufl_shape, "Operand shape mismatch in NablaGrad reconstruct.") ufl_assert(self.ufl_operands[0].ufl_free_indices == op.ufl_free_indices, "Free index mismatch in NablaGrad reconstruct.") return Zero(self.ufl_shape, self.ufl_free_indices, self.ufl_index_dimensions) return self._ufl_class_(op) @property def ufl_shape(self): return (self._dim,) + self.ufl_operands[0].ufl_shape def __str__(self): return "nabla_grad(%s)" % self.ufl_operands[0] def __repr__(self): return "NablaGrad(%r)" % self.ufl_operands[0] @ufl_type(num_ops=1, inherit_indices_from_operand=0) class NablaDiv(CompoundDerivative): __slots__ = () def __new__(cls, f): ufl_assert(not f.ufl_free_indices, "Free indices in the divergence argument is not allowed.") # Return zero if expression is trivially constant if f.is_cellwise_constant(): return Zero(f.ufl_shape[1:]) # No free indices asserted above return CompoundDerivative.__new__(cls) def __init__(self, f): CompoundDerivative.__init__(self, (f,)) @property def ufl_shape(self): return self.ufl_operands[0].ufl_shape[1:] def __str__(self): return "nabla_div(%s)" % self.ufl_operands[0] def __repr__(self): return "NablaDiv(%r)" % self.ufl_operands[0] _curl_shapes = { (): (2,), (2,): (), (3,): (3,) } @ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True) class Curl(CompoundDerivative): __slots__ = ("ufl_shape",) def __new__(cls, f): # Validate input sh = f.ufl_shape ufl_assert(f.ufl_shape in ((), (2,), (3,)), "Expecting a scalar, 2D vector or 3D vector.") ufl_assert(not f.ufl_free_indices, "Free indices in the curl argument is not allowed.") # Return zero if expression is trivially constant if f.is_cellwise_constant(): sh = { (): (2,), (2,): (), (3,): (3,) }[sh] return Zero(sh) # No free indices asserted above return CompoundDerivative.__new__(cls) def __init__(self, f): global _curl_shapes CompoundDerivative.__init__(self, (f,)) self.ufl_shape = _curl_shapes[f.ufl_shape] def __str__(self): return "curl(%s)" % self.ufl_operands[0] def __repr__(self): return "Curl(%r)" % self.ufl_operands[0] @ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True, is_in_reference_frame=True) class ReferenceCurl(CompoundDerivative): __slots__ = ("ufl_shape",) def __new__(cls, f): # Validate input sh = f.ufl_shape ufl_assert(f.ufl_shape in ((), (2,), (3,)), "Expecting a scalar, 2D vector or 3D vector.") ufl_assert(not f.ufl_free_indices, "Free indices in the curl argument is not allowed.") # Return zero if expression is trivially constant if f.is_cellwise_constant(): sh = { (): (2,), (2,): (), (3,): (3,) }[sh] return Zero(sh) # No free indices asserted above return CompoundDerivative.__new__(cls) def __init__(self, f): global _curl_shapes CompoundDerivative.__init__(self, (f,)) self.ufl_shape = _curl_shapes[f.ufl_shape] def __str__(self): return "reference_curl(%s)" % self.ufl_operands[0] def __repr__(self): return "ReferenceCurl(%r)" % self.ufl_operands[0] ufl-1.6.0/ufl/domain.py000066400000000000000000000417671255567402100147410ustar00rootroot00000000000000"Types for representing a geometric domain." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2009. # Modified by Kristian B. Oelgaard, 2009 # Modified by Marie E. Rognes 2012 from collections import defaultdict from six import iteritems from ufl.corealg.traversal import traverse_unique_terminals from ufl.log import warning, error, deprecate from ufl.assertions import ufl_assert from ufl.common import istr, EmptyDict from ufl.core.terminal import Terminal from ufl.protocols import id_or_none from ufl.cell import as_cell, affine_cells, Cell, ProductCell class Domain(object): """Symbolic representation of a geometrical domain. Used in the definition of geometric terminal expressions, finite element spaces, and integration measures. Takes a single positional argument which is either the cell of the underlying mesh D = Domain(triangle) or the coordinate field which is a vector valued Coefficient. P2 = VectorElement("CG", D, 2) x = Coefficient(P2) E = Domain(x) With the cell variant of the constructor, an optional label can be passed to distinguish two domains from each other. Da = Domain(cell, label="a") Db = Domain(cell, label="b") an optional data argument can also be passed, for integration with problem solver environments (e.g. dolfin), this is typically the underlying mesh. Da = Domain(cell, label="a", data=mesha) Db = Domain(cell, label="b", data=meshb) """ __slots__ = ( "_geometric_dimension", "_topological_dimension", "_cell", "_coordinates", "_label", "_data", ) def __init__(self, *args, **kwargs): # Parse positional argument, either a Cell or a Coefficient ufl_assert(len(args) == 1, "Only one positional argument accepted. See Domain docstring.") arg, = args # To avoid circular dependencies... from ufl.coefficient import Coefficient if isinstance(arg, Cell): # Allow keyword arguments for label or data self._coordinates = None self._cell = arg self._label = kwargs.pop("label", None) self._data = kwargs.pop("data", None) elif isinstance(arg, Coefficient): # Disallow additional label and data, get from underlying 'flat domain' self._coordinates = arg flat_domain = arg.domain() self._cell = flat_domain.cell() self._label = flat_domain.label() self._data = flat_domain.data() # Get geometric dimension from self._coordinates shape gdim, = self._coordinates.ufl_shape if gdim != self._cell.geometric_dimension(): warning("Using geometric dimension from coordinates!") self._cell = Cell(self._cell.cellname(), gdim) #ufl_assert(self._coordinates.ufl_shape == (self._cell.geometric_dimension(),), # "Shape of coordinates %s does not match geometric dimension %d of cell." %\ # (self._coordinates.ufl_shape, self._cell.geometric_dimension())) else: ufl_error("Invalid first argument to Domain.") # Now we should have a Cell or something went wrong ufl_assert(isinstance(self._cell, Cell), "Failed to construct a Cell from input arguments.") self._geometric_dimension = self._cell.geometric_dimension() self._topological_dimension = self._cell.topological_dimension() # Sanity checks ufl_assert(isinstance(self._geometric_dimension, int), "Expecting integer geometric dimension.") ufl_assert(isinstance(self._topological_dimension, int), "Expecting integer topological dimension.") ufl_assert(self._topological_dimension <= self._geometric_dimension, "Topological dimension cannot be greater than geometric dimension.") ufl_assert(self._topological_dimension >= 0, "Topological dimension must be non-negative.") if self._coordinates is not None: ufl_assert(isinstance(self._coordinates, Coefficient), "Expecting None or Coefficient for coordinates.") ufl_assert(self._coordinates.domain().coordinates() is None, "Coordinates must be defined on a domain without coordinates of its own.") ufl_assert(self._label is None or isinstance(self._label, str), "Expecting None or str for label.") ufl_assert(self._data is None or hasattr(self._data, "ufl_id"), "Expecting data object to implement ufl_id().") # Check that we didn't get any arguments that we havent interpreted ufl_assert(not kwargs, "Got unused keyword arguments %s" % ', '.join(sorted(kwargs))) def reconstruct(self, cell=None, coordinates=None, label=None, data=None): "Create a new Domain object with possibly changed label or data." if coordinates is None: if cell is None: cell = self.cell() if label is None: label = self.label() if data is None: data = self.data() return Domain(cell, label=label, data=data) else: ufl_assert(all((cell is None, label is None, data is None)), "No other arguments allowed with coordinates.") return Domain(coordinates) def geometric_dimension(self): "Return the dimension of the space this domain is embedded in." return self._geometric_dimension def topological_dimension(self): "Return the dimension of the topology of this domain." return self._topological_dimension def cell(self): "Return the cell this domain is defined in terms of." return self._cell def coordinates(self): "Return the coordinate vector field this domain is defined in terms of." return self._coordinates def coordinate_element(self): "Return the finite element of the coordinate vector field of this domain." x = self.coordinates() if x is None: from ufl import VectorElement return VectorElement("Lagrange", self, 1) else: return x.element() def label(self): "Return the label identifying this domain. None means no label has been set." return self._label def is_piecewise_linear_simplex_domain(self): return (self.coordinate_element().degree() == 1) and (self.cell().cellname() in affine_cells) def data(self): "Return attached data object." return self._data def signature_data(self, renumbering): "Signature data of domain depend on the global domain numbering." count = renumbering[self] cdata = self.cell() x = self.coordinates() xdata = (None if x is None else x.signature_data(renumbering)) return (count, cdata, xdata) def hash_data(self): # Including only id of data here. # If this is a problem in pydolfin, the user will just have # to create explicit Domain objects to avoid problems. # NB! This data is used in both __hash__ and __eq__. return (self._label, self._cell, self._coordinates, # None or a Coefficient id_or_none(self._data)) def __hash__(self): return hash(self.hash_data()) def __eq__(self, other): return type(self) == type(other) and self.hash_data() == other.hash_data() def __lt__(self, other): if type(self) != type(other): return NotImplemented return self.hash_data() < other.hash_data() def __str__(self): if self._coordinates is None: c = "" else: c = " and coordinates %r" % self._coordinates s = (self._cell, self._label, c) return "" % s def reconstruction_signature(self): """Format as string for evaluation as Python object. For use with cross language frameworks, stored in generated code and evaluated later in Python to reconstruct this object. This differs from repr in that it does not include domain label and data or coordinates, which must be reconstructed or supplied by other means. """ s = (self._cell,) return "Domain(%r)" % s def __repr__(self): if self._coordinates is None: d = None if self._data is None else "" % id_or_none(self._data) s = (self._cell, self._label, d) return "Domain(%r, label=%r, data=%r)" % s else: s = (self._coordinates,) return "Domain(%r)" % s class OverlapDomain(Domain): """WARNING: This is work in progress, design is in no way completed.""" __slots__ = ("_child_domains",) def __init__(self, domain1, domain2, label=None, data=None): # Check domain compatibility ufl_assert(domain1.cell() == domain2.cell(), "Cell mismatch in overlap domain.") ufl_assert(domain1.geometric_dimension() == domain2.geometric_dimension(), "Dimension mismatch in overlap domain.") ufl_assert(domain1.topological_dimension() == domain2.topological_dimension(), "Dimension mismatch in overlap domain.") # Initialize parent class Domain.__init__(self, domain1.cell(), label=label, data=data) # Save child domains for later self._child_domains = (domain1, domain2) def child_domains(self): return self._child_domains class IntersectionDomain(Domain): """WARNING: This is work in progress, design is in no way completed.""" __slots__ = ("_child_domains",) def __init__(self, domain1, domain2, label=None, data=None): # Check domain compatibility ufl_assert(domain1.cell() == domain2.cell(), "Cell mismatch in overlap domain.") ufl_assert(domain1.geometric_dimension() == domain2.geometric_dimension(), "Dimension mismatch in overlap domain.") ufl_assert(domain1.topological_dimension() == domain2.topological_dimension(), "Dimension mismatch in overlap domain.") # Get the right properties of this domain gdim = domain1.geometric_dimension() tdim = domain1.topological_dimension()-1 cell = Cell(domain1.cell().facet_cellname(), gdim) ufl_assert(cell.topological_dimension() == tdim) # Initialize parent class Domain.__init__(self, cell, gdim, tdim, label=label, data=data) # Save child domains for later self._child_domains = (domain1, domain2) def child_domains(self): return self._child_domains class ProductDomain(Domain): """WARNING: This is work in progress, design is in no way completed.""" __slots__ = ("_child_domains",) def __init__(self, domains, data=None): # Get the right properties of this domain gdim = sum(domain.geometric_dimension() for domain in domains) tdim = sum(domain.topological_dimension() for domain in domains) cell = ProductCell(*[domain.cell() for domain in domains]) label = "product_of_%s" % "_".join(str(domain.label()) for domain in domains) # Initialize parent class Domain.__init__(self, cell, gdim, tdim, label=label, data=data) # Save child domains for later self._child_domains = tuple(domains) def child_domains(self): return self._child_domains # --- Utility conversion functions def as_domain(domain): """Convert any valid object to a Domain (in particular, cell or cellname string), or return domain if it is already a Domain.""" if isinstance(domain, Domain): return domain elif hasattr(domain, "ufl_domain"): return domain.ufl_domain() else: return Domain(as_cell(domain)) def join_subdomain_data(subdomain_datas): # FIXME: Remove? Think it's unused now. newdata = {} for data in subdomain_datas: for k, v in iteritems(data): nv = newdata.get(k) if nv is None: # New item, just add it newdata[k] = v elif v is not None: id1 = id_or_none(nv) id2 = id_or_none(v) if id1 != id2: error("Found multiple data objects with key %s." % k) return newdata def check_domain_compatibility(domains): # Validate that the domains are the same except for possibly the data labels = set(domain.label() for domain in domains) ufl_assert(len(labels) == 1 or (len(labels) == 2 and None in labels), "Got incompatible domain labels %s in check_domain_compatibility." % (labels,)) all_cellnames = [dom.cell().cellname() for dom in domains] if len(set(all_cellnames)) != 1: error("Cellname mismatch between domains with same label.") all_coordinates = set(dom.coordinates() for dom in domains) - set((None,)) if len(all_coordinates) > 1: error("Coordinates mismatch between domains with same label.") def join_domains(domains): """Take a list of Domains and return a list with only unique domain objects. Checks that domains with the same label are compatible, and allows data to be None or """ # Ignore Nones in input domains domains = [domain for domain in domains if domain is not None] # Build lists of domain objects with same label label2domlist = defaultdict(list) for domain in domains: label2domlist[domain.label()].append(domain) # Extract None list from this dict, map to label but only if only one exists if None in label2domlist: none_domains = {} if len(label2domlist) == 1: pass elif len(label2domlist) == 2: none_domains = label2domlist[None] del label2domlist[None] key, = list(label2domlist.keys()) label2domlist[key].extend(none_domains) else: error("Ambiguous mapping of domains with label None to multiple domains with different labels.") else: none_domains = {} # Join domain data to get a list with only one domain for each label newdomains = [] for label in sorted(label2domlist.keys()): domlist = label2domlist[label] if len(domlist) == 1: dom, = domlist else: # Validate that the domains are the same except for possibly the data check_domain_compatibility(domlist) # Pick first non-None data object for dom in domlist: newdata = dom.data() if newdata is not None: break cell = dom.cell() gdim = dom.geometric_dimension() tdim = dom.topological_dimension() # Validate that data ids match if present if newdata is not None: data_ids = [id_or_none(dom.data()) for dom in domlist] data_ids = set(i for i in data_ids if i is not None) if len(data_ids) > 1: error("Found data objects with different ids in domains with same label.") # Pick first non-None coordinates object for dom in domlist: newcoordinates = dom.coordinates() if newcoordinates is not None: ufl_assert(newcoordinates.domain().coordinates() is None, "A coordinate domain cannot have coordinates.") break # Validate that coordinates match if present if newcoordinates is not None: all_coordinates = [dom.coordinates() for dom in domlist] all_coordinates = set(c for c in all_coordinates if c is not None) if len(all_coordinates) > 1: error("Found different coordinates in domains with same label.") # Construct a new domain object with fully completed data if newcoordinates is not None: dom = Domain(newcoordinates) else: dom = Domain(cell, label=label, data=newdata) newdomains.append(dom) return tuple(newdomains) def extract_domains(expr): domainlist = [] for t in traverse_unique_terminals(expr): domainlist.extend(t.domains()) return sorted(join_domains(domainlist)) ufl-1.6.0/ufl/equation.py000066400000000000000000000034561255567402100153100ustar00rootroot00000000000000"The Equation class, used to express equations like a == L." # Copyright (C) 2012-2014 Anders Logg and Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . class Equation: """This class is used to represent equations expressed by the "==" operator. Examples include a == L and F == 0 where a, L and F are Form objects.""" def __init__(self, lhs, rhs): "Create equation lhs == rhs" self.lhs = lhs self.rhs = rhs def __bool__(self): "Evaluate bool(lhs_form == rhs_form)." if type(self.lhs) != type(self.rhs): return False # Try to delegate to equals function if hasattr(self.lhs, "equals"): return self.lhs.equals(self.rhs) # Fall back to repr return repr(self.lhs) == repr(self.rhs) __nonzero__ = __bool__ def __eq__(self, other): "Compare two equations by comparing lhs and rhs." return isinstance(other, Equation) and \ bool(self.lhs == other.lhs) and \ bool(self.rhs == other.rhs) def __hash__(self): return hash((hash(self.lhs), hash(self.rhs))) def __repr__(self): return "Equation(%r, %r)" % (self.lhs, self.rhs) ufl-1.6.0/ufl/exprcontainers.py000066400000000000000000000072071255567402100165250ustar00rootroot00000000000000"""This module defines special types for representing mapping of expressions to expressions.""" # Copyright (C) 2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from ufl.log import error, warning from ufl.assertions import ufl_assert from ufl.common import EmptyDict from ufl.core.expr import Expr from ufl.core.operator import Operator from ufl.core.ufl_type import ufl_type #--- Non-tensor types --- @ufl_type(num_ops="varying") class ExprList(Operator): "List of Expr objects. For internal use, never to be created by end users." __slots__ = () def __init__(self, *operands): Operator.__init__(self, operands) if not all(isinstance(i, Expr) for i in operands): error("Expecting Expr in ExprList.") def __getitem__(self, i): return self.ufl_operands[i] def __len__(self): return len(self.ufl_operands) def __iter__(self): return iter(self.ufl_operands) def __str__(self): return "ExprList(*(%s,))" % ", ".join(str(i) for i in self.ufl_operands) def __repr__(self): return "ExprList(*%r)" % (self.ufl_operands,) @property def ufl_shape(self): error("A non-tensor type has no ufl_shape.") @property def ufl_free_indices(self): error("A non-tensor type has no ufl_free_indices.") def free_indices(self): error("A non-tensor type has no free_indices.") @property def ufl_index_dimensions(self): error("A non-tensor type has no ufl_index_dimensions.") def index_dimensions(self): error("A non-tensor type has no index_dimensions.") @ufl_type(num_ops="varying") class ExprMapping(Operator): "Mapping of Expr objects. For internal use, never to be created by end users." __slots__ = () def __init__(self, *operands): Operator.__init__(self, operands) if not all(isinstance(e, Expr) for e in operands): error("Expecting Expr in ExprMapping.") def domains(self): # Because this type can act like a terminal if it has no operands, we need to override some recursive operations if self.ufl_operands: return Operator.domains() else: return [] #def __getitem__(self, key): # return self.ufl_operands[key] #def __len__(self): # return len(self.ufl_operands) // 2 #def __iter__(self): # return iter(self.ufl_operands[::2]) def __str__(self): return "ExprMapping(*%r)" % (self.ufl_operands,) def __repr__(self): return "ExprMapping(*%r)" % (self.ufl_operands,) @property def ufl_shape(self): error("A non-tensor type has no ufl_shape.") @property def ufl_free_indices(self): error("A non-tensor type has no ufl_free_indices.") def free_indices(self): error("A non-tensor type has no free_indices.") @property def ufl_index_dimensions(self): error("A non-tensor type has no ufl_index_dimensions.") def index_dimensions(self): error("A non-tensor type has no index_dimensions.") ufl-1.6.0/ufl/exprequals.py000066400000000000000000000112231255567402100156430ustar00rootroot00000000000000 from collections import defaultdict from six.moves import zip from ufl.core.expr import Expr from ufl.log import error from ufl.core.operator import Operator from ufl.core.terminal import Terminal from ufl.common import pre_traversal hash_total = defaultdict(int) hash_collisions = defaultdict(int) hash_equals = defaultdict(int) hash_notequals = defaultdict(int) def print_collisions(): keys = sorted(hash_total.keys(), key=lambda x: (hash_collisions[x], x)) print("Collision statistics ({0} keys):".format(len(keys))) print("[key: equals; notequals; collisions]") n = max(len(str(k)) for k in keys) fmt = ("%%%ds" % n) + ": \t %6d (%3d%%); %6d (%3d%%); %6d (%3d%%) col; tot %d" for k in keys: co = hash_collisions[k] eq = hash_equals[k] ne = hash_notequals[k] tot = hash_total[k] sn, on = k # Skip those that are all not equal if sn != on and ne == tot: continue print(fmt % (k, eq, int(100.0*eq/tot), ne, int(100.0*ne/tot), co, int(100.0*co/tot), tot)) def measure_collisions(equals_func): def equals_func_with_collision_measuring(self, other): # Call equals equal = equals_func(self, other) # Get properties st = type(self) ot = type(other) sn = st.__name__ on = ot.__name__ sh = hash(self) oh = hash(other) key = (sn, on) # If hashes are the same but objects are not equal, we have a collision hash_total[key] += 1 if sh == oh and not equal: hash_collisions[key] += 1 elif sh != oh and equal: error("Equal objects must always have the same hash! Objects are:\n{0}\n{1}".format(self, other)) elif sh == oh and equal: hash_equals[key] += 1 elif sh != oh and not equal: hash_notequals[key] += 1 return equal return equals_func_with_collision_measuring #@measure_collisions def recursive_expr_equals(self, other): # Much faster than the more complex algorithms above! """Checks whether the two expressions are represented the exact same way. This does not check if the expressions are mathematically equal or equivalent! Used by sets and dicts.""" # To handle expr == int/float if not isinstance(other, Expr): return False # Fast cutoff for common case if self._ufl_typecode_ != other._ufl_typecode_: return False # Compare hashes, will cutoff more or less all nonequal types if hash(self) != hash(other): return False # Large objects are costly to compare with themselves if self is other: return True # Terminals if self._ufl_is_terminal_: # Compare terminals with custom == to capture subclass overloading of __eq__ return self == other # --- Operators, most likely equal, below here is the costly part if it recurses through a large tree! --- # Recurse manually to call expr_equals directly without the class EQ overhead! equal = all(recursive_expr_equals(a, b) for (a, b) in zip(self.ufl_operands, other.ufl_operands)) return equal #@measure_collisions def nonrecursive_expr_equals(self, other): """Checks whether the two expressions are represented the exact same way. This does not check if the expressions are mathematically equal or equivalent! Used by sets and dicts.""" # Fast cutoffs for common cases, type difference or # hash difference will cutoff more or less all nonequal types if type(self) != type(other) or hash(self) != hash(other): return False # Large objects are costly to compare with themselves if self is other: return True # Modelled after pre_traversal to avoid recursion: left = [(self, other)] while left: s, o = left.pop() if s._ufl_is_terminal_: # Compare terminals if not s == o: return False else: # Delve into subtrees so = s.ufl_operands oo = o.ufl_operands if len(so) != len(oo): return False for s, o in zip(so, oo): # Fast cutoff for common case if s._ufl_typecode_ != o._ufl_typecode_: return False # Skip subtree if objects are the same if s is o: continue # Append subtree for further inspection left.append((s, o)) # Equal if we get out of the above loop! return True #expr_equals = recursive_expr_equals expr_equals = nonrecursive_expr_equals ufl-1.6.0/ufl/exproperators.py000066400000000000000000000322301255567402100163700ustar00rootroot00000000000000"""This module attaches special functions to Expr. This way we avoid circular dependencies between e.g. Sum and its superclass Expr.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from itertools import chain from ufl.log import error from ufl.assertions import ufl_assert from ufl.common import mergedicts, subdict, StackDict from ufl.core.expr import Expr from ufl.core.operator import Operator from ufl.constantvalue import Zero, as_ufl from ufl.algebra import Sum, Product, Division, Power, Abs from ufl.tensoralgebra import Transposed, Inner from ufl.core.multiindex import MultiIndex, Index, FixedIndex, IndexBase, indices from ufl.indexed import Indexed from ufl.indexsum import IndexSum from ufl.tensors import as_tensor, ComponentTensor from ufl.restriction import PositiveRestricted, NegativeRestricted from ufl.differentiation import Grad from ufl.index_combination_utils import create_slice_indices, merge_overlapping_indices #--- Boolean operators --- from ufl.conditional import EQ, NE, LE, GE, LT, GT def _le(left, right): "UFL operator: A boolean expresion (left <= right) for use with conditional." return LE(left, right) def _ge(left, right): "UFL operator: A boolean expresion (left >= right) for use with conditional." return GE(left, right) def _lt(left, right): "UFL operator: A boolean expresion (left < right) for use with conditional." return LT(left, right) def _gt(left, right): "UFL operator: A boolean expresion (left > right) for use with conditional." return GT(left, right) # '==' needs to implement comparison of expression representations for use in # hashmaps (dict and set), but the others can be overloaded in the language. # It is possible that we can overload eq as well, but we'll need to fix some # issues first and also check for a possible significant performance hit with # compilation of complex forms. Replacing a==b with equiv(a,b) all over the # code could be one way to reduce such a performance hit, but we cannot do # anything about dict and set calling __eq__... from ufl.exprequals import expr_equals Expr.__eq__ = expr_equals # != is used at least by tests, possibly in code as well, and must mean # the opposite of ==, i.e. when evaluated as bool it must mean 'not equal representation'. def _ne(self, other): return not self.__eq__(other) Expr.__ne__ = _ne Expr.__lt__ = _lt Expr.__gt__ = _gt Expr.__le__ = _le Expr.__ge__ = _ge #Expr.__and__ = And #Expr.__or__ = Or #Expr.__xor__ = Xor #--- Helper functions for product handling --- def _mult(a, b): # Discover repeated indices, which results in index sums afi = a.ufl_free_indices bfi = b.ufl_free_indices afid = a.ufl_index_dimensions bfid = b.ufl_index_dimensions fi, fid, ri, rid = merge_overlapping_indices(afi, afid, bfi, bfid) # Pick out valid non-scalar products here (dot products): # - matrix-matrix (A*B, M*grad(u)) => A . B # - matrix-vector (A*v) => A . v s1, s2 = a.ufl_shape, b.ufl_shape r1, r2 = len(s1), len(s2) if r1 == 0 and r2 == 0: # Create scalar product p = Product(a, b) ti = () elif r1 == 0 or r2 == 0: # Scalar - tensor product if r2 == 0: a, b = b, a # Check for zero, simplifying early if possible if isinstance(a, Zero) or isinstance(b, Zero): shape = s1 or s2 return Zero(shape, fi, fid) # Repeated indices are allowed, like in: #v[i]*M[i,:] # Apply product to scalar components ti = indices(b.rank()) p = Product(a, b[ti]) elif r1 == 2 and r2 in (1, 2): # Matrix-matrix or matrix-vector ufl_assert(not ri, "Not expecting repeated indices in non-scalar product.") # Check for zero, simplifying early if possible if isinstance(a, Zero) or isinstance(b, Zero): shape = s1[:-1] + s2[1:] return Zero(shape, fi, fid) # Return dot product in index notation ai = indices(a.rank() - 1) bi = indices(b.rank() - 1) k = indices(1) p = a[ai + k] * b[k + bi] ti = ai + bi else: error("Invalid ranks {0} and {1} in product.".format(r1, r2)) # TODO: I think applying as_tensor after index sums results in cleaner expression graphs. # Wrap as tensor again if ti: p = as_tensor(p, ti) # If any repeated indices were found, apply implicit summation over those for i in ri: mi = MultiIndex((Index(count=i),)) p = IndexSum(p, mi) return p #--- Extend Expr with algebraic operators --- _valid_types = (Expr,) + (int, float) def _mul(self, o): if not isinstance(o, _valid_types): return NotImplemented o = as_ufl(o) return _mult(self, o) Expr.__mul__ = _mul def _rmul(self, o): if not isinstance(o, _valid_types): return NotImplemented o = as_ufl(o) return _mult(o, self) Expr.__rmul__ = _rmul def _add(self, o): if not isinstance(o, _valid_types): return NotImplemented return Sum(self, o) Expr.__add__ = _add def _radd(self, o): if not isinstance(o, _valid_types): return NotImplemented return Sum(o, self) Expr.__radd__ = _radd def _sub(self, o): if not isinstance(o, _valid_types): return NotImplemented return Sum(self, -o) Expr.__sub__ = _sub def _rsub(self, o): if not isinstance(o, _valid_types): return NotImplemented return Sum(o, -self) Expr.__rsub__ = _rsub def _div(self, o): if not isinstance(o, _valid_types): return NotImplemented sh = self.ufl_shape if sh: ii = indices(len(sh)) d = Division(self[ii], o) return as_tensor(d, ii) return Division(self, o) Expr.__div__ = _div Expr.__truediv__ = _div def _rdiv(self, o): if not isinstance(o, _valid_types): return NotImplemented return Division(o, self) Expr.__rdiv__ = _rdiv Expr.__rtruediv__ = _rdiv def _pow(self, o): if not isinstance(o, _valid_types): return NotImplemented if o == 2 and self.ufl_shape: return Inner(self, self) return Power(self, o) Expr.__pow__ = _pow def _rpow(self, o): if not isinstance(o, _valid_types): return NotImplemented return Power(o, self) Expr.__rpow__ = _rpow # TODO: Add Negated class for this? Might simplify reductions in Add. def _neg(self): return -1*self Expr.__neg__ = _neg def _abs(self): return Abs(self) Expr.__abs__ = _abs #--- Extend Expr with restiction operators a("+"), a("-") --- def _restrict(self, side): if side == "+": return PositiveRestricted(self) if side == "-": return NegativeRestricted(self) error("Invalid side %r in restriction operator." % side) def _eval(self, coord, mapping=None, component=()): # Evaluate expression at this particular coordinate, # with provided values for other terminals in mapping # Evaluate derivatives first from ufl.algorithms import expand_derivatives f = expand_derivatives(self) # Evaluate recursively if mapping is None: mapping = {} index_values = StackDict() return f.evaluate(coord, mapping, component, index_values) def _call(self, arg, mapping=None, component=()): # Taking the restriction or evaluating depending on argument if arg in ("+", "-"): ufl_assert(mapping is None, "Not expecting a mapping when taking restriction.") return _restrict(self, arg) else: return _eval(self, arg, mapping, component) Expr.__call__ = _call #--- Extend Expr with the transpose operation A.T --- def _transpose(self): """Transposed a rank two tensor expression. For more general transpose operations of higher order tensor expressions, use indexing and Tensor.""" return Transposed(self) Expr.T = property(_transpose) #--- Extend Expr with indexing operator a[i] --- def analyse_key(ii, rank): """Takes something the user might input as an index tuple inside [], which could include complete slices (:) and ellipsis (...), and returns tuples of actual UFL index objects. The return value is a tuple (indices, axis_indices), each being a tuple of IndexBase instances. The return value 'indices' corresponds to all input objects of these types: - Index - FixedIndex - int => Wrapped in FixedIndex The return value 'axis_indices' corresponds to all input objects of these types: - Complete slice (:) => Replaced by a single new index - Ellipsis (...) => Replaced by multiple new indices """ # Wrap in tuple if not isinstance(ii, (tuple, MultiIndex)): ii = (ii,) else: # Flatten nested tuples, happens with f[...,ii] where ii is a tuple of indices jj = [] for j in ii: if isinstance(j, (tuple, MultiIndex)): jj.extend(j) else: jj.append(j) ii = tuple(jj) # Convert all indices to Index or FixedIndex objects. # If there is an ellipsis, split the indices into before and after. axis_indices = set() pre = [] post = [] indexlist = pre for i in ii: if i == Ellipsis: # Switch from pre to post list when an ellipsis is encountered ufl_assert(indexlist is pre, "Found duplicate ellipsis.") indexlist = post else: # Convert index to a proper type if isinstance(i, int): idx = FixedIndex(i) elif isinstance(i, IndexBase): idx = i elif isinstance(i, slice): if i == slice(None): idx = Index() axis_indices.add(idx) else: # TODO: Use ListTensor to support partial slices? error("Partial slices not implemented, only complete slices like [:]") else: error("Can't convert this object to index: %r" % i) # Store index in pre or post list indexlist.append(idx) # Handle ellipsis as a number of complete slices, # that is create a number of new axis indices num_axis = rank - len(pre) - len(post) if indexlist is post: ellipsis_indices = indices(num_axis) axis_indices.update(ellipsis_indices) else: ellipsis_indices = () # Construct final tuples to return all_indices = tuple(chain(pre, ellipsis_indices, post)) axis_indices = tuple(i for i in all_indices if i in axis_indices) return all_indices, axis_indices def _getitem(self, component): # Treat component consistently as tuple below if not isinstance(component, tuple): component = (component,) shape = self.ufl_shape # Analyse slices (:) and Ellipsis (...) all_indices, slice_indices, repeated_indices = create_slice_indices(component, shape, self.ufl_free_indices) # Check that we have the right number of indices for a tensor with this shape if len(shape) != len(all_indices): error("Invalid number of indices {0} for expression of rank {1}.".format(len(all_indices), len(shape))) # Special case for simplifying foo[...] => foo, foo[:] => foo or similar if len(slice_indices) == len(all_indices): return self # Special case for simplifying as_tensor(ai,(i,))[i] => ai if isinstance(self, ComponentTensor): if all_indices == self.indices().indices(): return self.ufl_operands[0] # Apply all indices to index self, yielding a scalar valued expression mi = MultiIndex(all_indices) a = Indexed(self, mi) # TODO: I think applying as_tensor after index sums results in cleaner expression graphs. # If the Ellipsis or any slices were found, wrap as tensor # valued with the slice indices created at the top here if slice_indices: a = as_tensor(a, slice_indices) # If any repeated indices were found, apply implicit summation over those for i in repeated_indices: mi = MultiIndex((i,)) a = IndexSum(a, mi) # Check for zero (last so we can get indices etc from a, could possibly be done faster by checking early instead) if isinstance(self, Zero): shape = a.ufl_shape fi = a.ufl_free_indices fid = a.ufl_index_dimensions a = Zero(shape, fi, fid) return a Expr.__getitem__ = _getitem #--- Extend Expr with spatial differentiation operator a.dx(i) --- def _dx(self, *ii): "Return the partial derivative with respect to spatial variable number i." d = self # Apply all derivatives for i in ii: d = Grad(d) # Take all components, applying repeated index sums in the [] operation return d.__getitem__((Ellipsis,) + ii) Expr.dx = _dx ufl-1.6.0/ufl/finiteelement/000077500000000000000000000000001255567402100157315ustar00rootroot00000000000000ufl-1.6.0/ufl/finiteelement/__init__.py000066400000000000000000000034541255567402100200500ustar00rootroot00000000000000"This module defines the UFL finite element classes." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Kristian B. Oelgaard # Modified by Marie E. Rognes 2010, 2012 # Modified by Andrew T. T. McRae 2014 # Modified by Lawrence Mitchell 2014 from ufl.finiteelement.finiteelementbase import FiniteElementBase from ufl.finiteelement.finiteelement import FiniteElement from ufl.finiteelement.mixedelement import MixedElement from ufl.finiteelement.mixedelement import VectorElement from ufl.finiteelement.mixedelement import TensorElement from ufl.finiteelement.enrichedelement import EnrichedElement from ufl.finiteelement.restrictedelement import RestrictedElement from ufl.finiteelement.tensorproductelement import TensorProductElement from ufl.finiteelement.outerproductelement import OuterProductElement from ufl.finiteelement.outerproductelement import OuterProductVectorElement from ufl.finiteelement.hdivcurl import HDiv, HCurl from ufl.finiteelement.brokenelement import BrokenElement from ufl.finiteelement.traceelement import TraceElement from ufl.finiteelement.facetelement import FacetElement from ufl.finiteelement.interiorelement import InteriorElement ufl-1.6.0/ufl/finiteelement/brokenelement.py000066400000000000000000000044011255567402100211340ustar00rootroot00000000000000# Copyright (C) 2014 Andrew T. T. McRae # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from ufl.finiteelement.finiteelementbase import FiniteElementBase class BrokenElement(FiniteElementBase): """The discontinuous version of an existing Finite Element space""" def __init__(self, element): self._element = element self._repr = "BrokenElement(%s)" % str(element._repr) family = "BrokenElement" domain = element.domain() degree = element.degree() quad_scheme = element.quadrature_scheme() value_shape = element.value_shape() reference_value_shape = element.reference_value_shape() FiniteElementBase.__init__(self, family, domain, degree, quad_scheme, value_shape, reference_value_shape) def reconstruct(self, **kwargs): """Construct a new BrokenElement object with some properties replaced with new values.""" domain = kwargs.get("domain", self.domain()) ele = self._element.reconstruct(domain=domain) return BrokenElement(ele) def reconstruction_signature(self): return "BrokenElement(%s)" % self._element.reconstruction_signature() def signature_data(self, renumbering): data = ("BrokenElement", self._element.signature_data(renumbering), ("no domain" if self._domain is None else self._domain .signature_data(renumbering))) return data def __str__(self): return "BrokenElement(%s)" % str(self._element) def shortstr(self): return "BrokenElement(%s)" % str(self._element.shortstr()) def __repr__(self): return self._repr ufl-1.6.0/ufl/finiteelement/elementlist.py000066400000000000000000000353721255567402100206420ustar00rootroot00000000000000"""This module provides an extensive list of predefined finite element families. Users or more likely, form compilers, may register new elements by calling the function register_element.""" # Copyright (C) 2008-2014 Martin Sandve Alnes and Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Marie E. Rognes , 2010 # Modified by Lizao Li , 2015 from __future__ import print_function from ufl.assertions import ufl_assert from ufl.sobolevspace import L2, H1, H2, HDiv, HCurl, HEin from ufl.common import istr # List of valid elements ufl_elements = {} # Aliases: aliases[name] (...) -> (standard_name, ...) aliases = {} # Function for registering new elements def register_element(family, short_name, value_rank, sobolev_space, mapping, degree_range, cellnames): "Register new finite element family" ufl_assert(family not in ufl_elements, 'Finite element \"%s\" has already been registered.' % family) ufl_elements[family] = (family, short_name, value_rank, sobolev_space, mapping, degree_range, cellnames) ufl_elements[short_name] = (family, short_name, value_rank, sobolev_space, mapping, degree_range, cellnames) def register_element2(family, value_rank, sobolev_space, mapping, degree_range, cellnames): "Register new finite element family" ufl_assert(family not in ufl_elements, 'Finite element \"%s\" has already been registered.' % family) ufl_elements[family] = (family, family, value_rank, sobolev_space, mapping, degree_range, cellnames) def register_alias(alias, to): aliases[alias] = to def show_elements(): print("Showing all registered elements:") print("================================") shown = set() for k in sorted(ufl_elements.keys()): data = ufl_elements[k] if data in shown: continue shown.add(data) (family, short_name, value_rank, sobolev_space, mapping, degree_range, cellnames) = data print("Finite element family: %s, %s" % (repr(family), repr(short_name))) print("Sobolev space: %s" % (sobolev_space,)) print("Mapping: %s" % (mapping,)) print("Degree range: %s" % (degree_range,)) print("Value rank: %s" % (value_rank,)) print("Defined on cellnames: %s" % (cellnames,)) print() # FIXME: Consider cleanup of element names. Use notation from periodic table as the main, keep old names as compatibility aliases. # NOTE: Any element with polynomial degree 0 will be considered L2, independent of the space passed to register_element. # NOTE: The mapping of the element basis functions # from reference to physical representation is # chosen based on the sobolev space: # HDiv = contravariant Piola, # HCurl = covariant Piola, # H1/L2 = no mapping. # TODO: If determining mapping from sobolev_space isn't sufficient # in the future, add mapping name as another element property. # Cell groups simplices = ("interval", "triangle", "tetrahedron") cubes = ("interval", "quadrilateral", "hexahedron") any_cell = (None, "vertex", "interval", "triangle", "tetrahedron", "quadrilateral", "hexahedron") # Elements in the periodic table # TODO: Register these as aliases of periodic table element description instead of the other way around register_element("Lagrange", "CG", 0, H1, "identity", (1, None), any_cell) # "P" register_element("Brezzi-Douglas-Marini", "BDM", 1, HDiv, "contravariant Piola", (1, None), simplices[1:]) # "BDMF" (2d), "N2F" (3d) register_element("Discontinuous Lagrange", "DG", 0, L2, "identity", (0, None), any_cell) # "DP" register_element("Nedelec 1st kind H(curl)", "N1curl", 1, HCurl, "covariant Piola", (1, None), simplices[1:]) # "RTE" (2d), "N1E" (3d) register_element("Nedelec 2nd kind H(curl)", "N2curl", 1, HCurl, "covariant Piola", (1, None), simplices[1:]) # "BDME" (2d), "N2E" (3d) register_element("Raviart-Thomas", "RT", 1, HDiv, "contravariant Piola", (1, None), simplices[1:]) # "RTF" (2d), "N1F" (3d) # Elements not in the periodic table register_element("Argyris", "ARG", 0, H2, "identity", (1, None), simplices[1:]) register_element("Arnold-Winther", "AW", 0, H1, "identity", None, ("triangle",)) register_element("Brezzi-Douglas-Fortin-Marini", "BDFM", 1, HDiv, "contravariant Piola", (1, None), simplices[1:]) register_element("Crouzeix-Raviart", "CR", 0, L2, "identity", (1, 1), simplices[1:]) # TODO: Implement generic Tear operator for elements instead of this: register_element("Discontinuous Raviart-Thomas", "DRT", 1, L2, "contravariant Piola", (1, None), simplices[1:]) register_element("Hermite", "HER", 0, H1, "identity", None, simplices[1:]) register_element("Mardal-Tai-Winther", "MTW", 0, H1, "identity", None, ("triangle",)) register_element("Morley", "MOR", 0, H2, "identity", None, ("triangle",)) # Special elements register_element("Boundary Quadrature", "BQ", 0, L2, "identity", (0, None), any_cell) register_element("Bubble", "B", 0, H1, "identity", (2, None), simplices) register_element("Quadrature", "Quadrature", 0, L2, "identity", (0, None), any_cell) register_element("Real", "R", 0, L2, "identity", (0, 0), any_cell + ("OuterProductCell",)) register_element("Undefined", "U", 0, L2, "identity", (0, None), any_cell) register_element("Lobatto", "Lob", 0, L2, "identity", (1, None), ("interval",)) register_element("Radau", "Rad", 0, L2, "identity", (0, None), ("interval",)) register_element("Discontinuous Lagrange Trace", "DGT", 0, L2, "identity", (0, None), any_cell) register_element("Regge", "Regge", 2, HEin, "pullback as metric", (0, None), simplices[1:]) # Let Nedelec H(div) elements be aliases to BDMs/RTs register_alias("Nedelec 1st kind H(div)", lambda family, dim, order, degree: ("Raviart-Thomas", order)) register_alias("N1div", lambda family, dim, order, degree: ("Raviart-Thomas", order)) register_alias("Nedelec 2nd kind H(div)", lambda family, dim, order, degree: ("Brezzi-Douglas-Marini", order)) register_alias("N2div", lambda family, dim, order, degree: ("Brezzi-Douglas-Marini", order)) # New elements introduced for the periodic table 2014 register_element2("Q", 0, H1, "identity", (1, None), cubes) register_element2("DQ", 0, L2, "identity", (0, None), cubes) register_element2("RTCE", 1, HCurl, "covariant Piola", (1, None), ("quadrilateral",)) register_element2("RTCF", 1, HDiv, "contravariant Piola", (1, None), ("quadrilateral",)) register_element2("NCE", 1, HCurl, "covariant Piola", (1, None), ("hexahedron",)) register_element2("NCF", 1, HDiv, "contravariant Piola", (1, None), ("hexahedron",)) register_element2("S", 0, H1, "identity", (1, None), cubes) register_element2("DPC", 0, L2, "identity", (1, None), cubes) register_element2("BDMCE", 1, HCurl, "covariant Piola", (1, None), ("quadrilateral",)) register_element2("BDMCF", 1, HDiv, "contravariant Piola", (1, None), ("quadrilateral",)) register_element2("AAE", 1, HCurl, "covariant Piola", (1, None), ("hexahedron",)) register_element2("AAF", 1, HDiv, "contravariant Piola", (1, None), ("hexahedron",)) # New aliases introduced for the periodic table 2014 register_alias("P", lambda family, dim, order, degree: ("Lagrange", order)) register_alias("DP", lambda family, dim, order, degree: ("Discontinuous Lagrange", order)) register_alias("RTE", lambda family, dim, order, degree: ("Nedelec 1st kind H(curl)", order)) register_alias("RTF", lambda family, dim, order, degree: ("Raviart-Thomas", order)) register_alias("N1E", lambda family, dim, order, degree: ("Nedelec 1st kind H(curl)", order)) register_alias("N1F", lambda family, dim, order, degree: ("Raviart-Thomas", order)) register_alias("BDME", lambda family, dim, order, degree: ("Nedelec 2nd kind H(curl)", order)) register_alias("BDMF", lambda family, dim, order, degree: ("Brezzi-Douglas-Marini", order)) register_alias("N2E", lambda family, dim, order, degree: ("Nedelec 2nd kind H(curl)", order)) register_alias("N2F", lambda family, dim, order, degree: ("Brezzi-Douglas-Marini", order)) # Finite element exterior calculus notation def feec_element(family, n, r, k): # n = topological dimension of domain # r = polynomial order # k = form_degree # Note: We always map to edge elements in 2D, don't know how to differentiate otherwise? # Mapping from (feec name, domain dimension, form degree) to (family name, polynomial order) _feec_elements = { "P- Lambda": ( (("P", r), ("DP", r - 1)), (("P", r), ("RTE", r), ("DP", r - 1)), (("P", r), ("N1E", r), ("N1F", r), ("DP", r - 1)), ), "P Lambda": ( (("P", r), ("DP", r)), (("P", r), ("BDME", r), ("DP", r)), (("P", r), ("N2E", r), ("N2F", r), ("DP", r)), ), "Q- Lambda": ( (("Q", r), ("DQ", r - 1)), (("Q", r), ("RTCE", r), ("DQ", r - 1)), (("Q", r), ("NCE", r), ("NCF", r), ("DQ", r - 1)), ), "S Lambda": ( (("S", r), ("DPC", r)), (("S", r), ("BDMCE", r), ("DPC", r)), (("S", r), ("AAE", r), ("AAF", r), ("DPC", r)), ), } # New notation, old verbose notation (including "Lambda") might be removed _feec_elements["P-"] = _feec_elements["P- Lambda"] _feec_elements["P"] = _feec_elements["P Lambda"] _feec_elements["Q-"] = _feec_elements["Q- Lambda"] _feec_elements["S"] = _feec_elements["S Lambda"] family, r = _feec_elements[family][n - 1][k] return family, r # General FEEC notation, old verbose (can be removed) register_alias("P- Lambda", lambda family, dim, order, degree: feec_element(family, dim, order, degree)) register_alias("P Lambda", lambda family, dim, order, degree: feec_element(family, dim, order, degree)) register_alias("Q- Lambda", lambda family, dim, order, degree: feec_element(family, dim, order, degree)) register_alias("S Lambda", lambda family, dim, order, degree: feec_element(family, dim, order, degree)) # General FEEC notation, new compact notation register_alias("P-", lambda family, dim, order, degree: feec_element(family, dim, order, degree)) #register_alias("P Lambda", lambda family, dim, order, degree: feec_element(family, dim, order, degree)) register_alias("Q-", lambda family, dim, order, degree: feec_element(family, dim, order, degree)) #register_alias("S Lambda", lambda family, dim, order, degree: feec_element(family, dim, order, degree)) def canonical_element_description(family, cell, order, form_degree): """Given basic element information, return corresponding element information on canonical form. Input: family, cell, (polynomial) order, form_degree Output: family (canonical), short_name (for printing), order, value shape, reference value shape, sobolev_space This is used by the FiniteElement constructor to ved input data against the element list and aliases defined in ufl. """ # Get domain dimensions if cell is not None: tdim = cell.topological_dimension() gdim = cell.geometric_dimension() cellname = cell.cellname() else: tdim = None gdim = None cellname = None # Catch general FEEC notation "P" and "S" if form_degree is not None and family in ("P", "S"): family, order = feec_element(family, tdim, order, form_degree) # Check whether this family is an alias for something else while family in aliases: ufl_assert(tdim is not None, "Need dimension to handle element aliases.") (family, order) = aliases[family](family, tdim, order, form_degree) #info_blue("%s, is an alias for %s " % ( # (family, cell, order, form_degree), # (name, dummy_cell, r))) # Check that the element family exists ufl_assert(family in ufl_elements, 'Unknown finite element "%s".' % family) # Check that element data is valid (and also get common family name) (family, short_name, value_rank, sobolev_space, mapping, krange, cellnames) = ufl_elements[family] # Validate cellname if a valid cell is specified ufl_assert(cellname is None or cellname in cellnames, 'Cellname "%s" invalid for "%s" finite element.' % (cellname, family)) # Validate order if specified if order is not None: ufl_assert(krange is not None, 'Order "%s" invalid for "%s" finite element, '\ 'should be None.' % (order, family)) kmin, kmax = krange ufl_assert(kmin is None or order >= kmin, 'Order "%s" invalid for "%s" finite element.' %\ (order, family)) ufl_assert(kmax is None or order <= kmax, 'Order "%s" invalid for "%s" finite element.' %\ (istr(order), family)) # Override sobolev_space for piecewise constants (TODO: necessary?) if order == 0: sobolev_space = L2 if value_rank == 2: # Tensor valued fundamental elements in HEin have this shape ufl_assert(gdim is not None and tdim is not None, "Cannot infer shape of element without topological and geometric dimensions.") reference_value_shape = (tdim, tdim) value_shape = (gdim, gdim) elif value_rank == 1: # Vector valued fundamental elements in HDiv and HCurl have a shape ufl_assert(gdim != None and tdim != None, "Cannot infer shape of element without topological and geometric dimensions.") reference_value_shape = (tdim,) value_shape = (gdim,) elif value_rank == 0: # All other elements are scalar values reference_value_shape = () value_shape = () else: error("Invalid value rank %d." % value_rank) return family, short_name, order, value_shape, reference_value_shape, sobolev_space, mapping ufl-1.6.0/ufl/finiteelement/enrichedelement.py000066400000000000000000000111361255567402100214400ustar00rootroot00000000000000"This module defines the UFL finite element classes." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Kristian B. Oelgaard # Modified by Marie E. Rognes 2010, 2012 from six.moves import zip from ufl.assertions import ufl_assert from ufl.permutation import compute_indices from ufl.common import product, istr, EmptyDict from ufl.log import info_blue, warning, warning_blue, error from ufl.finiteelement.finiteelementbase import FiniteElementBase class EnrichedElement(FiniteElementBase): """The vector sum of two finite element spaces: EnrichedElement(V, Q) = {v + q | v in V, q in Q}. """ def __init__(self, *elements): self._elements = elements domain = elements[0].domain() ufl_assert(all(e.domain() == domain for e in elements), "Domain mismatch for sub elements of enriched element.") if isinstance(elements[0].degree(), int): degrees = { e.degree() for e in elements } - { None } degree = max(degrees) if degrees else None else: degree = tuple(map(max, zip(*[e.degree() for e in elements]))) # We can allow the scheme not to be defined, but all defined should be equal quad_schemes = [e.quadrature_scheme() for e in elements] quad_schemes = [qs for qs in quad_schemes if qs is not None] quad_scheme = quad_schemes[0] if quad_schemes else None ufl_assert(all(qs == quad_scheme for qs in quad_schemes),\ "Quadrature scheme mismatch.") value_shape = elements[0].value_shape() ufl_assert(all(e.value_shape() == value_shape for e in elements), "Element value shape mismatch.") reference_value_shape = elements[0].reference_value_shape() ufl_assert(all(e.reference_value_shape() == reference_value_shape for e in elements), "Element reference value shape mismatch.") #mapping = elements[0].mapping() # FIXME: This fails for a mixed subelement here. #ufl_assert(all(e.mapping() == mapping for e in elements), # "Element mapping mismatch.") # Initialize element data FiniteElementBase.__init__(self, "EnrichedElement", domain, degree, quad_scheme, value_shape, reference_value_shape) # Cache repr string self._repr = "EnrichedElement(*%r)" % ([repr(e) for e in self._elements],) def reconstruction_signature(self): """Format as string for evaluation as Python object. For use with cross language frameworks, stored in generated code and evaluated later in Python to reconstruct this object. This differs from repr in that it does not include domain label and data, which must be reconstructed or supplied by other means. """ return "EnrichedElement(%s)" % (', '.join(e.reconstruction_signature() for e in self._elements),) def reconstruct(self, **kwargs): """Construct a new EnrichedElement object with some properties replaced with new values.""" elements = [e.reconstruct(**kwargs) for e in self._elements] if all(a == b for (a, b) in zip(elements, self._elements)): return self return EnrichedElement(*elements) def is_cellwise_constant(self): """Return whether the basis functions of this element is spatially constant over each cell.""" return all(e.is_cellwise_constant() for e in self._elements) def mapping(self): return self._elements[0].mapping() def __str__(self): "Format as string for pretty printing." return "<%s>" % " + ".join(str(e) for e in self._elements) def shortstr(self): "Format as string for pretty printing." return "<%s>" % " + ".join(e.shortstr() for e in self._elements) def signature_data(self, renumbering): data = ("EnrichedElement", tuple(e.signature_data(renumbering) for e in self._elements)) return data ufl-1.6.0/ufl/finiteelement/facetelement.py000066400000000000000000000044441255567402100207450ustar00rootroot00000000000000# Copyright (C) 2014 Andrew T. T. McRae # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from ufl.finiteelement.finiteelementbase import FiniteElementBase class FacetElement(FiniteElementBase): """A version of an existing Finite Element space in which all dofs associated with the interior have been discarded""" def __init__(self, element): self._element = element self._repr = "FacetElement(%r)" % element family = "FacetElement" domain = element.domain() degree = element.degree() quad_scheme = element.quadrature_scheme() value_shape = element.value_shape() reference_value_shape = element.reference_value_shape() FiniteElementBase.__init__(self, family, domain, degree, quad_scheme, value_shape, reference_value_shape) def reconstruct(self, **kwargs): """Construct a new FacetElement object with some properties replaced with new values.""" domain = kwargs.get("domain", self.domain()) ele = self._element.reconstruct(domain=domain) return FacetElement(ele) def reconstruction_signature(self): return "FacetElement(%s)" % self._element.reconstruction_signature() def signature_data(self, renumbering): data = ("FacetElement", self._element.signature_data(renumbering), ("no domain" if self._domain is None else self._domain .signature_data(renumbering))) return data def __str__(self): return "FacetElement(%s)" % str(self._element) def shortstr(self): return "FacetElement(%s)" % str(self._element.shortstr()) def __repr__(self): return self._repr ufl-1.6.0/ufl/finiteelement/finiteelement.py000066400000000000000000000117401255567402100211360ustar00rootroot00000000000000"This module defines the UFL finite element classes." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Kristian B. Oelgaard # Modified by Marie E. Rognes 2010, 2012 # Modified by Anders Logg 2014 from ufl.assertions import ufl_assert from ufl.permutation import compute_indices from ufl.common import product, istr, EmptyDict from ufl.geometry import as_domain, as_cell from ufl.log import info_blue, warning, warning_blue, error from ufl.finiteelement.elementlist import canonical_element_description from ufl.finiteelement.finiteelementbase import FiniteElementBase class FiniteElement(FiniteElementBase): "The basic finite element class for all simple finite elements" # TODO: Move these to base? __slots__ = ("_short_name", "_sobolev_space", "_mapping", ) def __init__(self, family, domain=None, degree=None, form_degree=None, quad_scheme=None): """Create finite element. *Arguments* family (string) The finite element family domain The geometric domain degree (int) The polynomial degree (optional) form_degree (int) The form degree (FEEC notation, used when field is viewed as k-form) quad_scheme The quadrature scheme (optional) """ if domain is None: cell = None else: domain = as_domain(domain) cell = domain.cell() ufl_assert(cell is not None, "Missing cell in given domain.") family, short_name, degree, value_shape, reference_value_shape, sobolev_space, mapping = \ canonical_element_description(family, cell, degree, form_degree) # TODO: Move these to base? Might be better to instead simplify base though. self._sobolev_space = sobolev_space self._mapping = mapping self._short_name = short_name # Initialize element data FiniteElementBase.__init__(self, family, domain, degree, quad_scheme, value_shape, reference_value_shape) # Cache repr string self._repr = "FiniteElement(%r, %r, %r, quad_scheme=%r)" % ( self.family(), self.domain(), self.degree(), self.quadrature_scheme()) assert '"' not in self._repr def mapping(self): return self._mapping def sobolev_space(self): return self._sobolev_space def reconstruction_signature(self): """Format as string for evaluation as Python object. For use with cross language frameworks, stored in generated code and evaluated later in Python to reconstruct this object. This differs from repr in that it does not include domain label and data, which must be reconstructed or supplied by other means. """ return "FiniteElement(%r, %s, %r, %r)" % ( self.family(), self.domain().reconstruction_signature(), self.degree(), self.quadrature_scheme()) def signature_data(self, renumbering): data = ("FiniteElement", self._family, self._degree, self._value_shape, self._reference_value_shape, self._quad_scheme, ("no domain" if self._domain is None else self._domain.signature_data(renumbering))) return data def reconstruct(self, **kwargs): """Construct a new FiniteElement object with some properties replaced with new values.""" kwargs["family"] = kwargs.get("family", self.family()) kwargs["domain"] = kwargs.get("domain", self.domain()) kwargs["degree"] = kwargs.get("degree", self.degree()) kwargs["quad_scheme"] = kwargs.get("quad_scheme", self.quadrature_scheme()) return FiniteElement(**kwargs) def __str__(self): "Format as string for pretty printing." qs = self.quadrature_scheme() qs = "" if qs is None else "(%s)" % qs return "<%s%s%s on a %s>" % (self._short_name, istr(self.degree()),\ qs, self.domain()) def shortstr(self): "Format as string for pretty printing." return "%s%s(%s)" % (self._short_name, istr(self.degree()), istr(self.quadrature_scheme())) ufl-1.6.0/ufl/finiteelement/finiteelementbase.py000066400000000000000000000225151255567402100217730ustar00rootroot00000000000000"This module defines the UFL finite element classes." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Kristian B. Oelgaard # Modified by Marie E. Rognes 2010, 2012 from six.moves import zip from ufl.assertions import ufl_assert from ufl.permutation import compute_indices from ufl.common import product, istr, EmptyDict from ufl.geometry import Cell, as_cell, as_domain, Domain from ufl.log import info_blue, warning, warning_blue, error class FiniteElementBase(object): "Base class for all finite elements" __slots__ = ("_family", "_cell", "_domain", "_degree", "_form_degree", "_quad_scheme", "_value_shape", "_reference_value_shape", "_repr", "__weakref__") def __init__(self, family, domain, degree, quad_scheme, value_shape, reference_value_shape): "Initialize basic finite element data" ufl_assert(isinstance(family, str), "Invalid family type.") ufl_assert(isinstance(degree, (int, tuple)) or degree is None, "Invalid degree type.") ufl_assert(isinstance(value_shape, tuple), "Invalid value_shape type.") ufl_assert(isinstance(reference_value_shape, tuple), "Invalid reference_value_shape type.") # TODO: Support multiple domains for composite mesh mixed elements if domain is None: self._domain = None self._cell = None else: self._domain = as_domain(domain) self._cell = self._domain.cell() ufl_assert(isinstance(self._domain, Domain), "Invalid domain type.") ufl_assert(isinstance(self._cell, Cell), "Invalid cell type.") self._family = family self._degree = degree self._value_shape = value_shape self._reference_value_shape = reference_value_shape self._quad_scheme = quad_scheme def __repr__(self): "Format as string for evaluation as Python object." return self._repr def reconstruction_signature(self): """Format as string for evaluation as Python object. For use with cross language frameworks, stored in generated code and evaluated later in Python to reconstruct this object. This differs from repr in that it does not include domain label and data, which must be reconstructed or supplied by other means. """ raise NotImplementedError("Class %s must implement FiniteElementBase.reconstruction_signature" % (type(self).__name__,)) def signature_data(self, renumbering): data = ("FiniteElementBase", self._family, self._degree, self._value_shape, self._reference_value_shape, self._quad_scheme, ("no domain" if self._domain is None else self._domain.signature_data(renumbering))) return data def __hash__(self): "Compute hash code for insertion in hashmaps." return hash(repr(self)) def __eq__(self, other): "Compute element equality for insertion in hashmaps." return type(self) == type(other) and repr(self) == repr(other) def __lt__(self, other): "Compare elements by repr, to give a natural stable sorting." return repr(self) < repr(other) def family(self): # FIXME: Undefined for base? "Return finite element family" return self._family def degree(self, component=None): "Return polynomial degree of finite element" # FIXME: Consider embedded_degree concept for more accurate degree, see blueprint return self._degree def quadrature_scheme(self): "Return quadrature scheme of finite element" return self._quad_scheme def mapping(self): error("Missing implementation of mapping().") def cell(self): "Return cell of finite element" return self._cell def domain(self, component=None): # TODO: Deprecate this "Return the domain on which this element is defined." domains = self.domains(component) n = len(domains) if n == 0: return None elif n == 1: return domains[0] else: error("Cannot return the requested single domain, as this element has multiple domains.") def domains(self, component=None): "Return the domain on which this element is defined." if self._domain is None: return () else: return (self._domain,) def is_cellwise_constant(self, component=None): """Return whether the basis functions of this element is spatially constant over each cell.""" return self.family() == "Real" or self.degree() == 0 def value_shape(self): "Return the shape of the value space on the global domain." return self._value_shape def reference_value_shape(self): "Return the shape of the value space on the reference cell." return self._reference_value_shape def symmetry(self): # FIXME: different approach """Return the symmetry dict, which is a mapping c0 -> c1 meaning that component c0 is represented by component c1.""" return EmptyDict def _check_component(self, i): "Check that component index i is valid" sh = self.value_shape() r = len(sh) if not (len(i) == r and all(j < k for (j, k) in zip(i, sh))): error(("Illegal component index '%r' (value rank %d)" + \ "for element (value rank %d).") % (i, len(i), r)) def extract_subelement_component(self, i): """Extract direct subelement index and subelement relative component index for a given component index""" if isinstance(i, int): i = (i,) self._check_component(i) return (None, i) def extract_component(self, i): """Recursively extract component index relative to a (simple) element and that element for given value component index""" if isinstance(i, int): i = (i,) self._check_component(i) return (i, self) def _check_reference_component(self, i): "Check that reference component index i is valid" sh = self.value_shape() r = len(sh) if not (len(i) == r and all(j < k for (j, k) in zip(i, sh))): error(("Illegal component index '%r' (value rank %d)" + \ "for element (value rank %d).") % (i, len(i), r)) def extract_subelement_reference_component(self, i): """Extract direct subelement index and subelement relative reference component index for a given reference component index""" if isinstance(i, int): i = (i,) self._check_reference_component(i) return (None, i) def extract_reference_component(self, i): """Recursively extract reference component index relative to a (simple) element and that element for given reference value component index""" if isinstance(i, int): i = (i,) self._check_reference_component(i) return (i, self) def num_sub_elements(self): "Return number of sub elements" return 0 def sub_elements(self): "Return list of sub elements" return [] def __add__(self, other): "Add two elements, creating an enriched element" ufl_assert(isinstance(other, FiniteElementBase), "Can't add element and %s." % other.__class__) warning_blue("WARNING: Creating an EnrichedElement,\n " +\ "if you intended to create a MixedElement use '*' instead of '+'.") from ufl.finiteelement import EnrichedElement return EnrichedElement(self, other) def __mul__(self, other): "Multiply two elements, creating a mixed element" ufl_assert(isinstance(other, FiniteElementBase), "Can't multiply element and %s." % other.__class__) from ufl.finiteelement import MixedElement return MixedElement(self, other) def __getitem__(self, index): "Restrict finite element to a subdomain, subcomponent or topology (cell)." # NOTE: RestrictedElement will not be used to represent restriction # to subdomains, as that is represented by the element having # a domain property that is a Region. # NOTE: Implementing restriction to subdomains with [] should not be # done, as V[1] is ambiguously similar to both indexing expressions # and obtaining a subdomain, such as myexpr[1] and mydomain[1]. if isinstance(index, Cell) or index == "facet": from ufl.finiteelement import RestrictedElement return RestrictedElement(self, index) return NotImplemented ufl-1.6.0/ufl/finiteelement/hdivcurl.py000066400000000000000000000102541255567402100201250ustar00rootroot00000000000000# Copyright (C) 2008-2014 Andrew T. T. McRae # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from ufl.finiteelement.outerproductelement import OuterProductElement from ufl.finiteelement.finiteelementbase import FiniteElementBase class HDiv(OuterProductElement): """A div-conforming version of an outer product element, assuming this makes mathematical sense.""" __slots__ = ("_element") def __init__(self, element): self._element = element self._repr = "HDiv(%s)" % str(element._repr) family = "OuterProductElement" domain = element.domain() degree = element.degree() quad_scheme = element.quadrature_scheme() value_shape = (element.cell().geometric_dimension(),) reference_value_shape = (element.cell().topological_dimension(),) # TODO: Is this right? # Skipping OuterProductElement constructor! Bad code smell, refactor to avoid this non-inheritance somehow. FiniteElementBase.__init__(self, family, domain, degree, quad_scheme, value_shape, reference_value_shape) def reconstruct(self, **kwargs): """Construct a new HDiv object with some properties replaced with new values.""" domain = kwargs.get("domain", self.domain()) ele = self._element.reconstruct(domain=domain) return HDiv(ele) def reconstruction_signature(self): return "HDiv(%s)" % self._element.reconstruction_signature() def signature_data(self, renumbering): data = ("HDiv", self._element.signature_data(renumbering), ("no domain" if self._domain is None else self._domain .signature_data(renumbering))) return data def __str__(self): return "HDiv(%s)" % str(self._element) def shortstr(self): return "HDiv(%s)" % str(self._element.shortstr()) def __repr__(self): return self._repr class HCurl(OuterProductElement): """A curl-conforming version of an outer product element, assuming this makes mathematical sense.""" __slots__ = ("_element") def __init__(self, element): self._element = element self._repr = "HCurl(%s)" % str(element._repr) family = "OuterProductElement" domain = element.domain() degree = element.degree() quad_scheme = element.quadrature_scheme() value_shape = (element.cell().geometric_dimension(),) reference_value_shape = (element.cell().topological_dimension(),) # TODO: Is this right? # Skipping OuterProductElement constructor! Bad code smell, refactor to avoid this non-inheritance somehow. FiniteElementBase.__init__(self, family, domain, degree, quad_scheme, value_shape, reference_value_shape) def reconstruct(self, **kwargs): """Construct a new HCurl object with some properties replaced with new values.""" domain = kwargs.get("domain", self.domain()) ele = self._element.reconstruct(domain=domain) return HCurl(ele) def reconstruction_signature(self): return "HCurl(%s)" % self._element.reconstruction_signature() def signature_data(self, renumbering): data = ("HCurl", self._element.signature_data(renumbering), ("no domain" if self._domain is None else self._domain .signature_data(renumbering))) return data def __str__(self): return "HCurl(%s)" % str(self._element) def shortstr(self): return "HCurl(%s)" % str(self._element.shortstr()) def __repr__(self): return self._repr ufl-1.6.0/ufl/finiteelement/interiorelement.py000066400000000000000000000044771255567402100215240ustar00rootroot00000000000000# Copyright (C) 2014 Andrew T. T. McRae # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from ufl.finiteelement.finiteelementbase import FiniteElementBase class InteriorElement(FiniteElementBase): """A version of an existing Finite Element space in which only the dofs associated with the interior have been kept""" def __init__(self, element): self._element = element self._repr = "InteriorElement(%r)" % element family = "InteriorElement" domain = element.domain() degree = element.degree() quad_scheme = element.quadrature_scheme() value_shape = element.value_shape() reference_value_shape = element.reference_value_shape() FiniteElementBase.__init__(self, family, domain, degree, quad_scheme, value_shape, reference_value_shape) def reconstruct(self, **kwargs): """Construct a new InteriorElement object with some properties replaced with new values.""" domain = kwargs.get("domain", self.domain()) ele = self._element.reconstruct(domain=domain) return InteriorElement(ele) def reconstruction_signature(self): return "InteriorElement(%s)" % self._element.reconstruction_signature() def signature_data(self, renumbering): data = ("InteriorElement", self._element.signature_data(renumbering), ("no domain" if self._domain is None else self._domain .signature_data(renumbering))) return data def __str__(self): return "InteriorElement(%s)" % str(self._element) def shortstr(self): return "InteriorElement(%s)" % str(self._element.shortstr()) def __repr__(self): return self._repr ufl-1.6.0/ufl/finiteelement/mixedelement.py000066400000000000000000000544231255567402100207730ustar00rootroot00000000000000"This module defines the UFL finite element classes." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Kristian B. Oelgaard # Modified by Marie E. Rognes 2010, 2012 # Modified by Anders Logg 2014 from itertools import chain from six import iteritems from six.moves import zip from six.moves import xrange as range from ufl.assertions import ufl_assert from ufl.permutation import compute_indices from ufl.common import product, istr, EmptyDict from ufl.utils.indexflattening import flatten_multiindex, unflatten_index, shape_to_strides from ufl.geometry import as_domain from ufl.log import info_blue, warning, warning_blue, error from ufl.finiteelement.finiteelementbase import FiniteElementBase from ufl.finiteelement.finiteelement import FiniteElement class MixedElement(FiniteElementBase): "A finite element composed of a nested hierarchy of mixed or simple elements" __slots__ = ("_sub_elements", "_domains") def __init__(self, *elements, **kwargs): "Create mixed finite element from given list of elements" # Un-nest arguments if we get a single argument with a list of elements if len(elements) == 1 and isinstance(elements[0], (tuple, list)): elements = elements[0] # Interpret nested tuples as sub-mixedelements recursively elements = [MixedElement(e) if isinstance(e, (tuple, list)) else e for e in elements] self._sub_elements = elements # Pick the first domain, for now all should be equal domains = tuple(sorted(set(chain(*[element.domains() for element in elements])))) self._domains = domains if domains: # Base class currently only handles one domain, this is work in progress domain = domains[0] # Check that domains have same geometric dimension gdim = domain.geometric_dimension() ufl_assert(all(dom.geometric_dimension() == gdim for dom in domains), "Sub elements must live in the same geometric dimension.") # Require that all elements are defined on the same domain # TODO: allow mixed elements on different domains, # or add a CompositeMixedElement class for that ufl_assert(all(dom == domain for dom in domains), "Sub elements must live on the same domain (for now).") else: domain = None # Check that all elements use the same quadrature scheme # TODO: We can allow the scheme not to be defined. quad_scheme = elements[0].quadrature_scheme() ufl_assert(all(e.quadrature_scheme() == quad_scheme for e in elements),\ "Quadrature scheme mismatch for sub elements of mixed element.") # Compute value shape value_size_sum = sum(product(s.value_shape()) for s in self._sub_elements) # Default value dimension: Treated simply as all subelement # values unpacked in a vector. value_shape = kwargs.get('value_shape', (value_size_sum,)) # Validate value_shape if type(self) is MixedElement: # This is not valid for tensor elements with symmetries, # assume subclasses deal with their own validation ufl_assert(product(value_shape) == value_size_sum, "Provided value_shape doesn't match the total "\ "value size of all subelements.") # Always use a flat reference value shape reference_value_shape = (sum(product(s.reference_value_shape()) for s in self._sub_elements),) # Initialize element data degrees = { e.degree() for e in self._sub_elements } - { None } degree = max(degrees) if degrees else None FiniteElementBase.__init__(self, "Mixed", domain, degree, quad_scheme, value_shape, reference_value_shape) # Cache repr string self._repr = "MixedElement(*%r, **{'value_shape': %r })" %\ (self._sub_elements, self._value_shape) def reconstruction_signature(self): """Format as string for evaluation as Python object. For use with cross language frameworks, stored in generated code and evaluated later in Python to reconstruct this object. This differs from repr in that it does not include domain label and data, which must be reconstructed or supplied by other means. """ return "MixedElement(%s, **{'value_shape': %r })" % \ (', '.join(e.reconstruction_signature() for e in self._sub_elements), self._value_shape) def reconstruct(self, **kwargs): """Construct a new MixedElement object with some properties replaced with new values.""" elements = [e.reconstruct(**kwargs) for e in self._sub_elements] # Value shape cannot be changed, or at # least we have no valid use case for it. # Reconstructing an expression with a reconstructed # coefficient with a different value shape would # be way into undefined behaviour territory... ufl_assert("value_shape" not in kwargs, "Cannot change value_shape in reconstruct.") return self.reconstruct_from_elements(*elements) def reconstruct_from_elements(self, *elements): "Reconstruct a mixed element from new subelements." if all(a == b for (a, b) in zip(elements, self._sub_elements)): return self ufl_assert(all(a.value_shape() == b.value_shape() for (a, b) in zip(elements, self._sub_elements)), "Expecting new elements to have same value shape as old ones.") return MixedElement(*elements, value_shape=self.value_shape()) def symmetry(self): """Return the symmetry dict, which is a mapping c0 -> c1 meaning that component c0 is represented by component c1. A component is a tuple of one or more ints.""" # Build symmetry map from symmetries of subelements sm = {} # Base index of the current subelement into mixed value j = 0 for e in self._sub_elements: sh = e.value_shape() st = shape_to_strides(sh) # Map symmetries of subelement into index space of this element for c0, c1 in iteritems(e.symmetry()): j0 = flatten_multiindex(c0, st) + j j1 = flatten_multiindex(c1, st) + j sm[(j0,)] = (j1,) # Update base index for next element j += product(sh) ufl_assert(j == product(self.value_shape()), "Size mismatch in symmetry algorithm.") return sm or EmptyDict def mapping(self): error("The mapping of a mixed element is not defined. Inspect subelements instead.") def num_sub_elements(self): "Return number of sub elements." return len(self._sub_elements) def sub_elements(self): "Return list of sub elements." return self._sub_elements def extract_subelement_component(self, i): """Extract direct subelement index and subelement relative component index for a given component index""" if isinstance(i, int): i = (i,) self._check_component(i) # Select between indexing modes if len(self.value_shape()) == 1: # Indexing into a long vector of flattened subelement shapes j, = i # Find subelement for this index for sub_element_index, e in enumerate(self._sub_elements): sh = e.value_shape() si = product(sh) if j < si: break j -= si ufl_assert(j >= 0, "Moved past last value component!") # Convert index into a shape tuple st = shape_to_strides(sh) component = unflatten_index(j, st) else: # Indexing into a multidimensional tensor # where subelement index is first axis sub_element_index = i[0] ufl_assert(sub_element_index < len(self._sub_elements), "Illegal component index (dimension %d)." % sub_element_index) component = i[1:] return (sub_element_index, component) def extract_component(self, i): """Recursively extract component index relative to a (simple) element and that element for given value component index""" sub_element_index, component = self.extract_subelement_component(i) return self._sub_elements[sub_element_index].extract_component(component) def extract_subelement_reference_component(self, i): """Extract direct subelement index and subelement relative reference_component index for a given reference_component index""" if isinstance(i, int): i = (i,) self._check_reference_component(i) # Select between indexing modes assert len(self.reference_value_shape()) == 1 # Indexing into a long vector of flattened subelement shapes j, = i # Find subelement for this index for sub_element_index, e in enumerate(self._sub_elements): sh = e.reference_value_shape() si = product(sh) if j < si: break j -= si ufl_assert(j >= 0, "Moved past last value reference_component!") # Convert index into a shape tuple st = shape_to_strides(sh) reference_component = unflatten_index(j, st) return (sub_element_index, reference_component) def extract_reference_component(self, i): """Recursively extract reference_component index relative to a (simple) element and that element for given value reference_component index""" sub_element_index, reference_component = self.extract_subelement_reference_component(i) return self._sub_elements[sub_element_index].extract_reference_component(reference_component) def is_cellwise_constant(self, component=None): """Return whether the basis functions of this element is spatially constant over each cell.""" if component is None: return all(e.is_cellwise_constant() for e in self.sub_elements()) else: i, e = self.extract_component(component) return e.is_cellwise_constant() def domains(self, component=None): "Return the domain(s) on which this element is defined." if component is None: # Return all unique domains return self._domains else: # Return the domains of subelement i, e = self.extract_component(component) return e.domains() def degree(self, component=None): "Return polynomial degree of finite element" if component is None: return self._degree # from FiniteElementBase, computed as max of subelements in __init__ else: i, e = self.extract_component(component) return e.degree() def signature_data(self, renumbering): data = ("MixedElement", self._value_shape, tuple(e.signature_data(renumbering) for e in self._sub_elements)) return data def __str__(self): "Format as string for pretty printing." tmp = ", ".join(str(element) for element in self._sub_elements) return "" def shortstr(self): "Format as string for pretty printing." tmp = ", ".join(element.shortstr() for element in self._sub_elements) return "Mixed<" + tmp + ">" class VectorElement(MixedElement): "A special case of a mixed finite element where all elements are equal" def __init__(self, family, domain, degree, dim=None, form_degree=None, quad_scheme=None): """ Create vector element (repeated mixed element) *Arguments* family (string) The finite element family domain The geometric domain degree (int) The polynomial degree dim (int) The value dimension of the element (optional) form_degree (int) The form degree (FEEC notation, used when field is viewed as k-form) quad_scheme The quadrature scheme (optional) """ if domain is not None: domain = as_domain(domain) # Set default size if not specified if dim is None: ufl_assert(domain is not None, "Cannot infer vector dimension without a domain.") dim = domain.geometric_dimension() # Create mixed element from list of finite elements sub_element = FiniteElement(family, domain, degree, form_degree=form_degree, quad_scheme=quad_scheme) sub_elements = [sub_element]*dim # Get common family name (checked in FiniteElement.__init__) family = sub_element.family() # Compute value shape shape = (dim,) value_shape = shape + sub_element.value_shape() # Initialize element data MixedElement.__init__(self, sub_elements, value_shape=value_shape) self._family = family self._degree = degree self._sub_element = sub_element self._form_degree = form_degree # Storing for signature_data, not sure if it's needed # Cache repr string self._repr = "VectorElement(%r, %r, %r, dim=%d, quad_scheme=%r)" % \ (self._family, self.domain(), self._degree, len(self._sub_elements), self._quad_scheme) def mapping(self): return self._sub_element.mapping() def signature_data(self, renumbering): data = ("VectorElement", self._family, self._degree, len(self._sub_elements), self._quad_scheme, self._form_degree, ("no domain" if self._domain is None else self._domain.signature_data(renumbering))) return data def reconstruction_signature(self): """Format as string for evaluation as Python object. For use with cross language frameworks, stored in generated code and evaluated later in Python to reconstruct this object. This differs from repr in that it does not include domain label and data, which must be reconstructed or supplied by other means. """ return "VectorElement(%r, %s, %r, %d, %r)" % ( self._family, self.domain().reconstruction_signature(), self._degree, len(self._sub_elements), self._quad_scheme) def reconstruct(self, **kwargs): kwargs["family"] = kwargs.get("family", self.family()) kwargs["domain"] = kwargs.get("domain", self.domain()) kwargs["degree"] = kwargs.get("degree", self.degree()) ufl_assert("dim" not in kwargs, "Cannot change dim in reconstruct.") kwargs["dim"] = len(self._sub_elements) kwargs["quad_scheme"] = kwargs.get("quad_scheme", self.quadrature_scheme()) return VectorElement(**kwargs) def __str__(self): "Format as string for pretty printing." return "<%s vector element of degree %s on a %s: %d x %s>" % \ (self.family(), istr(self.degree()), self.domain(), len(self._sub_elements), self._sub_element) def shortstr(self): "Format as string for pretty printing." return "Vector<%d x %s>" % (len(self._sub_elements), self._sub_element.shortstr()) class TensorElement(MixedElement): "A special case of a mixed finite element where all elements are equal" __slots__ = ("_sub_element", "_shape", "_symmetry", "_sub_element_mapping",) def __init__(self, family, domain, degree, shape=None, symmetry=None, quad_scheme=None): "Create tensor element (repeated mixed element with optional symmetries)" if domain is not None: domain = as_domain(domain) # Set default shape if not specified if shape is None: ufl_assert(domain is not None, "Cannot infer vector dimension without a domain.") dim = domain.geometric_dimension() shape = (dim, dim) # Construct default symmetry for matrix elements if symmetry == True: ufl_assert(len(shape) == 2 and shape[0] == shape[1], "Cannot set automatic symmetry for non-square tensor.") symmetry = dict( ((i, j), (j, i)) for i in range(shape[0]) for j in range(shape[1]) if i > j ) # Validate indices in symmetry dict if isinstance(symmetry, dict): for i, j in iteritems(symmetry): ufl_assert(len(i) == len(j), "Non-matching length of symmetry index tuples.") for k in range(len(i)): ufl_assert(i[k] >= 0 and j[k] >= 0 and i[k] < shape[k] and j[k] < shape[k], "Symmetry dimensions out of bounds.") else: ufl_assert(symmetry is None, "Expecting symmetry to be None (unset), True, or dict.") symmetry = EmptyDict # Compute all index combinations for given shape indices = compute_indices(shape) # Compute sub elements and mapping from indices # to sub elements, accounting for symmetry sub_element = FiniteElement(family, domain, degree, quad_scheme) sub_elements = [] sub_element_mapping = {} for index in indices: if index in symmetry: continue sub_element_mapping[index] = len(sub_elements) sub_elements += [sub_element] # Update mapping for symmetry for index in indices: if index in symmetry: sub_element_mapping[index] = sub_element_mapping[symmetry[index]] # Get common family name (checked in FiniteElement.__init__) family = sub_element.family() # Compute value shape value_shape = shape + sub_element.value_shape() # Initialize element data MixedElement.__init__(self, sub_elements, value_shape=value_shape) self._family = family self._degree = degree self._sub_element = sub_element self._shape = shape self._symmetry = symmetry self._sub_element_mapping = sub_element_mapping # Cache repr string self._repr = "TensorElement(%r, %r, %r, shape=%r, symmetry=%r, quad_scheme=%r)" % \ (self._family, self.domain(), self._degree, self._shape, self._symmetry, self._quad_scheme) def mapping(self): return self._sub_element.mapping() def signature_data(self, renumbering): data = ("TensorElement", self._family, self._degree, self._shape, repr(self._symmetry), self._quad_scheme, ("no domain" if self._domain is None else self._domain.signature_data(renumbering))) return data def reconstruction_signature(self): """Format as string for evaluation as Python object. For use with cross language frameworks, stored in generated code and evaluated later in Python to reconstruct this object. This differs from repr in that it does not include domain label and data, which must be reconstructed or supplied by other means. """ return "TensorElement(%r, %s, %r, %r, %r, %r)" % ( self._family, self.domain().reconstruction_signature(), self._degree, self._shape, self._symmetry, self._quad_scheme) def reconstruct(self, **kwargs): kwargs["family"] = kwargs.get("family", self.family()) kwargs["domain"] = kwargs.get("domain", self.domain()) kwargs["degree"] = kwargs.get("degree", self.degree()) ufl_assert("shape" not in kwargs, "Cannot change shape in reconstruct.") kwargs["shape"] = self.value_shape() # Must use same shape as self! # Not sure about symmetry, but no use case I can see ufl_assert("symmetry" not in kwargs, "Cannot change symmetry in reconstruct.") kwargs["symmetry"] = self.symmetry() kwargs["quad_scheme"] = kwargs.get("quad_scheme", self.quadrature_scheme()) return TensorElement(**kwargs) def extract_subelement_component(self, i): """Extract direct subelement index and subelement relative component index for a given component index""" if isinstance(i, int): i = (i,) self._check_component(i) i = self.symmetry().get(i, i) l = len(self._shape) ii = i[:l] jj = i[l:] ufl_assert(ii in self._sub_element_mapping, "Illegal component index %s." % repr(i)) k = self._sub_element_mapping[ii] return (k, jj) def symmetry(self): """Return the symmetry dict, which is a mapping c0 -> c1 meaning that component c0 is represented by component c1.""" return self._symmetry def __str__(self): "Format as string for pretty printing." if self._symmetry: tmp = ", ".join("%s -> %s" % (a, b) for (a, b) in iteritems(self._symmetry)) sym = " with symmetries (%s)" % tmp else: sym = "" return "<%s tensor element of degree %s and shape %s on a %s%s>" % \ (self.family(), istr(self.degree()), self.value_shape(), self.domain(), sym) def shortstr(self): "Format as string for pretty printing." if self._symmetry: tmp = ", ".join("%s -> %s" % (a, b) for (a, b) in iteritems(self._symmetry)) sym = " with symmetries (%s)" % tmp else: sym = "" return "Tensor<%s x %s%s>" % (self.value_shape(), self._sub_element.shortstr(), sym) ufl-1.6.0/ufl/finiteelement/outerproductelement.py000066400000000000000000000164371255567402100224270ustar00rootroot00000000000000"This module defines the UFL finite element classes." # Copyright (C) 2008-2014 Martin Sandve Alnes and Andrew T. T. McRae # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Based on tensorproductelement.py # Modified by Andrew T. T. McRae 2014 # Modified by Lawrence Mitchell 2014 from ufl.assertions import ufl_assert from ufl.cell import OuterProductCell from ufl.domain import as_domain from ufl.finiteelement.mixedelement import MixedElement from ufl.finiteelement.finiteelementbase import FiniteElementBase class OuterProductElement(FiniteElementBase): r"""The outer (tensor) product of 2 element spaces: .. math:: V = A \otimes B Given bases :math:`{\phi_A, \phi_B}` for :math:`A, B`, :math:`{\phi_A * \phi_B}` forms a basis for :math:`V`. """ __slots__ = ("_A", "_B") def __init__(self, A, B, domain=None, form_degree=None, quad_scheme=None): "Create OuterProductElement from a given pair of elements." self._A = A self._B = B family = "OuterProductElement" if domain is None: # Define cell as the product of sub-cells cell = OuterProductCell(A.cell(), B.cell()) domain = as_domain(cell) else: domain = as_domain(domain) cell = domain.cell() ufl_assert(cell is not None, "Missing cell in given domain.") self._repr = "OuterProductElement(*%r, %r)" % (list([self._A, self._B]), domain) # Define polynomial degree as a tuple of sub-degrees degree = (A.degree(), B.degree()) # match FIAT implementation if len(A.value_shape()) == 0 and len(B.value_shape()) == 0: value_shape = () reference_value_shape = () elif len(A.value_shape()) == 1 and len(B.value_shape()) == 0: value_shape = (A.value_shape()[0],) reference_value_shape = (A.reference_value_shape()[0],) # TODO: Is this right? elif len(A.value_shape()) == 0 and len(B.value_shape()) == 1: value_shape = (B.value_shape()[0],) reference_value_shape = (B.reference_value_shape()[0],) # TODO: Is this right? else: raise Exception("Product of vector-valued elements not supported") FiniteElementBase.__init__(self, family, domain, degree, quad_scheme, value_shape, reference_value_shape) def mapping(self): error("TODO: The mapping of an outer product element is not implemented.") def reconstruct(self, **kwargs): """Construct a new OuterProductElement with some properties replaced with new values.""" domain = kwargs.get("domain", self.domain()) return OuterProductElement(self._A, self._B, domain=domain) def reconstruction_signature(self): """Format as string for evaluation as Python object. For use with cross language frameworks, stored in generated code and evaluated later in Python to reconstruct this object. This differs from repr in that it does not include domain label and data, which must be reconstructed or supplied by other means. """ return "OuterProductElement(%r, %r, %s, %r)" % ( self._A, self._B, self.domain().reconstruction_signature(), self._quad_scheme) def __str__(self): "Pretty-print." return "OuterProductElement(%s)" \ % str([str(self._A), str(self._B)]) def shortstr(self): "Short pretty-print." return "OuterProductElement(%s)" \ % str([self._A.shortstr(), self._B.shortstr()]) def signature_data(self, renumbering): data = ("OuterProductElement", self._A, self._B, self._quad_scheme, ("no domain" if self._domain is None else self._domain.signature_data(renumbering))) return data class OuterProductVectorElement(MixedElement): """A special case of a mixed finite element where all elements are equal OuterProductElements""" __slots__ = ("_sub_element") def __init__(self, A, B, domain=None, dim=None, form_degree=None, quad_scheme=None): if domain is not None: domain = as_domain(domain) sub_element = OuterProductElement(A, B, domain=domain) dim = dim or sub_element.cell().geometric_dimension() sub_elements = [sub_element]*dim # Get common family name (checked in FiniteElement.__init__) family = sub_element.family() # Compute value shape shape = (dim,) value_shape = shape + sub_element.value_shape() # Initialize element data MixedElement.__init__(self, sub_elements, value_shape=value_shape) self._family = family self._degree = A.degree(), B.degree() self._sub_element = sub_element # Cache repr string self._repr = "OuterProductVectorElement(%r, %r, dim=%d)" % \ (self._sub_element, self.domain(), len(self._sub_elements)) @property def _A(self): return self._sub_element._A @property def _B(self): return self._sub_element._B def signature_data(self, renumbering): data = ("OuterProductVectorElement", self._A, self._B, len(self._sub_elements), self._quad_scheme, ("no domain" if self._domain is None else self._domain.signature_data(renumbering))) return data def reconstruct(self, **kwargs): """Construct a new OuterProductVectorElement with some properties replaced with new values.""" domain = kwargs.get("domain", self.domain()) dim = kwargs.get("dim", self.num_sub_elements()) return OuterProductVectorElement(self._A, self._B, domain=domain, dim=dim) def reconstruction_signature(self): """Format as string for evaluation as Python object. For use with cross language frameworks, stored in generated code and evaluated later in Python to reconstruct this object. This differs from repr in that it does not include domain label and data, which must be reconstructed or supplied by other means. """ return "OuterProductVectorElement(%r, %s, %d, %r)" % ( self._sub_element, self.domain().reconstruction_signature(), len(self._sub_elements), self._quad_scheme) def __str__(self): "Format as string for pretty printing." return "" % \ (self._sub_element, self.num_sub_elements()) def shortstr(self): "Format as string for pretty printing." return "OPVector" ufl-1.6.0/ufl/finiteelement/restrictedelement.py000066400000000000000000000121331255567402100220250ustar00rootroot00000000000000"This module defines the UFL finite element classes." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Kristian B. Oelgaard # Modified by Marie E. Rognes 2010, 2012 from ufl.assertions import ufl_assert from ufl.permutation import compute_indices from ufl.common import product, istr, EmptyDict from ufl.geometry import Cell, as_cell from ufl.log import info_blue, warning, warning_blue, error from ufl.finiteelement.finiteelementbase import FiniteElementBase class RestrictedElement(FiniteElementBase): "Represents the restriction of a finite element to a type of cell entity." def __init__(self, element, cell_restriction): ufl_assert(isinstance(element, FiniteElementBase), "Expecting a finite element instance.") ufl_assert(isinstance(cell_restriction, Cell) or cell_restriction == "facet", "Expecting a Cell instance, or the string 'facet'.") FiniteElementBase.__init__(self, "RestrictedElement", element.domain(), element.degree(), element.quadrature_scheme(), element.value_shape(), element.reference_value_shape()) self._element = element if isinstance(cell_restriction, Cell): # Just attach cell_restriction if it is a Cell self._cell_restriction = cell_restriction elif cell_restriction == "facet": # Create a cell cell = element.cell() self._cell_restriction = Cell(cell.facet_cellname(), geometric_dimension=cell.geometric_dimension()) self._repr = "RestrictedElement(%r, %r)" % (self._element, self._cell_restriction) def reconstruction_signature(self): """Format as string for evaluation as Python object. For use with cross language frameworks, stored in generated code and evaluated later in Python to reconstruct this object. This differs from repr in that it does not include domain label and data, which must be reconstructed or supplied by other means. """ return "RestrictedElement(%s, %r)" % (self._element.reconstruction_signature(), self._cell_restriction) def reconstruct(self, **kwargs): """Construct a new RestrictedElement object with some properties replaced with new values.""" element = self._element.reconstruct(**kwargs) cell_restriction = kwargs.get("cell_restriction", self.cell_restriction()) return RestrictedElement(element=element, cell_restriction=cell_restriction) def is_cellwise_constant(self): """Return whether the basis functions of this element is spatially constant over each cell.""" return self._element.is_cellwise_constant() def element(self): "Return the element which is restricted." return self._element def mapping(self): return self._element.mapping() def cell_restriction(self): "Return the domain onto which the element is restricted." return self._cell_restriction def __str__(self): "Format as string for pretty printing." return "<%s>|_{%s}" % (self._element, self._cell_restriction) def shortstr(self): "Format as string for pretty printing." return "<%s>|_{%s}" % (self._element.shortstr(), self._cell_restriction) def symmetry(self): """Return the symmetry dict, which is a mapping c0 -> c1 meaning that component c0 is represented by component c1.""" return self._element.symmetry() def num_sub_elements(self): "Return number of sub elements" return self._element.num_sub_elements() #return 1 def sub_elements(self): "Return list of sub elements" return self._element.sub_elements() #return [self._element] def num_restricted_sub_elements(self): # FIXME: Use this where intended, for disambiguation # w.r.t. different sub_elements meanings. "Return number of restricted sub elements." return 1 def restricted_sub_elements(self): # FIXME: Use this where intended, for disambiguation # w.r.t. different sub_elements meanings. "Return list of restricted sub elements." return (self._element,) def signature_data(self, renumbering): data = ("RestrictedElement", self._element.signature_data(renumbering), repr(self._cell_restriction)) # Note: I'm pretty sure repr is safe here but that may change return data ufl-1.6.0/ufl/finiteelement/tensorproductelement.py000066400000000000000000000102651255567402100225740ustar00rootroot00000000000000"This module defines the UFL finite element classes." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Kristian B. Oelgaard # Modified by Marie E. Rognes 2010, 2012 from ufl.assertions import ufl_assert from ufl.permutation import compute_indices from ufl.common import product, istr, EmptyDict from ufl.geometry import as_domain, as_cell, ProductCell, ProductDomain from ufl.log import info_blue, warning, warning_blue, error from ufl.finiteelement.finiteelementbase import FiniteElementBase class TensorProductElement(FiniteElementBase): r"""The tensor product of d element spaces: .. math:: V = V_0 \otimes V_1 \otimes ... \otimes V_d Given bases {phi_i} for V_i for i = 1, ...., d, { phi_0 * phi_1 * ... * phi_d } forms a basis for V. """ __slots__ = ("_sub_elements",) def __init__(self, *elements): "Create TensorProductElement from a given list of elements." self._sub_elements = elements ufl_assert(len(self._sub_elements) > 0, "Cannot create TensorProductElement from empty list.") self._repr = "TensorProductElement(%s)" % ", ".join(repr(e) for e in self._sub_elements) family = "TensorProductElement" # Define domain as the product of each elements domain domain = ProductDomain([e.domain() for e in self._sub_elements]) # Define polynomial degree as the maximal of each subelement degrees = { e.degree() for e in self._sub_elements } - { None } degree = max(degrees) if degrees else None # No quadrature scheme defined quad_scheme = None # For now, check that all subelements have the same value # shape, and use this. # TODO: Not sure if this makes sense, what kind of product is used to build the basis? value_shape = self._sub_elements[0].value_shape() reference_value_shape = self._sub_elements[0].reference_value_shape() ufl_assert(all(e.value_shape() == value_shape for e in self._sub_elements), "All subelements in must have same value shape") FiniteElementBase.__init__(self, family, domain, degree, quad_scheme, value_shape, reference_value_shape) def reconstruction_signature(self): """Format as string for evaluation as Python object. For use with cross language frameworks, stored in generated code and evaluated later in Python to reconstruct this object. This differs from repr in that it does not include domain label and data, which must be reconstructed or supplied by other means. """ return "TensorProductElement(%s)" % (', '.join(e.reconstruction_signature() for e in self._sub_elements),) def mapping(self): error("The mapping of a mixed element is not defined. Inspect subelements instead.") def num_sub_elements(self): "Return number of subelements." return len(self._sub_elements) def sub_elements(self): "Return subelements (factors)." return self._sub_elements def __str__(self): "Pretty-print." return "TensorProductElement(%s)" \ % str([str(e) for e in self.sub_elements()]) def shortstr(self): "Short pretty-print." return "TensorProductElement(%s)" \ % str([e.shortstr() for e in self.sub_elements()]) def signature_data(self, renumbering): data = ("TensorProductElement", tuple(e.signature_data(renumbering) for e in self._sub_elements)) return data ufl-1.6.0/ufl/finiteelement/traceelement.py000066400000000000000000000044441255567402100207610ustar00rootroot00000000000000# Copyright (C) 2014 Andrew T. T. McRae # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from ufl.finiteelement.finiteelementbase import FiniteElementBase class TraceElement(FiniteElementBase): """A finite element space: the trace of a given hdiv element. This is effectively a scalar-valued restriction which is non-zero only on cell facets.""" def __init__(self, element): self._element = element self._repr = "TraceElement(%s)" % str(element._repr) family = "TraceElement" domain = element.domain() degree = element.degree() quad_scheme = element.quadrature_scheme() value_shape = () reference_value_shape = () FiniteElementBase.__init__(self, family, domain, degree, quad_scheme, value_shape, reference_value_shape) def reconstruct(self, **kwargs): """Construct a new TraceElement object with some properties replaced with new values.""" domain = kwargs.get("domain", self.domain()) ele = self._element.reconstruct(domain=domain) return TraceElement(ele) def reconstruction_signature(self): return "TraceElement(%s)" % self._element.reconstruction_signature() def signature_data(self, renumbering): data = ("TraceElement", self._element.signature_data(renumbering), ("no domain" if self._domain is None else self._domain .signature_data(renumbering))) return data def __str__(self): return "TraceElement(%s)" % str(self._element) def shortstr(self): return "TraceElement(%s)" % str(self._element.shortstr()) def __repr__(self): return self._repr ufl-1.6.0/ufl/form.py000066400000000000000000000416041255567402100144230ustar00rootroot00000000000000"The Form class." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2009-2011. import hashlib from itertools import chain from collections import defaultdict from ufl.log import error, deprecate from ufl.assertions import ufl_assert import ufl.measure from ufl.integral import Integral, Measure from ufl.checks import is_scalar_constant_expression from ufl.equation import Equation from ufl.core.expr import Expr from ufl.constantvalue import Zero from ufl.protocols import id_or_none # --- The Form class, representing a complete variational form or functional --- def _sorted_integrals(integrals): """Sort integrals by domain id, integral type, subdomain id for a more stable signature computation.""" # Group integrals in multilevel dict by keys [domain][integral_type][subdomain_id] integrals_dict = defaultdict(lambda:defaultdict(lambda:defaultdict(list))) for integral in integrals: d = integral.domain() ufl_assert(d is not None, "An Integral without a Domain is now illegal.") it = integral.integral_type() si = integral.subdomain_id() integrals_dict[d][it][si] += [integral] all_integrals = [] # Order integrals canonically to increase signature stability for d in sorted(integrals_dict): # Assuming Domain is sortable for it in sorted(integrals_dict[d]): # str is sortable for si in sorted(integrals_dict[d][it], key=lambda x: (type(x).__name__, x)): # int/str are sortable unsorted_integrals = integrals_dict[d][it][si] # TODO: At this point we could order integrals by metadata and integrand, # or even add the integrands with the same metadata. This is done # in accumulate_integrands_with_same_metadata in algorithms/domain_analysis.py # and would further increase the signature stability. all_integrals.extend(unsorted_integrals) #integrals_dict[d][it][si] = unsorted_integrals return tuple(all_integrals) #, integrals_dict class Form(object): """Description of a weak form consisting of a sum of integrals over subdomains.""" __slots__ = ( # --- List of Integral objects (a Form is a sum of these Integrals, everything else is derived) "_integrals", # --- Internal variables for caching various data "_integration_domains", "_domain_numbering", "_subdomain_data", "_arguments", "_coefficients", "_coefficient_numbering", "_hash", "_signature", # --- Dict that external frameworks can place framework-specific # data in to be carried with the form # Never use this internally in ufl! "_cache", ) def __init__(self, integrals): # Basic input checking (further compatibilty analysis happens later) ufl_assert(all(isinstance(itg, Integral) for itg in integrals), "Expecting list of integrals.") # Store integrals sorted canonically to increase signature stability self._integrals = _sorted_integrals(integrals) # Internal variables for caching domain data self._integration_domains = None self._domain_numbering = None # Internal variables for caching subdomain data self._subdomain_data = None # Internal variables for caching form argument data self._arguments = None self._coefficients = None self._coefficient_numbering = None # Internal variables for caching of hash and signature after first request self._hash = None self._signature = None # Never use this internally in ufl! self._cache = {} # --- Accessor interface --- def integrals(self): "Return a sequence of all integrals in form." return self._integrals def integrals_by_type(self, integral_type): "Return a sequence of all integrals with a particular domain type." return tuple(integral for integral in self.integrals() if integral.integral_type() == integral_type) #def integrals_dict(self): # "Returns a mapping on the form { domain: { integral_type: { subdomain_id: integral_list } } }." # return self._integrals_dict def empty(self): "Returns whether the form has no integrals." return self.integrals() == () def domains(self): """Return the geometric integration domains occuring in the form. NB! This does not include domains of coefficients defined on other meshes. The return type is a tuple even if only a single domain exists. """ if self._integration_domains is None: self._analyze_domains() return self._integration_domains def cell(self): "Return the single cell this form is defined on, fails if multiple cells are found." domains = self.domains() ufl_assert(all(domain.cell() == domains[0].cell() for domain in domains), "Calling Form.domain() is only valid if all integrals share domain.") # Need to support missing domain to allow # assemble(Constant(1)*dx, mesh=mesh) in dolfin return domains[0].cell() if domains else None def domain(self): """Return the single geometric integration domain occuring in the form. Fails if multiple domains are found. NB! This does not include domains of coefficients defined on other meshes, look at form data for that additional information. """ domains = self.domains() ufl_assert(all(domain == domains[0] for domain in domains), "Calling Form.domain() is only valid if all integrals share domain.") # Need to support missing domain to allow # assemble(Constant(1)*dx, mesh=mesh) in dolfin return domains[0] if domains else None def domain_numbering(self): "Return a contiguous numbering of domains in a mapping { domain: number }." if self._domain_numbering is None: self._analyze_domains() return self._domain_numbering def subdomain_data(self): "Returns a mapping on the form { domain: { integral_type: subdomain_data } }." if self._subdomain_data is None: self._analyze_subdomain_data() return self._subdomain_data def max_subdomain_ids(self): "Returns a mapping on the form { domain: { integral_type: max_subdomain_id } }." if self._max_subdomain_ids is None: self._analyze_subdomain_data() return self._max_subdomain_ids def arguments(self): "Return all Argument objects found in form." if self._arguments is None: self._analyze_form_arguments() return self._arguments def coefficients(self): "Return all Coefficient objects found in form." if self._coefficients is None: self._analyze_form_arguments() return self._coefficients def coefficient_numbering(self): "Return a contiguous numbering of coefficients in a mapping { coefficient: number }." if self._coefficient_numbering is None: self._analyze_form_arguments() return self._coefficient_numbering def signature(self): "Signature for use with jit cache (independent of incidental numbering of indices etc.)" if self._signature is None: self._compute_signature() return self._signature # --- Operator implementations --- def __hash__(self): "Hash code for use in dicts (includes incidental numbering of indices etc.)" if self._hash is None: self._hash = hash(tuple(hash(itg) for itg in self.integrals())) return self._hash def __eq__(self, other): """Delayed evaluation of the __eq__ operator! Just 'lhs_form == rhs_form' gives an Equation, while 'bool(lhs_form == rhs_form)' delegates to lhs_form.equals(rhs_form). """ return Equation(self, other) def equals(self, other): "Evaluate 'bool(lhs_form == rhs_form)'." if type(other) != Form: return False if len(self._integrals) != len(other._integrals): return False if hash(self) != hash(other): return False return all(a == b for a, b in zip(self._integrals, other._integrals)) def __radd__(self, other): # Ordering of form additions make no difference return self.__add__(other) def __add__(self, other): if isinstance(other, Form): # Add integrals from both forms return Form(list(chain(self.integrals(), other.integrals()))) elif isinstance(other, (int, float)) and other == 0: # Allow adding 0 or 0.0 as a no-op, needed for sum([a,b]) return self elif isinstance(other, Zero) and not (other.ufl_shape or other.ufl_free_indices): # Allow adding ufl Zero as a no-op, needed for sum([a,b]) return self else: # Let python protocols do their job if we don't handle it return NotImplemented def __sub__(self, other): "Subtract other form from this one." return self + (-other) def __neg__(self): """Negate all integrals in form. This enables the handy "-form" syntax for e.g. the linearized system (J, -F) from a nonlinear form F.""" return Form([-itg for itg in self.integrals()]) def __rmul__(self, scalar): "Multiply all integrals in form with constant scalar value." # This enables the handy "0*form" or "dt*form" syntax if is_scalar_constant_expression(scalar): return Form([scalar*itg for itg in self.integrals()]) return NotImplemented def __mul__(self, coefficient): "UFL form operator: Take the action of this form on the given coefficient." if isinstance(coefficient, Expr): #Coefficient): # TODO: Check whatever makes sense from ufl.formoperators import action return action(self, coefficient) return NotImplemented # --- String conversion functions, for UI purposes only --- def __str__(self): "Compute shorter string representation of form. This can be huge for complicated forms." # TODO: Add warning here to check if anyone actually calls it in libraries s = "\n + ".join(str(itg) for itg in self.integrals()) return s or "" def __repr__(self): "Compute repr string of form. This can be huge for complicated forms." # TODO: Add warning here to check if anyone actually calls it in libraries # Not caching this because it can be huge r = "Form([%s])" % ", ".join(repr(itg) for itg in self.integrals()) return r def x_repr_latex_(self): # TODO: This works, but enable when form latex rendering is fixed from ufl.algorithms import ufl2latex return "$$%s$$" % ufl2latex(self) def x_repr_png_(self): # TODO: This works, but enable when form latex rendering is fixed from IPython.lib.latextools import latex_to_png return latex_to_png(self._repr_latex_()) # --- Analysis functions, precomputation and caching of various quantities --- def _analyze_domains(self): # TODO: join_domains function needs work, later when dolfin integration of a Domain or ufl.Mesh class is finished. from ufl.geometry import join_domains # Collect integration domains and make canonical list of them integration_domains = join_domains([itg.domain() for itg in self._integrals]) self._integration_domains = tuple(sorted(integration_domains, key=lambda x: x.label())) # TODO: Not including domains from coefficients and arguments here, may need that later self._domain_numbering = dict((d, i) for i, d in enumerate(self._integration_domains)) def _analyze_subdomain_data(self): integration_domains = self.domains() integrals = self.integrals() # Make clear data structures to collect subdomain data in subdomain_data = {} for domain in integration_domains: subdomain_data[domain] = {} for integral in integrals: # Get integral properties domain = integral.domain() it = integral.integral_type() sd = integral.subdomain_data() # Collect subdomain data data = subdomain_data[domain].get(it) if data is None: subdomain_data[domain][it] = sd elif sd is not None: ufl_assert(data.ufl_id() == sd.ufl_id(), "Integrals in form have different subdomain_data objects.") self._subdomain_data = subdomain_data def _analyze_form_arguments(self): "Analyze which Argument and Coefficient objects can be found in the form." from ufl.algorithms.analysis import extract_arguments_and_coefficients arguments, coefficients = extract_arguments_and_coefficients(self) # Include coordinate coefficients from integration domains domains = self.domains() coordinates = [c for c in (domain.coordinates() for domain in domains) if c is not None] coefficients.extend(coordinates) # TODO: Not including domains from coefficients and arguments here. Will we need that later? # I believe argument domains must be among integration domains in each integral, anything else is not well defined. # Furthermore if a coefficient domain differ from the integration domain, it will # currently be interpolated to the same element on the integration domain in dolfin. # Therefore their domain should not be included here. # In the future we may generate code for quadrature point evaluation of these instead, # and then the coefficient domains are still of no value in the code generation process. # Define canonical numbering of arguments and coefficients self._arguments = tuple(sorted(set(arguments), key=lambda x: x.number())) self._coefficients = tuple(sorted(set(coefficients), key=lambda x: x.count())) self._coefficient_numbering = dict((c, i) for i, c in enumerate(self._coefficients)) def _compute_renumbering(self): # Include integration domains and coefficients in renumbering dn = self.domain_numbering() cn = self.coefficient_numbering() renumbering = {} renumbering.update(dn) renumbering.update(cn) # Add domains of coefficients, these may include domains not among integration domains k = len(dn) for c in cn: d = c.domain() if d is not None and d not in renumbering: renumbering[d] = k k += 1 return renumbering def _compute_signature(self): from ufl.algorithms.signature import compute_form_signature self._signature = compute_form_signature(self, self._compute_renumbering()) def as_form(form): "Convert to form if not a form, otherwise return form." if not isinstance(form, Form): error("Unable to convert object to a UFL form: %s" % repr(form)) return form def replace_integral_domains(form, common_domain): # TODO: Move elsewhere """Given a form and a domain, assign a common integration domain to all integrals. Does not modify the input form (Form should always be immutable). This is to support ill formed forms with no domain specified, some times occuring in pydolfin, e.g. assemble(1*dx, mesh=mesh). """ domains = form.domains() if common_domain is not None: gdim = common_domain.geometric_dimension() tdim = common_domain.topological_dimension() ufl_assert(all((gdim == domain.geometric_dimension() and tdim == domain.topological_dimension()) for domain in domains), "Common domain does not share dimensions with form domains.") reconstruct = False integrals = [] for itg in form.integrals(): domain = itg.domain() if domain is None or domain.label() != common_domain.label(): itg = itg.reconstruct(domain=common_domain) reconstruct = True integrals.append(itg) if reconstruct: form = Form(integrals) return form ufl-1.6.0/ufl/formatting/000077500000000000000000000000001255567402100152535ustar00rootroot00000000000000ufl-1.6.0/ufl/formatting/__init__.py000066400000000000000000000000001255567402100173520ustar00rootroot00000000000000ufl-1.6.0/ufl/formatting/graph.py000066400000000000000000000177071255567402100167420ustar00rootroot00000000000000"""Algorithms for working with linearized computational graphs.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from collections import defaultdict from six.moves import xrange as range from six.moves import map from heapq import heapify, heappop, heappush from ufl.corealg.traversal import pre_traversal from ufl.corealg.multifunction import MultiFunction # O(n) = O(|V|) = O(|E|), since |E| < c|V| for a fairly small c. #--- Basic utility functions --- def lists(n): return [[] for i in range(n)] def len_items(sequence): return list(map(len, sequence)) #--- Graph building functions --- def build_graph(expr): # O(n) """Build a linearized graph from an UFL Expr. Returns G = (V, E), with V being a list of graph nodes (Expr objects) in post traversal ordering and E being a list of edges. Each edge is represented as a (i, j) tuple where i and j are vertex indices into V. """ V = [] E = [] handled = {} #for v in post_traversal(expr): for v in reversed(list(pre_traversal(expr))): i = handled.get(v) if i is None: i = len(V) handled[v] = i V.append(v) for o in v.ufl_operands: j = handled[o] e = (i, j) E.append(e) G = V, E return G def extract_incoming_edges(G): # O(n) "Build lists of incoming edges to each vertex in a linearized graph." V, E = G n = len(V) Ein = lists(n) for i, e in enumerate(E): Ein[e[1]].append(i) return Ein def extract_outgoing_edges(G): # O(n) "Build list of outgoing edges from each vertex in a linearized graph." V, E = G n = len(V) Eout = lists(n) for i, e in enumerate(E): Eout[e[0]].append(i) return Eout def extract_incoming_vertex_connections(G): # O(n) """Build lists of vertices in incoming and outgoing edges to and from each vertex in a linearized graph. Returns lists Vin and Vout.""" V, E = G n = len(V) Vin = lists(n) for a, b in E: Vin[b].append(a) return Vin def extract_outgoing_vertex_connections(G): # O(n) """Build lists of vertices in incoming and outgoing edges to and from each vertex in a linearized graph. Returns lists Vin and Vout.""" V, E = G n = len(V) Vout = lists(n) for a, b in E: Vout[a].append(b) return Vout #--- Graph class --- class Graph: "Graph class which computes connectivity on demand." def __init__(self, expression): self._V, self._E = build_graph(expression) self._Ein = None self._Eout = None self._Vin = None self._Vout = None def V(self): return self._V def E(self): return self._E def Ein(self): if self._Ein is None: self._Ein = extract_incoming_edges((self._V, self._E)) return self._Ein def Eout(self): if self._Eout is None: self._Eout = extract_outgoing_edges((self._V, self._E)) return self._Eout def Vin(self): if self._Vin is None: self._Vin = extract_incoming_vertex_connections((self._V, self._E)) return self._Vin def Vout(self): if self._Vout is None: self._Vout = extract_outgoing_vertex_connections((self._V, self._E)) return self._Vout def __iter__(self): return iter((self._V, self._E)) #--- Scheduling algorithms --- class HeapItem(object): def __init__(self, incoming, outgoing, i): self.incoming = incoming self.outgoing = outgoing self.i = i def __lt__(self, other): a = (self.outgoing[self.i], self.incoming[self.i]) b = (other.outgoing[other.i], other.incoming[other.i]) return a < b def __le__(self, other): a = (self.outgoing[self.i], self.incoming[self.i]) b = (other.outgoing[other.i], other.incoming[other.i]) return a <= b def __eq__(self, other): a = (self.outgoing[self.i], self.incoming[self.i]) b = (other.outgoing[other.i], other.incoming[other.i]) return a == b def depth_first_ordering(G): V, E = G Vin = G.Vin() Vout = G.Vout() Ein_count = len_items(Vin) Eout_count = len_items(Vout) # Make a list and a heap of the same items n = len(V) q = [HeapItem(Ein_count, Eout_count, i) for i in range(n)] heapify(q) ordering = [] while q: item = heappop(q) iv = item.i ordering.append(iv) for i in Vin[iv]: Eout_count[i] -= 1 # Resort heap, worst case linear time, makes this algorithm O(n^2)... TODO: Not good! heapify(q) # TODO: Can later accumulate dependencies as well during dft-like algorithm. return ordering #--- Graph partitoning --- class StringDependencyDefiner(MultiFunction): """Given an expr, returns a frozenset of its dependencies. Possible dependency values are: "c" - depends on runtime information like the cell, local<->global coordinate mappings, facet normals, or coefficients "x" - depends on local coordinates "v%d" % i - depends on argument i, for i in [0,rank) """ def __init__(self, argument_deps = None, coefficient_deps = None): MultiFunction.__init__(self) if argument_deps is None: argument_deps = {} if coefficient_deps is None: coefficient_deps = {} self.argument_deps = argument_deps self.coefficient_deps = coefficient_deps def expr(self, o): return frozenset() def argument(self, x): default = frozenset(("v%d" % x.number(), "x")) # TODO: This is missing the part, but this code is ready for deletion anyway? return self.argument_deps.get(x, default) def coefficient(self, x): default = frozenset(("c", "x")) return self.coefficient_deps.get(x, default) def geometric_quantity(self, x): deps = frozenset(("c", "x",)) return deps def facet_normal(self, o): deps = frozenset(("c",)) # Enabling coordinate dependency for higher order geometries # (not handled anywhere else though, so consider this experimental) #if o.has_higher_degree_cell_geometry(): # deps = deps | frozenset(("x",)) return deps def spatial_derivative(self, o): # TODO: What about (basis) functions of which derivatives are constant? Should special-case spatial_derivative in partition(). deps = frozenset(("c",)) # Enabling coordinate dependency for higher order geometries (not handled anywhere else though). #if o.has_higher_degree_cell_geometry(): # deps = deps | frozenset(("x",)) return deps dd = StringDependencyDefiner() def string_set_criteria(v, keys): # Using sets of ufl objects key = dd(v) for k in keys: key |= k return frozenset(key) def partition(G, criteria=string_set_criteria): V, E = G n = len(V) Vout = G.Vout() partitions = defaultdict(list) keys = [None]*n for iv, v in enumerate(V): # Get keys from all outgoing edges edge_keys = [keys[ii] for ii in Vout[iv]] # Calculate key for this vertex key = criteria(v, edge_keys) # Store mappings from key to vertex and back partitions[key].append(iv) keys[iv] = key return partitions, keys ufl-1.6.0/ufl/formatting/latextools.py000066400000000000000000000071251255567402100200300ustar00rootroot00000000000000"This module defines basic utilities for stitching together LaTeX documents." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # --- Basic LaTeX tools --- documenttemplate = """\ \\documentclass{article} \\usepackage{amsmath} \\title{%s} \\begin{document} \\maketitle{} %s \\end{document} """ sectiontemplate = """\ \\section{%s} %s """ subsectiontemplate = """\ \\subsection{%s} %s """ subsubsectiontemplate = """\ \\subsubsection{%s} %s """ itemizetemplate = """\ \\begin{itemize} %s \\end{itemize} """ aligntemplate = """\ \\begin{align} %s \\end{align} """ verbatimtemplate = """\ \\begin{verbatim} %s \\end{verbatim} """ def verbatim(string): return verbatimtemplate % string def align(lines): # Calculate column lengths if isinstance(lines[0], str): body = " \\\\\n".join(l for l in lines) else: n = len(lines[0]) collengths = [0]*n for l in lines: for i, s in enumerate(l): collengths[i] = max(collengths[i], len(s)) def coljoin(cols): return " & ".join(c.ljust(collengths[i]) for (i, c) in enumerate(cols)) body = " \\\\\n".join(coljoin(l) for l in lines) return aligntemplate % body def itemize(items): body = "\n".join(items) return itemizetemplate % body def subsubsection(s): if isinstance(s, str): return s if isinstance(s, tuple): title, body = s if isinstance(body, list): body = itemize(list(map(str, body))) return subsubsectiontemplate % (title, body) def subsection(s): if isinstance(s, str): return s if isinstance(s, tuple): title, body = s if isinstance(body, list): body = "\n".join(subsubsection(ss) for ss in body) return subsectiontemplate % (title, body) def section(s): if isinstance(s, str): return s if isinstance(s, tuple): title, body = s if isinstance(body, list): body = "\n".join(subsection(ss) for ss in body) return sectiontemplate % (title, body) def document(title, sections): body = "\n".join(section(s) for s in sections) return documenttemplate % (title, body) def testdocument(): title = "Test title 1" sections = ["sec1", "sec2"] print(document(title, sections)) title = "Test title 2" sections = [("sec1", "secbody1"), ("sec2", "secbody2")] print(document(title, sections)) title = "Test title 3" section1 = [("subsec1", "subsecbody1"), ("subsec2", "subsecbody2")] section2 = [("subsec1", "subsecbody1"), ("subsec2", "subsecbody2"), ("subsec3", "subsecbody3"), ] section3 = "\\section{manual sec}\ntestelest" sections = [("sec1", section1), ("sec2", section2), ("sec3", section3)] print(document(title, sections)) matrix = [ ("a(...) ", "= \\int_\\Omega foo dx0"), ("", "+ \\int_\\Omega foo dx1"), ("", "+ \\int_\\Omega foo dx1"), ] print(align(matrix)) ufl-1.6.0/ufl/formatting/printing.py000066400000000000000000000106671255567402100174710ustar00rootroot00000000000000"""A collection of utility algorithms for printing of UFL objects, mostly intended for debugging purposes.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg 2009, 2014 from itertools import chain from ufl.log import error from ufl.assertions import ufl_assert from ufl.core.expr import Expr from ufl.core.terminal import Terminal from ufl.form import Form from ufl.integral import Integral, Measure #--- Utilities for constructing informative strings from UFL objects --- def integral_info(integral): ufl_assert(isinstance(integral, Integral), "Expecting an Integral.") s = " Integral:\n" s += " Type:\n" s += " %s\n" % integral.integral_type() s += " Domain:\n" s += " %r\n" % integral.domain() s += " Domain id:\n" s += " %r\n" % integral.subdomain_id() s += " Domain data:\n" s += " %s\n" % integral.subdomain_data() s += " Compiler metadata:\n" s += " %s\n" % integral.metadata() s += " Integrand expression representation:\n" s += " %r\n" % integral.integrand() s += " Integrand expression short form:\n" s += " %s" % integral.integrand() return s def form_info(form): ufl_assert(isinstance(form, Form), "Expecting a Form.") bf = form.arguments() cf = form.coefficients() s = "Form info:\n" s += " rank: %d\n" % len(bf) s += " num_coefficients: %d\n" % len(cf) s += "\n" for f in cf: if f._name: s += "\n" s += " Coefficient %d is named '%s'" % (f._count, f._name) s += "\n" integrals = form.integrals() integral_types = sorted(set(itg.integral_type() for itg in integrals)) for integral_type in integral_types: itgs = form.integrals_by_type(integral_type) s += " num_{0}_integrals: {1}\n".format(integral_type, len(itgs)) s += "\n" for integral_type in integral_types: itgs = form.integrals_by_type(integral_type) for itg in itgs: s += integral_info(itg) s += "\n" return s def _indent_string(n): return " "*n def _tree_format_expression(expression, indentation, parentheses): ind = _indent_string(indentation) if expression._ufl_is_terminal_: s = ind + "%s" % repr(expression) else: sops = [_tree_format_expression(o, indentation+1, parentheses) for o in expression.ufl_operands] s = ind + "%s\n" % expression._ufl_class_.__name__ if parentheses and len(sops) > 1: s += ind + "(\n" s += "\n".join(sops) if parentheses and len(sops) > 1: s += "\n" + ind + ")" return s def tree_format(expression, indentation=0, parentheses=True): s = "" if isinstance(expression, Form): form = expression integrals = form.integrals() integral_types = sorted(set(itg.integral_type() for itg in integrals)) itgs = [] for integral_type in integral_types: itgs += list(form.integrals_by_type(integral_type)) ind = _indent_string(indentation) s += ind + "Form:\n" s += "\n".join(tree_format(itg, indentation+1, parentheses) for itg in itgs) elif isinstance(expression, Integral): ind = _indent_string(indentation) s += ind + "Integral:\n" ind = _indent_string(indentation+1) s += ind + "integral type: %s\n" % expression.integral_type() s += ind + "subdomain id: %s\n" % expression.subdomain_id() s += ind + "integrand:\n" s += tree_format(expression._integrand, indentation+2, parentheses) elif isinstance(expression, Expr): s += _tree_format_expression(expression, indentation, parentheses) else: error("Invalid object type %s" % type(expression)) return s ufl-1.6.0/ufl/formatting/ufl2dot.py000066400000000000000000000176241255567402100172160ustar00rootroot00000000000000"""A collection of utility algorithms for printing of UFL objects in the DOT graph visualization language, mostly intended for debugging purposers.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from itertools import chain from six import itervalues from six.moves import xrange as range from ufl.log import error from ufl.core.expr import Expr from ufl.core.terminal import Terminal from ufl.form import Form from ufl.integral import Measure from ufl.variable import Variable from ufl.constantvalue import ScalarValue from ufl.classes import FormArgument, MultiIndex, NegativeRestricted, PositiveRestricted from ufl.algorithms.multifunction import MultiFunction class ReprLabeller(MultiFunction): def __init__(self): MultiFunction.__init__(self) def terminal(self, e): return repr(e) def operator(self, e): return e._ufl_class_.__name__.split(".")[-1] class CompactLabeller(ReprLabeller): def __init__(self, function_mapping=None): ReprLabeller.__init__(self) self._function_mapping = function_mapping # Terminals: def scalar_value(self, e): return repr(e._value) def zero(self, e): return "0" def identity(self, e): return "Id" def multi_index(self, e): return str(e) def form_argument(self, e): return self._function_mapping.get(id(e)) or str(e) def geometric_quantity(self, e): return str(e) # Operators: def sum(self, e): return "+" def product(self, e): return "*" def division(self, e): return "/" def power(self, e): return "**" def math_function(self, e): return e._name def index_sum(self, e): return "∑" def indexed(self, e): return "[]" def component_tensor(self, e): # TODO: Understandable short notation for this? return "][" def negative_restricted(self, e): return "[-]" def positive_restricted(self, e): return "[+]" def cell_avg(self, e): # TODO: Understandable short notation for this? return "_K_" def facet_avg(self, e): # TODO: Understandable short notation for this? return "_F_" def inner(self, e): return "inner" def dot(self, e): return "dot" def outer(self, e): return "outer" def transposed(self, e): return "transp." def determinant(self, e): return "det" def trace(self, e): return "tr" def dev(self, e): return "dev" def skew(self, e): return "skew" def grad(self, e): return "grad" def div(self, e): return "div" def curl(self, e): return "curl" def nabla_grad(self, e): return "nabla_grad" def nabla_div(self, e): return "nabla_div" def diff(self, e): return "diff" # Make this class like the ones above to use fancy math symbol labels class2label = { \ "IndexSum": "∑", "Sum": "∑", "Product": "∏", "Division": "/", "Inner": ":", "Dot": "⋅", "Outer": "⊗", "Grad": "grad", "Div": "div", "NablaGrad": "∇⊗", "NablaDiv": "∇⋅", "Curl": "∇×", } class FancyLabeller(CompactLabeller): pass def build_entities(e, nodes, edges, nodeoffset, prefix="", labeller=None): # TODO: Maybe this can be cleaner written using the graph utilities. # TODO: To collapse equal nodes with different objects, do not use id as key. Make this an option? # Cutoff if we have handled e before if id(e) in nodes: return if labeller is None: labeller = ReprLabeller() # Special-case Variable instances if isinstance(e, Variable): # FIXME: Is this really necessary? ops = (e._expression,) label = "variable %d" % e._label._count else: ops = e.ufl_operands label = labeller(e) # Create node for parent e nodename = "%sn%04d" % (prefix, len(nodes) + nodeoffset) nodes[id(e)] = (nodename, label) # Handle all children recursively n = len(ops) if n == 2: #oplabels = ["left", "right"] oplabels = ["L", "R"] elif n > 2: oplabels = ["op%d" % i for i in range(n)] else: oplabels = [None]*n for i, o in enumerate(ops): # Handle entire subtree for expression o build_entities(o, nodes, edges, nodeoffset, prefix, labeller) # Add edge between e and child node o edges.append((id(e), id(o), oplabels[i])) def format_entities(nodes, edges): entities = [] for (nodename, label) in itervalues(nodes): node = ' %s [label="%s"];' % (nodename, label) entities.append(node) for (aid, bid, label) in edges: anodename = nodes[aid][0] bnodename = nodes[bid][0] if label is None: edge = ' %s -> %s ;' % (anodename, bnodename) else: edge = ' %s -> %s [label="%s"] ;' % (anodename, bnodename, label) entities.append(edge) return "\n".join(entities) integralgraphformat = """ %(node)s [label="%(label)s"] form_%(formname)s -> %(node)s ; %(node)s -> %(root)s ; %(entities)s""" exprgraphformat = """ digraph ufl_expression { %s }""" def ufl2dot(expression, formname="a", nodeoffset=0, begin=True, end=True, labeling="repr", object_names=None): if labeling == "repr": labeller = ReprLabeller() elif labeling == "compact": labeller = CompactLabeller(object_names or {}) print(object_names) if isinstance(expression, Form): form = expression subgraphs = [] k = 0 for itgs in form.integrals(): for itg in itgs: prefix = "itg%d_" % k integralkey = "%s%s" % (itg.integral_type(), itg.subdomain_id()) integrallabel = "%s %s" % (itg.integral_type().capitalize().replace("_", " "), "integral") if len(itgs) > 1: integrallabel += " %s" % (itg.subdomain_id(),) integrand = itg.integrand() nodes = {} edges = [] build_entities(integrand, nodes, edges, nodeoffset, prefix, labeller) rootnode = nodes[id(integrand)][0] entitylist = format_entities(nodes, edges) integralnode = "%s_%s" % (formname, integralkey) subgraphs.append(integralgraphformat % { 'node': integralnode, 'label': integrallabel, 'formname': formname, 'root': rootnode, 'entities': entitylist, }) nodeoffset += len(nodes) s = "" if begin: s += 'digraph ufl_form\n{\n node [shape="box"] ;\n' s += ' form_%s [label="Form %s"] ;' % (formname, formname) s += "\n".join(subgraphs) if end: s += "\n}" elif isinstance(expression, Expr): nodes = {} edges = [] build_entities(expression, nodes, edges, nodeoffset, '', labeller) entitylist = format_entities(nodes, edges) s = exprgraphformat % entitylist nodeoffset += len(nodes) else: error("Invalid object type %s" % type(expression)) return s, nodeoffset ufl-1.6.0/ufl/formatting/ufl2latex.py000066400000000000000000000730021255567402100175350ustar00rootroot00000000000000"""This module defines expression transformation utilities, either converting UFL expressions to new UFL expressions or converting UFL expressions to other representations.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2008-2009. # Modified by Kristian B. Oelgaard, 2011 from itertools import chain import ufl from ufl.log import error, warning from ufl.assertions import ufl_assert from ufl.common import write_file, pdflatex, openpdf from ufl.permutation import compute_indices # All classes: from ufl.variable import Variable from ufl.core.multiindex import Index, FixedIndex from ufl.indexed import Indexed from ufl.tensors import ListTensor, ComponentTensor from ufl.algebra import Sum, Product, Division, Power, Abs from ufl.indexsum import IndexSum from ufl.tensoralgebra import Transposed, Outer, Inner, Dot, Cross, Trace, Determinant, Inverse, Deviatoric, Cofactor from ufl.mathfunctions import Sqrt, Exp, Ln, Cos, Sin, Tan, Cosh, Sinh, Tanh, Acos, Asin, Atan, Atan2, Erf, BesselJ, BesselY, BesselI, BesselK from ufl.restriction import PositiveRestricted, NegativeRestricted, CellAvg, FacetAvg from ufl.differentiation import VariableDerivative, Grad, Div, Curl, NablaGrad, NablaDiv from ufl.conditional import EQ, NE, LE, GE, LT, GT, Conditional from ufl.form import Form from ufl.integral import Measure from ufl.classes import terminal_classes from ufl.geometry import Domain # Other algorithms: from ufl.algorithms.compute_form_data import compute_form_data from ufl.algorithms.formfiles import load_forms from ufl.algorithms.transformer import Transformer from ufl.formatting.graph import build_graph, partition, extract_outgoing_vertex_connections from ufl.formatting.latextools import align, document, verbatim # TODO: Maybe this can be cleaner written using the graph utilities def _extract_variables(a): """Build a list of all Variable objects in a, which can be a Form, Integral or Expr. The ordering in the list obeys dependency order.""" handled = set() variables = [] for e in iter_expressions(a): for o in post_traversal(e): if isinstance(o, Variable): expr, label = o.ufl_operands if not label in handled: variables.append(o) handled.add(label) return variables # --- Tools for LaTeX rendering of UFL expressions --- # TODO: Finish precedence mapping def build_precedence_map(): precedence_list = [] # TODO: Review this list very carefully! precedence_list.append((Sum,)) precedence_list.append((IndexSum,)) # TODO: What to do with these? precedence_list.append((ListTensor, ComponentTensor)) precedence_list.append((CellAvg, )) precedence_list.append((NegativeRestricted, PositiveRestricted)) precedence_list.append((Conditional,)) precedence_list.append((LE, GT, GE, NE, EQ, LT)) precedence_list.append((Div, Grad, NablaGrad, NablaDiv, Curl, VariableDerivative, Determinant, Trace, Cofactor, Inverse, Deviatoric)) precedence_list.append((Product, Division, Cross, Dot, Outer, Inner)) precedence_list.append((Indexed, Transposed, Power)) precedence_list.append((Abs, Cos, Cosh, Exp, Ln, Sin, Sinh, Sqrt, Tan, Tanh, Acos, Asin, Atan, Atan2, Erf, BesselJ, BesselY, BesselI, BesselK)) precedence_list.append((Variable,)) precedence_list.append(terminal_classes) precedence_map = {} k = 0 for p in precedence_list: for c in p: precedence_map[c] = k k += 1 return precedence_map # Utility for parentesizing string def par(s, condition=True): # TODO: Finish precedence handling by adding condition argument to calls to this function! if condition: return r"\left(%s\right)" % s return str(s) def format_index(ii): if isinstance(ii, FixedIndex): s = "%d" % ii._value elif isinstance(ii, Index): s = "i_{%d}" % ii._count else: error("Invalid index type %s." % type(ii)) return s def format_multi_index(ii, formatstring="%s"): return "".join(formatstring % format_index(i) for i in ii) def bfname(i, p): s = "" if p is None else (",%d"%(p,)) return "{v_h^{%d%s}}" % (i, s) def cfname(i): return "{w_h^%d}" % i # TODO: Handle line wrapping # TODO: Handle ListTensors of rank > 1 correctly class Expression2LatexHandler(Transformer): def __init__(self, argument_names=None, coefficient_names=None): Transformer.__init__(self) self.argument_names = argument_names self.coefficient_names = coefficient_names # --- Terminal objects --- def scalar_value(self, o): if o.ufl_shape: return r"{\mathbf %s}" % o._value return "{%s}" % o._value def zero(self, o): return "0" if not o.ufl_shape else r"{\mathbf 0}" def identity(self, o): return r"{\mathbf I}" def permutation_symbol(self, o): return r"{\mathbf \varepsilon}" def facet_normal(self, o): return r"{\mathbf n}" def argument(self, o): # Using ^ for argument numbering and _ for indexing since indexing is more common than exponentiation if self.argument_names is None: return bfname(o.number(), o.part()) return self.argument_names[(o.number(), o.part())] def coefficient(self, o): # Using ^ for coefficient numbering and _ for indexing since indexing is more common than exponentiation if self.coefficient_names is None: return cfname(o.count()) return self.coefficient_names[o.count()] constant = coefficient def multi_index(self, o): return format_multi_index(o, formatstring="{%s}") def variable(self, o): # TODO: Ensure variable has been handled e, l = o.ufl_operands return "s_{%d}" % l._count # --- Non-terminal objects --- def index_sum(self, o, f, i): return r"\sum_{%s}%s" % (i, par(f)) def sum(self, o, *ops): return " + ".join(par(op) for op in ops) def product(self, o, *ops): return " ".join(par(op) for op in ops) def division(self, o, a, b): return r"\frac{%s}{%s}" % (a, b) def abs(self, o, a): return r"\|%s\|" % a def transposed(self, o, a): return "{%s}^T" % par(a) def indexed(self, o, a, b): return "{%s}_{%s}" % (par(a), b) def variable_derivative(self, o, f, v): nom = r"\partial%s" % par(f) denom = r"\partial%s" % par(v) return r"\frac{%s}{%s}" % (nom, denom) def coefficient_derivative(self, o, f, w, v): nom = r"\partial%s" % par(f) denom = r"\partial%s" % par(w) return r"\frac{%s}{%s}[%s]" % (nom, denom, v) # TODO: Fix this syntax... def grad(self, o, f): return r"\mathbf{grad}{%s}" % par(f) def div(self, o, f): return r"\mathbf{grad}{%s}" % par(f) def nabla_grad(self, o, f): return r"\nabla{\otimes %s}" % par(f) def nabla_div(self, o, f): return r"\nabla{\cdot %s}" % par(f) def curl(self, o, f): return r"\nabla{\times %s}" % par(f) def sqrt(self, o, f): return r"%s^{\frac 1 2}" % par(f) def exp(self, o, f): return "e^{%s}" % f def ln(self, o, f): return r"\ln{%s}" % par(f) def cos(self, o, f): return r"\cos{%s}" % par(f) def sin(self, o, f): return r"\sin{%s}" % par(f) def tan(self, o, f): return r"\tan{%s}" % par(f) def cosh(self, o, f): return r"\cosh{%s}" % par(f) def sinh(self, o, f): return r"\sinh{%s}" % par(f) def tanh(self, o, f): return r"\tanh{%s}" % par(f) def acos(self, o, f): return r"\arccos{%s}" % par(f) def asin(self, o, f): return r"\arcsin{%s}" % par(f) def atan(self, o, f): return r"\arctan{%s}" % par(f) def atan2(self, o, f1, f2): return r"\arctan_2{%s,%s}" % (par(f1), par(f2)) def erf(self, o, f): return r"\erf{%s}" % par(f) def bessel_j(self, o, nu, f): return r"J_{%s}{%s}" % (nu, par(f)) def bessel_y(self, o, nu, f): return r"Y_{%s}{%s}" % (nu, par(f)) def bessel_i(self, o, nu, f): return r"I_{%s}{%s}" % (nu, par(f)) def bessel_K(self, o, nu, f): return r"K_{%s}{%s}" % (nu, par(f)) def power(self, o, a, b): return "{%s}^{%s}" % (par(a), par(b)) def outer(self, o, a, b): return r"{%s}\otimes{%s}" % (par(a), par(b)) def inner(self, o, a, b): return "{%s}:{%s}" % (par(a), par(b)) def dot(self, o, a, b): return r"{%s}\cdot{%s}" % (par(a), par(b)) def cross(self, o, a, b): return r"{%s}\times{%s}" % (par(a), par(b)) def trace(self, o, A): return "tr{%s}" % par(A) # TODO: Get built-in function syntax like \sin for this def determinant(self, o, A): return "det{%s}" % par(A) # TODO: Get built-in function syntax like \sin for this def inverse(self, o, A): return "{%s}^{-1}" % par(A) def deviatoric(self, o, A): return "dev{%s}" % par(A) # TODO: Get built-in function syntax like \sin for this def cofactor(self, o, A): return "cofac{%s}" % par(A) # TODO: Get built-in function syntax like \sin for this def skew(self, o, A): return "skew{%s}" % par(A) # TODO: Get built-in function syntax like \sin for this def sym(self, o, A): return "sym{%s}" % par(A) # TODO: Get built-in function syntax like \sin for this def list_tensor(self, o): shape = o.ufl_shape if len(shape) == 1: ops = [self.visit(op) for op in o.ufl_operands] l = " \\\\ \n ".join(ops) elif len(shape) == 2: rows = [] for row in o.ufl_operands: cols = (self.visit(op) for op in row.ufl_operands) rows.append( " & \n ".join(cols) ) l = " \\\\ \n ".join(rows) else: error("TODO: LaTeX handler for list tensor of rank 3 or higher not implemented!") return "\\left[\\begin{matrix}{%s}\\end{matrix}\\right]^T" % l def component_tensor(self, o, *ops): A, ii = ops return "\\left[A \\quad | \\quad A_{%s} = {%s} \\quad \\forall {%s} \\right]" % (ii, A, ii) def positive_restricted(self, o, f): return "{%s}^+" % par(f) def negative_restricted(self, o, f): return "{%s}^-" % par(f) def cell_avg(self, o, f): return "{%s}_K" % par(f) def eq(self, o, a, b): return "(%s = %s)" % (a, b) def ne(self, o, a, b): return r"(%s \ne %s)" % (a, b) def le(self, o, a, b): return r"(%s \le %s)" % (a, b) def ge(self, o, a, b): return r"(%s \ge %s)" % (a, b) def lt(self, o, a, b): return "(%s < %s)" % (a, b) def gt(self, o, a, b): return "(%s > %s)" % (a, b) def and_condition(self, o, a, b): return "(%s && %s)" % (a, b) def or_condition(self, o, a, b): return "(%s || %s)" % (a, b) def not_condition(self, o, a): return "!(%s)" % (a,) def conditional(self, o, c, t, f): l = "\\begin{cases}\n" l += "%s, &\text{if }\quad %s, \\\\\n" % (t, c) l += "%s, &\text{otherwise.}\n" % f l += "\\end{cases}" return l def min_value(self, o, a, b): return "min(%s, %s)" % (a, b) def max_value(self, o, a, b): return "max(%s, %s)" % (a, b) def expr(self, o): error("Missing handler for type %s" % str(type(o))) def expression2latex(expression, argument_names=None, coefficient_names=None): visitor = Expression2LatexHandler(argument_names, coefficient_names) return visitor.visit(expression) def element2latex(element): e = str(element) e = e.replace("<", "") e = e.replace(">", "") e = "fixme" return r"{\mbox{%s}}" % e domain_strings = { "cell": r"\Omega", "exterior_facet": r"\Gamma^{ext}", "exterior_facet_bottom": r"\Gamma_{bottom}^{ext}", "exterior_facet_top": r"\Gamma_{top}^{ext}", "exterior_facet_vert": r"\Gamma_{vert}^{ext}", "interior_facet": r"\Gamma^{int}", "interior_facet_horiz": r"\Gamma_{horiz}^{int}", "interior_facet_vert": r"\Gamma_{vert}^{int}", "vertex": r"\Gamma^{vertex}", "custom": r"\Gamma^{custom}", } default_domain_string = "d(?)" def form2latex(form, formdata): formname = formdata.name argument_names = formdata.argument_names coefficient_names = formdata.coefficient_names # List of sections to make latex document from sections = [] # Define elements lines = [] for i, f in enumerate(formdata.original_arguments): lines.append(r"\mathcal{P}_{%d} = \{%s\} " % (i, element2latex(f.element()))) for i, f in enumerate(formdata.original_coefficients): lines.append(r"\mathcal{Q}_{%d} = \{%s\} " % (i, element2latex(f.element()))) if lines: sections.append(("Finite elements", align(lines))) # Define function spaces lines = [] for i, f in enumerate(formdata.original_arguments): lines.append("V_h^{%d} = \\{v : v \\vert_K \\in \\mathcal{P}_{%d}(K) \\quad \\forall K \\in \\mathcal{T}\\} " % (i, i)) for i, f in enumerate(formdata.original_coefficients): lines.append("W_h^{%d} = \\{v : v \\vert_K \\in \\mathcal{Q}_{%d}(K) \\quad \\forall K \\in \\mathcal{T}\\} " % (i, i)) if lines: sections.append(("Function spaces", align(lines))) # Define arguments and coefficients lines = [] for f in formdata.original_arguments: i = f.number() p = f.part() lines.append("%s = %s \\in V_h^{%d} " % (argument_names[(i, p)], bfname(i, p), i)) # FIXME: Handle part in V_h for i, f in enumerate(formdata.original_coefficients): lines.append("%s = %s \\in W_h^{%d} " % (coefficient_names[i], cfname(i), i)) if lines: sections.append(("Form arguments", align(lines))) # TODO: Wrap ListTensors, ComponentTensor and Conditionals in expression as variables before transformation # Define variables handled_variables = set() integrals = form.integrals() lines = [] for itg in integrals: variables = _extract_variables(itg.integrand()) for v in variables: l = v._label if not l in handled_variables: handled_variables.add(l) exprlatex = expression2latex(v._expression, formdata.argument_names, formdata.coefficient_names) lines.append(("s_{%d}" % l._count, "= %s" % exprlatex)) if lines: sections.append(("Variables", align(lines))) # Join form arguments for signature "a(...) =" b = ", ".join(formdata.argument_names) c = ", ".join(formdata.coefficient_names) arguments = "; ".join((b, c)) signature = "%s(%s) = " % (formname, arguments, ) # Define form as sum of integrals lines = [] a = signature p = "" for itg in integrals: # TODO: Get list of expression strings instead of single expression! integrand_string = expression2latex(itg.integrand(), formdata.argument_names, formdata.coefficient_names) integral_type = itg.integral_type() dstr = domain_strings[integral_type] domain = itg.domain() label = domain.label() # TODO: Use domain label! subdomain_id = itg.subdomain_id() if isinstance(subdomain_id, int): dstr += "_{%d}" % subdomain_id elif subdomain_id == "everywhere": pass elif subdomain_id == "otherwise": dstr += "_{\text{oth}}" elif isinstance(subdomain_id, tuple): dstr += "_{%s}" % subdomain_id b = p + "\\int_{%s}" % (dstr,) dxstr = ufl.measure.integral_type_to_measure_name[integral_type] c = "{ %s } \\,%s" % (integrand_string, dxstr) lines.append((a, b, c)) a = "{}" p = "{}+ " sections.append(("Form", align(lines))) return sections def ufl2latex(expression): "Generate LaTeX code for a UFL expression or form (wrapper for form2latex and expression2latex)." if isinstance(expression, Form): form_data = compute_form_data(expression) preprocessed_form = form_data.preprocessed_form return form2latex(preprocessed_form, form_data) else: return expression2latex(expression) # --- LaTeX rendering of composite UFL objects --- def deps2latex(deps): return "Dependencies: ${ %s }$." % ", ".join(sorted(deps)) def dependency_sorting(deplist, rank): #print "deplist = ", deplist def split(deps, state): left = [] todo = [] for dep in deps: if dep - state: left.append(dep) else: todo.append(dep) return todo, left deplistlist = [] state = set() left = deplist # --- Initialization time #state.remove("x") precompute, left = split(left, state) deplistlist.append(precompute) state.add("x") precompute_quad, left = split(left, state) deplistlist.append(precompute_quad) # Permutations of 0/1 dependence of arguments indices = compute_indices((2,)*rank) for bfs in indices[1:]: # skip (0,...,0), already handled that for i, bf in reversed(enumerate(bfs)): n = "v%d" % i if bf: if n in state: state.remove(n) else: state.add(n) next, left = split(left, state) deplistlist.append(next) # --- Runtime state.add("c") state.add("w") state.remove("x") runtime, left = split(left, state) deplistlist.append(runtime) state.add("x") runtime_quad, left = split(left, state) deplistlist.append(runtime_quad) indices = compute_indices((2,)*rank) for bfs in indices[1:]: # skip (0,...,0), already handled that for i, bf in reversed(enumerate(bfs)): n = "v%d" % i if bf: state.add(n) else: if n in state: state.remove(n) next, left = split(left, state) deplistlist.append(next) ufl_assert(not left, "Shouldn't have anything left!") #print #print "Created deplistlist:" #for deps in deplistlist: # print # print "--- New stage:" # print "\n".join(map(str, deps)) #print return deplistlist def code2latex(G, partitions, formdata): "TODO: Document me" bfn = formdata.argument_names cfn = formdata.coefficient_names V, E = G Vout = extract_outgoing_vertex_connections(G) # Sort dependency sets in a sensible way (preclude to a good quadrature code generator) deplistlist = dependency_sorting(list(partitions.keys()), len(bfn)) def format_v(i): return "s_{%d}" % i pieces = [] for deplist in deplistlist: #pieces.append("\n\n(Debugging: getting next list of dependencies)") for dep in deplist: lines = [] for iv in partitions[dep]: v = V[iv] vout = Vout[iv] vl = format_v(iv) args = ", ".join(format_v(i) for i in vout) if args: el = r"{\mbox{%s}}(%s)" % (v._ufl_class_.__name__, args) else: # terminal el = r"{\mbox{%s}}" % (repr(v),) lines.append((vl, "= " + el)) pieces.extend(("\n", deps2latex(dep), align(lines))) # Add final variable representing integrand vl = format_v(len(V)-1) pieces.append("\n") pieces.append("Variable representing integrand: %s" % vl) # Could also return list of (title, body) parts for subsections if wanted body = "\n".join(pieces) return body def integrand2code(integrand, formdata): G = build_graph(integrand) partitions, keys = partition(G) return G, partitions def formdata2latex(formdata): # TODO: Format better return verbatim(str(formdata)) def form2code2latex(formdata): # Render introductory sections title = "Form data" body = formdata2latex(formdata) sections = [(title, body)] # Render each integral as a separate section for itg in formdata.form.integrals(): title = "%s integral over domain %d" % (itg.integral_type(), itg.subdomain_id()) G, partitions = integrand2code(itg.integrand(), formdata) body = code2latex(G, partitions, formdata) sections.append((title, body)) return sections # --- Creating complete documents --- def forms2latexdocument(forms, uflfilename, compile=False): "Render forms from a .ufl file as a LaTeX document." # Render one section for each form sections = [] for form in forms: # Compute form data form_data = compute_form_data(form) # Generate LaTex code title = "Form %s" % form_data.name if compile: body = form2code2latex(form, form_data) else: body = form2latex(form, form_data) sections.append((title, body)) # Render title suffix = "from UFL file %s" % uflfilename.replace("_", "\\_") if compile: title = "Compiled forms " + suffix else: title = "Forms " + suffix return document(title, sections) # --- File operations --- def ufl2tex(uflfilename, latexfilename, compile=False): "Compile a .tex file from a .ufl file." forms = load_forms(uflfilename) latex = forms2latexdocument(forms, uflfilename, compile) write_file(latexfilename, latex) def tex2pdf(latexfilename, pdffilename): "Compile a .pdf file from a .tex file." pdflatex(latexfilename, pdffilename) openpdf(pdffilename) def ufl2pdf(uflfilename, latexfilename, pdffilename, compile=False): "Compile a .pdf file from a .ufl file." ufl2tex(uflfilename, latexfilename, compile) tex2pdf(latexfilename, pdffilename) """# Code from uflacs: from ffc.log import error from ffc.log import ffc_assert import ufl from ufl.corealg.multifunction import MultiFunction # TODO: Assuming in this code that preprocessed expressions # are formatted, so no compounds etc. are included here. # Would be nice to format e.g. dot(u, v) -> u \cdot v. class LatexFormattingRules(object): # === Error rules catching groups of missing types by their superclasses === # Generic fallback error messages for missing rules: def expr(self, o): error("Missing LaTeX formatting rule for expr type %s." % o._ufl_class_) def terminal(self, o): error("Missing LaTeX formatting rule for terminal type %s." % o._ufl_class_) def constant_value(self, o, component=(), derivatives=(), restriction=None): error("Missing LaTeX rule for constant value type %s." % o._ufl_class_) def geometric_quantity(self, o, component=(), derivatives=()): error("Missing LaTeX formatting rule for geometric quantity type %s." % o._ufl_class_) # Unexcepted type checks: def variable(self, o): error("Should strip away variables before formatting LaTeX code.") return o # or just do this if necessary def invalid_request(self, o, *ops): error("Invalid request for LaTeX formatting of a %s." % o._ufl_class_) wrapper_type = invalid_request index_sum = invalid_request indexed = invalid_request derivative = invalid_request restricted = invalid_request # === Formatting rules for literal constants === def zero(self, o, component=(), derivatives=(), restriction=None): return "0" if not o.ufl_shape else r"{\mathbf 0}" def int_value(self, o, component=(), derivatives=(), restriction=None): if derivatives: return self.zero(0 * o) else: return "%d" % int(o) def float_value(self, o, component=(), derivatives=(), restriction=None): # Using configurable precision parameter from ufl if derivatives: return self.zero(0 * o) else: return ufl.constantvalue.format_float(float(o)) # ... The compound literals below are removed during preprocessing def identity(self, o): return r"{\mathbf I}" def permutation_symbol(self, o): return r"{\mathbf \varepsilon}" # === Formatting rules for geometric quantities === # TODO: Add all geometric quantities here, use restriction def spatial_coordinate(self, o, component=(), derivatives=(), restriction=None): if component: i, = component else: i = 0 if derivatives: return "x_{%d, %s}" % (i, ' '.join('%d' % d for d in derivatives)) else: return "x_%d" % i def facet_normal(self, o, component=(), derivatives=(), restriction=None): if component: i, = component else: i = 0 if derivatives: return "n_{%d, %s}" % (i, ' '.join('%d' % d for d in derivatives)) else: return "n_%d" % i def cell_volume(self, o, component=(), derivatives=(), restriction=None): ffc_assert(not component, "Expecting no component for scalar value.") if derivatives: return "0" else: return r"K_{\text{vol}}" def circumradius(self, o, component=(), derivatives=(), restriction=None): ffc_assert(not component, "Expecting no component for scalar value.") if derivatives: return "0" else: return r"K_{\text{rad}}" # === Formatting rules for functions === def coefficient(self, o, component=(), derivatives=(), restriction=None): common_name = "w" c = o.count() ffc_assert(c >= 0, "Expecting positive count, have you preprocessed the expression?") name = r"\overset{%d}{%s}" % (c, common_name) # TODO: Use restriction if component: cstr = ' '.join('%d' % d for d in component) else: cstr = '' if derivatives: dstr = ' '.join('%d' % d for d in derivatives) return "%s_{%s, %s}" % (name, cstr, dstr) elif not component: return name else: return "%s_{%s}" % (name, cstr) def argument(self, o, component=(), derivatives=(), restriction=None): common_name = "v" c = o.number() name = r"\overset{%d}{%s}" % (c, common_name) # TODO: Use restriction if component: cstr = ' '.join('%d' % d for d in component) else: cstr = '' if derivatives: dstr = ' '.join('%d' % d for d in derivatives) return "%s_{%s, %s}" % (name, cstr, dstr) elif not component: return name else: return "%s_{%s}" % (name, cstr) # === Formatting rules for arithmetic operations === def sum(self, o, *ops): return " + ".join(ops) def product(self, o, *ops): return " ".join(ops) def division(self, o, a, b): return r"\frac{%s}{%s}" % (a, b) # === Formatting rules for cmath functions === def power(self, o, a, b): return "{%s}^{%s}" % (a, b) def sqrt(self, o, op): return "\sqrt{%s}" % (op,) def ln(self, o, op): return r"\ln(%s)" % (op,) def exp(self, o, op): return "e^{%s}" % (op,) def abs(self, o, op): return r"\|%s\|" % (op,) def cos(self, o, op): return r"\cos(%s)" % (op,) def sin(self, o, op): return r"\sin(%s)" % (op,) def tan(self, o, op): return r"\tan(%s)" % (op,) def cosh(self, o, op): return r"\cosh(%s)" % (op,) def sinh(self, o, op): return r"\sinh(%s)" % (op,) def tanh(self, o, op): return r"\tanh(%s)" % (op,) def acos(self, o, op): return r"\arccos(%s)" % (op,) def asin(self, o, op): return r"\arcsin(%s)" % (op,) def atan(self, o, op): return r"\arctan(%s)" % (op,) # === Formatting rules for bessel functions === # TODO: Bessel functions, erf # === Formatting rules for conditional expressions === def conditional(self, o, c, t, f): return r"\left{{%s} \text{if} {%s} \text{else} {%s}\right}" % (t, c, f) def eq(self, o, a, b): return r" = ".join((a, b)) def ne(self, o, a, b): return r" \ne ".join((a, b)) def le(self, o, a, b): return r" \le ".join((a, b)) def ge(self, o, a, b): return r" \ge ".join((a, b)) def lt(self, o, a, b): return r" \lt ".join((a, b)) def gt(self, o, a, b): return r" \gt ".join((a, b)) def and_condition(self, o, a, b): return r" \land ".join((a, b)) def or_condition(self, o, a, b): return r" \lor ".join((a, b)) def not_condition(self, o, a): return r" \lnot %s" % (a,) # === Formatting rules for restrictions === def positive_restricted(self, o, a): return r"%s^{[+]}" % (a,) # TODO def negative_restricted(self, o, a): return r"%s^{[-]}" % (a,) # TODO class LatexFormatter(MultiFunction, LatexFormattingRules): def __init__(self): MultiFunction.__init__(self) """ ufl-1.6.0/ufl/formoperators.py000066400000000000000000000270541255567402100163650ustar00rootroot00000000000000"Various high level ways to transform a complete Form into a new Form." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2009 from six import iteritems from six.moves import zip from six.moves import xrange as range from ufl.log import error from ufl.assertions import ufl_assert from ufl.form import Form, as_form from ufl.core.expr import Expr from ufl.split_functions import split from ufl.exprcontainers import ExprList, ExprMapping from ufl.variable import Variable from ufl.finiteelement import MixedElement from ufl.argument import Argument from ufl.coefficient import Coefficient from ufl.differentiation import CoefficientDerivative from ufl.constantvalue import is_true_ufl_scalar, as_ufl from ufl.indexed import Indexed from ufl.core.multiindex import FixedIndex, MultiIndex from ufl.tensors import as_tensor, ListTensor from ufl.sorting import sorted_expr # An exception to the rule that ufl.* does not depend on ufl.algorithms.* ... from ufl.algorithms import compute_form_adjoint, \ compute_form_action, \ compute_energy_norm, \ compute_form_lhs, \ compute_form_rhs, \ compute_form_functional, \ expand_derivatives, \ extract_arguments # Part of the external interface from ufl.algorithms import replace def lhs(form): """UFL form operator: Given a combined bilinear and linear form, extract the left hand side (bilinear form part). Example: a = u*v*dx + f*v*dx a = lhs(a) -> u*v*dx """ form = as_form(form) form = expand_derivatives(form) return compute_form_lhs(form) def rhs(form): """UFL form operator: Given a combined bilinear and linear form, extract the right hand side (negated linear form part). Example: a = u*v*dx + f*v*dx L = rhs(a) -> -f*v*dx """ form = as_form(form) form = expand_derivatives(form) return compute_form_rhs(form) def system(form): "UFL form operator: Split a form into the left hand side and right hand side, see lhs and rhs." return lhs(form), rhs(form) def functional(form): # TODO: Does this make sense for anything other than testing? "UFL form operator: Extract the functional part of form." form = as_form(form) form = expand_derivatives(form) return compute_form_functional(form) def action(form, coefficient=None): """UFL form operator: Given a bilinear form, return a linear form with an additional coefficient, representing the action of the form on the coefficient. This can be used for matrix-free methods.""" form = as_form(form) form = expand_derivatives(form) return compute_form_action(form, coefficient) def energy_norm(form, coefficient=None): """UFL form operator: Given a bilinear form a and a coefficient f, return the functional a(f,f).""" form = as_form(form) form = expand_derivatives(form) return compute_energy_norm(form, coefficient) def adjoint(form, reordered_arguments=None): """UFL form operator: Given a combined bilinear form, compute the adjoint form by changing the ordering (count) of the test and trial functions. By default, new Argument objects will be created with opposite ordering. However, if the adjoint form is to be added to other forms later, their arguments must match. In that case, the user must provide a tuple reordered_arguments=(u2,v2). """ form = as_form(form) form = expand_derivatives(form) return compute_form_adjoint(form, reordered_arguments) def zero_lists(shape): ufl_assert(len(shape) > 0, "Invalid shape.") if len(shape) == 1: return [0]*shape[0] else: return [zero_lists(shape[1:]) for i in range(shape[0])] def set_list_item(li, i, v): # Get to the innermost list if len(i) > 1: for j in i[:-1]: li = li[j] # Set item in innermost list li[i[-1]] = v def _handle_derivative_arguments(form, coefficient, argument): # Wrap single coefficient in tuple for uniform treatment below if isinstance(coefficient, (list, tuple, ListTensor)): coefficients = tuple(coefficient) else: coefficients = (coefficient,) if argument is None: # Try to create argument if not provided if not all(isinstance(c, Coefficient) for c in coefficients): error("Can only create arguments automatically for non-indexed coefficients.") # Get existing arguments from form and position the new one with the next argument number if isinstance(form, Form): form_arguments = form.arguments() else: # To handler derivative(expression), which is at least used in tests. Remove? form_arguments = extract_arguments(form) numbers = sorted(set(arg.number() for arg in form_arguments)) number = max(numbers + [-1]) + 1 # Don't know what to do with parts, let the user sort it out in that case parts = set(arg.part() for arg in form_arguments) ufl_assert(len(parts - {None}) == 0, "Not expecting parts here, provide your own arguments.") part = None # Create argument and split it if in a mixed space elements = [c.element() for c in coefficients] if len(elements) > 1: elm = MixedElement(*elements) arguments = split(Argument(elm, number, part)) else: elm, = elements arguments = (Argument(elm, number, part),) else: # Wrap single argument in tuple for uniform treatment below if isinstance(argument, (list, tuple)): arguments = tuple(argument) else: n = len(coefficients) if n == 1: arguments = (argument,) else: if argument.ufl_shape == (n,): arguments = tuple(argument[i] for i in range(n)) else: arguments = split(argument) # Build mapping from coefficient to argument m = {} for (c, a) in zip(coefficients, arguments): ufl_assert(c.ufl_shape == a.ufl_shape, "Coefficient and argument shapes do not match!") if isinstance(c, Coefficient): m[c] = a else: ufl_assert(isinstance(c, Indexed), "Invalid coefficient type for %s" % repr(c)) f, i = c.ufl_operands ufl_assert(isinstance(f, Coefficient), "Expecting an indexed coefficient, not %s" % repr(f)) ufl_assert(isinstance(i, MultiIndex) and all(isinstance(j, FixedIndex) for j in i), "Expecting one or more fixed indices, not %s" % repr(i)) i = tuple(int(j) for j in i) if f not in m: m[f] = {} m[f][i] = a # Merge coefficient derivatives (arguments) based on indices for c, p in iteritems(m): if isinstance(p, dict): a = zero_lists(c.ufl_shape) for i, g in iteritems(p): set_list_item(a, i, g) m[c] = as_tensor(a) # Wrap and return generic tuples items = sorted(m.items(), key=lambda x: x[0].count()) coefficients = ExprList(*[item[0] for item in items]) arguments = ExprList(*[item[1] for item in items]) return coefficients, arguments def derivative(form, coefficient, argument=None, coefficient_derivatives=None): """UFL form operator: Compute the Gateaux derivative of form w.r.t. coefficient in direction of argument. If the argument is omitted, a new Argument is created in the same space as the coefficient, with argument number one higher than the highest one in the form. The resulting form has one additional Argument in the same finite element space as the coefficient. A tuple of Coefficients may be provided in place of a single Coefficient, in which case the new Argument argument is based on a MixedElement created from this tuple. An indexed Coefficient from a mixed space may be provided, in which case the argument should be in the corresponding subspace of the coefficient space. If provided, coefficient_derivatives should be a mapping from Coefficient instances to their derivatives w.r.t. 'coefficient'. """ coefficients, arguments = _handle_derivative_arguments(form, coefficient, argument) if coefficient_derivatives is None: coefficient_derivatives = ExprMapping() else: cd = [] for k in sorted_expr(coefficient_derivatives.keys()): cd += [as_ufl(k), as_ufl(coefficient_derivatives[k])] coefficient_derivatives = ExprMapping(*cd) # Got a form? Apply derivatives to the integrands in turn. if isinstance(form, Form): integrals = [] for itg in form.integrals(): fd = CoefficientDerivative(itg.integrand(), coefficients, arguments, coefficient_derivatives) integrals.append(itg.reconstruct(fd)) return Form(integrals) elif isinstance(form, Expr): # What we got was in fact an integrand return CoefficientDerivative(form, coefficients, arguments, coefficient_derivatives) error("Invalid argument type %s." % str(type(form))) def sensitivity_rhs(a, u, L, v): """UFL form operator: Compute the right hand side for a sensitivity calculation system. The derivation behind this computation is as follows. Assume a, L to be bilinear and linear forms corresponding to the assembled linear system Ax = b. Where x is the vector of the discrete function corresponding to u. Let v be some scalar variable this equation depends on. Then we can write 0 = d/dv[Ax-b] = dA/dv x + A dx/dv - db/dv, A dx/dv = db/dv - dA/dv x, and solve this system for dx/dv, using the same bilinear form a and matrix A from the original system. Assume the forms are written v = variable(v_expression) L = IL(v)*dx a = Ia(v)*dx where IL and Ia are integrand expressions. Define a Coefficient u representing the solution to the equations. Then we can compute db/dv and dA/dv from the forms da = diff(a, v) dL = diff(L, v) and the action of da on u by dau = action(da, u) In total, we can build the right hand side of the system to compute du/dv with the single line dL = diff(L, v) - action(diff(a, v), u) or, using this function dL = sensitivity_rhs(a, u, L, v) """ msg = "Expecting (a, u, L, v), (bilinear form, function, linear form and scalar variable)." ufl_assert(isinstance(a, Form), msg) ufl_assert(isinstance(u, Coefficient), msg) ufl_assert(isinstance(L, Form), msg) ufl_assert(isinstance(v, Variable), msg) ufl_assert(is_true_ufl_scalar(v), "Expecting scalar variable.") from ufl.operators import diff return diff(L, v) - action(diff(a, v), u) ufl-1.6.0/ufl/geometry.py000066400000000000000000000566311255567402100153210ustar00rootroot00000000000000"Types for representing symbolic expressions for geometric quantities." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2009. # Modified by Kristian B. Oelgaard, 2009 # Modified by Marie E. Rognes 2012 from collections import defaultdict from ufl.log import warning, error, deprecate from ufl.assertions import ufl_assert from ufl.common import istr, EmptyDict from ufl.core.terminal import Terminal from ufl.protocols import id_or_none from ufl.cell import as_cell, affine_cells, Cell, ProductCell from ufl.domain import as_domain, Domain, extract_domains, join_domains, ProductDomain from ufl.core.ufl_type import ufl_type """ Possible coordinate bootstrapping: Xf = Xf[q] FacetCoordinate = quadrature point on facet (ds,dS) X = X[q] CellCoordinate = quadrature point on cell (dx) x = x[q] SpatialCoordinate = quadrature point from input array (dc) Jacobians of mappings between coordinates: Jcf = dX/dXf = grad_Xf X(Xf) CellFacetJacobian Jxc = dx/dX = grad_X x(X) Jacobian Jxf = dx/dXf = grad_Xf x(Xf) = Jxc Jcf = dx/dX dX/dXf = grad_X x(X) grad_Xf X(Xf) FacetJacobian = Jacobian * CellFacetJacobian Possible computation of X from Xf: X = Jcf Xf + X0f CellCoordinate = CellFacetJacobian * FacetCoordinate + CellFacetOrigin Possible computation of x from X: x = f(X) SpatialCoordinate = sum_k xdofs_k * xphi_k(X) x = Jxc X + x0 SpatialCoordinate = Jacobian * CellCoordinate + CellOrigin Possible computation of x from Xf: x = x(X(Xf)) x = Jxf Xf + x0f SpatialCoordinate = FacetJacobian * FacetCoordinate + FacetOrigin Inverse relations: X = K * (x - x0) CellCoordinate = JacobianInverse * (SpatialCoordinate - CellOrigio) Xf = FK * (x - x0f) FacetCoordinate = FacetJacobianInverse * (SpatialCoordinate - FacetOrigin) Xf = CFK * (X - X0f) FacetCoordinate = CellFacetJacobianInverse * (CellCoordinate - CellFacetOrigin) """ # --- Expression node types @ufl_type(is_abstract=True) class GeometricQuantity(Terminal): __slots__ = ("_domain",) def __init__(self, domain): Terminal.__init__(self) self._domain = as_domain(domain) def domains(self): return (self._domain,) def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell (or over each facet for facet quantities)." # NB! Geometric quantities are piecewise constant by default. Override if needed. return True # NB! Geometric quantities are scalar by default. Override if needed. ufl_shape = () def signature_data(self, renumbering): "Signature data of geometric quantities depend on the domain numbering." return (self._ufl_class_.__name__,) + self._domain.signature_data(renumbering) def __str__(self): return self._ufl_class_.name def __repr__(self): return "%s(%r)" % (self._ufl_class_.__name__, self._domain) def _ufl_compute_hash_(self): return hash((type(self).__name__,) + self._domain.hash_data()) def __eq__(self, other): return isinstance(other, self._ufl_class_) and other._domain == self._domain @ufl_type(is_abstract=True) class GeometricCellQuantity(GeometricQuantity): __slots__ = [] @ufl_type(is_abstract=True) class GeometricFacetQuantity(GeometricQuantity): __slots__ = [] # --- Coordinate represented in different coordinate systems @ufl_type() class SpatialCoordinate(GeometricCellQuantity): """UFL geometry representation: The coordinate in a domain. In the context of expression integration, represents the domain coordinate of each quadrature point. In the context of expression evaluation in a point, represents the value of that point. """ __slots__ = () name = "x" @property def ufl_shape(self): g = self._domain.geometric_dimension() return (g,) def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." # Only case this is true is if the domain is a vertex cell. t = self._domain.topological_dimension() return t == 0 def evaluate(self, x, mapping, component, index_values): if component == (): if isinstance(x, (tuple, list)): return float(x[0]) else: return float(x) else: return float(x[component[0]]) @ufl_type() class CellCoordinate(GeometricCellQuantity): """UFL geometry representation: The coordinate in a reference cell. In the context of expression integration, represents the reference cell coordinate of each quadrature point. In the context of expression evaluation in a point in a cell, represents that point in the reference coordinate system of the cell. """ __slots__ = () name = "X" @property def ufl_shape(self): t = self._domain.topological_dimension() return (t,) def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." # Only case this is true is if the domain is a vertex cell. t = self._domain.topological_dimension() return t == 0 @ufl_type() class FacetCoordinate(GeometricFacetQuantity): """UFL geometry representation: The coordinate in a reference cell of a facet. In the context of expression integration over a facet, represents the reference facet coordinate of each quadrature point. In the context of expression evaluation in a point on a facet, represents that point in the reference coordinate system of the facet. """ __slots__ = () name = "Xf" def __init__(self, domain): GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() ufl_assert(t > 1, "FacetCoordinate is only defined for topological dimensions > 1.") @property def ufl_shape(self): t = self._domain.topological_dimension() return (t-1,) def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." # Only case this is true is if the domain is an interval cell (with a vertex facet). t = self._domain.topological_dimension() return t <= 1 # --- Origin of coordinate systems in larger coordinate systems @ufl_type() class CellOrigin(GeometricCellQuantity): """UFL geometry representation: The spatial coordinate corresponding to origin of a reference cell.""" __slots__ = () name = "x0" @property def ufl_shape(self): g = self._domain.geometric_dimension() return (g,) def is_cellwise_constant(self): return True @ufl_type() class FacetOrigin(GeometricFacetQuantity): """UFL geometry representation: The spatial coordinate corresponding to origin of a reference facet.""" __slots__ = () name = "x0f" @property def ufl_shape(self): g = self._domain.geometric_dimension() return (g,) @ufl_type() class CellFacetOrigin(GeometricFacetQuantity): """UFL geometry representation: The reference cell coordinate corresponding to origin of a reference facet.""" __slots__ = () name = "X0f" @property def ufl_shape(self): t = self._domain.topological_dimension() return (t,) # --- Jacobians of mappings between coordinate systems @ufl_type() class Jacobian(GeometricCellQuantity): """UFL geometry representation: The Jacobian of the mapping from reference cell to spatial coordinates. J_ij = dx_i/dX_j """ __slots__ = () name = "J" @property def ufl_shape(self): g = self._domain.geometric_dimension() t = self._domain.topological_dimension() return (g, t) def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." # Only true for a piecewise linear coordinate field in simplex cells return self._domain.is_piecewise_linear_simplex_domain() @ufl_type() class FacetJacobian(GeometricFacetQuantity): """UFL geometry representation: The Jacobian of the mapping from reference facet to spatial coordinates. FJ_ij = dx_i/dXf_j The FacetJacobian is the product of the Jacobian and CellFacetJacobian: FJ = dx/dXf = dx/dX dX/dXf = J * CFJ """ __slots__ = () name = "FJ" def __init__(self, domain): GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() ufl_assert(t > 1, "FacetJacobian is only defined for topological dimensions > 1.") @property def ufl_shape(self): g = self._domain.geometric_dimension() t = self._domain.topological_dimension() return (g, t-1) def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." # Only true for a piecewise linear coordinate field in simplex cells return self._domain.is_piecewise_linear_simplex_domain() @ufl_type() class CellFacetJacobian(GeometricFacetQuantity): # dX/dXf """UFL geometry representation: The Jacobian of the mapping from reference facet to reference cell coordinates. CFJ_ij = dX_i/dXf_j """ __slots__ = () name = "CFJ" def __init__(self, domain): GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() ufl_assert(t > 1, "CellFacetJacobian is only defined for topological dimensions > 1.") @property def ufl_shape(self): t = self._domain.topological_dimension() return (t, t-1) def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." # This is always a constant mapping between two reference coordinate systems. return True @ufl_type() class CellEdgeVectors(GeometricCellQuantity): """UFL geometry representation: The vectors between reference cell vertices for each edge in cell.""" __slots__ = () name = "CEV" def __init__(self, domain): GeometricCellQuantity.__init__(self, domain) t = self._domain.topological_dimension() ufl_assert(t > 1, "CellEdgeVectors is only defined for topological dimensions >= 2.") @property def ufl_shape(self): cell = self.domain().cell() ne = cell.num_edges() t = cell.topological_dimension() return (ne, t) def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." # This is always constant for a given cell type return True @ufl_type() class FacetEdgeVectors(GeometricFacetQuantity): """UFL geometry representation: The vectors between reference cell vertices for each edge in current facet.""" __slots__ = () name = "FEV" def __init__(self, domain): GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() ufl_assert(t > 2, "FacetEdgeVectors is only defined for topological dimensions >= 3.") @property def ufl_shape(self): cell = self.domain().cell() nfe = cell.num_facet_edges() t = cell.topological_dimension() return (nfe, t) def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." # This is always constant for a given cell type return True # --- Determinants (signed or pseudo) of geometry mapping Jacobians @ufl_type() class JacobianDeterminant(GeometricCellQuantity): """UFL geometry representation: The determinant of the Jacobian. Represents the signed determinant of a square Jacobian or the pseudo-determinant of a non-square Jacobian. """ __slots__ = () name = "detJ" def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." # Only true for a piecewise linear coordinate field in simplex cells return self._domain.is_piecewise_linear_simplex_domain() @ufl_type() class FacetJacobianDeterminant(GeometricFacetQuantity): """UFL geometry representation: The pseudo-determinant of the FacetJacobian.""" __slots__ = () name = "detFJ" def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." # Only true for a piecewise linear coordinate field in simplex cells return self._domain.is_piecewise_linear_simplex_domain() @ufl_type() class CellFacetJacobianDeterminant(GeometricFacetQuantity): """UFL geometry representation: The pseudo-determinant of the CellFacetJacobian.""" __slots__ = () name = "detCFJ" def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." # Only true for a piecewise linear coordinate field in simplex cells return self._domain.is_piecewise_linear_simplex_domain() # --- Inverses (signed or pseudo) of geometry mapping Jacobians @ufl_type() class JacobianInverse(GeometricCellQuantity): """UFL geometry representation: The inverse of the Jacobian. Represents the inverse of a square Jacobian or the pseudo-inverse of a non-square Jacobian. """ __slots__ = () name = "K" @property def ufl_shape(self): g = self._domain.geometric_dimension() t = self._domain.topological_dimension() return (t, g) def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." # Only true for a piecewise linear coordinate field in simplex cells return self._domain.is_piecewise_linear_simplex_domain() @ufl_type() class FacetJacobianInverse(GeometricFacetQuantity): """UFL geometry representation: The pseudo-inverse of the FacetJacobian.""" __slots__ = () name = "FK" def __init__(self, domain): GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() ufl_assert(t > 1, "FacetJacobianInverse is only defined for topological dimensions > 1.") @property def ufl_shape(self): g = self._domain.geometric_dimension() t = self._domain.topological_dimension() return (t-1, g) def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." # Only true for a piecewise linear coordinate field in simplex cells return self._domain.is_piecewise_linear_simplex_domain() @ufl_type() class CellFacetJacobianInverse(GeometricFacetQuantity): """UFL geometry representation: The pseudo-inverse of the CellFacetJacobian.""" __slots__ = () name = "CFK" def __init__(self, domain): GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() ufl_assert(t > 1, "CellFacetJacobianInverse is only defined for topological dimensions > 1.") @property def ufl_shape(self): t = self._domain.topological_dimension() return (t-1, t) def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." # Only true for a piecewise linear coordinate field in simplex cells return self._domain.is_piecewise_linear_simplex_domain() # --- Types representing normal or tangent vectors @ufl_type() class FacetNormal(GeometricFacetQuantity): """UFL geometry representation: The outwards pointing normal vector of the current facet.""" __slots__ = () name = "n" @property def ufl_shape(self): g = self._domain.geometric_dimension() return (g,) def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." # TODO: For product cells, this depends on which facet. Seems like too much work to fix right now. # Only true for a piecewise linear coordinate field with simplex _facets_ is_piecewise_linear = self._domain.coordinate_element().degree() == 1 has_simplex_facets = (self._domain.cell().facet_cellname() in affine_cells) return is_piecewise_linear and has_simplex_facets # TODO: Should it be CellNormals? For interval in 3D we have two! @ufl_type() class CellNormal(GeometricCellQuantity): """UFL geometry representation: The upwards pointing normal vector of the current manifold cell.""" __slots__ = () name = "cell_normal" @property def ufl_shape(self): g = self._domain.geometric_dimension() return (g,) # TODO: Implement in change_to_reference_geometry and enable #@ufl_type() #class FacetTangents(GeometricFacetQuantity): # """UFL geometry representation: The tangent vectors of the current facet.""" # __slots__ = () # name = "t" # # def __init__(self, domain): # GeometricFacetQuantity.__init__(self, domain) # t = self._domain.topological_dimension() # ufl_assert(t > 1, "FacetTangents is only defined for topological dimensions > 1.") # # @property # def ufl_shape(self): # g = self._domain.geometric_dimension() # t = self._domain.topological_dimension() # return (t-1,g) # # def is_cellwise_constant(self): # NB! Copied from FacetNormal # "Return whether this expression is spatially constant over each cell." # # TODO: For product cells, this depends on which facet. Seems like too much work to fix right now. # # Only true for a piecewise linear coordinate field with simplex _facets_ # is_piecewise_linear = self._domain.coordinate_element().degree() == 1 # has_simplex_facets = (self._domain.cell().facet_cellname() in affine_cells) # return is_piecewise_linear and has_simplex_facets # TODO: Implement in change_to_reference_geometry and enable #@ufl_type() #class CellTangents(GeometricCellQuantity): # """UFL geometry representation: The tangent vectors of the current manifold cell.""" # __slots__ = () # name = "cell_tangents" # # @property # def ufl_shape(self): # g = self._domain.geometric_dimension() # t = self._domain.topological_dimension() # return (t,g) # --- Types representing midpoint coordinates # TODO: Implement in the rest of fenics #@ufl_type() #class CellMidpoint(GeometricCellQuantity): # """UFL geometry representation: The midpoint coordinate of the current cell.""" # __slots__ = () # name = "cell_midpoint" # # @property # def ufl_shape(self): # g = self._domain.geometric_dimension() # return (g,) # TODO: Implement in the rest of fenics #@ufl_type() #class FacetMidpoint(GeometricFacetQuantity): # """UFL geometry representation: The midpoint coordinate of the current facet.""" # __slots__ = () # name = "facet_midpoint" # # @property # def ufl_shape(self): # g = self._domain.geometric_dimension() # return (g,) # --- Types representing measures of the cell and entities of the cell, typically used for stabilisation terms # TODO: Clean up this set of types? Document! @ufl_type() class CellVolume(GeometricCellQuantity): """UFL geometry representation: The volume of the cell.""" __slots__ = () name = "volume" @ufl_type() class Circumradius(GeometricCellQuantity): """UFL geometry representation: The circumradius of the cell.""" __slots__ = () name = "circumradius" #@ufl_type() #class CellSurfaceArea(GeometricCellQuantity): # """UFL geometry representation: The total surface area of the cell.""" # __slots__ = () # name = "surfacearea" @ufl_type() class FacetArea(GeometricFacetQuantity): # FIXME: Should this be allowed for interval domain? """UFL geometry representation: The area of the facet.""" __slots__ = () name = "facetarea" @ufl_type() class MinCellEdgeLength(GeometricCellQuantity): """UFL geometry representation: The minimum edge length of the cell.""" __slots__ = () name = "mincelledgelength" @ufl_type() class MaxCellEdgeLength(GeometricCellQuantity): """UFL geometry representation: The maximum edge length of the cell.""" __slots__ = () name = "maxcelledgelength" @ufl_type() class MinFacetEdgeLength(GeometricFacetQuantity): """UFL geometry representation: The minimum edge length of the facet.""" __slots__ = () name = "minfacetedgelength" @ufl_type() class MaxFacetEdgeLength(GeometricFacetQuantity): """UFL geometry representation: The maximum edge length of the facet.""" __slots__ = () name = "maxfacetedgelength" # --- Types representing other stuff @ufl_type() class CellOrientation(GeometricCellQuantity): """UFL geometry representation: The orientation (+1/-1) of the current cell. For non-manifold cells (tdim == gdim), this equals the sign of the Jacobian determinant, i.e. +1 if the physical cell is oriented the same way as the reference cell and -1 otherwise. For manifold cells of tdim==gdim-1 this is input data belonging to the mesh, used to distinguish between the sides of the manifold. """ __slots__ = () name = "cell_orientation" @ufl_type() class FacetOrientation(GeometricFacetQuantity): """UFL geometry representation: The orientation (+1/-1) of the current facet relative to the reference cell.""" __slots__ = () name = "facet_orientation" # This doesn't quite fit anywhere. Make a special set of symbolic terminal types instead? @ufl_type() class QuadratureWeight(GeometricQuantity): """UFL geometry representation: The current quadrature weight. Only used inside a quadrature context. """ __slots__ = () name = "weight" def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." # The weight usually varies with the quadrature points return False # --- Attach deprecated cell properties # TODO: Remove this deprecated part after a release or two. def _deprecated_dim(self): """The dimension of the cell. Only valid if the geometric and topological dimensions are the same. """ deprecate("cell.d is deprecated, please use one of cell.topological_dimension(), cell.geometric_dimension(), domain.topological_dimension() or domain.geometric_dimension() instead.") ufl_assert(self.topological_dimension() == self.geometric_dimension(), "Cell.d is undefined when geometric and topological dimensions are not the same.") return self.geometric_dimension() def _deprecated_geometric_quantity(name, cls): def f(self): "UFL geometry value. Deprecated, please use the constructor types instead." deprecate("cell.%s is deprecated, please use %s(domain) instead" % (name, cls.__name__)) return cls(as_domain(self)) return f Cell.d = property(_deprecated_dim) Cell.x = property(_deprecated_geometric_quantity("x", SpatialCoordinate)) Cell.n = property(_deprecated_geometric_quantity("n", FacetNormal)) Cell.volume = property(_deprecated_geometric_quantity("volume", CellVolume)) Cell.circumradius = property(_deprecated_geometric_quantity("circumradius", Circumradius)) Cell.facet_area = property(_deprecated_geometric_quantity("facet_area", FacetArea)) ufl-1.6.0/ufl/index_combination_utils.py000066400000000000000000000162011255567402100203640ustar00rootroot00000000000000"Utilities for analysing and manipulating free index tuples" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from six.moves import zip from six.moves import xrange as range from ufl.assertions import ufl_assert from ufl.log import error from ufl.core.multiindex import FixedIndex, Index, indices # FIXME: Some of these might be merged into one function, some might be optimized def unique_sorted_indices(indices): """Given a list of (id, dim) tuples already sorted by id, return a unique list with duplicates removed. Also checks that the dimensions of duplicates are matching. """ newindices = [] prev = (None, None) for i in indices: if i[0] != prev[0]: newindices.append(i) prev = i else: ufl_assert(i[1] == prev[1], "Nonmatching dimensions for free indices with same id!") return tuple(newindices) def merge_unique_indices(afi, afid, bfi, bfid): """Merge two pairs of (index ids, index dimensions) sequences into one pair without duplicates. The id tuples afi, bfi are assumed already sorted by id. Given a list of (id, dim) tuples already sorted by id, return a unique list with duplicates removed. Also checks that the dimensions of duplicates are matching. """ na = len(afi) nb = len(bfi) if na == 0: return bfi, bfid elif nb == 0: return afi, afid ak = 0 bk = 0 fi = [] fid = [] while True: if afi[ak] < bfi[bk]: fi.append(afi[ak]) fid.append(afid[ak]) ak += 1 elif afi[ak] > bfi[bk]: fi.append(bfi[bk]) fid.append(bfid[bk]) bk += 1 else: fi.append(afi[ak]) fid.append(afid[ak]) ak += 1 bk += 1 if ak == na: if bk != nb: fi.extend(bfi[bk:]) fid.extend(bfid[bk:]) break elif bk == nb: fi.extend(afi[ak:]) fid.extend(afid[ak:]) break return tuple(fi), tuple(fid) def remove_indices(fi, fid, rfi): """ """ if not rfi: return fi, fid rfip = sorted((r,p) for p, r in enumerate(rfi)) nrfi = len(rfi) nfi = len(fi) shape = [None]*nrfi k = 0 pos = 0 newfiid = [] while pos < nfi: rk = rfip[k][0] # Keep while fi[pos] < rk: newfiid.append((fi[pos], fid[pos])) pos += 1 # Skip removed = 0 while pos < nfi and fi[pos] == rk: shape[rfip[k][1]] = fid[pos] pos += 1 removed += 1 # Expecting to find each index from rfi in fi if not removed: error("Index to be removed ({0}) not part of indices ({1}).".format(rk, fi)) # Next to remove k += 1 if k == nrfi: # No more to remove, keep the rest if pos < nfi: newfiid.extend(zip(fi[pos:], fid[pos:])) break assert None not in shape # Unpack into two tuples fi, fid = zip(*newfiid) if newfiid else ((), ()) return fi, fid, tuple(shape) def create_slice_indices(component, shape, fi): all_indices = [] slice_indices = [] repeated_indices = [] free_indices = [] for ind in component: if isinstance(ind, Index): all_indices.append(ind) if ind.count() in fi or ind in free_indices: repeated_indices.append(ind) free_indices.append(ind) elif isinstance(ind, FixedIndex): ufl_assert(int(ind) < shape[len(all_indices)], "Index out of bounds.") all_indices.append(ind) elif isinstance(ind, int): ufl_assert(int(ind) < shape[len(all_indices)], "Index out of bounds.") all_indices.append(FixedIndex(ind)) elif isinstance(ind, slice): ufl_assert(ind == slice(None), "Only full slices (:) allowed.") i = Index() slice_indices.append(i) all_indices.append(i) elif ind == Ellipsis: er = len(shape) - len(component) + 1 ii = indices(er) slice_indices.extend(ii) all_indices.extend(ii) else: error("Not expecting {0}.".format(ind)) ufl_assert(len(all_indices) == len(shape), "Component and shape length don't match.") return tuple(all_indices), tuple(slice_indices), tuple(repeated_indices) # Outer etc. def merge_nonoverlapping_indices(a, b): """Merge non-overlapping free indices into one representation. Example: C[i,j,r,s] = outer(A[i,s], B[j,r]) A, B -> (i,j,r,s), (idim,jdim,rdim,sdim) """ # Extract input properties ai = a.ufl_free_indices bi = b.ufl_free_indices aid = a.ufl_index_dimensions bid = b.ufl_index_dimensions # Merge lists to return s = sorted(zip(ai + bi, aid + bid)) if s: free_indices, index_dimensions = zip(*s) # Consistency checks ufl_assert(len(set(free_indices)) == len(free_indices), "Not expecting repeated indices.") else: free_indices, index_dimensions = (), () return free_indices, index_dimensions # Product def merge_overlapping_indices(afi, afid, bfi, bfid): """Merge overlapping free indices into one free and one repeated representation. Example: C[j,r] := A[i,j,k] * B[i,r,k] A, B -> (j,r), (jdim,rdim), (i,k), (idim,kdim) """ # Extract input properties an = len(afi) bn = len(bfi) # Lists to return free_indices = [] index_dimensions = [] repeated_indices = [] repeated_index_dimensions = [] # Find repeated indices, brute force version for i0 in range(an): for i1 in range(bn): if afi[i0] == bfi[i1]: repeated_indices.append(afi[i0]) repeated_index_dimensions.append(afid[i0]) break # Collect only non-repeated indices, brute force version for i, d in sorted(zip(afi + bfi, afid + bfid)): if i not in repeated_indices: free_indices.append(i) index_dimensions.append(d) # Consistency checks ufl_assert(len(set(free_indices)) == len(free_indices), "Not expecting repeated indices left.") ufl_assert(len(free_indices) + 2*len(repeated_indices) == an + bn, "Expecting only twice repeated indices.") return tuple(free_indices), tuple(index_dimensions), tuple(repeated_indices), tuple(repeated_index_dimensions) ufl-1.6.0/ufl/indexed.py000066400000000000000000000076261255567402100151060ustar00rootroot00000000000000"""This module defines the Indexed class.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from six.moves import zip from ufl.log import error from ufl.core.expr import Expr from ufl.core.operator import Operator from ufl.core.multiindex import Index, FixedIndex, MultiIndex, as_multi_index from ufl.index_combination_utils import unique_sorted_indices, merge_unique_indices from ufl.precedence import parstr from ufl.common import EmptyDict from ufl.core.ufl_type import ufl_type #--- Indexed expression --- @ufl_type(is_shaping=True, num_ops=2, is_terminal_modifier=True) class Indexed(Operator): __slots__ = ("ufl_free_indices", "ufl_index_dimensions",) def __init__(self, expression, multiindex): # Store operands Operator.__init__(self, (expression, multiindex)) # Error checking if not isinstance(expression, Expr): error("Expecting Expr instance, not %s." % repr(expression)) if not isinstance(multiindex, MultiIndex): error("Expecting MultiIndex instance, not %s." % repr(multiindex)) shape = expression.ufl_shape # Error checking if len(shape) != len(multiindex): error("Invalid number of indices (%d) for tensor "\ "expression of rank %d:\n\t%r\n"\ % (len(multiindex), expression.rank(), expression)) if any(int(di) >= int(si) for si, di in zip(shape, multiindex) if isinstance(di, FixedIndex)): error("Fixed index out of range!") # Build tuples of free index ids and dimensions if 1: efi = expression.ufl_free_indices efid = expression.ufl_index_dimensions fi = list(zip(efi, efid)) for pos, ind in enumerate(multiindex._indices): if isinstance(ind, Index): fi.append((ind.count(), shape[pos])) fi = unique_sorted_indices(sorted(fi)) if fi: fi, fid = zip(*fi) else: fi, fid = (), () else: mfiid = [(ind.count(), shape[pos]) for pos, ind in enumerate(multiindex._indices) if isinstance(ind, Index)] mfi, mfid = zip(*mfiid) if mfiid else ((), ()) fi, fid = merge_unique_indices(expression.ufl_free_indices, expression.ufl_index_dimensions, mfi, mfid) # Cache free index and dimensions self.ufl_free_indices = fi self.ufl_index_dimensions = fid ufl_shape = () def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." return self.ufl_operands[0].is_cellwise_constant() def evaluate(self, x, mapping, component, index_values, derivatives=()): A, ii = self.ufl_operands component = ii.evaluate(x, mapping, None, index_values) if derivatives: return A.evaluate(x, mapping, component, index_values, derivatives) else: return A.evaluate(x, mapping, component, index_values) def __str__(self): return "%s[%s]" % (parstr(self.ufl_operands[0], self), self.ufl_operands[1]) def __repr__(self): return "Indexed(%r, %r)" % self.ufl_operands def __getitem__(self, key): error("Attempting to index with %r, but object is already indexed: %r" % (key, self)) ufl-1.6.0/ufl/indexing.py000066400000000000000000000016611255567402100152640ustar00rootroot00000000000000"""This module defines the single index types and some internal index utilities.""" # Copyright (C) 2008-2014 Martin Sandve Alnes and Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # TODO: Fix imports and remove this file from ufl.core.multiindex import IndexBase, Index, FixedIndex, MultiIndex, indices, as_multi_index ufl-1.6.0/ufl/indexsum.py000066400000000000000000000064561255567402100153220ustar00rootroot00000000000000"""This module defines the IndexSum class.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from six.moves import xrange as range from ufl.log import error from ufl.assertions import ufl_assert from ufl.core.expr import Expr from ufl.core.operator import Operator from ufl.core.multiindex import Index, MultiIndex, as_multi_index from ufl.precedence import parstr from ufl.common import EmptyDict from ufl.core.ufl_type import ufl_type from ufl.constantvalue import Zero #--- Sum over an index --- @ufl_type(num_ops=2) class IndexSum(Operator): __slots__ = ("_dimension", "ufl_free_indices", "ufl_index_dimensions") def __new__(cls, summand, index): # Error checks if not isinstance(summand, Expr): error("Expecting Expr instance, not %s." % repr(summand)) if not isinstance(index, MultiIndex): error("Expecting MultiIndex instance, not %s." % repr(index)) if len(index) != 1: error("Expecting a single Index only.") # Simplification to zero if isinstance(summand, Zero): sh = summand.ufl_shape j, = index fi = summand.ufl_free_indices fid = summand.ufl_index_dimensions pos = fi.index(j.count()) fi = fi[:pos] + fi[pos+1:] fid = fid[:pos] + fid[pos+1:] return Zero(sh, fi, fid) return Operator.__new__(cls) def __init__(self, summand, index): j, = index fi = summand.ufl_free_indices fid = summand.ufl_index_dimensions pos = fi.index(j.count()) self._dimension = fid[pos] self.ufl_free_indices = fi[:pos] + fi[pos+1:] self.ufl_index_dimensions = fid[:pos] + fid[pos+1:] Operator.__init__(self, (summand, index)) def index(self): return self.ufl_operands[1][0] def dimension(self): return self._dimension @property def ufl_shape(self): return self.ufl_operands[0].ufl_shape def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." return self.ufl_operands[0].is_cellwise_constant() def evaluate(self, x, mapping, component, index_values): i, = self.ufl_operands[1] tmp = 0 for k in range(self._dimension): index_values.push(i, k) tmp += self.ufl_operands[0].evaluate(x, mapping, component, index_values) index_values.pop() return tmp def __str__(self): return "sum_{%s} %s " % (str(self.ufl_operands[1]), parstr(self.ufl_operands[0], self)) def __repr__(self): return "IndexSum(%r, %r)" % (self.ufl_operands[0], self.ufl_operands[1]) ufl-1.6.0/ufl/integral.py000066400000000000000000000123421255567402100152620ustar00rootroot00000000000000"""The Integral class.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2008-2009 import ufl from ufl.log import error, warning from ufl.assertions import ufl_assert from ufl.core.expr import Expr from ufl.checks import (is_true_ufl_scalar, is_python_scalar, is_globally_constant, is_scalar_constant_expression) from ufl.measure import Measure from ufl.protocols import id_or_none class Integral(object): "An integral over a single domain." __slots__ = ("_integrand", "_integral_type", "_domain", "_subdomain_id", "_metadata", "_subdomain_data", ) def __init__(self, integrand, integral_type, domain, subdomain_id, metadata, subdomain_data): ufl_assert(isinstance(integrand, Expr), "Expecting integrand to be an Expr instance.") self._integrand = integrand self._integral_type = integral_type self._domain = domain self._subdomain_id = subdomain_id self._metadata = metadata self._subdomain_data = subdomain_data def reconstruct(self, integrand=None, integral_type=None, domain=None, subdomain_id=None, metadata=None, subdomain_data=None): """Construct a new Integral object with some properties replaced with new values. Example: b = a.reconstruct(expand_compounds(a.integrand())) c = a.reconstruct(metadata={'quadrature_degree':2}) """ if integrand is None: integrand = self.integrand() if integral_type is None: integral_type = self.integral_type() if domain is None: domain = self.domain() if subdomain_id is None: subdomain_id = self.subdomain_id() if metadata is None: metadata = self.metadata() if subdomain_data is None: subdomain_data = self._subdomain_data return Integral(integrand, integral_type, domain, subdomain_id, metadata, subdomain_data) def integrand(self): "Return the integrand expression, which is an Expr instance." return self._integrand def integral_type(self): "Return the domain type of this integral." return self._integral_type def domain(self): "Return the integration domain of this integral." return self._domain def subdomain_id(self): "Return the subdomain id of this integral." return self._subdomain_id def metadata(self): "Return the compiler metadata this integral has been annotated with." return self._metadata def subdomain_data(self): "Return the domain data of this integral." return self._subdomain_data def __neg__(self): return self.reconstruct(-self._integrand) def __mul__(self, scalar): ufl_assert(is_python_scalar(scalar), "Cannot multiply an integral with non-constant values.") return self.reconstruct(scalar*self._integrand) def __rmul__(self, scalar): ufl_assert(is_scalar_constant_expression(scalar), "An integral can only be multiplied by a " "globally constant scalar expression.") return self.reconstruct(scalar*self._integrand) def __str__(self): fmt = "{ %s } * %s(%s[%s], %s)" mname = ufl.measure.integral_type_to_measure_name[self._integral_type] s = fmt % (self._integrand, mname, self._domain, self._subdomain_id, self._metadata) return s def __repr__(self): return "Integral(%r, %r, %r, %r, %r, %r)" % ( self._integrand, self._integral_type, self._domain, self._subdomain_id, self._metadata, self._subdomain_data) def __eq__(self, other): return (isinstance(other, Integral) and self._integral_type == other._integral_type and self._domain == other._domain and self._subdomain_id == other._subdomain_id and self._integrand == other._integrand and self._metadata == other._metadata and id_or_none(self._subdomain_data) == id_or_none(other._subdomain_data)) def __hash__(self): # Assuming few collisions by ignoring hash(self._metadata) # (a dict is not hashable but we assume it is immutable in practice) hashdata = (hash(self._integrand), self._integral_type, hash(self._domain), self._subdomain_id, id_or_none(self._subdomain_data)) return hash(hashdata) ufl-1.6.0/ufl/log.py000066400000000000000000000167021255567402100142420ustar00rootroot00000000000000"""This module provides functions used by the UFL implementation to output messages. These may be redirected by the user of UFL.""" # Copyright (C) 2005-2014 Anders Logg and Martin Sandve Alnaes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Johan Hake, 2009. import sys import types import logging log_functions = ["log", "debug", "info", "deprecate", "warning", "error", "begin", "end", "set_level", "push_level", "pop_level", "set_indent", "add_indent", "set_handler", "get_handler", "get_logger", "add_logfile", "set_prefix", "info_red", "info_green", "info_blue", "warning_red", "warning_green", "warning_blue"] __all__ = log_functions + ["DEBUG", "INFO", "DEPRECATE", "WARNING", "ERROR", "CRITICAL", "Logger", "log_functions"] # Import default log levels from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL DEPRECATE = (INFO + WARNING) // 2 # This is used to override emit() in StreamHandler for printing without newline def emit(self, record): message = self.format(record) format_string = "%s" if getattr(record, "continued", False) else "%s\n" self.stream.write(format_string % message) self.flush() # Colors RED = "\033[1;37;31m%s\033[0m" BLUE = "\033[1;37;34m%s\033[0m" GREEN = "\033[1;37;32m%s\033[0m" # Logger class class Logger: def __init__(self, name, exception_type=Exception): "Create logger instance." self._name = name self._exception_type = exception_type # Set up handler h = logging.StreamHandler(sys.stdout) h.setLevel(WARNING) # Override emit() in handler for indentation h.emit = types.MethodType(emit, h) self._handler = h # Set up logger self._log = logging.getLogger(name) assert len(self._log.handlers) == 0 self._log.addHandler(h) self._log.setLevel(DEBUG) self._logfiles = {} # Set initial indentation level self._indent_level = 0 # Setup stack with default logging level self._level_stack = [DEBUG] # Set prefix self._prefix = "" def add_logfile(self, filename=None, mode="a", level=DEBUG): if filename is None: filename = "%s.log" % self._name if filename in self._logfiles: self.warning("Trying to add logfile %s multiple times." % filename) return h = logging.FileHandler(filename, mode) h.emit = types.MethodType(emit, h) h.setLevel(level) self._log.addHandler(h) self._logfiles[filename] = h return h def get_logfile_handler(self, filename): return self._logfiles[filename] def log(self, level, *message): "Write a log message on given log level" text = self._format_raw(*message) if len(text) >= 3 and text[-3:] == "...": self._log.log(level, self._format(*message), extra={"continued": True}) else: self._log.log(level, self._format(*message)) def debug(self, *message): "Write debug message." self.log(DEBUG, *message) def info(self, *message): "Write info message." self.log(INFO, *message) def info_red(self, *message): "Write info message in red." self.log(INFO, RED % self._format_raw(*message)) def info_green(self, *message): "Write info message in green." self.log(INFO, GREEN % self._format_raw(*message)) def info_blue(self, *message): "Write info message in blue." self.log(INFO, BLUE % self._format_raw(*message)) def deprecate(self, *message): "Write deprecation message." self.log(DEPRECATE, RED % self._format_raw(*message)) def warning(self, *message): "Write warning message." self._log.warning(self._format(*message)) def warning_red(self, *message): "Write warning message in red." self._log.warning(RED % self._format(*message)) def warning_green(self, *message): "Write warning message in green." self._log.warning(GREEN % self._format(*message)) def warning_blue(self, *message): "Write warning message in blue." self._log.warning(BLUE % self._format(*message)) def error(self, *message): "Write error message and raise an exception." self._log.error(*message) raise self._exception_type(self._format_raw(*message)) def begin(self, *message): "Begin task: write message and increase indentation level." self.info(*message) self.info("-"*len(self._format_raw(*message))) self.add_indent() def end(self): "End task: write a newline and decrease indentation level." self.info("") self.add_indent(-1) def push_level(self, level): "Push a log level on the level stack." self._level_stack.append(level) self.set_level(level) def pop_level(self): "Pop log level from the level stack, reverting to before the last push_level." self._level_stack.pop() level = self._level_stack[-1] self.set_level(level) def set_level(self, level): "Set log level." self._level_stack[-1] = level #self._log.setLevel(level) self._handler.setLevel(level) def set_indent(self, level): "Set indentation level." self._indent_level = level def add_indent(self, increment=1): "Add to indentation level." self._indent_level += increment def get_handler(self): "Get handler for logging." return self._handler def set_handler(self, handler): """Replace handler for logging. To add additional handlers instead of replacing the existing, use log.get_logger().addHandler(myhandler). See the logging module for more details. """ self._log.removeHandler(self._handler) self._log.addHandler(handler) self._handler = handler handler.emit = types.MethodType(emit, self._handler, self._handler.__class__) def get_logger(self): "Return message logger." return self._log def set_prefix(self, prefix): "Set prefix for log messages." self._prefix = prefix def _format(self, *message): "Format message including indentation." indent = self._prefix + 2*self._indent_level*" " return "\n".join([indent + line for line in (message[0] % message[1:]).split("\n")]) def _format_raw(self, *message): "Format message without indentation." return message[0] % message[1:] #--- Set up global log functions --- # Base class for UFL exceptions class UFLException(Exception): "Base class for UFL exceptions" pass ufl_logger = Logger("UFL", UFLException) for foo in log_functions: exec("%s = ufl_logger.%s" % (foo, foo)) set_level(DEPRECATE) ufl-1.6.0/ufl/mathfunctions.py000066400000000000000000000261341255567402100163430ustar00rootroot00000000000000"""This module provides basic mathematical functions.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2008 # Modified by Kristian B. Oelgaard, 2011 import math from ufl.log import warning, error from ufl.assertions import ufl_assert from ufl.core.operator import Operator from ufl.constantvalue import is_true_ufl_scalar, ScalarValue, Zero, FloatValue, IntValue, as_ufl from ufl.common import EmptyDict from ufl.core.ufl_type import ufl_type """ TODO: Include additional functions available in (need derivatives as well): Exponential and logarithmic functions: log10 Compute common logarithm (function) TODO: Any other useful special functions? About bessel functions: http://en.wikipedia.org/wiki/Bessel_function Portable implementations of bessel functions: http://www.boost.org/doc/libs/1_47_0/libs/math/doc/sf_and_dist/html/math_toolkit/main_overview/tr1.html Implementation in C++ std::tr1:: or boost::math::tr1:: - BesselK: cyl_bessel_k(nu, x) - BesselI: cyl_bessel_i(nu, x) - BesselJ: cyl_bessel_j(nu, x) - BesselY: cyl_neumann(nu, x) """ #--- Function representations --- @ufl_type(is_abstract=True, is_scalar=True, num_ops=1) class MathFunction(Operator): "Base class for all unary scalar math functions." # Freeze member variables for objects in this class __slots__ = ("_name",) def __init__(self, name, argument): Operator.__init__(self, (argument,)) ufl_assert(is_true_ufl_scalar(argument), "Expecting scalar argument.") self._name = name def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) try: res = getattr(math, self._name)(a) except ValueError: warning('Value error in evaluation of function %s with argument %s.' % (self._name, a)) raise return res def __str__(self): return "%s(%s)" % (self._name, self.ufl_operands[0]) def __repr__(self): return "%s(%r)" % (self._name, self.ufl_operands[0]) @ufl_type() class Sqrt(MathFunction): __slots__ = () def __new__(cls, argument): if isinstance(argument, (ScalarValue, Zero)): return FloatValue(math.sqrt(float(argument))) return MathFunction.__new__(cls) def __init__(self, argument): MathFunction.__init__(self, "sqrt", argument) @ufl_type() class Exp(MathFunction): __slots__ = () def __new__(cls, argument): if isinstance(argument, (ScalarValue, Zero)): return FloatValue(math.exp(float(argument))) return MathFunction.__new__(cls) def __init__(self, argument): MathFunction.__init__(self, "exp", argument) @ufl_type() class Ln(MathFunction): __slots__ = () def __new__(cls, argument): if isinstance(argument, (ScalarValue, Zero)): return FloatValue(math.log(float(argument))) return MathFunction.__new__(cls) def __init__(self, argument): MathFunction.__init__(self, "ln", argument) def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) return math.log(a) @ufl_type() class Cos(MathFunction): __slots__ = () def __new__(cls, argument): if isinstance(argument, (ScalarValue, Zero)): return FloatValue(math.cos(float(argument))) return MathFunction.__new__(cls) def __init__(self, argument): MathFunction.__init__(self, "cos", argument) @ufl_type() class Sin(MathFunction): __slots__ = () def __new__(cls, argument): if isinstance(argument, (ScalarValue, Zero)): return FloatValue(math.sin(float(argument))) return MathFunction.__new__(cls) def __init__(self, argument): MathFunction.__init__(self, "sin", argument) @ufl_type() class Tan(MathFunction): __slots__ = () def __new__(cls, argument): if isinstance(argument, (ScalarValue, Zero)): return FloatValue(math.tan(float(argument))) return MathFunction.__new__(cls) def __init__(self, argument): MathFunction.__init__(self, "tan", argument) @ufl_type() class Cosh(MathFunction): __slots__ = () def __new__(cls, argument): if isinstance(argument, (ScalarValue, Zero)): return FloatValue(math.cosh(float(argument))) return MathFunction.__new__(cls) def __init__(self, argument): MathFunction.__init__(self, "cosh", argument) @ufl_type() class Sinh(MathFunction): __slots__ = () def __new__(cls, argument): if isinstance(argument, (ScalarValue, Zero)): return FloatValue(math.sinh(float(argument))) return MathFunction.__new__(cls) def __init__(self, argument): MathFunction.__init__(self, "sinh", argument) @ufl_type() class Tanh(MathFunction): __slots__ = () def __new__(cls, argument): if isinstance(argument, (ScalarValue, Zero)): return FloatValue(math.tanh(float(argument))) return MathFunction.__new__(cls) def __init__(self, argument): MathFunction.__init__(self, "tanh", argument) @ufl_type() class Acos(MathFunction): __slots__ = () def __new__(cls, argument): if isinstance(argument, (ScalarValue, Zero)): return FloatValue(math.acos(float(argument))) return MathFunction.__new__(cls) def __init__(self, argument): MathFunction.__init__(self, "acos", argument) @ufl_type() class Asin(MathFunction): __slots__ = () def __new__(cls, argument): if isinstance(argument, (ScalarValue, Zero)): return FloatValue(math.asin(float(argument))) return MathFunction.__new__(cls) def __init__(self, argument): MathFunction.__init__(self, "asin", argument) @ufl_type() class Atan(MathFunction): __slots__ = () def __new__(cls, argument): if isinstance(argument, (ScalarValue, Zero)): return FloatValue(math.atan(float(argument))) return MathFunction.__new__(cls) def __init__(self, argument): MathFunction.__init__(self, "atan", argument) @ufl_type(is_scalar=True, num_ops=2) class Atan2(Operator): __slots__ = () def __new__(cls, arg1, arg2): if isinstance(arg1, (ScalarValue, Zero)) and isinstance(arg2, (ScalarValue, Zero)): return FloatValue(math.atan2(float(arg1), float(arg2))) return Operator.__new__(cls) def __init__(self, arg1, arg2): Operator.__init__(self, (arg1, arg2)) ufl_assert(is_true_ufl_scalar(arg1), "Expecting scalar argument 1.") ufl_assert(is_true_ufl_scalar(arg2), "Expecting scalar argument 2.") def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) b = self.ufl_operands[1].evaluate(x, mapping, component, index_values) try: res = math.atan2(a, b) except ValueError: warning('Value error in evaluation of function atan_2 with arguments %s, %s.' % (a, b)) raise return res def __str__(self): return "atan_2(%s,%s)" % (self.ufl_operands[0], self.ufl_operands[1]) def __repr__(self): return "atan_2(%s,%s)" % (self.ufl_operands[0], self.ufl_operands[1]) def _find_erf(): import math if hasattr(math, 'erf'): return math.erf import scipy.special if hasattr(scipy.special, 'erf'): return scipy.special.erf return None @ufl_type() class Erf(MathFunction): __slots__ = () def __new__(cls, argument): if isinstance(argument, (ScalarValue, Zero)): erf = _find_erf() if erf is not None: return FloatValue(erf(float(argument))) return MathFunction.__new__(cls) def __init__(self, argument): MathFunction.__init__(self, "erf", argument) def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) erf = _find_erf() if erf is None: error("No python implementation of erf available on this system, cannot evaluate. Upgrade python or install scipy.") return erf(a) @ufl_type(is_abstract=True, is_scalar=True, num_ops=2) class BesselFunction(Operator): "Base class for all bessel functions" # Freeze member variables for objects in this class __slots__ = ("_name", "_classname") def __init__(self, name, classname, nu, argument): ufl_assert(is_true_ufl_scalar(nu), "Expecting scalar nu.") ufl_assert(is_true_ufl_scalar(argument), "Expecting scalar argument.") # Use integer representation if suitable fnu = float(nu) inu = int(nu) if fnu == inu: nu = as_ufl(inu) else: nu = as_ufl(fnu) Operator.__init__(self, (nu, argument)) self._classname = classname self._name = name def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[1].evaluate(x, mapping, component, index_values) try: import scipy.special except: error("You must have scipy installed to evaluate bessel functions in python.") name = self._name[-1] if isinstance(self.ufl_operands[0], IntValue): nu = int(self.ufl_operands[0]) functype = 'n' if name != 'i' else 'v' else: nu = self.ufl_operands[0].evaluate(x, mapping, component, index_values) functype = 'v' func = getattr(scipy.special, name + functype) return func(nu, a) def __str__(self): return "%s(%s, %s)" % (self._name, self.ufl_operands[0], self.ufl_operands[1]) def __repr__(self): return "%s(%r, %r)" % (self._classname, self.ufl_operands[0], self.ufl_operands[1]) @ufl_type() class BesselJ(BesselFunction): __slots__ = () def __init__(self, nu, argument): BesselFunction.__init__(self, "cyl_bessel_j", "BesselJ", nu, argument) @ufl_type() class BesselY(BesselFunction): __slots__ = () def __init__(self, nu, argument): BesselFunction.__init__(self, "cyl_bessel_y", "BesselY", nu, argument) @ufl_type() class BesselI(BesselFunction): __slots__ = () def __init__(self, nu, argument): BesselFunction.__init__(self, "cyl_bessel_i", "BesselI", nu, argument) @ufl_type() class BesselK(BesselFunction): __slots__ = () def __init__(self, nu, argument): BesselFunction.__init__(self, "cyl_bessel_k", "BesselK", nu, argument) ufl-1.6.0/ufl/measure.py000066400000000000000000000473111255567402100151220ustar00rootroot00000000000000"""The Measure class.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg 2008-2014 from ufl.assertions import ufl_assert from ufl.log import error, warning from ufl.core.expr import Expr from ufl.geometry import Domain, as_domain from ufl.checks import is_true_ufl_scalar from ufl.constantvalue import as_ufl from ufl.common import EmptyDict from ufl.protocols import id_or_none, metadata_equal, metadata_hashdata # TODO: Design a class DomainType(name, shortname, codim, num_cells, ...)? # TODO: Improve descriptions below: # Enumeration of valid domain types _integral_types = [ # === Integration over full topological dimension: ("cell", "dx"), # Over cells of a mesh #("macro_cell", "dE"), # Over a group of adjacent cells (TODO: Arbitrary cell group? Not currently used.) # === Integration over topological dimension - 1: ("exterior_facet", "ds"), # Over one-sided exterior facets of a mesh ("interior_facet", "dS"), # Over two-sided facets between pairs of adjacent cells of a mesh # === Integration over topological dimension 0 ("vertex", "dP"), # Over vertices of a mesh #("vertex", "dV"), # TODO: Use this over vertices? #("point", "dP"), # TODO: Use this over arbitrary points inside cells? # === Integration over custom domains ("custom", "dc"), # Over custom user-defined domains (run-time quadrature points) ("overlap", "dO"), # Over a cell fragment overlapping with two or more cells (run-time quadrature points) ("interface", "dI"), # Over facet fragment overlapping with two or more cells (run-time quadrature points) ("cutcell", "dC"), # Over a cell with some part cut away (run-time quadrature points) # === Firedrake specific hacks on the way out: # TODO: Remove these, firedrake can use metadata instead and create the measure objects in firedrake: ("exterior_facet_bottom", "ds_b"), # Over bottom facets on extruded mesh ("exterior_facet_top", "ds_t"), # Over top facets on extruded mesh ("exterior_facet_vert", "ds_v"), # Over side facets of an extruded mesh ("interior_facet_horiz", "dS_h"), # Over horizontal facets of an extruded mesh ("interior_facet_vert", "dS_v"), # Over vertical facets of an extruded mesh ] integral_type_to_measure_name = dict((l, s) for l, s in _integral_types) measure_name_to_integral_type = dict((s, l) for l, s in _integral_types) def register_integral_type(integral_type, measure_name): global integral_type_to_measure_name, measure_name_to_integral_type ufl_assert(measure_name == integral_type_to_measure_name.get(integral_type, measure_name), "Domain type already added with different measure name!") ufl_assert(integral_type == measure_name_to_integral_type.get(measure_name, integral_type), "Measure name already used for another domain type!") integral_type_to_measure_name[integral_type] = measure_name measure_name_to_integral_type[measure_name] = integral_type def as_integral_type(integral_type): "Map short name to long name and require a valid one." integral_type = integral_type.replace(" ", "_") integral_type = measure_name_to_integral_type.get(integral_type, integral_type) ufl_assert(integral_type in integral_type_to_measure_name, "Invalid integral_type.") return integral_type def integral_types(): "Return a tuple of all domain type strings." return tuple(sorted(integral_type_to_measure_name.keys())) def measure_names(): "Return a tuple of all measure name strings." return tuple(sorted(measure_name_to_integral_type.keys())) class Measure(object): __slots__ = ( "_integral_type", "_domain", "_subdomain_id", "_metadata", "_subdomain_data", ) """Representation of an integration measure. The Measure object holds information about integration properties to be transferred to a Form on multiplication with a scalar expression. """ def __init__(self, integral_type, # "dx" etc domain=None, subdomain_id="everywhere", metadata=None, subdomain_data=None ): """ integral_type: str, one of "cell", etc., or short form "dx", etc. domain: a Domain object (includes cell, dims, label, domain data) subdomain_id: either string "everywhere", a single subdomain id int, or tuple of ints metadata dict, with additional compiler-specific parameters affecting how code is generated, including parameters for optimization or debugging of generated code. subdomain_data object representing data to interpret subdomain_id with. """ # Map short name to long name and require a valid one self._integral_type = as_integral_type(integral_type) # Check that we either have a proper Domain or none self._domain = None if domain is None else as_domain(domain) ufl_assert(self._domain is None or isinstance(self._domain, Domain), "Invalid domain.") # Store subdomain data self._subdomain_data = subdomain_data # FIXME: Cannot require this (yet) because we currently have no way to implement ufl_id for dolfin SubDomain #ufl_assert(self._subdomain_data is None or hasattr(self._subdomain_data, "ufl_id"), # "Invalid domain data, missing ufl_id() implementation.") # Accept "everywhere", single subdomain, or multiple subdomains ufl_assert(subdomain_id in ("everywhere",) or isinstance(subdomain_id, int) or (isinstance(subdomain_id, tuple) and all(isinstance(did, int) for did in subdomain_id)), "Invalid subdomain_id.") self._subdomain_id = subdomain_id # Validate compiler options are None or dict ufl_assert(metadata is None or isinstance(metadata, dict), "Invalid metadata.") self._metadata = metadata or EmptyDict def integral_type(self): """Return the domain type. Valid domain types are "cell", "exterior_facet", "interior_facet", etc. """ return self._integral_type def domain(self): """Return the domain associated with this measure. This may be None or a Domain object. """ return self._domain def subdomain_id(self): "Return the domain id of this measure (integer)." return self._subdomain_id def metadata(self): """Return the integral metadata. This data is not interpreted by UFL. It is passed to the form compiler which can ignore it or use it to compile each integral of a form in a different way.""" return self._metadata def reconstruct(self, integral_type=None, subdomain_id=None, domain=None, metadata=None, subdomain_data=None): """Construct a new Measure object with some properties replaced with new values. Example: b = dm.reconstruct(subdomain_id=2) c = dm.reconstruct(metadata={ "quadrature_degree": 3 }) Used by the call operator, so this is equivalent: b = dm(2) c = dm(0, { "quadrature_degree": 3 }) """ if subdomain_id is None: subdomain_id = self.subdomain_id() if domain is None: domain = self.domain() if metadata is None: metadata = self.metadata() if subdomain_data is None: subdomain_data = self.subdomain_data() return Measure(self.integral_type(), domain=domain, subdomain_id=subdomain_id, metadata=metadata, subdomain_data=subdomain_data) def subdomain_data(self): """Return the integral subdomain_data. This data is not interpreted by UFL. Its intension is to give a context in which the domain id is interpreted.""" return self._subdomain_data # Note: Must keep the order of the first two arguments here (subdomain_id, metadata) for # backwards compatibility, because some tutorials write e.g. dx(0, {...}) to set metadata. def __call__(self, subdomain_id=None, metadata=None, domain=None, subdomain_data=None, degree=None, rule=None): """Reconfigure measure with new domain specification or metadata.""" # Let syntax dx() mean integral over everywhere all_args = (subdomain_id, metadata, domain, subdomain_data, degree, rule) if all(arg is None for arg in all_args): return self.reconstruct(subdomain_id="everywhere") # Let syntax dx(domain) or dx(domain, metadata) mean integral over entire domain. # To do this we need to hijack the first argument: if subdomain_id is not None and (isinstance(subdomain_id, Domain) or hasattr(subdomain_id, 'ufl_domain')): ufl_assert(domain is None, "Ambiguous: setting domain both as keyword argument and first argument.") subdomain_id, domain = "everywhere", as_domain(subdomain_id) # If degree or rule is set, inject into metadata. This is a quick fix to enable # the dx(..., degree=3) notation. TODO: Make degree and rule properties of integrals. if (degree, rule) != (None, None): metadata = {} if metadata is None else metadata.copy() if degree is not None: metadata["quadrature_degree"] = degree if rule is not None: metadata["quadrature_rule"] = rule # If we get any keywords, use them to reconstruct Measure. # Note that if only one argument is given, it is the subdomain_id, # e.g. dx(3) == dx(subdomain_id=3) return self.reconstruct(subdomain_id=subdomain_id, domain=domain, metadata=metadata, subdomain_data=subdomain_data) def __getitem__(self, data): """This operator supports legacy syntax in python dolfin programs. The implementation makes assumptions on the type of data, namely that it is a dolfin MeshFunction with a member mesh() which returns a dolfin Mesh. The intention is to deprecase and remove this operator at some later point. Please attach your domain data to a Domain object instead of using the ds[data] syntax. The old documentation reads: Return a new Measure for same integration type with an attached context for interpreting domain ids. By default this new Measure integrates over everywhere, but it can be restricted with a domain id as usual. Example: dx = dx[boundaries]; L = f*v*dx + g*v+dx(1). """ return self.reconstruct(subdomain_data=data) def __str__(self): global integral_type_to_measure_name name = integral_type_to_measure_name[self._integral_type] args = [] if self._subdomain_id is not None: args.append("subdomain_id=%s" % (self._subdomain_id,)) if self._domain is not None: args.append("domain=%s" % (self._domain,)) if self._metadata: # Stored as EmptyDict if None args.append("metadata=%s" % (self._metadata,)) if self._subdomain_data is not None: args.append("subdomain_data=%s" % (self._subdomain_data,)) return "%s(%s)" % (name, ', '.join(args)) def __repr__(self): "Return a repr string for this Measure." global integral_type_to_measure_name d = integral_type_to_measure_name[self._integral_type] args = [] args.append(repr(self._integral_type)) if self._subdomain_id is not None: args.append("subdomain_id=%r" % (self._subdomain_id,)) if self._domain is not None: args.append("domain=%r" % (self._domain,)) if self._metadata: # Stored as EmptyDict if None args.append("metadata=%r" % (self._metadata,)) if self._subdomain_data is not None: args.append("subdomain_data=%r" % (self._subdomain_data,)) return "%s(%s)" % (type(self).__name__, ', '.join(args)) def __hash__(self): "Return a hash value for this Measure." hashdata = (self._integral_type, self._subdomain_id, hash(self._domain), metadata_hashdata(self._metadata), id_or_none(self._subdomain_data)) return hash(hashdata) def __eq__(self, other): "Checks if two Measures are equal." return (isinstance(other, Measure) and self._integral_type == other._integral_type and self._subdomain_id == other._subdomain_id and self._domain == other._domain and id_or_none(self._subdomain_data) == id_or_none(other._subdomain_data) and metadata_equal(self._metadata, other._metadata)) def __add__(self, other): """Add two measures (self+other). Creates an intermediate object used for the notation expr * (dx(1) + dx(2)) := expr * dx(1) + expr * dx(2) """ if isinstance(other, Measure): # Let dx(1) + dx(2) equal dx((1,2)) return MeasureSum(self, other) else: # Can only add Measures return NotImplemented def __mul__(self, other): """Multiply two measures (self*other). Creates an intermediate object used for the notation expr * (dm1 * dm2) := expr * dm1 * dm2 This is work in progress and not functional. """ if isinstance(other, Measure): # Tensor product measure support return MeasureProduct(self, other) else: # Can't multiply Measure from the right with non-Measure type return NotImplemented def __rmul__(self, integrand): """Multiply a scalar expression with measure to construct a form with a single integral. This is to implement the notation form = integrand * self Integration properties are taken from this Measure object. """ # Avoid circular imports from ufl.integral import Integral from ufl.form import Form # Allow python literals: 1*dx and 1.0*dx if isinstance(integrand, (int, float)): integrand = as_ufl(integrand) # Let other types implement multiplication with Measure # if they want to (to support the dolfin-adjoint TimeMeasure) if not isinstance(integrand, Expr): return NotImplemented # Allow only scalar integrands if not is_true_ufl_scalar(integrand): msg = ("Can only integrate scalar expressions. The integrand is a " + "tensor expression with value rank %d and free indices %r.") error(msg % (integrand.rank(), integrand.ufl_free_indices)) # If we have a tuple of domain ids, delegate composition to Integral.__add__: subdomain_id = self.subdomain_id() if isinstance(subdomain_id, tuple): return sum(integrand*self.reconstruct(subdomain_id=d) for d in subdomain_id) # Check that we have an integer subdomain or a string # ("everywhere" or "otherwise", any more?) ufl_assert(isinstance(subdomain_id, (int, str)), "Expecting integer or string domain id.") # If we don't have an integration domain, try to find one in integrand domain = self.domain() if domain is None: domains = integrand.domains() if len(domains) == 1: domain, = domains else: # TODO: Should this be an error? For now we need to support # assemble(1*dx, mesh=mesh) in dolfin for compatibility. # Maybe we can add a deprecation warning? #deprecation_warning("Integrals over undefined domains are deprecated.") domain = None # FIXME: Fix getitem so we can support this as well: # (probably need to store subdomain_data with Form or Integral?) # Suggestion to store canonically in Form: # integral.subdomain_data() = value # form.subdomain_data()[label][key] = value # all(domain.data() == {} for domain in form.domains()) # Then getitem data follows the data flow: # dxs = dx[gd]; dxs._subdomain_data is gd # dxs0 = dxs(0); dxs0._subdomain_data is gd # M = 1*dxs0; M.integrals()[0].subdomain_data() is gd # ; M.subdomain_data()[None][dxs.integral_type()] is gd #assemble(1*dx[cells] + 1*ds[bnd], mesh=mesh) # Otherwise create and return a one-integral form integral = Integral(integrand=integrand, integral_type=self.integral_type(), domain=domain, subdomain_id=subdomain_id, metadata=self.metadata(), subdomain_data=self.subdomain_data()) return Form([integral]) class MeasureSum(object): """Represents a sum of measures. This is a notational intermediate object to translate the notation f*(ds(1)+ds(3)) into f*ds(1) + f*ds(3) """ __slots__ = ("_measures",) def __init__(self, *measures): self._measures = measures def __rmul__(self, other): integrals = [other*m for m in self._measures] return sum(integrals) def __add__(self, other): if isinstance(other, Measure): return MeasureSum(*(self._measures + (other,))) elif isinstance(other, MeasureSum): return MeasureSum(*(self._measures + other._measures)) return NotImplemented def __str__(self): return "{\n " + "\n + ".join(map(str, self._measures)) + "\n}" class MeasureProduct(object): """Represents a product of measures. This is a notational intermediate object to handle the notation f*(dm1*dm2) This is work in progress and not functional. It needs support in other parts of ufl and the rest of the code generation chain. """ __slots__ = ("_measures",) def __init__(self, *measures): "Create MeasureProduct from given list of measures." self._measures = measures ufl_assert(len(self._measures) > 1, "Expecting at least two measures.") def __mul__(self, other): """Flatten multiplication of product measures. This is to ensure that (dm1*dm2)*dm3 is stored as a simple list (dm1,dm2,dm3) in a single MeasureProduct. """ if isinstance(other, Measure): measures = self.sub_measures() + [other] return MeasureProduct(*measures) else: return NotImplemented def __rmul__(self, integrand): error("TODO: Implement MeasureProduct.__rmul__ to construct integral and form somehow.") def sub_measures(self): "Return submeasures." return self._measures ufl-1.6.0/ufl/measureoperators.py000066400000000000000000000000001255567402100170410ustar00rootroot00000000000000ufl-1.6.0/ufl/objects.py000066400000000000000000000030541255567402100151060ustar00rootroot00000000000000"Utility objects for pretty syntax in user code." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2008 # Modified by Kristian Oelgaard, 2009 from ufl.core.multiindex import indices from ufl.cell import Cell from ufl.measure import Measure # Default indices i, j, k, l = indices(4) p, q, r, s = indices(4) # Default measures for integration from ufl.measure import integral_type_to_measure_name for integral_type, measure_name in integral_type_to_measure_name.items(): globals()[measure_name] = Measure(integral_type) ds_tb = ds_b + ds_t # TODO: Firedrake hack, remove later # Cell types vertex = Cell("vertex", 0) interval = Cell("interval", 1) triangle = Cell("triangle", 2) tetrahedron = Cell("tetrahedron", 3) quadrilateral = Cell("quadrilateral", 2) hexahedron = Cell("hexahedron", 3) # Facet is just a dummy declaration for RestrictedElement facet = "facet" ufl-1.6.0/ufl/operators.py000066400000000000000000000454531255567402100155040ustar00rootroot00000000000000"""This module extends the form language with free function operators, which are either already available as member functions on UFL objects or defined as compound operators involving basic operations on the UFL objects.""" # Copyright (C) 2008-2014 Martin Sandve Alnes and Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Kristian B. Oelgaard, 2011 import operator from six.moves import map from six.moves import xrange as range from ufl.log import error, warning from ufl.assertions import ufl_assert from ufl.form import Form from ufl.constantvalue import Zero, ScalarValue, as_ufl from ufl.differentiation import VariableDerivative, Grad, Div, Curl, NablaGrad, NablaDiv from ufl.tensoralgebra import Transposed, Inner, Outer, Dot, Cross, \ Determinant, Inverse, Cofactor, Trace, Deviatoric, Skew, Sym from ufl.coefficient import Coefficient from ufl.variable import Variable from ufl.tensors import as_tensor, as_matrix, as_vector, ListTensor from ufl.conditional import EQ, NE, LE, GE, LT, GT, \ AndCondition, OrCondition, NotCondition, Conditional, MaxValue, MinValue from ufl.mathfunctions import Sqrt, Exp, Ln, Erf,\ Cos, Sin, Tan, Cosh, Sinh, Tanh, Acos, Asin, Atan, Atan2,\ BesselJ, BesselY, BesselI, BesselK from ufl.restriction import CellAvg, FacetAvg from ufl.core.multiindex import indices from ufl.indexed import Indexed from ufl.geometry import SpatialCoordinate #--- Basic operators --- def rank(f): "UFL operator: The rank of f." f = as_ufl(f) return len(f.ufl_shape) def shape(f): "UFL operator: The shape of f." f = as_ufl(f) return f.ufl_shape #--- Elementwise tensor operators --- def elem_op_items(op_ind, indices, *args): sh = args[0].ufl_shape indices = tuple(indices) n = sh[len(indices)] def extind(ii): return indices + (ii,) if len(sh) == len(indices)+1: return [op_ind(extind(i), *args) for i in range(n)] else: return [elem_op_items(op_ind, extind(i), *args) for i in range(n)] def elem_op(op, *args): "UFL operator: Take the elementwise application of operator op on scalar values from one or more tensor arguments." args = list(map(as_ufl, args)) sh = args[0].ufl_shape ufl_assert(all(sh == x.ufl_shape for x in args), "Cannot take elementwise operation with different shapes.") if sh == (): return op(*args) def op_ind(ind, *args): return op(*[x[ind] for x in args]) return as_tensor(elem_op_items(op_ind, (), *args)) def elem_mult(A, B): "UFL operator: Take the elementwise multiplication of the tensors A and B with the same shape." return elem_op(operator.mul, A, B) def elem_div(A, B): "UFL operator: Take the elementwise division of the tensors A and B with the same shape." return elem_op(operator.truediv, A, B) def elem_pow(A, B): "UFL operator: Take the elementwise power of the tensors A and B with the same shape." return elem_op(operator.pow, A, B) #--- Tensor operators --- def transpose(A): "UFL operator: Take the transposed of tensor A." A = as_ufl(A) if A.ufl_shape == (): return A return Transposed(A) def outer(*operands): "UFL operator: Take the outer product of two or more operands." n = len(operands) if n == 1: return operands[0] elif n == 2: a, b = operands else: a = outer(*operands[:-1]) b = operands[-1] a = as_ufl(a) b = as_ufl(b) if a.ufl_shape == () and b.ufl_shape == (): return a*b return Outer(a, b) def inner(a, b): "UFL operator: Take the inner product of a and b." a = as_ufl(a) b = as_ufl(b) if a.ufl_shape == () and b.ufl_shape == (): return a*b return Inner(a, b) # TODO: Something like this would be useful in some cases, # but should inner just support a.rank() != b.rank() instead? def _partial_inner(a, b): "UFL operator: Take the partial inner product of a and b." ar, br = a.rank(), b.rank() n = min(ar, br) return contraction(a, list(range(n-ar, n-ar+n)), b, list(range(n))) def dot(a, b): "UFL operator: Take the dot product of a and b." a = as_ufl(a) b = as_ufl(b) if a.ufl_shape == () and b.ufl_shape == (): return a*b return Dot(a, b) #return contraction(a, (a.rank()-1,), b, (b.rank()-1,)) def contraction(a, a_axes, b, b_axes): "UFL operator: Take the contraction of a and b over given axes." ai, bi = a_axes, b_axes ufl_assert(len(ai) == len(bi), "Contraction must be over the same number of axes.") ash = a.ufl_shape bsh = b.ufl_shape aii = indices(a.rank()) bii = indices(b.rank()) cii = indices(len(ai)) shape = [None]*len(ai) for i, j in enumerate(ai): aii[j] = cii[i] shape[i] = ash[j] for i, j in enumerate(bi): bii[j] = cii[i] ufl_assert(shape[i] == bsh[j], "Shape mismatch in contraction.") s = a[aii]*b[bii] cii = set(cii) ii = tuple(i for i in (aii + bii) if not i in cii) return as_tensor(s, ii) def perp(v): "UFL operator: Take the perp of v, i.e. (-v1, +v0)." v = as_ufl(v) ufl_assert(v.ufl_shape == (2,), "Expecting a 2D vector expression.") return as_vector((-v[1], v[0])) def cross(a, b): "UFL operator: Take the cross product of a and b." a = as_ufl(a) b = as_ufl(b) #ufl_assert(a.ufl_shape == (3,) and b.ufl_shape == (3,), # "Expecting 3D vectors in cross product.") return Cross(a, b) def det(A): "UFL operator: Take the determinant of A." A = as_ufl(A) if A.ufl_shape == (): return A return Determinant(A) def inv(A): "UFL operator: Take the inverse of A." A = as_ufl(A) if A.ufl_shape == (): return 1 / A return Inverse(A) def cofac(A): "UFL operator: Take the cofactor of A." A = as_ufl(A) return Cofactor(A) def tr(A): "UFL operator: Take the trace of A." A = as_ufl(A) return Trace(A) def diag(A): """UFL operator: Take the diagonal part of rank 2 tensor A _or_ make a diagonal rank 2 tensor from a rank 1 tensor. Always returns a rank 2 tensor. See also diag_vector.""" # TODO: Make a compound type or two for this operator # Get and check dimensions r = A.rank() if r == 1: n, = A.ufl_shape elif r == 2: m, n = A.ufl_shape ufl_assert(m == n, "Can only take diagonal of square tensors.") else: error("Expecting rank 1 or 2 tensor.") # Build matrix row by row rows = [] for i in range(n): row = [0]*n row[i] = A[i] if r == 1 else A[i, i] rows.append(row) return as_matrix(rows) def diag_vector(A): """UFL operator: Take the diagonal part of rank 2 tensor A and return as a vector. See also diag.""" # TODO: Make a compound type for this operator # Get and check dimensions ufl_assert(A.rank() == 2, "Expecting rank 2 tensor.") m, n = A.ufl_shape ufl_assert(m == n, "Can only take diagonal of square tensors.") # Return diagonal vector return as_vector([A[i, i] for i in range(n)]) def dev(A): "UFL operator: Take the deviatoric part of A." A = as_ufl(A) return Deviatoric(A) def skew(A): "UFL operator: Take the skew symmetric part of A." A = as_ufl(A) return Skew(A) def sym(A): "UFL operator: Take the symmetric part of A." A = as_ufl(A) return Sym(A) #--- Differential operators def Dx(f, *i): """UFL operator: Take the partial derivative of f with respect to spatial variable number i. Equivalent to f.dx(\*i).""" f = as_ufl(f) return f.dx(*i) def Dt(f): "UFL operator: The partial derivative of f with respect to time." #return TimeDerivative(f) # TODO: Add class raise NotImplementedError def Dn(f): """UFL operator: Take the directional derivative of f in the facet normal direction, Dn(f) := dot(grad(f), n).""" f = as_ufl(f) if f.is_cellwise_constant(): return Zero(f.ufl_shape, f.ufl_free_indices, f.ufl_index_dimensions) from ufl.geometry import FacetNormal return dot(grad(f), FacetNormal(f.domain())) def diff(f, v): """UFL operator: Take the derivative of f with respect to the variable v. If f is a form, diff is applied to each integrand. """ # Apply to integrands if isinstance(f, Form): from ufl.algorithms.map_integrands import map_integrands return map_integrands(lambda e: diff(e, v), f) # Apply to expression f = as_ufl(f) if isinstance(v, SpatialCoordinate): return grad(f) elif isinstance(v, (Variable, Coefficient)): return VariableDerivative(f, v) else: error("Expecting a Variable or SpatialCoordinate in diff.") def grad(f): """UFL operator: Take the gradient of f. This operator follows the grad convention where grad(s)[i] = s.dx(i) grad(v)[i,j] = v[i].dx(j) grad(T)[:,i] = T[:].dx(i) for scalar expressions s, vector expressions v, and arbitrary rank tensor expressions T. See also: :py:func:`nabla_grad` """ f = as_ufl(f) return Grad(f) def div(f): """UFL operator: Take the divergence of f. This operator follows the div convention where div(v) = v[i].dx(i) div(T)[:] = T[:,i].dx(i) for vector expressions v, and arbitrary rank tensor expressions T. See also: :py:func:`nabla_div` """ f = as_ufl(f) return Div(f) def nabla_grad(f): """UFL operator: Take the gradient of f. This operator follows the grad convention where nabla_grad(s)[i] = s.dx(i) nabla_grad(v)[i,j] = v[j].dx(i) nabla_grad(T)[i,:] = T[:].dx(i) for scalar expressions s, vector expressions v, and arbitrary rank tensor expressions T. See also: :py:func:`grad` """ f = as_ufl(f) return NablaGrad(f) def nabla_div(f): """UFL operator: Take the divergence of f. This operator follows the div convention where nabla_div(v) = v[i].dx(i) nabla_div(T)[:] = T[i,:].dx(i) for vector expressions v, and arbitrary rank tensor expressions T. See also: :py:func:`div` """ f = as_ufl(f) return NablaDiv(f) def curl(f): "UFL operator: Take the curl of f." f = as_ufl(f) return Curl(f) rot = curl #--- DG operators --- def jump(v, n=None): "UFL operator: Take the jump of v across a facet." v = as_ufl(v) is_not_constant = len(v.domains()) > 0 # FIXME: Not quite right... if is_not_constant: if n is None: return v('+') - v('-') r = v.rank() if r == 0: return v('+')*n('+') + v('-')*n('-') else: return dot(v('+'), n('+')) + dot(v('-'), n('-')) else: warning("Returning zero from jump of expression without a domain. This may be erroneous.") # FIXME: Is this right? If v has no cell, it doesn't depend on # anything spatially variable or any form arguments, and thus # the jump is zero. In other words, I'm assuming that # "not v.domains()" is equivalent with "v is a constant". # Update: This is NOT true for jump(Expression("x[0]")) from dolfin. return Zero(v.ufl_shape, v.ufl_free_indices, v.ufl_index_dimensions) def avg(v): "UFL operator: Take the average of v across a facet." v = as_ufl(v) return 0.5*(v('+') + v('-')) def cell_avg(f): "UFL operator: Take the average of v over a cell." #ufl_assert((isinstance(f, Restricted) and isinstance(f.ufl_operands[0], FormArgument)) or # isinstance(f, FormArgument), "Can only take the cell average of a (optionally restricted) Coefficient or Argument.") return CellAvg(f) def facet_avg(f): "UFL operator: Take the average of v over a facet." #ufl_assert((isinstance(f, Restricted) and isinstance(f.ufl_operands[0], FormArgument)) or # isinstance(f, FormArgument), "Can only take the cell average of a (optionally restricted) Coefficient or Argument.") return FacetAvg(f) #--- Other operators --- def variable(e): "UFL operator: Define a variable representing the given expression, see also diff()." e = as_ufl(e) return Variable(e) #--- Conditional expressions --- def conditional(condition, true_value, false_value): """UFL operator: A conditional expression, taking the value of true_value when condition evaluates to true and false_value otherwise.""" return Conditional(condition, true_value, false_value) def eq(left, right): "UFL operator: A boolean expresion (left == right) for use with conditional." return EQ(left, right) def ne(left, right): "UFL operator: A boolean expresion (left != right) for use with conditional." return NE(left, right) def le(left, right): "UFL operator: A boolean expresion (left <= right) for use with conditional." return as_ufl(left) <= as_ufl(right) def ge(left, right): "UFL operator: A boolean expresion (left >= right) for use with conditional." return as_ufl(left) >= as_ufl(right) def lt(left, right): "UFL operator: A boolean expresion (left < right) for use with conditional." return as_ufl(left) < as_ufl(right) def gt(left, right): "UFL operator: A boolean expresion (left > right) for use with conditional." return as_ufl(left) > as_ufl(right) def And(left, right): "UFL operator: A boolean expresion (left and right) for use with conditional." return AndCondition(left, right) def Or(left, right): "UFL operator: A boolean expresion (left or right) for use with conditional." return OrCondition(left, right) def Not(condition): "UFL operator: A boolean expresion (not condition) for use with conditional." return NotCondition(condition) def sign(x): "UFL operator: Take the sign (+1 or -1) of x." # TODO: Add a Sign type for this? return conditional(eq(x, 0), 0, conditional(lt(x, 0), -1, +1)) def max_value(x, y): "UFL operator: Take the maximum of x and y." x = as_ufl(x) y = as_ufl(y) return MaxValue(x, y) def min_value(x, y): "UFL operator: Take the minimum of x and y." x = as_ufl(x) y = as_ufl(y) return MinValue(x, y) def Max(x, y): # TODO: Deprecate this notation? "UFL operator: Take the maximum of x and y." #return conditional(gt(x, y), x, y) return max_value(x, y) def Min(x, y): # TODO: Deprecate this notation? "UFL operator: Take the minimum of x and y." #return conditional(lt(x, y), x, y) return min_value(x, y) #--- Math functions --- def _mathfunction(f, cls): f = as_ufl(f) r = cls(f) if isinstance(r, (ScalarValue, Zero, int, float)): return float(r) return r def sqrt(f): "UFL operator: Take the square root of f." return _mathfunction(f, Sqrt) def exp(f): "UFL operator: Take the exponential of f." return _mathfunction(f, Exp) def ln(f): "UFL operator: Take the natural logarithm of f." return _mathfunction(f, Ln) def cos(f): "UFL operator: Take the cosinus of f." return _mathfunction(f, Cos) def sin(f): "UFL operator: Take the sinus of f." return _mathfunction(f, Sin) def tan(f): "UFL operator: Take the tangent of f." return _mathfunction(f, Tan) def cosh(f): "UFL operator: Take the cosinus hyperbolicus of f." return _mathfunction(f, Cosh) def sinh(f): "UFL operator: Take the sinus hyperbolicus of f." return _mathfunction(f, Sinh) def tanh(f): "UFL operator: Take the tangent hyperbolicus of f." return _mathfunction(f, Tanh) def acos(f): "UFL operator: Take the inverse cosinus of f." return _mathfunction(f, Acos) def asin(f): "UFL operator: Take the inverse sinus of f." return _mathfunction(f, Asin) def atan(f): "UFL operator: Take the inverse tangent of f." return _mathfunction(f, Atan) def atan_2(f1, f2): "UFL operator: Take the inverse tangent of f." f1 = as_ufl(f1) f2 = as_ufl(f2) r = Atan2(f1, f2) if isinstance(r, (ScalarValue, Zero, int, float)): return float(r) return r def erf(f): "UFL operator: Take the error function of f." return _mathfunction(f, Erf) def bessel_J(nu, f): """UFL operator: cylindrical Bessel function of the first kind.""" nu = as_ufl(nu) f = as_ufl(f) return BesselJ(nu, f) def bessel_Y(nu, f): """UFL operator: cylindrical Bessel function of the second kind.""" nu = as_ufl(nu) f = as_ufl(f) return BesselY(nu, f) def bessel_I(nu, f): """UFL operator: regular modified cylindrical Bessel function.""" nu = as_ufl(nu) f = as_ufl(f) return BesselI(nu, f) def bessel_K(nu, f): """UFL operator: irregular modified cylindrical Bessel function.""" nu = as_ufl(nu) f = as_ufl(f) return BesselK(nu, f) #--- Special function for exterior_derivative def exterior_derivative(f): """UFL operator: Take the exterior derivative of f. The exterior derivative uses the element family to determine whether id, grad, curl or div should be used. Note that this uses the 'grad' and 'div' operators, as opposed to 'nabla_grad' and 'nabla_div'. """ # Extract the element from the input f if isinstance(f, Indexed): expression, indices = f.ufl_operands if len(indices) > 1: raise NotImplementedError index = int(indices[0]) element = expression.element() element = element.extract_component(index)[1] elif isinstance(f, ListTensor): f0 = f.ufl_operands[0] f0expr, f0indices = f0.ufl_operands # FIXME: Assumption on type of f0!!! if len(f0indices) > 1: raise NotImplementedError index = int(f0indices[0]) element = f0expr.element() element = element.extract_component(index)[1] else: try: element = f.element() except: error("Unable to determine element from %s" % f) # Extract the family and the family = element.family() gdim = element.domain().geometric_dimension() # L^2 elements: if "Disc" in family: return f # H^1 elements: if "Lagrange" in family: if gdim == 1: return grad(f)[0] # Special-case 1D vectors as scalars return grad(f) # H(curl) elements: if "curl" in family: return curl(f) # H(div) elements: if "Brezzi" in family or "Raviart" in family: return div(f) error("Unable to determine exterior_derivative. Family is '%s'" % family) ufl-1.6.0/ufl/permutation.py000066400000000000000000000077211255567402100160310ustar00rootroot00000000000000"""This module provides utility functions for computing permutations and generating index lists.""" # Copyright (C) 2008-2014 Anders Logg and Kent-Andre Mardal # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Martin Alnes 2009-2014 from six.moves import xrange as range def compute_indices(shape): "Compute all index combinations for given shape" if len(shape) == 0: return ((),) sub_indices = compute_indices(shape[1:]) indices = [] for i in range(shape[0]): for sub_index in sub_indices: indices.append((i,) + sub_index) return tuple(indices) # functional version: def compute_indices2(shape): "Compute all index combinations for given shape" return ((),) if len(shape) == 0 else tuple((i,) + sub_index for i in range(shape[0]) for sub_index in compute_indices2(shape[1:])) def build_component_numbering(shape, symmetry): """Build a numbering of components within the given value shape, taking into consideration a symmetry mapping which leaves the mapping noncontiguous. Returns a dict { component -> numbering } and an ordered list of components [ numbering -> component ]. The dict contains all components while the list only contains the ones not mapped by the symmetry mapping.""" vi2si, si2vi = {}, [] indices = compute_indices(shape) # Number components not in symmetry mapping for c in indices: if c not in symmetry: vi2si[c] = len(si2vi) si2vi.append(c) # Copy numbering to mapped components for c in indices: if c in symmetry: vi2si[c] = vi2si[symmetry[c]] # Validate for k, c in enumerate(si2vi): assert vi2si[c] == k return vi2si, si2vi def compute_permutations(k, n, skip = None): """Compute all permutations of k elements from (0, n) in rising order. Any elements that are contained in the list skip are not included.""" if k == 0: return [] if skip is None: skip = [] if k == 1: return [(i,) for i in range(n) if not i in skip] pp = compute_permutations(k - 1, n, skip) permutations = [] for i in range(n): if i in skip: continue for p in pp: if i < p[0]: permutations.append((i,) + p) return permutations def compute_permutation_pairs(j, k): """Compute all permutations of j + k elements from (0, j + k) in rising order within (0, j) and (j, j + k) respectively.""" permutations = [] pp0 = compute_permutations(j, j + k) for p0 in pp0: pp1 = compute_permutations(k, j + k, p0) for p1 in pp1: permutations.append((p0, p1)) return permutations def compute_sign(permutation): "Compute sign by sorting." sign = 1 n = len(permutation) p = [p for p in permutation] for i in range(n - 1): for j in range(n - 1): if p[j] > p[j + 1]: (p[j], p[j + 1]) = (p[j + 1], p[j]) sign = -sign elif p[j] == p[j + 1]: return 0 return sign def compute_order_tuples(k, n): "Compute all tuples of n integers such that the sum is k" if n == 1: return ((k,),) order_tuples = [] for i in range(k + 1): for order_tuple in compute_order_tuples(k - i, n - 1): order_tuples.append(order_tuple + (i,)) return tuple(order_tuples) ufl-1.6.0/ufl/precedence.py000066400000000000000000000122151255567402100155510ustar00rootroot00000000000000"Precedence handling." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from ufl.log import warning from six import iteritems # FIXME: This code is crap... def parstr(child, parent, pre="(", post=")", format=str): # Execute when needed instead of on import, # which leads to all kinds of circular trouble. # Fixing this could be an optimization of str(expr) though. if not hasattr(parent, '_precedence'): assign_precedences(build_precedence_list()) # We want child to be evaluated fully first, # and if the parent has higher precedence # we later wrap in (). s = format(child) # Operators where operands are always parenthesized because precedence is not defined below if parent._precedence == 0: return pre + s + post # If parent operator binds stronger than child, must parenthesize child # FIXME: Is this correct for all possible positions of () in a + b + c? # FIXME: Left-right rule if parent._precedence > child._precedence: # parent = indexed, child = terminal return pre + s + post # Nothing needed return s def build_precedence_list(): from ufl.classes import Operator, Terminal, Sum, IndexSum, Product, Division, Power, MathFunction, BesselFunction, Abs, Indexed # TODO: Fill in other types... #Power <= Transposed precedence_list = [] # Default operator behaviour: should always add parentheses precedence_list.append((Operator,)) precedence_list.append((Sum,)) # sum_i a + b = (sum_i a) + b != sum_i (a + b), sum_i binds stronger than +, but weaker than product precedence_list.append((IndexSum,)) precedence_list.append((Product, Division,)) # NB! Depends on language! precedence_list.append((Power, MathFunction, BesselFunction, Abs)) precedence_list.append((Indexed,)) # Default terminal behaviour: should never add parentheses precedence_list.append((Terminal,)) return precedence_list def build_precedence_mapping(precedence_list): """Given a precedence list, build a dict with class->int mappings. Utility function used by some external code. """ from ufl.classes import Expr, all_ufl_classes, abstract_classes pm = {} missing = set() # Assign integer values for each precedence level k = 0 for p in precedence_list: for c in p: pm[c] = k k += 1 # Check for missing classes, fill in subclasses for c in all_ufl_classes: if c not in abstract_classes and c not in pm: b = c.__bases__[0] while b is not Expr: if b in pm: pm[c] = pm[b] break b = b.__bases__[0] if c not in pm: missing.add(c) return pm, missing def assign_precedences(precedence_list): "Given a precedence list, assign ints to class._precedence." pm, missing = build_precedence_mapping(precedence_list) for c, p in sorted(iteritems(pm), key=lambda x: x[0].__name__): c._precedence = p if missing: msg = "Missing precedence levels for classes:\n" +\ "\n".join(' %s' % c for c in sorted(missing)) warning(msg) """ # Code from uflacs: import ufl def build_precedence_list(): "Builds a list of operator types by precedence order in the C language." # FIXME: Add all types we need here. pl = [] pl.append((ufl.classes.Conditional,)) pl.append((ufl.classes.OrCondition,)) pl.append((ufl.classes.AndCondition,)) pl.append((ufl.classes.EQ, ufl.classes.NE)) pl.append((ufl.classes.Condition,)) # <,>,<=,>= pl.append((ufl.classes.NotCondition,)) # FIXME pl.append((ufl.classes.Sum,)) pl.append((ufl.classes.Product, ufl.classes.Division,)) # The highest precedence items will never need # parentheses around them or their operands pl.append((ufl.classes.Power, ufl.classes.MathFunction, ufl.classes.Abs, ufl.classes.BesselFunction, ufl.classes.Indexed, ufl.classes.Grad, ufl.classes.PositiveRestricted, ufl.classes.NegativeRestricted, ufl.classes.Terminal)) # FIXME: Write a unit test that checks this list against all ufl classes return pl def build_precedence_map(): from ufl.precedence import build_precedence_mapping pm, missing = build_precedence_mapping(build_precedence_list()) if 0 and missing: # Enable to see which types we are missing print("Missing precedence levels for the types:") print("\n".join(' %s' % c for c in missing)) return pm """ ufl-1.6.0/ufl/protocols.py000066400000000000000000000015061255567402100155010ustar00rootroot00000000000000 def id_or_none(obj): """Returns None if the object is None, obj.ufl_id() if available, or id(obj) if not. This allows external libraries to implement an alternative to id(obj) in the ufl_id() function, such that ufl can identify objects as the same without knowing about their types. """ if obj is None: return None elif hasattr(obj, 'ufl_id'): return obj.ufl_id() else: #warning("Expecting an object implementing the ufl_id function.") # TODO: Can we enable this? Not sure about meshfunctions etc in dolfin. return id(obj) def metadata_equal(a, b): return (sorted((k, id(v)) for k, v in list(a.items())) == sorted((k, id(v)) for k, v in list(b.items()))) def metadata_hashdata(md): return tuple(sorted((k, id(v)) for k, v in list(md.items()))) ufl-1.6.0/ufl/referencevalue.py000066400000000000000000000036511255567402100164530ustar00rootroot00000000000000"Representation of the reference value of a function." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from ufl.core.ufl_type import ufl_type from ufl.core.operator import Operator from ufl.core.terminal import FormArgument from ufl.log import error from ufl.assertions import ufl_assert @ufl_type(num_ops=1, is_index_free=True, is_terminal_modifier=True, is_in_reference_frame=True) class ReferenceValue(Operator): "Representation of the reference cell value of a form argument." __slots__ = () def __init__(self, f): ufl_assert(isinstance(f, FormArgument), "Can only take reference value of form arguments.") Operator.__init__(self, (f,)) @property def ufl_shape(self): return self.ufl_operands[0].element().reference_value_shape() def reconstruct(self, op): "Return a new object of the same type with new operands." return self._ufl_class_(op) def evaluate(self, x, mapping, component, index_values, derivatives=()): "Get child from mapping and return the component asked for." error("Evaluate not implemented.") def __str__(self): return "reference_value(%s)" % self.ufl_operands[0] def __repr__(self): return "ReferenceValue(%r)" % self.ufl_operands[0] ufl-1.6.0/ufl/restriction.py000066400000000000000000000066371255567402100160340ustar00rootroot00000000000000"""Restriction operations.""" # Copyright (C) 2008-2015 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from ufl.log import error from ufl.core.operator import Operator from ufl.precedence import parstr from ufl.core.ufl_type import ufl_type #--- Restriction operators --- @ufl_type(is_abstract=True, num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0, is_restriction=True) class Restricted(Operator): __slots__ = () # TODO: Add __new__ operator here, e.g. restricted(literal) == literal def __init__(self, f): Operator.__init__(self, (f,)) def side(self): return self._side def evaluate(self, x, mapping, component, index_values): return self.ufl_operands[0].evaluate(x, mapping, component, index_values) def __str__(self): return "%s('%s')" % (parstr(self.ufl_operands[0], self), self._side) def __repr__(self): return "%s(%r)" % (self._ufl_class_.__name__, self.ufl_operands[0]) @ufl_type(is_terminal_modifier=True) class PositiveRestricted(Restricted): __slots__ = () _side = "+" @ufl_type(is_terminal_modifier=True) class NegativeRestricted(Restricted): __slots__ = () _side = "-" # TODO: Place in a better file? @ufl_type(is_index_free=True, num_ops=1, is_terminal_modifier=True, is_evaluation=True) class CellAvg(Operator): __slots__ = () # TODO: Add __new__ operator here, e.g. cell_avg(literal) == literal def __init__(self, f): Operator.__init__(self, (f,)) @property def ufl_shape(self): return self.ufl_operands[0].ufl_shape def evaluate(self, x, mapping, component, index_values): "Performs an approximate symbolic evaluation, since we dont have a cell." return self.ufl_operands[0].evaluate(x, mapping, component, index_values) def __str__(self): return "cell_avg(%s)" % (self.ufl_operands[0],) def __repr__(self): return "CellAvg(%r)" % self.ufl_operands[0] # TODO: Place in a better file? @ufl_type(is_index_free=True, num_ops=1, is_terminal_modifier=True, is_evaluation=True) class FacetAvg(Operator): __slots__ = () # TODO: Add __new__ operator here, e.g. facet_avg(literal) == literal def __init__(self, f): Operator.__init__(self, (f,)) @property def ufl_shape(self): return self.ufl_operands[0].ufl_shape def evaluate(self, x, mapping, component, index_values): "Performs an approximate symbolic evaluation, since we dont have a cell." return self.ufl_operands[0].evaluate(x, mapping, component, index_values) def __str__(self): return "facet_avg(%s)" % (self.ufl_operands[0],) def __repr__(self): return "FacetAvg(%r)" % self.ufl_operands[0] ufl-1.6.0/ufl/sobolevspace.py000066400000000000000000000067111255567402100161450ustar00rootroot00000000000000"""This module defines a symbolic heirarchy of Sobolev spaces to enable symbolic reasoning about the spaces in which finite elements lie.""" # Copyright (C) 2014 Imperial College London and others # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Written by David Ham 2014 # # Modified by Martin Alnaes 2014 # Modified by Lizao Li 2015 class SobolevSpace(object): """Symbolic representation of a Sobolev space. This implements a subset of the methods of a Python set so that finite elements and other Sobolev spaces can be tested for inclusion. """ def __init__(self, name, parents=None): """Instantiate a SobolevSpace object. :param name: The name of this space, :param parents: A set of Sobolev spaces of which this space is a subspace.""" self.name = name p = frozenset(parents or []) # Ensure that the inclusion operations are transitive. self.parents = p.union(*[p_.parents for p_ in p]) def __str__(self): return self.name def __repr__(self): return "SobolevSpace(%r, %r)" % (self.name, list(self.parents)) def _repr_latex_(self): if len(self.name) == 2: return "$%s^%s$" % tuple(self.name) else: return "$%s(%s)$" % (self.name[0], self.name[1:].lower()) def __eq__(self, other): return isinstance(other, SobolevSpace) and self.name == other.name def __hash__(self): return hash(("SobolevSpace", self.name)) def __contains__(self, other): """Implement `fe in s` where `fe` is a :class:`~finiteelement.FiniteElement` and `s` is a :class:`SobolevSpace`""" if isinstance(other, SobolevSpace): raise TypeError("Unable to test for inclusion of a " + "SobolevSpace in another SobolevSpace. " + "Did you mean to use <= instead?") return (other.sobolev_space() == self or self in other.sobolev_space().parents) def __lt__(self, other): """In common with intrinsic Python sets, < indicates "is a proper subset of.""" return other in self.parents def __le__(self, other): """In common with intrinsic Python sets, <= indicates "is a subset of." """ return (self == other) or (other in self.parents) def __gt__(self, other): """In common with intrinsic Python sets, > indicates "is a proper superset of.""" return self in other.parents def __ge__(self, other): """In common with intrinsic Python sets, >= indicates "is a superset of." """ return (self == other) or (self in other.parents) L2 = SobolevSpace("L2") HDiv = SobolevSpace("HDiv", [L2]) HCurl = SobolevSpace("HCurl", [L2]) H1 = SobolevSpace("H1", [HDiv, HCurl, L2]) H2 = SobolevSpace("H2", [H1]) HEin = SobolevSpace("HEin", [L2]) ufl-1.6.0/ufl/sorting.py000066400000000000000000000175731255567402100151550ustar00rootroot00000000000000"""This module contains a sorting rule for expr objects that is more robust w.r.t. argument numbering than using repr.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2009-2010. # Modified by Johan Hake, 2010. # # First added: 2008-11-26 # Last changed: 2013-01-02 from six.moves import zip from ufl.log import error from ufl.core.terminal import Terminal from ufl.argument import Argument from ufl.coefficient import Coefficient from ufl.core.multiindex import Index, FixedIndex, MultiIndex from ufl.variable import Label from ufl.geometry import GeometricQuantity def _cmp3(a, b): "Replacement for cmp(), removed in Python 3." return -1 if (a < b) else (+1 if a > b else 0) def _cmp_terminal(a, b): # Is it a... # ... MultiIndex? Careful not to depend on Index.count() here! This is placed first because it is most frequent. if isinstance(a, MultiIndex): # Make decision based on the first index pair possible for i, j in zip(a._indices, b._indices): fix1 = isinstance(i, FixedIndex) fix2 = isinstance(j, FixedIndex) if fix1 and fix2: # Both are FixedIndex, sort by value x, y = i._value, j._value if x < y: return -1 elif x > y: return +1 else: # Same value, no decision continue elif fix1: # Sort fixed before free return -1 elif fix2: # Sort fixed before free return +1 else: # Both are Index, no decision, do not depend on count! pass # Failed to make a decision, return 0 by default # (this does not mean equality, it could be e.g. # [i,0] vs [j,0] because the counts of i,j cannot be used) return 0 # ... Label object? elif isinstance(a, Label): # Don't compare counts! Causes circular problems when renumbering to get a canonical form. # Therefore, even though a and b are not equal in general (__eq__ won't be True), # but for this sorting they are considered equal and we return 0. return 0 # ... Coefficient? elif isinstance(a, Coefficient): # It's ok to compare relative counts for Coefficients, # since their ordering is a property of the form x, y = a._count, b._count if x < y: return -1 elif x > y: return +1 else: return 0 # ... Argument? elif isinstance(a, Argument): # It's ok to compare relative number and part for Arguments, # since their ordering is a property of the form x = (a._number, a._part) y = (b._number, b._part) if x < y: return -1 elif x > y: return +1 else: return 0 # ... another kind of Terminal object? else: # The cost of repr on a terminal is fairly small, and bounded x, y = repr(a), repr(b) if x < y: return -1 elif x > y: return +1 else: return 0 def _cmp_operator(a, b): # If the hash is the same, assume equal for the purpose of sorting. # This introduces a minor chance of nondeterministic behaviour, just as with MultiIndex. # Although collected statistics for complicated forms suggest that the hash # function is pretty good so there shouldn't be collisions. if hash(a) == hash(b): # FIXME: Test this for performance improvement. return 0 aops = a.ufl_operands bops = b.ufl_operands # Sort by children in natural order for (r, s) in zip(aops, bops): # Ouch! This becomes worst case O(n) then? # FIXME: Perhaps replace with comparison of hash value? But that's not stable between python versions. c = cmp_expr(r, s) if c: return c # All children compare as equal, a and b must be equal. Except for... # A few type, notably ExprList and ExprMapping, can have a different number of operands. # Sort by the length if it's different. Doing this after sorting by children because # these types are rare so we try to avoid the cost of this check for most nodes. return _cmp3(len(aops), len(bops)) def cmp_expr2(a, b): "Sorting rule for Expr objects. NB! Do not use to compare for equality!" # First sort quickly by type code c = _cmp3(a._ufl_typecode_, b._ufl_typecode_) if c: return c # Now we know that the type is the same, check further based on type specific properties. if a._ufl_is_terminal_: return _cmp_terminal(a, b) else: return _cmp_operator(a, b) # FIXME: Test and benchmark this! Could be faster since it avoids the recursion. def cmp_expr(a, b): # Modelled after pre_traversal to avoid recursion: left = [(a, b)] while left: a, b = left.pop() # First sort quickly by type code x, y = a._ufl_typecode_, b._ufl_typecode_ if x < y: return -1 elif x > y: return +1 # Now we know that the type is the same, check further based on type specific properties. if a._ufl_is_terminal_: c = _cmp_terminal(a, b) if c: return c else: # If the hash is the same, assume equal for the purpose of sorting. # This introduces a minor chance of nondeterministic behaviour, just as with MultiIndex. # Although collected statistics for complicated forms suggest that the hash # function is pretty good so there shouldn't be collisions. #if hash(a) == hash(b): # FIXME: Test this for performance improvement. # return 0 # Delve into subtrees aops = a.ufl_operands bops = b.ufl_operands # Sort by children in natural order for (r, s) in zip(aops, bops): # Skip subtree if objects are the same if r is s: continue # Append subtree for further inspection left.append((r, s)) # All children compare as equal, a and b must be equal. Except for... # A few type, notably ExprList and ExprMapping, can have a different number of operands. # Sort by the length if it's different. Doing this after sorting by children because # these types are rare so we try to avoid the cost of this check for most nodes. x, y = len(aops), len(bops) if x < y: return -1 elif x > y: return +1 # Equal if we get out of the above loop! return 0 # Not in python 2.6... #from functools import cmp_to_key class ExprKey(object): __slots__ = ('x',) def __init__(self, x): self.x = x def __lt__(self, other): return cmp_expr(self.x, other.x) < 0 def sorted_expr(seq): return sorted(seq, key=ExprKey) def sorted_expr_sum(seq): seq2 = sorted(seq, key=ExprKey) s = seq2[0] for e in seq2[1:]: s = s + e return s from ufl.common import topological_sorting # FIXME: Remove this, update whoever uses it in ufl and ffc etc. ufl-1.6.0/ufl/split_functions.py000066400000000000000000000114331255567402100167000ustar00rootroot00000000000000"Algorithm for splitting a Coefficient or Argument into subfunctions." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # # Modified by Anders Logg, 2008 from six.moves import xrange as range from ufl.log import error from ufl.assertions import ufl_assert from ufl.common import product, EmptyDict from ufl.finiteelement import MixedElement, TensorElement from ufl.tensors import as_vector, as_matrix, as_tensor def split(v): """UFL operator: If v is a Coefficient or Argument in a mixed space, returns a tuple with the function components corresponding to the subelements.""" # Special case: simple element, just return function in a tuple element = v.element() if not isinstance(element, MixedElement): return (v,) if isinstance(element, TensorElement): s = element.symmetry() if s: # FIXME: How should this be defined? Should we return one subfunction # for each value component or only for those not mapped to another? # I think split should ignore the symmetry. error("Split not implemented for symmetric tensor elements.") # Compute value size value_size = product(element.value_shape()) actual_value_size = value_size # Extract sub coefficient offset = 0 sub_functions = [] for i, e in enumerate(element.sub_elements()): shape = e.value_shape() rank = len(shape) if rank == 0: # This subelement is a scalar, always maps to a single value subv = v[offset] offset += 1 elif rank == 1: # This subelement is a vector, always maps to a sequence of values sub_size, = shape components = [v[j] for j in range(offset, offset + sub_size)] subv = as_vector(components) offset += sub_size elif rank == 2: # This subelement is a tensor, possibly with symmetries, slightly more complicated... # Size of this subvalue sub_size = product(shape) # If this subelement is a symmetric element, subtract symmetric components s = None if isinstance(e, TensorElement): s = e.symmetry() s = s or EmptyDict # If we do this, we must fix the size computation in MixedElement.__init__ as well #actual_value_size -= len(s) #sub_size -= len(s) #print s # Build list of lists of value components components = [] for ii in range(shape[0]): row = [] for jj in range(shape[1]): # Map component (i,j) through symmetry mapping c = (ii, jj) c = s.get(c, c) i, j = c # Extract component c of this subvalue from global tensor v if v.rank() == 1: # Mapping into a flattened vector k = offset + i*shape[1] + j component = v[k] #print "k, offset, i, j, shape, component", k, offset, i, j, shape, component elif v.rank() == 2: # Mapping into a concatenated tensor (is this a figment of my imagination?) error("Not implemented.") row_offset, col_offset = 0, 0 # TODO k = (row_offset + i, col_offset + j) component = v[k] row.append(component) components.append(row) # Make a matrix of the components subv = as_matrix(components) offset += sub_size else: # TODO: Handle rank > 2? Or is there such a thing? error("Don't know how to split functions with sub functions of rank %d (yet)." % rank) #for indices in compute_indices(shape): # #k = offset + sum(i*s for (i,s) in izip(indices, shape[1:] + (1,))) # vs.append(v[indices]) sub_functions.append(subv) ufl_assert(actual_value_size == offset, "Logic breach in function splitting.") return tuple(sub_functions) ufl-1.6.0/ufl/tensoralgebra.py000066400000000000000000000355421255567402100163140ustar00rootroot00000000000000"""Compound tensor algebra operations.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from ufl.log import warning from ufl.assertions import ufl_assert from ufl.constantvalue import Zero from ufl.algebra import Operator from ufl.precedence import parstr from ufl.sorting import sorted_expr from ufl.core.ufl_type import ufl_type from ufl.index_combination_utils import merge_nonoverlapping_indices ### Algebraic operations on tensors: # FloatValues: # dot(a,b) = a*b # inner(a,b) = a*b # outer(a,b) = a*b # Vectors: # dot(u,v) = u_i v_i # inner(u,v) = u_i v_i # outer(u,v) = A | A_ij = u_i v_j # Matrices: # dot(A,B) = C | C_ij = A_{ik} B_{kj} # inner(A,B) = A_{ij} B_{ij} # outer(A,B) = C | C_ijkl = A_ij B_kl # Combined: # dot(A,u) = v | v_i = A_{ik} u_k # inner(A,u) = v | v_i = A_{ik} u_k # outer(A,u) = C | C_ijk = B_ij u_k # dot(u,B) = v | v_i = u_k B_{ki} # inner(u,B) = v | v_i = u_k B_{ki} # outer(u,B) = C | C_ijk = u_i B_jk # # Argument requirements: # dot(x,y): last index of x has same dimension as first index of y # inner(x,y): shape of x equals the shape of y # --- Classes representing compound tensor algebra operations --- @ufl_type(is_abstract=True) class CompoundTensorOperator(Operator): __slots__ = () def __init__(self, operands): Operator.__init__(self, operands) # TODO: Use this and make Sum handle scalars only? # This would simplify some algorithms. The only # problem is we can't use + in many algorithms because # this type should be expanded by expand_compounds. #class TensorSum(CompoundTensorOperator): # "Sum of nonscalar expressions." # pass # TODO: Use this similarly to TensorSum? # This would simplify some algorithms. The only # problem is we can't use / in many algorithms because # this type should be expanded by expand_compounds. #class TensorDivision(CompoundTensorOperator): # "Division of nonscalar expression with a scalar expression." # pass # TODO: Use this similarly to TensorSum? # This would simplify some algorithms. The only # problem is we can't use * in many algorithms because # this type should be expanded by expand_compounds. #class MatrixProduct(CompoundTensorOperator): # "Product of a matrix with a matrix or vector." # pass # TODO: Use this similarly to TensorSum? # This would simplify some algorithms. The only # problem is we can't use abs in many algorithms because # this type should be expanded by expand_compounds. #class TensorAbs(CompoundTensorOperator): # "Absolute value of nonscalar expression." # pass @ufl_type(is_shaping=True, num_ops=1, inherit_indices_from_operand=0) class Transposed(CompoundTensorOperator): __slots__ = () def __new__(cls, A): if isinstance(A, Zero): a, b = A.ufl_shape return Zero((b, a), A.ufl_free_indices, A.ufl_index_dimensions) return CompoundTensorOperator.__new__(cls) def __init__(self, A): CompoundTensorOperator.__init__(self, (A,)) ufl_assert(A.rank() == 2, "Transposed is only defined for rank 2 tensors.") @property def ufl_shape(self): s = self.ufl_operands[0].ufl_shape return (s[1], s[0]) def __str__(self): return "%s^T" % parstr(self.ufl_operands[0], self) def __repr__(self): return "Transposed(%r)" % self.ufl_operands[0] @ufl_type(num_ops=2) class Outer(CompoundTensorOperator): __slots__ = ("ufl_free_indices", "ufl_index_dimensions") def __new__(cls, a, b): ash, bsh = a.ufl_shape, b.ufl_shape if isinstance(a, Zero) or isinstance(b, Zero): fi, fid = merge_nonoverlapping_indices(a, b) return Zero(ash + bsh, fi, fid) if ash == () or bsh == (): return a * b return CompoundTensorOperator.__new__(cls) def __init__(self, a, b): CompoundTensorOperator.__init__(self, (a, b)) fi, fid = merge_nonoverlapping_indices(a, b) self.ufl_free_indices = fi self.ufl_index_dimensions = fid @property def ufl_shape(self): return self.ufl_operands[0].ufl_shape + self.ufl_operands[1].ufl_shape def __str__(self): return "%s (X) %s" % (parstr(self.ufl_operands[0], self), parstr(self.ufl_operands[1], self)) def __repr__(self): return "Outer(%r, %r)" % (self.ufl_operands[0], self.ufl_operands[1]) @ufl_type(num_ops=2) class Inner(CompoundTensorOperator): __slots__ = ("ufl_free_indices", "ufl_index_dimensions") def __new__(cls, a, b): # Checks ash, bsh = a.ufl_shape, b.ufl_shape ufl_assert(ash == bsh, "Shape mismatch.") # Simplification if isinstance(a, Zero) or isinstance(b, Zero): fi, fid = merge_nonoverlapping_indices(a, b) return Zero((), fi, fid) elif ash == (): return a*b return CompoundTensorOperator.__new__(cls) def __init__(self, a, b): # sort operands for unique representation, # must be independent of various counts etc. # as explained in cmp_expr a, b = sorted_expr((a, b)) CompoundTensorOperator.__init__(self, (a, b)) fi, fid = merge_nonoverlapping_indices(a, b) self.ufl_free_indices = fi self.ufl_index_dimensions = fid ufl_shape = () def __str__(self): return "%s : %s" % (parstr(self.ufl_operands[0], self), parstr(self.ufl_operands[1], self)) def __repr__(self): return "Inner(%r, %r)" % (self.ufl_operands[0], self.ufl_operands[1]) @ufl_type(num_ops=2) class Dot(CompoundTensorOperator): __slots__ = ("ufl_free_indices", "ufl_index_dimensions") def __new__(cls, a, b): ash = a.ufl_shape bsh = b.ufl_shape ar, br = len(ash), len(bsh) scalar = (ar == 0 and br == 0) # Checks ufl_assert((ar >= 1 and br >= 1) or scalar, "Dot product requires non-scalar arguments, "\ "got arguments with ranks %d and %d." % \ (ar, br)) ufl_assert(scalar or ash[-1] == bsh[0], "Dimension mismatch in dot product.") # Simplification if isinstance(a, Zero) or isinstance(b, Zero): shape = ash[:-1] + bsh[1:] fi, fid = merge_nonoverlapping_indices(a, b) return Zero(shape, fi, fid) elif scalar: # TODO: Move this to def dot()? return a * b return CompoundTensorOperator.__new__(cls) def __init__(self, a, b): CompoundTensorOperator.__init__(self, (a, b)) fi, fid = merge_nonoverlapping_indices(a, b) self.ufl_free_indices = fi self.ufl_index_dimensions = fid @property def ufl_shape(self): return self.ufl_operands[0].ufl_shape[:-1] + self.ufl_operands[1].ufl_shape[1:] def __str__(self): return "%s . %s" % (parstr(self.ufl_operands[0], self), parstr(self.ufl_operands[1], self)) def __repr__(self): return "Dot(%r, %r)" % (self.ufl_operands[0], self.ufl_operands[1]) @ufl_type(num_ops=2) class Cross(CompoundTensorOperator): __slots__ = ("ufl_free_indices", "ufl_index_dimensions") def __new__(cls, a, b): ash = a.ufl_shape bsh = b.ufl_shape # Checks ufl_assert(len(ash) == 1 and ash == bsh, "Cross product requires arguments of rank 1.") # Simplification if isinstance(a, Zero) or isinstance(b, Zero): fi, fid = merge_nonoverlapping_indices(a, b) return Zero(ash, fi, fid) return CompoundTensorOperator.__new__(cls) def __init__(self, a, b): CompoundTensorOperator.__init__(self, (a, b)) fi, fid = merge_nonoverlapping_indices(a, b) self.ufl_free_indices = fi self.ufl_index_dimensions = fid ufl_shape = (3,) def __str__(self): return "%s x %s" % (parstr(self.ufl_operands[0], self), parstr(self.ufl_operands[1], self)) def __repr__(self): return "Cross(%r, %r)" % (self.ufl_operands[0], self.ufl_operands[1]) @ufl_type(num_ops=1, inherit_indices_from_operand=0) class Trace(CompoundTensorOperator): __slots__ = () def __new__(cls, A): # Checks ufl_assert(A.rank() == 2, "Trace of tensor with rank != 2 is undefined.") # Simplification if isinstance(A, Zero): return Zero((), A.ufl_free_indices, A.ufl_index_dimensions) return CompoundTensorOperator.__new__(cls) def __init__(self, A): CompoundTensorOperator.__init__(self, (A,)) ufl_shape = () def __str__(self): return "tr(%s)" % self.ufl_operands[0] def __repr__(self): return "Trace(%r)" % self.ufl_operands[0] @ufl_type(is_scalar=True, num_ops=1) class Determinant(CompoundTensorOperator): __slots__ = () def __new__(cls, A): sh = A.ufl_shape r = len(sh) Afi = A.ufl_free_indices # Checks ufl_assert(r == 0 or r == 2, "Determinant of tensor with rank != 2 is undefined.") ufl_assert(r == 0 or sh[0] == sh[1], "Cannot take determinant of rectangular rank 2 tensor.") ufl_assert(not Afi, "Not expecting free indices in determinant.") # Simplification if isinstance(A, Zero): return Zero((), Afi, A.ufl_index_dimensions) if r == 0: return A return CompoundTensorOperator.__new__(cls) def __init__(self, A): CompoundTensorOperator.__init__(self, (A,)) def __str__(self): return "det(%s)" % self.ufl_operands[0] def __repr__(self): return "Determinant(%r)" % self.ufl_operands[0] # TODO: Drop Inverse and represent it as product of Determinant and Cofactor? @ufl_type(is_index_free=True, num_ops=1) class Inverse(CompoundTensorOperator): __slots__ = () def __new__(cls, A): sh = A.ufl_shape r = len(sh) # Checks if A.ufl_free_indices: error("Not expecting free indices in Inverse.") if isinstance(A, Zero): error("Division by zero!") # Simplification if r == 0: return 1 / A # More checks if r != 2: error("Inverse of tensor with rank != 2 is undefined.") if sh[0] != sh[1]: error("Cannot take inverse of rectangular matrix with dimensions %s." % repr(sh)) return CompoundTensorOperator.__new__(cls) def __init__(self, A): CompoundTensorOperator.__init__(self, (A,)) @property def ufl_shape(self): return self.ufl_operands[0].ufl_shape def __str__(self): return "%s^-1" % parstr(self.ufl_operands[0], self) def __repr__(self): return "Inverse(%r)" % self.ufl_operands[0] @ufl_type(is_index_free=True, num_ops=1) class Cofactor(CompoundTensorOperator): __slots__ = () def __init__(self, A): CompoundTensorOperator.__init__(self, (A,)) # Checks sh = A.ufl_shape ufl_assert(len(sh) == 2, "Cofactor of tensor with rank != 2 is undefined.") if sh[0] != sh[1]: error("Cannot take cofactor of rectangular matrix with dimensions %s." % repr(sh)) ufl_assert(not A.ufl_free_indices, "Not expecting free indices in Cofactor.") ufl_assert(not isinstance(A, Zero), "Cannot take cofactor of zero matrix.") @property def ufl_shape(self): return self.ufl_operands[0].ufl_shape def __str__(self): return "cofactor(%s)" % self.ufl_operands[0] def __repr__(self): return "Cofactor(%r)" % self.ufl_operands[0] @ufl_type(num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class Deviatoric(CompoundTensorOperator): __slots__ = () def __new__(cls, A): sh = A.ufl_shape # Checks ufl_assert(len(sh) == 2, "Deviatoric part of tensor with rank != 2 is undefined.") if sh[0] != sh[1]: error("Cannot take deviatoric part of rectangular matrix with dimensions %s." % repr(sh)) ufl_assert(not A.ufl_free_indices, "Not expecting free indices in Deviatoric.") # Simplification if isinstance(A, Zero): return Zero(A.ufl_shape, A.ufl_free_indices, A.ufl_index_dimensions) return CompoundTensorOperator.__new__(cls) def __init__(self, A): CompoundTensorOperator.__init__(self, (A,)) def __str__(self): return "dev(%s)" % self.ufl_operands[0] def __repr__(self): return "Deviatoric(%r)" % self.ufl_operands[0] @ufl_type(num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class Skew(CompoundTensorOperator): __slots__ = () def __new__(cls, A): sh = A.ufl_shape Afi = A.ufl_free_indices # Checks ufl_assert(len(sh) == 2, "Skew symmetric part of tensor with rank != 2 is undefined.") if sh[0] != sh[1]: error("Cannot take skew part of rectangular matrix with dimensions %s." % repr(sh)) ufl_assert(not Afi, "Not expecting free indices in Skew.") # Simplification if isinstance(A, Zero): return Zero(A.ufl_shape, Afi, A.ufl_index_dimensions) return CompoundTensorOperator.__new__(cls) def __init__(self, A): CompoundTensorOperator.__init__(self, (A,)) def __str__(self): return "skew(%s)" % self.ufl_operands[0] def __repr__(self): return "Skew(%r)" % self.ufl_operands[0] @ufl_type(num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class Sym(CompoundTensorOperator): __slots__ = () def __new__(cls, A): sh = A.ufl_shape Afi = A.ufl_free_indices # Checks ufl_assert(len(sh) == 2, "Symmetric part of tensor with rank != 2 is undefined.") if sh[0] != sh[1]: error("Cannot take symmetric part of rectangular matrix with dimensions %s." % repr(sh)) ufl_assert(not Afi, "Not expecting free indices in Sym.") # Simplification if isinstance(A, Zero): return Zero(A.ufl_shape, Afi, A.ufl_index_dimensions) return CompoundTensorOperator.__new__(cls) def __init__(self, A): CompoundTensorOperator.__init__(self, (A,)) def __str__(self): return "sym(%s)" % self.ufl_operands[0] def __repr__(self): return "Sym(%r)" % self.ufl_operands[0] ufl-1.6.0/ufl/tensors.py000066400000000000000000000367351255567402100151660ustar00rootroot00000000000000"""Classes used to group scalar expressions into expressions with rank > 0.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from six.moves import zip from six.moves import xrange as range from ufl.log import warning, error from ufl.common import subdict, EmptyDict from ufl.assertions import ufl_assert from ufl.core.expr import Expr from ufl.core.operator import Operator from ufl.constantvalue import as_ufl, Zero from ufl.core.multiindex import Index, FixedIndex, MultiIndex, indices from ufl.indexed import Indexed from ufl.index_combination_utils import remove_indices from ufl.core.ufl_type import ufl_type # --- Classes representing tensors of UFL expressions --- @ufl_type(is_shaping=True, num_ops="varying", inherit_indices_from_operand=0) class ListTensor(Operator): """UFL operator type: Wraps a list of expressions into a tensor valued expression of one higher rank.""" __slots__ = () def __new__(cls, *expressions): # All lists and tuples should already be unwrapped in as_tensor if any(not isinstance(e, Expr) for e in expressions): error("Expecting only UFL expressions in ListTensor constructor.") # Get properties of the first expression e0 = expressions[0] sh = e0.ufl_shape fi = e0.ufl_free_indices fid = e0.ufl_index_dimensions # Obviously, each subexpression must have the same shape if any(sh != e.ufl_shape for e in expressions[1:]): error("Cannot create a tensor by joining subexpressions with different shapes.") if any(fi != e.ufl_free_indices for e in expressions[1:]): error("Cannot create a tensor where the components have different free indices.") if any(fid != e.ufl_index_dimensions for e in expressions[1:]): error("Cannot create a tensor where the components have different free index dimensions.") # Simplify to Zero if possible if all(isinstance(e, Zero) for e in expressions): shape = (len(expressions),) + sh return Zero(shape, fi, fid) return Operator.__new__(cls) def __init__(self, *expressions): Operator.__init__(self, expressions) # Checks indexset = set(self.ufl_operands[0].ufl_free_indices) ufl_assert(all(not (indexset ^ set(e.ufl_free_indices)) for e in self.ufl_operands),\ "Can't combine subtensor expressions with different sets of free indices.") @property def ufl_shape(self): return (len(self.ufl_operands),) + self.ufl_operands[0].ufl_shape def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." return all(e.is_cellwise_constant() for e in self.ufl_operands) def evaluate(self, x, mapping, component, index_values, derivatives=()): ufl_assert(len(component) == len(self.ufl_shape), "Can only evaluate scalars, expecting a component "\ "tuple of length %d, not %s." % (len(self.ufl_shape), component)) a = self.ufl_operands[component[0]] component = component[1:] if derivatives: return a.evaluate(x, mapping, component, index_values, derivatives) else: return a.evaluate(x, mapping, component, index_values) def __getitem__(self, key): origkey = key if isinstance(key, MultiIndex): key = key.indices() if not isinstance(key, tuple): key = (key,) k = key[0] if isinstance(k, (int, FixedIndex)): sub = self.ufl_operands[int(k)] return sub if len(key) == 1 else sub[key[1:]] return Expr.__getitem__(self, origkey) def __str__(self): def substring(expressions, indent): ind = " "*indent if any(isinstance(e, ListTensor) for e in expressions): substrings = [] for e in expressions: if isinstance(e, ListTensor): substrings.append(substring(e.ufl_operands, indent+2)) else: substrings.append(str(e)) s = (",\n" + ind).join(substrings) return "%s[\n%s%s\n%s]" % (ind, ind, s, ind) else: s = ", ".join(str(e) for e in expressions) return "%s[%s]" % (ind, s) return substring(self.ufl_operands, 0) def __repr__(self): return "ListTensor(%s)" % ", ".join(repr(e) for e in self.ufl_operands) @ufl_type(is_shaping=True, num_ops="varying") class ComponentTensor(Operator): """UFL operator type: Maps the free indices of a scalar valued expression to tensor axes.""" __slots__ = ("ufl_shape", "ufl_free_indices", "ufl_index_dimensions") def __new__(cls, expression, indices): # Simplify if isinstance(expression, Zero): fi, fid, sh = remove_indices(expression.ufl_free_indices, expression.ufl_index_dimensions, [ind.count() for ind in indices]) return Zero(sh, fi, fid) # Construct return Operator.__new__(cls) def __init__(self, expression, indices): ufl_assert(isinstance(expression, Expr), "Expecting ufl expression.") ufl_assert(expression.ufl_shape == (), "Expecting scalar valued expression.") ufl_assert(isinstance(indices, MultiIndex), "Expecting a MultiIndex.") ufl_assert(all(isinstance(i, Index) for i in indices), "Expecting sequence of Index objects, not %s." % repr(indices)) Operator.__init__(self, (expression, indices)) fi, fid, sh = remove_indices(expression.ufl_free_indices, expression.ufl_index_dimensions, [ind.count() for ind in indices]) self.ufl_free_indices = fi self.ufl_index_dimensions = fid self.ufl_shape = sh def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." return self.ufl_operands[0].is_cellwise_constant() def reconstruct(self, expressions, indices): # Special case for simplification as_tensor(A[ii], ii) -> A if isinstance(expressions, Indexed): A, ii = expressions.ufl_operands if indices == ii: #print "RETURNING", A, "FROM", expressions, indices, "SELF IS", self return A return Operator.reconstruct(self, expressions, indices) def indices(self): return self.ufl_operands[1] def evaluate(self, x, mapping, component, index_values): indices = self.ufl_operands[1] a = self.ufl_operands[0] ufl_assert(len(indices) == len(component), "Expecting a component matching the indices tuple.") # Map component to indices for i, c in zip(indices, component): index_values.push(i, c) a = a.evaluate(x, mapping, (), index_values) for _ in component: index_values.pop() return a def __str__(self): return "{ A | A_{%s} = %s }" % (self.ufl_operands[1], self.ufl_operands[0]) def __repr__(self): return "ComponentTensor(%r, %r)" % (self.ufl_operands[0], self.ufl_operands[1]) # --- User-level functions to wrap expressions in the correct way --- def numpy2nestedlists(arr): from numpy import ndarray if not isinstance(arr, ndarray): return arr return [numpy2nestedlists(arr[k]) for k in range(arr.shape[0])] def _as_list_tensor(expressions): if isinstance(expressions, (list, tuple)): expressions = [_as_list_tensor(e) for e in expressions] return ListTensor(*expressions) else: return as_ufl(expressions) def from_numpy_to_lists(expressions): try: import numpy if isinstance(expressions, numpy.ndarray): expressions = numpy2nestedlists(expressions) except: pass return expressions def as_tensor(expressions, indices=None): """UFL operator: Make a tensor valued expression. This works in two different ways, by using indices or lists. 1) Returns A such that A[indices] = expressions. If indices are provided, expressions must be a scalar valued expression with all the provided indices among its free indices. This operator will then map each of these indices to a tensor axis, thereby making a tensor valued expression from a scalar valued expression with free indices. 2) Returns A such that A[k,...] = expressions[k]. If no indices are provided, expressions must be a list or tuple of expressions. The expressions can also consist of recursively nested lists to build higher rank tensors. """ if indices is None: # Allow as_tensor(as_tensor(A)) and as_vector(as_vector(v)) in user code if isinstance(expressions, Expr): return expressions # Support numpy array, but avoid importing numpy if not needed if not isinstance(expressions, (list, tuple)): expressions = from_numpy_to_lists(expressions) # Sanity check if not isinstance(expressions, (list, tuple)): error("Expecting nested list or tuple.") # Recursive conversion from nested lists to nested ListTensor objects return _as_list_tensor(expressions) else: # Make sure we have a tuple of indices if isinstance(indices, list): indices = tuple(indices) elif not isinstance(indices, tuple): indices = (indices,) # Special case for as_tensor(expr, ii) with ii = () if indices == (): return expressions indices = MultiIndex(indices) # Special case for simplification as_tensor(A[ii], ii) -> A if isinstance(expressions, Indexed): A, ii = expressions.ufl_operands if indices.indices() == ii.indices(): return A # Make a tensor from given scalar expression with free indices return ComponentTensor(expressions, indices) def as_matrix(expressions, indices = None): "UFL operator: As as_tensor(), but limited to rank 2 tensors." if indices is None: # Allow as_matrix(as_matrix(A)) in user code if isinstance(expressions, Expr): ufl_assert(expressions.rank() == 2, "Expecting rank 2 tensor.") return expressions # To avoid importing numpy unneeded, it's quite slow... if not isinstance(expressions, (list, tuple)): expressions = from_numpy_to_lists(expressions) # Check for expected list structure ufl_assert(isinstance(expressions, (list, tuple)), "Expecting nested list or tuple of Exprs.") ufl_assert(isinstance(expressions[0], (list, tuple)), "Expecting nested list or tuple of Exprs.") else: ufl_assert(len(indices) == 2, "Expecting exactly two indices.") return as_tensor(expressions, indices) def as_vector(expressions, index = None): "UFL operator: As as_tensor(), but limited to rank 1 tensors." if index is None: # Allow as_vector(as_vector(v)) in user code if isinstance(expressions, Expr): ufl_assert(expressions.rank() == 1, "Expecting rank 1 tensor.") return expressions # To avoid importing numpy unneeded, it's quite slow... if not isinstance(expressions, (list, tuple)): expressions = from_numpy_to_lists(expressions) # Check for expected list structure ufl_assert(isinstance(expressions, (list, tuple)), "Expecting nested list or tuple of Exprs.") else: ufl_assert(isinstance(index, Index), "Expecting a single Index object.") index = (index,) return as_tensor(expressions, index) def as_scalar(expression): """Given a scalar or tensor valued expression A, returns either of the tuples:: (a,b) = (A, ()) (a,b) = (A[indices], indices) such that a is always a scalar valued expression.""" ii = indices(expression.rank()) if ii: expression = expression[ii] return expression, ii def as_scalars(*expressions): """Given multiple scalar or tensor valued expressions A, returns either of the tuples:: (a,b) = (A, ()) (a,b) = ([A[0][indices], ..., A[-1][indices]], indices) such that a is always a list of scalar valued expressions.""" ii = indices(expressions[0].rank()) if ii: expressions = [expression[ii] for expression in expressions] return expressions, ii def relabel(A, indexmap): "UFL operator: Relabel free indices of A with new indices, using the given mapping." ii = tuple(sorted(indexmap.keys())) jj = tuple(indexmap[i] for i in ii) ufl_assert(all(isinstance(i, Index) for i in ii), "Expecting Index objects.") ufl_assert(all(isinstance(j, Index) for j in jj), "Expecting Index objects.") return as_tensor(A, ii)[jj] # --- Experimental support for dyadic notation: def unit_list(i, n): return [(1 if i == j else 0) for j in range(n)] def unit_list2(i, j, n): return [[(1 if (i == i0 and j == j0) else 0) for j0 in range(n)] for i0 in range(n)] def unit_vector(i, d): "UFL value: A constant unit vector in direction i with dimension d." return as_vector(unit_list(i, d)) def unit_vectors(d): "UFL value: A tuple of constant unit vectors in all directions with dimension d." return tuple(unit_vector(i, d) for i in range(d)) def unit_matrix(i, j, d): "UFL value: A constant unit matrix in direction i,j with dimension d." return as_matrix(unit_list2(i, j, d)) def unit_matrices(d): "UFL value: A tuple of constant unit matrices in all directions with dimension d." return tuple(unit_matrix(i, j, d) for i in range(d) for j in range(d)) def dyad(d, *iota): "TODO: Develop this concept, can e.g. write A[i,j]*dyad(j,i) for the transpose." from ufl.constantvalue import Identity from ufl.operators import outer # a bit of circular dependency issue here I = Identity(d) i = iota[0] e = as_vector(I[i,:], i) for i in iota[1:]: e = outer(e, as_vector(I[i,:], i)) return e def unit_indexed_tensor(shape, component): from ufl.constantvalue import Identity from ufl.operators import outer # a bit of circular dependency issue here r = len(shape) if r == 0: return 0, () jj = indices(r) es = [] for i in range(r): s = shape[i] c = component[i] j = jj[i] e = Identity(s)[c, j] es.append(e) E = es[0] for e in es[1:]: E = outer(E, e) return E, jj def unwrap_list_tensor(lt): components = [] sh = lt.ufl_shape subs = lt.ufl_operands if len(sh) == 1: for s in range(sh[0]): components.append(((s,), subs[s])) else: for s, sub in enumerate(subs): for c, v in unwrap_list_tensor(sub): components.append(((s,)+c, v)) return components ufl-1.6.0/ufl/utils/000077500000000000000000000000001255567402100142415ustar00rootroot00000000000000ufl-1.6.0/ufl/utils/__init__.py000066400000000000000000000000001255567402100163400ustar00rootroot00000000000000ufl-1.6.0/ufl/utils/counted.py000066400000000000000000000033171255567402100162600ustar00rootroot00000000000000"Utilites for types with a global unique counter attached to each object." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . def counted_init(self, count=None, countedclass=None): "Initialize a counted object, see ExampleCounted below for how to use." if countedclass is None: countedclass = type(self) if count is None: count = countedclass._globalcount self._count = count if self._count >= countedclass._globalcount: countedclass._globalcount = self._count + 1 class ExampleCounted(object): """An example class for classes of objects identified by a global counter. Mimic this class to create globally counted objects within a single type. """ # Store the count for each object __slots__ = ("_count",) # Store a global counter with the class _globalcount = 0 # Call counted_init with an optional constructor argument and the class def __init__(self, count=None): counted_init(self, count, ExampleCounted) # Make the count accessible def count(self): return self._count ufl-1.6.0/ufl/utils/derivativetuples.py000066400000000000000000000062441255567402100202200ustar00rootroot00000000000000"This module contains a collection of utilities for representing partial derivatives as integer tuples." # Copyright (C) 2013-2014 Martin Sandve Alnes and Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . import itertools def derivative_counts_to_listing(derivative_counts): """Convert a derivative count tuple to a derivative listing tuple. The derivative d^3 / dy^2 dz is represented in counting form as (0, 2, 1) meaning (dx^0, dy^2, dz^1) and in listing form as (1, 1, 2) meaning (dy, dy, dz). """ derivatives = [] # = 1 for i, d in enumerate(derivative_counts): derivatives.extend((i,)*d) # *= d/dx_i^d return tuple(derivatives) def derivative_listing_to_counts(derivatives, gdim): """Convert a derivative listing tuple to a derivative count tuple. The derivative d^3 / dy^2 dz is represented in counting form as (0, 2, 1) meaning (dx^0, dy^2, dz^1) and in listing form as (1, 1, 2) meaning (dy, dy, dz). """ derivative_counts = [0]*gdim for d in derivatives: derivative_counts[d] += 1 return tuple(derivative_counts) def compute_derivative_tuples(n, gdim): """Compute the list of all derivative tuples for derivatives of given total order n and given geometric dimension gdim. This function returns two lists. The first is a list of tuples, where each tuple of length n specifies the coordinate directions of the n derivatives. The second is a corresponding list of tuples, where each tuple of length gdim specifies the number of derivatives in each direction. Both lists have length gdim^n and are ordered as expected by the UFC function tabulate_basis_derivatives. Example: If n = 2 and gdim = 3, then the nice tuples are (0, 0) <--> (2, 0, 0) <--> d^2/dxdx (0, 1) <--> (1, 1, 0) <--> d^2/dxdy (0, 2) <--> (1, 0, 1) <--> d^2/dxdz (1, 0) <--> (1, 1, 0) <--> d^2/dydx (1, 1) <--> (0, 2, 0) <--> d^2/dydy (1, 2) <--> (0, 1, 1) <--> d^2/dydz (2, 0) <--> (1, 0, 1) <--> d^2/dzdx (2, 1) <--> (0, 1, 1) <--> d^2/dzdy (2, 2) <--> (0, 0, 2) <--> d^2/dzdz """ # Create list of derivatives (note that we have d^n derivatives) deriv_tuples = [d for d in itertools.product(*(n*[range(0, gdim)]))] # Translate from list of derivative tuples to list of tuples # expressing the number of derivatives in each dimension... _deriv_tuples = [tuple(len([_d for _d in d if _d == i]) for i in range(gdim)) for d in deriv_tuples] return deriv_tuples, _deriv_tuples ufl-1.6.0/ufl/utils/dicts.py000066400000000000000000000042121255567402100157200ustar00rootroot00000000000000"Various dict manipulation utilities." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from six import iterkeys, iteritems from six import advance_iterator as next def split_dict(d, criteria): "Split a dict d into two dicts based on a criteria on the keys." a = {} b = {} for (k, v) in iteritems(d): if criteria(k): a[k] = v else: b[k] = v return a, b def slice_dict(dictionary, keys, default=None): return tuple(dictionary.get(k, default) for k in keys) def some_key(a_dict): "Return an arbitrary key from a dictionary." return next(iterkeys(a_dict)) def mergedicts(dicts): d = dict(dicts[0]) for d2 in dicts[1:]: d.update(d2) return d def mergedicts2(d1, d2): d = dict(d1) d.update(d2) return d def subdict(superdict, keys): return dict((k, superdict[k]) for k in keys) def dict_sum(items): "Construct a dict, in between dict(items) and sum(items), by accumulating items for each key." d = {} for k, v in items: if k not in d: d[k] = v else: d[k] += v return d class EmptyDictType(dict): def __setitem__(self, key, value): from ufl.log import error error("This is a frozen unique empty dictionary object, inserting values is an error.") def update(self, *args, **kwargs): from ufl.log import error error("This is a frozen unique empty dictionary object, inserting values is an error.") EmptyDict = EmptyDictType() ufl-1.6.0/ufl/utils/formatting.py000066400000000000000000000053441255567402100167730ustar00rootroot00000000000000"Various string formatting utilities." # Copyright (C) 2008-2014 Martin Sandve Alnes and Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . def camel2underscore(name): "Convert a CamelCaps string to underscore_syntax." letters = [] lastlower = False for l in name: thislower = l.islower() if not thislower: # Don't insert _ between multiple upper case letters if lastlower: letters.append("_") l = l.lower() lastlower = thislower letters.append(l) return "".join(letters) def lstr(l): "Pretty-print list or tuple, invoking str() on items instead of repr() like str() does." if isinstance(l, list): return "[" + ", ".join(lstr(item) for item in l) + "]" elif isinstance(l, tuple): return "(" + ", ".join(lstr(item) for item in l) + ")" return str(l) def dstr(d, colsize=80): "Pretty-print dictionary of key-value pairs." sorted_keys = sorted(d.keys()) return tstr([(key, d[key]) for key in sorted_keys], colsize) def tstr(t, colsize=80): "Pretty-print list of tuples of key-value pairs." if not t: return "" # Compute maximum key length keylen = max(len(str(item[0])) for item in t) # Key-length cannot be larger than colsize if keylen > colsize: return str(t) # Pretty-print table s = "" for (key, value) in t: key = str(key) if isinstance(value, str): value = "'%s'" % value else: value = str(value) s += key + ":" + " "*(keylen - len(key) + 1) space = "" while len(value) > 0: end = min(len(value), colsize - keylen) s += space + value[:end] + "\n" value = value[end:] space = " "*(keylen + 2) return s def sstr(s): "Pretty-print set." return ", ".join(str(x) for x in s) def istr(o): "Format object as string, inserting ? for None." if o is None: return "?" else: return str(o) def estr(elements): "Format list of elements for printing." return ", ".join(e.shortstr() for e in elements) ufl-1.6.0/ufl/utils/indexflattening.py000066400000000000000000000027711255567402100200050ustar00rootroot00000000000000"This module contains a collection of utilities for mapping between multiindices and a flattened index space." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from six.moves import zip from six.moves import xrange as range def shape_to_strides(sh): "Return a tuple of strides given a shape tuple." n = len(sh) if not n: return () strides = [None]*n strides[n-1] = 1 for i in range(n-1, 0, -1): strides[i-1] = strides[i]*sh[i] return tuple(strides) def flatten_multiindex(ii, strides): "Return the flat index corresponding to the given multiindex." i = 0 for c, s in zip(ii, strides): i += c * s return i def unflatten_index(i, strides): "Return the multiindex corresponding to the given flat index." ii = [] for s in strides: ii.append(i // s) i %= s return tuple(ii) ufl-1.6.0/ufl/utils/sequences.py000066400000000000000000000035011255567402100166050ustar00rootroot00000000000000"Various sequence manipulation utilities." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from six.moves import zip import operator import time def product(sequence): "Return the product of all elements in a sequence." p = 1 for f in sequence: p *= f return p def unzip(seq): "Inverse operation of zip: unzip(zip(a, b)) == (a, b)" return [s[0] for s in seq], [s[1] for s in seq] def xor(a, b): return bool(a) if b else not a def or_tuples(seqa, seqb): "Return 'or' of all pairs in two sequences of same length." return tuple(a or b for (a, b) in zip(seqa, seqb)) def and_tuples(seqa, seqb): "Return 'and' of all pairs in two sequences of same length." return tuple(a and b for (a, b) in zip(seqa, seqb)) def iter_tree(tree): """Iterate over all nodes in a tree represented by lists of lists of leaves.""" if isinstance(tree, list): for node in tree: for i in iter_tree(node): yield i else: yield tree def recursive_chain(lists): for l in lists: if isinstance(l, str): yield l else: for s in recursive_chain(l): yield s ufl-1.6.0/ufl/utils/sorting.py000066400000000000000000000064511255567402100163060ustar00rootroot00000000000000"Utilites for sorting." # Copyright (C) 2008-2014 Johan Hake # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from ufl.log import warning from six import itervalues, iteritems def topological_sorting(nodes, edges): """ Return a topologically sorted list of the nodes Implemented algorithm from Wikipedia :P No error for cyclic edges... """ L = [] S = nodes[:] for node in nodes: for es in itervalues(edges): if node in es and node in S: S.remove(node) continue while S: node = S.pop(0) L.append(node) node_edges = edges[node] while node_edges: m = node_edges.pop(0) found = False for es in itervalues(edges): found = m in es if found: break if not found: S.insert(0, m) return L def sorted_by_count(seq): "Sort a sequence by the item.count()." return sorted(seq, key=lambda x: x.count()) def sorted_by_key(mapping): "Sort dict items by key, allowing different key types." # Python3 doesn't allow comparing builtins of different type, therefore the typename trick here return sorted(iteritems(mapping), key=lambda x: (type(x[0]).__name__, x[0])) def canonicalize_metadata(metadata): """Assuming metadata to be a dict with string keys and builtin python types as values. Transform dict to a tuple of (key, value) item tuples ordered by key, with dict, list and tuple values converted the same way recursively. Lists and tuples are converted to tuples. Other values are converted using str(). This is such that the end result can be hashed and sorted using regular <, because python 3 doesn't allow e.g. (3 < "auto") which occurs regularly in metadata. """ if metadata is None: return () if isinstance(metadata, dict): keys = sorted(metadata.keys()) assert all(isinstance(key, str) for key in keys) values = [metadata[key] for key in keys] elif isinstance(metadata, (tuple, list)): values = metadata newvalues = [] for value in values: if isinstance(value, (dict, list, tuple)): value = canonicalize_metadata(value) elif isinstance(value, (int, float, str)): value = str(value) else: warning("Applying str() to a metadata value of type {0}, don't know if this is safe.".format(type(value).__name__)) value = str(value) newvalues.append(value) if isinstance(metadata, dict): return tuple(zip(keys, newvalues)) else: return tuple(newvalues) ufl-1.6.0/ufl/utils/stacks.py000066400000000000000000000031761255567402100161120ustar00rootroot00000000000000"Various utility data structures." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . class Stack(list): "A stack datastructure." def __init__(self, *args): list.__init__(self, *args) def push(self, v): list.append(self, v) def peek(self): return self[-1] class StackDict(dict): "A dict that can be changed incrementally with 'd.push(k,v)' and have changes rolled back with 'k,v = d.pop()'." def __init__(self, *args, **kwargs): dict.__init__(self, *args, **kwargs) self._l = [] def push(self, k, v): # Store previous state for this key self._l.append((k, self.get(k, None))) if v is None: if k in self: del self[k] else: self[k] = v def pop(self): # Restore previous state for this key k, v = self._l.pop() if v is None: if k in self: del self[k] else: self[k] = v return k, v ufl-1.6.0/ufl/utils/system.py000066400000000000000000000036221255567402100161420ustar00rootroot00000000000000"Various utilities accessing system io." # Copyright (C) 2008-2014 Martin Sandve Alnes and Johannes Ring # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . # Taken from http://ivory.idyll.org/blog/mar-07/replacing-commands-with-subprocess from subprocess import Popen, PIPE, STDOUT from functools import reduce def get_status_output(cmd, input=None, cwd=None, env=None): pipe = Popen(cmd, shell=True, cwd=cwd, env=env, stdout=PIPE, stderr=STDOUT) (output, errout) = pipe.communicate(input=input) assert not errout status = pipe.returncode return (status, output) def write_file(filename, text): with open(filename, "w") as f: f.write(text) def pdflatex(latexfilename, pdffilename, flags=""): # TODO: Options for this. "Execute pdflatex to compile a latex file into pdf." flags += "-file-line-error-style -interaction=nonstopmode" latexcmd = "pdflatex" cmd = "%s %s %s %s" % (latexcmd, flags, latexfilename, pdffilename) s, o = get_status_output(cmd) return s, o def openpdf(pdffilename): "Open PDF file in external pdf viewer." reader_cmd = "evince %s &" # TODO: Add option for which reader to use. Is there a portable way to do this? Like "get default pdf reader from os"? cmd = reader_cmd % pdffilename s, o = get_status_output(cmd) return s, o ufl-1.6.0/ufl/utils/timer.py000066400000000000000000000026711255567402100157410ustar00rootroot00000000000000"Timer utilites." # Copyright (C) 2008-2014 Martin Sandve Alnes and Anders Logg # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from six.moves import xrange as range import time class Timer(object): def __init__(self, name): self.name = name self.times = [] self('begin %s' % self.name) def __call__(self, msg): self.times.append((time.time(), msg)) def end(self): self('end %s' % self.name) def __str__(self): line = "-"*60 s = [line, "Timing of %s" % self.name] for i in range(len(self.times)-1): t = self.times[i+1][0] - self.times[i][0] msg = self.times[i][1] s.append("%9.2e s %s" % (t, msg)) s.append('Total time: %9.2e s' % (self.times[-1][0] - self.times[0][0])) s.append(line) return '\n'.join(s) ufl-1.6.0/ufl/utils/ufltypedicts.py000066400000000000000000000033151255567402100173340ustar00rootroot00000000000000"Various utility data structures." # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . class UFLTypeDict(dict): def __init__(self): dict.__init__(self) def __getitem__(self, key): return dict.__getitem__(self, key._ufl_class_) def __setitem__(self, key, value): return dict.__setitem__(self, key._ufl_class_, value) def __delitem__(self, key): return dict.__delitem__(self, key._ufl_class_) def __contains__(self, key): return dict.__contains__(self, key._ufl_class_) class UFLTypeDefaultDict(dict): def __init__(self, default): dict.__init__(self) def make_default(): return default self.setdefault(make_default) def __getitem__(self, key): return dict.__getitem__(self, key._ufl_class_) def __setitem__(self, key, value): return dict.__setitem__(self, key._ufl_class_, value) def __delitem__(self, key): return dict.__delitem__(self, key._ufl_class_) def __contains__(self, key): return dict.__contains__(self, key._ufl_class_) ufl-1.6.0/ufl/variable.py000066400000000000000000000075311255567402100152460ustar00rootroot00000000000000"""Defines the Variable and Label classes, used to label expressions as variables for differentiation.""" # Copyright (C) 2008-2014 Martin Sandve Alnes # # This file is part of UFL. # # UFL is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # UFL 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with UFL. If not, see . from ufl.common import counted_init from ufl.log import error from ufl.assertions import ufl_assert from ufl.core.expr import Expr from ufl.core.ufl_type import ufl_type from ufl.core.terminal import Terminal from ufl.core.operator import Operator from ufl.constantvalue import as_ufl @ufl_type() class Label(Terminal): __slots__ = ("_count",) _globalcount = 0 def __init__(self, count=None): Terminal.__init__(self) counted_init(self, count, Label) def count(self): return self._count def __str__(self): return "Label(%d)" % self._count def __repr__(self): return "Label(%d)" % self._count @property def ufl_shape(self): error("Label has no shape (it is not a tensor expression).") @property def ufl_free_indices(self): error("Label has no free indices (it is not a tensor expression).") @property def ufl_index_dimensions(self): error("Label has no free indices (it is not a tensor expression).") def is_cellwise_constant(self): error("Asking if a Label is cellwise constant makes no sense (it is not a tensor expression).") #return True # Could also just return True, after all it doesn't change with the cell def domains(self): "Return tuple of domains related to this terminal object." return () @ufl_type(is_shaping=True, is_index_free=True, num_ops=1, inherit_shape_from_operand=0) class Variable(Operator): """A Variable is a representative for another expression. It will be used by the end-user mainly for defining a quantity to differentiate w.r.t. using diff. Example:: e = <...> e = variable(e) f = exp(e**2) df = diff(f, e) """ __slots__ = () def __init__(self, expression, label=None): # Conversion expression = as_ufl(expression) if label is None: label = Label() # Checks ufl_assert(isinstance(expression, Expr), "Expecting Expr.") ufl_assert(isinstance(label, Label), "Expecting a Label.") ufl_assert(not expression.ufl_free_indices, "Variable cannot wrap an expression with free indices.") Operator.__init__(self, (expression, label)) def domains(self): return self.ufl_operands[0].domains() def is_cellwise_constant(self): return self.ufl_operands[0].is_cellwise_constant() def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) return a def expression(self): return self.ufl_operands[0] def label(self): return self.ufl_operands[1] def __eq__(self, other): return (isinstance(other, Variable) and self.ufl_operands[1] == other.ufl_operands[1] and self.ufl_operands[0] == other.ufl_operands[0]) def __str__(self): return "var%d(%s)" % (self.ufl_operands[1].count(), self.ufl_operands[0]) def __repr__(self): return "Variable(%r, %r)" % (self.ufl_operands[0], self.ufl_operands[1])