ufl-2017.2.0/0000755000231000000010000000000013211220450011631 5ustar chrisdaemonufl-2017.2.0/.mailmap0000644000231000000010000001575313211220450013265 0ustar chrisdaemonAnders Logg Anders Logg Anders Logg Anders Logg Anders Logg logg Anders Logg fenics Anders Logg hg Anders Logg root Anders Logg Anders Logg Anders Logg Anders Logg Anders Logg Anders Logg anders Anders Logg logg Anders Logg logg Anders Logg logg Anders Logg logg Andy R. Terrel Andy R. Terrel Andy R. Terrel aterrel Andy R. Terrel Benjamin Kehlet Benjamin Kehlet Benjamin Kehlet Benjamin Kehlet Benjamin Kehlet Chris Richardson Chris Richardson chris Chris Richardson Chris Richardson Chris Richardson root Corrado Maurini Corrado Maurini Dag Lindbo Dag Lindbo dag Dag Lindbo dag David Ham David Ham david.ham@imperial.ac.uk <> Evan Lezar Evan Lezar Evan Lezar elezar Evan Lezar elezar Fredrik Valdmanis Fredrik Valdmanis Fredrik Valdmanis Garth N. Wells Garth N. Wells root Garth N. Wells garth Garth N. Wells Garth N. Wells Garth N. Wells Garth N. Wells Garth N. Wells Garth N. Wells Garth N. Wells Garth N. Wells Garth N. Wells Garth N. Wells Garth N. Wells gnw20@cam.ac.uk <> gideonsimpson Gustav Magnus Vikström Gustav Magnus Vikström Gustav Magnus Vikström Gustav Magnus Vikström Harish Narayanan Harish Narayanan Harish Narayanan Johan Hoffman Johan Hoffman hoffman Johan Hoffman Johan Hoffman Ilmar Wilbers Ilmar Wilbers Ilmar Wilbers Jack S. Hale Jack S. Hale Johan Hake Johan Hake Johan Hake Johan Jansson Johan Jansson johan Johan Jansson johan Johan Jansson johanjan Johan Jansson johanjan Johan Jansson Johan Jansson Johannes Ring Johannes Ring Kent-Andre Mardal Kent-Andre Mardal Kristian B. Ølgaard Kristian B. Ølgaard Kristian B. Ølgaard Magnus Vikstrøm Marco Morandini Marco Morandini Marie E. Rognes Marie E. Rognes Marie E. Rognes Marie E. Rognes Marie E. Rognes (meg@simula.no) Marie E. Rognes meg@simula.no <> Martin Sandve Alnæs Martin Sandve Alnæs Martin Sandve Alnæs Michele Zaffalon Mikael Mortensen Mikael Mortensen Nate Sime Nate Sime Nuno Lopes Nuno Lopes N.Lopes Patrick Farrell Patrick Farrell Patrick Farrell Quang Ha Kristoffer Selim Kristoffer Selim Simon Funke Simon Funke Simon Funke Solveig Bruvoll Solveig Masvie Steven Vandekerckhove Steven Vandekerckhove stockli stockli Tianyi Li Steffen Müthing Steffen Müthing Steffen Müthing steffen.muething@ipvs.uni-stuttgart.de <> Miklós Homolya Åsmund Ødegård Åsmund Ødegård Ola Skavhaug Andre Massing Andrew McRae ufl-2017.2.0/COPYING0000644000231000000010000010451313211220450012670 0ustar chrisdaemon 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-2017.2.0/bitbucket-pipelines.yml0000644000231000000010000000044413211220450016320 0ustar chrisdaemonimage: quay.io/fenicsproject/pipelines pipelines: default: - step: script: - pip2 install . - pip3 install . - python2 -m flake8 ufl/ - python3 -m flake8 ufl/ - python2 -m pytest -v test/ - python3 -m pytest -v test/ ufl-2017.2.0/MANIFEST.in0000644000231000000010000000026413211220450013371 0ustar chrisdaemoninclude AUTHORS include COPYING include COPYING.LESSER include ChangeLog recursive-include demo * recursive-include doc * recursive-include test * global-exclude __pycache__ *.pyc ufl-2017.2.0/COPYING.LESSER0000644000231000000010000001672713211220450013675 0ustar chrisdaemon 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-2017.2.0/scripts/0000755000231000000010000000000013211220450013320 5ustar chrisdaemonufl-2017.2.0/scripts/ufl-version0000755000231000000010000000160513211220450015521 0ustar chrisdaemon#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2011-2016 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 is a simple script that just prints the UFL version number.""" from __future__ import print_function from ufl import __version__ print(__version__) ufl-2017.2.0/scripts/ufl-convert0000755000231000000010000001572313211220450015522 0ustar chrisdaemon#!/usr/bin/env python from __future__ import print_function import sys import subprocess import io import os import optparse from pprint import pprint from ufl.algorithms import tree_format, compute_form_data from ufl.formatting.ufl2dot import ufl2dot from ufl.formatting.ufl2latex import forms2latexdocument from ufl.formatting.ufl2unicode import form2unicode from ufl.algorithms.formfiles import load_ufl_file # --- Utilities def get_status_output(cmd, input=None, cwd=None, env=None): """Replacement for commands.getstatusoutput which does not work on Windows (or Python 3).""" if isinstance(cmd, string_types): cmd = cmd.strip().split() pipe = subprocess.Popen(cmd, shell=False, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) (output, errout) = pipe.communicate(input=input) assert not errout status = pipe.returncode if isinstance(output, bytes): output = output.decode('utf-8') 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." with io.open(filename, "w", encoding="utf-8") as f: f.write(text) 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("labeling", "l", "str", "repr", "Set to 'repr' or 'compact' for different naming of graph nodes."), opt("compile", "c", "int", 0, "'Compile' forms: apply expression transformations like in a quadrature based form compilation. Only used for latex formatting."), # Output formats: opt("format", "f", "str", "", "Rendering format (str, repr, tree, dot, latex, unicode)."), opt("filetype", "t", "str", "", "Output file type (txt, py, dot, tex, ps, pdf, png)."), ] 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) # 1) Load forms ufl_data = load_ufl_file(uflfilename) forms = ufl_data.forms #expressions = ufl_data.expressions # TODO: Allow rendering expressions without form stuff! # Preprocess forms form_datas = [compute_form_data(f) for f in forms] # 3) Render result format = options.format # Make format string conform if format == "latex": format = "tex" if format == "tex": rendered = forms2latexdocument(forms, uflfilename, compile=options.compile) elif format == "unicode": data = [] for form, form_data in zip(forms, form_datas): tmp = form2unicode(form, form_data) data.append(tmp) rendered = "\n\n".join(data) elif format in ("str", "repr", "tree"): data = [] for i, fd in enumerate(form_datas): f = fd.original_form name = ufl_data.object_names.get(f, "form") if format == "str": s = str(f) elif format == "repr": s = repr(f) elif format == "tree": s = tree_format(f) tmp = "Form %s:\n%s\n" % (name, s) data.append(tmp) rendered = "\n\n".join(data) elif format == "dot": data = [] nodeoffset = 0 for i, fd in enumerate(form_datas): f = fd.original_form name = ufl_data.object_names.get(f, "form") begin = (i == 0) end = (i == len(forms) - 1) dot, nodeoffset = ufl2dot(f, name, nodeoffset, begin, end, options.labeling, ufl_data.object_names) tmp = "/* Form %s: */\n%s\n" % (name, dot) data.append(tmp) rendered = "\n\n".join(data) 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" elif format == "unicode": filetype = "txt" # 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) 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)) 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)) 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-2017.2.0/scripts/ufl2py0000755000231000000010000000275413211220450014477 0ustar chrisdaemon#!/usr/bin/env python from __future__ import with_statement import os, sys, optparse from ufl.algorithms import validate_form, ufl2latex, tree_format from ufl.algorithms.formfiles import read_ufl_file # 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 # -*- coding: utf-8 -*- 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-2017.2.0/scripts/ufl-analyse0000755000231000000010000000604013211220450015466 0ustar chrisdaemon#!/usr/bin/env python __authors__ = "Martin Sandve Alnes" __date__ = "2008-05-09" # Modified by Anders Logg, 2009. # Last changed: 2015-01-05 import io 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 = io.open(outputfilename, "w", encoding="utf-8") 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-2017.2.0/doc/0000755000231000000010000000000013211220450012376 5ustar chrisdaemonufl-2017.2.0/doc/sphinx/0000755000231000000010000000000013211220450013707 5ustar chrisdaemonufl-2017.2.0/doc/sphinx/source/0000755000231000000010000000000013211220450015207 5ustar chrisdaemonufl-2017.2.0/doc/sphinx/source/conf.py0000644000231000000010000002342113211220450016510 0ustar chrisdaemon# -*- coding: utf-8 -*- # # Unified Form Language (UFL) documentation build configuration file, created by # sphinx-quickstart on Tue Nov 3 11:05:14 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # 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 import os import shlex import pkg_resources import datetime # 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('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # 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.coverage', 'sphinx.ext.mathjax', 'sphinx.ext.viewcode', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] 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 = u'Unified Form Language (UFL)' this_year = datetime.date.today().year copyright = u'%s, FEniCS Project' % this_year author = u'FEniCS Project' version = pkg_resources.get_distribution("fenics-ufl").version release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. 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 = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- 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 = 'alabaster' # 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 = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # 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'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # 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 # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' #html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value #html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. #html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'UnifiedFormLanguageUFLdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', # Latex figure (float) alignment #'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'UnifiedFormLanguageUFL.tex', u'Unified Form Language (UFL) Documentation', u'FEniCS Project', 'manual'), ] # 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 # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'unifiedformlanguageufl', u'Unified Form Language (UFL) Documentation', [author], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'UnifiedFormLanguageUFL', u'Unified Form Language (UFL) Documentation', author, 'UnifiedFormLanguageUFL', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False def run_apidoc(_): modules = ['ufl'] # Get location of Sphinx files sphinx_source_dir = os.path.abspath(os.path.dirname(__file__)) repo_dir = os.path.abspath(os.path.join(sphinx_source_dir, os.path.pardir, os.path.pardir, os.path.pardir)) apidoc_dir = os.path.join(sphinx_source_dir, "api-doc") from sphinx.apidoc import main for module in modules: # Generate .rst files ready for autodoc module_dir = os.path.join(repo_dir, module) main(["-f", "-d", "1", "-o", apidoc_dir, module_dir]) def setup(app): app.connect('builder-inited', run_apidoc) ufl-2017.2.0/doc/sphinx/source/releases/0000755000231000000010000000000013211220450017012 5ustar chrisdaemonufl-2017.2.0/doc/sphinx/source/releases/v2016.2.0.rst0000644000231000000010000000600713211220450020623 0ustar chrisdaemon=========================== Changes in version 2016.2.0 =========================== UFL 2016.2.0 was released on 2016-11-30. Summary of changes ================== - Deprecate ``.cell()``, ``.domain()``, ``.element()`` in favour of ``.ufl_cell()``, ``.ufl_domain()``, ``.ufl_element()``, in multiple classes, to allow closer integration with DOLFIN - Remove deprecated properties ``cell.{d,x,n,volume,circumradius,facet_area}`` - Remove ancient ``form2ufl`` script - Large reworking of symbolic geometry pipeline - Implement symbolic Piola mappings - ``OuterProductCell`` and ``OuterProductElement`` are merged into ``TensorProductCell`` and ``TensorProductElement`` respectively - Better degree estimation for quadrilaterals - Expansion rules for Q, DQ, RTCE, RTCF, NCE and NCF on tensor product cells - Add discontinuous Taylor elements - Add support for the mapping ``double covariant Piola`` in ``uflacs`` - Add support for the mapping ``double contravariant Piola`` in ``uflacs`` - Support for tensor-valued subelements in ``uflacs`` fixed - Replacing ``Discontinuous Lagrange Trace`` with ``HDiv Trace`` and removing ``TraceElement`` - Assigning ``Discontinuous Lagrange Trace`` and ``DGT`` as aliases for ``HDiv Trace`` Detailed changes ================ - Add call operator syntax to Form to replace arguments and coefficients. This makes it easier to e.g. express the norm defined by a bilinear form as a functional. Example usage:: # Equivalent to replace(a, {u: f, v: f}) M = a(f, f) # Equivalent to replace(a, {f:1}) c = a(coefficients={f:1}) - Add call operator syntax to Form to replace arguments and coefficients:: a(f, g) == replace(a, {u: f, v: g}) a(coefficients={f:1}) == replace(a, {f:1}) - Add @ operator to Form: form @ f == action(form, f) (python 3.5+ only) - Reduce noise in Mesh str such that print(form) gets more short and readable - Fix repeated split(function) for arbitrary nested elements - EnrichedElement: Remove +/* warning In the distant past, A + B => MixedElement([A, B]). The change that A + B => EnrichedElement([A, B]) was made in d622c74 (22 March 2010). A warning was introduced in fcbc5ff (26 March 2010) that the meaning of "+" had changed, and that users wanting a MixedElement should use "*" instead. People have, presumably, been seeing this warning for 6 1/2 years by now, so it's probably safe to remove. - Rework TensorProductElement implementation, replaces OuterProductElement - Rework TensorProductCell implementation, replaces OuterProductCell - Remove OuterProductVectorElement and OuterProductTensorElement - Add FacetElement and InteriorElement - Add Hellan-Herrmann-Johnson element - Add support for double covariant and contravariant mappings in mixed elements - Support discontinuous Taylor elements on all simplices - Some more performance improvements - Minor bugfixes - Improve Python 3 support - More permissive in integer types accepted some places - Make ufl pass almost all flake8 tests - Add bitbucket pipelines testing - Improve documentation ufl-2017.2.0/doc/sphinx/source/releases/next.rst0000644000231000000010000000100213211220450020513 0ustar chrisdaemon=========================== Changes in the next release =========================== Summary of changes ================== .. note:: Developers should use this page to track and list changes during development. At the time of release, this page should be published (and renamed) to list the most important changes in the new release. Detailed changes ================ .. note:: At the time of release, make a verbatim copy of the ChangeLog here (and remove this note). ufl-2017.2.0/doc/sphinx/source/releases/v2017.1.0.rst0000644000231000000010000000134313211220450020621 0ustar chrisdaemon=========================== Changes in version 2017.1.0 =========================== UFL 2017.1.0 was released on 2017-05-09. Summary of changes ================== - Add the ``DirectionalSobolevSpace`` subclass of ``SobolevSpace``. This allows one to use spaces where elements have varying continuity in different spatial directions. - Add ``sobolev_space`` methods for ``HDiv`` and ``HCurl`` finite elements. - Add ``sobolev_space`` methods for ``TensorProductElement`` and ``EnrichedElement``. The smallest shared Sobolev space will be returned for enriched elements. For the tensor product elements, a ``DirectionalSobolevSpace`` is returned depending on the order of the spaces associated with the component elements. ufl-2017.2.0/doc/sphinx/source/releases/v2017.1.0.post1.rst0000644000231000000010000000035013211220450021663 0ustar chrisdaemon================================= Changes in version 2017.1.0.post1 ================================= UFL 2017.1.0.post1 was released on 2017-09-12. Summary of changes ================== - Change PyPI package name to fenics-ufl. ufl-2017.2.0/doc/sphinx/source/releases/v1.6.0.rst0000644000231000000010000000255013211220450020376 0ustar chrisdaemon======================== Changes in version 1.6.0 ======================== UFL 1.6.0 was released on 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 ufl-2017.2.0/doc/sphinx/source/releases/v2017.2.0.rst0000644000231000000010000000062413211220450020623 0ustar chrisdaemon=========================== Changes in version 2017.2.0 =========================== UFL 2017.2.0 was released on 2017-11-30. Summary of changes ================== - Add ``CellDiameter`` expression giving diameter of a cell, i.e., maximal distance between any two points of the cell. Implemented for all simplices and quads/hexes. - Make ``(Min|Max)(Cell|Facet)EdgeLength`` working for quads/hexes ufl-2017.2.0/doc/sphinx/source/releases/v2016.1.0.rst0000644000231000000010000000225713211220450020625 0ustar chrisdaemon=========================== Changes in version 2016.1.0 =========================== UFL 2016.1.0 was released on 2016-06-23. - Add operator A^(i,j) := as_tensor(A, (i,j)) - Updates to old manual for publishing on fenics-ufl.readthedocs.org - Bugfix for ufl files with utf-8 encoding - Bugfix in conditional derivatives to avoid inf/nan values in generated code. This bugfix may break ffc if uflacs is not used, to get around that the old workaround in ufl can be enabled by setting ufl.algorithms.apply_derivatives.CONDITIONAL_WORKAROUND = True at the top of your program. - Allow sum([expressions]) where expressions are nonscalar by defining expr+0==expr - Allow form=0; form -= other; - Deprecate .cell(), .domain(), .element() in favour of .ufl_cell(), .ufl_domain(), .ufl_element(), in multiple classes, to allow closer integration with dolfin. - Remove deprecated properties cell.{d,x,n,volume,circumradius,facet_area}. - Remove ancient form2ufl script - Add new class Mesh to replace Domain - Add new class FunctionSpace(mesh, element) - Make FiniteElement classes take Cell, not Domain. - Large reworking of symbolic geometry pipeline - Implement symbolic Piola mappings ufl-2017.2.0/doc/sphinx/source/manual/0000755000231000000010000000000013211220450016464 5ustar chrisdaemonufl-2017.2.0/doc/sphinx/source/manual/command_line_utils.rst0000644000231000000010000000327213211220450023067 0ustar chrisdaemon********************* Commandline utilities ********************* See installed version: ``ufl-analyse`` ====================================== Run :: # ufl-version to see the currently installed version of UFL printed to the terminal. Validation and debugging: ``ufl-analyse`` ========================================= The command ``ufl-analyse`` loads all forms found in a ``.ufl`` file, tries to discover any errors in them, and prints various kinds of information about each form. Basic usage is :: # ufl-analyse myform.ufl For more information, type :: # ufl-analyse --help Note: This script is not well maintained, you will likely get more useful information from your form compiler. Formatting and visualization: ``ufl-convert`` ============================================= The command ``ufl-convert`` loads all forms found in a ``.ufl`` file, compiles them into a different form or extracts some information from them, and writes the result in a suitable file format. To try this tool, go to the ``demo/`` directory of the UFL source tree. Some of the features to try are basic printing of ``str`` and ``repr`` string representations of each form:: # ufl-convert --format=str stiffness.ufl # ufl-convert --format=repr stiffness.ufl compilation of forms to mathematical notation in LaTeX:: # ufl-convert --filetype=pdf --format=tex --show=1 stiffness.ufl LaTeX output of forms after processing with UFL compiler utilities:: # ufl-convert -tpdf -ftex -s1 --compile=1 stiffness.ufl and visualization of expression trees using graphviz via compilation of forms to the dot format:: # ufl-convert -tpdf -fdot -s1 stiffness.ufl Type ``ufl-convert --help`` for more details. ufl-2017.2.0/doc/sphinx/source/manual/internal_representation.rst0000644000231000000010000001374313211220450024164 0ustar chrisdaemon******************************* Internal representation details ******************************* .. FIXME: This chapter is very much outdated. Most of the concepts are still the same but a lot of the details are different. This chapter explains how UFL forms and expressions are represented in detail. Most operations are mirrored by a representation class, e.g., ``Sum`` and ``Product``, which are subclasses of ``Expr``. You can import all of them from the submodule ``ufl.classes`` by :: from ufl.classes import * Structure of a form =================== Each ``Form`` owns multiple ``Integral`` instances, each associated with a different ``Measure``. An ``Integral`` owns a ``Measure`` and an ``Expr``, which represents the integrand expression. The ``Expr`` is the base class of all expressions. It has two direct subclasses ``Terminal`` and ``Operator``. Subclasses of ``Terminal`` represent atomic quantities which terminate the expression tree, e.g. they have no subexpressions. Subclasses of ``Operator`` represent operations on one or more other expressions, which may usually be ``Expr`` subclasses of arbitrary type. Different ``Operator``\ s may have restrictions on some properties of their arguments. All the types mentioned here are conceptually immutable, i.e. they should never be modified over the course of their entire lifetime. When a modified expression, measure, integral, or form is needed, a new instance must be created, possibly sharing some data with the old one. Since the shared data is also immutable, sharing can cause no problems. General properties of expressions ================================= Any UFL expression has certain properties, defined by functions that every ``Expr`` subclass must implement. In the following, ``u`` represents an arbitrary UFL expression, i.e. an instance of an arbitrary ``Expr`` subclass. ``operands`` ------------ ``u.operands()`` returns a tuple with all the operands of u, which should all be ``Expr`` instances. ``reconstruct`` --------------- ``u.reconstruct(operands)`` returns a new ``Expr`` instance representing the same operation as ``u`` but with other operands. Terminal objects may simply return ``self`` since all ``Expr`` instance are immutable. An important invariant is that ``u.reconstruct(u.operands()) == u``. ``cell`` -------- ``u.cell()`` returns the first ``Cell`` instance found in ``u``. It is currently assumed in UFL that no two different cells are used in a single form. Not all expression define a cell, in which case this returns ``None`` and ``u`` is spatially constant. Note that this property is used in some algorithms. ``shape`` --------- ``u.shape()`` returns a tuple of integers, which is the tensor shape of ``u``. ``free_indices`` ----------------- ``u.free_indices()`` returns a tuple of ``Index`` objects, which are the unassigned, free indices of ``u``. ``index_dimensions`` --------------------- ``u.index_dimensions()`` returns a ``dict`` mapping from each ``Index`` instance in ``u.free_indices()`` to the integer dimension of the value space each index can range over. ``str(u)`` ---------- ``str(u)`` returns a human-readable string representation of ``u``. ``repr(u)`` ----------- ``repr(u)`` returns a Python string representation of ``u``, such that ``eval(repr(u)) == u`` holds in Python. ``hash(u)`` ----------- ``hash(u)`` returns a hash code for ``u``, which is used extensively (indirectly) in algorithms whenever ``u`` is placed in a Python ``dict`` or ``set``. ``u == v`` ---------- ``u == v`` returns true if and only if ``u`` and ``v`` represents the same expression in the exact same way. This is used extensively (indirectly) in algorithms whenever ``u`` is placed in a Python ``dict`` or ``set``. About other relational operators -------------------------------- In general, UFL expressions are not possible to fully evaluate since the cell and the values of form arguments are not available. Implementing relational operators for immediate evaluation is therefore impossible. Overloading relational operators as a part of the form language is not possible either, since it interferes with the correct use of container types in Python like ``dict`` or ``set``. Elements ======== All finite element classes have a common base class ``FiniteElementBase``. The class hierarchy looks like this: .. TODO: Class figure. .. TODO: Describe all FiniteElementBase subclasses here. Terminals ========= All ``Terminal`` subclasses have some non-``Expr`` data attached to them. ``ScalarValue`` has a Python scalar, ``Coefficient`` has a ``FiniteElement``, etc. Therefore, a unified implementation of ``reconstruct`` is not possible, but since all ``Expr`` instances are immutable, ``reconstruct`` for terminals can simply return self. This feature and the immutability property is used extensively in algorithms. Operators ========= All instances of ``Operator`` subclasses are fully specified by their type plus the tuple of ``Expr`` instances that are the operands. Their constructors should take these operands as the positional arguments, and only that. This way, a unified implementation of ``reconstruct`` is possible, by simply calling the constructor with new operands. This feature is used extensively in algorithms. Extending UFL ============= Adding new types to the UFL class hierarchy must be done with care. If you can get away with implementing a new operator as a combination of existing ones, that is the easiest route. The reason is that only some of the properties of an operator is represented by the ``Expr`` subclass. Other properties are part of the various algorithms in UFL. One example is derivatives, which are defined in the differentiation algorithm, and how to render a type to the ``LaTeX`` or dot formats. These properties could be merged into the class hierarchy, but other properties like how to map a UFL type to some ``ffc`` or ``dolfin`` type cannot be part of UFL. So before adding a new class, consider that doing so may require changes in multiple algorithms and even other projects. ufl-2017.2.0/doc/sphinx/source/manual/user_manual.rst0000644000231000000010000000034513211220450021533 0ustar chrisdaemon.. UFL user manual .. _ufl_user_manual: ############### UFL user manual ############### .. toctree:: :maxdepth: 1 introduction form_language examples internal_representation algorithms command_line_utils ufl-2017.2.0/doc/sphinx/source/manual/examples.rst0000644000231000000010000003710413211220450021041 0ustar chrisdaemon************* Example forms ************* The following examples illustrate basic usage of the form language for the definition of a collection of standard multilinear forms. We assume that ``dx`` has been declared as an integral over the interior of :math:`\Omega` and that both ``i`` and ``j`` have been declared as a free ``Index``. The examples presented below can all be found in the subdirectory ``demo/`` of the UFL source tree together with numerous other examples. The mass matrix =============== As a first example, consider the bilinear form corresponding to a mass matrix, .. math:: a(v, u) = \int_{\Omega} v \, u \mathop{dx}, which can be implemented in UFL as follows:: element = FiniteElement("Lagrange", triangle, 1) v = TestFunction(element) u = TrialFunction(element) a = v*u*dx This example is implemented in the file ``Mass.ufl`` in the collection of demonstration forms included with the UFL source distribution. Poisson equation ================ The bilinear and linear forms form for Poisson's equation, .. math:: a(v, u) &= \int_{\Omega} \nabla v \cdot \nabla u \mathop{dx}, \\ L(v; f) &= \int_{\Omega} v \, f \mathop{dx}, can be implemented as follows:: element = FiniteElement("Lagrange", triangle, 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) a = dot(grad(v), grad(u))*dx L = v*f*dx Alternatively, index notation can be used to express the scalar product like this:: a = Dx(v, i)*Dx(u, i)*dx or like this:: a = v.dx(i)*u.dx(i)*dx This example is implemented in the file ``Poisson.ufl`` in the collection of demonstration forms included with the UFL source distribution. Vector-valued Poisson ===================== The bilinear and linear forms for a system of (independent) Poisson equations, .. math:: a(v, u) &= \int_{\Omega} \nabla v : \nabla u \mathop{dx}, \\ L(v; f) &= \int_{\Omega} v \cdot f \mathop{dx}, with :math:`v`, :math:`u` and :math:`f` vector-valued can be implemented as follows:: element = VectorElement("Lagrange", triangle, 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) a = inner(grad(v), grad(u))*dx L = dot(v, f)*dx Alternatively, index notation may be used like this:: a = Dx(v[i], j)*Dx(u[i], j)*dx L = v[i]*f[i]*dx or like this:: a = v[i].dx(j)*u[i].dx(j)*dx L = v[i]*f[i]*dx This example is implemented in the file ``PoissonSystem.ufl`` in the collection of demonstration forms included with the UFL source distribution. The strain-strain term of linear elasticity =========================================== The strain-strain term of linear elasticity, .. math:: a(v, u) = \int_{\Omega} \epsilon(v) : \epsilon(u) \mathop{dx}, where .. math:: \epsilon(v) = \frac{1}{2}(\nabla v + (\nabla v)^{\top}) can be implemented as follows:: 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 Alternatively, index notation can be used to define the form:: a = 0.25*(Dx(v[j], i) + Dx(v[i], j))* \ (Dx(u[j], i) + Dx(u[i], j))*dx or like this:: a = 0.25*(v[j].dx(i) + v[i].dx(j))* \ (u[j].dx(i) + u[i].dx(j))*dx This example is implemented in the file ``Elasticity.ufl`` in the collection of demonstration forms included with the UFL source distribution. The nonlinear term of Navier--Stokes ==================================== The bilinear form for fixed-point iteration on the nonlinear term of the incompressible Navier--Stokes equations, .. math:: a(v, u; w) = \int_{\Omega} (w \cdot \nabla u) \cdot v \mathop{dx}, with :math:`w` the frozen velocity from a previous iteration, can be implemented as follows:: element = VectorElement("Lagrange", tetrahedron, 1) v = TestFunction(element) u = TrialFunction(element) w = Coefficient(element) a = dot(grad(u)*w, v)*dx alternatively using index notation like this:: a = v[i]*w[j]*Dx(u[i], j)*dx or like this:: a = v[i]*w[j]*u[i].dx(j)*dx This example is implemented in the file ``NavierStokes.ufl`` in the collection of demonstration forms included with the UFL source distribution. The heat equation ================= Discretizing the heat equation, .. math:: \dot{u} - \nabla \cdot (c \nabla u) = f, in time using the :math:`\mathrm{dG}(0)` method (backward Euler), we obtain the following variational problem for the discrete solution :math:`u_h = u_h(x, t)`: Find :math:`u_h^n = u_h(\cdot, t_n)` with :math:`u_h^{n-1} = u_h(\cdot, t_{n-1})` given such that .. math:: \frac{1}{k_n} \int_{\Omega} v \, (u_h^n - u_h^{n-1}) \mathop{dx} + \int_{\Omega} c \, \nabla v \cdot \nabla u_h^n \mathop{dx} = \int_{\Omega} v \, f^n \mathop{dx} for all test functions :math:`v`, where :math:`k_n = t_n - t_{n-1}` denotes the time step. In the example below, we implement this variational problem with piecewise linear test and trial functions, but other choices are possible (just choose another finite element). Rewriting the variational problem in the standard form :math:`a(v, u_h) = L(v)` for all :math:`v`, we obtain the following pair of bilinear and linear forms: .. math:: a(v, u_h^n; c, k) &= \int_{\Omega} v \, u_h^n \mathop{dx} + k_n \int_{\Omega} c \, \nabla v \cdot \nabla u_h^n \mathop{dx}, \\ L(v; u_h^{n-1}, f, k) &= \int_{\Omega} v \, u_h^{n-1} \mathop{dx} + k_n \int_{\Omega} v \, f^n \mathop{dx}, which can be implemented as follows:: element = FiniteElement("Lagrange", triangle, 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("triangle") # Time step a = v*u1*dx + k*c*dot(grad(v), grad(u1))*dx L = v*u0*dx + k*v*f*dx This example is implemented in the file ``Heat.ufl`` in the collection of demonstration forms included with the UFL source distribution. Mixed formulation of Stokes =========================== To solve Stokes' equations, .. math:: - \Delta u + \nabla p &= f, \\ \nabla \cdot u &= 0, we write the variational problem in standard form :math:`a(v, u) = L(v)` for all :math:`v` to obtain the following pair of bilinear and linear forms: .. math:: a((v, q), (u, p)) &= \int_{\Omega} \nabla v : \nabla u - (\nabla \cdot v) \, p + q \, (\nabla \cdot u) \mathop{dx}, \\ L((v, q); f) &= \int_{\Omega} v \cdot f \mathop{dx}. Using a mixed formulation with Taylor-Hood elements, this can be implemented as follows:: 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 This example is implemented in the file ``Stokes.ufl`` in the collection of demonstration forms included with the UFL source distribution. Mixed formulation of Poisson ============================ We next consider the following formulation of Poisson's equation as a pair of first order equations for :math:`\sigma \in H(\mathrm{div})` and :math:`u \in L^2`: .. math:: \sigma + \nabla u &= 0, \\ \nabla \cdot \sigma &= f. We multiply the two equations by a pair of test functions :math:`\tau` and :math:`w` and integrate by parts to obtain the following variational problem: Find :math:`(\sigma, u) \in V = H(\mathrm{div}) \times L^2` such that .. math:: a((\tau, w), (\sigma, u)) = L((\tau, w)) \quad \forall \, (\tau, w) \in V, where .. math:: a((\tau, w), (\sigma, u)) &= \int_{\Omega} \tau \cdot \sigma - \nabla \cdot \tau \, u + w \nabla \cdot \sigma \mathop{dx}, \\ L((\tau, w); f) &= \int_{\Omega} w \cdot f \mathop{dx}. We may implement the corresponding forms in our form language using first order BDM H(div)-conforming elements for :math:`\sigma` and piecewise constant :math:`L^2`-conforming elements for :math:`u` as follows:: 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 This example is implemented in the file ``MixedPoisson.ufl`` in the collection of demonstration forms included with the UFL source distribution. Poisson equation with DG elements ================================= We consider again Poisson's equation, but now in an (interior penalty) discontinuous Galerkin formulation: Find :math:`u \in V = L^2` such that .. math:: a(v, u) = L(v) \quad \forall v \in V, where .. math:: a(v, u; h) &= \int_{\Omega} \nabla v \cdot \nabla u \mathop{dx} \\ &+ \sum_S \int_S - \langle \nabla v \rangle \cdot [[ u ]]_n - [[ v ]]_n \cdot \langle \nabla u \rangle + (\alpha/h) [[ v ]]_n \cdot [[ u ]]_n \mathop{dS} \\ &+ \int_{\partial\Omega} - \nabla v \cdot [[ u ]]_n - [[ v ]]_n \cdot \nabla u + (\gamma/h) v u \mathop{ds} \\ L(v; f, g) &= \int_{\Omega} v f \mathop{dx} + \int_{\partial\Omega} v g \mathop{ds}. The corresponding finite element variational problem for discontinuous first order elements may be implemented as follows:: cell = triangle DG1 = FiniteElement("Discontinuous Lagrange", cell, 1) v = TestFunction(DG1) u = TrialFunction(DG1) f = Coefficient(DG1) g = Coefficient(DG1) h = 2.0*Circumradius(cell) alpha = 1 gamma = 1 a = dot(grad(v), grad(u))*dx \ - dot(avg(grad(v)), jump(u))*dS \ - dot(jump(v), avg(grad(u)))*dS \ + alpha/h('+')*dot(jump(v), jump(u))*dS \ - dot(grad(v), jump(u))*ds \ - dot(jump(v), grad(u))*ds \ + gamma/h*v*u*ds L = v*f*dx + v*g*ds .. TODO: set alpha and gamma to proper values This example is implemented in the file ``PoissonDG.ufl`` in the collection of demonstration forms included with the UFL source distribution. The Quadrature family ===================== .. *FIXME: The code examples in this section have been mostly converted to UFL syntax, but the quadrature elements need some more updating, as well as the text. In UFL, I think we should define the element order and not the number of points for quadrature elements, and let the form compiler choose a quadrature rule. This way the form depends less on the cell in use.* We consider here a nonlinear version of the Poisson's equation to illustrate the main point of the ``Quadrature`` finite element family. The strong equation looks as follows: .. math:: - \nabla \cdot (1+u^2)\nabla u = f. The linearised bilinear and linear forms for this equation, .. math:: a(v, u; u_0) &= \int_{\Omega} (1+u_{0}^2) \nabla v \cdot \nabla u \mathop{dx} + \int_{\Omega} 2u_0 u \nabla v \cdot \nabla u_0 \mathop{dx}, \\ L(v; u_0, f) &= \int_{\Omega} v \, f \mathop{dx} - \int_{\Omega} (1+u_{0}^2) \nabla v \cdot \nabla u_0 \mathop{dx}, can be implemented in a single form file as follows:: 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 Here, :math:`u_0` represents the solution from the previous Newton-Raphson iteration. The above form will be denoted REF1 and serves as our reference implementation for linear elements. A similar form (REF2) using quadratic elements will serve as a reference for quadratic elements. Now, assume that we want to treat the quantities :math:`C = (1 + u_{0}^2)` and :math:`\sigma_0 = (1+u_{0}^2) \nabla u_0` as given functions (to be computed elsewhere). Substituting into the bilinear and linear forms, we obtain .. math:: a(v, u) &= \int_{\Omega} \text{C} \nabla v \cdot \nabla u \mathop{dx} + \int_{\Omega} 2u_0 u \nabla v \cdot \nabla u_0 \mathop{dx}, \\ L(v; \sigma_0, f) &= \int_{\Omega} v \, f \mathop{dx} - \int_{\Omega} \nabla v \cdot \sigma_0 \mathop{dx}. Then, two additional forms are created to compute the tangent C and the gradient of :math:`u_0`. This situation shows up in plasticity and other problems where certain quantities need to be computed elsewhere (in user-defined functions). The three forms using the standard ``FiniteElement`` (linear elements) can then be implemented as :: # NonlinearPoisson.ufl element = FiniteElement("Lagrange", triangle, 1) DG = FiniteElement("Discontinuous Lagrange", triangle, 0) sig = VectorElement("Discontinuous Lagrange", triangle, 0) v = TestFunction(element) u = TrialFunction(element) u0 = Coefficient(element) C = Coefficient(DG) 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 and :: # Tangent.ufl element = FiniteElement("Lagrange", triangle, 1) DG = FiniteElement("Discontinuous Lagrange", triangle, 0) v = TestFunction(DG) u = TrialFunction(DG) u0= Coefficient(element) a = v*u*dx L = v*(1.0 + u0**2)*dx and :: # Gradient.ufl element = FiniteElement("Lagrange", triangle, 1) DG = VectorElement("Discontinuous Lagrange", triangle, 0) v = TestFunction(DG) u = TrialFunction(DG) u0 = Coefficient(element) a = dot(v, u)*dx L = dot(v, (1.0 + u0**2)*grad(u0))*dx The three forms can be implemented using the ``QuadratureElement`` in a similar fashion in which only the element declaration is different:: # QE1NonlinearPoisson.ufl element = FiniteElement("Lagrange", triangle, 1) QE = FiniteElement("Quadrature", triangle, 2) sig = VectorElement("Quadrature", triangle, 2) and :: # QE1Tangent.ufl element = FiniteElement("Lagrange", triangle, 1) QE = FiniteElement("Quadrature", triangle, 2) and :: # QE1Gradient.ufl element = FiniteElement("Lagrange", triangle, 1) QE = VectorElement("Quadrature", triangle, 2) Note that we use two points when declaring the ``QuadratureElement``. This is because the RHS of ``Tangent.form`` is second order and therefore we need two points for exact integration. Due to consistency issues, when passing functions around between the forms, we also need to use two points when declaring the ``QuadratureElement`` in the other forms. Typical values of the relative residual for each Newton iteration for all three approaches are shown in the table below. It is to be noted that the convergence rate is quadratic as it should be for all three methods. Relative residuals for each approach for linear elements:: Iteration REF1 FE1 QE1 ========= ==== === === 1 6.3e-02 6.3e-02 6.3e-02 2 5.3e-04 5.3e-04 5.3e-04 3 3.7e-08 3.7e-08 3.7e-08 4 2.9e-16 2.9e-16 2.5e-16 However, if quadratic elements are used to interpolate the unknown field :math:`u`, the order of all elements in the above forms is increased by 1. This influences the convergence rate as seen in the table below. Clearly, using the standard ``FiniteElement`` leads to a poor convergence whereas the ``QuadratureElement`` still leads to quadratic convergence. Relative residuals for each approach for quadratic elements:: Iteration REF2 FE2 QE2 ========= ==== === === 1 2.6e-01 3.9e-01 2.6e-01 2 1.1e-02 4.6e-02 1.1e-02 3 1.2e-05 1.1e-02 1.6e-05 4 1.1e-11 7.2e-04 9.1e-09 More examples ============= Feel free to send additional demo form files for your favourite PDE to the UFL mailing list. .. %TODO: Modify rest of FFC example forms to UFL syntax and add here. ufl-2017.2.0/doc/sphinx/source/manual/algorithms.rst0000644000231000000010000003633613211220450021402 0ustar chrisdaemon********** Algorithms ********** Algorithms to work with UFL forms and expressions can be found in the submodule ``ufl.algorithms``. You can import all of them with the line :: from ufl.algorithms import * This chapter gives an overview of (most of) the implemented algorithms. The intended audience is primarily developers, but advanced users may find information here useful for debugging. While domain specific languages introduce notation to express particular ideas more easily, which can reduce the probability of bugs in user code, they also add yet another layer of abstraction which can make debugging more difficult when the need arises. Many of the utilities described here can be useful in that regard. Formatting expressions ====================== Expressions can be formatted in various ways for inspection, which is particularly useful for debugging. We use the following as an example form for the formatting sections below:: element = FiniteElement("CG", triangle, 1) v = TestFunction(element) u = TrialFunction(element) c = Coefficient(element) f = Coefficient(element) a = c*u*v*dx + f*v*ds str --- Compact, human readable pretty printing. Useful in interactive Python sessions. Example output of ``str(a)``:: { v_0 * v_1 * w_0 } * dx(>>[everywhere], {}) + { v_0 * w_1 } * ds(>>[everywhere], {}) repr ---- Accurate description of an expression, with the property that ``eval(repr(a)) == a``. Useful to see which representation types occur in an expression, especially if ``str(a)`` is ambiguous. Example output of ``repr(a)``:: Form([Integral(Product(Argument(FunctionSpace(Mesh(VectorElement('Lagrange', triangle, 1, dim=2), -1), FiniteElement('Lagrange', triangle, 1)), 0, None), Product(Argument(FunctionSpace(Mesh(VectorElement('Lagrange', triangle, 1, dim=2), -1), FiniteElement('Lagrange', triangle, 1)), 1, None), Coefficient(FunctionSpace(Mesh(VectorElement('Lagrange', triangle, 1, dim=2), -1), FiniteElement('Lagrange', triangle, 1)), 0))), 'cell', Mesh(VectorElement('Lagrange', triangle, 1, dim=2), -1), 'everywhere', {}, None), Integral(Product(Argument(FunctionSpace(Mesh(VectorElement('Lagrange', triangle, 1, dim=2), -1), FiniteElement('Lagrange', triangle, 1)), 0, None), Coefficient(FunctionSpace(Mesh(VectorElement('Lagrange', triangle, 1, dim=2), -1), FiniteElement('Lagrange', triangle, 1)), 1)), 'exterior_facet', Mesh(VectorElement('Lagrange', triangle, 1, dim=2), -1), 'everywhere', {}, None)]) Tree formatting --------------- ASCII tree formatting, useful to inspect the tree structure of an expression in interactive Python sessions. Example output of ``tree_format(a)``:: Form: Integral: integral type: cell subdomain id: everywhere integrand: Product ( Argument(FunctionSpace(Mesh(VectorElement('Lagrange', triangle, 1, dim=2), -1), FiniteElement('Lagrange', triangle, 1)), 0, None) Product ( Argument(FunctionSpace(Mesh(VectorElement('Lagrange', triangle, 1, dim=2), -1), FiniteElement('Lagrange', triangle, 1)), 1, None) Coefficient(FunctionSpace(Mesh(VectorElement('Lagrange', triangle, 1, dim=2), -1), FiniteElement('Lagrange', triangle, 1)), 0) ) ) Integral: integral type: exterior_facet subdomain id: everywhere integrand: Product ( Argument(FunctionSpace(Mesh(VectorElement('Lagrange', triangle, 1, dim=2), -1), FiniteElement('Lagrange', triangle, 1)), 0, None) Coefficient(FunctionSpace(Mesh(VectorElement('Lagrange', triangle, 1, dim=2), -1), FiniteElement('Lagrange', triangle, 1)), 1) ) Inspecting and manipulating the expression tree =============================================== This subsection is mostly for form compiler developers and technically interested users. Traversing expressions ---------------------- ``iter_expressions`` ^^^^^^^^^^^^^^^^^^^^^ Example usage:: for e in iter_expressions(a): print str(e) outputs:: v_0 * v_1 * w_0 v_0 * w_1 .. ``post_traversal`` ^^^^^^^^^^^^^^^^^^^ .. TODO: traversal.py .. ``pre_traversal`` ^^^^^^^^^^^^^^^^^^ .. TODO: traversal.py .. ``walk`` ^^^^^^^^ .. TODO: traversal.py .. ``traverse_terminals`` ^^^^^^^^^^^^^^^^^^^^^^^ .. TODO: traversal.py .. Extracting information ---------------------- .. TODO: analysis.py Transforming expressions ------------------------ So far we presented algorithms meant to inspect expressions in various ways. Some recurring patterns occur when writing algorithms to modify expressions, either to apply mathematical transformations or to change their representation. Usually, different expression node types need different treatment. To assist in such algorithms, UFL provides the ``Transformer`` class. This implements a variant of the Visitor pattern to enable easy definition of transformation rules for the types you wish to handle. Shown here is maybe the simplest transformer possible:: class Printer(Transformer): def __init__(self): Transformer.__init__(self) def expr(self, o, *operands): print "Visiting", str(o), "with operands:" print ", ".join(map(str,operands)) return o element = FiniteElement("CG", triangle, 1) v = TestFunction(element) u = TrialFunction(element) a = u*v p = Printer() p.visit(a) The call to ``visit`` will traverse ``a`` and call ``Printer.expr`` on all expression nodes in post--order, with the argument ``operands`` holding the return values from visits to the operands of ``o``. The output is:: Visiting v_0 * v_1 with operands: v_0, v_1 :math:`(v^0_h)(v^1_h)` Implementing ``expr`` above provides a default handler for any expression node type. For each subclass of ``Expr`` you can define a handler function to override the default by using the name of the type in underscore notation, e.g. ``vector_constant`` for ``VectorConstant``. The constructor of ``Transformer`` and implementation of ``Transformer.visit`` handles the mapping from type to handler function automatically. Here is a simple example to show how to override default behaviour:: from ufl.classes import * class CoefficientReplacer(Transformer): def __init__(self): Transformer.__init__(self) expr = Transformer.reuse_if_possible terminal = Transformer.always_reuse def coefficient(self, o): return FloatValue(3.14) element = FiniteElement("CG", triangle, 1) v = TestFunction(element) f = Coefficient(element) a = f*v r = CoefficientReplacer() b = r.visit(a) print b which outputs :: 3.14 * v_0 The output of this code is the transformed expression ``b == 3.14*v``. This code also demonstrates how to reuse existing handlers. The handler ``Transformer.reuse_if_possible`` will return the input object if the operands have not changed, and otherwise reconstruct a new instance of the same type but with the new transformed operands. The handler ``Transformer.always_reuse`` always reuses the instance without recursing into its children, usually applied to terminals. To set these defaults with less code, inherit ``ReuseTransformer`` instead of ``Transformer``. This ensures that the parts of the expression tree that are not changed by the transformation algorithms will always reuse the same instances. We have already mentioned the difference between pre--traversal and post--traversal, and some times you need to combine the two. ``Transformer`` makes this easy by checking the number of arguments to your handler functions to see if they take transformed operands as input or not. If a handler function does not take more than a single argument in addition to self, its children are not visited automatically, and the handler function must call ``visit`` on its operands itself. Here is an example of mixing pre- and post-traversal:: class Traverser(ReuseTransformer): def __init__(self): ReuseTransformer.__init__(self) def sum(self, o): operands = o.operands() newoperands = [] for e in operands: newoperands.append( self.visit(e) ) return sum(newoperands) element = FiniteElement("CG", triangle, 1) f = Coefficient(element) g = Coefficient(element) h = Coefficient(element) a = f+g+h r = Traverser() b = r.visit(a) print b This code inherits the ``ReuseTransformer`` as explained above, so the default behaviour is to recurse into children first and then call ``Transformer.reuse_if_possible`` to reuse or reconstruct each expression node. Since ``sum`` only takes ``self`` and the expression node instance ``o`` as arguments, its children are not visited automatically, and ``sum`` explicitly calls ``self.visit`` to do this. Automatic differentiation implementation ======================================== This subsection is mostly for form compiler developers and technically interested users. First of all, we give a brief explanation of the algorithm. Recall that a ``Coefficient`` represents a sum of unknown coefficients multiplied with unknown basis functions in some finite element space. .. math:: w(x) = \sum_k w_k \phi_k(x) Also recall that an ``Argument`` represents any (unknown) basis function in some finite element space. .. math:: v(x) = \phi_k(x), \qquad \phi_k \in V_h . A form :math:`L(v; w)` implemented in UFL is intended for discretization like .. math:: b_i = L(\phi_i; \sum_k w_k \phi_k), \qquad \forall \phi_i \in V_h . The Jacobi matrix :math:`A_{ij}` of this vector can be obtained by differentiation of :math:`b_i` w.r.t. :math:`w_j`, which can be written .. math:: A_{ij} = \frac{d b_i}{d w_j} = a(\phi_i, \phi_j; \sum_k w_k \phi_k), \qquad \forall \phi_i \in V_h, \quad \forall \phi_j \in V_h , for some form `a`. In UFL, the form `a` can be obtained by differentiating `L`. To manage this, we note that as long as the domain :math:`\Omega` is independent of :math:`w_j`, :math:`\int_\Omega` commutes with :math:`\frac{d}{d w_j}`, and we can differentiate the integrand expression instead, e.g., .. math:: L(v; w) = \int_\Omega I_c(v; w) \, dx + \int_{\partial\Omega} I_e(v; w) \, ds, \\ \frac{d}{d w_j} L(v; w) = \int_\Omega \frac{d I_c}{d w_j} \, dx + \int_{\partial\Omega} \frac{d I_e}{d w_j} \, ds. In addition, we need that .. math:: \frac{d w}{d w_j} = \phi_j, \qquad \forall \phi_j \in V_h , which in UFL can be represented as .. math:: w &= \mathtt{Coefficient(element)}, \\ v &= \mathtt{Argument(element)}, \\ \frac{dw}{d w_j} &= v, since :math:`w` represents the sum and :math:`v` represents any and all basis functions in :math:`V_h`. Other operators have well defined derivatives, and by repeatedly applying the chain rule we can differentiate the integrand automatically. .. TODO: More details about AD algorithms for developers. .. Forward mode ------------ .. TODO: forward_ad.py .. Reverse mode ------------ .. TODO: reverse_ad.py .. Mixed derivatives ----------------- .. TODO: ad.py Computational graphs ==================== This section is for form compiler developers and is probably of no interest to end-users. An expression tree can be seen as a directed acyclic graph (DAG). To aid in the implementation of form compilers, UFL includes tools to build a linearized [#]_ computational graph from the abstract expression tree. A graph can be partitioned into subgraphs based on dependencies of subexpressions, such that a quadrature based compiler can easily place subexpressions inside the right sets of loops. .. [#] Linearized as in a linear datastructure, do not confuse this with automatic differentiation. .. TODO: Finish and test this before writing about it :) The vertices of a graph can be reordered to improve the efficiency of the generated code, an operation usually called operation scheduling. The computational graph ----------------------- .. TODO: finish graph.py: Consider the expression .. math:: f = (a + b) * (c + d) where a, b, c, d are arbitrary scalar expressions. The *expression tree* for f looks like this:: a b c d \ / \ / + + \ / * In UFL f is represented like this expression tree. If a, b, c, d are all distinct Coefficient instances, the UFL representation will look like this:: Coefficient Coefficient Coefficient Coefficient \ / \ / Sum Sum \ / --- Product --- If we instead have the expression .. math:: f = (a + b) * (a - b) the tree will in fact look like this, with the functions a and b only represented once:: Coefficient Coefficient | \ / | | Sum Product -- IntValue(-1) | | | | Product | | | | |------- Sum -------| The expression tree is a directed acyclic graph (DAG) where the vertices are Expr instances and each edge represents a direct dependency between two vertices, i.e. that one vertex is among the operands of another. A graph can also be represented in a linearized data structure, consisting of an array of vertices and an array of edges. This representation is convenient for many algorithms. An example to illustrate this graph representation follows:: G = V, E V = [a, b, a+b, c, d, c+d, (a+b)*(c+d)] E = [(6,2), (6,5), (5,3), (5,4), (2,0), (2,1)] In the following, this representation of an expression will be called the *computational graph*. To construct this graph from a UFL expression, simply do :: G = Graph(expression) V, E = G The Graph class can build some useful data structures for use in algorithms:: Vin = G.Vin() # Vin[i] = list of vertex indices j such that there is an edge from V[j] to V[i] Vout = G.Vout() # Vout[i] = list of vertex indices j such that there is an edge from V[i] to V[j] Ein = G.Ein() # Ein[i] = list of edge indices j such that E[j] is an edge to V[i], e.g. E[j][1] == i Eout = G.Eout() # Eout[i] = list of edge indices j such that E[j] is an edge from V[i], e.g. E[j][0] == i The ordering of the vertices in the graph can in principle be arbitrary, but here they are ordered such that .. math:: v_i \prec v_j, \quad \forall j > i, where :math:`a \prec b` means that :math:`a` does not depend on :math:`b` directly or indirectly. Another property of the computational graph built by UFL is that no identical expression is assigned to more than one vertex. This is achieved efficiently by inserting expressions in a dict (a hash map) during graph building. In principle, correct code can be generated for an expression from its computational graph simply by iterating over the vertices and generating code for each one separately. However, we can do better than that. Partitioning the graph ---------------------- To help generate better code efficiently, we can partition vertices by their dependencies, which allows us to, e.g., place expressions outside the quadrature loop if they don't depend (directly or indirectly) on the spatial coordinates. This is done simply by .. TODO :: P = partition(G) ufl-2017.2.0/doc/sphinx/source/manual/introduction.rst0000644000231000000010000000354013211220450021741 0ustar chrisdaemon************ Introduction ************ The Unified Form Language (UFL) is a domain specific language for defining discrete variational forms and functionals in a notation close to pen-and-paper formulation. UFL is part of the FEniCS Project and is usually used in combination with other components from this project to compute solutions to partial differential equations. The form compiler FFC uses UFL as its end-user interface, producing implementations of the UFC interface as output. See DOLFIN for more details about using UFL in an integrated problem solving environment. This manual is intended for different audiences. If you are an end-user and all you want to do is to solve your PDEs with the FEniCS framework, you should read :doc:`form_language`, and also :doc:`examples`. These two sections explain how to use all operators available in the language and present a number of examples to illustrate the use of the form language in applications. The remaining chapters contain more technical details intended for developers who need to understand what is happening behind the scenes and modify or extend UFL in the future. :doc:`internal_representation` describes the implementation of the language, in particular how expressions are represented internally by UFL. This can also be useful knowledge to understand error messages and debug errors in your form files. :doc:`algorithms` explains the many algorithms available to work with UFL expressions, mostly intended to aid developers of form compilers. The algorithms include helper functions for easy and efficient iteration over expression trees, formatting tools to present expressions as text or images of different kinds, utilities to analyse properties of expressions or checking their validity, automatic differentiation algorithms, as well as algorithms to work with the computational graphs of expressions. ufl-2017.2.0/doc/sphinx/source/manual/form_language.rst0000644000231000000010000014152513211220450022034 0ustar chrisdaemon************* Form language ************* UFL consists of a set of operators and atomic expressions that can be used to express variational forms and functionals. Below we will define all these operators and atomic expressions in detail. UFL is built on top of the Python language, and any Python code is valid in the definition of a form. In particular, comments (lines starting with ``#``) and functions (keyword ``def``, see user-defined_ below) are useful in the definition of a form. However, it is usually a good idea to avoid using advanced Python features in the form definition, to stay close to the mathematical notation. The entire form language can be imported in Python with the line .. code-block:: python from ufl import * which is assumed in all examples below and can be omitted in ``.ufl`` files. This can be useful for experimenting with the language in an interactive Python interpreter. Forms and integrals =================== UFL is designed to express forms in the following generalized format: .. math:: a(\mathbf{v}; \mathbf{w}) = \sum_{k=1}^{n_c} \int_{\Omega_k} I^c_k(\mathbf{v}; \mathbf{w}) dx + \sum_{k=1}^{n_e} \int_{\partial\Omega_k} I^e_k(\mathbf{v}; \mathbf{w}) ds + \sum_{k=1}^{n_i} \int_{\Gamma_k} I^i_k(\mathbf{v}; \mathbf{w}) dS. Here the form :math:`a` depends on the *form arguments* :math:`\mathbf{v} = (v_1, \ldots, v_r)` and the *form coefficients* :math:`\mathbf{w} = (w_1, \ldots, w_n)`, and its expression is a sum of integrals. Each term of a valid form expression must be a scalar-valued expression integrated exactly once. How to define form arguments and integrand expressions is detailed in the rest of this chapter. Integrals are expressed through multiplication with a measure, representing an integral over either * the interior of the domain :math:`\Omega` (``dx``, cell integral); * the boundary :math:`\partial\Omega` of :math:`\Omega` (``ds``, exterior facet integral); * the set of interior facets :math:`\Gamma` (``dS``, interior facet integral). (Note that newer versions of UFL support several other integral types currently not documented here). As a basic example, assume ``v`` is a scalar-valued expression and consider the integral of ``v`` over the interior of :math:`\Omega`. This may be expressed as:: a = v*dx and the integral of ``v`` over :math:`\partial\Omega` is written as:: a = v*ds. Alternatively, measures can be redefined to represent numbered subsets of a domain, such that a form evaluates to different expressions on different parts of the domain. If ``c``, ``e0`` and ``e1`` are scalar-valued expressions, then:: a = c*dx + e0*ds(0) + e1*ds(1) represents .. math:: a = \int_\Omega c\,dx + \int_{\partial\Omega_0} e_0 \, ds + \int_{\partial\Omega_1} e_1 \, ds, where .. math:: \partial\Omega_0 \subset \partial\Omega, \qquad \partial\Omega_1 \subset \partial\Omega. .. note:: The domain :math:`\Omega`, its subdomains and boundaries are not known to UFL. These are defined in a problem solving environment such as DOLFIN, which uses UFL to specify forms. Finite element spaces ===================== Before defining forms which can be integrated, it is necessary to describe the finite element spaces over which the integration takes place. UFL can represent very flexible general hierarchies of mixed finite elements, and has predefined names for most common element families. A finite element space is defined by an element domain, shape functions and nodal variables. In UFL, the element domain is called a ``Cell``. Cells ----- A polygonal cell is defined by a shape name and a geometric dimension, written as:: cell = Cell(shape, gdim) Valid shapes are "interval", "triangle", "tetrahedron", "quadrilateral", and "hexahedron". Some examples:: # Regular triangle cell cell = Cell("triangle") # Triangle cell embedded in 3D space cell = Cell("triangle", 3) Objects for regular cells of all basic shapes are predefined:: # Predefined linear cells cell = interval cell = triangle cell = tetrahedron cell = quadrilateral cell = hexahedron In the rest of this document, a variable name ``cell`` will be used where any cell is a valid argument, to make the examples dimension-independent wherever possible. Using a variable ``cell`` to hold the cell type used in a form is highly recommended, since this makes most form definitions dimension-independent. Element families ---------------- UFL predefines a set of names of known element families. When defining a finite element below, the argument ``family`` is a string and its possible values include * ``"Lagrange"`` or ``"CG"``, representing standard scalar Lagrange finite elements (continuous piecewise polynomial functions); * ``"Discontinuous Lagrange"`` or ``"DG"``, representing scalar discontinuous Lagrange finite elements (discontinuous piecewise polynomial functions); * ``"Crouzeix-Raviart"`` or ``"CR"``, representing scalar Crouzeix--Raviart elements; * ``"Brezzi-Douglas-Marini"`` or ``"BDM"``, representing vector-valued Brezzi--Douglas--Marini H(div) elements; * ``"Brezzi-Douglas-Fortin-Marini`` or ``"BDFM"``, representing vector-valued Brezzi--Douglas--Fortin--Marini H(div) elements; * ``"Raviart-Thomas"`` or ``"RT"``, representing vector-valued Raviart--Thomas H(div) elements. * ``"Nedelec 1st kind H(div)"`` or ``"N1div"``, representing vector-valued Nedelec H(div) elements (of the first kind). * ``"Nedelec 2st kind H(div)"`` or ``"N2div"``, representing vector-valued Nedelec H(div) elements (of the second kind). * ``"Nedelec 1st kind H(curl)"`` or ``"N1curl"``, representing vector-valued Nedelec H(curl) elements (of the first kind). * ``"Nedelec 2st kind H(curl)"`` or ``"N2curl"``, representing vector-valued Nedelec H(curl) elements (of the second kind). * ``"Bubble"``, representing bubble elements, useful for example to build the mini elements. * ``"Quadrature"`` or ``"Q"``, representing artificial "finite elements" with degrees of freedom being function evaluations at quadrature points; * ``"Boundary Quadrature"`` or ``"BQ"``, representing artificial "finite elements" with degrees of freedom being function evaluations at quadrature points on the boundary. Note that new versions of UFL also support notation from the Periodic Table of Finite Elements, currently not documented here. Basic elements -------------- A ``FiniteElement``, sometimes called a basic element, represents a finite element from some family on a given cell with a certain polynomial degree. Valid families and cells are explained above. The notation is :: element = FiniteElement(family, cell, degree) Some examples:: element = FiniteElement("Lagrange", interval, 3) element = FiniteElement("DG", tetrahedron, 0) element = FiniteElement("BDM", triangle, 1) Vector elements --------------- A ``VectorElement`` represents a combination of basic elements such that each component of a vector is represented by the basic element. The size is usually omitted, the default size equals the geometry dimension. The notation is :: element = VectorElement(family, cell, degree[, size]) Some examples:: # A quadratic "P2" vector element on a triangle element = VectorElement("CG", triangle, 2) # A linear 3D vector element on a 1D interval element = VectorElement("CG", interval, 1, size=3) # A six-dimensional piecewise constant element on a tetrahedron element = VectorElement("DG", tetrahedron, 0, size=6) Tensor elements --------------- A ``TensorElement`` represents a combination of basic elements such that each component of a tensor is represented by the basic element. The shape is usually omitted, the default shape is :math: `(d, d)` where :math: `d` is the geometric dimension. The notation is :: element = TensorElement(family, cell, degree[, shape, symmetry]) Any shape tuple consisting of positive integers is valid, and the optional symmetry can either be set to ``True`` which means standard matrix symmetry (like :math:`A_{ij} = A_{ji}`), or a ``dict`` like ``{ (0,1):(1,0), (0,2):(2,0) }`` where the ``dict`` keys are index tuples that are represented by the corresponding ``dict`` value. Examples:: element = TensorElement("CG", cell, 2) element = TensorElement("DG", cell, 0, shape=(6,6)) element = TensorElement("DG", cell, 0, symmetry=True) element = TensorElement("DG", cell, 0, symmetry={(0,0): (1,1)}) Mixed elements -------------- A ``MixedElement`` represents an arbitrary combination of other elements. ``VectorElement`` and ``TensorElement`` are special cases of a ``MixedElement`` where all sub-elements are equal. General notation for an arbitrary number of subelements:: element = MixedElement(element1, element2[, element3, ...]) Shorthand notation for two subelements:: element = element1 * element2 .. note:: The ``*`` operator is left-associative, such that:: element = element1 * element2 * element3 represents ``(e1 * e2) * e3``, i.e. this is a mixed element with two sub-elements ``(e1 * e2)`` and ``e3``. See `Form arguments`_ for details on how defining functions on mixed spaces can differ from defining functions on other finite element spaces. Examples:: # Taylor-Hood element V = VectorElement("Lagrange", cell, 2) P = FiniteElement("Lagrange", cell, 1) TH = V * P # A tensor-vector-scalar element T = TensorElement("Lagrange", cell, 2, symmetry=True) V = VectorElement("Lagrange", cell, 1) P = FiniteElement("DG", cell, 0) ME = MixedElement(T, V, P) EnrichedElement --------------- The data type ``EnrichedElement`` represents the vector sum of two (or more) finite elements. Example: The Mini element can be constructed as :: P1 = VectorElement("Lagrange", "triangle", 1) B = VectorElement("Bubble", "triangle", 3) Q = FiniteElement("Lagrange", "triangle", 1) Mini = (P1 + B) * Q Form arguments ============== Form arguments are divided in two groups, arguments and coefficients. An ``Argument`` represents an arbitrary basis function in a given discrete finite element space, while a ``Coefficient`` represents a function in a discrete finite element space that will be provided by the user at a later stage. The number of ``Argument``\ s that occur in a ``Form`` equals the "arity" of the form. Basis functions --------------- The data type ``Argument`` represents a basis function on a given finite element. An ``Argument`` must be created for a previously declared finite element (simple or mixed):: v = Argument(element) Note that more than one ``Argument`` can be declared for the same ``FiniteElement``. Basis functions are associated with the arguments of a multilinear form in the order of declaration. For a ``MixedElement``, the function ``Arguments`` can be used to construct tuples of ``Argument``\ s, as illustrated here for a mixed Taylor--Hood element:: v, q = Arguments(TH) u, p = Arguments(TH) For a ``Argument`` on a ``MixedElement`` (or ``VectorElement`` or ``TensorElement``), the function ``split`` can be used to extract basis function values on subspaces, as illustrated here for a mixed Taylor--Hood element:: vq = Argument(TH) v, q = split(up) This is equivalent to the previous use of ``Arguments``:: v, q = Arguments(TH) For convenience, ``TestFunction`` and ``TrialFunction`` are special instances of ``Argument`` with the property that a ``TestFunction`` will always be the first argument in a form and ``TrialFunction`` will always be the second argument in a form (order of declaration does not matter). Their usage is otherwise the same as for ``Argument``:: v = TestFunction(element) u = TrialFunction(element) v, q = TestFunctions(TH) u, p = TrialFunctions(TH) Meshes and function spaces -------------------------- Note that newer versions of UFL introduce the concept of a Mesh and a FunctionSpace. These are currently not documented here. Coefficient functions --------------------- The data type ``Coefficient`` represents a function belonging to a given finite element space, that is, a linear combination of basis functions of the finite element space. A ``Coefficient`` must be declared for a previously declared ``FiniteElement``:: f = Coefficient(element) Note that the order in which ``Coefficient``\ s are declared is important, directly reflected in the ordering they have among the arguments to each ``Form`` they are part of. ``Coefficient`` is used to represent user-defined functions, including, e.g., source terms, body forces, variable coefficients and stabilization terms. UFL treats each ``Coefficient`` as a linear combination of unknown basis functions with unknown coefficients, that is, UFL knows nothing about the concrete basis functions of the element and nothing about the value of the function. .. note:: Note that more than one function can be declared for the same ``FiniteElement``. The following example declares two ``Argument``\ s and two ``Coefficient``\ s for the same ``FiniteElement``:: v = Argument(element) u = Argument(element) f = Coefficient(element) g = Coefficient(element) For a ``Coefficient`` on a ``MixedElement`` (or ``VectorElement`` or ``TensorElement``), the function ``split`` can be used to extract function values on subspaces, as illustrated here for a mixed Taylor--Hood element:: up = Coefficient(TH) u, p = split(up) There is a shorthand for this, whose use is similar to ``Arguments``, called ``Coefficients``:: u, p = Coefficients(TH) Spatially constant (or discontinuous piecewise constant) functions can conveniently be represented by ``Constant``, ``VectorConstant``, and ``TensorConstant``:: c0 = Constant(cell) v0 = VectorConstant(cell) t0 = TensorConstant(cell) These three lines are equivalent with first defining DG0 elements and then defining a ``Coefficient`` on each, illustrated here:: DG0 = FiniteElement("Discontinuous Lagrange", cell, 0) DG0v = VectorElement("Discontinuous Lagrange", cell, 0) DG0t = TensorElement("Discontinuous Lagrange", cell, 0) c1 = Coefficient(DG0) v1 = Coefficient(DG0v) t1 = Coefficient(DG0t) Basic Datatypes =============== UFL expressions can depend on some other quantities in addition to the functions and basis functions described above. Literals and geometric quantities --------------------------------- Some atomic quantities are derived from the cell. For example, the (global) spatial coordinates are available as a vector valued expression ``SpatialCoordinate(cell)``:: # Linear form for a load vector with a sin(y) coefficient v = TestFunction(element) x = SpatialCoordinate(cell) L = sin(x[1])*v*dx Another quantity is the (outwards pointing) facet normal ``FacetNormal(cell)``. The normal vector is only defined on the boundary, so it can't be used in a cell integral. Example functional ``M``, an integral of the normal component of a function ``g`` over the boundary:: n = FacetNormal(cell) g = Coefficient(VectorElement("CG", cell, 1)) M = dot(n, g)*ds Python scalars (int, float) can be used anywhere a scalar expression is allowed. Another literal constant type is ``Identity`` which represents an :math:`n\times n` unit matrix of given size :math:`n`, as in this example:: # Geometric dimension d = cell.geometric_dimension() # d x d identiy matrix I = Identity(d) # Kronecker delta delta_ij = I[i,j] Indexing and tensor components ============================== UFL supports index notation, which is often a convenient way to express forms. The basic principle of index notation is that summation is implicit over indices repeated twice in each term of an expression. The following examples illustrate the index notation, assuming that each of the variables ``i`` and ``j`` has been declared as a free ``Index``: * ``v[i]*w[i]``: :math:`\sum_{i=0}^{n-1} v_i w_i = \mathbf{v}\cdot\mathbf{w}` * ``Dx(v, i)*Dx(w, i)``: :math:`\sum_{i=0}^{d-1} \frac{\partial v}{\partial x_i} \frac{\partial w}{\partial x_i} = \nabla v \cdot \nabla w` * ``Dx(v[i], i)``: :math:`\sum_{i=0}^{d-1} \frac{\partial v_i}{\partial x_i} = \nabla \cdot v` * ``Dx(v[i], j)*Dx(w[i], j)``: :math:`\sum_{i=0}^{n-1} \sum_{j=0}^{d-1} \frac{\partial v_i}{\partial x_j} \frac{\partial w_i}{\partial x_j} = \nabla \mathbf{v} : \nabla \mathbf{w}` Here we will try to very briefly summarize the basic concepts of tensor algebra and index notation, just enough to express the operators in UFL. Assuming an Euclidean space in :math:`d` dimensions with :math:`1 \le d \le 3`, and a set of orthonormal basis vectors :math:`\mathbf{i}_i` for :math:`i \in {0, \ldots, d-1 }`, we can define the dot product of any two basis functions as .. math:: \mathbf{i}_{i} \cdot \mathbf{i}_{j} = \delta_{ij}, where :math:`\delta_{ij}` is the Kronecker delta .. math:: \delta_{ij} \equiv \begin{cases} 1, \quad i = j, \\ 0, \quad \text{otherwise}. \end{cases} A rank 1 tensor (vector) quantity :math:`\mathbf{v}` can be represented in terms of unit vectors and its scalar components in that basis. In tensor algebra it is common to assume implicit summation over indices repeated twice in a product: .. math:: \mathbf{v} = v_k \mathbf{i}_k \equiv \sum_k v_k \mathbf{i}_k. Similarly, a rank two tensor (matrix) quantity :math:`\mathbf{A}` can be represented in terms of unit matrices, that is outer products of unit vectors: .. math:: \mathbf{A} = A_{ij} \mathbf{i}_i \mathbf{i}_j \equiv \sum_i \sum_j A_{ij} \mathbf{i}_i \mathbf{i}_j . This generalizes to tensors of arbitrary rank: .. math:: \mathcal{C} &= C_\iota \mathbf{i}_{\iota_0} \otimes \cdots \otimes \mathbf{i}_{\iota_{r-1}} \\ &\equiv \sum_{\iota_0} \cdots \sum_{\iota_{r-1}} C_\iota \mathbf{i}_{\iota_0}\otimes\cdots \otimes \mathbf{i}_{\iota_{r-1}}, where :math:`\mathcal{C}` is a rank :math:`r` tensor and :math:`\iota` is a multi-index of length :math:`r`. When writing equations on paper, a mathematician can easily switch between the :math:`\mathbf{v}` and :math:`v_i` representations without stating it explicitly. This is possible because of flexible notation and conventions. In a programming language, we can't use the boldface notation which associates :math:`\mathbf{v}` and :math:`v` by convention, and we can't always interpret such conventions unambiguously. Therefore, UFL requires that an expression is explicitly mapped from its tensor representation (:math:`\mathbf{v}`, :math:`\mathbf{A}`) to its component representation (:math:`v_i`, :math:`A_{ij}`) and back. This is done using ``Index`` objects, the indexing operator (``v[i]``) and the function ``as_tensor``. More details on these follow. In the following descriptions of UFL operator syntax, i-l and p-s are assumed to be predefined indices, and unless otherwise specified the name v refers to some vector valued expression, and the name A refers to some matrix valued expression. The name C refers to a tensor expression of arbitrary rank. Defining indices ---------------- A set of indices ``i``, ``j``, ``k``, ``l`` and ``p``, ``q``, ``r``, ``s`` are predefined, and these should be enough for many applications. Examples will usually use these objects instead of creating new ones to conserve space. The data type ``Index`` represents an index used for subscripting derivatives or taking components of non-scalar expressions. To create indices you can either make a single one using ``Index()`` or make several at once conveniently using ``indices(n)``:: i = Index() j, k, l = indices(3) Each of these represents an ``index range`` determined by the context; if used to subscript a tensor-valued expression, the range is given by the shape of the expression, and if used to subscript a derivative, the range is given by the dimension :math:`d` of the underlying shape of the finite element space. As we shall see below, indices can be a powerful tool when used to define forms in tensor notation. .. note:: Advanced usage If using UFL inside DOLFIN or another larger programming environment, it is a good idea to define your indices explicitly just before your form uses them, to avoid name collisions. The definition of the predefined indices is simply:: i, j, k, l = indices(4) p, q, r, s = indices(4) .. note:: Advanced usage Note that in the old FFC notation, the definition :: i = Index(0) meant that the value of the index remained constant. This does not mean the same in UFL, and this notation is only meant for internal usage. Fixed indices are simply integers instead:: i = 0 Taking components of tensors ---------------------------- Basic fixed indexing of a vector valued expression v or matrix valued expression A: * ``v[0]``: component access, representing the scalar value of the first component of v * ``A[0,1]``: component access, representing the scalar value of the first row, second column of A Basic indexing: * ``v[i]``: component access, representing the scalar value of some component of v * ``A[i,j]``: component access, representing the scalar value of some component i,j of A More advanced indexing: * ``A[i,0]``: component access, representing the scalar value of some component i of the first column of A * ``A[i,:]``: row access, representing some row i of A, i.e. rank(A[i,:]) == 1 * ``A[:,j]``: column access, representing some column j of A, i.e. rank(A[:,j]) == 1 * ``C[...,0]``: subtensor access, representing the subtensor of A with the last axis fixed, e.g., A[...,0] == A[:,0] * ``C[j,...]``: subtensor access, representing the subtensor of A with the first axis fixed, e.g., A[j,...] == A[j,:] Making tensors from components ------------------------------ If you have expressions for scalar components of a tensor and wish to convert them to a tensor, there are two ways to do it. If you have a single expression with free indices that should map to tensor axes, like mapping :math:`v_k` to :math:`\mathbf{v}` or :math:`A_{ij}` to :math:`\mathbf{A}`, the following examples show how this is done:: vk = Identity(cell.geometric_dimension())[0,k] v = as_tensor(vk, (k,)) Aij = v[i]*u[j] A = as_tensor(Aij, (i,j)) Here ``v`` will represent unit vector :math:`\mathbf{i}_0`, and ``A`` will represent the outer product of ``v`` and ``u``. If you have multiple expressions without indices, you can build tensors from them just as easily, as illustrated here:: v = as_vector([1.0, 2.0, 3.0]) A = as_matrix([[u[0], 0], [0, u[1]]]) B = as_matrix([[a+b for b in range(2)] for a in range(2)]) Here ``v``, ``A`` and ``B`` will represent the expressions .. math:: \mathbf{v} &= \mathbf{i}_0 + 2 \mathbf{i}_1 + 3 \mathbf{i}_2, \\ \mathbf{A} &= \begin{bmatrix} u_0 & 0 \\ 0 & u_1 \end{bmatrix}, \\ \mathbf{B} &= \begin{bmatrix} 0 & 1 \\ 1 & 2 \end{bmatrix}. Note that the function ``as_tensor`` generalizes from vectors to tensors of arbitrary rank, while the alternative functions ``as_vector`` and ``as_matrix`` work the same way but are only for constructing vectors and matrices. They are included for readability and convenience. Implicit summation ------------------ Implicit summation can occur in only a few situations. A product of two terms that shares the same free index is implicitly treated as a sum over that free index: * ``v[i]*v[i]``: :math:`\sum_i v_i v_i` * ``A[i,j]*v[i]*v[j]``: :math:`\sum_j (\sum_i A_{ij} v_i) v_j` A tensor valued expression indexed twice with the same free index is treated as a sum over that free index: * ``A[i,i]``: :math:`\sum_i A_{ii}` * ``C[i,j,j,i]``: :math:`\sum_i \sum_j C_{ijji}` The spatial derivative, in the direction of a free index, of an expression with the same free index, is treated as a sum over that free index: * ``v[i].dx(i)``: :math:`\sum_i \frac{d(v_{i})}{dx_i}` * ``A[i,j].dx(i)``: :math:`\sum_i \frac{d(A_{ij})}{dx_i}` Note that these examples are some times written :math:`v_{i,i}` and :math:`A_{ij,i}` in pen-and-paper index notation. Basic algebraic operators ========================= The basic algebraic operators ``+``, ``-``, ``*``, ``/`` can be used freely on UFL expressions. They do have some requirements on their operands, summarized here: Addition or subtraction, ``a + b`` or ``a - b``: * The operands ``a`` and ``b`` must have the same shape. * The operands ``a`` and ``b`` must have the same set of free indices. Division, ``a / b``: * The operand ``b`` must be a scalar expression. * The operand ``b`` must have no free indices. * The operand ``a`` can be non-scalar with free indices, in which division represents scalar division of all components with the scalar ``b``. Multiplication, ``a * b``: * The only non-scalar operations allowed is scalar-tensor, matrix-vector and matrix-matrix multiplication. * If either of the operands have any free indices, both must be scalar. * If any free indices are repeated, summation is implied. Basic nonlinear functions ========================= Some basic nonlinear functions are also available, their meaning mostly obvious. * ``abs(f)``: the absolute value of f. * ``sign(f)``: the sign of f (+1 or -1). * ``pow(f, g)`` or ``f**g``: f to the power g, :math:`f^g` * ``sqrt(f)``: square root, :math:`\sqrt{f}` * ``exp(f)``: exponential of f * ``ln(f)``: natural logarithm of f * ``cos(f)``: cosine of f * ``sin(f)``: sine of f * ``tan(f)``: tangent of f * ``cosh(f)``: hyperbolic cosine of f * ``sinh(f)``: hyperbolic sine of f * ``tanh(f)``: hyperbolic tangent of f * ``acos(f)``: inverse cosine of f * ``asin(f)``: inverse sine of f * ``atan(f)``: inverse tangent of f * ``atan2(f1, f2)``: inverse tangent of (f1/f2) * ``erf(f)``: error function of f, :math:`{2\over\sqrt{\pi}} \int_0^f \exp(-t^2) \mathop{dt}` * ``bessel_J(nu, f)``: Bessel function of the first kind, :math:`J_\nu(f)` * ``bessel_Y(nu, f)``: Bessel function of the second kind, :math:`Y_\nu(f)` * ``bessel_I(nu, f)``: Modified Bessel function of the first kind, :math:`I_\nu(f)` * ``bessel_K(nu, f)``: Modified Bessel function of the second kind, :math:`K_\nu(f)` These functions do not accept non-scalar operands or operands with free indices or ``Argument`` dependencies. Tensor algebra operators ======================== ``transpose`` ------------- The transpose of a matrix A can be written as:: AT = transpose(A) AT = A.T AT = as_matrix(A[i,j], (j,i)) The definition of the transpose is .. math:: \mathtt{AT[i,j]} \leftrightarrow (A^{\top})_{ij} = A_{ji} For transposing higher order tensor expressions, index notation can be used:: AT = as_tensor(A[i,j,k,l], (l,k,j,i)) ``tr`` ------ The trace of a matrix A is the sum of the diagonal entries. This can be written as:: t = tr(A) t = A[i,i] The definition of the trace is .. math:: \mathtt{tr(A)} \leftrightarrow \mathrm{tr} \mathbf{A} = A_{ii} = \sum_{i=0}^{n-1} A_{ii}. ``dot`` ------- The dot product of two tensors a and b can be written:: # General tensors f = dot(a, b) # Vectors a and b f = a[i]*b[i] # Matrices a and b f = as_matrix(a[i,k]*b[k,j], (i,j)) The definition of the dot product of unit vectors is (assuming an orthonormal basis for a Euclidean space): .. math:: \mathbf{i}_i \cdot \mathbf{i}_j = \delta_{ij} where :math:`\delta_{ij}` is the Kronecker delta function. The dot product of higher order tensors follow from this, as illustrated with the following examples. An example with two vectors .. math:: \mathbf{v} \cdot \mathbf{u} = (v_i \mathbf{i}_i) \cdot (u_j \mathbf{i}_j) = v_i u_j (\mathbf{i}_i \cdot \mathbf{i}_j) = v_i u_j \delta_{ij} = v_i u_i An example with a tensor of rank two .. math:: \mathbf{A} \cdot \mathbf{B} &= (A_{ij} \mathbf{i}_i \mathbf{i}_j) \cdot (B_{kl} \mathbf{i}_k \mathbf{i}_l) \\ &= (A_{ij}B_{kl}) \mathbf{i}_i(\mathbf{i}_j \cdot \mathbf{i}_k) \mathbf{i}_l \\ &= (A_{ij}B_{kl}\delta_{jk}) \mathbf{i}_i \mathbf{i}_l \\ &= A_{ik}B_{kl} \mathbf{i}_i \mathbf{i}_l. This is the same as a matrix-matrix multiplication. An example with a vector and a tensor of rank two .. math:: \mathbf{v} \cdot \mathbf{A} &= (v_j \mathbf{i}_j) \cdot (A_{kl} \mathbf{i}_k \mathbf{i}_l) \\ &= (v_j A_{kl}) (\mathbf{i}_j \cdot \mathbf{i}_k) \mathbf{i}_l \\ &= (v_j A_{kl}\delta_{jk}) \mathbf{i}_l \\ &= v_k A_{kl} \mathbf{i}_l This is the same as a vector-matrix multiplication. This generalizes to tensors of arbitrary rank: the dot product applies to the last axis of a and the first axis of b. The tensor rank of the product is rank(a)+rank(b)-2. ``inner`` --------- The inner product is a contraction over all axes of a and b, that is the sum of all component-wise products. The operands must have exactly the same dimensions. For two vectors it is equivalent to the dot product. If :math:`\mathbf{A}` and :math:`\mathbf{B}` are rank two tensors and :math:`\mathcal{C}` and :math:`\mathcal{D}` are rank 3 tensors their inner products are .. math:: \mathbf{A} : \mathbf{B} &= A_{ij} B_{ij} \\ \mathcal{C} : \mathcal{D} &= C_{ijk} D_{ijk} Using UFL notation, the following sets of declarations are equivalent:: # Vectors f = dot(a, b) f = inner(a, b) f = a[i]*b[i] # Matrices f = inner(A, B) f = A[i,j]*B[i,j] # Rank 3 tensors f = inner(C, D) f = C[i,j,k]*D[i,j,k] ``outer`` --------- The outer product of two tensors a and b can be written:: A = outer(a, b) The general definition of the outer product of two tensors :math:`\mathcal{C}` of rank :math:`r` and :math:`\mathcal{D}` of rank :math:`s` is .. math:: \mathcal{C} \otimes \mathcal{D} = C_{\iota^a_0 \ldots \iota^a_{r-1}} D_{\iota^b_0 \ldots\iota^b_{s-1}} \mathbf{i}_{\iota^a_0}\otimes\cdots\otimes\mathbf{i}_{\iota^a_{r-2}} \otimes \mathbf{i}_{\iota^b_1} \otimes \cdots \otimes \mathbf{i}_{\iota^b_{s-1}} Some examples with vectors and matrices are easier to understand: .. math:: \mathbf{v} \otimes \mathbf{u} = v_i u_j \mathbf{i}_i \mathbf{i}_j, \\ \mathbf{v} \otimes \mathbf{B} = v_i B_{kl} \mathbf{i}_i \mathbf{i}_k \mathbf{i}_l, \\ \mathbf{A} \otimes \mathbf{B} = A_{ij} B_{kl} \mathbf{i}_i \mathbf{i}_j \mathbf{i}_k \mathbf{i}_l . The outer product of vectors is often written simply as .. math:: \mathbf{v} \otimes \mathbf{u} = \mathbf{v} \mathbf{u}, which is what we have done with :math:`\mathbf{i}_i \mathbf{i}_j` above. The rank of the outer product is the sum of the ranks of the operands. ``cross`` --------- The operator ``cross`` accepts as arguments two logically vector-valued expressions and returns a vector which is the cross product (vector product) of the two vectors: .. math:: \mathtt{cross(v, w)} \leftrightarrow \mathbf{v} \times \mathbf{w} = (v_1 w_2 - v_2 w_1, v_2 w_0 - v_0 w_2, v_0 w_1 - v_1 w_0) Note that this operator is only defined for vectors of length three. ``det`` ------- The determinant of a matrix A can be written as :: d = det(A) ``dev`` ------- The deviatoric part of matrix A can be written as :: B = dev(A) The definition is .. math:: {\rm dev} \mathbf{A} = \mathbf{A} - \frac{\mathbf{A}_{ii}}{d} \mathbf{I} where :math:`d` is the rank of matrix A and :math:`\mathbf{I}` is the identity matrix. ``sym`` ------- The symmetric part of A can be written as :: B = sym(A) The definition is .. math:: {\rm sym} \mathbf{A} = \frac{1}{2}(\mathbf{A} + \mathbf{A}^T) ``skew`` -------- The skew symmetric part of A can be written as :: B = skew(A) The definition is .. math:: {\rm skew} \mathbf{A} = \frac{1}{2}(\mathbf{A} - \mathbf{A}^T) ``cofac`` --------- The cofactor of a matrix A can be written as :: B = cofac(A) The definition is .. math:: {\rm cofac} \mathbf{A} = \det (\mathbf{A}) \mathbf{A}^{-1} The implementation of this is currently rather crude, with a hardcoded symbolic expression for the cofactor. Therefore, this is limited to 1x1, 2x2 and 3x3 matrices. ``inv`` ------- The inverse of matrix A can be written as :: Ainv = inv(A) The implementation of this is currently rather crude, with a hardcoded symbolic expression for the inverse. Therefore, this is limited to 1x1, 2x2 and 3x3 matrices. Differential Operators ====================== Three different kinds of derivatives are currently supported: spatial derivatives, derivatives w.r.t. user defined variables, and derivatives of a form or functional w.r.t. a function. Basic spatial derivatives ------------------------- Spatial derivatives hold a special physical meaning in partial differential equations and there are several ways to express those. The basic way is:: # Derivative w.r.t. x_2 f = Dx(v, 2) f = v.dx(2) # Derivative w.r.t. x_i g = Dx(v, i) g = v.dx(i) If ``v`` is a scalar expression, ``f`` here is the scalar derivative of ``v`` with respect to spatial direction :math:`z`. If ``v`` has no free indices, ``g`` is the scalar derivative in spatial direction :math:`x_i`, and ``g`` has the free index ``i``. This can be expressed compactly as :math:`v_{,i}`: .. math:: f = \frac{\partial v}{\partial x_2} = v_{,2}, \\ g = \frac{\partial v}{\partial x_i} = v_{,i}. If the expression to be differentiated w.r.t. :math:`x_i` has ``i`` as a free-index, implicit summation is implied:: # Sum of derivatives w.r.t. x_i for all i g = Dx(v[i], i) g = v[i].dx(i) Here ``g`` will represent the sum of derivatives w.r.t. :math:`x_i` for all ``i``, that is .. math:: g = \sum_i \frac{\partial v}{\partial x_i} = v_{i,i}. .. note:: `v[i].dx(i)` and :math:`v_{i,i}` with compact notation denote implicit summation. Compound spatial derivatives ---------------------------- UFL implements several common differential operators. The notation is simple and their names should be self-explanatory:: Df = grad(f) df = div(f) cf = curl(v) rf = rot(f) The operand ``f`` can have no free indices. Gradient -------- The gradient of a scalar :math:`u` is defined as .. math:: \mathrm{grad}(u) \equiv \nabla u = \sum_{k=0}^{d-1} \frac{\partial u}{\partial x_k} \mathbf{i}_k, which is a vector of all spatial partial derivatives of :math:`u`. The gradient of a vector :math:`\mathbf{v}` is defined as .. math:: \mathrm{grad}(\mathbf{v}) \equiv \nabla \mathbf{v} = \frac{\partial v_i}{\partial x_j} \mathbf{i}_i \mathbf{i}_j, which, written componentwise, reads .. math:: \mathbf{A} = \nabla \mathbf{v}, \qquad A_{ij} = v_{i,j} In general for a tensor :math:`\mathbf{A}` of rank :math:`r` the definition is .. math:: {\rm grad}(\mathbf{A}) \equiv \nabla \mathbf{A} = (\frac{\partial}{\partial x_i}) (A_\iota\mathbf{i}_{\iota_0} \otimes\cdots\otimes \mathbf{i}_{\iota_{r-1}}) \otimes \mathbf{i}_i = \frac{\partial A_\iota}{\partial x_i} \mathbf{i}_{\iota_0} \otimes \cdots \otimes \mathbf{i}_{\iota_{r-1}} \otimes \mathbf{i}_i, where :math:`\iota` is a multi-index of length :math:`r`. In UFL, the following pairs of declarations are equivalent:: Dfi = grad(f)[i] Dfi = f.dx(i) Dvi = grad(v)[i, j] Dvi = v[i].dx(j) DAi = grad(A)[..., i] DAi = A.dx(i) for a scalar expression ``f``, a vector expression ``v``, and a tensor expression ``A`` of arbitrary rank. Divergence ---------- The divergence of any nonscalar (vector or tensor) expression :math:`\mathbf{A}` is defined as the contraction of the partial derivative over the last axis of the expression. The divergence of a vector :math:`\mathbf{v}` is defined as .. math:: \mathrm{div}(\mathbf{v}) \equiv \nabla\cdot\mathbf{v} = \sum_{k=0}^{d-1}\frac{\partial v_i}{\partial x_i} In UFL, the following declarations are equivalent:: dv = div(v) dv = v[i].dx(i) dA = div(A) dA = A[..., i].dx(i) for a vector expression v and a tensor expression A. Curl and rot ------------ The operator ``curl`` or ``rot`` accepts as argument a vector-valued expression and returns its curl .. math:: \mathrm{curl}(\mathbf{v}) = \nabla \times \mathbf{v} = (\frac{\partial v_2}{\partial x_1} - \frac{\partial v_1}{\partial x_2}, \frac{\partial v_0}{\partial x_2} - \frac{\partial v_2}{\partial x_0}, \frac{\partial v_1}{\partial x_0} - \frac{\partial v_0}{\partial x_1}). .. note:: The `curl` or `rot` operator is only defined for vectors of length three. In UFL, the following declarations are equivalent:: omega = curl(v) omega = rot(v) Variable derivatives -------------------- UFL also supports differentiation with respect to user defined variables. A user defined variable can be any expression that is defined as a variable. The notation is illustrated here:: # Define some arbitrary expression u = Coefficient(element) w = sin(u**2) # Annotate expression w as a variable that can be used by "diff" w = variable(w) # This expression is a function of w F = w**2 # The derivative of expression F w.r.t. the variable w dF = diff(F, w) # == 2*w Note that the variable ``w`` still represents the same expression. This can be useful for example to implement material laws in hyperelasticity where the stress tensor is derived from a Helmholtz strain energy function. Currently, UFL does not implement time in any particular way, but differentiation w.r.t. time can be done without this support through the use of a constant variable t:: t = variable(Constant(cell)) f = sin(x[0])**2 * cos(t) dfdt = diff(f, t) Functional derivatives ---------------------- The third and final kind of derivative are derivatives of functionals or forms w.r.t. to a ``Coefficient``. This is described in more detail in the section `AD`_ about form transformations. DG operators ============ UFL provides operators for implementation of discontinuous Galerkin methods. These include the evaluation of the jump and average of a function (or in general an expression) over the interior facets (edges or faces) of a mesh. Restriction: ``v('+')`` and ``v('-')`` ----------------------------------------------------------- When integrating over interior facets (``*dS``), one may restrict expressions to the positive or negative side of the facet:: element = FiniteElement("Discontinuous Lagrange", tetrahedron, 0) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) a = f('+')*dot(grad(v)('+'), grad(u)('-'))*dS Restriction may be applied to functions of any finite element space but will only have effect when applied to expressions that are discontinuous across facets. Jump: ``jump(v)`` ----------------- The operator ``jump`` may be used to express the jump of a function across a common facet of two cells. Two versions of the ``jump`` operator are provided. If called with only one argument, then the ``jump`` operator evaluates to the difference between the restrictions of the given expression on the positive and negative sides of the facet: .. math:: \mathtt{jump(v)} \leftrightarrow [[ v ]] = v^+ - v^- If the expression ``v`` is scalar, then ``jump(v)`` will also be scalar, and if ``v`` is vector-valued, then ``jump(v)`` will also be vector-valued. If called with two arguments, ``jump(v, n)`` evaluates to the jump in ``v`` weighted by ``n``. Typically, ``n`` will be chosen to represent the unit outward normal of the facet (as seen from each of the two neighboring cells). If ``v`` is scalar, then ``jump(v, n)`` is given by .. math:: \mathtt{jump(v, n)} \leftrightarrow [[ v ]]_n = v^+ n^+ + v^- n^- If ``v`` is vector-valued, then ``jump(v, n)`` is given by .. math:: \mathtt{jump(v, n)} \leftrightarrow [[ v ]]_n = v^+ \cdot n^+ + v^- \cdot n^- Thus, if the expression ``v`` is scalar, then ``jump(v, n)`` will be vector-valued, and if ``v`` is vector-valued, then ``jump(v, n)`` will be scalar. Average: ``avg(v)`` ------------------- The operator ``avg`` may be used to express the average of an expression across a common facet of two cells: .. math:: \mathtt{avg(v)} \leftrightarrow [[ v ]] = \frac{1}{2} (v^+ + v^-) The expression ``avg(v)`` has the same value shape as the expression ``v``. Conditional Operators ===================== Conditional ----------- UFL has limited support for branching, but for some PDEs it is needed. The expression ``c`` in:: c = conditional(condition, true_value, false_value) evaluates to ``true_value`` at run-time if ``condition`` evaluates to true, or to ``false_value`` otherwise. This corresponds to the C++ syntax ``(condition ? true_value: false_value)``, or the Python syntax ``(true_value if condition else false_value)``. Conditions ---------- * ``eq(a, b)`` must be used in place of the notation ``a == b`` * ``ne(a, b)`` must be used in place of the notation ``a != b`` * ``le(a, b)`` is equivalent to ``a <= b`` * ``ge(a, b)`` is equivalent to ``a >= b`` * ``lt(a, b)`` is equivalent to ``a < b`` * ``gt(a, b)`` is equivalent to ``a > b`` .. note:: Because of details in the way Python behaves, we cannot overload the == operator, hence these named operators. .. _user-defined: User-defined operators ====================== A user may define new operators, using standard Python syntax. As an example, consider the strain-rate operator :math:`\epsilon` of linear elasticity, defined by .. math:: \epsilon(v) = \frac{1}{2} (\nabla v + (\nabla v)^{\top}). This operator can be implemented as a function using the Python ``def`` keyword:: def epsilon(v): return 0.5*(grad(v) + grad(v).T) Alternatively, using the shorthand ``lambda`` notation, the strain operator may be defined as follows:: epsilon = lambda v: 0.5*(grad(v) + grad(v).T) Form Transformations ==================== When you have defined a ``Form``, you can derive new related forms from it automatically. UFL defines a set of common form transformations described in this section. Replacing arguments of a Form ----------------------------- The function ``replace`` lets you replace terminal objects with other values, using a mapping defined by a Python dicaionaryt. This can be used for example to replace a ``Coefficient`` with a fixed value for optimized runtime evaluation. Example:: f = Coefficient(element) g = Coefficient(element) c = Constant(cell) a = f*g*v*dx b = replace(a, { f: 3.14, g: c }) The replacement values must have the same basic properties as the original values, in particular value shape and free indices. Action of a form on a function ------------------------------ The action of a bilinear form :math:`a` is defined as .. math:: b(v; w) = a(v, w) The action of a linear form :math:`L` is defined as .. math:: f(;w) = L(w) This operation is implemented in UFL simply by replacing the rightmost basis function (trial function for `a`, test function for `L`) in a ``Form``, and is used like this:: L = action(a, w) f = action(L, w) To give a concrete example, these declarations are equivalent:: a = inner(grad(u), grad(v))*dx L = action(a, w) a = inner(grad(u), grad(v))*dx L = inner(grad(w), grad(v))*dx If a is a rank 2 form used to assemble the matrix A, L is a rank 1 form that can be used to assemble the vector :math:`b = Ax` directly. This can be used to define both the form of a matrix and the form of its action without code duplication, and for the action of a Jacobi matrix computed using derivative. If L is a rank 1 form used to assemble the vector b, f is a functional that can be used to assemble the scalar value :math:`f = b \cdot w` directly. This operation is sometimes used in, e.g., error control with L being the residual equation and w being the solution to the dual problem. (However, the discrete vector for the assembled residual equation will typically be available, so doing the dot product using linear algebra would be faster than using this feature.) Energy norm of a bilinear form ------------------------------- The functional representing the energy norm :math:`|v|_A = v^T A v` of a matrix A assembled from a form :math:`a` can be computed with:: f = energy_norm(a, w) which is equivalent to:: f = action(action(a, w), w) Adjoint of a bilinear form --------------------------- The adjoint :math:`a'` of a bilinear form :math:`a` is defined as .. math:: a'(u,v) = a(v,u). This operation is implemented in UFL simply by swapping test and trial functions in a ``Form``, and is used like this:: aprime = adjoint(a) Linear and bilinear parts of a form ----------------------------------- Sometimes it is useful to write an equation on the format .. math:: a(v,u) - L(v) = 0. Before assembly, we need to extract the forms corresponding to the left hand side and right hand side. This corresponds to extracting the bilinear and linear terms of the form respectively, or separating the terms that depend on both a test and a trial function on one side and the terms that depend on only a test function on the other. This is easily done in UFL using ``lhs`` and ``rhs``:: b = u*v*dx - f*v*dx a, L = lhs(b), rhs(b) Note that ``rhs`` multiplies the extracted terms by -1, corresponding to moving them from left to right, so this is equivalent to :: a = u*v*dx L = f*v*dx As a slightly more complicated example, this formulation:: F = v*(u - w)*dx + k*dot(grad(v), grad(0.5*(w + u)))*dx a, L = lhs(F), rhs(F) is equivalent to :: a = v*u*dx + k*dot(grad(v), 0.5*grad(u))*dx L = v*w*dx - k*dot(grad(v), 0.5*grad(w))*dx .. _AD: Automatic functional differentiation ------------------------------------ UFL can compute derivatives of functionals or forms w.r.t. to a ``Coefficient``. This functionality can be used for example to linearize your nonlinear residual equation automatically, or derive a linear system from a functional, or compute sensitivity vectors w.r.t. some coefficient. A functional can be differentiated to obtain a linear form, .. math:: F(v; w) = \frac{d}{dw} f(;w) and a linear form can be differentiated to obtain the bilinear form corresponding to its Jacobi matrix. .. note:: Note that by "linear form" we only mean a form that is linear in its test function, not in the function you differentiate with respect to. .. math:: J(v, u; w) = \frac{d}{dw} F(v; w). The UFL code to express this is (for a simple functional :math:`f(w)=\int_\Omega \frac 1 2 w^2\,dx`) :: f = (w**2)/2 * dx F = derivative(f, w, v) J = derivative(F, w, u) which is equivalent to :: f = (w**2)/2 * dx F = w*v*dx J = u*v*dx Assume in the following examples that :: v = TestFunction(element) u = TrialFunction(element) w = Coefficient(element) The stiffness matrix can be computed from the functional :math:`\int_\Omega \nabla w : \nabla w \, dx`, by :: f = inner(grad(w), grad(w))/2 * dx F = derivative(f, w, v) J = derivative(F, w, u) which is equivalent to :: f = inner(grad(w), grad(w))/2 * dx F = inner(grad(w), grad(v)) * dx J = inner(grad(u), grad(v)) * dx Note that here the basis functions are provided explicitly, which is sometimes necessary, e.g., if part of the form is linearlized manually as in :: g = Coefficient(element) f = inner(grad(w), grad(w))*dx F = derivative(f, w, v) + dot(w-g,v)*dx J = derivative(F, w, u) Derivatives can also be computed w.r.t. functions in mixed spaces. Consider this example, an implementation of the harmonic map equations using automatic differentiation:: 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)) Here ``L`` is defined as a functional with two coefficient functions ``x`` and ``y`` from separate finite element spaces. However, ``F`` and ``J`` become linear and bilinear forms respectively with basis functions defined on the mixed finite element :: M = X + Y There is a subtle difference between defining ``x`` and ``y`` separately and this alternative implementation (reusing the elements ``X``, ``Y``, ``M``):: 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) The difference is that the forms here have *one* coefficient function ``u`` in the mixed space, and the forms above have *two* coefficient functions ``x`` and ``y``. Combining form transformations ------------------------------ Form transformations can be combined freely. Note that, to do this, derivatives are usually evaluated before applying (e.g.) the action of a form, because ``derivative`` changes the arity of the form:: element = FiniteElement("CG", cell, 1) w = Coefficient(element) f = w**4/4*dx(0) + inner(grad(w), grad(w))*dx(1) F = derivative(f, w) J = derivative(F, w) Ja = action(J, w) Jp = adjoint(J) Jpa = action(Jp, w) g = Coefficient(element) Jnorm = energy_norm(J, g) Form files ========== UFL forms and elements can be collected in a *form file* with the extension *.ufl*. Form compilers will typically execute this file with the global UFL namespace available, and extract forms and elements that are defined after execution. The compilers do not compile all forms and elements that are defined in file, but only those that are "exported". A finite element with the variable name ``element`` is exported by default, as are forms with the names ``M``, ``L``, and ``a``. The default form names are intended for a functional, linear form, and bilinear form respectively. To export multiple forms and elements or use other names, an explicit list with the forms and elements to export can be defined. Simply write :: elements = [V, P, TH] forms = [a, L, F, J, L2, H1] at the end of the file to export the elements and forms held by these variables. ufl-2017.2.0/doc/sphinx/source/manual.rst0000644000231000000010000000036213211220450017217 0ustar chrisdaemon.. title:: User manual =========== User manual =========== .. toctree:: :maxdepth: 1 manual/introduction manual/form_language manual/examples manual/internal_representation manual/algorithms manual/command_line_utils ufl-2017.2.0/doc/sphinx/source/installation.rst0000644000231000000010000000212513211220450020442 0ustar chrisdaemon.. title:: Installation ============ Installation ============ UFL is normally installed as part of an installation of FEniCS. If you are using UFL as part of the FEniCS software suite, it is recommended that you follow the `installation instructions for FEniCS `__. To install UFL itself, read on below for a list of requirements and installation instructions. Requirements and dependencies ============================= UFL requires Python version 2.7 or later and depends on the following Python packages: * NumPy * six These packages will be automatically installed as part of the installation of UFL, if not already present on your system. Installation instructions ========================= To install UFL, download the source code from the `UFL Bitbucket repository `__, and run the following command: .. code-block:: console pip install . To install to a specific location, add the ``--prefix`` flag to the installation command: .. code-block:: console pip install --prefix= . ufl-2017.2.0/doc/sphinx/source/releases.rst0000644000231000000010000000037413211220450017550 0ustar chrisdaemon.. title:: Release notes ============= Release notes ============= .. toctree:: :maxdepth: 2 releases/next releases/v2017.2.0 releases/v2017.1.0.post1 releases/v2017.1.0 releases/v2016.2.0 releases/v2016.1.0 releases/v1.6.0 ufl-2017.2.0/doc/sphinx/source/index.rst0000644000231000000010000000136713211220450017057 0ustar chrisdaemon.. title:: UFL ========================== 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. UFL is part of the FEniCS Project. For more information, visit http://www.fenicsproject.org Documentation ============= .. toctree:: :titlesonly: :maxdepth: 1 installation manual API reference releases [FIXME: These links don't belong here, should go under API reference somehow.] * :ref:`genindex` * :ref:`modindex` ufl-2017.2.0/doc/sphinx/README0000644000231000000010000000113713211220450014571 0ustar chrisdaemon==================== Sphinx documentation ==================== UFL is documented using Sphinx and reStructured text. The documentation is hosted at http://fenics-ufl.readthedocs.org/. The online documentation is automatically updated upon pushes to the UFL master branch. Building the documentation locally ================================== The HTML documentation can be built locally using:: make html In order for changes in the docstring to be propagated to the html pages, ufl must be installed anew. In other words, sphinx reads the docstrings from the installed code, not the source code. ufl-2017.2.0/doc/sphinx/Makefile0000644000231000000010000001577613211220450015367 0ustar chrisdaemon# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext help: @echo "Please use \`make ' 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 " applehelp to make an Apple Help Book" @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 " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of 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/UnifiedFormLanguageUFL.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/UnifiedFormLanguageUFL.qhc" applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." 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." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @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." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 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." coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." ufl-2017.2.0/doc/man/0000755000231000000010000000000013211220450013151 5ustar chrisdaemonufl-2017.2.0/doc/man/man1/0000755000231000000010000000000013211220450014005 5ustar chrisdaemonufl-2017.2.0/doc/man/man1/ufl2py.1.gz0000644000231000000010000000121613211220450015727 0ustar chrisdaemon?WSn0+=+- *pdAHK+E|Gc]6(_;3;;5|ح67|a,*֐OKy3Zq 4B"8 " TSmV$Tʄ U?"O2٦@@mV`+# Ѵƃo[3ˌ97A%*;i6n6Pu\)EUGh3 0,|dL%+;aZ{ec[+uFC礠׵hF:`^hu-ނnc ZKwp ?HQFT,'pb;0P "Xi硛{Ͽ^xh3 QcۀB';ZG4遺 p@/='z+!Nyou;W<ᄤf Wn$i/>~H6IWI.VbLMC˳mB6޺j3 ̫q!-uQZR&k+H+Sn'ƸԪڤ/i@i70"܅Sc*?WxG旟z,p;čQ5K緅~:93gp=j6.,}M^G8(e+P*p;&0e[x~*(h> dY8\Cl쩼}VtjXf_ikރ[gb% gA7hkxP|ocn_ nx#KPrqZArմ$Cb5۶V1BIK3MKIIN#s!?< Qu ` peƖF@?*zt*)ŗol_/ \glieW''}9LNmE鵑E-y G'ԣSx<驓0aZ h1n*k[s=(EnZ!fJk[ yťI"]LM GޫpܤKUe8h}Ҟk S AΒW̉A Q:gI&vJlaE=J$_kJ( nT}ܭ;G"I"po )KmD[4պKl vGR^0+E^ >=Bgjea@o<IlOtVLqM[ bhlOz,|plh߅fn 8Hp] $ y㭪_T-}sH-wJvWP#RKsG7m;xh]t" Kn q&&NAv޽`_F[R0y37'2I kI}k-#%砖eوL//=ms@o~`_^ufl-2017.2.0/doc/man/man1/ufl-analyse.1.gz0000644000231000000010000000145113211220450016727 0ustar chrisdaemonUWTr6}W䩝fڙ6e#S*IQKqǺ(JE"؃s0j6HtW𚱤"}ȘoW\^@+$hcCo.*Xr _@OmX/j.`n{gbpB>9jP1! zCCMv$|$Œzɞ맋M >@kv+ a ?:GS3?WyVbjP`roi2ϘQpeXp2{>,Ah~|zni4׫C[s}3Dc?6àF+FIHN5Q9;am h';0,kTMVT;a)3u{n CluFl|HA˔z+ڑ ̫-=aHnXwwp KhPb;fՑ4G'" #O1m@LdGܝȃumi BJ xU~!-eZhfGKb Wn$!+oizzhȪ fRXe߬i U\TYq*mEDž ߍ53 AB8f`bR 2"p&Fb~Ov=0]US qfnͣTp*[{~iϕ'/R\~Ks/Cʮufl-2017.2.0/doc/man/man1/ufl-version.1.gz0000644000231000000010000000112413211220450016755 0ustar chrisdaemondWSMo@JV[E%lh٥aĿ㦉T27ͼ -\(}r_ -$S|#Oh v|!.!3_sxB~`"t fnPF`mڵà[C TW }:"+(} gtUp$X ԡ5zm q#7xPqEQz pUIAkLyUY;4,ǤZKGTh/`'*TSrb3j3^EF9ݬ͟~ye[#0> $wo8Gojj@n:fãJorcq94 tgR-1\4(}},{Ie٧!L؅)Ϣ`LYCëq!-9>*-)5t ʼntq0ZMM (VF9ĔqV%`w) `.hz+ ;ݶp+݊K(}wYcϕ'o#^TۗhqW+ufl-2017.2.0/.circleci/0000755000231000000010000000000013211220450013464 5ustar chrisdaemonufl-2017.2.0/.circleci/config.yml0000644000231000000010000000121713211220450015455 0ustar chrisdaemonversion: 2 jobs: build: docker: - image: circleci/python:3.6 working_directory: ~/ufl-test steps: - checkout - run: name: Install dependencies # Install with sudo as tests not run as superuser in circleci/python command: sudo pip install flake8 numpy pytest six --upgrade - run: name: Install UFL command: pip install --user . - run: name: Run flake8 tests command: python -m flake8 . - run: name: Run unit tests command: python -m pytest --junitxml=test-output test/ - store_test_results: path: test-output/ufl-2017.2.0/AUTHORS0000644000231000000010000000235013211220450012701 0ustar chrisdaemonAuthors ======= 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 T. T. McRae | Lawrence Mitchell | Johannes Ring | Aslak Bergersen | Chris Richardson | Massimiliano Leoni | Jan Blechta | Graham Markall | Lizao Li | Miklos Homolya | Matthias Liertzer | Maximilian Albert | Corrado Maurini | Jack S. Hale | Tuomas Airaksinen ufl-2017.2.0/release.conf0000644000231000000010000000030413211220450014115 0ustar chrisdaemon# Configuration file for fenics-release PACKAGE="ufl" BRANCH="master" FILES="ChangeLog.rst \ setup.py \ doc/sphinx/source/releases/next.rst \ doc/sphinx/source/releases.rst" ufl-2017.2.0/ChangeLog.rst0000644000231000000010000003735213211220450014224 0ustar chrisdaemonChangelog ========= 2017.2.0 (2017-11-30) --------------------- - Add geometric quantity ``CellDiameter`` defined as a set diameter of the cell, i.e., maximal distance between any two points of the cell; implemented on simplices and quads/hexes - Rename internally used reference quantities ``(Cell|Facet)EdgeVectors`` to ``Reference(Cell|Facet)EdgeVectors`` - Add internally used quantites ``CellVertices``, ``(Cell|Facet)EdgeVectors`` which are physical-coordinates-valued; will be useful for further geometry lowering implementations for quads/hexes - Implement geometry lowering of ``(Min|Max)(Cell|Facet)EdgeLength`` for quads and hexes 2017.1.0.post1 (2017-09-12) --------------------------- - Change PyPI package name to fenics-ufl. 2017.1.0 (2017-05-09) --------------------- - Add the ``DirectionalSobolevSpace`` subclass of ``SobolevSpace``. This allows one to use spaces where elements have varying continuity in different spatial directions. - Add ``sobolev_space`` methods for ``HDiv`` and ``HCurl`` finite elements. - Add ``sobolev_space`` methods for ``TensorProductElement`` and ``EnrichedElement``. The smallest shared Sobolev space will be returned for enriched elements. For the tensor product elements, a ``DirectionalSobolevSpace`` is returned depending on the order of the spaces associated with the component elements. 2016.2.0 (2016-11-30) --------------------- - Add call operator syntax to ``Form`` to replace arguments and coefficients. This makes it easier to e.g. express the norm defined by a bilinear form as a functional. Example usage:: # Equivalent to replace(a, {u: f, v: f}) M = a(f, f) # Equivalent to replace(a, {f:1}) c = a(coefficients={f:1}) - Add call operator syntax to ``Form`` to replace arguments and coefficients:: a(f, g) == replace(a, {u: f, v: g}) a(coefficients={f:1}) == replace(a, {f:1}) - Add ``@`` operator to ``Form``: ``form @ f == action(form, f)`` (python 3.5+ only) - Reduce noise in Mesh str such that ``print(form)`` gets more short and readable - Fix repeated ``split(function)`` for arbitrary nested elements - EnrichedElement: Remove ``+/*`` warning In the distant past, ``A + B => MixedElement([A, B])``. The change that ``A + B => EnrichedElement([A, B])`` was made in ``d622c74`` (22 March 2010). A warning was introduced in ``fcbc5ff`` (26 March 2010) that the meaning of ``+`` had changed, and that users wanting a ``MixedElement`` should use ``*`` instead. People have, presumably, been seeing this warning for 6 1/2 years by now, so it's probably safe to remove. - Rework ``TensorProductElement`` implementation, replaces ``OuterProductElement`` - Rework ``TensorProductCell`` implementation, replaces ``OuterProductCell`` - Remove ``OuterProductVectorElement`` and ``OuterProductTensorElement`` - Add ``FacetElement`` and ``InteriorElement`` - Add ``Hellan-Herrmann-Johnson`` element - Add support for double covariant and contravariant mappings in mixed elements - Support discontinuous Taylor elements on all simplices - Some more performance improvements - Minor bugfixes - Improve Python 3 support - More permissive in integer types accepted some places - Make ufl pass almost all flake8 tests - Add bitbucket pipelines testing - Improve documentation 2016.1.0 (2016-06-23) --------------------- - Add operator A^(i,j) := as_tensor(A, (i,j)) - Updates to old manual for publishing on fenics-ufl.readthedocs.org - Bugfix for ufl files with utf-8 encoding - Bugfix in conditional derivatives to avoid inf/nan values in generated code. This bugfix may break ffc if uflacs is not used, to get around that the old workaround in ufl can be enabled by setting ufl.algorithms.apply_derivatives.CONDITIONAL_WORKAROUND = True at the top of your program. - Allow sum([expressions]) where expressions are nonscalar by defining expr+0==expr - Allow form=0; form -= other; - Deprecate .cell(), .domain(), .element() in favour of .ufl_cell(), .ufl_domain(), .ufl_element(), in multiple classes, to allow closer integration with dolfin. - Remove deprecated properties cell.{d,x,n,volume,circumradius,facet_area}. - Remove ancient form2ufl script - Add new class Mesh to replace Domain - Add new class FunctionSpace(mesh, element) - Make FiniteElement classes take Cell, not Domain. - Large reworking of symbolic geometry pipeline - Implement symbolic Piola mappings 1.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) ------------------ - Unreleased development versions of UFL. ufl-2017.2.0/README.rst0000644000231000000010000000431313211220450013321 0ustar chrisdaemon=========================== 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. UFL is part of the FEniCS Project. For more information, visit http://www.fenicsproject.org Documentation ============= Documentation can be viewed at http://fenics-ufl.readthedocs.org/. .. image:: https://readthedocs.org/projects/fenics-ufl/badge/?version=latest :target: http://fenics.readthedocs.io/projects/ufl/en/latest/?badge=latest :alt: Documentation Status Automated Testing ================= We use Bitbucket Pipelines and Atlassian Bamboo to perform automated testing. .. image:: https://bitbucket-badges.useast.atlassian.io/badge/fenics-project/ufl.svg :target: https://bitbucket.org/fenics-project/ufl/addon/pipelines/home :alt: Pipelines Build Status .. image:: http://fenics-bamboo.simula.no:8085/plugins/servlet/wittified/build-status/UFL-UD :target: http://fenics-bamboo.simula.no:8085/browse/UFL-UD/latest :alt: Bamboo Build Status Code Coverage ============= Code coverage reports can be viewed at https://coveralls.io/bitbucket/fenics-project/ufl. .. image:: https://coveralls.io/repos/bitbucket/fenics-project/ufl/badge.svg?branch=master :target: https://coveralls.io/bitbucket/fenics-project/ufl?branch=master :alt: Coverage Status License ======= This program 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. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . ufl-2017.2.0/setup.cfg0000644000231000000010000000014113211220450013446 0ustar chrisdaemon[flake8] ignore = E501,E226 exclude = .git,__pycache__,doc/sphinx/source/conf.py,build,dist,test ufl-2017.2.0/demo/0000755000231000000010000000000013211220450012555 5ustar chrisdaemonufl-2017.2.0/demo/Mass.ufl0000644000231000000010000000163413211220450014174 0ustar chrisdaemon# 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-2017.2.0/demo/ShouldFail.ufl0000644000231000000010000000033013211220450015313 0ustar chrisdaemon # 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-2017.2.0/demo/MixedElasticity.ufl0000644000231000000010000000300113211220450016360 0ustar chrisdaemon# 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-2017.2.0/demo/RestrictedElement.ufl0000644000231000000010000000266513211220450016720 0ustar chrisdaemon# 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-2017.2.0/demo/EnergyNorm.ufl0000644000231000000010000000165213211220450015356 0ustar chrisdaemon# 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-2017.2.0/demo/FunctionOperators.ufl0000644000231000000010000000170713211220450016756 0ustar chrisdaemon# 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-2017.2.0/demo/NeumannProblem.ufl0000644000231000000010000000200513211220450016204 0ustar chrisdaemon# 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-2017.2.0/demo/ExplicitConvection.ufl0000644000231000000010000000033313211220450017075 0ustar chrisdaemon# # 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-2017.2.0/demo/ProjectionSystem.ufl0000644000231000000010000000023013211220450016601 0ustar chrisdaemon element = FiniteElement("Lagrange", triangle, 1) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) a = u*v*dx L = f*v*dx ufl-2017.2.0/demo/QuadratureElement.ufl0000644000231000000010000000237113211220450016717 0ustar chrisdaemon# 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-2017.2.0/demo/Stiffness.ufl0000644000231000000010000000027313211220450015233 0ustar chrisdaemon# # 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-2017.2.0/demo/PoissonDG.ufl0000644000231000000010000000261513211220450015136 0ustar chrisdaemon# 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-2017.2.0/demo/ConvectionVector.ufl0000644000231000000010000000030013211220450016550 0ustar chrisdaemon# # 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-2017.2.0/demo/_TensorProductElement.ufl0000644000231000000010000000174313211220450017556 0ustar chrisdaemon# 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-2017.2.0/demo/clean.sh0000755000231000000010000000034713211220450014202 0ustar chrisdaemon#!/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-2017.2.0/demo/HornSchunck.ufl0000644000231000000010000000123113211220450015507 0ustar chrisdaemon# # 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-2017.2.0/demo/Constant.ufl0000644000231000000010000000200013211220450015046 0ustar chrisdaemon# 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-2017.2.0/demo/README0000644000231000000010000000030113211220450013427 0ustar chrisdaemonTo 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-2017.2.0/demo/MixedMixedElement.ufl0000644000231000000010000000144713211220450016642 0ustar chrisdaemon# 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-2017.2.0/demo/HyperElasticity1D.ufl0000644000231000000010000000050313211220450016572 0ustar chrisdaemon# # 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-2017.2.0/demo/Heat.ufl0000644000231000000010000000234113211220450014146 0ustar chrisdaemon# 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-2017.2.0/demo/P5tet.ufl0000644000231000000010000000145113211220450014267 0ustar chrisdaemon# 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-2017.2.0/demo/P5tri.ufl0000644000231000000010000000144313211220450014272 0ustar chrisdaemon# 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-2017.2.0/demo/MixedPoisson2.ufl0000644000231000000010000000061113211220450015766 0ustar chrisdaemon# # 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-2017.2.0/demo/L2norm.ufl0000644000231000000010000000021713211220450014436 0ustar chrisdaemon# # Author: Martin Sandve Alnes # Date: 2008-10-03 # element = FiniteElement("Lagrange", triangle, 1) f = Coefficient(element) a = f**2*dx ufl-2017.2.0/demo/SubDomain.ufl0000644000231000000010000000167213211220450015154 0ustar chrisdaemon# 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-2017.2.0/demo/FEEC.ufl0000644000231000000010000000326413211220450013774 0ustar chrisdaemon# 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-2017.2.0/demo/StokesEquation.ufl0000644000231000000010000000233513211220450016246 0ustar chrisdaemon# 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-2017.2.0/demo/Equation.ufl0000644000231000000010000000304513211220450015054 0ustar chrisdaemon# 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-2017.2.0/demo/PoissonSystem.ufl0000644000231000000010000000205613211220450016127 0ustar chrisdaemon# 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-2017.2.0/demo/NavierStokes.ufl0000644000231000000010000000204213211220450015700 0ustar chrisdaemon# 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-2017.2.0/demo/H1norm.ufl0000644000231000000010000000025413211220450014432 0ustar chrisdaemon# # 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-2017.2.0/demo/ConvectionJacobi2.ufl0000644000231000000010000000035413211220450016570 0ustar chrisdaemon# # 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-2017.2.0/demo/HarmonicMap2.ufl0000644000231000000010000000055413211220450015551 0ustar chrisdaemon# # 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-2017.2.0/demo/SubDomains.ufl0000644000231000000010000000204513211220450015332 0ustar chrisdaemon# 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-2017.2.0/demo/TensorWeightedPoisson.ufl0000644000231000000010000000175513211220450017603 0ustar chrisdaemon# 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-2017.2.0/demo/Poisson.ufl0000644000231000000010000000204313211220450014716 0ustar chrisdaemon# -*- coding: utf-8 -*- # 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 Alnæs, 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) a = inner(grad(v), grad(u))*dx(degree=1) L = v*f*dx(degree=2) ufl-2017.2.0/demo/Elasticity.ufl0000644000231000000010000000044213211220450015377 0ustar chrisdaemon# # 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-2017.2.0/demo/MassAD.ufl0000644000231000000010000000034313211220450014375 0ustar chrisdaemon# # 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-2017.2.0/demo/Source.ufl0000644000231000000010000000025013211220450014522 0ustar chrisdaemon# # Author: Martin Sandve Alnes # Date: 2008-10-03 # element = FiniteElement("Lagrange", triangle, 1) v = TestFunction(element) f = Coefficient(element) a = f*v*dx ufl-2017.2.0/demo/VectorLaplaceGradCurl.ufl0000644000231000000010000000256313211220450017443 0ustar chrisdaemon# 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-2017.2.0/demo/MixedPoisson.ufl0000644000231000000010000000233013211220450015704 0ustar chrisdaemon# 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-2017.2.0/demo/NonlinearPoisson.ufl0000644000231000000010000000046213211220450016567 0ustar chrisdaemon# 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-2017.2.0/demo/ConvectionJacobi.ufl0000644000231000000010000000035313211220450016505 0ustar chrisdaemon# # 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-2017.2.0/demo/HyperElasticity.ufl0000644000231000000010000000350413211220450016411 0ustar chrisdaemon# # 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-2017.2.0/demo/Stokes.ufl0000644000231000000010000000217413211220450014541 0ustar chrisdaemon# 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-2017.2.0/demo/StiffnessAD.ufl0000644000231000000010000000066113211220450015441 0ustar chrisdaemon# # 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-2017.2.0/demo/HarmonicMap.ufl0000644000231000000010000000053713211220450015470 0ustar chrisdaemon# # 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-2017.2.0/demo/PowAD.ufl0000644000231000000010000000033213211220450014235 0ustar chrisdaemon# # 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-2017.2.0/test/0000755000231000000010000000000013211220450012610 5ustar chrisdaemonufl-2017.2.0/test/test_indexing.py0000755000231000000010000000372313211220450016036 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- 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() == () # Deprecated interface #assert z.index_dimensions() == {} # Deprecated interface z = Zero((3,)) assert z.ufl_shape == (3,) assert z.ufl_free_indices == () assert z.ufl_index_dimensions == () #assert z.free_indices() == () # Deprecated interface #assert z.index_dimensions() == {} # Deprecated interface 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) # Deprecated interface #assert z.index_dimensions() == {i: 3, j: 5} # Deprecated interface 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-2017.2.0/test/test_pickle.py0000755000231000000010000003276713211220450015512 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- """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-2017.2.0/test/test_analyse_demos.py0000755000231000000010000000150513211220450017050 0ustar chrisdaemon# -*- coding: utf-8 -*- from __future__ import print_function __authors__ = "Martin Sandve Alnæs" __date__ = "2008-09-28 -- 2008-09-28" import os import pytest from ufl.algorithms import load_ufl_file, compute_form_data, validate_form from glob import glob demodir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "demo")) def get_demo_filenames(): filenames = sorted( set(glob(os.path.join(demodir, "*.ufl"))) - set(glob(os.path.join(demodir, "_*.ufl"))) ) return filenames @pytest.mark.parametrize("filename", get_demo_filenames()) def test_demo_files(filename): "Check each form in each file with validate_form." data = load_ufl_file(filename) for form in data.forms: #fd = compute_form_data(form) # TODO: Skip failure examples validate_form(form) ufl-2017.2.0/test/test_elements.py0000755000231000000010000001521613211220450016045 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- # 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)) for p in range(1, 10): for family in ("TDG", "Discontinuous Taylor"): element = FiniteElement(family, interval, p) assert element.value_shape() == () 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-2017.2.0/test/conftest.py0000644000231000000010000000603013211220450015006 0ustar chrisdaemon# -*- coding: utf-8 -*- import pytest import os import glob import ufl from ufl import as_ufl, inner, dx from ufl.algorithms import compute_form_data from ufl.algorithms.formfiles import load_ufl_file 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(value.ufl_free_indices, expected.ufl_free_indices) self.assertEqual(value.ufl_index_dimensions, expected.ufl_index_dimensions) def assertSameIndices(self, expr, free_indices): self.assertEqual(expr.ufl_free_indices, tuple(sorted(i.count() for i in 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() def testspath(): return os.path.abspath(os.path.dirname(__file__)) def filespath(): return os.path.abspath(os.path.join(testspath(), "../demo")) class UFLTestDataBase(object): def __init__(self): self.filespath = filespath() self.cache = {} self.names = glob.glob(os.path.join(self.filespath, "*.ufl")) # Filter out files startign with udnerscore self.names = [n for n in self.names if not os.path.basename(n).startswith('_')] def __len__(self): return len(self.names) def __iter__(self): return self.keys() def __hasitem__(self, name): return names in self.names def keys(self): return iter(self.names) def values(self): return (self[name] for name in self.names) def items(self): return ((name, self[name]) for name in self.names) def __getitem__(self, name): if isinstance(name, int): name = self.names[name] # Cache file reads content = self.cache.get(name) if content is None: content = load_ufl_file(name) self.cache[name] = content return content _db = UFLTestDataBase() def _example_paths(): return _db.values() _all_cells = [ufl.interval, ufl.triangle, ufl.tetrahedron] @pytest.fixture(params=_all_cells) def cell(request): return request.param @pytest.fixture(params=_example_paths()) def example_files(request): return request.param ufl-2017.2.0/test/test_illegal.py0000755000231000000010000000245413211220450015642 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- 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-2017.2.0/test/test_utilities.py0000755000231000000010000001150113211220450016235 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- """ Test internal utility functions. """ 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.utils.stacks 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-2017.2.0/test/test_derivative.py0000755000231000000010000004050613211220450016373 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- from __future__ import print_function __authors__ = "Martin Sandve Alnæs" __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.ufl_domain().ufl_cell() bcell = expected.ufl_domain().ufl_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 testCellDiameter(self): def f(w): return CellDiameter(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, count=0) g = Coefficient(V, count=1) df = Coefficient(V, count=2) dg = Coefficient(V, count=3) u = Coefficient(V, 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, count=0) g = Coefficient(V, count=1) f = Coefficient(V, count=2) u = Coefficient(V, 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, count=0) g = Coefficient(V, count=1) dg = Coefficient(VV, count=2) f = Coefficient(V, count=3) u = Coefficient(V, 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() # 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-2017.2.0/test/pytest.ini0000644000231000000010000000006513211220450014642 0ustar chrisdaemon[pytest] minversion = 2.4 looponfailroots = . ../ufl ufl-2017.2.0/test/test_degree_estimation.py0000755000231000000010000001154613211220450017722 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- __authors__ = "Martin Sandve Alnæs" __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 = TensorProductElement(V1, V1) O2 = TensorProductElement(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-2017.2.0/test/test_domains.py0000755000231000000010000003564613211220450015674 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- """ Tests of domain language and attaching domains to forms. """ import pytest from ufl import * from ufl.domain import as_domain, default_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: D0 = Mesh(cell) D1 = default_domain(cell) D2 = as_domain(cell) assert D0 is not D1 assert D0 is not D2 assert D1 is D2 if 0: print() for D in (D1, D2): print(('id', id(D))) print(('str', str(D))) print(('repr', repr(D))) print() assert D0 != D1 assert D0 != D2 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 = Mesh(cell, ufl_id=2) D3 = Mesh(cell, ufl_id=3) D3b = Mesh(cell, ufl_id=3) 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 = [Mesh(cell, ufl_id=hash(cell.cellname())) for cell in all_cells] domains2 = [Mesh(cell, ufl_id=hash(cell.cellname())) for cell in sorted(all_cells)] sdomains = sorted(domains1, key=lambda D: (D.geometric_dimension(), D.topological_dimension(), D.ufl_cell(), D.ufl_id())) assert sdomains != domains1 assert sdomains == domains2 def test_topdomain_creation(): D = Mesh(interval) assert D.geometric_dimension() == 1 D = Mesh(triangle) assert D.geometric_dimension() == 2 D = Mesh(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.ufl_domains() == (D, ) M = f * dx assert M.ufl_domains() == (D, ) def test_simple_domain_case(): # Creating domain from just cell with label like new dolfin will do D = Mesh(triangle, ufl_id=3) V = FunctionSpace(D, FiniteElement("CG", D.ufl_cell(), 1)) f = Coefficient(V) assert f.ufl_domains() == (D, ) M = f * dx assert M.ufl_domains() == (D, ) def test_creating_domains_with_coordinate_fields(): # FIXME: Rewrite for new approach # Definition of higher order domain, element, coefficient, form # Mesh with P2 representation of coordinates cell = triangle P2 = VectorElement("CG", cell, 2) domain = Mesh(P2) # Piecewise linear function space over quadratic mesh element = FiniteElement("CG", cell, 1) V = FunctionSpace(domain, element) f = Coefficient(V) M = f * dx assert f.ufl_domains() == (domain, ) assert M.ufl_domains() == (domain, ) # Test the gymnastics that dolfin will have to go through domain2 = Mesh(P2, ufl_id=domain.ufl_id()) V2 = FunctionSpace(domain2, eval(repr(V.ufl_element()))) f2 = Coefficient(V2, count=f.count()) assert f == f2 assert domain == domain2 assert V == V2 def test_join_domains(): from ufl.domain import join_domains mesh7 = MockMesh(7) mesh8 = MockMesh(8) triangle3 = Cell("triangle", geometric_dimension=3) xa = VectorElement("CG", triangle, 1) xb = VectorElement("CG", triangle, 1) # Equal domains are joined assert 1 == len(join_domains([Mesh(triangle, ufl_id=7), Mesh(triangle, ufl_id=7)])) assert 1 == len(join_domains([Mesh(triangle, ufl_id=7, cargo=mesh7), Mesh(triangle, ufl_id=7, cargo=mesh7)])) assert 1 == len(join_domains([Mesh(xa, ufl_id=3), Mesh(xa, ufl_id=3)])) # Different domains are not joined assert 2 == len(join_domains([Mesh(triangle), Mesh(triangle)])) assert 2 == len(join_domains([Mesh(triangle, ufl_id=7), Mesh(triangle, ufl_id=8)])) assert 2 == len(join_domains([Mesh(triangle, ufl_id=7), Mesh(quadrilateral, ufl_id=8)])) assert 2 == len(join_domains([Mesh(xa, ufl_id=7), Mesh(xa, ufl_id=8)])) assert 2 == len(join_domains([Mesh(xa), Mesh(xb)])) # Incompatible cells require labeling # self.assertRaises(UFLException, lambda: join_domains([Mesh(triangle), Mesh(triangle3)])) # FIXME: Figure out # self.assertRaises(UFLException, lambda: join_domains([Mesh(triangle), # Mesh(quadrilateral)])) # FIXME: Figure out # Incompatible coordinates require labeling xc = Coefficient(FunctionSpace(Mesh(triangle), VectorElement("CG", triangle, 1))) xd = Coefficient(FunctionSpace(Mesh(triangle), VectorElement("CG", triangle, 1))) with pytest.raises(UFLException): join_domains([Mesh(xc), Mesh(xd)]) # Incompatible data is checked if and only if the domains are the same assert 2 == len(join_domains([Mesh(triangle, ufl_id=7, cargo=mesh7), Mesh(triangle, ufl_id=8, cargo=mesh8)])) assert 2 == len(join_domains([Mesh(triangle, ufl_id=7, cargo=mesh7), Mesh(quadrilateral, ufl_id=8, cargo=mesh8)])) # Geometric dimensions must match with pytest.raises(UFLException): join_domains([Mesh(triangle), Mesh(triangle3)]) with pytest.raises(UFLException): join_domains([Mesh(triangle, ufl_id=7, cargo=mesh7), Mesh(triangle3, ufl_id=8, cargo=mesh8)]) # Cargo and mesh ids must match with pytest.raises(UFLException): Mesh(triangle, ufl_id=7, cargo=mesh8) # Nones are removed assert 2 == len(join_domains([None, Mesh(triangle, ufl_id=3), None, Mesh(triangle, ufl_id=3), None, Mesh(triangle, ufl_id=4)])) assert 2 == len(join_domains([Mesh(triangle, ufl_id=7), None, Mesh(quadrilateral, ufl_id=8)])) assert None not in join_domains([Mesh(triangle3, ufl_id=7), None, Mesh(tetrahedron, ufl_id=8)]) def test_everywhere_integrals_with_backwards_compatibility(): D = Mesh(triangle) V = FunctionSpace(D, FiniteElement("CG", triangle, 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.ufl_element() == itg2.ufl_element() def xtest_mixed_elements_on_overlapping_regions(): # Old sketch, not working # Create domain and both disjoint and overlapping regions cell = tetrahedron D = Mesh(cell, 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.ufl_domain(0) == D assert M.ufl_domain(1) == DD assert M.ufl_domain(2) == DD assert M.ufl_domain(3) == DL # Vector element assert M.ufl_domain(4) == DL assert M.ufl_domain(5) == DL assert M.ufl_domain(6) == DR # assert M.ufl_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(): # Old sketch, not working # Create domains with different celltypes # TODO: Figure out PyDOLFIN integration with Mesh DA = Mesh(tetrahedron, label='DA') DB = Mesh(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.ufl_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.ufl_domain() == DA assert VAL.ufl_domain() == DAL assert VAR.ufl_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 Mesh 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 Mesh 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 = Mesh(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-2017.2.0/test/test_automatic_differentiation.py0000755000231000000010000004702413211220450021453 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- """ 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.corealg.traversal 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) R = Circumradius(cell) h = CellDiameter(cell) f = FacetArea(cell) # s = CellSurfaceArea(cell) mince = MinCellEdgeLength(cell) maxce = MaxCellEdgeLength(cell) minfe = MinFacetEdgeLength(cell) maxfe = MaxFacetEdgeLength(cell) J = Jacobian(cell) detJ = JacobianDeterminant(cell) invJ = JacobianInverse(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, R, h, f, mince, maxce, minfe, maxfe, J, detJ, invJ] 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.ufl_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,)*(len(g.ufl_shape)) 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-2017.2.0/test/test_conditionals.py0000755000231000000010000001074413211220450016720 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- __authors__ = "Martin Sandve Alnæs" __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-2017.2.0/test/test_apply_restrictions.py0000755000231000000010000000345613211220450020171 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- from pytest import raises from ufl import * from ufl.algorithms.apply_restrictions import apply_restrictions, apply_default_restrictions from ufl.algorithms.renumbering 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_default_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-2017.2.0/test/test_str.py0000755000231000000010000000471013211220450015036 0ustar chrisdaemon# -*- coding: utf-8 -*- 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 (shape (2,))" assert str(0*x*x[Index(42)]) == "0 (shape (2,), index labels (42,))" 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_diameter(self): assert str(CellDiameter(triangle)) == "diameter" # 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(): 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(): x, y, z = SpatialCoordinate(tetrahedron) v = as_vector((x, 0, 0)) assert str(v) == ("[%s, 0, 0]" % (x,)) def test_str_list_matrix(): 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(): 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-2017.2.0/test/test_measures.py0000755000231000000010000001457213211220450016061 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- """ 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.ufl_domain() == None assert dx.metadata() == {} # Check that we can create a basic form with default measure one = as_ufl(1) a = one * dx(Mesh(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 = Mesh(cell, ufl_id=9, cargo=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.ufl_cell() == cell assert mydomain.ufl_id() == 9 assert mydomain.ufl_cargo() == mymesh # Define a coefficient for use in tests below V = FunctionSpace(mydomain, FiniteElement("CG", cell, 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.ufl_domain().ufl_id() == mydomain.ufl_id() assert mydx.metadata() == metadata M = f * mydx # Compatibility: dx = Measure("dx") # domain=None, # subdomain_id="everywhere", # metadata=None) assert dx.ufl_domain() == None assert dx.subdomain_id() == "everywhere" # Set subdomain_id to "everywhere", still no domain set dxe = dx() assert dxe.ufl_domain() == None assert dxe.subdomain_id() == "everywhere" # Set subdomain_id to 5, still no domain set dx5 = dx(5) assert dx5.ufl_domain() == None assert dx5.subdomain_id() == 5 # Check that original dx is untouched assert dx.ufl_domain() == None assert dx.subdomain_id() == "everywhere" # Set subdomain_id to (2,3), still no domain set dx23 = dx((2, 3)) assert dx23.ufl_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.ufl_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.ufl_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.ufl_domain() == None assert dsd.ufl_domain() == None assert dSd.ufl_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 # 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.ufl_domains() assert domain.ufl_id() == mydomain.ufl_id() assert domain.ufl_cargo() == mymesh assert Mx.subdomain_data()[mydomain]["cell"] == cell_domains domain, = Ms.ufl_domains() assert domain.ufl_cargo() == mymesh assert Ms.subdomain_data()[mydomain][ "exterior_facet"] == exterior_facet_domains domain, = MS.ufl_domains() assert domain.ufl_cargo() == mymesh assert MS.subdomain_data()[mydomain][ "interior_facet"] == interior_facet_domains # Test joining of these domains in a single form domain, = M.ufl_domains() assert domain.ufl_cargo() == 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-2017.2.0/test/test_expand_indices.py0000755000231000000010000002317313211220450017207 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- __authors__ = "Martin Sandve Alnæs" __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.algorithms.renumbering import renumber_indices 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-2017.2.0/test/test_lhs_rhs.py0000755000231000000010000000317213211220450015671 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- __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-2017.2.0/test/clean.sh0000755000231000000010000000027613211220450014236 0ustar chrisdaemon#!/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-2017.2.0/test/test_diff.py0000755000231000000010000000631213211220450015136 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- __authors__ = "Martin Sandve Alnæs" __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-2017.2.0/test/README0000644000231000000010000000062713211220450013475 0ustar chrisdaemonTo 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-2017.2.0/test/test_new_ad.py0000755000231000000010000001376013211220450015470 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- 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.renumbering 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-2017.2.0/test/mockobjects.py0000644000231000000010000000203213211220450015462 0ustar chrisdaemon# -*- coding: utf-8 -*- 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 Mesh(triangle, ufl_id=self.ufl_id(), cargo=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-2017.2.0/test/test_sobolevspace.py0000755000231000000010000002113313211220450016711 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- __authors__ = "David Ham" __date__ = "2014-03-04" import pytest from ufl import (EnrichedElement, TensorProductElement, FiniteElement, triangle, interval, quadrilateral, HDiv, HCurl) from ufl.sobolevspace import SobolevSpace, DirectionalSobolevSpace from ufl import H2, H1, HDiv, HCurl, L2 # Construct directional Sobolev spaces, with varying smoothness in # spatial coordinates H0dx0dy = DirectionalSobolevSpace((0, 0)) H1dx1dy = DirectionalSobolevSpace((1, 1)) H2dx2dy = DirectionalSobolevSpace((2, 2)) H1dx = DirectionalSobolevSpace((1, 0)) H1dy = DirectionalSobolevSpace((0, 1)) H000 = DirectionalSobolevSpace((0, 0, 0)) H1dz = DirectionalSobolevSpace((0, 0, 1)) H1dh = DirectionalSobolevSpace((1, 1, 0)) H2dhH1dz = DirectionalSobolevSpace((2, 2, 1)) # 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 assert H1 > H2 assert L2 > H1 def test_directional_space_relations(): assert H0dx0dy == L2 assert H1dx1dy == H1 assert H2dx2dy == H2 assert H1dx1dy <= HDiv assert H1dx1dy <= HCurl assert H2dx2dy <= H1dx1dy assert H2dhH1dz < H1 assert H1dz > H2dhH1dz assert H1dh < L2 assert H1dz < L2 assert L2 > H1dx assert L2 > H1dy assert not H1dh <= HDiv assert not H1dh <= HCurl 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), # Tensor product elements: TensorProductElement(FiniteElement("DG", interval, 1), FiniteElement("DG", interval, 1)), TensorProductElement(FiniteElement("DG", interval, 1), FiniteElement("CG", interval, 2)), # Enriched element: EnrichedElement(FiniteElement("DG", triangle, 1), FiniteElement("B", triangle, 3)) ] for l2_element in l2_elements: assert l2_element in L2 assert l2_element in H0dx0dy assert l2_element not in H1 assert l2_element not in H1dx1dy assert l2_element not in HCurl assert l2_element not in HDiv assert l2_element not in H2 assert l2_element not in H2dx2dy 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), # Tensor product elements: TensorProductElement(FiniteElement("CG", interval, 1), FiniteElement("CG", interval, 1)), TensorProductElement(FiniteElement("CG", interval, 2), FiniteElement("CG", interval, 2)), # Enriched elements: EnrichedElement(FiniteElement("CG", triangle, 2), FiniteElement("B", triangle, 3)) ] for h1_element in h1_elements: assert h1_element in H1 assert h1_element in H1dx1dy assert h1_element in HDiv assert h1_element in HCurl assert h1_element in L2 assert h1_element in H0dx0dy assert h1_element not in H2 assert h1_element not in H2dx2dy 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 H2dx2dy assert h2_element in H1 assert h2_element in H1dx1dy assert h2_element in HDiv assert h2_element in HCurl assert h2_element in L2 assert h2_element in H0dx0dy def test_contains_hdiv(): hdiv_elements = [ FiniteElement("RT", triangle, 1), FiniteElement("BDM", triangle, 1), FiniteElement("BDFM", triangle, 2), # HDiv elements: HDiv(TensorProductElement(FiniteElement("DG", triangle, 1), FiniteElement("CG", interval, 2))), HDiv(TensorProductElement(FiniteElement("RT", triangle, 1), FiniteElement("DG", interval, 1))), HDiv(TensorProductElement(FiniteElement("N1curl", triangle, 1), FiniteElement("DG", interval, 1))) ] for hdiv_element in hdiv_elements: assert hdiv_element in HDiv assert hdiv_element in L2 assert hdiv_element in H0dx0dy assert hdiv_element not in H1 assert hdiv_element not in H1dx1dy assert hdiv_element not in HCurl assert hdiv_element not in H2 assert hdiv_element not in H2dx2dy def test_contains_hcurl(): hcurl_elements = [ FiniteElement("N1curl", triangle, 1), FiniteElement("N2curl", triangle, 1), # HCurl elements: HCurl(TensorProductElement(FiniteElement("CG", triangle, 1), FiniteElement("DG", interval, 1))), HCurl(TensorProductElement(FiniteElement("N1curl", triangle, 1), FiniteElement("CG", interval, 1))), HCurl(TensorProductElement(FiniteElement("RT", triangle, 1), FiniteElement("CG", interval, 1))) ] for hcurl_element in hcurl_elements: assert hcurl_element in HCurl assert hcurl_element in L2 assert hcurl_element in H0dx0dy assert hcurl_element not in H1 assert hcurl_element not in H1dx1dy assert hcurl_element not in HDiv assert hcurl_element not in H2 assert hcurl_element not in H2dx2dy def test_enriched_elements_hdiv(): A = FiniteElement("CG", interval, 1) B = FiniteElement("DG", interval, 0) AxB = TensorProductElement(A, B) BxA = TensorProductElement(B, A) C = FiniteElement("RTCF", quadrilateral, 1) D = FiniteElement("DQ", quadrilateral, 0) Q1 = TensorProductElement(C, B) Q2 = TensorProductElement(D, A) hdiv_elements = [ EnrichedElement(HDiv(AxB), HDiv(BxA)), EnrichedElement(HDiv(Q1), HDiv(Q2)) ] for hdiv_element in hdiv_elements: assert hdiv_element in HDiv assert hdiv_element in L2 assert hdiv_element in H0dx0dy assert hdiv_element not in H1 assert hdiv_element not in H1dx1dy assert hdiv_element not in HCurl assert hdiv_element not in H2 assert hdiv_element not in H2dx2dy def test_enriched_elements_hcurl(): A = FiniteElement("CG", interval, 1) B = FiniteElement("DG", interval, 0) AxB = TensorProductElement(A, B) BxA = TensorProductElement(B, A) C = FiniteElement("RTCE", quadrilateral, 1) D = FiniteElement("DQ", quadrilateral, 0) Q1 = TensorProductElement(C, B) Q2 = TensorProductElement(D, A) hcurl_elements = [ EnrichedElement(HCurl(AxB), HCurl(BxA)), EnrichedElement(HCurl(Q1), HCurl(Q2)) ] for hcurl_element in hcurl_elements: assert hcurl_element in HCurl assert hcurl_element in L2 assert hcurl_element in H0dx0dy assert hcurl_element not in H1 assert hcurl_element not in H1dx1dy assert hcurl_element not in HDiv assert hcurl_element not in H2 assert hcurl_element not in H2dx2dy def test_varying_continuity_elements(): P1DG_t = FiniteElement("DG", triangle, 1) P1DG_i = FiniteElement("DG", interval, 1) P1 = FiniteElement("CG", interval, 1) P2 = FiniteElement("CG", interval, 2) P3 = FiniteElement("CG", interval, 3) RT1 = FiniteElement("RT", triangle, 1) ARG = FiniteElement("ARG", triangle, 1) # Tensor product elements P1DGP2 = TensorProductElement(P1DG_t, P2) P1P1DG = TensorProductElement(P1, P1DG_i) P1DGP1 = TensorProductElement(P1DG_i, P1) RT1DG1 = TensorProductElement(RT1, P1DG_i) P2P3 = TensorProductElement(P2, P3) ARGP3 = TensorProductElement(ARG, P3) assert P1DGP2 in H1dz and P1DGP2 in L2 assert P1DGP2 not in H1dh assert P1DGP1 in H1dy and P1DGP2 in L2 assert P1P1DG in H1dx and P1P1DG in L2 assert P1P1DG not in H1dx1dy assert RT1DG1 in H000 and RT1DG1 in L2 assert P2P3 in H1dx1dy and P2P3 in H1 assert ARG in H2dx2dy assert ARGP3 in H2dhH1dz ufl-2017.2.0/test/test_classcoverage.py0000755000231000000010000003633713211220450017061 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- from __future__ import print_function __authors__ = "Martin Sandve Alnæs" __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.ufl_free_indices sh = a.ufl_shape # Compare with provided properties if free_indices is not None: free_indices = [i.count() for i in free_indices] 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) 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 = CellDiameter(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-2017.2.0/test/test_unicode_convert.py0000644000231000000010000000161213211220450017407 0ustar chrisdaemon from six import text_type from ufl.algorithms import compute_form_data from ufl.formatting.ufl2unicode import form2unicode def valid_forms(forms_list): forms = [] form_datas = [] for f in forms_list: fd = None try: fd = compute_form_data(f) except: fd = None if fd is not None: forms.append(f) form_datas.append(fd) return forms, form_datas def test_convert_examples(example_files): # Get example forms that can be analysed forms, form_datas = valid_forms(example_files.forms) if not forms: return # Mainly tests for execution without errors data = [] for form, form_data in zip(forms, form_datas): tmp = form2unicode(form, form_data) data.append(tmp) rendered = u"\n\n".join(data) assert isinstance(rendered, text_type) assert len(rendered) ufl-2017.2.0/test/test_evaluate.py0000755000231000000010000001635213211220450016041 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- __authors__ = "Martin Sandve Alnæs" __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-2017.2.0/test/test_equals.py0000755000231000000010000000346713211220450015530 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- """ 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-2017.2.0/test/test_change_to_reference_frame.py0000755000231000000010000002556213211220450021355 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- """Tests of the change to reference frame algorithm.""" import pytest from ufl import * from ufl.classes import Form, Integral, Expr, ReferenceGrad, ReferenceValue ''' from ufl.classes import ReferenceGrad, JacobianInverse from ufl.algorithms import tree_format, change_to_reference_grad from six.moves import xrange as range from ufl.log import error, warning 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 (Expr, FormArgument, GeometricQuantity, Terminal, ReferenceGrad, Grad, Restricted, ReferenceValue, Jacobian, JacobianInverse, JacobianDeterminant, FacetJacobian, FacetJacobianInverse, FacetJacobianDeterminant, CellFacetJacobian, ReferenceCellEdgeVectors, ReferenceFacetEdgeVectors, FacetNormal, CellNormal, ReferenceNormal, CellVolume, FacetArea, CellOrientation, FacetOrientation, QuadratureWeight, SpatialCoordinate, Indexed, MultiIndex, FixedIndex) from ufl.constantvalue import as_ufl, Identity from ufl.tensoralgebra import Transposed from ufl.tensors import as_tensor, as_vector, as_scalar, ComponentTensor from ufl.operators import sqrt, max_value, min_value, sign 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 from ufl.finiteelement import FiniteElement, EnrichedElement, VectorElement, MixedElement, TensorProductElement, TensorElement, FacetElement, InteriorElement, BrokenElement. ''' def change_integral_to_reference_frame(form, context): if False: # TODO: integral.is_in_reference_frame(): # TODO: Assume reference frame integral is written purely in # reference frame or tramsform integrand here as well? return integrand else: # Change integrand expression to reference frame integrand = change_to_reference_frame(integral.integrand()) # Compute and apply integration scaling factor scale = compute_integrand_scaling_factor(integral.ufl_domain(), integral.integral_type()) return integral.reconstruct(integrand * scale) # TODO: , reference=True) def change_expr_to_reference_frame(expr): expr = ReferenceValue(expr) return expr def change_to_reference_frame(expr): if isinstance(expr, Form): return change_form_to_reference_frame(expr) elif isinstance(expr, Integral): return change_integral_to_reference_frame(expr) elif isinstance(expr, Expr): return change_expr_to_reference_frame(expr) else: error("Invalid type.") def test_change_unmapped_form_arguments_to_reference_frame(): U = FiniteElement("CG", triangle, 1) V = VectorElement("CG", triangle, 1) T = TensorElement("CG", triangle, 1) expr = Coefficient(U) assert change_to_reference_frame(expr) == ReferenceValue(expr) expr = Coefficient(V) assert change_to_reference_frame(expr) == ReferenceValue(expr) expr = Coefficient(T) assert change_to_reference_frame(expr) == ReferenceValue(expr) def test_change_hdiv_form_arguments_to_reference_frame(): V = FiniteElement("RT", triangle, 1) expr = Coefficient(V) assert change_to_reference_frame(expr) == ReferenceValue(expr) def test_change_hcurl_form_arguments_to_reference_frame(): V = FiniteElement("RT", triangle, 1) expr = Coefficient(V) assert change_to_reference_frame(expr) == ReferenceValue(expr) ''' # user input grad(f + g)('+') # change to reference frame -> (K*rgrad(rv(M)*rv(f) + rv(M)*rv(g)))('+') # apply derivatives -> (K*(rv(M)*rgrad(rv(f)) + rv(M)*rgrad(rv(g))))('+') # apply restrictions -> K('+')*(rv(M('+'))*rgrad(rv(f('+'))) + rv(M('+'))*rgrad(rv(g('+')))) # user input grad(f + g)('+') # some derivatives applied before processing (grad(f) + grad(g))('+') # ... replace to get fully defined form arguments here ... # expand compounds # * context options: # - keep {types} without rewriting to lower level types # - preserve div and curl if applied directly to terminal # (ffc context may set this to off) # * output invariants: # - no compound operator types left in expression (simplified language) # - div and curl rewritten in terms of grad (optionally unless applied directly to terminal) -> (grad(f) + grad(g))('+') # change to reference frame # * context options: # - keep {types} without rewriting to lower level types (e.g. JacobianInverse) # (ffc context may initially add all code snippets expressions) # - keep {types} in global frame (e.g. Coefficient) # (ffc context may initially add Coefficient and Argument here to refrain from changing) # - skip integral scaling # (ffc context may turn skipping on to preserve current behaviour) # * output invariants: # - ReferenceValue bound directly to terminals where applicable # - grad replaced by mapping expression of rgrad # - div replaced by mapping expression of rdiv # - curl replaced by mapping expression of rcurl -> as_tensor(IndexSum(K[i,j]*rgrad(as_tensor(rv(M)[k,l]*rv(f)[l], (l,)) + as_tensor(rv(M)[r,s]*rv(g)[s], (s,)))[j], j), (i,))('+') # apply derivatives # * context options: # - N/A? # * output invariants: # - grad,div,curl, bound directly to terminals # - rgrad,rdiv,rcurl bound directly to referencevalue objects (rgrad(global_f) invalid) -> (K*(rv(M)*rgrad(rv(f)) + rv(M)*rgrad(rv(g))))('+') # apply restrictions # * context options: # - N/A? # * output invariants: # - *_restricted bound directly to terminals # - all terminals that must be restricted to make sense are restricted -> K('+')*(rv(M('+'))*rgrad(rv(f('+'))) + rv(M('+'))*rgrad(rv(g('+')))) # final modified terminal structure: t = terminal | restricted(terminal) # choice of terminal r = rval(t) | rgrad(r) # in reference frame: value or n-gradient g = t | grad(g) # in global frame: value or n-gradient v = r | g # value in either frame e = v | cell_avg(v) | facet_avg(v) | at_cell_midpoint(v) | at_facet_midpoint(v) # evaluated at point or averaged over cell entity m = e | indexed(e) # scalar component of ''' def new_analyse_modified_terminal(expr): assert expr._ufl_is_terminal_ or expr._ufl_is_terminal_modifier_type_ m = expr # The outermost expression may index to get a specific scalar value if isinstance(m, Indexed): unindexed, multi_index = m.ufl_operands indices = tuple(int(i) for i in multi_index) else: unindexed = m indices = () # The evaluation mode is one of current point, # a cell entity midpoint, or averaging over a cell entity if unindexed._ufl_is_evaluation_type_: # averages and point evaluations v, = v.ufl_operand evaluation = unindexed.ufl_handler_name else: v = unindexed evaluation = "current_point" # We're either in reference frame or global, checks below ensure we don't mix the two frame = "reference" if v._ufl_is_reference_type_ else "global" # Peel off derivatives (grad^n,div,curl,div(grad),grad(div) etc.) t = v derivatives = [] while t._ufl_is_derivative_type_: # ensure frame consistency assert t._ufl_is_reference_type_ == v._ufl_is_reference_type_ derivatives.append(t._ufl_class_) t, = t.ufl_operands core = t derivatives = tuple(derivatives) # This can be an intermediate step to use derivatives instead of ngrads: num_derivatives = len(derivatives) # If we had a reference type before unwrapping terminal, # there should be a ReferenceValue inside all the derivatives if v._ufl_is_reference_type_: assert isinstance(t, ReferenceValue) t, = t.ufl_operands # At the core we may have a restriction if t._ufl_is_restriction_type_: restriction = t.side() t, = t.ufl_operands else: restriction = "" # And then finally the terminal assert t._ufl_is_terminal_ terminal = t # This will only be correct for derivatives = grad^n gdim = terminal.ufl_domain().geometric_dimension() derivatives_shape = (gdim,)*num_derivatives # Get shapes expr_shape = expr.ufl_shape unindexed_shape = unindexed.ufl_shape core_shape = core.ufl_shape # Split indices core_indices = indices[:len(core_shape)] derivative_indices = indices[len(core_shape):] # Apply paranoid dimension checking assert len(indices) == len(unindexed_shape) assert all(0 <= i for i in indices) assert all(i < j for i, j in zip(indices, unindexed_shape)) assert len(core_indices) == len(core_shape) assert all(0 <= i for i in core_indices) assert all(i < j for i, j in zip(core_indices, core_shape)) assert len(derivative_indices) == len(derivatives_shape) # This will fail for e.g. div(grad(f)) assert all(0 <= i for i in derivative_indices) assert all(i < j for i, j in zip(derivative_indices, derivatives_shape)) # Return values: mt = ModifiedTerminal( # TODO: Use keyword args expr, indices, evaluation, frame, num_derivatives, derivatives, restriction, terminal ) return mt ''' New form preprocessing pipeline: Preferably introduce these changes: 1) Create new FormArgument Expression without element or domain 2) Create new FormArgument Constant without domain 3) Drop replace --> but just applying replace first is fine i) group and join integrals by (domain, type, subdomain_id), ii) process integrands: a) apply_coefficient_completion # replace coefficients to ensure proper elements and domains b) lower_compound_operators # expand_compounds c) change_to_reference_frame # change f->rv(f), m->M*rv(m), grad(f)->K*rgrad(rv(f)), grad(grad(f))->K*rgrad(K*rgrad(rv(f))), grad(expr)->K*rgrad(expr) # if grad(expr)->K*rgrad(expr) should be valid, then rgrad must be applicable to quite generic expressions d) apply_derivatives # one possibility is to add an apply_mapped_derivatives AD algorithm which includes mappings e) apply_geometry_lowering f) apply_restrictions # requiring grad(f)('+') instead of grad(f('+')) would simplify a lot... iii) extract final metadata about elements and coefficient ordering ''' ufl-2017.2.0/test/test_apply_algebra_lowering.py0000755000231000000010000000527113211220450020741 0ustar chrisdaemon# -*- coding: utf-8 -*- import pytest from ufl import * from ufl.compound_expressions import * from ufl.algorithms.renumbering import renumber_indices @pytest.fixture def A0(request): return Coefficient(FiniteElement("CG", interval, 1)) @pytest.fixture def A1(request): return Coefficient(TensorElement("CG", interval, 1)) @pytest.fixture def A2(request): return Coefficient(TensorElement("CG", triangle, 1)) @pytest.fixture def A3(request): return Coefficient(TensorElement("CG", tetrahedron, 1)) @pytest.fixture def A21(request): return Coefficient(TensorElement("CG", triangle, 1, shape=(2, 1))) @pytest.fixture def A31(request): return Coefficient(TensorElement("CG", triangle, 1, shape=(3, 1))) @pytest.fixture def A32(request): return Coefficient(TensorElement("CG", triangle, 1, shape=(3, 2))) def test_determinant0(A0): assert determinant_expr(A0) == A0 def test_determinant1(A1): assert determinant_expr(A1) == A1[0, 0] def test_determinant2(A2): assert determinant_expr(A2) == A2[0, 0]*A2[1, 1] - A2[0, 1]*A2[1, 0] def test_determinant3(A3): assert determinant_expr(A3) == (A3[0, 0]*(A3[1, 1]*A3[2, 2] - A3[1, 2]*A3[2, 1]) + A3[0, 1]*(A3[1, 2]*A3[2, 0] - A3[1, 0]*A3[2, 2]) + A3[0, 2]*(A3[1, 0]*A3[2, 1] - A3[1, 1]*A3[2, 0])) def test_pseudo_determinant21(A21): i = Index() assert renumber_indices(determinant_expr(A21)) == renumber_indices(sqrt(A21[i, 0]*A21[i, 0])) def test_pseudo_determinant31(A31): i = Index() assert renumber_indices(determinant_expr(A31)) == renumber_indices(sqrt((A31[i, 0]*A31[i, 0]))) def test_pseudo_determinant32(A32): i = Index() c = cross_expr(A32[:, 0], A32[:, 1]) assert renumber_indices(determinant_expr(A32)) == renumber_indices(sqrt(c[i]*c[i])) def test_inverse0(A0): expected = 1.0/A0 # stays scalar assert inverse_expr(A0) == renumber_indices(expected) def test_inverse1(A1): expected = as_tensor(((1.0/A1[0, 0],),)) # reshaped into 1x1 tensor assert inverse_expr(A1) == renumber_indices(expected) def xtest_inverse2(A2): expected = todo assert inverse_expr(A2) == renumber_indices(expected) def xtest_inverse3(A3): expected = todo assert inverse_expr(A3) == renumber_indices(expected) def xtest_pseudo_inverse21(A21): expected = todo assert renumber_indices(inverse_expr(A21)) == renumber_indices(expected) def xtest_pseudo_inverse31(A31): expected = todo assert renumber_indices(inverse_expr(A31)) == renumber_indices(expected) def xtest_pseudo_inverse32(A32): expected = todo assert renumber_indices(inverse_expr(A32)) == renumber_indices(expected) ufl-2017.2.0/test/test_literals.py0000755000231000000010000000677613211220450016063 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- __authors__ = "Martin Sandve Alnæs" __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-2017.2.0/test/test_reference_shapes.py0000755000231000000010000000175613211220450017536 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- 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() == (3, 3) S = TensorElement("CG", cell, 1, symmetry=True) assert S.value_shape() == (3, 3) assert S.reference_value_shape() == (6,) M = MixedElement(V, U, W) assert M.value_shape() == (7,) assert M.reference_value_shape() == (5,) ufl-2017.2.0/test/test_apply_function_pullbacks.py0000755000231000000010000002514613211220450021326 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- from __future__ import print_function from pytest import raises from ufl import * from ufl.algorithms.apply_function_pullbacks import apply_function_pullbacks, apply_single_function_pullbacks from ufl.algorithms.renumbering import renumber_indices from ufl.classes import Jacobian, JacobianInverse, JacobianDeterminant, ReferenceValue, CellOrientation def check_single_function_pullback(g, mappings): expected = mappings[g] actual = apply_single_function_pullbacks(g) rexp = renumber_indices(expected) ract = renumber_indices(actual) if not rexp == ract: print() print("In check_single_function_pullback:") print("input:") print(repr(g)) print("expected:") print(str(rexp)) print("actual:") print(str(ract)) print("signatures:") print((expected**2*dx).signature()) print((actual**2*dx).signature()) print() assert ract == rexp def test_apply_single_function_pullbacks_triangle3d(): triangle3d = Cell("triangle", geometric_dimension=3) cell = triangle3d domain = as_domain(cell) U0 = FiniteElement("DG", cell, 0) U = FiniteElement("CG", cell, 1) V = VectorElement("CG", cell, 1) Vd = FiniteElement("RT", cell, 1) Vc = FiniteElement("N1curl", cell, 1) T = TensorElement("CG", cell, 1) S = TensorElement("CG", cell, 1, symmetry=True) Um = U*U Vm = U*V Vdm = V*Vd Vcm = Vd*Vc Tm = Vc*T Sm = T*S Vd0 = Vd*U0 # case from failing ffc demo W = S*T*Vc*Vd*V*U u = Coefficient(U) v = Coefficient(V) vd = Coefficient(Vd) vc = Coefficient(Vc) t = Coefficient(T) s = Coefficient(S) um = Coefficient(Um) vm = Coefficient(Vm) vdm = Coefficient(Vdm) vcm = Coefficient(Vcm) tm = Coefficient(Tm) sm = Coefficient(Sm) vd0m = Coefficient(Vd0) # case from failing ffc demo w = Coefficient(W) ru = ReferenceValue(u) rv = ReferenceValue(v) rvd = ReferenceValue(vd) rvc = ReferenceValue(vc) rt = ReferenceValue(t) rs = ReferenceValue(s) rum = ReferenceValue(um) rvm = ReferenceValue(vm) rvdm = ReferenceValue(vdm) rvcm = ReferenceValue(vcm) rtm = ReferenceValue(tm) rsm = ReferenceValue(sm) rvd0m = ReferenceValue(vd0m) rw = ReferenceValue(w) assert len(w) == 9 + 9 + 3 + 3 + 3 + 1 assert len(rw) == 6 + 9 + 2 + 2 + 3 + 1 assert len(w) == 28 assert len(rw) == 23 assert len(vd0m) == 4 assert len(rvd0m) == 3 # Geometric quantities we need: J = Jacobian(domain) detJ = JacobianDeterminant(domain) Jinv = JacobianInverse(domain) # o = CellOrientation(domain) i, j, k, l = indices(4) # Contravariant H(div) Piola mapping: M_hdiv = ((1.0/detJ) * J) # Not applying cell orientation here # Covariant H(curl) Piola mapping: Jinv.T mappings = { # Simple elements should get a simple representation u: ru, v: rv, vd: as_vector(M_hdiv[i, j]*rvd[j], i), vc: as_vector(Jinv[j, i]*rvc[j], i), t: rt, s: as_tensor([[rs[0], rs[1], rs[2]], [rs[1], rs[3], rs[4]], [rs[2], rs[4], rs[5]]]), # Mixed elements become a bit more complicated um: rum, vm: rvm, vdm: as_vector([ # V rvdm[0], rvdm[1], rvdm[2], # Vd M_hdiv[0, j]*as_vector([rvdm[3], rvdm[4]])[j], M_hdiv[1, j]*as_vector([rvdm[3], rvdm[4]])[j], M_hdiv[2, j]*as_vector([rvdm[3], rvdm[4]])[j], ]), vcm: as_vector([ # Vd M_hdiv[0, j]*as_vector([rvcm[0], rvcm[1]])[j], M_hdiv[1, j]*as_vector([rvcm[0], rvcm[1]])[j], M_hdiv[2, j]*as_vector([rvcm[0], rvcm[1]])[j], # Vc Jinv[i, 0]*as_vector([rvcm[2], rvcm[3]])[i], Jinv[i, 1]*as_vector([rvcm[2], rvcm[3]])[i], Jinv[i, 2]*as_vector([rvcm[2], rvcm[3]])[i], ]), tm: as_vector([ # Vc Jinv[i, 0]*as_vector([rtm[0], rtm[1]])[i], Jinv[i, 1]*as_vector([rtm[0], rtm[1]])[i], Jinv[i, 2]*as_vector([rtm[0], rtm[1]])[i], # T rtm[2], rtm[3], rtm[4], rtm[5], rtm[6], rtm[7], rtm[8], rtm[9], rtm[10], ]), sm: as_vector([ # T rsm[0], rsm[1], rsm[2], rsm[3], rsm[4], rsm[5], rsm[6], rsm[7], rsm[8], # S rsm[9], rsm[10], rsm[11], rsm[10], rsm[12], rsm[13], rsm[11], rsm[13], rsm[14], ]), # Case from failing ffc demo: vd0m: as_vector([ M_hdiv[0, j]*as_vector([rvd0m[0], rvd0m[1]])[j], M_hdiv[1, j]*as_vector([rvd0m[0], rvd0m[1]])[j], M_hdiv[2, j]*as_vector([rvd0m[0], rvd0m[1]])[j], rvd0m[2] ]), # This combines it all: w: as_vector([ # S rw[0], rw[1], rw[2], rw[1], rw[3], rw[4], rw[2], rw[4], rw[5], # T rw[6], rw[7], rw[8], rw[9], rw[10], rw[11], rw[12], rw[13], rw[14], # Vc Jinv[i, 0]*as_vector([rw[15], rw[16]])[i], Jinv[i, 1]*as_vector([rw[15], rw[16]])[i], Jinv[i, 2]*as_vector([rw[15], rw[16]])[i], # Vd M_hdiv[0, j]*as_vector([rw[17], rw[18]])[j], M_hdiv[1, j]*as_vector([rw[17], rw[18]])[j], M_hdiv[2, j]*as_vector([rw[17], rw[18]])[j], # V rw[19], rw[20], rw[21], # U rw[22], ]), } # Check functions of various elements outside a mixed context check_single_function_pullback(u, mappings) check_single_function_pullback(v, mappings) check_single_function_pullback(vd, mappings) check_single_function_pullback(vc, mappings) check_single_function_pullback(t, mappings) check_single_function_pullback(s, mappings) # Check functions of various elements inside a mixed context check_single_function_pullback(um, mappings) check_single_function_pullback(vm, mappings) check_single_function_pullback(vdm, mappings) check_single_function_pullback(vcm, mappings) check_single_function_pullback(tm, mappings) check_single_function_pullback(sm, mappings) # Check the ridiculous mixed element W combining it all check_single_function_pullback(w, mappings) def test_apply_single_function_pullbacks_triangle(): cell = triangle domain = as_domain(cell) U = FiniteElement("CG", cell, 1) V = VectorElement("CG", cell, 1) Vd = FiniteElement("RT", cell, 1) Vc = FiniteElement("N1curl", cell, 1) T = TensorElement("CG", cell, 1) S = TensorElement("CG", cell, 1, symmetry=True) Um = U*U Vm = U*V Vdm = V*Vd Vcm = Vd*Vc Tm = Vc*T Sm = T*S W = S*T*Vc*Vd*V*U u = Coefficient(U) v = Coefficient(V) vd = Coefficient(Vd) vc = Coefficient(Vc) t = Coefficient(T) s = Coefficient(S) um = Coefficient(Um) vm = Coefficient(Vm) vdm = Coefficient(Vdm) vcm = Coefficient(Vcm) tm = Coefficient(Tm) sm = Coefficient(Sm) w = Coefficient(W) ru = ReferenceValue(u) rv = ReferenceValue(v) rvd = ReferenceValue(vd) rvc = ReferenceValue(vc) rt = ReferenceValue(t) rs = ReferenceValue(s) rum = ReferenceValue(um) rvm = ReferenceValue(vm) rvdm = ReferenceValue(vdm) rvcm = ReferenceValue(vcm) rtm = ReferenceValue(tm) rsm = ReferenceValue(sm) rw = ReferenceValue(w) assert len(w) == 4 + 4 + 2 + 2 + 2 + 1 assert len(rw) == 3 + 4 + 2 + 2 + 2 + 1 assert len(w) == 15 assert len(rw) == 14 # Geometric quantities we need: J = Jacobian(domain) detJ = JacobianDeterminant(domain) Jinv = JacobianInverse(domain) i, j, k, l = indices(4) # Contravariant H(div) Piola mapping: M_hdiv = (1.0/detJ) * J # Covariant H(curl) Piola mapping: Jinv.T mappings = { # Simple elements should get a simple representation u: ru, v: rv, vd: as_vector(M_hdiv[i, j]*rvd[j], i), vc: as_vector(Jinv[j, i]*rvc[j], i), t: rt, s: as_tensor([[rs[0], rs[1]], [rs[1], rs[2]]]), # Mixed elements become a bit more complicated um: rum, vm: rvm, vdm: as_vector([ # V rvdm[0], rvdm[1], # Vd M_hdiv[0, j]*as_vector([rvdm[2], rvdm[3]])[j], M_hdiv[1, j]*as_vector([rvdm[2], rvdm[3]])[j], ]), vcm: as_vector([ # Vd M_hdiv[0, j]*as_vector([rvcm[0], rvcm[1]])[j], M_hdiv[1, j]*as_vector([rvcm[0], rvcm[1]])[j], # Vc Jinv[i, 0]*as_vector([rvcm[2], rvcm[3]])[i], Jinv[i, 1]*as_vector([rvcm[2], rvcm[3]])[i], ]), tm: as_vector([ # Vc Jinv[i, 0]*as_vector([rtm[0], rtm[1]])[i], Jinv[i, 1]*as_vector([rtm[0], rtm[1]])[i], # T rtm[2], rtm[3], rtm[4], rtm[5], ]), sm: as_vector([ # T rsm[0], rsm[1], rsm[2], rsm[3], # S rsm[4], rsm[5], rsm[5], rsm[6], ]), # This combines it all: w: as_vector([ # S rw[0], rw[1], rw[1], rw[2], # T rw[3], rw[4], rw[5], rw[6], # Vc Jinv[i, 0]*as_vector([rw[7], rw[8]])[i], Jinv[i, 1]*as_vector([rw[7], rw[8]])[i], # Vd M_hdiv[0, j]*as_vector([rw[9], rw[10]])[j], M_hdiv[1, j]*as_vector([rw[9], rw[10]])[j], # V rw[11], rw[12], # U rw[13], ]), } # Check functions of various elements outside a mixed context check_single_function_pullback(u, mappings) check_single_function_pullback(v, mappings) check_single_function_pullback(vd, mappings) check_single_function_pullback(vc, mappings) check_single_function_pullback(t, mappings) check_single_function_pullback(s, mappings) # Check functions of various elements inside a mixed context check_single_function_pullback(um, mappings) check_single_function_pullback(vm, mappings) check_single_function_pullback(vdm, mappings) check_single_function_pullback(vcm, mappings) check_single_function_pullback(tm, mappings) check_single_function_pullback(sm, mappings) # Check the ridiculous mixed element W combining it all check_single_function_pullback(w, mappings) ufl-2017.2.0/test/test_algorithms.py0000755000231000000010000001015213211220450016374 0ustar chrisdaemon# -*- coding: utf-8 -*- __authors__ = "Martin Sandve Alnæs" __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 (FiniteElement, TestFunction, TrialFunction, triangle, div, grad, Argument, dx, adjoint, Coefficient, FacetNormal, inner, dot, ds) from ufl.algorithms import (extract_arguments, expand_derivatives, expand_indices, extract_elements, extract_unique_elements, extract_coefficients) from ufl.corealg.traversal import (pre_traversal, post_traversal, unique_pre_traversal, unique_post_traversal) # 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.ufl_element().degree() for arg in extract_arguments(a)] assert a_arg_degrees == [2, 1] b = adjoint(a) b_arg_degrees = [arg.ufl_element().degree() for arg in extract_arguments(b)] assert b_arg_degrees == [1, 2] c = adjoint(a, (u2, v2)) c_arg_degrees = [arg.ufl_element().degree() for arg in extract_arguments(c)] assert c_arg_degrees == [1, 2] d = adjoint(b) d_arg_degrees = [arg.ufl_element().degree() for arg in extract_arguments(d)] assert d_arg_degrees == [2, 1] ufl-2017.2.0/test/test_future_division.py0000755000231000000010000000164713211220450017452 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- # 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-2017.2.0/test/test_scratch.py0000755000231000000010000003533113211220450015660 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- """ 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.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 %s" % repr(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.") if not all(isinstance(k, FixedIndex) for k in vcomp): error("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: if wshape != (): error("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) if not all(isinstance(k, FixedIndex) for k in wcomp): error("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,) if len(oprimes) != len(self._v): error("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-2017.2.0/test/test_book_snippets.py0000755000231000000010000003241213211220450017105 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- """ 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 # ... x = SpatialCoordinate(cell) # Original: x = cell.x g = sin(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._ufl_expr_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 = SpatialCoordinate(cell) # Original: 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._ufl_expr_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 = SpatialCoordinate(cell) # Original: 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 = SpatialCoordinate(cell) # Original: 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-2017.2.0/test/test_simplify.py0000755000231000000010000000647313211220450016072 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- 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)) assert Bij2 == Bij ufl-2017.2.0/test/test_grad.py0000755000231000000010000001000313211220450015133 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- """ 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).ufl_free_indices == (i.count(),) assert v.dx(i).ufl_free_indices == (i.count(),) assert t.dx(i).ufl_free_indices == (i.count(),) 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)) self.assertTrue(s.dx(i, j).ufl_free_indices == tuple(sorted([i.count(), j.count()]))) self.assertTrue(v.dx(i, j).ufl_free_indices == tuple(sorted([i.count(), j.count()]))) self.assertTrue(t.dx(i, j).ufl_free_indices == tuple(sorted([i.count(), j.count()]))) 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-2017.2.0/test/test_check_arities.py0000755000231000000010000000124113211220450017017 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- import pytest from ufl import * from ufl.algorithms.compute_form_data import compute_form_data def test_check_arities(): # Code from bitbucket issue #49 cell = tetrahedron D = Mesh(cell) V = FunctionSpace(D, VectorElement("P", cell, 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-2017.2.0/test/test_indices.py0000755000231000000010000001575113211220450015653 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- 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.ufl_free_indices) == 1 assert left.ufl_free_indices[0] == i.count() assert len(right.ufl_free_indices) == 1 assert right.ufl_free_indices[0] == i.count() 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 len(vv.ufl_shape) == 1 assert len(uu.ufl_shape) == 1 assert len(w.ufl_shape) == 1 assert len(ww.ufl_shape) == 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 len(A.ufl_shape) == 2 assert len(B.ufl_shape) == 2 assert len(C.ufl_shape) == 2 assert len(D.ufl_shape) == 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 len(vv.ufl_shape) == 1 assert len(ww.ufl_shape) == 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 len(A.ufl_shape) == 2 assert len(B.ufl_shape) == 2 assert len(C.ufl_shape) == 2 assert len(D.ufl_shape) == 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 len(Cijkl.ufl_shape) == 0 assert set(Cijkl.ufl_free_indices) == {i.count(), j.count(), k.count(), l.count()} # make it a tensor C = as_tensor(Cijkl, (i, j, k, l)) assert len(C.ufl_shape) == 4 self.assertSameIndices(C, ()) # get sub-matrix A = C[:,:, 0, 0] assert len(A.ufl_shape) == 2 self.assertSameIndices(A, ()) A = C[:,:, i, j] assert len(A.ufl_shape) == 2 assert set(A.ufl_free_indices) == {i.count(), j.count()} # 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.ufl_free_indices) == {j.count(), i.count()} 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) assert set(a.ufl_free_indices) == {i.count(), j.count()} 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-2017.2.0/test/test_form.py0000755000231000000010000000711613211220450015174 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- import pytest from ufl import * @pytest.fixture def element(): cell = triangle element = FiniteElement("Lagrange", cell, 1) return element @pytest.fixture def mass(): cell = triangle element = FiniteElement("Lagrange", cell, 1) v = TestFunction(element) u = TrialFunction(element) return u * v * dx @pytest.fixture def stiffness(): cell = triangle element = FiniteElement("Lagrange", cell, 1) v = TestFunction(element) u = TrialFunction(element) return inner(grad(u), grad(v)) * dx @pytest.fixture def convection(): cell = triangle element = VectorElement("Lagrange", cell, 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 element = FiniteElement("Lagrange", cell, 1) f = Coefficient(element) v = TestFunction(element) return f * v * dx @pytest.fixture def boundary_load(): cell = triangle element = FiniteElement("Lagrange", cell, 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 = Mesh(cell) element = FiniteElement("Lagrange", cell, 1) V = FunctionSpace(domain, element) v = TestFunction(V) f = Coefficient(V) x = SpatialCoordinate(domain)[0] assert (x * dx).ufl_domains() == (domain,) assert (v * dx).ufl_domains() == (domain,) assert (f * dx).ufl_domains() == (domain,) assert (x * v * f * dx).ufl_domains() == (domain,) assert (1 * dx(domain)).ufl_domains() == (domain,) def test_form_empty(mass): assert not mass.empty() assert Form([]).empty() def test_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 def test_form_call(): V = FiniteElement("CG", triangle, 1) v = TestFunction(V) u = TrialFunction(V) f = Coefficient(V) g = Coefficient(V) a = g*inner(grad(u), grad(v))*dx M = a(f, f, coefficients={ g: 1 }) assert M == grad(f)**2*dx import sys if sys.version_info.major >= 3 and sys.version_info.minor >= 5: a = u*v*dx M = eval("(a @ f) @ g") assert M == g*f*dx ufl-2017.2.0/test/test_split.py0000755000231000000010000000444013211220450015361 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- from ufl import * __authors__ = "Martin Sandve Alnæs" __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) # Check that shapes of all these functions are correct: assert () == Coefficient(f).ufl_shape assert (d,) == Coefficient(v).ufl_shape assert (d+1,) == Coefficient(w).ufl_shape assert (d, d) == Coefficient(t).ufl_shape assert (d, d) == Coefficient(s).ufl_shape assert (d, d) == Coefficient(r).ufl_shape # sum of value sizes, not accounting for symmetries: assert (3*d*d + 2*d + 2,) == Coefficient(m).ufl_shape # 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 # Mixed elements of non-scalar subelements are flattened v2 = MixedElement(v, v) m2 = MixedElement(t, t) # assert d == 2 # assert (2,2) == Coefficient(v2).ufl_shape assert (d+d,) == Coefficient(v2).ufl_shape assert (2*d*d,) == Coefficient(m2).ufl_shape # Split twice on nested mixed elements gets # the innermost scalar subcomponents t = TestFunction(f*v) assert split(t) == (t[0], as_vector((t[1], t[2]))) assert split(split(t)[1]) == (t[1], t[2]) t = TestFunction(f*(f*v)) assert split(t) == (t[0], as_vector((t[1], t[2], t[3]))) assert split(split(t)[1]) == (t[1], as_vector((t[2], t[3]))) t = TestFunction((v*f)*(f*v)) assert split(t) == (as_vector((t[0], t[1], t[2])), as_vector((t[3], t[4], t[5]))) assert split(split(t)[0]) == (as_vector((t[0], t[1])), t[2]) assert split(split(t)[1]) == (t[3], as_vector((t[4], t[5]))) assert split(split(split(t)[0])[0]) == (t[0], t[1]) assert split(split(split(t)[0])[1]) == (t[2],) assert split(split(split(t)[1])[0]) == (t[3],) assert split(split(split(t)[1])[1]) == (t[4], t[5]) ufl-2017.2.0/test/test_signature.py0000755000231000000010000004146513211220450016237 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- """ Test the computation of form signatures. """ import pytest from ufl import * from ufl.utils.dicts 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._ufl_signature_data_({D1: 0}), D2._ufl_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) h = CellDiameter(cell) r = Circumradius(cell) a = FacetArea(cell) # s = CellSurfaceArea(cell) v = CellVolume(cell) I = Identity(d) ws = (x, n) qs = (h, 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*4*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 = Mesh(cell) d1 = Mesh(cell, ufl_id=1) d2 = Mesh(cell, ufl_id=2) s0 = d0._ufl_signature_data_({d0: 0}) s1 = d1._ufl_signature_data_({d1: 0}) s2 = d2._ufl_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() ufl_ids = [1, 2] cells = [triangle, quadrilateral] domains = [Mesh(cell, ufl_id=ufl_id) for cell in cells for ufl_id in ufl_ids] nreps = 2 num_exprs = 2 def forms(): for rep in range(nreps): for domain in domains: V = FunctionSpace(domain, FiniteElement("CG", domain.ufl_cell(), 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-2017.2.0/test/test_piecewise_checks.py0000755000231000000010000001616413211220450017531 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- """ Test the is_cellwise_constant function on all relevant terminal types. """ import pytest from ufl import * from ufl.classes import * from ufl.checks import is_cellwise_constant def get_domains(): all_cells = [ # vertex, interval, triangle, quadrilateral, tetrahedron, hexahedron, ] return [Mesh(cell) for cell in all_cells] def get_nonlinear(): domains_with_quadratic_coordinates = [] for D in get_domains(): V = VectorElement("CG", D.ufl_cell(), 2) E = Mesh(V) 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.ufl_cell(), 1) E = Mesh(V) 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 = [Mesh(cell) for cell in affine_cells] affine_domains_with_linear_coordinates = [] for D in affine_domains: V = VectorElement("CG", D.ufl_cell(), 1) E = Mesh(V) 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 = [Mesh(cell) for cell in affine_facet_cells] affine_facet_domains_with_linear_coordinates = [] for D in affine_facet_domains: V = VectorElement("CG", D.ufl_cell(), 1) E = Mesh(V) 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 = [Mesh(cell) for cell in nonaffine_cells] nonaffine_domains_with_linear_coordinates = [] for D in nonaffine_domains: V = VectorElement("CG", D.ufl_cell(), 1) E = Mesh(V) 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 = [Mesh(cell) for cell in nonaffine_facet_cells] nonaffine_facet_domains_with_linear_coordinates = [] for D in nonaffine_facet_domains: V = VectorElement("CG", D.ufl_cell(), 1) E = Mesh(V) 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 is_cellwise_constant(e) e = CellDiameter(domains) assert is_cellwise_constant(e) e = Circumradius(domains) assert is_cellwise_constant(e) e = FacetArea(domains) assert is_cellwise_constant(e) e = MinFacetEdgeLength(domains) assert is_cellwise_constant(e) e = MaxFacetEdgeLength(domains) assert is_cellwise_constant(e) def test_coordinates_never_cellwise_constant(domains): e = SpatialCoordinate(domains) assert not is_cellwise_constant(e) e = CellCoordinate(domains) assert not is_cellwise_constant(e) def test_coordinates_never_cellwise_constant_vertex(): # The only exception here: domains = Mesh(Cell("vertex", 3)) assert domains.ufl_cell().cellname() == "vertex" e = SpatialCoordinate(domains) assert is_cellwise_constant(e) e = CellCoordinate(domains) assert is_cellwise_constant(e) def mappings_are_cellwise_constant(domain, test): e = Jacobian(domain) assert is_cellwise_constant(e) == test e = JacobianDeterminant(domain) assert is_cellwise_constant(e) == test e = JacobianInverse(domain) assert is_cellwise_constant(e) == test if domain.topological_dimension() != 1: e = FacetJacobian(domain) assert is_cellwise_constant(e) == test e = FacetJacobianDeterminant(domain) assert is_cellwise_constant(e) == test e = FacetJacobianInverse(domain) assert is_cellwise_constant(e) == 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 is_cellwise_constant(e) == 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 is_cellwise_constant(e) V = FiniteElement("DG", domains_not_linear.ufl_cell(), 0) e = Coefficient(V) assert is_cellwise_constant(e) V = FiniteElement("R", domains_not_linear.ufl_cell(), 0) e = Coefficient(V) assert is_cellwise_constant(e) # This should be true, but that has to wait for a fix of issue #13 # e = TestFunction(V) # assert is_cellwise_constant(e) # V = FiniteElement("R", domains_not_linear.ufl_cell(), 0) # e = TestFunction(V) # assert is_cellwise_constant(e) def test_coefficient_mostly_not_cellwise_constant(domains_not_linear): V = FiniteElement("DG", domains_not_linear.ufl_cell(), 1) e = Coefficient(V) assert not is_cellwise_constant(e) e = TestFunction(V) assert not is_cellwise_constant(e) ufl-2017.2.0/test/test_arithmetic.py0000755000231000000010000000521213211220450016355 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- 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-2017.2.0/test/test_tensoralgebra.py0000755000231000000010000000750513211220450017063 0ustar chrisdaemon# -*- coding: utf-8 -*- """ 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 test_det(self, A): dims = (0, 1) C = det(A) D = sum((-A[i, 0]*A[0, i] if i !=0 else A[i-1, -1]*A[i, 0]) for i in dims) self.assertEqualValues(C, D) def test_cofac(self, A): C = cofac(A) D = as_matrix([[(-A[i,j] if i != j else A[i,j]) for j in (-1,0)] for i in (-1,0)]) self.assertEqualValues(C, D) def xtest_inv(self, A): C = inv(A) detA = sum((-A[i, 0]*A[0, i] if i !=0 else A[i-1, -1]*A[i, 0]) for i in (0,1)) D = as_matrix([[(-A[i,j] if i != j else A[i,j]) for j in (-1,0)] for i in (-1,0)]) / detA # FIXME: Test fails probably due to integer division self.assertEqualValues(C, D) ufl-2017.2.0/test/test_change_to_local.py0000755000231000000010000000473113211220450017332 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- """ 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 from ufl.algorithms.renumbering import renumber_indices def test_change_to_reference_grad(): cell = triangle domain = Mesh(cell) U = FunctionSpace(domain, FiniteElement("CG", cell, 1)) V = FunctionSpace(domain, VectorElement("CG", cell, 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-2017.2.0/test/test_ffcforms.py0000755000231000000010000002247413211220450016042 0ustar chrisdaemon#!/usr/bin/env py.test # -*- coding: utf-8 -*- """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-2017.2.0/ufl/0000755000231000000010000000000013211220450012417 5ustar chrisdaemonufl-2017.2.0/ufl/measure.py0000644000231000000010000005045213211220450014440 0ustar chrisdaemon# -*- coding: utf-8 -*- """The Measure class.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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-2016 # Modified by Massimiliano Leoni, 2016. from six import string_types import numbers from ufl.utils.py23 import as_native_strings from ufl.utils.py23 import as_native_str from ufl.log import error, deprecate from ufl.core.expr import Expr from ufl.checks import is_true_ufl_scalar from ufl.constantvalue import as_ufl from ufl.utils.dicts import EmptyDict from ufl.domain import as_domain, AbstractDomain, extract_domains from ufl.protocols import id_or_none, metadata_equal, metadata_hashdata # Export list for ufl.classes __all_classes__ = as_native_strings(["Measure", "MeasureSum", "MeasureProduct"]) # TODO: Design a class IntegralType(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) ("cutcell", "dC"), # Over a cell with some part cut away (run-time quadrature points) ("interface", "dI"), # Over a facet fragment overlapping with two or more cells (run-time quadrature points) ("overlap", "dO"), # Over a cell fragment overlapping with two or more cells (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) custom_integral_types = ("custom", "cutcell", "interface", "overlap") point_integral_types = ("vertex",) # "point") facet_integral_types = ("exterior_facet", "interior_facet") def register_integral_type(integral_type, measure_name): global integral_type_to_measure_name, measure_name_to_integral_type if measure_name != integral_type_to_measure_name.get(integral_type, measure_name): error("Integral type already added with different measure name!") if integral_type != measure_name_to_integral_type.get(measure_name, integral_type): error("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) if integral_type not in integral_type_to_measure_name: error("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__ = as_native_strings(("_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: an AbstractDomain object (most often a Mesh) 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 AbstractDomain or none self._domain = None if domain is None else as_domain(domain) if not (self._domain is None or isinstance(self._domain, AbstractDomain)): error("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 # if not (self._subdomain_data is None or hasattr(self._subdomain_data, "ufl_id")): # error("Invalid domain data, missing ufl_id() implementation.") # Accept "everywhere", single subdomain, or multiple # subdomains if isinstance(subdomain_id, tuple): for did in subdomain_id: if not isinstance(did, numbers.Integral): error("Invalid subdomain_id %s." % (did,)) else: if not (subdomain_id in ("everywhere",) or isinstance(subdomain_id, numbers.Integral)): error("Invalid subdomain_id %s." % (subdomain_id,)) self._subdomain_id = subdomain_id # Validate compiler options are None or dict if metadata is not None and not isinstance(metadata, dict): error("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 ufl_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.ufl_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, scheme=None, rule=None): """Reconfigure measure with new domain specification or metadata.""" # Deprecation of 'rule' in favour of 'scheme' if rule is not None: deprecate("Measure argument 'rule' has been renamed to 'scheme'.") assert scheme is None or scheme == rule scheme = rule # Let syntax dx() mean integral over everywhere all_args = (subdomain_id, metadata, domain, subdomain_data, degree, scheme) 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, AbstractDomain) or hasattr(subdomain_id, 'ufl_domain')): if domain is not None: error("Ambiguous: setting domain both as keyword argument and first argument.") subdomain_id, domain = "everywhere", as_domain(subdomain_id) # If degree or scheme is set, inject into metadata. This is a # quick fix to enable the dx(..., degree=3) notation. # TODO: Make degree and scheme properties of integrals instead of adding to metadata. if (degree, scheme) != (None, None): metadata = {} if metadata is None else metadata.copy() if degree is not None: metadata["quadrature_degree"] = degree if scheme is not None: metadata["quadrature_rule"] = scheme # 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 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). """ deprecate("Notation dx[meshfunction] is deprecated. Please use dx(subdomain_data=meshfunction) instead.") return self(subdomain_data=data) def __unicode__(self): # Only in python 2 return str(self).decode("utf-8") 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 args = [] args.append(repr(self._integral_type)) if self._subdomain_id is not None: args.append("subdomain_id=%s" % repr(self._subdomain_id)) if self._domain is not None: args.append("domain=%s" % repr(self._domain)) if self._metadata: # Stored as EmptyDict if None args.append("metadata=%s" % repr(self._metadata)) if self._subdomain_data is not None: args.append("subdomain_data=%s" % repr(self._subdomain_data)) r = "%s(%s)" % (type(self).__name__, ', '.join(args)) return as_native_str(r) 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): error("Can only integrate scalar expressions. The integrand is a " "tensor expression with value shape %s and free indices with labels %s." % (integrand.ufl_shape, 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?) if not isinstance(subdomain_id, string_types + (numbers.Integral,)): error("Expecting integer or string domain id.") # If we don't have an integration domain, try to find one in # integrand domain = self.ufl_domain() if domain is None: domains = extract_domains(integrand) if len(domains) == 1: domain, = domains elif len(domains) == 0: error("This integral is missing an integration domain.") else: error("Multiple domains found, making the choice of integration domain ambiguous.") # 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__ = as_native_strings(("_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 __unicode__(self): # Only in python 2 return str(self).decode("utf-8") 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__ = as_native_strings(("_measures",)) def __init__(self, *measures): "Create MeasureProduct from given list of measures." self._measures = measures if len(self._measures) < 2: error("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-2017.2.0/ufl/utils/0000755000231000000010000000000013211220450013557 5ustar chrisdaemonufl-2017.2.0/ufl/utils/counted.py0000644000231000000010000000345213211220450015576 0ustar chrisdaemon# -*- coding: utf-8 -*- "Utilites for types with a global unique counter attached to each object." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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.utils.py23 import as_native_strings 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__ = as_native_strings(("_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-2017.2.0/ufl/utils/derivativetuples.py0000644000231000000010000000630213211220450017531 0ustar chrisdaemon# -*- coding: utf-8 -*- "This module contains a collection of utilities for representing partial derivatives as integer tuples." # Copyright (C) 2013-2016 Martin Sandve Alnæs 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-2017.2.0/ufl/utils/indexflattening.py0000644000231000000010000000302513211220450017314 0ustar chrisdaemon# -*- coding: utf-8 -*- "This module contains a collection of utilities for mapping between multiindices and a flattened index space." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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-2017.2.0/ufl/utils/__init__.py0000644000231000000010000000000013211220450015656 0ustar chrisdaemonufl-2017.2.0/ufl/utils/stacks.py0000644000231000000010000000323113211220450015420 0ustar chrisdaemon# -*- coding: utf-8 -*- "Various utility data structures." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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-2017.2.0/ufl/utils/ufltypedicts.py0000644000231000000010000000335113211220450016652 0ustar chrisdaemon# -*- coding: utf-8 -*- "Various utility data structures." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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-2017.2.0/ufl/utils/sequences.py0000644000231000000010000000452713211220450016134 0ustar chrisdaemon# -*- coding: utf-8 -*- "Various sequence manipulation utilities." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 functools import reduce from six.moves import map, zip from six import string_types import numpy 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, string_types): yield l else: for s in recursive_chain(l): yield s def max_degree(degrees): """Maximum degree for mixture of scalar and tuple degrees.""" # numpy.maximum broadcasts scalar degrees to tuple degrees if # necessary. reduce applies numpy.maximum pairwise. degree = reduce(numpy.maximum, map(numpy.asarray, degrees)) if degree.ndim: degree = tuple(map(int, degree)) # tuple degree else: degree = int(degree) # scalar degree return degree ufl-2017.2.0/ufl/utils/formatting.py0000644000231000000010000000547113211220450016312 0ustar chrisdaemon# -*- coding: utf-8 -*- "Various string formatting utilities." # Copyright (C) 2008-2016 Martin Sandve Alnæs 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 import string_types 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() # noqa: E741 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, string_types): 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-2017.2.0/ufl/utils/dicts.py0000644000231000000010000000425613211220450015246 0ustar chrisdaemon# -*- coding: utf-8 -*- "Various dict manipulation utilities." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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-2017.2.0/ufl/utils/sorting.py0000644000231000000010000000703713211220450015625 0ustar chrisdaemon# -*- coding: utf-8 -*- "Utilites for sorting." # Copyright (C) 2008-2016 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 from six import string_types 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_ufl_id(seq): "Sort a sequence by the item.ufl_id()." return sorted(seq, key=lambda x: x.ufl_id()) 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 def _key(x): return (type(x[0]).__name__, x[0]) return sorted(iteritems(mapping), key=_key) 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, string_types) 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, string_types)) or value is None: 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-2017.2.0/ufl/utils/py23.py0000644000231000000010000000323613211220450014732 0ustar chrisdaemon# -*- coding: utf-8 -*- # Copyright (C) 2016-2016 Martin Sandve Alnæs # # 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 . """Python 2/3 compatibility utilities.""" import six if not six.PY2: unicode = str def as_native_str(s): "Return s as unicode string, decoded using utf-8 if necessary." if isinstance(s, bytes): return s.decode("utf-8") else: return s else: def as_native_str(s): "Return s as bytes string, encoded using utf-8 if necessary." if isinstance(s, unicode): return s.encode("utf-8") else: return s def as_native_strings(stringlist): return [as_native_str(s) for s in stringlist] def as_bytes(s): "Return s if bytes, or encode unicode string to bytes using utf-8." if isinstance(s, unicode): return s.encode("utf-8") else: return s def as_unicode(s): "Return s if unicode string, or decode bytes to unicode string using utf-8." if isinstance(s, bytes): return s.decode("utf-8") else: return s ufl-2017.2.0/ufl/indexed.py0000644000231000000010000001136413211220450014416 0ustar chrisdaemon# -*- coding: utf-8 -*- """This module defines the Indexed class.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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.utils.py23 import as_native_strings from ufl.constantvalue import Zero from ufl.core.expr import Expr, ufl_err_str from ufl.core.ufl_type import ufl_type from ufl.core.operator import Operator from ufl.core.multiindex import Index, FixedIndex, MultiIndex from ufl.index_combination_utils import unique_sorted_indices, merge_unique_indices from ufl.precedence import parstr # --- Indexed expression --- @ufl_type(is_shaping=True, num_ops=2, is_terminal_modifier=True) class Indexed(Operator): __slots__ = as_native_strings(( "ufl_free_indices", "ufl_index_dimensions", )) def __new__(cls, expression, multiindex): if isinstance(expression, Zero): # Zero-simplify indexed Zero objects shape = expression.ufl_shape 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 = (), () return Zero(shape=(), free_indices=fi, index_dimensions=fid) else: return Operator.__new__(cls) 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." % ufl_err_str(expression)) if not isinstance(multiindex, MultiIndex): error("Expecting MultiIndex instance, not %s." % ufl_err_str(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%s\n" % (len(multiindex), len(expression.ufl_shape), ufl_err_str(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 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 __getitem__(self, key): error("Attempting to index with %s, but object is already indexed: %s" % (ufl_err_str(key), ufl_err_str(self))) ufl-2017.2.0/ufl/argument.py0000644000231000000010000001644013211220450014620 0ustar chrisdaemon# -*- coding: utf-8 -*- """This module defines the class Argument and a number of related classes (functions), including TestFunction and TrialFunction.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 Massimiliano Leoni, 2016. import numbers from ufl.utils.py23 import as_native_str from ufl.utils.py23 import as_native_strings from ufl.log import error from ufl.core.ufl_type import ufl_type from ufl.core.terminal import FormArgument from ufl.split_functions import split from ufl.finiteelement import FiniteElementBase from ufl.domain import default_domain from ufl.functionspace import AbstractFunctionSpace, FunctionSpace # Export list for ufl.classes (TODO: not actually classes: drop? these are in ufl.*) __all_classes__ = as_native_strings(["TestFunction", "TrialFunction", "TestFunctions", "TrialFunctions"]) # --- Class representing an argument (basis function) in a form --- @ufl_type() class Argument(FormArgument): """UFL value: Representation of an argument to a form.""" __slots__ = as_native_strings(( "_ufl_function_space", "_ufl_shape", "_number", "_part", "_repr", )) def __init__(self, function_space, number, part=None): FormArgument.__init__(self) if isinstance(function_space, FiniteElementBase): # For legacy support for .ufl files using cells, we map the cell to # the default Mesh element = function_space domain = default_domain(element.cell()) function_space = FunctionSpace(domain, element) elif not isinstance(function_space, AbstractFunctionSpace): error("Expecting a FunctionSpace or FiniteElement.") self._ufl_function_space = function_space self._ufl_shape = function_space.ufl_element().value_shape() if not isinstance(number, numbers.Integral): error("Expecting an int for number, not %s" % (number,)) if part is not None and not isinstance(part, numbers.Integral): error("Expecting None or an int for part, not %s" % (part,)) self._number = number self._part = part self._repr = as_native_str("Argument(%s, %s, %s)" % ( repr(self._ufl_function_space), repr(self._number), repr(self._part))) @property def ufl_shape(self): "Return the associated UFL shape." return self._ufl_shape def ufl_function_space(self): "Get the function space of this Argument." return self._ufl_function_space def ufl_domain(self): "Deprecated, please use .ufl_function_space().ufl_domain() instead." # TODO: deprecate("Argument.ufl_domain() is deprecated, please # use .ufl_function_space().ufl_domain() instead.") return self._ufl_function_space.ufl_domain() def ufl_element(self): "Deprecated, please use .ufl_function_space().ufl_element() instead." # TODO: deprecate("Argument.ufl_domain() is deprecated, please # use .ufl_function_space().ufl_element() instead.") return self._ufl_function_space.ufl_element() def number(self): "Return the Argument number." return self._number def part(self): return self._part 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 ufl_domains(self): "Deprecated, please use .ufl_function_space().ufl_domains() instead." # TODO: deprecate("Argument.ufl_domains() is deprecated, # please use .ufl_function_space().ufl_domains() instead.") return self._ufl_function_space.ufl_domains() def _ufl_signature_data_(self, renumbering): "Signature data for form arguments depend on the global numbering of the form arguments and domains." fsdata = self._ufl_function_space._ufl_signature_data_(renumbering) return ("Argument", self._number, self._part, fsdata) 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._ufl_function_space == other._ufl_function_space) # --- Helper functions for pretty syntax --- def TestFunction(function_space, part=None): """UFL value: Create a test function argument to a form.""" return Argument(function_space, 0, part) def TrialFunction(function_space, part=None): """UFL value: Create a trial function argument to a form.""" return Argument(function_space, 1, part) # --- Helper functions for creating subfunctions on mixed elements --- def Arguments(function_space, 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(function_space, number)) def TestFunctions(function_space): """UFL value: Create a TestFunction in a mixed space, and return a tuple with the function components corresponding to the subelements.""" return Arguments(function_space, 0) def TrialFunctions(function_space): """UFL value: Create a TrialFunction in a mixed space, and return a tuple with the function components corresponding to the subelements.""" return Arguments(function_space, 1) ufl-2017.2.0/ufl/core/0000755000231000000010000000000013211220450013347 5ustar chrisdaemonufl-2017.2.0/ufl/core/operator.py0000644000231000000010000000440113211220450015553 0ustar chrisdaemon# -*- coding: utf-8 -*- # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 Massimiliano Leoni, 2016 from ufl.utils.py23 import as_native_str from ufl.utils.py23 import as_native_strings from ufl.core.expr import Expr from ufl.core.ufl_type import ufl_type # --- Base class for operator objects --- @ufl_type(is_abstract=True, is_terminal=False) class Operator(Expr): "Base class for all operators, i.e. non-terminal expression types." __slots__ = as_native_strings(("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 _ufl_expr_reconstruct_(self, *operands): "Return a new object of the same type with new operands." return self._ufl_class_(*operands) def _ufl_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 __repr__(self): "Default repr string construction for operators." # This should work for most cases r = "%s(%s)" % (self._ufl_class_.__name__, ", ".join(repr(op) for op in self.ufl_operands)) return as_native_str(r) ufl-2017.2.0/ufl/core/compute_expr_hash.py0000644000231000000010000000376513211220450017451 0ustar chrisdaemon# -*- coding: utf-8 -*- """Non-recursive traversal-based hash computation algorithm. Fast iteration over nodes in an ``Expr`` DAG to compute memorized hashes for all unique nodes. """ # Copyright (C) 2015 Martin Sandve Alnæs # # 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 Massimiliano Leoni, 2016 # 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-2017.2.0/ufl/core/expr.py0000644000231000000010000003701513211220450014705 0ustar chrisdaemon# -*- coding: utf-8 -*- """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 ``Expr`` instances 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-2016 Martin Sandve Alnæs # # 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 Massimiliano Leoni, 2016 from six.moves import xrange as range from ufl.utils.py23 import as_native_strings from ufl.log import error # --- The base object for all UFL expression tree nodes --- class Expr(object): """Base class for all UFL expression types. *Instance properties* Every ``Expr`` instance will have certain properties. The most important ones are ``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. Type traits are accessed via a class or instance object of 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__ = as_native_strings(("_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 --- # Each subclass of Expr is checked to have these properties 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", ) # Each subclass of Expr is checked to have these methods in # ufl_type # FIXME: Add more and enable all _ufl_required_methods_ = ( # To compute the hash on demand, this method is called. "_ufl_compute_hash_", # The data returned from this method is used to compute the # signature of a form "_ufl_signature_data_", # The == operator must be implemented to compare for identical # representation, used by set() and dict(). The __hash__ # operator is added by ufl_type. "__eq__", # To reconstruct an object of the same type with operands or # properties changed. "_ufl_expr_reconstruct_", # Implemented in Operator and Terminal so this should never fail "ufl_domains", # "ufl_cell", # "ufl_domain", # "__str__", # "__repr__", # TODO: Add checks for methods/properties of terminals only? # Required for terminals: # "is_cellwise_constant", # TODO: Rename to ufl_is_cellwise_constant? ) # --- 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 the 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 the object counting mechanism. Return 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 --- def _ufl_expr_reconstruct_(self, *operands): "Return a new object of the same type with new operands." raise NotImplementedError(self.__class__._ufl_expr_reconstruct_) # --- Functions for geometric properties of expression --- def ufl_domains(self): # TODO: Deprecate this and use extract_domains(expr) "Return all domains this expression is defined on." from ufl.domain import extract_domains return extract_domains(self) def ufl_domain(self): # TODO: Deprecate this and use extract_unique_domain(expr) "Return the single unique domain this expression is defined on, or throw an error." from ufl.domain import extract_unique_domain return extract_unique_domain(self) # --- 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 Exception: 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)) # --- Special functions for string representations --- # All subclasses must implement _ufl_signature_data_ def _ufl_signature_data_(self, renumbering): "Return data that uniquely identifies form compiler relevant aspects of this object." raise NotImplementedError(self.__class__._ufl_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 __unicode__(self): # Only in python 2 return str(self).decode("utf-8") def _ufl_err_str_(self): "Return a short string to represent this Expr in an error message." return "<%s id=%d>" % (self._ufl_class_.__name__, id(self)) 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) # --- Deprecated functions def geometric_dimension(self): "Return the geometric dimension this expression lives in." from ufl.domain import find_geometric_dimension return find_geometric_dimension(self) # 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) def ufl_err_str(expr): if hasattr(expr, "_ufl_err_str_"): return expr._ufl_err_str_() else: return repr(expr) ufl-2017.2.0/ufl/core/ufl_id.py0000644000231000000010000000431613211220450015167 0ustar chrisdaemon# -*- coding: utf-8 -*- "Utilites for types with a globally counted unique id attached to each object." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 Massimiliano Leoni, 2016 from ufl.utils.py23 import as_native_str def attach_ufl_id(cls): """Equip class with ``.ufl_id()`` and handle bookkeeping. Usage: 1. Apply to class:: @attach_ufl_id class MyClass(object): 2. If ``__slots__`` is defined, include ``_ufl_id`` attribute:: __slots__ = ("_ufl_id",) 3. Add keyword argument to constructor:: def __init__(self, *args, ufl_id=None): 4. Call ``self._init_ufl_id`` with ``ufl_id`` and assign to ``._ufl_id`` attribute:: self._ufl_id = self._init_ufl_id(ufl_id) Result: ``MyClass().ufl_id()`` returns unique value for each constructed object. """ def _get_ufl_id(self): "Return the ufl_id of this object." return self._ufl_id def _init_ufl_id(cls): "Initialize new ufl_id for the object under construction." # Bind cls with closure here def init_ufl_id(self, ufl_id): if ufl_id is None: ufl_id = cls._ufl_global_id cls._ufl_global_id = max(ufl_id, cls._ufl_global_id) + 1 return ufl_id return init_ufl_id # Modify class: if hasattr(cls, "__slots__"): assert as_native_str("_ufl_id") in cls.__slots__ cls._ufl_global_id = 0 cls.ufl_id = _get_ufl_id cls._init_ufl_id = _init_ufl_id(cls) return cls ufl-2017.2.0/ufl/core/__init__.py0000644000231000000010000000000013211220450015446 0ustar chrisdaemonufl-2017.2.0/ufl/core/terminal.py0000644000231000000010000000673613211220450015550 0ustar chrisdaemon# -*- coding: utf-8 -*- """This module defines the ``Terminal`` class, the superclass for all types that are terminal nodes in an expression tree.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 Massimiliano Leoni, 2016 from ufl.log import error, warning 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 _ufl_expr_reconstruct_(self, *operands): "Return self." if operands: error("Terminal has no operands.") return self ufl_operands = () ufl_free_indices = () ufl_index_dimensions = () def ufl_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 Exception: 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 _ufl_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): "An abstract class for a form argument." __slots__ = () def __init__(self): Terminal.__init__(self) ufl-2017.2.0/ufl/core/ufl_type.py0000644000231000000010000003672713211220450015567 0ustar chrisdaemon# -*- coding: utf-8 -*- # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 Massimiliano Leoni, 2016 import six from ufl.core.expr import Expr from ufl.core.compute_expr_hash import compute_expr_hash from ufl.utils.formatting import camel2underscore # Make UFL type coercion available under the as_ufl name # as_ufl = Expr._ufl_coerce_ def attach_operators_from_hash_data(cls): """Class decorator to attach ``__hash__``, ``__eq__`` and ``__ne__`` implementations. These are implemented in terms of a ``._ufl_hash_data()`` method on the class, which should return a tuple or hashable and comparable data. """ assert hasattr(cls, "_ufl_hash_data_") def __hash__(self): "__hash__ implementation attached in attach_operators_from_hash_data" return hash(self._ufl_hash_data_()) cls.__hash__ = __hash__ def __eq__(self, other): "__eq__ implementation attached in attach_operators_from_hash_data" return type(self) == type(other) and self._ufl_hash_data_() == other._ufl_hash_data_() cls.__eq__ = __eq__ def __ne__(self, other): "__ne__ implementation attached in attach_operators_from_hash_data" return type(self) != type(other) or self._ufl_hash_data_() != other._ufl_hash_data_() cls.__ne__ = __ne__ return cls 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): "Determine number of operands for this type." # 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_: cls.ufl_shape = () if cls._ufl_is_scalar_ or cls._ufl_is_index_free_: cls.ufl_free_indices = () cls.ufl_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 cls.ufl_shape = property(_inherited_ufl_shape) if inherit_indices_from_operand is not None: 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) 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 # FIXME: Apply this if everything beomes unicode if 0: cls = six.python_2_unicode_compatible(cls) # 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-2017.2.0/ufl/core/multiindex.py0000644000231000000010000001646513211220450016117 0ustar chrisdaemon# -*- coding: utf-8 -*- """This module defines the single index types and some internal index utilities.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs 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 Massimiliano Leoni, 2016. from six.moves import xrange as range from ufl.utils.py23 import as_native_str from ufl.utils.py23 import as_native_strings from ufl.log import error from ufl.utils.counted import counted_init from ufl.core.ufl_type import ufl_type from ufl.core.terminal import Terminal # Export list for ufl.classes __all_classes__ = as_native_strings(["IndexBase", "FixedIndex", "Index"]) class IndexBase(object): """Base class for all indices.""" __slots__ = () def __init__(self): pass def __unicode__(self): # Only in python 2 return str(self).decode("utf-8") class FixedIndex(IndexBase): """UFL value: An index with a specific value assigned.""" __slots__ = as_native_strings(("_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): r = "FixedIndex(%d)" % self._value return as_native_str(r) class Index(IndexBase): """UFL value: An index with no value assigned. Used to represent free indices in Einstein indexing notation.""" __slots__ = as_native_strings(("_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): r = "Index(%d)" % self._count return as_native_str(r) @ufl_type() class MultiIndex(Terminal): "Represents a sequence of indices, either fixed or free." __slots__ = as_native_strings(("_indices",)) _cache = {} def __getnewargs__(self): return (self._indices,) def __new__(cls, indices): if not isinstance(indices, tuple): error("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) if not all(isinstance(ind, IndexBase) for ind in indices): error("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 tuple of indices." 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): "Evaluate index." # 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): "This shall not be used." error("Multiindex has no shape (it is not a tensor expression).") @property def ufl_free_indices(self): "This shall not be used." error("Multiindex has no free indices (it is not a tensor expression).") @property def ufl_index_dimensions(self): "This shall not be used." error("Multiindex has no free indices (it is not a tensor expression).") def is_cellwise_constant(self): "Always True." return True def ufl_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): r = "MultiIndex(%s)" % repr(self._indices) return as_native_str(r) # --- 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): "Return a ``MultiIndex`` version of *ii*." 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 :math:`n` new Index objects." return tuple(Index() for i in range(n)) ufl-2017.2.0/ufl/operators.py0000644000231000000010000004531313211220450015015 0ustar chrisdaemon# -*- coding: utf-8 -*- """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-2016 Martin Sandve Alnæs 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 # Modified by Massimiliano Leoni, 2016. import operator from six.moves import xrange as range from ufl.log import error, warning 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, \ 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, FacetNormal from ufl.checks import is_cellwise_constant from ufl.domain import extract_domains # --- 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 = [as_ufl(arg) for arg in args] sh = args[0].ufl_shape if not all(sh == x.ufl_shape for x in args): error("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 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 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 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 len(a.ufl_shape) != len(b.ufl_shape) instead? def _partial_inner(a, b): "UFL operator: Take the partial inner product of a and b." ar, br = len(a.ufl_shape), len(b.ufl_shape) 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) 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 if len(ai) != len(bi): error("Contraction must be over the same number of axes.") ash = a.ufl_shape bsh = b.ufl_shape aii = indices(len(a.ufl_shape)) bii = indices(len(b.ufl_shape)) 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] if shape[i] != bsh[j]: error("Shape mismatch in contraction.") s = a[aii]*b[bii] cii = set(cii) ii = tuple(i for i in (aii + bii) if i not in cii) return as_tensor(s, ii) def perp(v): "UFL operator: Take the perp of *v*, i.e. :math:`(-v_1, +v_0)`." v = as_ufl(v) if v.ufl_shape != (2,): error("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) 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 = len(A.ufl_shape) if r == 1: n, = A.ufl_shape elif r == 2: m, n = A.ufl_shape if m != n: error("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 if len(A.ufl_shape) != 2: error("Expecting rank 2 tensor.") m, n = A.ufl_shape if m != n: error("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." 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 is_cellwise_constant(f): return Zero(f.ufl_shape, f.ufl_free_indices, f.ufl_index_dimensions) return dot(grad(f), FacetNormal(f.ufl_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_constant = len(extract_domains(v)) > 0 if is_constant: if n is None: return v('+') - v('-') r = len(v.ufl_shape) 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 if a dolfin.Expression is involved.") # FIXME: Is this right? If v has no domain, 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 "v # has no geometric domains" is equivalent with "v is a spatial # 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." return CellAvg(f) def facet_avg(f): "UFL operator: Take the average of *v* over a facet." 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 expression (left == right) for use with ``conditional``.""" return EQ(left, right) def ne(left, right): """UFL operator: A boolean expression (left != right) for use with ``conditional``.""" return NE(left, right) def le(left, right): """UFL operator: A boolean expression (left <= right) for use with ``conditional``.""" return as_ufl(left) <= as_ufl(right) def ge(left, right): """UFL operator: A boolean expression (left >= right) for use with ``conditional``.""" return as_ufl(left) >= as_ufl(right) def lt(left, right): """UFL operator: A boolean expression (left < right) for use with ``conditional``.""" return as_ufl(left) < as_ufl(right) def gt(left, right): """UFL operator: A boolean expression (left > right) for use with ``conditional``.""" return as_ufl(left) > as_ufl(right) def And(left, right): """UFL operator: A boolean expression (left and right) for use with ``conditional``.""" return AndCondition(left, right) def Or(left, right): """UFL operator: A boolean expression (left or right) for use with ``conditional``.""" return OrCondition(left, right) def Not(condition): """UFL operator: A boolean expression (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 max_value(x, y) def Min(x, y): # TODO: Deprecate this notation? "UFL operator: Take the minimum of *x* and *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 cosine of *f*." return _mathfunction(f, Cos) def sin(f): "UFL operator: Take the sine 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 hyperbolic cosine of *f*." return _mathfunction(f, Cosh) def sinh(f): "UFL operator: Take the hyperbolic sine of *f*." return _mathfunction(f, Sinh) def tanh(f): "UFL operator: Take the hyperbolic tangent of *f*." return _mathfunction(f, Tanh) def acos(f): "UFL operator: Take the inverse cosine of *f*." return _mathfunction(f, Acos) def asin(f): "UFL operator: Take the inverse sine 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 with two the arguments *f1* and *f2*." 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.ufl_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.ufl_element() element = element.extract_component(index)[1] else: try: element = f.ufl_element() except Exception: error("Unable to determine element from %s" % f) # Extract the family and the geometric dimension family = element.family() gdim = element.cell().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-2017.2.0/ufl/conditional.py0000644000231000000010000002500313211220450015274 0ustar chrisdaemon# -*- coding: utf-8 -*- """This module defines classes for conditional expressions.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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.utils.py23 import as_native_strings from ufl.core.expr import ufl_err_str from ufl.core.ufl_type import ufl_type 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 # --- 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.") __nonzero__ = __bool__ @ufl_type(is_abstract=True, num_ops=2) class BinaryCondition(Condition): __slots__ = as_native_strings(('_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 for arg in (left, right): if not isinstance(arg, Condition): error("Expecting a Condition, not %s." % ufl_err_str(arg)) else: # Binary operators acting on non-boolean expressions allow # only scalars if left.ufl_shape != () or right.ufl_shape != (): error("Expecting scalar arguments.") if left.ufl_free_indices != () or right.ufl_free_indices != (): error("Expecting scalar arguments.") def __str__(self): return "%s %s %s" % (parstr(self.ufl_operands[0], self), self._name, parstr(self.ufl_operands[1], self)) # 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,)) if not isinstance(condition, Condition): error("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]),) # --- 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): if not isinstance(condition, Condition): error("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 if tsh != fsh: error("Shape mismatch between conditional branches.") tfi = true_value.ufl_free_indices ffi = false_value.ufl_free_indices if tfi != ffi: error("Free index mismatch between conditional branches.") if isinstance(condition, (EQ, NE)): if not all((condition.ufl_operands[0].ufl_shape == (), condition.ufl_operands[0].ufl_free_indices == (), condition.ufl_operands[1].ufl_shape == (), condition.ufl_operands[1].ufl_free_indices == ())): error("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) # --- 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)) if not (is_true_ufl_scalar(left) and is_true_ufl_scalar(right)): error("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 @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)) if not (is_true_ufl_scalar(left) and is_true_ufl_scalar(right)): error("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 ufl-2017.2.0/ufl/cell.py0000644000231000000010000002713313211220450013716 0ustar chrisdaemon# -*- coding: utf-8 -*- "Types for representing a cell." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 # Modified by Massimiliano Leoni, 2016 from six.moves import reduce from six import string_types import numbers from ufl.utils.py23 import as_native_str from ufl.utils.py23 import as_native_strings from ufl.log import error from ufl.core.ufl_type import attach_operators_from_hash_data # Export list for ufl.classes __all_classes__ = as_native_strings(["AbstractCell", "Cell", "TensorProductCell"]) # --- The most abstract cell class, base class for other cell types class AbstractCell(object): """Representation of an abstract finite element cell with only the dimensions known. """ __slots__ = as_native_strings(("_topological_dimension", "_geometric_dimension")) def __init__(self, topological_dimension, geometric_dimension): # Validate dimensions if not isinstance(geometric_dimension, numbers.Integral): error("Expecting integer geometric_dimension.") if not isinstance(topological_dimension, numbers.Integral): error("Expecting integer topological_dimension.") if topological_dimension > geometric_dimension: error("Topological dimension cannot be larger than geometric dimension.") # Store validated dimensions self._topological_dimension = topological_dimension self._geometric_dimension = geometric_dimension 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 def is_simplex(self): "Return True if this is a simplex cell." raise NotImplementedError("Implement this to allow important checks and optimizations.") def has_simplex_facets(self): "Return True if all the facets of this cell are simplex cells." raise NotImplementedError("Implement this to allow important checks and optimizations.") def __lt__(self, other): "Define an arbitrarily chosen but fixed sort order for all cells." if not isinstance(other, AbstractCell): return NotImplemented # Sort by gdim first, tdim next, then whatever's left # depending on the subclass s = (self.geometric_dimension(), self.topological_dimension()) o = (other.geometric_dimension(), other.topological_dimension()) if s != o: return s < o return self._ufl_hash_data_() < other._ufl_hash_data_() def __unicode__(self): # Only in python 2 return str(self).decode("utf-8") # --- Basic topological properties of known basic cells # Mapping from cell name to number of cell entities of each # topological dimension num_cell_entities = {"vertex": (1,), "interval": (2, 1), "triangle": (3, 3, 1), "quadrilateral": (4, 4, 1), "tetrahedron": (4, 6, 4, 1), "hexahedron": (8, 12, 6, 1)} # Mapping from cell name to topological dimension cellname2dim = dict((k, len(v)-1) for k, v in num_cell_entities.items()) # Mapping from cell name to facet name # Note: This is not generalizable to product elements but it's still # in use a couple of places. cellname2facetname = {"interval": "vertex", "triangle": "interval", "quadrilateral": "interval", "tetrahedron": "triangle", "hexahedron": "quadrilateral"} # --- Basic cell representation classes # @six.python_2_unicode_compatible @attach_operators_from_hash_data class Cell(AbstractCell): "Representation of a named finite element cell with known structure." __slots__ = as_native_strings(("_cellname",)) def __init__(self, cellname, geometric_dimension=None): "Initialize basic cell description." self._cellname = cellname # 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 topological_dimension = len(num_cell_entities[cellname]) - 1 # The geometric dimension defaults to equal the topological # dimension unless overridden for embedded cells if geometric_dimension is None: geometric_dimension = topological_dimension # Initialize and validate dimensions AbstractCell.__init__(self, topological_dimension, geometric_dimension) # --- Overrides of AbstractCell methods --- def reconstruct(self, geometric_dimension=None): if geometric_dimension is None: geometric_dimension = self._geometric_dimension return Cell(self._cellname, geometric_dimension=geometric_dimension) def is_simplex(self): " Return True if this is a simplex cell." return self.num_vertices() == self.topological_dimension() + 1 def has_simplex_facets(self): "Return True if all the facets of this cell are simplex cells." return self.is_simplex() or self.cellname() == "quadrilateral" # --- Specific cell properties --- def cellname(self): "Return the cellname of the cell." return self._cellname def num_vertices(self): "The number of cell vertices." return num_cell_entities[self.cellname()][0] def num_edges(self): "The number of cell edges." return num_cell_entities[self.cellname()][1] def num_facets(self): "The number of cell facets." tdim = self.topological_dimension() return num_cell_entities[self.cellname()][tdim-1] # --- Facet properties --- def num_facet_edges(self): "The number of facet edges." # This is used in geometry.py fn = cellname2facetname[self.cellname()] return num_cell_entities[fn][1] # --- Special functions for proper object behaviour --- def __str__(self): gdim = self.geometric_dimension() tdim = self.topological_dimension() s = self.cellname() if gdim > tdim: s += "%dD" % gdim return s def __repr__(self): # For standard cells, return name of builtin cell object if # possible. This reduces the size of the repr strings for # domains, elements, etc. as well gdim = self.geometric_dimension() tdim = self.topological_dimension() name = self.cellname() if gdim == tdim and name in cellname2dim: r = name else: r = "Cell(%s, %s)" % (repr(name), repr(gdim)) return as_native_str(r) def _ufl_hash_data_(self): return (self._geometric_dimension, self._topological_dimension, self._cellname) # @six.python_2_unicode_compatible @attach_operators_from_hash_data class TensorProductCell(AbstractCell): __slots__ = as_native_strings(("_cells",)) def __init__(self, *cells, **kwargs): keywords = list(kwargs.keys()) if keywords and keywords != ["geometric_dimension"]: raise ValueError( "TensorProductCell got an unexpected keyword argument '%s'" % keywords[0]) self._cells = tuple(as_cell(cell) for cell in cells) tdim = sum([cell.topological_dimension() for cell in self._cells]) if kwargs: gdim = kwargs["geometric_dimension"] else: gdim = sum([cell.geometric_dimension() for cell in self._cells]) AbstractCell.__init__(self, tdim, gdim) def cellname(self): "Return the cellname of the cell." return " * ".join([cell._cellname for cell in self._cells]) def reconstruct(self, geometric_dimension=None): if geometric_dimension is None: geometric_dimension = self._geometric_dimension return TensorProductCell(*(self._cells), geometric_dimension=geometric_dimension) def is_simplex(self): "Return True if this is a simplex cell." if len(self._cells) == 1: return self._cells[0].is_simplex() return False def has_simplex_facets(self): "Return True if all the facets of this cell are simplex cells." if len(self._cells) == 1: return self._cells[0].has_simplex_facets() return False def num_vertices(self): "The number of cell vertices." return reduce(lambda x, y: x * y, [c.num_vertices() for c in self._cells]) def num_edges(self): "The number of cell edges." error("Not defined for TensorProductCell.") def num_facets(self): "The number of cell facets." return sum(c.num_facets() for c in self._cells if c.topological_dimension() > 0) def sub_cells(self): "Return list of cell factors." return self._cells def __str__(self): gdim = self.geometric_dimension() tdim = self.topological_dimension() reprs = ", ".join(repr(c) for c in self._cells) if gdim == tdim: gdimstr = "" else: gdimstr = ", geometric_dimension=%d" % gdim r = "TensorProductCell(%s%s)" % (reprs, gdimstr) return r def __repr__(self): return str(self) def _ufl_hash_data_(self): return tuple(c._ufl_hash_data_() for c in self._cells) + (self._geometric_dimension,) # --- Utility conversion functions # Mapping from topological dimension to reference cell name for # simplices _simplex_dim2cellname = {0: "vertex", 1: "interval", 2: "triangle", 3: "tetrahedron"} # Mapping from topological dimension to reference cell name for # hypercubes _hypercube_dim2cellname = {0: "vertex", 1: "interval", 2: "quadrilateral", 3: "hexahedron"} def simplex(topological_dimension, geometric_dimension=None): "Return a simplex cell of given dimension." return Cell(_simplex_dim2cellname[topological_dimension], geometric_dimension) def hypercube(topological_dimension, geometric_dimension=None): "Return a hypercube cell of given dimension." return Cell(_hypercube_dim2cellname[topological_dimension], geometric_dimension) def as_cell(cell): """Convert any valid object to a Cell or return cell if it is already a Cell. Allows an already valid cell, a known cellname string, or a tuple of cells for a product cell. """ if isinstance(cell, AbstractCell): return cell elif isinstance(cell, string_types): return Cell(cell) elif isinstance(cell, tuple): return TensorProductCell(cell) else: error("Invalid cell %s." % cell) ufl-2017.2.0/ufl/permutation.py0000644000231000000010000001000213211220450015331 0ustar chrisdaemon# -*- coding: utf-8 -*- """This module provides utility functions for computing permutations and generating index lists.""" # Copyright (C) 2008-2016 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 Alnæs 2009-2016 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 i not 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-2017.2.0/ufl/algebra.py0000644000231000000010000002615713211220450014401 0ustar chrisdaemon# -*- coding: utf-8 -*- "Basic algebra operations." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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.utils.py23 import as_native_strings from ufl.core.ufl_type import ufl_type from ufl.core.expr import Expr, ufl_err_str 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 # --- 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) @ufl_type(num_ops=2, binop="__mul__", rbinop="__rmul__") class Product(Operator): """The product of two or more UFL objects.""" __slots__ = as_native_strings(( "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, " "got\n\t%s\nand\n\t%s" % (ufl_err_str(a), ufl_err_str(b))) # 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: if sh != ops[-1].ufl_shape: error("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))) @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)) @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 %s." % ufl_err_str(a)) if not is_true_ufl_scalar(b): error("Cannot raise an expression to a non-scalar power %s." % ufl_err_str(b)) # 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)) @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,)) if not isinstance(a, Expr): error("Expecting Expr instance, not %s." % ufl_err_str(a)) 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),) ufl-2017.2.0/ufl/compound_expressions.py0000644000231000000010000003015013211220450017256 0ustar chrisdaemon# -*- coding: utf-8 -*- """Functions implementing compound expressions as equivalent representations using basic operators.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs 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 from ufl.core.multiindex import indices, Index 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 generic_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_determinant_expr(A): """Compute the pseudo-determinant of A.""" m, n = A.ufl_shape if n == 1: # Special case 1xm for simpler expression i = Index() return sqrt(A[i, 0]*A[i, 0]) elif n == 2 and m == 3: # Special case 2x3 for simpler expression c = cross_expr(A[:, 0], A[:, 1]) i = Index() return sqrt(c[i]*c[i]) else: # Generic formulation based on A.T*A return generic_pseudo_determinant_expr(A) def generic_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 pseudo_inverse_expr(A): """Compute the Penrose-Moore pseudo-inverse of A: (A.T*A)^-1 * A.T.""" m, n = A.ufl_shape if n == 1: # Simpler special case for 1d i, j, k = indices(3) return as_tensor(A[i, j], (j, i)) / (A[k, 0]*A[k, 0]) else: # Generic formulation return generic_pseudo_inverse_expr(A) def determinant_expr(A): "Compute the (pseudo-)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 old_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 determinant_expr_3x3(A): return codeterminant_expr_nxn(A, [0, 1, 2], [0, 1, 2]) def codeterminant_expr_nxn(A, rows, cols): if len(rows) == 2: return _det_2x2(A, rows[0], rows[1], cols[0], cols[1]) codet = 0.0 r = rows[0] subrows = rows[1:] for i, c in enumerate(cols): subcols = cols[i+1:] + cols[:i] codet += A[r, c] * codeterminant_expr_nxn(A, subrows, subcols) return codet 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 if sh[0] != sh[1]: error("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 if sh[0] != sh[1]: error("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 if sh[0] != sh[1]: error("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-2017.2.0/ufl/tensoralgebra.py0000644000231000000010000003473513211220450015635 0ustar chrisdaemon# -*- coding: utf-8 -*- """Compound tensor algebra operations.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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.utils.py23 import as_native_strings from ufl.core.expr import ufl_err_str from ufl.core.ufl_type import ufl_type from ufl.constantvalue import Zero from ufl.algebra import Operator from ufl.precedence import parstr from ufl.sorting import sorted_expr 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,)) if len(A.ufl_shape) != 2: error("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) @ufl_type(num_ops=2) class Outer(CompoundTensorOperator): __slots__ = as_native_strings(("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)) @ufl_type(num_ops=2) class Inner(CompoundTensorOperator): __slots__ = as_native_strings(("ufl_free_indices", "ufl_index_dimensions")) def __new__(cls, a, b): # Checks ash, bsh = a.ufl_shape, b.ufl_shape if ash != bsh: error("Shapes do not match: %s and %s." % (ufl_err_str(a), ufl_err_str(b))) # 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)) @ufl_type(num_ops=2) class Dot(CompoundTensorOperator): __slots__ = as_native_strings(("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 if not ((ar >= 1 and br >= 1) or scalar): error("Dot product requires non-scalar arguments, " "got arguments with ranks %d and %d." % (ar, br)) if not (scalar or ash[-1] == bsh[0]): error("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)) @ufl_type(num_ops=2) class Cross(CompoundTensorOperator): __slots__ = as_native_strings(("ufl_free_indices", "ufl_index_dimensions")) def __new__(cls, a, b): ash = a.ufl_shape bsh = b.ufl_shape # Checks if not (len(ash) == 1 and ash == bsh): error("Cross product requires arguments of rank 1, got %s and %s." % ( ufl_err_str(a), ufl_err_str(b))) # 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)) @ufl_type(num_ops=1, inherit_indices_from_operand=0) class Trace(CompoundTensorOperator): __slots__ = () def __new__(cls, A): # Checks if len(A.ufl_shape) != 2: error("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] @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 if r not in (0, 2): error("Determinant of tensor with rank != 2 is undefined.") if r == 2 and sh[0] != sh[1]: error("Cannot take determinant of rectangular rank 2 tensor.") if Afi: error("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] # 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." % (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) @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 if len(sh) != 2: error("Cofactor of tensor with rank != 2 is undefined.") if sh[0] != sh[1]: error("Cannot take cofactor of rectangular matrix with dimensions %s." % (sh,)) if A.ufl_free_indices: error("Not expecting free indices in Cofactor.") if isinstance(A, Zero): error("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] @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 if len(sh) != 2: error("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." % (sh,)) if A.ufl_free_indices: error("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] @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 if len(sh) != 2: error("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." % (sh,)) if Afi: error("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] @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 if len(sh) != 2: error("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." % (sh,)) if Afi: error("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] ufl-2017.2.0/ufl/objects.py0000644000231000000010000000323613211220450014426 0ustar chrisdaemon# -*- coding: utf-8 -*- "Utility objects for pretty syntax in user code." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 from ufl.measure import integral_type_to_measure_name # Default indices i, j, k, l = indices(4) # noqa: E741 p, q, r, s = indices(4) for integral_type, measure_name in integral_type_to_measure_name.items(): globals()[measure_name] = Measure(integral_type) # TODO: Firedrake hack, remove later ds_tb = ds_b + ds_t # noqa: F821 # Default measure dX including both uncut and cut cells dX = dx + dC # noqa: F821 # Create objects for builtin known 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-2017.2.0/ufl/exproperators.py0000644000231000000010000003425613211220450015720 0ustar chrisdaemon# -*- coding: utf-8 -*- """This module attaches special functions to Expr. This way we avoid circular dependencies between e.g. Sum and its superclass Expr.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 Massimiliano Leoni, 2016. from itertools import chain import numbers from ufl.log import error from ufl.utils.stacks import StackDict from ufl.core.expr import Expr 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 from ufl.exprequals import expr_equals # --- Boolean operators --- from ufl.conditional import 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__... 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 # Python operators 'and'/'or' cannot be overloaded, and bitwise # operators &/| don't have the right precedence levels # Expr.__and__ = _and # Expr.__or__ = _or def _as_tensor(self, indices): "UFL operator: A^indices := as_tensor(A, indices)." if not isinstance(indices, tuple): error("Expecting a tuple of Index objects to A^indices := as_tensor(A, indices).") if not all(isinstance(i, Index) for i in indices): error("Expecting a tuple of Index objects to A^indices := as_tensor(A, indices).") return as_tensor(self, indices) Expr.__xor__ = _as_tensor # --- 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(len(b.ufl_shape)) p = Product(a, b[ti]) elif r1 == 2 and r2 in (1, 2): # Matrix-matrix or matrix-vector if ri: error("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(len(a.ufl_shape) - 1) bi = indices(len(b.ufl_shape) - 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, numbers.Real, numbers.Integral) 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 if isinstance(o, numbers.Number) and o == 0: # Allow adding scalar int 0 as a no-op, even for shaped self, # needed for sum([a,b]) return self 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 '%s' 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 ("+", "-"): if mapping is not None: error("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): """Transpose a rank-2 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 if indexlist is not pre: error("Found duplicate ellipsis.") indexlist = post else: # Convert index to a proper type if isinstance(i, numbers.Integral): 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: %s" % (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 *ii*." d = self # Unwrap ii to allow .dx(i,j) and .dx((i,j)) if len(ii) == 1 and isinstance(ii[0], tuple): ii = ii[0] # 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-2017.2.0/ufl/differentiation.py0000644000231000000010000003320213211220450016143 0ustar chrisdaemon# -*- coding: utf-8 -*- "Differential operators." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 error from ufl.utils.py23 import as_native_strings from ufl.core.expr import Expr from ufl.core.terminal import Terminal from ufl.core.operator import Operator from ufl.core.ufl_type import ufl_type from ufl.exprcontainers import ExprList, ExprMapping from ufl.constantvalue import Zero from ufl.coefficient import Coefficient from ufl.variable import Variable from ufl.precedence import parstr from ufl.domain import find_geometric_dimension from ufl.checks import is_cellwise_constant # --- 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): if not isinstance(coefficients, ExprList): error("Expecting ExprList instance with Coefficients.") if not isinstance(arguments, ExprList): error("Expecting ExprList instance with Arguments.") if not isinstance(coefficient_derivatives, ExprMapping): error("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]) @ufl_type(num_ops=2) class VariableDerivative(Derivative): __slots__ = as_native_strings(( "ufl_shape", "ufl_free_indices", "ufl_index_dimensions", )) def __new__(cls, f, v): # Checks if not isinstance(f, Expr): error("Expecting an Expr in VariableDerivative.") if not isinstance(v, (Variable, Coefficient)): error("Expecting a Variable in VariableDerivative.") if v.ufl_free_indices: error("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)) # --- 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__ = as_native_strings(("_dim",)) def __new__(cls, f): # Return zero if expression is trivially constant if is_cellwise_constant(f): dim = find_geometric_dimension(f) 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 = find_geometric_dimension(f) def _ufl_expr_reconstruct_(self, op): "Return a new object of the same type with new operands." if is_cellwise_constant(op): if op.ufl_shape != self.ufl_operands[0].ufl_shape: error("Operand shape mismatch in Grad reconstruct.") if self.ufl_operands[0].ufl_free_indices != op.ufl_free_indices: error("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." 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] @ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True, is_in_reference_frame=True) class ReferenceGrad(CompoundDerivative): __slots__ = as_native_strings(("_dim",)) def __new__(cls, f): # Return zero if expression is trivially constant if is_cellwise_constant(f): dim = f.ufl_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,)) self._dim = f.ufl_domain().topological_dimension() def _ufl_expr_reconstruct_(self, op): "Return a new object of the same type with new operands." if is_cellwise_constant(op): if op.ufl_shape != self.ufl_operands[0].ufl_shape: error("Operand shape mismatch in ReferenceGrad reconstruct.") if self.ufl_operands[0].ufl_free_indices != op.ufl_free_indices: error("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." 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] @ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True) class Div(CompoundDerivative): __slots__ = () def __new__(cls, f): if f.ufl_free_indices: error("Free indices in the divergence argument is not allowed.") # Return zero if expression is trivially constant if is_cellwise_constant(f): 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] @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): if f.ufl_free_indices: error("Free indices in the divergence argument is not allowed.") # Return zero if expression is trivially constant if is_cellwise_constant(f): 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] @ufl_type(num_ops=1, inherit_indices_from_operand=0) class NablaGrad(CompoundDerivative): __slots__ = as_native_strings(("_dim",)) def __new__(cls, f): # Return zero if expression is trivially constant if is_cellwise_constant(f): dim = find_geometric_dimension(f) 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 = find_geometric_dimension(f) def _ufl_expr_reconstruct_(self, op): "Return a new object of the same type with new operands." if is_cellwise_constant(op): if op.ufl_shape != self.ufl_operands[0].ufl_shape: error("Operand shape mismatch in NablaGrad reconstruct.") if self.ufl_operands[0].ufl_free_indices != op.ufl_free_indices: error("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] @ufl_type(num_ops=1, inherit_indices_from_operand=0) class NablaDiv(CompoundDerivative): __slots__ = () def __new__(cls, f): if f.ufl_free_indices: error("Free indices in the divergence argument is not allowed.") # Return zero if expression is trivially constant if is_cellwise_constant(f): 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] _curl_shapes = {(): (2,), (2,): (), (3,): (3,)} @ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True) class Curl(CompoundDerivative): __slots__ = as_native_strings(("ufl_shape",)) def __new__(cls, f): # Validate input sh = f.ufl_shape if f.ufl_shape not in ((), (2,), (3,)): error("Expecting a scalar, 2D vector or 3D vector.") if f.ufl_free_indices: error("Free indices in the curl argument is not allowed.") # Return zero if expression is trivially constant if is_cellwise_constant(f): 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] @ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True, is_in_reference_frame=True) class ReferenceCurl(CompoundDerivative): __slots__ = as_native_strings(("ufl_shape",)) def __new__(cls, f): # Validate input sh = f.ufl_shape if f.ufl_shape not in ((), (2,), (3,)): error("Expecting a scalar, 2D vector or 3D vector.") if f.ufl_free_indices: error("Free indices in the curl argument is not allowed.") # Return zero if expression is trivially constant if is_cellwise_constant(f): 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] ufl-2017.2.0/ufl/log.py0000644000231000000010000001757513211220450013571 0ustar chrisdaemon# -*- coding: utf-8 -*- """This module provides functions used by the UFL implementation to output messages. These may be redirected by the user of UFL.""" # Copyright (C) 2005-2016 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. # Modified by Massimiliano Leoni, 2016 import sys import types import logging from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL # noqa: F401 from ufl.utils.py23 import as_native_strings 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__ = as_native_strings( log_functions + ["Logger", "log_functions"] + ["DEBUG", "INFO", "DEPRECATE", "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 if the terminal supports it (disabled e.g. when piped to # file) if sys.stdout.isatty() and sys.stderr.isatty(): RED = "\033[1;37;31m%s\033[0m" BLUE = "\033[1;37;34m%s\033[0m" GREEN = "\033[1;37;32m%s\033[0m" else: RED = "%s" BLUE = "%s" GREEN = "%s" # 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): "Add a log file." if filename is None: filename = "%s.log" % self._name if filename in self._logfiles: self.warning("Adding 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): "Gets the handler to the file identified by the given file name." 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._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 one, 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) 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 --- class UFLException(Exception): "Base class for UFL exceptions." pass class UFLValueError(UFLException): "Value type error." pass ufl_logger = Logger("UFL", UFLException) for foo in log_functions: exec("%s = ufl_logger.%s" % (foo, foo)) set_level(DEPRECATE) # noqa ufl-2017.2.0/ufl/exprcontainers.py0000644000231000000010000000732613211220450016045 0ustar chrisdaemon# -*- coding: utf-8 -*- """This module defines special types for representing mapping of expressions to expressions.""" # Copyright (C) 2014 Martin Sandve Alnæs # # 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.utils.py23 import as_native_str from ufl.log import error 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): r = "ExprList(*%s)" % repr(self.ufl_operands) return as_native_str(r) @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 ufl_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.ufl_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(*%s)" % repr(self.ufl_operands) def __repr__(self): r = "ExprMapping(*%s)" % repr(self.ufl_operands) return as_native_str(r) @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-2017.2.0/ufl/classes.py0000644000231000000010000001065213211220450014432 0ustar chrisdaemon# -*- coding: utf-8 -*- # flake8: noqa """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-2016 Martin Sandve Alnæs # # 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__ = [] from ufl.utils.py23 import as_native_strings # 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 # 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()) # Semi-automated imports of non-expr classes: def populate_namespace_with_module_classes(mod, loc): """Export the classes that submodules list in __all_classes__.""" names = mod.__all_classes__ for name in names: loc[name] = getattr(mod, name) return names import ufl.cell # noqa E401 __all__ += populate_namespace_with_module_classes(ufl.cell, locals()) import ufl.finiteelement # noqa E401 __all__ += populate_namespace_with_module_classes(ufl.finiteelement, locals()) import ufl.domain # noqa E401 __all__ += populate_namespace_with_module_classes(ufl.domain, locals()) import ufl.functionspace # noqa E401 __all__ += populate_namespace_with_module_classes(ufl.functionspace, locals()) import ufl.core.multiindex # noqa E401 __all__ += populate_namespace_with_module_classes(ufl.core.multiindex, locals()) import ufl.argument # noqa E401 __all__ += populate_namespace_with_module_classes(ufl.argument, locals()) import ufl.measure # noqa E401 __all__ += populate_namespace_with_module_classes(ufl.measure, locals()) import ufl.integral # noqa E401 __all__ += populate_namespace_with_module_classes(ufl.integral, locals()) import ufl.form # noqa E401 __all__ += populate_namespace_with_module_classes(ufl.form, locals()) import ufl.equation # noqa E401 __all__ += populate_namespace_with_module_classes(ufl.equation, locals()) __all__ = as_native_strings(__all__) ufl-2017.2.0/ufl/functionspace.py0000644000231000000010000001314613211220450015637 0ustar chrisdaemon# -*- coding: utf-8 -*- "Types for representing function spaces." # Copyright (C) 2015-2016 Martin Sandve Alnæs # # 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 Massimiliano Leoni, 2016 from ufl.utils.py23 import as_native_str from ufl.utils.py23 import as_native_strings from ufl.log import error from ufl.core.ufl_type import attach_operators_from_hash_data from ufl.domain import join_domains from ufl.finiteelement import MixedElement # Export list for ufl.classes __all_classes__ = as_native_strings([ "AbstractFunctionSpace", "FunctionSpace", "MixedFunctionSpace", "TensorProductFunctionSpace", ]) class AbstractFunctionSpace(object): def ufl_sub_spaces(self): raise NotImplementedError("Missing implementation of IFunctionSpace.ufl_sub_spaces in %s." % self.__class__.__name__) @attach_operators_from_hash_data class FunctionSpace(AbstractFunctionSpace): def __init__(self, domain, element): if domain is None: # DOLFIN hack # TODO: Is anything expected from element.cell() in this case? pass else: try: domain_cell = domain.ufl_cell() except AttributeError: error("Expected non-abstract domain for initalization of function space.") else: if element.cell() != domain_cell: error("Non-matching cell of finite element and domain.") AbstractFunctionSpace.__init__(self) self._ufl_domain = domain self._ufl_element = element def ufl_sub_spaces(self): "Return ufl sub spaces." return () def ufl_domain(self): "Return ufl domain." return self._ufl_domain def ufl_element(self): "Return ufl element." return self._ufl_element def ufl_domains(self): "Return ufl domains." domain = self.ufl_domain() if domain is None: return () else: return (domain,) def _ufl_hash_data_(self): domain = self.ufl_domain() element = self.ufl_element() if domain is None: ddata = None else: ddata = domain._ufl_hash_data_() if element is None: edata = None else: edata = element._ufl_hash_data_() return ("FunctionSpace", ddata, edata) def _ufl_signature_data_(self, renumbering): domain = self.ufl_domain() element = self.ufl_element() if domain is None: ddata = None else: ddata = domain._ufl_signature_data_(renumbering) if element is None: edata = None else: edata = element._ufl_signature_data_() return ("FunctionSpace", ddata, edata) def __repr__(self): r = "FunctionSpace(%s, %s)" % (repr(self._ufl_domain), repr(self._ufl_element)) return as_native_str(r) @attach_operators_from_hash_data class MixedFunctionSpace(AbstractFunctionSpace): def __init__(self, *function_spaces): AbstractFunctionSpace.__init__(self) self._ufl_function_spaces = function_spaces self._ufl_element = MixedElement(*[fs.ufl_element() for fs in function_spaces]) def ufl_sub_spaces(self): "Return ufl sub spaces." return self._ufl_function_spaces def ufl_element(self): "Return ufl element." return self._ufl_element def ufl_domains(self): "Return ufl domains." domainlist = [] for s in self._ufl_function_spaces: domainlist.extend(s.ufl_domains()) return join_domains(domainlist) def ufl_domain(self): "Return ufl domain." domains = self.ufl_domains() if len(domains) == 1: return domains[0] elif domains: error("Found multiple domains, cannot return just one.") else: return None def _ufl_hash_data_(self): return ("MixedFunctionSpace",) + tuple(V._ufl_hash_data_() for V in self.ufl_sub_spaces()) def _ufl_signature_data_(self, renumbering): return ("MixedFunctionSpace",) + tuple(V._ufl_signature_data_(renumbering) for V in self.ufl_sub_spaces()) def __repr__(self): r = "MixedFunctionSpace(*%s)" % repr(self._ufl_function_spaces) return as_native_str(r) @attach_operators_from_hash_data class TensorProductFunctionSpace(AbstractFunctionSpace): def __init__(self, *function_spaces): AbstractFunctionSpace.__init__(self) self._ufl_function_spaces = function_spaces def ufl_sub_spaces(self): return self._ufl_function_spaces def _ufl_hash_data_(self): return ("TensorProductFunctionSpace",) + tuple(V._ufl_hash_data_() for V in self.ufl_sub_spaces()) def _ufl_signature_data_(self, renumbering): return ("TensorProductFunctionSpace",) + tuple(V._ufl_signature_data_(renumbering) for V in self.ufl_sub_spaces()) def __repr__(self): r = "TensorProductFunctionSpace(*%s)" % repr(self._ufl_function_spaces) return as_native_str(r) ufl-2017.2.0/ufl/variable.py0000644000231000000010000000725113211220450014563 0ustar chrisdaemon# -*- coding: utf-8 -*- """Defines the Variable and Label classes, used to label expressions as variables for differentiation.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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.utils.counted import counted_init from ufl.utils.py23 import as_native_str from ufl.utils.py23 import as_native_strings from ufl.log import error 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__ = as_native_strings(("_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): r = "Label(%d)" % self._count return as_native_str(r) @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): return True def ufl_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 if not isinstance(expression, Expr): error("Expecting Expr.") if not isinstance(label, Label): error("Expecting a Label.") if expression.ufl_free_indices: error("Variable cannot wrap an expression with free indices.") Operator.__init__(self, (expression, label)) def ufl_domains(self): return self.ufl_operands[0].ufl_domains() 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]) ufl-2017.2.0/ufl/measureoperators.py0000644000231000000010000000000013211220450016357 0ustar chrisdaemonufl-2017.2.0/ufl/indexsum.py0000644000231000000010000000626113211220450014632 0ustar chrisdaemon# -*- coding: utf-8 -*- """This module defines the IndexSum class.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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.utils.py23 import as_native_strings from ufl.core.ufl_type import ufl_type from ufl.core.expr import Expr, ufl_err_str from ufl.core.operator import Operator from ufl.core.multiindex import MultiIndex from ufl.precedence import parstr from ufl.constantvalue import Zero # --- Sum over an index --- @ufl_type(num_ops=2) class IndexSum(Operator): __slots__ = as_native_strings(( "_dimension", "ufl_free_indices", "ufl_index_dimensions", )) def __new__(cls, summand, index): # Error checks if not isinstance(summand, Expr): error("Expecting Expr instance, got %s" % ufl_err_str(summand)) if not isinstance(index, MultiIndex): error("Expecting MultiIndex instance, got %s" % ufl_err_str(index)) if len(index) != 1: error("Expecting a single Index onlym got %d." % len(index)) # 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 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)) ufl-2017.2.0/ufl/index_combination_utils.py0000644000231000000010000001621713211220450017711 0ustar chrisdaemon# -*- coding: utf-8 -*- "Utilities for analysing and manipulating free index tuples" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 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: if i[1] != prev[1]: error("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): if int(ind) >= shape[len(all_indices)]: error("Index out of bounds.") all_indices.append(ind) elif isinstance(ind, int): if int(ind) >= shape[len(all_indices)]: error("Index out of bounds.") all_indices.append(FixedIndex(ind)) elif isinstance(ind, slice): if ind != slice(None): error("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)) if len(all_indices) != len(shape): error("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 if len(set(free_indices)) != len(free_indices): error("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 if len(set(free_indices)) != len(free_indices): error("Not expecting repeated indices left.") if len(free_indices) + 2*len(repeated_indices) != an + bn: error("Expecting only twice repeated indices.") return tuple(free_indices), tuple(index_dimensions), tuple(repeated_indices), tuple(repeated_index_dimensions) ufl-2017.2.0/ufl/exprequals.py0000644000231000000010000001121313211220450015160 0ustar chrisdaemon# -*- coding: utf-8 -*- from collections import defaultdict from six.moves import zip from ufl.core.expr import Expr from ufl.log import error 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-2017.2.0/ufl/geometry.py0000644000231000000010000006376513211220450014645 0ustar chrisdaemon# -*- coding: utf-8 -*- "Types for representing symbolic expressions for geometric quantities." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 Massimiliano Leoni, 2016 from ufl.utils.py23 import as_native_str from ufl.utils.py23 import as_native_strings from ufl.log import error from ufl.core.ufl_type import ufl_type from ufl.core.terminal import Terminal from ufl.domain import as_domain """ 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 - CellOrigin) 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__ = as_native_strings(("_domain",)) def __init__(self, domain): Terminal.__init__(self) self._domain = as_domain(domain) def ufl_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 _ufl_signature_data_(self, renumbering): "Signature data of geometric quantities depend on the domain numbering." return (self._ufl_class_.__name__,) + self._domain._ufl_signature_data_(renumbering) def __str__(self): return self._ufl_class_.name def __repr__(self): r = "%s(%s)" % (self._ufl_class_.__name__, repr(self._domain)) return as_native_str(r) def _ufl_compute_hash_(self): return hash((type(self).__name__,) + self._domain._ufl_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): """Return the number of coordinates defined (i.e. the geometric dimension of the domain).""" 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): "Return the value of the coordinate." 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() if t < 2: error("FacetCoordinate is only defined for topological dimensions >= 2.") @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. .. math:: J_{ij} = \\frac{dx_i}{dX_j} """ __slots__ = () name = "J" @property def ufl_shape(self): """Return the number of coordinates defined (i.e. the geometric dimension of the domain).""" 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() if t < 2: error("FacetJacobian is only defined for topological dimensions >= 2.") @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() if t < 2: error("CellFacetJacobian is only defined for topological dimensions >= 2.") @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 ReferenceCellEdgeVectors(GeometricCellQuantity): """UFL geometry representation: The vectors between reference cell vertices for each edge in cell.""" __slots__ = () name = "RCEV" def __init__(self, domain): GeometricCellQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 2: error("CellEdgeVectors is only defined for topological dimensions >= 2.") @property def ufl_shape(self): cell = self.ufl_domain().ufl_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 ReferenceFacetEdgeVectors(GeometricFacetQuantity): """UFL geometry representation: The vectors between reference cell vertices for each edge in current facet.""" __slots__ = () name = "RFEV" def __init__(self, domain): GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 3: error("FacetEdgeVectors is only defined for topological dimensions >= 3.") @property def ufl_shape(self): cell = self.ufl_domain().ufl_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 @ufl_type() class CellVertices(GeometricCellQuantity): """UFL geometry representation: Physical cell vertices.""" __slots__ = () name = "CV" def __init__(self, domain): GeometricCellQuantity.__init__(self, domain) @property def ufl_shape(self): cell = self.ufl_domain().ufl_cell() nv = cell.num_vertices() g = cell.geometric_dimension() return (nv, g) 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 CellEdgeVectors(GeometricCellQuantity): """UFL geometry representation: The vectors between physical cell vertices for each edge in cell.""" __slots__ = () name = "CEV" def __init__(self, domain): GeometricCellQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 2: error("CellEdgeVectors is only defined for topological dimensions >= 2.") @property def ufl_shape(self): cell = self.ufl_domain().ufl_cell() ne = cell.num_edges() g = cell.geometric_dimension() return (ne, g) 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 physical cell vertices for each edge in current facet.""" __slots__ = () name = "FEV" def __init__(self, domain): GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 3: error("FacetEdgeVectors is only defined for topological dimensions >= 3.") @property def ufl_shape(self): cell = self.ufl_domain().ufl_cell() nfe = cell.num_facet_edges() g = cell.geometric_dimension() return (nfe, g) 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): """Return the number of coordinates defined (i.e. the geometric dimension of the domain).""" 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() if t < 2: error("FacetJacobianInverse is only defined for topological dimensions >= 2.") @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() if t < 2: error("CellFacetJacobianInverse is only defined for topological dimensions >= 2.") @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): """Return the number of coordinates defined (i.e. the geometric dimension of the domain).""" g = self._domain.geometric_dimension() return (g,) def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." # For product cells, this is only true for some but not all # facets. 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.ufl_coordinate_element().degree() == 1 return is_piecewise_linear and self._domain.ufl_cell().has_simplex_facets() @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): """Return the number of coordinates defined (i.e. the geometric dimension of the domain).""" g = self._domain.geometric_dimension() # t = self._domain.topological_dimension() # return (g-t,g) # TODO: Should it be CellNormals? For interval in 3D we have two! return (g,) @ufl_type() class ReferenceNormal(GeometricFacetQuantity): """UFL geometry representation: The outwards pointing normal vector of the current facet on the reference cell""" __slots__ = () name = "reference_normal" @property def ufl_shape(self): t = self._domain.topological_dimension() return (t,) # TODO: Implement in apply_geometry_lowering 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() # if t < 2: # error("FacetTangents is only defined for topological dimensions >= 2.") # # @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." # # For product cells, this is only true for some but not all facets. 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.ufl_coordinate_element().degree() == 1 # return is_piecewise_linear and self._domain.ufl_cell().has_simplex_facets() # TODO: Implement in apply_geometry_lowering 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 ReferenceCellVolume(GeometricCellQuantity): """UFL geometry representation: The volume of the reference cell.""" __slots__ = () name = "reference_cell_volume" @ufl_type() class ReferenceFacetVolume(GeometricFacetQuantity): """UFL geometry representation: The volume of the reference cell of the current facet.""" __slots__ = () name = "reference_facet_volume" @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 CellDiameter(GeometricCellQuantity): """UFL geometry representation: The diameter of the cell, i.e., maximal distance of two points in the cell.""" __slots__ = () name = "diameter" # @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 ufl-2017.2.0/ufl/restriction.py0000644000231000000010000000637413211220450015350 0ustar chrisdaemon# -*- coding: utf-8 -*- """Restriction operations.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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.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) @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],) # 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],) ufl-2017.2.0/ufl/domain.py0000644000231000000010000003301713211220450014244 0ustar chrisdaemon# -*- coding: utf-8 -*- "Types for representing a geometric domain." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 import numbers from ufl.utils.py23 import as_native_str from ufl.utils.py23 import as_native_strings from ufl.core.ufl_type import attach_operators_from_hash_data from ufl.core.ufl_id import attach_ufl_id from ufl.corealg.traversal import traverse_unique_terminals from ufl.log import error from ufl.cell import as_cell, AbstractCell, TensorProductCell from ufl.finiteelement.tensorproductelement import TensorProductElement # Export list for ufl.classes __all_classes__ = as_native_strings(["AbstractDomain", "Mesh", "MeshView", "TensorProductMesh"]) class AbstractDomain(object): """Symbolic representation of a geometric domain with only a geometric and topological dimension. """ def __init__(self, topological_dimension, geometric_dimension): # Validate dimensions if not isinstance(geometric_dimension, numbers.Integral): error("Expecting integer geometric dimension, not %s" % (geometric_dimension.__class__,)) if not isinstance(topological_dimension, numbers.Integral): error("Expecting integer topological dimension, not %s" % (topological_dimension.__class__,)) if topological_dimension > geometric_dimension: error("Topological dimension cannot be larger than geometric dimension.") # Store validated dimensions self._topological_dimension = topological_dimension self._geometric_dimension = geometric_dimension 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 __unicode__(self): # Only in python 2 return str(self).decode("utf-8") # TODO: Would it be useful to have a domain representing R^d? E.g. for # Expression. # class EuclideanSpace(AbstractDomain): # def __init__(self, geometric_dimension): # AbstractDomain.__init__(self, geometric_dimension, geometric_dimension) @attach_operators_from_hash_data @attach_ufl_id class Mesh(AbstractDomain): """Symbolic representation of a mesh.""" def __init__(self, coordinate_element, ufl_id=None, cargo=None): self._ufl_id = self._init_ufl_id(ufl_id) # Store reference to object that will not be used by UFL self._ufl_cargo = cargo if cargo is not None and cargo.ufl_id() != self._ufl_id: error("Expecting cargo object (e.g. dolfin.Mesh) to have the same ufl_id.") # No longer accepting coordinates provided as a Coefficient from ufl.coefficient import Coefficient if isinstance(coordinate_element, Coefficient): error("Expecting a coordinate element in the ufl.Mesh construct.") # Accept a cell in place of an element for brevity Mesh(triangle) if isinstance(coordinate_element, AbstractCell): from ufl.finiteelement import VectorElement cell = coordinate_element coordinate_element = VectorElement("Lagrange", cell, 1, dim=cell.geometric_dimension()) # Store coordinate element self._ufl_coordinate_element = coordinate_element # Derive dimensions from element gdim, = coordinate_element.value_shape() tdim = coordinate_element.cell().topological_dimension() AbstractDomain.__init__(self, tdim, gdim) def ufl_cargo(self): "Return carried object that will not be used by UFL." return self._ufl_cargo def ufl_coordinate_element(self): return self._ufl_coordinate_element def ufl_cell(self): return self._ufl_coordinate_element.cell() def is_piecewise_linear_simplex_domain(self): return (self._ufl_coordinate_element.degree() == 1) and self.ufl_cell().is_simplex() def __repr__(self): r = "Mesh(%s, %s)" % (repr(self._ufl_coordinate_element), repr(self._ufl_id)) return as_native_str(r) def __str__(self): return "" % (self._ufl_id,) def _ufl_hash_data_(self): return (self._ufl_id, self._ufl_coordinate_element) def _ufl_signature_data_(self, renumbering): return ("Mesh", renumbering[self], self._ufl_coordinate_element) # NB! Dropped __lt__ here, don't want users to write 'mesh1 < # mesh2'. def _ufl_sort_key_(self): typespecific = (self._ufl_id, self._ufl_coordinate_element) return (self.geometric_dimension(), self.topological_dimension(), "Mesh", typespecific) @attach_operators_from_hash_data @attach_ufl_id class MeshView(AbstractDomain): """Symbolic representation of a mesh.""" def __init__(self, mesh, topological_dimension, ufl_id=None): self._ufl_id = self._init_ufl_id(ufl_id) # Store mesh self._ufl_mesh = mesh # Derive dimensions from element coordinate_element = mesh.ufl_coordinate_element() gdim, = coordinate_element.value_shape() tdim = coordinate_element.cell().topological_dimension() AbstractDomain.__init__(self, tdim, gdim) def ufl_mesh(self): return self._ufl_mesh def ufl_cell(self): return self._ufl_mesh.ufl_cell() def is_piecewise_linear_simplex_domain(self): return self._ufl_mesh.is_piecewise_linear_simplex_domain() def __repr__(self): tdim = self.topological_dimension() r = "MeshView(%s, %s, %s)" % (repr(self._ufl_mesh), repr(tdim), repr(self._ufl_id)) return as_native_str(r) def __str__(self): return "" % ( self._ufl_id, self.topological_dimension(), self._ufl_mesh) def _ufl_hash_data_(self): return (self._ufl_id,) + self._ufl_mesh._ufl_hash_data_() def _ufl_signature_data_(self, renumbering): return ("MeshView", renumbering[self], self._ufl_mesh._ufl_signature_data_(renumbering)) # NB! Dropped __lt__ here, don't want users to write 'mesh1 < # mesh2'. def _ufl_sort_key_(self): typespecific = (self._ufl_id, self._ufl_mesh) return (self.geometric_dimension(), self.topological_dimension(), "MeshView", typespecific) @attach_operators_from_hash_data @attach_ufl_id class TensorProductMesh(AbstractDomain): """Symbolic representation of a mesh.""" def __init__(self, meshes, ufl_id=None): self._ufl_id = self._init_ufl_id(ufl_id) # TODO: Error checking of meshes self._ufl_meshes = meshes # TODO: Is this what we want to do? # Build cell from mesh cells self._ufl_cell = TensorProductCell(*[mesh.ufl_cell() for mesh in meshes]) # TODO: Is this what we want to do? # Build coordinate element from mesh coordinate elements self._ufl_coordinate_element = TensorProductElement([mesh.ufl_coordinate_element() for mesh in meshes]) # Derive dimensions from meshes gdim = sum(mesh.geometric_dimension() for mesh in meshes) tdim = sum(mesh.topological_dimension() for mesh in meshes) AbstractDomain.__init__(self, tdim, gdim) def ufl_coordinate_element(self): return self._ufl_coordinate_element def ufl_cell(self): return self._ufl_cell def is_piecewise_linear_simplex_domain(self): return False # TODO: Any cases this is True def __repr__(self): r = "TensorProductMesh(%s, %s)" % (repr(self._ufl_meshes), repr(self._ufl_id)) return as_native_str(r) def __str__(self): return "" % ( self._ufl_id, self._ufl_meshes) def _ufl_hash_data_(self): return (self._ufl_id,) + tuple(mesh._ufl_hash_data_() for mesh in self._ufl_meshes) def _ufl_signature_data_(self, renumbering): return ("TensorProductMesh",) + tuple(mesh._ufl_signature_data_(renumbering) for mesh in self._ufl_meshes) # NB! Dropped __lt__ here, don't want users to write 'mesh1 < # mesh2'. def _ufl_sort_key_(self): typespecific = (self._ufl_id, tuple(mesh._ufl_sort_key_() for mesh in self._ufl_meshes)) return (self.geometric_dimension(), self.topological_dimension(), "TensorProductMesh", typespecific) # --- Utility conversion functions def affine_mesh(cell, ufl_id=None): "Create a Mesh over a given cell type with an affine geometric parameterization." from ufl.finiteelement import VectorElement cell = as_cell(cell) gdim = cell.geometric_dimension() degree = 1 coordinate_element = VectorElement("Lagrange", cell, degree, dim=gdim) return Mesh(coordinate_element, ufl_id=ufl_id) _default_domains = {} def default_domain(cell): """Create a singular default Mesh from a cell, always returning the same Mesh object for the same cell. """ global _default_domains assert isinstance(cell, AbstractCell) domain = _default_domains.get(cell) if domain is None: # Create one and only one affine Mesh with a negative ufl_id # to avoid id collision ufl_id = -(len(_default_domains)+1) domain = affine_mesh(cell, ufl_id=ufl_id) _default_domains[cell] = domain return domain def as_domain(domain): """Convert any valid object to an AbstractDomain type.""" if isinstance(domain, AbstractDomain): # Modern .ufl files and dolfin behaviour return domain elif hasattr(domain, "ufl_domain"): # If we get a dolfin.Mesh, it can provide us a corresponding # ufl.Mesh. This would be unnecessary if dolfin.Mesh could # subclass ufl.Mesh. return domain.ufl_domain() else: # Legacy .ufl files # TODO: Make this conversion in the relevant constructors # closer to the user interface? # TODO: Make this configurable to be an error from the dolfin side? cell = as_cell(domain) return default_domain(cell) def sort_domains(domains): "Sort domains in a canonical ordering." return tuple(sorted(domains, key=lambda domain: domain._ufl_sort_key_())) def join_domains(domains): """Take a list of domains and return a tuple with only unique domain objects. Checks that domains with the same id are compatible. """ # Use hashing to join domains, ignore None domains = set(domains) - set((None,)) if not domains: return () # Check geometric dimension compatibility gdims = set() for domain in domains: gdims.add(domain.geometric_dimension()) if len(gdims) != 1: error("Found domains with different geometric dimensions.") gdim, = gdims # Split into legacy and modern style domains legacy_domains = [] modern_domains = [] for domain in domains: if isinstance(domain, Mesh) and domain.ufl_id() < 0: assert domain.ufl_cargo() is None legacy_domains.append(domain) else: modern_domains.append(domain) # Handle legacy domains checking if legacy_domains: if modern_domains: error("Found both a new-style domain and a legacy default domain.\n" "These should not be used interchangeably. To find the legacy\n" "domain, note that it is automatically created from a cell so\n" "look for constructors taking a cell.") return tuple(legacy_domains) # Handle modern domains checking (assuming correct by construction) return tuple(modern_domains) # TODO: Move these to an analysis module? def extract_domains(expr): "Return all domains expression is defined on." domainlist = [] for t in traverse_unique_terminals(expr): domainlist.extend(t.ufl_domains()) return sorted(join_domains(domainlist)) def extract_unique_domain(expr): "Return the single unique domain expression is defined on or throw an error." domains = extract_domains(expr) if len(domains) == 1: return domains[0] elif domains: error("Found multiple domains, cannot return just one.") else: return None def find_geometric_dimension(expr): "Find the geometric dimension of an expression." gdims = set() for t in traverse_unique_terminals(expr): if hasattr(t, "ufl_domain"): domain = t.ufl_domain() if domain is not None: gdims.add(domain.geometric_dimension()) if hasattr(t, "ufl_element"): element = t.ufl_element() if element is not None: cell = element.cell() if cell is not None: gdims.add(cell.geometric_dimension()) if len(gdims) != 1: error("Cannot determine geometric dimension from expression.") gdim, = gdims return gdim ufl-2017.2.0/ufl/__init__.py0000644000231000000010000002716013211220450014536 0ustar chrisdaemon# -*- coding: utf-8 -*- # flake8: noqa """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 the mathematical one. 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 accessed by :: from ufl.algorithms import * 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: * Cells:: - AbstractCell - Cell - TensorProductCell - vertex - interval - triangle - tetrahedron - quadrilateral - hexahedron * Domains:: - AbstractDomain - Mesh - MeshView - TensorProductMesh * Sobolev spaces:: - L2 - H1 - H2 - HDiv - HCurl * Elements:: - FiniteElement - MixedElement - VectorElement - TensorElement - EnrichedElement - NodalEnrichedElement - RestrictedElement - TensorProductElement - HDivElement - HCurlElement - BrokenElement - FacetElement - InteriorElement * Function spaces:: - FunctionSpace * Arguments:: - Argument - TestFunction - TrialFunction - Arguments - TestFunctions - TrialFunctions * Coefficients:: - Coefficient - Constant - VectorConstant - TensorConstant * Splitting form arguments in mixed spaces:: - split * Literal constants:: - Identity - PermutationSymbol * Geometric quantities:: - SpatialCoordinate - FacetNormal - CellNormal - CellVolume - CellDiameter - Circumradius - MinCellEdgeLength - MaxCellEdgeLength - FacetArea - MinFacetEdgeLength - MaxFacetEdgeLength - Jacobian - JacobianDeterminant - JacobianInverse * 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, nabla_grad - div, nabla_div - curl, rot - Dx, Dn * 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:: - v('+'), v('-') - jump - avg - 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, dX - 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-2016 Martin Sandve Alnæs 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 # Modified by Massimiliano Leoni, 2016 import pkg_resources __version__ = pkg_resources.get_distribution("fenics-ufl").version ########## 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.utils.sequences 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, AbstractCell, Cell, TensorProductCell from ufl.domain import as_domain, AbstractDomain, Mesh, MeshView, TensorProductMesh from ufl.geometry import ( SpatialCoordinate, FacetNormal, CellNormal, CellVolume, CellDiameter, Circumradius, MinCellEdgeLength, MaxCellEdgeLength, FacetArea, MinFacetEdgeLength, MaxFacetEdgeLength, Jacobian, JacobianDeterminant, JacobianInverse ) # Sobolev spaces from ufl.sobolevspace import L2, H1, H2, HDiv, HCurl # Finite elements classes from ufl.finiteelement import FiniteElementBase, FiniteElement, \ MixedElement, VectorElement, TensorElement, EnrichedElement, \ NodalEnrichedElement, RestrictedElement, TensorProductElement, \ HDivElement, HCurlElement, BrokenElement, \ FacetElement, InteriorElement # Hook to extend predefined element families from ufl.finiteelement.elementlist import register_element, show_elements #, ufl_elements # Function spaces from ufl.functionspace import FunctionSpace, MixedFunctionSpace # 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, custom_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, block_split #, 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, dX, ds_b, ds_t, ds_tb, ds_v, dS_h, dS_v ) # Useful constants from math import e, pi # Define ufl.* namespace from ufl.utils.py23 import as_native_strings __all__ = as_native_strings([ 'product', 'get_handler', 'get_logger', 'set_handler', 'set_level', 'add_logfile', 'UFLException', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', 'as_cell', 'AbstractCell', 'Cell', 'TensorProductCell', 'as_domain', 'AbstractDomain', 'Mesh', 'MeshView', 'TensorProductMesh', 'L2', 'H1', 'H2', 'HCurl', 'HDiv', 'SpatialCoordinate', 'CellVolume', 'CellDiameter', 'Circumradius', 'MinCellEdgeLength', 'MaxCellEdgeLength', 'FacetArea', 'MinFacetEdgeLength', 'MaxFacetEdgeLength', 'FacetNormal', 'CellNormal', 'Jacobian', 'JacobianDeterminant', 'JacobianInverse', 'FiniteElementBase', 'FiniteElement', 'MixedElement', 'VectorElement', 'TensorElement', 'EnrichedElement', 'NodalEnrichedElement', 'RestrictedElement', 'TensorProductElement', 'HDivElement', 'HCurlElement', 'BrokenElement', 'FacetElement', 'InteriorElement', 'register_element', 'show_elements', 'FunctionSpace', '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', 'custom_integral_types', 'replace', 'replace_integral_domains', 'derivative', 'action', 'energy_norm', 'rhs', 'lhs', 'block_split', 'system', 'functional', 'adjoint', 'sensitivity_rhs', 'dx', 'ds', 'dS', 'dP', 'dc', 'dC', 'dO', 'dI', 'dX', '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-2017.2.0/ufl/integral.py0000644000231000000010000001352013211220450014577 0ustar chrisdaemon# -*- coding: utf-8 -*- """The Integral class.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 Massimiliano Leoni, 2016. import ufl from ufl.log import error from ufl.core.expr import Expr from ufl.checks import is_python_scalar, is_scalar_constant_expression from ufl.measure import Measure # noqa from ufl.protocols import id_or_none from ufl.utils.py23 import as_native_str from ufl.utils.py23 import as_native_strings # Export list for ufl.classes __all_classes__ = as_native_strings(["Integral"]) class Integral(object): "An integral over a single domain." __slots__ = as_native_strings(( "_integrand", "_integral_type", "_ufl_domain", "_subdomain_id", "_metadata", "_subdomain_data", )) def __init__(self, integrand, integral_type, domain, subdomain_id, metadata, subdomain_data): if not isinstance(integrand, Expr): error("Expecting integrand to be an Expr instance.") self._integrand = integrand self._integral_type = integral_type self._ufl_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.ufl_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 ufl_domain(self): "Return the integration domain of this integral." return self._ufl_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): if not is_python_scalar(scalar): error("Cannot multiply an integral with non-constant values.") return self.reconstruct(scalar*self._integrand) def __rmul__(self, scalar): if not is_scalar_constant_expression(scalar): error("An integral can only be multiplied by a " "globally constant scalar expression.") return self.reconstruct(scalar*self._integrand) def __unicode__(self): # Only in python 2 return str(self).decode("utf-8") 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._ufl_domain, self._subdomain_id, self._metadata) return s def __repr__(self): r = "Integral(%s, %s, %s, %s, %s, %s)" % (repr(self._integrand), repr(self._integral_type), repr(self._ufl_domain), repr(self._subdomain_id), repr(self._metadata), repr(self._subdomain_data)) return as_native_str(r) def __eq__(self, other): return (isinstance(other, Integral) and self._integral_type == other._integral_type and self._ufl_domain == other._ufl_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._ufl_domain), self._subdomain_id, id_or_none(self._subdomain_data)) return hash(hashdata) ufl-2017.2.0/ufl/tensors.py0000644000231000000010000003623713211220450014501 0ustar chrisdaemon# -*- coding: utf-8 -*- """Classes used to group scalar expressions into expressions with rank > 0.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 Massimiliano Leoni, 2016. from six.moves import zip from six.moves import xrange as range from ufl.utils.py23 import as_native_strings from ufl.log import error from ufl.core.ufl_type import ufl_type 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 # --- 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) if not all(not (indexset ^ set(e.ufl_free_indices)) for e in self.ufl_operands): error("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 evaluate(self, x, mapping, component, index_values, derivatives=()): if len(component) != len(self.ufl_shape): error("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) @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__ = as_native_strings(("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): if not isinstance(expression, Expr): error("Expecting ufl expression.") if expression.ufl_shape != (): error("Expecting scalar valued expression.") if not isinstance(indices, MultiIndex): error("Expecting a MultiIndex.") if not all(isinstance(i, Index) for i in indices): error("Expecting sequence of Index objects, not %s." % indices._ufl_err_str_()) 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 _ufl_expr_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: return A return Operator._ufl_expr_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] if len(indices) != len(component): error("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]) # --- 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 Exception: 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 :math:`A` such that :math:`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 :math:`A` such that :math:`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): if len(expressions.ufl_shape) != 2: error("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 if not isinstance(expressions, (list, tuple)): error("Expecting nested list or tuple of Exprs.") if not isinstance(expressions[0], (list, tuple)): error("Expecting nested list or tuple of Exprs.") else: if len(indices) != 2: error("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): if len(expressions.ufl_shape) != 1: error("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 if not isinstance(expressions, (list, tuple)): error("Expecting nested list or tuple of Exprs.") else: if not isinstance(index, Index): error("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(len(expression.ufl_shape)) 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(len(expressions[0].ufl_shape)) if ii: expressions = [expression[ii] for expression in expressions] return expressions, ii def relabel(A, indexmap): "UFL operator: Relabel free indices of :math:`A` with new indices, using the given mapping." ii = tuple(sorted(indexmap.keys())) jj = tuple(indexmap[i] for i in ii) if not all(isinstance(i, Index) for i in ii): error("Expecting Index objects.") if not all(isinstance(j, Index) for j in jj): error("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 Id = Identity(d) i = iota[0] e = as_vector(Id[i, :], i) for i in iota[1:]: e = outer(e, as_vector(Id[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-2017.2.0/ufl/coefficient.py0000644000231000000010000001310713211220450015251 0ustar chrisdaemon# -*- coding: utf-8 -*- """This module defines the Coefficient class and a number of related classes, including Constant.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 Massimiliano Leoni, 2016. from ufl.utils.py23 import as_native_str from ufl.log import error from ufl.core.ufl_type import ufl_type from ufl.core.terminal import FormArgument from ufl.finiteelement import FiniteElementBase, FiniteElement, VectorElement, TensorElement from ufl.domain import as_domain, default_domain from ufl.functionspace import AbstractFunctionSpace, FunctionSpace from ufl.split_functions import split from ufl.utils.counted import counted_init # --- 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", "_ufl_function_space", "_repr", "_ufl_shape") _ufl_noslots_ = True _globalcount = 0 def __init__(self, function_space, count=None): FormArgument.__init__(self) counted_init(self, count, Coefficient) if isinstance(function_space, FiniteElementBase): # For legacy support for .ufl files using cells, we map # the cell to The Default Mesh element = function_space domain = default_domain(element.cell()) function_space = FunctionSpace(domain, element) elif not isinstance(function_space, AbstractFunctionSpace): error("Expecting a FunctionSpace or FiniteElement.") self._ufl_function_space = function_space self._ufl_shape = function_space.ufl_element().value_shape() self._repr = as_native_str("Coefficient(%s, %s)" % ( repr(self._ufl_function_space), repr(self._count))) def count(self): return self._count @property def ufl_shape(self): "Return the associated UFL shape." return self._ufl_shape def ufl_function_space(self): "Get the function space of this coefficient." return self._ufl_function_space def ufl_domain(self): "Shortcut to get the domain of the function space of this coefficient." return self._ufl_function_space.ufl_domain() def ufl_element(self): "Shortcut to get the finite element of the function space of this coefficient." return self._ufl_function_space.ufl_element() def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell." return self.ufl_element().is_cellwise_constant() def ufl_domains(self): "Return tuple of domains related to this terminal object." return self._ufl_function_space.ufl_domains() def _ufl_signature_data_(self, renumbering): "Signature data for form arguments depend on the global numbering of the form arguments and domains." count = renumbering[self] fsdata = self._ufl_function_space._ufl_signature_data_(renumbering) return ("Coefficient", count, fsdata) def __str__(self): count = str(self._count) if len(count) == 1: return "w_%s" % count else: return "w_{%s}" % count def __repr__(self): 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._ufl_function_space == other._ufl_function_space) # --- Helper functions for defining constant coefficients without # --- specifying element --- def Constant(domain, count=None): """UFL value: Represents a globally constant scalar valued coefficient.""" domain = as_domain(domain) element = FiniteElement("Real", domain.ufl_cell(), 0) fs = FunctionSpace(domain, element) return Coefficient(fs, count=count) def VectorConstant(domain, dim=None, count=None): """UFL value: Represents a globally constant vector valued coefficient.""" domain = as_domain(domain) element = VectorElement("Real", domain.ufl_cell(), 0, dim) fs = FunctionSpace(domain, element) return Coefficient(fs, count=count) def TensorConstant(domain, shape=None, symmetry=None, count=None): """UFL value: Represents a globally constant tensor valued coefficient.""" domain = as_domain(domain) element = TensorElement("Real", domain.ufl_cell(), 0, shape=shape, symmetry=symmetry) fs = FunctionSpace(domain, element) return Coefficient(fs, count=count) # --- Helper functions for subfunctions on mixed elements --- def Coefficients(function_space): """UFL value: Create a Coefficient in a mixed space, and return a tuple with the function components corresponding to the subelements.""" return split(Coefficient(function_space)) ufl-2017.2.0/ufl/sobolevspace.py0000644000231000000010000001665713211220450015475 0ustar chrisdaemon# -*- coding: utf-8 -*- """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 # Modified by Thomas Gibson 2017 from ufl.utils.py23 import as_native_str from functools import total_ordering @total_ordering 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]) self._order = {"L2": 0, "H1": 1, "H2": 2, # Order for the elements below is taken from # its parent Sobolev space "HDiv": 0, "HCurl": 0, "HEin": 0, "HDivDiv": 0, "DirectionalH": 0}[self.name] def __unicode__(self): # Only in python 2 return str(self).decode("utf-8") def __str__(self): return self.name def __repr__(self): r = "SobolevSpace(%s, %s)" % (repr(self.name), repr(list(self.parents))) return as_native_str(r) 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 __ne__(self, other): return not self == other def __hash__(self): return hash(("SobolevSpace", self.name)) def __getitem__(self, spatial_index): """Returns the Sobolev space associated with a particular spatial coordinate. """ return self 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 __call__(self, element): """Syntax shortcut to create a HDivElement or HCurlElement.""" if self.name == "HDiv": from ufl.finiteelement import HDivElement return HDivElement(element) elif self.name == "HCurl": from ufl.finiteelement import HCurlElement return HCurlElement(element) raise NotImplementedError("SobolevSpace has no call operator (only the specific HDiv and HCurl instances).") @total_ordering class DirectionalSobolevSpace(SobolevSpace): """Symbolic representation of a Sobolev space with varying smoothness in differerent spatial directions. """ def __init__(self, orders): """Instantiate a DirectionalSobolevSpace object. :arg orders: an iterable of orders of weak derivatives, where the position denotes in what spatial variable the smoothness requirement is enforced. """ assert all(isinstance(x, int) for x in orders), ( "Order must be an integer." ) assert all(x < 3 for x in orders), ( "Not implemented for orders greater than 2" ) name = "DirectionalH" parents = [L2] super(DirectionalSobolevSpace, self).__init__(name, parents) self._orders = tuple(orders) self._spatial_indices = range(len(self._orders)) def __getitem__(self, spatial_index): """Returns the Sobolev space associated with a particular spatial coordinate. """ if spatial_index not in range(len(self._orders)): raise IndexError("Spatial index out of range.") spaces = {0: L2, 1: H1, 2: H2} return spaces[self._orders[spatial_index]] def __contains__(self, other): """Implement `fe in s` where `fe` is a :class:`~finiteelement.FiniteElement` and `s` is a :class:`DirectionalSobolevSpace`""" 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 all(self[i] in other.sobolev_space().parents for i in self._spatial_indices)) def __eq__(self, other): if isinstance(other, DirectionalSobolevSpace): return self._orders == other._orders return all(self[i] == other for i in self._spatial_indices) def __lt__(self, other): """In common with intrinsic Python sets, < indicates "is a proper subset of.""" if isinstance(other, DirectionalSobolevSpace): if self._spatial_indices != other._spatial_indices: return False return any(self._orders[i] > other._orders[i] for i in self._spatial_indices) if other in [HDiv, HCurl]: return all(self._orders[i] >= 1 for i in self._spatial_indices) elif other.name in ["HDivDiv", "HEin"]: # Don't know how these spaces compare return NotImplementedError( "Don't know how to compare with %s" % other.name ) else: return any(self._orders[i] > other._order for i in self._spatial_indices) def __str__(self): return self.name + "(%s)" % ", ".join(map(str, self._orders)) def _repr_latex_(self): return "H(%s)" % ", ".join(map(str, self._orders)) L2 = SobolevSpace("L2") HDiv = SobolevSpace("HDiv", [L2]) HCurl = SobolevSpace("HCurl", [L2]) H1 = SobolevSpace("H1", [HDiv, HCurl, L2]) H2 = SobolevSpace("H2", [H1, HDiv, HCurl, L2]) HEin = SobolevSpace("HEin", [L2]) HDivDiv = SobolevSpace("HDivDiv", [L2]) ufl-2017.2.0/ufl/equation.py0000644000231000000010000000503213211220450014616 0ustar chrisdaemon# -*- coding: utf-8 -*- "The Equation class, used to express equations like a == L." # Copyright (C) 2012-2016 Anders Logg and Martin Sandve Alnæs # # 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.utils.py23 import as_native_str from ufl.utils.py23 import as_native_strings # Export list for ufl.classes __all_classes__ = as_native_strings(["Equation"]) class Equation(object): """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). This will not trigger when setting 'equation = a == L', but when e.g. running 'if equation:'. """ # NB!: pep8 will say you should use isinstance here, but we do # actually want to compare the exact types in this case. # Not equal if types are not identical (i.e. not accepting # subclasses) if type(self.lhs) != type(self.rhs): # noqa: E721 return False # Try to delegate to equals function if hasattr(self.lhs, "equals"): return self.lhs.equals(self.rhs) elif hasattr(self.rhs, "equals"): return self.rhs.equals(self.lhs) else: error("Either lhs or rhs of Equation must implement self.equals(other).") __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): r = "Equation(%s, %s)" % (repr(self.lhs), repr(self.rhs)) return as_native_str(r) ufl-2017.2.0/ufl/checks.py0000644000231000000010000000614213211220450014234 0ustar chrisdaemon# -*- coding: utf-8 -*- """Utility functions for checking properties of expressions.""" # Copyright (C) 2013-2016 Martin Sandve Alnæs # # 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_cellwise_constant(expr): "Return whether expression is constant over a single cell." # TODO: Implement more accurately considering e.g. derivatives? return all(t.is_cellwise_constant() for t in traverse_unique_terminals(expr)) 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 from ufl.geometry import GeometricQuantity from ufl.core.terminal import FormArgument for e in traverse_unique_terminals(expr): # Return False if any single terminal is not constant if e._ufl_is_literal_: # Accept literals first, they are the most common # terminals continue elif isinstance(e, FormArgument): # Accept only Real valued Arguments and Coefficients if e.ufl_element().family() == "Real": continue else: return False elif isinstance(e, GeometricQuantity): # Reject all geometric quantities, they all vary over # cells 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-2017.2.0/ufl/precedence.py0000644000231000000010000001227113211220450015071 0ustar chrisdaemon# -*- coding: utf-8 -*- "Precedence handling." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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-2017.2.0/ufl/algorithms/0000755000231000000010000000000013211220450014570 5ustar chrisdaemonufl-2017.2.0/ufl/algorithms/traversal.py0000644000231000000010000000310413211220450017143 0ustar chrisdaemon# -*- coding: utf-8 -*- """This module contains algorithms for traversing expression trees in different ways.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs 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))) ufl-2017.2.0/ufl/algorithms/formsplitter.py0000644000231000000010000000434313211220450017700 0ustar chrisdaemon# -*- coding: utf-8 -*- "Extract part of a form in a mixed FunctionSpace." # Copyright (C) 2016 Chris Richardson and Lawrence Mitchell # # 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.corealg.multifunction import MultiFunction from ufl.algorithms.map_integrands import map_integrand_dags from ufl.constantvalue import Zero from ufl.tensors import as_vector from ufl.argument import Argument from ufl.functionspace import FunctionSpace class FormSplitter(MultiFunction): def split(self, form, ix, iy=0): # Remember which block to extract self.idx = [ix, iy] return map_integrand_dags(self, form) def argument(self, obj): Q = obj.ufl_function_space() dom = Q.ufl_domain() sub_elements = obj.ufl_element().sub_elements() # If not a mixed element, do nothing if (len(sub_elements) == 0): return obj # Split into sub-elements, creating appropriate space for each args = [] for i, sub_elem in enumerate(sub_elements): Q_i = FunctionSpace(dom, sub_elem) a = Argument(Q_i, obj.number(), part=obj.part()) indices = [()] for m in a.ufl_shape: indices = [(k + (j,)) for k in indices for j in range(m)] if (i == self.idx[obj.number()]): args += [a[j] for j in indices] else: args += [Zero() for j in indices] return as_vector(args) def multi_index(self, obj): return obj expr = MultiFunction.reuse_if_untouched def block_split(form, ix, iy=0): fs = FormSplitter() return fs.split(form, ix, iy) ufl-2017.2.0/ufl/algorithms/elementtransformations.py0000644000231000000010000000510013211220450021741 0ustar chrisdaemon# -*- coding: utf-8 -*- """This module provides helper functions to - FFC/DOLFIN adaptive chain, - UFL algorithms taking care of underspecified DOLFIN expressions.""" # Copyright (C) 2012 Marie E. Rognes, 2015 Jan Blechta # # 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.utils.py23 import as_native_strings from ufl.log import error from ufl.finiteelement import FiniteElement, VectorElement, TensorElement, \ MixedElement, EnrichedElement, NodalEnrichedElement __all__ = as_native_strings(['increase_order', 'tear']) def increase_order(element): "Return element of same family, but a polynomial degree higher." return _increase_degree(element, +1) def change_regularity(element, family): """ For a given finite element, return the corresponding space specified by 'family'. """ return element.reconstruct(family=family) def tear(element): "For a finite element, return the corresponding discontinuous element." return change_regularity(element, "DG") def _increase_degree(element, degree_rise): if isinstance(element, (FiniteElement, VectorElement, TensorElement)): # Can't increase degree for reals if element.family() == "Real": return element return element.reconstruct(degree=(element.degree() + degree_rise)) elif isinstance(element, MixedElement): return MixedElement([_increase_degree(e, degree_rise) for e in element.sub_elements()]) elif isinstance(element, EnrichedElement): return EnrichedElement([_increase_degree(e, degree_rise) for e in element.sub_elements()]) elif isinstance(element, NodalEnrichedElement): return NodalEnrichedElement([_increase_degree(e, degree_rise) for e in element.sub_elements()]) else: error("Element reconstruction is only done to stay compatible" " with hacks in DOLFIN. Not expecting a %s" % repr(element)) ufl-2017.2.0/ufl/algorithms/change_to_reference.py0000644000231000000010000004236413211220450021120 0ustar chrisdaemon# -*- coding: utf-8 -*- """Algorithm for replacing gradients in an expression with reference gradients and coordinate mappings.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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.core.multiindex import 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, Indexed, MultiIndex, FixedIndex) from ufl.constantvalue import as_ufl from ufl.tensors import as_tensor from ufl.permutation import compute_indices from ufl.finiteelement import MixedElement from ufl.algorithms.apply_function_pullbacks import apply_function_pullbacks from ufl.algorithms.apply_geometry_lowering import apply_geometry_lowering from ufl.checks import is_cellwise_constant """ # 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._ufl_expr_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." if self._restricted != '': error("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): if self._avg != '': error("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): if self._avg != '': error("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 if not isinstance(t, Terminal): error("Expecting a Terminal.") # Get modifiers accumulated by previous handler calls ngrads = self._ngrads restricted = self._restricted avg = self._avg if avg != "": error("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.ufl_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.ufl_element().extract_component2(gtc) # FIXME: Translate this correctly eoffset = 0 ec, element = t.ufl_element().extract_reference_component(gtc) # Select mapping M from element, pick row emapping = # M[ec,:], or emapping = [] if no mapping if isinstance(element, MixedElement): error("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() if len(dsh) != ngrads: error("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): # Peel off the Grads and count them, and get restriction if # it's between the grad and the terminal ngrads = 0 restricted = '' rv = False 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 elif isinstance(o, ReferenceValue): rv = True o, = o.ufl_operands else: error("Invalid type %s" % o._ufl_class_.__name__) f = o if rv: f = ReferenceValue(f) # Get domain and create Jacobian inverse object domain = o.ufl_domain() Jinv = JacobianInverse(domain) if is_cellwise_constant(Jinv): # Optimise slightly by turning Grad(Grad(...)) into # J^(-T)J^(-T)RefGrad(RefGrad(...)) # rather than J^(-T)RefGrad(J^(-T)RefGrad(...)) # Create some new indices ii = indices(len(f.ufl_shape)) # 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) else: # J^(-T)RefGrad(J^(-T)RefGrad(...)) # Preserve restricted property if restricted: Jinv = Jinv(restricted) f = f(restricted) jinv_lgrad_f = f for foo in range(ngrads): ii = indices(len(jinv_lgrad_f.ufl_shape)) # Indices to get to the scalar component of f j, k = indices(2) lgrad = ReferenceGrad(jinv_lgrad_f) jinv_lgrad_f = Jinv[j, k]*lgrad[ii+(j,)] # Wrap back in tensor shape, derivative axes at the end jinv_lgrad_f = as_tensor(jinv_lgrad_f, ii+(k,)) 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.") 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_integrand_geometry_representation(integrand, scale, integral_type): """Change integrand geometry to the right representations.""" integrand = apply_function_pullbacks(integrand) integrand = change_to_reference_grad(integrand) integrand = integrand * scale if integral_type == "quadrature": physical_coordinates_known = True else: physical_coordinates_known = False integrand = apply_geometry_lowering(integrand, physical_coordinates_known) return integrand ufl-2017.2.0/ufl/algorithms/apply_integral_scaling.py0000644000231000000010000000642213211220450021660 0ustar chrisdaemon# -*- coding: utf-8 -*- """Algorithm for replacing gradients in an expression with reference gradients and coordinate mappings.""" # Copyright (C) 2013-2016 Martin Sandve Alnæs # # 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.classes import JacobianDeterminant, FacetJacobianDeterminant, QuadratureWeight, Form, Integral from ufl.measure import custom_integral_types, point_integral_types def compute_integrand_scaling_factor(integral): """Change integrand geometry to the right representations.""" domain = integral.ufl_domain() integral_type = integral.integral_type() # co = CellOrientation(domain) weight = QuadratureWeight(domain) tdim = domain.topological_dimension() # gdim = domain.geometric_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_integral_types: # Scaling with custom weight, which includes eventual volume # scaling scale = weight elif integral_type in point_integral_types: # 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 apply_integral_scaling(form): "Multiply integrands by a factor to scale the integral to reference frame." # TODO: Consider adding an in_reference_frame property to Integral # and checking it here and setting it in the returned form if isinstance(form, Form): newintegrals = [apply_integral_scaling(integral) for integral in form.integrals()] return Form(newintegrals) elif isinstance(form, Integral): integral = form # Compute and apply integration scaling factor scale = compute_integrand_scaling_factor(integral) newintegrand = integral.integrand() * scale return integral.reconstruct(integrand=newintegrand) else: error("Invalid type %s" % (form.__class__.__name__,)) ufl-2017.2.0/ufl/algorithms/check_restrictions.py0000644000231000000010000000450213211220450021030 0ustar chrisdaemon# -*- coding: utf-8 -*- "Algorithms related to restrictions." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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.corealg.multifunction import MultiFunction from ufl.corealg.map_dag import map_expr_dag class RestrictionChecker(MultiFunction): def __init__(self, require_restriction): MultiFunction.__init__(self) self.current_restriction = None self.require_restriction = require_restriction def expr(self, o): pass def restricted(self, o): if self.current_restriction is not None: error("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: if self.current_restriction is None: error("Facet normal must be restricted in interior facet integrals.") else: if self.current_restriction is not None: error("Restrictions are only allowed for interior facet integrals.") def form_argument(self, o): if self.require_restriction: if self.current_restriction is None: error("Form argument must be restricted in interior facet integrals.") else: if self.current_restriction is not None: error("Restrictions are only allowed for interior facet integrals.") def check_restrictions(expression, require_restriction): "Check that types that must be restricted are restricted in expression." rules = RestrictionChecker(require_restriction) return map_expr_dag(rules, expression) ufl-2017.2.0/ufl/algorithms/preprocess_expression.py0000644000231000000010000000000013211220450021574 0ustar chrisdaemonufl-2017.2.0/ufl/algorithms/expand_compounds.py0000644000231000000010000000200513211220450020505 0ustar chrisdaemon# -*- coding: utf-8 -*- """Algorithm for expanding compound expressions into equivalent representations using basic operators.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs 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.algorithms.apply_algebra_lowering import apply_algebra_lowering def expand_compounds(e): return apply_algebra_lowering(e) ufl-2017.2.0/ufl/algorithms/estimate_degrees.py0000644000231000000010000002526413211220450020464 0ustar chrisdaemon# -*- coding: utf-8 -*- """Algorithms for estimating polynomial degrees of expressions.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs 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.log import warning, error from ufl.form import Form from ufl.integral import Integral from ufl.algorithms.multifunction import MultiFunction from ufl.corealg.map_dag import map_expr_dags from ufl.checks import is_cellwise_constant from ufl.constantvalue import IntValue class IrreducibleInt(int): """Degree type used by quadrilaterals. Unlike int, values of this type are not decremeneted by _reduce_degree. """ pass class SumDegreeEstimator(MultiFunction): "This algorithm is exact for a few operators and heuristic for many." def __init__(self, default_degree, element_replace_map): MultiFunction.__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 is_cellwise_constant(v): return 0 else: # As a heuristic, just returning domain degree to bump up degree somewhat return v.ufl_domain().ufl_coordinate_element().degree() def spatial_coordinate(self, v): "A coordinate provides additional degrees depending on coordinate field of domain." return v.ufl_domain().ufl_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.ufl_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.ufl_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 TensorProduct elements or quadrilateral elements are involved.""" if isinstance(f, int) and not isinstance(f, IrreducibleInt): return max(f-1, 0) else: # if tuple, do not reduce return f def _add_degrees(self, v, *ops): def add_single(ops): if any(isinstance(o, IrreducibleInt) for o in ops): return IrreducibleInt(sum(ops)) else: return sum(ops) if any(isinstance(o, tuple) for o in ops): # 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(add_single, zip(*tempops))) else: return add_single(ops) def _max_degrees(self, v, *ops): def max_single(ops): if any(isinstance(o, IrreducibleInt) for o in ops): return IrreducibleInt(max(ops)) else: return max(ops) if any(isinstance(o, tuple) for o in ops): tempops = [foo if isinstance(foo, tuple) else (foo, foo) for foo in ops] return tuple(map(max_single, zip(*tempops))) else: return max_single(ops + (0,)) 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 reference_value(self, rv, f): return f 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 reference_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 reference_div = _reduce_degree nabla_div = _reduce_degree curl = _reduce_degree reference_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 a positive integer: degree(a**b) == degree(a)*b otherwise use the heuristic degree(a**b) == degree(a) + 2""" f, g = v.ufl_operands if isinstance(g, IntValue): gi = g.value() if gi >= 0: if isinstance(a, int): return a*gi else: return tuple(foo*gi for foo in a) # Something to a non-(positive integer) power, e.g. float, # negative integer, Coefficient, etc. return self._add_degrees(v, a, 2) 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. """ 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): if not e.integrals(): error("Got form with no integrals!") degrees = map_expr_dags(de, [it.integrand() for it in e.integrals()]) elif isinstance(e, Integral): degrees = map_expr_dags(de, [e.integrand()]) else: degrees = map_expr_dags(de, [e]) degree = max(degrees) if degrees else default_degree return degree ufl-2017.2.0/ufl/algorithms/apply_function_pullbacks.py0000644000231000000010000002270713211220450022244 0ustar chrisdaemon# -*- coding: utf-8 -*- """Algorithm for replacing gradients in an expression with reference gradients and coordinate mappings.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 Lizao Li , 2016 from six.moves import xrange as range from ufl.log import error from ufl.core.multiindex import indices from ufl.corealg.multifunction import MultiFunction, memoized_handler from ufl.algorithms.map_integrands import map_integrand_dags from ufl.classes import (ReferenceValue, Jacobian, JacobianInverse, JacobianDeterminant, Index) from ufl.tensors import as_tensor, as_vector from ufl.utils.sequences import product def sub_elements_with_mappings(element): "Return an ordered list of the largest subelements that have a defined mapping." if element.mapping() != "undefined": return [element] elements = [] for subelm in element.sub_elements(): if subelm.mapping() != "undefined": elements.append(subelm) else: elements.extend(sub_elements_with_mappings(subelm)) return elements def create_nested_lists(shape): if len(shape) == 0: return [None] elif len(shape) == 1: return [None]*shape[0] else: return [create_nested_lists(shape[1:]) for i in range(shape[0])] def reshape_to_nested_list(components, shape): if len(shape) == 0: assert len(components) == 1 return [components[0]] elif len(shape) == 1: assert len(components) == shape[0] return components else: n = product(shape[1:]) return [reshape_to_nested_list(components[n*i:n*(i+1)], shape[1:]) for i in range(shape[0])] def apply_single_function_pullbacks(g): element = g.ufl_element() mapping = element.mapping() r = ReferenceValue(g) gsh = g.ufl_shape rsh = r.ufl_shape # Shortcut the "identity" case which includes Expression and # Constant from dolfin that may be ill-formed without a domain # (until we get that fixed) if mapping == "identity": assert rsh == gsh return r gsize = product(gsh) rsize = product(rsh) # Create some geometric objects for reuse domain = g.ufl_domain() J = Jacobian(domain) detJ = JacobianDeterminant(domain) Jinv = JacobianInverse(domain) # Create contravariant transform for reuse (note that detJ is the # _signed_ (pseudo-)determinant) transform_hdiv = (1.0/detJ) * J # Shortcut simple cases for a more efficient representation, # including directly Piola-mapped elements and mixed elements of # any combination of affinely mapped elements without symmetries if mapping == "symmetries": fcm = element.flattened_sub_element_mapping() assert gsize >= rsize assert len(fcm) == gsize assert sorted(set(fcm)) == sorted(range(rsize)) g_components = [r[fcm[i]] for i in range(gsize)] g_components = reshape_to_nested_list(g_components, gsh) f = as_tensor(g_components) assert f.ufl_shape == g.ufl_shape return f elif mapping == "contravariant Piola": assert transform_hdiv.ufl_shape == (gsize, rsize) i, j = indices(2) f = as_vector(transform_hdiv[i, j]*r[j], i) # f = as_tensor(transform_hdiv[i, j]*r[k,j], (k,i)) # FIXME: Handle Vector(Piola) here? assert f.ufl_shape == g.ufl_shape return f elif mapping == "covariant Piola": assert Jinv.ufl_shape == (rsize, gsize) i, j = indices(2) f = as_vector(Jinv[j, i]*r[j], i) # f = as_tensor(Jinv[j, i]*r[k,j], (k,i)) # FIXME: Handle Vector(Piola) here? assert f.ufl_shape == g.ufl_shape return f elif mapping == "double covariant Piola": i, j, m, n = indices(4) f = as_tensor(Jinv[m, i]*r[m, n]*Jinv[n, j], (i, j)) assert f.ufl_shape == g.ufl_shape return f elif mapping == "double contravariant Piola": i, j, m, n = indices(4) f = as_tensor((1.0/detJ)*(1.0/detJ)*J[i, m]*r[m, n]*J[j, n], (i, j)) assert f.ufl_shape == g.ufl_shape return f # By placing components in a list and using as_vector at the end, # we're assuming below that both global function g and its # reference value r have vector shape, which is the case for most # elements with the exceptions: # - TensorElements # - All cases with scalar subelements and without symmetries # are covered by the shortcut above # (ONLY IF REFERENCE VALUE SHAPE PRESERVES TENSOR RANK) # - All cases with scalar subelements and without symmetries are # covered by the shortcut above # - VectorElements of vector-valued basic elements (FIXME) # - TensorElements with symmetries (FIXME) assert len(gsh) == 1 assert len(rsh) == 1 g_components = [None]*gsize gpos = 0 rpos = 0 for subelm in sub_elements_with_mappings(element): gm = product(subelm.value_shape()) rm = product(subelm.reference_value_shape()) mp = subelm.mapping() if mp == "identity": assert gm == rm for i in range(gm): g_components[gpos + i] = r[rpos + i] elif mp == "symmetries": """ tensor_element.value_shape() == (2,2) tensor_element.reference_value_shape() == (3,) tensor_element.symmetry() == { (1,0): (0,1) } tensor_element.component_mapping() == { (0,0): 0, (0,1): 1, (1,0): 1, (1,1): 2 } tensor_element.flattened_component_mapping() == { 0: 0, 1: 1, 2: 1, 3: 2 } """ fcm = subelm.flattened_sub_element_mapping() assert gm >= rm assert len(fcm) == gm assert sorted(set(fcm)) == sorted(range(rm)) for i in range(gm): g_components[gpos + i] = r[rpos + fcm[i]] elif mp == "contravariant Piola": assert transform_hdiv.ufl_shape == (gm, rm) # Get reference value vector corresponding to this subelement: rv = as_vector([r[rpos+k] for k in range(rm)]) # Apply transform with IndexSum over j for each row j = Index() for i in range(gm): g_components[gpos + i] = transform_hdiv[i, j]*rv[j] elif mp == "covariant Piola": assert Jinv.ufl_shape == (rm, gm) # Get reference value vector corresponding to this subelement: rv = as_vector([r[rpos+k] for k in range(rm)]) # Apply transform with IndexSum over j for each row j = Index() for i in range(gm): g_components[gpos + i] = Jinv[j, i]*rv[j] elif mp == "double covariant Piola": # components are flatten, map accordingly rv = as_vector([r[rpos+k] for k in range(rm)]) dim = subelm.value_shape()[0] for i in range(dim): for j in range(dim): gv = 0 # int times Index is not allowed. so sum by hand for m in range(dim): for n in range(dim): gv += Jinv[m, i]*rv[m*dim+n]*Jinv[n, j] g_components[gpos + i * dim + j] = gv elif mp == "double contravariant Piola": # components are flatten, map accordingly rv = as_vector([r[rpos+k] for k in range(rm)]) dim = subelm.value_shape()[0] for i in range(dim): for j in range(dim): gv = 0 # int times Index is not allowed. so sum by hand for m in range(dim): for n in range(dim): gv += (1.0/detJ)*(1.0/detJ)*J[i, m]*rv[m*dim+n]*J[j, n] g_components[gpos + i * dim + j] = gv else: error("Unknown subelement mapping type %s for element %s." % (mp, str(subelm))) gpos += gm rpos += rm # Wrap up components in a vector, must return same shape as input # function g assert len(gsh) == 1 f = as_vector(g_components) assert f.ufl_shape == g.ufl_shape return f class FunctionPullbackApplier(MultiFunction): def __init__(self): MultiFunction.__init__(self) expr = MultiFunction.reuse_if_untouched def terminal(self, t): return t @memoized_handler def form_argument(self, o): # Represent 0-derivatives of form arguments on reference # element return apply_single_function_pullbacks(o) def apply_function_pullbacks(expr): """Change representation of coefficients and arguments in expression by applying Piola mappings where applicable and representing all form arguments in reference value. @param expr: An Expr. """ return map_integrand_dags(FunctionPullbackApplier(), expr) ufl-2017.2.0/ufl/algorithms/compute_form_data.py0000644000231000000010000004031613211220450020636 0ustar chrisdaemon# -*- coding: utf-8 -*- """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-2016 Martin Sandve Alnæs # # 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, info from ufl.utils.sequences import max_degree from ufl.classes import GeometricFacetQuantity, Coefficient, Form from ufl.corealg.traversal import traverse_unique_terminals from ufl.algorithms.analysis import extract_coefficients, extract_sub_elements, unique_tuple from ufl.algorithms.formdata import FormData from ufl.algorithms.formtransformations import compute_form_arities from ufl.algorithms.check_arities import check_form_arity # These are the main symbolic processing steps: from ufl.algorithms.apply_function_pullbacks import apply_function_pullbacks from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering from ufl.algorithms.apply_derivatives import apply_derivatives from ufl.algorithms.apply_integral_scaling import apply_integral_scaling from ufl.algorithms.apply_geometry_lowering import apply_geometry_lowering from ufl.algorithms.apply_restrictions import apply_restrictions, apply_default_restrictions from ufl.algorithms.estimate_degrees import estimate_total_polynomial_degree # See TODOs at the call sites of these below: from ufl.algorithms.domain_analysis import build_integral_data from ufl.algorithms.domain_analysis import reconstruct_form_from_integral_data from ufl.algorithms.domain_analysis import group_form_integrals 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_degree({e.degree() for e in elements} - {None} | {1}) def _compute_element_mapping(form): "Compute element mapping for element replacement" # The element mapping is a slightly messy concept with two use # cases: # - Expression with missing cell or element TODO: Implement proper # Expression handling in UFL and get rid of this # - Constant with missing cell TODO: Fix anything that needs to be # worked around to drop this requirement # Extract all elements and include subelements of mixed elements elements = [obj.ufl_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 cell cell = element.cell() if cell is None: domains = form.ufl_domains() if not all(domains[0].ufl_cell() == d.ufl_cell() for d in domains): error("Cannot replace unknown element cell without unique common cell in form.") cell = domains[0].ufl_cell() info("Adjusting missing element cell to %s." % (cell,)) 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(cell=cell, degree=degree) else: element_mapping[element] = element return element_mapping def _compute_max_subdomain_ids(integral_data): max_subdomain_ids = {} 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 = max_subdomain_ids.get(it, 0) max_subdomain_ids[it] = max(prevmax, newmax) return max_subdomain_ids def _compute_form_data_elements(self, arguments, coefficients, domains): self.argument_elements = tuple(f.ufl_element() for f in arguments) self.coefficient_elements = tuple(f.ufl_element() for f in coefficients) self.coordinate_elements = tuple(domain.ufl_coordinate_element() for domain in domains) # TODO: Include coordinate elements from argument and coefficient # domains as well? Can they differ? # Note: Removed self.elements and self.sub_elements to make sure # code that depends on the selection of argument + # coefficient elements blow up, as opposed to silently # almost working, with the introduction of the coordinate # elements here. all_elements = self.argument_elements + self.coefficient_elements + self.coordinate_elements all_sub_elements = extract_sub_elements(all_elements) self.unique_elements = unique_tuple(all_elements) self.unique_sub_elements = unique_tuple(all_sub_elements) def _check_elements(form_data): for element in chain(form_data.unique_elements, form_data.unique_sub_elements): if element.family() is None: error("Found element with undefined familty: %s" % repr(element)) if element.cell() is None: error("Found element with undefined cell: %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 not ("facet" in it or "custom" in it or "interface" in it): # Not a facet integral for expr in traverse_unique_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 if 1 != len(compute_form_arities(preprocessed_form)): error("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.ufl_element() new_e = element_mapping.get(old_e, old_e) new_f = Coefficient(new_e, count=i) new_coefficients.append(new_f) replace_map[f] = new_f return new_coefficients, replace_map def attach_estimated_degrees(form): """Attach estimated polynomial degree to a form's integrals. :arg form: The :class:`~.Form` to inspect. :returns: A new Form with estimate degrees attached. """ integrals = form.integrals() new_integrals = [] for integral in integrals: md = {} md.update(integral.metadata()) degree = estimate_total_polynomial_degree(integral.integrand()) md["estimated_polynomial_degree"] = degree new_integrals.append(integral.reconstruct(metadata=md)) return Form(new_integrals) def compute_form_data(form, # Default arguments configured to behave the way old FFC expects it: do_apply_function_pullbacks=False, do_apply_integral_scaling=False, do_apply_geometry_lowering=False, preserve_geometry_types=(), do_apply_default_restrictions=True, do_apply_restrictions=True, do_estimate_degrees=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 # --- Pass form integrands through some symbolic manipulation # Note: Default behaviour here will process form the way that is # currently expected by vanilla FFC # Lower abstractions for tensor-algebra types into index notation, # reducing the number of operators later algorithms and form # compilers need to handle form = apply_algebra_lowering(form) # Apply differentiation before function pullbacks, because for # example coefficient derivatives are more complicated to derive # after coefficients are rewritten, and in particular for # user-defined coefficient relations it just gets too messy form = apply_derivatives(form) # --- Group form integrals # TODO: Refactor this, it's rather opaque what this does # TODO: Is self.original_form.ufl_domains() right here? # It will matter when we start including 'num_domains' in ufc form. form = group_form_integrals(form, self.original_form.ufl_domains()) # Estimate polynomial degree of integrands now, before applying # any pullbacks and geometric lowering. Otherwise quad degrees # blow up horrifically. if do_estimate_degrees: form = attach_estimated_degrees(form) if do_apply_function_pullbacks: # Rewrite coefficients and arguments in terms of their # reference cell values with Piola transforms and symmetry # transforms injected where needed. # Decision: Not supporting grad(dolfin.Expression) without a # Domain. Current dolfin works if Expression has a # cell but this should be changed to a mesh. form = apply_function_pullbacks(form) # Scale integrals to reference cell frames if do_apply_integral_scaling: form = apply_integral_scaling(form) # Apply default restriction to fully continuous terminals if do_apply_default_restrictions: form = apply_default_restrictions(form) # Lower abstractions for geometric quantities into a smaller set # of quantities, allowing the form compiler to deal with a smaller # set of types and treating geometric quantities like any other # expressions w.r.t. loop-invariant code motion etc. if do_apply_geometry_lowering: form = apply_geometry_lowering(form, preserve_geometry_types) # Apply differentiation again, because the algorithms above can # generate new derivatives or rewrite expressions inside # derivatives if do_apply_function_pullbacks or do_apply_geometry_lowering: form = apply_derivatives(form) # Neverending story: apply_derivatives introduces new Jinvs, # which needs more geometry lowering if do_apply_geometry_lowering: form = apply_geometry_lowering(form, preserve_geometry_types) # Lower derivatives that may have appeared form = apply_derivatives(form) # Propagate restrictions to terminals if do_apply_restrictions: form = apply_restrictions(form) # --- Group integrals into IntegralData objects # Most of the heavy lifting is done above in group_form_integrals. self.integral_data = build_integral_data(form.integrals()) # --- 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())) # 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(self.original_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] # --- Collect some trivial data # Get rank of form from argument list (assuming not a mixed arity form) self.rank = len(self.original_form.arguments()) # Extract common geometric dimension (topological is not common!) self.geometric_dimension = self.original_form.integrals()[0].ufl_domain().geometric_dimension() # --- 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 # Expression-like functions that can be evaluated in quadrature # points. self.element_replace_map = _compute_element_mapping(self.original_form) # 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, self.original_form.arguments(), renumbered_coefficients, self.original_form.ufl_domains()) # --- Store number of domains for integral types # TODO: Group this by domain first. For now keep a backwards # compatible data structure. self.max_subdomain_ids = _compute_max_subdomain_ids(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, self.original_form.arguments()) # Currently testing how fast this is # TODO: This member is used by unit tests, change the tests to # remove this! self.preprocessed_form = preprocessed_form return self ufl-2017.2.0/ufl/algorithms/apply_algebra_lowering.py0000644000231000000010000001324513211220450021657 0ustar chrisdaemon# -*- coding: utf-8 -*- """Algorithm for expanding compound expressions into equivalent representations using basic operators.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs 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 from ufl.classes import Product, Grad from ufl.core.multiindex import indices, Index, FixedIndex from ufl.tensors import as_tensor, as_matrix, as_vector from ufl.compound_expressions import deviatoric_expr, determinant_expr, cofactor_expr, inverse_expr from ufl.corealg.multifunction import MultiFunction from ufl.algorithms.map_integrands import map_integrand_dags class LowerCompoundAlgebra(MultiFunction): """Expands high level compound operators (e.g. inner) to equivalent representations using basic operators (e.g. index notation).""" def __init__(self): MultiFunction.__init__(self) expr = MultiFunction.reuse_if_untouched # ------------ 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 if sh[0] != sh[1]: error("Expecting square matrix.") if sh[0] is None: error("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 altenative_dot(self, o, a, b): # TODO: Test this ash = a.ufl_shape bsh = b.ufl_shape ai = indices(len(ash) - 1) bi = indices(len(bsh) - 1) # Simplification for tensors where the dot-sum dimension has # length 1 if ash[-1] == 1: k = (FixedIndex(0),) else: k = (Index(),) # Potentially creates a single IndexSum over a Product s = a[ai+k]*b[k+bi] return as_tensor(s, ai+bi) def dot(self, o, a, b): ai = indices(len(a.ufl_shape)-1) bi = indices(len(b.ufl_shape)-1) k = (Index(),) # Creates a single IndexSum over a Product s = a[ai+k]*b[k+bi] return as_tensor(s, ai+bi) def alternative_inner(self, o, a, b): # TODO: Test this ash = a.ufl_shape bsh = b.ufl_shape if ash != bsh: error("Nonmatching shapes.") # Simplification for tensors with one or more dimensions of # length 1 ii = [] zi = FixedIndex(0) for n in ash: if n == 1: ii.append(zi) else: ii.append(Index()) ii = tuple(ii) # ii = indices(len(a.ufl_shape)) # Potentially creates multiple IndexSums over a Product s = a[ii]*b[ii] return s def inner(self, o, a, b): ash = a.ufl_shape bsh = b.ufl_shape if ash != bsh: error("Nonmatching shapes.") ii = indices(len(ash)) # Creates multiple IndexSums over a Product s = a[ii]*b[ii] return s def outer(self, o, a, b): ii = indices(len(a.ufl_shape)) jj = indices(len(b.ufl_shape)) # 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 nabla_div(self, o, a): i = Index() return a[i, ...].dx(i) def nabla_grad(self, o, a): sh = a.ufl_shape if sh == (): return Grad(a) else: j = Index() ii = tuple(indices(len(sh))) return as_tensor(a[ii].dx(j), (j,) + ii) 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,)) def apply_algebra_lowering(expr): """Expands high level compound operators (e.g. inner) to equivalent representations using basic operators (e.g. index notation).""" return map_integrand_dags(LowerCompoundAlgebra(), expr) ufl-2017.2.0/ufl/algorithms/apply_restrictions.py0000644000231000000010000002225013211220450021100 0ustar chrisdaemon# -*- coding: utf-8 -*- """This module contains the apply_restrictions algorithm which propagates restrictions in a form towards the terminals.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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.classes import 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 if self.current_restriction is not None: error("Cannot restrict an expression twice.") # Configure a propagator for this side and apply to subtree # FIXME: Reuse cache between these calls! return map_expr_dag(self._rp[o.side()], o.ufl_operands[0]) # --- 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." if self.current_restriction is None: error("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. # Considering all grads to be discontinuous, may # want something else for facet functions in future. grad = _require_restriction # 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, label): "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 # Require handlers to be specified for all terminals terminal = _missing_rule multi_index = _ignore_restriction label = _ignore_restriction # Default: Literals should ignore restriction constant_value = _ignore_restriction # Even arguments with continuous elements such as Lagrange must be # restricted to associate with the right part of the element # matrix argument = _require_restriction # Defaults for geometric quantities geometric_cell_quantity = _require_restriction geometric_facet_quantity = _require_restriction # Only a few geometric quantities are independent on the restriction: facet_coordinate = _ignore_restriction quadrature_weight = _ignore_restriction # Assuming homogeoneous mesh reference_cell_volume = _ignore_restriction reference_facet_volume = _ignore_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.ufl_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.ufl_domain() e = D.ufl_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) 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) class DefaultRestrictionApplier(MultiFunction): def __init__(self, side=None): MultiFunction.__init__(self) self.current_restriction = side self.default_restriction = "+" if self.current_restriction is None: self._rp = {"+": DefaultRestrictionApplier("+"), "-": DefaultRestrictionApplier("-")} def terminal(self, o): # Most terminals are unchanged return o # Default: Operators should reconstruct only if subtrees are not touched operator = MultiFunction.reuse_if_untouched def restricted(self, o): # Don't restrict twice return o def derivative(self, o): # I don't think it's safe to just apply default restriction # to the argument of any derivative, i.e. grad(cg1_function) # is not continuous across cells even if cg1_function is. return o 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) # These are the same from either side but to compute them # cell (or facet) data from one side must be selected: spatial_coordinate = _default_restricted # Depends on cell only to get to the facet: facet_jacobian = _default_restricted facet_jacobian_determinant = _default_restricted facet_jacobian_inverse = _default_restricted # facet_tangents = _default_restricted # facet_midpoint = _default_restricted facet_area = _default_restricted # facet_diameter = _default_restricted min_facet_edge_length = _default_restricted max_facet_edge_length = _default_restricted facet_origin = _default_restricted # FIXME: Is this valid for quads? def apply_default_restrictions(expression): """Some terminals can be restricted from either side. This applies a default restriction to such terminals if unrestricted.""" integral_types = [k for k in integral_type_to_measure_name.keys() if k.startswith("interior_facet")] rules = DefaultRestrictionApplier() return map_integrand_dags(rules, expression, only_integral_type=integral_types) ufl-2017.2.0/ufl/algorithms/formdata.py0000644000231000000010000000642513211220450016746 0ustar chrisdaemon# -*- coding: utf-8 -*- """FormData class easy for collecting of various data about a form.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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.utils.formatting import lstr, tstr, estr 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 __unicode__(self): # Only in python 2 return str(self).decode("utf-8") def __str__(self): "Return formatted summary of form data" types = sorted(self.max_subdomain_ids.keys()) geometry = ( ("Geometric dimension", self.geometric_dimension), ) subdomains = tuple(("Number of %s subdomains" % integral_type, self.max_subdomain_ids[integral_type]) for integral_type in types) functions = ( # 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)), ) return tstr(geometry + subdomains + functions) 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 __unicode__(self): # Only in python 2 return str(self).decode("utf-8") 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-2017.2.0/ufl/algorithms/ad.py0000644000231000000010000000316513211220450015533 0ustar chrisdaemon# -*- coding: utf-8 -*- """Front-end for AD routines.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering from ufl.algorithms.apply_derivatives import apply_derivatives def expand_derivatives(form, **kwargs): """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. """ # For a deprecation period (I see that dolfin-adjoint passes some # args here) if kwargs: warning("Deprecation: expand_derivatives no longer takes any keyword arguments") # Lower abstractions for tensor-algebra types into index notation form = apply_algebra_lowering(form) # Apply differentiation form = apply_derivatives(form) return form ufl-2017.2.0/ufl/algorithms/domain_analysis.py0000644000231000000010000003026313211220450020320 0ustar chrisdaemon# -*- coding: utf-8 -*- """Algorithms for building canonical data structure for integrals over subdomains.""" # Copyright (C) 2009-2016 Anders Logg and Martin Sandve Alnæs # # 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 import iteritems from six.moves import zip import ufl from ufl.log import error from ufl.utils.py23 import as_native_strings from ufl.integral import Integral from ufl.form import Form from ufl.sorting import cmp_expr, sorted_expr from ufl.utils.sorting import canonicalize_metadata, sorted_by_key import numbers 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__ = as_native_strings(('domain', 'integral_type', 'subdomain_id', 'integrals', 'metadata', 'integral_coefficients', 'enabled_coefficients')) def __init__(self, domain, integral_type, subdomain_id, integrals, metadata): if 1 != len(set(itg.ufl_domain() for itg in integrals)): error("Multiple domains mismatch in integral data.") if not all(integral_type == itg.integral_type() for itg in integrals): error("Integral type mismatch in integral data.") if not all(subdomain_id == itg.subdomain_id() for itg in integrals): error("Subdomain 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 __unicode__(self): # Only in python 2 return str(self).decode("utf-8") def __str__(self): s = "IntegralData 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) return s 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__ = as_native_strings(('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 AbstractDomain objects from the parent Form Output: integrals_by_domain_and_type: dict: (domain, integral_type) -> list(Integral) """ integrals_by_domain_and_type = defaultdict(list) for itg in integrals: if itg.ufl_domain() is None: error("Integral has no domain.") key = (itg.ufl_domain(), itg.integral_type()) # Append integral to list of integrals with shared key integrals_by_domain_and_type[key].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, numbers.Integral): return (did,) elif isinstance(did, tuple): if not all(isinstance(d, numbers.Integral) for d in did): error("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): """Build integral data given a list of integrals. :arg integrals: An iterable of :class:`~.Integral` objects. :returns: A tuple of :class:`IntegralData` objects. The integrals you pass in here must have been rearranged and gathered (removing the "everywhere" subdomain_id. To do this, you should call :func:`group_form_integrals`. """ itgs = defaultdict(list) for integral in integrals: domain = integral.ufl_domain() integral_type = integral.integral_type() subdomain_id = integral.subdomain_id() if subdomain_id == "everywhere": raise ValueError("'everywhere' not a valid subdomain id. Did you forget to call group_form_integrals?") # Group for integral data (One integral data object for all # integrals with same domain, itype, subdomain_id (but # possibly different metadata). itgs[(domain, integral_type, subdomain_id)].append(integral) # Build list with canonical ordering, iteration over dicts # is not deterministic across python versions def keyfunc(item): (d, itype, sid), integrals = item return (d._ufl_sort_key_(), itype, (type(sid).__name__, sid)) integral_datas = [] for (d, itype, sid), integrals in sorted(iteritems(itgs), key=keyfunc): integral_datas.append(IntegralData(d, itype, sid, integrals, {})) return integral_datas def group_form_integrals(form, domains): """Group integrals by domain and type, performing canonical simplification. :arg form: the :class:`~.Form` to group the integrals of. :arg domains: an iterable of :class:`~.Domain`\s. :returns: A new :class:`~.Form` with gathered integrands. """ # Group integrals by domain and type integrals_by_domain_and_type = \ group_integrals_by_domain_and_type(form.integrals(), domains) integrals = [] 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) for integrand, metadata in integrands_and_cds: integrals.append(Integral(integrand, integral_type, domain, subdomain_id, metadata, None)) return Form(integrals) def reconstruct_form_from_integral_data(integral_data): integrals = [] for ida in integral_data: integrals.extend(ida.integrals) return Form(integrals) ufl-2017.2.0/ufl/algorithms/signature.py0000644000231000000010000001462313211220450017151 0ustar chrisdaemon# -*- coding: utf-8 -*- """Signature computation for forms.""" # Copyright (C) 2012-2016 Martin Sandve Alnæs # # 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 (Label, Index, MultiIndex, Coefficient, Argument, GeometricQuantity, ConstantValue, ExprList, ExprMapping) from ufl.log import error from ufl.utils.py23 import as_bytes from ufl.corealg.traversal import traverse_unique_terminals, pre_traversal 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._ufl_signature_data_(renumbering) elif isinstance(expr, Coefficient): data = expr._ufl_signature_data_(renumbering) elif isinstance(expr, Argument): data = expr._ufl_signature_data_(renumbering) elif isinstance(expr, GeometricQuantity): data = expr._ufl_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._ufl_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 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 # (should we use sha1 instead?) data = as_bytes(str(expression_hashdata)) return hashlib.sha512(data).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.ufl_domain()._ufl_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 it through a seriously overkill hashing algorithm # (should we use sha1 instead?) data = as_bytes(str(hashdata)) return hashlib.sha512(data).hexdigest() ufl-2017.2.0/ufl/algorithms/__init__.py0000644000231000000010000001164613211220450016711 0ustar chrisdaemon# -*- coding: utf-8 -*- # flake8: noqa "This module collects algorithms and utility functions operating on UFL objects." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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. # FIXME: Clean up this to become a more official set of supported # algorithms. Currently contains too much stuff that's not # recommended to use. The __all__ list below is a start based # on grepping of other FEniCS code for ufl.algorithm imports. from ufl.utils.py23 import as_native_strings __all__ = as_native_strings([ "estimate_total_polynomial_degree", "sort_elements", "compute_form_data", "purge_list_tensors", "apply_transformer", "ReuseTransformer", "load_ufl_file", "Transformer", "MultiFunction", "extract_unique_elements", "extract_type", "extract_elements", "extract_sub_elements", "preprocess_expression", "expand_indices", "replace", "expand_derivatives", "extract_coefficients", "strip_variables", "post_traversal", "change_to_reference_grad", "expand_compounds", "validate_form", "ufl2latex", "FormSplitter", "extract_arguments", "compute_form_adjoint", "compute_form_action", "compute_energy_norm", "compute_form_lhs", "compute_form_rhs", "compute_form_functional", "compute_form_signature", "tree_format", ]) # 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 from ufl.corealg.traversal import 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.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, ReuseTransformer # from ufl.algorithms.transformer import is_post_handler from ufl.algorithms.transformer import apply_transformer from ufl.algorithms.transformer import strip_variables # from ufl.algorithms.replace import Replacer from ufl.algorithms.replace import replace from ufl.algorithms.change_to_reference import change_to_reference_grad from ufl.algorithms.expand_compounds import expand_compounds # from ufl.algorithms.estimate_degrees import SumDegreeEstimator from ufl.algorithms.estimate_degrees import estimate_total_polynomial_degree from ufl.algorithms.expand_indices import expand_indices, purge_list_tensors # Utilities for transforming complete Forms into other Forms from ufl.algorithms.formtransformations import compute_form_adjoint from ufl.algorithms.formtransformations import compute_form_action from ufl.algorithms.formtransformations import compute_energy_norm from ufl.algorithms.formtransformations import compute_form_lhs from ufl.algorithms.formtransformations import compute_form_rhs from ufl.algorithms.formtransformations import compute_form_functional from ufl.algorithms.formtransformations import compute_form_arities from ufl.algorithms.formsplitter import FormSplitter # Utilities for Automatic Functional Differentiation from ufl.algorithms.ad import expand_derivatives # Utilities for form file handling from ufl.algorithms.formfiles import read_ufl_file from ufl.algorithms.formfiles import load_ufl_file from ufl.algorithms.formfiles import load_forms # Utilities for UFL object printing # from ufl.formatting.printing import integral_info, form_info from ufl.formatting.printing import tree_format from ufl.formatting.ufl2latex import ufl2latex ufl-2017.2.0/ufl/algorithms/map_integrands.py0000644000231000000010000000440213211220450020135 0ustar chrisdaemon# -*- coding: utf-8 -*- """Basic algorithms for applying functions to subexpressions.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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.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): 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-2017.2.0/ufl/algorithms/formfiles.py0000644000231000000010000002015713211220450017135 0ustar chrisdaemon# -*- coding: utf-8 -*- """A collection of utility algorithms for handling UFL files.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 six import io import os import re import ufl from ufl.log import error, warning from ufl.utils.sorting import sorted_by_key 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__ def read_lines_decoded(fn): r = re.compile(b".*coding: *([^ ]+)") if six.PY3: def match(line): return r.match(line, re.ASCII) else: def match(line): return r.match(line) # First read lines as bytes with io.open(fn, "rb") as f: lines = f.readlines() # Check for coding: in the first two lines for i in range(min(2, len(lines))): m = match(lines[i]) if m: encoding, = m.groups() # Drop encoding line lines = lines[:i] + lines[i+1:] break else: # Default to utf-8 (works for ascii files # as well, default for python files in py3) encoding = "utf-8" # Decode all lines lines = [line.decode(encoding=encoding) for line in lines] return lines def replace_include_statements(lines): "Replace '#include foo.ufl' statements with contents of foo.ufl." r = re.compile(r"^#include (.*)$") newlines = [] for l in lines: m = r.search(l) if m: fn = m.groups()[0] newlines.append("# --- begin %s\n" % fn) newlines.extend(read_lines_decoded(fn)) newlines.append("# --- end %s\n" % fn) else: newlines.append(l) return newlines 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) lines = read_lines_decoded(filename) lines = replace_include_statements(lines) code = "".join(lines) return code infostring = """\ An exception occured during evaluation of .ufl file. If you need to debug it as a python script, rename it to .py and add the lines from ufl import * set_level(DEBUG) at the top then run with python. """ def execute_ufl_code(uflcode, filename): # Execute code namespace = {} namespace.update(vars(ufl)) try: exec(uflcode, namespace) except Exception as e: warning(infostring) raise e 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 if not isinstance(ufd.forms, (list, tuple)): error("Expecting 'forms' to be a list or tuple, not '%s'." % type(ufd.forms)) if not all(isinstance(a, Form) for a in ufd.forms): error("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 if not isinstance(ufd.elements, (list, tuple)): error("Expecting 'elements' to be a list or tuple, not '%s'." % type(ufd.elements)) if not all(isinstance(e, FiniteElementBase) for e in ufd.elements): error("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) # Validate types if not isinstance(ufd.coefficients, (list, tuple)): error("Expecting 'coefficients' to be a list or tuple, not '%s'." % type(ufd.coefficients)) if not all(isinstance(e, Coefficient) for e in ufd.coefficients): error("Expecting 'coefficients' to be a list of Coefficient instances.") # Get list of exported expressions ufd.expressions = namespace.get("expressions", []) # Validate types if not isinstance(ufd.expressions, (list, tuple)): error("Expecting 'expressions' to be a list or tuple, not '%s'." % type(ufd.expressions)) if not all(isinstance(e, Expr) for e in ufd.expressions): error("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-2017.2.0/ufl/algorithms/renumbering.py0000644000231000000010000000572313211220450017466 0ustar chrisdaemon# -*- coding: utf-8 -*- "Algorithms for renumbering of counted objects, currently variables and indices." # Copyright (C) 2008-2016 Martin Sandve Alnæs 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.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.classes import Zero class VariableRenumberingTransformer(ReuseTransformer): def __init__(self): ReuseTransformer.__init__(self) self.variable_map = {} def variable(self, o): e, l = o.ufl_operands # noqa: E741 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): "This is a poorly designed algorithm. It is used in some tests, please do not use for anything else." def __init__(self): VariableRenumberingTransformer.__init__(self) self.index_map = {} def zero(self, o): fi = o.ufl_free_indices fid = o.ufl_index_dimensions mapped_fi = tuple(self.index(Index(count=i)) for i in fi) paired_fid = [(mapped_fi[pos], fid[pos]) for pos, a in enumerate(fi)] new_fi, new_fid = zip(*tuple(sorted(paired_fid))) return Zero(o.ufl_shape, new_fi, new_fid) 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): new_indices = tuple(self.index(i) for i in o.indices()) return MultiIndex(new_indices) def renumber_indices(expr): if isinstance(expr, Expr): num_free_indices = len(expr.ufl_free_indices) result = apply_transformer(expr, IndexRenumberingTransformer()) if isinstance(expr, Expr): if num_free_indices != len(result.ufl_free_indices): error("The number of free indices left in expression should be invariant w.r.t. renumbering.") return result ufl-2017.2.0/ufl/algorithms/apply_derivatives.py0000644000231000000010000011402313211220450020675 0ustar chrisdaemon# -*- coding: utf-8 -*- """This module contains the apply_derivatives algorithm which computes the derivatives of a form of expression.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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.core.expr import ufl_err_str from ufl.core.terminal import Terminal from ufl.core.multiindex import MultiIndex, 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, ReferenceValue from ufl.classes import Grad, ReferenceGrad, Variable from ufl.classes import Indexed, ListTensor, ComponentTensor from ufl.classes import ExprList, ExprMapping from ufl.classes import Product, Sum, IndexSum from ufl.classes import JacobianInverse from ufl.classes import SpatialCoordinate from ufl.constantvalue import is_true_ufl_scalar, is_ufl_scalar from ufl.operators import (conditional, sign, sqrt, exp, ln, cos, sin, cosh, sinh, 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 from ufl.checks import is_cellwise_constant # TODO: Add more rulesets? # - DivRuleset # - CurlRuleset # - ReferenceGradRuleset # - ReferenceDivRuleset # Set this to True to enable previously default workaround # for bug in FFC handling of conditionals, uflacs does not # have this bug. CONDITIONAL_WORKAROUND = False 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__)) # --- Some types just don't have any derivative, this is just to # --- make algorithm structure generic def non_differentiable_terminal(self, o): "Labels and indices are not differentiable. It's convenient to return the non-differentiated object." return o label = non_differentiable_terminal multi_index = non_differentiable_terminal # --- Helper functions for creating zeros with the right shapes 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 # Literals are by definition independent of any differentiation variable 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_vertices = unexpected # cell_edge_vectors = unexpected # facet_edge_vectors = unexpected # reference_cell_edge_vectors = unexpected # reference_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, unused_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 = len(Ap.ufl_shape) - 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 if not is_ufl_scalar(f): error("Not expecting nonscalar nominator") if not is_true_ufl_scalar(g): error("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 if not is_true_ufl_scalar(f): error("Expecting scalar expression f in f**g.") if not is_true_ufl_scalar(g): error("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 if isinstance(f, Zero): error("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 if isinstance(fp, ConstantValue): return fp # TODO: Add simplification to Restricted 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, unused_dc, dt, df): global CONDITIONAL_WORKAROUND if isinstance(dt, Zero) and isinstance(df, Zero): # Assuming dt and df have the same indices here, which # should be the case return dt elif CONDITIONAL_WORKAROUND: # Placing t[1],f[1] outside here to avoid getting # arguments inside conditionals. This will fail when dt # or df become NaN or Inf in floating point computations! c = o.ufl_operands[0] dc = conditional(c, 1, 0) return dc*dt + (1.0 - dc)*df else: # Not placing t[1],f[1] outside, allowing arguments inside # conditionals. This will make legacy ffc fail, but # should work with uflacs. c = o.ufl_operands[0] return conditional(c, dt, 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): """Default for geometric quantities is dg/dx = 0 if piecewise constant, otherwise keep Grad(g). Override for specific types if other behaviour is needed.""" if is_cellwise_constant(o): 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 jacobian_inverse(self, o): # grad(K) == K_ji rgrad(K)_rj if is_cellwise_constant(o): return self.independent_terminal(o) if not o._ufl_is_terminal_: error("ReferenceValue can only wrap a terminal") r = indices(len(o.ufl_shape)) i, j = indices(2) Do = as_tensor(o[j, i]*ReferenceGrad(o)[r + (j,)], r + (i,)) return Do # TODO: Add more explicit geometry type handlers here, with # non-affine domains several should be non-zero. def spatial_coordinate(self, o): "dx/dx = I" return self._Id def cell_coordinate(self, o): "dX/dx = inv(dx/dX) = inv(J) = K" # FIXME: Is this true for manifolds? What about orientation? return JacobianInverse(o.ufl_domain()) # --- Specialized rules for form arguments def coefficient(self, o): if is_cellwise_constant(o): return self.independent_terminal(o) return Grad(o) def argument(self, o): # TODO: Enable this after fixing issue#13, unless we move # simplificat ion to a separate stage? # if is_cellwise_constant(o): # # Collapse gradient of cellwise constant function to zero # # TODO: Missing this type # return AnnotatedZero(o.ufl_shape + self._var_shape, arguments=(o,)) return Grad(o) # --- Rules for values or derivatives in reference frame def reference_value(self, o): # grad(o) == grad(rv(f)) -> K_ji*rgrad(rv(f))_rj f = o.ufl_operands[0] if not f._ufl_is_terminal_: error("ReferenceValue can only wrap a terminal") domain = f.ufl_domain() K = JacobianInverse(domain) r = indices(len(o.ufl_shape)) i, j = indices(2) Do = as_tensor(K[j, i]*ReferenceGrad(o)[r + (j,)], r + (i,)) return Do def reference_grad(self, o): # grad(o) == grad(rgrad(rv(f))) -> K_ji*rgrad(rgrad(rv(f)))_rj f = o.ufl_operands[0] valid_operand = f._ufl_is_in_reference_frame_ or isinstance(f, (JacobianInverse, SpatialCoordinate)) if not valid_operand: error("ReferenceGrad can only wrap a reference frame type!") domain = f.ufl_domain() K = JacobianInverse(domain) r = indices(len(o.ufl_shape)) i, j = indices(2) Do = as_tensor(K[j, i]*ReferenceGrad(o)[r + (j,)], r + (i,)) return Do # --- Nesting of gradients def grad(self, o): "Represent grad(grad(f)) as Grad(Grad(f))." # Check that o is a "differential terminal" if not isinstance(o.ufl_operands[0], (Grad, Terminal)): error("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 is_cellwise_constant(o): # 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 ReferenceGradRuleset(GenericDerivativeRuleset): def __init__(self, topological_dimension): GenericDerivativeRuleset.__init__(self, var_shape=(topological_dimension,)) self._Id = Identity(topological_dimension) # --- Specialized rules for geometric quantities def geometric_quantity(self, o): "dg/dX = 0 if piecewise constant, otherwise ReferenceGrad(g)" if is_cellwise_constant(o): return self.independent_terminal(o) else: # TODO: Which types does this involve? I don't think the # form compilers will handle this. return ReferenceGrad(o) def spatial_coordinate(self, o): "dx/dX = J" # Don't convert back to J, otherwise we get in a loop return ReferenceGrad(o) def cell_coordinate(self, o): "dX/dX = I" return self._Id # TODO: Add more geometry types here, with non-affine domains # several should be non-zero. # --- Specialized rules for form arguments def reference_value(self, o): if not o.ufl_operands[0]._ufl_is_terminal_: error("ReferenceValue can only wrap a terminal") return ReferenceGrad(o) def coefficient(self, o): error("Coefficient should be wrapped in ReferenceValue by now") def argument(self, o): error("Argument should be wrapped in ReferenceValue by now") # --- Nesting of gradients def grad(self, o): error("Grad should have been transformed by this point, but got {0}.".format(type(o).__name__)) def reference_grad(self, o): "Represent ref_grad(ref_grad(f)) as RefGrad(RefGrad(f))." # Check that o is a "differential terminal" if not isinstance(o.ufl_operands[0], (ReferenceGrad, ReferenceValue, Terminal)): error("Expecting only grads applied to a terminal.") return ReferenceGrad(o) 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) if var.ufl_free_indices: error("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" if not isinstance(o.ufl_operands[0], (Grad, Terminal)): error("Expecting only grads applied to a terminal.") return self.independent_terminal(o) # --- Rules for values or derivatives in reference frame def reference_value(self, o): # d/dv(o) == d/dv(rv(f)) = 0 if v is not f, or rv(dv/df) v = self._variable if isinstance(v, Coefficient) and o.ufl_operands[0] == v: if v.ufl_element().mapping() != "identity": # FIXME: This is a bit tricky, instead of Identity it is # actually inverse(transform), or we should rather not # convert to reference frame in the first place error("Missing implementation: To handle derivatives of rv(f) w.r.t. f for" + " mapped elements, rewriting to reference frame should not happen first...") # dv/dv = identity of rank 2*rank(v) return self._Id else: # df/v = 0 return self.independent_terminal(o) def reference_grad(self, o): "Variable derivative of a gradient of a terminal must be 0." if not isinstance(o.ufl_operands[0], (ReferenceGrad, ReferenceValue)): error("Unexpected argument to reference_grad.") 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 if not isinstance(coefficients, ExprList): error("Expecting a ExprList of coefficients.") if not isinstance(arguments, ExprList): error("Expecting a ExprList of arguments.") if not isinstance(coefficient_derivatives, ExprMapping): error("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 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 facet_avg(fp) # 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,) if len(dos) != len(self._v): error("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 reference_value(self, o): error("Currently no support for ReferenceValue in CoefficientDerivative.") # TODO: This is implementable for regular derivative(M(f),f,v) # but too messy if customized coefficient derivative # relations are given by the user. We would only need # this to allow the user to write # derivative(...ReferenceValue...,...). # f, = o.ufl_operands # if not f._ufl_is_terminal_: # error("ReferenceValue can only wrap terminals directly.") # FIXME: check all cases like in coefficient # if f is w: # # FIXME: requires that v is an Argument with the same element mapping! # return ReferenceValue(v) # else: # return self.independent_terminal(o) def reference_grad(self, o): error("Currently no support for ReferenceGrad in CoefficientDerivative.") # TODO: This is implementable for regular derivative(M(f),f,v) # but too messy if customized coefficient derivative # relations are given by the user. We would only need # this to allow the user to write # derivative(...ReferenceValue...,...). 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 %s" % ufl_err_str(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.") if not all(isinstance(k, FixedIndex) for k in vcomp): error("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: if wshape != (): error("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) if not all(isinstance(k, FixedIndex) for k in wcomp): error("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,) if len(oprimes) != len(self._v): error("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 reference_grad(self, o, f): rules = ReferenceGradRuleset(o.ufl_shape[-1]) # FIXME: Look over this and test better. 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 = len(Ap.ufl_shape) - 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-2017.2.0/ufl/algorithms/apply_geometry_lowering.py0000644000231000000010000004100413211220450022107 0ustar chrisdaemon# -*- coding: utf-8 -*- """Algorithm for lowering abstractions of geometric types. This means replacing high-level types with expressions of mostly the Jacobian and reference cell data. """ # Copyright (C) 2013-2016 Martin Sandve Alnæs # # 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 functools import reduce from itertools import combinations from ufl.log import error, warning from ufl.core.multiindex import Index, indices from ufl.corealg.multifunction import MultiFunction, memoized_handler from ufl.corealg.map_dag import map_expr_dag from ufl.measure import custom_integral_types, point_integral_types from ufl.classes import (Expr, Form, Integral, ReferenceGrad, Jacobian, JacobianInverse, JacobianDeterminant, CellOrientation, CellOrigin, CellCoordinate, FacetJacobian, FacetJacobianDeterminant, CellFacetJacobian, MaxCellEdgeLength, CellEdgeVectors, FacetEdgeVectors, CellVertices, ReferenceNormal, ReferenceCellVolume, ReferenceFacetVolume, CellVolume, SpatialCoordinate, FloatValue) # FacetJacobianInverse, # FacetOrientation, QuadratureWeight, from ufl.tensors import as_tensor, as_vector from ufl.operators import sqrt, max_value, min_value from ufl.compound_expressions import determinant_expr, cross_expr, inverse_expr class GeometryLoweringApplier(MultiFunction): def __init__(self, preserve_types=()): MultiFunction.__init__(self) # Store preserve_types as boolean lookup table self._preserve_types = [False]*Expr._ufl_num_typecodes_ for cls in preserve_types: self._preserve_types[cls._ufl_typecode_] = True expr = MultiFunction.reuse_if_untouched def terminal(self, t): return t @memoized_handler def jacobian(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() if domain.ufl_coordinate_element().mapping() != "identity": error("Piola mapped coordinates are not implemented.") # Note: No longer supporting domain.coordinates(), always # preserving SpatialCoordinate object. However if Jacobians # are not preserved, using # ReferenceGrad(SpatialCoordinate(domain)) to represent them. x = self.spatial_coordinate(SpatialCoordinate(domain)) return ReferenceGrad(x) @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): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() J = self.jacobian(Jacobian(domain)) # TODO: This could in principle use # preserve_types[JacobianDeterminant] with minor refactoring: K = inverse_expr(J) return K @memoized_handler def jacobian_determinant(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() J = self.jacobian(Jacobian(domain)) detJ = determinant_expr(J) # TODO: Is "signing" the determinant for manifolds the # cleanest approach? The alternative is to have a # specific type for the unsigned pseudo-determinant. if domain.topological_dimension() < domain.geometric_dimension(): co = CellOrientation(domain) detJ = co*detJ return detJ @memoized_handler def facet_jacobian(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_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): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() FJ = self.facet_jacobian(FacetJacobian(domain)) # This could in principle use # preserve_types[JacobianDeterminant] with minor refactoring: return inverse_expr(FJ) @memoized_handler def facet_jacobian_determinant(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() FJ = self.facet_jacobian(FacetJacobian(domain)) detFJ = determinant_expr(FJ) # TODO: Should we "sign" the facet jacobian determinant for # manifolds? It's currently used unsigned in # apply_integral_scaling. # if domain.topological_dimension() < domain.geometric_dimension(): # co = CellOrientation(domain) # detFJ = co*detFJ return detFJ @memoized_handler def spatial_coordinate(self, o): "Fall through to coordinate field of domain if it exists." if self._preserve_types[o._ufl_typecode_]: return o if o.ufl_domain().ufl_coordinate_element().mapping() != "identity": error("Piola mapped coordinates are not implemented.") # No longer supporting domain.coordinates(), always preserving # SpatialCoordinate object. return o @memoized_handler def cell_coordinate(self, o): "Compute from physical coordinates if they are known, using the appropriate mappings." if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() 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 @memoized_handler def facet_cell_coordinate(self, o): if self._preserve_types[o._ufl_typecode_]: return o error("Missing computation of facet reference coordinates " "from physical coordinates via mappings.") @memoized_handler def cell_volume(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() if not domain.is_piecewise_linear_simplex_domain(): # Don't lower for non-affine cells, instead leave it to # form compiler warning("Only know how to compute the cell volume of an affine cell.") return o r = self.jacobian_determinant(JacobianDeterminant(domain)) r0 = ReferenceCellVolume(domain) return abs(r * r0) @memoized_handler def facet_area(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() tdim = domain.topological_dimension() if not domain.is_piecewise_linear_simplex_domain(): # Don't lower for non-affine cells, instead leave it to # form compiler warning("Only know how to compute the facet area of an affine cell.") return o # Area of "facet" of interval (i.e. "area" of a vertex) is defined as 1.0 if tdim == 1: return FloatValue(1.0) r = self.facet_jacobian_determinant(FacetJacobianDeterminant(domain)) r0 = ReferenceFacetVolume(domain) return abs(r * r0) @memoized_handler def circumradius(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() if not domain.is_piecewise_linear_simplex_domain(): error("Circumradius only makes sense for affine simplex cells") cellname = domain.ufl_cell().cellname() cellvolume = self.cell_volume(CellVolume(domain)) if cellname == "interval": # Optimization for square interval; no square root needed return 0.5 * cellvolume # Compute lengths of cell edges edges = CellEdgeVectors(domain) num_edges = edges.ufl_shape[0] j = Index() elen = [sqrt(edges[e, j]*edges[e, j]) for e in range(num_edges)] if cellname == "triangle": return (elen[0] * elen[1] * elen[2]) / (4.0 * cellvolume) elif cellname == "tetrahedron": # la, lb, lc = lengths of the sides of an intermediate triangle # NOTE: Is here some hidden numbering assumption? 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)) return triangle_area / (6.0 * cellvolume) @memoized_handler def max_cell_edge_length(self, o): return self._reduce_cell_edge_length(o, max_value) @memoized_handler def min_cell_edge_length(self, o): return self._reduce_cell_edge_length(o, min_value) def _reduce_cell_edge_length(self, o, reduction_op): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() if not domain.ufl_coordinate_element().degree() == 1: # Don't lower bendy cells, instead leave it to form compiler warning("Only know how to compute cell edge lengths of P1 or Q1 cell.") return o elif domain.ufl_cell().cellname() == "interval": # Interval optimization, square root not needed return self.cell_volume(CellVolume(domain)) else: # Other P1 or Q1 cells edges = CellEdgeVectors(domain) num_edges = edges.ufl_shape[0] j = Index() elen2 = [edges[e, j]*edges[e, j] for e in range(num_edges)] return sqrt(reduce(reduction_op, elen2)) @memoized_handler def cell_diameter(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() if not domain.ufl_coordinate_element().degree() == 1: # Don't lower bendy cells, instead leave it to form compiler warning("Only know how to compute cell diameter of P1 or Q1 cell.") return o elif domain.is_piecewise_linear_simplex_domain(): # Simplices return self.max_cell_edge_length(MaxCellEdgeLength(domain)) else: # Q1 cells, maximal distance between any two vertices verts = CellVertices(domain) verts = [verts[v, ...] for v in range(verts.ufl_shape[0])] j = Index() elen2 = ((v0-v1)[j]*(v0-v1)[j] for v0, v1 in combinations(verts, 2)) return sqrt(reduce(max_value, elen2)) @memoized_handler def max_facet_edge_length(self, o): return self._reduce_facet_edge_length(o, max_value) @memoized_handler def min_facet_edge_length(self, o): return self._reduce_facet_edge_length(o, min_value) def _reduce_facet_edge_length(self, o, reduction_op): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() if domain.ufl_cell().topological_dimension() < 3: error("Facet edge lengths only make sense for topological dimension >= 3.") elif not domain.ufl_coordinate_element().degree() == 1: # Don't lower bendy cells, instead leave it to form compiler warning("Only know how to compute facet edge lengths of P1 or Q1 cell.") return o else: # P1 tetrahedron or Q1 hexahedron edges = FacetEdgeVectors(domain) num_edges = edges.ufl_shape[0] j = Index() elen2 = [edges[e, j]*edges[e, j] for e in range(num_edges)] return sqrt(reduce(reduction_op, elen2)) @memoized_handler def cell_normal(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() gdim = domain.geometric_dimension() tdim = domain.topological_dimension() if tdim == gdim - 1: # n-manifold embedded in n-1 space i = Index() J = self.jacobian(Jacobian(domain)) if tdim == 2: # Surface in 3D t0 = as_vector(J[i, 0], i) t1 = as_vector(J[i, 1], i) cell_normal = cross_expr(t0, t1) elif tdim == 1: # Line in 2D (cell normal is 'up' for a line pointing # to the 'right') cell_normal = as_vector((-J[1, 0], J[0, 0])) else: error("Cell normal not implemented for tdim %d, gdim %d" % (tdim, gdim)) # Return normalized vector, sign corrected by cell # orientation co = CellOrientation(domain) return co * cell_normal / sqrt(cell_normal[i]*cell_normal[i]) else: error("What do you want cell normal in gdim={0}, tdim={1} to be?".format(gdim, tdim)) @memoized_handler def facet_normal(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() tdim = domain.topological_dimension() if tdim == 1: # Special-case 1D (possibly immersed), for which we say # that n is just in the direction of J. J = self.jacobian(Jacobian(domain)) # dx/dX ndir = J[:, 0] gdim = domain.geometric_dimension() if gdim == 1: nlen = abs(ndir[0]) else: i = Index() nlen = sqrt(ndir[i]*ndir[i]) rn = ReferenceNormal(domain) # +/- 1.0 here n = rn[0] * ndir / nlen r = n else: # Recall that the covariant Piola transform u -> J^(-T)*u # preserves tangential components. The normal vector is # characterised by having zero tangential component in # reference and physical space. Jinv = self.jacobian_inverse(JacobianInverse(domain)) i, j = indices(2) rn = ReferenceNormal(domain) # compute signed, unnormalised normal; note transpose ndir = as_vector(Jinv[j, i] * rn[j], i) # normalise i = Index() n = ndir / sqrt(ndir[i]*ndir[i]) r = n if r.ufl_shape != o.ufl_shape: error("Inconsistent dimensions (in=%d, out=%d)." % (o.ufl_shape[0], r.ufl_shape[0])) return r def apply_geometry_lowering(form, preserve_types=()): """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 form: An Expr or Form. """ if isinstance(form, Form): newintegrals = [apply_geometry_lowering(integral, preserve_types) for integral in form.integrals()] return Form(newintegrals) elif isinstance(form, Integral): integral = form if integral.integral_type() in (custom_integral_types + point_integral_types): automatic_preserve_types = [SpatialCoordinate, Jacobian] else: automatic_preserve_types = [CellCoordinate] preserve_types = set(preserve_types) | set(automatic_preserve_types) mf = GeometryLoweringApplier(preserve_types) newintegrand = map_expr_dag(mf, integral.integrand()) return integral.reconstruct(integrand=newintegrand) elif isinstance(form, Expr): expr = form mf = GeometryLoweringApplier(preserve_types) return map_expr_dag(mf, expr) else: error("Invalid type %s" % (form.__class__.__name__,)) ufl-2017.2.0/ufl/algorithms/check_arities.py0000644000231000000010000001244313211220450017743 0ustar chrisdaemon# -*- coding: utf-8 -*- from itertools import chain from ufl.log import UFLException from ufl.corealg.traversal import traverse_unique_terminals from ufl.corealg.multifunction import MultiFunction from ufl.corealg.map_dag import map_expr_dag from ufl.classes import Argument, Zero 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_unique_terminals(o): if t._ufl_typecode_ == Argument._ufl_typecode_: raise ArityMismatch("Applying nonlinear operator {0} to expression depending on form argument {1}.".format(o._ufl_class_.__name__, 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 reference_grad = linear_operator reference_value = linear_operator # Does it make sense to have a Variable(Argument)? I see no # problem. def variable(self, o, f, l): return f # Conditional is linear on each side of the condition def conditional(self, o, c, a, b): if c: raise ArityMismatch("Condition cannot depend on form arguments ({0}).".format(a)) if a and isinstance(o.ufl_operands[2], Zero): # Allow conditional(c, arg, 0) return a elif b and isinstance(o.ufl_operands[1], Zero): # Allow conditional(c, 0, arg) return b elif a == b: # Allow conditional(c, test, test) return a else: # Do not allow e.g. conditional(c, test, trial), # conditional(c, test, nonzeroconstant) raise ArityMismatch("Conditional subexpressions with non-matching form arguments {0} vs {1}.".format(a, b)) 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-2017.2.0/ufl/algorithms/checks.py0000644000231000000010000001132613211220450016405 0ustar chrisdaemon# -*- coding: utf-8 -*- """Functions to check the validity of forms.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 error # UFL classes from ufl.core.expr import ufl_err_str from ufl.form import Form from ufl.argument import Argument from ufl.coefficient import Coefficient from ufl.constantvalue import is_true_ufl_scalar # UFL algorithms from ufl.algorithms.traversal import iter_expressions from ufl.corealg.traversal import traverse_unique_terminals from ufl.algorithms.check_restrictions import check_restrictions 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 = [] if not isinstance(form, Form): msg = "Validation failed, not a Form:\n%s" % ufl_err_str(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.ufl_domain() for e in iter_expressions(form) for t in traverse_unique_terminals(e)) - {None} if not domains: errors.append("Missing domain definition in form.") # Check that cell is the same everywhere cells = set(dom.ufl_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_unique_terminals(e): if isinstance(f, Coefficient): c = f.count() if c in coefficients: g = coefficients[c] if f is not 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 f is not 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: %s\n" % ufl_err_str(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-2017.2.0/ufl/algorithms/multifunction.py0000644000231000000010000000026013211220450020040 0ustar chrisdaemon# -*- coding: utf-8 -*- # Moved here to be usable in ufl.* files without depending on # ufl.algorithms.*... from ufl.corealg.multifunction import MultiFunction # flake8: noqa ufl-2017.2.0/ufl/algorithms/replace.py0000644000231000000010000000465013211220450016562 0ustar chrisdaemon# -*- coding: utf-8 -*- """Algorithm for replacing terminals in an expression.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs 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.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 if not all(k._ufl_is_terminal_ for k in iterkeys(mapping)): error("This implementation can only replace Terminal objects.") if not all(k.ufl_shape == v.ufl_shape for k, v in iteritems(mapping)): error("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-2017.2.0/ufl/algorithms/analysis.py0000644000231000000010000001504013211220450016765 0ustar chrisdaemon# -*- coding: utf-8 -*- """Utility algorithms for inspection of and information extraction from UFL objects in various ways.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 ufl.log import error from ufl.utils.sorting import sorted_by_count, topological_sorting from ufl.core.terminal import Terminal, FormArgument from ufl.argument import Argument from ufl.coefficient import Coefficient from ufl.algorithms.traversal import iter_expressions from ufl.corealg.traversal import unique_pre_traversal, traverse_unique_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 unique_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_unique_terminals(e) if isinstance(o, ufl_type)) else: return set(o for e in iter_expressions(a) for o in unique_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_unique_terminals else: traversal = unique_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_unique_terminals else: traversal = unique_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.ufl_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 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 = sorted(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-2017.2.0/ufl/algorithms/transformer.py0000644000231000000010000002027313211220450017510 0ustar chrisdaemon# -*- coding: utf-8 -*- """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-2016 Martin Sandve Alnæs 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 from ufl.classes import Variable, all_ufl_classes from ufl.algorithms.map_integrands import map_integrands 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 handler_name = c._ufl_handler_name_ function = getattr(self, handler_name, None) if function: cache_data[classobject._ufl_typecode_] = handler_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): # 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._ufl_expr_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._ufl_expr_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 # noqa: E741 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 # noqa: E741 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-2017.2.0/ufl/algorithms/formtransformations.py0000644000231000000010000004026013211220450021261 0ustar chrisdaemon# -*- coding: utf-8 -*- """This module defines utilities for transforming complete Forms into new related Forms.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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.log import error, warning, debug # All classes: from ufl.core.expr import ufl_err_str from ufl.argument import Argument from ufl.coefficient import Coefficient from ufl.constantvalue import Zero # 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." % ufl_err_str(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." % ufl_err_str(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.""" # 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] fs = u.ufl_function_space() if coefficient is None: coefficient = Coefficient(fs) elif coefficient.ufl_function_space() != fs: debug("Computing action of form on a coefficient in a different function 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.") if len(arguments) != 2: error("Expecting bilinear form.") v, u = arguments U = u.ufl_function_space() V = v.ufl_function_space() if U != V: error("Expecting equal finite elements for test and trial functions, got '%s' and '%s'." % (U, V)) if coefficient is None: coefficient = Coefficient(V) else: if coefficient.ufl_function_space() != U: error("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.") if len(arguments) != 2: error("Expecting bilinear form.") v, u = arguments if v.number() >= u.number(): error("Mistaken assumption in code!") if reordered_arguments is None: reordered_u = Argument(u.ufl_function_space(), number=v.number(), part=v.part()) reordered_v = Argument(v.ufl_function_space(), number=u.number(), part=u.part()) else: reordered_u, reordered_v = reordered_arguments if reordered_u.number() >= reordered_v.number(): error("Ordering of new arguments is the same as the old arguments!") if reordered_u.part() != v.part(): error("Ordering of new arguments is the same as the old arguments!") if reordered_v.part() != u.part(): error("Ordering of new arguments is the same as the old arguments!") if reordered_u.ufl_function_space() != u.ufl_function_space(): error("Element mismatch between new and old arguments (trial functions).") if reordered_v.ufl_function_space() != v.ufl_function_space(): error("Element mismatch between new and old arguments (test functions).") return replace(form, {v: reordered_v, u: reordered_u}) ufl-2017.2.0/ufl/algorithms/expand_indices.py0000644000231000000010000001726613211220450020133 0ustar chrisdaemon# -*- coding: utf-8 -*- """This module defines expression transformation utilities, for expanding free indices in expressions to explicit fixed indices only.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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.utils.stacks import Stack, StackDict from ufl.classes import Terminal, ListTensor 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 unique_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() if len(x.ufl_shape) != len(c): error("Component size mismatch.") return x[c] return x def form_argument(self, x): sh = x.ufl_shape if sh == (): return x else: e = x.ufl_element() r = len(sh) # Get component c = self.component() if r != len(c): error("Component size mismatch.") # Map it through an eventual symmetry mapping s = e.symmetry() c = s.get(c, c) if r != len(c): error("Component size mismatch after symmetry mapping.") return x[c] def zero(self, x): if len(x.ufl_shape) != len(self.component()): error("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() if len(x.ufl_shape) != len(self.component()): error("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 conditional(self, x): c, t, f = x.ufl_operands # Not accepting nonscalars in condition if c.ufl_shape != (): error("Not expecting tensor in condition.") # Conditional may be indexed, push empty component self._components.push(()) c = self.visit(c) self._components.pop() # Keep possibly non-scalar components for values t = self.visit(t) f = self.visit(f) return self.reuse_if_possible(x, c, t, f) def division(self, x): a, b = x.ufl_operands # Not accepting nonscalars in division anymore if a.ufl_shape != (): error("Not expecting tensor in division.") if self.component() != (): error("Not expecting component in division.") if b.ufl_shape != (): error("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 if expression.ufl_shape != (): error("Expecting scalar base expression.") # Update index map with component tuple values comp = self.component() if len(indices) != len(comp): error("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 if not isinstance(f, (Terminal, Grad)): error("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 unique_pre_traversal(expr)): return expand_indices(expr) # TODO: Only expand what's necessary to get rid of list tensors return expr ufl-2017.2.0/ufl/formoperators.py0000644000231000000010000003137013211220450015677 0ustar chrisdaemon# -*- coding: utf-8 -*- "Various high level ways to transform a complete Form into a new Form." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 Massimiliano Leoni, 2016 from six import iteritems from six.moves import zip from six.moves import xrange as range from ufl.log import error from ufl.form import Form, as_form from ufl.core.expr import Expr, ufl_err_str 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 from ufl.functionspace import FunctionSpace # An exception to the rule that ufl.* does not depend on ufl.algorithms.* ... from ufl.algorithms import compute_form_adjoint, compute_form_action from ufl.algorithms import compute_energy_norm from ufl.algorithms import compute_form_lhs, compute_form_rhs, compute_form_functional from ufl.algorithms import expand_derivatives, extract_arguments from ufl.algorithms import FormSplitter # Part of the external interface from ufl.algorithms import replace # noqa def block_split(form, ix, iy=0): """UFL form operator: Given a linear or bilinear form on a mixed space, extract the block correspoinding to the indices ix, iy. Example: a = inner(grad(u), grad(v))*dx + div(u)*q*dx + div(v)*p*dx a = block_split(a, 0, 0) -> inner(grad(u), grad(v))*dx """ fs = FormSplitter() return fs.split(form, ix, iy) 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 :math:`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): if len(shape) == 0: error("Invalid shape.") elif 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 handle 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) if len(parts - {None}) != 0: error("Not expecting parts here, provide your own arguments.") part = None # Create argument and split it if in a mixed space function_spaces = [c.ufl_function_space() for c in coefficients] domains = [fs.ufl_domain() for fs in function_spaces] elements = [fs.ufl_element() for fs in function_spaces] if len(function_spaces) == 1: arguments = (Argument(function_spaces[0], number, part),) else: # Create in mixed space over assumed (for now) same domain assert all(fs.ufl_domain() == domains[0] for fs in function_spaces) elm = MixedElement(*elements) fs = FunctionSpace(domains[0], elm) arguments = split(Argument(fs, 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): if c.ufl_shape != a.ufl_shape: error("Coefficient and argument shapes do not match!") if isinstance(c, Coefficient): m[c] = a else: if not isinstance(c, Indexed): error("Invalid coefficient type for %s" % ufl_err_str(c)) f, i = c.ufl_operands if not isinstance(f, Coefficient): error("Expecting an indexed coefficient, not %s" % ufl_err_str(f)) if not (isinstance(i, MultiIndex) and all(isinstance(j, FixedIndex) for j in i)): error("Expecting one or more fixed indices, not %s" % ufl_err_str(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 ``Coefficient`` s 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 .. math:: 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 .. math:: 0 = \\frac{d}{dv}(Ax-b) = \\frac{dA}{dv} x + A \\frac{dx}{dv} - \\frac{db}{dv}, A \\frac{dx}{dv} = \\frac{db}{dv} - \\frac{dA}{dv} x, and solve this system for :math:`\\frac{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 :math:`\\frac{db}{dv}` and :math:`\\frac{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 :math:`\\frac{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) """ if not (isinstance(a, Form) and isinstance(u, Coefficient) and isinstance(L, Form) and isinstance(v, Variable)): error("Expecting (a, u, L, v), (bilinear form, function, linear form and scalar variable).") if not is_true_ufl_scalar(v): error("Expecting scalar variable.") from ufl.operators import diff return diff(L, v) - action(diff(a, v), u) ufl-2017.2.0/ufl/assertions.py0000644000231000000010000000333313211220450015165 0ustar chrisdaemon# -*- coding: utf-8 -*- """This module provides assertion functions used by the UFL implementation.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 # TODO: Move to this file and make other files import from here from ufl.core.expr import ufl_err_str # TODO: Use these and add more # --- Standardized error messages --- def expecting_instance(v, c): error("Expecting %s instance, not %s." % (c.__name__, ufl_err_str(v))) def expecting_python_scalar(v): error("Expecting Python scalar, not %s." % ufl_err_str(v)) def expecting_expr(v): error("Expecting Expr instance, not %s." % ufl_err_str(v)) def expecting_terminal(v): error("Expecting Terminal instance, not %s." % ufl_err_str(v)) def expecting_true_ufl_scalar(v): error("Expecting UFL scalar expression with no free indices, not %s." % ufl_err_str(v)) # --- Standardized assertions --- # TODO: Stop using this def ufl_assert(condition, *message): "Assert that condition is true and otherwise issue an error with given message." if not condition: error(*message) ufl-2017.2.0/ufl/constantvalue.py0000644000231000000010000003220713211220450015663 0ustar chrisdaemon# -*- coding: utf-8 -*- "This module defines classes representing constant values." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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. # Modified by Massimiliano Leoni, 2016. from six.moves import xrange as range from six import iteritems from ufl.utils.py23 import as_native_str from ufl.utils.py23 import as_native_strings from ufl.log import error, UFLValueError from ufl.core.expr import Expr from ufl.core.terminal import Terminal from ufl.core.multiindex import Index, FixedIndex 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 # noqa: F401 # Precision for float formatting precision = None def format_float(x): "Format float value based on global UFL precision." if precision: return "{:.{prec}}".format(float(x), prec=precision) else: return "{}".format(float(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 ufl_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__ = as_native_strings(("_name", "ufl_shape")) # def __init__(self, name, shape): # ConstantValue.__init__(self) # self._name = name # self.ufl_shape = shape # # def __str__(self): # return "" % (self._name, self.ufl_shape) # # def __repr__(self): # r = "AbstractSymbol(%s, %s)" % (repr(self._name), repr(self.ufl_shape)) # return as_native_str(r) # # 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__ = as_native_strings(("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)) # Assuming sorted now to avoid this cost, enable for debugging: # if sorted(free_indices) != list(free_indices): # error("Expecting sorted input. Remove this check later for efficiency.") self.ufl_free_indices = free_indices self.ufl_index_dimensions = index_dimensions 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" if self.ufl_free_indices == (): return "0 (shape %s)" % (self.ufl_shape,) if self.ufl_shape == (): return "0 (index labels %s)" % (self.ufl_free_indices,) return "0 (shape %s, index labels %s)" % (self.ufl_shape, self.ufl_free_indices) def __repr__(self): r = "Zero(%s, %s, %s)" % ( repr(self.ufl_shape), repr(self.ufl_free_indices), repr(self.ufl_index_dimensions)) return as_native_str(r) 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__ = as_native_strings(("_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): r = "%s(%s)" % (type(self).__name__, format_float(self._value)) return as_native_str(r) @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): r = "%s(%s)" % (type(self).__name__, repr(self._value)) return as_native_str(r) # --- Identity matrix --- @ufl_type() class Identity(ConstantValue): "UFL literal type: Representation of an identity matrix." __slots__ = as_native_strings(("_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): "Evaluates the identity matrix on the given components." a, b = component return 1 if a == b else 0 def __getitem__(self, key): if len(key) != 2: error("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): r = "Identity(%d)" % self._dim return as_native_str(r) 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__ = as_native_strings(("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): "Evaluates the permutation symbol." return self.__eps(component) def __getitem__(self, key): if len(key) != self._dim: error("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): r = "PermutationSymbol(%d)" % self._dim return as_native_str(r) 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 elif isinstance(expression, float): return FloatValue(expression) elif isinstance(expression, int): return IntValue(expression) else: raise UFLValueError("Invalid type conversion: %s can not be converted" " to any UFL type." % str(expression)) ufl-2017.2.0/ufl/mathfunctions.py0000644000231000000010000002572713211220450015670 0ustar chrisdaemon# -*- coding: utf-8 -*- """This module provides basic mathematical functions.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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.utils.py23 import as_native_strings from ufl.log import warning, error from ufl.core.operator import Operator from ufl.core.ufl_type import ufl_type from ufl.constantvalue import is_true_ufl_scalar, ScalarValue, Zero, FloatValue, IntValue, as_ufl """ 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__ = as_native_strings(("_name",)) def __init__(self, name, argument): Operator.__init__(self, (argument,)) if not is_true_ufl_scalar(argument): error("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]) @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)) if not is_true_ufl_scalar(arg1): error("Expecting scalar argument 1.") if not is_true_ufl_scalar(arg2): error("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 _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" __slots__ = as_native_strings(("_name", "_classname")) def __init__(self, name, classname, nu, argument): if not is_true_ufl_scalar(nu): error("Expecting scalar nu.") if not is_true_ufl_scalar(argument): error("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 ImportError: 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]) @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-2017.2.0/ufl/referencevalue.py0000644000231000000010000000332513211220450015767 0ustar chrisdaemon# -*- coding: utf-8 -*- "Representation of the reference value of a function." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 @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): if not isinstance(f, FormArgument): error("Can only take reference value of form arguments.") Operator.__init__(self, (f,)) @property def ufl_shape(self): return self.ufl_operands[0].ufl_element().reference_value_shape() 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] ufl-2017.2.0/ufl/split_functions.py0000644000231000000010000001112013211220450016207 0ustar chrisdaemon# -*- coding: utf-8 -*- "Algorithm for splitting a Coefficient or Argument into subfunctions." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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.utils.sequences import product from ufl.finiteelement import MixedElement, TensorElement from ufl.tensors import as_vector, as_matrix, ListTensor from ufl.indexed import Indexed from ufl.permutation import compute_indices from ufl.utils.indexflattening import flatten_multiindex, shape_to_strides 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.""" # Default range is all of v begin = 0 end = None if isinstance(v, Indexed): # Special case: split previous output of split again # Consistent with simple element, just return function in a tuple return (v,) elif isinstance(v, ListTensor): # Special case: split previous output of split again ops = v.ufl_operands if all(isinstance(comp, Indexed) for comp in ops): args = [comp.ufl_operands[0] for comp in ops] if all(args[0] == args[i] for i in range(1, len(args))): # Get innermost terminal here and its element v = args[0] # Get relevant range of v components begin, = ops[0].ufl_operands[1] end, = ops[-1].ufl_operands[1] begin = int(begin) end = int(end) + 1 else: error("Don't know how to split %s." % (v,)) else: error("Don't know how to split %s." % (v,)) # Special case: simple element, just return function in a tuple element = v.ufl_element() if not isinstance(element, MixedElement): assert end is None return (v,) if isinstance(element, TensorElement): if element.symmetry(): error("Split not implemented for symmetric tensor elements.") if len(v.ufl_shape) != 1: error("Don't know how to split tensor valued mixed functions without flattened index space.") # Compute value size and set default range end value_size = product(element.value_shape()) if end is None: end = value_size else: # Recursively dive into mixedelement in to subelement # corresponding to beginning of range j = begin while True: sub_i, j = element.extract_subelement_component(j) element = element.sub_elements()[sub_i] # Then break when we find the subelement that covers the whole range if product(element.value_shape()) == (end - begin): break # Build expressions representing the subfunction of v for each subelement offset = begin sub_functions = [] for i, e in enumerate(element.sub_elements()): # Get shape, size, indices, and v components # corresponding to subelement value shape = e.value_shape() strides = shape_to_strides(shape) rank = len(shape) sub_size = product(shape) subindices = [flatten_multiindex(c, strides) for c in compute_indices(shape)] components = [v[k + offset] for k in subindices] # Shape components into same shape as subelement if rank == 0: subv, = components elif rank <= 1: subv = as_vector(components) elif rank == 2: subv = as_matrix([components[i*shape[1]: (i+1)*shape[1]] for i in range(shape[0])]) else: error("Don't know how to split functions with sub functions of rank %d." % rank) offset += sub_size sub_functions.append(subv) if end != offset: error("Function splitting failed to extract components for whole intended range. Something is wrong.") return tuple(sub_functions) ufl-2017.2.0/ufl/formatting/0000755000231000000010000000000013211220450014571 5ustar chrisdaemonufl-2017.2.0/ufl/formatting/ufl2dot.py0000644000231000000010000001723713211220450016534 0ustar chrisdaemon# -*- coding: utf-8 -*- """A collection of utility algorithms for printing of UFL objects in the DOT graph visualization language, mostly intended for debugging purposers.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 itervalues from six.moves import xrange as range from ufl.log import error from ufl.core.expr import Expr from ufl.form import Form from ufl.variable import Variable 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 = ["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 itg in form.integrals(): prefix = "itg%d_" % k integralkey = "%s%s" % (itg.integral_type(), itg.subdomain_id()) integrallabel = "%s %s" % (itg.integral_type().capitalize().replace("_", " "), "integral") 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-2017.2.0/ufl/formatting/printing.py0000644000231000000010000001030013211220450016767 0ustar chrisdaemon# -*- coding: utf-8 -*- """A collection of utility algorithms for printing of UFL objects, mostly intended for debugging purposes.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 ufl.log import error from ufl.core.expr import Expr from ufl.form import Form from ufl.integral import Integral # --- Utilities for constructing informative strings from UFL objects def integral_info(integral): if not isinstance(integral, Integral): error("Expecting an Integral.") s = " Integral:\n" s += " Type:\n" s += " %s\n" % integral.integral_type() s += " Domain:\n" s += " %s\n" % integral.ufl_domain() s += " Domain id:\n" s += " %s\n" % integral.subdomain_id() s += " Domain data:\n" s += " %s\n" % integral.subdomain_data() s += " Compiler metadata:\n" s += " %s\n" % integral.metadata() return s def form_info(form): if not isinstance(form, Form): error("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 = "%s%s" % (ind, repr(expression)) else: sops = [_tree_format_expression(o, indentation+1, parentheses) for o in expression.ufl_operands] s = "%s%s\n" % (ind, expression._ufl_class_.__name__) if parentheses and len(sops) > 1: s += "%s(\n" % (ind,) s += "\n".join(sops) if parentheses and len(sops) > 1: s += "\n%s)" % (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-2017.2.0/ufl/formatting/__init__.py0000644000231000000010000000000013211220450016670 0ustar chrisdaemonufl-2017.2.0/ufl/formatting/graph.py0000644000231000000010000001776113211220450016260 0ustar chrisdaemon# -*- coding: utf-8 -*- """Algorithms for working with linearized computational graphs.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 from ufl.corealg.traversal import unique_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 reversed(list(unique_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-2017.2.0/ufl/formatting/ufl2latex.py0000644000231000000010000007172513211220450017065 0ustar chrisdaemon# -*- coding: utf-8 -*- """This module defines expression transformation utilities, either converting UFL expressions to new UFL expressions or converting UFL expressions to other representations.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 import ufl from ufl.log import error from ufl.permutation import compute_indices from ufl.algorithms.traversal import iter_expressions # 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 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.classes import terminal_classes # Other algorithms: from ufl.algorithms.compute_form_data import compute_form_data from ufl.corealg.multifunction import MultiFunction from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.traversal import unique_post_traversal 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 unique_post_traversal(e): if isinstance(o, Variable): expr, label = o.ufl_operands if label not 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(MultiFunction): def __init__(self, argument_names=None, coefficient_names=None): MultiFunction.__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 # noqa: E741 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) # noqa: E741 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) # noqa: E741 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" # noqa: E741 l += "%s, &\text{if }\quad %s, \\\\\n" % (t, c) # noqa: E741 l += "%s, &\text{otherwise.}\n" % f # noqa: E741 l += "\\end{cases}" # noqa: E741 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): rules = Expression2LatexHandler(argument_names, coefficient_names) return map_expr_dag(rules, 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.ufl_element()))) for i, f in enumerate(formdata.original_coefficients): lines.append(r"\mathcal{Q}_{%d} = \{%s\} " % (i, element2latex(f.ufl_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 # noqa: E741 if l not 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.ufl_domain() # TODO: Render domain description 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): 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 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) if left: error("Shouldn't have anything left!") return deplistlist def code2latex(G, partitions, formdata): "TODO: Document me" bfn = formdata.argument_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: 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) """# 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-2017.2.0/ufl/formatting/ufl2unicode.py0000644000231000000010000004344013211220450017367 0ustar chrisdaemon# coding: utf-8 from __future__ import unicode_literals from six import unichr import numbers import ufl from ufl.log import error from ufl.corealg.multifunction import MultiFunction from ufl.corealg.map_dag import map_expr_dag from ufl.core.multiindex import Index, FixedIndex from ufl.form import Form from ufl.algorithms import compute_form_data class PrecedenceRules(MultiFunction): "An enum-like class for C operator precedence levels." def __init__(self): MultiFunction.__init__(self) def highest(self, o): return 0 terminal = highest list_tensor = highest component_tensor = highest def restricted(self, o): return 5 cell_avg = restricted facet_avg = restricted def call(self, o): return 10 indexed = call min_value = call max_value = call math_function = call bessel_function = call def power(self, o): return 12 def mathop(self, o): return 15 derivative = mathop trace = mathop deviatoric = mathop cofactor = mathop skew = mathop sym = mathop def not_condition(self, o): return 20 def product(self, o): return 30 division = product # mod = product dot = product inner = product outer = product cross = product def add(self, o): return 40 # sub = add index_sum = add def lt(self, o): return 50 le = lt gt = lt ge = lt def eq(self, o): return 60 ne = eq def and_condition(self, o): return 70 def or_condition(self, o): return 71 def conditional(self, o): return 72 def lowest(self, o): return 80 operator = lowest _precrules = PrecedenceRules() def precedence(expr): return _precrules(expr) try: import colorama has_colorama = True except ImportError: has_colorama = False class UC: "An enum-like class for unicode characters." # Letters in this alphabet have contiguous code point numbers bold_math_a = u"𝐚" bold_math_A = u"𝐀" thin_space = u"\u2009" superscript_plus = u'⁺' superscript_minus = u'⁻' superscript_equals = u'⁼' superscript_left_paren = u'⁽' superscript_right_paren = u'⁾' superscript_digits = ["⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹"] subscript_plus = u'₊' subscript_minus = u'₋' subscript_equals = u'₌' subscript_left_paren = u'₍' subscript_right_paren = u'₎' subscript_digits = ["₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉"] sqrt = u'√' transpose = u'ᵀ' integral = u'∫' integral_double = u'∬' integral_triple = u'∭' integral_contour = u'∮' integral_surface = u'∯' integral_volume = u'∰' sum = u'∑' division_slash = '∕' partial = u'∂' epsilon = u'ε' omega = u'ω' Omega = u'Ω' gamma = u'γ' Gamma = u'Γ' nabla = u'∇' for_all = u'∀' dot = u'⋅' cross_product = u'⨯' circled_times = u'⊗' nary_product = u'∏' ne = u'≠' lt = u'<' le = u'≤' gt = u'>' ge = u'≥' logical_and = u'∧' logical_or = u'∨' logical_not = u'¬' element_of = u'∈' not_element_of = u'∉' left_white_square_bracket = u'⟦' right_white_squared_bracket = u'⟧' left_angled_bracket = u'⟨' right_angled_bracket = u'⟩' left_double_angled_bracket = u'⟪' right_double_angled_bracket = u'⟫' combining_right_arrow_above = '\u20D7' combining_overline = '\u0305' def bolden_letter(c): if ord("A") <= ord(c) <= ord("Z"): c = unichr(ord(c) - ord(u"A") + ord(UC.bold_math_A)) elif ord("a") <= ord(c) <= ord("z"): c = unichr(ord(c) - ord(u"a") + ord(UC.bold_math_a)) return c def superscript_digit(digit): return UC.superscript_digits[ord(digit) - ord("0")] def subscript_digit(digit): return UC.subscript_digits[ord(digit) - ord("0")] def bolden_string(s): return u"".join(bolden_letter(c) for c in s) def overline_string(f): return u"".join("%s%s" % (c, UC.combining_overline) for c in f) def subscript_number(number): assert isinstance(number, int) prefix = UC.subscript_minus if number < 0 else '' number = str(number) return prefix + ''.join(subscript_digit(c) for c in str(number)) def superscript_number(number): assert isinstance(number, int) prefix = UC.superscript_minus if number < 0 else '' number = str(number) return prefix + ''.join(superscript_digit(c) for c in str(number)) def opfont(opname): return bolden_string(opname) def measure_font(dx): return bolden_string(dx) integral_by_dim = { 3: UC.integral_triple, 2: UC.integral_double, 1: UC.integral, } integral_type_to_codim = { "cell": 0, "exterior_facet": 1, "interior_facet": 1, "vertex": "tdim", "point": "tdim", "custom": 0, "overlap": 0, "interface": 1, "cutcell": 0, } integral_symbols = { "cell": UC.integral_volume, "exterior_facet": UC.integral_surface, "interior_facet": UC.integral_surface, "vertex": UC.integral, "point": UC.integral, "custom": UC.integral, "overlap": UC.integral, "interface": UC.integral, "cutcell": UC.integral, } integral_postfixes = { "cell": "", "exterior_facet": "ext", "interior_facet": "int", "vertex": "vertex", "point": "point", "custom": "custom", "overlap": "overlap", "interface": "interface", "cutcell": "cutcell", } def get_integral_symbol(integral_type, domain, subdomain_id): tdim = domain.topological_dimension() codim = integral_type_to_codim[integral_type] itgdim = tdim - codim # ipost = integral_postfixes[integral_type] istr = integral_by_dim[itgdim] # TODO: Render domain description if isinstance(subdomain_id, numbers.Integral): istr += subscript_number(int(subdomain_id)) elif subdomain_id == "everywhere": pass elif subdomain_id == "otherwise": istr += "[rest of domain]" elif isinstance(subdomain_id, tuple): istr += ",".join([subscript_number(int(i)) for i in subdomain_id]) dxstr = ufl.measure.integral_type_to_measure_name[integral_type] dxstr = measure_font(dxstr) return istr, dxstr def par(s): return "(%s)" % s def prec(expr): return 0 # FIXME # return precedence[expr._ufl_class_] def is_int(s): try: int(s) return True except ValueError: return False def format_index(ii): if isinstance(ii, FixedIndex): s = "%d" % ii._value elif isinstance(ii, Index): s = "i%s" % subscript_number(ii._count) else: error("Invalid index type %s." % type(ii)) return s def ufl2unicode(expression): "Generate Unicode string for a UFL expression or form." if isinstance(expression, Form): form_data = compute_form_data(expression) preprocessed_form = form_data.preprocessed_form return form2unicode(preprocessed_form, form_data) else: return expression2unicode(expression) def expression2unicode(expression, argument_names=None, coefficient_names=None): rules = Expression2UnicodeHandler(argument_names, coefficient_names) return map_expr_dag(rules, expression) def form2unicode(form, formdata): # formname = formdata.name argument_names = None coefficient_names = None # Define form as sum of integrals lines = [] integrals = form.integrals() for itg in integrals: integrand_string = expression2unicode( itg.integrand(), argument_names, coefficient_names) istr, dxstr = get_integral_symbol(itg.integral_type(), itg.ufl_domain(), itg.subdomain_id()) line = "%s %s %s" % (istr, integrand_string, dxstr) lines.append(line) return '\n + '.join(lines) def binop(expr, a, b, op, sep=" "): eprec = precedence(expr) op0, op1 = expr.ufl_operands aprec = precedence(op0) bprec = precedence(op1) # Assuming left-to-right evaluation, therefore >= and > here: if aprec >= eprec: a = par(a) if bprec > eprec: b = par(b) return sep.join((a, op, b)) def mathop(expr, arg, opname): eprec = precedence(expr) aprec = precedence(expr.ufl_operands[0]) op = opfont(opname) if aprec > eprec: arg = par(arg) sep = "" else: sep = UC.thin_space return "%s%s%s" % (op, sep, arg) class Expression2UnicodeHandler(MultiFunction): def __init__(self, argument_names=None, coefficient_names=None, colorama_bold=False): MultiFunction.__init__(self) self.argument_names = argument_names self.coefficient_names = coefficient_names self.colorama_bold = colorama_bold and has_colorama # --- Terminal objects --- def scalar_value(self, o): if o.ufl_shape and self.colorama_bold: return "%s%s%s" % (colorama.Style.BRIGHT, o._value, colorama.Style.RESET_ALL) return "%s" % o._value def zero(self, o): if o.ufl_shape and self.colorama_bold: if len(o.ufl_shape) == 1: return "0%s" % UC.combining_right_arrow_above return "%s0%s" % (colorama.Style.BRIGHT, colorama.Style.RESET_ALL) return "0" def identity(self, o): if self.colorama_bold: return "%sI%s" % (colorama.Style.BRIGHT, colorama.Style.RESET_ALL) return "I" def permutation_symbol(self, o): if self.colorama_bold: return "%s%s%s" % (colorama.Style.BRIGHT, UC.epsilon, colorama.Style.RESET_ALL) return UC.epsilon def facet_normal(self, o): return "n%s" % UC.combining_right_arrow_above def spatial_coordinate(self, o): return "x%s" % UC.combining_right_arrow_above def argument(self, o): # Using ^ for argument numbering and _ for indexing since # indexing is more common than exponentiation if self.argument_names is None: i = o.number() bfn = "v" if i == 0 else "u" if not o.ufl_shape: return bfn elif len(o.ufl_shape) == 1: return "%s%s" % (bfn, UC.combining_right_arrow_above) elif self.colorama_bold: return "%s%s%s" % (colorama.Style.BRIGHT, bfn, colorama.Style.RESET_ALL) else: return bfn 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: i = o.count() var = "w" if len(o.ufl_shape) == 1: var += UC.combining_right_arrow_above elif len(o.ufl_shape) > 1 and self.colorama_bold: var = "%s%s%s" % (colorama.Style.BRIGHT, var, colorama.Style.RESET_ALL) return "%s%s" % (var, superscript_number(i)) return self.coefficient_names[o.count()] def multi_index(self, o): return ",".join(format_index(i) for i in o) def label(self, o): return "l%s" % (subscript_number(o.count()),) # --- Non-terminal objects --- def variable(self, o, f, l): return "var(%s,%s)" % (f, l) def index_sum(self, o, f, i): if 1: # prec(o.ufl_operands[0]) >? prec(o): f = par(f) return "%s[%s]%s" % (UC.sum, i, f) def sum(self, o, a, b): return binop(o, a, b, "+") def product(self, o, a, b): return binop(o, a, b, " ", sep="") def division(self, o, a, b): if is_int(b): b = subscript_number(int(b)) if is_int(a): # Return as a fraction # NOTE: Maybe consider using fractional slash # with normal numbers if terminals can handle it a = superscript_number(int(a)) else: a = par(a) return "%s %s %s" % (a, UC.division_slash, b) return binop(o, a, b, UC.division_slash) def abs(self, o, a): return "|%s|" % (a,) def transposed(self, o, a): a = par(a) return "%s%s" % (a, UC.transpose) def indexed(self, o, A, ii): op0, op1 = o.ufl_operands Aprec = precedence(op0) oprec = precedence(o) if Aprec > oprec: A = par(A) return "%s[%s]" % (A, ii) def variable_derivative(self, o, f, v): f = par(f) v = par(v) nom = r"%s%s" % (UC.partial, f) denom = r"%s%s" % (UC.partial, v) return par(r"%s%s%s" % (nom, UC.division_slash, denom)) def coefficient_derivative(self, o, f, w, v, cd): f = par(f) w = par(w) nom = r"%s%s" % (UC.partial, f) denom = r"%s%s" % (UC.partial, w) return par(r"%s%s%s[%s]" % (nom, UC.division_slash, denom, v)) # TODO: Fix this syntax... def grad(self, o, f): return mathop(o, f, "grad") def div(self, o, f): return mathop(o, f, "div") def nabla_grad(self, o, f): oprec = precedence(o) fprec = precedence(o.ufl_operands[0]) if fprec > oprec: f = par(f) return "%s%s%s" % (UC.nabla, UC.thin_space, f) def nabla_div(self, o, f): oprec = precedence(o) fprec = precedence(o.ufl_operands[0]) if fprec > oprec: f = par(f) return "%s%s%s%s%s" % (UC.nabla, UC.thin_space, UC.dot, UC.thin_space, f) def curl(self, o, f): oprec = precedence(o) fprec = precedence(o.ufl_operands[0]) if fprec > oprec: f = par(f) return "%s%s%s%s%s" % (UC.nabla, UC.thin_space, UC.cross_product, UC.thin_space, f) def math_function(self, o, f): op = opfont(self._name) f = par(f) return "%s%s" % (op, f) def sqrt(self, o, f): f = par(f) return "%s%s" % (UC.sqrt, f) def exp(self, o, f): op = opfont("exp") f = par(f) return "%s%s" % (op, f) def atan2(self, o, f1, f2): f1 = par(f1) f2 = par(f2) op = opfont("arctan2") return "%s(%s, %s)" % (op, f1, f2) def bessel_j(self, o, nu, f): op = opfont("J") f = par(f) nu = subscript_number(int(nu)) return "%s%s%s" % (op, nu, f) def bessel_y(self, o, nu, f): op = opfont("Y") f = par(f) nu = subscript_number(int(nu)) return "%s%s%s" % (op, nu, f) def bessel_i(self, o, nu, f): op = opfont("I") f = par(f) nu = subscript_number(int(nu)) return "%s%s%s" % (op, nu, f) def bessel_K(self, o, nu, f): op = opfont("K") f = par(f) nu = subscript_number(int(nu)) return "%s%s%s" % (op, nu, f) def power(self, o, a, b): if is_int(b): b = superscript_number(int(b)) return binop(o, a, b, "", sep="") return binop(o, a, b, "^", sep="") def outer(self, o, a, b): return binop(o, a, b, UC.circled_times) def inner(self, o, a, b): return "%s%s, %s%s" % (UC.left_angled_bracket, a, b, UC.right_angled_bracket) def dot(self, o, a, b): return binop(o, a, b, UC.dot) def cross(self, o, a, b): return binop(o, a, b, UC.cross_product) def determinant(self, o, A): return "|%s|" % (A,) def inverse(self, o, A): A = par(A) return "%s%s" % (A, superscript_number(-1)) def trace(self, o, A): return mathop(o, A, "tr") def deviatoric(self, o, A): return mathop(o, A, "dev") def cofactor(self, o, A): return mathop(o, A, "cofac") def skew(self, o, A): return mathop(o, A, "skew") def sym(self, o, A): return mathop(o, A, "sym") def list_tensor(self, o, *ops): l = ", ".join(ops) # noqa: E741 return "%s%s%s" % ("[", l, "]") def component_tensor(self, o, A, ii): return "[%s %s %s]" % (A, UC.for_all, ii) def positive_restricted(self, o, f): f = par(f) return "%s%s" % (f, UC.superscript_plus) def negative_restricted(self, o, f): f = par(f) return "%s%s" % (f, UC.superscript_minus) def cell_avg(self, o, f): f = overline_string(f) return f def facet_avg(self, o, f): f = overline_string(f) return f def eq(self, o, a, b): return binop(o, a, b, "=") def ne(self, o, a, b): return binop(o, a, b, UC.ne) def le(self, o, a, b): return binop(o, a, b, UC.le) def ge(self, o, a, b): return binop(o, a, b, UC.ge) def lt(self, o, a, b): return binop(o, a, b, UC.lt) def gt(self, o, a, b): return binop(o, a, b, UC.gt) def and_condition(self, o, a, b): return binop(o, a, b, UC.logical_and) def or_condition(self, o, a, b): return binop(o, a, b, UC.logical_or) def not_condition(self, o, a): a = par(a) return "%s%s" % (UC.logical_not, a) def conditional(self, o, c, t, f): c = par(c) t = par(t) f = par(t) If = opfont("if") Else = opfont("else") l = " ".join((t, If, c, Else, f)) # noqa: E741 return l def min_value(self, o, a, b): op = opfont("min") return "%s(%s, %s)" % (op, a, b) def max_value(self, o, a, b): op = opfont("max") return "%s(%s, %s)" % (op, a, b) def expr_list(self, o, *ops): items = ", ".join(ops) return "%s %s %s" % (UC.left_white_square_bracket, items, UC.right_white_squared_bracket) def expr_mapping(self, o, *ops): items = ", ".join(ops) return "%s %s %s" % (UC.left_double_angled_bracket, items, UC.left_double_angled_bracket) def expr(self, o): raise ValueError("Missing handler for type %s" % str(type(o))) ufl-2017.2.0/ufl/formatting/latextools.py0000644000231000000010000000723413211220450017347 0ustar chrisdaemon# -*- coding: utf-8 -*- "This module defines basic utilities for stitching together LaTeX documents." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 string_types # --- 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], string_types): 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, string_types): 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, string_types): 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, string_types): 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-2017.2.0/ufl/finiteelement/0000755000231000000010000000000013211220450015247 5ustar chrisdaemonufl-2017.2.0/ufl/finiteelement/finiteelement.py0000644000231000000010000002367613211220450020467 0ustar chrisdaemon# -*- coding: utf-8 -*- "This module defines the UFL finite element classes." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 # Modified by Massimiliano Leoni, 2016 from ufl.log import error from ufl.utils.py23 import as_native_str from ufl.utils.py23 import as_native_strings from ufl.utils.formatting import istr from ufl.cell import as_cell from ufl.cell import TensorProductCell from ufl.finiteelement.elementlist import canonical_element_description, simplices 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__ = as_native_strings(("_short_name", "_sobolev_space", "_mapping", "_variant")) def __new__(cls, family, cell=None, degree=None, form_degree=None, quad_scheme=None, variant=None): """Intercepts construction to expand CG, DG, RTCE and RTCF spaces on TensorProductCells.""" if cell is not None: cell = as_cell(cell) if isinstance(cell, TensorProductCell): # Delay import to avoid circular dependency at module load time from ufl.finiteelement.tensorproductelement import TensorProductElement from ufl.finiteelement.enrichedelement import EnrichedElement from ufl.finiteelement.hdivcurl import HDivElement as HDiv, HCurlElement as HCurl family, short_name, degree, value_shape, reference_value_shape, sobolev_space, mapping = \ canonical_element_description(family, cell, degree, form_degree) if family in ["RTCF", "RTCE"]: cell_h, cell_v = cell.sub_cells() if cell_h.cellname() != "interval": error("%s is available on TensorProductCell(interval, interval) only." % family) if cell_v.cellname() != "interval": error("%s is available on TensorProductCell(interval, interval) only." % family) C_elt = FiniteElement("CG", "interval", degree, variant=variant) D_elt = FiniteElement("DG", "interval", degree - 1, variant=variant) CxD_elt = TensorProductElement(C_elt, D_elt, cell=cell) DxC_elt = TensorProductElement(D_elt, C_elt, cell=cell) if family == "RTCF": return EnrichedElement(HDiv(CxD_elt), HDiv(DxC_elt)) if family == "RTCE": return EnrichedElement(HCurl(CxD_elt), HCurl(DxC_elt)) elif family == "NCF": cell_h, cell_v = cell.sub_cells() if cell_h.cellname() != "quadrilateral": error("%s is available on TensorProductCell(quadrilateral, interval) only." % family) if cell_v.cellname() != "interval": error("%s is available on TensorProductCell(quadrilateral, interval) only." % family) Qc_elt = FiniteElement("RTCF", "quadrilateral", degree, variant=variant) Qd_elt = FiniteElement("DQ", "quadrilateral", degree - 1, variant=variant) Id_elt = FiniteElement("DG", "interval", degree - 1, variant=variant) Ic_elt = FiniteElement("CG", "interval", degree, variant=variant) return EnrichedElement(HDiv(TensorProductElement(Qc_elt, Id_elt, cell=cell)), HDiv(TensorProductElement(Qd_elt, Ic_elt, cell=cell))) elif family == "NCE": cell_h, cell_v = cell.sub_cells() if cell_h.cellname() != "quadrilateral": error("%s is available on TensorProductCell(quadrilateral, interval) only." % family) if cell_v.cellname() != "interval": error("%s is available on TensorProductCell(quadrilateral, interval) only." % family) Qc_elt = FiniteElement("Q", "quadrilateral", degree, variant=variant) Qd_elt = FiniteElement("RTCE", "quadrilateral", degree, variant=variant) Id_elt = FiniteElement("DG", "interval", degree - 1, variant=variant) Ic_elt = FiniteElement("CG", "interval", degree, variant=variant) return EnrichedElement(HCurl(TensorProductElement(Qc_elt, Id_elt, cell=cell)), HCurl(TensorProductElement(Qd_elt, Ic_elt, cell=cell))) elif family == "Q": return TensorProductElement(*[FiniteElement("CG", c, degree, variant=variant) for c in cell.sub_cells()], cell=cell) elif family == "DQ": def dq_family(cell): return "DG" if cell.cellname() in simplices else "DQ" return TensorProductElement(*[FiniteElement(dq_family(c), c, degree, variant=variant) for c in cell.sub_cells()], cell=cell) return super(FiniteElement, cls).__new__(cls) def __init__(self, family, cell=None, degree=None, form_degree=None, quad_scheme=None, variant=None): """Create finite element. *Arguments* family (string) The finite element family cell The geometric cell 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) variant Hint for the local basis function variant (optional) """ # Note: Unfortunately, dolfin sometimes passes None for # cell. Until this is fixed, allow it: if cell is not None: cell = as_cell(cell) 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 self._variant = variant # Finite elements on quadrilaterals and hexahedrons have an IrreducibleInt as degree if cell is not None: if cell.cellname() in ["quadrilateral", "hexahedron"]: from ufl.algorithms.estimate_degrees import IrreducibleInt degree = IrreducibleInt(degree) # Type check variant if variant is not None and not isinstance(variant, str): raise ValueError("Illegal variant: must be string or None") # Initialize element data FiniteElementBase.__init__(self, family, cell, degree, quad_scheme, value_shape, reference_value_shape) # Cache repr string qs = self.quadrature_scheme() if qs is None: quad_str = "" else: quad_str = ", quad_scheme=%s" % repr(qs) v = self.variant() if v is None: var_str = "" else: var_str = ", variant=%s" % repr(v) self._repr = as_native_str("FiniteElement(%s, %s, %s%s%s)" % ( repr(self.family()), repr(self.cell()), repr(self.degree()), quad_str, var_str)) assert '"' not in self._repr def mapping(self): return self._mapping def sobolev_space(self): "Return the underlying Sobolev space." return self._sobolev_space def variant(self): return self._variant def reconstruct(self, family=None, cell=None, degree=None): """Construct a new FiniteElement object with some properties replaced with new values.""" if family is None: family = self.family() if cell is None: cell = self.cell() if degree is None: degree = self.degree() return FiniteElement(family, cell, degree, quad_scheme=self.quadrature_scheme(), variant=self.variant()) def __str__(self): "Format as string for pretty printing." qs = self.quadrature_scheme() qs = "" if qs is None else "(%s)" % qs v = self.variant() v = "" if v is None else "(%s)" % v return "<%s%s%s%s on a %s>" % (self._short_name, istr(self.degree()), qs, v, self.cell()) def shortstr(self): "Format as string for pretty printing." return "%s%s(%s,%s)" % (self._short_name, istr(self.degree()), istr(self.quadrature_scheme()), istr(self.variant())) def __getnewargs__(self): """Return the arguments which pickle needs to recreate the object.""" return (self.family(), self.cell(), self.degree(), None, self.quadrature_scheme(), self.variant()) ufl-2017.2.0/ufl/finiteelement/hdivcurl.py0000644000231000000010000000735513211220450017453 0ustar chrisdaemon# -*- coding: utf-8 -*- # Copyright (C) 2008-2016 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 . # # Modified by Massimiliano Leoni, 2016 # import six from ufl.utils.py23 import as_native_str from ufl.utils.py23 import as_native_strings from ufl.finiteelement.finiteelementbase import FiniteElementBase from ufl.sobolevspace import HDiv, HCurl # @six.python_2_unicode_compatible class HDivElement(FiniteElementBase): """A div-conforming version of an outer product element, assuming this makes mathematical sense.""" __slots__ = as_native_strings(("_element",)) def __init__(self, element): self._element = element self._repr = as_native_str("HDivElement(%s)" % repr(element)) family = "TensorProductElement" cell = element.cell() degree = element.degree() quad_scheme = element.quadrature_scheme() value_shape = (element.cell().geometric_dimension(),) reference_value_shape = (element.cell().topological_dimension(),) # Skipping TensorProductElement constructor! Bad code smell, refactor to avoid this non-inheritance somehow. FiniteElementBase.__init__(self, family, cell, degree, quad_scheme, value_shape, reference_value_shape) def mapping(self): return "contravariant Piola" def sobolev_space(self): "Return the underlying Sobolev space." return HDiv def reconstruct(self, **kwargs): return HDivElement(self._element.reconstruct(**kwargs)) def __str__(self): return "HDivElement(%s)" % str(self._element) def shortstr(self): "Format as string for pretty printing." return "HDivElement(%s)" % str(self._element.shortstr()) # @six.python_2_unicode_compatible class HCurlElement(FiniteElementBase): """A curl-conforming version of an outer product element, assuming this makes mathematical sense.""" __slots__ = as_native_strings(("_element",)) def __init__(self, element): self._element = element self._repr = as_native_str("HCurlElement(%s)" % repr(element)) family = "TensorProductElement" cell = element.cell() degree = element.degree() quad_scheme = element.quadrature_scheme() cell = element.cell() value_shape = (cell.geometric_dimension(),) reference_value_shape = (cell.topological_dimension(),) # TODO: Is this right? # Skipping TensorProductElement constructor! Bad code smell, # refactor to avoid this non-inheritance somehow. FiniteElementBase.__init__(self, family, cell, degree, quad_scheme, value_shape, reference_value_shape) def mapping(self): return "covariant Piola" def sobolev_space(self): "Return the underlying Sobolev space." return HCurl def reconstruct(self, **kwargs): return HCurlElement(self._element.reconstruct(**kwargs)) def __str__(self): return "HCurlElement(%s)" % str(self._element) def shortstr(self): "Format as string for pretty printing." return "HCurlElement(%s)" % str(self._element.shortstr()) ufl-2017.2.0/ufl/finiteelement/tensorproductelement.py0000644000231000000010000001142213211220450022106 0ustar chrisdaemon# -*- coding: utf-8 -*- "This module defines the UFL finite element classes." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 Massimiliano Leoni, 2016 # import six from itertools import chain from ufl.log import error from ufl.utils.py23 import as_native_strings from ufl.cell import TensorProductCell, as_cell from ufl.sobolevspace import DirectionalSobolevSpace from ufl.finiteelement.finiteelementbase import FiniteElementBase # @six.python_2_unicode_compatible class TensorProductElement(FiniteElementBase): r"""The tensor product of :math:`d` element spaces: .. math:: V = V_1 \otimes V_2 \otimes ... \otimes V_d Given bases :math:`\{\phi_{j_i}\}` of the spaces :math:`V_i` for :math:`i = 1, ...., d`, :math:`\{ \phi_{j_1} \otimes \phi_{j_2} \otimes \cdots \otimes \phi_{j_d} \}` forms a basis for :math:`V`. """ __slots__ = as_native_strings(("_sub_elements", "_cell")) def __init__(self, *elements, **kwargs): "Create TensorProductElement from a given list of elements." if not elements: error("Cannot create TensorProductElement from empty list.") keywords = list(kwargs.keys()) if keywords and keywords != ["cell"]: raise ValueError("TensorProductElement got an unexpected keyword argument '%s'" % keywords[0]) cell = kwargs.get("cell") family = "TensorProductElement" if cell is None: # Define cell as the product of each elements cell cell = TensorProductCell(*[e.cell() for e in elements]) else: cell = as_cell(cell) # Define polynomial degree as a tuple of sub-degrees degree = tuple(e.degree() for e in elements) # No quadrature scheme defined quad_scheme = None # match FIAT implementation value_shape = tuple(chain(*[e.value_shape() for e in elements])) reference_value_shape = tuple(chain(*[e.reference_value_shape() for e in elements])) if len(value_shape) > 1: error("Product of vector-valued elements not supported") if len(reference_value_shape) > 1: error("Product of vector-valued elements not supported") FiniteElementBase.__init__(self, family, cell, degree, quad_scheme, value_shape, reference_value_shape) self._sub_elements = elements self._cell = cell self._repr = "TensorProductElement(%s, cell=%s)" % ( ", ".join(repr(e) for e in elements), repr(cell)) def mapping(self): if all(e.mapping() == "identity" for e in self._sub_elements): return "identity" else: return "undefined" def sobolev_space(self): "Return the underlying Sobolev space of the TensorProductElement." elements = self._sub_elements if all(e.sobolev_space() == elements[0].sobolev_space() for e in elements): return elements[0].sobolev_space() else: # Generate a DirectionalSobolevSpace which contains # continuity information parametrized by spatial index orders = [] for e in elements: e_dim = e.cell().geometric_dimension() e_order = (e.sobolev_space()._order,) * e_dim orders.extend(e_order) return DirectionalSobolevSpace(orders) 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 reconstruct(self, cell=None): return TensorProductElement(*self.sub_elements(), cell=cell) def __str__(self): "Pretty-print." return "TensorProductElement(%s, cell=%s)" \ % (', '.join([str(e) for e in self._sub_elements]), str(self._cell)) def shortstr(self): "Short pretty-print." return "TensorProductElement(%s, cell=%s)" \ % (', '.join([e.shortstr() for e in self._sub_elements]), str(self._cell)) ufl-2017.2.0/ufl/finiteelement/mixedelement.py0000644000231000000010000004767013211220450020317 0ustar chrisdaemon# -*- coding: utf-8 -*- "This module defines the UFL finite element classes." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 # Modified by Massimiliano Leoni, 2016 # import six from six import iteritems from six.moves import zip from six.moves import xrange as range from ufl.log import error from ufl.utils.py23 import as_native_strings from ufl.permutation import compute_indices from ufl.utils.sequences import product, max_degree from ufl.utils.dicts import EmptyDict from ufl.utils.indexflattening import flatten_multiindex, unflatten_index, shape_to_strides from ufl.cell import as_cell from ufl.finiteelement.finiteelementbase import FiniteElementBase from ufl.finiteelement.finiteelement import FiniteElement # @six.python_2_unicode_compatible class MixedElement(FiniteElementBase): """A finite element composed of a nested hierarchy of mixed or simple elements.""" __slots__ = as_native_strings(("_sub_elements", "_cells")) def __init__(self, *elements, **kwargs): "Create mixed finite element from given list of elements" if type(self) is MixedElement: if kwargs: error("Not expecting keyword arguments to MixedElement constructor.") # 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 cell, for now all should be equal cells = tuple(sorted(set(element.cell() for element in elements) - set([None]))) self._cells = cells if cells: cell = cells[0] # Require that all elements are defined on the same cell if not all(c == cell for c in cells[1:]): error("Sub elements must live on the same cell.") else: cell = 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() if not all(e.quadrature_scheme() == quad_scheme for e in elements): error("Quadrature scheme mismatch for sub elements of mixed element.") # Compute value sizes in global and reference configurations value_size_sum = sum(product(s.value_shape()) for s in self._sub_elements) reference_value_size_sum = sum(product(s.reference_value_shape()) for s in self._sub_elements) # Default value shape: Treated simply as all subelement values # unpacked in a vector. value_shape = kwargs.get('value_shape', (value_size_sum,)) # Default reference value shape: Treated simply as all # subelement reference values unpacked in a vector. reference_value_shape = kwargs.get('reference_value_shape', (reference_value_size_sum,)) # Validate value_shape (deliberately not for subclasses # VectorElement and TensorElement) if type(self) is MixedElement: # This is not valid for tensor elements with symmetries, # assume subclasses deal with their own validation if product(value_shape) != value_size_sum: error("Provided value_shape doesn't match the " "total value size of all subelements.") # Initialize element data degrees = {e.degree() for e in self._sub_elements} - {None} degree = max_degree(degrees) if degrees else None FiniteElementBase.__init__(self, "Mixed", cell, degree, quad_scheme, value_shape, reference_value_shape) # Cache repr string if type(self) is MixedElement: self._repr = "MixedElement(%s)" % ( ", ".join(repr(e) for e in self._sub_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 return MixedElement(*elements) def symmetry(self): """Return the symmetry dict, which is a mapping :math:`c_0 \\to c_1` meaning that component :math:`c_0` is represented by component :math:`c_1`. 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) if j != product(self.value_shape()): error("Size mismatch in symmetry algorithm.") return sm or EmptyDict def mapping(self): if all(e.mapping() == "identity" for e in self._sub_elements): return "identity" else: return "undefined" 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 if j < 0: error("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] if sub_element_index >= len(self._sub_elements): error("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 if j < 0: error("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 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 reconstruct(self, **kwargs): return MixedElement(*[e.reconstruct(**kwargs) for e in self.sub_elements()]) 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 + ">" # @six.python_2_unicode_compatible class VectorElement(MixedElement): "A special case of a mixed finite element where all elements are equal." def __init__(self, family, cell=None, degree=None, dim=None, form_degree=None, quad_scheme=None): """ Create vector element (repeated mixed element) *Arguments* family (string) The finite element family (or an existing FiniteElement) cell The geometric cell, ignored if family is a FiniteElement degree (int) The polynomial degree, ignored if family is a FiniteElement 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), ignored if family is a FiniteElement quad_scheme The quadrature scheme (optional), ignored if family is a FiniteElement """ if isinstance(family, FiniteElementBase): sub_element = family cell = sub_element.cell() else: if cell is not None: cell = as_cell(cell) # Create sub element sub_element = FiniteElement(family, cell, degree, form_degree=form_degree, quad_scheme=quad_scheme) # Set default size if not specified if dim is None: if cell is None: error("Cannot infer vector dimension without a cell.") dim = cell.geometric_dimension() # Create list of sub elements for mixed element constructor sub_elements = [sub_element]*dim # Compute value shapes value_shape = (dim,) + sub_element.value_shape() reference_value_shape = (dim,) + sub_element.reference_value_shape() # Initialize element data MixedElement.__init__(self, sub_elements, value_shape=value_shape, reference_value_shape=reference_value_shape) # FIXME: Storing this here is strange, isn't that handled by # subclass? self._family = sub_element.family() self._degree = sub_element.degree() self._sub_element = sub_element # Cache repr string self._repr = "VectorElement(%s, dim=%d)" % ( repr(sub_element), len(self._sub_elements)) def reconstruct(self, **kwargs): sub_element = self._sub_element.reconstruct(**kwargs) return VectorElement(sub_element, dim=len(self.sub_elements())) def __str__(self): "Format as string for pretty printing." return ("" % (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()) # @six.python_2_unicode_compatible class TensorElement(MixedElement): """A special case of a mixed finite element where all elements are equal. """ __slots__ = as_native_strings(("_sub_element", "_shape", "_symmetry", "_sub_element_mapping", "_flattened_sub_element_mapping", "_mapping")) def __init__(self, family, cell=None, degree=None, shape=None, symmetry=None, quad_scheme=None): """Create tensor element (repeated mixed element with optional symmetries). :arg family: The family string, or an existing FiniteElement. :arg cell: The geometric cell (ignored if family is a FiniteElement). :arg degree: The polynomial degree (ignored if family is a FiniteElement). :arg shape: The shape of the element (defaults to a square tensor given by the geometric dimension of the cell). :arg symmetry: Optional symmetries. :arg quad_scheme: Optional quadrature scheme (ignored if family is a FiniteElement).""" if isinstance(family, FiniteElementBase): sub_element = family cell = sub_element.cell() else: if cell is not None: cell = as_cell(cell) # Create scalar sub element sub_element = FiniteElement(family, cell, degree, quad_scheme=quad_scheme) if sub_element.value_shape() != (): error("Expecting only scalar valued subelement for TensorElement.") # Set default shape if not specified if shape is None: if cell is None: error("Cannot infer tensor shape without a cell.") dim = cell.geometric_dimension() shape = (dim, dim) if symmetry is None: symmetry = EmptyDict elif symmetry is True: # Construct default symmetry dict for matrix elements if not (len(shape) == 2 and shape[0] == shape[1]): error("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) else: if not isinstance(symmetry, dict): error("Expecting symmetry to be None (unset), True, or dict.") # Validate indices in symmetry dict for i, j in iteritems(symmetry): if len(i) != len(j): error("Non-matching length of symmetry index tuples.") for k in range(len(i)): if not (i[k] >= 0 and j[k] >= 0 and i[k] < shape[k] and j[k] < shape[k]): error("Symmetry dimensions out of bounds.") # Compute all index combinations for given shape indices = compute_indices(shape) # Compute mapping from indices to sub element number, # accounting for symmetry 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]] flattened_sub_element_mapping = [sub_element_mapping[index] for i, index in enumerate(indices)] # Compute value shape value_shape = shape # Compute reference value shape based on symmetries if symmetry: # Flatten and subtract symmetries reference_value_shape = (product(shape)-len(symmetry),) self._mapping = "symmetries" else: # Do not flatten if there are no symmetries reference_value_shape = shape self._mapping = "identity" # Initialize element data MixedElement.__init__(self, sub_elements, value_shape=value_shape, reference_value_shape=reference_value_shape) self._family = sub_element.family() self._degree = sub_element.degree() self._sub_element = sub_element self._shape = shape self._symmetry = symmetry self._sub_element_mapping = sub_element_mapping self._flattened_sub_element_mapping = flattened_sub_element_mapping # Cache repr string self._repr = "TensorElement(%s, shape=%s, symmetry=%s)" % ( repr(sub_element), repr(self._shape), repr(self._symmetry)) def mapping(self): if self._symmetry: return "symmetries" else: return "identity" def flattened_sub_element_mapping(self): return self._flattened_sub_element_mapping 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) # noqa: E741 ii = i[:l] jj = i[l:] if ii not in self._sub_element_mapping: error("Illegal component index %s." % (i,)) k = self._sub_element_mapping[ii] return (k, jj) def symmetry(self): """Return the symmetry dict, which is a mapping :math:`c_0 \\to c_1` meaning that component :math:`c_0` is represented by component :math:`c_1`. A component is a tuple of one or more ints.""" return self._symmetry def reconstruct(self, **kwargs): sub_element = self._sub_element.reconstruct(**kwargs) return TensorElement(sub_element, shape=self._shape, symmetry=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 ("" % (self.value_shape(), self._sub_element, 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-2017.2.0/ufl/finiteelement/__init__.py0000644000231000000010000000411713211220450017363 0ustar chrisdaemon# -*- coding: utf-8 -*- # flake8: noqa "This module defines the UFL finite element classes." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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.enrichedelement import NodalEnrichedElement from ufl.finiteelement.restrictedelement import RestrictedElement from ufl.finiteelement.tensorproductelement import TensorProductElement from ufl.finiteelement.hdivcurl import HDivElement, HCurlElement from ufl.finiteelement.brokenelement import BrokenElement from ufl.finiteelement.facetelement import FacetElement from ufl.finiteelement.interiorelement import InteriorElement # Export list for ufl.classes __all_classes__ = [ "FiniteElementBase", "FiniteElement", "MixedElement", "VectorElement", "TensorElement", "EnrichedElement", "NodalEnrichedElement", "RestrictedElement", "TensorProductElement", "HDivElement", "HCurlElement", "BrokenElement", "FacetElement", "InteriorElement", ] ufl-2017.2.0/ufl/finiteelement/finiteelementbase.py0000644000231000000010000002041013211220450021301 0ustar chrisdaemon# -*- coding: utf-8 -*- "This module defines the UFL finite element classes." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 Massimiliano Leoni, 2016 from six.moves import zip from six import string_types from ufl.utils.py23 import as_native_strings from ufl.utils.sequences import product from ufl.utils.dicts import EmptyDict from ufl.log import error from ufl.cell import AbstractCell, as_cell class FiniteElementBase(object): "Base class for all finite elements." __slots__ = as_native_strings(("_family", "_cell", "_degree", "_quad_scheme", "_value_shape", "_reference_value_shape", "_repr", "__weakref__")) # TODO: Not all these should be in the base class! In particular # family, degree, and quad_scheme do not belong here. def __init__(self, family, cell, degree, quad_scheme, value_shape, reference_value_shape): "Initialize basic finite element data." if not isinstance(family, string_types): error("Invalid family type.") if not (degree is None or isinstance(degree, (int, tuple))): error("Invalid degree type.") if not isinstance(value_shape, tuple): error("Invalid value_shape type.") if not isinstance(reference_value_shape, tuple): error("Invalid reference_value_shape type.") if cell is not None: cell = as_cell(cell) if not isinstance(cell, AbstractCell): error("Invalid cell type.") self._family = family self._cell = cell 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. NB! Assumes subclass has assigned its repr string to self._repr. """ return self._repr def __unicode__(self): # Only in python 2 return str(self).decode("utf-8") def _ufl_hash_data_(self): return repr(self) def _ufl_signature_data_(self): return repr(self) def __hash__(self): "Compute hash code for insertion in hashmaps." return hash(self._ufl_hash_data_()) def __eq__(self, other): "Compute element equality for insertion in hashmaps." return type(self) == type(other) and self._ufl_hash_data_() == other._ufl_hash_data_() def __ne__(self, other): "Compute element inequality for insertion in hashmaps." return not self.__eq__(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): "Not implemented." error("Missing implementation of mapping().") def cell(self): "Return cell of finite element." return self._cell 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 value_size(self): "Return the integer product of the value shape." return product(self.value_shape()) def reference_value_size(self): "Return the integer product of the reference value shape." return product(self.reference_value_shape()) def symmetry(self): # FIXME: different approach """Return the symmetry dict, which is a mapping :math:`c_0 \\to c_1` meaning that component :math:`c_0` is represented by component :math:`c_1`. A component is a tuple of one or more ints.""" 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 '%s' (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 '%s' (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" if not isinstance(other, FiniteElementBase): error("Can't add element and %s." % other.__class__) from ufl.finiteelement import EnrichedElement return EnrichedElement(self, other) def __mul__(self, other): "Multiply two elements, creating a mixed element" if not isinstance(other, FiniteElementBase): error("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)." if index in ("facet", "interior"): from ufl.finiteelement import RestrictedElement return RestrictedElement(self, index) return NotImplemented ufl-2017.2.0/ufl/finiteelement/facetelement.py0000644000231000000010000000210213211220450020250 0ustar chrisdaemon# -*- coding: utf-8 -*- # Copyright (C) 2017 Miklós Homolya # # 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.restrictedelement import RestrictedElement from ufl.log import deprecate def FacetElement(element): """Constructs the restriction of a finite element to the facets of the cell.""" deprecate('FacetElement(element) is deprecated, please use element["facet"] instead.') return RestrictedElement(element, restriction_domain="facet") ufl-2017.2.0/ufl/finiteelement/brokenelement.py0000644000231000000010000000365113211220450020460 0ustar chrisdaemon# -*- coding: utf-8 -*- # 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 . # # Modified by Massimiliano Leoni, 2016 # import six from ufl.utils.py23 import as_native_str from ufl.finiteelement.finiteelementbase import FiniteElementBase # @six.python_2_unicode_compatible class BrokenElement(FiniteElementBase): """The discontinuous version of an existing Finite Element space.""" def __init__(self, element): self._element = element self._repr = as_native_str("BrokenElement(%s)" % repr(element)) family = "BrokenElement" cell = element.cell() degree = element.degree() quad_scheme = element.quadrature_scheme() value_shape = element.value_shape() reference_value_shape = element.reference_value_shape() FiniteElementBase.__init__(self, family, cell, degree, quad_scheme, value_shape, reference_value_shape) def mapping(self): return self._element.mapping() def reconstruct(self, **kwargs): return BrokenElement(self._element.reconstruct(**kwargs)) def __str__(self): return "BrokenElement(%s)" % str(self._element) def shortstr(self): "Format as string for pretty printing." return "BrokenElement(%s)" % str(self._element.shortstr()) ufl-2017.2.0/ufl/finiteelement/elementlist.py0000644000231000000010000004047113211220450020154 0ustar chrisdaemon# -*- coding: utf-8 -*- """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-2016 Martin Sandve Alnæs 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, 2016 # Modified by Massimiliano Leoni, 2016 from __future__ import print_function from numpy import asarray from ufl.log import warning, error from ufl.sobolevspace import L2, H1, H2, HDiv, HCurl, HEin, HDivDiv from ufl.utils.formatting import istr from ufl.cell import Cell, TensorProductCell # 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." if family in ufl_elements: error('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." if family in ufl_elements: error('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(): "Shows all registered 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'" % (family, 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("Discontinuous Taylor", "TDG", 0, L2, "identity", (0, None), simplices) 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 + ("TensorProductCell",)) register_element("Undefined", "U", 0, L2, "identity", (0, None), any_cell) register_element("Radau", "Rad", 0, L2, "identity", (0, None), ("interval",)) register_element("Regge", "Regge", 2, HEin, "double covariant Piola", (0, None), simplices[1:]) register_element("HDiv Trace", "HDivT", 0, L2, "identity", (0, None), any_cell) register_element("Hellan-Herrmann-Johnson", "HHJ", 2, HDivDiv, "double contravariant Piola", (0, None), ("triangle",)) # Spectral elements. register_element("Gauss-Legendre", "GL", 0, L2, "identity", (0, None), ("interval",)) register_element("Gauss-Lobatto-Legendre", "GLL", 0, H1, "identity", (1, None), ("interval",)) register_alias("Lobatto", lambda family, dim, order, degree: ("Gauss-Lobatto-Legendre", order)) register_alias("Lob", lambda family, dim, order, degree: ("Gauss-Lobatto-Legendre", order)) # 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)) # Let Discontinuous Lagrange Trace element be alias to HDiv Trace register_alias("Discontinuous Lagrange Trace", lambda family, dim, order, degree: ("HDiv Trace", order)) register_alias("DGT", lambda family, dim, order, degree: ("HDiv Trace", 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)) def feec_element(family, n, r, k): """Finite element exterior calculus notation 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("Q-", 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() if isinstance(cell, Cell): cellname = cell.cellname() else: cellname = None 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: if tdim is None: error("Need dimension to handle element aliases.") (family, order) = aliases[family](family, tdim, order, form_degree) # Check that the element family exists if family not in ufl_elements: error('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] # Accept CG/DG on all kind of cells, but use Q/DQ on "product" cells if cellname in set(cubes) - set(simplices) or isinstance(cell, TensorProductCell): if family == "Lagrange": family = "Q" elif family == "Discontinuous Lagrange": if order >= 1: warning("Discontinuous Lagrange element requested on %s, creating DQ element." % cell.cellname()) family = "DQ" # Validate cellname if a valid cell is specified if not (cellname is None or cellname in cellnames): error('Cellname "%s" invalid for "%s" finite element.' % (cellname, family)) # Validate order if specified if order is not None: if krange is None: error('Order "%s" invalid for "%s" finite element, ' 'should be None.' % (order, family)) kmin, kmax = krange if not (kmin is None or (asarray(order) >= kmin).all()): error('Order "%s" invalid for "%s" finite element.' % (order, family)) if not (kmax is None or (asarray(order) <= kmax).all()): error('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 if gdim is None or tdim is None: error("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 if gdim is None or tdim is None: error("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-2017.2.0/ufl/finiteelement/enrichedelement.py0000644000231000000010000001364513211220450020765 0ustar chrisdaemon# -*- coding: utf-8 -*- "This module defines the UFL finite element classes." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 Massimiliano Leoni, 2016 # import six from ufl.utils.py23 import as_native_str from six.moves import zip from ufl.log import error from ufl.finiteelement.finiteelementbase import FiniteElementBase # @six.python_2_unicode_compatible class EnrichedElementBase(FiniteElementBase): """The vector sum of several finite element spaces: .. math:: \\textrm{EnrichedElement}(V, Q) = \\{v + q | v \\in V, q \\in Q\\}. """ def __init__(self, *elements): self._elements = elements cell = elements[0].cell() if not all(e.cell() == cell for e in elements[1:]): error("Cell 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 if not all(qs == quad_scheme for qs in quad_schemes): error("Quadrature scheme mismatch.") value_shape = elements[0].value_shape() if not all(e.value_shape() == value_shape for e in elements[1:]): error("Element value shape mismatch.") reference_value_shape = elements[0].reference_value_shape() if not all(e.reference_value_shape() == reference_value_shape for e in elements[1:]): error("Element reference value shape mismatch.") # mapping = elements[0].mapping() # FIXME: This fails for a mixed subelement here. # if not all(e.mapping() == mapping for e in elements[1:]): # error("Element mapping mismatch.") # Get name of subclass: EnrichedElement or NodalEnrichedElement class_name = as_native_str(self.__class__.__name__) # Initialize element data FiniteElementBase.__init__(self, class_name, cell, degree, quad_scheme, value_shape, reference_value_shape) # Cache repr string self._repr = as_native_str("%s(%s)" % (class_name, ", ".join(repr(e) for e in self._elements))) def mapping(self): return self._elements[0].mapping() def sobolev_space(self): "Return the underlying Sobolev space." elements = [e for e in self._elements] if all(e.sobolev_space() == elements[0].sobolev_space() for e in elements): return elements[0].sobolev_space() else: # Find smallest shared Sobolev space over all sub elements spaces = [e.sobolev_space() for e in elements] superspaces = [{s} | set(s.parents) for s in spaces] intersect = set.intersection(*superspaces) for s in intersect.copy(): for parent in s.parents: intersect.discard(parent) sobolev_space, = intersect return sobolev_space def reconstruct(self, **kwargs): return type(self)(*[e.reconstruct(**kwargs) for e in self._elements]) class EnrichedElement(EnrichedElementBase): """The vector sum of several finite element spaces: .. math:: \\textrm{EnrichedElement}(V, Q) = \\{v + q | v \\in V, q \\in Q\\}. Dual basis is a concatenation of subelements dual bases; primal basis is a concatenation of subelements primal bases; resulting element is not nodal even when subelements are. Structured basis may be exploited in form compilers. """ 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 __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) class NodalEnrichedElement(EnrichedElementBase): """The vector sum of several finite element spaces: .. math:: \\textrm{EnrichedElement}(V, Q) = \\{v + q | v \\in V, q \\in Q\\}. Primal basis is reorthogonalized to dual basis which is a concatenation of subelements dual bases; resulting element is nodal. """ def is_cellwise_constant(self): """Return whether the basis functions of this element is spatially constant over each cell.""" return False def __str__(self): "Format as string for pretty printing." return "" % ", ".join(str(e) for e in self._elements) def shortstr(self): "Format as string for pretty printing." return "NodalEnriched(%s)" % ", ".join(e.shortstr() for e in self._elements) ufl-2017.2.0/ufl/finiteelement/interiorelement.py0000644000231000000010000000212013211220450021021 0ustar chrisdaemon# -*- coding: utf-8 -*- # Copyright (C) 2017 Miklós Homolya # # 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.restrictedelement import RestrictedElement from ufl.log import deprecate def InteriorElement(element): """Constructs the restriction of a finite element to the interior of the cell.""" deprecate('InteriorElement(element) is deprecated, please use element["interior"] instead.') return RestrictedElement(element, restriction_domain="interior") ufl-2017.2.0/ufl/finiteelement/restrictedelement.py0000644000231000000010000001001113211220450021334 0ustar chrisdaemon# -*- coding: utf-8 -*- "This module defines the UFL finite element classes." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 Massimiliano Leoni, 2016 from ufl.utils.py23 import as_native_str from ufl.finiteelement.finiteelementbase import FiniteElementBase from ufl.log import error valid_restriction_domains = ("interior", "facet", "face", "edge", "vertex") class RestrictedElement(FiniteElementBase): "Represents the restriction of a finite element to a type of cell entity." def __init__(self, element, restriction_domain): if not isinstance(element, FiniteElementBase): error("Expecting a finite element instance.") if restriction_domain not in valid_restriction_domains: error("Expecting one of the strings %s." % (valid_restriction_domains,)) FiniteElementBase.__init__(self, "RestrictedElement", element.cell(), element.degree(), element.quadrature_scheme(), element.value_shape(), element.reference_value_shape()) self._element = element self._restriction_domain = restriction_domain self._repr = as_native_str("RestrictedElement(%s, %s)" % ( repr(self._element), repr(self._restriction_domain))) 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 sub_element(self): "Return the element which is restricted." return self._element def mapping(self): return self._element.mapping() def restriction_domain(self): "Return the domain onto which the element is restricted." return self._restriction_domain def reconstruct(self, **kwargs): element = self._element.reconstruct(**kwargs) return RestrictedElement(element, self._restriction_domain) def __str__(self): "Format as string for pretty printing." return "<%s>|_{%s}" % (self._element, self._restriction_domain) def shortstr(self): "Format as string for pretty printing." return "<%s>|_{%s}" % (self._element.shortstr(), self._restriction_domain) def symmetry(self): """Return the symmetry dict, which is a mapping :math:`c_0 \\to c_1` meaning that component :math:`c_0` is represented by component :math:`c_1`. A component is a tuple of one or more ints. """ return self._element.symmetry() def num_sub_elements(self): "Return number of sub elements." return self._element.num_sub_elements() def sub_elements(self): "Return list of sub elements." return self._element.sub_elements() 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,) ufl-2017.2.0/ufl/form.py0000644000231000000010000004537413211220450013751 0ustar chrisdaemon# -*- coding: utf-8 -*- "The Form class." # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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. # Modified by Massimiliano Leoni, 2016. from itertools import chain from collections import defaultdict from ufl.log import error, warning from ufl.domain import sort_domains from ufl.integral import Integral from ufl.checks import is_scalar_constant_expression from ufl.equation import Equation from ufl.core.expr import Expr from ufl.core.expr import ufl_err_str from ufl.constantvalue import Zero from ufl.utils.py23 import as_native_strings, as_native_str # Export list for ufl.classes __all_classes__ = as_native_strings(["Form"]) # --- 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.ufl_domain() if d is None: error("Each integral in a form must have a uniquely defined integration domain.") 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 sort_domains(integrals_dict): 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) if not all(isinstance(itg, Integral) for itg in integrals): error("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 empty(self): "Returns whether the form has no integrals." return self.integrals() == () def ufl_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 ufl_cell(self): """Return the single cell this form is defined on, fails if multiple cells are found. """ return self.ufl_domain().ufl_cell() def ufl_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. """ # Collect all domains domains = self.ufl_domains() # Check that all are equal TODO: don't return more than one if # all are equal? if not all(domain == domains[0] for domain in domains): error("Calling Form.ufl_domain() is only valid if all integrals share domain.") # Return the one and only domain return domains[0] def geometric_dimension(self): "Return the geometric dimension shared by all domains and functions in this form." gdims = tuple(set(domain.geometric_dimension() for domain in self.ufl_domains())) if len(gdims) != 1: error("Expecting all domains and functions in a form " "to share geometric dimension, got %s." % str(tuple(sorted(gdims)))) return gdims[0] 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 == 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 __ne__(self, other): "Immediate evaluation of the != operator (as opposed to the == operator)." return not self.equals(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 __rsub__(self, other): "Subtract this form from other." return other + (-self) 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): from ufl.formoperators import action return action(self, coefficient) return NotImplemented def __call__(self, *args, **kwargs): """UFL form operator: Evaluate form by replacing arguments and coefficients. Replaces form.arguments() with given positional arguments in same number and ordering. Number of positional arguments must be 0 or equal to the number of Arguments in the form. The optional keyword argument coefficients can be set to a dict to replace Coefficients with expressions of matching shapes. Example: V = FiniteElement("CG", triangle, 1) v = TestFunction(V) u = TrialFunction(V) f = Coefficient(V) g = Coefficient(V) a = g*inner(grad(u), grad(v))*dx M = a(f, f, coefficients={ g: 1 }) Is equivalent to M == grad(f)**2*dx. """ repdict = {} if args: arguments = self.arguments() if len(arguments) != len(args): error("Need %d arguments to form(), got %d." % (len(arguments), len(args))) repdict.update(zip(arguments, args)) coefficients = kwargs.pop("coefficients") if kwargs: error("Unknown kwargs %s." % str(list(kwargs))) if coefficients is not None: coeffs = self.coefficients() for f in coefficients: if f in coeffs: repdict[f] = coefficients[f] else: warning("Coefficient %s is not in form." % ufl_err_str(f)) if repdict: from ufl.formoperators import replace return replace(self, repdict) else: return self # "a @ f" notation in python 3.5 __matmul__ = __mul__ # --- String conversion functions, for UI purposes only --- def __unicode__(self): # Only in python 2 return str(self).decode("utf-8") def __str__(self): "Compute shorter string representation of form. This can be huge for complicated forms." # Warning used for making sure we don't use this in the general pipeline: # warning("Calling str on form is potentially expensive and should be avoided except during debugging.") # Not caching this because it can be huge 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." # Warning used for making sure we don't use this in the general pipeline: # warning("Calling repr on form is potentially expensive and should be avoided except during debugging.") # Not caching this because it can be huge itgs = ", ".join(repr(itg) for itg in self.integrals()) r = "Form([" + itgs + "])" return as_native_str(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): from ufl.domain import join_domains, sort_domains # Collect unique integration domains integration_domains = join_domains([itg.ufl_domain() for itg in self._integrals]) # Make canonically ordered list of the domains self._integration_domains = sort_domains(integration_domains) # 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.ufl_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.ufl_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: if data.ufl_id() != sd.ufl_id(): error("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) # 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.ufl_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" % ufl_err_str(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, sometimes occurring in pydolfin, e.g. assemble(1*dx, mesh=mesh). """ domains = form.ufl_domains() if common_domain is not None: gdim = common_domain.geometric_dimension() tdim = common_domain.topological_dimension() if not all((gdim == domain.geometric_dimension() and tdim == domain.topological_dimension()) for domain in domains): error("Common domain does not share dimensions with form domains.") reconstruct = False integrals = [] for itg in form.integrals(): domain = itg.ufl_domain() if domain != common_domain: itg = itg.reconstruct(domain=common_domain) reconstruct = True integrals.append(itg) if reconstruct: form = Form(integrals) return form ufl-2017.2.0/ufl/sorting.py0000644000231000000010000001353213211220450014462 0ustar chrisdaemon# -*- coding: utf-8 -*- """This module contains a sorting rule for expr objects that is more robust w.r.t. argument numbering than using repr.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 six.moves import zip from functools import cmp_to_key from ufl.core.expr import Expr from ufl.argument import Argument from ufl.coefficient import Coefficient from ufl.core.multiindex import FixedIndex, MultiIndex from ufl.variable import Label def _cmp_multi_index(a, b): # Careful not to depend on Index.count() here! # This is placed first because it is most frequent. # 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 def _cmp_label(a, b): # 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 def _cmp_coefficient(a, b): # 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 def _cmp_argument(a, b): # 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 def _cmp_terminal_by_repr(a, b): # The cost of repr on a terminal is fairly small, and bounded x = repr(a) y = repr(b) return -1 if x < y else (0 if x == y else +1) # Hack up a MultiFunction-like type dispatch for terminal comparisons _terminal_cmps = [_cmp_terminal_by_repr]*Expr._ufl_num_typecodes_ _terminal_cmps[MultiIndex._ufl_typecode_] = _cmp_multi_index _terminal_cmps[Argument._ufl_typecode_] = _cmp_argument _terminal_cmps[Coefficient._ufl_typecode_] = _cmp_coefficient _terminal_cmps[Label._ufl_typecode_] = _cmp_label def cmp_expr(a, b): "Replacement for cmp(a, b), removed in Python 3, for Expr objects." # 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 if x < y else +1 # Now we know that the type is the same, check further based # on type specific properties. if a._ufl_is_terminal_: c = _terminal_cmps[x](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 types, 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 if x < y else +1 # Equal if we get out of the above loop! return 0 def sorted_expr(sequence): "Return a canonically sorted list of Expr objects in sequence." return sorted(sequence, key=cmp_to_key(cmp_expr)) def sorted_expr_sum(seq): seq2 = sorted(seq, key=cmp_to_key(cmp_expr)) s = seq2[0] for e in seq2[1:]: s = s + e return s ufl-2017.2.0/ufl/protocols.py0000644000231000000010000000261313211220450015017 0ustar chrisdaemon# -*- coding: utf-8 -*- # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 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: 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-2017.2.0/ufl/corealg/0000755000231000000010000000000013211220450014033 5ustar chrisdaemonufl-2017.2.0/ufl/corealg/traversal.py0000644000231000000010000001345213211220450016415 0ustar chrisdaemon# -*- coding: utf-8 -*- """Various expression traversal utilities. The algorithms here are non-recursive, which is faster than recursion by a factor of 10 or so because of the function call overhead. """ # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 Massimiliano Leoni, 2016 # This limits the _depth_ of expression trees _recursion_limit_ = 6400 # should be enough for everyone def pre_traversal(expr): """Yield ``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): """Yield ``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): """Yield ``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): """Yield ``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 if visited is None: visited = 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): """Yield ``o`` for each node ``o`` in *expr*, child before parent. Never visit a node twice.""" stack = [None]*_recursion_limit_ stack[0] = (expr, list(expr.ufl_operands)) stacksize = 1 if visited is None: visited = 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 cutoff_unique_post_traversal(expr, cutofftypes, visited=None): """Yield ``o`` for each node ``o`` in *expr*, child before parent. Never visit a node twice.""" stack = [None]*_recursion_limit_ stack[0] = (expr, () if cutofftypes[expr._ufl_typecode_] else list(expr.ufl_operands)) stacksize = 1 if visited is None: visited = 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, () if cutofftypes[o._ufl_typecode_] else 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 *expr*, 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, visited=None): "Iterate over all terminal objects in *expr*, not including duplicates." stack = [None]*_recursion_limit_ stack[0] = expr stacksize = 1 if visited is None: 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-2017.2.0/ufl/corealg/__init__.py0000644000231000000010000000000013211220450016132 0ustar chrisdaemonufl-2017.2.0/ufl/corealg/multifunction.py0000644000231000000010000001073213211220450017310 0ustar chrisdaemon# -*- coding: utf-8 -*- """Base class for multifunctions with UFL ``Expr`` type dispatch.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # 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 Massimiliano Leoni, 2016 from inspect import getargspec from ufl.log import error from ufl.core.expr import Expr def get_num_args(function): "Return the number of arguments accepted by *function*." insp = getargspec(function) return len(insp[0]) + int(insp[1] is not None) def memoized_handler(handler): "Function decorator to memoize ``MultiFunction`` handlers." def _memoized_handler(self, o): c = getattr(self, "_memoized_handler_cache") r = c.get(o) if r is None: r = handler(self, o) c[o] = r return r return _memoized_handler class MultiFunction(object): """Base class for collections of non-recursive 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 Python's function call overhead 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: handler_names = [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 handler_name = c._ufl_handler_name_ if hasattr(self, handler_name): handler_names[classobject._ufl_typecode_] = handler_name break is_cutoff_type = [get_num_args(getattr(self, name)) == 2 for name in handler_names] cache_data = (handler_names, is_cutoff_type) MultiFunction._handlers_cache[algorithm_class] = cache_data # Build handler list for this particular class (get functions # bound to self, these cannot be cached) handler_names, is_cutoff_type = cache_data self._handlers = [getattr(self, name) for name in handler_names] self._is_cutoff_type = is_cutoff_type # Create cache for memoized_handler self._memoized_handler_cache = {} 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._ufl_expr_reconstruct_(*ops) # Set default behaviour for any Expr as undefined expr = undefined ufl-2017.2.0/ufl/corealg/map_dag.py0000644000231000000010000000767613211220450016015 0ustar chrisdaemon# -*- coding: utf-8 -*- """Basic algorithms for applying functions to subexpressions.""" # Copyright (C) 2014-2016 Martin Sandve Alnæs # # 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 Massimiliano Leoni, 2016 from ufl.core.expr import Expr from ufl.corealg.traversal import unique_post_traversal, cutoff_unique_post_traversal from ufl.corealg.multifunction import MultiFunction def map_expr_dag(function, expression, compress=True): """Apply a function to each subexpression node in an 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. Return the result of the final function call. """ result, = map_expr_dags(function, [expression], compress=compress) return result def map_expr_dags(function, expressions, compress=True): """Apply a function to each subexpression node in an 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. Return a list with the result of the final function call for each expression. """ # Temporary data structures vcache = {} # expr -> r = function(expr,...), cache of intermediate results rcache = {} # r -> r, cache of result objects for memory reuse # Build mapping typecode:bool, for which types to skip the subtree of if isinstance(function, MultiFunction): cutoff_types = function._is_cutoff_type handlers = function._handlers # Optimization else: # Regular function: no skipping supported cutoff_types = [False]*Expr._ufl_num_typecodes_ handlers = [function]*Expr._ufl_num_typecodes_ # Create visited set here to share between traversal calls visited = set() # Pick faster traversal algorithm if we have no cutoffs if any(cutoff_types): def traversal(expression): return cutoff_unique_post_traversal(expression, cutoff_types, visited) else: def traversal(expression): return unique_post_traversal(expression, visited) for expression in expressions: # Iterate over all subexpression nodes, child before parent for v in traversal(expression): # Skip transformations on cache hit if v in vcache: continue # Cache miss: Get transformed operands, then apply transformation if cutoff_types[v._ufl_typecode_]: r = handlers[v._ufl_typecode_](v) else: r = handlers[v._ufl_typecode_](v, *[vcache[u] for u in v.ufl_operands]) # Optionally check if r is in rcache, a memory optimization # to be able to keep representation of result compact if compress: r2 = rcache.get(r) if r2 is None: # Cache miss: store in rcache rcache[r] = r else: # Cache hit: Use previously computed object r2, # allowing r to be garbage collected as soon as possible r = r2 # Store result in cache vcache[v] = r return [vcache[expression] for expression in expressions] ufl-2017.2.0/.travis.yml0000644000231000000010000000160413211220450013743 0ustar chrisdaemonnotifications: slack: secure: r9t4/J1Y85Tv/D/bJBVPGaW153mSU+I5r+EZrq7E6HnYqFRQ0X0AJw0pUX1ap/MXGtSrHFmQRYmIN1TTUMLPAHlzF4YA4rFA1LxJxxlPQ1TuhmkLbu5DEf5qkSsVeOjIe8+ptx8E8rSUSVfzv1g/RpdgO6mOD6qge94gQhQ4XekrWkZE1OudkqPvHfznCvxtFs3T5dxZWX3suQsGA+l1Gn3gp2YFITTGEfCXFek+rRWL3aqn6Oq+nvI6jfrooei7NSmNQOpGawAW868uvOlWQ2NiH1nsUkOH9U9c4YpTPD7RLLc2r3pAisSzdhZ5LsT+9o+lycqJleGizcVqtF7BP0tBzc1G4uJQwOCCcypLG62VwuwF6HD3zx4bDXD2MTqEneUaLVNlT2JiAsXmg2RKn/z8+5OKrPNe23pDvD3h+NdtHzJxPSnSRynpKD1FBXh1tfiGSVvgY+Mm7Bdu1unuK2tbpb6OLhcq50kYzqW1sbH8GEceb2v10YWV1tWDQ/JORFUtSuM3DFLlTO5XFjTCHqcGMkPQnpmXo+KF4D4uMHmkqVQu1aHdWn99uxoJzyoQrYwa7+94Z/UJuhwSCc+HOr28vXfd5O9ouo3E4FlEMMaAeRiCIGh88Ota6x+7BHmihijroKb9NdB2J2E0HpbLtEQha1yWIsuF0UbWywCojo4= language: python python: - "2.7" - "3.4" before_install: - pip install flake8 - pip install pytest install: - pip install . script: - flake8 ufl/ - py.test test/ ufl-2017.2.0/setup.py0000755000231000000010000000511413211220450013347 0ustar chrisdaemon# -*- coding: utf-8 -*- from __future__ import print_function from setuptools import setup from os.path import join, split import sys import platform module_name = "ufl" if sys.version_info < (2, 7): print("Python 2.7 or higher required, please upgrade.") sys.exit(1) version = "2017.2.0" url = "https://bitbucket.org/fenics-project/%s/" % module_name tarball = None if 'dev' not in version: tarball = url + "downloads/fenics-%s-%s.tar.gz" % (module_name, version) script_names = ("ufl-analyse", "ufl-convert", "ufl-version", "ufl2py") scripts = [join("scripts", script) for script in script_names] man_files = [join("doc", "man", "man1", "%s.1.gz" % (script,)) for script in script_names] data_files = [(join("share", "man", "man1"), man_files)] 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" with open(batch_file, "w") as f: f.write(sys.executable + ' "%%~dp0\%s" %%*' % split(script)[1]) batch_files.append(batch_file) scripts.extend(batch_files) CLASSIFIERS = """\ Development Status :: 5 - Production/Stable Intended Audience :: Developers Intended Audience :: Science/Research License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+) Operating System :: POSIX Operating System :: POSIX :: Linux Operating System :: MacOS :: MacOS X Operating System :: Microsoft :: Windows Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Topic :: Scientific/Engineering :: Mathematics Topic :: Software Development :: Libraries :: Python Modules """ setup(name="fenics-ufl", version=version, description="Unified Form Language", author="Martin Sandve Alnæs, Anders Logg", author_email="fenics-dev@googlegroups.com", url=url, download_url=tarball, classifiers=[_f for _f in CLASSIFIERS.split('\n') if _f], scripts=scripts, packages=[ "ufl", "ufl.utils", "ufl.finiteelement", "ufl.core", "ufl.corealg", "ufl.algorithms", "ufl.formatting", ], package_dir={"ufl": "ufl"}, install_requires=["numpy", "six"], data_files=data_files )